1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-01 08:29:27 +02:00

Slim AudacityProject some...

... move some specialized operations with few uses into menus/
This commit is contained in:
Paul Licameli 2018-10-27 20:35:20 -04:00
commit 6fe0c6dd45
13 changed files with 521 additions and 509 deletions

View File

@ -118,6 +118,7 @@ void DoSelectSomething( AudacityProject &project );
}
namespace ViewActions {
double GetZoomOfToFit( const AudacityProject &project );
void DoZoomFit( AudacityProject &project );
void DoZoomFitV( AudacityProject &project );
}
@ -139,6 +140,12 @@ namespace TrackActions {
};
/// Move a track up, down, to top or to bottom.
void DoMoveTrack( AudacityProject &project, Track* target, MoveChoice choice );
// "exclusive" mute means mute the chosen track and unmute all others.
void DoTrackMute( AudacityProject &project, Track *pTrack, bool exclusive );
// Type of solo (standard or simple) follows the set preference, unless
// exclusive == true, which causes the opposite behavior.
void DoTrackSolo( AudacityProject &project, Track *pTrack, bool exclusive );
void DoRemoveTrack( AudacityProject &project, Track * toRemove );
void DoRemoveTracks( AudacityProject & );
}

View File

@ -10,6 +10,7 @@
**********************************************************************/
#include "Audacity.h"
#include "Menus.h"
#include "MixerBoard.h"
#include <cfloat>
@ -744,7 +745,8 @@ void MixerTrackCluster::OnSlider_Pan(wxCommandEvent& WXUNUSED(event))
void MixerTrackCluster::OnButton_Mute(wxCommandEvent& WXUNUSED(event))
{
mProject->HandleTrackMute(mTrack.get(), mToggleButton_Mute->WasShiftDown());
TrackActions::DoTrackMute(
*mProject, mTrack.get(), mToggleButton_Mute->WasShiftDown());
mToggleButton_Mute->SetAlternateIdx(mTrack->GetSolo() ? 1 : 0);
// Update the TrackPanel correspondingly.
@ -759,7 +761,8 @@ void MixerTrackCluster::OnButton_Mute(wxCommandEvent& WXUNUSED(event))
void MixerTrackCluster::OnButton_Solo(wxCommandEvent& WXUNUSED(event))
{
mProject->HandleTrackSolo(mTrack.get(), mToggleButton_Solo->WasShiftDown());
TrackActions::DoTrackSolo(
*mProject, mTrack.get(), mToggleButton_Solo->WasShiftDown());
bool bIsSolo = mTrack->GetSolo();
mToggleButton_Mute->SetAlternateIdx(bIsSolo ? 1 : 0);

View File

@ -2951,7 +2951,7 @@ AudacityProject *AudacityProject::OpenProject(
pProject->OpenFile( fileNameArg, addtohistory );
pNewProject = nullptr;
if( pProject && pProject->mIsRecovered )
pProject->Zoom( pProject->GetZoomOfToFit() );
pProject->Zoom( ViewActions::GetZoomOfToFit( *pProject ) );
return pProject;
}
@ -4739,26 +4739,6 @@ void AudacityProject::ClearClipboard()
}
}
void AudacityProject::Clear()
{
for (auto n : GetTracks()->Any()) {
if (n->GetSelected() || n->IsSyncLockSelected()) {
n->Clear(mViewInfo.selectedRegion.t0(), mViewInfo.selectedRegion.t1());
}
}
double seconds = mViewInfo.selectedRegion.duration();
mViewInfo.selectedRegion.collapseToT0();
PushState(wxString::Format(_("Deleted %.2f seconds at t=%.2f"),
seconds,
mViewInfo.selectedRegion.t0()),
_("Delete"));
RedrawProject();
}
// Utility function called by other zoom methods
void AudacityProject::Zoom(double level)
{
@ -5009,155 +4989,6 @@ void AudacityProject::OnTimer(wxTimerEvent& WXUNUSED(event))
RestartTimer();
}
//get regions selected by selected labels
//removes unnecessary regions, overlapping regions are merged
//regions memory need to be deleted by the caller
void AudacityProject::GetRegionsByLabel( Regions &regions )
{
//determine labeled regions
for (auto lt : GetTracks()->Selected< LabelTrack >()) {
for (int i = 0; i < lt->GetNumLabels(); i++)
{
const LabelStruct *ls = lt->GetLabel(i);
if (ls->selectedRegion.t0() >= mViewInfo.selectedRegion.t0() &&
ls->selectedRegion.t1() <= mViewInfo.selectedRegion.t1())
regions.push_back(Region(ls->getT0(), ls->getT1()));
}
}
//anything to do ?
if( regions.size() == 0 )
return;
//sort and remove unnecessary regions
std::sort(regions.begin(), regions.end());
unsigned int selected = 1;
while( selected < regions.size() )
{
const Region &cur = regions.at( selected );
Region &last = regions.at( selected - 1 );
if( cur.start < last.end )
{
if( cur.end > last.end )
last.end = cur.end;
regions.erase( regions.begin() + selected );
}
else
selected++;
}
}
//Executes the edit function on all selected wave tracks with
//regions specified by selected labels
//If No tracks selected, function is applied on all tracks
//If the function replaces the selection with audio of a different length,
// bSyncLockedTracks should be set true to perform the same action on sync-lock selected
// tracks.
void AudacityProject::EditByLabel( EditFunction action,
bool bSyncLockedTracks )
{
Regions regions;
GetRegionsByLabel( regions );
if( regions.size() == 0 )
return;
// if at least one wave track is selected
// apply only on the selected track
const bool allTracks = (GetTracks()->Selected< WaveTrack >()).empty();
//Apply action on wavetracks starting from
//labeled regions in the end. This is to correctly perform
//actions like 'Delete' which collapse the track area.
for (auto wt : GetTracks()->Any<WaveTrack>())
{
if (allTracks || wt->GetSelected() || (bSyncLockedTracks && wt->IsSyncLockSelected()))
{
for (int i = (int)regions.size() - 1; i >= 0; i--) {
const Region &region = regions.at(i);
(wt->*action)(region.start, region.end);
}
}
}
}
//Executes the edit function on all selected wave tracks with
//regions specified by selected labels
//If No tracks selected, function is applied on all tracks
//Functions copy the edited regions to clipboard, possibly in multiple tracks
//This probably should not be called if *action() changes the timeline, because
// the copy needs to happen by track, and the timeline change by group.
void AudacityProject::EditClipboardByLabel( EditDestFunction action )
{
Regions regions;
GetRegionsByLabel( regions );
if( regions.size() == 0 )
return;
// if at least one wave track is selected
// apply only on the selected track
const bool allTracks = (GetTracks()->Selected< WaveTrack >()).empty();
ClearClipboard();
auto pNewClipboard = TrackList::Create();
auto &newClipboard = *pNewClipboard;
//Apply action on wavetracks starting from
//labeled regions in the end. This is to correctly perform
//actions like 'Cut' which collapse the track area.
for(auto wt :
GetTracks()->Any<WaveTrack>()
+ (allTracks ? &Track::Any : &Track::IsSelected)
) {
// This track accumulates the needed clips, right to left:
Track::Holder merged;
for( int i = (int)regions.size() - 1; i >= 0; i-- )
{
const Region &region = regions.at(i);
auto dest = ( wt->*action )( region.start, region.end );
if( dest )
{
Track::FinishCopy( wt, dest.get() );
if( !merged )
merged = std::move(dest);
else
{
// Paste to the beginning; unless this is the first region,
// offset the track to account for time between the regions
if (i < (int)regions.size() - 1)
merged->Offset(
regions.at(i + 1).start - region.end);
// dest may have a placeholder clip at the end that is
// removed when pasting, which is okay because we proceed
// right to left. Any placeholder already in merged is kept.
// Only the rightmost placeholder is important in the final
// result.
merged->Paste( 0.0 , dest.get() );
}
}
else // nothing copied but there is a 'region', so the 'region' must be a 'point label' so offset
if (i < (int)regions.size() - 1)
if (merged)
merged->Offset(
regions.at(i + 1).start - region.end);
}
if( merged )
newClipboard.Add( std::move(merged) );
}
// Survived possibility of exceptions. Commit changes to the clipboard now.
newClipboard.Swap(*msClipboard);
wxTheApp->AddPendingEvent( wxCommandEvent{ EVT_CLIPBOARD_CHANGE } );
msClipT0 = regions.front().start;
msClipT1 = regions.back().end;
}
// TrackPanel callback method
void AudacityProject::TP_DisplayStatusMessage(const wxString &msg)
{
@ -5481,176 +5312,6 @@ void AudacityProject::SetSyncLock(bool flag)
}
}
void AudacityProject::DoTrackMute(Track *t, bool exclusive)
{
HandleTrackMute(t, exclusive);
mTrackPanel->UpdateAccessibility();
mTrackPanel->Refresh(false);
}
void AudacityProject::DoTrackSolo(Track *t, bool exclusive)
{
HandleTrackSolo(t, exclusive);
mTrackPanel->UpdateAccessibility();
mTrackPanel->Refresh(false);
}
void AudacityProject::SetTrackGain(WaveTrack * wt, LWSlider * slider)
{
wxASSERT(wt);
float newValue = slider->Get();
for (auto channel : TrackList::Channels(wt))
channel->SetGain(newValue);
PushState(_("Adjusted gain"), _("Gain"), UndoPush::CONSOLIDATE);
GetTrackPanel()->RefreshTrack(wt);
}
void AudacityProject::SetTrackPan(WaveTrack * wt, LWSlider * slider)
{
wxASSERT(wt);
float newValue = slider->Get();
for (auto channel : TrackList::Channels(wt))
channel->SetPan(newValue);
PushState(_("Adjusted Pan"), _("Pan"), UndoPush::CONSOLIDATE);
GetTrackPanel()->RefreshTrack(wt);
}
/// Removes the specified track. Called from HandleClosing.
void AudacityProject::RemoveTrack(Track * toRemove)
{
// If it was focused, then NEW focus is the next or, if
// unavailable, the previous track. (The NEW focus is set
// after the track has been removed.)
bool toRemoveWasFocused = mTrackPanel->GetFocusedTrack() == toRemove;
Track* newFocus{};
if (toRemoveWasFocused) {
auto iterNext = mTracks->FindLeader(toRemove), iterPrev = iterNext;
newFocus = *++iterNext;
if (!newFocus) {
newFocus = *--iterPrev;
}
}
wxString name = toRemove->GetName();
auto channels = TrackList::Channels(toRemove);
// Be careful to post-increment over positions that get erased!
auto &iter = channels.first;
while (iter != channels.end())
mTracks->Remove( * iter++ );
if (toRemoveWasFocused)
mTrackPanel->SetFocusedTrack(newFocus);
PushState(
wxString::Format(_("Removed track '%s.'"),
name),
_("Track Remove"));
TP_RedrawScrollbars();
HandleResize();
GetTrackPanel()->Refresh(false);
}
void AudacityProject::HandleTrackMute(Track *t, const bool exclusive)
{
// Whatever t is, replace with lead channel
t = *GetTracks()->FindLeader(t);
// "exclusive" mute means mute the chosen track and unmute all others.
if (exclusive) {
for (auto leader : GetTracks()->Leaders<PlayableTrack>()) {
const auto group = TrackList::Channels(leader);
bool chosen = (t == leader);
for (auto channel : group)
channel->SetMute( chosen ),
channel->SetSolo( false );
}
}
else {
// Normal click toggles this track.
auto pt = dynamic_cast<PlayableTrack *>( t );
if (!pt)
return;
bool wasMute = pt->GetMute();
for (auto channel : TrackList::Channels(pt))
channel->SetMute( !wasMute );
if (IsSoloSimple() || IsSoloNone())
{
// We also set a solo indicator if we have just one track / stereo pair playing.
// in a group of more than one playable tracks.
// otherwise clear solo on everything.
auto range = GetTracks()->Leaders<PlayableTrack>();
auto nPlayableTracks = range.size();
auto nPlaying = (range - &PlayableTrack::GetMute).size();
for (auto track : GetTracks()->Any<PlayableTrack>())
// will set both of a stereo pair
track->SetSolo( (nPlaying==1) && (nPlayableTracks > 1 ) && !track->GetMute() );
}
}
ModifyState(true);
}
// Type of solo (standard or simple) follows the set preference, unless
// alternate == true, which causes the opposite behavior.
void AudacityProject::HandleTrackSolo(Track *t, const bool alternate)
{
// Whatever t is, replace with lead channel
t = *GetTracks()->FindLeader(t);
const auto pt = dynamic_cast<PlayableTrack *>( t );
if (!pt)
return;
bool bWasSolo = pt->GetSolo();
bool bSoloMultiple = !IsSoloSimple() ^ alternate;
// Standard and Simple solo have opposite defaults:
// Standard - Behaves as individual buttons, shift=radio buttons
// Simple - Behaves as radio buttons, shift=individual
// In addition, Simple solo will mute/unmute tracks
// when in standard radio button mode.
if ( bSoloMultiple )
{
for (auto channel : TrackList::Channels(pt))
channel->SetSolo( !bWasSolo );
}
else
{
// Normal click solo this track only, mute everything else.
// OR unmute and unsolo everything.
for (auto leader : GetTracks()->Leaders<PlayableTrack>()) {
const auto group = TrackList::Channels(leader);
bool chosen = (t == leader);
for (auto channel : group) {
if (chosen) {
channel->SetSolo( !bWasSolo );
if( IsSoloSimple() )
channel->SetMute( false );
}
else {
channel->SetSolo( false );
if( IsSoloSimple() )
channel->SetMute( !bWasSolo );
}
}
}
}
ModifyState(true);
}
// Keyboard capture
// static
@ -5848,107 +5509,6 @@ int AudacityProject::GetEstimatedRecordingMinsLeftOnDisk(long lCaptureChannels)
}
double AudacityProject::GetZoomOfToFit(){
const double end = mTracks->GetEndTime();
const double start = mViewInfo.bScrollBeyondZero
? std::min(mTracks->GetStartTime(), 0.0)
: 0;
const double len = end - start;
if (len <= 0.0)
return mViewInfo.GetZoom();
int w;
mTrackPanel->GetTracksUsableArea(&w, NULL);
w -= 10;
return w/len;
}
double AudacityProject::GetZoomOfSelection(){
const double lowerBound =
std::max(mViewInfo.selectedRegion.t0(), ScrollingLowerBoundTime());
const double denom =
mViewInfo.selectedRegion.t1() - lowerBound;
if (denom <= 0.0)
return mViewInfo.GetZoom();
// LL: The "-1" is just a hack to get around an issue where zooming to
// selection doesn't actually get the entire selected region within the
// visible area. This causes a problem with scrolling at end of playback
// where the selected region may be scrolled off the left of the screen.
// I know this isn't right, but until the real rounding or 1-off issue is
// found, this will have to work.
// PRL: Did I fix this? I am not sure, so I leave the hack in place.
// Fixes might have resulted from commits
// 1b8f44d0537d987c59653b11ed75a842b48896ea and
// e7c7bb84a966c3b3cc4b3a9717d5f247f25e7296
int width;
mTrackPanel->GetTracksUsableArea(&width, NULL);
return (width - 1) / denom;
}
double AudacityProject::GetZoomOfPreset( int preset ){
// Sets a limit on how far we will zoom out as a factor over zoom to fit.
const double maxZoomOutFactor = 4.0;
// Sets how many pixels we allow for one uint, such as seconds.
const double pixelsPerUnit = 5.0;
double result = 1.0;
double zoomToFit = GetZoomOfToFit();
switch( preset ){
default:
case WaveTrack::kZoomDefault:
result = ZoomInfo::GetDefaultZoom();
break;
case WaveTrack::kZoomToFit:
result = zoomToFit;
break;
case WaveTrack::kZoomToSelection:
result = GetZoomOfSelection();
break;
case WaveTrack::kZoomMinutes:
result = pixelsPerUnit * 1.0/60;
break;
case WaveTrack::kZoomSeconds:
result = pixelsPerUnit * 1.0;
break;
case WaveTrack::kZoom5ths:
result = pixelsPerUnit * 5.0;
break;
case WaveTrack::kZoom10ths:
result = pixelsPerUnit * 10.0;
break;
case WaveTrack::kZoom20ths:
result = pixelsPerUnit * 20.0;
break;
case WaveTrack::kZoom50ths:
result = pixelsPerUnit * 50.0;
break;
case WaveTrack::kZoom100ths:
result = pixelsPerUnit * 100.0;
break;
case WaveTrack::kZoom500ths:
result = pixelsPerUnit * 500.0;
break;
case WaveTrack::kZoomMilliSeconds:
result = pixelsPerUnit * 1000.0;
break;
case WaveTrack::kZoomSamples:
result = 44100.0;
break;
case WaveTrack::kZoom4To1:
result = 44100.0 * 4;
break;
case WaveTrack::kMaxZoom:
result = ZoomInfo::GetMaxZoom();
break;
};
if( result < (zoomToFit/maxZoomOutFactor) )
result = zoomToFit / maxZoomOutFactor;
return result;
}
AudacityProject::PlaybackScroller::PlaybackScroller(AudacityProject *project)
: mProject(project)
{

View File

@ -93,8 +93,6 @@ class MixerBoardFrame;
struct AudioIOStartStreamOptions;
struct UndoState;
class Regions;
class LWSlider;
class UndoManager;
enum class UndoPush : unsigned char;
@ -318,8 +316,6 @@ private:
bool DoSave(bool fromSaveAs, bool bWantSaveCopy, bool bLossless = false);
public:
void Clear();// clears a selection
const wxString &GetFileName() { return mFileName; }
bool GetDirty() { return mDirty; }
void SetProjectTitle( int number =-1);
@ -426,29 +422,9 @@ public:
void SkipEnd(bool shift);
typedef void (WaveTrack::* EditFunction)(double, double);
typedef std::unique_ptr<Track> (WaveTrack::* EditDestFunction)(double, double);
void EditByLabel(EditFunction action, bool bSyncLockedTracks);
void EditClipboardByLabel(EditDestFunction action );
bool IsSyncLocked();
void SetSyncLock(bool flag);
void DoTrackMute(Track *pTrack, bool exclusive);
void DoTrackSolo(Track *pTrack, bool exclusive);
void SetTrackGain(WaveTrack * track, LWSlider * slider);
void SetTrackPan(WaveTrack * track, LWSlider * slider);
void RemoveTrack(Track * toRemove);
// "exclusive" mute means mute the chosen track and unmute all others.
void HandleTrackMute(Track *t, const bool exclusive);
// Type of solo (standard or simple) follows the set preference, unless
// alternate == true, which causes the opposite behavior.
void HandleTrackSolo(Track *t, const bool alternate);
// Snap To
void SetSnapTo(int snap);
@ -603,17 +579,10 @@ public:
void PopState(const UndoState &state);
void GetRegionsByLabel( Regions &regions );
void AutoSave();
void DeleteCurrentAutoSaveFile();
public:
double GetZoomOfToFit();
double GetZoomOfSelection();
double GetZoomOfPreset(int preset );
bool IsSoloSimple() const { return mSoloPref == wxT("Simple"); }
bool IsSoloNone() const { return mSoloPref == wxT("None"); }

View File

@ -87,7 +87,7 @@ bool ZoomInfo::ZoomOutAvailable() const
return zoom > gMinZoom;
}
double ZoomInfo::GetZoom( ){ return zoom;};
double ZoomInfo::GetZoom( ) const { return zoom;};
double ZoomInfo::GetMaxZoom( ) { return gMaxZoom;};
double ZoomInfo::GetMinZoom( ) { return gMinZoom;};

View File

@ -85,12 +85,14 @@ public:
static double GetDefaultZoom()
{ return 44100.0 / 512.0; }
// There is NO GetZoom()!
// Use TimeToPosition and PositionToTime and OffsetTimeByPixels!
// Limits zoom to certain bounds
void SetZoom(double pixelsPerSecond);
double GetZoom();
// This function should not be used to convert positions to times and back
// Use TimeToPosition and PositionToTime and OffsetTimeByPixels instead
double GetZoom() const;
static double GetMaxZoom( );
static double GetMinZoom( );

View File

@ -53,7 +53,7 @@ struct Region
}
};
class Regions : public std::vector < Region > {};
using Regions = std::vector < Region >;
class Envelope;

View File

@ -364,8 +364,27 @@ void OnCut(const CommandContext &context)
void OnDelete(const CommandContext &context)
{
auto &project = context.project;
project.Clear();
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
for (auto n : tracks.Any()) {
if (n->GetSelected() || n->IsSyncLockSelected()) {
n->Clear(selectedRegion.t0(), selectedRegion.t1());
}
}
double seconds = selectedRegion.duration();
selectedRegion.collapseToT0();
project.PushState(wxString::Format(_("Deleted %.2f seconds at t=%.2f"),
seconds,
selectedRegion.t0()),
_("Delete"));
project.RedrawProject();
}
void OnCopy(const CommandContext &context)
{

View File

@ -1,3 +1,4 @@
#include "../AudacityApp.h" // for EVT_CLIPBOARD_CHANGE
#include "../AudioIO.h"
#include "../LabelTrack.h"
#include "../Menus.h"
@ -80,6 +81,165 @@ int DoAddLabel(
return index;
}
//get regions selected by selected labels
//removes unnecessary regions, overlapping regions are merged
void GetRegionsByLabel(
const TrackList &tracks, const SelectedRegion &selectedRegion,
Regions &regions )
{
//determine labeled regions
for (auto lt : tracks.Selected< const LabelTrack >()) {
for (int i = 0; i < lt->GetNumLabels(); i++)
{
const LabelStruct *ls = lt->GetLabel(i);
if (ls->selectedRegion.t0() >= selectedRegion.t0() &&
ls->selectedRegion.t1() <= selectedRegion.t1())
regions.push_back(Region(ls->getT0(), ls->getT1()));
}
}
//anything to do ?
if( regions.size() == 0 )
return;
//sort and remove unnecessary regions
std::sort(regions.begin(), regions.end());
unsigned int selected = 1;
while( selected < regions.size() )
{
const Region &cur = regions.at( selected );
Region &last = regions.at( selected - 1 );
if( cur.start < last.end )
{
if( cur.end > last.end )
last.end = cur.end;
regions.erase( regions.begin() + selected );
}
else
++selected;
}
}
using EditFunction = void (WaveTrack::*)(double, double);
//Executes the edit function on all selected wave tracks with
//regions specified by selected labels
//If No tracks selected, function is applied on all tracks
//If the function replaces the selection with audio of a different length,
// bSyncLockedTracks should be set true to perform the same action on sync-lock
// selected tracks.
void EditByLabel(
TrackList &tracks, const SelectedRegion &selectedRegion,
EditFunction action, bool bSyncLockedTracks )
{
Regions regions;
GetRegionsByLabel( tracks, selectedRegion, regions );
if( regions.size() == 0 )
return;
// if at least one wave track is selected
// apply only on the selected track
const bool allTracks = (tracks.Selected< WaveTrack >()).empty();
//Apply action on wavetracks starting from
//labeled regions in the end. This is to correctly perform
//actions like 'Delete' which collapse the track area.
for (auto wt : tracks.Any<WaveTrack>())
{
if (allTracks || wt->GetSelected() ||
(bSyncLockedTracks && wt->IsSyncLockSelected()))
{
for (int i = (int)regions.size() - 1; i >= 0; i--) {
const Region &region = regions.at(i);
(wt->*action)(region.start, region.end);
}
}
}
}
using EditDestFunction = std::unique_ptr<Track> (WaveTrack::*)(double, double);
//Executes the edit function on all selected wave tracks with
//regions specified by selected labels
//If No tracks selected, function is applied on all tracks
//Functions copy the edited regions to clipboard, possibly in multiple tracks
//This probably should not be called if *action() changes the timeline, because
// the copy needs to happen by track, and the timeline change by group.
void EditClipboardByLabel(
TrackList &tracks, const SelectedRegion &selectedRegion,
EditDestFunction action )
{
Regions regions;
GetRegionsByLabel( tracks, selectedRegion, regions );
if( regions.size() == 0 )
return;
// if at least one wave track is selected
// apply only on the selected track
const bool allTracks = (tracks.Selected< WaveTrack >()).empty();
AudacityProject::ClearClipboard();
auto pNewClipboard = TrackList::Create();
auto &newClipboard = *pNewClipboard;
//Apply action on wavetracks starting from
//labeled regions in the end. This is to correctly perform
//actions like 'Cut' which collapse the track area.
for(auto wt :
tracks.Any<WaveTrack>()
+ (allTracks ? &Track::Any : &Track::IsSelected)
) {
// This track accumulates the needed clips, right to left:
Track::Holder merged;
for( int i = (int)regions.size() - 1; i >= 0; i-- )
{
const Region &region = regions.at(i);
auto dest = ( wt->*action )( region.start, region.end );
if( dest )
{
Track::FinishCopy( wt, dest.get() );
if( !merged )
merged = std::move(dest);
else
{
// Paste to the beginning; unless this is the first region,
// offset the track to account for time between the regions
if (i < (int)regions.size() - 1)
merged->Offset(
regions.at(i + 1).start - region.end);
// dest may have a placeholder clip at the end that is
// removed when pasting, which is okay because we proceed
// right to left. Any placeholder already in merged is kept.
// Only the rightmost placeholder is important in the final
// result.
merged->Paste( 0.0 , dest.get() );
}
}
else
// nothing copied but there is a 'region', so the 'region' must
// be a 'point label' so offset
if (i < (int)regions.size() - 1)
if (merged)
merged->Offset(
regions.at(i + 1).start - region.end);
}
if( merged )
newClipboard.Add( std::move(merged) );
}
// Survived possibility of exceptions. Commit changes to the clipboard now.
newClipboard.Swap(*AudacityProject::msClipboard);
wxTheApp->AddPendingEvent( wxCommandEvent{ EVT_CLIPBOARD_CHANGE } );
AudacityProject::msClipT0 = regions.front().start;
AudacityProject::msClipT1 = regions.back().end;
}
}
namespace LabelEditActions {
@ -196,6 +356,7 @@ void OnToggleTypeToCreateLabel(const CommandContext &WXUNUSED(context) )
void OnCutLabels(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
if( selectedRegion.isPoint() )
@ -203,12 +364,13 @@ void OnCutLabels(const CommandContext &context)
// Because of grouping the copy may need to operate on different tracks than
// the clear, so we do these actions separately.
project.EditClipboardByLabel( &WaveTrack::CopyNonconst );
EditClipboardByLabel( tracks, selectedRegion, &WaveTrack::CopyNonconst );
if( gPrefs->Read( wxT( "/GUI/EnableCutLines" ), ( long )0 ) )
project.EditByLabel( &WaveTrack::ClearAndAddCutLine, true );
EditByLabel(
tracks, selectedRegion, &WaveTrack::ClearAndAddCutLine, true );
else
project.EditByLabel( &WaveTrack::Clear, true );
EditByLabel( tracks, selectedRegion, &WaveTrack::Clear, true );
AudacityProject::msClipProject = &project;
@ -227,12 +389,13 @@ void OnCutLabels(const CommandContext &context)
void OnDeleteLabels(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
if( selectedRegion.isPoint() )
return;
project.EditByLabel( &WaveTrack::Clear, true );
EditByLabel( tracks, selectedRegion, &WaveTrack::Clear, true );
selectedRegion.collapseToT0();
@ -248,12 +411,13 @@ void OnDeleteLabels(const CommandContext &context)
void OnSplitCutLabels(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
if( selectedRegion.isPoint() )
return;
project.EditClipboardByLabel( &WaveTrack::SplitCut );
EditClipboardByLabel( tracks, selectedRegion, &WaveTrack::SplitCut );
AudacityProject::msClipProject = &project;
@ -270,12 +434,13 @@ void OnSplitCutLabels(const CommandContext &context)
void OnSplitDeleteLabels(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
if( selectedRegion.isPoint() )
return;
project.EditByLabel( &WaveTrack::SplitDelete, false );
EditByLabel( tracks, selectedRegion, &WaveTrack::SplitDelete, false );
project.PushState(
/* i18n-hint: (verb) Audacity has just done a special kind of DELETE on
@ -292,12 +457,13 @@ void OnSilenceLabels(const CommandContext &context)
{
auto &project = context.project;
auto trackPanel = project.GetTrackPanel();
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
if( selectedRegion.isPoint() )
return;
project.EditByLabel( &WaveTrack::Silence, false );
EditByLabel( tracks, selectedRegion, &WaveTrack::Silence, false );
project.PushState(
/* i18n-hint: (verb)*/
@ -312,12 +478,13 @@ void OnCopyLabels(const CommandContext &context)
{
auto &project = context.project;
auto trackPanel = project.GetTrackPanel();
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
if( selectedRegion.isPoint() )
return;
project.EditClipboardByLabel( &WaveTrack::CopyNonconst );
EditClipboardByLabel( tracks, selectedRegion, &WaveTrack::CopyNonconst );
AudacityProject::msClipProject = &project;
@ -331,8 +498,10 @@ void OnCopyLabels(const CommandContext &context)
void OnSplitLabels(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
project.EditByLabel( &WaveTrack::Split, false );
EditByLabel( tracks, selectedRegion, &WaveTrack::Split, false );
project.PushState(
/* i18n-hint: (verb) past tense. Audacity has just split the labeled
@ -347,12 +516,13 @@ void OnSplitLabels(const CommandContext &context)
void OnJoinLabels(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
if( selectedRegion.isPoint() )
return;
project.EditByLabel( &WaveTrack::Join, false );
EditByLabel( tracks, selectedRegion, &WaveTrack::Join, false );
project.PushState(
/* i18n-hint: (verb) Audacity has just joined the labeled audio (points or
@ -367,12 +537,13 @@ void OnJoinLabels(const CommandContext &context)
void OnDisjoinLabels(const CommandContext &context)
{
auto &project = context.project;
auto &tracks = *project.GetTracks();
auto &selectedRegion = project.GetViewInfo().selectedRegion;
if( selectedRegion.isPoint() )
return;
project.EditByLabel( &WaveTrack::Disjoin, false );
EditByLabel( tracks, selectedRegion, &WaveTrack::Disjoin, false );
project.PushState(
/* i18n-hint: (verb) Audacity has just detached the labeled audio regions.

View File

@ -503,6 +503,32 @@ void DoSortTracks( AudacityProject &project, int flags )
pTracks->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);
project.PushState(_("Adjusted gain"), _("Gain"), UndoPush::CONSOLIDATE);
project.GetTrackPanel()->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);
project.PushState(_("Adjusted Pan"), _("Pan"), UndoPush::CONSOLIDATE);
project.GetTrackPanel()->RefreshTrack(wt);
}
}
namespace TrackActions {
@ -549,6 +575,146 @@ void DoRemoveTracks( AudacityProject &project )
trackPanel->Refresh(false);
}
void DoTrackMute(AudacityProject &project, Track *t, bool exclusive)
{
auto &tracks = *project.GetTracks();
auto &trackPanel = *project.GetTrackPanel();
// Whatever t is, replace with lead channel
t = *tracks.FindLeader(t);
// "exclusive" mute means mute the chosen track and unmute all others.
if (exclusive) {
for (auto leader : tracks.Leaders<PlayableTrack>()) {
const auto group = TrackList::Channels(leader);
bool chosen = (t == leader);
for (auto channel : group)
channel->SetMute( chosen ),
channel->SetSolo( false );
}
}
else {
// Normal click toggles this track.
auto pt = dynamic_cast<PlayableTrack *>( t );
if (!pt)
return;
bool wasMute = pt->GetMute();
for (auto channel : TrackList::Channels(pt))
channel->SetMute( !wasMute );
if (project.IsSoloSimple() || project.IsSoloNone())
{
// We also set a solo indicator if we have just one track / stereo pair playing.
// in a group of more than one playable tracks.
// otherwise clear solo on everything.
auto range = tracks.Leaders<PlayableTrack>();
auto nPlayableTracks = range.size();
auto nPlaying = (range - &PlayableTrack::GetMute).size();
for (auto track : tracks.Any<PlayableTrack>())
// will set both of a stereo pair
track->SetSolo( (nPlaying==1) && (nPlayableTracks > 1 ) && !track->GetMute() );
}
}
project.ModifyState(true);
trackPanel.UpdateAccessibility();
trackPanel.Refresh(false);
}
void DoTrackSolo(AudacityProject &project, Track *t, bool exclusive)
{
auto &tracks = *project.GetTracks();
auto &trackPanel = *project.GetTrackPanel();
// Whatever t is, replace with lead channel
t = *tracks.FindLeader(t);
const auto pt = dynamic_cast<PlayableTrack *>( t );
if (!pt)
return;
bool bWasSolo = pt->GetSolo();
bool bSoloMultiple = !project.IsSoloSimple() ^ exclusive;
// Standard and Simple solo have opposite defaults:
// Standard - Behaves as individual buttons, shift=radio buttons
// Simple - Behaves as radio buttons, shift=individual
// In addition, Simple solo will mute/unmute tracks
// when in standard radio button mode.
if ( bSoloMultiple )
{
for (auto channel : TrackList::Channels(pt))
channel->SetSolo( !bWasSolo );
}
else
{
// Normal click solo this track only, mute everything else.
// OR unmute and unsolo everything.
for (auto leader : tracks.Leaders<PlayableTrack>()) {
const auto group = TrackList::Channels(leader);
bool chosen = (t == leader);
for (auto channel : group) {
if (chosen) {
channel->SetSolo( !bWasSolo );
if( project.IsSoloSimple() )
channel->SetMute( false );
}
else {
channel->SetSolo( false );
if( project.IsSoloSimple() )
channel->SetMute( !bWasSolo );
}
}
}
}
project.ModifyState(true);
trackPanel.UpdateAccessibility();
trackPanel.Refresh(false);
}
void DoRemoveTrack(AudacityProject &project, Track * toRemove)
{
auto &tracks = *project.GetTracks();
auto &trackPanel = *project.GetTrackPanel();
// If it was focused, then NEW focus is the next or, if
// unavailable, the previous track. (The NEW focus is set
// after the track has been removed.)
bool toRemoveWasFocused = trackPanel.GetFocusedTrack() == toRemove;
Track* newFocus{};
if (toRemoveWasFocused) {
auto iterNext = tracks.FindLeader(toRemove), iterPrev = iterNext;
newFocus = *++iterNext;
if (!newFocus) {
newFocus = *--iterPrev;
}
}
wxString name = toRemove->GetName();
auto channels = TrackList::Channels(toRemove);
// Be careful to post-increment over positions that get erased!
auto &iter = channels.first;
while (iter != channels.end())
tracks.Remove( * iter++ );
if (toRemoveWasFocused)
trackPanel.SetFocusedTrack(newFocus);
project.PushState(
wxString::Format(_("Removed track '%s.'"),
name),
_("Track Remove"));
project.FixScrollbars();
project.HandleResize();
trackPanel.Refresh(false);
}
void DoMoveTrack
(AudacityProject &project, Track* target, MoveChoice choice)
{
@ -1084,7 +1250,7 @@ void OnTrackPan(const CommandContext &context)
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = trackPanel->PanSlider(wt);
if (slider->ShowDialog())
project.SetTrackPan(wt, slider);
SetTrackPan(project, wt, slider);
});
}
@ -1097,7 +1263,7 @@ void OnTrackPanLeft(const CommandContext &context)
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = trackPanel->PanSlider(wt);
slider->Decrease(1);
project.SetTrackPan(wt, slider);
SetTrackPan(project, wt, slider);
});
}
@ -1110,7 +1276,7 @@ void OnTrackPanRight(const CommandContext &context)
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = trackPanel->PanSlider(wt);
slider->Increase(1);
project.SetTrackPan(wt, slider);
SetTrackPan(project, wt, slider);
});
}
@ -1124,7 +1290,7 @@ void OnTrackGain(const CommandContext &context)
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = trackPanel->GainSlider(wt);
if (slider->ShowDialog())
project.SetTrackGain(wt, slider);
SetTrackGain(project, wt, slider);
});
}
@ -1137,7 +1303,7 @@ void OnTrackGainInc(const CommandContext &context)
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = trackPanel->GainSlider(wt);
slider->Increase(1);
project.SetTrackGain(wt, slider);
SetTrackGain(project, wt, slider);
});
}
@ -1150,7 +1316,7 @@ void OnTrackGainDec(const CommandContext &context)
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
LWSlider *slider = trackPanel->GainSlider(wt);
slider->Decrease(1);
project.SetTrackGain(wt, slider);
SetTrackGain(project, wt, slider);
});
}
@ -1169,7 +1335,7 @@ void OnTrackMute(const CommandContext &context)
const auto track = trackPanel->GetFocusedTrack();
if (track) track->TypeSwitch( [&](PlayableTrack *t) {
project.DoTrackMute(t, false);
DoTrackMute(project, t, false);
});
}
@ -1180,7 +1346,7 @@ void OnTrackSolo(const CommandContext &context)
const auto track = trackPanel->GetFocusedTrack();
if (track) track->TypeSwitch( [&](PlayableTrack *t) {
project.DoTrackSolo(t, false);
DoTrackSolo(project, t, false);
});
}
@ -1203,7 +1369,7 @@ void OnTrackClose(const CommandContext &context)
return;
}
project.RemoveTrack(t);
DoRemoveTrack(project, t);
trackPanel->UpdateViewIfNoTracks();
trackPanel->Refresh(false);

View File

@ -14,12 +14,125 @@
// private helper classes and functions
namespace {
double GetZoomOfSelection( const AudacityProject &project )
{
const auto &viewInfo = project.GetViewInfo();
const auto &trackPanel = *project.GetTrackPanel();
const double lowerBound =
std::max(viewInfo.selectedRegion.t0(),
project.ScrollingLowerBoundTime());
const double denom =
viewInfo.selectedRegion.t1() - lowerBound;
if (denom <= 0.0)
return viewInfo.GetZoom();
// LL: The "-1" is just a hack to get around an issue where zooming to
// selection doesn't actually get the entire selected region within the
// visible area. This causes a problem with scrolling at end of playback
// where the selected region may be scrolled off the left of the screen.
// I know this isn't right, but until the real rounding or 1-off issue is
// found, this will have to work.
// PRL: Did I fix this? I am not sure, so I leave the hack in place.
// Fixes might have resulted from commits
// 1b8f44d0537d987c59653b11ed75a842b48896ea and
// e7c7bb84a966c3b3cc4b3a9717d5f247f25e7296
int width;
trackPanel.GetTracksUsableArea(&width, NULL);
return (width - 1) / denom;
}
double GetZoomOfPreset( const AudacityProject &project, int preset )
{
// Sets a limit on how far we will zoom out as a factor over zoom to fit.
const double maxZoomOutFactor = 4.0;
// Sets how many pixels we allow for one uint, such as seconds.
const double pixelsPerUnit = 5.0;
double result = 1.0;
double zoomToFit = ViewActions::GetZoomOfToFit( project );
switch( preset ){
default:
case WaveTrack::kZoomDefault:
result = ZoomInfo::GetDefaultZoom();
break;
case WaveTrack::kZoomToFit:
result = zoomToFit;
break;
case WaveTrack::kZoomToSelection:
result = GetZoomOfSelection( project );
break;
case WaveTrack::kZoomMinutes:
result = pixelsPerUnit * 1.0/60;
break;
case WaveTrack::kZoomSeconds:
result = pixelsPerUnit * 1.0;
break;
case WaveTrack::kZoom5ths:
result = pixelsPerUnit * 5.0;
break;
case WaveTrack::kZoom10ths:
result = pixelsPerUnit * 10.0;
break;
case WaveTrack::kZoom20ths:
result = pixelsPerUnit * 20.0;
break;
case WaveTrack::kZoom50ths:
result = pixelsPerUnit * 50.0;
break;
case WaveTrack::kZoom100ths:
result = pixelsPerUnit * 100.0;
break;
case WaveTrack::kZoom500ths:
result = pixelsPerUnit * 500.0;
break;
case WaveTrack::kZoomMilliSeconds:
result = pixelsPerUnit * 1000.0;
break;
case WaveTrack::kZoomSamples:
result = 44100.0;
break;
case WaveTrack::kZoom4To1:
result = 44100.0 * 4;
break;
case WaveTrack::kMaxZoom:
result = ZoomInfo::GetMaxZoom();
break;
};
if( result < (zoomToFit/maxZoomOutFactor) )
result = zoomToFit / maxZoomOutFactor;
return result;
}
}
namespace ViewActions {
// exported helper functions
double GetZoomOfToFit( const AudacityProject &project )
{
const auto &tracks = *project.GetTracks();
const auto &viewInfo = project.GetViewInfo();
const auto &trackPanel = *project.GetTrackPanel();
const double end = tracks.GetEndTime();
const double start = viewInfo.bScrollBeyondZero
? std::min( tracks.GetStartTime(), 0.0)
: 0;
const double len = end - start;
if (len <= 0.0)
return viewInfo.GetZoom();
int w;
trackPanel.GetTracksUsableArea(&w, NULL);
w -= 10;
return w/len;
}
void DoZoomFit(AudacityProject &project)
{
auto &viewInfo = project.GetViewInfo();
@ -29,7 +142,7 @@ void DoZoomFit(AudacityProject &project)
? std::min(tracks->GetStartTime(), 0.0)
: 0;
project.Zoom( project.GetZoomOfToFit() );
project.Zoom( GetZoomOfToFit( project ) );
project.TP_ScrollWindow(start);
}
@ -91,7 +204,7 @@ void OnZoomSel(const CommandContext &context)
auto &project = context.project;
auto &selectedRegion = project.GetViewInfo().selectedRegion;
project.Zoom( project.GetZoomOfSelection() );
project.Zoom( GetZoomOfSelection( project ) );
project.TP_ScrollWindow(selectedRegion.t0());
}
@ -105,8 +218,8 @@ void OnZoomToggle(const CommandContext &context)
// const double origWidth = GetScreenEndTime() - origLeft;
// Choose the zoom that is most different to the current zoom.
double Zoom1 = project.GetZoomOfPreset( TracksPrefs::Zoom1Choice() );
double Zoom2 = project.GetZoomOfPreset( TracksPrefs::Zoom2Choice() );
double Zoom1 = GetZoomOfPreset( project, TracksPrefs::Zoom1Choice() );
double Zoom2 = GetZoomOfPreset( project, TracksPrefs::Zoom2Choice() );
double Z = viewInfo.GetZoom();// Current Zoom.
double ChosenZoom =
fabs(log(Zoom1 / Z)) > fabs(log( Z / Zoom2)) ? Zoom1:Zoom2;

View File

@ -13,6 +13,7 @@ Paul Licameli split from TrackPanel.cpp
#include "../../../commands/CommandManager.h"
#include "../../../HitTestResult.h"
#include "../../../Menus.h"
#include "../../../Project.h"
#include "../../../RefreshCode.h"
#include "../../../Track.h"
@ -33,7 +34,7 @@ UIHandle::Result MuteButtonHandle::CommitChanges
{
auto pTrack = mpTrack.lock();
if ( dynamic_cast< PlayableTrack* >( pTrack.get() ) )
pProject->DoTrackMute(pTrack.get(), event.ShiftDown());
TrackActions::DoTrackMute(*pProject, pTrack.get(), event.ShiftDown());
return RefreshCode::RefreshNone;
}
@ -89,7 +90,7 @@ UIHandle::Result SoloButtonHandle::CommitChanges
{
auto pTrack = mpTrack.lock();
if ( dynamic_cast< PlayableTrack* >( pTrack.get() ) )
pProject->DoTrackSolo(pTrack.get(), event.ShiftDown());
TrackActions::DoTrackSolo(*pProject, pTrack.get(), event.ShiftDown());
return RefreshCode::RefreshNone;
}

View File

@ -12,6 +12,7 @@ Paul Licameli split from TrackPanel.cpp
#include "TrackButtonHandles.h"
#include "../../HitTestResult.h"
#include "../../Menus.h"
#include "../../Project.h"
#include "../../RefreshCode.h"
#include "../../Track.h"
@ -95,7 +96,7 @@ UIHandle::Result CloseButtonHandle::CommitChanges
pProject->StopIfPaused();
if (!pProject->IsAudioActive()) {
// This pushes an undo item:
pProject->RemoveTrack(pTrack.get());
TrackActions::DoRemoveTrack(*pProject, pTrack.get());
// Redraw all tracks when any one of them closes
// (Could we invent a return code that draws only those at or below
// the affected track?)