From 6057d1b2e783fd7fcfc00eb30fbfd12f6cff66fa Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Fri, 31 May 2024 20:16:33 -0400 Subject: [PATCH] Restore the computation of master effects --- libraries/lib-audio-io/AudioIO.cpp | 50 +++++++++++++++++++++++++----- libraries/lib-audio-io/AudioIO.h | 8 +++-- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/libraries/lib-audio-io/AudioIO.cpp b/libraries/lib-audio-io/AudioIO.cpp index 1fa3e303745b..843489416ed4 100644 --- a/libraries/lib-audio-io/AudioIO.cpp +++ b/libraries/lib-audio-io/AudioIO.cpp @@ -2073,8 +2073,27 @@ static PlaybackSlice FindSlice(const PlaybackSchedule &playbackSchedule, } void AudioIO::ProcessPlaybackSlices( + std::optional &pScope, size_t demand) +{ + const bool paused = IsPaused(); + size_t preprocessed = 0; + do { + const auto discarded = + ProcessPlaybackSlicesPass(paused, pScope, demand, preprocessed); + // Demand again, as much as was discarded at the last step + // Note <=, not < in the assertion. + // What, then, guarantees loop termination? + // This ultimately relies on each RealtimeEffectState strictly decreasing + // its outstanding latency, given a nonzero sized buffer to fill. + assert(discarded <= demand); + preprocessed += demand - std::min(discarded, demand); + demand = discarded; + } while(demand > 0); +} + +size_t AudioIO::ProcessPlaybackSlicesPass(bool paused, std::optional &pScope, - const size_t orig_demand) + const size_t orig_demand, size_t preprocessed) { auto demand = orig_demand; auto &policy = mPlaybackSchedule.GetPolicy(); @@ -2089,13 +2108,12 @@ void AudioIO::ProcessPlaybackSlices( // user interface. bool done = false; const auto numPlaybackChannels = GetNumPlaybackChannels(); - const bool paused = IsPaused(); const auto &pMessage = state.mpMessage; std::optional debugPrevTime, debugNextTime; // First fill ring buffers - bool allDone = ConsumeFromMixers(paused, orig_demand, pScope, + bool allDone = ConsumeFromMixers(paused, orig_demand, preprocessed, pScope, debugPrevTime, debugNextTime); debugTimes(debugPrevTime, lastTime, mRate); @@ -2130,9 +2148,25 @@ void AudioIO::ProcessPlaybackSlices( // Apply per-track gain and pan and muting and micro-fades, all post-effects ApplyChannelGains(); MixChannels(orig_demand); + + // Apply master effectcs + const auto discardable = + ApplyEffectStack(pScope, nullptr, &mMasterBuffers[0], preprocessed); + + // Discard equal amounts from the upstream, per-channel buffers + if (discardable > 0) + for (auto &track : mPlaybackTracks) { + for (size_t iChannel = 0; iChannel < numPlaybackChannels; ++iChannel) { + auto &ringBuffer = *track.mBuffers[iChannel]; + auto discarded = ringBuffer.Unput(discardable); + assert(discarded == discardable); + } + } + return discardable; } -bool AudioIO::ConsumeFromMixers(bool paused, const size_t orig_demand, +bool AudioIO::ConsumeFromMixers(bool paused, + const size_t orig_demand, size_t preprocessed, std::optional &pScope, std::optional &debugPrevTime, std::optional &debugNextTime) { @@ -2171,7 +2205,8 @@ bool AudioIO::ConsumeFromMixers(bool paused, const size_t orig_demand, const auto buffers = &track.mBuffers[0]; ConsumeFromMixer(frames, toProduce, mixer, vt->NChannels(), buffers); - auto discarded = ApplyEffectStack(pScope, pGroup, buffers); + auto discarded = + ApplyEffectStack(pScope, pGroup, buffers, preprocessed); hasLatency = hasLatency || discarded > 0; // this should reproduce the side effect on state that @@ -2185,6 +2220,7 @@ bool AudioIO::ConsumeFromMixers(bool paused, const size_t orig_demand, // loop may repeat and the state may run ahead, fetching more from // the track (looping as required), or feeding more silence into the // effect processor for pause or end of play. + // See comments in ProcessPlaybackSlices on termination guarantee assert(discarded <= frames); fewerFrames -= std::min(frames, discarded); @@ -2240,7 +2276,7 @@ void AudioIO::ConsumeFromMixer(size_t frames, size_t toProduce, size_t AudioIO::ApplyEffectStack( std::optional &pScope, const ChannelGroup *pGroup, - std::unique_ptr playbackBuffers[]) + std::unique_ptr playbackBuffers[], size_t preprocessed) { // Avoiding std::vector std::array pointers{}; @@ -2252,7 +2288,7 @@ size_t AudioIO::ApplyEffectStack( size_t len = 0; for (size_t iChannel = 0; iChannel < numPlaybackChannels; ++iChannel) { auto &ringBuffer = *playbackBuffers[iChannel]; - const auto pair = ringBuffer.GetUnflushed(0, iBlock); + const auto pair = ringBuffer.GetUnflushed(preprocessed, iBlock); // Playback RingBuffers have float format: see AllocateBuffers pointers[iChannel] = reinterpret_cast(pair.first); // The lengths of corresponding unflushed blocks should be diff --git a/libraries/lib-audio-io/AudioIO.h b/libraries/lib-audio-io/AudioIO.h index 13e113bb5b3e..e8550df202d5 100644 --- a/libraries/lib-audio-io/AudioIO.h +++ b/libraries/lib-audio-io/AudioIO.h @@ -620,7 +620,11 @@ class AUDIO_IO_API AudioIO final void PollUser(PlaybackState &state, size_t newFrames); void ProcessPlaybackSlices( std::optional &pScope, size_t demand); - bool ConsumeFromMixers(bool paused, size_t demand, + //! @return how many samples were discarded for master stack's latency + size_t ProcessPlaybackSlicesPass(bool paused, + std::optional &pScope, size_t demand, + size_t preprocessed); + bool ConsumeFromMixers(bool paused, size_t demand, size_t preprocessed, std::optional &pScope, std::optional &debugPrevTime, std::optional &debugNextTime); @@ -633,7 +637,7 @@ class AUDIO_IO_API AudioIO final size_t ApplyEffectStack( std::optional &scope, const ChannelGroup *pGroup, - std::unique_ptr playbackBuffers[]); + std::unique_ptr playbackBuffers[], size_t preprocessed); void ApplyChannelGains(); void MixChannels(size_t demand);