| @@ -82,8 +82,7 @@ Slow. Not recommended for parameter scaling. | |||||
| */ | */ | ||||
| template <typename T> | template <typename T> | ||||
| T exponentialBipolar(T b, T x) { | T exponentialBipolar(T b, T x) { | ||||
| T a = b - 1.f / b; | |||||
| return (std::pow(b, x) - std::pow(b, -x)) / a; | |||||
| return (simd::pow(b, x) - simd::pow(b, -x)) / (b - 1.f / b); | |||||
| } | } | ||||
| @@ -0,0 +1,212 @@ | |||||
| #pragma once | |||||
| #include <dsp/common.hpp> | |||||
| #include <midi.hpp> | |||||
| 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 channelPressures[CHANNELS]; | |||||
| int8_t ccs[128]; | |||||
| int16_t pw; | |||||
| bool clk; | |||||
| bool start; | |||||
| bool stop; | |||||
| bool cont; | |||||
| MidiGenerator() { | |||||
| reset(); | |||||
| } | |||||
| void reset() { | |||||
| for (int c = 0; c < CHANNELS; c++) { | |||||
| vels[c] = 100; | |||||
| notes[c] = 60; | |||||
| gates[c] = false; | |||||
| keyPressures[c] = -1; | |||||
| channelPressures[c] = -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); | |||||
| 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]); | |||||
| onMessage(m); | |||||
| } | |||||
| if (changedNote || enabledGate) { | |||||
| // Note on | |||||
| midi::Message m; | |||||
| m.setStatus(0x9); | |||||
| m.setNote(note); | |||||
| m.setValue(vels[c]); | |||||
| 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); | |||||
| onMessage(m); | |||||
| } | |||||
| void setChannelPressure(int8_t val, int c) { | |||||
| if (channelPressures[c] == val) | |||||
| return; | |||||
| channelPressures[c] = val; | |||||
| // Channel pressure | |||||
| midi::Message m; | |||||
| m.setSize(2); | |||||
| m.setStatus(0xd); | |||||
| m.setNote(val); | |||||
| onMessage(m); | |||||
| } | |||||
| void setCc(int8_t cc, int id) { | |||||
| if (ccs[id] == cc) | |||||
| return; | |||||
| ccs[id] = cc; | |||||
| // Control change | |||||
| midi::Message m; | |||||
| m.setStatus(0xb); | |||||
| m.setNote(id); | |||||
| m.setValue(cc); | |||||
| onMessage(m); | |||||
| } | |||||
| void setModWheel(int8_t mw) { | |||||
| setCc(mw, 0x01); | |||||
| } | |||||
| void setVolume(int8_t mw) { | |||||
| setCc(mw, 0x07); | |||||
| } | |||||
| void setPan(int8_t mw) { | |||||
| setCc(mw, 0x0a); | |||||
| } | |||||
| 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); | |||||
| 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); | |||||
| 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); | |||||
| 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); | |||||
| 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); | |||||
| onMessage(m); | |||||
| } | |||||
| } | |||||
| virtual void onMessage(midi::Message message) {} | |||||
| }; | |||||
| } // namespace dsp | |||||
| } // namespace rack | |||||
| @@ -18,6 +18,10 @@ struct Message { | |||||
| uint8_t size = 3; | uint8_t size = 3; | ||||
| uint8_t bytes[3] = {}; | uint8_t bytes[3] = {}; | ||||
| void setSize(uint8_t size) { | |||||
| assert(size <= 3); | |||||
| this->size = size; | |||||
| } | |||||
| uint8_t getChannel() { | uint8_t getChannel() { | ||||
| return bytes[0] & 0xf; | return bytes[0] & 0xf; | ||||
| } | } | ||||
| @@ -25,19 +29,19 @@ struct Message { | |||||
| bytes[0] = (bytes[0] & 0xf0) | (channel & 0xf); | bytes[0] = (bytes[0] & 0xf0) | (channel & 0xf); | ||||
| } | } | ||||
| uint8_t getStatus() { | uint8_t getStatus() { | ||||
| return (bytes[0] >> 4) & 0xf; | |||||
| return bytes[0] >> 4; | |||||
| } | } | ||||
| void setStatus(uint8_t status) { | void setStatus(uint8_t status) { | ||||
| bytes[0] = (bytes[0] & 0xf) | ((status << 4) & 0xf0); | |||||
| bytes[0] = (bytes[0] & 0xf) | (status << 4); | |||||
| } | } | ||||
| uint8_t getNote() { | uint8_t getNote() { | ||||
| return bytes[1] & 0x7f; | |||||
| return bytes[1]; | |||||
| } | } | ||||
| void setNote(uint8_t note) { | void setNote(uint8_t note) { | ||||
| bytes[1] = note & 0x7f; | bytes[1] = note & 0x7f; | ||||
| } | } | ||||
| uint8_t getValue() { | uint8_t getValue() { | ||||
| return bytes[2] & 0x7f; | |||||
| return bytes[2]; | |||||
| } | } | ||||
| void setValue(uint8_t value) { | void setValue(uint8_t value) { | ||||
| bytes[2] = value & 0x7f; | bytes[2] = value & 0x7f; | ||||
| @@ -84,6 +84,7 @@ | |||||
| #include <dsp/fft.hpp> | #include <dsp/fft.hpp> | ||||
| #include <dsp/filter.hpp> | #include <dsp/filter.hpp> | ||||
| #include <dsp/fir.hpp> | #include <dsp/fir.hpp> | ||||
| #include <dsp/midi.hpp> | |||||
| #include <dsp/minblep.hpp> | #include <dsp/minblep.hpp> | ||||
| #include <dsp/ode.hpp> | #include <dsp/ode.hpp> | ||||
| #include <dsp/resampler.hpp> | #include <dsp/resampler.hpp> | ||||
| @@ -1,207 +1,14 @@ | |||||
| #include "plugin.hpp" | #include "plugin.hpp" | ||||
| template <int C> | |||||
| struct PolyphonicMidiOutput : midi::Output { | |||||
| int vels[C]; | |||||
| int lastNotes[C]; | |||||
| int notes[C]; | |||||
| bool lastGates[C]; | |||||
| bool gates[C]; | |||||
| int lastAfts[C]; | |||||
| int lastPw; | |||||
| int lastMw; | |||||
| bool lastClk; | |||||
| int lastVol; | |||||
| int lastPan; | |||||
| bool lastStart; | |||||
| bool lastStop; | |||||
| bool lastCont; | |||||
| PolyphonicMidiOutput() { | |||||
| reset(); | |||||
| struct MidiOutput : dsp::MidiGenerator<PORT_MAX_CHANNELS>, midi::Output { | |||||
| void onMessage(midi::Message message) override { | |||||
| midi::Output::sendMessage(message); | |||||
| } | } | ||||
| void reset() { | void reset() { | ||||
| for (int c = 0; c < C; c++) { | |||||
| vels[c] = 100; | |||||
| lastNotes[c] = notes[c] = 60; | |||||
| lastGates[c] = gates[c] = false; | |||||
| lastAfts[c] = -1; | |||||
| } | |||||
| lastPw = 0x2000; | |||||
| lastMw = 0; | |||||
| lastClk = false; | |||||
| lastVol = 127; | |||||
| lastPan = 64; | |||||
| lastStart = false; | |||||
| lastStop = false; | |||||
| lastCont = 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); | |||||
| sendMessage(m); | |||||
| } | |||||
| } | |||||
| void setVelocity(int vel, int c) { | |||||
| vels[c] = vel; | |||||
| } | |||||
| void setNote(int note, int c) { | |||||
| notes[c] = note; | |||||
| } | |||||
| void setGate(bool gate, int c) { | |||||
| gates[c] = gate; | |||||
| } | |||||
| void stepChannel(int c) { | |||||
| bool changedNote = gates[c] && lastGates[c] && notes[c] != lastNotes[c]; | |||||
| bool enabledGate = gates[c] && !lastGates[c]; | |||||
| bool disabledGate = !gates[c] && lastGates[c]; | |||||
| if (changedNote || enabledGate) { | |||||
| // Note on | |||||
| midi::Message m; | |||||
| m.setStatus(0x9); | |||||
| m.setNote(notes[c]); | |||||
| m.setValue(vels[c]); | |||||
| sendMessage(m); | |||||
| } | |||||
| if (changedNote || disabledGate) { | |||||
| // Note off | |||||
| midi::Message m; | |||||
| m.setStatus(0x8); | |||||
| m.setNote(lastNotes[c]); | |||||
| m.setValue(vels[c]); | |||||
| sendMessage(m); | |||||
| } | |||||
| lastNotes[c] = notes[c]; | |||||
| lastGates[c] = gates[c]; | |||||
| } | |||||
| void setAftertouch(int aft, int c) { | |||||
| if (lastAfts[c] == aft) | |||||
| return; | |||||
| lastAfts[c] = aft; | |||||
| // Polyphonic key pressure | |||||
| midi::Message m; | |||||
| m.setStatus(0xa); | |||||
| m.setNote(notes[c]); | |||||
| m.setValue(aft); | |||||
| sendMessage(m); | |||||
| } | |||||
| void setPitchWheel(int pw) { | |||||
| if (lastPw == pw) | |||||
| return; | |||||
| lastPw = pw; | |||||
| // Pitch wheel | |||||
| midi::Message m; | |||||
| m.setStatus(0xe); | |||||
| m.setNote(pw & 0x7f); | |||||
| m.setValue((pw >> 7) & 0x7f); | |||||
| sendMessage(m); | |||||
| } | |||||
| void setModWheel(int mw) { | |||||
| if (lastMw == mw) | |||||
| return; | |||||
| lastMw = mw; | |||||
| // CC Mod wheel | |||||
| midi::Message m; | |||||
| m.setStatus(0xb); | |||||
| m.setNote(0x01); | |||||
| m.setValue(mw); | |||||
| sendMessage(m); | |||||
| } | |||||
| void setClock(bool clk) { | |||||
| if (lastClk == clk) | |||||
| return; | |||||
| lastClk = clk; | |||||
| if (clk) { | |||||
| // Timing clock | |||||
| midi::Message m; | |||||
| m.size = 1; | |||||
| m.setStatus(0xf); | |||||
| m.setChannel(0x8); | |||||
| sendMessage(m); | |||||
| } | |||||
| } | |||||
| void setVolume(int vol) { | |||||
| if (lastVol == vol) | |||||
| return; | |||||
| lastVol = vol; | |||||
| // CC Volume | |||||
| midi::Message m; | |||||
| m.setStatus(0xb); | |||||
| m.setNote(0x07); | |||||
| m.setValue(vol); | |||||
| sendMessage(m); | |||||
| } | |||||
| void setPan(int pan) { | |||||
| if (lastPan == pan) | |||||
| return; | |||||
| lastPan = pan; | |||||
| // CC Pan | |||||
| midi::Message m; | |||||
| m.setStatus(0xb); | |||||
| m.setNote(0x0a); | |||||
| m.setValue(pan); | |||||
| sendMessage(m); | |||||
| } | |||||
| void setStart(bool start) { | |||||
| if (lastStart == start) | |||||
| return; | |||||
| lastStart = start; | |||||
| if (start) { | |||||
| // Start | |||||
| midi::Message m; | |||||
| m.size = 1; | |||||
| m.setStatus(0xf); | |||||
| m.setChannel(0xa); | |||||
| sendMessage(m); | |||||
| } | |||||
| } | |||||
| void setContinue(bool cont) { | |||||
| if (lastCont == cont) | |||||
| return; | |||||
| lastCont = cont; | |||||
| if (cont) { | |||||
| // Continue | |||||
| midi::Message m; | |||||
| m.size = 1; | |||||
| m.setStatus(0xf); | |||||
| m.setChannel(0xb); | |||||
| sendMessage(m); | |||||
| } | |||||
| } | |||||
| void setStop(bool stop) { | |||||
| if (lastStop == stop) | |||||
| return; | |||||
| lastStop = stop; | |||||
| if (stop) { | |||||
| // Stop | |||||
| midi::Message m; | |||||
| m.size = 1; | |||||
| m.setStatus(0xf); | |||||
| m.setChannel(0xc); | |||||
| sendMessage(m); | |||||
| } | |||||
| Output::reset(); | |||||
| MidiGenerator::reset(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -232,7 +39,7 @@ struct CV_MIDI : Module { | |||||
| NUM_LIGHTS | NUM_LIGHTS | ||||
| }; | }; | ||||
| PolyphonicMidiOutput<PORT_MAX_CHANNELS> midiOutput; | |||||
| MidiOutput midiOutput; | |||||
| float rateLimiterPhase = 0.f; | float rateLimiterPhase = 0.f; | ||||
| CV_MIDI() { | CV_MIDI() { | ||||
| @@ -242,7 +49,6 @@ struct CV_MIDI : Module { | |||||
| void onReset() override { | void onReset() override { | ||||
| midiOutput.reset(); | midiOutput.reset(); | ||||
| midiOutput.midi::Output::reset(); | |||||
| } | } | ||||
| void process(const ProcessArgs &args) override { | void process(const ProcessArgs &args) override { | ||||
| @@ -262,16 +68,12 @@ struct CV_MIDI : Module { | |||||
| int note = (int) std::round(inputs[PITCH_INPUT].getVoltage(c) * 12.f + 60.f); | int note = (int) std::round(inputs[PITCH_INPUT].getVoltage(c) * 12.f + 60.f); | ||||
| note = clamp(note, 0, 127); | note = clamp(note, 0, 127); | ||||
| midiOutput.setNote(note, c); | |||||
| bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f; | bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f; | ||||
| midiOutput.setGate(gate, c); | |||||
| midiOutput.stepChannel(c); | |||||
| midiOutput.setNoteGate(note, gate, c); | |||||
| int aft = (int) std::round(inputs[AFT_INPUT].getPolyVoltage(c) / 10.f * 127); | int aft = (int) std::round(inputs[AFT_INPUT].getPolyVoltage(c) / 10.f * 127); | ||||
| aft = clamp(aft, 0, 127); | aft = clamp(aft, 0, 127); | ||||
| midiOutput.setAftertouch(aft, c); | |||||
| midiOutput.setKeyPressure(aft, c); | |||||
| } | } | ||||
| int pw = (int) std::round((inputs[PW_INPUT].getVoltage() + 5.f) / 10.f * 0x4000); | int pw = (int) std::round((inputs[PW_INPUT].getVoltage() + 5.f) / 10.f * 0x4000); | ||||