1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-04-30 15:49:41 +02:00
audacity/src/menus/TrackMenus.cpp
Paul Licameli 70ec69fa3d Pass untranslated command labels to CommandManager...
... and deduce whether to exclude from macros inside NewIdentifier, simplifying
argument lists further

Also fix the localization of "..." added to names by PluginMenus.cpp
2019-12-12 15:15:59 -05:00

1456 lines
46 KiB
C++

#include "../Audacity.h"
#include "../Experimental.h"
#include "../CommonCommandFlags.h"
#include "../LabelTrack.h"
#include "../Menus.h"
#include "../MissingAliasFileDialog.h"
#include "../Mix.h"
#include "../Prefs.h"
#include "../Project.h"
#include "../ProjectAudioIO.h"
#include "../ProjectHistory.h"
#include "../ProjectSettings.h"
#include "../PluginManager.h"
#include "../ProjectStatus.h"
#include "../ProjectWindow.h"
#include "../SelectUtilities.h"
#include "../ShuttleGui.h"
#include "../TimeTrack.h"
#include "../TrackPanelAx.h"
#include "../TrackPanel.h"
#include "../TrackUtilities.h"
#include "../UndoManager.h"
#include "../WaveClip.h"
#include "../ViewInfo.h"
#include "../WaveTrack.h"
#include "../commands/CommandContext.h"
#include "../commands/CommandManager.h"
#include "../effects/EffectManager.h"
#include "../tracks/playabletrack/wavetrack/ui/WaveTrackControls.h"
#include "../widgets/ASlider.h"
#include "../widgets/AudacityMessageBox.h"
#include "../widgets/ProgressDialog.h"
#include <wx/combobox.h>
#ifdef EXPERIMENTAL_SCOREALIGN
#include "../effects/ScoreAlignDialog.h"
#include "audioreader.h"
#include "scorealign.h"
#include "scorealign-glue.h"
#endif /* EXPERIMENTAL_SCOREALIGN */
// private helper classes and functions
namespace {
void DoMixAndRender
(AudacityProject &project, bool toNewTrack)
{
const auto &settings = ProjectSettings::Get( project );
auto &tracks = TrackList::Get( project );
auto &trackFactory = TrackFactory::Get( project );
auto rate = settings.GetRate();
auto defaultFormat = settings.GetDefaultFormat();
auto &trackPanel = TrackPanel::Get( project );
auto &window = ProjectWindow::Get( project );
MissingAliasFilesDialog::SetShouldShow(true);
WaveTrack::Holder uNewLeft, uNewRight;
::MixAndRender(
&tracks, &trackFactory, rate, defaultFormat, 0.0, 0.0, uNewLeft, uNewRight);
if (uNewLeft) {
// Remove originals, get stats on what tracks were mixed
auto trackRange = tracks.Selected< WaveTrack >();
auto selectedCount = (trackRange + &Track::IsLeader).size();
wxString firstName;
if (selectedCount > 0)
firstName = (*trackRange.begin())->GetName();
if (!toNewTrack) {
// Beware iterator invalidation!
for (auto &it = trackRange.first, &end = trackRange.second; it != end;)
tracks.Remove( *it++ );
}
// Add NEW tracks
auto pNewLeft = tracks.Add( uNewLeft );
decltype(pNewLeft) pNewRight{};
if (uNewRight)
pNewRight = tracks.Add( uNewRight );
// Do this only after adding tracks to the list
tracks.GroupChannels(*pNewLeft, pNewRight ? 2 : 1);
// If we're just rendering (not mixing), keep the track name the same
if (selectedCount==1) {
pNewLeft->SetName(firstName);
if (pNewRight)
pNewRight->SetName(firstName);
}
// Smart history/undo message
if (selectedCount==1) {
wxString msg;
msg.Printf(_("Rendered all audio in track '%s'"), firstName);
/* i18n-hint: Convert the audio into a more usable form, so apply
* panning and amplification and write to some external file.*/
ProjectHistory::Get( project ).PushState(msg, _("Render"));
}
else {
wxString msg;
if (pNewRight)
msg.Printf(
_("Mixed and rendered %d tracks into one new stereo track"),
(int)selectedCount);
else
msg.Printf(
_("Mixed and rendered %d tracks into one new mono track"),
(int)selectedCount);
ProjectHistory::Get( project ).PushState(msg, _("Mix and Render"));
}
trackPanel.SetFocus();
TrackFocus::Get( project ).Set( pNewLeft );
pNewLeft->EnsureVisible();
}
}
void DoPanTracks(AudacityProject &project, float PanValue)
{
auto &tracks = TrackList::Get( project );
auto &window = ProjectWindow::Get( project );
// count selected wave tracks
const auto range = tracks.Any< WaveTrack >();
const auto selectedRange = range + &Track::IsSelected;
auto count = selectedRange.size();
// iter through them, all if none selected.
for (auto left : count == 0 ? range : selectedRange )
left->SetPan( PanValue );
auto flags = UndoPush::AUTOSAVE;
/*i18n-hint: One or more audio tracks have been panned*/
ProjectHistory::Get( project )
.PushState(_("Panned audio track(s)"), _("Pan Track"), flags);
flags = flags | UndoPush::CONSOLIDATE;
}
enum {
kAlignStartZero = 0,
kAlignStartSelStart,
kAlignStartSelEnd,
kAlignEndSelStart,
kAlignEndSelEnd,
// The next two are only in one subMenu, so more easily handled at the end.
kAlignEndToEnd,
kAlignTogether
};
static const std::initializer_list< ComponentInterfaceSymbol > alignLabels{
{ wxT("StartToZero"), XO("Start to &Zero") },
{ wxT("StartToSelStart"), XO("Start to &Cursor/Selection Start") },
{ wxT("StartToSelEnd"), XO("Start to Selection &End") },
{ wxT("EndToSelStart"), XO("End to Cu&rsor/Selection Start") },
{ wxT("EndToSelEnd"), XO("End to Selection En&d") },
};
const size_t kAlignLabelsCount = alignLabels.end() - alignLabels.begin();
void DoAlign
(AudacityProject &project, int index, bool moveSel)
{
auto &tracks = TrackList::Get( project );
auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
auto &window = ProjectWindow::Get( project );
wxString action;
wxString shortAction;
double delta = 0.0;
double newPos = -1.0;
auto channelRange = tracks.Selected< AudioTrack >();
auto trackRange = tracks.SelectedLeaders< AudioTrack >();
auto FindOffset = []( const Track *pTrack ) {
return TrackList::Channels(pTrack).min( &Track::GetOffset ); };
auto firstTrackOffset = [&]{ return FindOffset( *trackRange.begin() ); };
auto minOffset = [&]{ return trackRange.min( FindOffset ); };
auto avgOffset = [&]{
return trackRange.sum( FindOffset ) /
std::max( size_t(1), trackRange.size() ); };
auto maxEndOffset = [&]{
return std::max(0.0, channelRange.max( &Track::GetEndTime ) ); };
switch(index) {
case kAlignStartZero:
delta = -minOffset();
action = moveSel
/* i18n-hint: In this and similar messages describing editing actions,
the starting or ending points of tracks are re-"aligned" to other
times, and the time selection may be "moved" too. The first
noun -- "start" in this example -- is the object of a verb (not of
an implied preposition "from"). */
? _("Aligned/Moved start to zero")
: _("Aligned start to zero");
/* i18n-hint: This and similar messages give shorter descriptions of
the aligning and moving editing actions */
shortAction = moveSel
? _("Align/Move Start")
: _("Align Start");
break;
case kAlignStartSelStart:
delta = selectedRegion.t0() - minOffset();
action = moveSel
? _("Aligned/Moved start to cursor/selection start")
: _("Aligned start to cursor/selection start");
shortAction = moveSel
? _("Align/Move Start")
: _("Align Start");
break;
case kAlignStartSelEnd:
delta = selectedRegion.t1() - minOffset();
action = moveSel
? _("Aligned/Moved start to selection end")
: _("Aligned start to selection end");
shortAction = moveSel
? _("Align/Move Start")
: _("Align Start");
break;
case kAlignEndSelStart:
delta = selectedRegion.t0() - maxEndOffset();
action = moveSel
? _("Aligned/Moved end to cursor/selection start")
: _("Aligned end to cursor/selection start");
shortAction =
moveSel
? _("Align/Move End")
: _("Align End");
break;
case kAlignEndSelEnd:
delta = selectedRegion.t1() - maxEndOffset();
action = moveSel
? _("Aligned/Moved end to selection end")
: _("Aligned end to selection end");
shortAction =
moveSel
? _("Align/Move End")
: _("Align End");
break;
// index set in alignLabelsNoSync
case kAlignEndToEnd:
newPos = firstTrackOffset();
action = moveSel
? _("Aligned/Moved end to end")
: _("Aligned end to end");
shortAction =
moveSel
? _("Align/Move End to End")
: _("Align End to End");
break;
case kAlignTogether:
newPos = avgOffset();
action = moveSel
? _("Aligned/Moved together")
: _("Aligned together");
shortAction =
moveSel
? _("Align/Move Together")
: _("Align Together");
}
if ((unsigned)index >= kAlignLabelsCount) {
// This is an alignLabelsNoSync command.
for (auto t : tracks.SelectedLeaders< AudioTrack >()) {
// This shifts different tracks in different ways, so no sync-lock
// move.
// Only align Wave and Note tracks end to end.
auto channels = TrackList::Channels(t);
auto trackStart = channels.min( &Track::GetStartTime );
auto trackEnd = channels.max( &Track::GetEndTime );
for (auto channel : channels)
// Move the track
channel->SetOffset(newPos + channel->GetStartTime() - trackStart);
if (index == kAlignEndToEnd)
newPos += (trackEnd - trackStart);
}
if (index == kAlignEndToEnd)
window.DoZoomFit();
}
if (delta != 0.0) {
// For a fixed-distance shift move sync-lock selected tracks also.
for (auto t : tracks.Any() + &Track::IsSelectedOrSyncLockSelected )
t->SetOffset(t->GetOffset() + delta);
}
if (moveSel)
selectedRegion.move(delta);
ProjectHistory::Get( project ).PushState(action, shortAction);
}
#ifdef EXPERIMENTAL_SCOREALIGN
// rough relative amount of time to compute one
// frame of audio or midi, or one cell of matrix, or one iteration
// of smoothing, measured on a 1.9GHz Core 2 Duo in 32-bit mode
// (see COLLECT_TIMING_DATA below)
#define AUDIO_WORK_UNIT 0.004F
#define MIDI_WORK_UNIT 0.0001F
#define MATRIX_WORK_UNIT 0.000002F
#define SMOOTHING_WORK_UNIT 0.000001F
// Write timing data to a file; useful for calibrating AUDIO_WORK_UNIT,
// MIDI_WORK_UNIT, MATRIX_WORK_UNIT, and SMOOTHING_WORK_UNIT coefficients
// Data is written to timing-data.txt; look in
// audacity-src/win/Release/modules/
#define COLLECT_TIMING_DATA
// Audacity Score Align Progress class -- progress reports come here
class ASAProgress final : public SAProgress {
private:
float mTotalWork;
float mFrames[2];
long mTotalCells; // how many matrix cells?
long mCellCount; // how many cells so far?
long mPrevCellCount; // cell_count last reported with Update()
Maybe<ProgressDialog> mProgress;
#ifdef COLLECT_TIMING_DATA
FILE *mTimeFile;
wxDateTime mStartTime;
long iterations;
#endif
public:
ASAProgress() {
smoothing = false;
#ifdef COLLECT_TIMING_DATA
mTimeFile = fopen("timing-data.txt", "w");
#endif
}
~ASAProgress() {
#ifdef COLLECT_TIMING_DATA
fclose(mTimeFile);
#endif
}
void set_phase(int i) override {
float work[2]; // chromagram computation work estimates
float work2, work3 = 0; // matrix and smoothing work estimates
SAProgress::set_phase(i);
#ifdef COLLECT_TIMING_DATA
long ms = 0;
wxDateTime now = wxDateTime::UNow();
wxFprintf(mTimeFile, "Phase %d begins at %s\n",
i, now.FormatTime());
if (i != 0)
ms = now.Subtract(mStartTime).GetMilliseconds().ToLong();
mStartTime = now;
#endif
if (i == 0) {
mCellCount = 0;
for (int j = 0; j < 2; j++) {
mFrames[j] = durations[j] / frame_period;
}
mTotalWork = 0;
for (int j = 0; j < 2; j++) {
work[j] =
(is_audio[j] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[j];
mTotalWork += work[j];
}
mTotalCells = mFrames[0] * mFrames[1];
work2 = mTotalCells * MATRIX_WORK_UNIT;
mTotalWork += work2;
// arbitarily assume 60 iterations to fit smooth segments and
// per frame per iteration is SMOOTHING_WORK_UNIT
if (smoothing) {
work3 =
wxMax(mFrames[0], mFrames[1]) * SMOOTHING_WORK_UNIT * 40;
mTotalWork += work3;
}
#ifdef COLLECT_TIMING_DATA
wxFprintf(mTimeFile,
" mTotalWork (an estimate) = %g\n", mTotalWork);
wxFprintf(mTimeFile, " work0 = %g, frames %g, is_audio %d\n",
work[0], mFrames[0], is_audio[0]);
wxFprintf(mTimeFile, " work1 = %g, frames %g, is_audio %d\n",
work[1], mFrames[1], is_audio[1]);
wxFprintf(mTimeFile, "work2 = %g, work3 = %g\n", work2, work3);
#endif
mProgress.create(_("Synchronize MIDI with Audio"),
_("Synchronizing MIDI and Audio Tracks"));
} else if (i < 3) {
wxFprintf(mTimeFile,
"Phase %d took %d ms for %g frames, coefficient = %g s/frame\n",
i - 1, ms, mFrames[i - 1], (ms * 0.001) / mFrames[i - 1]);
} else if (i == 3) {
wxFprintf(mTimeFile,
"Phase 2 took %d ms for %d cells, coefficient = %g s/cell\n",
ms, mCellCount, (ms * 0.001) / mCellCount);
} else if (i == 4) {
wxFprintf(mTimeFile,
"Phase 3 took %d ms for %d iterations on %g frames, "
"coefficient = %g s per frame per iteration\n",
ms, iterations, wxMax(mFrames[0], mFrames[1]),
(ms * 0.001) / (wxMax(mFrames[0], mFrames[1]) * iterations));
}
}
bool set_feature_progress(float s) override {
float work;
if (phase == 0) {
float f = s / frame_period;
work = (is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * f;
} else if (phase == 1) {
float f = s / frame_period;
work = (is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[0] +
(is_audio[1] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * f;
}
auto updateResult = mProgress->Update((int)(work), (int)(mTotalWork));
return (updateResult == ProgressResult::Success);
}
bool set_matrix_progress(int cells) override {
mCellCount += cells;
float work =
(is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[0] +
(is_audio[1] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[1];
work += mCellCount * MATRIX_WORK_UNIT;
auto updateResult = mProgress->Update((int)(work), (int)(mTotalWork));
return (updateResult == ProgressResult::Success);
}
bool set_smoothing_progress(int i) override {
iterations = i;
float work =
(is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[0] +
(is_audio[1] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[1] +
MATRIX_WORK_UNIT * mFrames[0] * mFrames[1];
work += i * wxMax(mFrames[0], mFrames[1]) * SMOOTHING_WORK_UNIT;
auto updateResult = mProgress->Update((int)(work), (int)(mTotalWork));
return (updateResult == ProgressResult::Success);
}
};
long mixer_process(void *mixer, float **buffer, long n)
{
Mixer *mix = (Mixer *) mixer;
long frame_count = mix->Process(std::max(0L, n));
*buffer = (float *) mix->GetBuffer();
return frame_count;
}
#endif // EXPERIMENTAL_SCOREALIGN
enum{
kAudacitySortByTime = (1 << 1),
kAudacitySortByName = (1 << 2),
};
void DoSortTracks( AudacityProject &project, int flags )
{
auto GetTime = [](const Track *t) {
return t->TypeSwitch< double >(
[&](const WaveTrack* w) {
auto stime = w->GetEndTime();
int ndx;
for (ndx = 0; ndx < w->GetNumClips(); ndx++) {
const auto c = w->GetClipByIndex(ndx);
if (c->GetNumSamples() == 0)
continue;
stime = std::min(stime, c->GetStartTime());
}
return stime;
},
[&](const LabelTrack* l) {
return l->GetStartTime();
}
);
};
size_t ndx = 0;
// This one place outside of TrackList where we must use undisguised
// std::list iterators! Avoid this elsewhere!
std::vector<TrackNodePointer> arr;
auto &tracks = TrackList::Get( project );
arr.reserve(tracks.size());
// First find the permutation.
// This routine, very unusually, deals with the underlying stl list
// iterators, not with TrackIter! Dangerous!
for (auto iter = tracks.ListOfTracks::begin(),
end = tracks.ListOfTracks::end(); iter != end; ++iter) {
const auto &track = *iter;
if ( !track->IsLeader() )
// keep channels contiguous
ndx++;
else {
auto size = arr.size();
for (ndx = 0; ndx < size;) {
Track &arrTrack = **arr[ndx].first;
auto channels = TrackList::Channels(&arrTrack);
if(flags & kAudacitySortByName) {
//do case insensitive sort - cmpNoCase returns less than zero if
// the string is 'less than' its argument
//also if we have case insensitive equality, then we need to sort
// by case as well
//We sort 'b' before 'B' accordingly. We uncharacteristically
// use greater than for the case sensitive
//compare because 'b' is greater than 'B' in ascii.
auto cmpValue = track->GetName().CmpNoCase(arrTrack.GetName());
if ( cmpValue < 0 ||
( 0 == cmpValue &&
track->GetName().CompareTo(arrTrack.GetName()) > 0 ) )
break;
}
//sort by time otherwise
else if(flags & kAudacitySortByTime) {
auto time1 = TrackList::Channels(track.get()).min( GetTime );
//get candidate's (from sorted array) time
auto time2 = channels.min( GetTime );
if (time1 < time2)
break;
}
ndx += channels.size();
}
}
arr.insert(arr.begin() + ndx, TrackNodePointer{iter, &tracks});
}
// Now apply the permutation
tracks.Permute(arr);
}
void SetTrackGain(AudacityProject &project, WaveTrack * wt, LWSlider * slider)
{
wxASSERT(wt);
float newValue = slider->Get();
for (auto channel : TrackList::Channels(wt))
channel->SetGain(newValue);
ProjectHistory::Get( project )
.PushState(_("Adjusted gain"), _("Gain"), UndoPush::CONSOLIDATE);
TrackPanel::Get( project ).RefreshTrack(wt);
}
void SetTrackPan(AudacityProject &project, WaveTrack * wt, LWSlider * slider)
{
wxASSERT(wt);
float newValue = slider->Get();
for (auto channel : TrackList::Channels(wt))
channel->SetPan(newValue);
ProjectHistory::Get( project )
.PushState(_("Adjusted Pan"), _("Pan"), UndoPush::CONSOLIDATE);
TrackPanel::Get( project ).RefreshTrack(wt);
}
}
namespace TrackActions {
// Menu handler functions
struct Handler : CommandHandlerObject {
void OnNewWaveTrack(const CommandContext &context)
{
auto &project = context.project;
const auto &settings = ProjectSettings::Get( project );
auto &tracks = TrackList::Get( project );
auto &trackFactory = TrackFactory::Get( project );
auto &window = ProjectWindow::Get( project );
auto defaultFormat = settings.GetDefaultFormat();
auto rate = settings.GetRate();
auto t = tracks.Add( trackFactory.NewWaveTrack( defaultFormat, rate ) );
SelectUtilities::SelectNone( project );
t->SetSelected(true);
ProjectHistory::Get( project )
.PushState(_("Created new audio track"), _("New Track"));
TrackFocus::Get(project).Set(t);
t->EnsureVisible();
}
void OnNewStereoTrack(const CommandContext &context)
{
auto &project = context.project;
const auto &settings = ProjectSettings::Get( project );
auto &tracks = TrackList::Get( project );
auto &trackFactory = TrackFactory::Get( project );
auto &window = ProjectWindow::Get( project );
auto defaultFormat = settings.GetDefaultFormat();
auto rate = settings.GetRate();
SelectUtilities::SelectNone( project );
auto left = tracks.Add( trackFactory.NewWaveTrack( defaultFormat, rate ) );
left->SetSelected(true);
auto right = tracks.Add( trackFactory.NewWaveTrack( defaultFormat, rate ) );
right->SetSelected(true);
tracks.GroupChannels(*left, 2);
ProjectHistory::Get( project )
.PushState(_("Created new stereo audio track"), _("New Track"));
TrackFocus::Get(project).Set(left);
left->EnsureVisible();
}
void OnNewLabelTrack(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = TrackList::Get( project );
auto &trackFactory = TrackFactory::Get( project );
auto &window = ProjectWindow::Get( project );
auto t = tracks.Add( trackFactory.NewLabelTrack() );
SelectUtilities::SelectNone( project );
t->SetSelected(true);
ProjectHistory::Get( project )
.PushState(_("Created new label track"), _("New Track"));
TrackFocus::Get(project).Set(t);
t->EnsureVisible();
}
void OnNewTimeTrack(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = TrackList::Get( project );
auto &trackFactory = TrackFactory::Get( project );
auto &window = ProjectWindow::Get( project );
if ( *tracks.Any<TimeTrack>().begin() ) {
AudacityMessageBox(_("This version of Audacity only allows one time track for each project window."));
return;
}
auto t = tracks.AddToHead( trackFactory.NewTimeTrack() );
SelectUtilities::SelectNone( project );
t->SetSelected(true);
ProjectHistory::Get( project )
.PushState(_("Created new time track"), _("New Track"));
TrackFocus::Get(project).Set(t);
t->EnsureVisible();
}
void OnStereoToMono(const CommandContext &context)
{
EffectManager::DoEffect(
EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono")),
context,
EffectManager::kConfigured);
}
void OnMixAndRender(const CommandContext &context)
{
auto &project = context.project;
DoMixAndRender(project, false);
}
void OnMixAndRenderToNewTrack(const CommandContext &context)
{
auto &project = context.project;
DoMixAndRender(project, true);
}
void OnResample(const CommandContext &context)
{
auto &project = context.project;
const auto &settings = ProjectSettings::Get( project );
auto projectRate = settings.GetRate();
auto &tracks = TrackList::Get( project );
auto &undoManager = UndoManager::Get( project );
auto &window = ProjectWindow::Get( project );
int newRate;
while (true)
{
wxDialogWrapper dlg(&window, wxID_ANY, wxString(_("Resample")));
ShuttleGui S(&dlg, eIsCreating);
wxString rate;
wxComboBox *cb;
rate.Printf(wxT("%ld"), lrint(projectRate));
wxArrayStringEx rates{
wxT("8000") ,
wxT("11025") ,
wxT("16000") ,
wxT("22050") ,
wxT("32000") ,
wxT("44100") ,
wxT("48000") ,
wxT("88200") ,
wxT("96000") ,
wxT("176400") ,
wxT("192000") ,
wxT("352800") ,
wxT("384000") ,
};
S.StartVerticalLay(true);
{
S.AddSpace(-1, 15);
S.StartHorizontalLay(wxCENTER, false);
{
cb = S.AddCombo(_("New sample rate (Hz):"),
rate,
rates);
}
S.EndHorizontalLay();
S.AddSpace(-1, 15);
S.AddStandardButtons();
}
S.EndVerticalLay();
dlg.Layout();
dlg.Fit();
dlg.Center();
if (dlg.ShowModal() != wxID_OK)
{
return; // user cancelled dialog
}
long lrate;
if (cb->GetValue().ToLong(&lrate) && lrate >= 1 && lrate <= 1000000)
{
newRate = (int)lrate;
break;
}
AudacityMessageBox(_("The entered value is invalid"), _("Error"),
wxICON_ERROR, &window);
}
int ndx = 0;
auto flags = UndoPush::AUTOSAVE;
for (auto wt : tracks.Selected< WaveTrack >())
{
wxString msg;
msg.Printf(_("Resampling track %d"), ++ndx);
ProgressDialog progress(_("Resample"), msg);
// The resampling of a track may be stopped by the user. This might
// leave a track with multiple clips in a partially resampled state.
// But the thrown exception will cause rollback in the application
// level handler.
wt->Resample(newRate, &progress);
// Each time a track is successfully, completely resampled,
// commit that to the undo stack. The second and later times,
// consolidate.
ProjectHistory::Get( project ).PushState(
_("Resampled audio track(s)"), _("Resample Track"), flags);
flags = flags | UndoPush::CONSOLIDATE;
}
undoManager.StopConsolidating();
// Need to reset
window.FinishAutoScroll();
}
void OnRemoveTracks(const CommandContext &context)
{
TrackUtilities::DoRemoveTracks( context.project );
}
void OnMuteAllTracks(const CommandContext &context)
{
auto &project = context.project;
const auto &settings = ProjectSettings::Get( project );
auto &tracks = TrackList::Get( project );
auto &window = ProjectWindow::Get( project );
auto soloSimple = settings.IsSoloSimple();
auto soloNone = settings.IsSoloNone();
for (auto pt : tracks.Any<PlayableTrack>())
{
pt->SetMute(true);
if (soloSimple || soloNone)
pt->SetSolo(false);
}
ProjectHistory::Get( project ).ModifyState(true);
}
void OnUnmuteAllTracks(const CommandContext &context)
{
auto &project = context.project;
const auto &settings = ProjectSettings::Get( project );
auto &tracks = TrackList::Get( project );
auto &window = ProjectWindow::Get( project );
auto soloSimple = settings.IsSoloSimple();
auto soloNone = settings.IsSoloNone();
for (auto pt : tracks.Any<PlayableTrack>())
{
pt->SetMute(false);
if (soloSimple || soloNone)
pt->SetSolo(false);
}
ProjectHistory::Get( project ).ModifyState(true);
}
void OnPanLeft(const CommandContext &context)
{
auto &project = context.project;
DoPanTracks( project, -1.0);
}
void OnPanRight(const CommandContext &context)
{
auto &project = context.project;
DoPanTracks( project, 1.0);
}
void OnPanCenter(const CommandContext &context)
{
auto &project = context.project;
DoPanTracks( project, 0.0);
}
void OnAlignNoSync(const CommandContext &context)
{
auto &project = context.project;
DoAlign(project,
context.index + kAlignLabelsCount, false);
}
void OnAlign(const CommandContext &context)
{
auto &project = context.project;
bool bMoveWith;
gPrefs->Read(wxT("/GUI/MoveSelectionWithTracks"), &bMoveWith, false);
DoAlign(project, context.index, bMoveWith);
}
/*
// Now handled in OnAlign.
void OnAlignMoveSel(int index)
{
DoAlign(index, true);
}
*/
void OnMoveSelectionWithTracks(const CommandContext &WXUNUSED(context) )
{
bool bMoveWith;
gPrefs->Read(wxT("/GUI/MoveSelectionWithTracks"), &bMoveWith, false);
gPrefs->Write(wxT("/GUI/MoveSelectionWithTracks"), !bMoveWith);
gPrefs->Flush();
}
#ifdef EXPERIMENTAL_SCOREALIGN
void OnScoreAlign(const CommandContext &context)
{
auto &project = context.project;
auto tracks = project.GetTracks();
const auto rate = project.GetRate();
int numWaveTracksSelected = 0;
int numNoteTracksSelected = 0;
int numOtherTracksSelected = 0;
double endTime = 0.0;
// Iterate through once to make sure that there is exactly
// one WaveTrack and one NoteTrack selected.
GetTracks()->Selected().Visit(
[&](WaveTrack *wt) {
numWaveTracksSelected++;
endTime = endTime > wt->GetEndTime() ? endTime : wt->GetEndTime();
},
[&](NoteTrack *) {
numNoteTracksSelected++;
},
[&](Track*) {
numOtherTracksSelected++;
}
);
if(numWaveTracksSelected == 0 ||
numNoteTracksSelected != 1 ||
numOtherTracksSelected != 0){
AudacityMessageBox(
_("Please select at least one audio track and one MIDI track."));
return;
}
// Creating the dialog also stores dialog into gScoreAlignDialog so
// that it can be delted by CloseScoreAlignDialog() either here or
// if the program is quit by the user while the dialog is up.
ScoreAlignParams params;
// safe because the class maintains a global resource pointer
safenew ScoreAlignDialog(params);
CloseScoreAlignDialog();
if (params.mStatus != wxID_OK) return;
// We're going to do it.
//pushing the state before the change is wrong (I think)
//PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio"));
// Make a copy of the note track in case alignment is canceled or fails
auto holder = nt->Duplicate();
auto alignedNoteTrack = static_cast<NoteTrack*>(holder.get());
// Remove offset from NoteTrack because audio is
// mixed starting at zero and incorporating clip offsets.
if (alignedNoteTrack->GetOffset() < 0) {
// remove the negative offset data before alignment
nt->Clear(alignedNoteTrack->GetOffset(), 0);
} else if (alignedNoteTrack->GetOffset() > 0) {
alignedNoteTrack->Shift(alignedNoteTrack->GetOffset());
}
alignedNoteTrack->SetOffset(0);
WaveTrackConstArray waveTracks =
tracks->GetWaveTrackConstArray(true /* selectionOnly */);
int result;
{
Mixer mix(
waveTracks, // const WaveTrackConstArray &inputTracks
false, // mayThrow -- is this right?
Mixer::WarpOptions{
*tracks->Any<const TimeTrack >().begin()
}, // const WarpOptions &warpOptions
0.0, // double startTime
endTime, // double stopTime
2, // int numOutChannels
44100u, // size_t outBufferSize
true, // bool outInterleaved
rate, // double outRate
floatSample, // sampleFormat outFormat
true, // bool highQuality = true
NULL); // MixerSpec *mixerSpec = NULL
ASAProgress progress;
// There's a lot of adjusting made to incorporate the note track offset into
// the note track while preserving the position of notes within beats and
// measures. For debugging, you can see just the pre-scorealign note track
// manipulation by setting SKIP_ACTUAL_SCORE_ALIGNMENT. You could then, for
// example, save the modified note track in ".gro" form to read the details.
//#define SKIP_ACTUAL_SCORE_ALIGNMENT 1
#ifndef SKIP_ACTUAL_SCORE_ALIGNMENT
result = scorealign((void *) &mix, &mixer_process,
2 /* channels */, 44100.0 /* srate */, endTime,
&alignedNoteTrack->GetSeq(), &progress, params);
#else
result = SA_SUCCESS;
#endif
}
if (result == SA_SUCCESS) {
tracks->Replace(nt, holder);
AudacityMessageBox(wxString::Format(
_("Alignment completed: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."),
params.mMidiStart, params.mMidiEnd,
params.mAudioStart, params.mAudioEnd));
ProjectHistory::Get( project )
.PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio"));
} else if (result == SA_TOOSHORT) {
AudacityMessageBox(wxString::Format(
_("Alignment error: input too short: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."),
params.mMidiStart, params.mMidiEnd,
params.mAudioStart, params.mAudioEnd));
} else if (result == SA_CANCEL) {
// wrong way to recover...
//GetActiveProject()->OnUndo(); // recover any changes to note track
return; // no message when user cancels alignment
} else {
//GetActiveProject()->OnUndo(); // recover any changes to note track
AudacityMessageBox(_("Internal error reported by alignment process."));
}
}
#endif /* EXPERIMENTAL_SCOREALIGN */
void OnSortTime(const CommandContext &context)
{
auto &project = context.project;
DoSortTracks(project, kAudacitySortByTime);
ProjectHistory::Get( project )
.PushState(_("Tracks sorted by time"), _("Sort by Time"));
}
void OnSortName(const CommandContext &context)
{
auto &project = context.project;
DoSortTracks(project, kAudacitySortByName);
ProjectHistory::Get( project )
.PushState(_("Tracks sorted by name"), _("Sort by Name"));
}
void OnSyncLock(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
bool bSyncLockTracks;
gPrefs->Read(wxT("/GUI/SyncLockTracks"), &bSyncLockTracks, false);
gPrefs->Write(wxT("/GUI/SyncLockTracks"), !bSyncLockTracks);
gPrefs->Flush();
// Toolbar, project sync-lock handled within
MenuManager::ModifyAllProjectToolbarMenus();
trackPanel.Refresh(false);
}
///The following methods operate controls on specified tracks,
///This will pop up the track panning dialog for specified track
void OnTrackPan(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
const auto track = TrackFocus::Get( project ).Get();
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = WaveTrackControls::PanSlider( trackPanel, *wt );
if (slider->ShowDialog())
SetTrackPan(project, wt, slider);
});
}
void OnTrackPanLeft(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
const auto track = TrackFocus::Get( project ).Get();
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = WaveTrackControls::PanSlider( trackPanel, *wt );
slider->Decrease(1);
SetTrackPan(project, wt, slider);
});
}
void OnTrackPanRight(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
const auto track = TrackFocus::Get( project ).Get();
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = WaveTrackControls::PanSlider( trackPanel, *wt );
slider->Increase(1);
SetTrackPan(project, wt, slider);
});
}
void OnTrackGain(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
/// This will pop up the track gain dialog for specified track
const auto track = TrackFocus::Get( project ).Get();
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = WaveTrackControls::GainSlider( trackPanel, *wt );
if (slider->ShowDialog())
SetTrackGain(project, wt, slider);
});
}
void OnTrackGainInc(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
const auto track = TrackFocus::Get( project ).Get();
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = WaveTrackControls::GainSlider( trackPanel, *wt );
slider->Increase(1);
SetTrackGain(project, wt, slider);
});
}
void OnTrackGainDec(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
const auto track = TrackFocus::Get( project ).Get();
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = WaveTrackControls::GainSlider( trackPanel, *wt );
slider->Decrease(1);
SetTrackGain(project, wt, slider);
});
}
void OnTrackMenu(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
trackPanel.OnTrackMenu();
}
void OnTrackMute(const CommandContext &context)
{
auto &project = context.project;
const auto track = TrackFocus::Get( project ).Get();
if (track) track->TypeSwitch( [&](PlayableTrack *t) {
TrackUtilities::DoTrackMute(project, t, false);
});
}
void OnTrackSolo(const CommandContext &context)
{
auto &project = context.project;
const auto track = TrackFocus::Get( project ).Get();
if (track) track->TypeSwitch( [&](PlayableTrack *t) {
TrackUtilities::DoTrackSolo(project, t, false);
});
}
void OnTrackClose(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
const auto t = TrackFocus::Get( project ).Get();
if (!t)
return;
auto isAudioActive = ProjectAudioIO::Get( project ).IsAudioActive();
if (isAudioActive)
{
ProjectStatus::Get( project ).Set(
_("Can't delete track with active audio"));
wxBell();
return;
}
TrackUtilities::DoRemoveTrack(project, t);
trackPanel.UpdateViewIfNoTracks();
trackPanel.Refresh(false);
}
void OnTrackMoveUp(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
auto &tracks = TrackList::Get( project );
const auto focusedTrack = TrackFocus::Get( project ).Get();
if (tracks.CanMoveUp(focusedTrack)) {
DoMoveTrack(project, focusedTrack, TrackUtilities::OnMoveUpID);
trackPanel.Refresh(false);
}
}
void OnTrackMoveDown(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
auto &tracks = TrackList::Get( project );
const auto focusedTrack = TrackFocus::Get( project ).Get();
if (tracks.CanMoveDown(focusedTrack)) {
DoMoveTrack(project, focusedTrack, TrackUtilities::OnMoveDownID);
trackPanel.Refresh(false);
}
}
void OnTrackMoveTop(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
auto &tracks = TrackList::Get( project );
const auto focusedTrack = TrackFocus::Get( project ).Get();
if (tracks.CanMoveUp(focusedTrack)) {
DoMoveTrack(project, focusedTrack, TrackUtilities::OnMoveTopID);
trackPanel.Refresh(false);
}
}
void OnTrackMoveBottom(const CommandContext &context)
{
auto &project = context.project;
auto &trackPanel = TrackPanel::Get( project );
auto &tracks = TrackList::Get( project );
const auto focusedTrack = TrackFocus::Get( project ).Get();
if (tracks.CanMoveDown(focusedTrack)) {
DoMoveTrack(project, focusedTrack, TrackUtilities::OnMoveBottomID);
trackPanel.Refresh(false);
}
}
}; // struct Handler
} // namespace
static CommandHandlerObject &findCommandHandler(AudacityProject &) {
// Handler is not stateful. Doesn't need a factory registered with
// AudacityProject.
static TrackActions::Handler instance;
return instance;
};
// Menu definitions
#define FN(X) findCommandHandler, \
static_cast<CommandFunctorPointer>(& TrackActions::Handler :: X)
MenuTable::BaseItemPtr TracksMenu( AudacityProject & )
{
// Tracks Menu (formerly Project Menu)
using namespace MenuTable;
using Options = CommandManager::Options;
return Menu( _("&Tracks"),
Menu( _("Add &New"),
Command( wxT("NewMonoTrack"), XXO("&Mono Track"), FN(OnNewWaveTrack),
AudioIONotBusyFlag, wxT("Ctrl+Shift+N") ),
Command( wxT("NewStereoTrack"), XXO("&Stereo Track"),
FN(OnNewStereoTrack), AudioIONotBusyFlag ),
Command( wxT("NewLabelTrack"), XXO("&Label Track"),
FN(OnNewLabelTrack), AudioIONotBusyFlag ),
Command( wxT("NewTimeTrack"), XXO("&Time Track"),
FN(OnNewTimeTrack), AudioIONotBusyFlag )
),
//////////////////////////////////////////////////////////////////////////
Separator(),
Menu( _("Mi&x"),
// Stereo to Mono is an oddball command that is also subject to control
// by the plug-in manager, as if an effect. Decide whether to show or
// hide it.
[](AudacityProject&) -> BaseItemPtr {
const PluginID ID =
EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono"));
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
if (plug && plug->IsEnabled())
return Command( wxT("Stereo to Mono"),
XXO("Mix Stereo Down to &Mono"), FN(OnStereoToMono),
AudioIONotBusyFlag | StereoRequiredFlag |
WaveTracksSelectedFlag );
else
return {};
},
Command( wxT("MixAndRender"), XXO("Mi&x and Render"),
FN(OnMixAndRender),
AudioIONotBusyFlag | WaveTracksSelectedFlag ),
Command( wxT("MixAndRenderToNewTrack"),
XXO("Mix and Render to Ne&w Track"),
FN(OnMixAndRenderToNewTrack),
AudioIONotBusyFlag | WaveTracksSelectedFlag, wxT("Ctrl+Shift+M") )
),
Command( wxT("Resample"), XXO("&Resample..."), FN(OnResample),
AudioIONotBusyFlag | WaveTracksSelectedFlag ),
Separator(),
Command( wxT("RemoveTracks"), XXO("Remo&ve Tracks"), FN(OnRemoveTracks),
AudioIONotBusyFlag | AnyTracksSelectedFlag ),
Separator(),
Menu( _("M&ute/Unmute"),
Command( wxT("MuteAllTracks"), XXO("&Mute All Tracks"),
FN(OnMuteAllTracks), AudioIONotBusyFlag, wxT("Ctrl+U") ),
Command( wxT("UnmuteAllTracks"), XXO("&Unmute All Tracks"),
FN(OnUnmuteAllTracks), AudioIONotBusyFlag, wxT("Ctrl+Shift+U") )
),
Menu( _("&Pan"),
// As Pan changes are not saved on Undo stack,
// pan settings for all tracks
// in the project could very easily be lost unless we
// require the tracks to be selected.
Command( wxT("PanLeft"), XXO("&Left"), FN(OnPanLeft),
TracksSelectedFlag,
Options{}.LongName( XO("Pan Left") ) ),
Command( wxT("PanRight"), XXO("&Right"), FN(OnPanRight),
TracksSelectedFlag,
Options{}.LongName( XO("Pan Right") ) ),
Command( wxT("PanCenter"), XXO("&Center"), FN(OnPanCenter),
TracksSelectedFlag,
Options{}.LongName( XO("Pan Center") ) )
),
Separator(),
//////////////////////////////////////////////////////////////////////////
Menu( _("&Align Tracks"), //_("Just Move Tracks"),
// Mutual alignment of tracks independent of selection or zero
CommandGroup(wxT("Align"),
{
{ wxT("EndToEnd"), XO("&Align End to End") },
{ wxT("Together"), XO("Align &Together") },
},
FN(OnAlignNoSync), AudioIONotBusyFlag | TracksSelectedFlag),
Separator(),
// Alignment commands using selection or zero
CommandGroup(wxT("Align"),
alignLabels,
FN(OnAlign), AudioIONotBusyFlag | TracksSelectedFlag),
Separator(),
Command( wxT("MoveSelectionWithTracks"),
XXO("&Move Selection with Tracks (on/off)"),
FN(OnMoveSelectionWithTracks),
AlwaysEnabledFlag,
Options{}.CheckState(
gPrefs->Read(wxT("/GUI/MoveSelectionWithTracks"), 0L ) ) )
),
#if 0
// TODO: Can these labels be made clearer?
// Do we need this sub-menu at all?
Menu( _("Move Sele&ction and Tracks"), {
CommandGroup(wxT("AlignMove"), alignLabels,
FN(OnAlignMoveSel), AudioIONotBusyFlag | TracksSelectedFlag),
} ),
#endif
//////////////////////////////////////////////////////////////////////////
#ifdef EXPERIMENTAL_SCOREALIGN
Command( wxT("ScoreAlign"), XXO("Synchronize MIDI with Audio"),
FN(OnScoreAlign),
AudioIONotBusyFlag | NoteTracksSelectedFlag | WaveTracksSelectedFlag ),
#endif // EXPERIMENTAL_SCOREALIGN
//////////////////////////////////////////////////////////////////////////
Menu( _("S&ort Tracks"),
Command( wxT("SortByTime"), XXO("By &Start Time"), FN(OnSortTime),
TracksExistFlag,
Options{}.LongName( XO("Sort by Time") ) ),
Command( wxT("SortByName"), XXO("By &Name"), FN(OnSortName),
TracksExistFlag,
Options{}.LongName( XO("Sort by Name") ) )
)
//////////////////////////////////////////////////////////////////////////
#ifdef EXPERIMENTAL_SYNC_LOCK
,
Separator(),
Command( wxT("SyncLock"), XXO("Sync-&Lock Tracks (on/off)"),
FN(OnSyncLock), AlwaysEnabledFlag,
Options{}.CheckState( gPrefs->Read(wxT("/GUI/SyncLockTracks"), 0L) ) )
#endif
);
}
MenuTable::BaseItemPtr ExtraTrackMenu( AudacityProject & )
{
using namespace MenuTable;
return Menu( _("&Track"),
Command( wxT("TrackPan"), XXO("Change P&an on Focused Track..."),
FN(OnTrackPan),
TrackPanelHasFocus | TracksExistFlag, wxT("Shift+P") ),
Command( wxT("TrackPanLeft"), XXO("Pan &Left on Focused Track"),
FN(OnTrackPanLeft),
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Left") ),
Command( wxT("TrackPanRight"), XXO("Pan &Right on Focused Track"),
FN(OnTrackPanRight),
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Right") ),
Command( wxT("TrackGain"), XXO("Change Gai&n on Focused Track..."),
FN(OnTrackGain),
TrackPanelHasFocus | TracksExistFlag, wxT("Shift+G") ),
Command( wxT("TrackGainInc"), XXO("&Increase Gain on Focused Track"),
FN(OnTrackGainInc),
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Up") ),
Command( wxT("TrackGainDec"), XXO("&Decrease Gain on Focused Track"),
FN(OnTrackGainDec),
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Down") ),
Command( wxT("TrackMenu"), XXO("Op&en Menu on Focused Track..."),
FN(OnTrackMenu),
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+M\tskipKeydown") ),
Command( wxT("TrackMute"), XXO("M&ute/Unmute Focused Track"),
FN(OnTrackMute),
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+U") ),
Command( wxT("TrackSolo"), XXO("&Solo/Unsolo Focused Track"),
FN(OnTrackSolo),
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+S") ),
Command( wxT("TrackClose"), XXO("&Close Focused Track"),
FN(OnTrackClose),
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag,
wxT("Shift+C") ),
Command( wxT("TrackMoveUp"), XXO("Move Focused Track U&p"),
FN(OnTrackMoveUp),
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag ),
Command( wxT("TrackMoveDown"), XXO("Move Focused Track Do&wn"),
FN(OnTrackMoveDown),
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag ),
Command( wxT("TrackMoveTop"), XXO("Move Focused Track to T&op"),
FN(OnTrackMoveTop),
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag ),
Command( wxT("TrackMoveBottom"), XXO("Move Focused Track to &Bottom"),
FN(OnTrackMoveBottom),
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag )
);
}
#undef FN