mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
436 lines
16 KiB
C++
436 lines
16 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2020 - Raw Material Software Limited
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
|
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
|
|
|
End User License Agreement: www.juce.com/juce-6-licence
|
|
Privacy Policy: www.juce.com/juce-privacy-policy
|
|
|
|
Or: You may also use this code under the terms of the GPL v3 (see
|
|
www.gnu.org/licenses).
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
|
|
template <typename Value>
|
|
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<const float> ins,
|
|
ChannelInfo<float> outs,
|
|
const int numSamples,
|
|
int processorIns,
|
|
int processorOuts,
|
|
AudioBuffer<float>& tempBuffer,
|
|
std::vector<float*>& 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)
|
|
{
|
|
}
|
|
|
|
AudioProcessorPlayer::~AudioProcessorPlayer()
|
|
{
|
|
setProcessor (nullptr);
|
|
}
|
|
|
|
//==============================================================================
|
|
AudioProcessorPlayer::NumChannels AudioProcessorPlayer::findMostSuitableLayout (const AudioProcessor& proc) const
|
|
{
|
|
if (proc.isMidiEffect())
|
|
return {};
|
|
|
|
std::vector<NumChannels> 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)
|
|
{
|
|
const ScopedLock sl (lock);
|
|
|
|
if (processor == processorToPlay)
|
|
return;
|
|
|
|
if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0)
|
|
{
|
|
defaultProcessorChannels = NumChannels { processorToPlay->getBusesLayout() };
|
|
actualProcessorChannels = findMostSuitableLayout (*processorToPlay);
|
|
|
|
if (processorToPlay->isMidiEffect())
|
|
processorToPlay->setRateAndBufferSizeDetails (sampleRate, blockSize);
|
|
else
|
|
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 = nullptr;
|
|
|
|
oldOne = isPrepared ? processor : nullptr;
|
|
processor = processorToPlay;
|
|
isPrepared = true;
|
|
resizeChannels();
|
|
|
|
if (oldOne != nullptr)
|
|
oldOne->releaseResources();
|
|
}
|
|
|
|
void AudioProcessorPlayer::setDoublePrecisionProcessing (bool doublePrecision)
|
|
{
|
|
if (doublePrecision != isDoublePrecision)
|
|
{
|
|
const ScopedLock sl (lock);
|
|
|
|
if (processor != nullptr)
|
|
{
|
|
processor->releaseResources();
|
|
|
|
auto supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision;
|
|
|
|
processor->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision
|
|
: AudioProcessor::singlePrecision);
|
|
processor->prepareToPlay (sampleRate, blockSize);
|
|
}
|
|
|
|
isDoublePrecision = doublePrecision;
|
|
}
|
|
}
|
|
|
|
void AudioProcessorPlayer::setMidiOutput (MidiOutput* midiOutputToUse)
|
|
{
|
|
if (midiOutput != midiOutputToUse)
|
|
{
|
|
const ScopedLock sl (lock);
|
|
midiOutput = midiOutputToUse;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChannelData,
|
|
const int numInputChannels,
|
|
float** const outputChannelData,
|
|
const int numOutputChannels,
|
|
const int numSamples)
|
|
{
|
|
const ScopedLock sl (lock);
|
|
|
|
// These should have been prepared by audioDeviceAboutToStart()...
|
|
jassert (sampleRate > 0 && blockSize > 0);
|
|
|
|
incomingMidi.clear();
|
|
messageCollector.removeNextBlockOfMessages (incomingMidi, numSamples);
|
|
|
|
initialiseIoBuffers ({ inputChannelData, numInputChannels },
|
|
{ outputChannelData, numOutputChannels },
|
|
numSamples,
|
|
actualProcessorChannels.ins,
|
|
actualProcessorChannels.outs,
|
|
tempBuffer,
|
|
channels);
|
|
|
|
const auto totalNumChannels = jmax (actualProcessorChannels.ins, actualProcessorChannels.outs);
|
|
AudioBuffer<float> buffer (channels.data(), (int) totalNumChannels, numSamples);
|
|
|
|
if (processor != nullptr)
|
|
{
|
|
// The processor should be prepared to deal with the same number of output channels
|
|
// as our output device.
|
|
jassert (processor->isMidiEffect() || numOutputChannels == actualProcessorChannels.outs);
|
|
|
|
const ScopedLock sl2 (processor->getCallbackLock());
|
|
|
|
if (! processor->isSuspended())
|
|
{
|
|
if (processor->isUsingDoublePrecision())
|
|
{
|
|
conversionBuffer.makeCopyOf (buffer, true);
|
|
processor->processBlock (conversionBuffer, incomingMidi);
|
|
buffer.makeCopyOf (conversionBuffer, true);
|
|
}
|
|
else
|
|
{
|
|
processor->processBlock (buffer, incomingMidi);
|
|
}
|
|
|
|
if (midiOutput != nullptr)
|
|
{
|
|
if (midiOutput->isBackgroundThreadRunning())
|
|
{
|
|
midiOutput->sendBlockOfMessages (incomingMidi,
|
|
Time::getMillisecondCounterHiRes(),
|
|
sampleRate);
|
|
}
|
|
else
|
|
{
|
|
midiOutput->sendBlockOfMessagesNow (incomingMidi);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < numOutputChannels; ++i)
|
|
FloatVectorOperations::clear (outputChannelData[i], numSamples);
|
|
}
|
|
|
|
void AudioProcessorPlayer::audioDeviceAboutToStart (AudioIODevice* const device)
|
|
{
|
|
auto newSampleRate = device->getCurrentSampleRate();
|
|
auto newBlockSize = device->getCurrentBufferSizeSamples();
|
|
auto numChansIn = device->getActiveInputChannels().countNumberOfSetBits();
|
|
auto numChansOut = device->getActiveOutputChannels().countNumberOfSetBits();
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
sampleRate = newSampleRate;
|
|
blockSize = newBlockSize;
|
|
deviceChannels = { numChansIn, numChansOut };
|
|
|
|
resizeChannels();
|
|
|
|
messageCollector.reset (sampleRate);
|
|
|
|
if (processor != nullptr)
|
|
{
|
|
if (isPrepared)
|
|
processor->releaseResources();
|
|
|
|
auto* oldProcessor = processor;
|
|
setProcessor (nullptr);
|
|
setProcessor (oldProcessor);
|
|
}
|
|
}
|
|
|
|
void AudioProcessorPlayer::audioDeviceStopped()
|
|
{
|
|
const ScopedLock sl (lock);
|
|
|
|
if (processor != nullptr && isPrepared)
|
|
processor->releaseResources();
|
|
|
|
sampleRate = 0.0;
|
|
blockSize = 0;
|
|
isPrepared = false;
|
|
tempBuffer.setSize (1, 1);
|
|
}
|
|
|
|
void AudioProcessorPlayer::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message)
|
|
{
|
|
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<float> tempBuffer (jmax (layout.numIns, layout.numOuts), numSamples);
|
|
std::vector<float*> 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<float> (value, value));
|
|
|
|
channelIndex += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static AudioBuffer<float> getTestBuffer (int numChannels, int numSamples)
|
|
{
|
|
AudioBuffer<float> 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
|