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