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