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