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#include "EffectStage.h"
15#include "AudacityException.h"
16#include "AudioGraphBuffers.h"
17#include "WideSampleSequence.h"
18#include <cassert>
19
20namespace {
21std::vector<std::shared_ptr<EffectInstance>> MakeInstances(
24 const WideSampleSequence &sequence,
25 std::optional<sampleCount> genLength, int channel)
26{
27 std::vector<std::shared_ptr<EffectInstance>> instances;
28 // Make as many instances as needed for the channels of the track, which
29 // depends on how the instances report how many channels they accept
30 const auto nChannels = (channel < 0) ? sequence.NChannels() : 1;
31 for (size_t ii = 0; ii < nChannels;) {
32 auto pInstance = factory();
33 if (!pInstance)
34 // A constructor that can't satisfy its post should throw instead
35 throw std::exception{};
36 auto count = pInstance->GetAudioInCount();
38 MakeChannelMap(sequence, channel, map);
39 // Give the plugin a chance to initialize
40 if (!pInstance->ProcessInitialize(settings, sampleRate, map))
41 throw std::exception{};
42 instances.resize(ii);
43
44 // Beware generators with zero in count
45 if (genLength)
46 count = nChannels;
47
48 instances.push_back(move(pInstance));
49
50 // Advance ii
51 if (count == 0)
52 // What? Can't make progress
53 throw std::exception();
54 ii += count;
55 }
56 return instances;
57}
58}
59
61 Source &upstream, Buffers &inBuffers,
63 double sampleRate, std::optional<sampleCount> genLength,
64 const WideSampleSequence &sequence
65) : mUpstream{ upstream }, mInBuffers{ inBuffers }
66 , mInstances{ MakeInstances(factory, settings, sampleRate, sequence,
67 genLength, channel) }
68 , mSettings{ settings }, mSampleRate{ sampleRate }
69 , mIsProcessor{ !genLength.has_value() }
70 , mDelayRemaining{ genLength ? *genLength : sampleCount::max() }
71{
72 assert(upstream.AcceptsBlockSize(inBuffers.BlockSize()));
73 assert(this->AcceptsBlockSize(inBuffers.BlockSize()));
74
75 // Establish invariant
77}
78
79auto EffectStage::Create(int channel,
80 Source &upstream, Buffers &inBuffers,
82 double sampleRate, std::optional<sampleCount> genLength,
83 const WideSampleSequence &sequence
84) -> std::unique_ptr<EffectStage>
85{
86 try {
87 return std::make_unique<EffectStage>(CreateToken{}, channel,
88 upstream, inBuffers, factory, settings, sampleRate, genLength,
89 sequence);
90 }
91 catch (const std::exception &) {
92 return nullptr;
93 }
94}
95
97{
98 // Allow the instances to cleanup
99 for (auto &pInstance : mInstances)
100 if (pInstance)
101 pInstance->ProcessFinalize();
102}
103
104bool EffectStage::AcceptsBuffers(const Buffers &buffers) const
105{
106 return true;
107}
108
110{
111 // Test the equality of input and output block sizes
112 return mInBuffers.BlockSize() == size;
113}
114
115std::optional<size_t> EffectStage::Acquire(Buffers &data, size_t bound)
116{
117 assert(AcceptsBuffers(data));
118 assert(AcceptsBlockSize(data.BlockSize()));
119 // pre, needed for Process() and Discard()
120 assert(bound <= std::min(data.BlockSize(), data.Remaining()));
121
122 // For each input block of samples, we pass it to the effect along with a
123 // variable output location. This output location is simply a pointer into a
124 // much larger buffer. This reduces the number of calls required to add the
125 // samples to the output track.
126 //
127 // Upon return from the effect, the output samples are "moved to the left" by
128 // the number of samples in the current latency setting, effectively removing any
129 // delay introduced by the effect.
130 //
131 // At the same time the total number of delayed samples are gathered and when
132 // there is no further input data to process, the loop continues to call the
133 // effect with an empty input buffer until the effect has had a chance to
134 // return all of the remaining delayed samples.
135
136 // Invariant satisfies pre for mUpstream.Acquire() and for Process()
138
139 size_t curBlockSize = 0;
140
141 if (auto oCurBlockSize = FetchProcessAndAdvance(data, bound, false)
142 ; !oCurBlockSize
143 )
144 return {};
145 else {
146 curBlockSize = *oCurBlockSize;
147 if (mIsProcessor && !mLatencyDone) {
148 // Come here only in the first call to Acquire()
149 // Some effects (like ladspa/lv2 swh plug-ins) don't report latency
150 // until at least one block of samples is processed. Find latency
151 // once only for the track and assume it doesn't vary
152 auto delay = mDelayRemaining =
153 mInstances[0]->GetLatency(mSettings, mSampleRate);
154 for (size_t ii = 1, nn = mInstances.size(); ii < nn; ++ii)
155 if (mInstances[ii] &&
156 mInstances[ii]->GetLatency(mSettings, mSampleRate) != delay)
157 // This mismatch is unexpected. Fail
158 return {};
159 // Discard all the latency
160 while (delay > 0 && curBlockSize > 0) {
161 auto discard = limitSampleBufferSize(curBlockSize, delay);
162 data.Discard(discard, curBlockSize - discard);
163 delay -= discard;
164 curBlockSize -= discard;
165 if (curBlockSize == 0) {
166 if (!(oCurBlockSize = FetchProcessAndAdvance(data, bound, false)
167 ))
168 return {};
169 else
170 curBlockSize = *oCurBlockSize;
171 }
172 mLastProduced -= discard;
173 }
174 if (curBlockSize > 0) {
175 assert(delay == 0);
176 if (curBlockSize < bound) {
177 // Discarded all the latency, while upstream may still be
178 // producing. Try to fill the buffer up to the bound.
179 if (!(oCurBlockSize = FetchProcessAndAdvance(
180 data, bound - curBlockSize, false, curBlockSize)
181 ))
182 return {};
183 else
184 curBlockSize += *oCurBlockSize;
185 }
186 }
187 else while (delay > 0) {
188 assert(curBlockSize == 0);
189 // Finish one-time delay in case it exceeds entire upstream length
190 // Upstream must have been exhausted
191 assert(mUpstream.Remaining() == 0);
192 // Feed zeroes to the effect
193 auto zeroes = limitSampleBufferSize(data.BlockSize(), delay);
194 if (!(FetchProcessAndAdvance(data, zeroes, true)))
195 return {};
196 delay -= zeroes;
197 // Debit mDelayRemaining later in Release()
198 }
199 mLatencyDone = true;
200 }
201 }
202
203 if (mIsProcessor && curBlockSize < bound) {
204 // If there is still a short buffer by this point, upstream must have
205 // been exhausted
206 assert(mUpstream.Remaining() == 0);
207
208 // Continue feeding zeroes; this code block will produce as many zeroes
209 // at the end as were discarded at the beginning (over one or more visits)
210 auto zeroes =
211 limitSampleBufferSize(bound - curBlockSize, mDelayRemaining);
212 if (!FetchProcessAndAdvance(data, zeroes, true, curBlockSize))
213 return {};
214 // Debit mDelayRemaining later in Release()
215 }
216
217 auto result = mLastProduced + mLastZeroes;
218 // assert the post
219 assert(data.Remaining() > 0);
220 assert(result <= bound);
221 assert(result <= data.Remaining());
222 assert(result <= Remaining());
223 assert(bound == 0 || Remaining() == 0 || result > 0);
224 return { result };
225}
226
228 Buffers &data, size_t bound, bool doZeroes, size_t outBufferOffset)
229{
230 std::optional<size_t> oCurBlockSize;
231 // Generator always supplies zeroes in
232 doZeroes = doZeroes || !mIsProcessor;
233 if (!doZeroes)
234 oCurBlockSize = mUpstream.Acquire(mInBuffers, bound);
235 else {
236 if (!mCleared) {
237 // Need to do this the first time, only, that we begin to give zeroes
238 // to the processor
240 const auto blockSize = mInBuffers.BlockSize();
241 for (size_t ii = 0; ii < mInBuffers.Channels(); ++ii) {
242 auto p = &mInBuffers.GetWritePosition(ii);
243 std::fill(p, p + blockSize, 0);
244 }
245 mCleared = true;
246 }
247 oCurBlockSize = {
249 if (!mIsProcessor)
250 // Do this (ignoring result) so we can correctly Release() upstream
251 mUpstream.Acquire(mInBuffers, bound);
252 }
253 if (!oCurBlockSize)
254 return {};
255
256 const auto curBlockSize = *oCurBlockSize;
257 if (curBlockSize == 0)
258 assert(doZeroes || mUpstream.Remaining() == 0); // post of Acquire()
259 else {
260 // Called only in Acquire()
261 // invariant or post of mUpstream.Acquire() satisfies pre of Process()
262 // because curBlockSize <= bound <= mInBuffers.blockSize()
263 // == data.BlockSize()
264 // and mInBuffers.BlockSize() <= mInBuffers.Remaining() by invariant
265 // and data.BlockSize() <= data.Remaining() by pre of Acquire()
266 for (size_t ii = 0, nn = mInstances.size(); ii < nn; ++ii) {
267 auto &pInstance = mInstances[ii];
268 if (!pInstance)
269 continue;
270 if (!Process(*pInstance, ii, data, curBlockSize, outBufferOffset))
271 return {};
272 }
273
274 if (doZeroes) {
275 // Either a generator or doing the tail; will count down delay
277 if (!mIsProcessor) {
278 // This allows polling the progress meter for a generator
279 if (!mUpstream.Release())
280 return {};
281 }
282 }
283 else {
284 // Will count down the upstream
285 mLastProduced += curBlockSize;
286 if (!mUpstream.Release())
287 return {};
288 mInBuffers.Advance(curBlockSize);
290 // Maintain invariant minimum availability
292 }
293 }
294 return oCurBlockSize;
295}
296
298 size_t channel, const Buffers &data, size_t curBlockSize,
299 size_t outBufferOffset) const
300{
301 size_t processed{};
302 try {
303 const auto positions = mInBuffers.Positions();
304 const auto nPositions = mInBuffers.Channels();
305 // channel may be nonzero in the case of a plug-in that only reads
306 // one channel at a time, so multiple instances are made to mix stereo
307 assert(channel <= nPositions);
308 std::vector<float *> inPositions(
309 positions + channel, positions + nPositions - channel);
310 // When the plug-in expects many input channels, replicate the last
311 // buffer (assumed to be zero-filled) as dummy input
312 inPositions.resize(
313 instance.GetAudioInCount() - channel, inPositions.back());
314
315 std::vector<float *> advancedOutPositions;
316 const auto size = instance.GetAudioOutCount() - channel;
317 advancedOutPositions.reserve(size);
318
319 auto outPositions = data.Positions();
320 // It is assumed that data has at least one dummy buffer last
321 auto channels = data.Channels();
322 // channel may be nonzero in the case of a plug-in that only writes
323 // one channel at a time, so multiple instances are made to mix stereo
324 for (size_t ii = channel; ii < channels; ++ii)
325 advancedOutPositions.push_back(outPositions[ii] + outBufferOffset);
326 // When the plug-in expects many output channels, replicate the last
327 // as dummy output
328 advancedOutPositions.resize(size, advancedOutPositions.back());
329
330 processed = instance.ProcessBlock(mSettings,
331 inPositions.data(), advancedOutPositions.data(), curBlockSize);
332 }
333 catch (const AudacityException &) {
334 // PRL: Bug 437:
335 // Pass this along to our application-level handler
336 throw;
337 }
338 catch (...) {
339 // PRL:
340 // Exceptions for other reasons, maybe in third-party code...
341 // Continue treating them as we used to, but I wonder if these
342 // should now be treated the same way.
343 return false;
344 }
345
346 return (processed == curBlockSize);
347}
348
350{
351 // Not correct until at least one call to Acquire() so that mDelayRemaining
352 // is assigned.
353 // mLastProduced will have the up-front latency discarding deducted.
354 // mDelayRemaining later decreases to 0 as zeroes are supplied to the
355 // processor at the end, compensating for the discarding.
356 return mLastProduced
357 + (mIsProcessor ? mUpstream.Remaining() : 0)
358 + DelayRemaining();
359}
360
362{
363 // Progress toward termination (Remaining() == 0),
364 // if mLastProduced + mLastZeroes > 0,
365 // which is what Acquire() last returned
367 assert(mDelayRemaining >= 0);
369 return true;
370}
371
373 const WideSampleSequence &sequence, int channel, ChannelName map[3])
374{
375 const auto nChannels = sequence.NChannels();
376 assert(channel < static_cast<int>(nChannels)); // precondition
377
378 size_t index = 0;
379 if (nChannels == 1)
380 map[index++] = ChannelNameMono;
381 else {
382 // TODO: more-than-two-channels
383 if (channel < 1)
384 map[index++] = ChannelNameFrontLeft;
385 if (channel != 0)
386 map[index++] = ChannelNameFrontRight;
387 }
388 map[index] = ChannelNameEOL;
389 return index;
390}
Declare abstract class AudacityException, some often-used subclasses, and GuardedCall.
static RegisteredToolbarFactory factory
int min(int a, int b)
ChannelName
@ ChannelNameFrontLeft
@ ChannelNameEOL
@ ChannelNameMono
@ ChannelNameFrontRight
unsigned MakeChannelMap(const WideSampleSequence &sequence, int channel, ChannelName map[3])
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
static Settings & settings()
Definition: TrackInfo.cpp:69
Base class for exceptions specially processed by the application.
Accumulates (non-interleaved) data during effect processing.
size_t BlockSize() const
float & GetWritePosition(unsigned iChannel)
Get writable position for one channel.
void Advance(size_t count)
Move the positions.
size_t Remaining() const
void Rewind()
Reset positions to starts of buffers.
size_t Rotate()
Shift all data at and after the old position to position 0.
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.
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.
Source & mUpstream
Definition: EffectStage.h:89
bool AcceptsBlockSize(size_t size) const override
See postcondition of constructor.
const std::vector< std::shared_ptr< EffectInstance > > mInstances
Definition: EffectStage.h:92
bool Release() override
Caller is done examining last Acquire()d positions.
std::optional< size_t > Acquire(Buffers &data, size_t bound) override
Occupy vacant space in Buffers with some data.
const double mSampleRate
Definition: EffectStage.h:94
sampleCount mDelayRemaining
Definition: EffectStage.h:97
std::optional< size_t > FetchProcessAndAdvance(Buffers &data, size_t bound, bool doZeros, size_t outBufferOffset=0)
size_t mLastZeroes
Definition: EffectStage.h:99
~EffectStage() override
Finalizes the instance.
Definition: EffectStage.cpp:96
static std::unique_ptr< EffectStage > Create(int channel, Source &upstream, Buffers &inBuffers, const Factory &factory, EffectSettings &settings, double sampleRate, std::optional< sampleCount > genLength, const WideSampleSequence &sequence)
Satisfies postcondition of constructor or returns null.
Definition: EffectStage.cpp:79
sampleCount Remaining() const override
Result includes any amount Acquired and not yet Released.
EffectStage(CreateToken, int channel, Source &upstream, Buffers &inBuffers, const Factory &factory, EffectSettings &settings, double sampleRate, std::optional< sampleCount > genLength, const WideSampleSequence &sequence)
Don't call directly but use Create()
Definition: EffectStage.cpp:60
bool AcceptsBuffers(const Buffers &buffers) const override
bool Process(EffectInstance &instance, size_t channel, const Buffers &data, size_t curBlockSize, size_t outBufferOffset) const
Produce exactly curBlockSize samples in data
size_t mLastProduced
Definition: EffectStage.h:98
std::function< std::shared_ptr< EffectInstance >()> Factory
Definition: EffectStage.h:30
Buffers & mInBuffers
Definition: EffectStage.h:91
sampleCount DelayRemaining() const
Definition: EffectStage.h:73
const bool mIsProcessor
Definition: EffectStage.h:95
bool mLatencyDone
Definition: EffectStage.h:100
EffectSettings & mSettings
Definition: EffectStage.h:93
virtual size_t NChannels() const =0
A constant property.
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
std::vector< std::shared_ptr< EffectInstance > > MakeInstances(const EffectStage::Factory &factory, EffectSettings &settings, double sampleRate, const WideSampleSequence &sequence, std::optional< sampleCount > genLength, int channel)
Definition: EffectStage.cpp:21
Externalized state of a plug-in.