diff --git a/plugins/Cardinal/plugin.json b/plugins/Cardinal/plugin.json index 3d1fc33..80d7799 100644 --- a/plugins/Cardinal/plugin.json +++ b/plugins/Cardinal/plugin.json @@ -106,6 +106,14 @@ "Visual" ] }, + { + "slug": "ExpanderInputMIDI", + "name": "ExpanderInputMIDI", + "description": "MIDI input expander for Carla Plugin Host or Ildaeil", + "tags": [ + "Expander" + ] + }, { "slug": "AudioFile", "name": "Audio File", diff --git a/plugins/Cardinal/src/Expander.hpp b/plugins/Cardinal/src/Expander.hpp new file mode 100644 index 0000000..aa50504 --- /dev/null +++ b/plugins/Cardinal/src/Expander.hpp @@ -0,0 +1,35 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021-2022 Filipe Coelho + * + * 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 any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +#pragma once + +#include "plugin.hpp" + +#include "CarlaNative.h" + +template +struct CardinalExpander : Module { + static const constexpr int kNumInputs = numInputs; + static const constexpr int kNumOutputs = numOutputs; +}; + +struct CardinalExpanderFromCVToCarlaMIDI : CardinalExpander<6, 0> { + static const constexpr uint MAX_MIDI_EVENTS = 128; + // continuously filled up, flushed on each new block frame + uint frame, midiEventCount; + NativeMidiEvent midiEvents[MAX_MIDI_EVENTS]; +}; diff --git a/plugins/Cardinal/src/Expanders.cpp b/plugins/Cardinal/src/Expanders.cpp new file mode 100644 index 0000000..b20b167 --- /dev/null +++ b/plugins/Cardinal/src/Expanders.cpp @@ -0,0 +1,228 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021-2022 Filipe Coelho + * + * 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 any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +#include "Expander.hpp" + +// -------------------------------------------------------------------------------------------------------------------- + +struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + PITCH_INPUT, + GATE_INPUT, + VEL_INPUT, + AFT_INPUT, + PW_INPUT, + MW_INPUT, + NUM_INPUTS + }; + enum OutputIds { + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + + static const constexpr uint CHANNELS = 16; + int8_t vels[CHANNELS]; + int8_t notes[CHANNELS]; + bool gates[CHANNELS]; + int8_t keyPressures[CHANNELS]; + int8_t mw; + int16_t pw; + + Module* lastConnectedModule = nullptr; + + CardinalExpanderForInputMIDI() { + static_assert(NUM_INPUTS == kNumInputs, "Invalid input configuration"); + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + configInput(PITCH_INPUT, "Pitch (1V/oct)"); + configInput(GATE_INPUT, "Gate"); + configInput(VEL_INPUT, "Velocity"); + configInput(AFT_INPUT, "Aftertouch"); + configInput(PW_INPUT, "Pitch wheel"); + configInput(MW_INPUT, "Mod wheel"); + onReset(); + } + + /** Must be called before setNoteGate(). */ + void setVelocity(int8_t vel, int c) { + vels[c] = vel; + } + + void setNoteGate(int8_t note, bool gate, int c) { + if (midiEventCount == MAX_MIDI_EVENTS) + return; + bool changedNote = gate && gates[c] && (note != notes[c]); + bool enabledGate = gate && !gates[c]; + 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[1] = notes[c]; + m.data[2] = vels[c]; + } + if (changedNote || enabledGate) { + // Note on + NativeMidiEvent& m(midiEvents[midiEventCount++]); + m.time = frame; + m.port = 0; + m.size = 3; + m.data[0] = 0x90; + 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) + return; + keyPressures[c] = val; + // Polyphonic key pressure + NativeMidiEvent& m(midiEvents[midiEventCount++]); + m.time = frame; + m.port = 0; + m.size = 3; + m.data[0] = 0xa0; + m.data[1] = notes[c]; + m.data[2] = val; + } + + void setModWheel(int8_t mw) { + if (this->mw == mw || midiEventCount == MAX_MIDI_EVENTS) + return; + this->mw = mw; + // Modulation Wheel (CC1) + NativeMidiEvent& m(midiEvents[midiEventCount++]); + m.time = frame; + m.port = 0; + m.size = 3; + m.data[0] = 0xb0; + m.data[1] = 1; + m.data[2] = mw; + } + + void setPitchWheel(int16_t pw) { + if (this->pw == pw || midiEventCount == MAX_MIDI_EVENTS) + 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; + } + + void onReset() override + { + for (uint c = 0; c < CHANNELS; c++) { + vels[c] = 100; + notes[c] = 60; + gates[c] = false; + keyPressures[c] = -1; + } + mw = -1; + pw = 0x2000; + midiEventCount = 0; + frame = UINT_MAX; + lastConnectedModule = nullptr; + } + + void process(const ProcessArgs& args) override + { + // only do stuff if there is something close to us + if (rightExpander.module == nullptr) + { + // something was connected before, but not anymore, reset + if (frame != UINT_MAX) + onReset(); + return; + } + else if (lastConnectedModule != nullptr && lastConnectedModule != rightExpander.module) + { + // whatever we were connected to has changed, reset + lastConnectedModule = rightExpander.module; + if (frame != UINT_MAX) + onReset(); + return; + } + + // wait until expanding side is ready + if (frame == UINT_MAX) + 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); + vel = clamp(vel, 0, 127); + setVelocity(vel, c); + + int note = (int) std::round(inputs[PITCH_INPUT].getVoltage(c) * 12.f + 60.f); + note = clamp(note, 0, 127); + 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); + 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 mw = (int) std::round(inputs[MW_INPUT].getVoltage() / 10.f * 127); + mw = clamp(mw, 0, 127); + setModWheel(mw); + + ++frame; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- + +struct CardinalExpanderForInputMIDIWidget : ModuleWidget { + CardinalExpanderForInputMIDIWidget(CardinalExpanderForInputMIDI* const module) + { + setModule(module); + box.size.x = RACK_GRID_WIDTH * 2; + + addChild(createWidget(Vec(0, 0))); + addChild(createWidget(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + for (int i=0; i<6; ++i) + addInput(createInput(Vec(15, 50 + 29 * i), module, i)); + } +}; + +// -------------------------------------------------------------------------------------------------------------------- + +Model* modelExpanderInputMIDI = createModel("ExpanderInputMIDI"); + +// -------------------------------------------------------------------------------------------------------------------- diff --git a/plugins/Cardinal/src/Ildaeil.cpp b/plugins/Cardinal/src/Ildaeil.cpp index 7efd2bd..2fc66d5 100644 --- a/plugins/Cardinal/src/Ildaeil.cpp +++ b/plugins/Cardinal/src/Ildaeil.cpp @@ -26,6 +26,7 @@ */ #include "plugincontext.hpp" +#include "Expander.hpp" #ifndef HEADLESS # include "ImGuiWidget.hpp" @@ -75,122 +76,6 @@ namespace ildaeil { // -------------------------------------------------------------------------------------------------------------------- -/** Converts gates and CV to MIDI messages. */ -struct IldaeilMidiGenerator { - static const constexpr uint CHANNELS = 16; - static const constexpr uint MAX_MIDI_EVENTS = 128; - int8_t vels[CHANNELS]; - int8_t notes[CHANNELS]; - bool gates[CHANNELS]; - int8_t keyPressures[CHANNELS]; - int8_t mw; - int16_t pw; - uint32_t frame; - // generated output - uint midiEventCount; - NativeMidiEvent midiEvents[MAX_MIDI_EVENTS]; - - IldaeilMidiGenerator() { - 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; - frame = 0; - midiEventCount = 0; - } - - void setFrame(uint32_t frame) { - this->frame = frame; - if (frame == 0) - midiEventCount = 0; - } - - /** Must be called before setNoteGate(). */ - void setVelocity(int8_t vel, int c) { - vels[c] = vel; - } - - void setNoteGate(int8_t note, bool gate, int c) { - if (midiEventCount == MAX_MIDI_EVENTS) - return; - bool changedNote = gate && gates[c] && (note != notes[c]); - bool enabledGate = gate && !gates[c]; - 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[1] = notes[c]; - m.data[2] = vels[c]; - } - if (changedNote || enabledGate) { - // Note on - NativeMidiEvent& m(midiEvents[midiEventCount++]); - m.time = frame; - m.port = 0; - m.size = 3; - m.data[0] = 0x90; - 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) - return; - keyPressures[c] = val; - // Polyphonic key pressure - NativeMidiEvent& m(midiEvents[midiEventCount++]); - m.time = frame; - m.port = 0; - m.size = 3; - m.data[0] = 0xa0; - m.data[1] = notes[c]; - m.data[2] = val; - } - - void setModWheel(int8_t mw) { - if (this->mw == mw || midiEventCount == MAX_MIDI_EVENTS) - return; - this->mw = mw; - // Modulation Wheel (CC1) - NativeMidiEvent& m(midiEvents[midiEventCount++]); - m.time = frame; - m.port = 0; - m.size = 3; - m.data[0] = 0xb0; - m.data[1] = 1; - m.data[2] = mw; - } - - void setPitchWheel(int16_t pw) { - if (this->pw == pw || midiEventCount == MAX_MIDI_EVENTS) - 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; - } -}; - // -------------------------------------------------------------------------------------------------------------------- using namespace CarlaBackend; @@ -227,12 +112,6 @@ struct IldaeilModule : Module { enum InputIds { INPUT1, INPUT2, - PITCH_INPUT, - GATE_INPUT, - VEL_INPUT, - AFT_INPUT, - PW_INPUT, - MW_INPUT, NUM_INPUTS }; enum OutputIds { @@ -268,8 +147,6 @@ struct IldaeilModule : Module { unsigned audioDataFill = 0; int64_t lastBlockFrame = -1; - IldaeilMidiGenerator midiGenerator; - IldaeilModule() : pcontext(static_cast(APP)) { @@ -280,13 +157,6 @@ struct IldaeilModule : Module { configInput(i, name); configOutput(i, name); } - configInput(PITCH_INPUT, "Pitch (1V/oct)"); - configInput(GATE_INPUT, "Gate"); - configInput(VEL_INPUT, "Velocity"); - configInput(AFT_INPUT, "Aftertouch"); - configInput(PW_INPUT, "Pitch wheel"); - configInput(MW_INPUT, "Mod wheel"); - std::memset(audioDataOut1, 0, sizeof(audioDataOut1)); std::memset(audioDataOut2, 0, sizeof(audioDataOut2)); @@ -472,31 +342,6 @@ struct IldaeilModule : Module { outputs[OUTPUT1].setVoltage(audioDataOut1[i] * 10.0f); outputs[OUTPUT2].setVoltage(audioDataOut2[i] * 10.0f); - midiGenerator.setFrame(i); - - 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); - vel = clamp(vel, 0, 127); - midiGenerator.setVelocity(vel, c); - - int note = (int) std::round(inputs[PITCH_INPUT].getVoltage(c) * 12.f + 60.f); - note = clamp(note, 0, 127); - bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f; - midiGenerator.setNoteGate(note, gate, c); - - int aft = (int) std::round(inputs[AFT_INPUT].getPolyVoltage(c) / 10.f * 127); - aft = clamp(aft, 0, 127); - midiGenerator.setKeyPressure(aft, c); - } - - int pw = (int) std::round((inputs[PW_INPUT].getVoltage() + 5.f) / 10.f * 0x4000); - pw = clamp(pw, 0, 0x3fff); - midiGenerator.setPitchWheel(pw); - - int mw = (int) std::round(inputs[MW_INPUT].getVoltage() / 10.f * 127); - mw = clamp(mw, 0, 127); - midiGenerator.setModWheel(mw); - if (audioDataFill == BUFFER_SIZE) { const int64_t blockFrame = pcontext->engine->getBlockFrame(); @@ -552,19 +397,30 @@ struct IldaeilModule : Module { } } + NativeMidiEvent* midiEvents; + uint midiEventCount; + + if (CardinalExpanderFromCVToCarlaMIDI* const midiInExpander = leftExpander.module != nullptr && leftExpander.module->model == modelExpanderInputMIDI + ? static_cast(leftExpander.module) + : nullptr) + { + midiEvents = midiInExpander->midiEvents; + midiEventCount = midiInExpander->midiEventCount; + midiInExpander->midiEventCount = midiInExpander->frame = 0; + } + else + { + midiEvents = nullptr; + midiEventCount = 0; + } + audioDataFill = 0; float* ins[2] = { audioDataIn1, audioDataIn2 }; float* outs[2] = { audioDataOut1, audioDataOut2 }; - fCarlaPluginDescriptor->process(fCarlaPluginHandle, ins, outs, BUFFER_SIZE, - midiGenerator.midiEvents, midiGenerator.midiEventCount); + fCarlaPluginDescriptor->process(fCarlaPluginHandle, ins, outs, BUFFER_SIZE, midiEvents, midiEventCount); } } - void onReset() override - { - midiGenerator.reset(); - } - void onSampleRateChange(const SampleRateChangeEvent& e) override { if (fCarlaPluginHandle == nullptr) @@ -1688,13 +1544,6 @@ struct IldaeilModuleWidget : ModuleWidget { addInput(createInput(Vec(3, 54 + 30), module, IldaeilModule::INPUT2)); addOutput(createOutput(Vec(3, 54 + 60), module, IldaeilModule::OUTPUT1)); addOutput(createOutput(Vec(3, 54 + 90), module, IldaeilModule::OUTPUT2)); - - addInput(createInput(Vec(3, 54 + 135), module, IldaeilModule::PITCH_INPUT)); - addInput(createInput(Vec(3, 54 + 165), module, IldaeilModule::GATE_INPUT)); - addInput(createInput(Vec(3, 54 + 195), module, IldaeilModule::VEL_INPUT)); - addInput(createInput(Vec(3, 54 + 225), module, IldaeilModule::AFT_INPUT)); - addInput(createInput(Vec(3, 54 + 255), module, IldaeilModule::PW_INPUT)); - addInput(createInput(Vec(3, 54 + 285), module, IldaeilModule::MW_INPUT)); } void draw(const DrawArgs& args) override @@ -1722,13 +1571,6 @@ struct IldaeilModuleWidget : ModuleWidget { addInput(createInput({}, module, IldaeilModule::INPUT2)); addOutput(createOutput({}, module, IldaeilModule::OUTPUT1)); addOutput(createOutput({}, module, IldaeilModule::OUTPUT2)); - - addInput(createInput({}, module, IldaeilModule::PITCH_INPUT)); - addInput(createInput({}, module, IldaeilModule::GATE_INPUT)); - addInput(createInput({}, module, IldaeilModule::VEL_INPUT)); - addInput(createInput({}, module, IldaeilModule::AFT_INPUT)); - addInput(createInput({}, module, IldaeilModule::PW_INPUT)); - addInput(createInput({}, module, IldaeilModule::MW_INPUT)); } }; #endif diff --git a/plugins/Cardinal/src/plugin.hpp b/plugins/Cardinal/src/plugin.hpp index 6aee084..07c352e 100644 --- a/plugins/Cardinal/src/plugin.hpp +++ b/plugins/Cardinal/src/plugin.hpp @@ -30,6 +30,7 @@ extern Plugin* pluginInstance; extern Model* modelAudioFile; extern Model* modelCarla; extern Model* modelCardinalBlank; +extern Model* modelExpanderInputMIDI; extern Model* modelGlBars; extern Model* modelHostAudio2; extern Model* modelHostAudio8; diff --git a/plugins/Makefile b/plugins/Makefile index 5cccc98..9f4312e 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -187,6 +187,7 @@ PLUGIN_FILES = plugins.cpp # Cardinal (built-in) PLUGIN_FILES += Cardinal/src/Blank.cpp +PLUGIN_FILES += Cardinal/src/Expanders.cpp PLUGIN_FILES += Cardinal/src/glBars.cpp PLUGIN_FILES += Cardinal/src/HostAudio.cpp PLUGIN_FILES += Cardinal/src/HostCV.cpp diff --git a/plugins/plugins.cpp b/plugins/plugins.cpp index be920c0..1af3179 100644 --- a/plugins/plugins.cpp +++ b/plugins/plugins.cpp @@ -674,6 +674,7 @@ static void initStatic__Cardinal() if (spl.ok()) { p->addModel(modelCardinalBlank); + p->addModel(modelExpanderInputMIDI); p->addModel(modelGlBars); p->addModel(modelHostAudio2); p->addModel(modelHostAudio8);