Audacity 3.2.0
DynamicRangeProcessorEditor.cpp
Go to the documentation of this file.
2#include "AllThemeResources.h"
3#include "BasicUI.h"
10#include "EffectInterface.h"
11#include "ShuttleGui.h"
12#include "Theme.h"
14#include "widgets/RulerPanel.h"
15#include "widgets/valnum.h"
16#include <wx/checkbox.h>
17#include <wx/dialog.h>
18#include <wx/sizer.h>
19#include <wx/slider.h>
20#include <wx/textctrl.h>
21
22#if wxUSE_ACCESSIBILITY
23# include "WindowAccessible.h"
24#endif
25
26namespace
27{
30
31constexpr auto historyPanelId = wxID_HIGHEST + 1;
32constexpr auto historyRulerPanelId = wxID_HIGHEST + 2;
33constexpr auto transferFunctionPanelId = wxID_HIGHEST + 3;
34constexpr auto checkboxId = wxID_HIGHEST + 4;
35constexpr auto compressionMeterRulerPanelId = wxID_HIGHEST + 5;
36constexpr auto compressionMeterPanelId = wxID_HIGHEST + 6;
37constexpr auto clipIndicatorId = wxID_HIGHEST + 7;
38constexpr auto rulerWidth = 30;
39constexpr auto borderSize = 5;
40constexpr auto compressionMeterPanelWidth = 30;
41
42float GetDbRange(float maxCompressionDb)
43{
44 constexpr auto minRange = 3.f;
45 return std::max(minRange, maxCompressionDb / 2);
46}
47
49{
50 const CompressorSettings* compressorSettings =
51 access.Get().cast<CompressorSettings>();
52 const LimiterSettings* limiterSettings =
53 access.Get().cast<LimiterSettings>();
54 return compressorSettings ?
55 DynamicRangeProcessorSettings { *compressorSettings } :
56 DynamicRangeProcessorSettings { *limiterSettings };
57}
58
60 wxWindow* parent, wxOrientation orientation, float dbRange,
61 int id = wxID_ANY)
62{
63 // Overridden by `.MinSize()` calls
64 const wxSize minSize { -1, -1 };
65
66 return safenew RulerPanel(
67 parent, id, orientation, minSize,
68 orientation == wxVERTICAL ? RulerPanel::Range { 0.0, -dbRange } :
69 RulerPanel::Range { -dbRange, 0.0 },
72 .LabelEdges(false)
73 .TicksAtExtremes(true)
74 .Flip(orientation == wxVERTICAL)
75 .TickColour(theTheme.Colour(clrGraphLabels)));
76}
77
78constexpr auto C = 5.;
79
80// Assumes x to be in [0, 1]
81auto MapExponentially(double x)
82{
83 return (std::exp(C * x) - 1) / (std::exp(C) - 1.);
84}
85
86auto MapExponentially(double min, double max, double x)
87{
88 // Map min and max to 0 and 1, and x accordingly, before applying
89 // exponential, or we may run into NaNs.
90 const auto a = 1.0 / (max - min);
91 const auto b = -min / (max - min);
92 const auto result = (MapExponentially(a * x + b) - b) / a;
93 // MH: Because we're using NumValidatorStyle::ONE_TRAILING_ZERO, we also
94 // quantize here to 0.1. (I think that's why.)
95 return std::round(result * 10) / 10;
96}
97
98auto MapLogarithmically(double x)
99{
100 return (std::log(x * (std::exp(C) - 1) + 1)) / C;
101}
102
103auto MapLogarithmically(double min, double max, double x)
104{
105 const auto a = 1.0 / (max - min);
106 const auto b = -min / (max - min);
107 return (MapLogarithmically(a * x + b) - b) / a;
108}
109
110auto SliderToTextValue(double value, const ExtendedCompressorParameter& setting)
111{
112 const auto scaled = value / setting.param->TextToSlider();
113 return setting.attributes.exponentialSlider ?
115 setting.param->SliderMin(), setting.param->SliderMax(),
116 scaled) :
117 scaled;
118}
119
121{
122 const auto unscaled = setting.attributes.exponentialSlider ?
124 setting.param->SliderMin(),
125 setting.param->SliderMax(), setting.value) :
126 setting.value;
127 return unscaled * setting.param->TextToSlider();
128}
129} // namespace
130
132 wxWindow* parent, CompressorInstance& instance, bool isRealtime,
133 const EffectUIServices& services, EffectSettingsAccess& access)
134 : EffectEditor { services, access }
135 , mUIParent { parent }
136 , mTopLevelParent(static_cast<wxDialog&>(*wxGetTopLevelParent(parent)))
137 , mCompressorInstance { instance }
138 , mIsRealtime { isRealtime }
139{
140}
141
143 std::vector<ExtendedCompressorParameter> parameters)
144{
145 assert(std::is_sorted(
146 parameters.begin(), parameters.end(),
147 [](const auto& a, const auto& b) { return a.category < b.category; }));
148 mParameters = std::move(parameters);
149}
150
152{
153 S.SetBorder(borderSize);
154 S.AddSpace(0, borderSize);
155
156 const auto compressorSettings = GetCompressorSettings();
157 if (compressorSettings)
158 {
159 S.StartMultiColumn(2, wxEXPAND);
160 {
161 S.SetStretchyCol(0);
163 AddCompressionCurvePanel(S, *compressorSettings);
164 }
165 S.EndMultiColumn();
166 }
167 else
169
170 if (!mIsRealtime)
171 // Not a real-time effect editor, no need for a graph
172 return;
173
174 const auto settings = GetSettings(mAccess);
175
176 const auto rulerPanel = MakeRulerPanel(
177 mUIParent, wxVERTICAL,
180
181 const auto onDbRangeChanged = [this](float newDbRange) {
182 for (const auto id :
184 if (
185 const auto panel = dynamic_cast<RulerPanel*>(
186 wxWindow::FindWindowById(id, mUIParent)))
187 {
188 panel->ruler.SetRange(0., -newDbRange);
189 panel->Refresh();
190 }
191 if (
192 const auto compressionMeterPanel =
193 dynamic_cast<CompressionMeterPanel*>(
194 wxWindow::FindWindowById(compressionMeterPanelId, mUIParent)))
195 compressionMeterPanel->SetDbRange(newDbRange);
196 };
197
198 const auto histPanel = safenew HistPanel(
199 mUIParent, historyPanelId, mCompressorInstance, onDbRangeChanged);
200 histPanel->ShowInput(settings.showInput);
201 histPanel->ShowOutput(settings.showOutput);
202 histPanel->ShowActual(settings.showActual);
203 histPanel->ShowTarget(settings.showTarget);
204
205 S.StartMultiColumn(compressorSettings ? 3 : 1, wxEXPAND);
206 {
207 S.SetStretchyCol(0);
209 if (compressorSettings)
210 {
212 S.AddSpace(rulerWidth, 0);
213 }
214 }
215 S.EndMultiColumn();
216
217 S.SetSizerProportion(1);
218 S.StartMultiColumn(5, wxEXPAND);
219 {
220 using namespace DynamicRangeProcessorPanel;
221
222 S.SetStretchyCol(1);
223 S.SetStretchyRow(0);
224 S.AddSpace(borderSize, 0);
225 S.Prop(1)
226 .Position(wxALIGN_LEFT | wxALIGN_TOP | wxEXPAND)
227 .MinSize({ HistPanel::minWidth, graphMinHeight })
228 .AddWindow(histPanel);
229
230 S.AddSpace(borderSize, 0);
231
232 const auto onClipped = [this] {
233 if (
234 const auto panel = dynamic_cast<ClipIndicatorPanel*>(
235 wxWindow::FindWindowById(clipIndicatorId, mUIParent)))
236 panel->SetClipped();
237 };
238 S.Prop(1)
239 .Position(wxALIGN_LEFT | wxALIGN_TOP | wxEXPAND)
243 graphMinRangeDb, onClipped))
244 ->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& evt) {
245 if (
246 const auto panel =
247 dynamic_cast<CompressionMeterPanel*>(evt.GetEventObject()))
248 panel->Reset();
249 if (
250 const auto indicator = dynamic_cast<ClipIndicatorPanel*>(
251 wxWindow::FindWindowById(clipIndicatorId, mUIParent)))
252 indicator->Reset();
253 });
254
255 S.Prop(1)
256 .Position(wxEXPAND | wxALIGN_TOP)
257 .MinSize({ rulerWidth, graphMinHeight })
258 .AddWindow(rulerPanel);
259 }
260 S.EndMultiColumn();
261
262 S.AddSpace(0, borderSize);
263}
264
267{
268#define GET_REF(settingName) \
269 GetCompressorSettings() ? GetCompressorSettings()->settingName : \
270 GetLimiterSettings()->settingName
271
272 S.StartVerticalLay(wxALIGN_BOTTOM, 0);
273 {
274 S.StartHorizontalLay(wxALIGN_LEFT, 0);
275 {
276 S.AddSpace(borderSize, 0);
277 const auto input = S.AddCheckBox(XO("I&nput"), settings.showInput);
278 input->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt) {
280 evt.IsChecked(), GET_REF(showInput), &HistPanel::ShowInput);
281 });
282 /* i18n-hint: show input on a graph */
283 input->SetName(_("Show input"));
284
285 const auto output = S.AddCheckBox(XO("O&utput"), settings.showOutput);
286 output->Bind(wxEVT_CHECKBOX, [&](wxCommandEvent& evt) {
288 evt.IsChecked(), GET_REF(showOutput), &HistPanel::ShowOutput);
289 });
290 /* i18n-hint: show output on a graph */
291 output->SetName(_("Show output"));
292
293 /* i18n-hint: The effective compression, including smoothing. */
294 const auto actual =
295 S.AddCheckBox(XO("A&ctual compression"), settings.showActual);
296 actual->Bind(wxEVT_CHECKBOX, [&](wxCommandEvent& evt) {
298 evt.IsChecked(), GET_REF(showActual), &HistPanel::ShowActual);
299 });
300 /* i18n-hint: show actual compression on a graph */
301 actual->SetName(_("Show actual compression"));
302
303 /* i18n-hint: The target compression, before smoothing. */
304 const auto target =
305 S.AddCheckBox(XO("Tar&get compression"), settings.showTarget);
306 target->Bind(wxEVT_CHECKBOX, [&](wxCommandEvent& evt) {
308 evt.IsChecked(), GET_REF(showTarget), &HistPanel::ShowTarget);
309 });
310 /* i18n-hint: show target compression on a graph */
311 target->SetName(_("Show target compression"));
312
313#if wxUSE_ACCESSIBILITY
314 // so that name can be set on a standard control
319#endif
320 }
321 S.EndHorizontalLay();
322 S.AddSpace(0, borderSize);
323 }
324 S.EndVerticalLay();
325}
326
328{
329 constexpr auto width = compressionMeterPanelWidth / 2 - 2;
330 constexpr auto height = compressionMeterPanelWidth / 4;
331 S.MinSize({ width, height })
332 .AddWindow(
334 wxALIGN_RIGHT | wxALIGN_BOTTOM)
335 ->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& evt) {
336 if (
337 const auto panel = dynamic_cast<CompressionMeterPanel*>(
338 wxWindow::FindWindowById(compressionMeterPanelId, mUIParent)))
339 panel->ResetClipped();
340 if (
341 const auto indicator =
342 dynamic_cast<ClipIndicatorPanel*>(evt.GetEventObject()))
343 indicator->Reset();
344 });
345}
346
348 ShuttleGui& S, const CompressorSettings& compressorSettings)
349{
350 S.StartMultiColumn(3);
351 {
352 S.SetStretchyRow(1);
353
354 constexpr auto tfWidth = 200;
355
356 // Horizontal ruler row
357 S.AddSpace(borderSize, 0);
358 S.Prop(1)
359 .Position(wxALIGN_BOTTOM)
360 .MinSize({ tfWidth, rulerWidth })
361 .AddWindow(MakeRulerPanel(mUIParent, wxHORIZONTAL, TFPanel::rangeDb));
362 S.AddSpace(rulerWidth, 0);
363
364 // Transfer function row
365 S.AddSpace(borderSize, 0);
366 S.Prop(1).Position(wxEXPAND).AddWindow(safenew TFPanel(
367 mUIParent, transferFunctionPanelId, compressorSettings));
368 S.Prop(1).Position(wxEXPAND).AddWindow(
369 MakeRulerPanel(mUIParent, wxVERTICAL, TFPanel::rangeDb));
370 }
371 S.EndMultiColumn();
372}
373
375{
376 using It = std::vector<ExtendedCompressorParameter>::iterator;
377 const auto addTextboxAndSliders = [&](
378 const TranslatableString& prompt,
379 const It& begin, const It& end) {
380 S.StartStatic(prompt);
381 S.StartMultiColumn(3, wxEXPAND);
382 {
383 S.SetStretchyCol(2);
384 std::for_each(begin, end, [&](ExtendedCompressorParameter& parameter) {
385 AddTextboxAndSlider(S, parameter);
386 });
387 }
388 S.EndMultiColumn();
389 S.EndStatic();
390 };
391
392 const auto firstSmoothingParameterIt =
393 std::find_if(mParameters.begin(), mParameters.end(), [](const auto& p) {
394 return p.category == ControllerCategory::TimeSmoothing;
395 });
396
397 S.StartPanel();
398 {
399 addTextboxAndSliders(
400 XO("Compression curve"), mParameters.begin(),
401 firstSmoothingParameterIt);
402 addTextboxAndSliders(
403 XO("Smoothing"), firstSmoothingParameterIt, mParameters.end());
404 }
405 S.EndPanel();
406}
407
410{
411 setting.text = S.Validator<FloatingPointValidator<double>>(
412 1, &setting.value, NumValidatorStyle::ONE_TRAILING_ZERO,
413 setting.param->Min(), setting.param->Max())
414 .AddTextBox(setting.attributes.caption, L"", 12);
415
416 setting.text->Bind(wxEVT_TEXT, [&](wxCommandEvent& evt) {
417 if (!EnableApply(mUIParent, mUIParent->TransferDataFromWindow()))
418 return;
419 setting.slider->SetValue(TextToSliderValue(setting));
420 ValidateUI();
421 UpdateUI();
423 });
424
425 setting.slider =
426 S.Name(setting.attributes.caption)
427 .Style(wxSL_HORIZONTAL)
428 .MinSize({ 100, -1 })
429 .AddSlider(
430 {}, TextToSliderValue(setting), setting.param->SliderMax(),
431 setting.param->SliderMin());
432
433 setting.slider->Bind(wxEVT_SLIDER, [&](wxCommandEvent& evt) {
434 setting.value = SliderToTextValue(setting.slider->GetValue(), setting);
435 setting.text->GetValidator()->TransferToWindow();
436 EnableApply(mUIParent, mUIParent->Validate());
437 ValidateUI();
438 UpdateUI();
440 });
441
442 // For exponential slider, for right/down arrow keys, because
443 // the setting value has precision of 1, ensure that
444 // the change in slider position results a change in value of
445 // at least 0.1, otherwise the slider position and setting value
446 // may not change.
447 setting.slider->Bind(wxEVT_SCROLL_LINEDOWN, [&](wxScrollEvent& evt) {
448 if (setting.attributes.exponentialSlider
449 && setting.value == SliderToTextValue(evt.GetInt(), setting)) {
450 setting.value += 0.1;
451 setting.slider->SetValue(std::round(TextToSliderValue(setting)));
452 }
453 });
454
455 // And similarly for left/up arrow keys.
456 setting.slider->Bind(wxEVT_SCROLL_LINEUP, [&](wxScrollEvent& evt) {
457 if (setting.attributes.exponentialSlider
458 && setting.value == SliderToTextValue(evt.GetInt(), setting)) {
459 setting.value -= 0.1;
460 setting.slider->SetValue(std::round(TextToSliderValue(setting)));
461 }
462 });
463}
464
466{
468 // pass back the modified settings to the MessageBuffer
469 if (auto compressorSettings = settings.cast<CompressorSettings>())
470 *compressorSettings = *GetCompressorSettings();
471 else
473 return nullptr;
474 });
475 return true;
476}
477
479{
480 if (CompressorSettings* compressorSettings = GetCompressorSettings())
481 *compressorSettings = *mAccess.Get().cast<CompressorSettings>();
482 else
484
485 for (auto& setting : mParameters)
486 setting.slider->SetValue(std::round(TextToSliderValue(setting)));
487
488 if (
489 auto tfPanel =
490 wxWindow::FindWindowById(transferFunctionPanelId, mUIParent))
491 tfPanel->Refresh();
492
493 return true;
494}
495
497 bool newVal, double& setting,
498 void (DynamicRangeProcessorHistoryPanel::*setter)(bool))
499{
500 setting = newVal;
501 if (
502 const auto panel = dynamic_cast<HistPanel*>(
503 wxWindow::FindWindowById(historyPanelId, mUIParent)))
504 (panel->*setter)(newVal);
505 ValidateUI();
507}
Toolkit-neutral facade for basic user interface services.
int min(int a, int b)
#define GET_REF(settingName)
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
#define safenew
Definition: MemoryX.h:10
THEME_API Theme theTheme
Definition: Theme.cpp:82
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:51
static float GetMaxCompressionDb(const DynamicRangeProcessorSettings &settings)
std::vector< ExtendedCompressorParameter > mParameters
void OnCheckbox(bool newVal, double &setting, void(DynamicRangeProcessorHistoryPanel::*)(bool))
void AddCompressionCurvePanel(ShuttleGui &S, const CompressorSettings &)
virtual const CompressorSettings * GetCompressorSettings() const
void Initialize(std::vector< ExtendedCompressorParameter > parameters)
DynamicRangeProcessorEditor(wxWindow *parent, CompressorInstance &instance, bool isRealtime, const EffectUIServices &services, EffectSettingsAccess &access)
virtual const LimiterSettings * GetLimiterSettings() const
void AddTextboxAndSlider(ShuttleGui &S, ExtendedCompressorParameter &param)
void AddCheckboxPanel(ShuttleGui &S, const DynamicRangeProcessorSettings &settings)
bool UpdateUI() final override
Update appearance of the panel for changes in settings.
bool ValidateUI() final override
Get settings data from the panel; may make error dialogs and return false.
static bool EnableApply(wxWindow *parent, bool enable=true)
Enable or disable the Apply button of the dialog that contains parent.
EffectSettingsAccess & mAccess
Definition: EffectEditor.h:92
void ModifySettings(Function &&function)
Do a correct read-modify-write of settings.
virtual const EffectSettings & Get()=0
static const LinearDBFormat & Instance()
CallbackReturn Publish(const EffectSettingChanged &message)
Send a message to connected callbacks.
Definition: Observer.h:207
RulerPanel class allows you to work with a Ruler like any other wxWindow.
Definition: RulerPanel.h:19
std::pair< double, double > Range
Definition: RulerPanel.h:23
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
wxColour & Colour(int iIndex)
Holds a msgid for the translation catalog; may also bind format arguments.
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
const T * cast() const noexcept
Like pointer-valued any_cast but a non-static member function.
Definition: TypedAny.h:68
auto TextToSliderValue(ExtendedCompressorParameter &setting)
DynamicRangeProcessorSettings GetSettings(EffectSettingsAccess &access)
auto MakeRulerPanel(wxWindow *parent, wxOrientation orientation, float dbRange, int id=wxID_ANY)
auto SliderToTextValue(double value, const ExtendedCompressorParameter &setting)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
Message sent by EffectEditor when a setting is changed by the user.
Definition: EffectEditor.h:26
Externalized state of a plug-in.
const std::shared_ptr< DynamicRangeProcessorParameter > param
const DynamicRangeProcessorParameterAttributes & attributes
Options & TickColour(const wxColour c)
Definition: RulerPanel.h:47
Options & TicksAtExtremes(bool t)
Definition: RulerPanel.h:44
Options & LabelEdges(bool l)
Definition: RulerPanel.h:41
Options & Flip(bool f)
Definition: RulerPanel.h:38