| @@ -82,8 +82,7 @@ Slow. Not recommended for parameter scaling. | |||
| */ | |||
| template <typename T> | |||
| 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 bytes[3] = {}; | |||
| void setSize(uint8_t size) { | |||
| assert(size <= 3); | |||
| this->size = size; | |||
| } | |||
| uint8_t getChannel() { | |||
| return bytes[0] & 0xf; | |||
| } | |||
| @@ -25,19 +29,19 @@ struct Message { | |||
| bytes[0] = (bytes[0] & 0xf0) | (channel & 0xf); | |||
| } | |||
| uint8_t getStatus() { | |||
| return (bytes[0] >> 4) & 0xf; | |||
| return bytes[0] >> 4; | |||
| } | |||
| void setStatus(uint8_t status) { | |||
| bytes[0] = (bytes[0] & 0xf) | ((status << 4) & 0xf0); | |||
| bytes[0] = (bytes[0] & 0xf) | (status << 4); | |||
| } | |||
| uint8_t getNote() { | |||
| return bytes[1] & 0x7f; | |||
| return bytes[1]; | |||
| } | |||
| void setNote(uint8_t note) { | |||
| bytes[1] = note & 0x7f; | |||
| } | |||
| uint8_t getValue() { | |||
| return bytes[2] & 0x7f; | |||
| return bytes[2]; | |||
| } | |||
| void setValue(uint8_t value) { | |||
| bytes[2] = value & 0x7f; | |||
| @@ -84,6 +84,7 @@ | |||
| #include <dsp/fft.hpp> | |||
| #include <dsp/filter.hpp> | |||
| #include <dsp/fir.hpp> | |||
| #include <dsp/midi.hpp> | |||
| #include <dsp/minblep.hpp> | |||
| #include <dsp/ode.hpp> | |||
| #include <dsp/resampler.hpp> | |||
| @@ -1,207 +1,14 @@ | |||
| #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() { | |||
| 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 | |||
| }; | |||
| PolyphonicMidiOutput<PORT_MAX_CHANNELS> midiOutput; | |||
| MidiOutput midiOutput; | |||
| float rateLimiterPhase = 0.f; | |||
| CV_MIDI() { | |||
| @@ -242,7 +49,6 @@ struct CV_MIDI : Module { | |||
| void onReset() override { | |||
| midiOutput.reset(); | |||
| midiOutput.midi::Output::reset(); | |||
| } | |||
| 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); | |||
| note = clamp(note, 0, 127); | |||
| midiOutput.setNote(note, c); | |||
| 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); | |||
| 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); | |||