Index: src/BlockFile.cpp
===================================================================
--- src/BlockFile.cpp	(revision 11311)
+++ src/BlockFile.cpp	(working copy)
@@ -99,11 +99,11 @@
 BlockFile::BlockFile(wxFileName fileName, sampleCount samples):
    mLockCount(0),
    mRefCount(1),
-   mFileName(fileName),
    mLen(samples),
    mSummaryInfo(samples)
 {
    mSilentLog=FALSE;
+   SetFileName(fileName);
 }
 
 BlockFile::~BlockFile()
@@ -123,9 +123,10 @@
 }
 
 ///sets the file name the summary info will be saved in.  threadsafe.
-void BlockFile::SetFileName(wxFileName &name)
+void BlockFile::SetFileName(const wxFileName &name)
 {
    mFileName=name;
+   mFullPath=name.GetFullPath();
 }
 
 
@@ -565,13 +566,13 @@
    // I would much rather have this code as part of the constructor, but
    // I can't call virtual functions from the constructor.  So we just
    // need to ensure that every derived class calls this in *its* constructor
-   wxFFile summaryFile(mFileName.GetFullPath(), wxT("wb"));
+   wxFFile summaryFile(GetFullPath(), wxT("wb"));
 
    if( !summaryFile.IsOpened() ){
       // Never silence the Log w.r.t write errors; they always count
       // as new errors
       wxLogError(wxT("Unable to write summary data to file %s"),
-                   mFileName.GetFullPath().c_str());
+                   GetFullPath().c_str());
       // If we can't write, there's nothing to do.
       return;
    }
@@ -599,7 +600,7 @@
 ///              be at least mSummaryInfo.totalSummaryBytes long.
 bool AliasBlockFile::ReadSummary(void *data)
 {
-   wxFFile summaryFile(mFileName.GetFullPath(), wxT("rb"));
+   wxFFile summaryFile(GetFullPath(), wxT("rb"));
    wxLogNull *silence=0;
    if(mSilentLog)silence= new wxLogNull();
 
@@ -637,7 +638,7 @@
 
 wxLongLong AliasBlockFile::GetSpaceUsage()
 {
-   wxFFile summaryFile(mFileName.GetFullPath());
+   wxFFile summaryFile(GetFullPath());
    return summaryFile.Length();
 }
 
Index: src/BlockFile.h
===================================================================
--- src/BlockFile.h	(revision 11311)
+++ src/BlockFile.h	(working copy)
@@ -71,8 +71,9 @@
    /// Gets the filename of the disk file associated with this BlockFile
    /// (can be empty -- some BlockFiles, like SilentBlockFile, correspond to
    ///  no file on disk)
+   wxString GetFullPath(){return mFullPath;}
    virtual wxFileName GetFileName();
-   virtual void SetFileName(wxFileName &name);
+   virtual void SetFileName(const wxFileName &name);
 
    virtual sampleCount GetLength() { return mLen; }
    virtual void SetLength(const sampleCount newLen) { mLen = newLen; }
@@ -163,6 +164,8 @@
 
  protected:
    wxFileName mFileName;
+   wxString mFullPath;
+
    sampleCount mLen;
    SummaryInfo mSummaryInfo;
    float mMin, mMax, mRMS;
Index: src/blockfile/LegacyAliasBlockFile.cpp
===================================================================
--- src/blockfile/LegacyAliasBlockFile.cpp	(revision 11311)
+++ src/blockfile/LegacyAliasBlockFile.cpp	(working copy)
@@ -66,7 +66,7 @@
    xmlFile.StartTag(wxT("legacyblockfile"));
    
    xmlFile.WriteAttr(wxT("alias"), 1);
-   xmlFile.WriteAttr(wxT("name"), mFileName.GetFullName());
+   xmlFile.WriteAttr(wxT("name"), GetFileName().GetFullName());
    xmlFile.WriteAttr(wxT("aliaspath"), mAliasedFileName.GetFullPath());
    xmlFile.WriteAttr(wxT("aliasstart"), mAliasStart);
    xmlFile.WriteAttr(wxT("aliaslen"), mLen);
Index: src/blockfile/LegacyBlockFile.cpp
===================================================================
--- src/blockfile/LegacyBlockFile.cpp	(revision 11311)
+++ src/blockfile/LegacyBlockFile.cpp	(working copy)
@@ -151,7 +151,7 @@
 /// mSummaryinfo.totalSummaryBytes long.
 bool LegacyBlockFile::ReadSummary(void *data)
 {
-   wxFFile summaryFile(mFileName.GetFullPath(), wxT("rb"));
+   wxFFile summaryFile(GetFullPath(), wxT("rb"));
    wxLogNull *silence=0;
    if(mSilentLog)silence= new wxLogNull();
 
@@ -209,7 +209,7 @@
    wxFile f;   // will be closed when it goes out of scope
    SNDFILE *sf = NULL;
 
-   if (f.Open(mFileName.GetFullPath())) {
+   if (f.Open(GetFullPath())) {
       // Even though there is an sf_open() that takes a filename, use the one that
       // takes a file descriptor since wxWidgets can open a file with a Unicode name and
       // libsndfile can't (under Windows).
@@ -274,7 +274,7 @@
 {
    xmlFile.StartTag(wxT("legacyblockfile"));
 
-   xmlFile.WriteAttr(wxT("name"), mFileName.GetFullName());
+   xmlFile.WriteAttr(wxT("name"), GetFileName().GetFullName());
    xmlFile.WriteAttr(wxT("len"), mLen);
    if (mSummaryInfo.fields < 3)
       xmlFile.WriteAttr(wxT("norms"), 1);
@@ -340,7 +340,7 @@
 
 wxLongLong LegacyBlockFile::GetSpaceUsage()
 {
-   wxFFile dataFile(mFileName.GetFullPath());
+   wxFFile dataFile(GetFullPath());
    return dataFile.Length();
 }
 
Index: src/blockfile/ODDecodeBlockFile.cpp
===================================================================
--- src/blockfile/ODDecodeBlockFile.cpp	(revision 11311)
+++ src/blockfile/ODDecodeBlockFile.cpp	(working copy)
@@ -77,7 +77,7 @@
 { 
    if(IsSummaryAvailable())
    {
-      wxFFile summaryFile(mFileName.GetFullPath());
+      wxFFile summaryFile(GetFullPath());
       return summaryFile.Length();
    }
    else
@@ -200,7 +200,7 @@
        //unlock to prevent deadlock and resume lock after.
       UnlockRead();
       mFileNameMutex.Lock();
-      xmlFile.WriteAttr(wxT("summaryfile"), mFileName.GetFullName());
+      xmlFile.WriteAttr(wxT("summaryfile"), GetFileName().GetFullName());
       mFileNameMutex.Unlock();
       LockRead();
       xmlFile.WriteAttr(wxT("audiofile"), mAudioFileName.GetFullPath());
@@ -354,6 +354,7 @@
 {
    mFileNameMutex.Lock();
    mFileName=name;
+   mFullPath=name.GetFullPath();
 /* mchinen oct 9 2009 don't think we need the char* but leaving it in for now just as a reminder that we might
    if wxFileName isn't threadsafe.  
    delete [] mFileNameChar;
@@ -367,7 +368,7 @@
 {
    wxFileName name;
    mFileNameMutex.Lock();
-   name = mFileName;
+   name = GetFileName();
    mFileNameMutex.Unlock();
    return name;
 }
Index: src/blockfile/ODPCMAliasBlockFile.cpp
===================================================================
--- src/blockfile/ODPCMAliasBlockFile.cpp	(revision 11311)
+++ src/blockfile/ODPCMAliasBlockFile.cpp	(working copy)
@@ -376,6 +376,7 @@
 {
    mFileNameMutex.Lock();
    mFileName=name;
+   mFullPath=name.GetFullPath();
    mFileNameMutex.Unlock();
 }
 
Index: src/blockfile/PCMAliasBlockFile.cpp
===================================================================
--- src/blockfile/PCMAliasBlockFile.cpp	(revision 11311)
+++ src/blockfile/PCMAliasBlockFile.cpp	(working copy)
@@ -175,7 +175,7 @@
 {
    xmlFile.StartTag(wxT("pcmaliasblockfile"));
 
-   xmlFile.WriteAttr(wxT("summaryfile"), mFileName.GetFullName());
+   xmlFile.WriteAttr(wxT("summaryfile"), GetFileName().GetFullName());
    xmlFile.WriteAttr(wxT("aliasfile"), mAliasedFileName.GetFullPath());
    xmlFile.WriteAttr(wxT("aliasstart"), mAliasStart);
    xmlFile.WriteAttr(wxT("aliaslen"), mLen);
Index: src/blockfile/SimpleBlockFile.cpp
===================================================================
--- src/blockfile/SimpleBlockFile.cpp	(revision 11311)
+++ src/blockfile/SimpleBlockFile.cpp	(working copy)
@@ -171,7 +171,7 @@
    DEBUG_OUTPUT("Writing simple block file");
 
    // Open and write the file
-   wxFFile file(mFileName.GetFullPath(), wxT("wb"));
+   wxFFile file(GetFullPath(), wxT("wb"));
 
    if( !file.IsOpened() ){
       // Can't do anything else.
@@ -255,7 +255,7 @@
    DEBUG_OUTPUT("Reading simple block file into cache");
    
    // Check sample format
-   wxFFile file(mFileName.GetFullPath(), wxT("rb"));
+   wxFFile file(GetFullPath(), wxT("rb"));
    if (!file.IsOpened())
    {
       // Don't read into cache if file not available
@@ -329,7 +329,7 @@
    {
       DEBUG_OUTPUT("ReadSummary: Reading summary from disk");
       
-      wxFFile file(mFileName.GetFullPath(), wxT("rb"));
+      wxFFile file(GetFullPath(), wxT("rb"));
 
       wxLogNull *silence=0;
       if(mSilentLog)silence= new wxLogNull();
@@ -394,7 +394,7 @@
       wxFile f;   // will be closed when it goes out of scope
       SNDFILE *sf = NULL;
 
-      if (f.Open(mFileName.GetFullPath())) {
+      if (f.Open(GetFullPath())) {
          // Even though there is an sf_open() that takes a filename, use the one that
          // takes a file descriptor since wxWidgets can open a file with a Unicode name and
          // libsndfile can't (under Windows).
@@ -459,7 +459,7 @@
 {
    xmlFile.StartTag(wxT("simpleblockfile"));
 
-   xmlFile.WriteAttr(wxT("filename"), mFileName.GetFullName());
+   xmlFile.WriteAttr(wxT("filename"), GetFileName().GetFullName());
    xmlFile.WriteAttr(wxT("len"), mLen);
    xmlFile.WriteAttr(wxT("min"), mMin);
    xmlFile.WriteAttr(wxT("max"), mMax);
@@ -534,13 +534,13 @@
       return 0;
    } else
    {
-      wxFFile dataFile(mFileName.GetFullPath());
+      wxFFile dataFile(this->GetFullPath());
       return dataFile.Length();
    }
 }
 
 void SimpleBlockFile::Recover(){
-   wxFFile file(mFileName.GetFullPath(), wxT("wb"));
+   wxFFile file(GetFullPath(), wxT("wb"));
    int i;
 
    if( !file.IsOpened() ){
Index: src/commands/CommandManager.cpp
===================================================================
--- src/commands/CommandManager.cpp	(revision 11311)
+++ src/commands/CommandManager.cpp	(working copy)
@@ -696,7 +696,7 @@
                    name.c_str(),
                    prev->label.BeforeFirst(wxT('\t')).c_str(),
                    tmpEntry->label.BeforeFirst(wxT('\t')).c_str());
-         wxASSERT(!prev);
+//         wxASSERT(!prev); // why??? (AM)
       }
 #endif
       mCommandNameHash[name] = tmpEntry;
Index: src/DirManager.cpp
===================================================================
--- src/DirManager.cpp	(revision 11311)
+++ src/DirManager.cpp	(working copy)
@@ -971,7 +971,7 @@
       //a summary file, so we should check before we copy.
       if(b->IsSummaryAvailable())
       {
-         if( !wxCopyFile(b->GetFileName().GetFullPath(),
+         if( !wxCopyFile(b->GetFullPath(),
                   newFile.GetFullPath()) )
             return NULL;
       }
@@ -1108,9 +1108,9 @@
       //check to see that summary exists before we copy.
       bool summaryExisted = f->IsSummaryAvailable();
       if (summaryExisted) {
-         if(!copy && !wxRenameFile(f->GetFileName().GetFullPath(), newFileName.GetFullPath()))
+         if(!copy && !wxRenameFile(f->GetFullPath(), newFileName.GetFullPath()))
             return false;
-         if(copy && !wxCopyFile(f->GetFileName().GetFullPath(), newFileName.GetFullPath()))
+         if(copy && !wxCopyFile(f->GetFullPath(), newFileName.GetFullPath()))
                return false;
       }
       f->SetFileName(newFileName);
@@ -1130,7 +1130,7 @@
             bool ok = wxCopyFile(oldFileName.GetFullPath(),
                             newFileName.GetFullPath());
             if(ok && !copy)
-               wxRemoveFile(f->GetFileName().GetFullPath());
+               wxRemoveFile(f->GetFullPath());
             else if (!ok)
                return false;
          }
Index: src/Experimental.h
===================================================================
--- src/Experimental.h	(revision 11311)
+++ src/Experimental.h	(working copy)
@@ -58,22 +58,27 @@
 // As a minimum, if this is turned on for a release,
 // it should have an easy mechanism to disable it at run-time, such as a menu item or a pref,
 // preferrably disabled until other work is done.  Martyn 22/12/2008.
-//#define EFFECT_CATEGORIES
+#define EFFECT_CATEGORIES
 
 // Andreas Micheler, 20.Nov 2007: 
 // A spectrumLogF-like view mode with notes quantization.
 // Just select the "Find Notes" checkbox in the spectrum prefs 
 // to activate it instead of the Spectrum log(f) mode.
-//#define EXPERIMENTAL_FIND_NOTES
+#define EXPERIMENTAL_FIND_NOTES
 
 // AM, 22.Nov 2007
 // Skip Points support in the spectrum view mode.
-//#define EXPERIMENTAL_FFT_SKIP_POINTS
+#define EXPERIMENTAL_FFT_SKIP_POINTS
 
 // AM, 22.Nov 2007: 
 // A Frequency Grid for the Spectrum Log(f) & Find Notes modes
-//#define EXPERIMENTAL_FFT_Y_GRID
+#define EXPERIMENTAL_FFT_Y_GRID
 
+// AM, 19. Nov 2011:
+// Thread for drawing a waveform, 
+// can be very fast if you have several CPU cores
+//#define EXPERIMENTAL_DRAW_THREAD
+
 // Andy Coder, 03.Mar 2009:
 // Allow keyboard seeking before initial playback position
 //#define EXPERIMENTAL_SEEK_BEHIND_CURSOR
@@ -158,3 +163,5 @@
 // Normally disabled, for now.
 // CleanSpeech should be done somehow else and not be polluting the code
 //#define CLEANSPEECH
+
+//#define REFACTORING_TRACKPANEL_DONE
Index: src/Menus.cpp
===================================================================
--- src/Menus.cpp	(revision 11311)
+++ src/Menus.cpp	(working copy)
@@ -180,7 +180,10 @@
 {
    CommandManager *c = &mCommandManager;
    EffectManager& em = EffectManager::Get();
+
+#ifndef EFFECT_CATEGORIES
    EffectArray *effects;
+#endif
    wxArrayString names;
    wxArrayInt indices;
 
Index: src/NoteTrack.h
===================================================================
--- src/NoteTrack.h	(revision 11311)
+++ src/NoteTrack.h	(working copy)
@@ -52,7 +52,7 @@
 
 class AUDACITY_DLL_API NoteTrack:public Track {
  public:
-   friend class TrackArtist;
+   friend class NoteTrackV;
 
    NoteTrack(DirManager * projDirManager);
    virtual ~NoteTrack();
Index: src/prefs/SpectrumPrefs.cpp
===================================================================
--- src/prefs/SpectrumPrefs.cpp	(revision 11311)
+++ src/prefs/SpectrumPrefs.cpp	(working copy)
@@ -153,7 +153,7 @@
          mFrequencyGain =
 			 S.TieNumericTextBox(_("Frequency g&ain (dB/dec):"),
                               wxT("/Spectrum/FrequencyGain"),
-                              0,
+                              -4,
                               4);
       }
       S.EndTwoColumn();
@@ -248,8 +248,8 @@
       wxMessageBox(_("The frequency gain must be an integer"));
       return false;
    }
-   if (frequencygain < 0) {
-      wxMessageBox(_("The frequency gain cannot be negative"));
+   if (frequencygain < -60) {
+      wxMessageBox(_("The frequency gain must be not less than -60 dB/dec"));
       return false;
    }
    if (frequencygain > 60) {
Index: src/Printing.cpp
===================================================================
--- src/Printing.cpp	(revision 11311)
+++ src/Printing.cpp	(working copy)
@@ -37,7 +37,8 @@
 class AudacityPrintout : public wxPrintout
 {
  public:
-   AudacityPrintout(wxString title,
+   AudacityPrintout(wxEvtHandler *win, 
+                    wxString title,
                     TrackList *tracks):
       wxPrintout(title),
       mTracks(tracks)
@@ -97,7 +98,7 @@
       r.width = width;
       r.height = (int)(n->GetHeight() * scale);
 
-      artist.DrawTrack(n, *dc, r, &viewInfo, false, false, false, false);
+      artist.DrawTrack(dc->GetWindow(), n, *dc, r, &viewInfo, false, false, false, false);
 
       dc->SetPen(*wxBLACK_PEN);
       AColor::Line(*dc, 0, r.y, width, r.y);
@@ -154,7 +155,7 @@
    wxPrintDialogData printDialogData(*gPrintData);
 
    wxPrinter printer(&printDialogData);
-   AudacityPrintout printout(name, tracks);
+   AudacityPrintout printout(parent, name, tracks);
    if (!printer.Print(parent, &printout, true)) {
       if (wxPrinter::GetLastError() == wxPRINTER_ERROR) {
          wxMessageBox(_("There was a problem printing."),
Index: src/Track.cpp
===================================================================
--- src/Track.cpp	(revision 11311)
+++ src/Track.cpp	(working copy)
@@ -61,6 +61,10 @@
    mOffset = 0.0;
 
    mChannel = MonoChannel;
+
+#ifdef EXPERIMENTAL_DRAW_THREAD
+   inUpdate=0;
+#endif //EXPERIMENTAL_DRAW_THREAD
 }
 
 Track::Track(const Track &orig)
@@ -74,6 +78,10 @@
 
    Init(orig);
    mOffset = orig.mOffset;
+
+#ifdef EXPERIMENTAL_DRAW_THREAD
+   inUpdate=0;
+#endif //EXPERIMENTAL_DRAW_THREAD
 }
 
 // Copy all the track properties except the actual contents
Index: src/Track.h
===================================================================
--- src/Track.h	(revision 11311)
+++ src/Track.h	(working copy)
@@ -186,6 +186,11 @@
 
    // Checks if linking is on and any track in its sync-lock group is selected.
    bool IsSyncLockSelected();
+
+#ifdef EXPERIMENTAL_DRAW_THREAD
+public:
+   int inUpdate;
+#endif //EXPERIMENTAL_DRAW_THREAD
 };
 
 struct TrackListNode
Index: src/TrackArtist.cpp
===================================================================
--- src/TrackArtist.cpp	(revision 11311)
+++ src/TrackArtist.cpp	(working copy)
@@ -145,7 +145,6 @@
 #include "Audacity.h"
 #include "AudacityApp.h"
 #include "TrackArtist.h"
-#include "float_cast.h"
 
 #include <math.h>
 #include <float.h>
@@ -161,14 +160,11 @@
 #include <wx/log.h>
 #include <wx/datetime.h>
 
-#ifdef USE_MIDI
-#include "NoteTrack.h"
-#endif // USE_MIDI
-
 #include "AColor.h"
 #include "BlockFile.h"
 #include "Envelope.h"
 #include "Track.h"
+#include "TrackPanel.h"
 #include "WaveTrack.h"
 #include "LabelTrack.h"
 #include "TimeTrack.h"
@@ -180,47 +176,6 @@
 #include "Theme.h"
 #include "AllThemeResources.h"
 
-#undef PROFILE_WAVEFORM
-#ifdef PROFILE_WAVEFORM
-   #ifdef __WXMSW__
-      #include <time.h>
-   #else
-      #include <sys/time.h>
-   #endif
-double gWaveformTimeTotal = 0;
-int gWaveformTimeCount = 0;
-#endif
-
-#ifdef USE_MIDI
-/*
-const int octaveHeight = 62;
-const int blackPos[5] = { 6, 16, 32, 42, 52 };
-const int whitePos[7] = { 0, 9, 17, 26, 35, 44, 53 };
-const int notePos[12] = { 1, 6, 11, 16, 21, 27,
-                        32, 37, 42, 47, 52, 57 };
-
-// map pitch number to window coordinate of the *top* of the note
-// Note the "free" variable bottom, which is assumed to be a local 
-// variable set to the offset of pitch 0 relative to the window
-#define IPITCH_TO_Y(t, p) (bottom - ((p) / 12) * octaveHeight - \
-                          notePos[(p) % 12] - (t)->GetPitchHeight())
-
-// GetBottom is called from a couple of places to compute the hypothetical
-// coordinate of the bottom of pitch 0 in window coordinates. See
-// IPITCH_TO_Y above, which computes coordinates relative to GetBottom()
-// Note the -NOTE_MARGIN, which leaves a little margin to draw notes that
-// are out of bounds. I'm not sure why the -2 is necessary.
-int TrackArtist::GetBottom(NoteTrack *t, const wxRect &r)
-{
-   int bottomNote = t->GetBottomNote();
-   int bottom = r.y + r.height - 2 - t->GetNoteMargin() +
-          ((bottomNote / 12) * octaveHeight + notePos[bottomNote % 12]);
-   return bottom;
-
-}
-*/
-#endif // USE_MIDI
-
 TrackArtist::TrackArtist()
 {
    mInsetLeft   = 0;
@@ -228,62 +183,48 @@
    mInsetRight  = 0;
    mInsetBottom = 0;
 
-   mdBrange = ENV_DB_RANGE;
+   vrulerv->mdBrange = ENV_DB_RANGE;
    mShowClipping = false;
    UpdatePrefs();
 
    SetColours();
-   vruler = new Ruler();
 
 #ifdef EXPERIMENTAL_FFT_Y_GRID
-   fftYGridOld=true;
+   mFftYGridOld=true;
 #endif //EXPERIMENTAL_FFT_Y_GRID
 
 #ifdef EXPERIMENTAL_FIND_NOTES
-   fftFindNotesOld=false;
+   mFftFindNotesOld=false;
 #endif
+   UpdatePrefs();
 }
 
 TrackArtist::~TrackArtist()
 {
-   delete vruler;
 }
 
-void TrackArtist::SetColours()
+void TrackArtist::UpdatePrefs()
 {
-   theTheme.SetBrushColour( blankBrush,      clrBlank );
-   theTheme.SetBrushColour( unselectedBrush, clrUnselected);
-   theTheme.SetBrushColour( selectedBrush,   clrSelected);
-   theTheme.SetBrushColour( sampleBrush,     clrSample);
-   theTheme.SetBrushColour( selsampleBrush,  clrSelSample);
-   theTheme.SetBrushColour( dragsampleBrush, clrDragSample);
-   theTheme.SetBrushColour( blankSelectedBrush, clrBlankSelected);
+   vrulerv->UpdatePrefs();
 
-   theTheme.SetPenColour(   blankPen,        clrBlank);
-   theTheme.SetPenColour(   unselectedPen,   clrUnselected);
-   theTheme.SetPenColour(   selectedPen,     clrSelected);
-   theTheme.SetPenColour(   samplePen,       clrSample);
-   theTheme.SetPenColour(   selsamplePen,    clrSelSample);
-   theTheme.SetPenColour(   muteSamplePen,   clrMuteSample);
-   theTheme.SetPenColour(   odProgressDonePen, clrProgressDone);
-   theTheme.SetPenColour(   odProgressNotYetPen, clrProgressNotYet);
-   theTheme.SetPenColour(   rmsPen,          clrRms);
-   theTheme.SetPenColour(   muteRmsPen,      clrMuteRms);
-   theTheme.SetPenColour(   shadowPen,       clrShadow);
-   theTheme.SetPenColour(   clippedPen,      clrClipped);
-   theTheme.SetPenColour(   muteClippedPen,  clrMuteClipped);
-   theTheme.SetPenColour(   blankSelectedPen,clrBlankSelected);
-}
+   mShowClipping = gPrefs->Read(wxT("/GUI/ShowClipping"), mShowClipping);
+   mFFTSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 256);
+   mIsGrayscale = (gPrefs->Read(wxT("/Spectrum/Grayscale"), 0L) != 0);
 
-void TrackArtist::SetInset(int left, int top, int right, int bottom)
-{
-   mInsetLeft   = left;
-   mInsetTop    = top;
-   mInsetRight  = right;
-   mInsetBottom = bottom;
+#ifdef EXPERIMENTAL_FFT_Y_GRID
+   mFftYGrid = (gPrefs->Read(wxT("/Spectrum/FFTYGrid"), 0L) != 0);
+#endif //EXPERIMENTAL_FFT_Y_GRID
+
+#ifdef EXPERIMENTAL_FIND_NOTES
+   mFftFindNotes = (gPrefs->Read(wxT("/Spectrum/FFTFindNotes"), 0L) != 0);
+   mFindNotesMinA = gPrefs->Read(wxT("/Spectrum/FindNotesMinA"), -30.0);
+   mNumberOfMaxima = gPrefs->Read(wxT("/Spectrum/FindNotesN"), 5L);
+   mFindNotesQuantize = (gPrefs->Read(wxT("/Spectrum/FindNotesQuantize"), 0L) != 0);
+#endif //EXPERIMENTAL_FIND_NOTES
 }
 
-void TrackArtist::DrawTracks(TrackList * tracks,
+void TrackArtist::DrawTracks(wxEvtHandler *win,
+                             TrackList * tracks,
                              Track * start,
                              wxDC & dc,
                              wxRegion & reg,
@@ -368,7 +309,7 @@
          rr.y += mInsetTop;
          rr.width -= (mInsetLeft + mInsetRight);
          rr.height -= (mInsetTop + mInsetBottom);
-         DrawTrack(t, dc, rr, viewInfo,
+         DrawTrack(win, t, dc, rr, viewInfo,
                    drawEnvelope, drawSamples, drawSliders, hasSolo);
       }
 
@@ -376,7 +317,8 @@
    }
 }
 
-void TrackArtist::DrawTrack(const Track * t,
+void TrackArtist::DrawTrack(wxEvtHandler *win, 
+                            const Track * t,
                             wxDC & dc,
                             const wxRect & r,
                             const ViewInfo * viewInfo,
@@ -405,13 +347,13 @@
                       drawEnvelope,  drawSamples, drawSliders, true, muted);
          break;
       case WaveTrack::SpectrumDisplay:
-         DrawSpectrum(wt, dc, r, viewInfo, false, false);
+         DrawSpectrum(win, wt, dc, r, viewInfo, false, false);
          break;
       case WaveTrack::SpectrumLogDisplay:
-         DrawSpectrum(wt, dc, r, viewInfo, false, true);
+         DrawSpectrum(win, wt, dc, r, viewInfo, false, true);
          break;
       case WaveTrack::PitchDisplay:
-         DrawSpectrum(wt, dc, r, viewInfo, true, false);
+         DrawSpectrum(win, wt, dc, r, viewInfo, true, false);
          break;
       }
       break;              // case Wave
@@ -433,2646 +375,37 @@
    }
 }
 
-void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r)
+void TrackArtist::SetColours()
 {
-   int kind = t->GetKind();
+   theTheme.SetBrushColour( blankBrush,      clrBlank );
+   theTheme.SetBrushColour( unselectedBrush, clrUnselected);
+   theTheme.SetBrushColour( selectedBrush,   clrSelected);
+   theTheme.SetBrushColour( sampleBrush,     clrSample);
+   theTheme.SetBrushColour( dragsampleBrush, clrDragSample);
+   theTheme.SetBrushColour( blankSelectedBrush, clrBlankSelected);
 
-   // Label and Time tracks do not have a vruler
-   // But give it a beveled area
-   if (kind == Track::Label || kind == Track::Time) {
-      wxRect bev = r;
-      bev.Inflate(-1, -1);
-      bev.width += 1;
-      AColor::BevelTrackInfo(*dc, true, bev);
-
-      return;
-   }
-
-   // All waves have a ruler in the info panel
-   // The ruler needs a bevelled surround.
-   if (kind == Track::Wave) {
-      wxRect bev = r;
-      bev.Inflate(-1, -1);
-      bev.width += 1;
-      AColor::BevelTrackInfo(*dc, true, bev);
-
-      // Pitch doesn't have a ruler
-      if (((WaveTrack *)t)->GetDisplay() == WaveTrack::PitchDisplay) {
-         return;
-      }
-
-      // Right align the ruler
-      wxRect rr = r;
-      rr.width--;
-      if (t->vrulerSize.GetWidth() < r.GetWidth()) {
-         int adj = rr.GetWidth() - t->vrulerSize.GetWidth();
-         rr.x += adj;
-         rr.width -= adj;
-      }
-
-      UpdateVRuler(t, rr);
-
-      vruler->Draw(*dc);
-
-      return;
-   }
-
-#ifdef USE_MIDI
-   // The note track draws a vertical keyboard to label pitches
-   if (kind == Track::Note) {
-      UpdateVRuler(t, r);
-
-      dc->SetPen(*wxTRANSPARENT_PEN);
-      dc->SetBrush(*wxWHITE_BRUSH);
-      wxRect bev = r;
-      bev.x++;
-      bev.y++;
-      bev.width--;
-      bev.height--;
-      dc->DrawRectangle(bev);
-
-      r.y += 2;
-      r.height -= 2;
-
-      //int bottom = GetBottom((NoteTrack *) t, r);
-      NoteTrack *track = (NoteTrack *) t;
-      track->PrepareIPitchToY(r);
-
-      wxPen hilitePen;
-      hilitePen.SetColour(120, 120, 120);
-      wxBrush blackKeyBrush;
-      blackKeyBrush.SetColour(70, 70, 70);
-
-      dc->SetBrush(blackKeyBrush);
-
-      int fontSize = 10;
-#ifdef __WXMSW__
-      fontSize = 8;
-#endif
-
-      wxFont labelFont(fontSize, wxSWISS, wxNORMAL, wxNORMAL);
-      dc->SetFont(labelFont);
-
-      int octave = 0;
-      int obottom = track->GetOctaveBottom(octave);
-      int marg = track->GetNoteMargin();
-      //IPITCH_TO_Y(octave * 12) + PITCH_HEIGHT + 1;
-      while (obottom >= r.y) {
-         dc->SetPen(*wxBLACK_PEN);
-         for (int white = 0; white < 7; white++) {
-            int pos = track->GetWhitePos(white);
-            if (obottom - pos > r.y + marg + 1 && 
-                // don't draw too close to margin line -- it's annoying
-                obottom - pos < r.y + r.height - marg - 3)
-               AColor::Line(*dc, r.x, obottom - pos,
-                            r.x + r.width, obottom - pos);
-         }
-         wxRect br = r;
-         br.height = track->GetPitchHeight();
-         br.x++;
-         br.width = 17;
-         for (int black = 0; black < 5; black++) {
-            br.y = obottom - track->GetBlackPos(black);
-            if (br.y > r.y + marg - 2 && br.y + br.height < r.y + r.height - marg) {
-               dc->SetPen(hilitePen);
-               dc->DrawRectangle(br);
-               dc->SetPen(*wxBLACK_PEN);
-               AColor::Line(*dc,
-                            br.x + 1, br.y + br.height - 1,
-                            br.x + br.width - 1, br.y + br.height - 1);
-               AColor::Line(*dc,
-                            br.x + br.width - 1, br.y + 1,
-                            br.x + br.width - 1, br.y + br.height - 1);
-            }
-         }
-
-         if (octave >= 1 && octave <= 10) {
-            wxString s;
-            // ISO standard: A440 is in the 4th octave, denoted
-            // A4 <- the "4" should be a subscript.
-            s.Printf(wxT("C%d"), octave - 1);
-            long width, height;
-            dc->GetTextExtent(s, &width, &height);
-            if (obottom - height + 4 > r.y &&
-                obottom + 4 < r.y + r.height) {
-               dc->SetTextForeground(wxColour(60, 60, 255));
-               dc->DrawText(s, r.x + r.width - width,
-                            obottom - height + 2);
-            }
-         }
-         obottom = track->GetOctaveBottom(++octave);
-      }
-      // draw lines delineating the out-of-bounds margins
-      dc->SetPen(*wxBLACK_PEN);
-      // you would think the -1 offset here should be -2 to match the
-      // adjustment to r.y (see above), but -1 produces correct output
-      AColor::Line(*dc, r.x, r.y + marg - 1, r.x + r.width, r.y + marg - 1);
-      // since the margin gives us the bottom of the line, 
-      // the extra -1 gets us to the top
-      AColor::Line(*dc, r.x, r.y + r.height - marg - 1,
-                        r.x + r.width, r.y + r.height - marg - 1);
-
-   }
-#endif // USE_MIDI
-
+   theTheme.SetPenColour(   blankPen,        clrBlank);
+   theTheme.SetPenColour(   samplePen,       clrSample);
+   theTheme.SetPenColour(   muteSamplePen,   clrMuteSample);
+   theTheme.SetPenColour(   rmsPen,          clrRms);
+   theTheme.SetPenColour(   muteRmsPen,      clrMuteRms);
+   theTheme.SetPenColour(   clippedPen,      clrClipped);
+   theTheme.SetPenColour(   muteClippedPen,  clrMuteClipped);
 }
 
-void TrackArtist::UpdateVRuler(Track *t, wxRect & r)
+void TrackArtist::SetBackgroundBrushes(
+   wxBrush unselectedBrush_, wxBrush selectedBrush_,
+   wxPen unselectedPen, wxPen selectedPen) 
 {
-   // Label tracks do not have a vruler
-   if (t->GetKind() == Track::Label) {
-      return;
-   }
-
-   // All waves have a ruler in the info panel
-   // The ruler needs a bevelled surround.
-   if (t->GetKind() == Track::Wave) {
-      WaveTrack *wt = (WaveTrack *)t;
-      int display = wt->GetDisplay();
-
-      if (display == WaveTrack::WaveformDisplay) {
-         // Waveform
-
-         float min, max;
-         wt->GetDisplayBounds(&min, &max);
-
-         vruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1);
-         vruler->SetOrientation(wxVERTICAL);
-         vruler->SetRange(max, min);
-         vruler->SetFormat(Ruler::RealFormat);
-         vruler->SetUnits(wxT(""));
-         vruler->SetLabelEdges(false);
-         vruler->SetLog(false);
-      }
-      else if (display == WaveTrack::WaveformDBDisplay) {
-         // Waveform (db)
-
-         vruler->SetUnits(wxT(""));
-
-         float min, max;
-         wt->GetDisplayBounds(&min, &max);
-
-         if (max > 0) {
-            int top = 0;
-            float topval = 0;
-            int bot = r.height;
-            float botval = -mdBrange;
-
-            if (min < 0) {
-               bot = top + (int)((max / (max-min))*(bot-top));
-               min = 0;
-            }
-
-            if (max > 1) {
-               top += (int)((max-1)/(max-min) * (bot-top));
-               max = 1;
-            }
-
-            if (max < 1)
-               topval = -((1-max)*mdBrange);
-
-            if (min > 0) {
-               botval = -((1-min)*mdBrange);
-            }
-
-            if (topval > botval && bot > top+10) {
-               vruler->SetBounds(r.x, r.y+top+1, r.x + r.width, r.y + bot-1);
-               vruler->SetOrientation(wxVERTICAL);
-               vruler->SetRange(topval, botval);
-               vruler->SetFormat(Ruler::LinearDBFormat);
-               vruler->SetLabelEdges(true);
-               vruler->SetLog(false);
-            }
-         }
-      }
-      else if (display == WaveTrack::SpectrumDisplay) {
-         // Spectrum
-
-         if (r.height < 60)
-            return;
-
-         double rate = wt->GetRate();
-         int freq = lrint(rate/2.);
-
-         int maxFreq = GetSpectrumMaxFreq(freq);
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-         maxFreq/=(mFftSkipPoints+1);
-#endif //EXPERIMENTAL_FFT_SKIP_POINTS
-         if(maxFreq > freq)
-            maxFreq = freq;
-
-         int minFreq = GetSpectrumMinFreq(0);
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-         minFreq/=(mFftSkipPoints+1);
-#endif //EXPERIMENTAL_FFT_SKIP_POINTS
-         if(minFreq < 0)
-            minFreq = 0;
-
-         /*
-            draw the ruler
-            we will use Hz if maxFreq is < 2000, otherwise we represent kHz,
-            and append to the numbers a "k"
-         */
-         vruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1);
-         vruler->SetOrientation(wxVERTICAL);
-         vruler->SetFormat(Ruler::RealFormat);
-         vruler->SetLabelEdges(true);
-         // use kHz in scale, if appropriate
-         if (maxFreq>=2000) {
-            vruler->SetRange((maxFreq/1000.), (minFreq/1000.));
-            vruler->SetUnits(wxT("k"));
-         } else {
-            // use Hz
-            vruler->SetRange(int(maxFreq), int(minFreq));
-            vruler->SetUnits(wxT(""));
-         }
-         vruler->SetLog(false);
-      }
-      else if (display == WaveTrack::SpectrumLogDisplay) {
-         // SpectrumLog
-
-         if (r.height < 10)
-            return;
-
-         double rate = wt->GetRate();
-         int freq = lrint(rate/2.);
-
-         int maxFreq = GetSpectrumLogMaxFreq(freq);
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-         maxFreq/=(mFftSkipPoints+1);
-#endif //EXPERIMENTAL_FFT_SKIP_POINTS
-         if(maxFreq > freq)
-            maxFreq = freq;
-
-         int minFreq = GetSpectrumLogMinFreq(freq/1000.0);
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-         minFreq/=(mFftSkipPoints+1);
-#endif //EXPERIMENTAL_FFT_SKIP_POINTS
-         if(minFreq < 1)
-            minFreq = 1;
-
-         /*
-            draw the ruler
-            we will use Hz if maxFreq is < 2000, otherwise we represent kHz,
-            and append to the numbers a "k"
-         */
-         vruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1);
-         vruler->SetOrientation(wxVERTICAL);
-         vruler->SetFormat(Ruler::IntFormat);
-         vruler->SetLabelEdges(true);
-         vruler->SetRange(maxFreq, minFreq);
-         vruler->SetUnits(wxT(""));
-         vruler->SetLog(true);
-      }
-      else if (display == WaveTrack::PitchDisplay) {
-         // Pitch
-      }
-   }
-
-#ifdef USE_MIDI
-   // The note track isn't drawing a ruler at all!
-   // But it needs to!
-   else if (t->GetKind() == Track::Note) {
-      vruler->SetBounds(r.x, r.y+1, r.x + 1, r.y + r.height-1);
-      vruler->SetOrientation(wxVERTICAL);
-   }
-#endif // USE_MIDI
-
-   vruler->GetMaxSize(&t->vrulerSize.x, &t->vrulerSize.y);
+   unselectedBrush = unselectedBrush_;
+   selectedBrush = selectedBrush_;
 }
 
-/// Takes a value between min and max and returns a value between 
-/// height and 0 
-/// \todo  Should this function move int GuiWaveTrack where it can
-/// then use the zoomMin, zoomMax and height values without having 
-/// to have them passed in to it??
-int GetWaveYPos(float value, float min, float max,
-                int height, bool dB, bool outer,
-                float dBr, bool clip)
+void TrackArtist::SetInset(int left, int top, int right, int bottom)
 {
-   if (dB) {
-      if (height == 0) {
-         return 0;
-      }
-
-      float sign = (value >= 0 ? 1 : -1);
-
-      if (value != 0.) {
-         float db = 20.0 * log10(fabs(value));
-         value = (db + dBr) / dBr;
-         if (!outer) {
-            value -= 0.5;
-         }
-         if (value < 0.0) {
-            value = 0.0;
-         }
-         value *= sign;
-      }
-   }
-   else {
-      if (!outer) {
-         if (value >= 0.0) {
-            value -= 0.5;
-         }
-         else {
-            value += 0.5;
-         }
-      }
-   }
-
-   if (clip) {
-      if (value < min) {
-         value = min;
-      }
-      if (value > max) {
-         value = max;
-      }
-   }
-
-   value = (max - value) / (max - min);
-   return (int) (value * (height - 1) + 0.5);
+   mInsetLeft   = left;
+   mInsetTop    = top;
+   mInsetRight  = right;
+   mInsetBottom = bottom;
 }
 
-void TrackArtist::DrawNegativeOffsetTrackArrows(wxDC &dc, const wxRect &r)
-{
-   // Draws two black arrows on the left side of the track to
-   // indicate the user that the track has been time-shifted
-   // to the left beyond t=0.0.
-
-   dc.SetPen(*wxBLACK_PEN);
-   AColor::Line(dc,
-                r.x + 2, r.y + 6,
-                r.x + 8, r.y + 6);
-   AColor::Line(dc,
-                r.x + 2, r.y + 6,
-                r.x + 6, r.y + 2);
-   AColor::Line(dc,
-                r.x + 2, r.y + 6,
-                r.x + 6, r.y + 10);
-   AColor::Line(dc,
-                r.x + 2, r.y + r.height - 8,
-                r.x + 8, r.y + r.height - 8);
-   AColor::Line(dc,
-                r.x + 2, r.y + r.height - 8,
-                r.x + 6, r.y + r.height - 4);
-   AColor::Line(dc,
-                r.x + 2, r.y + r.height - 8,
-                r.x + 6, r.y + r.height - 12);
-}
-
-void TrackArtist::DrawWaveformBackground(wxDC &dc, const wxRect &r, const double env[],
-                                         float zoomMin, float zoomMax, bool dB,
-                                         const sampleCount where[],
-                                         sampleCount ssel0, sampleCount ssel1,
-                                         bool drawEnvelope, bool bIsSyncLockSelected)
-{
-   // Visually (one vertical slice of the waveform background, on its side;
-   // the "*" is the actual waveform background we're drawing
-   //
-   //1.0                              0.0                             -1.0
-   // |--------------------------------|--------------------------------|
-   //      ***************                           ***************
-   //      |             |                           |             |
-   //    maxtop        maxbot                      mintop        minbot
-
-   int h = r.height;
-   int halfHeight = wxMax(h / 2, 1);
-   int maxtop, lmaxtop = 0;
-   int mintop, lmintop = 0;
-   int maxbot, lmaxbot = 0;
-   int minbot, lminbot = 0;
-   bool sel, lsel = false;
-   int x, lx = 0;
-   int l, w;
-
-   dc.SetPen(*wxTRANSPARENT_PEN);
-   dc.SetBrush(blankBrush);
-   dc.DrawRectangle(r);
-
-   for (x = 0; x < r.width; x++) {
-      // First we compute the truncated shape of the waveform background.
-      // If drawEnvelope is true, then we compute the lower border of the
-      // envelope.
-
-      maxtop = GetWaveYPos(env[x], zoomMin, zoomMax,
-                               h, dB, true, mdBrange, true);
-      maxbot = GetWaveYPos(env[x], zoomMin, zoomMax,
-                               h, dB, false, mdBrange, true);
-
-      mintop = GetWaveYPos(-env[x], zoomMin, zoomMax,
-                               h, dB, false, mdBrange, true);
-      minbot = GetWaveYPos(-env[x], zoomMin, zoomMax,
-                               h, dB, true, mdBrange, true);
-
-      // Make sure it's odd so that a that max and min mirror each other
-      mintop +=1;
-      minbot +=1;
-
-      if (!drawEnvelope || maxbot > mintop) {
-         maxbot = halfHeight;
-         mintop = halfHeight;
-      }
-
-      // We don't draw selection color for sync-lock selected tracks.
-      sel = (ssel0 <= where[x] && where[x + 1] < ssel1) && !bIsSyncLockSelected;
-
-      if (lmaxtop == maxtop &&
-          lmintop == mintop &&
-          lmaxbot == maxbot &&
-          lminbot == minbot &&
-          lsel == sel) {
-         continue;
-      }
-
-      dc.SetBrush(lsel ? selectedBrush : unselectedBrush);
-
-      l = r.x + lx;
-      w = x - lx;
-      if (lmaxbot != lmintop - 1) {
-         dc.DrawRectangle(l, r.y + lmaxtop, w, lmaxbot - lmaxtop);
-         dc.DrawRectangle(l, r.y + lmintop, w, lminbot - lmintop);
-      }
-      else {
-         dc.DrawRectangle(l, r.y + lmaxtop, w, lminbot - lmaxtop);
-      }
-
-      lmaxtop = maxtop;
-      lmintop = mintop;
-      lmaxbot = maxbot;
-      lminbot = minbot;
-      lsel = sel;
-      lx = x;
-   }
-
-   dc.SetBrush(lsel ? selectedBrush : unselectedBrush);
-   l = r.x + lx;
-   w = x - lx;
-   if (lmaxbot != lmintop - 1) {
-      dc.DrawRectangle(l, r.y + lmaxtop, w, lmaxbot - lmaxtop);
-      dc.DrawRectangle(l, r.y + lmintop, w, lminbot - lmintop);
-   }
-   else {
-      dc.DrawRectangle(l, r.y + lmaxtop, w, lminbot - lmaxtop);
-   }
-
-   // If sync-lock selected, draw in linked graphics.
-   if (bIsSyncLockSelected && ssel0 < ssel1) {
-      // Find the beginning/end of the selection
-      int begin, end;
-      for (x = 0; x < r.width && where[x] < ssel0; ++x);
-      begin = x;
-      for (; x < r.width && where[x] < ssel1; ++x);
-      end = x;
-      DrawSyncLockTiles(&dc, wxRect(r.x + begin, r.y, end - 1 - begin, r.height));
-   }
-
-   //OK, the display bounds are between min and max, which
-   //is spread across r.height.  Draw the line at the proper place.
-
-   if (zoomMin < 0 && zoomMax > 0) {
-      int half = (int)((zoomMax / (zoomMax - zoomMin)) * h);
-      dc.SetPen(*wxBLACK_PEN);
-      AColor::Line(dc, r.x, r.y + half, r.x + r.width, r.y + half);
-   }
-}
-
-void TrackArtist::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[],
-                                float zoomMin, float zoomMax, bool dB,
-                                const float min[], const float max[], const float rms[],
-                                const int bl[], bool showProgress, bool muted)
-{
-   // Display a line representing the
-   // min and max of the samples in this region
-   int lasth1 = std::numeric_limits<int>::max();
-   int lasth2 = std::numeric_limits<int>::min();
-   int h1;
-   int h2;
-   int *r1 = new int[r.width];
-   int *r2 = new int[r.width];
-   int *clipped = NULL;
-   int clipcnt = 0;
-   int x;
-
-   if (mShowClipping) {
-      clipped =  new int[r.width];
-   }
-
-   long pixAnimOffset = (long)fabs((double)(wxDateTime::Now().GetTicks() * -10)) +
-      wxDateTime::Now().GetMillisecond() / 100; //10 pixels a second
-   
-   bool drawStripes = true;
-   bool drawWaveform = true;
-
-   dc.SetPen(muted ? muteSamplePen : samplePen);
-   for (x = 0; x < r.width; x++) {
-      int xx = r.x + x;
-      double v;
-
-      v = min[x] * env[x];
-      if (mShowClipping && v <= -MAX_AUDIO) {
-         if (clipcnt == 0 || clipped[clipcnt - 1] != xx) {
-            clipped[clipcnt++] = xx;
-         }
-      }
-      h1 = GetWaveYPos(v, zoomMin, zoomMax,
-                       r.height, dB, true, mdBrange, true);
-
-      v = max[x] * env[x];
-      if (mShowClipping && v >= MAX_AUDIO) {
-         if (clipcnt == 0 || clipped[clipcnt - 1] != xx) {
-            clipped[clipcnt++] = xx;
-         }
-      }
-      h2 = GetWaveYPos(v, zoomMin, zoomMax,
-                       r.height, dB, true, mdBrange, true);
-
-      // JKC: This adjustment to h1 and h2 ensures that the drawn
-      // waveform is continuous.
-      if (x > 0) {
-         if (h1 < lasth2) {
-            h1 = lasth2 - 1;
-         }
-         if (h2 > lasth1) {
-            h2 = lasth1 + 1;
-         }
-      }
-      lasth1 = h1;
-      lasth2 = h2;
-
-      r1[x] = GetWaveYPos(-rms[x] * env[x], zoomMin, zoomMax,
-                          r.height, dB, true, mdBrange, true);
-      r2[x] = GetWaveYPos(rms[x] * env[x], zoomMin, zoomMax,
-                          r.height, dB, true, mdBrange, true);
-
-      // Make sure the rms isn't larger than the waveform min/max
-      if (r1[x] > h1 - 1) {
-         r1[x] = h1 - 1;
-      }
-      if (r2[x] < h2 + 1) {
-         r2[x] = h2 + 1;
-      }
-      if (r2[x] > r1[x]) {
-         r2[x] = r1[x];
-      }
-
-      if (bl[x] <= -1) {
-         if (drawStripes) {
-            // TODO:unify with buffer drawing.
-            dc.SetPen((bl[x] % 2) ? muteSamplePen : samplePen);
-            for (int y = 0; y < r.height / 25 + 1; y++) {
-               // we are drawing over the buffer, but I think DrawLine takes care of this.
-               AColor::Line(dc,
-                            xx,
-                            r.y + 25 * y + (x /*+pixAnimOffset*/) % 25,
-                            xx, 
-                            r.y + 25 * y + (x /*+pixAnimOffset*/) % 25 + 6); //take the min so we don't draw past the edge
-            }
-         }
-         
-         // draw a dummy waveform - some kind of sinusoid.  We want to animate it so the user knows it's a dummy.  Use the second's unit of a get time function.
-         // Lets use a triangle wave for now since it's easier - I don't want to use sin() or make a wavetable just for this.
-         if (drawWaveform) {
-            int triX;
-            dc.SetPen(samplePen);
-            triX = fabs((double)((x + pixAnimOffset) % (2 * r.height)) - r.height) + r.height;
-            for (int y = 0; y < r.height; y++) {
-               if ((y + triX) % r.height == 0) {
-                  dc.DrawPoint(xx, r.y + y);
-               }
-            }
-         }
-      }
-      else {
-         AColor::Line(dc, xx, r.y + h2, xx, r.y + h1);
-      }
-   }
-
-   dc.SetPen(muted ? muteRmsPen : rmsPen);
-   for (int x = 0; x < r.width; x++) {
-      int xx = r.x + x;
-      if (bl[x] <= -1) {
-      }
-      else if (r1[x] != r2[x]) {
-         AColor::Line(dc, xx, r.y + r2[x], xx, r.y + r1[x]);
-      }
-   }
-
-   // Draw the clipping lines
-   if (clipcnt) {
-      dc.SetPen(muted ? muteClippedPen : clippedPen);
-      while (--clipcnt >= 0) {
-         int xx = clipped[clipcnt];
-         AColor::Line(dc, xx, r.y, xx, r.y + r.height);
-      }
-   }
-
-   if (mShowClipping) {
-      delete[] clipped;
-   }
-
-   delete [] r1;
-   delete [] r2;
-}
-
-void TrackArtist::DrawIndividualSamples(wxDC &dc, const wxRect &r,
-                                        float zoomMin, float zoomMax, bool dB,
-                                        WaveClip *clip,
-                                        double t0, double pps, double h,
-                                        bool drawSamples, bool showPoints, bool muted)
-{
-   double rate = clip->GetRate();
-   sampleCount s0 = (sampleCount) (t0 * rate + 0.5);
-   sampleCount slen = (sampleCount) (r.width * rate / pps + 0.5);
-   sampleCount snSamples = clip->GetNumSamples(); 
-   
-   slen += 4;
-
-   if (s0 > snSamples) {
-      return;
-   }
-
-   if (s0 + slen > snSamples) {
-      slen = snSamples - s0;
-   }
-
-   float *buffer = new float[slen];
-   clip->GetSamples((samplePtr)buffer, floatSample, s0, slen);
-
-   int *xpos = new int[slen];
-   int *ypos = new int[slen];
-   int *clipped = NULL;
-   int clipcnt = 0;
-   sampleCount s;
-
-   if (mShowClipping) {
-      clipped = new int[slen];
-   }
-
-   dc.SetPen(muted ? muteSamplePen : samplePen);
-   
-   for (s = 0; s < slen; s++) {
-      double tt = (s / rate);
-
-      // MB: (s0/rate - t0) is the distance from the left edge of the screen
-      //     to the first sample.
-      int xx = (int)rint((tt + s0 / rate - t0) * pps);
-      
-      if (xx < -10000) {
-         xx = -10000;
-      }
-      if (xx > 10000) {
-         xx = 10000;
-      }
-      
-      xpos[s] = xx;
-
-      // t0 + clip->GetOffset() is 'h' (the absolute time of the left edge) for 'r'.
-      tt = buffer[s] * clip->GetEnvelope()->GetValueAtX(xx + r.x, r, t0 + clip->GetOffset(), pps);
-      if (mShowClipping && (tt <= -MAX_AUDIO || tt >= MAX_AUDIO)) {
-         clipped[clipcnt++] = xx;
-      }
-      ypos[s] = GetWaveYPos(tt, zoomMin, zoomMax,
-                            r.height, dB, true, mdBrange, false);
-      if (ypos[s] < -1) {
-         ypos[s] = -1;
-      }
-      if (ypos[s] > r.height) {
-         ypos[s] = r.height;
-      }
-   }
-
-   // Draw lines
-   for (s = 0; s < slen - 1; s++) {
-      AColor::Line(dc,
-                   r.x + xpos[s], r.y + ypos[s],
-                   r.x + xpos[s + 1], r.y + ypos[s + 1]);
-   }
-
-   if (showPoints) {
-      // Draw points
-      int tickSize= drawSamples ? 4 : 3;// Bigger ellipses when draggable.
-      wxRect pr;
-      pr.width = tickSize;
-      pr.height = tickSize;
-      //different colour when draggable.
-      dc.SetBrush( drawSamples ? dragsampleBrush : sampleBrush);
-      for (s = 0; s < slen; s++) {
-         if (ypos[s] >= 0 && ypos[s] < r.height) {
-            pr.x = r.x + xpos[s] - tickSize/2;
-            pr.y = r.y + ypos[s] - tickSize/2;
-            dc.DrawEllipse(pr);
-         }
-      }
-   }
-
-   // Draw clipping
-   if (clipcnt) {
-      dc.SetPen(muted ? muteClippedPen : clippedPen);
-      while (--clipcnt >= 0) {
-         s = clipped[clipcnt];
-         AColor::Line(dc, r.x + s, r.y, r.x + s, r.y + r.height);
-      }
-   }
-   
-   if (mShowClipping) {
-      delete [] clipped;
-   }
-
-   delete[]buffer;
-   delete[]xpos;
-   delete[]ypos;
-}
-
-void TrackArtist::DrawEnvelope(wxDC &dc, const wxRect &r, const double env[],
-                               float zoomMin, float zoomMax, bool dB)
-{
-   int h = r.height;
-
-   dc.SetPen(AColor::envelopePen);
-
-   for (int x = 0; x < r.width; x++) {
-      int cenvTop = GetWaveYPos(env[x], zoomMin, zoomMax,
-                                h, dB, true, mdBrange, true);
-
-      int cenvBot = GetWaveYPos(-env[x], zoomMin, zoomMax,
-                                h, dB, true, mdBrange, true);
-
-      int envTop = GetWaveYPos(env[x], zoomMin, zoomMax,
-                               h, dB, true, mdBrange, false);
-
-      int envBot = GetWaveYPos(-env[x], zoomMin, zoomMax,
-                               h, dB, true, mdBrange, false);
-
-      // Make the collision at zero actually look solid
-      if (cenvBot - cenvTop < 9) {
-         int value = (int)((zoomMax / (zoomMax - zoomMin)) * h);
-         cenvTop = value - 4;
-         cenvBot = value + 4;
-      }
-
-      DrawEnvLine(dc, r, x, envTop, cenvTop, true);
-      DrawEnvLine(dc, r, x, envBot, cenvBot, false);
-   }
-}
-
-void TrackArtist::DrawEnvLine(wxDC &dc, const wxRect &r, int x, int y, int cy, bool top)
-{
-   int xx = r.x + x;
-   int yy = r.y + cy;
-
-   if (y < 0) {
-      if (x % 4 != 3) {
-         AColor::Line(dc, xx, yy, xx, yy + 3);
-      }
-   }
-   else if (y > r.height) {
-      if (x % 4 != 3) {
-         AColor::Line(dc, xx, yy - 3, xx, yy);
-      }
-   }
-   else {
-      if (top) {
-         AColor::Line(dc, xx, yy, xx, yy + 3);
-      }
-      else {
-         AColor::Line(dc, xx, yy - 3, xx, yy);
-      }
-   }
-}
-
-void TrackArtist::DrawWaveform(WaveTrack *track,
-                               wxDC & dc,
-                               const wxRect & r,
-                               const ViewInfo *viewInfo,
-                               bool drawEnvelope,
-                               bool drawSamples,
-                               bool drawSliders,
-                               bool dB,
-                               bool muted)
-{
-   DrawBackgroundWithSelection(&dc, r, track, blankSelectedBrush, blankBrush,
-         viewInfo->sel0, viewInfo->sel1, viewInfo->h, viewInfo->zoom);
-
-   for (WaveClipList::compatibility_iterator it = track->GetClipIterator(); it; it = it->GetNext())
-      DrawClipWaveform(track, it->GetData(), dc, r, viewInfo,
-                       drawEnvelope, drawSamples, drawSliders,
-                       dB, muted);
-
-   // Update cache for locations, e.g. cutlines and merge points
-   track->UpdateLocationsCache();
-   
-   for (int i = 0; i<track->GetNumCachedLocations(); i++) {
-      WaveTrack::Location loc = track->GetCachedLocation(i);
-      double x = (loc.pos - viewInfo->h) * viewInfo->zoom;
-      if (x >= 0 && x < r.width) {
-         dc.SetPen(*wxGREY_PEN);
-         AColor::Line(dc, (int) (r.x + x - 1), r.y, (int) (r.x + x - 1), r.y + r.height);
-         if (loc.typ == WaveTrack::locationCutLine) {
-            dc.SetPen(*wxRED_PEN);
-         }
-         else {
-            dc.SetPen(*wxBLACK_PEN);
-         }
-         AColor::Line(dc, (int) (r.x + x), r.y, (int) (r.x + x), r.y + r.height);
-         dc.SetPen(*wxGREY_PEN);
-         AColor::Line(dc, (int) (r.x + x + 1), r.y, (int) (r.x + x + 1), r.y + r.height);
-      }
-   }
-}
-
-void TrackArtist::DrawClipWaveform(WaveTrack *track,
-                                   WaveClip *clip,
-                                   wxDC & dc,
-                                   const wxRect & r,
-                                   const ViewInfo *viewInfo,
-                                   bool drawEnvelope,
-                                   bool drawSamples,
-                                   bool drawSliders,
-                                   bool dB,
-                                   bool muted)
-{
-#if PROFILE_WAVEFORM
-#   ifdef __WXMSW__
-   __time64_t tv0, tv1;
-   _time64(&tv0);
-#   else
-   struct timeval tv0, tv1;
-   gettimeofday(&tv0, NULL);
-#   endif
-#endif
-   double h = viewInfo->h;          //The horizontal position in seconds
-   double pps = viewInfo->zoom;     //points-per-second--the zoom level
-   double sel0 = viewInfo->sel0;    //left selection bound
-   double sel1 = viewInfo->sel1;    //right selection bound
-   double trackLen = clip->GetEndTime() - clip->GetStartTime();
-   double tOffset = clip->GetOffset();
-   double rate = clip->GetRate();
-   double sps = 1./rate;            //seconds-per-sample
-
-   //If the track isn't selected, make the selection empty
-   if (!track->GetSelected() && !track->IsSyncLockSelected()) {
-      sel0 = sel1 = 0.0;
-   }
-
-   //Some bookkeeping time variables:
-   double tstep = 1.0 / pps;                  // Seconds per point
-   double tpre = h - tOffset;                 // offset corrected time of
-                                              //  left edge of display
-   double tpost = tpre + (r.width * tstep);   // offset corrected time of
-                                              //  right edge of display
-
-   // Determine whether we should show individual samples
-   // or draw circular points as well
-   bool showIndividualSamples = (pps / rate > 0.5);   //zoomed in a lot
-   bool showPoints = (pps / rate > 3.0);              //zoomed in even more
-
-   // Calculate actual selection bounds so that t0 > 0 and t1 < the
-   // end of the track 
-
-   double t0 = (tpre >= 0.0 ? tpre : 0.0);
-   double t1 = (tpost < trackLen - sps * .99 ? tpost : trackLen - sps * .99);
-   if (showIndividualSamples) {
-      // adjustment so that the last circular point doesn't appear
-      // to be hanging off the end
-      t1 += 2. / pps;
-   }
-
-   // Make sure t1 (the right bound) is greater than 0
-   if (t1 < 0.0) {
-      t1 = 0.0;
-   }
-
-   // Make sure t1 is greater than t0
-   if (t0 > t1) {
-      t0 = t1;
-   }
-
-   // Calculate sample-based offset-corrected selection 
-
-   // Use the WaveTrack method to show what is selected and 'should' be copied, pasted etc.
-   sampleCount ssel0 = wxMax(0, track->TimeToLongSamples(sel0 - tOffset));
-   sampleCount ssel1 = wxMax(0, track->TimeToLongSamples(sel1 - tOffset));
-
-   //trim selection so that it only contains the actual samples
-   if (ssel0 != ssel1 && ssel1 > (sampleCount)(0.5 + trackLen * rate)) {
-      ssel1 = (sampleCount)(0.5 + trackLen * rate);
-   }
-
-   // The variable "mid" will be the rectangle containing the
-   // actual waveform, as opposed to any blank area before
-   // or after the track.
-   wxRect mid = r;
-
-   dc.SetPen(*wxTRANSPARENT_PEN);
-
-   // If the left edge of the track is to the right of the left
-   // edge of the display, then there's some blank area to the
-   // left of the track.  Reduce the "mid"
-   if (tpre < 0) {
-      double delta = r.width;
-      if (t0 < tpost) {
-         delta = (int) ((t0 - tpre) * pps);
-      }
-      mid.x += (int)delta;
-      mid.width -= (int)delta;
-   }
-
-   // If the right edge of the track is to the left of the the right
-   // edge of the display, then there's some blank area to the right
-   // of the track.  Reduce the "mid" rect by the
-   // size of the blank area.
-   if (tpost > t1) {
-      wxRect post = r;
-      if (t1 > tpre) {
-         post.x += (int) ((t1 - tpre) * pps);
-      }
-      post.width = r.width - (post.x - r.x);
-      mid.width -= post.width;
-   }
-
-   // The "mid" rect contains the part of the display actually
-   // containing the waveform.  If it's empty, we're done.
-   if (mid.width <= 0) {
-#if PROFILE_WAVEFORM
-#   ifdef __WXMSW__
-      _time64(&tv1);
-      double elapsed = _difftime64(tv1, tv0);
-#   else
-      gettimeofday(&tv1, NULL);
-      double elapsed =
-         (tv1.tv_sec + tv1.tv_usec*0.000001) -
-         (tv0.tv_sec + tv0.tv_usec*0.000001);
-#   endif
-      gWaveformTimeTotal += elapsed;
-      gWaveformTimeCount++;
-#endif
-
-     return;
-   }
-
-   // If we get to this point, the clip is actually visible on the
-   // screen, so remember the display rectangle.
-   clip->SetDisplayRect(mid);
-
-   // The bounds (controlled by vertical zooming; -1.0...1.0
-   // by default)
-   float zoomMin, zoomMax;
-   track->GetDisplayBounds(&zoomMin, &zoomMax);
-
-   // Arrays containing the shape of the waveform - each array
-   // has one value per pixel.
-   float *min = new float[mid.width];
-   float *max = new float[mid.width];
-   float *rms = new float[mid.width];
-   sampleCount *where = new sampleCount[mid.width + 1];
-   int *bl = new int[mid.width];
-   bool isLoadingOD = false;//true if loading on demand block in sequence.
-
-   // The WaveClip class handles the details of computing the shape
-   // of the waveform.  The only way GetWaveDisplay will fail is if
-   // there's a serious error, like some of the waveform data can't
-   // be loaded.  So if the function returns false, we can just exit.
-   if (!clip->GetWaveDisplay(min, max, rms, bl, where,
-                             mid.width, t0, pps, isLoadingOD)) {
-      delete[] min;
-      delete[] max;
-      delete[] rms;
-      delete[] where;
-      delete[] bl;
-      return;
-   }
-
-   // Get the values of the envelope corresponding to each pixel
-   // in the display, and use these to compute the height of the
-   // track at each pixel
-
-   double *envValues = new double[mid.width];
-   clip->GetEnvelope()->GetValues(envValues, mid.width, t0 + tOffset, tstep);
-
-   // Draw the background of the track, outlining the shape of
-   // the envelope and using a colored pen for the selected
-   // part of the waveform
-   DrawWaveformBackground(dc, mid, envValues, zoomMin, zoomMax, dB,
-                          where, ssel0, ssel1, drawEnvelope,
-                          !track->GetSelected());
-
-   if (!showIndividualSamples) {
-      DrawMinMaxRMS(dc, mid, envValues, zoomMin, zoomMax, dB,
-                    min, max, rms, bl, isLoadingOD, muted);
-   }
-   else {
-      DrawIndividualSamples(dc, mid, zoomMin, zoomMax, dB,
-                            clip, t0, pps, h,
-                            drawSamples, showPoints, muted);
-   }
-
-   if (drawEnvelope) {
-      DrawEnvelope(dc, mid, envValues, zoomMin, zoomMax, dB);
-      clip->GetEnvelope()->DrawPoints(dc, r, h, pps, dB, zoomMin, zoomMax);
-   }
-
-   delete[] envValues;
-   delete[] min;
-   delete[] max;
-   delete[] rms;
-   delete[] where;
-   delete[] bl;
-   
-   // Draw arrows on the left side if the track extends to the left of the
-   // beginning of time.  :)
-   if (h == 0.0 && tOffset < 0.0) {
-      DrawNegativeOffsetTrackArrows(dc, r);
-   }
-
-   if (drawSliders) {
-      DrawTimeSlider(track, dc, r, viewInfo, true);  // directed right
-      DrawTimeSlider(track, dc, r, viewInfo, false); // directed left
-   }
-
-   // Draw clip edges
-   dc.SetPen(*wxGREY_PEN);
-   if (tpre < 0) {
-      AColor::Line(dc,
-                   mid.x - 1, mid.y,
-                   mid.x - 1, mid.y + r.height);
-   }
-   if (tpost > t1) {
-      AColor::Line(dc,
-                   mid.x + mid.width, mid.y,
-                   mid.x + mid.width, mid.y + r.height);
-   }
-
-#if PROFILE_WAVEFORM
-#   ifdef __WXMSW__
-   _time64(&tv1);
-   double elapsed = _difftime64(tv1, tv0);
-#   else
-   gettimeofday(&tv1, NULL);
-   double elapsed =
-      (tv1.tv_sec + tv1.tv_usec*0.000001) -
-      (tv0.tv_sec + tv0.tv_usec*0.000001);
-#   endif
-   gWaveformTimeTotal += elapsed;
-   gWaveformTimeCount++;
-   wxPrintf(wxT("Avg waveform drawing time: %f\n"),
-          gWaveformTimeTotal / gWaveformTimeCount);
-#endif
-}
-
-
-void TrackArtist::DrawTimeSlider(WaveTrack *track,
-                                 wxDC & dc,
-                                 const wxRect & r,
-                                 const ViewInfo *viewInfo,
-                                 bool rightwards)
-{
-   const int border = 3; // 3 pixels all round.
-   const int width = 6; // width of the drag box.
-   const int taper = 6; // how much the box tapers by.
-   const int barSpacing = 4; // how far apart the bars are.
-   const int barWidth = 3;
-   const int xFlat = 3;
-
-   //Enough space to draw in?
-   if (r.height <= ((taper+border + barSpacing) * 2)) {
-      return;
-   }
-   if (r.width <= (width * 2 + border * 3)) {
-      return;
-   }
-
-   // The draggable box is tapered towards the direction you drag it.
-   int leftTaper  = rightwards ? 0 : 6;
-   int rightTaper = rightwards ? 6 : 0;
-
-   int xLeft = rightwards ? (r.x + border - 2)
-                          : (r.x + r.width + 1 - (border + width));
-   int yTop  = r.y + border;
-   int yBot  = r.y + r.height - border - 1;
-
-   AColor::Light(&dc, false);
-   AColor::Line(dc, xLeft,         yBot - leftTaper, xLeft,         yTop + leftTaper);
-   AColor::Line(dc, xLeft,         yTop + leftTaper, xLeft + xFlat, yTop);
-   AColor::Line(dc, xLeft + xFlat, yTop,             xLeft + width, yTop + rightTaper);
-
-   AColor::Dark(&dc, false);
-   AColor::Line(dc, xLeft + width,         yTop + rightTaper, xLeft + width,       yBot - rightTaper);
-   AColor::Line(dc, xLeft + width,         yBot - rightTaper, xLeft + width-xFlat, yBot);
-   AColor::Line(dc, xLeft + width - xFlat, yBot,              xLeft,               yBot - leftTaper);
-
-   int firstBar = yTop + taper + taper / 2;
-   int nBars    = (yBot - yTop - taper * 3) / barSpacing + 1;
-   xLeft += (width - barWidth + 1) / 2;
-   int y;
-   int i;
-
-   AColor::Light(&dc, false);
-   for (i = 0;i < nBars; i++) {
-      y = firstBar + barSpacing * i;
-      AColor::Line(dc, xLeft, y, xLeft + barWidth, y);
-   }
-   AColor::Dark(&dc, false);
-   for(i = 0;i < nBars; i++){
-      y = firstBar + barSpacing * i + 1;
-      AColor::Line(dc, xLeft, y, xLeft + barWidth, y);
-   }
-}
-
-
-void TrackArtist::DrawSpectrum(WaveTrack *track,
-                               wxDC & dc,
-                               const wxRect & r,
-                               const ViewInfo *viewInfo,
-                               bool autocorrelation,
-                               bool logF)
-{
-   DrawBackgroundWithSelection(&dc, r, track, blankSelectedBrush, blankBrush,
-         viewInfo->sel0, viewInfo->sel1, viewInfo->h, viewInfo->zoom);
-
-   if(!viewInfo->bUpdateTrackIndicator && viewInfo->bIsPlaying) {
-      // BG: Draw (undecorated) waveform instead of spectrum
-      DrawWaveform(track, dc, r, viewInfo, false, false, false, false, false);
-      /*
-      // BG: uncomment to draw grey instead of spectrum
-      dc.SetBrush(unselectedBrush);
-      dc.SetPen(unselectedPen);
-      dc.DrawRectangle(r);
-      */
-      return;
-   }
-
-   for (WaveClipList::compatibility_iterator it = track->GetClipIterator(); it; it = it->GetNext()) {
-      DrawClipSpectrum(track, it->GetData(), dc, r, viewInfo, autocorrelation, logF);
-   }
-}
-
-float sumFreqValues(float *freq, int x0, float bin0, float bin1)
-{
-   float value;
-   if (int(bin1) == int(bin0)) {
-      value = freq[x0+int(bin0)];
-   } else {
-      float binwidth= bin1 - bin0;
-      value = freq[x0 + int(bin0)] * (1.f - bin0 + (int)bin0);
-
-      bin0 = 1 + int (bin0);
-      while (bin0 < int(bin1)) {
-         value += freq[x0 + int(bin0)];
-         bin0 += 1.0;
-      }
-      value += freq[x0 + int(bin1)] * (bin1 - int(bin1));
-      value /= binwidth;
-   }
-   return value;
-}
-
-void TrackArtist::DrawClipSpectrum(WaveTrack *track,
-                                   WaveClip *clip,
-                                   wxDC & dc,
-                                   const wxRect & r,
-                                   const ViewInfo *viewInfo,
-                                   bool autocorrelation,
-                                   bool logF)
-{
-#if PROFILE_WAVEFORM
-#  ifdef __WXMSW__
-   __time64_t tv0, tv1;
-   _time64(&tv0);
-#  else
-   struct timeval tv0, tv1;
-   gettimeofday(&tv0, NULL);
-#  endif
-#endif
-   double h = viewInfo->h;
-   double pps = viewInfo->zoom;
-   double sel0 = viewInfo->sel0;
-   double sel1 = viewInfo->sel1;
-
-   double tOffset = clip->GetOffset();
-   double rate = clip->GetRate();
-   double sps = 1./rate;
-
-   int range = gPrefs->Read(wxT("/Spectrum/Range"), 80L);
-   int gain = gPrefs->Read(wxT("/Spectrum/Gain"), 20L);
-
-   if (!track->GetSelected())
-      sel0 = sel1 = 0.0;
-
-   double tpre = h - tOffset;
-   double tstep = 1.0 / pps;
-   double tpost = tpre + (r.width * tstep);
-   double trackLen = clip->GetEndTime() - clip->GetStartTime();
-
-   bool showIndividualSamples = (pps / rate > 0.5);   //zoomed in a lot
-   double t0 = (tpre >= 0.0 ? tpre : 0.0);
-   double t1 = (tpost < trackLen - sps*.99 ? tpost : trackLen - sps*.99);
-   if(showIndividualSamples) t1+=2./pps; // for display consistency
-   // with Waveform display
-
-   // Make sure t1 (the right bound) is greater than 0
-   if (t1 < 0.0)
-      t1 = 0.0;
-
-   // Make sure t1 is greater than t0
-   if (t0 > t1)
-      t0 = t1;
-
-   sampleCount ssel0 = wxMax(0, sampleCount((sel0 - tOffset) * rate + .99));
-   sampleCount ssel1 = wxMax(0, sampleCount((sel1 - tOffset) * rate + .99));
-
-   //trim selection so that it only contains the actual samples
-   if (ssel0 != ssel1 && ssel1 > (sampleCount)(0.5+trackLen*rate))
-      ssel1 = (sampleCount)(0.5+trackLen*rate);
-
-   // The variable "mid" will be the rectangle containing the
-   // actual waveform, as opposed to any blank area before
-   // or after the track.
-   wxRect mid = r;
-
-   dc.SetPen(*wxTRANSPARENT_PEN);
-
-   // If the left edge of the track is to the right of the left
-   // edge of the display, then there's some blank area to the
-   // left of the track.  Reduce the "mid"
-   // rect by size of the blank area.
-   if (tpre < 0) {
-      // Fill in the area to the left of the track
-      wxRect pre = r;
-      if (t0 < tpost)
-         pre.width = (int) ((t0 - tpre) * pps);
-
-      // Offset the rectangle containing the waveform by the width
-      // of the area we just erased.
-      mid.x += pre.width;
-      mid.width -= pre.width;
-   }
-
-   // If the right edge of the track is to the left of the the right
-   // edge of the display, then there's some blank area to the right
-   // of the track.  Reduce the "mid" rect by the
-   // size of the blank area.
-   if (tpost > t1) {
-      wxRect post = r;
-      if (t1 > tpre)
-         post.x += (int) ((t1 - tpre) * pps);
-      post.width = r.width - (post.x - r.x);
-
-      // Reduce the rectangle containing the waveform by the width
-      // of the area we just erased.
-      mid.width -= post.width;
-   }
-
-   // The "mid" rect contains the part of the display actually
-   // containing the waveform.  If it's empty, we're done.
-   if (mid.width <= 0) {
-#if PROFILE_WAVEFORM
-#   ifdef __WXMSW__
-      _time64(&tv1);
-      double elapsed = _difftime64(tv1, tv0);
-#   else
-      gettimeofday(&tv1, NULL);
-      double elapsed =
-         (tv1.tv_sec + tv1.tv_usec*0.000001) -
-         (tv0.tv_sec + tv0.tv_usec*0.000001);
-#   endif
-      gWaveformTimeTotal += elapsed;
-      gWaveformTimeCount++;
-#endif
-      return;
-   }
-
-   // We draw directly to a bit image in memory,
-   // and then paint this directly to our offscreen
-   // bitmap.  Note that this could be optimized even
-   // more, but for now this is not bad.  -dmazzoni
-   wxImage *image = new wxImage((int) mid.width, (int) mid.height);
-   if (!image)return;
-   unsigned char *data = image->GetData();
-
-   int windowSize = GetSpectrumWindowSize();
-   int half = windowSize/2;
-   float *freq = new float[mid.width * half];
-   sampleCount *where = new sampleCount[mid.width+1];
-
-   bool updated = clip->GetSpectrogram(freq, where, mid.width,
-                              t0, pps, autocorrelation);
-   int ifreq = lrint(rate/2);
-
-   int maxFreq;
-   if (!logF)
-      maxFreq = GetSpectrumMaxFreq(ifreq);
-   else
-      maxFreq = GetSpectrumLogMaxFreq(ifreq);
-   if(maxFreq > ifreq)
-      maxFreq = ifreq;
-
-   int minFreq;
-   if (!logF) {
-      minFreq = GetSpectrumMinFreq(0);
-      if(minFreq < 0)
-         minFreq = 0;
-   }
-   else {
-      minFreq = GetSpectrumLogMinFreq(ifreq/1000.0);
-      if(minFreq < 1)
-         minFreq = ifreq/1000.0;
-   }
-
-   bool usePxCache = false;
-
-   if( !updated && clip->mSpecPxCache->valid && (clip->mSpecPxCache->len == mid.height * mid.width) 
-#ifdef EXPERIMENTAL_FFT_Y_GRID
-   && mFftYGrid==fftYGridOld
-#endif //EXPERIMENTAL_FFT_Y_GRID
-#ifdef EXPERIMENTAL_FIND_NOTES
-   && mFftFindNotes==fftFindNotesOld
-   && mFindNotesMinA==findNotesMinAOld
-   && mNumberOfMaxima==findNotesNOld
-   && mFindNotesQuantize==findNotesQuantizeOld
-#endif
-   ) {
-      usePxCache = true;
-   }
-   else {
-      delete clip->mSpecPxCache;
-      clip->mSpecPxCache = new SpecPxCache(mid.width * mid.height);
-      usePxCache = false;
-      clip->mSpecPxCache->valid = true;
-#ifdef EXPERIMENTAL_FIND_NOTES
-      fftFindNotesOld=mFftFindNotes;
-      findNotesMinAOld=mFindNotesMinA;
-      findNotesNOld=mNumberOfMaxima;
-      findNotesQuantizeOld=mFindNotesQuantize;
-#endif
-   }
-
-   int minSamples = int ((double)minFreq * (double)windowSize / rate + 0.5);   // units are fft bins
-   int maxSamples = int ((double)maxFreq * (double)windowSize / rate + 0.5);
-   int temp = sizeof(int);
-   float binPerPx = float(maxSamples - minSamples) / float(mid.height);
-
-   int x = 0;
-   sampleCount w1 = (sampleCount) ((t0*rate + x *rate *tstep) + .5);
- 
-   const float 
-//      e=exp(1.0f), 
-      f=rate/2.0f/half, 
-      lmin=logf(float(minFreq)),
-      lmax=logf(float(maxFreq)),
-#ifdef EXPERIMENTAL_FIND_NOTES
-      log2=logf(2.0f),
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-      lmins=logf(float(minFreq)/(mFftSkipPoints+1)),
-      lmaxs=logf(float(maxFreq)/(mFftSkipPoints+1)),
-#else //!EXPERIMENTAL_FFT_SKIP_POINTS
-      lmins=lmin,
-      lmaxs=lmax,
-#endif //EXPERIMENTAL_FFT_SKIP_POINTS
-#endif //EXPERIMENTAL_FIND_NOTES
-      scale=lmax-lmin /*, 
-      expo=exp(scale)*/ ;
-
-#ifdef EXPERIMENTAL_FFT_Y_GRID
-   const float
-      scale2=(lmax-lmin)/log2,
-      lmin2=lmin/log2;
-
-   bool *yGrid;
-   yGrid=new bool[mid.height];
-   for (int y = 0; y < mid.height; y++) {
-      float n =(float(y  )/mid.height*scale2-lmin2)*12; 
-      float n2=(float(y+1)/mid.height*scale2-lmin2)*12; 
-      float f =float(minFreq)/(mFftSkipPoints+1)*powf(2.0f, n /12.0f+lmin2);
-      float f2=float(minFreq)/(mFftSkipPoints+1)*powf(2.0f, n2/12.0f+lmin2);
-      n =logf(f /440)/log2*12;
-      n2=logf(f2/440)/log2*12;
-      if (floor(n) < floor(n2))
-         yGrid[y]=true;
-      else
-         yGrid[y]=false;
-   }
-#endif //EXPERIMENTAL_FFT_Y_GRID
-
-#ifdef EXPERIMENTAL_FIND_NOTES
-   int maxima[128];
-   float maxima0[128], maxima1[128];
-   const float 
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-      f2bin = half/(rate/2.0f/(mFftSkipPoints+1)),
-#else //!EXPERIMENTAL_FFT_SKIP_POINTS
-      f2bin = half/(rate/2.0f),
-#endif //EXPERIMENTAL_FFT_SKIP_POINTS
-      bin2f = 1.0f/f2bin,
-      minDistance = powf(2.0f, 2.0f/12.0f),
-      i0=expf(lmin)/f,
-      i1=expf(scale+lmin)/f,
-      minColor=0.0f;
-   const int maxTableSize=1024;
-   int *indexes=new int[maxTableSize];
-#endif //EXPERIMENTAL_FIND_NOTES
-
-   while (x < mid.width) 
-   {
-      sampleCount w0 = w1;
-      w1 = (sampleCount) ((t0*rate + (x+1) *rate *tstep) + .5);
-      if (!logF)
-      {
-         for (int yy = 0; yy < mid.height; yy++) {
-            bool selflag = (ssel0 <= w0 && w1 < ssel1);
-            unsigned char rv, gv, bv;
-            float value;
-
-            if(!usePxCache) {
-               float bin0 = float (yy) * binPerPx + minSamples;
-               float bin1 = float (yy + 1) * binPerPx + minSamples;
-
-
-               if (int (bin1) == int (bin0))
-                  value = freq[half * x + int (bin0)];
-               else {
-                  float binwidth= bin1 - bin0;
-                  value = freq[half * x + int (bin0)] * (1.f - bin0 + (int)bin0);
-
-                  bin0 = 1 + int (bin0);
-                  while (bin0 < int (bin1)) {
-                     value += freq[half * x + int (bin0)];
-                     bin0 += 1.0;
-                  }
-
-                  // Do not reference past end of freq array.
-                  if (int(bin1) >= half) {
-                     bin1 -= 1.0;
-                  }
-
-                  value += freq[half * x + int (bin1)] * (bin1 - int (bin1));
-
-                  value /= binwidth;
-               }
-
-               if (!autocorrelation) {
-                  // Last step converts dB to a 0.0-1.0 range
-                  value = (value + range + gain) / (double)range;
-               }
-
-               if (value > 1.0)
-                  value = float(1.0);
-               if (value < 0.0)
-                  value = float(0.0);
-               clip->mSpecPxCache->values[x * mid.height + yy] = value;
-            }
-            else
-               value = clip->mSpecPxCache->values[x * mid.height + yy];
-
-            GetColorGradient(value, selflag, mIsGrayscale, &rv, &gv, &bv);
-
-            int px = ((mid.height - 1 - yy) * mid.width + x) * 3;
-            data[px++] = rv;
-            data[px++] = gv;
-            data[px] = bv;
-         }
-      }
-      else //logF
-      {
-         bool selflag = (ssel0 <= w0 && w1 < ssel1);
-         unsigned char rv, gv, bv;
-         float value;
-         int x0=x*half;
-
-#ifdef EXPERIMENTAL_FIND_NOTES
-         int maximas=0;
-         if (!usePxCache && mFftFindNotes) {
-            for (int i = maxTableSize-1; i >= 0; i--)
-               indexes[i]=-1;
-            
-            // Build a table of (most) values, put the index in it.
-            for (int i = int(i0); i < int(i1); i++) {
-               float freqi=freq[x0+int(i)];
-               int value=int((freqi+gain+range)/range*(maxTableSize-1));
-               if (value < 0)
-                  value=0;
-               if (value >= maxTableSize)
-                  value=maxTableSize-1;
-               indexes[value]=i;
-            }
-            // Build from the indices an array of maxima.
-            for (int i = maxTableSize-1; i >= 0; i--) {
-               int index=indexes[i];
-               if (index >= 0) {
-                  float freqi=freq[x0+index];
-                  if (freqi < mFindNotesMinA) 
-                     break;
-
-                  bool ok=true;
-                  for (int m=0; m < maximas; m++) {
-                     // Avoid to store very close maxima.
-                     float maxm = maxima[m];
-                     if (maxm/index < minDistance && index/maxm < minDistance) {
-                        ok=false;
-                        break;
-                     }
-                  }
-                  if (ok) {
-                     maxima[maximas++] = index;
-                     if (maximas >= mNumberOfMaxima)
-                        break;
-                  }
-               }
-            }
-
-// The f2pix helper macro converts a frequency into a pixel coordinate.
-#define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*mid.height
-
-            // Possibly quantize the maxima frequencies and create the pixel block limits.
-            for (int i=0; i < maximas; i++) {
-               int index=maxima[i];
-               float f = float(index)*bin2f;
-               if (mFindNotesQuantize)
-               {  f = expf(int(log(f/440)/log2*12-0.5)/12.0f*log2)*440;
-                  maxima[i] = f*f2bin;
-               }
-               float f0 = expf((log(f/440)/log2*24-1)/24.0f*log2)*440;
-               maxima0[i] = f2pix(f0);
-               float f1 = expf((log(f/440)/log2*24+1)/24.0f*log2)*440;
-               maxima1[i] = f2pix(f1);
-            }
-         }
-         int it=0;
-         int oldBin0=-1;
-         bool inMaximum = false;
-#endif //EXPERIMENTAL_FIND_NOTES
-
-         double yy2_base=exp(lmin)/f;
-         float yy2 = yy2_base;
-         double exp_scale_per_height = exp(scale/mid.height);
-         for (int yy = 0; yy < mid.height; yy++) {
-            if(!usePxCache) {
-               if (int(yy2)>=half)
-                  yy2=half-1;
-               if (yy2<0)
-                  yy2=0;
-               float bin0 = float(yy2);
-               yy2_base *= exp_scale_per_height;
-               float yy3 = yy2_base;
-               if (int(yy3)>=half)
-                  yy3=half-1;
-               if (yy3<0)
-                  yy3=0;
-               float bin1 = float(yy3);
-
-#ifdef EXPERIMENTAL_FIND_NOTES
-               if (mFftFindNotes) {
-                  if (it < maximas) {
-                     float i0=maxima0[it];
-                     if (yy >= i0)
-                        inMaximum = true;
-
-                     if (inMaximum) {
-                        float i1=maxima1[it];
-                        if (yy+1 <= i1) {
-                           value=sumFreqValues(freq, x0, bin0, bin1);
-                           if (value < mFindNotesMinA)
-                              value = minColor;
-                           else
-                              value = (value + gain + range) / (double)range;
-                        } else {
-                           it++;
-                           inMaximum = false;
-                           value = minColor;
-                        }
-                     } else {
-                        value = minColor;
-                     }
-                  } else
-                     value = minColor;
-               } else
-#endif //EXPERIMENTAL_FIND_NOTES
-               {
-                  value=sumFreqValues(freq, x0, bin0, bin1);
-                  if (!autocorrelation) {
-                     // Last step converts dB to a 0.0-1.0 range
-                     value = (value + gain + range) / (double)range;
-                  }
-               }
-               if (value > 1.0)
-                  value = float(1.0);
-               if (value < 0.0)
-                  value = float(0.0);
-               clip->mSpecPxCache->values[x * mid.height + yy] = value;
-               yy2 = yy2_base;
-            }
-            else
-               value = clip->mSpecPxCache->values[x * mid.height + yy];
-
-            GetColorGradient(value, selflag, mIsGrayscale, &rv, &gv, &bv);
-
-#ifdef EXPERIMENTAL_FFT_Y_GRID
-            if (mFftYGrid && yGrid[yy]) {
-               rv /= 1.1f;
-               gv /= 1.1f;
-               bv /= 1.1f;
-            }
-#endif //EXPERIMENTAL_FFT_Y_GRID
-
-            int px = ((mid.height - 1 - yy) * mid.width + x) * 3;
-            data[px++] = rv;
-            data[px++] = gv;
-            data[px] = bv;
-         }
-      }
-      x++;
-   }
-
-   // If we get to this point, the clip is actually visible on the
-   // screen, so remember the display rectangle.
-   clip->SetDisplayRect(mid);
-
-   wxBitmap converted = wxBitmap(*image);
-
-   wxMemoryDC memDC;
-
-   memDC.SelectObject(converted);
-
-   dc.Blit(mid.x, mid.y, mid.width, mid.height, &memDC, 0, 0, wxCOPY, FALSE);
-
-   delete image;
-   delete[] where;
-   delete[] freq;
-#ifdef EXPERIMENTAL_FFT_Y_GRID
-   delete[] yGrid;
-#endif //EXPERIMENTAL_FFT_Y_GRID
-#ifdef EXPERIMENTAL_FIND_NOTES
-   delete[] indexes;
-#endif //EXPERIMENTAL_FIND_NOTES
-}
-
-void TrackArtist::InvalidateSpectrumCache(TrackList *tracks)
-{
-   TrackListOfKindIterator iter(Track::Wave, tracks);
-   for (Track *t = iter.First(); t; t = iter.Next()) {
-      InvalidateSpectrumCache((WaveTrack *)t);
-   }
-}
-
-void TrackArtist::InvalidateSpectrumCache(WaveTrack *track)
-{
-   WaveClipList::compatibility_iterator it;
-   for (it = track->GetClipIterator(); it; it = it->GetNext()) {
-      it->GetData()->mSpecPxCache->valid = false;
-   }
-}
-
-
-#ifdef USE_MIDI
-/*
-Note: recall that Allegro attributes end in a type identifying letter.
-
-In addition to standard notes, an Allegro_Note can denote a graphic.
-A graphic is a note with a loud of zero (for quick testing) and an
-attribute named "shapea" set to one of the following atoms:
-    line
-        from (time, pitch) to (time+dur, y1r), where y1r is an
-          attribute
-    rectangle
-        from (time, pitch) to (time+dur, y1r), where y1r is an
-          attribute
-    triangle
-        coordinates are (time, pitch), (x1r, y1r), (x2r, y2r)
-        dur must be the max of x1r-time, x2r-time
-    polygon
-        coordinates are (time, pitch), (x1r, y1r), (x2r, y2r),
-          (x3r, y3r), ... are coordinates (since we cannot represent
-          arrays as attribute values, we just generate as many
-          attribute names as we need)
-        dur must be the max of xNr-time for all N
-    oval
-        similar to rectangle
-        Note: this oval has horizontal and vertical axes only
-    text
-        drawn at (time, pitch)
-        duration should be zero (text is clipped based on time and duration,
-          NOT based on actual coordinates)
-
-and optional attributes as follows:
-    linecolori is 0x00rrggbb format color for line or text foreground
-    fillcolori is 0x00rrggbb format color for fill or text background
-    linethicki is line thickness in pixels, 0 for no line
-    filll is true to fill rectangle or draw text background (default is false)
-    fonta is one of ['roman', 'swiss', 'modern'] (font, otherwise use default)
-    weighta may be 'bold' (font) (default is normal)
-    sizei is font size (default is 8)
-    justifys is a string containing two letters, a horizontal code and a
-      vertical code. The horizontal code is as follows:
-        l: the coordinate is to the left of the string (default)
-        c: the coordinate is at the center of the string
-        r: the coordinate is at the right of the string
-      The vertical code is as follows:
-        t: the coordinate is at the top of the string
-        c: the coordinate is at the center of the string
-        b: the coordinate is at the bottom of the string
-        d: the coordinate is at the baseline of the string (default)
-      Thus, -justifys:"lt" places the left top of the string at the point
-        given by (pitch, time). The default value is "ld".
-*/
-
-/* Declare Static functions */
-static const char *IsShape(Alg_note_ptr note);
-static double LookupRealAttribute(Alg_note_ptr note, Alg_attribute attr, double def);
-static long LookupIntAttribute(Alg_note_ptr note, Alg_attribute attr, long def);
-static bool LookupLogicalAttribute(Alg_note_ptr note, Alg_attribute attr, bool def);
-static const char *LookupStringAttribute(Alg_note_ptr note, Alg_attribute attr, const char *def);
-static const char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def);
-//static int PITCH_TO_Y(double p, int bottom);
-
-// returns NULL if note is not a shape,
-// returns atom (string) value of note if note is a shape
-const char *IsShape(Alg_note_ptr note)
-{
-  Alg_parameters_ptr parameters = note->parameters;
-  while (parameters) {
-    if (strcmp(parameters->parm.attr_name(), "shapea") == 0) {
-      return parameters->parm.a;
-    }
-    parameters = parameters->next;
-  }
-  return NULL;
-}
-
-// returns value of attr, or default if not found
-double LookupRealAttribute(Alg_note_ptr note, Alg_attribute attr, double def)
-{
-  Alg_parameters_ptr parameters = note->parameters;
-  while (parameters) {
-    if (parameters->parm.attr_name() == attr + 1 &&
-        parameters->parm.attr_type() == 'r') {
-      return parameters->parm.r;
-    }
-    parameters = parameters->next;
-  }
-  return def;
-}
-
-// returns value of attr, or default if not found
-long LookupIntAttribute(Alg_note_ptr note, Alg_attribute attr, long def)
-{
-  Alg_parameters_ptr parameters = note->parameters;
-  while (parameters) {
-    if (parameters->parm.attr_name() == attr + 1 &&
-        parameters->parm.attr_type() == 'i') {
-      return parameters->parm.i;
-    }
-    parameters = parameters->next;
-  }
-  return def;
-}
-
-// returns value of attr, or default if not found
-bool LookupLogicalAttribute(Alg_note_ptr note, Alg_attribute attr, bool def)
-{
-  Alg_parameters_ptr parameters = note->parameters;
-  while (parameters) {
-    if (parameters->parm.attr_name() == attr + 1 &&
-        parameters->parm.attr_type() == 'l') {
-      return parameters->parm.l;
-    }
-    parameters = parameters->next;
-  }
-  return def;
-}
-
-// returns value of attr, or default if not found
-const char *LookupStringAttribute(Alg_note_ptr note, Alg_attribute attr, const char *def)
-{
-  Alg_parameters_ptr parameters = note->parameters;
-  while (parameters) {
-    if (parameters->parm.attr_name() == attr + 1 &&
-        parameters->parm.attr_type() == 's') {
-      return parameters->parm.s;
-    }
-    parameters = parameters->next;
-  }
-  return def;
-}
-
-// returns value of attr, or default if not found
-const char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def)
-{
-  Alg_parameters_ptr parameters = note->parameters;
-  while (parameters) {
-    if (parameters->parm.attr_name() == attr + 1 &&
-        parameters->parm.attr_type() == 'a') {
-      return parameters->parm.s;
-    }
-    parameters = parameters->next;
-  }
-  return def;
-}
-#endif // USE_MIDI
-
-#define TIME_TO_X(t) (r.x + (int) (((t) - h) * pps))
-#define X_TO_TIME(xx) (((xx) - r.x) / pps + h)
-
-// CLIP(x) changes x to lie between +/- CLIP_MAX due to graphics display problems
-//  with very large coordinate values (this happens when you zoom in very far)
-//  This will cause incorrect things to be displayed, but at these levels of zoom
-//  you will only see a small fraction of the overall shape. Note that rectangles
-//  and lines are clipped in a way that preserves correct graphics, so in
-//  particular, line plots will be correct at any zoom (limited by floating point
-//  precision).
-#define CLIP_MAX 16000
-#define CLIP(x) { long c = (x); if (c < -CLIP_MAX) c = -CLIP_MAX; \
-                  if (c > CLIP_MAX) c = CLIP_MAX; (x) = c; }
-
-#define RED(i) ( unsigned char )( (((i) >> 16) & 0xff) )
-#define GREEN(i) ( unsigned char )( (((i) >> 8) & 0xff) )
-#define BLUE(i) ( unsigned char )( ((i) & 0xff) )
-
-//#define PITCH_TO_Y(p) (r.y + r.height - int(pitchht * ((p) + 0.5 - pitch0) + 0.5))
-
-#ifdef USE_MIDI
-
-/*
-int PitchToY(double p, int bottom)
-{
-   int octave = (((int) (p + 0.5)) / 12);
-   int n = ((int) (p + 0.5)) % 12;
-   
-   return IPITCH_TO_Y((int) (p + 0.5));
-   // was: bottom - octave * octaveHeight - notePos[n] - 4;
-}
-*/
-
-/* DrawNoteBackground is called by DrawNoteTrack twice: once to draw
-   the unselected background, and once to draw the selected background.
-   The selected background is the same except for the horizontal range
-   and the colors. The background rectangle region is given by r; the
-   selected region is given by sel. The first time this is called,
-   sel is equal to r, and the entire region is drawn with unselected
-   background colors.
- */
-void TrackArtist::DrawNoteBackground(NoteTrack *track, wxDC &dc, 
-                                     const wxRect &r, const wxRect &sel, 
-                                     const ViewInfo *viewInfo,
-                                     const wxBrush &wb, const wxPen &wp,
-                                     const wxBrush &bb, const wxPen &bp,
-                                     const wxPen &mp)
-{
-   dc.SetBrush(wb);
-   dc.SetPen(wp);
-   dc.DrawRectangle(sel); // fill rectangle with white keys background
-   double h = viewInfo->h;
-   double pps = viewInfo->zoom;
-
-   int left = TIME_TO_X(track->GetOffset());
-   if (left < sel.x) left = sel.x; // clip on left
-
-   int right = TIME_TO_X(track->GetOffset() + track->mSeq->get_real_dur());
-   if (right > sel.x + sel.width) right = sel.x + sel.width; // clip on right
-
-   // need overlap between MIDI data and the background region
-   if (left >= right) return;
-
-   dc.SetBrush(bb);
-   int octave = 0;
-   // obottom is the window coordinate of octave divider line
-   int obottom = track->GetOctaveBottom(octave);
-   // eOffset is for the line between E and F; there's another line
-   // between B and C, hence the offset of 2 for two line thicknesses
-   int eOffset = track->GetPitchHeight() * 5 + 2;
-   while (obottom > r.y + track->GetNoteMargin() + 3) {
-      // draw a black line separating octaves if this octave botton is visible
-      if (obottom < r.y + r.height - track->GetNoteMargin()) {
-         dc.SetPen(*wxBLACK_PEN);
-         // obottom - 1 because obottom is at the bottom of the line
-         AColor::Line(dc, left, obottom - 1, right, obottom - 1);
-      }
-      dc.SetPen(bp);
-      // draw a black-key stripe colored line separating E and F if visible
-      if (obottom - eOffset > r.y && obottom - eOffset < r.y + r.height) {
-         AColor::Line(dc, left, obottom - eOffset, 
-                          right, obottom - eOffset);
-      }
-
-      // draw visible black key lines
-      wxRect br;
-      br.x = left;
-      br.width = right - left;
-      br.height = track->GetPitchHeight();
-      for (int black = 0; black < 5; black++) {
-         br.y = obottom - track->GetBlackPos(black);
-         if (br.y > r.y && br.y + br.height < r.y + r.height) {
-            dc.DrawRectangle(br); // draw each black key background stripe
-         }
-      }
-      obottom = track->GetOctaveBottom(++octave);
-   }
-
-   // draw bar lines
-   Alg_seq_ptr seq = track->mSeq;
-   // We assume that sliding a NoteTrack around slides the barlines
-   // along with the notes. This means that when we write out a track
-   // as Allegro or MIDI without the offset, we'll need to insert an
-   // integer number of measures of silence, using tempo change to
-   // match the duration to the offset.
-   // Iterate over all time signatures to generate beat positions of
-   // bar lines, map the beats to times, map the times to position,
-   // and draw the bar lines that fall within the region of interest (sel)
-   // seq->convert_to_beats();
-   dc.SetPen(mp);
-   Alg_time_sigs &sigs = seq->time_sig;
-   int i = 0; // index into ts[]
-   double next_bar_beat = 0.0;
-   double beats_per_measure = 4.0;
-   while (true) {
-      if (i < sigs.length() && sigs[i].beat < next_bar_beat + ALG_EPS) {
-         // new time signature takes effect
-         Alg_time_sig &sig = sigs[i++];
-         next_bar_beat = sig.beat;
-         beats_per_measure = (sig.num * 4.0) / sig.den;
-      }
-      // map beat to time
-      double t = seq->get_time_map()->beat_to_time(next_bar_beat);
-      // map time to position
-      int x = TIME_TO_X(t + track->GetOffset());
-      if (x > right) break;
-      AColor::Line(dc, x, sel.y, x, sel.y + sel.height);
-      next_bar_beat += beats_per_measure;
-   }
-}
-
-/* DrawNoteTrack:
-Draws a piano-roll style display of sequence data with added
-graphics. Since there may be notes outside of the display region,
-reserve a half-note-height margin at the top and bottom of the
-window and draw out-of-bounds notes here instead.
-*/
-void TrackArtist::DrawNoteTrack(NoteTrack *track,
-                                wxDC & dc,
-                                const wxRect & r,
-                                const ViewInfo *viewInfo,
-                                bool muted)
-{
-   SonifyBeginNoteBackground();
-   double h = viewInfo->h;
-   double pps = viewInfo->zoom;
-   double sel0 = viewInfo->sel0;
-   double sel1 = viewInfo->sel1;
-
-   double h1 = X_TO_TIME(r.x + r.width);
-
-   Alg_seq_ptr seq = track->mSeq;
-   if (!seq) {
-      assert(track->mSerializationBuffer);
-      Alg_track_ptr alg_track = seq->unserialize(track->mSerializationBuffer,
-            track->mSerializationLength);
-      assert(alg_track->get_type() == 's');
-      track->mSeq = seq = (Alg_seq_ptr) alg_track;
-      free(track->mSerializationBuffer);
-      track->mSerializationBuffer = NULL;
-   }
-   assert(seq);
-   int visibleChannels = track->mVisibleChannels;
-
-   if (!track->GetSelected())
-      sel0 = sel1 = 0.0;
-
-   // reserve 1/2 note height at top and bottom of track for
-   // out-of-bounds notes
-   int numPitches = (r.height) / track->GetPitchHeight();
-   if (numPitches < 0) numPitches = 0; // cannot be negative
-
-   // bottom is the hypothetical location of the bottom of pitch 0 relative to 
-   // the top of the clipping region r: r.height - PITCH_HEIGHT/2 is where the 
-   // bottomNote is displayed, and to that
-   // we add the height of bottomNote from the position of pitch 0
-   track->PrepareIPitchToY(r);
-
-   // Background comes in 6 colors:
-   //   214, 214,214 -- unselected white keys
-   //   192,192,192 -- unselected black keys
-   //   170,170,170 -- unselected bar lines
-   //   165,165,190 -- selected white keys
-   //   148,148,170 -- selected black keys
-   //   131,131,150 -- selected bar lines
-   wxPen blackStripePen;
-   blackStripePen.SetColour(192, 192, 192);
-   wxBrush blackStripeBrush;
-   blackStripeBrush.SetColour(192, 192, 192);
-   wxPen barLinePen;
-   barLinePen.SetColour(170, 170, 170);
-
-   DrawNoteBackground(track, dc, r, r, viewInfo, blankBrush, blankPen, 
-                      blackStripeBrush, blackStripePen, barLinePen);
-
-   dc.SetClippingRegion(r);
-
-   // Draw the selection background
-   // First, the white keys, as a single rectangle
-   // In other words fill the selection area with selectedWhiteKeyPen
-   wxRect selBG;
-   selBG.y = r.y;
-   selBG.height = r.height;
-   selBG.x = TIME_TO_X(sel0);
-   selBG.width = TIME_TO_X(sel1) - TIME_TO_X(sel0);
-
-   wxPen selectedWhiteKeyPen;
-   selectedWhiteKeyPen.SetColour(165, 165, 190);
-   dc.SetPen(selectedWhiteKeyPen);
-   
-   wxBrush selectedWhiteKeyBrush;
-   selectedWhiteKeyBrush.SetColour(165, 165, 190);
-   // Then, the black keys and octave stripes, as smaller rectangles
-   wxPen selectedBlackKeyPen;
-   selectedBlackKeyPen.SetColour(148, 148, 170);
-   wxBrush selectedBlackKeyBrush;
-   selectedBlackKeyBrush.SetColour(148, 148, 170);
-   wxPen selectedBarLinePen;
-   selectedBarLinePen.SetColour(131, 131, 150);
-
-   DrawNoteBackground(track, dc, r, selBG, viewInfo,
-                      selectedWhiteKeyBrush, selectedWhiteKeyPen,
-                      selectedBlackKeyBrush, selectedBlackKeyPen,
-                      selectedBarLinePen);
-   SonifyEndNoteBackground();
-   SonifyBeginNoteForeground();
-   int marg = track->GetNoteMargin();
-
-   // NOTE: it would be better to put this in some global initialization
-   // function rather than do lookups every time.
-   Alg_attribute line = symbol_table.insert_string("line");
-   Alg_attribute rectangle = symbol_table.insert_string("rectangle");
-   Alg_attribute triangle = symbol_table.insert_string("triangle");
-   Alg_attribute polygon = symbol_table.insert_string("polygon");
-   Alg_attribute oval = symbol_table.insert_string("oval");
-   Alg_attribute text = symbol_table.insert_string("text");
-   Alg_attribute texts = symbol_table.insert_string("texts");
-   Alg_attribute x1r = symbol_table.insert_string("x1r");
-   Alg_attribute x2r = symbol_table.insert_string("x2r");
-   Alg_attribute y1r = symbol_table.insert_string("y1r");
-   Alg_attribute y2r = symbol_table.insert_string("y2r");
-   Alg_attribute linecolori = symbol_table.insert_string("linecolori");
-   Alg_attribute fillcolori = symbol_table.insert_string("fillcolori");
-   Alg_attribute linethicki = symbol_table.insert_string("linethicki");
-   Alg_attribute filll = symbol_table.insert_string("filll");
-   Alg_attribute fonta = symbol_table.insert_string("fonta");
-   Alg_attribute roman = symbol_table.insert_string("roman");
-   Alg_attribute swiss = symbol_table.insert_string("swiss");
-   Alg_attribute modern = symbol_table.insert_string("modern");
-   Alg_attribute weighta = symbol_table.insert_string("weighta");
-   Alg_attribute bold = symbol_table.insert_string("bold");
-   Alg_attribute sizei = symbol_table.insert_string("sizei");
-   Alg_attribute justifys = symbol_table.insert_string("justifys");
-
-   // We want to draw in seconds, so we need to convert to seconds
-   seq->convert_to_seconds();
-
-   Alg_iterator iterator(seq, false);
-   iterator.begin();
-   //for every event
-   Alg_event_ptr evt;
-   while ((evt = iterator.next())) {
-      if (evt->get_type() == 'n') { // 'n' means a note
-         Alg_note_ptr note = (Alg_note_ptr) evt;
-         // if the note's channel is visible
-         if (visibleChannels & (1 << (evt->chan & 15))) {
-            double x = note->time + track->GetOffset();
-            double x1 = x + note->dur;
-            if (x < h1 && x1 > h) { // omit if outside box
-               const char *shape = NULL;
-               if (note->loud > 0.0 || !(shape = IsShape(note))) {
-                  wxRect nr; // "note rectangle"
-                  nr.y = track->PitchToY(note->pitch);
-                  nr.height = track->GetPitchHeight();
-
-                  nr.x = r.x + (int) ((x - h) * pps);
-                  nr.width = (int) ((note->dur * pps) + 0.5);
-
-                  if (nr.x + nr.width >= r.x && nr.x < r.x + r.width) {
-                     if (nr.x < r.x) {
-                        nr.width -= (r.x - nr.x);
-                        nr.x = r.x;
-                     }
-                     if (nr.x + nr.width > r.x + r.width) // clip on right
-                        nr.width = r.x + r.width - nr.x;
-
-                     if (nr.y + nr.height < r.y + marg + 3) {
-                         // too high for window
-                         nr.y = r.y;
-                         nr.height = marg;
-                         dc.SetBrush(*wxBLACK_BRUSH);
-                         dc.SetPen(*wxBLACK_PEN);
-                         dc.DrawRectangle(nr);
-                     } else if (nr.y >= r.y + r.height - marg - 1) { 
-                         // too low for window
-                         nr.y = r.y + r.height - marg;
-                         nr.height = marg;
-                         dc.SetBrush(*wxBLACK_BRUSH);
-                         dc.SetPen(*wxBLACK_PEN);
-                         dc.DrawRectangle(nr);
-                     } else {
-                        if (nr.y + nr.height > r.y + r.height - marg)
-                           nr.height = r.y + r.height - nr.y;
-                        if (nr.y < r.y + marg) {
-                           int offset = r.y + marg - nr.y;
-                           nr.height -= offset;
-                           nr.y += offset;
-                        }
-                        // nr.y += r.y;
-                        if (muted)
-                           AColor::LightMIDIChannel(&dc, note->chan + 1);
-                        else
-                           AColor::MIDIChannel(&dc, note->chan + 1);
-                        dc.DrawRectangle(nr);
-                        if (track->GetPitchHeight() > 2) {
-                           AColor::LightMIDIChannel(&dc, note->chan + 1);
-                           AColor::Line(dc, nr.x, nr.y, nr.x + nr.width-2, nr.y);
-                           AColor::Line(dc, nr.x, nr.y, nr.x, nr.y + nr.height-2);
-                           AColor::DarkMIDIChannel(&dc, note->chan + 1);
-                           AColor::Line(dc, nr.x+nr.width-1, nr.y,
-                                 nr.x+nr.width-1, nr.y+nr.height-1);
-                           AColor::Line(dc, nr.x, nr.y+nr.height-1,
-                                 nr.x+nr.width-1, nr.y+nr.height-1);
-                        }
-//                        }
-                     }
-                  }
-               } else if (shape) {
-                  // draw a shape according to attributes
-                  // add 0.5 to pitch because pitches are plotted with 
-                  // height = PITCH_HEIGHT; thus, the center is raised 
-                  // by PITCH_HEIGHT * 0.5
-                  int y = track->PitchToY(note->pitch);
-                  long linecolor = LookupIntAttribute(note, linecolori, -1);
-                  long linethick = LookupIntAttribute(note, linethicki, 1);
-                  long fillcolor = -1;
-                  long fillflag = 0;
-
-                  // set default color to be that of channel
-                  AColor::MIDIChannel(&dc, note->chan+1);
-                  if (shape != text) {
-                     if (linecolor != -1)
-                        dc.SetPen(wxPen(wxColour(RED(linecolor), 
-                              GREEN(linecolor),
-                              BLUE(linecolor)),
-                              linethick, wxSOLID));
-                  }
-                  if (shape != line) {
-                     fillcolor = LookupIntAttribute(note, fillcolori, -1);
-                     fillflag = LookupLogicalAttribute(note, filll, false);
-
-                     if (fillcolor != -1) 
-                        dc.SetBrush(wxBrush(wxColour(RED(fillcolor),
-                              GREEN(fillcolor),
-                              BLUE(fillcolor)),
-                              wxSOLID));
-                     if (!fillflag) dc.SetBrush(*wxTRANSPARENT_BRUSH);
-                  }
-                  int y1 = track->PitchToY(LookupRealAttribute(note, y1r, note->pitch));
-                  if (shape == line) {
-                     // extreme zooms caues problems under windows, so we have to do some
-                     // clipping before calling display routine
-                     if (x < h) { // clip line on left
-                        y = int((y + (y1 - y) * (h - x) / (x1 - x)) + 0.5);
-                        x = h;
-                     }
-                     if (x1 > h1) { // clip line on right
-                        y1 = int((y + (y1 - y) * (h1 - x) / (x1 - x)) + 0.5);
-                        x1 = h1;
-                     }
-                     AColor::Line(dc, TIME_TO_X(x), y, TIME_TO_X(x1), y1);
-                  } else if (shape == rectangle) {
-                     if (x < h) { // clip on left, leave 10 pixels to spare
-                        x = h - (linethick + 10) / pps;
-                     }
-                     if (x1 > h1) { // clip on right, leave 10 pixels to spare
-                        x1 = h1 + (linethick + 10) / pps;
-                     }
-                     dc.DrawRectangle(TIME_TO_X(x), y, int((x1 - x) * pps + 0.5), y1 - y + 1);
-                  } else if (shape == triangle) {
-                     wxPoint points[3];
-                     points[0].x = TIME_TO_X(x);
-                     CLIP(points[0].x);
-                     points[0].y = y;
-                     points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, note->pitch));
-                     CLIP(points[1].x);
-                     points[1].y = y1;
-                     points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, x));
-                     CLIP(points[2].x);
-                     points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch));
-                     dc.DrawPolygon(3, points);
-                  } else if (shape == polygon) {
-                     wxPoint points[20]; // upper bound of 20 sides
-                     points[0].x = TIME_TO_X(x);
-                     CLIP(points[0].x);
-                     points[0].y = y;
-                     points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, x));
-                     CLIP(points[1].x);
-                     points[1].y = y1;
-                     points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, x));
-                     CLIP(points[2].x);
-                     points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch));
-                     int n = 3;
-                     while (n < 20) {
-                        char name[8];
-                        sprintf(name, "x%dr", n);
-                        Alg_attribute attr = symbol_table.insert_string(name);
-                        double xn = LookupRealAttribute(note, attr, -1000000.0);
-                        if (xn == -1000000.0) break;
-                        points[n].x = TIME_TO_X(xn);
-                        CLIP(points[n].x);
-                        sprintf(name, "y%dr", n - 1);
-                        attr = symbol_table.insert_string(name);
-                        double yn = LookupRealAttribute(note, attr, -1000000.0);
-                        if (yn == -1000000.0) break;
-                        points[n].y = track->PitchToY(yn);
-                        n++;
-                     }
-                     dc.DrawPolygon(n, points);
-                  } else if (shape == oval) {
-                     int ix = TIME_TO_X(x);
-                     CLIP(ix);
-                     int ix1 = int((x1 - x) * pps + 0.5);
-                     if (ix1 > CLIP_MAX * 2) ix1 = CLIP_MAX * 2; // CLIP a width
-                     dc.DrawEllipse(ix, y, ix1, y1 - y + 1);
-                  } else if (shape == text) {
-                     if (linecolor != -1)
-                        dc.SetTextForeground(wxColour(RED(linecolor), 
-                              GREEN(linecolor),
-                              BLUE(linecolor)));
-                     // if no color specified, copy color from brush
-                     else dc.SetTextForeground(dc.GetBrush().GetColour());
-
-                     // This seems to have no effect, so I commented it out. -RBD
-                     //if (fillcolor != -1)
-                     //  dc.SetTextBackground(wxColour(RED(fillcolor), 
-                     //                                GREEN(fillcolor),
-                     //                                BLUE(fillcolor)));
-                     //// if no color specified, copy color from brush
-                     //else dc.SetTextBackground(dc.GetPen().GetColour());
-
-                     const char *font = LookupAtomAttribute(note, fonta, NULL);
-                     const char *weight = LookupAtomAttribute(note, weighta, NULL);
-                     int size = LookupIntAttribute(note, sizei, 8);
-                     const char *justify = LookupStringAttribute(note, justifys, "ld");
-                     wxFont wxfont;
-                     wxfont.SetFamily(font == roman ? wxROMAN : 
-                        (font == swiss ? wxSWISS :
-                           (font == modern ? wxMODERN : wxDEFAULT)));
-                     wxfont.SetStyle(wxNORMAL);
-                     wxfont.SetWeight(weight == bold ? wxBOLD : wxNORMAL);
-                     wxfont.SetPointSize(size);
-                     dc.SetFont(wxfont);
-
-                     // now do justification
-                     const char *s = LookupStringAttribute(note, texts, "");
-                     #ifdef __WXMAC__
-                        long textWidth, textHeight;
-                     #else
-                        int textWidth, textHeight;
-                     #endif
-                     dc.GetTextExtent(LAT1CTOWX(s), &textWidth, &textHeight);
-                     long hoffset = 0;
-                     long voffset = -textHeight; // default should be baseline of text
-
-                     if (strlen(justify) != 2) justify = "ld";
-
-                     if (justify[0] == 'c') hoffset = -(textWidth/2);
-                     else if (justify[0] == 'r') hoffset = -textWidth;
-
-                     if (justify[1] == 't') voffset = 0;
-                     else if (justify[1] == 'c') voffset = -(textHeight/2);
-                     else if (justify[1] == 'b') voffset = -textHeight;
-                     if (fillflag) {
-                        // It should be possible to do this with background color,
-                        // but maybe because of the transfer mode, no background is
-                        // drawn. To fix this, just draw a rectangle:
-                        dc.SetPen(wxPen(wxColour(RED(fillcolor), 
-                              GREEN(fillcolor),
-                              BLUE(fillcolor)),
-                              1, wxSOLID));
-                        dc.DrawRectangle(TIME_TO_X(x) + hoffset, y + voffset,
-                              textWidth, textHeight);
-                     }
-                     dc.DrawText(LAT1CTOWX(s), TIME_TO_X(x) + hoffset, y + voffset);
-                  }
-               }
-            }
-         }
-      }
-   }
-   iterator.end();
-   // draw black line between top/bottom margins and the track
-   dc.SetPen(*wxBLACK_PEN);
-   AColor::Line(dc, r.x, r.y + marg, r.x + r.width, r.y + marg);
-   AColor::Line(dc, r.x, r.y + r.height - marg - 1, // subtract 1 to get 
-                r.x + r.width, r.y + r.height - marg - 1); // top of line
-
-   if (h == 0.0 && track->GetOffset() < 0.0) {
-      DrawNegativeOffsetTrackArrows(dc, r);
-   }
-
-   dc.DestroyClippingRegion();
-   SonifyEndNoteForeground();
-}
-#endif // USE_MIDI
-
-
-void TrackArtist::DrawLabelTrack(LabelTrack *track,
-                                 wxDC & dc,
-                                 const wxRect & r,
-                                 const ViewInfo *viewInfo)
-{
-   double sel0 = viewInfo->sel0;
-   double sel1 = viewInfo->sel1;
-   
-   if (!track->GetSelected() && !track->IsSyncLockSelected())
-      sel0 = sel1 = 0.0;
-   
-   track->Draw(dc, r, viewInfo->h, viewInfo->zoom, sel0, sel1);
-}
-
-void TrackArtist::DrawTimeTrack(TimeTrack *track,
-                                wxDC & dc,
-                                const wxRect & r,
-                                const ViewInfo *viewInfo)
-{
-   track->Draw(dc, r, viewInfo->h, viewInfo->zoom);
-   wxRect envRect = r;
-   envRect.height -= 2;
-   track->GetEnvelope()->DrawPoints(dc, envRect, viewInfo->h, viewInfo->zoom, 
-               false,0.0,1.0);
-}
-
-void TrackArtist::UpdatePrefs()
-{
-   mdBrange = gPrefs->Read(wxT("/GUI/EnvdBRange"), mdBrange);
-   mShowClipping = gPrefs->Read(wxT("/GUI/ShowClipping"), mShowClipping);
-
-   mMaxFreq = gPrefs->Read(wxT("/Spectrum/MaxFreq"), -1);
-   mMinFreq = gPrefs->Read(wxT("/Spectrum/MinFreq"), -1);
-   mLogMaxFreq = gPrefs->Read(wxT("/SpectrumLog/MaxFreq"), -1);
-   if( mLogMaxFreq < 0 )
-      mLogMaxFreq = mMaxFreq;
-   mLogMinFreq = gPrefs->Read(wxT("/SpectrumLog/MinFreq"), -1);
-   if( mLogMinFreq < 0 )
-      mLogMinFreq = mMinFreq;
-
-   mWindowSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 256);
-   mIsGrayscale = (gPrefs->Read(wxT("/Spectrum/Grayscale"), 0L) != 0);
-
-#ifdef EXPERIMENTAL_FFT_Y_GRID
-   mFftYGrid = (gPrefs->Read(wxT("/Spectrum/FFTYGrid"), 0L) != 0);
-#endif //EXPERIMENTAL_FFT_Y_GRID
-
-#ifdef EXPERIMENTAL_FIND_NOTES
-   mFftFindNotes = (gPrefs->Read(wxT("/Spectrum/FFTFindNotes"), 0L) != 0);
-   mFindNotesMinA = gPrefs->Read(wxT("/Spectrum/FindNotesMinA"), -30.0);
-   mNumberOfMaxima = gPrefs->Read(wxT("/Spectrum/FindNotesN"), 5L);
-   mFindNotesQuantize = (gPrefs->Read(wxT("/Spectrum/FindNotesQuantize"), 0L) != 0);
-#endif //EXPERIMENTAL_FIND_NOTES
-
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-   mFftSkipPoints = gPrefs->Read(wxT("/Spectrum/FFTSkipPoints"), 0L);
-#endif //EXPERIMENTAL_FFT_SKIP_POINTS
-}
-
-// Get various preference values
-int TrackArtist::GetSpectrumMinFreq(int deffreq)
-{
-   return mMinFreq < 0 ? deffreq : mMinFreq;
-}
-
-int TrackArtist::GetSpectrumMaxFreq(int deffreq)
-{
-   return mMaxFreq < 0 ? deffreq : mMaxFreq;
-}
-
-int TrackArtist::GetSpectrumLogMinFreq(int deffreq)
-{
-   return mLogMinFreq < 0 ? deffreq : mLogMinFreq;
-}
-
-int TrackArtist::GetSpectrumLogMaxFreq(int deffreq)
-{
-   return mLogMaxFreq < 0 ? deffreq : mLogMaxFreq;
-}
-
-int TrackArtist::GetSpectrumWindowSize()
-{
-   return mWindowSize;
-}
-
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-int TrackArtist::GetSpectrumFftSkipPoints()
-{
-   return mFftSkipPoints;
-}
-#endif
-
-// Set various preference values
-void TrackArtist::SetSpectrumMinFreq(int freq)
-{
-   mMinFreq = freq;
-}
-
-void TrackArtist::SetSpectrumMaxFreq(int freq)
-{
-   mMaxFreq = freq;
-}
-
-void TrackArtist::SetSpectrumLogMinFreq(int freq)
-{
-   mLogMinFreq = freq;
-}
-
-void TrackArtist::SetSpectrumLogMaxFreq(int freq)
-{
-   mLogMaxFreq = freq;
-}
-
-// Draws the sync-lock bitmap, tiled; always draws stationary relative to the DC
-//
-// AWD: now that the tiles don't link together, we're drawing a tilted grid, at
-// two steps down for every one across. This creates a pattern that repeats in
-// 5-step by 5-step boxes. Because we're only drawing in 5/25 possible positions
-// we have a grid spacing somewhat smaller than the image dimensions. Thus we
-// acheive lower density than with a square grid and eliminate edge cases where
-// no tiles are displayed.
-//
-// The pattern draws in tiles at (0,0), (2,1), (4,2), (1,3), and (3,4) in each
-// 5x5 box.
-//
-// There may be a better way to do this, or a more appealing pattern.
-void TrackArtist::DrawSyncLockTiles(wxDC *dc, wxRect r)
-{
-   wxBitmap syncLockBitmap(theTheme.Image(bmpSyncLockSelTile));
-
-   // Grid spacing is a bit smaller than actual image size
-   int gridW = syncLockBitmap.GetWidth() - 6;
-   int gridH = syncLockBitmap.GetHeight() - 8;
-
-   // Horizontal position within the grid, modulo its period
-   int blockX = (r.x / gridW) % 5;
-   
-   // Amount to offset drawing of first column
-   int xOffset = r.x % gridW;
-   if (xOffset < 0) xOffset += gridW;
-
-   // Check if we're missing an extra column to the left (this can happen
-   // because the tiles are bigger than the grid spacing)
-   bool extraCol = false;
-   if (syncLockBitmap.GetWidth() - gridW > xOffset) {
-      extraCol = true;
-      xOffset += gridW;
-      blockX = (blockX - 1) % 5;
-   }
-   // Make sure blockX is non-negative
-   if (blockX < 0) blockX += 5;
-   
-   int x = 0;
-   while (x < r.width) {
-      int width = syncLockBitmap.GetWidth() - xOffset;
-      if (x + width > r.width)
-         width = r.width - x;
-
-      //
-      // Draw each row in this column
-      //
-
-      // Vertical position in the grid, modulo its period
-      int blockY = (r.y / gridH) % 5;
-
-      // Amount to offset drawing of first row
-      int yOffset = r.y % gridH;
-      if (yOffset < 0) yOffset += gridH;
-      
-      // Check if we're missing an extra row on top (this can happen because
-      // the tiles are bigger than the grid spacing)
-      bool extraRow = false;
-      if (syncLockBitmap.GetHeight() - gridH > yOffset) {
-         extraRow = true;
-         yOffset += gridH;
-         blockY = (blockY - 1) % 5;
-      }
-      // Make sure blockY is non-negative
-      if (blockY < 0) blockY += 5;
-
-      int y = 0;
-      while (y < r.height)
-      {
-         int height = syncLockBitmap.GetHeight() - yOffset;
-         if (y + height > r.height)
-            height = r.height - y;
-
-         // AWD: draw blocks according to our pattern
-         if ((blockX == 0 && blockY == 0) || (blockX == 2 && blockY == 1) ||
-             (blockX == 4 && blockY == 2) || (blockX == 1 && blockY == 3) ||
-             (blockX == 3 && blockY == 4))
-         {
-
-            // Do we need to get a sub-bitmap?
-            if (width != syncLockBitmap.GetWidth() || height != syncLockBitmap.GetHeight()) {
-               wxBitmap subSyncLockBitmap = 
-                  syncLockBitmap.GetSubBitmap(wxRect(xOffset, yOffset, width, height));
-               dc->DrawBitmap(subSyncLockBitmap, r.x + x, r.y + y, true);
-            }
-            else {
-               dc->DrawBitmap(syncLockBitmap, r.x + x, r.y + y, true);
-            }
-         }
-
-         // Updates for next row
-         if (extraRow) {
-            // Second offset row, still at y = 0; no more extra rows
-            yOffset -= gridH;
-            extraRow = false;
-         }
-         else {
-            // Move on in y, no more offset rows
-            y += gridH - yOffset;
-            yOffset = 0;
-         }
-         blockY = (blockY + 1) % 5;
-      }
-
-      // Updates for next column
-      if (extraCol) {
-         // Second offset column, still at x = 0; no more extra columns
-         xOffset -= gridW;
-         extraCol = false;
-      }
-      else {
-         // Move on in x, no more offset rows
-         x += gridW - xOffset;
-         xOffset = 0;
-      }
-      blockX = (blockX + 1) % 5;
-   }
-}
-
-void TrackArtist::DrawBackgroundWithSelection(wxDC *dc, const wxRect &r,
-      Track *track, wxBrush &selBrush, wxBrush &unselBrush,
-      double sel0, double sel1, double h, double pps)
-{
-   //MM: Draw background. We should optimize that a bit more.
-   //AWD: "+ 1.5" and "+ 2.5" throughout match code in
-   //AdornedRulerPanel::DoDrawSelection() and make selection line up with ruler.
-   //I don't know if/why this is correct.
-
-   dc->SetPen(*wxTRANSPARENT_PEN);
-   if (track->GetSelected() || track->IsSyncLockSelected())
-   {
-      // Rectangles before, within, after the selction
-      wxRect before = r;
-      wxRect within = r;
-      wxRect after = r;
-
-      before.width = int ((sel0 - h) * pps + 2.5);
-      if (before.GetRight() > r.GetRight()) {
-         before.width = r.width;
-      }
-
-      if (before.width > 0) {
-         dc->SetBrush(unselBrush);
-         dc->DrawRectangle(before);
-
-         within.x = before.GetRight();
-      }
-      within.width = r.x + int ((sel1 - h) * pps + 2.5) - within.x;
-
-      if (within.GetRight() > r.GetRight()) {
-         within.width = r.GetRight() - within.x;
-      }
-
-      if (within.width > 0) {
-         if (track->GetSelected()) {
-            dc->SetBrush(selBrush);
-            dc->DrawRectangle(within);
-         }
-         else {
-            // Per condition above, track must be sync-lock selected
-            dc->SetBrush(unselBrush);
-            dc->DrawRectangle(within);
-            DrawSyncLockTiles(dc, within);
-         }
-
-         after.x = within.GetRight();
-      }
-      else {
-         // `within` not drawn; start where it would have gone
-         after.x = within.x;
-      }
-
-      after.width = r.GetRight() - after.x;
-      if (after.width > 0) {
-         dc->SetBrush(unselBrush);
-         dc->DrawRectangle(after);
-      }
-   }
-   else
-   {
-      // Track not selected; just draw background
-      dc->SetBrush(unselBrush);
-      dc->DrawRectangle(r);
-   }
-}
-
Index: src/TrackArtist.h
===================================================================
--- src/TrackArtist.h	(revision 11311)
+++ src/TrackArtist.h	(working copy)
@@ -23,6 +23,15 @@
 #include "Experimental.h"
 #include "Sequence.h"
 
+#include "view/BaseV.h"
+#include "view/VRulerV.h"
+#include "view/TrackV.h"
+#include "view/LabelTrackV.h"
+#include "view/TimeTrackV.h"
+#include "view/NoteTrackV.h"
+#include "view/WaveTrackV.h"
+#include "view/SpecTrackV.h"
+
 class wxDC;
 class wxRect;
 class wxHashTable;
@@ -41,205 +50,49 @@
 typedef unsigned char uchar;
 #endif
 
-class AUDACITY_DLL_API TrackArtist {
+#ifndef REFACTORING_TRACKPANEL_DONE
 
+class AUDACITY_DLL_API TrackArtist :
+virtual public LabelTrackV, 
+virtual public TimeTrackV, 
+virtual public NoteTrackV, 
+virtual public WaveTrackV, 
+virtual public SpecTrackV
+{
  public:
    TrackArtist();
    ~TrackArtist();
 
-   void SetColours();
-   void DrawTracks(TrackList *tracks, Track *start,
+   void UpdatePrefs();
+
+   void DrawTracks(wxEvtHandler *win, TrackList *tracks, Track *start,
                    wxDC & dc, wxRegion & reg,
                    wxRect & r, wxRect & clip, ViewInfo *viewInfo, 
                    bool drawEnvelope, bool drawSamples, bool drawSliders);
 
-   void DrawTrack(const Track *t,
+   void DrawTrack(wxEvtHandler *win, const Track *t,
                   wxDC & dc, const wxRect & r, const ViewInfo *viewInfo, 
                   bool drawEnvelope, bool drawSamples, bool drawSliders,
                   bool hasSolo);
 
-   void DrawVRuler(Track *t, wxDC *dc, wxRect & r);
-
-   void UpdateVRuler(Track *t, wxRect & r);
-
    void SetInset(int left, int top, int right, int bottom);
-
-   void UpdatePrefs();
-
-   void InvalidateSpectrumCache(TrackList *tracks);
-   void InvalidateSpectrumCache(WaveTrack *track);
-   int GetSpectrumMinFreq(int deffreq);
-   int GetSpectrumMaxFreq(int deffreq);
-   int GetSpectrumLogMinFreq(int deffreq);
-   int GetSpectrumLogMaxFreq(int deffreq);
-   int GetSpectrumWindowSize();
-
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-   int GetSpectrumFftSkipPoints();
-#endif
-
-   void SetSpectrumMinFreq(int freq);
-   void SetSpectrumMaxFreq(int freq);
-   void SetSpectrumLogMinFreq(int freq);
-   void SetSpectrumLogMaxFreq(int freq);
-
+   void SetColours();
    void SetBackgroundBrushes(wxBrush unselectedBrush, wxBrush selectedBrush,
-			     wxPen unselectedPen, wxPen selectedPen) {
-     this->unselectedBrush = unselectedBrush;
-     this->selectedBrush = selectedBrush;
-     this->unselectedPen = unselectedPen;
-     this->selectedPen = selectedPen;
-   }
+		                       wxPen unselectedPen, wxPen selectedPen);
 
-   // Helper: draws the "sync-locked" watermark tiled to a rectangle
-   static void DrawSyncLockTiles(wxDC *dc, wxRect r);
-
-   // Helper: draws background with selection rect
-   static void DrawBackgroundWithSelection(wxDC *dc, const wxRect &r,
-         Track *track, wxBrush &selBrush, wxBrush &unselBrush,
-         double sel0, double sel1, double h, double pps);
-
  private:
-
-   //
-   // Lower-level drawing functions
-   // 
-
-   void DrawWaveform(WaveTrack *track,
-                     wxDC & dc, const wxRect & r, const ViewInfo *viewInfo, 
-                     bool drawEnvelope, bool drawSamples, bool drawSliders,
-                     bool dB, bool muted);
-
-   void DrawSpectrum(WaveTrack *track,
-                     wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
-                     bool autocorrelation, bool logF);
-#ifdef USE_MIDI
-   int GetBottom(NoteTrack *t, const wxRect &r);
-   void DrawNoteBackground(NoteTrack *track, wxDC &dc, 
-                           const wxRect &r, const wxRect &sel,
-                           const ViewInfo *viewInfo,
-                           const wxBrush &wb, const wxPen &wp,
-                           const wxBrush &bb, const wxPen &bp,
-                           const wxPen &mp);
-   void DrawNoteTrack(NoteTrack *track,
-                      wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
-                      bool muted);
-#endif // USE_MIDI
-
-   void DrawLabelTrack(LabelTrack *track,
-                       wxDC & dc, const wxRect & r, const ViewInfo *viewInfo);
-
-   void DrawTimeTrack(TimeTrack *track,
-                      wxDC & dc, const wxRect & r, const ViewInfo *viewInfo);
-
-   void DrawTimeSlider(WaveTrack *track,
-                       wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
-                       bool rightwards);
-
-   void DrawClipWaveform(WaveTrack *track, WaveClip *clip,
-                         wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
-                         bool drawEnvelope, bool drawSamples, bool drawSliders,
-                         bool dB, bool muted);
-
-   void DrawClipSpectrum(WaveTrack *track, WaveClip *clip,
-                         wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
-                         bool autocorrelation, bool logF);
-
-   // Waveform utility functions
-
-   void DrawWaveformBackground(wxDC & dc, const wxRect &r, const double env[],
-                               float zoomMin, float zoomMax, bool dB,
-                               const sampleCount where[],
-                               sampleCount ssel0, sampleCount ssel1,
-                               bool drawEnvelope, bool bIsSyncLockSelected);
-
-   void DrawMinMaxRMS(wxDC & dc, const wxRect & r, const double env[],
-                      float zoomMin, float zoomMax, bool dB,
-                      const float min[], const float max[], const float rms[],
-                      const int bl[], bool showProgress, bool muted);
-
-   void DrawIndividualSamples(wxDC & dc, const wxRect & r,
-                              float zoomMin, float zoomMax, bool dB,
-                              WaveClip *clip,
-                              double t0, double pps, double h,
-                              bool drawSamples, bool showPoints, bool muted);
-
-   void DrawNegativeOffsetTrackArrows(wxDC & dc, const wxRect & r);
-
-   void DrawEnvelope(wxDC & dc, const wxRect & r, const double env[],
-                     float zoomMin, float zoomMax, bool dB);
-   void DrawEnvLine(wxDC & dc, const wxRect & r, int x, int y, int cy, bool top);
-
-   // Preference values
-   float mdBrange;            // "/GUI/EnvdBRange"
-   long mShowClipping;        // "/GUI/ShowClipping"
-   int mLogMaxFreq;           // "/SpectrumLog/MaxFreq"
-   int mLogMinFreq;           // "/SpectrumLog/MinFreq"
-   int mMaxFreq;              // "/Spectrum/MaxFreq"
-   int mMinFreq;              // "/Spectrum/MinFreq"
-   int mWindowSize;           // "/Spectrum/FFTSize"
-   bool mIsGrayscale;         // "/Spectrum/Grayscale"
-
-#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-   int mFftSkipPoints;        // "/Spectrum/FFTSkipPoints"
-#endif //EXPERIMENTAL_FFT_SKIP_POINTS
-
-#ifdef EXPERIMENTAL_FFT_Y_GRID
-   bool mFftYGrid;            // "/Spectrum/FFTYGrid"
-#endif //EXPERIMENTAL_FFT_Y_GRID
-
-#ifdef EXPERIMENTAL_FIND_NOTES
-   bool mFftFindNotes;        // "/Spectrum/FFTFindNotes"
-   float mFindNotesMinA;      // "/Spectrum/FindNotesMinA"
-   int mNumberOfMaxima;       // "/Spectrum/FindNotesN"
-   bool mFindNotesQuantize;   // "/Spectrum/FindNotesQuantize")
-#endif //EXPERIMENTAL_FIND_NOTES
-   
    int mInsetLeft;
    int mInsetTop;
    int mInsetRight;
    int mInsetBottom;
 
-   wxBrush blankBrush;
-   wxBrush unselectedBrush;
-   wxBrush selectedBrush;
-   wxBrush sampleBrush;
-   wxBrush selsampleBrush;
-   wxBrush dragsampleBrush;// for samples which are draggable.
-   wxBrush muteSampleBrush;
-   wxBrush blankSelectedBrush;
-   wxPen blankPen;
-   wxPen unselectedPen;
-   wxPen selectedPen;
-   wxPen samplePen;
-   wxPen rmsPen;
-   wxPen muteRmsPen;
-   wxPen selsamplePen;
-   wxPen muteSamplePen;
-   wxPen odProgressNotYetPen;
-   wxPen odProgressDonePen;
-   wxPen shadowPen;
-   wxPen clippedPen;
-   wxPen muteClippedPen;
-   wxPen blankSelectedPen;
-
-   Ruler *vruler;
-
-#ifdef EXPERIMENTAL_FFT_Y_GRID
-   bool fftYGridOld;
-#endif //EXPERIMENTAL_FFT_Y_GRID
-
-#ifdef EXPERIMENTAL_FIND_NOTES
-   bool fftFindNotesOld;
-   int findNotesMinAOld;
-   int findNotesNOld;
-   bool findNotesQuantizeOld;
-#endif
+#ifdef EXPERIMENTAL_DRAW_THREAD
+ public:
+   int OnUpdateAfterThreadRunID;
+#endif //EXPERIMENTAL_DRAW_THREAD
 };
 
-extern int GetWaveYPos(float value, float min, float max,
-			  int height, bool dB, bool outer, float dBr, 
-			  bool clip);
+#endif //REFACTORING_TRACKPANEL_DONE
 
 #endif                          // define __AUDACITY_TRACKARTIST__
 
Index: src/TrackPanel.cpp
===================================================================
--- src/TrackPanel.cpp	(revision 11311)
+++ src/TrackPanel.cpp	(working copy)
@@ -313,6 +313,10 @@
    OnCutSelectedTextID,
    OnCopySelectedTextID,
    OnPasteSelectedTextID,
+
+#ifdef EXPERIMENTAL_DRAW_THREAD
+   OnUpdateAfterThreadRunID,
+#endif //EXPERIMENTAL_DRAW_THREAD
 };
 
 BEGIN_EVENT_TABLE(TrackPanel, wxWindow)
@@ -346,6 +350,10 @@
     EVT_MENU(OnCutSelectedTextID, TrackPanel::OnCutSelectedText)
     EVT_MENU(OnCopySelectedTextID, TrackPanel::OnCopySelectedText)
     EVT_MENU(OnPasteSelectedTextID, TrackPanel::OnPasteSelectedText)
+
+#ifdef EXPERIMENTAL_DRAW_THREAD
+    EVT_BUTTON(OnUpdateAfterThreadRunID, TrackPanel::OnUpdateAfterThreadRun)
+#endif //EXPERIMENTAL_DRAW_THREAD
 END_EVENT_TABLE()
 
 /// Makes a cursor from an XPM, uses CursorId as a fallback.
@@ -507,6 +515,9 @@
 
    mTrackArtist = new TrackArtist();
    mTrackArtist->SetInset(1, kTopInset + 1, kLeftInset + 2, 2);
+#ifdef EXPERIMENTAL_DRAW_THREAD
+   mTrackArtist->OnUpdateAfterThreadRunID=OnUpdateAfterThreadRunID;
+#endif //EXPERIMENTAL_DRAW_THREAD
 
    mCapturedTrack = NULL;
    mPopupMenuTarget = NULL;
@@ -3280,30 +3291,30 @@
    spectrum = ((WaveTrack *) track)->GetDisplay() == WaveTrack::SpectrumDisplay;
    spectrumLog=(((WaveTrack *) track)->GetDisplay() == WaveTrack::SpectrumLogDisplay);
    if(spectrum) {
-      min = mTrackArtist->GetSpectrumMinFreq(0);
+      min = mTrackArtist->vrulerv->GetSpectrumMinFreq(0);
       if(min < 0)
          min = 0;
-      max = mTrackArtist->GetSpectrumMaxFreq(8000);
+      max = mTrackArtist->vrulerv->GetSpectrumMaxFreq(8000);
       if(max > rate/2.)
          max = rate/2.;
       windowSize = mTrackArtist->GetSpectrumWindowSize();
 #ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-      fftSkipPoints = mTrackArtist->GetSpectrumFftSkipPoints();
+      fftSkipPoints = mTrackArtist->vrulerv->GetSpectrumFftSkipPoints();
 #endif //EXPERIMENTAL_FFT_SKIP_POINTS
       binSize = rate / windowSize;
       minBins = wxMin(10, windowSize/2); //minimum 10 freq bins, unless there are less
    }
    else
       if(spectrumLog) {
-         min = mTrackArtist->GetSpectrumLogMinFreq(lrint(rate/1000.0));
+         min = mTrackArtist->vrulerv->GetSpectrumLogMinFreq(lrint(rate/1000.0));
          if(min < 1)
             min = 1;
-         max = mTrackArtist->GetSpectrumLogMaxFreq(lrint(rate/2.));
+         max = mTrackArtist->vrulerv->GetSpectrumLogMaxFreq(lrint(rate/2.));
          if(max > rate/2.)
             max = rate/2.;
          windowSize = mTrackArtist->GetSpectrumWindowSize();
 #ifdef EXPERIMENTAL_FFT_SKIP_POINTS
-         fftSkipPoints = mTrackArtist->GetSpectrumFftSkipPoints();
+         fftSkipPoints = mTrackArtist->vrulerv->GetSpectrumFftSkipPoints();
 #endif //EXPERIMENTAL_FFT_SKIP_POINTS
          binSize = rate / windowSize;
          minBins = wxMin(10, windowSize/2); //minimum 10 freq bins, unless there are less
@@ -3455,13 +3466,13 @@
    }
 
    if(spectrum) {
-      mTrackArtist->SetSpectrumMaxFreq(max);
-      mTrackArtist->SetSpectrumMinFreq(min);
+      mTrackArtist->vrulerv->SetSpectrumMaxFreq(max);
+      mTrackArtist->vrulerv->SetSpectrumMinFreq(min);
       mTrackArtist->InvalidateSpectrumCache(mTracks);
    }
    else if(spectrumLog) {
-      mTrackArtist->SetSpectrumLogMaxFreq(max);
-      mTrackArtist->SetSpectrumLogMinFreq(min);
+      mTrackArtist->vrulerv->SetSpectrumLogMaxFreq(max);
+      mTrackArtist->vrulerv->SetSpectrumLogMinFreq(min);
       mTrackArtist->InvalidateSpectrumCache(mTracks);
    }
    else {
@@ -5481,7 +5492,7 @@
    bool sliderFlag     = bMultiToolDown;
 
    // The track artist actually draws the stuff inside each track
-   mTrackArtist->DrawTracks(mTracks, GetProject()->GetFirstVisible(),
+   mTrackArtist->DrawTracks(this, mTracks, GetProject()->GetFirstVisible(),
                             *dc, region, tracksRect, clip, mViewInfo, 
                             envelopeFlag, samplesFlag, sliderFlag);
 
@@ -5561,7 +5572,7 @@
          r.y += kTopInset;
          r.width = GetVRulerWidth();
          r.height -= (kTopInset + 2);
-         mTrackArtist->DrawVRuler(t, dc, r);
+         mTrackArtist->vrulerv->DrawVRuler(t, dc, r);
       }
    }
 
@@ -5844,11 +5855,11 @@
             t->GetHeight() - (kTopInset + 2));
 
    if (t) {
-      mTrackArtist->UpdateVRuler(t, r);
+      mTrackArtist->vrulerv->UpdateVRuler(t, r);
       Track *l = t->GetLink();
       if (l) {
          r.height = l->GetHeight() - (kTopInset + 2);
-         mTrackArtist->UpdateVRuler(l, r);
+         mTrackArtist->vrulerv->UpdateVRuler(l, r);
       }
    }
 }
@@ -7393,6 +7404,32 @@
    RefreshTrack(lt);
 }
 
+#ifdef EXPERIMENTAL_DRAW_THREAD
+
+void TrackPanel::OnUpdateAfterThreadRun(wxCommandEvent &event)
+{
+   TrackUpdateData *data=(TrackUpdateData*)event.GetClientData();
+   wxRect mid=data->mid;
+   wxImage *image=data->image;
+   WaveClip *clip=data->clip;
+   delete data;
+
+   // If we get to this point, the clip is actually visible on the
+   // screen, so remember the display rectangle.
+   clip->SetDisplayRect(mid);
+   wxBitmap converted = wxBitmap(*image);
+   wxMemoryDC memDC;
+
+   memDC.SelectObject(converted);
+   mBackingDC.Blit(mid.x, mid.y, mid.width, mid.height, &memDC, 0, 0, wxCOPY, FALSE);
+   delete image;
+   // Copy it to the display
+   wxClientDC dc(this);
+   dc.Blit(0, 0, mBacking->GetWidth(), mBacking->GetHeight(), &mBackingDC, 0, 0);
+}
+
+#endif //EXPERIMENTAL_DRAW_THREAD
+
 // Small helper class to enumerate all fonts in the system
 // We use this because the default implementation of
 // wxFontEnumerator::GetFacenames() has changed between wx2.6 and 2.8
Index: src/TrackPanel.h
===================================================================
--- src/TrackPanel.h	(revision 11311)
+++ src/TrackPanel.h	(working copy)
@@ -148,8 +148,16 @@
    friend class TrackPanel;
 };
 
+#ifdef EXPERIMENTAL_DRAW_THREAD
+class TrackUpdateData
+{
+public:
+   wxRect mid;
+   wxImage *image;
+   WaveClip *clip;
+};
+#endif //EXPERIMENTAL_DRAW_THREAD
 
-
 const int DragThreshold = 3;// Anything over 3 pixels is a drag, else a click.
 
 
@@ -428,6 +436,10 @@
    void OnCopySelectedText(wxCommandEvent &event);
    void OnPasteSelectedText(wxCommandEvent &event);
 
+#ifdef EXPERIMENTAL_DRAW_THREAD
+   void OnUpdateAfterThreadRun(wxCommandEvent &event);
+#endif //EXPERIMENTAL_DRAW_THREAD
+
    void SetTrackPan(Track * t, LWSlider * s);
    void SetTrackGain(Track * t, LWSlider * s);
 
Index: src/view/BaseV.cpp
===================================================================
--- src/view/BaseV.cpp	(revision 0)
+++ src/view/BaseV.cpp	(working copy)
@@ -0,0 +1,16 @@
+#include "../Audacity.h"
+#include "wx/panel.h"
+#include "BaseV.h"
+
+BaseV::BaseV(void)
+#ifdef REFACTORING_TRACKPANEL_DONE
+:
+wxPanel()
+#endif //REFACTORING_TRACKPANEL_DONE
+{
+}
+
+BaseV::~BaseV(void)
+{
+}
+
Index: src/view/BaseV.h
===================================================================
--- src/view/BaseV.h	(revision 0)
+++ src/view/BaseV.h	(working copy)
@@ -0,0 +1,10 @@
+
+class AUDACITY_DLL_API BaseV
+#ifdef REFACTORING_TRACKPANEL_DONE
+: public wxPanel
+#endif
+{
+public:
+   BaseV(void);
+   ~BaseV(void);
+};
Index: src/view/LabelTrackV.cpp
===================================================================
--- src/view/LabelTrackV.cpp	(revision 0)
+++ src/view/LabelTrackV.cpp	(working copy)
@@ -0,0 +1,26 @@
+#include "../Audacity.h"
+#include "../TrackArtist.h"
+
+LabelTrackV::LabelTrackV(void):
+TrackV()
+{
+}
+
+LabelTrackV::~LabelTrackV(void)
+{
+}
+
+void LabelTrackV::DrawLabelTrack(LabelTrack *track,
+                                 wxDC & dc,
+                                 const wxRect & r,
+                                 const ViewInfo *viewInfo)
+{
+   double sel0 = viewInfo->sel0;
+   double sel1 = viewInfo->sel1;
+   
+   if (!track->GetSelected() && !track->IsSyncLockSelected())
+      sel0 = sel1 = 0.0;
+   
+   track->Draw(dc, r, viewInfo->h, viewInfo->zoom, sel0, sel1);
+}
+
Index: src/view/LabelTrackV.h
===================================================================
--- src/view/LabelTrackV.h	(revision 0)
+++ src/view/LabelTrackV.h	(working copy)
@@ -0,0 +1,11 @@
+
+class AUDACITY_DLL_API LabelTrackV:
+virtual public TrackV
+{
+public:
+   LabelTrackV(void);
+   ~LabelTrackV(void);
+
+   void DrawLabelTrack(LabelTrack *track,
+                       wxDC & dc, const wxRect & r, const ViewInfo *viewInfo);
+};
Index: src/view/NoteTrackV.cpp
===================================================================
--- src/view/NoteTrackV.cpp	(revision 0)
+++ src/view/NoteTrackV.cpp	(working copy)
@@ -0,0 +1,699 @@
+#include "../Audacity.h"
+#include "../TrackArtist.h"
+#ifdef USE_MIDI
+#include "../NoteTrack.h"
+#endif // USE_MIDI
+
+NoteTrackV::NoteTrackV(void):
+TrackV()
+{
+}
+
+NoteTrackV::~NoteTrackV(void)
+{
+}
+
+#ifdef USE_MIDI
+/*
+const int octaveHeight = 62;
+const int blackPos[5] = { 6, 16, 32, 42, 52 };
+const int whitePos[7] = { 0, 9, 17, 26, 35, 44, 53 };
+const int notePos[12] = { 1, 6, 11, 16, 21, 27,
+                        32, 37, 42, 47, 52, 57 };
+
+// map pitch number to window coordinate of the *top* of the note
+// Note the "free" variable bottom, which is assumed to be a local 
+// variable set to the offset of pitch 0 relative to the window
+#define IPITCH_TO_Y(t, p) (bottom - ((p) / 12) * octaveHeight - \
+                          notePos[(p) % 12] - (t)->GetPitchHeight())
+
+// GetBottom is called from a couple of places to compute the hypothetical
+// coordinate of the bottom of pitch 0 in window coordinates. See
+// IPITCH_TO_Y above, which computes coordinates relative to GetBottom()
+// Note the -NOTE_MARGIN, which leaves a little margin to draw notes that
+// are out of bounds. I'm not sure why the -2 is necessary.
+int TrackArtist::GetBottom(NoteTrack *t, const wxRect &r)
+{
+   int bottomNote = t->GetBottomNote();
+   int bottom = r.y + r.height - 2 - t->GetNoteMargin() +
+          ((bottomNote / 12) * octaveHeight + notePos[bottomNote % 12]);
+   return bottom;
+
+}
+*/
+#endif // USE_MIDI
+
+#ifdef USE_MIDI
+/*
+Note: recall that Allegro attributes end in a type identifying letter.
+
+In addition to standard notes, an Allegro_Note can denote a graphic.
+A graphic is a note with a loud of zero (for quick testing) and an
+attribute named "shapea" set to one of the following atoms:
+    line
+        from (time, pitch) to (time+dur, y1r), where y1r is an
+          attribute
+    rectangle
+        from (time, pitch) to (time+dur, y1r), where y1r is an
+          attribute
+    triangle
+        coordinates are (time, pitch), (x1r, y1r), (x2r, y2r)
+        dur must be the max of x1r-time, x2r-time
+    polygon
+        coordinates are (time, pitch), (x1r, y1r), (x2r, y2r),
+          (x3r, y3r), ... are coordinates (since we cannot represent
+          arrays as attribute values, we just generate as many
+          attribute names as we need)
+        dur must be the max of xNr-time for all N
+    oval
+        similar to rectangle
+        Note: this oval has horizontal and vertical axes only
+    text
+        drawn at (time, pitch)
+        duration should be zero (text is clipped based on time and duration,
+          NOT based on actual coordinates)
+
+and optional attributes as follows:
+    linecolori is 0x00rrggbb format color for line or text foreground
+    fillcolori is 0x00rrggbb format color for fill or text background
+    linethicki is line thickness in pixels, 0 for no line
+    filll is true to fill rectangle or draw text background (default is false)
+    fonta is one of ['roman', 'swiss', 'modern'] (font, otherwise use default)
+    weighta may be 'bold' (font) (default is normal)
+    sizei is font size (default is 8)
+    justifys is a string containing two letters, a horizontal code and a
+      vertical code. The horizontal code is as follows:
+        l: the coordinate is to the left of the string (default)
+        c: the coordinate is at the center of the string
+        r: the coordinate is at the right of the string
+      The vertical code is as follows:
+        t: the coordinate is at the top of the string
+        c: the coordinate is at the center of the string
+        b: the coordinate is at the bottom of the string
+        d: the coordinate is at the baseline of the string (default)
+      Thus, -justifys:"lt" places the left top of the string at the point
+        given by (pitch, time). The default value is "ld".
+*/
+
+#define TIME_TO_X(t) (r.x + (int) (((t) - h) * pps))
+#define X_TO_TIME(xx) (((xx) - r.x) / pps + h)
+
+// CLIP(x) changes x to lie between +/- CLIP_MAX due to graphics display problems
+//  with very large coordinate values (this happens when you zoom in very far)
+//  This will cause incorrect things to be displayed, but at these levels of zoom
+//  you will only see a small fraction of the overall shape. Note that rectangles
+//  and lines are clipped in a way that preserves correct graphics, so in
+//  particular, line plots will be correct at any zoom (limited by floating point
+//  precision).
+#define CLIP_MAX 16000
+#define CLIP(x) { long c = (x); if (c < -CLIP_MAX) c = -CLIP_MAX; \
+                  if (c > CLIP_MAX) c = CLIP_MAX; (x) = c; }
+
+#define RED(i) ( unsigned char )( (((i) >> 16) & 0xff) )
+#define GREEN(i) ( unsigned char )( (((i) >> 8) & 0xff) )
+#define BLUE(i) ( unsigned char )( ((i) & 0xff) )
+
+//#define PITCH_TO_Y(p) (r.y + r.height - int(pitchht * ((p) + 0.5 - pitch0) + 0.5))
+
+#ifdef USE_MIDI
+
+/*
+int PitchToY(double p, int bottom)
+{
+   int octave = (((int) (p + 0.5)) / 12);
+   int n = ((int) (p + 0.5)) % 12;
+   
+   return IPITCH_TO_Y((int) (p + 0.5));
+   // was: bottom - octave * octaveHeight - notePos[n] - 4;
+}
+*/
+
+#endif // USE_MIDI
+/* Declare Static functions */
+static const char *IsShape(Alg_note_ptr note);
+static double LookupRealAttribute(Alg_note_ptr note, Alg_attribute attr, double def);
+static long LookupIntAttribute(Alg_note_ptr note, Alg_attribute attr, long def);
+static bool LookupLogicalAttribute(Alg_note_ptr note, Alg_attribute attr, bool def);
+static const char *LookupStringAttribute(Alg_note_ptr note, Alg_attribute attr, const char *def);
+static const char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def);
+//static int PITCH_TO_Y(double p, int bottom);
+
+/* DrawNoteTrack:
+Draws a piano-roll style display of sequence data with added
+graphics. Since there may be notes outside of the display region,
+reserve a half-note-height margin at the top and bottom of the
+window and draw out-of-bounds notes here instead.
+*/
+void NoteTrackV::DrawNoteTrack(NoteTrack *track,
+                                wxDC & dc,
+                                const wxRect & r,
+                                const ViewInfo *viewInfo,
+                                bool muted)
+{
+   SonifyBeginNoteBackground();
+   double h = viewInfo->h;
+   double pps = viewInfo->zoom;
+   double sel0 = viewInfo->sel0;
+   double sel1 = viewInfo->sel1;
+
+   double h1 = X_TO_TIME(r.x + r.width);
+
+   Alg_seq_ptr seq = track->mSeq;
+   if (!seq) {
+      assert(track->mSerializationBuffer);
+      Alg_track_ptr alg_track = seq->unserialize(track->mSerializationBuffer,
+            track->mSerializationLength);
+      assert(alg_track->get_type() == 's');
+      track->mSeq = seq = (Alg_seq_ptr) alg_track;
+      free(track->mSerializationBuffer);
+      track->mSerializationBuffer = NULL;
+   }
+   assert(seq);
+   int visibleChannels = track->mVisibleChannels;
+
+   if (!track->GetSelected())
+      sel0 = sel1 = 0.0;
+
+   // reserve 1/2 note height at top and bottom of track for
+   // out-of-bounds notes
+   int numPitches = (r.height) / track->GetPitchHeight();
+   if (numPitches < 0) numPitches = 0; // cannot be negative
+
+   // bottom is the hypothetical location of the bottom of pitch 0 relative to 
+   // the top of the clipping region r: r.height - PITCH_HEIGHT/2 is where the 
+   // bottomNote is displayed, and to that
+   // we add the height of bottomNote from the position of pitch 0
+   track->PrepareIPitchToY(r);
+
+   // Background comes in 6 colors:
+   //   214, 214,214 -- unselected white keys
+   //   192,192,192 -- unselected black keys
+   //   170,170,170 -- unselected bar lines
+   //   165,165,190 -- selected white keys
+   //   148,148,170 -- selected black keys
+   //   131,131,150 -- selected bar lines
+   wxPen blackStripePen;
+   blackStripePen.SetColour(192, 192, 192);
+   wxBrush blackStripeBrush;
+   blackStripeBrush.SetColour(192, 192, 192);
+   wxPen barLinePen;
+   barLinePen.SetColour(170, 170, 170);
+
+   DrawNoteBackground(track, dc, r, r, viewInfo, blankBrush, blankPen, 
+                      blackStripeBrush, blackStripePen, barLinePen);
+
+   dc.SetClippingRegion(r);
+
+   // Draw the selection background
+   // First, the white keys, as a single rectangle
+   // In other words fill the selection area with selectedWhiteKeyPen
+   wxRect selBG;
+   selBG.y = r.y;
+   selBG.height = r.height;
+   selBG.x = TIME_TO_X(sel0);
+   selBG.width = TIME_TO_X(sel1) - TIME_TO_X(sel0);
+
+   wxPen selectedWhiteKeyPen;
+   selectedWhiteKeyPen.SetColour(165, 165, 190);
+   dc.SetPen(selectedWhiteKeyPen);
+   
+   wxBrush selectedWhiteKeyBrush;
+   selectedWhiteKeyBrush.SetColour(165, 165, 190);
+   // Then, the black keys and octave stripes, as smaller rectangles
+   wxPen selectedBlackKeyPen;
+   selectedBlackKeyPen.SetColour(148, 148, 170);
+   wxBrush selectedBlackKeyBrush;
+   selectedBlackKeyBrush.SetColour(148, 148, 170);
+   wxPen selectedBarLinePen;
+   selectedBarLinePen.SetColour(131, 131, 150);
+
+   DrawNoteBackground(track, dc, r, selBG, viewInfo,
+                      selectedWhiteKeyBrush, selectedWhiteKeyPen,
+                      selectedBlackKeyBrush, selectedBlackKeyPen,
+                      selectedBarLinePen);
+   SonifyEndNoteBackground();
+   SonifyBeginNoteForeground();
+   int marg = track->GetNoteMargin();
+
+   // NOTE: it would be better to put this in some global initialization
+   // function rather than do lookups every time.
+   Alg_attribute line = symbol_table.insert_string("line");
+   Alg_attribute rectangle = symbol_table.insert_string("rectangle");
+   Alg_attribute triangle = symbol_table.insert_string("triangle");
+   Alg_attribute polygon = symbol_table.insert_string("polygon");
+   Alg_attribute oval = symbol_table.insert_string("oval");
+   Alg_attribute text = symbol_table.insert_string("text");
+   Alg_attribute texts = symbol_table.insert_string("texts");
+   Alg_attribute x1r = symbol_table.insert_string("x1r");
+   Alg_attribute x2r = symbol_table.insert_string("x2r");
+   Alg_attribute y1r = symbol_table.insert_string("y1r");
+   Alg_attribute y2r = symbol_table.insert_string("y2r");
+   Alg_attribute linecolori = symbol_table.insert_string("linecolori");
+   Alg_attribute fillcolori = symbol_table.insert_string("fillcolori");
+   Alg_attribute linethicki = symbol_table.insert_string("linethicki");
+   Alg_attribute filll = symbol_table.insert_string("filll");
+   Alg_attribute fonta = symbol_table.insert_string("fonta");
+   Alg_attribute roman = symbol_table.insert_string("roman");
+   Alg_attribute swiss = symbol_table.insert_string("swiss");
+   Alg_attribute modern = symbol_table.insert_string("modern");
+   Alg_attribute weighta = symbol_table.insert_string("weighta");
+   Alg_attribute bold = symbol_table.insert_string("bold");
+   Alg_attribute sizei = symbol_table.insert_string("sizei");
+   Alg_attribute justifys = symbol_table.insert_string("justifys");
+
+   // We want to draw in seconds, so we need to convert to seconds
+   seq->convert_to_seconds();
+
+   Alg_iterator iterator(seq, false);
+   iterator.begin();
+   //for every event
+   Alg_event_ptr evt;
+   while ((evt = iterator.next())) {
+      if (evt->get_type() == 'n') { // 'n' means a note
+         Alg_note_ptr note = (Alg_note_ptr) evt;
+         // if the note's channel is visible
+         if (visibleChannels & (1 << (evt->chan & 15))) {
+            double x = note->time + track->GetOffset();
+            double x1 = x + note->dur;
+            if (x < h1 && x1 > h) { // omit if outside box
+               const char *shape = NULL;
+               if (note->loud > 0.0 || !(shape = IsShape(note))) {
+                  wxRect nr; // "note rectangle"
+                  nr.y = track->PitchToY(note->pitch);
+                  nr.height = track->GetPitchHeight();
+
+                  nr.x = r.x + (int) ((x - h) * pps);
+                  nr.width = (int) ((note->dur * pps) + 0.5);
+
+                  if (nr.x + nr.width >= r.x && nr.x < r.x + r.width) {
+                     if (nr.x < r.x) {
+                        nr.width -= (r.x - nr.x);
+                        nr.x = r.x;
+                     }
+                     if (nr.x + nr.width > r.x + r.width) // clip on right
+                        nr.width = r.x + r.width - nr.x;
+
+                     if (nr.y + nr.height < r.y + marg + 3) {
+                         // too high for window
+                         nr.y = r.y;
+                         nr.height = marg;
+                         dc.SetBrush(*wxBLACK_BRUSH);
+                         dc.SetPen(*wxBLACK_PEN);
+                         dc.DrawRectangle(nr);
+                     } else if (nr.y >= r.y + r.height - marg - 1) { 
+                         // too low for window
+                         nr.y = r.y + r.height - marg;
+                         nr.height = marg;
+                         dc.SetBrush(*wxBLACK_BRUSH);
+                         dc.SetPen(*wxBLACK_PEN);
+                         dc.DrawRectangle(nr);
+                     } else {
+                        if (nr.y + nr.height > r.y + r.height - marg)
+                           nr.height = r.y + r.height - nr.y;
+                        if (nr.y < r.y + marg) {
+                           int offset = r.y + marg - nr.y;
+                           nr.height -= offset;
+                           nr.y += offset;
+                        }
+                        // nr.y += r.y;
+                        if (muted)
+                           AColor::LightMIDIChannel(&dc, note->chan + 1);
+                        else
+                           AColor::MIDIChannel(&dc, note->chan + 1);
+                        dc.DrawRectangle(nr);
+                        if (track->GetPitchHeight() > 2) {
+                           AColor::LightMIDIChannel(&dc, note->chan + 1);
+                           AColor::Line(dc, nr.x, nr.y, nr.x + nr.width-2, nr.y);
+                           AColor::Line(dc, nr.x, nr.y, nr.x, nr.y + nr.height-2);
+                           AColor::DarkMIDIChannel(&dc, note->chan + 1);
+                           AColor::Line(dc, nr.x+nr.width-1, nr.y,
+                                 nr.x+nr.width-1, nr.y+nr.height-1);
+                           AColor::Line(dc, nr.x, nr.y+nr.height-1,
+                                 nr.x+nr.width-1, nr.y+nr.height-1);
+                        }
+//                        }
+                     }
+                  }
+               } else if (shape) {
+                  // draw a shape according to attributes
+                  // add 0.5 to pitch because pitches are plotted with 
+                  // height = PITCH_HEIGHT; thus, the center is raised 
+                  // by PITCH_HEIGHT * 0.5
+                  int y = track->PitchToY(note->pitch);
+                  long linecolor = LookupIntAttribute(note, linecolori, -1);
+                  long linethick = LookupIntAttribute(note, linethicki, 1);
+                  long fillcolor = -1;
+                  long fillflag = 0;
+
+                  // set default color to be that of channel
+                  AColor::MIDIChannel(&dc, note->chan+1);
+                  if (shape != text) {
+                     if (linecolor != -1)
+                        dc.SetPen(wxPen(wxColour(RED(linecolor), 
+                              GREEN(linecolor),
+                              BLUE(linecolor)),
+                              linethick, wxSOLID));
+                  }
+                  if (shape != line) {
+                     fillcolor = LookupIntAttribute(note, fillcolori, -1);
+                     fillflag = LookupLogicalAttribute(note, filll, false);
+
+                     if (fillcolor != -1) 
+                        dc.SetBrush(wxBrush(wxColour(RED(fillcolor),
+                              GREEN(fillcolor),
+                              BLUE(fillcolor)),
+                              wxSOLID));
+                     if (!fillflag) dc.SetBrush(*wxTRANSPARENT_BRUSH);
+                  }
+                  int y1 = track->PitchToY(LookupRealAttribute(note, y1r, note->pitch));
+                  if (shape == line) {
+                     // extreme zooms caues problems under windows, so we have to do some
+                     // clipping before calling display routine
+                     if (x < h) { // clip line on left
+                        y = int((y + (y1 - y) * (h - x) / (x1 - x)) + 0.5);
+                        x = h;
+                     }
+                     if (x1 > h1) { // clip line on right
+                        y1 = int((y + (y1 - y) * (h1 - x) / (x1 - x)) + 0.5);
+                        x1 = h1;
+                     }
+                     AColor::Line(dc, TIME_TO_X(x), y, TIME_TO_X(x1), y1);
+                  } else if (shape == rectangle) {
+                     if (x < h) { // clip on left, leave 10 pixels to spare
+                        x = h - (linethick + 10) / pps;
+                     }
+                     if (x1 > h1) { // clip on right, leave 10 pixels to spare
+                        x1 = h1 + (linethick + 10) / pps;
+                     }
+                     dc.DrawRectangle(TIME_TO_X(x), y, int((x1 - x) * pps + 0.5), y1 - y + 1);
+                  } else if (shape == triangle) {
+                     wxPoint points[3];
+                     points[0].x = TIME_TO_X(x);
+                     CLIP(points[0].x);
+                     points[0].y = y;
+                     points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, note->pitch));
+                     CLIP(points[1].x);
+                     points[1].y = y1;
+                     points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, x));
+                     CLIP(points[2].x);
+                     points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch));
+                     dc.DrawPolygon(3, points);
+                  } else if (shape == polygon) {
+                     wxPoint points[20]; // upper bound of 20 sides
+                     points[0].x = TIME_TO_X(x);
+                     CLIP(points[0].x);
+                     points[0].y = y;
+                     points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, x));
+                     CLIP(points[1].x);
+                     points[1].y = y1;
+                     points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, x));
+                     CLIP(points[2].x);
+                     points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch));
+                     int n = 3;
+                     while (n < 20) {
+                        char name[8];
+                        sprintf(name, "x%dr", n);
+                        Alg_attribute attr = symbol_table.insert_string(name);
+                        double xn = LookupRealAttribute(note, attr, -1000000.0);
+                        if (xn == -1000000.0) break;
+                        points[n].x = TIME_TO_X(xn);
+                        CLIP(points[n].x);
+                        sprintf(name, "y%dr", n - 1);
+                        attr = symbol_table.insert_string(name);
+                        double yn = LookupRealAttribute(note, attr, -1000000.0);
+                        if (yn == -1000000.0) break;
+                        points[n].y = track->PitchToY(yn);
+                        n++;
+                     }
+                     dc.DrawPolygon(n, points);
+                  } else if (shape == oval) {
+                     int ix = TIME_TO_X(x);
+                     CLIP(ix);
+                     int ix1 = int((x1 - x) * pps + 0.5);
+                     if (ix1 > CLIP_MAX * 2) ix1 = CLIP_MAX * 2; // CLIP a width
+                     dc.DrawEllipse(ix, y, ix1, y1 - y + 1);
+                  } else if (shape == text) {
+                     if (linecolor != -1)
+                        dc.SetTextForeground(wxColour(RED(linecolor), 
+                              GREEN(linecolor),
+                              BLUE(linecolor)));
+                     // if no color specified, copy color from brush
+                     else dc.SetTextForeground(dc.GetBrush().GetColour());
+
+                     // This seems to have no effect, so I commented it out. -RBD
+                     //if (fillcolor != -1)
+                     //  dc.SetTextBackground(wxColour(RED(fillcolor), 
+                     //                                GREEN(fillcolor),
+                     //                                BLUE(fillcolor)));
+                     //// if no color specified, copy color from brush
+                     //else dc.SetTextBackground(dc.GetPen().GetColour());
+
+                     const char *font = LookupAtomAttribute(note, fonta, NULL);
+                     const char *weight = LookupAtomAttribute(note, weighta, NULL);
+                     int size = LookupIntAttribute(note, sizei, 8);
+                     const char *justify = LookupStringAttribute(note, justifys, "ld");
+                     wxFont wxfont;
+                     wxfont.SetFamily(font == roman ? wxROMAN : 
+                        (font == swiss ? wxSWISS :
+                           (font == modern ? wxMODERN : wxDEFAULT)));
+                     wxfont.SetStyle(wxNORMAL);
+                     wxfont.SetWeight(weight == bold ? wxBOLD : wxNORMAL);
+                     wxfont.SetPointSize(size);
+                     dc.SetFont(wxfont);
+
+                     // now do justification
+                     const char *s = LookupStringAttribute(note, texts, "");
+                     #ifdef __WXMAC__
+                        long textWidth, textHeight;
+                     #else
+                        int textWidth, textHeight;
+                     #endif
+                     dc.GetTextExtent(LAT1CTOWX(s), &textWidth, &textHeight);
+                     long hoffset = 0;
+                     long voffset = -textHeight; // default should be baseline of text
+
+                     if (strlen(justify) != 2) justify = "ld";
+
+                     if (justify[0] == 'c') hoffset = -(textWidth/2);
+                     else if (justify[0] == 'r') hoffset = -textWidth;
+
+                     if (justify[1] == 't') voffset = 0;
+                     else if (justify[1] == 'c') voffset = -(textHeight/2);
+                     else if (justify[1] == 'b') voffset = -textHeight;
+                     if (fillflag) {
+                        // It should be possible to do this with background color,
+                        // but maybe because of the transfer mode, no background is
+                        // drawn. To fix this, just draw a rectangle:
+                        dc.SetPen(wxPen(wxColour(RED(fillcolor), 
+                              GREEN(fillcolor),
+                              BLUE(fillcolor)),
+                              1, wxSOLID));
+                        dc.DrawRectangle(TIME_TO_X(x) + hoffset, y + voffset,
+                              textWidth, textHeight);
+                     }
+                     dc.DrawText(LAT1CTOWX(s), TIME_TO_X(x) + hoffset, y + voffset);
+                  }
+               }
+            }
+         }
+      }
+   }
+   iterator.end();
+   // draw black line between top/bottom margins and the track
+   dc.SetPen(*wxBLACK_PEN);
+   AColor::Line(dc, r.x, r.y + marg, r.x + r.width, r.y + marg);
+   AColor::Line(dc, r.x, r.y + r.height - marg - 1, // subtract 1 to get 
+                r.x + r.width, r.y + r.height - marg - 1); // top of line
+
+   if (h == 0.0 && track->GetOffset() < 0.0) {
+      DrawNegativeOffsetTrackArrows(dc, r);
+   }
+
+   dc.DestroyClippingRegion();
+   SonifyEndNoteForeground();
+}
+
+/* DrawNoteBackground is called by DrawNoteTrack twice: once to draw
+   the unselected background, and once to draw the selected background.
+   The selected background is the same except for the horizontal range
+   and the colors. The background rectangle region is given by r; the
+   selected region is given by sel. The first time this is called,
+   sel is equal to r, and the entire region is drawn with unselected
+   background colors.
+ */
+void NoteTrackV::DrawNoteBackground(NoteTrack *track, wxDC &dc, 
+                                     const wxRect &r, const wxRect &sel, 
+                                     const ViewInfo *viewInfo,
+                                     const wxBrush &wb, const wxPen &wp,
+                                     const wxBrush &bb, const wxPen &bp,
+                                     const wxPen &mp)
+{
+   dc.SetBrush(wb);
+   dc.SetPen(wp);
+   dc.DrawRectangle(sel); // fill rectangle with white keys background
+   double h = viewInfo->h;
+   double pps = viewInfo->zoom;
+
+   int left = TIME_TO_X(track->GetOffset());
+   if (left < sel.x) left = sel.x; // clip on left
+
+   int right = TIME_TO_X(track->GetOffset() + track->mSeq->get_real_dur());
+   if (right > sel.x + sel.width) right = sel.x + sel.width; // clip on right
+
+   // need overlap between MIDI data and the background region
+   if (left >= right) return;
+
+   dc.SetBrush(bb);
+   int octave = 0;
+   // obottom is the window coordinate of octave divider line
+   int obottom = track->GetOctaveBottom(octave);
+   // eOffset is for the line between E and F; there's another line
+   // between B and C, hence the offset of 2 for two line thicknesses
+   int eOffset = track->GetPitchHeight() * 5 + 2;
+   while (obottom > r.y + track->GetNoteMargin() + 3) {
+      // draw a black line separating octaves if this octave botton is visible
+      if (obottom < r.y + r.height - track->GetNoteMargin()) {
+         dc.SetPen(*wxBLACK_PEN);
+         // obottom - 1 because obottom is at the bottom of the line
+         AColor::Line(dc, left, obottom - 1, right, obottom - 1);
+      }
+      dc.SetPen(bp);
+      // draw a black-key stripe colored line separating E and F if visible
+      if (obottom - eOffset > r.y && obottom - eOffset < r.y + r.height) {
+         AColor::Line(dc, left, obottom - eOffset, 
+                          right, obottom - eOffset);
+      }
+
+      // draw visible black key lines
+      wxRect br;
+      br.x = left;
+      br.width = right - left;
+      br.height = track->GetPitchHeight();
+      for (int black = 0; black < 5; black++) {
+         br.y = obottom - track->GetBlackPos(black);
+         if (br.y > r.y && br.y + br.height < r.y + r.height) {
+            dc.DrawRectangle(br); // draw each black key background stripe
+         }
+      }
+      obottom = track->GetOctaveBottom(++octave);
+   }
+
+   // draw bar lines
+   Alg_seq_ptr seq = track->mSeq;
+   // We assume that sliding a NoteTrack around slides the barlines
+   // along with the notes. This means that when we write out a track
+   // as Allegro or MIDI without the offset, we'll need to insert an
+   // integer number of measures of silence, using tempo change to
+   // match the duration to the offset.
+   // Iterate over all time signatures to generate beat positions of
+   // bar lines, map the beats to times, map the times to position,
+   // and draw the bar lines that fall within the region of interest (sel)
+   // seq->convert_to_beats();
+   dc.SetPen(mp);
+   Alg_time_sigs &sigs = seq->time_sig;
+   int i = 0; // index into ts[]
+   double next_bar_beat = 0.0;
+   double beats_per_measure = 4.0;
+   while (true) {
+      if (i < sigs.length() && sigs[i].beat < next_bar_beat + ALG_EPS) {
+         // new time signature takes effect
+         Alg_time_sig &sig = sigs[i++];
+         next_bar_beat = sig.beat;
+         beats_per_measure = (sig.num * 4.0) / sig.den;
+      }
+      // map beat to time
+      double t = seq->get_time_map()->beat_to_time(next_bar_beat);
+      // map time to position
+      int x = TIME_TO_X(t + track->GetOffset());
+      if (x > right) break;
+      AColor::Line(dc, x, sel.y, x, sel.y + sel.height);
+      next_bar_beat += beats_per_measure;
+   }
+}
+
+// returns NULL if note is not a shape,
+// returns atom (string) value of note if note is a shape
+const char *IsShape(Alg_note_ptr note)
+{
+  Alg_parameters_ptr parameters = note->parameters;
+  while (parameters) {
+    if (strcmp(parameters->parm.attr_name(), "shapea") == 0) {
+      return parameters->parm.a;
+    }
+    parameters = parameters->next;
+  }
+  return NULL;
+}
+
+// returns value of attr, or default if not found
+double LookupRealAttribute(Alg_note_ptr note, Alg_attribute attr, double def)
+{
+  Alg_parameters_ptr parameters = note->parameters;
+  while (parameters) {
+    if (parameters->parm.attr_name() == attr + 1 &&
+        parameters->parm.attr_type() == 'r') {
+      return parameters->parm.r;
+    }
+    parameters = parameters->next;
+  }
+  return def;
+}
+
+// returns value of attr, or default if not found
+long LookupIntAttribute(Alg_note_ptr note, Alg_attribute attr, long def)
+{
+  Alg_parameters_ptr parameters = note->parameters;
+  while (parameters) {
+    if (parameters->parm.attr_name() == attr + 1 &&
+        parameters->parm.attr_type() == 'i') {
+      return parameters->parm.i;
+    }
+    parameters = parameters->next;
+  }
+  return def;
+}
+
+// returns value of attr, or default if not found
+bool LookupLogicalAttribute(Alg_note_ptr note, Alg_attribute attr, bool def)
+{
+  Alg_parameters_ptr parameters = note->parameters;
+  while (parameters) {
+    if (parameters->parm.attr_name() == attr + 1 &&
+        parameters->parm.attr_type() == 'l') {
+      return parameters->parm.l;
+    }
+    parameters = parameters->next;
+  }
+  return def;
+}
+
+// returns value of attr, or default if not found
+const char *LookupStringAttribute(Alg_note_ptr note, Alg_attribute attr, const char *def)
+{
+  Alg_parameters_ptr parameters = note->parameters;
+  while (parameters) {
+    if (parameters->parm.attr_name() == attr + 1 &&
+        parameters->parm.attr_type() == 's') {
+      return parameters->parm.s;
+    }
+    parameters = parameters->next;
+  }
+  return def;
+}
+
+// returns value of attr, or default if not found
+const char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def)
+{
+  Alg_parameters_ptr parameters = note->parameters;
+  while (parameters) {
+    if (parameters->parm.attr_name() == attr + 1 &&
+        parameters->parm.attr_type() == 'a') {
+      return parameters->parm.s;
+    }
+    parameters = parameters->next;
+  }
+  return def;
+}
+#endif // USE_MIDI
+
+
Index: src/view/NoteTrackV.h
===================================================================
--- src/view/NoteTrackV.h	(revision 0)
+++ src/view/NoteTrackV.h	(working copy)
@@ -0,0 +1,25 @@
+
+#ifdef USE_MIDI
+
+class AUDACITY_DLL_API NoteTrackV:
+virtual public TrackV
+{
+public:
+   NoteTrackV(void);
+   ~NoteTrackV(void);
+
+   void DrawNoteTrack(NoteTrack *track,
+                      wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
+                      bool muted);
+
+   void DrawNoteBackground(NoteTrack *track, wxDC &dc, 
+                           const wxRect &r, const wxRect &sel,
+                           const ViewInfo *viewInfo,
+                           const wxBrush &wb, const wxPen &wp,
+                           const wxBrush &bb, const wxPen &bp,
+                           const wxPen &mp);
+
+   int GetBottom(NoteTrack *t, const wxRect &r);
+};
+
+#endif // USE_MIDI
Index: src/view/SpecTrackV.cpp
===================================================================
--- src/view/SpecTrackV.cpp	(revision 0)
+++ src/view/SpecTrackV.cpp	(working copy)
@@ -0,0 +1,875 @@
+#include "../Audacity.h"
+#include "../TrackArtist.h"
+#include "float_cast.h"
+
+SpecTrackV::SpecTrackV(void): 
+WaveTrackV()
+{
+}
+
+SpecTrackV::~SpecTrackV(void)
+{
+}
+
+void SpecTrackV::DrawSpectrum(wxEvtHandler *win,
+                               WaveTrack *track,
+                               wxDC & dc,
+                               const wxRect & r,
+                               const ViewInfo *viewInfo,
+                               bool autocorrelation,
+                               bool logF)
+{
+   DrawBackgroundWithSelection(&dc, r, track, blankSelectedBrush, blankBrush,
+         viewInfo->sel0, viewInfo->sel1, viewInfo->h, viewInfo->zoom);
+
+   if(!viewInfo->bUpdateTrackIndicator && viewInfo->bIsPlaying) {
+      // BG: Draw (undecorated) waveform instead of spectrum
+      DrawWaveform(track, dc, r, viewInfo, false, false, false, false, false);
+      return;
+   }
+
+   for (WaveClipList::compatibility_iterator it = track->GetClipIterator(); it; it = it->GetNext()) {
+      DrawClipSpectrum(win, track, it->GetData(), dc, r, viewInfo, autocorrelation, logF);
+   }
+}
+
+#ifdef EXPERIMENTAL_DRAW_THREAD
+
+class DrawClipSpectrumThread: public wxThread
+{
+public:
+   TrackArtist *ta;
+   WaveTrack *track;
+   WaveClip *clip;
+   wxDC &dc;
+   const wxRect &r;
+   const ViewInfo *viewInfo;
+   bool autocorrelation;
+   bool logF;
+   wxImage *image;
+   wxRect mid;
+   wxEvtHandler *win;
+
+   DrawClipSpectrumThread(wxEvtHandler *win_,
+                          TrackArtist *ta_,
+                          WaveTrack *track_,
+                          WaveClip *clip_,
+                          wxDC & dc_,
+                          const wxRect & r_,
+                          const ViewInfo *viewInfo_,
+                          bool autocorrelation_,
+                          bool logF_,
+                          wxImage *image_,
+                          wxRect mid_):
+		wxThread(), ta(ta_), track(track_), clip(clip_), dc(dc_), r(r_), 
+      viewInfo(viewInfo_), autocorrelation(autocorrelation_), logF(logF_),
+      image(image_), mid(mid_), win(win_)
+      {}
+
+   virtual ExitCode Entry()
+	{	track->inUpdate++;
+      ta->DrawClipSpectrumReal(win, track, clip, dc, r, viewInfo, 
+         autocorrelation, logF, image, mid);
+      track->inUpdate--;
+      return 0;
+	}
+};
+#endif //EXPERIMENTAL_DRAW_THREAD
+
+void SpecTrackV::DrawClipSpectrum(wxEvtHandler *win,
+                                   WaveTrack *track,
+                                   WaveClip *clip,
+                                   wxDC & dc,
+                                   const wxRect & r,
+                                   const ViewInfo *viewInfo,
+                                   bool autocorrelation,
+                                   bool logF)
+{
+#ifdef EXPERIMENTAL_DRAW_THREAD
+   if (track->inUpdate > 0)
+      return;
+
+   double h = viewInfo->h;
+   double pps = viewInfo->zoom;
+   double sel0 = viewInfo->sel0;
+   double sel1 = viewInfo->sel1;
+
+   double tOffset = clip->GetOffset();
+   double rate = clip->GetRate();
+   double sps = 1./rate;
+
+   int range = gPrefs->Read(wxT("/Spectrum/Range"), 80L);
+   int gain = gPrefs->Read(wxT("/Spectrum/Gain"), 20L);
+
+   if (!track->GetSelected())
+      sel0 = sel1 = 0.0;
+
+   double tpre = h - tOffset;
+   double tstep = 1.0 / pps;
+   double tpost = tpre + (r.width * tstep);
+   double trackLen = clip->GetEndTime() - clip->GetStartTime();
+
+   bool showIndividualSamples = (pps / rate > 0.5);   //zoomed in a lot
+   double t0 = (tpre >= 0.0 ? tpre : 0.0);
+   double t1 = (tpost < trackLen - sps*.99 ? tpost : trackLen - sps*.99);
+   if(showIndividualSamples) t1+=2./pps; // for display consistency
+   // with Waveform display
+
+   // Make sure t1 (the right bound) is greater than 0
+   if (t1 < 0.0)
+      t1 = 0.0;
+
+   // Make sure t1 is greater than t0
+   if (t0 > t1)
+      t0 = t1;
+
+   sampleCount ssel0 = wxMax(0, sampleCount((sel0 - tOffset) * rate + .99));
+   sampleCount ssel1 = wxMax(0, sampleCount((sel1 - tOffset) * rate + .99));
+
+   //trim selection so that it only contains the actual samples
+   if (ssel0 != ssel1 && ssel1 > (sampleCount)(0.5+trackLen*rate))
+      ssel1 = (sampleCount)(0.5+trackLen*rate);
+
+   // The variable "mid" will be the rectangle containing the
+   // actual waveform, as opposed to any blank area before
+   // or after the track.
+   wxRect mid = r;
+
+   dc.SetPen(*wxTRANSPARENT_PEN);
+
+   // If the left edge of the track is to the right of the left
+   // edge of the display, then there's some blank area to the
+   // left of the track.  Reduce the "mid"
+   // rect by size of the blank area.
+   if (tpre < 0) {
+      // Fill in the area to the left of the track
+      wxRect pre = r;
+      if (t0 < tpost)
+         pre.width = (int) ((t0 - tpre) * pps);
+
+      // Offset the rectangle containing the waveform by the width
+      // of the area we just erased.
+      mid.x += pre.width;
+      mid.width -= pre.width;
+   }
+
+   // If the right edge of the track is to the left of the the right
+   // edge of the display, then there's some blank area to the right
+   // of the track.  Reduce the "mid" rect by the
+   // size of the blank area.
+   if (tpost > t1) {
+      wxRect post = r;
+      if (t1 > tpre)
+         post.x += (int) ((t1 - tpre) * pps);
+      post.width = r.width - (post.x - r.x);
+
+      // Reduce the rectangle containing the waveform by the width
+      // of the area we just erased.
+      mid.width -= post.width;
+   }
+
+   // The "mid" rect contains the part of the display actually
+   // containing the waveform.  If it's empty, we're done.
+   if (mid.width <= 0) {
+#if PROFILE_WAVEFORM
+#   ifdef __WXMSW__
+      _time64(&tv1);
+      double elapsed = _difftime64(tv1, tv0);
+#   else
+      gettimeofday(&tv1, NULL);
+      double elapsed =
+         (tv1.tv_sec + tv1.tv_usec*0.000001) -
+         (tv0.tv_sec + tv0.tv_usec*0.000001);
+#   endif
+      gWaveformTimeTotal += elapsed;
+      gWaveformTimeCount++;
+#endif
+      return;
+   }
+
+   // We draw directly to a bit image in memory,
+   // and then paint this directly to our offscreen
+   // bitmap.  Note that this could be optimized even
+   // more, but for now this is not bad.  -dmazzoni
+   wxImage *image = new wxImage((int) mid.width, (int) mid.height);
+   if (!image)return;
+
+   DrawClipSpectrumThread *the=new DrawClipSpectrumThread(win, this, 
+      track, clip, dc, r, viewInfo, autocorrelation, logF,
+      image, mid);
+   the->Create(0x100000);
+   the->Run();
+}
+
+void SpecTrackV::DrawClipSpectrumReal(wxEvtHandler *win,
+                                       WaveTrack *track,
+                                       WaveClip *clip,
+                                       wxDC & dc,
+                                       const wxRect & r,
+                                       const ViewInfo *viewInfo,
+                                       bool autocorrelation,
+                                       bool logF,
+                                       wxImage *image,
+                                       wxRect mid)
+{
+#endif //EXPERIMENTAL_DRAW_THREAD
+
+#if PROFILE_WAVEFORM
+#  ifdef __WXMSW__
+   __time64_t tv0, tv1;
+   _time64(&tv0);
+#  else
+   struct timeval tv0, tv1;
+   gettimeofday(&tv0, NULL);
+#  endif
+#endif
+   double h = viewInfo->h;
+   double pps = viewInfo->zoom;
+   double sel0 = viewInfo->sel0;
+   double sel1 = viewInfo->sel1;
+
+   double tOffset = clip->GetOffset();
+   double rate = clip->GetRate();
+   double sps = 1./rate;
+
+   if (!track->GetSelected())
+      sel0 = sel1 = 0.0;
+
+   double tpre = h - tOffset;
+   double tstep = 1.0 / pps;
+   double tpost = tpre + (r.width * tstep);
+   double trackLen = clip->GetEndTime() - clip->GetStartTime();
+
+   bool showIndividualSamples = (pps / rate > 0.5);   //zoomed in a lot
+   double t0 = (tpre >= 0.0 ? tpre : 0.0);
+   double t1 = (tpost < trackLen - sps*.99 ? tpost : trackLen - sps*.99);
+   if(showIndividualSamples) t1+=2./pps; // for display consistency
+   // with Waveform display
+
+   // Make sure t1 (the right bound) is greater than 0
+   if (t1 < 0.0)
+      t1 = 0.0;
+
+   // Make sure t1 is greater than t0
+   if (t0 > t1)
+      t0 = t1;
+
+   sampleCount ssel0 = wxMax(0, sampleCount((sel0 - tOffset) * rate + .99));
+   sampleCount ssel1 = wxMax(0, sampleCount((sel1 - tOffset) * rate + .99));
+
+   //trim selection so that it only contains the actual samples
+   if (ssel0 != ssel1 && ssel1 > (sampleCount)(0.5+trackLen*rate))
+      ssel1 = (sampleCount)(0.5+trackLen*rate);
+
+#ifdef EXPERIMENTAL_DRAW_THREAD
+#else //!EXPERIMENTAL_DRAW_THREAD
+   // The variable "mid" will be the rectangle containing the
+   // actual waveform, as opposed to any blank area before
+   // or after the track.
+   wxRect mid = r;
+
+   dc.SetPen(*wxTRANSPARENT_PEN);
+
+   // If the left edge of the track is to the right of the left
+   // edge of the display, then there's some blank area to the
+   // left of the track.  Reduce the "mid"
+   // rect by size of the blank area.
+   if (tpre < 0) {
+      // Fill in the area to the left of the track
+      wxRect pre = r;
+      if (t0 < tpost)
+         pre.width = (int) ((t0 - tpre) * pps);
+
+      // Offset the rectangle containing the waveform by the width
+      // of the area we just erased.
+      mid.x += pre.width;
+      mid.width -= pre.width;
+   }
+
+   // If the right edge of the track is to the left of the the right
+   // edge of the display, then there's some blank area to the right
+   // of the track.  Reduce the "mid" rect by the
+   // size of the blank area.
+   if (tpost > t1) {
+      wxRect post = r;
+      if (t1 > tpre)
+         post.x += (int) ((t1 - tpre) * pps);
+      post.width = r.width - (post.x - r.x);
+
+      // Reduce the rectangle containing the waveform by the width
+      // of the area we just erased.
+      mid.width -= post.width;
+   }
+
+   // The "mid" rect contains the part of the display actually
+   // containing the waveform.  If it's empty, we're done.
+   if (mid.width <= 0) {
+#if PROFILE_WAVEFORM
+#   ifdef __WXMSW__
+      _time64(&tv1);
+      double elapsed = _difftime64(tv1, tv0);
+#   else
+      gettimeofday(&tv1, NULL);
+      double elapsed =
+         (tv1.tv_sec + tv1.tv_usec*0.000001) -
+         (tv0.tv_sec + tv0.tv_usec*0.000001);
+#   endif
+      gWaveformTimeTotal += elapsed;
+      gWaveformTimeCount++;
+#endif
+      return;
+   }
+
+   // We draw directly to a bit image in memory,
+   // and then paint this directly to our offscreen
+   // bitmap.  Note that this could be optimized even
+   // more, but for now this is not bad.  -dmazzoni
+   wxImage *image = new wxImage((int) mid.width, (int) mid.height);
+   if (!image)return;
+#endif //EXPERIMENTAL_DRAW_THREAD
+
+   unsigned char *data = image->GetData();
+
+   int windowSize = GetSpectrumWindowSize();
+
+   int half = windowSize/2;
+   float *freq = new float[mid.width * half];
+   sampleCount *where = new sampleCount[mid.width+1];
+
+   bool updated = clip->GetSpectrogram(freq, where, mid.width,
+                              t0, pps, autocorrelation);
+   int ifreq = lrint(rate/2);
+
+   int maxFreq;
+   if (!logF)
+      maxFreq = vrulerv->GetSpectrumMaxFreq(ifreq);
+   else
+      maxFreq = vrulerv->GetSpectrumLogMaxFreq(ifreq);
+   if(maxFreq > ifreq)
+      maxFreq = ifreq;
+
+   int minFreq;
+   if (!logF) {
+      minFreq = vrulerv->GetSpectrumMinFreq(0);
+      if(minFreq < 0)
+         minFreq = 0;
+   }
+   else {
+      minFreq = vrulerv->GetSpectrumLogMinFreq(ifreq/1000.0);
+      if(minFreq < 1)
+         minFreq = ifreq/1000.0;
+   }
+
+   int range=vrulerv->mRange;
+   int gain=vrulerv->mGain;
+
+   bool usePxCache = false;
+
+   if( !updated && clip->GetSpecPxCache()->valid && (clip->GetSpecPxCache()->len == mid.height * mid.width) 
+#ifdef EXPERIMENTAL_FFT_Y_GRID
+   && mFftYGrid==mFftYGridOld
+#endif //EXPERIMENTAL_FFT_Y_GRID
+#ifdef EXPERIMENTAL_FIND_NOTES
+   && mFftFindNotes==mFftFindNotesOld
+   && mFindNotesMinA==mFindNotesMinAOld
+   && mNumberOfMaxima==mFindNotesNOld
+   && mFindNotesQuantize==mFindNotesQuantizeOld
+#endif
+   ) {
+      usePxCache = true;
+   }
+   else {
+      clip->SetSpecPxCache(new SpecPxCache(mid.width * mid.height));
+
+      usePxCache = false;
+      clip->GetSpecPxCache()->valid = true;
+#ifdef EXPERIMENTAL_FIND_NOTES
+      mFftFindNotesOld=mFftFindNotes;
+      mFindNotesMinAOld=mFindNotesMinA;
+      mFindNotesNOld=mNumberOfMaxima;
+      mFindNotesQuantizeOld=mFindNotesQuantize;
+#endif
+   }
+
+   int minSamples = int ((double)minFreq * (double)windowSize / rate + 0.5);   // units are fft bins
+   int maxSamples = int ((double)maxFreq * (double)windowSize / rate + 0.5);
+   int temp = sizeof(int);
+   float binPerPx = float(maxSamples - minSamples) / float(mid.height);
+
+   int x = 0;
+   sampleCount w1 = (sampleCount) ((t0*rate + x *rate *tstep) + .5);
+ 
+   const float 
+//      e=exp(1.0f), 
+      f=rate/2.0f/half, 
+      lmin=logf(float(minFreq)),
+      lmax=logf(float(maxFreq)),
+#ifdef EXPERIMENTAL_FIND_NOTES
+      log2=logf(2.0f),
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+      lmins=logf(float(minFreq)/(vrulerv->mFftSkipPoints+1)),
+      lmaxs=logf(float(maxFreq)/(vrulerv->mFftSkipPoints+1)),
+#else //!EXPERIMENTAL_FFT_SKIP_POINTS
+      lmins=lmin,
+      lmaxs=lmax,
+#endif //EXPERIMENTAL_FFT_SKIP_POINTS
+#endif //EXPERIMENTAL_FIND_NOTES
+      scale=lmax-lmin /*, 
+      expo=exp(scale)*/ ;
+
+#ifdef EXPERIMENTAL_FFT_Y_GRID
+   const float
+      scale2=(lmax-lmin)/log2,
+      lmin2=lmin/log2;
+
+   bool *yGrid;
+   yGrid=new bool[mid.height];
+   for (int y = 0; y < mid.height; y++) {
+      float n =(float(y  )/mid.height*scale2-lmin2)*12; 
+      float n2=(float(y+1)/mid.height*scale2-lmin2)*12; 
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+      float f =float(minFreq)/(vrulerv->mFftSkipPoints+1)*powf(2.0f, n /12.0f+lmin2);
+      float f2=float(minFreq)/(vrulerv->mFftSkipPoints+1)*powf(2.0f, n2/12.0f+lmin2);
+#else //!EXPERIMENTAL_FFT_SKIP_POINTS
+      float f =float(minFreq)*powf(2.0f, n /12.0f+lmin2);
+      float f2=float(minFreq)*powf(2.0f, n2/12.0f+lmin2);
+#endif //EXPERIMENTAL_FFT_SKIP_POINTS
+      n =logf(f /440)/log2*12;
+      n2=logf(f2/440)/log2*12;
+      if (floor(n) < floor(n2))
+         yGrid[y]=true;
+      else
+         yGrid[y]=false;
+   }
+#endif //EXPERIMENTAL_FFT_Y_GRID
+
+#ifdef EXPERIMENTAL_FIND_NOTES
+   int maxima[128];
+   float maxima0[128], maxima1[128];
+   const int MAX_NOTES=10;
+   float 
+      notes  [MAX_NOTES], 
+      starts [MAX_NOTES], 
+      levels [MAX_NOTES], 
+      lengths[MAX_NOTES];
+   for (int n=0; n<MAX_NOTES; n++)
+   {  notes[n]=0;
+      starts[n]=0;
+      levels[n]=0;
+      lengths[n]=0;
+   }
+   const float 
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+      f2bin = half/(rate/2.0f/(vrulerv->mFftSkipPoints+1)),
+#else //!EXPERIMENTAL_FFT_SKIP_POINTS
+      f2bin = half/(rate/2.0f),
+#endif //EXPERIMENTAL_FFT_SKIP_POINTS
+      bin2f = 1.0f/f2bin,
+      minDistance = powf(2.0f, 2.0f/12.0f),
+      i0=expf(lmin)/f,
+      i1=expf(scale+lmin)/f,
+      minColor=0.0f;
+   const int maxTableSize=1024;
+   int *indexes=new int[maxTableSize];
+
+   float rainbow_r[2][7], rainbow_g[2][7], rainbow_b[2][7];
+   for (int si=0; si<2; si++) {  
+      for (int hi=0; hi<7; hi++) {  
+         float h=hi/7.0f;
+         float s=1-float(si)/2;
+         hsb2rgb(h,s,1, rainbow_r[si][hi], rainbow_g[si][hi], rainbow_b[si][hi]);
+         rainbow_r[si][hi]*=255;
+         rainbow_g[si][hi]*=255;
+         rainbow_b[si][hi]*=255;
+      }
+   }
+#endif //EXPERIMENTAL_FIND_NOTES
+
+   while (x < mid.width) 
+   {
+      sampleCount w0 = w1;
+      w1 = (sampleCount) ((t0*rate + (x+1) *rate *tstep) + .5);
+      if (!logF)
+      {
+         for (int yy = 0; yy < mid.height; yy++) {
+            bool selflag = (ssel0 <= w0 && w1 < ssel1);
+            unsigned char rv, gv, bv;
+            float value;
+
+            if(!usePxCache) {
+               float bin0 = float (yy) * binPerPx + minSamples;
+               float bin1 = float (yy + 1) * binPerPx + minSamples;
+
+
+               if (int (bin1) == int (bin0))
+                  value = freq[half * x + int (bin0)];
+               else {
+                  float binwidth= bin1 - bin0;
+                  value = freq[half * x + int (bin0)] * (1.f - bin0 + (int)bin0);
+
+                  bin0 = 1 + int (bin0);
+                  while (bin0 < int (bin1)) {
+                     value += freq[half * x + int (bin0)];
+                     bin0 += 1.0;
+                  }
+
+                  // Do not reference past end of freq array.
+                  if (int(bin1) >= half) {
+                     bin1 -= 1.0;
+                  }
+
+                  value += freq[half * x + int (bin1)] * (bin1 - int (bin1));
+
+                  value /= binwidth;
+               }
+
+               if (!autocorrelation) {
+                  // Last step converts dB to a 0.0-1.0 range
+                  value = (value + range + gain) / (double)range;
+               }
+
+               if (value > 1.0)
+                  value = float(1.0);
+               if (value < 0.0)
+                  value = float(0.0);
+               clip->GetSpecPxCache()->values[x * mid.height + yy] = value;
+            }
+            else
+               value = clip->GetSpecPxCache()->values[x * mid.height + yy];
+
+            GetColorGradient(value, selflag, mIsGrayscale, &rv, &gv, &bv);
+
+            int px = ((mid.height - 1 - yy) * mid.width + x) * 3;
+            data[px++] = rv;
+            data[px++] = gv;
+            data[px] = bv;
+         }
+      }
+      else //logF
+      {
+         bool selflag = (ssel0 <= w0 && w1 < ssel1);
+         unsigned char rv, gv, bv;
+         float value;
+         int x0=x*half;
+
+#ifdef EXPERIMENTAL_FIND_NOTES
+         int maximas=0;
+         if (!usePxCache && mFftFindNotes) {
+            for (int i = maxTableSize-1; i >= 0; i--)
+               indexes[i]=-1;
+            
+            // Build a table of (most) values, put the index in it.
+            for (int i = int(i0); i < int(i1); i++) {
+               float freqi=freq[x0+int(i)];
+               int value=int((freqi+gain+range)/range*(maxTableSize-1));
+               if (value < 0)
+                  value=0;
+               if (value >= maxTableSize)
+                  value=maxTableSize-1;
+               indexes[value]=i;
+            }
+            // Build from the indices an array of maxima.
+            for (int i = maxTableSize-1; i >= 0; i--) {
+               int index=indexes[i];
+               if (index >= 0) {
+                  float freqi=freq[x0+index];
+                  if (freqi < mFindNotesMinA) 
+                     break;
+
+                  bool ok=true;
+                  for (int m=0; m < maximas; m++) {
+                     // Avoid to store very close maxima.
+                     float maxm = maxima[m];
+                     if (maxm/index < minDistance && index/maxm < minDistance) {
+                        ok=false;
+                        break;
+                     }
+                  }
+                  if (ok) {
+                     maxima[maximas++] = index;
+                     if (maximas >= mNumberOfMaxima)
+                        break;
+                  }
+               }
+            }
+
+// The f2pix helper macro converts a frequency into a pixel coordinate.
+#define f2pix(f) (logf(f)-lmins)/(lmaxs-lmins)*mid.height
+
+// The pix2f helper macro converts a pixel coordinate into a frequency.
+#define pix2f(pix) expf(float(pix)/mid.height*(lmaxs-lmins)+lmins)
+
+            // Possibly quantize the maxima frequencies and create the pixel block limits.
+            for (int i=0; i < maximas; i++) {
+               int index=maxima[i];
+               float f = float(index)*bin2f;
+               if (mFindNotesQuantize)
+               {  float note=int(log(f/440)/log2*12)-0.5f;
+                  f = expf(note/12.0f*log2)*440;
+                  maxima[i] = f*f2bin;
+                  float delta=1000;
+                  int nn=-1;
+                  for (int n=0; n<mNumberOfMaxima; n++)
+                  {  if (levels[n] < 0.01f)
+                     {  delta=abs(notes[n]-note);
+                        nn=n;
+                     }
+                     if (abs(notes[n]-note) < delta)
+                     {  delta=abs(notes[n]-note);
+                        nn=n;
+                     }
+                  }
+                  if (nn >= 0)
+                  {  if (levels[nn] < 0.01f)
+                        starts[nn]=x;
+                     notes[nn]=note;
+                     value = (freq[index] + gain + range) / (double)range;
+                     levels[nn]=(levels[nn]*2+value)/3;
+                     lengths[nn]=x-starts[nn];
+                     //if (lengths[nn] > 10)
+                     {  float f0 = expf((log(f/440)/log2*12-0.5f-levels[nn]/2)/12.0f*log2)*440;
+                        maxima0[i] = f2pix(f0);
+                        float f1 = expf((log(f/440)/log2*12)/12.0f*log2)*440;
+                        maxima1[i] = f2pix(f1);
+                     }
+                  }
+                  for (int n=0; n<mNumberOfMaxima; n++)
+                  {  levels[n]*=0.99f;
+                     if (levels[n] < 0.01f)
+                     {  starts[n]=x;
+                        lengths[n]=x-starts[n];
+                     }
+                  }
+               }else
+               {  float f0 = expf((log(f/440)/log2*12-1)/12.0f*log2)*440;
+                  maxima0[i] = f2pix(f0);
+                  float f1 = expf((log(f/440)/log2*12)/12.0f*log2)*440;
+                  maxima1[i] = f2pix(f1);
+               }
+            }
+         }
+         int it=0;
+         int oldBin0=-1;
+         bool inMaximum = false;
+#endif //EXPERIMENTAL_FIND_NOTES
+
+         double yy2_base=exp(lmin)/f;
+         float yy2 = yy2_base;
+         double exp_scale_per_height = exp(scale/mid.height);
+         for (int yy = 0; yy < mid.height; yy++) {
+            if(!usePxCache) {
+               if (int(yy2)>=half)
+                  yy2=half-1;
+               if (yy2<0)
+                  yy2=0;
+               float bin0 = float(yy2);
+               yy2_base *= exp_scale_per_height;
+               float yy3 = yy2_base;
+               if (int(yy3)>=half)
+                  yy3=half-1;
+               if (yy3<0)
+                  yy3=0;
+               float bin1 = float(yy3);
+
+#ifdef EXPERIMENTAL_FIND_NOTES
+               if (mFftFindNotes) {
+                  if (it < maximas) {
+                     float i0=maxima0[it];
+                     if (yy >= i0)
+                        inMaximum = true;
+
+                     if (inMaximum) {
+                        float i1=maxima1[it];
+                        if (yy+1 <= i1) {
+                           value=sumFreqValues(freq, x0, bin0, bin1);
+                           if (!mFindNotesQuantize && value < mFindNotesMinA)
+                              value = minColor;
+                           else
+                              value = (value + gain + range) / (double)range;
+                        } else {
+                           it++;
+                           inMaximum = false;
+                           value = minColor;
+                        }
+                     } else {
+                        value = minColor;
+                     }
+                  } else
+                     value = minColor;
+               } else
+#endif //EXPERIMENTAL_FIND_NOTES
+               {
+                  value=sumFreqValues(freq, x0, bin0, bin1);
+                  if (!autocorrelation) {
+                     // Last step converts dB to a 0.0-1.0 range
+                     value = (value + gain + range) / (double)range;
+                  }
+               }
+               if (value > 1.0)
+                  value = float(1.0);
+               if (value < 0.0)
+                  value = float(0.0);
+               clip->GetSpecPxCache()->values[x * mid.height + yy] = value;
+               yy2 = yy2_base;
+            }
+            else
+               value = clip->GetSpecPxCache()->values[x * mid.height + yy];
+
+
+#ifdef EXPERIMENTAL_FIND_NOTES
+            if (mIsGrayscale && mFftFindNotes) {
+               float f = pix2f(yy);
+               float note = logf(f/440.0f)/log2*12+0.5f+1200;
+               int inote = int(fmodf(note, 12.0f));
+               if (inote < 0) 
+                  inote+=12;
+               int scale[14]={0,0,1,2,2,3,3,4,5,5,6,6,7,7};
+               int scsat[14]={0,1,0,0,1,0,1,0,0,1,0,1,0,1};
+               int scaled=scale[inote];
+               int sat   =scsat[inote];
+               float grey=float(value)*float(value);
+	            rv=grey*rainbow_r[sat][scaled];
+	            gv=grey*rainbow_g[sat][scaled];
+	            bv=grey*rainbow_b[sat][scaled];
+            }
+            else
+#endif //EXPERIMENTAL_FIND_NOTES
+            {  GetColorGradient(value, selflag, mIsGrayscale, &rv, &gv, &bv);
+            }
+
+#ifdef EXPERIMENTAL_FFT_Y_GRID
+            if (mFftYGrid && yGrid[yy]) {
+               if (mIsGrayscale) {
+                  rv += 20; rv = rv>255? 255:rv;
+                  gv += 20; gv = gv>255? 255:gv;
+                  bv += 20; bv = bv>255? 255:bv;
+               } else {
+                  rv /= 1.1f;
+                  gv /= 1.1f;
+                  bv /= 1.1f;
+               }
+            }
+#endif //EXPERIMENTAL_FFT_Y_GRID
+
+            int px = ((mid.height - 1 - yy) * mid.width + x) * 3;
+            data[px++] = rv;
+            data[px++] = gv;
+            data[px] = bv;
+         }
+      }
+      x++;
+   }
+
+#ifdef EXPERIMENTAL_DRAW_THREAD
+   TrackUpdateData *udata=new TrackUpdateData;
+   udata->mid=mid;
+   udata->image=image;
+   udata->clip=clip;
+   wxCommandEvent ev(wxEVT_COMMAND_BUTTON_CLICKED, OnUpdateAfterThreadRunID);
+   ev.SetEventObject(win);
+   ev.SetClientData(udata);
+   win->AddPendingEvent(ev);
+#else //!EXPERIMENTAL_DRAW_THREAD
+   // If we get to this point, the clip is actually visible on the
+   // screen, so remember the display rectangle.
+   clip->SetDisplayRect(mid);
+
+   wxBitmap converted = wxBitmap(*image);
+
+   wxMemoryDC memDC;
+
+   memDC.SelectObject(converted);
+
+   dc.Blit(mid.x, mid.y, mid.width, mid.height, &memDC, 0, 0, wxCOPY, FALSE);
+
+   delete image;
+#endif //EXPERIMENTAL_DRAW_THREAD
+
+   delete[] where;
+   delete[] freq;
+
+#ifdef EXPERIMENTAL_FFT_Y_GRID
+   delete[] yGrid;
+#endif //EXPERIMENTAL_FFT_Y_GRID
+
+#ifdef EXPERIMENTAL_FIND_NOTES
+   delete[] indexes;
+#endif //EXPERIMENTAL_FIND_NOTES
+}
+
+void SpecTrackV::InvalidateSpectrumCache(TrackList *tracks)
+{
+   TrackListOfKindIterator iter(Track::Wave, tracks);
+   for (Track *t = iter.First(); t; t = iter.Next()) {
+      InvalidateSpectrumCache((WaveTrack *)t);
+   }
+}
+
+void SpecTrackV::InvalidateSpectrumCache(WaveTrack *track)
+{
+   WaveClipList::compatibility_iterator it;
+   for (it = track->GetClipIterator(); it; it = it->GetNext()) {
+      it->GetData()->GetSpecPxCache()->valid = false;
+   }
+}
+
+float SpecTrackV::sumFreqValues(float *freq, int x0, float bin0, float bin1)
+{
+   float value;
+   if (int(bin1) == int(bin0)) {
+      value = freq[x0+int(bin0)];
+   } else {
+      float binwidth= bin1 - bin0;
+      value = freq[x0 + int(bin0)] * (1.f - bin0 + (int)bin0);
+
+      bin0 = 1 + int (bin0);
+      while (bin0 < int(bin1)) {
+         value += freq[x0 + int(bin0)];
+         bin0 += 1.0;
+      }
+      value += freq[x0 + int(bin1)] * (bin1 - int(bin1));
+      value /= binwidth;
+   }
+   return value;
+}
+
+// (Hue,Saturation,Brightness) to (R,G,B):
+//
+// Hue: [0..1] {0: red, 0.33: green, 0.66: blue, 1: red}
+// Saturation: [0..1] {0: no color, 1: full color}
+// Brightness: [0..1] {0: black, 1: brightest}
+void SpecTrackV::hsb2rgb(float h, float s, float b_, float &r, float &g, float &b)
+{
+	float h2,L,x,y,z;
+	h=fmodf(h,1.0f)*3;
+	h2=fmodf(h,1.0f);
+	if (h2 < 0.5)
+	{	L=b_*(1+s*h2);
+		s=s/(1+s*h2);	//s>=0, h2>=0 always so never s/0
+		x=1+(-1-h2)*s;	y=1-h2*s;	z=1-(1-h2)*s;
+	}else
+	{	L=b_*(1+s*(1-h2));
+		s=s/(1+s*(1-h2));
+		y=1-h2*s;	z=1-(1-h2)*s;	x=1-(2-h2)*s;
+	}
+	x=x*L; 	y=y*L;	z=z*L;
+	if (h < 1.0)
+	{	b=x; r=y; g=z;
+	}else	if (h < 2.0)
+	{	r=x;	g=y;	b=z;
+	}else
+	{	g=x;	b=y;	r=z;
+	}
+	if (r < 0) r=0;
+	if (g < 0) g=0;
+	if (b < 0) b=0;
+	if (r > 1) r=1;
+	if (g > 1) g=1;
+	if (b > 1) b=1;
+}
+
+// Get various preference values
+int SpecTrackV::GetSpectrumWindowSize()
+{
+   return mFFTSize;
+}
Index: src/view/SpecTrackV.h
===================================================================
--- src/view/SpecTrackV.h	(revision 0)
+++ src/view/SpecTrackV.h	(working copy)
@@ -0,0 +1,50 @@
+
+class AUDACITY_DLL_API SpecTrackV:
+virtual public WaveTrackV
+{
+public:
+   SpecTrackV(void);
+   ~SpecTrackV(void);
+
+   void DrawSpectrum(wxEvtHandler *win, WaveTrack *track,
+                     wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
+                     bool autocorrelation, bool logF);
+
+   void DrawClipSpectrum(wxEvtHandler *win, WaveTrack *track, WaveClip *clip,
+                         wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
+                         bool autocorrelation, bool logF);
+
+#ifdef EXPERIMENTAL_DRAW_THREAD
+   void DrawClipSpectrumReal(wxEvtHandler *win, WaveTrack *track, WaveClip *clip,
+                             wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
+                             bool autocorrelation, bool logF, 
+                             wxImage *image, wxRect mid);
+#endif //EXPERIMENTAL_DRAW_THREAD
+
+   void InvalidateSpectrumCache(TrackList *tracks);
+   void InvalidateSpectrumCache(WaveTrack *track);
+
+   float sumFreqValues(float *freq, int x0, float bin0, float bin1);
+
+   void hsb2rgb(float h, float s, float b_, float &r, float &g, float &b);
+
+   int GetSpectrumWindowSize();
+
+   // Preference values
+#ifdef EXPERIMENTAL_FFT_Y_GRID
+   bool mFftYGrid;            // "/Spectrum/FFTYGrid"
+   bool mFftYGridOld;
+#endif //EXPERIMENTAL_FFT_Y_GRID
+
+#ifdef EXPERIMENTAL_FIND_NOTES
+   bool mFftFindNotes;        // "/Spectrum/FFTFindNotes"
+   bool mFftFindNotesOld;
+   float mFindNotesMinA;      // "/Spectrum/FindNotesMinA"
+   int mFindNotesMinAOld;
+   int mFindNotesNOld;
+   int mNumberOfMaxima;       // "/Spectrum/FindNotesN"
+   bool mFindNotesQuantize;   // "/Spectrum/FindNotesQuantize")
+   bool mFindNotesQuantizeOld;
+   int mFFTSize;           // "/Spectrum/FFTSize"
+#endif //EXPERIMENTAL_FIND_NOTES
+};
Index: src/view/TimeTrackV.cpp
===================================================================
--- src/view/TimeTrackV.cpp	(revision 0)
+++ src/view/TimeTrackV.cpp	(working copy)
@@ -0,0 +1,24 @@
+#include "../Audacity.h"
+#include "../TrackArtist.h"
+
+TimeTrackV::TimeTrackV(void):
+TrackV()
+{
+}
+
+TimeTrackV::~TimeTrackV(void)
+{
+}
+
+void TimeTrackV::DrawTimeTrack(TimeTrack *track,
+                                wxDC & dc,
+                                const wxRect & r,
+                                const ViewInfo *viewInfo)
+{
+   track->Draw(dc, r, viewInfo->h, viewInfo->zoom);
+   wxRect envRect = r;
+   envRect.height -= 2;
+   track->GetEnvelope()->DrawPoints(dc, envRect, viewInfo->h, viewInfo->zoom, 
+               false,0.0,1.0);
+}
+
Index: src/view/TimeTrackV.h
===================================================================
--- src/view/TimeTrackV.h	(revision 0)
+++ src/view/TimeTrackV.h	(working copy)
@@ -0,0 +1,11 @@
+
+class AUDACITY_DLL_API TimeTrackV :
+virtual public TrackV
+{
+public:
+   TimeTrackV(void);
+   ~TimeTrackV(void);
+
+   void DrawTimeTrack(TimeTrack *track,
+                      wxDC & dc, const wxRect & r, const ViewInfo *viewInfo);
+};
Index: src/view/TrackV.cpp
===================================================================
--- src/view/TrackV.cpp	(revision 0)
+++ src/view/TrackV.cpp	(working copy)
@@ -0,0 +1,41 @@
+#include "../Audacity.h"
+#include "../TrackArtist.h"
+
+TrackV::TrackV(void):
+BaseV()
+{
+   vrulerv = new VRulerV();
+}
+
+TrackV::~TrackV(void)
+{
+   delete vrulerv; vrulerv=0;
+}
+
+void TrackV::DrawNegativeOffsetTrackArrows(wxDC &dc, const wxRect &r)
+{
+   // Draws two black arrows on the left side of the track to
+   // indicate the user that the track has been time-shifted
+   // to the left beyond t=0.0.
+
+   dc.SetPen(*wxBLACK_PEN);
+   AColor::Line(dc,
+                r.x + 2, r.y + 6,
+                r.x + 8, r.y + 6);
+   AColor::Line(dc,
+                r.x + 2, r.y + 6,
+                r.x + 6, r.y + 2);
+   AColor::Line(dc,
+                r.x + 2, r.y + 6,
+                r.x + 6, r.y + 10);
+   AColor::Line(dc,
+                r.x + 2, r.y + r.height - 8,
+                r.x + 8, r.y + r.height - 8);
+   AColor::Line(dc,
+                r.x + 2, r.y + r.height - 8,
+                r.x + 6, r.y + r.height - 4);
+   AColor::Line(dc,
+                r.x + 2, r.y + r.height - 8,
+                r.x + 6, r.y + r.height - 12);
+}
+
Index: src/view/TrackV.h
===================================================================
--- src/view/TrackV.h	(revision 0)
+++ src/view/TrackV.h	(working copy)
@@ -0,0 +1,15 @@
+
+class AUDACITY_DLL_API TrackV:
+virtual public BaseV
+{
+public:
+   TrackV(void);
+   ~TrackV(void);
+
+   void DrawNegativeOffsetTrackArrows(wxDC & dc, const wxRect & r);
+
+   wxBrush blankBrush;
+   wxPen blankPen;
+
+   VRulerV *vrulerv;
+};
Index: src/view/VRulerV.cpp
===================================================================
--- src/view/VRulerV.cpp	(revision 0)
+++ src/view/VRulerV.cpp	(working copy)
@@ -0,0 +1,394 @@
+#include "../Audacity.h"
+#include "float_cast.h"
+#include "../TrackArtist.h"
+
+VRulerV::VRulerV(void):
+BaseV()
+{
+   ruler=new Ruler;
+}
+
+VRulerV::~VRulerV(void)
+{
+   delete ruler; ruler=0;
+}
+
+void VRulerV::UpdatePrefs()
+{
+   mRange = gPrefs->Read(wxT("/Spectrum/Range"), 80L);
+   mGain = gPrefs->Read(wxT("/Spectrum/Gain"), 20L);
+
+   mdBrange = gPrefs->Read(wxT("/GUI/EnvdBRange"), mdBrange);
+
+   mMaxFreq = gPrefs->Read(wxT("/Spectrum/MaxFreq"), -1);
+   mMinFreq = gPrefs->Read(wxT("/Spectrum/MinFreq"), -1);
+
+   mLogMaxFreq = gPrefs->Read(wxT("/SpectrumLog/MaxFreq"), -1);
+   if( mLogMaxFreq < 0 )
+      mLogMaxFreq = mMaxFreq;
+
+   mLogMinFreq = gPrefs->Read(wxT("/SpectrumLog/MinFreq"), -1);
+   if( mLogMinFreq < 0 )
+      mLogMinFreq = mMinFreq;
+
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+   mFftSkipPoints = gPrefs->Read(wxT("/Spectrum/FFTSkipPoints"), 0L);
+#endif //EXPERIMENTAL_FFT_SKIP_POINTS
+}
+
+void VRulerV::DrawVRuler(Track *t, wxDC * dc, wxRect & r)
+{
+   int kind = t->GetKind();
+
+   // Label and Time tracks do not have a vruler
+   // But give it a beveled area
+   if (kind == Track::Label || kind == Track::Time) {
+      wxRect bev = r;
+      bev.Inflate(-1, -1);
+      bev.width += 1;
+      AColor::BevelTrackInfo(*dc, true, bev);
+
+      return;
+   }
+
+   // All waves have a ruler in the info panel
+   // The ruler needs a bevelled surround.
+   if (kind == Track::Wave) {
+      wxRect bev = r;
+      bev.Inflate(-1, -1);
+      bev.width += 1;
+      AColor::BevelTrackInfo(*dc, true, bev);
+
+      // Pitch doesn't have a ruler
+      if (((WaveTrack *)t)->GetDisplay() == WaveTrack::PitchDisplay) {
+         return;
+      }
+
+      // Right align the ruler
+      wxRect rr = r;
+      rr.width--;
+      if (t->vrulerSize.GetWidth() < r.GetWidth()) {
+         int adj = rr.GetWidth() - t->vrulerSize.GetWidth();
+         rr.x += adj;
+         rr.width -= adj;
+      }
+
+      UpdateVRuler(t, rr);
+
+      ruler->Draw(*dc);
+
+      return;
+   }
+
+#ifdef USE_MIDI
+   // The note track draws a vertical keyboard to label pitches
+   if (kind == Track::Note) {
+      UpdateVRuler(t, r);
+
+      dc->SetPen(*wxTRANSPARENT_PEN);
+      dc->SetBrush(*wxWHITE_BRUSH);
+      wxRect bev = r;
+      bev.x++;
+      bev.y++;
+      bev.width--;
+      bev.height--;
+      dc->DrawRectangle(bev);
+
+      r.y += 2;
+      r.height -= 2;
+
+      //int bottom = GetBottom((NoteTrack *) t, r);
+      NoteTrack *track = (NoteTrack *) t;
+      track->PrepareIPitchToY(r);
+
+      wxPen hilitePen;
+      hilitePen.SetColour(120, 120, 120);
+      wxBrush blackKeyBrush;
+      blackKeyBrush.SetColour(70, 70, 70);
+
+      dc->SetBrush(blackKeyBrush);
+
+      int fontSize = 10;
+#ifdef __WXMSW__
+      fontSize = 8;
+#endif
+
+      wxFont labelFont(fontSize, wxSWISS, wxNORMAL, wxNORMAL);
+      dc->SetFont(labelFont);
+
+      int octave = 0;
+      int obottom = track->GetOctaveBottom(octave);
+      int marg = track->GetNoteMargin();
+      //IPITCH_TO_Y(octave * 12) + PITCH_HEIGHT + 1;
+      while (obottom >= r.y) {
+         dc->SetPen(*wxBLACK_PEN);
+         for (int white = 0; white < 7; white++) {
+            int pos = track->GetWhitePos(white);
+            if (obottom - pos > r.y + marg + 1 && 
+                // don't draw too close to margin line -- it's annoying
+                obottom - pos < r.y + r.height - marg - 3)
+               AColor::Line(*dc, r.x, obottom - pos,
+                            r.x + r.width, obottom - pos);
+         }
+         wxRect br = r;
+         br.height = track->GetPitchHeight();
+         br.x++;
+         br.width = 17;
+         for (int black = 0; black < 5; black++) {
+            br.y = obottom - track->GetBlackPos(black);
+            if (br.y > r.y + marg - 2 && br.y + br.height < r.y + r.height - marg) {
+               dc->SetPen(hilitePen);
+               dc->DrawRectangle(br);
+               dc->SetPen(*wxBLACK_PEN);
+               AColor::Line(*dc,
+                            br.x + 1, br.y + br.height - 1,
+                            br.x + br.width - 1, br.y + br.height - 1);
+               AColor::Line(*dc,
+                            br.x + br.width - 1, br.y + 1,
+                            br.x + br.width - 1, br.y + br.height - 1);
+            }
+         }
+
+         if (octave >= 1 && octave <= 10) {
+            wxString s;
+            // ISO standard: A440 is in the 4th octave, denoted
+            // A4 <- the "4" should be a subscript.
+            s.Printf(wxT("C%d"), octave - 1);
+            long width, height;
+            dc->GetTextExtent(s, &width, &height);
+            if (obottom - height + 4 > r.y &&
+                obottom + 4 < r.y + r.height) {
+               dc->SetTextForeground(wxColour(60, 60, 255));
+               dc->DrawText(s, r.x + r.width - width,
+                            obottom - height + 2);
+            }
+         }
+         obottom = track->GetOctaveBottom(++octave);
+      }
+      // draw lines delineating the out-of-bounds margins
+      dc->SetPen(*wxBLACK_PEN);
+      // you would think the -1 offset here should be -2 to match the
+      // adjustment to r.y (see above), but -1 produces correct output
+      AColor::Line(*dc, r.x, r.y + marg - 1, r.x + r.width, r.y + marg - 1);
+      // since the margin gives us the bottom of the line, 
+      // the extra -1 gets us to the top
+      AColor::Line(*dc, r.x, r.y + r.height - marg - 1,
+                        r.x + r.width, r.y + r.height - marg - 1);
+
+   }
+#endif // USE_MIDI
+
+}
+
+void VRulerV::UpdateVRuler(Track *t, wxRect & r)
+{
+   // Label tracks do not have a vruler
+   if (t->GetKind() == Track::Label) {
+      return;
+   }
+
+   // All waves have a ruler in the info panel
+   // The ruler needs a bevelled surround.
+   if (t->GetKind() == Track::Wave) {
+      WaveTrack *wt = (WaveTrack *)t;
+      int display = wt->GetDisplay();
+
+      if (display == WaveTrack::WaveformDisplay) {
+         // Waveform
+
+         float min, max;
+         wt->GetDisplayBounds(&min, &max);
+
+         ruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1);
+         ruler->SetOrientation(wxVERTICAL);
+         ruler->SetRange(max, min);
+         ruler->SetFormat(Ruler::RealFormat);
+         ruler->SetUnits(wxT(""));
+         ruler->SetLabelEdges(false);
+         ruler->SetLog(false);
+      }
+      else if (display == WaveTrack::WaveformDBDisplay) {
+         // Waveform (db)
+
+         ruler->SetUnits(wxT(""));
+
+         float min, max;
+         wt->GetDisplayBounds(&min, &max);
+
+         if (max > 0) {
+            int top = 0;
+            float topval = 0;
+            int bot = r.height;
+            float botval = -mdBrange;
+
+            if (min < 0) {
+               bot = top + (int)((max / (max-min))*(bot-top));
+               min = 0;
+            }
+
+            if (max > 1) {
+               top += (int)((max-1)/(max-min) * (bot-top));
+               max = 1;
+            }
+
+            if (max < 1)
+               topval = -((1-max)*mdBrange);
+
+            if (min > 0) {
+               botval = -((1-min)*mdBrange);
+            }
+
+            if (topval > botval && bot > top+10) {
+               ruler->SetBounds(r.x, r.y+top+1, r.x + r.width, r.y + bot-1);
+               ruler->SetOrientation(wxVERTICAL);
+               ruler->SetRange(topval, botval);
+               ruler->SetFormat(Ruler::LinearDBFormat);
+               ruler->SetLabelEdges(true);
+               ruler->SetLog(false);
+            }
+         }
+      }
+      else if (display == WaveTrack::SpectrumDisplay) {
+         // Spectrum
+
+         if (r.height < 60)
+            return;
+
+         double rate = wt->GetRate();
+         int freq = lrint(rate/2.);
+
+         int maxFreq = GetSpectrumMaxFreq(freq);
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+         maxFreq/=(mFftSkipPoints+1);
+#endif //EXPERIMENTAL_FFT_SKIP_POINTS
+         if(maxFreq > freq)
+            maxFreq = freq;
+
+         int minFreq = GetSpectrumMinFreq(0);
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+         minFreq/=(mFftSkipPoints+1);
+#endif //EXPERIMENTAL_FFT_SKIP_POINTS
+         if(minFreq < 0)
+            minFreq = 0;
+
+         /*
+            draw the ruler
+            we will use Hz if maxFreq is < 2000, otherwise we represent kHz,
+            and append to the numbers a "k"
+         */
+         ruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1);
+         ruler->SetOrientation(wxVERTICAL);
+         ruler->SetFormat(Ruler::RealFormat);
+         ruler->SetLabelEdges(true);
+         // use kHz in scale, if appropriate
+         if (maxFreq>=2000) {
+            ruler->SetRange((maxFreq/1000.), (minFreq/1000.));
+            ruler->SetUnits(wxT("k"));
+         } else {
+            // use Hz
+            ruler->SetRange(int(maxFreq), int(minFreq));
+            ruler->SetUnits(wxT(""));
+         }
+         ruler->SetLog(false);
+      }
+      else if (display == WaveTrack::SpectrumLogDisplay) {
+         // SpectrumLog
+
+         if (r.height < 10)
+            return;
+
+         double rate = wt->GetRate();
+         int freq = lrint(rate/2.);
+
+         int maxFreq = GetSpectrumLogMaxFreq(freq);
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+         maxFreq/=(mFftSkipPoints+1);
+#endif //EXPERIMENTAL_FFT_SKIP_POINTS
+         if(maxFreq > freq)
+            maxFreq = freq;
+
+         int minFreq = GetSpectrumLogMinFreq(freq/1000.0);
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+         minFreq/=(mFftSkipPoints+1);
+#endif //EXPERIMENTAL_FFT_SKIP_POINTS
+         if(minFreq < 1)
+            minFreq = 1;
+
+         /*
+            draw the ruler
+            we will use Hz if maxFreq is < 2000, otherwise we represent kHz,
+            and append to the numbers a "k"
+         */
+         ruler->SetBounds(r.x, r.y+1, r.x + r.width, r.y + r.height-1);
+         ruler->SetOrientation(wxVERTICAL);
+         ruler->SetFormat(Ruler::IntFormat);
+         ruler->SetLabelEdges(true);
+         ruler->SetRange(maxFreq, minFreq);
+         ruler->SetUnits(wxT(""));
+         ruler->SetLog(true);
+      }
+      else if (display == WaveTrack::PitchDisplay) {
+         // Pitch
+      }
+   }
+
+#ifdef USE_MIDI
+   // The note track isn't drawing a ruler at all!
+   // But it needs to!
+   else if (t->GetKind() == Track::Note) {
+      ruler->SetBounds(r.x, r.y+1, r.x + 1, r.y + r.height-1);
+      ruler->SetOrientation(wxVERTICAL);
+   }
+#endif // USE_MIDI
+
+   ruler->GetMaxSize(&t->vrulerSize.x, &t->vrulerSize.y);
+}
+
+int VRulerV::GetSpectrumMinFreq(int deffreq)
+{
+   return mMinFreq < 0 ? deffreq : mMinFreq;
+}
+
+int VRulerV::GetSpectrumMaxFreq(int deffreq)
+{
+   return mMaxFreq < 0 ? deffreq : mMaxFreq;
+}
+
+int VRulerV::GetSpectrumLogMinFreq(int deffreq)
+{
+   return mLogMinFreq < 0 ? deffreq : mLogMinFreq;
+}
+
+int VRulerV::GetSpectrumLogMaxFreq(int deffreq)
+{
+   return mLogMaxFreq < 0 ? deffreq : mLogMaxFreq;
+}
+
+// Set various preference values
+void VRulerV::SetSpectrumMinFreq(int freq)
+{
+   mMinFreq = freq;
+}
+
+void VRulerV::SetSpectrumMaxFreq(int freq)
+{
+   mMaxFreq = freq;
+}
+
+void VRulerV::SetSpectrumLogMinFreq(int freq)
+{
+   mLogMinFreq = freq;
+}
+
+void VRulerV::SetSpectrumLogMaxFreq(int freq)
+{
+   mLogMaxFreq = freq;
+}
+
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+int VRulerV::GetSpectrumFftSkipPoints()
+{
+   return mFftSkipPoints;
+}
+#endif
+
Index: src/view/VRulerV.h
===================================================================
--- src/view/VRulerV.h	(revision 0)
+++ src/view/VRulerV.h	(working copy)
@@ -0,0 +1,43 @@
+
+class AUDACITY_DLL_API VRulerV:
+virtual public BaseV
+{
+public:
+   VRulerV(void);
+   ~VRulerV(void);
+
+   void UpdatePrefs();
+
+   void DrawVRuler(Track *t, wxDC *dc, wxRect & r);
+   void UpdateVRuler(Track *t, wxRect & r);
+
+   int GetSpectrumMinFreq(int deffreq);
+   int GetSpectrumMaxFreq(int deffreq);
+   int GetSpectrumLogMinFreq(int deffreq);
+   int GetSpectrumLogMaxFreq(int deffreq);
+
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+   int GetSpectrumFftSkipPoints();
+#endif
+
+   void SetSpectrumMinFreq(int freq);
+   void SetSpectrumMaxFreq(int freq);
+   void SetSpectrumLogMinFreq(int freq);
+   void SetSpectrumLogMaxFreq(int freq);
+
+   Ruler *ruler;
+
+   float mdBrange;            // "/GUI/EnvdBRange"
+
+   int mLogMaxFreq;           // "/SpectrumLog/MaxFreq"
+   int mLogMinFreq;           // "/SpectrumLog/MinFreq"
+   int mMaxFreq;              // "/Spectrum/MaxFreq"
+   int mMinFreq;              // "/Spectrum/MinFreq"
+
+   int mRange;                // "/Spectrum/Range"
+   int mGain;                 // "/Spectrum/Gain"
+
+#ifdef EXPERIMENTAL_FFT_SKIP_POINTS
+   int mFftSkipPoints;        // "/Spectrum/FFTSkipPoints"
+#endif //EXPERIMENTAL_FFT_SKIP_POINTS
+};
Index: src/view/WaveTrackV.cpp
===================================================================
--- src/view/WaveTrackV.cpp	(revision 0)
+++ src/view/WaveTrackV.cpp	(working copy)
@@ -0,0 +1,1020 @@
+#include "../Audacity.h"
+#include "../TrackArtist.h"
+#include "../AllThemeResources.h"
+
+#undef PROFILE_WAVEFORM
+#ifdef PROFILE_WAVEFORM
+   #ifdef __WXMSW__
+      #include <time.h>
+   #else
+      #include <sys/time.h>
+   #endif
+double gWaveformTimeTotal = 0;
+int gWaveformTimeCount = 0;
+#endif
+
+WaveTrackV::WaveTrackV(void):
+TrackV()
+{
+}
+
+WaveTrackV::~WaveTrackV(void)
+{
+}
+
+void WaveTrackV::DrawWaveform(WaveTrack *track,
+                               wxDC & dc,
+                               const wxRect & r,
+                               const ViewInfo *viewInfo,
+                               bool drawEnvelope,
+                               bool drawSamples,
+                               bool drawSliders,
+                               bool dB,
+                               bool muted)
+{
+   DrawBackgroundWithSelection(&dc, r, track, blankSelectedBrush, blankBrush,
+         viewInfo->sel0, viewInfo->sel1, viewInfo->h, viewInfo->zoom);
+
+   for (WaveClipList::compatibility_iterator it = track->GetClipIterator(); it; it = it->GetNext())
+      DrawClipWaveform(track, it->GetData(), dc, r, viewInfo,
+                       drawEnvelope, drawSamples, drawSliders,
+                       dB, muted);
+
+   // Update cache for locations, e.g. cutlines and merge points
+   track->UpdateLocationsCache();
+   
+   for (int i = 0; i<track->GetNumCachedLocations(); i++) {
+      WaveTrack::Location loc = track->GetCachedLocation(i);
+      double x = (loc.pos - viewInfo->h) * viewInfo->zoom;
+      if (x >= 0 && x < r.width) {
+         dc.SetPen(*wxGREY_PEN);
+         AColor::Line(dc, (int) (r.x + x - 1), r.y, (int) (r.x + x - 1), r.y + r.height);
+         if (loc.typ == WaveTrack::locationCutLine) {
+            dc.SetPen(*wxRED_PEN);
+         }
+         else {
+            dc.SetPen(*wxBLACK_PEN);
+         }
+         AColor::Line(dc, (int) (r.x + x), r.y, (int) (r.x + x), r.y + r.height);
+         dc.SetPen(*wxGREY_PEN);
+         AColor::Line(dc, (int) (r.x + x + 1), r.y, (int) (r.x + x + 1), r.y + r.height);
+      }
+   }
+}
+
+void WaveTrackV::DrawClipWaveform(WaveTrack *track,
+                                   WaveClip *clip,
+                                   wxDC & dc,
+                                   const wxRect & r,
+                                   const ViewInfo *viewInfo,
+                                   bool drawEnvelope,
+                                   bool drawSamples,
+                                   bool drawSliders,
+                                   bool dB,
+                                   bool muted)
+{
+#if PROFILE_WAVEFORM
+#   ifdef __WXMSW__
+   __time64_t tv0, tv1;
+   _time64(&tv0);
+#   else
+   struct timeval tv0, tv1;
+   gettimeofday(&tv0, NULL);
+#   endif
+#endif
+   double h = viewInfo->h;          //The horizontal position in seconds
+   double pps = viewInfo->zoom;     //points-per-second--the zoom level
+   double sel0 = viewInfo->sel0;    //left selection bound
+   double sel1 = viewInfo->sel1;    //right selection bound
+   double trackLen = clip->GetEndTime() - clip->GetStartTime();
+   double tOffset = clip->GetOffset();
+   double rate = clip->GetRate();
+   double sps = 1./rate;            //seconds-per-sample
+
+   //If the track isn't selected, make the selection empty
+   if (!track->GetSelected() && !track->IsSyncLockSelected()) {
+      sel0 = sel1 = 0.0;
+   }
+
+   //Some bookkeeping time variables:
+   double tstep = 1.0 / pps;                  // Seconds per point
+   double tpre = h - tOffset;                 // offset corrected time of
+                                              //  left edge of display
+   double tpost = tpre + (r.width * tstep);   // offset corrected time of
+                                              //  right edge of display
+
+   // Determine whether we should show individual samples
+   // or draw circular points as well
+   bool showIndividualSamples = (pps / rate > 0.5);   //zoomed in a lot
+   bool showPoints = (pps / rate > 3.0);              //zoomed in even more
+
+   // Calculate actual selection bounds so that t0 > 0 and t1 < the
+   // end of the track 
+
+   double t0 = (tpre >= 0.0 ? tpre : 0.0);
+   double t1 = (tpost < trackLen - sps * .99 ? tpost : trackLen - sps * .99);
+   if (showIndividualSamples) {
+      // adjustment so that the last circular point doesn't appear
+      // to be hanging off the end
+      t1 += 2. / pps;
+   }
+
+   // Make sure t1 (the right bound) is greater than 0
+   if (t1 < 0.0) {
+      t1 = 0.0;
+   }
+
+   // Make sure t1 is greater than t0
+   if (t0 > t1) {
+      t0 = t1;
+   }
+
+   // Calculate sample-based offset-corrected selection 
+
+   // Use the WaveTrack method to show what is selected and 'should' be copied, pasted etc.
+   sampleCount ssel0 = wxMax(0, track->TimeToLongSamples(sel0 - tOffset));
+   sampleCount ssel1 = wxMax(0, track->TimeToLongSamples(sel1 - tOffset));
+
+   //trim selection so that it only contains the actual samples
+   if (ssel0 != ssel1 && ssel1 > (sampleCount)(0.5 + trackLen * rate)) {
+      ssel1 = (sampleCount)(0.5 + trackLen * rate);
+   }
+
+   // The variable "mid" will be the rectangle containing the
+   // actual waveform, as opposed to any blank area before
+   // or after the track.
+   wxRect mid = r;
+
+   dc.SetPen(*wxTRANSPARENT_PEN);
+
+   // If the left edge of the track is to the right of the left
+   // edge of the display, then there's some blank area to the
+   // left of the track.  Reduce the "mid"
+   if (tpre < 0) {
+      double delta = r.width;
+      if (t0 < tpost) {
+         delta = (int) ((t0 - tpre) * pps);
+      }
+      mid.x += (int)delta;
+      mid.width -= (int)delta;
+   }
+
+   // If the right edge of the track is to the left of the the right
+   // edge of the display, then there's some blank area to the right
+   // of the track.  Reduce the "mid" rect by the
+   // size of the blank area.
+   if (tpost > t1) {
+      wxRect post = r;
+      if (t1 > tpre) {
+         post.x += (int) ((t1 - tpre) * pps);
+      }
+      post.width = r.width - (post.x - r.x);
+      mid.width -= post.width;
+   }
+
+   // The "mid" rect contains the part of the display actually
+   // containing the waveform.  If it's empty, we're done.
+   if (mid.width <= 0) {
+#if PROFILE_WAVEFORM
+#   ifdef __WXMSW__
+      _time64(&tv1);
+      double elapsed = _difftime64(tv1, tv0);
+#   else
+      gettimeofday(&tv1, NULL);
+      double elapsed =
+         (tv1.tv_sec + tv1.tv_usec*0.000001) -
+         (tv0.tv_sec + tv0.tv_usec*0.000001);
+#   endif
+      gWaveformTimeTotal += elapsed;
+      gWaveformTimeCount++;
+#endif
+
+     return;
+   }
+
+   // If we get to this point, the clip is actually visible on the
+   // screen, so remember the display rectangle.
+   clip->SetDisplayRect(mid);
+
+   // The bounds (controlled by vertical zooming; -1.0...1.0
+   // by default)
+   float zoomMin, zoomMax;
+   track->GetDisplayBounds(&zoomMin, &zoomMax);
+
+   // Arrays containing the shape of the waveform - each array
+   // has one value per pixel.
+   float *min = new float[mid.width];
+   float *max = new float[mid.width];
+   float *rms = new float[mid.width];
+   sampleCount *where = new sampleCount[mid.width + 1];
+   int *bl = new int[mid.width];
+   bool isLoadingOD = false;//true if loading on demand block in sequence.
+
+   // The WaveClip class handles the details of computing the shape
+   // of the waveform.  The only way GetWaveDisplay will fail is if
+   // there's a serious error, like some of the waveform data can't
+   // be loaded.  So if the function returns false, we can just exit.
+   if (!clip->GetWaveDisplay(min, max, rms, bl, where,
+                             mid.width, t0, pps, isLoadingOD)) {
+      delete[] min;
+      delete[] max;
+      delete[] rms;
+      delete[] where;
+      delete[] bl;
+      return;
+   }
+
+   // Get the values of the envelope corresponding to each pixel
+   // in the display, and use these to compute the height of the
+   // track at each pixel
+
+   double *envValues = new double[mid.width];
+   clip->GetEnvelope()->GetValues(envValues, mid.width, t0 + tOffset, tstep);
+
+   // Draw the background of the track, outlining the shape of
+   // the envelope and using a colored pen for the selected
+   // part of the waveform
+   DrawWaveformBackground(dc, mid, envValues, zoomMin, zoomMax, dB,
+                          where, ssel0, ssel1, drawEnvelope,
+                          !track->GetSelected());
+
+   if (!showIndividualSamples) {
+      DrawMinMaxRMS(dc, mid, envValues, zoomMin, zoomMax, dB,
+                    min, max, rms, bl, isLoadingOD, muted);
+   }
+   else {
+      DrawIndividualSamples(dc, mid, zoomMin, zoomMax, dB,
+                            clip, t0, pps, h,
+                            drawSamples, showPoints, muted);
+   }
+
+   if (drawEnvelope) {
+      DrawEnvelope(dc, mid, envValues, zoomMin, zoomMax, dB);
+      clip->GetEnvelope()->DrawPoints(dc, r, h, pps, dB, zoomMin, zoomMax);
+   }
+
+   delete[] envValues;
+   delete[] min;
+   delete[] max;
+   delete[] rms;
+   delete[] where;
+   delete[] bl;
+   
+   // Draw arrows on the left side if the track extends to the left of the
+   // beginning of time.  :)
+   if (h == 0.0 && tOffset < 0.0) {
+      DrawNegativeOffsetTrackArrows(dc, r);
+   }
+
+   if (drawSliders) {
+      DrawTimeSlider(track, dc, r, viewInfo, true);  // directed right
+      DrawTimeSlider(track, dc, r, viewInfo, false); // directed left
+   }
+
+   // Draw clip edges
+   dc.SetPen(*wxGREY_PEN);
+   if (tpre < 0) {
+      AColor::Line(dc,
+                   mid.x - 1, mid.y,
+                   mid.x - 1, mid.y + r.height);
+   }
+   if (tpost > t1) {
+      AColor::Line(dc,
+                   mid.x + mid.width, mid.y,
+                   mid.x + mid.width, mid.y + r.height);
+   }
+
+#if PROFILE_WAVEFORM
+#   ifdef __WXMSW__
+   _time64(&tv1);
+   double elapsed = _difftime64(tv1, tv0);
+#   else
+   gettimeofday(&tv1, NULL);
+   double elapsed =
+      (tv1.tv_sec + tv1.tv_usec*0.000001) -
+      (tv0.tv_sec + tv0.tv_usec*0.000001);
+#   endif
+   gWaveformTimeTotal += elapsed;
+   gWaveformTimeCount++;
+   wxPrintf(wxT("Avg waveform drawing time: %f\n"),
+          gWaveformTimeTotal / gWaveformTimeCount);
+#endif
+}
+
+void WaveTrackV::DrawWaveformBackground(wxDC &dc, const wxRect &r, const double env[],
+                                         float zoomMin, float zoomMax, bool dB,
+                                         const sampleCount where[],
+                                         sampleCount ssel0, sampleCount ssel1,
+                                         bool drawEnvelope, bool bIsSyncLockSelected)
+{
+   // Visually (one vertical slice of the waveform background, on its side;
+   // the "*" is the actual waveform background we're drawing
+   //
+   //1.0                              0.0                             -1.0
+   // |--------------------------------|--------------------------------|
+   //      ***************                           ***************
+   //      |             |                           |             |
+   //    maxtop        maxbot                      mintop        minbot
+
+   int h = r.height;
+   int halfHeight = wxMax(h / 2, 1);
+   int maxtop, lmaxtop = 0;
+   int mintop, lmintop = 0;
+   int maxbot, lmaxbot = 0;
+   int minbot, lminbot = 0;
+   bool sel, lsel = false;
+   int x, lx = 0;
+   int l, w;
+
+   dc.SetPen(*wxTRANSPARENT_PEN);
+   dc.SetBrush(blankBrush);
+   dc.DrawRectangle(r);
+
+   for (x = 0; x < r.width; x++) {
+      // First we compute the truncated shape of the waveform background.
+      // If drawEnvelope is true, then we compute the lower border of the
+      // envelope.
+
+      maxtop = GetWaveYPos(env[x], zoomMin, zoomMax,
+                               h, dB, true, mdBrange, true);
+      maxbot = GetWaveYPos(env[x], zoomMin, zoomMax,
+                               h, dB, false, mdBrange, true);
+
+      mintop = GetWaveYPos(-env[x], zoomMin, zoomMax,
+                               h, dB, false, mdBrange, true);
+      minbot = GetWaveYPos(-env[x], zoomMin, zoomMax,
+                               h, dB, true, mdBrange, true);
+
+      // Make sure it's odd so that a that max and min mirror each other
+      mintop +=1;
+      minbot +=1;
+
+      if (!drawEnvelope || maxbot > mintop) {
+         maxbot = halfHeight;
+         mintop = halfHeight;
+      }
+
+      // We don't draw selection color for sync-lock selected tracks.
+      sel = (ssel0 <= where[x] && where[x + 1] < ssel1) && !bIsSyncLockSelected;
+
+      if (lmaxtop == maxtop &&
+          lmintop == mintop &&
+          lmaxbot == maxbot &&
+          lminbot == minbot &&
+          lsel == sel) {
+         continue;
+      }
+
+      dc.SetBrush(lsel ? selectedBrush : unselectedBrush);
+
+      l = r.x + lx;
+      w = x - lx;
+      if (lmaxbot != lmintop - 1) {
+         dc.DrawRectangle(l, r.y + lmaxtop, w, lmaxbot - lmaxtop);
+         dc.DrawRectangle(l, r.y + lmintop, w, lminbot - lmintop);
+      }
+      else {
+         dc.DrawRectangle(l, r.y + lmaxtop, w, lminbot - lmaxtop);
+      }
+
+      lmaxtop = maxtop;
+      lmintop = mintop;
+      lmaxbot = maxbot;
+      lminbot = minbot;
+      lsel = sel;
+      lx = x;
+   }
+
+   dc.SetBrush(lsel ? selectedBrush : unselectedBrush);
+   l = r.x + lx;
+   w = x - lx;
+   if (lmaxbot != lmintop - 1) {
+      dc.DrawRectangle(l, r.y + lmaxtop, w, lmaxbot - lmaxtop);
+      dc.DrawRectangle(l, r.y + lmintop, w, lminbot - lmintop);
+   }
+   else {
+      dc.DrawRectangle(l, r.y + lmaxtop, w, lminbot - lmaxtop);
+   }
+
+   // If sync-lock selected, draw in linked graphics.
+   if (bIsSyncLockSelected && ssel0 < ssel1) {
+      // Find the beginning/end of the selection
+      int begin, end;
+      for (x = 0; x < r.width && where[x] < ssel0; ++x);
+      begin = x;
+      for (; x < r.width && where[x] < ssel1; ++x);
+      end = x;
+      DrawSyncLockTiles(&dc, wxRect(r.x + begin, r.y, end - 1 - begin, r.height));
+   }
+
+   //OK, the display bounds are between min and max, which
+   //is spread across r.height.  Draw the line at the proper place.
+
+   if (zoomMin < 0 && zoomMax > 0) {
+      int half = (int)((zoomMax / (zoomMax - zoomMin)) * h);
+      dc.SetPen(*wxBLACK_PEN);
+      AColor::Line(dc, r.x, r.y + half, r.x + r.width, r.y + half);
+   }
+}
+
+void WaveTrackV::DrawBackgroundWithSelection(wxDC *dc, const wxRect &r,
+      Track *track, wxBrush &selBrush, wxBrush &unselBrush,
+      double sel0, double sel1, double h, double pps)
+{
+   //MM: Draw background. We should optimize that a bit more.
+   //AWD: "+ 1.5" and "+ 2.5" throughout match code in
+   //AdornedRulerPanel::DoDrawSelection() and make selection line up with ruler.
+   //I don't know if/why this is correct.
+
+   dc->SetPen(*wxTRANSPARENT_PEN);
+   if (track->GetSelected() || track->IsSyncLockSelected())
+   {
+      // Rectangles before, within, after the selction
+      wxRect before = r;
+      wxRect within = r;
+      wxRect after = r;
+
+      before.width = int ((sel0 - h) * pps + 2.5);
+      if (before.GetRight() > r.GetRight()) {
+         before.width = r.width;
+      }
+
+      if (before.width > 0) {
+         dc->SetBrush(unselBrush);
+         dc->DrawRectangle(before);
+
+         within.x = before.GetRight();
+      }
+      within.width = r.x + int ((sel1 - h) * pps + 2.5) - within.x;
+
+      if (within.GetRight() > r.GetRight()) {
+         within.width = r.GetRight() - within.x;
+      }
+
+      if (within.width > 0) {
+         if (track->GetSelected()) {
+            dc->SetBrush(selBrush);
+            dc->DrawRectangle(within);
+         }
+         else {
+            // Per condition above, track must be sync-lock selected
+            dc->SetBrush(unselBrush);
+            dc->DrawRectangle(within);
+            DrawSyncLockTiles(dc, within);
+         }
+
+         after.x = within.GetRight();
+      }
+      else {
+         // `within` not drawn; start where it would have gone
+         after.x = within.x;
+      }
+
+      after.width = r.GetRight() - after.x;
+      if (after.width > 0) {
+         dc->SetBrush(unselBrush);
+         dc->DrawRectangle(after);
+      }
+   }
+   else
+   {
+      // Track not selected; just draw background
+      dc->SetBrush(unselBrush);
+      dc->DrawRectangle(r);
+   }
+}
+
+void WaveTrackV::DrawMinMaxRMS(wxDC &dc, const wxRect &r, const double env[],
+                                float zoomMin, float zoomMax, bool dB,
+                                const float min[], const float max[], const float rms[],
+                                const int bl[], bool showProgress, bool muted)
+{
+   // Display a line representing the
+   // min and max of the samples in this region
+   int lasth1 = std::numeric_limits<int>::max();
+   int lasth2 = std::numeric_limits<int>::min();
+   int h1;
+   int h2;
+   int *r1 = new int[r.width];
+   int *r2 = new int[r.width];
+   int *clipped = NULL;
+   int clipcnt = 0;
+   int x;
+
+   if (mShowClipping) {
+      clipped =  new int[r.width];
+   }
+
+   long pixAnimOffset = (long)fabs((double)(wxDateTime::Now().GetTicks() * -10)) +
+      wxDateTime::Now().GetMillisecond() / 100; //10 pixels a second
+   
+   bool drawStripes = true;
+   bool drawWaveform = true;
+
+   dc.SetPen(muted ? muteSamplePen : samplePen);
+   for (x = 0; x < r.width; x++) {
+      int xx = r.x + x;
+      double v;
+
+      v = min[x] * env[x];
+      if (mShowClipping && v <= -MAX_AUDIO) {
+         if (clipcnt == 0 || clipped[clipcnt - 1] != xx) {
+            clipped[clipcnt++] = xx;
+         }
+      }
+      h1 = GetWaveYPos(v, zoomMin, zoomMax,
+                       r.height, dB, true, mdBrange, true);
+
+      v = max[x] * env[x];
+      if (mShowClipping && v >= MAX_AUDIO) {
+         if (clipcnt == 0 || clipped[clipcnt - 1] != xx) {
+            clipped[clipcnt++] = xx;
+         }
+      }
+      h2 = GetWaveYPos(v, zoomMin, zoomMax,
+                       r.height, dB, true, mdBrange, true);
+
+      // JKC: This adjustment to h1 and h2 ensures that the drawn
+      // waveform is continuous.
+      if (x > 0) {
+         if (h1 < lasth2) {
+            h1 = lasth2 - 1;
+         }
+         if (h2 > lasth1) {
+            h2 = lasth1 + 1;
+         }
+      }
+      lasth1 = h1;
+      lasth2 = h2;
+
+      r1[x] = GetWaveYPos(-rms[x] * env[x], zoomMin, zoomMax,
+                          r.height, dB, true, mdBrange, true);
+      r2[x] = GetWaveYPos(rms[x] * env[x], zoomMin, zoomMax,
+                          r.height, dB, true, mdBrange, true);
+
+      // Make sure the rms isn't larger than the waveform min/max
+      if (r1[x] > h1 - 1) {
+         r1[x] = h1 - 1;
+      }
+      if (r2[x] < h2 + 1) {
+         r2[x] = h2 + 1;
+      }
+      if (r2[x] > r1[x]) {
+         r2[x] = r1[x];
+      }
+
+      if (bl[x] <= -1) {
+         if (drawStripes) {
+            // TODO:unify with buffer drawing.
+            dc.SetPen((bl[x] % 2) ? muteSamplePen : samplePen);
+            for (int y = 0; y < r.height / 25 + 1; y++) {
+               // we are drawing over the buffer, but I think DrawLine takes care of this.
+               AColor::Line(dc,
+                            xx,
+                            r.y + 25 * y + (x /*+pixAnimOffset*/) % 25,
+                            xx, 
+                            r.y + 25 * y + (x /*+pixAnimOffset*/) % 25 + 6); //take the min so we don't draw past the edge
+            }
+         }
+         
+         // draw a dummy waveform - some kind of sinusoid.  We want to animate it so the user knows it's a dummy.  Use the second's unit of a get time function.
+         // Lets use a triangle wave for now since it's easier - I don't want to use sin() or make a wavetable just for this.
+         if (drawWaveform) {
+            int triX;
+            dc.SetPen(samplePen);
+            triX = fabs((double)((x + pixAnimOffset) % (2 * r.height)) - r.height) + r.height;
+            for (int y = 0; y < r.height; y++) {
+               if ((y + triX) % r.height == 0) {
+                  dc.DrawPoint(xx, r.y + y);
+               }
+            }
+         }
+      }
+      else {
+         AColor::Line(dc, xx, r.y + h2, xx, r.y + h1);
+      }
+   }
+
+   dc.SetPen(muted ? muteRmsPen : rmsPen);
+   for (int x = 0; x < r.width; x++) {
+      int xx = r.x + x;
+      if (bl[x] <= -1) {
+      }
+      else if (r1[x] != r2[x]) {
+         AColor::Line(dc, xx, r.y + r2[x], xx, r.y + r1[x]);
+      }
+   }
+
+   // Draw the clipping lines
+   if (clipcnt) {
+      dc.SetPen(muted ? muteClippedPen : clippedPen);
+      while (--clipcnt >= 0) {
+         int xx = clipped[clipcnt];
+         AColor::Line(dc, xx, r.y, xx, r.y + r.height);
+      }
+   }
+
+   if (mShowClipping) {
+      delete[] clipped;
+   }
+
+   delete [] r1;
+   delete [] r2;
+}
+
+void WaveTrackV::DrawIndividualSamples(wxDC &dc, const wxRect &r,
+                                        float zoomMin, float zoomMax, bool dB,
+                                        WaveClip *clip,
+                                        double t0, double pps, double h,
+                                        bool drawSamples, bool showPoints, bool muted)
+{
+   double rate = clip->GetRate();
+   sampleCount s0 = (sampleCount) (t0 * rate + 0.5);
+   sampleCount slen = (sampleCount) (r.width * rate / pps + 0.5);
+   sampleCount snSamples = clip->GetNumSamples(); 
+   
+   slen += 4;
+
+   if (s0 > snSamples) {
+      return;
+   }
+
+   if (s0 + slen > snSamples) {
+      slen = snSamples - s0;
+   }
+
+   float *buffer = new float[slen];
+   clip->GetSamples((samplePtr)buffer, floatSample, s0, slen);
+
+   int *xpos = new int[slen];
+   int *ypos = new int[slen];
+   int *clipped = NULL;
+   int clipcnt = 0;
+   sampleCount s;
+
+   if (mShowClipping) {
+      clipped = new int[slen];
+   }
+
+   dc.SetPen(muted ? muteSamplePen : samplePen);
+   
+   for (s = 0; s < slen; s++) {
+      double tt = (s / rate);
+
+      // MB: (s0/rate - t0) is the distance from the left edge of the screen
+      //     to the first sample.
+      int xx = (int)rint((tt + s0 / rate - t0) * pps);
+      
+      if (xx < -10000) {
+         xx = -10000;
+      }
+      if (xx > 10000) {
+         xx = 10000;
+      }
+      
+      xpos[s] = xx;
+
+      // t0 + clip->GetOffset() is 'h' (the absolute time of the left edge) for 'r'.
+      tt = buffer[s] * clip->GetEnvelope()->GetValueAtX(xx + r.x, r, t0 + clip->GetOffset(), pps);
+      if (mShowClipping && (tt <= -MAX_AUDIO || tt >= MAX_AUDIO)) {
+         clipped[clipcnt++] = xx;
+      }
+      ypos[s] = GetWaveYPos(tt, zoomMin, zoomMax,
+                            r.height, dB, true, mdBrange, false);
+      if (ypos[s] < -1) {
+         ypos[s] = -1;
+      }
+      if (ypos[s] > r.height) {
+         ypos[s] = r.height;
+      }
+   }
+
+   // Draw lines
+   for (s = 0; s < slen - 1; s++) {
+      AColor::Line(dc,
+                   r.x + xpos[s], r.y + ypos[s],
+                   r.x + xpos[s + 1], r.y + ypos[s + 1]);
+   }
+
+   if (showPoints) {
+      // Draw points
+      int tickSize= drawSamples ? 4 : 3;// Bigger ellipses when draggable.
+      wxRect pr;
+      pr.width = tickSize;
+      pr.height = tickSize;
+      //different colour when draggable.
+      dc.SetBrush( drawSamples ? dragsampleBrush : sampleBrush);
+      for (s = 0; s < slen; s++) {
+         if (ypos[s] >= 0 && ypos[s] < r.height) {
+            pr.x = r.x + xpos[s] - tickSize/2;
+            pr.y = r.y + ypos[s] - tickSize/2;
+            dc.DrawEllipse(pr);
+         }
+      }
+   }
+
+   // Draw clipping
+   if (clipcnt) {
+      dc.SetPen(muted ? muteClippedPen : clippedPen);
+      while (--clipcnt >= 0) {
+         s = clipped[clipcnt];
+         AColor::Line(dc, r.x + s, r.y, r.x + s, r.y + r.height);
+      }
+   }
+   
+   if (mShowClipping) {
+      delete [] clipped;
+   }
+
+   delete[]buffer;
+   delete[]xpos;
+   delete[]ypos;
+}
+
+void WaveTrackV::DrawEnvelope(wxDC &dc, const wxRect &r, const double env[],
+                               float zoomMin, float zoomMax, bool dB)
+{
+   int h = r.height;
+
+   dc.SetPen(AColor::envelopePen);
+
+   for (int x = 0; x < r.width; x++) {
+      int cenvTop = GetWaveYPos(env[x], zoomMin, zoomMax,
+                                h, dB, true, mdBrange, true);
+
+      int cenvBot = GetWaveYPos(-env[x], zoomMin, zoomMax,
+                                h, dB, true, mdBrange, true);
+
+      int envTop = GetWaveYPos(env[x], zoomMin, zoomMax,
+                               h, dB, true, mdBrange, false);
+
+      int envBot = GetWaveYPos(-env[x], zoomMin, zoomMax,
+                               h, dB, true, mdBrange, false);
+
+      // Make the collision at zero actually look solid
+      if (cenvBot - cenvTop < 9) {
+         int value = (int)((zoomMax / (zoomMax - zoomMin)) * h);
+         cenvTop = value - 4;
+         cenvBot = value + 4;
+      }
+
+      DrawEnvLine(dc, r, x, envTop, cenvTop, true);
+      DrawEnvLine(dc, r, x, envBot, cenvBot, false);
+   }
+}
+
+void WaveTrackV::DrawEnvLine(wxDC &dc, const wxRect &r, int x, int y, int cy, bool top)
+{
+   int xx = r.x + x;
+   int yy = r.y + cy;
+
+   if (y < 0) {
+      if (x % 4 != 3) {
+         AColor::Line(dc, xx, yy, xx, yy + 3);
+      }
+   }
+   else if (y > r.height) {
+      if (x % 4 != 3) {
+         AColor::Line(dc, xx, yy - 3, xx, yy);
+      }
+   }
+   else {
+      if (top) {
+         AColor::Line(dc, xx, yy, xx, yy + 3);
+      }
+      else {
+         AColor::Line(dc, xx, yy - 3, xx, yy);
+      }
+   }
+}
+
+void WaveTrackV::DrawTimeSlider(WaveTrack *track,
+                                 wxDC & dc,
+                                 const wxRect & r,
+                                 const ViewInfo *viewInfo,
+                                 bool rightwards)
+{
+   const int border = 3; // 3 pixels all round.
+   const int width = 6; // width of the drag box.
+   const int taper = 6; // how much the box tapers by.
+   const int barSpacing = 4; // how far apart the bars are.
+   const int barWidth = 3;
+   const int xFlat = 3;
+
+   //Enough space to draw in?
+   if (r.height <= ((taper+border + barSpacing) * 2)) {
+      return;
+   }
+   if (r.width <= (width * 2 + border * 3)) {
+      return;
+   }
+
+   // The draggable box is tapered towards the direction you drag it.
+   int leftTaper  = rightwards ? 0 : 6;
+   int rightTaper = rightwards ? 6 : 0;
+
+   int xLeft = rightwards ? (r.x + border - 2)
+                          : (r.x + r.width + 1 - (border + width));
+   int yTop  = r.y + border;
+   int yBot  = r.y + r.height - border - 1;
+
+   AColor::Light(&dc, false);
+   AColor::Line(dc, xLeft,         yBot - leftTaper, xLeft,         yTop + leftTaper);
+   AColor::Line(dc, xLeft,         yTop + leftTaper, xLeft + xFlat, yTop);
+   AColor::Line(dc, xLeft + xFlat, yTop,             xLeft + width, yTop + rightTaper);
+
+   AColor::Dark(&dc, false);
+   AColor::Line(dc, xLeft + width,         yTop + rightTaper, xLeft + width,       yBot - rightTaper);
+   AColor::Line(dc, xLeft + width,         yBot - rightTaper, xLeft + width-xFlat, yBot);
+   AColor::Line(dc, xLeft + width - xFlat, yBot,              xLeft,               yBot - leftTaper);
+
+   int firstBar = yTop + taper + taper / 2;
+   int nBars    = (yBot - yTop - taper * 3) / barSpacing + 1;
+   xLeft += (width - barWidth + 1) / 2;
+   int y;
+   int i;
+
+   AColor::Light(&dc, false);
+   for (i = 0;i < nBars; i++) {
+      y = firstBar + barSpacing * i;
+      AColor::Line(dc, xLeft, y, xLeft + barWidth, y);
+   }
+   AColor::Dark(&dc, false);
+   for(i = 0;i < nBars; i++){
+      y = firstBar + barSpacing * i + 1;
+      AColor::Line(dc, xLeft, y, xLeft + barWidth, y);
+   }
+}
+
+// Draws the sync-lock bitmap, tiled; always draws stationary relative to the DC
+//
+// AWD: now that the tiles don't link together, we're drawing a tilted grid, at
+// two steps down for every one across. This creates a pattern that repeats in
+// 5-step by 5-step boxes. Because we're only drawing in 5/25 possible positions
+// we have a grid spacing somewhat smaller than the image dimensions. Thus we
+// acheive lower density than with a square grid and eliminate edge cases where
+// no tiles are displayed.
+//
+// The pattern draws in tiles at (0,0), (2,1), (4,2), (1,3), and (3,4) in each
+// 5x5 box.
+//
+// There may be a better way to do this, or a more appealing pattern.
+void WaveTrackV::DrawSyncLockTiles(wxDC *dc, wxRect r)
+{
+   wxBitmap syncLockBitmap(theTheme.Image(bmpSyncLockSelTile));
+
+   // Grid spacing is a bit smaller than actual image size
+   int gridW = syncLockBitmap.GetWidth() - 6;
+   int gridH = syncLockBitmap.GetHeight() - 8;
+
+   // Horizontal position within the grid, modulo its period
+   int blockX = (r.x / gridW) % 5;
+   
+   // Amount to offset drawing of first column
+   int xOffset = r.x % gridW;
+   if (xOffset < 0) xOffset += gridW;
+
+   // Check if we're missing an extra column to the left (this can happen
+   // because the tiles are bigger than the grid spacing)
+   bool extraCol = false;
+   if (syncLockBitmap.GetWidth() - gridW > xOffset) {
+      extraCol = true;
+      xOffset += gridW;
+      blockX = (blockX - 1) % 5;
+   }
+   // Make sure blockX is non-negative
+   if (blockX < 0) blockX += 5;
+   
+   int x = 0;
+   while (x < r.width) {
+      int width = syncLockBitmap.GetWidth() - xOffset;
+      if (x + width > r.width)
+         width = r.width - x;
+
+      //
+      // Draw each row in this column
+      //
+
+      // Vertical position in the grid, modulo its period
+      int blockY = (r.y / gridH) % 5;
+
+      // Amount to offset drawing of first row
+      int yOffset = r.y % gridH;
+      if (yOffset < 0) yOffset += gridH;
+      
+      // Check if we're missing an extra row on top (this can happen because
+      // the tiles are bigger than the grid spacing)
+      bool extraRow = false;
+      if (syncLockBitmap.GetHeight() - gridH > yOffset) {
+         extraRow = true;
+         yOffset += gridH;
+         blockY = (blockY - 1) % 5;
+      }
+      // Make sure blockY is non-negative
+      if (blockY < 0) blockY += 5;
+
+      int y = 0;
+      while (y < r.height)
+      {
+         int height = syncLockBitmap.GetHeight() - yOffset;
+         if (y + height > r.height)
+            height = r.height - y;
+
+         // AWD: draw blocks according to our pattern
+         if ((blockX == 0 && blockY == 0) || (blockX == 2 && blockY == 1) ||
+             (blockX == 4 && blockY == 2) || (blockX == 1 && blockY == 3) ||
+             (blockX == 3 && blockY == 4))
+         {
+
+            // Do we need to get a sub-bitmap?
+            if (width != syncLockBitmap.GetWidth() || height != syncLockBitmap.GetHeight()) {
+               wxBitmap subSyncLockBitmap = 
+                  syncLockBitmap.GetSubBitmap(wxRect(xOffset, yOffset, width, height));
+               dc->DrawBitmap(subSyncLockBitmap, r.x + x, r.y + y, true);
+            }
+            else {
+               dc->DrawBitmap(syncLockBitmap, r.x + x, r.y + y, true);
+            }
+         }
+
+         // Updates for next row
+         if (extraRow) {
+            // Second offset row, still at y = 0; no more extra rows
+            yOffset -= gridH;
+            extraRow = false;
+         }
+         else {
+            // Move on in y, no more offset rows
+            y += gridH - yOffset;
+            yOffset = 0;
+         }
+         blockY = (blockY + 1) % 5;
+      }
+
+      // Updates for next column
+      if (extraCol) {
+         // Second offset column, still at x = 0; no more extra columns
+         xOffset -= gridW;
+         extraCol = false;
+      }
+      else {
+         // Move on in x, no more offset rows
+         x += gridW - xOffset;
+         xOffset = 0;
+      }
+      blockX = (blockX + 1) % 5;
+   }
+}
+
+/// Takes a value between min and max and returns a value between 
+/// height and 0 
+/// \todo  Should this function move int GuiWaveTrack where it can
+/// then use the zoomMin, zoomMax and height values without having 
+/// to have them passed in to it??
+int GetWaveYPos(float value, float min, float max,
+                int height, bool dB, bool outer,
+                float dBr, bool clip)
+{
+   if (dB) {
+      if (height == 0) {
+         return 0;
+      }
+
+      float sign = (value >= 0 ? 1 : -1);
+
+      if (value != 0.) {
+         float db = 20.0 * log10(fabs(value));
+         value = (db + dBr) / dBr;
+         if (!outer) {
+            value -= 0.5;
+         }
+         if (value < 0.0) {
+            value = 0.0;
+         }
+         value *= sign;
+      }
+   }
+   else {
+      if (!outer) {
+         if (value >= 0.0) {
+            value -= 0.5;
+         }
+         else {
+            value += 0.5;
+         }
+      }
+   }
+
+   if (clip) {
+      if (value < min) {
+         value = min;
+      }
+      if (value > max) {
+         value = max;
+      }
+   }
+
+   value = (max - value) / (max - min);
+   return (int) (value * (height - 1) + 0.5);
+}
+
Index: src/view/WaveTrackV.h
===================================================================
--- src/view/WaveTrackV.h	(revision 0)
+++ src/view/WaveTrackV.h	(working copy)
@@ -0,0 +1,75 @@
+
+class AUDACITY_DLL_API WaveTrackV :
+virtual public TrackV
+{
+public:
+   WaveTrackV(void);
+   ~WaveTrackV(void);
+
+   void DrawWaveform(WaveTrack *track,
+                     wxDC & dc, const wxRect & r, const ViewInfo *viewInfo, 
+                     bool drawEnvelope, bool drawSamples, bool drawSliders,
+                     bool dB, bool muted);
+
+   void DrawClipWaveform(WaveTrack *track, WaveClip *clip,
+                         wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
+                         bool drawEnvelope, bool drawSamples, bool drawSliders,
+                         bool dB, bool muted);
+
+   // Waveform utility functions
+
+   void DrawWaveformBackground(wxDC & dc, const wxRect &r, const double env[],
+                               float zoomMin, float zoomMax, bool dB,
+                               const sampleCount where[],
+                               sampleCount ssel0, sampleCount ssel1,
+                               bool drawEnvelope, bool bIsSyncLockSelected);
+
+   void DrawMinMaxRMS(wxDC & dc, const wxRect & r, const double env[],
+                      float zoomMin, float zoomMax, bool dB,
+                      const float min[], const float max[], const float rms[],
+                      const int bl[], bool showProgress, bool muted);
+
+   void DrawIndividualSamples(wxDC & dc, const wxRect & r,
+                              float zoomMin, float zoomMax, bool dB,
+                              WaveClip *clip,
+                              double t0, double pps, double h,
+                              bool drawSamples, bool showPoints, bool muted);
+
+   void DrawEnvelope(wxDC & dc, const wxRect & r, const double env[],
+                     float zoomMin, float zoomMax, bool dB);
+   void DrawEnvLine(wxDC & dc, const wxRect & r, int x, int y, int cy, bool top);
+
+   void DrawTimeSlider(WaveTrack *track,
+                       wxDC & dc, const wxRect & r, const ViewInfo *viewInfo,
+                       bool rightwards);
+
+   // Helper: draws background with selection rect
+   static void DrawBackgroundWithSelection(wxDC *dc, const wxRect &r,
+         Track *track, wxBrush &selBrush, wxBrush &unselBrush,
+         double sel0, double sel1, double h, double pps);
+
+   // Helper: draws the "sync-locked" watermark tiled to a rectangle
+   static void DrawSyncLockTiles(wxDC *dc, wxRect r);
+
+   wxBrush blankSelectedBrush;
+   wxBrush dragsampleBrush;// for samples which are draggable.
+   wxBrush sampleBrush;
+   wxBrush selectedBrush;
+   wxBrush unselectedBrush;
+
+   wxPen clippedPen;
+   wxPen muteClippedPen;
+   wxPen muteRmsPen;
+   wxPen muteSamplePen;
+   wxPen samplePen;
+   wxPen rmsPen;
+
+   // Preference values
+   long mShowClipping;        // "/GUI/ShowClipping"
+   float mdBrange;            // "/GUI/EnvdBRange"
+   bool mIsGrayscale;         // "/Spectrum/Grayscale"
+};
+
+extern int GetWaveYPos(float value, float min, float max,
+			  int height, bool dB, bool outer, float dBr, 
+			  bool clip);
Index: src/WaveClip.h
===================================================================
--- src/WaveClip.h	(revision 11311)
+++ src/WaveClip.h	(working copy)
@@ -228,13 +228,17 @@
    virtual XMLTagHandler *HandleXMLChild(const wxChar *tag);
    virtual void WriteXML(XMLWriter &xmlFile);
 
-   // Cache of values to colour pixels of Spectrogram - used by TrackArtist
-   SpecPxCache    *mSpecPxCache;
-
    // AWD, Oct 2009: for pasting whitespace at the end of selection
    bool GetIsPlaceholder() { return mIsPlaceholder; };
    void SetIsPlaceholder(bool val) { mIsPlaceholder = val; };
 
+   SpecPxCache *GetSpecPxCache(){return mSpecPxCache;}
+   void SetSpecPxCache(SpecPxCache *c) 
+   {  SpecPxCache *oldcache=mSpecPxCache;
+      mSpecPxCache=c;
+      delete oldcache;
+   }
+
 protected:
    wxRect mDisplayRect;
 
@@ -264,6 +268,9 @@
 
    // AWD, Oct. 2009: for whitespace-at-end-of-selection pasting
    bool mIsPlaceholder;
+
+   // Cache of values to colour pixels of Spectrogram - used by TrackArtist
+   SpecPxCache    *mSpecPxCache;
 };
 
 #endif
Index: win/audacity.sln
===================================================================
--- win/audacity.sln	(revision 11311)
+++ win/audacity.sln	(working copy)
@@ -1,77 +1,54 @@
-Microsoft Visual Studio Solution File, Format Version 10.00
-# Visual C++ Express 2008
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Audacity", "Projects\Audacity\Audacity.vcproj", "{1D64095C-F936-4FCF-B609-56E9DDF941FA}"
-	ProjectSection(ProjectDependencies) = postProject
-		{8A1C2514-85DD-4AE2-9CF3-3183B66C537D} = {8A1C2514-85DD-4AE2-9CF3-3183B66C537D}
-		{A64FFB5D-0CF0-43EE-9DE3-C72260864BFF} = {A64FFB5D-0CF0-43EE-9DE3-C72260864BFF}
-		{D5AB2D87-51DC-4277-A9AB-2A6018D0E947} = {D5AB2D87-51DC-4277-A9AB-2A6018D0E947}
-		{C0FE933B-4AF7-4ACD-95E8-ACD3A73F1400} = {C0FE933B-4AF7-4ACD-95E8-ACD3A73F1400}
-		{EC3F5835-C486-4970-8A6B-A0700F4B3637} = {EC3F5835-C486-4970-8A6B-A0700F4B3637}
-		{6C7DC635-26FB-419A-B69A-7ECBBB068245} = {6C7DC635-26FB-419A-B69A-7ECBBB068245}
-		{B28C9F3F-FF0E-4FEC-844C-685390B8AC06} = {B28C9F3F-FF0E-4FEC-844C-685390B8AC06}
-		{5284D863-3813-479F-BBF0-AC234E216BC6} = {5284D863-3813-479F-BBF0-AC234E216BC6}
-		{F4B4A272-4ED3-4951-A6EE-B7BAAC1C4952} = {F4B4A272-4ED3-4951-A6EE-B7BAAC1C4952}
-		{727D6675-67EE-4D0B-9DC1-177A0AF741F0} = {727D6675-67EE-4D0B-9DC1-177A0AF741F0}
-		{7ABA0F80-94AE-4E82-AB89-2E1258212D59} = {7ABA0F80-94AE-4E82-AB89-2E1258212D59}
-		{3A76129B-55AB-4D54-BAA7-08F63ED52569} = {3A76129B-55AB-4D54-BAA7-08F63ED52569}
-		{A52BBEA5-8B02-4147-8734-5D9BBF4D1177} = {A52BBEA5-8B02-4147-8734-5D9BBF4D1177}
-		{3DDDCAA9-276D-4FC3-A15C-485F7B9B24CC} = {3DDDCAA9-276D-4FC3-A15C-485F7B9B24CC}
-		{38C2C6B1-FBF8-4C5C-BB90-63F996083F56} = {38C2C6B1-FBF8-4C5C-BB90-63F996083F56}
-		{8C69F7B6-684F-48D9-9057-8912CA3DAA8B} = {8C69F7B6-684F-48D9-9057-8912CA3DAA8B}
-		{D96C7BE1-E3F1-4767-BBBB-320E082CE425} = {D96C7BE1-E3F1-4767-BBBB-320E082CE425}
-		{7AA41BED-41B0-427A-9148-DEA40549D158} = {7AA41BED-41B0-427A-9148-DEA40549D158}
-		{A61E2BF1-21AA-4118-B0D8-FD3D53DB892E} = {A61E2BF1-21AA-4118-B0D8-FD3D53DB892E}
-		{F00717F2-67C8-44E1-AF00-541DFA9CB7F2} = {F00717F2-67C8-44E1-AF00-541DFA9CB7F2}
-		{A939AAF8-44F1-4CE7-9DD0-7A6E99814857} = {A939AAF8-44F1-4CE7-9DD0-7A6E99814857}
-	EndProjectSection
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Audacity", "Projects\Audacity\Audacity.vcxproj", "{1D64095C-F936-4FCF-B609-56E9DDF941FA}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "soundtouch", "Projects\soundtouch\soundtouch.vcproj", "{EC3F5835-C486-4970-8A6B-A0700F4B3637}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "soundtouch", "Projects\soundtouch\soundtouch.vcxproj", "{EC3F5835-C486-4970-8A6B-A0700F4B3637}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "expat", "Projects\expat\expat.vcproj", "{38C2C6B1-FBF8-4C5C-BB90-63F996083F56}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "expat", "Projects\expat\expat.vcxproj", "{38C2C6B1-FBF8-4C5C-BB90-63F996083F56}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libflac", "Projects\libflac\libflac.vcproj", "{6C7DC635-26FB-419A-B69A-7ECBBB068245}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libflac", "Projects\libflac\libflac.vcxproj", "{6C7DC635-26FB-419A-B69A-7ECBBB068245}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libflac++", "Projects\libflac++\libflac++.vcproj", "{B28C9F3F-FF0E-4FEC-844C-685390B8AC06}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libflac++", "Projects\libflac++\libflac++.vcxproj", "{B28C9F3F-FF0E-4FEC-844C-685390B8AC06}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libid3tag", "Projects\libid3tag\libid3tag.vcproj", "{D96C7BE1-E3F1-4767-BBBB-320E082CE425}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libid3tag", "Projects\libid3tag\libid3tag.vcxproj", "{D96C7BE1-E3F1-4767-BBBB-320E082CE425}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmad", "Projects\libmad\libmad.vcproj", "{A52BBEA5-8B02-4147-8734-5D9BBF4D1177}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmad", "Projects\libmad\libmad.vcxproj", "{A52BBEA5-8B02-4147-8734-5D9BBF4D1177}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnyquist", "Projects\libnyquist\libnyquist.vcproj", "{7AA41BED-41B0-427A-9148-DEA40549D158}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnyquist", "Projects\libnyquist\libnyquist.vcxproj", "{7AA41BED-41B0-427A-9148-DEA40549D158}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libresample", "Projects\libresample\libresample.vcproj", "{F00717F2-67C8-44E1-AF00-541DFA9CB7F2}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libresample", "Projects\libresample\libresample.vcxproj", "{F00717F2-67C8-44E1-AF00-541DFA9CB7F2}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsamplerate", "Projects\libsamplerate\libsamplerate.vcproj", "{3DDDCAA9-276D-4FC3-A15C-485F7B9B24CC}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsamplerate", "Projects\libsamplerate\libsamplerate.vcxproj", "{3DDDCAA9-276D-4FC3-A15C-485F7B9B24CC}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsndfile", "Projects\libsndfile\libsndfile.vcproj", "{F4B4A272-4ED3-4951-A6EE-B7BAAC1C4952}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsndfile", "Projects\libsndfile\libsndfile.vcxproj", "{F4B4A272-4ED3-4951-A6EE-B7BAAC1C4952}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libogg", "Projects\libogg\libogg.vcproj", "{A939AAF8-44F1-4CE7-9DD0-7A6E99814857}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libogg", "Projects\libogg\libogg.vcxproj", "{A939AAF8-44F1-4CE7-9DD0-7A6E99814857}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "portmixer", "Projects\portmixer\portmixer.vcproj", "{3A76129B-55AB-4D54-BAA7-08F63ED52569}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "portmixer", "Projects\portmixer\portmixer.vcxproj", "{3A76129B-55AB-4D54-BAA7-08F63ED52569}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libvorbis", "Projects\libvorbis\libvorbis.vcproj", "{727D6675-67EE-4D0B-9DC1-177A0AF741F0}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libvorbis", "Projects\libvorbis\libvorbis.vcxproj", "{727D6675-67EE-4D0B-9DC1-177A0AF741F0}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "portaudio-v19", "Projects\portaudio-v19\portaudio-v19.vcproj", "{7ABA0F80-94AE-4E82-AB89-2E1258212D59}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "portaudio-v19", "Projects\portaudio-v19\portaudio-v19.vcxproj", "{7ABA0F80-94AE-4E82-AB89-2E1258212D59}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "locale", "Projects\locale\locale.vcproj", "{BE9F28C5-058A-45F5-B2C1-D077BC058AAE}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "locale", "Projects\locale\locale.vcxproj", "{BE9F28C5-058A-45F5-B2C1-D077BC058AAE}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "filedialog", "Projects\filedialog\filedialog.vcproj", "{5284D863-3813-479F-BBF0-AC234E216BC6}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "filedialog", "Projects\filedialog\filedialog.vcxproj", "{5284D863-3813-479F-BBF0-AC234E216BC6}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mod-script-pipe", "Projects\mod-script-pipe\mod-script-pipe.vcproj", "{85774A29-EA2F-4A40-994F-6BE593D847A5}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mod-script-pipe", "Projects\mod-script-pipe\mod-script-pipe.vcxproj", "{85774A29-EA2F-4A40-994F-6BE593D847A5}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libvamp", "Projects\libvamp\libvamp.vcproj", "{A61E2BF1-21AA-4118-B0D8-FD3D53DB892E}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libvamp", "Projects\libvamp\libvamp.vcxproj", "{A61E2BF1-21AA-4118-B0D8-FD3D53DB892E}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "twolame", "Projects\twolame\twolame.vcproj", "{8C69F7B6-684F-48D9-9057-8912CA3DAA8B}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "twolame", "Projects\twolame\twolame.vcxproj", "{8C69F7B6-684F-48D9-9057-8912CA3DAA8B}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "portsmf", "Projects\portsmf\portsmf.vcproj", "{8A1C2514-85DD-4AE2-9CF3-3183B66C537D}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "portsmf", "Projects\portsmf\portsmf.vcxproj", "{8A1C2514-85DD-4AE2-9CF3-3183B66C537D}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sbsms", "Projects\sbsms\sbsms.vcproj", "{A64FFB5D-0CF0-43EE-9DE3-C72260864BFF}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sbsms", "Projects\sbsms\sbsms.vcxproj", "{A64FFB5D-0CF0-43EE-9DE3-C72260864BFF}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libscorealign", "Projects\libscorealign\libscorealign.vcproj", "{C0FE933B-4AF7-4ACD-95E8-ACD3A73F1400}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libscorealign", "Projects\libscorealign\libscorealign.vcxproj", "{C0FE933B-4AF7-4ACD-95E8-ACD3A73F1400}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "portmidi", "Projects\portmidi\portmidi.vcproj", "{D5AB2D87-51DC-4277-A9AB-2A6018D0E947}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "portmidi", "Projects\portmidi\portmidi.vcxproj", "{D5AB2D87-51DC-4277-A9AB-2A6018D0E947}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "help", "Projects\help\help.vcproj", "{02F94A40-586A-4403-8464-13B50801FFEC}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "help", "Projects\help\help.vcxproj", "{02F94A40-586A-4403-8464-13B50801FFEC}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
