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.

793 lines
24KB

  1. #include <rack.hpp>
  2. #include <osdialog.h>
  3. #include <iostream>
  4. #include <fstream>
  5. #include <sstream>
  6. #include <thread>
  7. #include <mutex>
  8. #include "ScriptEngine.hpp"
  9. #include <efsw/efsw.h>
  10. #if defined ARCH_WIN
  11. #include <windows.h>
  12. #endif
  13. using namespace rack;
  14. Plugin* pluginInstance;
  15. // Don't bother deleting this with a destructor.
  16. __attribute((init_priority(999)))
  17. std::map<std::string, ScriptEngineFactory*> scriptEngineFactories;
  18. ScriptEngine* createScriptEngine(std::string extension) {
  19. auto it = scriptEngineFactories.find(extension);
  20. if (it == scriptEngineFactories.end())
  21. return NULL;
  22. return it->second->createScriptEngine();
  23. }
  24. static std::string settingsEditorPath;
  25. static std::string settingsPdEditorPath =
  26. #if defined ARCH_LIN
  27. "\"/usr/bin/pd-gui\"";
  28. #else
  29. "";
  30. #endif
  31. json_t* settingsToJson() {
  32. json_t* rootJ = json_object();
  33. json_object_set_new(rootJ, "editorPath", json_string(settingsEditorPath.c_str()));
  34. json_object_set_new(rootJ, "pdEditorPath", json_string(settingsPdEditorPath.c_str()));
  35. return rootJ;
  36. }
  37. void settingsFromJson(json_t* rootJ) {
  38. json_t* editorPathJ = json_object_get(rootJ, "editorPath");
  39. if (editorPathJ)
  40. settingsEditorPath = json_string_value(editorPathJ);
  41. json_t* pdEditorPathJ = json_object_get(rootJ, "pdEditorPath");
  42. if (pdEditorPathJ)
  43. settingsPdEditorPath = json_string_value(pdEditorPathJ);
  44. }
  45. void settingsLoad() {
  46. // Load plugin settings
  47. std::string filename = asset::user("VCV-Prototype.json");
  48. FILE* file = std::fopen(filename.c_str(), "r");
  49. if (!file) {
  50. return;
  51. }
  52. DEFER({
  53. std::fclose(file);
  54. });
  55. json_error_t error;
  56. json_t* rootJ = json_loadf(file, 0, &error);
  57. if (rootJ) {
  58. settingsFromJson(rootJ);
  59. json_decref(rootJ);
  60. }
  61. }
  62. void settingsSave() {
  63. json_t* rootJ = settingsToJson();
  64. std::string filename = asset::user("VCV-Prototype.json");
  65. FILE* file = std::fopen(filename.c_str(), "w");
  66. if (file) {
  67. json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  68. std::fclose(file);
  69. }
  70. json_decref(rootJ);
  71. }
  72. std::string getApplicationPathDialog() {
  73. char* pathC = NULL;
  74. #if defined ARCH_LIN
  75. pathC = osdialog_file(OSDIALOG_OPEN, "/usr/bin/", NULL, NULL);
  76. #elif defined ARCH_WIN
  77. osdialog_filters* filters = osdialog_filters_parse("Executable:exe");
  78. pathC = osdialog_file(OSDIALOG_OPEN, "C:/", NULL, filters);
  79. osdialog_filters_free(filters);
  80. #elif defined ARCH_MAC
  81. osdialog_filters* filters = osdialog_filters_parse("Application:app");
  82. pathC = osdialog_file(OSDIALOG_OPEN, "/Applications/", NULL, filters);
  83. osdialog_filters_free(filters);
  84. #endif
  85. if (!pathC)
  86. return "";
  87. std::string path = "\"";
  88. path += pathC;
  89. path += "\"";
  90. std::free(pathC);
  91. return path;
  92. }
  93. void setEditorDialog() {
  94. std::string path = getApplicationPathDialog();
  95. if (path == "")
  96. return;
  97. settingsEditorPath = path;
  98. settingsSave();
  99. }
  100. void setPdEditorDialog() {
  101. std::string path = getApplicationPathDialog();
  102. if (path == "")
  103. return;
  104. settingsPdEditorPath = path;
  105. settingsSave();
  106. }
  107. struct Prototype : Module {
  108. enum ParamIds {
  109. ENUMS(KNOB_PARAMS, NUM_ROWS),
  110. ENUMS(SWITCH_PARAMS, NUM_ROWS),
  111. NUM_PARAMS
  112. };
  113. enum InputIds {
  114. ENUMS(IN_INPUTS, NUM_ROWS),
  115. NUM_INPUTS
  116. };
  117. enum OutputIds {
  118. ENUMS(OUT_OUTPUTS, NUM_ROWS),
  119. NUM_OUTPUTS
  120. };
  121. enum LightIds {
  122. ENUMS(LIGHT_LIGHTS, NUM_ROWS * 3),
  123. ENUMS(SWITCH_LIGHTS, NUM_ROWS * 3),
  124. NUM_LIGHTS
  125. };
  126. std::string message;
  127. std::string path;
  128. std::string script;
  129. std::string engineName;
  130. std::mutex scriptMutex;
  131. ScriptEngine* scriptEngine = NULL;
  132. int frame = 0;
  133. int frameDivider;
  134. // This is dynamically allocated to have some protection against script bugs.
  135. ProcessBlock* block;
  136. int bufferIndex = 0;
  137. efsw_watcher efsw = NULL;
  138. /** Script that has not yet been approved to load */
  139. std::string unsecureScript;
  140. bool securityRequested = false;
  141. bool securityAccepted = false;
  142. Prototype() {
  143. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  144. for (int i = 0; i < NUM_ROWS; i++)
  145. configParam(KNOB_PARAMS + i, 0.f, 1.f, 0.5f, string::f("Knob %d", i + 1));
  146. for (int i = 0; i < NUM_ROWS; i++)
  147. configParam(SWITCH_PARAMS + i, 0.f, 1.f, 0.f, string::f("Switch %d", i + 1));
  148. // for (int i = 0; i < NUM_ROWS; i++)
  149. // configInput(IN_INPUTS + i, string::f("#%d", i + 1));
  150. // for (int i = 0; i < NUM_ROWS; i++)
  151. // configOutput(OUT_OUTPUTS + i, string::f("#%d", i + 1));
  152. block = new ProcessBlock;
  153. setPath("");
  154. }
  155. ~Prototype() {
  156. setPath("");
  157. delete block;
  158. }
  159. void onReset() override {
  160. setScript(script);
  161. }
  162. void process(const ProcessArgs& args) override {
  163. // Load security-sandboxed script if the security warning message is accepted.
  164. if (unsecureScript != "" && securityAccepted) {
  165. setScript(unsecureScript);
  166. unsecureScript = "";
  167. }
  168. // Frame divider for reducing sample rate
  169. if (++frame < frameDivider)
  170. return;
  171. frame = 0;
  172. // Clear outputs if no script is running
  173. if (!scriptEngine) {
  174. for (int i = 0; i < NUM_ROWS; i++)
  175. for (int c = 0; c < 3; c++)
  176. lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(0.f);
  177. for (int i = 0; i < NUM_ROWS; i++)
  178. for (int c = 0; c < 3; c++)
  179. lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(0.f);
  180. for (int i = 0; i < NUM_ROWS; i++)
  181. outputs[OUT_OUTPUTS + i].setVoltage(0.f);
  182. return;
  183. }
  184. // Inputs
  185. for (int i = 0; i < NUM_ROWS; i++)
  186. block->inputs[i][bufferIndex] = inputs[IN_INPUTS + i].getVoltage();
  187. // Process block
  188. if (++bufferIndex >= block->bufferSize) {
  189. std::lock_guard<std::mutex> lock(scriptMutex);
  190. bufferIndex = 0;
  191. // Block settings
  192. block->sampleRate = args.sampleRate;
  193. block->sampleTime = args.sampleTime;
  194. // Params
  195. for (int i = 0; i < NUM_ROWS; i++)
  196. block->knobs[i] = params[KNOB_PARAMS + i].getValue();
  197. for (int i = 0; i < NUM_ROWS; i++)
  198. block->switches[i] = params[SWITCH_PARAMS + i].getValue() > 0.f;
  199. float oldKnobs[NUM_ROWS];
  200. std::memcpy(oldKnobs, block->knobs, sizeof(oldKnobs));
  201. // Run ScriptEngine's process function
  202. {
  203. // Process buffer
  204. if (scriptEngine) {
  205. if (scriptEngine->process()) {
  206. WARN("Script %s process() failed. Stopped script.", path.c_str());
  207. delete scriptEngine;
  208. scriptEngine = NULL;
  209. return;
  210. }
  211. }
  212. }
  213. // Params
  214. // Only set params if values were changed by the script. This avoids issues when the user is manipulating them from the UI thread.
  215. for (int i = 0; i < NUM_ROWS; i++) {
  216. if (block->knobs[i] != oldKnobs[i])
  217. params[KNOB_PARAMS + i].setValue(block->knobs[i]);
  218. }
  219. // Lights
  220. for (int i = 0; i < NUM_ROWS; i++)
  221. for (int c = 0; c < 3; c++)
  222. lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(block->lights[i][c]);
  223. for (int i = 0; i < NUM_ROWS; i++)
  224. for (int c = 0; c < 3; c++)
  225. lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(block->switchLights[i][c]);
  226. }
  227. // Outputs
  228. for (int i = 0; i < NUM_ROWS; i++)
  229. outputs[OUT_OUTPUTS + i].setVoltage(block->outputs[i][bufferIndex]);
  230. }
  231. void setPath(std::string path) {
  232. // Cleanup
  233. if (efsw) {
  234. efsw_release(efsw);
  235. efsw = NULL;
  236. }
  237. this->path = "";
  238. setScript("");
  239. if (path == "")
  240. return;
  241. this->path = path;
  242. loadPath();
  243. if (this->script == "")
  244. return;
  245. // Watch file
  246. std::string dir = system::getDirectory(path);
  247. efsw = efsw_create(false);
  248. efsw_addwatch(efsw, dir.c_str(), watchCallback, false, this);
  249. efsw_watch(efsw);
  250. }
  251. void loadPath() {
  252. // Read file
  253. std::ifstream file;
  254. file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  255. try {
  256. file.open(path);
  257. std::stringstream buffer;
  258. buffer << file.rdbuf();
  259. std::string script = buffer.str();
  260. setScript(script);
  261. }
  262. catch (const std::runtime_error& err) {
  263. // Fail silently
  264. }
  265. }
  266. void setScript(std::string script) {
  267. std::lock_guard<std::mutex> lock(scriptMutex);
  268. // Reset script state
  269. if (scriptEngine) {
  270. delete scriptEngine;
  271. scriptEngine = NULL;
  272. }
  273. this->script = "";
  274. this->engineName = "";
  275. this->message = "";
  276. // Reset process state
  277. frameDivider = 32;
  278. frame = 0;
  279. bufferIndex = 0;
  280. // Reset block
  281. *block = ProcessBlock();
  282. if (script == "")
  283. return;
  284. this->script = script;
  285. // Create script engine from path extension
  286. std::string extension = system::getExtension(path);
  287. scriptEngine = createScriptEngine(extension);
  288. if (!scriptEngine) {
  289. message = string::f("No engine for %s extension", extension.c_str());
  290. return;
  291. }
  292. scriptEngine->module = this;
  293. // Run script
  294. if (scriptEngine->run(path, script)) {
  295. // Error message should have been set by ScriptEngine
  296. delete scriptEngine;
  297. scriptEngine = NULL;
  298. return;
  299. }
  300. this->engineName = scriptEngine->getEngineName();
  301. }
  302. static void watchCallback(efsw_watcher watcher, efsw_watchid watchid, const char* dir, const char* filename, enum efsw_action action, const char* old_filename, void* param) {
  303. Prototype* that = (Prototype*) param;
  304. if (action == EFSW_ADD || action == EFSW_DELETE || action == EFSW_MODIFIED || action == EFSW_MOVED) {
  305. // Check filename
  306. std::string pathFilename = system::getFilename(that->path);
  307. if (pathFilename == filename) {
  308. that->loadPath();
  309. }
  310. }
  311. }
  312. json_t* dataToJson() override {
  313. json_t* rootJ = json_object();
  314. json_object_set_new(rootJ, "path", json_string(path.c_str()));
  315. std::string script = this->script;
  316. // If we haven't accepted the security of this script, serialize the security-sandboxed script anyway.
  317. if (script == "")
  318. script = unsecureScript;
  319. json_object_set_new(rootJ, "script", json_stringn(script.data(), script.size()));
  320. return rootJ;
  321. }
  322. void dataFromJson(json_t* rootJ) override {
  323. json_t* pathJ = json_object_get(rootJ, "path");
  324. if (pathJ) {
  325. std::string path = json_string_value(pathJ);
  326. setPath(path);
  327. }
  328. // Only get the script string if the script file wasn't found.
  329. if (this->path != "" && this->script == "") {
  330. WARN("Script file %s not found, using script in patch", this->path.c_str());
  331. json_t* scriptJ = json_object_get(rootJ, "script");
  332. if (scriptJ) {
  333. std::string script = std::string(json_string_value(scriptJ), json_string_length(scriptJ));
  334. if (script != "") {
  335. // Request security warning message
  336. securityAccepted = false;
  337. securityRequested = true;
  338. unsecureScript = script;
  339. }
  340. }
  341. }
  342. }
  343. bool doesPathExist() {
  344. if (path == "")
  345. return false;
  346. // Try to open file
  347. std::ifstream file(path);
  348. return file.good();
  349. }
  350. void newScriptDialog() {
  351. std::string ext = ".js";
  352. // Get current extension if a script is currently loaded
  353. if (!path.empty()) {
  354. ext = system::getExtension(path);
  355. }
  356. std::string dir = asset::plugin(pluginInstance, "examples");
  357. std::string filename = "Untitled" + ext;
  358. char* newPathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), NULL);
  359. if (!newPathC) {
  360. return;
  361. }
  362. std::string newPath = newPathC;
  363. std::free(newPathC);
  364. // Unload script so the user is guaranteed to see the following error messages if they occur.
  365. setPath("");
  366. // Get extension of requested filename
  367. ext = system::getExtension(newPath);
  368. if (ext == "") {
  369. message = "File extension required";
  370. return;
  371. }
  372. auto it = scriptEngineFactories.find(ext);
  373. if (it == scriptEngineFactories.end()) {
  374. message = "File extension \"" + ext + "\" not recognized";
  375. return;
  376. }
  377. // Copy template to new script
  378. std::string templatePath = asset::plugin(pluginInstance, "examples/template" + ext);
  379. {
  380. std::ifstream templateFile(templatePath, std::ios::binary);
  381. std::ofstream newFile(newPath, std::ios::binary);
  382. newFile << templateFile.rdbuf();
  383. }
  384. setPath(newPath);
  385. editScript();
  386. }
  387. void loadScriptDialog() {
  388. std::string dir = asset::plugin(pluginInstance, "examples");
  389. char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL);
  390. if (!pathC) {
  391. return;
  392. }
  393. std::string path = pathC;
  394. std::free(pathC);
  395. setPath(path);
  396. }
  397. void reloadScript() {
  398. loadPath();
  399. }
  400. void saveScriptDialog() {
  401. if (script == "")
  402. return;
  403. std::string ext = system::getExtension(path);
  404. std::string dir = asset::plugin(pluginInstance, "examples");
  405. std::string filename = "Untitled" + ext;
  406. char* newPathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), NULL);
  407. if (!newPathC) {
  408. return;
  409. }
  410. std::string newPath = newPathC;
  411. std::free(newPathC);
  412. // Add extension if user didn't specify one
  413. std::string newExt = system::getExtension(newPath);
  414. if (newExt == "")
  415. newPath += ext;
  416. // Write and close file
  417. {
  418. std::ofstream f(newPath);
  419. f << script;
  420. }
  421. // Load path so that it reloads and is watched.
  422. setPath(newPath);
  423. }
  424. void editScript() {
  425. std::string editorPath = getEditorPath();
  426. if (editorPath.empty())
  427. return;
  428. if (path.empty())
  429. return;
  430. // Launch editor and detach
  431. #if defined ARCH_LIN
  432. std::string command = editorPath + " \"" + path + "\" &";
  433. (void) std::system(command.c_str());
  434. #elif defined ARCH_MAC
  435. std::string command = "open -a " + editorPath + " \"" + path + "\" &";
  436. (void) std::system(command.c_str());
  437. #elif defined ARCH_WIN
  438. std::string command = editorPath + " \"" + path + "\"";
  439. std::wstring commandW = string::UTF8toUTF16(command);
  440. STARTUPINFOW startupInfo;
  441. std::memset(&startupInfo, 0, sizeof(startupInfo));
  442. startupInfo.cb = sizeof(startupInfo);
  443. PROCESS_INFORMATION processInfo;
  444. // Use the non-const [] accessor for commandW. Since C++11, it is null-terminated.
  445. CreateProcessW(NULL, &commandW[0], NULL, NULL, false, 0, NULL, NULL, &startupInfo, &processInfo);
  446. #endif
  447. }
  448. void setClipboardMessage() {
  449. glfwSetClipboardString(APP->window->win, message.c_str());
  450. }
  451. void appendContextMenu(Menu* menu) {
  452. struct NewScriptItem : MenuItem {
  453. Prototype* module;
  454. void onAction(const event::Action& e) override {
  455. module->newScriptDialog();
  456. }
  457. };
  458. NewScriptItem* newScriptItem = createMenuItem<NewScriptItem>("New script");
  459. newScriptItem->module = this;
  460. menu->addChild(newScriptItem);
  461. struct LoadScriptItem : MenuItem {
  462. Prototype* module;
  463. void onAction(const event::Action& e) override {
  464. module->loadScriptDialog();
  465. }
  466. };
  467. LoadScriptItem* loadScriptItem = createMenuItem<LoadScriptItem>("Load script");
  468. loadScriptItem->module = this;
  469. menu->addChild(loadScriptItem);
  470. struct ReloadScriptItem : MenuItem {
  471. Prototype* module;
  472. void onAction(const event::Action& e) override {
  473. module->reloadScript();
  474. }
  475. };
  476. ReloadScriptItem* reloadScriptItem = createMenuItem<ReloadScriptItem>("Reload script");
  477. reloadScriptItem->module = this;
  478. menu->addChild(reloadScriptItem);
  479. struct SaveScriptItem : MenuItem {
  480. Prototype* module;
  481. void onAction(const event::Action& e) override {
  482. module->saveScriptDialog();
  483. }
  484. };
  485. SaveScriptItem* saveScriptItem = createMenuItem<SaveScriptItem>("Save script as");
  486. saveScriptItem->module = this;
  487. menu->addChild(saveScriptItem);
  488. struct EditScriptItem : MenuItem {
  489. Prototype* module;
  490. void onAction(const event::Action& e) override {
  491. module->editScript();
  492. }
  493. };
  494. EditScriptItem* editScriptItem = createMenuItem<EditScriptItem>("Edit script");
  495. editScriptItem->module = this;
  496. editScriptItem->disabled = !doesPathExist() || (getEditorPath() == "");
  497. menu->addChild(editScriptItem);
  498. menu->addChild(new MenuSeparator);
  499. struct SetEditorItem : MenuItem {
  500. void onAction(const event::Action& e) override {
  501. setEditorDialog();
  502. }
  503. };
  504. SetEditorItem* setEditorItem = createMenuItem<SetEditorItem>("Set text editor application");
  505. menu->addChild(setEditorItem);
  506. struct SetPdEditorItem : MenuItem {
  507. void onAction(const event::Action& e) override {
  508. setPdEditorDialog();
  509. }
  510. };
  511. SetPdEditorItem* setPdEditorItem = createMenuItem<SetPdEditorItem>("Set Pure Data application");
  512. menu->addChild(setPdEditorItem);
  513. }
  514. std::string getEditorPath() {
  515. if (path == "")
  516. return "";
  517. // HACK check if extension is .pd
  518. if (system::getExtension(path) == ".pd")
  519. return settingsPdEditorPath;
  520. return settingsEditorPath;
  521. }
  522. };
  523. void ScriptEngine::display(const std::string& message) {
  524. module->message = message;
  525. }
  526. void ScriptEngine::setFrameDivider(int frameDivider) {
  527. module->frameDivider = std::max(frameDivider, 1);
  528. }
  529. void ScriptEngine::setBufferSize(int bufferSize) {
  530. module->block->bufferSize = clamp(bufferSize, 1, MAX_BUFFER_SIZE);
  531. }
  532. ProcessBlock* ScriptEngine::getProcessBlock() {
  533. return module->block;
  534. }
  535. struct FileChoice : LedDisplayChoice {
  536. Prototype* module;
  537. void step() override {
  538. if (module && module->engineName != "")
  539. text = module->engineName;
  540. else
  541. text = "Script";
  542. text += ": ";
  543. if (module && module->path != "")
  544. text += system::getFilename(module->path);
  545. else
  546. text += "(click to load)";
  547. }
  548. void onAction(const event::Action& e) override {
  549. Menu* menu = createMenu();
  550. module->appendContextMenu(menu);
  551. }
  552. };
  553. struct MessageChoice : LedDisplayChoice {
  554. Prototype* module;
  555. void step() override {
  556. text = module ? module->message : "";
  557. }
  558. void draw(const DrawArgs& args) override {
  559. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  560. std::shared_ptr<Font> font = APP->window->loadFont(fontPath);
  561. if (font && font->handle >= 0) {
  562. nvgFillColor(args.vg, color);
  563. nvgFontFaceId(args.vg, font->handle);
  564. nvgTextLetterSpacing(args.vg, 0.0);
  565. nvgTextLineHeight(args.vg, 1.08);
  566. nvgFontSize(args.vg, 12);
  567. nvgTextBox(args.vg, textOffset.x, textOffset.y, box.size.x - textOffset.x, text.c_str(), NULL);
  568. }
  569. nvgResetScissor(args.vg);
  570. }
  571. void onAction(const event::Action& e) override {
  572. Menu* menu = createMenu();
  573. struct SetClipboardMessageItem : MenuItem {
  574. Prototype* module;
  575. void onAction(const event::Action& e) override {
  576. module->setClipboardMessage();
  577. }
  578. };
  579. SetClipboardMessageItem* item = createMenuItem<SetClipboardMessageItem>("Copy");
  580. item->module = module;
  581. menu->addChild(item);
  582. }
  583. };
  584. struct PrototypeDisplay : LedDisplay {
  585. PrototypeDisplay() {
  586. box.size = mm2px(Vec(69.879, 27.335));
  587. }
  588. void setModule(Prototype* module) {
  589. FileChoice* fileChoice = new FileChoice;
  590. fileChoice->box.size.x = box.size.x;
  591. fileChoice->module = module;
  592. addChild(fileChoice);
  593. LedDisplaySeparator* fileSeparator = new LedDisplaySeparator;
  594. fileSeparator->box.size.x = box.size.x;
  595. fileSeparator->box.pos = fileChoice->box.getBottomLeft();
  596. addChild(fileSeparator);
  597. MessageChoice* messageChoice = new MessageChoice;
  598. messageChoice->box.pos = fileChoice->box.getBottomLeft();
  599. messageChoice->box.size.x = box.size.x;
  600. messageChoice->box.size.y = box.size.y - messageChoice->box.pos.y;
  601. messageChoice->module = module;
  602. addChild(messageChoice);
  603. }
  604. };
  605. struct PrototypeWidget : ModuleWidget {
  606. PrototypeWidget(Prototype* module) {
  607. setModule(module);
  608. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Prototype.svg")));
  609. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  610. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  611. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  612. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  613. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(8.099, 64.401)), module, Prototype::KNOB_PARAMS + 0));
  614. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(20.099, 64.401)), module, Prototype::KNOB_PARAMS + 1));
  615. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(32.099, 64.401)), module, Prototype::KNOB_PARAMS + 2));
  616. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(44.099, 64.401)), module, Prototype::KNOB_PARAMS + 3));
  617. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(56.099, 64.401)), module, Prototype::KNOB_PARAMS + 4));
  618. addParam(createParamCentered<RoundSmallBlackKnob>(mm2px(Vec(68.099, 64.401)), module, Prototype::KNOB_PARAMS + 5));
  619. addParam(createParamCentered<PB61303>(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_PARAMS + 0));
  620. addParam(createParamCentered<PB61303>(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_PARAMS + 1));
  621. addParam(createParamCentered<PB61303>(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_PARAMS + 2));
  622. addParam(createParamCentered<PB61303>(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_PARAMS + 3));
  623. addParam(createParamCentered<PB61303>(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_PARAMS + 4));
  624. addParam(createParamCentered<PB61303>(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_PARAMS + 5));
  625. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(8.099, 96.025)), module, Prototype::IN_INPUTS + 0));
  626. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(20.099, 96.025)), module, Prototype::IN_INPUTS + 1));
  627. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(32.099, 96.025)), module, Prototype::IN_INPUTS + 2));
  628. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(44.099, 96.025)), module, Prototype::IN_INPUTS + 3));
  629. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(56.099, 96.025)), module, Prototype::IN_INPUTS + 4));
  630. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(68.099, 96.025)), module, Prototype::IN_INPUTS + 5));
  631. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(8.099, 112.25)), module, Prototype::OUT_OUTPUTS + 0));
  632. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(20.099, 112.25)), module, Prototype::OUT_OUTPUTS + 1));
  633. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(32.099, 112.25)), module, Prototype::OUT_OUTPUTS + 2));
  634. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(44.099, 112.25)), module, Prototype::OUT_OUTPUTS + 3));
  635. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(56.099, 112.25)), module, Prototype::OUT_OUTPUTS + 4));
  636. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(68.099, 112.25)), module, Prototype::OUT_OUTPUTS + 5));
  637. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(8.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 0));
  638. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(20.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 1));
  639. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(32.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 2));
  640. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(44.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 3));
  641. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(56.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 4));
  642. addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(68.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 5));
  643. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 0));
  644. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 1));
  645. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 2));
  646. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 3));
  647. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 4));
  648. addChild(createLightCentered<PB61303Light<RedGreenBlueLight>>(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 5));
  649. PrototypeDisplay* display = createWidget<PrototypeDisplay>(mm2px(Vec(3.16, 14.837)));
  650. display->setModule(module);
  651. addChild(display);
  652. }
  653. void appendContextMenu(Menu* menu) override {
  654. Prototype* module = dynamic_cast<Prototype*>(this->module);
  655. menu->addChild(new MenuSeparator);
  656. module->appendContextMenu(menu);
  657. }
  658. void onPathDrop(const event::PathDrop& e) override {
  659. Prototype* module = dynamic_cast<Prototype*>(this->module);
  660. if (!module)
  661. return;
  662. if (e.paths.size() < 1)
  663. return;
  664. module->setPath(e.paths[0]);
  665. }
  666. void step() override {
  667. Prototype* module = dynamic_cast<Prototype*>(this->module);
  668. if (module && module->securityRequested) {
  669. if (osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "VCV Prototype is requesting to run a script from a patch or module preset. Running Prototype scripts from untrusted sources may compromise your computer and personal information. Proceed and run script?")) {
  670. module->securityAccepted = true;
  671. }
  672. module->securityRequested = false;
  673. }
  674. ModuleWidget::step();
  675. }
  676. };
  677. void init(Plugin* p) {
  678. pluginInstance = p;
  679. p->addModel(createModel<Prototype, PrototypeWidget>("Prototype"));
  680. settingsLoad();
  681. }