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 //if( !mMeterDisabled )
531 //{
532 // mBkgndBrush.SetColour( GetParent()->GetBackgroundColour() );
533 //}
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 mRuler.Draw(dc);
666
667 // Bitmap created...unselect
668 dc.SelectObject(wxNullBitmap);
669 }
670
671 // Copy predrawn bitmap to the dest DC
672 destDC.DrawBitmap(*mBitmap, 0, 0);
673
674 // Go draw the meter bars, Left & Right channels using current levels
675 for (unsigned int i = 0; i < mNumBars; i++)
676 {
677 DrawMeterBar(destDC, &mBar[i]);
678 }
679
680 destDC.SetTextForeground( clrText );
681
682 // We can have numbers over the bars, in which case we have to draw them each time.
684 {
685 mRuler.SetTickColour( clrText );
686 // If the text colour is too similar to the meter colour, then we need a background
687 // for the text. We require a total of at least one full-scale RGB difference.
688 int d = theTheme.ColourDistance( clrText, theTheme.Colour( clrMeterOutputRMSBrush ) );
689 if( d < 256 )
690 {
691 destDC.SetBackgroundMode( wxSOLID );
692 destDC.SetTextBackground( clrBoxFill );
693 }
694 mRuler.Draw(destDC);
695 }
696
698 {
699 bool highlighted =
700 wxRect{ mSliderPos, mSliderSize }.Contains(
701 ScreenToClient(
702 ::wxGetMousePosition()));
703
704 mSlider->Move(mSliderPos);
705 mSlider->AdjustSize(mSliderSize);
706 mSlider->OnPaint(destDC, highlighted);
707 }
708
709 if (mIsFocused)
710 {
711 auto r = GetClientRect();
712 AColor::DrawFocus(destDC, r);
713 }
714}
715
716void MeterPanel::OnSize(wxSizeEvent & WXUNUSED(event))
717{
718 GetClientSize(&mWidth, &mHeight);
719
720 mLayoutValid = false;
721 Refresh();
722}
723
724void MeterPanel::OnMouse(wxMouseEvent &evt)
725{
726 if ((evt.GetEventType() == wxEVT_MOTION || evt.Entering() || evt.Leaving())) {
727 mLayoutValid = false;
728 Refresh();
729 }
730
731 if (mStyle == MixerTrackCluster) // MixerTrackCluster style has no menu.
732 return;
733
734 if (evt.Entering()) {
735 mTipTimer.StartOnce(500);
736 }
737 else if(evt.Leaving())
738 mTipTimer.Stop();
739
740 if (evt.RightDown())
741 ShowMenu(evt.GetPosition());
742 else
743 {
744
745 if (mSlider)
746 mSlider->OnMouseEvent(evt);
747 }
748}
749
750void MeterPanel::OnCharHook(wxKeyEvent& evt)
751{
752 switch(evt.GetKeyCode())
753 {
754 // These are handled in the OnCharHook handler because, on Windows at least, the
755 // key up event will be passed on to the menu if we show it here. This causes
756 // the default sound to be heard if assigned.
757 case WXK_RETURN:
758 case WXK_NUMPAD_ENTER:
759 case WXK_WINDOWS_MENU:
760 case WXK_MENU:
762 ShowMenu(GetClientRect().GetBottomLeft());
763 else
764 evt.Skip();
765 break;
766 default:
767 evt.Skip();
768 break;
769 }
770}
771
772void MeterPanel::OnContext(wxContextMenuEvent &evt)
773{
774 if (mStyle != MixerTrackCluster) // MixerTrackCluster style has no menu.
775 {
776 ShowMenu(GetClientRect().GetBottomLeft());
777 }
778 else
779 {
780 evt.Skip();
781 }
782}
783
784void MeterPanel::OnKeyDown(wxKeyEvent &evt)
785{
786 switch (evt.GetKeyCode())
787 {
788 case WXK_TAB:
789 if (evt.ShiftDown())
790 Navigate(wxNavigationKeyEvent::IsBackward);
791 else
792 Navigate(wxNavigationKeyEvent::IsForward);
793 break;
794 default:
795 mSlider->OnKeyDown(evt);
796 break;
797 }
798}
799
800void MeterPanel::OnSetFocus(wxFocusEvent & WXUNUSED(evt))
801{
802 mIsFocused = true;
803 Refresh(false);
804}
805
806void MeterPanel::OnKillFocus(wxFocusEvent & WXUNUSED(evt))
807{
808 if(mSlider)
809 mSlider->OnKillFocus();
810 mTipTimer.Stop();
811
812 mIsFocused = false;
813 Refresh(false);
814}
815
817{
818 if (mStyle != newStyle && mDesiredStyle == AutomaticStereo)
819 {
820 SetActiveStyle(newStyle);
821
822 mLayoutValid = false;
823
824 Refresh(false);
825 }
826}
827
828void MeterPanel::SetMixer(wxCommandEvent & WXUNUSED(event))
829{
830#if USE_PORTMIXER
831 if (mSlider)
832 {
833 float inputVolume;
834 float outputVolume;
835 int inputSource;
836
837 Refresh();
838
839 auto gAudioIO = AudioIO::Get();
840 gAudioIO->GetMixer(&inputSource, &inputVolume, &outputVolume);
841
842 if (mIsInput)
843 inputVolume = mSlider->Get();
844 else
845 outputVolume = mSlider->Get();
846
847 gAudioIO->SetMixer(inputSource, inputVolume, outputVolume);
848
849#if wxUSE_ACCESSIBILITY
850 GetAccessible()->NotifyEvent( wxACC_EVENT_OBJECT_VALUECHANGE,
851 this,
852 wxOBJID_CLIENT,
853 wxACC_SELF );
854#endif
855
856 }
857#endif // USE_PORTMIXER
858}
859
861{
862 if (!mSlider)
863 return false;
864
865 auto changed = mSlider->ShowDialog();
866 if (changed)
867 {
868 wxCommandEvent e;
869 SetMixer(e);
870 }
871
872 return changed;
873}
874
875void MeterPanel::Increase(float steps)
876{
877 if (mSlider)
878 {
879 wxCommandEvent e;
880
881 mSlider->Increase(steps);
882 SetMixer(e);
883 }
884}
885
886void MeterPanel::Decrease(float steps)
887{
888 if (mSlider)
889 {
890 wxCommandEvent e;
891
892 mSlider->Decrease(steps);
893 SetMixer(e);
894 }
895}
896
897void MeterPanel::Reset(double sampleRate, bool resetClipping)
898{
899 mT = 0;
901 for (int j = 0; j < kMaxMeterBars; j++)
902 {
903 ResetBar(&mBar[j], resetClipping);
904 }
905
906 // wxTimers seem to be a little unreliable - sometimes they stop for
907 // no good reason, so this "primes" it every now and then...
908 mTimer.Stop();
909
910 // While it's stopped, empty the queue
911 mQueue.Clear();
912
913 mLayoutValid = false;
914
915 mTimer.Start(1000 / mMeterRefreshRate);
916
917 Refresh(false);
918}
919
920static float floatMax(float a, float b)
921{
922 return a>b? a: b;
923}
924
925/* Unused as yet.
926static int intmin(int a, int b)
927{
928 return a<b? a: b;
929}
930*/
931
932static int intmax(int a, int b)
933{
934 return a>b? a: b;
935}
936
937static float ClipZeroToOne(float z)
938{
939 if (z > 1.0)
940 return 1.0;
941 else if (z < 0.0)
942 return 0.0;
943 else
944 return z;
945}
946
947static float ToDB(float v, float range)
948{
949 double db;
950 if (v > 0)
951 db = LINEAR_TO_DB(fabs(v));
952 else
953 db = -999;
954 return ClipZeroToOne((db + range) / range);
955}
956
958 unsigned numChannels, int numFrames, const float *sampleData)
959{
960 auto sptr = sampleData;
961 auto num = std::min(numChannels, mNumBars);
962 MeterUpdateMsg msg;
963
964 memset(&msg, 0, sizeof(msg));
965 msg.numFrames = numFrames;
966
967 for(int i=0; i<numFrames; i++) {
968 for(unsigned int j=0; j<num; j++) {
969 msg.peak[j] = floatMax(msg.peak[j], fabs(sptr[j]));
970 msg.rms[j] += sptr[j]*sptr[j];
971
972 // In addition to looking for mNumPeakSamplesToClip peaked
973 // samples in a row, also send the number of peaked samples
974 // at the head and tail, in case there's a run of peaked samples
975 // that crosses block boundaries
976 if (fabs(sptr[j])>=MAX_AUDIO) {
977 if (msg.headPeakCount[j]==i)
978 msg.headPeakCount[j]++;
979 msg.tailPeakCount[j]++;
981 msg.clipping[j] = true;
982 }
983 else
984 msg.tailPeakCount[j] = 0;
985 }
986 sptr += numChannels;
987 }
988 for(unsigned int j=0; j<mNumBars; j++)
989 msg.rms[j] = sqrt(msg.rms[j]/numFrames);
990
991 mQueue.Put(msg);
992}
993
994// Vaughan, 2010-11-29: This not currently used. See comments in MixerTrackCluster::UpdateMeter().
995//void MeterPanel::UpdateDisplay(int numChannels, int numFrames,
996// // Need to make these double-indexed arrays if we handle more than 2 channels.
997// float* maxLeft, float* rmsLeft,
998// float* maxRight, float* rmsRight,
999// const size_t kSampleCount)
1000//{
1001// int i, j;
1002// int num = intmin(numChannels, mNumBars);
1003// MeterUpdateMsg msg;
1004//
1005// msg.numFrames = kSampleCount;
1006// for(j=0; j<mNumBars; j++) {
1007// msg.peak[j] = 0.0;
1008// msg.rms[j] = 0.0;
1009// msg.clipping[j] = false;
1010// msg.headPeakCount[j] = 0;
1011// msg.tailPeakCount[j] = 0;
1012// }
1013//
1014// for(i=0; i<numFrames; i++) {
1015// for(j=0; j<num; j++) {
1016// msg.peak[j] = floatMax(msg.peak[j], ((j == 0) ? maxLeft[i] : maxRight[i]));
1017// msg.rms[j] = floatMax(msg.rms[j], ((j == 0) ? rmsLeft[i] : rmsRight[i]));
1018//
1019// // In addition to looking for mNumPeakSamplesToClip peaked
1020// // samples in a row, also send the number of peaked samples
1021// // at the head and tail, in case there's a run
1022// // of peaked samples that crosses block boundaries.
1023// if (fabs((j == 0) ? maxLeft[i] : maxRight[i]) >= MAX_AUDIO)
1024// {
1025// if (msg.headPeakCount[j]==i)
1026// msg.headPeakCount[j]++;
1027// msg.tailPeakCount[j]++;
1028// if (msg.tailPeakCount[j] > mNumPeakSamplesToClip)
1029// msg.clipping[j] = true;
1030// }
1031// else
1032// msg.tailPeakCount[j] = 0;
1033// }
1034// }
1035//
1036// mQueue.Put(msg);
1037//}
1038
1039void MeterPanel::OnMeterUpdate(wxTimerEvent & WXUNUSED(event))
1040{
1041 MeterUpdateMsg msg;
1042 int numChanges = 0;
1043#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1044 double maxPeak = 0.0;
1045 bool discarded = false;
1046#endif
1047
1048 // We shouldn't receive any events if the meter is disabled, but clear it to be safe
1049 if (mMeterDisabled) {
1050 mQueue.Clear();
1051 return;
1052 }
1053
1054
1055 // There may have been several update messages since the last
1056 // time we got to this function. Catch up to real-time by
1057 // popping them off until there are none left. It is necessary
1058 // to process all of them, otherwise we won't handle peaks and
1059 // peak-hold bars correctly.
1060 while(mQueue.Get(msg)) {
1061 numChanges++;
1062 double deltaT = msg.numFrames / mRate;
1063
1064 mT += deltaT;
1065 for(unsigned int j=0; j<mNumBars; j++) {
1066 mBar[j].isclipping = false;
1067
1068 //
1069 if (mDB) {
1070 msg.peak[j] = ToDB(msg.peak[j], mDBRange);
1071 msg.rms[j] = ToDB(msg.rms[j], mDBRange);
1072 }
1073
1074 if (mDecay) {
1075 if (mDB) {
1076 float decayAmount = mDecayRate * deltaT / mDBRange;
1077 mBar[j].peak = floatMax(msg.peak[j],
1078 mBar[j].peak - decayAmount);
1079 }
1080 else {
1081 double decayAmount = mDecayRate * deltaT;
1082 double decayFactor = DB_TO_LINEAR(-decayAmount);
1083 mBar[j].peak = floatMax(msg.peak[j],
1084 mBar[j].peak * decayFactor);
1085 }
1086 }
1087 else
1088 mBar[j].peak = msg.peak[j];
1089
1090 // This smooths out the RMS signal
1091 float smooth = pow(0.9, (double)msg.numFrames/1024.0);
1092 mBar[j].rms = mBar[j].rms * smooth + msg.rms[j] * (1.0 - smooth);
1093
1094 if (mT - mBar[j].peakHoldTime > mPeakHoldDuration ||
1095 mBar[j].peak > mBar[j].peakHold) {
1096 mBar[j].peakHold = mBar[j].peak;
1097 mBar[j].peakHoldTime = mT;
1098 }
1099
1100 if (mBar[j].peak > mBar[j].peakPeakHold )
1101 mBar[j].peakPeakHold = mBar[j].peak;
1102
1103 if (msg.clipping[j] ||
1104 mBar[j].tailPeakCount+msg.headPeakCount[j] >=
1106 mBar[j].clipping = true;
1107 mBar[j].isclipping = true;
1108 }
1109
1110 mBar[j].tailPeakCount = msg.tailPeakCount[j];
1111#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1112 if (mT > gAudioIO->AILAGetLastDecisionTime()) {
1113 discarded = false;
1114 maxPeak = msg.peak[j] > maxPeak ? msg.peak[j] : maxPeak;
1115 wxPrintf("%f@%f ", msg.peak[j], mT);
1116 }
1117 else {
1118 discarded = true;
1119 wxPrintf("%f@%f discarded\n", msg.peak[j], mT);
1120 }
1121#endif
1122 }
1123 } // while
1124
1125 if (numChanges > 0) {
1126 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1127 if (gAudioIO->AILAIsActive() && mIsInput && !discarded) {
1128 gAudioIO->AILAProcess(maxPeak);
1129 putchar('\n');
1130 }
1131 #endif
1133 }
1134}
1135
1136void MeterPanel::OnTipTimeout(wxTimerEvent& evt)
1137{
1138 if(mSlider)
1139 mSlider->ShowTip(true);
1140}
1141
1142
1144{
1145 float maxPeak = 0.;
1146
1147 for(unsigned int j=0; j<mNumBars; j++)
1148 maxPeak = mBar[j].peak > maxPeak ? mBar[j].peak : maxPeak;
1149
1150 return(maxPeak);
1151}
1152
1154{
1155 auto peakHold = .0f;
1156 for (unsigned int i = 0; i < mNumBars; i++)
1157 peakHold = std::max(peakHold, mBar[i].peakPeakHold);
1158 return peakHold;
1159}
1160
1162{
1163 int fontSize = 10;
1164#if defined __WXMSW__
1165 fontSize = 8;
1166#endif
1167
1168 return wxFont(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1169}
1170
1171void MeterPanel::ResetBar(MeterBar *b, bool resetClipping)
1172{
1173 b->peak = 0.0;
1174 b->rms = 0.0;
1175 b->peakHold = 0.0;
1176 b->peakHoldTime = 0.0;
1177 if (resetClipping)
1178 {
1179 b->clipping = false;
1180 b->peakPeakHold = 0.0;
1181 }
1182 b->isclipping = false;
1183 b->tailPeakCount = 0;
1184}
1185
1187{
1188 return mActive;
1189}
1190
1192{
1193 return mMonitoring;
1194}
1195
1197{
1198 for (int c = 0; c < mNumBars; c++)
1199 if (mBar[c].clipping)
1200 return true;
1201 return false;
1202}
1203
1205{
1206 mStyle = newStyle;
1207
1208 // Set dummy ruler bounds so width/height can be retrieved
1209 // NOTE: Make sure the Right and Bottom values are large enough to
1210 // ensure full width/height of digits get calculated.
1211 mRuler.SetBounds(0, 0, 500, 500);
1212
1213 if (mDB)
1214 {
1217 {
1218 mRuler.SetOrientation(wxHORIZONTAL);
1220 }
1221 else
1222 {
1223 mRuler.SetOrientation(wxVERTICAL);
1225 }
1226 }
1227 else
1228 {
1231 {
1232 mRuler.SetOrientation(wxHORIZONTAL);
1233 mRuler.SetRange(0, 1);
1234 }
1235 else
1236 {
1237 mRuler.SetOrientation(wxVERTICAL);
1238 mRuler.SetRange(1, 0);
1239 }
1240 }
1241
1243}
1244
1245void MeterPanel::SetBarAndClip(int iBar, bool vert)
1246{
1247 // Save the orientation
1248 mBar[iBar].vert = vert;
1249
1250 // Create the bar rectangle and educe to fit inside the bevel
1251 mBar[iBar].r = mBar[iBar].b;
1252 mBar[iBar].r.x += 1;
1253 mBar[iBar].r.width -= 1;
1254 mBar[iBar].r.y += 1;
1255 mBar[iBar].r.height -= 1;
1256
1257 if (vert)
1258 {
1259 if (mClip)
1260 {
1261 // Create the clip rectangle
1262 mBar[iBar].rClip = mBar[iBar].b;
1263 mBar[iBar].rClip.height = 3;
1264
1265 // Make room for the clipping indicator
1266 mBar[iBar].b.y += 3 + gap;
1267 mBar[iBar].b.height -= 3 + gap;
1268 mBar[iBar].r.y += 3 + gap;
1269 mBar[iBar].r.height -= 3 + gap;
1270 }
1271 }
1272 else
1273 {
1274 if (mClip)
1275 {
1276 // Make room for the clipping indicator
1277 mBar[iBar].b.width -= 4;
1278 mBar[iBar].r.width -= 4;
1279
1280 // Create the indicator rectangle
1281 mBar[iBar].rClip = mBar[iBar].b;
1282 mBar[iBar].rClip.x = mBar[iBar].b.GetRight() + 1 + gap; // +1 for bevel
1283 mBar[iBar].rClip.width = 3;
1284 }
1285 }
1286}
1287
1289{
1290 // Refresh to reflect any language changes
1291 /* i18n-hint: One-letter abbreviation for Left, in VU Meter */
1292 mLeftText = _("L");
1293 /* i18n-hint: One-letter abbreviation for Right, in VU Meter */
1294 mRightText = _("R");
1295
1296 dc.SetFont(GetFont());
1297 int width = mWidth;
1298 int height = mHeight;
1299 int left = 0;
1300 int top = 0;
1301 int barw;
1302 int barh;
1303 int lside;
1304 int rside;
1305
1306 // MixerTrackCluster has no L/R labels or icon
1308 {
1310 {
1312 }
1313
1315 {
1317 }
1319 {
1321 }
1322
1323 if (mLeftSize.GetWidth() == 0) // Not yet initialized to dc.
1324 {
1325 dc.GetTextExtent(mLeftText, &mLeftSize.x, &mLeftSize.y);
1326 dc.GetTextExtent(mRightText, &mRightSize.x, &mRightSize.y);
1327 }
1328 }
1329
1330 int ltxtWidth = mLeftSize.GetWidth();
1331 int ltxtHeight = mLeftSize.GetHeight();
1332 int rtxtWidth = mRightSize.GetWidth();
1333 int rtxtHeight = mRightSize.GetHeight();
1334
1335 switch (mStyle)
1336 {
1337 default:
1338 wxPrintf(wxT("Style not handled yet!\n"));
1339 break;
1340 case MixerTrackCluster:
1341 // width is now the entire width of the meter canvas
1342 width -= mRulerWidth + left;
1343
1344 // height is now the entire height of the meter canvas
1345 height -= top + gap;
1346
1347 // barw is half of the canvas while allowing for a gap between meters
1348 barw = (width - gap) / 2;
1349
1350 // barh is now the height of the canvas
1351 barh = height;
1352
1353 // We always have 2 bars
1354 mNumBars = 2;
1355
1356 // Save dimensions of the left bevel
1357 mBar[0].b = wxRect(left, top, barw, barh);
1358
1359 // Save dimensions of the right bevel
1360 mBar[1].b = mBar[0].b;
1361 mBar[1].b.SetLeft(mBar[0].b.GetRight() + 1 + gap); // +1 for right edge
1362
1363 // Set bar and clipping indicator dimensions
1364 SetBarAndClip(0, true);
1365 SetBarAndClip(1, true);
1366
1367 mRuler.SetBounds(mBar[1].r.GetRight() + 1, // +1 for the bevel
1368 mBar[1].r.GetTop(),
1369 mWidth,
1370 mBar[1].r.GetBottom());
1371 mRuler.OfflimitsPixels(0, 0);
1372 break;
1373 case VerticalStereo:
1374 // Determine required width of each side;
1375 lside = ltxtWidth + gap;
1376 rside = intmax(mRulerWidth, rtxtWidth);
1377
1378 // left is now the right edge of the icon or L label
1379 left = lside;
1380
1381 // Ensure there's a margin between top edge of window and the meters
1382 top = gap;
1383
1384 // Position the L/R labels
1385 mLeftTextPos = wxPoint(left - ltxtWidth - gap, height - gap - ltxtHeight);
1386 mRightTextPos = wxPoint(width - rside - gap, height - gap - rtxtHeight);
1387
1388 // left is now left edge of left bar
1389 left += gap;
1390
1391 // width is now the entire width of the meter canvas
1392 width -= gap + rside + gap + left;
1393
1394 // height is now the entire height of the meter canvas
1395 height -= top + gap;
1396
1397 mSliderPos = wxPoint{ 0, top - gap };
1398 mSliderSize = wxSize{ width, height + 2 * gap };
1399
1400 // barw is half of the canvas while allowing for a gap between meters
1401 barw = (width - gap) / 2;
1402
1403 // barh is now the height of the canvas
1404 barh = height;
1405
1406 // We always have 2 bars
1407 mNumBars = 2;
1408
1409 // Save dimensions of the left bevel
1410 mBar[0].b = wxRect(left, top, barw, barh);
1411
1412 // Save dimensions of the right bevel
1413 mBar[1].b = mBar[0].b;
1414 mBar[1].b.SetLeft(mBar[0].b.GetRight() + 1 + gap); // +1 for right edge
1415
1416 // Set bar and clipping indicator dimensions
1417 SetBarAndClip(0, true);
1418 SetBarAndClip(1, true);
1419
1420 mRuler.SetBounds(mBar[1].r.GetRight() + 1, // +1 for the bevel
1421 mBar[1].r.GetTop(),
1422 mWidth,
1423 mBar[1].r.GetBottom());
1424 mRuler.OfflimitsPixels(mRightTextPos.y - gap, mBar[1].r.GetBottom());
1425 break;
1427 // Ensure there's a margin between top edge of window and the meters
1428 top = gap;
1429
1430 // height is now the entire height of the meter canvas
1431 height -= top + gap + ltxtHeight + gap;
1432
1433 mSliderPos = wxPoint{ 0, top - gap };
1434 mSliderSize = wxSize{ width, height + 2 * gap };
1435
1436 // barw is half of the canvas while allowing for a gap between meters
1437 barw = (width / 2) - gap;
1438
1439 // barh is now the height of the canvas
1440 barh = height;
1441
1442 // We always have 2 bars
1443 mNumBars = 2;
1444
1445 // Save dimensions of the left bevel
1446 mBar[0].b = wxRect(left, top, barw, barh);
1447
1448 // Save dimensions of the right bevel
1449 mBar[1].b = mBar[0].b;
1450 mBar[1].b.SetLeft(mBar[0].b.GetRight() + 1 + gap); // +1 for right edge
1451
1452 // Set bar and clipping indicator dimensions
1453 SetBarAndClip(0, true);
1454 SetBarAndClip(1, true);
1455
1456 // L/R is centered horizontally under each bar
1457 mLeftTextPos = wxPoint(mBar[0].b.GetLeft() + ((mBar[0].b.GetWidth() - ltxtWidth) / 2), top + barh + gap);
1458 mRightTextPos = wxPoint(mBar[1].b.GetLeft() + ((mBar[1].b.GetWidth() - rtxtWidth) / 2), top + barh + gap);
1459
1461 mBar[1].r.GetTop(),
1462 (mWidth - mRulerWidth) / 2,
1463 mBar[1].r.GetBottom());
1464 mRuler.OfflimitsPixels(0, 0);
1465 break;
1466 case HorizontalStereo:
1467 // Button right next to dragger.
1468 left = 0;
1469
1470 // Add a gap between bottom of icon and bottom of window
1471 height -= gap;
1472
1473 left = gap;
1474
1475 // Make sure there's room for icon and gap between the bottom of the meter and icon
1476 height -= rtxtHeight + gap;
1477
1478 // L/R is centered vertically and to the left of a each bar
1479 mLeftTextPos = wxPoint(left, (height / 4) - ltxtHeight / 2);
1480 mRightTextPos = wxPoint(left, (height * 3 / 4) - rtxtHeight / 2);
1481
1482 // Add width of widest of the L/R characters
1483 left += intmax(ltxtWidth, rtxtWidth); //, iconWidth);
1484
1485 mSliderPos = wxPoint{ left - gap, 0 };
1486
1487 // Add gap between L/R and meter bevel
1488 left += gap;
1489
1490 // width is now the entire width of the meter canvas
1491 width -= left;
1492
1493 mSliderSize = wxSize{ width + 2 * gap, height };
1494
1495 // barw is now the width of the canvas minus gap between canvas and right window edge
1496 barw = width - gap;
1497
1498 // barh is half of the canvas while allowing for a gap between meters
1499 barh = (height - gap) / 2;
1500
1501 // We always have 2 bars
1502 mNumBars = 2;
1503
1504 // Save dimensions of the top bevel
1505 mBar[0].b = wxRect(left, top, barw, barh);
1506
1507 // Save dimensions of the bottom bevel
1508 mBar[1].b = mBar[0].b;
1509 mBar[1].b.SetTop(mBar[0].b.GetBottom() + 1 + gap); // +1 for bottom edge
1510
1511 // Set bar and clipping indicator dimensions
1512 SetBarAndClip(0, false);
1513 SetBarAndClip(1, false);
1514
1515 mRuler.SetBounds(mBar[1].r.GetLeft(),
1516 mBar[1].r.GetBottom() + 1, // +1 to fit below bevel
1517 mBar[1].r.GetRight(),
1518 mHeight - mBar[1].r.GetBottom() + 1);
1519 break;
1521 left = gap;
1522
1523 // L/R is centered vertically and to the left of a each bar
1524 mLeftTextPos = wxPoint(left, (height / 4) - (ltxtHeight / 2));
1525 mRightTextPos = wxPoint(left, (height * 3 / 4) - (ltxtHeight / 2));
1526
1527 // Add width of widest of the L/R characters
1528 left += intmax(ltxtWidth, rtxtWidth);
1529
1530 mSliderPos = wxPoint{ left - gap, 0 };
1531
1532 // Add gap between L/R and meter bevel
1533 left += gap;
1534
1535 // width is now the entire width of the meter canvas
1536 width -= left;
1537
1538 mSliderSize = wxSize{ width + 2 * gap, height };
1539
1540 // barw is now the width of the canvas minus gap between canvas and window edge
1541 barw = width - gap;
1542
1543 // barh is half of the canvas while allowing for a gap between meters
1544 barh = (height - gap) / 2;
1545
1546 // We always have 2 bars
1547 mNumBars = 2;
1548
1549 // Save dimensions of the top bevel
1550 mBar[0].b = wxRect(left, top, barw, barh);
1551
1552 // Save dimensions of the bottom bevel
1553 // Since the bars butt up against the window's top and bottom edges, we need
1554 // to include an extra pixel in the bottom bar when the window height and
1555 // meter height do not exactly match.
1556 mBar[1].b = mBar[0].b;
1557 mBar[1].b.SetTop(mBar[0].b.GetBottom() + 1 + gap); // +1 for bottom bevel
1558 mBar[1].b.SetHeight(mHeight - mBar[1].b.GetTop() - 1); // +1 for bottom bevel
1559
1560 // Add clipping indicators - do after setting bar/bevel dimensions above
1561 SetBarAndClip(0, false);
1562 SetBarAndClip(1, false);
1563
1564 mRuler.SetBounds(mBar[1].r.GetLeft(),
1565 mBar[1].b.GetTop() - (mRulerHeight / 2),
1566 mBar[1].r.GetRight(),
1567 mBar[1].b.GetTop() - (mRulerHeight / 2));
1568 mRuler.OfflimitsPixels(0, 0);
1569 break;
1570 }
1571
1572 mLayoutValid = true;
1573}
1574
1576{
1577 if (mLayoutValid)
1578 {
1579 // Invalidate the bars so they get redrawn
1580 for (unsigned int i = 0; i < mNumBars; i++)
1581 {
1582 Refresh(false);
1583 }
1584
1585 // Immediate redraw (using wxPaintDC)
1586 Update();
1587
1588 return;
1589 }
1590}
1591
1593{
1594 // Cache some metrics
1595 wxCoord x = bar->r.GetLeft();
1596 wxCoord y = bar->r.GetTop();
1597 wxCoord w = bar->r.GetWidth();
1598 wxCoord h = bar->r.GetHeight();
1599 wxCoord ht;
1600 wxCoord wd;
1601
1602 // Setup for erasing the background
1603 dc.SetPen(*wxTRANSPARENT_PEN);
1605
1606 if (mGradient)
1607 {
1608 // Map the predrawn bitmap into the source DC
1609 wxMemoryDC srcDC;
1610 srcDC.SelectObject(*mBitmap);
1611
1612 if (bar->vert)
1613 {
1614 // Copy as much of the predrawn meter bar as is required for the
1615 // current peak.
1616 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1617 ht = (int)(bar->peak * (h - 1) + 0.5);
1618
1619 // Blank out the rest
1620 if (h - ht)
1621 {
1622 // ht includes peak value...not really needed but doesn't hurt
1623 dc.DrawRectangle(x, y, w, h - ht);
1624 }
1625
1626 // Copy as much of the predrawn meter bar as is required for the
1627 // current peak.
1628 // +/-1 to include the peak position
1629 if (ht)
1630 {
1631 dc.Blit(x, y + h - ht - 1, w, ht + 1, &srcDC, x, y + h - ht - 1);
1632 }
1633
1634 // Draw the "recent" peak hold line using the predrawn meter bar so that
1635 // it will be the same color as the original level.
1636 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1637 ht = (int)(bar->peakHold * (h - 1) + 0.5);
1638 if (ht > 1)
1639 {
1640 dc.Blit(x, y + h - ht - 1, w, 2, &srcDC, x, y + h - ht - 1);
1641 }
1642
1643 // Draw the "maximum" peak hold line
1644 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1645 dc.SetPen(mPeakPeakPen);
1646 ht = (int)(bar->peakPeakHold * (h - 1) + 0.5);
1647 if (ht > 0)
1648 {
1649 AColor::Line(dc, x, y + h - ht - 1, x + w - 1, y + h - ht - 1);
1650 if (ht > 1)
1651 {
1652 AColor::Line(dc, x, y + h - ht, x + w - 1, y + h - ht);
1653 }
1654 }
1655 }
1656 else
1657 {
1658 // Calculate the peak position
1659 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1660 wd = (int)(bar->peak * (w - 1) + 0.5);
1661
1662 // Blank out the rest
1663 if (w - wd)
1664 {
1665 // wd includes peak value...not really needed but doesn't hurt
1666 dc.DrawRectangle(x + wd, y, w - wd, h);
1667 }
1668
1669 // Copy as much of the predrawn meter bar as is required for the
1670 // current peak. But, only blit() if there's something to copy
1671 // to prevent display corruption.
1672 // +1 to include peak position
1673 if (wd)
1674 {
1675 dc.Blit(x, y, wd + 1, h, &srcDC, x, y);
1676 }
1677
1678 // Draw the "recent" peak hold line using the predrawn meter bar so that
1679 // it will be the same color as the original level.
1680 // -1 to give a 2 pixel width
1681 wd = (int)(bar->peakHold * (w - 1) + 0.5);
1682 if (wd > 1)
1683 {
1684 dc.Blit(x + wd - 1, y, 2, h, &srcDC, x + wd, y);
1685 }
1686
1687 // Draw the "maximum" peak hold line using a themed color
1688 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1689 dc.SetPen(mPeakPeakPen);
1690 wd = (int)(bar->peakPeakHold * (w - 1) + 0.5);
1691 if (wd > 0)
1692 {
1693 AColor::Line(dc, x + wd, y, x + wd, y + h - 1);
1694 if (wd > 1)
1695 {
1696 AColor::Line(dc, x + wd - 1, y, x + wd - 1, y + h - 1);
1697 }
1698 }
1699 }
1700
1701 // No longer need the source DC, so unselect the predrawn bitmap
1702 srcDC.SelectObject(wxNullBitmap);
1703 }
1704 else
1705 {
1706 if (bar->vert)
1707 {
1708 // Calculate the peak position
1709 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1710 ht = (int)(bar->peak * (h - 1) + 0.5);
1711
1712 // Blank out the rest
1713 if (h - ht)
1714 {
1715 // ht includes peak value...not really needed but doesn't hurt
1716 dc.DrawRectangle(x, y, w, h - ht);
1717 }
1718
1719 // Draw the peak level
1720 // +/-1 to include the peak position
1721 dc.SetPen(*wxTRANSPARENT_PEN);
1722 dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mBrush);
1723 if (ht)
1724 {
1725 dc.DrawRectangle(x, y + h - ht - 1, w, ht + 1);
1726 }
1727
1728 // Draw the "recent" peak hold line
1729 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1730 dc.SetPen(mPen);
1731 ht = (int)(bar->peakHold * (h - 1) + 0.5);
1732 if (ht > 0)
1733 {
1734 AColor::Line(dc, x, y + h - ht - 1, x + w - 1, y + h - ht - 1);
1735 if (ht > 1)
1736 {
1737 AColor::Line(dc, x, y + h - ht, x + w - 1, y + h - ht);
1738 }
1739 }
1740
1741 // Calculate the rms position
1742 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1743 // +1 to include the rms position
1744 ht = (int)(bar->rms * (h - 1) + 0.5);
1745
1746 // Draw the RMS level
1747 dc.SetPen(*wxTRANSPARENT_PEN);
1749 if (ht)
1750 {
1751 dc.DrawRectangle(x, y + h - ht - 1, w, ht + 1);
1752 }
1753
1754 // Draw the "maximum" peak hold line
1755 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1756 dc.SetPen(mPeakPeakPen);
1757 ht = (int)(bar->peakPeakHold * (h - 1) + 0.5);
1758 if (ht > 0)
1759 {
1760 AColor::Line(dc, x, y + h - ht - 1, x + w - 1, y + h - ht - 1);
1761 if (ht > 1)
1762 {
1763 AColor::Line(dc, x, y + h - ht, x + w - 1, y + h - ht);
1764 }
1765 }
1766 }
1767 else
1768 {
1769 // Calculate the peak position
1770 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1771 wd = (int)(bar->peak * (w - 1) + 0.5);
1772
1773 // Blank out the rest
1774 if (w - wd)
1775 {
1776 // wd includes peak value...not really needed but doesn't hurt
1777 dc.DrawRectangle(x + wd, y, w - wd, h);
1778 }
1779
1780 // Draw the peak level
1781 // +1 to include peak position
1782 dc.SetPen(*wxTRANSPARENT_PEN);
1783 dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mBrush);
1784 if (wd)
1785 {
1786 dc.DrawRectangle(x, y, wd + 1, h);
1787 }
1788
1789 // Draw the "recent" peak hold line
1790 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1791 dc.SetPen(mPen);
1792 wd = (int)(bar->peakHold * (w - 1) + 0.5);
1793 if (wd > 0)
1794 {
1795 AColor::Line(dc, x + wd, y, x + wd, y + h - 1);
1796 if (wd > 1)
1797 {
1798 AColor::Line(dc, x + wd - 1, y, x + wd - 1, y + h - 1);
1799 }
1800 }
1801
1802 // Calculate the rms position
1803 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1804 wd = (int)(bar->rms * (w - 1) + 0.5);
1805
1806 // Draw the rms level
1807 // +1 to include the rms position
1808 dc.SetPen(*wxTRANSPARENT_PEN);
1810 if (wd)
1811 {
1812 dc.DrawRectangle(x, y, wd + 1, h);
1813 }
1814
1815 // Draw the "maximum" peak hold line using a themed color
1816 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1817 dc.SetPen(mPeakPeakPen);
1818 wd = (int)(bar->peakPeakHold * (w - 1) + 0.5);
1819 if (wd > 0)
1820 {
1821 AColor::Line(dc, x + wd, y, x + wd, y + h - 1);
1822 if (wd > 1)
1823 {
1824 AColor::Line(dc, x + wd - 1, y, x + wd - 1, y + h - 1);
1825 }
1826 }
1827 }
1828 }
1829
1830 // If meter had a clipping indicator, draw or erase it
1831 // LLL: At least I assume that's what "mClip" is supposed to be for as
1832 // it is always "true".
1833 if (mClip)
1834 {
1835 if (bar->clipping)
1836 {
1837 dc.SetBrush(mClipBrush);
1838 }
1839 else
1840 {
1842 }
1843 dc.SetPen(*wxTRANSPARENT_PEN);
1844 wxRect r(bar->rClip.GetX() + 1,
1845 bar->rClip.GetY() + 1,
1846 bar->rClip.GetWidth() - 1,
1847 bar->rClip.GetHeight() - 1);
1848 dc.DrawRectangle(r);
1849 }
1850}
1851
1853{
1854 return mMeterDisabled != 0;
1855}
1856
1858{
1859 bool start = !mMonitoring;
1860
1861 auto gAudioIO = AudioIO::Get();
1862 if (gAudioIO->IsMonitoring()){
1863 gAudioIO->StopStream();
1864 }
1865
1866 if (start && !gAudioIO->IsBusy()){
1868 if (p)
1869 gAudioIO->StartMonitoring(ProjectAudioIO::GetDefaultOptions(*p));
1870
1871 mLayoutValid = false;
1872
1873 Refresh(false);
1874 }
1875}
1876
1878 mMonitoring = false;
1879 auto gAudioIO = AudioIO::Get();
1880 if (gAudioIO->IsMonitoring()){
1881 gAudioIO->StopStream();
1882 }
1883}
1884
1886{
1887 if (!mIsInput != (evt.type == AudioIOEvent::PLAYBACK))
1888 return;
1889
1890 AudacityProject *p = evt.pProject;
1891 mActive = evt.on && (p == mProject);
1892 if( mActive ){
1893 mTimer.Start(1000 / mMeterRefreshRate);
1894 if (evt.type == AudioIOEvent::MONITOR)
1896 } else {
1897 mTimer.Stop();
1898 mMonitoring = false;
1899 }
1900
1901 // Only refresh is we're the active meter
1902 if (IsShownOnScreen())
1903 Refresh(false);
1904}
1905
1907{
1908 if (
1909 event.type == AudioIOEvent::CAPTURE &&
1910 (event.pProject != mProject || !event.on))
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"))
IMPLEMENT_CLASS(AudioSetupToolBar, ToolBar)
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:340
#define safenew
Definition: MemoryX.h:9
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:338
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:337
@ OnPreferencesID
Definition: MeterPanel.cpp:269
@ OnMonitorID
Definition: MeterPanel.cpp:268
@ OnMeterUpdateID
Definition: MeterPanel.cpp:267
@ OnTipTimeoutID
Definition: MeterPanel.cpp:270
static float ClipZeroToOne(float z)
Definition: MeterPanel.cpp:937
static int intmax(int a, int b)
Definition: MeterPanel.cpp:932
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
static float ToDB(float v, float range)
Definition: MeterPanel.cpp:947
static float floatMax(float a, float b)
Definition: MeterPanel.cpp:920
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,...
@ 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:750
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:957
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:860
void OnTipTimeout(wxTimerEvent &evt)
void OnKeyDown(wxKeyEvent &evt)
Definition: MeterPanel.cpp:784
void Decrease(float steps)
Definition: MeterPanel.cpp:886
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:772
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:875
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:724
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:800
wxSize mLeftSize
Definition: MeterPanel.h:293
wxPen mPeakPeakPen
Definition: MeterPanel.h:297
void RepaintBarsNow()
void SetStyle(Style newStyle)
Definition: MeterPanel.cpp:816
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:897
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:716
void OnKillFocus(wxFocusEvent &evt)
Definition: MeterPanel.cpp:806
void SetMixer(wxCommandEvent &event)
Definition: MeterPanel.cpp:828
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:612
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:640
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:384
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:375
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
__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.