diff --git a/src/Project.cpp b/src/Project.cpp index 65c82bda2..8c795af24 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -2051,25 +2051,6 @@ void AudacityProject::FixScrollbars() GetTrackPanel()->HandleCursorForPresentMouseState(); } ); } -std::shared_ptr AudacityProject::GetFirstVisible() -{ - std::shared_ptr pTrack; - if (GetTracks()) { - TrackListIterator iter(GetTracks()); - for (Track *t = iter.First(); t; t = iter.Next()) { - int y = t->GetY(); - int h = t->GetHeight(); - if (y + h - 1 >= mViewInfo.vpos) { - // At least the bottom row of pixels is not scrolled away above - pTrack = Track::Pointer(t); - break; - } - } - } - - return pTrack; -} - void AudacityProject::UpdateLayout() { if (!mTrackPanel) @@ -4232,23 +4213,35 @@ AudacityProject::AddImportedTracks(const wxString &fileName, bool initiallyEmpty = mTracks->empty(); double newRate = 0; wxString trackNameBase = fileName.AfterLast(wxFILE_SEP_PATH).BeforeLast('.'); - bool isLinked = false; int i = -1; - for (auto &uNewTrack : newTracks) { - ++i; + // Must add all tracks first (before using Track::IsLeader) + for (auto &uNewTrack : newTracks) { auto newTrack = mTracks->Add(std::move(uNewTrack)); results.push_back(Track::Pointer(newTrack)); + } + newTracks.clear(); + + // Now name them + + // Add numbers to track names only if there is more than one (mono or stereo) + // track (not necessarily, more than one channel) + const bool useSuffix = + make_iterator_range( results.begin() + 1, results.end() ) + .any_of( []( decltype(*results.begin()) &pTrack ) + { return pTrack->IsLeader(); } ); + + for (const auto &newTrack : results) { + if ( newTrack->IsLeader() ) + // Count groups only + ++i; + newTrack->SetSelected(true); - //we need to check link status based on the first channel only. - if(0==i) - isLinked = newTrack->GetLinked(); - if (numTracks > 2 || (numTracks > 1 && !isLinked) ) { + + if ( useSuffix ) newTrack->SetName(trackNameBase + wxString::Format(wxT(" %d" ), i + 1)); - } - else { + else newTrack->SetName(trackNameBase); - } newTrack->TypeSwitch( [&](WaveTrack *wt) { if (newRate == 0) @@ -4299,8 +4292,6 @@ AudacityProject::AddImportedTracks(const wxString &fileName, // Moved this call to higher levels to prevent flicker redrawing everything on each file. // HandleResize(); - newTracks.clear(); - return results; } diff --git a/src/Project.h b/src/Project.h index 3d533e624..53986d0f0 100644 --- a/src/Project.h +++ b/src/Project.h @@ -213,8 +213,6 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame, const ViewInfo &GetViewInfo() const { return mViewInfo; } ViewInfo &GetViewInfo() { return mViewInfo; } - std::shared_ptr GetFirstVisible(); - void GetPlayRegion(double* playRegionStart, double *playRegionEnd); bool IsPlayRegionLocked() { return mLockPlayRegion; } void SetPlayRegionLocked(bool value) { mLockPlayRegion = value; } diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp index 576416602..8bb54047b 100644 --- a/src/TrackArtist.cpp +++ b/src/TrackArtist.cpp @@ -341,7 +341,6 @@ void TrackArtist::SetMargins(int left, int top, int right, int bottom) void TrackArtist::DrawTracks(TrackPanelDrawingContext &context, const TrackList * tracks, - Track * start, const wxRegion & reg, const wxRect & rect, const wxRect & clip, @@ -351,13 +350,11 @@ void TrackArtist::DrawTracks(TrackPanelDrawingContext &context, bool bigPoints, bool drawSliders) { + // Copy the horizontal extent of rect; will later change only the vertical. wxRect trackRect = rect; - wxRect stereoTrackRect; - TrackListConstIterator iter(tracks); - const Track *t; bool hasSolo = false; - for (t = iter.First(); t; t = iter.Next()) { + for (const Track *t : *tracks) { t = t->SubstitutePendingChangedTrack().get(); auto pt = dynamic_cast(t); if (pt && pt->GetSolo()) { @@ -381,60 +378,52 @@ void TrackArtist::DrawTracks(TrackPanelDrawingContext &context, gPrefs->Read(wxT("/GUI/ShowTrackNameInWaveform"), &mbShowTrackNameInWaveform, false); - t = iter.StartWith(start); - while (t) { - t = t->SubstitutePendingChangedTrack().get(); - trackRect.y = t->GetY() - zoomInfo.vpos; - trackRect.height = t->GetHeight(); + for(auto leader : tracks->Leaders()) { + auto group = TrackList::Channels( leader ); + leader = leader->SubstitutePendingChangedTrack().get(); - if (trackRect.y > clip.GetBottom() && !t->GetLinked()) { + trackRect.y = leader->GetY() - zoomInfo.vpos; + trackRect.height = group.sum( [&] (const Track *channel) { + channel = channel->SubstitutePendingChangedTrack().get(); + return channel->GetHeight(); + }); + + if (trackRect.GetBottom() < clip.GetTop()) + continue; + else if (trackRect.GetTop() > clip.GetBottom()) break; - } + + for (auto t : group) { + t = t->SubstitutePendingChangedTrack().get(); #if defined(DEBUG_CLIENT_AREA) - // Filled rectangle to show the interior of the client area - wxRect zr = trackRect; - zr.x+=1; zr.y+=5; zr.width-=7; zr.height-=7; - dc.SetPen(*wxCYAN_PEN); - dc.SetBrush(*wxRED_BRUSH); - dc.DrawRectangle(zr); + // Filled rectangle to show the interior of the client area + wxRect zr = trackRect; + zr.x+=1; zr.y+=5; zr.width-=7; zr.height-=7; + dc.SetPen(*wxCYAN_PEN); + dc.SetBrush(*wxRED_BRUSH); + dc.DrawRectangle(zr); #endif - stereoTrackRect = trackRect; + // For various reasons, the code will break if we display one + // of a stereo pair of tracks but not the other - for example, + // if you try to edit the envelope of one track when its linked + // pair is off the screen, then it won't be able to edit the + // offscreen envelope. So we compute the rect of the track and + // its linked partner, and see if any part of that rect is on-screen. + // If so, we draw both. Otherwise, we can safely draw neither. - // For various reasons, the code will break if we display one - // of a stereo pair of tracks but not the other - for example, - // if you try to edit the envelope of one track when its linked - // pair is off the screen, then it won't be able to edit the - // offscreen envelope. So we compute the rect of the track and - // its linked partner, and see if any part of that rect is on-screen. - // If so, we draw both. Otherwise, we can safely draw neither. - - Track *link = t->GetLink(); - if (link) { - if (t->GetLinked()) { - // If we're the first track - stereoTrackRect.height += link->GetHeight(); - } - else { - // We're the second of two - stereoTrackRect.y -= link->GetHeight(); - stereoTrackRect.height += link->GetHeight(); + if (trackRect.Intersects(clip) && reg.Contains(trackRect)) { + wxRect rr = trackRect; + rr.x += mMarginLeft; + rr.y += mMarginTop; + rr.width -= (mMarginLeft + mMarginRight); + rr.height -= (mMarginTop + mMarginBottom); + DrawTrack(context, t, rr, + selectedRegion, zoomInfo, + drawEnvelope, bigPoints, drawSliders, hasSolo); } } - - if (stereoTrackRect.Intersects(clip) && reg.Contains(stereoTrackRect)) { - wxRect rr = trackRect; - rr.x += mMarginLeft; - rr.y += mMarginTop; - rr.width -= (mMarginLeft + mMarginRight); - rr.height -= (mMarginTop + mMarginBottom); - DrawTrack(context, t, rr, - selectedRegion, zoomInfo, - drawEnvelope, bigPoints, drawSliders, hasSolo); - } - - t = iter.Next(); } } @@ -480,8 +469,7 @@ void TrackArtist::DrawTrack(TrackPanelDrawingContext &context, #endif if (mbShowTrackNameInWaveform && - // Exclude right channel of stereo track - !(!wt->GetLinked() && wt->GetLink()) && + wt->IsLeader() && // Exclude empty name. !wt->GetName().IsEmpty()) { wxBrush Brush; diff --git a/src/TrackArtist.h b/src/TrackArtist.h index f3c5ab543..1cd00c5e1 100644 --- a/src/TrackArtist.h +++ b/src/TrackArtist.h @@ -55,7 +55,7 @@ class AUDACITY_DLL_API TrackArtist { void SetColours(int iColorIndex); void DrawTracks(TrackPanelDrawingContext &context, - const TrackList *tracks, Track *start, + const TrackList *tracks, const wxRegion & reg, const wxRect & rect, const wxRect & clip, const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo, diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 8168f5db3..db2755daa 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -1156,8 +1156,7 @@ void TrackPanel::DrawTracks(wxDC * dc) TrackPanelDrawingContext context{ *dc, Target(), mLastMouseState }; // The track artist actually draws the stuff inside each track - auto first = GetProject()->GetFirstVisible(); - mTrackArtist->DrawTracks(context, GetTracks(), first.get(), + mTrackArtist->DrawTracks(context, GetTracks(), region, tracksRect, clip, mViewInfo->selectedRegion, *mViewInfo, envelopeFlag, bigPointsFlag, sliderFlag); @@ -1847,14 +1846,12 @@ void TrackPanel::UpdateTrackVRuler(const Track *t) wxRect rect(GetVRulerOffset(), kTopMargin, GetVRulerWidth(), - t->GetHeight() - (kTopMargin + kBottomMargin)); + 0); - mTrackArtist->UpdateVRuler(t, rect); - const Track *l = t->GetLink(); - if (l) - { - rect.height = l->GetHeight() - (kTopMargin + kBottomMargin); - mTrackArtist->UpdateVRuler(l, rect); + + for (auto channel : TrackList::Channels(t)) { + rect.height = channel->GetHeight() - (kTopMargin + kBottomMargin); + mTrackArtist->UpdateVRuler(channel, rect); } } @@ -2080,30 +2077,29 @@ wxRect TrackPanel::FindTrackRect( const Track * target, bool label ) return { 0, 0, 0, 0 }; } - wxRect rect{ - 0, - target->GetY() - mViewInfo->vpos, - GetSize().GetWidth(), - target->GetHeight() - }; - // PRL: I think the following very old comment misused the term "race // condition" for a bug that happened with only a single thread. I think the // real problem referred to, was that this function could be reached, via // TrackPanelAx callbacks, during low-level operations while the TrackList - // was not in a consistent state. Therefore GetLinked() did not imply - // that GetLink() was not null. + // was not in a consistent state. // Now the problem is fixed by delaying the handling of events generated - // by TrackList. + // by TrackList. And besides that, we use Channels() instead of looking + // directly at the links. // Old comment: // The check for a null linked track is necessary because there's // a possible race condition between the time the 2 linked tracks // are added and when wxAccessible methods are called. This is // most evident when using Jaws. - if (target->GetLinked() && target->GetLink()) { - rect.height += target->GetLink()->GetHeight(); - } + auto height = TrackList::Channels( target ).sum( &Track::GetHeight ); + + wxRect rect{ + 0, + target->GetY() - mViewInfo->vpos, + GetSize().GetWidth(), + height + }; + rect.x += kLeftMargin; if (label) @@ -2151,8 +2147,7 @@ void TrackPanel::SetFocusedCell() void TrackPanel::SetFocusedTrack( Track *t ) { // Make sure we always have the first linked track of a stereo track - if (t && !t->GetLinked() && t->GetLink()) - t = (WaveTrack*)t->GetLink(); + t = *GetTracks()->FindLeader(t); auto cell = mAx->SetFocus( Track::Pointer( t ) ).get(); @@ -2657,7 +2652,7 @@ TrackPanelCellIterator &TrackPanelCellIterator::operator++ () } if ( mpTrack ) { if ( mType == CellType::Label && - mpTrack->GetLink() && !mpTrack->GetLinked() ) + !mpTrack->IsLeader() ) // Visit label of stereo track only once ++mType; switch ( mType ) { @@ -2723,10 +2718,10 @@ void TrackPanelCellIterator::UpdateRect() mRect.x = kLeftMargin; mRect.width = kTrackInfoWidth - mRect.x; mRect.y += kTopMargin; + mRect.height = + TrackList::Channels(mpTrack.get()) + .sum( &Track::GetHeight ); mRect.height -= (kBottomMargin + kTopMargin); - auto partner = mpTrack->GetLink(); - if ( partner && mpTrack->GetLinked() ) - mRect.height += partner->GetHeight(); break; } case CellType::VRuler: @@ -2742,11 +2737,12 @@ void TrackPanelCellIterator::UpdateRect() // The resizer region encompasses the bottom margin proper to this // track, plus the top margin of the next track (or, an equally // tall zone below, in case there is no next track) - auto partner = mpTrack->GetLink(); - if ( partner && mpTrack->GetLinked() ) - mRect.x = kTrackInfoWidth; - else + if ( mpTrack.get() == + *TrackList::Channels(mpTrack.get()).rbegin() ) + // Last channel has a resizer extending farther leftward mRect.x = kLeftMargin; + else + mRect.x = kTrackInfoWidth; mRect.width -= (mRect.x + kRightMargin); mRect.y += (mRect.height - kBottomMargin); mRect.height = (kBottomMargin + kTopMargin); diff --git a/src/TrackPanelAx.cpp b/src/TrackPanelAx.cpp index 96c115692..859979847 100644 --- a/src/TrackPanelAx.cpp +++ b/src/TrackPanelAx.cpp @@ -144,13 +144,9 @@ bool TrackPanelAx::IsFocused( const Track *track ) if (origTrack) track = origTrack; - if( ( track == focusedTrack.get() ) || - ( focusedTrack && track == focusedTrack->GetLink() ) ) - { - return true; - } - - return false; + return focusedTrack + ? TrackList::Channels(focusedTrack.get()).contains(track) + : !track; } int TrackPanelAx::TrackNum( const std::shared_ptr &target ) diff --git a/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp index d79cb9197..c47d97421 100644 --- a/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp @@ -143,8 +143,6 @@ UIHandle::Result CutlineHandle::Click // Cutline data changed on either branch, so refresh the track display. UIHandle::Result result = RefreshCell; - // Assume linked track is wave or null - const auto linked = static_cast(mpTrack->GetLink()); if (event.LeftDown()) { @@ -156,26 +154,30 @@ UIHandle::Result CutlineHandle::Click // When user presses left button on cut line, expand the line again double cutlineStart = 0, cutlineEnd = 0; + double *pCutlineStart = &cutlineStart, *pCutlineEnd = &cutlineEnd; - mpTrack->ExpandCutLine(mLocation.pos, &cutlineStart, &cutlineEnd); - - if (linked) - // Expand the cutline in the opposite channel if it is present. - linked->ExpandCutLine(mLocation.pos); + for (auto channel : + TrackList::Channels(mpTrack.get())) { + channel->ExpandCutLine( + mLocation.pos, pCutlineStart, pCutlineEnd); + if ( channel == mpTrack.get() ) + pCutlineStart = pCutlineEnd = nullptr; + } viewInfo.selectedRegion.setTimes(cutlineStart, cutlineEnd); result |= UpdateSelection; } else if (mLocation.typ == WaveTrackLocation::locationMergePoint) { const double pos = mLocation.pos; - mpTrack->MergeClips(mLocation.clipidx1, mLocation.clipidx2); - - if (linked) { + for (auto channel : + TrackList::Channels(mpTrack.get())) { // Don't assume correspondence of merge points across channels! - int idx = FindMergeLine(linked, pos); + int idx = FindMergeLine(channel, pos); if (idx >= 0) { - WaveTrack::Location location = linked->GetCachedLocations()[idx]; - linked->MergeClips(location.clipidx1, location.clipidx2); + WaveTrack::Location location = + channel->GetCachedLocations()[idx]; + channel->MergeClips( + location.clipidx1, location.clipidx2); } } @@ -184,10 +186,10 @@ UIHandle::Result CutlineHandle::Click } else if (event.RightDown()) { - bool removed = mpTrack->RemoveCutLine(mLocation.pos); - - if (linked) - removed = linked->RemoveCutLine(mLocation.pos) || removed; + bool removed = false; + for (auto channel : + TrackList::Channels(mpTrack.get())) + removed = channel->RemoveCutLine(mLocation.pos) || removed; if (!removed) // Nothing happened, make no Undo item diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp index cb719bbce..99ebfc8ed 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp @@ -213,14 +213,9 @@ void WaveColorMenuTable::OnWaveColorChange(wxCommandEvent & event) int newWaveColor = id - OnInstrument1ID; AudacityProject *const project = ::GetActiveProject(); -// TrackList *const tracks = project->GetTracks(); - pTrack->SetWaveColorIndex(newWaveColor); - - // Assume partner is wave or null - const auto partner = static_cast(pTrack->GetLink()); - if (partner) - partner->SetWaveColorIndex(newWaveColor); + for (auto channel : TrackList::Channels(pTrack)) + channel->SetWaveColorIndex(newWaveColor); project->PushState(wxString::Format(_("Changed '%s' to %s"), pTrack->GetName(), @@ -336,12 +331,9 @@ void FormatMenuTable::OnFormatChange(wxCommandEvent & event) return; // Nothing to do. AudacityProject *const project = ::GetActiveProject(); - pTrack->ConvertToSampleFormat(newFormat); - // Assume partner is wave or null - const auto partner = static_cast(pTrack->GetLink()); - if (partner) - partner->ConvertToSampleFormat(newFormat); + for (auto channel : TrackList::Channels(pTrack)) + channel->ConvertToSampleFormat(newFormat); /* i18n-hint: The strings name a track and a format */ project->PushState(wxString::Format(_("Changed '%s' to %s"), @@ -439,11 +431,9 @@ int RateMenuTable::IdOfRate(int rate) void RateMenuTable::SetRate(WaveTrack * pTrack, double rate) { AudacityProject *const project = ::GetActiveProject(); - pTrack->SetRate(rate); - // Assume linked track is wave or null - const auto partner = static_cast(pTrack->GetLink()); - if (partner) - partner->SetRate(rate); + for (auto channel : TrackList::Channels(pTrack)) + channel->SetRate(rate); + // Separate conversion of "rate" enables changing the decimals without affecting i18n wxString rateString = wxString::Format(wxT("%.3f"), rate); /* i18n-hint: The string names a track */ @@ -733,22 +723,13 @@ void WaveTrackMenuTable::OnSetDisplay(wxCommandEvent & event) (id == WaveTrack::Waveform && pTrack->GetWaveformSettings().isLinear() != linear); if (wrongType || wrongScale) { - pTrack->SetLastScaleType(); - pTrack->SetDisplay(WaveTrack::WaveTrackDisplay(id)); - if (wrongScale) - pTrack->GetIndependentWaveformSettings().scaleType = linear - ? WaveformSettings::stLinear - : WaveformSettings::stLogarithmic; - - // Assume partner is wave or null - auto partner = static_cast(pTrack->GetLink()); - if (partner) { - partner->SetLastScaleType(); - partner->SetDisplay(WaveTrack::WaveTrackDisplay(id)); + for (auto channel : TrackList::Channels(pTrack)) { + channel->SetLastScaleType(); + channel->SetDisplay(WaveTrack::WaveTrackDisplay(id)); if (wrongScale) - partner->GetIndependentWaveformSettings().scaleType = linear - ? WaveformSettings::stLinear - : WaveformSettings::stLogarithmic; + channel->GetIndependentWaveformSettings().scaleType = linear + ? WaveformSettings::stLinear + : WaveformSettings::stLogarithmic; } AudacityProject *const project = ::GetActiveProject(); diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp index 939dac2a8..dcf64e25f 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp @@ -47,12 +47,9 @@ UIHandle::Result GainSliderHandle::SetValue auto pTrack = GetWaveTrack(); if (pTrack) { - pTrack->SetGain(newValue); - - // Assume linked track is wave or null - const auto link = static_cast(mpTrack.lock()->GetLink()); - if (link) - link->SetGain(newValue); + for (auto channel : + TrackList::Channels(pTrack.get())) + channel->SetGain(newValue); MixerBoard *const pMixerBoard = pProject->GetMixerBoard(); if (pMixerBoard) @@ -131,12 +128,9 @@ UIHandle::Result PanSliderHandle::SetValue(AudacityProject *pProject, float newV auto pTrack = GetWaveTrack(); if (pTrack) { - pTrack->SetPan(newValue); - - // Assume linked track is wave or null - const auto link = static_cast(pTrack->GetLink()); - if (link) - link->SetPan(newValue); + for (auto channel : + TrackList::Channels(pTrack.get())) + channel->SetPan(newValue); MixerBoard *const pMixerBoard = pProject->GetMixerBoard(); if (pMixerBoard) diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackVRulerControls.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackVRulerControls.cpp index d818167c8..db0ce17f5 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackVRulerControls.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackVRulerControls.cpp @@ -58,13 +58,12 @@ void WaveTrackVRulerControls::DoZoomPreset( int i) const auto wt = static_cast(pTrack.get()); - // Don't pass the partner, that causes problems when updating display + // Don't do all channels, that causes problems when updating display // during recording and there are special pending tracks. // This function implements WaveTrack::DoSetMinimized which is always // called in a context that loops over linked tracks too and reinvokes. - auto partner = nullptr; WaveTrackVZoomHandle::DoZoom( - NULL, wt, partner, (i==1)?kZoomHalfWave: kZoom1to1, + NULL, wt, false, (i==1)?kZoomHalfWave: kZoom1to1, wxRect(0,0,0,0), 0,0, true); } @@ -88,9 +87,7 @@ unsigned WaveTrackVRulerControls::HandleWheelRotation wxASSERT(pTrack->GetKind() == Track::Wave); auto steps = evt.steps; - const auto wt = static_cast(pTrack.get()); - // Assume linked track is wave or null - const auto partner = static_cast(wt->GetLink()); + WaveTrack *const wt = static_cast(pTrack.get()); const bool isDB = wt->GetDisplay() == WaveTrack::Waveform && wt->GetWaveformSettings().scaleType == WaveformSettings::stLogarithmic; @@ -102,24 +99,20 @@ unsigned WaveTrackVRulerControls::HandleWheelRotation if (!(min < 0.0 && max > 0.0)) return RefreshNone; - WaveformSettings &settings = wt->GetIndependentWaveformSettings(); + WaveformSettings &settings = + wt->GetIndependentWaveformSettings(); float olddBRange = settings.dBRange; - if (steps < 0) - // Zoom out - settings.NextLowerDBRange(); - else - settings.NextHigherDBRange(); - float newdBRange = settings.dBRange; - - if (partner) { - WaveformSettings &settings = partner->GetIndependentWaveformSettings(); + for (auto channel : TrackList::Channels(wt)) { + WaveformSettings &channelSettings = + channel->GetIndependentWaveformSettings(); if (steps < 0) // Zoom out - settings.NextLowerDBRange(); + channelSettings.NextLowerDBRange(); else - settings.NextHigherDBRange(); + channelSettings.NextHigherDBRange(); } + float newdBRange = settings.dBRange; // Is y coordinate within the rectangle half-height centered about // the zero level? @@ -135,19 +128,16 @@ unsigned WaveTrackVRulerControls::HandleWheelRotation const float extreme = (LINEAR_TO_DB(2) + newdBRange) / newdBRange; max = std::min(extreme, max * olddBRange / newdBRange); min = std::max(-extreme, min * olddBRange / newdBRange); - wt->SetLastdBRange(); - wt->SetDisplayBounds(min, max); - if (partner) { - partner->SetLastdBRange(); - partner->SetDisplayBounds(min, max); + for (auto channel : TrackList::Channels(wt)) { + channel->SetLastdBRange(); + channel->SetDisplayBounds(min, max); } } } else if (event.CmdDown() && !event.ShiftDown()) { const int yy = event.m_y; - const auto partner = static_cast(wt); WaveTrackVZoomHandle::DoZoom( - pProject, wt, partner, (steps < 0)?kZoomOut:kZoomIn, + pProject, wt, true, (steps < 0)?kZoomOut:kZoomIn, evt.rect, yy, yy, true); } else if (!event.CmdDown() && event.ShiftDown()) { @@ -173,9 +163,8 @@ unsigned WaveTrackVRulerControls::HandleWheelRotation std::min(bound, numberScale.PositionToValue(numberScale.ValueToPosition(newBottom) + 1.0f)); - wt->SetSpectrumBounds(newBottom, newTop); - if (partner) - partner->SetSpectrumBounds(newBottom, newTop); + for (auto channel : TrackList::Channels(wt)) + channel->SetSpectrumBounds(newBottom, newTop); } else { float topLimit = 2.0; @@ -191,9 +180,8 @@ unsigned WaveTrackVRulerControls::HandleWheelRotation float newTop = std::min(topLimit, top + delta); const float newBottom = std::max(bottomLimit, newTop - range); newTop = std::min(topLimit, newBottom + range); - wt->SetDisplayBounds(newBottom, newTop); - if (partner) - partner->SetDisplayBounds(newBottom, newTop); + for (auto channel : TrackList::Channels(wt)) + channel->SetDisplayBounds(newBottom, newTop); } } else diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackVZoomHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackVZoomHandle.cpp index c4f2afd68..1305d4c00 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackVZoomHandle.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackVZoomHandle.cpp @@ -65,11 +65,12 @@ void WaveTrackVZoomHandle::Enter(bool) // the zoomKind and cause a drag-zoom-in. void WaveTrackVZoomHandle::DoZoom (AudacityProject *pProject, - WaveTrack *pTrack, WaveTrack *partner, int ZoomKind, + WaveTrack *pTrack, bool allChannels, int ZoomKind, const wxRect &rect, int zoomStart, int zoomEnd, bool fixedMousePoint) { static const float ZOOMLIMIT = 0.001f; + int height = rect.height; int ypos = rect.y; @@ -318,15 +319,13 @@ void WaveTrackVZoomHandle::DoZoom } // Now actually apply the zoom. - if (spectral) { - pTrack->SetSpectrumBounds(min, max); - if (partner) - partner->SetSpectrumBounds(min, max); - } - else { - pTrack->SetDisplayBounds(min, max); - if (partner) - partner->SetDisplayBounds(min, max); + for (auto channel : TrackList::Channels(pTrack)) { + if (!allChannels && channel != pTrack) + continue; + if (spectral) + channel->SetSpectrumBounds(min, max); + else + channel->SetDisplayBounds(min, max); } zoomEnd = zoomStart = 0; @@ -389,10 +388,8 @@ void WaveTrackVRulerMenuTable::InitMenu(Menu *, void *pUserData) void WaveTrackVRulerMenuTable::OnZoom( int iZoomCode ) { - // Assume linked track is wave or null - const auto partner = static_cast(mpData->pTrack->GetLink()); WaveTrackVZoomHandle::DoZoom - (::GetActiveProject(), mpData->pTrack, partner, + (::GetActiveProject(), mpData->pTrack, true, iZoomCode, mpData->rect, mpData->yy, mpData->yy, false); using namespace RefreshCode; @@ -467,17 +464,17 @@ void WaveformVRulerMenuTable::OnWaveformScaleType(wxCommandEvent &evt) { WaveTrack *const wt = mpData->pTrack; // Assume linked track is wave or null - const auto partner = static_cast(wt->GetLink()); const WaveformSettings::ScaleType newScaleType = WaveformSettings::ScaleType( std::max(0, std::min((int)(WaveformSettings::stNumScaleTypes) - 1, evt.GetId() - OnFirstWaveformScaleID ))); + if (wt->GetWaveformSettings().scaleType != newScaleType) { - wt->GetIndependentWaveformSettings().scaleType = newScaleType; - if (partner) - partner->GetIndependentWaveformSettings().scaleType = newScaleType; + for (auto channel : TrackList::Channels(wt)) { + channel->GetIndependentWaveformSettings().scaleType = newScaleType; + } ::GetActiveProject()->ModifyState(true); @@ -540,8 +537,7 @@ END_POPUP_MENU() void SpectrumVRulerMenuTable::OnSpectrumScaleType(wxCommandEvent &evt) { WaveTrack *const wt = mpData->pTrack; - // Assume linked track is wave or null - const auto partner = static_cast(wt->GetLink()); + const SpectrogramSettings::ScaleType newScaleType = SpectrogramSettings::ScaleType( std::max(0, @@ -549,9 +545,8 @@ void SpectrumVRulerMenuTable::OnSpectrumScaleType(wxCommandEvent &evt) evt.GetId() - OnFirstSpectrumScaleID ))); if (wt->GetSpectrogramSettings().scaleType != newScaleType) { - wt->GetIndependentSpectrogramSettings().scaleType = newScaleType; - if (partner) - partner->GetIndependentSpectrogramSettings().scaleType = newScaleType; + for (auto channel : TrackList::Channels(wt)) + channel->GetIndependentSpectrogramSettings().scaleType = newScaleType; ::GetActiveProject()->ModifyState(true); @@ -678,8 +673,7 @@ UIHandle::Result WaveTrackVZoomHandle::Release if( bVZoom ){ if( shiftDown ) mZoomStart=mZoomEnd; - const auto partner = static_cast(pTrack->GetLink()); - DoZoom(pProject, pTrack.get(), partner, + DoZoom(pProject, pTrack.get(), true, shiftDown ? (rightUp ? kZoom1to1 : kZoomOut) : kZoomIn, mRect, mZoomStart, mZoomEnd, !shiftDown); } diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackVZoomHandle.h b/src/tracks/playabletrack/wavetrack/ui/WaveTrackVZoomHandle.h index 500c124a2..56f5e2517 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackVZoomHandle.h +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackVZoomHandle.h @@ -42,7 +42,7 @@ public: static void DoZoom (AudacityProject *pProject, - WaveTrack *pTrack, WaveTrack *partner, int ZoomKind, + WaveTrack *pTrack, bool allChannels, int ZoomKind, const wxRect &rect, int zoomStart, int zoomEnd, bool fixedMousePoint); diff --git a/src/tracks/ui/EnvelopeHandle.cpp b/src/tracks/ui/EnvelopeHandle.cpp index abea440f3..809f4bb42 100644 --- a/src/tracks/ui/EnvelopeHandle.cpp +++ b/src/tracks/ui/EnvelopeHandle.cpp @@ -178,6 +178,8 @@ UIHandle::Result EnvelopeHandle::Click const ViewInfo &viewInfo = pProject->GetViewInfo(); const auto pTrack = static_cast(evt.pCell.get()); + mEnvelopeEditors.clear(); + unsigned result = Cancelled; if (pTrack) result = pTrack->TypeSwitch< decltype(RefreshNone) >( @@ -191,18 +193,21 @@ UIHandle::Result EnvelopeHandle::Click mLog = !wt->GetWaveformSettings().isLinear(); wt->GetDisplayBounds(&mLower, &mUpper); mdBRange = wt->GetWaveformSettings().dBRange; - mEnvelopeEditor = - std::make_unique< EnvelopeEditor >( *mEnvelope, true ); - mEnvelopeEditorRight.reset(); - - // Assume linked track is wave or null - auto partner = static_cast(wt->GetLink()); - if (partner) - { - auto clickedEnvelope = partner->GetEnvelopeAtX(event.GetX()); - if (clickedEnvelope) - mEnvelopeEditorRight = - std::make_unique< EnvelopeEditor >( *clickedEnvelope, true ); + auto channels = TrackList::Channels( wt ); + for ( auto channel : channels ) { + if (channel == wt) + mEnvelopeEditors.push_back( + std::make_unique< EnvelopeEditor >( *mEnvelope, true ) ); + else { + auto e2 = channel->GetEnvelopeAtX(event.GetX()); + if (e2) + mEnvelopeEditors.push_back( + std::make_unique< EnvelopeEditor >( *e2, true ) ); + else { + // There isn't necessarily an envelope there; no guarantee a + // linked track has the same WaveClip structure... + } + } } return RefreshNone; @@ -211,9 +216,9 @@ UIHandle::Result EnvelopeHandle::Click if (!mEnvelope) return Cancelled; GetTimeTrackData( *pProject, *tt, mdBRange, mLog, mLower, mUpper); - mEnvelopeEditor = - std::make_unique< EnvelopeEditor >( *mEnvelope, false ); - mEnvelopeEditorRight.reset(); + mEnvelopeEditors.push_back( + std::make_unique< EnvelopeEditor >( *mEnvelope, false ) + ); return RefreshNone; }, @@ -289,8 +294,7 @@ UIHandle::Result EnvelopeHandle::Release _("Envelope") ); - mEnvelopeEditor.reset(); - mEnvelopeEditorRight.reset(); + mEnvelopeEditors.clear(); using namespace RefreshCode; return needUpdate ? RefreshCell : RefreshNone; @@ -299,8 +303,7 @@ UIHandle::Result EnvelopeHandle::Release UIHandle::Result EnvelopeHandle::Cancel(AudacityProject *pProject) { pProject->RollbackState(); - mEnvelopeEditor.reset(); - mEnvelopeEditorRight.reset(); + mEnvelopeEditors.clear(); return RefreshCode::RefreshCell; } @@ -313,14 +316,13 @@ bool EnvelopeHandle::ForwardEventToEnvelopes // AS: I'm not sure why we can't let the Envelope take care of // redrawing itself. ? - bool needUpdate = - mEnvelopeEditor->MouseEvent( - event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper); - - if (mEnvelopeEditorRight) - needUpdate |= - mEnvelopeEditorRight->MouseEvent( - event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper); + bool needUpdate = false; + for (const auto &pEditor : mEnvelopeEditors) { + needUpdate = + pEditor->MouseEvent( + event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper) + || needUpdate; + } return needUpdate; } diff --git a/src/tracks/ui/EnvelopeHandle.h b/src/tracks/ui/EnvelopeHandle.h index 11787ecc7..75e3ce4a8 100644 --- a/src/tracks/ui/EnvelopeHandle.h +++ b/src/tracks/ui/EnvelopeHandle.h @@ -14,6 +14,8 @@ Paul Licameli split from TrackPanel.cpp #include "../../UIHandle.h" #include "../../MemoryX.h" +#include + class wxMouseEvent; class wxMouseState; #include @@ -85,8 +87,7 @@ private: double mdBRange{}; Envelope *mEnvelope{}; - std::unique_ptr mEnvelopeEditor; - std::unique_ptr mEnvelopeEditorRight; + std::vector< std::unique_ptr > mEnvelopeEditors; bool mTimeTrack{}; }; diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index cca2fe513..2d24ad2e2 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -152,6 +152,21 @@ namespace AddClipsToCaptured( state, t, t->GetStartTime(), t->GetEndTime() ); } + WaveTrack *NthChannel(WaveTrack &leader, int nn) + { + if (nn < 0) + return nullptr; + return *TrackList::Channels( &leader ).begin().advance(nn); + } + + int ChannelPosition(const Track *pChannel) + { + return static_cast( + TrackList::Channels( pChannel ) + .EndingAfter( pChannel ).size() + ) - 1; + } + // Don't count right channels. WaveTrack *NthAudioTrack(TrackList &list, int nn) { @@ -206,15 +221,10 @@ namespace clip.track->Offset( offset ); } } - else if ( pTrack ) { + else if ( pTrack ) // Was a shift-click - for (auto channel = - pTrack->GetLink() && !pTrack->GetLinked() - ? pTrack->GetLink() : pTrack; - channel; - channel = channel->GetLinked() ? channel->GetLink() : nullptr) + for (auto channel : TrackList::Channels( pTrack )) channel->Offset( offset ); - } } } @@ -236,16 +246,12 @@ void TimeShiftHandle::CreateListOfCapturedClips state.capturedClipArray.push_back (TrackClip( &capturedTrack, state.capturedClip )); - // Check for stereo partner - Track *partner = capturedTrack.GetLink(); - WaveTrack *wt; - if (state.capturedClip && - // Assume linked track is wave or null - nullptr != (wt = static_cast(partner))) { - WaveClip *const clip = FindClipAtTime(wt, clickTime); - - if (clip) - state.capturedClipArray.push_back(TrackClip(partner, clip)); + if (state.capturedClip) { + // Check for other channels + auto wt = static_cast(&capturedTrack); + for ( auto channel : TrackList::Channels( wt ).Excluding( wt ) ) + if (WaveClip *const clip = FindClipAtTime(channel, clickTime)) + state.capturedClipArray.push_back(TrackClip(channel, clip)); } } @@ -493,22 +499,28 @@ namespace { for ( auto &trackClip : state.capturedClipArray ) { if (trackClip.clip) { // Move all clips up or down by an equal count of audio tracks. + // Can only move between tracks with equal numbers of channels, + // and among corresponding channels. + Track *const pSrcTrack = trackClip.track; auto pDstTrack = NthAudioTrack(trackList, diff + TrackPosition(trackList, pSrcTrack)); - // Can only move mono to mono, or left to left, or right to right - // And that must be so for each captured clip - bool stereo = (pSrcTrack->GetLink() != 0); - if (pDstTrack && stereo && !pSrcTrack->GetLinked()) - // Assume linked track is wave or null - pDstTrack = static_cast(pDstTrack->GetLink()); - bool ok = pDstTrack && - (stereo == (pDstTrack->GetLink() != 0)) && - (!stereo || (pSrcTrack->GetLinked() == pDstTrack->GetLinked())); - if (ok) - trackClip.dstTrack = pDstTrack; - else + if (!pDstTrack) return false; + + if (TrackList::Channels(pSrcTrack).size() != + TrackList::Channels(pDstTrack).size()) + return false; + + auto pDstChannel = NthChannel( + *pDstTrack, ChannelPosition(pSrcTrack)); + + if (!pDstChannel) { + wxASSERT(false); + return false; + } + + trackClip.dstTrack = pDstChannel; } } return true; diff --git a/src/tracks/ui/TrackButtonHandles.cpp b/src/tracks/ui/TrackButtonHandles.cpp index caa8265e2..b8fc577e0 100644 --- a/src/tracks/ui/TrackButtonHandles.cpp +++ b/src/tracks/ui/TrackButtonHandles.cpp @@ -34,9 +34,9 @@ UIHandle::Result MinimizeButtonHandle::CommitChanges auto pTrack = mpTrack.lock(); if (pTrack) { - pTrack->SetMinimized(!pTrack->GetMinimized()); - if (pTrack->GetLink()) - pTrack->GetLink()->SetMinimized(pTrack->GetMinimized()); + bool wasMinimized = pTrack->GetMinimized(); + for (auto channel : TrackList::Channels(pTrack.get())) + channel->SetMinimized(!wasMinimized); pProject->ModifyState(true); // Redraw all tracks when any one of them expands or contracts diff --git a/src/tracks/ui/TrackControls.cpp b/src/tracks/ui/TrackControls.cpp index 27d72802b..bdf2fe855 100644 --- a/src/tracks/ui/TrackControls.cpp +++ b/src/tracks/ui/TrackControls.cpp @@ -209,11 +209,8 @@ void TrackMenuTable::OnSetName(wxCommandEvent &) if (bResult) { wxString newName = Command.mName; - pTrack->SetName(newName); - // if we have a linked channel this name should change as well - // (otherwise sort by name and time will crash). - if (pTrack->GetLinked()) - pTrack->GetLink()->SetName(newName); + for (auto channel : TrackList::Channels(pTrack)) + channel->SetName(newName); MixerBoard *const pMixerBoard = proj->GetMixerBoard(); auto pt = dynamic_cast(pTrack);