diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 8a4852f01..e4a6f994e 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -2449,8 +2449,20 @@ void AudioIO::StopStream() // Stop those exceptions here, or else they propagate through too // many parts of Audacity that are not effects or editing // operations. GuardedCall ensures that the user sees a warning. + + // Also be sure to Flush each track, at the top of the guarded call, + // relying on the guarantee that the track will be left in a flushed + // state, though the append buffer may be lost. + + // If the other track operations fail their strong guarantees, then + // the shift for latency correction may be skipped. GuardedCall( [&] { WaveTrack* track = mCaptureTracks[i]; + + // use NOFAIL-GUARANTEE that track is flushed, + // PARTIAL-GUARANTEE that some initial length of the recording + // is saved. + // See comments in FillBuffers(). track->Flush(); if (mPlaybackTracks.size() > 0) @@ -2475,12 +2487,15 @@ void AudioIO::StopStream() if( appendRecord ) { // append-recording if (recordingOffset < 0) + // use STRONG-GUARANTEE track->Clear(mT0, mT0 - recordingOffset); // cut the latency out else + // use STRONG-GUARANTEE track->InsertSilence(mT0, recordingOffset); // put silence in } else { // recording into a NEW track + // gives NOFAIL-GUARANTEE though we only need STRONG track->SetOffset(track->GetStartTime() + recordingOffset); if(track->GetEndTime() < 0.) { @@ -2488,6 +2503,7 @@ void AudioIO::StopStream() "Latency Correction setting has caused the recorded audio to be hidden before zero.\nAudacity has brought it back to start at zero.\nYou may have to use the Time Shift Tool (<---> or F5) to drag the track to the right place."), _("Latency problem"), wxOK); m.ShowModal(); + // gives NOFAIL-GUARANTEE though we only need STRONG track->SetOffset(0.); } } diff --git a/src/WaveClip.cpp b/src/WaveClip.cpp index 9d85cc16a..fcc9b0f93 100644 --- a/src/WaveClip.cpp +++ b/src/WaveClip.cpp @@ -395,6 +395,7 @@ WaveClip::~WaveClip() } void WaveClip::SetOffset(double offset) +// NOFAIL-GUARANTEE { mOffset = offset; mEnvelope->SetOffset(mOffset); @@ -1345,6 +1346,7 @@ void WaveClip::ConvertToSampleFormat(sampleFormat format) } void WaveClip::UpdateEnvelopeTrackLen() +// NOFAIL-GUARANTEE { mEnvelope->SetTrackLen((mSequence->GetNumSamples().as_double()) / mRate); } @@ -1378,6 +1380,9 @@ void WaveClip::GetDisplayRect(wxRect* r) bool WaveClip::Append(samplePtr buffer, sampleFormat format, size_t len, unsigned int stride /* = 1 */, XMLWriter* blockFileLog /*=NULL*/) +// PARTIAL-GUARANTEE in case of exceptions: +// Some prefix (maybe none) of the buffer is appended, and no content already +// flushed to disk is lost. { //wxLogDebug(wxT("Append: len=%lli"), (long long) len); @@ -1388,13 +1393,23 @@ bool WaveClip::Append(samplePtr buffer, sampleFormat format, if (!mAppendBuffer.ptr()) mAppendBuffer.Allocate(maxBlockSize, seqFormat); + auto cleanup = finally( [&] { + // use NOFAIL-GUARANTEE + UpdateEnvelopeTrackLen(); + MarkChanged(); + } ); + for(;;) { if (mAppendBufferLen >= blockSize) { bool success = + // flush some previously appended contents + // use STRONG-GUARANTEE mSequence->Append(mAppendBuffer.ptr(), seqFormat, blockSize, blockFileLog); if (!success) return false; + + // use NOFAIL-GUARANTEE for rest of this "if" memmove(mAppendBuffer.ptr(), mAppendBuffer.ptr() + blockSize * SAMPLE_SIZE(seqFormat), (mAppendBufferLen - blockSize) * SAMPLE_SIZE(seqFormat)); @@ -1405,6 +1420,7 @@ bool WaveClip::Append(samplePtr buffer, sampleFormat format, if (len == 0) break; + // use NOFAIL-GUARANTEE for rest of this "for" wxASSERT(mAppendBufferLen <= maxBlockSize); auto toCopy = std::min(len, maxBlockSize - mAppendBufferLen); @@ -1420,16 +1436,17 @@ bool WaveClip::Append(samplePtr buffer, sampleFormat format, len -= toCopy; } - UpdateEnvelopeTrackLen(); - MarkChanged(); - return true; } bool WaveClip::AppendAlias(const wxString &fName, sampleCount start, size_t len, int channel,bool useOD) +// STRONG-GUARANTEE { + // use STRONG-GUARANTEE bool result = mSequence->AppendAlias(fName, start, len, channel,useOD); + + // use NOFAIL-GUARANTEE if (result) { UpdateEnvelopeTrackLen(); @@ -1440,9 +1457,13 @@ bool WaveClip::AppendAlias(const wxString &fName, sampleCount start, bool WaveClip::AppendCoded(const wxString &fName, sampleCount start, size_t len, int channel, int decodeType) +// STRONG-GUARANTEE { + // use STRONG-GUARANTEE bool result = mSequence->AppendCoded(fName, start, len, channel, decodeType); - if (result) + + // use NOFAIL-GUARANTEE +if (result) { UpdateEnvelopeTrackLen(); MarkChanged(); @@ -1451,6 +1472,10 @@ bool WaveClip::AppendCoded(const wxString &fName, sampleCount start, } bool 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 +// clip gets appended; no previously flushed contents are lost. { //wxLogDebug(wxT("WaveClip::Flush")); //wxLogDebug(wxT(" mAppendBufferLen=%lli"), (long long) mAppendBufferLen); @@ -1458,12 +1483,18 @@ bool WaveClip::Flush() bool success = true; if (mAppendBufferLen > 0) { - success = mSequence->Append(mAppendBuffer.ptr(), mSequence->GetSampleFormat(), mAppendBufferLen); - if (success) { + + auto cleanup = finally( [&] { + // Blow away the append buffer even in case of failure. May lose some + // data but don't leave the track in an un-flushed state. + + // Use NOFAIL-GUARANTEE of these steps. mAppendBufferLen = 0; UpdateEnvelopeTrackLen(); MarkChanged(); - } + } ); + + success = mSequence->Append(mAppendBuffer.ptr(), mSequence->GetSampleFormat(), mAppendBufferLen); } //wxLogDebug(wxT("now sample count %lli"), (long long) mSequence->GetNumSamples()); @@ -1603,16 +1634,20 @@ bool WaveClip::Paste(double t0, const WaveClip* other) } bool WaveClip::InsertSilence(double t, double len) +// STRONG-GUARANTEE { sampleCount s0; TimeToSamplesClip(t, &s0); auto slen = (sampleCount)floor(len * mRate + 0.5); + // use STRONG-GUARANTEE if (!GetSequence()->InsertSilence(s0, slen)) { wxASSERT(false); return false; } + + // use NOFAIL-GUARANTEE OffsetCutLines(t, len); GetEnvelope()->InsertSpace(t, len); MarkChanged(); diff --git a/src/WaveClip.h b/src/WaveClip.h index 44ee6a65f..fc6e0ad43 100644 --- a/src/WaveClip.h +++ b/src/WaveClip.h @@ -237,7 +237,8 @@ public: void SetOffset(double offset); double GetOffset() const { return mOffset; } - void Offset(double delta) { SetOffset(GetOffset() + delta); } + void Offset(double delta) // NOFAIL-GUARANTEE + { SetOffset(GetOffset() + delta); } double GetStartTime() const; double GetEndTime() const; sampleCount GetStartSample() const; @@ -268,7 +269,8 @@ public: /** WaveTrack calls this whenever data in the wave clip changes. It is * called automatically when WaveClip has a chance to know that something * has changed, like when member functions SetSamples() etc. are called. */ - void MarkChanged() { mDirty++; } + void MarkChanged() // NOFAIL-GUARANTEE + { mDirty++; } /** Getting high-level data from the for screen display and clipping * calculations and Contrast */ diff --git a/src/WaveTrack.cpp b/src/WaveTrack.cpp index 80aeae2c3..9b89c52b7 100644 --- a/src/WaveTrack.cpp +++ b/src/WaveTrack.cpp @@ -188,10 +188,12 @@ double WaveTrack::GetOffset() const } void WaveTrack::SetOffset(double o) +// NOFAIL-GUARANTEE { double delta = o - GetOffset(); for (const auto &clip : mClips) + // assume NOFAIL-GUARANTEE clip->SetOffset(clip->GetOffset() + delta); mOffset = o; @@ -543,6 +545,7 @@ Track::Holder WaveTrack::Cut(double t0, double t1) } Track::Holder WaveTrack::SplitCut(double t0, double t1) +// STRONG-GUARANTEE { if (t1 < t0) //THROW_INCONSISTENCY_EXCEPTION @@ -703,11 +706,13 @@ Track::Holder WaveTrack::CopyNonconst(double t0, double t1) } void WaveTrack::Clear(double t0, double t1) +// STRONG-GUARANTEE { HandleClear(t0, t1, false, false); } void WaveTrack::ClearAndAddCutLine(double t0, double t1) +// STRONG-GUARANTEE { HandleClear(t0, t1, true, false); } @@ -953,6 +958,7 @@ void WaveTrack::ClearAndPaste(double t0, // Start of time to clear } void WaveTrack::SplitDelete(double t0, double t1) +// STRONG-GUARANTEE { bool addCutLines = false; bool split = true; @@ -1016,6 +1022,7 @@ void WaveTrack::AddClip(movable_ptr &&clip) void WaveTrack::HandleClear(double t0, double t1, bool addCutLines, bool split) +// STRONG-GUARANTEE { if (t1 < t0) // THROW_INCONSISTENCY_EXCEPTION; // ? @@ -1054,7 +1061,12 @@ void WaveTrack::HandleClear(double t0, double t1, // Clip data is affected by command if (addCutLines) { - clip->ClearAndAddCutLine(t0,t1); + // Don't modify this clip in place, because we want a strong + // guarantee, and might modify another clip + clipsToDelete.push_back( clip.get() ); + auto newClip = make_movable( *clip, mDirManager, true ); + newClip->ClearAndAddCutLine( t0, t1 ); + clipsToAdd.push_back( std::move( newClip ) ); } else { @@ -1063,14 +1075,28 @@ void WaveTrack::HandleClear(double t0, double t1, if (clip->BeforeClip(t0)) { // Delete from the left edge - clip->Clear(clip->GetStartTime(), t1); - clip->Offset(t1-clip->GetStartTime()); - } else - if (clip->AfterClip(t1)) { + + // Don't modify this clip in place, because we want a strong + // guarantee, and might modify another clip + clipsToDelete.push_back( clip.get() ); + auto newClip = make_movable( *clip, mDirManager, true ); + newClip->Clear(clip->GetStartTime(), t1); + newClip->Offset(t1-clip->GetStartTime()); + + clipsToAdd.push_back( std::move( newClip ) ); + } + else if (clip->AfterClip(t1)) { // Delete to right edge - clip->Clear(t0, clip->GetEndTime()); - } else - { + + // Don't modify this clip in place, because we want a strong + // guarantee, and might modify another clip + clipsToDelete.push_back( clip.get() ); + auto newClip = make_movable( *clip, mDirManager, true ); + newClip->Clear(t0, clip->GetEndTime()); + + clipsToAdd.push_back( std::move( newClip ) ); + } + else { // Delete in the middle of the clip...we actually create two // NEW clips out of the left and right halves... @@ -1089,7 +1115,14 @@ void WaveTrack::HandleClear(double t0, double t1, clipsToDelete.push_back(clip.get()); } } - else { // (We are not doing a split cut) + else { + // (We are not doing a split cut) + + // Don't modify this clip in place, because we want a strong + // guarantee, and might modify another clip + clipsToDelete.push_back( clip.get() ); + auto newClip = make_movable( *clip, mDirManager, true ); + /* We are going to DELETE part of the clip here. The clip may * have envelope points, and we need to ensure that the envelope * outside of the cleared region is not affected. This means @@ -1099,23 +1132,32 @@ void WaveTrack::HandleClear(double t0, double t1, // clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion if (clip->GetEnvelope()->GetNumberOfPoints() > 0) { // don't insert env pts if none exist double val; - if (clip->WithinClip(t0)) - { // start of region within clip + if (clip->WithinClip(t0)) { + // start of region within clip val = clip->GetEnvelope()->GetValue(t0); - clip->GetEnvelope()->Insert(t0 - clip->GetOffset() - 1.0/clip->GetRate(), val); - } - if (clip->WithinClip(t1)) - { // end of region within clip + newClip->GetEnvelope()->Insert(t0 - clip->GetOffset() - 1.0/clip->GetRate(), val); + } + if (clip->WithinClip(t1)) { + // end of region within clip val = clip->GetEnvelope()->GetValue(t1); - clip->GetEnvelope()->Insert(t1 - clip->GetOffset(), val); - } + newClip->GetEnvelope()->Insert(t1 - clip->GetOffset(), val); + } } - if (!clip->Clear(t0,t1)) + if (!newClip->Clear(t0,t1)) return; - clip->GetEnvelope()->RemoveUnneededPoints(t0); + newClip->GetEnvelope()->RemoveUnneededPoints(t0); + + clipsToAdd.push_back( std::move( newClip ) ); } } - } else + } + } + + // Only now, change the contents of this track + // use NOFAIL-GUARANTEE for the rest + + for (const auto &clip : mClips) + { if (clip->BeforeClip(t1)) { // Clip is "behind" the region -- offset it unless we're splitting @@ -1379,6 +1421,7 @@ void WaveTrack::Silence(double t0, double t1) } void WaveTrack::InsertSilence(double t, double len) +// STRONG-GUARANTEE { if (len <= 0) // THROW_INCONSISTENCY_EXCEPTION; // ? @@ -1387,20 +1430,28 @@ void WaveTrack::InsertSilence(double t, double len) if (mClips.empty()) { // Special case if there is no clip yet - WaveClip* clip = CreateClip(); + auto clip = make_movable(mDirManager, mFormat, mRate); clip->InsertSilence(0, len); + // use NOFAIL-GUARANTEE + mClips.push_back( std::move( clip ) ); return; } + else { + // Assume at most one clip contains t + const auto end = mClips.end(); + const auto it = std::find_if( mClips.begin(), end, + [&](const WaveClipHolder &clip) { return clip->WithinClip(t); } ); - for (const auto &clip : mClips) - { - if (clip->BeforeClip(t)) - clip->Offset(len); - else if (clip->WithinClip(t)) - { - if (!clip->InsertSilence(t, len)) { + // use STRONG-GUARANTEE + if (it != end) + if(!it->get()->InsertSilence(t, len)) return; - } + + // use NOFAIL-GUARANTEE + for (const auto &clip : mClips) + { + if (clip->BeforeClip(t)) + clip->Offset(len); } } } @@ -1541,6 +1592,9 @@ void WaveTrack::Join(double t0, double t1) void WaveTrack::Append(samplePtr buffer, sampleFormat format, size_t len, unsigned int stride /* = 1 */, XMLWriter *blockFileLog /* = NULL */) +// PARTIAL-GUARANTEE in case of exceptions: +// Some prefix (maybe none) of the buffer is appended, and no content already +// flushed to disk is lost. { RightmostOrNewClip()->Append(buffer, format, len, stride, blockFileLog); @@ -1548,12 +1602,14 @@ void WaveTrack::Append(samplePtr buffer, sampleFormat format, void WaveTrack::AppendAlias(const wxString &fName, sampleCount start, size_t len, int channel,bool useOD) +// STRONG-GUARANTEE { RightmostOrNewClip()->AppendAlias(fName, start, len, channel, useOD); } void WaveTrack::AppendCoded(const wxString &fName, sampleCount start, size_t len, int channel, int decodeType) +// STRONG-GUARANTEE { RightmostOrNewClip()->AppendCoded(fName, start, len, channel, decodeType); } @@ -1627,6 +1683,10 @@ size_t WaveTrack::GetIdealBlockSize() } void WaveTrack::Flush() +// NOFAIL-GUARANTEE that the rightmost clip will be in a flushed state. +// PARTIAL-GUARANTEE in case of exceptions: +// Some initial portion (maybe none) of the append buffer of the rightmost +// clip gets appended; no previously saved contents are lost. { // After appending, presumably. Do this to the clip that gets appended. RightmostOrNewClip()->Flush(); @@ -2203,6 +2263,7 @@ WaveClip* WaveTrack::NewestOrNewClip() } WaveClip* WaveTrack::RightmostOrNewClip() +// NOFAIL-GUARANTEE { if (mClips.empty()) { WaveClip *clip = CreateClip();