mirror of
https://github.com/cookiengineer/audacity
synced 2025-04-30 23:59:41 +02:00
List of commands that were executed in the `src directory`: * sed -i 's/Audacity: A Digital Audio Editor/Tenacity/g' *.h * sed -i 's/Audacity: A Digital Audio Editor/Tenacity/g' *.cpp Signed-off-by: Panagiotis Vasilopoulos <hello@alwayslivid.com>
1957 lines
62 KiB
C++
1957 lines
62 KiB
C++
/**********************************************************************
|
|
|
|
Tenacity
|
|
|
|
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 sample blocks in the database.
|
|
Contrast with RingBuffer.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class SeqBlock
|
|
\brief Data structure containing pointer to a sample block and
|
|
a start time. Element of a BlockArray.
|
|
|
|
*//*******************************************************************/
|
|
|
|
|
|
|
|
#include "Sequence.h"
|
|
|
|
#include <algorithm>
|
|
#include <float.h>
|
|
#include <math.h>
|
|
|
|
#include <wx/intl.h>
|
|
#include <wx/filefn.h>
|
|
#include <wx/ffile.h>
|
|
#include <wx/log.h>
|
|
|
|
#include "SampleBlock.h"
|
|
#include "InconsistencyException.h"
|
|
#include "widgets/AudacityMessageBox.h"
|
|
|
|
size_t Sequence::sMaxDiskBlockSize = 1048576;
|
|
|
|
// Sequence methods
|
|
Sequence::Sequence(
|
|
const SampleBlockFactoryPtr &pFactory, sampleFormat format)
|
|
: mpFactory(pFactory),
|
|
mSampleFormat(format),
|
|
mMinSamples(sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2),
|
|
mMaxSamples(mMinSamples * 2)
|
|
{
|
|
}
|
|
|
|
// essentially a copy constructor - but you must pass in the
|
|
// current project, because we might be copying from one
|
|
// project to another
|
|
Sequence::Sequence(
|
|
const Sequence &orig, const SampleBlockFactoryPtr &pFactory)
|
|
: mpFactory(pFactory),
|
|
mSampleFormat(orig.mSampleFormat),
|
|
mMinSamples(orig.mMinSamples),
|
|
mMaxSamples(orig.mMaxSamples)
|
|
{
|
|
Paste(0, &orig);
|
|
}
|
|
|
|
Sequence::~Sequence()
|
|
{
|
|
}
|
|
|
|
size_t Sequence::GetMaxBlockSize() const
|
|
{
|
|
return mMaxSamples;
|
|
}
|
|
|
|
size_t Sequence::GetIdealBlockSize() const
|
|
{
|
|
return mMaxSamples;
|
|
}
|
|
|
|
bool Sequence::CloseLock()
|
|
{
|
|
for (unsigned int i = 0; i < mBlock.size(); i++)
|
|
mBlock[i].sb->CloseLock();
|
|
|
|
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;
|
|
}
|
|
*/
|
|
|
|
namespace {
|
|
void ensureSampleBufferSize(SampleBuffer &buffer, sampleFormat format,
|
|
size_t &size, size_t required,
|
|
SampleBuffer *pSecondBuffer = nullptr)
|
|
{
|
|
// This should normally do nothing, but it is a defense against corrupt
|
|
// projects than might have inconsistent block files bigger than the
|
|
// expected maximum size.
|
|
if (size < required) {
|
|
// reallocate
|
|
buffer.Allocate(required, format);
|
|
if (pSecondBuffer && pSecondBuffer->ptr())
|
|
pSecondBuffer->Allocate(required, format);
|
|
if (!buffer.ptr() || (pSecondBuffer && !pSecondBuffer->ptr())) {
|
|
// malloc failed
|
|
// Perhaps required is a really crazy value,
|
|
// and perhaps we should throw an AudacityException, but that is
|
|
// a second-order concern
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
}
|
|
size = required;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*! @excsafety{Strong} */
|
|
bool Sequence::ConvertToSampleFormat(sampleFormat format,
|
|
const std::function<void(size_t)> & progressReport)
|
|
{
|
|
if (format == mSampleFormat)
|
|
// no change
|
|
return false;
|
|
|
|
if (mBlock.size() == 0)
|
|
{
|
|
mSampleFormat = format;
|
|
return true;
|
|
}
|
|
|
|
const sampleFormat oldFormat = mSampleFormat;
|
|
mSampleFormat = format;
|
|
|
|
const auto oldMinSamples = mMinSamples, oldMaxSamples = mMaxSamples;
|
|
// These are the same calculations as in the constructor.
|
|
mMinSamples = sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2;
|
|
mMaxSamples = mMinSamples * 2;
|
|
|
|
bool bSuccess = false;
|
|
auto cleanup = finally( [&] {
|
|
if (!bSuccess) {
|
|
// Conversion failed. Revert these member vars.
|
|
mSampleFormat = oldFormat;
|
|
mMaxSamples = oldMaxSamples;
|
|
mMinSamples = oldMinSamples;
|
|
}
|
|
} );
|
|
|
|
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));
|
|
|
|
{
|
|
size_t oldSize = oldMaxSamples;
|
|
SampleBuffer bufferOld(oldSize, oldFormat);
|
|
size_t newSize = oldMaxSamples;
|
|
SampleBuffer bufferNew(newSize, format);
|
|
|
|
for (size_t i = 0, nn = mBlock.size(); i < nn; i++)
|
|
{
|
|
SeqBlock &oldSeqBlock = mBlock[i];
|
|
const auto &oldBlockFile = oldSeqBlock.sb;
|
|
const auto len = oldBlockFile->GetSampleCount();
|
|
ensureSampleBufferSize(bufferOld, oldFormat, oldSize, len);
|
|
Read(bufferOld.ptr(), oldFormat, oldSeqBlock, 0, len, true);
|
|
|
|
ensureSampleBufferSize(bufferNew, format, newSize, len);
|
|
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 auto blockstart = oldSeqBlock.start;
|
|
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
|
newBlockArray, blockstart, bufferNew.ptr(), len);
|
|
|
|
if (progressReport)
|
|
progressReport(len);
|
|
}
|
|
}
|
|
|
|
// Invalidate all the old, non-aliased block files.
|
|
// Aliased files will be converted at save, per comment above.
|
|
|
|
// Commit the changes to block file array
|
|
CommitChangesIfConsistent
|
|
(newBlockArray, mNumSamples, wxT("Sequence::ConvertToSampleFormat()"));
|
|
|
|
// Commit the other changes
|
|
bSuccess = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
std::pair<float, float> Sequence::GetMinMax(
|
|
sampleCount start, sampleCount len, bool mayThrow) const
|
|
{
|
|
if (len == 0 || mBlock.size() == 0) {
|
|
return {
|
|
0.f,
|
|
// FLT_MAX? So it doesn't look like a spurious '0' to a caller?
|
|
|
|
0.f
|
|
// -FLT_MAX? So it doesn't look like a spurious '0' to a caller?
|
|
};
|
|
}
|
|
|
|
float min = FLT_MAX;
|
|
float max = -FLT_MAX;
|
|
|
|
unsigned int block0 = FindBlock(start);
|
|
unsigned int block1 = FindBlock(start + len - 1);
|
|
|
|
// 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) {
|
|
auto results = mBlock[b].sb->GetMinMaxRMS(mayThrow);
|
|
|
|
if (results.min < min)
|
|
min = results.min;
|
|
if (results.max > max)
|
|
max = results.max;
|
|
}
|
|
|
|
// 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.
|
|
{
|
|
const SeqBlock &theBlock = mBlock[block0];
|
|
const auto &theFile = theBlock.sb;
|
|
auto results = theFile->GetMinMaxRMS(mayThrow);
|
|
|
|
if (results.min < min || results.max > max) {
|
|
// start lies within theBlock:
|
|
auto s0 = ( start - theBlock.start ).as_size_t();
|
|
const auto maxl0 = (
|
|
// start lies within theBlock:
|
|
theBlock.start + theFile->GetSampleCount() - start
|
|
).as_size_t();
|
|
wxASSERT(maxl0 <= mMaxSamples); // Vaughan, 2011-10-19
|
|
const auto l0 = limitSampleBufferSize ( maxl0, len );
|
|
|
|
results = theFile->GetMinMaxRMS(s0, l0, mayThrow);
|
|
if (results.min < min)
|
|
min = results.min;
|
|
if (results.max > max)
|
|
max = results.max;
|
|
}
|
|
}
|
|
|
|
if (block1 > block0)
|
|
{
|
|
const SeqBlock &theBlock = mBlock[block1];
|
|
const auto &theFile = theBlock.sb;
|
|
auto results = theFile->GetMinMaxRMS(mayThrow);
|
|
|
|
if (results.min < min || results.max > max) {
|
|
|
|
// start + len - 1 lies in theBlock:
|
|
const auto l0 = ( start + len - theBlock.start ).as_size_t();
|
|
wxASSERT(l0 <= mMaxSamples); // Vaughan, 2011-10-19
|
|
|
|
results = theFile->GetMinMaxRMS(0, l0, mayThrow);
|
|
if (results.min < min)
|
|
min = results.min;
|
|
if (results.max > max)
|
|
max = results.max;
|
|
}
|
|
}
|
|
|
|
return { min, max };
|
|
}
|
|
|
|
float Sequence::GetRMS(sampleCount start, sampleCount len, bool mayThrow) 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)
|
|
return 0.f;
|
|
|
|
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);
|
|
|
|
// 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++) {
|
|
const SeqBlock &theBlock = mBlock[b];
|
|
const auto &sb = theBlock.sb;
|
|
auto results = sb->GetMinMaxRMS(mayThrow);
|
|
|
|
const auto fileLen = sb->GetSampleCount();
|
|
const auto blockRMS = results.RMS;
|
|
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];
|
|
const auto &sb = theBlock.sb;
|
|
// start lies within theBlock
|
|
auto s0 = ( start - theBlock.start ).as_size_t();
|
|
// start lies within theBlock
|
|
const auto maxl0 =
|
|
(theBlock.start + sb->GetSampleCount() - start).as_size_t();
|
|
wxASSERT(maxl0 <= mMaxSamples); // Vaughan, 2011-10-19
|
|
const auto l0 = limitSampleBufferSize( maxl0, len );
|
|
|
|
auto results = sb->GetMinMaxRMS(s0, l0, mayThrow);
|
|
const auto partialRMS = results.RMS;
|
|
sumsq += partialRMS * partialRMS * l0;
|
|
length += l0;
|
|
}
|
|
|
|
if (block1 > block0) {
|
|
const SeqBlock &theBlock = mBlock[block1];
|
|
const auto &sb = theBlock.sb;
|
|
|
|
// start + len - 1 lies within theBlock
|
|
const auto l0 = ( start + len - theBlock.start ).as_size_t();
|
|
wxASSERT(l0 <= mMaxSamples); // PRL: I think Vaughan missed this
|
|
|
|
auto results = sb->GetMinMaxRMS(0, l0, mayThrow);
|
|
const auto partialRMS = results.RMS;
|
|
sumsq += partialRMS * partialRMS * l0;
|
|
length += l0;
|
|
}
|
|
|
|
// PRL: catch bugs like 1320:
|
|
wxASSERT(length == len);
|
|
|
|
return sqrt(sumsq / length.as_double() );
|
|
}
|
|
|
|
// Must pass in the correct factory for the result. If it's not the same
|
|
// as in this, then block contents must be copied.
|
|
std::unique_ptr<Sequence> Sequence::Copy( const SampleBlockFactoryPtr &pFactory,
|
|
sampleCount s0, sampleCount s1) const
|
|
{
|
|
// Make a new Sequence object for the specified factory:
|
|
auto dest = std::make_unique<Sequence>(pFactory, mSampleFormat);
|
|
if (s0 >= s1 || s0 >= mNumSamples || s1 < 0) {
|
|
return dest;
|
|
}
|
|
|
|
// Decide whether to share sample blocks or make new copies, when whole block
|
|
// contents are used -- must copy if factories are different:
|
|
auto pUseFactory = (pFactory == mpFactory) ? nullptr : pFactory.get();
|
|
|
|
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->mBlock.reserve(b1 - b0 + 1);
|
|
|
|
auto bufferSize = mMaxSamples;
|
|
SampleBuffer buffer(bufferSize, mSampleFormat);
|
|
|
|
int blocklen;
|
|
|
|
// Do any initial partial block
|
|
|
|
const SeqBlock &block0 = mBlock[b0];
|
|
if (s0 != block0.start) {
|
|
const auto &sb = block0.sb;
|
|
// Nonnegative result is length of block0 or less:
|
|
blocklen =
|
|
( std::min(s1, block0.start + sb->GetSampleCount()) - s0 ).as_size_t();
|
|
wxASSERT(blocklen <= (int)mMaxSamples); // Vaughan, 2012-02-29
|
|
ensureSampleBufferSize(buffer, mSampleFormat, bufferSize, blocklen);
|
|
Get(b0, buffer.ptr(), mSampleFormat, s0, blocklen, true);
|
|
|
|
dest->Append(buffer.ptr(), mSampleFormat, blocklen);
|
|
}
|
|
else
|
|
--b0;
|
|
|
|
// If there are blocks in the middle, use the blocks whole
|
|
for (int bb = b0 + 1; bb < b1; ++bb)
|
|
AppendBlock(pUseFactory, mSampleFormat,
|
|
dest->mBlock, dest->mNumSamples, mBlock[bb]);
|
|
// Increase ref count or duplicate file
|
|
|
|
// Do the last block
|
|
if (b1 > b0) {
|
|
// Probable case of a partial block
|
|
const SeqBlock &block = mBlock[b1];
|
|
const auto &sb = block.sb;
|
|
// s1 is within block:
|
|
blocklen = (s1 - block.start).as_size_t();
|
|
wxASSERT(blocklen <= (int)mMaxSamples); // Vaughan, 2012-02-29
|
|
if (blocklen < (int)sb->GetSampleCount()) {
|
|
ensureSampleBufferSize(buffer, mSampleFormat, bufferSize, blocklen);
|
|
Get(b1, buffer.ptr(), mSampleFormat, block.start, blocklen, true);
|
|
dest->Append(buffer.ptr(), mSampleFormat, blocklen);
|
|
}
|
|
else
|
|
// Special case of a whole block
|
|
AppendBlock(pUseFactory, mSampleFormat,
|
|
dest->mBlock, dest->mNumSamples, block);
|
|
// Increase ref count or duplicate file
|
|
}
|
|
|
|
dest->ConsistencyCheck(wxT("Sequence::Copy()"));
|
|
|
|
return dest;
|
|
}
|
|
|
|
namespace {
|
|
inline bool Overflows(double numSamples)
|
|
{
|
|
return numSamples > wxLL(9223372036854775807);
|
|
}
|
|
|
|
SampleBlockPtr ShareOrCopySampleBlock(
|
|
SampleBlockFactory *pFactory, sampleFormat format, SampleBlockPtr sb )
|
|
{
|
|
if ( pFactory ) {
|
|
// must copy contents to a fresh SampleBlock object in another database
|
|
auto sampleCount = sb->GetSampleCount();
|
|
SampleBuffer buffer{ sampleCount, format };
|
|
sb->GetSamples( buffer.ptr(), format, 0, sampleCount );
|
|
sb = pFactory->Create( buffer.ptr(), sampleCount, format );
|
|
}
|
|
else
|
|
// Can just share
|
|
;
|
|
return sb;
|
|
}
|
|
}
|
|
|
|
/*! @excsafety{Strong} */
|
|
void 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(s.as_double(), 0),
|
|
Internat::ToString(mNumSamples.as_double(), 0));
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
}
|
|
|
|
// Quick check to make sure that it doesn't overflow
|
|
if (Overflows((mNumSamples.as_double()) + (src->mNumSamples.as_double())))
|
|
{
|
|
wxLogError(
|
|
wxT("Sequence::Paste: mNumSamples %s + src->mNumSamples %s would overflow."),
|
|
// PRL: Why bother with Internat when the above is just wxT?
|
|
Internat::ToString(mNumSamples.as_double(), 0),
|
|
Internat::ToString(src->mNumSamples.as_double(), 0));
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
}
|
|
|
|
if (src->mSampleFormat != mSampleFormat)
|
|
{
|
|
wxLogError(
|
|
wxT("Sequence::Paste: Sample format to be pasted, %s, does not match destination format, %s."),
|
|
GetSampleFormatStr(src->mSampleFormat).Debug(),
|
|
GetSampleFormatStr(mSampleFormat).Debug());
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
}
|
|
|
|
const BlockArray &srcBlock = src->mBlock;
|
|
auto addedLen = src->mNumSamples;
|
|
const unsigned int srcNumBlocks = srcBlock.size();
|
|
auto sampleSize = SAMPLE_SIZE(mSampleFormat);
|
|
|
|
if (addedLen == 0 || srcNumBlocks == 0)
|
|
return;
|
|
|
|
const size_t numBlocks = mBlock.size();
|
|
|
|
// Decide whether to share sample blocks or make new copies, when whole block
|
|
// contents are used -- must copy if factories are different:
|
|
auto pUseFactory =
|
|
(src->mpFactory == mpFactory) ? nullptr : mpFactory.get();
|
|
|
|
if (numBlocks == 0 ||
|
|
(s == mNumSamples && mBlock.back().sb->GetSampleCount() >= 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
|
|
|
|
// Build and swap a copy so there is a strong exception safety guarantee
|
|
BlockArray newBlock{ mBlock };
|
|
sampleCount samples = mNumSamples;
|
|
for (unsigned int i = 0; i < srcNumBlocks; i++)
|
|
// AppendBlock may throw for limited disk space, if pasting from
|
|
// one project into another.
|
|
AppendBlock(pUseFactory, mSampleFormat,
|
|
newBlock, samples, srcBlock[i]);
|
|
|
|
CommitChangesIfConsistent
|
|
(newBlock, samples, wxT("Paste branch one"));
|
|
return;
|
|
}
|
|
|
|
const int b = (s == mNumSamples) ? mBlock.size() - 1 : FindBlock(s);
|
|
wxASSERT((b >= 0) && (b < (int)numBlocks));
|
|
SeqBlock *const pBlock = &mBlock[b];
|
|
const auto length = pBlock->sb->GetSampleCount();
|
|
const auto 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;
|
|
// largerBlockLen is not more than mMaxSamples...
|
|
SampleBuffer buffer(largerBlockLen.as_size_t(), mSampleFormat);
|
|
|
|
// ...and addedLen is not more than largerBlockLen
|
|
auto sAddedLen = addedLen.as_size_t();
|
|
// s lies within block:
|
|
auto splitPoint = ( s - block.start ).as_size_t();
|
|
Read(buffer.ptr(), mSampleFormat, block, 0, splitPoint, true);
|
|
src->Get(0, buffer.ptr() + splitPoint*sampleSize,
|
|
mSampleFormat, 0, sAddedLen, true);
|
|
Read(buffer.ptr() + (splitPoint + sAddedLen) * sampleSize,
|
|
mSampleFormat, block,
|
|
splitPoint, length - splitPoint, true);
|
|
|
|
// largerBlockLen is not more than mMaxSamples...
|
|
block.sb = mpFactory->Create(
|
|
buffer.ptr(),
|
|
largerBlockLen.as_size_t(),
|
|
mSampleFormat);
|
|
|
|
// Don't make a duplicate array. We can still give Strong-guarantee
|
|
// if we modify only one block in place.
|
|
|
|
// use No-fail-guarantee in remaining steps
|
|
for (unsigned int i = b + 1; i < numBlocks; i++)
|
|
mBlock[i].start += addedLen;
|
|
|
|
mNumSamples += addedLen;
|
|
|
|
// This consistency check won't throw, it asserts.
|
|
// Proof that we kept consistency is not hard.
|
|
ConsistencyCheck(wxT("Paste branch two"), false);
|
|
return;
|
|
}
|
|
|
|
// 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];
|
|
auto splitLen = splitBlock.sb->GetSampleCount();
|
|
// s lies within splitBlock
|
|
auto splitPoint = ( s - splitBlock.start ).as_size_t();
|
|
|
|
unsigned int i;
|
|
if (srcNumBlocks <= 4) {
|
|
|
|
// addedLen is at most four times maximum block size
|
|
auto sAddedLen = addedLen.as_size_t();
|
|
const auto sum = splitLen + sAddedLen;
|
|
|
|
SampleBuffer sumBuffer(sum, mSampleFormat);
|
|
Read(sumBuffer.ptr(), mSampleFormat, splitBlock, 0, splitPoint, true);
|
|
src->Get(0, sumBuffer.ptr() + splitPoint * sampleSize,
|
|
mSampleFormat,
|
|
0, sAddedLen, true);
|
|
Read(sumBuffer.ptr() + (splitPoint + sAddedLen) * sampleSize, mSampleFormat,
|
|
splitBlock, splitPoint,
|
|
splitLen - splitPoint, true);
|
|
|
|
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
|
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
|
|
// used whole, and the last two get merged with the last
|
|
// half of the split block.
|
|
|
|
const auto srcFirstTwoLen =
|
|
srcBlock[0].sb->GetSampleCount() + srcBlock[1].sb->GetSampleCount();
|
|
const auto leftLen = splitPoint + srcFirstTwoLen;
|
|
|
|
const SeqBlock &penultimate = srcBlock[srcNumBlocks - 2];
|
|
const auto srcLastTwoLen =
|
|
penultimate.sb->GetSampleCount() +
|
|
srcBlock[srcNumBlocks - 1].sb->GetSampleCount();
|
|
const auto rightSplit = splitBlock.sb->GetSampleCount() - splitPoint;
|
|
const auto rightLen = rightSplit + srcLastTwoLen;
|
|
|
|
SampleBuffer sampleBuffer(std::max(leftLen, rightLen), mSampleFormat);
|
|
|
|
Read(sampleBuffer.ptr(), mSampleFormat, splitBlock, 0, splitPoint, true);
|
|
src->Get(0, sampleBuffer.ptr() + splitPoint*sampleSize,
|
|
mSampleFormat, 0, srcFirstTwoLen, true);
|
|
|
|
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
|
newBlock, splitBlock.start, sampleBuffer.ptr(), leftLen);
|
|
|
|
for (i = 2; i < srcNumBlocks - 2; i++) {
|
|
const SeqBlock &block = srcBlock[i];
|
|
auto sb = ShareOrCopySampleBlock(
|
|
pUseFactory, mSampleFormat, block.sb );
|
|
newBlock.push_back(SeqBlock(sb, block.start + s));
|
|
}
|
|
|
|
auto lastStart = penultimate.start;
|
|
src->Get(srcNumBlocks - 2, sampleBuffer.ptr(), mSampleFormat,
|
|
lastStart, srcLastTwoLen, true);
|
|
Read(sampleBuffer.ptr() + srcLastTwoLen * sampleSize, mSampleFormat,
|
|
splitBlock, splitPoint, rightSplit, true);
|
|
|
|
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
|
newBlock, s + lastStart, sampleBuffer.ptr(), rightLen);
|
|
}
|
|
|
|
// 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));
|
|
|
|
CommitChangesIfConsistent
|
|
(newBlock, mNumSamples + addedLen, wxT("Paste branch three"));
|
|
}
|
|
|
|
/*! @excsafety{Strong} */
|
|
void Sequence::SetSilence(sampleCount s0, sampleCount len)
|
|
{
|
|
SetSamples(NULL, mSampleFormat, s0, len);
|
|
}
|
|
|
|
/*! @excsafety{Strong} */
|
|
void Sequence::InsertSilence(sampleCount s0, sampleCount len)
|
|
{
|
|
auto &factory = *mpFactory;
|
|
|
|
// Quick check to make sure that it doesn't overflow
|
|
if (Overflows((mNumSamples.as_double()) + (len.as_double())))
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
|
|
if (len <= 0)
|
|
return;
|
|
|
|
// Create a NEW track containing as much silence as we
|
|
// need to insert, and then call Paste to do the insertion.
|
|
|
|
Sequence sTrack(mpFactory, mSampleFormat);
|
|
|
|
auto idealSamples = GetIdealBlockSize();
|
|
|
|
sampleCount pos = 0;
|
|
|
|
// Could nBlocks overflow a size_t? Not very likely. You need perhaps
|
|
// 2 ^ 52 samples which is over 3000 years at 44.1 kHz.
|
|
auto nBlocks = (len + idealSamples - 1) / idealSamples;
|
|
sTrack.mBlock.reserve(nBlocks.as_size_t());
|
|
|
|
if (len >= idealSamples) {
|
|
auto silentFile = factory.CreateSilent(
|
|
idealSamples,
|
|
mSampleFormat);
|
|
while (len >= idealSamples) {
|
|
sTrack.mBlock.push_back(SeqBlock(silentFile, pos));
|
|
|
|
pos += idealSamples;
|
|
len -= idealSamples;
|
|
}
|
|
}
|
|
if (len != 0) {
|
|
// len is not more than idealSamples:
|
|
sTrack.mBlock.push_back(SeqBlock(
|
|
factory.CreateSilent(len.as_size_t(), mSampleFormat), pos));
|
|
pos += len;
|
|
}
|
|
|
|
sTrack.mNumSamples = pos;
|
|
|
|
// use Strong-guarantee
|
|
Paste(s0, &sTrack);
|
|
}
|
|
|
|
void Sequence::AppendBlock( SampleBlockFactory *pFactory, sampleFormat format,
|
|
BlockArray &mBlock, sampleCount &mNumSamples, const SeqBlock &b)
|
|
{
|
|
// Quick check to make sure that it doesn't overflow
|
|
if (Overflows((mNumSamples.as_double()) + ((double)b.sb->GetSampleCount())))
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
|
|
auto sb = ShareOrCopySampleBlock( pFactory, format, b.sb );
|
|
SeqBlock newBlock(sb, mNumSamples);
|
|
|
|
// We can assume newBlock.sb is not null
|
|
|
|
mBlock.push_back(newBlock);
|
|
mNumSamples += newBlock.sb->GetSampleCount();
|
|
|
|
// Don't do a consistency check here because this
|
|
// function gets called in an inner loop.
|
|
}
|
|
|
|
sampleCount Sequence::GetBlockStart(sampleCount position) const
|
|
{
|
|
int b = FindBlock(position);
|
|
return mBlock[b].start;
|
|
}
|
|
|
|
size_t 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];
|
|
// start is in block:
|
|
auto result = (block.start + block.sb->GetSampleCount() - start).as_size_t();
|
|
|
|
decltype(result) length;
|
|
while(result < mMinSamples && b+1<numBlocks &&
|
|
((length = mBlock[b+1].sb->GetSampleCount()) + result) <= mMaxSamples) {
|
|
b++;
|
|
result += length;
|
|
}
|
|
|
|
wxASSERT(result > 0 && result <= mMaxSamples);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool Sequence::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
|
{
|
|
auto &factory = *mpFactory;
|
|
|
|
/* handle waveblock tag and its attributes */
|
|
if (!wxStrcmp(tag, wxT("waveblock")))
|
|
{
|
|
SeqBlock wb;
|
|
|
|
// Give SampleBlock a go at the attributes first
|
|
wb.sb = factory.CreateFromXML(mSampleFormat, attrs);
|
|
if (wb.sb == nullptr)
|
|
{
|
|
mErrorOpening = true;
|
|
return false;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
long long nValue = 0;
|
|
|
|
const wxString strValue = value; // promote string, we need this for all
|
|
|
|
if (wxStrcmp(attr, wxT("start")) == 0)
|
|
{
|
|
// This attribute is a sample offset, so can be 64bit
|
|
if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0))
|
|
{
|
|
mErrorOpening = true;
|
|
return false;
|
|
}
|
|
|
|
wb.start = nValue;
|
|
}
|
|
}
|
|
|
|
mBlock.push_back(wb);
|
|
|
|
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;
|
|
}
|
|
|
|
long long nValue = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
// nValue is now safe for size_t
|
|
mMaxSamples = nValue;
|
|
}
|
|
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
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Sequence::HandleXMLEndTag(const wxChar *tag)
|
|
{
|
|
if (wxStrcmp(tag, wxT("sequence")) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure that the sequence is valid.
|
|
|
|
// 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)
|
|
{
|
|
wxLogWarning(
|
|
wxT("Gap detected in project file.\n")
|
|
wxT(" Start (%s) for block file %lld 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(block.start.as_double(), 0),
|
|
block.sb->GetBlockID(),
|
|
Internat::ToString(numSamples.as_double(), 0));
|
|
block.start = numSamples;
|
|
mErrorOpening = true;
|
|
}
|
|
numSamples += block.sb->GetSampleCount();
|
|
}
|
|
|
|
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(mNumSamples.as_double(), 0),
|
|
Internat::ToString(numSamples.as_double(), 0));
|
|
mNumSamples = numSamples;
|
|
mErrorOpening = true;
|
|
}
|
|
}
|
|
|
|
XMLTagHandler *Sequence::HandleXMLChild(const wxChar *tag)
|
|
{
|
|
if (!wxStrcmp(tag, wxT("waveblock")))
|
|
{
|
|
return this;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Throws exceptions rather than reporting errors.
|
|
void Sequence::WriteXML(XMLWriter &xmlFile) const
|
|
// may throw
|
|
{
|
|
unsigned int b;
|
|
|
|
xmlFile.StartTag(wxT("sequence"));
|
|
|
|
xmlFile.WriteAttr(wxT("maxsamples"), mMaxSamples);
|
|
xmlFile.WriteAttr(wxT("sampleformat"), (size_t)mSampleFormat);
|
|
xmlFile.WriteAttr(wxT("numsamples"), mNumSamples.as_long_long() );
|
|
|
|
for (b = 0; b < mBlock.size(); b++) {
|
|
const SeqBlock &bb = mBlock[b];
|
|
|
|
// See http://bugzilla.audacityteam.org/show_bug.cgi?id=451.
|
|
if (bb.sb->GetSampleCount() > mMaxSamples)
|
|
{
|
|
// PRL: Bill observed this error. Not sure how it was caused.
|
|
// I have added code in ConsistencyCheck that should abort the
|
|
// editing operation that caused this, not fixing
|
|
// the problem but moving the point of detection earlier if we
|
|
// find a reproducible case.
|
|
auto sMsg =
|
|
XO("Sequence has block file exceeding maximum %s samples per block.\nTruncating to this maximum length.")
|
|
.Format( Internat::ToString(((wxLongLong)mMaxSamples).ToDouble(), 0) );
|
|
AudacityMessageBox(
|
|
sMsg,
|
|
XO("Warning - Truncating Overlong Block File"),
|
|
wxICON_EXCLAMATION | wxOK);
|
|
wxLogWarning(sMsg.Translation()); //Debug?
|
|
// bb.sb->SetLength(mMaxSamples);
|
|
}
|
|
|
|
xmlFile.StartTag(wxT("waveblock"));
|
|
xmlFile.WriteAttr(wxT("start"), bb.start.as_long_long() );
|
|
|
|
bb.sb->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();
|
|
|
|
size_t lo = 0, hi = numBlocks, guess;
|
|
sampleCount loSamples = 0, hiSamples = mNumSamples;
|
|
|
|
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 = (pos - loSamples).as_double() /
|
|
(hiSamples - loSamples).as_double();
|
|
guess = std::min(hi - 1, lo + size_t(frac * (hi - lo)));
|
|
const SeqBlock &block = mBlock[guess];
|
|
|
|
wxASSERT(block.sb->GetSampleCount() > 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.sb->GetSampleCount();
|
|
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].sb->GetSampleCount());
|
|
|
|
return rval;
|
|
}
|
|
|
|
//static
|
|
bool Sequence::Read(samplePtr buffer, sampleFormat format,
|
|
const SeqBlock &b, size_t blockRelativeStart, size_t len,
|
|
bool mayThrow)
|
|
{
|
|
const auto &sb = b.sb;
|
|
|
|
wxASSERT(blockRelativeStart + len <= sb->GetSampleCount());
|
|
|
|
// Either throws, or of !mayThrow, tells how many were really read
|
|
auto result = sb->GetSamples(buffer, format, blockRelativeStart, len, mayThrow);
|
|
|
|
if (result != len)
|
|
{
|
|
wxLogWarning(wxT("Expected to read %ld samples, got %ld samples."),
|
|
len, result);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Sequence::Get(samplePtr buffer, sampleFormat format,
|
|
sampleCount start, size_t len, bool mayThrow) const
|
|
{
|
|
if (start == mNumSamples) {
|
|
return len == 0;
|
|
}
|
|
|
|
if (start < 0 || start + len > mNumSamples) {
|
|
if (mayThrow)
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
ClearSamples( buffer, floatSample, 0, len );
|
|
return false;
|
|
}
|
|
int b = FindBlock(start);
|
|
|
|
return Get(b, buffer, format, start, len, mayThrow);
|
|
}
|
|
|
|
bool Sequence::Get(int b, samplePtr buffer, sampleFormat format,
|
|
sampleCount start, size_t len, bool mayThrow) const
|
|
{
|
|
bool result = true;
|
|
while (len) {
|
|
const SeqBlock &block = mBlock[b];
|
|
// start is in block
|
|
const auto bstart = (start - block.start).as_size_t();
|
|
// bstart is not more than block length
|
|
const auto blen = std::min(len, block.sb->GetSampleCount() - bstart);
|
|
|
|
if (! Read(buffer, format, block, bstart, blen, mayThrow) )
|
|
result = false;
|
|
|
|
len -= blen;
|
|
buffer += (blen * SAMPLE_SIZE(format));
|
|
b++;
|
|
start += blen;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Pass NULL to set silence
|
|
/*! @excsafety{Strong} */
|
|
void Sequence::SetSamples(constSamplePtr buffer, sampleFormat format,
|
|
sampleCount start, sampleCount len)
|
|
{
|
|
auto &factory = *mpFactory;
|
|
|
|
const auto size = mBlock.size();
|
|
|
|
if (start < 0 || start + len > mNumSamples)
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
|
|
size_t tempSize = mMaxSamples;
|
|
// to do: allocate this only on demand
|
|
SampleBuffer scratch(tempSize, mSampleFormat);
|
|
|
|
SampleBuffer temp;
|
|
if (buffer && format != mSampleFormat) {
|
|
temp.Allocate(tempSize, mSampleFormat);
|
|
}
|
|
|
|
int b = FindBlock(start);
|
|
BlockArray newBlock;
|
|
std::copy( mBlock.begin(), mBlock.begin() + b, std::back_inserter(newBlock) );
|
|
|
|
while (len > 0
|
|
// Redundant termination condition,
|
|
// but it guards against infinite loop in case of inconsistencies
|
|
// (too-small files, not yet seen?)
|
|
// that cause the loop to make no progress because blen == 0
|
|
&& b < (int)size
|
|
) {
|
|
newBlock.push_back( mBlock[b] );
|
|
SeqBlock &block = newBlock.back();
|
|
// start is within block
|
|
const auto bstart = ( start - block.start ).as_size_t();
|
|
const auto fileLength = block.sb->GetSampleCount();
|
|
|
|
// the std::min is a guard against inconsistent Sequence
|
|
const auto blen =
|
|
limitSampleBufferSize( fileLength - std::min( bstart, fileLength ),
|
|
len );
|
|
wxASSERT(blen == 0 || bstart + blen <= fileLength);
|
|
|
|
#if 0
|
|
// PRL: This inconsistency (too-big file) has been seen in "the wild"
|
|
// in 2.2.0. It is the least problematic kind of inconsistency.
|
|
// We will tolerate it for 2.2.1.
|
|
// Not known whether it is only in projects saved in earlier versions.
|
|
// After 2.2.1, we should detect and correct it at file loading time.
|
|
if (fileLength > mMaxSamples) {
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
}
|
|
#endif
|
|
|
|
ensureSampleBufferSize(scratch, mSampleFormat, tempSize, fileLength,
|
|
&temp);
|
|
|
|
auto useBuffer = buffer;
|
|
if (buffer && format != mSampleFormat)
|
|
{
|
|
// To do: remove the extra movement.
|
|
// Note: we ensured temp can hold fileLength. blen is not more
|
|
CopySamples(buffer, format, temp.ptr(), mSampleFormat, blen);
|
|
useBuffer = temp.ptr();
|
|
}
|
|
|
|
// 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.
|
|
|
|
if ( bstart > 0 || blen < fileLength ) {
|
|
// First or last block is only partially overwritten
|
|
Read(scratch.ptr(), mSampleFormat, block, 0, fileLength, true);
|
|
|
|
if (useBuffer) {
|
|
auto sampleSize = SAMPLE_SIZE(mSampleFormat);
|
|
memcpy(scratch.ptr() +
|
|
bstart * sampleSize, useBuffer, blen * sampleSize);
|
|
}
|
|
else
|
|
ClearSamples(scratch.ptr(), mSampleFormat, bstart, blen);
|
|
|
|
block.sb = factory.Create(
|
|
scratch.ptr(),
|
|
fileLength,
|
|
mSampleFormat);
|
|
}
|
|
else {
|
|
// Avoid reading the disk when the replacement is total
|
|
if (useBuffer)
|
|
block.sb = factory.Create(useBuffer, fileLength, mSampleFormat);
|
|
else
|
|
block.sb = factory.CreateSilent(fileLength, mSampleFormat);
|
|
}
|
|
|
|
// blen might be zero for inconsistent Sequence...
|
|
if( buffer )
|
|
buffer += (blen * SAMPLE_SIZE(format));
|
|
|
|
len -= blen;
|
|
start += blen;
|
|
|
|
// ... but this, at least, always guarantees some loop progress:
|
|
b++;
|
|
}
|
|
|
|
std::copy( mBlock.begin() + b, mBlock.end(), std::back_inserter(newBlock) );
|
|
|
|
CommitChangesIfConsistent( newBlock, mNumSamples, wxT("SetSamples") );
|
|
}
|
|
|
|
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,
|
|
size_t len, const sampleCount *where) const
|
|
{
|
|
wxASSERT(len > 0);
|
|
const auto 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 auto s1 =
|
|
std::min(mNumSamples, std::max(1 + where[len - 1], where[len]));
|
|
Floats temp{ mMaxSamples };
|
|
|
|
decltype(len) pixel = 0;
|
|
|
|
auto srcX = s0;
|
|
decltype(srcX) nextSrcX = 0;
|
|
int lastRmsDenom = 0;
|
|
int lastDivisor = 0;
|
|
auto whereNow = std::min(s1 - 1, where[0]);
|
|
decltype(whereNow) 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.
|
|
const SeqBlock &seqBlock = mBlock[b];
|
|
const auto start = seqBlock.start;
|
|
nextSrcX = std::min(s1, start + seqBlock.sb->GetSampleCount());
|
|
|
|
// 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)
|
|
decltype(len) 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 =
|
|
(whereNext - whereNow).as_double() / (nextPixel - pixel);
|
|
const int divisor =
|
|
(samplesPerPixel >= 65536) ? 65536
|
|
: (samplesPerPixel >= 256) ? 256
|
|
: 1;
|
|
|
|
int blockStatus = b;
|
|
|
|
// How many samples or triples are needed?
|
|
|
|
const size_t startPosition =
|
|
// srcX and start are in the same block
|
|
std::max(sampleCount(0), (srcX - start) / divisor).as_size_t();
|
|
const size_t inclusiveEndPosition =
|
|
// nextSrcX - 1 and start are in the same block
|
|
std::min((sampleCount(mMaxSamples) / divisor) - 1,
|
|
(nextSrcX - 1 - start) / divisor).as_size_t();
|
|
const auto 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
|
|
// no-throw for display operations!
|
|
Read((samplePtr)temp.get(), floatSample, seqBlock, startPosition, num, false);
|
|
break;
|
|
case 256:
|
|
// Read triples
|
|
// Ignore the return value.
|
|
// This function fills with zeroes if read fails
|
|
seqBlock.sb->GetSummary256(temp.get(), startPosition, num);
|
|
break;
|
|
case 65536:
|
|
// Read triples
|
|
// Ignore the return value.
|
|
// This function fills with zeroes if read fails
|
|
seqBlock.sb->GetSummary64k(temp.get(), startPosition, num);
|
|
break;
|
|
}
|
|
|
|
auto filePosition = startPosition;
|
|
|
|
// The previous pixel column might straddle blocks.
|
|
// If so, impute some of the data to it.
|
|
if (b > block0 && pixel > 0) {
|
|
// whereNow and start are in the same block
|
|
auto midPosition = ((whereNow - start) / divisor).as_size_t();
|
|
int diff(midPosition - filePosition);
|
|
if (diff > 0) {
|
|
MinMaxSumsq values(temp.get(), 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)
|
|
auto pixelX = pixel + 1;
|
|
decltype(filePosition) positionX = 0;
|
|
while (pixelX < nextPixel &&
|
|
filePosition ==
|
|
(positionX = (
|
|
// s1 - 1 or where[pixelX] and start are in the same block
|
|
(std::min(s1 - 1, where[pixelX]) - start) / divisor).as_size_t() )
|
|
)
|
|
++pixelX;
|
|
if (pixelX >= nextPixel)
|
|
positionX = 1 + inclusiveEndPosition;
|
|
|
|
// Find results to assign
|
|
rmsDenom = (positionX - filePosition);
|
|
wxASSERT(rmsDenom > 0);
|
|
const float *const pv =
|
|
temp.get() + (filePosition - startPosition) * (divisor == 1 ? 1 : 3);
|
|
MinMaxSumsq values(pv, std::max(0, 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);
|
|
|
|
return true;
|
|
}
|
|
|
|
size_t Sequence::GetIdealAppendLen() const
|
|
{
|
|
int numBlocks = mBlock.size();
|
|
const auto max = GetMaxBlockSize();
|
|
|
|
if (numBlocks == 0)
|
|
return max;
|
|
|
|
const auto lastBlockLen = mBlock.back().sb->GetSampleCount();
|
|
if (lastBlockLen >= max)
|
|
return max;
|
|
else
|
|
return max - lastBlockLen;
|
|
}
|
|
|
|
/*! @excsafety{Strong} */
|
|
SeqBlock::SampleBlockPtr Sequence::AppendNewBlock(
|
|
constSamplePtr buffer, sampleFormat format, size_t len)
|
|
{
|
|
return DoAppend( buffer, format, len, false );
|
|
}
|
|
|
|
/*! @excsafety{Strong} */
|
|
void Sequence::AppendSharedBlock(const SeqBlock::SampleBlockPtr &pBlock)
|
|
{
|
|
auto len = pBlock->GetSampleCount();
|
|
|
|
// Quick check to make sure that it doesn't overflow
|
|
if (Overflows(mNumSamples.as_double() + ((double)len)))
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
|
|
BlockArray newBlock;
|
|
newBlock.emplace_back( pBlock, mNumSamples );
|
|
auto newNumSamples = mNumSamples + len;
|
|
|
|
AppendBlocksIfConsistent(newBlock, false,
|
|
newNumSamples, wxT("Append"));
|
|
|
|
// 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
|
|
}
|
|
|
|
/*! @excsafety{Strong} */
|
|
void Sequence::Append(constSamplePtr buffer, sampleFormat format, size_t len)
|
|
{
|
|
DoAppend(buffer, format, len, true);
|
|
}
|
|
|
|
/*! @excsafety{Strong} */
|
|
SeqBlock::SampleBlockPtr Sequence::DoAppend(
|
|
constSamplePtr buffer, sampleFormat format, size_t len, bool coalesce)
|
|
{
|
|
SeqBlock::SampleBlockPtr result;
|
|
|
|
if (len == 0)
|
|
return result;
|
|
|
|
auto &factory = *mpFactory;
|
|
|
|
// Quick check to make sure that it doesn't overflow
|
|
if (Overflows(mNumSamples.as_double() + ((double)len)))
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
|
|
BlockArray newBlock;
|
|
sampleCount newNumSamples = mNumSamples;
|
|
|
|
// If the last block is not full, we need to add samples to it
|
|
int numBlocks = mBlock.size();
|
|
SeqBlock *pLastBlock;
|
|
decltype(pLastBlock->sb->GetSampleCount()) length;
|
|
size_t bufferSize = mMaxSamples;
|
|
SampleBuffer buffer2(bufferSize, mSampleFormat);
|
|
bool replaceLast = false;
|
|
if (coalesce &&
|
|
numBlocks > 0 &&
|
|
(length =
|
|
(pLastBlock = &mBlock.back())->sb->GetSampleCount()) < mMinSamples) {
|
|
// Enlarge a sub-minimum block at the end
|
|
const SeqBlock &lastBlock = *pLastBlock;
|
|
const auto addLen = std::min(mMaxSamples - length, len);
|
|
|
|
Read(buffer2.ptr(), mSampleFormat, lastBlock, 0, length, true);
|
|
|
|
CopySamples(buffer,
|
|
format,
|
|
buffer2.ptr() + length * SAMPLE_SIZE(mSampleFormat),
|
|
mSampleFormat,
|
|
addLen);
|
|
|
|
const auto newLastBlockLen = length + addLen;
|
|
SampleBlockPtr pBlock = factory.Create(
|
|
buffer2.ptr(),
|
|
newLastBlockLen,
|
|
mSampleFormat);
|
|
SeqBlock newLastBlock(pBlock, lastBlock.start);
|
|
|
|
newBlock.push_back( newLastBlock );
|
|
|
|
len -= addLen;
|
|
newNumSamples += addLen;
|
|
buffer += addLen * SAMPLE_SIZE(format);
|
|
|
|
replaceLast = true;
|
|
}
|
|
// Append the rest as NEW blocks
|
|
while (len) {
|
|
const auto idealSamples = GetIdealBlockSize();
|
|
const auto addedLen = std::min(idealSamples, len);
|
|
SampleBlockPtr pBlock;
|
|
if (format == mSampleFormat) {
|
|
pBlock = factory.Create(buffer, addedLen, mSampleFormat);
|
|
// It's expected that when not requesting coalescence, the
|
|
// data should fit in one block
|
|
wxASSERT( coalesce || !result );
|
|
result = pBlock;
|
|
}
|
|
else {
|
|
CopySamples(buffer, format, buffer2.ptr(), mSampleFormat, addedLen);
|
|
pBlock = factory.Create(buffer2.ptr(), addedLen, mSampleFormat);
|
|
}
|
|
|
|
newBlock.push_back(SeqBlock(pBlock, newNumSamples));
|
|
|
|
buffer += addedLen * SAMPLE_SIZE(format);
|
|
newNumSamples += addedLen;
|
|
len -= addedLen;
|
|
}
|
|
|
|
AppendBlocksIfConsistent(newBlock, replaceLast,
|
|
newNumSamples, wxT("Append"));
|
|
|
|
// 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 result;
|
|
}
|
|
|
|
void Sequence::Blockify(SampleBlockFactory &factory,
|
|
size_t mMaxSamples, sampleFormat mSampleFormat,
|
|
BlockArray &list, sampleCount start,
|
|
constSamplePtr buffer, size_t len)
|
|
{
|
|
if (len <= 0)
|
|
return;
|
|
|
|
auto num = (len + (mMaxSamples - 1)) / mMaxSamples;
|
|
list.reserve(list.size() + num);
|
|
|
|
for (decltype(num) i = 0; i < num; i++) {
|
|
SeqBlock b;
|
|
|
|
const auto offset = i * len / num;
|
|
b.start = start + offset;
|
|
int newLen = ((i + 1) * len / num) - offset;
|
|
auto bufStart = buffer + (offset * SAMPLE_SIZE(mSampleFormat));
|
|
|
|
b.sb = factory.Create(bufStart, newLen, mSampleFormat);
|
|
|
|
list.push_back(b);
|
|
}
|
|
}
|
|
|
|
/*! @excsafety{Strong} */
|
|
void Sequence::Delete(sampleCount start, sampleCount len)
|
|
{
|
|
if (len == 0)
|
|
return;
|
|
|
|
if (len < 0 || start < 0 || start + len > mNumSamples)
|
|
THROW_INCONSISTENCY_EXCEPTION;
|
|
|
|
auto &factory = *mpFactory;
|
|
|
|
const unsigned int numBlocks = mBlock.size();
|
|
|
|
const unsigned int b0 = FindBlock(start);
|
|
unsigned int b1 = FindBlock(start + len - 1);
|
|
|
|
auto sampleSize = SAMPLE_SIZE(mSampleFormat);
|
|
|
|
SeqBlock *pBlock;
|
|
decltype(pBlock->sb->GetSampleCount()) length;
|
|
|
|
// One buffer for reuse in various branches here
|
|
SampleBuffer scratch;
|
|
// The maximum size that should ever be needed
|
|
auto scratchSize = mMaxSamples + mMinSamples;
|
|
|
|
// 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:
|
|
if (b0 == b1 &&
|
|
(length = (pBlock = &mBlock[b0])->sb->GetSampleCount()) - len >= mMinSamples) {
|
|
SeqBlock &b = *pBlock;
|
|
// start is within block
|
|
auto pos = ( start - b.start ).as_size_t();
|
|
|
|
// Guard against failure of this anyway below with limitSampleBufferSize
|
|
wxASSERT(len < length);
|
|
|
|
// len must be less than length
|
|
// because start + len - 1 is also in the block...
|
|
auto newLen = ( length - limitSampleBufferSize( length, len ) );
|
|
|
|
scratch.Allocate(scratchSize, mSampleFormat);
|
|
ensureSampleBufferSize(scratch, mSampleFormat, scratchSize, newLen);
|
|
|
|
Read(scratch.ptr(), mSampleFormat, b, 0, pos, true);
|
|
Read(scratch.ptr() + (pos * sampleSize), mSampleFormat,
|
|
b,
|
|
// ... and therefore pos + len
|
|
// is not more than the length of the block
|
|
( pos + len ).as_size_t(), newLen - pos, true);
|
|
|
|
b.sb = factory.Create(scratch.ptr(), newLen, mSampleFormat);
|
|
|
|
// Don't make a duplicate array. We can still give Strong-guarantee
|
|
// if we modify only one block in place.
|
|
|
|
// use No-fail-guarantee in remaining steps
|
|
|
|
for (unsigned int j = b0 + 1; j < numBlocks; j++)
|
|
mBlock[j].start -= len;
|
|
|
|
mNumSamples -= len;
|
|
|
|
// This consistency check won't throw, it asserts.
|
|
// Proof that we kept consistency is not hard.
|
|
ConsistencyCheck(wxT("Delete - branch one"), false);
|
|
return;
|
|
}
|
|
|
|
// 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];
|
|
// start is within preBlock
|
|
auto preBufferLen = ( start - preBlock.start ).as_size_t();
|
|
if (preBufferLen) {
|
|
if (preBufferLen >= mMinSamples || b0 == 0) {
|
|
if (!scratch.ptr())
|
|
scratch.Allocate(scratchSize, mSampleFormat);
|
|
ensureSampleBufferSize(scratch, mSampleFormat, scratchSize, preBufferLen);
|
|
Read(scratch.ptr(), mSampleFormat, preBlock, 0, preBufferLen, true);
|
|
auto pFile =
|
|
factory.Create(scratch.ptr(), preBufferLen, mSampleFormat);
|
|
|
|
newBlock.push_back(SeqBlock(pFile, preBlock.start));
|
|
} else {
|
|
const SeqBlock &prepreBlock = mBlock[b0 - 1];
|
|
const auto prepreLen = prepreBlock.sb->GetSampleCount();
|
|
const auto sum = prepreLen + preBufferLen;
|
|
|
|
if (!scratch.ptr())
|
|
scratch.Allocate(scratchSize, mSampleFormat);
|
|
ensureSampleBufferSize(scratch, mSampleFormat, scratchSize,
|
|
sum);
|
|
|
|
Read(scratch.ptr(), mSampleFormat, prepreBlock, 0, prepreLen, true);
|
|
Read(scratch.ptr() + prepreLen*sampleSize, mSampleFormat,
|
|
preBlock, 0, preBufferLen, true);
|
|
|
|
newBlock.pop_back();
|
|
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
|
newBlock, prepreBlock.start, scratch.ptr(), sum);
|
|
}
|
|
}
|
|
else {
|
|
// The sample where we begin deletion happens to fall
|
|
// right on the beginning of a block.
|
|
}
|
|
|
|
// 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];
|
|
// start + len - 1 lies within postBlock
|
|
const auto postBufferLen = (
|
|
(postBlock.start + postBlock.sb->GetSampleCount()) - (start + len)
|
|
).as_size_t();
|
|
if (postBufferLen) {
|
|
if (postBufferLen >= mMinSamples || b1 == numBlocks - 1) {
|
|
if (!scratch.ptr())
|
|
// Last use of scratch, can ask for smaller
|
|
scratch.Allocate(postBufferLen, mSampleFormat);
|
|
// start + len - 1 lies within postBlock
|
|
auto pos = (start + len - postBlock.start).as_size_t();
|
|
Read(scratch.ptr(), mSampleFormat, postBlock, pos, postBufferLen, true);
|
|
auto file =
|
|
factory.Create(scratch.ptr(), postBufferLen, mSampleFormat);
|
|
|
|
newBlock.push_back(SeqBlock(file, start));
|
|
} else {
|
|
SeqBlock &postpostBlock = mBlock[b1 + 1];
|
|
const auto postpostLen = postpostBlock.sb->GetSampleCount();
|
|
const auto sum = postpostLen + postBufferLen;
|
|
|
|
if (!scratch.ptr())
|
|
// Last use of scratch, can ask for smaller
|
|
scratch.Allocate(sum, mSampleFormat);
|
|
// start + len - 1 lies within postBlock
|
|
auto pos = (start + len - postBlock.start).as_size_t();
|
|
Read(scratch.ptr(), mSampleFormat, postBlock, pos, postBufferLen, true);
|
|
Read(scratch.ptr() + (postBufferLen * sampleSize), mSampleFormat,
|
|
postpostBlock, 0, postpostLen, true);
|
|
|
|
Blockify(*mpFactory, mMaxSamples, mSampleFormat,
|
|
newBlock, start, scratch.ptr(), sum);
|
|
b1++;
|
|
}
|
|
}
|
|
else {
|
|
// The sample where we begin deletion happens to fall
|
|
// right on the end of a block.
|
|
}
|
|
|
|
// Copy the remaining blocks over from the old array
|
|
for (i = b1 + 1; i < numBlocks; i++)
|
|
newBlock.push_back(mBlock[i].Plus(-len));
|
|
|
|
CommitChangesIfConsistent
|
|
(newBlock, mNumSamples - len, wxT("Delete - branch two"));
|
|
}
|
|
|
|
void Sequence::ConsistencyCheck(const wxChar *whereStr, bool mayThrow) const
|
|
{
|
|
ConsistencyCheck(mBlock, mMaxSamples, 0, mNumSamples, whereStr, mayThrow);
|
|
}
|
|
|
|
void Sequence::ConsistencyCheck
|
|
(const BlockArray &mBlock, size_t maxSamples, size_t from,
|
|
sampleCount mNumSamples, const wxChar *whereStr,
|
|
bool WXUNUSED(mayThrow))
|
|
{
|
|
// Construction of the exception at the appropriate line of the function
|
|
// gives a little more discrimination
|
|
Optional<InconsistencyException> ex;
|
|
|
|
unsigned int numBlocks = mBlock.size();
|
|
|
|
unsigned int i;
|
|
sampleCount pos = from < numBlocks ? mBlock[from].start : mNumSamples;
|
|
if ( from == 0 && pos != 0 )
|
|
ex.emplace( CONSTRUCT_INCONSISTENCY_EXCEPTION );
|
|
|
|
for (i = from; !ex && i < numBlocks; i++) {
|
|
const SeqBlock &seqBlock = mBlock[i];
|
|
if (pos != seqBlock.start)
|
|
ex.emplace( CONSTRUCT_INCONSISTENCY_EXCEPTION );
|
|
|
|
if ( seqBlock.sb ) {
|
|
const auto length = seqBlock.sb->GetSampleCount();
|
|
if (length > maxSamples)
|
|
ex.emplace( CONSTRUCT_INCONSISTENCY_EXCEPTION );
|
|
pos += length;
|
|
}
|
|
else
|
|
ex.emplace( CONSTRUCT_INCONSISTENCY_EXCEPTION );
|
|
}
|
|
if ( !ex && pos != mNumSamples )
|
|
ex.emplace( CONSTRUCT_INCONSISTENCY_EXCEPTION );
|
|
|
|
if ( ex )
|
|
{
|
|
wxLogError(wxT("*** Consistency check failed at %d after %s. ***"),
|
|
ex->GetLine(), whereStr);
|
|
wxString str;
|
|
DebugPrintf(mBlock, mNumSamples, &str);
|
|
wxLogError(wxT("%s"), str);
|
|
wxLogError(wxT("*** Please report this error to https://forum.audacityteam.org/. ***\n\n")
|
|
wxT("Recommended course of action:\n")
|
|
wxT("Undo the failed operation(s), then export or save your work and quit."));
|
|
|
|
//if (mayThrow)
|
|
//throw *ex;
|
|
//else
|
|
wxASSERT(false);
|
|
}
|
|
}
|
|
|
|
void Sequence::CommitChangesIfConsistent
|
|
(BlockArray &newBlock, sampleCount numSamples, const wxChar *whereStr)
|
|
{
|
|
ConsistencyCheck( newBlock, mMaxSamples, 0, numSamples, whereStr ); // may throw
|
|
|
|
// now commit
|
|
// use No-fail-guarantee
|
|
|
|
mBlock.swap(newBlock);
|
|
mNumSamples = numSamples;
|
|
}
|
|
|
|
void Sequence::AppendBlocksIfConsistent
|
|
(BlockArray &additionalBlocks, bool replaceLast,
|
|
sampleCount numSamples, const wxChar *whereStr)
|
|
{
|
|
// Any additional blocks are meant to be appended,
|
|
// replacing the final block if there was one.
|
|
|
|
if (additionalBlocks.empty())
|
|
return;
|
|
|
|
bool tmpValid = false;
|
|
SeqBlock tmp;
|
|
|
|
if ( replaceLast && ! mBlock.empty() ) {
|
|
tmp = mBlock.back(), tmpValid = true;
|
|
mBlock.pop_back();
|
|
}
|
|
|
|
auto prevSize = mBlock.size();
|
|
|
|
bool consistent = false;
|
|
auto cleanup = finally( [&] {
|
|
if ( !consistent ) {
|
|
mBlock.resize( prevSize );
|
|
if ( tmpValid )
|
|
mBlock.push_back( tmp );
|
|
}
|
|
} );
|
|
|
|
std::copy( additionalBlocks.begin(), additionalBlocks.end(),
|
|
std::back_inserter( mBlock ) );
|
|
|
|
// Check consistency only of the blocks that were added,
|
|
// avoiding quadratic time for repeated checking of repeating appends
|
|
ConsistencyCheck( mBlock, mMaxSamples, prevSize, numSamples, whereStr ); // may throw
|
|
|
|
// now commit
|
|
// use No-fail-guarantee
|
|
|
|
mNumSamples = numSamples;
|
|
consistent = true;
|
|
}
|
|
|
|
void Sequence::DebugPrintf
|
|
(const BlockArray &mBlock, sampleCount mNumSamples, wxString *dest)
|
|
{
|
|
unsigned int i;
|
|
decltype(mNumSamples) 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 %ld, id %lld"),
|
|
i,
|
|
seqBlock.start.as_long_long(),
|
|
seqBlock.sb ? (long long) seqBlock.sb->GetSampleCount() : 0,
|
|
seqBlock.sb ? seqBlock.sb.use_count() : 0,
|
|
seqBlock.sb ? (long long) seqBlock.sb->GetBlockID() : 0);
|
|
|
|
if ((pos != seqBlock.start) || !seqBlock.sb)
|
|
*dest += wxT(" ERROR\n");
|
|
else
|
|
*dest += wxT("\n");
|
|
|
|
if (seqBlock.sb)
|
|
pos += seqBlock.sb->GetSampleCount();
|
|
}
|
|
if (pos != mNumSamples)
|
|
*dest += wxString::Format
|
|
(wxT("ERROR mNumSamples = %lld\n"), mNumSamples.as_long_long());
|
|
}
|
|
|
|
// static
|
|
void Sequence::SetMaxDiskBlockSize(size_t bytes)
|
|
{
|
|
sMaxDiskBlockSize = bytes;
|
|
}
|
|
|
|
size_t Sequence::GetMaxDiskBlockSize()
|
|
{
|
|
return sMaxDiskBlockSize;
|
|
}
|