1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-09-23 15:41:09 +02:00

Exception safety in: general effect performing functions

This commit is contained in:
Paul Licameli 2016-12-16 13:27:24 -05:00
parent 2cbdd1cc43
commit 79c3bef2ce
5 changed files with 259 additions and 219 deletions

View File

@ -131,7 +131,8 @@ public:
virtual bool IsReady() = 0; virtual bool IsReady() = 0;
virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL) = 0; virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL) = 0;
virtual bool ProcessFinalize() = 0; // This may be called during stack unwinding:
virtual bool ProcessFinalize() /* noexcept */ = 0;
virtual size_t ProcessBlock(float **inBlock, float **outBlock, size_t blockLen) = 0; virtual size_t ProcessBlock(float **inBlock, float **outBlock, size_t blockLen) = 0;
virtual bool RealtimeInitialize() = 0; virtual bool RealtimeInitialize() = 0;

View File

@ -3628,6 +3628,27 @@ bool AudacityProject::OnEffect(const PluginID & ID, int flags)
WaveTrack *newTrack{}; WaveTrack *newTrack{};
wxWindow *focus = wxWindow::FindFocus(); wxWindow *focus = wxWindow::FindFocus();
bool success = false;
auto cleanup = finally( [&] {
if (!success) {
if (newTrack) {
mTracks->Remove(newTrack);
mTrackPanel->Refresh(false);
}
// For now, we're limiting realtime preview to a single effect, so
// make sure the menus reflect that fact that one may have just been
// opened.
UpdateMenus(false);
}
if (focus != NULL) {
focus->SetFocus();
}
} );
//double prevEndTime = mTracks->GetEndTime(); //double prevEndTime = mTracks->GetEndTime();
int count = 0; int count = 0;
bool clean = true; bool clean = true;
@ -3650,24 +3671,13 @@ bool AudacityProject::OnEffect(const PluginID & ID, int flags)
EffectManager & em = EffectManager::Get(); EffectManager & em = EffectManager::Get();
bool success = em.DoEffect(ID, this, mRate, success = em.DoEffect(ID, this, mRate,
GetTracks(), GetTrackFactory(), GetTracks(), GetTrackFactory(),
&mViewInfo.selectedRegion, &mViewInfo.selectedRegion,
(flags & OnEffectFlags::kConfigured) == 0); (flags & OnEffectFlags::kConfigured) == 0);
if (!success) { if (!success)
if (newTrack) {
mTracks->Remove(newTrack);
mTrackPanel->Refresh(false);
}
// For now, we're limiting realtime preview to a single effect, so
// make sure the menus reflect that fact that one may have just been
// opened.
UpdateMenus(false);
return false; return false;
}
if (em.GetSkipStateFlag()) if (em.GetSkipStateFlag())
flags = flags | OnEffectFlags::kSkipState; flags = flags | OnEffectFlags::kSkipState;
@ -3681,7 +3691,7 @@ bool AudacityProject::OnEffect(const PluginID & ID, int flags)
if (!(flags & OnEffectFlags::kDontRepeatLast)) if (!(flags & OnEffectFlags::kDontRepeatLast))
{ {
// Only remember a successful effect, don't rmemeber insert, // Only remember a successful effect, don't remember insert,
// or analyze effects. // or analyze effects.
if (type == EffectTypeProcess) { if (type == EffectTypeProcess) {
wxString shortDesc = em.GetEffectName(ID); wxString shortDesc = em.GetEffectName(ID);
@ -3705,9 +3715,6 @@ bool AudacityProject::OnEffect(const PluginID & ID, int flags)
// mTrackPanel->Refresh(false); // mTrackPanel->Refresh(false);
} }
RedrawProject(); RedrawProject();
if (focus != NULL) {
focus->SetFocus();
}
mTrackPanel->EnsureVisible(mTrackPanel->GetFirstSelectedTrack()); mTrackPanel->EnsureVisible(mTrackPanel->GetFirstSelectedTrack());
mTrackPanel->Refresh(false); mTrackPanel->Refresh(false);

View File

@ -1118,16 +1118,6 @@ void Effect::SetBatchProcessing(bool start)
} }
} }
namespace {
struct SetProgress {
SetProgress(ProgressDialog *& mProgress_, ProgressDialog *progress)
: mProgress(mProgress_)
{ mProgress = progress; }
~SetProgress() { mProgress = nullptr; }
ProgressDialog *& mProgress;
};
}
bool Effect::DoEffect(wxWindow *parent, bool Effect::DoEffect(wxWindow *parent,
double projectRate, double projectRate,
TrackList *list, TrackList *list,
@ -1191,26 +1181,33 @@ bool Effect::DoEffect(wxWindow *parent,
// Prompting will be bypassed when applying an effect that has already // Prompting will be bypassed when applying an effect that has already
// been configured, e.g. repeating the last effect on a different selection. // been configured, e.g. repeating the last effect on a different selection.
// Prompting may call Effect::Preview
if (shouldPrompt && IsInteractive() && !PromptUser(parent)) if (shouldPrompt && IsInteractive() && !PromptUser(parent))
{ {
return false; return false;
} }
auto cleanup = finally( [&] {
End();
// In case of failed effect, be sure to free memory.
ReplaceProcessedTracks( false );
} );
bool returnVal = true; bool returnVal = true;
bool skipFlag = CheckWhetherSkipEffect(); bool skipFlag = CheckWhetherSkipEffect();
if (skipFlag == false) if (skipFlag == false)
{ {
ProgressDialog progress(GetName(), ProgressDialog progress{
GetName(),
wxString::Format(_("Applying %s..."), GetName().c_str()), wxString::Format(_("Applying %s..."), GetName().c_str()),
pdlgHideStopButton); pdlgHideStopButton
SetProgress sp(mProgress, &progress); };
auto vr = valueRestorer( mProgress, &progress );
returnVal = Process(); returnVal = Process();
} }
End();
mOutputTracks.reset();
if (returnVal) if (returnVal)
{ {
selectedRegion->setTimes(mT0, mT1); selectedRegion->setTimes(mT0, mT1);
@ -1286,8 +1283,8 @@ bool Effect::ProcessPass()
bool editClipCanMove; bool editClipCanMove;
gPrefs->Read(wxT("/GUI/EditClipCanMove"), &editClipCanMove, true); gPrefs->Read(wxT("/GUI/EditClipCanMove"), &editClipCanMove, true);
mInBuffer.reset(); FloatBuffers inBuffer, outBuffer;
mOutBuffer.reset(); ArrayOf<float *> inBufPos, outBufPos;
ChannelName map[3]; ChannelName map[3];
@ -1389,36 +1386,36 @@ bool Effect::ProcessPass()
{ {
// Always create the number of input buffers the client expects even if we don't have // Always create the number of input buffers the client expects even if we don't have
// the same number of channels. // the same number of channels.
mInBufPos.reinit( mNumAudioIn ); inBufPos.reinit( mNumAudioIn );
mInBuffer.reinit( mNumAudioIn, mBufferSize ); inBuffer.reinit( mNumAudioIn, mBufferSize );
// We won't be using more than the first 2 buffers, so clear the rest (if any) // We won't be using more than the first 2 buffers, so clear the rest (if any)
for (size_t i = 2; i < mNumAudioIn; i++) for (size_t i = 2; i < mNumAudioIn; i++)
{ {
for (size_t j = 0; j < mBufferSize; j++) for (size_t j = 0; j < mBufferSize; j++)
{ {
mInBuffer[i][j] = 0.0; inBuffer[i][j] = 0.0;
} }
} }
// Always create the number of output buffers the client expects even if we don't have // Always create the number of output buffers the client expects even if we don't have
// the same number of channels. // the same number of channels.
mOutBufPos.reinit( mNumAudioOut );
// Output buffers get an extra mBlockSize worth to give extra room if // Output buffers get an extra mBlockSize worth to give extra room if
// the plugin adds latency // the plugin adds latency
mOutBuffer.reinit( mNumAudioOut, mBufferSize + mBlockSize ); outBufPos.reinit( mNumAudioOut );
outBuffer.reinit( mNumAudioOut, mBufferSize + mBlockSize );
} }
// (Re)Set the input buffer positions // (Re)Set the input buffer positions
for (size_t i = 0; i < mNumAudioIn; i++) for (size_t i = 0; i < mNumAudioIn; i++)
{ {
mInBufPos[i] = mInBuffer[i].get(); inBufPos[i] = inBuffer[i].get();
} }
// (Re)Set the output buffer positions // (Re)Set the output buffer positions
for (size_t i = 0; i < mNumAudioOut; i++) for (size_t i = 0; i < mNumAudioOut; i++)
{ {
mOutBufPos[i] = mOutBuffer[i].get(); outBufPos[i] = outBuffer[i].get();
} }
// Clear unused input buffers // Clear unused input buffers
@ -1426,13 +1423,15 @@ bool Effect::ProcessPass()
{ {
for (size_t j = 0; j < mBufferSize; j++) for (size_t j = 0; j < mBufferSize; j++)
{ {
mInBuffer[1][j] = 0.0; inBuffer[1][j] = 0.0;
} }
clear = true; clear = true;
} }
// Go process the track(s) // Go process the track(s)
bGoodResult = ProcessTrack(count, map, left, right, leftStart, rightStart, len); bGoodResult = ProcessTrack(
count, map, left, right, leftStart, rightStart, len,
inBuffer, outBuffer, inBufPos, outBufPos);
if (!bGoodResult) if (!bGoodResult)
{ {
break; break;
@ -1441,11 +1440,6 @@ bool Effect::ProcessPass()
count++; count++;
} }
mOutBuffer.reset();
mOutBufPos.reset();
mInBuffer.reset();
mInBufPos.reset();
if (bGoodResult && GetType() == EffectTypeGenerate) if (bGoodResult && GetType() == EffectTypeGenerate)
{ {
mT1 = mT0 + mDuration; mT1 = mT0 + mDuration;
@ -1460,7 +1454,11 @@ bool Effect::ProcessTrack(int count,
WaveTrack *right, WaveTrack *right,
sampleCount leftStart, sampleCount leftStart,
sampleCount rightStart, sampleCount rightStart,
sampleCount len) sampleCount len,
FloatBuffers &inBuffer,
FloatBuffers &outBuffer,
ArrayOf< float * > &inBufPos,
ArrayOf< float *> &outBufPos)
{ {
bool rc = true; bool rc = true;
@ -1470,6 +1468,16 @@ bool Effect::ProcessTrack(int count,
return false; return false;
} }
{ // Start scope for cleanup
auto cleanup = finally( [&] {
// Allow the plugin to cleanup
if (!ProcessFinalize())
{
// In case of non-exceptional flow of control, set rc
rc = false;
}
} );
// For each input block of samples, we pass it to the effect along with a // For each input block of samples, we pass it to the effect along with a
// variable output location. This output location is simply a pointer into a // variable output location. This output location is simply a pointer into a
// much larger buffer. This reduces the number of calls required to add the // much larger buffer. This reduces the number of calls required to add the
@ -1539,16 +1547,16 @@ bool Effect::ProcessTrack(int count,
limitSampleBufferSize( mBufferSize, inputRemaining ); limitSampleBufferSize( mBufferSize, inputRemaining );
// Fill the input buffers // Fill the input buffers
left->Get((samplePtr) mInBuffer[0].get(), floatSample, inLeftPos, inputBufferCnt); left->Get((samplePtr) inBuffer[0].get(), floatSample, inLeftPos, inputBufferCnt);
if (right) if (right)
{ {
right->Get((samplePtr) mInBuffer[1].get(), floatSample, inRightPos, inputBufferCnt); right->Get((samplePtr) inBuffer[1].get(), floatSample, inRightPos, inputBufferCnt);
} }
// Reset the input buffer positions // Reset the input buffer positions
for (size_t i = 0; i < mNumChannels; i++) for (size_t i = 0; i < mNumChannels; i++)
{ {
mInBufPos[i] = mInBuffer[i].get(); inBufPos[i] = inBuffer[i].get();
} }
} }
@ -1568,7 +1576,7 @@ bool Effect::ProcessTrack(int count,
{ {
for (decltype(cnt) j = 0 ; j < cnt; j++) for (decltype(cnt) j = 0 ; j < cnt; j++)
{ {
mInBufPos[i][j + curBlockSize] = 0.0; inBufPos[i][j + curBlockSize] = 0.0;
} }
} }
@ -1595,12 +1603,12 @@ bool Effect::ProcessTrack(int count,
// Reset the input buffer positions // Reset the input buffer positions
for (size_t i = 0; i < mNumChannels; i++) for (size_t i = 0; i < mNumChannels; i++)
{ {
mInBufPos[i] = mInBuffer[i].get(); inBufPos[i] = inBuffer[i].get();
// And clear // And clear
for (size_t j = 0; j < mBlockSize; j++) for (size_t j = 0; j < mBlockSize; j++)
{ {
mInBuffer[i][j] = 0.0; inBuffer[i][j] = 0.0;
} }
} }
cleared = true; cleared = true;
@ -1611,7 +1619,7 @@ bool Effect::ProcessTrack(int count,
decltype(curBlockSize) processed; decltype(curBlockSize) processed;
try try
{ {
processed = ProcessBlock(mInBufPos.get(), mOutBufPos.get(), curBlockSize); processed = ProcessBlock(inBufPos.get(), outBufPos.get(), curBlockSize);
} }
catch( const AudacityException &e ) catch( const AudacityException &e )
{ {
@ -1635,7 +1643,7 @@ bool Effect::ProcessTrack(int count,
{ {
for (size_t i = 0; i < mNumChannels; i++) for (size_t i = 0; i < mNumChannels; i++)
{ {
mInBufPos[i] += curBlockSize; inBufPos[i] += curBlockSize;
} }
inputRemaining -= curBlockSize; inputRemaining -= curBlockSize;
inputBufferCnt -= curBlockSize; inputBufferCnt -= curBlockSize;
@ -1672,7 +1680,7 @@ bool Effect::ProcessTrack(int count,
curBlockSize -= delay; curBlockSize -= delay;
for (size_t i = 0; i < chans; i++) for (size_t i = 0; i < chans; i++)
{ {
memmove(mOutBufPos[i], mOutBufPos[i] + delay, sizeof(float) * curBlockSize); memmove(outBufPos[i], outBufPos[i] + delay, sizeof(float) * curBlockSize);
} }
curDelay = 0; curDelay = 0;
} }
@ -1687,7 +1695,7 @@ bool Effect::ProcessTrack(int count,
// Bump to next output buffer position // Bump to next output buffer position
for (size_t i = 0; i < chans; i++) for (size_t i = 0; i < chans; i++)
{ {
mOutBufPos[i] += curBlockSize; outBufPos[i] += curBlockSize;
} }
} }
// Output buffers have filled // Output buffers have filled
@ -1696,32 +1704,32 @@ bool Effect::ProcessTrack(int count,
if (isProcessor) if (isProcessor)
{ {
// Write them out // Write them out
left->Set((samplePtr) mOutBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt); left->Set((samplePtr) outBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt);
if (right) if (right)
{ {
if (chans >= 2) if (chans >= 2)
{ {
right->Set((samplePtr) mOutBuffer[1].get(), floatSample, outRightPos, outputBufferCnt); right->Set((samplePtr) outBuffer[1].get(), floatSample, outRightPos, outputBufferCnt);
} }
else else
{ {
right->Set((samplePtr) mOutBuffer[0].get(), floatSample, outRightPos, outputBufferCnt); right->Set((samplePtr) outBuffer[0].get(), floatSample, outRightPos, outputBufferCnt);
} }
} }
} }
else if (isGenerator) else if (isGenerator)
{ {
genLeft->Append((samplePtr) mOutBuffer[0].get(), floatSample, outputBufferCnt); genLeft->Append((samplePtr) outBuffer[0].get(), floatSample, outputBufferCnt);
if (genRight) if (genRight)
{ {
genRight->Append((samplePtr) mOutBuffer[1].get(), floatSample, outputBufferCnt); genRight->Append((samplePtr) outBuffer[1].get(), floatSample, outputBufferCnt);
} }
} }
// Reset the output buffer positions // Reset the output buffer positions
for (size_t i = 0; i < chans; i++) for (size_t i = 0; i < chans; i++)
{ {
mOutBufPos[i] = mOutBuffer[i].get(); outBufPos[i] = outBuffer[i].get();
} }
// Bump to the next track position // Bump to the next track position
@ -1753,34 +1761,34 @@ bool Effect::ProcessTrack(int count,
} }
// Put any remaining output // Put any remaining output
if (outputBufferCnt) if (rc && outputBufferCnt)
{ {
if (isProcessor) if (isProcessor)
{ {
left->Set((samplePtr) mOutBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt); left->Set((samplePtr) outBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt);
if (right) if (right)
{ {
if (chans >= 2) if (chans >= 2)
{ {
right->Set((samplePtr) mOutBuffer[1].get(), floatSample, outRightPos, outputBufferCnt); right->Set((samplePtr) outBuffer[1].get(), floatSample, outRightPos, outputBufferCnt);
} }
else else
{ {
right->Set((samplePtr) mOutBuffer[0].get(), floatSample, outRightPos, outputBufferCnt); right->Set((samplePtr) outBuffer[0].get(), floatSample, outRightPos, outputBufferCnt);
} }
} }
} }
else if (isGenerator) else if (isGenerator)
{ {
genLeft->Append((samplePtr) mOutBuffer[0].get(), floatSample, outputBufferCnt); genLeft->Append((samplePtr) outBuffer[0].get(), floatSample, outputBufferCnt);
if (genRight) if (genRight)
{ {
genRight->Append((samplePtr) mOutBuffer[1].get(), floatSample, outputBufferCnt); genRight->Append((samplePtr) outBuffer[1].get(), floatSample, outputBufferCnt);
} }
} }
} }
if (isGenerator) if (rc && isGenerator)
{ {
AudacityProject *p = GetActiveProject(); AudacityProject *p = GetActiveProject();
@ -1794,7 +1802,7 @@ bool Effect::ProcessTrack(int count,
// The purpose was to remap split lines inside the selected region when // The purpose was to remap split lines inside the selected region when
// a generator replaces it with sound of different duration. But // a generator replaces it with sound of different duration. But
// the "correct" version might have the effect of mapping some splits too // the "correct" version might have the effect of mapping some splits too
// far left, to before the seletion. // far left, to before the selection.
// In practice the wrong version probably did nothing most of the time, // In practice the wrong version probably did nothing most of the time,
// because the cutoff time for the step time warper was 44100 times too // because the cutoff time for the step time warper was 44100 times too
// far from mT0. // far from mT0.
@ -1813,12 +1821,7 @@ bool Effect::ProcessTrack(int count,
} }
} }
// Allow the plugin to cleanup } // End scope for cleanup
if (!ProcessFinalize())
{
return false;
}
return rc; return rc;
} }
@ -2125,20 +2128,26 @@ auto Effect::ModifyAnalysisTrack
// Else clear and DELETE mOutputTracks copies. // Else clear and DELETE mOutputTracks copies.
void Effect::ReplaceProcessedTracks(const bool bGoodResult) void Effect::ReplaceProcessedTracks(const bool bGoodResult)
{ {
wxASSERT(mOutputTracks); // Make sure we at least did the CopyInputTracks().
if (!bGoodResult) { if (!bGoodResult) {
// Free resources, unless already freed.
// Processing failed or was cancelled so throw away the processed tracks. // Processing failed or was cancelled so throw away the processed tracks.
mOutputTracks->Clear(); if ( mOutputTracks )
mOutputTracks->Clear();
// Reset map // Reset map
mIMap.clear(); mIMap.clear();
mOMap.clear(); mOMap.clear();
mOutputTracksType = Track::None;
//TODO:undo the non-gui ODTask transfer //TODO:undo the non-gui ODTask transfer
return; return;
} }
// Assume resources need to be freed.
wxASSERT(mOutputTracks); // Make sure we at least did the CopyInputTracks().
auto iterOut = mOutputTracks->begin(), iterEnd = mOutputTracks->end(); auto iterOut = mOutputTracks->begin(), iterEnd = mOutputTracks->end();
size_t cnt = mOMap.size(); size_t cnt = mOMap.size();
@ -2465,142 +2474,144 @@ void Effect::Preview(bool dryOnly)
return; return;
bool success = true; bool success = true;
double oldT0 = mT0; auto vr0 = valueRestorer( mT0 );
double oldT1 = mT1; auto vr1 = valueRestorer( mT1 );
// Most effects should stop at t1. // Most effects should stop at t1.
if (!mPreviewFullSelection) if (!mPreviewFullSelection)
mT1 = t1; mT1 = t1;
{ // Save the original track list
// Save the original track list TrackList *saveTracks = mTracks;
TrackList *saveTracks = mTracks;
auto cleanup = finally( [&] { mTracks = saveTracks; } );
// Build NEW tracklist from rendering tracks auto cleanup = finally( [&] {
auto uTracks = std::make_unique<TrackList>(); mTracks = saveTracks;
mTracks = uTracks.get();
// Linear Effect preview optimised by pre-mixing to one track. // Effect is already inited; we will call Process, End, and then Init
// Generators need to generate per track. // again, so the state is exactly the way it was before Preview
if (mIsLinearEffect && !isGenerator) { // was called.
WaveTrack::Holder mixLeft, mixRight;
MixAndRender(saveTracks, mFactory, rate, floatSample, mT0, t1, mixLeft, mixRight);
if (!mixLeft)
return;
mixLeft->Offset(-mixLeft->GetStartTime());
mixLeft->InsertSilence(0.0, mT0);
mixLeft->SetSelected(true);
mixLeft->SetDisplay(WaveTrack::NoDisplay);
mTracks->Add(std::move(mixLeft));
if (mixRight) {
mixRight->Offset(-mixRight->GetStartTime());
mixRight->InsertSilence(0.0, mT0);
mixRight->SetSelected(true);
mTracks->Add(std::move(mixRight));
}
}
else {
TrackListOfKindIterator iter(Track::Wave, saveTracks);
WaveTrack *src = (WaveTrack *) iter.First();
while (src)
{
if (src->GetSelected() || mPreviewWithNotSelected) {
auto dest = src->Copy(mT0, t1);
dest->InsertSilence(0.0, mT0);
dest->SetSelected(src->GetSelected());
static_cast<WaveTrack*>(dest.get())->SetDisplay(WaveTrack::NoDisplay);
mTracks->Add(std::move(dest));
}
src = (WaveTrack *) iter.Next();
}
}
// Update track/group counts
CountWaveTracks();
// Apply effect
if (!dryOnly) { if (!dryOnly) {
ProgressDialog progress(GetName(), End();
_("Preparing preview"), GuardedCall< void >( [&]{ Init(); } );
pdlgHideCancelButton); // Have only "Stop" button.
SetProgress sp(mProgress, &progress);
mIsPreview = true;
success = Process();
mIsPreview = false;
} }
if (success)
{
WaveTrackConstArray playbackTracks;
WaveTrackArray recordingTracks;
SelectedTrackListOfKindIterator iter(Track::Wave, mTracks);
WaveTrack *src = (WaveTrack *) iter.First();
while (src) {
playbackTracks.push_back(src);
src = (WaveTrack *) iter.Next();
}
// Some effects (Paulstretch) may need to generate more
// than previewLen, so take the min.
t1 = std::min(mT0 + previewLen, mT1);
#ifdef EXPERIMENTAL_MIDI_OUT
NoteTrackArray empty;
#endif
// Start audio playing
AudioIOStartStreamOptions options { rate };
int token =
gAudioIO->StartStream(playbackTracks, recordingTracks,
#ifdef EXPERIMENTAL_MIDI_OUT
empty,
#endif
mT0, t1, options);
if (token) {
auto previewing = ProgressResult::Success;
// The progress dialog must be deleted before stopping the stream
// to allow events to flow to the app during StopStream processing.
// The progress dialog blocks these events.
{
ProgressDialog progress
(GetName(), _("Previewing"), pdlgHideCancelButton);
while (gAudioIO->IsStreamActive(token) && previewing == ProgressResult::Success) {
::wxMilliSleep(100);
previewing = progress.Update(gAudioIO->GetStreamTime() - mT0, t1 - mT0);
}
}
gAudioIO->StopStream();
while (gAudioIO->IsBusy()) {
::wxMilliSleep(100);
}
}
else {
wxMessageBox(_("Error opening sound device. Try changing the audio host, playback device and the project sample rate."),
_("Error"), wxOK | wxICON_EXCLAMATION, FocusDialog);
}
}
if (FocusDialog) { if (FocusDialog) {
FocusDialog->SetFocus(); FocusDialog->SetFocus();
} }
mOutputTracks.reset(); // In case of failed effect, be sure to free memory.
ReplaceProcessedTracks( false );
} );
// Build NEW tracklist from rendering tracks
auto uTracks = std::make_unique<TrackList>();
mTracks = uTracks.get();
// Linear Effect preview optimised by pre-mixing to one track.
// Generators need to generate per track.
if (mIsLinearEffect && !isGenerator) {
WaveTrack::Holder mixLeft, mixRight;
MixAndRender(saveTracks, mFactory, rate, floatSample, mT0, t1, mixLeft, mixRight);
if (!mixLeft)
return;
mixLeft->Offset(-mixLeft->GetStartTime());
mixLeft->InsertSilence(0.0, mT0);
mixLeft->SetSelected(true);
mixLeft->SetDisplay(WaveTrack::NoDisplay);
mTracks->Add(std::move(mixLeft));
if (mixRight) {
mixRight->Offset(-mixRight->GetStartTime());
mixRight->InsertSilence(0.0, mT0);
mixRight->SetSelected(true);
mTracks->Add(std::move(mixRight));
}
}
else {
TrackListOfKindIterator iter(Track::Wave, saveTracks);
WaveTrack *src = (WaveTrack *) iter.First();
while (src)
{
if (src->GetSelected() || mPreviewWithNotSelected) {
auto dest = src->Copy(mT0, t1);
dest->InsertSilence(0.0, mT0);
dest->SetSelected(src->GetSelected());
static_cast<WaveTrack*>(dest.get())->SetDisplay(WaveTrack::NoDisplay);
mTracks->Add(std::move(dest));
}
src = (WaveTrack *) iter.Next();
}
} }
mT0 = oldT0;
mT1 = oldT1;
// Effect is already inited; we call Process, End, and then Init // Update track/group counts
// again, so the state is exactly the way it was before Preview CountWaveTracks();
// was called.
// Apply effect
if (!dryOnly) { if (!dryOnly) {
End(); ProgressDialog progress{
Init(); GetName(),
_("Preparing preview"),
pdlgHideCancelButton
}; // Have only "Stop" button.
auto vr = valueRestorer( mProgress, &progress );
auto vr2 = valueRestorer( mIsPreview, true );
success = Process();
}
if (success)
{
WaveTrackConstArray playbackTracks;
WaveTrackArray recordingTracks;
SelectedTrackListOfKindIterator iter(Track::Wave, mTracks);
WaveTrack *src = (WaveTrack *) iter.First();
while (src) {
playbackTracks.push_back(src);
src = (WaveTrack *) iter.Next();
}
// Some effects (Paulstretch) may need to generate more
// than previewLen, so take the min.
t1 = std::min(mT0 + previewLen, mT1);
#ifdef EXPERIMENTAL_MIDI_OUT
NoteTrackArray empty;
#endif
// Start audio playing
AudioIOStartStreamOptions options { rate };
int token =
gAudioIO->StartStream(playbackTracks, recordingTracks,
#ifdef EXPERIMENTAL_MIDI_OUT
empty,
#endif
mT0, t1, options);
if (token) {
auto previewing = ProgressResult::Success;
// The progress dialog must be deleted before stopping the stream
// to allow events to flow to the app during StopStream processing.
// The progress dialog blocks these events.
{
ProgressDialog progress
(GetName(), _("Previewing"), pdlgHideCancelButton);
while (gAudioIO->IsStreamActive(token) && previewing == ProgressResult::Success) {
::wxMilliSleep(100);
previewing = progress.Update(gAudioIO->GetStreamTime() - mT0, t1 - mT0);
}
}
gAudioIO->StopStream();
while (gAudioIO->IsBusy()) {
::wxMilliSleep(100);
}
}
else {
wxMessageBox(_("Error opening sound device. Try changing the audio host, playback device and the project sample rate."),
_("Error"), wxOK | wxICON_EXCLAMATION, FocusDialog);
}
} }
} }
@ -3167,11 +3178,10 @@ void EffectUIHost::OnApply(wxCommandEvent & evt)
// Progress dialog no longer yields, so this "shouldn't" be necessary (yet to be proven // Progress dialog no longer yields, so this "shouldn't" be necessary (yet to be proven
// for sure), but it is a nice visual cue that something is going on. // for sure), but it is a nice visual cue that something is going on.
mApplyBtn->Disable(); mApplyBtn->Disable();
auto cleanup = finally( [&] { mApplyBtn->Enable(); } );
mEffect->Apply(); mEffect->Apply();
mApplyBtn->Enable();
return; return;
} }

View File

@ -287,7 +287,9 @@ protected:
virtual bool InitPass2(); virtual bool InitPass2();
virtual int GetPass(); virtual int GetPass();
// clean up any temporary memory // clean up any temporary memory, needed only per invocation of the
// effect, after either successful or failed or exception-aborted processing.
// Invoked inside a "finally" block so it must be no-throw.
virtual void End(); virtual void End();
// Most effects just use the previewLength, but time-stretching/compressing // Most effects just use the previewLength, but time-stretching/compressing
@ -470,8 +472,12 @@ protected:
WaveTrack *right, WaveTrack *right,
sampleCount leftStart, sampleCount leftStart,
sampleCount rightStart, sampleCount rightStart,
sampleCount len); sampleCount len,
FloatBuffers &inBuffer,
FloatBuffers &outBuffer,
ArrayOf< float * > &inBufPos,
ArrayOf< float *> &outBufPos);
// //
// private data // private data
// //
@ -505,11 +511,6 @@ private:
size_t mNumAudioIn; size_t mNumAudioIn;
size_t mNumAudioOut; size_t mNumAudioOut;
FloatBuffers mInBuffer, mOutBuffer;
using Positions = ArrayOf < float* > ; // Array of non-owning pointers into the above
Positions mInBufPos, mOutBufPos;
size_t mBufferSize; size_t mBufferSize;
size_t mBlockSize; size_t mBlockSize;
unsigned mNumChannels; unsigned mNumChannels;

View File

@ -17,6 +17,8 @@
#if defined(EXPERIMENTAL_EFFECTS_RACK) #if defined(EXPERIMENTAL_EFFECTS_RACK)
#include "../MemoryX.h" #include "../MemoryX.h"
#include "../UndoManager.h"
#include <wx/access.h> #include <wx/access.h>
#include <wx/defs.h> #include <wx/defs.h>
#include <wx/bmpbuttn.h> #include <wx/bmpbuttn.h>
@ -289,17 +291,36 @@ void EffectRack::OnTimer(wxTimerEvent & WXUNUSED(evt))
void EffectRack::OnApply(wxCommandEvent & WXUNUSED(evt)) void EffectRack::OnApply(wxCommandEvent & WXUNUSED(evt))
{ {
AudacityProject *project = GetActiveProject(); AudacityProject *project = GetActiveProject();
bool success = false;
auto state = project->GetUndoManager()->GetCurrentState();
auto cleanup = finally( [&] {
if(!success)
project->SetStateTo(state);
} );
for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++) for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
{ {
if (mPowerState[i]) if (mPowerState[i])
{ {
project->OnEffect(mEffects[i]->GetID(), if (!project->OnEffect(mEffects[i]->GetID(),
AudacityProject::OnEffectFlags::kConfigured); AudacityProject::OnEffectFlags::kConfigured))
// If any effect fails (or throws), then stop.
return;
}
}
success = true;
// Only after all succeed, do the following.
for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
{
if (mPowerState[i])
{
mPowerState[i] = false; mPowerState[i] = false;
wxBitmapButton *btn = static_cast<wxBitmapButton *>(FindWindowById(ID_POWER + i)); wxBitmapButton *btn =
static_cast<wxBitmapButton *>(FindWindowById(ID_POWER + i));
btn->SetBitmapLabel(mPowerRaised); btn->SetBitmapLabel(mPowerRaised);
btn->SetBitmapSelected(mPowerRaised); btn->SetBitmapSelected(mPowerRaised);
} }