1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-05 22:28:57 +02:00

Strong guarantee, void return from all mutating methods of Sequence

This commit is contained in:
Paul Licameli 2017-04-02 21:35:10 -04:00
commit ae48ac2cbd
6 changed files with 368 additions and 276 deletions

View File

@ -411,7 +411,8 @@ void RecordingRecoveryHandler::HandleXMLEndTag(const wxChar *tag)
WaveClip* clip = track->NewestOrNewClip();
Sequence* seq = clip->GetSequence();
seq->ConsistencyCheck(wxT("RecordingRecoveryHandler::HandleXMLEndTag"));
seq->ConsistencyCheck
(wxT("RecordingRecoveryHandler::HandleXMLEndTag"), false);
}
}

View File

@ -474,7 +474,8 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
elapsed = timer.Time();
if (mBlockDetail) {
t->GetClipByIndex(0)->GetSequence()->DebugPrintf(&tempStr);
auto seq = t->GetClipByIndex(0)->GetSequence();
seq->DebugPrintf(seq->GetBlockArray(), seq->GetNumSamples(), &tempStr);
mToPrint += tempStr;
}
Printf(wxT("Time to perform %d edits: %ld ms\n"), trials, elapsed);

View File

@ -50,6 +50,8 @@
#include "blockfile/SimpleBlockFile.h"
#include "blockfile/SilentBlockFile.h"
#include "InconsistencyException.h"
size_t Sequence::sMaxDiskBlockSize = 1048576;
// Sequence methods
@ -70,9 +72,7 @@ Sequence::Sequence(const Sequence &orig, const std::shared_ptr<DirManager> &proj
, mMinSamples(orig.mMinSamples)
, mMaxSamples(orig.mMaxSamples)
{
bool bResult = Paste(0, &orig);
wxASSERT(bResult); // TO DO: Actually handle this.
(void)bResult;
Paste(0, &orig);
}
Sequence::~Sequence()
@ -129,19 +129,16 @@ bool Sequence::SetSampleFormat(sampleFormat format)
}
*/
bool Sequence::ConvertToSampleFormat(sampleFormat format, bool* pbChanged)
bool Sequence::ConvertToSampleFormat(sampleFormat format)
// STRONG-GUARANTEE
{
wxASSERT(pbChanged);
*pbChanged = false;
// Caller should check this no-change case before calling; we ignore it here.
if (format == mSampleFormat)
return true;
// no change
return false;
if (mBlock.size() == 0)
{
mSampleFormat = format;
*pbChanged = true;
return true;
}
@ -153,25 +150,32 @@ bool Sequence::ConvertToSampleFormat(sampleFormat format, bool* pbChanged)
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 = 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));
bool bSuccess = true;
{
SampleBuffer bufferOld(oldMaxSamples, oldFormat);
SampleBuffer bufferNew(oldMaxSamples, format);
for (size_t i = 0, nn = mBlock.size(); i < nn && bSuccess; i++)
for (size_t i = 0, nn = mBlock.size(); i < nn; i++)
{
SeqBlock &oldSeqBlock = mBlock[i];
const auto &oldBlockFile = oldSeqBlock.f;
const auto len = oldBlockFile->GetLength();
bSuccess = (oldBlockFile->ReadData(bufferOld.ptr(), oldFormat, 0, len) > 0);
if (!bSuccess)
break;
Read(bufferOld.ptr(), oldFormat, oldSeqBlock, 0, len, true);
CopySamples(bufferOld.ptr(), oldFormat, bufferNew.ptr(), format, len);
@ -189,42 +193,20 @@ bool Sequence::ConvertToSampleFormat(sampleFormat format, bool* pbChanged)
const unsigned prevSize = newBlockArray.size();
Blockify(*mDirManager, mMaxSamples, mSampleFormat,
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.
// Invalidate all the old, non-aliased block files.
// Aliased files will be converted at save, per comment above.
// 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.
// Commit the changes to block file array
CommitChangesIfConsistent
(newBlockArray, mNumSamples, wxT("Sequence::ConvertToSampleFormat()"));
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.
*/
// Commit the other changes
bSuccess = true;
// 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;
return true;
}
std::pair<float, float> Sequence::GetMinMax(
@ -437,9 +419,7 @@ std::unique_ptr<Sequence> Sequence::Copy(sampleCount s0, sampleCount s1) const
// Increase ref count or duplicate file
}
if (! ConsistencyCheck(wxT("Sequence::Copy()")))
//THROW_INCONSISTENCY_EXCEPTION
;
dest->ConsistencyCheck(wxT("Sequence::Copy()"));
return dest;
}
@ -451,7 +431,8 @@ namespace {
}
}
bool Sequence::Paste(sampleCount s, const Sequence *src)
void Sequence::Paste(sampleCount s, const Sequence *src)
// STRONG-GUARANTEE
{
if ((s < 0) || (s > mNumSamples))
{
@ -460,8 +441,8 @@ bool Sequence::Paste(sampleCount s, const Sequence *src)
// PRL: Why bother with Internat when the above is just wxT?
Internat::ToString(s.as_double(), 0).c_str(),
Internat::ToString(mNumSamples.as_double(), 0).c_str());
wxASSERT(false);
return false;
//THROW_INCONSISTENCY_EXCEPTION
;
}
// Quick check to make sure that it doesn't overflow
@ -472,8 +453,8 @@ bool Sequence::Paste(sampleCount s, const Sequence *src)
// PRL: Why bother with Internat when the above is just wxT?
Internat::ToString(mNumSamples.as_double(), 0).c_str(),
Internat::ToString(src->mNumSamples.as_double(), 0).c_str());
wxASSERT(false);
return false;
//THROW_INCONSISTENCY_EXCEPTION
;
}
if (src->mSampleFormat != mSampleFormat)
@ -481,8 +462,8 @@ bool Sequence::Paste(sampleCount s, const Sequence *src)
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;
//THROW_INCONSISTENCY_EXCEPTION
;
}
const BlockArray &srcBlock = src->mBlock;
@ -491,7 +472,7 @@ bool Sequence::Paste(sampleCount s, const Sequence *src)
auto sampleSize = SAMPLE_SIZE(mSampleFormat);
if (addedLen == 0 || srcNumBlocks == 0)
return true;
return;
const size_t numBlocks = mBlock.size();
@ -501,11 +482,18 @@ bool Sequence::Paste(sampleCount s, const Sequence *src)
// 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(*mDirManager, mBlock, mNumSamples, srcBlock[i]);
// AppendBlock may throw for limited disk space, if pasting from
// one project into another.
AppendBlock(*mDirManager, newBlock, samples, srcBlock[i]);
// Increase ref count or duplicate file
return ConsistencyCheck(wxT("Paste branch one"));
CommitChangesIfConsistent
(newBlock, samples, wxT("Paste branch one"));
return;
}
const int b = (s == mNumSamples) ? mBlock.size() - 1 : FindBlock(s);
@ -540,6 +528,10 @@ bool Sequence::Paste(sampleCount s, const Sequence *src)
// largerBlockLen is not more than mMaxSamples...
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 NOFAIL-GUARANTEE in remaining steps
block.f = file;
for (unsigned int i = b + 1; i < numBlocks; i++)
@ -547,7 +539,10 @@ bool Sequence::Paste(sampleCount s, const Sequence *src)
mNumSamples += addedLen;
return ConsistencyCheck(wxT("Paste branch two"));
// 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,
@ -635,26 +630,26 @@ bool Sequence::Paste(sampleCount s, const Sequence *src)
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"));
CommitChangesIfConsistent
(newBlock, mNumSamples + addedLen, wxT("Paste branch three"));
}
bool Sequence::SetSilence(sampleCount s0, sampleCount len)
void Sequence::SetSilence(sampleCount s0, sampleCount len)
// STRONG-GUARANTEE
{
return Set(NULL, mSampleFormat, s0, len);
SetSamples(NULL, mSampleFormat, s0, len);
}
bool Sequence::InsertSilence(sampleCount s0, sampleCount len)
void Sequence::InsertSilence(sampleCount s0, sampleCount len)
// STRONG-GUARANTEE
{
// Quick check to make sure that it doesn't overflow
if (Overflows((mNumSamples.as_double()) + (len.as_double())))
return false;
//THROW_INCONSISTENCY_EXCEPTION
;
if (len <= 0)
return true;
return;
// Create a NEW track containing as much silence as we
// need to insert, and then call Paste to do the insertion.
@ -690,19 +685,19 @@ bool Sequence::InsertSilence(sampleCount s0, sampleCount len)
sTrack.mNumSamples = pos;
bool bResult = Paste(s0, &sTrack);
wxASSERT(bResult);
return bResult && ConsistencyCheck(wxT("InsertSilence"));
// use STRONG-GUARANTEE
Paste(s0, &sTrack);
}
bool Sequence::AppendAlias(const wxString &fullPath,
void Sequence::AppendAlias(const wxString &fullPath,
sampleCount start,
size_t len, int channel, bool useOD)
// STRONG-GUARANTEE
{
// Quick check to make sure that it doesn't overflow
if (Overflows((mNumSamples.as_double()) + ((double)len)))
return false;
//THROW_INCONSISTENCY_EXCEPTION
;
SeqBlock newBlock(
useOD?
@ -712,16 +707,16 @@ bool Sequence::AppendAlias(const wxString &fullPath,
);
mBlock.push_back(newBlock);
mNumSamples += len;
return true;
}
bool Sequence::AppendCoded(const wxString &fName, sampleCount start,
void Sequence::AppendCoded(const wxString &fName, sampleCount start,
size_t len, int channel, int decodeType)
// STRONG-GUARANTEE
{
// Quick check to make sure that it doesn't overflow
if (Overflows((mNumSamples.as_double()) + ((double)len)))
return false;
//THROW_INCONSISTENCY_EXCEPTION
;
SeqBlock newBlock(
mDirManager->NewODDecodeBlockFile(fName, start, len, channel, decodeType),
@ -729,17 +724,17 @@ bool Sequence::AppendCoded(const wxString &fName, sampleCount start,
);
mBlock.push_back(newBlock);
mNumSamples += len;
return true;
}
bool Sequence::AppendBlock
void Sequence::AppendBlock
(DirManager &mDirManager,
BlockArray &mBlock, sampleCount &mNumSamples, const SeqBlock &b)
{
// Quick check to make sure that it doesn't overflow
if (Overflows((mNumSamples.as_double()) + ((double)b.f->GetLength())))
return false;
// THROW_INCONSISTENCY_EXCEPTION
return
;
SeqBlock newBlock(
mDirManager.CopyBlockFile(b.f), // Bump ref count if not locked, else copy
@ -751,16 +746,11 @@ bool Sequence::AppendBlock
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.
@ -1180,12 +1170,14 @@ bool Sequence::Get(int b, samplePtr buffer, sampleFormat format,
}
// Pass NULL to set silence
bool Sequence::Set(samplePtr buffer, sampleFormat format,
void Sequence::SetSamples(samplePtr buffer, sampleFormat format,
sampleCount start, sampleCount len)
// STRONG-GUARANTEE
{
if (start < 0 || start >= mNumSamples ||
start+len > mNumSamples)
return false;
start + len > mNumSamples)
//THROW_INCONSISTENCY_EXCEPTION
;
SampleBuffer scratch(mMaxSamples, mSampleFormat);
@ -1196,9 +1188,12 @@ bool Sequence::Set(samplePtr buffer, sampleFormat format,
}
int b = FindBlock(start);
BlockArray newBlock;
std::copy( mBlock.begin(), mBlock.begin() + b, std::back_inserter(newBlock) );
while (len != 0) {
SeqBlock &block = mBlock[b];
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.f->GetLength();
@ -1253,7 +1248,9 @@ bool Sequence::Set(samplePtr buffer, sampleFormat format,
b++;
}
return ConsistencyCheck(wxT("Set"));
std::copy( mBlock.begin() + b, mBlock.end(), std::back_inserter(newBlock) );
CommitChangesIfConsistent( newBlock, mNumSamples, wxT("SetSamples") );
}
namespace {
@ -1521,22 +1518,32 @@ size_t Sequence::GetIdealAppendLen() const
return max - lastBlockLen;
}
bool Sequence::Append(samplePtr buffer, sampleFormat format,
void Sequence::Append(samplePtr buffer, sampleFormat format,
size_t len, XMLWriter* blockFileLog /*=NULL*/)
// STRONG-GUARANTEE
{
if (len == 0)
return;
// Quick check to make sure that it doesn't overflow
if (Overflows(mNumSamples.as_double() + ((double)len)))
return false;
//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->f->GetLength()) length;
SampleBuffer buffer2(mMaxSamples, mSampleFormat);
bool replaceLast = false;
if (numBlocks > 0 &&
(length =
(pLastBlock = &mBlock.back())->f->GetLength()) < mMinSamples) {
SeqBlock &lastBlock = *pLastBlock;
// 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);
@ -1547,11 +1554,13 @@ bool Sequence::Append(samplePtr buffer, sampleFormat format,
mSampleFormat,
addLen);
const int newLastBlockLen = length + addLen;
const auto newLastBlockLen = length + addLen;
SeqBlock newLastBlock(
mDirManager->NewSimpleBlockFile(buffer2.ptr(), newLastBlockLen, mSampleFormat,
blockFileLog != NULL),
mDirManager->NewSimpleBlockFile(
buffer2.ptr(), newLastBlockLen, mSampleFormat,
blockFileLog != NULL
),
lastBlock.start
);
@ -1560,46 +1569,49 @@ bool Sequence::Append(samplePtr buffer, sampleFormat format,
static_cast< SimpleBlockFile * >( &*newLastBlock.f )
->SaveXML( *blockFileLog );
lastBlock = newLastBlock;
newBlock.push_back( newLastBlock );
len -= addLen;
mNumSamples += addLen;
newNumSamples += addLen;
buffer += addLen * SAMPLE_SIZE(format);
replaceLast = true;
}
// Append the rest as NEW blocks
while (len) {
const auto idealSamples = GetIdealBlockSize();
const auto l = std::min(idealSamples, len);
const auto addedLen = std::min(idealSamples, len);
BlockFilePtr pFile;
if (format == mSampleFormat) {
pFile = mDirManager->NewSimpleBlockFile(buffer, l, mSampleFormat,
blockFileLog != NULL);
pFile = mDirManager->NewSimpleBlockFile(
buffer, addedLen, mSampleFormat, blockFileLog != NULL);
}
else {
CopySamples(buffer, format, buffer2.ptr(), mSampleFormat, l);
pFile = mDirManager->NewSimpleBlockFile(buffer2.ptr(), l, mSampleFormat,
blockFileLog != NULL);
CopySamples(buffer, format, buffer2.ptr(), mSampleFormat, addedLen);
pFile = mDirManager->NewSimpleBlockFile(
buffer2.ptr(), addedLen, mSampleFormat, blockFileLog != NULL);
}
if (blockFileLog)
// shouldn't throw, because XMLWriter is not XMLFileWriter
static_cast< SimpleBlockFile * >( &*pFile )->SaveXML( *blockFileLog );
mBlock.push_back(SeqBlock(pFile, mNumSamples));
newBlock.push_back(SeqBlock(pFile, mNumSamples));
buffer += l * SAMPLE_SIZE(format);
mNumSamples += l;
len -= l;
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 true;
}
void Sequence::Blockify
@ -1625,12 +1637,15 @@ void Sequence::Blockify
}
}
bool Sequence::Delete(sampleCount start, sampleCount len)
void Sequence::Delete(sampleCount start, sampleCount len)
// STRONG-GUARANTEE
{
if (len == 0)
return true;
return;
if (len < 0 || start < 0 || start >= mNumSamples)
return false;
//THROW_INCONSISTENCY_EXCEPTION
;
//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
@ -1644,9 +1659,6 @@ bool Sequence::Delete(sampleCount start, sampleCount len)
auto 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;
decltype(pBlock->f->GetLength()) length;
@ -1655,7 +1667,11 @@ bool Sequence::Delete(sampleCount start, sampleCount len)
// The maximum size that will ever be needed
const auto scratchSize = mMaxSamples + mMinSamples;
if (b0 == b1 && (length = (pBlock = &mBlock[b0])->f->GetLength()) - len >= 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])->f->GetLength()) - len >= mMinSamples) {
SeqBlock &b = *pBlock;
// start is within block
auto pos = ( start - b.start ).as_size_t();
@ -1673,17 +1689,25 @@ bool Sequence::Delete(sampleCount start, sampleCount len)
// is not more than the length of the block
( pos + len ).as_size_t(), newLen - pos, true);
b = SeqBlock(
mDirManager->NewSimpleBlockFile(scratch.ptr(), newLen, mSampleFormat),
b.start
);
auto newFile =
mDirManager->NewSimpleBlockFile(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 NOFAIL-GUARANTEE in remaining steps
b.f = newFile;
for (unsigned int j = b0 + 1; j < numBlocks; j++)
mBlock[j].start -= len;
mNumSamples -= len;
return ConsistencyCheck(wxT("Delete - branch one"));
// 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
@ -1784,50 +1808,120 @@ bool Sequence::Delete(sampleCount start, sampleCount len)
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"));
CommitChangesIfConsistent
(newBlock, mNumSamples - len, wxT("Delete - branch two"));
}
bool Sequence::ConsistencyCheck(const wxChar *whereStr) const
void Sequence::ConsistencyCheck(const wxChar *whereStr, bool mayThrow) const
{
unsigned int i;
sampleCount pos = 0;
unsigned int numBlocks = mBlock.size();
bool bError = false;
ConsistencyCheck(mBlock, 0, mNumSamples, whereStr, mayThrow);
}
for (i = 0; !bError && i < numBlocks; i++) {
void Sequence::ConsistencyCheck
(const BlockArray &mBlock, size_t from,
sampleCount mNumSamples, const wxChar *whereStr,
bool mayThrow)
{
bool bError = false;
// Construction of the exception at the appropriate line of the function
// gives a little more discrimination
InconsistencyException ex;
unsigned int i;
sampleCount pos = mBlock[from].start;
if ( from == 0 && pos != 0 )
ex = CONSTRUCT_INCONSISTENCY_EXCEPTION, bError = true;
unsigned int numBlocks = mBlock.size();
for (i = from; !bError && i < numBlocks; i++) {
const SeqBlock &seqBlock = mBlock[i];
if (pos != seqBlock.start)
bError = true;
ex = CONSTRUCT_INCONSISTENCY_EXCEPTION, bError = true;
if (seqBlock.f)
if ( seqBlock.f )
pos += seqBlock.f->GetLength();
else
bError = true;
ex = CONSTRUCT_INCONSISTENCY_EXCEPTION, bError = true;
}
if (pos != mNumSamples)
bError = true;
if ( !bError && pos != mNumSamples )
ex = CONSTRUCT_INCONSISTENCY_EXCEPTION, bError = true;
if (bError)
if ( bError )
{
wxLogError(wxT("*** Consistency check failed after %s. ***"), whereStr);
wxString str;
DebugPrintf(&str);
DebugPrintf(mBlock, mNumSamples, &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;
if (mayThrow)
//throw ex
;
else
wxASSERT(false);
}
}
void Sequence::DebugPrintf(wxString *dest) const
void Sequence::CommitChangesIfConsistent
(BlockArray &newBlock, sampleCount numSamples, const wxChar *whereStr)
{
ConsistencyCheck( newBlock, 0, numSamples, whereStr ); // may throw
// now commit
// use NOFAIL-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, prevSize, numSamples, whereStr ); // may throw
// now commit
// use NOFAIL-GUARANTEE
mNumSamples = numSamples;
consistent = true;
}
void Sequence::DebugPrintf
(const BlockArray &mBlock, sampleCount mNumSamples, wxString *dest)
{
unsigned int i;
decltype(mNumSamples) pos = 0;

View File

@ -90,7 +90,7 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
// Note that len is not size_t, because nullptr may be passed for buffer, in
// which case, silence is inserted, possibly a large amount.
bool Set(samplePtr buffer, sampleFormat format,
void SetSamples(samplePtr buffer, sampleFormat format,
sampleCount start, sampleCount len);
// where is input, assumed to be nondecreasing, and its size is len + 1.
@ -104,17 +104,17 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
size_t len, const sampleCount *where) const;
std::unique_ptr<Sequence> Copy(sampleCount s0, sampleCount s1) const;
bool Paste(sampleCount s0, const Sequence *src);
void Paste(sampleCount s0, const Sequence *src);
size_t GetIdealAppendLen() const;
bool Append(samplePtr buffer, sampleFormat format, size_t len,
void Append(samplePtr buffer, sampleFormat format, size_t len,
XMLWriter* blockFileLog=NULL);
bool Delete(sampleCount start, sampleCount len);
bool AppendAlias(const wxString &fullPath,
void Delete(sampleCount start, sampleCount len);
void AppendAlias(const wxString &fullPath,
sampleCount start,
size_t len, int channel, bool useOD);
bool AppendCoded(const wxString &fName, sampleCount start,
void AppendCoded(const wxString &fName, sampleCount start,
size_t len, int channel, int decodeType);
///gets an int with OD flags so that we can determine which ODTasks should be run on this track after save/open, etc.
@ -128,8 +128,8 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
// loaded from an XML file via DirManager::HandleXMLTag
void AppendBlockFile(const BlockFilePtr &blockFile);
bool SetSilence(sampleCount s0, sampleCount len);
bool InsertSilence(sampleCount s0, sampleCount len);
void SetSilence(sampleCount s0, sampleCount len);
void InsertSilence(sampleCount s0, sampleCount len);
const std::shared_ptr<DirManager> &GetDirManager() { return mDirManager; }
@ -162,8 +162,9 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
//
sampleFormat GetSampleFormat() const;
// bool SetSampleFormat(sampleFormat format);
bool ConvertToSampleFormat(sampleFormat format, bool* pbChanged);
// Return true iff there is a change
bool ConvertToSampleFormat(sampleFormat format);
//
// Retrieving summary info
@ -245,7 +246,7 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
int FindBlock(sampleCount pos) const;
static bool AppendBlock
static void AppendBlock
(DirManager &dirManager,
BlockArray &blocks, sampleCount &numSamples, const SeqBlock &b);
@ -263,19 +264,38 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
bool Get(int b, samplePtr buffer, sampleFormat format,
sampleCount start, size_t len, bool mayThrow) const;
public:
public:
//
// Public methods intended for debugging only
// Public methods
//
// This function makes sure that the track isn't messed up
// This function throws if the track is messed up
// because of inconsistent block starts & lengths
bool ConsistencyCheck(const wxChar *whereStr) const;
void ConsistencyCheck (const wxChar *whereStr, bool mayThrow = true) const;
// This function prints information to stdout about the blocks in the
// tracks and indicates if there are inconsistencies.
void DebugPrintf(wxString *dest) const;
static void DebugPrintf
(const BlockArray &block, sampleCount numSamples, wxString *dest);
private:
static void ConsistencyCheck
(const BlockArray &block, size_t from,
sampleCount numSamples, const wxChar *whereStr,
bool mayThrow = true);
// The next two are used in methods that give a strong guarantee.
// They either throw because final consistency check fails, or swap the
// changed contents into place.
void CommitChangesIfConsistent
(BlockArray &newBlock, sampleCount numSamples, const wxChar *whereStr);
void AppendBlocksIfConsistent
(BlockArray &additionalBlocks, bool replaceLast,
sampleCount numSamples, const wxChar *whereStr);
};
#endif // __AUDACITY_SEQUENCE__

View File

@ -413,7 +413,7 @@ void WaveClip::SetSamples(samplePtr buffer, sampleFormat format,
// STRONG-GUARANTEE
{
// use STRONG-GUARANTEE
mSequence->Set(buffer, format, start, len);
mSequence->SetSamples(buffer, format, start, len);
// use NOFAIL-GUARANTEE
MarkChanged();
@ -1342,11 +1342,9 @@ void WaveClip::ConvertToSampleFormat(sampleFormat format)
// Note: it is not necessary to do this recursively to cutlines.
// They get converted as needed when they are expanded.
bool bChanged;
bool bResult = mSequence->ConvertToSampleFormat(format, &bChanged);
if (bResult && bChanged)
auto bChanged = mSequence->ConvertToSampleFormat(format);
if (bChanged)
MarkChanged();
wxASSERT(bResult); // TODO: Throw an actual error.
}
void WaveClip::UpdateEnvelopeTrackLen()
@ -1405,13 +1403,10 @@ void WaveClip::Append(samplePtr buffer, sampleFormat format,
for(;;) {
if (mAppendBufferLen >= blockSize) {
bool success =
// flush some previously appended contents
// use STRONG-GUARANTEE
mSequence->Append(mAppendBuffer.ptr(), seqFormat, blockSize,
blockFileLog);
if (!success)
return;
// flush some previously appended contents
// use STRONG-GUARANTEE
mSequence->Append(mAppendBuffer.ptr(), seqFormat, blockSize,
blockFileLog);
// use NOFAIL-GUARANTEE for rest of this "if"
memmove(mAppendBuffer.ptr(),
@ -1446,14 +1441,11 @@ void WaveClip::AppendAlias(const wxString &fName, sampleCount start,
// STRONG-GUARANTEE
{
// use STRONG-GUARANTEE
bool result = mSequence->AppendAlias(fName, start, len, channel,useOD);
mSequence->AppendAlias(fName, start, len, channel,useOD);
// use NOFAIL-GUARANTEE
if (result)
{
UpdateEnvelopeTrackLen();
MarkChanged();
}
UpdateEnvelopeTrackLen();
MarkChanged();
}
void WaveClip::AppendCoded(const wxString &fName, sampleCount start,
@ -1461,17 +1453,14 @@ void WaveClip::AppendCoded(const wxString &fName, sampleCount start,
// STRONG-GUARANTEE
{
// use STRONG-GUARANTEE
bool result = mSequence->AppendCoded(fName, start, len, channel, decodeType);
mSequence->AppendCoded(fName, start, len, channel, decodeType);
// use NOFAIL-GUARANTEE
if (result)
{
UpdateEnvelopeTrackLen();
MarkChanged();
}
UpdateEnvelopeTrackLen();
MarkChanged();
}
bool WaveClip::Flush()
void WaveClip::Flush()
// NOFAIL-GUARANTEE that the clip will be in a flushed state.
// PARTIAL-GUARANTEE in case of exceptions:
// Some initial portion (maybe none) of the append buffer of the
@ -1481,7 +1470,6 @@ bool WaveClip::Flush()
//wxLogDebug(wxT(" mAppendBufferLen=%lli"), (long long) mAppendBufferLen);
//wxLogDebug(wxT(" previous sample count %lli"), (long long) mSequence->GetNumSamples());
bool success = true;
if (mAppendBufferLen > 0) {
auto cleanup = finally( [&] {
@ -1494,12 +1482,11 @@ bool WaveClip::Flush()
MarkChanged();
} );
success = mSequence->Append(mAppendBuffer.ptr(), mSequence->GetSampleFormat(), mAppendBufferLen);
mSequence->Append(mAppendBuffer.ptr(), mSequence->GetSampleFormat(),
mAppendBufferLen);
}
//wxLogDebug(wxT("now sample count %lli"), (long long) mSequence->GetNumSamples());
return success;
}
bool WaveClip::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
@ -1613,18 +1600,16 @@ void WaveClip::Paste(double t0, const WaveClip* other)
TimeToSamplesClip(t0, &s0);
// Assume STRONG-GUARANTEE from Sequence::Paste
if (mSequence->Paste(s0, pastedClip->mSequence.get()))
{
// Assume NOFAIL-GUARANTEE in the remaining
MarkChanged();
mEnvelope->Paste(s0.as_double()/mRate + mOffset, pastedClip->mEnvelope.get());
mEnvelope->RemoveUnneededPoints();
OffsetCutLines(t0, pastedClip->GetEndTime() - pastedClip->GetStartTime());
mSequence->Paste(s0, pastedClip->mSequence.get());
for (auto &holder : newCutlines)
mCutLines.push_back(std::move(holder));
// Assume NOFAIL-GUARANTEE in the remaining
MarkChanged();
mEnvelope->Paste(s0.as_double()/mRate + mOffset, pastedClip->mEnvelope.get());
mEnvelope->RemoveUnneededPoints();
OffsetCutLines(t0, pastedClip->GetEndTime() - pastedClip->GetStartTime());
}
for (auto &holder : newCutlines)
mCutLines.push_back(std::move(holder));
}
void WaveClip::InsertSilence(double t, double len)
@ -1635,11 +1620,7 @@ void WaveClip::InsertSilence(double t, double len)
auto slen = (sampleCount)floor(len * mRate + 0.5);
// use STRONG-GUARANTEE
if (!GetSequence()->InsertSilence(s0, slen))
{
wxASSERT(false);
return;
}
GetSequence()->InsertSilence(s0, slen);
// use NOFAIL-GUARANTEE
OffsetCutLines(t, len);
@ -1656,55 +1637,55 @@ void WaveClip::Clear(double t0, double t1)
TimeToSamplesClip(t1, &s1);
// use STRONG-GUARANTEE
if (GetSequence()->Delete(s0, s1-s0))
// use NOFAIL-GUARANTEE in the remaining
GetSequence()->Delete(s0, s1-s0);
// use NOFAIL-GUARANTEE in the remaining
// msmeyer
//
// Delete all cutlines that are within the given area, if any.
//
// Note that when cutlines are active, two functions are used:
// Clear() and ClearAndAddCutLine(). ClearAndAddCutLine() is called
// whenever the user directly calls a command that removes some audio, e.g.
// "Cut" or "Clear" from the menu. This command takes care about recursive
// preserving of cutlines within clips. Clear() is called when internal
// operations want to remove audio. In the latter case, it is the right
// thing to just remove all cutlines within the area.
//
double clip_t0 = t0;
double clip_t1 = t1;
if (clip_t0 < GetStartTime())
clip_t0 = GetStartTime();
if (clip_t1 > GetEndTime())
clip_t1 = GetEndTime();
// May DELETE as we iterate, so don't use range-for
for (auto it = mCutLines.begin(); it != mCutLines.end();)
{
// msmeyer
//
// Delete all cutlines that are within the given area, if any.
//
// Note that when cutlines are active, two functions are used:
// Clear() and ClearAndAddCutLine(). ClearAndAddCutLine() is called
// whenever the user directly calls a command that removes some audio, e.g.
// "Cut" or "Clear" from the menu. This command takes care about recursive
// preserving of cutlines within clips. Clear() is called when internal
// operations want to remove audio. In the latter case, it is the right
// thing to just remove all cutlines within the area.
//
double clip_t0 = t0;
double clip_t1 = t1;
if (clip_t0 < GetStartTime())
clip_t0 = GetStartTime();
if (clip_t1 > GetEndTime())
clip_t1 = GetEndTime();
// May DELETE as we iterate, so don't use range-for
for (auto it = mCutLines.begin(); it != mCutLines.end();)
WaveClip* clip = it->get();
double cutlinePosition = mOffset + clip->GetOffset();
if (cutlinePosition >= t0 && cutlinePosition <= t1)
{
WaveClip* clip = it->get();
double cutlinePosition = mOffset + clip->GetOffset();
if (cutlinePosition >= t0 && cutlinePosition <= t1)
{
// This cutline is within the area, DELETE it
it = mCutLines.erase(it);
}
else
{
if (cutlinePosition >= t1)
{
clip->Offset(clip_t0 - clip_t1);
}
++it;
}
// This cutline is within the area, DELETE it
it = mCutLines.erase(it);
}
else
{
if (cutlinePosition >= t1)
{
clip->Offset(clip_t0 - clip_t1);
}
++it;
}
// Collapse envelope
GetEnvelope()->CollapseRegion(t0, t1);
if (t0 < GetStartTime())
Offset(-(GetStartTime() - t0));
MarkChanged();
}
// Collapse envelope
GetEnvelope()->CollapseRegion(t0, t1);
if (t0 < GetStartTime())
Offset(-(GetStartTime() - t0));
MarkChanged();
}
void WaveClip::ClearAndAddCutLine(double t0, double t1)
@ -1749,17 +1730,16 @@ void WaveClip::ClearAndAddCutLine(double t0, double t1)
TimeToSamplesClip(t1, &s1);
// use WEAK-GUARANTEE
if (GetSequence()->Delete(s0, s1-s0))
{
// Collapse envelope
GetEnvelope()->CollapseRegion(t0, t1);
if (t0 < GetStartTime())
Offset(-(GetStartTime() - t0));
GetSequence()->Delete(s0, s1-s0);
MarkChanged();
// Collapse envelope
GetEnvelope()->CollapseRegion(t0, t1);
if (t0 < GetStartTime())
Offset(-(GetStartTime() - t0));
mCutLines.push_back(std::move(newClip));
}
MarkChanged();
mCutLines.push_back(std::move(newClip));
}
bool WaveClip::FindCutLine(double cutLinePosition,
@ -1915,12 +1895,8 @@ void WaveClip::Resample(int rate, ProgressDialog *progress)
break;
}
if (!newSequence->Append((samplePtr)outBuffer.get(), floatSample,
outGenerated))
{
error = true;
break;
}
newSequence->Append((samplePtr)outBuffer.get(), floatSample,
outGenerated);
if (progress)
{

View File

@ -301,7 +301,7 @@ public:
size_t len, unsigned int stride=1,
XMLWriter* blockFileLog = NULL);
/// Flush must be called after last Append
bool Flush();
void Flush();
void AppendAlias(const wxString &fName, sampleCount start,
size_t len, int channel,bool useOD);