Audacity  2.2.2
MixerBoard.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  MixerBoard.cpp
6 
7  Vaughan Johnson, January 2007
8  Dominic Mazzoni
9 
10 **********************************************************************/
11 
12 #include "Audacity.h"
13 
14 #include <cfloat>
15 #include <math.h>
16 
17 #include <wx/dcmemory.h>
18 #include <wx/icon.h>
19 #include <wx/settings.h> // for wxSystemSettings::GetColour and wxSystemSettings::GetMetric
20 
21 #include "Theme.h"
22 #include "Experimental.h"
23 #include "MixerBoard.h"
24 #include "AColor.h"
25 #include "AllThemeResources.h"
26 #include "AudioIO.h"
27 
28 #ifdef USE_MIDI
29 #include "NoteTrack.h"
30 #endif
31 
32 #include "Project.h"
33 #include "TrackPanel.h" // for EVT_TRACK_PANEL_TIMER
34 #include "UndoManager.h"
35 #include "WaveTrack.h"
36 
37 #include "widgets/Meter.h"
38 
39 
40 #include "../images/MusicalInstruments.h"
41 #ifdef __WXMSW__
42  #include "../images/AudacityLogo.xpm"
43 #else
44  #include "../images/AudacityLogo48x48.xpm"
45 #endif
46 
47 
48 // class MixerTrackSlider
49 
50 BEGIN_EVENT_TABLE(MixerTrackSlider, ASlider)
51  EVT_MOUSE_EVENTS(MixerTrackSlider::OnMouseEvent)
52 
53  EVT_SET_FOCUS(MixerTrackSlider::OnFocus)
54  EVT_KILL_FOCUS(MixerTrackSlider::OnFocus)
55  EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, MixerTrackSlider::OnCaptureKey)
56 
58 
60  wxWindowID id,
61  const wxString &name,
62  const wxPoint & pos,
63  const wxSize & size,
64  const ASlider::Options &options)
65 : ASlider(parent, id, name, pos, size, options)
66 {
67 }
68 
69 void MixerTrackSlider::OnMouseEvent(wxMouseEvent &event)
70 {
71  ASlider::OnMouseEvent(event);
72 
73  if (event.ButtonUp())
74  {
75  MixerTrackCluster* pMixerTrackCluster = (MixerTrackCluster*)(this->GetParent());
76  switch (mStyle)
77  {
78  case DB_SLIDER: pMixerTrackCluster->HandleSliderGain(true); break;
79  case PAN_SLIDER: pMixerTrackCluster->HandleSliderPan(true); break;
80  default: break; // no-op
81  }
82  }
83 }
84 
85 void MixerTrackSlider::OnFocus(wxFocusEvent &event)
86 {
87  if (event.GetEventType() == wxEVT_KILL_FOCUS) {
89  }
90  else {
92  }
93 
94  Refresh(false);
95 
96  event.Skip();
97 }
98 
99 void MixerTrackSlider::OnCaptureKey(wxCommandEvent &event)
100 {
101  wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject();
102  int keyCode = kevent->GetKeyCode();
103 
104  // Pass LEFT/RIGHT/UP/DOWN/PAGEUP/PAGEDOWN through for input/output sliders
105  if (keyCode == WXK_LEFT || keyCode == WXK_RIGHT ||
106  keyCode == WXK_UP || keyCode == WXK_DOWN ||
107  keyCode == WXK_PAGEUP || keyCode == WXK_PAGEDOWN) {
108  return;
109  }
110 
111  event.Skip();
112 
113  return;
114 }
115 
116 
117 
118 // class MixerTrackCluster
119 
120 const int kInset = 4;
121 const int kDoubleInset = (2 * kInset);
122 const int kTripleInset = (3 * kInset);
123 const int kQuadrupleInset = (4 * kInset);
124 
125 const int TRACK_NAME_HEIGHT = 18;
127 const int MUTE_SOLO_HEIGHT = 19;
128 const int PAN_HEIGHT = 24;
129 
130 const int kLeftSideStackWidth = MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH - kDoubleInset; //vvv Change when numbers shown on slider scale.
132 const int kMixerTrackClusterWidth = kLeftSideStackWidth + kRightSideStackWidth + kQuadrupleInset; // kDoubleInset margin on both sides
133 
134 enum {
138 #ifdef EXPERIMENTAL_MIDI_OUT
139  ID_SLIDER_VELOCITY,
140 #endif
143 };
144 
145 BEGIN_EVENT_TABLE(MixerTrackCluster, wxPanelWrapper)
146  EVT_MOUSE_EVENTS(MixerTrackCluster::OnMouseEvent)
147  EVT_PAINT(MixerTrackCluster::OnPaint)
148 
150  EVT_SLIDER(ID_SLIDER_PAN, MixerTrackCluster::OnSlider_Pan)
151  EVT_SLIDER(ID_SLIDER_GAIN, MixerTrackCluster::OnSlider_Gain)
152 #ifdef EXPERIMENTAL_MIDI_OUT
153  EVT_SLIDER(ID_SLIDER_VELOCITY, MixerTrackCluster::OnSlider_Velocity)
154 #endif
155  //v EVT_COMMAND_SCROLL(ID_SLIDER_GAIN, MixerTrackCluster::OnSliderScroll_Gain)
157  EVT_COMMAND(ID_TOGGLEBUTTON_SOLO, wxEVT_COMMAND_BUTTON_CLICKED, MixerTrackCluster::OnButton_Solo)
159 
161  MixerBoard* grandParent, AudacityProject* project,
162  const std::shared_ptr<PlayableTrack> &pTrack,
163  const wxPoint& pos /*= wxDefaultPosition*/,
164  const wxSize& size /*= wxDefaultSize*/)
165 : wxPanelWrapper(parent, -1, pos, size)
166 , mTrack{ pTrack }
167 {
168  mMixerBoard = grandParent;
169  mProject = project;
170  wxASSERT( pTrack );
171 
172  SetName(mTrack->GetName());
173 
174  //this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
175  this->SetBackgroundColour( theTheme.Colour( clrMedium ) );
176  // Not sure why, but sizers weren't getting offset vertically,
177  // probably because not using wxDefaultPosition,
178  // so positions are calculated explicitly below, and sizers code was removed.
179  // (Still available in Audacity_UmixIt branch off 1.2.6.)
180 
181  // track name
182  wxPoint ctrlPos(kDoubleInset, kDoubleInset);
183  wxSize ctrlSize(size.GetWidth() - kQuadrupleInset, TRACK_NAME_HEIGHT);
184  mStaticText_TrackName =
185  safenew auStaticText(this, mTrack->GetName());
186  //v Useful when different tracks are different colors, but not now.
187  // mStaticText_TrackName->SetBackgroundColour(this->GetTrackColor());
188  mStaticText_TrackName->SetForegroundColour(theTheme.Colour(clrMedium));
189  mStaticText_TrackName->SetForegroundColour(theTheme.Colour(clrTrackPanelText));
190  mStaticText_TrackName->SetSize( ctrlSize );
191  mStaticText_TrackName->SetPosition( ctrlPos );
192 
193 
194  // gain and velocity sliders at left (both in same place)
195  ctrlPos.x = kDoubleInset;
196  ctrlPos.y += TRACK_NAME_HEIGHT + kDoubleInset;
197  const int nGainSliderHeight =
198  size.GetHeight() - ctrlPos.y - kQuadrupleInset;
199  ctrlSize.Set(kLeftSideStackWidth - kQuadrupleInset, nGainSliderHeight);
200 
201  mSlider_Gain =
203  this, ID_SLIDER_GAIN,
204  /* i18n-hint: title of the Gain slider, used to adjust the volume */
205  _("Gain"),
206  ctrlPos, ctrlSize,
208  .Style( DB_SLIDER )
209  .Orientation( wxVERTICAL ));
210  mSlider_Gain->SetName(_("Gain"));
211  this->UpdateGain();
212 #ifdef EXPERIMENTAL_MIDI_OUT
213  mSlider_Velocity =
215  this, ID_SLIDER_VELOCITY,
216  /* i18n-hint: title of the MIDI Velocity slider */
217  _("Velocity"),
218  ctrlPos, ctrlSize,
220  .Style( VEL_SLIDER )
221  .Orientation( wxVERTICAL ));
222  mSlider_Velocity->SetName(_("Velocity"));
223  this->UpdateVelocity();
224 #endif
225 
226  // other controls and meter at right
227 
228  // musical instrument image
229  ctrlPos.x += kLeftSideStackWidth + kInset; // + kInset to center it in right side stack
231  wxBitmap* bitmap = mMixerBoard->GetMusicalInstrumentBitmap(mTrack.get());
232  wxASSERT(bitmap);
233  mBitmapButton_MusicalInstrument =
234  safenew wxBitmapButton(this, ID_BITMAPBUTTON_MUSICAL_INSTRUMENT, *bitmap,
235  ctrlPos, ctrlSize,
236  wxBU_AUTODRAW, wxDefaultValidator,
237  _("Musical Instrument"));
238  mBitmapButton_MusicalInstrument->SetName(_("Musical Instrument"));
239 
240 
241  // pan slider
242  ctrlPos.x -= kInset; // Remove inset for instrument, so Pan is at leftmost of left side stack.
244  ctrlSize.Set(kRightSideStackWidth, PAN_HEIGHT);
245 
246  // The width of the pan slider must be odd (don't ask).
247  if (!(ctrlSize.x & 1))
248  ctrlSize.x--;
249 
250  mSlider_Pan =
252  this, ID_SLIDER_PAN,
253  /* i18n-hint: Title of the Pan slider, used to move the sound left or right */
254  _("Pan"),
255  ctrlPos, ctrlSize,
257  mSlider_Pan->SetName(_("Pan"));
258 
259  this->UpdatePan();
260 
261  // mute/solo buttons stacked below Pan slider
262  ctrlPos.y += PAN_HEIGHT + kDoubleInset;
263  ctrlSize.Set(mMixerBoard->mMuteSoloWidth, MUTE_SOLO_HEIGHT);
264  mToggleButton_Mute =
266  ctrlPos, ctrlSize,
267  *(mMixerBoard->mImageMuteUp), *(mMixerBoard->mImageMuteOver),
268  *(mMixerBoard->mImageMuteDown), *(mMixerBoard->mImageMuteDown),
269  *(mMixerBoard->mImageMuteDisabled),
270  true); // toggle button
271  mToggleButton_Mute->SetName(_("Mute"));
272  mToggleButton_Mute->SetAlternateImages(
273  1,
274  *(mMixerBoard->mImageMuteUp), *(mMixerBoard->mImageMuteOver),
275  *(mMixerBoard->mImageMuteDown), *(mMixerBoard->mImageMuteDown),
276  *(mMixerBoard->mImageMuteDisabled));
277  this->UpdateMute();
278 
279  ctrlPos.y += MUTE_SOLO_HEIGHT;
280  mToggleButton_Solo =
282  ctrlPos, ctrlSize,
283  *(mMixerBoard->mImageSoloUp), *(mMixerBoard->mImageSoloOver),
284  *(mMixerBoard->mImageSoloDown), *(mMixerBoard->mImageSoloDown),
285  *(mMixerBoard->mImageSoloDisabled),
286  true); // toggle button
287  mToggleButton_Solo->SetName(_("Solo"));
288  this->UpdateSolo();
289  bool bSoloNone = mProject->IsSoloNone();
290  mToggleButton_Solo->Show(!bSoloNone);
291 
292 
293  // meter
294  ctrlPos.y += (bSoloNone ? 0 : MUTE_SOLO_HEIGHT) + kDoubleInset;
295  const int nMeterHeight =
296  nGainSliderHeight -
298  (PAN_HEIGHT + kDoubleInset) -
299  (MUTE_SOLO_HEIGHT + (bSoloNone ? 0 : MUTE_SOLO_HEIGHT) + kDoubleInset);
300  ctrlSize.Set(kRightSideStackWidth, nMeterHeight);
301 
302  mMeter = NULL;
303  if (GetWave()) {
304  mMeter =
305  safenew MeterPanel(GetActiveProject(), // AudacityProject* project,
306  this, -1, // wxWindow* parent, wxWindowID id,
307  false, // bool isInput
308  ctrlPos, ctrlSize, // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
309  MeterPanel::MixerTrackCluster); // Style style = HorizontalStereo,
310  mMeter->SetName(_("Signal Level Meter"));
311  }
312 
313  #if wxUSE_TOOLTIPS
314  mStaticText_TrackName->SetToolTip(mTrack->GetName());
315  mToggleButton_Mute->SetToolTip(_("Mute"));
316  mToggleButton_Solo->SetToolTip(_("Solo"));
317  if (GetWave())
318  mMeter->SetToolTip(_("Signal Level Meter"));
319  #endif // wxUSE_TOOLTIPS
320 
321  #ifdef __WXMAC__
322  wxSizeEvent event(GetSize(), GetId());
323  event.SetEventObject(this);
324  GetEventHandler()->ProcessEvent(event);
325  UpdateGain();
326  #endif
327 }
328 
330 {
331  return dynamic_cast< WaveTrack * >( mTrack.get() );
332 }
333 
335 {
336  auto left = GetWave();
337  if (left)
338  return static_cast<WaveTrack*>(left->GetLink());
339  else
340  return nullptr;
341 }
342 
343 #ifdef EXPERIMENTAL_MIDI_OUT
344 NoteTrack *MixerTrackCluster::GetNote() const
345 {
346  return dynamic_cast< NoteTrack * >( mTrack.get() );
347 }
348 #endif
349 
351 {
352  this->SetBackgroundColour( theTheme.Colour( clrMedium ) );
353  mStaticText_TrackName->SetForegroundColour(theTheme.Colour(clrTrackPanelText));
354  if (mMeter)
355  mMeter->UpdatePrefs(); // in case meter range has changed
356  HandleResize(); // in case prefs "/GUI/Solo" changed
357 }
358 
359 void MixerTrackCluster::HandleResize() // For wxSizeEvents, update gain slider and meter.
360 {
361  wxSize scrolledWindowClientSize = this->GetParent()->GetClientSize();
362  const int newClusterHeight =
363  scrolledWindowClientSize.GetHeight() - kDoubleInset - // nClusterHeight from MixerBoard::UpdateTrackClusters
364  wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y) + // wxScrolledWindow::GetClientSize doesn't account for its scrollbar size.
365  kDoubleInset;
366 
367  this->SetSize(-1, newClusterHeight);
368 
369  // Change only the heights of mSlider_Gain and mMeter.
370  // But update shown status of mToggleButton_Solo, which affects top of mMeter.
371  const int nGainSliderHeight =
372  newClusterHeight -
373  (kInset + // margin above mStaticText_TrackName
374  TRACK_NAME_HEIGHT + kDoubleInset) - // mStaticText_TrackName + margin
375  kQuadrupleInset; // margin below gain slider
376  mSlider_Gain->SetSize(-1, nGainSliderHeight);
377 #ifdef EXPERIMENTAL_MIDI_OUT
378  mSlider_Velocity->SetSize(-1, nGainSliderHeight);
379 #endif
380 
381  bool bSoloNone = mProject->IsSoloNone();
382 
383  mToggleButton_Solo->Show(!bSoloNone);
384 
385  const int nRequiredHeightAboveMeter =
386  MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH + kDoubleInset +
387  PAN_HEIGHT + kDoubleInset +
388  MUTE_SOLO_HEIGHT + (bSoloNone ? 0 : MUTE_SOLO_HEIGHT) + kDoubleInset;
389  const int nMeterY =
390  kDoubleInset + // margin at top
391  TRACK_NAME_HEIGHT + kDoubleInset +
392  nRequiredHeightAboveMeter;
393  const int nMeterHeight = nGainSliderHeight - nRequiredHeightAboveMeter;
394  if (mMeter)
395  mMeter->SetSize(-1, nMeterY, -1, nMeterHeight);
396 }
397 
398 void MixerTrackCluster::HandleSliderGain(const bool bWantPushState /*= false*/)
399 {
400  float fValue = mSlider_Gain->Get();
401  if (GetWave())
402  GetWave()->SetGain(fValue);
403  if (GetRight())
404  GetRight()->SetGain(fValue);
405 
406  // Update the TrackPanel correspondingly.
408  if (bWantPushState)
409  mProject->TP_PushState(_("Moved gain slider"), _("Gain"), UndoPush::CONSOLIDATE );
410 }
411 
412 #ifdef EXPERIMENTAL_MIDI_OUT
413 void MixerTrackCluster::HandleSliderVelocity(const bool bWantPushState /*= false*/)
414 {
415  float fValue = mSlider_Velocity->Get();
416  if (GetNote())
417  GetNote()->SetVelocity(fValue);
418 
419  // Update the TrackPanel correspondingly.
421  if (bWantPushState)
422  mProject->TP_PushState(_("Moved velocity slider"), _("Velocity"), UndoPush::CONSOLIDATE);
423 }
424 #endif
425 
426 void MixerTrackCluster::HandleSliderPan(const bool bWantPushState /*= false*/)
427 {
428  float fValue = mSlider_Pan->Get();
429  if (GetWave()) // test in case track is a NoteTrack
430  GetWave()->SetPan(fValue);
431  if (GetRight())
432  GetRight()->SetPan(fValue);
433 
434  // Update the TrackPanel correspondingly.
436 
437  if (bWantPushState)
438  mProject->TP_PushState(_("Moved pan slider"), _("Pan"), UndoPush::CONSOLIDATE );
439 }
440 
441 void MixerTrackCluster::ResetMeter(const bool bResetClipping)
442 {
443  if (mMeter)
444  mMeter->Reset(GetWave()->GetRate(), bResetClipping);
445 }
446 
447 
448 // These are used by TrackPanel for synchronizing control states, etc.
449 
450 // Update the controls that can be affected by state change.
452 {
453  this->UpdateName();
454  this->UpdatePan();
455  this->UpdateGain();
456 }
457 
459 {
460  const wxString newName = mTrack->GetName();
461  SetName(newName);
462  mStaticText_TrackName->SetLabel(newName);
463  mStaticText_TrackName->SetName(newName);
464  #if wxUSE_TOOLTIPS
465  mStaticText_TrackName->SetToolTip(newName);
466  #endif
467  mBitmapButton_MusicalInstrument->SetBitmapLabel(
469  Refresh();
470 }
471 
473 {
474  mToggleButton_Mute->SetAlternateIdx(mTrack->GetSolo() ? 1 : 0);
475  if (mTrack->GetMute())
477  else
479 }
480 
482 {
483  bool bIsSolo = mTrack->GetSolo();
484  if (bIsSolo)
486  else
488  mToggleButton_Mute->SetAlternateIdx(bIsSolo ? 1 : 0);
489 }
490 
492 {
493  if (!GetWave()) {
494  mSlider_Pan->Hide();
495  return;
496  }
497  mSlider_Pan->Set(GetWave()->GetPan());
498 }
499 
501 {
502  if (!GetWave()) {
503  mSlider_Gain->Hide();
504  return;
505  }
506  mSlider_Gain->Set(GetWave()->GetGain());
507 }
508 
509 #ifdef EXPERIMENTAL_MIDI_OUT
510 void MixerTrackCluster::UpdateVelocity()
511 {
512  if (!GetNote()) {
513  mSlider_Velocity->Hide();
514  return;
515  }
516  mSlider_Velocity->Set(GetNote()->GetVelocity());
517 }
518 #endif
519 
520 void MixerTrackCluster::UpdateMeter(const double t0, const double t1)
521 {
522  // NoteTracks do not (currently) register on meters. It would probably be
523  // a good idea to display 16 channel "active" lights rather than a meter
524  if (!GetWave())
525  return;
526 
527  if ((t0 < 0.0) || (t1 < 0.0) || (t0 >= t1) || // bad time value or nothing to show
528  ((mMixerBoard->HasSolo() || mTrack->GetMute()) && !mTrack->GetSolo())
529  )
530  {
531  //v Vaughan, 2011-02-25: Moved the update back to TrackPanel::OnTimer() as it helps with
532  // playback issues reported by Bill and noted on Bug 258, so no assert.
533  // Vaughan, 2011-02-04: Now that we're updating all meters from audacityAudioCallback,
534  // this causes an assert if you click Mute while playing, because ResetMeter() resets
535  // the timer, and wxTimerbase says that can only be done from main thread --
536  // but it seems to work fine.
537  this->ResetMeter(false);
538  return;
539  }
540 
541  // Vaughan, 2010-11-27:
542  // This commented out code is flawed. Mistaken understanding of "frame" vs "window".
543  // Caused me to override MeterPanel::UpdateDisplay().
544  // But I think it's got a good idea, of calling WaveTracks' GetMinMax and GetRMS
545  // instead of passing in all the data and asking the meter to derive peak and rms.
546  // May be worth revisiting as I think it should perform better, because it uses the min/max/rms
547  // stored in blockfiles, rather than calculating them, but for now, changing it to use the
548  // original MeterPanel::UpdateDisplay(). New code is below the previous (now commented out).
549  //
550  //const size_t kFramesPerBuffer = 4;
551  //float min; // dummy, since it's not shown in meters
552  //Floats maxLeft{kFramesPerBuffer};
553  //Floats rmsLeft{kFramesPerBuffer};
554  //Floats maxRight{kFramesPerBuffer};
555  //Floats rmsRight{kFramesPerBuffer};
556  //
557  //#ifdef EXPERIMENTAL_MIDI_OUT
558  // bool bSuccess = (GetWave() != nullptr);
559  //#else
560  // bool bSuccess = true;
561  //#endif
562 
563  //const double dFrameInterval = (t1 - t0) / (double)kFramesPerBuffer;
564  //double dFrameT0 = t0;
565  //double dFrameT1 = t0 + dFrameInterval;
566  //int i = 0;
567  //while (bSuccess && (i < kFramesPerBuffer))
568  //{
569  // bSuccess &=
570  // mTrack->GetMinMax(&min, &(maxLeft[i]), dFrameT0, dFrameT1) &&
571  // mTrack->GetRMS(&(rmsLeft[i]), dFrameT0, dFrameT1);
572  // if (bSuccess && mRightTrack)
573  // bSuccess &=
574  // mRightTrack->GetMinMax(&min, &(maxRight[i]), dFrameT0, dFrameT1) &&
575  // mRightTrack->GetRMS(&(rmsRight[i]), dFrameT0, dFrameT1);
576  // else
577  // {
578  // // Mono: Start with raw values same as left.
579  // // To be modified by bWantPostFadeValues and channel pan/gain.
580  // maxRight[i] = maxLeft[i];
581  // rmsRight[i] = rmsLeft[i];
582  // }
583  // dFrameT0 += dFrameInterval;
584  // dFrameT1 += dFrameInterval;
585  // i++;
586  //}
587  //
588  //const bool bWantPostFadeValues = true; //v Turn this into a checkbox on MixerBoard? For now, always true.
589  //if (bSuccess && bWantPostFadeValues)
590  //if (bSuccess)
591  //{
592  // for (i = 0; i < kFramesPerBuffer; i++)
593  // {
594  // float gain = mTrack->GetChannelGain(0);
595  // maxLeft[i] *= gain;
596  // rmsLeft[i] *= gain;
597  // if (mRightTrack)
598  // gain = mRightTrack->GetChannelGain(1);
599  // maxRight[i] *= gain;
600  // rmsRight[i] *= gain;
601  // }
602  // if ( mMeter ) mMeter->UpdateDisplay(
603  // 2, // If mono, show left track values in both meters, as in MeterToolBar, rather than nChannels.
604  // kFramesPerBuffer,
605  // maxLeft, rmsLeft,
606  // maxRight, rmsRight,
607  // mTrack->TimeToLongSamples(t1 - t0));
608  //}
609  //
610 
611  const auto pTrack = GetWave();
612  auto startSample = (sampleCount)((pTrack->GetRate() * t0) + 0.5);
613  auto scnFrames = (sampleCount)((pTrack->GetRate() * (t1 - t0)) + 0.5);
614 
615  // Expect that the difference of t1 and t0 is the part of a track played
616  // in about 1/20 second (ticks of TrackPanel timer), so this won't overflow
617  auto nFrames = scnFrames.as_size_t();
618 
619  Floats tempFloatsArray{ nFrames };
620  decltype(tempFloatsArray) meterFloatsArray;
621  // Don't throw on read error in this drawing update routine
622  bool bSuccess = pTrack->Get((samplePtr)tempFloatsArray.get(),
623  floatSample, startSample, nFrames, fillZero, false);
624  if (bSuccess)
625  {
626  // We always pass a stereo sample array to the meter, as it shows 2 channels.
627  // Mono shows same in both meters.
628  // Since we're not mixing, need to duplicate same signal for "right" channel in mono case.
629  meterFloatsArray = Floats{ 2 * nFrames };
630 
631  // Interleave for stereo. Left/mono first.
632  for (unsigned int index = 0; index < nFrames; index++)
633  meterFloatsArray[2 * index] = tempFloatsArray[index];
634 
635  if (GetRight())
636  // Again, don't throw
637  bSuccess = GetRight()->Get((samplePtr)tempFloatsArray.get(),
638  floatSample, startSample, nFrames, fillZero, false);
639 
640  if (bSuccess)
641  // Interleave right channel, or duplicate same signal for "right" channel in mono case.
642  for (unsigned int index = 0; index < nFrames; index++)
643  meterFloatsArray[(2 * index) + 1] = tempFloatsArray[index];
644  }
645 
646  //const bool bWantPostFadeValues = true; //v Turn this into a checkbox on MixerBoard? For now, always true.
647  //if (bSuccess && bWantPostFadeValues)
648  if (bSuccess)
649  {
650  //vvv Need to apply envelope, too? See Mixer::MixSameRate.
651  float gain = pTrack->GetChannelGain(0);
652  if (gain < 1.0)
653  for (unsigned int index = 0; index < nFrames; index++)
654  meterFloatsArray[2 * index] *= gain;
655  if (GetRight())
656  gain = GetRight()->GetChannelGain(1);
657  else
658  gain = pTrack->GetChannelGain(1);
659  if (gain < 1.0)
660  for (unsigned int index = 0; index < nFrames; index++)
661  meterFloatsArray[(2 * index) + 1] *= gain;
662  // Clip to [-1.0, 1.0] range.
663  for (unsigned int index = 0; index < 2 * nFrames; index++)
664  if (meterFloatsArray[index] < -1.0)
665  meterFloatsArray[index] = -1.0;
666  else if (meterFloatsArray[index] > 1.0)
667  meterFloatsArray[index] = 1.0;
668 
669  mMeter->UpdateDisplay(2, nFrames, meterFloatsArray.get());
670  }
671  else
672  this->ResetMeter(false);
673 }
674 
675 // private
676 
678 {
679  return wxColour(102, 255, 102); // same as Meter playback color
680 }
681 
682 
683 // event handlers
684 
685 void MixerTrackCluster::HandleSelect(bool bShiftDown, bool bControlDown)
686 {
687  mProject->HandleListSelection( mTrack.get(), bShiftDown, bControlDown, true
688  );
689 }
690 
691 void MixerTrackCluster::OnMouseEvent(wxMouseEvent& event)
692 {
693  if (event.ButtonUp())
694  this->HandleSelect(event.ShiftDown(), event.ControlDown());
695  else
696  event.Skip();
697 }
698 
699 void MixerTrackCluster::OnPaint(wxPaintEvent & WXUNUSED(event))
700 {
701  auto selected = mTrack->GetSelected();
702 
703  wxColour col = theTheme.Colour(selected ? clrTrackInfoSelected : clrTrackInfo) ;
704  SetBackgroundColour( col );
705  mMeter->SetBackgroundColour( col );
706  mStaticText_TrackName->SetBackgroundColour( col );
707  mSlider_Gain->SetBackgroundColour( col );
708  mSlider_Pan->SetBackgroundColour( col );
709 
710  wxPaintDC dc(this);
711 
712  AColor::MediumTrackInfo(&dc, selected);
713  dc.DrawRectangle(this->GetClientRect());
714 
715  wxSize clusterSize = this->GetSize();
716  wxRect bev(0, 0, clusterSize.GetWidth() - 1, clusterSize.GetHeight() - 1);
717 
718  //bev.Inflate(-1, -1);
719  AColor::Bevel(dc, true, bev);// same bevel whether selected or not.
720 }
721 
722 
723 void MixerTrackCluster::OnButton_MusicalInstrument(wxCommandEvent& WXUNUSED(event))
724 {
725  const auto &state = ::wxGetMouseState();
726  this->HandleSelect(state.ShiftDown(), state.ControlDown());
727 }
728 
729 void MixerTrackCluster::OnSlider_Gain(wxCommandEvent& WXUNUSED(event))
730 {
731  this->HandleSliderGain();
732 }
733 
734 #ifdef EXPERIMENTAL_MIDI_OUT
735 void MixerTrackCluster::OnSlider_Velocity(wxCommandEvent& WXUNUSED(event))
736 {
737  this->HandleSliderVelocity();
738 }
739 #endif
740 
741 //v void MixerTrackCluster::OnSliderScroll_Gain(wxScrollEvent& WXUNUSED(event))
742 //{
743  //int sliderValue = (int)(mSlider_Gain->Get()); //v mSlider_Gain->GetValue();
744  //#ifdef __WXMSW__
745  // // Negate because wxSlider on Windows has min at top, max at bottom.
746  // // mSlider_Gain->GetValue() is in [-6,36]. wxSlider has min at top, so this is [-36dB,6dB].
747  // sliderValue = -sliderValue;
748  //#endif
749  //wxString str = _("Gain: ");
750  //if (sliderValue > 0)
751  // str += "+";
752  //str += wxString::Format("%d dB", sliderValue);
753  //mSlider_Gain->SetToolTip(str);
754 //}
755 
756 void MixerTrackCluster::OnSlider_Pan(wxCommandEvent& WXUNUSED(event))
757 {
758  this->HandleSliderPan();
759 }
760 
761 void MixerTrackCluster::OnButton_Mute(wxCommandEvent& WXUNUSED(event))
762 {
764  mToggleButton_Mute->SetAlternateIdx(mTrack->GetSolo() ? 1 : 0);
765 
766  // Update the TrackPanel correspondingly.
767  if (mProject->IsSoloSimple())
768  {
769  // Have to refresh all tracks.
772  }
773  else
774  // Update only the changed track.
776 }
777 
778 void MixerTrackCluster::OnButton_Solo(wxCommandEvent& WXUNUSED(event))
779 {
781  bool bIsSolo = mTrack->GetSolo();
782  mToggleButton_Mute->SetAlternateIdx(bIsSolo ? 1 : 0);
783 
784  // Update the TrackPanel correspondingly.
785  if (mProject->IsSoloSimple())
786  {
787  // Have to refresh all tracks.
790  }
791  // Bug 509: Must repaint all, as many tracks can change with one Solo change.
793 }
794 
795 
796 // class MusicalInstrument
797 
798 MusicalInstrument::MusicalInstrument(std::unique_ptr<wxBitmap> &&pBitmap, const wxString & strXPMfilename)
799 {
800  mBitmap = std::move(pBitmap);
801 
802  int nUnderscoreIndex;
803  wxString strFilename = strXPMfilename;
804  strFilename.MakeLower(); // Make sure, so we don't have to do case insensitive comparison.
805  wxString strKeyword;
806  while ((nUnderscoreIndex = strFilename.Find(wxT('_'))) != -1)
807  {
808  strKeyword = strFilename.Left(nUnderscoreIndex);
809  mKeywords.Add(strKeyword);
810  strFilename = strFilename.Mid(nUnderscoreIndex + 1);
811  }
812  if (!strFilename.IsEmpty()) // Skip trailing underscores.
813  mKeywords.Add(strFilename); // Add the last one.
814 }
815 
817 {
818  mKeywords.Clear();
819 }
820 
821 
822 // class MixerBoardScrolledWindow
823 
824 // wxScrolledWindow ignores mouse clicks in client area,
825 // but they don't get passed to Mixerboard.
826 // We need to catch them to deselect all track clusters.
827 
828 BEGIN_EVENT_TABLE(MixerBoardScrolledWindow, wxScrolledWindow)
829  EVT_MOUSE_EVENTS(MixerBoardScrolledWindow::OnMouseEvent)
831 
833  MixerBoard* parent, wxWindowID id /*= -1*/,
834  const wxPoint& pos /*= wxDefaultPosition*/,
835  const wxSize& size /*= wxDefaultSize*/,
836  long style /*= wxHSCROLL | wxVSCROLL*/)
837 : wxScrolledWindow(parent, id, pos, size, style)
838 {
839  mMixerBoard = parent;
840  mProject = project;
841 }
842 
844 {
845 }
846 
847 void MixerBoardScrolledWindow::OnMouseEvent(wxMouseEvent& event)
848 {
849  if (event.ButtonUp())
850  {
851  //v Even when I implement MixerBoard::OnMouseEvent and call event.Skip()
852  // here, MixerBoard::OnMouseEvent never gets called.
853  // So, added mProject to MixerBoardScrolledWindow and just directly do what's needed here.
854  mProject->SelectNone();
855  }
856  else
857  event.Skip();
858 }
859 
860 
861 // class MixerBoard
862 
863 #define MIXER_BOARD_MIN_HEIGHT 460
864 
865 // Min width is one cluster wide, plus margins.
866 #define MIXER_BOARD_MIN_WIDTH kTripleInset + kMixerTrackClusterWidth*2 + kTripleInset
867 
868 
869 BEGIN_EVENT_TABLE(MixerBoard, wxWindow)
870  EVT_SIZE(MixerBoard::OnSize)
872 
874  wxFrame* parent,
875  const wxPoint& pos /*= wxDefaultPosition*/,
876  const wxSize& size /*= wxDefaultSize*/)
877 : wxWindow(parent, -1, pos, size)
878 {
879  // public data members
880 
881  // mute & solo button images
882  // Create once and store on MixerBoard for use in all MixerTrackClusters.
883  mImageMuteUp = NULL;
884  mImageMuteOver = NULL;
885  mImageMuteDown = NULL;
886  mImageMuteDownWhileSolo = NULL;
887  mImageMuteDisabled = NULL;
888  mImageSoloUp = NULL;
889  mImageSoloOver = NULL;
890  mImageSoloDown = NULL;
891  mImageSoloDisabled = NULL;
892 
893  mMuteSoloWidth = kRightSideStackWidth - kInset; // correct for max width, but really set in MixerBoard::CreateMuteSoloImages
894 
895  // private data members
896  this->LoadMusicalInstruments(); // Set up mMusicalInstruments.
897  mProject = pProject;
898 
899  wxASSERT(pProject); // to justify safenew
900  mScrolledWindow =
902  pProject, // AudacityProject* project,
903  this, -1, // wxWindow* parent, wxWindowID id = -1,
904  this->GetClientAreaOrigin(), // const wxPoint& pos = wxDefaultPosition,
905  size, // const wxSize& size = wxDefaultSize,
906  wxHSCROLL); // long style = wxHSCROLL | wxVSCROLL, const wxString& name = "scrolledWindow")
907 
908  // Set background color to same as TrackPanel background.
909 // #ifdef EXPERIMENTAL_THEMING
910 // mScrolledWindow->SetBackgroundColour(this->GetParent()->GetBackgroundColour());
911 // #else
912 // mScrolledWindow->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
913 // #endif
914  mScrolledWindow->SetBackgroundColour( theTheme.Colour( clrMedium ) );
915 
916  mScrolledWindow->SetScrollRate(10, 0); // no vertical scroll
917  mScrolledWindow->SetVirtualSize(size);
918 
919  /* This doesn't work to make the mScrolledWindow automatically resize, so do it explicitly in OnSize.
920  auto pBoxSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
921  pBoxSizer->Add(mScrolledWindow, 0, wxExpand, 0);
922  this->SetAutoLayout(true);
923  this->SetSizer(pBoxSizer);
924  pBoxSizer->Fit(this);
925  pBoxSizer->SetSizeHints(this);
926  */
927 
928  mPrevT1 = 0.0;
929  mTracks = mProject->GetTracks();
930 
931  // Events from the project don't propagate directly to this other frame, so...
932  mProject->Bind(EVT_TRACK_PANEL_TIMER,
934  this);
935 }
936 
937 
938 
939 
941 {
943 
944 // Old approach modified things in situ.
945 // However with a theme change there is so much to modify, it is easier
946 // to recreate.
947 #if 0
948  mScrolledWindow->SetBackgroundColour( theTheme.Colour( clrMedium ) );
949  if( mImageMuteUp ){
950  mImageMuteUp.reset();
951  mImageMuteOver.reset();
952  mImageMuteDown.reset();
953  mImageMuteDownWhileSolo.reset();
954  mImageMuteDisabled.reset();
955  mImageSoloUp.reset();
956  mImageSoloOver.reset();
957  mImageSoloDown.reset();
958  mImageSoloDisabled.reset();
959  }
960  for (unsigned int nClusterIndex = 0; nClusterIndex < mMixerTrackClusters.GetCount(); nClusterIndex++)
961  mMixerTrackClusters[nClusterIndex]->UpdatePrefs();
962  Refresh();
963 #endif
964 }
965 
966 // Reassign mixer input strips (MixerTrackClusters) to Track Clusters
967 // both have the same order.
968 // If EXPERIMENTAL_MIDI_OUT, then Note Tracks appear in the
969 // mixer, and we must be able to convert and reuse a MixerTrackCluster
970 // from audio to midi or midi to audio. This task is handled by
971 // UpdateForStateChange().
972 //
974 {
975  if (!mImageMuteUp)
976  this->CreateMuteSoloImages();
977 
978  const int nClusterHeight = mScrolledWindow->GetClientSize().GetHeight() - kDoubleInset;
979  size_t nClusterCount = mMixerTrackClusters.size();
980  unsigned int nClusterIndex = 0;
981  TrackListIterator iterTracks(mTracks);
982  MixerTrackCluster* pMixerTrackCluster = NULL;
983  Track* pTrack;
984 
985  pTrack = iterTracks.First();
986  while (pTrack) {
987  if (auto pPlayableTrack = dynamic_cast<PlayableTrack*>(pTrack))
988  {
989  auto spTrack = Track::Pointer<PlayableTrack>( pPlayableTrack );
990  if (nClusterIndex < nClusterCount)
991  {
992  // Already showing it.
993  // Track clusters are maintained in the same order as the WaveTracks.
994  // Track pointers can change for the "same" track for different states
995  // on the undo stack, so update the pointers and display name.
996  mMixerTrackClusters[nClusterIndex]->mTrack = spTrack;
997  // Assume linked track is wave or null
998  mMixerTrackClusters[nClusterIndex]->UpdateForStateChange();
999  }
1000  else
1001  {
1002  // Not already showing it. Add a NEW MixerTrackCluster.
1003  wxPoint clusterPos(
1004  kInset + nClusterIndex * kMixerTrackClusterWidth,
1005  kInset);
1006  wxSize clusterSize(kMixerTrackClusterWidth, nClusterHeight);
1007  pMixerTrackCluster =
1009  spTrack,
1010  clusterPos, clusterSize);
1011  if (pMixerTrackCluster)
1012  mMixerTrackClusters.push_back(pMixerTrackCluster);
1013  }
1014  nClusterIndex++;
1015  }
1016  pTrack = iterTracks.Next(true);
1017  }
1018 
1019  if (pMixerTrackCluster)
1020  {
1021  // Added at least one MixerTrackCluster.
1022  this->UpdateWidth();
1023  this->ResizeTrackClusters();
1024  }
1025  else while (nClusterIndex < nClusterCount)
1026  {
1027  // We've got too many clusters.
1028  // This can happen only on things like Undo New Audio Track or Undo Import
1029  // that don't call RemoveTrackCluster explicitly.
1030  // We've already updated the track pointers for the clusters to the left, so just remove all the rest.
1031  // Successively DELETE from right to left.
1032  RemoveTrackCluster(--nClusterCount);
1033  }
1034 }
1035 
1037 {
1038  return
1039  kInset + // extra margin at left for first one
1040  (mMixerTrackClusters.size() * // number of tracks times
1041  (kInset + kMixerTrackClusterWidth)) + // left margin and width for each
1042  kDoubleInset; // plus final right margin
1043 }
1044 
1046  bool bUp) // Up in TrackPanel is left in MixerBoard.
1047 {
1048  MixerTrackCluster* pMixerTrackCluster;
1049  int nIndex = FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
1050  if (pMixerTrackCluster == NULL)
1051  return; // Couldn't find it.
1052 
1053  wxPoint pos;
1054  if (bUp)
1055  { // Move it up (left).
1056  if (nIndex <= 0)
1057  return; // It's already first (0), or not found (-1).
1058 
1059  pos = pMixerTrackCluster->GetPosition();
1060  mMixerTrackClusters[nIndex] = mMixerTrackClusters[nIndex - 1];
1061  mMixerTrackClusters[nIndex]->Move(pos);
1062 
1063  mMixerTrackClusters[nIndex - 1] = pMixerTrackCluster;
1064  pMixerTrackCluster->Move(pos.x - (kInset + kMixerTrackClusterWidth), pos.y);
1065  }
1066  else
1067  { // Move it down (right).
1068  if (((unsigned int)nIndex + 1) >= mMixerTrackClusters.size())
1069  return; // It's already last.
1070 
1071  pos = pMixerTrackCluster->GetPosition();
1072  mMixerTrackClusters[nIndex] = mMixerTrackClusters[nIndex + 1];
1073  mMixerTrackClusters[nIndex]->Move(pos);
1074 
1075  mMixerTrackClusters[nIndex + 1] = pMixerTrackCluster;
1076  pMixerTrackCluster->Move(pos.x + (kInset + kMixerTrackClusterWidth), pos.y);
1077  }
1078 }
1079 
1081 {
1082  // Find and destroy.
1083  MixerTrackCluster* pMixerTrackCluster;
1084  int nIndex = this->FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
1085 
1086  if (pMixerTrackCluster == NULL)
1087  return; // Couldn't find it.
1088 
1089  RemoveTrackCluster(nIndex);
1090 }
1091 
1093 {
1094  auto pMixerTrackCluster = mMixerTrackClusters[nIndex];
1095  mMixerTrackClusters.erase(mMixerTrackClusters.begin() + nIndex);
1096  pMixerTrackCluster->Destroy(); // DELETE is unsafe on wxWindow.
1097 
1098  // Close the gap, if any.
1099  wxPoint pos;
1100  int targetX;
1101  for (unsigned int i = nIndex; i < mMixerTrackClusters.size(); i++)
1102  {
1103  pos = mMixerTrackClusters[i]->GetPosition();
1104  targetX =
1105  kInset + // extra inset to left for first one, so it's double
1106  (i * (kInset + kMixerTrackClusterWidth)) + // left margin and width for each
1107  kInset; // plus left margin for this cluster
1108  if (pos.x != targetX)
1109  mMixerTrackClusters[i]->Move(targetX, pos.y);
1110  }
1111 
1112  this->UpdateWidth();
1113 }
1114 
1115 
1117 {
1118  if (mMusicalInstruments.empty())
1119  return NULL;
1120 
1121  // random choice: return mMusicalInstruments[(int)pTrack % mMusicalInstruments.GetCount()].mBitmap;
1122 
1123  const wxString strTrackName(pTrack->GetName().MakeLower());
1124  size_t nBestItemIndex = 0;
1125  unsigned int nBestScore = 0;
1126  unsigned int nInstrIndex = 0;
1127  unsigned int nKeywordIndex;
1128  unsigned int nNumKeywords;
1129  unsigned int nPointsPerMatch;
1130  unsigned int nScore;
1131  for (nInstrIndex = 0; nInstrIndex < mMusicalInstruments.size(); nInstrIndex++)
1132  {
1133  nScore = 0;
1134 
1135  nNumKeywords = mMusicalInstruments[nInstrIndex]->mKeywords.GetCount();
1136  if (nNumKeywords > 0)
1137  {
1138  nPointsPerMatch = 10 / nNumKeywords;
1139  for (nKeywordIndex = 0; nKeywordIndex < nNumKeywords; nKeywordIndex++)
1140  if (strTrackName.Contains(mMusicalInstruments[nInstrIndex]->mKeywords[nKeywordIndex]))
1141  {
1142  nScore +=
1143  nPointsPerMatch +
1144  // Longer keywords get more points.
1145  (2 * mMusicalInstruments[nInstrIndex]->mKeywords[nKeywordIndex].Length());
1146  }
1147  }
1148 
1149  // Choose later one if just matching nBestScore, for better variety,
1150  // and so default works as last element.
1151  if (nScore >= nBestScore)
1152  {
1153  nBestScore = nScore;
1154  nBestItemIndex = nInstrIndex;
1155  }
1156  }
1157  return mMusicalInstruments[nBestItemIndex]->mBitmap.get();
1158 }
1159 
1161 {
1162  TrackListIterator iterTracks(mTracks);
1163  Track* pTrack;
1164  for (pTrack = iterTracks.First(); pTrack; pTrack = iterTracks.Next()) {
1165  auto pPlayable = dynamic_cast<PlayableTrack *>( pTrack );
1166  if (pPlayable && pPlayable->GetSolo())
1167  return true;
1168  }
1169  return false;
1170 }
1171 
1172 void MixerBoard::RefreshTrackCluster(const PlayableTrack* pTrack, bool bEraseBackground /*= true*/)
1173 {
1174  MixerTrackCluster* pMixerTrackCluster;
1175  this->FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
1176  if (pMixerTrackCluster)
1177  pMixerTrackCluster->Refresh(bEraseBackground);
1178 }
1179 
1180 void MixerBoard::RefreshTrackClusters(bool bEraseBackground /*= true*/)
1181 {
1182  for (unsigned int i = 0; i < mMixerTrackClusters.size(); i++)
1183  mMixerTrackClusters[i]->Refresh(bEraseBackground);
1184 }
1185 
1187 {
1188  for (unsigned int nClusterIndex = 0; nClusterIndex < mMixerTrackClusters.size(); nClusterIndex++)
1189  mMixerTrackClusters[nClusterIndex]->HandleResize();
1190 }
1191 
1192 void MixerBoard::ResetMeters(const bool bResetClipping)
1193 {
1195 
1196  if (!this->IsShown())
1197  return;
1198 
1199  for (unsigned int i = 0; i < mMixerTrackClusters.size(); i++)
1200  mMixerTrackClusters[i]->ResetMeter(bResetClipping);
1201 }
1202 
1204 {
1205  MixerTrackCluster* pMixerTrackCluster;
1206  this->FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
1207  if (pMixerTrackCluster)
1208  pMixerTrackCluster->UpdateName();
1209 }
1210 
1211 void MixerBoard::UpdateMute(const PlayableTrack* pTrack /*= NULL*/) // NULL means update for all tracks.
1212 {
1213  if (pTrack == NULL)
1214  {
1215  for (unsigned int i = 0; i < mMixerTrackClusters.size(); i++)
1217  }
1218  else
1219  {
1220  MixerTrackCluster* pMixerTrackCluster;
1221  FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
1222  if (pMixerTrackCluster)
1223  pMixerTrackCluster->UpdateMute();
1224  }
1225 }
1226 
1227 void MixerBoard::UpdateSolo(const PlayableTrack* pTrack /*= NULL*/) // NULL means update for all tracks.
1228 {
1229  if (pTrack == NULL)
1230  {
1231  for (unsigned int i = 0; i < mMixerTrackClusters.size(); i++)
1233  }
1234  else
1235  {
1236  MixerTrackCluster* pMixerTrackCluster;
1237  FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
1238  if (pMixerTrackCluster)
1239  pMixerTrackCluster->UpdateSolo();
1240  }
1241 }
1242 
1244 {
1245  if (pTrack == NULL)
1246  {
1247  for (unsigned int i = 0; i < mMixerTrackClusters.size(); i++)
1249  }
1250  else
1251  {
1252  MixerTrackCluster* pMixerTrackCluster;
1253  FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
1254  if (pMixerTrackCluster)
1255  pMixerTrackCluster->UpdatePan();
1256  }
1257 }
1258 
1260 {
1261  MixerTrackCluster* pMixerTrackCluster;
1262  FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
1263  if (pMixerTrackCluster)
1264  pMixerTrackCluster->UpdateGain();
1265 }
1266 
1267 #ifdef EXPERIMENTAL_MIDI_OUT
1268 void MixerBoard::UpdateVelocity(const PlayableTrack* pTrack)
1269 {
1270  MixerTrackCluster* pMixerTrackCluster;
1271  FindMixerTrackCluster(pTrack, &pMixerTrackCluster);
1272  if (pMixerTrackCluster)
1273  pMixerTrackCluster->UpdateVelocity();
1274 }
1275 #endif
1276 
1277 void MixerBoard::UpdateMeters(const double t1, const bool bLoopedPlay)
1278 {
1279  if (!this->IsShown() || (t1 == BAD_STREAM_TIME))
1280  return;
1281 
1282  if (mPrevT1 == BAD_STREAM_TIME)
1283  {
1284  mPrevT1 = t1;
1285  return;
1286  }
1287 
1288  // In loopedPlay mode, at the end of the loop, mPrevT1 is set to
1289  // selection end, so the next t1 will be less, but we do want to
1290  // keep updating the meters.
1291  if (t1 <= mPrevT1)
1292  {
1293  if (bLoopedPlay)
1294  mPrevT1 = t1;
1295  return;
1296  }
1297 
1298  for (unsigned int i = 0; i < mMixerTrackClusters.size(); i++)
1299  mMixerTrackClusters[i]->UpdateMeter(mPrevT1, t1);
1300 
1301  mPrevT1 = t1;
1302 }
1303 
1304 
1306 {
1307  int newWidth = this->GetTrackClustersWidth();
1308 
1309  // Min width is one cluster wide, plus margins.
1310  if (newWidth < MIXER_BOARD_MIN_WIDTH)
1311  newWidth = MIXER_BOARD_MIN_WIDTH;
1312 
1313  mScrolledWindow->SetVirtualSize(newWidth, -1);
1314  this->GetParent()->SetSize(newWidth + kDoubleInset, -1);
1315 }
1316 
1317 //
1318 // private methods
1319 //
1320 
1321 
1322 void MixerBoard::MakeButtonBitmap( wxMemoryDC & dc, wxBitmap & WXUNUSED(bitmap), wxRect & bev, const wxString & str, bool up )
1323 {
1324 
1325  int textWidth, textHeight;
1326 
1327  int fontSize = 10;
1328  #ifdef __WXMSW__
1329  fontSize = 8;
1330  #endif
1331  wxFont font(fontSize, wxSWISS, wxNORMAL, wxNORMAL);
1332  GetTextExtent(str, &textWidth, &textHeight, NULL, NULL, &font);
1333 
1334  AColor::UseThemeColour( &dc, clrMedium );
1335  dc.DrawRectangle(bev);
1336 
1337  AColor::Bevel2( dc, up, bev, false );
1338 
1339  wxCoord x = bev.x + (bev.width - textWidth) / 2;
1340  wxCoord y = bev.y + (bev.height - textHeight) / 2;
1341  dc.SetFont(font);
1342  dc.SetTextForeground(theTheme.Colour(clrTrackPanelText));
1343  dc.SetBackgroundMode(wxTRANSPARENT);
1344  dc.DrawText(str, x, y);
1345 // dc.DrawText(str, 0, 0);
1346 }
1347 
1349 {
1350  // Much of this is similar to TrackInfo::MuteOrSoloDrawFunction.
1351  wxMemoryDC dc;
1352  wxString str = _("Mute");
1353 
1354  //mMuteSoloWidth = textWidth + kQuadrupleInset;
1355  //if (mMuteSoloWidth < kRightSideStackWidth - kInset)
1357 
1358  wxBitmap bitmap(mMuteSoloWidth, MUTE_SOLO_HEIGHT,24);
1359  dc.SelectObject(bitmap);
1360  wxRect bev(0, 0, mMuteSoloWidth, MUTE_SOLO_HEIGHT);
1361 
1362  const bool up=true;
1363  const bool down=false;
1364 
1365  MakeButtonBitmap( dc, bitmap, bev, str, up );
1366  mImageMuteUp = std::make_unique<wxImage>(bitmap.ConvertToImage());
1367  mImageMuteOver = std::make_unique<wxImage>(bitmap.ConvertToImage()); // Same as up, for now.
1368 
1369  MakeButtonBitmap( dc, bitmap, bev, str, down );
1370  //AColor::Bevel(dc, false, bev);
1371  mImageMuteDown = std::make_unique<wxImage>(bitmap.ConvertToImage());
1372 
1373  MakeButtonBitmap( dc, bitmap, bev, str, down );
1374  mImageMuteDownWhileSolo = std::make_unique<wxImage>(bitmap.ConvertToImage());
1375 
1376  mImageMuteDisabled = std::make_unique<wxImage>(mMuteSoloWidth, MUTE_SOLO_HEIGHT); // Leave empty because unused.
1377 
1378  str = _("Solo");
1379  MakeButtonBitmap( dc, bitmap, bev, str, up );
1380  mImageSoloUp = std::make_unique<wxImage>(bitmap.ConvertToImage());
1381  mImageSoloOver = std::make_unique<wxImage>(bitmap.ConvertToImage()); // Same as up, for now.
1382 
1383  MakeButtonBitmap( dc, bitmap, bev, str, down );
1384  mImageSoloDown = std::make_unique<wxImage>(bitmap.ConvertToImage());
1385 
1386  mImageSoloDisabled = std::make_unique<wxImage>(mMuteSoloWidth, MUTE_SOLO_HEIGHT); // Leave empty because unused.
1387 }
1388 
1390  MixerTrackCluster** hMixerTrackCluster) const
1391 {
1392  *hMixerTrackCluster = NULL;
1393  for (unsigned int i = 0; i < mMixerTrackClusters.size(); i++)
1394  {
1395  if (mMixerTrackClusters[i]->mTrack.get() == pTrack)
1396  {
1397  *hMixerTrackCluster = mMixerTrackClusters[i];
1398  return i;
1399  }
1400  }
1401  return -1;
1402 }
1403 
1405 {
1406  const struct Data { const char * const *bitmap; wxString name; } table[] = {
1407  {acoustic_guitar_gtr_xpm, wxT("acoustic_guitar_gtr")},
1408  {acoustic_piano_pno_xpm, wxT("acoustic_piano_pno")},
1409  {back_vocal_bg_vox_xpm, wxT("back_vocal_bg_vox")},
1410  {clap_xpm, wxT("clap")},
1411  {drums_dr_xpm, wxT("drums_dr")},
1412  {electric_bass_guitar_bs_gtr_xpm, wxT("electric_bass_guitar_bs_gtr")},
1413  {electric_guitar_gtr_xpm, wxT("electric_guitar_gtr")},
1414  {electric_piano_pno_key_xpm, wxT("electric_piano_pno_key")},
1415  {kick_xpm, wxT("kick")},
1416  {loop_xpm, wxT("loop")},
1417  {organ_org_xpm, wxT("organ_org")},
1418  {perc_xpm, wxT("perc")},
1419  {sax_xpm, wxT("sax")},
1420  {snare_xpm, wxT("snare")},
1421  {string_violin_cello_xpm, wxT("string_violin_cello")},
1422  {synth_xpm, wxT("synth")},
1423  {tambo_xpm, wxT("tambo")},
1424  {trumpet_horn_xpm, wxT("trumpet_horn")},
1425  {turntable_xpm, wxT("turntable")},
1426  {vibraphone_vibes_xpm, wxT("vibraphone_vibes")},
1427  {vocal_vox_xpm, wxT("vocal_vox")},
1428 
1429  // This one must be last, so it wins when best score is 0.
1430  {_default_instrument_xpm, wxEmptyString},
1431  };
1432 
1434  wxMemoryDC dc;
1435 
1436  for (const auto &data : table) {
1437  auto bmp = std::make_unique<wxBitmap>(data.bitmap);
1438  dc.SelectObject(*bmp);
1439  AColor::Bevel(dc, false, bev);
1440  mMusicalInstruments.push_back(std::make_unique<MusicalInstrument>(
1441  std::move(bmp), data.name
1442  ));
1443  };
1444 }
1445 
1446 // event handlers
1447 
1448 void MixerBoard::OnSize(wxSizeEvent &evt)
1449 {
1450  // this->FitInside() doesn't work, and it doesn't happen automatically. Is wxScrolledWindow wrong?
1451  mScrolledWindow->SetSize(evt.GetSize());
1452 
1453  this->ResizeTrackClusters();
1454  this->RefreshTrackClusters(true);
1455 }
1456 
1457 void MixerBoard::OnTimer(wxCommandEvent &event)
1458 {
1459  // PRL 12 Jul 2015: Moved the below (with comments) out of TrackPanel::OnTimer.
1460 
1461  // Vaughan, 2011-01-28: No longer doing this on timer.
1462  // Now it's in AudioIO::SetMeters() and AudioIO::StopStream(), as with Meter Toolbar meters.
1463  //if (pMixerBoard)
1464  // pMixerBoard->ResetMeters(false);
1465 
1466  //v Vaughan, 2011-02-25: Moved this update back here from audacityAudioCallback.
1467  // See note there.
1468  // Vaughan, 2010-01-30:
1469  // Since all we're doing here is updating the meters, I moved it to
1470  // audacityAudioCallback where it calls gAudioIO->mOutputMeter->UpdateDisplay().
1471  if (mProject->IsAudioActive())
1472  {
1475  }
1476 
1477  // Let other listeners get the notification
1478  event.Skip();
1479 }
1480 
1481 
1482 // class MixerBoardFrame
1483 
1484 BEGIN_EVENT_TABLE(MixerBoardFrame, wxFrame)
1485  EVT_KEY_DOWN(MixerBoardFrame::OnKeyEvent)
1486  EVT_CLOSE(MixerBoardFrame::OnCloseWindow)
1487  EVT_MAXIMIZE(MixerBoardFrame::OnMaximize)
1488  EVT_SIZE(MixerBoardFrame::OnSize)
1490 
1491 // Default to fitting one track.
1492 const wxSize kDefaultSize =
1494 
1496 : wxFrame(parent, -1,
1497  wxString::Format(_("Audacity Mixer Board%s"),
1498  ((parent->GetName() == wxEmptyString) ?
1499  wxT("") :
1500  wxString::Format(wxT(" - %s"),
1501  parent->GetName()))),
1502  wxDefaultPosition, kDefaultSize,
1503  //vvv Bug in wxFRAME_FLOAT_ON_PARENT:
1504  // If both the project frame and MixerBoardFrame are minimized and you restore MixerBoardFrame,
1505  // you can't restore project frame until you close MixerBoardFrame, but then project frame and
1506  // MixerBoardFrame are restored but MixerBoardFrame is unresponsive because it thinks it's not shown.
1507  // wxDEFAULT_FRAME_STYLE | wxFRAME_FLOAT_ON_PARENT)
1508  wxDEFAULT_FRAME_STYLE)
1509 {
1510  mMixerBoard = safenew MixerBoard(parent, this, wxDefaultPosition, kDefaultSize);
1511 
1512  this->SetSizeHints(MIXER_BOARD_MIN_WIDTH, MIXER_BOARD_MIN_HEIGHT);
1513 
1514  mMixerBoard->UpdateTrackClusters();
1515 
1516  // loads either the XPM or the windows resource, depending on the platform
1517 #if !defined(__WXMAC__) && !defined(__WXX11__)
1518  {
1519 #ifdef __WXMSW__
1520  wxIcon ic{ wxICON(AudacityLogo) };
1521 #else
1522  wxIcon ic{wxICON(AudacityLogo48x48)};
1523 #endif
1524  SetIcon(ic);
1525  }
1526 #endif
1527  Center();
1528 }
1529 
1531 {
1532 }
1533 
1534 
1535 // event handlers
1536 void MixerBoardFrame::OnCloseWindow(wxCloseEvent &WXUNUSED(event))
1537 {
1538  this->Hide();
1539 }
1540 
1541 void MixerBoardFrame::OnMaximize(wxMaximizeEvent &event)
1542 {
1543  // Update the size hints to show all tracks before skipping to let default handling happen.
1545  event.Skip();
1546 }
1547 
1548 void MixerBoardFrame::OnSize(wxSizeEvent & WXUNUSED(event))
1549 {
1550  mMixerBoard->SetSize(this->GetClientSize());
1551 }
1552 
1553 void MixerBoardFrame::OnKeyEvent(wxKeyEvent & event)
1554 {
1555  AudacityProject *project = GetActiveProject();
1556  project->GetCommandManager()->FilterKeyEvent(project, event, true);
1557 }
1558 
1559 
void RefreshTrackClusters(bool bEraseBackground=true)
void SetAlternateIdx(unsigned idx)
Definition: AButton.cpp:248
void OnKeyEvent(wxKeyEvent &evt)
void RedrawProject(const bool bForceWaveTracks=false)
Definition: Project.cpp:1371
AUDACITY_DLL_API Theme theTheme
Definition: Theme.cpp:209
void HandleSelect(bool bShiftDown, bool bControlDown)
Definition: MixerBoard.cpp:685
void OnPaint(wxPaintEvent &evt)
Definition: MixerBoard.cpp:699
const int TRACK_NAME_HEIGHT
Definition: MixerBoard.cpp:125
void TP_PushState(const wxString &longDesc, const wxString &shortDesc, UndoPush flags) override
Definition: Project.cpp:5406
void OnButton_Solo(wxCommandEvent &event)
Definition: MixerBoard.cpp:778
std::unique_ptr< wxImage > mImageMuteDisabled
Definition: MixerBoard.h:258
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:89
MixerTrackSlider * mSlider_Pan
Definition: MixerBoard.h:144
void OnMouseEvent(wxMouseEvent &event)
Definition: MixerBoard.cpp:847
void PopUp()
Definition: AButton.cpp:525
Abstract base class used in importing a file.
Definition: Import.h:32
#define MIXER_BOARD_MIN_HEIGHT
Definition: MixerBoard.cpp:863
double GetStreamTime()
During playback, the (unwarped) track time most recently played.
Definition: AudioIO.cpp:2988
static void CaptureKeyboard(wxWindow *handler)
Definition: Project.cpp:5954
void OnSlider_Gain(wxCommandEvent &event)
Definition: MixerBoard.cpp:729
const int kDoubleInset
Definition: MixerBoard.cpp:121
#define MIXER_BOARD_MIN_WIDTH
Definition: MixerBoard.cpp:866
bool WasShiftDown()
Definition: AButton.cpp:485
const int kMixerTrackClusterWidth
Definition: MixerBoard.cpp:132
bool FilterKeyEvent(AudacityProject *project, const wxKeyEvent &evt, bool permit=false)
static void Bevel2(wxDC &dc, bool up, const wxRect &r, bool bSel=false, bool bHighlight=false)
Definition: AColor.cpp:222
std::unique_ptr< wxImage > mImageSoloDisabled
Definition: MixerBoard.h:258
float Get(bool convert=true)
Definition: ASlider.cpp:1567
void ResizeTrackClusters()
AudacityProject * mProject
Definition: MixerBoard.h:269
void OnMouseEvent(wxMouseEvent &event)
Definition: MixerBoard.cpp:691
std::unique_ptr< wxImage > mImageMuteDown
Definition: MixerBoard.h:258
const int kInset
Definition: MixerBoard.cpp:120
is like wxStaticText, except it can be themed. wxStaticText can't be.
Definition: Theme.h:177
Options & Orientation(wxOrientation o)
Definition: ASlider.h:261
void OnMaximize(wxMaximizeEvent &event)
void OnFocus(wxFocusEvent &event)
Definition: MixerBoard.cpp:85
static void MediumTrackInfo(wxDC *dc, bool selected)
Definition: AColor.cpp:328
void RefreshTrackCluster(const PlayableTrack *pTrack, bool bEraseBackground=true)
void RemoveTrackCluster(const PlayableTrack *pTrack)
std::unique_ptr< wxImage > mImageSoloOver
Definition: MixerBoard.h:258
float GetChannelGain(int channel) const
Definition: WaveTrack.cpp:440
void UpdateForStateChange()
Definition: MixerBoard.cpp:451
CommandManager * GetCommandManager()
Definition: Project.h:346
MeterPanel is a panel that paints the meter used for monitoring or playback.
Definition: Meter.h:97
std::vector< MixerTrackCluster * > mMixerTrackClusters
Definition: MixerBoard.h:266
int mMuteSoloWidth
Definition: MixerBoard.h:262
void LoadMusicalInstruments()
virtual ~MixerBoardScrolledWindow()
Definition: MixerBoard.cpp:843
void UpdatePan(const PlayableTrack *pTrack=NULL)
wxColour GetTrackColor()
Definition: MixerBoard.cpp:677
const int MUTE_SOLO_HEIGHT
Definition: MixerBoard.cpp:127
std::unique_ptr< wxBitmap > mBitmap
Definition: MixerBoard.h:162
int mStyle
Definition: ASlider.h:327
#define safenew
Definition: Audacity.h:230
#define BAD_STREAM_TIME
Definition: AudioIO.h:79
void UpdateMeter(const double t0, const double t1)
Definition: MixerBoard.cpp:520
void OnButton_MusicalInstrument(wxCommandEvent &event)
Definition: MixerBoard.cpp:723
void HandleSliderPan(const bool bWantPushState=false)
Definition: MixerBoard.cpp:426
void CreateMuteSoloImages()
void HandleTrackMute(Track *t, const bool exclusive)
Definition: Project.cpp:5779
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:176
void RefreshTPTrack(Track *pTrk, bool refreshbacking=true)
Definition: Project.cpp:5399
std::unique_ptr< wxImage > mImageMuteDownWhileSolo
Definition: MixerBoard.h:258
void ResetMeter(const bool bResetClipping)
Definition: MixerBoard.cpp:441
void OnMouseEvent(wxMouseEvent &event)
Definition: ASlider.cpp:1510
#define PAN_SLIDER
Definition: ASlider.h:46
static void Bevel(wxDC &dc, bool up, const wxRect &r)
Definition: AColor.cpp:202
const int PAN_HEIGHT
Definition: MixerBoard.cpp:128
wxBitmapButton * mBitmapButton_MusicalInstrument
Definition: MixerBoard.h:141
virtual ~MusicalInstrument()
Definition: MixerBoard.cpp:816
wxBitmap * GetMusicalInstrumentBitmap(const Track *pTrack)
void UpdateSolo(const PlayableTrack *pTrack=NULL)
void UpdateDisplay(unsigned numChannels, int numFrames, float *sampleData)
Update the meters with a block of audio data.
Definition: Meter.cpp:886
MixerBoardScrolledWindow * mScrolledWindow
Definition: MixerBoard.h:270
void OnMouseEvent(wxMouseEvent &event)
Definition: MixerBoard.cpp:69
std::unique_ptr< wxImage > mImageSoloUp
Definition: MixerBoard.h:258
std::shared_ptr< PlayableTrack > mTrack
Definition: MixerBoard.h:133
std::unique_ptr< wxImage > mImageMuteOver
Definition: MixerBoard.h:258
char * samplePtr
Definition: Types.h:203
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
void UpdatePrefs()
Definition: Meter.cpp:340
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:101
void UpdateWidth()
AudacityProject * mProject
Definition: MixerBoard.h:188
const int kQuadrupleInset
Definition: MixerBoard.cpp:123
virtual ~MixerBoardFrame()
void UpdateGain(const PlayableTrack *pTrack)
wxString GetName() const
Definition: Track.h:270
void SetGain(float newGain)
Definition: WaveTrack.cpp:420
virtual Track * First(TrackList *val=nullptr)
Definition: Track.cpp:418
void MoveTrackCluster(const PlayableTrack *pTrack, bool bUp)
void OnCaptureKey(wxCommandEvent &event)
Definition: MixerBoard.cpp:99
bool IsAudioActive() const
Definition: Project.cpp:1447
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1)
Definition: AColor.cpp:289
void Set(float value)
Definition: ASlider.cpp:1572
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
PlayMode mLastPlayMode
Definition: Project.h:557
An AudioTrack that can be played and stopped.
Definition: Track.h:375
AButton * mToggleButton_Solo
Definition: MixerBoard.h:143
void OnSlider_Pan(wxCommandEvent &event)
Definition: MixerBoard.cpp:756
void UpdateMeters(const double t1, const bool bLoopedPlay)
int FindMixerTrackCluster(const PlayableTrack *pTrack, MixerTrackCluster **hMixerTrackCluster) const
bool IsSoloNone() const
Definition: Project.h:607
bool IsSoloSimple() const
Definition: Project.h:606
An iterator for a TrackList.
Definition: Track.h:401
const wxSize kDefaultSize
#define DB_SLIDER
Definition: ASlider.h:45
static void ReleaseKeyboard(wxWindow *handler)
Definition: Project.cpp:5965
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
void UpdateName(const PlayableTrack *pTrack)
#define VEL_SLIDER
Definition: ASlider.h:49
_("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
std::unique_ptr< wxImage > mImageMuteUp
Definition: MixerBoard.h:258
AudioIO * gAudioIO
Definition: AudioIO.cpp:482
void OnTimer(wxCommandEvent &event)
ASlider is a custom slider, allowing for a slicker look and feel.
Definition: ASlider.h:243
const wxChar * name
Definition: Distortion.cpp:94
AButton * mToggleButton_Mute
Definition: MixerBoard.h:142
const int kTripleInset
Definition: MixerBoard.cpp:122
virtual Track * Next(bool skiplinked=false)
Definition: Track.cpp:460
void OnSize(wxSizeEvent &evt)
void MakeButtonBitmap(wxMemoryDC &dc, wxBitmap &bitmap, wxRect &bev, const wxString &str, bool up)
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:308
void OnSize(wxSizeEvent &evt)
WaveTrack * GetRight() const
Definition: MixerBoard.cpp:334
MusicalInstrumentArray mMusicalInstruments
Definition: MixerBoard.h:268
void OnButton_Mute(wxCommandEvent &event)
Definition: MixerBoard.cpp:761
MixerTrackSlider * mSlider_Gain
Definition: MixerBoard.h:145
void SelectNone()
Definition: Menus.cpp:6267
void OnCloseWindow(wxCloseEvent &WXUNUSED(event))
const int kRightSideStackWidth
Definition: MixerBoard.cpp:131
wxArrayString mKeywords
Definition: MixerBoard.h:163
WaveTrack * GetWave() const
Definition: MixerBoard.cpp:329
const int kLeftSideStackWidth
Definition: MixerBoard.cpp:130
std::unique_ptr< wxImage > mImageSoloDown
Definition: MixerBoard.h:258
MeterPanel * mMeter
Definition: MixerBoard.h:149
int GetTrackClustersWidth()
wxColour & Colour(int iIndex)
Definition: Theme.cpp:1225
void PushDown()
Definition: AButton.cpp:517
MixerBoard * mMixerBoard
Definition: MixerBoard.h:136
MusicalInstrument(std::unique_ptr< wxBitmap > &&pBitmap, const wxString &strXPMfilename)
Definition: MixerBoard.cpp:798
void UpdatePrefs()
Definition: MixerBoard.cpp:940
AudacityProject * mProject
Definition: MixerBoard.h:137
END_EVENT_TABLE()
bool Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true, sampleCount *pNumCopied=nullptr) const
Definition: WaveTrack.cpp:1971
double mPrevT1
Definition: MixerBoard.h:271
void SetPan(float newPan) override
Definition: WaveTrack.cpp:430
Functions for doing the mixdown of the tracks.
Definition: Mix.h:80
Options & Style(int s)
Definition: ASlider.h:260
void ResetMeters(const bool bResetClipping)
TrackList * mTracks
Definition: MixerBoard.h:272
MixerBoard * mMixerBoard
Definition: MixerBoard.h:293
void HandleTrackSolo(Track *t, const bool alternate)
Definition: Project.cpp:5864
void HandleSliderGain(const bool bWantPushState=false)
Definition: MixerBoard.cpp:398
void HandleListSelection(Track *t, bool shift, bool ctrl, bool modifyState)
Definition: Menus.cpp:3559
bool HasSolo()
void RecreateMixerBoard()
Definition: Project.cpp:4859
void UpdateMute(const PlayableTrack *pTrack=NULL)
A wxButton with mouse-over behaviour.
Definition: AButton.h:28
const int MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH
Definition: MixerBoard.cpp:126
A Track that is used for Midi notes. (Somewhat old code).
void UpdateTrackClusters()
Definition: MixerBoard.cpp:973