diff --git a/src/toolbars/ControlToolBar.cpp b/src/toolbars/ControlToolBar.cpp index 8bf3e7bec..5bb0e9133 100644 --- a/src/toolbars/ControlToolBar.cpp +++ b/src/toolbars/ControlToolBar.cpp @@ -508,6 +508,7 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, PlayAppearance appearance, /* = PlayOption::Straight */ bool backwards, /* = false */ bool playWhiteSpace /* = false */) +// STRONG-GUARANTEE (for state of mCutPreviewTracks) { if (!CanStopAudioStream()) return -1; @@ -526,28 +527,29 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, SetPlay(true, appearance); - if (gAudioIO->IsBusy()) { - SetPlay(false); + bool success = false; + auto cleanup = finally( [&] { + if (!success) { + SetPlay(false); + SetStop(false); + SetRecord(false); + } + } ); + + if (gAudioIO->IsBusy()) return -1; - } const bool cutpreview = appearance == PlayAppearance::CutPreview; - if (cutpreview && t0==t1) { - SetPlay(false); + if (cutpreview && t0==t1) return -1; /* msmeyer: makes no sense */ - } AudacityProject *p = GetActiveProject(); - if (!p) { - SetPlay(false); + if (!p) return -1; // Should never happen, but... - } TrackList *t = p->GetTracks(); - if (!t) { - mPlay->PopUp(); + if (!t) return -1; // Should never happen, but... - } p->mLastPlayMode = mode; @@ -566,10 +568,8 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, double latestEnd = (playWhiteSpace)? t1 : t->GetEndTime(); - if (!hasaudio) { - SetPlay(false); + if (!hasaudio) return -1; // No need to continue without audio tracks - } #if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR) double init_seek = 0.0; @@ -620,7 +620,7 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, } int token = -1; - bool success = false; + if (t1 != t0) { if (cutpreview) { const double tless = std::min(t0, t1); @@ -647,13 +647,9 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, #endif tcp0, tcp1, myOptions); } - else { + else // Cannot create cut preview tracks, clean up and exit - SetPlay(false); - SetStop(false); - SetRecord(false); return -1; - } } else { // Lifted the following into AudacityProject::GetDefaultPlayOptions() @@ -687,12 +683,8 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion, } } - if (!success) { - SetPlay(false); - SetStop(false); - SetRecord(false); + if (!success) return -1; - } StartScrollingIfPreferred(); @@ -768,8 +760,8 @@ void ControlToolBar::OnPlay(wxCommandEvent & WXUNUSED(evt)) if (p) p->TP_DisplaySelection(); + auto cleanup = finally( [&]{ UpdateStatusBar(p); } ); PlayDefault(); - UpdateStatusBar(p); } void ControlToolBar::OnStop(wxCommandEvent & WXUNUSED(evt)) @@ -861,6 +853,7 @@ void ControlToolBar::Pause() } void ControlToolBar::OnRecord(wxCommandEvent &evt) +// STRONG-GUARANTEE (for state of current project's tracks) { if (gAudioIO->IsBusy()) { if (!CanStopAudioStream() || 0 == gAudioIO->GetNumCaptureChannels()) @@ -878,14 +871,43 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt) SetRecord(true, mRecord->WasShiftDown()); + bool success = false; + + bool shifted = mRecord->WasShiftDown(); +#ifdef EXPERIMENTAL_DA + shifted = !shifted; +#endif + + TrackList *trackList = p->GetTracks(); + TrackList tracksCopy{}; + bool tracksCopied = false; + + WaveTrackArray recordingTracks; + + auto cleanup = finally( [&] { + if (!success) { + if (tracksCopied) + // Restore the tracks to remove any inserted silence + *trackList = std::move(tracksCopy); + + if ( ! shifted ) { + // msmeyer: Delete recently added tracks if opening stream fails + for ( auto track : recordingTracks ) + trackList->Remove(track); + } + + SetPlay(false); + SetStop(false); + SetRecord(false); + } + + // Success or not: + UpdateStatusBar(GetActiveProject()); + } ); + if (p) { - TrackList *trackList = p->GetTracks(); TrackListIterator it(trackList); - bool shifted = mRecord->WasShiftDown(); -#ifdef EXPERIMENTAL_DA - shifted = !shifted; -#endif bool hasWave = false; for (auto t = it.First(); t; t = it.Next()) { if (t->GetKind() == Track::Wave) { @@ -905,7 +927,6 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt) /* TODO: set up stereo tracks if that is how the user has set up * their preferences, and choose sample format based on prefs */ - WaveTrackArray newRecordingTracks; WaveTrackConstArray playbackTracks; #ifdef EXPERIMENTAL_MIDI_OUT NoteTrackArray midiTracks; @@ -928,8 +949,6 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt) // If SHIFT key was down, the user wants append to tracks int recordingChannels = 0; - TrackList tracksCopy{}; - bool tracksCopied = false; if (shifted) { recordingChannels = gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2); @@ -975,10 +994,15 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt) t1 = wt->GetEndTime(); if (t1 < t0) { if (!tracksCopied) { + // Duplicate all tracks before modifying any of them. + // The duplicates are used to restore state in case + // of failure. tracksCopied = true; tracksCopy = *trackList; } + // Pad the recording track with silence, up to the + // maximum time. auto newTrack = p->GetTrackFactory()->NewWaveTrack(); newTrack->InsertSilence(0.0, t0 - t1); newTrack->Flush(); @@ -987,9 +1011,9 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt) wxASSERT(bResult); // TO DO: Actually handle this. wxUnusedVar(bResult); } - newRecordingTracks.push_back(wt); + recordingTracks.push_back(wt); // Don't record more channels than configured recording pref. - if( (int)newRecordingTracks.size() >= recordingChannels ){ + if( (int)recordingTracks.size() >= recordingChannels ){ break; } } @@ -1076,7 +1100,7 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt) } // Let the list hold the track, and keep a pointer to it - newRecordingTracks.push_back( + recordingTracks.push_back( static_cast<WaveTrack*>( trackList->Add( std::move(newTrack)))); @@ -1090,13 +1114,13 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt) AudioIOStartStreamOptions options(p->GetDefaultPlayOptions()); int token = gAudioIO->StartStream(playbackTracks, - newRecordingTracks, + recordingTracks, #ifdef EXPERIMENTAL_MIDI_OUT midiTracks, #endif t0, t1, options); - bool success = (token != 0); + success = (token != 0); if (success) { p->SetAudioIOToken(token); @@ -1105,28 +1129,11 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt) StartScrollingIfPreferred(); } else { - if (shifted) { - // Restore the tracks to remove any inserted silence - if (tracksCopied) - *trackList = std::move(tracksCopy); - } - else { - // msmeyer: Delete recently added tracks if opening stream fails - for (unsigned int i = 0; i < newRecordingTracks.size(); i++) { - trackList->Remove(newRecordingTracks[i]); - } - } - // msmeyer: Show error message if stream could not be opened wxMessageBox(_("Error opening sound device. Try changing the audio host, recording device and the project sample rate."), _("Error"), wxOK | wxICON_EXCLAMATION, this); - - SetPlay(false); - SetStop(false); - SetRecord(false); } } - UpdateStatusBar(GetActiveProject()); } @@ -1187,6 +1194,8 @@ void ControlToolBar::OnFF(wxCommandEvent & WXUNUSED(evt)) void ControlToolBar::SetupCutPreviewTracks(double WXUNUSED(playStart), double cutStart, double cutEnd, double WXUNUSED(playEnd)) + +// STRONG-GUARANTEE (for state of mCutPreviewTracks) { ClearCutPreviewTracks(); AudacityProject *p = GetActiveProject(); @@ -1207,6 +1216,7 @@ void ControlToolBar::SetupCutPreviewTracks(double WXUNUSED(playStart), double cu if (track1) { // Duplicate and change tracks + // Clear has a very small chance of throwing auto new1 = track1->Duplicate(); new1->Clear(cutStart, cutEnd); decltype(new1) new2{}; @@ -1216,6 +1226,8 @@ void ControlToolBar::SetupCutPreviewTracks(double WXUNUSED(playStart), double cu new2->Clear(cutStart, cutEnd); } + // use NOTHROW-GUARANTEE: + mCutPreviewTracks = std::make_unique<TrackList>(); mCutPreviewTracks->Add(std::move(new1)); if (track2) diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp index 04d59df1c..eab3a30b4 100644 --- a/src/widgets/Ruler.cpp +++ b/src/widgets/Ruler.cpp @@ -2672,19 +2672,21 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt) ClearPlayRegion(); } - StartQPPlay(evt.ShiftDown(), evt.ControlDown()); - mMouseEventState = mesNone; mIsDragging = false; mLeftDownClick = -1; - if (mPlayRegionLock) { - // Restore Locked Play region - SetPlayRegion(mOldPlayRegionStart, mOldPlayRegionEnd); - mProject->OnLockPlayRegion(); - // and release local lock - mPlayRegionLock = false; - } + auto cleanup = finally( [&] { + if (mPlayRegionLock) { + // Restore Locked Play region + SetPlayRegion(mOldPlayRegionStart, mOldPlayRegionEnd); + mProject->OnLockPlayRegion(); + // and release local lock + mPlayRegionLock = false; + } + } ); + + StartQPPlay(evt.ShiftDown(), evt.ControlDown()); } void AdornedRulerPanel::StartQPPlay(bool looped, bool cutPreview) @@ -2732,18 +2734,20 @@ void AdornedRulerPanel::StartQPPlay(bool looped, bool cutPreview) options.timeTrack = NULL; ControlToolBar::PlayAppearance appearance = - cutPreview ? ControlToolBar::PlayAppearance::CutPreview - : options.playLooped ? ControlToolBar::PlayAppearance::Looped - : ControlToolBar::PlayAppearance::Straight; + cutPreview ? ControlToolBar::PlayAppearance::CutPreview + : options.playLooped ? ControlToolBar::PlayAppearance::Looped + : ControlToolBar::PlayAppearance::Straight; + + mPlayRegionStart = start; + mPlayRegionEnd = end; + Refresh(); + ctb->PlayPlayRegion((SelectedRegion(start, end)), options, PlayMode::normalPlay, appearance, false, true); - mPlayRegionStart = start; - mPlayRegionEnd = end; - Refresh(); } }