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