Cleanup input midi expander, add channel option
This commit is contained in:
parent
10fe1db762
commit
d0224fce14
3 changed files with 167 additions and 53 deletions
|
|
@ -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 {
|
struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI {
|
||||||
enum ParamIds {
|
enum ParamIds {
|
||||||
NUM_PARAMS
|
NUM_PARAMS
|
||||||
|
|
@ -27,10 +37,10 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI {
|
||||||
enum InputIds {
|
enum InputIds {
|
||||||
PITCH_INPUT,
|
PITCH_INPUT,
|
||||||
GATE_INPUT,
|
GATE_INPUT,
|
||||||
VEL_INPUT,
|
VELOCITY_INPUT,
|
||||||
AFT_INPUT,
|
AFTERTOUCH_INPUT,
|
||||||
PW_INPUT,
|
PITCHBEND_INPUT,
|
||||||
MW_INPUT,
|
MODWHEEL_INPUT,
|
||||||
NUM_INPUTS
|
NUM_INPUTS
|
||||||
};
|
};
|
||||||
enum OutputIds {
|
enum OutputIds {
|
||||||
|
|
@ -45,112 +55,170 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI {
|
||||||
int8_t notes[CHANNELS];
|
int8_t notes[CHANNELS];
|
||||||
bool gates[CHANNELS];
|
bool gates[CHANNELS];
|
||||||
int8_t keyPressures[CHANNELS];
|
int8_t keyPressures[CHANNELS];
|
||||||
int8_t mw;
|
int8_t modwheel;
|
||||||
int16_t pw;
|
int16_t pitchbend;
|
||||||
|
|
||||||
|
uint8_t channel = 0;
|
||||||
Module* lastConnectedModule = nullptr;
|
Module* lastConnectedModule = nullptr;
|
||||||
|
|
||||||
CardinalExpanderForInputMIDI() {
|
CardinalExpanderForInputMIDI()
|
||||||
|
{
|
||||||
static_assert(NUM_INPUTS == kNumInputs, "Invalid input configuration");
|
static_assert(NUM_INPUTS == kNumInputs, "Invalid input configuration");
|
||||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
|
||||||
configInput(PITCH_INPUT, "1V/octave pitch");
|
configInput(PITCH_INPUT, "1V/octave pitch");
|
||||||
configInput(GATE_INPUT, "Gate");
|
configInput(GATE_INPUT, "Gate");
|
||||||
configInput(VEL_INPUT, "Velocity");
|
configInput(VELOCITY_INPUT, "Velocity");
|
||||||
configInput(AFT_INPUT, "Aftertouch");
|
configInput(AFTERTOUCH_INPUT, "Aftertouch");
|
||||||
configInput(PW_INPUT, "Pitchbend");
|
configInput(PITCHBEND_INPUT, "Pitchbend");
|
||||||
configInput(MW_INPUT, "Mod wheel");
|
configInput(MODWHEEL_INPUT, "Mod wheel");
|
||||||
onReset();
|
onReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Must be called before setNoteGate(). */
|
/** Must be called before setNoteGate(). */
|
||||||
void setVelocity(int8_t vel, int c) {
|
void setVelocity(int8_t vel, int c)
|
||||||
|
{
|
||||||
vels[c] = vel;
|
vels[c] = vel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setNoteGate(int8_t note, bool gate, int c) {
|
void setNoteGate(int8_t note, bool gate, int c)
|
||||||
if (midiEventCount == MAX_MIDI_EVENTS)
|
{
|
||||||
|
if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX)
|
||||||
return;
|
return;
|
||||||
bool changedNote = gate && gates[c] && (note != notes[c]);
|
|
||||||
bool enabledGate = gate && !gates[c];
|
const bool changedNote = gate && gates[c] && (note != notes[c]);
|
||||||
bool disabledGate = !gate && gates[c];
|
const bool enabledGate = gate && !gates[c];
|
||||||
if (changedNote || disabledGate) {
|
const bool disabledGate = !gate && gates[c];
|
||||||
|
|
||||||
|
if (changedNote || disabledGate)
|
||||||
|
{
|
||||||
// Note off
|
// Note off
|
||||||
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
||||||
m.time = frame;
|
m.time = frame;
|
||||||
m.port = 0;
|
m.port = 0;
|
||||||
m.size = 3;
|
m.size = 3;
|
||||||
m.data[0] = 0x80;
|
m.data[0] = 0x80 | channel;
|
||||||
m.data[1] = notes[c];
|
m.data[1] = notes[c];
|
||||||
m.data[2] = vels[c];
|
m.data[2] = vels[c];
|
||||||
}
|
}
|
||||||
if (changedNote || enabledGate) {
|
|
||||||
|
if (changedNote || enabledGate)
|
||||||
|
{
|
||||||
// Note on
|
// Note on
|
||||||
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
||||||
m.time = frame;
|
m.time = frame;
|
||||||
m.port = 0;
|
m.port = 0;
|
||||||
m.size = 3;
|
m.size = 3;
|
||||||
m.data[0] = 0x90;
|
m.data[0] = 0x90 | channel;
|
||||||
m.data[1] = note;
|
m.data[1] = note;
|
||||||
m.data[2] = vels[c];
|
m.data[2] = vels[c];
|
||||||
}
|
}
|
||||||
|
|
||||||
notes[c] = note;
|
notes[c] = note;
|
||||||
gates[c] = gate;
|
gates[c] = gate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeyPressure(int8_t val, int c) {
|
void setKeyPressure(int8_t val, int c)
|
||||||
if (keyPressures[c] == val || midiEventCount == MAX_MIDI_EVENTS)
|
{
|
||||||
|
if (keyPressures[c] == val)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
keyPressures[c] = val;
|
keyPressures[c] = val;
|
||||||
|
|
||||||
|
if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX)
|
||||||
|
return;
|
||||||
|
|
||||||
// Polyphonic key pressure
|
// Polyphonic key pressure
|
||||||
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
||||||
m.time = frame;
|
m.time = frame;
|
||||||
m.port = 0;
|
m.port = 0;
|
||||||
m.size = 3;
|
m.size = 3;
|
||||||
m.data[0] = 0xa0;
|
m.data[0] = 0xa0 | channel;
|
||||||
m.data[1] = notes[c];
|
m.data[1] = notes[c];
|
||||||
m.data[2] = val;
|
m.data[2] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setModWheel(int8_t mw) {
|
void setModWheel(int8_t modwheel)
|
||||||
if (this->mw == mw || midiEventCount == MAX_MIDI_EVENTS)
|
{
|
||||||
|
if (this->modwheel == modwheel)
|
||||||
return;
|
return;
|
||||||
this->mw = mw;
|
|
||||||
|
this->modwheel = modwheel;
|
||||||
|
|
||||||
|
if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX)
|
||||||
|
return;
|
||||||
|
|
||||||
// Modulation Wheel (CC1)
|
// Modulation Wheel (CC1)
|
||||||
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
||||||
m.time = frame;
|
m.time = frame;
|
||||||
m.port = 0;
|
m.port = 0;
|
||||||
m.size = 3;
|
m.size = 3;
|
||||||
m.data[0] = 0xb0;
|
m.data[0] = 0xb0 | channel;
|
||||||
m.data[1] = 1;
|
m.data[1] = 1;
|
||||||
m.data[2] = mw;
|
m.data[2] = modwheel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPitchWheel(int16_t pw) {
|
void setPitchbend(int16_t pitchbend)
|
||||||
if (this->pw == pw || midiEventCount == MAX_MIDI_EVENTS)
|
{
|
||||||
|
if (this->pitchbend == pitchbend)
|
||||||
return;
|
return;
|
||||||
this->pw = pw;
|
|
||||||
|
this->pitchbend = pitchbend;
|
||||||
|
|
||||||
|
if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX)
|
||||||
|
return;
|
||||||
|
|
||||||
// Pitch Wheel
|
// Pitch Wheel
|
||||||
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
NativeMidiEvent& m(midiEvents[midiEventCount++]);
|
||||||
m.time = frame;
|
m.time = frame;
|
||||||
m.port = 0;
|
m.port = 0;
|
||||||
m.size = 3;
|
m.size = 3;
|
||||||
m.data[0] = 0xe0;
|
m.data[0] = 0xe0 | channel;
|
||||||
m.data[1] = pw & 0x7f;
|
m.data[1] = pitchbend & 0x7f;
|
||||||
m.data[2] = (pw >> 7) & 0x7f;
|
m.data[2] = (pitchbend >> 7) & 0x7f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onReset() override
|
void panic()
|
||||||
|
{
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
for (uint c = 0; c < CHANNELS; c++) {
|
|
||||||
vels[c] = 100;
|
vels[c] = 100;
|
||||||
notes[c] = 60;
|
notes[c] = 60;
|
||||||
gates[c] = false;
|
gates[c] = false;
|
||||||
keyPressures[c] = -1;
|
keyPressures[c] = -1;
|
||||||
}
|
}
|
||||||
mw = -1;
|
modwheel = -1;
|
||||||
pw = 0x2000;
|
pitchbend = 0x2000;
|
||||||
midiEventCount = 0;
|
midiEventCount = 0;
|
||||||
frame = UINT_MAX;
|
frame = UINT_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onReset() override
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
channel = 0;
|
||||||
lastConnectedModule = nullptr;
|
lastConnectedModule = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,7 +246,7 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
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[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);
|
vel = clamp(vel, 0, 127);
|
||||||
setVelocity(vel, c);
|
setVelocity(vel, c);
|
||||||
|
|
||||||
|
|
@ -187,21 +255,36 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI {
|
||||||
bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f;
|
bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f;
|
||||||
setNoteGate(note, gate, c);
|
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);
|
aft = clamp(aft, 0, 127);
|
||||||
setKeyPressure(aft, c);
|
setKeyPressure(aft, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
int pw = (int) std::round((inputs[PW_INPUT].getVoltage() + 5.f) / 10.f * 0x4000);
|
int pitchbend = (int) std::round((inputs[PITCHBEND_INPUT].getVoltage() + 5.f) / 10.f * 16383);
|
||||||
pw = clamp(pw, 0, 0x3fff);
|
pitchbend = clamp(pitchbend, 0, 16383);
|
||||||
setPitchWheel(pw);
|
setPitchbend(pitchbend);
|
||||||
|
|
||||||
int mw = (int) std::round(inputs[MW_INPUT].getVoltage() / 10.f * 127);
|
int modwheel = (int) std::round(inputs[MODWHEEL_INPUT].getVoltage() / 10.f * 127);
|
||||||
mw = clamp(mw, 0, 127);
|
modwheel = clamp(modwheel, 0, 127);
|
||||||
setModWheel(mw);
|
setModWheel(modwheel);
|
||||||
|
|
||||||
++frame;
|
++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 startY = 90.0f;
|
||||||
static constexpr const float padding = 49.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")));
|
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, 0)));
|
||||||
addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
|
addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
|
||||||
|
|
||||||
for (int i=0; i<CardinalExpanderForInputMIDI::NUM_INPUTS; ++i)
|
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
|
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 * 4 - 4.0f, "Pb", nullptr);
|
||||||
nvgText(args.vg, box.size.x * 0.666f, startY + padding * 5 - 4.0f, "MW", 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(); }
|
||||||
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -635,7 +635,7 @@ struct HostMIDI : Module {
|
||||||
return rootJ;
|
return rootJ;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dataFromJson(json_t* rootJ) override
|
void dataFromJson(json_t* const rootJ) override
|
||||||
{
|
{
|
||||||
if (json_t* const smoothJ = json_object_get(rootJ, "smooth"))
|
if (json_t* const smoothJ = json_object_get(rootJ, "smooth"))
|
||||||
midiInput.smooth = json_boolean_value(smoothJ);
|
midiInput.smooth = json_boolean_value(smoothJ);
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ PLUGIN_FILES = plugins.cpp
|
||||||
# Cardinal (built-in)
|
# Cardinal (built-in)
|
||||||
|
|
||||||
PLUGIN_FILES += Cardinal/src/Blank.cpp
|
PLUGIN_FILES += Cardinal/src/Blank.cpp
|
||||||
PLUGIN_FILES += Cardinal/src/Expanders.cpp
|
PLUGIN_FILES += Cardinal/src/ExpanderInputMIDI.cpp
|
||||||
PLUGIN_FILES += Cardinal/src/glBars.cpp
|
PLUGIN_FILES += Cardinal/src/glBars.cpp
|
||||||
PLUGIN_FILES += Cardinal/src/HostAudio.cpp
|
PLUGIN_FILES += Cardinal/src/HostAudio.cpp
|
||||||
PLUGIN_FILES += Cardinal/src/HostCV.cpp
|
PLUGIN_FILES += Cardinal/src/HostCV.cpp
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue