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