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