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