diff --git a/include/audio.hpp b/include/audio.hpp index b967410f..daf8f833 100644 --- a/include/audio.hpp +++ b/include/audio.hpp @@ -36,6 +36,8 @@ struct AudioIO { std::string getDeviceDetail(int device); void openStream(); void closeStream(); + /** Returns whether the audio stream is open and running */ + bool isActive(); std::vector listSampleRates(); diff --git a/include/engine.hpp b/include/engine.hpp index df0edf17..9618504a 100644 --- a/include/engine.hpp +++ b/include/engine.hpp @@ -63,8 +63,9 @@ struct Module { /** Advances the module by 1 audio frame with duration 1.0 / gSampleRate */ virtual void step() {} - virtual void onSampleRateChange() {} + /** Called when the engine sample rate is changed */ + virtual void onSampleRateChange() {} /** Called when module is created by the Add Module popup, cloning, or when loading a patch or autosave */ virtual void onCreate() {} /** Called when user explicitly deletes the module, not when Rack is closed or a new patch is loaded */ diff --git a/include/midi.hpp b/include/midi.hpp index 342a5b55..c1e70cbd 100644 --- a/include/midi.hpp +++ b/include/midi.hpp @@ -22,7 +22,7 @@ struct MidiMessage { struct MidiIO { - int port = -1; + int device = -1; /* For MIDI output, the channel to output messages. For MIDI input, the channel to filter. Set to -1 to allow all MIDI channels (for input). @@ -32,9 +32,11 @@ struct MidiIO { RtMidi *rtMidi = NULL; virtual ~MidiIO() {} - int getPortCount(); - std::string getPortName(int port); - void openPort(int port); + int getDeviceCount(); + std::string getDeviceName(int device); + void openDevice(int device); + /** Returns whether the audio stream is open and running */ + bool isActive(); json_t *toJson(); void fromJson(json_t *rootJ); }; diff --git a/src/app/MidiWidget.cpp b/src/app/MidiWidget.cpp index 5f832278..b3826c3c 100644 --- a/src/app/MidiWidget.cpp +++ b/src/app/MidiWidget.cpp @@ -5,11 +5,11 @@ namespace rack { -struct MidiPortItem : MenuItem { +struct MidiDeviceItem : MenuItem { MidiIO *midiIO; - int port; + int device; void onAction(EventAction &e) override { - midiIO->openPort(port); + midiIO->openDevice(device); } }; @@ -31,13 +31,21 @@ void MidiWidget::onMouseDown(EventMouseDown &e) { Menu *menu = gScene->createMenu(); - menu->addChild(construct(&MenuLabel::text, "MIDI port")); - for (int port = 0; port < midiIO->getPortCount(); port++) { - MidiPortItem *item = new MidiPortItem(); + menu->addChild(construct(&MenuLabel::text, "MIDI device")); + { + MidiDeviceItem *item = new MidiDeviceItem(); item->midiIO = midiIO; - item->port = port; - item->text = midiIO->getPortName(port); - item->rightText = CHECKMARK(item->port == midiIO->port); + 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()); diff --git a/src/audio.cpp b/src/audio.cpp index 812951eb..1e652afd 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -216,6 +216,14 @@ void AudioIO::closeStream() { onCloseStream(); } +bool AudioIO::isActive() { + if (rtAudio) + return rtAudio->isStreamRunning(); + // TODO Bridge + return false; +} + + std::vector AudioIO::listSampleRates() { if (rtAudio) { try { diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index e4edc563..994a16bf 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -18,7 +18,7 @@ #define MAX_OUTPUTS 8 #define MAX_INPUTS 8 -static auto audioTimeout = std::chrono::milliseconds(100); +static const auto audioTimeout = std::chrono::milliseconds(100); using namespace rack; @@ -45,7 +45,7 @@ struct AudioInterfaceIO : AudioIO { void processStream(const float *input, float *output, int length) override { if (numInputs > 0) { - // TODO Do we need to wait on the input to be consumed here? + // TODO Do we need to wait on the input to be consumed here? Experimentally, it works fine if we don't. for (int i = 0; i < length; i++) { if (inputBuffer.full()) break; @@ -97,8 +97,13 @@ struct AudioInterface : Module { ENUMS(AUDIO_OUTPUT, MAX_OUTPUTS), NUM_OUTPUTS }; + enum LightIds { + ACTIVE_LIGHT, + NUM_LIGHTS + }; AudioInterfaceIO audioIO; + int lastSampleRate = 0; SampleRateConverter inputSrc; SampleRateConverter outputSrc; @@ -107,7 +112,8 @@ struct AudioInterface : Module { DoubleRingBuffer, 16> inputBuffer; DoubleRingBuffer, 16> outputBuffer; - AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { + AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onSampleRateChange(); } void step() override; @@ -123,6 +129,17 @@ struct AudioInterface : Module { audioIO.fromJson(audioJ); } + void onSampleRateChange() override { + // for (int i = 0; i < MAX_INPUTS; i++) { + // inputSrc[i].setRates(audioIO.sampleRate, engineGetSampleRate()); + // } + // for (int i = 0; i < MAX_OUTPUTS; i++) { + // outputSrc[i].setRates(engineGetSampleRate(), audioIO.sampleRate); + // } + inputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); + outputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); + } + void onReset() override { audioIO.closeStream(); } @@ -133,9 +150,14 @@ void AudioInterface::step() { Frame inputFrame; memset(&inputFrame, 0, sizeof(inputFrame)); + // Update sample rate if changed by audio driver + if (audioIO.sampleRate != lastSampleRate) { + onSampleRateChange(); + lastSampleRate = audioIO.sampleRate; + } + if (audioIO.numInputs > 0) { if (inputBuffer.empty()) { - inputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); int inLen = audioIO.inputBuffer.size(); int outLen = inputBuffer.capacity(); inputSrc.process(audioIO.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); @@ -169,7 +191,6 @@ void AudioInterface::step() { }; if (audioIO.engineCv.wait_for(lock, audioTimeout, cond)) { // Push converted output - outputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); int inLen = outputBuffer.size(); int outLen = audioIO.outputBuffer.capacity(); outputSrc.process(outputBuffer.startData(), &inLen, audioIO.outputBuffer.endData(), &outLen); @@ -183,6 +204,9 @@ void AudioInterface::step() { } audioIO.audioCv.notify_all(); + + // Lights + lights[ACTIVE_LIGHT].value = audioIO.isActive() ? 1.0 : 0.0; } @@ -196,10 +220,10 @@ AudioInterfaceWidget::AudioInterfaceWidget() { addChild(panel); } - // 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))); + 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))); Vec margin = Vec(5, 2); float labelHeight = 15; @@ -251,7 +275,8 @@ AudioInterfaceWidget::AudioInterfaceWidget() { yPos += 5; xPos = 10; for (int i = 0; i < 4; i++) { - addOutput(createOutput(Vec(xPos, yPos), module, AudioInterface::AUDIO_OUTPUT + i)); + Port *port = createOutput(Vec(xPos, yPos), module, AudioInterface::AUDIO_OUTPUT + i); + addOutput(port); Label *label = new Label(); label->box.pos = Vec(xPos + 4, yPos + 28); label->text = stringf("%d", i + 1); @@ -277,4 +302,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { AudioWidget *audioWidget = construct(); audioWidget->audioIO = &module->audioIO; addChild(audioWidget); + + // Lights + addChild(createLight>(Vec(40, 20), module, AudioInterface::ACTIVE_LIGHT)); } diff --git a/src/core/MidiToCV.cpp b/src/core/MidiToCV.cpp index ba3620c1..cd2b8900 100644 --- a/src/core/MidiToCV.cpp +++ b/src/core/MidiToCV.cpp @@ -15,6 +15,7 @@ struct MidiValue { bool changed = false; // Value has been changed by midi message (only if it is in sync!) }; + struct MIDIToCVInterface : Module { enum ParamIds { RESET_PARAM, @@ -33,6 +34,7 @@ struct MIDIToCVInterface : Module { NUM_OUTPUTS }; enum LightIds { + ACTIVE_LIGHT, RESET_LIGHT, NUM_LIGHTS }; @@ -136,6 +138,9 @@ void MIDIToCVInterface::step() { outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; */ + + // Lights + lights[ACTIVE_LIGHT].value = midiInput.isActive() ? 1.0 : 0.0; } void MIDIToCVInterface::pressNote(int note) { @@ -272,4 +277,7 @@ MidiToCVWidget::MidiToCVWidget() { MidiWidget *midiWidget = construct(); midiWidget->midiIO = &module->midiInput; addChild(midiWidget); + + // Lights + addChild(createLight>(Vec(40, 20), module, MIDIToCVInterface::ACTIVE_LIGHT)); } diff --git a/src/midi.cpp b/src/midi.cpp index bbb2a9a1..1fb6ae05 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -8,41 +8,54 @@ namespace rack { // MidiIO //////////////////// -int MidiIO::getPortCount() { - return rtMidi->getPortCount(); +int MidiIO::getDeviceCount() { + if (rtMidi) + return rtMidi->getPortCount(); + return 0; } -std::string MidiIO::getPortName(int port) { - if (port < 0) - return ""; - return rtMidi->getPortName(port); +std::string MidiIO::getDeviceName(int device) { + if (rtMidi) { + if (device < 0) + return ""; + return rtMidi->getPortName(device); + } + return ""; } -void MidiIO::openPort(int port) { - rtMidi->closePort(); +void MidiIO::openDevice(int device) { + if (rtMidi) { + rtMidi->closePort(); - if (port >= 0) { - rtMidi->openPort(port); + if (device >= 0) { + rtMidi->openPort(device); + } + this->device = device; } - this->port = port; +} + +bool MidiIO::isActive() { + if (rtMidi) + return rtMidi->isPortOpen(); + return false; } json_t *MidiIO::toJson() { json_t *rootJ = json_object(); - std::string portName = getPortName(port); - json_object_set_new(rootJ, "port", json_string(portName.c_str())); + std::string deviceName = getDeviceName(device); + json_object_set_new(rootJ, "device", json_string(deviceName.c_str())); json_object_set_new(rootJ, "channel", json_integer(channel)); return rootJ; } void MidiIO::fromJson(json_t *rootJ) { - json_t *portNameJ = json_object_get(rootJ, "port"); - if (portNameJ) { - std::string portName = json_string_value(portNameJ); - // Search for port with equal name - for (int port = 0; port < getPortCount(); port++) { - if (getPortName(port) == portName) { - openPort(port); + json_t *deviceNameJ = json_object_get(rootJ, "device"); + if (deviceNameJ) { + std::string deviceName = json_string_value(deviceNameJ); + // Search for device with equal name + for (int device = 0; device < getDeviceCount(); device++) { + if (getDeviceName(device) == deviceName) { + openDevice(device); break; } }