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.

550 lines
15KB

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