Audacity 3.2.0
MeterPanel.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 MeterPanel.cpp
6
7 Dominic Mazzoni
8 Vaughan Johnson
9
10 2004.06.25 refresh rate limited to 30mS, by ChackoN
11
12*******************************************************************//****************************************************************//****************************************************************//****************************************************************//******************************************************************/
40
41#include "MeterPanel.h"
42
43#include <algorithm>
44#include <wx/setup.h> // for wxUSE_* macros
45#include <wx/wxcrtvararg.h>
46#include <wx/defs.h>
47#include <wx/dialog.h>
48#include <wx/dcbuffer.h>
49#include <wx/frame.h>
50#include <wx/image.h>
51#include <wx/intl.h>
52#include <wx/menu.h>
53#include <wx/settings.h>
54#include <wx/textdlg.h>
55#include <wx/numdlg.h>
56#include <wx/radiobut.h>
57#include <wx/tooltip.h>
58
59#include <math.h>
60
61#include "../AudioIO.h"
62#include "AColor.h"
63#include "../widgets/BasicMenu.h"
64#include "ImageManipulation.h"
65#include "Decibels.h"
66#include "Project.h"
67#include "../ProjectAudioManager.h"
68#include "ProjectStatus.h"
69#include "../ProjectWindows.h"
70#include "Prefs.h"
71#include "../ShuttleGui.h"
72#include "Theme.h"
73#include "../widgets/wxWidgetsWindowPlacement.h"
74
75#include "AllThemeResources.h"
76#include "../widgets/valnum.h"
77
78#if wxUSE_ACCESSIBILITY
79#include "WindowAccessible.h"
80
81class MeterAx final : public WindowAccessible
82{
83public:
84 MeterAx(wxWindow * window);
85
86 virtual ~ MeterAx();
87
88 // Performs the default action. childId is 0 (the action for this object)
89 // or > 0 (the action for a child).
90 // Return wxACC_NOT_SUPPORTED if there is no default action for this
91 // window (e.g. an edit control).
92 wxAccStatus DoDefaultAction(int childId) override;
93
94 // Retrieves the address of an IDispatch interface for the specified child.
95 // All objects must support this property.
96 wxAccStatus GetChild(int childId, wxAccessible** child) override;
97
98 // Gets the number of children.
99 wxAccStatus GetChildCount(int* childCount) override;
100
101 // Gets the default action for this object (0) or > 0 (the action for a child).
102 // Return wxACC_OK even if there is no action. actionName is the action, or the empty
103 // string if there is no action.
104 // The retrieved string describes the action that is performed on an object,
105 // not what the object does as a result. For example, a toolbar button that prints
106 // a document has a default action of "Press" rather than "Prints the current document."
107 wxAccStatus GetDefaultAction(int childId, wxString *actionName) override;
108
109 // Returns the description for this object or a child.
110 wxAccStatus GetDescription(int childId, wxString *description) override;
111
112 // Gets the window with the keyboard focus.
113 // If childId is 0 and child is NULL, no object in
114 // this subhierarchy has the focus.
115 // If this object has the focus, child should be 'this'.
116 wxAccStatus GetFocus(int *childId, wxAccessible **child) override;
117
118 // Returns help text for this object or a child, similar to tooltip text.
119 wxAccStatus GetHelpText(int childId, wxString *helpText) override;
120
121 // Returns the keyboard shortcut for this object or child.
122 // Return e.g. ALT+K
123 wxAccStatus GetKeyboardShortcut(int childId, wxString *shortcut) override;
124
125 // Returns the rectangle for this object (id = 0) or a child element (id > 0).
126 // rect is in screen coordinates.
127 wxAccStatus GetLocation(wxRect& rect, int elementId) override;
128
129 // Returns a role constant.
130 wxAccStatus GetRole(int childId, wxAccRole *role) override;
131
132 // Gets a variant representing the selected children
133 // of this object.
134 // Acceptable values:
135 // - a null variant (IsNull() returns TRUE)
136 // - a list variant (GetType() == wxT("list"))
137 // - an integer representing the selected child element,
138 // or 0 if this object is selected (GetType() == wxT("long"))
139 // - a "void*" pointer to a wxAccessible child object
140 wxAccStatus GetSelections(wxVariant *selections) override;
141
142 // Returns a state constant.
143 wxAccStatus GetState(int childId, long* state) override;
144
145 // Returns a localized string representing the value for the object
146 // or child.
147 wxAccStatus GetValue(int childId, wxString* strValue) override;
148
149};
150
151#endif // wxUSE_ACCESSIBILITY
152
153static const long MIN_REFRESH_RATE = 1;
154static const long MAX_REFRESH_RATE = 100;
155
156/* Updates to the meter are passed across via meter updates, each contained in
157 * a MeterUpdateMsg object */
159{
160wxString output; // somewhere to build up a string in
161output = wxString::Format(wxT("Meter update msg: %i channels, %i samples\n"), \
163for (int i = 0; i<kMaxMeterBars; i++)
164 { // for each channel of the meters
165 output += wxString::Format(wxT("%f peak, %f rms "), peak[i], rms[i]);
166 if (clipping[i])
167 output += wxString::Format(wxT("clipped "));
168 else
169 output += wxString::Format(wxT("no clip "));
170 output += wxString::Format(wxT("%i head, %i tail\n"), headPeakCount[i], tailPeakCount[i]);
171 }
172return output;
173}
174
176{
177 for (int i = 0; i<kMaxMeterBars; i++)
178 {
179 if (clipping[i] || (headPeakCount[i] > 0) || (tailPeakCount[i] > 0))
180 return toString();
181 }
182 return wxT("");
183}
184
185//
186// The MeterPanel passes itself messages via this queue so that it can
187// communicate between the audio thread and the GUI thread.
188// This class uses lock-free synchronization with atomics.
189//
190
192 mBufferSize(maxLen)
193{
194 Clear();
195}
196
197// destructor
199{
200}
201
203{
204 mStart.store(0);
205 mEnd.store(0);
206}
207
208// Add a message to the end of the queue. Return false if the
209// queue was full.
211{
212 auto start = mStart.load(std::memory_order_acquire);
213 auto end = mEnd.load(std::memory_order_relaxed);
214 // mStart can be greater than mEnd because it is all mod mBufferSize
215 assert( (end + mBufferSize - start) >= 0 );
216 int len = (end + mBufferSize - start) % mBufferSize;
217
218 // Never completely fill the queue, because then the
219 // state is ambiguous (mStart==mEnd)
220 if (len + 1 >= (int)(mBufferSize))
221 return false;
222
223 //wxLogDebug(wxT("Put: %s"), msg.toString());
224
225 mBuffer[end] = msg;
226 mEnd.store((end + 1) % mBufferSize, std::memory_order_release);
227
228 return true;
229}
230
231// Get the next message from the start of the queue.
232// Return false if the queue was empty.
234{
235 auto start = mStart.load(std::memory_order_relaxed);
236 auto end = mEnd.load(std::memory_order_acquire);
237 int len = (end + mBufferSize - start) % mBufferSize;
238
239 if (len == 0)
240 return false;
241
242 msg = mBuffer[start];
243 mStart.store((start + 1) % mBufferSize, std::memory_order_release);
244
245 return true;
246}
247
248//
249// MeterPanel class
250//
251
252#include "../../images/SpeakerMenu.xpm"
253#include "../../images/MicMenu.xpm"
254
255// How many pixels between items?
256const static int gap = 2;
257
258const static wxChar *PrefStyles[] =
259{
260 wxT("AutomaticStereo"),
261 wxT("HorizontalStereo"),
262 wxT("VerticalStereo")
263};
264
265enum {
271
272BEGIN_EVENT_TABLE(MeterPanel, MeterPanelBase)
275 EVT_SLIDER(wxID_ANY, MeterPanel::SetMixer)
276 EVT_MOUSE_EVENTS(MeterPanel::OnMouse)
277 EVT_CONTEXT_MENU(MeterPanel::OnContext)
278 EVT_KEY_DOWN(MeterPanel::OnKeyDown)
279 EVT_CHAR_HOOK(MeterPanel::OnCharHook)
280 EVT_SET_FOCUS(MeterPanel::OnSetFocus)
281 EVT_KILL_FOCUS(MeterPanel::OnKillFocus)
282 EVT_ERASE_BACKGROUND(MeterPanel::OnErase)
283 EVT_PAINT(MeterPanel::OnPaint)
284 EVT_SIZE(MeterPanel::OnSize)
288
290
292 wxWindow* parent, wxWindowID id,
293 bool isInput,
294 const wxPoint& pos /*= wxDefaultPosition*/,
295 const wxSize& size /*= wxDefaultSize*/,
296 Style style /*= HorizontalStereo*/,
297 float fDecayRate /*= 60.0f*/)
298: MeterPanelBase(parent, id, pos, size, wxTAB_TRAVERSAL | wxNO_BORDER | wxWANTS_CHARS),
299 mProject(project),
300 mQueue{ 1024 },
301 mWidth(size.x),
302 mHeight(size.y),
303 mIsInput(isInput),
304 mDesiredStyle(style),
305 mGradient(true),
306 mDB(true),
307 mDBRange(DecibelScaleCutoff.Read()),
308 mDecay(true),
309 mDecayRate(fDecayRate),
310 mClip(true),
311 mNumPeakSamplesToClip(3),
312 mPeakHoldDuration(3),
313 mT(0),
314 mRate(0),
315 mMonitoring(false),
316 mActive(false),
317 mNumBars(0),
318 mLayoutValid(false),
319 mBitmap{}
320{
321 // i18n-hint: Noun (the meter is used for playback or record level monitoring)
322 SetName( XO("Meter") );
323 // Suppress warnings about the header file
324 wxUnusedVar(SpeakerMenu_xpm);
325 wxUnusedVar(MicMenu_xpm);
326 wxUnusedVar(PrefStyles);
327
328 mStyle = mDesiredStyle;
329
330 mIsFocused = false;
331
332#if wxUSE_ACCESSIBILITY
333 SetAccessible(safenew MeterAx(this));
334#endif
335
336 // Do this BEFORE UpdatePrefs()!
337 mRuler.SetFonts(GetFont(), GetFont(), GetFont());
338 mRuler.SetFlip(mStyle != MixerTrackCluster);
339 mRuler.SetLabelEdges(true);
340 //mRuler.SetTickColour( wxColour( 0,0,255 ) );
341
342 if (mStyle != MixerTrackCluster)
343 {
344 mSlider = std::make_unique<LWSlider>(this, XO(""),
345 pos,
346 size,
348 false, /* showlabels */
349 false, /* drawticks */
350 false, /* drawtrack */
351 false /* alwayshidetip */
352 );
353 mSlider->SetScroll(0.1f, 2.0f);
354 }
355
356 UpdateSliderControl();
357 UpdatePrefs();
358
359 wxColour backgroundColour = theTheme.Colour( clrMedium);
360 mBkgndBrush = wxBrush(backgroundColour, wxBRUSHSTYLE_SOLID);
361 SetBackgroundColour( backgroundColour );
362
363 mPeakPeakPen = wxPen(theTheme.Colour( clrMeterPeak), 1, wxPENSTYLE_SOLID);
364 mDisabledPen = wxPen(theTheme.Colour( clrMeterDisabledPen), 1, wxPENSTYLE_SOLID);
365
366 mAudioIOStatusSubscription = AudioIO::Get()
368
369 mAudioCaptureSubscription = AudioIO::Get()
371
372 if (mIsInput) {
373 mPen = wxPen( theTheme.Colour( clrMeterInputPen ), 1, wxPENSTYLE_SOLID);
374 mBrush = wxBrush( theTheme.Colour( clrMeterInputBrush ), wxBRUSHSTYLE_SOLID);
375 mRMSBrush = wxBrush( theTheme.Colour( clrMeterInputRMSBrush ), wxBRUSHSTYLE_SOLID);
376 mClipBrush = wxBrush( theTheme.Colour( clrMeterInputClipBrush ), wxBRUSHSTYLE_SOLID);
377// mLightPen = wxPen( theTheme.Colour( clrMeterInputLightPen ), 1, wxSOLID);
378// mDarkPen = wxPen( theTheme.Colour( clrMeterInputDarkPen ), 1, wxSOLID);
379 }
380 else {
381 mPen = wxPen( theTheme.Colour( clrMeterOutputPen ), 1, wxPENSTYLE_SOLID);
382 mBrush = wxBrush( theTheme.Colour( clrMeterOutputBrush ), wxBRUSHSTYLE_SOLID);
383 mRMSBrush = wxBrush( theTheme.Colour( clrMeterOutputRMSBrush ), wxBRUSHSTYLE_SOLID);
384 mClipBrush = wxBrush( theTheme.Colour( clrMeterOutputClipBrush ), wxBRUSHSTYLE_SOLID);
385// mLightPen = wxPen( theTheme.Colour( clrMeterOutputLightPen ), 1, wxSOLID);
386// mDarkPen = wxPen( theTheme.Colour( clrMeterOutputDarkPen ), 1, wxSOLID);
387 }
388
389// mDisabledBkgndBrush = wxBrush(theTheme.Colour( clrMeterDisabledBrush), wxSOLID);
390 // No longer show a difference in the background colour when not monitoring.
391 // We have the tip instead.
392 mDisabledBkgndBrush = mBkgndBrush;
393
394 mTipTimer.SetOwner(this, OnTipTimeoutID);
395 mTimer.SetOwner(this, OnMeterUpdateID);
396 // TODO: Yikes. Hard coded sample rate.
397 // JKC: I've looked at this, and it's benignish. It just means that the meter
398 // balistics are right for 44KHz and a bit more frisky than they should be
399 // for higher sample rates.
400 Reset(44100.0, true);
401}
402
404{
405 mQueue.Clear();
406}
407
409{
411
414 gPrefs->Read(Key(wxT("RefreshRate")), 30)));
415 mGradient = gPrefs->Read(Key(wxT("Bars")), wxT("Gradient")) == wxT("Gradient");
416 mDB = gPrefs->Read(Key(wxT("Type")), wxT("dB")) == wxT("dB");
417 mMeterDisabled = gPrefs->Read(Key(wxT("Disabled")), (long)0);
418
420 {
421 wxString style = gPrefs->Read(Key(wxT("Style")));
422 if (style == wxT("AutomaticStereo"))
423 {
425 }
426 else if (style == wxT("HorizontalStereo"))
427 {
429 }
430 else if (style == wxT("VerticalStereo"))
431 {
433 }
434 else
435 {
437 }
438 }
439
440 // Set the desired orientation (resets ruler orientation)
442
443 // Reset to ensure NEW size is retrieved when language changes
444 mLeftSize = wxSize(0, 0);
445 mRightSize = wxSize(0, 0);
446
447 Reset(mRate, false);
448
449 mLayoutValid = false;
450
451 Refresh(false);
452}
453
454static int MeterPrefsID()
455{
456 static int value = wxNewId();
457 return value;
458}
459
461{
462 if (id == MeterPrefsID())
463 {
464#if USE_PORTMIXER
465 if (mIsInput && mSlider)
466 {
467 // Show or hide the input slider based on whether it works
468 auto gAudioIO = AudioIO::Get();
469 mSlider->SetEnabled(mEnabled && gAudioIO->InputMixerWorks());
470 }
471#endif
472 UpdatePrefs();
473 }
474}
475
477{
478#if USE_PORTMIXER
479 float inputVolume;
480 float playbackVolume;
481 int inputSource;
482
483 // Show or hide the input slider based on whether it works
484 auto gAudioIO = AudioIO::Get();
485 if (mIsInput && mSlider)
486 mSlider->SetEnabled(mEnabled && gAudioIO->InputMixerWorks());
487
488 gAudioIO->GetMixer(&inputSource, &inputVolume, &playbackVolume);
489
490 const auto volume = mIsInput ? inputVolume : playbackVolume;
491
492 if (mSlider && (mSlider->Get() != volume))
493 mSlider->Set(volume);
494
495#endif // USE_PORTMIXER
496}
497
498void MeterPanel::OnErase(wxEraseEvent & WXUNUSED(event))
499{
500 // Ignore it to prevent flashing
501}
502
503void MeterPanel::OnPaint(wxPaintEvent & WXUNUSED(event))
504{
505#if defined(__WXMAC__)
506 auto paintDC = std::make_unique<wxPaintDC>(this);
507#else
508 std::unique_ptr<wxDC> paintDC{ wxAutoBufferedPaintDCFactory(this) };
509#endif
510 wxDC & destDC = *paintDC;
511 wxColour clrText = theTheme.Colour( clrTrackPanelText );
512 wxColour clrBoxFill = theTheme.Colour( clrMedium );
513
514 if (mLayoutValid == false || (mStyle == MixerTrackCluster ))
515 {
516 // Create a NEW one using current size and select into the DC
517 mBitmap = std::make_unique<wxBitmap>();
518 mBitmap->Create(mWidth, mHeight, destDC);
519 wxMemoryDC dc;
520 dc.SelectObject(*mBitmap);
521
522 // Go calculate all of the layout metrics
523 HandleLayout(dc);
524
525 // Start with a clean background
526 // LLL: Should research USE_AQUA_THEME usefulness...
527//#ifndef USE_AQUA_THEME
528#ifdef EXPERIMENTAL_THEMING
529 //if( !mMeterDisabled )
530 //{
531 // mBkgndBrush.SetColour( GetParent()->GetBackgroundColour() );
532 //}
533#endif
534 mBkgndBrush.SetColour( GetBackgroundColour() );
535 dc.SetPen(*wxTRANSPARENT_PEN);
536 dc.SetBrush(mBkgndBrush);
537 dc.DrawRectangle(0, 0, mWidth, mHeight);
538//#endif
539
540 // MixerTrackCluster style has no icon or L/R labels
542 {
543 dc.SetFont(GetFont());
544 dc.SetTextForeground( clrText );
545 dc.SetTextBackground( clrBoxFill );
546 dc.DrawText(mLeftText, mLeftTextPos.x, mLeftTextPos.y);
547 dc.DrawText(mRightText, mRightTextPos.x, mRightTextPos.y);
548 }
549
550 // Setup the colors for the 3 sections of the meter bars
551 wxColor green(117, 215, 112);
552 wxColor yellow(255, 255, 0);
553 wxColor red(255, 0, 0);
554
555 // Bug #2473 - (Sort of) Hack to make text on meters more
556 // visible with darker backgrounds. It would be better to have
557 // different colors entirely and as part of the theme.
558 if (GetBackgroundColour().GetLuminance() < 0.25)
559 {
560 green = wxColor(117-100, 215-100, 112-100);
561 yellow = wxColor(255-100, 255-100, 0);
562 red = wxColor(255-100, 0, 0);
563 }
564 else if (GetBackgroundColour().GetLuminance() < 0.50)
565 {
566 green = wxColor(117-50, 215-50, 112-50);
567 yellow = wxColor(255-50, 255-50, 0);
568 red = wxColor(255-50, 0, 0);
569 }
570
571 // Draw the meter bars at maximum levels
572 for (unsigned int i = 0; i < mNumBars; i++)
573 {
574 // Give it a recessed look
575 AColor::Bevel(dc, false, mBar[i].b);
576
577 // Draw the clip indicator bevel
578 if (mClip)
579 {
580 AColor::Bevel(dc, false, mBar[i].rClip);
581 }
582
583 // Cache bar rect
584 wxRect r = mBar[i].r;
585
586 if (mGradient)
587 {
588 // Calculate the size of the two gradiant segments of the meter
589 double gradw;
590 double gradh;
591 if (mDB)
592 {
593 gradw = (double) r.GetWidth() / mDBRange * 6.0;
594 gradh = (double) r.GetHeight() / mDBRange * 6.0;
595 }
596 else
597 {
598 gradw = (double) r.GetWidth() / 100 * 25;
599 gradh = (double) r.GetHeight() / 100 * 25;
600 }
601
602 if (mBar[i].vert)
603 {
604 // Draw the "critical" segment (starts at top of meter and works down)
605 r.SetHeight(gradh);
606 dc.GradientFillLinear(r, red, yellow, wxSOUTH);
607
608 // Draw the "warning" segment
609 r.SetTop(r.GetBottom());
610 dc.GradientFillLinear(r, yellow, green, wxSOUTH);
611
612 // Draw the "safe" segment
613 r.SetTop(r.GetBottom());
614 r.SetBottom(mBar[i].r.GetBottom());
615 dc.SetPen(*wxTRANSPARENT_PEN);
616 dc.SetBrush(green);
617 dc.DrawRectangle(r);
618 }
619 else
620 {
621 // Draw the "safe" segment
622 r.SetWidth(r.GetWidth() - (int) (gradw + gradw + 0.5));
623 dc.SetPen(*wxTRANSPARENT_PEN);
624 dc.SetBrush(green);
625 dc.DrawRectangle(r);
626
627 // Draw the "warning" segment
628 r.SetLeft(r.GetRight() + 1);
629 r.SetWidth(floor(gradw));
630 dc.GradientFillLinear(r, green, yellow);
631
632 // Draw the "critical" segment
633 r.SetLeft(r.GetRight() + 1);
634 r.SetRight(mBar[i].r.GetRight());
635 dc.GradientFillLinear(r, yellow, red);
636 }
637#ifdef EXPERIMENTAL_METER_LED_STYLE
638 if (!mBar[i].vert)
639 {
640 wxRect r = mBar[i].r;
641 wxPen BackgroundPen;
642 BackgroundPen.SetColour( wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE) );
643 dc.SetPen( BackgroundPen );
644 int i;
645 for(i=0;i<r.width;i++)
646 {
647 // 2 pixel spacing between the LEDs
648 if( (i%7)<2 ){
649 AColor::Line( dc, i+r.x, r.y, i+r.x, r.y+r.height );
650 } else {
651 // The LEDs have triangular ends.
652 // This code shapes the ends.
653 int j = abs( (i%7)-4);
654 AColor::Line( dc, i+r.x, r.y, i+r.x, r.y+j +1);
655 AColor::Line( dc, i+r.x, r.y+r.height-j, i+r.x, r.y+r.height );
656 }
657 }
658 }
659#endif
660 }
661 }
662 mRuler.SetTickColour( clrText );
663 dc.SetTextForeground( clrText );
664 // Draw the ruler
665#ifndef EXPERIMENTAL_DA
666 mRuler.Draw(dc);
667#endif
668
669 // Bitmap created...unselect
670 dc.SelectObject(wxNullBitmap);
671 }
672
673 // Copy predrawn bitmap to the dest DC
674 destDC.DrawBitmap(*mBitmap, 0, 0);
675
676 // Go draw the meter bars, Left & Right channels using current levels
677 for (unsigned int i = 0; i < mNumBars; i++)
678 {
679 DrawMeterBar(destDC, &mBar[i]);
680 }
681
682 destDC.SetTextForeground( clrText );
683
684#ifndef EXPERIMENTAL_DA
685 // We can have numbers over the bars, in which case we have to draw them each time.
687 {
688 mRuler.SetTickColour( clrText );
689 // If the text colour is too similar to the meter colour, then we need a background
690 // for the text. We require a total of at least one full-scale RGB difference.
691 int d = theTheme.ColourDistance( clrText, theTheme.Colour( clrMeterOutputRMSBrush ) );
692 if( d < 256 )
693 {
694 destDC.SetBackgroundMode( wxSOLID );
695 destDC.SetTextBackground( clrBoxFill );
696 }
697 mRuler.Draw(destDC);
698 }
699#endif
700
702 {
703 bool highlighted =
704 wxRect{ mSliderPos, mSliderSize }.Contains(
705 ScreenToClient(
706 ::wxGetMousePosition()));
707
708 mSlider->Move(mSliderPos);
709 mSlider->AdjustSize(mSliderSize);
710 mSlider->OnPaint(destDC, highlighted);
711 }
712
713 if (mIsFocused)
714 {
715 auto r = GetClientRect();
716 AColor::DrawFocus(destDC, r);
717 }
718}
719
720void MeterPanel::OnSize(wxSizeEvent & WXUNUSED(event))
721{
722 GetClientSize(&mWidth, &mHeight);
723
724 mLayoutValid = false;
725 Refresh();
726}
727
728void MeterPanel::OnMouse(wxMouseEvent &evt)
729{
730 if ((evt.GetEventType() == wxEVT_MOTION || evt.Entering() || evt.Leaving())) {
731 mLayoutValid = false;
732 Refresh();
733 }
734
735 if (mStyle == MixerTrackCluster) // MixerTrackCluster style has no menu.
736 return;
737
738 if (evt.Entering()) {
739 mTipTimer.StartOnce(500);
740 }
741 else if(evt.Leaving())
742 mTipTimer.Stop();
743
744 if (evt.RightDown())
745 ShowMenu(evt.GetPosition());
746 else
747 {
748
749 if (mSlider)
750 mSlider->OnMouseEvent(evt);
751 }
752}
753
754void MeterPanel::OnCharHook(wxKeyEvent& evt)
755{
756 switch(evt.GetKeyCode())
757 {
758 // These are handled in the OnCharHook handler because, on Windows at least, the
759 // key up event will be passed on to the menu if we show it here. This causes
760 // the default sound to be heard if assigned.
761 case WXK_RETURN:
762 case WXK_NUMPAD_ENTER:
763 case WXK_WINDOWS_MENU:
764 case WXK_MENU:
766 ShowMenu(GetClientRect().GetBottomLeft());
767 else
768 evt.Skip();
769 break;
770 default:
771 evt.Skip();
772 break;
773 }
774}
775
776void MeterPanel::OnContext(wxContextMenuEvent &evt)
777{
778 if (mStyle != MixerTrackCluster) // MixerTrackCluster style has no menu.
779 {
780 ShowMenu(GetClientRect().GetBottomLeft());
781 }
782 else
783 {
784 evt.Skip();
785 }
786}
787
788void MeterPanel::OnKeyDown(wxKeyEvent &evt)
789{
790 switch (evt.GetKeyCode())
791 {
792 case WXK_TAB:
793 if (evt.ShiftDown())
794 Navigate(wxNavigationKeyEvent::IsBackward);
795 else
796 Navigate(wxNavigationKeyEvent::IsForward);
797 break;
798 default:
799 mSlider->OnKeyDown(evt);
800 break;
801 }
802}
803
804void MeterPanel::OnSetFocus(wxFocusEvent & WXUNUSED(evt))
805{
806 mIsFocused = true;
807 Refresh(false);
808}
809
810void MeterPanel::OnKillFocus(wxFocusEvent & WXUNUSED(evt))
811{
812 if(mSlider)
813 mSlider->OnKillFocus();
814 mTipTimer.Stop();
815
816 mIsFocused = false;
817 Refresh(false);
818}
819
821{
822 if (mStyle != newStyle && mDesiredStyle == AutomaticStereo)
823 {
824 SetActiveStyle(newStyle);
825
826 mLayoutValid = false;
827
828 Refresh(false);
829 }
830}
831
832void MeterPanel::SetMixer(wxCommandEvent & WXUNUSED(event))
833{
834#if USE_PORTMIXER
835 if (mSlider)
836 {
837 float inputVolume;
838 float outputVolume;
839 int inputSource;
840
841 Refresh();
842
843 auto gAudioIO = AudioIO::Get();
844 gAudioIO->GetMixer(&inputSource, &inputVolume, &outputVolume);
845
846 if (mIsInput)
847 inputVolume = mSlider->Get();
848 else
849 outputVolume = mSlider->Get();
850
851 gAudioIO->SetMixer(inputSource, inputVolume, outputVolume);
852
853#if wxUSE_ACCESSIBILITY
854 GetAccessible()->NotifyEvent( wxACC_EVENT_OBJECT_VALUECHANGE,
855 this,
856 wxOBJID_CLIENT,
857 wxACC_SELF );
858#endif
859
860 }
861#endif // USE_PORTMIXER
862}
863
865{
866 if (!mSlider)
867 return false;
868
869 auto changed = mSlider->ShowDialog();
870 if (changed)
871 {
872 wxCommandEvent e;
873 SetMixer(e);
874 }
875
876 return changed;
877}
878
879void MeterPanel::Increase(float steps)
880{
881 if (mSlider)
882 {
883 wxCommandEvent e;
884
885 mSlider->Increase(steps);
886 SetMixer(e);
887 }
888}
889
890void MeterPanel::Decrease(float steps)
891{
892 if (mSlider)
893 {
894 wxCommandEvent e;
895
896 mSlider->Decrease(steps);
897 SetMixer(e);
898 }
899}
900
901void MeterPanel::Reset(double sampleRate, bool resetClipping)
902{
903 mT = 0;
904 mRate = sampleRate;
905 for (int j = 0; j < kMaxMeterBars; j++)
906 {
907 ResetBar(&mBar[j], resetClipping);
908 }
909
910 // wxTimers seem to be a little unreliable - sometimes they stop for
911 // no good reason, so this "primes" it every now and then...
912 mTimer.Stop();
913
914 // While it's stopped, empty the queue
915 mQueue.Clear();
916
917 mLayoutValid = false;
918
919 mTimer.Start(1000 / mMeterRefreshRate);
920
921 Refresh(false);
922}
923
924static float floatMax(float a, float b)
925{
926 return a>b? a: b;
927}
928
929/* Unused as yet.
930static int intmin(int a, int b)
931{
932 return a<b? a: b;
933}
934*/
935
936static int intmax(int a, int b)
937{
938 return a>b? a: b;
939}
940
941static float ClipZeroToOne(float z)
942{
943 if (z > 1.0)
944 return 1.0;
945 else if (z < 0.0)
946 return 0.0;
947 else
948 return z;
949}
950
951static float ToDB(float v, float range)
952{
953 double db;
954 if (v > 0)
955 db = LINEAR_TO_DB(fabs(v));
956 else
957 db = -999;
958 return ClipZeroToOne((db + range) / range);
959}
960
962 unsigned numChannels, int numFrames, const float *sampleData)
963{
964 auto sptr = sampleData;
965 auto num = std::min(numChannels, mNumBars);
966 MeterUpdateMsg msg;
967
968 memset(&msg, 0, sizeof(msg));
969 msg.numFrames = numFrames;
970
971 for(int i=0; i<numFrames; i++) {
972 for(unsigned int j=0; j<num; j++) {
973 msg.peak[j] = floatMax(msg.peak[j], fabs(sptr[j]));
974 msg.rms[j] += sptr[j]*sptr[j];
975
976 // In addition to looking for mNumPeakSamplesToClip peaked
977 // samples in a row, also send the number of peaked samples
978 // at the head and tail, in case there's a run of peaked samples
979 // that crosses block boundaries
980 if (fabs(sptr[j])>=MAX_AUDIO) {
981 if (msg.headPeakCount[j]==i)
982 msg.headPeakCount[j]++;
983 msg.tailPeakCount[j]++;
985 msg.clipping[j] = true;
986 }
987 else
988 msg.tailPeakCount[j] = 0;
989 }
990 sptr += numChannels;
991 }
992 for(unsigned int j=0; j<mNumBars; j++)
993 msg.rms[j] = sqrt(msg.rms[j]/numFrames);
994
995 mQueue.Put(msg);
996}
997
998// Vaughan, 2010-11-29: This not currently used. See comments in MixerTrackCluster::UpdateMeter().
999//void MeterPanel::UpdateDisplay(int numChannels, int numFrames,
1000// // Need to make these double-indexed arrays if we handle more than 2 channels.
1001// float* maxLeft, float* rmsLeft,
1002// float* maxRight, float* rmsRight,
1003// const size_t kSampleCount)
1004//{
1005// int i, j;
1006// int num = intmin(numChannels, mNumBars);
1007// MeterUpdateMsg msg;
1008//
1009// msg.numFrames = kSampleCount;
1010// for(j=0; j<mNumBars; j++) {
1011// msg.peak[j] = 0.0;
1012// msg.rms[j] = 0.0;
1013// msg.clipping[j] = false;
1014// msg.headPeakCount[j] = 0;
1015// msg.tailPeakCount[j] = 0;
1016// }
1017//
1018// for(i=0; i<numFrames; i++) {
1019// for(j=0; j<num; j++) {
1020// msg.peak[j] = floatMax(msg.peak[j], ((j == 0) ? maxLeft[i] : maxRight[i]));
1021// msg.rms[j] = floatMax(msg.rms[j], ((j == 0) ? rmsLeft[i] : rmsRight[i]));
1022//
1023// // In addition to looking for mNumPeakSamplesToClip peaked
1024// // samples in a row, also send the number of peaked samples
1025// // at the head and tail, in case there's a run
1026// // of peaked samples that crosses block boundaries.
1027// if (fabs((j == 0) ? maxLeft[i] : maxRight[i]) >= MAX_AUDIO)
1028// {
1029// if (msg.headPeakCount[j]==i)
1030// msg.headPeakCount[j]++;
1031// msg.tailPeakCount[j]++;
1032// if (msg.tailPeakCount[j] > mNumPeakSamplesToClip)
1033// msg.clipping[j] = true;
1034// }
1035// else
1036// msg.tailPeakCount[j] = 0;
1037// }
1038// }
1039//
1040// mQueue.Put(msg);
1041//}
1042
1043void MeterPanel::OnMeterUpdate(wxTimerEvent & WXUNUSED(event))
1044{
1045 MeterUpdateMsg msg;
1046 int numChanges = 0;
1047#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1048 double maxPeak = 0.0;
1049 bool discarded = false;
1050#endif
1051
1052 // We shouldn't receive any events if the meter is disabled, but clear it to be safe
1053 if (mMeterDisabled) {
1054 mQueue.Clear();
1055 return;
1056 }
1057
1058
1059 // There may have been several update messages since the last
1060 // time we got to this function. Catch up to real-time by
1061 // popping them off until there are none left. It is necessary
1062 // to process all of them, otherwise we won't handle peaks and
1063 // peak-hold bars correctly.
1064 while(mQueue.Get(msg)) {
1065 numChanges++;
1066 double deltaT = msg.numFrames / mRate;
1067
1068 mT += deltaT;
1069 for(unsigned int j=0; j<mNumBars; j++) {
1070 mBar[j].isclipping = false;
1071
1072 //
1073 if (mDB) {
1074 msg.peak[j] = ToDB(msg.peak[j], mDBRange);
1075 msg.rms[j] = ToDB(msg.rms[j], mDBRange);
1076 }
1077
1078 if (mDecay) {
1079 if (mDB) {
1080 float decayAmount = mDecayRate * deltaT / mDBRange;
1081 mBar[j].peak = floatMax(msg.peak[j],
1082 mBar[j].peak - decayAmount);
1083 }
1084 else {
1085 double decayAmount = mDecayRate * deltaT;
1086 double decayFactor = DB_TO_LINEAR(-decayAmount);
1087 mBar[j].peak = floatMax(msg.peak[j],
1088 mBar[j].peak * decayFactor);
1089 }
1090 }
1091 else
1092 mBar[j].peak = msg.peak[j];
1093
1094 // This smooths out the RMS signal
1095 float smooth = pow(0.9, (double)msg.numFrames/1024.0);
1096 mBar[j].rms = mBar[j].rms * smooth + msg.rms[j] * (1.0 - smooth);
1097
1098 if (mT - mBar[j].peakHoldTime > mPeakHoldDuration ||
1099 mBar[j].peak > mBar[j].peakHold) {
1100 mBar[j].peakHold = mBar[j].peak;
1101 mBar[j].peakHoldTime = mT;
1102 }
1103
1104 if (mBar[j].peak > mBar[j].peakPeakHold )
1105 mBar[j].peakPeakHold = mBar[j].peak;
1106
1107 if (msg.clipping[j] ||
1108 mBar[j].tailPeakCount+msg.headPeakCount[j] >=
1110 mBar[j].clipping = true;
1111 mBar[j].isclipping = true;
1112 }
1113
1114 mBar[j].tailPeakCount = msg.tailPeakCount[j];
1115#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1116 if (mT > gAudioIO->AILAGetLastDecisionTime()) {
1117 discarded = false;
1118 maxPeak = msg.peak[j] > maxPeak ? msg.peak[j] : maxPeak;
1119 wxPrintf("%[email protected]%f ", msg.peak[j], mT);
1120 }
1121 else {
1122 discarded = true;
1123 wxPrintf("%[email protected]%f discarded\n", msg.peak[j], mT);
1124 }
1125#endif
1126 }
1127 } // while
1128
1129 if (numChanges > 0) {
1130 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1131 if (gAudioIO->AILAIsActive() && mIsInput && !discarded) {
1132 gAudioIO->AILAProcess(maxPeak);
1133 putchar('\n');
1134 }
1135 #endif
1137 }
1138}
1139
1140void MeterPanel::OnTipTimeout(wxTimerEvent& evt)
1141{
1142 if(mSlider)
1143 mSlider->ShowTip(true);
1144}
1145
1146
1148{
1149 float maxPeak = 0.;
1150
1151 for(unsigned int j=0; j<mNumBars; j++)
1152 maxPeak = mBar[j].peak > maxPeak ? mBar[j].peak : maxPeak;
1153
1154 return(maxPeak);
1155}
1156
1158{
1159 auto peakHold = .0f;
1160 for (unsigned int i = 0; i < mNumBars; i++)
1161 peakHold = std::max(peakHold, mBar[i].peakPeakHold);
1162 return peakHold;
1163}
1164
1166{
1167 int fontSize = 10;
1168#if defined __WXMSW__
1169 fontSize = 8;
1170#endif
1171
1172 return wxFont(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1173}
1174
1175void MeterPanel::ResetBar(MeterBar *b, bool resetClipping)
1176{
1177 b->peak = 0.0;
1178 b->rms = 0.0;
1179 b->peakHold = 0.0;
1180 b->peakHoldTime = 0.0;
1181 if (resetClipping)
1182 {
1183 b->clipping = false;
1184 b->peakPeakHold = 0.0;
1185 }
1186 b->isclipping = false;
1187 b->tailPeakCount = 0;
1188}
1189
1191{
1192 return mActive;
1193}
1194
1196{
1197 return mMonitoring;
1198}
1199
1201{
1202 for (int c = 0; c < mNumBars; c++)
1203 if (mBar[c].clipping)
1204 return true;
1205 return false;
1206}
1207
1209{
1210 mStyle = newStyle;
1211
1212 // Set dummy ruler bounds so width/height can be retrieved
1213 // NOTE: Make sure the Right and Bottom values are large enough to
1214 // ensure full width/height of digits get calculated.
1215 mRuler.SetBounds(0, 0, 500, 500);
1216
1217 if (mDB)
1218 {
1221 {
1222 mRuler.SetOrientation(wxHORIZONTAL);
1224 }
1225 else
1226 {
1227 mRuler.SetOrientation(wxVERTICAL);
1229 }
1230 }
1231 else
1232 {
1235 {
1236 mRuler.SetOrientation(wxHORIZONTAL);
1237 mRuler.SetRange(0, 1);
1238 }
1239 else
1240 {
1241 mRuler.SetOrientation(wxVERTICAL);
1242 mRuler.SetRange(1, 0);
1243 }
1244 }
1245
1247}
1248
1249void MeterPanel::SetBarAndClip(int iBar, bool vert)
1250{
1251 // Save the orientation
1252 mBar[iBar].vert = vert;
1253
1254 // Create the bar rectangle and educe to fit inside the bevel
1255 mBar[iBar].r = mBar[iBar].b;
1256 mBar[iBar].r.x += 1;
1257 mBar[iBar].r.width -= 1;
1258 mBar[iBar].r.y += 1;
1259 mBar[iBar].r.height -= 1;
1260
1261 if (vert)
1262 {
1263 if (mClip)
1264 {
1265 // Create the clip rectangle
1266 mBar[iBar].rClip = mBar[iBar].b;
1267 mBar[iBar].rClip.height = 3;
1268
1269 // Make room for the clipping indicator
1270 mBar[iBar].b.y += 3 + gap;
1271 mBar[iBar].b.height -= 3 + gap;
1272 mBar[iBar].r.y += 3 + gap;
1273 mBar[iBar].r.height -= 3 + gap;
1274 }
1275 }
1276 else
1277 {
1278 if (mClip)
1279 {
1280 // Make room for the clipping indicator
1281 mBar[iBar].b.width -= 4;
1282 mBar[iBar].r.width -= 4;
1283
1284 // Create the indicator rectangle
1285 mBar[iBar].rClip = mBar[iBar].b;
1286 mBar[iBar].rClip.x = mBar[iBar].b.GetRight() + 1 + gap; // +1 for bevel
1287 mBar[iBar].rClip.width = 3;
1288 }
1289 }
1290}
1291
1293{
1294 // Refresh to reflect any language changes
1295 /* i18n-hint: One-letter abbreviation for Left, in VU Meter */
1296 mLeftText = _("L");
1297 /* i18n-hint: One-letter abbreviation for Right, in VU Meter */
1298 mRightText = _("R");
1299
1300 dc.SetFont(GetFont());
1301 int width = mWidth;
1302 int height = mHeight;
1303 int left = 0;
1304 int top = 0;
1305 int barw;
1306 int barh;
1307 int lside;
1308 int rside;
1309
1310 // MixerTrackCluster has no L/R labels or icon
1312 {
1314 {
1316 }
1317
1319 {
1321 }
1323 {
1325 }
1326
1327 if (mLeftSize.GetWidth() == 0) // Not yet initialized to dc.
1328 {
1329 dc.GetTextExtent(mLeftText, &mLeftSize.x, &mLeftSize.y);
1330 dc.GetTextExtent(mRightText, &mRightSize.x, &mRightSize.y);
1331 }
1332 }
1333
1334 int ltxtWidth = mLeftSize.GetWidth();
1335 int ltxtHeight = mLeftSize.GetHeight();
1336 int rtxtWidth = mRightSize.GetWidth();
1337 int rtxtHeight = mRightSize.GetHeight();
1338
1339 switch (mStyle)
1340 {
1341 default:
1342 wxPrintf(wxT("Style not handled yet!\n"));
1343 break;
1344 case MixerTrackCluster:
1345 // width is now the entire width of the meter canvas
1346 width -= mRulerWidth + left;
1347
1348 // height is now the entire height of the meter canvas
1349 height -= top + gap;
1350
1351 // barw is half of the canvas while allowing for a gap between meters
1352 barw = (width - gap) / 2;
1353
1354 // barh is now the height of the canvas
1355 barh = height;
1356
1357 // We always have 2 bars
1358 mNumBars = 2;
1359
1360 // Save dimensions of the left bevel
1361 mBar[0].b = wxRect(left, top, barw, barh);
1362
1363 // Save dimensions of the right bevel
1364 mBar[1].b = mBar[0].b;
1365 mBar[1].b.SetLeft(mBar[0].b.GetRight() + 1 + gap); // +1 for right edge
1366
1367 // Set bar and clipping indicator dimensions
1368 SetBarAndClip(0, true);
1369 SetBarAndClip(1, true);
1370
1371 mRuler.SetBounds(mBar[1].r.GetRight() + 1, // +1 for the bevel
1372 mBar[1].r.GetTop(),
1373 mWidth,
1374 mBar[1].r.GetBottom());
1375 mRuler.OfflimitsPixels(0, 0);
1376 break;
1377 case VerticalStereo:
1378 // Determine required width of each side;
1379 lside = ltxtWidth + gap;
1380 rside = intmax(mRulerWidth, rtxtWidth);
1381
1382 // left is now the right edge of the icon or L label
1383 left = lside;
1384
1385 // Ensure there's a margin between top edge of window and the meters
1386 top = gap;
1387
1388 // Position the L/R labels
1389 mLeftTextPos = wxPoint(left - ltxtWidth - gap, height - gap - ltxtHeight);
1390 mRightTextPos = wxPoint(width - rside - gap, height - gap - rtxtHeight);
1391
1392 // left is now left edge of left bar
1393 left += gap;
1394
1395 // width is now the entire width of the meter canvas
1396 width -= gap + rside + gap + left;
1397
1398 // height is now the entire height of the meter canvas
1399 height -= top + gap;
1400
1401 mSliderPos = wxPoint{ 0, top - gap };
1402 mSliderSize = wxSize{ width, height + 2 * gap };
1403
1404 // barw is half of the canvas while allowing for a gap between meters
1405 barw = (width - gap) / 2;
1406
1407 // barh is now the height of the canvas
1408 barh = height;
1409
1410 // We always have 2 bars
1411 mNumBars = 2;
1412
1413 // Save dimensions of the left bevel
1414 mBar[0].b = wxRect(left, top, barw, barh);
1415
1416 // Save dimensions of the right bevel
1417 mBar[1].b = mBar[0].b;
1418 mBar[1].b.SetLeft(mBar[0].b.GetRight() + 1 + gap); // +1 for right edge
1419
1420 // Set bar and clipping indicator dimensions
1421 SetBarAndClip(0, true);
1422 SetBarAndClip(1, true);
1423
1424 mRuler.SetBounds(mBar[1].r.GetRight() + 1, // +1 for the bevel
1425 mBar[1].r.GetTop(),
1426 mWidth,
1427 mBar[1].r.GetBottom());
1428 mRuler.OfflimitsPixels(mRightTextPos.y - gap, mBar[1].r.GetBottom());
1429 break;
1431 // Ensure there's a margin between top edge of window and the meters
1432 top = gap;
1433
1434 // height is now the entire height of the meter canvas
1435 height -= top + gap + ltxtHeight + gap;
1436
1437 mSliderPos = wxPoint{ 0, top - gap };
1438 mSliderSize = wxSize{ width, height + 2 * gap };
1439
1440 // barw is half of the canvas while allowing for a gap between meters
1441 barw = (width / 2) - gap;
1442
1443 // barh is now the height of the canvas
1444 barh = height;
1445
1446 // We always have 2 bars
1447 mNumBars = 2;
1448
1449 // Save dimensions of the left bevel
1450 mBar[0].b = wxRect(left, top, barw, barh);
1451
1452 // Save dimensions of the right bevel
1453 mBar[1].b = mBar[0].b;
1454 mBar[1].b.SetLeft(mBar[0].b.GetRight() + 1 + gap); // +1 for right edge
1455
1456 // Set bar and clipping indicator dimensions
1457 SetBarAndClip(0, true);
1458 SetBarAndClip(1, true);
1459
1460 // L/R is centered horizontally under each bar
1461 mLeftTextPos = wxPoint(mBar[0].b.GetLeft() + ((mBar[0].b.GetWidth() - ltxtWidth) / 2), top + barh + gap);
1462 mRightTextPos = wxPoint(mBar[1].b.GetLeft() + ((mBar[1].b.GetWidth() - rtxtWidth) / 2), top + barh + gap);
1463
1465 mBar[1].r.GetTop(),
1466 (mWidth - mRulerWidth) / 2,
1467 mBar[1].r.GetBottom());
1468 mRuler.OfflimitsPixels(0, 0);
1469 break;
1470 case HorizontalStereo:
1471 // Button right next to dragger.
1472 left = 0;
1473
1474 // Add a gap between bottom of icon and bottom of window
1475 height -= gap;
1476
1477 left = gap;
1478
1479 // Make sure there's room for icon and gap between the bottom of the meter and icon
1480 height -= rtxtHeight + gap;
1481
1482 // L/R is centered vertically and to the left of a each bar
1483 mLeftTextPos = wxPoint(left, (height / 4) - ltxtHeight / 2);
1484 mRightTextPos = wxPoint(left, (height * 3 / 4) - rtxtHeight / 2);
1485
1486 // Add width of widest of the L/R characters
1487 left += intmax(ltxtWidth, rtxtWidth); //, iconWidth);
1488
1489 mSliderPos = wxPoint{ left - gap, 0 };
1490
1491 // Add gap between L/R and meter bevel
1492 left += gap;
1493
1494 // width is now the entire width of the meter canvas
1495 width -= left;
1496
1497 mSliderSize = wxSize{ width + 2 * gap, height };
1498
1499 // barw is now the width of the canvas minus gap between canvas and right window edge
1500 barw = width - gap;
1501
1502 // barh is half of the canvas while allowing for a gap between meters
1503 barh = (height - gap) / 2;
1504
1505 // We always have 2 bars
1506 mNumBars = 2;
1507
1508 // Save dimensions of the top bevel
1509 mBar[0].b = wxRect(left, top, barw, barh);
1510
1511 // Save dimensions of the bottom bevel
1512 mBar[1].b = mBar[0].b;
1513 mBar[1].b.SetTop(mBar[0].b.GetBottom() + 1 + gap); // +1 for bottom edge
1514
1515 // Set bar and clipping indicator dimensions
1516 SetBarAndClip(0, false);
1517 SetBarAndClip(1, false);
1518
1519 mRuler.SetBounds(mBar[1].r.GetLeft(),
1520 mBar[1].r.GetBottom() + 1, // +1 to fit below bevel
1521 mBar[1].r.GetRight(),
1522 mHeight - mBar[1].r.GetBottom() + 1);
1523 break;
1525 left = gap;
1526
1527 // L/R is centered vertically and to the left of a each bar
1528 mLeftTextPos = wxPoint(left, (height / 4) - (ltxtHeight / 2));
1529 mRightTextPos = wxPoint(left, (height * 3 / 4) - (ltxtHeight / 2));
1530
1531 // Add width of widest of the L/R characters
1532 left += intmax(ltxtWidth, rtxtWidth);
1533
1534 mSliderPos = wxPoint{ left - gap, 0 };
1535
1536 // Add gap between L/R and meter bevel
1537 left += gap;
1538
1539 // width is now the entire width of the meter canvas
1540 width -= left;
1541
1542 mSliderSize = wxSize{ width + 2 * gap, height };
1543
1544 // barw is now the width of the canvas minus gap between canvas and window edge
1545 barw = width - gap;
1546
1547 // barh is half of the canvas while allowing for a gap between meters
1548 barh = (height - gap) / 2;
1549
1550 // We always have 2 bars
1551 mNumBars = 2;
1552
1553 // Save dimensions of the top bevel
1554 mBar[0].b = wxRect(left, top, barw, barh);
1555
1556 // Save dimensions of the bottom bevel
1557 // Since the bars butt up against the window's top and bottom edges, we need
1558 // to include an extra pixel in the bottom bar when the window height and
1559 // meter height do not exactly match.
1560 mBar[1].b = mBar[0].b;
1561 mBar[1].b.SetTop(mBar[0].b.GetBottom() + 1 + gap); // +1 for bottom bevel
1562 mBar[1].b.SetHeight(mHeight - mBar[1].b.GetTop() - 1); // +1 for bottom bevel
1563
1564 // Add clipping indicators - do after setting bar/bevel dimensions above
1565 SetBarAndClip(0, false);
1566 SetBarAndClip(1, false);
1567
1568 mRuler.SetBounds(mBar[1].r.GetLeft(),
1569 mBar[1].b.GetTop() - (mRulerHeight / 2),
1570 mBar[1].r.GetRight(),
1571 mBar[1].b.GetTop() - (mRulerHeight / 2));
1572 mRuler.OfflimitsPixels(0, 0);
1573 break;
1574 }
1575
1576 mLayoutValid = true;
1577}
1578
1580{
1581 if (mLayoutValid)
1582 {
1583 // Invalidate the bars so they get redrawn
1584 for (unsigned int i = 0; i < mNumBars; i++)
1585 {
1586 Refresh(false);
1587 }
1588
1589 // Immediate redraw (using wxPaintDC)
1590 Update();
1591
1592 return;
1593 }
1594}
1595
1597{
1598 // Cache some metrics
1599 wxCoord x = bar->r.GetLeft();
1600 wxCoord y = bar->r.GetTop();
1601 wxCoord w = bar->r.GetWidth();
1602 wxCoord h = bar->r.GetHeight();
1603 wxCoord ht;
1604 wxCoord wd;
1605
1606 // Setup for erasing the background
1607 dc.SetPen(*wxTRANSPARENT_PEN);
1609
1610 if (mGradient)
1611 {
1612 // Map the predrawn bitmap into the source DC
1613 wxMemoryDC srcDC;
1614 srcDC.SelectObject(*mBitmap);
1615
1616 if (bar->vert)
1617 {
1618 // Copy as much of the predrawn meter bar as is required for the
1619 // current peak.
1620 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1621 ht = (int)(bar->peak * (h - 1) + 0.5);
1622
1623 // Blank out the rest
1624 if (h - ht)
1625 {
1626 // ht includes peak value...not really needed but doesn't hurt
1627 dc.DrawRectangle(x, y, w, h - ht);
1628 }
1629
1630 // Copy as much of the predrawn meter bar as is required for the
1631 // current peak.
1632 // +/-1 to include the peak position
1633 if (ht)
1634 {
1635 dc.Blit(x, y + h - ht - 1, w, ht + 1, &srcDC, x, y + h - ht - 1);
1636 }
1637
1638 // Draw the "recent" peak hold line using the predrawn meter bar so that
1639 // it will be the same color as the original level.
1640 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1641 ht = (int)(bar->peakHold * (h - 1) + 0.5);
1642 if (ht > 1)
1643 {
1644 dc.Blit(x, y + h - ht - 1, w, 2, &srcDC, x, y + h - ht - 1);
1645 }
1646
1647 // Draw the "maximum" peak hold line
1648 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1649 dc.SetPen(mPeakPeakPen);
1650 ht = (int)(bar->peakPeakHold * (h - 1) + 0.5);
1651 if (ht > 0)
1652 {
1653 AColor::Line(dc, x, y + h - ht - 1, x + w - 1, y + h - ht - 1);
1654 if (ht > 1)
1655 {
1656 AColor::Line(dc, x, y + h - ht, x + w - 1, y + h - ht);
1657 }
1658 }
1659 }
1660 else
1661 {
1662 // Calculate the peak position
1663 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1664 wd = (int)(bar->peak * (w - 1) + 0.5);
1665
1666 // Blank out the rest
1667 if (w - wd)
1668 {
1669 // wd includes peak value...not really needed but doesn't hurt
1670 dc.DrawRectangle(x + wd, y, w - wd, h);
1671 }
1672
1673 // Copy as much of the predrawn meter bar as is required for the
1674 // current peak. But, only blit() if there's something to copy
1675 // to prevent display corruption.
1676 // +1 to include peak position
1677 if (wd)
1678 {
1679 dc.Blit(x, y, wd + 1, h, &srcDC, x, y);
1680 }
1681
1682 // Draw the "recent" peak hold line using the predrawn meter bar so that
1683 // it will be the same color as the original level.
1684 // -1 to give a 2 pixel width
1685 wd = (int)(bar->peakHold * (w - 1) + 0.5);
1686 if (wd > 1)
1687 {
1688 dc.Blit(x + wd - 1, y, 2, h, &srcDC, x + wd, y);
1689 }
1690
1691 // Draw the "maximum" peak hold line using a themed color
1692 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1693 dc.SetPen(mPeakPeakPen);
1694 wd = (int)(bar->peakPeakHold * (w - 1) + 0.5);
1695 if (wd > 0)
1696 {
1697 AColor::Line(dc, x + wd, y, x + wd, y + h - 1);
1698 if (wd > 1)
1699 {
1700 AColor::Line(dc, x + wd - 1, y, x + wd - 1, y + h - 1);
1701 }
1702 }
1703 }
1704
1705 // No longer need the source DC, so unselect the predrawn bitmap
1706 srcDC.SelectObject(wxNullBitmap);
1707 }
1708 else
1709 {
1710 if (bar->vert)
1711 {
1712 // Calculate the peak position
1713 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1714 ht = (int)(bar->peak * (h - 1) + 0.5);
1715
1716 // Blank out the rest
1717 if (h - ht)
1718 {
1719 // ht includes peak value...not really needed but doesn't hurt
1720 dc.DrawRectangle(x, y, w, h - ht);
1721 }
1722
1723 // Draw the peak level
1724 // +/-1 to include the peak position
1725 dc.SetPen(*wxTRANSPARENT_PEN);
1726 dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mBrush);
1727 if (ht)
1728 {
1729 dc.DrawRectangle(x, y + h - ht - 1, w, ht + 1);
1730 }
1731
1732 // Draw the "recent" peak hold line
1733 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1734 dc.SetPen(mPen);
1735 ht = (int)(bar->peakHold * (h - 1) + 0.5);
1736 if (ht > 0)
1737 {
1738 AColor::Line(dc, x, y + h - ht - 1, x + w - 1, y + h - ht - 1);
1739 if (ht > 1)
1740 {
1741 AColor::Line(dc, x, y + h - ht, x + w - 1, y + h - ht);
1742 }
1743 }
1744
1745 // Calculate the rms position
1746 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1747 // +1 to include the rms position
1748 ht = (int)(bar->rms * (h - 1) + 0.5);
1749
1750 // Draw the RMS level
1751 dc.SetPen(*wxTRANSPARENT_PEN);
1753 if (ht)
1754 {
1755 dc.DrawRectangle(x, y + h - ht - 1, w, ht + 1);
1756 }
1757
1758 // Draw the "maximum" peak hold line
1759 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1760 dc.SetPen(mPeakPeakPen);
1761 ht = (int)(bar->peakPeakHold * (h - 1) + 0.5);
1762 if (ht > 0)
1763 {
1764 AColor::Line(dc, x, y + h - ht - 1, x + w - 1, y + h - ht - 1);
1765 if (ht > 1)
1766 {
1767 AColor::Line(dc, x, y + h - ht, x + w - 1, y + h - ht);
1768 }
1769 }
1770 }
1771 else
1772 {
1773 // Calculate the peak position
1774 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1775 wd = (int)(bar->peak * (w - 1) + 0.5);
1776
1777 // Blank out the rest
1778 if (w - wd)
1779 {
1780 // wd includes peak value...not really needed but doesn't hurt
1781 dc.DrawRectangle(x + wd, y, w - wd, h);
1782 }
1783
1784 // Draw the peak level
1785 // +1 to include peak position
1786 dc.SetPen(*wxTRANSPARENT_PEN);
1787 dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mBrush);
1788 if (wd)
1789 {
1790 dc.DrawRectangle(x, y, wd + 1, h);
1791 }
1792
1793 // Draw the "recent" peak hold line
1794 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1795 dc.SetPen(mPen);
1796 wd = (int)(bar->peakHold * (w - 1) + 0.5);
1797 if (wd > 0)
1798 {
1799 AColor::Line(dc, x + wd, y, x + wd, y + h - 1);
1800 if (wd > 1)
1801 {
1802 AColor::Line(dc, x + wd - 1, y, x + wd - 1, y + h - 1);
1803 }
1804 }
1805
1806 // Calculate the rms position
1807 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1808 wd = (int)(bar->rms * (w - 1) + 0.5);
1809
1810 // Draw the rms level
1811 // +1 to include the rms position
1812 dc.SetPen(*wxTRANSPARENT_PEN);
1814 if (wd)
1815 {
1816 dc.DrawRectangle(x, y, wd + 1, h);
1817 }
1818
1819 // Draw the "maximum" peak hold line using a themed color
1820 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1821 dc.SetPen(mPeakPeakPen);
1822 wd = (int)(bar->peakPeakHold * (w - 1) + 0.5);
1823 if (wd > 0)
1824 {
1825 AColor::Line(dc, x + wd, y, x + wd, y + h - 1);
1826 if (wd > 1)
1827 {
1828 AColor::Line(dc, x + wd - 1, y, x + wd - 1, y + h - 1);
1829 }
1830 }
1831 }
1832 }
1833
1834 // If meter had a clipping indicator, draw or erase it
1835 // LLL: At least I assume that's what "mClip" is supposed to be for as
1836 // it is always "true".
1837 if (mClip)
1838 {
1839 if (bar->clipping)
1840 {
1841 dc.SetBrush(mClipBrush);
1842 }
1843 else
1844 {
1846 }
1847 dc.SetPen(*wxTRANSPARENT_PEN);
1848 wxRect r(bar->rClip.GetX() + 1,
1849 bar->rClip.GetY() + 1,
1850 bar->rClip.GetWidth() - 1,
1851 bar->rClip.GetHeight() - 1);
1852 dc.DrawRectangle(r);
1853 }
1854}
1855
1857{
1858 return mMeterDisabled != 0;
1859}
1860
1862{
1863 bool start = !mMonitoring;
1864
1865 auto gAudioIO = AudioIO::Get();
1866 if (gAudioIO->IsMonitoring()){
1867 gAudioIO->StopStream();
1868 }
1869
1870 if (start && !gAudioIO->IsBusy()){
1872 if (p){
1873 gAudioIO->StartMonitoring( DefaultPlayOptions( *p ) );
1874 }
1875
1876 mLayoutValid = false;
1877
1878 Refresh(false);
1879 }
1880}
1881
1883 mMonitoring = false;
1884 auto gAudioIO = AudioIO::Get();
1885 if (gAudioIO->IsMonitoring()){
1886 gAudioIO->StopStream();
1887 }
1888}
1889
1891{
1892 if (!mIsInput != (evt.type == AudioIOEvent::PLAYBACK))
1893 return;
1894
1895 AudacityProject *p = evt.pProject;
1896 mActive = evt.on && (p == mProject);
1897 if( mActive ){
1898 mTimer.Start(1000 / mMeterRefreshRate);
1899 if (evt.type == AudioIOEvent::MONITOR)
1901 } else {
1902 mTimer.Stop();
1903 mMonitoring = false;
1904 }
1905
1906 // Only refresh is we're the active meter
1907 if (IsShownOnScreen())
1908 Refresh(false);
1909}
1910
1912{
1913 if (event.type == AudioIOEvent::CAPTURE && event.pProject != mProject)
1914 {
1915 mEnabled = !event.on;
1916
1917 if (mSlider)
1918 mSlider->SetEnabled(mEnabled);
1919 }
1920}
1921
1922// SaveState() and RestoreState() exist solely for purpose of recreating toolbars
1923// They should really be querying the project for current audio I/O state, but there
1924// isn't a clear way of doing that just yet. (It should NOT query AudioIO.)
1926{
1927 return { true, mMonitoring, mActive };
1928}
1929
1931{
1932 if (!state.mSaved)
1933 return;
1934
1935 mMonitoring = state.mMonitoring;
1936 mActive = state.mActive;
1937 //wxLogDebug("Restore state for %p, is %i", this, mActive );
1938
1939 if (mActive)
1940 mTimer.Start(1000 / mMeterRefreshRate);
1941}
1942
1943//
1944// Pop-up menu
1945//
1946
1947void MeterPanel::ShowMenu(const wxPoint & pos)
1948{
1949 wxMenu menu;
1950 // Note: these should be kept in the same order as the enum
1951 if (mIsInput) {
1952 wxMenuItem *mi;
1953 if (mMonitoring)
1954 mi = menu.Append(OnMonitorID, _("Stop Monitoring"));
1955 else
1956 mi = menu.Append(OnMonitorID, _("Start Monitoring"));
1957 mi->Enable(!mActive || mMonitoring);
1958 }
1959
1960 menu.Append(OnPreferencesID, _("Options..."));
1961
1962 BasicMenu::Handle{ &menu }.Popup(
1964 { pos.x, pos.y }
1965 );
1966}
1967
1969{
1971 if(mSlider)
1972 mSlider->SetName(tip);
1973}
1974
1975
1976void MeterPanel::OnMonitor(wxCommandEvent & WXUNUSED(event))
1977{
1979}
1980
1981void MeterPanel::OnPreferences(wxCommandEvent & WXUNUSED(event))
1982{
1983 wxTextCtrl *rate;
1984 wxRadioButton *gradient;
1985 wxRadioButton *rms;
1986 wxRadioButton *db;
1987 wxRadioButton *linear;
1988 wxRadioButton *automatic;
1989 wxRadioButton *horizontal;
1990 wxRadioButton *vertical;
1991 int meterRefreshRate = mMeterRefreshRate;
1992
1993 auto title = mIsInput ? XO("Recording Meter Options") : XO("Playback Meter Options");
1994
1995 // Dialog is a child of the project, rather than of the toolbar.
1996 // This determines where it pops up.
1997
1998 wxDialogWrapper dlg( FindProjectFrame( mProject ), wxID_ANY, title);
1999 dlg.SetName();
2000 ShuttleGui S(&dlg, eIsCreating);
2001 S.StartVerticalLay();
2002 {
2003 S.StartStatic(XO("Refresh Rate"), 0);
2004 {
2005 S.AddFixedText(XO(
2006"Higher refresh rates make the meter show more frequent\nchanges. A rate of 30 per second or less should prevent\nthe meter affecting audio quality on slower machines."));
2007 S.StartHorizontalLay();
2008 {
2009 rate = S.Name(XO("Meter refresh rate per second [1-100]"))
2010 .Validator<IntegerValidator<long>>(
2011 &mMeterRefreshRate, NumValidatorStyle::DEFAULT,
2013 .AddTextBox(XXO("Meter refresh rate per second [1-100]: "),
2014 wxString::Format(wxT("%d"), meterRefreshRate),
2015 10);
2016 }
2017 S.EndHorizontalLay();
2018 }
2019 S.EndStatic();
2020
2021 S.StartHorizontalLay();
2022 {
2023 S.StartStatic(XO("Meter Style"), 0);
2024 {
2025 S.StartVerticalLay();
2026 {
2027 gradient = S.AddRadioButton(XXO("Gradient"), true, mGradient);
2028 rms = S.AddRadioButtonToGroup(XXO("RMS"), false, mGradient);
2029 }
2030 S.EndVerticalLay();
2031 }
2032 S.EndStatic();
2033
2034 S.StartStatic(XO("Meter Type"), 0);
2035 {
2036 S.StartVerticalLay();
2037 {
2038 db = S.AddRadioButton(XXO("dB"), true, mDB);
2039 linear = S.AddRadioButtonToGroup(XXO("Linear"), false, mDB);
2040 }
2041 S.EndVerticalLay();
2042 }
2043 S.EndStatic();
2044
2045 S.StartStatic(XO("Orientation"), 1);
2046 {
2047 S.StartVerticalLay();
2048 {
2049 automatic = S.AddRadioButton(
2050 XXO("Automatic"), AutomaticStereo, mDesiredStyle);
2051 horizontal = S.AddRadioButtonToGroup(
2052 XXO("Horizontal"), HorizontalStereo, mDesiredStyle);
2053 vertical = S.AddRadioButtonToGroup(
2054 XXO("Vertical"), VerticalStereo, mDesiredStyle);
2055 }
2056 S.EndVerticalLay();
2057 }
2058 S.EndStatic();
2059 }
2060 S.EndHorizontalLay();
2061 S.AddStandardButtons();
2062 }
2063 S.EndVerticalLay();
2064 dlg.Layout();
2065 dlg.Fit();
2066
2067 dlg.CenterOnParent();
2068
2069 if (dlg.ShowModal() == wxID_OK)
2070 {
2071 wxArrayStringEx style{
2072 wxT("AutomaticStereo") ,
2073 wxT("HorizontalStereo") ,
2074 wxT("VerticalStereo") ,
2075 };
2076
2077 int s = 0;
2078 s = automatic->GetValue() ? 0 : s;
2079 s = horizontal->GetValue() ? 1 : s;
2080 s = vertical->GetValue() ? 2 : s;
2081
2082 gPrefs->Write(Key(wxT("Style")), style[s]);
2083 gPrefs->Write(Key(wxT("Bars")), gradient->GetValue() ? wxT("Gradient") : wxT("RMS"));
2084 gPrefs->Write(Key(wxT("Type")), db->GetValue() ? wxT("dB") : wxT("Linear"));
2085 gPrefs->Write(Key(wxT("RefreshRate")), rate->GetValue());
2086
2087 gPrefs->Flush();
2088
2089 // Currently, there are 2 playback meters and 2 record meters and any number of
2090 // mixerboard meters, so we have to send out an preferences updated message to
2091 // ensure they all update themselves.
2093 }
2094}
2095
2096wxString MeterPanel::Key(const wxString & key) const
2097{
2099 {
2100 return wxT("/Meter/Mixerboard/") + key;
2101 }
2102
2103 if (mIsInput)
2104 {
2105 return wxT("/Meter/Input/") + key;
2106 }
2107
2108 return wxT("/Meter/Output/") + key;
2109}
2110
2111// This compensates for a but in wxWidgets 3.0.2 for mac:
2112// Couldn't set focus from keyboard when AcceptsFocus returns false;
2113// this bypasses that limitation
2115{
2116 auto temp = TemporarilyAllowFocus();
2117 SetFocus();
2118}
2119
2120
2121#if wxUSE_ACCESSIBILITY
2122
2123MeterAx::MeterAx(wxWindow *window):
2124 WindowAccessible(window)
2125{
2126}
2127
2128MeterAx::~MeterAx()
2129{
2130}
2131
2132// Performs the default action. childId is 0 (the action for this object)
2133// or > 0 (the action for a child).
2134// Return wxACC_NOT_SUPPORTED if there is no default action for this
2135// window (e.g. an edit control).
2136wxAccStatus MeterAx::DoDefaultAction(int WXUNUSED(childId))
2137{
2138 MeterPanel *m = wxDynamicCast(GetWindow(), MeterPanel);
2139
2140 if (m && m->mIsInput)
2141 m->StartMonitoring();
2142
2143 return wxACC_OK;
2144}
2145
2146// Retrieves the address of an IDispatch interface for the specified child.
2147// All objects must support this property.
2148wxAccStatus MeterAx::GetChild(int childId, wxAccessible** child)
2149{
2150 if (childId == wxACC_SELF)
2151 *child = this;
2152 else
2153 *child = NULL;
2154 return wxACC_OK;
2155}
2156
2157// Gets the number of children.
2158wxAccStatus MeterAx::GetChildCount(int* childCount)
2159{
2160 *childCount = 0;
2161 return wxACC_OK;
2162}
2163
2164// Gets the default action for this object (0) or > 0 (the action for
2165// a child). Return wxACC_OK even if there is no action. actionName
2166// is the action, or the empty string if there is no action. The
2167// retrieved string describes the action that is performed on an
2168// object, not what the object does as a result. For example, a
2169// toolbar button that prints a document has a default action of
2170// "Press" rather than "Prints the current document."
2171wxAccStatus MeterAx::GetDefaultAction(int WXUNUSED(childId), wxString* actionName)
2172{
2173 *actionName = _("Press");
2174 return wxACC_OK;
2175}
2176
2177// Returns the description for this object or a child.
2178wxAccStatus MeterAx::GetDescription(int WXUNUSED(childId), wxString *description)
2179{
2180 description->clear();
2181 return wxACC_NOT_SUPPORTED;
2182}
2183
2184// Gets the window with the keyboard focus.
2185// If childId is 0 and child is NULL, no object in
2186// this subhierarchy has the focus.
2187// If this object has the focus, child should be 'this'.
2188wxAccStatus MeterAx::GetFocus(int* childId, wxAccessible** child)
2189{
2190 *childId = 0;
2191 *child = this;
2192 return wxACC_OK;
2193}
2194
2195// Returns help text for this object or a child, similar to tooltip text.
2196wxAccStatus MeterAx::GetHelpText(int WXUNUSED(childId), wxString *helpText)
2197{
2198 helpText->clear();
2199 return wxACC_NOT_SUPPORTED;
2200}
2201
2202// Returns the keyboard shortcut for this object or child.
2203// Return e.g. ALT+K
2204wxAccStatus MeterAx::GetKeyboardShortcut(int WXUNUSED(childId), wxString *shortcut)
2205{
2206 shortcut->clear();
2207 return wxACC_OK;
2208}
2209
2210// Returns the rectangle for this object (id = 0) or a child element (id > 0).
2211// rect is in screen coordinates.
2212wxAccStatus MeterAx::GetLocation(wxRect & rect, int WXUNUSED(elementId))
2213{
2214 MeterPanel *m = wxDynamicCast(GetWindow(), MeterPanel);
2215
2216 rect = m->GetClientRect();
2217 rect.SetPosition(m->ClientToScreen(rect.GetPosition()));
2218
2219 return wxACC_OK;
2220}
2221
2222// Returns a role constant.
2223wxAccStatus MeterAx::GetRole(int WXUNUSED(childId), wxAccRole* role)
2224{
2225 *role = wxROLE_SYSTEM_SLIDER;
2226
2227 return wxACC_OK;
2228}
2229
2230// Gets a variant representing the selected children
2231// of this object.
2232// Acceptable values:
2233// - a null variant (IsNull() returns TRUE)
2234// - a list variant (GetType() == wxT("list"))
2235// - an integer representing the selected child element,
2236// or 0 if this object is selected (GetType() == wxT("long"))
2237// - a "void*" pointer to a wxAccessible child object
2238wxAccStatus MeterAx::GetSelections(wxVariant * WXUNUSED(selections))
2239{
2240 return wxACC_NOT_IMPLEMENTED;
2241}
2242
2243// Returns a state constant.
2244wxAccStatus MeterAx::GetState(int WXUNUSED(childId), long* state)
2245{
2246 MeterPanel *m = wxDynamicCast( GetWindow(), MeterPanel );
2247
2248 *state = wxACC_STATE_SYSTEM_FOCUSABLE;
2249 *state |= ( m == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0 );
2250
2251 return wxACC_OK;
2252}
2253
2254// Returns a localized string representing the value for the object
2255// or child.
2256wxAccStatus MeterAx::GetValue(int WXUNUSED(childId), wxString* strValue)
2257{
2258 MeterPanel *m = wxDynamicCast(GetWindow(), MeterPanel);
2259
2260 *strValue = m->mSlider->GetStringValue();
2261 return wxACC_OK;
2262}
2263
2264#endif
#define PERCENT_SLIDER
Definition: ASlider.h:39
EVT_MENU(OnSetPlayRegionToSelectionID, AdornedRulerPanel::OnSetPlayRegionToSelection) EVT_COMMAND(OnTogglePinnedStateID
wxT("CloseDown"))
END_EVENT_TABLE()
static const AudacityProject::AttachedObjects::RegisteredFactory key
int min(int a, int b)
IntSetting DecibelScaleCutoff
Negation of this value is the lowest dB level that should be shown in dB scales.
Definition: Decibels.cpp:12
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
#define _(s)
Definition: Internat.h:75
#define MAX_AUDIO
Definition: MemoryX.h:546
#define safenew
Definition: MemoryX.h:10
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:544
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:543
static float ClipZeroToOne(float z)
Definition: MeterPanel.cpp:941
static int intmax(int a, int b)
Definition: MeterPanel.cpp:936
static const int gap
Definition: MeterPanel.cpp:256
static const long MIN_REFRESH_RATE
Definition: MeterPanel.cpp:153
static const long MAX_REFRESH_RATE
Definition: MeterPanel.cpp:154
@ OnPreferencesID
Definition: MeterPanel.cpp:268
@ OnMonitorID
Definition: MeterPanel.cpp:267
@ OnMeterUpdateID
Definition: MeterPanel.cpp:266
@ OnTipTimeoutID
Definition: MeterPanel.cpp:269
static const wxChar * PrefStyles[]
Definition: MeterPanel.cpp:258
static float ToDB(float v, float range)
Definition: MeterPanel.cpp:951
static float floatMax(float a, float b)
Definition: MeterPanel.cpp:924
static int MeterPrefsID()
Definition: MeterPanel.cpp:454
const int kMaxMeterBars
Definition: MeterPanel.h:37
static const auto title
FileConfig * gPrefs
Definition: Prefs.cpp:71
AudioIOStartStreamOptions DefaultPlayOptions(AudacityProject &project, bool newDefault)
wxFrame * FindProjectFrame(AudacityProject *project)
Get a pointer to the window associated with a project, or null if the given pointer is null,...
IMPLEMENT_CLASS(cloud::ShareAudioToolbar, ToolBar)
@ eIsCreating
Definition: ShuttleGui.h:39
THEME_API Theme theTheme
Definition: Theme.cpp:82
#define S(N)
Definition: ToChars.cpp:64
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static void Bevel(wxDC &dc, bool up, const wxRect &r)
Definition: AColor.cpp:266
static void DrawFocus(wxDC &dc, wxRect &r)
Definition: AColor.cpp:235
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
static AudioIO * Get()
Definition: AudioIO.cpp:133
void Popup(const BasicUI::WindowPlacement &window, const Point &pos={})
Display the menu at pos, invoke at most one action, then hide it.
Definition: BasicMenu.cpp:209
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
Inherits wxPanel and has a Meter; exposes shared_ptr to the Meter.
static TempAllowFocus TemporarilyAllowFocus()
MeterPanel is a panel that paints the meter used for monitoring or playback.
Definition: MeterPanel.h:104
bool IsActive() const
void HandleLayout(wxDC &dc)
long mMeterRefreshRate
Definition: MeterPanel.h:278
void OnCharHook(wxKeyEvent &evt)
Definition: MeterPanel.cpp:754
wxString mRightText
Definition: MeterPanel.h:305
bool mLayoutValid
Definition: MeterPanel.h:288
wxSize mRightSize
Definition: MeterPanel.h:294
void UpdatePrefs() override
Definition: MeterPanel.cpp:408
void UpdateDisplay(unsigned numChannels, int numFrames, const float *sampleData) override
Update the meters with a block of audio data.
Definition: MeterPanel.cpp:961
void ResetBar(MeterBar *bar, bool resetClipping)
Style mStyle
Definition: MeterPanel.h:266
void Clear() override
Definition: MeterPanel.cpp:403
wxPoint mSliderPos
Definition: MeterPanel.h:308
void StartMonitoring()
bool ShowDialog()
Definition: MeterPanel.cpp:864
void OnTipTimeout(wxTimerEvent &evt)
void OnKeyDown(wxKeyEvent &evt)
Definition: MeterPanel.cpp:788
void Decrease(float steps)
Definition: MeterPanel.cpp:890
int mRulerHeight
Definition: MeterPanel.h:262
float GetPeakHold() const
void OnErase(wxEraseEvent &evt)
Definition: MeterPanel.cpp:498
wxFont GetFont() const
double mRate
Definition: MeterPanel.h:277
Style mDesiredStyle
Definition: MeterPanel.h:267
bool mClip
Definition: MeterPanel.h:273
void SetFocusFromKbd() override
void OnContext(wxContextMenuEvent &evt)
Definition: MeterPanel.cpp:776
int mDBRange
Definition: MeterPanel.h:270
bool mIsInput
Definition: MeterPanel.h:264
int mRulerWidth
Definition: MeterPanel.h:261
Ruler mRuler
Definition: MeterPanel.h:303
bool mEnabled
Definition: MeterPanel.h:311
void Increase(float steps)
Definition: MeterPanel.cpp:879
wxTimer mTimer
Definition: MeterPanel.h:255
float mDecayRate
Definition: MeterPanel.h:272
wxBrush mDisabledBkgndBrush
Definition: MeterPanel.h:302
bool mDecay
Definition: MeterPanel.h:271
double mPeakHoldDuration
Definition: MeterPanel.h:275
std::unique_ptr< LWSlider > mSlider
Definition: MeterPanel.h:307
double mT
Definition: MeterPanel.h:276
wxString mLeftText
Definition: MeterPanel.h:304
wxString Key(const wxString &key) const
AudacityProject * mProject
Definition: MeterPanel.h:253
MeterBar mBar[kMaxMeterBars]
Definition: MeterPanel.h:286
unsigned mNumBars
Definition: MeterPanel.h:285
wxBrush mBkgndBrush
Definition: MeterPanel.h:301
bool IsMonitoring() const
void OnAudioIOStatus(AudioIOEvent)
bool IsClipping() const override
bool mMonitoring
Definition: MeterPanel.h:281
bool mGradient
Definition: MeterPanel.h:268
bool mActive
Definition: MeterPanel.h:283
void UpdateSliderControl()
Definition: MeterPanel.cpp:476
void OnMonitor(wxCommandEvent &evt)
void OnMouse(wxMouseEvent &evt)
Definition: MeterPanel.cpp:728
wxBrush mBrush
Definition: MeterPanel.h:298
void SetBarAndClip(int iBar, bool vert)
std::unique_ptr< wxBitmap > mBitmap
Definition: MeterPanel.h:290
wxPoint mLeftTextPos
Definition: MeterPanel.h:291
wxTimer mTipTimer
Definition: MeterPanel.h:256
void OnPreferences(wxCommandEvent &evt)
void StopMonitoring()
void OnSetFocus(wxFocusEvent &evt)
Definition: MeterPanel.cpp:804
wxSize mLeftSize
Definition: MeterPanel.h:293
wxPen mPeakPeakPen
Definition: MeterPanel.h:297
void RepaintBarsNow()
void SetStyle(Style newStyle)
Definition: MeterPanel.cpp:820
wxPoint mRightTextPos
Definition: MeterPanel.h:292
void ShowMenu(const wxPoint &pos)
int mNumPeakSamplesToClip
Definition: MeterPanel.h:274
void OnPaint(wxPaintEvent &evt)
Definition: MeterPanel.cpp:503
void UpdateSelectedPrefs(int) override
Definition: MeterPanel.cpp:460
@ HorizontalStereoCompact
Definition: MeterPanel.h:115
@ AutomaticStereo
Definition: MeterPanel.h:111
@ HorizontalStereo
Definition: MeterPanel.h:112
@ VerticalStereo
Definition: MeterPanel.h:113
@ VerticalStereoCompact
Definition: MeterPanel.h:116
@ MixerTrackCluster
Definition: MeterPanel.h:114
State SaveState()
wxSize mSliderSize
Definition: MeterPanel.h:309
void OnMeterUpdate(wxTimerEvent &evt)
void SetActiveStyle(Style style)
wxBrush mClipBrush
Definition: MeterPanel.h:300
void Reset(double sampleRate, bool resetClipping) override
This method is thread-safe! Feel free to call from a different thread (like from an audio I/O callbac...
Definition: MeterPanel.cpp:901
void DrawMeterBar(wxDC &dc, MeterBar *meterBar)
void OnAudioCapture(AudioIOEvent)
wxPen mPen
Definition: MeterPanel.h:295
bool mIsFocused
Definition: MeterPanel.h:313
float GetMaxPeak() const override
MeterUpdateQueue mQueue
Definition: MeterPanel.h:254
long mMeterDisabled
Definition: MeterPanel.h:279
wxBrush mRMSBrush
Definition: MeterPanel.h:299
void OnSize(wxSizeEvent &evt)
Definition: MeterPanel.cpp:720
void OnKillFocus(wxFocusEvent &evt)
Definition: MeterPanel.cpp:810
void SetMixer(wxCommandEvent &event)
Definition: MeterPanel.cpp:832
void RestoreState(const State &state)
bool IsMeterDisabled() const override
Find out if the level meter is disabled or not.
Message used to update the MeterPanel.
Definition: MeterPanel.h:55
bool clipping[kMaxMeterBars]
Definition: MeterPanel.h:60
float peak[kMaxMeterBars]
Definition: MeterPanel.h:58
int tailPeakCount[kMaxMeterBars]
Definition: MeterPanel.h:62
int headPeakCount[kMaxMeterBars]
Definition: MeterPanel.h:61
wxString toStringIfClipped()
Only print meter updates if clipping may be happening.
Definition: MeterPanel.cpp:175
wxString toString()
Print out all the values in the meter update message.
Definition: MeterPanel.cpp:158
float rms[kMaxMeterBars]
Definition: MeterPanel.h:59
bool Put(MeterUpdateMsg &msg)
Definition: MeterPanel.cpp:210
ArrayOf< MeterUpdateMsg > mBuffer
Definition: MeterPanel.h:92
NonInterfering< std::atomic< size_t > > mEnd
Definition: MeterPanel.h:89
NonInterfering< std::atomic< size_t > > mStart
Definition: MeterPanel.h:89
MeterUpdateQueue(size_t maxLen)
Definition: MeterPanel.cpp:191
const size_t mBufferSize
Definition: MeterPanel.h:91
bool Get(MeterUpdateMsg &msg)
Definition: MeterPanel.cpp:233
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
static void Broadcast(int id=0)
Call this static function to notify all PrefsListener objects.
Definition: Prefs.cpp:98
void SetTickColour(const wxColour &colour)
Definition: Ruler.h:148
void OfflimitsPixels(int start, int end)
Definition: Ruler.cpp:302
void SetOrientation(int orient)
Definition: Ruler.cpp:174
void Draw(wxDC &dc) const
Definition: Ruler.cpp:1428
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:1603
void SetBounds(int left, int top, int right, int bottom)
Definition: Ruler.cpp:332
void SetRange(double min, double max)
Definition: Ruler.cpp:188
void SetFormat(RulerFormat format)
Definition: Ruler.cpp:131
@ LinearDBFormat
Definition: Ruler.h:34
@ RealFormat
Definition: Ruler.h:31
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:200
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:628
wxColour & Colour(int iIndex)
int ColourDistance(wxColour &From, wxColour &To)
Definition: Theme.cpp:307
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 ...
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
void SetName(const TranslatableString &title)
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
AUDACITY_DLL_API void UpdatePrefs(wxWindow *pParent)
bool on
Definition: AudioIO.h:75
enum AudioIOEvent::Type type
AudacityProject * pProject
Definition: AudioIO.h:69
A struct used by MeterPanel to hold the position of one bar.
Definition: MeterPanel.h:39
wxRect rClip
Definition: MeterPanel.h:47
bool isclipping
Definition: MeterPanel.h:49
float peakPeakHold
Definition: MeterPanel.h:51
float peak
Definition: MeterPanel.h:43
int tailPeakCount
Definition: MeterPanel.h:50
float rms
Definition: MeterPanel.h:44
bool vert
Definition: MeterPanel.h:40
wxRect b
Definition: MeterPanel.h:41
bool clipping
Definition: MeterPanel.h:48
double peakHoldTime
Definition: MeterPanel.h:46
wxRect r
Definition: MeterPanel.h:42
float peakHold
Definition: MeterPanel.h:45
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.