audacia/src/menus/TransportMenus.cpp

1087 lines
35 KiB
C++

#include "../Audacity.h"
#include "../Experimental.h"
#include "../AdornedRulerPanel.h"
#include "../AudioIO.h"
#include "../CommonCommandFlags.h"
#include "../DeviceManager.h"
#include "../LabelTrack.h"
#include "../Menus.h"
#include "../Prefs.h"
#include "../Project.h"
#include "../ProjectAudioIO.h"
#include "../ProjectAudioManager.h"
#include "../ProjectFileIO.h"
#include "../ProjectHistory.h"
#include "../ProjectSettings.h"
#include "../ProjectWindow.h"
#include "../SoundActivatedRecord.h"
#include "../TimerRecordDialog.h"
#include "../TrackPanelAx.h"
#include "../TrackPanel.h"
#include "../UndoManager.h"
#include "../WaveClip.h"
#include "../prefs/RecordingPrefs.h"
#include "../WaveTrack.h"
#include "../ViewInfo.h"
#include "../commands/CommandContext.h"
#include "../commands/CommandManager.h"
#include "../toolbars/ControlToolBar.h"
#include "../toolbars/TranscriptionToolBar.h"
#include "../tracks/ui/Scrubbing.h"
#include "../widgets/AudacityMessageBox.h"
#include "../widgets/ErrorDialog.h"
#include <float.h>
// private helper classes and functions
namespace {
// TODO: Should all these functions which involve
// the toolbar actually move into ControlToolBar?
/// MakeReadyToPlay stops whatever is currently playing
/// and pops the play button up. Then, if nothing is now
/// playing, it pushes the play button down and enables
/// the stop button.
bool MakeReadyToPlay(AudacityProject &project)
{
auto &toolbar = ControlToolBar::Get( project );
wxCommandEvent evt;
// If this project is playing, stop playing
auto gAudioIO = AudioIOBase::Get();
if (gAudioIO->IsStreamActive(
ProjectAudioIO::Get( project ).GetAudioIOToken()
)) {
// Make momentary changes of button appearances
toolbar.SetPlay(false); //Pops
toolbar.SetStop(); //Pushes stop down
toolbar.OnStop(evt);
::wxMilliSleep(100);
}
// If it didn't stop playing quickly, or if some other
// project is playing, return
if (gAudioIO->IsBusy())
return false;
return true;
}
// Post Timer Recording Actions
// Ensure this matches the enum in TimerRecordDialog.cpp
enum {
POST_TIMER_RECORD_STOPPED = -3,
POST_TIMER_RECORD_CANCEL_WAIT,
POST_TIMER_RECORD_CANCEL,
POST_TIMER_RECORD_NOTHING,
POST_TIMER_RECORD_CLOSE,
POST_TIMER_RECORD_RESTART,
POST_TIMER_RECORD_SHUTDOWN
};
void DoPlayStop(const CommandContext &context)
{
auto &project = context.project;
auto &projectAudioManager = ProjectAudioManager::Get( project );
auto &toolbar = ControlToolBar::Get( project );
auto &window = ProjectWindow::Get( project );
auto token = ProjectAudioIO::Get( project ).GetAudioIOToken();
//If this project is playing, stop playing, make sure everything is unpaused.
auto gAudioIO = AudioIOBase::Get();
if (gAudioIO->IsStreamActive(token)) {
toolbar.SetStop(); //Pushes stop down
projectAudioManager.Stop();
}
else if (gAudioIO->IsStreamActive()) {
// If this project isn't playing, but another one is, stop playing the
// old and start the NEW.
//find out which project we need;
auto start = AllProjects{}.begin(), finish = AllProjects{}.end(),
iter = std::find_if( start, finish,
[&]( const AllProjects::value_type &ptr ){
return gAudioIO->IsStreamActive(
ProjectAudioIO::Get( *ptr ).GetAudioIOToken()); } );
//stop playing the other project
if(iter != finish) {
auto otherProject = *iter;
auto &otherToolbar = ControlToolBar::Get( *otherProject );
auto &otherProjectAudioManager =
ProjectAudioManager::Get( *otherProject );
otherToolbar.SetStop(); //Pushes stop down
otherProjectAudioManager.Stop();
}
//play the front project
if (!gAudioIO->IsBusy()) {
//Otherwise, start playing (assuming audio I/O isn't busy)
// Will automatically set mLastPlayMode
projectAudioManager.PlayCurrentRegion(false);
}
}
else if (!gAudioIO->IsBusy()) {
//Otherwise, start playing (assuming audio I/O isn't busy)
// Will automatically set mLastPlayMode
projectAudioManager.PlayCurrentRegion(false);
}
}
void DoMoveToLabel(AudacityProject &project, bool next)
{
auto &tracks = TrackList::Get( project );
auto &trackFocus = TrackFocus::Get( project );
auto &window = ProjectWindow::Get( project );
// Find the number of label tracks, and ptr to last track found
auto trackRange = tracks.Any<LabelTrack>();
auto lt = *trackRange.rbegin();
auto nLabelTrack = trackRange.size();
if (nLabelTrack == 0 ) {
trackFocus.MessageForScreenReader(_("no label track"));
}
else if (nLabelTrack > 1) {
// find first label track, if any, starting at the focused track
lt =
*tracks.Find(trackFocus.Get()).Filter<LabelTrack>();
if (!lt)
trackFocus.MessageForScreenReader(
_("no label track at or below focused track"));
}
// If there is a single label track, or there is a label track at or below
// the focused track
auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
if (lt) {
int i;
if (next)
i = lt->FindNextLabel(selectedRegion);
else
i = lt->FindPrevLabel(selectedRegion);
if (i >= 0) {
const LabelStruct* label = lt->GetLabel(i);
if (ProjectAudioIO::Get( project ).IsAudioActive()) {
DoPlayStop(project); // stop
selectedRegion = label->selectedRegion;
window.RedrawProject();
DoPlayStop(project); // play
}
else {
selectedRegion = label->selectedRegion;
window.ScrollIntoView(selectedRegion.t0());
window.RedrawProject();
}
wxString message;
message.Printf(
wxT("%s %d of %d"), label->title, i + 1, lt->GetNumLabels() );
trackFocus.MessageForScreenReader(message);
}
else {
trackFocus.MessageForScreenReader(_("no labels in label track"));
}
}
}
}
// Menu handler functions
namespace TransportActions {
struct Handler : CommandHandlerObject {
void OnPlayStop(const CommandContext &context)
{
DoPlayStop( context.project );
}
void OnPlayStopSelect(const CommandContext &context)
{
ProjectAudioManager::Get( context.project ).DoPlayStopSelect();
}
void OnPlayLooped(const CommandContext &context)
{
auto &project = context.project;
if( !MakeReadyToPlay(project) )
return;
// Now play in a loop
// Will automatically set mLastPlayMode
ProjectAudioManager::Get( project ).PlayCurrentRegion(true);
}
void OnPause(const CommandContext &context)
{
ProjectAudioManager::Get( context.project ).OnPause();
}
void OnRecord(const CommandContext &context)
{
ProjectAudioManager::Get( context.project ).OnRecord(false);
}
// If first choice is record same track 2nd choice is record NEW track
// and vice versa.
void OnRecord2ndChoice(const CommandContext &context)
{
auto &project = context.project;
ProjectAudioManager::Get( project ).OnRecord(true);
}
void OnTimerRecord(const CommandContext &context)
{
auto &project = context.project;
const auto &settings = ProjectSettings::Get( project );
auto &undoManager = UndoManager::Get( project );
auto &window = ProjectWindow::Get( project );
// MY: Due to improvements in how Timer Recording saves and/or exports
// it is now safer to disable Timer Recording when there is more than
// one open project.
if (AllProjects{}.size() > 1) {
AudacityMessageBox(_("Timer Recording cannot be used with more than one open project.\n\nPlease close any additional projects and try again."),
_("Timer Recording"),
wxICON_INFORMATION | wxOK);
return;
}
// MY: If the project has unsaved changes then we no longer allow access
// to Timer Recording. This decision has been taken as the safest approach
// preventing issues surrounding "dirty" projects when Automatic Save/Export
// is used in Timer Recording.
if ((undoManager.UnsavedChanges()) &&
(TrackList::Get( project ).Any() || settings.EmptyCanBeDirty())) {
AudacityMessageBox(_("Timer Recording cannot be used while you have unsaved changes.\n\nPlease save or close this project and try again."),
_("Timer Recording"),
wxICON_INFORMATION | wxOK);
return;
}
// We use this variable to display "Current Project" in the Timer Recording
// save project field
bool bProjectSaved = ProjectFileIO::Get( project ).IsProjectSaved();
//we break the prompting and waiting dialogs into two sections
//because they both give the user a chance to click cancel
//and therefore remove the newly inserted track.
TimerRecordDialog dialog(
&window, bProjectSaved); /* parent, project saved? */
int modalResult = dialog.ShowModal();
if (modalResult == wxID_CANCEL)
{
// Cancelled before recording - don't need to do anything.
}
else
{
// Timer Record should not record into a selection.
bool bPreferNewTrack;
gPrefs->Read("/GUI/PreferNewTrackRecord",&bPreferNewTrack, false);
if (bPreferNewTrack) {
window.Rewind(false);
} else {
window.SkipEnd(false);
}
int iTimerRecordingOutcome = dialog.RunWaitDialog();
switch (iTimerRecordingOutcome) {
case POST_TIMER_RECORD_CANCEL_WAIT:
// Canceled on the wait dialog
ProjectHistory::Get( project ).RollbackState();
break;
case POST_TIMER_RECORD_CANCEL:
// RunWaitDialog() shows the "wait for start" as well as "recording"
// dialog if it returned POST_TIMER_RECORD_CANCEL it means the user
// cancelled while the recording, so throw out the fresh track.
// However, we can't undo it here because the PushState() is called in TrackPanel::OnTimer(),
// which is blocked by this function.
// so instead we mark a flag to undo it there.
ProjectAudioManager::Get( project ).SetTimerRecordCancelled();
break;
case POST_TIMER_RECORD_NOTHING:
// No action required
break;
case POST_TIMER_RECORD_CLOSE:
wxTheApp->CallAfter( []{
// Simulate the application Exit menu item
wxCommandEvent evt{ wxEVT_MENU, wxID_EXIT };
wxTheApp->AddPendingEvent( evt );
} );
break;
case POST_TIMER_RECORD_RESTART:
// Restart System
#ifdef __WINDOWS__
system("shutdown /r /f /t 30");
#endif
break;
case POST_TIMER_RECORD_SHUTDOWN:
// Shutdown System
#ifdef __WINDOWS__
system("shutdown /s /f /t 30");
#endif
break;
}
}
}
#ifdef EXPERIMENTAL_PUNCH_AND_ROLL
void OnPunchAndRoll(const CommandContext &context)
{
AudacityProject &project = context.project;
auto &viewInfo = ViewInfo::Get( project );
auto &window = GetProjectFrame( project );
static const auto url =
wxT("Punch_and_Roll_Record#Using_Punch_and_Roll_Record");
auto gAudioIO = AudioIO::Get();
if (gAudioIO->IsBusy())
return;
// Ignore all but left edge of the selection.
viewInfo.selectedRegion.collapseToT0();
double t1 = std::max(0.0, viewInfo.selectedRegion.t1());
// Decide which tracks to record in.
auto tracks =
ProjectAudioManager::ChooseExistingRecordingTracks(project, true);
if (tracks.empty()) {
int recordingChannels =
std::max(0L, gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2));
auto message =
(recordingChannels == 1)
? _("Please select in a mono track.")
: (recordingChannels == 2)
? _("Please select in a stereo track.")
: wxString::Format(
_("Please select at least %d channels."), recordingChannels);
ShowErrorDialog(&window, _("Error"), message, url);
return;
}
// Delete the portion of the target tracks right of the selection, but first,
// remember a part of the deletion for crossfading with the new recording.
// We may also adjust the starting point leftward if it is too close to the
// end of the track, so that at least some nonzero crossfade data can be
// taken.
PRCrossfadeData crossfadeData;
const double crossFadeDuration = std::max(0.0,
gPrefs->Read(AUDIO_ROLL_CROSSFADE_KEY, DEFAULT_ROLL_CROSSFADE_MS)
/ 1000.0
);
// The test for t1 == 0.0 stops punch and roll deleting everything where the
// selection is at zero. There wouldn't be any cued audio to play in
// that case, so a normal record, not a punch and roll, is called for.
bool error = (t1 == 0.0);
double newt1 = t1;
for (const auto &wt : tracks) {
sampleCount testSample(floor(t1 * wt->GetRate()));
auto clip = wt->GetClipAtSample(testSample);
if (!clip)
// Bug 1890 (an enhancement request)
// Try again, a little to the left.
// Subtract 10 to allow a selection exactly at or slightly after the
// end time
clip = wt->GetClipAtSample(testSample - 10);
if (!clip)
error = true;
else {
// May adjust t1 left
// Let's ignore the possibilty of a clip even shorter than the
// crossfade duration!
newt1 = std::min(newt1, clip->GetEndTime() - crossFadeDuration);
}
}
if (error) {
auto message = _("Please select a time within a clip.");
ShowErrorDialog( &window, _("Error"), message, url);
return;
}
t1 = newt1;
for (const auto &wt : tracks) {
const auto endTime = wt->GetEndTime();
const auto duration =
std::max(0.0, std::min(crossFadeDuration, endTime - t1));
const size_t getLen = floor(duration * wt->GetRate());
std::vector<float> data(getLen);
if (getLen > 0) {
float *const samples = data.data();
const sampleCount pos = wt->TimeToLongSamples(t1);
wt->Get((samplePtr)samples, floatSample, pos, getLen);
}
crossfadeData.push_back(std::move(data));
}
// Change tracks only after passing the error checks above
for (const auto &wt : tracks) {
wt->Clear(t1, wt->GetEndTime());
}
// Choose the tracks for playback.
TransportTracks transportTracks;
const auto duplex = ProjectAudioManager::UseDuplex();
if (duplex)
// play all
transportTracks =
ProjectAudioManager::GetAllPlaybackTracks(
TrackList::Get( project ), false, true);
else
// play recording tracks only
std::copy(tracks.begin(), tracks.end(),
std::back_inserter(transportTracks.playbackTracks));
// Unlike with the usual recording, a track may be chosen both for playback
// and recording.
transportTracks.captureTracks = std::move(tracks);
// Try to start recording
auto options = DefaultPlayOptions( project );
options.preRoll = std::max(0L,
gPrefs->Read(AUDIO_PRE_ROLL_KEY, DEFAULT_PRE_ROLL_SECONDS));
options.pCrossfadeData = &crossfadeData;
bool success = ProjectAudioManager::Get( project ).DoRecord(project,
transportTracks,
t1, DBL_MAX,
false, // altAppearance
options);
if (success)
// Undo state will get pushed elsewhere, when record finishes
;
else
// Roll back the deletions
ProjectHistory::Get( project ).RollbackState();
}
#endif
void OnLockPlayRegion(const CommandContext &context)
{
AdornedRulerPanel::Get( context.project ).LockPlayRegion();
}
void OnUnlockPlayRegion(const CommandContext &context)
{
AdornedRulerPanel::Get( context.project ).UnlockPlayRegion();
}
void OnRescanDevices(const CommandContext &WXUNUSED(context) )
{
DeviceManager::Instance()->Rescan();
}
void OnSoundActivated(const CommandContext &context)
{
AudacityProject &project = context.project;
SoundActivatedRecordDialog dialog( &GetProjectFrame( project ) /* parent */ );
dialog.ShowModal();
}
void OnToggleSoundActivated(const CommandContext &WXUNUSED(context) )
{
bool pause;
gPrefs->Read(wxT("/AudioIO/SoundActivatedRecord"), &pause, false);
gPrefs->Write(wxT("/AudioIO/SoundActivatedRecord"), !pause);
gPrefs->Flush();
MenuManager::ModifyAllProjectToolbarMenus();
}
void OnTogglePinnedHead(const CommandContext &context)
{
AdornedRulerPanel::Get( context.project ).TogglePinnedHead();
}
void OnTogglePlayRecording(const CommandContext &WXUNUSED(context) )
{
bool Duplex;
#ifdef EXPERIMENTAL_DA
gPrefs->Read(wxT("/AudioIO/Duplex"), &Duplex, false);
#else
gPrefs->Read(wxT("/AudioIO/Duplex"), &Duplex, true);
#endif
gPrefs->Write(wxT("/AudioIO/Duplex"), !Duplex);
gPrefs->Flush();
MenuManager::ModifyAllProjectToolbarMenus();
}
void OnToggleSWPlaythrough(const CommandContext &WXUNUSED(context) )
{
bool SWPlaythrough;
gPrefs->Read(wxT("/AudioIO/SWPlaythrough"), &SWPlaythrough, false);
gPrefs->Write(wxT("/AudioIO/SWPlaythrough"), !SWPlaythrough);
gPrefs->Flush();
MenuManager::ModifyAllProjectToolbarMenus();
}
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
void OnToggleAutomatedInputLevelAdjustment(
const CommandContext &WXUNUSED(context) )
{
bool AVEnabled;
gPrefs->Read(
wxT("/AudioIO/AutomatedInputLevelAdjustment"), &AVEnabled, false);
gPrefs->Write(wxT("/AudioIO/AutomatedInputLevelAdjustment"), !AVEnabled);
gPrefs->Flush();
MenuManager::ModifyAllProjectToolbarMenus();
}
#endif
void OnStop(const CommandContext &context)
{
ProjectAudioManager::Get( context.project ).Stop();
}
void OnPlayOneSecond(const CommandContext &context)
{
auto &project = context.project;
if( !MakeReadyToPlay(project) )
return;
auto &trackPanel = TrackPanel::Get( project );
auto options = DefaultPlayOptions( project );
double pos = trackPanel.GetMostRecentXPos();
ProjectAudioManager::Get( project ).PlayPlayRegion(
SelectedRegion(pos - 0.5, pos + 0.5), options,
PlayMode::oneSecondPlay);
}
/// The idea for this function (and first implementation)
/// was from Juhana Sadeharju. The function plays the
/// sound between the current mouse position and the
/// nearest selection boundary. This gives four possible
/// play regions depending on where the current mouse
/// position is relative to the left and right boundaries
/// of the selection region.
void OnPlayToSelection(const CommandContext &context)
{
auto &project = context.project;
if( !MakeReadyToPlay(project) )
return;
auto &trackPanel = TrackPanel::Get( project );
auto &viewInfo = ViewInfo::Get( project );
const auto &selectedRegion = viewInfo.selectedRegion;
double pos = trackPanel.GetMostRecentXPos();
double t0,t1;
// check region between pointer and the nearest selection edge
if (fabs(pos - selectedRegion.t0()) <
fabs(pos - selectedRegion.t1())) {
t0 = t1 = selectedRegion.t0();
} else {
t0 = t1 = selectedRegion.t1();
}
if( pos < t1)
t0=pos;
else
t1=pos;
// JKC: oneSecondPlay mode disables auto scrolling
// On balance I think we should always do this in this function
// since you are typically interested in the sound EXACTLY
// where the cursor is.
// TODO: have 'playing attributes' such as 'with_autoscroll'
// rather than modes, since that's how we're now using the modes.
// An alternative, commented out below, is to disable autoscroll
// only when playing a short region, less than or equal to a second.
// mLastPlayMode = ((t1-t0) > 1.0) ? normalPlay : oneSecondPlay;
auto playOptions = DefaultPlayOptions( project );
ProjectAudioManager::Get( project ).PlayPlayRegion(
SelectedRegion(t0, t1), playOptions, PlayMode::oneSecondPlay);
}
// The next 4 functions provide a limited version of the
// functionality of OnPlayToSelection() for keyboard users
void OnPlayBeforeSelectionStart(const CommandContext &context)
{
auto &project = context.project;
if( !MakeReadyToPlay(project) )
return;
auto &viewInfo = ViewInfo::Get( project );
const auto &selectedRegion = viewInfo.selectedRegion;
double t0 = selectedRegion.t0();
double beforeLen;
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
auto playOptions = DefaultPlayOptions( project );
ProjectAudioManager::Get( project ).PlayPlayRegion(
SelectedRegion(t0 - beforeLen, t0), playOptions, PlayMode::oneSecondPlay);
}
void OnPlayAfterSelectionStart(const CommandContext &context)
{
auto &project = context.project;
if( !MakeReadyToPlay(project) )
return;
auto &viewInfo = ViewInfo::Get( project );
const auto &selectedRegion = viewInfo.selectedRegion;
double t0 = selectedRegion.t0();
double t1 = selectedRegion.t1();
double afterLen;
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
auto &projectAudioManager = ProjectAudioManager::Get( project );
auto playOptions = DefaultPlayOptions( project );
if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
projectAudioManager.PlayPlayRegion(
SelectedRegion(t0, t1), playOptions, PlayMode::oneSecondPlay);
else
projectAudioManager.PlayPlayRegion(
SelectedRegion(t0, t0 + afterLen), playOptions,
PlayMode::oneSecondPlay);
}
void OnPlayBeforeSelectionEnd(const CommandContext &context)
{
auto &project = context.project;
if( !MakeReadyToPlay(project) )
return;
auto &viewInfo = ViewInfo::Get( project );
const auto &selectedRegion = viewInfo.selectedRegion;
double t0 = selectedRegion.t0();
double t1 = selectedRegion.t1();
double beforeLen;
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
auto &projectAudioManager = ProjectAudioManager::Get( project );
auto playOptions = DefaultPlayOptions( project );
if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
projectAudioManager.PlayPlayRegion(
SelectedRegion(t0, t1), playOptions, PlayMode::oneSecondPlay);
else
projectAudioManager.PlayPlayRegion(
SelectedRegion(t1 - beforeLen, t1), playOptions,
PlayMode::oneSecondPlay);
}
void OnPlayAfterSelectionEnd(const CommandContext &context)
{
auto &project = context.project;
if( !MakeReadyToPlay(project) )
return;
auto &viewInfo = ViewInfo::Get( project );
const auto &selectedRegion = viewInfo.selectedRegion;
double t1 = selectedRegion.t1();
double afterLen;
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
auto playOptions = DefaultPlayOptions( project );
ProjectAudioManager::Get( project ).PlayPlayRegion(
SelectedRegion(t1, t1 + afterLen), playOptions, PlayMode::oneSecondPlay);
}
void OnPlayBeforeAndAfterSelectionStart
(const CommandContext &context)
{
auto &project = context.project;
if (!MakeReadyToPlay(project))
return;
auto &viewInfo = ViewInfo::Get( project );
const auto &selectedRegion = viewInfo.selectedRegion;
double t0 = selectedRegion.t0();
double t1 = selectedRegion.t1();
double beforeLen;
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
double afterLen;
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
auto &projectAudioManager = ProjectAudioManager::Get( project );
auto playOptions = DefaultPlayOptions( project );
if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
projectAudioManager.PlayPlayRegion(
SelectedRegion(t0 - beforeLen, t1), playOptions,
PlayMode::oneSecondPlay);
else
projectAudioManager.PlayPlayRegion(
SelectedRegion(t0 - beforeLen, t0 + afterLen), playOptions,
PlayMode::oneSecondPlay);
}
void OnPlayBeforeAndAfterSelectionEnd
(const CommandContext &context)
{
auto &project = context.project;
if (!MakeReadyToPlay(project))
return;
auto &viewInfo = ViewInfo::Get( project );
const auto &selectedRegion = viewInfo.selectedRegion;
double t0 = selectedRegion.t0();
double t1 = selectedRegion.t1();
double beforeLen;
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
double afterLen;
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
auto &projectAudioManager = ProjectAudioManager::Get( project );
auto playOptions = DefaultPlayOptions( project );
if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
projectAudioManager.PlayPlayRegion(
SelectedRegion(t0, t1 + afterLen), playOptions,
PlayMode::oneSecondPlay);
else
projectAudioManager.PlayPlayRegion(
SelectedRegion(t1 - beforeLen, t1 + afterLen), playOptions,
PlayMode::oneSecondPlay);
}
void OnPlayCutPreview(const CommandContext &context)
{
auto &project = context.project;
if ( !MakeReadyToPlay(project) )
return;
// Play with cut preview
ProjectAudioManager::Get( project ).PlayCurrentRegion(
false, true
);
}
void OnPlayAtSpeed(const CommandContext &context)
{
auto &project = context.project;
auto tb = &TranscriptionToolBar::Get( project );
if (tb) {
tb->PlayAtSpeed(false, false);
}
}
void OnPlayAtSpeedLooped(const CommandContext &context)
{
auto &project = context.project;
auto tb = &TranscriptionToolBar::Get( project );
if (tb) {
tb->PlayAtSpeed(true, false);
}
}
void OnPlayAtSpeedCutPreview(const CommandContext &context)
{
auto &project = context.project;
auto tb = &TranscriptionToolBar::Get( project );
if (tb) {
tb->PlayAtSpeed(false, true);
}
}
void OnSetPlaySpeed(const CommandContext &context)
{
auto &project = context.project;
auto tb = &TranscriptionToolBar::Get( project );
if (tb) {
tb->ShowPlaySpeedDialog();
}
}
void OnPlaySpeedInc(const CommandContext &context)
{
auto &project = context.project;
auto tb = &TranscriptionToolBar::Get( project );
if (tb) {
tb->AdjustPlaySpeed(0.1f);
}
}
void OnPlaySpeedDec(const CommandContext &context)
{
auto &project = context.project;
auto tb = &TranscriptionToolBar::Get( project );
if (tb) {
tb->AdjustPlaySpeed(-0.1f);
}
}
void OnMoveToPrevLabel(const CommandContext &context)
{
auto &project = context.project;
DoMoveToLabel(project, false);
}
void OnMoveToNextLabel(const CommandContext &context)
{
auto &project = context.project;
DoMoveToLabel(project, true);
}
#if 0
// Legacy handlers, not used as of version 2.3.0
void OnStopSelect(const CommandContext &context)
{
auto &project = context.project;
auto &history = ProjectHistory::Get( project );
auto &viewInfo = project.GetViewInfo();
auto &selectedRegion = viewInfo.selectedRegion;
auto gAudioIO = AudioIOBase::Get();
if (gAudioIO->IsStreamActive()) {
selectedRegion.setT0(gAudioIO->GetStreamTime(), false);
ProjectAudioManager::Get( project ).Stop();
history.ModifyState(false); // without bWantsAutoSave
}
}
#endif
}; // struct Handler
} // namespace
static CommandHandlerObject &findCommandHandler(AudacityProject &) {
// Handler is not stateful. Doesn't need a factory registered with
// AudacityProject.
static TransportActions::Handler instance;
return instance;
};
// Menu definitions
#define FN(X) findCommandHandler, \
static_cast<CommandFunctorPointer>(& TransportActions::Handler :: X)
#define XXO(X) _(X), wxString{X}.Contains("...")
MenuTable::BaseItemPtr CursorMenu( AudacityProject& );
MenuTable::BaseItemPtr TransportMenu( AudacityProject &project )
{
using namespace MenuTable;
using Options = CommandManager::Options;
static const auto checkOff = Options{}.CheckState( false );
static const auto checkOn = Options{}.CheckState( true );
static const auto CanStopFlags = AudioIONotBusyFlag | CanStopAudioStreamFlag;
/* i18n-hint: 'Transport' is the name given to the set of controls that
play, record, pause etc. */
return Menu( _("Tra&nsport"),
Menu( _("Pl&aying"),
/* i18n-hint: (verb) Start or Stop audio playback*/
Command( wxT("PlayStop"), XXO("Pl&ay/Stop"), FN(OnPlayStop),
CanStopAudioStreamFlag, wxT("Space") ),
Command( wxT("PlayStopSelect"), XXO("Play/Stop and &Set Cursor"),
FN(OnPlayStopSelect), CanStopAudioStreamFlag, wxT("X") ),
Command( wxT("PlayLooped"), XXO("&Loop Play"), FN(OnPlayLooped),
CanStopAudioStreamFlag, wxT("Shift+Space") ),
Command( wxT("Pause"), XXO("&Pause"), FN(OnPause),
CanStopAudioStreamFlag, wxT("P") )
),
Menu( _("&Recording"),
/* i18n-hint: (verb)*/
Command( wxT("Record1stChoice"), XXO("&Record"), FN(OnRecord),
CanStopFlags, wxT("R") ),
// The OnRecord2ndChoice function is: if normal record records beside,
// it records below, if normal record records below, it records beside.
// TODO: Do 'the right thing' with other options like TimerRecord.
Command( wxT("Record2ndChoice"),
// Our first choice is bound to R (by default)
// and gets the prime position.
// We supply the name for the 'other one' here.
// It should be bound to Shift+R
(gPrefs->ReadBool("/GUI/PreferNewTrackRecord", false)
? _("&Append Record") : _("Record &New Track")),
false, FN(OnRecord2ndChoice), CanStopFlags,
wxT("Shift+R")
),
Command( wxT("TimerRecord"), XXO("&Timer Record..."),
FN(OnTimerRecord), CanStopFlags, wxT("Shift+T") ),
#ifdef EXPERIMENTAL_PUNCH_AND_ROLL
Command( wxT("PunchAndRoll"), XXO("Punch and Rol&l Record"),
FN(OnPunchAndRoll),
WaveTracksExistFlag | AudioIONotBusyFlag, wxT("Shift+D") ),
#endif
// JKC: I decided to duplicate this between play and record,
// rather than put it at the top level.
// CommandManger::AddItem can now cope with simple duplicated items.
// PRL: caution, this is a duplicated command name!
Command( wxT("Pause"), XXO("&Pause"), FN(OnPause),
CanStopAudioStreamFlag, wxT("P") )
),
// Scrubbing sub-menu
Scrubber::Get( project ).Menu(),
CursorMenu,
Separator(),
//////////////////////////////////////////////////////////////////////////
Menu( _("Pla&y Region"),
Command( wxT("LockPlayRegion"), XXO("&Lock"), FN(OnLockPlayRegion),
PlayRegionNotLockedFlag ),
Command( wxT("UnlockPlayRegion"), XXO("&Unlock"),
FN(OnUnlockPlayRegion), PlayRegionLockedFlag )
),
Separator(),
Command( wxT("RescanDevices"), XXO("R&escan Audio Devices"),
FN(OnRescanDevices), AudioIONotBusyFlag | CanStopAudioStreamFlag ),
Menu( _("Transport &Options"),
// Sound Activated recording options
Command( wxT("SoundActivationLevel"),
XXO("Sound Activation Le&vel..."), FN(OnSoundActivated),
AudioIONotBusyFlag | CanStopAudioStreamFlag ),
Command( wxT("SoundActivation"),
XXO("Sound A&ctivated Recording (on/off)"),
FN(OnToggleSoundActivated),
AudioIONotBusyFlag | CanStopAudioStreamFlag, checkOff ),
Separator(),
Command( wxT("PinnedHead"), XXO("Pinned Play/Record &Head (on/off)"),
FN(OnTogglePinnedHead),
// Switching of scrolling on and off is permitted
// even during transport
AlwaysEnabledFlag, checkOff ),
Command( wxT("Overdub"), XXO("&Overdub (on/off)"),
FN(OnTogglePlayRecording),
AudioIONotBusyFlag | CanStopAudioStreamFlag, checkOn ),
Command( wxT("SWPlaythrough"), XXO("So&ftware Playthrough (on/off)"),
FN(OnToggleSWPlaythrough),
AudioIONotBusyFlag | CanStopAudioStreamFlag, checkOff )
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
,
Command( wxT("AutomatedInputLevelAdjustmentOnOff"),
XXO("A&utomated Recording Level Adjustment (on/off)"),
FN(OnToggleAutomatedInputLevelAdjustment),
AudioIONotBusyFlag | CanStopAudioStreamFlag, checkOff )
#endif
)
);
}
MenuTable::BaseItemPtr ExtraTransportMenu( AudacityProject & )
{
using namespace MenuTable;
return Menu( _("T&ransport"),
// PlayStop is already in the menus.
/* i18n-hint: (verb) Start playing audio*/
Command( wxT("Play"), XXO("Pl&ay"), FN(OnPlayStop),
WaveTracksExistFlag | AudioIONotBusyFlag ),
/* i18n-hint: (verb) Stop playing audio*/
Command( wxT("Stop"), XXO("Sto&p"), FN(OnStop),
AudioIOBusyFlag | CanStopAudioStreamFlag ),
Command( wxT("PlayOneSec"), XXO("Play &One Second"), FN(OnPlayOneSecond),
CaptureNotBusyFlag, wxT("1") ),
Command( wxT("PlayToSelection"), XXO("Play to &Selection"),
FN(OnPlayToSelection),
CaptureNotBusyFlag, wxT("B") ),
Command( wxT("PlayBeforeSelectionStart"),
XXO("Play &Before Selection Start"), FN(OnPlayBeforeSelectionStart),
CaptureNotBusyFlag, wxT("Shift+F5") ),
Command( wxT("PlayAfterSelectionStart"),
XXO("Play Af&ter Selection Start"), FN(OnPlayAfterSelectionStart),
CaptureNotBusyFlag, wxT("Shift+F6") ),
Command( wxT("PlayBeforeSelectionEnd"),
XXO("Play Be&fore Selection End"), FN(OnPlayBeforeSelectionEnd),
CaptureNotBusyFlag, wxT("Shift+F7") ),
Command( wxT("PlayAfterSelectionEnd"),
XXO("Play Aft&er Selection End"), FN(OnPlayAfterSelectionEnd),
CaptureNotBusyFlag, wxT("Shift+F8") ),
Command( wxT("PlayBeforeAndAfterSelectionStart"),
XXO("Play Before a&nd After Selection Start"),
FN(OnPlayBeforeAndAfterSelectionStart), CaptureNotBusyFlag,
wxT("Ctrl+Shift+F5") ),
Command( wxT("PlayBeforeAndAfterSelectionEnd"),
XXO("Play Before an&d After Selection End"),
FN(OnPlayBeforeAndAfterSelectionEnd), CaptureNotBusyFlag,
wxT("Ctrl+Shift+F7") ),
Command( wxT("PlayCutPreview"), XXO("Play C&ut Preview"),
FN(OnPlayCutPreview),
CaptureNotBusyFlag, wxT("C") )
);
}
MenuTable::BaseItemPtr ExtraPlayAtSpeedMenu( AudacityProject & )
{
using namespace MenuTable;
return Menu( _("&Play-at-Speed"),
/* i18n-hint: 'Normal Play-at-Speed' doesn't loop or cut preview. */
Command( wxT("PlayAtSpeed"), XXO("Normal Pl&ay-at-Speed"),
FN(OnPlayAtSpeed), CaptureNotBusyFlag ),
Command( wxT("PlayAtSpeedLooped"), XXO("&Loop Play-at-Speed"),
FN(OnPlayAtSpeedLooped), CaptureNotBusyFlag ),
Command( wxT("PlayAtSpeedCutPreview"), XXO("Play C&ut Preview-at-Speed"),
FN(OnPlayAtSpeedCutPreview), CaptureNotBusyFlag ),
Command( wxT("SetPlaySpeed"), XXO("Ad&just Playback Speed..."),
FN(OnSetPlaySpeed), CaptureNotBusyFlag ),
Command( wxT("PlaySpeedInc"), XXO("&Increase Playback Speed"),
FN(OnPlaySpeedInc), CaptureNotBusyFlag ),
Command( wxT("PlaySpeedDec"), XXO("&Decrease Playback Speed"),
FN(OnPlaySpeedDec), CaptureNotBusyFlag ),
// These were on the original transcription toolbar.
// But they are not on the
// shortened one.
Command( wxT("MoveToPrevLabel"), XXO("Move to &Previous Label"),
FN(OnMoveToPrevLabel),
CaptureNotBusyFlag | TrackPanelHasFocus, wxT("Alt+Left") ),
Command( wxT("MoveToNextLabel"), XXO("Move to &Next Label"),
FN(OnMoveToNextLabel),
CaptureNotBusyFlag | TrackPanelHasFocus, wxT("Alt+Right") )
);
}
#undef XXO
#undef FN