| @@ -1,5 +1,7 @@ | |||
| #pragma once | |||
| #include <jansson.h> | |||
| #pragma GCC diagnostic push | |||
| #pragma GCC diagnostic ignored "-Wsuggest-override" | |||
| #include <RtAudio.h> | |||
| @@ -13,20 +15,20 @@ struct AudioIO { | |||
| int maxOutputs = 8; | |||
| int maxInputs = 8; | |||
| RtAudio *stream = NULL; | |||
| // Stream properties | |||
| int driver = 0; | |||
| int device = -1; | |||
| int sampleRate = 44100; | |||
| int blockSize = 256; | |||
| int numOutputs = 0; | |||
| int numInputs = 0; | |||
| RtAudio *rtAudio = NULL; | |||
| AudioIO(); | |||
| virtual ~AudioIO(); | |||
| std::vector<int> listDrivers(); | |||
| std::string getDriverName(int driver); | |||
| int getDriver(); | |||
| void setDriver(int driver); | |||
| int getDeviceCount(); | |||
| @@ -40,6 +42,8 @@ struct AudioIO { | |||
| virtual void processStream(const float *input, float *output, int length) {} | |||
| virtual void onCloseStream() {} | |||
| virtual void onOpenStream() {} | |||
| json_t *toJson(); | |||
| void fromJson(json_t *rootJ); | |||
| }; | |||
| @@ -1,5 +1,11 @@ | |||
| #pragma once | |||
| #include "util.hpp" | |||
| #include <queue> | |||
| #include <vector> | |||
| #include <jansson.h> | |||
| #pragma GCC diagnostic push | |||
| #pragma GCC diagnostic ignored "-Wsuggest-override" | |||
| #include "rtmidi/RtMidi.h" | |||
| @@ -9,8 +15,13 @@ | |||
| namespace rack { | |||
| struct MidiMessage { | |||
| double time; | |||
| std::vector<uint8_t> data; | |||
| }; | |||
| struct MidiIO { | |||
| RtMidi *midi; | |||
| int port = -1; | |||
| /* For MIDI output, the channel to output messages. | |||
| For MIDI input, the channel to filter. | |||
| @@ -18,17 +29,27 @@ struct MidiIO { | |||
| Zero indexed. | |||
| */ | |||
| int channel = -1; | |||
| RtMidi *rtMidi = NULL; | |||
| 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 { | |||
| 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->driver = 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(construct<MenuEntry>()); | |||
| @@ -110,7 +110,7 @@ void RackWidget::savePatch(std::string path) { | |||
| FILE *file = fopen(path.c_str(), "w"); | |||
| if (file) { | |||
| json_dumpf(rootJ, file, JSON_INDENT(2)); | |||
| json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
| fclose(file); | |||
| } | |||
| @@ -3,6 +3,9 @@ | |||
| #include "audio.hpp" | |||
| #define DRIVER_BRIDGE -1 | |||
| namespace rack { | |||
| @@ -20,6 +23,8 @@ std::vector<int> AudioIO::listDrivers() { | |||
| std::vector<int> drivers; | |||
| for (RtAudio::Api api : apis) | |||
| drivers.push_back((int) api); | |||
| // Add Bridge fake driver | |||
| // drivers.push_back(DRIVER_BRIDGE); | |||
| return drivers; | |||
| } | |||
| @@ -35,55 +40,71 @@ std::string AudioIO::getDriverName(int driver) { | |||
| case RtAudio::WINDOWS_ASIO: return "ASIO"; | |||
| case RtAudio::WINDOWS_DS: return "DirectSound"; | |||
| case RtAudio::RTAUDIO_DUMMY: return "Dummy"; | |||
| case DRIVER_BRIDGE: return "VCV Bridge"; | |||
| default: return "Unknown"; | |||
| } | |||
| } | |||
| int AudioIO::getDriver() { | |||
| if (!stream) | |||
| return RtAudio::UNSPECIFIED; | |||
| return stream->getCurrentApi(); | |||
| } | |||
| void AudioIO::setDriver(int driver) { | |||
| // Close driver | |||
| 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() { | |||
| 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) { | |||
| 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) { | |||
| 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) { | |||
| @@ -97,17 +118,18 @@ void AudioIO::openStream() { | |||
| // Close device but remember the current device number | |||
| int device = this->device; | |||
| closeStream(); | |||
| if (!stream) | |||
| if (device < 0) | |||
| return; | |||
| // Open new device | |||
| if (device >= 0) { | |||
| if (rtAudio) { | |||
| // Open new device | |||
| RtAudio::DeviceInfo deviceInfo; | |||
| try { | |||
| deviceInfo = stream->getDeviceInfo(device); | |||
| deviceInfo = rtAudio->getDeviceInfo(device); | |||
| } | |||
| catch (RtAudioError &e) { | |||
| warn("Failed to query audio device: %s", e.what()); | |||
| warn("Failed to query RtAudio device: %s", e.what()); | |||
| return; | |||
| } | |||
| @@ -115,7 +137,7 @@ void AudioIO::openStream() { | |||
| numInputs = mini(deviceInfo.inputChannels, maxInputs); | |||
| 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; | |||
| } | |||
| @@ -138,56 +160,56 @@ void AudioIO::openStream() { | |||
| } | |||
| try { | |||
| debug("Opening audio stream %d", device); | |||
| stream->openStream( | |||
| debug("Opening audio RtAudio device %d", device); | |||
| rtAudio->openStream( | |||
| numOutputs == 0 ? NULL : &outParameters, | |||
| numInputs == 0 ? NULL : &inParameters, | |||
| RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL); | |||
| } | |||
| catch (RtAudioError &e) { | |||
| warn("Failed to open audio stream: %s", e.what()); | |||
| warn("Failed to open RtAudio stream: %s", e.what()); | |||
| return; | |||
| } | |||
| try { | |||
| debug("Starting audio stream %d", device); | |||
| stream->startStream(); | |||
| debug("Starting RtAudio stream %d", device); | |||
| rtAudio->startStream(); | |||
| } | |||
| catch (RtAudioError &e) { | |||
| warn("Failed to start audio stream: %s", e.what()); | |||
| warn("Failed to start RtAudio stream: %s", e.what()); | |||
| return; | |||
| } | |||
| // Update sample rate because this may have changed | |||
| this->sampleRate = stream->getStreamSampleRate(); | |||
| this->sampleRate = rtAudio->getStreamSampleRate(); | |||
| this->device = device; | |||
| onOpenStream(); | |||
| } | |||
| } | |||
| 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 { | |||
| stream->stopStream(); | |||
| rtAudio->stopStream(); | |||
| } | |||
| 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 { | |||
| stream->closeStream(); | |||
| rtAudio->closeStream(); | |||
| } | |||
| 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; | |||
| numOutputs = 0; | |||
| numInputs = 0; | |||
| @@ -195,18 +217,59 @@ void AudioIO::closeStream() { | |||
| } | |||
| 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 *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; | |||
| } | |||
| 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 { | |||
| @@ -236,7 +209,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||
| { | |||
| Label *label = new Label(); | |||
| label->box.pos = Vec(margin.x, yPos); | |||
| label->text = "Outputs"; | |||
| label->text = "Outputs (DACs)"; | |||
| addChild(label); | |||
| yPos += labelHeight + margin.y; | |||
| } | |||
| @@ -270,7 +243,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||
| { | |||
| Label *label = new Label(); | |||
| label->box.pos = Vec(margin.x, yPos); | |||
| label->text = "Inputs"; | |||
| label->text = "Inputs (ADCs)"; | |||
| addChild(label); | |||
| 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 <algorithm> | |||
| #include "core.hpp" | |||
| @@ -12,7 +11,7 @@ | |||
| */ | |||
| struct MidiValue { | |||
| 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!) | |||
| }; | |||
| @@ -38,6 +37,7 @@ struct MIDIToCVInterface : Module { | |||
| NUM_LIGHTS | |||
| }; | |||
| MidiInputQueue midiInput; | |||
| std::list<int> notes; | |||
| bool pedal = false; | |||
| 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) { | |||
| pitchWheel.val = 64; | |||
| pitchWheel.tSmooth.set(0, 0); | |||
| // pitchWheel.tSmooth.set(0, 0); | |||
| } | |||
| ~MIDIToCVInterface() { | |||
| @@ -67,22 +67,22 @@ struct MIDIToCVInterface : Module { | |||
| json_t *toJson() override { | |||
| json_t *rootJ = json_object(); | |||
| addBaseJson(rootJ); | |||
| // addBaseJson(rootJ); | |||
| return rootJ; | |||
| } | |||
| void fromJson(json_t *rootJ) override { | |||
| baseFromJson(rootJ); | |||
| // baseFromJson(rootJ); | |||
| } | |||
| void onReset() override { | |||
| resetMidi(); | |||
| // resetMidi(); | |||
| } | |||
| void resetMidi() override; | |||
| // void resetMidi() override; | |||
| }; | |||
| /* | |||
| void MIDIToCVInterface::resetMidi() { | |||
| mod.val = 0; | |||
| mod.tSmooth.set(0, 0); | |||
| @@ -94,8 +94,10 @@ void MIDIToCVInterface::resetMidi() { | |||
| gate = false; | |||
| notes.clear(); | |||
| } | |||
| */ | |||
| void MIDIToCVInterface::step() { | |||
| /* | |||
| if (isPortOpen()) { | |||
| std::vector<unsigned char> message; | |||
| @@ -132,11 +134,8 @@ void MIDIToCVInterface::step() { | |||
| } | |||
| 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; | |||
| */ | |||
| } | |||
| void MIDIToCVInterface::pressNote(int note) { | |||
| @@ -171,6 +170,7 @@ void MIDIToCVInterface::releaseNote(int note) { | |||
| } | |||
| void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
| /* | |||
| int channel = msg[0] & 0xf; | |||
| int status = (msg[0] >> 4) & 0xf; | |||
| int data1 = msg[1]; | |||
| @@ -220,6 +220,7 @@ void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
| afterTouch.changed = true; | |||
| break; | |||
| } | |||
| */ | |||
| } | |||
| @@ -254,35 +255,6 @@ MidiToCVWidget::MidiToCVWidget() { | |||
| 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)); | |||
| { | |||
| 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"}; | |||
| @@ -296,5 +268,8 @@ MidiToCVWidget::MidiToCVWidget() { | |||
| 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<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<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<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<NotesWidget>("Core", "Notes", "Notes", BLANK_TAG)); | |||
| } | |||
| @@ -5,6 +5,7 @@ | |||
| #include "settings.hpp" | |||
| #include "asset.hpp" | |||
| #include <unistd.h> | |||
| #include "../ext/osdialog/osdialog.h" | |||
| using namespace rack; | |||
| @@ -40,8 +41,12 @@ int main(int argc, char* argv[]) { | |||
| skipAutosaveOnLaunch = true; | |||
| settingsSave(assetLocal("settings.json")); | |||
| 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")); | |||
| } | |||
| engineStart(); | |||
| guiRun(); | |||
| @@ -4,38 +4,101 @@ | |||
| namespace rack { | |||
| //////////////////// | |||
| // MidiIO | |||
| //////////////////// | |||
| int MidiIO::getPortCount() { | |||
| return midi->getPortCount(); | |||
| return rtMidi->getPortCount(); | |||
| } | |||
| std::string MidiIO::getPortName(int port) { | |||
| return midi->getPortName(port); | |||
| if (port < 0) | |||
| return ""; | |||
| return rtMidi->getPortName(port); | |||
| } | |||
| void MidiIO::openPort(int port) { | |||
| midi->closePort(); | |||
| rtMidi->closePort(); | |||
| if (port >= 0) { | |||
| midi->openPort(port); | |||
| rtMidi->openPort(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() { | |||
| midi = new RtMidiIn(); | |||
| RtMidiIn *rtMidiIn = new RtMidiIn(); | |||
| rtMidi = rtMidiIn; | |||
| rtMidiIn->setCallback(midiInputCallback, this); | |||
| } | |||
| 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() { | |||
| midi = new RtMidiOut(); | |||
| rtMidi = new RtMidiOut(); | |||
| } | |||
| MidiOutput::~MidiOutput() { | |||
| delete dynamic_cast<RtMidiOut*>(midi); | |||
| delete dynamic_cast<RtMidiOut*>(rtMidi); | |||
| } | |||
| @@ -137,7 +137,7 @@ void settingsSave(std::string filename) { | |||
| if (!file) | |||
| return; | |||
| json_dumpf(rootJ, file, JSON_INDENT(2)); | |||
| json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
| json_decref(rootJ); | |||
| fclose(file); | |||
| } | |||