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