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:
parent
2cbdd1cc43
commit
79c3bef2ce
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user