From a97c1d7ee0a483876ffb8a062dd9648dfad02bd7 Mon Sep 17 00:00:00 2001 From: attila Date: Wed, 14 Dec 2022 18:27:10 +0100 Subject: [PATCH] ARAPluginDemo: Use crossfade in the EditorRenderer to avoid clicks --- examples/Plugins/ARAPluginDemo.h | 78 ++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/examples/Plugins/ARAPluginDemo.h b/examples/Plugins/ARAPluginDemo.h index 2abc297ec4..ded495d4cc 100644 --- a/examples/Plugins/ARAPluginDemo.h +++ b/examples/Plugins/ARAPluginDemo.h @@ -117,6 +117,18 @@ private: SpinLock processingFlag; }; +static void crossfade (const float* sourceA, + const float* sourceB, + float aProportionAtStart, + float aProportionAtFinish, + float* destinationBuffer, + int numSamples) +{ + AudioBuffer destination { &destinationBuffer, 1, numSamples }; + destination.copyFromWithRamp (0, 0, sourceA, numSamples, aProportionAtStart, aProportionAtFinish); + destination.addFromWithRamp (0, 0, sourceB, numSamples, 1.0f - aProportionAtStart, 1.0f - aProportionAtFinish); +} + class Looper { public: @@ -138,28 +150,60 @@ public: } const auto numChannelsToCopy = std::min (inputBuffer->getNumChannels(), buffer.getNumChannels()); + const auto actualCrossfadeLengthSamples = std::min (loopRange.getLength() / 2, (int64) desiredCrossfadeLengthSamples); for (auto samplesCopied = 0; samplesCopied < buffer.getNumSamples();) { - const auto numSamplesToCopy = - std::min (buffer.getNumSamples() - samplesCopied, (int) (loopRange.getEnd() - pos)); + const auto [needsCrossfade, samplePosOfNextCrossfadeTransition] = [&]() -> std::pair + { + if (const auto endOfFadeIn = loopRange.getStart() + actualCrossfadeLengthSamples; pos < endOfFadeIn) + return { true, endOfFadeIn }; + + return { false, loopRange.getEnd() - actualCrossfadeLengthSamples }; + }(); + + const auto samplesToNextCrossfadeTransition = samplePosOfNextCrossfadeTransition - pos; + const auto numSamplesToCopy = std::min (buffer.getNumSamples() - samplesCopied, + (int) samplesToNextCrossfadeTransition); + + const auto getFadeInGainAtPos = [this, actualCrossfadeLengthSamples] (auto p) + { + return jmap ((float) p, (float) loopRange.getStart(), (float) loopRange.getStart() + (float) actualCrossfadeLengthSamples - 1.0f, 0.0f, 1.0f); + }; for (int i = 0; i < numChannelsToCopy; ++i) { - buffer.copyFrom (i, samplesCopied, *inputBuffer, i, (int) pos, numSamplesToCopy); + if (needsCrossfade) + { + const auto overlapStart = loopRange.getEnd() - actualCrossfadeLengthSamples + + (pos - loopRange.getStart()); + + crossfade (inputBuffer->getReadPointer (i, (int) pos), + inputBuffer->getReadPointer (i, (int) overlapStart), + getFadeInGainAtPos (pos), + getFadeInGainAtPos (pos + numSamplesToCopy), + buffer.getWritePointer (i, samplesCopied), + numSamplesToCopy); + } + else + { + buffer.copyFrom (i, samplesCopied, *inputBuffer, i, (int) pos, numSamplesToCopy); + } } samplesCopied += numSamplesToCopy; pos += numSamplesToCopy; - jassert (pos <= loopRange.getEnd()); + jassert (pos <= loopRange.getEnd() - actualCrossfadeLengthSamples); - if (pos == loopRange.getEnd()) + if (pos == loopRange.getEnd() - actualCrossfadeLengthSamples) pos = loopRange.getStart(); } } private: + static constexpr int desiredCrossfadeLengthSamples = 50; + const AudioBuffer* inputBuffer; Range loopRange; int64 pos; @@ -510,8 +554,21 @@ public: if (! locked) return true; + const auto fadeOutIfNecessary = [this, &buffer] + { + if (std::exchange (wasPreviewing, false)) + { + previewLooper.writeInto (buffer); + const auto fadeOutStart = std::max (0, buffer.getNumSamples() - 50); + buffer.applyGainRamp (fadeOutStart, buffer.getNumSamples() - fadeOutStart, 1.0f, 0.0f); + } + }; + if (positionInfo.getIsPlaying()) + { + fadeOutIfNecessary(); return true; + } if (const auto previewedRegion = previewState->previewedRegion.load()) { @@ -565,9 +622,19 @@ public: else { previewLooper.writeInto (buffer); + + if (! std::exchange (wasPreviewing, true)) + { + const auto fadeInLength = std::min (50, buffer.getNumSamples()); + buffer.applyGainRamp (0, fadeInLength, 0.0f, 1.0f); + } } } } + else + { + fadeOutIfNecessary(); + } return true; }); @@ -599,6 +666,7 @@ private: double lastPreviewTime = 0.0; ARAPlaybackRegion* lastPlaybackRegion = nullptr; bool lastPreviewDimmed = false; + bool wasPreviewing = false; std::unique_ptr> previewBuffer; Looper previewLooper;