1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 AutoDuck.cpp
6
7 Markus Meyer
8
9 *******************************************************************//**
10
11 \class EffectAutoDuck
12 \brief Implements the Auto Ducking effect
13
14 \class AutoDuckRegion
15 \brief a struct that holds a start and end time.
16
17 *******************************************************************/
18
19
20 #include "AutoDuck.h"
21 #include "LoadEffects.h"
22
23 #include <math.h>
24 #include <float.h>
25
26 #include <wx/dcclient.h>
27 #include <wx/dcmemory.h>
28 #include <wx/intl.h>
29
30 #include "AColor.h"
31 #include "AllThemeResources.h"
32 #include "Prefs.h"
33 #include "../Shuttle.h"
34 #include "../ShuttleGui.h"
35 #include "Theme.h"
36 #include "../widgets/valnum.h"
37
38 #include "../WaveTrack.h"
39 #include "../widgets/AudacityMessageBox.h"
40
41 // Define keys, defaults, minimums, and maximums for the effect parameters
42 //
43 // Name Type Key Def Min Max Scale
44 Param( DuckAmountDb, double, wxT("DuckAmountDb"), -12.0, -24.0, 0.0, 1 );
45 Param( InnerFadeDownLen, double, wxT("InnerFadeDownLen"), 0.0, 0.0, 3.0, 1 );
46 Param( InnerFadeUpLen, double, wxT("InnerFadeUpLen"), 0.0, 0.0, 3.0, 1 );
47 Param( OuterFadeDownLen, double, wxT("OuterFadeDownLen"), 0.5, 0.0, 3.0, 1 );
48 Param( OuterFadeUpLen, double, wxT("OuterFadeUpLen"), 0.5, 0.0, 3.0, 1 );
49 Param( ThresholdDb, double, wxT("ThresholdDb"), -30.0, -100.0, 0.0, 1 );
50 Param( MaximumPause, double, wxT("MaximumPause"), 1.0, 0.0, DBL_MAX, 1 );
51
52 /*
53 * Common constants
54 */
55
56 static const size_t kBufSize = 131072u; // number of samples to process at once
57 static const size_t kRMSWindowSize = 100u; // samples in circular RMS window buffer
58
59 /*
60 * A auto duck region and an array of auto duck regions
61 */
62
63 struct AutoDuckRegion
64 {
AutoDuckRegionAutoDuckRegion65 AutoDuckRegion(double t0, double t1)
66 {
67 this->t0 = t0;
68 this->t1 = t1;
69 }
70
71 double t0;
72 double t1;
73 };
74
75 /*
76 * Effect implementation
77 */
78
79 const ComponentInterfaceSymbol EffectAutoDuck::Symbol
80 { XO("Auto Duck") };
81
82 namespace{ BuiltinEffectsModule::Registration< EffectAutoDuck > reg; }
83
BEGIN_EVENT_TABLE(EffectAutoDuck,wxEvtHandler)84 BEGIN_EVENT_TABLE(EffectAutoDuck, wxEvtHandler)
85 EVT_TEXT(wxID_ANY, EffectAutoDuck::OnValueChanged)
86 END_EVENT_TABLE()
87
88 EffectAutoDuck::EffectAutoDuck()
89 {
90 mDuckAmountDb = DEF_DuckAmountDb;
91 mInnerFadeDownLen = DEF_InnerFadeDownLen;
92 mInnerFadeUpLen = DEF_InnerFadeUpLen;
93 mOuterFadeDownLen = DEF_OuterFadeDownLen;
94 mOuterFadeUpLen = DEF_OuterFadeUpLen;
95 mThresholdDb = DEF_ThresholdDb;
96 mMaximumPause = DEF_MaximumPause;
97
98 SetLinearEffectFlag(true);
99
100 mControlTrack = NULL;
101
102 mPanel = NULL;
103 }
104
~EffectAutoDuck()105 EffectAutoDuck::~EffectAutoDuck()
106 {
107 }
108
109 // ComponentInterface implementation
110
GetSymbol()111 ComponentInterfaceSymbol EffectAutoDuck::GetSymbol()
112 {
113 return Symbol;
114 }
115
GetDescription()116 TranslatableString EffectAutoDuck::GetDescription()
117 {
118 return XO("Reduces (ducks) the volume of one or more tracks whenever the volume of a specified \"control\" track reaches a particular level");
119 }
120
ManualPage()121 ManualPageID EffectAutoDuck::ManualPage()
122 {
123 return L"Auto_Duck";
124 }
125
126 // EffectDefinitionInterface implementation
127
GetType()128 EffectType EffectAutoDuck::GetType()
129 {
130 return EffectTypeProcess;
131 }
132
133 // EffectClientInterface implementation
DefineParams(ShuttleParams & S)134 bool EffectAutoDuck::DefineParams( ShuttleParams & S ){
135 S.SHUTTLE_PARAM( mDuckAmountDb, DuckAmountDb);
136 S.SHUTTLE_PARAM( mInnerFadeDownLen, InnerFadeDownLen);
137 S.SHUTTLE_PARAM( mInnerFadeUpLen, InnerFadeUpLen);
138 S.SHUTTLE_PARAM( mOuterFadeDownLen, OuterFadeDownLen);
139 S.SHUTTLE_PARAM( mOuterFadeUpLen, OuterFadeUpLen);
140 S.SHUTTLE_PARAM( mThresholdDb, ThresholdDb);
141 S.SHUTTLE_PARAM( mMaximumPause, MaximumPause);
142 return true;
143 }
144
GetAutomationParameters(CommandParameters & parms)145 bool EffectAutoDuck::GetAutomationParameters(CommandParameters & parms)
146 {
147 parms.Write(KEY_DuckAmountDb, mDuckAmountDb);
148 parms.Write(KEY_InnerFadeDownLen, mInnerFadeDownLen);
149 parms.Write(KEY_InnerFadeUpLen, mInnerFadeUpLen);
150 parms.Write(KEY_OuterFadeDownLen, mOuterFadeDownLen);
151 parms.Write(KEY_OuterFadeUpLen, mOuterFadeUpLen);
152 parms.Write(KEY_ThresholdDb, mThresholdDb);
153 parms.Write(KEY_MaximumPause, mMaximumPause);
154
155 return true;
156 }
157
SetAutomationParameters(CommandParameters & parms)158 bool EffectAutoDuck::SetAutomationParameters(CommandParameters & parms)
159 {
160 ReadAndVerifyDouble(DuckAmountDb);
161 ReadAndVerifyDouble(InnerFadeDownLen);
162 ReadAndVerifyDouble(InnerFadeUpLen);
163 ReadAndVerifyDouble(OuterFadeDownLen);
164 ReadAndVerifyDouble(OuterFadeUpLen);
165 ReadAndVerifyDouble(ThresholdDb);
166 ReadAndVerifyDouble(MaximumPause);
167
168 mDuckAmountDb = DuckAmountDb;
169 mInnerFadeDownLen = InnerFadeDownLen;
170 mInnerFadeUpLen = InnerFadeUpLen;
171 mOuterFadeDownLen = OuterFadeDownLen;
172 mOuterFadeUpLen = OuterFadeUpLen;
173 mThresholdDb = ThresholdDb;
174 mMaximumPause = MaximumPause;
175
176 return true;
177 }
178
179 // Effect implementation
180
Startup()181 bool EffectAutoDuck::Startup()
182 {
183 wxString base = wxT("/Effects/AutoDuck/");
184
185 // Migrate settings from 2.1.0 or before
186
187 // Already migrated, so bail
188 if (gPrefs->Exists(base + wxT("Migrated")))
189 {
190 return true;
191 }
192
193 // Load the old "current" settings
194 if (gPrefs->Exists(base))
195 {
196 gPrefs->Read(base + wxT("DuckAmountDb"), &mDuckAmountDb, DEF_DuckAmountDb);
197 gPrefs->Read(base + wxT("InnerFadeDownLen"), &mInnerFadeDownLen, DEF_InnerFadeDownLen);
198 gPrefs->Read(base + wxT("InnerFadeUpLen"), &mInnerFadeUpLen, DEF_InnerFadeUpLen);
199 gPrefs->Read(base + wxT("OuterFadeDownLen"), &mOuterFadeDownLen, DEF_OuterFadeDownLen);
200 gPrefs->Read(base + wxT("OuterFadeUpLen"), &mOuterFadeUpLen, DEF_OuterFadeUpLen);
201 gPrefs->Read(base + wxT("ThresholdDb"), &mThresholdDb, DEF_ThresholdDb);
202 gPrefs->Read(base + wxT("MaximumPause"), &mMaximumPause, DEF_MaximumPause);
203
204 SaveUserPreset(GetCurrentSettingsGroup());
205
206 // Do not migrate again
207 gPrefs->Write(base + wxT("Migrated"), true);
208 gPrefs->Flush();
209 }
210
211 return true;
212 }
213
Init()214 bool EffectAutoDuck::Init()
215 {
216 mControlTrack = NULL;
217
218 bool lastWasSelectedWaveTrack = false;
219 const WaveTrack *controlTrackCandidate = NULL;
220
221 for (auto t : inputTracks()->Any())
222 {
223 if (lastWasSelectedWaveTrack && !t->GetSelected()) {
224 // This could be the control track, so remember it
225 controlTrackCandidate = track_cast<const WaveTrack *>(t);
226 }
227
228 lastWasSelectedWaveTrack = false;
229
230 if (t->GetSelected()) {
231 bool ok = t->TypeSwitch<bool>(
232 [&](const WaveTrack *) {
233 lastWasSelectedWaveTrack = true;
234 return true;
235 },
236 [&](const Track *) {
237 Effect::MessageBox(
238 /* i18n-hint: Auto duck is the name of an effect that 'ducks' (reduces the volume)
239 * of the audio automatically when there is sound on another track. Not as
240 * in 'Donald-Duck'!*/
241 XO("You selected a track which does not contain audio. AutoDuck can only process audio tracks."),
242 wxICON_ERROR );
243 return false;
244 }
245 );
246 if (!ok)
247 return false;
248 }
249 }
250
251 if (!controlTrackCandidate)
252 {
253 Effect::MessageBox(
254 /* i18n-hint: Auto duck is the name of an effect that 'ducks' (reduces the volume)
255 * of the audio automatically when there is sound on another track. Not as
256 * in 'Donald-Duck'!*/
257 XO("Auto Duck needs a control track which must be placed below the selected track(s)."),
258 wxICON_ERROR );
259 return false;
260 }
261
262 mControlTrack = controlTrackCandidate;
263
264 return true;
265 }
266
End()267 void EffectAutoDuck::End()
268 {
269 mControlTrack = NULL;
270 }
271
Process()272 bool EffectAutoDuck::Process()
273 {
274 if (GetNumWaveTracks() == 0 || !mControlTrack)
275 return false;
276
277 bool cancel = false;
278
279 auto start =
280 mControlTrack->TimeToLongSamples(mT0 + mOuterFadeDownLen);
281 auto end =
282 mControlTrack->TimeToLongSamples(mT1 - mOuterFadeUpLen);
283
284 if (end <= start)
285 return false;
286
287 // the minimum number of samples we have to wait until the maximum
288 // pause has been exceeded
289 double maxPause = mMaximumPause;
290
291 // We don't fade in until we have time enough to actually fade out again
292 if (maxPause < mOuterFadeDownLen + mOuterFadeUpLen)
293 maxPause = mOuterFadeDownLen + mOuterFadeUpLen;
294
295 auto minSamplesPause =
296 mControlTrack->TimeToLongSamples(maxPause);
297
298 double threshold = DB_TO_LINEAR(mThresholdDb);
299
300 // adjust the threshold so we can compare it to the rmsSum value
301 threshold = threshold * threshold * kRMSWindowSize;
302
303 int rmsPos = 0;
304 double rmsSum = 0;
305 // to make the progress bar appear more natural, we first look for all
306 // duck regions and apply them all at once afterwards
307 std::vector<AutoDuckRegion> regions;
308 bool inDuckRegion = false;
309 {
310 Floats rmsWindow{ kRMSWindowSize, true };
311
312 Floats buf{ kBufSize };
313
314 // initialize the following two variables to prevent compiler warning
315 double duckRegionStart = 0;
316 sampleCount curSamplesPause = 0;
317
318 auto pos = start;
319
320 while (pos < end)
321 {
322 const auto len = limitSampleBufferSize( kBufSize, end - pos );
323
324 mControlTrack->GetFloats(buf.get(), pos, len);
325
326 for (auto i = pos; i < pos + len; i++)
327 {
328 rmsSum -= rmsWindow[rmsPos];
329 // i - pos is bounded by len:
330 auto index = ( i - pos ).as_size_t();
331 rmsWindow[rmsPos] = buf[ index ] * buf[ index ];
332 rmsSum += rmsWindow[rmsPos];
333 rmsPos = (rmsPos + 1) % kRMSWindowSize;
334
335 bool thresholdExceeded = rmsSum > threshold;
336
337 if (thresholdExceeded)
338 {
339 // everytime the threshold is exceeded, reset our count for
340 // the number of pause samples
341 curSamplesPause = 0;
342
343 if (!inDuckRegion)
344 {
345 // the threshold has been exceeded for the first time, so
346 // let the duck region begin here
347 inDuckRegion = true;
348 duckRegionStart = mControlTrack->LongSamplesToTime(i);
349 }
350 }
351
352 if (!thresholdExceeded && inDuckRegion)
353 {
354 // the threshold has not been exceeded and we are in a duck
355 // region, but only fade in if the maximum pause has been
356 // exceeded
357 curSamplesPause += 1;
358
359 if (curSamplesPause >= minSamplesPause)
360 {
361 // do the actual duck fade and reset all values
362 double duckRegionEnd =
363 mControlTrack->LongSamplesToTime(i - curSamplesPause);
364
365 regions.push_back(AutoDuckRegion(
366 duckRegionStart - mOuterFadeDownLen,
367 duckRegionEnd + mOuterFadeUpLen));
368
369 inDuckRegion = false;
370 }
371 }
372 }
373
374 pos += len;
375
376 if (TotalProgress(
377 (pos - start).as_double() /
378 (end - start).as_double() /
379 (GetNumWaveTracks() + 1)
380 ))
381 {
382 cancel = true;
383 break;
384 }
385 }
386
387 // apply last duck fade, if any
388 if (inDuckRegion)
389 {
390 double duckRegionEnd =
391 mControlTrack->LongSamplesToTime(end - curSamplesPause);
392 regions.push_back(AutoDuckRegion(
393 duckRegionStart - mOuterFadeDownLen,
394 duckRegionEnd + mOuterFadeUpLen));
395 }
396 }
397
398 if (!cancel)
399 {
400 CopyInputTracks(); // Set up mOutputTracks.
401
402 int trackNum = 0;
403
404 for( auto iterTrack : mOutputTracks->Selected< WaveTrack >() )
405 {
406 for (size_t i = 0; i < regions.size(); i++)
407 {
408 const AutoDuckRegion& region = regions[i];
409 if (ApplyDuckFade(trackNum, iterTrack, region.t0, region.t1))
410 {
411 cancel = true;
412 break;
413 }
414 }
415
416 if (cancel)
417 break;
418
419 trackNum++;
420 }
421 }
422
423 ReplaceProcessedTracks(!cancel);
424 return !cancel;
425 }
426
PopulateOrExchange(ShuttleGui & S)427 void EffectAutoDuck::PopulateOrExchange(ShuttleGui & S)
428 {
429 S.SetBorder(5);
430 S.StartVerticalLay(true);
431 {
432 S.AddSpace(0, 5);
433
434 mPanel = safenew EffectAutoDuckPanel(S.GetParent(), wxID_ANY, this);
435 S.AddWindow(mPanel);
436
437 S.AddSpace(0, 5);
438
439 S.StartMultiColumn(6, wxCENTER);
440 {
441 mDuckAmountDbBox = S.Validator<FloatingPointValidator<double>>(
442 1, &mDuckAmountDb, NumValidatorStyle::NO_TRAILING_ZEROES,
443 MIN_DuckAmountDb, MAX_DuckAmountDb
444 )
445 .NameSuffix(XO("db"))
446 .AddTextBox(XXO("Duck &amount:"), wxT(""), 10);
447 S.AddUnits(XO("dB"));
448
449 mMaximumPauseBox = S.Validator<FloatingPointValidator<double>>(
450 2, &mMaximumPause, NumValidatorStyle::NO_TRAILING_ZEROES,
451 MIN_MaximumPause, MAX_MaximumPause
452 )
453 .NameSuffix(XO("seconds"))
454 .AddTextBox(XXO("Ma&ximum pause:"), wxT(""), 10);
455 S.AddUnits(XO("seconds"));
456
457 mOuterFadeDownLenBox = S.Validator<FloatingPointValidator<double>>(
458 2, &mOuterFadeDownLen, NumValidatorStyle::NO_TRAILING_ZEROES,
459 MIN_OuterFadeDownLen, MAX_OuterFadeDownLen
460 )
461 .NameSuffix(XO("seconds"))
462 .AddTextBox(XXO("Outer fade &down length:"), wxT(""), 10);
463 S.AddUnits(XO("seconds"));
464
465 mOuterFadeUpLenBox = S.Validator<FloatingPointValidator<double>>(
466 2, &mOuterFadeUpLen, NumValidatorStyle::NO_TRAILING_ZEROES,
467 MIN_OuterFadeUpLen, MAX_OuterFadeUpLen
468 )
469 .NameSuffix(XO("seconds"))
470 .AddTextBox(XXO("Outer fade &up length:"), wxT(""), 10);
471 S.AddUnits(XO("seconds"));
472
473 mInnerFadeDownLenBox = S.Validator<FloatingPointValidator<double>>(
474 2, &mInnerFadeDownLen, NumValidatorStyle::NO_TRAILING_ZEROES,
475 MIN_InnerFadeDownLen, MAX_InnerFadeDownLen
476 )
477 .NameSuffix(XO("seconds"))
478 .AddTextBox(XXO("Inner fade d&own length:"), wxT(""), 10);
479 S.AddUnits(XO("seconds"));
480
481 mInnerFadeUpLenBox = S.Validator<FloatingPointValidator<double>>(
482 2, &mInnerFadeUpLen, NumValidatorStyle::NO_TRAILING_ZEROES,
483 MIN_InnerFadeUpLen, MAX_InnerFadeUpLen
484 )
485 .NameSuffix(XO("seconds"))
486 .AddTextBox(XXO("Inner &fade up length:"), wxT(""), 10);
487 S.AddUnits(XO("seconds"));
488 }
489 S.EndMultiColumn();
490
491 S.StartMultiColumn(3, wxCENTER);
492 {
493 mThresholdDbBox = S.Validator<FloatingPointValidator<double>>(
494 2, &mThresholdDb, NumValidatorStyle::NO_TRAILING_ZEROES,
495 MIN_ThresholdDb, MAX_ThresholdDb
496 )
497 .NameSuffix(XO("db"))
498 .AddTextBox(XXO("&Threshold:"), wxT(""), 10);
499 S.AddUnits(XO("dB"));
500 }
501 S.EndMultiColumn();
502
503 }
504 S.EndVerticalLay();
505
506 return;
507 }
508
TransferDataToWindow()509 bool EffectAutoDuck::TransferDataToWindow()
510 {
511 if (!mUIParent->TransferDataToWindow())
512 {
513 return false;
514 }
515
516 mPanel->Refresh(false);
517
518 return true;
519 }
520
TransferDataFromWindow()521 bool EffectAutoDuck::TransferDataFromWindow()
522 {
523 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
524 {
525 return false;
526 }
527
528 return true;
529 }
530
531 // EffectAutoDuck implementation
532
533 // this currently does an exponential fade
ApplyDuckFade(int trackNum,WaveTrack * t,double t0,double t1)534 bool EffectAutoDuck::ApplyDuckFade(int trackNum, WaveTrack* t,
535 double t0, double t1)
536 {
537 bool cancel = false;
538
539 auto start = t->TimeToLongSamples(t0);
540 auto end = t->TimeToLongSamples(t1);
541
542 Floats buf{ kBufSize };
543 auto pos = start;
544
545 auto fadeDownSamples = t->TimeToLongSamples(
546 mOuterFadeDownLen + mInnerFadeDownLen);
547 if (fadeDownSamples < 1)
548 fadeDownSamples = 1;
549
550 auto fadeUpSamples = t->TimeToLongSamples(
551 mOuterFadeUpLen + mInnerFadeUpLen);
552 if (fadeUpSamples < 1)
553 fadeUpSamples = 1;
554
555 float fadeDownStep = mDuckAmountDb / fadeDownSamples.as_double();
556 float fadeUpStep = mDuckAmountDb / fadeUpSamples.as_double();
557
558 while (pos < end)
559 {
560 const auto len = limitSampleBufferSize( kBufSize, end - pos );
561
562 t->GetFloats(buf.get(), pos, len);
563
564 for (auto i = pos; i < pos + len; i++)
565 {
566 float gainDown = fadeDownStep * (i - start).as_float();
567 float gainUp = fadeUpStep * (end - i).as_float();
568
569 float gain;
570 if (gainDown > gainUp)
571 gain = gainDown;
572 else
573 gain = gainUp;
574 if (gain < mDuckAmountDb)
575 gain = mDuckAmountDb;
576
577 // i - pos is bounded by len:
578 buf[ ( i - pos ).as_size_t() ] *= DB_TO_LINEAR(gain);
579 }
580
581 t->Set((samplePtr)buf.get(), floatSample, pos, len);
582
583 pos += len;
584
585 float curTime = t->LongSamplesToTime(pos);
586 float fractionFinished = (curTime - mT0) / (mT1 - mT0);
587 if (TotalProgress( (trackNum + 1 + fractionFinished) /
588 (GetNumWaveTracks() + 1) ))
589 {
590 cancel = true;
591 break;
592 }
593 }
594
595 return cancel;
596 }
597
OnValueChanged(wxCommandEvent & WXUNUSED (evt))598 void EffectAutoDuck::OnValueChanged(wxCommandEvent & WXUNUSED(evt))
599 {
600 mPanel->Refresh(false);
601 }
602
603 /*
604 * EffectAutoDuckPanel implementation
605 */
606
607 #define CONTROL_POINT_REGION 10 // pixel distance to click on a control point
608 #define CONTROL_POINT_MIN_MOVE 5 // min mouse move until value is changed
609
610 #define TEXT_DISTANCE 15 // pixel distance text <-> center of control point
611
612 #define FADE_DOWN_START 150 // x coordinate
613 #define FADE_UP_START 450 // x coordinate
614 #define DUCK_AMOUNT_START 50 // y coordinate
615
616 #define FADE_SCALE 40 // scale factor for second -> pixel conversion
617 #define DUCK_AMOUNT_SCALE 8 // scale factor for db -> pixel conversion
618
GetDistance(const wxPoint & first,const wxPoint & second)619 static int GetDistance(const wxPoint& first, const wxPoint& second)
620 {
621 int distanceX = abs(first.x - second.x);
622 int distanceY = abs(first.y - second.y);
623 if (distanceX > distanceY)
624 return distanceX;
625 else
626 return distanceY;
627 }
628
BEGIN_EVENT_TABLE(EffectAutoDuckPanel,wxPanelWrapper)629 BEGIN_EVENT_TABLE(EffectAutoDuckPanel, wxPanelWrapper)
630 EVT_PAINT(EffectAutoDuckPanel::OnPaint)
631 EVT_MOUSE_CAPTURE_CHANGED(EffectAutoDuckPanel::OnMouseCaptureChanged)
632 EVT_MOUSE_CAPTURE_LOST(EffectAutoDuckPanel::OnMouseCaptureLost)
633 EVT_LEFT_DOWN(EffectAutoDuckPanel::OnLeftDown)
634 EVT_LEFT_UP(EffectAutoDuckPanel::OnLeftUp)
635 EVT_MOTION(EffectAutoDuckPanel::OnMotion)
636 END_EVENT_TABLE()
637
638 EffectAutoDuckPanel::EffectAutoDuckPanel(
639 wxWindow *parent, wxWindowID winid, EffectAutoDuck *effect)
640 : wxPanelWrapper(parent, winid, wxDefaultPosition, wxSize(600, 300))
641 {
642 mParent = parent;
643 mEffect = effect;
644 mCurrentControlPoint = none;
645 mBackgroundBitmap = NULL;
646
647 ResetControlPoints();
648 }
649
~EffectAutoDuckPanel()650 EffectAutoDuckPanel::~EffectAutoDuckPanel()
651 {
652 if(HasCapture())
653 ReleaseMouse();
654 }
655
ResetControlPoints()656 void EffectAutoDuckPanel::ResetControlPoints()
657 {
658 mControlPoints[innerFadeDown] = wxPoint(-100,-100);
659 mControlPoints[innerFadeUp] = wxPoint(-100,-100);
660 mControlPoints[outerFadeDown] = wxPoint(-100,-100);
661 mControlPoints[outerFadeUp] = wxPoint(-100,-100);
662 mControlPoints[duckAmount] = wxPoint(-100,-100);
663 }
664
OnPaint(wxPaintEvent & WXUNUSED (evt))665 void EffectAutoDuckPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
666 {
667 int clientWidth, clientHeight;
668 GetSize(&clientWidth, &clientHeight);
669
670 if (!mBackgroundBitmap || mBackgroundBitmap->GetWidth() != clientWidth ||
671 mBackgroundBitmap->GetHeight() != clientHeight)
672 {
673 mBackgroundBitmap = std::make_unique<wxBitmap>(clientWidth, clientHeight,24);
674 }
675
676 wxMemoryDC dc;
677 dc.SelectObject(*mBackgroundBitmap);
678
679 dc.SetBrush(*wxWHITE_BRUSH);
680 dc.SetPen(*wxBLACK_PEN);
681 dc.DrawRectangle(0, 0, clientWidth, clientHeight);
682
683 dc.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
684 wxFONTWEIGHT_NORMAL));
685 dc.SetTextForeground(*wxBLACK);
686 dc.SetTextBackground(*wxWHITE);
687
688 double duckAmountDb = 0;
689 double innerFadeDownLen = 0;
690 double innerFadeUpLen = 0;
691 double outerFadeDownLen = 0;
692 double outerFadeUpLen = 0;
693 mEffect->mDuckAmountDbBox->GetValue().ToDouble(&duckAmountDb);
694 mEffect->mInnerFadeDownLenBox->GetValue().ToDouble(&innerFadeDownLen);
695 mEffect->mInnerFadeUpLenBox->GetValue().ToDouble(&innerFadeUpLen);
696 mEffect->mOuterFadeDownLenBox->GetValue().ToDouble(&outerFadeDownLen);
697 mEffect->mOuterFadeUpLenBox->GetValue().ToDouble(&outerFadeUpLen);
698
699 if (innerFadeDownLen < MIN_InnerFadeDownLen || innerFadeDownLen > MAX_InnerFadeDownLen ||
700 innerFadeUpLen < MIN_InnerFadeUpLen || innerFadeUpLen > MAX_InnerFadeUpLen ||
701 outerFadeDownLen < MIN_OuterFadeDownLen || outerFadeDownLen > MAX_OuterFadeDownLen ||
702 outerFadeUpLen < MIN_OuterFadeUpLen || outerFadeUpLen > MAX_OuterFadeUpLen ||
703 duckAmountDb < MIN_DuckAmountDb || duckAmountDb > MAX_DuckAmountDb)
704 {
705 // values are out of range, no preview available
706 wxString message = _("Preview not available");
707 int textWidth = 0, textHeight = 0;
708 dc.GetTextExtent(message, &textWidth, &textHeight);
709 dc.DrawText(message, (clientWidth - textWidth) / 2,
710 (clientHeight - textHeight) / 2);
711
712 ResetControlPoints();
713 } else
714 {
715 // draw preview
716 dc.SetBrush(*wxTRANSPARENT_BRUSH);
717 dc.SetPen(wxPen(theTheme.Colour(clrGraphLines), 3, wxPENSTYLE_SOLID));
718
719 wxPoint points[6];
720
721 points[0].x = 10;
722 points[0].y = DUCK_AMOUNT_START;
723
724 points[1].x = FADE_DOWN_START - (int)(outerFadeDownLen * FADE_SCALE);
725 points[1].y = DUCK_AMOUNT_START;
726
727 points[2].x = FADE_DOWN_START + (int)(innerFadeDownLen * FADE_SCALE);
728 points[2].y = DUCK_AMOUNT_START -
729 (int)(duckAmountDb * DUCK_AMOUNT_SCALE);
730
731 points[3].x = FADE_UP_START - (int)(innerFadeUpLen * FADE_SCALE);
732 points[3].y = DUCK_AMOUNT_START -
733 (int)(duckAmountDb * DUCK_AMOUNT_SCALE);
734
735 points[4].x = FADE_UP_START + (int)(outerFadeUpLen * FADE_SCALE);
736 points[4].y = DUCK_AMOUNT_START;
737
738 points[5].x = clientWidth - 10;
739 points[5].y = DUCK_AMOUNT_START;
740
741 AColor::Lines(dc, 6, points);
742
743 dc.SetPen(wxPen(*wxBLACK, 1, wxPENSTYLE_DOT));
744
745 AColor::Line(dc, FADE_DOWN_START, 10, FADE_DOWN_START, clientHeight - 10);
746 AColor::Line(dc, FADE_UP_START, 10, FADE_UP_START, clientHeight - 10);
747
748 dc.SetPen(AColor::envelopePen);
749 dc.SetBrush(*wxWHITE_BRUSH);
750
751 mControlPoints[outerFadeDown] = points[1];
752 mControlPoints[innerFadeDown] = points[2];
753 mControlPoints[innerFadeUp] = points[3];
754 mControlPoints[outerFadeUp] = points[4];
755 mControlPoints[duckAmount] = wxPoint(
756 (points[2].x + points[3].x) / 2, points[2].y);
757
758 for (int i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++)
759 {
760 EControlPoint cp = (EControlPoint)i;
761 int digits;
762 float value;
763
764 if (cp == innerFadeDown)
765 {
766 value = innerFadeDownLen;
767 digits = 2;
768 }
769 else if (cp == innerFadeUp)
770 {
771 value = innerFadeUpLen;
772 digits = 2;
773 }
774 else if (cp == outerFadeDown)
775 {
776 value = outerFadeDownLen;
777 digits = 2;
778 } else if (cp == outerFadeUp)
779 {
780 value = outerFadeUpLen;
781 digits = 2;
782 }
783 else
784 {
785 value = duckAmountDb;
786 digits = 1;
787 }
788
789 wxString valueStr = Internat::ToDisplayString(value, digits);
790 valueStr += wxT(" ");
791
792 if (cp == duckAmount)
793 /* i18n-hint: short form of 'decibels'.*/
794 valueStr += _("dB");
795 else
796 /* i18n-hint: short form of 'seconds'.*/
797 valueStr += _("s");
798
799 int textWidth = 0, textHeight = 0;
800 GetTextExtent(valueStr, &textWidth, &textHeight);
801
802 int textPosX = mControlPoints[i].x - textWidth / 2;
803 int textPosY = mControlPoints[i].y;
804
805 if (cp == duckAmount || cp == outerFadeDown || cp == outerFadeUp)
806 textPosY -= TEXT_DISTANCE + textHeight;
807 else
808 textPosY += TEXT_DISTANCE;
809
810 dc.DrawText(valueStr, textPosX, textPosY);
811
812 dc.DrawEllipse(mControlPoints[i].x - 3,
813 mControlPoints[i].y - 3, 6, 6);
814 }
815 }
816
817 // copy background buffer to paint dc
818 wxPaintDC paintDC(this);
819 paintDC.Blit(0, 0, clientWidth, clientHeight, &dc, 0, 0);
820
821 // clean up: necessary to free resources on Windows
822 dc.SetPen(wxNullPen);
823 dc.SetBrush(wxNullBrush);
824 dc.SetFont(wxNullFont);
825 dc.SelectObject(wxNullBitmap);
826 }
827
OnMouseCaptureChanged(wxMouseCaptureChangedEvent & WXUNUSED (evt))828 void EffectAutoDuckPanel::OnMouseCaptureChanged(
829 wxMouseCaptureChangedEvent & WXUNUSED(evt))
830 {
831 SetCursor(wxNullCursor);
832 mCurrentControlPoint = none;
833 }
834
OnMouseCaptureLost(wxMouseCaptureLostEvent & WXUNUSED (evt))835 void EffectAutoDuckPanel::OnMouseCaptureLost(
836 wxMouseCaptureLostEvent & WXUNUSED(evt))
837 {
838 mCurrentControlPoint = none;
839
840 if (HasCapture())
841 {
842 ReleaseMouse();
843 }
844 }
845
846 EffectAutoDuckPanel::EControlPoint
GetNearestControlPoint(const wxPoint & pt)847 EffectAutoDuckPanel::GetNearestControlPoint(const wxPoint & pt)
848 {
849 int dist[AUTO_DUCK_PANEL_NUM_CONTROL_POINTS];
850 int i;
851
852 for (i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++)
853 dist[i] = GetDistance(pt, mControlPoints[i]);
854
855 int curMinimum = 0;
856 for (i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++)
857 if (dist[i] < dist[curMinimum])
858 curMinimum = i;
859
860 if (dist[curMinimum] <= CONTROL_POINT_REGION)
861 return (EControlPoint)curMinimum;
862 else
863 return none;
864 }
865
OnLeftDown(wxMouseEvent & evt)866 void EffectAutoDuckPanel::OnLeftDown(wxMouseEvent & evt)
867 {
868 EControlPoint nearest = GetNearestControlPoint(evt.GetPosition());
869
870 if (nearest != none)
871 {
872 // this control point has been clicked
873 mMouseDownPoint = evt.GetPosition();
874
875 mCurrentControlPoint = nearest;
876 mControlPointMoveActivated = false;
877
878 for (int i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++)
879 mMoveStartControlPoints[i] = mControlPoints[i];
880
881 if( !HasCapture() )
882 CaptureMouse();
883 }
884 }
885
OnLeftUp(wxMouseEvent & WXUNUSED (evt))886 void EffectAutoDuckPanel::OnLeftUp(wxMouseEvent & WXUNUSED(evt))
887 {
888 if (mCurrentControlPoint != none)
889 {
890 mCurrentControlPoint = none;
891 ReleaseMouse();
892 }
893 }
894
OnMotion(wxMouseEvent & evt)895 void EffectAutoDuckPanel::OnMotion(wxMouseEvent & evt)
896 {
897 switch (GetNearestControlPoint(evt.GetPosition()))
898 {
899 case none:
900 SetCursor(wxNullCursor);
901 break;
902 case innerFadeDown:
903 case innerFadeUp:
904 case outerFadeDown:
905 case outerFadeUp:
906 SetCursor(wxCursor(wxCURSOR_SIZEWE));
907 break;
908 case duckAmount:
909 SetCursor(wxCursor(wxCURSOR_SIZENS));
910 break;
911 }
912
913 if (mCurrentControlPoint != none)
914 {
915 if (!mControlPointMoveActivated)
916 {
917 int dist;
918
919 if (mCurrentControlPoint == duckAmount)
920 dist = abs(evt.GetY() - mMouseDownPoint.y);
921 else
922 dist = abs(evt.GetX() - mMouseDownPoint.x);
923
924 if (dist >= CONTROL_POINT_MIN_MOVE)
925 mControlPointMoveActivated = true;
926 }
927
928 if (mControlPointMoveActivated)
929 {
930 float newValue;
931
932 switch (mCurrentControlPoint)
933 {
934 case outerFadeDown:
935 newValue = ((double)(FADE_DOWN_START - evt.GetX())) / FADE_SCALE;
936 mEffect->mOuterFadeDownLen = TrapDouble(newValue, MIN_OuterFadeDownLen, MAX_OuterFadeDownLen);
937 break;
938 case outerFadeUp:
939 newValue = ((double)(evt.GetX() - FADE_UP_START)) / FADE_SCALE;
940 mEffect->mOuterFadeUpLen = TrapDouble(newValue, MIN_OuterFadeUpLen, MAX_OuterFadeUpLen);
941 break;
942 case innerFadeDown:
943 newValue = ((double)(evt.GetX() - FADE_DOWN_START)) / FADE_SCALE;
944 mEffect->mInnerFadeDownLen = TrapDouble(newValue, MIN_InnerFadeDownLen, MAX_InnerFadeDownLen);
945 break;
946 case innerFadeUp:
947 newValue = ((double)(FADE_UP_START - evt.GetX())) / FADE_SCALE;
948 mEffect->mInnerFadeUpLen = TrapDouble(newValue, MIN_InnerFadeUpLen, MAX_InnerFadeUpLen);
949 break;
950 case duckAmount:
951 newValue = ((double)(DUCK_AMOUNT_START - evt.GetY())) / DUCK_AMOUNT_SCALE;
952 mEffect->mDuckAmountDb = TrapDouble(newValue, MIN_DuckAmountDb, MAX_DuckAmountDb);
953 break;
954 case none:
955 wxASSERT(false); // should not happen
956 }
957 mEffect->TransferDataToWindow();
958 Refresh(false);
959 }
960 }
961 }
962