mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
DSP: Various improvements to the convolution engine (see commit message for more info)
* The stereo option now works like in most guitar amplifier simulations : whatever the number of channels in the process function, or defined in the prepare function, the parameter "wantsStereo" allows the class to do all the time mono or stereo processing * Fixed a few issues when the user tries to change the normalization or trimming option without changing the impulse response itself * Reduced memory consumption in various places * Various improvements to the documentation
This commit is contained in:
parent
1dc62a397c
commit
f73fc41af1
2 changed files with 236 additions and 167 deletions
|
|
@ -50,17 +50,22 @@ struct ConvolutionEngine
|
|||
SourceType sourceType = SourceType::sourceNone;
|
||||
|
||||
const void* sourceData;
|
||||
size_t sourceDataSize;
|
||||
int sourceDataSize;
|
||||
File fileImpulseResponse;
|
||||
double bufferSampleRate;
|
||||
|
||||
double originalSampleRate;
|
||||
int originalSize = 0;
|
||||
int originalNumChannels = 1;
|
||||
|
||||
AudioBuffer<float>* buffer;
|
||||
|
||||
double sampleRate = 0;
|
||||
bool wantsStereo = true;
|
||||
bool wantsTrimming = true;
|
||||
bool wantsNormalization = true;
|
||||
size_t impulseResponseSize = 0;
|
||||
int64 wantedSize = 0;
|
||||
int finalSize = 0;
|
||||
|
||||
double sampleRate = 0;
|
||||
size_t maximumBufferSize = 0;
|
||||
};
|
||||
|
||||
|
|
@ -86,11 +91,11 @@ struct ConvolutionEngine
|
|||
FFTSize = blockSize > 128 ? 2 * blockSize
|
||||
: 4 * blockSize;
|
||||
|
||||
numSegments = ((size_t) info.buffer->getNumSamples()) / (FFTSize - blockSize) + 1u;
|
||||
numSegments = ((size_t) info.finalSize) / (FFTSize - blockSize) + 1u;
|
||||
|
||||
numInputSegments = (blockSize > 128 ? numSegments : 3 * numSegments);
|
||||
|
||||
FFTobject = new FFT (roundToInt (std::log2 (FFTSize)));
|
||||
FFTobject.reset (new FFT (roundToInt (std::log2 (FFTSize))));
|
||||
|
||||
bufferInput.setSize (1, static_cast<int> (FFTSize));
|
||||
bufferOutput.setSize (1, static_cast<int> (FFTSize * 2));
|
||||
|
|
@ -115,28 +120,24 @@ struct ConvolutionEngine
|
|||
}
|
||||
|
||||
ScopedPointer<FFT> FFTTempObject = new FFT (roundToInt (std::log2 (FFTSize)));
|
||||
auto numChannels = (info.wantsStereo && info.buffer->getNumChannels() >= 2 ? 2 : 1);
|
||||
|
||||
if (channel < numChannels)
|
||||
auto* channelData = info.buffer->getWritePointer (channel);
|
||||
|
||||
for (size_t n = 0; n < numSegments; ++n)
|
||||
{
|
||||
auto* channelData = info.buffer->getWritePointer (channel);
|
||||
buffersImpulseSegments.getReference (static_cast<int> (n)).clear();
|
||||
|
||||
for (size_t n = 0; n < numSegments; ++n)
|
||||
{
|
||||
buffersImpulseSegments.getReference (static_cast<int> (n)).clear();
|
||||
auto* impulseResponse = buffersImpulseSegments.getReference (static_cast<int> (n)).getWritePointer (0);
|
||||
|
||||
auto* impulseResponse = buffersImpulseSegments.getReference (static_cast<int> (n)).getWritePointer (0);
|
||||
if (n == 0)
|
||||
impulseResponse[0] = 1.0f;
|
||||
|
||||
if (n == 0)
|
||||
impulseResponse[0] = 1.0f;
|
||||
for (size_t i = 0; i < FFTSize - blockSize; ++i)
|
||||
if (i + n * (FFTSize - blockSize) < (size_t) info.finalSize)
|
||||
impulseResponse[i] = channelData[i + n * (FFTSize - blockSize)];
|
||||
|
||||
for (size_t i = 0; i < FFTSize - blockSize; ++i)
|
||||
if (i + n * (FFTSize - blockSize) < (size_t) info.buffer->getNumSamples())
|
||||
impulseResponse[i] = channelData[i + n * (FFTSize - blockSize)];
|
||||
|
||||
FFTTempObject->performRealOnlyForwardTransform (impulseResponse);
|
||||
prepareForConvolution (impulseResponse);
|
||||
}
|
||||
FFTTempObject->performRealOnlyForwardTransform (impulseResponse);
|
||||
prepareForConvolution (impulseResponse);
|
||||
}
|
||||
|
||||
reset();
|
||||
|
|
@ -149,7 +150,7 @@ struct ConvolutionEngine
|
|||
{
|
||||
if (FFTSize != other.FFTSize)
|
||||
{
|
||||
FFTobject = new FFT (roundToInt (std::log2 (other.FFTSize)));
|
||||
FFTobject.reset (new FFT (roundToInt (std::log2 (other.FFTSize))));
|
||||
FFTSize = other.FFTSize;
|
||||
}
|
||||
|
||||
|
|
@ -344,6 +345,7 @@ struct Convolution::Pimpl : private Thread
|
|||
changeStereo,
|
||||
changeTrimming,
|
||||
changeNormalization,
|
||||
changeIgnore,
|
||||
numChangeRequestTypes
|
||||
};
|
||||
|
||||
|
|
@ -353,14 +355,21 @@ struct Convolution::Pimpl : private Thread
|
|||
Pimpl() : Thread ("Convolution"), abstractFifo (fifoSize)
|
||||
{
|
||||
abstractFifo.reset();
|
||||
fifoRequestsType.resize (fifoSize);
|
||||
fifoRequestsParameter.resize (fifoSize);
|
||||
|
||||
requestsType.resize (fifoSize);
|
||||
requestsParameter.resize (fifoSize);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
for (auto i = 0; i < 4; ++i)
|
||||
engines.add (new ConvolutionEngine());
|
||||
|
||||
currentInfo.maximumBufferSize = 0;
|
||||
currentInfo.buffer = &impulseResponse;
|
||||
|
||||
temporaryBuffer.setSize (2, static_cast<int> (maximumTimeInSamples), false, false, true);
|
||||
impulseResponseOriginal.setSize (2, static_cast<int> (maximumTimeInSamples), false, false, true);
|
||||
impulseResponse.setSize (2, static_cast<int> (maximumTimeInSamples), false, false, true);
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
|
|
@ -377,14 +386,14 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
if (size1 > 0)
|
||||
{
|
||||
requestsType.setUnchecked (start1, type);
|
||||
requestsParameter.setUnchecked (start1, parameter);
|
||||
fifoRequestsType.setUnchecked (start1, type);
|
||||
fifoRequestsParameter.setUnchecked (start1, parameter);
|
||||
}
|
||||
|
||||
if (size2 > 0)
|
||||
{
|
||||
requestsType.setUnchecked (start2, type);
|
||||
requestsParameter.setUnchecked (start2, parameter);
|
||||
fifoRequestsType.setUnchecked (start2, type);
|
||||
fifoRequestsParameter.setUnchecked (start2, parameter);
|
||||
}
|
||||
|
||||
abstractFifo.finishedWrite (size1 + size2);
|
||||
|
|
@ -398,19 +407,19 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
if (size1 > 0)
|
||||
{
|
||||
for (int i = 0; i < size1; ++i)
|
||||
for (auto i = 0; i < size1; ++i)
|
||||
{
|
||||
requestsType.setUnchecked (start1 + i, types[i]);
|
||||
requestsParameter.setUnchecked (start1 + i, parameters[i]);
|
||||
fifoRequestsType.setUnchecked (start1 + i, types[i]);
|
||||
fifoRequestsParameter.setUnchecked (start1 + i, parameters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (size2 > 0)
|
||||
{
|
||||
for (int i = 0; i < size2; ++i)
|
||||
for (auto i = 0; i < size2; ++i)
|
||||
{
|
||||
requestsType.setUnchecked (start2 + i, types[i + size1]);
|
||||
requestsParameter.setUnchecked (start2 + i, parameters[i + size1]);
|
||||
fifoRequestsType.setUnchecked (start2 + i, types[i + size1]);
|
||||
fifoRequestsParameter.setUnchecked (start2 + i, parameters[i + size1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -425,14 +434,14 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
if (size1 > 0)
|
||||
{
|
||||
type = requestsType[start1];
|
||||
parameter = requestsParameter[start1];
|
||||
type = fifoRequestsType[start1];
|
||||
parameter = fifoRequestsParameter[start1];
|
||||
}
|
||||
|
||||
if (size2 > 0)
|
||||
{
|
||||
type = requestsType[start2];
|
||||
parameter = requestsParameter[start2];
|
||||
type = fifoRequestsType[start2];
|
||||
parameter = fifoRequestsParameter[start2];
|
||||
}
|
||||
|
||||
abstractFifo.finishedRead (size1 + size2);
|
||||
|
|
@ -456,10 +465,9 @@ struct Convolution::Pimpl : private Thread
|
|||
if (getNumRemainingEntries() == 0 || isThreadRunning() || mustInterpolate)
|
||||
return;
|
||||
|
||||
// retrieve the information from the FIFO for processing
|
||||
Array<ChangeRequest> requests;
|
||||
Array<juce::var> requestParameters;
|
||||
auto numRequests = 0;
|
||||
|
||||
// retrieve the information from the FIFO for processing
|
||||
while (getNumRemainingEntries() > 0)
|
||||
{
|
||||
ChangeRequest type = ChangeRequest::changeEngine;
|
||||
|
|
@ -467,37 +475,34 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
readFromFifo (type, parameter);
|
||||
|
||||
requests.add (type);
|
||||
requestParameters.add (parameter);
|
||||
requestsType.setUnchecked (numRequests, type);
|
||||
requestsParameter.setUnchecked (numRequests, parameter);
|
||||
|
||||
numRequests++;
|
||||
}
|
||||
|
||||
// remove any useless messages
|
||||
for (int i = 0; i < (int) ChangeRequest::numChangeRequestTypes; ++i)
|
||||
for (auto i = 0; i < (int) ChangeRequest::numChangeRequestTypes; ++i)
|
||||
{
|
||||
bool exists = false;
|
||||
|
||||
for (int n = requests.size(); --n >= 0;)
|
||||
for (auto n = numRequests; --n >= 0;)
|
||||
{
|
||||
if (requests[n] == (ChangeRequest) i)
|
||||
if (requestsType[n] == (ChangeRequest) i)
|
||||
{
|
||||
if (! exists)
|
||||
{
|
||||
exists = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
requests.remove (n);
|
||||
requestParameters.remove (n);
|
||||
}
|
||||
requestsType.setUnchecked (n, ChangeRequest::changeIgnore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeLevel = 0;
|
||||
|
||||
for (int n = 0; n < requests.size(); ++n)
|
||||
for (auto n = 0; n < numRequests; ++n)
|
||||
{
|
||||
switch (requests[n])
|
||||
switch (requestsType[n])
|
||||
{
|
||||
case ChangeRequest::changeEngine:
|
||||
changeLevel = 3;
|
||||
|
|
@ -505,7 +510,7 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
case ChangeRequest::changeSampleRate:
|
||||
{
|
||||
double newSampleRate = requestParameters[n];
|
||||
double newSampleRate = requestsParameter[n];
|
||||
|
||||
if (currentInfo.sampleRate != newSampleRate)
|
||||
changeLevel = 3;
|
||||
|
|
@ -516,7 +521,7 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
case ChangeRequest::changeMaximumBufferSize:
|
||||
{
|
||||
int newMaximumBufferSize = requestParameters[n];
|
||||
int newMaximumBufferSize = requestsParameter[n];
|
||||
|
||||
if (currentInfo.maximumBufferSize != (size_t) newMaximumBufferSize)
|
||||
changeLevel = 3;
|
||||
|
|
@ -527,7 +532,7 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
case ChangeRequest::changeSource:
|
||||
{
|
||||
auto* arrayParameters = requestParameters[n].getArray();
|
||||
auto* arrayParameters = requestsParameter[n].getArray();
|
||||
auto newSourceType = static_cast<SourceType> (static_cast<int> (arrayParameters->getUnchecked (0)));
|
||||
|
||||
if (currentInfo.sourceType != newSourceType)
|
||||
|
|
@ -539,7 +544,7 @@ struct Convolution::Pimpl : private Thread
|
|||
auto* newMemoryBlock = prm.getBinaryData();
|
||||
|
||||
auto* newPtr = newMemoryBlock->getData();
|
||||
auto newSize = newMemoryBlock->getSize();
|
||||
auto newSize = (int) newMemoryBlock->getSize();
|
||||
|
||||
if (currentInfo.sourceData != newPtr || currentInfo.sourceDataSize != newSize)
|
||||
changeLevel = jmax (2, changeLevel);
|
||||
|
|
@ -563,11 +568,11 @@ struct Convolution::Pimpl : private Thread
|
|||
}
|
||||
else if (newSourceType == SourceType::sourceAudioBuffer)
|
||||
{
|
||||
double bufferSampleRate (arrayParameters->getUnchecked (1));
|
||||
double originalSampleRate (arrayParameters->getUnchecked (1));
|
||||
changeLevel = jmax (2, changeLevel);
|
||||
|
||||
currentInfo.sourceType = SourceType::sourceAudioBuffer;
|
||||
currentInfo.bufferSampleRate = bufferSampleRate;
|
||||
currentInfo.originalSampleRate = originalSampleRate;
|
||||
currentInfo.fileImpulseResponse = File();
|
||||
currentInfo.sourceData = nullptr;
|
||||
currentInfo.sourceDataSize = 0;
|
||||
|
|
@ -577,21 +582,21 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
case ChangeRequest::changeImpulseResponseSize:
|
||||
{
|
||||
int64 newSize = requestParameters[n];
|
||||
int64 newSize = requestsParameter[n];
|
||||
|
||||
if (currentInfo.impulseResponseSize != (size_t) newSize)
|
||||
if (currentInfo.wantedSize != newSize)
|
||||
changeLevel = jmax (1, changeLevel);
|
||||
|
||||
currentInfo.impulseResponseSize = (size_t) newSize;
|
||||
currentInfo.wantedSize = newSize;
|
||||
}
|
||||
break;
|
||||
|
||||
case ChangeRequest::changeStereo:
|
||||
{
|
||||
bool newWantsStereo = requestParameters[n];
|
||||
bool newWantsStereo = requestsParameter[n];
|
||||
|
||||
if (currentInfo.wantsStereo != newWantsStereo)
|
||||
changeLevel = jmax (1, changeLevel);
|
||||
changeLevel = jmax (0, changeLevel);
|
||||
|
||||
currentInfo.wantsStereo = newWantsStereo;
|
||||
}
|
||||
|
|
@ -599,7 +604,7 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
case ChangeRequest::changeTrimming:
|
||||
{
|
||||
bool newWantsTrimming = requestParameters[n];
|
||||
bool newWantsTrimming = requestsParameter[n];
|
||||
|
||||
if (currentInfo.wantsTrimming != newWantsTrimming)
|
||||
changeLevel = jmax (1, changeLevel);
|
||||
|
|
@ -610,7 +615,7 @@ struct Convolution::Pimpl : private Thread
|
|||
|
||||
case ChangeRequest::changeNormalization:
|
||||
{
|
||||
bool newWantsNormalization = requestParameters[n];
|
||||
bool newWantsNormalization = requestsParameter[n];
|
||||
|
||||
if (currentInfo.wantsNormalization != newWantsNormalization)
|
||||
changeLevel = jmax (1, changeLevel);
|
||||
|
|
@ -619,6 +624,9 @@ struct Convolution::Pimpl : private Thread
|
|||
}
|
||||
break;
|
||||
|
||||
case ChangeRequest::changeIgnore:
|
||||
break;
|
||||
|
||||
default:
|
||||
jassertfalse;
|
||||
break;
|
||||
|
|
@ -635,8 +643,8 @@ struct Convolution::Pimpl : private Thread
|
|||
if (currentInfo.maximumBufferSize == 0)
|
||||
currentInfo.maximumBufferSize = 128;
|
||||
|
||||
currentInfo.bufferSampleRate = currentInfo.sampleRate;
|
||||
currentInfo.impulseResponseSize = 1;
|
||||
currentInfo.originalSampleRate = currentInfo.sampleRate;
|
||||
currentInfo.wantedSize = 1;
|
||||
currentInfo.fileImpulseResponse = File();
|
||||
currentInfo.sourceData = nullptr;
|
||||
currentInfo.sourceDataSize = 0;
|
||||
|
|
@ -651,46 +659,35 @@ struct Convolution::Pimpl : private Thread
|
|||
// action depending on the change level
|
||||
if (changeLevel == 3)
|
||||
{
|
||||
interpolationBuffer.setSize (2, static_cast<int> (currentInfo.maximumBufferSize));
|
||||
interpolationBuffer.setSize (2, static_cast<int> (currentInfo.maximumBufferSize), false, false, true);
|
||||
|
||||
loadImpulseResponse();
|
||||
processImpulseResponse();
|
||||
initializeConvolutionEngines();
|
||||
}
|
||||
else if (changeLevel == 2)
|
||||
{
|
||||
startThread();
|
||||
}
|
||||
else if (changeLevel == 1)
|
||||
else if (changeLevel > 0)
|
||||
{
|
||||
startThread();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void copyBufferToTemporaryLocation (const AudioBuffer<float>& buffer)
|
||||
/** This function copies a buffer to a temporary location, so that any external
|
||||
audio source can be processed then in the dedicated thread.
|
||||
*/
|
||||
void copyBufferToTemporaryLocation (dsp::AudioBlock<float> block)
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (processLock);
|
||||
|
||||
auto numChannels = buffer.getNumChannels() > 1 ? 2 : 1;
|
||||
temporaryBuffer.setSize (numChannels, buffer.getNumSamples(), false, false, true);
|
||||
currentInfo.originalNumChannels = (block.getNumChannels() > 1 ? 2 : 1);
|
||||
currentInfo.originalSize = (int) jmin ((size_t) maximumTimeInSamples, block.getNumSamples());
|
||||
|
||||
for (auto channel = 0; channel < numChannels; ++channel)
|
||||
temporaryBuffer.copyFrom (channel, 0, buffer, channel, 0, buffer.getNumSamples());
|
||||
}
|
||||
|
||||
/** Copies a buffer from a temporary location to the impulseResponseOriginal
|
||||
buffer for the sourceAudioBuffer. */
|
||||
void copyBufferFromTemporaryLocation()
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (processLock);
|
||||
|
||||
impulseResponseOriginal.setSize (2, temporaryBuffer.getNumSamples(), false, false, true);
|
||||
|
||||
for (auto channel = 0; channel < temporaryBuffer.getNumChannels(); ++channel)
|
||||
impulseResponseOriginal.copyFrom (channel, 0, temporaryBuffer, channel, 0, temporaryBuffer.getNumSamples());
|
||||
for (auto channel = 0; channel < currentInfo.originalNumChannels; ++channel)
|
||||
temporaryBuffer.copyFrom (channel, 0, block.getChannelPointer ((size_t) channel), (int) currentInfo.originalSize);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Resets the convolution engines states. */
|
||||
void reset()
|
||||
{
|
||||
for (auto* e : engines)
|
||||
|
|
@ -704,7 +701,7 @@ struct Convolution::Pimpl : private Thread
|
|||
{
|
||||
processFifo();
|
||||
|
||||
size_t numChannels = input.getNumChannels();
|
||||
size_t numChannels = jmin (input.getNumChannels(), (size_t) (currentInfo.wantsStereo ? 2 : 1));
|
||||
size_t numSamples = jmin (input.getNumSamples(), output.getNumSamples());
|
||||
|
||||
if (mustInterpolate == false)
|
||||
|
|
@ -714,7 +711,7 @@ struct Convolution::Pimpl : private Thread
|
|||
}
|
||||
else
|
||||
{
|
||||
auto interpolated = AudioBlock<float> (interpolationBuffer).getSubBlock (0, numSamples);
|
||||
auto interpolated = dsp::AudioBlock<float> (interpolationBuffer).getSubBlock (0, numSamples);
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
|
|
@ -732,6 +729,14 @@ struct Convolution::Pimpl : private Thread
|
|||
buffer += interpolated.getSingleChannelBlock (channel);
|
||||
}
|
||||
|
||||
if (input.getNumChannels() > 1 && currentInfo.wantsStereo == false)
|
||||
{
|
||||
auto&& buffer = output.getSingleChannelBlock (1);
|
||||
|
||||
changeVolumes[1].applyGain (buffer.getChannelPointer (0), (int) numSamples);
|
||||
changeVolumes[3].applyGain (buffer.getChannelPointer (0), (int) numSamples);
|
||||
}
|
||||
|
||||
if (changeVolumes[0].isSmoothing() == false)
|
||||
{
|
||||
mustInterpolate = false;
|
||||
|
|
@ -740,103 +745,137 @@ struct Convolution::Pimpl : private Thread
|
|||
engines[channel]->copyStateFromOtherEngine (*engines[channel + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.getNumChannels() > 1 && currentInfo.wantsStereo == false)
|
||||
output.getSingleChannelBlock (1).copy (output.getSingleChannelBlock (0));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const int64 maximumTimeInSamples = 10 * 96000;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
/** This the thread run function which does the preparation of data depending
|
||||
on the requested change level.
|
||||
*/
|
||||
void run() override
|
||||
{
|
||||
if (changeLevel == 2)
|
||||
{
|
||||
processImpulseResponse();
|
||||
loadImpulseResponse();
|
||||
|
||||
if (isThreadRunning() && threadShouldExit())
|
||||
return;
|
||||
}
|
||||
|
||||
initializeConvolutionEngines();
|
||||
}
|
||||
else if (changeLevel == 1)
|
||||
{
|
||||
initializeConvolutionEngines();
|
||||
}
|
||||
processImpulseResponse();
|
||||
|
||||
if (isThreadRunning() && threadShouldExit())
|
||||
return;
|
||||
|
||||
initializeConvolutionEngines();
|
||||
}
|
||||
|
||||
void processImpulseResponse()
|
||||
/** Loads the impulse response from the requested audio source. */
|
||||
void loadImpulseResponse()
|
||||
{
|
||||
if (currentInfo.sourceType == SourceType::sourceBinaryData)
|
||||
{
|
||||
copyAudioStreamInAudioBuffer (new MemoryInputStream (currentInfo.sourceData, currentInfo.sourceDataSize, false));
|
||||
if (! (copyAudioStreamInAudioBuffer (new MemoryInputStream (currentInfo.sourceData, (size_t) currentInfo.sourceDataSize, false))))
|
||||
return;
|
||||
}
|
||||
else if (currentInfo.sourceType == SourceType::sourceAudioFile)
|
||||
{
|
||||
copyAudioStreamInAudioBuffer (new FileInputStream (currentInfo.fileImpulseResponse));
|
||||
if (! (copyAudioStreamInAudioBuffer (new FileInputStream (currentInfo.fileImpulseResponse))))
|
||||
return;
|
||||
}
|
||||
else if (currentInfo.sourceType == SourceType::sourceAudioBuffer)
|
||||
{
|
||||
copyBufferFromTemporaryLocation();
|
||||
trimAndResampleImpulseResponse (temporaryBuffer.getNumChannels(), currentInfo.bufferSampleRate, currentInfo.wantsTrimming);
|
||||
}
|
||||
}
|
||||
|
||||
/** Processes the impulse response data with the requested treatments
|
||||
and resampling if needed.
|
||||
*/
|
||||
void processImpulseResponse()
|
||||
{
|
||||
trimAndResampleImpulseResponse (currentInfo.originalNumChannels, currentInfo.originalSampleRate, currentInfo.wantsTrimming);
|
||||
|
||||
if (isThreadRunning() && threadShouldExit())
|
||||
return;
|
||||
|
||||
if (currentInfo.wantsNormalization)
|
||||
{
|
||||
if (currentInfo.wantsStereo)
|
||||
if (currentInfo.originalNumChannels > 1)
|
||||
{
|
||||
normalizeImpulseResponse (currentInfo.buffer->getWritePointer(0), currentInfo.buffer->getNumSamples(), 1.0);
|
||||
normalizeImpulseResponse (currentInfo.buffer->getWritePointer(1), currentInfo.buffer->getNumSamples(), 1.0);
|
||||
normalizeImpulseResponse (currentInfo.buffer->getWritePointer (0), (int) currentInfo.finalSize, 1.0);
|
||||
normalizeImpulseResponse (currentInfo.buffer->getWritePointer (1), (int) currentInfo.finalSize, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
normalizeImpulseResponse (currentInfo.buffer->getWritePointer (0), currentInfo.buffer->getNumSamples(), 1.0);
|
||||
normalizeImpulseResponse (currentInfo.buffer->getWritePointer (0), (int) currentInfo.finalSize, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInfo.originalNumChannels == 1)
|
||||
currentInfo.buffer->copyFrom (1, 0, *currentInfo.buffer, 0, 0, (int) currentInfo.finalSize);
|
||||
}
|
||||
|
||||
/** Converts the data from an audio file into a stereo audio buffer of floats, and
|
||||
performs resampling if necessary.
|
||||
*/
|
||||
double copyAudioStreamInAudioBuffer (InputStream* stream)
|
||||
bool copyAudioStreamInAudioBuffer (InputStream* stream)
|
||||
{
|
||||
AudioFormatManager manager;
|
||||
manager.registerBasicFormats();
|
||||
|
||||
if (ScopedPointer<AudioFormatReader> formatReader = manager.createReaderFor (stream))
|
||||
{
|
||||
auto maximumTimeInSeconds = 10.0;
|
||||
auto maximumLength = static_cast<int64> (roundToInt (maximumTimeInSeconds * formatReader->sampleRate));
|
||||
auto numChannels = formatReader->numChannels > 1 ? 2 : 1;
|
||||
currentInfo.originalNumChannels = formatReader->numChannels > 1 ? 2 : 1;
|
||||
currentInfo.originalSampleRate = formatReader->sampleRate;
|
||||
currentInfo.originalSize = static_cast<int> (jmin (maximumTimeInSamples, formatReader->lengthInSamples));
|
||||
|
||||
impulseResponseOriginal.setSize (2, static_cast<int> (jmin (maximumLength, formatReader->lengthInSamples)), false, false, true);
|
||||
impulseResponseOriginal.clear();
|
||||
formatReader->read (&(impulseResponseOriginal), 0, impulseResponseOriginal.getNumSamples(), 0, true, numChannels > 1);
|
||||
formatReader->read (&(impulseResponseOriginal), 0, (int) currentInfo.originalSize, 0, true, currentInfo.originalNumChannels > 1);
|
||||
|
||||
return trimAndResampleImpulseResponse (numChannels, formatReader->sampleRate, currentInfo.wantsTrimming);
|
||||
return true;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
return false;
|
||||
}
|
||||
|
||||
double trimAndResampleImpulseResponse (int numChannels, double bufferSampleRate, bool mustTrim)
|
||||
/** Copies a buffer from a temporary location to the impulseResponseOriginal
|
||||
buffer for the sourceAudioBuffer.
|
||||
*/
|
||||
void copyBufferFromTemporaryLocation()
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (processLock);
|
||||
|
||||
for (auto channel = 0; channel < currentInfo.originalNumChannels; ++channel)
|
||||
impulseResponseOriginal.copyFrom (channel, 0, temporaryBuffer, channel, 0, (int) currentInfo.originalSize);
|
||||
}
|
||||
|
||||
/** Trim and resample the impulse response if needed. */
|
||||
void trimAndResampleImpulseResponse (int numChannels, double srcSampleRate, bool mustTrim)
|
||||
{
|
||||
auto thresholdTrim = Decibels::decibelsToGain (-80.0f);
|
||||
auto indexStart = 0;
|
||||
auto indexEnd = impulseResponseOriginal.getNumSamples() - 1;
|
||||
auto indexEnd = currentInfo.originalSize - 1;
|
||||
|
||||
if (mustTrim)
|
||||
{
|
||||
indexStart = impulseResponseOriginal.getNumSamples() - 1;
|
||||
indexStart = currentInfo.originalSize - 1;
|
||||
indexEnd = 0;
|
||||
|
||||
for (auto channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto localIndexStart = 0;
|
||||
auto localIndexEnd = impulseResponseOriginal.getNumSamples() - 1;
|
||||
auto localIndexEnd = currentInfo.originalSize - 1;
|
||||
|
||||
auto* channelData = impulseResponseOriginal.getReadPointer (channel);
|
||||
|
||||
while (localIndexStart < impulseResponseOriginal.getNumSamples() - 1
|
||||
while (localIndexStart < currentInfo.originalSize - 1
|
||||
&& channelData[localIndexStart] <= thresholdTrim
|
||||
&& channelData[localIndexStart] >= -thresholdTrim)
|
||||
++localIndexStart;
|
||||
|
|
@ -859,44 +898,39 @@ private:
|
|||
for (auto i = 0; i < indexEnd - indexStart + 1; ++i)
|
||||
channelData[i] = channelData[i + indexStart];
|
||||
|
||||
for (auto i = indexEnd - indexStart + 1; i < impulseResponseOriginal.getNumSamples() - 1; ++i)
|
||||
for (auto i = indexEnd - indexStart + 1; i < currentInfo.originalSize - 1; ++i)
|
||||
channelData[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double factorReading;
|
||||
|
||||
if (currentInfo.sampleRate == bufferSampleRate)
|
||||
if (currentInfo.sampleRate == srcSampleRate)
|
||||
{
|
||||
// No resampling
|
||||
factorReading = 1.0;
|
||||
auto impulseSize = jmin (static_cast<int> (currentInfo.impulseResponseSize), indexEnd - indexStart + 1);
|
||||
currentInfo.finalSize = jmin (static_cast<int> (currentInfo.wantedSize), indexEnd - indexStart + 1);
|
||||
|
||||
impulseResponse.setSize (2, impulseSize);
|
||||
impulseResponse.clear();
|
||||
|
||||
for (auto channel = 0; channel < numChannels; ++channel)
|
||||
impulseResponse.copyFrom (channel, 0, impulseResponseOriginal, channel, 0, impulseSize);
|
||||
impulseResponse.copyFrom (channel, 0, impulseResponseOriginal, channel, 0, (int) currentInfo.finalSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resampling
|
||||
factorReading = bufferSampleRate / currentInfo.sampleRate;
|
||||
auto impulseSize = jmin (static_cast<int> (currentInfo.impulseResponseSize), roundToInt ((indexEnd - indexStart + 1) / factorReading));
|
||||
auto factorReading = srcSampleRate / currentInfo.sampleRate;
|
||||
currentInfo.finalSize = jmin (static_cast<int> (currentInfo.wantedSize), roundToInt ((indexEnd - indexStart + 1) / factorReading));
|
||||
|
||||
impulseResponse.setSize (2, impulseSize);
|
||||
impulseResponse.clear();
|
||||
|
||||
MemoryAudioSource memorySource (impulseResponseOriginal, false);
|
||||
ResamplingAudioSource resamplingSource (&memorySource, false, numChannels);
|
||||
ResamplingAudioSource resamplingSource (&memorySource, false, (int) numChannels);
|
||||
|
||||
resamplingSource.setResamplingRatio (factorReading);
|
||||
resamplingSource.prepareToPlay (impulseSize, currentInfo.sampleRate);
|
||||
resamplingSource.prepareToPlay ((int) currentInfo.finalSize, currentInfo.sampleRate);
|
||||
|
||||
AudioSourceChannelInfo info;
|
||||
info.startSample = 0;
|
||||
info.numSamples = impulseSize;
|
||||
info.numSamples = (int) currentInfo.finalSize;
|
||||
info.buffer = &impulseResponse;
|
||||
|
||||
resamplingSource.getNextAudioBlock (info);
|
||||
|
|
@ -904,44 +938,42 @@ private:
|
|||
|
||||
// Filling the second channel with the first if necessary
|
||||
if (numChannels == 1)
|
||||
impulseResponse.copyFrom (1, 0, impulseResponse, 0, 0, impulseResponse.getNumSamples());
|
||||
|
||||
return factorReading;
|
||||
impulseResponse.copyFrom (1, 0, impulseResponse, 0, 0, (int) currentInfo.finalSize);
|
||||
}
|
||||
|
||||
/** Normalization of the impulse response based on its energy. */
|
||||
void normalizeImpulseResponse (float* samples, int numSamples, double factorResampling) const
|
||||
{
|
||||
auto magnitude = 0.0f;
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
for (auto i = 0; i < numSamples; ++i)
|
||||
magnitude += samples[i] * samples[i];
|
||||
|
||||
auto magnitudeInv = 1.0f / (4.0f * std::sqrt (magnitude)) * 0.5f * static_cast <float> (factorResampling);
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
for (auto i = 0; i < numSamples; ++i)
|
||||
samples[i] *= magnitudeInv;
|
||||
}
|
||||
|
||||
// ================================================================================================================
|
||||
/** Initializes the convolution engines depending on the provided sizes
|
||||
and performs the FFT on the impulse responses.
|
||||
*/
|
||||
void initializeConvolutionEngines()
|
||||
{
|
||||
if (currentInfo.maximumBufferSize == 0)
|
||||
return;
|
||||
|
||||
auto numChannels = (currentInfo.wantsStereo ? 2 : 1);
|
||||
|
||||
if (changeLevel == 3)
|
||||
{
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
for (auto i = 0; i < 2; ++i)
|
||||
engines[i]->initializeConvolutionEngine (currentInfo, i);
|
||||
|
||||
if (numChannels == 1)
|
||||
engines[1]->copyStateFromOtherEngine (*engines[0]);
|
||||
|
||||
mustInterpolate = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
for (auto i = 0; i < 2; ++i)
|
||||
{
|
||||
engines[i + 2]->initializeConvolutionEngine (currentInfo, i);
|
||||
engines[i + 2]->reset();
|
||||
|
|
@ -950,10 +982,7 @@ private:
|
|||
return;
|
||||
}
|
||||
|
||||
if (numChannels == 1)
|
||||
engines[3]->copyStateFromOtherEngine (*engines[2]);
|
||||
|
||||
for (size_t i = 0; i < 2; ++i)
|
||||
for (auto i = 0; i < 2; ++i)
|
||||
{
|
||||
changeVolumes[i].setValue (1.0f);
|
||||
changeVolumes[i].reset (currentInfo.sampleRate, 0.05);
|
||||
|
|
@ -974,6 +1003,9 @@ private:
|
|||
static constexpr int fifoSize = 256; // the size of the fifo which handles all the change requests
|
||||
AbstractFifo abstractFifo; // the abstract fifo
|
||||
|
||||
Array<ChangeRequest> fifoRequestsType; // an array of ChangeRequest
|
||||
Array<juce::var> fifoRequestsParameter; // an array of change parameters
|
||||
|
||||
Array<ChangeRequest> requestsType; // an array of ChangeRequest
|
||||
Array<juce::var> requestsParameter; // an array of change parameters
|
||||
|
||||
|
|
@ -1019,6 +1051,9 @@ void Convolution::loadImpulseResponse (const void* sourceData, size_t sourceData
|
|||
if (sourceData == nullptr)
|
||||
return;
|
||||
|
||||
auto maximumSamples = (size_t) pimpl->maximumTimeInSamples;
|
||||
auto wantedSize = (size == 0 ? maximumSamples : jmin (size, maximumSamples));
|
||||
|
||||
Pimpl::ChangeRequest types[] = { Pimpl::ChangeRequest::changeSource,
|
||||
Pimpl::ChangeRequest::changeImpulseResponseSize,
|
||||
Pimpl::ChangeRequest::changeStereo,
|
||||
|
|
@ -1031,7 +1066,7 @@ void Convolution::loadImpulseResponse (const void* sourceData, size_t sourceData
|
|||
sourceParameter.add (juce::var (sourceData, sourceDataSize));
|
||||
|
||||
juce::var parameters[] = { juce::var (sourceParameter),
|
||||
juce::var (static_cast<int64> (size)),
|
||||
juce::var (static_cast<int64> (wantedSize)),
|
||||
juce::var (wantsStereo),
|
||||
juce::var (wantsTrimming),
|
||||
juce::var (wantsNormalization) };
|
||||
|
|
@ -1045,6 +1080,9 @@ void Convolution::loadImpulseResponse (const File& fileImpulseResponse, bool wan
|
|||
if (! fileImpulseResponse.existsAsFile())
|
||||
return;
|
||||
|
||||
auto maximumSamples = (size_t) pimpl->maximumTimeInSamples;
|
||||
auto wantedSize = (size == 0 ? maximumSamples : jmin (size, maximumSamples));
|
||||
|
||||
Pimpl::ChangeRequest types[] = { Pimpl::ChangeRequest::changeSource,
|
||||
Pimpl::ChangeRequest::changeImpulseResponseSize,
|
||||
Pimpl::ChangeRequest::changeStereo,
|
||||
|
|
@ -1057,7 +1095,7 @@ void Convolution::loadImpulseResponse (const File& fileImpulseResponse, bool wan
|
|||
sourceParameter.add (juce::var (fileImpulseResponse.getFullPathName()));
|
||||
|
||||
juce::var parameters[] = { juce::var (sourceParameter),
|
||||
juce::var (static_cast<int64> (size)),
|
||||
juce::var (static_cast<int64> (wantedSize)),
|
||||
juce::var (wantsStereo),
|
||||
juce::var (wantsTrimming),
|
||||
juce::var (wantsNormalization) };
|
||||
|
|
@ -1065,15 +1103,25 @@ void Convolution::loadImpulseResponse (const File& fileImpulseResponse, bool wan
|
|||
pimpl->addToFifo (types, parameters, 5);
|
||||
}
|
||||
|
||||
void Convolution::copyAndLoadImpulseResponseFromBuffer (const AudioBuffer<float>& buffer,
|
||||
void Convolution::copyAndLoadImpulseResponseFromBuffer (AudioBuffer<float>& buffer,
|
||||
double bufferSampleRate, bool wantsStereo, bool wantsTrimming, bool wantsNormalization, size_t size)
|
||||
{
|
||||
copyAndLoadImpulseResponseFromBlock (AudioBlock<float> (buffer), bufferSampleRate,
|
||||
wantsStereo, wantsTrimming, wantsNormalization, size);
|
||||
}
|
||||
|
||||
void Convolution::copyAndLoadImpulseResponseFromBlock (AudioBlock<float> block, double bufferSampleRate,
|
||||
bool wantsStereo, bool wantsTrimming, bool wantsNormalization, size_t size)
|
||||
{
|
||||
jassert (bufferSampleRate > 0);
|
||||
|
||||
if (buffer.getNumSamples() == 0)
|
||||
if (block.getNumSamples() == 0)
|
||||
return;
|
||||
|
||||
pimpl->copyBufferToTemporaryLocation (buffer);
|
||||
auto maximumSamples = (size_t) pimpl->maximumTimeInSamples;
|
||||
auto wantedSize = (size == 0 ? maximumSamples : jmin (size, maximumSamples));
|
||||
|
||||
pimpl->copyBufferToTemporaryLocation (block);
|
||||
|
||||
Pimpl::ChangeRequest types[] = { Pimpl::ChangeRequest::changeSource,
|
||||
Pimpl::ChangeRequest::changeImpulseResponseSize,
|
||||
|
|
@ -1086,7 +1134,7 @@ void Convolution::copyAndLoadImpulseResponseFromBuffer (const AudioBuffer<float>
|
|||
sourceParameter.add (juce::var (bufferSampleRate));
|
||||
|
||||
juce::var parameters[] = { juce::var (sourceParameter),
|
||||
juce::var (static_cast<int64> (size)),
|
||||
juce::var (static_cast<int64> (wantedSize)),
|
||||
juce::var (wantsStereo),
|
||||
juce::var (wantsTrimming),
|
||||
juce::var (wantsNormalization) };
|
||||
|
|
@ -1134,7 +1182,7 @@ void Convolution::processSamples (const AudioBlock<float>& input, AudioBlock<flo
|
|||
jassert (input.getNumChannels() == output.getNumChannels());
|
||||
jassert (isPositiveAndBelow (input.getNumChannels(), static_cast<size_t> (3))); // only mono and stereo is supported
|
||||
|
||||
auto numChannels = input.getNumChannels();
|
||||
auto numChannels = jmin (input.getNumChannels(), (size_t) 2);
|
||||
auto numSamples = jmin (input.getNumSamples(), output.getNumSamples());
|
||||
|
||||
auto dry = dryBuffer.getSubsetChannelBlock (0, numChannels);
|
||||
|
|
|
|||
|
|
@ -90,9 +90,10 @@ public:
|
|||
|
||||
@param sourceData the block of data to use as the stream's source
|
||||
@param sourceDataSize the number of bytes in the source data block
|
||||
@param wantsStereo requests to load both stereo channels or only one mono channel
|
||||
@param wantsStereo requests to process both stereo channels or only one mono channel
|
||||
@param wantsTrimming requests to trim the start and the end of the impulse response
|
||||
@param size the expected size for the impulse response after loading
|
||||
@param size the expected size for the impulse response after loading, can be
|
||||
set to 0 for requesting maximum original impulse response size
|
||||
@param wantsNormalization requests to normalize the impulse response amplitude
|
||||
*/
|
||||
void loadImpulseResponse (const void* sourceData, size_t sourceDataSize,
|
||||
|
|
@ -104,9 +105,10 @@ public:
|
|||
resampling and pre-processing as well if needed.
|
||||
|
||||
@param fileImpulseResponse the location of the audio file
|
||||
@param wantsStereo requests to load both stereo channels or only one mono channel
|
||||
@param wantsStereo requests to process both stereo channels or only one mono channel
|
||||
@param wantsTrimming requests to trim the start and the end of the impulse response
|
||||
@param size the expected size for the impulse response after loading
|
||||
@param size the expected size for the impulse response after loading, can be
|
||||
set to 0 for requesting maximum original impulse response size
|
||||
@param wantsNormalization requests to normalize the impulse response amplitude
|
||||
*/
|
||||
void loadImpulseResponse (const File& fileImpulseResponse,
|
||||
|
|
@ -119,13 +121,32 @@ public:
|
|||
|
||||
@param buffer the AudioBuffer to use
|
||||
@param bufferSampleRate the sampleRate of the data in the AudioBuffer
|
||||
@param wantsStereo requests to load both stereo channels or only one mono channel
|
||||
@param wantsStereo requests to process both stereo channels or only one mono channel
|
||||
@param wantsTrimming requests to trim the start and the end of the impulse response
|
||||
@param wantsNormalization requests to normalize the impulse response amplitude
|
||||
@param size the expected size for the impulse response after loading
|
||||
@param size the expected size for the impulse response after loading, can be
|
||||
set to 0 for requesting maximum original impulse response size
|
||||
*/
|
||||
void copyAndLoadImpulseResponseFromBuffer (const AudioBuffer<float>& buffer, double bufferSampleRate,
|
||||
bool wantsStereo, bool wantsTrimming, bool wantsNormalization, size_t size);
|
||||
void copyAndLoadImpulseResponseFromBuffer (AudioBuffer<float>& buffer, double bufferSampleRate,
|
||||
bool wantsStereo, bool wantsTrimming, bool wantsNormalization,
|
||||
size_t size);
|
||||
|
||||
/** This function loads an impulse response from an audio block, which is
|
||||
copied before doing anything else. Performs some resampling and
|
||||
pre-processing as well if needed.
|
||||
|
||||
@param block the AudioBlock to use
|
||||
@param bufferSampleRate the sampleRate of the data in the AudioBuffer
|
||||
@param wantsStereo requests to process both stereo channels or only one channel
|
||||
@param wantsTrimming requests to trim the start and the end of the impulse response
|
||||
@param wantsNormalization requests to normalize the impulse response amplitude
|
||||
@param size the expected size for the impulse response after loading,
|
||||
-1 for maximum length
|
||||
*/
|
||||
void copyAndLoadImpulseResponseFromBlock (AudioBlock<float> block, double bufferSampleRate,
|
||||
bool wantsStereo, bool wantsTrimming, bool wantsNormalization,
|
||||
size_t size);
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue