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