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.

562 lines
15KB

  1. #include "SubmarineFree.hpp"
  2. #include <mutex>
  3. #include "torpedo.hpp"
  4. #include <fstream>
  5. #include <cctype>
  6. #include "UpdateRing.hpp"
  7. namespace rack_plugin_SubmarineFree {
  8. struct WK_Update {
  9. float offsets[12];
  10. int isDirty = false;
  11. };
  12. struct WK_Tuning {
  13. std::string name;
  14. float offsets[12];
  15. };
  16. std::vector<WK_Tuning> tunings;
  17. int tuningsLoaded = false;
  18. struct WK_Tunings {
  19. static void loadTuningsFromWK(const char *path);
  20. static void loadTuningsFromScala(const char /*Plugin*/ *_plugin);
  21. static void loadScalaFile(std::string path);
  22. static void loadTunings(const char /*Plugin*/ *_plugin) {
  23. if (tuningsLoaded)
  24. return;
  25. tuningsLoaded = true;
  26. loadTuningsFromWK(assetPlugin(_plugin, "WK_Custom.tunings").c_str());
  27. loadTuningsFromScala(_plugin);
  28. }
  29. };
  30. void WK_Tunings::loadTuningsFromWK(const char *path) {
  31. FILE *file = fopen(path, "r");
  32. if (!file) {
  33. return;
  34. }
  35. int defaultSize = tunings.size();
  36. json_error_t error;
  37. json_t *rootJ = json_loadf(file, 0, &error);
  38. if (rootJ) {
  39. int size = json_array_size(rootJ);
  40. for (int i = 0; i < size; i++) {
  41. json_t *j0 = json_array_get(rootJ, i);
  42. if (j0) {
  43. json_t *jname = json_object_get(j0, "name");
  44. if (jname) {
  45. json_t *joffsets = json_object_get(j0, "tunings");
  46. if (joffsets) {
  47. tunings.push_back(WK_Tuning());
  48. tunings[i + defaultSize].name.assign(json_string_value(jname));
  49. int tsize = json_array_size(joffsets);
  50. for (int j = 0; j < 12; j++) {
  51. if (j < tsize) {
  52. json_t *joffset = json_array_get(joffsets, j);
  53. if (joffset) {
  54. tunings[i + defaultSize].offsets[j] = json_number_value(joffset);
  55. }
  56. }
  57. else {
  58. tunings[i + defaultSize].offsets[j] = 0.0f;
  59. }
  60. }
  61. }
  62. }
  63. }
  64. }
  65. json_decref(rootJ);
  66. }
  67. else {
  68. std::string message = stringf("SubmarineFree WK: JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  69. warn(message.c_str());
  70. }
  71. fclose(file);
  72. }
  73. void WK_Tunings::loadScalaFile(std::string path) {
  74. std::ifstream fs{path, std::ios_base::in};
  75. if (fs) {
  76. std::vector<std::string> strings;
  77. while (!fs.eof()) {
  78. std::string line;
  79. getline(fs, line);
  80. int iscomment = false;
  81. for (unsigned int i = 0; i < line.size(); i++) {
  82. if (std::isspace(line[i]))
  83. continue;
  84. if (line[i] == '!') {
  85. iscomment = true;
  86. break;
  87. }
  88. }
  89. if (iscomment)
  90. continue;
  91. strings.push_back(std::string(line));
  92. if (strings.size() >= 14)
  93. break;
  94. }
  95. fs.close();
  96. if (strings.size() < 2) return;
  97. WK_Tuning tuning = { "", { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } };
  98. tuning.name.assign(strings[0]);
  99. for (unsigned int i = 2; i < strings.size(); i++) {
  100. // remove leading whitespace
  101. while (strings[i].size() && std::isspace(strings[i][0]))
  102. strings[i].erase(0,1);
  103. std::string line;
  104. int decimal = false;
  105. int ratio = false;
  106. while (strings[i].size() && !std::isspace(strings[i][0])) {
  107. char c = strings[i][0];
  108. line.append(1,c);
  109. strings[i].erase(0,1);
  110. if (!std::isdigit(c) && (c != '/') && (c != '.')) {
  111. warn("SubmarineFree WK: Scala file format error in %s", stringFilename(path).c_str());
  112. return;
  113. }
  114. if (c == '.')
  115. decimal = true;
  116. if (c == '/' && !ratio)
  117. ratio = line.size();
  118. if (decimal && ratio) {
  119. warn("SubmarineFree WK: Scala file format error in %s", stringFilename(path).c_str());
  120. return;
  121. }
  122. }
  123. if (decimal) {
  124. try {
  125. float d = std::stof(line, nullptr);
  126. d -= (i-1) * 100.0;
  127. if ((d < -50.0) || (d > 50.0)) {
  128. warn("SubmarineFree WK: Scala file format error in %s", stringFilename(path).c_str());
  129. return;
  130. }
  131. tuning.offsets[(i-1)%12] = d;
  132. }
  133. catch (std::exception &err) {
  134. warn("SubmarineFree WK: Scala file format error in %s", stringFilename(path).c_str());
  135. return;
  136. }
  137. }
  138. else {
  139. if (ratio) {
  140. std::string num = line.substr(0,ratio);
  141. std::string denom = line.substr(ratio);
  142. try {
  143. int inum = std::stoi(num,nullptr);
  144. int idenom = std::stoi(denom, nullptr);
  145. if (!idenom) {
  146. warn("SubmarineFree WK: Scala file format error in %s", stringFilename(path).c_str());
  147. return;
  148. }
  149. float r = (1.0f * inum / idenom);
  150. float d = 1200.0 * log2(r);
  151. d -= (i-1) * 100.0;
  152. if ((d < -50.0) || (d > 50.0)) {
  153. warn("SubmarineFree WK: Scala file format error in %s", stringFilename(path).c_str());
  154. return;
  155. }
  156. tuning.offsets[(i-1)%12] = d;
  157. }
  158. catch (std::exception &err) {
  159. warn("SubmarineFree WK: Scala file format error in %s", stringFilename(path).c_str());
  160. return;
  161. }
  162. }
  163. else {
  164. try {
  165. int inum = std::stoi(line, nullptr);
  166. float d = 1200.0 * log2(inum);
  167. d -= (i-1) * 100.0;
  168. if ((d < -50.0) || (d > 50.0)) {
  169. warn("SubmarineFree WK: Scala file format error in %s", stringFilename(path).c_str());
  170. return;
  171. }
  172. tuning.offsets[(i-1)%12] = d;
  173. }
  174. catch (std::exception &err) {
  175. warn("SubmarineFree WK: Scala file format error in %s", stringFilename(path).c_str());
  176. return;
  177. }
  178. }
  179. }
  180. }
  181. int index = tunings.size();
  182. tunings.push_back(WK_Tuning());
  183. tunings[index].name = tuning.name;
  184. for (int i = 0; i < 12; i++)
  185. tunings[index].offsets[i] = tuning.offsets[i];
  186. info("SubmarineFree WK: Loaded Scala file %s", tuning.name.c_str());
  187. }
  188. }
  189. void WK_Tunings::loadTuningsFromScala(const char /*Plugin*/ *_plugin) {
  190. std::vector<std::string> dirList = systemListEntries(assetPlugin(_plugin, "Scala"));
  191. for (auto entry : dirList) {
  192. if (systemIsDirectory(entry)) continue;
  193. if (stringExtension(entry).compare("scl")) continue;
  194. loadScalaFile(entry);
  195. }
  196. }
  197. struct WK_101;
  198. struct WK101_InputPort : Torpedo::PatchInputPort {
  199. WK_101 *wkModule;
  200. WK101_InputPort(WK_101 *module, unsigned int portNum):PatchInputPort((Module *)module, portNum) { wkModule = module;};
  201. void received(std::string pluginName, std::string moduleName, json_t *rootJ) override;
  202. };
  203. struct WK_101 : Module {
  204. enum ParamIds {
  205. PARAM_1,
  206. PARAM_2,
  207. PARAM_3,
  208. PARAM_4,
  209. PARAM_5,
  210. PARAM_6,
  211. PARAM_7,
  212. PARAM_8,
  213. PARAM_9,
  214. PARAM_10,
  215. PARAM_11,
  216. PARAM_12,
  217. NUM_PARAMS
  218. };
  219. enum InputIds {
  220. INPUT_CV,
  221. INPUT_TOR,
  222. NUM_INPUTS
  223. };
  224. enum OutputIds {
  225. OUTPUT_CV,
  226. OUTPUT_TOR,
  227. NUM_OUTPUTS
  228. };
  229. enum LightIds {
  230. LIGHT_1,
  231. LIGHT_2,
  232. LIGHT_3,
  233. LIGHT_4,
  234. LIGHT_5,
  235. LIGHT_6,
  236. LIGHT_7,
  237. LIGHT_8,
  238. LIGHT_9,
  239. LIGHT_10,
  240. LIGHT_11,
  241. LIGHT_12,
  242. NUM_LIGHTS
  243. };
  244. float tunings[12];
  245. int toSend = 0;
  246. UpdateRing<WK_Update> updateRing;
  247. Torpedo::PatchOutputPort outPort = Torpedo::PatchOutputPort(this, OUTPUT_TOR);
  248. WK101_InputPort inPort = WK101_InputPort(this, INPUT_TOR);
  249. WK_101() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {outPort.size(5);}
  250. void step() override;
  251. void PrepareUpdate() {
  252. WK_Update *upd = updateRing.bg();
  253. for (int i = 0; i < 12; i++)
  254. upd->offsets[i] = tunings[i];
  255. upd->isDirty = true;
  256. updateRing.swap();
  257. }
  258. };
  259. void WK_101::step() {
  260. int quantized = floor((12.0f * inputs[INPUT_CV].value) + 0.5f);
  261. int note = (120 + quantized) % 12;
  262. outputs[OUTPUT_CV].value = (tunings[note] / 1200.0f) + (quantized / 12.0f);
  263. for (int i = 0; i < 12; i++)
  264. lights[LIGHT_1 + i].value = (note == i)?1.0f:0.0f;
  265. if (toSend && !outPort.isBusy()) {
  266. toSend = 0;
  267. json_t *rootJ = json_array();
  268. for (int i = 0; i < 12; i++)
  269. json_array_append_new(rootJ, json_real(tunings[i]));
  270. outPort.send(std::string(TOSTRING(SLUG)), std::string("WK"), rootJ);
  271. }
  272. outPort.process();
  273. inPort.process();
  274. }
  275. void WK101_InputPort::received(std::string pluginName, std::string moduleName, json_t *rootJ) {
  276. if (pluginName.compare(TOSTRING(SLUG))) return;
  277. if (moduleName.compare("WK")) return;
  278. float tunings[12];
  279. int size = json_array_size(rootJ);
  280. if (!size) return;
  281. if (size > 12)
  282. size = 12;
  283. for (int i = 0; i < size; i++) {
  284. json_t *j1 = json_array_get(rootJ, i);
  285. if (j1)
  286. tunings[i] = json_number_value(j1);
  287. }
  288. {
  289. //std::lock_guard<std::mutex> guard(wkModule->mtx);
  290. //wkModule->isDirty = true;
  291. WK_Update *upd = wkModule->updateRing.bg();
  292. for (int i = 0; i < 12; i++)
  293. upd->offsets[i] = tunings[i];
  294. upd->isDirty = true;
  295. wkModule->updateRing.swap();
  296. }
  297. }
  298. struct WK_Display : TransparentWidget {
  299. std::shared_ptr<Font> font;
  300. WK_101 *module;
  301. int index;
  302. char dspText[20];
  303. WK_Display() {
  304. font = Font::load(assetGlobal("res/fonts/DejaVuSans.ttf"));
  305. }
  306. void draw(NVGcontext *vg) override {
  307. float val = module->tunings[index];
  308. sprintf(dspText, "%+05.2f", val);
  309. nvgFontSize(vg, 14);
  310. nvgFontFaceId(vg, font->handle);
  311. nvgFillColor(vg, nvgRGBA(0x28, 0xb0, 0xf3, 0xff));
  312. nvgTextAlign(vg, NVG_ALIGN_CENTER);
  313. nvgText(vg, 30, 13, dspText, NULL);
  314. }
  315. };
  316. struct WK101_MenuItem : MenuItem {
  317. WK_101 *module;
  318. int index;
  319. void onAction(EventAction &e) override {
  320. for (int i = 0; i < 12; i++)
  321. module->tunings[i] = tunings[index].offsets[i];
  322. module->PrepareUpdate();
  323. module->toSend = true;
  324. }
  325. };
  326. struct WK_Param : MedKnob<LightKnob> {
  327. void onChange(EventChange &e) override {
  328. MedKnob<LightKnob>::onChange(e);
  329. WK_101 *module = dynamic_cast<WK_101 *>(this->module);
  330. module->tunings[paramId - WK_101::PARAM_1] = value;
  331. module->toSend = true;
  332. }
  333. };
  334. struct WK101 : ModuleWidget {
  335. WK_Param *widgets[12];
  336. WK101(WK_101 *module) : ModuleWidget(module) {
  337. setPanel(SVG::load(assetPlugin(plugin, "res/WK-101.svg")));
  338. addInput(Port::create<SilverPort>(Vec(4,29), Port::INPUT, module, WK_101::INPUT_CV));
  339. addOutput(Port::create<SilverPort>(Vec(43,29), Port::OUTPUT, module, WK_101::OUTPUT_CV));
  340. addInput(Port::create<BlackPort>(Vec(82,29), Port::INPUT, module, WK_101::INPUT_TOR));
  341. addOutput(Port::create<BlackPort>(Vec(121,29), Port::OUTPUT, module, WK_101::OUTPUT_TOR));
  342. for (int i = 0; i < 5; i++)
  343. {
  344. WK_Display *display = new WK_Display();
  345. display->module = module;
  346. display->index = i;
  347. display->box.pos = Vec(45, 79 + 21 * i);
  348. display->box.size = Vec(60, 20);
  349. addChild(display);
  350. widgets[i] = ParamWidget::create<WK_Param>(Vec(4 + 104 * (i%2),70 + 21 * i), module, WK_101::PARAM_1 + i, -50.0f, 50.0f, 0.0f);
  351. addParam(widgets[i]);
  352. addChild(ModuleLightWidget::create<TinyLight<BlueLight>>(Vec(21.5 + 104 * (i%2), 87.5 + 21 * i), module, WK_101::LIGHT_1 + i));
  353. }
  354. for (int i = 5; i < 12; i++)
  355. {
  356. WK_Display *display = new WK_Display();
  357. display->module = module;
  358. display->index = i;
  359. display->box.pos = Vec(45, 100 + 21 * i);
  360. display->box.size = Vec(60, 20);
  361. addChild(display);
  362. widgets[i] = ParamWidget::create<WK_Param>(Vec(108 - 104 * (i%2),91 + 21 * i), module, WK_101::PARAM_1 + i, -50.0f, 50.0f, 0.0f);
  363. addParam(widgets[i]);
  364. addChild(ModuleLightWidget::create<TinyLight<BlueLight>>(Vec(125.5 - 104 * (i%2), 108.5 + 21 * i), module, WK_101::LIGHT_1 + i));
  365. }
  366. WK_Tunings::loadTunings(plugin);
  367. }
  368. void appendContextMenu(Menu *menu) override;
  369. void step() override;
  370. };
  371. void WK101::appendContextMenu(Menu *menu) {
  372. WK_101 *module = dynamic_cast<WK_101 *>(this->module);
  373. menu->addChild(MenuEntry::create());
  374. for (unsigned int i = 0; i < tunings.size(); i++) {
  375. WK101_MenuItem *m = MenuItem::create<WK101_MenuItem>(tunings[i].name.c_str());
  376. m->module = module;
  377. m->index = i;
  378. menu->addChild(m);
  379. }
  380. }
  381. void WK101::step() {
  382. float tunings[12];
  383. int isDirty = 0;
  384. WK_101 *module = dynamic_cast<WK_101 *>(this->module);
  385. {
  386. //std::lock_guard<std::mutex> guard(module->mtx);
  387. WK_Update *upd = module->updateRing.fg();
  388. if (upd->isDirty) {
  389. for (int i = 0; i < 12; i++)
  390. tunings[i] = upd->offsets[i];
  391. upd->isDirty = false;
  392. isDirty = true;
  393. }
  394. }
  395. if (isDirty) {
  396. for (int i = 0; i < 12; i++) {
  397. if (widgets[i]->value != tunings[i])
  398. widgets[i]->setValue(tunings[i]);
  399. }
  400. }
  401. ModuleWidget::step();
  402. }
  403. struct WK_205;
  404. struct WK205_InputPort : Torpedo::PatchInputPort {
  405. WK_205 *wkModule;
  406. WK205_InputPort(WK_205 *module, unsigned int portNum):PatchInputPort((Module *)module, portNum) { wkModule = module;};
  407. void received(std::string pluginName, std::string moduleName, json_t *rootJ) override;
  408. };
  409. struct WK_205 : Module {
  410. static const int deviceCount = 5;
  411. enum ParamIds {
  412. NUM_PARAMS
  413. };
  414. enum InputIds {
  415. INPUT_CV_1,
  416. INPUT_CV_2,
  417. INPUT_CV_3,
  418. INPUT_CV_4,
  419. INPUT_CV_5,
  420. INPUT_TOR,
  421. NUM_INPUTS
  422. };
  423. enum OutputIds {
  424. OUTPUT_CV_1,
  425. OUTPUT_CV_2,
  426. OUTPUT_CV_3,
  427. OUTPUT_CV_4,
  428. OUTPUT_CV_5,
  429. NUM_OUTPUTS
  430. };
  431. enum LightIds {
  432. NUM_LIGHTS
  433. };
  434. float tunings[12];
  435. WK205_InputPort inPort = WK205_InputPort(this, INPUT_TOR);
  436. WK_205() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  437. void step() override;
  438. json_t *toJson(void) override {
  439. json_t *rootJ = json_array();
  440. for (int i = 0; i < 12; i++)
  441. json_array_append_new(rootJ, json_real(tunings[i]));
  442. return rootJ;
  443. }
  444. void fromJson(json_t *rootJ) override {
  445. int size = json_array_size(rootJ);
  446. if (!size) return;
  447. if (size > 12)
  448. size = 12;
  449. for (int i = 0; i < size; i++) {
  450. json_t *j1 = json_array_get(rootJ, i);
  451. if (j1)
  452. tunings[i] = json_number_value(j1);
  453. }
  454. }
  455. };
  456. void WK_205::step() {
  457. for (int i = 0; i < deviceCount; i++) {
  458. int quantized = floor((12.0f * inputs[INPUT_CV_1 + i].value) + 0.5f);
  459. int note = (120 + quantized) % 12;
  460. outputs[OUTPUT_CV_1 + i].value = (tunings[note] / 1200.0f) + (quantized / 12.0f);
  461. }
  462. inPort.process();
  463. }
  464. void WK205_InputPort::received(std::string pluginName, std::string moduleName, json_t *rootJ) {
  465. if (pluginName.compare(TOSTRING(SLUG))) return;
  466. if (moduleName.compare("WK")) return;
  467. int size = json_array_size(rootJ);
  468. if (!size) return;
  469. if (size > 12)
  470. size = 12;
  471. for (int i = 0; i < size; i++) {
  472. json_t *j1 = json_array_get(rootJ, i);
  473. if (j1)
  474. wkModule->tunings[i] = json_number_value(j1);
  475. }
  476. }
  477. struct WK205_MenuItem : MenuItem {
  478. WK_205 *module;
  479. int index;
  480. void onAction(EventAction &e) override {
  481. for (int i = 0; i < 12; i++)
  482. module->tunings[i] = tunings[index].offsets[i];
  483. }
  484. };
  485. struct WK205 : ModuleWidget {
  486. WK205(WK_205 *module) : ModuleWidget(module) {
  487. setPanel(SVG::load(assetPlugin(plugin, "res/WK-205.svg")));
  488. addInput(Port::create<BlackPort>(Vec(2.5,19), Port::INPUT, module, WK_205::INPUT_TOR));
  489. for (int i = 0; i < WK_205::deviceCount; i++) {
  490. addInput(Port::create<SilverPort>(Vec(2.5,63 + i * 60), Port::INPUT, module, WK_205::INPUT_CV_1 + i));
  491. addOutput(Port::create<SilverPort>(Vec(2.5,92 + i * 60), Port::OUTPUT, module, WK_205::OUTPUT_CV_1 + i));
  492. }
  493. WK_Tunings::loadTunings(plugin);
  494. }
  495. void appendContextMenu(Menu *menu) override;
  496. };
  497. void WK205::appendContextMenu(Menu *menu) {
  498. WK_205 *module = dynamic_cast<WK_205 *>(this->module);
  499. menu->addChild(MenuEntry::create());
  500. for (unsigned int i = 0; i < tunings.size(); i++) {
  501. WK205_MenuItem *m = MenuItem::create<WK205_MenuItem>(tunings[i].name.c_str());
  502. m->module = module;
  503. m->index = i;
  504. menu->addChild(m);
  505. }
  506. }
  507. } // namespace rack_plugin_SubmarineFree
  508. using namespace rack_plugin_SubmarineFree;
  509. RACK_PLUGIN_MODEL_INIT(SubmarineFree, WK101) {
  510. Model *modelWK101 = Model::create<WK_101, WK101>("Submarine (Free)", "WK-101", "WK-101 Das Wohltemperierte Klavier", QUANTIZER_TAG, TUNER_TAG);
  511. return modelWK101;
  512. }
  513. RACK_PLUGIN_MODEL_INIT(SubmarineFree, WK205) {
  514. Model *modelWK205 = Model::create<WK_205, WK205>("Submarine (Free)", "WK-205", "WK-205 Das Wohltemperierte Klavier Nano", QUANTIZER_TAG, TUNER_TAG, MULTIPLE_TAG);
  515. return modelWK205;
  516. }