|
- #pragma once
- #include <dsp/common.hpp>
- #include <dsp/filter.hpp>
- #include <dsp/digital.hpp>
- #include <midi.hpp>
- #include <jansson.h>
-
-
- namespace rack {
- namespace dsp {
-
-
- /** Converts gates and CV to MIDI messages.
- CHANNELS is the number of polyphony channels. Use 1 for monophonic.
- */
- template <int CHANNELS>
- 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 <uint8_t MAX_CHANNELS>
- 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<uint8_t> 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
|