1254 lines
24 KiB
C++
1254 lines
24 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
Track.cpp
|
|
|
|
Dominic Mazzoni
|
|
|
|
*******************************************************************//**
|
|
|
|
\class Track
|
|
\brief Fundamental data object of Audacity, placed in the TrackPanel.
|
|
Classes derived form it include the WaveTrack, NoteTrack, LabelTrack
|
|
and TimeTrack.
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include <float.h>
|
|
#include <wx/file.h>
|
|
#include <wx/textfile.h>
|
|
#include <wx/log.h>
|
|
|
|
#include "Track.h"
|
|
#include "WaveTrack.h"
|
|
#include "NoteTrack.h"
|
|
#include "LabelTrack.h"
|
|
#include "Project.h"
|
|
#include "TimeTrack.h"
|
|
#include "DirManager.h"
|
|
|
|
#ifdef _MSC_VER
|
|
//Disable truncation warnings
|
|
#pragma warning( disable : 4786 )
|
|
#endif
|
|
|
|
#ifdef __WXDEBUG__
|
|
// if we are in a debug build of audacity
|
|
/// Define this to do extended (slow) debuging of TrackListIterator
|
|
// #define DEBUG_TLI
|
|
#endif
|
|
|
|
Track::Track(DirManager * projDirManager)
|
|
: vrulerSize(36,0),
|
|
mDirManager(projDirManager)
|
|
{
|
|
mDirManager->Ref();
|
|
|
|
mList = NULL;
|
|
mNode = NULL;
|
|
mSelected = false;
|
|
mLinked = false;
|
|
mMute = false;
|
|
mSolo = false;
|
|
|
|
mY = 0;
|
|
mHeight = 150;
|
|
mIndex = 0;
|
|
|
|
mMinimized = false;
|
|
|
|
mOffset = 0.0;
|
|
|
|
mChannel = MonoChannel;
|
|
}
|
|
|
|
Track::Track(const Track &orig)
|
|
{
|
|
mDirManager = NULL;
|
|
|
|
mList = NULL;
|
|
mNode = NULL;
|
|
mY = 0;
|
|
mIndex = 0;
|
|
|
|
Init(orig);
|
|
mOffset = orig.mOffset;
|
|
}
|
|
|
|
// Copy all the track properties except the actual contents
|
|
void Track::Init(const Track &orig)
|
|
{
|
|
mDefaultName = orig.mDefaultName;
|
|
mName = orig.mName;
|
|
|
|
if (mDirManager != orig.mDirManager) {
|
|
if (mDirManager) {
|
|
mDirManager->Deref(); // MM: unreference old DirManager
|
|
}
|
|
|
|
// MM: Assign and ref new DirManager
|
|
mDirManager = orig.mDirManager;
|
|
mDirManager->Ref();
|
|
}
|
|
|
|
mSelected = orig.mSelected;
|
|
mLinked = orig.mLinked;
|
|
mMute = orig.mMute;
|
|
mSolo = orig.mSolo;
|
|
mHeight = orig.mHeight;
|
|
mMinimized = orig.mMinimized;
|
|
mChannel = orig.mChannel;
|
|
}
|
|
|
|
void Track::Merge(const Track &orig)
|
|
{
|
|
mSelected = orig.mSelected;
|
|
mMute = orig.mMute;
|
|
mSolo = orig.mSolo;
|
|
}
|
|
|
|
Track::~Track()
|
|
{
|
|
mDirManager->Deref();
|
|
}
|
|
|
|
|
|
const TrackListNode *Track::GetNode()
|
|
{
|
|
return mNode;
|
|
}
|
|
|
|
// A track can only live on one list at a time, so if you're moving a
|
|
// track from one list to another, you must call SetOwner() with NULL
|
|
// pointers first and then with the real pointers.
|
|
void Track::SetOwner(TrackList *list, TrackListNode *node)
|
|
{
|
|
// Try to detect offenders while in development.
|
|
wxASSERT(list == NULL || mList == NULL);
|
|
wxASSERT(node == NULL || mNode == NULL);
|
|
wxASSERT((list != NULL && node != NULL) || (list == NULL && node == NULL));
|
|
|
|
mList = list;
|
|
mNode = node;
|
|
}
|
|
|
|
int Track::GetMinimizedHeight() const
|
|
{
|
|
if (GetLink()) {
|
|
return 20;
|
|
}
|
|
|
|
return 40;
|
|
}
|
|
|
|
int Track::GetIndex() const
|
|
{
|
|
return mIndex;
|
|
}
|
|
|
|
void Track::SetIndex(int index)
|
|
{
|
|
mIndex = index;
|
|
}
|
|
|
|
int Track::GetY() const
|
|
{
|
|
return mY;
|
|
}
|
|
|
|
void Track::SetY(int y)
|
|
{
|
|
mY = y;
|
|
}
|
|
|
|
int Track::GetHeight() const
|
|
{
|
|
if (mMinimized) {
|
|
return GetMinimizedHeight();
|
|
}
|
|
|
|
return mHeight;
|
|
}
|
|
|
|
void Track::SetHeight(int h)
|
|
{
|
|
mHeight = h;
|
|
mList->RecalcPositions(mNode);
|
|
mList->ResizedEvent(mNode);
|
|
}
|
|
|
|
bool Track::GetMinimized() const
|
|
{
|
|
return mMinimized;
|
|
}
|
|
|
|
void Track::SetMinimized(bool isMinimized)
|
|
{
|
|
mMinimized = isMinimized;
|
|
mList->RecalcPositions(mNode);
|
|
mList->ResizedEvent(mNode);
|
|
}
|
|
|
|
Track *Track::GetLink() const
|
|
{
|
|
if (mNode) {
|
|
if (mNode->next && mLinked) {
|
|
return mNode->next->t;
|
|
}
|
|
|
|
if (mNode->prev && mNode->prev->t->GetLinked()) {
|
|
return mNode->prev->t;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool Track::IsSyncLockSelected()
|
|
{
|
|
#ifdef EXPERIMENTAL_SYNC_LOCK
|
|
AudacityProject *p = GetActiveProject();
|
|
if (!p || !p->IsSyncLocked())
|
|
return false;
|
|
|
|
SyncLockedTracksIterator git(mList);
|
|
Track *t = git.First(this);
|
|
|
|
if (!t) {
|
|
// Not in a sync-locked group.
|
|
return ((this->GetKind() == Track::Wave) || (this->GetKind() == Track::Label)) && GetSelected();
|
|
}
|
|
|
|
for (; t; t = git.Next()) {
|
|
if (t->GetSelected())
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Track::SyncLockAdjust(double oldT1, double newT1)
|
|
{
|
|
if (newT1 > oldT1) {
|
|
// Insert space within the track
|
|
|
|
if (oldT1 > GetEndTime())
|
|
return true;
|
|
|
|
Track *tmp;
|
|
bool ret;
|
|
|
|
ret = Cut(oldT1, GetEndTime(), &tmp);
|
|
if (!ret) return false;
|
|
|
|
ret = Paste(newT1, tmp);
|
|
|
|
delete tmp;
|
|
return ret;
|
|
}
|
|
else if (newT1 < oldT1) {
|
|
// Remove from the track
|
|
return Clear(newT1, oldT1);
|
|
}
|
|
|
|
// fall-through: no change
|
|
return true;
|
|
}
|
|
|
|
// TrackListIterator
|
|
TrackListIterator::TrackListIterator(TrackList * val)
|
|
{
|
|
l = val;
|
|
cur = NULL;
|
|
}
|
|
|
|
Track *TrackListIterator::StartWith(Track * val)
|
|
{
|
|
if (val == NULL) {
|
|
return First();
|
|
}
|
|
|
|
if (l == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
cur = (TrackListNode *) val->GetNode();
|
|
|
|
if (cur) {
|
|
return cur->t;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Track *TrackListIterator::First(TrackList * val)
|
|
{
|
|
if (val != NULL) {
|
|
l = val;
|
|
}
|
|
|
|
if (l == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
cur = l->head;
|
|
|
|
if (cur) {
|
|
return cur->t;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Track *TrackListIterator::Last(bool skiplinked)
|
|
{
|
|
if (l == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
cur = l->tail;
|
|
|
|
// With skiplinked set, we won't return the second channel of a linked pair
|
|
if (skiplinked && cur && cur->t && cur->t->GetLink() && !cur->t->GetLinked())
|
|
{
|
|
cur = cur->prev;
|
|
}
|
|
|
|
if (cur) {
|
|
return cur->t;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Track *TrackListIterator::Next(bool skipLinked)
|
|
{
|
|
#ifdef DEBUG_TLI // if we are debugging this bit
|
|
wxASSERT_MSG((!cur || (*l).Contains((*cur).t)), wxT("cur invalid at start of Next(). List changed since iterator created?")); // check that cur is in the list
|
|
#endif
|
|
|
|
if (skipLinked && cur && cur->t->GetLinked()) {
|
|
cur = cur->next;
|
|
}
|
|
|
|
#ifdef DEBUG_TLI // if we are debugging this bit
|
|
wxASSERT_MSG((!cur || (*l).Contains((*cur).t)), wxT("cur invalid after skipping linked tracks.")); // check that cur is in the list
|
|
#endif
|
|
|
|
if (cur) {
|
|
cur = cur->next;
|
|
}
|
|
|
|
#ifdef DEBUG_TLI // if we are debugging this bit
|
|
wxASSERT_MSG((!cur || (*l).Contains((*cur).t)), wxT("cur invalid after moving to next track.")); // check that cur is in the list if it is not null
|
|
#endif
|
|
|
|
if (cur) {
|
|
return cur->t;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Track *TrackListIterator::Prev(bool skiplinked)
|
|
{
|
|
if (cur)
|
|
cur = cur->prev;
|
|
|
|
if (skiplinked && cur && cur->prev && cur->prev->t->GetLinked())
|
|
cur = cur->prev;
|
|
|
|
return cur ? cur->t : NULL;
|
|
}
|
|
|
|
Track *TrackListIterator::RemoveCurrent(bool deletetrack)
|
|
{
|
|
TrackListNode *next = cur->next;
|
|
|
|
l->Remove(cur->t, deletetrack);
|
|
|
|
cur = next;
|
|
|
|
#ifdef DEBUG_TLI // if we are debugging this bit
|
|
wxASSERT_MSG((!cur || (*l).Contains((*cur).t)), wxT("cur invalid after deletion of track.")); // check that cur is in the list
|
|
#endif
|
|
|
|
if (cur) {
|
|
return cur->t;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Track *TrackListIterator::ReplaceCurrent(Track *t)
|
|
{
|
|
Track *p = NULL;
|
|
|
|
if (cur) {
|
|
p = cur->t;
|
|
p->SetOwner(NULL, NULL);
|
|
|
|
cur->t = t;
|
|
t->SetOwner(l, cur);
|
|
l->RecalcPositions(cur);
|
|
l->UpdatedEvent(cur);
|
|
l->ResizedEvent(cur);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
//
|
|
// TrackListCondIterator (base class for iterators that iterate over all tracks
|
|
// that meet a condition)
|
|
//
|
|
|
|
Track *TrackListCondIterator::StartWith(Track *val)
|
|
{
|
|
Track *t = TrackListIterator::StartWith(val);
|
|
|
|
if (t && !this->Condition(t))
|
|
return NULL;
|
|
|
|
return t;
|
|
}
|
|
|
|
Track *TrackListCondIterator::First(TrackList *val)
|
|
{
|
|
Track *t = TrackListIterator::First(val);
|
|
|
|
while (t && !this->Condition(t)) {
|
|
t = TrackListIterator::Next();
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
Track *TrackListCondIterator::Next(bool skiplinked)
|
|
{
|
|
while (Track *t = TrackListIterator::Next(skiplinked)) {
|
|
if (this->Condition(t)) {
|
|
return t;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Track *TrackListCondIterator::Prev(bool skiplinked)
|
|
{
|
|
while (Track *t = TrackListIterator::Prev(skiplinked))
|
|
{
|
|
if (this->Condition(t)) {
|
|
return t;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Track *TrackListCondIterator::Last(bool skiplinked)
|
|
{
|
|
Track *t = TrackListIterator::Last(skiplinked);
|
|
|
|
while (t && !this->Condition(t)) {
|
|
t = TrackListIterator::Prev(skiplinked);
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
// TrackListOfKindIterator
|
|
TrackListOfKindIterator::TrackListOfKindIterator(int kind, TrackList * val)
|
|
: TrackListCondIterator(val)
|
|
{
|
|
this->kind = kind;
|
|
}
|
|
|
|
bool TrackListOfKindIterator::Condition(Track *t)
|
|
{
|
|
return kind == Track::All || t->GetKind() == kind;
|
|
}
|
|
|
|
//SelectedTrackListOfKindIterator
|
|
bool SelectedTrackListOfKindIterator::Condition(Track *t)
|
|
{
|
|
return TrackListOfKindIterator::Condition(t) && t->GetSelected();
|
|
}
|
|
|
|
// VisibleTrackIterator
|
|
//
|
|
// Based on TrackListIterator returns only the currently visible tracks.
|
|
//
|
|
VisibleTrackIterator::VisibleTrackIterator(AudacityProject *project)
|
|
: TrackListCondIterator(project->GetTracks())
|
|
{
|
|
mProject = project;
|
|
mPanelRect.SetTop(mProject->mViewInfo.vpos);
|
|
mPanelRect.SetSize(mProject->GetTPTracksUsableArea());
|
|
}
|
|
|
|
bool VisibleTrackIterator::Condition(Track *t)
|
|
{
|
|
wxRect r(0, t->GetY(), 1, t->GetHeight());
|
|
return r.Intersects(mPanelRect);
|
|
}
|
|
|
|
// SyncLockedTracksIterator
|
|
//
|
|
// Based on TrackListIterator returns only tracks belonging to the group
|
|
// in which the starting track is a member.
|
|
//
|
|
SyncLockedTracksIterator::SyncLockedTracksIterator(TrackList * val)
|
|
: TrackListIterator(val),
|
|
mInLabelSection(false)
|
|
{
|
|
}
|
|
|
|
Track *SyncLockedTracksIterator::First(Track * member)
|
|
{
|
|
Track *t = NULL;
|
|
|
|
// A sync-locked group consists of any positive number of wave tracks followed by any
|
|
// non-negative number of label tracks. Step back through any label tracks,
|
|
// and then through the wave tracks above them.
|
|
|
|
while (member && member->GetKind() == Track::Label) {
|
|
member = l->GetPrev(member);
|
|
}
|
|
|
|
while (member && (member->GetKind() == Track::Wave
|
|
#ifdef USE_MIDI
|
|
|| member->GetKind() == Track::Note
|
|
#endif
|
|
)) {
|
|
t = member;
|
|
member = l->GetPrev(member);
|
|
}
|
|
|
|
// Make it current (if t is still NULL there are no wave tracks, so we're
|
|
// not in a sync-locked group).
|
|
if (t)
|
|
cur = (TrackListNode *) t->GetNode();
|
|
|
|
mInLabelSection = false;
|
|
|
|
return t;
|
|
}
|
|
|
|
Track *SyncLockedTracksIterator::Next(bool skiplinked)
|
|
{
|
|
Track *t = TrackListIterator::Next(skiplinked);
|
|
|
|
//
|
|
// Ways to end a sync-locked group
|
|
//
|
|
|
|
// End of tracks
|
|
if (!t)
|
|
return NULL;
|
|
|
|
// In the label section, encounter a non-label track
|
|
if (mInLabelSection && t->GetKind() != Track::Label) {
|
|
cur = NULL;
|
|
return NULL;
|
|
}
|
|
// This code block stops a group when a NoteTrack is encountered
|
|
#ifndef USE_MIDI
|
|
// Encounter a non-wave non-label track
|
|
if (t->GetKind() != Track::Wave && t->GetKind() != Track::Label) {
|
|
cur = NULL;
|
|
return NULL;
|
|
}
|
|
#endif
|
|
// Otherwise, check if we're in the label section
|
|
mInLabelSection = (t->GetKind() == Track::Label);
|
|
|
|
return t;
|
|
}
|
|
|
|
Track *SyncLockedTracksIterator::Prev(bool skiplinked)
|
|
{
|
|
Track *t = TrackListIterator::Prev(skiplinked);
|
|
|
|
//
|
|
// Ways to end a sync-locked group in reverse
|
|
//
|
|
|
|
// Beginning of tracks
|
|
if (!t)
|
|
return NULL;
|
|
|
|
// In wave section, encounter a label track
|
|
if (!mInLabelSection && t->GetKind() == Track::Label) {
|
|
cur = NULL;
|
|
return NULL;
|
|
}
|
|
#ifndef USE_MIDI
|
|
// Encounter a non-wave non-label track
|
|
if (t->GetKind() != Track::Wave && t->GetKind() != Track::Label) {
|
|
cur = NULL;
|
|
return NULL;
|
|
}
|
|
#endif
|
|
// Otherwise, check if we're in the label section
|
|
mInLabelSection = (t->GetKind() == Track::Label);
|
|
|
|
return t;
|
|
}
|
|
|
|
Track *SyncLockedTracksIterator::Last(bool skiplinked)
|
|
{
|
|
if (!cur)
|
|
return NULL;
|
|
|
|
Track *t = cur->t;
|
|
|
|
while (l->GetNext(t)) {
|
|
// Check if this is the last track in the sync-locked group.
|
|
int nextKind = l->GetNext(t)->GetKind();
|
|
if (mInLabelSection && nextKind != Track::Label)
|
|
break;
|
|
if (nextKind != Track::Label && nextKind != Track::Wave
|
|
#ifdef USE_MIDI
|
|
&& nextKind != Track::Note
|
|
#endif
|
|
)
|
|
break;
|
|
|
|
t = Next(skiplinked);
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
|
|
// TrackList
|
|
//
|
|
// The TrackList sends itself events whenever an update occurs to the list it
|
|
// is managing. Any other classes that may be interested in get these updates
|
|
// should use TrackList::Connect() and TrackList::Disconnect().
|
|
//
|
|
DEFINE_EVENT_TYPE(EVT_TRACKLIST_RESIZED);
|
|
DEFINE_EVENT_TYPE(EVT_TRACKLIST_UPDATED);
|
|
|
|
TrackList::TrackList()
|
|
: wxEvtHandler()
|
|
{
|
|
head = NULL;
|
|
tail = NULL;
|
|
}
|
|
|
|
TrackList::~TrackList()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void TrackList::RecalcPositions(const TrackListNode *node)
|
|
{
|
|
Track *t;
|
|
int i = 0;
|
|
int y = 0;
|
|
|
|
if (!node) {
|
|
return;
|
|
}
|
|
|
|
if (node->prev) {
|
|
t = node->prev->t;
|
|
i = t->GetIndex() + 1;
|
|
y = t->GetY() + t->GetHeight();
|
|
}
|
|
|
|
for (const TrackListNode *n = node; n; n = n->next) {
|
|
t = n->t;
|
|
t->SetIndex(i++);
|
|
t->SetY(y);
|
|
y += t->GetHeight();
|
|
}
|
|
}
|
|
|
|
void TrackList::UpdatedEvent(const TrackListNode *node)
|
|
{
|
|
wxCommandEvent e(EVT_TRACKLIST_UPDATED);
|
|
if (node) {
|
|
e.SetClientData(node->t);
|
|
}
|
|
else {
|
|
e.SetClientData(NULL);
|
|
}
|
|
ProcessEvent(e);
|
|
}
|
|
|
|
void TrackList::ResizedEvent(const TrackListNode *node)
|
|
{
|
|
if (node) {
|
|
wxCommandEvent e(EVT_TRACKLIST_RESIZED);
|
|
e.SetClientData(node->t);
|
|
ProcessEvent(e);
|
|
}
|
|
}
|
|
|
|
void TrackList::Add(Track * t)
|
|
{
|
|
TrackListNode *n = new TrackListNode();
|
|
t->SetOwner(this, n);
|
|
|
|
n->t = (Track *) t;
|
|
n->prev = tail;
|
|
n->next = NULL;
|
|
|
|
if (tail) {
|
|
tail->next = n;
|
|
}
|
|
tail = n;
|
|
|
|
if (!head) {
|
|
head = n;
|
|
}
|
|
|
|
RecalcPositions(n);
|
|
UpdatedEvent(n);
|
|
}
|
|
|
|
void TrackList::AddToHead(Track * t)
|
|
{
|
|
TrackListNode *n = new TrackListNode();
|
|
t->SetOwner(this, n);
|
|
|
|
n->t = (Track *) t;
|
|
n->prev = NULL;
|
|
n->next = head;
|
|
|
|
if (head) {
|
|
head->prev = n;
|
|
}
|
|
head = n;
|
|
|
|
if (!tail) {
|
|
tail = n;
|
|
}
|
|
|
|
RecalcPositions(head);
|
|
UpdatedEvent(n);
|
|
ResizedEvent(n);
|
|
}
|
|
|
|
void TrackList::Replace(Track * t, Track * with, bool deletetrack)
|
|
{
|
|
if (t && with) {
|
|
TrackListNode *node = (TrackListNode *) t->GetNode();
|
|
|
|
t->SetOwner(NULL, NULL);
|
|
if (deletetrack) {
|
|
delete t;
|
|
}
|
|
|
|
node->t = with;
|
|
with->SetOwner(this, node);
|
|
RecalcPositions(node);
|
|
UpdatedEvent(node);
|
|
ResizedEvent(node);
|
|
}
|
|
}
|
|
|
|
void TrackList::Remove(Track * t, bool deletetrack)
|
|
{
|
|
if (t) {
|
|
TrackListNode *node = (TrackListNode *) t->GetNode();
|
|
|
|
t->SetOwner(NULL, NULL);
|
|
if (deletetrack) {
|
|
delete t;
|
|
}
|
|
|
|
if (node) {
|
|
if (node->prev) {
|
|
node->prev->next = node->next;
|
|
}
|
|
else {
|
|
head = node->next;
|
|
}
|
|
|
|
if (node->next) {
|
|
node->next->prev = node->prev;
|
|
RecalcPositions(node->next);
|
|
}
|
|
else {
|
|
tail = node->prev;
|
|
}
|
|
|
|
UpdatedEvent(NULL);
|
|
ResizedEvent(node->next);
|
|
|
|
delete node;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrackList::Clear(bool deleteTracks /* = false */)
|
|
{
|
|
while (head) {
|
|
TrackListNode *temp = head;
|
|
|
|
head->t->SetOwner(NULL, NULL);
|
|
if (deleteTracks) {
|
|
delete head->t;
|
|
}
|
|
|
|
head = head->next;
|
|
delete temp;
|
|
}
|
|
tail = NULL;
|
|
|
|
UpdatedEvent(NULL);
|
|
}
|
|
|
|
void TrackList::Select(Track * t, bool selected /* = true */ )
|
|
{
|
|
if (t) {
|
|
const TrackListNode *node = t->GetNode();
|
|
if (node) {
|
|
t->SetSelected(selected);
|
|
if (t->GetLinked() && node->next) {
|
|
node->next->t->SetSelected(selected);
|
|
}
|
|
else if (node->prev && node->prev->t->GetLinked()) {
|
|
node->prev->t->SetSelected(selected);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Track *TrackList::GetLink(Track * t) const
|
|
{
|
|
if (t) {
|
|
return t->GetLink();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Track *TrackList::GetNext(Track * t, bool linked) const
|
|
{
|
|
if (t) {
|
|
const TrackListNode *node = t->GetNode();
|
|
if (node) {
|
|
if (linked && t->GetLinked()) {
|
|
node = node->next;
|
|
}
|
|
|
|
if (node) {
|
|
node = node->next;
|
|
}
|
|
|
|
if (node) {
|
|
return node->t;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Track *TrackList::GetPrev(Track * t, bool linked) const
|
|
{
|
|
if (t) {
|
|
const TrackListNode *node = t->GetNode();
|
|
if (node) {
|
|
if (linked) {
|
|
// Input track second in team?
|
|
if (!t->GetLinked() && t->GetLink()) {
|
|
// Make it the first
|
|
node = node->prev;
|
|
}
|
|
|
|
// Get the previous node
|
|
node = node->prev;
|
|
|
|
// Bump back to start of team
|
|
if (node && node->t->GetLink()) {
|
|
node = node->prev;
|
|
}
|
|
}
|
|
else {
|
|
node = node->prev;
|
|
}
|
|
|
|
if (node) {
|
|
return node->t;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/// For mono track height of track
|
|
/// For stereo track combined height of both channels.
|
|
int TrackList::GetGroupHeight(Track * t) const
|
|
{
|
|
int height = t->GetHeight();
|
|
|
|
t = t->GetLink();
|
|
if (t) {
|
|
height += t->GetHeight();
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
bool TrackList::CanMoveUp(Track * t) const
|
|
{
|
|
return GetPrev(t, true) != NULL;
|
|
}
|
|
|
|
bool TrackList::CanMoveDown(Track * t) const
|
|
{
|
|
return GetNext(t, true) != NULL;
|
|
}
|
|
|
|
// Precondition: if either of s1 or s2 are "linked", then
|
|
// s1 and s2 must each be the FIRST node of the linked pair.
|
|
//
|
|
// This is used when you want to swap the track or pair of
|
|
// tracks in s1 with the track or pair of tracks in s2.
|
|
// The complication is that the tracks are stored in a single
|
|
// linked list, and pairs of tracks are marked only by a flag
|
|
// in one of the tracks.
|
|
void TrackList::Swap(TrackListNode * s1, TrackListNode * s2)
|
|
{
|
|
Track *link;
|
|
Track *source[4];
|
|
TrackListNode *target[4];
|
|
|
|
// if a null pointer is passed in, we want to know about it
|
|
wxASSERT(s1);
|
|
wxASSERT(s2);
|
|
|
|
// Deal with firat track in each team
|
|
link = s1->t->GetLink();
|
|
if (!s1->t->GetLinked() && link) {
|
|
s1 = (TrackListNode *) link->GetNode();
|
|
}
|
|
|
|
link = s2->t->GetLink();
|
|
if (!s2->t->GetLinked() && link) {
|
|
s2 = (TrackListNode *) link->GetNode();
|
|
}
|
|
|
|
target[0] = s1;
|
|
source[0] = target[0]->t;
|
|
if (source[0]->GetLinked()) {
|
|
target[1] = target[0]->next;
|
|
source[1] = target[1]->t;
|
|
}
|
|
else {
|
|
target[1] = NULL;
|
|
source[1] = NULL;
|
|
}
|
|
|
|
target[2] = s2;
|
|
source[2] = target[2]->t;
|
|
if (source[2]->GetLinked()) {
|
|
target[3] = target[2]->next;
|
|
source[3] = target[3]->t;
|
|
}
|
|
else {
|
|
target[3] = NULL;
|
|
source[3] = NULL;
|
|
}
|
|
|
|
int s = 2;
|
|
for (int t = 0; t < 4; t++) {
|
|
if (target[t]) {
|
|
target[t]->t = source[s];
|
|
target[t]->t->SetOwner(NULL, NULL);
|
|
target[t]->t->SetOwner(this, target[t]);
|
|
|
|
s = (s + 1) % 4;
|
|
if (!source[s]) {
|
|
s = (s + 1) % 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
RecalcPositions(s1);
|
|
UpdatedEvent(s1);
|
|
ResizedEvent(s1);
|
|
}
|
|
|
|
bool TrackList::MoveUp(Track * t)
|
|
{
|
|
if (t) {
|
|
Track *p = GetPrev(t, true);
|
|
if (p) {
|
|
Swap((TrackListNode *)p->GetNode(), (TrackListNode *)t->GetNode());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TrackList::MoveDown(Track * t)
|
|
{
|
|
if (t) {
|
|
Track *n = GetNext(t, true);
|
|
if (n) {
|
|
Swap((TrackListNode *)t->GetNode(), (TrackListNode *)n->GetNode());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TrackList::Contains(Track * t) const
|
|
{
|
|
TrackListNode *p = head;
|
|
while (p) {
|
|
if (p->t == t) {
|
|
return true;
|
|
}
|
|
p = p->next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TrackList::IsEmpty() const
|
|
{
|
|
return (head == NULL);
|
|
}
|
|
|
|
int TrackList::GetCount() const
|
|
{
|
|
int cnt = 0;
|
|
|
|
if (tail) {
|
|
cnt = tail->t->GetIndex() + 1;
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
TimeTrack *TrackList::GetTimeTrack()
|
|
{
|
|
TrackListNode *p = head;
|
|
while (p) {
|
|
if (p->t->GetKind() == Track::Time) {
|
|
return (TimeTrack *)p->t;
|
|
}
|
|
p = p->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int TrackList::GetNumExportChannels(bool selectionOnly)
|
|
{
|
|
/* counters for tracks panned different places */
|
|
int numLeft = 0;
|
|
int numRight = 0;
|
|
int numMono = 0;
|
|
/* track iteration kit */
|
|
Track *tr;
|
|
TrackListIterator iter;
|
|
|
|
for (tr = iter.First(this); tr != NULL; tr = iter.Next()) {
|
|
|
|
// Only want wave tracks
|
|
if (tr->GetKind() != Track::Wave) {
|
|
continue;
|
|
}
|
|
|
|
// do we only want selected ones?
|
|
if (selectionOnly && !(tr->GetSelected())) {
|
|
//want selected but this one is not
|
|
continue;
|
|
}
|
|
|
|
// Found a left channel
|
|
if (tr->GetChannel() == Track::LeftChannel) {
|
|
numLeft++;
|
|
}
|
|
|
|
// Found a right channel
|
|
else if (tr->GetChannel() == Track::RightChannel) {
|
|
numRight++;
|
|
}
|
|
|
|
// Found a mono channel, but it may be panned
|
|
else if (tr->GetChannel() == Track::MonoChannel) {
|
|
float pan = ((WaveTrack*)tr)->GetPan();
|
|
|
|
// Figure out what kind of channel it should be
|
|
if (pan == -1.0) { // panned hard left
|
|
numLeft++;
|
|
}
|
|
else if (pan == 1.0) { // panned hard right
|
|
numRight++;
|
|
}
|
|
else if (pan == 0) { // panned dead center
|
|
numMono++;
|
|
}
|
|
else { // panned somewhere else
|
|
numLeft++;
|
|
numRight++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if there is stereo content, report 2, else report 1
|
|
if (numRight > 0 || numLeft > 0) {
|
|
return 2;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void TrackList::GetWaveTracks(bool selectionOnly,
|
|
int *num, WaveTrack ***tracks)
|
|
{
|
|
int i;
|
|
*num = 0;
|
|
|
|
TrackListNode *p = head;
|
|
while (p) {
|
|
if (p->t->GetKind() == Track::Wave && !(p->t->GetMute()) &&
|
|
(p->t->GetSelected() || !selectionOnly)) {
|
|
(*num)++;
|
|
}
|
|
p = p->next;
|
|
}
|
|
|
|
*tracks = new WaveTrack*[*num];
|
|
p = head;
|
|
i = 0;
|
|
while (p) {
|
|
if (p->t->GetKind() == Track::Wave && !(p->t->GetMute()) &&
|
|
(p->t->GetSelected() || !selectionOnly)) {
|
|
(*tracks)[i++] = (WaveTrack *)p->t;
|
|
}
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
WaveTrackArray TrackList::GetWaveTrackArray(bool selectionOnly)
|
|
{
|
|
WaveTrackArray waveTrackArray;
|
|
|
|
TrackListNode *p = head;
|
|
while (p) {
|
|
if (p->t->GetKind() == Track::Wave &&
|
|
(p->t->GetSelected() || !selectionOnly)) {
|
|
waveTrackArray.Add((WaveTrack*)p->t);
|
|
}
|
|
|
|
p = p->next;
|
|
}
|
|
|
|
return waveTrackArray;
|
|
}
|
|
|
|
#if defined(USE_MIDI)
|
|
NoteTrackArray TrackList::GetNoteTrackArray(bool selectionOnly)
|
|
{
|
|
NoteTrackArray noteTrackArray;
|
|
|
|
TrackListNode *p = head;
|
|
while (p) {
|
|
if (p->t->GetKind() == Track::Note &&
|
|
(p->t->GetSelected() || !selectionOnly)) {
|
|
noteTrackArray.Add((NoteTrack*)p->t);
|
|
}
|
|
|
|
p = p->next;
|
|
}
|
|
|
|
return noteTrackArray;
|
|
}
|
|
#endif
|
|
|
|
int TrackList::GetHeight() const
|
|
{
|
|
int height = 0;
|
|
|
|
if (tail) {
|
|
const Track *t = tail->t;
|
|
height = t->GetY() + t->GetHeight();
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
double TrackList::GetMinOffset() const
|
|
{
|
|
const TrackListNode *node = head;
|
|
if (!node) {
|
|
return 0.0;
|
|
}
|
|
|
|
double len = node->t->GetOffset();
|
|
|
|
while ((node = node->next)) {
|
|
double l = node->t->GetOffset();
|
|
if (l < len) {
|
|
len = l;
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
double TrackList::GetStartTime() const
|
|
{
|
|
const TrackListNode *node = head;
|
|
if (!node) {
|
|
return 0.0;
|
|
}
|
|
|
|
double min = node->t->GetStartTime();
|
|
|
|
while ((node = node->next)) {
|
|
double l = node->t->GetStartTime();
|
|
if (l < min) {
|
|
min = l;
|
|
}
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
double TrackList::GetEndTime() const
|
|
{
|
|
const TrackListNode *node = head;
|
|
if (!node) {
|
|
return 0.0;
|
|
}
|
|
|
|
double max = node->t->GetEndTime();
|
|
|
|
while ((node = node->next)) {
|
|
double l = node->t->GetEndTime();
|
|
if (l > max) {
|
|
max = l;
|
|
}
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|
|
// Indentation settings for Vim and Emacs and unique identifier for Arch, a
|
|
// version control system. Please do not modify past this point.
|
|
//
|
|
// Local Variables:
|
|
// c-basic-offset: 3
|
|
// indent-tabs-mode: nil
|
|
// End:
|
|
//
|
|
// vim: et sts=3 sw=3
|
|
// arch-tag: 575d97aa-2da9-476d-a39e-2ccad16b7cdd
|
|
|