diff --git a/KoralfxVCV.json b/KoralfxVCV.json new file mode 100644 index 0000000..f5d7402 --- /dev/null +++ b/KoralfxVCV.json @@ -0,0 +1,25 @@ +{ + "slug": "KoralfxVCV", + "name": "Koralfx VCV", + "author": "Tomek Sosnowski", + "license": "BSD 3-clause", + "version": "0.5.8", + "homepage": "https://github.com/koralfx/KoralfxVCV", + "donation": "https://www.paypal.me/koralfx/", + "manual": "https://github.com/koralfx/KoralfxVCV/blob/master/README.md", + "source": "https://github.com/koralfx/KoralfxVCV", + "downloads": { + "win": { + "download": "https://example.com/AudibleInstruments-0.5.0-win.zip", + "sha256": "9372ce3f8ef42d7e874beda36f7c051b3d7de9c904e259a5fc9dba8dc664bf65" + }, + "lin": { + "download": "https://example.com/AudibleInstruments-0.5.0-lin.zip", + "sha256": "238145156cc4e11b3ca6d750df38ca2daf3e09648d9c7db5f23e9518c1ccf5dc" + }, + "mac": { + "download": "https://example.com/AudibleInstruments-0.5.0-mac.zip", + "sha256": "c19fcdfd07dc6184ce30953bf9adb2b4a77d20ef66d2b1c6a6024c2ca4ff505b" + } + } + } \ No newline at end of file diff --git a/KoralfxVCV_Plugins.png b/KoralfxVCV_Plugins.png index ccd757e..6e71106 100644 Binary files a/KoralfxVCV_Plugins.png and b/KoralfxVCV_Plugins.png differ diff --git a/Makefile b/Makefile index 6f9af01..cdb8891 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ SLUG = KoralfxVCV # Must follow the format in the Versioning section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html -VERSION = 0.5.7 +VERSION = 0.5.8 # FLAGS will be passed to both the C and C++ compiler FLAGS += @@ -25,3 +25,16 @@ RACK_DIR ?= ../.. # Include the VCV Rack plugin Makefile framework include $(RACK_DIR)/plugin.mk + + +.PHONY: dist + +dist: all + mkdir -p dist/$(SLUG) + cp plugin.* dist/$(SLUG)/ + cp LICENSE.txt dist/$(SLUG)/ + cp README.md dist/$(SLUG)/ + cp *.png dist/$(SLUG)/ + mkdir -p dist/$(SLUG)/res/ + cp -R res/*.svg dist/$(SLUG)/res/ + cd dist && zip -5 -r $(SLUG)-$(VERSION)-$(ARCH).zip $(SLUG) \ No newline at end of file diff --git a/README.md b/README.md index 5578e01..b03f1a5 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ You can support the development by making a donation, it will be appreciated! ## Mixovnik -A constant-power panning 16-channel mixer width two AUX sends, external mix input and CV Pan inputs (bipolar mode). - +A constant-power panning 16-channel mixer width two AUX sends and returns, external mix input, CV Vol (unipolar mode) and CV Pan (bipolar mode) inputs. Also with mute and solo buttons. ## Quantovnik A pitch quantizer with note info and bipolar-unipolar converter. Also with octave and course shift. - +## Beatovnik +Based on the external BPM clock, the module generates 2x and 4x multiply and 2x and 4x divide clock signals. In addition, you can set the pulse width (proportionally for all outputs). The last important thing - CV output compatible 100% with Fundamental Delay CV Time input. This is a CV to time conversion, of course based on BPM. Enjoy! \ No newline at end of file diff --git a/res/Beatovnik-Dark.svg b/res/Beatovnik-Dark.svg new file mode 100644 index 0000000..49a0d5b --- /dev/null +++ b/res/Beatovnik-Dark.svg @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/Beatovnik-Light.svg b/res/Beatovnik-Light.svg new file mode 100644 index 0000000..5dd78c1 --- /dev/null +++ b/res/Beatovnik-Light.svg @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/Mixovnik-Dark.svg b/res/Mixovnik-Dark.svg index b2f7571..4c972d5 100644 --- a/res/Mixovnik-Dark.svg +++ b/res/Mixovnik-Dark.svg @@ -382,9 +382,9 @@ C673.9,276.2,673.1,277.9,671,277.9z M671,272.1c-1.4,0-1.6,1.2-1.6,2.3c0,1.1,0.3,2.3,1.6,2.3s1.6-1.2,1.6-2.3 C672.6,273.3,672.4,272.1,671,272.1z"/> - - - + - @@ -867,48 +867,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/Mixovnik-Light.svg b/res/Mixovnik-Light.svg index db80521..14c69f8 100644 --- a/res/Mixovnik-Light.svg +++ b/res/Mixovnik-Light.svg @@ -411,88 +411,22 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -736,48 +670,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -891,22 +827,88 @@ C652,130.3,650,128.1,647.5,128.1z"/> - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Beatovnik.cpp b/src/Beatovnik.cpp new file mode 100644 index 0000000..a5e6034 --- /dev/null +++ b/src/Beatovnik.cpp @@ -0,0 +1,437 @@ +#include "Koralfx.hpp" + +struct Beatovnik : Module { + enum ParamIds { + WIDTH_PARAM, + TIME_PARAM, + TIME_T_PARAM, + NUM_PARAMS + }; + enum InputIds { + CLOCK_INPUT, + NUM_INPUTS + }; + enum OutputIds { + CV_OUTPUT, + BEAT2X_MUL_OUTPUT, + BEAT4X_MUL_OUTPUT, + BEAT2X_DIV_OUTPUT, + BEAT4X_DIV_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + CLOCK_LIGHT, + CLOCK_LOCK_LIGHT, + BEAT2X_MUL_LIGHT, + BEAT4X_MUL_LIGHT, + BEAT2X_DIV_LIGHT, + BEAT4X_DIV_LIGHT, + NOTE_LIGHT, + NUM_LIGHTS = NOTE_LIGHT + 6 + }; + + enum DynamicViewMode { + ACTIVE_LOW, + ACTIVE_HIGH + }; + int panelStyle = 0; + json_t *toJson() override; + void fromJson(json_t *rootJ) override; + + bool inMemory = false; + + bool beatLock = false; + float beatTime = 0; + int beatCount = 0; + int beatCountMemory = 0; + float beatMemory = 0; + + int stepper =0; + bool stepperInc = false; + + float gateWidth2xMul = 0; + float gateWidth4xMul = 0; + float gateWidth2xDiv = 0; + float gateWidth4xDiv = 0; + + bool gateDec2xMul = false; + bool gateDec4xMul = false; + bool gateDec2xDiv = false; + bool gateDec4xDiv = false; + + Beatovnik() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} + void step() override; +}; + +//------------------------------------------------------------------------------------------------------ +//------------------------------------------------- MAIN ---------------------------------------------- +//------------------------------------------------------------------------------------------------------ + +void Beatovnik::step() { + +float deltaTime = engineGetSampleTime(); + + +if (inputs[CLOCK_INPUT].active) { + + float clockInput = inputs[CLOCK_INPUT].value; + + //A rising slope + if (clockInput > 1 && !inMemory) { + stepper = 0; + beatCount ++; + lights[CLOCK_LIGHT].value = 1; + inMemory = true; + //Set divide with tempo knob + int choice = round(params[TIME_PARAM].value); + float divide = 0.25 * pow(2,choice) * ((params[TIME_T_PARAM].value) ? 0.333 : 1); + float CV = (log(beatMemory * 1000 * divide) /log(10 / 1e-3)); + outputs[CV_OUTPUT].value = CV * 10; + + for (int i = 0; i < 6; i++) { + lights[NOTE_LIGHT + i].value = ((choice == i) ? 1 : 0); + } + + + //BPM is locked + if (beatCount == 2) { + lights[CLOCK_LOCK_LIGHT].value = 1; + beatLock = true; + beatMemory = beatTime; + } + + //BPM lost + if (beatCount > 2) { + if (fabs(beatMemory - beatTime) > 0.0001) { + beatLock = false; + beatCount = 0; + lights[CLOCK_LOCK_LIGHT].value = 0; + stepper = 0; + stepperInc = false; + for (int i = 0; i < 13; i++) { + lights[CLOCK_LIGHT + i].value = 0; + } + } + } + beatTime = 0; + } + + + + //Falling slope + if (clockInput <= 0 && inMemory) { + lights[CLOCK_LIGHT].value = 0; + inMemory = false; + } + + //When BPM is locked... + if (beatLock) { + int division = 16; + int bM = round((beatMemory / division) * 1000000); + int bT = round(beatTime * 1000000); + + if ( bT % bM <= deltaTime * 1000000) { + stepperInc = true; + } + + //MULTIPLY 4X + int noteRate = 4; + if (stepperInc) { + if ( stepper % noteRate == 0) { + lights[BEAT4X_MUL_LIGHT].value = 1; + outputs[BEAT4X_MUL_OUTPUT].value = 10; + gateDec4xMul = true; + } + } + + //led off + if (gateDec4xMul) gateWidth4xMul -= 1; + if (gateWidth4xMul <= 0 ) { + gateDec4xMul = false; + gateWidth4xMul = params[WIDTH_PARAM].value * (beatMemory / deltaTime / noteRate); + lights[BEAT4X_MUL_LIGHT].value = 0; + outputs[BEAT4X_MUL_OUTPUT].value = 0; + } + + + //MULTIPLY 2X + noteRate = 8; + if (stepperInc) { + if ( stepper % noteRate == 0) { + lights[BEAT2X_MUL_LIGHT].value = 1; + outputs[BEAT2X_MUL_OUTPUT].value = 10; + gateDec2xMul = true; + } + } + + //led off + if (gateDec2xMul) gateWidth2xMul -= 1; + if (gateWidth2xMul <= 0 ) { + gateDec2xMul = false; + gateWidth2xMul = params[WIDTH_PARAM].value * (beatMemory / deltaTime / noteRate) * 4; + lights[BEAT2X_MUL_LIGHT].value = 0; + outputs[BEAT2X_MUL_OUTPUT].value = 0; + } + + + + if (beatCountMemory != beatCount) { + + //DIV 2X + if (stepperInc) { + if ( beatCount % 2 == 0) { + lights[BEAT2X_DIV_LIGHT].value = 1; + outputs[BEAT2X_DIV_OUTPUT].value = 10; + gateDec2xDiv = true; + } + } + + //DIV 4X + if (stepperInc) { + if ( beatCount % 4 == 0) { + lights[BEAT4X_DIV_LIGHT].value = 1; + outputs[BEAT4X_DIV_OUTPUT].value = 10; + gateDec4xDiv = true; + } + } + + } + + + //DIV 2X led off + if (gateDec2xDiv) gateWidth2xDiv -= 1; + if (gateWidth2xDiv <= 0 ) { + gateDec2xDiv = false; + gateWidth2xDiv = params[WIDTH_PARAM].value * (beatMemory / deltaTime / noteRate) * 16; + lights[BEAT2X_DIV_LIGHT].value = 0; + outputs[BEAT2X_DIV_OUTPUT].value = 0; + } + + //DIV 4X led off + if (gateDec4xDiv) gateWidth4xDiv -= 1; + if (gateWidth4xDiv <= 0 ) { + gateDec4xDiv = false; + gateWidth4xDiv = params[WIDTH_PARAM].value * (beatMemory / deltaTime / noteRate) * 32; + lights[BEAT4X_DIV_LIGHT].value = 0; + outputs[BEAT4X_DIV_OUTPUT].value = 0; + } + + + //Nest step of stepper + if (stepperInc) { + stepper++; + stepperInc = false; + } + + + } //end of beatlock routine + + + + beatTime += deltaTime; + + //when beat is lost + if (beatTime > 2 ) { + beatLock = false; + beatCount = 0; + lights[CLOCK_LOCK_LIGHT].value = 0; + stepper = 0; + stepperInc = false; + for (int i = 0; i < 13; i++) { + lights[CLOCK_LIGHT + i].value = 0; + } + } + + + beatCountMemory = beatCount; + + + } else { + + beatLock = false; + beatCount = 0; + //lights[CLOCK_LOCK_LIGHT].value = 0; + //lights[BEAT4X_MUL_LIGHT].value = 0; + + for (int i = 0; i < 13; i++) { + lights[CLOCK_LIGHT + i].value = 0; + } + + } //end of input active routine + + + +} + + +json_t *Beatovnik::toJson() { + json_t *rootJ = json_object(); + json_object_set_new(rootJ, "panelStyle", json_integer(panelStyle)); + + return rootJ; +} + +void Beatovnik::fromJson(json_t *rootJ) { + json_t *j_panelStyle = json_object_get(rootJ, "panelStyle"); + panelStyle = json_integer_value(j_panelStyle); +} + + +//------------------------------------------------------------------------------------------------------ +//-------------------------------------------------- GUI ---------------------------------------------- +//------------------------------------------------------------------------------------------------------ + + +//Panel Border and Dynamic panel code is adapted from The Dexter by Dale Johnson +//https://github.com/ValleyAudio + +struct PanelBorder : TransparentWidget { + void draw(NVGcontext *vg) override { + NVGcolor borderColor = nvgRGBAf(0.5, 0.5, 0.5, 0.5); + nvgBeginPath(vg); + nvgRect(vg, 0.5, 0.5, box.size.x - 1.0, box.size.y - 1.0); + nvgStrokeColor(vg, borderColor); + nvgStrokeWidth(vg, 1.0); + nvgStroke(vg); + } +}; + +struct DynamicPanelBeatovnik : FramebufferWidget { + int* mode; + int oldMode; + std::vector> panels; + SVGWidget* panel; + + DynamicPanelBeatovnik() { + mode = nullptr; + oldMode = -1; + panel = new SVGWidget(); + addChild(panel); + addPanel(SVG::load(assetPlugin(plugin, "res/Beatovnik-Dark.svg"))); + addPanel(SVG::load(assetPlugin(plugin, "res/Beatovnik-Light.svg"))); + PanelBorder *pb = new PanelBorder(); + pb->box.size = box.size; + addChild(pb); + } + + void addPanel(std::shared_ptr svg) { + panels.push_back(svg); + if(!panel->svg) { + panel->setSVG(svg); + box.size = panel->box.size.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE); + } + } + + void step() override { + if (nearf(gPixelRatio, 1.f)) { + oversample = 2.f; + } + if(mode != nullptr && *mode != oldMode) { + panel->setSVG(panels[*mode]); + oldMode = *mode; + dirty = true; + } + } +}; + + +BeatovnikWidget::BeatovnikWidget() { + Beatovnik *module = new Beatovnik(); + setModule(module); + box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); + + { + DynamicPanelBeatovnik *panel = new DynamicPanelBeatovnik(); + panel->box.size = box.size; + panel->mode = &module->panelStyle; + addChild(panel); + } + //Standard screws + //addChild(createScrew(Vec(RACK_GRID_WIDTH, 0))); + //addChild(createScrew(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + //addChild(createScrew(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + //addChild(createScrew(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + //Knobs + addParam(createParam(Vec(51, 249), module, Beatovnik::WIDTH_PARAM, 0.01, 0.99, 0.5)); + addParam(createParam(Vec(6, 73), module, Beatovnik::TIME_PARAM, -0.3, 5.3, 2)); + + //Buttons + addParam(createParam(Vec(54,60), module, Beatovnik::TIME_T_PARAM, 0, 1, 0)); + + + //Inputs + addInput(createInput (Vec(13, 251), module, Beatovnik::CLOCK_INPUT)); + + //Outputs + addOutput(createOutput(Vec(52, 88), module, Beatovnik::CV_OUTPUT)); + addOutput(createOutput(Vec(14, 145), module, Beatovnik::BEAT2X_MUL_OUTPUT)); + addOutput(createOutput(Vec(52, 145), module, Beatovnik::BEAT4X_MUL_OUTPUT)); + addOutput(createOutput(Vec(14, 205), module, Beatovnik::BEAT2X_DIV_OUTPUT)); + addOutput(createOutput(Vec(52, 205), module, Beatovnik::BEAT4X_DIV_OUTPUT)); + + //Lights + addChild(createLight>(Vec(17, 323), module, Beatovnik::CLOCK_LIGHT)); + addChild(createLight>(Vec(69, 321), module, Beatovnik::CLOCK_LOCK_LIGHT)); + + + addChild(createLight>(Vec(30, 176), module, Beatovnik::BEAT2X_MUL_LIGHT)); + addChild(createLight>(Vec(69, 176), module, Beatovnik::BEAT4X_MUL_LIGHT)); + addChild(createLight>(Vec(30, 236), module, Beatovnik::BEAT2X_DIV_LIGHT)); + addChild(createLight>(Vec(69, 236), module, Beatovnik::BEAT4X_DIV_LIGHT)); + + + for (int i = 0; i < 6; i++) { + addChild(createLight>(Vec(12+i*12, 53), module, Beatovnik::NOTE_LIGHT + i)); + } + +} + + +//------------------------------------------------------------------------------------------------------ +//-------------------------------------------- Context Menu -------------------------------------------- +//------------------------------------------------------------------------------------------------------ + +//Context menu code is adapted from The Dexter by Dale Johnson +//https://github.com/ValleyAudio + +struct BeatovnikPanelStyleItem : MenuItem { + Beatovnik* teatovnik; + int panelStyle; + void onAction(EventAction &e) override { + teatovnik->panelStyle = panelStyle; + } + void step() override { + rightText = (teatovnik->panelStyle == panelStyle) ? "✔" : ""; + } +}; + +Menu* BeatovnikWidget::createContextMenu() { + Menu* menu = ModuleWidget::createContextMenu(); + Beatovnik* teatovnik = dynamic_cast(module); + assert(teatovnik); + + // Panel Style + MenuLabel *panelStyleSpacerLabel = new MenuLabel(); + menu->addChild(panelStyleSpacerLabel); + MenuLabel *panelStyleLabel = new MenuLabel(); + panelStyleLabel->text = "Frame of mind"; + menu->addChild(panelStyleLabel); + + BeatovnikPanelStyleItem *darkPanelStyleItem = new BeatovnikPanelStyleItem(); + darkPanelStyleItem->text = "Dark Calm Night"; + darkPanelStyleItem->teatovnik = teatovnik; + darkPanelStyleItem->panelStyle = 0; + menu->addChild(darkPanelStyleItem); + + BeatovnikPanelStyleItem *lightPanelStyleItem = new BeatovnikPanelStyleItem(); + lightPanelStyleItem->text = "Happy Bright Day"; + lightPanelStyleItem->teatovnik = teatovnik; + lightPanelStyleItem->panelStyle = 1; + menu->addChild(lightPanelStyleItem); + + + + return menu; +} + diff --git a/src/Koralfx.cpp b/src/Koralfx.cpp index 3d856af..30fac1a 100644 --- a/src/Koralfx.cpp +++ b/src/Koralfx.cpp @@ -13,14 +13,14 @@ void init(rack::Plugin *p) { //p->slug = TOSTRING(SLUG); //p->version = TOSTRING(VERSION); p->slug = "KoralfxVCV"; - p->version = "0.5.7"; + p->version = "0.5.8"; p->website = "https://github.com/koralfx/KoralfxVCV"; p->manual = "https://github.com/koralfx/KoralfxVCV/blob/master/README.md"; // For each module, specify the ModuleWidget subclass, manufacturer slug (for saving in patches), manufacturer human-readable name, module slug, and module name p->addModel(createModel("KoralfxVCV", "Quantovnik", "Quantovnik", UTILITY_TAG)); - p->addModel(createModel("KoralfxVCV", "Mixovnik", "Mixovnik", MIXER_TAG, UTILITY_TAG)); - + p->addModel(createModel("KoralfxVCV", "Mixovnik", "Mixovnik", MIXER_TAG)); + p->addModel(createModel("KoralfxVCV", "Beatovnik", "Beatovnik", UTILITY_TAG)); // Any other plugin initialization may go here. // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. diff --git a/src/Koralfx.hpp b/src/Koralfx.hpp index 9dd5a5b..17b2ff9 100644 --- a/src/Koralfx.hpp +++ b/src/Koralfx.hpp @@ -19,6 +19,10 @@ struct MixovnikWidget : ModuleWidget { Menu* createContextMenu() override; }; +struct BeatovnikWidget : ModuleWidget { + BeatovnikWidget(); + Menu* createContextMenu() override; +}; ////////////////////////////////////// //MODULE COMPONENTS