@@ -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); | ||||
} | } | ||||