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