|
|
@@ -20,6 +20,16 @@ |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
/** |
|
|
|
* This class contains a substantial amount of code from VCVRack's dsp/midi.hpp |
|
|
|
* Copyright (C) 2016-2021 VCV. |
|
|
|
* |
|
|
|
* This program is free software: you can redistribute it and/or |
|
|
|
* modify it under the terms of the GNU General Public License as |
|
|
|
* published by the Free Software Foundation; either version 3 of |
|
|
|
* the License, or (at your option) any later version. |
|
|
|
*/ |
|
|
|
|
|
|
|
struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { |
|
|
|
enum ParamIds { |
|
|
|
NUM_PARAMS |
|
|
@@ -27,10 +37,10 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { |
|
|
|
enum InputIds { |
|
|
|
PITCH_INPUT, |
|
|
|
GATE_INPUT, |
|
|
|
VEL_INPUT, |
|
|
|
AFT_INPUT, |
|
|
|
PW_INPUT, |
|
|
|
MW_INPUT, |
|
|
|
VELOCITY_INPUT, |
|
|
|
AFTERTOUCH_INPUT, |
|
|
|
PITCHBEND_INPUT, |
|
|
|
MODWHEEL_INPUT, |
|
|
|
NUM_INPUTS |
|
|
|
}; |
|
|
|
enum OutputIds { |
|
|
@@ -45,112 +55,170 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { |
|
|
|
int8_t notes[CHANNELS]; |
|
|
|
bool gates[CHANNELS]; |
|
|
|
int8_t keyPressures[CHANNELS]; |
|
|
|
int8_t mw; |
|
|
|
int16_t pw; |
|
|
|
int8_t modwheel; |
|
|
|
int16_t pitchbend; |
|
|
|
|
|
|
|
uint8_t channel = 0; |
|
|
|
Module* lastConnectedModule = nullptr; |
|
|
|
|
|
|
|
CardinalExpanderForInputMIDI() { |
|
|
|
CardinalExpanderForInputMIDI() |
|
|
|
{ |
|
|
|
static_assert(NUM_INPUTS == kNumInputs, "Invalid input configuration"); |
|
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
|
configInput(PITCH_INPUT, "1V/octave pitch"); |
|
|
|
configInput(GATE_INPUT, "Gate"); |
|
|
|
configInput(VEL_INPUT, "Velocity"); |
|
|
|
configInput(AFT_INPUT, "Aftertouch"); |
|
|
|
configInput(PW_INPUT, "Pitchbend"); |
|
|
|
configInput(MW_INPUT, "Mod wheel"); |
|
|
|
configInput(VELOCITY_INPUT, "Velocity"); |
|
|
|
configInput(AFTERTOUCH_INPUT, "Aftertouch"); |
|
|
|
configInput(PITCHBEND_INPUT, "Pitchbend"); |
|
|
|
configInput(MODWHEEL_INPUT, "Mod wheel"); |
|
|
|
onReset(); |
|
|
|
} |
|
|
|
|
|
|
|
/** Must be called before setNoteGate(). */ |
|
|
|
void setVelocity(int8_t vel, int c) { |
|
|
|
void setVelocity(int8_t vel, int c) |
|
|
|
{ |
|
|
|
vels[c] = vel; |
|
|
|
} |
|
|
|
|
|
|
|
void setNoteGate(int8_t note, bool gate, int c) { |
|
|
|
if (midiEventCount == MAX_MIDI_EVENTS) |
|
|
|
void setNoteGate(int8_t note, bool gate, int c) |
|
|
|
{ |
|
|
|
if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX) |
|
|
|
return; |
|
|
|
bool changedNote = gate && gates[c] && (note != notes[c]); |
|
|
|
bool enabledGate = gate && !gates[c]; |
|
|
|
bool disabledGate = !gate && gates[c]; |
|
|
|
if (changedNote || disabledGate) { |
|
|
|
|
|
|
|
const bool changedNote = gate && gates[c] && (note != notes[c]); |
|
|
|
const bool enabledGate = gate && !gates[c]; |
|
|
|
const bool disabledGate = !gate && gates[c]; |
|
|
|
|
|
|
|
if (changedNote || disabledGate) |
|
|
|
{ |
|
|
|
// Note off |
|
|
|
NativeMidiEvent& m(midiEvents[midiEventCount++]); |
|
|
|
m.time = frame; |
|
|
|
m.port = 0; |
|
|
|
m.size = 3; |
|
|
|
m.data[0] = 0x80; |
|
|
|
m.data[0] = 0x80 | channel; |
|
|
|
m.data[1] = notes[c]; |
|
|
|
m.data[2] = vels[c]; |
|
|
|
} |
|
|
|
if (changedNote || enabledGate) { |
|
|
|
|
|
|
|
if (changedNote || enabledGate) |
|
|
|
{ |
|
|
|
// Note on |
|
|
|
NativeMidiEvent& m(midiEvents[midiEventCount++]); |
|
|
|
m.time = frame; |
|
|
|
m.port = 0; |
|
|
|
m.size = 3; |
|
|
|
m.data[0] = 0x90; |
|
|
|
m.data[0] = 0x90 | channel; |
|
|
|
m.data[1] = note; |
|
|
|
m.data[2] = vels[c]; |
|
|
|
} |
|
|
|
|
|
|
|
notes[c] = note; |
|
|
|
gates[c] = gate; |
|
|
|
} |
|
|
|
|
|
|
|
void setKeyPressure(int8_t val, int c) { |
|
|
|
if (keyPressures[c] == val || midiEventCount == MAX_MIDI_EVENTS) |
|
|
|
void setKeyPressure(int8_t val, int c) |
|
|
|
{ |
|
|
|
if (keyPressures[c] == val) |
|
|
|
return; |
|
|
|
|
|
|
|
keyPressures[c] = val; |
|
|
|
|
|
|
|
if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX) |
|
|
|
return; |
|
|
|
|
|
|
|
// Polyphonic key pressure |
|
|
|
NativeMidiEvent& m(midiEvents[midiEventCount++]); |
|
|
|
m.time = frame; |
|
|
|
m.port = 0; |
|
|
|
m.size = 3; |
|
|
|
m.data[0] = 0xa0; |
|
|
|
m.data[0] = 0xa0 | channel; |
|
|
|
m.data[1] = notes[c]; |
|
|
|
m.data[2] = val; |
|
|
|
} |
|
|
|
|
|
|
|
void setModWheel(int8_t mw) { |
|
|
|
if (this->mw == mw || midiEventCount == MAX_MIDI_EVENTS) |
|
|
|
void setModWheel(int8_t modwheel) |
|
|
|
{ |
|
|
|
if (this->modwheel == modwheel) |
|
|
|
return; |
|
|
|
this->mw = mw; |
|
|
|
|
|
|
|
this->modwheel = modwheel; |
|
|
|
|
|
|
|
if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX) |
|
|
|
return; |
|
|
|
|
|
|
|
// Modulation Wheel (CC1) |
|
|
|
NativeMidiEvent& m(midiEvents[midiEventCount++]); |
|
|
|
m.time = frame; |
|
|
|
m.port = 0; |
|
|
|
m.size = 3; |
|
|
|
m.data[0] = 0xb0; |
|
|
|
m.data[0] = 0xb0 | channel; |
|
|
|
m.data[1] = 1; |
|
|
|
m.data[2] = mw; |
|
|
|
m.data[2] = modwheel; |
|
|
|
} |
|
|
|
|
|
|
|
void setPitchWheel(int16_t pw) { |
|
|
|
if (this->pw == pw || midiEventCount == MAX_MIDI_EVENTS) |
|
|
|
void setPitchbend(int16_t pitchbend) |
|
|
|
{ |
|
|
|
if (this->pitchbend == pitchbend) |
|
|
|
return; |
|
|
|
|
|
|
|
this->pitchbend = pitchbend; |
|
|
|
|
|
|
|
if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX) |
|
|
|
return; |
|
|
|
this->pw = pw; |
|
|
|
|
|
|
|
// Pitch Wheel |
|
|
|
NativeMidiEvent& m(midiEvents[midiEventCount++]); |
|
|
|
m.time = frame; |
|
|
|
m.port = 0; |
|
|
|
m.size = 3; |
|
|
|
m.data[0] = 0xe0; |
|
|
|
m.data[1] = pw & 0x7f; |
|
|
|
m.data[2] = (pw >> 7) & 0x7f; |
|
|
|
m.data[0] = 0xe0 | channel; |
|
|
|
m.data[1] = pitchbend & 0x7f; |
|
|
|
m.data[2] = (pitchbend >> 7) & 0x7f; |
|
|
|
} |
|
|
|
|
|
|
|
void onReset() override |
|
|
|
void panic() |
|
|
|
{ |
|
|
|
for (uint c = 0; c < CHANNELS; c++) { |
|
|
|
if (frame != UINT_MAX) |
|
|
|
{ |
|
|
|
// Send all note off commands |
|
|
|
for (int note = 0; note <= 127; ++note) |
|
|
|
{ |
|
|
|
if (midiEventCount == MAX_MIDI_EVENTS) |
|
|
|
break; |
|
|
|
// Note off |
|
|
|
NativeMidiEvent& m(midiEvents[midiEventCount++]); |
|
|
|
m.time = frame; |
|
|
|
m.port = 0; |
|
|
|
m.size = 3; |
|
|
|
m.data[0] = 0x80 | channel; |
|
|
|
m.data[1] = note; |
|
|
|
m.data[2] = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
reset(); |
|
|
|
} |
|
|
|
|
|
|
|
void reset() |
|
|
|
{ |
|
|
|
for (uint c = 0; c < CHANNELS; ++c) |
|
|
|
{ |
|
|
|
vels[c] = 100; |
|
|
|
notes[c] = 60; |
|
|
|
gates[c] = false; |
|
|
|
keyPressures[c] = -1; |
|
|
|
} |
|
|
|
mw = -1; |
|
|
|
pw = 0x2000; |
|
|
|
modwheel = -1; |
|
|
|
pitchbend = 0x2000; |
|
|
|
midiEventCount = 0; |
|
|
|
frame = UINT_MAX; |
|
|
|
} |
|
|
|
|
|
|
|
void onReset() override |
|
|
|
{ |
|
|
|
reset(); |
|
|
|
channel = 0; |
|
|
|
lastConnectedModule = nullptr; |
|
|
|
} |
|
|
|
|
|
|
@@ -178,7 +246,7 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { |
|
|
|
return; |
|
|
|
|
|
|
|
for (int c = 0; c < inputs[PITCH_INPUT].getChannels(); c++) { |
|
|
|
int vel = (int) std::round(inputs[VEL_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127); |
|
|
|
int vel = (int) std::round(inputs[VELOCITY_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127); |
|
|
|
vel = clamp(vel, 0, 127); |
|
|
|
setVelocity(vel, c); |
|
|
|
|
|
|
@@ -187,21 +255,36 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { |
|
|
|
bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f; |
|
|
|
setNoteGate(note, gate, c); |
|
|
|
|
|
|
|
int aft = (int) std::round(inputs[AFT_INPUT].getPolyVoltage(c) / 10.f * 127); |
|
|
|
int aft = (int) std::round(inputs[AFTERTOUCH_INPUT].getPolyVoltage(c) / 10.f * 127); |
|
|
|
aft = clamp(aft, 0, 127); |
|
|
|
setKeyPressure(aft, c); |
|
|
|
} |
|
|
|
|
|
|
|
int pw = (int) std::round((inputs[PW_INPUT].getVoltage() + 5.f) / 10.f * 0x4000); |
|
|
|
pw = clamp(pw, 0, 0x3fff); |
|
|
|
setPitchWheel(pw); |
|
|
|
int pitchbend = (int) std::round((inputs[PITCHBEND_INPUT].getVoltage() + 5.f) / 10.f * 16383); |
|
|
|
pitchbend = clamp(pitchbend, 0, 16383); |
|
|
|
setPitchbend(pitchbend); |
|
|
|
|
|
|
|
int mw = (int) std::round(inputs[MW_INPUT].getVoltage() / 10.f * 127); |
|
|
|
mw = clamp(mw, 0, 127); |
|
|
|
setModWheel(mw); |
|
|
|
int modwheel = (int) std::round(inputs[MODWHEEL_INPUT].getVoltage() / 10.f * 127); |
|
|
|
modwheel = clamp(modwheel, 0, 127); |
|
|
|
setModWheel(modwheel); |
|
|
|
|
|
|
|
++frame; |
|
|
|
} |
|
|
|
|
|
|
|
json_t* dataToJson() override |
|
|
|
{ |
|
|
|
json_t* const rootJ = json_object(); |
|
|
|
DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, nullptr); |
|
|
|
|
|
|
|
json_object_set_new(rootJ, "channel", json_integer(channel)); |
|
|
|
return rootJ; |
|
|
|
} |
|
|
|
|
|
|
|
void dataFromJson(json_t* const rootJ) override |
|
|
|
{ |
|
|
|
if (json_t* const channelJ = json_object_get(rootJ, "channel")) |
|
|
|
channel = json_integer_value(channelJ) & 0x0F; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -211,16 +294,19 @@ struct CardinalExpanderForInputMIDIWidget : ModuleWidgetWith3HP { |
|
|
|
static constexpr const float startY = 90.0f; |
|
|
|
static constexpr const float padding = 49.0f; |
|
|
|
|
|
|
|
CardinalExpanderForInputMIDIWidget(CardinalExpanderForInputMIDI* const module) |
|
|
|
CardinalExpanderForInputMIDI* const module; |
|
|
|
|
|
|
|
CardinalExpanderForInputMIDIWidget(CardinalExpanderForInputMIDI* const m) |
|
|
|
: module(m) |
|
|
|
{ |
|
|
|
setModule(module); |
|
|
|
setModule(m); |
|
|
|
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ExpanderMIDI.svg"))); |
|
|
|
|
|
|
|
addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, 0))); |
|
|
|
addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); |
|
|
|
|
|
|
|
for (int i=0; i<CardinalExpanderForInputMIDI::NUM_INPUTS; ++i) |
|
|
|
addInput(createInput<PJ301MPort>(Vec(startX + 4.0f, startY + padding * i), module, i)); |
|
|
|
addInput(createInput<PJ301MPort>(Vec(startX + 4.0f, startY + padding * i), m, i)); |
|
|
|
} |
|
|
|
|
|
|
|
void draw(const DrawArgs& args) override |
|
|
@@ -261,7 +347,35 @@ struct CardinalExpanderForInputMIDIWidget : ModuleWidgetWith3HP { |
|
|
|
nvgText(args.vg, box.size.x * 0.666f, startY + padding * 4 - 4.0f, "Pb", nullptr); |
|
|
|
nvgText(args.vg, box.size.x * 0.666f, startY + padding * 5 - 4.0f, "MW", nullptr); |
|
|
|
|
|
|
|
ModuleWidgetWithSideScrews::draw(args); |
|
|
|
ModuleWidgetWith3HP::draw(args); |
|
|
|
} |
|
|
|
|
|
|
|
void appendContextMenu(Menu* const menu) override |
|
|
|
{ |
|
|
|
menu->addChild(new MenuSeparator); |
|
|
|
|
|
|
|
struct ChannelItem : MenuItem { |
|
|
|
CardinalExpanderForInputMIDI* module; |
|
|
|
Menu* createChildMenu() override { |
|
|
|
Menu* menu = new Menu; |
|
|
|
for (uint8_t c = 0; c < 16; c++) { |
|
|
|
menu->addChild(createCheckMenuItem(string::f("%d", c+1), "", |
|
|
|
[=]() {return module->channel == c;}, |
|
|
|
[=]() {module->channel = c;} |
|
|
|
)); |
|
|
|
} |
|
|
|
return menu; |
|
|
|
} |
|
|
|
}; |
|
|
|
ChannelItem* const channelItem = new ChannelItem; |
|
|
|
channelItem->text = "MIDI channel"; |
|
|
|
channelItem->rightText = string::f("%d", module->channel+1) + " " + RIGHT_ARROW; |
|
|
|
channelItem->module = module; |
|
|
|
menu->addChild(channelItem); |
|
|
|
|
|
|
|
menu->addChild(createMenuItem("Panic", "", |
|
|
|
[=]() { module->panic(); } |
|
|
|
)); |
|
|
|
} |
|
|
|
}; |
|
|
|
|