Audacity 3.2.0
CompressionMeterPanel.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 CompressionMeterPanel.cpp
7
8 Matthieu Hodgkinson
9
10**********************************************************************/
12#include "AudioIO.h"
13#include "CompressorInstance.h"
15#include <algorithm>
16#include <limits>
17#include <wx/dcclient.h>
18#include <wx/graphics.h>
19
20namespace
21{
22using namespace DynamicRangeProcessorPanel;
23
24constexpr auto timerId = 7000;
25// An overestimate: 44.1kHz and 512 samples per buffer, in frames per second,
26// would be 86.13.
27constexpr auto audioFramePerSec = 200;
29// This will be set as max size for the lock-free queue.
31} // namespace
32
33BEGIN_EVENT_TABLE(CompressionMeterPanel, wxPanelWrapper)
37
39 wxWindow* parent, int id, CompressorInstance& instance, float dbRange,
40 std::function<void()> onClipped)
41 : wxPanelWrapper { parent, id }
42 , mMeterValuesQueue { std::make_unique<
44 , mPlaybackStartStopSubscription { static_cast<
46 instance)
47 .Subscribe(
48 [&](const std::optional<
50 evt) {
51 if (evt)
52 Reset();
53 else
54 mStopWhenZero = true;
55 }) }
56 , mPlaybackPausedSubscription { AudioIO::Get()->Subscribe(
57 [this](const AudioIOEvent& evt) {
58 if (evt.type != AudioIOEvent::PAUSE)
59 return;
60 if (evt.on)
61 mTimer.Stop();
62 else
64 }) }
65 , mOnClipped { onClipped }
66 , mDbBottomEdgeValue { -dbRange }
67 , mCompressionMeter { MeterValueProvider::Create(
69 , mOutputMeter { MeterValueProvider::Create(
71{
72 if (instance.GetSampleRate().has_value())
73 // Playback is ongoing, and so the `InitializeProcessingSettings` event
74 // was already fired.
75 Reset();
76 instance.SetMeterValuesQueue(mMeterValuesQueue);
77 mTimer.SetOwner(this, timerId);
78 SetDoubleBuffered(true);
79}
80
82{
83 mDbBottomEdgeValue = -dbRange;
84 Refresh(true);
85}
86
88{
93 mStopWhenZero = false;
94 mClipped = false;
96}
97
99{
100 mClipped = false;
101}
102
103void CompressionMeterPanel::OnPaint(wxPaintEvent& evt)
104{
105 using namespace DynamicRangeProcessorPanel;
106
107 wxPaintDC dc(this);
108
109 const auto rect = DynamicRangeProcessorPanel::GetPanelRect(*this);
110 const auto gc = MakeGraphicsContext(dc);
111 const auto left = rect.GetLeft();
112 const auto top = rect.GetTop();
113 const auto width = rect.GetWidth();
114 const auto height = rect.GetHeight();
115
116 gc->SetPen(*wxTRANSPARENT_PEN);
117 gc->SetBrush(GetGraphBackgroundBrush(*gc, height));
118 gc->DrawRectangle(left, top, width, height);
119
120 auto leftRect = rect;
121 leftRect.SetWidth(rect.GetWidth() / 2 - 2);
122 leftRect.Offset(1, 0);
124
125 auto rightRect = leftRect;
126 rightRect.Offset(leftRect.GetWidth(), 0);
127 PaintMeter(dc, outputColor, rightRect, *mOutputMeter);
128
129 gc->SetPen(lineColor);
130 gc->SetBrush(wxNullBrush);
131 gc->DrawRectangle(left, top, width, height);
132}
133
135 wxPaintDC& dc, const wxColor& color, const wxRect& rect,
136 const MeterValueProvider& provider)
137{
138 const auto dB = provider.GetCurrentMax();
139 const auto maxDb = provider.GetGlobalMax();
140 const auto fiveSecMaxDb = provider.GetFiveSecMax();
141 const auto downwards =
143
144 const auto gc = MakeGraphicsContext(dc);
145
146 const double left = rect.GetLeft();
147 const double top = rect.GetTop();
148 const double width = rect.GetWidth();
149 const double height = rect.GetHeight();
150
151 constexpr auto lineWidth = 6.;
152
153 const double dbFrac = std::clamp<double>(dB / mDbBottomEdgeValue, 0., 1.);
154 // So that the top of the cap is aligned with the dB value.
155 const auto yAdjust = lineWidth / 2 * (downwards ? -1 : 1);
156 const double dbY = height * dbFrac + yAdjust;
157 const double maxDbY = maxDb / mDbBottomEdgeValue * height + yAdjust;
158 const double fiveSecMaxDbY =
159 fiveSecMaxDb / mDbBottomEdgeValue * height + yAdjust;
160
161 const auto levelTop = downwards ? top : dbY;
162 const auto levelHeight = downwards ? dbY : height - dbY;
163 const auto maxLevelTop = downwards ? levelHeight - top : maxDbY;
164 const auto maxLevelHeight = std::abs(maxDbY - dbY);
165
166 const auto rectLeft = left + 3;
167 const auto rectWidth = width - 4;
168 const auto opaqueColor = wxColor { color.GetRGB() };
169 gc->SetBrush(GetColorMix(opaqueColor, wxTransparentColor, 0.8));
170 gc->DrawRectangle(rectLeft, levelTop, rectWidth, levelHeight);
171 gc->SetBrush(GetColorMix(opaqueColor, wxTransparentColor, 0.6));
172 gc->DrawRectangle(rectLeft, maxLevelTop, rectWidth, maxLevelHeight);
173 gc->SetBrush(opaqueColor);
174 gc->DrawRectangle(rectLeft, dbY - 2, rectWidth, lineWidth);
175 gc->DrawRectangle(rectLeft, maxDbY - 2, rectWidth, lineWidth);
176 gc->SetPen({ opaqueColor, static_cast<int>(lineWidth / 2) });
177 gc->StrokeLine(
178 left + rectWidth / 2, fiveSecMaxDbY, left + rectWidth, fiveSecMaxDbY);
179}
180
181void CompressionMeterPanel::OnTimer(wxTimerEvent& evt)
182{
183 // Take the max of all values newly pushed by the audio frames - make sure we
184 // don't miss a peak.
186 auto lowestCompressionGain = 0.f;
187 auto highestOutputGain = std::numeric_limits<float>::lowest();
188 while (mMeterValuesQueue->Get(values))
189 {
190 lowestCompressionGain =
191 std::min(values.compressionGainDb, lowestCompressionGain);
192 highestOutputGain = std::max(values.outputDb, highestOutputGain);
193 }
194
195 const auto updateFiveSecondMax = !mStopWhenZero;
196 mCompressionMeter->Update(lowestCompressionGain, updateFiveSecondMax);
197 mOutputMeter->Update(highestOutputGain, updateFiveSecondMax);
198 const auto clipped = mOutputMeter->GetCurrentMax() > 0;
199 if (clipped && !mClipped)
200 {
201 mOnClipped();
202 mClipped = true;
203 }
204
205 Refresh(false);
206
207 if (
208 mCompressionMeter->IsInvisible() && mOutputMeter->IsInvisible() &&
210 {
211 // Decay is complete. Until playback starts again, no need for
212 // timer-triggered updates.
213 mTimer.Stop();
214 mStopWhenZero = false;
215 }
216}
217
219{
220 return false;
221}
222
224{
225 return false;
226}
END_EVENT_TABLE()
int min(int a, int b)
static constexpr auto compressorMeterUpdatePeriodMs
const wxChar * values
static AudioIO * Get()
Definition: AudioIO.cpp:126
bool AcceptsFocus() const override
const std::function< void()> mOnClipped
void SetDbRange(float dbRange)
std::unique_ptr< MeterValueProvider > mCompressionMeter
void OnTimer(wxTimerEvent &evt)
void PaintMeter(wxPaintDC &dc, const wxColor &color, const wxRect &rect, const MeterValueProvider &provider)
void OnPaint(wxPaintEvent &evt)
std::unique_ptr< MeterValueProvider > mOutputMeter
bool AcceptsFocusFromKeyboard() const override
const std::shared_ptr< DynamicRangeProcessorMeterValuesQueue > mMeterValuesQueue
virtual float GetCurrentMax() const =0
virtual float GetGlobalMax() const =0
static std::unique_ptr< MeterValueProvider > Create(Direction direction)
virtual float GetFiveSecMax() const =0
virtual Direction GetDirection() const =0
An object that sends messages to an open-ended list of subscribed callbacks.
Definition: Observer.h:108
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
wxColor GetColorMix(const wxColor &a, const wxColor &b, double aWeight)
std::unique_ptr< wxGraphicsContext > MakeGraphicsContext(const wxPaintDC &dc)
wxRect GetPanelRect(const wxPanelWrapper &panel)
wxGraphicsBrush GetGraphBackgroundBrush(wxGraphicsContext &gc, int height)
STL namespace.
bool on
Definition: AudioIO.h:68
enum AudioIOEvent::Type type