1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   Compressor.cpp
6 
7   Dominic Mazzoni
8   Martyn Shaw
9   Steve Jolly
10 
11 *******************************************************************//**
12 
13 \class EffectCompressor
14 \brief An Effect derived from EffectTwoPassSimpleMono
15 
16  - Martyn Shaw made it inherit from EffectTwoPassSimpleMono 10/2005.
17  - Steve Jolly made it inherit from EffectSimpleMono.
18  - GUI added and implementation improved by Dominic Mazzoni, 5/11/2003.
19 
20 *//****************************************************************//**
21 
22 \class CompressorPanel
23 \brief Panel used within the EffectCompressor for EffectCompressor.
24 
25 *//*******************************************************************/
26 
27 
28 #include "Compressor.h"
29 #include "LoadEffects.h"
30 
31 #include <math.h>
32 
33 #include <wx/brush.h>
34 #include <wx/checkbox.h>
35 #include <wx/dcclient.h>
36 #include <wx/intl.h>
37 #include <wx/slider.h>
38 #include <wx/stattext.h>
39 
40 #include "AColor.h"
41 #include "Prefs.h"
42 #include "../Shuttle.h"
43 #include "../ShuttleGui.h"
44 #include "Theme.h"
45 #include "float_cast.h"
46 #include "../widgets/Ruler.h"
47 
48 #include "../WaveTrack.h"
49 #include "AllThemeResources.h"
50 
51 enum
52 {
53    ID_Threshold = 10000,
54    ID_NoiseFloor,
55    ID_Ratio,
56    ID_Attack,
57    ID_Decay
58 };
59 
60 // Define keys, defaults, minimums, and maximums for the effect parameters
61 //
62 //     Name          Type     Key                  Def      Min      Max      Scale
63 Param( Threshold,    double,  wxT("Threshold"),     -12.0,   -60.0,   -1.0,    1   );
64 Param( NoiseFloor,   double,  wxT("NoiseFloor"),    -40.0,   -80.0,   -20.0,   0.2   );
65 Param( Ratio,        double,  wxT("Ratio"),         2.0,     1.1,     10.0,    10  );
66 Param( AttackTime,   double,  wxT("AttackTime"),    0.2,     0.1,     5.0,     100 );
67 Param( ReleaseTime,  double,  wxT("ReleaseTime"),   1.0,     1.0,     30.0,    10  );
68 Param( Normalize,    bool,    wxT("Normalize"),     true,    false,   true,    1   );
69 Param( UsePeak,      bool,    wxT("UsePeak"),       false,   false,   true,    1   );
70 
71 //----------------------------------------------------------------------------
72 // EffectCompressor
73 //----------------------------------------------------------------------------
74 
75 const ComponentInterfaceSymbol EffectCompressor::Symbol
76 { XO("Compressor") };
77 
78 namespace{ BuiltinEffectsModule::Registration< EffectCompressor > reg; }
79 
BEGIN_EVENT_TABLE(EffectCompressor,wxEvtHandler)80 BEGIN_EVENT_TABLE(EffectCompressor, wxEvtHandler)
81    EVT_SLIDER(wxID_ANY, EffectCompressor::OnSlider)
82 END_EVENT_TABLE()
83 
84 EffectCompressor::EffectCompressor()
85 {
86    mThresholdDB = DEF_Threshold;
87    mNoiseFloorDB = DEF_NoiseFloor;
88    mAttackTime = DEF_AttackTime;          // seconds
89    mDecayTime = DEF_ReleaseTime;          // seconds
90    mRatio = DEF_Ratio;                    // positive number > 1.0
91    mNormalize = DEF_Normalize;
92    mUsePeak = DEF_UsePeak;
93 
94    mThreshold = 0.25;
95    mNoiseFloor = 0.01;
96    mCompression = 0.5;
97    mFollowLen = 0;
98 
99    SetLinearEffectFlag(false);
100 }
101 
~EffectCompressor()102 EffectCompressor::~EffectCompressor()
103 {
104 }
105 
106 // ComponentInterface implementation
107 
GetSymbol()108 ComponentInterfaceSymbol EffectCompressor::GetSymbol()
109 {
110    return Symbol;
111 }
112 
GetDescription()113 TranslatableString EffectCompressor::GetDescription()
114 {
115    return XO("Compresses the dynamic range of audio");
116 }
117 
ManualPage()118 ManualPageID EffectCompressor::ManualPage()
119 {
120    return L"Compressor";
121 }
122 
123 // EffectDefinitionInterface implementation
124 
GetType()125 EffectType EffectCompressor::GetType()
126 {
127    return EffectTypeProcess;
128 }
129 
130 // EffectClientInterface implementation
DefineParams(ShuttleParams & S)131 bool EffectCompressor::DefineParams( ShuttleParams & S ){
132    S.SHUTTLE_PARAM( mThresholdDB, Threshold );
133    S.SHUTTLE_PARAM( mNoiseFloorDB, NoiseFloor );
134    S.SHUTTLE_PARAM( mRatio, Ratio);
135    S.SHUTTLE_PARAM( mAttackTime, AttackTime);
136    S.SHUTTLE_PARAM( mDecayTime, ReleaseTime);
137    S.SHUTTLE_PARAM( mNormalize, Normalize);
138    S.SHUTTLE_PARAM( mUsePeak, UsePeak);
139    return true;
140 }
141 
GetAutomationParameters(CommandParameters & parms)142 bool EffectCompressor::GetAutomationParameters(CommandParameters & parms)
143 {
144    parms.Write(KEY_Threshold, mThresholdDB);
145    parms.Write(KEY_NoiseFloor, mNoiseFloorDB);
146    parms.Write(KEY_Ratio, mRatio);
147    parms.Write(KEY_AttackTime, mAttackTime);
148    parms.Write(KEY_ReleaseTime, mDecayTime);
149    parms.Write(KEY_Normalize, mNormalize);
150    parms.Write(KEY_UsePeak, mUsePeak);
151 
152    return true;
153 }
154 
SetAutomationParameters(CommandParameters & parms)155 bool EffectCompressor::SetAutomationParameters(CommandParameters & parms)
156 {
157    ReadAndVerifyDouble(Threshold);
158    ReadAndVerifyDouble(NoiseFloor);
159    ReadAndVerifyDouble(Ratio);
160    ReadAndVerifyDouble(AttackTime);
161    ReadAndVerifyDouble(ReleaseTime);
162    ReadAndVerifyBool(Normalize);
163    ReadAndVerifyBool(UsePeak);
164 
165    mThresholdDB = Threshold;
166    mNoiseFloorDB = NoiseFloor;
167    mRatio = Ratio;
168    mAttackTime = AttackTime;
169    mDecayTime = ReleaseTime;
170    mNormalize = Normalize;
171    mUsePeak = UsePeak;
172 
173    return true;
174 }
175 
176 // Effect Implementation
177 
Startup()178 bool EffectCompressor::Startup()
179 {
180    wxString base = wxT("/Effects/Compressor/");
181 
182    // Migrate settings from 2.1.0 or before
183 
184    // Already migrated, so bail
185    if (gPrefs->Exists(base + wxT("Migrated")))
186    {
187       return true;
188    }
189 
190    // Load the old "current" settings
191    if (gPrefs->Exists(base))
192    {
193       gPrefs->Read(base + wxT("ThresholdDB"), &mThresholdDB, -12.0f );
194       gPrefs->Read(base + wxT("NoiseFloorDB"), &mNoiseFloorDB, -40.0f );
195       gPrefs->Read(base + wxT("Ratio"), &mRatio, 2.0f );
196       gPrefs->Read(base + wxT("AttackTime"), &mAttackTime, 0.2f );
197       gPrefs->Read(base + wxT("DecayTime"), &mDecayTime, 1.0f );
198       gPrefs->Read(base + wxT("Normalize"), &mNormalize, true );
199       gPrefs->Read(base + wxT("UsePeak"), &mUsePeak, false );
200 
201       SaveUserPreset(GetCurrentSettingsGroup());
202 
203       // Do not migrate again
204       gPrefs->Write(base + wxT("Migrated"), true);
205       gPrefs->Flush();
206    }
207 
208    return true;
209 }
210 
211 namespace {
212 
ThresholdFormat(int value)213 TranslatableString ThresholdFormat( int value )
214    /* i18n-hint: usually leave this as is as dB doesn't get translated*/
215 { return XO("%3d dB").Format(value); }
216 
AttackTimeFormat(double value)217 TranslatableString AttackTimeFormat( double value )
218 { return XO("%.2f secs").Format( value ); }
219 
DecayTimeFormat(double value)220 TranslatableString DecayTimeFormat( double value )
221 { return XO("%.1f secs").Format( value ); }
222 
RatioTextFormat(int sliderValue,double value)223 TranslatableString RatioTextFormat( int sliderValue, double value )
224 {
225    auto format = (sliderValue % 10 == 0)
226       /* i18n-hint: Unless your language has a different convention for ratios,
227        * like 8:1, leave as is.*/
228       ? XO("%.0f:1")
229       /* i18n-hint: Unless your language has a different convention for ratios,
230        * like 8:1, leave as is.*/
231       : XO("%.1f:1");
232    return format.Format( value );
233 }
234 
RatioLabelFormat(int sliderValue,double value)235 TranslatableString RatioLabelFormat( int sliderValue, double value )
236 {
237    auto format = (sliderValue % 10 == 0)
238       ? XO("Ratio %.0f to 1")
239       : XO("Ratio %.1f to 1");
240    return format.Format( value );
241 }
242 
243 }
244 
PopulateOrExchange(ShuttleGui & S)245 void EffectCompressor::PopulateOrExchange(ShuttleGui & S)
246 {
247    S.SetBorder(5);
248 
249    S.StartHorizontalLay(wxEXPAND, true);
250    {
251       S.SetBorder(10);
252       mPanel = safenew EffectCompressorPanel(S.GetParent(), wxID_ANY,
253                                          mThresholdDB,
254                                          mNoiseFloorDB,
255                                          mRatio);
256       S.Prop(true)
257          .Position(wxEXPAND | wxALL)
258          .MinSize( { 400, 200 } )
259          .AddWindow(mPanel);
260       S.SetBorder(5);
261    }
262    S.EndHorizontalLay();
263 
264    S.StartStatic( {} );
265    {
266       S.StartMultiColumn(3, wxEXPAND);
267       {
268          S.SetStretchyCol(1);
269          mThresholdLabel = S.AddVariableText(XO("&Threshold:"), true,
270             wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
271          mThresholdSlider = S.Id(ID_Threshold)
272             .Name(XO("Threshold"))
273             .Style(wxSL_HORIZONTAL)
274             .AddSlider( {},
275                DEF_Threshold * SCL_Threshold,
276                MAX_Threshold * SCL_Threshold,
277                MIN_Threshold * SCL_Threshold);
278          mThresholdText = S.AddVariableText(ThresholdFormat(999), true,
279             wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
280 
281          mNoiseFloorLabel = S.AddVariableText(XO("&Noise Floor:"), true,
282             wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
283          mNoiseFloorSlider = S.Id(ID_NoiseFloor)
284             .Name(XO("Noise Floor"))
285             .Style(wxSL_HORIZONTAL)
286             .AddSlider( {},
287                DEF_NoiseFloor * SCL_NoiseFloor,
288                MAX_NoiseFloor * SCL_NoiseFloor,
289                MIN_NoiseFloor * SCL_NoiseFloor);
290          mNoiseFloorText = S.AddVariableText(ThresholdFormat(999),
291             true, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
292 
293          mRatioLabel = S.AddVariableText(XO("&Ratio:"), true,
294             wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
295          mRatioSlider = S.Id(ID_Ratio)
296             .Name(XO("Ratio"))
297             .Style(wxSL_HORIZONTAL)
298             .AddSlider( {},
299                DEF_Ratio * SCL_Ratio,
300                MAX_Ratio * SCL_Ratio,
301                MIN_Ratio * SCL_Ratio);
302          mRatioSlider->SetPageSize(5);
303          mRatioText = S.AddVariableText(RatioTextFormat( 1, 99.9 ), true,
304             wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
305 
306          /* i18n-hint: Particularly in percussion, sounds can be regarded as having
307           * an 'attack' phase where the sound builds up and a 'decay' where the
308           * sound dies away.  So this means 'onset duration'.  */
309          mAttackLabel = S.AddVariableText(XO("&Attack Time:"), true,
310             wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
311          mAttackSlider = S.Id(ID_Attack)
312          /* i18n-hint: Particularly in percussion, sounds can be regarded as having
313           * an 'attack' phase where the sound builds up and a 'decay' where the
314           * sound dies away.  So this means 'onset duration'.  */
315             .Name(XO("Attack Time"))
316             .Style(wxSL_HORIZONTAL)
317             .AddSlider( {},
318                DEF_AttackTime * SCL_AttackTime,
319                MAX_AttackTime * SCL_AttackTime,
320                MIN_AttackTime * SCL_AttackTime);
321          mAttackText = S.AddVariableText(
322             AttackTimeFormat(9.99),
323             true, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
324 
325          /* i18n-hint: Particularly in percussion, sounds can be regarded as having
326           * an 'attack' phase where the sound builds up and a 'decay' or 'release' where the
327           * sound dies away.  */
328          mDecayLabel = S.AddVariableText(XO("R&elease Time:"), true,
329             wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
330          mDecaySlider = S.Id(ID_Decay)
331          /* i18n-hint: Particularly in percussion, sounds can be regarded as having
332           * an 'attack' phase where the sound builds up and a 'decay' or 'release' where the
333           * sound dies away.  */
334             .Name(XO("Release Time"))
335             .Style(wxSL_HORIZONTAL)
336             .AddSlider( {},
337                DEF_ReleaseTime * SCL_ReleaseTime,
338                MAX_ReleaseTime * SCL_ReleaseTime,
339                MIN_ReleaseTime * SCL_ReleaseTime);
340 
341          mDecayText = S.AddVariableText(
342             DecayTimeFormat(99.9),
343             true, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
344       }
345       S.EndMultiColumn();
346    }
347    S.EndStatic();
348 
349    S.StartHorizontalLay(wxCENTER, false);
350    {
351       /* i18n-hint: Make-up, i.e. correct for any reduction, rather than fabricate it.*/
352       mGainCheckBox = S.AddCheckBox(XXO("Ma&ke-up gain for 0 dB after compressing"),
353                                     DEF_Normalize);
354       /* i18n-hint: "Compress" here means reduce variations of sound volume,
355        NOT related to file-size compression; Peaks means extremes in volume */
356       mPeakCheckBox = S.AddCheckBox(XXO("C&ompress based on Peaks"),
357                                     DEF_UsePeak);
358    }
359    S.EndHorizontalLay();
360 }
361 
TransferDataToWindow()362 bool EffectCompressor::TransferDataToWindow()
363 {
364    mThresholdSlider->SetValue(lrint(mThresholdDB));
365    mNoiseFloorSlider->SetValue(lrint(mNoiseFloorDB * SCL_NoiseFloor));
366    mRatioSlider->SetValue(lrint(mRatio * SCL_Ratio));
367    mAttackSlider->SetValue(lrint(mAttackTime * SCL_AttackTime));
368    mDecaySlider->SetValue(lrint(mDecayTime * SCL_ReleaseTime));
369    mGainCheckBox->SetValue(mNormalize);
370    mPeakCheckBox->SetValue(mUsePeak);
371 
372    UpdateUI();
373 
374    return true;
375 }
376 
TransferDataFromWindow()377 bool EffectCompressor::TransferDataFromWindow()
378 {
379    if (!mUIParent->Validate())
380    {
381       return false;
382    }
383 
384    mThresholdDB = (double) mThresholdSlider->GetValue();
385    mNoiseFloorDB = (double) mNoiseFloorSlider->GetValue() / SCL_NoiseFloor;
386    mRatio = (double) mRatioSlider->GetValue() / SCL_Ratio;
387    mAttackTime = (double) mAttackSlider->GetValue() / 100.0; //SCL_AttackTime;
388    mDecayTime = (double) mDecaySlider->GetValue() / SCL_ReleaseTime;
389    mNormalize = mGainCheckBox->GetValue();
390    mUsePeak = mPeakCheckBox->GetValue();
391 
392    return true;
393 }
394 
395 // EffectTwoPassSimpleMono implementation
396 
NewTrackPass1()397 bool EffectCompressor::NewTrackPass1()
398 {
399    mThreshold = DB_TO_LINEAR(mThresholdDB);
400    mNoiseFloor = DB_TO_LINEAR(mNoiseFloorDB);
401    mNoiseCounter = 100;
402 
403    mAttackInverseFactor = exp(log(mThreshold) / (mCurRate * mAttackTime + 0.5));
404    mAttackFactor = 1.0 / mAttackInverseFactor;
405    mDecayFactor = exp(log(mThreshold) / (mCurRate * mDecayTime + 0.5));
406 
407    if(mRatio > 1)
408       mCompression = 1.0-1.0/mRatio;
409    else
410       mCompression = 0.0;
411 
412    mLastLevel = mThreshold;
413 
414    mCircleSize = 100;
415    mCircle.reinit( mCircleSize, true );
416    mCirclePos = 0;
417    mRMSSum = 0.0;
418 
419    return true;
420 }
421 
InitPass1()422 bool EffectCompressor::InitPass1()
423 {
424    mMax=0.0;
425    if (!mNormalize)
426       DisableSecondPass();
427 
428    // Find the maximum block length required for any track
429    size_t maxlen = inputTracks()->Selected< const WaveTrack >().max(
430       &WaveTrack::GetMaxBlockSize
431    );
432    mFollow1.reset();
433    mFollow2.reset();
434    // Allocate buffers for the envelope
435    if(maxlen > 0) {
436       mFollow1.reinit(maxlen);
437       mFollow2.reinit(maxlen);
438    }
439    mFollowLen = maxlen;
440 
441    return true;
442 }
443 
InitPass2()444 bool EffectCompressor::InitPass2()
445 {
446    // Actually, this should not even be called, because we call
447    // DisableSecondPass() before, if mNormalize is false.
448    return mNormalize;
449 }
450 
451 // Process the input with 2 buffers available at a time
452 // buffer1 will be written upon return
453 // buffer2 will be passed as buffer1 on the next call
TwoBufferProcessPass1(float * buffer1,size_t len1,float * buffer2,size_t len2)454 bool EffectCompressor::TwoBufferProcessPass1
455    (float *buffer1, size_t len1, float *buffer2, size_t len2)
456 {
457    // If buffers are bigger than allocated, then abort
458    // (this should never happen, but if it does, we don't want to crash)
459    if((len1 > mFollowLen) || (len2 > mFollowLen))
460       return false;
461 
462    // This makes sure that the initial value is well-chosen
463    // buffer1 == NULL on the first and only the first call
464    if (buffer1 == NULL) {
465       // Initialize the mLastLevel to the peak level in the first buffer
466       // This avoids problems with large spike events near the beginning of the track
467       mLastLevel = mThreshold;
468       for(size_t i=0; i<len2; i++) {
469          if(mLastLevel < fabs(buffer2[i]))
470             mLastLevel = fabs(buffer2[i]);
471       }
472    }
473 
474    // buffer2 is NULL on the last and only the last call
475    if(buffer2 != NULL) {
476       Follow(buffer2, mFollow2.get(), len2, mFollow1.get(), len1);
477    }
478 
479    if(buffer1 != NULL) {
480       for (size_t i = 0; i < len1; i++) {
481          buffer1[i] = DoCompression(buffer1[i], mFollow1[i]);
482       }
483    }
484 
485 
486 #if 0
487    // Copy the envelope over the track data (for debug purposes)
488    memcpy(buffer1, mFollow1, len1*sizeof(float));
489 #endif
490 
491    // Rotate the buffer pointers
492    mFollow1.swap(mFollow2);
493 
494    return true;
495 }
496 
ProcessPass2(float * buffer,size_t len)497 bool EffectCompressor::ProcessPass2(float *buffer, size_t len)
498 {
499    if (mMax != 0)
500    {
501       for (size_t i = 0; i < len; i++)
502          buffer[i] /= mMax;
503    }
504 
505    return true;
506 }
507 
FreshenCircle()508 void EffectCompressor::FreshenCircle()
509 {
510    // Recompute the RMS sum periodically to prevent accumulation of rounding errors
511    // during long waveforms
512    mRMSSum = 0;
513    for(size_t i=0; i<mCircleSize; i++)
514       mRMSSum += mCircle[i];
515 }
516 
AvgCircle(float value)517 float EffectCompressor::AvgCircle(float value)
518 {
519    float level;
520 
521    // Calculate current level from root-mean-squared of
522    // circular buffer ("RMS")
523    mRMSSum -= mCircle[mCirclePos];
524    mCircle[mCirclePos] = value*value;
525    mRMSSum += mCircle[mCirclePos];
526    level = sqrt(mRMSSum/mCircleSize);
527    mCirclePos = (mCirclePos+1)%mCircleSize;
528 
529    return level;
530 }
531 
Follow(float * buffer,float * env,size_t len,float * previous,size_t previous_len)532 void EffectCompressor::Follow(float *buffer, float *env, size_t len, float *previous, size_t previous_len)
533 {
534    /*
535 
536    "Follow"ing algorithm by Roger B. Dannenberg, taken from
537    Nyquist.  His description follows.  -DMM
538 
539    Description: this is a sophisticated envelope follower.
540     The input is an envelope, e.g. something produced with
541     the AVG function. The purpose of this function is to
542     generate a smooth envelope that is generally not less
543     than the input signal. In other words, we want to "ride"
544     the peaks of the signal with a smooth function. The
545     algorithm is as follows: keep a current output value
546     (called the "value"). The value is allowed to increase
547     by at most rise_factor and decrease by at most fall_factor.
548     Therefore, the next value should be between
549     value * rise_factor and value * fall_factor. If the input
550     is in this range, then the next value is simply the input.
551     If the input is less than value * fall_factor, then the
552     next value is just value * fall_factor, which will be greater
553     than the input signal. If the input is greater than value *
554     rise_factor, then we compute a rising envelope that meets
555     the input value by working bacwards in time, changing the
556     previous values to input / rise_factor, input / rise_factor^2,
557     input / rise_factor^3, etc. until this NEW envelope intersects
558     the previously computed values. There is only a limited buffer
559     in which we can work backwards, so if the NEW envelope does not
560     intersect the old one, then make yet another pass, this time
561     from the oldest buffered value forward, increasing on each
562     sample by rise_factor to produce a maximal envelope. This will
563     still be less than the input.
564 
565     The value has a lower limit of floor to make sure value has a
566     reasonable positive value from which to begin an attack.
567    */
568    double level,last;
569 
570    if(!mUsePeak) {
571       // Update RMS sum directly from the circle buffer
572       // to avoid accumulation of rounding errors
573       FreshenCircle();
574    }
575    // First apply a peak detect with the requested decay rate
576    last = mLastLevel;
577    for(size_t i=0; i<len; i++) {
578       if(mUsePeak)
579          level = fabs(buffer[i]);
580       else // use RMS
581          level = AvgCircle(buffer[i]);
582       // Don't increase gain when signal is continuously below the noise floor
583       if(level < mNoiseFloor) {
584          mNoiseCounter++;
585       } else {
586          mNoiseCounter = 0;
587       }
588       if(mNoiseCounter < 100) {
589          last *= mDecayFactor;
590          if(last < mThreshold)
591             last = mThreshold;
592          if(level > last)
593             last = level;
594       }
595       env[i] = last;
596    }
597    mLastLevel = last;
598 
599    // Next do the same process in reverse direction to get the requested attack rate
600    last = mLastLevel;
601    for(size_t i = len; i--;) {
602       last *= mAttackInverseFactor;
603       if(last < mThreshold)
604          last = mThreshold;
605       if(env[i] < last)
606          env[i] = last;
607       else
608          last = env[i];
609    }
610 
611    if((previous != NULL) && (previous_len > 0)) {
612       // If the previous envelope was passed, propagate the rise back until we intersect
613       for(size_t i = previous_len; i--;) {
614          last *= mAttackInverseFactor;
615          if(last < mThreshold)
616             last = mThreshold;
617          if(previous[i] < last)
618             previous[i] = last;
619          else // Intersected the previous envelope buffer, so we are finished
620             return;
621       }
622       // If we can't back up far enough, project the starting level forward
623       // until we intersect the desired envelope
624       last = previous[0];
625       for(size_t i=1; i<previous_len; i++) {
626          last *= mAttackFactor;
627          if(previous[i] > last)
628             previous[i] = last;
629          else // Intersected the desired envelope, so we are finished
630             return;
631       }
632       // If we still didn't intersect, then continue ramp up into current buffer
633       for(size_t i=0; i<len; i++) {
634          last *= mAttackFactor;
635          if(buffer[i] > last)
636             buffer[i] = last;
637          else // Finally got an intersect
638             return;
639       }
640       // If we still didn't intersect, then reset mLastLevel
641       mLastLevel = last;
642    }
643 }
644 
DoCompression(float value,double env)645 float EffectCompressor::DoCompression(float value, double env)
646 {
647    float out;
648    if(mUsePeak) {
649       // Peak values map 1.0 to 1.0 - 'upward' compression
650       out = value * pow(1.0/env, mCompression);
651    } else {
652       // With RMS-based compression don't change values below mThreshold - 'downward' compression
653       out = value * pow(mThreshold/env, mCompression);
654    }
655 
656    // Retain the maximum value for use in the normalization pass
657    if(mMax < fabs(out))
658       mMax = fabs(out);
659 
660    return out;
661 }
662 
OnSlider(wxCommandEvent & WXUNUSED (evt))663 void EffectCompressor::OnSlider(wxCommandEvent & WXUNUSED(evt))
664 {
665    TransferDataFromWindow();
666    UpdateUI();
667 }
668 
UpdateUI()669 void EffectCompressor::UpdateUI()
670 {
671    mThresholdLabel->SetName(wxString::Format(_("Threshold %d dB"), (int) mThresholdDB));
672    mThresholdText->SetLabel(ThresholdFormat((int) mThresholdDB).Translation());
673    mThresholdText->SetName(mThresholdText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
674 
675    mNoiseFloorLabel->SetName(wxString::Format(_("Noise Floor %d dB"), (int) mNoiseFloorDB));
676    mNoiseFloorText->SetLabel(ThresholdFormat((int) mNoiseFloorDB).Translation());
677    mNoiseFloorText->SetName(mNoiseFloorText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
678 
679    mRatioLabel->SetName(
680       RatioLabelFormat(mRatioSlider->GetValue(), mRatio).Translation());
681    mRatioText->SetLabel(
682       RatioTextFormat(mRatioSlider->GetValue(), mRatio).Translation());
683    mRatioText->SetName(mRatioText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
684 
685    mAttackLabel->SetName(wxString::Format(_("Attack Time %.2f secs"), mAttackTime));
686    mAttackText->SetLabel(AttackTimeFormat(mAttackTime).Translation());
687    mAttackText->SetName(mAttackText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
688 
689    mDecayLabel->SetName(wxString::Format(_("Release Time %.1f secs"), mDecayTime));
690    mDecayText->SetLabel(DecayTimeFormat(mDecayTime).Translation());
691    mDecayText->SetName(mDecayText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
692 
693    mPanel->Refresh(false);
694 
695    return;
696 }
697 
698 //----------------------------------------------------------------------------
699 // EffectCompressorPanel
700 //----------------------------------------------------------------------------
701 
BEGIN_EVENT_TABLE(EffectCompressorPanel,wxPanelWrapper)702 BEGIN_EVENT_TABLE(EffectCompressorPanel, wxPanelWrapper)
703    EVT_PAINT(EffectCompressorPanel::OnPaint)
704    EVT_SIZE(EffectCompressorPanel::OnSize)
705 END_EVENT_TABLE()
706 
707 EffectCompressorPanel::EffectCompressorPanel(wxWindow *parent, wxWindowID winid,
708                                              double & threshold,
709                                              double & noiseFloor,
710                                              double & ratio)
711 :  wxPanelWrapper(parent, winid),
712    threshold(threshold),
713    noiseFloor(noiseFloor),
714    ratio(ratio)
715 {
716 }
717 
OnPaint(wxPaintEvent & WXUNUSED (evt))718 void EffectCompressorPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
719 {
720    wxPaintDC dc(this);
721 
722    int width, height;
723    GetSize(&width, &height);
724 
725    double rangeDB = 60;
726 
727    // Ruler
728    int w = 0;
729    int h = 0;
730 
731    Ruler vRuler;
732    vRuler.SetBounds(0, 0, width, height);
733    vRuler.SetOrientation(wxVERTICAL);
734    vRuler.SetRange(0, -rangeDB);
735    vRuler.SetFormat(Ruler::LinearDBFormat);
736    vRuler.SetUnits(XO("dB"));
737    vRuler.GetMaxSize(&w, NULL);
738 
739    Ruler hRuler;
740    hRuler.SetBounds(0, 0, width, height);
741    hRuler.SetOrientation(wxHORIZONTAL);
742    hRuler.SetRange(-rangeDB, 0);
743    hRuler.SetFormat(Ruler::LinearDBFormat);
744    hRuler.SetUnits(XO("dB"));
745    hRuler.SetFlip(true);
746    hRuler.GetMaxSize(NULL, &h);
747 
748    vRuler.SetBounds(0, 0, w, height - h);
749    hRuler.SetBounds(w, height - h, width, height);
750 
751    vRuler.SetTickColour( theTheme.Colour( clrGraphLabels ));
752    hRuler.SetTickColour( theTheme.Colour( clrGraphLabels ));
753 
754 #if defined(__WXMSW__)
755    dc.Clear();
756 #endif
757 
758    wxRect border;
759    border.x = w;
760    border.y = 0;
761    border.width = width - w;
762    border.height = height - h + 1;
763 
764    dc.SetBrush(*wxWHITE_BRUSH);
765    dc.SetPen(*wxBLACK_PEN);
766    dc.DrawRectangle(border);
767 
768    wxRect envRect = border;
769    envRect.Deflate( 2, 2 );
770 
771    int kneeX = lrint((rangeDB+threshold)*envRect.width/rangeDB);
772    int kneeY = lrint((rangeDB+threshold/ratio)*envRect.height/rangeDB);
773 
774    int finalY = envRect.height;
775    int startY = lrint((threshold*(1.0/ratio-1.0))*envRect.height/rangeDB);
776 
777    // Yellow line for threshold
778 /*   dc.SetPen(wxPen(wxColour(220, 220, 0), 1, wxSOLID));
779    AColor::Line(dc,
780                 envRect.x,
781                 envRect.y + envRect.height - kneeY,
782                 envRect.x + envRect.width - 1,
783                 envRect.y + envRect.height - kneeY);*/
784 
785    // Was: Nice dark red line for the compression diagram
786 //   dc.SetPen(wxPen(wxColour(180, 40, 40), 3, wxSOLID));
787 
788    // Nice blue line for compressor, same color as used in the waveform envelope.
789    dc.SetPen( AColor::WideEnvelopePen) ;
790 
791    AColor::Line(dc,
792                 envRect.x,
793                 envRect.y + envRect.height - startY,
794                 envRect.x + kneeX - 1,
795                 envRect.y + envRect.height - kneeY);
796 
797    AColor::Line(dc,
798                 envRect.x + kneeX,
799                 envRect.y + envRect.height - kneeY,
800                 envRect.x + envRect.width - 1,
801                 envRect.y + envRect.height - finalY);
802 
803    // Paint border again
804    dc.SetBrush(*wxTRANSPARENT_BRUSH);
805    dc.SetPen(*wxBLACK_PEN);
806    dc.DrawRectangle(border);
807 
808    vRuler.Draw(dc);
809    hRuler.Draw(dc);
810 }
811 
OnSize(wxSizeEvent & WXUNUSED (evt))812 void EffectCompressorPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
813 {
814    Refresh(false);
815 }
816