text: /**********************************************************************
  
    Audacity: A Digital Audio Editor
  
    WaveTrack.cpp
  
    Dominic Mazzoni
  
  *******************************************************************//**
  
  \class WaveTrack
  \brief A Track that contains audio waveform data.
  
  *//****************************************************************//**
  
  \class WaveTrack::Location
  \brief Used only by WaveTrack, a special way to hold location that
  can accommodate merged regions.
  
  *//****************************************************************//**
  
  \class TrackFactory
  \brief Used to create a WaveTrack, or a LabelTrack..  Implementation
  of the functions of this class are dispersed through the different
  Track classes.
  
  *//*******************************************************************/
  
  
  #include "WaveTrack.h"
  #include <wx/defs.h>
  #include <wx/intl.h>
  #include <wx/debug.h>
  
  #include <float.h>
  #include <math.h>
  #include <algorithm>
  #include "MemoryX.h"
  
  #include "float_cast.h"
  
  #include "Envelope.h"
  #include "Sequence.h"
  #include "Spectrum.h"
  
  #include "Project.h"
  #include "Internat.h"
  
  #include "AudioIO.h"
  #include "Prefs.h"
  
  #include "ondemand/ODManager.h"
  
  #include "effects/TimeWarper.h"
  #include "prefs/SpectrumPrefs.h"
  #include "prefs/TracksPrefs.h"
  #include "prefs/WaveformPrefs.h"
  
  #include "InconsistencyException.h"
  
  #include "Experimental.h"
  
  #include "TrackPanel.h" // for TrackInfo
  // Assumptions in objects separation were wrong.  We need to activate
  // VZooming (that lives in WaveTrackVRulerHandle) from an action on the
  // TCP collapse/expand.  So we need visibility here.
  #include "tracks/playabletrack/wavetrack/ui/WaveTrackVRulerControls.h"
  
  using std::max;
  
  WaveTrack::Holder TrackFactory::DuplicateWaveTrack(const WaveTrack &orig)
  {
     return std::unique_ptr<WaveTrack>
     { static_cast<WaveTrack*>(orig.Duplicate().release()) };
  }
  
  
  WaveTrack::Holder TrackFactory::NewWaveTrack(sampleFormat format, double rate)
  {
     return std::unique_ptr<WaveTrack>
     { safenew WaveTrack(mDirManager, format, rate) };
  }
  
  WaveTrack::WaveTrack(const std::shared_ptr<DirManager> &projDirManager, sampleFormat format, double rate) :
     PlayableTrack(projDirManager)
  {
     if (format == (sampleFormat)0)
     {
        format = GetActiveProject()->GetDefaultFormat();
     }
     if (rate == 0)
     {
        rate = GetActiveProject()->GetRate();
     }
  
     // Force creation always:
     WaveformSettings &settings = GetIndependentWaveformSettings();
  
     mDisplay = TracksPrefs::ViewModeChoice();
     if (mDisplay == obsoleteWaveformDBDisplay) {
        mDisplay = Waveform;
        settings.scaleType = WaveformSettings::stLogarithmic;
     }
  
     mLegacyProjectFileOffset = 0;
  
     mFormat = format;
     mRate = (int) rate;
     mGain = 1.0;
     mPan = 0.0;
     mWaveColorIndex = 0;
     SetDefaultName(TracksPrefs::GetDefaultAudioTrackNamePreference());
     SetName(GetDefaultName());
     mDisplayMin = -1.0;
     mDisplayMax = 1.0;
     mSpectrumMin = mSpectrumMax = -1; // so values will default to settings
     mLastScaleType = -1;
     mLastdBRange = -1;
     mAutoSaveIdent = 0;
  
     SetHeight( TrackInfo::DefaultWaveTrackHeight() );
  }
  
  WaveTrack::WaveTrack(const WaveTrack &orig):
     PlayableTrack(orig)
     , mpSpectrumSettings(orig.mpSpectrumSettings
        ? std::make_unique<SpectrogramSettings>(*orig.mpSpectrumSettings)
        : nullptr
     )
     , mpWaveformSettings(orig.mpWaveformSettings 
        ? std::make_unique<WaveformSettings>(*orig.mpWaveformSettings)
        : nullptr
     )
  {
     mLastScaleType = -1;
     mLastdBRange = -1;
  
     mLegacyProjectFileOffset = 0;
  
     Init(orig);
  
     for (const auto &clip : orig.mClips)
        mClips.push_back
           ( std::make_unique<WaveClip>( *clip, mDirManager, true ) );
  }
  
  // Copy the track metadata but not the contents.
  void WaveTrack::Init(const WaveTrack &orig)
  {
     PlayableTrack::Init(orig);
     mFormat = orig.mFormat;
     mWaveColorIndex = orig.mWaveColorIndex;
     mRate = orig.mRate;
     mGain = orig.mGain;
     mPan = orig.mPan;
     SetDefaultName(orig.GetDefaultName());
     SetName(orig.GetName());
     mDisplay = orig.mDisplay;
     mDisplayMin = orig.mDisplayMin;
     mDisplayMax = orig.mDisplayMax;
     mSpectrumMin = orig.mSpectrumMin;
     mSpectrumMax = orig.mSpectrumMax;
     mDisplayLocationsCache.clear();
  }
  
  void WaveTrack::Reinit(const WaveTrack &orig)
  {
     Init(orig);
  
     {
        auto &settings = orig.mpSpectrumSettings;
        if (settings)
           mpSpectrumSettings = std::make_unique<SpectrogramSettings>(*settings);
        else
           mpSpectrumSettings.reset();
     }
  
     {
        auto &settings = orig.mpWaveformSettings;
        if (settings)
           mpWaveformSettings = std::make_unique<WaveformSettings>(*settings);
        else
           mpWaveformSettings.reset();
     }
  
     this->SetOffset(orig.GetOffset());
  
  #ifdef EXPERIMENTAL_OUTPUT_DISPLAY
     // To do:  mYv, mHeightV, mPerY, mVirtualStereo
  #endif
  }
  
  void WaveTrack::Merge(const Track &orig)
  {
     if (orig.GetKind() == Wave)
     {
        const WaveTrack &wt = static_cast<const WaveTrack&>(orig);
        mDisplay = wt.mDisplay;
        mGain    = wt.mGain;
        mPan     = wt.mPan;
        mDisplayMin = wt.mDisplayMin;
        mDisplayMax = wt.mDisplayMax;
        SetSpectrogramSettings(wt.mpSpectrumSettings
           ? std::make_unique<SpectrogramSettings>(*wt.mpSpectrumSettings) : nullptr);
        SetWaveformSettings
           (wt.mpWaveformSettings ? std::make_unique<WaveformSettings>(*wt.mpWaveformSettings) : nullptr);
     }
     PlayableTrack::Merge(orig);
  }
  
  WaveTrack::~WaveTrack()
  {
     //Let the ODManager know this WaveTrack is disappearing.
     //Deschedules tasks associated with this track.
     if(ODManager::IsInstanceCreated())
        ODManager::Instance()->RemoveWaveTrack(this);
  }
  
  double WaveTrack::GetOffset() const
  {
     return GetStartTime();
  }
  
  void WaveTrack::SetOffset(double o)
  // NOFAIL-GUARANTEE
  {
     double delta = o - GetOffset();
  
     for (const auto &clip : mClips)
        // assume NOFAIL-GUARANTEE
        clip->SetOffset(clip->GetOffset() + delta);
  
     mOffset = o;
  }
  
  int WaveTrack::GetChannel() const 
  {
     if( mChannel != Track::MonoChannel )
        return mChannel; 
     auto pan = GetPan();
     if( pan < -0.99 )
        return Track::LeftChannel;
     if( pan >  0.99 )
        return Track::RightChannel;
     return mChannel;
  }
  
  void WaveTrack::SetPanFromChannelType()
  { 
     if( mChannel == Track::LeftChannel )
        SetPan( -1.0f );
     else if( mChannel == Track::RightChannel )
        SetPan( 1.0f );
  };
  
  
  // static
  WaveTrack::WaveTrackDisplay
  WaveTrack::ConvertLegacyDisplayValue(int oldValue)
  {
     // Remap old values.
     enum OldValues {
        Waveform,
        WaveformDB,
        Spectrogram,
        SpectrogramLogF,
        Pitch,
     };
  
     WaveTrackDisplay newValue;
     switch (oldValue) {
     default:
     case Waveform:
        newValue = WaveTrack::Waveform; break;
     case WaveformDB:
        newValue = WaveTrack::obsoleteWaveformDBDisplay; break;
     case Spectrogram:
     case SpectrogramLogF:
     case Pitch:
        newValue = WaveTrack::Spectrum; break;
        /*
     case SpectrogramLogF:
        newValue = WaveTrack::SpectrumLogDisplay; break;
     case Pitch:
        newValue = WaveTrack::PitchDisplay; break;
        */
     }
     return newValue;
  }
  
  // static
  WaveTrack::WaveTrackDisplay
  WaveTrack::ValidateWaveTrackDisplay(WaveTrackDisplay display)
  {
     switch (display) {
        // non-obsolete codes
     case Waveform:
     case obsoleteWaveformDBDisplay:
     case Spectrum:
        return display;
  
        // obsolete codes
     case obsolete1: // was SpectrumLogDisplay
     case obsolete2: // was SpectralSelectionDisplay
     case obsolete3: // was SpectralSelectionLogDisplay
     case obsolete4: // was PitchDisplay
        return Spectrum;
  
        // codes out of bounds (from future prefs files?)
     default:
        return MinDisplay;
     }
  }
  
  void WaveTrack::SetLastScaleType() const
  {
     mLastScaleType = GetWaveformSettings().scaleType;
  }
  
  void WaveTrack::SetLastdBRange() const
  {
     mLastdBRange = GetWaveformSettings().dBRange;
  }
  
  void WaveTrack::GetDisplayBounds(float *min, float *max) const
  {
     *min = mDisplayMin;
     *max = mDisplayMax;
  }
  
  void WaveTrack::SetDisplayBounds(float min, float max) const
  {
     mDisplayMin = min;
     mDisplayMax = max;
  }
  
  void WaveTrack::GetSpectrumBounds(float *min, float *max) const
  {
     const double rate = GetRate();
  
     const SpectrogramSettings &settings = GetSpectrogramSettings();
     const SpectrogramSettings::ScaleType type = settings.scaleType;
  
     const float top = (rate / 2.);
  
     float bottom;
     if (type == SpectrogramSettings::stLinear)
        bottom = 0.0f;
     else if (type == SpectrogramSettings::stPeriod) {
        // special case
        const auto half = settings.GetFFTLength() / 2;
        // EAC returns no data for below this frequency:
        const float bin2 = rate / half;
        bottom = bin2;
     }
     else
        // logarithmic, etc.
        bottom = 1.0f;
  
     {
        float spectrumMax = mSpectrumMax;
        if (spectrumMax < 0)
           spectrumMax = settings.maxFreq;
        if (spectrumMax < 0)
           *max = top;
        else
           *max = std::max(bottom, std::min(top, spectrumMax));
     }
  
     {
        float spectrumMin = mSpectrumMin;
        if (spectrumMin < 0)
           spectrumMin = settings.minFreq;
        if (spectrumMin < 0)
           *min = std::max(bottom, top / 1000.0f);
        else
           *min = std::max(bottom, std::min(top, spectrumMin));
     }
  }
  
  void WaveTrack::SetSpectrumBounds(float min, float max) const
  {
     mSpectrumMin = min;
     mSpectrumMax = max;
  }
  
  int WaveTrack::ZeroLevelYCoordinate(wxRect rect) const
  {
     return rect.GetTop() +
        (int)((mDisplayMax / (mDisplayMax - mDisplayMin)) * rect.height);
  }
  
  Track::Holder WaveTrack::Duplicate() const
  {
     return Track::Holder{ safenew WaveTrack{ *this } };
  }
  
  double WaveTrack::GetRate() const
  {
     return mRate;
  }
  
  void WaveTrack::SetRate(double newRate)
  {
     wxASSERT( newRate > 0 );
     newRate = std::max( 1.0, newRate );
     auto ratio = mRate / newRate;
     mRate = (int) newRate;
     for (const auto &clip : mClips) {
        clip->SetRate((int)newRate);
        clip->SetOffset( clip->GetOffset() * ratio );
     }
  }
  
  float WaveTrack::GetGain() const
  {
     return mGain;
  }
  
  void WaveTrack::SetGain(float newGain)
  {
     mGain = newGain;
  }
  
  float WaveTrack::GetPan() const
  {
     return mPan;
  }
  
  void WaveTrack::SetPan(float newPan)
  {
     if (newPan > 1.0)
        mPan = 1.0;
     else if (newPan < -1.0)
        mPan = -1.0;
     else
        mPan = newPan;
  }
  
  float WaveTrack::GetChannelGain(int channel) const
  {
     float left = 1.0;
     float right = 1.0;
  
     if (mPan < 0)
        right = (mPan + 1.0);
     else if (mPan > 0)
        left = 1.0 - mPan;
  
     if ((channel%2) == 0)
        return left*mGain;
     else
        return right*mGain;
  }
  
  void WaveTrack::DoSetMinimized(bool isMinimized){
  
  #ifdef EXPERIMENTAL_HALF_WAVE
     bool bHalfWave;
     gPrefs->Read(wxT("/GUI/CollapseToHalfWave"), &bHalfWave, false);
     if( bHalfWave )
     {
        // Show half wave on collapse, full on restore.
        std::shared_ptr<TrackVRulerControls> pTvc = GetVRulerControls();
  
        // An awkward workaround for a function that lives 'in the wrong place'.
        // We use magic numbers, 0 and 1, to tell it to zoom reset or zoom half-wave.
        WaveTrackVRulerControls * pWtvc = reinterpret_cast<WaveTrackVRulerControls*>(pTvc.get());
        if( pWtvc )
           pWtvc->DoZoomPreset( isMinimized ? 1:0);
     }
  #endif
  
     PlayableTrack::DoSetMinimized( isMinimized );
  }
  
  void WaveTrack::SetWaveColorIndex(int colorIndex)
  // STRONG-GUARANTEE
  {
     for (const auto &clip : mClips)
        clip->SetColourIndex( colorIndex );
     mWaveColorIndex = colorIndex;
  }
  
  
  void WaveTrack::ConvertToSampleFormat(sampleFormat format)
  // WEAK-GUARANTEE
  // might complete on only some tracks
  {
     for (const auto &clip : mClips)
        clip->ConvertToSampleFormat(format);
     mFormat = format;
  }
  
  bool WaveTrack::IsEmpty(double t0, double t1) const
  {
     if (t0 > t1)
        return true;
  
     //wxPrintf("Searching for overlap in %.6f...%.6f\n", t0, t1);
     for (const auto &clip : mClips)
     {
        if (!clip->BeforeClip(t1) && !clip->AfterClip(t0)) {
           //wxPrintf("Overlapping clip: %.6f...%.6f\n",
           //       clip->GetStartTime(),
           //       clip->GetEndTime());
           // We found a clip that overlaps this region
           return false;
        }
     }
     //wxPrintf("No overlap found\n");
  
     // Otherwise, no clips overlap this region
     return true;
  }
  
  Track::Holder WaveTrack::Cut(double t0, double t1)
  {
     if (t1 < t0)
        THROW_INCONSISTENCY_EXCEPTION;
  
     auto tmp = Copy(t0, t1);
  
     Clear(t0, t1);
  
     return tmp;
  }
  
  Track::Holder WaveTrack::SplitCut(double t0, double t1)
  // STRONG-GUARANTEE
  {
     if (t1 < t0)
        THROW_INCONSISTENCY_EXCEPTION;
  
     // SplitCut is the same as 'Copy', then 'SplitDelete'
     auto tmp = Copy(t0, t1);
  
     SplitDelete(t0, t1);
  
     return tmp;
  }
  
  #if 0
  Track::Holder WaveTrack::CutAndAddCutLine(double t0, double t1)
  {
     if (t1 < t0)
        THROW_INCONSISTENCY_EXCEPTION;
  
     // Cut is the same as 'Copy', then 'Delete'
     auto tmp = Copy(t0, t1);
  
     ClearAndAddCutLine(t0, t1);
  
     return tmp;
  }
  #endif
  
  
  
  //Trim trims within a clip, rather than trimming everything.
  //If a bound is outside a clip, it trims everything.
  void WaveTrack::Trim (double t0, double t1)
  // WEAK-GUARANTEE
  {
     bool inside0 = false;
     bool inside1 = false;
     //Keeps track of the offset of the first clip greater than
     // the left selection t0.
     double firstGreaterOffset = -1;
  
     for (const auto &clip : mClips)
     {
        //Find the first clip greater than the offset.
        //If we end up clipping the entire track, this is useful.
        if(firstGreaterOffset < 0 &&
              clip->GetStartTime() >= t0)
           firstGreaterOffset = clip->GetStartTime();
  
        if(t1 > clip->GetStartTime() && t1 < clip->GetEndTime())
        {
           clip->Clear(t1,clip->GetEndTime());
           inside1 = true;
        }
  
        if(t0 > clip->GetStartTime() && t0 < clip->GetEndTime())
        {
           clip->Clear(clip->GetStartTime(),t0);
           clip->SetOffset(t0);
           inside0 = true;
        }
     }
  
     //if inside0 is false, then the left selector was between
     //clips, so DELETE everything to its left.
     if(!inside1 && t1 < GetEndTime())
        Clear(t1,GetEndTime());
  
     if(!inside0 && t0 > GetStartTime())
        SplitDelete(GetStartTime(), t0);
  }
  
  
  
  
  Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
  {
     if (t1 < t0)
        THROW_INCONSISTENCY_EXCEPTION;
  
     WaveTrack *newTrack;
     Track::Holder result
     { newTrack = safenew WaveTrack{ mDirManager } };
  
     newTrack->Init(*this);
  
     // PRL:  Why shouldn't cutlines be copied and pasted too?  I don't know, but
     // that was the old behavior.  But this function is also used by the
     // Duplicate command and I changed its behavior in that case.
  
     for (const auto &clip : mClips)
     {
        if (t0 <= clip->GetStartTime() && t1 >= clip->GetEndTime())
        {
           // Whole clip is in copy region
           //wxPrintf("copy: clip %i is in copy region\n", (int)clip);
  
           newTrack->mClips.push_back
              (std::make_unique<WaveClip>(*clip, mDirManager, ! forClipboard));
           WaveClip *const newClip = newTrack->mClips.back().get();
           newClip->Offset(-t0);
        }
        else if (t1 > clip->GetStartTime() && t0 < clip->GetEndTime())
        {
           // Clip is affected by command
           //wxPrintf("copy: clip %i is affected by command\n", (int)clip);
  
           const double clip_t0 = std::max(t0, clip->GetStartTime());
           const double clip_t1 = std::min(t1, clip->GetEndTime());
  
           auto newClip = std::make_unique<WaveClip>
              (*clip, mDirManager, ! forClipboard, clip_t0, clip_t1);
  
           //wxPrintf("copy: clip_t0=%f, clip_t1=%f\n", clip_t0, clip_t1);
  
           newClip->Offset(-t0);
           if (newClip->GetOffset() < 0)
              newClip->SetOffset(0);
  
           newTrack->mClips.push_back(std::move(newClip)); // transfer ownership
        }
     }
  
     // AWD, Oct 2009: If the selection ends in whitespace, create a placeholder
     // clip representing that whitespace
     // PRL:  Only if we want the track for pasting into other tracks.  Not if it
     // goes directly into a project as in the Duplicate command.
     if (forClipboard &&
         newTrack->GetEndTime() + 1.0 / newTrack->GetRate() < t1 - t0)
     {
        auto placeholder = std::make_unique<WaveClip>(mDirManager,
              newTrack->GetSampleFormat(),
              static_cast<int>(newTrack->GetRate()),
              0 /*colourindex*/);
        placeholder->SetIsPlaceholder(true);
        placeholder->InsertSilence(0, (t1 - t0) - newTrack->GetEndTime());
        placeholder->Offset(newTrack->GetEndTime());
        newTrack->mClips.push_back(std::move(placeholder)); // transfer ownership
     }
  
     return result;
  }
  
  Track::Holder WaveTrack::CopyNonconst(double t0, double t1)
  {
     return Copy(t0, t1);
  }
  
  void WaveTrack::Clear(double t0, double t1)
  // STRONG-GUARANTEE
  {
     HandleClear(t0, t1, false, false);
  }
  
  void WaveTrack::ClearAndAddCutLine(double t0, double t1)
  // STRONG-GUARANTEE
  {
     HandleClear(t0, t1, true, false);
  }
  
  const SpectrogramSettings &WaveTrack::GetSpectrogramSettings() const
  {
     if (mpSpectrumSettings)
        return *mpSpectrumSettings;
     else
        return SpectrogramSettings::defaults();
  }
  
  SpectrogramSettings &WaveTrack::GetSpectrogramSettings()
  {
     if (mpSpectrumSettings)
        return *mpSpectrumSettings;
     else
        return SpectrogramSettings::defaults();
  }
  
  SpectrogramSettings &WaveTrack::GetIndependentSpectrogramSettings()
  {
     if (!mpSpectrumSettings)
        mpSpectrumSettings =
        std::make_unique<SpectrogramSettings>(SpectrogramSettings::defaults());
     return *mpSpectrumSettings;
  }
  
  void WaveTrack::SetSpectrogramSettings(std::unique_ptr<SpectrogramSettings> &&pSettings)
  {
     if (mpSpectrumSettings != pSettings) {
        mpSpectrumSettings = std::move(pSettings);
     }
  }
  
  void WaveTrack::UseSpectralPrefs( bool bUse )
  {  
     if( bUse ){
        if( !mpSpectrumSettings )
           return;
        // reset it, and next we will be getting the defaults.
        mpSpectrumSettings.reset();
     }
     else {
        if( mpSpectrumSettings )
           return;
        GetIndependentSpectrogramSettings();
     }
  }
  
  
  
  const WaveformSettings &WaveTrack::GetWaveformSettings() const
  {
     if (mpWaveformSettings)
        return *mpWaveformSettings;
     else
        return WaveformSettings::defaults();
  }
  
  WaveformSettings &WaveTrack::GetWaveformSettings()
  {
     if (mpWaveformSettings)
        return *mpWaveformSettings;
     else
        return WaveformSettings::defaults();
  }
  
  WaveformSettings &WaveTrack::GetIndependentWaveformSettings()
  {
     if (!mpWaveformSettings)
        mpWaveformSettings = std::make_unique<WaveformSettings>(WaveformSettings::defaults());
     return *mpWaveformSettings;
  }
  
  void WaveTrack::SetWaveformSettings(std::unique_ptr<WaveformSettings> &&pSettings)
  {
     if (mpWaveformSettings != pSettings) {
        mpWaveformSettings = std::move(pSettings);
     }
  }
  
  //
  // ClearAndPaste() is a specialized version of HandleClear()
  // followed by Paste() and is used mostly by effects that
  // can't replace track data directly using Get()/Set().
  //
  // HandleClear() removes any cut/split lines lines with the
  // cleared range, but, in most cases, effects want to preserve
  // the existing cut/split lines, so they are saved before the
  // HandleClear()/Paste() and restored after.
  //
  // If the pasted track overlaps two or more clips, then it will
  // be pasted with visible split lines.  Normally, effects do not
  // want these extra lines, so they may be merged out.
  //
  void WaveTrack::ClearAndPaste(double t0, // Start of time to clear
                                double t1, // End of time to clear
                                const Track *src, // What to paste
                                bool preserve, // Whether to reinsert splits/cuts
                                bool merge, // Whether to remove 'extra' splits
                                const TimeWarper *effectWarper // How does time change
                                )
  // WEAK-GUARANTEE
  // this WaveTrack remains destructible in case of AudacityException.
  // But some of its cutline clips may have been destroyed.
  {
     double dur = std::min(t1 - t0, src->GetEndTime());
  
     // If duration is 0, then it's just a plain paste
     if (dur == 0.0) {
        // use WEAK-GUARANTEE
        Paste(t0, src);
        return;
     }
  
     std::vector<double> splits;
     WaveClipHolders cuts;
  
     // If provided time warper was NULL, use a default one that does nothing
     IdentityTimeWarper localWarper;
     const TimeWarper *warper = (effectWarper ? effectWarper : &localWarper);
  
     // Align to a sample
     t0 = LongSamplesToTime(TimeToLongSamples(t0));
     t1 = LongSamplesToTime(TimeToLongSamples(t1));
  
     // Save the cut/split lines whether preserving or not since merging
     // needs to know if a clip boundary is being crossed since Paste()
     // will add split lines around the pasted clip if so.
     for (const auto &clip : mClips) {
        double st;
  
        // Remember clip boundaries as locations to split
        st = LongSamplesToTime(TimeToLongSamples(clip->GetStartTime()));
        if (st >= t0 && st <= t1 && !make_iterator_range(splits).contains(st)) {
           splits.push_back(st);
        }
  
        st = LongSamplesToTime(TimeToLongSamples(clip->GetEndTime()));
        if (st >= t0 && st <= t1 && !make_iterator_range(splits).contains(st)) {
           splits.push_back(st);
        }
  
        // Search for cut lines
        auto &cutlines = clip->GetCutLines();
        // May erase from cutlines, so don't use range-for
        for (auto it = cutlines.begin(); it != cutlines.end(); ) {
           WaveClip *cut = it->get();
           double cs = LongSamplesToTime(TimeToLongSamples(clip->GetOffset() +
                                                           cut->GetOffset()));
  
           // Remember cut point
           if (cs >= t0 && cs <= t1) {
  
              // Remember the absolute offset and add to our cuts array.
              cut->SetOffset(cs);
              cuts.push_back(std::move(*it)); // transfer ownership!
              it = cutlines.erase(it);
           }
           else
              ++it;
        }
     }
  
     const auto tolerance = 2.0 / GetRate();
  
     // Now, clear the selection
     HandleClear(t0, t1, false, false);
     {
  
        // And paste in the NEW data
        Paste(t0, src);
        {
           // First, merge the NEW clip(s) in with the existing clips
           if (merge && splits.size() > 0)
           {
              // Now t1 represents the absolute end of the pasted data.
              t1 = t0 + src->GetEndTime();
  
              // Get a sorted array of the clips
              auto clips = SortedClipArray();
  
              // Scan the sorted clips for the first clip whose start time
              // exceeds the pasted regions end time.
              {
                 WaveClip *prev = nullptr;
                 for (const auto clip : clips) {
                    // Merge this clip and the previous clip if the end time
                    // falls within it and this isn't the first clip in the track.
                    if (fabs(t1 - clip->GetStartTime()) < tolerance) {
                       if (prev)
                          MergeClips(GetClipIndex(prev), GetClipIndex(clip));
                       break;
                    }
                    prev = clip;
                 }
              }
           }
  
           // Refill the array since clips have changed.
           auto clips = SortedClipArray();
  
           {
              // Scan the sorted clips to look for the start of the pasted
              // region.
              WaveClip *prev = nullptr;
              for (const auto clip : clips) {
                 if (prev) {
                    // It must be that clip is what was pasted and it begins where
                    // prev ends.
                    // use WEAK-GUARANTEE
                    MergeClips(GetClipIndex(prev), GetClipIndex(clip));
                    break;
                 }
                 if (fabs(t0 - clip->GetEndTime()) < tolerance)
                    // Merge this clip and the next clip if the start time
                    // falls within it and this isn't the last clip in the track.
                    prev = clip;
                 else
                    prev = nullptr;
              }
           }
        }
  
        // Restore cut/split lines
        if (preserve) {
  
           // Restore the split lines, transforming the position appropriately
           for (const auto split: splits) {
              SplitAt(warper->Warp(split));
           }
  
           // Restore the saved cut lines, also transforming if time altered
           for (const auto &clip : mClips) {
              double st;
              double et;
  
              st = clip->GetStartTime();
              et = clip->GetEndTime();
  
              // Scan the cuts for any that live within this clip
              for (auto it = cuts.begin(); it != cuts.end();) {
                 WaveClip *cut = it->get();
                 double cs = cut->GetOffset();
  
                 // Offset the cut from the start of the clip and add it to
                 // this clips cutlines.
                 if (cs >= st && cs <= et) {
                    cut->SetOffset(warper->Warp(cs) - st);
                    clip->GetCutLines().push_back( std::move(*it) ); // transfer ownership!
                    it = cuts.erase(it);
                 }
                 else
                    ++it;
              }
           }
        }
     }
  }
  
  void WaveTrack::SplitDelete(double t0, double t1)
  // STRONG-GUARANTEE
  {
     bool addCutLines = false;
     bool split = true;
     HandleClear(t0, t1, addCutLines, split);
  }
  
  namespace
  {
     WaveClipHolders::const_iterator
        FindClip(const WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
     {
        if (distance)
           *distance = 0;
        auto it = list.begin();
        for (const auto end = list.end(); it != end; ++it)
        {
           if (it->get() == clip)
              break;
           if (distance)
              ++*distance;
        }
        return it;
     }
  
     WaveClipHolders::iterator
        FindClip(WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
     {
        if (distance)
           *distance = 0;
        auto it = list.begin();
        for (const auto end = list.end(); it != end; ++it)
        {
           if (it->get() == clip)
              break;
           if (distance)
              ++*distance;
        }
        return it;
     }
  }
  
  std::shared_ptr<WaveClip> WaveTrack::RemoveAndReturnClip(WaveClip* clip)
  {
     // Be clear about who owns the clip!!
     auto it = FindClip(mClips, clip);
     if (it != mClips.end()) {
        auto result = std::move(*it); // Array stops owning the clip, before we shrink it
        mClips.erase(it);
        return result;
     }
     else
        return {};
  }
  
  void WaveTrack::AddClip(std::shared_ptr<WaveClip> &&clip)
  {
     // Uncomment the following line after we correct the problem of zero-length clips
     //if (CanInsertClip(clip))
        mClips.push_back(std::move(clip)); // transfer ownership
  }
  
  void WaveTrack::HandleClear(double t0, double t1,
                              bool addCutLines, bool split)
  // STRONG-GUARANTEE
  {
     if (t1 < t0)
        THROW_INCONSISTENCY_EXCEPTION;
  
     bool editClipCanMove = gPrefs->GetEditClipsCanMove();
  
     WaveClipPointers clipsToDelete;
     WaveClipHolders clipsToAdd;
  
     // We only add cut lines when deleting in the middle of a single clip
     // The cut line code is not really prepared to handle other situations
     if (addCutLines)
     {
        for (const auto &clip : mClips)
        {
           if (!clip->BeforeClip(t1) && !clip->AfterClip(t0) &&
                 (clip->BeforeClip(t0) || clip->AfterClip(t1)))
           {
              addCutLines = false;
              break;
           }
        }
     }
  
     for (const auto &clip : mClips)
     {
        if (clip->BeforeClip(t0) && clip->AfterClip(t1))
        {
           // Whole clip must be deleted - remember this
           clipsToDelete.push_back(clip.get());
        }
        else if (!clip->BeforeClip(t1) && !clip->AfterClip(t0))
        {
           // Clip data is affected by command
           if (addCutLines)
           {
              // Don't modify this clip in place, because we want a strong
              // guarantee, and might modify another clip
              clipsToDelete.push_back( clip.get() );
              auto newClip = std::make_unique<WaveClip>( *clip, mDirManager, true );
              newClip->ClearAndAddCutLine( t0, t1 );
              clipsToAdd.push_back( std::move( newClip ) );
           }
           else
           {
              if (split) {
                 // Three cases:
  
                 if (clip->BeforeClip(t0)) {
                    // Delete from the left edge
  
                    // Don't modify this clip in place, because we want a strong
                    // guarantee, and might modify another clip
                    clipsToDelete.push_back( clip.get() );
                    auto newClip = std::make_unique<WaveClip>( *clip, mDirManager, true );
                    newClip->Clear(clip->GetStartTime(), t1);
                    newClip->Offset(t1-clip->GetStartTime());
  
                    clipsToAdd.push_back( std::move( newClip ) );
                 }
                 else if (clip->AfterClip(t1)) {
                    // Delete to right edge
  
                    // Don't modify this clip in place, because we want a strong
                    // guarantee, and might modify another clip
                    clipsToDelete.push_back( clip.get() );
                    auto newClip = std::make_unique<WaveClip>( *clip, mDirManager, true );
                    newClip->Clear(t0, clip->GetEndTime());
  
                    clipsToAdd.push_back( std::move( newClip ) );
                 }
                 else {
                    // Delete in the middle of the clip...we actually create two
                    // NEW clips out of the left and right halves...
  
                    // left
                    clipsToAdd.push_back
                       ( std::make_unique<WaveClip>( *clip, mDirManager, true ) );
                    clipsToAdd.back()->Clear(t0, clip->GetEndTime());
  
                    // right
                    clipsToAdd.push_back
                       ( std::make_unique<WaveClip>( *clip, mDirManager, true ) );
                    WaveClip *const right = clipsToAdd.back().get();
                    right->Clear(clip->GetStartTime(), t1);
                    right->Offset(t1 - clip->GetStartTime());
  
                    clipsToDelete.push_back(clip.get());
                 }
              }
              else {
                 // (We are not doing a split cut)
  
                 // Don't modify this clip in place, because we want a strong
                 // guarantee, and might modify another clip
                 clipsToDelete.push_back( clip.get() );
                 auto newClip = std::make_unique<WaveClip>( *clip, mDirManager, true );
  
                 // clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion
                 newClip->Clear(t0,t1);
  
                 clipsToAdd.push_back( std::move( newClip ) );
              }
           }
        }
     }
  
     // Only now, change the contents of this track
     // use NOFAIL-GUARANTEE for the rest
  
     for (const auto &clip : mClips)
     {
        if (clip->BeforeClip(t1))
        {
           // Clip is "behind" the region -- offset it unless we're splitting
           // or we're using the "don't move other clips" mode
           if (!split && editClipCanMove)
              clip->Offset(-(t1-t0));
        }
     }
  
     for (const auto &clip: clipsToDelete)
     {
        auto myIt = FindClip(mClips, clip);
        if (myIt != mClips.end())
           mClips.erase(myIt); // deletes the clip!
        else
           wxASSERT(false);
     }
  
     for (auto &clip: clipsToAdd)
        mClips.push_back(std::move(clip)); // transfer ownership
  }
  
  void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
  {
     if (newT1 > oldT1) {
        // Insert space within the track
  
        // JKC: This is a rare case where using >= rather than > on a float matters.
        // GetEndTime() looks through the clips and may give us EXACTLY the same
        // value as T1, when T1 was set to be at the end of one of those clips.
        if (oldT1 >= GetEndTime())
           return;
  
        // If track is empty at oldT1 insert whitespace; otherwise, silence
        if (IsEmpty(oldT1, oldT1))
        {
           // Check if clips can move
           bool clipsCanMove = true;
           gPrefs->Read(wxT("/GUI/EditClipCanMove"), &clipsCanMove);
           if (clipsCanMove) {
              auto tmp = Cut (oldT1, GetEndTime() + 1.0/GetRate());
  
              Paste(newT1, tmp.get());
           }
           return;
        }
        else {
           // AWD: Could just use InsertSilence() on its own here, but it doesn't
           // follow EditClipCanMove rules (Paste() does it right)
           AudacityProject *p = GetActiveProject();
           if (!p)
              THROW_INCONSISTENCY_EXCEPTION;
           TrackFactory *f = p->GetTrackFactory();
           if (!f)
              THROW_INCONSISTENCY_EXCEPTION;
           auto tmp = f->NewWaveTrack(GetSampleFormat(), GetRate());
  
           tmp->InsertSilence(0.0, newT1 - oldT1);
           tmp->Flush();
           Paste(oldT1, tmp.get());
        }
     }
     else if (newT1 < oldT1) {
        Clear(newT1, oldT1);
     }
  }
  
  void WaveTrack::Paste(double t0, const Track *src)
  // WEAK-GUARANTEE
  {
     bool editClipCanMove = gPrefs->GetEditClipsCanMove();
     
     if( src == NULL )
        // THROW_INCONSISTENCY_EXCEPTION; // ?
        return;
  
     if (src->GetKind() != Track::Wave)
        // THROW_INCONSISTENCY_EXCEPTION; // ?
        return;
  
     const WaveTrack* other = static_cast<const WaveTrack*>(src);
  
     //
     // Pasting is a bit complicated, because with the existence of multiclip mode,
     // we must guess the behaviour the user wants.
     //
     // Currently, two modes are implemented:
     //
     // - If a single clip should be pasted, and it should be pasted inside another
     //   clip, no NEW clips are generated. The audio is simply inserted.
     //   This resembles the old (pre-multiclip support) behaviour. However, if
     //   the clip is pasted outside of any clip, a NEW clip is generated. This is
     //   the only behaviour which is different to what was done before, but it
     //   shouldn't confuse users too much.
     //
     // - If multiple clips should be pasted, or a single clip that does not fill
     // the duration of the pasted track, these are always pasted as single
     // clips, and the current clip is splitted, when necessary. This may seem
     // strange at first, but it probably is better than trying to auto-merge
     // anything. The user can still merge the clips by hand (which should be a
     // simple command reachable by a hotkey or single mouse click).
     //
  
     if (other->GetNumClips() == 0)
        return;
  
     //wxPrintf("paste: we have at least one clip\n");
  
     bool singleClipMode = (other->GetNumClips() == 1 &&
           other->GetStartTime() == 0.0);
  
     const double insertDuration = other->GetEndTime();
     if( insertDuration != 0 && insertDuration < 1.0/mRate )
        // PRL:  I added this check to avoid violations of preconditions in other WaveClip and Sequence
        // methods, but allow the value 0 so I don't subvert the purpose of commit
        // 739422ba70ceb4be0bb1829b6feb0c5401de641e which causes append-recording always to make
        // a new clip.
        return;
  
     //wxPrintf("Check if we need to make room for the pasted data\n");
  
     // Make room for the pasted data
     if (editClipCanMove) {
        if (!singleClipMode) {
           // We need to insert multiple clips, so split the current clip and
           // move everything to the right, then try to paste again
           if (!IsEmpty(t0, GetEndTime())) {
              auto tmp = Cut(t0, GetEndTime()+1.0/mRate);
              Paste(t0 + insertDuration, tmp.get());
           }
        }
        else {
           // We only need to insert one single clip, so just move all clips
           // to the right of the paste point out of the way
           for (const auto &clip : mClips)
           {
              if (clip->GetStartTime() > t0-(1.0/mRate))
                 clip->Offset(insertDuration);
           }
        }
     }
  
     if (singleClipMode)
     {
        // Single clip mode
        // wxPrintf("paste: checking for single clip mode!\n");
  
        WaveClip *insideClip = NULL;
  
        for (const auto &clip : mClips)
        {
           if (editClipCanMove)
           {
              if (clip->WithinClip(t0))
              {
                 //wxPrintf("t0=%.6f: inside clip is %.6f ... %.6f\n",
                 //       t0, clip->GetStartTime(), clip->GetEndTime());
                 insideClip = clip.get();
                 break;
              }
           }
           else
           {
              // If clips are immovable we also allow prepending to clips
              if (clip->WithinClip(t0) ||
                    TimeToLongSamples(t0) == clip->GetStartSample())
              {
                 insideClip = clip.get();
                 break;
              }
           }
        }
  
        if (insideClip)
        {
           // Exhibit traditional behaviour
           //wxPrintf("paste: traditional behaviour\n");
           if (!editClipCanMove)
           {
              // We did not move other clips out of the way already, so
              // check if we can paste without having to move other clips
              for (const auto &clip : mClips)
              {
                 if (clip->GetStartTime() > insideClip->GetStartTime() &&
                     insideClip->GetEndTime() + insertDuration >
                                                        clip->GetStartTime())
                    // STRONG-GUARANTEE in case of this path
                    // not that it matters.
                    throw SimpleMessageBoxException{
                       _("There is not enough room available to paste the selection")
                    };
              }
           }
  
           insideClip->Paste(t0, other->GetClipByIndex(0));
           return;
        }
  
        // Just fall through and exhibit NEW behaviour
     }
  
     // Insert NEW clips
     //wxPrintf("paste: multi clip mode!\n");
  
     if (!editClipCanMove && !IsEmpty(t0, t0+insertDuration-1.0/mRate))
        // STRONG-GUARANTEE in case of this path
        // not that it matters.
        throw SimpleMessageBoxException{
           _("There is not enough room available to paste the selection")
        };
  
     for (const auto &clip : other->mClips)
     {
        // AWD Oct. 2009: Don't actually paste in placeholder clips
        if (!clip->GetIsPlaceholder())
        {
           auto newClip =
              std::make_unique<WaveClip>( *clip, mDirManager, true );
           newClip->Resample(mRate);
           newClip->Offset(t0);
           newClip->MarkChanged();
           mClips.push_back(std::move(newClip)); // transfer ownership
        }
     }
  }
  
  void WaveTrack::Silence(double t0, double t1)
  {
     if (t1 < t0)
        THROW_INCONSISTENCY_EXCEPTION;
  
     auto start = (sampleCount)floor(t0 * mRate + 0.5);
     auto len = (sampleCount)floor(t1 * mRate + 0.5) - start;
  
     for (const auto &clip : mClips)
     {
        auto clipStart = clip->GetStartSample();
        auto clipEnd = clip->GetEndSample();
  
        if (clipEnd > start && clipStart < start+len)
        {
           // Clip sample region and Get/Put sample region overlap
           auto samplesToCopy = start+len - clipStart;
           if (samplesToCopy > clip->GetNumSamples())
              samplesToCopy = clip->GetNumSamples();
           auto startDelta = clipStart - start;
           decltype(startDelta) inclipDelta = 0;
           if (startDelta < 0)
           {
              inclipDelta = -startDelta; // make positive value
              samplesToCopy -= inclipDelta;
              startDelta = 0;
           }
  
           clip->GetSequence()->SetSilence(inclipDelta, samplesToCopy);
           clip->MarkChanged();
        }
     }
  }
  
  void WaveTrack::InsertSilence(double t, double len)
  // STRONG-GUARANTEE
  {
     // Nothing to do, if length is zero.
     // Fixes Bug 1626
     if( len == 0 )
        return;
     if (len <= 0)
        THROW_INCONSISTENCY_EXCEPTION;
  
     if (mClips.empty())
     {
        // Special case if there is no clip yet
        auto clip = std::make_unique<WaveClip>(mDirManager, mFormat, mRate, this->GetWaveColorIndex());
        clip->InsertSilence(0, len);
        // use NOFAIL-GUARANTEE
        mClips.push_back( std::move( clip ) );
        return;
     }
     else {
        // Assume at most one clip contains t
        const auto end = mClips.end();
        const auto it = std::find_if( mClips.begin(), end,
           [&](const WaveClipHolder &clip) { return clip->WithinClip(t); } );
  
        // use STRONG-GUARANTEE
        if (it != end)
           it->get()->InsertSilence(t, len);
  
        // use NOFAIL-GUARANTEE
        for (const auto &clip : mClips)
        {
           if (clip->BeforeClip(t))
              clip->Offset(len);
        }
     }
  }
  
  //Performs the opposite of Join
  //Analyses selected region for possible Joined clips and disjoins them
  void WaveTrack::Disjoin(double t0, double t1)
  // WEAK-GUARANTEE
  {
     auto minSamples = TimeToLongSamples( WAVETRACK_MERGE_POINT_TOLERANCE );
     const size_t maxAtOnce = 1048576;
     Floats buffer{ maxAtOnce };
     Regions regions;
  
     wxBusyCursor busy;
  
     for (const auto &clip : mClips)
     {
        double startTime = clip->GetStartTime();
        double endTime = clip->GetEndTime();
  
        if( endTime < t0 || startTime > t1 )
           continue;
  
        if( t0 > startTime )
           startTime = t0;
        if( t1 < endTime )
           endTime = t1;
  
        //simply look for a sequence of zeroes and if the sequence
        //is greater than minimum number, split-DELETE the region
  
        sampleCount seqStart = -1;
        sampleCount start, end;
        clip->TimeToSamplesClip( startTime, &start );
        clip->TimeToSamplesClip( endTime, &end );
  
        auto len = ( end - start );
        for( decltype(len) done = 0; done < len; done += maxAtOnce )
        {
           auto numSamples = limitSampleBufferSize( maxAtOnce, len - done );
  
           clip->GetSamples( ( samplePtr )buffer.get(), floatSample, start + done,
                 numSamples );
           for( decltype(numSamples) i = 0; i < numSamples; i++ )
           {
              auto curSamplePos = start + done + i;
  
              //start a NEW sequence
              if( buffer[ i ] == 0.0 && seqStart == -1 )
                 seqStart = curSamplePos;
              else if( buffer[ i ] != 0.0 || curSamplePos == end - 1 )
              {
                 if( seqStart != -1 )
                 {
                    decltype(end) seqEnd;
  
                    //consider the end case, where selection ends in zeroes
                    if( curSamplePos == end - 1 && buffer[ i ] == 0.0 )
                       seqEnd = end;
                    else
                       seqEnd = curSamplePos;
                    if( seqEnd - seqStart + 1 > minSamples )
                    {
                       regions.push_back(Region(
                          seqStart.as_double() / GetRate()
                                                + clip->GetStartTime(),
                          seqEnd.as_double() / GetRate()
                                                + clip->GetStartTime()));
                    }
                    seqStart = -1;
                 }
              }
           }
        }
     }
  
     for( unsigned int i = 0; i < regions.size(); i++ )
     {
        const Region &region = regions.at(i);
        SplitDelete(region.start, region.end );
     }
  }
  
  void WaveTrack::Join(double t0, double t1)
  // WEAK-GUARANTEE
  {
     // Merge all WaveClips overlapping selection into one
  
     WaveClipPointers clipsToDelete;
     WaveClip *newClip;
  
     for (const auto &clip: mClips)
     {
        if (clip->GetStartTime() < t1-(1.0/mRate) &&
            clip->GetEndTime()-(1.0/mRate) > t0) {
  
           // Put in sorted order
           auto it = clipsToDelete.begin(), end = clipsToDelete.end();
           for (; it != end; ++it)
              if ((*it)->GetStartTime() > clip->GetStartTime())
                 break;
           //wxPrintf("Insert clip %.6f at position %d\n", clip->GetStartTime(), i);
           clipsToDelete.insert(it, clip.get());
        }
     }
  
     //if there are no clips to DELETE, nothing to do
     if( clipsToDelete.size() == 0 )
        return;
  
     newClip = CreateClip();
     double t = clipsToDelete[0]->GetOffset();
     newClip->SetOffset(t);
     for (const auto &clip : clipsToDelete)
     {
        //wxPrintf("t=%.6f adding clip (offset %.6f, %.6f ... %.6f)\n",
        //       t, clip->GetOffset(), clip->GetStartTime(), clip->GetEndTime());
  
        if (clip->GetOffset() - t > (1.0 / mRate)) {
           double addedSilence = (clip->GetOffset() - t);
           //wxPrintf("Adding %.6f seconds of silence\n");
           auto offset = clip->GetOffset();
           auto value = clip->GetEnvelope()->GetValue( offset );
           newClip->AppendSilence( addedSilence, value );
           t += addedSilence;
        }
  
        //wxPrintf("Pasting at %.6f\n", t);
        newClip->Paste(t, clip);
  
        t = newClip->GetEndTime();
  
        auto it = FindClip(mClips, clip);
        mClips.erase(it); // deletes the clip
     }
  }
  
  void WaveTrack::Append(samplePtr buffer, sampleFormat format,
                         size_t len, unsigned int stride /* = 1 */,
                         XMLWriter *blockFileLog /* = NULL */)
  // PARTIAL-GUARANTEE in case of exceptions:
  // Some prefix (maybe none) of the buffer is appended, and no content already
  // flushed to disk is lost.
  {
     RightmostOrNewClip()->Append(buffer, format, len, stride,
                                          blockFileLog);
  }
  
  void WaveTrack::AppendAlias(const wxString &fName, sampleCount start,
                              size_t len, int channel,bool useOD)
  // STRONG-GUARANTEE
  {
     RightmostOrNewClip()->AppendAlias(fName, start, len, channel, useOD);
  }
  
  void WaveTrack::AppendCoded(const wxString &fName, sampleCount start,
                              size_t len, int channel, int decodeType)
  // STRONG-GUARANTEE
  {
     RightmostOrNewClip()->AppendCoded(fName, start, len, channel, decodeType);
  }
  
  ///gets an int with OD flags so that we can determine which ODTasks should be run on this track after save/open, etc.
  unsigned int WaveTrack::GetODFlags() const
  {
     unsigned int ret = 0;
     for (const auto &clip : mClips)
     {
        ret = ret | clip->GetSequence()->GetODFlags();
     }
     return ret;
  }
  
  
  sampleCount WaveTrack::GetBlockStart(sampleCount s) const
  {
     for (const auto &clip : mClips)
     {
        const auto startSample = (sampleCount)floor(0.5 + clip->GetStartTime()*mRate);
        const auto endSample = startSample + clip->GetNumSamples();
        if (s >= startSample && s < endSample)
           return startSample + clip->GetSequence()->GetBlockStart(s - startSample);
     }
  
     return -1;
  }
  
  size_t WaveTrack::GetBestBlockSize(sampleCount s) const
  {
     auto bestBlockSize = GetMaxBlockSize();
  
     for (const auto &clip : mClips)
     {
        auto startSample = (sampleCount)floor(clip->GetStartTime()*mRate + 0.5);
        auto endSample = startSample + clip->GetNumSamples();
        if (s >= startSample && s < endSample)
        {
           bestBlockSize = clip->GetSequence()->GetBestBlockSize(s - startSample);
           break;
        }
     }
  
     return bestBlockSize;
  }
  
  size_t WaveTrack::GetMaxBlockSize() const
  {
     decltype(GetMaxBlockSize()) maxblocksize = 0;
     for (const auto &clip : mClips)
     {
        maxblocksize = std::max(maxblocksize, clip->GetSequence()->GetMaxBlockSize());
     }
  
     if (maxblocksize == 0)
     {
        // We really need the maximum block size, so create a
        // temporary sequence to get it.
        maxblocksize = Sequence{ mDirManager, mFormat }.GetMaxBlockSize();
     }
  
     wxASSERT(maxblocksize > 0);
  
     return maxblocksize;
  }
  
  size_t WaveTrack::GetIdealBlockSize()
  {
     return NewestOrNewClip()->GetSequence()->GetIdealBlockSize();
  }
  
  void WaveTrack::Flush()
  // NOFAIL-GUARANTEE that the rightmost clip will be in a flushed state.
  // PARTIAL-GUARANTEE in case of exceptions:
  // Some initial portion (maybe none) of the append buffer of the rightmost
  // clip gets appended; no previously saved contents are lost.
  {
     // After appending, presumably.  Do this to the clip that gets appended.
     RightmostOrNewClip()->Flush();
  }
  
  bool WaveTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
  {
     if (!wxStrcmp(tag, wxT("wavetrack"))) {
        double dblValue;
        long nValue;
        while(*attrs) {
           const wxChar *attr = *attrs++;
           const wxChar *value = *attrs++;
  
           if (!value)
              break;
  
           const wxString strValue = value;
           if (!wxStrcmp(attr, wxT("rate")))
           {
              // mRate is an int, but "rate" in the project file is a float.
              if (!XMLValueChecker::IsGoodString(strValue) ||
                    !Internat::CompatibleToDouble(strValue, &dblValue) ||
                    (dblValue < 1.0) || (dblValue > 1000000.0)) // allow a large range to be read
                 return false;
              mRate = lrint(dblValue);
           }
           else if (!wxStrcmp(attr, wxT("offset")) &&
                    XMLValueChecker::IsGoodString(strValue) &&
                    Internat::CompatibleToDouble(strValue, &dblValue))
           {
              // Offset is only relevant for legacy project files. The value
              // is cached until the actual WaveClip containing the legacy
              // track is created.
              mLegacyProjectFileOffset = dblValue;
           }
           else if (this->PlayableTrack::HandleXMLAttribute(attr, value))
           {}
           else if (!wxStrcmp(attr, wxT("height")) &&
                    XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
              mHeight = nValue;
           else if (!wxStrcmp(attr, wxT("minimized")) &&
                    XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
              mMinimized = (nValue != 0);
           else if (!wxStrcmp(attr, wxT("isSelected")) &&
                    XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
              this->SetSelected(nValue != 0);
           else if (!wxStrcmp(attr, wxT("gain")) &&
                    XMLValueChecker::IsGoodString(strValue) &&
                    Internat::CompatibleToDouble(strValue, &dblValue))
              mGain = dblValue;
           else if (!wxStrcmp(attr, wxT("pan")) &&
                    XMLValueChecker::IsGoodString(strValue) &&
                    Internat::CompatibleToDouble(strValue, &dblValue) &&
                    (dblValue >= -1.0) && (dblValue <= 1.0))
              mPan = dblValue;
           else if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(strValue))
              mName = strValue;
           else if (!wxStrcmp(attr, wxT("channel")))
           {
              if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&nValue) ||
                    !XMLValueChecker::IsValidChannel(nValue))
                 return false;
              mChannel = nValue;
           }
           else if (!wxStrcmp(attr, wxT("linked")) &&
                    XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
              SetLinked(nValue != 0);
           else if (!wxStrcmp(attr, wxT("autosaveid")) &&
                    XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
              mAutoSaveIdent = (int) nValue;
           else if (!wxStrcmp(attr, wxT("colorindex")) &&
                    XMLValueChecker::IsGoodString(strValue) &&
                    strValue.ToLong(&nValue))
              // Don't use SetWaveColorIndex as it sets the clips too.
              mWaveColorIndex  = nValue;
        } // while
        return true;
     }
  
     return false;
  }
  
  void WaveTrack::HandleXMLEndTag(const wxChar * WXUNUSED(tag))
  {
     // In case we opened a pre-multiclip project, we need to
     // simulate closing the waveclip tag.
     NewestOrNewClip()->HandleXMLEndTag(wxT("waveclip"));
  }
  
  XMLTagHandler *WaveTrack::HandleXMLChild(const wxChar *tag)
  {
     //
     // This is legacy code (1.2 and previous) and is not called for NEW projects!
     //
     if (!wxStrcmp(tag, wxT("sequence")) || !wxStrcmp(tag, wxT("envelope")))
     {
        // This is a legacy project, so set the cached offset
        NewestOrNewClip()->SetOffset(mLegacyProjectFileOffset);
  
        // Legacy project file tracks are imported as one single wave clip
        if (!wxStrcmp(tag, wxT("sequence")))
           return NewestOrNewClip()->GetSequence();
        else if (!wxStrcmp(tag, wxT("envelope")))
           return NewestOrNewClip()->GetEnvelope();
     }
  
     // JKC... for 1.1.0, one step better than what we had, but still badly broken.
     //If we see a waveblock at this level, we'd better generate a sequence.
     if( !wxStrcmp( tag, wxT("waveblock" )))
     {
        // This is a legacy project, so set the cached offset
        NewestOrNewClip()->SetOffset(mLegacyProjectFileOffset);
        Sequence *pSeq = NewestOrNewClip()->GetSequence();
        return pSeq;
     }
  
     //
     // This is for the NEW file format (post-1.2)
     //
     if (!wxStrcmp(tag, wxT("waveclip")))
        return CreateClip();
     else
        return NULL;
  }
  
  void WaveTrack::WriteXML(XMLWriter &xmlFile) const
  // may throw
  {
     xmlFile.StartTag(wxT("wavetrack"));
     if (mAutoSaveIdent)
     {
        xmlFile.WriteAttr(wxT("autosaveid"), mAutoSaveIdent);
     }
     xmlFile.WriteAttr(wxT("name"), mName);
     xmlFile.WriteAttr(wxT("channel"), mChannel);
     xmlFile.WriteAttr(wxT("linked"), mLinked);
     this->PlayableTrack::WriteXMLAttributes(xmlFile);
     xmlFile.WriteAttr(wxT("height"), this->GetActualHeight());
     xmlFile.WriteAttr(wxT("minimized"), this->GetMinimized());
     xmlFile.WriteAttr(wxT("isSelected"), this->GetSelected());
     xmlFile.WriteAttr(wxT("rate"), mRate);
     xmlFile.WriteAttr(wxT("gain"), (double)mGain);
     xmlFile.WriteAttr(wxT("pan"), (double)mPan);
     xmlFile.WriteAttr(wxT("colorindex"), mWaveColorIndex );
  
     for (const auto &clip : mClips)
     {
        clip->WriteXML(xmlFile);
     }
  
     xmlFile.EndTag(wxT("wavetrack"));
  }
  
  bool WaveTrack::GetErrorOpening()
  {
     for (const auto &clip : mClips)
        if (clip->GetSequence()->GetErrorOpening())
           return true;
  
     return false;
  }
  
  bool WaveTrack::Lock() const
  {
     for (const auto &clip : mClips)
        clip->Lock();
  
     return true;
  }
  
  bool WaveTrack::CloseLock()
  {
     for (const auto &clip : mClips)
        clip->CloseLock();
  
     return true;
  }
  
  
  bool WaveTrack::Unlock() const
  {
     for (const auto &clip : mClips)
        clip->Unlock();
  
     return true;
  }
  
  AUDACITY_DLL_API sampleCount WaveTrack::TimeToLongSamples(double t0) const
  {
     return sampleCount( floor(t0 * mRate + 0.5) );
  }
  
  double WaveTrack::LongSamplesToTime(sampleCount pos) const
  {
     return pos.as_double() / mRate;
  }
  
  double WaveTrack::GetStartTime() const
  {
     bool found = false;
     double best = 0.0;
  
     if (mClips.empty())
        return 0;
  
     for (const auto &clip : mClips)
        if (!found)
        {
           found = true;
           best = clip->GetStartTime();
        }
        else if (clip->GetStartTime() < best)
           best = clip->GetStartTime();
  
     return best;
  }
  
  double WaveTrack::GetEndTime() const
  {
     bool found = false;
     double best = 0.0;
  
     if (mClips.empty())
        return 0;
  
     for (const auto &clip : mClips)
        if (!found)
        {
           found = true;
           best = clip->GetEndTime();
        }
        else if (clip->GetEndTime() > best)
           best = clip->GetEndTime();
  
     return best;
  }
  
  //
  // Getting/setting samples.  The sample counts here are
  // expressed relative to t=0.0 at the track's sample rate.
  //
  
  std::pair<float, float> WaveTrack::GetMinMax(
     double t0, double t1, bool mayThrow) const
  {
     std::pair<float, float> results {
        // we need these at extremes to make sure we find true min and max
        FLT_MAX, -FLT_MAX
     };
     bool clipFound = false;
  
     if (t0 > t1) {
        if (mayThrow)
           THROW_INCONSISTENCY_EXCEPTION;
        return results;
     }
  
     if (t0 == t1)
        return results;
  
     for (const auto &clip: mClips)
     {
        if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
        {
           clipFound = true;
           auto clipResults = clip->GetMinMax(t0, t1, mayThrow);
           if (clipResults.first < results.first)
              results.first = clipResults.first;
           if (clipResults.second > results.second)
              results.second = clipResults.second;
        }
     }
  
     if(!clipFound)
     {
        results = { 0.f, 0.f }; // sensible defaults if no clips found
     }
  
     return results;
  }
  
  float WaveTrack::GetRMS(double t0, double t1, bool mayThrow) const
  {
     if (t0 > t1) {
        if (mayThrow)
           THROW_INCONSISTENCY_EXCEPTION;
        return 0.f;
     }
  
     if (t0 == t1)
        return 0.f;
  
     double sumsq = 0.0;
     sampleCount length = 0;
  
     for (const auto &clip: mClips)
     {
        // If t1 == clip->GetStartTime() or t0 == clip->GetEndTime(), then the clip
        // is not inside the selection, so we don't want it.
        // if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
        if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
        {
           sampleCount clipStart, clipEnd;
  
           float cliprms = clip->GetRMS(t0, t1, mayThrow);
  
           clip->TimeToSamplesClip(wxMax(t0, clip->GetStartTime()), &clipStart);
           clip->TimeToSamplesClip(wxMin(t1, clip->GetEndTime()), &clipEnd);
           sumsq += cliprms * cliprms * (clipEnd - clipStart).as_float();
           length += (clipEnd - clipStart);
        }
     }
     return length > 0 ? sqrt(sumsq / length.as_double()) : 0.0;
  }
  
  bool WaveTrack::Get(samplePtr buffer, sampleFormat format,
                      sampleCount start, size_t len, fillFormat fill,
                      bool mayThrow, sampleCount * pNumCopied) const
  {
     // Simple optimization: When this buffer is completely contained within one clip,
     // don't clear anything (because we won't have to). Otherwise, just clear
     // everything to be on the safe side.
     bool doClear = true;
     bool result = true;
     sampleCount samplesCopied = 0;
     for (const auto &clip: mClips)
     {
        if (start >= clip->GetStartSample() && start+len <= clip->GetEndSample())
        {
           doClear = false;
           break;
        }
     }
     if (doClear)
     {
        // Usually we fill in empty space with zero
        if( fill == fillZero )
           ClearSamples(buffer, format, 0, len);
        // but we don't have to.
        else if( fill==fillTwo )
        {
           wxASSERT( format==floatSample );
           float * pBuffer = (float*)buffer;
           for(size_t i=0;i<len;i++)
              pBuffer[i]=2.0f;
        }
        else
        {
           wxFAIL_MSG(wxT("Invalid fill format"));
        }
     }
  
     for (const auto &clip: mClips)
     {
        auto clipStart = clip->GetStartSample();
        auto clipEnd = clip->GetEndSample();
  
        if (clipEnd > start && clipStart < start+len)
        {
           // Clip sample region and Get/Put sample region overlap
           auto samplesToCopy =
              std::min( start+len - clipStart, clip->GetNumSamples() );
           auto startDelta = clipStart - start;
           decltype(startDelta) inclipDelta = 0;
           if (startDelta < 0)
           {
              inclipDelta = -startDelta; // make positive value
              samplesToCopy -= inclipDelta;
              // samplesToCopy is now either len or
              //    (clipEnd - clipStart) - (start - clipStart)
              //    == clipEnd - start > 0
              // samplesToCopy is not more than len
              //
              startDelta = 0;
              // startDelta is zero
           }
           else {
              // startDelta is nonnegative and less than than len
              // samplesToCopy is positive and not more than len
           }
  
           if (!clip->GetSamples(
                 (samplePtr)(((char*)buffer) +
                             startDelta.as_size_t() *
                             SAMPLE_SIZE(format)),
                 format, inclipDelta, samplesToCopy.as_size_t(), mayThrow ))
              result = false;
           else
              samplesCopied += samplesToCopy;
        }
     }
     if( pNumCopied )
        *pNumCopied = samplesCopied;
     return result;
  }
  
  void WaveTrack::Set(samplePtr buffer, sampleFormat format,
                      sampleCount start, size_t len)
  // WEAK-GUARANTEE
  {
     for (const auto &clip: mClips)
     {
        auto clipStart = clip->GetStartSample();
        auto clipEnd = clip->GetEndSample();
  
        if (clipEnd > start && clipStart < start+len)
        {
           // Clip sample region and Get/Put sample region overlap
           auto samplesToCopy =
              std::min( start+len - clipStart, clip->GetNumSamples() );
           auto startDelta = clipStart - start;
           decltype(startDelta) inclipDelta = 0;
           if (startDelta < 0)
           {
              inclipDelta = -startDelta; // make positive value
              samplesToCopy -= inclipDelta;
              // samplesToCopy is now either len or
              //    (clipEnd - clipStart) - (start - clipStart)
              //    == clipEnd - start > 0
              // samplesToCopy is not more than len
              //
              startDelta = 0;
              // startDelta is zero
           }
           else {
              // startDelta is nonnegative and less than than len
              // samplesToCopy is positive and not more than len
           }
  
           clip->SetSamples(
                 (samplePtr)(((char*)buffer) +
                             startDelta.as_size_t() *
                             SAMPLE_SIZE(format)),
                            format, inclipDelta, samplesToCopy.as_size_t() );
           clip->MarkChanged();
        }
     }
  }
  
  void WaveTrack::GetEnvelopeValues(double *buffer, size_t bufferLen,
                                    double t0) const
  {
     // The output buffer corresponds to an unbroken span of time which the callers expect
     // to be fully valid.  As clips are processed below, the output buffer is updated with
     // envelope values from any portion of a clip, start, end, middle, or none at all.
     // Since this does not guarantee that the entire buffer is filled with values we need
     // to initialize the entire buffer to a default value.
     //
     // This does mean that, in the cases where a usable clip is located, the buffer value will
     // be set twice.  Unfortunately, there is no easy way around this since the clips are not
     // stored in increasing time order.  If they were, we could just track the time as the
     // buffer is filled.
     for (decltype(bufferLen) i = 0; i < bufferLen; i++)
     {
        buffer[i] = 1.0;
     }
  
     double startTime = t0;
     auto tstep = 1.0 / mRate;
     double endTime = t0 + tstep * bufferLen;
     for (const auto &clip: mClips)
     {
        // IF clip intersects startTime..endTime THEN...
        auto dClipStartTime = clip->GetStartTime();
        auto dClipEndTime = clip->GetEndTime();
        if ((dClipStartTime < endTime) && (dClipEndTime > startTime))
        {
           auto rbuf = buffer;
           auto rlen = bufferLen;
           auto rt0 = t0;
  
           if (rt0 < dClipStartTime)
           {
              // This is not more than the number of samples in
              // (endTime - startTime) which is bufferLen:
              auto nDiff = (sampleCount)floor((dClipStartTime - rt0) * mRate + 0.5);
              auto snDiff = nDiff.as_size_t();
              rbuf += snDiff;
              wxASSERT(snDiff <= rlen);
              rlen -= snDiff;
              rt0 = dClipStartTime;
           }
  
           if (rt0 + rlen*tstep > dClipEndTime)
           {
              auto nClipLen = clip->GetEndSample() - clip->GetStartSample();
  
              if (nClipLen <= 0) // Testing for bug 641, this problem is consistently '== 0', but doesn't hurt to check <.
                 return;
  
              // This check prevents problem cited in http://bugzilla.audacityteam.org/show_bug.cgi?id=528#c11,
              // Gale's cross_fade_out project, which was already corrupted by bug 528.
              // This conditional prevents the previous write past the buffer end, in clip->GetEnvelope() call.
              // Never increase rlen here.
              // PRL bug 827:  rewrote it again
              rlen = limitSampleBufferSize( rlen, nClipLen );
              rlen = std::min(rlen, size_t(floor(0.5 + (dClipEndTime - rt0) / tstep)));
           }
           // Samples are obtained for the purpose of rendering a wave track,
           // so quantize time
           clip->GetEnvelope()->GetValues(rbuf, rlen, rt0, tstep);
        }
     }
  }
  
  WaveClip* WaveTrack::GetClipAtX(int xcoord)
  {
     for (const auto &clip: mClips)
     {
        wxRect r;
        clip->GetDisplayRect(&r);
        if (xcoord >= r.x && xcoord < r.x+r.width)
           return clip.get();
     }
  
     return NULL;
  }
  
  WaveClip* WaveTrack::GetClipAtSample(sampleCount sample)
  {
     for (const auto &clip: mClips)
     {
        auto start = clip->GetStartSample();
        auto len   = clip->GetNumSamples();
  
        if (sample >= start && sample < start + len)
           return clip.get();
     }
  
     return NULL;
  }
  
  // When the time is both the end of a clip and the start of the next clip, the
  // latter clip is returned.
  WaveClip* WaveTrack::GetClipAtTime(double time)
  {
     
     const auto clips = SortedClipArray();
     auto p = std::find_if(clips.rbegin(), clips.rend(), [&] (WaveClip* const& clip) {
        return time >= clip->GetStartTime() && time <= clip->GetEndTime(); });
  
     // When two clips are immediately next to each other, the GetEndTime() of the first clip
     // and the GetStartTime() of the second clip may not be exactly equal due to rounding errors.
     // If "time" is the end time of the first of two such clips, and the end time is slightly
     // less than the start time of the second clip, then the first rather than the
     // second clip is found by the above code. So correct this.
     if (p != clips.rend() && p != clips.rbegin() &&
        time == (*p)->GetEndTime() &&
        (*p)->SharesBoundaryWithNextClip(*(p-1))) {
        p--;
     }
  
     return p != clips.rend() ? *p : nullptr;
  }
  
  Envelope* WaveTrack::GetEnvelopeAtX(int xcoord)
  {
     WaveClip* clip = GetClipAtX(xcoord);
     if (clip)
        return clip->GetEnvelope();
     else
        return NULL;
  }
  
  Sequence* WaveTrack::GetSequenceAtX(int xcoord)
  {
     WaveClip* clip = GetClipAtX(xcoord);
     if (clip)
        return clip->GetSequence();
     else
        return NULL;
  }
  
  WaveClip* WaveTrack::CreateClip()
  {
     mClips.push_back(std::make_unique<WaveClip>(mDirManager, mFormat, mRate, GetWaveColorIndex()));
     return mClips.back().get();
  }
  
  WaveClip* WaveTrack::NewestOrNewClip()
  {
     if (mClips.empty()) {
        WaveClip *clip = CreateClip();
        clip->SetOffset(mOffset);
        return clip;
     }
     else
        return mClips.back().get();
  }
  
  WaveClip* WaveTrack::RightmostOrNewClip()
  // NOFAIL-GUARANTEE
  {
     if (mClips.empty()) {
        WaveClip *clip = CreateClip();
        clip->SetOffset(mOffset);
        return clip;
     }
     else
     {
        auto it = mClips.begin();
        WaveClip *rightmost = (*it++).get();
        double maxOffset = rightmost->GetOffset();
        for (auto end = mClips.end(); it != end; ++it)
        {
           WaveClip *clip = it->get();
           double offset = clip->GetOffset();
           if (maxOffset < offset)
              maxOffset = offset, rightmost = clip;
        }
        return rightmost;
     }
  }
  
  int WaveTrack::GetClipIndex(const WaveClip* clip) const
  {
     int result;
     FindClip(mClips, clip, &result);
     return result;
  }
  
  WaveClip* WaveTrack::GetClipByIndex(int index)
  {
     if(index < (int)mClips.size())
        return mClips[index].get();
     else
        return nullptr;
  }
  
  const WaveClip* WaveTrack::GetClipByIndex(int index) const
  {
     return const_cast<WaveTrack&>(*this).GetClipByIndex(index);
  }
  
  int WaveTrack::GetNumClips() const
  {
     return mClips.size();
  }
  
  bool WaveTrack::CanOffsetClip(WaveClip* clip, double amount,
                                double *allowedAmount /* = NULL */)
  {
     if (allowedAmount)
        *allowedAmount = amount;
  
     for (const auto &c: mClips)
     {
        if (c.get() != clip && c->GetStartTime() < clip->GetEndTime()+amount &&
                         c->GetEndTime() > clip->GetStartTime()+amount)
        {
           if (!allowedAmount)
              return false; // clips overlap
  
           if (amount > 0)
           {
              if (c->GetStartTime()-clip->GetEndTime() < *allowedAmount)
                 *allowedAmount = c->GetStartTime()-clip->GetEndTime();
              if (*allowedAmount < 0)
                 *allowedAmount = 0;
           } else
           {
              if (c->GetEndTime()-clip->GetStartTime() > *allowedAmount)
                 *allowedAmount = c->GetEndTime()-clip->GetStartTime();
              if (*allowedAmount > 0)
                 *allowedAmount = 0;
           }
        }
     }
  
     if (allowedAmount)
     {
        if (*allowedAmount == amount)
           return true;
  
        // Check if the NEW calculated amount would not violate
        // any other constraint
        if (!CanOffsetClip(clip, *allowedAmount, NULL)) {
           *allowedAmount = 0; // play safe and don't allow anything
           return false;
        }
        else
           return true;
     } else
        return true;
  }
  
  bool WaveTrack::CanInsertClip(WaveClip* clip,  double &slideBy, double &tolerance)
  {
     for (const auto &c : mClips)
     {
        double d1 = c->GetStartTime() - (clip->GetEndTime()+slideBy);
        double d2 = (clip->GetStartTime()+slideBy) - c->GetEndTime();
        if ( (d1<0) &&  (d2<0) )
        {
           // clips overlap.
           // Try to rescue it.
           // The rescue logic is not perfect, and will typically
           // move the clip at most once.  
           // We divide by 1000 rather than set to 0, to allow for 
           // a second 'micro move' that is really about rounding error.
           if( -d1 < tolerance ){
              // right edge of clip overlaps slightly.
              // slide clip left a small amount.
              slideBy +=d1;
              tolerance /=1000;
           } else if( -d2 < tolerance ){
              // left edge of clip overlaps slightly.
              // slide clip right a small amount.
              slideBy -= d2;
              tolerance /=1000;
           }
           else
              return false; // clips overlap  No tolerance left.
        }
     }
  
     return true;
  }
  
  void WaveTrack::Split( double t0, double t1 )
  // WEAK-GUARANTEE
  {
     SplitAt( t0 );
     if( t0 != t1 )
        SplitAt( t1 );
  }
  
  void WaveTrack::SplitAt(double t)
  // WEAK-GUARANTEE
  {
     for (const auto &c : mClips)
     {
        if (c->WithinClip(t))
        {
           t = LongSamplesToTime(TimeToLongSamples(t)); // put t on a sample
           auto newClip = std::make_unique<WaveClip>( *c, mDirManager, true );
           c->Clear(t, c->GetEndTime());
           newClip->Clear(c->GetStartTime(), t);
  
           //offset the NEW clip by the splitpoint (noting that it is already offset to c->GetStartTime())
           sampleCount here = llrint(floor(((t - c->GetStartTime()) * mRate) + 0.5));
           newClip->Offset(here.as_double()/(double)mRate);
           // This could invalidate the iterators for the loop!  But we return
           // at once so it's okay
           mClips.push_back(std::move(newClip)); // transfer ownership
           return;
        }
     }
  }
  
  void WaveTrack::UpdateLocationsCache() const
  {
     auto clips = SortedClipArray();
  
     mDisplayLocationsCache.clear();
  
     // Count number of display locations
     int num = 0;
     {
        const WaveClip *prev = nullptr;
        for (const auto clip : clips)
        {
           num += clip->NumCutLines();
  
           if (prev && fabs(prev->GetEndTime() -
                            clip->GetStartTime()) < WAVETRACK_MERGE_POINT_TOLERANCE)
              ++num;
  
           prev = clip;
        }
     }
  
     if (num == 0)
        return;
  
     // Alloc necessary number of display locations
     mDisplayLocationsCache.reserve(num);
  
     // Add all display locations to cache
     int curpos = 0;
  
     const WaveClip *previousClip = nullptr;
     for (const auto clip: clips)
     {
        for (const auto &cc : clip->GetCutLines())
        {
           // Add cut line expander point
           mDisplayLocationsCache.push_back(WaveTrackLocation{
              clip->GetOffset() + cc->GetOffset(),
              WaveTrackLocation::locationCutLine
           });
           curpos++;
        }
  
        if (previousClip)
        {
           if (fabs(previousClip->GetEndTime() - clip->GetStartTime())
                                            < WAVETRACK_MERGE_POINT_TOLERANCE)
           {
              // Add merge point
              mDisplayLocationsCache.push_back(WaveTrackLocation{
                 previousClip->GetEndTime(),
                 WaveTrackLocation::locationMergePoint,
                 GetClipIndex(previousClip),
                 GetClipIndex(clip)
              });
              curpos++;
           }
        }
  
        previousClip = clip;
     }
  
     wxASSERT(curpos == num);
  }
  
  // Expand cut line (that is, re-insert audio, then DELETE audio saved in cut line)
  void WaveTrack::ExpandCutLine(double cutLinePosition, double* cutlineStart,
                                double* cutlineEnd)
  // STRONG-GUARANTEE
  {
     bool editClipCanMove = gPrefs->GetEditClipsCanMove();
  
     // Find clip which contains this cut line
     double start = 0, end = 0;
     auto pEnd = mClips.end();
     auto pClip = std::find_if( mClips.begin(), pEnd,
        [&](const WaveClipHolder &clip) {
           return clip->FindCutLine(cutLinePosition, &start, &end); } );
     if (pClip != pEnd)
     {
        auto &clip = *pClip;
        if (!editClipCanMove)
        {
           // We are not allowed to move the other clips, so see if there
           // is enough room to expand the cut line
           for (const auto &clip2: mClips)
           {
              if (clip2->GetStartTime() > clip->GetStartTime() &&
                  clip->GetEndTime() + end - start > clip2->GetStartTime())
                 // STRONG-GUARANTEE in case of this path
                 throw SimpleMessageBoxException{
                    _("There is not enough room available to expand the cut line")
                 };
            }
        }
  
        clip->ExpandCutLine(cutLinePosition);
  
        // STRONG-GUARANTEE provided that the following gives NOFAIL-GUARANTEE
  
        if (cutlineStart)
           *cutlineStart = start;
        if (cutlineEnd)
           *cutlineEnd = end;
  
        // Move clips which are to the right of the cut line
        if (editClipCanMove)
        {
           for (const auto &clip2 : mClips)
           {
              if (clip2->GetStartTime() > clip->GetStartTime())
                 clip2->Offset(end - start);
           }
        }
     }
  }
  
  bool WaveTrack::RemoveCutLine(double cutLinePosition)
  {
     for (const auto &clip : mClips)
        if (clip->RemoveCutLine(cutLinePosition))
           return true;
  
     return false;
  }
  
  void WaveTrack::MergeClips(int clipidx1, int clipidx2)
  // STRONG-GUARANTEE
  {
     WaveClip* clip1 = GetClipByIndex(clipidx1);
     WaveClip* clip2 = GetClipByIndex(clipidx2);
  
     if (!clip1 || !clip2) // Could happen if one track of a linked pair had a split and the other didn't.
        return; // Don't throw, just do nothing.
  
     // Append data from second clip to first clip
     // use STRONG-GUARANTEE
     clip1->Paste(clip1->GetEndTime(), clip2);
     
     // use NOFAIL-GUARANTEE for the rest
     // Delete second clip
     auto it = FindClip(mClips, clip2);
     mClips.erase(it);
  }
  
  void WaveTrack::Resample(int rate, ProgressDialog *progress)
  // WEAK-GUARANTEE
  // Partial completion may leave clips at differing sample rates!
  {
     for (const auto &clip : mClips)
        clip->Resample(rate, progress);
  
     mRate = rate;
  }
  
  namespace {
     template < typename Cont1, typename Cont2 >
     Cont1 FillSortedClipArray(const Cont2& mClips)
     {
        Cont1 clips;
        for (const auto &clip : mClips)
           clips.push_back(clip.get());
        std::sort(clips.begin(), clips.end(),
           [](const WaveClip *a, const WaveClip *b)
        { return a->GetStartTime() < b->GetStartTime(); });
        return clips;
     }
  }
  
  WaveClipPointers WaveTrack::SortedClipArray()
  {
     return FillSortedClipArray<WaveClipPointers>(mClips);
  }
  
  WaveClipConstPointers WaveTrack::SortedClipArray() const
  {
     return FillSortedClipArray<WaveClipConstPointers>(mClips);
  }
  
  ///Deletes all clips' wavecaches.  Careful, This may not be threadsafe.
  void WaveTrack::ClearWaveCaches()
  {
     for (const auto &clip : mClips)
        clip->ClearWaveCache();
  }
  
  ///Adds an invalid region to the wavecache so it redraws that portion only.
  void WaveTrack::AddInvalidRegion(sampleCount startSample, sampleCount endSample)
  {
     for (const auto &clip : mClips)
        clip->AddInvalidRegion(startSample, endSample);
  }
  
  int WaveTrack::GetAutoSaveIdent()
  {
     return mAutoSaveIdent;
  }
  
  void WaveTrack::SetAutoSaveIdent(int ident)
  {
     mAutoSaveIdent = ident;
  }
  
  WaveTrackCache::~WaveTrackCache()
  {
  }
  
  void WaveTrackCache::SetTrack(const std::shared_ptr<const WaveTrack> &pTrack)
  {
     if (mPTrack != pTrack) {
        if (pTrack) {
           mBufferSize = pTrack->GetMaxBlockSize();
           if (!mPTrack ||
               mPTrack->GetMaxBlockSize() != mBufferSize) {
              Free();
              mBuffers[0].data = Floats{ mBufferSize };
              mBuffers[1].data = Floats{ mBufferSize };
           }
        }
        else
           Free();
        mPTrack = pTrack;
        mNValidBuffers = 0;
     }
  }
  
  constSamplePtr WaveTrackCache::Get(sampleFormat format,
     sampleCount start, size_t len, bool mayThrow)
  {
     if (format == floatSample && len > 0) {
        const auto end = start + len;
  
        bool fillFirst = (mNValidBuffers < 1);
        bool fillSecond = (mNValidBuffers < 2);
  
        // Discard cached results that we no longer need
        if (mNValidBuffers > 0 &&
            (end <= mBuffers[0].start ||
             start >= mBuffers[mNValidBuffers - 1].end())) {
           // Complete miss
           fillFirst = true;
           fillSecond = true;
        }
        else if (mNValidBuffers == 2 &&
                 start >= mBuffers[1].start &&
                 end > mBuffers[1].end()) {
           // Request starts in the second buffer and extends past it.
           // Discard the first buffer.
           // (But don't deallocate the buffer space.)
           mBuffers[0] .swap ( mBuffers[1] );
           fillSecond = true;
           mNValidBuffers = 1;
        }
        else if (mNValidBuffers > 0 &&
           start < mBuffers[0].start &&
           0 <= mPTrack->GetBlockStart(start)) {
           // Request is not a total miss but starts before the cache,
           // and there is a clip to fetch from.
           // Not the access pattern for drawing spectrogram or playback,
           // but maybe scrubbing causes this.
           // Move the first buffer into second place, and later
           // refill the first.
           // (This case might be useful when marching backwards through
           // the track, as with scrubbing.)
           mBuffers[0] .swap ( mBuffers[1] );
           fillFirst = true;
           fillSecond = false;
           // Cache is not in a consistent state yet
           mNValidBuffers = 0;
        }
  
        // Refill buffers as needed
        if (fillFirst) {
           const auto start0 = mPTrack->GetBlockStart(start);
           if (start0 >= 0) {
              const auto len0 = mPTrack->GetBestBlockSize(start0);
              wxASSERT(len0 <= mBufferSize);
              if (!mPTrack->Get(
                    samplePtr(mBuffers[0].data.get()), floatSample, start0, len0,
                    fillZero, mayThrow))
                 return 0;
              mBuffers[0].start = start0;
              mBuffers[0].len = len0;
              if (!fillSecond &&
                  mBuffers[0].end() != mBuffers[1].start)
                 fillSecond = true;
              // Keep the partially updated state consistent:
              mNValidBuffers = fillSecond ? 1 : 2;
           }
           else {
              // Request may fall between the clips of a track.
              // Invalidate all.  WaveTrack::Get() will return zeroes.
              mNValidBuffers = 0;
              fillSecond = false;
           }
        }
        wxASSERT(!fillSecond || mNValidBuffers > 0);
        if (fillSecond) {
           mNValidBuffers = 1;
           const auto end0 = mBuffers[0].end();
           if (end > end0) {
              const auto start1 = mPTrack->GetBlockStart(end0);
              if (start1 == end0) {
                 const auto len1 = mPTrack->GetBestBlockSize(start1);
                 wxASSERT(len1 <= mBufferSize);
                 if (!mPTrack->Get(samplePtr(mBuffers[1].data.get()), floatSample, start1, len1, fillZero, mayThrow))
                    return 0;
                 mBuffers[1].start = start1;
                 mBuffers[1].len = len1;
                 mNValidBuffers = 2;
              }
           }
        }
        wxASSERT(mNValidBuffers < 2 || mBuffers[0].end() == mBuffers[1].start);
  
        samplePtr buffer = 0;
        auto remaining = len;
  
        // Possibly get an initial portion that is uncached
  
        // This may be negative
        const auto initLen =
           mNValidBuffers < 1 ? sampleCount( len )
              : std::min(sampleCount( len ), mBuffers[0].start - start);
  
        if (initLen > 0) {
           // This might be fetching zeroes between clips
           mOverlapBuffer.Resize(len, format);
           // initLen is not more than len:
           auto sinitLen = initLen.as_size_t();
           if (!mPTrack->Get(mOverlapBuffer.ptr(), format, start, sinitLen,
                             fillZero, mayThrow))
              return 0;
           wxASSERT( sinitLen <= remaining );
           remaining -= sinitLen;
           start += initLen;
           buffer = mOverlapBuffer.ptr() + sinitLen * SAMPLE_SIZE(format);
        }
  
        // Now satisfy the request from the buffers
        for (int ii = 0; ii < mNValidBuffers && remaining > 0; ++ii) {
           const auto starti = start - mBuffers[ii].start;
           // Treatment of initLen above establishes this loop invariant,
           // and statements below preserve it:
           wxASSERT(starti >= 0);
  
           // This may be negative
           const auto leni =
              std::min( sampleCount( remaining ), mBuffers[ii].len - starti );
           if (initLen <= 0 && leni == len) {
              // All is contiguous already.  We can completely avoid copying
              // leni is nonnegative, therefore start falls within mBuffers[ii],
              // so starti is bounded between 0 and buffer length
              return samplePtr(mBuffers[ii].data.get() + starti.as_size_t() );
           }
           else if (leni > 0) {
              // leni is nonnegative, therefore start falls within mBuffers[ii]
              // But we can't satisfy all from one buffer, so copy
              if (buffer == 0) {
                 mOverlapBuffer.Resize(len, format);
                 buffer = mOverlapBuffer.ptr();
              }
              // leni is positive and not more than remaining
              const size_t size = sizeof(float) * leni.as_size_t();
              // starti is less than mBuffers[ii].len and nonnegative
              memcpy(buffer, mBuffers[ii].data.get() + starti.as_size_t(), size);
              wxASSERT( leni <= remaining );
              remaining -= leni.as_size_t();
              start += leni;
              buffer += size;
           }
        }
  
        if (remaining > 0) {
           // Very big request!
           // Fall back to direct fetch
           if (buffer == 0) {
              mOverlapBuffer.Resize(len, format);
              buffer = mOverlapBuffer.ptr();
           }
           if (!mPTrack->Get(buffer, format, start, remaining, fillZero, mayThrow))
              return 0;
        }
  
        return mOverlapBuffer.ptr();
     }
  
     // Cache works only for float format.
     mOverlapBuffer.Resize(len, format);
     if (mPTrack->Get(mOverlapBuffer.ptr(), format, start, len, fillZero, mayThrow))
        return mOverlapBuffer.ptr();
     else
        return 0;
  }
  
  void WaveTrackCache::Free()
  {
     mBuffers[0].Free();
     mBuffers[1].Free();
     mOverlapBuffer.Free();
     mNValidBuffers = 0;
  }
  