/* 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;
}