Browse Source

MIDI to CV: Add "Use release velocity" setting. Refactor MidiParser.

tags/v2.6.5
Andrew Belt 5 months ago
parent
commit
f90af88905
2 changed files with 109 additions and 79 deletions
  1. +106
    -78
      include/dsp/midi.hpp
  2. +3
    -1
      src/core/MIDI_CV.cpp

+ 106
- 78
include/dsp/midi.hpp View File

@@ -243,16 +243,7 @@ template <uint8_t MAX_CHANNELS>
struct MidiParser { struct MidiParser {
// Settings // Settings


/** Number of semitones to bend up/down by pitch wheel */
float pwRange;

/** Enables pitch-wheel and mod-wheel exponential smoothing */
bool smooth;

/** Number of 24 PPQN clocks between clock divider pulses */
uint32_t clockDivision;

/** Actual number of polyphonic channels */
/** Actual number of output polyphonic channels */
uint8_t channels; uint8_t channels;


enum MonoMode { enum MonoMode {
@@ -264,6 +255,7 @@ struct MidiParser {
}; };
MonoMode monoMode; MonoMode monoMode;


/** In monophonic mode, generate Retrigger pulse when the active note is released and another takes over. */
bool retriggerOnResume; bool retriggerOnResume;


/** Method for assigning notes to polyphony channels */ /** Method for assigning notes to polyphony channels */
@@ -276,6 +268,18 @@ struct MidiParser {
}; };
PolyMode polyMode; PolyMode polyMode;


/** Set Velocity output from Note Off velocity */
bool releaseVelocityEnabled;

/** Number of semitones to bend up/down by pitch wheel */
float pwRange;

/** Enables pitch-wheel and mod-wheel exponential smoothing */
bool smooth;

/** Number of 24 PPQN clocks between clock divider pulses */
uint32_t clockDivision;

// States // States


/** Clock index from song start */ /** Clock index from song start */
@@ -382,7 +386,7 @@ struct MidiParser {
switch (msg.getStatus()) { switch (msg.getStatus()) {
// note off // note off
case 0x8: { case 0x8: {
releaseNote(msg.getNote());
releaseNote(msg.getNote(), msg.getChannel(), msg.getValue());
} break; } break;
// note on // note on
case 0x9: { case 0x9: {
@@ -392,7 +396,7 @@ struct MidiParser {
} }
else { else {
// Note-on event with velocity 0 is an alternative for note-off event. // Note-on event with velocity 0 is an alternative for note-off event.
releaseNote(msg.getNote());
releaseNote(msg.getNote(), msg.getChannel(), -1);
} }
} break; } break;
// key pressure // key pressure
@@ -410,7 +414,7 @@ struct MidiParser {
} break; } break;
// channel pressure // channel pressure
case 0xd: { case 0xd: {
if (polyMode == MPE_MODE) {
if (channels > 1 && polyMode == MPE_MODE) {
// Set the channel aftertouch // Set the channel aftertouch
aftertouches[msg.getChannel()] = msg.getNote(); aftertouches[msg.getChannel()] = msg.getNote();
} }
@@ -423,7 +427,7 @@ struct MidiParser {
} break; } break;
// pitch wheel // pitch wheel
case 0xe: { case 0xe: {
uint8_t c = (polyMode == MPE_MODE) ? msg.getChannel() : 0;
uint8_t c = (channels > 1 && polyMode == MPE_MODE) ? msg.getChannel() : 0;
int16_t pw = msg.getValue(); int16_t pw = msg.getValue();
pw <<= 7; pw <<= 7;
pw |= msg.getNote(); pw |= msg.getNote();
@@ -441,7 +445,7 @@ struct MidiParser {
switch (msg.getNote()) { switch (msg.getNote()) {
// mod // mod
case 0x01: { case 0x01: {
uint8_t c = (polyMode == MPE_MODE) ? msg.getChannel() : 0;
uint8_t c = (channels > 1 && polyMode == MPE_MODE) ? msg.getChannel() : 0;
mods[c] = msg.getValue(); mods[c] = msg.getValue();
} break; } break;
// sustain // sustain
@@ -550,6 +554,9 @@ struct MidiParser {
// Handle monophonic modes // Handle monophonic modes
else { else {
channel = 0; channel = 0;
if (monoMode == LAST_PRIORITY_MODE) {
// Always play note
}
if (monoMode == FIRST_PRIORITY_MODE) { if (monoMode == FIRST_PRIORITY_MODE) {
if (heldNotes.size() > 1) if (heldNotes.size() > 1)
return; return;
@@ -572,80 +579,94 @@ struct MidiParser {
retriggerPulses[channel].trigger(1e-3); retriggerPulses[channel].trigger(1e-3);
} }


void releaseNote(uint8_t note) {
/** -1 velocity means unset. */
void releaseNote(uint8_t note, int8_t channel, int8_t velocity) {
// Remove the note // Remove the note
heldNotes.erase(std::remove(heldNotes.begin(), heldNotes.end(), note), heldNotes.end()); heldNotes.erase(std::remove(heldNotes.begin(), heldNotes.end(), note), heldNotes.end());
// Hold note if pedal is pressed // Hold note if pedal is pressed
if (pedal) if (pedal)
return; return;
// Turn off gate of all channels with note
for (uint8_t c = 0; c < channels; c++) {
if (notes[c] == note) {
gates[c] = false;
}
// Find output channel of released note, if any
if (channels > 1 && polyMode == MPE_MODE) {
// Each MPE channel must be monophonic so the output channel must be the released note channel
} }
// In all monophonic modes, set a previous note if the released note was the active note
if (channels == 1 && note == notes[0] && !heldNotes.empty()) {
if (monoMode == LAST_PRIORITY_MODE) {
notes[0] = heldNotes.back();
}
if (monoMode == FIRST_PRIORITY_MODE) {
notes[0] = heldNotes.front();
}
if (monoMode == LOWEST_PRIORITY_MODE) {
notes[0] = *std::min_element(heldNotes.begin(), heldNotes.end());
}
if (monoMode == HIGHEST_PRIORITY_MODE) {
notes[0] = *std::max_element(heldNotes.begin(), heldNotes.end());
}
gates[0] = true;
// TODO Set velocity
if (retriggerOnResume) {
retriggerPulses[0].trigger(1e-3);
else {
// Find channel of active note
channel = -1;
for (uint8_t c = 0; c < channels; c++) {
if (gates[c] && notes[c] == note) {
channel = c;
break;
}
} }
} }
}

void pressPedal() {
if (pedal)
if (channel < 0) {
// Released note is not active on any channel
return; return;
pedal = true;
}
// Deactivate note
gates[channel] = false;
refreshHeld();
// Set velocity
if (releaseVelocityEnabled && velocity >= 0) {
velocities[channel] = velocity;
}
} }


void releasePedal() {
if (!pedal)
return;
pedal = false;
// Set last note if monophonic
if (channels == 1) {
/** Deactivates all notes that are not held, and reactivates notes that are. */
void refreshHeld() {
// Monophonic
if (channels <= 1) {
// Reactivate note if at least one is held
if (!heldNotes.empty()) { if (!heldNotes.empty()) {
// Replace note with last held note
uint8_t lastNote = heldNotes.back();
notes[0] = lastNote;
if (monoMode == LAST_PRIORITY_MODE) {
notes[0] = heldNotes.back();
}
if (monoMode == FIRST_PRIORITY_MODE) {
notes[0] = heldNotes.front();
}
if (monoMode == LOWEST_PRIORITY_MODE) {
notes[0] = *std::min_element(heldNotes.begin(), heldNotes.end());
}
if (monoMode == HIGHEST_PRIORITY_MODE) {
notes[0] = *std::max_element(heldNotes.begin(), heldNotes.end());
}
gates[0] = true;
if (retriggerOnResume) {
retriggerPulses[0].trigger(1e-3);
}
} }
else { else {
// Disable gate
gates[0] = false; gates[0] = false;
} }
} }
// Clear notes that are not held if polyphonic
// Polyphonic
else { else {
// Deactivate notes that are not held
for (uint8_t c = 0; c < channels; c++) { for (uint8_t c = 0; c < channels; c++) {
if (!gates[c]) if (!gates[c])
continue; continue;
// Disable all gates
gates[c] = false;
// Re-enable gate if channel's note is still held
for (uint8_t note : heldNotes) {
if (notes[c] == note) {
gates[c] = true;
break;
}
}
// Check if note is still held
bool held = std::find(heldNotes.begin(), heldNotes.end(), notes[c]) != heldNotes.end();
if (!held)
gates[c] = false;
} }
} }
} }


void pressPedal() {
if (pedal)
return;
pedal = true;
}

void releasePedal() {
if (!pedal)
return;
pedal = false;
refreshHeld();
}

uint8_t getChannels() { uint8_t getChannels() {
return channels; return channels;
} }
@@ -672,7 +693,7 @@ struct MidiParser {
} }


float getPitchVoltage(uint8_t channel) { float getPitchVoltage(uint8_t channel) {
uint8_t wheelChannel = (polyMode == MPE_MODE) ? channel : 0;
uint8_t wheelChannel = (channels > 1 && polyMode == MPE_MODE) ? channel : 0;
return (notes[channel] - 60.f + pwFilters[wheelChannel].out * pwRange) / 12.f; return (notes[channel] - 60.f + pwFilters[wheelChannel].out * pwRange) / 12.f;
} }


@@ -696,20 +717,23 @@ struct MidiParser {


/** Returns number of polyphonic channels for pitch and mod wheels. */ /** Returns number of polyphonic channels for pitch and mod wheels. */
uint8_t getWheelChannels() { uint8_t getWheelChannels() {
return (polyMode == MPE_MODE) ? MAX_CHANNELS : 1;
return (channels > 1 && polyMode == MPE_MODE) ? MAX_CHANNELS : 1;
} }


json_t* toJson() { json_t* toJson() {
json_t* rootJ = json_object(); json_t* rootJ = json_object();
json_object_set_new(rootJ, "pwRange", json_real(pwRange));
json_object_set_new(rootJ, "smooth", json_boolean(smooth));
json_object_set_new(rootJ, "channels", json_integer(channels)); json_object_set_new(rootJ, "channels", json_integer(channels));
json_object_set_new(rootJ, "monoMode", json_integer(monoMode)); json_object_set_new(rootJ, "monoMode", json_integer(monoMode));
json_object_set_new(rootJ, "retriggerOnResume", json_boolean(retriggerOnResume)); json_object_set_new(rootJ, "retriggerOnResume", json_boolean(retriggerOnResume));
json_object_set_new(rootJ, "polyMode", json_integer(polyMode)); json_object_set_new(rootJ, "polyMode", json_integer(polyMode));
json_object_set_new(rootJ, "releaseVelocityEnabled", json_boolean(releaseVelocityEnabled));
json_object_set_new(rootJ, "pwRange", json_real(pwRange));
json_object_set_new(rootJ, "smooth", json_boolean(smooth));
json_object_set_new(rootJ, "clockDivision", json_integer(clockDivision)); json_object_set_new(rootJ, "clockDivision", json_integer(clockDivision));
// Saving/restoring pitch and mod doesn't make much sense for MPE.
if (polyMode != MPE_MODE) {
if (channels > 1 && polyMode == MPE_MODE) {
// Saving/restoring pitch and mod doesn't make much sense for MPE.
}
else {
json_object_set_new(rootJ, "lastPw", json_integer(pws[0])); json_object_set_new(rootJ, "lastPw", json_integer(pws[0]));
json_object_set_new(rootJ, "lastMod", json_integer(mods[0])); json_object_set_new(rootJ, "lastMod", json_integer(mods[0]));
} }
@@ -719,14 +743,6 @@ struct MidiParser {
} }


void fromJson(json_t* rootJ) { void fromJson(json_t* rootJ) {
json_t* pwRangeJ = json_object_get(rootJ, "pwRange");
if (pwRangeJ)
pwRange = json_number_value(pwRangeJ);

json_t* smoothJ = json_object_get(rootJ, "smooth");
if (smoothJ)
smooth = json_boolean_value(smoothJ);

json_t* channelsJ = json_object_get(rootJ, "channels"); json_t* channelsJ = json_object_get(rootJ, "channels");
if (channelsJ) if (channelsJ)
setChannels(json_integer_value(channelsJ)); setChannels(json_integer_value(channelsJ));
@@ -743,6 +759,18 @@ struct MidiParser {
if (polyModeJ) if (polyModeJ)
polyMode = (PolyMode) json_integer_value(polyModeJ); polyMode = (PolyMode) json_integer_value(polyModeJ);


json_t* releaseVelocityEnabledJ = json_object_get(rootJ, "releaseVelocityEnabled");
if (releaseVelocityEnabledJ)
releaseVelocityEnabled = json_boolean_value(releaseVelocityEnabledJ);

json_t* pwRangeJ = json_object_get(rootJ, "pwRange");
if (pwRangeJ)
pwRange = json_number_value(pwRangeJ);

json_t* smoothJ = json_object_get(rootJ, "smooth");
if (smoothJ)
smooth = json_boolean_value(smoothJ);

json_t* clockDivisionJ = json_object_get(rootJ, "clockDivision"); json_t* clockDivisionJ = json_object_get(rootJ, "clockDivision");
if (clockDivisionJ) if (clockDivisionJ)
clockDivision = json_integer_value(clockDivisionJ); clockDivision = json_integer_value(clockDivisionJ);


+ 3
- 1
src/core/MIDI_CV.cpp View File

@@ -177,7 +177,7 @@ struct MIDI_CVWidget : ModuleWidget {
module->midiParser.setMonoMode(decltype(module->midiParser)::MonoMode(monoMode)); module->midiParser.setMonoMode(decltype(module->midiParser)::MonoMode(monoMode));
})); }));


menu->addChild(createBoolPtrMenuItem("Release retrigger", "", &module->midiParser.retriggerOnResume));
menu->addChild(createBoolPtrMenuItem("Retrigger on release", "", &module->midiParser.retriggerOnResume));


menu->addChild(createIndexSubmenuItem("Polyphony mode", { menu->addChild(createIndexSubmenuItem("Polyphony mode", {
"Rotate", "Rotate",
@@ -192,6 +192,8 @@ struct MIDI_CVWidget : ModuleWidget {


menu->addChild(new MenuSeparator); menu->addChild(new MenuSeparator);


menu->addChild(createBoolPtrMenuItem("Use release velocity", "", &module->midiParser.releaseVelocityEnabled));

static const std::vector<float> pwRanges = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24, 36, 48}; static const std::vector<float> pwRanges = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24, 36, 48};
auto getPwRangeLabel = [](float pwRange) -> std::string { auto getPwRangeLabel = [](float pwRange) -> std::string {
if (pwRange == 0) if (pwRange == 0)


Loading…
Cancel
Save