1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-05 14:49:25 +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 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 bool RealtimeInitialize() = 0;

View File

@ -3628,6 +3628,27 @@ bool AudacityProject::OnEffect(const PluginID & ID, int flags)
WaveTrack *newTrack{};
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();
int count = 0;
bool clean = true;
@ -3650,24 +3671,13 @@ bool AudacityProject::OnEffect(const PluginID & ID, int flags)
EffectManager & em = EffectManager::Get();
bool success = em.DoEffect(ID, this, mRate,
success = em.DoEffect(ID, this, mRate,
GetTracks(), GetTrackFactory(),
&mViewInfo.selectedRegion,
(flags & OnEffectFlags::kConfigured) == 0);
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 (!success)
return false;
}
if (em.GetSkipStateFlag())
flags = flags | OnEffectFlags::kSkipState;
@ -3681,7 +3691,7 @@ bool AudacityProject::OnEffect(const PluginID & ID, int flags)
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.
if (type == EffectTypeProcess) {
wxString shortDesc = em.GetEffectName(ID);
@ -3705,9 +3715,6 @@ bool AudacityProject::OnEffect(const PluginID & ID, int flags)
// mTrackPanel->Refresh(false);
}
RedrawProject();
if (focus != NULL) {
focus->SetFocus();
}
mTrackPanel->EnsureVisible(mTrackPanel->GetFirstSelectedTrack());
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,
double projectRate,
TrackList *list,
@ -1191,26 +1181,33 @@ bool Effect::DoEffect(wxWindow *parent,
// Prompting will be bypassed when applying an effect that has already
// been configured, e.g. repeating the last effect on a different selection.
// Prompting may call Effect::Preview
if (shouldPrompt && IsInteractive() && !PromptUser(parent))
{
return false;
}
auto cleanup = finally( [&] {
End();
// In case of failed effect, be sure to free memory.
ReplaceProcessedTracks( false );
} );
bool returnVal = true;
bool skipFlag = CheckWhetherSkipEffect();
if (skipFlag == false)
{
ProgressDialog progress(GetName(),
ProgressDialog progress{
GetName(),
wxString::Format(_("Applying %s..."), GetName().c_str()),
pdlgHideStopButton);
SetProgress sp(mProgress, &progress);
pdlgHideStopButton
};
auto vr = valueRestorer( mProgress, &progress );
returnVal = Process();
}
End();
mOutputTracks.reset();
if (returnVal)
{
selectedRegion->setTimes(mT0, mT1);
@ -1286,8 +1283,8 @@ bool Effect::ProcessPass()
bool editClipCanMove;
gPrefs->Read(wxT("/GUI/EditClipCanMove"), &editClipCanMove, true);
mInBuffer.reset();
mOutBuffer.reset();
FloatBuffers inBuffer, outBuffer;
ArrayOf<float *> inBufPos, outBufPos;
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
// the same number of channels.
mInBufPos.reinit( mNumAudioIn );
mInBuffer.reinit( mNumAudioIn, mBufferSize );
inBufPos.reinit( mNumAudioIn );
inBuffer.reinit( mNumAudioIn, mBufferSize );
// 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 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
// the same number of channels.
mOutBufPos.reinit( mNumAudioOut );
// Output buffers get an extra mBlockSize worth to give extra room if
// the plugin adds latency
mOutBuffer.reinit( mNumAudioOut, mBufferSize + mBlockSize );
outBufPos.reinit( mNumAudioOut );
outBuffer.reinit( mNumAudioOut, mBufferSize + mBlockSize );
}
// (Re)Set the input buffer positions
for (size_t i = 0; i < mNumAudioIn; i++)
{
mInBufPos[i] = mInBuffer[i].get();
inBufPos[i] = inBuffer[i].get();
}
// (Re)Set the output buffer positions
for (size_t i = 0; i < mNumAudioOut; i++)
{
mOutBufPos[i] = mOutBuffer[i].get();
outBufPos[i] = outBuffer[i].get();
}
// Clear unused input buffers
@ -1426,13 +1423,15 @@ bool Effect::ProcessPass()
{
for (size_t j = 0; j < mBufferSize; j++)
{
mInBuffer[1][j] = 0.0;
inBuffer[1][j] = 0.0;
}
clear = true;
}
// 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)
{
break;
@ -1441,11 +1440,6 @@ bool Effect::ProcessPass()
count++;
}
mOutBuffer.reset();
mOutBufPos.reset();
mInBuffer.reset();
mInBufPos.reset();
if (bGoodResult && GetType() == EffectTypeGenerate)
{
mT1 = mT0 + mDuration;
@ -1460,7 +1454,11 @@ bool Effect::ProcessTrack(int count,
WaveTrack *right,
sampleCount leftStart,
sampleCount rightStart,
sampleCount len)
sampleCount len,
FloatBuffers &inBuffer,
FloatBuffers &outBuffer,
ArrayOf< float * > &inBufPos,
ArrayOf< float *> &outBufPos)
{
bool rc = true;
@ -1470,6 +1468,16 @@ bool Effect::ProcessTrack(int count,
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
// 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
@ -1539,16 +1547,16 @@ bool Effect::ProcessTrack(int count,
limitSampleBufferSize( mBufferSize, inputRemaining );
// Fill the input buffers
left->Get((samplePtr) mInBuffer[0].get(), floatSample, inLeftPos, inputBufferCnt);
left->Get((samplePtr) inBuffer[0].get(), floatSample, inLeftPos, inputBufferCnt);
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
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++)
{
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
for (size_t i = 0; i < mNumChannels; i++)
{
mInBufPos[i] = mInBuffer[i].get();
inBufPos[i] = inBuffer[i].get();
// And clear
for (size_t j = 0; j < mBlockSize; j++)
{
mInBuffer[i][j] = 0.0;
inBuffer[i][j] = 0.0;
}
}
cleared = true;
@ -1611,7 +1619,7 @@ bool Effect::ProcessTrack(int count,
decltype(curBlockSize) processed;
try
{
processed = ProcessBlock(mInBufPos.get(), mOutBufPos.get(), curBlockSize);
processed = ProcessBlock(inBufPos.get(), outBufPos.get(), curBlockSize);
}
catch( const AudacityException &e )
{
@ -1635,7 +1643,7 @@ bool Effect::ProcessTrack(int count,
{
for (size_t i = 0; i < mNumChannels; i++)
{
mInBufPos[i] += curBlockSize;
inBufPos[i] += curBlockSize;
}
inputRemaining -= curBlockSize;
inputBufferCnt -= curBlockSize;
@ -1672,7 +1680,7 @@ bool Effect::ProcessTrack(int count,
curBlockSize -= delay;
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;
}
@ -1687,7 +1695,7 @@ bool Effect::ProcessTrack(int count,
// Bump to next output buffer position
for (size_t i = 0; i < chans; i++)
{
mOutBufPos[i] += curBlockSize;
outBufPos[i] += curBlockSize;
}
}
// Output buffers have filled
@ -1696,32 +1704,32 @@ bool Effect::ProcessTrack(int count,
if (isProcessor)
{
// Write them out
left->Set((samplePtr) mOutBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt);
left->Set((samplePtr) outBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt);
if (right)
{
if (chans >= 2)
{
right->Set((samplePtr) mOutBuffer[1].get(), floatSample, outRightPos, outputBufferCnt);
right->Set((samplePtr) outBuffer[1].get(), floatSample, outRightPos, outputBufferCnt);
}
else
{
right->Set((samplePtr) mOutBuffer[0].get(), floatSample, outRightPos, outputBufferCnt);
right->Set((samplePtr) outBuffer[0].get(), floatSample, outRightPos, outputBufferCnt);
}
}
}
else if (isGenerator)
{
genLeft->Append((samplePtr) mOutBuffer[0].get(), floatSample, outputBufferCnt);
genLeft->Append((samplePtr) outBuffer[0].get(), floatSample, outputBufferCnt);
if (genRight)
{
genRight->Append((samplePtr) mOutBuffer[1].get(), floatSample, outputBufferCnt);
genRight->Append((samplePtr) outBuffer[1].get(), floatSample, outputBufferCnt);
}
}
// Reset the output buffer positions
for (size_t i = 0; i < chans; i++)
{
mOutBufPos[i] = mOutBuffer[i].get();
outBufPos[i] = outBuffer[i].get();
}
// Bump to the next track position
@ -1753,34 +1761,34 @@ bool Effect::ProcessTrack(int count,
}
// Put any remaining output
if (outputBufferCnt)
if (rc && outputBufferCnt)
{
if (isProcessor)
{
left->Set((samplePtr) mOutBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt);
left->Set((samplePtr) outBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt);
if (right)
{
if (chans >= 2)
{
right->Set((samplePtr) mOutBuffer[1].get(), floatSample, outRightPos, outputBufferCnt);
right->Set((samplePtr) outBuffer[1].get(), floatSample, outRightPos, outputBufferCnt);
}
else
{
right->Set((samplePtr) mOutBuffer[0].get(), floatSample, outRightPos, outputBufferCnt);
right->Set((samplePtr) outBuffer[0].get(), floatSample, outRightPos, outputBufferCnt);
}
}
}
else if (isGenerator)
{
genLeft->Append((samplePtr) mOutBuffer[0].get(), floatSample, outputBufferCnt);
genLeft->Append((samplePtr) outBuffer[0].get(), floatSample, outputBufferCnt);
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();
@ -1794,7 +1802,7 @@ bool Effect::ProcessTrack(int count,
// The purpose was to remap split lines inside the selected region when
// a generator replaces it with sound of different duration. But
// 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,
// because the cutoff time for the step time warper was 44100 times too
// far from mT0.
@ -1813,12 +1821,7 @@ bool Effect::ProcessTrack(int count,
}
}
// Allow the plugin to cleanup
if (!ProcessFinalize())
{
return false;
}
} // End scope for cleanup
return rc;
}
@ -2125,20 +2128,26 @@ auto Effect::ModifyAnalysisTrack
// Else clear and DELETE mOutputTracks copies.
void Effect::ReplaceProcessedTracks(const bool bGoodResult)
{
wxASSERT(mOutputTracks); // Make sure we at least did the CopyInputTracks().
if (!bGoodResult) {
// Free resources, unless already freed.
// Processing failed or was cancelled so throw away the processed tracks.
mOutputTracks->Clear();
if ( mOutputTracks )
mOutputTracks->Clear();
// Reset map
mIMap.clear();
mOMap.clear();
mOutputTracksType = Track::None;
//TODO:undo the non-gui ODTask transfer
return;
}
// Assume resources need to be freed.
wxASSERT(mOutputTracks); // Make sure we at least did the CopyInputTracks().
auto iterOut = mOutputTracks->begin(), iterEnd = mOutputTracks->end();
size_t cnt = mOMap.size();
@ -2465,142 +2474,144 @@ void Effect::Preview(bool dryOnly)
return;
bool success = true;
double oldT0 = mT0;
double oldT1 = mT1;
auto vr0 = valueRestorer( mT0 );
auto vr1 = valueRestorer( mT1 );
// Most effects should stop at t1.
if (!mPreviewFullSelection)
mT1 = t1;
{
// Save the original track list
TrackList *saveTracks = mTracks;
auto cleanup = finally( [&] { mTracks = saveTracks; } );
// Save the original track list
TrackList *saveTracks = mTracks;
// Build NEW tracklist from rendering tracks
auto uTracks = std::make_unique<TrackList>();
mTracks = uTracks.get();
auto cleanup = finally( [&] {
mTracks = saveTracks;
// 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();
}
}
// Update track/group counts
CountWaveTracks();
// Apply effect
// Effect is already inited; we will call Process, End, and then Init
// again, so the state is exactly the way it was before Preview
// was called.
if (!dryOnly) {
ProgressDialog progress(GetName(),
_("Preparing preview"),
pdlgHideCancelButton); // Have only "Stop" button.
SetProgress sp(mProgress, &progress);
mIsPreview = true;
success = Process();
mIsPreview = false;
End();
GuardedCall< void >( [&]{ Init(); } );
}
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) {
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
// again, so the state is exactly the way it was before Preview
// was called.
// Update track/group counts
CountWaveTracks();
// Apply effect
if (!dryOnly) {
End();
Init();
ProgressDialog progress{
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
// for sure), but it is a nice visual cue that something is going on.
mApplyBtn->Disable();
auto cleanup = finally( [&] { mApplyBtn->Enable(); } );
mEffect->Apply();
mApplyBtn->Enable();
return;
}

View File

@ -287,7 +287,9 @@ protected:
virtual bool InitPass2();
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();
// Most effects just use the previewLength, but time-stretching/compressing
@ -470,8 +472,12 @@ protected:
WaveTrack *right,
sampleCount leftStart,
sampleCount rightStart,
sampleCount len);
sampleCount len,
FloatBuffers &inBuffer,
FloatBuffers &outBuffer,
ArrayOf< float * > &inBufPos,
ArrayOf< float *> &outBufPos);
//
// private data
//
@ -505,11 +511,6 @@ private:
size_t mNumAudioIn;
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 mBlockSize;
unsigned mNumChannels;

View File

@ -17,6 +17,8 @@
#if defined(EXPERIMENTAL_EFFECTS_RACK)
#include "../MemoryX.h"
#include "../UndoManager.h"
#include <wx/access.h>
#include <wx/defs.h>
#include <wx/bmpbuttn.h>
@ -289,17 +291,36 @@ void EffectRack::OnTimer(wxTimerEvent & WXUNUSED(evt))
void EffectRack::OnApply(wxCommandEvent & WXUNUSED(evt))
{
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++)
{
if (mPowerState[i])
{
project->OnEffect(mEffects[i]->GetID(),
AudacityProject::OnEffectFlags::kConfigured);
if (!project->OnEffect(mEffects[i]->GetID(),
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;
wxBitmapButton *btn = static_cast<wxBitmapButton *>(FindWindowById(ID_POWER + i));
wxBitmapButton *btn =
static_cast<wxBitmapButton *>(FindWindowById(ID_POWER + i));
btn->SetBitmapLabel(mPowerRaised);
btn->SetBitmapSelected(mPowerRaised);
}