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