Implement MIDI out expander \o/
This commit is contained in:
parent
d0224fce14
commit
ccb94fd000
10 changed files with 740 additions and 8 deletions
|
|
@ -109,9 +109,19 @@
|
||||||
{
|
{
|
||||||
"slug": "ExpanderInputMIDI",
|
"slug": "ExpanderInputMIDI",
|
||||||
"name": "ExpanderInputMIDI",
|
"name": "ExpanderInputMIDI",
|
||||||
"description": "MIDI input expander for Carla Plugin Host or Ildaeil",
|
"description": "MIDI input (from CV) expander for Carla Plugin Host and Ildaeil",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Expander"
|
"Expander",
|
||||||
|
"MIDI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "ExpanderOutputMIDI",
|
||||||
|
"name": "ExpanderOutputMIDI",
|
||||||
|
"description": "MIDI output (to CV) expander for Carla Plugin Host and Ildaeil",
|
||||||
|
"tags": [
|
||||||
|
"Expander",
|
||||||
|
"MIDI"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ struct CarlaModule : Module {
|
||||||
float* dataOutPtr[NUM_OUTPUTS];
|
float* dataOutPtr[NUM_OUTPUTS];
|
||||||
unsigned audioDataFill = 0;
|
unsigned audioDataFill = 0;
|
||||||
int64_t lastBlockFrame = -1;
|
int64_t lastBlockFrame = -1;
|
||||||
|
CardinalExpanderFromCarlaMIDIToCV* midiOutExpander = nullptr;
|
||||||
std::string patchStorage;
|
std::string patchStorage;
|
||||||
|
|
||||||
CarlaModule()
|
CarlaModule()
|
||||||
|
|
@ -387,16 +388,28 @@ struct CarlaModule : Module {
|
||||||
midiEventCount = 0;
|
midiEventCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((midiOutExpander = rightExpander.module != nullptr && rightExpander.module->model == modelExpanderOutputMIDI
|
||||||
|
? static_cast<CardinalExpanderFromCarlaMIDIToCV*>(rightExpander.module)
|
||||||
|
: nullptr))
|
||||||
|
midiOutExpander->midiEventCount = 0;
|
||||||
|
|
||||||
audioDataFill = 0;
|
audioDataFill = 0;
|
||||||
fCarlaPluginDescriptor->process(fCarlaPluginHandle, dataInPtr, dataOutPtr, BUFFER_SIZE, midiEvents, midiEventCount);
|
fCarlaPluginDescriptor->process(fCarlaPluginHandle, dataInPtr, dataOutPtr, BUFFER_SIZE, midiEvents, midiEventCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onReset() override
|
||||||
|
{
|
||||||
|
midiOutExpander = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void onSampleRateChange(const SampleRateChangeEvent& e) override
|
void onSampleRateChange(const SampleRateChangeEvent& e) override
|
||||||
{
|
{
|
||||||
if (fCarlaPluginHandle == nullptr)
|
if (fCarlaPluginHandle == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
midiOutExpander = nullptr;
|
||||||
|
|
||||||
fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
|
fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
|
||||||
fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
|
fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
|
||||||
0, 0, nullptr, e.sampleRate);
|
0, 0, nullptr, e.sampleRate);
|
||||||
|
|
@ -434,6 +447,16 @@ static const NativeTimeInfo* host_get_time_info(const NativeHostHandle handle)
|
||||||
|
|
||||||
static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
|
static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
|
||||||
{
|
{
|
||||||
|
if (CardinalExpanderFromCarlaMIDIToCV* const expander = static_cast<CarlaModule*>(handle)->midiOutExpander)
|
||||||
|
{
|
||||||
|
if (expander->midiEventCount == CardinalExpanderFromCarlaMIDIToCV::MAX_MIDI_EVENTS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NativeMidiEvent& expanderEvent(expander->midiEvents[expander->midiEventCount++]);
|
||||||
|
carla_copyStruct(expanderEvent, *event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -464,6 +487,7 @@ static intptr_t host_dispatcher(const NativeHostHandle handle, const NativeHostD
|
||||||
struct CarlaModuleWidget : ModuleWidgetWith9HP, IdleCallback {
|
struct CarlaModuleWidget : ModuleWidgetWith9HP, IdleCallback {
|
||||||
CarlaModule* const module;
|
CarlaModule* const module;
|
||||||
bool hasLeftSideExpander = false;
|
bool hasLeftSideExpander = false;
|
||||||
|
bool hasRightSideExpander = false;
|
||||||
bool idleCallbackActive = false;
|
bool idleCallbackActive = false;
|
||||||
bool visible = false;
|
bool visible = false;
|
||||||
|
|
||||||
|
|
@ -578,7 +602,6 @@ struct CarlaModuleWidget : ModuleWidgetWith9HP, IdleCallback {
|
||||||
void draw(const DrawArgs& args) override
|
void draw(const DrawArgs& args) override
|
||||||
{
|
{
|
||||||
drawBackground(args.vg);
|
drawBackground(args.vg);
|
||||||
drawOutputJacksArea(args.vg, CarlaModule::NUM_INPUTS);
|
|
||||||
|
|
||||||
if (hasLeftSideExpander)
|
if (hasLeftSideExpander)
|
||||||
{
|
{
|
||||||
|
|
@ -599,6 +622,21 @@ struct CarlaModuleWidget : ModuleWidgetWith9HP, IdleCallback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasRightSideExpander)
|
||||||
|
{
|
||||||
|
nvgFillColor(args.vg, nvgRGB(0xd0, 0xd0, 0xd0));
|
||||||
|
|
||||||
|
for (int i=0; i<6; ++i)
|
||||||
|
{
|
||||||
|
const float y = 90 + 49 * i - 19;
|
||||||
|
nvgBeginPath(args.vg);
|
||||||
|
nvgRect(args.vg, box.size.x - 19, y, 18, 49 - 4);
|
||||||
|
nvgFill(args.vg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawOutputJacksArea(args.vg, CarlaModule::NUM_INPUTS);
|
||||||
|
|
||||||
setupTextLines(args.vg);
|
setupTextLines(args.vg);
|
||||||
|
|
||||||
drawTextLine(args.vg, 0, "Audio 1");
|
drawTextLine(args.vg, 0, "Audio 1");
|
||||||
|
|
@ -621,6 +659,10 @@ struct CarlaModuleWidget : ModuleWidgetWith9HP, IdleCallback {
|
||||||
&& module->leftExpander.module != nullptr
|
&& module->leftExpander.module != nullptr
|
||||||
&& module->leftExpander.module->model == modelExpanderInputMIDI;
|
&& module->leftExpander.module->model == modelExpanderInputMIDI;
|
||||||
|
|
||||||
|
hasRightSideExpander = module != nullptr
|
||||||
|
&& module->rightExpander.module != nullptr
|
||||||
|
&& module->rightExpander.module->model == modelExpanderOutputMIDI;
|
||||||
|
|
||||||
ModuleWidgetWith9HP::step();
|
ModuleWidgetWith9HP::step();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,16 @@ struct CardinalExpander : Module {
|
||||||
|
|
||||||
struct CardinalExpanderFromCVToCarlaMIDI : CardinalExpander<6, 0> {
|
struct CardinalExpanderFromCVToCarlaMIDI : CardinalExpander<6, 0> {
|
||||||
static const constexpr uint MAX_MIDI_EVENTS = 128;
|
static const constexpr uint MAX_MIDI_EVENTS = 128;
|
||||||
// continuously filled up, flushed on each new block frame
|
// continuously filled up by expander, flushed on each new block frame
|
||||||
|
// frames are related to host block size
|
||||||
uint frame, midiEventCount;
|
uint frame, midiEventCount;
|
||||||
NativeMidiEvent midiEvents[MAX_MIDI_EVENTS];
|
NativeMidiEvent midiEvents[MAX_MIDI_EVENTS];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CardinalExpanderFromCarlaMIDIToCV : CardinalExpander<0, 6> {
|
||||||
|
static const constexpr uint MAX_MIDI_EVENTS = 128;
|
||||||
|
// filled up by connector-side in bursts, must be reset on next cycle by expander
|
||||||
|
// frames are not related to any particular block size
|
||||||
|
uint midiEventCount;
|
||||||
|
NativeMidiEvent midiEvents[MAX_MIDI_EVENTS];
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI {
|
||||||
CardinalExpanderForInputMIDI()
|
CardinalExpanderForInputMIDI()
|
||||||
{
|
{
|
||||||
static_assert(NUM_INPUTS == kNumInputs, "Invalid input configuration");
|
static_assert(NUM_INPUTS == kNumInputs, "Invalid input configuration");
|
||||||
|
static_assert(NUM_OUTPUTS == kNumOutputs, "Invalid output 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");
|
||||||
|
|
@ -333,8 +334,6 @@ struct CardinalExpanderForInputMIDIWidget : ModuleWidgetWith3HP {
|
||||||
nvgRoundedRect(args.vg, 6.5f, startY - 19.0f, 3.0f, padding * 6.0f - 4.0f, 1);
|
nvgRoundedRect(args.vg, 6.5f, startY - 19.0f, 3.0f, padding * 6.0f - 4.0f, 1);
|
||||||
nvgFill(args.vg);
|
nvgFill(args.vg);
|
||||||
|
|
||||||
nvgBeginPath(args.vg);
|
|
||||||
nvgRect(args.vg, box.size.x * 0.5f, 0, box.size.x, box.size.y);
|
|
||||||
nvgFillColor(args.vg, color::BLACK);
|
nvgFillColor(args.vg, color::BLACK);
|
||||||
nvgFontFaceId(args.vg, 0);
|
nvgFontFaceId(args.vg, 0);
|
||||||
nvgFontSize(args.vg, 11);
|
nvgFontSize(args.vg, 11);
|
||||||
|
|
|
||||||
617
plugins/Cardinal/src/ExpanderOutputMIDI.cpp
Normal file
617
plugins/Cardinal/src/ExpanderOutputMIDI.cpp
Normal file
|
|
@ -0,0 +1,617 @@
|
||||||
|
/*
|
||||||
|
* DISTRHO Cardinal Plugin
|
||||||
|
* Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com>
|
||||||
|
*
|
||||||
|
* 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"
|
||||||
|
#include "ModuleWidgets.hpp"
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains a substantial amount of code from VCVRack's core/MIDI_CV.cpp
|
||||||
|
* 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 CardinalExpanderForOutputMIDI : CardinalExpanderFromCarlaMIDIToCV {
|
||||||
|
enum ParamIds {
|
||||||
|
NUM_PARAMS
|
||||||
|
};
|
||||||
|
enum InputIds {
|
||||||
|
NUM_INPUTS
|
||||||
|
};
|
||||||
|
enum OutputIds {
|
||||||
|
PITCH_OUTPUT,
|
||||||
|
GATE_OUTPUT,
|
||||||
|
VELOCITY_OUTPUT,
|
||||||
|
AFTERTOUCH_OUTPUT,
|
||||||
|
PITCHBEND_OUTPUT,
|
||||||
|
MODWHEEL_OUTPUT,
|
||||||
|
NUM_OUTPUTS
|
||||||
|
};
|
||||||
|
enum LightIds {
|
||||||
|
NUM_LIGHTS
|
||||||
|
};
|
||||||
|
|
||||||
|
NativeMidiEvent midiEventsCopy[MAX_MIDI_EVENTS];
|
||||||
|
const NativeMidiEvent* midiEventsPtr;
|
||||||
|
uint32_t midiEventsLeft;
|
||||||
|
uint32_t midiEventFrame;
|
||||||
|
uint8_t channel;
|
||||||
|
Module* lastConnectedModule;
|
||||||
|
midi::Message converterMsg;
|
||||||
|
|
||||||
|
// stuff from Rack
|
||||||
|
bool smooth;
|
||||||
|
int channels;
|
||||||
|
enum PolyMode {
|
||||||
|
ROTATE_MODE,
|
||||||
|
REUSE_MODE,
|
||||||
|
RESET_MODE,
|
||||||
|
MPE_MODE,
|
||||||
|
NUM_POLY_MODES
|
||||||
|
};
|
||||||
|
PolyMode polyMode;
|
||||||
|
|
||||||
|
bool pedal;
|
||||||
|
// Indexed by channel
|
||||||
|
uint8_t notes[16];
|
||||||
|
bool gates[16];
|
||||||
|
uint8_t velocities[16];
|
||||||
|
uint8_t aftertouches[16];
|
||||||
|
std::vector<uint8_t> heldNotes;
|
||||||
|
|
||||||
|
int rotateIndex;
|
||||||
|
|
||||||
|
/** Pitch wheel.
|
||||||
|
When MPE is disabled, only the first channel is used.
|
||||||
|
[channel]
|
||||||
|
*/
|
||||||
|
uint16_t pws[16];
|
||||||
|
/** [channel] */
|
||||||
|
uint8_t mods[16];
|
||||||
|
dsp::ExponentialFilter pwFilters[16];
|
||||||
|
dsp::ExponentialFilter modFilters[16];
|
||||||
|
|
||||||
|
CardinalExpanderForOutputMIDI() {
|
||||||
|
static_assert(NUM_INPUTS == kNumInputs, "Invalid input configuration");
|
||||||
|
static_assert(NUM_OUTPUTS == kNumOutputs, "Invalid output configuration");
|
||||||
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
|
||||||
|
configOutput(PITCH_OUTPUT, "1V/octave pitch");
|
||||||
|
configOutput(GATE_OUTPUT, "Gate");
|
||||||
|
configOutput(VELOCITY_OUTPUT, "Velocity");
|
||||||
|
configOutput(AFTERTOUCH_OUTPUT, "Aftertouch");
|
||||||
|
configOutput(PITCHBEND_OUTPUT, "Pitchbend");
|
||||||
|
configOutput(MODWHEEL_OUTPUT, "Mod wheel");
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
midiEventsPtr = nullptr;
|
||||||
|
midiEventCount = 0;
|
||||||
|
midiEventsLeft = 0;
|
||||||
|
midiEventFrame = 0;
|
||||||
|
channel = 0;
|
||||||
|
smooth = true;
|
||||||
|
channels = 1;
|
||||||
|
polyMode = ROTATE_MODE;
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
|
||||||
|
void panic()
|
||||||
|
{
|
||||||
|
for (int c = 0; c < 16; c++) {
|
||||||
|
notes[c] = 60;
|
||||||
|
gates[c] = false;
|
||||||
|
velocities[c] = 0;
|
||||||
|
aftertouches[c] = 0;
|
||||||
|
pws[c] = 8192;
|
||||||
|
mods[c] = 0;
|
||||||
|
pwFilters[c].reset();
|
||||||
|
modFilters[c].reset();
|
||||||
|
}
|
||||||
|
pedal = false;
|
||||||
|
rotateIndex = -1;
|
||||||
|
heldNotes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setChannels(const int channels)
|
||||||
|
{
|
||||||
|
if (channels == this->channels)
|
||||||
|
return;
|
||||||
|
this->channels = channels;
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPolyMode(const PolyMode polyMode)
|
||||||
|
{
|
||||||
|
if (polyMode == this->polyMode)
|
||||||
|
return;
|
||||||
|
this->polyMode = polyMode;
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onReset() override
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
channel = 0;
|
||||||
|
lastConnectedModule = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void process(const ProcessArgs& args) override
|
||||||
|
{
|
||||||
|
// only handle messages if there is something close to us
|
||||||
|
if (leftExpander.module == nullptr)
|
||||||
|
{
|
||||||
|
// something was connected before, but not anymore, reset
|
||||||
|
if (midiEventCount != 0)
|
||||||
|
onReset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (lastConnectedModule != nullptr && lastConnectedModule != leftExpander.module)
|
||||||
|
{
|
||||||
|
// whatever we were connected to has changed, reset
|
||||||
|
lastConnectedModule = leftExpander.module;
|
||||||
|
if (midiEventCount != 0)
|
||||||
|
onReset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if expanding side has messages for us
|
||||||
|
if (midiEventCount != 0)
|
||||||
|
{
|
||||||
|
midiEventFrame = 0;
|
||||||
|
midiEventsLeft = midiEventCount;
|
||||||
|
midiEventsPtr = midiEventsCopy;
|
||||||
|
std::memcpy(midiEventsCopy, midiEvents, sizeof(NativeMidiEvent)*midiEventCount);
|
||||||
|
// reset as required
|
||||||
|
midiEventCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (midiEventsLeft != 0)
|
||||||
|
{
|
||||||
|
const NativeMidiEvent& midiEvent(*midiEventsPtr);
|
||||||
|
|
||||||
|
if (midiEvent.time > midiEventFrame)
|
||||||
|
break;
|
||||||
|
|
||||||
|
++midiEventsPtr;
|
||||||
|
--midiEventsLeft;
|
||||||
|
|
||||||
|
const uint8_t* const data = midiEvent.data;
|
||||||
|
|
||||||
|
if (channel != 0 && data[0] < 0xF0)
|
||||||
|
{
|
||||||
|
if ((data[0] & 0x0F) != (channel - 1))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
converterMsg.frame = midiEventFrame;
|
||||||
|
std::memcpy(converterMsg.bytes.data(), data, midiEvent.size);
|
||||||
|
|
||||||
|
processMessage(converterMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
++midiEventFrame;
|
||||||
|
|
||||||
|
// Rack stuff
|
||||||
|
outputs[PITCH_OUTPUT].setChannels(channels);
|
||||||
|
outputs[GATE_OUTPUT].setChannels(channels);
|
||||||
|
outputs[VELOCITY_OUTPUT].setChannels(channels);
|
||||||
|
outputs[AFTERTOUCH_OUTPUT].setChannels(channels);
|
||||||
|
for (int c = 0; c < channels; c++) {
|
||||||
|
outputs[PITCH_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[c], 0, 127, 0.f, 10.f), c);
|
||||||
|
outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(aftertouches[c], 0, 127, 0.f, 10.f), c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set pitch and mod wheel
|
||||||
|
const int wheelChannels = (polyMode == MPE_MODE) ? 16 : 1;
|
||||||
|
outputs[PITCHBEND_OUTPUT].setChannels(wheelChannels);
|
||||||
|
outputs[MODWHEEL_OUTPUT].setChannels(wheelChannels);
|
||||||
|
for (int c = 0; c < wheelChannels; c++) {
|
||||||
|
float pw = ((int) pws[c] - 8192) / 8191.f;
|
||||||
|
pw = clamp(pw, -1.f, 1.f);
|
||||||
|
if (smooth)
|
||||||
|
pw = pwFilters[c].process(args.sampleTime, pw);
|
||||||
|
else
|
||||||
|
pwFilters[c].out = pw;
|
||||||
|
outputs[PITCHBEND_OUTPUT].setVoltage(pw * 5.f);
|
||||||
|
|
||||||
|
float mod = mods[c] / 127.f;
|
||||||
|
mod = clamp(mod, 0.f, 1.f);
|
||||||
|
if (smooth)
|
||||||
|
mod = modFilters[c].process(args.sampleTime, mod);
|
||||||
|
else
|
||||||
|
modFilters[c].out = mod;
|
||||||
|
outputs[MODWHEEL_OUTPUT].setVoltage(mod * 10.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processMessage(const midi::Message& msg)
|
||||||
|
{
|
||||||
|
// DEBUG("MIDI: %ld %s", msg.getFrame(), msg.toString().c_str());
|
||||||
|
|
||||||
|
switch (msg.getStatus()) {
|
||||||
|
// note off
|
||||||
|
case 0x8: {
|
||||||
|
releaseNote(msg.getNote());
|
||||||
|
} break;
|
||||||
|
// note on
|
||||||
|
case 0x9: {
|
||||||
|
if (msg.getValue() > 0) {
|
||||||
|
int c = msg.getChannel();
|
||||||
|
pressNote(msg.getNote(), &c);
|
||||||
|
velocities[c] = msg.getValue();
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
// key pressure
|
||||||
|
case 0xa: {
|
||||||
|
// 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.getNote();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Set all aftertouches
|
||||||
|
for (int c = 0; c < 16; c++) {
|
||||||
|
aftertouches[c] = msg.getNote();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
// pitch wheel
|
||||||
|
case 0xe: {
|
||||||
|
int c = (polyMode == MPE_MODE) ? msg.getChannel() : 0;
|
||||||
|
pws[c] = ((uint16_t) msg.getValue() << 7) | msg.getNote();
|
||||||
|
} break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processCC(const midi::Message& msg) {
|
||||||
|
switch (msg.getNote()) {
|
||||||
|
// mod
|
||||||
|
case 0x01: {
|
||||||
|
int c = (polyMode == MPE_MODE) ? msg.getChannel() : 0;
|
||||||
|
mods[c] = msg.getValue();
|
||||||
|
} break;
|
||||||
|
// sustain
|
||||||
|
case 0x40: {
|
||||||
|
if (msg.getValue() >= 64)
|
||||||
|
pressPedal();
|
||||||
|
else
|
||||||
|
releasePedal();
|
||||||
|
} break;
|
||||||
|
// all notes off (panic)
|
||||||
|
case 0x7b: {
|
||||||
|
if (msg.getValue() == 0) {
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int assignChannel(uint8_t note) {
|
||||||
|
if (channels == 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
switch (polyMode) {
|
||||||
|
case REUSE_MODE: {
|
||||||
|
// Find channel with the same note
|
||||||
|
for (int c = 0; c < channels; c++) {
|
||||||
|
if (notes[c] == note)
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
} // fallthrough
|
||||||
|
|
||||||
|
case ROTATE_MODE: {
|
||||||
|
// Find next available channel
|
||||||
|
for (int i = 0; i < channels; i++) {
|
||||||
|
rotateIndex++;
|
||||||
|
if (rotateIndex >= channels)
|
||||||
|
rotateIndex = 0;
|
||||||
|
if (!gates[rotateIndex])
|
||||||
|
return rotateIndex;
|
||||||
|
}
|
||||||
|
// No notes are available. Advance rotateIndex once more.
|
||||||
|
rotateIndex++;
|
||||||
|
if (rotateIndex >= channels)
|
||||||
|
rotateIndex = 0;
|
||||||
|
return rotateIndex;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case RESET_MODE: {
|
||||||
|
for (int c = 0; c < channels; c++) {
|
||||||
|
if (!gates[c])
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return channels - 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case MPE_MODE: {
|
||||||
|
// This case is handled by querying the MIDI message channel.
|
||||||
|
return 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pressNote(uint8_t note, int* channel) {
|
||||||
|
// Remove existing similar note
|
||||||
|
auto it = std::find(heldNotes.begin(), heldNotes.end(), note);
|
||||||
|
if (it != heldNotes.end())
|
||||||
|
heldNotes.erase(it);
|
||||||
|
// Push note
|
||||||
|
heldNotes.push_back(note);
|
||||||
|
// Determine actual channel
|
||||||
|
if (polyMode == MPE_MODE) {
|
||||||
|
// Channel is already decided for us
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*channel = assignChannel(note);
|
||||||
|
}
|
||||||
|
// Set note
|
||||||
|
notes[*channel] = note;
|
||||||
|
gates[*channel] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void releaseNote(uint8_t note) {
|
||||||
|
// Remove the note
|
||||||
|
auto it = std::find(heldNotes.begin(), heldNotes.end(), note);
|
||||||
|
if (it != heldNotes.end())
|
||||||
|
heldNotes.erase(it);
|
||||||
|
// Hold note if pedal is pressed
|
||||||
|
if (pedal)
|
||||||
|
return;
|
||||||
|
// Turn off gate of all channels with note
|
||||||
|
for (int c = 0; c < channels; c++) {
|
||||||
|
if (notes[c] == note) {
|
||||||
|
gates[c] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set last note if monophonic
|
||||||
|
if (channels == 1) {
|
||||||
|
if (note == notes[0] && !heldNotes.empty()) {
|
||||||
|
uint8_t lastNote = heldNotes.back();
|
||||||
|
notes[0] = lastNote;
|
||||||
|
gates[0] = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pressPedal() {
|
||||||
|
if (pedal)
|
||||||
|
return;
|
||||||
|
pedal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void releasePedal() {
|
||||||
|
if (!pedal)
|
||||||
|
return;
|
||||||
|
pedal = false;
|
||||||
|
// Set last note if monophonic
|
||||||
|
if (channels == 1) {
|
||||||
|
if (!heldNotes.empty()) {
|
||||||
|
uint8_t lastNote = heldNotes.back();
|
||||||
|
notes[0] = lastNote;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clear notes that are not held if polyphonic
|
||||||
|
else {
|
||||||
|
for (int c = 0; c < channels; c++) {
|
||||||
|
if (!gates[c])
|
||||||
|
continue;
|
||||||
|
gates[c] = false;
|
||||||
|
for (uint8_t note : heldNotes) {
|
||||||
|
if (notes[c] == note) {
|
||||||
|
gates[c] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t* dataToJson() override
|
||||||
|
{
|
||||||
|
json_t* const rootJ = json_object();
|
||||||
|
DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, nullptr);
|
||||||
|
|
||||||
|
json_object_set_new(rootJ, "smooth", json_boolean(smooth));
|
||||||
|
json_object_set_new(rootJ, "channels", json_integer(channels));
|
||||||
|
json_object_set_new(rootJ, "polyMode", json_integer(polyMode));
|
||||||
|
|
||||||
|
// Saving/restoring pitch and mod doesn't make much sense for MPE.
|
||||||
|
if (polyMode != MPE_MODE)
|
||||||
|
{
|
||||||
|
json_object_set_new(rootJ, "lastPitch", json_integer(pws[0]));
|
||||||
|
json_object_set_new(rootJ, "lastMod", json_integer(mods[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_set_new(rootJ, "channel", json_integer(channel));
|
||||||
|
return rootJ;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dataFromJson(json_t* const rootJ) override
|
||||||
|
{
|
||||||
|
if (json_t* const smoothJ = json_object_get(rootJ, "smooth"))
|
||||||
|
smooth = json_boolean_value(smoothJ);
|
||||||
|
|
||||||
|
if (json_t* const channelsJ = json_object_get(rootJ, "channels"))
|
||||||
|
setChannels(json_integer_value(channelsJ));
|
||||||
|
|
||||||
|
if (json_t* const polyModeJ = json_object_get(rootJ, "polyMode"))
|
||||||
|
polyMode = (PolyMode) json_integer_value(polyModeJ);
|
||||||
|
|
||||||
|
if (json_t* const lastPitchJ = json_object_get(rootJ, "lastPitch"))
|
||||||
|
pws[0] = json_integer_value(lastPitchJ);
|
||||||
|
|
||||||
|
if (json_t* const lastModJ = json_object_get(rootJ, "lastMod"))
|
||||||
|
mods[0] = json_integer_value(lastModJ);
|
||||||
|
|
||||||
|
if (json_t* const channelJ = json_object_get(rootJ, "channel"))
|
||||||
|
channel = json_integer_value(channelJ);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct CardinalExpanderForOutputMIDIWidget : ModuleWidgetWith3HP {
|
||||||
|
static constexpr const float startX = 1.0f;
|
||||||
|
static constexpr const float startY = 90.0f;
|
||||||
|
static constexpr const float padding = 49.0f;
|
||||||
|
|
||||||
|
CardinalExpanderForOutputMIDI* const module;
|
||||||
|
|
||||||
|
CardinalExpanderForOutputMIDIWidget(CardinalExpanderForOutputMIDI* const m)
|
||||||
|
: module(m)
|
||||||
|
{
|
||||||
|
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<CardinalExpanderForOutputMIDI::NUM_OUTPUTS; ++i)
|
||||||
|
addOutput(createOutput<PJ301MPort>(Vec(startX + 4.0f, startY + padding * i), m, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(const DrawArgs& args) override
|
||||||
|
{
|
||||||
|
drawBackground(args.vg);
|
||||||
|
|
||||||
|
nvgFillColor(args.vg, nvgRGB(0xd0, 0xd0, 0xd0));
|
||||||
|
|
||||||
|
nvgSave(args.vg);
|
||||||
|
nvgIntersectScissor(args.vg, startX, 0.0f, box.size.x - startX - 1.0f, box.size.y);
|
||||||
|
|
||||||
|
for (int i=0; i<CardinalExpanderForOutputMIDI::NUM_OUTPUTS; ++i)
|
||||||
|
{
|
||||||
|
const float y = startY + i * padding;
|
||||||
|
|
||||||
|
nvgBeginPath(args.vg);
|
||||||
|
nvgRoundedRect(args.vg, -4.0f, y - 19.0f, 35.0f, padding - 4.0f, 4);
|
||||||
|
nvgFill(args.vg);
|
||||||
|
}
|
||||||
|
|
||||||
|
nvgRestore(args.vg);
|
||||||
|
|
||||||
|
nvgBeginPath(args.vg);
|
||||||
|
nvgRoundedRect(args.vg, box.size.x - 9.5f, startY - 19.0f, 3.0f, padding * 6.0f - 4.0f, 1);
|
||||||
|
nvgFill(args.vg);
|
||||||
|
|
||||||
|
nvgFillColor(args.vg, color::BLACK);
|
||||||
|
nvgFontFaceId(args.vg, 0);
|
||||||
|
nvgFontSize(args.vg, 11);
|
||||||
|
nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
|
||||||
|
|
||||||
|
nvgText(args.vg, box.size.x * 0.333f + 1.0f, startY + padding * 0 - 4.0f, "V/Oct", nullptr);
|
||||||
|
nvgText(args.vg, box.size.x * 0.333f + 1.0f, startY + padding * 1 - 4.0f, "Gate", nullptr);
|
||||||
|
nvgText(args.vg, box.size.x * 0.333f + 1.0f, startY + padding * 2 - 4.0f, "Vel", nullptr);
|
||||||
|
nvgText(args.vg, box.size.x * 0.333f + 1.0f, startY + padding * 3 - 4.0f, "Aft", nullptr);
|
||||||
|
nvgText(args.vg, box.size.x * 0.333f + 1.0f, startY + padding * 4 - 4.0f, "Pb", nullptr);
|
||||||
|
nvgText(args.vg, box.size.x * 0.333f + 1.0f, startY + padding * 5 - 4.0f, "MW", nullptr);
|
||||||
|
|
||||||
|
ModuleWidgetWithSideScrews::draw(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendContextMenu(Menu* const menu) override
|
||||||
|
{
|
||||||
|
menu->addChild(new MenuSeparator);
|
||||||
|
|
||||||
|
menu->addChild(createBoolPtrMenuItem("Smooth pitch/mod wheel", "", &module->smooth));
|
||||||
|
|
||||||
|
struct ChannelItem : MenuItem {
|
||||||
|
CardinalExpanderForOutputMIDI* module;
|
||||||
|
Menu* createChildMenu() override {
|
||||||
|
Menu* menu = new Menu;
|
||||||
|
for (int c = 0; c <= 16; c++) {
|
||||||
|
menu->addChild(createCheckMenuItem((c == 0) ? "All" : string::f("%d", c), "",
|
||||||
|
[=]() {return module->channel == c;},
|
||||||
|
[=]() {module->channel = c;}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ChannelItem* const channelItem = new ChannelItem;
|
||||||
|
channelItem->text = "MIDI channel";
|
||||||
|
channelItem->rightText = (module->channel ? string::f("%d", module->channel) : "All") + " " + RIGHT_ARROW;
|
||||||
|
channelItem->module = module;
|
||||||
|
menu->addChild(channelItem);
|
||||||
|
|
||||||
|
struct PolyphonyChannelItem : MenuItem {
|
||||||
|
CardinalExpanderForOutputMIDI* module;
|
||||||
|
Menu* createChildMenu() override {
|
||||||
|
Menu* menu = new Menu;
|
||||||
|
for (int c = 1; c <= 16; c++) {
|
||||||
|
menu->addChild(createCheckMenuItem((c == 1) ? "Monophonic" : string::f("%d", c), "",
|
||||||
|
[=]() {return module->channels == c;},
|
||||||
|
[=]() {module->setChannels(c);}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PolyphonyChannelItem* const polyphonyChannelItem = new PolyphonyChannelItem;
|
||||||
|
polyphonyChannelItem->text = "Polyphony channels";
|
||||||
|
polyphonyChannelItem->rightText = string::f("%d", module->channels) + " " + RIGHT_ARROW;
|
||||||
|
polyphonyChannelItem->module = module;
|
||||||
|
menu->addChild(polyphonyChannelItem);
|
||||||
|
|
||||||
|
menu->addChild(createIndexPtrSubmenuItem("Polyphony mode", {
|
||||||
|
"Rotate",
|
||||||
|
"Reuse",
|
||||||
|
"Reset",
|
||||||
|
"MPE",
|
||||||
|
}, &module->polyMode));
|
||||||
|
|
||||||
|
menu->addChild(createMenuItem("Panic", "",
|
||||||
|
[=]() { module->panic(); }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Model* modelExpanderOutputMIDI = createModel<CardinalExpanderForOutputMIDI, CardinalExpanderForOutputMIDIWidget>("ExpanderOutputMIDI");
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -238,7 +238,7 @@ struct HostMIDI : Module {
|
||||||
outputs[GATE_OUTPUT].setChannels(channels);
|
outputs[GATE_OUTPUT].setChannels(channels);
|
||||||
outputs[VELOCITY_OUTPUT].setChannels(channels);
|
outputs[VELOCITY_OUTPUT].setChannels(channels);
|
||||||
outputs[AFTERTOUCH_OUTPUT].setChannels(channels);
|
outputs[AFTERTOUCH_OUTPUT].setChannels(channels);
|
||||||
outputs[RETRIGGER_OUTPUT].setChannels(channels);
|
|
||||||
for (int c = 0; c < channels; c++) {
|
for (int c = 0; c < channels; c++) {
|
||||||
outputs[PITCH_OUTPUT].setVoltage((notes[c] - 60.f) / 12.f, c);
|
outputs[PITCH_OUTPUT].setVoltage((notes[c] - 60.f) / 12.f, c);
|
||||||
outputs[GATE_OUTPUT].setVoltage(gates[c] ? 10.f : 0.f, c);
|
outputs[GATE_OUTPUT].setVoltage(gates[c] ? 10.f : 0.f, c);
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,7 @@ struct IldaeilModule : Module {
|
||||||
float audioDataOut2[BUFFER_SIZE];
|
float audioDataOut2[BUFFER_SIZE];
|
||||||
unsigned audioDataFill = 0;
|
unsigned audioDataFill = 0;
|
||||||
int64_t lastBlockFrame = -1;
|
int64_t lastBlockFrame = -1;
|
||||||
|
CardinalExpanderFromCarlaMIDIToCV* midiOutExpander = nullptr;
|
||||||
|
|
||||||
volatile bool resetMeterIn = true;
|
volatile bool resetMeterIn = true;
|
||||||
volatile bool resetMeterOut = true;
|
volatile bool resetMeterOut = true;
|
||||||
|
|
@ -422,6 +423,11 @@ struct IldaeilModule : Module {
|
||||||
midiEventCount = 0;
|
midiEventCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((midiOutExpander = rightExpander.module != nullptr && rightExpander.module->model == modelExpanderOutputMIDI
|
||||||
|
? static_cast<CardinalExpanderFromCarlaMIDIToCV*>(rightExpander.module)
|
||||||
|
: nullptr))
|
||||||
|
midiOutExpander->midiEventCount = 0;
|
||||||
|
|
||||||
audioDataFill = 0;
|
audioDataFill = 0;
|
||||||
float* ins[2] = { audioDataIn1, audioDataIn2 };
|
float* ins[2] = { audioDataIn1, audioDataIn2 };
|
||||||
float* outs[2] = { audioDataOut1, audioDataOut2 };
|
float* outs[2] = { audioDataOut1, audioDataOut2 };
|
||||||
|
|
@ -447,6 +453,7 @@ struct IldaeilModule : Module {
|
||||||
void onReset() override
|
void onReset() override
|
||||||
{
|
{
|
||||||
resetMeterIn = resetMeterOut = true;
|
resetMeterIn = resetMeterOut = true;
|
||||||
|
midiOutExpander = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSampleRateChange(const SampleRateChangeEvent& e) override
|
void onSampleRateChange(const SampleRateChangeEvent& e) override
|
||||||
|
|
@ -455,6 +462,7 @@ struct IldaeilModule : Module {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
resetMeterIn = resetMeterOut = true;
|
resetMeterIn = resetMeterOut = true;
|
||||||
|
midiOutExpander = nullptr;
|
||||||
|
|
||||||
fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
|
fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
|
||||||
fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
|
fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
|
||||||
|
|
@ -491,6 +499,16 @@ static const NativeTimeInfo* host_get_time_info(const NativeHostHandle handle)
|
||||||
|
|
||||||
static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
|
static bool host_write_midi_event(const NativeHostHandle handle, const NativeMidiEvent* const event)
|
||||||
{
|
{
|
||||||
|
if (CardinalExpanderFromCarlaMIDIToCV* const expander = static_cast<IldaeilModule*>(handle)->midiOutExpander)
|
||||||
|
{
|
||||||
|
if (expander->midiEventCount == CardinalExpanderFromCarlaMIDIToCV::MAX_MIDI_EVENTS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NativeMidiEvent& expanderEvent(expander->midiEvents[expander->midiEventCount++]);
|
||||||
|
carla_copyStruct(expanderEvent, *event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1595,6 +1613,7 @@ struct IldaeilNanoMeterOut : NanoMeter {
|
||||||
|
|
||||||
struct IldaeilModuleWidget : ModuleWidgetWithSideScrews<26> {
|
struct IldaeilModuleWidget : ModuleWidgetWithSideScrews<26> {
|
||||||
bool hasLeftSideExpander = false;
|
bool hasLeftSideExpander = false;
|
||||||
|
bool hasRightSideExpander = false;
|
||||||
IldaeilWidget* ildaeilWidget = nullptr;
|
IldaeilWidget* ildaeilWidget = nullptr;
|
||||||
|
|
||||||
IldaeilModuleWidget(IldaeilModule* const module)
|
IldaeilModuleWidget(IldaeilModule* const module)
|
||||||
|
|
@ -1631,7 +1650,6 @@ struct IldaeilModuleWidget : ModuleWidgetWithSideScrews<26> {
|
||||||
void draw(const DrawArgs& args) override
|
void draw(const DrawArgs& args) override
|
||||||
{
|
{
|
||||||
drawBackground(args.vg);
|
drawBackground(args.vg);
|
||||||
drawOutputJacksArea(args.vg, 2);
|
|
||||||
|
|
||||||
if (hasLeftSideExpander)
|
if (hasLeftSideExpander)
|
||||||
{
|
{
|
||||||
|
|
@ -1652,6 +1670,36 @@ struct IldaeilModuleWidget : ModuleWidgetWithSideScrews<26> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasRightSideExpander)
|
||||||
|
{
|
||||||
|
// i == 0
|
||||||
|
nvgBeginPath(args.vg);
|
||||||
|
nvgRect(args.vg, box.size.x - 19, 90 - 19, 18, 49 - 4);
|
||||||
|
nvgFillColor(args.vg, nvgRGB(0xd0, 0xd0, 0xd0));
|
||||||
|
nvgFill(args.vg);
|
||||||
|
|
||||||
|
// gradient for i > 0
|
||||||
|
nvgBeginPath(args.vg);
|
||||||
|
nvgRect(args.vg, box.size.x - 19, 90 + 49 - 23, 18, 49 * 5);
|
||||||
|
nvgFillPaint(args.vg, nvgLinearGradient(args.vg,
|
||||||
|
box.size.x - 19, 0, box.size.x - 1, 0,
|
||||||
|
nvgRGBA(0xd0, 0xd0, 0xd0, 0), nvgRGB(0xd0, 0xd0, 0xd0)));
|
||||||
|
nvgFill(args.vg);
|
||||||
|
|
||||||
|
for (int i=1; i<6; ++i)
|
||||||
|
{
|
||||||
|
const float y = 90 + 49 * i - 23;
|
||||||
|
const int col1 = 0x18 + static_cast<int>((y / box.size.y) * (0x21 - 0x18) + 0.5f);
|
||||||
|
const int col2 = 0x19 + static_cast<int>((y / box.size.y) * (0x22 - 0x19) + 0.5f);
|
||||||
|
nvgBeginPath(args.vg);
|
||||||
|
nvgRect(args.vg, box.size.x - 19, y, 18, 4);
|
||||||
|
nvgFillColor(args.vg, nvgRGB(col1, col2, col2));
|
||||||
|
nvgFill(args.vg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawOutputJacksArea(args.vg, 2);
|
||||||
|
|
||||||
ModuleWidgetWithSideScrews<26>::draw(args);
|
ModuleWidgetWithSideScrews<26>::draw(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1661,6 +1709,10 @@ struct IldaeilModuleWidget : ModuleWidgetWithSideScrews<26> {
|
||||||
&& module->leftExpander.module != nullptr
|
&& module->leftExpander.module != nullptr
|
||||||
&& module->leftExpander.module->model == modelExpanderInputMIDI;
|
&& module->leftExpander.module->model == modelExpanderInputMIDI;
|
||||||
|
|
||||||
|
hasRightSideExpander = module != nullptr
|
||||||
|
&& module->rightExpander.module != nullptr
|
||||||
|
&& module->rightExpander.module->model == modelExpanderOutputMIDI;
|
||||||
|
|
||||||
ModuleWidgetWithSideScrews<26>::step();
|
ModuleWidgetWithSideScrews<26>::step();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ extern Model* modelAudioFile;
|
||||||
extern Model* modelCarla;
|
extern Model* modelCarla;
|
||||||
extern Model* modelCardinalBlank;
|
extern Model* modelCardinalBlank;
|
||||||
extern Model* modelExpanderInputMIDI;
|
extern Model* modelExpanderInputMIDI;
|
||||||
|
extern Model* modelExpanderOutputMIDI;
|
||||||
extern Model* modelGlBars;
|
extern Model* modelGlBars;
|
||||||
extern Model* modelHostAudio2;
|
extern Model* modelHostAudio2;
|
||||||
extern Model* modelHostAudio8;
|
extern Model* modelHostAudio8;
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ PLUGIN_FILES = plugins.cpp
|
||||||
|
|
||||||
PLUGIN_FILES += Cardinal/src/Blank.cpp
|
PLUGIN_FILES += Cardinal/src/Blank.cpp
|
||||||
PLUGIN_FILES += Cardinal/src/ExpanderInputMIDI.cpp
|
PLUGIN_FILES += Cardinal/src/ExpanderInputMIDI.cpp
|
||||||
|
PLUGIN_FILES += Cardinal/src/ExpanderOutputMIDI.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
|
||||||
|
|
|
||||||
|
|
@ -675,6 +675,7 @@ static void initStatic__Cardinal()
|
||||||
{
|
{
|
||||||
p->addModel(modelCardinalBlank);
|
p->addModel(modelCardinalBlank);
|
||||||
p->addModel(modelExpanderInputMIDI);
|
p->addModel(modelExpanderInputMIDI);
|
||||||
|
p->addModel(modelExpanderOutputMIDI);
|
||||||
p->addModel(modelGlBars);
|
p->addModel(modelGlBars);
|
||||||
p->addModel(modelHostAudio2);
|
p->addModel(modelHostAudio2);
|
||||||
p->addModel(modelHostAudio8);
|
p->addModel(modelHostAudio8);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue