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#ifndef EXPERIMENTAL_DA
668 mRuler.Draw(dc);
669#endif
670
671 // Bitmap created...unselect
672 dc.SelectObject(wxNullBitmap);
673 }
674
675 // Copy predrawn bitmap to the dest DC
676 destDC.DrawBitmap(*mBitmap, 0, 0);
677
678 // Go draw the meter bars, Left & Right channels using current levels
679 for (unsigned int i = 0; i < mNumBars; i++)
680 {
681 DrawMeterBar(destDC, &mBar[i]);
682 }
683
684 destDC.SetTextForeground( clrText );
685
686#ifndef EXPERIMENTAL_DA
687 // We can have numbers over the bars, in which case we have to draw them each time.
689 {
690 mRuler.SetTickColour( clrText );
691 // If the text colour is too similar to the meter colour, then we need a background
692 // for the text. We require a total of at least one full-scale RGB difference.
693 int d = theTheme.ColourDistance( clrText, theTheme.Colour( clrMeterOutputRMSBrush ) );
694 if( d < 256 )
695 {
696 destDC.SetBackgroundMode( wxSOLID );
697 destDC.SetTextBackground( clrBoxFill );
698 }
699 mRuler.Draw(destDC);
700 }
701#endif
702
704 {
705 bool highlighted =
706 wxRect{ mSliderPos, mSliderSize }.Contains(
707 ScreenToClient(
708 ::wxGetMousePosition()));
709
710 mSlider->Move(mSliderPos);
711 mSlider->AdjustSize(mSliderSize);
712 mSlider->OnPaint(destDC, highlighted);
713 }
714
715 if (mIsFocused)
716 {
717 auto r = GetClientRect();
718 AColor::DrawFocus(destDC, r);
719 }
720}
721
722void MeterPanel::OnSize(wxSizeEvent & WXUNUSED(event))
723{
724 GetClientSize(&mWidth, &mHeight);
725
726 mLayoutValid = false;
727 Refresh();
728}
729
730void MeterPanel::OnMouse(wxMouseEvent &evt)
731{
732 if ((evt.GetEventType() == wxEVT_MOTION || evt.Entering() || evt.Leaving())) {
733 mLayoutValid = false;
734 Refresh();
735 }
736
737 if (mStyle == MixerTrackCluster) // MixerTrackCluster style has no menu.
738 return;
739
740 if (evt.Entering()) {
741 mTipTimer.StartOnce(500);
742 }
743 else if(evt.Leaving())
744 mTipTimer.Stop();
745
746 if (evt.RightDown())
747 ShowMenu(evt.GetPosition());
748 else
749 {
750
751 if (mSlider)
752 mSlider->OnMouseEvent(evt);
753 }
754}
755
756void MeterPanel::OnCharHook(wxKeyEvent& evt)
757{
758 switch(evt.GetKeyCode())
759 {
760 // These are handled in the OnCharHook handler because, on Windows at least, the
761 // key up event will be passed on to the menu if we show it here. This causes
762 // the default sound to be heard if assigned.
763 case WXK_RETURN:
764 case WXK_NUMPAD_ENTER:
765 case WXK_WINDOWS_MENU:
766 case WXK_MENU:
768 ShowMenu(GetClientRect().GetBottomLeft());
769 else
770 evt.Skip();
771 break;
772 default:
773 evt.Skip();
774 break;
775 }
776}
777
778void MeterPanel::OnContext(wxContextMenuEvent &evt)
779{
780 if (mStyle != MixerTrackCluster) // MixerTrackCluster style has no menu.
781 {
782 ShowMenu(GetClientRect().GetBottomLeft());
783 }
784 else
785 {
786 evt.Skip();
787 }
788}
789
790void MeterPanel::OnKeyDown(wxKeyEvent &evt)
791{
792 switch (evt.GetKeyCode())
793 {
794 case WXK_TAB:
795 if (evt.ShiftDown())
796 Navigate(wxNavigationKeyEvent::IsBackward);
797 else
798 Navigate(wxNavigationKeyEvent::IsForward);
799 break;
800 default:
801 mSlider->OnKeyDown(evt);
802 break;
803 }
804}
805
806void MeterPanel::OnSetFocus(wxFocusEvent & WXUNUSED(evt))
807{
808 mIsFocused = true;
809 Refresh(false);
810}
811
812void MeterPanel::OnKillFocus(wxFocusEvent & WXUNUSED(evt))
813{
814 if(mSlider)
815 mSlider->OnKillFocus();
816 mTipTimer.Stop();
817
818 mIsFocused = false;
819 Refresh(false);
820}
821
823{
824 if (mStyle != newStyle && mDesiredStyle == AutomaticStereo)
825 {
826 SetActiveStyle(newStyle);
827
828 mLayoutValid = false;
829
830 Refresh(false);
831 }
832}
833
834void MeterPanel::SetMixer(wxCommandEvent & WXUNUSED(event))
835{
836#if USE_PORTMIXER
837 if (mSlider)
838 {
839 float inputVolume;
840 float outputVolume;
841 int inputSource;
842
843 Refresh();
844
845 auto gAudioIO = AudioIO::Get();
846 gAudioIO->GetMixer(&inputSource, &inputVolume, &outputVolume);
847
848 if (mIsInput)
849 inputVolume = mSlider->Get();
850 else
851 outputVolume = mSlider->Get();
852
853 gAudioIO->SetMixer(inputSource, inputVolume, outputVolume);
854
855#if wxUSE_ACCESSIBILITY
856 GetAccessible()->NotifyEvent( wxACC_EVENT_OBJECT_VALUECHANGE,
857 this,
858 wxOBJID_CLIENT,
859 wxACC_SELF );
860#endif
861
862 }
863#endif // USE_PORTMIXER
864}
865
867{
868 if (!mSlider)
869 return false;
870
871 auto changed = mSlider->ShowDialog();
872 if (changed)
873 {
874 wxCommandEvent e;
875 SetMixer(e);
876 }
877
878 return changed;
879}
880
881void MeterPanel::Increase(float steps)
882{
883 if (mSlider)
884 {
885 wxCommandEvent e;
886
887 mSlider->Increase(steps);
888 SetMixer(e);
889 }
890}
891
892void MeterPanel::Decrease(float steps)
893{
894 if (mSlider)
895 {
896 wxCommandEvent e;
897
898 mSlider->Decrease(steps);
899 SetMixer(e);
900 }
901}
902
903void MeterPanel::Reset(double sampleRate, bool resetClipping)
904{
905 mT = 0;
907 for (int j = 0; j < kMaxMeterBars; j++)
908 {
909 ResetBar(&mBar[j], resetClipping);
910 }
911
912 // wxTimers seem to be a little unreliable - sometimes they stop for
913 // no good reason, so this "primes" it every now and then...
914 mTimer.Stop();
915
916 // While it's stopped, empty the queue
917 mQueue.Clear();
918
919 mLayoutValid = false;
920
921 mTimer.Start(1000 / mMeterRefreshRate);
922
923 Refresh(false);
924}
925
926static float floatMax(float a, float b)
927{
928 return a>b? a: b;
929}
930
931/* Unused as yet.
932static int intmin(int a, int b)
933{
934 return a<b? a: b;
935}
936*/
937
938static int intmax(int a, int b)
939{
940 return a>b? a: b;
941}
942
943static float ClipZeroToOne(float z)
944{
945 if (z > 1.0)
946 return 1.0;
947 else if (z < 0.0)
948 return 0.0;
949 else
950 return z;
951}
952
953static float ToDB(float v, float range)
954{
955 double db;
956 if (v > 0)
957 db = LINEAR_TO_DB(fabs(v));
958 else
959 db = -999;
960 return ClipZeroToOne((db + range) / range);
961}
962
964 unsigned numChannels, int numFrames, const float *sampleData)
965{
966 auto sptr = sampleData;
967 auto num = std::min(numChannels, mNumBars);
968 MeterUpdateMsg msg;
969
970 memset(&msg, 0, sizeof(msg));
971 msg.numFrames = numFrames;
972
973 for(int i=0; i<numFrames; i++) {
974 for(unsigned int j=0; j<num; j++) {
975 msg.peak[j] = floatMax(msg.peak[j], fabs(sptr[j]));
976 msg.rms[j] += sptr[j]*sptr[j];
977
978 // In addition to looking for mNumPeakSamplesToClip peaked
979 // samples in a row, also send the number of peaked samples
980 // at the head and tail, in case there's a run of peaked samples
981 // that crosses block boundaries
982 if (fabs(sptr[j])>=MAX_AUDIO) {
983 if (msg.headPeakCount[j]==i)
984 msg.headPeakCount[j]++;
985 msg.tailPeakCount[j]++;
987 msg.clipping[j] = true;
988 }
989 else
990 msg.tailPeakCount[j] = 0;
991 }
992 sptr += numChannels;
993 }
994 for(unsigned int j=0; j<mNumBars; j++)
995 msg.rms[j] = sqrt(msg.rms[j]/numFrames);
996
997 mQueue.Put(msg);
998}
999
1000// Vaughan, 2010-11-29: This not currently used. See comments in MixerTrackCluster::UpdateMeter().
1001//void MeterPanel::UpdateDisplay(int numChannels, int numFrames,
1002// // Need to make these double-indexed arrays if we handle more than 2 channels.
1003// float* maxLeft, float* rmsLeft,
1004// float* maxRight, float* rmsRight,
1005// const size_t kSampleCount)
1006//{
1007// int i, j;
1008// int num = intmin(numChannels, mNumBars);
1009// MeterUpdateMsg msg;
1010//
1011// msg.numFrames = kSampleCount;
1012// for(j=0; j<mNumBars; j++) {
1013// msg.peak[j] = 0.0;
1014// msg.rms[j] = 0.0;
1015// msg.clipping[j] = false;
1016// msg.headPeakCount[j] = 0;
1017// msg.tailPeakCount[j] = 0;
1018// }
1019//
1020// for(i=0; i<numFrames; i++) {
1021// for(j=0; j<num; j++) {
1022// msg.peak[j] = floatMax(msg.peak[j], ((j == 0) ? maxLeft[i] : maxRight[i]));
1023// msg.rms[j] = floatMax(msg.rms[j], ((j == 0) ? rmsLeft[i] : rmsRight[i]));
1024//
1025// // In addition to looking for mNumPeakSamplesToClip peaked
1026// // samples in a row, also send the number of peaked samples
1027// // at the head and tail, in case there's a run
1028// // of peaked samples that crosses block boundaries.
1029// if (fabs((j == 0) ? maxLeft[i] : maxRight[i]) >= MAX_AUDIO)
1030// {
1031// if (msg.headPeakCount[j]==i)
1032// msg.headPeakCount[j]++;
1033// msg.tailPeakCount[j]++;
1034// if (msg.tailPeakCount[j] > mNumPeakSamplesToClip)
1035// msg.clipping[j] = true;
1036// }
1037// else
1038// msg.tailPeakCount[j] = 0;
1039// }
1040// }
1041//
1042// mQueue.Put(msg);
1043//}
1044
1045void MeterPanel::OnMeterUpdate(wxTimerEvent & WXUNUSED(event))
1046{
1047 MeterUpdateMsg msg;
1048 int numChanges = 0;
1049#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1050 double maxPeak = 0.0;
1051 bool discarded = false;
1052#endif
1053
1054 // We shouldn't receive any events if the meter is disabled, but clear it to be safe
1055 if (mMeterDisabled) {
1056 mQueue.Clear();
1057 return;
1058 }
1059
1060
1061 // There may have been several update messages since the last
1062 // time we got to this function. Catch up to real-time by
1063 // popping them off until there are none left. It is necessary
1064 // to process all of them, otherwise we won't handle peaks and
1065 // peak-hold bars correctly.
1066 while(mQueue.Get(msg)) {
1067 numChanges++;
1068 double deltaT = msg.numFrames / mRate;
1069
1070 mT += deltaT;
1071 for(unsigned int j=0; j<mNumBars; j++) {
1072 mBar[j].isclipping = false;
1073
1074 //
1075 if (mDB) {
1076 msg.peak[j] = ToDB(msg.peak[j], mDBRange);
1077 msg.rms[j] = ToDB(msg.rms[j], mDBRange);
1078 }
1079
1080 if (mDecay) {
1081 if (mDB) {
1082 float decayAmount = mDecayRate * deltaT / mDBRange;
1083 mBar[j].peak = floatMax(msg.peak[j],
1084 mBar[j].peak - decayAmount);
1085 }
1086 else {
1087 double decayAmount = mDecayRate * deltaT;
1088 double decayFactor = DB_TO_LINEAR(-decayAmount);
1089 mBar[j].peak = floatMax(msg.peak[j],
1090 mBar[j].peak * decayFactor);
1091 }
1092 }
1093 else
1094 mBar[j].peak = msg.peak[j];
1095
1096 // This smooths out the RMS signal
1097 float smooth = pow(0.9, (double)msg.numFrames/1024.0);
1098 mBar[j].rms = mBar[j].rms * smooth + msg.rms[j] * (1.0 - smooth);
1099
1100 if (mT - mBar[j].peakHoldTime > mPeakHoldDuration ||
1101 mBar[j].peak > mBar[j].peakHold) {
1102 mBar[j].peakHold = mBar[j].peak;
1103 mBar[j].peakHoldTime = mT;
1104 }
1105
1106 if (mBar[j].peak > mBar[j].peakPeakHold )
1107 mBar[j].peakPeakHold = mBar[j].peak;
1108
1109 if (msg.clipping[j] ||
1110 mBar[j].tailPeakCount+msg.headPeakCount[j] >=
1112 mBar[j].clipping = true;
1113 mBar[j].isclipping = true;
1114 }
1115
1116 mBar[j].tailPeakCount = msg.tailPeakCount[j];
1117#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1118 if (mT > gAudioIO->AILAGetLastDecisionTime()) {
1119 discarded = false;
1120 maxPeak = msg.peak[j] > maxPeak ? msg.peak[j] : maxPeak;
1121 wxPrintf("%f@%f ", msg.peak[j], mT);
1122 }
1123 else {
1124 discarded = true;
1125 wxPrintf("%f@%f discarded\n", msg.peak[j], mT);
1126 }
1127#endif
1128 }
1129 } // while
1130
1131 if (numChanges > 0) {
1132 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1133 if (gAudioIO->AILAIsActive() && mIsInput && !discarded) {
1134 gAudioIO->AILAProcess(maxPeak);
1135 putchar('\n');
1136 }
1137 #endif
1139 }
1140}
1141
1142void MeterPanel::OnTipTimeout(wxTimerEvent& evt)
1143{
1144 if(mSlider)
1145 mSlider->ShowTip(true);
1146}
1147
1148
1150{
1151 float maxPeak = 0.;
1152
1153 for(unsigned int j=0; j<mNumBars; j++)
1154 maxPeak = mBar[j].peak > maxPeak ? mBar[j].peak : maxPeak;
1155
1156 return(maxPeak);
1157}
1158
1160{
1161 auto peakHold = .0f;
1162 for (unsigned int i = 0; i < mNumBars; i++)
1163 peakHold = std::max(peakHold, mBar[i].peakPeakHold);
1164 return peakHold;
1165}
1166
1168{
1169 int fontSize = 10;
1170#if defined __WXMSW__
1171 fontSize = 8;
1172#endif
1173
1174 return wxFont(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1175}
1176
1177void MeterPanel::ResetBar(MeterBar *b, bool resetClipping)
1178{
1179 b->peak = 0.0;
1180 b->rms = 0.0;
1181 b->peakHold = 0.0;
1182 b->peakHoldTime = 0.0;
1183 if (resetClipping)
1184 {
1185 b->clipping = false;
1186 b->peakPeakHold = 0.0;
1187 }
1188 b->isclipping = false;
1189 b->tailPeakCount = 0;
1190}
1191
1193{
1194 return mActive;
1195}
1196
1198{
1199 return mMonitoring;
1200}
1201
1203{
1204 for (int c = 0; c < mNumBars; c++)
1205 if (mBar[c].clipping)
1206 return true;
1207 return false;
1208}
1209
1211{
1212 mStyle = newStyle;
1213
1214 // Set dummy ruler bounds so width/height can be retrieved
1215 // NOTE: Make sure the Right and Bottom values are large enough to
1216 // ensure full width/height of digits get calculated.
1217 mRuler.SetBounds(0, 0, 500, 500);
1218
1219 if (mDB)
1220 {
1223 {
1224 mRuler.SetOrientation(wxHORIZONTAL);
1226 }
1227 else
1228 {
1229 mRuler.SetOrientation(wxVERTICAL);
1231 }
1232 }
1233 else
1234 {
1237 {
1238 mRuler.SetOrientation(wxHORIZONTAL);
1239 mRuler.SetRange(0, 1);
1240 }
1241 else
1242 {
1243 mRuler.SetOrientation(wxVERTICAL);
1244 mRuler.SetRange(1, 0);
1245 }
1246 }
1247
1249}
1250
1251void MeterPanel::SetBarAndClip(int iBar, bool vert)
1252{
1253 // Save the orientation
1254 mBar[iBar].vert = vert;
1255
1256 // Create the bar rectangle and educe to fit inside the bevel
1257 mBar[iBar].r = mBar[iBar].b;
1258 mBar[iBar].r.x += 1;
1259 mBar[iBar].r.width -= 1;
1260 mBar[iBar].r.y += 1;
1261 mBar[iBar].r.height -= 1;
1262
1263 if (vert)
1264 {
1265 if (mClip)
1266 {
1267 // Create the clip rectangle
1268 mBar[iBar].rClip = mBar[iBar].b;
1269 mBar[iBar].rClip.height = 3;
1270
1271 // Make room for the clipping indicator
1272 mBar[iBar].b.y += 3 + gap;
1273 mBar[iBar].b.height -= 3 + gap;
1274 mBar[iBar].r.y += 3 + gap;
1275 mBar[iBar].r.height -= 3 + gap;
1276 }
1277 }
1278 else
1279 {
1280 if (mClip)
1281 {
1282 // Make room for the clipping indicator
1283 mBar[iBar].b.width -= 4;
1284 mBar[iBar].r.width -= 4;
1285
1286 // Create the indicator rectangle
1287 mBar[iBar].rClip = mBar[iBar].b;
1288 mBar[iBar].rClip.x = mBar[iBar].b.GetRight() + 1 + gap; // +1 for bevel
1289 mBar[iBar].rClip.width = 3;
1290 }
1291 }
1292}
1293
1295{
1296 // Refresh to reflect any language changes
1297 /* i18n-hint: One-letter abbreviation for Left, in VU Meter */
1298 mLeftText = _("L");
1299 /* i18n-hint: One-letter abbreviation for Right, in VU Meter */
1300 mRightText = _("R");
1301
1302 dc.SetFont(GetFont());
1303 int width = mWidth;
1304 int height = mHeight;
1305 int left = 0;
1306 int top = 0;
1307 int barw;
1308 int barh;
1309 int lside;
1310 int rside;
1311
1312 // MixerTrackCluster has no L/R labels or icon
1314 {
1316 {
1318 }
1319
1321 {
1323 }
1325 {
1327 }
1328
1329 if (mLeftSize.GetWidth() == 0) // Not yet initialized to dc.
1330 {
1331 dc.GetTextExtent(mLeftText, &mLeftSize.x, &mLeftSize.y);
1332 dc.GetTextExtent(mRightText, &mRightSize.x, &mRightSize.y);
1333 }
1334 }
1335
1336 int ltxtWidth = mLeftSize.GetWidth();
1337 int ltxtHeight = mLeftSize.GetHeight();
1338 int rtxtWidth = mRightSize.GetWidth();
1339 int rtxtHeight = mRightSize.GetHeight();
1340
1341 switch (mStyle)
1342 {
1343 default:
1344 wxPrintf(wxT("Style not handled yet!\n"));
1345 break;
1346 case MixerTrackCluster:
1347 // width is now the entire width of the meter canvas
1348 width -= mRulerWidth + left;
1349
1350 // height is now the entire height of the meter canvas
1351 height -= top + gap;
1352
1353 // barw is half of the canvas while allowing for a gap between meters
1354 barw = (width - gap) / 2;
1355
1356 // barh is now the height of the canvas
1357 barh = height;
1358
1359 // We always have 2 bars
1360 mNumBars = 2;
1361
1362 // Save dimensions of the left bevel
1363 mBar[0].b = wxRect(left, top, barw, barh);
1364
1365 // Save dimensions of the right bevel
1366 mBar[1].b = mBar[0].b;
1367 mBar[1].b.SetLeft(mBar[0].b.GetRight() + 1 + gap); // +1 for right edge
1368
1369 // Set bar and clipping indicator dimensions
1370 SetBarAndClip(0, true);
1371 SetBarAndClip(1, true);
1372
1373 mRuler.SetBounds(mBar[1].r.GetRight() + 1, // +1 for the bevel
1374 mBar[1].r.GetTop(),
1375 mWidth,
1376 mBar[1].r.GetBottom());
1377 mRuler.OfflimitsPixels(0, 0);
1378 break;
1379 case VerticalStereo:
1380 // Determine required width of each side;
1381 lside = ltxtWidth + gap;
1382 rside = intmax(mRulerWidth, rtxtWidth);
1383
1384 // left is now the right edge of the icon or L label
1385 left = lside;
1386
1387 // Ensure there's a margin between top edge of window and the meters
1388 top = gap;
1389
1390 // Position the L/R labels
1391 mLeftTextPos = wxPoint(left - ltxtWidth - gap, height - gap - ltxtHeight);
1392 mRightTextPos = wxPoint(width - rside - gap, height - gap - rtxtHeight);
1393
1394 // left is now left edge of left bar
1395 left += gap;
1396
1397 // width is now the entire width of the meter canvas
1398 width -= gap + rside + gap + left;
1399
1400 // height is now the entire height of the meter canvas
1401 height -= top + gap;
1402
1403 mSliderPos = wxPoint{ 0, top - gap };
1404 mSliderSize = wxSize{ width, height + 2 * gap };
1405
1406 // barw is half of the canvas while allowing for a gap between meters
1407 barw = (width - gap) / 2;
1408
1409 // barh is now the height of the canvas
1410 barh = height;
1411
1412 // We always have 2 bars
1413 mNumBars = 2;
1414
1415 // Save dimensions of the left bevel
1416 mBar[0].b = wxRect(left, top, barw, barh);
1417
1418 // Save dimensions of the right bevel
1419 mBar[1].b = mBar[0].b;
1420 mBar[1].b.SetLeft(mBar[0].b.GetRight() + 1 + gap); // +1 for right edge
1421
1422 // Set bar and clipping indicator dimensions
1423 SetBarAndClip(0, true);
1424 SetBarAndClip(1, true);
1425
1426 mRuler.SetBounds(mBar[1].r.GetRight() + 1, // +1 for the bevel
1427 mBar[1].r.GetTop(),
1428 mWidth,
1429 mBar[1].r.GetBottom());
1430 mRuler.OfflimitsPixels(mRightTextPos.y - gap, mBar[1].r.GetBottom());
1431 break;
1433 // Ensure there's a margin between top edge of window and the meters
1434 top = gap;
1435
1436 // height is now the entire height of the meter canvas
1437 height -= top + gap + ltxtHeight + gap;
1438
1439 mSliderPos = wxPoint{ 0, top - gap };
1440 mSliderSize = wxSize{ width, height + 2 * gap };
1441
1442 // barw is half of the canvas while allowing for a gap between meters
1443 barw = (width / 2) - gap;
1444
1445 // barh is now the height of the canvas
1446 barh = height;
1447
1448 // We always have 2 bars
1449 mNumBars = 2;
1450
1451 // Save dimensions of the left bevel
1452 mBar[0].b = wxRect(left, top, barw, barh);
1453
1454 // Save dimensions of the right bevel
1455 mBar[1].b = mBar[0].b;
1456 mBar[1].b.SetLeft(mBar[0].b.GetRight() + 1 + gap); // +1 for right edge
1457
1458 // Set bar and clipping indicator dimensions
1459 SetBarAndClip(0, true);
1460 SetBarAndClip(1, true);
1461
1462 // L/R is centered horizontally under each bar
1463 mLeftTextPos = wxPoint(mBar[0].b.GetLeft() + ((mBar[0].b.GetWidth() - ltxtWidth) / 2), top + barh + gap);
1464 mRightTextPos = wxPoint(mBar[1].b.GetLeft() + ((mBar[1].b.GetWidth() - rtxtWidth) / 2), top + barh + gap);
1465
1467 mBar[1].r.GetTop(),
1468 (mWidth - mRulerWidth) / 2,
1469 mBar[1].r.GetBottom());
1470 mRuler.OfflimitsPixels(0, 0);
1471 break;
1472 case HorizontalStereo:
1473 // Button right next to dragger.
1474 left = 0;
1475
1476 // Add a gap between bottom of icon and bottom of window
1477 height -= gap;
1478
1479 left = gap;
1480
1481 // Make sure there's room for icon and gap between the bottom of the meter and icon
1482 height -= rtxtHeight + gap;
1483
1484 // L/R is centered vertically and to the left of a each bar
1485 mLeftTextPos = wxPoint(left, (height / 4) - ltxtHeight / 2);
1486 mRightTextPos = wxPoint(left, (height * 3 / 4) - rtxtHeight / 2);
1487
1488 // Add width of widest of the L/R characters
1489 left += intmax(ltxtWidth, rtxtWidth); //, iconWidth);
1490
1491 mSliderPos = wxPoint{ left - gap, 0 };
1492
1493 // Add gap between L/R and meter bevel
1494 left += gap;
1495
1496 // width is now the entire width of the meter canvas
1497 width -= left;
1498
1499 mSliderSize = wxSize{ width + 2 * gap, height };
1500
1501 // barw is now the width of the canvas minus gap between canvas and right window edge
1502 barw = width - gap;
1503
1504 // barh is half of the canvas while allowing for a gap between meters
1505 barh = (height - gap) / 2;
1506
1507 // We always have 2 bars
1508 mNumBars = 2;
1509
1510 // Save dimensions of the top bevel
1511 mBar[0].b = wxRect(left, top, barw, barh);
1512
1513 // Save dimensions of the bottom bevel
1514 mBar[1].b = mBar[0].b;
1515 mBar[1].b.SetTop(mBar[0].b.GetBottom() + 1 + gap); // +1 for bottom edge
1516
1517 // Set bar and clipping indicator dimensions
1518 SetBarAndClip(0, false);
1519 SetBarAndClip(1, false);
1520
1521 mRuler.SetBounds(mBar[1].r.GetLeft(),
1522 mBar[1].r.GetBottom() + 1, // +1 to fit below bevel
1523 mBar[1].r.GetRight(),
1524 mHeight - mBar[1].r.GetBottom() + 1);
1525 break;
1527 left = gap;
1528
1529 // L/R is centered vertically and to the left of a each bar
1530 mLeftTextPos = wxPoint(left, (height / 4) - (ltxtHeight / 2));
1531 mRightTextPos = wxPoint(left, (height * 3 / 4) - (ltxtHeight / 2));
1532
1533 // Add width of widest of the L/R characters
1534 left += intmax(ltxtWidth, rtxtWidth);
1535
1536 mSliderPos = wxPoint{ left - gap, 0 };
1537
1538 // Add gap between L/R and meter bevel
1539 left += gap;
1540
1541 // width is now the entire width of the meter canvas
1542 width -= left;
1543
1544 mSliderSize = wxSize{ width + 2 * gap, height };
1545
1546 // barw is now the width of the canvas minus gap between canvas and window edge
1547 barw = width - gap;
1548
1549 // barh is half of the canvas while allowing for a gap between meters
1550 barh = (height - gap) / 2;
1551
1552 // We always have 2 bars
1553 mNumBars = 2;
1554
1555 // Save dimensions of the top bevel
1556 mBar[0].b = wxRect(left, top, barw, barh);
1557
1558 // Save dimensions of the bottom bevel
1559 // Since the bars butt up against the window's top and bottom edges, we need
1560 // to include an extra pixel in the bottom bar when the window height and
1561 // meter height do not exactly match.
1562 mBar[1].b = mBar[0].b;
1563 mBar[1].b.SetTop(mBar[0].b.GetBottom() + 1 + gap); // +1 for bottom bevel
1564 mBar[1].b.SetHeight(mHeight - mBar[1].b.GetTop() - 1); // +1 for bottom bevel
1565
1566 // Add clipping indicators - do after setting bar/bevel dimensions above
1567 SetBarAndClip(0, false);
1568 SetBarAndClip(1, false);
1569
1570 mRuler.SetBounds(mBar[1].r.GetLeft(),
1571 mBar[1].b.GetTop() - (mRulerHeight / 2),
1572 mBar[1].r.GetRight(),
1573 mBar[1].b.GetTop() - (mRulerHeight / 2));
1574 mRuler.OfflimitsPixels(0, 0);
1575 break;
1576 }
1577
1578 mLayoutValid = true;
1579}
1580
1582{
1583 if (mLayoutValid)
1584 {
1585 // Invalidate the bars so they get redrawn
1586 for (unsigned int i = 0; i < mNumBars; i++)
1587 {
1588 Refresh(false);
1589 }
1590
1591 // Immediate redraw (using wxPaintDC)
1592 Update();
1593
1594 return;
1595 }
1596}
1597
1599{
1600 // Cache some metrics
1601 wxCoord x = bar->r.GetLeft();
1602 wxCoord y = bar->r.GetTop();
1603 wxCoord w = bar->r.GetWidth();
1604 wxCoord h = bar->r.GetHeight();
1605 wxCoord ht;
1606 wxCoord wd;
1607
1608 // Setup for erasing the background
1609 dc.SetPen(*wxTRANSPARENT_PEN);
1611
1612 if (mGradient)
1613 {
1614 // Map the predrawn bitmap into the source DC
1615 wxMemoryDC srcDC;
1616 srcDC.SelectObject(*mBitmap);
1617
1618 if (bar->vert)
1619 {
1620 // Copy as much of the predrawn meter bar as is required for the
1621 // current peak.
1622 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1623 ht = (int)(bar->peak * (h - 1) + 0.5);
1624
1625 // Blank out the rest
1626 if (h - ht)
1627 {
1628 // ht includes peak value...not really needed but doesn't hurt
1629 dc.DrawRectangle(x, y, w, h - ht);
1630 }
1631
1632 // Copy as much of the predrawn meter bar as is required for the
1633 // current peak.
1634 // +/-1 to include the peak position
1635 if (ht)
1636 {
1637 dc.Blit(x, y + h - ht - 1, w, ht + 1, &srcDC, x, y + h - ht - 1);
1638 }
1639
1640 // Draw the "recent" peak hold line using the predrawn meter bar so that
1641 // it will be the same color as the original level.
1642 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1643 ht = (int)(bar->peakHold * (h - 1) + 0.5);
1644 if (ht > 1)
1645 {
1646 dc.Blit(x, y + h - ht - 1, w, 2, &srcDC, x, y + h - ht - 1);
1647 }
1648
1649 // Draw the "maximum" peak hold line
1650 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1651 dc.SetPen(mPeakPeakPen);
1652 ht = (int)(bar->peakPeakHold * (h - 1) + 0.5);
1653 if (ht > 0)
1654 {
1655 AColor::Line(dc, x, y + h - ht - 1, x + w - 1, y + h - ht - 1);
1656 if (ht > 1)
1657 {
1658 AColor::Line(dc, x, y + h - ht, x + w - 1, y + h - ht);
1659 }
1660 }
1661 }
1662 else
1663 {
1664 // Calculate the peak position
1665 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1666 wd = (int)(bar->peak * (w - 1) + 0.5);
1667
1668 // Blank out the rest
1669 if (w - wd)
1670 {
1671 // wd includes peak value...not really needed but doesn't hurt
1672 dc.DrawRectangle(x + wd, y, w - wd, h);
1673 }
1674
1675 // Copy as much of the predrawn meter bar as is required for the
1676 // current peak. But, only blit() if there's something to copy
1677 // to prevent display corruption.
1678 // +1 to include peak position
1679 if (wd)
1680 {
1681 dc.Blit(x, y, wd + 1, h, &srcDC, x, y);
1682 }
1683
1684 // Draw the "recent" peak hold line using the predrawn meter bar so that
1685 // it will be the same color as the original level.
1686 // -1 to give a 2 pixel width
1687 wd = (int)(bar->peakHold * (w - 1) + 0.5);
1688 if (wd > 1)
1689 {
1690 dc.Blit(x + wd - 1, y, 2, h, &srcDC, x + wd, y);
1691 }
1692
1693 // Draw the "maximum" peak hold line using a themed color
1694 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1695 dc.SetPen(mPeakPeakPen);
1696 wd = (int)(bar->peakPeakHold * (w - 1) + 0.5);
1697 if (wd > 0)
1698 {
1699 AColor::Line(dc, x + wd, y, x + wd, y + h - 1);
1700 if (wd > 1)
1701 {
1702 AColor::Line(dc, x + wd - 1, y, x + wd - 1, y + h - 1);
1703 }
1704 }
1705 }
1706
1707 // No longer need the source DC, so unselect the predrawn bitmap
1708 srcDC.SelectObject(wxNullBitmap);
1709 }
1710 else
1711 {
1712 if (bar->vert)
1713 {
1714 // Calculate the peak position
1715 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1716 ht = (int)(bar->peak * (h - 1) + 0.5);
1717
1718 // Blank out the rest
1719 if (h - ht)
1720 {
1721 // ht includes peak value...not really needed but doesn't hurt
1722 dc.DrawRectangle(x, y, w, h - ht);
1723 }
1724
1725 // Draw the peak level
1726 // +/-1 to include the peak position
1727 dc.SetPen(*wxTRANSPARENT_PEN);
1728 dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mBrush);
1729 if (ht)
1730 {
1731 dc.DrawRectangle(x, y + h - ht - 1, w, ht + 1);
1732 }
1733
1734 // Draw the "recent" peak hold line
1735 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1736 dc.SetPen(mPen);
1737 ht = (int)(bar->peakHold * (h - 1) + 0.5);
1738 if (ht > 0)
1739 {
1740 AColor::Line(dc, x, y + h - ht - 1, x + w - 1, y + h - ht - 1);
1741 if (ht > 1)
1742 {
1743 AColor::Line(dc, x, y + h - ht, x + w - 1, y + h - ht);
1744 }
1745 }
1746
1747 // Calculate the rms position
1748 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1749 // +1 to include the rms position
1750 ht = (int)(bar->rms * (h - 1) + 0.5);
1751
1752 // Draw the RMS level
1753 dc.SetPen(*wxTRANSPARENT_PEN);
1755 if (ht)
1756 {
1757 dc.DrawRectangle(x, y + h - ht - 1, w, ht + 1);
1758 }
1759
1760 // Draw the "maximum" peak hold line
1761 // (h - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1762 dc.SetPen(mPeakPeakPen);
1763 ht = (int)(bar->peakPeakHold * (h - 1) + 0.5);
1764 if (ht > 0)
1765 {
1766 AColor::Line(dc, x, y + h - ht - 1, x + w - 1, y + h - ht - 1);
1767 if (ht > 1)
1768 {
1769 AColor::Line(dc, x, y + h - ht, x + w - 1, y + h - ht);
1770 }
1771 }
1772 }
1773 else
1774 {
1775 // Calculate the peak position
1776 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1777 wd = (int)(bar->peak * (w - 1) + 0.5);
1778
1779 // Blank out the rest
1780 if (w - wd)
1781 {
1782 // wd includes peak value...not really needed but doesn't hurt
1783 dc.DrawRectangle(x + wd, y, w - wd, h);
1784 }
1785
1786 // Draw the peak level
1787 // +1 to include peak position
1788 dc.SetPen(*wxTRANSPARENT_PEN);
1789 dc.SetBrush(mMeterDisabled ? mDisabledBkgndBrush : mBrush);
1790 if (wd)
1791 {
1792 dc.DrawRectangle(x, y, wd + 1, h);
1793 }
1794
1795 // Draw the "recent" peak hold line
1796 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1797 dc.SetPen(mPen);
1798 wd = (int)(bar->peakHold * (w - 1) + 0.5);
1799 if (wd > 0)
1800 {
1801 AColor::Line(dc, x + wd, y, x + wd, y + h - 1);
1802 if (wd > 1)
1803 {
1804 AColor::Line(dc, x + wd - 1, y, x + wd - 1, y + h - 1);
1805 }
1806 }
1807
1808 // Calculate the rms position
1809 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1810 wd = (int)(bar->rms * (w - 1) + 0.5);
1811
1812 // Draw the rms level
1813 // +1 to include the rms position
1814 dc.SetPen(*wxTRANSPARENT_PEN);
1816 if (wd)
1817 {
1818 dc.DrawRectangle(x, y, wd + 1, h);
1819 }
1820
1821 // Draw the "maximum" peak hold line using a themed color
1822 // (w - 1) corresponds to the mRuler.SetBounds() in HandleLayout()
1823 dc.SetPen(mPeakPeakPen);
1824 wd = (int)(bar->peakPeakHold * (w - 1) + 0.5);
1825 if (wd > 0)
1826 {
1827 AColor::Line(dc, x + wd, y, x + wd, y + h - 1);
1828 if (wd > 1)
1829 {
1830 AColor::Line(dc, x + wd - 1, y, x + wd - 1, y + h - 1);
1831 }
1832 }
1833 }
1834 }
1835
1836 // If meter had a clipping indicator, draw or erase it
1837 // LLL: At least I assume that's what "mClip" is supposed to be for as
1838 // it is always "true".
1839 if (mClip)
1840 {
1841 if (bar->clipping)
1842 {
1843 dc.SetBrush(mClipBrush);
1844 }
1845 else
1846 {
1848 }
1849 dc.SetPen(*wxTRANSPARENT_PEN);
1850 wxRect r(bar->rClip.GetX() + 1,
1851 bar->rClip.GetY() + 1,
1852 bar->rClip.GetWidth() - 1,
1853 bar->rClip.GetHeight() - 1);
1854 dc.DrawRectangle(r);
1855 }
1856}
1857
1859{
1860 return mMeterDisabled != 0;
1861}
1862
1864{
1865 bool start = !mMonitoring;
1866
1867 auto gAudioIO = AudioIO::Get();
1868 if (gAudioIO->IsMonitoring()){
1869 gAudioIO->StopStream();
1870 }
1871
1872 if (start && !gAudioIO->IsBusy()){
1874 if (p)
1875 gAudioIO->StartMonitoring(ProjectAudioIO::GetDefaultOptions(*p));
1876
1877 mLayoutValid = false;
1878
1879 Refresh(false);
1880 }
1881}
1882
1884 mMonitoring = false;
1885 auto gAudioIO = AudioIO::Get();
1886 if (gAudioIO->IsMonitoring()){
1887 gAudioIO->StopStream();
1888 }
1889}
1890
1892{
1893 if (!mIsInput != (evt.type == AudioIOEvent::PLAYBACK))
1894 return;
1895
1896 AudacityProject *p = evt.pProject;
1897 mActive = evt.on && (p == mProject);
1898 if( mActive ){
1899 mTimer.Start(1000 / mMeterRefreshRate);
1900 if (evt.type == AudioIOEvent::MONITOR)
1902 } else {
1903 mTimer.Stop();
1904 mMonitoring = false;
1905 }
1906
1907 // Only refresh is we're the active meter
1908 if (IsShownOnScreen())
1909 Refresh(false);
1910}
1911
1913{
1914 if (event.type == AudioIOEvent::CAPTURE && event.pProject != mProject)
1915 {
1916 mEnabled = !event.on;
1917
1918 if (mSlider)
1919 mSlider->SetEnabled(mEnabled);
1920 }
1921}
1922
1923// SaveState() and RestoreState() exist solely for purpose of recreating toolbars
1924// They should really be querying the project for current audio I/O state, but there
1925// isn't a clear way of doing that just yet. (It should NOT query AudioIO.)
1927{
1928 return { true, mMonitoring, mActive };
1929}
1930
1932{
1933 if (!state.mSaved)
1934 return;
1935
1936 mMonitoring = state.mMonitoring;
1937 mActive = state.mActive;
1938 //wxLogDebug("Restore state for %p, is %i", this, mActive );
1939
1940 if (mActive)
1941 mTimer.Start(1000 / mMeterRefreshRate);
1942}
1943
1944//
1945// Pop-up menu
1946//
1947
1948void MeterPanel::ShowMenu(const wxPoint & pos)
1949{
1950 wxMenu menu;
1951 // Note: these should be kept in the same order as the enum
1952 if (mIsInput) {
1953 wxMenuItem *mi;
1954 if (mMonitoring)
1955 mi = menu.Append(OnMonitorID, _("Disable Silent Monitoring"));
1956 else
1957 mi = menu.Append(OnMonitorID, _("Enable Silent Monitoring"));
1958 mi->Enable(!mActive || mMonitoring);
1959 }
1960
1961 menu.Append(OnPreferencesID, _("Options..."));
1962
1963 BasicMenu::Handle{ &menu }.Popup(
1965 { pos.x, pos.y }
1966 );
1967}
1968
1970{
1972 if(mSlider)
1973 mSlider->SetName(tip);
1974}
1975
1976
1977void MeterPanel::OnMonitor(wxCommandEvent & WXUNUSED(event))
1978{
1980}
1981
1982void MeterPanel::OnPreferences(wxCommandEvent & WXUNUSED(event))
1983{
1984 wxTextCtrl *rate;
1985 wxRadioButton *gradient;
1986 wxRadioButton *rms;
1987 wxRadioButton *db;
1988 wxRadioButton *linear;
1989 wxRadioButton *automatic;
1990 wxRadioButton *horizontal;
1991 wxRadioButton *vertical;
1992 int meterRefreshRate = mMeterRefreshRate;
1993
1994 auto title = mIsInput ? XO("Recording Meter Options") : XO("Playback Meter Options");
1995
1996 // Dialog is a child of the project, rather than of the toolbar.
1997 // This determines where it pops up.
1998
1999 wxDialogWrapper dlg( FindProjectFrame( mProject ), wxID_ANY, title);
2000 dlg.SetName();
2001 ShuttleGui S(&dlg, eIsCreating);
2002 S.StartVerticalLay();
2003 {
2004 S.StartStatic(XO("Refresh Rate"), 0);
2005 {
2006 S.AddFixedText(XO(
2007"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."));
2008 S.StartHorizontalLay();
2009 {
2010 rate = S.Name(XO("Meter refresh rate per second [1-100]"))
2011 .Validator<IntegerValidator<long>>(
2012 &mMeterRefreshRate, NumValidatorStyle::DEFAULT,
2014 .AddTextBox(XXO("Meter refresh rate per second [1-100]: "),
2015 wxString::Format(wxT("%d"), meterRefreshRate),
2016 10);
2017 }
2018 S.EndHorizontalLay();
2019 }
2020 S.EndStatic();
2021
2022 S.StartHorizontalLay();
2023 {
2024 S.StartStatic(XO("Meter Style"), 0);
2025 {
2026 S.StartVerticalLay();
2027 {
2028 gradient = S.AddRadioButton(XXO("Gradient"), true, mGradient);
2029 rms = S.AddRadioButtonToGroup(XXO("RMS"), false, mGradient);
2030 }
2031 S.EndVerticalLay();
2032 }
2033 S.EndStatic();
2034
2035 S.StartStatic(XO("Meter Type"), 0);
2036 {
2037 S.StartVerticalLay();
2038 {
2039 db = S.AddRadioButton(XXO("dB"), true, mDB);
2040 linear = S.AddRadioButtonToGroup(XXO("Linear"), false, mDB);
2041 }
2042 S.EndVerticalLay();
2043 }
2044 S.EndStatic();
2045
2046 S.StartStatic(XO("Orientation"), 1);
2047 {
2048 S.StartVerticalLay();
2049 {
2050 automatic = S.AddRadioButton(
2051 XXO("Automatic"), AutomaticStereo, mDesiredStyle);
2052 horizontal = S.AddRadioButtonToGroup(
2053 XXO("Horizontal"), HorizontalStereo, mDesiredStyle);
2054 vertical = S.AddRadioButtonToGroup(
2055 XXO("Vertical"), VerticalStereo, mDesiredStyle);
2056 }
2057 S.EndVerticalLay();
2058 }
2059 S.EndStatic();
2060 }
2061 S.EndHorizontalLay();
2062 S.AddStandardButtons();
2063 }
2064 S.EndVerticalLay();
2065 dlg.Layout();
2066 dlg.Fit();
2067
2068 dlg.CenterOnParent();
2069
2070 if (dlg.ShowModal() == wxID_OK)
2071 {
2072 wxArrayStringEx style{
2073 wxT("AutomaticStereo") ,
2074 wxT("HorizontalStereo") ,
2075 wxT("VerticalStereo") ,
2076 };
2077
2078 int s = 0;
2079 s = automatic->GetValue() ? 0 : s;
2080 s = horizontal->GetValue() ? 1 : s;
2081 s = vertical->GetValue() ? 2 : s;
2082
2083 gPrefs->Write(Key(wxT("Style")), style[s]);
2084 gPrefs->Write(Key(wxT("Bars")), gradient->GetValue() ? wxT("Gradient") : wxT("RMS"));
2085 gPrefs->Write(Key(wxT("Type")), db->GetValue() ? wxT("dB") : wxT("Linear"));
2086 gPrefs->Write(Key(wxT("RefreshRate")), rate->GetValue());
2087
2088 gPrefs->Flush();
2089
2090 // Currently, there are 2 playback meters and 2 record meters and any number of
2091 // mixerboard meters, so we have to send out an preferences updated message to
2092 // ensure they all update themselves.
2094 }
2095}
2096
2097wxString MeterPanel::Key(const wxString & key) const
2098{
2100 {
2101 return wxT("/Meter/Mixerboard/") + key;
2102 }
2103
2104 if (mIsInput)
2105 {
2106 return wxT("/Meter/Input/") + key;
2107 }
2108
2109 return wxT("/Meter/Output/") + key;
2110}
2111
2112// This compensates for a but in wxWidgets 3.0.2 for mac:
2113// Couldn't set focus from keyboard when AcceptsFocus returns false;
2114// this bypasses that limitation
2116{
2117 auto temp = TemporarilyAllowFocus();
2118 SetFocus();
2119}
2120
2121
2122#if wxUSE_ACCESSIBILITY
2123
2124MeterAx::MeterAx(wxWindow *window):
2125 WindowAccessible(window)
2126{
2127}
2128
2129MeterAx::~MeterAx()
2130{
2131}
2132
2133// Performs the default action. childId is 0 (the action for this object)
2134// or > 0 (the action for a child).
2135// Return wxACC_NOT_SUPPORTED if there is no default action for this
2136// window (e.g. an edit control).
2137wxAccStatus MeterAx::DoDefaultAction(int WXUNUSED(childId))
2138{
2139 MeterPanel *m = wxDynamicCast(GetWindow(), MeterPanel);
2140
2141 if (m && m->mIsInput)
2142 m->StartMonitoring();
2143
2144 return wxACC_OK;
2145}
2146
2147// Retrieves the address of an IDispatch interface for the specified child.
2148// All objects must support this property.
2149wxAccStatus MeterAx::GetChild(int childId, wxAccessible** child)
2150{
2151 if (childId == wxACC_SELF)
2152 *child = this;
2153 else
2154 *child = NULL;
2155 return wxACC_OK;
2156}
2157
2158// Gets the number of children.
2159wxAccStatus MeterAx::GetChildCount(int* childCount)
2160{
2161 *childCount = 0;
2162 return wxACC_OK;
2163}
2164
2165// Gets the default action for this object (0) or > 0 (the action for
2166// a child). Return wxACC_OK even if there is no action. actionName
2167// is the action, or the empty string if there is no action. The
2168// retrieved string describes the action that is performed on an
2169// object, not what the object does as a result. For example, a
2170// toolbar button that prints a document has a default action of
2171// "Press" rather than "Prints the current document."
2172wxAccStatus MeterAx::GetDefaultAction(int WXUNUSED(childId), wxString* actionName)
2173{
2174 *actionName = _("Press");
2175 return wxACC_OK;
2176}
2177
2178// Returns the description for this object or a child.
2179wxAccStatus MeterAx::GetDescription(int WXUNUSED(childId), wxString *description)
2180{
2181 description->clear();
2182 return wxACC_NOT_SUPPORTED;
2183}
2184
2185// Gets the window with the keyboard focus.
2186// If childId is 0 and child is NULL, no object in
2187// this subhierarchy has the focus.
2188// If this object has the focus, child should be 'this'.
2189wxAccStatus MeterAx::GetFocus(int* childId, wxAccessible** child)
2190{
2191 *childId = 0;
2192 *child = this;
2193 return wxACC_OK;
2194}
2195
2196// Returns help text for this object or a child, similar to tooltip text.
2197wxAccStatus MeterAx::GetHelpText(int WXUNUSED(childId), wxString *helpText)
2198{
2199 helpText->clear();
2200 return wxACC_NOT_SUPPORTED;
2201}
2202
2203// Returns the keyboard shortcut for this object or child.
2204// Return e.g. ALT+K
2205wxAccStatus MeterAx::GetKeyboardShortcut(int WXUNUSED(childId), wxString *shortcut)
2206{
2207 shortcut->clear();
2208 return wxACC_OK;
2209}
2210
2211// Returns the rectangle for this object (id = 0) or a child element (id > 0).
2212// rect is in screen coordinates.
2213wxAccStatus MeterAx::GetLocation(wxRect & rect, int WXUNUSED(elementId))
2214{
2215 MeterPanel *m = wxDynamicCast(GetWindow(), MeterPanel);
2216
2217 rect = m->GetClientRect();
2218 rect.SetPosition(m->ClientToScreen(rect.GetPosition()));
2219
2220 return wxACC_OK;
2221}
2222
2223// Returns a role constant.
2224wxAccStatus MeterAx::GetRole(int WXUNUSED(childId), wxAccRole* role)
2225{
2226 *role = wxROLE_SYSTEM_SLIDER;
2227
2228 return wxACC_OK;
2229}
2230
2231// Gets a variant representing the selected children
2232// of this object.
2233// Acceptable values:
2234// - a null variant (IsNull() returns TRUE)
2235// - a list variant (GetType() == wxT("list"))
2236// - an integer representing the selected child element,
2237// or 0 if this object is selected (GetType() == wxT("long"))
2238// - a "void*" pointer to a wxAccessible child object
2239wxAccStatus MeterAx::GetSelections(wxVariant * WXUNUSED(selections))
2240{
2241 return wxACC_NOT_IMPLEMENTED;
2242}
2243
2244// Returns a state constant.
2245wxAccStatus MeterAx::GetState(int WXUNUSED(childId), long* state)
2246{
2247 MeterPanel *m = wxDynamicCast( GetWindow(), MeterPanel );
2248
2249 *state = wxACC_STATE_SYSTEM_FOCUSABLE;
2250 *state |= ( m == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0 );
2251
2252 return wxACC_OK;
2253}
2254
2255// Returns a localized string representing the value for the object
2256// or child.
2257wxAccStatus MeterAx::GetValue(int WXUNUSED(childId), wxString* strValue)
2258{
2259 MeterPanel *m = wxDynamicCast(GetWindow(), MeterPanel);
2260
2261 *strValue = m->mSlider->GetStringValue();
2262 return wxACC_OK;
2263}
2264
2265#endif
#define PERCENT_SLIDER
Definition: ASlider.h:39
EVT_MENU(OnSetPlayRegionToSelectionID, AdornedRulerPanel::OnSetPlayRegionToSelection) EVT_COMMAND(OnTogglePinnedStateID
wxT("CloseDown"))
END_EVENT_TABLE()
static const AudacityProject::AttachedObjects::RegisteredFactory key
int min(int a, int b)
IntSetting DecibelScaleCutoff
Negation of this value is the lowest dB level that should be shown in dB scales.
Definition: Decibels.cpp:12
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define _(s)
Definition: Internat.h:73
#define MAX_AUDIO
Definition: MemoryX.h:564
#define safenew
Definition: MemoryX.h:10
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:562
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:561
static float ClipZeroToOne(float z)
Definition: MeterPanel.cpp:943
static int intmax(int a, int b)
Definition: MeterPanel.cpp:938
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:953
static float floatMax(float a, float b)
Definition: MeterPanel.cpp:926
static int MeterPrefsID()
Definition: MeterPanel.cpp:456
const int kMaxMeterBars
Definition: MeterPanel.h:37
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:123
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:756
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:963
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:866
void OnTipTimeout(wxTimerEvent &evt)
void OnKeyDown(wxKeyEvent &evt)
Definition: MeterPanel.cpp:790
void Decrease(float steps)
Definition: MeterPanel.cpp:892
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:778
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:881
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:730
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:806
wxSize mLeftSize
Definition: MeterPanel.h:293
wxPen mPeakPeakPen
Definition: MeterPanel.h:297
void RepaintBarsNow()
void SetStyle(Style newStyle)
Definition: MeterPanel.cpp:822
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:903
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:722
void OnKillFocus(wxFocusEvent &evt)
Definition: MeterPanel.cpp:812
void SetMixer(wxCommandEvent &event)
Definition: MeterPanel.cpp:834
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:99
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:205
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:352
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:343
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
AUDACITY_DLL_API void UpdatePrefs(wxWindow *pParent)
__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.