@@ -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); | ||||