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