Browse Source

Implement ch.pressure and pitchbend in host-midi-cc; Cleanup

Signed-off-by: falkTX <falktx@falktx.com>
tags/22.02
falkTX 3 years ago
parent
commit
d24ad09f96
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
4 changed files with 152 additions and 76 deletions
  1. +1
    -1
      plugins/Cardinal/plugin.json
  2. +142
    -41
      plugins/Cardinal/src/HostMIDI-CC.cpp
  3. +1
    -1
      plugins/Cardinal/src/HostMIDI-Map.cpp
  4. +8
    -33
      plugins/Cardinal/src/HostMIDI.cpp

+ 1
- 1
plugins/Cardinal/plugin.json View File

@@ -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"


+ 142
- 41
plugins/Cardinal/src/HostMIDI-CC.cpp View File

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



+ 1
- 1
plugins/Cardinal/src/HostMIDI-Map.cpp View File

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


+ 8
- 33
plugins/Cardinal/src/HostMIDI.cpp View File

@@ -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 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 pw = (int) std::round((inputs[PITCHBEND_INPUT].getVoltage() + 5.f) / 10.f * 16383);
pw = clamp(pw, 0, 16383);
midiOutput.setPitchWheel(pw);

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…
Cancel
Save