diff --git a/ext/osdialog b/ext/osdialog index 4dd22f56..015d0206 160000 --- a/ext/osdialog +++ b/ext/osdialog @@ -1 +1 @@ -Subproject commit 4dd22f56d6b733c8de13d6e8b12f13390aa5782e +Subproject commit 015d020615e8169d2f227ad385c5f2aa1e091fd1 diff --git a/src/core/MidiCCToCV.cpp b/src/core/MidiCCToCV.cpp index 4995ff1b..706b84b8 100644 --- a/src/core/MidiCCToCV.cpp +++ b/src/core/MidiCCToCV.cpp @@ -4,10 +4,22 @@ #include "core.hpp" #include "MidiIO.hpp" -/* - * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod weel to - * CV - */ +struct CCValue { + int val = 0; // Controller value + TransitionSmoother tSmooth; + int num = 0; // Controller number + bool numInited = false; // Num inited by config file + bool numSelected = false; // Text field selected for midi learn + bool changed = false; // Value has been changed by midi message (only if it is in sync!) + int sync = 0; // Output value sync (implies diff) + bool syncFirst = true; // First value after sync was reset + + void resetSync() { + sync = 0; + syncFirst = true; + } +}; + struct MIDICCToCVInterface : MidiIO, Module { enum ParamIds { NUM_PARAMS @@ -22,27 +34,15 @@ struct MIDICCToCVInterface : MidiIO, Module { NUM_LIGHTS = 16 }; - int cc[NUM_OUTPUTS]; - int ccNum[NUM_OUTPUTS]; - int ccSync[NUM_OUTPUTS]; - bool ccSyncFirst[NUM_OUTPUTS]; - bool ccNumInited[NUM_OUTPUTS]; - bool onFocus[NUM_OUTPUTS]; - + CCValue cc[NUM_OUTPUTS]; MIDICCToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { for (int i = 0; i < NUM_OUTPUTS; i++) { - cc[i] = 0; - ccNum[i] = i; - ccSync[i] = 0; - ccSyncFirst[i] = true; - onFocus[i] = false; + cc[i].num = i; } } - ~MIDICCToCVInterface() { - - } + ~MIDICCToCVInterface() {} void step() override; @@ -54,9 +54,9 @@ struct MIDICCToCVInterface : MidiIO, Module { json_t *rootJ = json_object(); addBaseJson(rootJ); for (int i = 0; i < NUM_OUTPUTS; i++) { - json_object_set_new(rootJ, ("ccNum" + std::to_string(i)).c_str(), json_integer(ccNum[i])); + json_object_set_new(rootJ, ("ccNum" + std::to_string(i)).c_str(), json_integer(cc[i].num)); if (outputs[i].active) { - json_object_set_new(rootJ, ("ccVal" + std::to_string(i)).c_str(), json_integer(cc[i])); + json_object_set_new(rootJ, ("ccVal" + std::to_string(i)).c_str(), json_integer(cc[i].val)); } } return rootJ; @@ -67,13 +67,15 @@ struct MIDICCToCVInterface : MidiIO, Module { for (int i = 0; i < NUM_OUTPUTS; i++) { json_t *ccNumJ = json_object_get(rootJ, ("ccNum" + std::to_string(i)).c_str()); if (ccNumJ) { - ccNum[i] = json_integer_value(ccNumJ); - ccNumInited[i] = true; + cc[i].num = json_integer_value(ccNumJ); + cc[i].numInited = true; } json_t *ccValJ = json_object_get(rootJ, ("ccVal" + std::to_string(i)).c_str()); if (ccValJ) { - cc[i] = json_integer_value(ccValJ); + cc[i].val = json_integer_value(ccValJ); + cc[i].tSmooth.set((cc[i].val / 127.0 * 10.0), (cc[i].val / 127.0 * 10.0)); + cc[i].resetSync(); } } @@ -97,19 +99,25 @@ void MIDICCToCVInterface::step() { } } + for (int i = 0; i < NUM_OUTPUTS; i++) { - lights[i].setBrightness(ccSync[i] / 127.0); + lights[i].setBrightness(cc[i].sync / 127.0); + + if (cc[i].changed) { + cc[i].tSmooth.set(outputs[i].value, (cc[i].val / 127.0 * 10.0), int(engineGetSampleRate() / 32)); + cc[i].changed = false; + } - outputs[i].value = cc[i] / 127.0 * 10.0; + outputs[i].value = cc[i].tSmooth.next(); } } void MIDICCToCVInterface::resetMidi() { for (int i = 0; i < NUM_OUTPUTS; i++) { - cc[i] = 0; - ccSync[i] = 0; - ccSyncFirst[i] = true; + cc[i].val = 0; + cc[i].resetSync(); + cc[i].tSmooth.set(0,0); } }; @@ -127,27 +135,29 @@ void MIDICCToCVInterface::processMidi(std::vector msg) { if (status == 0xb) { for (int i = 0; i < NUM_OUTPUTS; i++) { - if (onFocus[i]) { - ccSync[i] = true; - ccSyncFirst[i] = true; - ccNum[i] = data1; + if (cc[i].numSelected) { + cc[i].resetSync(); + cc[i].num = data1; } - if (data1 == ccNum[i]) { - if (ccSyncFirst[i]) { - ccSyncFirst[i] = false; - - if (data2 < cc[i] + 2 && data2 > cc[i] - 2) { - ccSync[i] = 0; - } else { - ccSync[i] = absi(data2 - cc[i]); + if (data1 == cc[i].num) { + /* If the first value we received after sync was reset is +/- 1 of + * the output value the values are in sync*/ + if (cc[i].syncFirst) { + cc[i].syncFirst = false; + if (data2 < cc[i].val + 2 && data2 > cc[i].val - 2) { + cc[i].sync = 0; + }else { + cc[i].sync = absi(data2 - cc[i].val); } + return; } - if (ccSync[i] == 0) { - cc[i] = data2; + if (cc[i].sync == 0) { + cc[i].val = data2; + cc[i].changed = true; } else { - ccSync[i] = absi(data2 - cc[i]); + cc[i].sync = absi(data2 - cc[i].val); } } } @@ -160,12 +170,10 @@ struct CCTextField : TextField { void draw(NVGcontext *vg) override; void onMouseDown(EventMouseDown &e) override; - void onMouseUp(EventMouseUp &e) override; - void onMouseLeave(EventMouseLeave &e) override; - int num; + int outNum; MIDICCToCVInterface *module; }; @@ -173,59 +181,59 @@ struct CCTextField : TextField { void CCTextField::draw(NVGcontext *vg) { /* This is necessary, since the save * file is loaded after constructing the widget*/ - if (module->ccNumInited[num]) { - module->ccNumInited[num] = false; - text = std::to_string(module->ccNum[num]); + if (module->cc[outNum].numInited) { + module->cc[outNum].numInited = false; + text = std::to_string(module->cc[outNum].num); } - if (module->onFocus[num]) { - text = std::to_string(module->ccNum[num]); + /* If number is selected for midi learn*/ + if (module->cc[outNum].numSelected) { + text = std::to_string(module->cc[outNum].num); } TextField::draw(vg); } -void CCTextField::onMouseDown(EventMouseDown &e) { +void CCTextField::onMouseUp(EventMouseUp &e) { if (e.button == 1) { - module->onFocus[num] = true; + module->cc[outNum].numSelected = false; + e.consumed = true; } - e.consumed = true; + TextField::onMouseUp(e); } -void CCTextField::onMouseUp(EventMouseUp &e) { +void CCTextField::onMouseDown(EventMouseDown &e) { if (e.button == 1) { - module->onFocus[num] = false; + module->cc[outNum].numSelected = true; + e.consumed = true; } - e.consumed = true; + TextField::onMouseDown(e); } void CCTextField::onMouseLeave(EventMouseLeave &e) { - module->onFocus[num] = false; + module->cc[outNum].numSelected = false; + e.consumed = true; } void CCTextField::onTextChange() { - int *ccNum = &module->ccNum[num]; if (text.size() > 0) { try { - *ccNum = std::stoi(text); + int num = std::stoi(text); // Only allow valid cc numbers - if (*ccNum < 0 || *ccNum > 127 || text.size() > 3) { + if (num < 0 || num > 127 || text.size() > 3) { text = ""; begin = end = 0; - *ccNum = -1; - return; - } - - if (!module->ccNumInited[num] && *ccNum != std::stoi(text)) { - module->ccSync[num] = 0; - module->ccSyncFirst[num] = true; + module->cc[outNum].num = -1; + } else { + module->cc[outNum].num = num; + module->cc[outNum].resetSync(); } } catch (...) { text = ""; begin = end = 0; - *ccNum = -1; + module->cc[outNum].num = -1; } }; } @@ -288,8 +296,8 @@ MIDICCToCVWidget::MIDICCToCVWidget() { for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) { CCTextField *ccNumChoice = new CCTextField(); ccNumChoice->module = module; - ccNumChoice->num = i; - ccNumChoice->text = std::to_string(module->ccNum[i]); + ccNumChoice->outNum = i; + ccNumChoice->text = std::to_string(module->cc[i].num); ccNumChoice->box.pos = Vec(11 + (i % 4) * (63), yPos); ccNumChoice->box.size.x = 29; diff --git a/src/core/MidiClockToCV.cpp b/src/core/MidiClockToCV.cpp index 66750f40..8e3f5516 100644 --- a/src/core/MidiClockToCV.cpp +++ b/src/core/MidiClockToCV.cpp @@ -19,7 +19,9 @@ struct MIDIClockToCVInterface : MidiIO, Module { enum OutputIds { CLOCK1_PULSE, CLOCK2_PULSE, - RESET_PULSE, + CONTINUE_PULSE, + START_PULSE, + STOP_PULSE, NUM_OUTPUTS }; @@ -28,10 +30,30 @@ struct MIDIClockToCVInterface : MidiIO, Module { PulseGenerator clock1Pulse; PulseGenerator clock2Pulse; - PulseGenerator resetPulse; + PulseGenerator continuePulse; + PulseGenerator startPulse; + PulseGenerator stopPulse; bool tick = false; bool running = false; - bool reset = false; + bool start = false; + bool stop = false; + bool cont = false; + int c_bar = 0; + + /* Note this is in relation to the Midi clock's Tick (6x per 16th note). + * Therefore, e.g. the 2:3 is calculated: + * + * 24 (Ticks per quarter note) * 2 / 3 = 16 + * + * Implying that every 16 midi clock ticks we need to send a pulse + * */ + const int ratios[9] = {6, 8, 12, 16, 24, 32, 48, 96, 192}; + const int numratios = 9; + + /* + * Length of clock pulse + */ + const float pulseTime = 0.005; MIDIClockToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { @@ -72,19 +94,7 @@ struct MIDIClockToCVInterface : MidiIO, Module { }; void MIDIClockToCVInterface::step() { - static int c_bar = 0; - static float trigger_length = 0.05; - static float sampleRate = engineGetSampleRate(); - - /* Note this is in relation to the Midi clock's Tick (6x per 16th note). - * Therefore, e.g. the 2:3 is calculated: - * - * 24 (Ticks per quarter note) * 2 / 3 = 16 - * - * Implying that every 16 midi clock ticks we need to send a pulse - * */ - static int ratios[] = {6, 8, 12, 16, 24, 32, 48, 96, 192}; - static int numratios = sizeof(ratios) / sizeof(*ratios); + float sampleRate = engineGetSampleRate(); if (isPortOpen()) { std::vector message; @@ -105,14 +115,23 @@ void MIDIClockToCVInterface::step() { clock2ratio = int(clampf(inputs[CLOCK2_RATIO].value, 0.0, 10.0) * (numratios - 1) / 10); } - if (reset) { - resetPulse.trigger(trigger_length); - reset = false; + if (start) { + start = false; + running = true; + startPulse.trigger(pulseTime); c_bar = 0; - clock1Pulse.time = 0.0; - clock1Pulse.pulseTime = 0.0; - clock2Pulse.time = 0.0; - clock2Pulse.pulseTime = 0.0; + } + + if (stop) { + stop = false; + running = false; + stopPulse.trigger(pulseTime); + } + + if (cont) { + cont = false; + running = true; + continuePulse.trigger(pulseTime); } if (tick) { @@ -125,11 +144,11 @@ void MIDIClockToCVInterface::step() { */ if (running) { if (c_bar % ratios[clock1ratio] == 0) { - clock1Pulse.trigger(trigger_length); + clock1Pulse.trigger(pulseTime); } if (c_bar % ratios[clock2ratio] == 0) { - clock2Pulse.trigger(trigger_length); + clock2Pulse.trigger(pulseTime); } } @@ -141,31 +160,39 @@ void MIDIClockToCVInterface::step() { } } - bool pulse = clock1Pulse.process(1.0 / sampleRate); outputs[CLOCK1_PULSE].value = pulse ? 10.0 : 0.0; pulse = clock2Pulse.process(1.0 / sampleRate); outputs[CLOCK2_PULSE].value = pulse ? 10.0 : 0.0; - pulse = resetPulse.process(1.0 / sampleRate); - outputs[RESET_PULSE].value = pulse ? 10.0 : 0.0; + pulse = continuePulse.process(1.0 / sampleRate); + outputs[CONTINUE_PULSE].value = pulse ? 10.0 : 0.0; + + pulse = startPulse.process(1.0 / sampleRate); + outputs[START_PULSE].value = pulse ? 10.0 : 0.0; + + pulse = stopPulse.process(1.0 / sampleRate); + outputs[STOP_PULSE].value = pulse ? 10.0 : 0.0; } void MIDIClockToCVInterface::resetMidi() { outputs[CLOCK1_PULSE].value = 0.0; + outputs[CLOCK2_PULSE].value = 0.0; } void MIDIClockToCVInterface::processMidi(std::vector msg) { switch (msg[0]) { case 0xfa: - reset = true; - running = true; + start = true; + break; + case 0xfb: + cont = true; break; case 0xfc: - running = false; + stop = true; break; case 0xf8: tick = true; @@ -243,7 +270,7 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { label->box.pos = Vec(box.size.x - margin - 7 * 15, margin); label->text = "MIDI Clk-CV"; addChild(label); - yPos = labelHeight * 2; + yPos = labelHeight*2; } { @@ -258,26 +285,46 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { midiChoice->box.pos = Vec(margin, yPos); midiChoice->box.size.x = box.size.x - 10; addChild(midiChoice); - yPos += midiChoice->box.size.y + margin * 6; + yPos += midiChoice->box.size.y + margin * 4; } + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "Start"; + addChild(label); + addOutput(createOutput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::START_PULSE)); + yPos += labelHeight + margin * 4; + } { Label *label = new Label(); label->box.pos = Vec(margin, yPos); - label->text = "C1 Ratio"; + label->text = "Stop"; addChild(label); + addOutput(createOutput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::STOP_PULSE)); + yPos += labelHeight + margin * 4; + } - addInput(createInput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_RATIO)); + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "Continue"; + addChild(label); + addOutput(createOutput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CONTINUE_PULSE)); + yPos += labelHeight + margin * 6; + } - yPos += margin * 6; + { + addInput(createInput(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_RATIO)); ClockRatioChoice *ratioChoice = new ClockRatioChoice(); ratioChoice->clockRatio = &module->clock1ratio; - ratioChoice->box.pos = Vec(margin, yPos); - ratioChoice->box.size.x = box.size.x - 10; + ratioChoice->box.pos = Vec(int(box.size.x/3), yPos); + ratioChoice->box.size.x = int(box.size.x/1.5 - margin); + addChild(ratioChoice); - yPos += ratioChoice->box.size.y + margin * 2; + yPos += ratioChoice->box.size.y + margin * 3; } @@ -293,22 +340,15 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { { - Label *label = new Label(); - label->box.pos = Vec(margin, yPos); - label->text = "C2 Ratio"; - addChild(label); - - addInput(createInput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_RATIO)); - - yPos += margin * 6; + addInput(createInput(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_RATIO)); ClockRatioChoice *ratioChoice = new ClockRatioChoice(); ratioChoice->clockRatio = &module->clock2ratio; - ratioChoice->box.pos = Vec(margin, yPos); - ratioChoice->box.size.x = box.size.x - 10; - addChild(ratioChoice); - yPos += ratioChoice->box.size.y + margin * 2; + ratioChoice->box.pos = Vec(int(box.size.x/3), yPos); + ratioChoice->box.size.x = int(box.size.x/1.5 - margin); + addChild(ratioChoice); + yPos += ratioChoice->box.size.y + margin * 3; } { @@ -318,16 +358,10 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { addChild(label); addOutput(createOutput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_PULSE)); - yPos += labelHeight + margin * 7; + yPos += labelHeight + margin * 3; } - { - Label *label = new Label(); - label->box.pos = Vec(margin, yPos); - label->text = "Reset"; - addChild(label); - addOutput(createOutput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::RESET_PULSE)); - } + } void MIDIClockToCVWidget::step() { diff --git a/src/core/MidiIO.cpp b/src/core/MidiIO.cpp index ec45eace..816e3110 100644 --- a/src/core/MidiIO.cpp +++ b/src/core/MidiIO.cpp @@ -49,15 +49,28 @@ void MidiIO::baseFromJson(json_t *rootJ) { } std::vector MidiIO::getDevices() { - /* Note: we could also use an existing interface if one exists */ - static RtMidiIn *m = new RtMidiIn(); - std::vector names = {}; + if (isOut) { + // TODO + return names; + } + + RtMidiIn *m; + try { + m = new RtMidiIn(); + } catch (RtMidiError &error) { + fprintf(stderr, "Failed to create RtMidiIn: %s\n", error.getMessage().c_str()); + return names; + } + for (unsigned int i = 0; i < m->getPortCount(); i++) { names.push_back(m->getPortName(i)); } + if (!isPortOpen()) + delete (m); + return names; } @@ -81,6 +94,13 @@ void MidiIO::openDevice(std::string deviceName) { break; } } + + if (!mw->isPortOpen()) { + fprintf(stderr, "Failed to create RtMidiIn: No such device %s\n", deviceName.c_str()); + this->deviceName = ""; + this->id = -1; + return; + } } catch (RtMidiError &error) { fprintf(stderr, "Failed to create RtMidiIn: %s\n", error.getMessage().c_str()); @@ -99,17 +119,17 @@ void MidiIO::openDevice(std::string deviceName) { void MidiIO::setIgnores(bool ignoreSysex, bool ignoreTime, bool ignoreSense) { bool sy = true, ti = true, se = true; - midiInMap[deviceName]->ignoresMap[id][0] = ignoreSysex; - midiInMap[deviceName]->ignoresMap[id][1] = ignoreTime; - midiInMap[deviceName]->ignoresMap[id][2] = ignoreSense; + midiInMap[deviceName]->ignoresMap[id].midiSysex = ignoreSysex; + midiInMap[deviceName]->ignoresMap[id].midiTime = ignoreTime; + midiInMap[deviceName]->ignoresMap[id].midiSense = ignoreSense; for (auto kv : midiInMap[deviceName]->ignoresMap) { - sy = sy && kv.second[0]; - ti = ti && kv.second[1]; - se = se && kv.second[2]; + sy = sy && kv.second.midiSysex; + ti = ti && kv.second.midiTime; + se = se && kv.second.midiSense; } - midiInMap[deviceName]->ignoreTypes(se,ti,se); + midiInMap[deviceName]->ignoreTypes(se, ti, se); } @@ -119,7 +139,7 @@ std::string MidiIO::getDeviceName() { } double MidiIO::getMessage(std::vector *msg) { - std::vector next_msg; + MidiMessage next_msg = MidiMessage(); MidiInWrapper *mw = midiInMap[deviceName]; @@ -128,24 +148,23 @@ double MidiIO::getMessage(std::vector *msg) { return 0; } - double stamp = midiInMap[deviceName]->getMessage(&next_msg); + next_msg.timeStamp = mw->getMessage(&next_msg.bytes); + if (next_msg.bytes.size() > 0) { + for (auto &kv : mw->idMessagesMap) { - if (next_msg.size() > 0) { - for (auto kv : mw->idMessagesMap) { - mw->idMessagesMap[kv.first].push_back(next_msg); - mw->idStampsMap[kv.first].push_back(stamp); + kv.second.push_back(next_msg); } } - if (mw->idMessagesMap[id].size() <= 0) { - *msg = next_msg; - return stamp; + + if (mw->idMessagesMap[id].size() > 0) { + next_msg = mw->idMessagesMap[id].front(); + mw->idMessagesMap[id].pop_front(); } - *msg = mw->idMessagesMap[id].front(); - stamp = mw->idStampsMap[id].front(); - mw->idMessagesMap[id].pop_front(); - return stamp; + *msg = next_msg.bytes; + + return next_msg.timeStamp; } bool MidiIO::isPortOpen() { @@ -165,9 +184,10 @@ void MidiIO::close() { mw->erase(id); - if (mw->subscribers == 0) { + if (mw->idMessagesMap.size() == 0) { mw->closePort(); midiInMap.erase(deviceName); + delete (mw); } id = -1; @@ -238,4 +258,4 @@ void ChannelChoice::onAction(EventAction &e) { void ChannelChoice::step() { text = (midiModule->channel >= 0) ? stringf("%d", midiModule->channel + 1) : "All"; -} \ No newline at end of file +} diff --git a/src/core/MidiIO.hpp b/src/core/MidiIO.hpp index 806518ed..5148c5fa 100644 --- a/src/core/MidiIO.hpp +++ b/src/core/MidiIO.hpp @@ -5,50 +5,53 @@ using namespace rack; +struct IgnoreFlags { + bool midiSysex = true; + bool midiTime = true; + bool midiSense = true; +}; + +struct MidiMessage { + std::vector bytes; + double timeStamp; + + MidiMessage() : bytes(0), timeStamp(0.0) {}; + +}; /** * This class allows to use one instance of rtMidiIn with * multiple modules. A MidiIn port will be opened only once while multiple * instances can use it simultaniously, each receiving all its incoming messages. */ - struct MidiInWrapper : RtMidiIn { - std::unordered_map>> idMessagesMap; - std::unordered_map> idStampsMap; - /* Stores Ignore settings for each instance in the following order: - * {ignore_midiSysex, ignore_midiTime, ignore_midiSense} - */ - std::unordered_map ignoresMap; + std::unordered_map> idMessagesMap; + std::unordered_map ignoresMap; int uid_c = 0; - int subscribers = 0; MidiInWrapper() : RtMidiIn() { idMessagesMap = {}; - idStampsMap = {}; }; int add() { int id = ++uid_c; - subscribers++; idMessagesMap[id] = {}; - idStampsMap[id] = {}; - - ignoresMap[id][0] = true; - ignoresMap[id][1] = true; - ignoresMap[id][2] = true; + ignoresMap[id] = IgnoreFlags(); return id; } void erase(int id) { - subscribers--; idMessagesMap.erase(id); - idStampsMap.erase(id); ignoresMap.erase(id); } }; +/** + * Note: MidiIO is not thread safe which might become + * important in the future + */ struct MidiIO { private: static std::unordered_map midiInMap; @@ -90,10 +93,80 @@ public: /* called when midi port is set */ virtual void resetMidi() {} - /* called if a user switches or sets the deivce (and after this device is initialised)*/ + /* called if a user switches or sets the device (and after this device is initialised)*/ virtual void onDeviceChange() {} }; + +struct TransitionSmoother { + enum TransitionFunction { + SMOOTHSTEP, + EXP, + LIN, + }; + + enum TransitionMode { + DELTA, + CONST, + }; + + float start; + float end; + float x; + float delta; + float step; + TransitionFunction t; + + + void set(float start, float end, int l = 1500, TransitionFunction t = LIN, TransitionMode m = DELTA, bool reset = true) { + this->start = start; + this->end = end; + this->delta = end - start; + this->t = t; + + if (reset || x >= 1) { + this->x = 0; + } + + switch (m) { + case DELTA: + /* If the change is smaller, the transition phase is longer */ + this->step = delta > 0 ? delta/l : -delta/l; + break; + case CONST: + this->step = 1.0/l; + break; + } + + } + + float next() { + float next = start; + + x += step; + if (x >= 1) + return end; + + switch (t) { + case SMOOTHSTEP: + next += delta*x*x*(3-2*x); + break; + case EXP: + next += delta*x*x; + break; + case LIN: + next += delta*x; + break; + } + + if ((delta > 0 && next > end) || (delta <= 0 && next < end)) + return end; + + return next;; + } +}; + + ////////////////////// // MIDI module widgets ////////////////////// diff --git a/src/core/MidiToCV.cpp b/src/core/MidiToCV.cpp index 740765a2..69be33be 100644 --- a/src/core/MidiToCV.cpp +++ b/src/core/MidiToCV.cpp @@ -9,6 +9,12 @@ * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to * CV */ +struct MidiValue { + int val = 0; // Controller value + TransitionSmoother tSmooth; + bool changed = false; // Value has been changed by midi message (only if it is in sync!) +}; + struct MIDIToCVInterface : MidiIO, Module { enum ParamIds { RESET_PARAM, @@ -34,17 +40,17 @@ struct MIDIToCVInterface : MidiIO, Module { std::list notes; bool pedal = false; int note = 60; // C4, most modules should use 261.626 Hz - int mod = 0; int vel = 0; - int afterTouch = 0; - int pitchWheel = 64; - bool retrigger = false; - bool retriggered = false; + MidiValue mod; + MidiValue afterTouch; + MidiValue pitchWheel; + bool gate = false; SchmittTrigger resetTrigger; MIDIToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { - + pitchWheel.val = 64; + pitchWheel.tSmooth.set(0, 0); } ~MIDIToCVInterface() { @@ -77,11 +83,14 @@ struct MIDIToCVInterface : MidiIO, Module { }; void MIDIToCVInterface::resetMidi() { - mod = 0; - pitchWheel = 64; - afterTouch = 0; + mod.val = 0; + mod.tSmooth.set(0, 0); + pitchWheel.val = 64; + pitchWheel.tSmooth.set(0, 0); + afterTouch.val = 0; + afterTouch.tSmooth.set(0, 0); vel = 0; - outputs[GATE_OUTPUT].value = 0.0; + gate = false; notes.clear(); } @@ -99,11 +108,6 @@ void MIDIToCVInterface::step() { outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; - bool gate = pedal || !notes.empty(); - if (retrigger && retriggered) { - gate = false; - retriggered = false; - } if (resetTrigger.process(params[RESET_PARAM].value)) { resetMidi(); return; @@ -112,11 +116,27 @@ void MIDIToCVInterface::step() { lights[RESET_LIGHT].value -= lights[RESET_LIGHT].value / 0.55 / engineGetSampleRate(); // fade out light outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; - outputs[MOD_OUTPUT].value = mod / 127.0 * 10.0; - outputs[PITCHWHEEL_OUTPUT].value = (pitchWheel - 64) / 64.0 * 10.0; - outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch / 127.0 * 10.0; outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0; + int steps = int(engineGetSampleRate() / 32); + + if (mod.changed) { + mod.tSmooth.set(outputs[MOD_OUTPUT].value, (mod.val / 127.0 * 10.0), steps); + mod.changed = false; + } + outputs[MOD_OUTPUT].value = mod.tSmooth.next(); + + if (pitchWheel.changed) { + pitchWheel.tSmooth.set(outputs[PITCHWHEEL_OUTPUT].value, (pitchWheel.val - 64) / 64.0 * 10.0, steps); + pitchWheel.changed = false; + } + outputs[PITCHWHEEL_OUTPUT].value = pitchWheel.tSmooth.next(); + + + /* NOTE: I'll leave out value smoothing for after touch for now. I currently don't + * have an after touch capable device around and I assume it would require different + * smoothing*/ + outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; } void MIDIToCVInterface::pressNote(int note) { @@ -127,7 +147,7 @@ void MIDIToCVInterface::pressNote(int note) { // Push note notes.push_back(note); this->note = note; - retriggered = true; + gate = true; } void MIDIToCVInterface::releaseNote(int note) { @@ -138,12 +158,15 @@ void MIDIToCVInterface::releaseNote(int note) { if (pedal) { // Don't release if pedal is held + gate = true; } else if (!notes.empty()) { // Play previous note auto it2 = notes.end(); it2--; this->note = *it2; - retriggered = true; + gate = true; + } else { + gate = false; } } @@ -153,7 +176,7 @@ void MIDIToCVInterface::processMidi(std::vector msg) { int data1 = msg[1]; int data2 = msg[2]; - //fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2); + // fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1, data2); // Filter channels if (this->channel >= 0 && this->channel != channel) @@ -177,19 +200,24 @@ void MIDIToCVInterface::processMidi(std::vector msg) { case 0xb: // cc switch (data1) { case 0x01: // mod - this->mod = data2; + mod.val = data2; + mod.changed = true; break; case 0x40: // sustain pedal = (data2 >= 64); - releaseNote(-1); + if (!pedal) { + releaseNote(-1); + } break; } break; case 0xe: // pitch wheel - this->pitchWheel = data2; + pitchWheel.val = data2; + pitchWheel.changed = true; break; case 0xd: // channel aftertouch - this->afterTouch = data1; + afterTouch.val = data1; + afterTouch.changed = true; break; } } @@ -225,7 +253,8 @@ MidiToCVWidget::MidiToCVWidget() { } addParam(createParam(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0)); - addChild(createLight>(Vec(7 * 15 + 5, labelHeight + 5), module, MIDIToCVInterface::RESET_LIGHT)); + addChild(createLight>(Vec(7 * 15 + 5, labelHeight + 5), module, + MIDIToCVInterface::RESET_LIGHT)); { Label *label = new Label(); label->box.pos = Vec(margin, yPos); diff --git a/src/core/MidiTriggerToCV.cpp b/src/core/MidiTriggerToCV.cpp index 95a81b73..4bb60020 100644 --- a/src/core/MidiTriggerToCV.cpp +++ b/src/core/MidiTriggerToCV.cpp @@ -7,10 +7,13 @@ using namespace rack; -/* - * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod weel to - * CV - */ +struct TriggerValue { + int val = 0; + int num; + bool numInited = false; + bool onFocus = false; +}; + struct MIDITriggerToCVInterface : MidiIO, Module { enum ParamIds { NUM_PARAMS @@ -22,16 +25,11 @@ struct MIDITriggerToCVInterface : MidiIO, Module { NUM_OUTPUTS = 16 }; - int trigger[NUM_OUTPUTS]; - int triggerNum[NUM_OUTPUTS]; - bool triggerNumInited[NUM_OUTPUTS]; - bool onFocus[NUM_OUTPUTS]; + TriggerValue trigger[NUM_OUTPUTS]; MIDITriggerToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { for (int i = 0; i < NUM_OUTPUTS; i++) { - trigger[i] = 0; - triggerNum[i] = i; - onFocus[i] = false; + trigger[i].num = i; } } @@ -48,7 +46,7 @@ struct MIDITriggerToCVInterface : MidiIO, Module { json_t *rootJ = json_object(); addBaseJson(rootJ); for (int i = 0; i < NUM_OUTPUTS; i++) { - json_object_set_new(rootJ, std::to_string(i).c_str(), json_integer(triggerNum[i])); + json_object_set_new(rootJ, std::to_string(i).c_str(), json_integer(trigger[i].num)); } return rootJ; } @@ -58,8 +56,8 @@ struct MIDITriggerToCVInterface : MidiIO, Module { for (int i = 0; i < NUM_OUTPUTS; i++) { json_t *ccNumJ = json_object_get(rootJ, std::to_string(i).c_str()); if (ccNumJ) { - triggerNum[i] = json_integer_value(ccNumJ); - triggerNumInited[i] = true; + trigger[i].num = json_integer_value(ccNumJ); + trigger[i].numInited = true; } } @@ -68,7 +66,6 @@ struct MIDITriggerToCVInterface : MidiIO, Module { void reset() override { resetMidi(); } - }; @@ -88,13 +85,13 @@ void MIDITriggerToCVInterface::step() { // Note: Could have an option to select between gate and velocity // but trigger seams more useful // outputs[i].value = trigger[i] / 127.0 * 10; - outputs[i].value = trigger[i] > 0 ? 10.0 : 0.0; + outputs[i].value = trigger[i].val > 0 ? 10.0 : 0.0; } } void MIDITriggerToCVInterface::resetMidi() { for (int i = 0; i < NUM_OUTPUTS; i++) { - trigger[i] = 0; + trigger[i].val = 0; } }; @@ -112,8 +109,8 @@ void MIDITriggerToCVInterface::processMidi(std::vector msg) { if (status == 0x8) { // note off for (int i = 0; i < NUM_OUTPUTS; i++) { - if (data1 == triggerNum[i]) { - trigger[i] = data2; + if (data1 == trigger[i].num) { + trigger[i].val = data2; } } return; @@ -121,14 +118,12 @@ void MIDITriggerToCVInterface::processMidi(std::vector msg) { if (status == 0x9) { // note on for (int i = 0; i < NUM_OUTPUTS; i++) { - if (onFocus[i]) { - this->triggerNum[i] = data1; + if (trigger[i].onFocus && data2 > 0) { + trigger[i].num = data1; } - } - for (int i = 0; i < NUM_OUTPUTS; i++) { - if (data1 == triggerNum[i]) { - trigger[i] = data2; + if (data1 == trigger[i].num) { + trigger[i].val = data2; } } } @@ -146,22 +141,20 @@ struct TriggerTextField : TextField { void onMouseLeave(EventMouseLeave &e) override; - - int *triggerNum; - bool *inited; - bool *onFocus; + int outNum; + MIDITriggerToCVInterface *module; }; void TriggerTextField::draw(NVGcontext *vg) { /* This is necessary, since the save * file is loaded after constructing the widget*/ - if (*inited) { - *inited = false; - text = std::to_string(*triggerNum); + if (module->trigger[outNum].numInited) { + module->trigger[outNum].numInited = false; + text = std::to_string(module->trigger[outNum].num); } - if (*onFocus) { - text = std::to_string(*triggerNum); + if (module->trigger[outNum].onFocus) { + text = std::to_string(module->trigger[outNum].num); } TextField::draw(vg); @@ -170,37 +163,42 @@ void TriggerTextField::draw(NVGcontext *vg) { void TriggerTextField::onTextChange() { if (text.size() > 0) { try { - *triggerNum = std::stoi(text); + int num = std::stoi(text); // Only allow valid cc numbers - if (*triggerNum < 0 || *triggerNum > 127 || text.size() > 3) { + if (num < 0 || num > 127 || text.size() > 3) { text = ""; begin = end = 0; - *triggerNum = -1; + module->trigger[outNum].num = -1; + }else { + module->trigger[outNum].num = num; } } catch (...) { text = ""; begin = end = 0; - *triggerNum = -1; + module->trigger[outNum].num = -1; } }; } -void TriggerTextField::onMouseDown(EventMouseDown &e) { +void TriggerTextField::onMouseUp(EventMouseUp &e) { if (e.button == 1) { - *onFocus = true; + module->trigger[outNum].onFocus = false; + e.consumed = true; } - e.consumed = true; + TextField::onMouseUp(e); } -void TriggerTextField::onMouseUp(EventMouseUp &e) { +void TriggerTextField::onMouseDown(EventMouseDown &e) { if (e.button == 1) { - *onFocus = false; + module->trigger[outNum].onFocus = true; + e.consumed = true; } - e.consumed = true; + TextField::onMouseDown(e); } void TriggerTextField::onMouseLeave(EventMouseLeave &e) { - *onFocus = false; + module->trigger[outNum].onFocus = false; + e.consumed = true; } MIDITriggerToCVWidget::MIDITriggerToCVWidget() { @@ -260,10 +258,9 @@ MIDITriggerToCVWidget::MIDITriggerToCVWidget() { for (int i = 0; i < MIDITriggerToCVInterface::NUM_OUTPUTS; i++) { TriggerTextField *triggerNumChoice = new TriggerTextField(); - triggerNumChoice->triggerNum = &module->triggerNum[i]; - triggerNumChoice->inited = &module->triggerNumInited[i]; - triggerNumChoice->onFocus = &module->onFocus[i]; - triggerNumChoice->text = std::to_string(module->triggerNum[i]); + triggerNumChoice->module = module; + triggerNumChoice->outNum = i; + triggerNumChoice->text = std::to_string(module->trigger[i].num); triggerNumChoice->box.pos = Vec(11 + (i % 4) * (63), yPos); triggerNumChoice->box.size.x = 29; diff --git a/src/core/QuadMidiToCV.cpp b/src/core/QuadMidiToCV.cpp index e5fe1725..39982913 100644 --- a/src/core/QuadMidiToCV.cpp +++ b/src/core/QuadMidiToCV.cpp @@ -9,7 +9,6 @@ struct MidiKey { int pitch = 60; int at = 0; // aftertouch int vel = 0; // velocity - int retriggerC = 0; bool gate = false; }; @@ -97,10 +96,9 @@ void QuadMIDIToCVInterface::resetMidi() { } void QuadMIDIToCVInterface::step() { - static int msgsProcessed = 0; - if (isPortOpen()) { std::vector message; + int msgsProcessed = 0; // midiIn->getMessage returns empty vector if there are no messages in the queue // NOTE: For the quadmidi we will process max 4 midi messages per step to avoid @@ -111,7 +109,6 @@ void QuadMIDIToCVInterface::step() { getMessage(&message); msgsProcessed++; } - msgsProcessed = 0; } @@ -136,8 +133,7 @@ void QuadMIDIToCVInterface::processMidi(std::vector msg) { int status = (msg[0] >> 4) & 0xf; int data1 = msg[1]; int data2 = msg[2]; - - static int gate; + bool gate; // Filter channels if (this->channel >= 0 && this->channel != channel) @@ -168,6 +164,7 @@ void QuadMIDIToCVInterface::processMidi(std::vector msg) { if (data1 == 0x40) { // pedal pedal = (data2 >= 64); if (!pedal) { + open.clear(); for (int i = 0; i < 4; i++) { activeKeys[i].gate = false; open.push_back(i); @@ -179,7 +176,11 @@ void QuadMIDIToCVInterface::processMidi(std::vector msg) { return; } - if (!pedal && !gate) { + if (pedal && !gate) { + return; + } + + if (!gate) { for (int i = 0; i < 4; i++) { if (activeKeys[i].pitch == data1) { activeKeys[i].gate = false; @@ -194,21 +195,20 @@ void QuadMIDIToCVInterface::processMidi(std::vector msg) { } if (open.empty()) { - open.clear(); for (int i = 0; i < 4; i++) { open.push_back(i); } } - if (!activeKeys[0].gate && !activeKeys[1].gate && !activeKeys[2].gate && !activeKeys[3].gate) { open.sort(); } + switch (mode) { case RESET: - if (open.size() == 4 ) { + if (open.size() >= 4) { for (int i = 0; i < 4; i++) { activeKeys[i].gate = false; open.push_back(i); @@ -220,17 +220,25 @@ void QuadMIDIToCVInterface::processMidi(std::vector msg) { break; case ROTATE: break; - default: - fprintf(stderr, "No mode selected?!\n"); } - activeKeys[open.front()].gate = true; - activeKeys[open.front()].pitch = data1; - activeKeys[open.front()].vel = data2; + int next = open.front(); open.pop_front(); - return; + for (int i = 0; i < 4; i++) { + if (activeKeys[i].pitch == data1 && activeKeys[i].gate) { + activeKeys[i].vel = data2; + if (std::find(open.begin(), open.end(), i) != open.end()) + open.remove(i); + + open.push_front(i); + activeKeys[i].gate = false; + } + } + activeKeys[next].gate = true; + activeKeys[next].pitch = data1; + activeKeys[next].vel = data2; } int QuadMIDIToCVInterface::getMode() const {