Audacity 3.2.0
Classes | Public Types | Public Member Functions | Static Public Member Functions | Private Member Functions | Private Attributes | List of all members
AudioGraph::EffectStage Class Referencefinal

Decorates a source with a non-timewarping effect, which may have latency. More...

#include <EffectStage.h>

Inheritance diagram for AudioGraph::EffectStage:
[legend]
Collaboration diagram for AudioGraph::EffectStage:
[legend]

Classes

struct  CreateToken
 

Public Types

using Factory = std::function< std::shared_ptr< EffectInstance >()>
 
- Public Types inherited from AudioGraph::Source
using Buffers = AudioGraph::Buffers
 

Public Member Functions

 EffectStage (CreateToken, bool multi, Source &upstream, Buffers &inBuffers, const Factory &factory, EffectSettings &settings, double sampleRate, std::optional< sampleCount > genLength, const Track &track)
 Don't call directly but use Create() More...
 
 EffectStage (const EffectStage &)=delete
 
EffectStageoperator= (const EffectStage &)=delete
 
 ~EffectStage () override
 Finalizes the instance. More...
 
bool AcceptsBuffers (const Buffers &buffers) const override
 
bool AcceptsBlockSize (size_t size) const override
 See postcondition of constructor. More...
 
std::optional< size_t > Acquire (Buffers &data, size_t bound) override
 Occupy vacant space in Buffers with some data. More...
 
sampleCount Remaining () const override
 Result includes any amount Acquired and not yet Released. More...
 
bool Release () override
 Caller is done examining last Acquire()d positions. More...
 
- Public Member Functions inherited from AudioGraph::Source
virtual ~Source ()
 
virtual bool AcceptsBuffers (const Buffers &buffers) const =0
 
virtual bool AcceptsBlockSize (size_t blockSize) const =0
 
virtual std::optional< size_t > Acquire (Buffers &data, size_t bound)=0
 Occupy vacant space in Buffers with some data. More...
 
virtual sampleCount Remaining () const =0
 Result includes any amount Acquired and not yet Released. More...
 
virtual bool Release ()=0
 Caller is done examining last Acquire()d positions. More...
 
virtual bool Terminates () const
 Needed only to make some postconditions assertable; defaults true. More...
 

Static Public Member Functions

static std::unique_ptr< EffectStageCreate (bool multi, Source &upstream, Buffers &inBuffers, const Factory &factory, EffectSettings &settings, double sampleRate, std::optional< sampleCount > genLength, const Track &track)
 Satisfies postcondition of constructor or returns null. More...
 

Private Member Functions

sampleCount DelayRemaining () const
 
bool Process (EffectInstance &instance, size_t channel, const Buffers &data, size_t curBlockSize, size_t outBufferOffset) const
 Produce exactly curBlockSize samples in data More...
 
std::optional< size_t > FetchProcessAndAdvance (Buffers &data, size_t bound, bool doZeros, size_t outBufferOffset=0)
 

Private Attributes

SourcemUpstream
 
BuffersmInBuffers
 
const std::vector< std::shared_ptr< EffectInstance > > mInstances
 
EffectSettingsmSettings
 
const double mSampleRate
 
const bool mIsProcessor
 
sampleCount mDelayRemaining
 
size_t mLastProduced {}
 
size_t mLastZeroes {}
 
bool mLatencyDone { false }
 
bool mCleared { false }
 

Detailed Description

Decorates a source with a non-timewarping effect, which may have latency.

Definition at line 28 of file EffectStage.h.

Member Typedef Documentation

◆ Factory

using AudioGraph::EffectStage::Factory = std::function<std::shared_ptr<EffectInstance>()>

Definition at line 32 of file EffectStage.h.

Constructor & Destructor Documentation

◆ EffectStage() [1/2]

AudioGraph::EffectStage::EffectStage ( CreateToken  ,
bool  multi,
Source upstream,
Buffers inBuffers,
const Factory factory,
EffectSettings settings,
double  sampleRate,
std::optional< sampleCount genLength,
const Track track 
)

Don't call directly but use Create()

Parameters
factoryused only in construction, will be invoked one or more times
Precondition
upstream.AcceptsBlockSize(inBuffers.BlockSize())
Postcondition
AcceptsBlockSize(inBuffers.BlockSize())
ProcessInitialize() succeeded on each instance that was made by factory
Parameters
mapnot required after construction

Definition at line 69 of file EffectStage.cpp.

73 : mUpstream{ upstream }, mInBuffers{ inBuffers }
74 , mInstances{ MakeInstances(factory, settings, sampleRate, track,
75 genLength, multi) }
76 , mSettings{ settings }, mSampleRate{ sampleRate }
77 , mIsProcessor{ !genLength.has_value() }
78 , mDelayRemaining{ genLength ? *genLength : sampleCount::max() }
79{
80 assert(upstream.AcceptsBlockSize(inBuffers.BlockSize()));
81 assert(this->AcceptsBlockSize(inBuffers.BlockSize()));
82
83 // Establish invariant
85}
static Settings & settings()
Definition: TrackInfo.cpp:83
void Rewind()
Reset positions to starts of buffers.
const std::vector< std::shared_ptr< EffectInstance > > mInstances
Definition: EffectStage.h:91
bool AcceptsBlockSize(size_t size) const override
See postcondition of constructor.
const double mSampleRate
Definition: EffectStage.h:93
sampleCount mDelayRemaining
Definition: EffectStage.h:96
EffectSettings & mSettings
Definition: EffectStage.h:92
static sampleCount max()
Definition: SampleCount.h:69
std::vector< std::shared_ptr< EffectInstance > > MakeInstances(const AudioGraph::EffectStage::Factory &factory, EffectSettings &settings, double sampleRate, const Track &track, std::optional< sampleCount > genLength, bool multi)
Definition: EffectStage.cpp:23
static RegisteredToolbarFactory factory

References AudioGraph::Source::AcceptsBlockSize(), AcceptsBlockSize(), AudioGraph::Buffers::BlockSize(), mInBuffers, and AudioGraph::Buffers::Rewind().

Here is the call graph for this function:

◆ EffectStage() [2/2]

AudioGraph::EffectStage::EffectStage ( const EffectStage )
delete

◆ ~EffectStage()

AudioGraph::EffectStage::~EffectStage ( )
override

Finalizes the instance.

Definition at line 102 of file EffectStage.cpp.

103{
104 // Allow the instances to cleanup
105 for (auto &pInstance : mInstances)
106 if (pInstance)
107 pInstance->ProcessFinalize();
108}

Member Function Documentation

◆ AcceptsBlockSize()

bool AudioGraph::EffectStage::AcceptsBlockSize ( size_t  size) const
overridevirtual

See postcondition of constructor.

Implements AudioGraph::Source.

Definition at line 115 of file EffectStage.cpp.

116{
117 // Test the equality of input and output block sizes
118 return mInBuffers.BlockSize() == size;
119}
size_t BlockSize() const

References size.

Referenced by EffectStage().

Here is the caller graph for this function:

◆ AcceptsBuffers()

bool AudioGraph::EffectStage::AcceptsBuffers ( const Buffers buffers) const
overridevirtual
Returns
true

Implements AudioGraph::Source.

Definition at line 110 of file EffectStage.cpp.

111{
112 return true;
113}

◆ Acquire()

std::optional< size_t > AudioGraph::EffectStage::Acquire ( Buffers data,
size_t  bound 
)
overridevirtual

Occupy vacant space in Buffers with some data.

May exceeed a single block of production Can assume same buffer is passed each time, while the caller advances it over the previous production, or discards it, or rotates the buffer. May rewind or rotate the buffer.

Returns
number of positions available to read from data or nullopt to fail
Precondition
AcceptsBuffers(data)
AcceptsBlockSize(data.BlockSize())
bound <= data.BlockSize()
data.BlockSize() <= data.Remaining()
Postcondition
result: !result || *result <= bound
result: !result || *result <= data.Remaining()
result: !result || *result <= Remaining()
data.Remaining() > 0
result: !result || bound == 0 || Remaining() == 0 || *result > 0 (progress guarantee)
!Terminates() or Remaining() was not previously defined, or is unchanged

Implements AudioGraph::Source.

Definition at line 122 of file EffectStage.cpp.

123{
124 assert(AcceptsBuffers(data));
125 assert(AcceptsBlockSize(data.BlockSize()));
126 // pre, needed for Process() and Discard()
127 assert(bound <= std::min(data.BlockSize(), data.Remaining()));
128
129 // For each input block of samples, we pass it to the effect along with a
130 // variable output location. This output location is simply a pointer into a
131 // much larger buffer. This reduces the number of calls required to add the
132 // samples to the output track.
133 //
134 // Upon return from the effect, the output samples are "moved to the left" by
135 // the number of samples in the current latency setting, effectively removing any
136 // delay introduced by the effect.
137 //
138 // At the same time the total number of delayed samples are gathered and when
139 // there is no further input data to process, the loop continues to call the
140 // effect with an empty input buffer until the effect has had a chance to
141 // return all of the remaining delayed samples.
142
143 // Invariant satisfies pre for mUpstream.Acquire() and for Process()
145
146 size_t curBlockSize = 0;
147
148 if (auto oCurBlockSize = FetchProcessAndAdvance(data, bound, false)
149 ; !oCurBlockSize
150 )
151 return {};
152 else {
153 curBlockSize = *oCurBlockSize;
154 if (mIsProcessor && !mLatencyDone) {
155 // Come here only in the first call to Acquire()
156 // Some effects (like ladspa/lv2 swh plug-ins) don't report latency
157 // until at least one block of samples is processed. Find latency
158 // once only for the track and assume it doesn't vary
159 auto delay = mDelayRemaining =
160 mInstances[0]->GetLatency(mSettings, mSampleRate);
161 for (size_t ii = 1, nn = mInstances.size(); ii < nn; ++ii)
162 if (mInstances[ii] &&
163 mInstances[ii]->GetLatency(mSettings, mSampleRate) != delay)
164 // This mismatch is unexpected. Fail
165 return {};
166 // Discard all the latency
167 while (delay > 0 && curBlockSize > 0) {
168 auto discard = limitSampleBufferSize(curBlockSize, delay);
169 data.Discard(discard, curBlockSize - discard);
170 delay -= discard;
171 curBlockSize -= discard;
172 if (curBlockSize == 0) {
173 if (!(oCurBlockSize = FetchProcessAndAdvance(data, bound, false)
174 ))
175 return {};
176 else
177 curBlockSize = *oCurBlockSize;
178 }
179 mLastProduced -= discard;
180 }
181 if (curBlockSize > 0) {
182 assert(delay == 0);
183 if (curBlockSize < bound) {
184 // Discarded all the latency, while upstream may still be
185 // producing. Try to fill the buffer up to the bound.
186 if (!(oCurBlockSize = FetchProcessAndAdvance(
187 data, bound - curBlockSize, false, curBlockSize)
188 ))
189 return {};
190 else
191 curBlockSize += *oCurBlockSize;
192 }
193 }
194 else while (delay > 0) {
195 assert(curBlockSize == 0);
196 // Finish one-time delay in case it exceeds entire upstream length
197 // Upstream must have been exhausted
198 assert(mUpstream.Remaining() == 0);
199 // Feed zeroes to the effect
200 auto zeroes = limitSampleBufferSize(data.BlockSize(), delay);
201 if (!(FetchProcessAndAdvance(data, zeroes, true)))
202 return {};
203 delay -= zeroes;
204 // Debit mDelayRemaining later in Release()
205 }
206 mLatencyDone = true;
207 }
208 }
209
210 if (mIsProcessor && curBlockSize < bound) {
211 // If there is still a short buffer by this point, upstream must have
212 // been exhausted
213 assert(mUpstream.Remaining() == 0);
214
215 // Continue feeding zeroes; this code block will produce as many zeroes
216 // at the end as were discarded at the beginning (over one or more visits)
217 auto zeroes =
218 limitSampleBufferSize(bound - curBlockSize, mDelayRemaining);
219 if (!FetchProcessAndAdvance(data, zeroes, true, curBlockSize))
220 return {};
221 // Debit mDelayRemaining later in Release()
222 }
223
224 auto result = mLastProduced + mLastZeroes;
225 // assert the post
226 assert(data.Remaining() > 0);
227 assert(result <= bound);
228 assert(result <= data.Remaining());
229 assert(result <= Remaining());
230 assert(bound == 0 || Remaining() == 0 || result > 0);
231 return { result };
232}
int min(int a, int b)
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
size_t Remaining() const
sampleCount Remaining() const override
Result includes any amount Acquired and not yet Released.
bool AcceptsBuffers(const Buffers &buffers) const override
std::optional< size_t > FetchProcessAndAdvance(Buffers &data, size_t bound, bool doZeros, size_t outBufferOffset=0)
virtual sampleCount Remaining() const =0
Result includes any amount Acquired and not yet Released.

References AudioGraph::Buffers::BlockSize(), AudioGraph::Buffers::Discard(), limitSampleBufferSize(), min(), and AudioGraph::Buffers::Remaining().

Here is the call graph for this function:

◆ Create()

auto AudioGraph::EffectStage::Create ( bool  multi,
Source upstream,
Buffers inBuffers,
const Factory factory,
EffectSettings settings,
double  sampleRate,
std::optional< sampleCount genLength,
const Track track 
)
static

Satisfies postcondition of constructor or returns null.

Definition at line 87 of file EffectStage.cpp.

92{
93 try {
94 return std::make_unique<EffectStage>(CreateToken{}, multi,
95 upstream, inBuffers, factory, settings, sampleRate, genLength, track);
96 }
97 catch (const std::exception &) {
98 return nullptr;
99 }
100}

References cloud::factory, and settings().

Referenced by PerTrackEffect::ProcessTrack().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ DelayRemaining()

sampleCount AudioGraph::EffectStage::DelayRemaining ( ) const
inlineprivate

Definition at line 72 of file EffectStage.h.

73 { return std::max<sampleCount>(0, mDelayRemaining); }

◆ FetchProcessAndAdvance()

std::optional< size_t > AudioGraph::EffectStage::FetchProcessAndAdvance ( Buffers data,
size_t  bound,
bool  doZeros,
size_t  outBufferOffset = 0 
)
private

Definition at line 234 of file EffectStage.cpp.

236{
237 std::optional<size_t> oCurBlockSize;
238 // Generator always supplies zeroes in
239 doZeroes = doZeroes || !mIsProcessor;
240 if (!doZeroes)
241 oCurBlockSize = mUpstream.Acquire(mInBuffers, bound);
242 else {
243 if (!mCleared) {
244 // Need to do this the first time, only, that we begin to give zeroes
245 // to the processor
247 const auto blockSize = mInBuffers.BlockSize();
248 for (size_t ii = 0; ii < mInBuffers.Channels(); ++ii) {
249 auto p = &mInBuffers.GetWritePosition(ii);
250 std::fill(p, p + blockSize, 0);
251 }
252 mCleared = true;
253 }
254 oCurBlockSize = {
256 if (!mIsProcessor)
257 // Do this (ignoring result) so we can correctly Release() upstream
259 }
260 if (!oCurBlockSize)
261 return {};
262
263 const auto curBlockSize = *oCurBlockSize;
264 if (curBlockSize == 0)
265 assert(doZeroes || mUpstream.Remaining() == 0); // post of Acquire()
266 else {
267 // Called only in Acquire()
268 // invariant or post of mUpstream.Acquire() satisfies pre of Process()
269 // because curBlockSize <= bound <= mInBuffers.blockSize()
270 // == data.BlockSize()
271 // and mInBuffers.BlockSize() <= mInBuffers.Remaining() by invariant
272 // and data.BlockSize() <= data.Remaining() by pre of Acquire()
273 for (size_t ii = 0, nn = mInstances.size(); ii < nn; ++ii) {
274 auto &pInstance = mInstances[ii];
275 if (!pInstance)
276 continue;
277 if (!Process(*pInstance, ii, data, curBlockSize, outBufferOffset))
278 return {};
279 }
280
281 if (doZeroes) {
282 // Either a generator or doing the tail; will count down delay
284 if (!mIsProcessor) {
285 // This allows polling the progress meter for a generator
286 if (!mUpstream.Release())
287 return {};
288 }
289 }
290 else {
291 // Will count down the upstream
292 mLastProduced += curBlockSize;
293 if (!mUpstream.Release())
294 return {};
295 mInBuffers.Advance(curBlockSize);
297 // Maintain invariant minimum availability
299 }
300 }
301 return oCurBlockSize;
302}
float & GetWritePosition(unsigned iChannel)
Get writable position for one channel.
void Advance(size_t count)
Move the positions.
size_t Rotate()
Shift all data at and after the old position to position 0.
unsigned Channels() const
bool Process(EffectInstance &instance, size_t channel, const Buffers &data, size_t curBlockSize, size_t outBufferOffset) const
Produce exactly curBlockSize samples in data
sampleCount DelayRemaining() const
Definition: EffectStage.h:72
virtual std::optional< size_t > Acquire(Buffers &data, size_t bound)=0
Occupy vacant space in Buffers with some data.
virtual bool Release()=0
Caller is done examining last Acquire()d positions.

References limitSampleBufferSize().

Here is the call graph for this function:

◆ operator=()

EffectStage & AudioGraph::EffectStage::operator= ( const EffectStage )
delete

◆ Process()

bool AudioGraph::EffectStage::Process ( EffectInstance instance,
size_t  channel,
const Buffers data,
size_t  curBlockSize,
size_t  outBufferOffset 
) const
private

Produce exactly curBlockSize samples in data

Precondition
curBlockSize <= data.BlockSize() - outBufferOffset
curBlockSize <= data.Remaining() - outBufferOffset
curBlockSize <= mInBuffers.Remaining()
Returns
success

Definition at line 304 of file EffectStage.cpp.

307{
308 size_t processed{};
309 try {
310 const auto positions = mInBuffers.Positions();
311 const auto nPositions = mInBuffers.Channels();
312 // channel may be nonzero in the case of a plug-in that only reads
313 // one channel at a time, so multiple instances are made to mix stereo
314 assert(channel <= nPositions);
315 std::vector<float *> inPositions(
316 positions + channel, positions + nPositions - channel);
317 // When the plug-in expects many input channels, replicate the last
318 // buffer (assumed to be zero-filled) as dummy input
319 inPositions.resize(
320 instance.GetAudioInCount() - channel, inPositions.back());
321
322 std::vector<float *> advancedOutPositions;
323 const auto size = instance.GetAudioOutCount() - channel;
324 advancedOutPositions.reserve(size);
325
326 auto outPositions = data.Positions();
327 // It is assumed that data has at least one dummy buffer last
328 auto channels = data.Channels();
329 // channel may be nonzero in the case of a plug-in that only writes
330 // one channel at a time, so multiple instances are made to mix stereo
331 for (size_t ii = channel; ii < channels; ++ii)
332 advancedOutPositions.push_back(outPositions[ii] + outBufferOffset);
333 // When the plug-in expects many output channels, replicate the last
334 // as dummy output
335 advancedOutPositions.resize(size, advancedOutPositions.back());
336
337 processed = instance.ProcessBlock(mSettings,
338 inPositions.data(), advancedOutPositions.data(), curBlockSize);
339 }
340 catch (const AudacityException &) {
341 // PRL: Bug 437:
342 // Pass this along to our application-level handler
343 throw;
344 }
345 catch (...) {
346 // PRL:
347 // Exceptions for other reasons, maybe in third-party code...
348 // Continue treating them as we used to, but I wonder if these
349 // should now be treated the same way.
350 return false;
351 }
352
353 return (processed == curBlockSize);
354}
Base class for exceptions specially processed by the application.
float *const * Positions() const
Get array of positions in the buffers.
virtual unsigned GetAudioInCount() const =0
How many input buffers to allocate at once.
virtual size_t ProcessBlock(EffectSettings &settings, const float *const *inBlock, float *const *outBlock, size_t blockLen)=0
Called for destructive effect computation.
virtual unsigned GetAudioOutCount() const =0
How many output buffers to allocate at once.

References AudioGraph::Buffers::Channels(), EffectInstance::GetAudioInCount(), EffectInstance::GetAudioOutCount(), AudioGraph::Buffers::Positions(), EffectInstance::ProcessBlock(), and size.

Here is the call graph for this function:

◆ Release()

bool AudioGraph::EffectStage::Release ( )
overridevirtual

Caller is done examining last Acquire()d positions.

May be called only after at least one successful call to Acquire()

Returns
success
Postcondition
!Terminates() or Remaining() reduced by what was last returned by Acquire()

Implements AudioGraph::Source.

Definition at line 368 of file EffectStage.cpp.

369{
370 // Progress toward termination (Remaining() == 0),
371 // if mLastProduced + mLastZeroes > 0,
372 // which is what Acquire() last returned
374 assert(mDelayRemaining >= 0);
376 return true;
377}

◆ Remaining()

sampleCount AudioGraph::EffectStage::Remaining ( ) const
overridevirtual

Result includes any amount Acquired and not yet Released.

May be undefined before the first successful call to Acquire()

Postcondition
result: result >= 0

Implements AudioGraph::Source.

Definition at line 356 of file EffectStage.cpp.

357{
358 // Not correct until at least one call to Acquire() so that mDelayRemaining
359 // is assigned.
360 // mLastProduced will have the up-front latency discarding deducted.
361 // mDelayRemaining later decreases to 0 as zeroes are supplied to the
362 // processor at the end, compensating for the discarding.
363 return mLastProduced
365 + DelayRemaining();
366}

Member Data Documentation

◆ mCleared

bool AudioGraph::EffectStage::mCleared { false }
private

Definition at line 100 of file EffectStage.h.

◆ mDelayRemaining

sampleCount AudioGraph::EffectStage::mDelayRemaining
private

Definition at line 96 of file EffectStage.h.

◆ mInBuffers

Buffers& AudioGraph::EffectStage::mInBuffers
private
Invariant
mInBuffers.BlockSize() <= mInBuffers.Remaining()

Definition at line 90 of file EffectStage.h.

Referenced by EffectStage().

◆ mInstances

const std::vector<std::shared_ptr<EffectInstance> > AudioGraph::EffectStage::mInstances
private

Definition at line 91 of file EffectStage.h.

◆ mIsProcessor

const bool AudioGraph::EffectStage::mIsProcessor
private

Definition at line 94 of file EffectStage.h.

◆ mLastProduced

size_t AudioGraph::EffectStage::mLastProduced {}
private

Definition at line 97 of file EffectStage.h.

◆ mLastZeroes

size_t AudioGraph::EffectStage::mLastZeroes {}
private

Definition at line 98 of file EffectStage.h.

◆ mLatencyDone

bool AudioGraph::EffectStage::mLatencyDone { false }
private

Definition at line 99 of file EffectStage.h.

◆ mSampleRate

const double AudioGraph::EffectStage::mSampleRate
private

Definition at line 93 of file EffectStage.h.

◆ mSettings

EffectSettings& AudioGraph::EffectStage::mSettings
private

Definition at line 92 of file EffectStage.h.

◆ mUpstream

Source& AudioGraph::EffectStage::mUpstream
private

Definition at line 88 of file EffectStage.h.


The documentation for this class was generated from the following files: