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.

361 lines
9.3KB

  1. /* FV1 VCV PlugIn
  2. * Copyright (C)2018 - Eduard Heidt
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. *
  17. */
  18. #include "EH_modules.h"
  19. #include "FV1emu.hpp"
  20. #include <rack.hpp>
  21. #include <dsp/digital.hpp>
  22. #include <osdialog.h>
  23. #ifdef WIN32
  24. #include "dirent_win32/dirent.h"
  25. #else
  26. #include <dirent.h>
  27. #endif // WIN32
  28. #include <iterator>
  29. #include <thread>
  30. using namespace rack;
  31. namespace rack_plugin_EH_modules {
  32. struct FV1EmuModule : Module
  33. {
  34. enum ParamIds
  35. {
  36. POT0_PARAM,
  37. POT1_PARAM,
  38. POT2_PARAM,
  39. TPOT0_PARAM,
  40. TPOT1_PARAM,
  41. TPOT2_PARAM,
  42. FX_PREV,
  43. FX_NEXT,
  44. DRYWET_PARAM,
  45. NUM_PARAMS
  46. };
  47. enum InputIds
  48. {
  49. POT_0,
  50. POT_1,
  51. POT_2,
  52. INPUT_L,
  53. INPUT_R,
  54. NUM_INPUTS
  55. };
  56. enum OutputIds
  57. {
  58. OUTPUT_L,
  59. OUTPUT_R,
  60. NUM_OUTPUTS
  61. };
  62. FV1emu fx;
  63. SchmittTrigger nextTrigger;
  64. SchmittTrigger prevTrigger;
  65. FV1EmuModule() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS)
  66. {
  67. loadFx(assetPlugin(plugin, "fx/demo.spn"));
  68. info("FV1EmuModule()");
  69. }
  70. ~FV1EmuModule()
  71. {
  72. info("~FV1EmuModule()");
  73. }
  74. void step() override
  75. {
  76. if (filesInPath.size() > 0)
  77. {
  78. if (nextTrigger.process(params[FX_NEXT].value))
  79. {
  80. auto it = std::find(filesInPath.cbegin(), filesInPath.cend(), lastPath);
  81. ;
  82. if (it == filesInPath.cend() || ++it == filesInPath.cend())
  83. it = filesInPath.cbegin();
  84. loadFx(*it);
  85. }
  86. if (prevTrigger.process(params[FX_PREV].value))
  87. {
  88. auto it = std::find(filesInPath.crbegin(), filesInPath.crend(), lastPath);
  89. if (it == filesInPath.crend() || ++it == filesInPath.crend())
  90. it = filesInPath.crbegin();
  91. loadFx(*it);
  92. }
  93. }
  94. //float deltaTime = engineGetSampleTime();
  95. auto inL = inputs[INPUT_L].value;
  96. auto inR = inputs[INPUT_R].value;
  97. auto outL = 0.0f;
  98. auto outR = 0.0f;
  99. auto p0 = params[POT0_PARAM].value;
  100. auto p1 = params[POT1_PARAM].value;
  101. auto p2 = params[POT2_PARAM].value;
  102. p0 += inputs[POT_0].value * 0.1f * params[TPOT0_PARAM].value;
  103. p1 += inputs[POT_1].value * 0.1f * params[TPOT1_PARAM].value;
  104. p2 += inputs[POT_2].value * 0.1f * params[TPOT2_PARAM].value;
  105. float mix = params[DRYWET_PARAM].value;
  106. float d = clamp(1.f - mix, 0.0f, 1.0f);
  107. float w = clamp(1.f + mix, 0.0f, 1.0f);
  108. if (w > 0)
  109. {
  110. fx.run(inL * 0.1, inR * 0.1, p0, p1, p2, outL, outR);
  111. outL *= 10;
  112. outR *= 10;
  113. }
  114. outputs[OUTPUT_L].value = clamp(inputs[INPUT_L].value * d + outL * w, -10.0f, 10.0f);
  115. outputs[OUTPUT_R].value = clamp(inputs[INPUT_R].value * d + outR * w, -10.0f, 10.0f);
  116. }
  117. json_t *toJson() override
  118. {
  119. json_t *rootJ = json_object();
  120. json_object_set_new(rootJ, "lastPath", json_string(lastPath.c_str()));
  121. return rootJ;
  122. }
  123. void fromJson(json_t *rootJ) override
  124. {
  125. if (json_t *lastPathJ = json_object_get(rootJ, "lastPath"))
  126. {
  127. std::string file = json_string_value(lastPathJ);
  128. loadFx(file);
  129. }
  130. }
  131. std::string display;
  132. std::string lastPath;
  133. std::vector<std::string> filesInPath;
  134. void loadFx(const std::string &file)
  135. {
  136. info(file.c_str());
  137. this->lastPath = file;
  138. this->fx.load(file);
  139. filesInPath.clear();
  140. auto dir = stringDirectory(this->lastPath);
  141. if (auto rep = opendir(dir.c_str()))
  142. {
  143. while (auto dirp = readdir(rep))
  144. {
  145. std::string name = dirp->d_name;
  146. std::size_t found = name.find(".spn", name.length() - 5);
  147. if (found == std::string::npos)
  148. found = name.find(".spn", name.length() - 5);
  149. if (found != std::string::npos)
  150. {
  151. #ifdef _WIN32
  152. filesInPath.push_back(dir + "\\" + name);
  153. #else
  154. filesInPath.push_back(dir + "/" + name);
  155. #endif
  156. info(name.c_str());
  157. }
  158. }
  159. closedir(rep);
  160. }
  161. std::sort(filesInPath.begin(), filesInPath.end());
  162. auto it = std::find(filesInPath.cbegin(), filesInPath.cend(), lastPath);
  163. auto fxIndex = it - filesInPath.cbegin();
  164. display = std::to_string(fxIndex) + ": " + this->fx.getDisplay();
  165. }
  166. struct MyWidget : ModuleWidget
  167. {
  168. struct MyMenuItem : MenuItem
  169. {
  170. std::function<void()> action;
  171. MyMenuItem(const char *text, std::function<void()> action)
  172. {
  173. this->text = text;
  174. this->action = action;
  175. }
  176. void onAction(EventAction &e) override
  177. {
  178. this->action();
  179. }
  180. };
  181. Menu *createContextMenu() override
  182. {
  183. Menu *menu = ModuleWidget::createContextMenu();
  184. menu->addChild(new MenuLabel());
  185. auto module = dynamic_cast<FV1EmuModule *>(this->module);
  186. menu->addChild(new MyMenuItem("LoadFx..", [module]() {
  187. auto dir = module->lastPath.empty() ? assetLocal("") : stringDirectory(module->lastPath);
  188. auto *filters = osdialog_filters_parse("FV1-FX Asm:spn");
  189. char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
  190. if (path)
  191. {
  192. module->loadFx(path);
  193. free(path);
  194. }
  195. osdialog_filters_free(filters);
  196. }));
  197. menu->addChild(new MyMenuItem("HELP...", [this]() {
  198. std::thread t([&]() {
  199. systemOpenBrowser("https://github.com/eh2k/fv1-emu/blob/master/README.md");
  200. });
  201. t.detach();
  202. }));
  203. menu->addChild(new MyMenuItem("Free DSP Programs...", [this]() {
  204. std::thread t([&]() {
  205. systemOpenBrowser("https://github.com/eh2k/fv1-emu/blob/master/README.md#free-dsp-programs");
  206. });
  207. t.detach();
  208. }));
  209. menu->addChild(new MenuLabel());
  210. menu->addChild(new MyMenuItem("DEBUG", [this]() {
  211. if (this->debugText == nullptr)
  212. {
  213. this->debugText = Widget::create<LedDisplayTextField>(Vec(box.size.x, 0));
  214. this->debugText->box.size = Vec(250, box.size.y);
  215. this->debugText->multiline = true;
  216. this->addChild(debugText);
  217. }
  218. else
  219. {
  220. this->removeChild(this->debugText);
  221. auto tmp = this->debugText;
  222. this->debugText = nullptr;
  223. delete tmp;
  224. }
  225. }));
  226. return menu;
  227. }
  228. struct DisplayPanel : TransparentWidget
  229. {
  230. const std::string &text;
  231. std::shared_ptr<Font> font;
  232. DisplayPanel(const Vec &pos, const Vec &size, const std::string &display) : text(display)
  233. {
  234. box.pos = pos;
  235. box.size = size;
  236. font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf"));
  237. }
  238. void draw(NVGcontext *vg) override
  239. {
  240. nvgFontSize(vg, 12);
  241. nvgFillColor(vg, nvgRGBAf(1, 1, 1, 1));
  242. std::stringstream stream(text);
  243. std::string line;
  244. int y = 11;
  245. while (std::getline(stream, line))
  246. {
  247. nvgText(vg, 5, y, line.c_str(), NULL);
  248. if (y == 11)
  249. y += 5;
  250. y += 11;
  251. }
  252. }
  253. };
  254. LedDisplay *display;
  255. MyWidget(FV1EmuModule *module) : ModuleWidget(module)
  256. {
  257. //setWidth(7);
  258. setPanel(SVG::load(assetPlugin(plugin, "res/panel.svg")));
  259. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  260. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  261. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  262. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  263. auto display = new DisplayPanel(Vec(12, 31), Vec(100, 50), module->display);
  264. addChild(display);
  265. addParam(ParamWidget::create<TL1105>(Vec(105, 88), module, FX_PREV, 0.0f, 1.0f, 0.0f));
  266. addParam(ParamWidget::create<TL1105>(Vec(130, 88), module, FX_NEXT, 0.0f, 1.0f, 0.0f));
  267. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(13, 115), module, POT0_PARAM, 0, 1.0, 0.0));
  268. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(64, 115), module, POT1_PARAM, 0, 1.0, 0.0));
  269. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(115, 115), module, POT2_PARAM, 0, 1.0, 0.0));
  270. addParam(ParamWidget::create<Trimpot>(Vec(21, 169), module, TPOT0_PARAM, -1.0f, 1.0f, 0.0f));
  271. addParam(ParamWidget::create<Trimpot>(Vec(72, 169), module, TPOT1_PARAM, -1.0f, 1.0f, 0.0f));
  272. addParam(ParamWidget::create<Trimpot>(Vec(123, 169), module, TPOT2_PARAM, -1.0f, 1.0f, 0.0f));
  273. addInput(Port::create<PJ301MPort>(Vec(18, 202), Port::INPUT, module, POT_0));
  274. addInput(Port::create<PJ301MPort>(Vec(69, 202), Port::INPUT, module, POT_1));
  275. addInput(Port::create<PJ301MPort>(Vec(120, 202), Port::INPUT, module, POT_2));
  276. addParam(ParamWidget::create<RoundBlackKnob>(Vec(67, 235), module, DRYWET_PARAM, -1.0f, 1.0f, 0.0f));
  277. addInput(Port::create<PJ301MPort>(Vec(10, 280), Port::INPUT, module, INPUT_L));
  278. addInput(Port::create<PJ301MPort>(Vec(10, 320), Port::INPUT, module, INPUT_R));
  279. addOutput(Port::create<PJ301MPort>(Vec(box.size.x - 30, 280), Port::OUTPUT, module, OUTPUT_L));
  280. addOutput(Port::create<PJ301MPort>(Vec(box.size.x - 30, 320), Port::OUTPUT, module, OUTPUT_R));
  281. }
  282. TextField *debugText = nullptr;
  283. void draw(NVGcontext *vg) override
  284. {
  285. if (debugText)
  286. debugText->text = ((FV1EmuModule *)module)->fx.dumpState("\n");
  287. ModuleWidget::draw(vg);
  288. }
  289. };
  290. };
  291. } // namespace rack_plugin_EH_modules
  292. using namespace rack_plugin_EH_modules;
  293. RACK_PLUGIN_MODEL_INIT(EH_modules, FV1Emu) {
  294. auto modelMyModule = Model::create<FV1EmuModule, FV1EmuModule::MyWidget>("eh", "FV-1.emu", "FV-1.emu", EFFECT_TAG);
  295. return modelMyModule;
  296. }