From b8b4b23910634a1d4daf31655076d5b0d2ea72c9 Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Wed, 27 May 2015 02:42:02 -0500 Subject: [PATCH 1/4] Didn't handle multiple providers reporting the same path properly This can happen when a .dll, .so, etc. is found in the Plug-ins folder. The VST and LADSPA providers will report the same path since they don't really know at scan time if the .dll/.so is something they can really handle. --- src/PluginManager.cpp | 102 ++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index 4b22f381c..04a5abcf4 100755 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -371,9 +371,11 @@ enum STATE_COUNT }; +WX_DEFINE_ARRAY(PluginDescriptor *, DescriptorArray); + struct ItemData { - PluginDescriptor *plug; + DescriptorArray plugs; wxString name; wxString path; int state; @@ -582,7 +584,7 @@ void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) { int x; mEffects->GetTextExtent(mStates[i], &x, NULL); - colWidths[COL_State] = wxMax(colWidths[COL_State], x); + colWidths[COL_State] = wxMax(colWidths[COL_State], x + 4); // 2 pixel margin on each side } PluginManager & pm = PluginManager::Get(); @@ -598,7 +600,7 @@ void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) wxString path = plug.GetPath(); ItemData & item = mItems[path]; // will create new entry - item.plug = &plug; + item.plugs.Add(&plug); item.path = path; item.state = plug.IsEnabled() ? STATE_Enabled : STATE_Disabled; item.valid = plug.IsValid(); @@ -614,7 +616,7 @@ void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) { wxFileName fname = path; item.name = fname.GetName().Trim(false).Trim(true); - if (!plug.IsValid()) + if (!item.valid) { item.state = STATE_New; } @@ -929,7 +931,7 @@ void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) ItemData & item = iter->second; wxString path = item.path; - if (item.state == STATE_Enabled && item.plug->GetPluginType() == PluginTypeStub) + if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) { enableCount++; } @@ -952,7 +954,7 @@ void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) ItemData & item = iter->second; wxString path = item.path; - if (item.state == STATE_Enabled && item.plug->GetPluginType() == PluginTypeStub) + if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) { last3 = last3.AfterFirst(wxT('\n')) + item.path + wxT("\n"); status = progress.Update(++i, enableCount, wxString::Format(_("Enabling effect:\n\n%s"), last3.c_str())); @@ -961,21 +963,30 @@ void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) break; } - if (mm.RegisterPlugin(item.plug->GetProviderID(), path)) + // Try to register the plugin via each provider until one succeeds + for (size_t j = 0, cnt = item.plugs.GetCount(); j < cnt; j++) { - // Registration successful, so remove the stub - PluginID ID = item.plug->GetProviderID() + wxT("_") + path; - pm.mPlugins.erase(ID); + if (mm.RegisterPlugin(item.plugs[j]->GetProviderID(), path)) + { + for (size_t j = 0, cnt = item.plugs.GetCount(); j < cnt; j++) + { + pm.mPlugins.erase(item.plugs[j]->GetProviderID() + wxT("_") + path); + } + break; + } } } else if (item.state == STATE_New) { - item.plug->SetValid(false); + for (size_t j = 0, cnt = item.plugs.GetCount(); i < cnt; i++) + { + item.plugs[j]->SetValid(false); + } } else if (item.state != STATE_New) { - item.plug->SetEnabled(item.state == STATE_Enabled); - item.plug->SetValid(item.valid); + item.plugs[0]->SetEnabled(item.state == STATE_Enabled); + item.plugs[0]->SetValid(item.valid); } } @@ -2092,17 +2103,20 @@ void PluginManager::CheckForUpdates() continue; } - pathIndex.Add(plug.GetProviderID() + plug.GetPath().BeforeFirst(wxT(';'))); + pathIndex.Add(plug.GetPath().BeforeFirst(wxT(';'))); } - ProviderMap map; - - // Always check for and disable missing plugins + // Check all known plugins to ensure they are still valid and scan for new ones. // - // Since the user's saved presets are in the registery, never delete them. That is - // a job for the plugin manager UI (once it is written). + // All new plugins get a stub entry created that will remain in place until the + // user enables or disables the plugin. // - // Also check for plugins that are no longer valid. + // Becuase we use the plugins "path" as returned by the providers, we can actually + // have multiple providers report the same path since, at this point, they only + // know that the path might possibly be one supported by the provider. + // + // When the user enables the plugin, each provider that reported it will be asked + // to register the plugin. for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) { PluginDescriptor & plug = iter->second; @@ -2120,6 +2134,7 @@ void PluginManager::CheckForUpdates() { if (!mm.IsProviderValid(plugID, plugPath)) { + plug.SetEnabled(false); plug.SetValid(false); } else @@ -2128,11 +2143,17 @@ void PluginManager::CheckForUpdates() wxArrayString paths = mm.FindPluginsForProvider(plugID, plugPath); for (size_t i = 0, cnt = paths.GetCount(); i < cnt; i++) { - wxString path = paths[i]; - wxASSERT(!path.IsEmpty()); - if (pathIndex.Index(plugID + path.BeforeFirst(wxT(';'))) == wxNOT_FOUND) + wxString path = paths[i].BeforeFirst(wxT(';'));; + if (pathIndex.Index(path) == wxNOT_FOUND) { - map[path].Add(plugID); + PluginID ID = plugID + wxT("_") + path; + PluginDescriptor & plug = mPlugins[ID]; // This will create a new descriptor + plug.SetPluginType(PluginTypeStub); + plug.SetID(ID); + plug.SetProviderID(plugID); + plug.SetPath(path); + plug.SetEnabled(false); + plug.SetValid(false); } } } @@ -2140,35 +2161,10 @@ void PluginManager::CheckForUpdates() else if (plugType != PluginTypeNone && plugType != PluginTypeStub) { plug.SetValid(mm.IsPluginValid(plug.GetProviderID(), plugPath)); - } - } - - // The provider map now includes only paths that haven't been seen before, - // so create stub descriptors for them. - // - // In 2.1.0, they were called "placeholder" in the registry, but since 2.1.1+ - // use them slightly differently, they are not called "stub" in the registry - // and the "placeholder" are left intact. This way switching between 2.1.0 - // and 2.1.1+ doesn't cause rescans each time. - for (ProviderMap::iterator iter = map.begin(); iter != map.end(); ++iter) - { - wxString & path = iter->first; - wxArrayString & providers = iter->second; - - // Create a descriptor for each provider. Don't know why there would - // be multiple providers for the same path, but might as well. - for (size_t i = 0, cnt = providers.GetCount(); i < cnt; i++) - { - PluginID ID = providers[i] + wxT("_") + path; - // Stub descriptors have a plugin type of PluginTypeNone and the ID - // is the path. They are also marked as disabled and not valid. - PluginDescriptor & plug = mPlugins[ID]; // This will create a new descriptor - plug.SetPluginType(PluginTypeStub); - plug.SetID(ID); - plug.SetProviderID(providers[i]); - plug.SetPath(path); - plug.SetEnabled(false); - plug.SetValid(false); + if (!plug.IsValid()) + { + plug.SetEnabled(false); + } } } From c372519e521700620485e803d2c34f79a458e4dd Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Wed, 27 May 2015 08:11:02 -0500 Subject: [PATCH 2/4] Use the correct variable --- src/PluginManager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index 04a5abcf4..2b3615d6e 100755 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -947,7 +947,6 @@ void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) ProgressDialog progress(_("Plugin Manager: Effects"), msg, pdlgHideStopButton); progress.CenterOnParent(); - int status; int i = 0; for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) { @@ -957,7 +956,7 @@ void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) { last3 = last3.AfterFirst(wxT('\n')) + item.path + wxT("\n"); - status = progress.Update(++i, enableCount, wxString::Format(_("Enabling effect:\n\n%s"), last3.c_str())); + int status = progress.Update(++i, enableCount, wxString::Format(_("Enabling effect:\n\n%s"), last3.c_str())); if (!status) { break; @@ -978,7 +977,7 @@ void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) } else if (item.state == STATE_New) { - for (size_t j = 0, cnt = item.plugs.GetCount(); i < cnt; i++) + for (size_t j = 0, cnt = item.plugs.GetCount(); j < cnt; j++) { item.plugs[j]->SetValid(false); } From 0d62be77765f758dab08c50b93ab40380db855d8 Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Wed, 27 May 2015 08:42:15 -0500 Subject: [PATCH 3/4] Use a common method for the duration format in generators --- include/audacity/EffectInterface.h | 3 +- src/effects/DtmfGen.cpp | 11 ++--- src/effects/Effect.cpp | 71 ++++++++++++++--------------- src/effects/Effect.h | 5 +- src/effects/Noise.cpp | 19 ++++---- src/effects/Silence.cpp | 7 +-- src/effects/ToneGen.cpp | 19 ++++---- src/effects/VST/VSTEffect.cpp | 7 +-- src/effects/ladspa/LadspaEffect.cpp | 7 +-- src/effects/lv2/LV2Effect.cpp | 7 +-- 10 files changed, 69 insertions(+), 87 deletions(-) mode change 100644 => 100755 include/audacity/EffectInterface.h mode change 100644 => 100755 src/effects/DtmfGen.cpp mode change 100644 => 100755 src/effects/Effect.h mode change 100644 => 100755 src/effects/Noise.cpp mode change 100644 => 100755 src/effects/Silence.cpp mode change 100644 => 100755 src/effects/ToneGen.cpp mode change 100644 => 100755 src/effects/ladspa/LadspaEffect.cpp mode change 100644 => 100755 src/effects/lv2/LV2Effect.cpp diff --git a/include/audacity/EffectInterface.h b/include/audacity/EffectInterface.h old mode 100644 new mode 100755 index 7354a9f6c..78e894781 --- a/include/audacity/EffectInterface.h +++ b/include/audacity/EffectInterface.h @@ -94,7 +94,8 @@ public: virtual ~EffectHostInterface() {}; virtual double GetDefaultDuration() = 0; - virtual double GetDuration(bool *isSelection = NULL) = 0; + virtual double GetDuration() = 0; + virtual wxString GetDurationFormat() = 0; virtual void SetDuration(double seconds) = 0; virtual bool Apply() = 0; diff --git a/src/effects/DtmfGen.cpp b/src/effects/DtmfGen.cpp old mode 100644 new mode 100755 index b90225502..90ba2a300 --- a/src/effects/DtmfGen.cpp +++ b/src/effects/DtmfGen.cpp @@ -303,16 +303,13 @@ void EffectDtmf::PopulateOrExchange(ShuttleGui & S) vldAmp.SetRange(MIN_Amplitude, MAX_Amplitude); S.Id(ID_Amplitude).AddTextBox(_("Amplitude (0-1):"), wxT(""), 10)->SetValidator(vldAmp); - bool isSelection; - double duration = GetDuration(&isSelection); - S.AddPrompt(_("Duration:")); mDtmfDurationT = new NumericTextCtrl(NumericConverter::TIME, S.GetParent(), ID_Duration, - isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - duration, + GetDurationFormat(), + GetDuration(), mProjectRate, wxDefaultPosition, wxDefaultSize, @@ -370,8 +367,8 @@ bool EffectDtmf::TransferDataFromWindow() return false; } -// dtmfDutyCycle = (double) mDtmfDutyCycleS->GetValue() / SCL_DutyCycle; -// SetDuration(mDtmfDurationT->GetValue()); + dtmfDutyCycle = (double) mDtmfDutyCycleS->GetValue() / SCL_DutyCycle; + SetDuration(mDtmfDurationT->GetValue()); // recalculate to make sure all values are up-to-date. This is especially // important if the user did not change any values in the dialog diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index afd98bd71..ab3644037 100755 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -705,49 +705,21 @@ double Effect::GetDefaultDuration() return 30.0; } -double Effect::GetDuration(bool *isSelection) +double Effect::GetDuration() { - if (mT1 > mT0) - { - // there is a selection: let's fit in there... - // MJS: note that this is just for the TTC and is independent of the track rate - // but we do need to make sure we have the right number of samples at the project rate - double quantMT0 = QUANTIZED_TIME(mT0, mProjectRate); - double quantMT1 = QUANTIZED_TIME(mT1, mProjectRate); - mDuration = quantMT1 - quantMT0; - - if (isSelection) - { - *isSelection = true; - } - - return mDuration; - } - - if (isSelection) - { - *isSelection = false; - } - - GetPrivateConfig(GetCurrentSettingsGroup(), wxT("LastUsedDuration"), mDuration, 0.0); - if (mDuration > 0.0) - { - return mDuration; - } - if (mDuration < 0.0) { mDuration = 0.0; } - if (GetType() == EffectTypeGenerate) - { - mDuration = GetDefaultDuration(); - } - return mDuration; } +wxString Effect::GetDurationFormat() +{ + return mDurationFormat; +} + void Effect::SetDuration(double seconds) { if (seconds < 0.0) @@ -755,14 +727,17 @@ void Effect::SetDuration(double seconds) seconds = 0.0; } - if (mDuration != seconds) + if (GetType() == EffectTypeGenerate) { SetPrivateConfig(GetCurrentSettingsGroup(), wxT("LastUsedDuration"), seconds); } mDuration = seconds; + mT1 = mT0 + mDuration; mSetDuration = mDuration; + mIsSelection = false; + return; } @@ -1170,9 +1145,33 @@ bool Effect::DoEffect(wxWindow *parent, mProjectRate = projectRate; mParent = parent; mTracks = list; + + bool isSelection = false; + + mDuration = 0.0; + + if (GetType() == EffectTypeGenerate) + { + GetPrivateConfig(GetCurrentSettingsGroup(), wxT("LastUsedDuration"), mDuration, GetDefaultDuration()); + } + mT0 = selectedRegion->t0(); mT1 = selectedRegion->t1(); - mDuration = GetDuration(); + if (mT1 > mT0) + { + // there is a selection: let's fit in there... + // MJS: note that this is just for the TTC and is independent of the track rate + // but we do need to make sure we have the right number of samples at the project rate + double quantMT0 = QUANTIZED_TIME(mT0, mProjectRate); + double quantMT1 = QUANTIZED_TIME(mT1, mProjectRate); + mDuration = quantMT1 - quantMT0; + mT1 = mT0 + mDuration; + + isSelection = true; + } + + mDurationFormat = isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"); + #ifdef EXPERIMENTAL_SPECTRAL_EDITING mF0 = selectedRegion->f0(); mF1 = selectedRegion->f1(); diff --git a/src/effects/Effect.h b/src/effects/Effect.h old mode 100644 new mode 100755 index e61d43039..e630c9916 --- a/src/effects/Effect.h +++ b/src/effects/Effect.h @@ -144,7 +144,8 @@ class AUDACITY_DLL_API Effect : public wxEvtHandler, // EffectHostInterface implementation virtual double GetDefaultDuration(); - virtual double GetDuration(bool *isSelection = NULL); + virtual double GetDuration(); + virtual wxString GetDurationFormat(); virtual void SetDuration(double duration); virtual bool Apply(); @@ -400,7 +401,9 @@ private: bool mIsLinearEffect; bool mPreviewWithNotSelected; + bool mIsSelection; double mDuration; + wxString mDurationFormat; // mSetDuration should ONLY be set when SetDuration() is called. double mSetDuration; diff --git a/src/effects/Noise.cpp b/src/effects/Noise.cpp old mode 100644 new mode 100755 index 8ec5a7b33..7bf3ebbd2 --- a/src/effects/Noise.cpp +++ b/src/effects/Noise.cpp @@ -224,20 +224,17 @@ void EffectNoise::PopulateOrExchange(ShuttleGui & S) vldAmp.SetRange(MIN_Amp, MAX_Amp); S.AddTextBox(_("Amplitude (0-1):"), wxT(""), 12)->SetValidator(vldAmp); - bool isSelection; - double duration = GetDuration(&isSelection); - S.AddPrompt(_("Duration:")); mNoiseDurationT = new NumericTextCtrl(NumericConverter::TIME, - S.GetParent(), - wxID_ANY, - isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - duration, - mProjectRate, - wxDefaultPosition, - wxDefaultSize, - true); + S.GetParent(), + wxID_ANY, + GetDurationFormat(), + GetDuration(), + mProjectRate, + wxDefaultPosition, + wxDefaultSize, + true); mNoiseDurationT->SetName(_("Duration")); mNoiseDurationT->EnableMenu(); S.AddWindow(mNoiseDurationT, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL); diff --git a/src/effects/Silence.cpp b/src/effects/Silence.cpp old mode 100644 new mode 100755 index 00f2306fe..bf2707ab6 --- a/src/effects/Silence.cpp +++ b/src/effects/Silence.cpp @@ -55,16 +55,13 @@ void EffectSilence::PopulateOrExchange(ShuttleGui & S) { S.StartHorizontalLay(); { - bool isSelection; - double duration = GetDuration(&isSelection); - S.AddPrompt(_("Duration:")); mDurationT = new NumericTextCtrl(NumericConverter::TIME, S.GetParent(), wxID_ANY, - isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - duration, + GetDurationFormat(), + GetDuration(), mProjectRate, wxDefaultPosition, wxDefaultSize, diff --git a/src/effects/ToneGen.cpp b/src/effects/ToneGen.cpp old mode 100644 new mode 100755 index 66a1c7a51..dd425bc35 --- a/src/effects/ToneGen.cpp +++ b/src/effects/ToneGen.cpp @@ -394,20 +394,17 @@ void EffectToneGen::PopulateOrExchange(ShuttleGui & S) t->SetValidator(vldAmplitude); } - bool isSelection; - double duration = GetDuration(&isSelection); - S.AddPrompt(_("Duration:")); mToneDurationT = new NumericTextCtrl(NumericConverter::TIME, - S.GetParent(), - wxID_ANY, - isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - duration, - mProjectRate, - wxDefaultPosition, - wxDefaultSize, - true); + S.GetParent(), + wxID_ANY, + GetDurationFormat(), + GetDuration(), + mProjectRate, + wxDefaultPosition, + wxDefaultSize, + true); mToneDurationT->SetName(_("Duration")); mToneDurationT->EnableMenu(); S.AddWindow(mToneDurationT, wxALIGN_LEFT | wxALL); diff --git a/src/effects/VST/VSTEffect.cpp b/src/effects/VST/VSTEffect.cpp index b277f8e05..c51f8e3c4 100755 --- a/src/effects/VST/VSTEffect.cpp +++ b/src/effects/VST/VSTEffect.cpp @@ -3515,17 +3515,14 @@ void VSTEffect::BuildPlain() // Add the duration control for generators if (GetType() == EffectTypeGenerate) { - bool isSelection; - double duration = mHost->GetDuration(&isSelection); - wxControl *item = new wxStaticText(scroller, 0, _("Duration:")); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); mDuration = new NumericTextCtrl(NumericConverter::TIME, scroller, ID_Duration, - isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - duration, + mHost->GetDurationFormat(), + mHost->GetDuration(), mSampleRate, wxDefaultPosition, wxDefaultSize, diff --git a/src/effects/ladspa/LadspaEffect.cpp b/src/effects/ladspa/LadspaEffect.cpp old mode 100644 new mode 100755 index 113dc0367..cb5a83ab5 --- a/src/effects/ladspa/LadspaEffect.cpp +++ b/src/effects/ladspa/LadspaEffect.cpp @@ -1146,17 +1146,14 @@ bool LadspaEffect::PopulateUI(wxWindow *parent) // Add the duration control for generators if (GetType() == EffectTypeGenerate) { - bool isSelection; - double duration = mHost->GetDuration(&isSelection); - item = new wxStaticText(w, 0, _("Duration:")); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); mDuration = new NumericTextCtrl(NumericConverter::TIME, w, ID_Duration, - isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - duration, + mHost->GetDurationFormat(), + mHost->GetDuration(), mSampleRate, wxDefaultPosition, wxDefaultSize, diff --git a/src/effects/lv2/LV2Effect.cpp b/src/effects/lv2/LV2Effect.cpp old mode 100644 new mode 100755 index 29f82e5ff..318d0d9d2 --- a/src/effects/lv2/LV2Effect.cpp +++ b/src/effects/lv2/LV2Effect.cpp @@ -1579,17 +1579,14 @@ bool LV2Effect::BuildPlain() wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); - bool isSelection; - double duration = mHost->GetDuration(&isSelection); - wxWindow *item = new wxStaticText(w, 0, _("&Duration:")); sizer->Add(item, 0, wxALIGN_CENTER | wxALL, 5); mDuration = new NumericTextCtrl(NumericConverter::TIME, w, ID_Duration, - isSelection ? _("hh:mm:ss + samples") : _("hh:mm:ss + milliseconds"), - duration, + mHost->GetDurationFormat(), + mHost->GetDuration(), mSampleRate, wxDefaultPosition, wxDefaultSize, From 19acf89d2a2f33ea98c152248d2b6914399e66f7 Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Wed, 27 May 2015 08:50:00 -0500 Subject: [PATCH 4/4] Fix for bug #869 --- src/effects/Effect.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index ab3644037..7d3bd42f7 100755 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -2522,21 +2522,24 @@ void Effect::Preview(bool dryOnly) if (token) { int previewing = eProgressSuccess; - mProgress = new ProgressDialog(GetName(), - _("Previewing"), pdlgHideCancelButton); + // 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 = + new ProgressDialog(GetName(), _("Previewing"), pdlgHideCancelButton); while (gAudioIO->IsStreamActive(token) && previewing == eProgressSuccess) { ::wxMilliSleep(100); - previewing = mProgress->Update(gAudioIO->GetStreamTime() - mT0, mT1); + previewing = progress->Update(gAudioIO->GetStreamTime() - mT0, mT1); } + + delete progress; + gAudioIO->StopStream(); while (gAudioIO->IsBusy()) { ::wxMilliSleep(100); } - - delete mProgress; - mProgress = NULL; } else { wxMessageBox(_("Error while opening sound device. Please check the playback device settings and the project sample rate."),