You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
12KB

  1. #include <rack.hpp>
  2. #include <osdialog.h>
  3. #include <iostream>
  4. #include <fstream>
  5. #include <sstream>
  6. #include <mutex>
  7. #include "ScriptEngine.hpp"
  8. using namespace rack;
  9. Plugin* pluginInstance;
  10. struct Prototype : Module {
  11. enum ParamIds {
  12. ENUMS(KNOB_PARAMS, NUM_ROWS),
  13. ENUMS(SWITCH_PARAMS, NUM_ROWS),
  14. NUM_PARAMS
  15. };
  16. enum InputIds {
  17. ENUMS(IN_INPUTS, NUM_ROWS),
  18. NUM_INPUTS
  19. };
  20. enum OutputIds {
  21. ENUMS(OUT_OUTPUTS, NUM_ROWS),
  22. NUM_OUTPUTS
  23. };
  24. enum LightIds {
  25. ENUMS(LIGHT_LIGHTS, NUM_ROWS * 3),
  26. ENUMS(SWITCH_LIGHTS, NUM_ROWS * 3),
  27. NUM_LIGHTS
  28. };
  29. std::string message;
  30. std::string path;
  31. std::string script;
  32. std::string engineName;
  33. std::mutex scriptMutex;
  34. ScriptEngine* scriptEngine = NULL;
  35. int frame = 0;
  36. int frameDivider;
  37. Prototype() {
  38. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  39. for (int i = 0; i < NUM_ROWS; i++)
  40. configParam(KNOB_PARAMS + i, 0.f, 1.f, 0.f, string::f("Knob %d", i + 1));
  41. for (int i = 0; i < NUM_ROWS; i++)
  42. configParam(SWITCH_PARAMS + i, 0.f, 1.f, 0.f, string::f("Switch %d", i + 1));
  43. clearScriptEngine();
  44. }
  45. ~Prototype() {
  46. std::lock_guard<std::mutex> lock(scriptMutex);
  47. clearScriptEngine();
  48. }
  49. void process(const ProcessArgs& args) override {
  50. if (!scriptEngine)
  51. return;
  52. // Frame divider for reducing sample rate
  53. if (++frame < frameDivider)
  54. return;
  55. frame = 0;
  56. ScriptEngine::ProcessArgs scriptArgs;
  57. scriptArgs.sampleRate = args.sampleRate;
  58. scriptArgs.sampleTime = args.sampleTime;
  59. {
  60. std::lock_guard<std::mutex> lock(scriptMutex);
  61. // Check for certain inside the mutex
  62. if (scriptEngine) {
  63. if (scriptEngine->process(scriptArgs)) {
  64. clearScriptEngine();
  65. return;
  66. }
  67. }
  68. }
  69. }
  70. void clearScriptEngine() {
  71. if (scriptEngine) {
  72. delete scriptEngine;
  73. scriptEngine = NULL;
  74. }
  75. // Reset outputs and lights because they might hold old values
  76. for (int i = 0; i < NUM_ROWS; i++)
  77. outputs[OUT_OUTPUTS + i].setVoltage(0.f);
  78. for (int i = 0; i < NUM_ROWS; i++)
  79. for (int c = 0; c < 3; c++)
  80. lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(0.f);
  81. for (int i = 0; i < NUM_ROWS; i++)
  82. for (int c = 0; c < 3; c++)
  83. lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(0.f);
  84. // Reset settings
  85. frameDivider = 32;
  86. frame = 0;
  87. }
  88. void setScriptString(std::string path, std::string script) {
  89. std::lock_guard<std::mutex> lock(scriptMutex);
  90. message = "";
  91. this->path = "";
  92. this->script = "";
  93. this->engineName = "";
  94. clearScriptEngine();
  95. // Get ScriptEngine from path extension
  96. if (path == "") {
  97. return;
  98. }
  99. INFO("Loading script %s", path.c_str());
  100. std::string ext = string::filenameExtension(string::filename(path));
  101. scriptEngine = createScriptEngine(ext);
  102. if (!scriptEngine) {
  103. message = string::f("No engine for .%s extension", ext.c_str());
  104. return;
  105. }
  106. this->path = path;
  107. this->script = script;
  108. this->engineName = scriptEngine->getEngineName();
  109. scriptEngine->module = this;
  110. // Read file
  111. std::ifstream file;
  112. file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  113. try {
  114. file.open(this->path);
  115. std::stringstream buffer;
  116. buffer << file.rdbuf();
  117. this->script = buffer.str();
  118. }
  119. catch (const std::runtime_error& err) {
  120. WARN("Script %s not found, using stored script string", this->path.c_str());
  121. }
  122. // Run script
  123. if (this->script == "") {
  124. message = "Could not load script.";
  125. clearScriptEngine();
  126. return;
  127. }
  128. if (scriptEngine->run(this->path, this->script)) {
  129. // Error message should have been set by ScriptEngine
  130. clearScriptEngine();
  131. return;
  132. }
  133. INFO("Successfully ran script %s", this->path.c_str());
  134. }
  135. json_t* dataToJson() override {
  136. json_t* rootJ = json_object();
  137. json_object_set_new(rootJ, "path", json_string(path.c_str()));
  138. json_object_set_new(rootJ, "script", json_stringn(script.data(), script.size()));
  139. return rootJ;
  140. }
  141. void dataFromJson(json_t* rootJ) override {
  142. json_t* pathJ = json_object_get(rootJ, "path");
  143. json_t* scriptJ = json_object_get(rootJ, "script");
  144. if (pathJ && scriptJ) {
  145. std::string path = json_string_value(pathJ);
  146. std::string script = std::string(json_string_value(scriptJ), json_string_length(scriptJ));
  147. setScriptString(path, script);
  148. }
  149. }
  150. void reloadScript() {
  151. setScriptString(path, script);
  152. }
  153. };
  154. void ScriptEngine::setMessage(const std::string& message) {
  155. module->message = message;
  156. }
  157. int ScriptEngine::getFrameDivider() {
  158. return module->frameDivider;
  159. }
  160. void ScriptEngine::setFrameDivider(int frameDivider) {
  161. module->frameDivider = frameDivider;
  162. }
  163. float ScriptEngine::getInput(int index) {
  164. return module->inputs[Prototype::IN_INPUTS + index].getVoltage();
  165. }
  166. void ScriptEngine::setOutput(int index, float voltage) {
  167. module->outputs[Prototype::OUT_OUTPUTS + index].setVoltage(voltage);
  168. }
  169. float ScriptEngine::getKnob(int index) {
  170. return module->params[Prototype::KNOB_PARAMS + index].getValue();
  171. }
  172. bool ScriptEngine::getSwitch(int index) {
  173. return module->params[Prototype::SWITCH_PARAMS + index].getValue() > 0.f;
  174. }
  175. void ScriptEngine::setLight(int index, int color, float brightness) {
  176. module->lights[Prototype::LIGHT_LIGHTS + index * 3 + color].setBrightness(brightness);
  177. }
  178. void ScriptEngine::setSwitchLight(int index, int color, float brightness) {
  179. module->lights[Prototype::SWITCH_LIGHTS + index * 3 + color].setBrightness(brightness);
  180. }
  181. struct FileChoice : LedDisplayChoice {
  182. Prototype* module;
  183. void step() override {
  184. if (module && module->engineName != "")
  185. text = module->engineName;
  186. else
  187. text = "Script";
  188. text += ": ";
  189. if (module && module->path != "")
  190. text += string::filename(module->path);
  191. else
  192. text += "(click to load)";
  193. }
  194. void onAction(const event::Action& e) override {
  195. std::string dir = asset::user("");
  196. char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL);
  197. if (!pathC) {
  198. return;
  199. }
  200. std::string path = pathC;
  201. std::free(pathC);
  202. module->setScriptString(path, "");
  203. }
  204. };
  205. struct MessageChoice : LedDisplayChoice {
  206. Prototype* module;
  207. void step() override {
  208. text = module ? module->message : "";
  209. }
  210. };
  211. struct PrototypeDisplay : LedDisplay {
  212. PrototypeDisplay() {
  213. box.size = mm2px(Vec(69.879, 27.335));
  214. }
  215. void setModule(Prototype* module) {
  216. FileChoice* fileChoice = new FileChoice;
  217. fileChoice->box.size.x = box.size.x;
  218. fileChoice->module = module;
  219. addChild(fileChoice);
  220. LedDisplaySeparator* fileSeparator = new LedDisplaySeparator;
  221. fileSeparator->box.size.x = box.size.x;
  222. fileSeparator->box.pos = fileChoice->box.getBottomLeft();
  223. addChild(fileSeparator);
  224. MessageChoice* messageChoice = new MessageChoice;
  225. messageChoice->box.pos = fileChoice->box.getBottomLeft();
  226. messageChoice->box.size.x = box.size.x;
  227. messageChoice->module = module;
  228. addChild(messageChoice);
  229. }
  230. };
  231. struct ReloadScriptItem : MenuItem {
  232. Prototype* module;
  233. void onAction(const event::Action& e) override {
  234. module->reloadScript();
  235. }
  236. };
  237. struct PrototypeWidget : ModuleWidget {
  238. PrototypeWidget(Prototype* module) {
  239. setModule(module);
  240. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Prototype.svg")));
  241. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  242. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  243. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  244. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  245. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(8.099, 64.401)), module, Prototype::KNOB_PARAMS + 0));
  246. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(20.099, 64.401)), module, Prototype::KNOB_PARAMS + 1));
  247. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(32.099, 64.401)), module, Prototype::KNOB_PARAMS + 2));
  248. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(44.099, 64.401)), module, Prototype::KNOB_PARAMS + 3));
  249. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(56.099, 64.401)), module, Prototype::KNOB_PARAMS + 4));
  250. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(68.099, 64.401)), module, Prototype::KNOB_PARAMS + 5));
  251. addParam(createParamCentered<PB61303>(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_PARAMS + 0));
  252. addParam(createParamCentered<PB61303>(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_PARAMS + 1));
  253. addParam(createParamCentered<PB61303>(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_PARAMS + 2));
  254. addParam(createParamCentered<PB61303>(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_PARAMS + 3));
  255. addParam(createParamCentered<PB61303>(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_PARAMS + 4));
  256. addParam(createParamCentered<PB61303>(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_PARAMS + 5));
  257. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(8.099, 96.025)), module, Prototype::IN_INPUTS + 0));
  258. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(20.099, 96.025)), module, Prototype::IN_INPUTS + 1));
  259. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(32.099, 96.025)), module, Prototype::IN_INPUTS + 2));
  260. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(44.099, 96.025)), module, Prototype::IN_INPUTS + 3));
  261. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(56.099, 96.025)), module, Prototype::IN_INPUTS + 4));
  262. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(68.099, 96.025)), module, Prototype::IN_INPUTS + 5));
  263. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(8.099, 112.25)), module, Prototype::OUT_OUTPUTS + 0));
  264. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(20.099, 112.25)), module, Prototype::OUT_OUTPUTS + 1));
  265. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(32.099, 112.25)), module, Prototype::OUT_OUTPUTS + 2));
  266. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(44.099, 112.25)), module, Prototype::OUT_OUTPUTS + 3));
  267. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(56.099, 112.25)), module, Prototype::OUT_OUTPUTS + 4));
  268. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(68.099, 112.25)), module, Prototype::OUT_OUTPUTS + 5));
  269. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(8.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 0));
  270. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(20.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 1));
  271. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(32.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 2));
  272. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(44.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 3));
  273. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(56.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 4));
  274. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(68.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 5));
  275. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 0));
  276. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 1));
  277. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 2));
  278. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 3));
  279. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 4));
  280. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 5));
  281. PrototypeDisplay* display = createWidget<PrototypeDisplay>(mm2px(Vec(3.16, 14.837)));
  282. display->setModule(module);
  283. addChild(display);
  284. }
  285. void appendContextMenu(Menu* menu) override {
  286. Prototype* module = dynamic_cast<Prototype*>(this->module);
  287. menu->addChild(new MenuEntry);
  288. ReloadScriptItem* reloadScriptItem = createMenuItem<ReloadScriptItem>("Reload script");
  289. reloadScriptItem->module = module;
  290. menu->addChild(reloadScriptItem);
  291. }
  292. };
  293. void init(Plugin* p) {
  294. pluginInstance = p;
  295. p->addModel(createModel<Prototype, PrototypeWidget>("Prototype"));
  296. }