1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-04-29 23:29:41 +02:00

Add LinkType to Track

LinkType replaces old boolean 'linked', mostly for WaveTrack display purposes
Track::GroupChannels splitted into Track::UnlinkChannels and Track::MakeMultiChannelTrack (which is also aware of new LinkType)
LinkConsistensyCheck made virtual to allow Track subclasses to perform type-specific consistensy checks.
Introduces some corner-cases with copy-pasting/old project importing/file importing..., which are handled

(cherry picked from audacity commit 607961da70968c179b60a811ce084b55a94438ec)

Signed-off-by: akleja <storspov@gmail.com>
This commit is contained in:
Vitaly Sverchinsky 2021-08-19 20:10:43 +03:00 committed by akleja
parent 39d49be5b4
commit f969701756
12 changed files with 186 additions and 100 deletions

View File

@ -722,7 +722,7 @@ bool ProjectAudioManager::DoRecord(AudacityProject &project,
transportTracks.captureTracks.push_back(newTrack);
}
TrackList::Get( *p ).GroupChannels(*first, recordingChannels);
TrackList::Get( *p ).MakeMultiChannelTrack(*first, recordingChannels, true);
// Bug 1548. First of new tracks needs the focus.
TrackFocus::Get(*p).Set(first);
if (TrackList::Get(*p).back())

View File

@ -1113,7 +1113,7 @@ ProjectFileManager::AddImportedTracks(const FilePath &fileName,
auto newTrack = tracks.Add( uNewTrack );
results.push_back(newTrack->SharedPointer());
}
tracks.GroupChannels(*first, nChannels);
tracks.MakeMultiChannelTrack(*first, nChannels, true);
}
newTracks.clear();

View File

@ -50,7 +50,6 @@ Track::Track()
: vrulerSize(36,0)
{
mSelected = false;
mLinked = false;
mIndex = 0;
@ -76,7 +75,7 @@ void Track::Init(const Track &orig)
mName = orig.mName;
mSelected = orig.mSelected;
mLinked = orig.mLinked;
mLinkType = orig.mLinkType;
mChannel = orig.mChannel;
}
@ -172,18 +171,18 @@ void Track::SetIndex(int index)
mIndex = index;
}
void Track::SetLinked(bool l)
void Track::SetLinkType(LinkType linkType)
{
auto pList = mList.lock();
if (pList && !pList->mPendingUpdates.empty()) {
auto orig = pList->FindById( GetId() );
if (orig && orig != this) {
orig->SetLinked(l);
orig->SetLinkType(linkType);
return;
}
}
DoSetLinked(l);
DoSetLinkType(linkType);
if (pList) {
pList->RecalcPositions(mNode);
@ -191,9 +190,14 @@ void Track::SetLinked(bool l)
}
}
void Track::DoSetLinked(bool l)
void Track::DoSetLinkType(LinkType linkType) noexcept
{
mLinked = l;
mLinkType = linkType;
}
void Track::SetChannel(ChannelType c) noexcept
{
mChannel = c;
}
Track *Track::GetLinkedTrack() const
@ -224,7 +228,7 @@ Track *Track::GetLinkedTrack() const
bool Track::HasLinkedTrack() const noexcept
{
return mLinked;
return mLinkType != LinkType::None;
}
namespace {
@ -385,7 +389,7 @@ void Track::FinishCopy
{
if (dest) {
dest->SetChannel(n->GetChannel());
dest->SetLinked(n->HasLinkedTrack());
dest->SetLinkType(n->GetLinkType());
dest->SetName(n->GetName());
}
}
@ -408,7 +412,7 @@ bool Track::LinkConsistencyCheck()
wxT("Left track %s had linked right track %s with extra right track link.\n Removing extra link from right track."),
GetName(), link->GetName());
err = true;
link->SetLinked(false);
link->SetLinkType(LinkType::None);
}
// Channels should be left and right
@ -421,7 +425,7 @@ bool Track::LinkConsistencyCheck()
wxT("Track %s and %s had left/right track links out of order. Setting tracks to not be linked."),
GetName(), link->GetName());
err = true;
SetLinked(false);
SetLinkType(LinkType::None);
}
}
else
@ -430,7 +434,7 @@ bool Track::LinkConsistencyCheck()
wxT("Track %s had link to NULL track. Setting it to not be linked."),
GetName());
err = true;
SetLinked(false);
SetLinkType(LinkType::None);
}
}
@ -711,57 +715,6 @@ Track *TrackList::DoAdd(const std::shared_ptr<Track> &t)
return back().get();
}
void TrackList::GroupChannels(
Track &track, size_t groupSize, bool resetChannels )
{
// If group size is exactly two, group as stereo, else mono (bug 2195).
auto list = track.mList.lock();
if ( groupSize > 0 && list.get() == this ) {
auto iter = track.mNode.first;
auto after = iter;
auto end = this->ListOfTracks::end();
auto count = groupSize;
for ( ; after != end && count; ++after, --count )
;
if ( count == 0 ) {
auto unlink = [&] ( Track &tr ) {
if ( tr.HasLinkedTrack() ) {
if ( resetChannels ) {
auto link = tr.GetLinkedTrack();
if ( link )
link->SetChannel( Track::MonoChannel );
}
tr.SetLinked( false );
}
if ( resetChannels )
tr.SetChannel( Track::MonoChannel );
};
// Disassociate previous tracks -- at most one
auto pLeader = this->FindLeader( &track );
if ( *pLeader && *pLeader != &track )
unlink( **pLeader );
// First disassociate given and later tracks, then reassociate them
for ( auto iter2 = iter; iter2 != after; ++iter2 )
unlink( **iter2 );
if ( groupSize > 1 ) {
const auto channel = *iter++;
channel->SetLinked( groupSize == 2 );
channel->SetChannel( groupSize == 2? Track::LeftChannel : Track::MonoChannel );
(*iter++)->SetChannel( groupSize == 2? Track::RightChannel : Track::MonoChannel );
while (iter != after)
(*iter++)->SetChannel( Track::MonoChannel );
}
return;
}
}
// *this does not contain the track or sufficient following channels
// or group size is zero
THROW_INCONSISTENCY_EXCEPTION;
}
auto TrackList::Replace(Track * t, const ListOfTracks::value_type &with) ->
ListOfTracks::value_type
{
@ -784,6 +737,58 @@ auto TrackList::Replace(Track * t, const ListOfTracks::value_type &with) ->
return holder;
}
void TrackList::UnlinkChannels(Track& track)
{
auto list = track.mList.lock();
if (list.get() == this)
{
auto channels = TrackList::Channels(&track);
for (auto c : channels)
{
c->SetLinkType(Track::LinkType::None);
c->SetChannel(Track::ChannelType::MonoChannel);
}
}
else
THROW_INCONSISTENCY_EXCEPTION;
}
bool TrackList::MakeMultiChannelTrack(Track& track, int nChannels, bool aligned)
{
if (nChannels != 2)
return false;
auto list = track.mList.lock();
if (list.get() == this)
{
if (*list->FindLeader(&track) != &track)
return false;
auto first = list->Find(&track);
auto canLink = [&]() -> bool {
int count = nChannels;
for (auto it = first, end = TrackList::end(); it != end && count; ++it)
{
if ((*it)->HasLinkedTrack())
return false;
--count;
}
return count == 0;
}();
if (!canLink)
return false;
(*first)->SetLinkType(aligned ? Track::LinkType::Aligned : Track::LinkType::Group);
(*first)->SetChannel(Track::LeftChannel);
auto second = std::next(first);
(*second)->SetChannel(Track::RightChannel);
}
else
THROW_INCONSISTENCY_EXCEPTION;
return true;
}
TrackNodePointer TrackList::Remove(Track *t)
{
auto result = getEnd();
@ -1075,7 +1080,7 @@ void TrackList::UpdatePendingTracks()
if (pendingTrack && src) {
if (updater)
updater( *pendingTrack, *src );
pendingTrack->DoSetLinked(src->HasLinkedTrack());
pendingTrack->DoSetLinkType(src->GetLinkType());
}
++pUpdater;
}
@ -1303,3 +1308,8 @@ bool TrackList::HasPendingTracks() const
return true;
return false;
}
Track::LinkType Track::GetLinkType() const noexcept
{
return mLinkType;
}

View File

@ -236,10 +236,22 @@ class TENACITY_DLL_API Track /* not final */
, public AttachedTrackObjects
, public std::enable_shared_from_this<Track> // see SharedPointer()
{
public:
//! For two tracks describes the type of the linkage
enum class LinkType : int {
None = 0, //< No linkage
Group = 2, //< Tracks are grouped together
Aligned, //< Tracks are grouped and changes should be synchronized
};
private:
friend class TrackList;
private:
TrackId mId; //!< Identifies the track only in-session, not persistently
LinkType mLinkType{ LinkType::None };
protected:
std::weak_ptr<TrackList> mList; //!< Back pointer to owning TrackList
@ -253,9 +265,6 @@ class TENACITY_DLL_API Track /* not final */
private:
bool mSelected;
protected:
bool mLinked;
public:
//! Alias for my base type
@ -360,25 +369,25 @@ public:
static void FinishCopy (const Track *n, Track *dest);
// For use when loading a file. Return true if ok, else make repair
bool LinkConsistencyCheck();
virtual bool LinkConsistencyCheck();
bool HasOwner() const { return static_cast<bool>(GetOwner());}
std::shared_ptr<TrackList> GetOwner() const { return mList.lock(); }
LinkType GetLinkType() const noexcept;
protected:
void SetLinkType(LinkType linkType);
void DoSetLinkType(LinkType linkType) noexcept;
void SetChannel(ChannelType c) noexcept;
private:
Track* GetLinkedTrack() const;
//! Returns true for leaders of multichannel groups
bool HasLinkedTrack() const noexcept;
friend WaveTrack; // WaveTrack needs to call SetLinked when reloading project
void SetLinked (bool l);
void SetChannel(ChannelType c) { mChannel = c; }
private:
// No need yet to make this virtual
void DoSetLinked(bool l);
//! Retrieve mNode with debug checks
TrackNodePointer GetNode() const;
@ -1487,16 +1496,18 @@ public:
template<typename TrackKind>
TrackKind *Add( const std::shared_ptr< TrackKind > &t )
{ return static_cast< TrackKind* >( DoAdd( t ) ); }
/** \brief Define a group of channels starting at the given track
*
* @param track and (groupSize - 1) following tracks must be in this
* list. They will be disassociated from any groups they already belong to.
* @param groupSize must be at least 1.
* @param resetChannels if true, disassociated channels will be marked Mono.
//! Removes linkage if track belongs to a group
void UnlinkChannels(Track& track);
/** \brief Converts channels to a multichannel track.
* @param first and the following must be in this list. Tracks should
* not be a part of another group (not linked)
* @param nChannels number of channels, for now only 2 channels supported
* @param aligned if true, the link type will be set to Track::LinkType::Aligned,
* or Track::LinkType::Group otherwise
* @returns true on success, false if some prerequisites do not met
*/
void GroupChannels(
Track &track, size_t groupSize, bool resetChannels = true );
bool MakeMultiChannelTrack(Track& first, int nChannels, bool aligned);
/// Replace first track with second track, give back a holder
/// Give the replacement the same id as the replaced

View File

@ -35,6 +35,7 @@ from the project that will own the track.
#include <wx/defs.h>
#include <wx/intl.h>
#include <wx/debug.h>
#include <wx/log.h>
#include <float.h>
#include <math.h>
@ -64,6 +65,33 @@ from the project that will own the track.
using std::max;
namespace {
bool AreAligned(const WaveClipPointers& a, const WaveClipPointers& b)
{
if (a.size() != b.size())
return false;
const auto compare = [](const WaveClip* a, const WaveClip* b) {
return a->GetStartTime() == b->GetStartTime() &&
a->GetNumSamples() == b->GetNumSamples();
};
return std::mismatch(a.begin(), a.end(), b.begin(), compare).first == a.end();
}
//Handles possible future file values
Track::LinkType ToLinkType(int value)
{
if (value < 0)
return Track::LinkType::None;
else if (value > 3)
return Track::LinkType::Group;
return static_cast<Track::LinkType>(value);
}
}
static ProjectFileIORegistry::Entry registerFactory{
wxT( "wavetrack" ),
[]( AudacityProject &project ){
@ -240,7 +268,39 @@ void WaveTrack::SetPanFromChannelType()
SetPan( -1.0f );
else if( mChannel == Track::RightChannel )
SetPan( 1.0f );
};
}
bool WaveTrack::LinkConsistencyCheck()
{
auto err = PlayableTrack::LinkConsistencyCheck();
auto linkType = GetLinkType();
if (static_cast<int>(linkType) == 1 || //Comes from old audacity version
linkType == LinkType::Aligned)
{
auto next = dynamic_cast<WaveTrack*>(*std::next(GetOwner()->Find(this)));
if (next == nullptr)
{
//next track is not a wave track, fix and report error
wxLogWarning(
wxT("Right track %s is expected to be a WaveTrack.\n Removing link from left wave track %s."),
next->GetName(), GetName());
SetLinkType(LinkType::None);
SetChannel(MonoChannel);
err = true;
}
else
{
auto newLinkType = AreAligned(SortedClipArray(), next->SortedClipArray())
? LinkType::Aligned : LinkType::Group;
//not an error
if (newLinkType != linkType)
SetLinkType(newLinkType);
}
}
return !err;
}
void WaveTrack::SetLastScaleType() const
{
@ -1667,7 +1727,7 @@ bool WaveTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
}
else if (!wxStrcmp(attr, wxT("linked")) &&
XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
SetLinked(nValue != 0);
SetLinkType(ToLinkType(nValue));
else if (!wxStrcmp(attr, wxT("colorindex")) &&
XMLValueChecker::IsGoodString(strValue) &&
strValue.ToLong(&nValue))
@ -1734,7 +1794,7 @@ void WaveTrack::WriteXML(XMLWriter &xmlFile) const
xmlFile.StartTag(wxT("wavetrack"));
this->Track::WriteCommonXMLAttributes( xmlFile );
xmlFile.WriteAttr(wxT("channel"), mChannel);
xmlFile.WriteAttr(wxT("linked"), mLinked);
xmlFile.WriteAttr(wxT("linked"), static_cast<int>(GetLinkType()));
this->PlayableTrack::WriteXMLAttributes(xmlFile);
xmlFile.WriteAttr(wxT("rate"), mRate);
xmlFile.WriteAttr(wxT("gain"), (double)mGain);

View File

@ -100,6 +100,8 @@ private:
ChannelType GetChannel() const override;
virtual void SetPanFromChannelType() override;
bool LinkConsistencyCheck() override;
/** @brief Get the time at which the first clip in the track starts
*
* @return time in seconds, or zero if there are no clips in the track

View File

@ -2395,8 +2395,8 @@ void Effect::Preview(bool dryOnly)
mixRight->Offset(-mixRight->GetStartTime());
mixRight->SetSelected(true);
pRight = mTracks->Add( mixRight );
mTracks->MakeMultiChannelTrack(*pLeft, 2, true);
}
mTracks->GroupChannels(*pLeft, pRight ? 2 : 1);
}
else {
for (auto src : saveTracks->Any< const WaveTrack >()) {

View File

@ -213,7 +213,7 @@ bool EffectStereoToMono::ProcessOne(sampleCount & curTime, sampleCount totalTime
double minStart = wxMin(left->GetStartTime(), right->GetStartTime());
left->Clear(left->GetStartTime(), left->GetEndTime());
left->Paste(minStart, outTrack.get());
mOutputTracks->GroupChannels(*left, 1);
mOutputTracks->UnlinkChannels(*left);
mOutputTracks->Remove(right);
return bResult;

View File

@ -607,6 +607,7 @@ void OnPaste(const CommandContext &context)
if (ff) {
TrackFocus::Get(project).Set(ff);
ff->EnsureVisible();
ff->LinkConsistencyCheck();
}
}
}

View File

@ -87,10 +87,10 @@ void DoMixAndRender
auto pNewLeft = tracks.Add( uNewLeft );
decltype(pNewLeft) pNewRight{};
if (uNewRight)
pNewRight = tracks.Add( uNewRight );
// Do this only after adding tracks to the list
tracks.GroupChannels(*pNewLeft, pNewRight ? 2 : 1);
{
pNewRight = tracks.Add(uNewRight);
tracks.MakeMultiChannelTrack(*pNewLeft, 2, true);
}
// If we're just rendering (not mixing), keep the track name the same
if (selectedCount==1) {
@ -644,7 +644,7 @@ void OnNewStereoTrack(const CommandContext &context)
auto right = tracks.Add( trackFactory.NewWaveTrack( defaultFormat, rate ) );
right->SetSelected(true);
tracks.GroupChannels(*left, 2);
tracks.MakeMultiChannelTrack(*left, 2, true);
ProjectHistory::Get( project )
.PushState(XO("Created new stereo audio track"), XO("New Track"));

View File

@ -828,7 +828,7 @@ void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
((TrackView::Get( *pTrack ).GetMinimized()) &&
(TrackView::Get( *partner ).GetMinimized()));
tracks.GroupChannels( *pTrack, 2 );
tracks.MakeMultiChannelTrack( *pTrack, 2, false );
// Set partner's parameters to match target.
partner->Merge(*pTrack);
@ -884,7 +884,7 @@ void WaveTrackMenuTable::SplitStereo(bool stereo)
++nChannels;
}
TrackList::Get( *project ).GroupChannels( *pTrack, 1 );
TrackList::Get( *project ).UnlinkChannels( *pTrack );
int averageHeight = totalHeight / nChannels;
for (auto channel : channels)
@ -898,6 +898,7 @@ void WaveTrackMenuTable::OnSwapChannels(wxCommandEvent &)
AudacityProject *const project = &mpData->project;
WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
const auto linkType = pTrack->GetLinkType();
auto channels = TrackList::Channels( pTrack );
if (channels.size() != 2)
return;
@ -911,9 +912,8 @@ void WaveTrackMenuTable::OnSwapChannels(wxCommandEvent &)
SplitStereo(false);
auto &tracks = TrackList::Get( *project );
tracks.MoveUp( partner );
tracks.GroupChannels( *partner, 2 );
tracks.MoveUp(partner);
tracks.MakeMultiChannelTrack(*partner, 2, linkType == Track::LinkType::Aligned);
if (hasFocus)
trackFocus.Set(partner);

View File

@ -961,6 +961,8 @@ UIHandle::Result TimeShiftHandle::Release
if (mDidSlideVertically) {
msg = XO("Moved clips to another track");
consolidate = false;
for (auto& pair : mClipMoveState.shifters)
pair.first->LinkConsistencyCheck();
}
else {
msg = ( mClipMoveState.hSlideAmount > 0