1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-06 14:52:34 +02:00

Implement the more than weak guarantees needed for recording...

... in WaveClip and WaveTrack, to save as much recording as we can,
assuming the strong guarantees that Sequence will give.

Also comment that some other WaveTrack methods can give strong guarantee,
incidentally to making HandleClear give strong.
This commit is contained in:
Paul Licameli 2017-04-01 22:20:38 -04:00
parent e3d4a8dcfe
commit aa83c4cf29
4 changed files with 152 additions and 38 deletions

View File

@ -2449,8 +2449,20 @@ void AudioIO::StopStream()
// Stop those exceptions here, or else they propagate through too // Stop those exceptions here, or else they propagate through too
// many parts of Audacity that are not effects or editing // many parts of Audacity that are not effects or editing
// operations. GuardedCall ensures that the user sees a warning. // 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<void>( [&] { GuardedCall<void>( [&] {
WaveTrack* track = mCaptureTracks[i]; 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(); track->Flush();
if (mPlaybackTracks.size() > 0) if (mPlaybackTracks.size() > 0)
@ -2475,12 +2487,15 @@ void AudioIO::StopStream()
if( appendRecord ) if( appendRecord )
{ // append-recording { // append-recording
if (recordingOffset < 0) if (recordingOffset < 0)
// use STRONG-GUARANTEE
track->Clear(mT0, mT0 - recordingOffset); // cut the latency out track->Clear(mT0, mT0 - recordingOffset); // cut the latency out
else else
// use STRONG-GUARANTEE
track->InsertSilence(mT0, recordingOffset); // put silence in track->InsertSilence(mT0, recordingOffset); // put silence in
} }
else else
{ // recording into a NEW track { // recording into a NEW track
// gives NOFAIL-GUARANTEE though we only need STRONG
track->SetOffset(track->GetStartTime() + recordingOffset); track->SetOffset(track->GetStartTime() + recordingOffset);
if(track->GetEndTime() < 0.) 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 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); _("Latency problem"), wxOK);
m.ShowModal(); m.ShowModal();
// gives NOFAIL-GUARANTEE though we only need STRONG
track->SetOffset(0.); track->SetOffset(0.);
} }
} }

View File

@ -395,6 +395,7 @@ WaveClip::~WaveClip()
} }
void WaveClip::SetOffset(double offset) void WaveClip::SetOffset(double offset)
// NOFAIL-GUARANTEE
{ {
mOffset = offset; mOffset = offset;
mEnvelope->SetOffset(mOffset); mEnvelope->SetOffset(mOffset);
@ -1345,6 +1346,7 @@ void WaveClip::ConvertToSampleFormat(sampleFormat format)
} }
void WaveClip::UpdateEnvelopeTrackLen() void WaveClip::UpdateEnvelopeTrackLen()
// NOFAIL-GUARANTEE
{ {
mEnvelope->SetTrackLen((mSequence->GetNumSamples().as_double()) / mRate); mEnvelope->SetTrackLen((mSequence->GetNumSamples().as_double()) / mRate);
} }
@ -1378,6 +1380,9 @@ void WaveClip::GetDisplayRect(wxRect* r)
bool WaveClip::Append(samplePtr buffer, sampleFormat format, bool WaveClip::Append(samplePtr buffer, sampleFormat format,
size_t len, unsigned int stride /* = 1 */, size_t len, unsigned int stride /* = 1 */,
XMLWriter* blockFileLog /*=NULL*/) 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); //wxLogDebug(wxT("Append: len=%lli"), (long long) len);
@ -1388,13 +1393,23 @@ bool WaveClip::Append(samplePtr buffer, sampleFormat format,
if (!mAppendBuffer.ptr()) if (!mAppendBuffer.ptr())
mAppendBuffer.Allocate(maxBlockSize, seqFormat); mAppendBuffer.Allocate(maxBlockSize, seqFormat);
auto cleanup = finally( [&] {
// use NOFAIL-GUARANTEE
UpdateEnvelopeTrackLen();
MarkChanged();
} );
for(;;) { for(;;) {
if (mAppendBufferLen >= blockSize) { if (mAppendBufferLen >= blockSize) {
bool success = bool success =
// flush some previously appended contents
// use STRONG-GUARANTEE
mSequence->Append(mAppendBuffer.ptr(), seqFormat, blockSize, mSequence->Append(mAppendBuffer.ptr(), seqFormat, blockSize,
blockFileLog); blockFileLog);
if (!success) if (!success)
return false; return false;
// use NOFAIL-GUARANTEE for rest of this "if"
memmove(mAppendBuffer.ptr(), memmove(mAppendBuffer.ptr(),
mAppendBuffer.ptr() + blockSize * SAMPLE_SIZE(seqFormat), mAppendBuffer.ptr() + blockSize * SAMPLE_SIZE(seqFormat),
(mAppendBufferLen - blockSize) * SAMPLE_SIZE(seqFormat)); (mAppendBufferLen - blockSize) * SAMPLE_SIZE(seqFormat));
@ -1405,6 +1420,7 @@ bool WaveClip::Append(samplePtr buffer, sampleFormat format,
if (len == 0) if (len == 0)
break; break;
// use NOFAIL-GUARANTEE for rest of this "for"
wxASSERT(mAppendBufferLen <= maxBlockSize); wxASSERT(mAppendBufferLen <= maxBlockSize);
auto toCopy = std::min(len, maxBlockSize - mAppendBufferLen); auto toCopy = std::min(len, maxBlockSize - mAppendBufferLen);
@ -1420,16 +1436,17 @@ bool WaveClip::Append(samplePtr buffer, sampleFormat format,
len -= toCopy; len -= toCopy;
} }
UpdateEnvelopeTrackLen();
MarkChanged();
return true; return true;
} }
bool WaveClip::AppendAlias(const wxString &fName, sampleCount start, bool WaveClip::AppendAlias(const wxString &fName, sampleCount start,
size_t len, int channel,bool useOD) size_t len, int channel,bool useOD)
// STRONG-GUARANTEE
{ {
// use STRONG-GUARANTEE
bool result = mSequence->AppendAlias(fName, start, len, channel,useOD); bool result = mSequence->AppendAlias(fName, start, len, channel,useOD);
// use NOFAIL-GUARANTEE
if (result) if (result)
{ {
UpdateEnvelopeTrackLen(); UpdateEnvelopeTrackLen();
@ -1440,9 +1457,13 @@ bool WaveClip::AppendAlias(const wxString &fName, sampleCount start,
bool WaveClip::AppendCoded(const wxString &fName, sampleCount start, bool WaveClip::AppendCoded(const wxString &fName, sampleCount start,
size_t len, int channel, int decodeType) size_t len, int channel, int decodeType)
// STRONG-GUARANTEE
{ {
// use STRONG-GUARANTEE
bool result = mSequence->AppendCoded(fName, start, len, channel, decodeType); bool result = mSequence->AppendCoded(fName, start, len, channel, decodeType);
if (result)
// use NOFAIL-GUARANTEE
if (result)
{ {
UpdateEnvelopeTrackLen(); UpdateEnvelopeTrackLen();
MarkChanged(); MarkChanged();
@ -1451,6 +1472,10 @@ bool WaveClip::AppendCoded(const wxString &fName, sampleCount start,
} }
bool WaveClip::Flush() 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("WaveClip::Flush"));
//wxLogDebug(wxT(" mAppendBufferLen=%lli"), (long long) mAppendBufferLen); //wxLogDebug(wxT(" mAppendBufferLen=%lli"), (long long) mAppendBufferLen);
@ -1458,12 +1483,18 @@ bool WaveClip::Flush()
bool success = true; bool success = true;
if (mAppendBufferLen > 0) { 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; mAppendBufferLen = 0;
UpdateEnvelopeTrackLen(); UpdateEnvelopeTrackLen();
MarkChanged(); MarkChanged();
} } );
success = mSequence->Append(mAppendBuffer.ptr(), mSequence->GetSampleFormat(), mAppendBufferLen);
} }
//wxLogDebug(wxT("now sample count %lli"), (long long) mSequence->GetNumSamples()); //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) bool WaveClip::InsertSilence(double t, double len)
// STRONG-GUARANTEE
{ {
sampleCount s0; sampleCount s0;
TimeToSamplesClip(t, &s0); TimeToSamplesClip(t, &s0);
auto slen = (sampleCount)floor(len * mRate + 0.5); auto slen = (sampleCount)floor(len * mRate + 0.5);
// use STRONG-GUARANTEE
if (!GetSequence()->InsertSilence(s0, slen)) if (!GetSequence()->InsertSilence(s0, slen))
{ {
wxASSERT(false); wxASSERT(false);
return false; return false;
} }
// use NOFAIL-GUARANTEE
OffsetCutLines(t, len); OffsetCutLines(t, len);
GetEnvelope()->InsertSpace(t, len); GetEnvelope()->InsertSpace(t, len);
MarkChanged(); MarkChanged();

View File

@ -237,7 +237,8 @@ public:
void SetOffset(double offset); void SetOffset(double offset);
double GetOffset() const { return mOffset; } 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 GetStartTime() const;
double GetEndTime() const; double GetEndTime() const;
sampleCount GetStartSample() const; sampleCount GetStartSample() const;
@ -268,7 +269,8 @@ public:
/** WaveTrack calls this whenever data in the wave clip changes. It is /** WaveTrack calls this whenever data in the wave clip changes. It is
* called automatically when WaveClip has a chance to know that something * called automatically when WaveClip has a chance to know that something
* has changed, like when member functions SetSamples() etc. are called. */ * 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 /** Getting high-level data from the for screen display and clipping
* calculations and Contrast */ * calculations and Contrast */

View File

@ -188,10 +188,12 @@ double WaveTrack::GetOffset() const
} }
void WaveTrack::SetOffset(double o) void WaveTrack::SetOffset(double o)
// NOFAIL-GUARANTEE
{ {
double delta = o - GetOffset(); double delta = o - GetOffset();
for (const auto &clip : mClips) for (const auto &clip : mClips)
// assume NOFAIL-GUARANTEE
clip->SetOffset(clip->GetOffset() + delta); clip->SetOffset(clip->GetOffset() + delta);
mOffset = o; mOffset = o;
@ -543,6 +545,7 @@ Track::Holder WaveTrack::Cut(double t0, double t1)
} }
Track::Holder WaveTrack::SplitCut(double t0, double t1) Track::Holder WaveTrack::SplitCut(double t0, double t1)
// STRONG-GUARANTEE
{ {
if (t1 < t0) if (t1 < t0)
//THROW_INCONSISTENCY_EXCEPTION //THROW_INCONSISTENCY_EXCEPTION
@ -703,11 +706,13 @@ Track::Holder WaveTrack::CopyNonconst(double t0, double t1)
} }
void WaveTrack::Clear(double t0, double t1) void WaveTrack::Clear(double t0, double t1)
// STRONG-GUARANTEE
{ {
HandleClear(t0, t1, false, false); HandleClear(t0, t1, false, false);
} }
void WaveTrack::ClearAndAddCutLine(double t0, double t1) void WaveTrack::ClearAndAddCutLine(double t0, double t1)
// STRONG-GUARANTEE
{ {
HandleClear(t0, t1, true, false); 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) void WaveTrack::SplitDelete(double t0, double t1)
// STRONG-GUARANTEE
{ {
bool addCutLines = false; bool addCutLines = false;
bool split = true; bool split = true;
@ -1016,6 +1022,7 @@ void WaveTrack::AddClip(movable_ptr<WaveClip> &&clip)
void WaveTrack::HandleClear(double t0, double t1, void WaveTrack::HandleClear(double t0, double t1,
bool addCutLines, bool split) bool addCutLines, bool split)
// STRONG-GUARANTEE
{ {
if (t1 < t0) if (t1 < t0)
// THROW_INCONSISTENCY_EXCEPTION; // ? // THROW_INCONSISTENCY_EXCEPTION; // ?
@ -1054,7 +1061,12 @@ void WaveTrack::HandleClear(double t0, double t1,
// Clip data is affected by command // Clip data is affected by command
if (addCutLines) 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<WaveClip>( *clip, mDirManager, true );
newClip->ClearAndAddCutLine( t0, t1 );
clipsToAdd.push_back( std::move( newClip ) );
} }
else else
{ {
@ -1063,14 +1075,28 @@ void WaveTrack::HandleClear(double t0, double t1,
if (clip->BeforeClip(t0)) { if (clip->BeforeClip(t0)) {
// Delete from the left edge // Delete from the left edge
clip->Clear(clip->GetStartTime(), t1);
clip->Offset(t1-clip->GetStartTime()); // Don't modify this clip in place, because we want a strong
} else // guarantee, and might modify another clip
if (clip->AfterClip(t1)) { clipsToDelete.push_back( clip.get() );
auto newClip = make_movable<WaveClip>( *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 // 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<WaveClip>( *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 // Delete in the middle of the clip...we actually create two
// NEW clips out of the left and right halves... // 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()); 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<WaveClip>( *clip, mDirManager, true );
/* We are going to DELETE part of the clip here. The clip may /* We are going to DELETE part of the clip here. The clip may
* have envelope points, and we need to ensure that the envelope * have envelope points, and we need to ensure that the envelope
* outside of the cleared region is not affected. This means * 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 // clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion
if (clip->GetEnvelope()->GetNumberOfPoints() > 0) { // don't insert env pts if none exist if (clip->GetEnvelope()->GetNumberOfPoints() > 0) { // don't insert env pts if none exist
double val; double val;
if (clip->WithinClip(t0)) if (clip->WithinClip(t0)) {
{ // start of region within clip // start of region within clip
val = clip->GetEnvelope()->GetValue(t0); val = clip->GetEnvelope()->GetValue(t0);
clip->GetEnvelope()->Insert(t0 - clip->GetOffset() - 1.0/clip->GetRate(), val); newClip->GetEnvelope()->Insert(t0 - clip->GetOffset() - 1.0/clip->GetRate(), val);
} }
if (clip->WithinClip(t1)) if (clip->WithinClip(t1)) {
{ // end of region within clip // end of region within clip
val = clip->GetEnvelope()->GetValue(t1); 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; 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)) if (clip->BeforeClip(t1))
{ {
// Clip is "behind" the region -- offset it unless we're splitting // 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) void WaveTrack::InsertSilence(double t, double len)
// STRONG-GUARANTEE
{ {
if (len <= 0) if (len <= 0)
// THROW_INCONSISTENCY_EXCEPTION; // ? // THROW_INCONSISTENCY_EXCEPTION; // ?
@ -1387,20 +1430,28 @@ void WaveTrack::InsertSilence(double t, double len)
if (mClips.empty()) if (mClips.empty())
{ {
// Special case if there is no clip yet // Special case if there is no clip yet
WaveClip* clip = CreateClip(); auto clip = make_movable<WaveClip>(mDirManager, mFormat, mRate);
clip->InsertSilence(0, len); clip->InsertSilence(0, len);
// use NOFAIL-GUARANTEE
mClips.push_back( std::move( clip ) );
return; 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) // use STRONG-GUARANTEE
{ if (it != end)
if (clip->BeforeClip(t)) if(!it->get()->InsertSilence(t, len))
clip->Offset(len);
else if (clip->WithinClip(t))
{
if (!clip->InsertSilence(t, len)) {
return; 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, void WaveTrack::Append(samplePtr buffer, sampleFormat format,
size_t len, unsigned int stride /* = 1 */, size_t len, unsigned int stride /* = 1 */,
XMLWriter *blockFileLog /* = NULL */) 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, RightmostOrNewClip()->Append(buffer, format, len, stride,
blockFileLog); blockFileLog);
@ -1548,12 +1602,14 @@ void WaveTrack::Append(samplePtr buffer, sampleFormat format,
void WaveTrack::AppendAlias(const wxString &fName, sampleCount start, void WaveTrack::AppendAlias(const wxString &fName, sampleCount start,
size_t len, int channel,bool useOD) size_t len, int channel,bool useOD)
// STRONG-GUARANTEE
{ {
RightmostOrNewClip()->AppendAlias(fName, start, len, channel, useOD); RightmostOrNewClip()->AppendAlias(fName, start, len, channel, useOD);
} }
void WaveTrack::AppendCoded(const wxString &fName, sampleCount start, void WaveTrack::AppendCoded(const wxString &fName, sampleCount start,
size_t len, int channel, int decodeType) size_t len, int channel, int decodeType)
// STRONG-GUARANTEE
{ {
RightmostOrNewClip()->AppendCoded(fName, start, len, channel, decodeType); RightmostOrNewClip()->AppendCoded(fName, start, len, channel, decodeType);
} }
@ -1627,6 +1683,10 @@ size_t WaveTrack::GetIdealBlockSize()
} }
void WaveTrack::Flush() 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. // After appending, presumably. Do this to the clip that gets appended.
RightmostOrNewClip()->Flush(); RightmostOrNewClip()->Flush();
@ -2203,6 +2263,7 @@ WaveClip* WaveTrack::NewestOrNewClip()
} }
WaveClip* WaveTrack::RightmostOrNewClip() WaveClip* WaveTrack::RightmostOrNewClip()
// NOFAIL-GUARANTEE
{ {
if (mClips.empty()) { if (mClips.empty()) {
WaveClip *clip = CreateClip(); WaveClip *clip = CreateClip();