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