Audacity 3.2.0
PerTrackEffect.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 PerTrackEffect.cpp
6
7 Dominic Mazzoni
8 Vaughan Johnson
9 Martyn Shaw
10
11 Paul Licameli split from Effect.cpp
12
13*******************************************************************//*******************************************************************/
19
20
21#include "PerTrackEffect.h"
22
23#include "TimeWarper.h"
24#include "../SyncLock.h"
25#include "ViewInfo.h"
26#include "../WaveTrack.h"
27
29
31{
32 return mProcessor.Process(*this, settings);
33}
34
37{
38 return true;
39}
40
42{
43 return true;
44}
45
47 const EffectSettings &, double)
48{
49 return 0;
50}
51
53
55{
56 return true;
57}
58
60{
61 return false;
62}
63
65 EffectInstance &instance, EffectSettings &settings) const
66{
67 auto pThis = const_cast<PerTrackEffect *>(this);
68 pThis->CopyInputTracks(true);
69 bool bGoodResult = true;
70 // mPass = 1;
71 if (DoPass1()) {
72 auto &myInstance = dynamic_cast<Instance&>(instance);
73 bGoodResult = pThis->ProcessPass(myInstance, settings);
74 // mPass = 2;
75 if (bGoodResult && DoPass2())
76 bGoodResult = pThis->ProcessPass(myInstance, settings);
77 }
78 pThis->ReplaceProcessedTracks(bGoodResult);
79 return bGoodResult;
80}
81
83{
84 const auto duration = settings.extra.GetDuration();
85 bool bGoodResult = true;
86 bool isGenerator = GetType() == EffectTypeGenerate;
87
88 FloatBuffers inBuffer, outBuffer;
89 ArrayOf<float *> inBufPos, outBufPos;
90 ChannelName map[3];
91 size_t bufferSize = 0;
92 size_t blockSize = 0;
93 int count = 0;
94 bool clear = false;
95
96 // It's possible that the number of channels the effect expects changed based on
97 // the parameters (the Audacity Reverb effect does when the stereo width is 0).
98 const auto numAudioIn = GetAudioInCount();
99 const auto numAudioOut = GetAudioOutCount();
100 const bool multichannel = numAudioIn > 1;
101 auto range = multichannel
102 ? mOutputTracks->Leaders()
103 : mOutputTracks->Any();
104 range.VisitWhile( bGoodResult,
105 [&](WaveTrack *left, const Track::Fallthrough &fallthrough) {
106 if (!left->GetSelected())
107 return fallthrough();
108
109 sampleCount len = 0;
110 sampleCount start = 0;
111 unsigned numChannels = 0;
112 WaveTrack *right{};
113
114 // Iterate either over one track which could be any channel,
115 // or if multichannel, then over all channels of left,
116 // which is a leader.
117 for (auto channel :
118 TrackList::Channels(left).StartingWith(left)) {
119 if (channel->GetChannel() == Track::LeftChannel)
120 map[numChannels] = ChannelNameFrontLeft;
121 else if (channel->GetChannel() == Track::RightChannel)
122 map[numChannels] = ChannelNameFrontRight;
123 else
124 map[numChannels] = ChannelNameMono;
125 ++ numChannels;
126 map[numChannels] = ChannelNameEOL;
127 if (! multichannel)
128 break;
129 if (numChannels == 2) {
130 // TODO: more-than-two-channels
131 right = channel;
132 clear = false;
133 // Ignore other channels
134 break;
135 }
136 }
137
138 if (!isGenerator) {
139 GetBounds(*left, right, &start, &len);
140 mSampleCnt = len;
141 }
142 else
143 mSampleCnt = left->TimeToLongSamples(duration);
144
145 const auto sampleRate = left->GetRate();
146
147 // Get the block size the client wants to use
148 auto max = left->GetMaxBlockSize() * 2;
149 blockSize = instance.SetBlockSize(max);
150
151 // Calculate the buffer size to be at least the max rounded up to the clients
152 // selected block size.
153 const auto prevBufferSize = bufferSize;
154 bufferSize = ((max + (blockSize - 1)) / blockSize) * blockSize;
155
156 // If the buffer size has changed, then (re)allocate the buffers
157 if (prevBufferSize != bufferSize) {
158 // Always create the number of input buffers the client expects even if we don't have
159 // the same number of channels.
160 inBufPos.reinit( numAudioIn );
161 inBuffer.reinit( numAudioIn, bufferSize );
162
163 // We won't be using more than the first 2 buffers, so clear the rest (if any)
164 for (size_t i = 2; i < numAudioIn; i++)
165 for (size_t j = 0; j < bufferSize; j++)
166 inBuffer[i][j] = 0.0;
167
168 // Always create the number of output buffers the client expects even if we don't have
169 // the same number of channels.
170 outBufPos.reinit( numAudioOut );
171 // Output buffers get an extra blockSize worth to give extra room if
172 // the plugin adds latency
173 outBuffer.reinit( numAudioOut, bufferSize + blockSize );
174 }
175
176 // (Re)Set the input buffer positions
177 for (size_t i = 0; i < numAudioIn; i++)
178 inBufPos[i] = inBuffer[i].get();
179
180 // (Re)Set the output buffer positions
181 for (size_t i = 0; i < numAudioOut; i++)
182 outBufPos[i] = outBuffer[i].get();
183
184 // Clear unused input buffers
185 if (!right && !clear && numAudioIn > 1) {
186 for (size_t j = 0; j < bufferSize; j++)
187 inBuffer[1][j] = 0.0;
188 clear = true;
189 }
190
191 // Go process the track(s)
192 bGoodResult = ProcessTrack(instance, settings, sampleRate,
193 count, map, left, right, start, len,
194 inBuffer, outBuffer, inBufPos, outBufPos, bufferSize, blockSize,
195 numChannels);
196 if (!bGoodResult)
197 return;
198
199 count++;
200 },
201 [&](Track *t) {
203 t->SyncLockAdjust(mT1, mT0 + duration);
204 }
205 );
206
207 if (bGoodResult && GetType() == EffectTypeGenerate)
208 mT1 = mT0 + duration;
209
210 return bGoodResult;
211}
212
214 double sampleRate, int count, ChannelNames map,
215 WaveTrack *left, WaveTrack *right,
216 sampleCount start, sampleCount len,
217 FloatBuffers &inBuffer, FloatBuffers &outBuffer,
218 ArrayOf< float * > &inBufPos, ArrayOf< float *> &outBufPos,
219 size_t bufferSize, size_t blockSize,
220 unsigned numChannels) const
221{
222 bool rc = true;
223
224 // Give the plugin a chance to initialize
225 if (!instance.ProcessInitialize(settings, sampleRate, len, map))
226 return false;
227
228 { // Start scope for cleanup
229 auto cleanup = finally( [&] {
230 // Allow the plugin to cleanup
231 if (!instance.ProcessFinalize())
232 // In case of non-exceptional flow of control, set rc
233 rc = false;
234 } );
235
236 // For each input block of samples, we pass it to the effect along with a
237 // variable output location. This output location is simply a pointer into a
238 // much larger buffer. This reduces the number of calls required to add the
239 // samples to the output track.
240 //
241 // Upon return from the effect, the output samples are "moved to the left" by
242 // the number of samples in the current latency setting, effectively removing any
243 // delay introduced by the effect.
244 //
245 // At the same time the total number of delayed samples are gathered and when
246 // there is no further input data to process, the loop continues to call the
247 // effect with an empty input buffer until the effect has had a chance to
248 // return all of the remaining delayed samples.
249 auto inPos = start;
250 auto outPos = start;
251 auto inputRemaining = len;
252 decltype(instance.GetLatency(settings, sampleRate))
253 curDelay = 0, delayRemaining = 0;
254 decltype(blockSize) curBlockSize = 0;
255 decltype(bufferSize) inputBufferCnt = 0;
256 decltype(bufferSize) outputBufferCnt = 0;
257 bool cleared = false;
258 auto chans = std::min<unsigned>(GetAudioOutCount(), numChannels);
259 std::shared_ptr<WaveTrack> genLeft, genRight;
260 decltype(len) genLength = 0;
261 bool isGenerator = GetType() == EffectTypeGenerate;
262 bool isProcessor = GetType() == EffectTypeProcess;
263 double genDur = 0;
264 if (isGenerator) {
265 const auto duration = settings.extra.GetDuration();
266 if (IsPreviewing()) {
267 gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &genDur, 6.0);
268 genDur = std::min(duration, CalcPreviewInputLength(settings, genDur));
269 }
270 else
271 genDur = duration;
272 genLength = sampleCount((left->GetRate() * genDur) + 0.5); // round to nearest sample
273 delayRemaining = genLength;
274 cleared = true;
275
276 // Create temporary tracks
277 genLeft = left->EmptyCopy();
278
279 if (right)
280 genRight = right->EmptyCopy();
281 }
282
283 // Call the effect until we run out of input or delayed samples
284 while (inputRemaining != 0 || delayRemaining != 0) {
285 // Still working on the input samples
286 if (inputRemaining != 0) {
287 // Need to refill the input buffers
288 if (inputBufferCnt == 0) {
289 // Calculate the number of samples to get
290 inputBufferCnt =
291 limitSampleBufferSize( bufferSize, inputRemaining );
292
293 // Fill the input buffers
294 left->GetFloats(inBuffer[0].get(), inPos, inputBufferCnt);
295 if (right)
296 right->GetFloats(inBuffer[1].get(), inPos, inputBufferCnt);
297
298 // Reset the input buffer positions
299 for (size_t i = 0; i < numChannels; i++)
300 inBufPos[i] = inBuffer[i].get();
301 }
302
303 // Calculate the number of samples to process
304 curBlockSize = blockSize;
305 if (curBlockSize > inputRemaining) {
306 // We've reached the last block...set current block size to what's left
307 // inputRemaining is positive and bounded by a size_t
308 curBlockSize = inputRemaining.as_size_t();
309 inputRemaining = 0;
310
311 // Clear the remainder of the buffers so that a full block can be passed
312 // to the effect
313 auto cnt = blockSize - curBlockSize;
314 for (size_t i = 0; i < numChannels; i++)
315 for (decltype(cnt) j = 0 ; j < cnt; j++)
316 inBufPos[i][j + curBlockSize] = 0.0;
317
318 // Might be able to use up some of the delayed samples
319 if (delayRemaining != 0) {
320 // Don't use more than needed
321 cnt = limitSampleBufferSize(cnt, delayRemaining);
322 delayRemaining -= cnt;
323 curBlockSize += cnt;
324 }
325 }
326 }
327 // We've exhausted the input samples and are now working on the delay
328 else if (delayRemaining != 0) {
329 // Calculate the number of samples to process
330 curBlockSize = limitSampleBufferSize( blockSize, delayRemaining );
331 delayRemaining -= curBlockSize;
332
333 // From this point on, we only want to feed zeros to the plugin
334 if (!cleared) {
335 // Reset the input buffer positions
336 for (size_t i = 0; i < numChannels; i++) {
337 inBufPos[i] = inBuffer[i].get();
338 // And clear
339 for (size_t j = 0; j < blockSize; j++)
340 inBuffer[i][j] = 0.0;
341 }
342 cleared = true;
343 }
344 }
345
346 // Finally call the plugin to process the block
347 decltype(curBlockSize) processed;
348 try {
349 processed = instance.ProcessBlock(
350 settings, inBufPos.get(), outBufPos.get(), curBlockSize);
351 }
352 catch( const AudacityException & WXUNUSED(e) ) {
353 // PRL: Bug 437:
354 // Pass this along to our application-level handler
355 throw;
356 }
357 catch(...) {
358 // PRL:
359 // Exceptions for other reasons, maybe in third-party code...
360 // Continue treating them as we used to, but I wonder if these
361 // should now be treated the same way.
362 return false;
363 }
364 if (processed != curBlockSize)
365 return false;
366 wxUnusedVar(processed);
367
368 // Bump to next input buffer position
369 if (inputRemaining != 0) {
370 for (size_t i = 0; i < numChannels; i++)
371 inBufPos[i] += curBlockSize;
372 inputRemaining -= curBlockSize;
373 inputBufferCnt -= curBlockSize;
374 }
375
376 // "ls" and "rs" serve as the input sample index for the left and
377 // right channels when processing the input samples. If we flip
378 // over to processing delayed samples, they simply become counters
379 // for the progress display.
380 inPos += curBlockSize;
381
382 // Get the current number of delayed samples and accumulate
383 if (isProcessor) {
384 {
385 auto delay = instance.GetLatency(settings, sampleRate);
386 curDelay += delay;
387 delayRemaining += delay;
388 }
389
390 // If the plugin has delayed the output by more samples than our current
391 // block size, then we leave the output pointers alone. This effectively
392 // removes those delayed samples from the output buffer.
393 if (curDelay >= curBlockSize) {
394 curDelay -= curBlockSize;
395 curBlockSize = 0;
396 }
397 // We have some delayed samples, at the beginning of the output samples,
398 // so overlay them by shifting the remaining output samples.
399 else if (curDelay > 0) {
400 // curDelay is bounded by curBlockSize:
401 auto delay = curDelay.as_size_t();
402 curBlockSize -= delay;
403 for (size_t i = 0; i < chans; i++)
404 memmove(outBufPos[i], outBufPos[i] + delay, sizeof(float) * curBlockSize);
405 curDelay = 0;
406 }
407 }
408
409 // Adjust the number of samples in the output buffers
410 outputBufferCnt += curBlockSize;
411
412 // Still have room in the output buffers
413 if (outputBufferCnt < bufferSize) {
414 // Bump to next output buffer position
415 for (size_t i = 0; i < chans; i++)
416 outBufPos[i] += curBlockSize;
417 }
418 // Output buffers have filled
419 else {
420 if (isProcessor) {
421 // Write them out
422 left->Set((samplePtr) outBuffer[0].get(), floatSample, outPos, outputBufferCnt);
423 if (right) {
424 if (chans >= 2)
425 right->Set((samplePtr) outBuffer[1].get(), floatSample, outPos, outputBufferCnt);
426 else
427 right->Set((samplePtr) outBuffer[0].get(), floatSample, outPos, outputBufferCnt);
428 }
429 }
430 else if (isGenerator) {
431 genLeft->Append((samplePtr) outBuffer[0].get(), floatSample, outputBufferCnt);
432 if (genRight)
433 genRight->Append((samplePtr) outBuffer[1].get(), floatSample, outputBufferCnt);
434 }
435
436 // Reset the output buffer positions
437 for (size_t i = 0; i < chans; i++)
438 outBufPos[i] = outBuffer[i].get();
439
440 // Bump to the next track position
441 outPos += outputBufferCnt;
442 outputBufferCnt = 0;
443 }
444
445 if (numChannels > 1) {
446 if (TrackGroupProgress(count,
447 (inPos - start).as_double() /
448 (isGenerator ? genLength : len).as_double())) {
449 rc = false;
450 break;
451 }
452 }
453 else {
454 if (TrackProgress(count,
455 (inPos - start).as_double() /
456 (isGenerator ? genLength : len).as_double())) {
457 rc = false;
458 break;
459 }
460 }
461 }
462
463 // Put any remaining output
464 if (rc && outputBufferCnt) {
465 if (isProcessor) {
466 left->Set((samplePtr) outBuffer[0].get(), floatSample, outPos, outputBufferCnt);
467 if (right) {
468 if (chans >= 2)
469 right->Set((samplePtr) outBuffer[1].get(), floatSample, outPos, outputBufferCnt);
470 else
471 right->Set((samplePtr) outBuffer[0].get(), floatSample, outPos, outputBufferCnt);
472 }
473 }
474 else if (isGenerator) {
475 genLeft->Append((samplePtr) outBuffer[0].get(), floatSample, outputBufferCnt);
476 if (genRight)
477 genRight->Append((samplePtr) outBuffer[1].get(), floatSample, outputBufferCnt);
478 }
479 }
480
481 if (rc && isGenerator) {
482 auto pProject = FindProject();
483
484 // Transfer the data from the temporary tracks to the actual ones
485 genLeft->Flush();
486 // mT1 gives us the NEW selection. We want to replace up to GetSel1().
487 auto &selectedRegion = ViewInfo::Get( *pProject ).selectedRegion;
488 auto t1 = selectedRegion.t1();
489 PasteTimeWarper warper{ t1, mT0 + genLeft->GetEndTime() };
490 left->ClearAndPaste(mT0, t1, genLeft.get(), true, true,
491 &warper);
492
493 if (genRight) {
494 genRight->Flush();
495 right->ClearAndPaste(mT0, selectedRegion.t1(),
496 genRight.get(), true, true, nullptr /* &warper */);
497 }
498 }
499
500 } // End scope for cleanup
501 return rc;
502}
int min(int a, int b)
@ EffectTypeGenerate
@ EffectTypeProcess
enum ChannelName * ChannelNames
ChannelName
@ ChannelNameFrontLeft
@ ChannelNameEOL
@ ChannelNameMono
@ ChannelNameFrontRight
FileConfig * gPrefs
Definition: Prefs.cpp:71
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:23
@ floatSample
Definition: SampleFormat.h:34
char * samplePtr
Definition: SampleFormat.h:49
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
static Settings & settings()
Definition: TrackInfo.cpp:87
Memory.h template class for making an array of float, bool, etc.
Definition: MemoryX.h:27
void reinit(Integral count, bool initialize=false)
Definition: MemoryX.h:57
void reinit(Integral count)
Definition: MemoryX.h:109
Base class for exceptions specially processed by the application.
double mT1
Definition: EffectBase.h:107
std::shared_ptr< TrackList > mOutputTracks
Definition: EffectBase.h:105
bool IsPreviewing() const
Definition: EffectBase.h:83
double mT0
Definition: EffectBase.h:106
const AudacityProject * FindProject() const
Definition: EffectBase.cpp:315
bool TrackGroupProgress(int whichGroup, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:700
unsigned GetAudioInCount() const override
How many input buffers to allocate at once.
Definition: Effect.cpp:137
void CopyInputTracks(bool allSyncLockSelected=false)
Definition: Effect.cpp:739
double CalcPreviewInputLength(const EffectSettings &settings, double previewLength) const override
Default implementation returns previewLength
Definition: Effect.cpp:863
bool TrackProgress(int whichTrack, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:691
EffectType GetType() const override
Type determines how it behaves.
Definition: Effect.cpp:96
unsigned GetAudioOutCount() const override
How many output buffers to allocate at once.
Definition: Effect.cpp:142
void GetBounds(const WaveTrack &track, const WaveTrack *pRight, sampleCount *start, sampleCount *len)
Definition: Effect.cpp:709
Performs effect computation.
virtual size_t SetBlockSize(size_t maxBlockSize)=0
double t1() const
Definition: ViewInfo.h:35
Unit slope but with either a jump (pasting more) or a flat interval (pasting less)
Definition: TimeWarper.h:181
virtual size_t ProcessBlock(EffectSettings &settings, const float *const *inBlock, float *const *outBlock, size_t blockLen)=0
Called for destructive effect computation.
virtual sampleCount GetLatency(const EffectSettings &settings, double sampleRate)
virtual bool ProcessFinalize()
virtual bool ProcessInitialize(EffectSettings &settings, double sampleRate, sampleCount totalLen, ChannelNames chanMap)
bool Process(EffectSettings &settings) final
Uses the other virtual functions of this class.
const PerTrackEffect & mProcessor
Base class for many of the effects in Audacity.
bool DoPass2() const
bool ProcessPass(Instance &instance, EffectSettings &settings)
bool ProcessTrack(Instance &instance, EffectSettings &settings, double sampleRate, int count, ChannelNames map, WaveTrack *left, WaveTrack *right, sampleCount start, sampleCount len, FloatBuffers &inBuffer, FloatBuffers &outBuffer, ArrayOf< float * > &inBufPos, ArrayOf< float * > &outBufPos, size_t bufferSize, size_t blockSize, unsigned mNumChannels) const
bool Process(EffectInstance &instance, EffectSettings &settings) const
~PerTrackEffect() override
bool DoPass1() const
sampleCount mSampleCnt
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
Retrieve samples from a track in floating-point format, regardless of the storage format.
Definition: SampleTrack.h:65
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: SampleTrack.cpp:35
static bool IsSyncLockSelected(const Track *pTrack)
Definition: SyncLock.cpp:43
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
bool GetSelected() const
Definition: Track.h:461
@ LeftChannel
Definition: Track.h:275
@ RightChannel
Definition: Track.h:276
Continuation<> Fallthrough
Type of arguments passed as optional second parameter to TypeSwitch<void>() cases.
Definition: Track.h:533
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1533
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:216
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
size_t GetMaxBlockSize() const override
This returns a nonnegative number of samples meant to size a memory buffer.
Definition: WaveTrack.cpp:1808
void Set(constSamplePtr buffer, sampleFormat format, sampleCount start, size_t len)
Definition: WaveTrack.cpp:2197
double GetRate() const override
Definition: WaveTrack.cpp:481
void ClearAndPaste(double t0, double t1, const Track *src, bool preserve=true, bool merge=true, const TimeWarper *effectWarper=NULL)
Definition: WaveTrack.cpp:917
Holder EmptyCopy(const SampleBlockFactoryPtr &pFactory={}, bool keepLink=true) const
Definition: WaveTrack.cpp:707
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:18
size_t as_size_t() const
Definition: SampleCount.cpp:17
Externalized state of a plug-in.