|
|
@@ -15,6 +15,16 @@ |
|
|
|
* For a full copy of the GNU General Public License see the LICENSE file. |
|
|
|
*/ |
|
|
|
|
|
|
|
/** |
|
|
|
* This file uses code adapted from VCVRack's CV_MIDI.cpp and midi.hpp |
|
|
|
* Copyright (C) 2016-2021 VCV. |
|
|
|
* |
|
|
|
* This program is free software: you can redistribute it and/or |
|
|
|
* modify it under the terms of the GNU General Public License as |
|
|
|
* published by the Free Software Foundation; either version 3 of |
|
|
|
* the License, or (at your option) any later version. |
|
|
|
*/ |
|
|
|
|
|
|
|
#include "plugincontext.hpp" |
|
|
|
|
|
|
|
#ifndef HEADLESS |
|
|
@@ -61,6 +71,124 @@ 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; |
|
|
|
|
|
|
|
static uint32_t host_get_buffer_size(NativeHostHandle); |
|
|
@@ -85,6 +213,12 @@ struct IldaeilModule : Module { |
|
|
|
enum InputIds { |
|
|
|
INPUT1, |
|
|
|
INPUT2, |
|
|
|
PITCH_INPUT, |
|
|
|
GATE_INPUT, |
|
|
|
VEL_INPUT, |
|
|
|
AFT_INPUT, |
|
|
|
PW_INPUT, |
|
|
|
MW_INPUT, |
|
|
|
NUM_INPUTS |
|
|
|
}; |
|
|
|
enum OutputIds { |
|
|
@@ -105,7 +239,6 @@ struct IldaeilModule : Module { |
|
|
|
CarlaHostHandle fCarlaHostHandle = nullptr; |
|
|
|
|
|
|
|
mutable NativeTimeInfo fCarlaTimeInfo; |
|
|
|
// mutable water::MemoryOutputStream fLastProjectState; |
|
|
|
|
|
|
|
void* fUI = nullptr; |
|
|
|
|
|
|
@@ -115,10 +248,24 @@ struct IldaeilModule : Module { |
|
|
|
float audioDataOut2[BUFFER_SIZE]; |
|
|
|
unsigned audioDataFill = 0; |
|
|
|
|
|
|
|
IldaeilMidiGenerator midiGenerator; |
|
|
|
|
|
|
|
IldaeilModule() |
|
|
|
: pcontext(reinterpret_cast<CardinalPluginContext*>(APP)) |
|
|
|
{ |
|
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
|
for (uint i=0; i<2; ++i) |
|
|
|
{ |
|
|
|
const char name[] = { 'A','u','d','i','o',' ','#',static_cast<char>('0'+i+1),'\0' }; |
|
|
|
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)); |
|
|
@@ -270,7 +417,7 @@ struct IldaeilModule : Module { |
|
|
|
engine->loadProjectInternal(xml, true); |
|
|
|
} |
|
|
|
|
|
|
|
void process(const ProcessArgs&) override |
|
|
|
void process(const ProcessArgs& args) override |
|
|
|
{ |
|
|
|
if (fCarlaPluginHandle == nullptr) |
|
|
|
return; |
|
|
@@ -282,15 +429,46 @@ 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) |
|
|
|
{ |
|
|
|
audioDataFill = 0; |
|
|
|
float* ins[2] = { audioDataIn1, audioDataIn2 }; |
|
|
|
float* outs[2] = { audioDataOut1, audioDataOut2 }; |
|
|
|
fCarlaPluginDescriptor->process(fCarlaPluginHandle, ins, outs, BUFFER_SIZE, nullptr, 0); |
|
|
|
fCarlaPluginDescriptor->process(fCarlaPluginHandle, ins, outs, BUFFER_SIZE, |
|
|
|
midiGenerator.midiEvents, midiGenerator.midiEventCount); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void onReset() override |
|
|
|
{ |
|
|
|
midiGenerator.reset(); |
|
|
|
} |
|
|
|
|
|
|
|
void onSampleRateChange(const SampleRateChangeEvent& e) override |
|
|
|
{ |
|
|
|
if (fCarlaPluginHandle == nullptr) |
|
|
@@ -493,10 +671,14 @@ struct IldaeilWidget : ImGuiWidget, IdleCallback, Thread { |
|
|
|
if (module->fCarlaHostHandle != nullptr) |
|
|
|
{ |
|
|
|
module->fUI = nullptr; |
|
|
|
|
|
|
|
if (fPluginRunning) |
|
|
|
carla_show_custom_ui(module->fCarlaHostHandle, 0, false); |
|
|
|
|
|
|
|
carla_set_engine_option(module->fCarlaHostHandle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0"); |
|
|
|
carla_show_custom_ui(module->fCarlaHostHandle, 0, false); |
|
|
|
|
|
|
|
module->pcontext->removeIdleCallback(this); |
|
|
|
if (idleCallbackActive) |
|
|
|
module->pcontext->removeIdleCallback(this); |
|
|
|
} |
|
|
|
|
|
|
|
if (isThreadRunning()) |
|
|
@@ -1235,6 +1417,13 @@ 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)); |
|
|
|
|
|
|
|
addOutput(createInput<PJ301MPort>(Vec(3, 54 + 135), module, IldaeilModule::PITCH_INPUT)); |
|
|
|
addOutput(createInput<PJ301MPort>(Vec(3, 54 + 165), module, IldaeilModule::GATE_INPUT)); |
|
|
|
addOutput(createInput<PJ301MPort>(Vec(3, 54 + 195), module, IldaeilModule::VEL_INPUT)); |
|
|
|
addOutput(createInput<PJ301MPort>(Vec(3, 54 + 225), module, IldaeilModule::AFT_INPUT)); |
|
|
|
addOutput(createInput<PJ301MPort>(Vec(3, 54 + 255), module, IldaeilModule::PW_INPUT)); |
|
|
|
addOutput(createInput<PJ301MPort>(Vec(3, 54 + 285), module, IldaeilModule::MW_INPUT)); |
|
|
|
} |
|
|
|
}; |
|
|
|
#else |
|
|
|