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 &leader, WaveChannel &chan, bool isLeader) {
125 if (isLeader)
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(leader, channel, map);
134 if (multichannel) {
135 assert(numAudioIn > 1);
136 if (numChannels == 2) {
137 // TODO: more-than-two-channels
138 pRight = (*leader.Channels().rbegin()).get();
139 clear = false;
140 }
141 }
142
143 if (!isGenerator) {
144 GetBounds(leader, &start, &len);
145 mSampleCnt = len;
146 if (len > 0 && numAudioIn < 1) {
147 bGoodResult = false;
148 return;
149 }
150 }
151 else
152 mSampleCnt = leader.TimeToLongSamples(duration);
153
154 const auto sampleRate = leader.GetRate();
155
156 // Get the block size the client wants to use
157 auto max = leader.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, &leader, 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{ (leader.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 // For now EmptyCopy and WideEmptyCopy still return different types
268 auto wideTrack =
269 (pRight && isGenerator) ? leader.WideEmptyCopy() : nullptr;
270 auto narrowTrack =
271 (!pRight && isGenerator) ? leader.EmptyCopy() : nullptr;
272 const auto pGenerated = wideTrack
273 ? *wideTrack->Any<WaveTrack>().begin()
274 : narrowTrack.get();
275 const auto tempList =
276 wideTrack ? move(wideTrack)
277 : narrowTrack ? TrackList::Temporary(nullptr, narrowTrack, nullptr)
278 : nullptr;
279
280 WaveTrackSink sink{ chan, pRight, pGenerated, start, isProcessor,
282 };
283 assert(sink.AcceptsBuffers(outBuffers));
284
285 // Go process the track(s)
286 const auto factory =
287 [this, &recycledInstances, counter = 0]() mutable {
288 auto index = counter++;
289 if (index < recycledInstances.size())
290 return recycledInstances[index];
291 else
292 return recycledInstances.emplace_back(MakeInstance());
293 };
294 bGoodResult = ProcessTrack(channel, factory, settings, source, sink,
295 genLength, sampleRate, leader, inBuffers, outBuffers);
296 if (bGoodResult) {
297 sink.Flush(outBuffers);
298 bGoodResult = sink.IsOk();
299 if (bGoodResult && tempList) {
300 if (!results)
301 results = tempList;
302 else
303 results->Append(std::move(*tempList));
304 }
305 }
306 if (!bGoodResult)
307 return;
308 ++count;
309 };
310 const auto defaultTrackVisitor =
311 [&](Track &t) {
313 t.SyncLockAdjust(mT1, mT0 + duration);
314 };
315
316 outputs.Any().VisitWhile(bGoodResult,
317 [&](auto &&fallthrough){ return [&](WaveTrack &wt) {
318 if (!wt.GetSelected())
319 return fallthrough();
320 const auto channels = wt.Channels();
321 if (multichannel)
322 waveTrackVisitor(wt, **channels.begin(), true);
323 else {
324 bool first = true;
325 for (const auto pChannel : channels) {
326 waveTrackVisitor(wt, *pChannel, first);
327 first = false;
328 }
329 }
330 if (results) {
331 const auto t1 = ViewInfo::Get(*FindProject()).selectedRegion.t1();
332 PasteTimeWarper warper { t1,
333 mT0 + (*results->begin())->GetEndTime() };
334 wt.ClearAndPaste(mT0, t1, *results, true, true, &warper);
335 results.reset();
336 }
337 }; },
338 defaultTrackVisitor
339 );
340
341 if (bGoodResult && GetType() == EffectTypeGenerate)
342 mT1 = mT0 + duration;
343
344 return bGoodResult;
345}
346
349 AudioGraph::Source &upstream, AudioGraph::Sink &sink,
350 std::optional<sampleCount> genLength,
351 const double sampleRate, const SampleTrack &leader,
352 Buffers &inBuffers, Buffers &outBuffers)
353{
354 assert(upstream.AcceptsBuffers(inBuffers));
355 assert(sink.AcceptsBuffers(outBuffers));
356
357 const auto blockSize = inBuffers.BlockSize();
358 assert(upstream.AcceptsBlockSize(blockSize));
359 assert(blockSize == outBuffers.BlockSize());
360
361 auto pSource = EffectStage::Create(channel, upstream, inBuffers,
362 factory, settings, sampleRate, genLength, leader);
363 if (!pSource)
364 return false;
365 assert(pSource->AcceptsBlockSize(blockSize)); // post of ctor
366 assert(pSource->AcceptsBuffers(outBuffers));
367
368 AudioGraph::Task task{ *pSource, outBuffers, sink };
369 return task.RunLoop();
370}
371
372std::shared_ptr<EffectOutputTracks> PerTrackEffect::MakeOutputTracks()
373{
374 return mpOutputTracks =
375 std::make_shared<EffectOutputTracks>(*mTracks, GetType(),
377}
378
380{
381 mpOutputTracks.reset();
382}
wxT("CloseDown"))
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:116
bool IsPreviewing() const
Definition: EffectBase.h:89
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:109
double mT0
Definition: EffectBase.h:115
const AudacityProject * FindProject() const
Definition: EffectBase.cpp:225
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:385
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
static bool ProcessTrack(int channel, const Factory &factory, EffectSettings &settings, AudioGraph::Source &source, AudioGraph::Sink &sink, std::optional< sampleCount > genLength, double sampleRate, const SampleTrack &leader, Buffers &inBuffers, Buffers &outBuffers)
~PerTrackEffect() override
bool DoPass1() const
std::function< std::shared_ptr< EffectInstance >()> Factory
sampleCount mSampleCnt
static bool IsSyncLockSelected(const Track *pTrack)
Definition: SyncLock.cpp:82
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:122
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:975
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1079
static TrackListHolder Temporary(AudacityProject *pProject, const Track::Holder &left={}, const Track::Holder &right={})
Definition: Track.cpp:1418
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:222
auto Channels()
Definition: WaveTrack.h:272
double GetRate() const override
Definition: WaveTrack.cpp:1102
size_t GetMaxBlockSize() const
Definition: WaveTrack.cpp:2716
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
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
static RegisteredToolbarFactory factory
Copies from a Source to a Sink, mediated by Buffers.
Externalized state of a plug-in.