audacia/src/effects/Effect.cpp

4145 lines
102 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
Effect.cpp
Dominic Mazzoni
Vaughan Johnson
Martyn Shaw
*******************************************************************//**
\class Effect
\brief Base class for many of the effects in Audacity.
*//****************************************************************//**
\class EffectDialog
\brief New (Jun-2006) base class for effects dialogs. Likely to get
greater use in future.
*//*******************************************************************/
#include "../Audacity.h"
#include "Effect.h"
#include <algorithm>
#include <wx/defs.h>
#include <wx/hashmap.h>
#include <wx/sizer.h>
#include <wx/stockitem.h>
#include <wx/string.h>
#include <wx/tglbtn.h>
#include <wx/timer.h>
#include <wx/utils.h>
#include <wx/log.h>
#include "audacity/ConfigInterface.h"
#include "../AudacityException.h"
#include "../AudioIO.h"
#include "../LabelTrack.h"
#include "../Mix.h"
#include "../Prefs.h"
#include "../Project.h"
#include "../ShuttleGui.h"
#include "../Shuttle.h"
#include "../WaveTrack.h"
#include "../toolbars/ControlToolBar.h"
#include "../widgets/AButton.h"
#include "../widgets/ProgressDialog.h"
#include "../ondemand/ODManager.h"
#include "TimeWarper.h"
#include "nyquist/Nyquist.h"
#include "../widgets/HelpSystem.h"
#include "../widgets/LinkingHtmlWindow.h"
#include "../widgets/NumericTextCtrl.h"
#include "../widgets/ErrorDialog.h"
#include "../FileNames.h"
#include "../commands/AudacityCommand.h"
#include "../commands/CommandContext.h"
#if defined(__WXMAC__)
#include <Cocoa/Cocoa.h>
#endif
#include "../Experimental.h"
#include "../commands/ScreenshotCommand.h"
#ifndef __AUDACITY_OLD_STD__
#include <unordered_map>
#endif
static const int kDummyID = 20000;
static const int kSaveAsID = 20001;
static const int kImportID = 20002;
static const int kExportID = 20003;
static const int kDefaultsID = 20004;
static const int kOptionsID = 20005;
static const int kUserPresetsDummyID = 20006;
static const int kDeletePresetDummyID = 20007;
static const int kMenuID = 20100;
static const int kEnableID = 20101;
static const int kPlayID = 20102;
static const int kRewindID = 20103;
static const int kFFwdID = 20104;
static const int kPlaybackID = 20105;
static const int kCaptureID = 20106;
static const int kUserPresetsID = 21000;
static const int kDeletePresetID = 22000;
static const int kFactoryPresetsID = 23000;
const wxString Effect::kUserPresetIdent = wxT("User Preset:");
const wxString Effect::kFactoryPresetIdent = wxT("Factory Preset:");
const wxString Effect::kCurrentSettingsIdent = wxT("<Current Settings>");
const wxString Effect::kFactoryDefaultsIdent = wxT("<Factory Defaults>");
using t2bHash = std::unordered_map< void*, bool >;
Effect::Effect()
{
mClient = NULL;
mTracks = NULL;
mOutputTracksType = Track::None;
mT0 = 0.0;
mT1 = 0.0;
mDuration = 0.0;
mIsPreview = false;
mIsLinearEffect = false;
mPreviewWithNotSelected = false;
mPreviewFullSelection = false;
mNumTracks = 0;
mNumGroups = 0;
mProgress = NULL;
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount = 1; // Effects are initially suspended
mRealtimeSuspendLock.Leave();
mUIParent = NULL;
mUIDialog = NULL;
mNumAudioIn = 0;
mNumAudioOut = 0;
mBufferSize = 0;
mBlockSize = 0;
mNumChannels = 0;
mUIDebug = false;
AudacityProject *p = GetActiveProject();
mProjectRate = p ? p->GetRate() : 44100;
mIsBatch = false;
}
Effect::~Effect()
{
if (mUIDialog)
{
mUIDialog->Close();
}
}
// EffectDefinitionInterface implementation
EffectType Effect::GetType()
{
if (mClient)
{
return mClient->GetType();
}
return EffectTypeNone;
}
wxString Effect::GetPath()
{
if (mClient)
{
return mClient->GetPath();
}
return BUILTIN_EFFECT_PREFIX + GetSymbol().Internal();
}
IdentInterfaceSymbol Effect::GetSymbol()
{
if (mClient)
{
return mClient->GetSymbol();
}
return {};
}
IdentInterfaceSymbol Effect::GetVendor()
{
if (mClient)
{
return mClient->GetVendor();
}
return XO("Audacity");
}
wxString Effect::GetVersion()
{
if (mClient)
{
return mClient->GetVersion();
}
return AUDACITY_VERSION_STRING;
}
wxString Effect::GetDescription()
{
if (mClient)
{
return mClient->GetDescription();
}
return wxEmptyString;
}
IdentInterfaceSymbol Effect::GetFamilyId()
{
if (mClient)
{
return mClient->GetFamilyId();
}
// Unusually, the internal and visible strings differ for the built-in
// effect family.
return { wxT("Audacity"), XO("Built-in") };
}
bool Effect::IsInteractive()
{
if (mClient)
{
return mClient->IsInteractive();
}
return true;
}
bool Effect::IsDefault()
{
if (mClient)
{
return mClient->IsDefault();
}
return true;
}
bool Effect::IsLegacy()
{
if (mClient)
{
return false;
}
return true;
}
bool Effect::SupportsRealtime()
{
if (mClient)
{
return mClient->SupportsRealtime();
}
return false;
}
bool Effect::SupportsAutomation()
{
if (mClient)
{
return mClient->SupportsAutomation();
}
return true;
}
// EffectClientInterface implementation
bool Effect::SetHost(EffectHostInterface *host)
{
if (mClient)
{
return mClient->SetHost(host);
}
return true;
}
unsigned Effect::GetAudioInCount()
{
if (mClient)
{
return mClient->GetAudioInCount();
}
return 0;
}
unsigned Effect::GetAudioOutCount()
{
if (mClient)
{
return mClient->GetAudioOutCount();
}
return 0;
}
int Effect::GetMidiInCount()
{
if (mClient)
{
return mClient->GetMidiInCount();
}
return 0;
}
int Effect::GetMidiOutCount()
{
if (mClient)
{
return mClient->GetMidiOutCount();
}
return 0;
}
void Effect::SetSampleRate(double rate)
{
if (mClient)
{
mClient->SetSampleRate(rate);
}
mSampleRate = rate;
}
size_t Effect::SetBlockSize(size_t maxBlockSize)
{
if (mClient)
{
return mClient->SetBlockSize(maxBlockSize);
}
mBlockSize = maxBlockSize;
return mBlockSize;
}
sampleCount Effect::GetLatency()
{
if (mClient)
{
return mClient->GetLatency();
}
return 0;
}
size_t Effect::GetTailSize()
{
if (mClient)
{
return mClient->GetTailSize();
}
return 0;
}
bool Effect::IsReady()
{
if (mClient)
{
return mClient->IsReady();
}
return true;
}
bool Effect::ProcessInitialize(sampleCount totalLen, ChannelNames chanMap)
{
if (mClient)
{
return mClient->ProcessInitialize(totalLen, chanMap);
}
return true;
}
bool Effect::ProcessFinalize()
{
if (mClient)
{
return mClient->ProcessFinalize();
}
return true;
}
size_t Effect::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
{
if (mClient)
{
return mClient->ProcessBlock(inBlock, outBlock, blockLen);
}
return 0;
}
bool Effect::RealtimeInitialize()
{
if (mClient)
{
mBlockSize = mClient->SetBlockSize(512);
return mClient->RealtimeInitialize();
}
mBlockSize = 512;
return false;
}
bool Effect::RealtimeAddProcessor(unsigned numChannels, float sampleRate)
{
if (mClient)
{
return mClient->RealtimeAddProcessor(numChannels, sampleRate);
}
return true;
}
bool Effect::RealtimeFinalize()
{
if (mClient)
{
return mClient->RealtimeFinalize();
}
return false;
}
bool Effect::RealtimeSuspend()
{
if (mClient)
{
if (mClient->RealtimeSuspend())
{
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount++;
mRealtimeSuspendLock.Leave();
return true;
}
return false;
}
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount++;
mRealtimeSuspendLock.Leave();
return true;
}
bool Effect::RealtimeResume()
{
if (mClient)
{
if (mClient->RealtimeResume())
{
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount--;
mRealtimeSuspendLock.Leave();
return true;
}
return false;
}
mRealtimeSuspendLock.Enter();
mRealtimeSuspendCount--;
mRealtimeSuspendLock.Leave();
return true;
}
bool Effect::RealtimeProcessStart()
{
if (mClient)
{
return mClient->RealtimeProcessStart();
}
return true;
}
size_t Effect::RealtimeProcess(int group,
float **inbuf,
float **outbuf,
size_t numSamples)
{
if (mClient)
{
return mClient->RealtimeProcess(group, inbuf, outbuf, numSamples);
}
return 0;
}
bool Effect::RealtimeProcessEnd()
{
if (mClient)
{
return mClient->RealtimeProcessEnd();
}
return true;
}
bool Effect::ShowInterface(wxWindow *parent, bool forceModal)
{
if (!IsInteractive())
{
return true;
}
if (mUIDialog)
{
if ( mUIDialog->Close(true) )
mUIDialog = nullptr;
return false;
}
if (mClient)
{
return mClient->ShowInterface(parent, forceModal);
}
// mUIDialog is null
auto cleanup = valueRestorer( mUIDialog );
mUIDialog = CreateUI(parent, this);
if (!mUIDialog)
{
return false;
}
mUIDialog->Layout();
mUIDialog->Fit();
mUIDialog->SetMinSize(mUIDialog->GetSize());
if( ScreenshotCommand::MayCapture( mUIDialog ) )
return false;
if( SupportsRealtime() && !forceModal )
{
mUIDialog->Show();
cleanup.release();
// Return false to bypass effect processing
return false;
}
bool res = mUIDialog->ShowModal() != 0;
return res;
}
bool Effect::GetAutomationParameters(CommandParameters & parms)
{
if (mClient)
{
return mClient->GetAutomationParameters(parms);
}
return true;
}
bool Effect::SetAutomationParameters(CommandParameters & parms)
{
if (mClient)
{
return mClient->SetAutomationParameters(parms);
}
return true;
}
bool Effect::LoadUserPreset(const wxString & name)
{
if (mClient)
{
return mClient->LoadUserPreset(name);
}
wxString parms;
if (!GetPrivateConfig(name, wxT("Parameters"), parms))
{
return false;
}
return SetAutomationParameters(parms);
}
bool Effect::SaveUserPreset(const wxString & name)
{
if (mClient)
{
return mClient->SaveUserPreset(name);
}
wxString parms;
if (!GetAutomationParameters(parms))
{
return false;
}
return SetPrivateConfig(name, wxT("Parameters"), parms);
}
wxArrayString Effect::GetFactoryPresets()
{
if (mClient)
{
return mClient->GetFactoryPresets();
}
return wxArrayString();
}
bool Effect::LoadFactoryPreset(int id)
{
if (mClient)
{
return mClient->LoadFactoryPreset(id);
}
return true;
}
bool Effect::LoadFactoryDefaults()
{
if (mClient)
{
return mClient->LoadFactoryDefaults();
}
return LoadUserPreset(GetFactoryDefaultsGroup());
}
// EffectUIClientInterface implementation
void Effect::SetHostUI(EffectUIHostInterface *WXUNUSED(host))
{
}
bool Effect::PopulateUI(wxWindow *parent)
{
mUIParent = parent;
mUIParent->PushEventHandler(this);
// LoadUserPreset(GetCurrentSettingsGroup());
ShuttleGui S(mUIParent, eIsCreating);
PopulateOrExchange(S);
mUIParent->SetMinSize(mUIParent->GetSizer()->GetMinSize());
return true;
}
bool Effect::IsGraphicalUI()
{
return false;
}
bool Effect::ValidateUI()
{
return mUIParent->Validate();
}
bool Effect::HideUI()
{
return true;
}
bool Effect::CloseUI()
{
if (mUIParent)
mUIParent->RemoveEventHandler(this);
mUIParent = NULL;
mUIDialog = NULL;
return true;
}
bool Effect::CanExportPresets()
{
return false;
}
void Effect::ExportPresets()
{
}
void Effect::ImportPresets()
{
}
bool Effect::HasOptions()
{
return false;
}
void Effect::ShowOptions()
{
}
// EffectHostInterface implementation
double Effect::GetDefaultDuration()
{
return 30.0;
}
double Effect::GetDuration()
{
if (mDuration < 0.0)
{
mDuration = 0.0;
}
return mDuration;
}
NumericFormatId Effect::GetDurationFormat()
{
return mDurationFormat;
}
NumericFormatId Effect::GetSelectionFormat()
{
return GetActiveProject()->GetSelectionFormat();
}
void Effect::SetDuration(double seconds)
{
if (seconds < 0.0)
{
seconds = 0.0;
}
if (GetType() == EffectTypeGenerate)
{
SetPrivateConfig(GetCurrentSettingsGroup(), wxT("LastUsedDuration"), seconds);
}
mDuration = seconds;
mIsSelection = false;
return;
}
bool Effect::Apply()
{
CommandContext context( *GetActiveProject() );
// This is absolute hackage...but easy and I can't think of another way just now.
//
// It should callback to the EffectManager to kick off the processing
return GetActiveProject()->DoEffect(GetID(), context,
AudacityProject::OnEffectFlags::kConfigured);
}
void Effect::Preview()
{
Preview(false);
}
wxDialog *Effect::CreateUI(wxWindow *parent, EffectUIClientInterface *client)
{
Destroy_ptr<EffectUIHost> dlg
{ safenew EffectUIHost{ parent, this, client} };
if (dlg->Initialize())
{
// release() is safe because parent will own it
return dlg.release();
}
return NULL;
}
wxString Effect::GetUserPresetsGroup(const wxString & name)
{
wxString group = wxT("UserPresets");
if (!name.IsEmpty())
{
group += wxCONFIG_PATH_SEPARATOR + name;
}
return group;
}
wxString Effect::GetCurrentSettingsGroup()
{
return wxT("CurrentSettings");
}
wxString Effect::GetFactoryDefaultsGroup()
{
return wxT("FactoryDefaults");
}
wxString Effect::GetSavedStateGroup()
{
return wxT("SavedState");
}
// ConfigClientInterface implementation
bool Effect::HasSharedConfigGroup(const wxString & group)
{
return PluginManager::Get().HasSharedConfigGroup(GetID(), group);
}
bool Effect::GetSharedConfigSubgroups(const wxString & group, wxArrayString & subgroups)
{
return PluginManager::Get().GetSharedConfigSubgroups(GetID(), group, subgroups);
}
bool Effect::GetSharedConfig(const wxString & group, const wxString & key, wxString & value, const wxString & defval)
{
return PluginManager::Get().GetSharedConfig(GetID(), group, key, value, defval);
}
bool Effect::GetSharedConfig(const wxString & group, const wxString & key, int & value, int defval)
{
return PluginManager::Get().GetSharedConfig(GetID(), group, key, value, defval);
}
bool Effect::GetSharedConfig(const wxString & group, const wxString & key, bool & value, bool defval)
{
return PluginManager::Get().GetSharedConfig(GetID(), group, key, value, defval);
}
bool Effect::GetSharedConfig(const wxString & group, const wxString & key, float & value, float defval)
{
return PluginManager::Get().GetSharedConfig(GetID(), group, key, value, defval);
}
bool Effect::GetSharedConfig(const wxString & group, const wxString & key, double & value, double defval)
{
return PluginManager::Get().GetSharedConfig(GetID(), group, key, value, defval);
}
bool Effect::SetSharedConfig(const wxString & group, const wxString & key, const wxString & value)
{
return PluginManager::Get().SetSharedConfig(GetID(), group, key, value);
}
bool Effect::SetSharedConfig(const wxString & group, const wxString & key, const int & value)
{
return PluginManager::Get().SetSharedConfig(GetID(), group, key, value);
}
bool Effect::SetSharedConfig(const wxString & group, const wxString & key, const bool & value)
{
return PluginManager::Get().SetSharedConfig(GetID(), group, key, value);
}
bool Effect::SetSharedConfig(const wxString & group, const wxString & key, const float & value)
{
return PluginManager::Get().SetSharedConfig(GetID(), group, key, value);
}
bool Effect::SetSharedConfig(const wxString & group, const wxString & key, const double & value)
{
return PluginManager::Get().SetSharedConfig(GetID(), group, key, value);
}
bool Effect::RemoveSharedConfigSubgroup(const wxString & group)
{
return PluginManager::Get().RemoveSharedConfigSubgroup(GetID(), group);
}
bool Effect::RemoveSharedConfig(const wxString & group, const wxString & key)
{
return PluginManager::Get().RemoveSharedConfig(GetID(), group, key);
}
bool Effect::HasPrivateConfigGroup(const wxString & group)
{
return PluginManager::Get().HasPrivateConfigGroup(GetID(), group);
}
bool Effect::GetPrivateConfigSubgroups(const wxString & group, wxArrayString & subgroups)
{
return PluginManager::Get().GetPrivateConfigSubgroups(GetID(), group, subgroups);
}
bool Effect::GetPrivateConfig(const wxString & group, const wxString & key, wxString & value, const wxString & defval)
{
return PluginManager::Get().GetPrivateConfig(GetID(), group, key, value, defval);
}
bool Effect::GetPrivateConfig(const wxString & group, const wxString & key, int & value, int defval)
{
return PluginManager::Get().GetPrivateConfig(GetID(), group, key, value, defval);
}
bool Effect::GetPrivateConfig(const wxString & group, const wxString & key, bool & value, bool defval)
{
return PluginManager::Get().GetPrivateConfig(GetID(), group, key, value, defval);
}
bool Effect::GetPrivateConfig(const wxString & group, const wxString & key, float & value, float defval)
{
return PluginManager::Get().GetPrivateConfig(GetID(), group, key, value, defval);
}
bool Effect::GetPrivateConfig(const wxString & group, const wxString & key, double & value, double defval)
{
return PluginManager::Get().GetPrivateConfig(GetID(), group, key, value, defval);
}
bool Effect::SetPrivateConfig(const wxString & group, const wxString & key, const wxString & value)
{
return PluginManager::Get().SetPrivateConfig(GetID(), group, key, value);
}
bool Effect::SetPrivateConfig(const wxString & group, const wxString & key, const int & value)
{
return PluginManager::Get().SetPrivateConfig(GetID(), group, key, value);
}
bool Effect::SetPrivateConfig(const wxString & group, const wxString & key, const bool & value)
{
return PluginManager::Get().SetPrivateConfig(GetID(), group, key, value);
}
bool Effect::SetPrivateConfig(const wxString & group, const wxString & key, const float & value)
{
return PluginManager::Get().SetPrivateConfig(GetID(), group, key, value);
}
bool Effect::SetPrivateConfig(const wxString & group, const wxString & key, const double & value)
{
return PluginManager::Get().SetPrivateConfig(GetID(), group, key, value);
}
bool Effect::RemovePrivateConfigSubgroup(const wxString & group)
{
return PluginManager::Get().RemovePrivateConfigSubgroup(GetID(), group);
}
bool Effect::RemovePrivateConfig(const wxString & group, const wxString & key)
{
return PluginManager::Get().RemovePrivateConfig(GetID(), group, key);
}
// Effect implementation
PluginID Effect::GetID()
{
if (mClient)
{
return PluginManager::GetID(mClient);
}
return PluginManager::GetID(this);
}
bool Effect::Startup(EffectClientInterface *client)
{
// Let destructor know we need to be shutdown
mClient = client;
// Set host so client startup can use our services
if (!SetHost(this))
{
// Bail if the client startup fails
mClient = NULL;
return false;
}
mNumAudioIn = GetAudioInCount();
mNumAudioOut = GetAudioOutCount();
bool haveDefaults;
GetPrivateConfig(GetFactoryDefaultsGroup(), wxT("Initialized"), haveDefaults, false);
if (!haveDefaults)
{
SaveUserPreset(GetFactoryDefaultsGroup());
SetPrivateConfig(GetFactoryDefaultsGroup(), wxT("Initialized"), true);
}
LoadUserPreset(GetCurrentSettingsGroup());
return Startup();
}
bool Effect::Startup()
{
return true;
}
bool Effect::GetAutomationParameters(wxString & parms)
{
CommandParameters eap;
if (mUIDialog && !TransferDataFromWindow())
{
return false;
}
ShuttleGetAutomation S;
S.mpEap = &eap;
if( DefineParams( S ) ){
;// got eap value using DefineParams.
}
// Won't be needed in future
else if (!GetAutomationParameters(eap))
{
return false;
}
return eap.GetParameters(parms);
}
bool Effect::SetAutomationParameters(const wxString & parms)
{
wxString preset = parms;
bool success = false;
if (preset.StartsWith(kUserPresetIdent))
{
preset.Replace(kUserPresetIdent, wxEmptyString, false);
success = LoadUserPreset(GetUserPresetsGroup(preset));
}
else if (preset.StartsWith(kFactoryPresetIdent))
{
preset.Replace(kFactoryPresetIdent, wxEmptyString, false);
wxArrayString presets = GetFactoryPresets();
success = LoadFactoryPreset(presets.Index(preset));
}
else if (preset.StartsWith(kCurrentSettingsIdent))
{
preset.Replace(kCurrentSettingsIdent, wxEmptyString, false);
success = LoadUserPreset(GetCurrentSettingsGroup());
}
else if (preset.StartsWith(kFactoryDefaultsIdent))
{
preset.Replace(kFactoryDefaultsIdent, wxEmptyString, false);
success = LoadUserPreset(GetFactoryDefaultsGroup());
}
else
{
CommandParameters eap(parms);
ShuttleSetAutomation S;
S.SetForValidating( &eap );
// DefineParams returns false if not defined for this effect.
if( !DefineParams( S ) )
// the old method...
success = SetAutomationParameters(eap);
else if( !S.bOK )
success = false;
else{
success = true;
S.SetForWriting( &eap );
DefineParams( S );
}
}
if (!success)
{
Effect::MessageBox(
wxString::Format(
_("%s: Could not load settings below. Default settings will be used.\n\n%s"),
GetTranslatedName(),
preset
)
);
return false;
}
if (!mUIDialog)
{
return true;
}
return TransferDataToWindow();
}
wxArrayString Effect::GetUserPresets()
{
wxArrayString presets;
GetPrivateConfigSubgroups(GetUserPresetsGroup(wxEmptyString), presets);
presets.Sort();
return presets;
}
bool Effect::HasCurrentSettings()
{
return HasPrivateConfigGroup(GetCurrentSettingsGroup());
}
bool Effect::HasFactoryDefaults()
{
return HasPrivateConfigGroup(GetFactoryDefaultsGroup());
}
wxString Effect::GetPreset(wxWindow * parent, const wxString & parms)
{
EffectPresetsDialog dlg(parent, this);
dlg.Layout();
dlg.Fit();
dlg.SetSize(dlg.GetMinSize());
dlg.CenterOnParent();
dlg.SetSelected(parms);
if (dlg.ShowModal())
{
return dlg.GetSelected();
}
return wxEmptyString;
}
wxString Effect::ManualPage()
{
return wxEmptyString;
}
wxString Effect::HelpPage()
{
return wxEmptyString;
}
bool Effect::IsBatchProcessing()
{
return mIsBatch;
}
void Effect::SetBatchProcessing(bool start)
{
mIsBatch = start;
if (start)
{
SaveUserPreset(GetSavedStateGroup());
}
else
{
LoadUserPreset(GetSavedStateGroup());
}
}
bool Effect::DoEffect(wxWindow *parent,
double projectRate,
TrackList *list,
TrackFactory *factory,
SelectedRegion *selectedRegion,
bool shouldPrompt /* = true */)
{
wxASSERT(selectedRegion->duration() >= 0.0);
mOutputTracks.reset();
mpSelectedRegion = selectedRegion;
mFactory = factory;
mProjectRate = projectRate;
mTracks = list;
bool isSelection = false;
mDuration = 0.0;
if (GetType() == EffectTypeGenerate)
{
GetPrivateConfig(GetCurrentSettingsGroup(), wxT("LastUsedDuration"), mDuration, GetDefaultDuration());
}
mT0 = selectedRegion->t0();
mT1 = selectedRegion->t1();
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
? NumericConverter::TimeAndSampleFormat()
: NumericConverter::DefaultSelectionFormat();
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
mF0 = selectedRegion->f0();
mF1 = selectedRegion->f1();
wxArrayString Names;
if( mF0 != SelectedRegion::UndefinedFrequency )
Names.Add(wxT("control-f0"));
if( mF1 != SelectedRegion::UndefinedFrequency )
Names.Add(wxT("control-f1"));
SetPresetParameters( &Names, NULL );
#endif
CountWaveTracks();
// Note: Init may read parameters from preferences
if (!Init())
{
return false;
}
// 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)
{
auto name = GetTranslatedName();
ProgressDialog progress{
name,
wxString::Format(_("Applying %s..."), name),
pdlgHideStopButton
};
auto vr = valueRestorer( mProgress, &progress );
returnVal = Process();
}
if (returnVal)
{
selectedRegion->setTimes(mT0, mT1);
}
return returnVal;
}
bool Effect::Delegate( Effect &delegate, wxWindow *parent, bool shouldPrompt)
{
return delegate.DoEffect( parent, mProjectRate, mTracks, mFactory,
mpSelectedRegion, shouldPrompt );
}
// All legacy effects should have this overridden
bool Effect::Init()
{
return true;
}
// Remove this method once NoiseReduction gets migrated
bool Effect::PromptUser(wxWindow *parent)
{
return ShowInterface(parent, IsBatchProcessing());
}
int Effect::GetPass()
{
return mPass;
}
bool Effect::InitPass1()
{
return true;
}
bool Effect::InitPass2()
{
return false;
}
bool Effect::Process()
{
CopyInputTracks(Track::All);
bool bGoodResult = true;
// It's possible that the number of channels the effect expects changed based on
// the parameters (the Audacity Reverb effect does when the stereo width is 0).
mNumAudioIn = GetAudioInCount();
mNumAudioOut = GetAudioOutCount();
mPass = 1;
if (InitPass1())
{
bGoodResult = ProcessPass();
mPass = 2;
if (bGoodResult && InitPass2())
{
bGoodResult = ProcessPass();
}
}
ReplaceProcessedTracks(bGoodResult);
return bGoodResult;
}
bool Effect::ProcessPass()
{
bool bGoodResult = true;
bool isGenerator = GetType() == EffectTypeGenerate;
FloatBuffers inBuffer, outBuffer;
ArrayOf<float *> inBufPos, outBufPos;
ChannelName map[3];
mBufferSize = 0;
mBlockSize = 0;
TrackListIterator iter(mOutputTracks.get());
int count = 0;
bool clear = false;
Track* t = iter.First();
for (t = iter.First(); t; t = iter.Next())
{
if (t->GetKind() != Track::Wave || !t->GetSelected())
{
if (t->IsSyncLockSelected())
{
t->SyncLockAdjust(mT1, mT0 + mDuration);
}
continue;
}
WaveTrack *left = (WaveTrack *)t;
WaveTrack *right;
sampleCount len;
sampleCount leftStart;
sampleCount rightStart;
if (!isGenerator)
{
GetSamples(left, &leftStart, &len);
mSampleCnt = len;
}
else
{
len = 0;
leftStart = 0;
mSampleCnt = left->TimeToLongSamples(mDuration);
}
mNumChannels = 1;
if (left->GetChannel() == Track::LeftChannel)
{
map[0] = ChannelNameFrontLeft;
}
else if (left->GetChannel() == Track::RightChannel)
{
map[0] = ChannelNameFrontRight;
}
else
{
map[0] = ChannelNameMono;
}
map[1] = ChannelNameEOL;
right = NULL;
rightStart = 0;
if (left->GetLinked() && mNumAudioIn > 1)
{
// Assume linked track is wave
right = static_cast<WaveTrack *>(iter.Next());
if (!isGenerator)
{
GetSamples(right, &rightStart, &len);
}
clear = false;
mNumChannels = 2;
if (right->GetChannel() == Track::LeftChannel)
{
map[1] = ChannelNameFrontLeft;
}
else if (right->GetChannel() == Track::RightChannel)
{
map[1] = ChannelNameFrontRight;
}
else
{
map[1] = ChannelNameMono;
}
map[2] = ChannelNameEOL;
}
// Let the client know the sample rate
SetSampleRate(left->GetRate());
// Get the block size the client wants to use
auto max = left->GetMaxBlockSize() * 2;
mBlockSize = SetBlockSize(max);
// Calculate the buffer size to be at least the max rounded up to the clients
// selected block size.
const auto prevBufferSize = mBufferSize;
mBufferSize = ((max + (mBlockSize - 1)) / mBlockSize) * mBlockSize;
// If the buffer size has changed, then (re)allocate the buffers
if (prevBufferSize != mBufferSize)
{
// Always create the number of input buffers the client expects even if we don't have
// the same number of channels.
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++)
{
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.
// Output buffers get an extra mBlockSize worth to give extra room if
// the plugin adds latency
outBufPos.reinit( mNumAudioOut );
outBuffer.reinit( mNumAudioOut, mBufferSize + mBlockSize );
}
// (Re)Set the input buffer positions
for (size_t i = 0; i < mNumAudioIn; i++)
{
inBufPos[i] = inBuffer[i].get();
}
// (Re)Set the output buffer positions
for (size_t i = 0; i < mNumAudioOut; i++)
{
outBufPos[i] = outBuffer[i].get();
}
// Clear unused input buffers
if (!right && !clear && mNumAudioIn > 1)
{
for (size_t j = 0; j < mBufferSize; j++)
{
inBuffer[1][j] = 0.0;
}
clear = true;
}
// Go process the track(s)
bGoodResult = ProcessTrack(
count, map, left, right, leftStart, rightStart, len,
inBuffer, outBuffer, inBufPos, outBufPos);
if (!bGoodResult)
{
break;
}
count++;
}
if (bGoodResult && GetType() == EffectTypeGenerate)
{
mT1 = mT0 + mDuration;
}
return bGoodResult;
}
bool Effect::ProcessTrack(int count,
ChannelNames map,
WaveTrack *left,
WaveTrack *right,
sampleCount leftStart,
sampleCount rightStart,
sampleCount len,
FloatBuffers &inBuffer,
FloatBuffers &outBuffer,
ArrayOf< float * > &inBufPos,
ArrayOf< float *> &outBufPos)
{
bool rc = true;
// Give the plugin a chance to initialize
if (!ProcessInitialize(len, map))
{
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
// samples to the output track.
//
// Upon return from the effect, the output samples are "moved to the left" by
// the number of samples in the current latency setting, effectively removing any
// delay introduced by the effect.
//
// At the same time the total number of delayed samples are gathered and when
// there is no further input data to process, the loop continues to call the
// effect with an empty input buffer until the effect has had a chance to
// return all of the remaining delayed samples.
auto inLeftPos = leftStart;
auto inRightPos = rightStart;
auto outLeftPos = leftStart;
auto outRightPos = rightStart;
auto inputRemaining = len;
decltype(GetLatency()) curDelay = 0, delayRemaining = 0;
decltype(mBlockSize) curBlockSize = 0;
decltype(mBufferSize) inputBufferCnt = 0;
decltype(mBufferSize) outputBufferCnt = 0;
bool cleared = false;
auto chans = std::min<unsigned>(mNumAudioOut, mNumChannels);
std::unique_ptr<WaveTrack> genLeft, genRight;
decltype(len) genLength = 0;
bool isGenerator = GetType() == EffectTypeGenerate;
bool isProcessor = GetType() == EffectTypeProcess;
double genDur = 0;
if (isGenerator)
{
if (mIsPreview) {
gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &genDur, 6.0);
genDur = wxMin(mDuration, CalcPreviewInputLength(genDur));
}
else {
genDur = mDuration;
}
genLength = sampleCount((left->GetRate() * genDur) + 0.5); // round to nearest sample
delayRemaining = genLength;
cleared = true;
// Create temporary tracks
genLeft = mFactory->NewWaveTrack(left->GetSampleFormat(), left->GetRate());
genLeft->SetWaveColorIndex( left->GetWaveColorIndex() );
if (right)
{
genRight = mFactory->NewWaveTrack(right->GetSampleFormat(), right->GetRate());
genRight->SetWaveColorIndex( right->GetWaveColorIndex() );
}
}
// Call the effect until we run out of input or delayed samples
while (inputRemaining != 0 || delayRemaining != 0)
{
// Still working on the input samples
if (inputRemaining != 0)
{
// Need to refill the input buffers
if (inputBufferCnt == 0)
{
// Calculate the number of samples to get
inputBufferCnt =
limitSampleBufferSize( mBufferSize, inputRemaining );
// Fill the input buffers
left->Get((samplePtr) inBuffer[0].get(), floatSample, inLeftPos, inputBufferCnt);
if (right)
{
right->Get((samplePtr) inBuffer[1].get(), floatSample, inRightPos, inputBufferCnt);
}
// Reset the input buffer positions
for (size_t i = 0; i < mNumChannels; i++)
{
inBufPos[i] = inBuffer[i].get();
}
}
// Calculate the number of samples to process
curBlockSize = mBlockSize;
if (curBlockSize > inputRemaining)
{
// We've reached the last block...set current block size to what's left
// inputRemaining is positive and bounded by a size_t
curBlockSize = inputRemaining.as_size_t();
inputRemaining = 0;
// Clear the remainder of the buffers so that a full block can be passed
// to the effect
auto cnt = mBlockSize - curBlockSize;
for (size_t i = 0; i < mNumChannels; i++)
{
for (decltype(cnt) j = 0 ; j < cnt; j++)
{
inBufPos[i][j + curBlockSize] = 0.0;
}
}
// Might be able to use up some of the delayed samples
if (delayRemaining != 0)
{
// Don't use more than needed
cnt = limitSampleBufferSize(cnt, delayRemaining);
delayRemaining -= cnt;
curBlockSize += cnt;
}
}
}
// We've exhausted the input samples and are now working on the delay
else if (delayRemaining != 0)
{
// Calculate the number of samples to process
curBlockSize = limitSampleBufferSize( mBlockSize, delayRemaining );
delayRemaining -= curBlockSize;
// From this point on, we only want to feed zeros to the plugin
if (!cleared)
{
// Reset the input buffer positions
for (size_t i = 0; i < mNumChannels; i++)
{
inBufPos[i] = inBuffer[i].get();
// And clear
for (size_t j = 0; j < mBlockSize; j++)
{
inBuffer[i][j] = 0.0;
}
}
cleared = true;
}
}
// Finally call the plugin to process the block
decltype(curBlockSize) processed;
try
{
processed = ProcessBlock(inBufPos.get(), outBufPos.get(), curBlockSize);
}
catch( const AudacityException & WXUNUSED(e) )
{
// PRL: Bug 437:
// Pass this along to our application-level handler
throw;
}
catch(...)
{
// PRL:
// Exceptions for other reasons, maybe in third-party code...
// Continue treating them as we used to, but I wonder if these
// should now be treated the same way.
return false;
}
wxASSERT(processed == curBlockSize);
wxUnusedVar(processed);
// Bump to next input buffer position
if (inputRemaining != 0)
{
for (size_t i = 0; i < mNumChannels; i++)
{
inBufPos[i] += curBlockSize;
}
inputRemaining -= curBlockSize;
inputBufferCnt -= curBlockSize;
}
// "ls" and "rs" serve as the input sample index for the left and
// right channels when processing the input samples. If we flip
// over to processing delayed samples, they simply become counters
// for the progress display.
inLeftPos += curBlockSize;
inRightPos += curBlockSize;
// Get the current number of delayed samples and accumulate
if (isProcessor)
{
auto delay = GetLatency();
curDelay += delay;
delayRemaining += delay;
// If the plugin has delayed the output by more samples than our current
// block size, then we leave the output pointers alone. This effectively
// removes those delayed samples from the output buffer.
if (curDelay >= curBlockSize)
{
curDelay -= curBlockSize;
curBlockSize = 0;
}
// We have some delayed samples, at the beginning of the output samples,
// so overlay them by shifting the remaining output samples.
else if (curDelay > 0)
{
// curDelay is bounded by curBlockSize:
auto delay = curDelay.as_size_t();
curBlockSize -= delay;
for (size_t i = 0; i < chans; i++)
{
memmove(outBufPos[i], outBufPos[i] + delay, sizeof(float) * curBlockSize);
}
curDelay = 0;
}
}
// Adjust the number of samples in the output buffers
outputBufferCnt += curBlockSize;
// Still have room in the output buffers
if (outputBufferCnt < mBufferSize)
{
// Bump to next output buffer position
for (size_t i = 0; i < chans; i++)
{
outBufPos[i] += curBlockSize;
}
}
// Output buffers have filled
else
{
if (isProcessor)
{
// Write them out
left->Set((samplePtr) outBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt);
if (right)
{
if (chans >= 2)
{
right->Set((samplePtr) outBuffer[1].get(), floatSample, outRightPos, outputBufferCnt);
}
else
{
right->Set((samplePtr) outBuffer[0].get(), floatSample, outRightPos, outputBufferCnt);
}
}
}
else if (isGenerator)
{
genLeft->Append((samplePtr) outBuffer[0].get(), floatSample, outputBufferCnt);
if (genRight)
{
genRight->Append((samplePtr) outBuffer[1].get(), floatSample, outputBufferCnt);
}
}
// Reset the output buffer positions
for (size_t i = 0; i < chans; i++)
{
outBufPos[i] = outBuffer[i].get();
}
// Bump to the next track position
outLeftPos += outputBufferCnt;
outRightPos += outputBufferCnt;
outputBufferCnt = 0;
}
if (mNumChannels > 1)
{
if (TrackGroupProgress(count,
(inLeftPos - leftStart).as_double() /
(isGenerator ? genLength : len).as_double()))
{
rc = false;
break;
}
}
else
{
if (TrackProgress(count,
(inLeftPos - leftStart).as_double() /
(isGenerator ? genLength : len).as_double()))
{
rc = false;
break;
}
}
}
// Put any remaining output
if (rc && outputBufferCnt)
{
if (isProcessor)
{
left->Set((samplePtr) outBuffer[0].get(), floatSample, outLeftPos, outputBufferCnt);
if (right)
{
if (chans >= 2)
{
right->Set((samplePtr) outBuffer[1].get(), floatSample, outRightPos, outputBufferCnt);
}
else
{
right->Set((samplePtr) outBuffer[0].get(), floatSample, outRightPos, outputBufferCnt);
}
}
}
else if (isGenerator)
{
genLeft->Append((samplePtr) outBuffer[0].get(), floatSample, outputBufferCnt);
if (genRight)
{
genRight->Append((samplePtr) outBuffer[1].get(), floatSample, outputBufferCnt);
}
}
}
if (rc && isGenerator)
{
AudacityProject *p = GetActiveProject();
// PRL: this code was here and could not have been the right
// intent, mixing time and sampleCount values:
// StepTimeWarper warper(mT0 + genLength, genLength - (mT1 - mT0));
// This looks like what it should have been:
// StepTimeWarper warper(mT0 + genDur, genDur - (mT1 - mT0));
// But rather than fix it, I will just disable the use of it for now.
// 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 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.
// Transfer the data from the temporary tracks to the actual ones
genLeft->Flush();
// mT1 gives us the NEW selection. We want to replace up to GetSel1().
left->ClearAndPaste(mT0, p->GetSel1(), genLeft.get(), true, true,
nullptr /* &warper */);
if (genRight)
{
genRight->Flush();
right->ClearAndPaste(mT0, mT1, genRight.get(), true, true,
nullptr /* &warper */);
}
}
} // End scope for cleanup
return rc;
}
void Effect::End()
{
}
void Effect::PopulateOrExchange(ShuttleGui & WXUNUSED(S))
{
return;
}
bool Effect::TransferDataToWindow()
{
return true;
}
bool Effect::TransferDataFromWindow()
{
return true;
}
bool Effect::EnableApply(bool enable)
{
// May be called during initialization, so try to find the dialog
wxWindow *dlg = mUIDialog;
if (!dlg && mUIParent)
{
dlg = wxGetTopLevelParent(mUIParent);
}
if (dlg)
{
wxWindow *apply = dlg->FindWindow(wxID_APPLY);
// Don't allow focus to get trapped
if (!enable)
{
wxWindow *focus = dlg->FindFocus();
if (focus == apply)
{
dlg->FindWindow(wxID_CLOSE)->SetFocus();
}
}
apply->Enable(enable);
}
EnablePreview(enable);
return enable;
}
bool Effect::EnablePreview(bool enable)
{
// May be called during initialization, so try to find the dialog
wxWindow *dlg = mUIDialog;
if (!dlg && mUIParent)
{
dlg = wxGetTopLevelParent(mUIParent);
}
if (dlg)
{
wxWindow *play = dlg->FindWindow(kPlayID);
if (play)
{
wxWindow *rewind = dlg->FindWindow(kRewindID);
wxWindow *ffwd = dlg->FindWindow(kFFwdID);
// Don't allow focus to get trapped
if (!enable)
{
wxWindow *focus = dlg->FindFocus();
if (focus && (focus == play || focus == rewind || focus == ffwd))
{
dlg->FindWindow(wxID_CLOSE)->SetFocus();
}
}
play->Enable(enable);
if (SupportsRealtime())
{
rewind->Enable(enable);
ffwd->Enable(enable);
}
}
}
return enable;
}
void Effect::EnableDebug(bool enable)
{
mUIDebug = enable;
}
void Effect::SetLinearEffectFlag(bool linearEffectFlag)
{
mIsLinearEffect = linearEffectFlag;
}
void Effect::SetPreviewFullSelectionFlag(bool previewDurationFlag)
{
mPreviewFullSelection = previewDurationFlag;
}
void Effect::IncludeNotSelectedPreviewTracks(bool includeNotSelected)
{
mPreviewWithNotSelected = includeNotSelected;
}
bool Effect::TotalProgress(double frac)
{
auto updateResult = (mProgress ?
mProgress->Update(frac) :
ProgressResult::Success);
return (updateResult != ProgressResult::Success);
}
bool Effect::TrackProgress(int whichTrack, double frac, const wxString &msg)
{
auto updateResult = (mProgress ?
mProgress->Update(whichTrack + frac, (double) mNumTracks, msg) :
ProgressResult::Success);
return (updateResult != ProgressResult::Success);
}
bool Effect::TrackGroupProgress(int whichGroup, double frac, const wxString &msg)
{
auto updateResult = (mProgress ?
mProgress->Update(whichGroup + frac, (double) mNumGroups, msg) :
ProgressResult::Success);
return (updateResult != ProgressResult::Success);
}
void Effect::GetSamples(
const WaveTrack *track, sampleCount *start, sampleCount *len)
{
double trackStart = track->GetStartTime();
double trackEnd = track->GetEndTime();
double t0 = mT0 < trackStart ? trackStart : mT0;
double t1 = mT1 > trackEnd ? trackEnd : mT1;
#if 0
if (GetType() & INSERT_EFFECT) {
t1 = t0 + mDuration;
if (mT0 == mT1) {
// Not really part of the calculation, but convenient to put here
track->InsertSilence(t0, t1);
}
}
#endif
if (t1 > t0) {
*start = track->TimeToLongSamples(t0);
auto end = track->TimeToLongSamples(t1);
*len = end - *start;
}
else {
*start = 0;
*len = 0;
}
}
//
// private methods
//
// Use these two methods to copy the input tracks to mOutputTracks, if
// doing the processing on them, and replacing the originals only on success (and not cancel).
// Copy the group tracks that have tracks selected
void Effect::CopyInputTracks()
{
CopyInputTracks(Track::Wave);
}
void Effect::CopyInputTracks(int trackType)
{
// Reset map
mIMap.clear();
mOMap.clear();
mOutputTracks = TrackList::Create();
mOutputTracksType = trackType;
//iterate over tracks of type trackType (All types if Track::All)
TrackListOfKindIterator aIt(trackType, mTracks);
t2bHash added;
for (Track *aTrack = aIt.First(); aTrack; aTrack = aIt.Next())
{
// Include selected tracks, plus sync-lock selected tracks for Track::All.
if (aTrack->GetSelected() ||
(trackType == Track::All && aTrack->IsSyncLockSelected()))
{
Track *o = mOutputTracks->Add(aTrack->Duplicate());
mIMap.push_back(aTrack);
mOMap.push_back(o);
}
}
}
Track *Effect::AddToOutputTracks(std::unique_ptr<Track> &&t)
{
mIMap.push_back(NULL);
mOMap.push_back(t.get());
return mOutputTracks->Add(std::move(t));
}
Effect::AddedAnalysisTrack::AddedAnalysisTrack(Effect *pEffect, const wxString &name)
: mpEffect(pEffect)
{
LabelTrack::Holder pTrack{ pEffect->mFactory->NewLabelTrack() };
mpTrack = pTrack.get();
if (!name.empty())
pTrack->SetName(name);
pEffect->mTracks->Add(std::move(pTrack));
}
Effect::AddedAnalysisTrack::AddedAnalysisTrack(AddedAnalysisTrack &&that)
{
mpEffect = that.mpEffect;
mpTrack = that.mpTrack;
that.Commit();
}
void Effect::AddedAnalysisTrack::Commit()
{
mpEffect = nullptr;
}
Effect::AddedAnalysisTrack::~AddedAnalysisTrack()
{
if (mpEffect) {
// not committed -- DELETE the label track
mpEffect->mTracks->Remove(mpTrack);
}
}
auto Effect::AddAnalysisTrack(const wxString &name) -> std::shared_ptr<AddedAnalysisTrack>
{
return std::shared_ptr<AddedAnalysisTrack>
{ safenew AddedAnalysisTrack{ this, name } };
}
Effect::ModifiedAnalysisTrack::ModifiedAnalysisTrack
(Effect *pEffect, const LabelTrack *pOrigTrack, const wxString &name)
: mpEffect(pEffect)
{
// copy LabelTrack here, so it can be undone on cancel
auto newTrack = pOrigTrack->Copy(pOrigTrack->GetStartTime(), pOrigTrack->GetEndTime());
mpTrack = static_cast<LabelTrack*>(newTrack.get());
// Why doesn't LabelTrack::Copy complete the job? :
mpTrack->SetOffset(pOrigTrack->GetStartTime());
if (!name.empty())
mpTrack->SetName(name);
// mpOrigTrack came from mTracks which we own but expose as const to subclasses
// So it's okay that we cast it back to const
mpOrigTrack =
pEffect->mTracks->Replace(const_cast<LabelTrack*>(pOrigTrack),
#ifdef __AUDACITY_OLD_STD__
std::shared_ptr<Track>(newTrack.release())
#else
std::move(newTrack)
#endif
);
}
Effect::ModifiedAnalysisTrack::ModifiedAnalysisTrack(ModifiedAnalysisTrack &&that)
{
mpEffect = that.mpEffect;
mpTrack = that.mpTrack;
mpOrigTrack = std::move(that.mpOrigTrack);
that.Commit();
}
void Effect::ModifiedAnalysisTrack::Commit()
{
mpEffect = nullptr;
}
Effect::ModifiedAnalysisTrack::~ModifiedAnalysisTrack()
{
if (mpEffect) {
// not committed -- DELETE the label track
// mpOrigTrack came from mTracks which we own but expose as const to subclasses
// So it's okay that we cast it back to const
mpEffect->mTracks->Replace(mpTrack, std::move(mpOrigTrack));
}
}
auto Effect::ModifyAnalysisTrack
(const LabelTrack *pOrigTrack, const wxString &name) -> ModifiedAnalysisTrack
{
return{ this, pOrigTrack, name };
}
// If bGoodResult, replace mTracks tracks with successfully processed mOutputTracks copies.
// Else clear and DELETE mOutputTracks copies.
void Effect::ReplaceProcessedTracks(const bool bGoodResult)
{
if (!bGoodResult) {
// Free resources, unless already freed.
// Processing failed or was cancelled so throw away the processed tracks.
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->ListOfTracks::begin(),
iterEnd = mOutputTracks->ListOfTracks::end();
size_t cnt = mOMap.size();
size_t i = 0;
for (; iterOut != iterEnd; ++i) {
ListOfTracks::value_type o = std::move(*iterOut);
// If tracks were removed from mOutputTracks, then there will be
// tracks in the map that must be removed from mTracks.
while (i < cnt && mOMap[i] != o.get()) {
const auto t = mIMap[i];
if (t) {
mTracks->Remove(t);
}
i++;
}
// This should never happen
wxASSERT(i < cnt);
// Remove the track from the output list...don't DELETE it
iterOut = mOutputTracks->erase(iterOut);
const auto t = mIMap[i];
if (t == NULL)
{
// This track is a NEW addition to output tracks; add it to mTracks
mTracks->Add(std::move(o));
}
else
{
// Replace mTracks entry with the NEW track
WaveTrack *newTrack = static_cast<WaveTrack*>(o.get());
mTracks->Replace(t, std::move(o));
// Swap the wavecache track the ondemand task uses, since now the NEW
// one will be kept in the project
if (ODManager::IsInstanceCreated()) {
ODManager::Instance()->ReplaceWaveTrack((WaveTrack *)t,
newTrack);
}
}
}
// If tracks were removed from mOutputTracks, then there may be tracks
// left at the end of the map that must be removed from mTracks.
while (i < cnt) {
const auto t = mIMap[i];
if (t) {
mTracks->Remove(t);
}
i++;
}
// Reset map
mIMap.clear();
mOMap.clear();
// Make sure we processed everything
wxASSERT(mOutputTracks->empty());
// The output list is no longer needed
mOutputTracks.reset();
mOutputTracksType = Track::None;
}
void Effect::CountWaveTracks()
{
mNumTracks = 0;
mNumGroups = 0;
TrackListOfKindIterator iter(Track::Wave, mTracks);
Track *t = iter.First();
while(t) {
if (!t->GetSelected()) {
t = iter.Next();
continue;
}
if (t->GetKind() == Track::Wave) {
mNumTracks++;
if (!t->GetLinked())
mNumGroups++;
}
t = iter.Next();
}
}
double Effect::CalcPreviewInputLength(double previewLength)
{
return previewLength;
}
// RealtimeAddProcessor and RealtimeProcess use the same method of
// determining the current processor index, so updates to one should
// be reflected in the other.
bool Effect::RealtimeAddProcessor(int group, unsigned chans, float rate)
{
auto ichans = chans;
auto ochans = chans;
auto gchans = chans;
// Reset processor index
if (group == 0)
{
mCurrentProcessor = 0;
mGroupProcessor.clear();
}
// Remember the processor starting index
mGroupProcessor.push_back(mCurrentProcessor);
// Call the client until we run out of input or output channels
while (ichans > 0 && ochans > 0)
{
// If we don't have enough input channels to accomodate the client's
// requirements, then we replicate the input channels until the
// client's needs are met.
if (ichans < mNumAudioIn)
{
// All input channels have been consumed
ichans = 0;
}
// Otherwise fullfil the client's needs with as many input channels as possible.
// After calling the client with this set, we will loop back up to process more
// of the input/output channels.
else if (ichans >= mNumAudioIn)
{
gchans = mNumAudioIn;
ichans -= gchans;
}
// If we don't have enough output channels to accomodate the client's
// requirements, then we provide all of the output channels and fulfill
// the client's needs with dummy buffers. These will just get tossed.
if (ochans < mNumAudioOut)
{
// All output channels have been consumed
ochans = 0;
}
// Otherwise fullfil the client's needs with as many output channels as possible.
// After calling the client with this set, we will loop back up to process more
// of the input/output channels.
else if (ochans >= mNumAudioOut)
{
ochans -= mNumAudioOut;
}
// Add a NEW processor
RealtimeAddProcessor(gchans, rate);
// Bump to next processor
mCurrentProcessor++;
}
return true;
}
// RealtimeAddProcessor and RealtimeProcess use the same method of
// determining the current processor group, so updates to one should
// be reflected in the other.
size_t Effect::RealtimeProcess(int group,
unsigned chans,
float **inbuf,
float **outbuf,
size_t numSamples)
{
//
// The caller passes the number of channels to process and specifies
// the number of input and output buffers. There will always be the
// same number of output buffers as there are input buffers.
//
// Effects always require a certain number of input and output buffers,
// so if the number of channels we're curently processing are different
// than what the effect expects, then we use a few methods of satisfying
// the effects requirements.
float **clientIn = (float **) alloca(mNumAudioIn * sizeof(float *));
float **clientOut = (float **) alloca(mNumAudioOut * sizeof(float *));
float *dummybuf = (float *) alloca(numSamples * sizeof(float));
decltype(numSamples) len = 0;
auto ichans = chans;
auto ochans = chans;
auto gchans = chans;
unsigned indx = 0;
unsigned ondx = 0;
int processor = mGroupProcessor[group];
// Call the client until we run out of input or output channels
while (ichans > 0 && ochans > 0)
{
// If we don't have enough input channels to accomodate the client's
// requirements, then we replicate the input channels until the
// client's needs are met.
if (ichans < mNumAudioIn)
{
for (size_t i = 0; i < mNumAudioIn; i++)
{
if (indx == ichans)
{
indx = 0;
}
clientIn[i] = inbuf[indx++];
}
// All input channels have been consumed
ichans = 0;
}
// Otherwise fullfil the client's needs with as many input channels as possible.
// After calling the client with this set, we will loop back up to process more
// of the input/output channels.
else if (ichans >= mNumAudioIn)
{
gchans = 0;
for (size_t i = 0; i < mNumAudioIn; i++, ichans--, gchans++)
{
clientIn[i] = inbuf[indx++];
}
}
// If we don't have enough output channels to accomodate the client's
// requirements, then we provide all of the output channels and fulfill
// the client's needs with dummy buffers. These will just get tossed.
if (ochans < mNumAudioOut)
{
for (size_t i = 0; i < mNumAudioOut; i++)
{
if (i < ochans)
{
clientOut[i] = outbuf[i];
}
else
{
clientOut[i] = dummybuf;
}
}
// All output channels have been consumed
ochans = 0;
}
// Otherwise fullfil the client's needs with as many output channels as possible.
// After calling the client with this set, we will loop back up to process more
// of the input/output channels.
else if (ochans >= mNumAudioOut)
{
for (size_t i = 0; i < mNumAudioOut; i++, ochans--)
{
clientOut[i] = outbuf[ondx++];
}
}
// Finally call the plugin to process the block
len = 0;
for (decltype(numSamples) block = 0; block < numSamples; block += mBlockSize)
{
auto cnt = std::min(numSamples - block, mBlockSize);
len += RealtimeProcess(processor, clientIn, clientOut, cnt);
for (size_t i = 0 ; i < mNumAudioIn; i++)
{
clientIn[i] += cnt;
}
for (size_t i = 0 ; i < mNumAudioOut; i++)
{
clientOut[i] += cnt;
}
}
// Bump to next processor
processor++;
}
return len;
}
bool Effect::IsRealtimeActive()
{
return mRealtimeSuspendCount == 0;
}
bool Effect::IsHidden()
{
return false;
}
void Effect::Preview(bool dryOnly)
{
if (mNumTracks == 0) { // nothing to preview
return;
}
if (gAudioIO->IsBusy()) {
return;
}
wxWindow *FocusDialog = wxWindow::FindFocus();
double previewDuration;
bool isNyquist = GetFamilyId() == NYQUISTEFFECTS_FAMILY;
bool isGenerator = GetType() == EffectTypeGenerate;
// Mix a few seconds of audio from all of the tracks
double previewLen;
gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLen, 6.0);
const double rate = mProjectRate;
if (isNyquist && isGenerator) {
previewDuration = CalcPreviewInputLength(previewLen);
}
else {
previewDuration = wxMin(mDuration, CalcPreviewInputLength(previewLen));
}
double t1 = mT0 + previewDuration;
if ((t1 > mT1) && !(isNyquist && isGenerator)) {
t1 = mT1;
}
if (t1 <= mT0)
return;
bool success = true;
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;
// 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) {
End();
GuardedCall( [&]{ Init(); } );
}
if (FocusDialog) {
FocusDialog->SetFocus();
}
// In case of failed effect, be sure to free memory.
ReplaceProcessedTracks( false );
} );
// Build NEW tracklist from rendering tracks
auto uTracks = TrackList::Create();
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->SetSelected(true);
mixLeft->SetDisplay(WaveTrack::NoDisplay);
mTracks->Add(std::move(mixLeft));
if (mixRight) {
mixRight->Offset(-mixRight->GetStartTime());
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->SetSelected(src->GetSelected());
static_cast<WaveTrack*>(dest.get())->SetDisplay(WaveTrack::NoDisplay);
mTracks->Add(std::move(dest));
}
src = (WaveTrack *) iter.Next();
}
}
// NEW tracks start at time zero.
// Adjust mT0 and mT1 to be the times to process, and to
// play back in these tracks
mT1 -= mT0;
mT0 = 0.0;
// Update track/group counts
CountWaveTracks();
// Apply effect
if (!dryOnly) {
ProgressDialog progress{
GetTranslatedName(),
_("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(Track::Pointer<WaveTrack>(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
(GetTranslatedName(), _("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 {
ShowErrorDialog(FocusDialog, _("Error"),
_("Error opening sound device.\nTry changing the audio host, playback device and the project sample rate."),
wxT("Error_opening_sound_device"));
}
}
}
int Effect::MessageBox
(const wxString& message, long style, const wxString &titleStr)
{
wxString title;
if (titleStr.empty())
title = GetTranslatedName();
else
title = wxString::Format(_("%s: %s"), GetTranslatedName(), titleStr);
return AudacityMessageBox(message, title, style, mUIParent);
}
BEGIN_EVENT_TABLE(EffectDialog, wxDialogWrapper)
EVT_BUTTON(wxID_OK, EffectDialog::OnOk)
END_EVENT_TABLE()
EffectDialog::EffectDialog(wxWindow * parent,
const wxString & title,
int type,
int flags,
int additionalButtons)
: wxDialogWrapper(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, flags)
{
mType = type;
mAdditionalButtons = additionalButtons;
}
void EffectDialog::Init()
{
ShuttleGui S(this, eIsCreating);
S.SetBorder(5);
S.StartVerticalLay(true);
{
PopulateOrExchange(S);
long buttons = eOkButton;
if ((mType != EffectTypeAnalyze) && (mType != EffectTypeTool))
{
buttons |= eCancelButton;
if (mType == EffectTypeProcess)
{
buttons |= ePreviewButton;
}
}
S.AddStandardButtons(buttons|mAdditionalButtons);
}
S.EndVerticalLay();
Layout();
Fit();
SetMinSize(GetSize());
Center();
}
/// This is a virtual function which will be overridden to
/// provide the actual parameters that we want for each
/// kind of dialog.
void EffectDialog::PopulateOrExchange(ShuttleGui & WXUNUSED(S))
{
return;
}
bool EffectDialog::TransferDataToWindow()
{
ShuttleGui S(this, eIsSettingToDialog);
PopulateOrExchange(S);
return true;
}
bool EffectDialog::TransferDataFromWindow()
{
ShuttleGui S(this, eIsGettingFromDialog);
PopulateOrExchange(S);
return true;
}
bool EffectDialog::Validate()
{
return true;
}
void EffectDialog::OnPreview(wxCommandEvent & WXUNUSED(evt))
{
return;
}
void EffectDialog::OnOk(wxCommandEvent & WXUNUSED(evt))
{
// On wxGTK (wx2.8.12), the default action is still executed even if
// the button is disabled. This appears to affect all wxDialogs, not
// just our Effects dialogs. So, this is a only temporary workaround
// for legacy effects that disable the OK button. Hopefully this has
// been corrected in wx3.
if (FindWindow(wxID_OK)->IsEnabled() && Validate() && TransferDataFromWindow())
{
EndModal(true);
}
return;
}
///////////////////////////////////////////////////////////////////////////////
//
// EffectPanel
//
///////////////////////////////////////////////////////////////////////////////
class EffectPanel final : public wxPanelWrapper
{
public:
EffectPanel(wxWindow *parent)
: wxPanelWrapper(parent)
{
// This fools NVDA into not saying "Panel" when the dialog gets focus
SetName(wxT("\a"));
SetLabel(wxT("\a"));
mAcceptsFocus = true;
}
virtual ~EffectPanel()
{
}
// ============================================================================
// wxWindow implementation
// ============================================================================
bool AcceptsFocus() const override
{
return mAcceptsFocus;
}
// So that wxPanel is not included in Tab traversal, when required - see wxWidgets bug 15581
bool AcceptsFocusFromKeyboard() const override
{
return mAcceptsFocus;
}
// ============================================================================
// EffectPanel implementation
// ============================================================================
void SetAccept(bool accept)
{
mAcceptsFocus = accept;
}
private:
bool mAcceptsFocus;
};
///////////////////////////////////////////////////////////////////////////////
//
// EffectUIHost
//
///////////////////////////////////////////////////////////////////////////////
#include "../../images/Effect.h"
BEGIN_EVENT_TABLE(EffectUIHost, wxDialogWrapper)
EVT_INIT_DIALOG(EffectUIHost::OnInitDialog)
EVT_ERASE_BACKGROUND(EffectUIHost::OnErase)
EVT_PAINT(EffectUIHost::OnPaint)
EVT_CLOSE(EffectUIHost::OnClose)
EVT_BUTTON(wxID_APPLY, EffectUIHost::OnApply)
EVT_BUTTON(wxID_CANCEL, EffectUIHost::OnCancel)
EVT_BUTTON(wxID_HELP, EffectUIHost::OnHelp)
EVT_BUTTON(eDebugID, EffectUIHost::OnDebug)
EVT_BUTTON(kMenuID, EffectUIHost::OnMenu)
EVT_CHECKBOX(kEnableID, EffectUIHost::OnEnable)
EVT_BUTTON(kPlayID, EffectUIHost::OnPlay)
EVT_BUTTON(kRewindID, EffectUIHost::OnRewind)
EVT_BUTTON(kFFwdID, EffectUIHost::OnFFwd)
EVT_MENU(kSaveAsID, EffectUIHost::OnSaveAs)
EVT_MENU(kImportID, EffectUIHost::OnImport)
EVT_MENU(kExportID, EffectUIHost::OnExport)
EVT_MENU(kOptionsID, EffectUIHost::OnOptions)
EVT_MENU(kDefaultsID, EffectUIHost::OnDefaults)
EVT_MENU_RANGE(kUserPresetsID, kUserPresetsID + 999, EffectUIHost::OnUserPreset)
EVT_MENU_RANGE(kDeletePresetID, kDeletePresetID + 999, EffectUIHost::OnDeletePreset)
EVT_MENU_RANGE(kFactoryPresetsID, kFactoryPresetsID + 999, EffectUIHost::OnFactoryPreset)
END_EVENT_TABLE()
EffectUIHost::EffectUIHost(wxWindow *parent,
Effect *effect,
EffectUIClientInterface *client)
: wxDialogWrapper(parent, wxID_ANY, effect->GetTranslatedName(),
wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX)
{
#if defined(__WXMAC__)
// Make sure the effect window actually floats above the main window
[[((NSView *)GetHandle()) window] setLevel:NSFloatingWindowLevel];
#endif
SetName( effect->GetTranslatedName() );
SetExtraStyle(GetExtraStyle() | wxWS_EX_VALIDATE_RECURSIVELY);
mParent = parent;
mEffect = effect;
mCommand = NULL;
mClient = client;
mProject = GetActiveProject();
mInitialized = false;
mSupportsRealtime = false;
mDisableTransport = false;
mEnabled = true;
mPlayPos = 0.0;
mClient->SetHostUI(this);
}
EffectUIHost::EffectUIHost(wxWindow *parent,
AudacityCommand *command,
EffectUIClientInterface *client)
: wxDialogWrapper(parent, wxID_ANY, _("Some Command") /*command->GetTranslatedName()*/,
wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX)
{
#if defined(__WXMAC__)
// Make sure the effect window actually floats above the main window
[[((NSView *)GetHandle()) window] setLevel:NSFloatingWindowLevel];
#endif
//SetName( command->GetTranslatedName() );
SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
mParent = parent;
mEffect = NULL;
mCommand = command;
mClient = client;
mProject = GetActiveProject();
mInitialized = false;
mSupportsRealtime = false;
mDisableTransport = false;
mEnabled = true;
mPlayPos = 0.0;
mClient->SetHostUI(this);
}
EffectUIHost::~EffectUIHost()
{
CleanupRealtime();
if (mClient)
{
if (mNeedsResume)
Resume();
mClient->CloseUI();
mClient = NULL;
}
}
// ============================================================================
// wxWindow implementation
// ============================================================================
bool EffectUIHost::TransferDataToWindow()
{
if( mEffect )
return mEffect->TransferDataToWindow();
if( mCommand )
return mCommand->TransferDataToWindow();
return false;
}
bool EffectUIHost::TransferDataFromWindow()
{
if( mEffect)
return mEffect->TransferDataFromWindow();
if( mCommand)
return mCommand->TransferDataFromWindow();
return false;
}
// ============================================================================
// wxDialog implementation
// ============================================================================
int EffectUIHost::ShowModal()
{
#if defined(__WXMSW__)
// Swap the Close and Apply buttons
wxSizer *sz = mApplyBtn->GetContainingSizer();
wxASSERT(mApplyBtn->GetParent()); // To justify safenew
wxButton *apply = safenew wxButton(mApplyBtn->GetParent(), wxID_APPLY);
sz->Replace(mCloseBtn, apply);
sz->Replace(mApplyBtn, mCloseBtn);
sz->Layout();
mApplyBtn->Destroy();
mApplyBtn = apply;
mApplyBtn->SetDefault();
mApplyBtn->SetLabel(wxGetStockLabel(wxID_OK, 0));
mCloseBtn->SetLabel(wxGetStockLabel(wxID_CANCEL, 0));
#else
mApplyBtn->SetLabel(wxGetStockLabel(wxID_OK));
mCloseBtn->SetLabel(wxGetStockLabel(wxID_CANCEL));
#endif
Layout();
return wxDialogWrapper::ShowModal();
}
// ============================================================================
// EffectUIHost implementation
// ============================================================================
bool EffectUIHost::Initialize()
{
EffectPanel *w = safenew EffectPanel(this);
{
auto vs = std::make_unique<wxBoxSizer>(wxVERTICAL);
{
auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
// Try to give the window a sensible default/minimum size
w->SetMinSize(wxSize(wxMax(600, mParent->GetSize().GetWidth() * 2 / 3),
mParent->GetSize().GetHeight() / 2));
mDisableTransport = !gAudioIO->IsAvailable(mProject);
mPlaying = gAudioIO->IsStreamActive(); // not exactly right, but will suffice
mCapturing = gAudioIO->IsStreamActive() && gAudioIO->GetNumCaptureChannels() > 0;
if (!mClient->PopulateUI(w))
{
return false;
}
hs->Add(w, 1, wxEXPAND);
vs->Add(hs.release(), 1, wxEXPAND);
}
wxPanel *buttonPanel = safenew wxPanelWrapper(this, wxID_ANY);
wxPanel *const bar = safenew wxPanelWrapper(buttonPanel, wxID_ANY);
// This fools NVDA into not saying "Panel" when the dialog gets focus
bar->SetName(wxT("\a"));
bar->SetLabel(wxT("\a"));
{
auto bs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
mSupportsRealtime = mEffect && mEffect->SupportsRealtime();
mIsGUI = mClient->IsGraphicalUI();
mIsBatch = (mEffect && mEffect->IsBatchProcessing()) ||
(mCommand && mCommand->IsBatchProcessing());
wxBitmapButton *bb;
int margin = 0;
#if defined(__WXMAC__)
margin = 3; // I'm sure it's needed because of the order things are created...
#endif
if (!mIsGUI)
{
wxASSERT(bar); // To justify safenew
mMenuBtn = safenew wxButton(bar, kMenuID, _("&Manage"));
bs->Add(mMenuBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin);
}
else
{
wxASSERT(bar); // To justify safenew
mMenuBtn = safenew wxBitmapButton(bar, kMenuID, CreateBitmap(effect_menu_xpm, true, false));
#if defined(__WXMAC__)
mMenuBtn->SetName(_("&Manage"));
#else
mMenuBtn->SetLabel(_("&Manage"));
#endif
bs->Add(mMenuBtn);
}
mMenuBtn->SetToolTip(_("Manage presets and options"));
bs->Add(5, 5);
if (!mIsBatch)
{
if (!mIsGUI)
{
if (mSupportsRealtime)
{
wxASSERT(bar); // To justify safenew
mPlayToggleBtn = safenew wxButton(bar, kPlayID, _("Start &Playback"));
mPlayToggleBtn->SetToolTip(_("Start and stop playback"));
bs->Add(mPlayToggleBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin);
}
else if (mEffect &&
(mEffect->GetType() != EffectTypeAnalyze) &&
(mEffect->GetType() != EffectTypeTool)
)
{
wxASSERT(bar); // To justify safenew
mPlayToggleBtn = safenew wxButton(bar, kPlayID, _("&Preview"));
mPlayToggleBtn->SetToolTip(_("Preview effect"));
bs->Add(mPlayToggleBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin);
}
}
else
{
mPlayBM = CreateBitmap(effect_play_xpm, true, false);
mPlayDisabledBM = CreateBitmap(effect_play_disabled_xpm, true, false);
mStopBM = CreateBitmap(effect_stop_xpm, true, false);
mStopDisabledBM = CreateBitmap(effect_stop_disabled_xpm, true, false);
wxASSERT(bar); // To justify safenew
bb = safenew wxBitmapButton(bar, kPlayID, mPlayBM);
bb->SetBitmapDisabled(mPlayDisabledBM);
mPlayBtn = bb;
bs->Add(mPlayBtn);
if (!mSupportsRealtime)
{
mPlayBtn->SetToolTip(_("Preview effect"));
#if defined(__WXMAC__)
mPlayBtn->SetName(_("Preview effect"));
#else
mPlayBtn->SetLabel(_("&Preview effect"));
#endif
}
}
if (mSupportsRealtime)
{
if (!mIsGUI)
{
wxASSERT(bar); // To justify safenew
mRewindBtn = safenew wxButton(bar, kRewindID, _("Skip &Backward"));
bs->Add(mRewindBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin);
}
else
{
wxASSERT(bar); // To justify safenew
bb = safenew wxBitmapButton(bar, kRewindID, CreateBitmap(effect_rewind_xpm, true, true));
bb->SetBitmapDisabled(CreateBitmap(effect_rewind_disabled_xpm, true, true));
mRewindBtn = bb;
#if defined(__WXMAC__)
mRewindBtn->SetName(_("Skip &Backward"));
#else
mRewindBtn->SetLabel(_("Skip &Backward"));
#endif
bs->Add(mRewindBtn);
}
mRewindBtn->SetToolTip(_("Skip backward"));
if (!mIsGUI)
{
wxASSERT(bar); // To justify safenew
mFFwdBtn = safenew wxButton(bar, kFFwdID, _("Skip &Forward"));
bs->Add(mFFwdBtn, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin);
}
else
{
wxASSERT(bar); // To justify safenew
bb = safenew wxBitmapButton(bar, kFFwdID, CreateBitmap(effect_ffwd_xpm, true, true));
bb->SetBitmapDisabled(CreateBitmap(effect_ffwd_disabled_xpm, true, true));
mFFwdBtn = bb;
#if defined(__WXMAC__)
mFFwdBtn->SetName(_("Skip &Forward"));
#else
mFFwdBtn->SetLabel(_("Skip &Forward"));
#endif
bs->Add(mFFwdBtn);
}
mFFwdBtn->SetToolTip(_("Skip forward"));
bs->Add(5, 5);
mEnableCb = safenew wxCheckBox(bar, kEnableID, _("&Enable"));
mEnableCb->SetValue(mEnabled);
mEnableCb->SetName(_("Enable"));
bs->Add(mEnableCb, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, margin);
}
}
bar->SetSizerAndFit(bs.release());
}
long buttons;
if ( mEffect && mEffect->ManualPage().IsEmpty() && mEffect->HelpPage().IsEmpty()) {
buttons = eApplyButton + eCloseButton;
this->SetAcceleratorTable(wxNullAcceleratorTable);
}
else {
buttons = eApplyButton + eCloseButton + eHelpButton;
wxAcceleratorEntry entries[1];
#if defined(__WXMAC__)
// Is there a standard shortcut on Mac?
#else
entries[0].Set(wxACCEL_NORMAL, (int) WXK_F1, wxID_HELP);
#endif
wxAcceleratorTable accel(1, entries);
this->SetAcceleratorTable(accel);
}
if (mEffect && mEffect->mUIDebug) {
buttons += eDebugButton;
}
buttonPanel->SetSizer(CreateStdButtonSizer(buttonPanel, buttons, bar).release());
vs->Add(buttonPanel, 0, wxEXPAND);
SetSizer(vs.release());
}
Layout();
Fit();
Center();
mApplyBtn = (wxButton *) FindWindow(wxID_APPLY);
mCloseBtn = (wxButton *) FindWindow(wxID_CANCEL);
UpdateControls();
w->SetAccept(!mIsGUI);
(!mIsGUI ? w : FindWindow(wxID_APPLY))->SetFocus();
LoadUserPresets();
InitializeRealtime();
SetMinSize(GetSize());
return true;
}
void EffectUIHost::OnInitDialog(wxInitDialogEvent & evt)
{
// Do default handling
wxDialogWrapper::OnInitDialog(evt);
#if wxCHECK_VERSION(3, 0, 0)
//#warning "check to see if this still needed in wx3"
#endif
// Pure hackage coming down the pike...
//
// I have no idea why, but if a wxTextCtrl is the first control in the
// panel, then its contents will not be automatically selected when the
// dialog is displayed.
//
// So, we do the selection manually.
wxTextCtrl *focused = wxDynamicCast(FindFocus(), wxTextCtrl);
if (focused)
{
focused->SelectAll();
}
}
void EffectUIHost::OnErase(wxEraseEvent & WXUNUSED(evt))
{
// Ignore it
}
void EffectUIHost::OnPaint(wxPaintEvent & WXUNUSED(evt))
{
wxPaintDC dc(this);
dc.Clear();
}
void EffectUIHost::OnClose(wxCloseEvent & WXUNUSED(evt))
{
DoCancel();
CleanupRealtime();
Hide();
if (mNeedsResume)
Resume();
mClient->CloseUI();
mClient = NULL;
Destroy();
}
void EffectUIHost::OnApply(wxCommandEvent & evt)
{
// On wxGTK (wx2.8.12), the default action is still executed even if
// the button is disabled. This appears to affect all wxDialogs, not
// just our Effects dialogs. So, this is a only temporary workaround
// for legacy effects that disable the OK button. Hopefully this has
// been corrected in wx3.
if (!FindWindow(wxID_APPLY)->IsEnabled())
{
return;
}
// Honor the "select all if none" preference...a little hackish, but whatcha gonna do...
if (!mIsBatch && mEffect && mEffect->GetType() != EffectTypeGenerate && mProject->mViewInfo.selectedRegion.isPoint())
{
auto flags = AlwaysEnabledFlag;
bool allowed = mProject->ReportIfActionNotAllowed(
mEffect->GetTranslatedName(),
flags,
WaveTracksSelectedFlag | TimeSelectedFlag,
WaveTracksSelectedFlag | TimeSelectedFlag);
if (!allowed)
return;
}
if (!mClient->ValidateUI())
{
return;
}
// This will take care of calling TransferDataFromWindow() for an effect.
if (mEffect && !mEffect->SaveUserPreset(mEffect->GetCurrentSettingsGroup()))
{
return;
}
// This will take care of calling TransferDataFromWindow() for a command.
if (mCommand ){
wxString params;
mCommand->GetAutomationParameters( params );
}
if( mEffect )
mEffect->mUIResultID = evt.GetId();
if (IsModal())
{
mDismissed = true;
EndModal(true);
Close();
return;
}
// 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(); } );
if( mEffect )
mEffect->Apply();
if( mCommand )
mCommand->Apply();
}
void EffectUIHost::DoCancel()
{
if (!mDismissed) {
if( mEffect )
mEffect->mUIResultID = wxID_CANCEL;
if (IsModal())
EndModal(false);
else
Hide();
mDismissed = true;
}
}
void EffectUIHost::OnCancel(wxCommandEvent & WXUNUSED(evt))
{
DoCancel();
Close();
}
void EffectUIHost::OnHelp(wxCommandEvent & WXUNUSED(event))
{
if (mEffect && mEffect->GetFamilyId() == NYQUISTEFFECTS_FAMILY && (mEffect->ManualPage().IsEmpty())) {
// Old ShowHelp required when there is no on-line manual.
// Always use default web browser to allow full-featured HTML pages.
HelpSystem::ShowHelp(FindWindow(wxID_HELP), mEffect->HelpPage(), wxEmptyString, true, true);
}
else if( mEffect )
{
// otherwise use the NEW ShowHelp
HelpSystem::ShowHelp(FindWindow(wxID_HELP), mEffect->ManualPage(), true);
}
}
void EffectUIHost::OnDebug(wxCommandEvent & evt)
{
OnApply(evt);
if( mEffect )
mEffect->mUIResultID = evt.GetId();
}
void EffectUIHost::OnMenu(wxCommandEvent & WXUNUSED(evt))
{
wxMenu menu;
if( !mEffect )
return;
LoadUserPresets();
if (mUserPresets.GetCount() == 0)
{
menu.Append(kUserPresetsDummyID, _("User Presets"))->Enable(false);
}
else
{
auto sub = std::make_unique<wxMenu>();
for (size_t i = 0, cnt = mUserPresets.GetCount(); i < cnt; i++)
{
sub->Append(kUserPresetsID + i, mUserPresets[i]);
}
menu.Append(0, _("User Presets"), sub.release());
}
menu.Append(kSaveAsID, _("Save Preset..."));
if (mUserPresets.GetCount() == 0)
{
menu.Append(kDeletePresetDummyID, _("Delete Preset"))->Enable(false);
}
else
{
auto sub = std::make_unique<wxMenu>();
for (size_t i = 0, cnt = mUserPresets.GetCount(); i < cnt; i++)
{
sub->Append(kDeletePresetID + i, mUserPresets[i]);
}
menu.Append(0, _("Delete Preset"), sub.release());
}
menu.AppendSeparator();
wxArrayString factory = mEffect->GetFactoryPresets();
{
auto sub = std::make_unique<wxMenu>();
sub->Append(kDefaultsID, _("Defaults"));
if (factory.GetCount() > 0)
{
sub->AppendSeparator();
for (size_t i = 0, cnt = factory.GetCount(); i < cnt; i++)
{
wxString label = factory[i];
if (label.IsEmpty())
{
label = _("None");
}
sub->Append(kFactoryPresetsID + i, label);
}
}
menu.Append(0, _("Factory Presets"), sub.release());
}
menu.AppendSeparator();
menu.Append(kImportID, _("Import..."))->Enable(mClient->CanExportPresets());
menu.Append(kExportID, _("Export..."))->Enable(mClient->CanExportPresets());
menu.AppendSeparator();
menu.Append(kOptionsID, _("Options..."))->Enable(mClient->HasOptions());
menu.AppendSeparator();
{
auto sub = std::make_unique<wxMenu>();
sub->Append(kDummyID, wxString::Format(_("Type: %s"),
::wxGetTranslation( mEffect->GetFamilyId().Translation() )));
sub->Append(kDummyID, wxString::Format(_("Name: %s"), mEffect->GetTranslatedName()));
sub->Append(kDummyID, wxString::Format(_("Version: %s"), mEffect->GetVersion()));
sub->Append(kDummyID, wxString::Format(_("Vendor: %s"), mEffect->GetVendor().Translation()));
sub->Append(kDummyID, wxString::Format(_("Description: %s"), mEffect->GetDescription()));
menu.Append(0, _("About"), sub.release());
}
wxWindow *btn = FindWindow(kMenuID);
wxRect r = btn->GetRect();
btn->PopupMenu(&menu, r.GetLeft(), r.GetBottom());
}
void EffectUIHost::Resume()
{
if (!mClient->ValidateUI()) {
// If we're previewing we should still be able to stop playback
// so don't disable transport buttons.
// mEffect->EnableApply(false); // currently this would also disable transport buttons.
// The preferred behaviour is currently undecided, so for now
// just disallow enabling until settings are valid.
mEnabled = false;
mEnableCb->SetValue(mEnabled);
return;
}
mEffect->RealtimeResume();
}
void EffectUIHost::OnEnable(wxCommandEvent & WXUNUSED(evt))
{
mEnabled = mEnableCb->GetValue();
if (mEnabled) {
Resume();
mNeedsResume = false;
}
else
{
mEffect->RealtimeSuspend();
mNeedsResume = true;
}
UpdateControls();
}
void EffectUIHost::OnPlay(wxCommandEvent & WXUNUSED(evt))
{
if (!mSupportsRealtime)
{
if (!mClient->ValidateUI() || !mEffect->TransferDataFromWindow())
{
return;
}
mEffect->Preview(false);
return;
}
if (mPlaying)
{
mPlayPos = gAudioIO->GetStreamTime();
mProject->GetControlToolBar()->StopPlaying();
}
else
{
if (mProject->IsPlayRegionLocked())
{
double t0, t1;
mProject->GetPlayRegion(&t0, &t1);
mRegion.setTimes(t0, t1);
mPlayPos = mRegion.t0();
}
else if (mProject->mViewInfo.selectedRegion.t0() != mRegion.t0() ||
mProject->mViewInfo.selectedRegion.t1() != mRegion.t1())
{
mRegion = mProject->mViewInfo.selectedRegion;
mPlayPos = mRegion.t0();
}
if (mPlayPos > mRegion.t1())
{
mPlayPos = mRegion.t1();
}
mProject->GetControlToolBar()->PlayPlayRegion
(SelectedRegion(mPlayPos, mRegion.t1()),
mProject->GetDefaultPlayOptions(), PlayMode::normalPlay);
}
}
void EffectUIHost::OnRewind(wxCommandEvent & WXUNUSED(evt))
{
if (mPlaying)
{
double seek;
gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &seek, 1.0);
double pos = gAudioIO->GetStreamTime();
if (pos - seek < mRegion.t0())
{
seek = pos - mRegion.t0();
}
gAudioIO->SeekStream(-seek);
}
else
{
mPlayPos = mRegion.t0();
}
}
void EffectUIHost::OnFFwd(wxCommandEvent & WXUNUSED(evt))
{
if (mPlaying)
{
double seek;
gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &seek, 1.0);
double pos = gAudioIO->GetStreamTime();
if (mRegion.t0() < mRegion.t1() && pos + seek > mRegion.t1())
{
seek = mRegion.t1() - pos;
}
gAudioIO->SeekStream(seek);
}
else
{
// It allows to play past end of selection...probably useless
mPlayPos = mRegion.t1();
}
}
void EffectUIHost::OnPlayback(wxCommandEvent & evt)
{
evt.Skip();
if (evt.GetInt() != 0)
{
if (evt.GetEventObject() != mProject)
{
mDisableTransport = true;
}
else
{
mPlaying = true;
}
}
else
{
mDisableTransport = false;
mPlaying = false;
}
if (mPlaying)
{
mRegion = mProject->mViewInfo.selectedRegion;
mPlayPos = mRegion.t0();
}
UpdateControls();
}
void EffectUIHost::OnCapture(wxCommandEvent & evt)
{
evt.Skip();
if (evt.GetInt() != 0)
{
if (evt.GetEventObject() != mProject)
{
mDisableTransport = true;
}
else
{
mCapturing = true;
}
}
else
{
mDisableTransport = false;
mCapturing = false;
}
UpdateControls();
}
void EffectUIHost::OnUserPreset(wxCommandEvent & evt)
{
int preset = evt.GetId() - kUserPresetsID;
mEffect->LoadUserPreset(mEffect->GetUserPresetsGroup(mUserPresets[preset]));
return;
}
void EffectUIHost::OnFactoryPreset(wxCommandEvent & evt)
{
mEffect->LoadFactoryPreset(evt.GetId() - kFactoryPresetsID);
return;
}
void EffectUIHost::OnDeletePreset(wxCommandEvent & evt)
{
wxString preset = mUserPresets[evt.GetId() - kDeletePresetID];
int res = AudacityMessageBox(wxString::Format(_("Are you sure you want to delete \"%s\"?"), preset),
_("Delete Preset"),
wxICON_QUESTION | wxYES_NO);
if (res == wxYES)
{
mEffect->RemovePrivateConfigSubgroup(mEffect->GetUserPresetsGroup(preset));
}
LoadUserPresets();
return;
}
void EffectUIHost::OnSaveAs(wxCommandEvent & WXUNUSED(evt))
{
wxTextCtrl *text;
wxString name;
wxDialogWrapper dlg(this, wxID_ANY, wxString(_("Save Preset")));
ShuttleGui S(&dlg, eIsCreating);
S.StartPanel();
{
S.StartVerticalLay(1);
{
S.StartHorizontalLay(wxALIGN_LEFT, 0);
{
text = S.AddTextBox(_("Preset name:"), name, 30);
}
S.EndHorizontalLay();
S.SetBorder(10);
S.AddStandardButtons();
}
S.EndVerticalLay();
}
S.EndPanel();
dlg.SetSize(dlg.GetSizer()->GetMinSize());
dlg.Center();
while (true)
{
int rc = dlg.ShowModal();
if (rc != wxID_OK)
{
break;
}
name = text->GetValue();
if (name.IsEmpty())
{
AudacityMessageDialog md(this,
_("You must specify a name"),
_("Save Preset"));
md.Center();
md.ShowModal();
continue;
}
if (mUserPresets.Index(name) != wxNOT_FOUND)
{
AudacityMessageDialog md(this,
_("Preset already exists.\n\nReplace?"),
_("Save Preset"),
wxYES_NO | wxCANCEL | wxICON_EXCLAMATION);
md.Center();
int choice = md.ShowModal();
if (choice == wxID_CANCEL)
{
break;
}
if (choice == wxID_NO)
{
continue;
}
}
mEffect->SaveUserPreset(mEffect->GetUserPresetsGroup(name));
LoadUserPresets();
break;
}
return;
}
void EffectUIHost::OnImport(wxCommandEvent & WXUNUSED(evt))
{
mClient->ImportPresets();
LoadUserPresets();
return;
}
void EffectUIHost::OnExport(wxCommandEvent & WXUNUSED(evt))
{
// may throw
// exceptions are handled in AudacityApp::OnExceptionInMainLoop
mClient->ExportPresets();
return;
}
void EffectUIHost::OnOptions(wxCommandEvent & WXUNUSED(evt))
{
mClient->ShowOptions();
return;
}
void EffectUIHost::OnDefaults(wxCommandEvent & WXUNUSED(evt))
{
mEffect->LoadFactoryDefaults();
return;
}
wxBitmap EffectUIHost::CreateBitmap(const char * const xpm[], bool up, bool pusher)
{
wxMemoryDC dc;
wxBitmap pic(xpm);
wxBitmap mod(pic.GetWidth() + 6, pic.GetHeight() + 6);
dc.SelectObject(mod);
#if !defined(__WXMAC__)
#if defined(__WXGTK__)
wxColour newColour = wxSystemSettings::GetColour(wxSYS_COLOUR_BACKGROUND);
#elif defined(__WXMSW__)
wxColour newColour = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
#endif
dc.SetBackground(wxBrush(newColour));
dc.Clear();
#endif
int offset = 3;
if (pusher)
{
if (!up)
{
offset += 1;
}
}
dc.DrawBitmap(pic, offset, offset, true);
dc.SelectObject(wxNullBitmap);
return mod;
}
void EffectUIHost::UpdateControls()
{
if (mIsBatch)
{
return;
}
if (mCapturing || mDisableTransport)
{
// Don't allow focus to get trapped
wxWindow *focus = FindFocus();
if (focus == mRewindBtn || focus == mFFwdBtn || focus == mPlayBtn || focus == mEnableCb)
{
mCloseBtn->SetFocus();
}
}
mApplyBtn->Enable(!mCapturing);
if (mEffect && (mEffect->GetType() != EffectTypeAnalyze) && (mEffect->GetType() != EffectTypeTool) )
{
(!mIsGUI ? mPlayToggleBtn : mPlayBtn)->Enable(!(mCapturing || mDisableTransport));
}
if (mSupportsRealtime)
{
mRewindBtn->Enable(!(mCapturing || mDisableTransport));
mFFwdBtn->Enable(!(mCapturing || mDisableTransport));
mEnableCb->Enable(!(mCapturing || mDisableTransport));
wxBitmapButton *bb;
if (mPlaying)
{
if (!mIsGUI)
{
/* i18n-hint: The access key "&P" should be the same in
"Stop &Playback" and "Start &Playback" */
mPlayToggleBtn->SetLabel(_("Stop &Playback"));
mPlayToggleBtn->Refresh();
}
else
{
bb = (wxBitmapButton *) mPlayBtn;
bb->SetBitmapLabel(mStopBM);
bb->SetBitmapDisabled(mStopDisabledBM);
bb->SetToolTip(_("Stop"));
#if defined(__WXMAC__)
bb->SetName(_("Stop &Playback"));
#else
bb->SetLabel(_("Stop &Playback"));
#endif
}
}
else
{
if (!mIsGUI)
{
/* i18n-hint: The access key "&P" should be the same in
"Stop &Playback" and "Start &Playback" */
mPlayToggleBtn->SetLabel(_("Start &Playback"));
mPlayToggleBtn->Refresh();
}
else
{
bb = (wxBitmapButton *) mPlayBtn;
bb->SetBitmapLabel(mPlayBM);
bb->SetBitmapDisabled(mPlayDisabledBM);
bb->SetToolTip(_("Play"));
#if defined(__WXMAC__)
bb->SetName(_("Start &Playback"));
#else
bb->SetLabel(_("Start &Playback"));
#endif
}
}
}
}
void EffectUIHost::LoadUserPresets()
{
mUserPresets.Clear();
if( mEffect )
mEffect->GetPrivateConfigSubgroups(mEffect->GetUserPresetsGroup(wxEmptyString), mUserPresets);
mUserPresets.Sort();
return;
}
void EffectUIHost::InitializeRealtime()
{
if (mSupportsRealtime && !mInitialized)
{
EffectManager::Get().RealtimeAddEffect(mEffect);
wxTheApp->Bind(EVT_AUDIOIO_PLAYBACK,
&EffectUIHost::OnPlayback,
this);
wxTheApp->Bind(EVT_AUDIOIO_CAPTURE,
&EffectUIHost::OnCapture,
this);
mInitialized = true;
}
}
void EffectUIHost::CleanupRealtime()
{
if (mSupportsRealtime && mInitialized)
{
EffectManager::Get().RealtimeRemoveEffect(mEffect);
mInitialized = false;
}
}
///////////////////////////////////////////////////////////////////////////////
//
// EffectPresetsDialog
//
///////////////////////////////////////////////////////////////////////////////
enum
{
ID_Type = 10000
};
BEGIN_EVENT_TABLE(EffectPresetsDialog, wxDialogWrapper)
EVT_CHOICE(ID_Type, EffectPresetsDialog::OnType)
EVT_LISTBOX_DCLICK(wxID_ANY, EffectPresetsDialog::OnOk)
EVT_BUTTON(wxID_OK, EffectPresetsDialog::OnOk)
EVT_BUTTON(wxID_CANCEL, EffectPresetsDialog::OnCancel)
END_EVENT_TABLE()
EffectPresetsDialog::EffectPresetsDialog(wxWindow *parent, Effect *effect)
: wxDialogWrapper(parent, wxID_ANY, wxString(_("Select Preset")))
{
ShuttleGui S(this, eIsCreating);
S.StartVerticalLay();
{
S.StartTwoColumn();
S.SetStretchyCol(1);
{
wxArrayString empty;
S.AddPrompt(_("Type:"));
mType = S.Id(ID_Type).AddChoice( {}, wxT(""), &empty);
mType->SetSelection(0);
S.AddPrompt(_("&Preset:"));
mPresets = S.AddListBox(&empty, wxLB_SINGLE | wxLB_NEEDED_SB );
}
S.EndTwoColumn();
S.AddStandardButtons();
}
S.EndVerticalLay();
mUserPresets = effect->GetUserPresets();
mFactoryPresets = effect->GetFactoryPresets();
if (mUserPresets.GetCount() > 0)
{
mType->Append(_("User Presets"));
}
if (mFactoryPresets.GetCount() > 0)
{
mType->Append(_("Factory Presets"));
}
if (effect->HasCurrentSettings())
{
mType->Append(_("Current Settings"));
}
if (effect->HasFactoryDefaults())
{
mType->Append(_("Factory Defaults"));
}
UpdateUI();
}
EffectPresetsDialog::~EffectPresetsDialog()
{
}
wxString EffectPresetsDialog::GetSelected() const
{
return mSelection;
}
void EffectPresetsDialog::SetSelected(const wxString & parms)
{
wxString preset = parms;
if (preset.StartsWith(Effect::kUserPresetIdent))
{
preset.Replace(Effect::kUserPresetIdent, wxEmptyString, false);
SetPrefix(_("User Presets"), preset);
}
else if (preset.StartsWith(Effect::kFactoryPresetIdent))
{
preset.Replace(Effect::kFactoryPresetIdent, wxEmptyString, false);
SetPrefix(_("Factory Presets"), preset);
}
else if (preset.StartsWith(Effect::kCurrentSettingsIdent))
{
SetPrefix(_("Current Settings"), wxEmptyString);
}
else if (preset.StartsWith(Effect::kFactoryDefaultsIdent))
{
SetPrefix(_("Factory Defaults"), wxEmptyString);
}
}
void EffectPresetsDialog::SetPrefix(const wxString & type, const wxString & prefix)
{
mType->SetStringSelection(type);
if (type.IsSameAs(_("User Presets")))
{
mPresets->Clear();
mPresets->Append(mUserPresets);
mPresets->Enable(true);
mPresets->SetStringSelection(prefix);
if (mPresets->GetSelection() == wxNOT_FOUND)
{
mPresets->SetSelection(0);
}
mSelection = Effect::kUserPresetIdent + mPresets->GetStringSelection();
}
else if (type.IsSameAs(_("Factory Presets")))
{
mPresets->Clear();
for (size_t i = 0, cnt = mFactoryPresets.GetCount(); i < cnt; i++)
{
wxString label = mFactoryPresets[i];
if (label.IsEmpty())
{
label = _("None");
}
mPresets->Append(label);
}
mPresets->Enable(true);
mPresets->SetStringSelection(prefix);
if (mPresets->GetSelection() == wxNOT_FOUND)
{
mPresets->SetSelection(0);
}
mSelection = Effect::kFactoryPresetIdent + mPresets->GetStringSelection();
}
else if (type.IsSameAs(_("Current Settings")))
{
mPresets->Clear();
mPresets->Enable(false);
mSelection = Effect::kCurrentSettingsIdent;
}
else if (type.IsSameAs(_("Factory Defaults")))
{
mPresets->Clear();
mPresets->Enable(false);
mSelection = Effect::kFactoryDefaultsIdent;
}
}
void EffectPresetsDialog::UpdateUI()
{
int selected = mType->GetSelection();
if (selected == wxNOT_FOUND)
{
selected = 0;
mType->SetSelection(selected);
}
wxString type = mType->GetString(selected);
if (type.IsSameAs(_("User Presets")))
{
selected = mPresets->GetSelection();
if (selected == wxNOT_FOUND)
{
selected = 0;
}
mPresets->Clear();
mPresets->Append(mUserPresets);
mPresets->Enable(true);
mPresets->SetSelection(selected);
mSelection = Effect::kUserPresetIdent + mPresets->GetString(selected);
}
else if (type.IsSameAs(_("Factory Presets")))
{
selected = mPresets->GetSelection();
if (selected == wxNOT_FOUND)
{
selected = 0;
}
mPresets->Clear();
for (size_t i = 0, cnt = mFactoryPresets.GetCount(); i < cnt; i++)
{
wxString label = mFactoryPresets[i];
if (label.IsEmpty())
{
label = _("None");
}
mPresets->Append(label);
}
mPresets->Enable(true);
mPresets->SetSelection(selected);
mSelection = Effect::kFactoryPresetIdent + mPresets->GetString(selected);
}
else if (type.IsSameAs(_("Current Settings")))
{
mPresets->Clear();
mPresets->Enable(false);
mSelection = Effect::kCurrentSettingsIdent;
}
else if (type.IsSameAs(_("Factory Defaults")))
{
mPresets->Clear();
mPresets->Enable(false);
mSelection = Effect::kFactoryDefaultsIdent;
}
}
void EffectPresetsDialog::OnType(wxCommandEvent & WXUNUSED(evt))
{
UpdateUI();
}
void EffectPresetsDialog::OnOk(wxCommandEvent & WXUNUSED(evt))
{
UpdateUI();
EndModal(true);
}
void EffectPresetsDialog::OnCancel(wxCommandEvent & WXUNUSED(evt))
{
mSelection = wxEmptyString;
EndModal(false);
}