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.

467 lines
15KB

  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. ScriptEngine::ProcessBlock block;
  38. int bufferIndex = 0;
  39. Prototype() {
  40. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  41. for (int i = 0; i < NUM_ROWS; i++)
  42. configParam(KNOB_PARAMS + i, 0.f, 1.f, 0.5f, string::f("Knob %d", i + 1));
  43. for (int i = 0; i < NUM_ROWS; i++)
  44. configParam(SWITCH_PARAMS + i, 0.f, 1.f, 0.f, string::f("Switch %d", i + 1));
  45. clearScriptEngine();
  46. }
  47. ~Prototype() {
  48. std::lock_guard<std::mutex> lock(scriptMutex);
  49. clearScriptEngine();
  50. }
  51. void process(const ProcessArgs& args) override {
  52. if (!scriptEngine)
  53. return;
  54. // Frame divider for reducing sample rate
  55. if (++frame < frameDivider)
  56. return;
  57. frame = 0;
  58. // Inputs
  59. for (int i = 0; i < NUM_ROWS; i++)
  60. block.inputs[i][bufferIndex] = inputs[IN_INPUTS + i].getVoltage();
  61. // Process block
  62. if (++bufferIndex >= block.bufferSize) {
  63. bufferIndex = 0;
  64. // Block settings
  65. block.sampleRate = args.sampleRate;
  66. block.sampleTime = args.sampleTime;
  67. // Params
  68. for (int i = 0; i < NUM_ROWS; i++)
  69. block.knobs[i] = params[KNOB_PARAMS + i].getValue();
  70. for (int i = 0; i < NUM_ROWS; i++)
  71. block.switches[i] = params[SWITCH_PARAMS + i].getValue() > 0.f;
  72. float oldKnobs[NUM_ROWS];
  73. std::memcpy(oldKnobs, block.knobs, sizeof(block.knobs));
  74. // Run ScriptEngine's process function
  75. {
  76. std::lock_guard<std::mutex> lock(scriptMutex);
  77. // Check for certain inside the mutex
  78. if (scriptEngine) {
  79. if (scriptEngine->process()) {
  80. WARN("Script %s process() failed. Stopped script.", path.c_str());
  81. clearScriptEngine();
  82. return;
  83. }
  84. }
  85. }
  86. // Params
  87. for (int i = 0; i < NUM_ROWS; i++) {
  88. if (block.knobs[i] != oldKnobs[i])
  89. params[KNOB_PARAMS + i].setValue(block.knobs[i]);
  90. }
  91. // Lights
  92. for (int i = 0; i < NUM_ROWS; i++)
  93. for (int c = 0; c < 3; c++)
  94. lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(block.lights[i][c]);
  95. for (int i = 0; i < NUM_ROWS; i++)
  96. for (int c = 0; c < 3; c++)
  97. lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(block.switchLights[i][c]);
  98. }
  99. // Outputs
  100. for (int i = 0; i < NUM_ROWS; i++)
  101. outputs[OUT_OUTPUTS + i].setVoltage(block.outputs[i][bufferIndex]);
  102. }
  103. void clearScriptEngine() {
  104. if (scriptEngine) {
  105. delete scriptEngine;
  106. scriptEngine = NULL;
  107. }
  108. // Reset outputs and lights because they might hold old values
  109. for (int i = 0; i < NUM_ROWS; i++)
  110. outputs[OUT_OUTPUTS + i].setVoltage(0.f);
  111. for (int i = 0; i < NUM_ROWS; i++)
  112. for (int c = 0; c < 3; c++)
  113. lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(0.f);
  114. for (int i = 0; i < NUM_ROWS; i++)
  115. for (int c = 0; c < 3; c++)
  116. lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(0.f);
  117. // Reset settings
  118. frameDivider = 32;
  119. frame = 0;
  120. block = ScriptEngine::ProcessBlock();
  121. bufferIndex = 0;
  122. }
  123. void setScriptString(std::string path, std::string script) {
  124. std::lock_guard<std::mutex> lock(scriptMutex);
  125. message = "";
  126. this->path = "";
  127. this->script = "";
  128. this->engineName = "";
  129. clearScriptEngine();
  130. // Get ScriptEngine from path extension
  131. if (path == "") {
  132. // Empty path means no script is requested. Fail silently.
  133. return;
  134. }
  135. INFO("Loading script %s", path.c_str());
  136. std::string ext = string::filenameExtension(string::filename(path));
  137. scriptEngine = createScriptEngine(ext);
  138. if (!scriptEngine) {
  139. message = string::f("No engine for .%s extension", ext.c_str());
  140. return;
  141. }
  142. scriptEngine->module = this;
  143. scriptEngine->block = &block;
  144. this->path = path;
  145. this->script = script;
  146. this->engineName = scriptEngine->getEngineName();
  147. // Read file
  148. std::ifstream file;
  149. file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  150. try {
  151. file.open(this->path);
  152. std::stringstream buffer;
  153. buffer << file.rdbuf();
  154. this->script = buffer.str();
  155. }
  156. catch (const std::runtime_error& err) {
  157. WARN("Script %s not found, using stored script string", this->path.c_str());
  158. }
  159. // Run script
  160. if (this->script == "") {
  161. message = "Could not load script.";
  162. clearScriptEngine();
  163. return;
  164. }
  165. if (scriptEngine->run(this->path, this->script)) {
  166. // Error message should have been set by ScriptEngine
  167. clearScriptEngine();
  168. return;
  169. }
  170. INFO("Successfully ran script %s", this->path.c_str());
  171. }
  172. json_t* dataToJson() override {
  173. json_t* rootJ = json_object();
  174. json_object_set_new(rootJ, "path", json_string(path.c_str()));
  175. json_object_set_new(rootJ, "script", json_stringn(script.data(), script.size()));
  176. return rootJ;
  177. }
  178. void dataFromJson(json_t* rootJ) override {
  179. json_t* pathJ = json_object_get(rootJ, "path");
  180. json_t* scriptJ = json_object_get(rootJ, "script");
  181. if (pathJ && scriptJ) {
  182. std::string path = json_string_value(pathJ);
  183. std::string script = std::string(json_string_value(scriptJ), json_string_length(scriptJ));
  184. setScriptString(path, script);
  185. }
  186. }
  187. void loadScriptDialog() {
  188. std::string dir = asset::plugin(pluginInstance, "examples");
  189. char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL);
  190. if (!pathC) {
  191. return;
  192. }
  193. std::string path = pathC;
  194. std::free(pathC);
  195. setScriptString(path, "");
  196. }
  197. void reloadScript() {
  198. setScriptString(path, script);
  199. }
  200. void saveScriptDialog() {
  201. if (script == "")
  202. return;
  203. std::string ext = string::filenameExtension(string::filename(path));
  204. std::string dir = asset::plugin(pluginInstance, "examples");
  205. std::string filename = "Untitled." + ext;
  206. char* newPathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), NULL);
  207. if (!newPathC) {
  208. return;
  209. }
  210. std::string newPath = newPathC;
  211. std::free(newPathC);
  212. // Add extension if user didn't specify one
  213. std::string newExt = string::filenameExtension(string::filename(newPath));
  214. if (newExt == "")
  215. newPath += "." + ext;
  216. std::ofstream f(newPath);
  217. f << script;
  218. path = newPath;
  219. }
  220. };
  221. void ScriptEngine::setMessage(const std::string& message) {
  222. module->message = message;
  223. }
  224. void ScriptEngine::setFrameDivider(int frameDivider) {
  225. module->frameDivider = frameDivider;
  226. }
  227. struct FileChoice : LedDisplayChoice {
  228. Prototype* module;
  229. void step() override {
  230. if (module && module->engineName != "")
  231. text = module->engineName;
  232. else
  233. text = "Script";
  234. text += ": ";
  235. if (module && module->path != "")
  236. text += string::filename(module->path);
  237. else
  238. text += "(click to load)";
  239. }
  240. void onAction(const event::Action& e) override {
  241. module->loadScriptDialog();
  242. }
  243. };
  244. struct MessageChoice : LedDisplayChoice {
  245. Prototype* module;
  246. void step() override {
  247. text = module ? module->message : "";
  248. }
  249. void draw(const DrawArgs& args) override {
  250. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  251. if (font->handle >= 0) {
  252. nvgFillColor(args.vg, color);
  253. nvgFontFaceId(args.vg, font->handle);
  254. nvgTextLetterSpacing(args.vg, 0.0);
  255. nvgTextLineHeight(args.vg, 1.08);
  256. nvgFontSize(args.vg, 12);
  257. nvgTextBox(args.vg, textOffset.x, textOffset.y, box.size.x - textOffset.x, text.c_str(), NULL);
  258. }
  259. nvgResetScissor(args.vg);
  260. }
  261. };
  262. struct PrototypeDisplay : LedDisplay {
  263. PrototypeDisplay() {
  264. box.size = mm2px(Vec(69.879, 27.335));
  265. }
  266. void setModule(Prototype* module) {
  267. FileChoice* fileChoice = new FileChoice;
  268. fileChoice->box.size.x = box.size.x;
  269. fileChoice->module = module;
  270. addChild(fileChoice);
  271. LedDisplaySeparator* fileSeparator = new LedDisplaySeparator;
  272. fileSeparator->box.size.x = box.size.x;
  273. fileSeparator->box.pos = fileChoice->box.getBottomLeft();
  274. addChild(fileSeparator);
  275. MessageChoice* messageChoice = new MessageChoice;
  276. messageChoice->box.pos = fileChoice->box.getBottomLeft();
  277. messageChoice->box.size.x = box.size.x;
  278. messageChoice->box.size.y = box.size.y - messageChoice->box.pos.y;
  279. messageChoice->module = module;
  280. addChild(messageChoice);
  281. }
  282. };
  283. struct LoadScriptItem : MenuItem {
  284. Prototype* module;
  285. void onAction(const event::Action& e) override {
  286. module->loadScriptDialog();
  287. }
  288. };
  289. struct ReloadScriptItem : MenuItem {
  290. Prototype* module;
  291. void onAction(const event::Action& e) override {
  292. module->reloadScript();
  293. }
  294. };
  295. struct SaveScriptItem : MenuItem {
  296. Prototype* module;
  297. void onAction(const event::Action& e) override {
  298. module->saveScriptDialog();
  299. }
  300. };
  301. struct PrototypeWidget : ModuleWidget {
  302. PrototypeWidget(Prototype* module) {
  303. setModule(module);
  304. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Prototype.svg")));
  305. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  306. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  307. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  308. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  309. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(8.099, 64.401)), module, Prototype::KNOB_PARAMS + 0));
  310. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(20.099, 64.401)), module, Prototype::KNOB_PARAMS + 1));
  311. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(32.099, 64.401)), module, Prototype::KNOB_PARAMS + 2));
  312. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(44.099, 64.401)), module, Prototype::KNOB_PARAMS + 3));
  313. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(56.099, 64.401)), module, Prototype::KNOB_PARAMS + 4));
  314. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(68.099, 64.401)), module, Prototype::KNOB_PARAMS + 5));
  315. addParam(createParamCentered<PB61303>(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_PARAMS + 0));
  316. addParam(createParamCentered<PB61303>(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_PARAMS + 1));
  317. addParam(createParamCentered<PB61303>(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_PARAMS + 2));
  318. addParam(createParamCentered<PB61303>(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_PARAMS + 3));
  319. addParam(createParamCentered<PB61303>(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_PARAMS + 4));
  320. addParam(createParamCentered<PB61303>(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_PARAMS + 5));
  321. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(8.099, 96.025)), module, Prototype::IN_INPUTS + 0));
  322. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(20.099, 96.025)), module, Prototype::IN_INPUTS + 1));
  323. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(32.099, 96.025)), module, Prototype::IN_INPUTS + 2));
  324. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(44.099, 96.025)), module, Prototype::IN_INPUTS + 3));
  325. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(56.099, 96.025)), module, Prototype::IN_INPUTS + 4));
  326. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(68.099, 96.025)), module, Prototype::IN_INPUTS + 5));
  327. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(8.099, 112.25)), module, Prototype::OUT_OUTPUTS + 0));
  328. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(20.099, 112.25)), module, Prototype::OUT_OUTPUTS + 1));
  329. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(32.099, 112.25)), module, Prototype::OUT_OUTPUTS + 2));
  330. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(44.099, 112.25)), module, Prototype::OUT_OUTPUTS + 3));
  331. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(56.099, 112.25)), module, Prototype::OUT_OUTPUTS + 4));
  332. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(68.099, 112.25)), module, Prototype::OUT_OUTPUTS + 5));
  333. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(8.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 0));
  334. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(20.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 1));
  335. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(32.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 2));
  336. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(44.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 3));
  337. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(56.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 4));
  338. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(68.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 5));
  339. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 0));
  340. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 1));
  341. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 2));
  342. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 3));
  343. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 4));
  344. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 5));
  345. PrototypeDisplay* display = createWidget<PrototypeDisplay>(mm2px(Vec(3.16, 14.837)));
  346. display->setModule(module);
  347. addChild(display);
  348. }
  349. void appendContextMenu(Menu* menu) override {
  350. Prototype* module = dynamic_cast<Prototype*>(this->module);
  351. menu->addChild(new MenuEntry);
  352. LoadScriptItem* loadScriptItem = createMenuItem<LoadScriptItem>("Load script");
  353. loadScriptItem->module = module;
  354. loadScriptItem->rightText = RACK_MOD_CTRL_NAME "+O";
  355. menu->addChild(loadScriptItem);
  356. ReloadScriptItem* reloadScriptItem = createMenuItem<ReloadScriptItem>("Reload/rerun script");
  357. reloadScriptItem->rightText = RACK_MOD_CTRL_NAME "+J";
  358. reloadScriptItem->module = module;
  359. menu->addChild(reloadScriptItem);
  360. SaveScriptItem* saveScriptItem = createMenuItem<SaveScriptItem>("Save script as");
  361. saveScriptItem->module = module;
  362. menu->addChild(saveScriptItem);
  363. }
  364. void onPathDrop(const event::PathDrop& e) override {
  365. Prototype* module = dynamic_cast<Prototype*>(this->module);
  366. if (module && !e.paths.empty()) {
  367. module->setScriptString(e.paths[0], "");
  368. }
  369. }
  370. void onHoverKey(const event::HoverKey& e) override {
  371. ModuleWidget::onHoverKey(e);
  372. if (e.isConsumed())
  373. return;
  374. Prototype* module = dynamic_cast<Prototype*>(this->module);
  375. if (e.action == GLFW_PRESS) {
  376. switch (e.key) {
  377. case GLFW_KEY_O: {
  378. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  379. module->loadScriptDialog();
  380. e.consume(this);
  381. }
  382. } break;
  383. case GLFW_KEY_J: {
  384. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  385. module->reloadScript();
  386. e.consume(this);
  387. }
  388. } break;
  389. }
  390. }
  391. }
  392. };
  393. void init(Plugin* p) {
  394. pluginInstance = p;
  395. p->addModel(createModel<Prototype, PrototypeWidget>("Prototype"));
  396. }