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