/**********************************************************************

  Audacity: A Digital Audio Editor

  Sequence.cpp

  Dominic Mazzoni

*******************************************************************//**
\file Sequence.cpp
\brief Implements classes Sequence and SeqBlock.

*//****************************************************************//**

\class Sequence
\brief A WaveTrack contains WaveClip(s).
   A WaveClip contains a Sequence. A Sequence is primarily an
   interface to an array of SeqBlock instances, corresponding to
   the audio BlockFiles on disk.
   Contrast with RingBuffer.

*//****************************************************************//**

\class SeqBlock
\brief Data structure containing pointer to a BlockFile and
   a start time. Element of a BlockArray.

*//*******************************************************************/


#include "Audacity.h"
#include "Sequence.h"

#include <algorithm>
#include <float.h>
#include <math.h>

#include <wx/dynarray.h>
#include <wx/intl.h>
#include <wx/filefn.h>
#include <wx/ffile.h>
#include <wx/log.h>

#include "BlockFile.h"
#include "blockfile/ODDecodeBlockFile.h"
#include "DirManager.h"

#include "blockfile/SimpleBlockFile.h"
#include "blockfile/SilentBlockFile.h"

int Sequence::sMaxDiskBlockSize = 1048576;

// Sequence methods
Sequence::Sequence(DirManager * projDirManager, sampleFormat format)
   : mDirManager(projDirManager)
   , mSampleFormat(format)
   , mMinSamples(sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2)
   , mMaxSamples(mMinSamples * 2)
{
   mDirManager->Ref();
}

// essentially a copy constructor - but you must pass in the
// current project's DirManager, because we might be copying
// from one project to another
Sequence::Sequence(const Sequence &orig, DirManager *projDirManager)
   : mDirManager(projDirManager)
   , mSampleFormat(orig.mSampleFormat)
   , mMinSamples(orig.mMinSamples)
   , mMaxSamples(orig.mMaxSamples)
{
   mDirManager->Ref();

   bool bResult = Paste(0, &orig);
   wxASSERT(bResult); // TO DO: Actually handle this.
   (void)bResult;
}

Sequence::~Sequence()
{
   DerefAllFiles();
   mDirManager->Deref();
}

void Sequence::DerefAllFiles()
{
   for (size_t i = 0, nn = mBlock.size(); i < nn; i++)
   {
      BlockFile *& pOldFile = mBlock[i].f;
      if (pOldFile) {
         mDirManager->Deref(pOldFile);
         pOldFile = NULL;
      }
   }
}

sampleCount Sequence::GetMaxBlockSize() const
{
   return mMaxSamples;
}

sampleCount Sequence::GetIdealBlockSize() const
{
   return mMaxSamples;
}

bool Sequence::Lock()
{
   for (unsigned int i = 0; i < mBlock.size(); i++)
      mBlock[i].f->Lock();

   return true;
}

bool Sequence::CloseLock()
{
   for (unsigned int i = 0; i < mBlock.size(); i++)
      mBlock[i].f->CloseLock();

   return true;
}

bool Sequence::Unlock()
{
   for (unsigned int i = 0; i < mBlock.size(); i++)
      mBlock[i].f->Unlock();

   return true;
}

sampleFormat Sequence::GetSampleFormat() const
{
   return mSampleFormat;
}

/*
bool Sequence::SetSampleFormat(sampleFormat format)
{
   if (mBlock.size() > 0 || mNumSamples > 0)
      return false;

   mSampleFormat = format;
   return true;
}
*/

bool Sequence::ConvertToSampleFormat(sampleFormat format, bool* pbChanged)
{
   wxASSERT(pbChanged);
   *pbChanged = false;

   // Caller should check this no-change case before calling; we ignore it here.
   if (format == mSampleFormat)
      return true;

   if (mBlock.size() == 0)
   {
      mSampleFormat = format;
      *pbChanged = true;
      return true;
   }

   const sampleFormat oldFormat = mSampleFormat;
   mSampleFormat = format;

   const sampleCount oldMinSamples = mMinSamples, oldMaxSamples = mMaxSamples;
   // These are the same calculations as in the constructor.
   mMinSamples = sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2;
   mMaxSamples = mMinSamples * 2;

   BlockArray newBlockArray;
   // Use the ratio of old to NEW mMaxSamples to make a reasonable guess at allocation.
   newBlockArray.reserve(1 + mBlock.size() * ((float)oldMaxSamples / (float)mMaxSamples));

   bool bSuccess = true;
   {
      SampleBuffer bufferOld(mMaxSamples, oldFormat);
      SampleBuffer bufferNew(mMaxSamples, format);

      for (size_t i = 0, nn = mBlock.size(); i < nn && bSuccess; i++)
      {
         SeqBlock &oldSeqBlock = mBlock[i];
         BlockFile* oldBlockFile = oldSeqBlock.f;

         sampleCount len = oldBlockFile->GetLength();

         bSuccess = (oldBlockFile->ReadData(bufferOld.ptr(), oldFormat, 0, len) > 0);
         if (!bSuccess)
            break;

         CopySamples(bufferOld.ptr(), oldFormat, bufferNew.ptr(), format, len);

         // Note this fix for http://bugzilla.audacityteam.org/show_bug.cgi?id=451,
         // using Blockify, allows (len < mMinSamples).
         // This will happen consistently when going from more bytes per sample to fewer...
         // This will create a block that's smaller than mMinSamples, which
         // shouldn't be allowed, but we agreed it's okay for now.
         //vvv ANSWER-ME: Does this cause any bugs, or failures on write, elsewhere?
         //    If so, need to special-case (len < mMinSamples) and start combining data
         //    from the old blocks... Oh no!

         // Using Blockify will handle the cases where len > the NEW mMaxSamples. Previous code did not.
         const sampleCount blockstart = oldSeqBlock.start;
         const unsigned prevSize = newBlockArray.size();
         Blockify(newBlockArray, blockstart, bufferNew.ptr(), len);
         bSuccess = (newBlockArray.size() > prevSize);
         if (bSuccess)
            *pbChanged = true;
      }
   }

   if (bSuccess)
   {
      // Invalidate all the old, non-aliased block files.
      // Aliased files will be converted at save, per comment above.

      DerefAllFiles();

      // Replace with NEW blocks.
      mBlock.swap(newBlockArray);
   }
   else
   {
      /* vvvvv We *should do the following, but TrackPanel::OnFormatChange() doesn't actually check the conversion results,
         it just assumes the conversion was successful.
         TODO: Uncomment this section when TrackPanel::OnFormatChange() is upgraded to check the results.

         PRL:  I don't understand why the comment above justifies leaving the sequence in an inconsistent state.
         If this function must fail, better to leave it as a no-op on this sequence.  I am uncommenting the
         lines below, and adding one to revert mMinSamples too.
         */

      // Conversion failed. Revert these member vars.
      mSampleFormat = oldFormat;
      mMaxSamples = oldMaxSamples;
      mMinSamples = oldMinSamples;

      *pbChanged = false;  // Revert overall change flag, in case we had some partial success in the loop.
   }

   bSuccess &= ConsistencyCheck(wxT("Sequence::ConvertToSampleFormat()"));

   return bSuccess;
}

bool Sequence::GetMinMax(sampleCount start, sampleCount len,
                         float * outMin, float * outMax) const
{
   if (len == 0 || mBlock.size() == 0) {
      *outMin = float(0.0);   // FLT_MAX?  So it doesn't look like a spurious '0' to a caller?
      *outMax = float(0.0);   // -FLT_MAX?  So it doesn't look like a spurious '0' to a caller?
      return true;
   }

   float min = FLT_MAX;
   float max = -FLT_MAX;

   unsigned int block0 = FindBlock(start);
   unsigned int block1 = FindBlock(start + len - 1);

   sampleCount s0, l0, maxl0;

   // First calculate the min/max of the blocks in the middle of this region;
   // this is very fast because we have the min/max of every entire block
   // already in memory.

   for (unsigned b = block0 + 1; b < block1; ++b) {
      float blockMin, blockMax, blockRMS;
      mBlock[b].f->GetMinMax(&blockMin, &blockMax, &blockRMS);

      if (blockMin < min)
         min = blockMin;
      if (blockMax > max)
         max = blockMax;
   }

   // Now we take the first and last blocks into account, noting that the
   // selection may only partly overlap these blocks.  If the overall min/max
   // of either of these blocks is within min...max, then we can ignore them.
   // If not, we need read some samples and summaries from disk.
   {
      float block0Min, block0Max, block0RMS;
      const SeqBlock &theBlock = mBlock[block0];
      BlockFile *const theFile = theBlock.f;
      theFile->GetMinMax(&block0Min, &block0Max, &block0RMS);

      if (block0Min < min || block0Max > max) {
         s0 = start - theBlock.start;
         l0 = len;
         maxl0 = theBlock.start + theFile->GetLength() - start;
         wxASSERT(maxl0 <= mMaxSamples); // Vaughan, 2011-10-19
         if (l0 > maxl0)
            l0 = maxl0;

         float partialMin, partialMax, partialRMS;
         theFile->GetMinMax(s0, l0,
            &partialMin, &partialMax, &partialRMS);
         if (partialMin < min)
            min = partialMin;
         if (partialMax > max)
            max = partialMax;
      }
   }

   if (block1 > block0)
   {
      float block1Min, block1Max, block1RMS;
      const SeqBlock &theBlock = mBlock[block1];
      BlockFile *const theFile = theBlock.f;
      theFile->GetMinMax(&block1Min, &block1Max, &block1RMS);

      if (block1Min < min || block1Max > max) {

         s0 = 0;
         l0 = (start + len) - theBlock.start;
         wxASSERT(l0 <= mMaxSamples); // Vaughan, 2011-10-19

         float partialMin, partialMax, partialRMS;
         theFile->GetMinMax(s0, l0,
            &partialMin, &partialMax, &partialRMS);
         if (partialMin < min)
            min = partialMin;
         if (partialMax > max)
            max = partialMax;
      }
   }

   *outMin = min;
   *outMax = max;

   return true;
}

bool Sequence::GetRMS(sampleCount start, sampleCount len,
                         float * outRMS) const
{
   // len is the number of samples that we want the rms of.
   // it may be longer than a block, and the code is carefully set up to handle that.
   if (len == 0 || mBlock.size() == 0) {
      *outRMS = float(0.0);
      return true;
   }

   double sumsq = 0.0;
   sampleCount length = 0; // this is the cumulative length of the bits we have the ms of so far, and should end up == len

   unsigned int block0 = FindBlock(start);
   unsigned int block1 = FindBlock(start + len - 1);

   sampleCount s0, l0, maxl0;

   // First calculate the rms of the blocks in the middle of this region;
   // this is very fast because we have the rms of every entire block
   // already in memory.
   for (unsigned b = block0 + 1; b < block1; b++) {
      float blockMin, blockMax, blockRMS;
      const SeqBlock &theBlock = mBlock[b];
      BlockFile *const theFile = theBlock.f;
      theFile->GetMinMax(&blockMin, &blockMax, &blockRMS);

      const sampleCount fileLen = theFile->GetLength();
      sumsq += blockRMS * blockRMS * fileLen;
      length += fileLen;
   }

   // Now we take the first and last blocks into account, noting that the
   // selection may only partly overlap these blocks.
   // If not, we need read some samples and summaries from disk.
   {
      const SeqBlock &theBlock = mBlock[block0];
      BlockFile *const theFile = theBlock.f;
      s0 = start - theBlock.start;
      l0 = len;
      maxl0 = theBlock.start + theFile->GetLength() - start;
      wxASSERT(maxl0 <= mMaxSamples); // Vaughan, 2011-10-19
      if (l0 > maxl0)
         l0 = maxl0;

      float partialMin, partialMax, partialRMS;
      theFile->GetMinMax(s0, l0, &partialMin, &partialMax, &partialRMS);

      sumsq += partialRMS * partialRMS * l0;
      length += l0;
   }

   if (block1 > block0) {
      const SeqBlock &theBlock = mBlock[block1];
      BlockFile *const theFile = theBlock.f;

      s0 = 0;
      l0 = (start + len) - theBlock.start;
      wxASSERT(l0 <= mMaxSamples); // PRL: I think Vaughan missed this

      float partialMin, partialMax, partialRMS;
      theFile->GetMinMax(s0, l0, &partialMin, &partialMax, &partialRMS);
      sumsq += partialRMS * partialRMS * l0;
      length += l0;
   }

   // PRL: catch bugs like 1320:
   wxASSERT(length == len);

   *outRMS = sqrt(sumsq/length);

   return true;
}

bool Sequence::Copy(sampleCount s0, sampleCount s1, std::unique_ptr<Sequence> &dest) const
{
   dest.reset();

   if (s0 >= s1 || s0 >= mNumSamples || s1 < 0)
      return false;

   int numBlocks = mBlock.size();

   int b0 = FindBlock(s0);
   const int b1 = FindBlock(s1 - 1);
   wxASSERT(b0 >= 0);
   wxASSERT(b0 < numBlocks);
   wxASSERT(b1 < numBlocks);
   wxUnusedVar(numBlocks);
   wxASSERT(b0 <= b1);

   dest = std::make_unique<Sequence>(mDirManager, mSampleFormat);
   dest->mBlock.reserve(b1 - b0 + 1);

   SampleBuffer buffer(mMaxSamples, mSampleFormat);

   int blocklen;

   // Do the first block

   const SeqBlock &block0 = mBlock[b0];
   if (s0 != block0.start) {
      BlockFile *const file = block0.f;
      blocklen = std::min(s1, block0.start + file->GetLength()) - s0;
      wxASSERT(file->IsAlias() || (blocklen <= mMaxSamples)); // Vaughan, 2012-02-29
      Get(b0, buffer.ptr(), mSampleFormat, s0, blocklen);

      dest->Append(buffer.ptr(), mSampleFormat, blocklen);
   }
   else
      --b0;

   // If there are blocks in the middle, copy the blockfiles directly
   for (int bb = b0 + 1; bb < b1; ++bb)
      dest->AppendBlock(mBlock[bb]); // Increase ref count or duplicate file

   // Do the last block
   if (b1 > b0) {
      const SeqBlock &block = mBlock[b1];
      BlockFile *const file = block.f;
      blocklen = (s1 - block.start);
      wxASSERT(file->IsAlias() || (blocklen <= mMaxSamples)); // Vaughan, 2012-02-29
      if (blocklen < file->GetLength()) {
         Get(b1, buffer.ptr(), mSampleFormat, block.start, blocklen);
         dest->Append(buffer.ptr(), mSampleFormat, blocklen);
      }
      else
         // Special case, copy exactly
         dest->AppendBlock(block); // Increase ref count or duplicate file
   }

   return ConsistencyCheck(wxT("Sequence::Copy()"));
}

namespace {
   inline bool Overflows(double numSamples)
   {
      return numSamples > wxLL(9223372036854775807);
   }
}

bool Sequence::Paste(sampleCount s, const Sequence *src)
{
   if ((s < 0) || (s > mNumSamples))
   {
      wxLogError(
         wxT("Sequence::Paste: sampleCount s %s is < 0 or > mNumSamples %s)."),
         // PRL:  Why bother with Internat when the above is just wxT?
         Internat::ToString(((wxLongLong)s).ToDouble(), 0).c_str(),
         Internat::ToString(((wxLongLong)mNumSamples).ToDouble(), 0).c_str());
      wxASSERT(false);
      return false;
   }

   // Quick check to make sure that it doesn't overflow
   if (Overflows(((double)mNumSamples) + ((double)src->mNumSamples)))
   {
      wxLogError(
         wxT("Sequence::Paste: mNumSamples %s + src->mNumSamples %s would overflow."),
         // PRL:  Why bother with Internat when the above is just wxT?
         Internat::ToString(((wxLongLong)mNumSamples).ToDouble(), 0).c_str(),
         Internat::ToString(((wxLongLong)src->mNumSamples).ToDouble(), 0).c_str());
      wxASSERT(false);
      return false;
   }

   if (src->mSampleFormat != mSampleFormat)
   {
      wxLogError(
         wxT("Sequence::Paste: Sample format to be pasted, %s, does not match destination format, %s."),
         GetSampleFormatStr(src->mSampleFormat), GetSampleFormatStr(src->mSampleFormat));
      wxASSERT(false);
      return false;
   }

   const BlockArray &srcBlock = src->mBlock;
   sampleCount addedLen = src->mNumSamples;
   const unsigned int srcNumBlocks = srcBlock.size();
   int sampleSize = SAMPLE_SIZE(mSampleFormat);

   if (addedLen == 0 || srcNumBlocks == 0)
      return true;

   const size_t numBlocks = mBlock.size();

   if (numBlocks == 0 ||
       (s == mNumSamples && mBlock.back().f->GetLength() >= mMinSamples)) {
      // Special case: this track is currently empty, or it's safe to append
      // onto the end because the current last block is longer than the
      // minimum size

      for (unsigned int i = 0; i < srcNumBlocks; i++)
         AppendBlock(srcBlock[i]); // Increase ref count or duplicate file

      return ConsistencyCheck(wxT("Paste branch one"));
   }

   const int b = (s == mNumSamples) ? mBlock.size() - 1 : FindBlock(s);
   wxASSERT((b >= 0) && (b < (int)numBlocks));
   SeqBlock *const pBlock = &mBlock[b];
   const sampleCount length = pBlock->f->GetLength();
   const sampleCount largerBlockLen = addedLen + length;
   // PRL: when insertion point is the first sample of a block,
   // and the following test fails, perhaps we could test
   // whether coalescence with the previous block is possible.
   if (largerBlockLen <= mMaxSamples) {
      // Special case: we can fit all of the NEW samples inside of
      // one block!

      SeqBlock &block = *pBlock;
      SampleBuffer buffer(largerBlockLen, mSampleFormat);

      int splitPoint = s - block.start;
      Read(buffer.ptr(), mSampleFormat, block, 0, splitPoint);
      src->Get(0, buffer.ptr() + splitPoint*sampleSize,
               mSampleFormat, 0, addedLen);
      Read(buffer.ptr() + (splitPoint + addedLen)*sampleSize,
           mSampleFormat, block,
           splitPoint, length - splitPoint);

      BlockFile *const file =
         mDirManager->NewSimpleBlockFile(buffer.ptr(), largerBlockLen, mSampleFormat);

      mDirManager->Deref(block.f);
      block.f = file;

      for (unsigned int i = b + 1; i < numBlocks; i++)
         mBlock[i].start += addedLen;

      mNumSamples += addedLen;

      return ConsistencyCheck(wxT("Paste branch two"));
   }

   // Case three: if we are inserting four or fewer blocks,
   // it's simplest to just lump all the data together
   // into one big block along with the split block,
   // then resplit it all
   BlockArray newBlock;
   newBlock.reserve(numBlocks + srcNumBlocks + 2);
   newBlock.insert(newBlock.end(), mBlock.begin(), mBlock.begin() + b);

   SeqBlock &splitBlock = mBlock[b];
   sampleCount splitLen = splitBlock.f->GetLength();
   int splitPoint = s - splitBlock.start;

   unsigned int i;
   if (srcNumBlocks <= 4) {

      sampleCount sum = splitLen + addedLen;

      SampleBuffer sumBuffer(sum, mSampleFormat);
      Read(sumBuffer.ptr(), mSampleFormat, splitBlock, 0, splitPoint);
      src->Get(0, sumBuffer.ptr() + splitPoint * sampleSize,
               mSampleFormat,
               0, addedLen);
      Read(sumBuffer.ptr() + (splitPoint + addedLen) * sampleSize, mSampleFormat,
           splitBlock, splitPoint,
           splitLen - splitPoint);

      Blockify(newBlock, splitBlock.start, sumBuffer.ptr(), sum);
   } else {

      // The final case is that we're inserting at least five blocks.
      // We divide these into three groups: the first two get merged
      // with the first half of the split block, the middle ones get
      // copied in as is, and the last two get merged with the last
      // half of the split block.

      const sampleCount srcFirstTwoLen =
          srcBlock[0].f->GetLength() + srcBlock[1].f->GetLength();
      const sampleCount leftLen = splitPoint + srcFirstTwoLen;

      const SeqBlock &penultimate = srcBlock[srcNumBlocks - 2];
      const sampleCount srcLastTwoLen =
         penultimate.f->GetLength() +
         srcBlock[srcNumBlocks - 1].f->GetLength();
      const sampleCount rightSplit = splitBlock.f->GetLength() - splitPoint;
      const sampleCount rightLen = rightSplit + srcLastTwoLen;

      SampleBuffer sampleBuffer(std::max(leftLen, rightLen), mSampleFormat);

      Read(sampleBuffer.ptr(), mSampleFormat, splitBlock, 0, splitPoint);
      src->Get(0, sampleBuffer.ptr() + splitPoint*sampleSize,
         mSampleFormat, 0, srcFirstTwoLen);

      Blockify(newBlock, splitBlock.start, sampleBuffer.ptr(), leftLen);

      for (i = 2; i < srcNumBlocks - 2; i++) {
         const SeqBlock &block = srcBlock[i];
         BlockFile *const file = mDirManager->CopyBlockFile(block.f);
         if (!file) {
            wxASSERT(false); // TODO: Handle this better, alert the user of failure.
            return false;
         }

         newBlock.push_back(SeqBlock(file, block.start + s));
      }

      sampleCount lastStart = penultimate.start;
      src->Get(srcNumBlocks - 2, sampleBuffer.ptr(), mSampleFormat,
               lastStart, srcLastTwoLen);
      Read(sampleBuffer.ptr() + srcLastTwoLen * sampleSize, mSampleFormat,
           splitBlock, splitPoint, rightSplit);

      Blockify(newBlock, s + lastStart, sampleBuffer.ptr(), rightLen);
   }

   mDirManager->Deref(splitBlock.f);

   // Copy remaining blocks to NEW block array and
   // swap the NEW block array in for the old
   for (i = b + 1; i < numBlocks; i++)
      newBlock.push_back(mBlock[i].Plus(addedLen));

   mBlock.swap(newBlock);

   mNumSamples += addedLen;

   return ConsistencyCheck(wxT("Paste branch three"));
}

bool Sequence::SetSilence(sampleCount s0, sampleCount len)
{
   return Set(NULL, mSampleFormat, s0, len);
}

bool Sequence::InsertSilence(sampleCount s0, sampleCount len)
{
   // Quick check to make sure that it doesn't overflow
   if (Overflows(((double)mNumSamples) + ((double)len)))
      return false;

   if (len <= 0)
      return true;

   // Create a NEW track containing as much silence as we
   // need to insert, and then call Paste to do the insertion.
   // We make use of a SilentBlockFile, which takes up no
   // space on disk.

   Sequence sTrack(mDirManager, mSampleFormat);

   sampleCount idealSamples = GetIdealBlockSize();

   sampleCount pos = 0;

   sTrack.mBlock.reserve((len + idealSamples - 1) / idealSamples);

   BlockFile *silentFile = 0;
   if (len >= idealSamples)
      silentFile = new SilentBlockFile(idealSamples);
   while (len >= idealSamples) {
      sTrack.mBlock.push_back(SeqBlock(silentFile, pos));
      mDirManager->Ref(silentFile);

      pos += idealSamples;
      len -= idealSamples;
   }
   if (silentFile)
      mDirManager->Deref(silentFile);
   if (len) {
      sTrack.mBlock.push_back(SeqBlock(new SilentBlockFile(len), pos));
      pos += len;
   }

   sTrack.mNumSamples = pos;

   bool bResult = Paste(s0, &sTrack);
   wxASSERT(bResult);

   return bResult && ConsistencyCheck(wxT("InsertSilence"));
}

bool Sequence::AppendAlias(const wxString &fullPath,
                           sampleCount start,
                           sampleCount len, int channel, bool useOD)
{
   // Quick check to make sure that it doesn't overflow
   if (Overflows(((double)mNumSamples) + ((double)len)))
      return false;

   SeqBlock newBlock(
      useOD?
         mDirManager->NewODAliasBlockFile(fullPath, start, len, channel):
         mDirManager->NewAliasBlockFile(fullPath, start, len, channel),
      mNumSamples
   );
   mBlock.push_back(newBlock);
   mNumSamples += len;

   return true;
}

bool Sequence::AppendCoded(const wxString &fName, sampleCount start,
                            sampleCount len, int channel, int decodeType)
{
   // Quick check to make sure that it doesn't overflow
   if (Overflows(((double)mNumSamples) + ((double)len)))
      return false;

   SeqBlock newBlock(
      mDirManager->NewODDecodeBlockFile(fName, start, len, channel, decodeType),
      mNumSamples
   );
   mBlock.push_back(newBlock);
   mNumSamples += len;

   return true;
}

bool Sequence::AppendBlock(const SeqBlock &b)
{
   // Quick check to make sure that it doesn't overflow
   if (Overflows(((double)mNumSamples) + ((double)b.f->GetLength())))
      return false;

   SeqBlock newBlock(
      mDirManager->CopyBlockFile(b.f), // Bump ref count if not locked, else copy
      mNumSamples
   );
   if (!newBlock.f) {
      /// \todo Error Could not paste!  (Out of disk space?)
      wxASSERT(false); // TODO: Handle this better, alert the user of failure.
      return false;
   }

   //Don't need to Ref because it was done by CopyBlockFile, above...
   //mDirManager->Ref(newBlock.f);

   mBlock.push_back(newBlock);
   mNumSamples += newBlock.f->GetLength();

   // Don't do a consistency check here because this
   // function gets called in an inner loop.

   return true;
}

///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 Sequence::GetODFlags()
{
   unsigned int ret = 0;
   for (unsigned int i = 0; i < mBlock.size(); i++) {
      BlockFile *const file = mBlock[i].f;
      if(!file->IsDataAvailable())
         ret |= (static_cast<ODDecodeBlockFile*>(file))->GetDecodeType();
      else if(!file->IsSummaryAvailable())
         ret |= ODTask::eODPCMSummary;
   }
   return ret;
}

sampleCount Sequence::GetBlockStart(sampleCount position) const
{
   int b = FindBlock(position);
   return mBlock[b].start;
}

sampleCount Sequence::GetBestBlockSize(sampleCount start) const
{
   // This method returns a nice number of samples you should try to grab in
   // one big chunk in order to land on a block boundary, based on the starting
   // sample.  The value returned will always be nonzero and will be no larger
   // than the value of GetMaxBlockSize()

   if (start < 0 || start >= mNumSamples)
      return mMaxSamples;

   int b = FindBlock(start);
   int numBlocks = mBlock.size();

   const SeqBlock &block = mBlock[b];
   sampleCount result = (block.start + block.f->GetLength() - start);

   sampleCount length;
   while(result < mMinSamples && b+1<numBlocks &&
         ((length = mBlock[b+1].f->GetLength()) + result) <= mMaxSamples) {
      b++;
      result += length;
   }

   wxASSERT(result > 0 && result <= mMaxSamples);

   return result;
}

bool Sequence::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
{
   sampleCount nValue;

   /* handle waveblock tag and its attributes */
   if (!wxStrcmp(tag, wxT("waveblock"))) {
      SeqBlock wb;

      // loop through attrs, which is a null-terminated list of
      // attribute-value pairs
      while(*attrs) {
         const wxChar *attr = *attrs++;
         const wxChar *value = *attrs++;

         if (!value)
            break;

         // Both these attributes have non-negative integer counts of samples, so
         // we can test & convert here, making sure that values > 2^31 are OK
         // because long clips will need them.
         const wxString strValue = value;
         if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0))
         {
            mErrorOpening = true;
            wxLogWarning(
               wxT("   Sequence has bad %s attribute value, %s, that should be a positive integer."),
               attr, strValue.c_str());
            return false;
         }

         if (!wxStrcmp(attr, wxT("start")))
            wb.start = nValue;

         // Vaughan, 2011-10-10: I don't think we ever write a "len" attribute for "waveblock" tag,
         // so I think this is actually legacy code, or something intended, but not completed.
         // Anyway, might as well leave this code in, especially now that it has the check
         // against mMaxSamples.
         if (!wxStrcmp(attr, wxT("len")))
         {
            // mMaxSamples should already have been set by calls to the "sequence" clause below.
            // The check intended here was already done in DirManager::HandleXMLTag(), where
            // it let the block be built, then checked against mMaxSamples, and deleted the block
            // if the size of the block is bigger than mMaxSamples.
            if (nValue > mMaxSamples)
            {
               mErrorOpening = true;
               return false;
            }
            mDirManager->SetLoadingBlockLength(nValue);
         }
      } // while

      mBlock.push_back(wb);
      mDirManager->SetLoadingTarget(&mBlock, mBlock.size() - 1);

      return true;
   }

   /* handle sequence tag and its attributes */
   if (!wxStrcmp(tag, wxT("sequence"))) {
      while(*attrs) {
         const wxChar *attr = *attrs++;
         const wxChar *value = *attrs++;

         if (!value)
            break;

         const wxString strValue = value;	// promote string, we need this for all

         if (!wxStrcmp(attr, wxT("maxsamples")))
         {
            // This attribute is a sample count, so can be 64bit
            if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0))
            {
               mErrorOpening = true;
               return false;
            }
            // Dominic, 12/10/2006:
            //    Let's check that maxsamples is >= 1024 and <= 64 * 1024 * 1024
            //    - that's a pretty wide range of reasonable values.
            if ((nValue < 1024) || (nValue > 64 * 1024 * 1024))
            {
               mErrorOpening = true;
               return false;
            }
            mMaxSamples = nValue;

            // PRL:  Is the following really okay?  DirManager might be shared across projects!
            // PRL:  Yes, because it only affects DirManager's behavior in opening the project.
            mDirManager->SetLoadingMaxSamples(mMaxSamples);
         }
         else if (!wxStrcmp(attr, wxT("sampleformat")))
         {
            // This attribute is a sample format, normal int
            long fValue;
            if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&fValue) || (fValue < 0) || !XMLValueChecker::IsValidSampleFormat(fValue))
            {
               mErrorOpening = true;
               return false;
            }
            mSampleFormat = (sampleFormat)fValue;
         }
         else if (!wxStrcmp(attr, wxT("numsamples")))
         {
            // This attribute is a sample count, so can be 64bit
            if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0))
            {
               mErrorOpening = true;
               return false;
            }
            mNumSamples = nValue;
         }
      } // while

      //// Both mMaxSamples and mSampleFormat should have been set.
      //// Check that mMaxSamples is right for mSampleFormat, using the calculations from the constructor.
      //if ((mMinSamples != sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2) ||
      //      (mMaxSamples != mMinSamples * 2))
      //{
      //   mErrorOpening = true;
      //   return false;
      //}

      return true;
   }

   return false;
}

void Sequence::HandleXMLEndTag(const wxChar *tag)
{
   if (wxStrcmp(tag, wxT("sequence")) != 0)
      return;

   // Make sure that the sequence is valid.
   // First, replace missing blockfiles with SilentBlockFiles
   for (unsigned b = 0, nn = mBlock.size(); b < nn; b++) {
      SeqBlock &block = mBlock[b];
      if (!block.f) {
         sampleCount len;

         if (b < nn - 1)
            len = mBlock[b+1].start - block.start;
         else
            len = mNumSamples - block.start;

         if (len > mMaxSamples)
         {
            // This could be why the blockfile failed, so limit
            // the silent replacement to mMaxSamples.
            wxLogWarning(
               wxT("   Sequence has missing block file with length %s > mMaxSamples %s.\n      Setting length to mMaxSamples. This will likely cause some block files to be considered orphans."),
               // PRL:  Why bother with Internat when the above is just wxT?
               Internat::ToString(((wxLongLong)len).ToDouble(), 0).c_str(),
               Internat::ToString(((wxLongLong)mMaxSamples).ToDouble(), 0).c_str());
            len = mMaxSamples;
         }
         block.f = new SilentBlockFile(len);
         wxLogWarning(
            wxT("Gap detected in project file. Replacing missing block file with silence."));
         mErrorOpening = true;
      }
   }

   // Next, make sure that start times and lengths are consistent
   sampleCount numSamples = 0;
   for (unsigned b = 0, nn = mBlock.size(); b < nn;  b++) {
      SeqBlock &block = mBlock[b];
      if (block.start != numSamples) {
         wxString sFileAndExtension = block.f->GetFileName().name.GetFullName();
         if (sFileAndExtension.IsEmpty())
            sFileAndExtension = wxT("(replaced with silence)");
         else
            sFileAndExtension = wxT("\"") + sFileAndExtension + wxT("\"");
         wxLogWarning(
            wxT("Gap detected in project file.\n")
            wxT("   Start (%s) for block file %s is not one sample past end of previous block (%s).\n")
            wxT("   Moving start so blocks are contiguous."),
            // PRL:  Why bother with Internat when the above is just wxT?
            Internat::ToString(((wxLongLong)(block.start)).ToDouble(), 0).c_str(),
            sFileAndExtension.c_str(),
            Internat::ToString(((wxLongLong)numSamples).ToDouble(), 0).c_str());
         block.start = numSamples;
         mErrorOpening = true;
      }
      numSamples += block.f->GetLength();
   }
   if (mNumSamples != numSamples) {
      wxLogWarning(
         wxT("Gap detected in project file. Correcting sequence sample count from %s to %s."),
         // PRL:  Why bother with Internat when the above is just wxT?
         Internat::ToString(((wxLongLong)mNumSamples).ToDouble(), 0).c_str(),
         Internat::ToString(((wxLongLong)numSamples).ToDouble(), 0).c_str());
      mNumSamples = numSamples;
      mErrorOpening = true;
   }
}

XMLTagHandler *Sequence::HandleXMLChild(const wxChar *tag)
{
   if (!wxStrcmp(tag, wxT("waveblock")))
      return this;
   else {
      mDirManager->SetLoadingFormat(mSampleFormat);
      return mDirManager;
   }
}

void Sequence::WriteXML(XMLWriter &xmlFile)
{
   unsigned int b;

   xmlFile.StartTag(wxT("sequence"));

   xmlFile.WriteAttr(wxT("maxsamples"), mMaxSamples);
   xmlFile.WriteAttr(wxT("sampleformat"), mSampleFormat);
   xmlFile.WriteAttr(wxT("numsamples"), mNumSamples);

   for (b = 0; b < mBlock.size(); b++) {
      SeqBlock &bb = mBlock[b];

      // See http://bugzilla.audacityteam.org/show_bug.cgi?id=451.
      // Also, don't check against mMaxSamples for AliasBlockFiles, because if you convert sample format,
      // mMaxSample gets changed to match the format, but the number of samples in the aliased file
      // has not changed (because sample format conversion was not actually done in the aliased file).
      if (!bb.f->IsAlias() && (bb.f->GetLength() > mMaxSamples))
      {
         wxString sMsg =
            wxString::Format(
               _("Sequence has block file exceeding maximum %s samples per block.\nTruncating to this maximum length."),
               Internat::ToString(((wxLongLong)mMaxSamples).ToDouble(), 0).c_str());
         wxMessageBox(sMsg, _("Warning - Truncating Overlong Block File"), wxICON_EXCLAMATION | wxOK);
         wxLogWarning(sMsg);
         bb.f->SetLength(mMaxSamples);
      }

      xmlFile.StartTag(wxT("waveblock"));
      xmlFile.WriteAttr(wxT("start"), bb.start);

      bb.f->SaveXML(xmlFile);

      xmlFile.EndTag(wxT("waveblock"));
   }

   xmlFile.EndTag(wxT("sequence"));
}

int Sequence::FindBlock(sampleCount pos) const
{
   wxASSERT(pos >= 0 && pos < mNumSamples);

   if (pos == 0)
      return 0;

   int numBlocks = mBlock.size();

   sampleCount lo = 0, loSamples = 0;
   sampleCount hi = numBlocks, hiSamples = mNumSamples;
   sampleCount guess;

   while (true) {
      //this is not a binary search, but a
      //dictionary search where we guess something smarter than the binary division
      //of the unsearched area, since samples are usually proportional to block file number.
      const double frac = double(pos - loSamples) / (hiSamples - loSamples);
      guess = std::min(hi - 1, lo + sampleCount(frac * (hi - lo)));
      const SeqBlock &block = mBlock[guess];

      wxASSERT(block.f->GetLength() > 0);
      wxASSERT(lo <= guess && guess < hi && lo < hi);

      if (pos < block.start) {
         wxASSERT(lo != guess);
         hi = guess;
         hiSamples = block.start;
      }
      else {
         const sampleCount nextStart = block.start + block.f->GetLength();
         if (pos < nextStart)
            break;
         else {
            wxASSERT(guess < hi - 1);
            lo = guess + 1;
            loSamples = nextStart;
         }
      }
   }

   const int rval = guess;
   wxASSERT(rval >= 0 && rval < numBlocks &&
            pos >= mBlock[rval].start &&
            pos < mBlock[rval].start + mBlock[rval].f->GetLength());

   return rval;
}

bool Sequence::Read(samplePtr buffer, sampleFormat format,
                    const SeqBlock &b, sampleCount start, sampleCount len) const
{
   BlockFile *f = b.f;

   wxASSERT(start >= 0);
   wxASSERT(start + len <= f->GetLength());

   int result = f->ReadData(buffer, format, start, len);

   if (result != len)
   {
      wxLogWarning(wxT("Expected to read %ld samples, got %d samples."), len, result);
      if (result < 0)
         result = 0;
      ClearSamples(buffer, format, result, len-result);
   }

   return true;
}

bool Sequence::CopyWrite(SampleBuffer &scratch,
                         samplePtr buffer, SeqBlock &b,
                         sampleCount start, sampleCount len)
{
   // We don't ever write to an existing block; to support Undo,
   // we copy the old block entirely into memory, dereference it,
   // make the change, and then write the NEW block to disk.

   const sampleCount length = b.f->GetLength();
   wxASSERT(length <= mMaxSamples);
   wxASSERT(start + len <= length);
   wxASSERT(start >= 0);

   int sampleSize = SAMPLE_SIZE(mSampleFormat);

   Read(scratch.ptr(), mSampleFormat, b, 0, length);
   memcpy(scratch.ptr() + start*sampleSize, buffer, len*sampleSize);

   BlockFile *const oldBlockFile = b.f;
   b.f = mDirManager->NewSimpleBlockFile(scratch.ptr(), length, mSampleFormat);

   mDirManager->Deref(oldBlockFile);

   return true;
}

bool Sequence::Get(samplePtr buffer, sampleFormat format,
   sampleCount start, sampleCount len) const
{
   if (start == mNumSamples) {
      return len == 0;
   }

   if (start < 0 || start > mNumSamples ||
      start + len > mNumSamples)
      return false;
   int b = FindBlock(start);

   return Get(b, buffer, format, start, len);
}

bool Sequence::Get(int b, samplePtr buffer, sampleFormat format,
   sampleCount start, sampleCount len) const
{
   while (len) {
      const SeqBlock &block = mBlock[b];
      const sampleCount bstart = (start - (block.start));
      const sampleCount blen =
          std::min(len, block.f->GetLength() - bstart);

      Read(buffer, format, block, bstart, blen);

      len -= blen;
      buffer += (blen * SAMPLE_SIZE(format));
      b++;
      start += blen;
   }

   return true;
}

// Pass NULL to set silence
bool Sequence::Set(samplePtr buffer, sampleFormat format,
                   sampleCount start, sampleCount len)
{
   if (start < 0 || start >= mNumSamples ||
       start+len > mNumSamples)
      return false;

   SampleBuffer scratch(mMaxSamples, mSampleFormat);

   SampleBuffer temp;
   if (buffer && format != mSampleFormat) {
      temp.Allocate(std::min(len, mMaxSamples), mSampleFormat);
   }

   int b = FindBlock(start);

   while (len) {
      SeqBlock &block = mBlock[b];
      const sampleCount bstart = start - block.start;
      const sampleCount fileLength = block.f->GetLength();
      const int blen =
         std::min(len, fileLength - bstart);

      if (buffer) {
         if (format == mSampleFormat)
            CopyWrite(scratch, buffer, block, bstart, blen);
         else {
            // To do: remove the extra movement.  Can we copy-samples within CopyWrite?
            CopySamples(buffer, format, temp.ptr(), mSampleFormat, blen);
            CopyWrite(scratch, temp.ptr(), block, bstart, blen);
         }
         buffer += (blen * SAMPLE_SIZE(format));
      }
      else {
         // If it's a full block of silence
         if (start == block.start &&
             blen == fileLength) {

            mDirManager->Deref(block.f);
            block.f = new SilentBlockFile(blen);
         }
         else {
            // Odd partial blocks of silence at start or end.
            temp.Allocate(blen, format);
            ClearSamples(temp.ptr(), format, 0, blen);
            // Otherwise write silence just to the portion of the block
            CopyWrite(scratch, temp.ptr(), block, bstart, blen);
         }
      }

      len -= blen;
      start += blen;
      b++;
   }

   return ConsistencyCheck(wxT("Set"));
}

namespace {

struct MinMaxSumsq
{
   MinMaxSumsq(const float *pv, int count, int divisor)
   {
      min = FLT_MAX, max = -FLT_MAX, sumsq = 0.0f;
      while (count--) {
         float v;
         switch (divisor) {
         default:
         case 1:
            // array holds samples
            v = *pv++;
            if (v < min)
               min = v;
            if (v > max)
               max = v;
            sumsq += v * v;
            break;
         case 256:
         case 65536:
            // array holds triples of min, max, and rms values
            v = *pv++;
            if (v < min)
               min = v;
            v = *pv++;
            if (v > max)
               max = v;
            v = *pv++;
            sumsq += v * v;
            break;
         }
      }
   }

   float min;
   float max;
   float sumsq;
};

}

bool Sequence::GetWaveDisplay(float *min, float *max, float *rms, int* bl,
                              int len, const sampleCount *where)
{
   const sampleCount s0 = std::max(sampleCount(0), where[0]);
   if (s0 >= mNumSamples)
      // None of the samples asked for are in range. Abandon.
      return false;

   // In case where[len - 1] == where[len], raise the limit by one,
   // so we load at least one pixel for column len - 1
   // ... unless the mNumSamples ceiling applies, and then there are other defenses
   const sampleCount s1 =
      std::min(mNumSamples, std::max(1 + where[len - 1], where[len]));
   float *temp = new float[mMaxSamples];

   int pixel = 0;

   sampleCount srcX = s0;
   sampleCount nextSrcX = 0;
   int lastRmsDenom = 0;
   int lastDivisor = 0;
   sampleCount whereNow = std::min(s1 - 1, where[0]);
   sampleCount whereNext = 0;
   // Loop over block files, opening and reading and closing each
   // not more than once
   unsigned nBlocks = mBlock.size();
   const unsigned int block0 = FindBlock(s0);
   for (unsigned int b = block0; b < nBlocks; ++b) {
      if (b > block0)
         srcX = nextSrcX;
      if (srcX >= s1)
         break;

      // Find the range of sample values for this block that
      // are in the display.
      SeqBlock &seqBlock = mBlock[b];
      const sampleCount start = seqBlock.start;
      nextSrcX = std::min(s1, start + seqBlock.f->GetLength());

      // The column for pixel p covers samples from
      // where[p] up to but excluding where[p + 1].

      // Find the range of pixels covered by the current block file
      // (Their starting samples covered by it, to be exact)
      int nextPixel;
      if (nextSrcX >= s1)
         // last pass
         nextPixel = len;
      else {
         nextPixel = pixel;
         // Taking min with s1 - 1, here and elsewhere, is another defense
         // to be sure the last pixel column gets at least one sample
         while (nextPixel < len &&
                (whereNext = std::min(s1 - 1, where[nextPixel])) < nextSrcX)
            ++nextPixel;
      }
      if (nextPixel == pixel)
         // The entire block's samples fall within one pixel column.
         // Either it's a rare odd block at the end, or else,
         // we must be really zoomed out!
         // Omit the entire block's contents from min/max/rms
         // calculation, which is not correct, but correctness might not
         // be worth the compute time if this happens every pixel
         // column. -- PRL
         continue;
      if (nextPixel == len)
         whereNext = s1;

      // Decide the summary level
      const double samplesPerPixel =
         double(whereNext - whereNow) / (nextPixel - pixel);
      const int divisor =
           (samplesPerPixel >= 65536) ? 65536
         : (samplesPerPixel >= 256) ? 256
         : 1;

      int blockStatus = b;

      // How many samples or triples are needed?
      const sampleCount startPosition =
         std::max(sampleCount(0), (srcX - start) / divisor);
      const sampleCount inclusiveEndPosition =
         std::min((mMaxSamples / divisor) - 1, (nextSrcX - 1 - start) / divisor);
      const sampleCount num = 1 + inclusiveEndPosition - startPosition;
      if (num <= 0) {
         // What?  There was a zero length block file?
         wxASSERT(false);
         // Do some defense against this case anyway
         while (pixel < nextPixel) {
            min[pixel] = max[pixel] = rms[pixel] = 0;
            bl[pixel] = blockStatus;//MC
            ++pixel;
         }
         continue;
      }

      // Read from the block file or its summary
      switch (divisor) {
      default:
      case 1:
         // Read samples
         Read((samplePtr)temp, floatSample, seqBlock, startPosition, num);
         break;
      case 256:
         // Read triples
         //check to see if summary data has been computed
         if (seqBlock.f->IsSummaryAvailable())
            seqBlock.f->Read256(temp, startPosition, num);
         else
            //otherwise, mark the display as not yet computed
            blockStatus = -1 - b;
         break;
      case 65536:
         // Read triples
         //check to see if summary data has been computed
         if (seqBlock.f->IsSummaryAvailable())
            seqBlock.f->Read64K(temp, startPosition, num);
         else
            //otherwise, mark the display as not yet computed
            blockStatus = -1 - b;
         break;
      }
      
      sampleCount filePosition = startPosition;

      // The previous pixel column might straddle blocks.
      // If so, impute some of the data to it.
      if (b > block0 && pixel > 0) {
         sampleCount midPosition = (whereNow - start) / divisor;
         int diff(midPosition - filePosition);
         if (diff > 0) {
            MinMaxSumsq values(temp, diff, divisor);
            const int lastPixel = pixel - 1;
            float &lastMin = min[lastPixel];
            lastMin = std::min(lastMin, values.min);
            float &lastMax = max[lastPixel];
            lastMax = std::max(lastMax, values.max);
            float &lastRms = rms[lastPixel];
            int lastNumSamples = lastRmsDenom * lastDivisor;
            lastRms = sqrt(
               (lastRms * lastRms * lastNumSamples + values.sumsq * divisor) /
               (lastNumSamples + diff * divisor)
            );

            filePosition = midPosition;
         }
      }

      // Loop over file positions
      int rmsDenom = 0;
      for (; filePosition <= inclusiveEndPosition;) {
         // Find range of pixel columns for this file position
         // (normally just one, but maybe more when zoomed very close)
         // and the range of positions for those columns
         // (normally one or more, for that one column)
         int pixelX = pixel + 1;
         sampleCount positionX = 0;
         while (pixelX < nextPixel &&
            filePosition ==
                (positionX = (std::min(s1 - 1, where[pixelX]) - start) / divisor)
         )
            ++pixelX;
         if (pixelX >= nextPixel)
            positionX = 1 + inclusiveEndPosition;

         // Find results to assign
         rmsDenom = (positionX - filePosition);
         wxASSERT(rmsDenom > 0);
         const float *const pv =
            temp + (filePosition - startPosition) * (divisor == 1 ? 1 : 3);
         MinMaxSumsq values(pv, rmsDenom, divisor);

         // Assign results
         std::fill(&min[pixel], &min[pixelX], values.min);
         std::fill(&max[pixel], &max[pixelX], values.max);
         std::fill(&bl[pixel], &bl[pixelX], blockStatus);
         std::fill(&rms[pixel], &rms[pixelX], (float)sqrt(values.sumsq / rmsDenom));

         pixel = pixelX;
         filePosition = positionX;
      }

      wxASSERT(pixel == nextPixel);
      whereNow = whereNext;
      pixel = nextPixel;
      lastDivisor = divisor;
      lastRmsDenom = rmsDenom;
   } // for each block file

   wxASSERT(pixel == len);

   delete[] temp;

   return true;
}

sampleCount Sequence::GetIdealAppendLen()
{
   int numBlocks = mBlock.size();
   const sampleCount max = GetMaxBlockSize();

   if (numBlocks == 0)
      return max;

   const sampleCount lastBlockLen = mBlock.back().f->GetLength();
   if (lastBlockLen == max)
      return max;
   else
      return max - lastBlockLen;
}

bool Sequence::Append(samplePtr buffer, sampleFormat format,
                      sampleCount len, XMLWriter* blockFileLog /*=NULL*/)
{
   // Quick check to make sure that it doesn't overflow
   if (Overflows(((double)mNumSamples) + ((double)len)))
      return false;

   // If the last block is not full, we need to add samples to it
   int numBlocks = mBlock.size();
   sampleCount length;
   SeqBlock *pLastBlock;
   SampleBuffer buffer2(mMaxSamples, mSampleFormat);
   if (numBlocks > 0 &&
       (length =
        (pLastBlock = &mBlock.back())->f->GetLength()) < mMinSamples) {
      SeqBlock &lastBlock = *pLastBlock;
      const sampleCount addLen = std::min(mMaxSamples - length, len);

      Read(buffer2.ptr(), mSampleFormat, lastBlock, 0, length);

      CopySamples(buffer,
                  format,
                  buffer2.ptr() + length * SAMPLE_SIZE(mSampleFormat),
                  mSampleFormat,
                  addLen);

      const int newLastBlockLen = length + addLen;

      SeqBlock newLastBlock(
         mDirManager->NewSimpleBlockFile(buffer2.ptr(), newLastBlockLen, mSampleFormat,
            blockFileLog != NULL),
         lastBlock.start
      );
      if (blockFileLog)
         static_cast<SimpleBlockFile*>(newLastBlock.f)->SaveXML(*blockFileLog);

      mDirManager->Deref(lastBlock.f);
      lastBlock = newLastBlock;

      len -= addLen;
      mNumSamples += addLen;
      buffer += addLen * SAMPLE_SIZE(format);
   }
   // Append the rest as NEW blocks
   while (len) {
      const sampleCount idealSamples = GetIdealBlockSize();
      const sampleCount l = std::min(idealSamples, len);
      BlockFile *pFile;
      if (format == mSampleFormat) {
         pFile = mDirManager->NewSimpleBlockFile(buffer, l, mSampleFormat,
                                                blockFileLog != NULL);
      }
      else {
         CopySamples(buffer, format, buffer2.ptr(), mSampleFormat, l);
         pFile = mDirManager->NewSimpleBlockFile(buffer2.ptr(), l, mSampleFormat,
                                                blockFileLog != NULL);
      }

      if (blockFileLog)
         static_cast<SimpleBlockFile*>(pFile)->SaveXML(*blockFileLog);

      mBlock.push_back(SeqBlock(pFile, mNumSamples));

      buffer += l * SAMPLE_SIZE(format);
      mNumSamples += l;
      len -= l;
   }

// JKC: During generate we use Append again and again.
// If generating a long sequence this test would give O(n^2)
// performance - not good!
#ifdef VERY_SLOW_CHECKING
   ConsistencyCheck(wxT("Append"));
#endif

   return true;
}

void Sequence::Blockify(BlockArray &list, sampleCount start, samplePtr buffer, sampleCount len)
{
   if (len <= 0)
      return;

   const int num = (len + (mMaxSamples - 1)) / mMaxSamples;
   list.reserve(list.size() + num);

   for (int i = 0; i < num; i++) {
      SeqBlock b;

      const sampleCount offset = i * len / num;
      b.start = start + offset;
      int newLen = ((i + 1) * len / num) - offset;
      samplePtr bufStart = buffer + (offset * SAMPLE_SIZE(mSampleFormat));

      b.f = mDirManager->NewSimpleBlockFile(bufStart, newLen, mSampleFormat);

      list.push_back(b);
   }
}

bool Sequence::Delete(sampleCount start, sampleCount len)
{
   if (len == 0)
      return true;
   if (len < 0 || start < 0 || start >= mNumSamples)
      return false;

   //TODO: add a ref-deref mechanism to SeqBlock/BlockArray so we don't have to make this a critical section.
   //On-demand threads iterate over the mBlocks and the GUI thread deletes them, so for now put a mutex here over
   //both functions,
   DeleteUpdateMutexLocker locker(*this);

   const unsigned int numBlocks = mBlock.size();

   const unsigned int b0 = FindBlock(start);
   unsigned int b1 = FindBlock(start + len - 1);

   int sampleSize = SAMPLE_SIZE(mSampleFormat);

   // Special case: if the samples to DELETE are all within a single
   // block and the resulting length is not too small, perform the
   // deletion within this block:
   SeqBlock *pBlock;
   sampleCount length;

   // One buffer for reuse in various branches here
   SampleBuffer scratch;
   // The maximum size that will ever be needed
   const sampleCount scratchSize = mMaxSamples + mMinSamples;

   if (b0 == b1 && (length = (pBlock = &mBlock[b0])->f->GetLength()) - len >= mMinSamples) {
      SeqBlock &b = *pBlock;
      sampleCount pos = start - b.start;
      sampleCount newLen = length - len;

      scratch.Allocate(scratchSize, mSampleFormat);

      Read(scratch.ptr(), mSampleFormat, b, 0, pos);
      Read(scratch.ptr() + (pos * sampleSize), mSampleFormat,
           b, pos + len, newLen - pos);

      BlockFile *const oldFile = b.f;
      b = SeqBlock(
         mDirManager->NewSimpleBlockFile(scratch.ptr(), newLen, mSampleFormat),
         b.start
      );
      mDirManager->Deref(oldFile);

      for (unsigned int j = b0 + 1; j < numBlocks; j++)
         mBlock[j].start -= len;

      mNumSamples -= len;

      return ConsistencyCheck(wxT("Delete - branch one"));
   }

   // Create a NEW array of blocks
   BlockArray newBlock;
   newBlock.reserve(numBlocks - (b1 - b0) + 2);

   // Copy the blocks before the deletion point over to
   // the NEW array
   newBlock.insert(newBlock.end(), mBlock.begin(), mBlock.begin() + b0);
   unsigned int i;

   // First grab the samples in block b0 before the deletion point
   // into preBuffer.  If this is enough samples for its own block,
   // or if this would be the first block in the array, write it out.
   // Otherwise combine it with the previous block (splitting them
   // 50/50 if necessary).
   const SeqBlock &preBlock = mBlock[b0];
   sampleCount preBufferLen = start - preBlock.start;
   if (preBufferLen) {
      if (preBufferLen >= mMinSamples || b0 == 0) {
         if (!scratch.ptr())
            scratch.Allocate(scratchSize, mSampleFormat);
         Read(scratch.ptr(), mSampleFormat, preBlock, 0, preBufferLen);
         BlockFile *const pFile =
            mDirManager->NewSimpleBlockFile(scratch.ptr(), preBufferLen, mSampleFormat);

         newBlock.push_back(SeqBlock(pFile, preBlock.start));
      } else {
         const SeqBlock &prepreBlock = mBlock[b0 - 1];
         const sampleCount prepreLen = prepreBlock.f->GetLength();
         const sampleCount sum = prepreLen + preBufferLen;

         if (!scratch.ptr())
            scratch.Allocate(scratchSize, mSampleFormat);

         Read(scratch.ptr(), mSampleFormat, prepreBlock, 0, prepreLen);
         Read(scratch.ptr() + prepreLen*sampleSize, mSampleFormat,
              preBlock, 0, preBufferLen);

         newBlock.erase(newBlock.end() - 1);
         Blockify(newBlock, prepreBlock.start, scratch.ptr(), sum);

         mDirManager->Deref(prepreBlock.f);
      }
   }
   else {
      // The sample where we begin deletion happens to fall
      // right on the beginning of a block.
   }

   if (b0 != b1) {
      mDirManager->Deref(preBlock.f);
   }

   // Next, DELETE blocks strictly between b0 and b1
   for (i = b0 + 1; i < b1; i++) {
      mDirManager->Deref(mBlock[i].f);
   }

   // Now, symmetrically, grab the samples in block b1 after the
   // deletion point into postBuffer.  If this is enough samples
   // for its own block, or if this would be the last block in
   // the array, write it out.  Otherwise combine it with the
   // subsequent block (splitting them 50/50 if necessary).
   const SeqBlock &postBlock = mBlock[b1];
   sampleCount postBufferLen =
       (postBlock.start + postBlock.f->GetLength()) - (start + len);
   if (postBufferLen) {
      if (postBufferLen >= mMinSamples || b1 == numBlocks - 1) {
         if (!scratch.ptr())
            // Last use of scratch, can ask for smaller
            scratch.Allocate(postBufferLen, mSampleFormat);
         sampleCount pos = (start + len) - postBlock.start;
         Read(scratch.ptr(), mSampleFormat, postBlock, pos, postBufferLen);
         BlockFile *const file =
            mDirManager->NewSimpleBlockFile(scratch.ptr(), postBufferLen, mSampleFormat);

         newBlock.push_back(SeqBlock(file, start));
      } else {
         SeqBlock &postpostBlock = mBlock[b1 + 1];
         sampleCount postpostLen = postpostBlock.f->GetLength();
         sampleCount sum = postpostLen + postBufferLen;

         if (!scratch.ptr())
            // Last use of scratch, can ask for smaller
            scratch.Allocate(sum, mSampleFormat);
         sampleCount pos = (start + len) - postBlock.start;
         Read(scratch.ptr(), mSampleFormat, postBlock, pos, postBufferLen);
         Read(scratch.ptr() + (postBufferLen * sampleSize), mSampleFormat,
              postpostBlock, 0, postpostLen);

         Blockify(newBlock, start, scratch.ptr(), sum);
         b1++;

         mDirManager->Deref(postpostBlock.f);
      }
   }
   else {
      // The sample where we begin deletion happens to fall
      // right on the end of a block.
   }
   mDirManager->Deref(postBlock.f);

   // Copy the remaining blocks over from the old array
   for (i = b1 + 1; i < numBlocks; i++)
      newBlock.push_back(mBlock[i].Plus(-len));

   // Substitute our NEW array for the old one
   mBlock.swap(newBlock);

   // Update total number of samples and do a consistency check.
   mNumSamples -= len;

   return ConsistencyCheck(wxT("Delete - branch two"));
}

bool Sequence::ConsistencyCheck(const wxChar *whereStr) const
{
   unsigned int i;
   sampleCount pos = 0;
   unsigned int numBlocks = mBlock.size();
   bool bError = false;

   for (i = 0; !bError && i < numBlocks; i++) {
      const SeqBlock &seqBlock = mBlock[i];
      if (pos != seqBlock.start)
         bError = true;

      if (seqBlock.f)
         pos += seqBlock.f->GetLength();
      else
         bError = true;
   }
   if (pos != mNumSamples)
      bError = true;

   if (bError)
   {
      wxLogError(wxT("*** Consistency check failed after %s. ***"), whereStr);
      wxString str;
      DebugPrintf(&str);
      wxLogError(wxT("%s"), str.c_str());
      wxLogError(wxT("*** Please report this error to feedback@audacityteam.org. ***\n\n")
                 wxT("Recommended course of action:\n")
                 wxT("Undo the failed operation(s), then export or save your work and quit."));
   }

   return !bError;
}

void Sequence::DebugPrintf(wxString *dest) const
{
   unsigned int i;
   int pos = 0;

   for (i = 0; i < mBlock.size(); i++) {
      const SeqBlock &seqBlock = mBlock[i];
      *dest += wxString::Format
         (wxT("   Block %3u: start %8lld, len %8lld, refs %d, "),
          i,
          (long long) seqBlock.start,
          seqBlock.f ? (long long) seqBlock.f->GetLength() : 0,
          seqBlock.f ? mDirManager->GetRefCount(seqBlock.f) : 0);

      if (seqBlock.f)
         *dest += seqBlock.f->GetFileName().name.GetFullName();
      else
         *dest += wxT("<missing block file>");

      if ((pos != seqBlock.start) || !seqBlock.f)
         *dest += wxT("      ERROR\n");
      else
         *dest += wxT("\n");

      if (seqBlock.f)
         pos += seqBlock.f->GetLength();
   }
   if (pos != mNumSamples)
      *dest += wxString::Format
         (wxT("ERROR mNumSamples = %lld\n"), (long long) mNumSamples);
}

// static
void Sequence::SetMaxDiskBlockSize(int bytes)
{
   sMaxDiskBlockSize = bytes;
}

int Sequence::GetMaxDiskBlockSize()
{
   return sMaxDiskBlockSize;
}

void Sequence::AppendBlockFile(BlockFile* blockFile)
{
   // We assume blockFile has the correct ref count already

   mBlock.push_back(SeqBlock(blockFile, mNumSamples));
   mNumSamples += blockFile->GetLength();

   // PRL:  I hoisted the intended consistency check out of the inner loop
   // See RecordingRecoveryHandler::HandleXMLEndTag

#ifdef VERY_SLOW_CHECKING
   ConsistencyCheck(wxT("AppendBlockFile"));
#endif
}