Browse Source

Added both channel and key pressure (aftertouch) in MIDI-CV. Fixed mod and pitch for MPE.

tags/v1.0.0
Andrew Belt 6 years ago
parent
commit
42cbb5194a
1 changed files with 74 additions and 37 deletions
  1. +74
    -37
      src/Core/MIDI_CV.cpp

+ 74
- 37
src/Core/MIDI_CV.cpp View File

@@ -48,18 +48,18 @@ struct MIDI_CV : Module {
// Indexed by channel
uint8_t notes[16];
bool gates[16];
// Indexed by note
uint8_t velocities[128];
uint8_t aftertouches[128];
uint8_t velocities[16];
uint8_t aftertouches[16];
std::vector<uint8_t> heldNotes;

int rotateIndex;

uint16_t pitch;
uint8_t mod;
// 16 channels for MPE. When MPE is disabled, only the first channel is used.
uint16_t pitches[16];
uint8_t mods[16];
dsp::ExponentialFilter pitchFilters[16];
dsp::ExponentialFilter modFilters[16];

dsp::ExponentialFilter pitchFilter;
dsp::ExponentialFilter modFilter;
dsp::PulseGenerator clockPulse;
dsp::PulseGenerator clockDividerPulse;
dsp::PulseGenerator retriggerPulses[16];
@@ -70,8 +70,10 @@ struct MIDI_CV : Module {
MIDI_CV() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
heldNotes.reserve(128);
pitchFilter.lambda = 1 / 0.01f;
modFilter.lambda = 1 / 0.01f;
for (int c = 0; c < 16; c++) {
pitchFilters[c].lambda = 1 / 0.01f;
modFilters[c].lambda = 1 / 0.01f;
}
onReset();
}

@@ -89,17 +91,15 @@ struct MIDI_CV : Module {
for (int c = 0; c < 16; c++) {
notes[c] = 60;
gates[c] = false;
}
for (int i = 0; i < 128; i++) {
velocities[i] = 0;
aftertouches[i] = 0;
velocities[c] = 0;
aftertouches[c] = 0;
pitches[c] = 8192;
mods[c] = 0;
pitchFilters[c].reset();
modFilters[c].reset();
}
pedal = false;
rotateIndex = -1;
pitch = 8192;
mod = 0;
pitchFilter.reset();
modFilter.reset();
heldNotes.clear();
}

@@ -116,17 +116,27 @@ struct MIDI_CV : Module {
outputs[AFTERTOUCH_OUTPUT].setChannels(channels);
outputs[RETRIGGER_OUTPUT].setChannels(channels);
for (int c = 0; c < channels; c++) {
uint8_t note = notes[c];
outputs[CV_OUTPUT].setVoltage((note - 60.f) / 12.f, c);
outputs[CV_OUTPUT].setVoltage((notes[c] - 60.f) / 12.f, c);
outputs[GATE_OUTPUT].setVoltage(gates[c] ? 10.f : 0.f, c);
outputs[VELOCITY_OUTPUT].setVoltage(rescale(velocities[note], 0, 127, 0.f, 10.f), c);
outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(aftertouches[note], 0, 127, 0.f, 10.f), c);
outputs[VELOCITY_OUTPUT].setVoltage(rescale(velocities[c], 0, 127, 0.f, 10.f), c);
outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(aftertouches[c], 0, 127, 0.f, 10.f), c);
outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(deltaTime) ? 10.f : 0.f, c);
}

uint16_t pitchAdjusted = (pitch == 16383) ? 16384 : pitch;
outputs[PITCH_OUTPUT].setVoltage(pitchFilter.process(deltaTime, rescale(pitchAdjusted, 0, 1<<14, -5.f, 5.f)));
outputs[MOD_OUTPUT].setVoltage(modFilter.process(deltaTime, rescale(mod, 0, 127, 0.f, 10.f)));
if (polyMode == MPE_MODE) {
for (int c = 0; c < channels; c++) {
outputs[PITCH_OUTPUT].setChannels(channels);
outputs[MOD_OUTPUT].setChannels(channels);
outputs[PITCH_OUTPUT].setVoltage(pitchFilters[c].process(deltaTime, rescale(pitches[c], 0, 1<<14, -5.f, 5.f)), c);
outputs[MOD_OUTPUT].setVoltage(modFilters[c].process(deltaTime, rescale(mods[c], 0, 127, 0.f, 10.f)), c);
}
}
else {
outputs[PITCH_OUTPUT].setChannels(1);
outputs[MOD_OUTPUT].setChannels(1);
outputs[PITCH_OUTPUT].setVoltage(pitchFilters[0].process(deltaTime, rescale(pitches[0], 0, 1<<14, -5.f, 5.f)));
outputs[MOD_OUTPUT].setVoltage(modFilters[0].process(deltaTime, rescale(mods[0], 0, 127, 0.f, 10.f)));
}

outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(deltaTime) ? 10.f : 0.f);
outputs[CLOCK_DIV_OUTPUT].setVoltage(clockDividerPulse.process(deltaTime) ? 10.f : 0.f);
@@ -146,25 +156,45 @@ struct MIDI_CV : Module {
// note on
case 0x9: {
if (msg.getValue() > 0) {
velocities[msg.getNote()] = msg.getValue();
pressNote(msg.getNote(), msg.getChannel());
int c = (polyMode == MPE_MODE) ? msg.getChannel() : assignChannel(msg.getNote());
velocities[c] = msg.getValue();
pressNote(msg.getNote(), c);
}
else {
// For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released.
releaseNote(msg.getNote());
}
} break;
// channel aftertouch
// key pressure
case 0xa: {
aftertouches[msg.getNote()] = msg.getValue();
// Set the aftertouches with the same note
// TODO Should we handle the MPE case differently?
for (int c = 0; c < 16; c++) {
if (notes[c] == msg.getNote())
aftertouches[c] = msg.getValue();
}
} break;
// cc
case 0xb: {
processCC(msg);
} break;
// channel pressure
case 0xd: {
if (polyMode == MPE_MODE) {
// Set the channel aftertouch
aftertouches[msg.getChannel()] = msg.getValue();
}
else {
// Set all aftertouches
for (int c = 0; c < 16; c++) {
aftertouches[c] = msg.getValue();
}
}
} break;
// pitch wheel
case 0xe: {
pitch = ((uint16_t) msg.getValue() << 7) | msg.getNote();
int c = (polyMode == MPE_MODE) ? msg.getChannel() : 0;
pitches[c] = ((uint16_t) msg.getValue() << 7) | msg.getNote();
} break;
case 0xf: {
processSystem(msg);
@@ -177,7 +207,8 @@ struct MIDI_CV : Module {
switch (msg.getNote()) {
// mod
case 0x01: {
mod = msg.getValue();
int c = (polyMode == MPE_MODE) ? msg.getChannel() : 0;
mods[c] = msg.getValue();
} break;
// sustain
case 0x40: {
@@ -255,6 +286,11 @@ struct MIDI_CV : Module {
return channels - 1;
} break;

case MPE_MODE: {
// This case is handled by querying the MIDI message channel.
return 0;
} break;

default: return 0;
}
}
@@ -267,8 +303,6 @@ struct MIDI_CV : Module {
// Push note
heldNotes.push_back(note);
// Set note
if (polyMode != MPE_MODE)
channel = assignChannel(note);
notes[channel] = note;
gates[channel] = true;
retriggerPulses[channel].trigger(1e-3);
@@ -309,7 +343,7 @@ struct MIDI_CV : Module {
for (int c = 0; c < 16; c++) {
gates[c] = false;
}
// Add only the gates from heldNotes
// Add back only the gates from heldNotes
for (uint8_t note : heldNotes) {
// Find note's channels
for (int c = 0; c < channels; c++) {
@@ -346,8 +380,11 @@ struct MIDI_CV : Module {
json_object_set_new(rootJ, "channels", json_integer(channels));
json_object_set_new(rootJ, "polyMode", json_integer(polyMode));
json_object_set_new(rootJ, "clockDivision", json_integer(clockDivision));
json_object_set_new(rootJ, "lastPitch", json_integer(pitch));
json_object_set_new(rootJ, "lastMod", json_integer(mod));
// Saving/restoring pitch and mod doesn't make much sense for MPE.
if (polyMode != MPE_MODE) {
json_object_set_new(rootJ, "lastPitch", json_integer(pitches[0]));
json_object_set_new(rootJ, "lastMod", json_integer(mods[0]));
}
json_object_set_new(rootJ, "midi", midiInput.toJson());
return rootJ;
}
@@ -367,11 +404,11 @@ struct MIDI_CV : Module {

json_t *lastPitchJ = json_object_get(rootJ, "lastPitch");
if (lastPitchJ)
pitch = json_integer_value(lastPitchJ);
pitches[0] = json_integer_value(lastPitchJ);

json_t *lastModJ = json_object_get(rootJ, "lastMod");
if (lastModJ)
mod = json_integer_value(lastModJ);
mods[0] = json_integer_value(lastModJ);

json_t *midiJ = json_object_get(rootJ, "midi");
if (midiJ)


Loading…
Cancel
Save