Audacity 3.2.0
WaveTrackControls.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5WaveTrackControls.cpp
6
7Paul Licameli split from TrackPanel.cpp
8
9**********************************************************************/
10
11
12#include "WaveTrackControls.h"
13
14#include "../../ui/PlayableTrackButtonHandles.h"
16
17#include "WaveChannelView.h"
19#include "AudioIOBase.h"
20#include "../../../../CellularPanel.h"
21#include "Project.h"
22#include "ProjectAudioIO.h"
23#include "ProjectHistory.h"
24#include "../../../../ProjectWindows.h"
25#include "../../../../RefreshCode.h"
26#include "ShuttleGui.h"
27#include "SyncLock.h"
28#include "Theme.h"
29#include "../../../../TrackArtist.h"
30#include "../../../../TrackPanel.h"
31#include "TrackFocus.h"
32#include "../../../../TrackPanelMouseEvent.h"
33#include "WaveClip.h"
34#include "WaveTrack.h"
35#include "WaveTrackUtilities.h"
37#include "../../../../prefs/PrefsDialog.h"
38#include "../../../../prefs/ThemePrefs.h"
39#include "AudacityMessageBox.h"
40#include "ProgressDialog.h"
41#include "UserException.h"
42#include "Identifier.h"
43
44#include <wx/app.h>
45#include <wx/combobox.h>
46#include <wx/frame.h>
47#include <wx/sizer.h>
48
49#include "MixAndRender.h"
50
52
53std::vector<UIHandlePtr> WaveTrackControls::HitTest
54(const TrackPanelMouseState & st,
55 const AudacityProject *pProject)
56{
57 // Hits are mutually exclusive, results single
58 const wxMouseState &state = st.state;
59 const wxRect &rect = st.rect;
60 if (state.ButtonIsDown(wxMOUSE_BTN_LEFT)) {
61 auto track = FindTrack();
62 std::vector<UIHandlePtr> results;
63 auto result = [&]{
64 UIHandlePtr result;
65 if (NULL != (result = MuteButtonHandle::HitTest(
66 mMuteHandle, state, rect, pProject, track)))
67 return result;
68
69 if (NULL != (result = SoloButtonHandle::HitTest(
70 mSoloHandle, state, rect, pProject, track)))
71 return result;
72
73 if (NULL != (result = EffectsButtonHandle::HitTest(
74 mEffectsHandle, state, rect, pProject, track)))
75 return result;
76
77 if (NULL != (result = GainSliderHandle::HitTest(
78 mGainHandle, state, rect, track)))
79 return result;
80
81 if (NULL != (result = PanSliderHandle::HitTest(
82 mPanHandle, state, rect, track)))
83 return result;
84
85 return result;
86 }();
87 if (result) {
88 results.push_back(result);
89 return results;
90 }
91 }
92
93 return PlayableTrackControls::HitTest(st, pProject);
94}
95
97{
98 return static_cast<WaveTrack&>(mpData->track);
99};
100
101enum {
103
104 OnRate8ID = 30000, // <---
109 OnRate48ID, // | Leave these in order
117 // |
120 OnFloatID, // <---
121
123
125
129
131
135
137
138 // Range of ids for registered items -- keep this last!
140};
141
142
143namespace {
144using ValueFinder = std::function< int( WaveTrack& ) >;
145
146// A function that makes functions that check and enable sub-menu items,
147// parametrized by how you get the relevant value from a track's settings
148template< typename Table >
150{
151 return [findValue]( PopupMenuHandler &handler, wxMenu &menu, int id ){
152 auto pData = static_cast<Table&>( handler ).mpData;
153 auto &track = static_cast<WaveTrack&>(pData->track);
154 auto &project = pData->project;
155 bool unsafe = ProjectAudioIO::Get( project ).IsAudioActive();
156
157 menu.Check(id, id == findValue(track));
158 menu.Enable( id, !unsafe );
159 };
160};
161}
162
163
164//=============================================================================
165// Table class for a sub-menu
167{
169 : PopupMenuTable{ "SampleFormat", XO("&Format") }
170 {}
172
173 static FormatMenuTable &Instance();
174
175 void InitUserData(void *pUserData) override;
176
178
179 static int IdOfFormat(sampleFormat format);
180
181 void OnFormatChange(wxCommandEvent & event);
182};
183
185{
186 static FormatMenuTable instance;
187 return instance;
188}
189
190void FormatMenuTable::InitUserData(void *pUserData)
191{
192 mpData = static_cast<PlayableTrackControls::InitMenuData*>(pUserData);
193}
194
195
197 static const auto fn = initFn< FormatMenuTable >(
198 []( WaveTrack &track ){
199 return IdOfFormat( track.GetSampleFormat() );
200 }
201 );
202
204 GetSampleFormatStr(int16Sample), POPUP_MENU_FN( OnFormatChange ), fn );
206 GetSampleFormatStr( int24Sample), POPUP_MENU_FN( OnFormatChange ), fn );
208 GetSampleFormatStr(floatSample), POPUP_MENU_FN( OnFormatChange ), fn );
209
211
212
214{
215 switch (format) {
216 case int16Sample:
217 return On16BitID;
218 case int24Sample:
219 return On24BitID;
220 case floatSample:
221 return OnFloatID;
222 default:
223 // ERROR -- should not happen
224 wxASSERT(false);
225 break;
226 }
227 return OnFloatID;// Compiler food.
228}
229
232void FormatMenuTable::OnFormatChange(wxCommandEvent & event)
233{
234 int id = event.GetId();
235 wxASSERT(id >= On16BitID && id <= OnFloatID);
236 auto &track = static_cast<WaveTrack&>(mpData->track);
237
238 sampleFormat newFormat = int16Sample;
239
240 switch (id) {
241 case On16BitID:
242 newFormat = int16Sample;
243 break;
244 case On24BitID:
245 newFormat = int24Sample;
246 break;
247 case OnFloatID:
248 newFormat = floatSample;
249 break;
250 default:
251 // ERROR -- should not happen
252 wxASSERT(false);
253 break;
254 }
255 if (newFormat == track.GetSampleFormat())
256 return; // Nothing to do.
257
259
260 ProgressDialog progress{ XO("Changing sample format"),
261 XO("Processing... 0%%"),
263
264 // Simply finding a denominator for the progress dialog
265 // Hidden samples are processed too, they should be counted as well
266 // (Correctly counting all samples of all channels)
267 const sampleCount totalSamples =
269 sampleCount processedSamples{ 0 };
270
271 // Below is the lambda function that is passed along the call chain to
272 // the Sequence::ConvertToSampleFormat. This callback function is used
273 // to report the conversion progress and update the progress dialog.
274 auto progressUpdate = [&progress, &totalSamples, &processedSamples]
275 (size_t newlyProcessedCount)->void
276 {
277 processedSamples += newlyProcessedCount;
278 double d_processed = processedSamples.as_double();
279 double d_total = totalSamples.as_double();
280 int percentage{ static_cast<int>((d_processed / d_total) * 100) };
281
282 auto progressStatus = progress.Update(d_processed, d_total,
283 XO("Processing... %i%%").Format(percentage));
284
285 if (progressStatus != ProgressResult::Success)
286 throw UserException{};
287 };
288
289 track.ConvertToSampleFormat(newFormat, progressUpdate);
290
292 /* i18n-hint: The strings name a track and a format */
293 .PushState(XO("Changed '%s' to %s")
294 .Format(track.GetName(), GetSampleFormatStr(newFormat)),
295 XO("Format Change"));
296
297 using namespace RefreshCode;
299}
300
301
302//=============================================================================
303// Table class for a sub-menu
305{
307 : PopupMenuTable{ "SampleRate", XO("Rat&e") }
308 {}
310
311 static RateMenuTable &Instance();
312
313 void InitUserData(void *pUserData) override;
314
316
317 static int IdOfRate(int rate);
319 void SetRate(WaveTrack &track, double rate);
320
321 void OnRateChange(wxCommandEvent & event);
322 void OnRateOther(wxCommandEvent & event);
323};
324
326{
327 static RateMenuTable instance;
328 return instance;
329}
330
331void RateMenuTable::InitUserData(void *pUserData)
332{
333 mpData = static_cast<PlayableTrackControls::InitMenuData*>(pUserData);
334}
335
336// Because of Bug 1780 we can't use AppendRadioItem
337// If we did, we'd get no message when clicking on Other...
338// when it is already selected.
340 static const auto fn = initFn< RateMenuTable >(
341 []( WaveTrack &track ){
342 return IdOfRate( (int)track.GetRate() );
343 }
344 );
345
346 AppendCheckItem( "8000", OnRate8ID, XXO("8000 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
347 AppendCheckItem( "11025", OnRate11ID, XXO("11025 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
348 AppendCheckItem( "16000", OnRate16ID, XXO("16000 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
349 AppendCheckItem( "22050", OnRate22ID, XXO("22050 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
350 AppendCheckItem( "44100", OnRate44ID, XXO("44100 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
351 AppendCheckItem( "48000", OnRate48ID, XXO("48000 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
352 AppendCheckItem( "88200", OnRate88ID, XXO("88200 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
353 AppendCheckItem( "96000", OnRate96ID, XXO("96000 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
354 AppendCheckItem( "176400", OnRate176ID, XXO("176400 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
355 AppendCheckItem( "192000", OnRate192ID, XXO("192000 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
356 AppendCheckItem( "352800", OnRate352ID, XXO("352800 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
357 AppendCheckItem( "384000", OnRate384ID, XXO("384000 Hz"), POPUP_MENU_FN( OnRateChange ), fn );
358 AppendCheckItem( "Other", OnRateOtherID, XXO("&Other..."), POPUP_MENU_FN( OnRateOther ), fn );
359
361
362const int nRates = 12;
363
366static int gRates[nRates] = { 8000, 11025, 16000, 22050, 44100, 48000, 88200, 96000,
367176400, 192000, 352800, 384000 };
368
371{
372 for (int i = 0; i<nRates; i++) {
373 if (gRates[i] == rate)
374 return i + OnRate8ID;
375 }
376 return OnRateOtherID;
377}
378
379void RateMenuTable::SetRate(WaveTrack &track, double rate)
380{
382 auto end1 = track.GetEndTime();
383 track.SetRate(rate);
384 if (SyncLockState::Get(*project).IsSyncLocked()) {
385 auto end2 = track.GetEndTime();
386 for (auto pLocked : SyncLock::Group(track)) {
387 if (pLocked != &track)
388 pLocked->SyncLockAdjust(end1, end2);
389 }
390 }
391
392 // Separate conversion of "rate" enables changing the decimals without affecting i18n
393 wxString rateString = wxString::Format(wxT("%.3f"), rate);
395 /* i18n-hint: The string names a track */
396 .PushState(XO("Changed '%s' to %s Hz")
397 .Format(track.GetName(), rateString),
398 XO("Rate Change"));
399}
400
403void RateMenuTable::OnRateChange(wxCommandEvent & event)
404{
405 int id = event.GetId();
406 wxASSERT(id >= OnRate8ID && id <= OnRate384ID);
407 auto &track = static_cast<WaveTrack&>(mpData->track);
408
409 SetRate(track, gRates[id - OnRate8ID]);
410
411 using namespace RefreshCode;
413}
414
415void RateMenuTable::OnRateOther(wxCommandEvent &)
416{
417 auto &track = static_cast<WaveTrack&>(mpData->track);
418
419 int newRate;
420
423 while (true)
424 {
425 wxDialogWrapper dlg(mpData->pParent, wxID_ANY, XO("Set Rate"));
426 dlg.SetName();
427 ShuttleGui S(&dlg, eIsCreating);
428 wxString rate;
429 wxComboBox *cb;
430
431 rate.Printf(wxT("%ld"), lrint(track.GetRate()));
432
433 wxArrayStringEx rates{
434 wxT("8000") ,
435 wxT("11025") ,
436 wxT("16000") ,
437 wxT("22050") ,
438 wxT("44100") ,
439 wxT("48000") ,
440 wxT("88200") ,
441 wxT("96000") ,
442 wxT("176400") ,
443 wxT("192000") ,
444 wxT("352800") ,
445 wxT("384000") ,
446 };
447
448 S.StartVerticalLay(true);
449 {
450 S.SetBorder(10);
451 S.StartHorizontalLay(wxEXPAND, false);
452 {
453 cb = S.AddCombo(XXO("New sample rate (Hz):"),
454 rate,
455 rates);
456#if defined(__WXMAC__)
457 // As of wxMac-2.8.12, setting manually is required
458 // to handle rates not in the list. See: Bug #427
459 cb->SetValue(rate);
460#endif
461 }
462 S.EndHorizontalLay();
463 S.AddStandardButtons();
464 }
465 S.EndVerticalLay();
466
467 dlg.SetClientSize(dlg.GetSizer()->CalcMin());
468 dlg.Center();
469
470 if (dlg.ShowModal() != wxID_OK)
471 {
472 return; // user cancelled dialog
473 }
474
475 long lrate;
476 if (cb->GetValue().ToLong(&lrate) && lrate >= 1 && lrate <= 1000000)
477 {
478 newRate = (int)lrate;
479 break;
480 }
481
483 XO("The entered value is invalid"),
484 XO("Error"),
485 wxICON_ERROR,
486 mpData->pParent);
487 }
488
489 SetRate(track, newRate);
490
491 using namespace RefreshCode;
493}
494
495static const auto MenuPathStart = wxT("WaveTrackMenu");
496
497//=============================================================================
498// Class defining common command handlers for mono and stereo tracks
500{
502
505 {
507 }
508
509 void InitUserData(void *pUserData) override;
510
512
513 void OnMultiView(wxCommandEvent & event);
514 void OnSetDisplay(wxCommandEvent & event);
515
516 void OnMergeStereo(wxCommandEvent & event);
517
518 // TODO: more-than-two-channels
519 // How should we define generalized channel manipulation operations?
522 void SplitStereo(bool stereo);
523
524 void OnSwapChannels(wxCommandEvent & event);
525 void OnSplitStereo(wxCommandEvent & event);
526 void OnSplitStereoMono(wxCommandEvent & event);
527};
528
530{
531 static WaveTrackMenuTable instance;
532 return instance;
533}
534
536{
537 mpData = static_cast<PlayableTrackControls::InitMenuData*>(pUserData);
538}
539
540static std::vector<WaveChannelSubViewType> AllTypes()
541{
542 auto result = WaveChannelSubViewType::All();
543 if (result.size() > reserveDisplays) {
544 wxASSERT( false );
545 result.resize(reserveDisplays);
546 }
547 return result;
548}
549
551 // Functions usable in callbacks to check and disable items
552 static const auto isMono =
553 []( PopupMenuHandler &handler ) -> bool {
554 auto &track =
555 static_cast< WaveTrackMenuTable& >( handler ).FindWaveTrack();
556 return 1 == track.NChannels();
557 };
558
559 static const auto isUnsafe =
560 []( PopupMenuHandler &handler ) -> bool {
561 auto &project =
562 static_cast< WaveTrackMenuTable& >( handler ).mpData->project;
565 };
566
567
568 BeginSection( "SubViews" );
569 // Multi-view check mark item, if more than one track sub-view type is
570 // known
571 Append(Adapt<My>([](My &table) {
573 ? std::make_unique<Entry>(
574 "MultiView", Entry::CheckItem, OnMultiViewID, XXO("&Multi-view"),
575 POPUP_MENU_FN( OnMultiView ),
576 table,
577 [](PopupMenuHandler &handler, wxMenu &menu, int id){
578 auto &table = static_cast<WaveTrackMenuTable&>(handler);
579 auto &track = table.FindWaveTrack();
580 const auto &view = WaveChannelView::GetFirst(track);
581 menu.Check(id, view.GetMultiView());
582 })
583 : nullptr;
584 }));
585
586 // Append either a checkbox or radio item for each sub-view.
587 // Radio buttons if in single-view mode, else checkboxes
589 for ( const auto &type : AllTypes() ) {
590 static const auto initFn = []( bool radio ){ return
591 [radio]( PopupMenuHandler &handler, wxMenu &menu, int id ){
592 // Find all known sub-view types
593 const auto allTypes = AllTypes();
594
595 // How to convert a type to a menu item id
596 const auto IdForType =
597 [&allTypes](const WaveChannelSubViewType &type) -> int {
598 const auto begin = allTypes.begin();
599 return OnSetDisplayId +
600 (std::find(begin, allTypes.end(), type) - begin);
601 };
602
603 auto &table = static_cast< WaveTrackMenuTable& >( handler );
604 auto &track = table.FindWaveTrack();
605
606 const auto &view = WaveChannelView::GetFirst(track);
607
608 const auto displays = view.GetDisplays();
609 const auto end = displays.end();
610 bool check = (end !=
611 std::find_if(displays.begin(), end,
612 [&](const WaveChannelSubViewType &type){
613 return id == IdForType(type); }));
614 menu.Check( id, check );
615
616 // Bug2275 residual
617 // Disable the checking-off of the only sub-view
618 if ( !radio && displays.size() == 1 && check )
619 menu.Enable( id, false );
620 };
621 };
622 Append(Adapt<My>([type, id](My &table) {
623 const auto pTrack = &table.FindWaveTrack();
624 const auto &view = WaveChannelView::GetFirst(*pTrack);
625 const auto itemType =
626 view.GetMultiView() ? Entry::CheckItem : Entry::RadioItem;
627 return std::make_unique<Entry>( type.name.Internal(), itemType,
628 id, type.name.Msgid(),
629 POPUP_MENU_FN( OnSetDisplay ), table,
630 initFn( !view.GetMultiView() ) );
631 }));
632 ++id;
633 }
634 BeginSection( "Extra" );
636 EndSection();
637
638 BeginSection( "Channels" );
639 AppendItem( "MakeStereo", OnMergeStereoID, XXO("Ma&ke Stereo Track"),
640 POPUP_MENU_FN( OnMergeStereo ),
641 []( PopupMenuHandler &handler, wxMenu &menu, int id ){
642 bool canMakeStereo = !isUnsafe( handler ) && isMono( handler );
643 if ( canMakeStereo ) {
645 static_cast< WaveTrackMenuTable& >( handler ).mpData->project;
646 auto &tracks = TrackList::Get( project );
647 auto &table = static_cast< WaveTrackMenuTable& >( handler );
648 auto &track = table.FindWaveTrack();
649 auto next = * ++ tracks.Find(&track);
650 canMakeStereo =
651 (next &&
652 next->NChannels() == 1 &&
653 track_cast<WaveTrack*>(next));
654 }
655 menu.Enable( id, canMakeStereo );
656 }
657 );
658
659 AppendItem( "Swap", OnSwapChannelsID, XXO("Swap Stereo &Channels"),
660 POPUP_MENU_FN( OnSwapChannels ),
661 []( PopupMenuHandler &handler, wxMenu &menu, int id ){
662 auto &track =
663 static_cast< WaveTrackMenuTable& >( handler ).FindWaveTrack();
664 bool isStereo =
665 2 == track.NChannels();
666 menu.Enable( id, isStereo && !isUnsafe( handler ) );
667 }
668 );
669
670 static const auto enableSplitStereo =
671 []( PopupMenuHandler &handler, wxMenu &menu, int id ){
672 menu.Enable( id, !isMono( handler ) && !isUnsafe( handler ) );
673 };
674
675 AppendItem( "Split", OnSplitStereoID, XXO("Spl&it Stereo Track"),
676 POPUP_MENU_FN( OnSplitStereo ), enableSplitStereo );
678 XXO("Split Stereo to Mo&no"), POPUP_MENU_FN( OnSplitStereoMono ),
680 EndSection();
681
682 BeginSection( "Format" );
683 POPUP_MENU_SUB_MENU( "Format", FormatMenuTable, mpData )
684 EndSection();
685
686 BeginSection( "Rate" );
687 POPUP_MENU_SUB_MENU( "Rate", RateMenuTable, mpData )
688 EndSection();
690
691
692void WaveTrackMenuTable::OnMultiView(wxCommandEvent & event)
693{
694 auto &track = static_cast<WaveTrack&>(mpData->track);
695 auto &view = WaveChannelView::GetFirst(track);
696 bool multi = !view.GetMultiView();
697 const auto &displays = view.GetDisplays();
698 const auto display = displays.empty()
699 ? WaveChannelViewConstants::Waveform : displays.begin()->id;
700 view.SetMultiView(multi);
701
702 // Whichever sub-view was on top stays on top
703 // If going into Multi-view, it will be 1/nth the height.
704 // If exiting multi-view, it will be full height.
705 view.SetDisplay(display, !multi);
706}
707
709void WaveTrackMenuTable::OnSetDisplay(wxCommandEvent & event)
710{
711 int idInt = event.GetId();
712 wxASSERT(idInt >= OnSetDisplayId &&
713 idInt <= lastDisplayId);
714 auto &track = static_cast<WaveTrack&>(mpData->track);
715
716 auto id = AllTypes()[ idInt - OnSetDisplayId ].id;
717
718 auto &view = WaveChannelView::GetFirst(track);
719 if (view.GetMultiView()) {
720 if (!WaveChannelView::GetFirst(track)
721 .ToggleSubView(WaveChannelView::Display{ id } )) {
722 // Trying to toggle off the last sub-view. It was refused.
723 // Decide what to do here. Turn off multi-view instead?
724 // PRL: I don't agree that it makes sense
725 }
726 else
728 }
729 else {
730 const auto displays = view.GetDisplays();
731 const bool wrongType =
732 !(displays.size() == 1 && displays[0].id == id);
733 if (wrongType) {
735
737 ProjectHistory::Get( *project ).ModifyState(true);
738
739 using namespace RefreshCode;
741 }
742 }
743}
744
747{
749 auto &tracks = TrackList::Get( *project );
750
751 const auto first = tracks.Any<WaveTrack>().find(&mpData->track);
752 const auto left = *first;
753 const auto right = *std::next(first);
754
755 const auto checkAligned = [](const WaveTrack& left, const WaveTrack& right)
756 {
757 auto eqTrims = [](double a, double b)
758 {
759 return std::abs(a - b) <=
760 std::numeric_limits<double>::epsilon() * std::max(a, b);
761 };
762 const auto eps = 0.5 / left.GetRate();
763 const auto &rightIntervals = right.Intervals();
764 for (const auto &a : left.Intervals()) {
765 auto it = std::find_if(
766 rightIntervals.begin(),
767 rightIntervals.end(),
768 [&](const auto& b)
769 {
770 //Start() and End() are always snapped to a sample grid
771 return std::abs(a->Start() - b->Start()) < eps &&
772 std::abs(a->End() - b->End()) < eps &&
773 eqTrims(a->GetTrimLeft(), b->GetTrimLeft()) &&
774 eqTrims(a->GetTrimRight(), b->GetTrimRight()) &&
775 a->HasEqualPitchAndSpeed(*b);
776 });
777 if(it == rightIntervals.end())
778 return false;
779 }
780 return true;
781 };
782
783 if(RealtimeEffectList::Get(*left).GetStatesCount() != 0 ||
785 !checkAligned(*left, *right))
786 {
787 const auto answer = BasicUI::ShowMessageBox(
788 XO(
789"The tracks you are attempting to merge to stereo contain clips at\n"
790"different positions, or otherwise mismatching clips. Merging them\n"
791"will render the tracks.\n\n"
792"This causes any realtime effects to be applied to the waveform and\n"
793"hidden data to be removed. Additionally, the entire track will\n"
794"become one large clip.\n\n"
795"Do you wish to continue?"
796 ),
799 .Caption(XO("Combine mono to stereo")));
801 return;
802 }
803
804 const auto viewMinimized =
806 ChannelView::Get(*right->GetChannel(0)).GetMinimized();
807 const auto averageViewHeight =
810
811 left->SetPan(-1.0f);
812 right->SetPan(1.0f);
813 auto mix = MixAndRender(
815 tracks.Any<const WaveTrack>().find(left),
816 ++tracks.Any<const WaveTrack>().find(right)
817 },
818 Mixer::WarpOptions{ tracks.GetOwner() },
819 (*first)->GetName(),
821 //use highest sample rate
822 std::max(left->GetRate(), right->GetRate()),
823 //use widest sample format
824 std::max(left->GetSampleFormat(), right->GetSampleFormat()),
825 0.0, 0.0);
826
827 tracks.Insert(*first, mix);
828 tracks.Remove(*left);
829 tracks.Remove(*right);
830
831 for(const auto& channel : mix->Channels())
832 {
833 // Set NEW track heights and minimized state
834 auto& view = ChannelView::Get(*channel);
835 view.SetMinimized(viewMinimized);
836 view.SetExpandedHeight(averageViewHeight);
837 }
838 ProjectHistory::Get( *project ).PushState(
839 /* i18n-hint: The string names a track */
840 XO("Made '%s' a stereo track").Format(mix->GetName()),
841 XO("Make Stereo"));
842
843 using namespace RefreshCode;
845}
846
849{
851
852 int totalHeight = 0;
853 int nChannels = 0;
854
855 auto &track = static_cast<WaveTrack&>(mpData->track);
856 const std::vector<WaveTrack::Holder> unlinkedTracks = track.SplitChannels();
857 if (stereo) {
858 unlinkedTracks[0]->SetPan(-1.0f);
859 unlinkedTracks[1]->SetPan(1.0f);
860 }
861
862 for (const auto track : unlinkedTracks) {
863 auto &view = ChannelView::Get(*track->GetChannel(0));
864
865 //make sure no channel is smaller than its minimum height
866 if (view.GetHeight() < view.GetMinimizedHeight())
867 view.SetExpandedHeight(view.GetMinimizedHeight());
868 totalHeight += view.GetHeight();
869 ++nChannels;
870 }
871
872 int averageHeight = totalHeight / nChannels;
873
874 for (const auto track : unlinkedTracks)
875 // Make tracks the same height
876 ChannelView::Get(*track->GetChannel(0)).SetExpandedHeight(averageHeight);
877}
878
881{
883
884 auto &trackFocus = TrackFocus::Get( *project );
885 auto &track = static_cast<WaveTrack&>(mpData->track);
886 track.SwapChannels();
887 ProjectHistory::Get( *project ).PushState(
888 /* i18n-hint: The string names a track */
889 XO("Swapped Channels in '%s'").Format(track.GetName()),
890 XO("Swap Channels"));
891
893}
894
897{
898 SplitStereo(true);
899 auto &track = static_cast<WaveTrack&>(mpData->track);
901 ProjectHistory::Get( *project ).PushState(
902 /* i18n-hint: The string names a track */
903 XO("Split stereo track '%s'").Format(track.GetName()),
904 XO("Split"));
905
906 using namespace RefreshCode;
908}
909
912{
913 SplitStereo(false);
914 auto &track = static_cast<WaveTrack&>(mpData->track);
916 ProjectHistory::Get( *project ).PushState(
917 /* i18n-hint: The string names a track */
918 XO("Split Stereo to Mono '%s'").Format(track.GetName()),
919 XO("Split to Mono"));
920
921 using namespace RefreshCode;
923}
924
926{
929 {
930 {wxT("/SubViews/Extra"), wxT("WaveColor,SpectrogramSettings")},
931 }
932 };
933
935 return &result;
936}
937
939{
941}
942
943// drawing related
944#include "../../../../widgets/ASlider.h"
945#include "../../../ui/CommonTrackInfo.h"
946#include "../../../../TrackPanelDrawingContext.h"
947#include "ViewInfo.h"
948
949namespace {
950
952( LWSlider *(*Selector)
953 (const wxRect &sliderRect, const WaveTrack *t, bool captured, wxWindow*),
954 wxDC *dc, const wxRect &rect, const Track *pTrack,
955 wxWindow *pParent,
956 bool captured, bool highlight )
957{
958 wxRect sliderRect = rect;
959 CommonTrackInfo::GetSliderHorizontalBounds( rect.GetTopLeft(), sliderRect );
960 auto wt = static_cast<const WaveTrack*>( pTrack );
961 Selector( sliderRect, wt, captured, pParent )->OnPaint(*dc, highlight);
962}
963
965( TrackPanelDrawingContext &context,
966 const wxRect &rect, const Track *pTrack )
967{
968 auto target = dynamic_cast<PanSliderHandle*>( context.target.get() );
969 auto dc = &context.dc;
970 bool hit = target && target->GetTrack().get() == pTrack;
971 bool captured = hit && target->IsDragging();
972
973 const auto artist = TrackArtist::Get( context );
974 auto pParent = FindProjectFrame( artist->parent->GetProject() );
975
977 &WaveTrackControls::PanSlider, dc, rect, pTrack,
978 pParent, captured, hit);
979}
980
982( TrackPanelDrawingContext &context,
983 const wxRect &rect, const Track *pTrack )
984{
985 auto target = dynamic_cast<GainSliderHandle*>( context.target.get() );
986 auto dc = &context.dc;
987 bool hit = target && target->GetTrack().get() == pTrack;
988 if( hit )
989 hit=hit;
990 bool captured = hit && target->IsDragging();
991
992 const auto artist = TrackArtist::Get( context );
993 auto pParent = FindProjectFrame( artist->parent->GetProject() );
994
996 &WaveTrackControls::GainSlider, dc, rect, pTrack,
997 pParent, captured, hit);
998}
999
1000
1001}
1002
1004
1005static const struct WaveTrackTCPLines
1007 (TCPLines&)*this =
1009 insert( end(), {
1010
1015
1016 } );
1018
1019void WaveTrackControls::GetGainRect(const wxPoint &topleft, wxRect & dest)
1020{
1023 dest.y = topleft.y + results.first;
1024 dest.height = results.second;
1025}
1026
1027void WaveTrackControls::GetPanRect(const wxPoint &topleft, wxRect & dest)
1028{
1029 GetGainRect( topleft, dest );
1031 dest.y = topleft.y + results.first;
1032}
1033
1035{
1037}
1038
1040{
1041 return waveTrackTCPLines;
1042}
1043
1044namespace
1045{
1046std::unique_ptr<LWSlider>
1051}
1052
1054 CellularPanel &panel, const WaveTrack &wt )
1055{
1056 auto &controls = TrackControls::Get( wt );
1057 auto rect = panel.FindRect( controls );
1058 wxRect sliderRect;
1059 GetGainRect( rect.GetTopLeft(), sliderRect );
1060 return GainSlider( sliderRect, &wt, false, &panel );
1061}
1062
1064(const wxRect &sliderRect, const WaveTrack *t, bool captured, wxWindow *pParent)
1065{
1066 static std::once_flag flag;
1067 std::call_once( flag, []{ ReCreateGainSlider({}); });
1069
1070 wxPoint pos = sliderRect.GetPosition();
1071 float gain = t ? t->GetGain() : 1.0;
1072
1073 gGain->Move(pos);
1074 gGain->Set(gain);
1075 gGainCaptured->Move(pos);
1076 gGainCaptured->Set(gain);
1077
1078 auto slider = (captured ? gGainCaptured : gGain).get();
1079 slider->SetParent( pParent );
1080 return slider;
1081}
1082
1084{
1085 if (message.appearance)
1086 return;
1087 const wxPoint point{ 0, 0 };
1088 wxRect sliderRect;
1089 GetGainRect(point, sliderRect);
1090
1091 float defPos = 1.0;
1092 /* i18n-hint: Title of the Gain slider, used to adjust the volume */
1093 gGain = std::make_unique<LWSlider>(nullptr, XO("Gain"),
1094 wxPoint(sliderRect.x, sliderRect.y),
1095 wxSize(sliderRect.width, sliderRect.height),
1096 DB_SLIDER);
1097 gGain->SetDefaultValue(defPos);
1098
1099 gGainCaptured = std::make_unique<LWSlider>(nullptr, XO("Gain"),
1100 wxPoint(sliderRect.x, sliderRect.y),
1101 wxSize(sliderRect.width, sliderRect.height),
1102 DB_SLIDER);
1103 gGainCaptured->SetDefaultValue(defPos);
1104}
1105
1107 CellularPanel &panel, const WaveTrack &wt )
1108{
1109 auto &controls = TrackControls::Get( wt );
1110 auto rect = panel.FindRect( controls );
1111 wxRect sliderRect;
1112 GetPanRect( rect.GetTopLeft(), sliderRect );
1113 return PanSlider( sliderRect, &wt, false, &panel );
1114}
1115
1117(const wxRect &sliderRect, const WaveTrack *t, bool captured, wxWindow *pParent)
1118{
1119 static std::once_flag flag;
1120 std::call_once( flag, []{ ReCreatePanSlider({}); });
1122
1123 wxPoint pos = sliderRect.GetPosition();
1124 float pan = t ? t->GetPan() : 0.0;
1125
1126 gPan->Move(pos);
1127 gPan->Set(pan);
1128 gPanCaptured->Move(pos);
1129 gPanCaptured->Set(pan);
1130
1131 auto slider = (captured ? gPanCaptured : gPan).get();
1132 slider->SetParent( pParent );
1133 return slider;
1134}
1135
1137{
1138 if (message.appearance)
1139 return;
1140 const wxPoint point{ 0, 0 };
1141 wxRect sliderRect;
1142 GetPanRect(point, sliderRect);
1143
1144 float defPos = 0.0;
1145 /* i18n-hint: Title of the Pan slider, used to move the sound left or right */
1146 gPan = std::make_unique<LWSlider>(nullptr, XO("Pan"),
1147 wxPoint(sliderRect.x, sliderRect.y),
1148 wxSize(sliderRect.width, sliderRect.height),
1149 PAN_SLIDER);
1150 gPan->SetDefaultValue(defPos);
1151
1152 gPanCaptured = std::make_unique<LWSlider>(nullptr, XO("Pan"),
1153 wxPoint(sliderRect.x, sliderRect.y),
1154 wxSize(sliderRect.width, sliderRect.height),
1155 PAN_SLIDER);
1156 gPanCaptured->SetDefaultValue(defPos);
1157}
1158
1161 return [](WaveTrack &track) {
1162 return std::make_shared<WaveTrackControls>( track.SharedPointer() );
1163 };
1164}
1165
1168 return [](WaveChannel &) {
1170 };
1171}
1172
#define DB_SLIDER
Definition: ASlider.h:33
#define PAN_SLIDER
Definition: ASlider.h:34
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
std::vector< TrackInfo::TCPLine > TCPLines
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
Track::Holder MixAndRender(const TrackIterRange< const WaveTrack > &trackRange, const Mixer::WarpOptions &warpOptions, const wxString &newTrackName, WaveTrackFactory *trackFactory, double rate, sampleFormat format, double startTime, double endTime)
Mixes together all input tracks, applying any envelopes, amplitude gain, panning, and real-time effec...
#define END_POPUP_MENU()
#define POPUP_MENU_SUB_MENU(stringId, classname, pUserData)
#define BEGIN_POPUP_MENU(HandlerClass)
#define POPUP_MENU_FN(memFn)
@ pdlgHideStopButton
wxFrame * FindProjectFrame(AudacityProject *project)
Get a pointer to the window associated with a project, or null if the given pointer is null,...
TranslatableString GetSampleFormatStr(sampleFormat format)
constexpr sampleFormat int16Sample
Definition: SampleFormat.h:43
constexpr sampleFormat floatSample
Definition: SampleFormat.h:45
constexpr sampleFormat int24Sample
Definition: SampleFormat.h:44
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
@ eIsCreating
Definition: ShuttleGui.h:37
const auto tracks
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
#define S(N)
Definition: ToChars.cpp:64
An AudacityException with no visible message.
@ kTrackInfoSliderExtra
Definition: ViewInfo.h:101
@ kTrackInfoSliderHeight
Definition: ViewInfo.h:98
DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetWaveTrackControls)
static const auto fn
@ OnRate16ID
@ OnRate352ID
@ On16BitID
@ OnMultiViewID
@ OnChannelRightID
@ OnRate176ID
@ OnRate88ID
@ OnFloatID
@ ChannelMenuID
@ OnRate192ID
@ OnSplitStereoMonoID
@ OnRate48ID
@ OnMergeStereoID
@ reserveDisplays
@ OnRate384ID
@ OnRate44ID
@ OnRateOtherID
@ OnSplitStereoID
@ lastDisplayId
@ On24BitID
@ OnRate22ID
@ OnSetDisplayId
@ OnChannelLeftID
@ OnChannelMonoID
@ OnRate8ID
@ OnRate96ID
@ OnSwapChannelsID
@ FirstAttachedItemId
@ OnRate11ID
EndSection()
AppendCheckItem("8000", OnRate8ID, XXO("8000 Hz"), POPUP_MENU_FN(OnRateChange), fn)
Append(Adapt< My >([](My &table) { return(WaveChannelSubViews::numFactories() > 1) ? std::make_unique< Entry >("MultiView", Entry::CheckItem, OnMultiViewID, XXO("&Multi-view"), POPUP_MENU_FN(OnMultiView), table, [](PopupMenuHandler &handler, wxMenu &menu, int id){ auto &table=static_cast< WaveTrackMenuTable & >(handler);auto &track=table.FindWaveTrack();const auto &view=WaveChannelView::GetFirst(track);menu.Check(id, view.GetMultiView());}) :nullptr;}))
static std::vector< WaveChannelSubViewType > AllTypes()
TrackInfo::TCPLine TCPLine
const int nRates
static const auto enableSplitStereo
BeginSection("SubViews")
int id
static int gRates[nRates]
WaveTrackTCPLines waveTrackTCPLines
AppendRadioItem("16Bit", On16BitID, GetSampleFormatStr(int16Sample), POPUP_MENU_FN(OnFormatChange), fn)
AppendItem("MakeStereo", OnMergeStereoID, XXO("Ma&ke Stereo Track"), POPUP_MENU_FN(OnMergeStereo), [](PopupMenuHandler &handler, wxMenu &menu, int id){ bool canMakeStereo=!isUnsafe(handler) &&isMono(handler);if(canMakeStereo) { AudacityProject &project=static_cast< WaveTrackMenuTable & >(handler).mpData->project;auto &tracks=TrackList::Get(project);auto &table=static_cast< WaveTrackMenuTable & >(handler);auto &track=table.FindWaveTrack();auto next= *++tracks.Find(&track);canMakeStereo=(next &&next->NChannels()==1 &&track_cast< WaveTrack * >(next));} menu.Enable(id, canMakeStereo);})
WaveTrackPopupMenuTable & GetWaveTrackMenuTable()
static const auto isMono
static const auto MenuPathStart
static const auto isUnsafe
static std::once_flag flag
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Formerly part of TrackPanel, this abstract base class has no special knowledge of Track objects and i...
Definition: CellularPanel.h:34
wxRect FindRect(const TrackPanelCell &cell)
static ChannelView & Get(Channel &channel)
bool GetMinimized() const
Definition: ChannelView.h:69
int GetHeight() const
void SetExpandedHeight(int height)
static size_t numFactories()
How many static factories have been registered with this specialization of Site.
Definition: ClientData.h:267
virtual std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *) override=0
std::shared_ptr< Track > FindTrack()
static UIHandlePtr HitTest(std::weak_ptr< EffectsButtonHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< Track > &pTrack)
Abstract base class used in importing a file.
static UIHandlePtr HitTest(std::weak_ptr< GainSliderHandle > &holder, const wxMouseState &state, const wxRect &rect, const std::shared_ptr< Track > &pTrack)
Lightweight version of ASlider. In other words it does not have a window permanently associated with ...
Definition: ASlider.h:64
static UIHandlePtr HitTest(std::weak_ptr< MuteButtonHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< Track > &pTrack)
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
static UIHandlePtr HitTest(std::weak_ptr< PanSliderHandle > &holder, const wxMouseState &state, const wxRect &rect, const std::shared_ptr< Track > &pTrack)
static const TCPLines & StaticWaveTCPLines()
const TranslatableString & Caption() const
ProgressDialog Class.
bool IsAudioActive() const
static ProjectAudioIO & Get(AudacityProject &project)
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static RealtimeEffectList & Get(AudacityProject &project)
size_t GetStatesCount() const noexcept
static RealtimeEffectManager & Get(AudacityProject &project)
bool IsActive() const noexcept
To be called only from main thread.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
static UIHandlePtr HitTest(std::weak_ptr< SoloButtonHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< Track > &pTrack)
static TrackIterRange< Track > Group(Track &track)
Definition: SyncLock.cpp:150
bool IsSyncLocked() const
Definition: SyncLock.cpp:44
static SyncLockState & Get(AudacityProject &project)
Definition: SyncLock.cpp:27
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
static TrackControls & Get(Track &track)
Track * Get()
Definition: TrackFocus.cpp:156
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
std::shared_ptr< Subclass > SharedPointer()
Definition: Track.h:146
const wxString & GetName() const
Name is always the same for all channels of a group.
Definition: Track.cpp:64
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
Can be thrown when user cancels operations, as with a progress dialog. Delayed handler does nothing.
Definition: UserException.h:17
void SetDisplay(Display display, bool exclusive=true)
static WaveChannelView & GetFirst(WaveTrack &wt)
Get the view of the first channel.
PopupMenuTable * GetMenuExtension(Track *pTrack) override
std::weak_ptr< MuteButtonHandle > mMuteHandle
static unsigned DefaultWaveTrackHeight()
~WaveTrackControls() override
std::weak_ptr< PanSliderHandle > mPanHandle
static void GetGainRect(const wxPoint &topLeft, wxRect &dest)
static void GetPanRect(const wxPoint &topLeft, wxRect &dest)
std::weak_ptr< EffectsButtonHandle > mEffectsHandle
const TCPLines & GetTCPLines() const override
std::weak_ptr< GainSliderHandle > mGainHandle
static LWSlider * PanSlider(CellularPanel &panel, const WaveTrack &wt)
std::weak_ptr< SoloButtonHandle > mSoloHandle
static void ReCreatePanSlider(struct ThemeChangeMessage)
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *pProject) override
static void ReCreateGainSlider(struct ThemeChangeMessage)
static LWSlider * GainSlider(CellularPanel &panel, const WaveTrack &wt)
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:3349
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
void SetRate(double newRate)
!brief Sets the new rate for the track without resampling it
Definition: WaveTrack.cpp:803
std::vector< Holder > SplitChannels()
Definition: WaveTrack.cpp:1041
sampleFormat GetSampleFormat() const override
Definition: WaveTrack.cpp:895
float GetPan() const
Definition: WaveTrack.cpp:837
void SetPan(float newPan)
Definition: WaveTrack.cpp:847
double GetEndTime() const override
Implement WideSampleSequence.
Definition: WaveTrack.cpp:2586
float GetGain() const
Definition: WaveTrack.cpp:819
double GetRate() const override
Definition: WaveTrack.cpp:798
auto Intervals()
Definition: WaveTrack.h:670
void SwapChannels()
Definition: WaveTrack.cpp:1060
size_t NChannels() const override
A constant property.
Definition: WaveTrack.cpp:532
auto GetChannel(size_t iChannel)
Definition: WaveTrack.h:258
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
double as_double() const
Definition: SampleCount.h:46
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
void SetName(const TranslatableString &title)
#define lrint(dbl)
Definition: float_cast.h:169
@ YesNo
Two buttons.
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:279
AUDACITY_DLL_API unsigned DefaultTrackHeight(const TCPLines &topLines)
AUDACITY_DLL_API void GetSliderHorizontalBounds(const wxPoint &topleft, wxRect &dest)
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
AUDACITY_DLL_API std::pair< int, int > CalcItemY(const TCPLines &lines, unsigned iItem)
Definition: TrackInfo.cpp:57
WAVE_TRACK_API sampleCount GetSequenceSamplesCount(const WaveTrack &track)
static float findValue(const float *spectrum, float bin0, float bin1, unsigned nBins, bool autocorrelation, int gain, int range)
void SliderDrawFunction(LWSlider *(*Selector)(const wxRect &sliderRect, const WaveTrack *t, bool captured, wxWindow *), wxDC *dc, const wxRect &rect, const Track *pTrack, wxWindow *pParent, bool captured, bool highlight)
std::function< int(WaveTrack &) > ValueFinder
void GainSliderDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
void PanSliderDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
PopupMenuTableEntry::InitFunction initFn(const ValueFinder &findValue)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
For defining overrides of the method.
MessageBoxOptions && ButtonStyle(Button style) &&
Definition: BasicUI.h:107
void OnFormatChange(wxCommandEvent &event)
void InitUserData(void *pUserData) override
Called before the menu items are appended.
PlayableTrackControls::InitMenuData * mpData
DECLARE_POPUP_MENU(FormatMenuTable)
static int IdOfFormat(sampleFormat format)
Converts a format enumeration to a wxWidgets menu item Id.
static FormatMenuTable & Instance()
Immutable structure is an argument to Mixer's constructor.
Definition: MixerOptions.h:56
std::function< void(PopupMenuHandler &handler, wxMenu &menu, int id) > InitFunction
void InitUserData(void *pUserData) override
Called before the menu items are appended.
DECLARE_POPUP_MENU(RateMenuTable)
void OnRateOther(wxCommandEvent &event)
static int IdOfRate(int rate)
Converts a sampling rate to a wxWidgets menu item id.
void SetRate(WaveTrack &track, double rate)
Sets the sample rate for a track.
static RateMenuTable & Instance()
PlayableTrackControls::InitMenuData * mpData
void OnRateChange(wxCommandEvent &event)
std::optional< PreferredSystemAppearance > appearance
Definition: Theme.h:111
Range between two TrackIters, usable in range-for statements, and with Visit member functions.
Definition: Track.h:682
static const std::vector< WaveChannelSubViewType > & All()
Discover all registered types.
void SplitStereo(bool stereo)
Splits stereo track into two mono tracks, preserving panning if stereo is set.
void OnSplitStereo(wxCommandEvent &event)
Split a stereo track into two tracks...
DECLARE_POPUP_MENU(WaveTrackMenuTable)
void InitUserData(void *pUserData) override
Called before the menu items are appended.
void OnSplitStereoMono(wxCommandEvent &event)
Split a stereo track into two mono tracks...
static WaveTrackMenuTable & Instance()
void OnSwapChannels(wxCommandEvent &event)
Swap the left and right channels of a stero track...
void OnMultiView(wxCommandEvent &event)
void OnSetDisplay(wxCommandEvent &event)
Set the Display mode based on the menu choice in the Track Menu.
void OnMergeStereo(wxCommandEvent &event)
Merge two tracks into one stereo track ??
WaveTrack & FindWaveTrack() const
PlayableTrackControls::InitMenuData * mpData