diff --git a/include/app.hpp b/include/app.hpp index b7d0b4be..f42b67f4 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -337,6 +337,7 @@ struct LedDisplaySeparator : TransparentWidget { struct LedDisplayChoice : TransparentWidget { std::string text; std::shared_ptr font; + Vec textOffset; NVGcolor color; LedDisplayChoice(); void draw(NVGcontext *vg) override; @@ -345,6 +346,7 @@ struct LedDisplayChoice : TransparentWidget { struct LedDisplayTextField : TextField { std::shared_ptr font; + Vec textOffset; NVGcolor color; LedDisplayTextField(); void draw(NVGcontext *vg) override; @@ -358,20 +360,26 @@ struct MidiIO; struct AudioWidget : LedDisplay { /** Not owned */ AudioIO *audioIO = NULL; - struct Internal; - Internal *internal; + LedDisplayChoice *driverChoice; + LedDisplaySeparator *driverSeparator; + LedDisplayChoice *deviceChoice; + LedDisplaySeparator *deviceSeparator; + LedDisplayChoice *sampleRateChoice; + LedDisplaySeparator *sampleRateSeparator; + LedDisplayChoice *bufferSizeChoice; AudioWidget(); - ~AudioWidget(); void step() override; }; struct MidiWidget : LedDisplay { /** Not owned */ MidiIO *midiIO = NULL; - struct Internal; - Internal *internal; + LedDisplayChoice *driverChoice; + LedDisplaySeparator *driverSeparator; + LedDisplayChoice *deviceChoice; + LedDisplaySeparator *deviceSeparator; + LedDisplayChoice *channelChoice; MidiWidget(); - ~MidiWidget(); void step() override; }; diff --git a/include/audio.hpp b/include/audio.hpp index 7c677d0b..d7d834f1 100644 --- a/include/audio.hpp +++ b/include/audio.hpp @@ -23,12 +23,13 @@ struct AudioIO { int numOutputs = 0; int numInputs = 0; RtAudio *rtAudio = NULL; + /** Cached */ RtAudio::DeviceInfo deviceInfo; AudioIO(); virtual ~AudioIO(); - std::vector listDrivers(); + std::vector getDrivers(); std::string getDriverName(int driver); void setDriver(int driver); @@ -40,7 +41,7 @@ struct AudioIO { /** Returns whether the audio stream is open and running */ bool isActive(); - std::vector listSampleRates(); + std::vector getSampleRates(); virtual void processStream(const float *input, float *output, int length) {} virtual void onCloseStream() {} diff --git a/include/dsp/filter.hpp b/include/dsp/filter.hpp index ad4d5aea..a61e907d 100644 --- a/include/dsp/filter.hpp +++ b/include/dsp/filter.hpp @@ -6,13 +6,13 @@ namespace rack { struct RCFilter { - float c = 0.0; + float c = 0.f; float xstate[1] = {}; float ystate[1] = {}; // `r` is the ratio between the cutoff frequency and sample rate, i.e. r = f_c / f_s void setCutoff(float r) { - c = 2.0 / r; + c = 2.f / r; } void process(float x) { float y = (x + xstate[0] - ystate[0] * (1 - c)) / (1 + c); @@ -29,12 +29,12 @@ struct RCFilter { struct PeakFilter { - float state = 0.0; - float c = 0.0; + float state = 0.f; + float c = 0.f; /** Rate is lambda / sampleRate */ void setRate(float r) { - c = 1.0 - r; + c = 1.f - r; } void process(float x) { if (x > state) @@ -48,9 +48,9 @@ struct PeakFilter { struct SlewLimiter { - float rise = 1.0; - float fall = 1.0; - float out = 0.0; + float rise = 1.f; + float fall = 1.f; + float out = 0.f; void setRiseFall(float _rise, float _fall) { rise = _rise; @@ -64,4 +64,23 @@ struct SlewLimiter { }; +/** Applies exponential smoothing to a signal with the ODE +dy/dt = x * lambda +*/ +struct ExponentialFilter { + float out = 0.f; + float lambda = 1.f; + + float process(float in) { + float y = out + (in - out) * lambda; + // If no change was detected, assume float granularity is too small and snap output to input + if (out == y) + out = in; + else + out = y; + return out; + } +}; + + } // namespace rack diff --git a/include/midi.hpp b/include/midi.hpp index 70af314c..10c36627 100644 --- a/include/midi.hpp +++ b/include/midi.hpp @@ -16,12 +16,21 @@ namespace rack { struct MidiMessage { - double time; - std::vector data; + uint8_t cmd = 0x00; + uint8_t data1 = 0x00; + uint8_t data2 = 0x00; + + uint8_t channel() { + return cmd & 0xf; + } + uint8_t status() { + return (cmd >> 4) & 0xf; + } }; struct MidiIO { + int driver = -1; int device = -1; /* For MIDI output, the channel to output messages. For MIDI input, the channel to filter. @@ -30,11 +39,19 @@ struct MidiIO { */ int channel = -1; RtMidi *rtMidi = NULL; + /** Cached */ + std::string deviceName; + + virtual ~MidiIO(); + std::vector getDrivers(); + std::string getDriverName(int driver); + virtual void setDriver(int driver) {} - virtual ~MidiIO() {} int getDeviceCount(); std::string getDeviceName(int device); - void openDevice(int device); + void setDevice(int device); + + std::string getChannelName(int channel); /** Returns whether the audio stream is open and running */ bool isActive(); json_t *toJson(); @@ -45,21 +62,24 @@ struct MidiIO { struct MidiInput : MidiIO { RtMidiIn *rtMidiIn = NULL; MidiInput(); - ~MidiInput(); + void setDriver(int driver) override; virtual void onMessage(const MidiMessage &message) {} }; struct MidiInputQueue : MidiInput { - std::queue messageQueue; + int queueSize = 8192; + std::queue queue; void onMessage(const MidiMessage &message) override; + /** If a MidiMessage is available, writes `message` and return true */ + bool shift(MidiMessage *message); }; struct MidiOutput : MidiIO { RtMidiOut *rtMidiOut = NULL; MidiOutput(); - ~MidiOutput(); + void setDriver(int driver) override; }; diff --git a/include/window.hpp b/include/window.hpp index 24432579..0594b888 100644 --- a/include/window.hpp +++ b/include/window.hpp @@ -1,5 +1,5 @@ #pragma once -#include "app.hpp" +#include "widgets.hpp" #include #include diff --git a/res/Core/MIDICCToCVInterface.svg b/res/Core/MIDICCToCVInterface.svg new file mode 100644 index 00000000..4f947124 --- /dev/null +++ b/res/Core/MIDICCToCVInterface.svg @@ -0,0 +1,453 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/AudioWidget.cpp b/src/app/AudioWidget.cpp index 0b0aedf3..2f0752e2 100644 --- a/src/app/AudioWidget.cpp +++ b/src/app/AudioWidget.cpp @@ -18,7 +18,7 @@ struct AudioDriverChoice : LedDisplayChoice { void onAction(EventAction &e) override { Menu *menu = gScene->createMenu(); menu->addChild(construct(&MenuLabel::text, "Audio driver")); - for (int driver : audioWidget->audioIO->listDrivers()) { + for (int driver : audioWidget->audioIO->getDrivers()) { AudioDriverItem *item = new AudioDriverItem(); item->audioIO = audioWidget->audioIO; item->driver = driver; @@ -93,7 +93,7 @@ struct AudioSampleRateChoice : LedDisplayChoice { void onAction(EventAction &e) override { Menu *menu = gScene->createMenu(); menu->addChild(construct(&MenuLabel::text, "Sample rate")); - for (int sampleRate : audioWidget->audioIO->listSampleRates()) { + for (int sampleRate : audioWidget->audioIO->getSampleRates()) { AudioSampleRateItem *item = new AudioSampleRateItem(); item->audioIO = audioWidget->audioIO; item->sampleRate = sampleRate; @@ -139,18 +139,7 @@ struct AudioBlockSizeChoice : LedDisplayChoice { }; -struct AudioWidget::Internal { - LedDisplayChoice *driverChoice; - LedDisplaySeparator *driverSeparator; - LedDisplayChoice *deviceChoice; - LedDisplaySeparator *deviceSeparator; - LedDisplayChoice *sampleRateChoice; - LedDisplaySeparator *sampleRateSeparator; - LedDisplayChoice *bufferSizeChoice; -}; - AudioWidget::AudioWidget() { - internal = new Internal(); box.size = mm2px(Vec(44, 28)); Vec pos = Vec(); @@ -159,48 +148,44 @@ AudioWidget::AudioWidget() { driverChoice->audioWidget = this; addChild(driverChoice); pos = driverChoice->box.getBottomLeft(); - internal->driverChoice = driverChoice; + this->driverChoice = driverChoice; - internal->driverSeparator = Widget::create(pos); - addChild(internal->driverSeparator); + this->driverSeparator = Widget::create(pos); + addChild(this->driverSeparator); AudioDeviceChoice *deviceChoice = Widget::create(pos); deviceChoice->audioWidget = this; addChild(deviceChoice); pos = deviceChoice->box.getBottomLeft(); - internal->deviceChoice = deviceChoice; + this->deviceChoice = deviceChoice; - internal->deviceSeparator = Widget::create(pos); - addChild(internal->deviceSeparator); + this->deviceSeparator = Widget::create(pos); + addChild(this->deviceSeparator); AudioSampleRateChoice *sampleRateChoice = Widget::create(pos); sampleRateChoice->audioWidget = this; addChild(sampleRateChoice); - internal->sampleRateChoice = sampleRateChoice; + this->sampleRateChoice = sampleRateChoice; - internal->sampleRateSeparator = Widget::create(pos); - internal->sampleRateSeparator->box.size.y = internal->sampleRateChoice->box.size.y; - addChild(internal->sampleRateSeparator); + this->sampleRateSeparator = Widget::create(pos); + this->sampleRateSeparator->box.size.y = this->sampleRateChoice->box.size.y; + addChild(this->sampleRateSeparator); AudioBlockSizeChoice *bufferSizeChoice = Widget::create(pos); bufferSizeChoice->audioWidget = this; addChild(bufferSizeChoice); - internal->bufferSizeChoice = bufferSizeChoice; -} - -AudioWidget::~AudioWidget() { - delete internal; + this->bufferSizeChoice = bufferSizeChoice; } void AudioWidget::step() { - internal->driverChoice->box.size.x = box.size.x; - internal->driverSeparator->box.size.x = box.size.x; - internal->deviceChoice->box.size.x = box.size.x; - internal->deviceSeparator->box.size.x = box.size.x; - internal->sampleRateChoice->box.size.x = box.size.x / 2; - internal->sampleRateSeparator->box.pos.x = box.size.x / 2; - internal->bufferSizeChoice->box.pos.x = box.size.x / 2; - internal->bufferSizeChoice->box.size.x = box.size.x / 2; + this->driverChoice->box.size.x = box.size.x; + this->driverSeparator->box.size.x = box.size.x; + this->deviceChoice->box.size.x = box.size.x; + this->deviceSeparator->box.size.x = box.size.x; + this->sampleRateChoice->box.size.x = box.size.x / 2; + this->sampleRateSeparator->box.pos.x = box.size.x / 2; + this->bufferSizeChoice->box.pos.x = box.size.x / 2; + this->bufferSizeChoice->box.size.x = box.size.x / 2; LedDisplay::step(); } diff --git a/src/app/LedDisplay.cpp b/src/app/LedDisplay.cpp index 0305b3da..c733abca 100644 --- a/src/app/LedDisplay.cpp +++ b/src/app/LedDisplay.cpp @@ -34,6 +34,7 @@ LedDisplayChoice::LedDisplayChoice() { box.size = mm2px(Vec(0, 28.0 / 3)); font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); color = nvgRGB(0xff, 0xd7, 0x14); + textOffset = Vec(10, 18); } void LedDisplayChoice::draw(NVGcontext *vg) { @@ -44,13 +45,12 @@ void LedDisplayChoice::draw(NVGcontext *vg) { nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, 0.0); - Vec textPos = Vec(10, 18); nvgFontSize(vg, 12); - nvgText(vg, textPos.x, textPos.y, text.c_str(), NULL); + nvgText(vg, textOffset.x, textOffset.y, text.c_str(), NULL); } void LedDisplayChoice::onMouseDown(EventMouseDown &e) { - if (e.button == 1) { + if (e.button == 0 || e.button == 1) { EventAction eAction; onAction(eAction); e.consumed = true; @@ -62,9 +62,9 @@ void LedDisplayChoice::onMouseDown(EventMouseDown &e) { LedDisplayTextField::LedDisplayTextField() { font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); color = nvgRGB(0xff, 0xd7, 0x14); + textOffset = Vec(5, 5); } -static const Vec textFieldPos = Vec(5, 5); void LedDisplayTextField::draw(NVGcontext *vg) { // Background @@ -82,8 +82,8 @@ void LedDisplayTextField::draw(NVGcontext *vg) { NVGcolor highlightColor = color; highlightColor.a = 0.5; int cend = (this == gFocusedWidget) ? end : -1; - bndIconLabelCaret(vg, textFieldPos.x, textFieldPos.y, - box.size.x - 2*textFieldPos.x, box.size.y - 2*textFieldPos.y, + bndIconLabelCaret(vg, textOffset.x, textOffset.y, + box.size.x - 2*textOffset.x, box.size.y - 2*textOffset.y, -1, color, 12, text.c_str(), highlightColor, begin, cend); bndSetFont(gGuiFont->handle); @@ -91,8 +91,8 @@ void LedDisplayTextField::draw(NVGcontext *vg) { int LedDisplayTextField::getTextPosition(Vec mousePos) { bndSetFont(font->handle); - int textPos = bndIconLabelTextPosition(gVg, textFieldPos.x, textFieldPos.y, - box.size.x - 2*textFieldPos.x, box.size.y - 2*textFieldPos.y, + int textPos = bndIconLabelTextPosition(gVg, textOffset.x, textOffset.y, + box.size.x - 2*textOffset.x, box.size.y - 2*textOffset.y, -1, 12, text.c_str(), mousePos.x, mousePos.y); bndSetFont(gGuiFont->handle); return textPos; diff --git a/src/app/MidiWidget.cpp b/src/app/MidiWidget.cpp index 2de8c063..9e901b54 100644 --- a/src/app/MidiWidget.cpp +++ b/src/app/MidiWidget.cpp @@ -9,26 +9,26 @@ struct MidiDriverItem : MenuItem { MidiIO *midiIO; int driver; void onAction(EventAction &e) override { - // midiIO->openDriver(device); + midiIO->setDriver(driver); } }; struct MidiDriverChoice : LedDisplayChoice { MidiWidget *midiWidget; void onAction(EventAction &e) override { - // Menu *menu = gScene->createMenu(); - // menu->addChild(construct(&MenuLabel::text, "Audio driver")); - // for (int driver : audioWidget->audioIO->listDrivers()) { - // AudioDriverItem *item = new AudioDriverItem(); - // item->audioIO = audioWidget->audioIO; - // item->driver = driver; - // item->text = audioWidget->audioIO->getDriverName(driver); - // item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); - // menu->addChild(item); - // } + Menu *menu = gScene->createMenu(); + menu->addChild(construct(&MenuLabel::text, "MIDI driver")); + for (int driver : midiWidget->midiIO->getDrivers()) { + MidiDriverItem *item = new MidiDriverItem(); + item->midiIO = midiWidget->midiIO; + item->driver = driver; + item->text = midiWidget->midiIO->getDriverName(driver); + item->rightText = CHECKMARK(item->driver == midiWidget->midiIO->driver); + menu->addChild(item); + } } void step() override { - // text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); + text = midiWidget->midiIO->getDriverName(midiWidget->midiIO->driver); } }; @@ -36,26 +36,28 @@ struct MidiDeviceItem : MenuItem { MidiIO *midiIO; int device; void onAction(EventAction &e) override { - // midiIO->openDevice(device); + midiIO->setDevice(device); } }; struct MidiDeviceChoice : LedDisplayChoice { MidiWidget *midiWidget; void onAction(EventAction &e) override { - // Menu *menu = gScene->createMenu(); - // menu->addChild(construct(&MenuLabel::text, "Audio driver")); - // for (int driver : audioWidget->audioIO->listDrivers()) { - // AudioDriverItem *item = new AudioDriverItem(); - // item->audioIO = audioWidget->audioIO; - // item->driver = driver; - // item->text = audioWidget->audioIO->getDriverName(driver); - // item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); - // menu->addChild(item); - // } + Menu *menu = gScene->createMenu(); + menu->addChild(construct(&MenuLabel::text, "MIDI device")); + int driverCount = midiWidget->midiIO->getDeviceCount(); + for (int device = 0; device < driverCount; device++) { + MidiDeviceItem *item = new MidiDeviceItem(); + item->midiIO = midiWidget->midiIO; + item->device = device; + item->text = midiWidget->midiIO->getDeviceName(device); + item->rightText = CHECKMARK(item->device == midiWidget->midiIO->device); + menu->addChild(item); + } } void step() override { - // text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); + text = midiWidget->midiIO->getDeviceName(midiWidget->midiIO->device); + text = ellipsize(text, 14); } }; @@ -63,40 +65,31 @@ struct MidiChannelItem : MenuItem { MidiIO *midiIO; int channel; void onAction(EventAction &e) override { - // midiIO->channel = channel; + midiIO->channel = channel; } }; struct MidiChannelChoice : LedDisplayChoice { MidiWidget *midiWidget; void onAction(EventAction &e) override { - // Menu *menu = gScene->createMenu(); - // menu->addChild(construct(&MenuLabel::text, "Audio driver")); - // for (int driver : audioWidget->audioIO->listDrivers()) { - // AudioDriverItem *item = new AudioDriverItem(); - // item->audioIO = audioWidget->audioIO; - // item->driver = driver; - // item->text = audioWidget->audioIO->getDriverName(driver); - // item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); - // menu->addChild(item); - // } + Menu *menu = gScene->createMenu(); + menu->addChild(construct(&MenuLabel::text, "MIDI channel")); + for (int channel = -1; channel < 16; channel++) { + MidiChannelItem *item = new MidiChannelItem(); + item->midiIO = midiWidget->midiIO; + item->channel = channel; + item->text = midiWidget->midiIO->getChannelName(channel); + item->rightText = CHECKMARK(item->channel == midiWidget->midiIO->channel); + menu->addChild(item); + } } void step() override { - // text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); + text = midiWidget->midiIO->getChannelName(midiWidget->midiIO->channel); } }; -struct MidiWidget::Internal { - LedDisplayChoice *driverChoice; - LedDisplaySeparator *driverSeparator; - LedDisplayChoice *deviceChoice; - LedDisplaySeparator *deviceSeparator; - LedDisplayChoice *channelChoice; -}; - MidiWidget::MidiWidget() { - internal = new Internal(); box.size = mm2px(Vec(44, 28)); Vec pos = Vec(); @@ -105,85 +98,34 @@ MidiWidget::MidiWidget() { driverChoice->midiWidget = this; addChild(driverChoice); pos = driverChoice->box.getBottomLeft(); - internal->driverChoice = driverChoice; + this->driverChoice = driverChoice; - internal->driverSeparator = Widget::create(pos); - addChild(internal->driverSeparator); + this->driverSeparator = Widget::create(pos); + addChild(this->driverSeparator); MidiDeviceChoice *deviceChoice = Widget::create(pos); deviceChoice->midiWidget = this; addChild(deviceChoice); pos = deviceChoice->box.getBottomLeft(); - internal->deviceChoice = deviceChoice; + this->deviceChoice = deviceChoice; - internal->deviceSeparator = Widget::create(pos); - addChild(internal->deviceSeparator); + this->deviceSeparator = Widget::create(pos); + addChild(this->deviceSeparator); MidiChannelChoice *channelChoice = Widget::create(pos); channelChoice->midiWidget = this; addChild(channelChoice); - internal->channelChoice = channelChoice; -} - -MidiWidget::~MidiWidget() { - delete internal; + this->channelChoice = channelChoice; } void MidiWidget::step() { - internal->driverChoice->box.size.x = box.size.x; - internal->driverSeparator->box.size.x = box.size.x; - internal->deviceChoice->box.size.x = box.size.x; - internal->deviceSeparator->box.size.x = box.size.x; - internal->channelChoice->box.size.x = box.size.x; + this->driverChoice->box.size.x = box.size.x; + this->driverSeparator->box.size.x = box.size.x; + this->deviceChoice->box.size.x = box.size.x; + this->deviceSeparator->box.size.x = box.size.x; + this->channelChoice->box.size.x = box.size.x; LedDisplay::step(); } -/* -void MidiWidget::onAction(EventAction &e) { - if (!midiIO) - return; - - Menu *menu = gScene->createMenu(); - - menu->addChild(construct(&MenuLabel::text, "MIDI device")); - { - MidiDeviceItem *item = new MidiDeviceItem(); - item->midiIO = midiIO; - item->device = -1; - item->text = "No device"; - item->rightText = CHECKMARK(item->device == midiIO->device); - menu->addChild(item); - } - for (int device = 0; device < midiIO->getDeviceCount(); device++) { - MidiDeviceItem *item = new MidiDeviceItem(); - item->midiIO = midiIO; - item->device = device; - item->text = midiIO->getDeviceName(device); - item->rightText = CHECKMARK(item->device == midiIO->device); - menu->addChild(item); - } - menu->addChild(construct()); - - menu->addChild(construct(&MenuLabel::text, "MIDI channel")); - MidiInput *midiInput = dynamic_cast(midiIO); - if (midiInput) { - MidiChannelItem *item = new MidiChannelItem(); - item->midiIO = midiIO; - item->channel = -1; - item->text = "All"; - item->rightText = CHECKMARK(item->channel == midiIO->channel); - menu->addChild(item); - } - for (int channel = 0; channel < 16; channel++) { - MidiChannelItem *item = new MidiChannelItem(); - item->midiIO = midiIO; - item->channel = channel; - item->text = stringf("%d", channel + 1); - item->rightText = CHECKMARK(item->channel == midiIO->channel); - menu->addChild(item); - } -} -*/ - } // namespace rack diff --git a/src/audio.cpp b/src/audio.cpp index 2d1477c3..8d14cca3 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -17,7 +17,7 @@ AudioIO::~AudioIO() { closeStream(); } -std::vector AudioIO::listDrivers() { +std::vector AudioIO::getDrivers() { std::vector apis; RtAudio::getCompiledApi(apis); std::vector drivers; @@ -39,7 +39,7 @@ std::string AudioIO::getDriverName(int driver) { case RtAudio::WINDOWS_WASAPI: return "WASAPI"; case RtAudio::WINDOWS_ASIO: return "ASIO"; case RtAudio::WINDOWS_DS: return "DirectSound"; - case RtAudio::RTAUDIO_DUMMY: return "Dummy"; + case RtAudio::RTAUDIO_DUMMY: return "Dummy Audio"; case BRIDGE_DRIVER: return "Bridge"; default: return "Unknown"; } @@ -245,7 +245,7 @@ bool AudioIO::isActive() { } -std::vector AudioIO::listSampleRates() { +std::vector AudioIO::getSampleRates() { if (rtAudio) { try { RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); diff --git a/src/core/MidiToCV.cpp b/src/core/MIDIToCVInterface.cpp similarity index 50% rename from src/core/MidiToCV.cpp rename to src/core/MIDIToCVInterface.cpp index bdfed3fe..0df5eec0 100644 --- a/src/core/MidiToCV.cpp +++ b/src/core/MIDIToCVInterface.cpp @@ -1,18 +1,11 @@ -#include -#include #include "core.hpp" #include "midi.hpp" -#include "dsp/digital.hpp" +#include "dsp/filter.hpp" -/* - * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to - * CV - */ -struct MidiValue { - int val = 0; // Controller value - // TransitionSmoother tSmooth; - bool changed = false; // Value has been changed by midi message (only if it is in sync!) +struct MidiNoteData { + uint8_t velocity; + uint8_t aftertouch; }; @@ -42,109 +35,160 @@ struct MIDIToCVInterface : Module { NUM_LIGHTS }; - MidiInput midiIO; - MidiInputQueue midiInput; - std::list notes; - bool pedal = false; - int note = 60; // C4, most modules should use 261.626 Hz - int vel = 0; - MidiValue mod; - MidiValue afterTouch; - MidiValue pitchWheel; - bool gate = false; - - SchmittTrigger resetTrigger; - - MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { - pitchWheel.val = 64; - // pitchWheel.tSmooth.set(0, 0); - } - ~MIDIToCVInterface() { - }; + uint8_t mod = 0; + ExponentialFilter modFilter; + uint16_t pitch = 0; + ExponentialFilter pitchFilter; - void step() override; + MidiNoteData noteData[128]; - void pressNote(int note); + MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} - void releaseNote(int note); + void step() override { + MidiMessage msg; + while (midiInput.shift(&msg)) { + processMessage(msg); + } - void processMidi(std::vector msg); + modFilter.lambda = 100.f * engineGetSampleTime(); + outputs[MOD_OUTPUT].value = modFilter.process(rescale(mod, 0, 127, 0.f, 10.f)); - json_t *toJson() override { - json_t *rootJ = json_object(); - // addBaseJson(rootJ); - return rootJ; - } + pitchFilter.lambda = 100.f * engineGetSampleTime(); + outputs[PITCH_OUTPUT].value = pitchFilter.process(rescale(pitch, 0, 16384, -5.f, 5.f)); - void fromJson(json_t *rootJ) override { - // baseFromJson(rootJ); - } + /* + if (isPortOpen()) { + std::vector message; - void onReset() override { - // resetMidi(); - } + // midiIn->getMessage returns empty vector if there are no messages in the queue + getMessage(&message); + if (message.size() > 0) { + processMidi(message); + } + } - // void resetMidi() override; -}; + outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; -/* -void MIDIToCVInterface::resetMidi() { - mod.val = 0; - mod.tSmooth.set(0, 0); - pitchWheel.val = 64; - pitchWheel.tSmooth.set(0, 0); - afterTouch.val = 0; - afterTouch.tSmooth.set(0, 0); - vel = 0; - gate = false; - notes.clear(); -} -*/ + if (resetTrigger.process(params[RESET_PARAM].value)) { + resetMidi(); + return; + } + + lights[RESET_LIGHT].value -= lights[RESET_LIGHT].value / 0.55 / engineGetSampleRate(); // fade out light + + outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; + outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0; -void MIDIToCVInterface::step() { - /* - if (isPortOpen()) { - std::vector message; + int steps = int(engineGetSampleRate() / 32); - // midiIn->getMessage returns empty vector if there are no messages in the queue - getMessage(&message); - if (message.size() > 0) { - processMidi(message); + if (mod.changed) { + mod.tSmooth.set(outputs[MOD_OUTPUT].value, (mod.val / 127.0 * 10.0), steps); + mod.changed = false; } - } + outputs[MOD_OUTPUT].value = mod.tSmooth.next(); - outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; + if (pitchWheel.changed) { + pitchWheel.tSmooth.set(outputs[PITCHWHEEL_OUTPUT].value, (pitchWheel.val - 64) / 64.0 * 10.0, steps); + pitchWheel.changed = false; + } + outputs[PITCHWHEEL_OUTPUT].value = pitchWheel.tSmooth.next(); - if (resetTrigger.process(params[RESET_PARAM].value)) { - resetMidi(); - return; + outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; + */ } - lights[RESET_LIGHT].value -= lights[RESET_LIGHT].value / 0.55 / engineGetSampleRate(); // fade out light + void processMessage(MidiMessage msg) { + debug("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.data1, msg.data2); + + switch (msg.status()) { + // note off + case 0x8: { + // releaseNote(msg.data1); + } break; + // note on + case 0x9: { + if (msg.data2 > 0) { + uint8_t note = msg.data1 & 0x7f; + noteData[note].velocity = msg.data2; + noteData[note].aftertouch = 0; + // pressNote(msg.data1); + } + 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.data1); + } + } break; + // cc + case 0xb: { + processCC(msg); + } break; + // pitch wheel + case 0xe: { + pitch = msg.data2 * 128 + msg.data1; + } break; + // channel aftertouch + case 0xd: { + // TODO This is pressure, not aftertouch. + // aftertouch = rescale(msg.data1, 0.f, 128.f, 0.f, 10.f); + } break; + case 0xf: { + processSystem(msg); + } break; + default: break; + } + } - outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; - outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0; + void processCC(MidiMessage msg) { + switch (msg.data1) { + // mod + case 0x01: { + mod = msg.data2; + } break; + // sustain + case 0x40: { + // pedal = (msg.data2 >= 64); + // if (!pedal) { + // releaseNote(-1); + // } + } break; + default: break; + } + } - int steps = int(engineGetSampleRate() / 32); + void processSystem(MidiMessage msg) { + switch (msg.channel()) { + case 0x8: { + debug("timing clock"); + } break; + case 0xa: { + debug("start"); + } break; + case 0xb: { + debug("continue"); + } break; + case 0xc: { + debug("stop"); + } break; + default: break; + } + } - if (mod.changed) { - mod.tSmooth.set(outputs[MOD_OUTPUT].value, (mod.val / 127.0 * 10.0), steps); - mod.changed = false; + json_t *toJson() override { + json_t *rootJ = json_object(); + json_object_set_new(rootJ, "midi", midiInput.toJson()); + return rootJ; } - outputs[MOD_OUTPUT].value = mod.tSmooth.next(); - if (pitchWheel.changed) { - pitchWheel.tSmooth.set(outputs[PITCHWHEEL_OUTPUT].value, (pitchWheel.val - 64) / 64.0 * 10.0, steps); - pitchWheel.changed = false; + void fromJson(json_t *rootJ) override { + json_t *midiJ = json_object_get(rootJ, "midi"); + midiInput.fromJson(midiJ); } - outputs[PITCHWHEEL_OUTPUT].value = pitchWheel.tSmooth.next(); +}; - outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; - */ -} +/* void MIDIToCVInterface::pressNote(int note) { // Remove existing similar note auto it = std::find(notes.begin(), notes.end(), note); @@ -175,60 +219,7 @@ void MIDIToCVInterface::releaseNote(int note) { gate = false; } } - -void MIDIToCVInterface::processMidi(std::vector msg) { - /* - int channel = msg[0] & 0xf; - int status = (msg[0] >> 4) & 0xf; - int data1 = msg[1]; - int data2 = msg[2]; - - // fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1, data2); - - // Filter channels - if (this->channel >= 0 && this->channel != channel) - return; - - switch (status) { - // note off - case 0x8: { - releaseNote(data1); - } - break; - case 0x9: // note on - if (data2 > 0) { - pressNote(data1); - this->vel = data2; - } 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(data1); - } - break; - case 0xb: // cc - switch (data1) { - case 0x01: // mod - mod.val = data2; - mod.changed = true; - break; - case 0x40: // sustain - pedal = (data2 >= 64); - if (!pedal) { - releaseNote(-1); - } - break; - } - break; - case 0xe: // pitch wheel - pitchWheel.val = data2; - pitchWheel.changed = true; - break; - case 0xd: // channel aftertouch - afterTouch.val = data1; - afterTouch.changed = true; - break; - } - */ -} +*/ struct MIDIToCVInterfaceWidget : ModuleWidget { @@ -255,10 +246,10 @@ struct MIDIToCVInterfaceWidget : ModuleWidget { MidiWidget *midiWidget = Widget::create(mm2px(Vec(3.41891, 14.8373))); midiWidget->box.size = mm2px(Vec(33.840, 28)); - midiWidget->midiIO = &module->midiIO; + midiWidget->midiIO = &module->midiInput; addChild(midiWidget); } }; -Model *modelMidiToCvInterface = Model::create("Core", "MIDIToCVInterface", "MIDI-1", MIDI_TAG, EXTERNAL_TAG); +Model *modelMIDIToCVInterface = Model::create("Core", "MIDIToCVInterface", "MIDI-1", MIDI_TAG, EXTERNAL_TAG); diff --git a/src/core/MidiCCToCV.cpp b/src/core/MidiCCToCV.cpp index c9aa2be4..8b280cdb 100644 --- a/src/core/MidiCCToCV.cpp +++ b/src/core/MidiCCToCV.cpp @@ -1,328 +1,154 @@ -#if 0 -#include -#include #include "core.hpp" -#include "MidiIO.hpp" +#include "midi.hpp" +#include "dsp/filter.hpp" -struct CCValue { - int val = 0; // Controller value - TransitionSmoother tSmooth; - int num = 0; // Controller number - bool numInited = false; // Num inited by config file - bool numSelected = false; // Text field selected for midi learn - bool changed = false; // Value has been changed by midi message (only if it is in sync!) - int sync = 0; // Output value sync (implies diff) - bool syncFirst = true; // First value after sync was reset - void resetSync() { - sync = 0; - syncFirst = true; +struct CcChoice : LedDisplayChoice { + CcChoice() { + box.size.y = mm2px(6.666); + textOffset.y -= 4; } }; -struct MIDICCToCVInterface : MidiIO, Module { - enum ParamIds { - NUM_PARAMS - }; - enum InputIds { - NUM_INPUTS - }; - enum OutputIds { - NUM_OUTPUTS = 16 - }; - enum LightIds { - NUM_LIGHTS = 16 - }; - CCValue cc[NUM_OUTPUTS]; +struct CcMidiWidget : MidiWidget { + LedDisplaySeparator *hSeparators[4]; + LedDisplaySeparator *vSeparators[4]; + LedDisplayChoice *ccChoices[4][4]; - MIDICCToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { - for (int i = 0; i < NUM_OUTPUTS; i++) { - cc[i].num = i; + CcMidiWidget() { + Vec pos = channelChoice->box.getBottomLeft(); + for (int x = 1; x < 4; x++) { + vSeparators[x] = Widget::create(pos); + addChild(vSeparators[x]); } - } - - ~MIDICCToCVInterface() {} - - void step() override; - - void processMidi(std::vector msg); - - void resetMidi() override; - - json_t *toJson() override { - json_t *rootJ = json_object(); - addBaseJson(rootJ); - for (int i = 0; i < NUM_OUTPUTS; i++) { - json_object_set_new(rootJ, ("ccNum" + std::to_string(i)).c_str(), json_integer(cc[i].num)); - if (outputs[i].active) { - json_object_set_new(rootJ, ("ccVal" + std::to_string(i)).c_str(), json_integer(cc[i].val)); - } - } - return rootJ; - } - - void fromJson(json_t *rootJ) override { - baseFromJson(rootJ); - for (int i = 0; i < NUM_OUTPUTS; i++) { - json_t *ccNumJ = json_object_get(rootJ, ("ccNum" + std::to_string(i)).c_str()); - if (ccNumJ) { - cc[i].num = json_integer_value(ccNumJ); - cc[i].numInited = true; - } - - json_t *ccValJ = json_object_get(rootJ, ("ccVal" + std::to_string(i)).c_str()); - if (ccValJ) { - cc[i].val = json_integer_value(ccValJ); - cc[i].tSmooth.set((cc[i].val / 127.0 * 10.0), (cc[i].val / 127.0 * 10.0)); - cc[i].resetSync(); + for (int y = 0; y < 4; y++) { + hSeparators[y] = Widget::create(pos); + addChild(hSeparators[y]); + for (int x = 0; x < 4; x++) { + CcChoice *ccChoice = Widget::create(pos); + ccChoice->text = stringf("%d", x*4+y); + ccChoices[x][y] = ccChoice; + addChild(ccChoice); } - + pos = ccChoices[0][y]->box.getBottomLeft(); } - } - - void onReset() override { - resetMidi(); - } - -}; - -void MIDICCToCVInterface::step() { - if (isPortOpen()) { - std::vector message; - - - // midiIn->getMessage returns empty vector if there are no messages in the queue - getMessage(&message); - if (message.size() > 0) { - processMidi(message); + for (int x = 1; x < 4; x++) { + vSeparators[x]->box.size.y = pos.y - vSeparators[x]->box.pos.y; } } - - - for (int i = 0; i < NUM_OUTPUTS; i++) { - - lights[i].setBrightness(cc[i].sync / 127.0); - - if (cc[i].changed) { - cc[i].tSmooth.set(outputs[i].value, (cc[i].val / 127.0 * 10.0), int(engineGetSampleRate() / 32)); - cc[i].changed = false; + void step() override { + MidiWidget::step(); + for (int x = 1; x < 4; x++) { + vSeparators[x]->box.pos.x = box.size.x / 4 * x; } - - outputs[i].value = cc[i].tSmooth.next(); - } -} - -void MIDICCToCVInterface::resetMidi() { - for (int i = 0; i < NUM_OUTPUTS; i++) { - cc[i].val = 0; - cc[i].resetSync(); - cc[i].tSmooth.set(0, 0); - } -}; - -void MIDICCToCVInterface::processMidi(std::vector msg) { - int channel = msg[0] & 0xf; - int status = (msg[0] >> 4) & 0xf; - int data1 = msg[1]; - int data2 = msg[2]; - - //fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2); - - // Filter channels - if (this->channel >= 0 && this->channel != channel) - return; - - if (status == 0xb) { - for (int i = 0; i < NUM_OUTPUTS; i++) { - if (cc[i].numSelected) { - cc[i].resetSync(); - cc[i].num = data1; - } - - if (data1 == cc[i].num) { - /* If the first value we received after sync was reset is +/- 1 of - * the output value the values are in sync*/ - if (cc[i].syncFirst) { - cc[i].syncFirst = false; - if (data2 < cc[i].val + 2 && data2 > cc[i].val - 2) { - cc[i].sync = 0; - } - else { - cc[i].sync = absi(data2 - cc[i].val); - } - return; - } - - if (cc[i].sync == 0) { - cc[i].val = data2; - cc[i].changed = true; - } - else { - cc[i].sync = absi(data2 - cc[i].val); - } + for (int y = 0; y < 4; y++) { + hSeparators[y]->box.size.x = box.size.x; + for (int x = 0; x < 4; x++) { + ccChoices[x][y]->box.size.x = box.size.x / 4; + ccChoices[x][y]->box.pos.x = box.size.x / 4 * x; } } } -} - -struct CCTextField : TextField { - void onTextChange() override; - - void draw(NVGcontext *vg) override; - - void onMouseDown(EventMouseDown &e) override; - void onMouseUp(EventMouseUp &e) override; - void onMouseLeave(EventMouseLeave &e) override; - - int outNum; - - MIDICCToCVInterface *module; }; -void CCTextField::draw(NVGcontext *vg) { - /* This is necessary, since the save - * file is loaded after constructing the widget*/ - if (module->cc[outNum].numInited) { - module->cc[outNum].numInited = false; - text = std::to_string(module->cc[outNum].num); - } - /* If number is selected for midi learn*/ - if (module->cc[outNum].numSelected) { - text = std::to_string(module->cc[outNum].num); - } - - TextField::draw(vg); -} - -void CCTextField::onMouseUp(EventMouseUp &e) { - if (e.button == 1) { - module->cc[outNum].numSelected = false; - e.consumed = true; - } - TextField::onMouseUp(e); -} - -void CCTextField::onMouseDown(EventMouseDown &e) { - if (e.button == 1) { - module->cc[outNum].numSelected = true; - e.consumed = true; - } - TextField::onMouseDown(e); -} - -void CCTextField::onMouseLeave(EventMouseLeave &e) { - module->cc[outNum].numSelected = false; - e.consumed = true; -} +struct MIDICCToCVInterface : Module { + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + NUM_INPUTS + }; + enum OutputIds { + ENUMS(CC_OUTPUT, 16), + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + MidiInputQueue midiInput; + uint8_t cvs[128] = {}; + ExponentialFilter ccFilters[16]; -void CCTextField::onTextChange() { - if (text.size() > 0) { - try { - int num = std::stoi(text); - // Only allow valid cc numbers - if (num < 0 || num > 127 || text.size() > 3) { - text = ""; - begin = end = 0; - module->cc[outNum].num = -1; - } - else { - module->cc[outNum].num = num; - module->cc[outNum].resetSync(); - } + MIDICCToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} + void step() override { + MidiMessage msg; + while (midiInput.shift(&msg)) { + processMessage(msg); } - catch (...) { - text = ""; - begin = end = 0; - module->cc[outNum].num = -1; - } - }; -} -MIDICCToCVWidget::MIDICCToCVWidget() { - MIDICCToCVInterface *module = new MIDICCToCVInterface(); - setModule(module); - box.size = Vec(16 * 15, 380); - - { - Panel *panel = new LightPanel(); - panel->box.size = box.size; - addChild(panel); + float lambda = 100.f * engineGetSampleTime(); + for (int i = 0; i < 16; i++) { + float value = rescale(cvs[i], 0, 127, 0.f, 10.f); + ccFilters[i].lambda = lambda; + outputs[CC_OUTPUT + i].value = ccFilters[i].process(value); + } } - float margin = 5; - float labelHeight = 15; - float yPos = margin; + void processMessage(MidiMessage msg) { + // debug("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.data1, msg.data2); - addChild(createScrew(Vec(15, 0))); - addChild(createScrew(Vec(box.size.x - 30, 0))); - addChild(createScrew(Vec(15, 365))); - addChild(createScrew(Vec(box.size.x - 30, 365))); - { - Label *label = new Label(); - label->box.pos = Vec(box.size.x - margin - 11 * 15, margin); - label->text = "MIDI CC to CV"; - addChild(label); - yPos = labelHeight * 2; + switch (msg.status()) { + // cc + case 0xb: { + uint8_t cc = msg.data1 & 0x7f; + uint8_t cv = msg.data2 & 0x7f; + cvs[cc] = cv; + } break; + default: break; + } } - { - Label *label = new Label(); - label->box.pos = Vec(margin, yPos); - label->text = "MIDI Interface"; - addChild(label); - - MidiChoice *midiChoice = new MidiChoice(); - midiChoice->midiModule = dynamic_cast(module); - midiChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos); - midiChoice->box.size.x = (box.size.x / 2.0) - margin; - addChild(midiChoice); - yPos += midiChoice->box.size.y + margin; + json_t *toJson() override { + json_t *rootJ = json_object(); + json_object_set_new(rootJ, "midi", midiInput.toJson()); + return rootJ; } - { - Label *label = new Label(); - label->box.pos = Vec(margin, yPos); - label->text = "Channel"; - addChild(label); - - ChannelChoice *channelChoice = new ChannelChoice(); - channelChoice->midiModule = dynamic_cast(module); - channelChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos); - channelChoice->box.size.x = (box.size.x / 2.0) - margin; - addChild(channelChoice); - yPos += channelChoice->box.size.y + margin * 3; + void fromJson(json_t *rootJ) override { + json_t *midiJ = json_object_get(rootJ, "midi"); + midiInput.fromJson(midiJ); } +}; - for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) { - CCTextField *ccNumChoice = new CCTextField(); - ccNumChoice->module = module; - ccNumChoice->outNum = i; - ccNumChoice->text = std::to_string(module->cc[i].num); - ccNumChoice->box.pos = Vec(11 + (i % 4) * (63), yPos); - ccNumChoice->box.size.x = 29; - - addChild(ccNumChoice); - yPos += labelHeight + margin; - addOutput(createOutput(Vec((i % 4) * (63) + 10, yPos + 5), module, i)); - addChild(createLight>(Vec((i % 4) * (63) + 32, yPos + 5), module, i)); +struct MIDICCToCVInterfaceWidget : ModuleWidget { + MIDICCToCVInterfaceWidget(MIDICCToCVInterface *module) : ModuleWidget(module) { + setPanel(SVG::load(assetGlobal("res/Core/MIDICCToCVInterface.svg"))); + + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addOutput(Port::create(mm2px(Vec(3.894335, 73.344704)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 0)); + addOutput(Port::create(mm2px(Vec(15.494659, 73.344704)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 1)); + addOutput(Port::create(mm2px(Vec(27.094982, 73.344704)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 2)); + addOutput(Port::create(mm2px(Vec(38.693932, 73.344704)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 3)); + addOutput(Port::create(mm2px(Vec(3.8943355, 84.945023)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 4)); + addOutput(Port::create(mm2px(Vec(15.49466, 84.945023)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 5)); + addOutput(Port::create(mm2px(Vec(27.094982, 84.945023)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 6)); + addOutput(Port::create(mm2px(Vec(38.693932, 84.945023)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 7)); + addOutput(Port::create(mm2px(Vec(3.8943343, 96.543976)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 8)); + addOutput(Port::create(mm2px(Vec(15.494659, 96.543976)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 9)); + addOutput(Port::create(mm2px(Vec(27.09498, 96.543976)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 10)); + addOutput(Port::create(mm2px(Vec(38.693932, 96.543976)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 11)); + addOutput(Port::create(mm2px(Vec(3.894335, 108.14429)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 12)); + addOutput(Port::create(mm2px(Vec(15.49466, 108.14429)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 13)); + addOutput(Port::create(mm2px(Vec(27.09498, 108.14429)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 14)); + addOutput(Port::create(mm2px(Vec(38.693932, 108.14429)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 15)); + + MidiWidget *midiWidget = Widget::create(mm2px(Vec(3.399621, 14.837339))); + midiWidget->box.size = mm2px(Vec(44, 54.667)); + midiWidget->midiIO = &module->midiInput; + addChild(midiWidget); - if ((i + 1) % 4 == 0) { - yPos += 47 + margin; - } - else { - yPos -= labelHeight + margin; - } } -} +}; -void MIDICCToCVWidget::step() { - ModuleWidget::step(); -} -#endif \ No newline at end of file +Model *modelMIDICCToCVInterface = Model::create("Core", "MIDICCToCVInterface", "MIDI-CC", MIDI_TAG, EXTERNAL_TAG); diff --git a/src/core/core.cpp b/src/core/core.cpp index 4df1dc4b..566b4488 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -6,7 +6,8 @@ void init(rack::Plugin *p) { p->version = TOSTRING(VERSION); p->addModel(modelAudioInterface); - p->addModel(modelMidiToCvInterface); + p->addModel(modelMIDIToCVInterface); + p->addModel(modelMIDICCToCVInterface); p->addModel(modelBlank); p->addModel(modelNotes); diff --git a/src/core/core.hpp b/src/core/core.hpp index 0415a7a3..2cced2d3 100644 --- a/src/core/core.hpp +++ b/src/core/core.hpp @@ -5,7 +5,8 @@ using namespace rack; extern Model *modelAudioInterface; -extern Model *modelMidiToCvInterface; +extern Model *modelMIDIToCVInterface; +extern Model *modelMIDICCToCVInterface; extern Model *modelBlank; extern Model *modelNotes; diff --git a/src/midi.cpp b/src/midi.cpp index 7f367fec..15eff369 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -8,9 +8,39 @@ namespace rack { // MidiIO //////////////////// +MidiIO::~MidiIO() { + setDriver(-1); +} + +std::vector MidiIO::getDrivers() { + std::vector rtApis; + RtMidi::getCompiledApi(rtApis); + + std::vector drivers; + for (RtMidi::Api api : rtApis) { + drivers.push_back((int) api); + } + // drivers.push_back(BRIDGE_DRIVER) + return drivers; +} + +std::string MidiIO::getDriverName(int driver) { + switch (driver) { + case RtMidi::UNSPECIFIED: return "Unspecified"; + case RtMidi::MACOSX_CORE: return "Core MIDI"; + case RtMidi::LINUX_ALSA: return "ALSA"; + case RtMidi::UNIX_JACK: return "JACK"; + case RtMidi::WINDOWS_MM: return "Windows MIDI"; + case RtMidi::RTMIDI_DUMMY: return "Dummy MIDI"; + // case BRIDGE_DRIVER: return "Bridge"; + default: return "Unknown"; + } +} + int MidiIO::getDeviceCount() { - if (rtMidi) + if (rtMidi) { return rtMidi->getPortCount(); + } return 0; } @@ -18,22 +48,33 @@ std::string MidiIO::getDeviceName(int device) { if (rtMidi) { if (device < 0) return ""; - return rtMidi->getPortName(device); + if (device == this->device) + return deviceName; + else + return rtMidi->getPortName(device); } return ""; } -void MidiIO::openDevice(int device) { +void MidiIO::setDevice(int device) { if (rtMidi) { rtMidi->closePort(); if (device >= 0) { rtMidi->openPort(device); + deviceName = rtMidi->getPortName(device); } this->device = device; } } +std::string MidiIO::getChannelName(int channel) { + if (channel == -1) + return "All channels"; + else + return stringf("Channel %d", channel + 1); +} + bool MidiIO::isActive() { if (rtMidi) return rtMidi->isPortOpen(); @@ -42,20 +83,27 @@ bool MidiIO::isActive() { json_t *MidiIO::toJson() { json_t *rootJ = json_object(); + json_object_set_new(rootJ, "driver", json_integer(driver)); std::string deviceName = getDeviceName(device); - json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); + if (!deviceName.empty()) + json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); json_object_set_new(rootJ, "channel", json_integer(channel)); return rootJ; } void MidiIO::fromJson(json_t *rootJ) { + json_t *driverJ = json_object_get(rootJ, "driver"); + if (driverJ) + setDriver(json_integer_value(driverJ)); + json_t *deviceNameJ = json_object_get(rootJ, "deviceName"); if (deviceNameJ) { std::string deviceName = json_string_value(deviceNameJ); // Search for device with equal name - for (int device = 0; device < getDeviceCount(); device++) { + int deviceCount = getDeviceCount(); + for (int device = 0; device < deviceCount; device++) { if (getDeviceName(device) == deviceName) { - openDevice(device); + setDevice(device); break; } } @@ -77,42 +125,70 @@ static void midiInputCallback(double timeStamp, std::vector *mess MidiInput *midiInput = (MidiInput*) userData; if (!midiInput) return; MidiMessage midiMessage; - midiMessage.time = timeStamp; - midiMessage.data = *message; + if (message->size() >= 1) + midiMessage.cmd = (*message)[0]; + if (message->size() >= 2) + midiMessage.data1 = (*message)[1]; + if (message->size() >= 3) + midiMessage.data2 = (*message)[2]; midiInput->onMessage(midiMessage); } MidiInput::MidiInput() { - rtMidiIn = new RtMidiIn(); - rtMidi = rtMidiIn; - rtMidiIn->setCallback(midiInputCallback, this); + setDriver(RtMidi::UNSPECIFIED); } -MidiInput::~MidiInput() { - delete rtMidiIn; +void MidiInput::setDriver(int driver) { + setDevice(-1); + if (rtMidiIn) { + delete rtMidiIn; + rtMidi = rtMidiIn = NULL; + } + + if (driver >= 0) { + rtMidiIn = new RtMidiIn((RtMidi::Api) driver); + rtMidiIn->setCallback(midiInputCallback, this); + rtMidi = rtMidiIn; + this->driver = rtMidiIn->getCurrentApi(); + } } void MidiInputQueue::onMessage(const MidiMessage &message) { - for (uint8_t d : message.data) { - debug("MIDI message: %02x", d); - } + if ((int) queue.size() < queueSize) + queue.push(message); +} - const int messageQueueSize = 8192; - if (messageQueue.size() < messageQueueSize) - messageQueue.push(message); +bool MidiInputQueue::shift(MidiMessage *message) { + if (!message) return false; + if (!queue.empty()) { + *message = queue.front(); + queue.pop(); + return true; + } + return false; } + //////////////////// // MidiOutput //////////////////// MidiOutput::MidiOutput() { - rtMidiOut = new RtMidiOut(); - rtMidi = rtMidiOut; + setDriver(RtMidi::UNSPECIFIED); } -MidiOutput::~MidiOutput() { - delete rtMidiOut; +void MidiOutput::setDriver(int driver) { + setDevice(-1); + if (rtMidiOut) { + delete rtMidiOut; + rtMidi = rtMidiOut = NULL; + } + + if (driver >= 0) { + rtMidiOut = new RtMidiOut((RtMidi::Api) driver); + rtMidi = rtMidiOut; + this->driver = rtMidiOut->getCurrentApi(); + } } diff --git a/src/ui/Scene.cpp b/src/ui/Scene.cpp index 64a003f2..01742c0c 100644 --- a/src/ui/Scene.cpp +++ b/src/ui/Scene.cpp @@ -1,4 +1,4 @@ -#include "widgets.hpp" +#include "ui.hpp" #include "window.hpp" diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp index cf17fcea..09c27024 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp @@ -48,8 +48,10 @@ void TextField::onFocus(EventFocus &e) { } void TextField::onText(EventText &e) { - std::string newText(1, (char) e.codepoint); - insertText(newText); + if (e.codepoint < 128) { + std::string newText(1, (char) e.codepoint); + insertText(newText); + } e.consumed = true; } @@ -110,6 +112,15 @@ void TextField::onKey(EventKey &e) { insertText(newText); } break; + case GLFW_KEY_X: + if (windowIsModPressed()) { + if (begin < end) { + std::string selectedText = text.substr(begin, end - begin); + glfwSetClipboardString(gWindow, selectedText.c_str()); + insertText(""); + } + } + break; case GLFW_KEY_C: if (windowIsModPressed()) { if (begin < end) { @@ -118,6 +129,12 @@ void TextField::onKey(EventKey &e) { } } break; + case GLFW_KEY_A: + if (windowIsModPressed()) { + begin = 0; + end = text.size(); + } + break; case GLFW_KEY_ENTER: if (multiline) { insertText("\n");