Audacity 3.2.0
EffectStage.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file EffectStage.cpp
6
7 Dominic Mazzoni
8 Vaughan Johnson
9 Martyn Shaw
10
11 Paul Licameli split from PerTrackEffect.cpp
12
13**********************************************************************/
14
15
16#include "EffectStage.h"
17#include "AudacityException.h"
18#include "AudioGraphBuffers.h"
19#include "Track.h"
20#include <cassert>
21
22namespace {
23std::vector<std::shared_ptr<EffectInstance>> MakeInstances(
25 EffectSettings &settings, double sampleRate, const Track &track
26 , std::optional<sampleCount> genLength, bool multi)
27{
28 std::vector<std::shared_ptr<EffectInstance>> instances;
29 // Make as many instances as needed for the channels of the track, which
30 // depends on how the instances report how many channels they accept
31 const auto range = multi
32 ? TrackList::Channels(&track)
33 : TrackList::Channels(&track).StartingWith(&track).EndingAfter(&track);
34 const auto nChannels = range.size();
35 size_t ii = 0;
36 for (auto iter = range.begin(); iter != range.end();) {
37 auto channel = *iter;
38 auto pInstance = factory();
39 if (!pInstance)
40 // A constructor that can't satisfy its post should throw instead
41 throw std::exception{};
42 auto count = pInstance->GetAudioInCount();
44 AudioGraph::MakeChannelMap(*channel, count > 1, map);
45 // Give the plugin a chance to initialize
46 if (!pInstance->ProcessInitialize(settings, sampleRate, map))
47 throw std::exception{};
48 instances.resize(ii);
49
50 // Beware generators with zero in count
51 if (genLength)
52 count = nChannels;
53
54 instances.push_back(move(pInstance));
55
56 // Advance ii and iter
57 if (count == 0)
58 // What? Can't make progress
59 throw std::exception();
60 ii += count;
61 if (ii >= nChannels)
62 break;
63 std::advance(iter, count);
64 }
65 return instances;
66}
67}
68
70 Source &upstream, Buffers &inBuffers,
72 double sampleRate, std::optional<sampleCount> genLength, const Track &track
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}
86
88 Source &upstream, Buffers &inBuffers,
90 double sampleRate, std::optional<sampleCount> genLength, const Track &track
91) -> std::unique_ptr<EffectStage>
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}
101
103{
104 // Allow the instances to cleanup
105 for (auto &pInstance : mInstances)
106 if (pInstance)
107 pInstance->ProcessFinalize();
108}
109
111{
112 return true;
113}
114
116{
117 // Test the equality of input and output block sizes
118 return mInBuffers.BlockSize() == size;
119}
120
121std::optional<size_t>
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()
144 assert(mInBuffers.BlockSize() <= mInBuffers.Remaining());
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}
233
235 Buffers &data, size_t bound, bool doZeroes, size_t outBufferOffset)
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
246 mInBuffers.Rewind();
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 = {
255 mIsProcessor ? bound : limitSampleBufferSize(bound, mDelayRemaining) };
256 if (!mIsProcessor)
257 // Do this (ignoring result) so we can correctly Release() upstream
258 mUpstream.Acquire(mInBuffers, bound);
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
283 mLastZeroes = limitSampleBufferSize(curBlockSize, DelayRemaining());
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);
296 if (mInBuffers.Remaining() < mInBuffers.BlockSize())
297 // Maintain invariant minimum availability
298 mInBuffers.Rotate();
299 }
300 }
301 return oCurBlockSize;
302}
303
305 size_t channel, const Buffers &data, size_t curBlockSize,
306 size_t outBufferOffset) const
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}
355
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
364 + (mIsProcessor ? mUpstream.Remaining() : 0)
365 + DelayRemaining();
366}
367
369{
370 // Progress toward termination (Remaining() == 0),
371 // if mLastProduced + mLastZeroes > 0,
372 // which is what Acquire() last returned
373 mDelayRemaining -= mLastZeroes;
374 assert(mDelayRemaining >= 0);
375 mLastProduced = mLastZeroes = 0;
376 return true;
377}
378
380 const Track &track, bool multichannel, ChannelName map[3])
381{
382 // Iterate either over one track which could be any channel,
383 // or if multichannel, then over all channels of track,
384 // which is a leader.
385 unsigned numChannels = 0;
386 for (auto channel : TrackList::Channels(&track).StartingWith(&track)) {
387 if (channel->GetChannel() == Track::LeftChannel)
388 map[numChannels] = ChannelNameFrontLeft;
389 else if (channel->GetChannel() == Track::RightChannel)
390 map[numChannels] = ChannelNameFrontRight;
391 else
392 map[numChannels] = ChannelNameMono;
393 ++ numChannels;
394 map[numChannels] = ChannelNameEOL;
395 if (! multichannel)
396 break;
397 if (numChannels == 2) {
398 // TODO: more-than-two-channels
399 // Ignore other channels
400 break;
401 }
402 }
403 return numChannels;
404}
Declare abstract class AudacityException, some often-used subclasses, and GuardedCall.
int min(int a, int b)
ChannelName
@ ChannelNameFrontLeft
@ ChannelNameEOL
@ ChannelNameMono
@ ChannelNameFrontRight
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
declares abstract base class Track, TrackList, and iterators over TrackList
static Settings & settings()
Definition: TrackInfo.cpp:83
Base class for exceptions specially processed by the application.
Accumulates (non-interleaved) data during effect processing.
size_t BlockSize() const
size_t Remaining() const
void Rewind()
Reset positions to starts of buffers.
unsigned Channels() const
void Discard(size_t drop, size_t keep)
Discard some data at the (unchanging) positions.
float *const * Positions() const
Get array of positions in the buffers.
~EffectStage() override
Finalizes the instance.
bool Process(EffectInstance &instance, size_t channel, const Buffers &data, size_t curBlockSize, size_t outBufferOffset) const
Produce exactly curBlockSize samples in data
bool Release() override
Caller is done examining last Acquire()d positions.
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()
Definition: EffectStage.cpp:69
bool AcceptsBlockSize(size_t size) const override
See postcondition of constructor.
std::function< std::shared_ptr< EffectInstance >()> Factory
Definition: EffectStage.h:32
static std::unique_ptr< EffectStage > Create(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.
Definition: EffectStage.cpp:87
std::optional< size_t > Acquire(Buffers &data, size_t bound) override
Occupy vacant space in Buffers with some data.
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)
Upstream producer of sample streams, taking Buffers as external context.
virtual bool AcceptsBlockSize(size_t blockSize) const =0
Performs effect computation.
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.
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:162
@ LeftChannel
Definition: Track.h:217
@ RightChannel
Definition: Track.h:218
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1417
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
AUDIO_GRAPH_API unsigned MakeChannelMap(const Track &track, bool multichannel, ChannelName map[3])
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
Externalized state of a plug-in.