mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-03 17:19:43 +02:00
1873 lines
59 KiB
C++
1873 lines
59 KiB
C++
/**********************************************************************
|
|
|
|
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
|
|
}
|