diff --git a/include/dsp/samplerate.hpp b/include/dsp/samplerate.hpp index d74d99a6..25809b07 100644 --- a/include/dsp/samplerate.hpp +++ b/include/dsp/samplerate.hpp @@ -10,53 +10,82 @@ namespace rack { template struct SampleRateConverter { - SpeexResamplerState *state; - bool bypass = false; + SpeexResamplerState *st = NULL; + int channels = CHANNELS; + int quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; + int inRate = 44100; + int outRate = 44100; SampleRateConverter() { - int error; - state = speex_resampler_init(CHANNELS, 44100, 44100, SPEEX_RESAMPLER_QUALITY_DEFAULT, &error); - assert(error == RESAMPLER_ERR_SUCCESS); + refreshState(); } ~SampleRateConverter() { - speex_resampler_destroy(state); + if (st) { + speex_resampler_destroy(st); + } + } + + /** Sets the number of channels to actually process. This can be at most CHANNELS. */ + void setChannels(int channels) { + assert(channels <= CHANNELS); + this->channels = channels; + refreshState(); } void setQuality(int quality) { - speex_resampler_set_quality(state, quality); + this->quality = quality; + refreshState(); } void setRates(int inRate, int outRate) { - int oldInRate, oldOutRate; - getRates(&oldInRate, &oldOutRate); - if (inRate == oldInRate && outRate == oldOutRate) - return; - int error = speex_resampler_set_rate(state, inRate, outRate); - assert(error == RESAMPLER_ERR_SUCCESS); + this->inRate = inRate; + this->outRate = outRate; + refreshState(); } - void getRates(int *inRate, int *outRate) { - spx_uint32_t inRate32, outRate32; - speex_resampler_get_rate(state, &inRate32, &outRate32); - if (inRate) *inRate = inRate32; - if (outRate) *outRate = outRate32; + void refreshState() { + if (st) { + speex_resampler_destroy(st); + st = NULL; + } + + if (channels > 0 && inRate != outRate) { + int err; + st = speex_resampler_init(channels, inRate, outRate, quality, &err); + assert(st); + assert(err == RESAMPLER_ERR_SUCCESS); + + speex_resampler_set_input_stride(st, CHANNELS); + speex_resampler_set_output_stride(st, CHANNELS); + } } /** `in` and `out` are interlaced with the number of channels */ void process(const Frame *in, int *inFrames, Frame *out, int *outFrames) { - if (bypass) { - int len = std::min(*inFrames, *outFrames); - memcpy(out, in, len * sizeof(Frame)); - *inFrames = len; - *outFrames = len; - return; + assert(in); + assert(inFrames); + assert(out); + assert(outFrames); + if (st) { + // Resample each channel at a time + spx_uint32_t inLen; + spx_uint32_t outLen; + for (int i = 0; i < channels; i++) { + inLen = *inFrames; + outLen = *outFrames; + int err = speex_resampler_process_float(st, i, ((const float*) in) + i, &inLen, ((float*) out) + i, &outLen); + assert(err == RESAMPLER_ERR_SUCCESS); + } + *inFrames = inLen; + *outFrames = outLen; + } + else { + // Simply copy the buffer without conversion + int frames = min(*inFrames, *outFrames); + memcpy(out, in, frames * sizeof(Frame)); + *inFrames = frames; + *outFrames = frames; } - speex_resampler_process_interleaved_float(state, (const float*)in, (unsigned int*)inFrames, (float*)out, (unsigned int*)outFrames); - } - - void reset() { - int error = speex_resampler_reset_mem(state); - assert(error == RESAMPLER_ERR_SUCCESS); } }; diff --git a/src/Core/AudioInterface.cpp b/src/Core/AudioInterface.cpp index f3d16380..64606793 100644 --- a/src/Core/AudioInterface.cpp +++ b/src/Core/AudioInterface.cpp @@ -15,8 +15,8 @@ #pragma GCC diagnostic pop -#define OUTPUTS 8 -#define INPUTS 8 +#define AUDIO_OUTPUTS 8 +#define AUDIO_INPUTS 8 using namespace rack; @@ -28,9 +28,9 @@ struct AudioInterfaceIO : AudioIO { std::mutex audioMutex; std::condition_variable audioCv; // Audio thread produces, engine thread consumes - DoubleRingBuffer, (1<<15)> inputBuffer; + DoubleRingBuffer, (1<<15)> inputBuffer; // Audio thread consumes, engine thread produces - DoubleRingBuffer, (1<<15)> outputBuffer; + DoubleRingBuffer, (1<<15)> outputBuffer; bool active = false; ~AudioInterfaceIO() { @@ -51,7 +51,7 @@ struct AudioInterfaceIO : AudioIO { for (int i = 0; i < frames; i++) { if (inputBuffer.full()) break; - Frame inputFrame; + Frame inputFrame; memset(&inputFrame, 0, sizeof(inputFrame)); memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float)); inputBuffer.push(inputFrame); @@ -67,7 +67,7 @@ struct AudioInterfaceIO : AudioIO { if (audioCv.wait_for(lock, timeout, cond)) { // Consume audio block for (int i = 0; i < frames; i++) { - Frame f = outputBuffer.shift(); + Frame f = outputBuffer.shift(); for (int j = 0; j < numOutputs; j++) { output[numOutputs*i + j] = clamp(f.samples[j], -1.f, 1.f); } @@ -99,28 +99,30 @@ struct AudioInterface : Module { NUM_PARAMS }; enum InputIds { - ENUMS(AUDIO_INPUT, INPUTS), + ENUMS(AUDIO_INPUT, AUDIO_INPUTS), NUM_INPUTS }; enum OutputIds { - ENUMS(AUDIO_OUTPUT, OUTPUTS), + ENUMS(AUDIO_OUTPUT, AUDIO_OUTPUTS), NUM_OUTPUTS }; enum LightIds { - ENUMS(INPUT_LIGHT, INPUTS / 2), - ENUMS(OUTPUT_LIGHT, OUTPUTS / 2), + ENUMS(INPUT_LIGHT, AUDIO_INPUTS / 2), + ENUMS(OUTPUT_LIGHT, AUDIO_OUTPUTS / 2), NUM_LIGHTS }; AudioInterfaceIO audioIO; int lastSampleRate = 0; + int lastNumOutputs = -1; + int lastNumInputs = -1; - SampleRateConverter inputSrc; - SampleRateConverter outputSrc; + SampleRateConverter inputSrc; + SampleRateConverter outputSrc; // in rack's sample rate - DoubleRingBuffer, 16> inputBuffer; - DoubleRingBuffer, 16> outputBuffer; + DoubleRingBuffer, 16> inputBuffer; + DoubleRingBuffer, 16> outputBuffer; AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { onSampleRateChange(); @@ -140,8 +142,13 @@ struct AudioInterface : Module { } void onSampleRateChange() override { - inputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); - outputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); + inputSrc.setRates(audioIO.sampleRate, (int) engineGetSampleRate()); + outputSrc.setRates((int) engineGetSampleRate(), audioIO.sampleRate); + } + + void onChannelsChange() { + inputSrc.setChannels(audioIO.numInputs); + outputSrc.setChannels(audioIO.numOutputs); } void onReset() override { @@ -157,6 +164,13 @@ void AudioInterface::step() { lastSampleRate = audioIO.sampleRate; } + // Update number of channels if changed by audio driver + if (audioIO.numOutputs != lastNumOutputs || audioIO.numInputs != lastNumInputs) { + lastNumOutputs = audioIO.numOutputs; + lastNumInputs = audioIO.numInputs; + onChannelsChange(); + } + // Inputs: audio engine -> rack engine if (audioIO.active && audioIO.numInputs > 0) { // Wait until inputs are present @@ -182,23 +196,26 @@ void AudioInterface::step() { } // Take input from buffer - Frame inputFrame; + Frame inputFrame; if (!inputBuffer.empty()) { inputFrame = inputBuffer.shift(); } else { memset(&inputFrame, 0, sizeof(inputFrame)); } - for (int i = 0; i < INPUTS; i++) { + for (int i = 0; i < audioIO.numInputs; i++) { outputs[AUDIO_OUTPUT + i].value = 10.f * inputFrame.samples[i]; } + for (int i = audioIO.numInputs; i < AUDIO_INPUTS; i++) { + outputs[AUDIO_OUTPUT + i].value = 0.f; + } // Outputs: rack engine -> audio engine if (audioIO.active && audioIO.numOutputs > 0) { // Get and push output SRC frame if (!outputBuffer.full()) { - Frame outputFrame; - for (int i = 0; i < OUTPUTS; i++) { + Frame outputFrame; + for (int i = 0; i < AUDIO_OUTPUTS; i++) { outputFrame.samples[i] = inputs[AUDIO_INPUT + i].value / 10.f; } outputBuffer.push(outputFrame); @@ -233,9 +250,9 @@ void AudioInterface::step() { } // Turn on light if at least one port is enabled in the nearby pair - for (int i = 0; i < INPUTS / 2; i++) + for (int i = 0; i < AUDIO_INPUTS / 2; i++) lights[INPUT_LIGHT + i].value = (audioIO.active && audioIO.numOutputs >= 2*i+1); - for (int i = 0; i < OUTPUTS / 2; i++) + for (int i = 0; i < AUDIO_OUTPUTS / 2; i++) lights[OUTPUT_LIGHT + i].value = (audioIO.active && audioIO.numInputs >= 2*i+1); } diff --git a/src/Core/MIDIToCVInterface.cpp b/src/Core/MIDIToCVInterface.cpp index 662099b1..eaba2db4 100644 --- a/src/Core/MIDIToCVInterface.cpp +++ b/src/Core/MIDIToCVInterface.cpp @@ -301,7 +301,7 @@ struct MIDIToCVInterfaceWidget : ModuleWidget { Menu *createChildMenu() override { Menu *menu = new Menu(); std::vector divisions = {24*4, 24*2, 24, 24/2, 24/4, 24/8, 2, 1}; - std::vector divisionNames = {"Whole", "Half", "Quarter", "8th", "16th", "32nd", "48th", "96th"}; + std::vector divisionNames = {"Whole", "Half", "Quarter", "8th", "16th", "32nd", "12 PPQN", "24 PPQN"}; for (size_t i = 0; i < divisions.size(); i++) { ClockDivisionItem *item = MenuItem::create(divisionNames[i], CHECKMARK(module->divisions[index] == divisions[i])); item->module = module;