New project. New track. Generate sound -- do not cancel. Select all, generate sound again -- this time, do cancel the progress dialog. Undo, so you see one, empty track again. Save. Close. Reopen. The cause of these orphans is different from the first case. The previous commit is necessary but not sufficient to fix this.
1352 lines
26 KiB
C++
1352 lines
26 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;
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
mYv = mHeight;
|
|
mHeightv = mHeight;
|
|
mPerY = 0.5;
|
|
mVirtualStereo = false;
|
|
#endif
|
|
|
|
mMinimized = false;
|
|
|
|
mOffset = 0.0;
|
|
|
|
mChannel = MonoChannel;
|
|
}
|
|
|
|
Track::Track(const Track &orig)
|
|
{
|
|
mDirManager = NULL;
|
|
|
|
mList = NULL;
|
|
mNode = NULL;
|
|
mY = 0;
|
|
mIndex = 0;
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
mPerY = 0.5;
|
|
#endif
|
|
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;
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
mVirtualStereo = orig.mVirtualStereo;
|
|
mHeightv = orig.mHeightv;
|
|
mYv = orig.mYv;
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
int Track::GetY(bool vStereo) const
|
|
{
|
|
if(vStereo && mChannel == Track::MonoChannel) return mYv;
|
|
return mY;
|
|
}
|
|
|
|
void Track::SetY(int y, bool vStereo)
|
|
{
|
|
if(vStereo && mChannel == Track::MonoChannel) mYv = y;
|
|
else mY = y;
|
|
}
|
|
|
|
int Track::GetHeight(bool vStereo) const
|
|
{
|
|
if (mMinimized) {
|
|
return GetMinimizedHeight();
|
|
}
|
|
|
|
if(vStereo && mChannel == Track::MonoChannel) return mHeightv;
|
|
return mHeight;
|
|
}
|
|
|
|
void Track::SetHeight(int h, bool vStereo)
|
|
{
|
|
|
|
if(vStereo && mChannel == Track::MonoChannel) mHeightv = h;
|
|
else mHeight = h;
|
|
|
|
mList->RecalcPositions(mNode);
|
|
mList->ResizedEvent(mNode);
|
|
}
|
|
|
|
#else // EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
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);
|
|
}
|
|
#endif // EXPERIMENTAL_OUTPUT_DISPLAY
|
|
|
|
bool Track::GetMinimized() const
|
|
{
|
|
return mMinimized;
|
|
}
|
|
|
|
void Track::SetMinimized(bool isMinimized)
|
|
{
|
|
mMinimized = isMinimized;
|
|
mList->RecalcPositions(mNode);
|
|
mList->ResizedEvent(mNode);
|
|
}
|
|
|
|
void Track::SetLinked(bool l)
|
|
{
|
|
mLinked = l;
|
|
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;
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
void Track::ReorderList(bool resize)
|
|
{
|
|
mList->RecalcPositions(mNode);
|
|
if(resize)
|
|
mList->ResizedEvent(mNode);
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
wxASSERT(ret);
|
|
|
|
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(bool destructorDeletesTracks)
|
|
: wxEvtHandler()
|
|
, mDestructorDeletesTracks(destructorDeletesTracks)
|
|
{
|
|
head = NULL;
|
|
tail = NULL;
|
|
}
|
|
|
|
TrackList::~TrackList()
|
|
{
|
|
Clear(mDestructorDeletesTracks);
|
|
}
|
|
|
|
void TrackList::RecalcPositions(const TrackListNode *node)
|
|
{
|
|
Track *t;
|
|
int i = 0;
|
|
int y = 0;
|
|
|
|
if (!node) {
|
|
return;
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
int cnt = 0;
|
|
if (node->prev) {
|
|
t = node->prev->t;
|
|
i = t->GetIndex() + 1;
|
|
if(MONO_WAVE_PAN(t))
|
|
y = t->GetY(true) + t->GetHeight(true);
|
|
else
|
|
y = t->GetY() + t->GetHeight();
|
|
}
|
|
|
|
for (const TrackListNode *n = node; n; n = n->next) {
|
|
t = n->t;
|
|
if(MONO_WAVE_PAN(t))
|
|
cnt++;
|
|
|
|
if(cnt != 2){
|
|
t->SetIndex(i++);
|
|
t->SetY(y);
|
|
y += t->GetHeight();
|
|
}
|
|
if(cnt != 0){
|
|
t->SetY(y,true);
|
|
y += t->GetHeight(true);
|
|
}
|
|
cnt = 0;
|
|
}
|
|
#else // EXPERIMENTAL_OUTPUT_DISPLAY
|
|
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();
|
|
}
|
|
#endif // EXPERIMENTAL_OUTPUT_DISPLAY
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// Return a track in the list that comes after Track t
|
|
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();
|
|
}
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
else if(MONO_WAVE_PAN(t)){
|
|
height += t->GetHeight(true);
|
|
}
|
|
#endif
|
|
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()) {
|
|
|
|
// Want only unmuted wave tracks.
|
|
if ((tr->GetKind() != Track::Wave) || tr->GetMute())
|
|
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;
|
|
|
|
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
|
|
if (tail) {
|
|
const Track *t = tail->t;
|
|
if(MONO_WAVE_PAN(t))
|
|
height = t->GetY(true) + t->GetHeight(true);
|
|
else
|
|
height = t->GetY() + t->GetHeight();
|
|
}
|
|
#else
|
|
if (tail) {
|
|
const Track *t = tail->t;
|
|
height = t->GetY() + t->GetHeight();
|
|
}
|
|
#endif
|
|
return height;
|
|
}
|
|
|
|
double TrackList::GetMinOffset() const
|
|
{
|
|
const TrackListNode *node = head;
|
|
if (!node) {
|
|
return 0.0;
|
|
}
|
|
|
|
double len = node->t->GetOffset();
|
|
|
|
while ((node = node->next)!=NULL) {
|
|
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)!=NULL) {
|
|
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)!=NULL) {
|
|
double l = node->t->GetEndTime();
|
|
if (l > max) {
|
|
max = l;
|
|
}
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|