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