1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-10 09:01:13 +02:00

Exception safety in: ControlToolBar & Ruler functions...

... because SetupCutPreviewTracks has a small chance of throwing for want
of disk space.

StopStream however is considered nonthrowing.
This commit is contained in:
Paul Licameli 2016-12-03 09:29:58 -05:00
parent 0bb6a3d971
commit 2cbdd1cc43
2 changed files with 88 additions and 72 deletions

View File

@ -508,6 +508,7 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
PlayAppearance appearance, /* = PlayOption::Straight */ PlayAppearance appearance, /* = PlayOption::Straight */
bool backwards, /* = false */ bool backwards, /* = false */
bool playWhiteSpace /* = false */) bool playWhiteSpace /* = false */)
// STRONG-GUARANTEE (for state of mCutPreviewTracks)
{ {
if (!CanStopAudioStream()) if (!CanStopAudioStream())
return -1; return -1;
@ -526,28 +527,29 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
SetPlay(true, appearance); SetPlay(true, appearance);
if (gAudioIO->IsBusy()) { bool success = false;
SetPlay(false); auto cleanup = finally( [&] {
if (!success) {
SetPlay(false);
SetStop(false);
SetRecord(false);
}
} );
if (gAudioIO->IsBusy())
return -1; return -1;
}
const bool cutpreview = appearance == PlayAppearance::CutPreview; const bool cutpreview = appearance == PlayAppearance::CutPreview;
if (cutpreview && t0==t1) { if (cutpreview && t0==t1)
SetPlay(false);
return -1; /* msmeyer: makes no sense */ return -1; /* msmeyer: makes no sense */
}
AudacityProject *p = GetActiveProject(); AudacityProject *p = GetActiveProject();
if (!p) { if (!p)
SetPlay(false);
return -1; // Should never happen, but... return -1; // Should never happen, but...
}
TrackList *t = p->GetTracks(); TrackList *t = p->GetTracks();
if (!t) { if (!t)
mPlay->PopUp();
return -1; // Should never happen, but... return -1; // Should never happen, but...
}
p->mLastPlayMode = mode; p->mLastPlayMode = mode;
@ -566,10 +568,8 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
double latestEnd = (playWhiteSpace)? t1 : t->GetEndTime(); double latestEnd = (playWhiteSpace)? t1 : t->GetEndTime();
if (!hasaudio) { if (!hasaudio)
SetPlay(false);
return -1; // No need to continue without audio tracks return -1; // No need to continue without audio tracks
}
#if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR) #if defined(EXPERIMENTAL_SEEK_BEHIND_CURSOR)
double init_seek = 0.0; double init_seek = 0.0;
@ -620,7 +620,7 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
} }
int token = -1; int token = -1;
bool success = false;
if (t1 != t0) { if (t1 != t0) {
if (cutpreview) { if (cutpreview) {
const double tless = std::min(t0, t1); const double tless = std::min(t0, t1);
@ -647,13 +647,9 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
#endif #endif
tcp0, tcp1, myOptions); tcp0, tcp1, myOptions);
} }
else { else
// Cannot create cut preview tracks, clean up and exit // Cannot create cut preview tracks, clean up and exit
SetPlay(false);
SetStop(false);
SetRecord(false);
return -1; return -1;
}
} }
else { else {
// Lifted the following into AudacityProject::GetDefaultPlayOptions() // Lifted the following into AudacityProject::GetDefaultPlayOptions()
@ -687,12 +683,8 @@ int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
} }
} }
if (!success) { if (!success)
SetPlay(false);
SetStop(false);
SetRecord(false);
return -1; return -1;
}
StartScrollingIfPreferred(); StartScrollingIfPreferred();
@ -768,8 +760,8 @@ void ControlToolBar::OnPlay(wxCommandEvent & WXUNUSED(evt))
if (p) p->TP_DisplaySelection(); if (p) p->TP_DisplaySelection();
auto cleanup = finally( [&]{ UpdateStatusBar(p); } );
PlayDefault(); PlayDefault();
UpdateStatusBar(p);
} }
void ControlToolBar::OnStop(wxCommandEvent & WXUNUSED(evt)) void ControlToolBar::OnStop(wxCommandEvent & WXUNUSED(evt))
@ -861,6 +853,7 @@ void ControlToolBar::Pause()
} }
void ControlToolBar::OnRecord(wxCommandEvent &evt) void ControlToolBar::OnRecord(wxCommandEvent &evt)
// STRONG-GUARANTEE (for state of current project's tracks)
{ {
if (gAudioIO->IsBusy()) { if (gAudioIO->IsBusy()) {
if (!CanStopAudioStream() || 0 == gAudioIO->GetNumCaptureChannels()) if (!CanStopAudioStream() || 0 == gAudioIO->GetNumCaptureChannels())
@ -878,14 +871,43 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
SetRecord(true, mRecord->WasShiftDown()); 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) { if (p) {
TrackList *trackList = p->GetTracks();
TrackListIterator it(trackList); TrackListIterator it(trackList);
bool shifted = mRecord->WasShiftDown();
#ifdef EXPERIMENTAL_DA
shifted = !shifted;
#endif
bool hasWave = false; bool hasWave = false;
for (auto t = it.First(); t; t = it.Next()) { for (auto t = it.First(); t; t = it.Next()) {
if (t->GetKind() == Track::Wave) { 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 /* TODO: set up stereo tracks if that is how the user has set up
* their preferences, and choose sample format based on prefs */ * their preferences, and choose sample format based on prefs */
WaveTrackArray newRecordingTracks;
WaveTrackConstArray playbackTracks; WaveTrackConstArray playbackTracks;
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
NoteTrackArray midiTracks; NoteTrackArray midiTracks;
@ -928,8 +949,6 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
// If SHIFT key was down, the user wants append to tracks // If SHIFT key was down, the user wants append to tracks
int recordingChannels = 0; int recordingChannels = 0;
TrackList tracksCopy{};
bool tracksCopied = false;
if (shifted) { if (shifted) {
recordingChannels = gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2); recordingChannels = gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2);
@ -975,10 +994,15 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
t1 = wt->GetEndTime(); t1 = wt->GetEndTime();
if (t1 < t0) { if (t1 < t0) {
if (!tracksCopied) { if (!tracksCopied) {
// Duplicate all tracks before modifying any of them.
// The duplicates are used to restore state in case
// of failure.
tracksCopied = true; tracksCopied = true;
tracksCopy = *trackList; tracksCopy = *trackList;
} }
// Pad the recording track with silence, up to the
// maximum time.
auto newTrack = p->GetTrackFactory()->NewWaveTrack(); auto newTrack = p->GetTrackFactory()->NewWaveTrack();
newTrack->InsertSilence(0.0, t0 - t1); newTrack->InsertSilence(0.0, t0 - t1);
newTrack->Flush(); newTrack->Flush();
@ -987,9 +1011,9 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
wxASSERT(bResult); // TO DO: Actually handle this. wxASSERT(bResult); // TO DO: Actually handle this.
wxUnusedVar(bResult); wxUnusedVar(bResult);
} }
newRecordingTracks.push_back(wt); recordingTracks.push_back(wt);
// Don't record more channels than configured recording pref. // Don't record more channels than configured recording pref.
if( (int)newRecordingTracks.size() >= recordingChannels ){ if( (int)recordingTracks.size() >= recordingChannels ){
break; break;
} }
} }
@ -1076,7 +1100,7 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
} }
// Let the list hold the track, and keep a pointer to it // Let the list hold the track, and keep a pointer to it
newRecordingTracks.push_back( recordingTracks.push_back(
static_cast<WaveTrack*>( static_cast<WaveTrack*>(
trackList->Add( trackList->Add(
std::move(newTrack)))); std::move(newTrack))));
@ -1090,13 +1114,13 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
AudioIOStartStreamOptions options(p->GetDefaultPlayOptions()); AudioIOStartStreamOptions options(p->GetDefaultPlayOptions());
int token = gAudioIO->StartStream(playbackTracks, int token = gAudioIO->StartStream(playbackTracks,
newRecordingTracks, recordingTracks,
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
midiTracks, midiTracks,
#endif #endif
t0, t1, options); t0, t1, options);
bool success = (token != 0); success = (token != 0);
if (success) { if (success) {
p->SetAudioIOToken(token); p->SetAudioIOToken(token);
@ -1105,28 +1129,11 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
StartScrollingIfPreferred(); StartScrollingIfPreferred();
} }
else { 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 // 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."), wxMessageBox(_("Error opening sound device. Try changing the audio host, recording device and the project sample rate."),
_("Error"), wxOK | wxICON_EXCLAMATION, this); _("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, void ControlToolBar::SetupCutPreviewTracks(double WXUNUSED(playStart), double cutStart,
double cutEnd, double WXUNUSED(playEnd)) double cutEnd, double WXUNUSED(playEnd))
// STRONG-GUARANTEE (for state of mCutPreviewTracks)
{ {
ClearCutPreviewTracks(); ClearCutPreviewTracks();
AudacityProject *p = GetActiveProject(); AudacityProject *p = GetActiveProject();
@ -1207,6 +1216,7 @@ void ControlToolBar::SetupCutPreviewTracks(double WXUNUSED(playStart), double cu
if (track1) if (track1)
{ {
// Duplicate and change tracks // Duplicate and change tracks
// Clear has a very small chance of throwing
auto new1 = track1->Duplicate(); auto new1 = track1->Duplicate();
new1->Clear(cutStart, cutEnd); new1->Clear(cutStart, cutEnd);
decltype(new1) new2{}; decltype(new1) new2{};
@ -1216,6 +1226,8 @@ void ControlToolBar::SetupCutPreviewTracks(double WXUNUSED(playStart), double cu
new2->Clear(cutStart, cutEnd); new2->Clear(cutStart, cutEnd);
} }
// use NOTHROW-GUARANTEE:
mCutPreviewTracks = std::make_unique<TrackList>(); mCutPreviewTracks = std::make_unique<TrackList>();
mCutPreviewTracks->Add(std::move(new1)); mCutPreviewTracks->Add(std::move(new1));
if (track2) if (track2)

View File

@ -2672,19 +2672,21 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
ClearPlayRegion(); ClearPlayRegion();
} }
StartQPPlay(evt.ShiftDown(), evt.ControlDown());
mMouseEventState = mesNone; mMouseEventState = mesNone;
mIsDragging = false; mIsDragging = false;
mLeftDownClick = -1; mLeftDownClick = -1;
if (mPlayRegionLock) { auto cleanup = finally( [&] {
// Restore Locked Play region if (mPlayRegionLock) {
SetPlayRegion(mOldPlayRegionStart, mOldPlayRegionEnd); // Restore Locked Play region
mProject->OnLockPlayRegion(); SetPlayRegion(mOldPlayRegionStart, mOldPlayRegionEnd);
// and release local lock mProject->OnLockPlayRegion();
mPlayRegionLock = false; // and release local lock
} mPlayRegionLock = false;
}
} );
StartQPPlay(evt.ShiftDown(), evt.ControlDown());
} }
void AdornedRulerPanel::StartQPPlay(bool looped, bool cutPreview) void AdornedRulerPanel::StartQPPlay(bool looped, bool cutPreview)
@ -2732,18 +2734,20 @@ void AdornedRulerPanel::StartQPPlay(bool looped, bool cutPreview)
options.timeTrack = NULL; options.timeTrack = NULL;
ControlToolBar::PlayAppearance appearance = ControlToolBar::PlayAppearance appearance =
cutPreview ? ControlToolBar::PlayAppearance::CutPreview cutPreview ? ControlToolBar::PlayAppearance::CutPreview
: options.playLooped ? ControlToolBar::PlayAppearance::Looped : options.playLooped ? ControlToolBar::PlayAppearance::Looped
: ControlToolBar::PlayAppearance::Straight; : ControlToolBar::PlayAppearance::Straight;
mPlayRegionStart = start;
mPlayRegionEnd = end;
Refresh();
ctb->PlayPlayRegion((SelectedRegion(start, end)), ctb->PlayPlayRegion((SelectedRegion(start, end)),
options, PlayMode::normalPlay, options, PlayMode::normalPlay,
appearance, appearance,
false, false,
true); true);
mPlayRegionStart = start;
mPlayRegionEnd = end;
Refresh();
} }
} }