|
|
|
@@ -26,6 +26,102 @@ |
|
|
|
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)
|
|
|
|
{
|
|
|
|
@@ -37,28 +133,64 @@ AudioProcessorPlayer::~AudioProcessorPlayer() |
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
AudioProcessorPlayer::NumChannels AudioProcessorPlayer::findMostSuitableLayout (const AudioProcessor& proc) const
|
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (processor != processorToPlay)
|
|
|
|
{
|
|
|
|
if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0)
|
|
|
|
{
|
|
|
|
processorToPlay->setPlayConfigDetails (numInputChans, numOutputChans, sampleRate, blockSize);
|
|
|
|
defaultProcessorChannels = NumChannels { processorToPlay->getBusesLayout() };
|
|
|
|
actualProcessorChannels = findMostSuitableLayout (*processorToPlay);
|
|
|
|
|
|
|
|
processorToPlay->setPlayConfigDetails (actualProcessorChannels.ins,
|
|
|
|
actualProcessorChannels.outs,
|
|
|
|
sampleRate,
|
|
|
|
blockSize);
|
|
|
|
|
|
|
|
bool supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision;
|
|
|
|
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<float> buffer (channels, totalNumChans, numSamples);
|
|
|
|
const auto totalNumChannels = jmax (actualProcessorChannels.ins, actualProcessorChannels.outs);
|
|
|
|
AudioBuffer<float> 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<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
|