#pragma once #include #include #include #include #include namespace rack { namespace dsp { /** Converts gates and CV to MIDI messages. CHANNELS is the number of polyphony channels. Use 1 for monophonic. */ template struct MidiGenerator { int8_t vels[CHANNELS]; int8_t notes[CHANNELS]; bool gates[CHANNELS]; int8_t keyPressures[CHANNELS]; int8_t channelPressure; int8_t ccs[128]; int16_t pw; bool clk; bool start; bool stop; bool cont; int64_t frame = -1; MidiGenerator() { reset(); } void reset() { for (int c = 0; c < CHANNELS; c++) { vels[c] = 100; notes[c] = 60; gates[c] = false; keyPressures[c] = -1; } channelPressure = -1; for (int i = 0; i < 128; i++) { ccs[i] = -1; } pw = 0x2000; clk = false; start = false; stop = false; cont = false; } void panic() { reset(); // Send all note off commands for (int note = 0; note <= 127; note++) { // Note off midi::Message m; m.setStatus(0x8); m.setNote(note); m.setValue(0); m.setFrame(frame); onMessage(m); } } /** Must be called before setNoteGate(). */ void setVelocity(int8_t vel, int c) { vels[c] = vel; } void setNoteGate(int8_t note, bool gate, int c) { bool changedNote = gate && gates[c] && (note != notes[c]); bool enabledGate = gate && !gates[c]; bool disabledGate = !gate && gates[c]; if (changedNote || disabledGate) { // Note off midi::Message m; m.setStatus(0x8); m.setNote(notes[c]); m.setValue(vels[c]); m.setFrame(frame); onMessage(m); } if (changedNote || enabledGate) { // Note on midi::Message m; m.setStatus(0x9); m.setNote(note); m.setValue(vels[c]); m.setFrame(frame); onMessage(m); } notes[c] = note; gates[c] = gate; } void setKeyPressure(int8_t val, int c) { if (keyPressures[c] == val) return; keyPressures[c] = val; // Polyphonic key pressure midi::Message m; m.setStatus(0xa); m.setNote(notes[c]); m.setValue(val); m.setFrame(frame); onMessage(m); } void setChannelPressure(int8_t val) { if (channelPressure == val) return; channelPressure = val; // Channel pressure midi::Message m; m.setSize(2); m.setStatus(0xd); m.setNote(val); m.setFrame(frame); onMessage(m); } void setCc(int8_t cc, int id) { if (ccs[id] == cc) return; ccs[id] = cc; // Continuous controller midi::Message m; m.setStatus(0xb); m.setNote(id); m.setValue(cc); m.setFrame(frame); onMessage(m); } void setModWheel(int8_t cc) { setCc(cc, 0x01); } void setVolume(int8_t cc) { setCc(cc, 0x07); } void setBalance(int8_t cc) { setCc(cc, 0x08); } void setPan(int8_t cc) { setCc(cc, 0x0a); } void setSustainPedal(int8_t cc) { setCc(cc, 0x40); } void setPitchWheel(int16_t pw) { if (this->pw == pw) return; this->pw = pw; // Pitch wheel midi::Message m; m.setStatus(0xe); m.setNote(pw & 0x7f); m.setValue((pw >> 7) & 0x7f); m.setFrame(frame); onMessage(m); } void setClock(bool clk) { if (this->clk == clk) return; this->clk = clk; if (clk) { // Timing clock midi::Message m; m.setSize(1); m.setStatus(0xf); m.setChannel(0x8); m.setFrame(frame); onMessage(m); } } void setStart(bool start) { if (this->start == start) return; this->start = start; if (start) { // Start midi::Message m; m.setSize(1); m.setStatus(0xf); m.setChannel(0xa); m.setFrame(frame); onMessage(m); } } void setContinue(bool cont) { if (this->cont == cont) return; this->cont = cont; if (cont) { // Continue midi::Message m; m.setSize(1); m.setStatus(0xf); m.setChannel(0xb); m.setFrame(frame); onMessage(m); } } void setStop(bool stop) { if (this->stop == stop) return; this->stop = stop; if (stop) { // Stop midi::Message m; m.setSize(1); m.setStatus(0xf); m.setChannel(0xc); m.setFrame(frame); onMessage(m); } } void setFrame(int64_t frame) { this->frame = frame; } virtual void onMessage(const midi::Message& message) {} }; /** Converts MIDI note and transport messages to gates, CV, and other states. MAX_CHANNELS is the maximum number of polyphonic channels. */ template struct MidiParser { // Settings /** Number of semitones to bend up/down by pitch wheel */ float pwRange; /** Enables pitch-wheel and mod-wheel exponential smoothing */ bool smooth; /** Number of 24 PPQN clocks between clock divider pulses */ uint32_t clockDivision; /** Actual number of polyphonic channels */ uint8_t channels; /** Method for assigning notes to polyphony channels */ enum PolyMode { ROTATE_MODE, REUSE_MODE, RESET_MODE, MPE_MODE, NUM_POLY_MODES }; PolyMode polyMode; // States /** Clock index from song start */ int64_t clock; /** Whether sustain pedal is held. */ bool pedal; uint8_t notes[MAX_CHANNELS]; bool gates[MAX_CHANNELS]; uint8_t velocities[MAX_CHANNELS]; uint8_t aftertouches[MAX_CHANNELS]; std::vector heldNotes; int8_t rotateIndex; /** Pitch wheel values, from -8192 to 8191. When MPE is disabled, only the first channel is used. */ int16_t pws[MAX_CHANNELS]; /** Mod wheel values, from 0 to 127. */ uint8_t mods[MAX_CHANNELS]; /** Smoothing filters for wheel values */ dsp::ExponentialFilter pwFilters[MAX_CHANNELS]; dsp::ExponentialFilter modFilters[MAX_CHANNELS]; dsp::PulseGenerator clockPulse; dsp::PulseGenerator clockDividerPulse; dsp::PulseGenerator retriggerPulses[MAX_CHANNELS]; dsp::PulseGenerator startPulse; dsp::PulseGenerator stopPulse; dsp::PulseGenerator continuePulse; MidiParser() { heldNotes.reserve(128); reset(); } /** Resets settings and performance state */ void reset() { clock = 0; smooth = true; channels = 1; polyMode = ROTATE_MODE; pwRange = 2.f; clockDivision = 24; setFilterLambda(30.f); panic(); } /** Resets performance state */ void panic() { for (uint8_t c = 0; c < MAX_CHANNELS; c++) { // Middle C notes[c] = 60; gates[c] = false; velocities[c] = 0; aftertouches[c] = 0; pws[c] = 0; mods[c] = 0; pwFilters[c].reset(); modFilters[c].reset(); } pedal = false; rotateIndex = -1; heldNotes.clear(); } void processFilters(float deltaTime) { uint8_t wheelChannels = getWheelChannels(); for (uint8_t c = 0; c < wheelChannels; c++) { float pw = pws[c] / 8191.f; pw = math::clamp(pw, -1.f, 1.f); if (smooth) pw = pwFilters[c].process(deltaTime, pw); else pwFilters[c].out = pw; float mod = mods[c] / 127.f; mod = math::clamp(mod, 0.f, 1.f); if (smooth) mod = modFilters[c].process(deltaTime, mod); else modFilters[c].out = mod; } } void processPulses(float deltaTime) { clockPulse.process(deltaTime); clockDividerPulse.process(deltaTime); startPulse.process(deltaTime); stopPulse.process(deltaTime); continuePulse.process(deltaTime); for (uint8_t c = 0; c < channels; c++) { retriggerPulses[c].process(deltaTime); } } void processMessage(const midi::Message& msg) { // DEBUG("MIDI: %ld %s", msg.getFrame(), msg.toString().c_str()); switch (msg.getStatus()) { // note off case 0x8: { releaseNote(msg.getNote()); } break; // note on case 0x9: { if (msg.getValue() > 0) { uint8_t c = msg.getChannel(); c = pressNote(msg.getNote(), c); velocities[c] = msg.getValue(); } else { // Note-on event with velocity 0 is an alternative for note-off event. releaseNote(msg.getNote()); } } break; // key pressure case 0xa: { // Set the aftertouches with the same note // TODO Should we handle the MPE case differently? for (uint8_t c = 0; c < MAX_CHANNELS; c++) { if (notes[c] == msg.getNote()) aftertouches[c] = msg.getValue(); } } break; // cc case 0xb: { processCC(msg); } break; // channel pressure case 0xd: { if (polyMode == MPE_MODE) { // Set the channel aftertouch aftertouches[msg.getChannel()] = msg.getNote(); } else { // Set all aftertouches for (uint8_t c = 0; c < MAX_CHANNELS; c++) { aftertouches[c] = msg.getNote(); } } } break; // pitch wheel case 0xe: { uint8_t c = (polyMode == MPE_MODE) ? msg.getChannel() : 0; int16_t pw = msg.getValue(); pw <<= 7; pw |= msg.getNote(); pw -= 8192; pws[c] = pw; } break; case 0xf: { processSystem(msg); } break; default: break; } } void processCC(const midi::Message& msg) { switch (msg.getNote()) { // mod case 0x01: { uint8_t c = (polyMode == MPE_MODE) ? msg.getChannel() : 0; mods[c] = msg.getValue(); } break; // sustain case 0x40: { if (msg.getValue() >= 64) pressPedal(); else releasePedal(); } break; // all notes off (panic) case 0x7b: { if (msg.getValue() == 0) { panic(); } } break; default: break; } } void processSystem(const midi::Message& msg) { switch (msg.getChannel()) { // Song Position Pointer case 0x2: { int64_t pos = int64_t(msg.getNote()) | (int64_t(msg.getValue()) << 7); clock = pos * 6; } break; // Timing case 0x8: { clockPulse.trigger(1e-3); if (clock % clockDivision == 0) { clockDividerPulse.trigger(1e-3); } clock++; } break; // Start case 0xa: { startPulse.trigger(1e-3); clock = 0; } break; // Continue case 0xb: { continuePulse.trigger(1e-3); } break; // Stop case 0xc: { stopPulse.trigger(1e-3); } break; default: break; } } uint8_t assignChannel(uint8_t note) { if (channels == 1) return 0; switch (polyMode) { case REUSE_MODE: { // Find channel with the same note for (uint8_t c = 0; c < channels; c++) { if (notes[c] == note) return c; } } // fallthrough case ROTATE_MODE: { // Find next available channel for (uint8_t i = 0; i < channels; i++) { rotateIndex++; if (rotateIndex >= channels) rotateIndex = 0; if (!gates[rotateIndex]) return rotateIndex; } // No notes are available. Advance rotateIndex once more. rotateIndex++; if (rotateIndex >= channels) rotateIndex = 0; return rotateIndex; } break; case RESET_MODE: { for (uint8_t c = 0; c < channels; c++) { if (!gates[c]) return c; } return channels - 1; } break; case MPE_MODE: { // This case is handled by querying the MIDI message channel. return 0; } break; default: return 0; } } /** Returns actual assigned channel */ uint8_t pressNote(uint8_t note, uint8_t channel) { // 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); // Determine actual channel if (polyMode == MPE_MODE) { // Channel is already decided for us } else { channel = assignChannel(note); } // Set note notes[channel] = note; gates[channel] = true; retriggerPulses[channel].trigger(1e-3); return channel; } 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; // Turn off gate of all channels with note for (uint8_t c = 0; c < channels; c++) { if (notes[c] == note) { gates[c] = false; } } // Set last note if monophonic if (channels == 1) { if (note == notes[0] && !heldNotes.empty()) { uint8_t lastNote = heldNotes.back(); notes[0] = lastNote; gates[0] = true; return; } } } void pressPedal() { if (pedal) return; pedal = true; } void releasePedal() { if (!pedal) return; pedal = false; // Set last note if monophonic if (channels == 1) { if (!heldNotes.empty()) { // Replace note with last held note uint8_t lastNote = heldNotes.back(); notes[0] = lastNote; } else { // Disable gate gates[0] = false; } } // Clear notes that are not held if polyphonic else { for (uint8_t c = 0; c < channels; c++) { if (!gates[c]) continue; // Disable all gates gates[c] = false; // Re-enable gate if channel's note is still held for (uint8_t note : heldNotes) { if (notes[c] == note) { gates[c] = true; break; } } } } } uint8_t getChannels() { return channels; } void setChannels(uint8_t channels) { if (channels == this->channels) return; this->channels = channels; panic(); } void setPolyMode(PolyMode polyMode) { if (polyMode == this->polyMode) return; this->polyMode = polyMode; panic(); } float getPitchVoltage(uint8_t channel) { uint8_t wheelChannel = (polyMode == MPE_MODE) ? channel : 0; return (notes[channel] - 60.f + pwFilters[wheelChannel].out * pwRange) / 12.f; } /** Sets exponential smoothing filter lambda speed. */ void setFilterLambda(float lambda) { for (uint8_t c = 0; c < MAX_CHANNELS; c++) { pwFilters[c].setLambda(lambda); modFilters[c].setLambda(lambda); } } /** Returns pitch wheel value, from -1 to 1. */ float getPw(uint8_t channel) { return pwFilters[channel].out; } /** Returns mod wheel value, from 0 to 1. */ float getMod(uint8_t channel) { return modFilters[channel].out; } /** Returns number of polyphonic channels for pitch and mod wheels. */ uint8_t getWheelChannels() { return (polyMode == MPE_MODE) ? MAX_CHANNELS : 1; } json_t* toJson() { json_t* rootJ = json_object(); json_object_set_new(rootJ, "pwRange", json_real(pwRange)); json_object_set_new(rootJ, "smooth", json_boolean(smooth)); 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)); // Saving/restoring pitch and mod doesn't make much sense for MPE. if (polyMode != MPE_MODE) { json_object_set_new(rootJ, "lastPw", json_integer(pws[0])); json_object_set_new(rootJ, "lastMod", json_integer(mods[0])); } // Assume all filter lambdas are the same json_object_set_new(rootJ, "filterLambda", json_real(pwFilters[0].lambda)); return rootJ; } void fromJson(json_t* rootJ) { json_t* pwRangeJ = json_object_get(rootJ, "pwRange"); if (pwRangeJ) pwRange = json_number_value(pwRangeJ); json_t* smoothJ = json_object_get(rootJ, "smooth"); if (smoothJ) smooth = json_boolean_value(smoothJ); json_t* channelsJ = json_object_get(rootJ, "channels"); if (channelsJ) setChannels(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* lastPwJ = json_object_get(rootJ, "lastPw"); if (lastPwJ) pws[0] = json_integer_value(lastPwJ); // In Rack <2.5.3, `lastPitch` was used from 0 to 16383. json_t* lastPitchJ = json_object_get(rootJ, "lastPitch"); if (lastPitchJ) pws[0] = json_integer_value(lastPitchJ) - 8192; json_t* lastModJ = json_object_get(rootJ, "lastMod"); if (lastModJ) mods[0] = json_integer_value(lastModJ); // Added in Rack 2.5.3 json_t* filterLambdaJ = json_object_get(rootJ, "filterLambda"); if (filterLambdaJ) setFilterLambda(json_number_value(filterLambdaJ)); } }; } // namespace dsp } // namespace rack