25#include <wx/dcclient.h>
26#include <wx/graphics.h>
27#include <wx/platinfo.h>
59 std::function<
void(
float)> onDbRangeChanged)
61 , mCompressorInstance { instance }
62 , mOnDbRangeChanged { std::move(onDbRangeChanged) }
63 , mInitializeProcessingSettingsSubscription {
static_cast<
67 [&](
const std::optional<
71 InitializeForPlayback(
85 .Subscribe([
this](
auto) {
87 mHistory->BeginNewSegment();
101 mTimer.Start(timerPeriodMs);
105 if (
const auto&
sampleRate = instance.GetSampleRate();
111 SetDoubleBuffered(
true);
112 mTimer.SetOwner(
this,
timerId);
120 const auto secondsPerPixel =
128 constexpr auto displayDelay = 0.2f;
129 return panelWidth - 1 -
130 (elapsedSincePacket - displayDelay) / secondsPerPixel;
140 std::vector<wxPoint2DDouble>&
A, std::vector<wxPoint2DDouble>& B)
142 assert(
A.size() == B.size());
143 if (
A.size() != B.size())
145 std::optional<bool> aWasBelow;
151 while (it !=
A.end())
153 const auto x2 = it->m_x;
154 const auto y2_a = it->m_y;
155 const auto y2_b = jt->m_y;
156 const auto aIsBelow = y2_a < y2_b;
157 if (aWasBelow.has_value() && *aWasBelow != aIsBelow)
167 x0 + (x2 - x0) * (y0_a - y0_b) / (y2_b - y2_a + y0_a - y0_b);
168 const auto y = y0_a + (x1 - x0) / (x2 - x0) * (y2_a - y0_a);
169 if (std::isfinite(x1) && std::isfinite(y))
171 it =
A.emplace(it, x1, y)++;
172 jt = B.emplace(jt, x1, y)++;
178 aWasBelow = aIsBelow;
189 std::vector<wxPoint2DDouble> lines,
const wxColor& color,
190 wxGraphicsContext& gc,
const wxRect& rect)
192 const auto height = rect.GetHeight();
193 const auto left = std::max<double>(rect.GetX(), lines.front().m_x);
194 const auto right = std::min<double>(rect.GetWidth(), lines.back().m_x);
195 auto area = gc.CreatePath();
196 area.MoveToPoint(right, height);
197 area.AddLineToPoint(left, height);
198 std::for_each(lines.begin(), lines.end(), [&area](
const auto& p) {
199 area.AddLineToPoint(p);
206void DrawLegend(
size_t height, wxPaintDC& dc, wxGraphicsContext& gc)
210 constexpr auto legendWidth = 16;
211 constexpr auto legendHeight = 16;
212 constexpr auto legendSpacing = 8;
213 constexpr auto legendX = 5;
214 const auto legendY = height - 5 - legendHeight;
215 const auto legendTextX = legendX + legendWidth + 5;
216 const auto legendTextHeight = dc.GetTextExtent(
"X").GetHeight();
217 const auto legendTextYOffset = (legendHeight - legendTextHeight) / 2;
218 const auto legendTextY = legendY + legendTextYOffset;
226 std::vector<LegendInfo> legends = {
231 int legendTextXOffset = 0;
232 dc.SetTextForeground(*wxBLACK);
234 { 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL });
235 for (
const auto& legend : legends)
239 gc.SetPen(*wxTRANSPARENT_PEN);
242 legendX + legendTextXOffset, legendY, legendWidth, legendHeight);
244 gc.SetBrush(wxColor { legend.color.GetRGB() });
246 legendX + legendTextXOffset, legendY, legendWidth, legendHeight);
249 gc.SetBrush(*wxTRANSPARENT_BRUSH);
251 legendX + legendTextXOffset, legendY, legendWidth, legendHeight);
254 legend.text.Translation(), legendTextX + legendTextXOffset,
256 const auto legendTextWidth =
257 dc.GetTextExtent(legend.text.Translation()).GetWidth();
258 legendTextXOffset += legendWidth + 5 + legendTextWidth + legendSpacing;
261 const auto lineY = legendY + legendHeight / 2.;
262 constexpr auto lineWidth = 24;
265 const auto actualX = legendX + legendTextXOffset + legendSpacing;
266 const std::array<wxPoint2DDouble, 2> actualLine {
267 wxPoint2DDouble(actualX, lineY),
268 wxPoint2DDouble(actualX + lineWidth, lineY)
272 gc.DrawLines(2, actualLine.data());
276 wxGraphicsPenInfo penInfo;
282 penInfo.LinearGradient(actualX, 0, actualX + lineWidth, 0, stops)
284 gc.SetPen(gc.CreatePen(penInfo));
289 gc.DrawLines(2, actualLine.data());
290 const auto actualText =
XO(
"Actual Compression");
291 const auto actualTextX = actualX + lineWidth + legendSpacing;
292 dc.DrawText(actualText.Translation(), actualTextX, legendTextY);
297 actualTextX + dc.GetTextExtent(actualText.Translation()).GetWidth() + 10;
298 gc.StrokeLine(targetX, lineY, targetX + lineWidth, lineY);
299 const auto compressionText =
XO(
"Target Compression");
301 compressionText.Translation(), targetX + lineWidth + 5, legendTextY);
337 const auto x = rect.GetX();
338 const auto y = rect.GetY();
339 const auto width = rect.GetWidth();
340 const auto height = rect.GetHeight();
343 gc->SetPen(wxTransparentColor);
344 gc->DrawRectangle(x, y, width, height);
351 constexpr auto drawLegend =
false;
354 gc->SetBrush(*wxTRANSPARENT_BRUSH);
356 gc->DrawRectangle(x, y, width, height);
363 const auto text =
XO(
"awaiting playback");
364 const wxDCFontChanger changer { dc,
365 { 16, wxFONTFAMILY_DEFAULT,
367 wxFONTWEIGHT_NORMAL } };
368 const auto textWidth = dc.GetTextExtent(text.Translation()).GetWidth();
369 const auto textHeight =
370 dc.GetTextExtent(text.Translation()).GetHeight();
371 dc.SetTextForeground(wxColor { 128, 128, 128 });
373 text.Translation(), (width - textWidth) / 2,
374 (height - textHeight) / 2);
379 const auto& segments =
mHistory->GetSegments();
380 const auto elapsedTimeSinceFirstPacket =
381 std::chrono::duration<float>(
mSync->now -
mSync->start).count();
383 const auto dbPerPixel = rangeDb / height;
385 for (
const auto& segment : segments)
393 mX.resize(segment.size());
394 auto lastInvisibleLeft = 0;
395 auto firstInvisibleRight = 0;
397 segment.begin(), segment.end(),
mX.begin(), [&](
const auto& packet) {
398 const auto x = GetDisplayPixel(
399 elapsedTimeSinceFirstPacket -
400 (packet.time - mSync->firstPacketTime),
405 ++firstInvisibleRight;
408 lastInvisibleLeft = std::max<int>(--lastInvisibleLeft, 0);
409 firstInvisibleRight = std::min<int>(++firstInvisibleRight,
mX.size());
411 mX.erase(
mX.begin() + firstInvisibleRight,
mX.end());
412 mX.erase(
mX.begin(),
mX.begin() + lastInvisibleLeft);
417 auto segmentIndex = lastInvisibleLeft;
422 std::for_each(
mX.begin(),
mX.end(), [&](
auto x) {
423 const auto& packet = segment[segmentIndex++];
424 const auto elapsedSincePacket = elapsedTimeSinceFirstPacket -
425 (packet.time - mSync->firstPacketTime);
426 mTarget.emplace_back(x, -packet.target / dbPerPixel);
427 mActual.emplace_back(x, -packet.follower / dbPerPixel);
428 mInput.emplace_back(x, -packet.input / dbPerPixel);
429 mOutput.emplace_back(x, -packet.output / dbPerPixel);
439 outputGc->SetPen({ wxColor {
outputColor.GetRGB() }, 2 });
458 wxGraphicsGradientStops stops;
459 const auto xLeft =
mActual.front().m_x;
460 const auto xRight =
mActual.back().m_x;
461 const auto span = xRight - xLeft;
462 for (
auto i = 0; i <
mActual.size(); ++i)
465 const auto actualIsBelow = diff < 0;
466 const auto w =
std::min(1.0, std::abs(diff) * dbPerPixel / 6);
471 stops.Add(color, (
mActual[i].m_x - xLeft) / span);
473 wxGraphicsPenInfo penInfo;
479 actualGc->SetPen(actualGc->CreatePen(penInfo));
529 wxPanelWrapper::Update();
540 constexpr auto leastPacketSize = 100;
550 std::make_unique<DynamicRangeProcessorOutputPacketQueue>(maxQueueSize);
555 wxPanelWrapper::Update();
float GetLatencyMs() const
void SetOutputQueue(std::weak_ptr< DynamicRangeProcessorOutputPacketQueue >)
std::chrono::steady_clock::time_point GetNow() const
static constexpr auto maxTimeSeconds
DynamicRangeProcessorClock mClock
std::vector< wxPoint2DDouble > mOutput
bool mPlaybackAboutToStart
bool AcceptsFocusFromKeyboard() const override
std::optional< DynamicRangeProcessorHistory > mHistory
void ShowInput(bool show)
std::optional< ClockSynchronization > mSync
void OnTimer(wxTimerEvent &evt)
void InitializeForPlayback(CompressorInstance &instance, double sampleRate)
bool AcceptsFocus() const override
std::vector< wxPoint2DDouble > mTarget
void ShowOutput(bool show)
std::shared_ptr< DynamicRangeProcessorOutputPacketQueue > mOutputQueue
void ShowTarget(bool show)
void OnSize(wxSizeEvent &evt)
std::vector< wxPoint2DDouble > mInput
void ShowActual(bool show)
std::vector< wxPoint2DDouble > mActual
std::vector< DynamicRangeProcessorOutputPacket > mPacketBuffer
const std::function< void(float)> mOnDbRangeChanged
CompressorInstance & mCompressorInstance
void OnPaint(wxPaintEvent &evt)
An object that sends messages to an open-ended list of subscribed callbacks.
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Holds a msgid for the translation catalog; may also bind format arguments.
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
wxColor GetColorMix(const wxColor &a, const wxColor &b, double aWeight)
static const wxColour targetCompressionColor
static const wxColour actualCompressionColor
float GetGraphDbRange(int height)
static constexpr auto targetCompressionLineWidth
std::unique_ptr< wxGraphicsContext > MakeGraphicsContext(const wxPaintDC &dc)
static const wxColour attackColor
static constexpr auto graphMinHeight
static const wxColour backgroundColor
wxRect GetPanelRect(const wxPanelWrapper &panel)
static const wxColour releaseColor
static const wxColour inputColor
static const wxColour lineColor
wxGraphicsBrush GetGraphBackgroundBrush(wxGraphicsContext &gc, int height)
static const wxColour outputColor
constexpr auto sampleRate
double GetDisplayPixel(float elapsedSincePacket, int panelWidth)
constexpr auto timerPeriodMs
void FillUpTo(std::vector< wxPoint2DDouble > lines, const wxColor &color, wxGraphicsContext &gc, const wxRect &rect)
void DrawLegend(size_t height, wxPaintDC &dc, wxGraphicsContext &gc)
bool MayUsePenGradients()
int GetActualCompressionLineWidth()
void InsertCrossings(std::vector< wxPoint2DDouble > &A, std::vector< wxPoint2DDouble > &B)
enum AudioIOEvent::Type type
"finally" as in The C++ Programming Language, 4th ed., p. 358 Useful for defining ad-hoc RAII actions...