|
|
@@ -13,100 +13,7 @@ namespace core { |
|
|
|
|
|
|
|
|
|
|
|
template <int NUM_AUDIO_INPUTS, int NUM_AUDIO_OUTPUTS> |
|
|
|
struct AudioInterfacePort : audio::Port { |
|
|
|
// std::mutex engineMutex; |
|
|
|
// std::condition_variable engineCv; |
|
|
|
// std::mutex audioMutex; |
|
|
|
// std::condition_variable audioCv; |
|
|
|
// // Audio thread produces, engine thread consumes |
|
|
|
// dsp::DoubleRingBuffer < dsp::Frame<NUM_AUDIO_INPUTS>, (1 << 15) > inputBuffer; |
|
|
|
// // Audio thread consumes, engine thread produces |
|
|
|
// dsp::DoubleRingBuffer < dsp::Frame<NUM_AUDIO_OUTPUTS>, (1 << 15) > outputBuffer; |
|
|
|
// bool active = false; |
|
|
|
|
|
|
|
// For checking getPrimaryModule() |
|
|
|
Module* module = NULL; |
|
|
|
const float* input = NULL; |
|
|
|
float* output = NULL; |
|
|
|
int frame = 0; |
|
|
|
int numFrames = 0; |
|
|
|
|
|
|
|
~AudioInterfacePort() { |
|
|
|
// Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close. |
|
|
|
setDeviceId(-1, 0); |
|
|
|
} |
|
|
|
|
|
|
|
void processStream(const float* input, float* output, int frames) override { |
|
|
|
if (APP->engine->getPrimaryModule() != module) { |
|
|
|
// TEMP |
|
|
|
std::memset(output, 0, sizeof(float) * frames * numOutputs); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
frame = 0; |
|
|
|
numFrames = frames; |
|
|
|
this->input = input; |
|
|
|
this->output = output; |
|
|
|
|
|
|
|
APP->engine->step(frames); |
|
|
|
|
|
|
|
// // Reactivate idle stream |
|
|
|
// if (!active) { |
|
|
|
// active = true; |
|
|
|
// inputBuffer.clear(); |
|
|
|
// outputBuffer.clear(); |
|
|
|
// } |
|
|
|
|
|
|
|
// if (numInputs > 0) { |
|
|
|
// // 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 < frames; i++) { |
|
|
|
// if (inputBuffer.full()) |
|
|
|
// break; |
|
|
|
// dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; |
|
|
|
// std::memset(&inputFrame, 0, sizeof(inputFrame)); |
|
|
|
// std::memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float)); |
|
|
|
// inputBuffer.push(inputFrame); |
|
|
|
// } |
|
|
|
// } |
|
|
|
|
|
|
|
// if (numOutputs > 0) { |
|
|
|
// std::unique_lock<std::mutex> lock(audioMutex); |
|
|
|
// auto cond = [&] { |
|
|
|
// return (outputBuffer.size() >= (size_t) frames); |
|
|
|
// }; |
|
|
|
// auto timeout = std::chrono::milliseconds(100); |
|
|
|
// if (audioCv.wait_for(lock, timeout, cond)) { |
|
|
|
// // Consume audio block |
|
|
|
// for (int i = 0; i < frames; i++) { |
|
|
|
// dsp::Frame<NUM_AUDIO_OUTPUTS> f = outputBuffer.shift(); |
|
|
|
// for (int j = 0; j < numOutputs; j++) { |
|
|
|
// output[numOutputs * i + j] = clamp(f.samples[j], -1.f, 1.f); |
|
|
|
// } |
|
|
|
// } |
|
|
|
// } |
|
|
|
// else { |
|
|
|
// // Timed out, fill output with zeros |
|
|
|
// std::memset(output, 0, frames * numOutputs * sizeof(float)); |
|
|
|
// // DEBUG("Audio Interface Port underflow"); |
|
|
|
// } |
|
|
|
// } |
|
|
|
|
|
|
|
// // Notify engine when finished processing |
|
|
|
// engineCv.notify_one(); |
|
|
|
} |
|
|
|
|
|
|
|
void onCloseStream() override { |
|
|
|
// inputBuffer.clear(); |
|
|
|
// outputBuffer.clear(); |
|
|
|
} |
|
|
|
|
|
|
|
void onChannelsChange() override { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
template <int NUM_AUDIO_INPUTS, int NUM_AUDIO_OUTPUTS> |
|
|
|
struct AudioInterface : Module { |
|
|
|
struct AudioInterface : Module, audio::Port { |
|
|
|
enum ParamIds { |
|
|
|
NUM_PARAMS |
|
|
|
}; |
|
|
@@ -124,7 +31,6 @@ struct AudioInterface : Module { |
|
|
|
NUM_LIGHTS |
|
|
|
}; |
|
|
|
|
|
|
|
AudioInterfacePort<NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS> port; |
|
|
|
// int lastSampleRate = 0; |
|
|
|
// int lastNumOutputs = -1; |
|
|
|
// int lastNumInputs = -1; |
|
|
@@ -132,9 +38,8 @@ struct AudioInterface : Module { |
|
|
|
// dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc; |
|
|
|
// dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc; |
|
|
|
|
|
|
|
// // in rack's sample rate |
|
|
|
// dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_INPUTS>, 16> inputBuffer; |
|
|
|
// dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_OUTPUTS>, 16> outputBuffer; |
|
|
|
dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_INPUTS>, 16384> inputBuffer; |
|
|
|
dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_OUTPUTS>, 16384> outputBuffer; |
|
|
|
|
|
|
|
AudioInterface() { |
|
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
@@ -142,33 +47,32 @@ struct AudioInterface : Module { |
|
|
|
configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1)); |
|
|
|
for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) |
|
|
|
configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); |
|
|
|
port.maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); |
|
|
|
port.module = this; |
|
|
|
maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); |
|
|
|
onSampleRateChange(); |
|
|
|
} |
|
|
|
|
|
|
|
void process(const ProcessArgs& args) override { |
|
|
|
// Claim primary module if there is none |
|
|
|
if (!APP->engine->getPrimaryModule()) { |
|
|
|
APP->engine->setPrimaryModule(this); |
|
|
|
} |
|
|
|
~AudioInterface() { |
|
|
|
// Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close. |
|
|
|
setDeviceId(-1, 0); |
|
|
|
} |
|
|
|
|
|
|
|
void process(const ProcessArgs& args) override { |
|
|
|
// Get inputs |
|
|
|
for (int i = 0; i < port.numOutputs; i++) { |
|
|
|
float v = inputs[AUDIO_INPUTS + i].getVoltage() / 10.f; |
|
|
|
port.output[port.frame * port.numOutputs + i] = v; |
|
|
|
if (!inputBuffer.full()) { |
|
|
|
dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; |
|
|
|
for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { |
|
|
|
inputFrame.samples[i] = inputs[AUDIO_INPUTS + i].getVoltage() / 10.f; |
|
|
|
} |
|
|
|
inputBuffer.push(inputFrame); |
|
|
|
} |
|
|
|
|
|
|
|
// Set outputs |
|
|
|
for (int i = 0; i < port.numInputs; i++) { |
|
|
|
float v = port.input[port.frame * port.numInputs + i]; |
|
|
|
outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * v); |
|
|
|
if (!outputBuffer.empty()) { |
|
|
|
dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame = outputBuffer.shift(); |
|
|
|
for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { |
|
|
|
outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * outputFrame.samples[i]); |
|
|
|
} |
|
|
|
} |
|
|
|
for (int i = port.numInputs; i < NUM_AUDIO_INPUTS; i++) { |
|
|
|
outputs[AUDIO_OUTPUTS + i].setVoltage(0.f); |
|
|
|
} |
|
|
|
|
|
|
|
port.frame++; |
|
|
|
|
|
|
|
// // Update SRC states |
|
|
|
// inputSrc.setRates(port.sampleRate, args.sampleRate); |
|
|
@@ -259,27 +163,112 @@ struct AudioInterface : Module { |
|
|
|
|
|
|
|
// Turn on light if at least one port is enabled in the nearby pair |
|
|
|
for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { |
|
|
|
lights[INPUT_LIGHTS + i].setBrightness(port.numOutputs >= 2 * i + 1); |
|
|
|
lights[INPUT_LIGHTS + i].setBrightness(numOutputs >= 2 * i + 1); |
|
|
|
} |
|
|
|
for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) { |
|
|
|
lights[OUTPUT_LIGHTS + i].setBrightness(port.numInputs >= 2 * i + 1); |
|
|
|
lights[OUTPUT_LIGHTS + i].setBrightness(numInputs >= 2 * i + 1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
json_t* dataToJson() override { |
|
|
|
json_t* rootJ = json_object(); |
|
|
|
json_object_set_new(rootJ, "audio", port.toJson()); |
|
|
|
json_object_set_new(rootJ, "audio", audio::Port::toJson()); |
|
|
|
return rootJ; |
|
|
|
} |
|
|
|
|
|
|
|
void dataFromJson(json_t* rootJ) override { |
|
|
|
json_t* audioJ = json_object_get(rootJ, "audio"); |
|
|
|
if (audioJ) |
|
|
|
port.fromJson(audioJ); |
|
|
|
audio::Port::fromJson(audioJ); |
|
|
|
} |
|
|
|
|
|
|
|
void onReset() override { |
|
|
|
port.setDeviceId(-1, 0); |
|
|
|
setDeviceId(-1, 0); |
|
|
|
} |
|
|
|
|
|
|
|
// audio::Port |
|
|
|
|
|
|
|
void processStream(const float* input, float* output, int frames) override { |
|
|
|
// Claim primary module if there is none |
|
|
|
if (!APP->engine->getPrimaryModule()) { |
|
|
|
APP->engine->setPrimaryModule(this); |
|
|
|
} |
|
|
|
|
|
|
|
// Clear output in case the audio driver uses this buffer in another thread before this method returns. (Not sure if any do this in practice.) |
|
|
|
std::memset(output, 0, sizeof(float) * numOutputs * frames); |
|
|
|
|
|
|
|
// audio input -> module output |
|
|
|
for (int i = 0; i < frames; i++) { |
|
|
|
if (outputBuffer.full()) |
|
|
|
break; |
|
|
|
dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame; |
|
|
|
std::memset(&outputFrame, 0, sizeof(outputFrame)); |
|
|
|
for (int j = 0; j < std::min(numInputs, NUM_AUDIO_OUTPUTS); j++) { |
|
|
|
outputFrame.samples[j] = input[i * numInputs + j]; |
|
|
|
} |
|
|
|
outputBuffer.push(outputFrame); |
|
|
|
} |
|
|
|
|
|
|
|
// Step engine estimated number of steps |
|
|
|
if (APP->engine->getPrimaryModule() == this) { |
|
|
|
APP->engine->step(frames); |
|
|
|
} |
|
|
|
|
|
|
|
// module input -> audio output |
|
|
|
for (int i = 0; i < frames; i++) { |
|
|
|
if (inputBuffer.empty()) |
|
|
|
break; |
|
|
|
dsp::Frame<NUM_AUDIO_INPUTS> inputFrame = inputBuffer.shift(); |
|
|
|
for (int j = 0; j < std::min(numOutputs, NUM_AUDIO_INPUTS); j++) { |
|
|
|
output[i * numOutputs + j] = inputFrame.samples[j]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// if (numInputs > 0) { |
|
|
|
// // 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 < frames; i++) { |
|
|
|
// if (inputBuffer.full()) |
|
|
|
// break; |
|
|
|
// dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; |
|
|
|
// std::memset(&inputFrame, 0, sizeof(inputFrame)); |
|
|
|
// std::memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float)); |
|
|
|
// inputBuffer.push(inputFrame); |
|
|
|
// } |
|
|
|
// } |
|
|
|
|
|
|
|
// if (numOutputs > 0) { |
|
|
|
// std::unique_lock<std::mutex> lock(audioMutex); |
|
|
|
// auto cond = [&] { |
|
|
|
// return (outputBuffer.size() >= (size_t) frames); |
|
|
|
// }; |
|
|
|
// auto timeout = std::chrono::milliseconds(100); |
|
|
|
// if (audioCv.wait_for(lock, timeout, cond)) { |
|
|
|
// // Consume audio block |
|
|
|
// for (int i = 0; i < frames; i++) { |
|
|
|
// dsp::Frame<NUM_AUDIO_OUTPUTS> f = outputBuffer.shift(); |
|
|
|
// for (int j = 0; j < numOutputs; j++) { |
|
|
|
// output[numOutputs * i + j] = clamp(f.samples[j], -1.f, 1.f); |
|
|
|
// } |
|
|
|
// } |
|
|
|
// } |
|
|
|
// else { |
|
|
|
// // Timed out, fill output with zeros |
|
|
|
// std::memset(output, 0, frames * numOutputs * sizeof(float)); |
|
|
|
// // DEBUG("Audio Interface Port underflow"); |
|
|
|
// } |
|
|
|
// } |
|
|
|
|
|
|
|
// // Notify engine when finished processing |
|
|
|
// engineCv.notify_one(); |
|
|
|
} |
|
|
|
|
|
|
|
void onCloseStream() override { |
|
|
|
// inputBuffer.clear(); |
|
|
|
// outputBuffer.clear(); |
|
|
|
} |
|
|
|
|
|
|
|
void onChannelsChange() override { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
@@ -338,7 +327,7 @@ struct AudioInterfaceWidget : ModuleWidget { |
|
|
|
|
|
|
|
AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(3.2122073, 14.837339))); |
|
|
|
audioWidget->box.size = mm2px(Vec(44, 28)); |
|
|
|
audioWidget->setAudioPort(module ? &module->port : NULL); |
|
|
|
audioWidget->setAudioPort(module); |
|
|
|
addChild(audioWidget); |
|
|
|
} |
|
|
|
else if (NUM_AUDIO_INPUTS == 16 && NUM_AUDIO_OUTPUTS == 16) { |
|
|
@@ -402,7 +391,7 @@ struct AudioInterfaceWidget : ModuleWidget { |
|
|
|
|
|
|
|
AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(2.57, 14.839))); |
|
|
|
audioWidget->box.size = mm2px(Vec(91.382, 28.0)); |
|
|
|
audioWidget->setAudioPort(module ? &module->port : NULL); |
|
|
|
audioWidget->setAudioPort(module); |
|
|
|
addChild(audioWidget); |
|
|
|
} |
|
|
|
} |
|
|
|