1 
2 
3 #include "../AdornedRulerPanel.h"
4 #include "../AudioIO.h"
5 #include "../CommonCommandFlags.h"
6 #include "DeviceManager.h"
7 #include "../LabelTrack.h"
8 #include "../Menus.h"
9 #include "Prefs.h"
10 #include "Project.h"
11 #include "../ProjectAudioIO.h"
12 #include "../ProjectAudioManager.h"
13 #include "../ProjectFileIO.h"
14 #include "../ProjectHistory.h"
15 #include "ProjectRate.h"
16 #include "../ProjectSettings.h"
17 #include "../ProjectWindows.h"
18 #include "../ProjectWindow.h"
19 #include "../ProjectManager.h"
20 #include "../SelectUtilities.h"
21 #include "../SoundActivatedRecord.h"
22 #include "../TimerRecordDialog.h"
23 #include "../TrackPanelAx.h"
24 #include "../TrackPanel.h"
25 #include "../UndoManager.h"
26 #include "../WaveClip.h"
27 #include "../prefs/RecordingPrefs.h"
28 #include "../prefs/TracksPrefs.h"
29 #include "../WaveTrack.h"
30 #include "ViewInfo.h"
31 #include "../commands/CommandContext.h"
32 #include "../commands/CommandManager.h"
33 #include "../toolbars/ControlToolBar.h"
34 #include "../toolbars/TranscriptionToolBar.h"
35 #include "../widgets/AudacityMessageBox.h"
36 #include "BasicUI.h"
37 #include "../widgets/ProgressDialog.h"
38 
39 #include <float.h>
40 #include <wx/app.h>
41 
42 // private helper classes and functions
43 namespace {
44 
PlayCurrentRegionAndWait(const CommandContext & context,bool newDefault=false,bool cutpreview=false)45 void PlayCurrentRegionAndWait(const CommandContext &context,
46                               bool newDefault = false,
47                               bool cutpreview = false)
48 {
49    auto &project = context.project;
50    auto &projectAudioManager = ProjectAudioManager::Get(project);
51 
52    const auto &playRegion = ViewInfo::Get(project).playRegion;
53    double t0 = playRegion.GetStart();
54    double t1 = playRegion.GetEnd();
55 
56    projectAudioManager.PlayCurrentRegion(newDefault, cutpreview);
57 
58    if (project.mBatchMode > 0 && t0 != t1 && !newDefault) {
59       wxYieldIfNeeded();
60 
61       /* i18n-hint: This title appears on a dialog that indicates the progress
62          in doing something.*/
63       ProgressDialog progress(XO("Progress"), XO("Playing"), pdlgHideCancelButton);
64       auto gAudioIO = AudioIO::Get();
65 
66       while (projectAudioManager.Playing()) {
67          ProgressResult result = progress.Update(gAudioIO->GetStreamTime() - t0, t1 - t0);
68          if (result != ProgressResult::Success) {
69             projectAudioManager.Stop();
70             if (result != ProgressResult::Stopped) {
71                context.Error(wxT("Playing interrupted"));
72             }
73             break;
74          }
75 
76          wxMilliSleep(100);
77          wxYieldIfNeeded();
78       }
79 
80       projectAudioManager.Stop();
81       wxYieldIfNeeded();
82    }
83 }
84 
PlayPlayRegionAndWait(const CommandContext & context,const SelectedRegion & selectedRegion,const AudioIOStartStreamOptions & options,PlayMode mode)85 void PlayPlayRegionAndWait(const CommandContext &context,
86                            const SelectedRegion &selectedRegion,
87                            const AudioIOStartStreamOptions &options,
88                            PlayMode mode)
89 {
90    auto &project = context.project;
91    auto &projectAudioManager = ProjectAudioManager::Get(project);
92 
93    double t0 = selectedRegion.t0();
94    double t1 = selectedRegion.t1();
95 
96    projectAudioManager.PlayPlayRegion(selectedRegion, options, mode);
97 
98    if (project.mBatchMode > 0) {
99       wxYieldIfNeeded();
100 
101       /* i18n-hint: This title appears on a dialog that indicates the progress
102          in doing something.*/
103       ProgressDialog progress(XO("Progress"), XO("Playing"), pdlgHideCancelButton);
104       auto gAudioIO = AudioIO::Get();
105 
106       while (projectAudioManager.Playing()) {
107          ProgressResult result = progress.Update(gAudioIO->GetStreamTime() - t0, t1 - t0);
108          if (result != ProgressResult::Success) {
109             projectAudioManager.Stop();
110             if (result != ProgressResult::Stopped) {
111                context.Error(wxT("Playing interrupted"));
112             }
113             break;
114          }
115 
116          wxMilliSleep(100);
117          wxYieldIfNeeded();
118       }
119 
120       projectAudioManager.Stop();
121       wxYieldIfNeeded();
122    }
123 }
124 
RecordAndWait(const CommandContext & context,bool altAppearance)125 void RecordAndWait(const CommandContext &context, bool altAppearance)
126 {
127    auto &project = context.project;
128    auto &projectAudioManager = ProjectAudioManager::Get(project);
129 
130    const auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
131    double t0 = selectedRegion.t0();
132    double t1 = selectedRegion.t1();
133 
134    projectAudioManager.OnRecord(altAppearance);
135 
136    if (project.mBatchMode > 0 && t1 != t0) {
137       wxYieldIfNeeded();
138 
139       /* i18n-hint: This title appears on a dialog that indicates the progress
140          in doing something.*/
141       ProgressDialog progress(XO("Progress"), XO("Recording"), pdlgHideCancelButton);
142       auto gAudioIO = AudioIO::Get();
143 
144       while (projectAudioManager.Recording()) {
145          ProgressResult result = progress.Update(gAudioIO->GetStreamTime() - t0, t1 - t0);
146          if (result != ProgressResult::Success) {
147             projectAudioManager.Stop();
148             if (result != ProgressResult::Stopped) {
149                context.Error(wxT("Recording interrupted"));
150             }
151             break;
152          }
153 
154          wxMilliSleep(100);
155          wxYieldIfNeeded();
156       }
157 
158       projectAudioManager.Stop();
159       wxYieldIfNeeded();
160    }
161 }
162 
163 // TODO: Should all these functions which involve
164 // the toolbar actually move into ControlToolBar?
165 
166 /// MakeReadyToPlay stops whatever is currently playing
167 /// and pops the play button up.  Then, if nothing is now
168 /// playing, it pushes the play button down and enables
169 /// the stop button.
MakeReadyToPlay(AudacityProject & project)170 bool MakeReadyToPlay(AudacityProject &project)
171 {
172    auto &toolbar = ControlToolBar::Get( project );
173    wxCommandEvent evt;
174 
175    // If this project is playing, stop playing
176    auto gAudioIO = AudioIOBase::Get();
177    if (gAudioIO->IsStreamActive(
178       ProjectAudioIO::Get( project ).GetAudioIOToken()
179    )) {
180       // Make momentary changes of button appearances
181       toolbar.SetPlay(false);        //Pops
182       toolbar.SetStop();         //Pushes stop down
183       toolbar.OnStop(evt);
184 
185       ::wxMilliSleep(100);
186    }
187 
188    // If it didn't stop playing quickly, or if some other
189    // project is playing, return
190    if (gAudioIO->IsBusy())
191       return false;
192 
193    return true;
194 }
195 
196 // Returns true if this project was stopped, otherwise false.
197 // (it may though have stopped another project playing)
DoStopPlaying(const CommandContext & context)198 bool DoStopPlaying(const CommandContext &context)
199 {
200    auto &project = context.project;
201    auto &projectAudioManager = ProjectAudioManager::Get(project);
202    auto gAudioIO = AudioIOBase::Get();
203    auto &toolbar = ControlToolBar::Get(project);
204    auto token = ProjectAudioIO::Get(project).GetAudioIOToken();
205 
206    //If this project is playing, stop playing, make sure everything is unpaused.
207    if (gAudioIO->IsStreamActive(token)) {
208       toolbar.SetStop();         //Pushes stop down
209       projectAudioManager.Stop();
210       // Playing project was stopped.  All done.
211       return true;
212    }
213 
214    // This project isn't playing.
215    // If some other project is playing, stop playing it
216    if (gAudioIO->IsStreamActive()) {
217 
218       //find out which project we need;
219       auto start = AllProjects{}.begin(), finish = AllProjects{}.end(),
220          iter = std::find_if(start, finish,
221             [&](const AllProjects::value_type &ptr) {
222          return gAudioIO->IsStreamActive(
223             ProjectAudioIO::Get(*ptr).GetAudioIOToken()); });
224 
225       //stop playing the other project
226       if (iter != finish) {
227          auto otherProject = *iter;
228          auto &otherToolbar = ControlToolBar::Get(*otherProject);
229          auto &otherProjectAudioManager =
230             ProjectAudioManager::Get(*otherProject);
231          otherToolbar.SetStop();         //Pushes stop down
232          otherProjectAudioManager.Stop();
233       }
234    }
235    return false;
236 }
237 
DoStartPlaying(const CommandContext & context,bool newDefault=false)238 void DoStartPlaying(const CommandContext &context, bool newDefault = false)
239 {
240    auto &project = context.project;
241    auto &projectAudioManager = ProjectAudioManager::Get(project);
242    auto gAudioIO = AudioIOBase::Get();
243    //play the front project
244    if (!gAudioIO->IsBusy()) {
245       //Otherwise, start playing (assuming audio I/O isn't busy)
246 
247       // Will automatically set mLastPlayMode
248       PlayCurrentRegionAndWait(context, newDefault);
249    }
250 }
251 
DoMoveToLabel(AudacityProject & project,bool next)252 void DoMoveToLabel(AudacityProject &project, bool next)
253 {
254    auto &tracks = TrackList::Get( project );
255    auto &trackFocus = TrackFocus::Get( project );
256    auto &window = ProjectWindow::Get( project );
257    auto &projectAudioManager = ProjectAudioManager::Get(project);
258 
259    // Find the number of label tracks, and ptr to last track found
260    auto trackRange = tracks.Any<LabelTrack>();
261    auto lt = *trackRange.rbegin();
262    auto nLabelTrack = trackRange.size();
263 
264    if (nLabelTrack == 0 ) {
265       trackFocus.MessageForScreenReader(XO("no label track"));
266    }
267    else if (nLabelTrack > 1) {
268       // find first label track, if any, starting at the focused track
269       lt =
270          *tracks.Find(trackFocus.Get()).Filter<LabelTrack>();
271       if (!lt)
272          trackFocus.MessageForScreenReader(
273             XO("no label track at or below focused track"));
274    }
275 
276    // If there is a single label track, or there is a label track at or below
277    // the focused track
278    auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
279    if (lt) {
280       int i;
281       if (next)
282          i = lt->FindNextLabel(selectedRegion);
283       else
284          i = lt->FindPrevLabel(selectedRegion);
285 
286       if (i >= 0) {
287          const LabelStruct* label = lt->GetLabel(i);
288          bool newDefault = projectAudioManager.Looping();
289          if (ProjectAudioIO::Get( project ).IsAudioActive()) {
290             DoStopPlaying(project);
291             selectedRegion = label->selectedRegion;
292             window.RedrawProject();
293             DoStartPlaying(project, newDefault);
294          }
295          else {
296             selectedRegion = label->selectedRegion;
297             window.ScrollIntoView(selectedRegion.t0());
298             window.RedrawProject();
299          }
300          /* i18n-hint:
301             String is replaced by the name of a label,
302             first number gives the position of that label in a sequence
303             of labels,
304             and the last number is the total number of labels in the sequence.
305          */
306          auto message = XO("%s %d of %d")
307             .Format( label->title, i + 1, lt->GetNumLabels() );
308          trackFocus.MessageForScreenReader(message);
309       }
310       else {
311          trackFocus.MessageForScreenReader(XO("no labels in label track"));
312       }
313    }
314 }
315 
IsLoopingEnabled(const AudacityProject & project)316 bool IsLoopingEnabled(const AudacityProject& project)
317 {
318    auto &playRegion = ViewInfo::Get(project).playRegion;
319     return playRegion.Active();
320 }
321 
322 }
323 
324 // Strings for menu items and also for dialog titles
325 /* i18n-hint Sets a starting point for looping play */
326 static const auto SetLoopInTitle = XXO("Set Loop &In");
327 /* i18n-hint Sets an ending point for looping play */
328 static const auto SetLoopOutTitle = XXO("Set Loop &Out");
329 
330 // Menu handler functions
331 
332 namespace TransportActions {
333 
334 struct Handler : CommandHandlerObject {
335 
336 // This plays (once, with fixed bounds) OR Stops audio.  It's a toggle.
337 // The default binding for Shift+SPACE.
OnPlayOnceOrStopTransportActions::Handler338 void OnPlayOnceOrStop(const CommandContext &context)
339 {
340    if (DoStopPlaying(context.project))
341       return;
342    DoStartPlaying(context.project);
343 }
344 
OnPlayStopSelectTransportActions::Handler345 void OnPlayStopSelect(const CommandContext &context)
346 {
347    ProjectAudioManager::Get( context.project ).DoPlayStopSelect();
348 }
349 
350 // This plays (looping, maybe adjusting the loop) OR Stops audio.  It's a toggle.
351 // The default binding for SPACE
OnPlayDefaultOrStopTransportActions::Handler352 void OnPlayDefaultOrStop(const CommandContext &context)
353 {
354    auto &project = context.project;
355    if (DoStopPlaying(project))
356       return;
357 
358    if( !MakeReadyToPlay(project) )
359       return;
360 
361    // Now play in a loop
362    // Will automatically set mLastPlayMode
363    PlayCurrentRegionAndWait(context, true);
364 }
365 
OnPauseTransportActions::Handler366 void OnPause(const CommandContext &context)
367 {
368    ProjectAudioManager::Get( context.project ).OnPause();
369 }
370 
OnRecordTransportActions::Handler371 void OnRecord(const CommandContext &context)
372 {
373    RecordAndWait(context, false);
374 }
375 
376 // If first choice is record same track 2nd choice is record NEW track
377 // and vice versa.
OnRecord2ndChoiceTransportActions::Handler378 void OnRecord2ndChoice(const CommandContext &context)
379 {
380    RecordAndWait(context, true);
381 }
382 
OnTimerRecordTransportActions::Handler383 void OnTimerRecord(const CommandContext &context)
384 {
385    auto &project = context.project;
386    const auto &settings = ProjectSettings::Get( project );
387    auto &undoManager = UndoManager::Get( project );
388    auto &window = ProjectWindow::Get( project );
389 
390    // MY: Due to improvements in how Timer Recording saves and/or exports
391    // it is now safer to disable Timer Recording when there is more than
392    // one open project.
393    if (AllProjects{}.size() > 1) {
394       AudacityMessageBox(
395          XO(
396 "Timer Recording cannot be used with more than one open project.\n\nPlease close any additional projects and try again."),
397          XO("Timer Recording"),
398          wxICON_INFORMATION | wxOK);
399       return;
400    }
401 
402    // MY: If the project has unsaved changes then we no longer allow access
403    // to Timer Recording.  This decision has been taken as the safest approach
404    // preventing issues surrounding "dirty" projects when Automatic Save/Export
405    // is used in Timer Recording.
406    if ((undoManager.UnsavedChanges()) &&
407        (TrackList::Get( project ).Any() || settings.EmptyCanBeDirty())) {
408       AudacityMessageBox(
409          XO(
410 "Timer Recording cannot be used while you have unsaved changes.\n\nPlease save or close this project and try again."),
411          XO("Timer Recording"),
412          wxICON_INFORMATION | wxOK);
413       return;
414    }
415 
416    // We check the selected tracks to see if there is enough of them to accommodate
417    // all input channels and all of them have the same sampling rate.
418    // Those checks will be later performed by recording function anyway,
419    // but we want to warn the user about potential problems from the very start.
420    const auto selectedTracks{ GetPropertiesOfSelected(project) };
421    const int rateOfSelected{ selectedTracks.rateOfSelected };
422    const int numberOfSelected{ selectedTracks.numberOfSelected };
423    const bool allSameRate{ selectedTracks.allSameRate };
424 
425    if (!allSameRate) {
426       AudacityMessageBox(XO("The tracks selected "
427          "for recording must all have the same sampling rate"),
428          XO("Mismatched Sampling Rates"),
429          wxICON_ERROR | wxCENTRE);
430 
431       return;
432    }
433 
434    const auto existingTracks{ ProjectAudioManager::ChooseExistingRecordingTracks(project, true, rateOfSelected) };
435    if (existingTracks.empty()) {
436       if (numberOfSelected > 0 && rateOfSelected !=
437           ProjectRate::Get(project).GetRate()) {
438          AudacityMessageBox(XO(
439             "Too few tracks are selected for recording at this sample rate.\n"
440             "(Audacity requires two channels at the same sample rate for\n"
441             "each stereo track)"),
442             XO("Too Few Compatible Tracks Selected"),
443             wxICON_ERROR | wxCENTRE);
444 
445          return;
446       }
447    }
448 
449    // We use this variable to display "Current Project" in the Timer Recording
450    // save project field
451    bool bProjectSaved = !ProjectFileIO::Get( project ).IsModified();
452 
453    //we break the prompting and waiting dialogs into two sections
454    //because they both give the user a chance to click cancel
455    //and therefore remove the newly inserted track.
456 
457    TimerRecordDialog dialog(
458       &window, project, bProjectSaved); /* parent, project, project saved? */
459    int modalResult = dialog.ShowModal();
460    if (modalResult == wxID_CANCEL)
461    {
462       // Cancelled before recording - don't need to do anything.
463    }
464    else
465    {
466       // Bug #2382
467       // Allow recording to start at current cursor position.
468       #if 0
469       // Timer Record should not record into a selection.
470       bool bPreferNewTrack;
471       gPrefs->Read("/GUI/PreferNewTrackRecord",&bPreferNewTrack, false);
472       if (bPreferNewTrack) {
473          window.Rewind(false);
474       } else {
475          window.SkipEnd(false);
476       }
477       #endif
478 
479       int iTimerRecordingOutcome = dialog.RunWaitDialog();
480       switch (iTimerRecordingOutcome) {
481       case POST_TIMER_RECORD_CANCEL_WAIT:
482          // Canceled on the wait dialog
483          ProjectHistory::Get( project ).RollbackState();
484          break;
485       case POST_TIMER_RECORD_CANCEL:
486          // RunWaitDialog() shows the "wait for start" as well as "recording"
487          // dialog if it returned POST_TIMER_RECORD_CANCEL it means the user
488          // cancelled while the recording, so throw out the fresh track.
489          // However, we can't undo it here because the PushState() is called in TrackPanel::OnTimer(),
490          // which is blocked by this function.
491          // so instead we mark a flag to undo it there.
492          ProjectAudioManager::Get( project ).SetTimerRecordCancelled();
493          break;
494       case POST_TIMER_RECORD_NOTHING:
495          // No action required
496          break;
497       case POST_TIMER_RECORD_CLOSE:
498          wxTheApp->CallAfter( []{
499             // Simulate the application Exit menu item
500             wxCommandEvent evt{ wxEVT_MENU, wxID_EXIT };
501             wxTheApp->AddPendingEvent( evt );
502          } );
503          ProjectManager::Get(project).SetSkipSavePrompt(true);
504          break;
505 
506 #ifdef __WINDOWS__
507       case POST_TIMER_RECORD_RESTART:
508          // Restart System
509          ProjectManager::Get(project).SetSkipSavePrompt(true);
510          system("shutdown /r /f /t 30");
511          break;
512       case POST_TIMER_RECORD_SHUTDOWN:
513          // Shutdown System
514          ProjectManager::Get(project).SetSkipSavePrompt(true);
515          system("shutdown /s /f /t 30");
516          break;
517 #endif
518       }
519    }
520 }
521 
522 #ifdef EXPERIMENTAL_PUNCH_AND_ROLL
OnPunchAndRollTransportActions::Handler523 void OnPunchAndRoll(const CommandContext &context)
524 {
525    AudacityProject &project = context.project;
526    auto &viewInfo = ViewInfo::Get( project );
527 
528    static const auto url =
529       wxT("Punch_and_Roll_Record#Using_Punch_and_Roll_Record");
530 
531    auto gAudioIO = AudioIO::Get();
532    if (gAudioIO->IsBusy())
533       return;
534 
535    // Ignore all but left edge of the selection.
536    viewInfo.selectedRegion.collapseToT0();
537    double t1 = std::max(0.0, viewInfo.selectedRegion.t1());
538 
539    // Checking the selected tracks: making sure they all have the same rate
540    const auto selectedTracks{ GetPropertiesOfSelected(project) };
541    const int rateOfSelected{ selectedTracks.rateOfSelected };
542    const bool allSameRate{ selectedTracks.allSameRate };
543 
544    if (!allSameRate) {
545       AudacityMessageBox(XO("The tracks selected "
546          "for recording must all have the same sampling rate"),
547          XO("Mismatched Sampling Rates"),
548          wxICON_ERROR | wxCENTRE);
549 
550       return;
551    }
552 
553    // Decide which tracks to record in.
554    auto tracks =
555       ProjectAudioManager::ChooseExistingRecordingTracks(project, true, rateOfSelected);
556    if (tracks.empty()) {
557       auto recordingChannels =
558          std::max(0, AudioIORecordChannels.Read());
559       auto message =
560          (recordingChannels == 1)
561          ? XO("Please select in a mono track.")
562          : (recordingChannels == 2)
563          ? XO("Please select in a stereo track or two mono tracks.")
564          : XO("Please select at least %d channels.").Format( recordingChannels );
565       BasicUI::ShowErrorDialog( *ProjectFramePlacement(&project),
566          XO("Error"), message, url);
567       return;
568    }
569 
570    // Delete the portion of the target tracks right of the selection, but first,
571    // remember a part of the deletion for crossfading with the new recording.
572    // We may also adjust the starting point leftward if it is too close to the
573    // end of the track, so that at least some nonzero crossfade data can be
574    // taken.
575    PRCrossfadeData crossfadeData;
576    const double crossFadeDuration = std::max(0.0,
577       gPrefs->Read(AUDIO_ROLL_CROSSFADE_KEY, DEFAULT_ROLL_CROSSFADE_MS)
578          / 1000.0
579    );
580 
581    // The test for t1 == 0.0 stops punch and roll deleting everything where the
582    // selection is at zero.  There wouldn't be any cued audio to play in
583    // that case, so a normal record, not a punch and roll, is called for.
584    bool error = (t1 == 0.0);
585 
586    double newt1 = t1;
587    for (const auto &wt : tracks) {
588       sampleCount testSample(floor(t1 * wt->GetRate()));
589       auto clip = wt->GetClipAtSample(testSample);
590       if (!clip)
591          // Bug 1890 (an enhancement request)
592          // Try again, a little to the left.
593          // Subtract 10 to allow a selection exactly at or slightly after the
594          // end time
595          clip = wt->GetClipAtSample(testSample - 10);
596       if (!clip)
597          error = true;
598       else {
599          // May adjust t1 left
600          // Let's ignore the possibility of a clip even shorter than the
601          // crossfade duration!
602          newt1 = std::min(newt1, clip->GetPlayEndTime() - crossFadeDuration);
603       }
604    }
605 
606    if (error) {
607       auto message = XO("Please select a time within a clip.");
608       BasicUI::ShowErrorDialog(
609          *ProjectFramePlacement(&project), XO("Error"), message, url);
610       return;
611    }
612 
613    t1 = newt1;
614    for (const auto &wt : tracks) {
615       const auto endTime = wt->GetEndTime();
616       const auto duration =
617          std::max(0.0, std::min(crossFadeDuration, endTime - t1));
618       const size_t getLen = floor(duration * wt->GetRate());
619       std::vector<float> data(getLen);
620       if (getLen > 0) {
621          float *const samples = data.data();
622          const sampleCount pos = wt->TimeToLongSamples(t1);
623          wt->GetFloats(samples, pos, getLen);
624       }
625       crossfadeData.push_back(std::move(data));
626    }
627 
628    // Change tracks only after passing the error checks above
629    for (const auto &wt : tracks) {
630       wt->Clear(t1, wt->GetEndTime());
631    }
632 
633    // Choose the tracks for playback.
634    TransportTracks transportTracks;
635    const auto duplex = ProjectAudioManager::UseDuplex();
636    if (duplex)
637       // play all
638       transportTracks =
639          ProjectAudioManager::GetAllPlaybackTracks(
640             TrackList::Get( project ), false, true);
641    else
642       // play recording tracks only
643       std::copy(tracks.begin(), tracks.end(),
644          std::back_inserter(transportTracks.playbackTracks));
645 
646    // Unlike with the usual recording, a track may be chosen both for playback
647    // and recording.
648    transportTracks.captureTracks = std::move(tracks);
649 
650    // Try to start recording
651    auto options = DefaultPlayOptions( project );
652    options.rate = rateOfSelected;
653    options.preRoll = std::max(0L,
654       gPrefs->Read(AUDIO_PRE_ROLL_KEY, DEFAULT_PRE_ROLL_SECONDS));
655    options.pCrossfadeData = &crossfadeData;
656    bool success = ProjectAudioManager::Get( project ).DoRecord(project,
657       transportTracks,
658       t1, DBL_MAX,
659       false, // altAppearance
660       options);
661 
662    if (success)
663       // Undo state will get pushed elsewhere, when record finishes
664       ;
665    else
666       // Roll back the deletions
667       ProjectHistory::Get( project ).RollbackState();
668 }
669 #endif
670 
OnTogglePlayRegionTransportActions::Handler671 void OnTogglePlayRegion(const CommandContext &context)
672 {
673    SelectUtilities::TogglePlayRegion(context.project);
674 }
675 
OnClearPlayRegionTransportActions::Handler676 void OnClearPlayRegion(const CommandContext &context)
677 {
678    SelectUtilities::ClearPlayRegion(context.project);
679 }
680 
OnSetPlayRegionInTransportActions::Handler681 void OnSetPlayRegionIn(const CommandContext &context)
682 {
683    auto &project = context.project;
684    auto &playRegion = ViewInfo::Get(project).playRegion;
685    if (!playRegion.Active())
686       SelectUtilities::ActivatePlayRegion(project);
687    SelectUtilities::OnSetRegion(project,
688       true, false, SetLoopInTitle.Stripped());
689 }
690 
691 
OnSetPlayRegionOutTransportActions::Handler692 void OnSetPlayRegionOut(const CommandContext &context)
693 {
694    auto &project = context.project;
695    auto &playRegion = ViewInfo::Get(project).playRegion;
696    if (!playRegion.Active())
697       SelectUtilities::ActivatePlayRegion(project);
698    SelectUtilities::OnSetRegion(project,
699       false, false, SetLoopOutTitle.Stripped());
700 }
701 
OnSetPlayRegionToSelectionTransportActions::Handler702 void OnSetPlayRegionToSelection(const CommandContext &context)
703 {
704    SelectUtilities::SetPlayRegionToSelection(context.project);
705 }
706 
OnRescanDevicesTransportActions::Handler707 void OnRescanDevices(const CommandContext &WXUNUSED(context) )
708 {
709    DeviceManager::Instance()->Rescan();
710 }
711 
OnSoundActivatedTransportActions::Handler712 void OnSoundActivated(const CommandContext &context)
713 {
714    AudacityProject &project = context.project;
715 
716    SoundActivatedRecordDialog dialog( &GetProjectFrame( project ) /* parent */ );
717    dialog.ShowModal();
718 }
719 
OnToggleSoundActivatedTransportActions::Handler720 void OnToggleSoundActivated(const CommandContext &WXUNUSED(context) )
721 {
722    bool pause;
723    gPrefs->Read(wxT("/AudioIO/SoundActivatedRecord"), &pause, false);
724    gPrefs->Write(wxT("/AudioIO/SoundActivatedRecord"), !pause);
725    gPrefs->Flush();
726    MenuManager::ModifyAllProjectToolbarMenus();
727 }
728 
OnTogglePinnedHeadTransportActions::Handler729 void OnTogglePinnedHead(const CommandContext &context)
730 {
731    AdornedRulerPanel::Get( context.project ).TogglePinnedHead();
732 }
733 
OnTogglePlayRecordingTransportActions::Handler734 void OnTogglePlayRecording(const CommandContext &WXUNUSED(context) )
735 {
736    bool Duplex;
737 #ifdef EXPERIMENTAL_DA
738    gPrefs->Read(wxT("/AudioIO/Duplex"), &Duplex, false);
739 #else
740    gPrefs->Read(wxT("/AudioIO/Duplex"), &Duplex, true);
741 #endif
742    gPrefs->Write(wxT("/AudioIO/Duplex"), !Duplex);
743    gPrefs->Flush();
744    MenuManager::ModifyAllProjectToolbarMenus();
745 }
746 
OnToggleSWPlaythroughTransportActions::Handler747 void OnToggleSWPlaythrough(const CommandContext &WXUNUSED(context) )
748 {
749    bool SWPlaythrough;
750    gPrefs->Read(wxT("/AudioIO/SWPlaythrough"), &SWPlaythrough, false);
751    gPrefs->Write(wxT("/AudioIO/SWPlaythrough"), !SWPlaythrough);
752    gPrefs->Flush();
753    MenuManager::ModifyAllProjectToolbarMenus();
754 }
755 
756 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
OnToggleAutomatedInputLevelAdjustmentTransportActions::Handler757 void OnToggleAutomatedInputLevelAdjustment(
758    const CommandContext &WXUNUSED(context) )
759 {
760    bool AVEnabled;
761    gPrefs->Read(
762       wxT("/AudioIO/AutomatedInputLevelAdjustment"), &AVEnabled, false);
763    gPrefs->Write(wxT("/AudioIO/AutomatedInputLevelAdjustment"), !AVEnabled);
764    gPrefs->Flush();
765    MenuManager::ModifyAllProjectToolbarMenus();
766 }
767 #endif
768 
OnStopTransportActions::Handler769 void OnStop(const CommandContext &context)
770 {
771    ProjectAudioManager::Get( context.project ).Stop();
772 }
773 
OnPlayOneSecondTransportActions::Handler774 void OnPlayOneSecond(const CommandContext &context)
775 {
776    auto &project = context.project;
777    if( !MakeReadyToPlay(project) )
778       return;
779 
780    auto &trackPanel = TrackPanel::Get( project );
781    auto options = DefaultPlayOptions( project );
782 
783    double pos = trackPanel.GetMostRecentXPos();
784    PlayPlayRegionAndWait(context, SelectedRegion(pos - 0.5, pos + 0.5),
785       options, PlayMode::oneSecondPlay);
786 }
787 
788 /// The idea for this function (and first implementation)
789 /// was from Juhana Sadeharju.  The function plays the
790 /// sound between the current mouse position and the
791 /// nearest selection boundary.  This gives four possible
792 /// play regions depending on where the current mouse
793 /// position is relative to the left and right boundaries
794 /// of the selection region.
OnPlayToSelectionTransportActions::Handler795 void OnPlayToSelection(const CommandContext &context)
796 {
797    auto &project = context.project;
798 
799    if( !MakeReadyToPlay(project) )
800       return;
801 
802    auto &trackPanel = TrackPanel::Get( project );
803    auto &viewInfo = ViewInfo::Get( project );
804    const auto &selectedRegion = viewInfo.selectedRegion;
805 
806    double pos = trackPanel.GetMostRecentXPos();
807 
808    double t0,t1;
809    // check region between pointer and the nearest selection edge
810    if (fabs(pos - selectedRegion.t0()) <
811        fabs(pos - selectedRegion.t1())) {
812       t0 = t1 = selectedRegion.t0();
813    } else {
814       t0 = t1 = selectedRegion.t1();
815    }
816    if( pos < t1)
817       t0=pos;
818    else
819       t1=pos;
820 
821    // JKC: oneSecondPlay mode disables auto scrolling
822    // On balance I think we should always do this in this function
823    // since you are typically interested in the sound EXACTLY
824    // where the cursor is.
825    // TODO: have 'playing attributes' such as 'with_autoscroll'
826    // rather than modes, since that's how we're now using the modes.
827 
828    // An alternative, commented out below, is to disable autoscroll
829    // only when playing a short region, less than or equal to a second.
830 //   mLastPlayMode = ((t1-t0) > 1.0) ? normalPlay : oneSecondPlay;
831 
832    auto playOptions = DefaultPlayOptions( project );
833 
834    PlayPlayRegionAndWait(context, SelectedRegion(t0, t1),
835       playOptions, PlayMode::oneSecondPlay);
836 }
837 
838 // The next 4 functions provide a limited version of the
839 // functionality of OnPlayToSelection() for keyboard users
840 
OnPlayBeforeSelectionStartTransportActions::Handler841 void OnPlayBeforeSelectionStart(const CommandContext &context)
842 {
843    auto &project = context.project;
844 
845    if( !MakeReadyToPlay(project) )
846       return;
847 
848    auto &viewInfo = ViewInfo::Get( project );
849    const auto &selectedRegion = viewInfo.selectedRegion;
850 
851    double t0 = selectedRegion.t0();
852    double beforeLen;
853    gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
854 
855    auto playOptions = DefaultPlayOptions( project );
856 
857    PlayPlayRegionAndWait(context, SelectedRegion(t0 - beforeLen, t0),
858       playOptions, PlayMode::oneSecondPlay);
859 }
860 
OnPlayAfterSelectionStartTransportActions::Handler861 void OnPlayAfterSelectionStart(const CommandContext &context)
862 {
863    auto &project = context.project;
864 
865    if( !MakeReadyToPlay(project) )
866       return;
867 
868    auto &viewInfo = ViewInfo::Get( project );
869    const auto &selectedRegion = viewInfo.selectedRegion;
870 
871    double t0 = selectedRegion.t0();
872    double t1 = selectedRegion.t1();
873    double afterLen;
874    gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
875 
876    auto playOptions = DefaultPlayOptions( project );
877 
878    if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
879       PlayPlayRegionAndWait(context, SelectedRegion(t0, t1),
880          playOptions, PlayMode::oneSecondPlay);
881    else
882       PlayPlayRegionAndWait(context, SelectedRegion(t0, t0 + afterLen),
883          playOptions, PlayMode::oneSecondPlay);
884 }
885 
OnPlayBeforeSelectionEndTransportActions::Handler886 void OnPlayBeforeSelectionEnd(const CommandContext &context)
887 {
888    auto &project = context.project;
889 
890    if( !MakeReadyToPlay(project) )
891       return;
892 
893    auto &viewInfo = ViewInfo::Get( project );
894    const auto &selectedRegion = viewInfo.selectedRegion;
895 
896    double t0 = selectedRegion.t0();
897    double t1 = selectedRegion.t1();
898    double beforeLen;
899    gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
900 
901    auto playOptions = DefaultPlayOptions( project );
902 
903    if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
904       PlayPlayRegionAndWait(context, SelectedRegion(t0, t1),
905          playOptions, PlayMode::oneSecondPlay);
906    else
907       PlayPlayRegionAndWait(context, SelectedRegion(t1 - beforeLen, t1),
908          playOptions, PlayMode::oneSecondPlay);
909 }
910 
OnPlayAfterSelectionEndTransportActions::Handler911 void OnPlayAfterSelectionEnd(const CommandContext &context)
912 {
913    auto &project = context.project;
914 
915    if( !MakeReadyToPlay(project) )
916       return;
917 
918    auto &viewInfo = ViewInfo::Get( project );
919    const auto &selectedRegion = viewInfo.selectedRegion;
920 
921    double t1 = selectedRegion.t1();
922    double afterLen;
923    gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
924 
925    auto playOptions = DefaultPlayOptions( project );
926 
927    PlayPlayRegionAndWait(context, SelectedRegion(t1, t1 + afterLen),
928       playOptions, PlayMode::oneSecondPlay);
929 }
930 
OnPlayBeforeAndAfterSelectionStartTransportActions::Handler931 void OnPlayBeforeAndAfterSelectionStart
932 (const CommandContext &context)
933 {
934    auto &project = context.project;
935 
936    if (!MakeReadyToPlay(project))
937       return;
938 
939    auto &viewInfo = ViewInfo::Get( project );
940    const auto &selectedRegion = viewInfo.selectedRegion;
941 
942    double t0 = selectedRegion.t0();
943    double t1 = selectedRegion.t1();
944    double beforeLen;
945    gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
946    double afterLen;
947    gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
948 
949    auto playOptions = DefaultPlayOptions( project );
950 
951    if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
952       PlayPlayRegionAndWait(context, SelectedRegion(t0 - beforeLen, t1),
953          playOptions, PlayMode::oneSecondPlay);
954    else
955       PlayPlayRegionAndWait(context, SelectedRegion(t0 - beforeLen, t0 + afterLen),
956          playOptions, PlayMode::oneSecondPlay);
957 }
958 
OnPlayBeforeAndAfterSelectionEndTransportActions::Handler959 void OnPlayBeforeAndAfterSelectionEnd
960 (const CommandContext &context)
961 {
962    auto &project = context.project;
963 
964    if (!MakeReadyToPlay(project))
965       return;
966 
967    auto &viewInfo = ViewInfo::Get( project );
968    const auto &selectedRegion = viewInfo.selectedRegion;
969 
970    double t0 = selectedRegion.t0();
971    double t1 = selectedRegion.t1();
972    double beforeLen;
973    gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
974    double afterLen;
975    gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
976 
977    auto playOptions = DefaultPlayOptions( project );
978 
979    if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
980       PlayPlayRegionAndWait(context, SelectedRegion(t0, t1 + afterLen),
981          playOptions, PlayMode::oneSecondPlay);
982    else
983       PlayPlayRegionAndWait(context, SelectedRegion(t1 - beforeLen, t1 + afterLen),
984          playOptions, PlayMode::oneSecondPlay);
985 }
986 
OnPlayCutPreviewTransportActions::Handler987 void OnPlayCutPreview(const CommandContext &context)
988 {
989    auto &project = context.project;
990 
991    if ( !MakeReadyToPlay(project) )
992       return;
993 
994    // Play with cut preview
995    PlayCurrentRegionAndWait(context, false, true);
996 }
997 
OnPlayAtSpeedTransportActions::Handler998 void OnPlayAtSpeed(const CommandContext &context)
999 {
1000    auto &project = context.project;
1001    auto tb = &TranscriptionToolBar::Get( project );
1002 
1003    if (tb) {
1004       tb->PlayAtSpeed(false, false);
1005    }
1006 }
1007 
OnPlayAtSpeedLoopedTransportActions::Handler1008 void OnPlayAtSpeedLooped(const CommandContext &context)
1009 {
1010    auto &project = context.project;
1011    auto tb = &TranscriptionToolBar::Get( project );
1012 
1013    if (tb) {
1014       tb->PlayAtSpeed(true, false);
1015    }
1016 }
1017 
OnPlayAtSpeedCutPreviewTransportActions::Handler1018 void OnPlayAtSpeedCutPreview(const CommandContext &context)
1019 {
1020    auto &project = context.project;
1021    auto tb = &TranscriptionToolBar::Get( project );
1022 
1023    if (tb) {
1024       tb->PlayAtSpeed(false, true);
1025    }
1026 }
1027 
OnSetPlaySpeedTransportActions::Handler1028 void OnSetPlaySpeed(const CommandContext &context)
1029 {
1030    auto &project = context.project;
1031    auto tb = &TranscriptionToolBar::Get( project );
1032 
1033    if (tb) {
1034       tb->ShowPlaySpeedDialog();
1035    }
1036 }
1037 
OnPlaySpeedIncTransportActions::Handler1038 void OnPlaySpeedInc(const CommandContext &context)
1039 {
1040    auto &project = context.project;
1041    auto tb = &TranscriptionToolBar::Get( project );
1042 
1043    if (tb) {
1044       tb->AdjustPlaySpeed(0.1f);
1045    }
1046 }
1047 
OnPlaySpeedDecTransportActions::Handler1048 void OnPlaySpeedDec(const CommandContext &context)
1049 {
1050    auto &project = context.project;
1051    auto tb = &TranscriptionToolBar::Get( project );
1052 
1053    if (tb) {
1054       tb->AdjustPlaySpeed(-0.1f);
1055    }
1056 }
1057 
OnMoveToPrevLabelTransportActions::Handler1058 void OnMoveToPrevLabel(const CommandContext &context)
1059 {
1060    auto &project = context.project;
1061    DoMoveToLabel(project, false);
1062 }
1063 
OnMoveToNextLabelTransportActions::Handler1064 void OnMoveToNextLabel(const CommandContext &context)
1065 {
1066    auto &project = context.project;
1067    DoMoveToLabel(project, true);
1068 }
1069 
1070 #if 0
1071 // Legacy handlers, not used as of version 2.3.0
1072 void OnStopSelect(const CommandContext &context)
1073 {
1074    auto &project = context.project;
1075    auto &history = ProjectHistory::Get( project );
1076    auto &viewInfo = project.GetViewInfo();
1077    auto &selectedRegion = viewInfo.selectedRegion;
1078 
1079    auto gAudioIO = AudioIOBase::Get();
1080    if (gAudioIO->IsStreamActive()) {
1081       selectedRegion.setT0(gAudioIO->GetStreamTime(), false);
1082       ProjectAudioManager::Get( project ).Stop();
1083       history.ModifyState(false);           // without bWantsAutoSave
1084    }
1085 }
1086 #endif
1087 
1088 }; // struct Handler
1089 
1090 } // namespace
1091 
findCommandHandler(AudacityProject &)1092 static CommandHandlerObject &findCommandHandler(AudacityProject &) {
1093    // Handler is not stateful.  Doesn't need a factory registered with
1094    // AudacityProject.
1095    static TransportActions::Handler instance;
1096    return instance;
1097 };
1098 
1099 // Menu definitions
1100 
1101 #define FN(X) (& TransportActions::Handler :: X)
1102 
1103 // Under /MenuBar
1104 namespace {
1105 using namespace MenuTable;
TransportMenu()1106 BaseItemSharedPtr TransportMenu()
1107 {
1108    using Options = CommandManager::Options;
1109 
1110    static const auto CanStopFlags = AudioIONotBusyFlag() | CanStopAudioStreamFlag();
1111 
1112    static BaseItemSharedPtr menu{
1113    ( FinderScope{ findCommandHandler },
1114    /* i18n-hint: 'Transport' is the name given to the set of controls that
1115       play, record, pause etc. */
1116    Menu( wxT("Transport"), XXO("Tra&nsport"),
1117       Section( "Basic",
1118          Menu( wxT("Play"), XXO("Pl&aying"),
1119             /* i18n-hint: (verb) Start or Stop audio playback*/
1120             Command( wxT("DefaultPlayStop"), XXO("Pl&ay/Stop"), FN(OnPlayDefaultOrStop),
1121                CanStopAudioStreamFlag(), wxT("Space") ),
1122             Command( wxT("PlayStopSelect"), XXO("Play/Stop and &Set Cursor"),
1123                FN(OnPlayStopSelect), CanStopAudioStreamFlag(), wxT("X") ),
1124             Command( wxT("OncePlayStop"), XXO("Play &Once/Stop"), FN(OnPlayOnceOrStop),
1125                CanStopAudioStreamFlag(), wxT("Shift+Space") ),
1126             Command( wxT("Pause"), XXO("&Pause"), FN(OnPause),
1127                CanStopAudioStreamFlag(), wxT("P") )
1128          ),
1129 
1130          Menu( wxT("Record"), XXO("&Recording"),
1131             /* i18n-hint: (verb)*/
1132             Command( wxT("Record1stChoice"), XXO("&Record"), FN(OnRecord),
1133                CanStopFlags, wxT("R") ),
1134 
1135             // The OnRecord2ndChoice function is: if normal record records beside,
1136             // it records below, if normal record records below, it records beside.
1137             // TODO: Do 'the right thing' with other options like TimerRecord.
1138             // Delayed evaluation in case gPrefs is not yet defined
1139             [](const AudacityProject&)
1140             { return Command( wxT("Record2ndChoice"),
1141                // Our first choice is bound to R (by default)
1142                // and gets the prime position.
1143                // We supply the name for the 'other one' here.
1144                // It should be bound to Shift+R
1145                (gPrefs->ReadBool("/GUI/PreferNewTrackRecord", false)
1146                 ? XXO("&Append Record") : XXO("Record &New Track")),
1147                FN(OnRecord2ndChoice), CanStopFlags,
1148                wxT("Shift+R"),
1149                findCommandHandler
1150             ); },
1151 
1152             Command( wxT("TimerRecord"), XXO("&Timer Record..."),
1153                FN(OnTimerRecord), CanStopFlags, wxT("Shift+T") ),
1154 
1155    #ifdef EXPERIMENTAL_PUNCH_AND_ROLL
1156             Command( wxT("PunchAndRoll"), XXO("Punch and Rol&l Record"),
1157                FN(OnPunchAndRoll),
1158                WaveTracksExistFlag() | AudioIONotBusyFlag(), wxT("Shift+D") ),
1159    #endif
1160 
1161             // JKC: I decided to duplicate this between play and record,
1162             // rather than put it at the top level.
1163             // CommandManger::AddItem can now cope with simple duplicated items.
1164             // PRL:  caution, this is a duplicated command name!
1165             Command( wxT("Pause"), XXO("&Pause"), FN(OnPause),
1166                CanStopAudioStreamFlag(), wxT("P") )
1167          )
1168       ),
1169 
1170       Section( "Other",
1171          Section( "",
1172             Menu( wxT("PlayRegion"), XXO("&Looping"),
1173                Command( wxT("TogglePlayRegion"), LoopToggleText,
1174                   FN(OnTogglePlayRegion), AlwaysEnabledFlag,
1175                      Options(L"L").CheckTest([](const AudacityProject& project){
1176                          return IsLoopingEnabled(project);
1177                      } )),
1178                Command( wxT("ClearPlayRegion"), XXO("&Clear Loop"),
1179                   FN(OnClearPlayRegion), AlwaysEnabledFlag, L"Shift+Alt+L" ),
1180                Command( wxT("SetPlayRegionToSelection"),
1181                   XXO("&Set Loop to Selection"),
1182                   FN(OnSetPlayRegionToSelection), AlwaysEnabledFlag,
1183                      L"Shift+L" ),
1184                Command( wxT("SetPlayRegionIn"),
1185                   SetLoopInTitle,
1186                   FN(OnSetPlayRegionIn), AlwaysEnabledFlag ),
1187                Command( wxT("SetPlayRegionOut"),
1188                   SetLoopOutTitle,
1189                   FN(OnSetPlayRegionOut), AlwaysEnabledFlag )
1190             )
1191          ),
1192 
1193          Command( wxT("RescanDevices"), XXO("R&escan Audio Devices"),
1194             FN(OnRescanDevices), AudioIONotBusyFlag() | CanStopAudioStreamFlag() ),
1195 
1196          Menu( wxT("Options"), XXO("Transport &Options"),
1197             Section( "",
1198                // Sound Activated recording options
1199                Command( wxT("SoundActivationLevel"),
1200                   XXO("Sound Activation Le&vel..."), FN(OnSoundActivated),
1201                   AudioIONotBusyFlag() | CanStopAudioStreamFlag() ),
1202                Command( wxT("SoundActivation"),
1203                   XXO("Sound A&ctivated Recording (on/off)"),
1204                   FN(OnToggleSoundActivated),
1205                   AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
1206                   Options{}.CheckTest(wxT("/AudioIO/SoundActivatedRecord"), false) )
1207             ),
1208 
1209             Section( "",
1210                Command( wxT("PinnedHead"), XXO("Pinned Play/Record &Head (on/off)"),
1211                   FN(OnTogglePinnedHead),
1212                   // Switching of scrolling on and off is permitted
1213                   // even during transport
1214                   AlwaysEnabledFlag,
1215                   Options{}.CheckTest([](const AudacityProject&){
1216                      return TracksPrefs::GetPinnedHeadPreference(); } ) ),
1217 
1218                Command( wxT("Overdub"), XXO("&Overdub (on/off)"),
1219                   FN(OnTogglePlayRecording),
1220                   AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
1221                   Options{}.CheckTest( wxT("/AudioIO/Duplex"),
1222 #ifdef EXPERIMENTAL_DA
1223                      false
1224 #else
1225                      true
1226 #endif
1227                   ) ),
1228                Command( wxT("SWPlaythrough"), XXO("So&ftware Playthrough (on/off)"),
1229                   FN(OnToggleSWPlaythrough),
1230                   AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
1231                   Options{}.CheckTest( wxT("/AudioIO/SWPlaythrough"), false ) )
1232 
1233 
1234       #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
1235                ,
1236                Command( wxT("AutomatedInputLevelAdjustmentOnOff"),
1237                   XXO("A&utomated Recording Level Adjustment (on/off)"),
1238                   FN(OnToggleAutomatedInputLevelAdjustment),
1239                   AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
1240                   Options{}.CheckTest(
1241                      wxT("/AudioIO/AutomatedInputLevelAdjustment"), false ) )
1242       #endif
1243             )
1244          )
1245       )
1246    ) ) };
1247    return menu;
1248 }
1249 
1250 AttachedItem sAttachment1{
1251    wxT(""),
1252    Shared( TransportMenu() )
1253 };
1254 
ExtraTransportMenu()1255 BaseItemSharedPtr ExtraTransportMenu()
1256 {
1257    static BaseItemSharedPtr menu{
1258    ( FinderScope{ findCommandHandler },
1259    Menu( wxT("Transport"), XXO("T&ransport"),
1260       // PlayStop is already in the menus.
1261       /* i18n-hint: (verb) Start playing audio*/
1262       Command( wxT("Play"), XXO("Pl&ay Once"), FN(OnPlayOnceOrStop),
1263          WaveTracksExistFlag() | AudioIONotBusyFlag() ),
1264       /* i18n-hint: (verb) Stop playing audio*/
1265       Command( wxT("Stop"), XXO("Sto&p"), FN(OnStop),
1266          AudioIOBusyFlag() | CanStopAudioStreamFlag() ),
1267       Command( wxT("PlayOneSec"), XXO("Play &One Second"), FN(OnPlayOneSecond),
1268          CaptureNotBusyFlag(), wxT("1") ),
1269       Command( wxT("PlayToSelection"), XXO("Play to &Selection"),
1270          FN(OnPlayToSelection),
1271          CaptureNotBusyFlag(), wxT("B") ),
1272       Command( wxT("PlayBeforeSelectionStart"),
1273          XXO("Play &Before Selection Start"), FN(OnPlayBeforeSelectionStart),
1274          CaptureNotBusyFlag(), wxT("Shift+F5") ),
1275       Command( wxT("PlayAfterSelectionStart"),
1276          XXO("Play Af&ter Selection Start"), FN(OnPlayAfterSelectionStart),
1277          CaptureNotBusyFlag(), wxT("Shift+F6") ),
1278       Command( wxT("PlayBeforeSelectionEnd"),
1279          XXO("Play Be&fore Selection End"), FN(OnPlayBeforeSelectionEnd),
1280          CaptureNotBusyFlag(), wxT("Shift+F7") ),
1281       Command( wxT("PlayAfterSelectionEnd"),
1282          XXO("Play Aft&er Selection End"), FN(OnPlayAfterSelectionEnd),
1283          CaptureNotBusyFlag(), wxT("Shift+F8") ),
1284       Command( wxT("PlayBeforeAndAfterSelectionStart"),
1285          XXO("Play Before a&nd After Selection Start"),
1286          FN(OnPlayBeforeAndAfterSelectionStart), CaptureNotBusyFlag(),
1287          wxT("Ctrl+Shift+F5") ),
1288       Command( wxT("PlayBeforeAndAfterSelectionEnd"),
1289          XXO("Play Before an&d After Selection End"),
1290          FN(OnPlayBeforeAndAfterSelectionEnd), CaptureNotBusyFlag(),
1291          wxT("Ctrl+Shift+F7") ),
1292       Command( wxT("PlayCutPreview"), XXO("Play C&ut Preview"),
1293          FN(OnPlayCutPreview),
1294          CaptureNotBusyFlag(), wxT("C") )
1295    ) ) };
1296    return menu;
1297 }
1298 
1299 AttachedItem sAttachment2{
1300    wxT("Optional/Extra/Part1"),
1301    Shared( ExtraTransportMenu() )
1302 };
1303 
ExtraPlayAtSpeedMenu()1304 BaseItemSharedPtr ExtraPlayAtSpeedMenu()
1305 {
1306    static BaseItemSharedPtr menu{
1307    ( FinderScope{ findCommandHandler },
1308    Menu( wxT("PlayAtSpeed"), XXO("&Play-at-Speed"),
1309       /* i18n-hint: 'Normal Play-at-Speed' doesn't loop or cut preview. */
1310       Command( wxT("PlayAtSpeedLooped"), XXO("&Play-at-Speed"),
1311          FN(OnPlayAtSpeedLooped), CaptureNotBusyFlag() ),
1312       Command( wxT("PlayAtSpeed"), XXO("Play-at-Speed &Once"),
1313          FN(OnPlayAtSpeed), CaptureNotBusyFlag() ),
1314       Command( wxT("PlayAtSpeedCutPreview"), XXO("Play C&ut Preview-at-Speed"),
1315          FN(OnPlayAtSpeedCutPreview), CaptureNotBusyFlag() ),
1316       Command( wxT("SetPlaySpeed"), XXO("Ad&just Playback Speed..."),
1317          FN(OnSetPlaySpeed), CaptureNotBusyFlag() ),
1318       Command( wxT("PlaySpeedInc"), XXO("&Increase Playback Speed"),
1319          FN(OnPlaySpeedInc), CaptureNotBusyFlag() ),
1320       Command( wxT("PlaySpeedDec"), XXO("&Decrease Playback Speed"),
1321          FN(OnPlaySpeedDec), CaptureNotBusyFlag() )
1322    ) ) };
1323    return menu;
1324 }
1325 
1326 AttachedItem sAttachment3{
1327    wxT("Optional/Extra/Part1"),
1328    Shared( ExtraPlayAtSpeedMenu() )
1329 };
1330 
ExtraSelectionItems()1331 BaseItemSharedPtr ExtraSelectionItems()
1332 {
1333    using Options = CommandManager::Options;
1334    static BaseItemSharedPtr items{
1335    (FinderScope{ findCommandHandler },
1336    Items(wxT("MoveToLabel"),
1337       Command(wxT("MoveToPrevLabel"), XXO("Move to Pre&vious Label"),
1338          FN(OnMoveToPrevLabel),
1339          CaptureNotBusyFlag() | TrackPanelHasFocus(), wxT("Alt+Left")),
1340       Command(wxT("MoveToNextLabel"), XXO("Move to Ne&xt Label"),
1341          FN(OnMoveToNextLabel),
1342          CaptureNotBusyFlag() | TrackPanelHasFocus(), wxT("Alt+Right"))
1343    )) };
1344    return items;
1345 }
1346 
1347 AttachedItem sAttachment4{
1348   { wxT("Optional/Extra/Part1/Select"), { OrderingHint::End, {} } },
1349   Shared(ExtraSelectionItems())
1350 };
1351 
1352 }
1353 
1354 #undef FN
1355