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