Audacity 3.2.0
EqualizationBandSliders.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 EqualizationBandSliders.cpp
6
7 Mitch Golden
8 Vaughan Johnson (Preview)
9 Martyn Shaw (FIR filters, response curve, graphic EQ)
10
11 Paul Licameli split from Equalization.cpp
12
13**********************************************************************/
15#include "SampleFormat.h"
16#include "ShuttleGui.h"
17
18#if wxUSE_ACCESSIBILITY
19#include "WindowAccessible.h"
20#endif
21
22static const double kThirdOct[] =
23{
24 20., 25., 31., 40., 50., 63., 80., 100., 125., 160., 200.,
25 250., 315., 400., 500., 630., 800., 1000., 1250., 1600., 2000.,
26 2500., 3150., 4000., 5000., 6300., 8000., 10000., 12500., 16000., 20000.,
27};
28
31 : mCurvesList{ curvesList }
32{
33 for (size_t i = 0; i < NUM_PTS - 1; ++i)
34 mWhens[i] = (double)i / (NUM_PTS - 1.);
35 mWhens[NUM_PTS - 1] = 1.;
38}
39
41{
42 mBandsInUse = 0;
46 break;
47 }
48}
49
51{
52 wxWindow *pParent = S.GetParent();
53
54 // for (int i = 0; (i < NUMBER_OF_BANDS) && (kThirdOct[i] <= hiFreq ); ++i)
55 // May show more sliders than needed. Fixes Bug 2269
56 for (int i = 0; i < NUMBER_OF_BANDS; ++i) {
57 TranslatableString freq = kThirdOct[i] < 1000.
58 ? XO("%d Hz").Format((int)kThirdOct[i])
59 : XO("%g kHz").Format(kThirdOct[i] / 1000.);
60 TranslatableString fNum = kThirdOct[i] < 1000.
61 ? Verbatim("%d").Format((int)kThirdOct[i])
62 /* i18n-hint k is SI abbreviation for x1,000. Usually unchanged in translation. */
63 : XO("%gk").Format(kThirdOct[i] / 1000.);
64 S.StartVerticalLay();
65 {
66 S.AddFixedText( fNum );
67 mSliders[i] = safenew wxSliderWrapper(pParent, wxID_ANY, 0, -20, +20,
68 wxDefaultPosition, wxSize(-1,50), wxSL_VERTICAL | wxSL_INVERSE);
69
70 #if wxUSE_ACCESSIBILITY
71 mSliders[i]->SetAccessible(safenew SliderAx(mSliders[i], XO("%d dB")));
72 #endif
73 BindTo(*mSliders[i], wxEVT_SLIDER,
75
76 mSlidersOld[i] = 0;
77 mEQVals[i] = 0.;
78 S.Prop(1)
79 .Name(freq)
80 .ConnectRoot(
81 wxEVT_ERASE_BACKGROUND, &EqualizationBandSliders::OnErase)
82 .Position(wxEXPAND)
83 .Size({ -1, 50 })
84 .AddWindow(mSliders[i]);
85 }
86 S.EndVerticalLay();
87 }
88}
89
90//
91// Flatten the curve
92//
94{
95 auto &parameters = mCurvesList.mParameters;
96 const auto &drawMode = parameters.mDrawMode;
97 auto &linEnvelope = parameters.mLinEnvelope;
98 auto &logEnvelope = parameters.mLogEnvelope;
99
100 logEnvelope.Flatten(0.);
101 logEnvelope.SetTrackLen(1.0);
102 linEnvelope.Flatten(0.);
103 linEnvelope.SetTrackLen(1.0);
105 if( !drawMode )
106 {
107 for( size_t i = 0; i < mBandsInUse; i++)
108 {
109 mSliders[i]->SetValue(0);
110 mSlidersOld[i] = 0;
111 mEQVals[i] = 0.;
112
113 wxString tip;
114 if( kThirdOct[i] < 1000.)
115 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], 0. );
116 else
117 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., 0. );
118 mSliders[i]->SetToolTip(tip);
119 }
120 }
122}
123
125{
126 auto &parameters = mCurvesList.mParameters;
127 auto &linEnvelope = parameters.mLinEnvelope;
128 auto &logEnvelope = parameters.mLogEnvelope;
129 const auto &hiFreq = parameters.mHiFreq;
130
131 size_t numPoints = logEnvelope.GetNumberOfPoints();
132 if( numPoints == 0 )
133 {
134 return;
135 }
136
137 Doubles when{ numPoints };
138 Doubles value{ numPoints };
139
140 linEnvelope.Flatten(0.);
141 linEnvelope.SetTrackLen(1.0);
142 logEnvelope.GetPoints( when.get(), value.get(), numPoints );
143 linEnvelope.Reassign(0., value[0]);
144 double loLog = log10(20.);
145 double hiLog = log10(hiFreq);
146 double denom = hiLog - loLog;
147
148 for (size_t i = 0; i < numPoints; i++)
149 linEnvelope.Insert(pow( 10., ((when[i] * denom) + loLog))/hiFreq , value[i]);
150 linEnvelope.Reassign(1., value[numPoints-1]);
151}
152
154{
155 auto &parameters = mCurvesList.mParameters;
156 auto &linEnvelope = parameters.mLinEnvelope;
157 auto &logEnvelope = parameters.mLogEnvelope;
158 const auto &hiFreq = parameters.mHiFreq;
159
160 size_t numPoints = linEnvelope.GetNumberOfPoints();
161 if( numPoints == 0 )
162 {
163 return;
164 }
165
166 Doubles when{ numPoints };
167 Doubles value{ numPoints };
168
169 logEnvelope.Flatten(0.);
170 logEnvelope.SetTrackLen(1.0);
171 linEnvelope.GetPoints( when.get(), value.get(), numPoints );
172 logEnvelope.Reassign(0., value[0]);
173 double loLog = log10(20.);
174 double hiLog = log10(hiFreq );
175 double denom = hiLog - loLog;
176 bool changed = false;
177
178 for (size_t i = 0; i < numPoints; i++)
179 {
180 if( when[i]*hiFreq >= 20 )
181 {
182 // Caution: on Linux, when when == 20, the log calculation rounds
183 // to just under zero, which causes an assert error.
184 double flog = (log10(when[i]*hiFreq )-loLog)/denom;
185 logEnvelope.Insert(std::max(0.0, flog) , value[i]);
186 }
187 else
188 { //get the first point as close as we can to the last point requested
189 changed = true;
190 double v = value[i];
191 logEnvelope.Insert(0., v);
192 }
193 }
194 logEnvelope.Reassign(1., value[numPoints - 1]);
195
196 if(changed)
197 mCurvesList.EnvelopeUpdated(logEnvelope, false);
198}
199
201{
202 const auto &parameters = mCurvesList.mParameters;
203 const auto &logEnvelope = parameters.mLogEnvelope;
204 const auto &curves = mCurvesList.mCurves;
205 const auto &loFreq = parameters.mLoFreq;
206 const auto &hiFreq = parameters.mHiFreq;
207
208 const double loLog = log10(loFreq);
209 const double hiLog = log10(hiFreq );
210 const double denom = hiLog - loLog;
211
212 for (size_t i = 0; i < mBandsInUse; ++i)
213 {
214 if( kThirdOct[i] == loFreq )
215 mWhenSliders[i] = 0.;
216 else
217 mWhenSliders[i] = (log10(kThirdOct[i]) - loLog) / denom;
218 // set initial values of sliders
219 mEQVals[i] =
220 std::clamp(logEnvelope.GetValue(mWhenSliders[i]), -20., 20.);
221 }
222
223 double vals[NUM_PTS];
224 double error = 0.0;
225 double oldError = 0.0;
226 double mEQValsOld = 0.0;
227 double correction = 1.6;
228 bool flag;
229 size_t j=0;
230 Envelope testEnvelope{ logEnvelope };
231
232 for(size_t i = 0; i < NUM_PTS; i++)
233 vals[i] = testEnvelope.GetValue(mWhens[i]);
234
235 // Do error minimisation
236 error = 0.;
237 GraphicEQ(testEnvelope);
238 for(size_t i = 0; i < NUM_PTS; i++) //calc initial error
239 {
240 double err = vals[i] - testEnvelope.GetValue(mWhens[i]);
241 error += err*err;
242 }
243 oldError = error;
244 while( j < mBandsInUse * 12 ) //loop over the sliders a number of times
245 {
246 auto i = j % mBandsInUse; //use this slider
247 if( (j > 0) & (i == 0) ) // if we've come back to the first slider again...
248 {
249 if( correction > 0 )
250 correction = -correction; //go down
251 else
252 correction = -correction/2.; //go up half as much
253 }
254 flag = true; // check if we've hit the slider limit
255 do
256 {
257 oldError = error;
258 mEQValsOld = mEQVals[i];
259 mEQVals[i] += correction; //move fader value
260 if( mEQVals[i] > 20. )
261 {
262 mEQVals[i] = 20.;
263 flag = false;
264 }
265 if( mEQVals[i] < -20. )
266 {
267 mEQVals[i] = -20.;
268 flag = false;
269 }
270 GraphicEQ(testEnvelope); //calculate envelope
271 error = 0.;
272 for(size_t k = 0; k < NUM_PTS; k++) //calculate error
273 {
274 double err = vals[k] - testEnvelope.GetValue(mWhens[k]);
275 error += err*err;
276 }
277 }
278 while( (error < oldError) && flag );
279 if( error > oldError )
280 {
281 mEQVals[i] = mEQValsOld; //last one didn't work
282 error = oldError;
283 }
284 else
285 oldError = error;
286 if( error < .0025 * mBandsInUse)
287 break; // close enuff
288 j++; //try next slider
289 }
290 if( error > .0025 * mBandsInUse ) // not within 0.05dB on each slider, on average
291 {
292 mCurvesList.Select( (int) curves.size() - 1 );
293 mCurvesList.EnvelopeUpdated(testEnvelope, false);
294 }
295
296 for (size_t i = 0; i < mBandsInUse; ++i)
297 {
298 // actually set slider positions
299 mSliders[i]->SetValue(lrint(mEQVals[i]));
300 mSlidersOld[i] = mSliders[i]->GetValue();
301 wxString tip;
302 if (kThirdOct[i] < 1000.)
303 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
304 else
305 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
306 mSliders[i]->SetToolTip(tip);
307 }
308}
309
311{
312 const auto &parameters = mCurvesList.mParameters;
313 const auto &interp = parameters.mInterp;
314
315 // JKC: 'value' is for height of curve.
316 // The 0.0 initial value would only get used if NUM_PTS were 0.
317 double value = 0.0;
318 double dist, span, s;
319
320 env.Flatten(0.);
321 env.SetTrackLen(1.0);
322
323 switch( interp )
324 {
325 case EqualizationParameters::kBspline: // B-spline
326 {
327 int minF = 0;
328 for(size_t i = 0; i < NUM_PTS; i++)
329 {
330 while( (mWhenSliders[minF] <= mWhens[i]) & (minF < (int)mBandsInUse) )
331 minF++;
332 minF--;
333 if( minF < 0 ) //before first slider
334 {
335 dist = mWhens[i] - mWhenSliders[0];
336 span = mWhenSliders[1] - mWhenSliders[0];
337 s = dist/span;
338 if( s < -1.5 )
339 value = 0.;
340 else if( s < -.5 )
341 value = mEQVals[0]*(s + 1.5)*(s + 1.5)/2.;
342 else
343 value = mEQVals[0]*(.75 - s*s) + mEQVals[1]*(s + .5)*(s + .5)/2.;
344 }
345 else
346 {
347 if( mWhens[i] > mWhenSliders[mBandsInUse-1] ) //after last fader
348 {
349 dist = mWhens[i] - mWhenSliders[mBandsInUse-1];
351 s = dist/span;
352 if( s > 1.5 )
353 value = 0.;
354 else if( s > .5 )
355 value = mEQVals[mBandsInUse-1]*(s - 1.5)*(s - 1.5)/2.;
356 else
357 value = mEQVals[mBandsInUse-1]*(.75 - s*s) +
358 mEQVals[mBandsInUse-2]*(s - .5)*(s - .5)/2.;
359 }
360 else //normal case
361 {
362 dist = mWhens[i] - mWhenSliders[minF];
363 span = mWhenSliders[minF+1] - mWhenSliders[minF];
364 s = dist/span;
365 if(s < .5 )
366 {
367 value = mEQVals[minF]*(0.75 - s*s);
368 if( minF+1 < (int)mBandsInUse )
369 value += mEQVals[minF+1]*(s+.5)*(s+.5)/2.;
370 if( minF-1 >= 0 )
371 value += mEQVals[minF-1]*(s-.5)*(s-.5)/2.;
372 }
373 else
374 {
375 value = mEQVals[minF]*(s-1.5)*(s-1.5)/2.;
376 if( minF+1 < (int)mBandsInUse )
377 value += mEQVals[minF+1]*(.75-(1.-s)*(1.-s));
378 if( minF+2 < (int)mBandsInUse )
379 value += mEQVals[minF+2]*(s-.5)*(s-.5)/2.;
380 }
381 }
382 }
383 if(mWhens[i]<=0.)
384 env.Reassign(0., value);
385 env.Insert( mWhens[i], value );
386 }
387 env.Reassign( 1., value );
388 break;
389 }
390
391 case EqualizationParameters::kCosine: // Cosine squared
392 {
393 int minF = 0;
394 for(size_t i = 0; i < NUM_PTS; i++)
395 {
396 while( (mWhenSliders[minF] <= mWhens[i]) & (minF < (int)mBandsInUse) )
397 minF++;
398 minF--;
399 if( minF < 0 ) //before first slider
400 {
401 dist = mWhenSliders[0] - mWhens[i];
402 span = mWhenSliders[1] - mWhenSliders[0];
403 if( dist < span )
404 value = mEQVals[0]*(1. + cos(M_PI*dist/span))/2.;
405 else
406 value = 0.;
407 }
408 else
409 {
410 if( mWhens[i] > mWhenSliders[mBandsInUse-1] ) //after last fader
411 {
413 dist = mWhens[i] - mWhenSliders[mBandsInUse-1];
414 if( dist < span )
415 value = mEQVals[mBandsInUse-1]*(1. + cos(M_PI*dist/span))/2.;
416 else
417 value = 0.;
418 }
419 else //normal case
420 {
421 span = mWhenSliders[minF+1] - mWhenSliders[minF];
422 dist = mWhenSliders[minF+1] - mWhens[i];
423 value = mEQVals[minF]*(1. + cos(M_PI*(span-dist)/span))/2. +
424 mEQVals[minF+1]*(1. + cos(M_PI*dist/span))/2.;
425 }
426 }
427 if(mWhens[i]<=0.)
428 env.Reassign(0., value);
429 env.Insert( mWhens[i], value );
430 }
431 env.Reassign( 1., value );
432 break;
433 }
434
435 case EqualizationParameters::kCubic: // Cubic Spline
436 {
437 double y2[NUMBER_OF_BANDS+1];
440 for(double xf=0; xf<1.; xf+=1./NUM_PTS)
441 {
442 env.Insert(xf, splint(mWhenSliders, mEQVals, mBandsInUse+1, y2, xf));
443 }
444 break;
445 }
446 }
447
449}
450
452 double x[], double y[], size_t n, double y2[])
453{
454 wxASSERT( n > 0 );
455
456 double p, sig;
457 Doubles u{ n };
458
459 y2[0] = 0.; //
460 u[0] = 0.; //'natural' boundary conditions
461 for (size_t i = 1; i + 1 < n; i++)
462 {
463 sig = ( x[i] - x[i-1] ) / ( x[i+1] - x[i-1] );
464 p = sig * y2[i-1] + 2.;
465 y2[i] = (sig - 1.)/p;
466 u[i] = ( y[i+1] - y[i] ) / ( x[i+1] - x[i] ) - ( y[i] - y[i-1] ) / ( x[i] - x[i-1] );
467 u[i] = (6.*u[i]/( x[i+1] - x[i-1] ) - sig * u[i-1]) / p;
468 }
469 y2[n - 1] = 0.;
470 for (size_t i = n - 1; i--;)
471 y2[i] = y2[i]*y2[i+1] + u[i];
472}
473
475 double x[], double y[], size_t n, double y2[], double xr)
476{
477 wxASSERT( n > 1 );
478
479 double a, b, h;
480 static double xlast = 0.; // remember last x value requested
481 static size_t k = 0; // and which interval we were in
482
483 if( xr < xlast )
484 k = 0; // gone back to start, (or somewhere to the left)
485 xlast = xr;
486 while( (x[k] <= xr) && (k + 1 < n) )
487 k++;
488 wxASSERT( k > 0 );
489 k--;
490 h = x[k+1] - x[k];
491 a = ( x[k+1] - xr )/h;
492 b = (xr - x[k])/h;
493 return( a*y[k]+b*y[k+1]+((a*a*a-a)*y2[k]+(b*b*b-b)*y2[k+1])*h*h/6.);
494}
495
497{
498}
499
500void EqualizationBandSliders::OnSlider(wxCommandEvent & event)
501{
502 auto &parameters = mCurvesList.mParameters;
503 auto &logEnvelope = parameters.mLogEnvelope;
504
505 wxSlider *s = (wxSlider *)event.GetEventObject();
506 for (size_t i = 0; i < mBandsInUse; i++)
507 {
508 if( s == mSliders[i])
509 {
510 int posn = mSliders[i]->GetValue();
511 if( wxGetKeyState(WXK_SHIFT) )
512 {
513 if( posn > mSlidersOld[i] )
514 mEQVals[i] += (float).1;
515 else
516 if( posn < mSlidersOld[i] )
517 mEQVals[i] -= .1f;
518 }
519 else
520 mEQVals[i] += (posn - mSlidersOld[i]);
521 if( mEQVals[i] > 20. )
522 mEQVals[i] = 20.;
523 if( mEQVals[i] < -20. )
524 mEQVals[i] = -20.;
525 int newPosn = (int)mEQVals[i];
526 mSliders[i]->SetValue( newPosn );
527 mSlidersOld[i] = newPosn;
528 wxString tip;
529 if( kThirdOct[i] < 1000.)
530 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
531 else
532 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
533 s->SetToolTip(tip);
534 break;
535 }
536 }
537 GraphicEQ(logEnvelope);
539}
540
541void EqualizationBandSliders::Invert() // Inverts any curve
542{
543 auto &parameters = mCurvesList.mParameters;
544 auto &linEnvelope = parameters.mLinEnvelope;
545 auto &logEnvelope = parameters.mLogEnvelope;
546
547 if (!parameters.mDrawMode) // Graphic (Slider) mode. Invert the sliders.
548 {
549 for (size_t i = 0; i < mBandsInUse; i++)
550 {
551 mEQVals[i] = -mEQVals[i];
552 int newPosn = (int)mEQVals[i];
553 mSliders[i]->SetValue( newPosn );
554 mSlidersOld[i] = newPosn;
555
556 wxString tip;
557 if( kThirdOct[i] < 1000.)
558 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
559 else
560 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
561 mSliders[i]->SetToolTip(tip);
562 }
563 GraphicEQ(logEnvelope);
564 }
565 else // Draw mode. Invert the points.
566 {
567 bool lin = parameters.IsLinear(); // refers to the 'log' or 'lin' of the frequency scale, not the amplitude
568 size_t numPoints; // number of points in the curve/envelope
569
570 // determine if log or lin curve is the current one
571 // and find out how many points are in the curve
572 if(lin) // lin freq scale and so envelope
573 {
574 numPoints = linEnvelope.GetNumberOfPoints();
575 }
576 else
577 {
578 numPoints = logEnvelope.GetNumberOfPoints();
579 }
580
581 if( numPoints == 0 )
582 return;
583
584 Doubles when{ numPoints };
585 Doubles value{ numPoints };
586
587 if(lin)
588 linEnvelope.GetPoints( when.get(), value.get(), numPoints );
589 else
590 logEnvelope.GetPoints( when.get(), value.get(), numPoints );
591
592 // invert the curve
593 for (size_t i = 0; i < numPoints; i++)
594 {
595 if(lin)
596 linEnvelope.Reassign(when[i] , -value[i]);
597 else
598 logEnvelope.Reassign(when[i] , -value[i]);
599 }
600
601 // copy it back to the other one (just in case)
602 if(lin)
603 EnvLinToLog();
604 else
605 EnvLogToLin();
606 }
607
608 // and update the display etc
611}
wxT("CloseDown"))
#define M_PI
Definition: Distortion.cpp:22
static const double kThirdOct[]
#define NUM_PTS
#define NUMBER_OF_BANDS
XO("Cut/Copy/Paste")
#define safenew
Definition: MemoryX.h:10
#define S(N)
Definition: ToChars.cpp:64
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
static std::once_flag flag
Piecewise linear or piecewise exponential function from double to double.
Definition: Envelope.h:72
void Insert(int point, const EnvPoint &p) noexcept
insert a point
Definition: Envelope.cpp:381
double GetValue(double t, double sampleDur=0) const
Get envelope value at time t.
Definition: Envelope.cpp:880
void SetTrackLen(double trackLen, double sampleDur=0.0)
Definition: Envelope.cpp:832
size_t GetNumberOfPoints() const
Return number of points.
Definition: Envelope.cpp:723
int Reassign(double when, double value)
Move a point at when to value.
Definition: Envelope.cpp:700
void Flatten(double value)
Definition: Envelope.cpp:140
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
Holds a msgid for the translation catalog; may also bind format arguments.
TranslatableString & Format(Args &&...args) &
Capture variadic format arguments (by copy) when there is no plural.
#define lrint(dbl)
Definition: float_cast.h:169
static void spline(double x[], double y[], size_t n, double y2[])
void BindTo(wxEvtHandler &src, const EventTag &eventType, void(Class::*pmf)(Event &))
static double splint(double x[], double y[], size_t n, double y2[], double xr)
double mWhenSliders[NUMBER_OF_BANDS+1]
int mSlidersOld[NUMBER_OF_BANDS]
EqualizationCurvesList & mCurvesList
void AddBandSliders(ShuttleGui &S)
EqualizationBandSliders(EqualizationCurvesList &curvesList)
double mEQVals[NUMBER_OF_BANDS+1]
wxSlider * mSliders[NUMBER_OF_BANDS]
void OnSlider(wxCommandEvent &event)
Maintains a list of preset curves for Equalization effects.
EqualizationFilter & mParameters