diff --git a/Makefile b/Makefile index b5d8d920..590290c2 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ RACK_DIR ?= . VERSION = 1.dev.$(shell git rev-parse --short HEAD) -FLAGS += -DAPP_VERSION=$(VERSION) +FLAGS += -DVERSION=$(VERSION) FLAGS += -Iinclude FLAGS += -Idep/include -Idep/lib/libzip/include diff --git a/include/app/common.hpp b/include/app/common.hpp index 2d31941b..9d6193dd 100644 --- a/include/app/common.hpp +++ b/include/app/common.hpp @@ -8,8 +8,8 @@ namespace rack { namespace app { -static const char NAME[] = "VCV Rack"; -static const char VERSION[] = TOSTRING(APP_VERSION); +static const char APP_NAME[] = "VCV Rack"; +static const char APP_VERSION[] = TOSTRING(VERSION); static const char API_URL[] = "https://api.vcvrack.com"; static const float SVG_DPI = 75.0; diff --git a/include/dsp/common.hpp b/include/dsp/common.hpp index c54adb4f..5d69d450 100644 --- a/include/dsp/common.hpp +++ b/include/dsp/common.hpp @@ -33,6 +33,7 @@ inline float hann(float p) { return 0.5f * (1.f - std::cos(2*M_PI * p)); } +/** Applies the Hann window to a signal `x` */ inline void hannWindow(float *x, int len) { for (int i = 0; i < len; i++) { x[i] *= hann((float) i / (len - 1)); diff --git a/include/dsp/filter.hpp b/include/dsp/filter.hpp index c0c21699..4e0ea628 100644 --- a/include/dsp/filter.hpp +++ b/include/dsp/filter.hpp @@ -84,14 +84,14 @@ struct ExponentialSlewLimiter { /** Applies exponential smoothing to a signal with the ODE -dy/dt = x * lambda +\f$ \frac{dy}{dt} = x \lambda \f$. */ struct ExponentialFilter { float out = 0.f; - float lambda = 1.f; + float lambda = 0.f; - float process(float in) { - float y = out + (in - out) * lambda; + float process(float deltaTime, float in) { + float y = out + (in - out) * lambda * deltaTime; // If no change was detected, assume float granularity is too small and snap output to input if (out == y) out = in; @@ -99,6 +99,8 @@ struct ExponentialFilter { out = y; return out; } + + DEPRECATED float process(float in) {return process(1.f, in);} }; diff --git a/res/Core/MIDI-CV.svg b/res/Core/MIDI-CV.svg index 711bb11f..f80ea423 100644 --- a/res/Core/MIDI-CV.svg +++ b/res/Core/MIDI-CV.svg @@ -9,15 +9,423 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="40.639721mm" + width="40.639725mm" height="128.50069mm" - viewBox="0 0 40.639721 128.50069" + viewBox="0 0 40.639725 128.50069" version="1.1" - id="svg21243" - inkscape:version="0.92.2 2405546, 2018-03-11" + id="svg22336" + inkscape:version="0.92.4 5da689c313, 2019-01-14" sodipodi:docname="MIDI-CV.svg"> + id="defs22330"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id="metadata22333"> @@ -56,396 +464,736 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - transform="translate(-311.42837,33.900484)"> + transform="translate(-77.197996,-46.785373)"> + d="M 77.291704,46.879082 H 117.74401 V 175.19235 H 77.291704 Z m 0,0" + id="path11739" /> + d="M 117.83772,46.785373 H 77.197996 V 175.28606 H 117.83772 Z M 117.65031,175.09865 H 77.384033 V 46.972787 h 40.266277 z m 0,0" + id="path11741" /> + d="m 94.765228,171.1423 c -0.117133,0 -0.224621,-0.0661 -0.275608,-0.1695 l -1.003212,-2.00505 c -0.07579,-0.15296 -0.01379,-0.33761 0.137802,-0.41479 0.151585,-0.0758 0.337619,-0.0138 0.413413,0.13919 l 0.727605,1.45245 0.726224,-1.45245 c 0.07579,-0.15297 0.261828,-0.21498 0.413414,-0.13919 0.15296,0.0772 0.214972,0.26183 0.137802,0.41479 l -1.001833,2.00505 c -0.05237,0.10336 -0.159854,0.1695 -0.275607,0.1695" + id="path11803" /> + d="m 100.27049,171.1423 c -0.11714,0 -0.22324,-0.0661 -0.275607,-0.1695 l -1.003212,-2.00505 c -0.07579,-0.15296 -0.01379,-0.33761 0.139181,-0.41479 0.151582,-0.0758 0.337619,-0.0138 0.41341,0.13919 l 0.726228,1.45245 0.72623,-1.45245 c 0.0758,-0.15297 0.26182,-0.21498 0.41341,-0.13919 0.15296,0.0772 0.21497,0.26183 0.13918,0.41479 l -1.00321,2.00505 c -0.0524,0.10336 -0.15848,0.1695 -0.27561,0.1695" + id="path11805" /> + d="m 97.667375,171.1423 c -0.72347,0 -1.311892,-0.58705 -1.311892,-1.31051 0,-0.72209 0.588422,-1.31052 1.311892,-1.31052 0.286632,0 0.558105,0.091 0.78686,0.26183 0.135047,0.10197 0.16261,0.29628 0.06063,0.4327 -0.101974,0.13505 -0.294897,0.16261 -0.431324,0.0606 -0.121267,-0.091 -0.264583,-0.13919 -0.416168,-0.13919 -0.383096,0 -0.694531,0.31144 -0.694531,0.69454 0,0.38309 0.311435,0.69452 0.694531,0.69452 0.151585,0 0.294901,-0.0482 0.416168,-0.13918 0.136427,-0.10197 0.32935,-0.0744 0.431324,0.062 0.101978,0.13504 0.07442,0.32935 -0.06063,0.43132 -0.228755,0.17088 -0.500228,0.26183 -0.78686,0.26183" + id="path11807" /> - - - - - - - - - - - - - - - - - + d="m 98.087678,169.81662 c 0,0.22187 -0.179148,0.40102 -0.40101,0.40102 -0.220486,0 -0.399633,-0.17915 -0.399633,-0.40102 0,-0.22186 0.179147,-0.401 0.399633,-0.401 0.221862,0 0.40101,0.17914 0.40101,0.401" + id="path11809" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + d="m 113.95854,119.20541 c 0,-0.54983 -0.44924,-1.00045 -1.00045,-1.00045 h -7.68119 c -0.55121,0 -1.00046,0.45062 -1.00046,1.00045 v 12.17222 c 0,0.54983 0.44925,1.00045 1.00046,1.00045 h 7.68119 c 0.55121,0 1.00045,-0.45062 1.00045,-1.00045 z m 0,0" + id="path11875" /> + d="m 102.35822,119.20541 c 0,-0.54983 -0.44924,-1.00045 -0.99908,-1.00045 h -7.682564 c -0.549835,0 -1.000456,0.45062 -1.000456,1.00045 v 12.17222 c 0,0.54983 0.450621,1.00045 1.000456,1.00045 h 7.682564 c 0.54984,0 0.99908,-0.45062 0.99908,-1.00045 z m 0,0" + id="path11877" /> - - - - - - - + d="m 90.75927,119.20541 c 0,-0.54983 -0.450621,-1.00045 -1.000456,-1.00045 h -7.682561 c -0.54984,0 -0.999078,0.45062 -0.999078,1.00045 v 12.17222 c 0,0.54983 0.449238,1.00045 0.999078,1.00045 h 7.682561 c 0.549835,0 1.000456,-0.45062 1.000456,-1.00045 z m 0,0" + id="path11879" /> + + + + + + + + + + + + + + + + + + + + + + d="m 113.95854,103.20501 c 0,-0.54983 -0.44924,-1.00045 -1.00045,-1.00045 h -7.68119 c -0.55121,0 -1.00046,0.45062 -1.00046,1.00045 v 12.17222 c 0,0.55121 0.44925,1.00045 1.00046,1.00045 h 7.68119 c 0.55121,0 1.00045,-0.44924 1.00045,-1.00045 z m 0,0" + id="path11903" /> + d="m 102.35822,103.20501 c 0,-0.54983 -0.44924,-1.00045 -0.99908,-1.00045 h -7.682564 c -0.549835,0 -1.000456,0.45062 -1.000456,1.00045 v 12.17222 c 0,0.55121 0.450621,1.00045 1.000456,1.00045 h 7.682564 c 0.54984,0 0.99908,-0.44924 0.99908,-1.00045 z m 0,0" + id="path11905" /> - - - - - - - - - - - - + d="m 90.75927,103.20501 c 0,-0.54983 -0.450621,-1.00045 -1.000456,-1.00045 h -7.682561 c -0.54984,0 -0.999078,0.45062 -0.999078,1.00045 v 12.17222 c 0,0.55121 0.449238,1.00045 0.999078,1.00045 h 7.682561 c 0.549835,0 1.000456,-0.44924 1.000456,-1.00045 z m 0,0" + id="path11907" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + d="m 113.95854,135.20444 c 0,-0.54984 -0.44924,-0.99908 -1.00045,-0.99908 h -7.68119 c -0.55121,0 -1.00046,0.44924 -1.00046,0.99908 v 12.17359 c 0,0.54984 0.44925,1.00045 1.00046,1.00045 h 7.68119 c 0.55121,0 1.00045,-0.45061 1.00045,-1.00045 z m 0,0" + id="path12157" /> + d="m 102.35822,135.20444 c 0,-0.54984 -0.44924,-0.99908 -0.99908,-0.99908 h -7.682564 c -0.549835,0 -1.000456,0.44924 -1.000456,0.99908 v 12.17359 c 0,0.54984 0.450621,1.00045 1.000456,1.00045 h 7.682564 c 0.54984,0 0.99908,-0.45061 0.99908,-1.00045 z m 0,0" + id="path12159" /> - - - - - - - - - - - - + d="m 90.75927,135.20444 c 0,-0.54984 -0.450621,-0.99908 -1.000456,-0.99908 h -7.682561 c -0.54984,0 -0.999078,0.44924 -0.999078,0.99908 v 12.17359 c 0,0.54984 0.449238,1.00045 0.999078,1.00045 h 7.682561 c 0.549835,0 1.000456,-0.45061 1.000456,-1.00045 z m 0,0" + id="path12161" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + d="m 113.95854,151.20484 c 0,-0.54983 -0.44924,-1.00045 -1.00045,-1.00045 h -7.68119 c -0.55121,0 -1.00046,0.45062 -1.00046,1.00045 v 12.17359 c 0,0.54846 0.44925,0.99908 1.00046,0.99908 h 7.68119 c 0.55121,0 1.00045,-0.45062 1.00045,-0.99908 z m 0,0" + id="path12209" /> + d="m 102.35822,151.20484 c 0,-0.54983 -0.44924,-1.00045 -0.99908,-1.00045 h -7.682564 c -0.549835,0 -1.000456,0.45062 -1.000456,1.00045 v 12.17359 c 0,0.54846 0.450621,0.99908 1.000456,0.99908 h 7.682564 c 0.54984,0 0.99908,-0.45062 0.99908,-0.99908 z m 0,0" + id="path12211" /> - - - - - - - - - - - - + d="m 90.75927,151.20484 c 0,-0.54983 -0.450621,-1.00045 -1.000456,-1.00045 h -7.682561 c -0.54984,0 -0.999078,0.45062 -0.999078,1.00045 v 12.17359 c 0,0.54846 0.449238,0.99908 0.999078,0.99908 h 7.682561 c 0.549835,0 1.000456,-0.45062 1.000456,-0.99908 z m 0,0" + id="path12213" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Core/MIDI_CC.cpp b/src/Core/MIDI_CC.cpp index 184c9032..ffb6d0f3 100644 --- a/src/Core/MIDI_CC.cpp +++ b/src/Core/MIDI_CC.cpp @@ -25,6 +25,9 @@ struct MIDI_CC : Module { MIDI_CC() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + for (int i = 0; i < 16; i++) { + valueFilters[i].lambda = 1 / 0.01f; + } onReset(); } @@ -45,7 +48,8 @@ struct MIDI_CC : Module { processMessage(msg); } - float lambda = APP->engine->getSampleTime() * 100.f; + float deltaTime = APP->engine->getSampleTime(); + for (int i = 0; i < 16; i++) { if (!outputs[CC_OUTPUT + i].isConnected()) continue; @@ -53,7 +57,6 @@ struct MIDI_CC : Module { int cc = learnedCcs[i]; float value = rescale(values[cc], 0, 127, 0.f, 10.f); - valueFilters[i].lambda = lambda; // Detect behavior from MIDI buttons. if ((lastValues[i] == 0 && values[cc] == 127) || (lastValues[i] == 127 && values[cc] == 0)) { @@ -62,7 +65,7 @@ struct MIDI_CC : Module { } else { // Smooth value with filter - valueFilters[i].process(value); + valueFilters[i].process(deltaTime, value); } lastValues[i] = values[cc]; outputs[CC_OUTPUT + i].setVoltage(valueFilters[i].out); diff --git a/src/Core/MIDI_CV.cpp b/src/Core/MIDI_CV.cpp index f3ff420b..b1c12494 100644 --- a/src/Core/MIDI_CV.cpp +++ b/src/Core/MIDI_CV.cpp @@ -18,8 +18,8 @@ struct MIDI_CV : Module { PITCH_OUTPUT, MOD_OUTPUT, RETRIGGER_OUTPUT, - CLOCK_1_OUTPUT, - CLOCK_2_OUTPUT, + CLOCK_OUTPUT, + CLOCK_DIV_OUTPUT, START_OUTPUT, STOP_OUTPUT, CONTINUE_OUTPUT, @@ -31,43 +31,57 @@ struct MIDI_CV : Module { midi::InputQueue midiInput; - uint8_t mod = 0; - dsp::ExponentialFilter modFilter; + // std::vector heldNotes; + + int channels; + enum PolyMode { + ROTATE_MODE, + REUSE_MODE, + RESET_MODE, + REASSIGN_MODE, + NUM_POLY_MODES + }; + PolyMode polyMode; + + uint32_t clock = 0; + int clockDivision; + + bool pedal = false; + uint8_t notes[16] = {60}; + bool gates[128] = {}; + uint8_t velocities[128] = {}; + uint8_t aftertouches[128] = {}; + uint16_t pitch = 8192; + uint8_t mod = 0; + dsp::ExponentialFilter pitchFilter; - dsp::PulseGenerator retriggerPulse; - dsp::PulseGenerator clockPulses[2]; + dsp::ExponentialFilter modFilter; + dsp::PulseGenerator clockPulse; + dsp::PulseGenerator clockDividerPulse; + dsp::PulseGenerator retriggerPulses[16]; dsp::PulseGenerator startPulse; dsp::PulseGenerator stopPulse; dsp::PulseGenerator continuePulse; - int clock = 0; - int divisions[2]; - - struct NoteData { - uint8_t velocity = 0; - uint8_t aftertouch = 0; - }; - NoteData noteData[128]; - std::vector heldNotes; - uint8_t lastNote; - bool pedal; - bool gate; MIDI_CV() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); - heldNotes.resize(128, 0); + // heldNotes.resize(128, 0); + pitchFilter.lambda = 1 / 0.01f; + modFilter.lambda = 1 / 0.01f; onReset(); } void onReset() override { - heldNotes.clear(); - lastNote = 60; - pedal = false; - gate = false; - clock = 0; - divisions[0] = 24; - divisions[1] = 6; + // heldNotes.clear(); + // lastNote = 60; + // pedal = false; + // gate = false; + // clock = 0; + channels = 1; + polyMode = RESET_MODE; + clockDivision = 24; midiInput.reset(); } @@ -78,20 +92,26 @@ struct MIDI_CV : Module { } float deltaTime = APP->engine->getSampleTime(); - outputs[CV_OUTPUT].setVoltage((lastNote - 60) / 12.f); - outputs[GATE_OUTPUT].setVoltage(gate ? 10.f : 0.f); - outputs[VELOCITY_OUTPUT].setVoltage(rescale(noteData[lastNote].velocity, 0, 127, 0.f, 10.f)); - - outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(noteData[lastNote].aftertouch, 0, 127, 0.f, 10.f)); - pitchFilter.lambda = 100.f * deltaTime; - outputs[PITCH_OUTPUT].setVoltage(pitchFilter.process(rescale(pitch, 0, 16384, -5.f, 5.f))); - modFilter.lambda = 100.f * deltaTime; - outputs[MOD_OUTPUT].setVoltage(modFilter.process(rescale(mod, 0, 127, 0.f, 10.f))); + outputs[CV_OUTPUT].setChannels(channels); + outputs[GATE_OUTPUT].setChannels(channels); + outputs[VELOCITY_OUTPUT].setChannels(channels); + outputs[AFTERTOUCH_OUTPUT].setChannels(channels); + outputs[RETRIGGER_OUTPUT].setChannels(channels); + for (int c = 0; c < channels; c++) { + uint8_t note = notes[c]; + outputs[CV_OUTPUT].setVoltage((note - 60.f) / 12.f, c); + outputs[GATE_OUTPUT].setVoltage(gates[note] ? 10.f : 0.f, c); + outputs[VELOCITY_OUTPUT].setVoltage(rescale(velocities[note], 0, 127, 0.f, 10.f), c); + outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(aftertouches[note], 0, 127, 0.f, 10.f), c); + outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(deltaTime) ? 10.f : 0.f, c); + } - outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulse.process(deltaTime) ? 10.f : 0.f); - outputs[CLOCK_1_OUTPUT].setVoltage(clockPulses[0].process(deltaTime) ? 10.f : 0.f); - outputs[CLOCK_2_OUTPUT].setVoltage(clockPulses[1].process(deltaTime) ? 10.f : 0.f); + uint16_t pitchAdjusted = (pitch == 16383) ? 16384 : pitch; + outputs[PITCH_OUTPUT].setVoltage(pitchFilter.process(deltaTime, rescale(pitchAdjusted, 0, 1<<14, -5.f, 5.f))); + outputs[MOD_OUTPUT].setVoltage(modFilter.process(deltaTime, rescale(mod, 0, 127, 0.f, 10.f))); + outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(deltaTime) ? 10.f : 0.f); + outputs[CLOCK_DIV_OUTPUT].setVoltage(clockDividerPulse.process(deltaTime) ? 10.f : 0.f); outputs[START_OUTPUT].setVoltage(startPulse.process(deltaTime) ? 10.f : 0.f); outputs[STOP_OUTPUT].setVoltage(stopPulse.process(deltaTime) ? 10.f : 0.f); outputs[CONTINUE_OUTPUT].setVoltage(continuePulse.process(deltaTime) ? 10.f : 0.f); @@ -108,7 +128,7 @@ struct MIDI_CV : Module { // note on case 0x9: { if (msg.getValue() > 0) { - noteData[msg.getNote()].velocity = msg.getValue(); + velocities[msg.getNote()] = msg.getValue(); pressNote(msg.getNote()); } else { @@ -118,8 +138,7 @@ struct MIDI_CV : Module { } break; // channel aftertouch case 0xa: { - uint8_t note = msg.getNote(); - noteData[note].aftertouch = msg.getValue(); + aftertouches[msg.getNote()] = msg.getValue(); } break; // cc case 0xb: { @@ -127,7 +146,7 @@ struct MIDI_CV : Module { } break; // pitch wheel case 0xe: { - pitch = msg.getValue() * 128 + msg.getNote(); + pitch = ((uint16_t) msg.getValue() << 7) | msg.getNote(); } break; case 0xf: { processSystem(msg); @@ -157,16 +176,11 @@ struct MIDI_CV : Module { switch (msg.getChannel()) { // Timing case 0x8: { - if (clock % divisions[0] == 0) { - clockPulses[0].trigger(1e-3); - } - if (clock % divisions[1] == 0) { - clockPulses[1].trigger(1e-3); - } - if (++clock >= (24*16*16)) { - // Avoid overflowing the integer - clock = 0; + clockPulse.trigger(1e-3); + if (clock % clockDivision == 0) { + clockDividerPulse.trigger(1e-3); } + clock++; } break; // Start case 0xa: { @@ -180,7 +194,6 @@ struct MIDI_CV : Module { // Stop case 0xc: { stopPulse.trigger(1e-3); - // Reset timing clock = 0; } break; default: break; @@ -188,33 +201,37 @@ struct MIDI_CV : Module { } void pressNote(uint8_t note) { - // Remove existing similar note - auto it = std::find(heldNotes.begin(), heldNotes.end(), note); - if (it != heldNotes.end()) - heldNotes.erase(it); - // Push note - heldNotes.push_back(note); - lastNote = note; - gate = true; - retriggerPulse.trigger(1e-3); + int c = 0; + notes[c] = note; + gates[note] = true; + // // Remove existing similar note + // auto it = std::find(heldNotes.begin(), heldNotes.end(), note); + // if (it != heldNotes.end()) + // heldNotes.erase(it); + // // Push note + // heldNotes.push_back(note); + // lastNote = note; + // gate = true; + retriggerPulses[c].trigger(1e-3); } void releaseNote(uint8_t note) { - // Remove the note - auto it = std::find(heldNotes.begin(), heldNotes.end(), note); - if (it != heldNotes.end()) - heldNotes.erase(it); - // Hold note if pedal is pressed - if (pedal) - return; - // Set last note - if (!heldNotes.empty()) { - lastNote = heldNotes[heldNotes.size() - 1]; - gate = true; - } - else { - gate = false; - } + gates[note] = false; + // // Remove the note + // auto it = std::find(heldNotes.begin(), heldNotes.end(), note); + // if (it != heldNotes.end()) + // heldNotes.erase(it); + // // Hold note if pedal is pressed + // if (pedal) + // return; + // // Set last note + // if (!heldNotes.empty()) { + // lastNote = heldNotes[heldNotes.size() - 1]; + // gate = true; + // } + // else { + // gate = false; + // } } void pressPedal() { @@ -223,32 +240,30 @@ struct MIDI_CV : Module { void releasePedal() { pedal = false; - releaseNote(255); + // releaseNote(255); } json_t *dataToJson() override { json_t *rootJ = json_object(); - - json_t *divisionsJ = json_array(); - for (int i = 0; i < 2; i++) { - json_t *divisionJ = json_integer(divisions[i]); - json_array_append_new(divisionsJ, divisionJ); - } - json_object_set_new(rootJ, "divisions", divisionsJ); - + json_object_set_new(rootJ, "channels", json_integer(channels)); + json_object_set_new(rootJ, "polyMode", json_integer(polyMode)); + json_object_set_new(rootJ, "clockDivision", json_integer(clockDivision)); json_object_set_new(rootJ, "midi", midiInput.toJson()); return rootJ; } void dataFromJson(json_t *rootJ) override { - json_t *divisionsJ = json_object_get(rootJ, "divisions"); - if (divisionsJ) { - for (int i = 0; i < 2; i++) { - json_t *divisionJ = json_array_get(divisionsJ, i); - if (divisionJ) - divisions[i] = json_integer_value(divisionJ); - } - } + json_t *channelsJ = json_object_get(rootJ, "channels"); + if (channelsJ) + channels = json_integer_value(channelsJ); + + json_t *polyModeJ = json_object_get(rootJ, "polyMode"); + if (polyModeJ) + polyMode = (PolyMode) json_integer_value(polyModeJ); + + json_t *clockDivisionJ = json_object_get(rootJ, "clockDivision"); + if (clockDivisionJ) + clockDivision = json_integer_value(clockDivisionJ); json_t *midiJ = json_object_get(rootJ, "midi"); if (midiJ) @@ -257,6 +272,96 @@ struct MIDI_CV : Module { }; +struct ClockDivisionValueItem : MenuItem { + MIDI_CV *module; + int clockDivision; + void onAction(const event::Action &e) override { + module->clockDivision = clockDivision; + } +}; + + +struct ClockDivisionItem : MenuItem { + MIDI_CV *module; + Menu *createChildMenu() override { + Menu *menu = new Menu; + std::vector divisions = {24*4, 24*2, 24, 24/2, 24/4, 24/8, 2, 1}; + std::vector divisionNames = {"Whole", "Half", "Quarter", "8th", "16th", "32nd", "12 PPQN", "24 PPQN"}; + for (size_t i = 0; i < divisions.size(); i++) { + ClockDivisionValueItem *item = new ClockDivisionValueItem; + item->text = divisionNames[i]; + item->rightText = CHECKMARK(module->clockDivision == divisions[i]); + item->module = module; + item->clockDivision = divisions[i]; + menu->addChild(item); + } + return menu; + } +}; + + +struct ChannelValueItem : MenuItem { + MIDI_CV *module; + int channels; + void onAction(const event::Action &e) override { + module->channels = channels; + } +}; + + +struct ChannelItem : MenuItem { + MIDI_CV *module; + Menu *createChildMenu() override { + Menu *menu = new Menu; + for (int channels = 1; channels <= 16; channels++) { + ChannelValueItem *item = new ChannelValueItem; + if (channels == 1) + item->text = "Monophonic"; + else + item->text = string::f("%d", channels); + item->rightText = CHECKMARK(module->channels == channels); + item->module = module; + item->channels = channels; + menu->addChild(item); + } + return menu; + } +}; + + +struct PolyModeValueItem : MenuItem { + MIDI_CV *module; + MIDI_CV::PolyMode polyMode; + void onAction(const event::Action &e) override { + module->polyMode = polyMode; + } +}; + + +struct PolyModeItem : MenuItem { + MIDI_CV *module; + Menu *createChildMenu() override { + Menu *menu = new Menu; + std::vector polyModeNames = { + "Rotate", + "Reuse", + "Reset", + "Reassign", + }; + for (int i = 0; i < MIDI_CV::NUM_POLY_MODES; i++) { + MIDI_CV::PolyMode polyMode = (MIDI_CV::PolyMode) i; + PolyModeValueItem *item = new PolyModeValueItem; + item->text = polyModeNames[i]; + item->rightText = CHECKMARK(module->polyMode == polyMode); + item->module = module; + item->polyMode = polyMode; + menu->addChild(item); + } + return menu; + } +}; + + struct MIDI_CVWidget : ModuleWidget { MIDI_CVWidget(MIDI_CV *module) { setModule(module); @@ -273,9 +378,9 @@ struct MIDI_CVWidget : ModuleWidget { addOutput(createOutput(mm2px(Vec(4.61505, 76.1449)), module, MIDI_CV::AFTERTOUCH_OUTPUT)); addOutput(createOutput(mm2px(Vec(16.214, 76.1449)), module, MIDI_CV::PITCH_OUTPUT)); addOutput(createOutput(mm2px(Vec(27.8143, 76.1449)), module, MIDI_CV::MOD_OUTPUT)); - addOutput(createOutput(mm2px(Vec(4.61505, 92.1439)), module, MIDI_CV::RETRIGGER_OUTPUT)); - addOutput(createOutput(mm2px(Vec(16.214, 92.1439)), module, MIDI_CV::CLOCK_1_OUTPUT)); - addOutput(createOutput(mm2px(Vec(27.8143, 92.1439)), module, MIDI_CV::CLOCK_2_OUTPUT)); + addOutput(createOutput(mm2px(Vec(4.61505, 92.1439)), module, MIDI_CV::CLOCK_OUTPUT)); + addOutput(createOutput(mm2px(Vec(16.214, 92.1439)), module, MIDI_CV::CLOCK_DIV_OUTPUT)); + addOutput(createOutput(mm2px(Vec(27.8143, 92.1439)), module, MIDI_CV::RETRIGGER_OUTPUT)); addOutput(createOutput(mm2px(Vec(4.61505, 108.144)), module, MIDI_CV::START_OUTPUT)); addOutput(createOutput(mm2px(Vec(16.214, 108.144)), module, MIDI_CV::STOP_OUTPUT)); addOutput(createOutput(mm2px(Vec(27.8143, 108.144)), module, MIDI_CV::CONTINUE_OUTPUT)); @@ -290,40 +395,20 @@ struct MIDI_CVWidget : ModuleWidget { void appendContextMenu(Menu *menu) override { MIDI_CV *module = dynamic_cast(this->module); - struct ClockDivisionItem : MenuItem { - MIDI_CV *module; - int index; - int division; - void onAction(const event::Action &e) override { - module->divisions[index] = division; - } - }; + menu->addChild(new MenuEntry); - struct ClockItem : MenuItem { - MIDI_CV *module; - int index; - Menu *createChildMenu() override { - Menu *menu = new Menu; - std::vector divisions = {24*4, 24*2, 24, 24/2, 24/4, 24/8, 2, 1}; - std::vector divisionNames = {"Whole", "Half", "Quarter", "8th", "16th", "32nd", "12 PPQN", "24 PPQN"}; - for (size_t i = 0; i < divisions.size(); i++) { - ClockDivisionItem *item = createMenuItem(divisionNames[i], CHECKMARK(module->divisions[index] == divisions[i])); - item->module = module; - item->index = index; - item->division = divisions[i]; - menu->addChild(item); - } - return menu; - } - }; + ClockDivisionItem *clockDivisionItem = new ClockDivisionItem; + clockDivisionItem->text = "CLK/N divider"; + clockDivisionItem->module = module; + menu->addChild(clockDivisionItem); - menu->addChild(construct()); - for (int i = 0; i < 2; i++) { - ClockItem *item = createMenuItem(string::f("CLK %d rate", i + 1)); - item->module = module; - item->index = i; - menu->addChild(item); - } + ChannelItem *channelItem = createMenuItem("Polyphony channels"); + channelItem->module = module; + menu->addChild(channelItem); + + PolyModeItem *polyModeItem = createMenuItem("Polyphony mode"); + polyModeItem->module = module; + menu->addChild(polyModeItem); } }; diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 6677a0b3..7e3df0a2 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -399,7 +399,7 @@ void ModuleWidget::fromJson(json_t *rootJ) { if (versionJ) { std::string version = json_string_value(versionJ); if (version != model->plugin->version) { - INFO("Patch created with %s version %s, using version %s.", pluginSlug.c_str(), version.c_str(), model->plugin->version.c_str()); + INFO("Patch created with %s v%s, currently using v%s.", pluginSlug.c_str(), version.c_str(), model->plugin->version.c_str()); } } @@ -677,7 +677,7 @@ void ModuleWidget::createContextMenu() { assert(model); ui::MenuLabel *menuLabel = new ui::MenuLabel; - menuLabel->text = model->plugin->name + " " + model->name + " " + model->plugin->version; + menuLabel->text = model->plugin->name + " " + model->name + " v" + model->plugin->version; menu->addChild(menuLabel); ModuleResetItem *resetItem = new ModuleResetItem; diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index 6cc84b66..90e64db2 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -58,12 +58,13 @@ void Scene::step() { // Autosave every 15 seconds int frame = APP->window->frame; if (frame > 0 && frame % (60 * 15) == 0) { + // DEBUG("frame %d", frame); APP->patch->save(asset::user("autosave.vcv")); settings.save(asset::user("settings.json")); } // Set zoom every few frames - if (APP->window->frame % 10 == 0) + if (frame % 10 == 0) zoomWidget->setZoom(std::round(settings.zoom * 100) / 100); // Request latest version from server @@ -75,7 +76,7 @@ void Scene::step() { // Version popup message if (!latestVersion.empty()) { - std::string versionMessage = string::f("Rack %s is available.\n\nYou have Rack %s.\n\nClose Rack and download new version on the website?", latestVersion.c_str(), app::VERSION); + std::string versionMessage = string::f("Rack v%s is available.\n\nYou have Rack v%s.\n\nClose Rack and download new version on the website?", latestVersion.c_str(), app::APP_VERSION); if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, versionMessage.c_str())) { std::thread t(system::openBrowser, "https://vcvrack.com/"); t.detach(); @@ -172,7 +173,7 @@ void Scene::runCheckVersion() { json_t *versionJ = json_object_get(versionResJ, "version"); if (versionJ) { std::string version = json_string_value(versionJ); - if (version != app::VERSION) { + if (version != app::APP_VERSION) { latestVersion = version; } } diff --git a/src/main.cpp b/src/main.cpp index bc0620e3..6e8e48fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -65,7 +65,7 @@ int main(int argc, char *argv[]) { logger::init(devMode); // Log environment - INFO("%s %s", app::NAME, app::VERSION); + INFO("%s v%s", app::APP_NAME, app::APP_VERSION); if (devMode) INFO("Development mode"); INFO("System directory: %s", asset::systemDir.c_str()); diff --git a/src/patch.cpp b/src/patch.cpp index d1076d6c..2f929469 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -223,7 +223,7 @@ json_t *PatchManager::toJson() { json_t *rootJ = json_object(); // version - json_t *versionJ = json_string(app::VERSION); + json_t *versionJ = json_string(app::APP_VERSION); json_object_set_new(rootJ, "version", versionJ); // Merge with RackWidget JSON @@ -243,8 +243,8 @@ void PatchManager::fromJson(json_t *rootJ) { json_t *versionJ = json_object_get(rootJ, "version"); if (versionJ) version = json_string_value(versionJ); - if (version != app::VERSION) { - INFO("Patch made with Rack version %s, current Rack version is %s", version.c_str(), app::VERSION); + if (version != app::APP_VERSION) { + INFO("Patch was made with Rack v%s, current Rack version is v%s", version.c_str(), app::APP_VERSION); } // Detect old patches with ModuleWidget::params/inputs/outputs indices. diff --git a/src/plugin.cpp b/src/plugin.cpp index e073f390..52fc7b7c 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -132,7 +132,7 @@ static bool loadPlugin(std::string path) { // Add plugin to list plugins.push_back(plugin); - INFO("Loaded plugin %s %s from %s", plugin->slug.c_str(), plugin->version.c_str(), libraryFilename.c_str()); + INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), libraryFilename.c_str()); return true; } diff --git a/src/window.cpp b/src/window.cpp index 66fb4d67..84cfa0d7 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -332,9 +332,9 @@ void Window::run() { // Set window title std::string windowTitle; - windowTitle = app::NAME; - windowTitle += " "; - windowTitle += app::VERSION; + windowTitle = app::APP_NAME; + windowTitle += " v"; + windowTitle += app::APP_VERSION; if (!APP->patch->path.empty()) { windowTitle += " - "; windowTitle += string::filename(APP->patch->path);