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#include "EffectOutputTracks.h"
23
24#include "AudioGraphBuffers.h"
25#include "AudioGraphTask.h"
26#include "EffectStage.h"
27#include "SyncLock.h"
28#include "TimeWarper.h"
29#include "ViewInfo.h"
30#include "WaveTrack.h"
31#include "WaveTrackSink.h"
32#include "WideSampleSource.h"
33
35
37{
38 return mProcessor.Process(*this, settings);
39}
40
42 double, ChannelNames)
43{
44 return true;
45}
46
48{
49 return true;
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
69 // Destroy any pre-formed output tracks when done
70 auto pOutputs = mpOutputTracks.get();
71
72 std::optional<EffectOutputTracks> outputs;
73 if (!pOutputs)
74 pOutputs = &outputs.emplace(*mTracks, GetType(),
76
77 bool bGoodResult = true;
78 // mPass = 1;
79 if (DoPass1()) {
80 auto &myInstance = dynamic_cast<Instance&>(instance);
81 bGoodResult = pThis->ProcessPass(pOutputs->Get(), myInstance, settings);
82 // mPass = 2;
83 if (bGoodResult && DoPass2())
84 bGoodResult = pThis->ProcessPass(pOutputs->Get(), myInstance, settings);
85 }
86 if (bGoodResult)
87 pOutputs->Commit();
89 return bGoodResult;
90}
91
94{
95 const auto duration = settings.extra.GetDuration();
96 bool bGoodResult = true;
97 bool isGenerator = GetType() == EffectTypeGenerate;
98 bool isProcessor = GetType() == EffectTypeProcess;
99
100 Buffers inBuffers, outBuffers;
101 ChannelName map[3];
102 size_t prevBufferSize = 0;
103 int count = 0;
104 bool clear = false;
105
106 // It's possible that the number of channels the effect expects changed based on
107 // the parameters (the Audacity Reverb effect does when the stereo width is 0).
108 const auto numAudioIn = instance.GetAudioInCount();
109 const auto numAudioOut = instance.GetAudioOutCount();
110 if (numAudioOut < 1)
111 return false;
112
113 // Instances that can be reused in each loop pass
114 std::vector<std::shared_ptr<EffectInstance>> recycledInstances{
115 // First one is the given one; any others pushed onto here are
116 // discarded when we exit
117 std::dynamic_pointer_cast<EffectInstanceEx>(instance.shared_from_this())
118 };
119
120 const bool multichannel = numAudioIn > 1;
121 int iChannel = 0;
122 TrackListHolder results;
123 const auto waveTrackVisitor =
124 [&](WaveTrack &wt, WaveChannel &chan, bool isFirst) {
125 if (isFirst)
126 iChannel = 0;
127
128 sampleCount len = 0;
129 sampleCount start = 0;
130 WaveChannel *pRight{};
131
132 const int channel = (multichannel ? -1 : iChannel++);
133 const auto numChannels = MakeChannelMap(wt, channel, map);
134 if (multichannel) {
135 assert(numAudioIn > 1);
136 if (numChannels == 2) {
137 // TODO: more-than-two-channels
138 pRight = (*wt.Channels().rbegin()).get();
139 clear = false;
140 }
141 }
142
143 if (!isGenerator) {
144 GetBounds(wt, &start, &len);
145 mSampleCnt = len;
146 if (len > 0 && numAudioIn < 1) {
147 bGoodResult = false;
148 return;
149 }
150 }
151 else
152 mSampleCnt = wt.TimeToLongSamples(duration);
153
154 const auto sampleRate = wt.GetRate();
155
156 // Get the block size the client wants to use
157 auto max = wt.GetMaxBlockSize() * 2;
158 const auto blockSize = instance.SetBlockSize(max);
159 if (blockSize == 0) {
160 bGoodResult = false;
161 return;
162 }
163
164 // Calculate the buffer size to be at least the max rounded up to the clients
165 // selected block size.
166 const auto bufferSize =
167 ((max + (blockSize - 1)) / blockSize) * blockSize;
168 if (bufferSize == 0) {
169 bGoodResult = false;
170 return;
171 }
172
173 // Always create the number of input buffers the client expects even
174 // if we don't have
175 // the same number of channels.
176 // (These resizes may do nothing after the first track)
177
178 if (len > 0)
179 assert(numAudioIn > 0); // checked above
180 inBuffers.Reinit(
181 // TODO fix this hack for making Generator progress work without
182 // assertion violations. Make a dummy Source class that doesn't
183 // care about the buffers.
184 std::max(1u, numAudioIn),
185 blockSize,
186 std::max<size_t>(1, bufferSize / blockSize));
187 if (len > 0)
188 // post of Reinit later satisfies pre of Source::Acquire()
189 assert(inBuffers.Channels() > 0);
190
191 if (prevBufferSize != bufferSize) {
192 // Buffer size has changed
193 // We won't be using more than the first 2 buffers,
194 // so clear the rest (if any)
195 for (size_t i = 2; i < numAudioIn; i++)
196 inBuffers.ClearBuffer(i, bufferSize);
197 }
198 prevBufferSize = bufferSize;
199
200 // Always create the number of output buffers the client expects
201 // even if we don't have the same number of channels.
202 // (These resizes may do nothing after the first track)
203 // Output buffers get an extra blockSize worth to give extra room if
204 // the plugin adds latency -- PRL: actually not important to do
205 assert(numAudioOut > 0); // checked above
206 outBuffers.Reinit(numAudioOut, blockSize,
207 (bufferSize / blockSize) + 1);
208 // post of Reinit satisfies pre of ProcessTrack
209 assert(outBuffers.Channels() > 0);
210
211 // (Re)Set the input buffer positions
212 inBuffers.Rewind();
213
214 // Clear unused input buffers
215 if (!pRight && !clear && numAudioIn > 1) {
216 inBuffers.ClearBuffer(1, bufferSize);
217 clear = true;
218 }
219
220 const auto genLength = [this, &settings, &wt, isGenerator](
221 ) -> std::optional<sampleCount> {
222 double genDur = 0;
223 if (isGenerator) {
224 const auto duration = settings.extra.GetDuration();
225 if (IsPreviewing()) {
226 gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &genDur, 6.0);
227 genDur = std::min(duration, CalcPreviewInputLength(settings, genDur));
228 }
229 else
230 genDur = duration;
231 // round to nearest sample
232 return sampleCount{ (wt.GetRate() * genDur) + 0.5 };
233 }
234 else
235 return {};
236 }();
237
238 const auto pollUser = [this, numChannels, count, start,
239 length = (genLength ? *genLength : len).as_double()
240 ](sampleCount inPos){
241 if (numChannels > 1) {
243 count, (inPos - start).as_double() / length)
244 )
245 return false;
246 }
247 else {
248 if (TrackProgress(count, (inPos - start).as_double() / length))
249 return false;
250 }
251 return true;
252 };
253
254 // Assured above
255 assert(len == 0 || inBuffers.Channels() > 0);
256 // TODO fix this hack to make the time remaining of the generator
257 // progress dialog correct
258 if (len == 0 && genLength)
259 len = *genLength;
260 WideSampleSource source{
261 chan, size_t(pRight ? 2 : 1), start, len, pollUser };
262 // Assert source is safe to Acquire inBuffers
263 assert(source.AcceptsBuffers(inBuffers));
264 assert(source.AcceptsBlockSize(inBuffers.BlockSize()));
265
266 // Make "wide" or "narrow" copy of the track if generating
267 // Old generator code may still proceed "interval-major" and later
268 // join mono into stereo
269 auto wideTrack =
270 (pRight && isGenerator) ? wt.EmptyCopy() : nullptr;
271 auto narrowTrack =
272 (!pRight && isGenerator) ? wt.EmptyCopy(1) : nullptr;
273 const auto pGenerated = wideTrack
274 ? wideTrack
275 : narrowTrack;
276
277 WaveTrackSink sink{ chan, pRight, pGenerated.get(), start, isProcessor,
279 };
280 assert(sink.AcceptsBuffers(outBuffers));
281
282 // Go process the track(s)
283 const auto factory =
284 [this, &recycledInstances, counter = 0]() mutable {
285 auto index = counter++;
286 if (index < recycledInstances.size())
287 return recycledInstances[index];
288 else
289 return recycledInstances.emplace_back(MakeInstance());
290 };
291 bGoodResult = ProcessTrack(channel, factory, settings, source, sink,
292 genLength, sampleRate, wt, inBuffers, outBuffers);
293 if (bGoodResult) {
294 sink.Flush(outBuffers);
295 bGoodResult = sink.IsOk();
296 if (bGoodResult && pGenerated) {
297 if (!results)
298 results = TrackList::Temporary(nullptr, pGenerated);
299 else {
300 results->Add(pGenerated);
301 if (!multichannel && !isFirst && narrowTrack) {
302 // Generated a stereo track, in channel-major fashion.
303 // Get the last track but one -- generated in the previous
304 // pass
305 const auto pLast =
306 static_cast<WaveTrack*>(*std::next(results->rbegin()));
307 pLast->ZipClips();
308 }
309 }
310 }
311 }
312 if (!bGoodResult)
313 return;
314 ++count;
315 };
316 const auto defaultTrackVisitor =
317 [&](Track &t) {
319 t.SyncLockAdjust(mT1, mT0 + duration);
320 };
321
322 outputs.Any().VisitWhile(bGoodResult,
323 [&](auto &&fallthrough){ return [&](WaveTrack &wt) {
324 if (!wt.GetSelected())
325 return fallthrough();
326 const auto channels = wt.Channels();
327 if (multichannel)
328 waveTrackVisitor(wt, **channels.begin(), true);
329 else {
330 bool first = true;
331 for (const auto pChannel : channels) {
332 waveTrackVisitor(wt, *pChannel, first);
333 first = false;
334 }
335 }
336 if (results) {
337 const auto t1 = ViewInfo::Get(*FindProject()).selectedRegion.t1();
338 PasteTimeWarper warper { t1,
339 mT0 + (*results->begin())->GetEndTime() };
340 wt.ClearAndPaste(mT0, t1,
341 static_cast<WaveTrack&>(*results->DetachFirst()),
342 true, true, &warper);
343 results.reset();
344 }
345 }; },
346 defaultTrackVisitor
347 );
348
349 if (bGoodResult && GetType() == EffectTypeGenerate)
350 mT1 = mT0 + duration;
351
352 return bGoodResult;
353}
354
357 AudioGraph::Source &upstream, AudioGraph::Sink &sink,
358 std::optional<sampleCount> genLength,
359 const double sampleRate, const SampleTrack &wt,
360 Buffers &inBuffers, Buffers &outBuffers)
361{
362 assert(upstream.AcceptsBuffers(inBuffers));
363 assert(sink.AcceptsBuffers(outBuffers));
364
365 const auto blockSize = inBuffers.BlockSize();
366 assert(upstream.AcceptsBlockSize(blockSize));
367 assert(blockSize == outBuffers.BlockSize());
368
369 auto pSource = EffectStage::Create(channel, upstream, inBuffers,
370 factory, settings, sampleRate, genLength, wt);
371 if (!pSource)
372 return false;
373 assert(pSource->AcceptsBlockSize(blockSize)); // post of ctor
374 assert(pSource->AcceptsBuffers(outBuffers));
375
376 AudioGraph::Task task{ *pSource, outBuffers, sink };
377 return task.RunLoop();
378}
379
380std::shared_ptr<EffectOutputTracks> PerTrackEffect::MakeOutputTracks()
381{
382 return mpOutputTracks =
383 std::make_shared<EffectOutputTracks>(*mTracks, GetType(),
385}
386
388{
389 mpOutputTracks.reset();
390}
wxT("CloseDown"))
static RegisteredToolbarFactory factory
int min(int a, int b)
@ EffectTypeGenerate
@ EffectTypeProcess
ChannelName
unsigned MakeChannelMap(const WideSampleSequence &sequence, int channel, ChannelName map[3])
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
@ narrowestSampleFormat
Two synonyms for previous values that might change if more values were added.
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
std::shared_ptr< TrackList > TrackListHolder
Definition: Track.h:42
static Settings & settings()
Definition: TrackInfo.cpp:69
Accumulates (non-interleaved) data during effect processing.
size_t BlockSize() const
void Rewind()
Reset positions to starts of buffers.
void Reinit(unsigned nChannels, size_t blockSize, size_t nBlocks, size_t padding=0)
unsigned Channels() const
void ClearBuffer(unsigned iChannel, size_t n)
Downstream receiver of sample streams, taking Buffers as external context.
virtual bool AcceptsBuffers(const Buffers &buffers) const =0
Upstream producer of sample streams, taking Buffers as external context.
virtual bool AcceptsBlockSize(size_t blockSize) const =0
virtual bool AcceptsBuffers(const Buffers &buffers) const =0
double mT1
Definition: EffectBase.h:114
bool IsPreviewing() const
Definition: EffectBase.h:89
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:107
double mT0
Definition: EffectBase.h:113
const AudacityProject * FindProject() const
Definition: EffectBase.cpp:220
virtual EffectType GetType() const =0
Type determines how it behaves.
bool TrackGroupProgress(int whichGroup, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:353
void GetBounds(const WaveTrack &track, sampleCount *start, sampleCount *len)
Definition: Effect.cpp:363
double CalcPreviewInputLength(const EffectSettings &settings, double previewLength) const override
Default implementation returns previewLength
Definition: Effect.cpp:384
bool TrackProgress(int whichTrack, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:343
virtual std::shared_ptr< EffectInstance > MakeInstance() const =0
Make an object maintaining short-term state of an Effect.
Performs effect computation.
virtual unsigned GetAudioInCount() const =0
How many input buffers to allocate at once.
virtual size_t SetBlockSize(size_t maxBlockSize)=0
virtual bool NeedsDither() const
virtual unsigned GetAudioOutCount() const =0
How many output buffers to allocate at once.
std::pair< double, double > TimeInterval
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
double t1() const
Definition: ViewInfo.h:36
Unit slope but with either a jump (pasting more) or a flat interval (pasting less)
Definition: TimeWarper.h:181
bool ProcessFinalize() noexcept override
bool ProcessInitialize(EffectSettings &settings, double sampleRate, ChannelNames chanMap) override
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
std::shared_ptr< EffectOutputTracks > mpOutputTracks
void DestroyOutputTracks() const
bool ProcessPass(TrackList &outputs, Instance &instance, EffectSettings &settings)
std::shared_ptr< EffectOutputTracks > MakeOutputTracks()
bool Process(EffectInstance &instance, EffectSettings &settings) const
~PerTrackEffect() override
static bool ProcessTrack(int channel, const Factory &factory, EffectSettings &settings, AudioGraph::Source &source, AudioGraph::Sink &sink, std::optional< sampleCount > genLength, double sampleRate, const SampleTrack &wt, Buffers &inBuffers, Buffers &outBuffers)
bool DoPass1() const
std::function< std::shared_ptr< EffectInstance >()> Factory
sampleCount mSampleCnt
static bool IsSyncLockSelected(const Track &track)
Definition: SyncLock.cpp:80
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
bool GetSelected() const
Selectedness is always the same for all channels of a group.
Definition: Track.cpp:78
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:850
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:950
static TrackListHolder Temporary(AudacityProject *pProject, const Track::Holder &pTrack={})
Definition: Track.cpp:858
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:215
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
auto Channels()
Definition: WaveTrack.h:263
void ZipClips(bool mustAlign=true)
Definition: WaveTrack.cpp:3290
double GetRate() const override
Definition: WaveTrack.cpp:798
size_t GetMaxBlockSize() const
Definition: WaveTrack.cpp:2258
void ClearAndPaste(double t0, double t1, const WaveTrack &src, bool preserve=true, bool merge=true, const TimeWarper *effectWarper=nullptr, bool clearByTrimming=false)
Definition: WaveTrack.cpp:1196
sampleCount TimeToLongSamples(double t0) const
Adapts WideSampleSequence to the interface AudioGraph::Source.
virtual bool Read(const wxString &key, bool *value) const =0
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
Copies from a Source to a Sink, mediated by Buffers.
Externalized state of a plug-in.