1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-19 22:27:43 +02:00

Rewrite channel iterations that don't generalize to more than two...

... requiring at least some message rewrites.

All these places have a comment:

// TODO: more-than-two-channels

or in simpler cases just

// TODO: more-than-two-channels-message
This commit is contained in:
Paul Licameli 2018-09-30 11:39:23 -04:00
commit 7d55defde6
20 changed files with 518 additions and 506 deletions

View File

@ -1891,20 +1891,17 @@ int AudioIO::StartStream(const TransportTracks &tracks,
// group determination should mimic what is done in audacityAudioCallback() // group determination should mimic what is done in audacityAudioCallback()
// when calling RealtimeProcess(). // when calling RealtimeProcess().
int group = 0; int group = 0;
for (size_t i = 0, cnt = mPlaybackTracks.size(); i < cnt; i++) for (size_t i = 0, cnt = mPlaybackTracks.size(); i < cnt;)
{ {
const WaveTrack *vt = mPlaybackTracks[i].get(); const WaveTrack *vt = mPlaybackTracks[i].get();
unsigned chanCnt = 1; // TODO: more-than-two-channels
if (vt->GetLinked()) unsigned chanCnt = TrackList::Channels(vt).size();
{ i += chanCnt;
i++;
chanCnt++;
}
// Setup for realtime playback at the rate of the realtime // Setup for realtime playback at the rate of the realtime
// stream, not the rate of the track. // stream, not the rate of the track.
em.RealtimeAddProcessor(group++, chanCnt, mRate); em.RealtimeAddProcessor(group++, std::min(2u, chanCnt), mRate);
} }
} }
@ -4992,26 +4989,38 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
bool drop = false; bool drop = false;
bool dropQuickly = false; bool dropQuickly = false;
bool linkFlag = false;
for (unsigned t = 0; t < numPlaybackTracks; t++) for (unsigned t = 0; t < numPlaybackTracks; t++)
{ {
WaveTrack *vt = mPlaybackTracks[t].get(); WaveTrack *vt = mPlaybackTracks[t].get();
chans[chanCnt] = vt; chans[chanCnt] = vt;
if ( linkFlag ) { // TODO: more-than-two-channels
linkFlag = false; auto nextTrack =
t + 1 < numPlaybackTracks
? mPlaybackTracks[t + 1].get()
: nullptr;
bool firstChannel = vt->IsLeader();
bool lastChannel = !nextTrack || nextTrack->IsLeader();
if ( ! firstChannel )
dropQuickly = dropQuickly && doneMicrofading( *vt ); dropQuickly = dropQuickly && doneMicrofading( *vt );
}
else { else {
drop = dropTrack( *vt ); drop = dropTrack( *vt );
linkFlag = vt->GetLinked();
selected = vt->GetSelected(); selected = vt->GetSelected();
// If we have a mono track, clear the right channel if ( lastChannel ) {
if (!linkFlag) // TODO: more-than-two-channels
#if 1
// If we have a mono track, clear the right channel
memset(tempBufs[1], 0, framesPerBuffer * sizeof(float)); memset(tempBufs[1], 0, framesPerBuffer * sizeof(float));
#else
// clear any other channels
for ( size_t c = chanCnt + 1; c < numPlaybackChannels; ++c )
memset(tempBufs[c], 0, framesPerBuffer * sizeof(float));
#endif
}
dropQuickly = drop && doneMicrofading( *vt ); dropQuickly = drop && doneMicrofading( *vt );
} }
@ -5055,7 +5064,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
maxLen = std::max(maxLen, len); maxLen = std::max(maxLen, len);
if (linkFlag) if ( ! lastChannel )
{ {
continue; continue;
} }

View File

@ -2318,13 +2318,12 @@ CommandFlag MenuCommandHandler::GetUpdateFlags
flags |= PlayableTracksExistFlag; flags |= PlayableTracksExistFlag;
if (t->GetSelected()) { if (t->GetSelected()) {
flags |= TracksSelectedFlag; flags |= TracksSelectedFlag;
if (t->GetLinked()) { // TODO: more-than-two-channels
if (TrackList::Channels(t).size() > 1) {
flags |= StereoRequiredFlag; flags |= StereoRequiredFlag;
} }
else { flags |= WaveTracksSelectedFlag;
flags |= WaveTracksSelectedFlag; flags |= AudioTracksSelectedFlag;
flags |= AudioTracksSelectedFlag;
}
} }
if( t->GetEndTime() > t->GetStartTime() ) if( t->GetEndTime() > t->GetStartTime() )
flags |= HasWaveDataFlag; flags |= HasWaveDataFlag;
@ -3856,13 +3855,14 @@ double MenuCommandHandler::OnClipMove
auto t0 = selectedRegion.t0(); auto t0 = selectedRegion.t0();
state.capturedClip = wt->GetClipAtTime( t0 ); // Find the first channel that has a clip at time t0
if (state.capturedClip == nullptr && track->GetLinked() && track->GetLink()) { for (auto channel : TrackList::Channels(wt) ) {
// the clips in the right channel may be different from the left if( nullptr != (state.capturedClip = channel->GetClipAtTime( t0 )) ) {
track = track->GetLink(); wt = channel;
wt = static_cast<WaveTrack*>(track); break;
state.capturedClip = wt->GetClipAtTime(t0); }
} }
if (state.capturedClip == nullptr) if (state.capturedClip == nullptr)
return 0.0; return 0.0;
@ -5709,6 +5709,7 @@ void MenuCommandHandler::OnPaste(const CommandContext &context)
bool bPastedSomething = false; bool bPastedSomething = false;
auto pC = clipTrackRange.begin(); auto pC = clipTrackRange.begin();
size_t nnChannels, ncChannels;
while (*pN && *pC) { while (*pN && *pC) {
auto n = *pN; auto n = *pN;
auto c = *pC; auto c = *pC;
@ -5763,15 +5764,34 @@ void MenuCommandHandler::OnPaste(const CommandContext &context)
_("Pasting one type of track into another is not allowed.") _("Pasting one type of track into another is not allowed.")
}; };
// When trying to copy from stereo to mono track, show error and exit // We should need this check only each time we visit the leading
// TODO: Automatically offer user to mix down to mono (unfortunately // channel
// this is not easy to implement if ( n->IsLeader() ) {
if (c->GetLinked() && !n->GetLinked()) wxASSERT( c->IsLeader() ); // the iteration logic should ensure this
// Throw, so that any previous changes to the project in this loop
// are discarded. auto cChannels = TrackList::Channels(c);
throw SimpleMessageBoxException{ ncChannels = cChannels.size();
_("Copying stereo audio into a mono track is not allowed.") auto nChannels = TrackList::Channels(n);
}; nnChannels = nChannels.size();
// When trying to copy from stereo to mono track, show error and exit
// TODO: Automatically offer user to mix down to mono (unfortunately
// this is not easy to implement
if (ncChannels > nnChannels)
{
if (ncChannels > 2) {
// TODO: more-than-two-channels-message
// Re-word the error message
}
// else
// Throw, so that any previous changes to the project in this loop
// are discarded.
throw SimpleMessageBoxException{
_("Copying stereo audio into a mono track is not allowed.")
};
}
}
if (!ff) if (!ff)
ff = n; ff = n;
@ -5806,11 +5826,17 @@ void MenuCommandHandler::OnPaste(const CommandContext &context)
} }
); );
--nnChannels;
--ncChannels;
// When copying from mono to stereo track, paste the wave form // When copying from mono to stereo track, paste the wave form
// to both channels // to both channels
if (n->GetLinked() && !c->GetLinked()) // TODO: more-than-two-channels
// This will replicate the last pasted channel as many times as needed
while (nnChannels > 0 && ncChannels == 0)
{ {
n = * ++ pN; n = * ++ pN;
--nnChannels;
n->TypeSwitch( n->TypeSwitch(
[&](WaveTrack *wn){ [&](WaveTrack *wn){
@ -5826,7 +5852,7 @@ void MenuCommandHandler::OnPaste(const CommandContext &context)
); );
} }
if (bAdvanceClipboard){ if (bAdvanceClipboard) {
prevClip = c; prevClip = c;
c = * ++ pC; c = * ++ pC;
} }
@ -6906,34 +6932,29 @@ int MenuCommandHandler::FindClips
std::vector<FoundClip> results; std::vector<FoundClip> results;
int nTracksSearched = 0; int nTracksSearched = 0;
int trackNum = 1; auto leaders = tracks->Leaders();
for (auto track : tracks->Leaders()) { auto range = leaders.Filter<const WaveTrack>();
if (track->GetKind() == Track::Wave && (!anyWaveTracksSelected || track->GetSelected())) { if (anyWaveTracksSelected)
auto waveTrack = static_cast<const WaveTrack*>(track); range = range + &Track::GetSelected;
bool stereoAndDiff = waveTrack->GetLinked() && !ChannelsHaveSameClipBoundaries(waveTrack); for (auto waveTrack : range) {
bool stereoAndDiff = ChannelsHaveDifferentClipBoundaries(waveTrack);
auto result = next ? FindNextClip(project, waveTrack, t0, t1) : auto range = stereoAndDiff
FindPrevClip(project, waveTrack, t0, t1); ? TrackList::Channels( waveTrack )
: TrackList::SingletonRange( waveTrack );
for ( auto wt : range ) {
auto result = next ? FindNextClip(project, wt, t0, t1) :
FindPrevClip(project, wt, t0, t1);
if (result.found) { if (result.found) {
result.trackNum = trackNum; result.trackNum =
1 + std::distance( leaders.begin(), leaders.find( waveTrack ) );
result.channel = stereoAndDiff; result.channel = stereoAndDiff;
results.push_back(result); results.push_back(result);
} }
if (stereoAndDiff) {
auto waveTrack2 = static_cast<const WaveTrack*>(track->GetLink());
auto result = next ? FindNextClip(project, waveTrack2, t0, t1) :
FindPrevClip(project, waveTrack2, t0, t1);
if (result.found) {
result.trackNum = trackNum;
result.channel = stereoAndDiff;
results.push_back(result);
}
}
nTracksSearched++;
} }
trackNum++; nTracksSearched++;
} }
@ -6974,14 +6995,13 @@ int MenuCommandHandler::FindClips
return nTracksSearched; // can be used for screen reader messages if required return nTracksSearched; // can be used for screen reader messages if required
} }
// Whether the two channels of a stereo track have the same clips namespace {
bool MenuCommandHandler::ChannelsHaveSameClipBoundaries(const WaveTrack* wt) bool TwoChannelsHaveSameBoundaries
{ ( const WaveTrack *first, const WaveTrack *second )
bool sameClips = false; {
bool sameClips = false;
if (wt->GetLinked() && wt->GetLink()) { auto& left = first->GetClips();
auto& left = wt->GetClips(); auto& right = second->GetClips();
auto& right = static_cast<const WaveTrack*>(wt->GetLink())->GetClips();
if (left.size() == right.size()) { if (left.size() == right.size()) {
sameClips = true; sameClips = true;
for (unsigned int i = 0; i < left.size(); i++) { for (unsigned int i = 0; i < left.size(); i++) {
@ -6992,9 +7012,24 @@ bool MenuCommandHandler::ChannelsHaveSameClipBoundaries(const WaveTrack* wt)
} }
} }
} }
return sameClips;
}
}
bool MenuCommandHandler::ChannelsHaveDifferentClipBoundaries(
const WaveTrack* wt)
{
// This is quadratic in the number of channels
auto channels = TrackList::Channels(wt);
while (!channels.empty()) {
auto channel = *channels.first++;
for (auto other : channels) {
if (!TwoChannelsHaveSameBoundaries(channel, other))
return true;
}
} }
return sameClips; return false;
} }
void MenuCommandHandler::OnSelectPrevClip(const CommandContext &context) void MenuCommandHandler::OnSelectPrevClip(const CommandContext &context)
@ -8120,34 +8155,29 @@ int MenuCommandHandler::FindClipBoundaries
std::vector<FoundClipBoundary> results; std::vector<FoundClipBoundary> results;
int nTracksSearched = 0; int nTracksSearched = 0;
int trackNum = 1; auto leaders = tracks->Leaders();
for (auto track : tracks->Leaders()) { auto range = leaders.Filter<const WaveTrack>();
if (track->GetKind() == Track::Wave && (!anyWaveTracksSelected || track->GetSelected())) { if (anyWaveTracksSelected)
auto waveTrack = static_cast<const WaveTrack*>(track); range = range + &Track::GetSelected;
bool stereoAndDiff = waveTrack->GetLinked() && !ChannelsHaveSameClipBoundaries(waveTrack); for (auto waveTrack : range) {
bool stereoAndDiff = ChannelsHaveDifferentClipBoundaries(waveTrack);
auto result = next ? FindNextClipBoundary(waveTrack, time) : auto range = stereoAndDiff
FindPrevClipBoundary(waveTrack, time); ? TrackList::Channels( waveTrack )
: TrackList::SingletonRange(waveTrack);
for (auto wt : range) {
auto result = next ? FindNextClipBoundary(wt, time) :
FindPrevClipBoundary(wt, time);
if (result.nFound > 0) { if (result.nFound > 0) {
result.trackNum = trackNum; result.trackNum =
1 + std::distance( leaders.begin(), leaders.find( waveTrack ) );
result.channel = stereoAndDiff; result.channel = stereoAndDiff;
results.push_back(result); results.push_back(result);
} }
if (stereoAndDiff) {
waveTrack = static_cast<const WaveTrack*>(track->GetLink());
result = next ? FindNextClipBoundary(waveTrack, time) :
FindPrevClipBoundary(waveTrack, time);
if (result.nFound > 0) {
result.trackNum = trackNum;
result.channel = stereoAndDiff;
results.push_back(result);
}
}
nTracksSearched++;
} }
trackNum++; nTracksSearched++;
} }
@ -8215,7 +8245,8 @@ wxString MenuCommandHandler::FoundTrack::ComposeTrackName() const
: name; : name;
auto longName = shortName; auto longName = shortName;
if (channel) { if (channel) {
if ( waveTrack->GetLinked() ) // TODO: more-than-two-channels-message
if ( waveTrack->IsLeader() )
/* i18n-hint: given the name of a track, specify its left channel */ /* i18n-hint: given the name of a track, specify its left channel */
longName = wxString::Format(_("%s left"), shortName); longName = wxString::Format(_("%s left"), shortName);
else else

View File

@ -357,7 +357,7 @@ FoundClip FindPrevClip
int FindClips int FindClips
(AudacityProject &project, (AudacityProject &project,
double t0, double t1, bool next, std::vector<FoundClip>& results); double t0, double t1, bool next, std::vector<FoundClip>& results);
bool ChannelsHaveSameClipBoundaries(const WaveTrack* wt); bool ChannelsHaveDifferentClipBoundaries(const WaveTrack* wt);
void OnSelectPrevClip(const CommandContext &context ); void OnSelectPrevClip(const CommandContext &context );
void OnSelectNextClip(const CommandContext &context ); void OnSelectNextClip(const CommandContext &context );
void OnSelectClip(AudacityProject &project, bool next); void OnSelectClip(AudacityProject &project, bool next);

View File

@ -49,28 +49,23 @@ void MixAndRender(TrackList *tracks, TrackFactory *trackFactory,
uLeft.reset(), uRight.reset(); uLeft.reset(), uRight.reset();
// This function was formerly known as "Quick Mix". // This function was formerly known as "Quick Mix".
const Track *t;
bool mono = false; /* flag if output can be mono without loosing anything*/ bool mono = false; /* flag if output can be mono without loosing anything*/
bool oneinput = false; /* flag set to true if there is only one input track bool oneinput = false; /* flag set to true if there is only one input track
(mono or stereo) */ (mono or stereo) */
TrackListIterator iter(tracks); const auto trackRange = tracks->Selected< const WaveTrack >();
SelectedTrackListOfKindIterator usefulIter(Track::Wave, tracks); auto first = *trackRange.begin();
// this only iterates tracks which are relevant to this function, i.e. // this only iterates tracks which are relevant to this function, i.e.
// selected WaveTracks. The tracklist is (confusingly) the list of all // selected WaveTracks. The tracklist is (confusingly) the list of all
// tracks in the project // tracks in the project
int numWaves = 0; /* number of wave tracks in the selection */ int numWaves = 0; /* number of wave tracks in the selection */
int numMono = 0; /* number of mono, centre-panned wave tracks in selection*/ int numMono = 0; /* number of mono, centre-panned wave tracks in selection*/
t = iter.First(); for(auto wt : trackRange) {
while (t) { numWaves++;
if (t->GetSelected() && t->GetKind() == Track::Wave) { float pan = wt->GetPan();
numWaves++; if (wt->GetChannel() == Track::MonoChannel && pan == 0)
float pan = ((WaveTrack*)t)->GetPan(); numMono++;
if (t->GetChannel() == Track::MonoChannel && pan == 0)
numMono++;
}
t = iter.Next();
} }
if (numMono == numWaves) if (numMono == numWaves)
@ -88,45 +83,42 @@ void MixAndRender(TrackList *tracks, TrackFactory *trackFactory,
double tstart, tend; // start and end times for one track. double tstart, tend; // start and end times for one track.
WaveTrackConstArray waveArray; WaveTrackConstArray waveArray;
t = iter.First();
while (t) { for(auto wt : trackRange) {
if (t->GetSelected() && t->GetKind() == Track::Wave) { waveArray.push_back( Track::Pointer< const WaveTrack >( wt ) );
waveArray.push_back(Track::Pointer<const WaveTrack>(t)); tstart = wt->GetStartTime();
tstart = t->GetStartTime(); tend = wt->GetEndTime();
tend = t->GetEndTime(); if (tend > mixEndTime)
if (tend > mixEndTime) mixEndTime = tend;
mixEndTime = tend; // try and get the start time. If the track is empty we will get 0,
// try and get the start time. If the track is empty we will get 0, // which is ambiguous because it could just mean the track starts at
// which is ambiguous because it could just mean the track starts at // the beginning of the project, as well as empty track. The give-away
// the beginning of the project, as well as empty track. The give-away // is that an empty track also ends at zero.
// is that an empty track also ends at zero.
if (tstart != tend) { if (tstart != tend) {
// we don't get empty tracks here // we don't get empty tracks here
if (!gotstart) { if (!gotstart) {
// no previous start, use this one unconditionally // no previous start, use this one unconditionally
mixStartTime = tstart; mixStartTime = tstart;
gotstart = true; gotstart = true;
} else if (tstart < mixStartTime) } else if (tstart < mixStartTime)
mixStartTime = tstart; // have a start, only make it smaller mixStartTime = tstart; // have a start, only make it smaller
} // end if start and end are different } // end if start and end are different
} // end if track is a selected WaveTrack.
/** @TODO: could we not use a SelectedTrackListOfKindIterator here? */
t = iter.Next();
} }
/* create the destination track (NEW track) */ /* create the destination track (NEW track) */
if ((numWaves == 1) || ((numWaves == 2) && (usefulIter.First()->GetLink() != NULL))) if (numWaves == TrackList::Channels(first).size())
oneinput = true; oneinput = true;
// only one input track (either 1 mono or one linked stereo pair) // only one input track (either 1 mono or one linked stereo pair)
auto mixLeft = trackFactory->NewWaveTrack(format, rate); auto mixLeft = trackFactory->NewWaveTrack(format, rate);
if (oneinput) if (oneinput)
mixLeft->SetName(usefulIter.First()->GetName()); /* set name of output track to be the same as the sole input track */ mixLeft->SetName(first->GetName()); /* set name of output track to be the same as the sole input track */
else else
mixLeft->SetName(_("Mix")); mixLeft->SetName(_("Mix"));
mixLeft->SetOffset(mixStartTime); mixLeft->SetOffset(mixStartTime);
// TODO: more-than-two-channels
decltype(mixLeft) mixRight{}; decltype(mixLeft) mixRight{};
if (mono) { if (mono) {
mixLeft->SetChannel(Track::MonoChannel); mixLeft->SetChannel(Track::MonoChannel);
@ -134,10 +126,11 @@ void MixAndRender(TrackList *tracks, TrackFactory *trackFactory,
else { else {
mixRight = trackFactory->NewWaveTrack(format, rate); mixRight = trackFactory->NewWaveTrack(format, rate);
if (oneinput) { if (oneinput) {
if (usefulIter.First()->GetLink() != NULL) // we have linked track auto channels = TrackList::Channels(first);
mixRight->SetName(usefulIter.First()->GetLink()->GetName()); /* set name to match input track's right channel!*/ if (channels.size() > 1)
mixRight->SetName((*channels.begin().advance(1))->GetName()); /* set name to match input track's right channel!*/
else else
mixRight->SetName(usefulIter.First()->GetName()); /* set name to that of sole input channel */ mixRight->SetName(first->GetName()); /* set name to that of sole input channel */
} }
else else
mixRight->SetName(_("Mix")); mixRight->SetName(_("Mix"));

View File

@ -334,9 +334,10 @@ WaveTrack *MixerTrackCluster::GetWave() const
WaveTrack *MixerTrackCluster::GetRight() const WaveTrack *MixerTrackCluster::GetRight() const
{ {
// TODO: more-than-two-channels
auto left = GetWave(); auto left = GetWave();
if (left) if (left)
return static_cast<WaveTrack*>(left->GetLink()); return * ++ TrackList::Channels(left).begin();
else else
return nullptr; return nullptr;
} }
@ -981,9 +982,9 @@ void MixerBoard::UpdateTrackClusters()
size_t nClusterCount = mMixerTrackClusters.size(); size_t nClusterCount = mMixerTrackClusters.size();
unsigned int nClusterIndex = 0; unsigned int nClusterIndex = 0;
MixerTrackCluster* pMixerTrackCluster = NULL; MixerTrackCluster* pMixerTrackCluster = NULL;
Track* pTrack;
for (auto pPlayableTrack: mTracks->Leaders<PlayableTrack>()) { for (auto pPlayableTrack: mTracks->Leaders<PlayableTrack>()) {
// TODO: more-than-two-channels
auto spTrack = Track::Pointer<PlayableTrack>( pPlayableTrack ); auto spTrack = Track::Pointer<PlayableTrack>( pPlayableTrack );
if (nClusterIndex < nClusterCount) if (nClusterIndex < nClusterCount)
{ {

View File

@ -703,16 +703,20 @@ void ScreenFrame::OnOneHour(wxCommandEvent & WXUNUSED(event))
void ScreenFrame::SizeTracks(int h) void ScreenFrame::SizeTracks(int h)
{ {
TrackListIterator iter(mContext.GetProject()->GetTracks()); // h is the height for a channel
for (Track * t = iter.First(); t; t = iter.Next()) { // Set the height of a mono track twice as high
if (t->GetKind() == Track::Wave) {
if (t->GetLink()) { // TODO: more-than-two-channels
t->SetHeight(h); // If there should be more-than-stereo tracks, this makes
} // each channel as high as for a stereo channel
else {
t->SetHeight(h*2); auto tracks = mContext.GetProject()->GetTracks();
} for (auto t : tracks->Leaders<WaveTrack>()) {
} auto channels = TrackList::Channels(t);
auto nChannels = channels.size();
auto height = nChannels == 1 ? 2 * h : h;
for (auto channel : channels)
channel->SetHeight(height);
} }
mContext.GetProject()->RedrawProject(); mContext.GetProject()->RedrawProject();
} }

View File

@ -1078,23 +1078,17 @@ void TrackPanel::RefreshTrack(Track *trk, bool refreshbacking)
if (!trk) if (!trk)
return; return;
Track *link = trk->GetLink(); trk = *GetTracks()->FindLeader(trk);
auto height =
if (link && !trk->GetLinked()) { TrackList::Channels(trk).sum( &Track::GetHeight )
trk = link; - kTopInset - kShadowThickness;
link = trk->GetLink();
}
// subtract insets and shadows from the rectangle, but not border // subtract insets and shadows from the rectangle, but not border
// This matters because some separators do paint over the border // This matters because some separators do paint over the border
wxRect rect(kLeftInset, wxRect rect(kLeftInset,
-mViewInfo->vpos + trk->GetY() + kTopInset, -mViewInfo->vpos + trk->GetY() + kTopInset,
GetRect().GetWidth() - kLeftInset - kRightInset - kShadowThickness, GetRect().GetWidth() - kLeftInset - kRightInset - kShadowThickness,
trk->GetHeight() - kTopInset - kShadowThickness); height);
if (link) {
rect.height += link->GetHeight();
}
if( refreshbacking ) if( refreshbacking )
{ {
@ -1184,41 +1178,24 @@ void TrackPanel::DrawEverythingElse(TrackPanelDrawingContext &context,
trackRect.y = t->GetY() - mViewInfo->vpos; trackRect.y = t->GetY() - mViewInfo->vpos;
trackRect.height = t->GetHeight(); trackRect.height = t->GetHeight();
// If this track is linked to the next one, display a common auto leaderTrack = *GetTracks()->FindLeader( t );
// border for both, otherwise draw a normal border
wxRect rect = trackRect;
bool skipBorder = false;
Track *l = t->GetLink();
if (l && t->GetLinked()) {
rect.height += l->GetHeight();
}
else if (l && trackRect.y >= 0) {
skipBorder = true;
}
// If the previous track is linked to this one but isn't on the screen // If the previous track is linked to this one but isn't on the screen
// (and thus would have been skipped) we need to draw that track's border // (and thus would have been skipped) we need to draw that track's border
// instead. // instead.
const Track *borderTrack = t; bool drawBorder = (t == leaderTrack || trackRect.y < 0);
wxRect borderRect = rect;
if (l && !t->GetLinked() && trackRect.y < 0) if (drawBorder) {
{ wxRect teamRect = trackRect;
borderTrack = l; teamRect.y = leaderTrack->GetY() - mViewInfo->vpos;
// danger with pending tracks?
teamRect.height =
TrackList::Channels(leaderTrack)
.sum( &Track::GetHeight );
borderRect = trackRect;
borderRect.y = l->GetY() - mViewInfo->vpos;
borderRect.height = l->GetHeight();
borderRect.height += t->GetHeight();
}
if (!skipBorder) {
if (mAx->IsFocused(t)) { if (mAx->IsFocused(t)) {
focusRect = borderRect; focusRect = teamRect;
} }
DrawOutside(context, borderTrack, borderRect); DrawOutside(context, leaderTrack, teamRect);
} }
// Believe it or not, we can speed up redrawing if we don't // Believe it or not, we can speed up redrawing if we don't
@ -1675,7 +1652,9 @@ void TrackInfo::Status1DrawFunction
/// stereo and what sample rate it's using. /// stereo and what sample rate it's using.
auto rate = wt ? wt->GetRate() : 44100.0; auto rate = wt ? wt->GetRate() : 44100.0;
wxString s; wxString s;
if (!wt || (wt->GetLinked())) if (!pTrack || TrackList::Channels(pTrack).size() > 1)
// TODO: more-than-two-channels-message
// more appropriate strings
s = _("Stereo, %dHz"); s = _("Stereo, %dHz");
else { else {
if (wt->GetChannel() == Track::MonoChannel) if (wt->GetChannel() == Track::MonoChannel)
@ -1714,6 +1693,15 @@ void TrackPanel::DrawOutside
wxRect rect = rec; wxRect rect = rec;
DrawOutsideOfTrack(context, t, rect); DrawOutsideOfTrack(context, t, rect);
{
auto channels = TrackList::Channels(t);
// omit last (perhaps, only) channel
--channels.second;
for (auto channel : channels)
// draw the sash below this channel
DrawSash(channel, dc, rect);
}
// Now exclude left, right, and top insets // Now exclude left, right, and top insets
rect.x += kLeftInset; rect.x += kLeftInset;
rect.y += kTopInset; rect.y += kTopInset;
@ -1733,7 +1721,16 @@ void TrackPanel::DrawOutside
// TrackArtist::DrawSyncLockTiles(dc, tileFill); // TrackArtist::DrawSyncLockTiles(dc, tileFill);
//} //}
DrawBordersAroundTrack(t, dc, rect, labelw, vrul); DrawBordersAroundTrack(dc, rect, vrul);
{
auto channels = TrackList::Channels(t);
// omit last (perhaps, only) channel
--channels.second;
for (auto channel : channels)
// draw the sash below this channel
DrawBordersAroundSash(channel, dc, rect, labelw);
}
DrawShadow(t, dc, rect); DrawShadow(t, dc, rect);
} }
@ -1777,16 +1774,17 @@ void TrackPanel::DrawOutsideOfTrack
side.x += side.width - kRightInset; side.x += side.width - kRightInset;
side.width = kRightInset; side.width = kRightInset;
dc->DrawRectangle(side); dc->DrawRectangle(side);
}
// Area between tracks of stereo group void TrackPanel::DrawSash(const Track * t, wxDC * dc, const wxRect & rect)
if (t->GetLinked()) { {
// Paint the channel separator over (what would be) the shadow of the top // Area between channels of a group
// channel, and the top inset of the bottom channel // Paint the channel separator over (what would be) the shadow of this
side = rect; // channel, and the top inset of the following channel
side.y += t->GetHeight() - kShadowThickness; wxRect side = rect;
side.height = kTopInset + kShadowThickness; side.y = t->GetY() - mViewInfo->vpos + t->GetHeight() - kShadowThickness;
dc->DrawRectangle(side); side.height = kTopInset + kShadowThickness;
} dc->DrawRectangle(side);
} }
void TrackPanel::SetBackgroundCell void TrackPanel::SetBackgroundCell
@ -1979,8 +1977,8 @@ void TrackPanel::VerticalScroll( float fracPosition){
// Given rectangle excludes the insets left, right, and top // Given rectangle excludes the insets left, right, and top
// Draw a rectangular border and also a vertical separator of track controls // Draw a rectangular border and also a vertical separator of track controls
// from the rest (ruler and proper track area) // from the rest (ruler and proper track area)
void TrackPanel::DrawBordersAroundTrack(const Track * t, wxDC * dc, void TrackPanel::DrawBordersAroundTrack(wxDC * dc,
const wxRect & rect, const int labelw, const wxRect & rect,
const int vrul) const int vrul)
{ {
// Border around track and label area // Border around track and label area
@ -1991,21 +1989,21 @@ void TrackPanel::DrawBordersAroundTrack(const Track * t, wxDC * dc,
rect.width - kShadowThickness, rect.width - kShadowThickness,
rect.height - kShadowThickness); rect.height - kShadowThickness);
// between vruler and TrackInfo // between vruler and TrackInfo
AColor::Line(*dc, vrul, rect.y, vrul, rect.y + rect.height - 1); AColor::Line(*dc, vrul, rect.y, vrul, rect.y + rect.height - 1);
}
// The lines at bottom of 1st track and top of second track of stereo group void TrackPanel::DrawBordersAroundSash(const Track * t, wxDC * dc,
// Possibly replace with DrawRectangle to add left border. const wxRect & rect, const int labelw)
if (t->GetLinked()) { {
// The given rect has had the top inset subtracted int h1 = t->GetY() - mViewInfo->vpos + t->GetHeight();
int h1 = rect.y + t->GetHeight() - kTopInset; // h1 is the top coordinate of the following channel's rectangle
// h1 is the top coordinate of the second tracks' rectangle // Draw (part of) the bottom border of the top channel and top border of the bottom
// Draw (part of) the bottom border of the top channel and top border of the bottom // At left it extends between the vertical rulers too
// At left it extends between the vertical rulers too // These lines stroke over what is otherwise "border" of each channel
// These lines stroke over what is otherwise "border" of each channel AColor::Line(*dc, labelw, h1 - kBottomMargin, rect.x + rect.width - 1, h1 - kBottomMargin);
AColor::Line(*dc, labelw, h1 - kBottomMargin, rect.x + rect.width - 1, h1 - kBottomMargin); AColor::Line(*dc, labelw, h1 + kTopInset, rect.x + rect.width - 1, h1 + kTopInset);
AColor::Line(*dc, labelw, h1 + kTopInset, rect.x + rect.width - 1, h1 + kTopInset);
}
} }
// Given rectangle has insets subtracted left, right, and top // Given rectangle has insets subtracted left, right, and top

View File

@ -373,16 +373,20 @@ protected:
void DrawEverythingElse(TrackPanelDrawingContext &context, void DrawEverythingElse(TrackPanelDrawingContext &context,
const wxRegion & region, const wxRegion & region,
const wxRect & clip); const wxRect & clip);
void DrawOutside void DrawOutside(
(TrackPanelDrawingContext &context, TrackPanelDrawingContext &context,
const Track *t, const wxRect & rec); const Track *leaderTrack, const wxRect & teamRect);
void HighlightFocusedTrack (wxDC* dc, const wxRect &rect); void HighlightFocusedTrack (wxDC* dc, const wxRect &rect);
void DrawShadow (const Track *t, wxDC* dc, const wxRect & rect); void DrawShadow (const Track *t, wxDC* dc, const wxRect & rect);
void DrawBordersAroundTrack(const Track *t, wxDC* dc, const wxRect & rect, const int labelw, const int vrul); void DrawBordersAroundTrack(wxDC* dc, const wxRect & rect,
void DrawOutsideOfTrack const int vrul);
(TrackPanelDrawingContext &context, void DrawBordersAroundSash (const Track *t, wxDC* dc, const wxRect & rect,
const Track *t, const wxRect & rect); const int labelw);
void DrawOutsideOfTrack (
TrackPanelDrawingContext &context,
const Track *t, const wxRect & rect);
void DrawSash (const Track *t, wxDC* dc, const wxRect & rect);
public: public:
// Set the object that performs catch-all event handling when the pointer // Set the object that performs catch-all event handling when the pointer

View File

@ -26,6 +26,8 @@ Paul Licameli split from TrackPanel.cpp
HitTestPreview TrackPanelResizeHandle::HitPreview(bool bLinked) HitTestPreview TrackPanelResizeHandle::HitPreview(bool bLinked)
{ {
// TODO: more-than-two-channels-message
static wxCursor resizeCursor{ wxCURSOR_SIZENS }; static wxCursor resizeCursor{ wxCURSOR_SIZENS };
/// When in the resize area we can adjust size or relative size. /// When in the resize area we can adjust size or relative size.
@ -59,40 +61,34 @@ UIHandle::Result TrackPanelResizeHandle::Click
} }
TrackPanelResizeHandle::TrackPanelResizeHandle TrackPanelResizeHandle::TrackPanelResizeHandle
( const std::shared_ptr<Track> &track, int y, const AudacityProject *pProject ) ( const std::shared_ptr<Track> &track, int y )
: mpTrack{ track } : mpTrack{ track }
, mMouseClickY( y ) , mMouseClickY( y )
{ {
auto tracks = pProject->GetTracks(); // TODO: more-than-two-channels
auto prev = * -- tracks->Find(track.get());
auto next = * ++ tracks->Find(track.get());
//STM: Determine whether we should rescale one or two tracks //STM: Determine whether we should rescale one or two tracks
if (prev && prev->GetLink() == track.get()) { auto channels = TrackList::Channels(track.get());
// mpTrack is the lower track auto last = *channels.rbegin();
mInitialTrackHeight = track->GetHeight(); mInitialTrackHeight = last->GetHeight();
mInitialActualHeight = track->GetActualHeight(); mInitialActualHeight = last->GetActualHeight();
mInitialMinimized = track->GetMinimized(); mInitialMinimized = last->GetMinimized();
mInitialUpperTrackHeight = prev->GetHeight();
mInitialUpperActualHeight = prev->GetActualHeight(); if (channels.size() > 1) {
mMode = IsResizingBelowLinkedTracks; auto first = *channels.begin();
mInitialUpperTrackHeight = first->GetHeight();
mInitialUpperActualHeight = first->GetActualHeight();
if (track.get() == *channels.rbegin())
// capturedTrack is the lowest track
mMode = IsResizingBelowLinkedTracks;
else
// capturedTrack is not the lowest track
mMode = IsResizingBetweenLinkedTracks;
} }
else if (next && track->GetLink() == next) { else
// mpTrack is the upper track
mInitialTrackHeight = next->GetHeight();
mInitialActualHeight = next->GetActualHeight();
mInitialMinimized = next->GetMinimized();
mInitialUpperTrackHeight = track->GetHeight();
mInitialUpperActualHeight = track->GetActualHeight();
mMode = IsResizingBetweenLinkedTracks;
}
else {
// DM: Save the initial mouse location and the initial height
mInitialTrackHeight = track->GetHeight();
mInitialActualHeight = track->GetActualHeight();
mInitialMinimized = track->GetMinimized();
mMode = IsResizing; mMode = IsResizing;
}
} }
UIHandle::Result TrackPanelResizeHandle::Drag UIHandle::Result TrackPanelResizeHandle::Drag
@ -113,23 +109,24 @@ UIHandle::Result TrackPanelResizeHandle::Drag
// This used to be in HandleResizeClick(), but simply clicking // This used to be in HandleResizeClick(), but simply clicking
// on a resize border would switch the minimized state. // on a resize border would switch the minimized state.
if (pTrack->GetMinimized()) { if (pTrack->GetMinimized()) {
Track *link = pTrack->GetLink(); auto channels = TrackList::Channels( pTrack.get() );
for (auto channel : channels) {
channel->SetHeight(channel->GetHeight());
channel->SetMinimized(false);
}
pTrack->SetHeight(pTrack->GetHeight()); if (channels.size() > 1) {
pTrack->SetMinimized(false);
if (link) {
link->SetHeight(link->GetHeight());
link->SetMinimized(false);
// Initial values must be reset since they weren't based on the // Initial values must be reset since they weren't based on the
// minimized heights. // minimized heights.
mInitialUpperTrackHeight = link->GetHeight(); mInitialUpperTrackHeight = (*channels.begin())->GetHeight();
mInitialTrackHeight = pTrack->GetHeight(); mInitialTrackHeight = (*channels.rbegin())->GetHeight();
} }
} }
// Common pieces of code for MONO_WAVE_PAN and otherwise. // Common pieces of code for MONO_WAVE_PAN and otherwise.
auto doResizeBelow = [&] (Track *prev, bool WXUNUSED(vStereo)) { auto doResizeBelow = [&] (Track *prev, bool WXUNUSED(vStereo)) {
// TODO: more-than-two-channels
double proportion = static_cast < double >(mInitialTrackHeight) double proportion = static_cast < double >(mInitialTrackHeight)
/ (mInitialTrackHeight + mInitialUpperTrackHeight); / (mInitialTrackHeight + mInitialUpperTrackHeight);
@ -150,6 +147,8 @@ UIHandle::Result TrackPanelResizeHandle::Drag
}; };
auto doResizeBetween = [&] (Track *next, bool WXUNUSED(vStereo)) { auto doResizeBetween = [&] (Track *next, bool WXUNUSED(vStereo)) {
// TODO: more-than-two-channels
int newUpperTrackHeight = mInitialUpperTrackHeight + delta; int newUpperTrackHeight = mInitialUpperTrackHeight + delta;
int newTrackHeight = mInitialTrackHeight - delta; int newTrackHeight = mInitialTrackHeight - delta;

View File

@ -22,9 +22,7 @@ class TrackPanelResizeHandle final : public UIHandle
TrackPanelResizeHandle(const TrackPanelResizeHandle&) = delete; TrackPanelResizeHandle(const TrackPanelResizeHandle&) = delete;
public: public:
explicit TrackPanelResizeHandle explicit TrackPanelResizeHandle( const std::shared_ptr<Track> &pTrack, int y );
( const std::shared_ptr<Track> &pTrack, int y,
const AudacityProject *pProject );
TrackPanelResizeHandle &operator=(const TrackPanelResizeHandle&) = default; TrackPanelResizeHandle &operator=(const TrackPanelResizeHandle&) = default;

View File

@ -28,7 +28,7 @@ std::vector<UIHandlePtr> TrackPanelResizerCell::HitTest
auto pTrack = mpTrack.lock(); auto pTrack = mpTrack.lock();
if (pTrack) { if (pTrack) {
auto result = std::make_shared<TrackPanelResizeHandle>( auto result = std::make_shared<TrackPanelResizeHandle>(
pTrack, st.state.m_y, pProject ); pTrack, st.state.m_y );
result = AssignUIHandlePtr(mResizeHandle, result); result = AssignUIHandlePtr(mResizeHandle, result);
results.push_back(result); results.push_back(result);
} }

View File

@ -1359,10 +1359,9 @@ bool Effect::ProcessPass()
if (!left->GetSelected()) if (!left->GetSelected())
return fallthrough(); return fallthrough();
WaveTrack *right;
sampleCount len; sampleCount len;
sampleCount leftStart; sampleCount leftStart;
sampleCount rightStart; sampleCount rightStart = 0;
if (!isGenerator) if (!isGenerator)
{ {
@ -1376,48 +1375,37 @@ bool Effect::ProcessPass()
mSampleCnt = left->TimeToLongSamples(mDuration); mSampleCnt = left->TimeToLongSamples(mDuration);
} }
mNumChannels = 1; mNumChannels = 0;
WaveTrack *right{};
if (left->GetChannel() == Track::LeftChannel) // Iterate either over one track which could be any channel,
{ // or if multichannel, then over all channels of left,
map[0] = ChannelNameFrontLeft; // which is a leader.
} for (auto channel :
else if (left->GetChannel() == Track::RightChannel) TrackList::Channels(left).StartingWith(left)) {
{ if (channel->GetChannel() == Track::LeftChannel)
map[0] = ChannelNameFrontRight; map[mNumChannels] = ChannelNameFrontLeft;
} else if (channel->GetChannel() == Track::RightChannel)
else map[mNumChannels] = ChannelNameFrontRight;
{
map[0] = ChannelNameMono;
}
map[1] = ChannelNameEOL;
right = NULL;
rightStart = 0;
if (left->GetLinked() && multichannel)
{
// Assume linked track is wave
right = static_cast<WaveTrack *>(left->GetLink());
if (!isGenerator)
{
GetSamples(right, &rightStart, &len);
}
clear = false;
mNumChannels = 2;
if (right->GetChannel() == Track::LeftChannel)
{
map[1] = ChannelNameFrontLeft;
}
else if (right->GetChannel() == Track::RightChannel)
{
map[1] = ChannelNameFrontRight;
}
else else
{ map[mNumChannels] = ChannelNameMono;
map[1] = ChannelNameMono;
++ mNumChannels;
map[mNumChannels] = ChannelNameEOL;
if (! multichannel)
break;
if (mNumChannels == 2) {
// TODO: more-than-two-channels
right = channel;
clear = false;
if (!isGenerator)
GetSamples(right, &rightStart, &len);
// Ignore other channels
break;
} }
map[2] = ChannelNameEOL;
} }
// Let the client know the sample rate // Let the client know the sample rate

View File

@ -219,10 +219,6 @@ bool EffectNormalize::Process()
//Iterate over each track //Iterate over each track
this->CopyInputTracks(); // Set up mOutputTracks. this->CopyInputTracks(); // Set up mOutputTracks.
bool bGoodResult = true; bool bGoodResult = true;
SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks.get());
WaveTrack *track = (WaveTrack *) iter.First();
WaveTrack *prevTrack;
prevTrack = track;
double progress = 0; double progress = 0;
wxString topMsg; wxString topMsg;
if(mDC && mGain) if(mDC && mGain)
@ -234,8 +230,10 @@ bool EffectNormalize::Process()
else if(!mDC && !mGain) else if(!mDC && !mGain)
topMsg = _("Not doing anything...\n"); // shouldn't get here topMsg = _("Not doing anything...\n"); // shouldn't get here
while (track) { for ( auto track : mOutputTracks->Selected< WaveTrack >()
+ ( mStereoInd ? &Track::Any : &Track::IsLeader ) ) {
//Get start and end times from track //Get start and end times from track
// PRL: No accounting for multiple channels?
double trackStart = track->GetStartTime(); double trackStart = track->GetStartTime();
double trackEnd = track->GetEndTime(); double trackEnd = track->GetEndTime();
@ -244,121 +242,106 @@ bool EffectNormalize::Process()
mCurT0 = mT0 < trackStart? trackStart: mT0; mCurT0 = mT0 < trackStart? trackStart: mT0;
mCurT1 = mT1 > trackEnd? trackEnd: mT1; mCurT1 = mT1 > trackEnd? trackEnd: mT1;
auto range = mStereoInd
? TrackList::SingletonRange(track)
: TrackList::Channels(track);
// Process only if the right marker is to the right of the left marker // Process only if the right marker is to the right of the left marker
if (mCurT1 > mCurT0) { if (mCurT1 > mCurT0) {
wxString msg; wxString trackName = track->GetName();
auto trackName = track->GetName();
if(!track->GetLinked() || mStereoInd) float extent;
msg = #ifdef EXPERIMENTAL_R128_NORM
topMsg + wxString::Format( _("Analyzing: %s"), trackName ); if (mUseLoudness)
// Loudness: use sum of both tracks.
// As a result, stereo tracks appear about 3 LUFS louder,
// as specified.
extent = 0;
else else
msg = #endif
topMsg + wxString::Format( _("Analyzing first track of stereo pair: %s"), trackName ); // Will compute a maximum
float offset, extent; extent = std::numeric_limits<float>::lowest();
bGoodResult = AnalyseTrack(track, msg, progress, offset, extent); std::vector<float> offsets;
if (!bGoodResult )
break; wxString msg;
if(!track->GetLinked() || mStereoInd) { if (range.size() == 1)
// mono or 'stereo tracks independently' // mono or 'stereo tracks independently'
if( (extent > 0) && mGain ) msg = topMsg +
{ wxString::Format( _("Analyzing: %s"), trackName );
mMult = ratio / extent; else
msg = topMsg +
// TODO: more-than-two-channels-message
wxString::Format( _("Analyzing first track of stereo pair: %s"), trackName);
// Analysis loop over channels collects offsets and extent
for (auto channel : range) {
float offset, extent2;
bGoodResult =
AnalyseTrack( channel, msg, progress, offset, extent2 );
if ( ! bGoodResult )
goto break2;
#ifdef EXPERIMENTAL_R128_NORM #ifdef EXPERIMENTAL_R128_NORM
if(mUseLoudness) { if (mUseLoudness)
// LUFS is defined as -0.691 dB + 10*log10(sum(channels)) extent += extent2;
mMult /= 0.8529037031;
// LUFS are related to square values so the multiplier must be the root.
mMult = sqrt(ratio / extent);
}
#endif
}
else else
mMult = 1.0; #endif
extent = std::max( extent, extent2 );
msg = offsets.push_back(offset);
topMsg + wxString::Format( _("Processing: %s"), trackName ); // TODO: more-than-two-channels-message
msg = topMsg +
if(track->GetLinked() || prevTrack->GetLinked()) // only get here if there is a linked track but we are processing independently wxString::Format( _("Analyzing second track of stereo pair: %s"), trackName );
msg =
topMsg + wxString::Format( _("Processing stereo channels independently: %s"), trackName );
if (!ProcessOne(track, msg, progress, offset))
{
bGoodResult = false;
break;
}
} }
else {
// we have a linked stereo track
// so we need to find it's min, max and offset
// as they are needed to calc the multiplier for both tracks
track = (WaveTrack *) iter.Next(); // get the next one
msg =
topMsg + wxString::Format( _("Analyzing second track of stereo pair: %s"), trackName );
float offset2, extent2;
bGoodResult = AnalyseTrack(track, msg, progress, offset2, extent2);
if ( !bGoodResult )
break;
// Compute the multiplier using extent
if( (extent > 0) && mGain ) {
mMult = ratio / extent;
#ifdef EXPERIMENTAL_R128_NORM #ifdef EXPERIMENTAL_R128_NORM
if (mUseLoudness) { if(mUseLoudness) {
// Loudness: use sum of both tracks. // PRL: See commit 9cbb67a for the origin of the next line,
// As a result, stereo tracks appear about 3 LUFS louder, as specified. // which has no effect because mMult is again overwritten. What
extent = extent + extent2; // was the intent?
// LUFS is defined as -0.691 dB + 10*log10(sum(channels)) // LUFS is defined as -0.691 dB + 10*log10(sum(channels))
extent *= 0.8529037031; mMult /= 0.8529037031;
// LUFS are related to square values so the multiplier must be the root.
mMult = sqrt(ratio / extent);
} }
else {
// Peak: use maximum of both tracks.
extent = fmax(extent, extent2);
}
#else
// Peak: use maximum of both tracks.
extent = fmax(extent, extent2);
#endif #endif
}
else
mMult = 1.0;
if( (extent > 0) && mGain ) if (range.size() == 1) {
{ if (TrackList::Channels(track).size() == 1)
mMult = ratio / extent; // we need to use this for both linked tracks // really mono
#ifdef EXPERIMENTAL_R128_NORM msg = topMsg +
if(mUseLoudness) { wxString::Format( _("Processing: %s"), trackName );
// LUFS are related to square values so the multiplier must be the root.
mMult = sqrt(mMult);
}
#endif
}
else else
mMult = 1.0; //'stereo tracks independently'
// TODO: more-than-two-channels-message
msg = topMsg +
wxString::Format( _("Processing stereo channels independently: %s"), trackName);
}
else
msg = topMsg +
// TODO: more-than-two-channels-message
wxString::Format( _("Processing first track of stereo pair: %s"), trackName);
track = (WaveTrack *) iter.Prev(); // go back to the first linked one // Use multiplier in the second, processing loop over channels
msg = auto pOffset = offsets.begin();
topMsg + wxString::Format( _("Processing first track of stereo pair: %s"), trackName ); for (auto channel : range) {
if (false ==
if (!ProcessOne(track, msg, progress, offset)) (bGoodResult = ProcessOne(channel, msg, progress, *pOffset++)) )
{ goto break2;
bGoodResult = false; // TODO: more-than-two-channels-message
break; msg = topMsg +
} wxString::Format( _("Processing second track of stereo pair: %s"), trackName);
track = (WaveTrack *) iter.Next(); // go to the second linked one
msg =
topMsg + wxString::Format( _("Processing second track of stereo pair: %s"), trackName );
if (!ProcessOne(track, msg, progress, offset2))
{
bGoodResult = false;
break;
}
} }
} }
//Iterate to the next track
prevTrack = track;
track = (WaveTrack *) iter.Next();
} }
break2:
this->ReplaceProcessedTracks(bGoodResult); this->ReplaceProcessedTracks(bGoodResult);
return bGoodResult; return bGoodResult;
} }

View File

@ -252,11 +252,11 @@ bool EffectSBSMS::Process()
auto start = leftTrack->TimeToLongSamples(mCurT0); auto start = leftTrack->TimeToLongSamples(mCurT0);
auto end = leftTrack->TimeToLongSamples(mCurT1); auto end = leftTrack->TimeToLongSamples(mCurT1);
WaveTrack* rightTrack = NULL; // TODO: more-than-two-channels
if (leftTrack->GetLinked()) { WaveTrack *rightTrack =
* ++ TrackList::Channels(leftTrack).begin();
if (rightTrack) {
double t; double t;
// Assume linked track is wave or null
rightTrack = static_cast<WaveTrack*>(leftTrack->GetLink());
//Adjust bounds by the right tracks markers //Adjust bounds by the right tracks markers
t = rightTrack->GetStartTime(); t = rightTrack->GetStartTime();

View File

@ -120,10 +120,10 @@ bool EffectSoundTouch::ProcessWithTimeWarper(const TimeWarper &warper)
// Process only if the right marker is to the right of the left marker // Process only if the right marker is to the right of the left marker
if (mCurT1 > mCurT0) { if (mCurT1 > mCurT0) {
if (leftTrack->GetLinked()) { // TODO: more-than-two-channels
auto channels = TrackList::Channels(leftTrack);
if (auto rightTrack = * ++ channels.begin()) {
double t; double t;
// Assume linked track is wave
WaveTrack* rightTrack = static_cast<WaveTrack*>(leftTrack->GetLink());
//Adjust bounds by the right tracks markers //Adjust bounds by the right tracks markers
t = rightTrack->GetStartTime(); t = rightTrack->GetStartTime();

View File

@ -75,47 +75,40 @@ bool EffectStereoToMono::Process()
this->CopyInputTracks(); // Set up mOutputTracks. this->CopyInputTracks(); // Set up mOutputTracks.
bool bGoodResult = true; bool bGoodResult = true;
auto trackRange = mOutputTracks->Selected< WaveTrack >(); auto trackRange = mOutputTracks->SelectedLeaders< WaveTrack >();
mLeftTrack = *trackRange.first;
bool refreshIter = false; bool refreshIter = false;
if(mLeftTrack)
{
// create a NEW WaveTrack to hold all of the output
AudacityProject *p = GetActiveProject();
mOutTrack = p->GetTrackFactory()->NewWaveTrack(floatSample, mLeftTrack->GetRate());
}
int count = 0; int count = 0;
while ( trackRange.first != trackRange.second ) { while ( trackRange.first != trackRange.second ) {
mLeftTrack = *trackRange.first; mLeftTrack = *trackRange.first;
if (mLeftTrack->GetLinked()) { auto channels = TrackList::Channels( mLeftTrack );
if (channels.size() != 2) {
// TODO: more-than-two-channels
++ trackRange.first;
continue;
}
// Assume linked track is wave mRightTrack = * channels.rbegin();
mRightTrack = * ++ trackRange.first;
if ((mLeftTrack->GetRate() == mRightTrack->GetRate())) { if ((mLeftTrack->GetRate() == mRightTrack->GetRate())) {
auto leftTrackStart = mLeftTrack->TimeToLongSamples(mLeftTrack->GetStartTime()); auto leftTrackStart = mLeftTrack->TimeToLongSamples(mLeftTrack->GetStartTime());
auto rightTrackStart = mRightTrack->TimeToLongSamples(mRightTrack->GetStartTime()); auto rightTrackStart = mRightTrack->TimeToLongSamples(mRightTrack->GetStartTime());
mStart = wxMin(leftTrackStart, rightTrackStart); mStart = wxMin(leftTrackStart, rightTrackStart);
auto leftTrackEnd = mLeftTrack->TimeToLongSamples(mLeftTrack->GetEndTime()); auto leftTrackEnd = mLeftTrack->TimeToLongSamples(mLeftTrack->GetEndTime());
auto rightTrackEnd = mRightTrack->TimeToLongSamples(mRightTrack->GetEndTime()); auto rightTrackEnd = mRightTrack->TimeToLongSamples(mRightTrack->GetEndTime());
mEnd = wxMax(leftTrackEnd, rightTrackEnd); mEnd = wxMax(leftTrackEnd, rightTrackEnd);
bGoodResult = ProcessOne(count); bGoodResult = ProcessOne(count);
if (!bGoodResult) if (!bGoodResult)
break; break;
mOutTrack->Clear(mOutTrack->GetStartTime(), mOutTrack->GetEndTime()); // The right channel has been deleted, so we must restart from the beginning
refreshIter = true;
// The right channel has been deleted, so we must restart from the beginning
refreshIter = true;
}
} }
if (refreshIter) { if (refreshIter) {
trackRange = mOutputTracks->Selected< WaveTrack >(); trackRange = mOutputTracks->SelectedLeaders< WaveTrack >();
refreshIter = false; refreshIter = false;
} }
else else
@ -128,11 +121,6 @@ bool EffectStereoToMono::Process()
return bGoodResult; return bGoodResult;
} }
void EffectStereoToMono::End()
{
mOutTrack.reset();
}
bool EffectStereoToMono::ProcessOne(int count) bool EffectStereoToMono::ProcessOne(int count)
{ {
float curLeftFrame; float curLeftFrame;
@ -145,6 +133,10 @@ bool EffectStereoToMono::ProcessOne(int count)
Floats rightBuffer{ idealBlockLen }; Floats rightBuffer{ idealBlockLen };
bool bResult = true; bool bResult = true;
AudacityProject *p = GetActiveProject();
auto outTrack =
p->GetTrackFactory()->NewWaveTrack(floatSample, mLeftTrack->GetRate());
while (index < mEnd) { while (index < mEnd) {
bResult &= mLeftTrack->Get((samplePtr)leftBuffer.get(), floatSample, index, idealBlockLen); bResult &= mLeftTrack->Get((samplePtr)leftBuffer.get(), floatSample, index, idealBlockLen);
bResult &= mRightTrack->Get((samplePtr)rightBuffer.get(), floatSample, index, idealBlockLen); bResult &= mRightTrack->Get((samplePtr)rightBuffer.get(), floatSample, index, idealBlockLen);
@ -156,15 +148,15 @@ bool EffectStereoToMono::ProcessOne(int count)
curMonoFrame = (curLeftFrame + curRightFrame) / 2.0; curMonoFrame = (curLeftFrame + curRightFrame) / 2.0;
leftBuffer[i] = curMonoFrame; leftBuffer[i] = curMonoFrame;
} }
mOutTrack->Append((samplePtr)leftBuffer.get(), floatSample, limit); outTrack->Append((samplePtr)leftBuffer.get(), floatSample, limit);
if (TrackProgress(count, 2.*(index.as_double() / (mEnd - mStart).as_double()))) if (TrackProgress(count, 2.*(index.as_double() / (mEnd - mStart).as_double())))
return false; return false;
} }
double minStart = wxMin(mLeftTrack->GetStartTime(), mRightTrack->GetStartTime()); double minStart = wxMin(mLeftTrack->GetStartTime(), mRightTrack->GetStartTime());
mLeftTrack->Clear(mLeftTrack->GetStartTime(), mLeftTrack->GetEndTime()); mLeftTrack->Clear(mLeftTrack->GetStartTime(), mLeftTrack->GetEndTime());
mOutTrack->Flush(); outTrack->Flush();
mLeftTrack->Paste(minStart, mOutTrack.get()); mLeftTrack->Paste(minStart, outTrack.get());
mLeftTrack->SetLinked(false); mLeftTrack->SetLinked(false);
mRightTrack->SetLinked(false); mRightTrack->SetLinked(false);
mLeftTrack->SetChannel(Track::MonoChannel); mLeftTrack->SetChannel(Track::MonoChannel);

View File

@ -41,7 +41,6 @@ public:
// Effect implementation // Effect implementation
bool Process() override; bool Process() override;
void End() override;
bool IsHidden() override; bool IsHidden() override;
private: private:
@ -54,7 +53,6 @@ private:
sampleCount mEnd; sampleCount mEnd;
WaveTrack *mLeftTrack; WaveTrack *mLeftTrack;
WaveTrack *mRightTrack; WaveTrack *mRightTrack;
std::unique_ptr<WaveTrack> mOutTrack;
}; };
#endif #endif

View File

@ -769,7 +769,7 @@ bool NyquistEffect::Process()
Effect::MessageBox(message, wxOK | wxCENTRE | wxICON_EXCLAMATION, _("Nyquist Error")); Effect::MessageBox(message, wxOK | wxCENTRE | wxICON_EXCLAMATION, _("Nyquist Error"));
} }
auto trackRange = mOutputTracks->Selected< WaveTrack >(); auto trackRange = mOutputTracks->Selected< WaveTrack >() + &Track::IsLeader;
// Keep track of whether the current track is first selected in its sync-lock group // Keep track of whether the current track is first selected in its sync-lock group
// (we have no idea what the length of the returned audio will be, so we have // (we have no idea what the length of the returned audio will be, so we have
@ -785,10 +785,14 @@ bool NyquistEffect::Process()
if (bOnePassTool) { if (bOnePassTool) {
} }
else { else {
if (mCurTrack[0]->GetLinked()) { auto channels = TrackList::Channels(mCurTrack[0]);
if (channels.size() > 1) {
// TODO: more-than-two-channels
// Pay attention to consistency of mNumSelectedChannels
// with the running tally made by this loop!
mCurNumChannels = 2; mCurNumChannels = 2;
mCurTrack[1] = * ++ iter; mCurTrack[1] = * ++ channels.first;
if (mCurTrack[1]->GetRate() != mCurTrack[0]->GetRate()) { if (mCurTrack[1]->GetRate() != mCurTrack[0]->GetRate()) {
Effect::MessageBox(_("Sorry, cannot apply effect on stereo tracks where the tracks don't match."), Effect::MessageBox(_("Sorry, cannot apply effect on stereo tracks where the tracks don't match."),
wxOK | wxCENTRE); wxOK | wxCENTRE);

View File

@ -299,25 +299,25 @@ bool VampEffect::Init()
{ {
mRate = 0.0; mRate = 0.0;
for (auto left : inputTracks()->Leaders< const WaveTrack >() ) // PRL: this loop checked that channels of a track have the same rate,
{ // but there was no check that all tracks have one rate, and only the first
if (mRate == 0.0) // is remembered in mRate. Is that correct?
{
mRate = left->GetRate();
}
if (left->GetLinked())
{
auto right = static_cast<const WaveTrack*>( left->GetLink() );
for (auto leader : inputTracks()->Leaders<const WaveTrack>()) {
auto channelGroup = TrackList::Channels( leader );
auto rate = (*channelGroup.first++) -> GetRate();
for(auto channel : channelGroup) {
if (rate != channel->GetRate())
// PRL: Track rate might not match individual clip rates. // PRL: Track rate might not match individual clip rates.
// So is this check not adequate? // So is this check not adequate?
if (left->GetRate() != right->GetRate()) {
{ // TODO: more-than-two-channels-message
Effect::MessageBox(_("Sorry, Vamp Plug-ins cannot be run on stereo tracks where the individual channels of the track do not match.")); Effect::MessageBox(_("Sorry, Vamp Plug-ins cannot be run on stereo tracks where the individual channels of the track do not match."));
return false; return false;
} }
} }
if (mRate == 0.0)
mRate = rate;
} }
if (mRate <= 0.0) if (mRate <= 0.0)
@ -361,22 +361,26 @@ bool VampEffect::Process()
std::vector<std::shared_ptr<Effect::AddedAnalysisTrack>> addedTracks; std::vector<std::shared_ptr<Effect::AddedAnalysisTrack>> addedTracks;
for (auto left : inputTracks()->Leaders< const WaveTrack >() ) for (auto leader : inputTracks()->Leaders<const WaveTrack>())
{ {
auto channelGroup = TrackList::Channels(leader);
auto left = *channelGroup.first++;
sampleCount lstart, rstart = 0; sampleCount lstart, rstart = 0;
sampleCount len; sampleCount len;
GetSamples(left, &lstart, &len); GetSamples(left, &lstart, &len);
unsigned channels = 1; unsigned channels = 1;
const WaveTrack *right{}; const WaveTrack *right = *channelGroup.first++;
if (left->GetLinked()) if (right)
{ {
right = static_cast< const WaveTrack* >( left->GetLink() );
channels = 2; channels = 2;
GetSamples(right, &rstart, &len); GetSamples(right, &rstart, &len);
} }
// TODO: more-than-two-channels
size_t step = mPlugin->getPreferredStepSize(); size_t step = mPlugin->getPreferredStepSize();
size_t block = mPlugin->getPreferredBlockSize(); size_t block = mPlugin->getPreferredBlockSize();

View File

@ -562,6 +562,8 @@ protected:
void OnChannelChange(wxCommandEvent & event); void OnChannelChange(wxCommandEvent & event);
void OnMergeStereo(wxCommandEvent & event); void OnMergeStereo(wxCommandEvent & event);
// TODO: more-than-two-channels
// How should we define generalized channel manipulation operations?
void SplitStereo(bool stereo); void SplitStereo(bool stereo);
void OnSwapChannels(wxCommandEvent & event); void OnSwapChannels(wxCommandEvent & event);
@ -603,23 +605,28 @@ void WaveTrackMenuTable::InitMenu(Menu *pMenu, void *pUserData)
(display == WaveTrack::Spectrum) && !bAudioBusy); (display == WaveTrack::Spectrum) && !bAudioBusy);
AudacityProject *const project = ::GetActiveProject(); AudacityProject *const project = ::GetActiveProject();
TrackList *const tracks = project->GetTracks();
bool unsafe = EffectManager::Get().RealtimeIsActive() && bool unsafe = EffectManager::Get().RealtimeIsActive() &&
project->IsAudioActive(); project->IsAudioActive();
const bool isMono = !pTrack->GetLink(); auto nChannels = TrackList::Channels(pTrack).size();
const bool isMono = ( nChannels == 1 );
const bool isStereo = ( nChannels == 2 );
// Maybe more than stereo tracks some time?
if ( isMono ) if ( isMono )
{ {
mpData = static_cast<TrackControls::InitMenuData*>(pUserData); mpData = static_cast<TrackControls::InitMenuData*>(pUserData);
WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack); WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
TrackList *const tracks = project->GetTracks();
auto next = * ++ tracks->Find(pTrack); auto next = * ++ tracks->Find(pTrack);
if (isMono) { if (isMono) {
const bool canMakeStereo = const bool canMakeStereo =
(next && !next->GetLinked() (next &&
&& pTrack->GetKind() == Track::Wave TrackList::Channels(next).size() == 1 &&
&& next->GetKind() == Track::Wave); track_cast<WaveTrack*>(next));
pMenu->Enable(OnMergeStereoID, canMakeStereo && !unsafe); pMenu->Enable(OnMergeStereoID, canMakeStereo && !unsafe);
int itemId; int itemId;
@ -647,8 +654,8 @@ void WaveTrackMenuTable::InitMenu(Menu *pMenu, void *pUserData)
return end != std::find(checkedIds.begin(), end, id); return end != std::find(checkedIds.begin(), end, id);
}); });
// Enable this only for properly stereo tracks:
pMenu->Enable(OnSwapChannelsID, !isMono && !unsafe); pMenu->Enable(OnSwapChannelsID, isStereo && !unsafe);
pMenu->Enable(OnSplitStereoID, !isMono && !unsafe); pMenu->Enable(OnSplitStereoID, !isMono && !unsafe);
#ifndef EXPERIMENTAL_DA #ifndef EXPERIMENTAL_DA
@ -836,11 +843,16 @@ void WaveTrackMenuTable::OnChannelChange(wxCommandEvent & event)
/// Merge two tracks into one stereo track ?? /// Merge two tracks into one stereo track ??
void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &) void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
{ {
AudacityProject *const project = ::GetActiveProject();
const auto tracks = project->GetTracks();
WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack); WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
wxASSERT(pTrack); wxASSERT(pTrack);
auto partner = static_cast< WaveTrack * >
( *tracks->Find( pTrack ).advance( 1 ) );
pTrack->SetLinked(true); pTrack->SetLinked(true);
// Assume partner is wave or null
const auto partner = static_cast<WaveTrack*>(pTrack->GetLink());
if (partner) { if (partner) {
// Set partner's parameters to match target. // Set partner's parameters to match target.
@ -884,51 +896,40 @@ void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
mpData->result = RefreshCode::RefreshAll; mpData->result = RefreshCode::RefreshAll;
} }
/// Split a stereo track into two tracks... /// Split a stereo track (or more-than-stereo?) into two (or more) tracks...
void WaveTrackMenuTable::SplitStereo(bool stereo) void WaveTrackMenuTable::SplitStereo(bool stereo)
{ {
WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack); WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
wxASSERT(pTrack); wxASSERT(pTrack);
AudacityProject *const project = ::GetActiveProject();
auto channels = TrackList::Channels( pTrack );
if (stereo)
pTrack->SetPanFromChannelType();
pTrack->SetChannel(Track::MonoChannel);
// Assume partner is present, and is wave int totalHeight = 0;
const auto partner = static_cast<WaveTrack*>(pTrack->GetLink()); int nChannels = 0;
wxASSERT(partner); for (auto channel : channels) {
if (!partner)
return;
if (partner)
{
// Keep original stereo track name. // Keep original stereo track name.
partner->SetName(pTrack->GetName()); channel->SetName(pTrack->GetName());
if (stereo) if (stereo)
partner->SetPanFromChannelType(); channel->SetPanFromChannelType();
partner->SetChannel(Track::MonoChannel); channel->SetChannel(Track::MonoChannel);
//On Demand - have each channel add its own. //On Demand - have each channel add its own.
if (ODManager::IsInstanceCreated() && partner->GetKind() == Track::Wave) if (ODManager::IsInstanceCreated())
ODManager::Instance()->MakeWaveTrackIndependent(partner); ODManager::Instance()->MakeWaveTrackIndependent(channel);
//make sure no channel is smaller than its minimum height
if (channel->GetHeight() < channel->GetMinimizedHeight())
channel->SetHeight(channel->GetMinimizedHeight());
totalHeight += channel->GetHeight();
++nChannels;
} }
pTrack->SetLinked(false); pTrack->SetLinked(false);
//make sure neither track is smaller than its minimum height int averageHeight = totalHeight / nChannels;
if (pTrack->GetHeight() < pTrack->GetMinimizedHeight())
pTrack->SetHeight(pTrack->GetMinimizedHeight());
if (partner)
{
if (partner->GetHeight() < partner->GetMinimizedHeight())
partner->SetHeight(partner->GetMinimizedHeight());
for (auto channel : channels)
// Make tracks the same height // Make tracks the same height
if (pTrack->GetHeight() != partner->GetHeight()) channel->SetHeight( averageHeight );
{
pTrack->SetHeight((pTrack->GetHeight() + partner->GetHeight()) / 2.0);
partner->SetHeight(pTrack->GetHeight());
}
}
mpData->result = RefreshCode::RefreshAll; mpData->result = RefreshCode::RefreshAll;
} }
@ -939,14 +940,19 @@ void WaveTrackMenuTable::OnSwapChannels(wxCommandEvent &)
AudacityProject *const project = ::GetActiveProject(); AudacityProject *const project = ::GetActiveProject();
WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack); WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
// Assume partner is wave or null auto channels = TrackList::Channels( pTrack );
const auto partner = static_cast<WaveTrack*>(pTrack->GetLink()); if (channels.size() != 2)
return;
Track *const focused = project->GetTrackPanel()->GetFocusedTrack(); Track *const focused = project->GetTrackPanel()->GetFocusedTrack();
const bool hasFocus = const bool hasFocus = channels.contains( focused );
(focused == pTrack || focused == partner);
auto first = *channels.begin();
auto partner = *channels.rbegin();
SplitStereo(false); SplitStereo(false);
pTrack->SetChannel(Track::RightChannel);
first->SetChannel(Track::RightChannel);
partner->SetChannel(Track::LeftChannel); partner->SetChannel(Track::LeftChannel);
TrackList *const tracks = project->GetTracks(); TrackList *const tracks = project->GetTracks();