Implement ch.pressure and pitchbend in host-midi-cc; Cleanup
Signed-off-by: falkTX <falktx@falktx.com>
This commit is contained in:
parent
de63835867
commit
d24ad09f96
4 changed files with 152 additions and 76 deletions
|
|
@ -50,7 +50,7 @@
|
|||
{
|
||||
"slug": "HostMIDICC",
|
||||
"name": "Host MIDI CC",
|
||||
"description": "Exposes host-provided MIDI CC in a module",
|
||||
"description": "Exposes host-provided MIDI CC, channel pressure and pitchbend in a module",
|
||||
"tags": [
|
||||
"External",
|
||||
"MIDI"
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ struct HostMIDICC : Module {
|
|||
};
|
||||
enum InputIds {
|
||||
ENUMS(CC_INPUTS, 16),
|
||||
CC_INPUT_CHANNEL_PRESSURE,
|
||||
CC_INPUT_CH_PRESSURE,
|
||||
CC_INPUT_PITCHBEND,
|
||||
NUM_INPUTS
|
||||
};
|
||||
enum OutputIds {
|
||||
ENUMS(CC_OUTPUT, 16),
|
||||
CC_OUTPUT_CHANNEL_PRESSURE,
|
||||
CC_OUTPUT_CH_PRESSURE,
|
||||
CC_OUTPUT_PITCHBEND,
|
||||
NUM_OUTPUTS
|
||||
};
|
||||
|
|
@ -64,16 +64,19 @@ struct HostMIDICC : Module {
|
|||
int64_t lastBlockFrame;
|
||||
uint8_t channel;
|
||||
|
||||
uint8_t chPressure[16];
|
||||
uint16_t pitchbend[16];
|
||||
|
||||
// stuff from Rack
|
||||
/** [cc][channel] */
|
||||
int8_t ccValues[128][16];
|
||||
uint8_t ccValues[128][16];
|
||||
/** When LSB is enabled for CC 0-31, the MSB is stored here until the LSB is received.
|
||||
[cc][channel]
|
||||
*/
|
||||
int8_t msbValues[32][16];
|
||||
uint8_t msbValues[32][16];
|
||||
int learningId;
|
||||
/** [cell][channel] */
|
||||
dsp::ExponentialFilter valueFilters[16][16];
|
||||
dsp::ExponentialFilter valueFilters[NUM_OUTPUTS][16];
|
||||
bool smooth;
|
||||
bool mpeMode;
|
||||
bool lsbMode;
|
||||
|
|
@ -81,7 +84,7 @@ struct HostMIDICC : Module {
|
|||
MidiInput(CardinalPluginContext* const pc)
|
||||
: pcontext(pc)
|
||||
{
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_OUTPUTS; i++) {
|
||||
for (int c = 0; c < 16; c++) {
|
||||
valueFilters[i][c].setTau(1 / 30.f);
|
||||
}
|
||||
|
|
@ -107,6 +110,10 @@ struct HostMIDICC : Module {
|
|||
msbValues[cc][c] = 0;
|
||||
}
|
||||
}
|
||||
for (int c = 0; c < 16; c++) {
|
||||
chPressure[c] = 0;
|
||||
pitchbend[c] = 8192;
|
||||
}
|
||||
learningId = -1;
|
||||
smooth = true;
|
||||
mpeMode = false;
|
||||
|
|
@ -148,33 +155,47 @@ struct HostMIDICC : Module {
|
|||
continue;
|
||||
}
|
||||
|
||||
// adapted from Rack
|
||||
if ((data[0] & 0xF0) != 0xB0)
|
||||
const uint8_t status = data[0] & 0xF0;
|
||||
const uint8_t chan = data[0] & 0x0F;
|
||||
|
||||
/**/ if (status == 0xD0)
|
||||
{
|
||||
chPressure[chan] = data[1];
|
||||
}
|
||||
else if (status == 0xE0)
|
||||
{
|
||||
pitchbend[chan] = (data[2] << 7) | data[1];
|
||||
}
|
||||
else if (status != 0xB0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t c = mpeMode ? (data[0] & 0x0F) : 0;
|
||||
uint8_t cc = data[1];
|
||||
// adapted from Rack
|
||||
const uint8_t c = mpeMode ? chan : 0;
|
||||
const uint8_t cc = data[1];
|
||||
const uint8_t value = data[2];
|
||||
|
||||
// Allow CC to be negative if the 8th bit is set.
|
||||
// The gamepad driver abuses this, for example.
|
||||
// Cast uint8_t to int8_t
|
||||
int8_t value = data[2];
|
||||
// Learn
|
||||
if (learningId >= 0 && ccValues[cc][c] != value) {
|
||||
if (learningId >= 0 && ccValues[cc][c] != value)
|
||||
{
|
||||
learnedCcs[learningId] = cc;
|
||||
learningId = -1;
|
||||
}
|
||||
|
||||
if (lsbMode && cc < 32) {
|
||||
if (lsbMode && cc < 32)
|
||||
{
|
||||
// Don't set MSB yet. Wait for LSB to be received.
|
||||
msbValues[cc][c] = value;
|
||||
}
|
||||
else if (lsbMode && 32 <= cc && cc < 64) {
|
||||
else if (lsbMode && 32 <= cc && cc < 64)
|
||||
{
|
||||
// Apply MSB when LSB is received
|
||||
ccValues[cc - 32][c] = msbValues[cc - 32][c];
|
||||
ccValues[cc][c] = value;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
ccValues[cc][c] = value;
|
||||
}
|
||||
}
|
||||
|
|
@ -184,35 +205,87 @@ struct HostMIDICC : Module {
|
|||
// Rack stuff
|
||||
const int channels = mpeMode ? 16 : 1;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (!outputs[CC_OUTPUT + i].isConnected())
|
||||
continue;
|
||||
outputs[CC_OUTPUT + i].setChannels(channels);
|
||||
|
||||
int cc = learnedCcs[i];
|
||||
|
||||
for (int c = 0; c < channels; c++) {
|
||||
for (int c = 0; c < channels; c++)
|
||||
{
|
||||
int16_t cellValue = int16_t(ccValues[cc][c]) * 128;
|
||||
if (lsbMode && cc < 32)
|
||||
cellValue += ccValues[cc + 32][c];
|
||||
|
||||
// Maximum value for 14-bit CC should be MSB=127 LSB=0, not MSB=127 LSB=127, because this is the maximum value that 7-bit controllers can send.
|
||||
float value = float(cellValue) / (128 * 127);
|
||||
// Support negative values because the gamepad MIDI driver generates nonstandard 8-bit CC values.
|
||||
value = clamp(value, -1.f, 1.f);
|
||||
const float value = static_cast<float>(cellValue) / (128.0f * 127.0f);
|
||||
|
||||
// Detect behavior from MIDI buttons.
|
||||
if (smooth && std::fabs(valueFilters[i][c].out - value) < 1.f) {
|
||||
if (smooth && std::fabs(valueFilters[i][c].out - value) < 1.f)
|
||||
{
|
||||
// Smooth value with filter
|
||||
valueFilters[i][c].process(args.sampleTime, value);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Jump value
|
||||
valueFilters[i][c].out = value;
|
||||
}
|
||||
|
||||
outputs[CC_OUTPUT + i].setVoltage(valueFilters[i][c].out * 10.f, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (outputs[CC_OUTPUT_CH_PRESSURE].isConnected())
|
||||
{
|
||||
outputs[CC_OUTPUT_CH_PRESSURE].setChannels(channels);
|
||||
|
||||
for (int c = 0; c < channels; c++)
|
||||
{
|
||||
const float value = static_cast<float>(chPressure[c]) / 128.0f;
|
||||
|
||||
// Detect behavior from MIDI buttons.
|
||||
if (smooth && std::fabs(valueFilters[CC_OUTPUT_CH_PRESSURE][c].out - value) < 1.f)
|
||||
{
|
||||
// Smooth value with filter
|
||||
valueFilters[CC_OUTPUT_CH_PRESSURE][c].process(args.sampleTime, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Jump value
|
||||
valueFilters[CC_OUTPUT_CH_PRESSURE][c].out = value;
|
||||
}
|
||||
|
||||
outputs[CC_OUTPUT_CH_PRESSURE].setVoltage(valueFilters[CC_OUTPUT_CH_PRESSURE][c].out * 10.f, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (outputs[CC_OUTPUT_PITCHBEND].isConnected())
|
||||
{
|
||||
outputs[CC_OUTPUT_PITCHBEND].setChannels(channels);
|
||||
|
||||
for (int c = 0; c < channels; c++)
|
||||
{
|
||||
const float value = static_cast<float>(pitchbend[c]) / 16384.0f;
|
||||
|
||||
// Detect behavior from MIDI buttons.
|
||||
if (smooth && std::fabs(valueFilters[CC_OUTPUT_PITCHBEND][c].out - value) < 1.f)
|
||||
{
|
||||
// Smooth value with filter
|
||||
valueFilters[CC_OUTPUT_PITCHBEND][c].process(args.sampleTime, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Jump value
|
||||
valueFilters[CC_OUTPUT_PITCHBEND][c].out = value;
|
||||
}
|
||||
|
||||
outputs[CC_OUTPUT_CH_PRESSURE].setVoltage(valueFilters[CC_OUTPUT_PITCHBEND][c].out * 10.f, c);
|
||||
}
|
||||
}
|
||||
|
||||
return blockFrameChanged;
|
||||
}
|
||||
|
||||
|
|
@ -224,8 +297,7 @@ struct HostMIDICC : Module {
|
|||
uint8_t channel = 0;
|
||||
|
||||
// from Rack
|
||||
dsp::Timer rateLimiterTimer;
|
||||
int lastValues[128];
|
||||
int lastValues[130];
|
||||
int64_t frame = 0;
|
||||
|
||||
MidiOutput(CardinalPluginContext* const pc)
|
||||
|
|
@ -236,16 +308,15 @@ struct HostMIDICC : Module {
|
|||
|
||||
void reset()
|
||||
{
|
||||
for (int n = 0; n < 128; n++)
|
||||
for (int n = 0; n < 130; ++n)
|
||||
lastValues[n] = -1;
|
||||
}
|
||||
|
||||
void setValue(int value, int cc)
|
||||
void sendCC(const int cc, const int value)
|
||||
{
|
||||
if (value == lastValues[cc])
|
||||
if (lastValues[cc] == value)
|
||||
return;
|
||||
lastValues[cc] = value;
|
||||
// CC
|
||||
midi::Message m;
|
||||
m.setStatus(0xb);
|
||||
m.setNote(cc);
|
||||
|
|
@ -254,6 +325,31 @@ struct HostMIDICC : Module {
|
|||
sendMessage(m);
|
||||
}
|
||||
|
||||
void sendChanPressure(const int pressure)
|
||||
{
|
||||
if (lastValues[128] == pressure)
|
||||
return;
|
||||
lastValues[128] = pressure;
|
||||
midi::Message m;
|
||||
m.setStatus(0xd);
|
||||
m.setNote(pressure);
|
||||
m.setFrame(frame);
|
||||
sendMessage(m);
|
||||
}
|
||||
|
||||
void sendPitchbend(const int pitchbend)
|
||||
{
|
||||
if (lastValues[129] == pitchbend)
|
||||
return;
|
||||
lastValues[129] = pitchbend;
|
||||
midi::Message m;
|
||||
m.setStatus(0xe);
|
||||
m.setNote(pitchbend & 0x7F);
|
||||
m.setValue(pitchbend >> 7);
|
||||
m.setFrame(frame);
|
||||
sendMessage(m);
|
||||
}
|
||||
|
||||
void sendMessage(const midi::Message& message)
|
||||
{
|
||||
pcontext->writeMidiMessage(message, channel);
|
||||
|
|
@ -276,13 +372,13 @@ struct HostMIDICC : Module {
|
|||
for (int i = 0; i < 16; i++)
|
||||
configInput(CC_INPUTS + i, string::f("Cell %d", i + 1));
|
||||
|
||||
configInput(CC_INPUT_CHANNEL_PRESSURE, "Channel pressure");
|
||||
configInput(CC_INPUT_CH_PRESSURE, "Channel pressure");
|
||||
configInput(CC_INPUT_PITCHBEND, "Pitchbend");
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
configOutput(CC_OUTPUT + i, string::f("Cell %d", i + 1));
|
||||
|
||||
configOutput(CC_OUTPUT_CHANNEL_PRESSURE, "Channel pressure");
|
||||
configOutput(CC_OUTPUT_CH_PRESSURE, "Channel pressure");
|
||||
configOutput(CC_OUTPUT_PITCHBEND, "Pitchbend");
|
||||
|
||||
onReset();
|
||||
|
|
@ -304,18 +400,23 @@ struct HostMIDICC : Module {
|
|||
else
|
||||
++midiOutput.frame;
|
||||
|
||||
const float rateLimiterPeriod = 1 / 200.f;
|
||||
bool rateLimiterTriggered = (midiOutput.rateLimiterTimer.process(args.sampleTime) >= rateLimiterPeriod);
|
||||
if (rateLimiterTriggered)
|
||||
midiOutput.rateLimiterTimer.time -= rateLimiterPeriod;
|
||||
else
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
int value = (int) std::round(inputs[CC_INPUTS + i].getVoltage() / 10.f * 127);
|
||||
value = clamp(value, 0, 127);
|
||||
midiOutput.setValue(value, learnedCcs[i]);
|
||||
midiOutput.sendCC(learnedCcs[i], value);
|
||||
}
|
||||
|
||||
{
|
||||
int value = (int) std::round(inputs[CC_INPUT_CH_PRESSURE].getVoltage() / 10.f * 127);
|
||||
value = clamp(value, 0, 127);
|
||||
midiOutput.sendChanPressure(value);
|
||||
}
|
||||
|
||||
{
|
||||
int value = (int) std::round(inputs[CC_INPUT_PITCHBEND].getVoltage() / 10.f * 16383);
|
||||
value = clamp(value, 0, 16383);
|
||||
midiOutput.sendPitchbend(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -708,7 +708,7 @@ struct HostMIDIMapWidget : ModuleWidget {
|
|||
for (int c = 0; c <= 16; c++) {
|
||||
menu->addChild(createCheckMenuItem((c == 0) ? "All" : string::f("%d", c), "",
|
||||
[=]() {return module->channel == c;},
|
||||
[=]() {module->channel = c;}
|
||||
[=]() {module->setChannel(c);}
|
||||
));
|
||||
}
|
||||
return menu;
|
||||
|
|
|
|||
|
|
@ -519,7 +519,6 @@ struct HostMIDI : Module {
|
|||
struct MidiOutput : dsp::MidiGenerator<PORT_MAX_CHANNELS> {
|
||||
CardinalPluginContext* const pcontext;
|
||||
uint8_t channel = 0;
|
||||
dsp::Timer rateLimiterTimer;
|
||||
|
||||
MidiOutput(CardinalPluginContext* const pc)
|
||||
: pcontext(pc) {}
|
||||
|
|
@ -579,15 +578,8 @@ struct HostMIDI : Module {
|
|||
else
|
||||
++midiOutput.frame;
|
||||
|
||||
// MIDI baud rate is 31250 b/s, or 3125 B/s.
|
||||
// CC messages are 3 bytes, so we can send a maximum of 1041 CC messages per second.
|
||||
// Since multiple CCs can be generated, play it safe and limit the CC rate to 200 Hz.
|
||||
static constexpr const float rateLimiterPeriod = 1 / 200.f;
|
||||
bool rateLimiterTriggered = (midiOutput.rateLimiterTimer.process(args.sampleTime) >= rateLimiterPeriod);
|
||||
if (rateLimiterTriggered)
|
||||
midiOutput.rateLimiterTimer.time -= rateLimiterPeriod;
|
||||
|
||||
for (int c = 0; c < inputs[PITCH_INPUT].getChannels(); c++) {
|
||||
for (int c = 0; c < inputs[PITCH_INPUT].getChannels(); ++c)
|
||||
{
|
||||
int vel = (int) std::round(inputs[VELOCITY_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127);
|
||||
vel = clamp(vel, 0, 127);
|
||||
midiOutput.setVelocity(vel, c);
|
||||
|
|
@ -602,30 +594,13 @@ struct HostMIDI : Module {
|
|||
midiOutput.setKeyPressure(aft, c);
|
||||
}
|
||||
|
||||
if (rateLimiterTriggered) {
|
||||
int pw = (int) std::round((inputs[PITCHBEND_INPUT].getVoltage() + 5.f) / 10.f * 0x4000);
|
||||
pw = clamp(pw, 0, 0x3fff);
|
||||
midiOutput.setPitchWheel(pw);
|
||||
int pw = (int) std::round((inputs[PITCHBEND_INPUT].getVoltage() + 5.f) / 10.f * 16383);
|
||||
pw = clamp(pw, 0, 16383);
|
||||
midiOutput.setPitchWheel(pw);
|
||||
|
||||
int mw = (int) std::round(inputs[MODWHEEL_INPUT].getVoltage() / 10.f * 127);
|
||||
mw = clamp(mw, 0, 127);
|
||||
midiOutput.setModWheel(mw);
|
||||
|
||||
/* unused
|
||||
int vol = (int) std::round(inputs[VOL_INPUT].getNormalVoltage(10.f) / 10.f * 127);
|
||||
vol = clamp(vol, 0, 127);
|
||||
midiOutput.setVolume(vol);
|
||||
|
||||
int pan = (int) std::round((inputs[PAN_INPUT].getVoltage() + 5.f) / 10.f * 127);
|
||||
pan = clamp(pan, 0, 127);
|
||||
midiOutput.setPan(pan);
|
||||
*/
|
||||
}
|
||||
|
||||
/* unused
|
||||
bool clk = inputs[CLK_INPUT].getVoltage() >= 1.f;
|
||||
midiOutput.setClock(clk);
|
||||
*/
|
||||
int mw = (int) std::round(inputs[MODWHEEL_INPUT].getVoltage() / 10.f * 127);
|
||||
mw = clamp(mw, 0, 127);
|
||||
midiOutput.setModWheel(mw);
|
||||
|
||||
bool start = inputs[START_INPUT].getVoltage() >= 1.f;
|
||||
midiOutput.setStart(start);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue