@@ -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<int> listSampleRates(); | |||
@@ -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 */ | |||
@@ -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); | |||
}; | |||
@@ -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>(&MenuLabel::text, "MIDI port")); | |||
for (int port = 0; port < midiIO->getPortCount(); port++) { | |||
MidiPortItem *item = new MidiPortItem(); | |||
menu->addChild(construct<MenuLabel>(&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<MenuEntry>()); | |||
@@ -216,6 +216,14 @@ void AudioIO::closeStream() { | |||
onCloseStream(); | |||
} | |||
bool AudioIO::isActive() { | |||
if (rtAudio) | |||
return rtAudio->isStreamRunning(); | |||
// TODO Bridge | |||
return false; | |||
} | |||
std::vector<int> AudioIO::listSampleRates() { | |||
if (rtAudio) { | |||
try { | |||
@@ -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<MAX_INPUTS> inputSrc; | |||
SampleRateConverter<MAX_OUTPUTS> outputSrc; | |||
@@ -107,7 +112,8 @@ struct AudioInterface : Module { | |||
DoubleRingBuffer<Frame<MAX_INPUTS>, 16> inputBuffer; | |||
DoubleRingBuffer<Frame<MAX_OUTPUTS>, 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<MAX_INPUTS> 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<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))); | |||
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))); | |||
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<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO_OUTPUT + i)); | |||
Port *port = createOutput<PJ3410Port>(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<USB_B_AudioWidget>(); | |||
audioWidget->audioIO = &module->audioIO; | |||
addChild(audioWidget); | |||
// Lights | |||
addChild(createLight<SmallLight<GreenLight>>(Vec(40, 20), module, AudioInterface::ACTIVE_LIGHT)); | |||
} |
@@ -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<MIDI_DIN_MidiWidget>(); | |||
midiWidget->midiIO = &module->midiInput; | |||
addChild(midiWidget); | |||
// Lights | |||
addChild(createLight<SmallLight<GreenLight>>(Vec(40, 20), module, MIDIToCVInterface::ACTIVE_LIGHT)); | |||
} |
@@ -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; | |||
} | |||
} | |||