/* FV1 VCV PlugIn * Copyright (C)2018 - Eduard Heidt * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "EH_modules.h" #include "FV1emu.hpp" #include #include #include #ifdef WIN32 #include "dirent_win32/dirent.h" #else #include #endif // WIN32 #include #include using namespace rack; namespace rack_plugin_EH_modules { struct FV1EmuModule : Module { enum ParamIds { POT0_PARAM, POT1_PARAM, POT2_PARAM, TPOT0_PARAM, TPOT1_PARAM, TPOT2_PARAM, FX_PREV, FX_NEXT, DRYWET_PARAM, NUM_PARAMS }; enum InputIds { POT_0, POT_1, POT_2, INPUT_L, INPUT_R, NUM_INPUTS }; enum OutputIds { OUTPUT_L, OUTPUT_R, NUM_OUTPUTS }; FV1emu fx; SchmittTrigger nextTrigger; SchmittTrigger prevTrigger; FV1EmuModule() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { loadFx(assetPlugin(plugin, "fx/demo.spn")); info("FV1EmuModule()"); } ~FV1EmuModule() { info("~FV1EmuModule()"); } void step() override { if (filesInPath.size() > 0) { if (nextTrigger.process(params[FX_NEXT].value)) { auto it = std::find(filesInPath.cbegin(), filesInPath.cend(), lastPath); ; if (it == filesInPath.cend() || ++it == filesInPath.cend()) it = filesInPath.cbegin(); loadFx(*it); } if (prevTrigger.process(params[FX_PREV].value)) { auto it = std::find(filesInPath.crbegin(), filesInPath.crend(), lastPath); if (it == filesInPath.crend() || ++it == filesInPath.crend()) it = filesInPath.crbegin(); loadFx(*it); } } //float deltaTime = engineGetSampleTime(); auto inL = inputs[INPUT_L].value; auto inR = inputs[INPUT_R].value; auto outL = 0.0f; auto outR = 0.0f; auto p0 = params[POT0_PARAM].value; auto p1 = params[POT1_PARAM].value; auto p2 = params[POT2_PARAM].value; p0 += inputs[POT_0].value * 0.1f * params[TPOT0_PARAM].value; p1 += inputs[POT_1].value * 0.1f * params[TPOT1_PARAM].value; p2 += inputs[POT_2].value * 0.1f * params[TPOT2_PARAM].value; float mix = params[DRYWET_PARAM].value; float d = clamp(1.f - mix, 0.0f, 1.0f); float w = clamp(1.f + mix, 0.0f, 1.0f); if (w > 0) { fx.run(inL * 0.1, inR * 0.1, p0, p1, p2, outL, outR); outL *= 10; outR *= 10; } outputs[OUTPUT_L].value = clamp(inputs[INPUT_L].value * d + outL * w, -10.0f, 10.0f); outputs[OUTPUT_R].value = clamp(inputs[INPUT_R].value * d + outR * w, -10.0f, 10.0f); } json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "lastPath", json_string(lastPath.c_str())); return rootJ; } void fromJson(json_t *rootJ) override { if (json_t *lastPathJ = json_object_get(rootJ, "lastPath")) { std::string file = json_string_value(lastPathJ); loadFx(file); } } std::string display; std::string lastPath; std::vector filesInPath; void loadFx(const std::string &file) { info(file.c_str()); this->lastPath = file; this->fx.load(file); filesInPath.clear(); auto dir = stringDirectory(this->lastPath); if (auto rep = opendir(dir.c_str())) { while (auto dirp = readdir(rep)) { std::string name = dirp->d_name; std::size_t found = name.find(".spn", name.length() - 5); if (found == std::string::npos) found = name.find(".spn", name.length() - 5); if (found != std::string::npos) { #ifdef _WIN32 filesInPath.push_back(dir + "\\" + name); #else filesInPath.push_back(dir + "/" + name); #endif info(name.c_str()); } } closedir(rep); } std::sort(filesInPath.begin(), filesInPath.end()); auto it = std::find(filesInPath.cbegin(), filesInPath.cend(), lastPath); auto fxIndex = it - filesInPath.cbegin(); display = std::to_string(fxIndex) + ": " + this->fx.getDisplay(); } struct MyWidget : ModuleWidget { struct MyMenuItem : MenuItem { std::function action; MyMenuItem(const char *text, std::function action) { this->text = text; this->action = action; } void onAction(EventAction &e) override { this->action(); } }; Menu *createContextMenu() override { Menu *menu = ModuleWidget::createContextMenu(); menu->addChild(new MenuLabel()); auto module = dynamic_cast(this->module); menu->addChild(new MyMenuItem("LoadFx..", [module]() { auto dir = module->lastPath.empty() ? assetLocal("") : stringDirectory(module->lastPath); auto *filters = osdialog_filters_parse("FV1-FX Asm:spn"); char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters); if (path) { module->loadFx(path); free(path); } osdialog_filters_free(filters); })); menu->addChild(new MyMenuItem("HELP...", [this]() { std::thread t([&]() { systemOpenBrowser("https://github.com/eh2k/fv1-emu/blob/master/README.md"); }); t.detach(); })); menu->addChild(new MyMenuItem("Free DSP Programs...", [this]() { std::thread t([&]() { systemOpenBrowser("https://github.com/eh2k/fv1-emu/blob/master/README.md#free-dsp-programs"); }); t.detach(); })); menu->addChild(new MenuLabel()); menu->addChild(new MyMenuItem("DEBUG", [this]() { if (this->debugText == nullptr) { this->debugText = Widget::create(Vec(box.size.x, 0)); this->debugText->box.size = Vec(250, box.size.y); this->debugText->multiline = true; this->addChild(debugText); } else { this->removeChild(this->debugText); auto tmp = this->debugText; this->debugText = nullptr; delete tmp; } })); return menu; } struct DisplayPanel : TransparentWidget { const std::string &text; std::shared_ptr font; DisplayPanel(const Vec &pos, const Vec &size, const std::string &display) : text(display) { box.pos = pos; box.size = size; font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); } void draw(NVGcontext *vg) override { nvgFontSize(vg, 12); nvgFillColor(vg, nvgRGBAf(1, 1, 1, 1)); std::stringstream stream(text); std::string line; int y = 11; while (std::getline(stream, line)) { nvgText(vg, 5, y, line.c_str(), NULL); if (y == 11) y += 5; y += 11; } } }; LedDisplay *display; MyWidget(FV1EmuModule *module) : ModuleWidget(module) { //setWidth(7); setPanel(SVG::load(assetPlugin(plugin, "res/panel.svg"))); addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); auto display = new DisplayPanel(Vec(12, 31), Vec(100, 50), module->display); addChild(display); addParam(ParamWidget::create(Vec(105, 88), module, FX_PREV, 0.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec(130, 88), module, FX_NEXT, 0.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec(13, 115), module, POT0_PARAM, 0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(64, 115), module, POT1_PARAM, 0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(115, 115), module, POT2_PARAM, 0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(21, 169), module, TPOT0_PARAM, -1.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec(72, 169), module, TPOT1_PARAM, -1.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec(123, 169), module, TPOT2_PARAM, -1.0f, 1.0f, 0.0f)); addInput(Port::create(Vec(18, 202), Port::INPUT, module, POT_0)); addInput(Port::create(Vec(69, 202), Port::INPUT, module, POT_1)); addInput(Port::create(Vec(120, 202), Port::INPUT, module, POT_2)); addParam(ParamWidget::create(Vec(67, 235), module, DRYWET_PARAM, -1.0f, 1.0f, 0.0f)); addInput(Port::create(Vec(10, 280), Port::INPUT, module, INPUT_L)); addInput(Port::create(Vec(10, 320), Port::INPUT, module, INPUT_R)); addOutput(Port::create(Vec(box.size.x - 30, 280), Port::OUTPUT, module, OUTPUT_L)); addOutput(Port::create(Vec(box.size.x - 30, 320), Port::OUTPUT, module, OUTPUT_R)); } TextField *debugText = nullptr; void draw(NVGcontext *vg) override { if (debugText) debugText->text = ((FV1EmuModule *)module)->fx.dumpState("\n"); ModuleWidget::draw(vg); } }; }; } // namespace rack_plugin_EH_modules using namespace rack_plugin_EH_modules; RACK_PLUGIN_MODEL_INIT(EH_modules, FV1Emu) { auto modelMyModule = Model::create("eh", "FV-1.emu", "FV-1.emu", EFFECT_TAG); return modelMyModule; }