diff --git a/include/midi.hpp b/include/midi.hpp index 57ed40ee..5cf1e7d0 100644 --- a/include/midi.hpp +++ b/include/midi.hpp @@ -26,6 +26,12 @@ struct MidiMessage { uint8_t status() { return (cmd >> 4) & 0xf; } + uint8_t note() { + return data1 & 0x7f; + } + uint8_t value() { + return data2 & 0x7f; + } }; diff --git a/src/Core/MIDIToCVInterface.cpp b/src/Core/MIDIToCVInterface.cpp index 9685185f..613f1760 100644 --- a/src/Core/MIDIToCVInterface.cpp +++ b/src/Core/MIDIToCVInterface.cpp @@ -144,29 +144,28 @@ struct MIDIToCVInterface : Module { } void processMessage(MidiMessage msg) { - // debug("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.data1, msg.data2); + // debug("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.note(), msg.value()); switch (msg.status()) { // note off case 0x8: { - releaseNote(msg.data1); + releaseNote(msg.note()); } break; // note on case 0x9: { - if (msg.data2 > 0) { - uint8_t note = msg.data1 & 0x7f; - noteData[note].velocity = msg.data2; - pressNote(msg.data1); + if (msg.value() > 0) { + noteData[msg.note()].velocity = msg.value(); + pressNote(msg.note()); } else { // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released. - releaseNote(msg.data1); + releaseNote(msg.note()); } } break; // channel aftertouch case 0xa: { - uint8_t note = msg.data1 & 0x7f; - noteData[note].aftertouch = msg.data2; + uint8_t note = msg.note(); + noteData[note].aftertouch = msg.value(); } break; // cc case 0xb: { @@ -174,7 +173,7 @@ struct MIDIToCVInterface : Module { } break; // pitch wheel case 0xe: { - pitch = msg.data2 * 128 + msg.data1; + pitch = msg.value() * 128 + msg.note(); } break; case 0xf: { processSystem(msg); @@ -184,14 +183,14 @@ struct MIDIToCVInterface : Module { } void processCC(MidiMessage msg) { - switch (msg.data1) { + switch (msg.note()) { // mod case 0x01: { - mod = msg.data2; + mod = msg.value(); } break; // sustain case 0x40: { - if (msg.data2 >= 64) + if (msg.value() >= 64) pressPedal(); else releasePedal(); diff --git a/src/Core/MIDITriggerToCVInterface.cpp b/src/Core/MIDITriggerToCVInterface.cpp index b2a37b91..be9d32de 100644 --- a/src/Core/MIDITriggerToCVInterface.cpp +++ b/src/Core/MIDITriggerToCVInterface.cpp @@ -71,23 +71,76 @@ struct MIDITriggerToCVInterface : Module { MidiInputQueue midiInput; - MIDITriggerToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} + bool gates[16]; + float gateTimes[16]; + + MIDITriggerToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onReset(); + } + + void onReset() override { + for (int i = 0; i < 16; i++) { + gates[i] = false; + gateTimes[i] = 0.f; + } + } + + void pressNote(uint8_t note) { + // TEMP + if (note >= 16) + return; + int i = note; + + gates[i] = true; + gateTimes[i] = 1e-3f; + } + + void releaseNote(uint8_t note) { + // TEMP + if (note >= 16) + return; + int i = note; + + gates[i] = false; + } void step() override { MidiMessage msg; while (midiInput.shift(&msg)) { processMessage(msg); } + float deltaTime = engineGetSampleTime(); for (int i = 0; i < 16; i++) { - outputs[TRIG_OUTPUT + i].value = 0.f; + if (gateTimes[i] > 0.f) { + outputs[TRIG_OUTPUT + i].value = 10.f; + // If the gate is off, wait 1 ms before turning the pulse off. + // This avoids drum controllers sending a pulse with 0 ms duration. + if (!gates[i]) { + gateTimes[i] -= deltaTime; + } + } + else { + outputs[TRIG_OUTPUT + i].value = 0.f; + } } } void processMessage(MidiMessage msg) { - // debug("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.data1, msg.data2); - switch (msg.status()) { + // note off + case 0x8: { + releaseNote(msg.note()); + } break; + // note on + case 0x9: { + if (msg.value() > 0) { + pressNote(msg.note()); + } + else { + releaseNote(msg.note()); + } + } break; default: break; } } diff --git a/src/Core/MidiClockToCV.cpp b/src/Core/MidiClockToCV.cpp deleted file mode 100644 index fe6e8f2a..00000000 --- a/src/Core/MidiClockToCV.cpp +++ /dev/null @@ -1,373 +0,0 @@ -#if 0 -#include -#include -#include "core.hpp" -#include "MidiIO.hpp" -#include "dsp/digital.hpp" - - -using namespace rack; - -struct MIDIClockToCVInterface : MidiIO, Module { - enum ParamIds { - NUM_PARAMS - }; - enum InputIds { - CLOCK1_RATIO, - CLOCK2_RATIO, - NUM_INPUTS - }; - enum OutputIds { - CLOCK1_PULSE, - CLOCK2_PULSE, - CONTINUE_PULSE, - START_PULSE, - STOP_PULSE, - NUM_OUTPUTS - }; - - int clock1ratio = 0; - int clock2ratio = 0; - - PulseGenerator clock1Pulse; - PulseGenerator clock2Pulse; - PulseGenerator continuePulse; - PulseGenerator startPulse; - PulseGenerator stopPulse; - bool tick = false; - bool running = 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) { - - } - - ~MIDIClockToCVInterface() { - } - - void step() override; - - void processMidi(std::vector msg); - - void onDeviceChange() override; - - void resetMidi() override; - - json_t *toJson() override { - json_t *rootJ = json_object(); - addBaseJson(rootJ); - json_object_set_new(rootJ, "clock1ratio", json_integer(clock1ratio)); - json_object_set_new(rootJ, "clock2ratio", json_integer(clock2ratio)); - return rootJ; - } - - void fromJson(json_t *rootJ) override { - baseFromJson(rootJ); - json_t *c1rJ = json_object_get(rootJ, "clock1ratio"); - if (c1rJ) { - clock1ratio = json_integer_value(c1rJ); - } - - json_t *c2rJ = json_object_get(rootJ, "clock2ratio"); - if (c2rJ) { - clock2ratio = json_integer_value(c2rJ); - } - } -}; - -void MIDIClockToCVInterface::step() { - float sampleRate = engineGetSampleRate(); - - if (isPortOpen()) { - std::vector message; - - // midiIn->getMessage returns empty vector if there are no messages in the queue - getMessage(&message); - if (message.size() > 0) { - processMidi(message); - } - } - - if (inputs[CLOCK1_RATIO].active) { - clock1ratio = int(clamp(inputs[CLOCK1_RATIO].value, 0.0, 10.0) * (numratios - 1) / 10); - } - - if (inputs[CLOCK2_RATIO].active) { - clock2ratio = int(clamp(inputs[CLOCK2_RATIO].value, 0.0, 10.0) * (numratios - 1) / 10); - } - - if (start) { - start = false; - running = true; - startPulse.trigger(pulseTime); - c_bar = 0; - } - - if (stop) { - stop = false; - running = false; - stopPulse.trigger(pulseTime); - } - - if (cont) { - cont = false; - running = true; - continuePulse.trigger(pulseTime); - } - - if (tick) { - tick = false; - - /* Note: At least for my midi clock, the clock ticks are sent - * even if the midi clock is stopped. - * Therefore, we need to keep track of ticks even when the clock - * is stopped. Otherwise we can run into weird timing issues. - */ - if (running) { - if (c_bar % ratios[clock1ratio] == 0) { - clock1Pulse.trigger(pulseTime); - } - - if (c_bar % ratios[clock2ratio] == 0) { - clock2Pulse.trigger(pulseTime); - } - } - - c_bar++; - - // One "midi bar" = 4 whole notes = (6 ticks per 16th) 6 * 16 *4 = 384 - if (c_bar >= 384) { - c_bar = 0; - } - } - - 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 = 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: - start = true; - break; - case 0xfb: - cont = true; - break; - case 0xfc: - stop = true; - break; - case 0xf8: - tick = true; - break; - } - - -} - -void MIDIClockToCVInterface::onDeviceChange() { - setIgnores(true, false); -} - -struct ClockRatioItem : MenuItem { - int ratio; - int *clockRatio; - - void onAction(EventAction &e) override { - *clockRatio = ratio; - } -}; - -struct ClockRatioChoice : ChoiceButton { - int *clockRatio; - const std::vector ratioNames = {"Sixteenth note (1:4 ratio)", "Eighth note triplet (1:3 ratio)", - "Eighth note (1:2 ratio)", "Quarter note triplet (2:3 ratio)", - "Quarter note (tap speed)", "Half note triplet (4:3 ratio)", - "Half note (2:1 ratio)", "Whole note (4:1 ratio)", - "Two whole notes (8:1 ratio)" - }; - - const std::vector ratioNames_short = {"1:4 ratio", "1:3 ratio", "1:2 ratio", "2:3 ratio", "1:1 ratio", - "4:3", "2:1 ratio", "4:1 ratio", "8:1 ratio" - }; - - void onAction(EventAction &e) override { - Menu *menu = gScene->createMenu(); - menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); - menu->box.size.x = box.size.x; - - for (unsigned long ratio = 0; ratio < ratioNames.size(); ratio++) { - ClockRatioItem *clockRatioItem = new ClockRatioItem(); - clockRatioItem->ratio = ratio; - clockRatioItem->clockRatio = clockRatio; - clockRatioItem->text = ratioNames[ratio]; - menu->addChild(clockRatioItem); - } - } - - void step() override { - text = ratioNames_short[*clockRatio]; - } -}; - -MIDIClockToCVWidget::MIDIClockToCVWidget() { - MIDIClockToCVInterface *module = new MIDIClockToCVInterface(); - setModule(module); - box.size = Vec(15 * 9, 380); - - { - Panel *panel = new LightPanel(); - panel->box.size = box.size; - addChild(panel); - } - - float margin = 5; - float labelHeight = 15; - float yPos = margin; - - addChild(createScrew(Vec(15, 0))); - addChild(createScrew(Vec(box.size.x - 30, 0))); - addChild(createScrew(Vec(15, 365))); - addChild(createScrew(Vec(box.size.x - 30, 365))); - - { - Label *label = new Label(); - label->box.pos = Vec(box.size.x - margin - 7 * 15, margin); - label->text = "MIDI Clk-CV"; - addChild(label); - yPos = labelHeight * 2; - } - - { - Label *label = new Label(); - label->box.pos = Vec(margin, yPos); - label->text = "MIDI Interface"; - addChild(label); - yPos += labelHeight + margin; - - MidiChoice *midiChoice = new MidiChoice(); - midiChoice->midiModule = dynamic_cast(module); - midiChoice->box.pos = Vec(margin, yPos); - midiChoice->box.size.x = box.size.x - 10; - addChild(midiChoice); - 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 = "Stop"; - addChild(label); - addOutput(createOutput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::STOP_PULSE)); - yPos += labelHeight + margin * 4; - } - - { - 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; - } - - - { - addInput(createInput(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_RATIO)); - ClockRatioChoice *ratioChoice = new ClockRatioChoice(); - ratioChoice->clockRatio = &module->clock1ratio; - 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; - - } - - { - Label *label = new Label(); - label->box.pos = Vec(margin, yPos); - label->text = "C1 Pulse"; - addChild(label); - - addOutput(createOutput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_PULSE)); - yPos += margin * 10; - } - - - { - - addInput(createInput(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_RATIO)); - ClockRatioChoice *ratioChoice = new ClockRatioChoice(); - ratioChoice->clockRatio = &module->clock2ratio; - 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; - } - - { - Label *label = new Label(); - label->box.pos = Vec(margin, yPos); - label->text = "C2 Pulse"; - addChild(label); - - addOutput(createOutput(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_PULSE)); - yPos += labelHeight + margin * 3; - } - - -} - -void MIDIClockToCVWidget::step() { - - ModuleWidget::step(); -} -#endif \ No newline at end of file diff --git a/src/Core/QuadMIDIToCVInterface.cpp b/src/Core/QuadMIDIToCVInterface.cpp index 73a3a903..99e0f210 100644 --- a/src/Core/QuadMIDIToCVInterface.cpp +++ b/src/Core/QuadMIDIToCVInterface.cpp @@ -147,24 +147,21 @@ struct QuadMIDIToCVInterface : Module { switch (msg.status()) { // note off case 0x8: { - // releaseNote(msg.data1); + releaseNote(msg.note()); } break; // note on case 0x9: { - if (msg.data2 > 0) { - uint8_t note = msg.data1 & 0x7f; - noteData[note].velocity = msg.data2; - // pressNote(msg.data1); + if (msg.value() > 0) { + noteData[msg.note()].velocity = msg.value(); + pressNote(msg.note()); } else { - // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released. - // releaseNote(msg.data1); + releaseNote(msg.note()); } } break; // channel aftertouch case 0xa: { - uint8_t note = msg.data1 & 0x7f; - noteData[note].aftertouch = msg.data2; + noteData[msg.note()].aftertouch = msg.value(); } break; // cc case 0xb: { @@ -175,10 +172,10 @@ struct QuadMIDIToCVInterface : Module { } void processCC(MidiMessage msg) { - switch (msg.data1) { + switch (msg.note()) { // sustain case 0x40: { - if (msg.data2 >= 64) + if (msg.value() >= 64) pressPedal(); else releasePedal();