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