Audacity 3.2.0
AutoDuckBase.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 AutoDuckBase.cpp
6
7 Markus Meyer
8
9*******************************************************************/
18#include "AutoDuckBase.h"
19#include "BasicUI.h"
20#include "EffectOutputTracks.h"
21#include "ShuttleAutomation.h"
22#include "TimeStretching.h"
23#include "UserException.h"
24#include "WaveClip.h"
25#include "WaveTrack.h"
26#include <cmath>
27
29
31{
32 static CapturedParameters<
35 parameters;
36 return parameters;
37}
38
39/*
40 * Common constants
41 */
42
43static const size_t kBufSize = 131072u; // number of samples to process at once
44static const size_t kRMSWindowSize =
45 100u; // samples in circular RMS window buffer
46
47/*
48 * A auto duck region and an array of auto duck regions
49 */
50
52{
53 AutoDuckRegion(double t0, double t1)
54 {
55 this->t0 = t0;
56 this->t1 = t1;
57 }
58
59 double t0;
60 double t1;
61};
62
64{
65 Parameters().Reset(*this);
67}
68
70{
71}
72
73// ComponentInterface implementation
74
76{
77 return Symbol;
78}
79
81{
82 return XO(
83 "Reduces (ducks) the volume of one or more tracks whenever the volume of a specified \"control\" track reaches a particular level");
84}
85
87{
88 return L"Auto_Duck";
89}
90
91// EffectDefinitionInterface implementation
92
94{
95 return EffectTypeProcess;
96}
97
98// Effect implementation
99
101{
102 mControlTrack = nullptr;
103
104 // Find the control track, which is the non-selected wave track immediately
105 // after the last selected wave track. Fail if there is no such track or if
106 // any selected track is not a wave track.
107 bool lastWasSelectedWaveTrack = false;
108 const WaveTrack* controlTrackCandidate = nullptr;
109 for (auto t : *inputTracks())
110 {
111 if (lastWasSelectedWaveTrack && !t->GetSelected())
112 // This could be the control track, so remember it
113 controlTrackCandidate = dynamic_cast<const WaveTrack*>(t);
114
115 lastWasSelectedWaveTrack = false;
116 if (t->GetSelected())
117 {
118 bool ok = t->TypeSwitch<bool>(
119 [&](const WaveTrack&) {
120 lastWasSelectedWaveTrack = true;
121 controlTrackCandidate = nullptr;
122 return true;
123 },
124 [&](const Track&) {
125 using namespace BasicUI;
127 /* i18n-hint: Auto duck is the name of an effect that 'ducks'
128 (reduces the volume) of the audio automatically when there is
129 sound on another track. Not as in 'Donald-Duck'!*/
130 XO("You selected a track which does not contain audio. "
131 "AutoDuck can only process audio tracks."),
132 MessageBoxOptions {}.IconStyle(Icon::Error));
133 return false;
134 });
135 if (!ok)
136 return false;
137 }
138 }
139
140 if (!controlTrackCandidate)
141 {
142 using namespace BasicUI;
144 /* i18n-hint: Auto duck is the name of an effect that 'ducks' (reduces
145 the volume) of the audio automatically when there is sound on another
146 track. Not as in 'Donald-Duck'!*/
147 XO("Auto Duck needs a control track which must be placed below the "
148 "selected track(s)."),
149 MessageBoxOptions {}.IconStyle(Icon::Error));
150 return false;
151 }
152
153 mControlTrack = controlTrackCandidate;
154 return true;
155}
156
158{
159 if (GetNumWaveTracks() == 0 || !mControlTrack)
160 return false;
161
162 bool cancel = false;
163
166
167 if (end <= start)
168 return false;
169
170 WaveTrack::Holder pFirstTrack;
171 auto pControlTrack = mControlTrack;
172 // If there is any stretch in the control track, substitute a temporary
173 // rendering before trying to use GetFloats
174 {
175 const auto t0 = pControlTrack->LongSamplesToTime(start);
176 const auto t1 = pControlTrack->LongSamplesToTime(end);
177 if (TimeStretching::HasPitchOrSpeed(*pControlTrack, t0, t1))
178 {
179 pFirstTrack = pControlTrack->Duplicate()->SharedPointer<WaveTrack>();
180 if (pFirstTrack)
181 {
183 [&](const ProgressReporter& reportProgress) {
184 pFirstTrack->ApplyPitchAndSpeed(
185 { { t0, t1 } }, reportProgress);
186 },
188 XO("Rendering Control-Track Time-Stretched Audio"));
189 pControlTrack = pFirstTrack.get();
190 }
191 }
192 }
193
194 // the minimum number of samples we have to wait until the maximum
195 // pause has been exceeded
196 double maxPause = mMaximumPause;
197
198 // We don't fade in until we have time enough to actually fade out again
199 if (maxPause < mOuterFadeDownLen + mOuterFadeUpLen)
201
202 auto minSamplesPause = pControlTrack->TimeToLongSamples(maxPause);
203
204 double threshold = DB_TO_LINEAR(mThresholdDb);
205
206 // adjust the threshold so we can compare it to the rmsSum value
207 threshold = threshold * threshold * kRMSWindowSize;
208
209 int rmsPos = 0;
210 double rmsSum = 0;
211 // to make the progress bar appear more natural, we first look for all
212 // duck regions and apply them all at once afterwards
213 std::vector<AutoDuckRegion> regions;
214 bool inDuckRegion = false;
215 {
216 Floats rmsWindow { kRMSWindowSize, true };
217
218 Floats buf { kBufSize };
219
220 // initialize the following two variables to prevent compiler warning
221 double duckRegionStart = 0;
222 sampleCount curSamplesPause = 0;
223
224 auto pos = start;
225
226 const auto pControlChannel = *pControlTrack->Channels().begin();
227 while (pos < end)
228 {
229 const auto len = limitSampleBufferSize(kBufSize, end - pos);
230
231 pControlChannel->GetFloats(buf.get(), pos, len);
232
233 for (auto i = pos; i < pos + len; i++)
234 {
235 rmsSum -= rmsWindow[rmsPos];
236 // i - pos is bounded by len:
237 auto index = (i - pos).as_size_t();
238 rmsWindow[rmsPos] = buf[index] * buf[index];
239 rmsSum += rmsWindow[rmsPos];
240 rmsPos = (rmsPos + 1) % kRMSWindowSize;
241
242 bool thresholdExceeded = rmsSum > threshold;
243
244 if (thresholdExceeded)
245 {
246 // everytime the threshold is exceeded, reset our count for
247 // the number of pause samples
248 curSamplesPause = 0;
249
250 if (!inDuckRegion)
251 {
252 // the threshold has been exceeded for the first time, so
253 // let the duck region begin here
254 inDuckRegion = true;
255 duckRegionStart = pControlTrack->LongSamplesToTime(i);
256 }
257 }
258
259 if (!thresholdExceeded && inDuckRegion)
260 {
261 // the threshold has not been exceeded and we are in a duck
262 // region, but only fade in if the maximum pause has been
263 // exceeded
264 curSamplesPause += 1;
265
266 if (curSamplesPause >= minSamplesPause)
267 {
268 // do the actual duck fade and reset all values
269 double duckRegionEnd =
270 pControlTrack->LongSamplesToTime(i - curSamplesPause);
271
272 regions.push_back(AutoDuckRegion(
273 duckRegionStart - mOuterFadeDownLen,
274 duckRegionEnd + mOuterFadeUpLen));
275
276 inDuckRegion = false;
277 }
278 }
279 }
280
281 pos += len;
282
283 if (TotalProgress(
284 (pos - start).as_double() / (end - start).as_double() /
285 (GetNumWaveTracks() + 1)))
286 {
287 cancel = true;
288 break;
289 }
290 }
291
292 // apply last duck fade, if any
293 if (inDuckRegion)
294 {
295 double duckRegionEnd =
296 pControlTrack->LongSamplesToTime(end - curSamplesPause);
297 regions.push_back(AutoDuckRegion(
298 duckRegionStart - mOuterFadeDownLen,
299 duckRegionEnd + mOuterFadeUpLen));
300 }
301 }
302
303 if (!cancel)
304 {
305 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } } };
306
307 int trackNum = 0;
308
309 for (auto iterTrack : outputs.Get().Selected<WaveTrack>())
310 {
311 for (const auto pChannel : iterTrack->Channels())
312 for (size_t i = 0; i < regions.size(); ++i)
313 {
314 const AutoDuckRegion& region = regions[i];
315 if (ApplyDuckFade(trackNum++, *pChannel, region.t0, region.t1))
316 {
317 cancel = true;
318 goto done;
319 }
320 }
321
322 done:
323 if (cancel)
324 break;
325 }
326
327 if (!cancel)
328 outputs.Commit();
329 }
330
331 return !cancel;
332}
333
334// AutoDuckBase implementation
335
336// this currently does an exponential fade
338 int trackNum, WaveChannel& track, double t0, double t1)
339{
340 bool cancel = false;
341
342 auto start = track.TimeToLongSamples(t0);
343 auto end = track.TimeToLongSamples(t1);
344
345 Floats buf { kBufSize };
346 auto pos = start;
347
348 auto fadeDownSamples =
350 if (fadeDownSamples < 1)
351 fadeDownSamples = 1;
352
353 auto fadeUpSamples =
355 if (fadeUpSamples < 1)
356 fadeUpSamples = 1;
357
358 float fadeDownStep = mDuckAmountDb / fadeDownSamples.as_double();
359 float fadeUpStep = mDuckAmountDb / fadeUpSamples.as_double();
360
361 while (pos < end)
362 {
363 const auto len = limitSampleBufferSize(kBufSize, end - pos);
364 track.GetFloats(buf.get(), pos, len);
365 for (auto i = pos; i < pos + len; ++i)
366 {
367 float gainDown = fadeDownStep * (i - start).as_float();
368 float gainUp = fadeUpStep * (end - i).as_float();
369
370 float gain;
371 if (gainDown > gainUp)
372 gain = gainDown;
373 else
374 gain = gainUp;
375 if (gain < mDuckAmountDb)
376 gain = mDuckAmountDb;
377
378 // i - pos is bounded by len:
379 buf[(i - pos).as_size_t()] *= DB_TO_LINEAR(gain);
380 }
381
382 if (!track.SetFloats(buf.get(), pos, len))
383 {
384 cancel = true;
385 break;
386 }
387
388 pos += len;
389
390 float curTime = track.LongSamplesToTime(pos);
391 float fractionFinished = (curTime - mT0) / (mT1 - mT0);
392 if (TotalProgress(
393 (trackNum + 1 + fractionFinished) / (GetNumWaveTracks() + 1)))
394 {
395 cancel = true;
396 break;
397 }
398 }
399
400 return cancel;
401}
static const size_t kBufSize
static const size_t kRMSWindowSize
Toolkit-neutral facade for basic user interface services.
EffectType
@ EffectTypeProcess
XO("Cut/Copy/Paste")
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:338
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
An AudacityException with no visible message.
static constexpr EffectParameter OuterFadeUpLen
Definition: AutoDuckBase.h:75
double mOuterFadeUpLen
Definition: AutoDuckBase.h:55
const EffectParameterMethods & Parameters() const override
double mInnerFadeDownLen
Definition: AutoDuckBase.h:52
double mThresholdDb
Definition: AutoDuckBase.h:56
bool ApplyDuckFade(int trackNum, WaveChannel &track, double t0, double t1)
EffectType GetType() const override
Type determines how it behaves.
static const ComponentInterfaceSymbol Symbol
Definition: AutoDuckBase.h:25
TranslatableString GetDescription() const override
static constexpr EffectParameter ThresholdDb
Definition: AutoDuckBase.h:78
virtual ~AutoDuckBase()
static constexpr EffectParameter MaximumPause
Definition: AutoDuckBase.h:81
static constexpr EffectParameter InnerFadeDownLen
Definition: AutoDuckBase.h:66
static constexpr EffectParameter InnerFadeUpLen
Definition: AutoDuckBase.h:69
static constexpr EffectParameter DuckAmountDb
Definition: AutoDuckBase.h:63
double mMaximumPause
Definition: AutoDuckBase.h:57
static constexpr EffectParameter OuterFadeDownLen
Definition: AutoDuckBase.h:72
double mOuterFadeDownLen
Definition: AutoDuckBase.h:54
double mDuckAmountDb
Definition: AutoDuckBase.h:51
const WaveTrack * mControlTrack
Definition: AutoDuckBase.h:59
bool Init() override
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
bool Process(EffectInstance &instance, EffectSettings &settings) override
double mInnerFadeUpLen
Definition: AutoDuckBase.h:53
ComponentInterfaceSymbol GetSymbol() const override
Generates EffectParameterMethods overrides from variadic template arguments.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
double mT1
Definition: EffectBase.h:123
void SetLinearEffectFlag(bool linearEffectFlag)
Definition: EffectBase.cpp:210
const TrackList * inputTracks() const
Definition: EffectBase.h:102
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:116
double mT0
Definition: EffectBase.h:122
bool TotalProgress(double frac, const TranslatableString &={}) const
Definition: Effect.cpp:335
int GetNumWaveTracks() const
Definition: Effect.h:139
Performs effect computation.
Use this object to copy the input tracks to tentative outputTracks.
Interface for manipulations of an Effect's settings.
virtual void Reset(Effect &effect) const =0
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
R TypeSwitch(const Functions &...functions)
Definition: Track.h:381
Holds a msgid for the translation catalog; may also bind format arguments.
static void WithCancellableProgress(std::function< void(const ProgressReporter &)> action, TranslatableString title, TranslatableString message)
A frequently useful convenience wraps a lambda and may throw this type.
bool SetFloats(const float *buffer, sampleCount start, size_t len, sampleFormat effectiveFormat=widestSampleFormat)
Random-access assignment of a range of samples.
Definition: WaveTrack.h:162
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=FillFormat::fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
"narrow" overload fetches from the unique channel
Definition: WaveTrack.h:129
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
std::shared_ptr< WaveTrack > Holder
Definition: WaveTrack.h:247
double LongSamplesToTime(sampleCount pos) const
sampleCount TimeToLongSamples(double t0) const
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
std::function< void(double)> ProgressReporter
Definition: BasicUI.h:25
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:287
WAVE_TRACK_API const TranslatableString defaultStretchRenderingTitle
WAVE_TRACK_API bool HasPitchOrSpeed(const WaveTrack &track, double t0, double t1)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
a struct that holds a start and end time.
AutoDuckRegion(double t0, double t1)
MessageBoxOptions && IconStyle(Icon style) &&
Definition: BasicUI.h:104
Externalized state of a plug-in.