diff --git a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index d78740f94c..3ec1bed66d 100644 --- a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -113,7 +113,7 @@ public: startTimer (500); } - virtual ~StandalonePluginHolder() override + ~StandalonePluginHolder() override { stopTimer(); diff --git a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp index ec31b15f07..c6e27dd628 100644 --- a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp +++ b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp @@ -26,6 +26,102 @@ namespace juce { +template +struct ChannelInfo +{ + ChannelInfo() = default; + ChannelInfo (Value** dataIn, int numChannelsIn) + : data (dataIn), numChannels (numChannelsIn) {} + + Value** data = nullptr; + int numChannels = 0; +}; + +/** Sets up `channels` so that it contains channel pointers suitable for passing to + an AudioProcessor's processBlock. + + On return, `channels` will hold `max (processorIns, processorOuts)` entries. + The first `processorIns` entries will point to buffers holding input data. + Any entries after the first `processorIns` entries will point to zeroed buffers. + + In the case that the system only provides a single input channel, but the processor + has been initialised with multiple input channels, the system input will be copied + to all processor inputs. + + In the case that the system provides no input channels, but the processor has + been initialise with multiple input channels, the processor's input channels will + all be zeroed. + + @param ins the system inputs. + @param outs the system outputs. + @param numSamples the number of samples in the system buffers. + @param processorIns the number of input channels requested by the processor. + @param processorOuts the number of output channels requested by the processor. + @param tempBuffer temporary storage for inputs that don't have a corresponding output. + @param channels holds pointers to each of the processor's audio channels. +*/ +static void initialiseIoBuffers (ChannelInfo ins, + ChannelInfo outs, + const int numSamples, + int processorIns, + int processorOuts, + AudioBuffer& tempBuffer, + std::vector& channels) +{ + jassert ((int) channels.size() >= jmax (processorIns, processorOuts)); + + size_t totalNumChans = 0; + const auto numBytes = (size_t) numSamples * sizeof (float); + + const auto prepareInputChannel = [&] (int index) + { + if (ins.numChannels == 0) + zeromem (channels[totalNumChans], numBytes); + else + memcpy (channels[totalNumChans], ins.data[index % ins.numChannels], numBytes); + }; + + if (processorIns > processorOuts) + { + // If there aren't enough output channels for the number of + // inputs, we need to use some temporary extra ones (can't + // use the input data in case it gets written to). + jassert (tempBuffer.getNumChannels() >= processorIns - processorOuts); + jassert (tempBuffer.getNumSamples() >= numSamples); + + for (int i = 0; i < processorOuts; ++i) + { + channels[totalNumChans] = outs.data[i]; + prepareInputChannel (i); + ++totalNumChans; + } + + for (auto i = processorOuts; i < processorIns; ++i) + { + channels[totalNumChans] = tempBuffer.getWritePointer (i - outs.numChannels); + prepareInputChannel (i); + ++totalNumChans; + } + } + else + { + for (int i = 0; i < processorIns; ++i) + { + channels[totalNumChans] = outs.data[i]; + prepareInputChannel (i); + ++totalNumChans; + } + + for (auto i = processorIns; i < processorOuts; ++i) + { + channels[totalNumChans] = outs.data[i]; + zeromem (channels[totalNumChans], (size_t) numSamples * sizeof (float)); + ++totalNumChans; + } + } +} + +//============================================================================== AudioProcessorPlayer::AudioProcessorPlayer (bool doDoublePrecisionProcessing) : isDoublePrecision (doDoublePrecisionProcessing) { @@ -37,28 +133,64 @@ AudioProcessorPlayer::~AudioProcessorPlayer() } //============================================================================== +AudioProcessorPlayer::NumChannels AudioProcessorPlayer::findMostSuitableLayout (const AudioProcessor& proc) const +{ + std::vector layouts { deviceChannels }; + + if (deviceChannels.ins == 0 || deviceChannels.ins == 1) + { + layouts.emplace_back (defaultProcessorChannels.ins, deviceChannels.outs); + layouts.emplace_back (deviceChannels.outs, deviceChannels.outs); + } + + const auto it = std::find_if (layouts.begin(), layouts.end(), [&] (const NumChannels& chans) + { + return proc.checkBusesLayoutSupported (chans.toLayout()); + }); + + return it != std::end (layouts) ? *it : layouts[0]; +} + +void AudioProcessorPlayer::resizeChannels() +{ + const auto maxChannels = jmax (deviceChannels.ins, + deviceChannels.outs, + actualProcessorChannels.ins, + actualProcessorChannels.outs); + channels.resize ((size_t) maxChannels); + tempBuffer.setSize (maxChannels, blockSize); +} + void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay) { if (processor != processorToPlay) { if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0) { - processorToPlay->setPlayConfigDetails (numInputChans, numOutputChans, sampleRate, blockSize); + defaultProcessorChannels = NumChannels { processorToPlay->getBusesLayout() }; + actualProcessorChannels = findMostSuitableLayout (*processorToPlay); - bool supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision; + processorToPlay->setPlayConfigDetails (actualProcessorChannels.ins, + actualProcessorChannels.outs, + sampleRate, + blockSize); + + auto supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision; processorToPlay->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision : AudioProcessor::singlePrecision); processorToPlay->prepareToPlay (sampleRate, blockSize); } - AudioProcessor* oldOne; + AudioProcessor* oldOne = nullptr; { const ScopedLock sl (lock); + oldOne = isPrepared ? processor : nullptr; processor = processorToPlay; isPrepared = true; + resizeChannels(); } if (oldOne != nullptr) @@ -76,7 +208,7 @@ void AudioProcessorPlayer::setDoublePrecisionProcessing (bool doublePrecision) { processor->releaseResources(); - bool supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision; + auto supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision; processor->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision : AudioProcessor::singlePrecision); @@ -103,53 +235,26 @@ void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChann const int numOutputChannels, const int numSamples) { - // these should have been prepared by audioDeviceAboutToStart()... + // These should have been prepared by audioDeviceAboutToStart()... jassert (sampleRate > 0 && blockSize > 0); + // The processor should be prepared to deal with the same number of output channels + // as our output device. + jassert (processor == nullptr || numOutputChannels == actualProcessorChannels.outs); + incomingMidi.clear(); messageCollector.removeNextBlockOfMessages (incomingMidi, numSamples); - int totalNumChans = 0; - if (numInputChannels > numOutputChannels) - { - // if there aren't enough output channels for the number of - // inputs, we need to create some temporary extra ones (can't - // use the input data in case it gets written to) - tempBuffer.setSize (numInputChannels - numOutputChannels, numSamples, - false, false, true); + initialiseIoBuffers ({ inputChannelData, numInputChannels }, + { outputChannelData, numOutputChannels }, + numSamples, + actualProcessorChannels.ins, + actualProcessorChannels.outs, + tempBuffer, + channels); - for (int i = 0; i < numOutputChannels; ++i) - { - channels[totalNumChans] = outputChannelData[i]; - memcpy (channels[totalNumChans], inputChannelData[i], (size_t) numSamples * sizeof (float)); - ++totalNumChans; - } - - for (int i = numOutputChannels; i < numInputChannels; ++i) - { - channels[totalNumChans] = tempBuffer.getWritePointer (i - numOutputChannels); - memcpy (channels[totalNumChans], inputChannelData[i], (size_t) numSamples * sizeof (float)); - ++totalNumChans; - } - } - else - { - for (int i = 0; i < numInputChannels; ++i) - { - channels[totalNumChans] = outputChannelData[i]; - memcpy (channels[totalNumChans], inputChannelData[i], (size_t) numSamples * sizeof (float)); - ++totalNumChans; - } - - for (int i = numInputChannels; i < numOutputChannels; ++i) - { - channels[totalNumChans] = outputChannelData[i]; - zeromem (channels[totalNumChans], (size_t) numSamples * sizeof (float)); - ++totalNumChans; - } - } - - AudioBuffer buffer (channels, totalNumChans, numSamples); + const auto totalNumChannels = jmax (actualProcessorChannels.ins, actualProcessorChannels.outs); + AudioBuffer buffer (channels.data(), (int) totalNumChannels, numSamples); { const ScopedLock sl (lock); @@ -205,11 +310,11 @@ void AudioProcessorPlayer::audioDeviceAboutToStart (AudioIODevice* const device) sampleRate = newSampleRate; blockSize = newBlockSize; - numInputChans = numChansIn; - numOutputChans = numChansOut; + deviceChannels = { numChansIn, numChansOut }; + + resizeChannels(); messageCollector.reset (sampleRate); - channels.calloc (jmax (numChansIn, numChansOut) + 2); if (processor != nullptr) { @@ -240,4 +345,90 @@ void AudioProcessorPlayer::handleIncomingMidiMessage (MidiInput*, const MidiMess messageCollector.addMessageToQueue (message); } +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +struct AudioProcessorPlayerTests : public UnitTest +{ + AudioProcessorPlayerTests() + : UnitTest ("AudioProcessorPlayer", UnitTestCategories::audio) {} + + void runTest() override + { + struct Layout + { + int numIns, numOuts; + }; + + const Layout processorLayouts[] { Layout { 0, 0 }, + Layout { 1, 1 }, + Layout { 4, 4 }, + Layout { 4, 8 }, + Layout { 8, 4 } }; + + beginTest ("Buffers are prepared correctly for a variety of channel layouts"); + { + for (const auto& layout : processorLayouts) + { + for (const auto numSystemInputs : { 0, 1, layout.numIns }) + { + const int numSamples = 256; + const auto systemIns = getTestBuffer (numSystemInputs, numSamples); + auto systemOuts = getTestBuffer (layout.numOuts, numSamples); + AudioBuffer tempBuffer (jmax (layout.numIns, layout.numOuts), numSamples); + std::vector channels ((size_t) jmax (layout.numIns, layout.numOuts), nullptr); + + initialiseIoBuffers ({ systemIns.getArrayOfReadPointers(), systemIns.getNumChannels() }, + { systemOuts.getArrayOfWritePointers(), systemOuts.getNumChannels() }, + numSamples, + layout.numIns, + layout.numOuts, + tempBuffer, + channels); + + int channelIndex = 0; + + for (const auto& channel : channels) + { + const auto value = [&] + { + // Any channels past the number of inputs should be silent. + if (layout.numIns <= channelIndex) + return 0.0f; + + // If there's no input, all input channels should be silent. + if (numSystemInputs == 0) return 0.0f; + + // If there's one input, all input channels should copy from that input. + if (numSystemInputs == 1) return 1.0f; + + // Otherwise, each processor input should match the corresponding system input. + return (float) (channelIndex + 1); + }(); + + expect (FloatVectorOperations::findMinAndMax (channel, numSamples) == Range (value, value)); + + channelIndex += 1; + } + } + } + } + } + + static AudioBuffer getTestBuffer (int numChannels, int numSamples) + { + AudioBuffer result (numChannels, numSamples); + + for (int i = 0; i < result.getNumChannels(); ++i) + FloatVectorOperations::fill (result.getWritePointer (i), (float) i + 1, result.getNumSamples()); + + return result; + } +}; + +static AudioProcessorPlayerTests audioProcessorPlayerTests; + +#endif + } // namespace juce diff --git a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h index 4458dd2ab5..b078518a79 100644 --- a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h +++ b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h @@ -101,6 +101,27 @@ public: void handleIncomingMidiMessage (MidiInput*, const MidiMessage&) override; private: + struct NumChannels + { + NumChannels() = default; + NumChannels (int numIns, int numOuts) : ins (numIns), outs (numOuts) {} + + explicit NumChannels (const AudioProcessor::BusesLayout& layout) + : ins (layout.getNumChannels (true, 0)), outs (layout.getNumChannels (false, 0)) {} + + AudioProcessor::BusesLayout toLayout() const + { + return { { AudioChannelSet::canonicalChannelSet (ins) }, + { AudioChannelSet::canonicalChannelSet (outs) } }; + } + + int ins = 0, outs = 0; + }; + + //============================================================================== + NumChannels findMostSuitableLayout (const AudioProcessor&) const; + void resizeChannels(); + //============================================================================== AudioProcessor* processor = nullptr; CriticalSection lock; @@ -108,8 +129,8 @@ private: int blockSize = 0; bool isPrepared = false, isDoublePrecision = false; - int numInputChans = 0, numOutputChans = 0; - HeapBlock channels; + NumChannels deviceChannels, defaultProcessorChannels, actualProcessorChannels; + std::vector channels; AudioBuffer tempBuffer; AudioBuffer conversionBuffer;