1290 lines
42 KiB
C++
1290 lines
42 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 "../ProjectManager.h"
|
|
#include "../SoundActivatedRecord.h"
|
|
#include "../TimerRecordDialog.h"
|
|
#include "../TrackPanelAx.h"
|
|
#include "../TrackPanel.h"
|
|
#include "../UndoManager.h"
|
|
#include "../WaveClip.h"
|
|
#include "../prefs/RecordingPrefs.h"
|
|
#include "../prefs/TracksPrefs.h"
|
|
#include "../WaveTrack.h"
|
|
#include "../ViewInfo.h"
|
|
#include "../commands/CommandContext.h"
|
|
#include "../commands/CommandManager.h"
|
|
#include "../toolbars/ControlToolBar.h"
|
|
#include "../toolbars/TranscriptionToolBar.h"
|
|
#include "../widgets/AudacityMessageBox.h"
|
|
#include "../widgets/ErrorDialog.h"
|
|
#include "../widgets/ProgressDialog.h"
|
|
|
|
#include <float.h>
|
|
|
|
// private helper classes and functions
|
|
namespace {
|
|
|
|
void PlayCurrentRegionAndWait(const CommandContext &context,
|
|
bool looped = false,
|
|
bool cutpreview = false)
|
|
{
|
|
auto &project = context.project;
|
|
auto &projectAudioManager = ProjectAudioManager::Get(project);
|
|
|
|
const auto &playRegion = ViewInfo::Get(project).playRegion;
|
|
double t0 = playRegion.GetStart();
|
|
double t1 = playRegion.GetEnd();
|
|
|
|
projectAudioManager.PlayCurrentRegion(looped, cutpreview);
|
|
|
|
if (project.mBatchMode > 0 && t0 != t1) {
|
|
wxYieldIfNeeded();
|
|
|
|
/* i18n-hint: This title appears on a dialog that indicates the progress
|
|
in doing something.*/
|
|
ProgressDialog progress(XO("Progress"), XO("Playing"), pdlgHideCancelButton);
|
|
auto gAudioIO = AudioIO::Get();
|
|
|
|
while (projectAudioManager.Playing()) {
|
|
ProgressResult result = progress.Update(gAudioIO->GetStreamTime() - t0, t1 - t0);
|
|
if (result != ProgressResult::Success) {
|
|
projectAudioManager.Stop();
|
|
if (result != ProgressResult::Stopped) {
|
|
context.Error(wxT("Playing interrupted"));
|
|
}
|
|
break;
|
|
}
|
|
|
|
wxMilliSleep(100);
|
|
wxYieldIfNeeded();
|
|
}
|
|
|
|
projectAudioManager.Stop();
|
|
wxYieldIfNeeded();
|
|
}
|
|
}
|
|
|
|
void PlayPlayRegionAndWait(const CommandContext &context,
|
|
const SelectedRegion &selectedRegion,
|
|
const AudioIOStartStreamOptions &options,
|
|
PlayMode mode)
|
|
{
|
|
auto &project = context.project;
|
|
auto &projectAudioManager = ProjectAudioManager::Get(project);
|
|
|
|
double t0 = selectedRegion.t0();
|
|
double t1 = selectedRegion.t1();
|
|
|
|
projectAudioManager.PlayPlayRegion(selectedRegion, options, mode);
|
|
|
|
if (project.mBatchMode > 0) {
|
|
wxYieldIfNeeded();
|
|
|
|
/* i18n-hint: This title appears on a dialog that indicates the progress
|
|
in doing something.*/
|
|
ProgressDialog progress(XO("Progress"), XO("Playing"), pdlgHideCancelButton);
|
|
auto gAudioIO = AudioIO::Get();
|
|
|
|
while (projectAudioManager.Playing()) {
|
|
ProgressResult result = progress.Update(gAudioIO->GetStreamTime() - t0, t1 - t0);
|
|
if (result != ProgressResult::Success) {
|
|
projectAudioManager.Stop();
|
|
if (result != ProgressResult::Stopped) {
|
|
context.Error(wxT("Playing interrupted"));
|
|
}
|
|
break;
|
|
}
|
|
|
|
wxMilliSleep(100);
|
|
wxYieldIfNeeded();
|
|
}
|
|
|
|
projectAudioManager.Stop();
|
|
wxYieldIfNeeded();
|
|
}
|
|
}
|
|
|
|
void RecordAndWait(const CommandContext &context, bool altAppearance)
|
|
{
|
|
auto &project = context.project;
|
|
auto &projectAudioManager = ProjectAudioManager::Get(project);
|
|
|
|
const auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
|
|
double t0 = selectedRegion.t0();
|
|
double t1 = selectedRegion.t1();
|
|
|
|
projectAudioManager.OnRecord(altAppearance);
|
|
|
|
if (project.mBatchMode > 0 && t1 != t0) {
|
|
wxYieldIfNeeded();
|
|
|
|
/* i18n-hint: This title appears on a dialog that indicates the progress
|
|
in doing something.*/
|
|
ProgressDialog progress(XO("Progress"), XO("Recording"), pdlgHideCancelButton);
|
|
auto gAudioIO = AudioIO::Get();
|
|
|
|
while (projectAudioManager.Recording()) {
|
|
ProgressResult result = progress.Update(gAudioIO->GetStreamTime() - t0, t1 - t0);
|
|
if (result != ProgressResult::Success) {
|
|
projectAudioManager.Stop();
|
|
if (result != ProgressResult::Stopped) {
|
|
context.Error(wxT("Recording interrupted"));
|
|
}
|
|
break;
|
|
}
|
|
|
|
wxMilliSleep(100);
|
|
wxYieldIfNeeded();
|
|
}
|
|
|
|
projectAudioManager.Stop();
|
|
wxYieldIfNeeded();
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Returns true if this project was stopped, otherwise false.
|
|
// (it may though have stopped another project playing)
|
|
bool DoStopPlaying(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &projectAudioManager = ProjectAudioManager::Get(project);
|
|
auto gAudioIO = AudioIOBase::Get();
|
|
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.
|
|
if (gAudioIO->IsStreamActive(token)) {
|
|
toolbar.SetStop(); //Pushes stop down
|
|
projectAudioManager.Stop();
|
|
// Playing project was stopped. All done.
|
|
return true;
|
|
}
|
|
|
|
// This project isn't playing.
|
|
// If some other project is playing, stop playing it
|
|
if (gAudioIO->IsStreamActive()) {
|
|
|
|
//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();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DoStartPlaying(const CommandContext &context, bool looping = false)
|
|
{
|
|
auto &project = context.project;
|
|
auto &projectAudioManager = ProjectAudioManager::Get(project);
|
|
auto gAudioIO = AudioIOBase::Get();
|
|
//play the front project
|
|
if (!gAudioIO->IsBusy()) {
|
|
//Otherwise, start playing (assuming audio I/O isn't busy)
|
|
|
|
// Will automatically set mLastPlayMode
|
|
PlayCurrentRegionAndWait(context, looping);
|
|
}
|
|
}
|
|
|
|
void DoMoveToLabel(AudacityProject &project, bool next)
|
|
{
|
|
auto &tracks = TrackList::Get( project );
|
|
auto &trackFocus = TrackFocus::Get( project );
|
|
auto &window = ProjectWindow::Get( project );
|
|
auto &projectAudioManager = ProjectAudioManager::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(XO("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(
|
|
XO("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);
|
|
bool looping = projectAudioManager.Looping();
|
|
if (ProjectAudioIO::Get( project ).IsAudioActive()) {
|
|
DoStopPlaying(project);
|
|
selectedRegion = label->selectedRegion;
|
|
window.RedrawProject();
|
|
DoStartPlaying(project, looping);
|
|
}
|
|
else {
|
|
selectedRegion = label->selectedRegion;
|
|
window.ScrollIntoView(selectedRegion.t0());
|
|
window.RedrawProject();
|
|
}
|
|
auto message = XO("%s %d of %d")
|
|
.Format( label->title, i + 1, lt->GetNumLabels() );
|
|
trackFocus.MessageForScreenReader(message);
|
|
}
|
|
else {
|
|
trackFocus.MessageForScreenReader(XO("no labels in label track"));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Menu handler functions
|
|
|
|
namespace TransportActions {
|
|
|
|
struct Handler : CommandHandlerObject {
|
|
|
|
// This Plays OR Stops audio. It's a toggle.
|
|
// It is usually bound to the SPACE key.
|
|
void OnPlayStop(const CommandContext &context)
|
|
{
|
|
if (DoStopPlaying(context.project))
|
|
return;
|
|
DoStartPlaying(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
|
|
PlayCurrentRegionAndWait(context, true);
|
|
}
|
|
|
|
void OnPause(const CommandContext &context)
|
|
{
|
|
ProjectAudioManager::Get( context.project ).OnPause();
|
|
}
|
|
|
|
void OnRecord(const CommandContext &context)
|
|
{
|
|
RecordAndWait(context, false);
|
|
}
|
|
|
|
// If first choice is record same track 2nd choice is record NEW track
|
|
// and vice versa.
|
|
void OnRecord2ndChoice(const CommandContext &context)
|
|
{
|
|
RecordAndWait(context, 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(
|
|
XO(
|
|
"Timer Recording cannot be used with more than one open project.\n\nPlease close any additional projects and try again."),
|
|
XO("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(
|
|
XO(
|
|
"Timer Recording cannot be used while you have unsaved changes.\n\nPlease save or close this project and try again."),
|
|
XO("Timer Recording"),
|
|
wxICON_INFORMATION | wxOK);
|
|
return;
|
|
}
|
|
|
|
// We check the selected tracks to see if there is enough of them to accommodate
|
|
// all input channels and all of them have the same sampling rate.
|
|
// Those checks will be later performed by recording function anyway,
|
|
// but we want to warn the user about potential problems from the very start.
|
|
const auto selectedTracks{ GetPropertiesOfSelected(project) };
|
|
const int rateOfSelected{ selectedTracks.rateOfSelected };
|
|
const int numberOfSelected{ selectedTracks.numberOfSelected };
|
|
const bool allSameRate{ selectedTracks.allSameRate };
|
|
|
|
if (!allSameRate) {
|
|
AudacityMessageBox(XO("The tracks selected "
|
|
"for recording must all have the same sampling rate"),
|
|
XO("Mismatched Sampling Rates"),
|
|
wxICON_ERROR | wxCENTRE);
|
|
|
|
return;
|
|
}
|
|
|
|
const auto existingTracks{ ProjectAudioManager::ChooseExistingRecordingTracks(project, true, rateOfSelected) };
|
|
if (existingTracks.empty()) {
|
|
if (numberOfSelected > 0 && rateOfSelected != settings.GetRate()) {
|
|
AudacityMessageBox(XO(
|
|
"Too few tracks are selected for recording at this sample rate.\n"
|
|
"(Audacity requires two channels at the same sample rate for\n"
|
|
"each stereo track)"),
|
|
XO("Too Few Compatible Tracks Selected"),
|
|
wxICON_ERROR | wxCENTRE);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// We use this variable to display "Current Project" in the Timer Recording
|
|
// save project field
|
|
bool bProjectSaved = !ProjectFileIO::Get( project ).IsModified();
|
|
|
|
//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, project, bProjectSaved); /* parent, project, project saved? */
|
|
int modalResult = dialog.ShowModal();
|
|
if (modalResult == wxID_CANCEL)
|
|
{
|
|
// Cancelled before recording - don't need to do anything.
|
|
}
|
|
else
|
|
{
|
|
// Bug #2382
|
|
// Allow recording to start at current cursor position.
|
|
#if 0
|
|
// 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);
|
|
}
|
|
#endif
|
|
|
|
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 );
|
|
} );
|
|
ProjectManager::Get(project).SetSkipSavePrompt(true);
|
|
break;
|
|
|
|
#ifdef __WINDOWS__
|
|
case POST_TIMER_RECORD_RESTART:
|
|
// Restart System
|
|
ProjectManager::Get(project).SetSkipSavePrompt(true);
|
|
system("shutdown /r /f /t 30");
|
|
break;
|
|
case POST_TIMER_RECORD_SHUTDOWN:
|
|
// Shutdown System
|
|
ProjectManager::Get(project).SetSkipSavePrompt(true);
|
|
system("shutdown /s /f /t 30");
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
#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());
|
|
|
|
// Checking the selected tracks: making sure they all have the same rate
|
|
const auto selectedTracks{ GetPropertiesOfSelected(project) };
|
|
const int rateOfSelected{ selectedTracks.rateOfSelected };
|
|
const bool allSameRate{ selectedTracks.allSameRate };
|
|
|
|
if (!allSameRate) {
|
|
AudacityMessageBox(XO("The tracks selected "
|
|
"for recording must all have the same sampling rate"),
|
|
XO("Mismatched Sampling Rates"),
|
|
wxICON_ERROR | wxCENTRE);
|
|
|
|
return;
|
|
}
|
|
|
|
// Decide which tracks to record in.
|
|
auto tracks =
|
|
ProjectAudioManager::ChooseExistingRecordingTracks(project, true, rateOfSelected);
|
|
if (tracks.empty()) {
|
|
int recordingChannels =
|
|
std::max(0L, gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2));
|
|
auto message =
|
|
(recordingChannels == 1)
|
|
? XO("Please select in a mono track.")
|
|
: (recordingChannels == 2)
|
|
? XO("Please select in a stereo track or two mono tracks.")
|
|
: XO("Please select at least %d channels.").Format( recordingChannels );
|
|
ShowErrorDialog(&window, XO("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 possibility of a clip even shorter than the
|
|
// crossfade duration!
|
|
newt1 = std::min(newt1, clip->GetEndTime() - crossFadeDuration);
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
auto message = XO("Please select a time within a clip.");
|
|
ShowErrorDialog( &window, XO("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.rate = rateOfSelected;
|
|
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();
|
|
PlayPlayRegionAndWait(context, 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 );
|
|
|
|
PlayPlayRegionAndWait(context, 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 );
|
|
|
|
PlayPlayRegionAndWait(context, 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 playOptions = DefaultPlayOptions( project );
|
|
|
|
if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
|
|
PlayPlayRegionAndWait(context, SelectedRegion(t0, t1),
|
|
playOptions, PlayMode::oneSecondPlay);
|
|
else
|
|
PlayPlayRegionAndWait(context, 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 playOptions = DefaultPlayOptions( project );
|
|
|
|
if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
|
|
PlayPlayRegionAndWait(context, SelectedRegion(t0, t1),
|
|
playOptions, PlayMode::oneSecondPlay);
|
|
else
|
|
PlayPlayRegionAndWait(context, 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 );
|
|
|
|
PlayPlayRegionAndWait(context, 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 playOptions = DefaultPlayOptions( project );
|
|
|
|
if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
|
|
PlayPlayRegionAndWait(context, SelectedRegion(t0 - beforeLen, t1),
|
|
playOptions, PlayMode::oneSecondPlay);
|
|
else
|
|
PlayPlayRegionAndWait(context, 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 playOptions = DefaultPlayOptions( project );
|
|
|
|
if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
|
|
PlayPlayRegionAndWait(context, SelectedRegion(t0, t1 + afterLen),
|
|
playOptions, PlayMode::oneSecondPlay);
|
|
else
|
|
PlayPlayRegionAndWait(context, 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
|
|
PlayCurrentRegionAndWait(context, 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) (& TransportActions::Handler :: X)
|
|
|
|
// Under /MenuBar
|
|
namespace {
|
|
using namespace MenuTable;
|
|
BaseItemSharedPtr TransportMenu()
|
|
{
|
|
using Options = CommandManager::Options;
|
|
|
|
static const auto CanStopFlags = AudioIONotBusyFlag() | CanStopAudioStreamFlag();
|
|
|
|
static BaseItemSharedPtr menu{
|
|
( FinderScope{ findCommandHandler },
|
|
/* i18n-hint: 'Transport' is the name given to the set of controls that
|
|
play, record, pause etc. */
|
|
Menu( wxT("Transport"), XXO("Tra&nsport"),
|
|
Section( "Basic",
|
|
Menu( wxT("Play"), XXO("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( wxT("Record"), XXO("&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.
|
|
// Delayed evaluation in case gPrefs is not yet defined
|
|
[](const AudacityProject&)
|
|
{ return 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)
|
|
? XXO("&Append Record") : XXO("Record &New Track")),
|
|
FN(OnRecord2ndChoice), CanStopFlags,
|
|
wxT("Shift+R"),
|
|
findCommandHandler
|
|
); },
|
|
|
|
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") )
|
|
)
|
|
),
|
|
|
|
Section( "Other",
|
|
Section( "",
|
|
Menu( wxT("PlayRegion"), XXO("Pla&y Region"),
|
|
Command( wxT("LockPlayRegion"), XXO("&Lock"), FN(OnLockPlayRegion),
|
|
PlayRegionNotLockedFlag() ),
|
|
Command( wxT("UnlockPlayRegion"), XXO("&Unlock"),
|
|
FN(OnUnlockPlayRegion), PlayRegionLockedFlag() )
|
|
)
|
|
),
|
|
|
|
Command( wxT("RescanDevices"), XXO("R&escan Audio Devices"),
|
|
FN(OnRescanDevices), AudioIONotBusyFlag() | CanStopAudioStreamFlag() ),
|
|
|
|
Menu( wxT("Options"), XXO("Transport &Options"),
|
|
Section( "",
|
|
// 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(),
|
|
Options{}.CheckTest(wxT("/AudioIO/SoundActivatedRecord"), false) )
|
|
),
|
|
|
|
Section( "",
|
|
Command( wxT("PinnedHead"), XXO("Pinned Play/Record &Head (on/off)"),
|
|
FN(OnTogglePinnedHead),
|
|
// Switching of scrolling on and off is permitted
|
|
// even during transport
|
|
AlwaysEnabledFlag,
|
|
Options{}.CheckTest([](const AudacityProject&){
|
|
return TracksPrefs::GetPinnedHeadPreference(); } ) ),
|
|
|
|
Command( wxT("Overdub"), XXO("&Overdub (on/off)"),
|
|
FN(OnTogglePlayRecording),
|
|
AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
|
|
Options{}.CheckTest( wxT("/AudioIO/Duplex"),
|
|
#ifdef EXPERIMENTAL_DA
|
|
false
|
|
#else
|
|
true
|
|
#endif
|
|
) ),
|
|
Command( wxT("SWPlaythrough"), XXO("So&ftware Playthrough (on/off)"),
|
|
FN(OnToggleSWPlaythrough),
|
|
AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
|
|
Options{}.CheckTest( wxT("/AudioIO/SWPlaythrough"), false ) )
|
|
|
|
|
|
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
|
,
|
|
Command( wxT("AutomatedInputLevelAdjustmentOnOff"),
|
|
XXO("A&utomated Recording Level Adjustment (on/off)"),
|
|
FN(OnToggleAutomatedInputLevelAdjustment),
|
|
AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
|
|
Options{}.CheckTest(
|
|
wxT("/AudioIO/AutomatedInputLevelAdjustment"), false ) )
|
|
#endif
|
|
)
|
|
)
|
|
)
|
|
) ) };
|
|
return menu;
|
|
}
|
|
|
|
AttachedItem sAttachment1{
|
|
wxT(""),
|
|
Shared( TransportMenu() )
|
|
};
|
|
|
|
BaseItemSharedPtr ExtraTransportMenu()
|
|
{
|
|
static BaseItemSharedPtr menu{
|
|
( FinderScope{ findCommandHandler },
|
|
Menu( wxT("Transport"), XXO("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") )
|
|
) ) };
|
|
return menu;
|
|
}
|
|
|
|
AttachedItem sAttachment2{
|
|
wxT("Optional/Extra/Part1"),
|
|
Shared( ExtraTransportMenu() )
|
|
};
|
|
|
|
BaseItemSharedPtr ExtraPlayAtSpeedMenu()
|
|
{
|
|
static BaseItemSharedPtr menu{
|
|
( FinderScope{ findCommandHandler },
|
|
Menu( wxT("PlayAtSpeed"), XXO("&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() )
|
|
) ) };
|
|
return menu;
|
|
}
|
|
|
|
AttachedItem sAttachment3{
|
|
wxT("Optional/Extra/Part1"),
|
|
Shared( ExtraPlayAtSpeedMenu() )
|
|
};
|
|
|
|
BaseItemSharedPtr ExtraSelectionItems()
|
|
{
|
|
using Options = CommandManager::Options;
|
|
static BaseItemSharedPtr items{
|
|
(FinderScope{ findCommandHandler },
|
|
Items(wxT("MoveToLabel"),
|
|
Command(wxT("MoveToPrevLabel"), XXO("Move to Pre&vious Label"),
|
|
FN(OnMoveToPrevLabel),
|
|
CaptureNotBusyFlag() | TrackPanelHasFocus(), wxT("Alt+Left")),
|
|
Command(wxT("MoveToNextLabel"), XXO("Move to Ne&xt Label"),
|
|
FN(OnMoveToNextLabel),
|
|
CaptureNotBusyFlag() | TrackPanelHasFocus(), wxT("Alt+Right"))
|
|
)) };
|
|
return items;
|
|
}
|
|
|
|
AttachedItem sAttachment4{
|
|
{ wxT("Optional/Extra/Part1/Select"), { OrderingHint::End, {} } },
|
|
Shared(ExtraSelectionItems())
|
|
};
|
|
|
|
}
|
|
|
|
#undef FN
|