Very crude and dirty first host midi implementation
Signed-off-by: falkTX <falktx@falktx.com>
This commit is contained in:
parent
810928190c
commit
b863d0e54c
6 changed files with 758 additions and 450 deletions
|
|
@ -15,26 +15,613 @@
|
||||||
* For a full copy of the GNU General Public License see the LICENSE file.
|
* For a full copy of the GNU General Public License see the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains a substantial amount of code from VCVRack's core/CV_MIDI.cpp and 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.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "plugincontext.hpp"
|
#include "plugincontext.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
USE_NAMESPACE_DISTRHO;
|
USE_NAMESPACE_DISTRHO;
|
||||||
|
|
||||||
struct HostMIDI : Module {
|
struct HostMIDI : Module {
|
||||||
|
enum ParamIds {
|
||||||
|
NUM_PARAMS
|
||||||
|
};
|
||||||
|
enum InputIds {
|
||||||
|
PITCH_INPUT,
|
||||||
|
GATE_INPUT,
|
||||||
|
VELOCITY_INPUT,
|
||||||
|
AFTERTOUCH_INPUT,
|
||||||
|
PITCHBEND_INPUT,
|
||||||
|
MODWHEEL_INPUT,
|
||||||
|
CLK_INPUT, // RETRIGGER_OUTPUT
|
||||||
|
VOL_INPUT, // CLOCK_OUTPUT
|
||||||
|
PAN_INPUT, // CLOCK_DIV_OUTPUT
|
||||||
|
START_INPUT,
|
||||||
|
STOP_INPUT,
|
||||||
|
CONTINUE_INPUT,
|
||||||
|
NUM_INPUTS
|
||||||
|
};
|
||||||
|
enum OutputIds {
|
||||||
|
PITCH_OUTPUT,
|
||||||
|
GATE_OUTPUT,
|
||||||
|
VELOCITY_OUTPUT,
|
||||||
|
AFTERTOUCH_OUTPUT,
|
||||||
|
PITCHBEND_OUTPUT,
|
||||||
|
MODWHEEL_OUTPUT,
|
||||||
|
RETRIGGER_OUTPUT, // CLK_INPUT
|
||||||
|
CLOCK_OUTPUT, // VOL_INPUT
|
||||||
|
CLOCK_DIV_OUTPUT, // PAN_INPUT
|
||||||
|
START_OUTPUT,
|
||||||
|
STOP_OUTPUT,
|
||||||
|
CONTINUE_OUTPUT,
|
||||||
|
NUM_OUTPUTS
|
||||||
|
};
|
||||||
|
enum LightIds {
|
||||||
|
NUM_LIGHTS
|
||||||
|
};
|
||||||
|
|
||||||
CardinalPluginContext* const pcontext;
|
CardinalPluginContext* const pcontext;
|
||||||
|
|
||||||
|
struct MidiInput {
|
||||||
|
CardinalPluginContext* const pcontext;
|
||||||
|
midi::Message converterMsg;
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
dsp::PulseGenerator startPulse;
|
||||||
|
dsp::PulseGenerator stopPulse;
|
||||||
|
dsp::PulseGenerator continuePulse;
|
||||||
|
|
||||||
|
MidiInput(CardinalPluginContext* const pc)
|
||||||
|
: pcontext(pc)
|
||||||
|
{
|
||||||
|
converterMsg.bytes.resize(0xff);
|
||||||
|
heldNotes.reserve(128);
|
||||||
|
for (int c = 0; c < 16; c++) {
|
||||||
|
pwFilters[c].setTau(1 / 30.f);
|
||||||
|
modFilters[c].setTau(1 / 30.f);
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
smooth = true;
|
||||||
|
channels = 1;
|
||||||
|
polyMode = ROTATE_MODE;
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resets performance state */
|
||||||
|
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 process(const ProcessArgs& args, std::vector<rack::engine::Output>& outputs)
|
||||||
|
{
|
||||||
|
DISTRHO_SAFE_ASSERT_RETURN(args.frame >= 0,);
|
||||||
|
|
||||||
|
const int64_t blockFrame = pcontext->engine->getBlockFrame();
|
||||||
|
DISTRHO_SAFE_ASSERT_RETURN(blockFrame >= 0,);
|
||||||
|
DISTRHO_SAFE_ASSERT_RETURN(args.frame >= blockFrame,);
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (lastBlockFrame != blockFrame)
|
||||||
|
{
|
||||||
|
lastBlockFrame = blockFrame;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const uint32_t frame = static_cast<uint32_t>(args.frame - blockFrame);
|
||||||
|
|
||||||
|
for (uint32_t i=0; i<pcontext->midiEventCount; ++i)
|
||||||
|
{
|
||||||
|
const MidiEvent& midiEvent(pcontext->midiEvents[i]);
|
||||||
|
|
||||||
|
if (midiEvent.frame < frame)
|
||||||
|
continue;
|
||||||
|
if (midiEvent.frame > frame)
|
||||||
|
break;
|
||||||
|
|
||||||
|
const uint8_t* data;
|
||||||
|
|
||||||
|
if (midiEvent.size > MidiEvent::kDataSize)
|
||||||
|
{
|
||||||
|
data = midiEvent.dataExt;
|
||||||
|
converterMsg.bytes.resize(midiEvent.size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = midiEvent.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
converterMsg.frame = midiEvent.frame;
|
||||||
|
std::memcpy(converterMsg.bytes.data(), data, midiEvent.size);
|
||||||
|
|
||||||
|
processMessage(converterMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs[PITCH_OUTPUT].setChannels(channels);
|
||||||
|
outputs[GATE_OUTPUT].setChannels(channels);
|
||||||
|
outputs[VELOCITY_OUTPUT].setChannels(channels);
|
||||||
|
outputs[AFTERTOUCH_OUTPUT].setChannels(channels);
|
||||||
|
outputs[RETRIGGER_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs[START_OUTPUT].setVoltage(startPulse.process(args.sampleTime) ? 10.f : 0.f);
|
||||||
|
outputs[STOP_OUTPUT].setVoltage(stopPulse.process(args.sampleTime) ? 10.f : 0.f);
|
||||||
|
outputs[CONTINUE_OUTPUT].setVoltage(continuePulse.process(args.sampleTime) ? 10.f : 0.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;
|
||||||
|
case 0xf: {
|
||||||
|
processSystem(msg);
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processSystem(const midi::Message& msg)
|
||||||
|
{
|
||||||
|
switch (msg.getChannel())
|
||||||
|
{
|
||||||
|
// Start
|
||||||
|
case 0xa:
|
||||||
|
startPulse.trigger(1e-3);
|
||||||
|
break;
|
||||||
|
// Continue
|
||||||
|
case 0xb:
|
||||||
|
continuePulse.trigger(1e-3);
|
||||||
|
break;
|
||||||
|
// Stop
|
||||||
|
case 0xc:
|
||||||
|
stopPulse.trigger(1e-3);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
} midiInput;
|
||||||
|
|
||||||
|
struct MidiOutput : dsp::MidiGenerator<PORT_MAX_CHANNELS> {
|
||||||
|
CardinalPluginContext* const pcontext;
|
||||||
|
dsp::Timer rateLimiterTimer;
|
||||||
|
|
||||||
|
MidiOutput(CardinalPluginContext* const pc)
|
||||||
|
: pcontext(pc) {}
|
||||||
|
|
||||||
|
void onMessage(const midi::Message& message) override
|
||||||
|
{
|
||||||
|
pcontext->writeMidiMessage(message);
|
||||||
|
}
|
||||||
|
} midiOutput;
|
||||||
|
|
||||||
HostMIDI()
|
HostMIDI()
|
||||||
: pcontext(static_cast<CardinalPluginContext*>(APP))
|
: pcontext(static_cast<CardinalPluginContext*>(APP)),
|
||||||
|
midiInput(pcontext),
|
||||||
|
midiOutput(pcontext)
|
||||||
{
|
{
|
||||||
if (pcontext == nullptr)
|
if (pcontext == nullptr)
|
||||||
throw rack::Exception("Plugin context is null");
|
throw rack::Exception("Plugin context is null");
|
||||||
|
|
||||||
config(0, 9, 9, 0);
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
|
||||||
|
configInput(PITCH_INPUT, "1V/octave pitch");
|
||||||
|
configInput(GATE_INPUT, "Gate");
|
||||||
|
configInput(VELOCITY_INPUT, "Velocity");
|
||||||
|
configInput(AFTERTOUCH_INPUT, "Aftertouch");
|
||||||
|
configInput(PITCHBEND_INPUT, "Pitchbend");
|
||||||
|
configInput(MODWHEEL_INPUT, "Mod wheel");
|
||||||
|
configInput(CLK_INPUT, "Clock");
|
||||||
|
configInput(VOL_INPUT, "Volume");
|
||||||
|
configInput(PAN_INPUT, "Pan");
|
||||||
|
configInput(START_INPUT, "Start trigger");
|
||||||
|
configInput(STOP_INPUT, "Stop trigger");
|
||||||
|
configInput(CONTINUE_INPUT, "Continue trigger");
|
||||||
|
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");
|
||||||
|
configOutput(RETRIGGER_OUTPUT, "Retrigger");
|
||||||
|
configOutput(CLOCK_OUTPUT, "Clock");
|
||||||
|
configOutput(CLOCK_DIV_OUTPUT, "Clock divider");
|
||||||
|
configOutput(START_OUTPUT, "Start trigger");
|
||||||
|
configOutput(STOP_OUTPUT, "Stop trigger");
|
||||||
|
configOutput(CONTINUE_OUTPUT, "Continue trigger");
|
||||||
}
|
}
|
||||||
|
|
||||||
void process(const ProcessArgs&) override
|
void process(const ProcessArgs& args) override
|
||||||
{}
|
{
|
||||||
|
midiInput.process(args, outputs);
|
||||||
|
|
||||||
|
// MIDI baud rate is 31250 b/s, or 3125 B/s.
|
||||||
|
// CC messages are 3 bytes, so we can send a maximum of 1041 CC messages per second.
|
||||||
|
// Since multiple CCs can be generated, play it safe and limit the CC rate to 200 Hz.
|
||||||
|
static constexpr const float rateLimiterPeriod = 1 / 200.f;
|
||||||
|
bool rateLimiterTriggered = (midiOutput.rateLimiterTimer.process(args.sampleTime) >= rateLimiterPeriod);
|
||||||
|
if (rateLimiterTriggered)
|
||||||
|
midiOutput.rateLimiterTimer.time -= rateLimiterPeriod;
|
||||||
|
|
||||||
|
midiOutput.setFrame(args.frame);
|
||||||
|
|
||||||
|
for (int c = 0; c < inputs[PITCH_INPUT].getChannels(); c++) {
|
||||||
|
int vel = (int) std::round(inputs[VELOCITY_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127);
|
||||||
|
vel = clamp(vel, 0, 127);
|
||||||
|
midiOutput.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;
|
||||||
|
midiOutput.setNoteGate(note, gate, c);
|
||||||
|
|
||||||
|
int aft = (int) std::round(inputs[AFTERTOUCH_INPUT].getPolyVoltage(c) / 10.f * 127);
|
||||||
|
aft = clamp(aft, 0, 127);
|
||||||
|
midiOutput.setKeyPressure(aft, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rateLimiterTriggered) {
|
||||||
|
int pw = (int) std::round((inputs[PITCHBEND_INPUT].getVoltage() + 5.f) / 10.f * 0x4000);
|
||||||
|
pw = clamp(pw, 0, 0x3fff);
|
||||||
|
midiOutput.setPitchWheel(pw);
|
||||||
|
|
||||||
|
int mw = (int) std::round(inputs[MODWHEEL_INPUT].getVoltage() / 10.f * 127);
|
||||||
|
mw = clamp(mw, 0, 127);
|
||||||
|
midiOutput.setModWheel(mw);
|
||||||
|
|
||||||
|
/* unused
|
||||||
|
int vol = (int) std::round(inputs[VOL_INPUT].getNormalVoltage(10.f) / 10.f * 127);
|
||||||
|
vol = clamp(vol, 0, 127);
|
||||||
|
midiOutput.setVolume(vol);
|
||||||
|
|
||||||
|
int pan = (int) std::round((inputs[PAN_INPUT].getVoltage() + 5.f) / 10.f * 127);
|
||||||
|
pan = clamp(pan, 0, 127);
|
||||||
|
midiOutput.setPan(pan);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unused
|
||||||
|
bool clk = inputs[CLK_INPUT].getVoltage() >= 1.f;
|
||||||
|
midiOutput.setClock(clk);
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool start = inputs[START_INPUT].getVoltage() >= 1.f;
|
||||||
|
midiOutput.setStart(start);
|
||||||
|
|
||||||
|
bool stop = inputs[STOP_INPUT].getVoltage() >= 1.f;
|
||||||
|
midiOutput.setStop(stop);
|
||||||
|
|
||||||
|
bool cont = inputs[CONTINUE_INPUT].getVoltage() >= 1.f;
|
||||||
|
midiOutput.setContinue(cont);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t* dataToJson() override
|
||||||
|
{
|
||||||
|
json_t* rootJ = json_object();
|
||||||
|
json_object_set_new(rootJ, "smooth", json_boolean(midiInput.smooth));
|
||||||
|
json_object_set_new(rootJ, "channels", json_integer(midiInput.channels));
|
||||||
|
json_object_set_new(rootJ, "polyMode", json_integer(midiInput.polyMode));
|
||||||
|
// Saving/restoring pitch and mod doesn't make much sense for MPE.
|
||||||
|
if (midiInput.polyMode != MidiInput::MPE_MODE) {
|
||||||
|
json_object_set_new(rootJ, "lastPitch", json_integer(midiInput.pws[0]));
|
||||||
|
json_object_set_new(rootJ, "lastMod", json_integer(midiInput.mods[0]));
|
||||||
|
}
|
||||||
|
return rootJ;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dataFromJson(json_t* rootJ) override
|
||||||
|
{
|
||||||
|
json_t* smoothJ = json_object_get(rootJ, "smooth");
|
||||||
|
if (smoothJ)
|
||||||
|
midiInput.smooth = json_boolean_value(smoothJ);
|
||||||
|
|
||||||
|
json_t* channelsJ = json_object_get(rootJ, "channels");
|
||||||
|
if (channelsJ)
|
||||||
|
midiInput.setChannels(json_integer_value(channelsJ));
|
||||||
|
|
||||||
|
json_t* polyModeJ = json_object_get(rootJ, "polyMode");
|
||||||
|
if (polyModeJ)
|
||||||
|
midiInput.polyMode = (MidiInput::PolyMode) json_integer_value(polyModeJ);
|
||||||
|
|
||||||
|
json_t* lastPitchJ = json_object_get(rootJ, "lastPitch");
|
||||||
|
if (lastPitchJ)
|
||||||
|
midiInput.pws[0] = json_integer_value(lastPitchJ);
|
||||||
|
|
||||||
|
json_t* lastModJ = json_object_get(rootJ, "lastMod");
|
||||||
|
if (lastModJ)
|
||||||
|
midiInput.mods[0] = json_integer_value(lastModJ);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -44,7 +631,7 @@ struct HostMIDIWidget : ModuleWidget {
|
||||||
static constexpr const float startX_Out = 96.0f;
|
static constexpr const float startX_Out = 96.0f;
|
||||||
static constexpr const float startY = 74.0f;
|
static constexpr const float startY = 74.0f;
|
||||||
static constexpr const float padding = 29.0f;
|
static constexpr const float padding = 29.0f;
|
||||||
static constexpr const float middleX = startX_In + (startX_Out - startX_In) * 0.5f + padding * 0.25f;
|
static constexpr const float middleX = startX_In + (startX_Out - startX_In) * 0.5f + padding * 0.35f;
|
||||||
|
|
||||||
HostMIDI* const module;
|
HostMIDI* const module;
|
||||||
|
|
||||||
|
|
@ -59,19 +646,33 @@ struct HostMIDIWidget : ModuleWidget {
|
||||||
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)));
|
||||||
addChild(createWidget<ScrewBlack>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
|
addChild(createWidget<ScrewBlack>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
|
||||||
|
|
||||||
for (uint i=0; i<9; ++i)
|
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * 0), m, HostMIDI::PITCH_INPUT));
|
||||||
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * i), m, i));
|
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * 1), m, HostMIDI::GATE_INPUT));
|
||||||
|
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * 2), m, HostMIDI::VELOCITY_INPUT));
|
||||||
|
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * 3), m, HostMIDI::AFTERTOUCH_INPUT));
|
||||||
|
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * 4), m, HostMIDI::PITCHBEND_INPUT));
|
||||||
|
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * 5), m, HostMIDI::MODWHEEL_INPUT));
|
||||||
|
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * 6), m, HostMIDI::START_INPUT));
|
||||||
|
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * 7), m, HostMIDI::STOP_INPUT));
|
||||||
|
addInput(createInput<PJ301MPort>(Vec(startX_In, startY + padding * 8), m, HostMIDI::CONTINUE_INPUT));
|
||||||
|
|
||||||
for (uint i=0; i<9; ++i)
|
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * 0), m, HostMIDI::PITCH_OUTPUT));
|
||||||
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * i), m, i));
|
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * 1), m, HostMIDI::GATE_OUTPUT));
|
||||||
|
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * 2), m, HostMIDI::VELOCITY_OUTPUT));
|
||||||
|
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * 3), m, HostMIDI::AFTERTOUCH_OUTPUT));
|
||||||
|
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * 4), m, HostMIDI::PITCHBEND_OUTPUT));
|
||||||
|
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * 5), m, HostMIDI::MODWHEEL_OUTPUT));
|
||||||
|
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * 6), m, HostMIDI::START_OUTPUT));
|
||||||
|
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * 7), m, HostMIDI::STOP_OUTPUT));
|
||||||
|
addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY + padding * 8), m, HostMIDI::CONTINUE_OUTPUT));
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawTextLine(NVGcontext* const vg, const float offsetX, const uint posY, const char* const text)
|
void drawTextLine(NVGcontext* const vg, const uint posY, const char* const text)
|
||||||
{
|
{
|
||||||
const float y = startY + posY * padding;
|
const float y = startY + posY * padding;
|
||||||
nvgBeginPath(vg);
|
nvgBeginPath(vg);
|
||||||
nvgFillColor(vg, color::WHITE);
|
nvgFillColor(vg, color::WHITE);
|
||||||
nvgText(vg, middleX + offsetX, y + 16, text, nullptr);
|
nvgText(vg, middleX, y + 16, text, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const DrawArgs& args) override
|
void draw(const DrawArgs& args) override
|
||||||
|
|
@ -87,22 +688,61 @@ struct HostMIDIWidget : ModuleWidget {
|
||||||
nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
|
nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
|
||||||
|
|
||||||
nvgBeginPath(args.vg);
|
nvgBeginPath(args.vg);
|
||||||
nvgRoundedRect(args.vg, startX_Out - 4.0f, startY - 2.0f, padding, padding * 9, 4);
|
nvgRoundedRect(args.vg, startX_Out - 2.5f, startY - 2.0f, padding, padding * 9, 4);
|
||||||
nvgFillColor(args.vg, nvgRGB(0xd0, 0xd0, 0xd0));
|
nvgFillColor(args.vg, nvgRGB(0xd0, 0xd0, 0xd0));
|
||||||
nvgFill(args.vg);
|
nvgFill(args.vg);
|
||||||
|
|
||||||
drawTextLine(args.vg, 0.0f, 0, "V/Oct");
|
drawTextLine(args.vg, 0, "V/Oct");
|
||||||
drawTextLine(args.vg, 0.0f, 1, "Gate");
|
drawTextLine(args.vg, 1, "Gate");
|
||||||
drawTextLine(args.vg, 0.0f, 2, "Vel");
|
drawTextLine(args.vg, 2, "Velocity");
|
||||||
drawTextLine(args.vg, 0.0f, 3, "Aft");
|
drawTextLine(args.vg, 3, "Aftertouch");
|
||||||
drawTextLine(args.vg, 0.0f, 4, "PW");
|
drawTextLine(args.vg, 4, "Pitchbend");
|
||||||
drawTextLine(args.vg, 0.0f, 5, "MW");
|
drawTextLine(args.vg, 5, "Mod Wheel");
|
||||||
drawTextLine(args.vg, 0.0f, 6, "Start");
|
drawTextLine(args.vg, 6, "Start");
|
||||||
drawTextLine(args.vg, 0.0f, 7, "Stop");
|
drawTextLine(args.vg, 7, "Stop");
|
||||||
drawTextLine(args.vg, 0.0f, 8, "Cont");
|
drawTextLine(args.vg, 8, "Cont");
|
||||||
|
|
||||||
ModuleWidget::draw(args);
|
ModuleWidget::draw(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void appendContextMenu(Menu* const menu) override
|
||||||
|
{
|
||||||
|
menu->addChild(new MenuSeparator);
|
||||||
|
|
||||||
|
/*
|
||||||
|
menu->addChild(createBoolPtrMenuItem("Smooth pitch/mod wheel", "", &module->smooth));
|
||||||
|
|
||||||
|
struct ChannelItem : MenuItem {
|
||||||
|
MIDI_CV* 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ChannelItem* channelItem = new ChannelItem;
|
||||||
|
channelItem->text = "Polyphony channels";
|
||||||
|
channelItem->rightText = string::f("%d", module->channels) + " " + RIGHT_ARROW;
|
||||||
|
channelItem->module = module;
|
||||||
|
menu->addChild(channelItem);
|
||||||
|
|
||||||
|
menu->addChild(createIndexPtrSubmenuItem("Polyphony mode", {
|
||||||
|
"Rotate",
|
||||||
|
"Reuse",
|
||||||
|
"Reset",
|
||||||
|
"MPE",
|
||||||
|
}, &module->polyMode));
|
||||||
|
|
||||||
|
menu->addChild(createMenuItem("Panic", "",
|
||||||
|
[=]() {module->panic();}
|
||||||
|
));
|
||||||
|
*/
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,14 @@ enum CardinalVariant {
|
||||||
class Plugin;
|
class Plugin;
|
||||||
class UI;
|
class UI;
|
||||||
|
|
||||||
|
struct MidiEvent {
|
||||||
|
static const uint32_t kDataSize = 4;
|
||||||
|
uint32_t frame;
|
||||||
|
uint32_t size;
|
||||||
|
uint8_t data[kDataSize];
|
||||||
|
const uint8_t* dataExt;
|
||||||
|
};
|
||||||
|
|
||||||
struct CardinalPluginContext : rack::Context {
|
struct CardinalPluginContext : rack::Context {
|
||||||
uint32_t bufferSize;
|
uint32_t bufferSize;
|
||||||
double sampleRate;
|
double sampleRate;
|
||||||
|
|
@ -55,11 +63,14 @@ struct CardinalPluginContext : rack::Context {
|
||||||
uintptr_t nativeWindowId;
|
uintptr_t nativeWindowId;
|
||||||
const float* const* dataIns;
|
const float* const* dataIns;
|
||||||
float** dataOuts;
|
float** dataOuts;
|
||||||
|
const MidiEvent* midiEvents;
|
||||||
|
uint32_t midiEventCount;
|
||||||
Plugin* const plugin;
|
Plugin* const plugin;
|
||||||
#ifndef HEADLESS
|
#ifndef HEADLESS
|
||||||
UI* ui;
|
UI* ui;
|
||||||
#endif
|
#endif
|
||||||
CardinalPluginContext(Plugin* const p);
|
CardinalPluginContext(Plugin* const p);
|
||||||
|
void writeMidiMessage(const rack::midi::Message& message);
|
||||||
#ifndef HEADLESS
|
#ifndef HEADLESS
|
||||||
bool addIdleCallback(IdleCallback* cb) const;
|
bool addIdleCallback(IdleCallback* cb) const;
|
||||||
void removeIdleCallback(IdleCallback* cb) const;
|
void removeIdleCallback(IdleCallback* cb) const;
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
#include "DistrhoPluginUtils.hpp"
|
#include "DistrhoPluginUtils.hpp"
|
||||||
#include "PluginDriver.hpp"
|
#include "PluginContext.hpp"
|
||||||
#include "extra/Base64.hpp"
|
#include "extra/Base64.hpp"
|
||||||
#include "extra/SharedResourcePointer.hpp"
|
#include "extra/SharedResourcePointer.hpp"
|
||||||
|
|
||||||
|
|
@ -182,9 +182,6 @@ struct Initializer
|
||||||
"Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str());
|
"Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
INFO("Initializing midi driver");
|
|
||||||
midi::addDriver(0, new CardinalMidiDriver);
|
|
||||||
|
|
||||||
INFO("Initializing plugins");
|
INFO("Initializing plugins");
|
||||||
plugin::initStaticPlugins();
|
plugin::initStaticPlugins();
|
||||||
|
|
||||||
|
|
@ -315,6 +312,66 @@ struct Initializer
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void CardinalPluginContext::writeMidiMessage(const rack::midi::Message& message)
|
||||||
|
{
|
||||||
|
const size_t size = message.bytes.size();
|
||||||
|
DISTRHO_SAFE_ASSERT_RETURN(size > 0,);
|
||||||
|
DISTRHO_SAFE_ASSERT_RETURN(message.frame >= 0,);
|
||||||
|
|
||||||
|
MidiEvent event;
|
||||||
|
event.frame = message.frame;
|
||||||
|
|
||||||
|
switch (message.bytes[0] & 0xF0)
|
||||||
|
{
|
||||||
|
case 0x80:
|
||||||
|
case 0x90:
|
||||||
|
case 0xA0:
|
||||||
|
case 0xB0:
|
||||||
|
case 0xE0:
|
||||||
|
event.size = 3;
|
||||||
|
break;
|
||||||
|
case 0xC0:
|
||||||
|
case 0xD0:
|
||||||
|
event.size = 2;
|
||||||
|
break;
|
||||||
|
case 0xF0:
|
||||||
|
switch (message.bytes[0] & 0x0F)
|
||||||
|
{
|
||||||
|
case 0x0:
|
||||||
|
case 0x4:
|
||||||
|
case 0x5:
|
||||||
|
case 0x7:
|
||||||
|
case 0x9:
|
||||||
|
case 0xD:
|
||||||
|
// unsupported
|
||||||
|
return;
|
||||||
|
case 0x1:
|
||||||
|
case 0x2:
|
||||||
|
case 0x3:
|
||||||
|
case 0xE:
|
||||||
|
event.size = 3;
|
||||||
|
break;
|
||||||
|
case 0x6:
|
||||||
|
case 0x8:
|
||||||
|
case 0xA:
|
||||||
|
case 0xB:
|
||||||
|
case 0xC:
|
||||||
|
case 0xF:
|
||||||
|
event.size = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
DISTRHO_SAFE_ASSERT_RETURN(size >= event.size,);
|
||||||
|
|
||||||
|
std::memcpy(event.data, message.bytes.data(), event.size);
|
||||||
|
|
||||||
|
plugin->writeMidiEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
struct ScopedContext {
|
struct ScopedContext {
|
||||||
ScopedContext(const CardinalBasePlugin* const plugin)
|
ScopedContext(const CardinalBasePlugin* const plugin)
|
||||||
{
|
{
|
||||||
|
|
@ -327,6 +384,7 @@ struct ScopedContext {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
class CardinalPlugin : public CardinalBasePlugin
|
class CardinalPlugin : public CardinalBasePlugin
|
||||||
|
|
@ -335,13 +393,8 @@ class CardinalPlugin : public CardinalBasePlugin
|
||||||
|
|
||||||
float** fAudioBufferCopy;
|
float** fAudioBufferCopy;
|
||||||
std::string fAutosavePath;
|
std::string fAutosavePath;
|
||||||
String fWindowSize;
|
|
||||||
|
|
||||||
// for base/context handling
|
|
||||||
CardinalMidiInputDevice** fCurrentMidiInputs;
|
|
||||||
CardinalMidiOutputDevice** fCurrentMidiOutputs;
|
|
||||||
uint64_t fPreviousFrame;
|
uint64_t fPreviousFrame;
|
||||||
Mutex fDeviceMutex;
|
String fWindowSize;
|
||||||
|
|
||||||
#ifndef HEADLESS
|
#ifndef HEADLESS
|
||||||
// real values, not VCV interpreted ones
|
// real values, not VCV interpreted ones
|
||||||
|
|
@ -353,8 +406,6 @@ public:
|
||||||
: CardinalBasePlugin(kModuleParameters + kWindowParameterCount, 0, kCardinalStateCount),
|
: CardinalBasePlugin(kModuleParameters + kWindowParameterCount, 0, kCardinalStateCount),
|
||||||
fInitializer(this),
|
fInitializer(this),
|
||||||
fAudioBufferCopy(nullptr),
|
fAudioBufferCopy(nullptr),
|
||||||
fCurrentMidiInputs(nullptr),
|
|
||||||
fCurrentMidiOutputs(nullptr),
|
|
||||||
fPreviousFrame(0)
|
fPreviousFrame(0)
|
||||||
{
|
{
|
||||||
#ifndef HEADLESS
|
#ifndef HEADLESS
|
||||||
|
|
@ -432,14 +483,6 @@ public:
|
||||||
delete context;
|
delete context;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
const MutexLocker cml(fDeviceMutex);
|
|
||||||
delete[] fCurrentMidiInputs;
|
|
||||||
fCurrentMidiInputs = nullptr;
|
|
||||||
delete[] fCurrentMidiOutputs;
|
|
||||||
fCurrentMidiOutputs = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! fAutosavePath.empty())
|
if (! fAutosavePath.empty())
|
||||||
rack::system::removeRecursively(fAutosavePath);
|
rack::system::removeRecursively(fAutosavePath);
|
||||||
}
|
}
|
||||||
|
|
@ -450,101 +493,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/* --------------------------------------------------------------------------------------------------------
|
|
||||||
* Cardinal Base things */
|
|
||||||
|
|
||||||
void assignMidiInputDevice(CardinalMidiInputDevice* const dev) noexcept override
|
|
||||||
{
|
|
||||||
CardinalMidiInputDevice** const oldDevs = fCurrentMidiInputs;
|
|
||||||
|
|
||||||
uint numDevs = 0;
|
|
||||||
if (oldDevs != nullptr)
|
|
||||||
{
|
|
||||||
while (oldDevs[numDevs] != nullptr)
|
|
||||||
++numDevs;
|
|
||||||
}
|
|
||||||
|
|
||||||
CardinalMidiInputDevice** const newDevs = new CardinalMidiInputDevice*[numDevs + 2];
|
|
||||||
|
|
||||||
for (uint i=0; i<numDevs; ++i)
|
|
||||||
newDevs[i] = oldDevs[i];
|
|
||||||
|
|
||||||
newDevs[numDevs+0] = dev;
|
|
||||||
newDevs[numDevs+1] = nullptr;
|
|
||||||
|
|
||||||
{
|
|
||||||
const MutexLocker cml(fDeviceMutex);
|
|
||||||
fCurrentMidiInputs = newDevs;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] oldDevs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void assignMidiOutputDevice(CardinalMidiOutputDevice* const dev) noexcept override
|
|
||||||
{
|
|
||||||
CardinalMidiOutputDevice** const oldDevs = fCurrentMidiOutputs;
|
|
||||||
|
|
||||||
uint numDevs = 0;
|
|
||||||
if (oldDevs != nullptr)
|
|
||||||
{
|
|
||||||
while (oldDevs[numDevs] != nullptr)
|
|
||||||
++numDevs;
|
|
||||||
}
|
|
||||||
|
|
||||||
CardinalMidiOutputDevice** const newDevs = new CardinalMidiOutputDevice*[numDevs + 2];
|
|
||||||
|
|
||||||
for (uint i=0; i<numDevs; ++i)
|
|
||||||
newDevs[i] = oldDevs[i];
|
|
||||||
|
|
||||||
newDevs[numDevs+0] = dev;
|
|
||||||
newDevs[numDevs+1] = nullptr;
|
|
||||||
|
|
||||||
{
|
|
||||||
const MutexLocker cml(fDeviceMutex);
|
|
||||||
fCurrentMidiOutputs = newDevs;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] oldDevs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearMidiInputDevice(CardinalMidiInputDevice* const dev) noexcept override
|
|
||||||
{
|
|
||||||
const MutexLocker cml(fDeviceMutex);
|
|
||||||
|
|
||||||
CardinalMidiInputDevice** const inputs = fCurrentMidiInputs;
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(inputs != nullptr,);
|
|
||||||
|
|
||||||
for (uint i=0; inputs[i] != nullptr; ++i)
|
|
||||||
{
|
|
||||||
CardinalMidiInputDevice* const input = inputs[i];
|
|
||||||
if (input != dev)
|
|
||||||
continue;
|
|
||||||
for (; inputs[i+1] != nullptr; ++i)
|
|
||||||
inputs[i] = inputs[i+1];
|
|
||||||
inputs[i] = nullptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearMidiOutputDevice(CardinalMidiOutputDevice* const dev) noexcept override
|
|
||||||
{
|
|
||||||
const MutexLocker cml(fDeviceMutex);
|
|
||||||
|
|
||||||
CardinalMidiOutputDevice** const outputs = fCurrentMidiOutputs;
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(outputs != nullptr,);
|
|
||||||
|
|
||||||
for (uint i=0; outputs[i] != nullptr; ++i)
|
|
||||||
{
|
|
||||||
CardinalMidiOutputDevice* const output = outputs[i];
|
|
||||||
if (output != dev)
|
|
||||||
continue;
|
|
||||||
for (; outputs[i+1] != nullptr; ++i)
|
|
||||||
outputs[i] = outputs[i+1];
|
|
||||||
outputs[i] = nullptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------------------------------------
|
||||||
* Information */
|
* Information */
|
||||||
|
|
||||||
|
|
@ -875,32 +823,22 @@ protected:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void sendSingleSimpleMidiMessage(const MidiEvent& midiEvent)
|
|
||||||
{
|
|
||||||
if (CardinalMidiInputDevice** inputs = fCurrentMidiInputs)
|
|
||||||
{
|
|
||||||
for (;*inputs != nullptr; ++inputs)
|
|
||||||
(*inputs)->handleSingleSimpleMessageFromHost(midiEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void run(const float** const inputs, float** const outputs, const uint32_t frames,
|
void run(const float** const inputs, float** const outputs, const uint32_t frames,
|
||||||
const MidiEvent* const midiEvents, const uint32_t midiEventCount) override
|
const MidiEvent* const midiEvents, const uint32_t midiEventCount) override
|
||||||
{
|
{
|
||||||
const MutexLocker cml(fDeviceMutex);
|
|
||||||
rack::contextSet(context);
|
rack::contextSet(context);
|
||||||
|
|
||||||
{
|
{
|
||||||
const TimePosition& timePos(getTimePosition());
|
const TimePosition& timePos(getTimePosition());
|
||||||
|
|
||||||
bool reset = false;
|
bool reset = false;
|
||||||
MidiEvent singleTimeMidiEvent = { 0, 1, { 0, 0, 0, 0 }, nullptr };
|
|
||||||
|
|
||||||
if (timePos.playing)
|
if (timePos.playing)
|
||||||
{
|
{
|
||||||
if (timePos.frame == 0 || fPreviousFrame + frames != timePos.frame)
|
if (timePos.frame == 0 || fPreviousFrame + frames != timePos.frame)
|
||||||
reset = true;
|
reset = true;
|
||||||
|
|
||||||
|
/*
|
||||||
if (! context->playing)
|
if (! context->playing)
|
||||||
{
|
{
|
||||||
if (timePos.frame == 0)
|
if (timePos.frame == 0)
|
||||||
|
|
@ -912,11 +850,14 @@ protected:
|
||||||
singleTimeMidiEvent.data[0] = 0xFB; // continue
|
singleTimeMidiEvent.data[0] = 0xFB; // continue
|
||||||
sendSingleSimpleMidiMessage(singleTimeMidiEvent);
|
sendSingleSimpleMidiMessage(singleTimeMidiEvent);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
else if (context->playing)
|
else if (context->playing)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
singleTimeMidiEvent.data[0] = 0xFC; // stop
|
singleTimeMidiEvent.data[0] = 0xFC; // stop
|
||||||
sendSingleSimpleMidiMessage(singleTimeMidiEvent);
|
sendSingleSimpleMidiMessage(singleTimeMidiEvent);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
context->playing = timePos.playing;
|
context->playing = timePos.playing;
|
||||||
|
|
@ -945,12 +886,6 @@ protected:
|
||||||
fPreviousFrame = timePos.frame;
|
fPreviousFrame = timePos.frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CardinalMidiInputDevice** inputs = fCurrentMidiInputs)
|
|
||||||
{
|
|
||||||
for (;*inputs != nullptr; ++inputs)
|
|
||||||
(*inputs)->handleMessagesFromHost(midiEvents, midiEventCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// separate buffers, use them
|
// separate buffers, use them
|
||||||
if (inputs != outputs && (inputs == nullptr || inputs[0] != outputs[0]))
|
if (inputs != outputs && (inputs == nullptr || inputs[0] != outputs[0]))
|
||||||
{
|
{
|
||||||
|
|
@ -970,6 +905,9 @@ protected:
|
||||||
for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
|
for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
|
||||||
std::memset(outputs[i], 0, sizeof(float)*frames);
|
std::memset(outputs[i], 0, sizeof(float)*frames);
|
||||||
|
|
||||||
|
context->midiEvents = midiEvents;
|
||||||
|
context->midiEventCount = midiEventCount;
|
||||||
|
|
||||||
context->engine->stepBlock(frames);
|
context->engine->stepBlock(frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ struct CardinalPluginContext : rack::Context {
|
||||||
uintptr_t nativeWindowId;
|
uintptr_t nativeWindowId;
|
||||||
const float* const* dataIns;
|
const float* const* dataIns;
|
||||||
float** dataOuts;
|
float** dataOuts;
|
||||||
|
const MidiEvent* midiEvents;
|
||||||
|
uint32_t midiEventCount;
|
||||||
Plugin* const plugin;
|
Plugin* const plugin;
|
||||||
#ifndef HEADLESS
|
#ifndef HEADLESS
|
||||||
UI* ui;
|
UI* ui;
|
||||||
|
|
@ -95,6 +97,8 @@ struct CardinalPluginContext : rack::Context {
|
||||||
nativeWindowId(0),
|
nativeWindowId(0),
|
||||||
dataIns(nullptr),
|
dataIns(nullptr),
|
||||||
dataOuts(nullptr),
|
dataOuts(nullptr),
|
||||||
|
midiEvents(nullptr),
|
||||||
|
midiEventCount(0),
|
||||||
plugin(p)
|
plugin(p)
|
||||||
#ifndef HEADLESS
|
#ifndef HEADLESS
|
||||||
, ui(nullptr)
|
, ui(nullptr)
|
||||||
|
|
@ -103,6 +107,8 @@ struct CardinalPluginContext : rack::Context {
|
||||||
std::memset(parameters, 0, sizeof(parameters));
|
std::memset(parameters, 0, sizeof(parameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void writeMidiMessage(const rack::midi::Message& message);
|
||||||
|
|
||||||
#ifndef HEADLESS
|
#ifndef HEADLESS
|
||||||
bool addIdleCallback(IdleCallback* cb) const;
|
bool addIdleCallback(IdleCallback* cb) const;
|
||||||
void removeIdleCallback(IdleCallback* cb) const;
|
void removeIdleCallback(IdleCallback* cb) const;
|
||||||
|
|
@ -129,10 +135,6 @@ public:
|
||||||
: Plugin(parameterCount, programCount, stateCount),
|
: Plugin(parameterCount, programCount, stateCount),
|
||||||
context(new CardinalPluginContext(this)) {}
|
context(new CardinalPluginContext(this)) {}
|
||||||
~CardinalBasePlugin() override {}
|
~CardinalBasePlugin() override {}
|
||||||
virtual void assignMidiInputDevice(CardinalMidiInputDevice* dev) noexcept = 0;
|
|
||||||
virtual void assignMidiOutputDevice(CardinalMidiOutputDevice* dev) noexcept = 0;
|
|
||||||
virtual void clearMidiInputDevice(CardinalMidiInputDevice* dev) noexcept = 0;
|
|
||||||
virtual void clearMidiOutputDevice(CardinalMidiOutputDevice* dev) noexcept = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef HEADLESS
|
#ifndef HEADLESS
|
||||||
|
|
|
||||||
|
|
@ -1,257 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "PluginContext.hpp"
|
|
||||||
|
|
||||||
START_NAMESPACE_DISTRHO
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
struct CardinalMidiInputDevice : rack::midi::InputDevice
|
|
||||||
{
|
|
||||||
CardinalBasePlugin* const fPlugin;
|
|
||||||
rack::midi::Message msg;
|
|
||||||
|
|
||||||
CardinalMidiInputDevice(CardinalBasePlugin* const plugin)
|
|
||||||
: fPlugin(plugin)
|
|
||||||
{
|
|
||||||
msg.bytes.resize(0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() override
|
|
||||||
{
|
|
||||||
return "Cardinal";
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void handleSingleSimpleMessageFromHost(const MidiEvent& midiEvent)
|
|
||||||
{
|
|
||||||
if (subscribed.size() == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
msg.frame = midiEvent.frame;
|
|
||||||
std::memcpy(msg.bytes.data(), midiEvent.data, midiEvent.size);
|
|
||||||
|
|
||||||
onMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void handleMessagesFromHost(const MidiEvent* const midiEvents, const uint32_t midiEventCount)
|
|
||||||
{
|
|
||||||
if (subscribed.size() == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (uint32_t i=0; i<midiEventCount; ++i)
|
|
||||||
{
|
|
||||||
const MidiEvent& midiEvent(midiEvents[i]);
|
|
||||||
const uint8_t* data;
|
|
||||||
|
|
||||||
if (midiEvent.size > MidiEvent::kDataSize)
|
|
||||||
{
|
|
||||||
data = midiEvent.dataExt;
|
|
||||||
msg.bytes.resize(midiEvent.size);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data = midiEvent.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.frame = midiEvent.frame;
|
|
||||||
std::memcpy(msg.bytes.data(), data, midiEvent.size);
|
|
||||||
|
|
||||||
onMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
struct CardinalMidiOutputDevice : rack::midi::OutputDevice
|
|
||||||
{
|
|
||||||
CardinalBasePlugin* const fPlugin;
|
|
||||||
|
|
||||||
CardinalMidiOutputDevice(CardinalBasePlugin* const plugin)
|
|
||||||
: fPlugin(plugin) {}
|
|
||||||
|
|
||||||
std::string getName() override
|
|
||||||
{
|
|
||||||
return "Cardinal";
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendMessage(const rack::midi::Message& message) override
|
|
||||||
{
|
|
||||||
const size_t size = message.bytes.size();
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(size > 0,);
|
|
||||||
|
|
||||||
MidiEvent event;
|
|
||||||
event.frame = message.frame < 0 ? 0 : (message.frame - fPlugin->context->engine->getBlockFrame());
|
|
||||||
|
|
||||||
switch (message.bytes[0] & 0xF0)
|
|
||||||
{
|
|
||||||
case 0x80:
|
|
||||||
case 0x90:
|
|
||||||
case 0xA0:
|
|
||||||
case 0xB0:
|
|
||||||
case 0xE0:
|
|
||||||
event.size = 3;
|
|
||||||
break;
|
|
||||||
case 0xC0:
|
|
||||||
case 0xD0:
|
|
||||||
event.size = 2;
|
|
||||||
break;
|
|
||||||
case 0xF0:
|
|
||||||
switch (message.bytes[0] & 0x0F)
|
|
||||||
{
|
|
||||||
case 0x0:
|
|
||||||
case 0x4:
|
|
||||||
case 0x5:
|
|
||||||
case 0x7:
|
|
||||||
case 0x9:
|
|
||||||
case 0xD:
|
|
||||||
// unsupported
|
|
||||||
return;
|
|
||||||
case 0x1:
|
|
||||||
case 0x2:
|
|
||||||
case 0x3:
|
|
||||||
case 0xE:
|
|
||||||
event.size = 3;
|
|
||||||
break;
|
|
||||||
case 0x6:
|
|
||||||
case 0x8:
|
|
||||||
case 0xA:
|
|
||||||
case 0xB:
|
|
||||||
case 0xC:
|
|
||||||
case 0xF:
|
|
||||||
event.size = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(size >= event.size,);
|
|
||||||
|
|
||||||
std::memcpy(event.data, message.bytes.data(), event.size);
|
|
||||||
|
|
||||||
fPlugin->writeMidiEvent(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
struct CardinalMidiDriver : rack::midi::Driver
|
|
||||||
{
|
|
||||||
CardinalMidiDriver() {}
|
|
||||||
|
|
||||||
std::string getName() override
|
|
||||||
{
|
|
||||||
return "Plugin Driver";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<int> getInputDeviceIds() override
|
|
||||||
{
|
|
||||||
return std::vector<int>({ 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<int> getOutputDeviceIds() override
|
|
||||||
{
|
|
||||||
return std::vector<int>({ 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
int getDefaultInputDeviceId() override
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getDefaultOutputDeviceId() override
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getInputDeviceName(int) override
|
|
||||||
{
|
|
||||||
return "Plugin Device";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getOutputDeviceName(int) override
|
|
||||||
{
|
|
||||||
return "Plugin Device";
|
|
||||||
}
|
|
||||||
|
|
||||||
rack::midi::InputDevice* subscribeInput(int, rack::midi::Input* const input) override
|
|
||||||
{
|
|
||||||
CardinalPluginContext* const pluginContext = reinterpret_cast<CardinalPluginContext*>(input->context);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(pluginContext != nullptr, nullptr);
|
|
||||||
|
|
||||||
CardinalBasePlugin* const plugin = reinterpret_cast<CardinalBasePlugin*>(pluginContext->plugin);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(plugin != nullptr, nullptr);
|
|
||||||
|
|
||||||
CardinalMidiInputDevice* const device = new CardinalMidiInputDevice(plugin);
|
|
||||||
device->subscribe(input);
|
|
||||||
plugin->assignMidiInputDevice(device);
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
rack::midi::OutputDevice* subscribeOutput(int, rack::midi::Output* const output) override
|
|
||||||
{
|
|
||||||
CardinalPluginContext* const pluginContext = reinterpret_cast<CardinalPluginContext*>(output->context);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(pluginContext != nullptr, nullptr);
|
|
||||||
|
|
||||||
CardinalBasePlugin* const plugin = reinterpret_cast<CardinalBasePlugin*>(pluginContext->plugin);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(plugin != nullptr, nullptr);
|
|
||||||
|
|
||||||
CardinalMidiOutputDevice* const device = new CardinalMidiOutputDevice(plugin);
|
|
||||||
device->subscribe(output);
|
|
||||||
plugin->assignMidiOutputDevice(device);
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unsubscribeInput(int, rack::midi::Input* const input) override
|
|
||||||
{
|
|
||||||
CardinalMidiInputDevice* const device = reinterpret_cast<CardinalMidiInputDevice*>(input->device);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(device != nullptr,);
|
|
||||||
|
|
||||||
CardinalPluginContext* const pluginContext = reinterpret_cast<CardinalPluginContext*>(input->context);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(pluginContext != nullptr,);
|
|
||||||
|
|
||||||
CardinalBasePlugin* const plugin = reinterpret_cast<CardinalBasePlugin*>(pluginContext->plugin);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(plugin != nullptr,);
|
|
||||||
|
|
||||||
plugin->clearMidiInputDevice(device);
|
|
||||||
device->unsubscribe(input);
|
|
||||||
delete device;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unsubscribeOutput(int, rack::midi::Output* const output) override
|
|
||||||
{
|
|
||||||
CardinalMidiOutputDevice* const device = reinterpret_cast<CardinalMidiOutputDevice*>(output->device);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(device != nullptr,);
|
|
||||||
|
|
||||||
CardinalPluginContext* const pluginContext = reinterpret_cast<CardinalPluginContext*>(output->context);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(pluginContext != nullptr,);
|
|
||||||
|
|
||||||
CardinalBasePlugin* const plugin = reinterpret_cast<CardinalBasePlugin*>(pluginContext->plugin);
|
|
||||||
DISTRHO_SAFE_ASSERT_RETURN(plugin != nullptr,);
|
|
||||||
|
|
||||||
plugin->clearMidiOutputDevice(device);
|
|
||||||
device->unsubscribe(output);
|
|
||||||
delete device;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
END_NAMESPACE_DISTRHO
|
|
||||||
|
|
@ -24,9 +24,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"plugin": "Core",
|
"plugin": "Cardinal",
|
||||||
"version": "2.0.0",
|
"model": "HostMIDI",
|
||||||
"model": "MIDIToCVInterface",
|
"version": "2.0",
|
||||||
"params": [],
|
"params": [],
|
||||||
"leftModuleId": 1,
|
"leftModuleId": 1,
|
||||||
"rightModuleId": 3,
|
"rightModuleId": 3,
|
||||||
|
|
@ -35,12 +35,7 @@
|
||||||
"polyMode": 0,
|
"polyMode": 0,
|
||||||
"clockDivision": 24,
|
"clockDivision": 24,
|
||||||
"lastPitch": 8192,
|
"lastPitch": 8192,
|
||||||
"lastMod": 0,
|
"lastMod": 0
|
||||||
"midi": {
|
|
||||||
"driver": 0,
|
|
||||||
"deviceName": "Cardinal",
|
|
||||||
"channel": -1
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"pos": [
|
"pos": [
|
||||||
5,
|
5,
|
||||||
|
|
@ -49,47 +44,26 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"plugin": "Core",
|
"plugin": "Cardinal",
|
||||||
"version": "2.0.0",
|
"model": "HostParameters",
|
||||||
"model": "CV-MIDI",
|
"version": "2.0",
|
||||||
"params": [],
|
"params": [],
|
||||||
"leftModuleId": 2,
|
"leftModuleId": 2,
|
||||||
"rightModuleId": 4,
|
"rightModuleId": 4,
|
||||||
"data": {
|
|
||||||
"midi": {
|
|
||||||
"driver": 0,
|
|
||||||
"deviceName": "Cardinal",
|
|
||||||
"channel": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pos": [
|
"pos": [
|
||||||
13,
|
18,
|
||||||
0
|
0
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"plugin": "Cardinal",
|
"plugin": "Cardinal",
|
||||||
"model": "HostParameters",
|
|
||||||
"version": "2.0",
|
|
||||||
"params": [],
|
|
||||||
"leftModuleId": 3,
|
|
||||||
"rightModuleId": 5,
|
|
||||||
"pos": [
|
|
||||||
21,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"plugin": "Cardinal",
|
|
||||||
"model": "HostTime",
|
"model": "HostTime",
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"params": [],
|
"params": [],
|
||||||
"leftModuleId": 4,
|
"leftModuleId": 3,
|
||||||
"rightModuleId": 6,
|
|
||||||
"pos": [
|
"pos": [
|
||||||
30,
|
27,
|
||||||
0
|
0
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue