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