Audacity 3.2.0
EffectBase.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 EffectBase.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 "EffectBase.h"
22
23#include <thread>
24#include "../AudioIO.h"
25#include "ConfigInterface.h"
27#include "../MixAndRender.h"
28#include "PluginManager.h"
29#include "../ProjectAudioManager.h"
30#include "QualitySettings.h"
31#include "TransactionScope.h"
32#include "ViewInfo.h"
33#include "../WaveTrack.h"
34#include "../widgets/ProgressDialog.h"
35#include "../widgets/NumericTextCtrl.h"
36
37// Effect application counter
39
41{
42 // PRL: I think this initialization of mProjectRate doesn't matter
43 // because it is always reassigned in DoEffect before it is used
44 // STF: but can't call AudioIOBase::GetOptimalSupportedSampleRate() here.
45 // (Which is called to compute the default-default value.) (Bug 2280)
47}
48
49EffectBase::~EffectBase() = default;
50
52{
53 return 30.0;
54}
55
56// TODO: Lift the possible user-prompting part out of this function, so that
57// the recursive paths into this function via Effect::Delegate are simplified,
58// and we don't have both EffectSettings and EffectSettingsAccessPtr
59// If pAccess is not null, settings should have come from its Get()
61 TrackList *list,
63 NotifyingSelectedRegion &selectedRegion,
64 unsigned flags,
65 wxWindow *pParent,
66 const EffectDialogFactory &dialogFactory,
67 const EffectSettingsAccessPtr &pAccess)
68{
69 auto cleanup0 = valueRestorer(mUIFlags, flags);
70 wxASSERT(selectedRegion.duration() >= 0.0);
71
72 mOutputTracks.reset();
73
75 mProjectRate = projectRate;
76 mTracks = list;
77
78 // This is for performance purposes only, no additional recovery implied
79 auto &pProject = *const_cast<AudacityProject*>(FindProject()); // how to remove this const_cast?
80 TransactionScope trans(pProject, "Effect");
81
82 // Update track/group counts
84
85 bool isSelection = false;
86
87 auto duration = 0.0;
92
93 WaveTrack *newTrack{};
94 bool success = false;
95 auto oldDuration = duration;
96
97 auto cleanup = finally( [&] {
98 if (!success) {
99 if (newTrack) {
100 mTracks->Remove(newTrack);
101 }
102 // On failure, restore the old duration setting
103 settings.extra.SetDuration(oldDuration);
104 }
105 else
106 trans.Commit();
107
108 ReplaceProcessedTracks( false );
109 mPresetNames.clear();
110 } );
111
112 // We don't yet know the effect type for code in the Nyquist Prompt, so
113 // assume it requires a track and handle errors when the effect runs.
114 if ((GetType() == EffectTypeGenerate || GetPath() == NYQUIST_PROMPT_ID) && (mNumTracks == 0)) {
115 auto track = mFactory->Create();
117 newTrack = mTracks->Add(track);
118 newTrack->SetSelected(true);
119 }
120
121 mT0 = selectedRegion.t0();
122 mT1 = selectedRegion.t1();
123 if (mT1 > mT0)
124 {
125 // there is a selection: let's fit in there...
126 // MJS: note that this is just for the TTC and is independent of the track rate
127 // but we do need to make sure we have the right number of samples at the project rate
128 double quantMT0 = QUANTIZED_TIME(mT0, mProjectRate);
129 double quantMT1 = QUANTIZED_TIME(mT1, mProjectRate);
130 duration = quantMT1 - quantMT0;
131 isSelection = true;
132 mT1 = mT0 + duration;
133 }
134
135 // This is happening inside EffectSettingsAccess::ModifySettings
136 auto newFormat = isSelection
139 auto updater = [&](EffectSettings &settings) {
140 settings.extra.SetDuration(duration);
141 settings.extra.SetDurationFormat( newFormat );
142 };
143 // Update our copy of settings; update the EffectSettingsAccess too,
144 // if we are going to show a dialog
145 updater(settings);
146 if (pAccess)
147 pAccess->ModifySettings(updater);
148
149#ifdef EXPERIMENTAL_SPECTRAL_EDITING
150 mF0 = selectedRegion.f0();
151 mF1 = selectedRegion.f1();
153 mPresetNames.push_back(L"control-f0");
155 mPresetNames.push_back(L"control-f1");
156
157#endif
159
160 // Note: Init may read parameters from preferences
161 auto pInstance = MakeInstance();
162 if (!pInstance->Init())
163 return false;
164
165 // Prompting will be bypassed when applying an effect that has already
166 // been configured, e.g. repeating the last effect on a different selection.
167 // Prompting may call EffectBase::Preview
168 if ( pParent && dialogFactory && pAccess &&
169 IsInteractive()) {
171 *pParent, dialogFactory, *pInstance, *pAccess, IsBatchProcessing() ) )
172 return false;
173 else
174 // Retrieve again after the dialog modified settings
175 settings = pAccess->Get();
176 }
177
178 // If the dialog was shown, then it has been closed without errors or
179 // cancellation, and any change of duration has been saved in the config file
180
181 bool returnVal = true;
182 bool skipFlag = CheckWhetherSkipEffect(settings);
183 if (skipFlag == false)
184 {
185 using namespace BasicUI;
186 auto name = GetName();
187 auto progress = MakeProgress(
188 name,
189 XO("Applying %s...").Format( name ),
191 );
192 auto vr = valueRestorer( mProgress, progress.get() );
193
194 returnVal = pInstance->Process(settings);
195 }
196
197 if (returnVal && (mT1 >= mT0 ))
198 {
199 selectedRegion.setTimes(mT0, mT1);
200 }
201
202 success = returnVal;
203 return returnVal;
204}
205
206void EffectBase::SetLinearEffectFlag(bool linearEffectFlag)
207{
208 mIsLinearEffect = linearEffectFlag;
209}
210
211void EffectBase::SetPreviewFullSelectionFlag(bool previewDurationFlag)
212{
213 mPreviewFullSelection = previewDurationFlag;
214}
215
216
218{
219 mPreviewWithNotSelected = includeNotSelected;
220}
221
222// If bGoodResult, replace mTracks tracks with successfully processed mOutputTracks copies.
223// Else clear and DELETE mOutputTracks copies.
224void EffectBase::ReplaceProcessedTracks(const bool bGoodResult)
225{
226 if (!bGoodResult) {
227 // Free resources, unless already freed.
228
229 // Processing failed or was cancelled so throw away the processed tracks.
230 if ( mOutputTracks )
231 mOutputTracks->Clear();
232
233 // Reset map
234 mIMap.clear();
235 mOMap.clear();
236
237 //TODO:undo the non-gui ODTask transfer
238 return;
239 }
240
241 // Assume resources need to be freed.
242 wxASSERT(mOutputTracks); // Make sure we at least did the CopyInputTracks().
243
244 auto iterOut = mOutputTracks->ListOfTracks::begin(),
245 iterEnd = mOutputTracks->ListOfTracks::end();
246
247 size_t cnt = mOMap.size();
248 size_t i = 0;
249
250 for (; iterOut != iterEnd; ++i) {
251 ListOfTracks::value_type o = *iterOut;
252 // If tracks were removed from mOutputTracks, then there will be
253 // tracks in the map that must be removed from mTracks.
254 while (i < cnt && mOMap[i] != o.get()) {
255 const auto t = mIMap[i];
256 if (t) {
257 mTracks->Remove(t);
258 }
259 i++;
260 }
261
262 // This should never happen
263 wxASSERT(i < cnt);
264
265 // Remove the track from the output list...don't DELETE it
266 iterOut = mOutputTracks->erase(iterOut);
267
268 const auto t = mIMap[i];
269 if (t == NULL)
270 {
271 // This track is a NEW addition to output tracks; add it to mTracks
272 mTracks->Add( o );
273 }
274 else
275 {
276 // Replace mTracks entry with the NEW track
277 mTracks->Replace(t, o);
278 }
279 }
280
281 // If tracks were removed from mOutputTracks, then there may be tracks
282 // left at the end of the map that must be removed from mTracks.
283 while (i < cnt) {
284 const auto t = mIMap[i];
285 if (t) {
286 mTracks->Remove(t);
287 }
288 i++;
289 }
290
291 // Reset map
292 mIMap.clear();
293 mOMap.clear();
294
295 // Make sure we processed everything
296 wxASSERT(mOutputTracks->empty());
297
298 // The output list is no longer needed
299 mOutputTracks.reset();
300 nEffectsDone++;
301}
302
304{
305 if (!inputTracks())
306 return nullptr;
307 return inputTracks()->GetOwner();
308}
309
311{
312 mNumTracks = mTracks->Selected< const WaveTrack >().size();
314}
315
316void EffectBase::Preview(EffectSettingsAccess &access, bool dryOnly)
317{
318 if (mNumTracks == 0) { // nothing to preview
319 return;
320 }
321
322 auto gAudioIO = AudioIO::Get();
323 if (gAudioIO->IsBusy()) {
324 return;
325 }
326
327 wxWindow *FocusDialog = wxWindow::FindFocus();
328
329 double previewDuration;
330 bool isNyquist = GetFamily() == NYQUISTEFFECTS_FAMILY;
331 bool isGenerator = GetType() == EffectTypeGenerate;
332
333 // Mix a few seconds of audio from all of the tracks
334 double previewLen;
335 gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLen, 6.0);
336
337 const double rate = mProjectRate;
338
339 const auto &settings = access.Get();
340 if (isNyquist && isGenerator)
341 previewDuration = CalcPreviewInputLength(settings, previewLen);
342 else
343 previewDuration = std::min(settings.extra.GetDuration(),
344 CalcPreviewInputLength(settings, previewLen));
345
346 double t1 = mT0 + previewDuration;
347
348 if ((t1 > mT1) && !isGenerator) {
349 t1 = mT1;
350 }
351
352 if (t1 <= mT0)
353 return;
354
355 bool success = true;
356
357 auto cleanup = finally( [&] {
358
359 // Effect is already inited; we will call Process and then Init
360 // again, so the state is exactly the way it was before Preview
361 // was called.
362 if (!dryOnly)
363 // TODO remove this reinitialization of state within the Effect object
364 // It is done indirectly via Effect::Instance
365 MakeInstance()->Init();
366
367 // In case any dialog control depends on mT1 or mDuration:
368 if ( mUIDialog )
369 mUIDialog->TransferDataToWindow();
370 } );
371
372 auto vr0 = valueRestorer( mT0 );
373 auto vr1 = valueRestorer( mT1 );
374 // Most effects should stop at t1.
376 mT1 = t1;
377
378 // In case any dialog control depends on mT1 or mDuration:
379 if ( mUIDialog ) {
380 mUIDialog->TransferDataToWindow();
381 }
382
383 // Save the original track list
384 TrackList *saveTracks = mTracks;
385
386 auto cleanup2 = finally( [&] {
387 mTracks = saveTracks;
388 if (FocusDialog) {
389 FocusDialog->SetFocus();
390 }
391
392 // In case of failed effect, be sure to free memory.
393 ReplaceProcessedTracks( false );
394 } );
395
396 // Build NEW tracklist from rendering tracks
397 // Set the same owning project, so FindProject() can see it within Process()
398 const auto pProject = saveTracks->GetOwner();
399 auto uTracks = TrackList::Create( pProject );
400 mTracks = uTracks.get();
401
402 // Linear Effect preview optimised by pre-mixing to one track.
403 // Generators need to generate per track.
404 if (mIsLinearEffect && !isGenerator) {
405 WaveTrack::Holder mixLeft, mixRight;
406 MixAndRender(saveTracks->Selected<const WaveTrack>(),
407 Mixer::WarpOptions{ *saveTracks },
408 wxString{}, // Don't care about the name of the temporary tracks
409 mFactory, rate, floatSample, mT0, t1, mixLeft, mixRight);
410 if (!mixLeft)
411 return;
412
413 mixLeft->Offset(-mixLeft->GetStartTime());
414 mixLeft->SetSelected(true);
415 auto pLeft = mTracks->Add( mixLeft );
416 Track *pRight{};
417 if (mixRight) {
418 mixRight->Offset(-mixRight->GetStartTime());
419 mixRight->SetSelected(true);
420 pRight = mTracks->Add( mixRight );
421 mTracks->MakeMultiChannelTrack(*pLeft, 2, true);
422 }
423 }
424 else {
425 for (auto src : saveTracks->Any< const WaveTrack >()) {
426 if (src->GetSelected() || mPreviewWithNotSelected) {
427 auto dest = src->Copy(mT0, t1);
428 dest->SetSelected(src->GetSelected());
429 mTracks->Add( dest );
430 }
431 }
432 }
433
434 // NEW tracks start at time zero.
435 // Adjust mT0 and mT1 to be the times to process, and to
436 // play back in these tracks
437 mT1 -= mT0;
438 mT0 = 0.0;
439
440 // Update track/group counts
442
443 // Apply effect
444 if (!dryOnly) {
445 using namespace BasicUI;
446 auto progress = MakeProgress(
447 GetName(),
448 XO("Preparing preview"),
450 ); // Have only "Stop" button.
451 auto vr = valueRestorer( mProgress, progress.get() );
452
453 auto vr2 = valueRestorer( mIsPreview, true );
454
456 success = MakeInstance()->Process(settings);
457 });
458 }
459
460 if (success)
461 {
463
464 // Some effects (Paulstretch) may need to generate more
465 // than previewLen, so take the min.
466 t1 = std::min(mT0 + previewLen, mT1);
467
468 // Start audio playing
469 auto options = DefaultPlayOptions(*pProject);
470 int token = gAudioIO->StartStream(tracks, mT0, t1, t1, options);
471
472 if (token) {
473 auto previewing = ProgressResult::Success;
474 // The progress dialog must be deleted before stopping the stream
475 // to allow events to flow to the app during StopStream processing.
476 // The progress dialog blocks these events.
477 {
478 ProgressDialog progress
479 (GetName(), XO("Previewing"), pdlgHideCancelButton);
480
481 while (gAudioIO->IsStreamActive(token) && previewing == ProgressResult::Success) {
482 using namespace std::chrono;
483 std::this_thread::sleep_for(100ms);
484 previewing = progress.Update(gAudioIO->GetStreamTime() - mT0, t1 - mT0);
485 }
486 }
487
488 gAudioIO->StopStream();
489
490 while (gAudioIO->IsBusy()) {
491 using namespace std::chrono;
492 std::this_thread::sleep_for(100ms);
493 }
494 }
495 else {
496 using namespace BasicUI;
498 wxWidgetsWindowPlacement{ FocusDialog }, XO("Error"),
499 XO("Error opening sound device.\nTry changing the audio host, playback device and the project sample rate."),
500 wxT("Error_opening_sound_device"),
501 ErrorDialogOptions{ ErrorDialogType::ModalErrorReport } );
502 }
503 }
504}
int min(int a, int b)
static RegisteredToolbarFactory factory
const TranslatableString name
Definition: Distortion.cpp:82
#define NYQUISTEFFECTS_FAMILY
Definition: EffectBase.h:144
const RegistryPath & CurrentSettingsGroup()
Component of a configuration key path, for last-used destructive settings.
@ EffectTypeGenerate
std::function< wxDialog *(wxWindow &parent, EffectPlugin &, EffectUIClientInterface &, EffectInstance &, EffectSettingsAccess &) > EffectDialogFactory
Type of function that creates a dialog for an effect.
Definition: EffectPlugin.h:34
#define XO(s)
Definition: Internat.h:31
ValueRestorer< T > valueRestorer(T &var)
inline functions provide convenient parameter type deduction
Definition: MemoryX.h:226
#define QUANTIZED_TIME(time, rate)
Definition: MemoryX.h:500
void MixAndRender(const TrackIterRange< const WaveTrack > &trackRange, const Mixer::WarpOptions &warpOptions, const wxString &newTrackName, WaveTrackFactory *trackFactory, double rate, sampleFormat format, double startTime, double endTime, WaveTrack::Holder &uLeft, WaveTrack::Holder &uRight)
Mixes together all input tracks, applying any envelopes, amplitude gain, panning, and real-time effec...
#define NYQUIST_PROMPT_ID
FileConfig * gPrefs
Definition: Prefs.cpp:71
@ pdlgHideCancelButton
AudioIOStartStreamOptions DefaultPlayOptions(AudacityProject &project, bool newDefault)
@ floatSample
Definition: SampleFormat.h:34
static Settings & settings()
Definition: TrackInfo.cpp:87
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
static AudioIO * Get()
Definition: AudioIO.cpp:140
Abstraction of a progress dialog with well defined time-to-completion estimate.
Definition: BasicUI.h:154
virtual PluginPath GetPath() const =0
TranslatableString GetName() const
int mNumTracks
Definition: EffectBase.h:136
double mT1
Definition: EffectBase.h:107
std::shared_ptr< TrackList > mOutputTracks
Definition: EffectBase.h:105
bool DoEffect(EffectSettings &settings, double projectRate, TrackList *list, WaveTrackFactory *factory, NotifyingSelectedRegion &selectedRegion, unsigned flags, wxWindow *pParent, const EffectDialogFactory &dialogFactory, const EffectSettingsAccessPtr &pAccess) override
Unfortunately complicated dual-use function.
Definition: EffectBase.cpp:60
WaveTrackFactory * mFactory
Definition: EffectBase.h:101
~EffectBase() override
TrackList * mTracks
Definition: EffectBase.h:125
void CountWaveTracks()
Definition: EffectBase.cpp:310
std::vector< Track * > mOMap
Definition: EffectBase.h:134
void SetPreviewFullSelectionFlag(bool previewDurationFlag)
Definition: EffectBase.cpp:211
void SetLinearEffectFlag(bool linearEffectFlag)
Definition: EffectBase.cpp:206
const TrackList * inputTracks() const
Definition: EffectBase.h:102
virtual bool CheckWhetherSkipEffect(const EffectSettings &settings) const =0
After Init(), tell whether Process() should be skipped.
double mProjectRate
Definition: EffectBase.h:99
bool mIsPreview
Definition: EffectBase.h:131
BasicUI::ProgressDialog * mProgress
Definition: EffectBase.h:98
bool mPreviewWithNotSelected
Definition: EffectBase.h:128
int mNumGroups
Definition: EffectBase.h:137
bool mPreviewFullSelection
Definition: EffectBase.h:129
virtual double CalcPreviewInputLength(const EffectSettings &settings, double previewLength) const =0
std::vector< Track * > mIMap
Definition: EffectBase.h:133
wxArrayString mPresetNames
Definition: EffectBase.h:112
double GetDefaultDuration()
Definition: EffectBase.cpp:51
wxWeakRef< wxDialog > mUIDialog
This weak pointer may be the same as mUIParent, or null.
Definition: EffectBase.h:119
double mT0
Definition: EffectBase.h:106
unsigned mUIFlags
Definition: EffectBase.h:113
void IncludeNotSelectedPreviewTracks(bool includeNotSelected)
Definition: EffectBase.cpp:217
void ReplaceProcessedTracks(const bool bGoodResult)
Definition: EffectBase.cpp:224
bool mIsLinearEffect
This weak pointer may be the same as mHostUIDialog, or null.
Definition: EffectBase.h:127
void Preview(EffectSettingsAccess &access, bool dryOnly) override
Definition: EffectBase.cpp:316
static int nEffectsDone
Definition: EffectBase.h:91
const AudacityProject * FindProject() const
Definition: EffectBase.cpp:303
virtual EffectType GetType() const =0
Type determines how it behaves.
virtual bool IsInteractive() const =0
Whether the effect needs a dialog for entry of settings.
virtual EffectFamilySymbol GetFamily() const =0
Report identifier and user-visible name of the effect protocol.
virtual std::shared_ptr< EffectInstance > MakeInstance() const =0
Make an object maintaining short-term state of an Effect.
virtual int ShowHostInterface(wxWindow &parent, const EffectDialogFactory &factory, EffectInstance &instance, EffectSettingsAccess &access, bool forceModal=false)=0
Usually applies factory to self and given access.
std::shared_ptr< EffectSettingsAccess > EffectSettingsAccessPtr
Definition: EffectPlugin.h:49
virtual const EffectSettingsManager & GetDefinition() const =0
virtual bool IsBatchProcessing() const =0
void ModifySettings(Function &&function)
Do a correct read-modify-write of settings.
virtual const EffectSettings & Get()=0
static const RegistryPath & DurationKey()
Abstract base class used in importing a file.
double t1() const
Definition: ViewInfo.h:35
double f1() const
Definition: ViewInfo.h:37
double duration() const
Definition: ViewInfo.h:40
bool setTimes(double t0, double t1)
Definition: ViewInfo.cpp:51
double f0() const
Definition: ViewInfo.h:36
double t0() const
Definition: ViewInfo.h:34
static NumericFormatSymbol DefaultSelectionFormat()
static NumericFormatSymbol TimeAndSampleFormat()
static TransportTracks GetAllPlaybackTracks(TrackList &trackList, bool selectedOnly, bool nonWaveToo=false)
static const int UndefinedFrequency
bool ReadWithDefault(T *pVar, const T &defaultValue) const
overload of ReadWithDefault returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:191
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
void Offset(double t)
Definition: Track.h:474
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:1330
bool MakeMultiChannelTrack(Track &first, int nChannels, bool aligned)
Converts channels to a multichannel track.
Definition: Track.cpp:747
static std::shared_ptr< TrackList > Create(AudacityProject *pOwner)
Definition: Track.cpp:483
auto SelectedLeaders() -> TrackIterRange< TrackType >
Definition: Track.h:1480
ListOfTracks::value_type Replace(Track *t, const ListOfTracks::value_type &with)
Definition: Track.cpp:709
wxString MakeUniqueTrackName(const wxString &baseTrackName) const
Returns string that contains baseTrackName, but is guaranteed to be unique among other tracks in that...
Definition: Track.cpp:524
TrackKind * Add(const std::shared_ptr< TrackKind > &t)
Definition: Track.h:1556
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1429
TrackNodePointer Remove(Track *t)
Remove the Track and return an iterator to what followed it.
Definition: Track.cpp:783
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:1446
AudacityProject * GetOwner()
Definition: Track.h:1365
RAII for a database transaction, possibly nested.
bool Commit()
Commit the transaction.
Used to create or clone a WaveTrack, with appropriate context from the project that will own the trac...
Definition: WaveTrack.h:606
std::shared_ptr< WaveTrack > Create()
Creates an unnamed empty WaveTrack with default sample format and default rate.
Definition: WaveTrack.cpp:118
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
static wxString GetDefaultAudioTrackNamePreference()
Definition: WaveTrack.cpp:100
std::shared_ptr< WaveTrack > Holder
Definition: WaveTrack.h:105
@ ProgressShowCancel
Definition: BasicUI.h:139
@ ProgressShowStop
Definition: BasicUI.h:138
void ShowErrorDialog(const WindowPlacement &placement, const TranslatableString &dlogTitle, const TranslatableString &message, const ManualPageID &helpPage, const ErrorDialogOptions &options={})
Show an error dialog with a link to the manual for further help.
Definition: BasicUI.h:241
std::unique_ptr< ProgressDialog > MakeProgress(const TranslatableString &title, const TranslatableString &message, unsigned flags=(ProgressShowStop|ProgressShowCancel), const TranslatableString &remainingLabelText={})
Create and display a progress dialog.
Definition: BasicUI.h:271
bool GetConfig(const EffectDefinitionInterface &ident, ConfigurationType type, const RegistryPath &group, const RegistryPath &key, Value &var, const Value &defval)
PROJECT_RATE_API IntSetting DefaultSampleRate
Options for variations of error dialogs; the default is for modal dialogs.
Definition: BasicUI.h:49
Externalized state of a plug-in.
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.