| @@ -1,5 +1,7 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <jansson.h> | |||||
| #pragma GCC diagnostic push | #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wsuggest-override" | #pragma GCC diagnostic ignored "-Wsuggest-override" | ||||
| #include <RtAudio.h> | #include <RtAudio.h> | ||||
| @@ -13,20 +15,20 @@ struct AudioIO { | |||||
| int maxOutputs = 8; | int maxOutputs = 8; | ||||
| int maxInputs = 8; | int maxInputs = 8; | ||||
| RtAudio *stream = NULL; | |||||
| // Stream properties | // Stream properties | ||||
| int driver = 0; | |||||
| int device = -1; | int device = -1; | ||||
| int sampleRate = 44100; | int sampleRate = 44100; | ||||
| int blockSize = 256; | int blockSize = 256; | ||||
| int numOutputs = 0; | int numOutputs = 0; | ||||
| int numInputs = 0; | int numInputs = 0; | ||||
| RtAudio *rtAudio = NULL; | |||||
| AudioIO(); | AudioIO(); | ||||
| virtual ~AudioIO(); | virtual ~AudioIO(); | ||||
| std::vector<int> listDrivers(); | std::vector<int> listDrivers(); | ||||
| std::string getDriverName(int driver); | std::string getDriverName(int driver); | ||||
| int getDriver(); | |||||
| void setDriver(int driver); | void setDriver(int driver); | ||||
| int getDeviceCount(); | int getDeviceCount(); | ||||
| @@ -40,6 +42,8 @@ struct AudioIO { | |||||
| virtual void processStream(const float *input, float *output, int length) {} | virtual void processStream(const float *input, float *output, int length) {} | ||||
| virtual void onCloseStream() {} | virtual void onCloseStream() {} | ||||
| virtual void onOpenStream() {} | virtual void onOpenStream() {} | ||||
| json_t *toJson(); | |||||
| void fromJson(json_t *rootJ); | |||||
| }; | }; | ||||
| @@ -1,5 +1,11 @@ | |||||
| #pragma once | #pragma once | ||||
| #include "util.hpp" | |||||
| #include <queue> | |||||
| #include <vector> | |||||
| #include <jansson.h> | |||||
| #pragma GCC diagnostic push | #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wsuggest-override" | #pragma GCC diagnostic ignored "-Wsuggest-override" | ||||
| #include "rtmidi/RtMidi.h" | #include "rtmidi/RtMidi.h" | ||||
| @@ -9,8 +15,13 @@ | |||||
| namespace rack { | namespace rack { | ||||
| struct MidiMessage { | |||||
| double time; | |||||
| std::vector<uint8_t> data; | |||||
| }; | |||||
| struct MidiIO { | struct MidiIO { | ||||
| RtMidi *midi; | |||||
| int port = -1; | int port = -1; | ||||
| /* For MIDI output, the channel to output messages. | /* For MIDI output, the channel to output messages. | ||||
| For MIDI input, the channel to filter. | For MIDI input, the channel to filter. | ||||
| @@ -18,17 +29,27 @@ struct MidiIO { | |||||
| Zero indexed. | Zero indexed. | ||||
| */ | */ | ||||
| int channel = -1; | int channel = -1; | ||||
| RtMidi *rtMidi = NULL; | |||||
| virtual ~MidiIO() {} | virtual ~MidiIO() {} | ||||
| virtual int getPortCount(); | |||||
| virtual std::string getPortName(int port); | |||||
| virtual void openPort(int port); | |||||
| int getPortCount(); | |||||
| std::string getPortName(int port); | |||||
| void openPort(int port); | |||||
| json_t *toJson(); | |||||
| void fromJson(json_t *rootJ); | |||||
| }; | }; | ||||
| struct MidiInput : MidiIO { | struct MidiInput : MidiIO { | ||||
| MidiInput(); | MidiInput(); | ||||
| ~MidiInput(); | ~MidiInput(); | ||||
| virtual void onMessage(const MidiMessage &message) {} | |||||
| }; | |||||
| struct MidiInputQueue : MidiInput { | |||||
| std::queue<MidiMessage> messageQueue; | |||||
| void onMessage(const MidiMessage &message) override; | |||||
| }; | }; | ||||
| @@ -59,7 +59,7 @@ void AudioWidget::onMouseDown(EventMouseDown &e) { | |||||
| item->audioIO = audioIO; | item->audioIO = audioIO; | ||||
| item->driver = driver; | item->driver = driver; | ||||
| item->text = audioIO->getDriverName(driver); | item->text = audioIO->getDriverName(driver); | ||||
| item->rightText = CHECKMARK(item->driver == audioIO->getDriver()); | |||||
| item->rightText = CHECKMARK(item->driver == audioIO->driver); | |||||
| menu->addChild(item); | menu->addChild(item); | ||||
| } | } | ||||
| menu->addChild(construct<MenuEntry>()); | menu->addChild(construct<MenuEntry>()); | ||||
| @@ -110,7 +110,7 @@ void RackWidget::savePatch(std::string path) { | |||||
| FILE *file = fopen(path.c_str(), "w"); | FILE *file = fopen(path.c_str(), "w"); | ||||
| if (file) { | if (file) { | ||||
| json_dumpf(rootJ, file, JSON_INDENT(2)); | |||||
| json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||||
| fclose(file); | fclose(file); | ||||
| } | } | ||||
| @@ -3,6 +3,9 @@ | |||||
| #include "audio.hpp" | #include "audio.hpp" | ||||
| #define DRIVER_BRIDGE -1 | |||||
| namespace rack { | namespace rack { | ||||
| @@ -20,6 +23,8 @@ std::vector<int> AudioIO::listDrivers() { | |||||
| std::vector<int> drivers; | std::vector<int> drivers; | ||||
| for (RtAudio::Api api : apis) | for (RtAudio::Api api : apis) | ||||
| drivers.push_back((int) api); | drivers.push_back((int) api); | ||||
| // Add Bridge fake driver | |||||
| // drivers.push_back(DRIVER_BRIDGE); | |||||
| return drivers; | return drivers; | ||||
| } | } | ||||
| @@ -35,55 +40,71 @@ std::string AudioIO::getDriverName(int driver) { | |||||
| case RtAudio::WINDOWS_ASIO: return "ASIO"; | case RtAudio::WINDOWS_ASIO: return "ASIO"; | ||||
| case RtAudio::WINDOWS_DS: return "DirectSound"; | case RtAudio::WINDOWS_DS: return "DirectSound"; | ||||
| case RtAudio::RTAUDIO_DUMMY: return "Dummy"; | case RtAudio::RTAUDIO_DUMMY: return "Dummy"; | ||||
| case DRIVER_BRIDGE: return "VCV Bridge"; | |||||
| default: return "Unknown"; | default: return "Unknown"; | ||||
| } | } | ||||
| } | } | ||||
| int AudioIO::getDriver() { | |||||
| if (!stream) | |||||
| return RtAudio::UNSPECIFIED; | |||||
| return stream->getCurrentApi(); | |||||
| } | |||||
| void AudioIO::setDriver(int driver) { | void AudioIO::setDriver(int driver) { | ||||
| // Close driver | |||||
| closeStream(); | closeStream(); | ||||
| if (stream) | |||||
| delete stream; | |||||
| stream = new RtAudio((RtAudio::Api) driver); | |||||
| if (rtAudio) { | |||||
| delete rtAudio; | |||||
| rtAudio = NULL; | |||||
| } | |||||
| this->driver = 0; | |||||
| // Open driver | |||||
| if (driver >= 0) { | |||||
| rtAudio = new RtAudio((RtAudio::Api) driver); | |||||
| this->driver = (int) rtAudio->getCurrentApi(); | |||||
| } | |||||
| else if (driver == DRIVER_BRIDGE) { | |||||
| // TODO Connect to Bridge | |||||
| this->driver = DRIVER_BRIDGE; | |||||
| } | |||||
| } | } | ||||
| int AudioIO::getDeviceCount() { | int AudioIO::getDeviceCount() { | ||||
| if (!stream) | |||||
| return 0; | |||||
| return stream->getDeviceCount(); | |||||
| if (rtAudio) { | |||||
| return rtAudio->getDeviceCount(); | |||||
| } | |||||
| if (driver == DRIVER_BRIDGE) { | |||||
| return 16; | |||||
| } | |||||
| return 0; | |||||
| } | } | ||||
| std::string AudioIO::getDeviceName(int device) { | std::string AudioIO::getDeviceName(int device) { | ||||
| if (!stream || device < 0) | |||||
| return ""; | |||||
| try { | |||||
| RtAudio::DeviceInfo deviceInfo = stream->getDeviceInfo(device); | |||||
| return deviceInfo.name; | |||||
| if (rtAudio) { | |||||
| try { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); | |||||
| return deviceInfo.name; | |||||
| } | |||||
| catch (RtAudioError &e) { | |||||
| warn("Failed to query RtAudio device: %s", e.what()); | |||||
| } | |||||
| } | } | ||||
| catch (RtAudioError &e) { | |||||
| warn("Failed to query audio device: %s", e.what()); | |||||
| return ""; | |||||
| if (driver == DRIVER_BRIDGE) { | |||||
| return stringf("%d", device + 1); | |||||
| } | } | ||||
| return ""; | |||||
| } | } | ||||
| std::string AudioIO::getDeviceDetail(int device) { | std::string AudioIO::getDeviceDetail(int device) { | ||||
| if (!stream || device < 0) | |||||
| return ""; | |||||
| try { | |||||
| RtAudio::DeviceInfo deviceInfo = stream->getDeviceInfo(device); | |||||
| return stringf("%s (%d in, %d out)", deviceInfo.name.c_str(), deviceInfo.inputChannels, deviceInfo.outputChannels); | |||||
| if (rtAudio) { | |||||
| try { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); | |||||
| return stringf("%s (%d in, %d out)", deviceInfo.name.c_str(), deviceInfo.inputChannels, deviceInfo.outputChannels); | |||||
| } | |||||
| catch (RtAudioError &e) { | |||||
| warn("Failed to query RtAudio device: %s", e.what()); | |||||
| } | |||||
| } | } | ||||
| catch (RtAudioError &e) { | |||||
| warn("Failed to query audio device: %s", e.what()); | |||||
| return ""; | |||||
| if (driver == DRIVER_BRIDGE) { | |||||
| return stringf("Channel %d", device + 1); | |||||
| } | } | ||||
| return ""; | |||||
| } | } | ||||
| static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData) { | static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData) { | ||||
| @@ -97,17 +118,18 @@ void AudioIO::openStream() { | |||||
| // Close device but remember the current device number | // Close device but remember the current device number | ||||
| int device = this->device; | int device = this->device; | ||||
| closeStream(); | closeStream(); | ||||
| if (!stream) | |||||
| if (device < 0) | |||||
| return; | return; | ||||
| // Open new device | |||||
| if (device >= 0) { | |||||
| if (rtAudio) { | |||||
| // Open new device | |||||
| RtAudio::DeviceInfo deviceInfo; | RtAudio::DeviceInfo deviceInfo; | ||||
| try { | try { | ||||
| deviceInfo = stream->getDeviceInfo(device); | |||||
| deviceInfo = rtAudio->getDeviceInfo(device); | |||||
| } | } | ||||
| catch (RtAudioError &e) { | catch (RtAudioError &e) { | ||||
| warn("Failed to query audio device: %s", e.what()); | |||||
| warn("Failed to query RtAudio device: %s", e.what()); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -115,7 +137,7 @@ void AudioIO::openStream() { | |||||
| numInputs = mini(deviceInfo.inputChannels, maxInputs); | numInputs = mini(deviceInfo.inputChannels, maxInputs); | ||||
| if (numOutputs == 0 && numInputs == 0) { | if (numOutputs == 0 && numInputs == 0) { | ||||
| warn("Audio device %d has 0 inputs and 0 outputs"); | |||||
| warn("RtAudio device %d has 0 inputs and 0 outputs"); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -138,56 +160,56 @@ void AudioIO::openStream() { | |||||
| } | } | ||||
| try { | try { | ||||
| debug("Opening audio stream %d", device); | |||||
| stream->openStream( | |||||
| debug("Opening audio RtAudio device %d", device); | |||||
| rtAudio->openStream( | |||||
| numOutputs == 0 ? NULL : &outParameters, | numOutputs == 0 ? NULL : &outParameters, | ||||
| numInputs == 0 ? NULL : &inParameters, | numInputs == 0 ? NULL : &inParameters, | ||||
| RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL); | RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL); | ||||
| } | } | ||||
| catch (RtAudioError &e) { | catch (RtAudioError &e) { | ||||
| warn("Failed to open audio stream: %s", e.what()); | |||||
| warn("Failed to open RtAudio stream: %s", e.what()); | |||||
| return; | return; | ||||
| } | } | ||||
| try { | try { | ||||
| debug("Starting audio stream %d", device); | |||||
| stream->startStream(); | |||||
| debug("Starting RtAudio stream %d", device); | |||||
| rtAudio->startStream(); | |||||
| } | } | ||||
| catch (RtAudioError &e) { | catch (RtAudioError &e) { | ||||
| warn("Failed to start audio stream: %s", e.what()); | |||||
| warn("Failed to start RtAudio stream: %s", e.what()); | |||||
| return; | return; | ||||
| } | } | ||||
| // Update sample rate because this may have changed | // Update sample rate because this may have changed | ||||
| this->sampleRate = stream->getStreamSampleRate(); | |||||
| this->sampleRate = rtAudio->getStreamSampleRate(); | |||||
| this->device = device; | this->device = device; | ||||
| onOpenStream(); | onOpenStream(); | ||||
| } | } | ||||
| } | } | ||||
| void AudioIO::closeStream() { | void AudioIO::closeStream() { | ||||
| if (stream) { | |||||
| if (stream->isStreamRunning()) { | |||||
| debug("Stopping audio stream %d", device); | |||||
| if (rtAudio) { | |||||
| if (rtAudio->isStreamRunning()) { | |||||
| debug("Stopping RtAudio stream %d", device); | |||||
| try { | try { | ||||
| stream->stopStream(); | |||||
| rtAudio->stopStream(); | |||||
| } | } | ||||
| catch (RtAudioError &e) { | catch (RtAudioError &e) { | ||||
| warn("Failed to stop stream %s", e.what()); | |||||
| warn("Failed to stop RtAudio stream %s", e.what()); | |||||
| } | } | ||||
| } | } | ||||
| if (stream->isStreamOpen()) { | |||||
| debug("Closing audio stream %d", device); | |||||
| if (rtAudio->isStreamOpen()) { | |||||
| debug("Closing RtAudio stream %d", device); | |||||
| try { | try { | ||||
| stream->closeStream(); | |||||
| rtAudio->closeStream(); | |||||
| } | } | ||||
| catch (RtAudioError &e) { | catch (RtAudioError &e) { | ||||
| warn("Failed to close stream %s", e.what()); | |||||
| warn("Failed to close RtAudio stream %s", e.what()); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| // Reset stream settings | |||||
| // Reset rtAudio settings | |||||
| device = -1; | device = -1; | ||||
| numOutputs = 0; | numOutputs = 0; | ||||
| numInputs = 0; | numInputs = 0; | ||||
| @@ -195,18 +217,59 @@ void AudioIO::closeStream() { | |||||
| } | } | ||||
| std::vector<int> AudioIO::listSampleRates() { | std::vector<int> AudioIO::listSampleRates() { | ||||
| if (!stream || device < 0) | |||||
| return {}; | |||||
| try { | |||||
| RtAudio::DeviceInfo deviceInfo = stream->getDeviceInfo(device); | |||||
| std::vector<int> sampleRates(deviceInfo.sampleRates.begin(), deviceInfo.sampleRates.end()); | |||||
| return sampleRates; | |||||
| if (rtAudio) { | |||||
| try { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); | |||||
| std::vector<int> sampleRates(deviceInfo.sampleRates.begin(), deviceInfo.sampleRates.end()); | |||||
| return sampleRates; | |||||
| } | |||||
| catch (RtAudioError &e) { | |||||
| warn("Failed to query RtAudio device: %s", e.what()); | |||||
| } | |||||
| } | } | ||||
| catch (RtAudioError &e) { | |||||
| warn("Failed to query audio device: %s", e.what()); | |||||
| return {}; | |||||
| if (driver == DRIVER_BRIDGE) { | |||||
| return {44100, 48000, 88200, 96000, 176400, 192000}; | |||||
| } | } | ||||
| return {}; | |||||
| } | |||||
| json_t *AudioIO::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())); | |||||
| json_object_set_new(rootJ, "sampleRate", json_integer(sampleRate)); | |||||
| json_object_set_new(rootJ, "blockSize", json_integer(blockSize)); | |||||
| return rootJ; | |||||
| } | |||||
| void AudioIO::fromJson(json_t *rootJ) { | |||||
| json_t *driverJ = json_object_get(rootJ, "driver"); | |||||
| if (driverJ) | |||||
| setDriver(json_number_value(driverJ)); | |||||
| json_t *deviceNameJ = json_object_get(rootJ, "deviceName"); | |||||
| if (deviceNameJ) { | |||||
| std::string deviceName = json_string_value(deviceNameJ); | |||||
| // Search for device ID with equal name | |||||
| for (int device = 0; device < getDeviceCount(); device++) { | |||||
| if (getDeviceName(device) == deviceName) { | |||||
| this->device = device; | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); | |||||
| if (sampleRateJ) | |||||
| sampleRate = json_integer_value(sampleRateJ); | |||||
| json_t *blockSizeJ = json_object_get(rootJ, "blockSize"); | |||||
| if (blockSizeJ) | |||||
| blockSize = json_integer_value(blockSizeJ); | |||||
| openStream(); | |||||
| } | } | ||||
| @@ -114,40 +114,13 @@ struct AudioInterface : Module { | |||||
| json_t *toJson() override { | json_t *toJson() override { | ||||
| json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
| json_object_set_new(rootJ, "driver", json_integer(audioIO.getDriver())); | |||||
| std::string deviceName = audioIO.getDeviceName(audioIO.device); | |||||
| json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); | |||||
| json_object_set_new(rootJ, "sampleRate", json_integer(audioIO.sampleRate)); | |||||
| json_object_set_new(rootJ, "blockSize", json_integer(audioIO.blockSize)); | |||||
| json_object_set_new(rootJ, "audio", audioIO.toJson()); | |||||
| return rootJ; | return rootJ; | ||||
| } | } | ||||
| void fromJson(json_t *rootJ) override { | void fromJson(json_t *rootJ) override { | ||||
| json_t *driverJ = json_object_get(rootJ, "driver"); | |||||
| if (driverJ) | |||||
| audioIO.setDriver(json_number_value(driverJ)); | |||||
| json_t *deviceNameJ = json_object_get(rootJ, "deviceName"); | |||||
| if (deviceNameJ) { | |||||
| std::string deviceName = json_string_value(deviceNameJ); | |||||
| // Search for device ID with equal name | |||||
| for (int device = 0; device < audioIO.getDeviceCount(); device++) { | |||||
| if (audioIO.getDeviceName(device) == deviceName) { | |||||
| audioIO.device = device; | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); | |||||
| if (sampleRateJ) | |||||
| audioIO.sampleRate = json_integer_value(sampleRateJ); | |||||
| json_t *blockSizeJ = json_object_get(rootJ, "blockSize"); | |||||
| if (blockSizeJ) | |||||
| audioIO.blockSize = json_integer_value(blockSizeJ); | |||||
| audioIO.openStream(); | |||||
| json_t *audioJ = json_object_get(rootJ, "audio"); | |||||
| audioIO.fromJson(audioJ); | |||||
| } | } | ||||
| void onReset() override { | void onReset() override { | ||||
| @@ -236,7 +209,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||||
| { | { | ||||
| Label *label = new Label(); | Label *label = new Label(); | ||||
| label->box.pos = Vec(margin.x, yPos); | label->box.pos = Vec(margin.x, yPos); | ||||
| label->text = "Outputs"; | |||||
| label->text = "Outputs (DACs)"; | |||||
| addChild(label); | addChild(label); | ||||
| yPos += labelHeight + margin.y; | yPos += labelHeight + margin.y; | ||||
| } | } | ||||
| @@ -270,7 +243,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||||
| { | { | ||||
| Label *label = new Label(); | Label *label = new Label(); | ||||
| label->box.pos = Vec(margin.x, yPos); | label->box.pos = Vec(margin.x, yPos); | ||||
| label->text = "Inputs"; | |||||
| label->text = "Inputs (ADCs)"; | |||||
| addChild(label); | addChild(label); | ||||
| yPos += labelHeight + margin.y; | yPos += labelHeight + margin.y; | ||||
| } | } | ||||
| @@ -1,44 +0,0 @@ | |||||
| #include "core.hpp" | |||||
| using namespace rack; | |||||
| struct Bridge : Module { | |||||
| enum ParamIds { | |||||
| NUM_PARAMS | |||||
| }; | |||||
| enum InputIds { | |||||
| NUM_INPUTS | |||||
| }; | |||||
| enum OutputIds { | |||||
| NUM_OUTPUTS | |||||
| }; | |||||
| Bridge() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { | |||||
| } | |||||
| ~Bridge() { | |||||
| } | |||||
| void step() override; | |||||
| }; | |||||
| void Bridge::step() { | |||||
| } | |||||
| BridgeWidget::BridgeWidget() { | |||||
| Bridge *module = new Bridge(); | |||||
| setModule(module); | |||||
| box.size = Vec(15*8, 380); | |||||
| { | |||||
| Panel *panel = new LightPanel(); | |||||
| panel->box.size = box.size; | |||||
| addChild(panel); | |||||
| } | |||||
| addChild(createScrew<ScrewSilver>(Vec(15, 0))); | |||||
| addChild(createScrew<ScrewSilver>(Vec(box.size.x-30, 0))); | |||||
| addChild(createScrew<ScrewSilver>(Vec(15, 365))); | |||||
| addChild(createScrew<ScrewSilver>(Vec(box.size.x-30, 365))); | |||||
| } | |||||
| @@ -1,4 +1,3 @@ | |||||
| #if 0 | |||||
| #include <list> | #include <list> | ||||
| #include <algorithm> | #include <algorithm> | ||||
| #include "core.hpp" | #include "core.hpp" | ||||
| @@ -12,7 +11,7 @@ | |||||
| */ | */ | ||||
| struct MidiValue { | struct MidiValue { | ||||
| int val = 0; // Controller value | int val = 0; // Controller value | ||||
| TransitionSmoother tSmooth; | |||||
| // TransitionSmoother tSmooth; | |||||
| bool changed = false; // Value has been changed by midi message (only if it is in sync!) | bool changed = false; // Value has been changed by midi message (only if it is in sync!) | ||||
| }; | }; | ||||
| @@ -38,6 +37,7 @@ struct MIDIToCVInterface : Module { | |||||
| NUM_LIGHTS | NUM_LIGHTS | ||||
| }; | }; | ||||
| MidiInputQueue midiInput; | |||||
| std::list<int> notes; | std::list<int> notes; | ||||
| bool pedal = false; | bool pedal = false; | ||||
| int note = 60; // C4, most modules should use 261.626 Hz | int note = 60; // C4, most modules should use 261.626 Hz | ||||
| @@ -51,7 +51,7 @@ struct MIDIToCVInterface : Module { | |||||
| MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | ||||
| pitchWheel.val = 64; | pitchWheel.val = 64; | ||||
| pitchWheel.tSmooth.set(0, 0); | |||||
| // pitchWheel.tSmooth.set(0, 0); | |||||
| } | } | ||||
| ~MIDIToCVInterface() { | ~MIDIToCVInterface() { | ||||
| @@ -67,22 +67,22 @@ struct MIDIToCVInterface : Module { | |||||
| json_t *toJson() override { | json_t *toJson() override { | ||||
| json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
| addBaseJson(rootJ); | |||||
| // addBaseJson(rootJ); | |||||
| return rootJ; | return rootJ; | ||||
| } | } | ||||
| void fromJson(json_t *rootJ) override { | void fromJson(json_t *rootJ) override { | ||||
| baseFromJson(rootJ); | |||||
| // baseFromJson(rootJ); | |||||
| } | } | ||||
| void onReset() override { | void onReset() override { | ||||
| resetMidi(); | |||||
| // resetMidi(); | |||||
| } | } | ||||
| void resetMidi() override; | |||||
| // void resetMidi() override; | |||||
| }; | }; | ||||
| /* | |||||
| void MIDIToCVInterface::resetMidi() { | void MIDIToCVInterface::resetMidi() { | ||||
| mod.val = 0; | mod.val = 0; | ||||
| mod.tSmooth.set(0, 0); | mod.tSmooth.set(0, 0); | ||||
| @@ -94,8 +94,10 @@ void MIDIToCVInterface::resetMidi() { | |||||
| gate = false; | gate = false; | ||||
| notes.clear(); | notes.clear(); | ||||
| } | } | ||||
| */ | |||||
| void MIDIToCVInterface::step() { | void MIDIToCVInterface::step() { | ||||
| /* | |||||
| if (isPortOpen()) { | if (isPortOpen()) { | ||||
| std::vector<unsigned char> message; | std::vector<unsigned char> message; | ||||
| @@ -132,11 +134,8 @@ void MIDIToCVInterface::step() { | |||||
| } | } | ||||
| outputs[PITCHWHEEL_OUTPUT].value = pitchWheel.tSmooth.next(); | outputs[PITCHWHEEL_OUTPUT].value = pitchWheel.tSmooth.next(); | ||||
| /* NOTE: I'll leave out value smoothing for after touch for now. I currently don't | |||||
| * have an after touch capable device around and I assume it would require different | |||||
| * smoothing*/ | |||||
| outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; | outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; | ||||
| */ | |||||
| } | } | ||||
| void MIDIToCVInterface::pressNote(int note) { | void MIDIToCVInterface::pressNote(int note) { | ||||
| @@ -171,6 +170,7 @@ void MIDIToCVInterface::releaseNote(int note) { | |||||
| } | } | ||||
| void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | ||||
| /* | |||||
| int channel = msg[0] & 0xf; | int channel = msg[0] & 0xf; | ||||
| int status = (msg[0] >> 4) & 0xf; | int status = (msg[0] >> 4) & 0xf; | ||||
| int data1 = msg[1]; | int data1 = msg[1]; | ||||
| @@ -220,6 +220,7 @@ void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||||
| afterTouch.changed = true; | afterTouch.changed = true; | ||||
| break; | break; | ||||
| } | } | ||||
| */ | |||||
| } | } | ||||
| @@ -254,35 +255,6 @@ MidiToCVWidget::MidiToCVWidget() { | |||||
| addParam(createParam<LEDButton>(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0)); | addParam(createParam<LEDButton>(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0)); | ||||
| addChild(createLight<SmallLight<RedLight>>(Vec(7 * 15 + 5, labelHeight + 5), module, MIDIToCVInterface::RESET_LIGHT)); | addChild(createLight<SmallLight<RedLight>>(Vec(7 * 15 + 5, labelHeight + 5), module, MIDIToCVInterface::RESET_LIGHT)); | ||||
| { | |||||
| Label *label = new Label(); | |||||
| label->box.pos = Vec(margin, yPos); | |||||
| label->text = "MIDI Interface"; | |||||
| addChild(label); | |||||
| yPos += labelHeight + margin; | |||||
| MidiChoice *midiChoice = new MidiChoice(); | |||||
| midiChoice->midiModule = dynamic_cast<MidiIO *>(module); | |||||
| midiChoice->box.pos = Vec(margin, yPos); | |||||
| midiChoice->box.size.x = box.size.x - 10; | |||||
| addChild(midiChoice); | |||||
| yPos += midiChoice->box.size.y + margin; | |||||
| } | |||||
| { | |||||
| Label *label = new Label(); | |||||
| label->box.pos = Vec(margin, yPos); | |||||
| label->text = "Channel"; | |||||
| addChild(label); | |||||
| yPos += labelHeight + margin; | |||||
| ChannelChoice *channelChoice = new ChannelChoice(); | |||||
| channelChoice->midiModule = dynamic_cast<MidiIO *>(module); | |||||
| channelChoice->box.pos = Vec(margin, yPos); | |||||
| channelChoice->box.size.x = box.size.x - 10; | |||||
| addChild(channelChoice); | |||||
| yPos += channelChoice->box.size.y + margin + 15; | |||||
| } | |||||
| std::string labels[MIDIToCVInterface::NUM_OUTPUTS] = {"1V/oct", "Gate", "Velocity", "Mod Wheel", "Pitch Wheel", "Aftertouch"}; | std::string labels[MIDIToCVInterface::NUM_OUTPUTS] = {"1V/oct", "Gate", "Velocity", "Mod Wheel", "Pitch Wheel", "Aftertouch"}; | ||||
| @@ -296,5 +268,8 @@ MidiToCVWidget::MidiToCVWidget() { | |||||
| yPos += yGap + margin; | yPos += yGap + margin; | ||||
| } | } | ||||
| MidiWidget *midiWidget = construct<MIDI_DIN_MidiWidget>(); | |||||
| midiWidget->midiIO = &module->midiInput; | |||||
| addChild(midiWidget); | |||||
| } | } | ||||
| #endif | |||||
| @@ -9,13 +9,12 @@ void init(rack::Plugin *p) { | |||||
| p->addModel(createModel<AudioInterfaceWidget>("Core", "AudioInterface", "Audio Interface", EXTERNAL_TAG)); | p->addModel(createModel<AudioInterfaceWidget>("Core", "AudioInterface", "Audio Interface", EXTERNAL_TAG)); | ||||
| // p->addModel(createModel<MidiToCVWidget>("Core", "MIDIToCVInterface", "MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||||
| p->addModel(createModel<MidiToCVWidget>("Core", "MIDIToCVInterface", "MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||||
| // p->addModel(createModel<MIDICCToCVWidget>("Core", "MIDICCToCVInterface", "MIDI CC-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | // p->addModel(createModel<MIDICCToCVWidget>("Core", "MIDICCToCVInterface", "MIDI CC-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | ||||
| // p->addModel(createModel<MIDIClockToCVWidget>("Core", "MIDIClockToCVInterface", "MIDI Clock-to-CV Interface", MIDI_TAG, EXTERNAL_TAG, CLOCK_TAG)); | // p->addModel(createModel<MIDIClockToCVWidget>("Core", "MIDIClockToCVInterface", "MIDI Clock-to-CV Interface", MIDI_TAG, EXTERNAL_TAG, CLOCK_TAG)); | ||||
| // p->addModel(createModel<MIDITriggerToCVWidget>("Core", "MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | // p->addModel(createModel<MIDITriggerToCVWidget>("Core", "MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | ||||
| // p->addModel(createModel<QuadMidiToCVWidget>("Core", "QuadMIDIToCVInterface", "Quad MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG, QUAD_TAG)); | // p->addModel(createModel<QuadMidiToCVWidget>("Core", "QuadMIDIToCVInterface", "Quad MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG, QUAD_TAG)); | ||||
| // p->addModel(createModel<BridgeWidget>("Core", "Bridge", "Bridge")); | |||||
| p->addModel(createModel<BlankWidget>("Core", "Blank", "Blank", BLANK_TAG)); | p->addModel(createModel<BlankWidget>("Core", "Blank", "Blank", BLANK_TAG)); | ||||
| p->addModel(createModel<NotesWidget>("Core", "Notes", "Notes", BLANK_TAG)); | p->addModel(createModel<NotesWidget>("Core", "Notes", "Notes", BLANK_TAG)); | ||||
| } | } | ||||
| @@ -5,6 +5,7 @@ | |||||
| #include "settings.hpp" | #include "settings.hpp" | ||||
| #include "asset.hpp" | #include "asset.hpp" | ||||
| #include <unistd.h> | #include <unistd.h> | ||||
| #include "../ext/osdialog/osdialog.h" | |||||
| using namespace rack; | using namespace rack; | ||||
| @@ -40,8 +41,12 @@ int main(int argc, char* argv[]) { | |||||
| skipAutosaveOnLaunch = true; | skipAutosaveOnLaunch = true; | ||||
| settingsSave(assetLocal("settings.json")); | settingsSave(assetLocal("settings.json")); | ||||
| skipAutosaveOnLaunch = false; | skipAutosaveOnLaunch = false; | ||||
| if (!oldSkipAutosaveOnLaunch) | |||||
| if (oldSkipAutosaveOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, likely caused by a faulty module in your patch. Would you like to clear your patch and start over?")) { | |||||
| // Do nothing. Empty patch is already loaded. | |||||
| } | |||||
| else { | |||||
| gRackWidget->loadPatch(assetLocal("autosave.vcv")); | gRackWidget->loadPatch(assetLocal("autosave.vcv")); | ||||
| } | |||||
| engineStart(); | engineStart(); | ||||
| guiRun(); | guiRun(); | ||||
| @@ -4,38 +4,101 @@ | |||||
| namespace rack { | namespace rack { | ||||
| //////////////////// | |||||
| // MidiIO | |||||
| //////////////////// | |||||
| int MidiIO::getPortCount() { | int MidiIO::getPortCount() { | ||||
| return midi->getPortCount(); | |||||
| return rtMidi->getPortCount(); | |||||
| } | } | ||||
| std::string MidiIO::getPortName(int port) { | std::string MidiIO::getPortName(int port) { | ||||
| return midi->getPortName(port); | |||||
| if (port < 0) | |||||
| return ""; | |||||
| return rtMidi->getPortName(port); | |||||
| } | } | ||||
| void MidiIO::openPort(int port) { | void MidiIO::openPort(int port) { | ||||
| midi->closePort(); | |||||
| rtMidi->closePort(); | |||||
| if (port >= 0) { | if (port >= 0) { | ||||
| midi->openPort(port); | |||||
| rtMidi->openPort(port); | |||||
| } | } | ||||
| this->port = port; | this->port = port; | ||||
| } | } | ||||
| 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())); | |||||
| 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); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| json_t *channelJ = json_object_get(rootJ, "channel"); | |||||
| if (channelJ) | |||||
| channel = json_integer_value(channelJ); | |||||
| } | |||||
| //////////////////// | |||||
| // MidiInput | |||||
| //////////////////// | |||||
| static void midiInputCallback(double timeStamp, std::vector<unsigned char> *message, void *userData) { | |||||
| if (!message) return; | |||||
| if (!userData) return; | |||||
| MidiInput *midiInput = (MidiInput*) userData; | |||||
| if (!midiInput) return; | |||||
| MidiMessage midiMessage; | |||||
| midiMessage.time = timeStamp; | |||||
| midiMessage.data = *message; | |||||
| midiInput->onMessage(midiMessage); | |||||
| } | |||||
| MidiInput::MidiInput() { | MidiInput::MidiInput() { | ||||
| midi = new RtMidiIn(); | |||||
| RtMidiIn *rtMidiIn = new RtMidiIn(); | |||||
| rtMidi = rtMidiIn; | |||||
| rtMidiIn->setCallback(midiInputCallback, this); | |||||
| } | } | ||||
| MidiInput::~MidiInput() { | MidiInput::~MidiInput() { | ||||
| delete dynamic_cast<RtMidiIn*>(midi); | |||||
| delete dynamic_cast<RtMidiIn*>(rtMidi); | |||||
| } | |||||
| void MidiInputQueue::onMessage(const MidiMessage &message) { | |||||
| for (uint8_t d : message.data) { | |||||
| debug("MIDI message: %02x", d); | |||||
| } | |||||
| const int messageQueueSize = 8192; | |||||
| if (messageQueue.size() < messageQueueSize) | |||||
| messageQueue.push(message); | |||||
| } | } | ||||
| //////////////////// | |||||
| // MidiOutput | |||||
| //////////////////// | |||||
| MidiOutput::MidiOutput() { | MidiOutput::MidiOutput() { | ||||
| midi = new RtMidiOut(); | |||||
| rtMidi = new RtMidiOut(); | |||||
| } | } | ||||
| MidiOutput::~MidiOutput() { | MidiOutput::~MidiOutput() { | ||||
| delete dynamic_cast<RtMidiOut*>(midi); | |||||
| delete dynamic_cast<RtMidiOut*>(rtMidi); | |||||
| } | } | ||||
| @@ -137,7 +137,7 @@ void settingsSave(std::string filename) { | |||||
| if (!file) | if (!file) | ||||
| return; | return; | ||||
| json_dumpf(rootJ, file, JSON_INDENT(2)); | |||||
| json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||||
| json_decref(rootJ); | json_decref(rootJ); | ||||
| fclose(file); | fclose(file); | ||||
| } | } | ||||