From 0535c7eabc62dd3175e69ea8ddfc366eaf2330ca Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 14 Nov 2021 22:15:01 +0000 Subject: [PATCH] Ildaeil: Add MIDI input (converted from polyphonic CV) --- plugins/Cardinal/src/Ildaeil.cpp | 199 ++++++++++++++++++++++++++++++- 1 file changed, 194 insertions(+), 5 deletions(-) diff --git a/plugins/Cardinal/src/Ildaeil.cpp b/plugins/Cardinal/src/Ildaeil.cpp index ba6b3d5..88c3c19 100644 --- a/plugins/Cardinal/src/Ildaeil.cpp +++ b/plugins/Cardinal/src/Ildaeil.cpp @@ -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(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('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(Vec(3, 54 + 30), module, IldaeilModule::INPUT2)); addOutput(createOutput(Vec(3, 54 + 60), module, IldaeilModule::OUTPUT1)); addOutput(createOutput(Vec(3, 54 + 90), module, IldaeilModule::OUTPUT2)); + + addOutput(createInput(Vec(3, 54 + 135), module, IldaeilModule::PITCH_INPUT)); + addOutput(createInput(Vec(3, 54 + 165), module, IldaeilModule::GATE_INPUT)); + addOutput(createInput(Vec(3, 54 + 195), module, IldaeilModule::VEL_INPUT)); + addOutput(createInput(Vec(3, 54 + 225), module, IldaeilModule::AFT_INPUT)); + addOutput(createInput(Vec(3, 54 + 255), module, IldaeilModule::PW_INPUT)); + addOutput(createInput(Vec(3, 54 + 285), module, IldaeilModule::MW_INPUT)); } }; #else