audacia/src/effects/ladspa/LadspaEffect.cpp

1829 lines
44 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
LadspaEffect.cpp
Dominic Mazzoni
This class implements a LADSPA Plug-in effect.
*******************************************************************//**
\class LadspaEffect
\brief An Effect that calls up a LADSPA plug in, i.e. many possible
effects from this one class.
*//****************************************************************//**
\class LadspaEffectDialog
\brief Dialog used with Effect
*//*******************************************************************/
#include "LadspaEffect.h" // This class's header file
#include <float.h>
#if !defined(__WXMSW__)
#include <dlfcn.h>
#ifndef RTLD_DEEPBIND
#define RTLD_DEEPBIND 0
#endif
#endif
#include <wx/setup.h> // for wxUSE_* macros
#include <wx/wxprec.h>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/dcclient.h>
#include <wx/filename.h>
#include <wx/log.h>
#include <wx/menu.h>
#include <wx/sizer.h>
#include <wx/slider.h>
#include <wx/statbox.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/tokenzr.h>
#include <wx/intl.h>
#include <wx/scrolwin.h>
#include <wx/version.h>
#include "../../FileNames.h"
#include "../../ShuttleGui.h"
#include "../../widgets/NumericTextCtrl.h"
#include "../../widgets/valnum.h"
#include "../../widgets/wxPanelWrapper.h"
#include "../../ModuleManager.h"
#if wxUSE_ACCESSIBILITY
#include "../../widgets/WindowAccessible.h"
#endif
// ============================================================================
// List of effects that ship with Audacity. These will be autoregistered.
// ============================================================================
const static wxChar *kShippedEffects[] =
{
wxT("sc4_1882.dll"),
};
// ============================================================================
// Module registration entry point
//
// This is the symbol that Audacity looks for when the module is built as a
// dynamic library.
//
// When the module is builtin to Audacity, we use the same function, but it is
// declared static so as not to clash with other builtin modules.
// ============================================================================
DECLARE_MODULE_ENTRY(AudacityModule)
{
// Create and register the importer
// Trust the module manager not to leak this
return safenew LadspaEffectsModule();
}
// ============================================================================
// Register this as a builtin module
// ============================================================================
DECLARE_BUILTIN_MODULE(LadspaBuiltin);
///////////////////////////////////////////////////////////////////////////////
//
// LadspaEffectsModule
//
///////////////////////////////////////////////////////////////////////////////
LadspaEffectsModule::LadspaEffectsModule()
{
}
LadspaEffectsModule::~LadspaEffectsModule()
{
}
// ============================================================================
// ComponentInterface implementation
// ============================================================================
PluginPath LadspaEffectsModule::GetPath()
{
return {};
}
ComponentInterfaceSymbol LadspaEffectsModule::GetSymbol()
{
/* i18n-hint: abbreviates "Linux Audio Developer's Simple Plugin API"
(Application programming interface)
*/
return XO("LADSPA Effects");
}
VendorSymbol LadspaEffectsModule::GetVendor()
{
return XO("The Audacity Team");
}
wxString LadspaEffectsModule::GetVersion()
{
// This "may" be different if this were to be maintained as a separate DLL
return LADSPAEFFECTS_VERSION;
}
TranslatableString LadspaEffectsModule::GetDescription()
{
return XO("Provides LADSPA Effects");
}
// ============================================================================
// ModuleInterface implementation
// ============================================================================
bool LadspaEffectsModule::Initialize()
{
// Nothing to do here
return true;
}
void LadspaEffectsModule::Terminate()
{
// Nothing to do here
return;
}
EffectFamilySymbol LadspaEffectsModule::GetOptionalFamilySymbol()
{
#if USE_LADSPA
return LADSPAEFFECTS_FAMILY;
#else
return {};
#endif
}
const FileExtensions &LadspaEffectsModule::GetFileExtensions()
{
static FileExtensions result{{
#ifdef __WXMSW__
_T("dll")
#else
_T("so")
#ifdef __WXMAC__
// Is it correct that these are candidate plug-in files too for macOs?
, _T("dylib")
#endif
#endif
}};
return result;
}
FilePath LadspaEffectsModule::InstallPath()
{
// To do: better choice
return FileNames::PlugInDir();
}
bool LadspaEffectsModule::AutoRegisterPlugins(PluginManagerInterface & pm)
{
// Autoregister effects that we "think" are ones that have been shipped with
// Audacity. A little simplistic, but it should suffice for now.
auto pathList = GetSearchPaths();
FilePaths files;
TranslatableString ignoredErrMsg;
for (int i = 0; i < (int)WXSIZEOF(kShippedEffects); i++)
{
files.clear();
pm.FindFilesInPathList(kShippedEffects[i], pathList, files);
for (size_t j = 0, cnt = files.size(); j < cnt; j++)
{
if (!pm.IsPluginRegistered(files[j]))
{
// No checking for error ?
DiscoverPluginsAtPath(files[j], ignoredErrMsg,
PluginManagerInterface::DefaultRegistrationCallback);
}
}
}
// We still want to be called during the normal registration process
return false;
}
PluginPaths LadspaEffectsModule::FindPluginPaths(PluginManagerInterface & pm)
{
auto pathList = GetSearchPaths();
FilePaths files;
#if defined(__WXMAC__)
// Recursively scan for all shared objects
pm.FindFilesInPathList(wxT("*.so"), pathList, files, true);
#elif defined(__WXMSW__)
// Recursively scan for all DLLs
pm.FindFilesInPathList(wxT("*.dll"), pathList, files, true);
#else
// Recursively scan for all shared objects
pm.FindFilesInPathList(wxT("*.so"), pathList, files, true);
#endif
return { files.begin(), files.end() };
}
unsigned LadspaEffectsModule::DiscoverPluginsAtPath(
const PluginPath & path, TranslatableString &errMsg,
const RegistrationCallback &callback)
{
errMsg = {};
// Since we now have builtin VST support, ignore the VST bridge as it
// causes duplicate menu entries to appear.
wxFileName ff(path);
if (ff.GetName().CmpNoCase(wxT("vst-bridge")) == 0) {
errMsg = XO("Audacity no longer uses vst-bridge");
return 0;
}
// As a courtesy to some plug-ins that might be bridges to
// open other plug-ins, we set the current working
// directory to be the plug-in's directory.
wxString envpath;
bool hadpath = wxGetEnv(wxT("PATH"), &envpath);
wxSetEnv(wxT("PATH"), ff.GetPath() + wxFILE_SEP_PATH + envpath);
wxString saveOldCWD = ff.GetCwd();
ff.SetCwd();
int index = 0;
int nLoaded = 0;
LADSPA_Descriptor_Function mainFn = NULL;
#if defined(__WXMSW__)
wxDynamicLibrary lib;
if (lib.Load(path, wxDL_NOW)) {
wxLogNull logNo;
mainFn = (LADSPA_Descriptor_Function) lib.GetSymbol(wxT("ladspa_descriptor"));
#else
void *lib = dlopen((const char *)path.ToUTF8(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
if (lib) {
mainFn = (LADSPA_Descriptor_Function) dlsym(lib, "ladspa_descriptor");
#endif
if (mainFn) {
const LADSPA_Descriptor *data;
for (data = mainFn(index); data; data = mainFn(++index)) {
LadspaEffect effect(path, index);
if (effect.SetHost(NULL)) {
++nLoaded;
if (callback)
callback( this, &effect );
}
else
errMsg = XO("Could not load the library");
}
}
}
else
errMsg = XO("Could not load the library");
#if defined(__WXMSW__)
if (lib.IsLoaded()) {
// PRL: I suspect Bug1257 -- Crash when enabling Amplio2 -- is the fault of a timing-
// dependent multi-threading bug in the Amplio2 library itself, in case the unload of the .dll
// comes too soon after the load. I saw the bug in Release builds but not Debug.
// A sleep of even 1 ms was enough to fix the problem for me, but let's be even more generous.
::wxMilliSleep(10);
lib.Unload();
}
#else
if (lib) {
dlclose(lib);
}
#endif
wxSetWorkingDirectory(saveOldCWD);
hadpath ? wxSetEnv(wxT("PATH"), envpath) : wxUnsetEnv(wxT("PATH"));
return nLoaded;
}
bool LadspaEffectsModule::IsPluginValid(const PluginPath & path, bool bFast)
{
if( bFast )
return true;
wxString realPath = path.BeforeFirst(wxT(';'));
return wxFileName::FileExists(realPath);
}
std::unique_ptr<ComponentInterface>
LadspaEffectsModule::CreateInstance(const PluginPath & path)
{
// Acquires a resource for the application.
// For us, the path is two words.
// 1) The library's path
// 2) The LADSPA descriptor index
long index;
wxString realPath = path.BeforeFirst(wxT(';'));
path.AfterFirst(wxT(';')).ToLong(&index);
return std::make_unique<LadspaEffect>(realPath, (int)index);
}
FilePaths LadspaEffectsModule::GetSearchPaths()
{
FilePaths pathList;
wxString pathVar;
// Check for the LADSPA_PATH environment variable
pathVar = wxString::FromUTF8(getenv("LADSPA_PATH"));
if (!pathVar.empty())
{
wxStringTokenizer tok(pathVar, wxPATH_SEP);
while (tok.HasMoreTokens())
{
pathList.push_back(tok.GetNextToken());
}
}
#if defined(__WXMAC__)
#define LADSPAPATH wxT("/Library/Audio/Plug-Ins/LADSPA")
// Look in ~/Library/Audio/Plug-Ins/LADSPA and /Library/Audio/Plug-Ins/LADSPA
pathList.push_back(wxGetHomeDir() + wxFILE_SEP_PATH + LADSPAPATH);
pathList.push_back(LADSPAPATH);
#elif defined(__WXMSW__)
// No special paths...probably should look in %CommonProgramFiles%\LADSPA
#else
pathList.push_back(wxGetHomeDir() + wxFILE_SEP_PATH + wxT(".ladspa"));
#if defined(__LP64__)
pathList.push_back(wxT("/usr/local/lib64/ladspa"));
pathList.push_back(wxT("/usr/lib64/ladspa"));
#endif
pathList.push_back(wxT("/usr/local/lib/ladspa"));
pathList.push_back(wxT("/usr/lib/ladspa"));
pathList.push_back(wxT(LIBDIR) wxT("/ladspa"));
#endif
return pathList;
}
///////////////////////////////////////////////////////////////////////////////
//
// LadspaEffectOptionsDialog
//
///////////////////////////////////////////////////////////////////////////////
class LadspaEffectOptionsDialog final : public wxDialogWrapper
{
public:
LadspaEffectOptionsDialog(wxWindow * parent, EffectHostInterface *host);
virtual ~LadspaEffectOptionsDialog();
void PopulateOrExchange(ShuttleGui & S);
void OnOk(wxCommandEvent & evt);
private:
EffectHostInterface *mHost;
bool mUseLatency;
DECLARE_EVENT_TABLE()
};
BEGIN_EVENT_TABLE(LadspaEffectOptionsDialog, wxDialogWrapper)
EVT_BUTTON(wxID_OK, LadspaEffectOptionsDialog::OnOk)
END_EVENT_TABLE()
LadspaEffectOptionsDialog::LadspaEffectOptionsDialog(wxWindow * parent, EffectHostInterface *host)
: wxDialogWrapper(parent, wxID_ANY, XO("LADSPA Effect Options"))
{
mHost = host;
mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true);
ShuttleGui S(this, eIsCreating);
PopulateOrExchange(S);
}
LadspaEffectOptionsDialog::~LadspaEffectOptionsDialog()
{
}
void LadspaEffectOptionsDialog::PopulateOrExchange(ShuttleGui & S)
{
S.SetBorder(5);
S.StartHorizontalLay(wxEXPAND, 1);
{
S.StartVerticalLay(false);
{
S.StartStatic(XO("Latency Compensation"));
{
S.AddVariableText( XO(
"As part of their processing, some LADSPA effects must delay returning "
"audio to Audacity. When not compensating for this delay, you will "
"notice that small silences have been inserted into the audio. "
"Enabling this option will provide that compensation, but it may "
"not work for all LADSPA effects."),
false, 0, 650);
S.StartHorizontalLay(wxALIGN_LEFT);
{
S.TieCheckBox(XXO("Enable &compensation"),
mUseLatency);
}
S.EndHorizontalLay();
}
S.EndStatic();
}
S.EndVerticalLay();
}
S.EndHorizontalLay();
S.AddStandardButtons();
Layout();
Fit();
Center();
}
void LadspaEffectOptionsDialog::OnOk(wxCommandEvent & WXUNUSED(evt))
{
if (!Validate())
{
return;
}
ShuttleGui S(this, eIsGettingFromDialog);
PopulateOrExchange(S);
mHost->SetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency);
EndModal(wxID_OK);
}
enum
{
ID_Duration = 20000,
ID_Toggles = 21000,
ID_Sliders = 22000,
ID_Texts = 23000,
};
///////////////////////////////////////////////////////////////////////////////
//
// LadspaEffectMeter
//
///////////////////////////////////////////////////////////////////////////////
class LadspaEffectMeter final : public wxWindow
{
public:
LadspaEffectMeter(wxWindow *parent, const float & val, float min, float max);
virtual ~LadspaEffectMeter();
private:
void OnErase(wxEraseEvent & evt);
void OnPaint(wxPaintEvent & evt);
void OnIdle(wxIdleEvent & evt);
void OnSize(wxSizeEvent & evt);
private:
const float & mVal;
float mMin;
float mMax;
float mLastValue;
DECLARE_EVENT_TABLE()
};
BEGIN_EVENT_TABLE(LadspaEffectMeter, wxWindow)
EVT_IDLE(LadspaEffectMeter::OnIdle)
EVT_ERASE_BACKGROUND(LadspaEffectMeter::OnErase)
EVT_PAINT(LadspaEffectMeter::OnPaint)
EVT_SIZE(LadspaEffectMeter::OnSize)
END_EVENT_TABLE()
LadspaEffectMeter::LadspaEffectMeter(wxWindow *parent, const float & val, float min, float max)
: wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDEFAULT_CONTROL_BORDER),
mVal(val)
{
mMin = min;
mMax = max;
mLastValue = -mVal;
SetBackgroundColour(*wxWHITE);
}
LadspaEffectMeter::~LadspaEffectMeter()
{
}
void LadspaEffectMeter::OnIdle(wxIdleEvent &evt)
{
evt.Skip();
if (mLastValue != mVal)
{
Refresh(false);
}
}
void LadspaEffectMeter::OnErase(wxEraseEvent & WXUNUSED(evt))
{
// Just ignore it to prevent flashing
}
void LadspaEffectMeter::OnPaint(wxPaintEvent & WXUNUSED(evt))
{
wxPaintDC dc(this);
// Cache some metrics
wxRect r = GetClientRect();
wxCoord x = r.GetLeft();
wxCoord y = r.GetTop();
wxCoord w = r.GetWidth();
wxCoord h = r.GetHeight();
// These use unscaled value, min, and max
float val = mVal;
if (val > mMax)
{
val = mMax;
}
if (val < mMin)
{
val = mMin;
}
val -= mMin;
// Setup for erasing the background
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxColour(100, 100, 220));
dc.Clear();
dc.DrawRectangle(x, y, (w * (val / fabs(mMax - mMin))), h);
mLastValue = mVal;
}
void LadspaEffectMeter::OnSize(wxSizeEvent & WXUNUSED(evt))
{
Refresh(false);
}
///////////////////////////////////////////////////////////////////////////////
//
// LadspaEffect
//
///////////////////////////////////////////////////////////////////////////////
BEGIN_EVENT_TABLE(LadspaEffect, wxEvtHandler)
EVT_COMMAND_RANGE(ID_Toggles, ID_Toggles + 999, wxEVT_COMMAND_CHECKBOX_CLICKED, LadspaEffect::OnCheckBox)
EVT_COMMAND_RANGE(ID_Sliders, ID_Sliders + 999, wxEVT_COMMAND_SLIDER_UPDATED, LadspaEffect::OnSlider)
EVT_COMMAND_RANGE(ID_Texts, ID_Texts + 999, wxEVT_COMMAND_TEXT_UPDATED, LadspaEffect::OnTextCtrl)
END_EVENT_TABLE()
LadspaEffect::LadspaEffect(const wxString & path, int index)
{
mPath = path;
mIndex = index;
mData = NULL;
mHost = NULL;
mMaster = NULL;
mReady = false;
mInteractive = false;
mAudioIns = 0;
mAudioOuts = 0;
mNumInputControls = 0;
mNumOutputControls = 0;
mSampleRate = 44100;
mBlockSize = 0;
mLatencyPort = -1;
mDialog = NULL;
mParent = NULL;
}
LadspaEffect::~LadspaEffect()
{
}
// ============================================================================
// ComponentInterface implementation
// ============================================================================
PluginPath LadspaEffect::GetPath()
{
return wxString::Format(wxT("%s;%d"), mPath, mIndex);
}
ComponentInterfaceSymbol LadspaEffect::GetSymbol()
{
return LAT1CTOWX(mData->Name);
}
VendorSymbol LadspaEffect::GetVendor()
{
return { LAT1CTOWX(mData->Maker) };
}
wxString LadspaEffect::GetVersion()
{
return _("n/a");
}
TranslatableString LadspaEffect::GetDescription()
{
return Verbatim( LAT1CTOWX(mData->Copyright) );
}
// ============================================================================
// EffectDefinitionInterface implementation
// ============================================================================
EffectType LadspaEffect::GetType()
{
if (mAudioIns == 0 && mAudioOuts == 0)
{
return EffectTypeTool;
}
if (mAudioIns == 0)
{
return EffectTypeGenerate;
}
if (mAudioOuts == 0)
{
return EffectTypeAnalyze;
}
return EffectTypeProcess;
}
EffectFamilySymbol LadspaEffect::GetFamily()
{
return LADSPAEFFECTS_FAMILY;
}
bool LadspaEffect::IsInteractive()
{
return mInteractive;
}
bool LadspaEffect::IsDefault()
{
return false;
}
bool LadspaEffect::IsLegacy()
{
return false;
}
bool LadspaEffect::SupportsRealtime()
{
return GetType() != EffectTypeGenerate;
}
bool LadspaEffect::SupportsAutomation()
{
return mNumInputControls > 0;
}
// ============================================================================
// EffectClientInterface Implementation
// ============================================================================
bool LadspaEffect::SetHost(EffectHostInterface *host)
{
mHost = host;
if (!Load())
{
return false;
}
mInputPorts.reinit( mData->PortCount );
mOutputPorts.reinit( mData->PortCount );
mInputControls.reinit( mData->PortCount );
mOutputControls.reinit( mData->PortCount );
for (unsigned long p = 0; p < mData->PortCount; p++)
{
LADSPA_PortDescriptor d = mData->PortDescriptors[p];
// Collect the audio ports
if (LADSPA_IS_PORT_AUDIO(d))
{
if (LADSPA_IS_PORT_INPUT(d))
{
mInputPorts[mAudioIns++] = p;
}
else if (LADSPA_IS_PORT_OUTPUT(d))
{
mOutputPorts[mAudioOuts++] = p;
}
}
// Determine the port's default value
else if (LADSPA_IS_PORT_CONTROL(d) && LADSPA_IS_PORT_INPUT(d))
{
mInteractive = true;
LADSPA_PortRangeHint hint = mData->PortRangeHints[p];
float val = float(1.0);
float lower = hint.LowerBound;
float upper = hint.UpperBound;
if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor))
{
lower *= mSampleRate;
upper *= mSampleRate;
}
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor) && val < lower)
{
val = lower;
}
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor) && val > upper)
{
val = upper;
}
if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint.HintDescriptor))
{
val = lower;
}
if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint.HintDescriptor))
{
val = upper;
}
if (LADSPA_IS_HINT_DEFAULT_LOW(hint.HintDescriptor))
{
if (LADSPA_IS_HINT_LOGARITHMIC(hint.HintDescriptor))
{
val = exp(log(lower) * 0.75f + log(upper) * 0.25f);
}
else
{
val = lower * 0.75f + upper * 0.25f;
}
}
if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint.HintDescriptor))
{
if (LADSPA_IS_HINT_LOGARITHMIC(hint.HintDescriptor))
{
val = exp(log(lower) * 0.5f + log(upper) * 0.5f);
}
else
{
val = lower * 0.5f + upper * 0.5f;
}
}
if (LADSPA_IS_HINT_DEFAULT_HIGH(hint.HintDescriptor))
{
if (LADSPA_IS_HINT_LOGARITHMIC(hint.HintDescriptor))
{
val = exp(log(lower) * 0.25f + log(upper) * 0.75f);
}
else
{
val = lower * 0.25f + upper * 0.75f;
}
}
if (LADSPA_IS_HINT_DEFAULT_0(hint.HintDescriptor))
{
val = 0.0f;
}
if (LADSPA_IS_HINT_DEFAULT_1(hint.HintDescriptor))
{
val = 1.0f;
}
if (LADSPA_IS_HINT_DEFAULT_100(hint.HintDescriptor))
{
val = 100.0f;
}
if (LADSPA_IS_HINT_DEFAULT_440(hint.HintDescriptor))
{
val = 440.0f;
}
mNumInputControls++;
mInputControls[p] = val;
}
else if (LADSPA_IS_PORT_CONTROL(d) && LADSPA_IS_PORT_OUTPUT(d))
{
mOutputControls[p] = 0.0;
// LADSPA effects have a convention of providing latency on an output
// control port whose name is "latency".
if (strcmp(mData->PortNames[p], "latency") == 0)
{
mLatencyPort = p;
}
else
{
mInteractive = true;
mNumOutputControls++;
}
}
}
// mHost will be null during registration
if (mHost)
{
mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true);
bool haveDefaults;
mHost->GetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), haveDefaults, false);
if (!haveDefaults)
{
SaveParameters(mHost->GetFactoryDefaultsGroup());
mHost->SetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), true);
}
LoadParameters(mHost->GetCurrentSettingsGroup());
}
return true;
}
unsigned LadspaEffect::GetAudioInCount()
{
return mAudioIns;
}
unsigned LadspaEffect::GetAudioOutCount()
{
return mAudioOuts;
}
int LadspaEffect::GetMidiInCount()
{
return 0;
}
int LadspaEffect::GetMidiOutCount()
{
return 0;
}
void LadspaEffect::SetSampleRate(double rate)
{
mSampleRate = rate;
}
size_t LadspaEffect::SetBlockSize(size_t maxBlockSize)
{
mBlockSize = maxBlockSize;
return mBlockSize;
}
size_t LadspaEffect::GetBlockSize() const
{
return mBlockSize;
}
sampleCount LadspaEffect::GetLatency()
{
if (mUseLatency && mLatencyPort >= 0 && !mLatencyDone)
{
mLatencyDone = true;
return sampleCount ( mOutputControls[mLatencyPort] );
}
return 0;
}
size_t LadspaEffect::GetTailSize()
{
return 0;
}
bool LadspaEffect::IsReady()
{
return mReady;
}
bool LadspaEffect::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
{
/* Instantiate the plugin */
if (!mReady)
{
mMaster = InitInstance(mSampleRate);
if (!mMaster)
{
return false;
}
mReady = true;
}
mLatencyDone = false;
return true;
}
bool LadspaEffect::ProcessFinalize()
{
if (mReady)
{
mReady = false;
FreeInstance(mMaster);
mMaster = NULL;
}
return true;
}
size_t LadspaEffect::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
{
for (int i = 0; i < (int)mAudioIns; i++)
{
mData->connect_port(mMaster, mInputPorts[i], inBlock[i]);
}
for (int i = 0; i < (int)mAudioOuts; i++)
{
mData->connect_port(mMaster, mOutputPorts[i], outBlock[i]);
}
mData->run(mMaster, blockLen);
RefreshControls(true);
return blockLen;
}
bool LadspaEffect::RealtimeInitialize()
{
return true;
}
bool LadspaEffect::RealtimeAddProcessor(unsigned WXUNUSED(numChannels), float sampleRate)
{
LADSPA_Handle slave = InitInstance(sampleRate);
if (!slave)
{
return false;
}
mSlaves.push_back(slave);
return true;
}
bool LadspaEffect::RealtimeFinalize()
{
for (size_t i = 0, cnt = mSlaves.size(); i < cnt; i++)
{
FreeInstance(mSlaves[i]);
}
mSlaves.clear();
return true;
}
bool LadspaEffect::RealtimeSuspend()
{
return true;
}
bool LadspaEffect::RealtimeResume()
{
return true;
}
bool LadspaEffect::RealtimeProcessStart()
{
return true;
}
size_t LadspaEffect::RealtimeProcess(int group,
float **inbuf,
float **outbuf,
size_t numSamples)
{
for (int i = 0; i < (int)mAudioIns; i++)
{
mData->connect_port(mSlaves[group], mInputPorts[i], inbuf[i]);
}
for (int i = 0; i < (int)mAudioOuts; i++)
{
mData->connect_port(mSlaves[group], mOutputPorts[i], outbuf[i]);
}
mData->run(mSlaves[group], numSamples);
return numSamples;
}
bool LadspaEffect::RealtimeProcessEnd()
{
return true;
}
bool LadspaEffect::ShowInterface(
wxWindow &parent, const EffectDialogFactory &factory, bool forceModal)
{
if (mDialog)
{
if ( mDialog->Close(true) )
mDialog = nullptr;
return false;
}
// mDialog is null
auto cleanup = valueRestorer( mDialog );
if ( factory )
mDialog = factory(parent, mHost, this);
if (!mDialog)
{
return false;
}
mDialog->Layout();
mDialog->Fit();
mDialog->SetMinSize(mDialog->GetSize());
if ((SupportsRealtime() || GetType() == EffectTypeAnalyze) && !forceModal)
{
mDialog->Show();
cleanup.release();
return false;
}
bool res = mDialog->ShowModal() != 0;
return res;
}
bool LadspaEffect::GetAutomationParameters(CommandParameters & parms)
{
for (unsigned long p = 0; p < mData->PortCount; p++)
{
LADSPA_PortDescriptor d = mData->PortDescriptors[p];
if (LADSPA_IS_PORT_CONTROL(d) && LADSPA_IS_PORT_INPUT(d))
{
if (!parms.Write(LAT1CTOWX(mData->PortNames[p]), mInputControls[p]))
{
return false;
}
}
}
return true;
}
bool LadspaEffect::SetAutomationParameters(CommandParameters & parms)
{
for (unsigned long p = 0; p < mData->PortCount; p++)
{
LADSPA_PortDescriptor descriptor = mData->PortDescriptors[p];
if (LADSPA_IS_PORT_CONTROL(descriptor) && LADSPA_IS_PORT_INPUT(descriptor))
{
wxString labelText = LAT1CTOWX(mData->PortNames[p]);
double d = 0.0;
if (!parms.Read(labelText, &d))
{
return false;
}
mInputControls[p] = d;
}
}
return true;
}
bool LadspaEffect::LoadUserPreset(const RegistryPath & name)
{
if (!LoadParameters(name))
{
return false;
}
RefreshControls();
return true;
}
bool LadspaEffect::SaveUserPreset(const RegistryPath & name)
{
return SaveParameters(name);
}
RegistryPaths LadspaEffect::GetFactoryPresets()
{
return {};
}
bool LadspaEffect::LoadFactoryPreset(int WXUNUSED(id))
{
return true;
}
bool LadspaEffect::LoadFactoryDefaults()
{
if (!LoadParameters(mHost->GetFactoryDefaultsGroup()))
{
return false;
}
RefreshControls();
return true;
}
// ============================================================================
// EffectUIClientInterface Implementation
// ============================================================================
void LadspaEffect::SetHostUI(EffectUIHostInterface *host)
{
mUIHost = host;
}
bool LadspaEffect::PopulateUI(ShuttleGui &S)
{
auto parent = S.GetParent();
mParent = parent;
mParent->PushEventHandler(this);
mToggles.reinit( mData->PortCount );
mSliders.reinit( mData->PortCount );
mFields.reinit( mData->PortCount, true);
mLabels.reinit( mData->PortCount );
mMeters.reinit( mData->PortCount );
wxASSERT(mParent); // To justify safenew
wxScrolledWindow *const w = safenew wxScrolledWindow(mParent,
wxID_ANY,
wxDefaultPosition,
wxDefaultSize,
wxVSCROLL | wxTAB_TRAVERSAL);
{
auto mainSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
w->SetScrollRate(0, 20);
// This fools NVDA into not saying "Panel" when the dialog gets focus
w->SetName(wxT("\a"));
w->SetLabel(wxT("\a"));
mainSizer->Add(w, 1, wxEXPAND);
mParent->SetSizer(mainSizer.release());
}
wxSizer *marginSizer;
{
auto uMarginSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
marginSizer = uMarginSizer.get();
if (mNumInputControls)
{
auto paramSizer = std::make_unique<wxStaticBoxSizer>(wxVERTICAL, w, _("Effect Settings"));
auto gridSizer = std::make_unique<wxFlexGridSizer>(5, 0, 0);
gridSizer->AddGrowableCol(3);
wxControl *item;
// Add the duration control for generators
if (GetType() == EffectTypeGenerate)
{
item = safenew wxStaticText(w, 0, _("Duration:"));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
mDuration = safenew
NumericTextCtrl(w, ID_Duration,
NumericConverter::TIME,
mHost->GetDurationFormat(),
mHost->GetDuration(),
mSampleRate,
NumericTextCtrl::Options{}
.AutoPos(true));
mDuration->SetName( XO("Duration") );
gridSizer->Add(mDuration, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
}
for (unsigned long p = 0; p < mData->PortCount; p++)
{
LADSPA_PortDescriptor d = mData->PortDescriptors[p];
if (LADSPA_IS_PORT_AUDIO(d) || LADSPA_IS_PORT_OUTPUT(d))
{
continue;
}
wxString labelText = LAT1CTOWX(mData->PortNames[p]);
item = safenew wxStaticText(w, 0, wxString::Format(_("%s:"), labelText));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
wxString fieldText;
LADSPA_PortRangeHint hint = mData->PortRangeHints[p];
if (LADSPA_IS_HINT_TOGGLED(hint.HintDescriptor))
{
mToggles[p] = safenew wxCheckBox(w, ID_Toggles + p, wxT(""));
mToggles[p]->SetName(labelText);
mToggles[p]->SetValue(mInputControls[p] > 0);
gridSizer->Add(mToggles[p], 0, wxALL, 5);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
continue;
}
wxString bound;
float lower = -FLT_MAX;
float upper = FLT_MAX;
bool haslo = false;
bool hashi = false;
bool forceint = false;
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor))
{
lower = hint.LowerBound;
haslo = true;
}
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor))
{
upper = hint.UpperBound;
hashi = true;
}
if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor))
{
lower *= mSampleRate;
upper *= mSampleRate;
forceint = true;
}
// Limit to the UI precision
lower = ceilf(lower * 1000000.0) / 1000000.0;
upper = floorf(upper * 1000000.0) / 1000000.0;
mInputControls[p] = roundf(mInputControls[p] * 1000000.0) / 1000000.0;
if (haslo && mInputControls[p] < lower)
{
mInputControls[p] = lower;
}
if (hashi && mInputControls[p] > upper)
{
mInputControls[p] = lower;
}
// Don't specify a value at creation time. This prevents unwanted events
// being sent to the OnTextCtrl() handler before the associated slider
// has been created.
mFields[p] = safenew wxTextCtrl(w, ID_Texts + p);
mFields[p]->SetName(labelText);
gridSizer->Add(mFields[p], 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString str;
if (haslo)
{
if (LADSPA_IS_HINT_INTEGER(hint.HintDescriptor) || forceint)
{
str.Printf(wxT("%d"), (int)(lower + 0.5));
}
else
{
str = Internat::ToDisplayString(lower);
}
item = safenew wxStaticText(w, 0, str);
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
}
else
{
gridSizer->Add(1, 1, 0);
}
mSliders[p] = safenew wxSliderWrapper(w, ID_Sliders + p,
0, 0, 1000,
wxDefaultPosition,
wxSize(200, -1));
#if wxUSE_ACCESSIBILITY
// so that name can be set on a standard control
mSliders[p]->SetAccessible(safenew WindowAccessible(mSliders[p]));
#endif
mSliders[p]->SetName(labelText);
gridSizer->Add(mSliders[p], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5);
if (hashi)
{
if (LADSPA_IS_HINT_INTEGER(hint.HintDescriptor) || forceint)
{
str.Printf(wxT("%d"), (int)(upper + 0.5));
}
else
{
str = Internat::ToDisplayString(upper);
}
item = safenew wxStaticText(w, 0, str);
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5);
}
else
{
gridSizer->Add(1, 1, 0);
}
if (LADSPA_IS_HINT_INTEGER(hint.HintDescriptor) || forceint)
{
fieldText.Printf(wxT("%d"), (int)(mInputControls[p] + 0.5));
IntegerValidator<float> vld(&mInputControls[p]);
vld.SetRange(haslo ? lower : INT_MIN,
hashi ? upper : INT_MAX);
mFields[p]->SetValidator(vld);
}
else
{
fieldText = Internat::ToDisplayString(mInputControls[p]);
// > 12 decimal places can cause rounding errors in display.
FloatingPointValidator<float> vld(6, &mInputControls[p]);
vld.SetRange(lower, upper);
// Set number of decimal places
if (upper - lower < 10.0)
{
vld.SetStyle(NumValidatorStyle::THREE_TRAILING_ZEROES);
}
else if (upper - lower < 100.0)
{
vld.SetStyle(NumValidatorStyle::TWO_TRAILING_ZEROES);
}
else
{
vld.SetStyle(NumValidatorStyle::ONE_TRAILING_ZERO);
}
mFields[p]->SetValidator(vld);
}
// Set the textctrl value. This will trigger an event so OnTextCtrl()
// can update the slider.
mFields[p]->SetValue(fieldText);
}
paramSizer->Add(gridSizer.release(), 0, wxEXPAND | wxALL, 5);
marginSizer->Add(paramSizer.release(), 0, wxEXPAND | wxALL, 5);
}
if (mNumOutputControls > 0)
{
auto paramSizer = std::make_unique<wxStaticBoxSizer>(wxVERTICAL, w, _("Effect Output"));
auto gridSizer = std::make_unique<wxFlexGridSizer>(2, 0, 0);
gridSizer->AddGrowableCol(1);
wxControl *item;
for (unsigned long p = 0; p < mData->PortCount; p++)
{
LADSPA_PortDescriptor d = mData->PortDescriptors[p];
if (LADSPA_IS_PORT_AUDIO(d) || LADSPA_IS_PORT_INPUT(d))
{
continue;
}
wxString labelText = LAT1CTOWX(mData->PortNames[p]);
item = safenew wxStaticText(w, 0, wxString::Format(_("%s:"), labelText));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
//LADSPA_PortRangeHint hint = mData->PortRangeHints[p];
wxString bound;
float lower = 0.0;
float upper = 1.0;
/*
bool haslo = false;
bool hashi = false;
bool forceint = false;
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor))
{
lower = hint.LowerBound;
haslo = true;
}
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor))
{
upper = hint.UpperBound;
hashi = true;
}
if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor))
{
lower *= mSampleRate;
upper *= mSampleRate;
forceint = true;
}
*/
// Limit to the UI precision
lower = ceilf(lower * 1000000.0) / 1000000.0;
upper = floorf(upper * 1000000.0) / 1000000.0;
mInputControls[p] = roundf(mInputControls[p] * 1000000.0) / 1000000.0;
mMeters[p] = safenew LadspaEffectMeter(w, mOutputControls[p], lower, upper);
mMeters[p]->SetLabel(labelText); // for screen readers
gridSizer->Add(mMeters[p], 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 5);
}
paramSizer->Add(gridSizer.release(), 0, wxEXPAND | wxALL, 5);
marginSizer->Add(paramSizer.release(), 0, wxEXPAND | wxALL, 5);
RefreshControls(true);
}
w->SetSizer(uMarginSizer.release());
}
w->Layout();
// Try to give the window a sensible default/minimum size
wxSize sz1 = marginSizer->GetMinSize();
wxSize sz2 = mParent->GetMinSize();
w->SetMinSize( { std::min(sz1.x, sz2.x), std::min(sz1.y, sz2.y) } );
// And let the parent reduce to the NEW minimum if possible
mParent->SetMinSize({ -1, -1 });
return true;
}
bool LadspaEffect::IsGraphicalUI()
{
return false;
}
bool LadspaEffect::ValidateUI()
{
if (!mParent->Validate())
{
return false;
}
if (GetType() == EffectTypeGenerate)
{
mHost->SetDuration(mDuration->GetValue());
}
return true;
}
bool LadspaEffect::HideUI()
{
return true;
}
bool LadspaEffect::CloseUI()
{
mParent->RemoveEventHandler(this);
mToggles.reset();
mSliders.reset();
mFields.reset();
mLabels.reset();
mUIHost = NULL;
mParent = NULL;
mDialog = NULL;
return true;
}
bool LadspaEffect::CanExportPresets()
{
return false;
}
void LadspaEffect::ExportPresets()
{
}
void LadspaEffect::ImportPresets()
{
}
bool LadspaEffect::HasOptions()
{
return true;
}
void LadspaEffect::ShowOptions()
{
LadspaEffectOptionsDialog dlg(mParent, mHost);
if (dlg.ShowModal())
{
// Reinitialize configuration options
mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true);
}
}
// ============================================================================
// LadspaEffect Implementation
// ============================================================================
bool LadspaEffect::Load()
{
if (mLib.IsLoaded())
{
return true;
}
wxFileName ff = mPath;
wxString envpath;
bool hadpath = wxGetEnv(wxT("PATH"), &envpath);
wxSetEnv(wxT("PATH"), ff.GetPath() + wxFILE_SEP_PATH + envpath);
wxString saveOldCWD = ff.GetCwd();
ff.SetCwd();
LADSPA_Descriptor_Function mainFn = NULL;
if (mLib.Load(mPath, wxDL_NOW))
{
wxLogNull logNo;
mainFn = (LADSPA_Descriptor_Function) mLib.GetSymbol(wxT("ladspa_descriptor"));
if (mainFn)
{
mData = mainFn(mIndex);
return true;
}
}
if (mLib.IsLoaded())
{
mLib.Unload();
}
wxSetWorkingDirectory(saveOldCWD);
hadpath ? wxSetEnv(wxT("PATH"), envpath) : wxUnsetEnv(wxT("PATH"));
return false;
}
void LadspaEffect::Unload()
{
if (mLib.IsLoaded())
{
mLib.Unload();
}
}
bool LadspaEffect::LoadParameters(const RegistryPath & group)
{
wxString parms;
if (!mHost->GetPrivateConfig(group, wxT("Parameters"), parms, wxEmptyString))
{
return false;
}
CommandParameters eap;
if (!eap.SetParameters(parms))
{
return false;
}
return SetAutomationParameters(eap);
}
bool LadspaEffect::SaveParameters(const RegistryPath & group)
{
CommandParameters eap;
if (!GetAutomationParameters(eap))
{
return false;
}
wxString parms;
if (!eap.GetParameters(parms))
{
return false;
}
return mHost->SetPrivateConfig(group, wxT("Parameters"), parms);
}
LADSPA_Handle LadspaEffect::InitInstance(float sampleRate)
{
/* Instantiate the plugin */
LADSPA_Handle handle = mData->instantiate(mData, sampleRate);
if (!handle)
{
return NULL;
}
for (unsigned long p = 0; p < mData->PortCount; p++)
{
LADSPA_PortDescriptor d = mData->PortDescriptors[p];
if (LADSPA_IS_PORT_CONTROL(d))
{
if (LADSPA_IS_PORT_INPUT(d))
{
mData->connect_port(handle, p, &mInputControls[p]);
}
else
{
mData->connect_port(handle, p, &mOutputControls[p]);
}
}
}
if (mData->activate)
{
mData->activate(handle);
}
return handle;
}
void LadspaEffect::FreeInstance(LADSPA_Handle handle)
{
if (mData->deactivate)
{
mData->deactivate(handle);
}
mData->cleanup(handle);
}
void LadspaEffect::OnCheckBox(wxCommandEvent & evt)
{
int p = evt.GetId() - ID_Toggles;
mInputControls[p] = mToggles[p]->GetValue();
}
void LadspaEffect::OnSlider(wxCommandEvent & evt)
{
int p = evt.GetId() - ID_Sliders;
float val;
float lower = float(0.0);
float upper = float(10.0);
float range;
bool forceint = false;
LADSPA_PortRangeHint hint = mData->PortRangeHints[p];
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor))
lower = hint.LowerBound;
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor))
upper = hint.UpperBound;
if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor)) {
lower *= mSampleRate;
upper *= mSampleRate;
forceint = true;
}
range = upper - lower;
val = (mSliders[p]->GetValue() / 1000.0) * range + lower;
wxString str;
if (LADSPA_IS_HINT_INTEGER(hint.HintDescriptor) || forceint)
str.Printf(wxT("%d"), (int)(val + 0.5));
else
str = Internat::ToDisplayString(val);
mFields[p]->SetValue(str);
mInputControls[p] = val;
}
void LadspaEffect::OnTextCtrl(wxCommandEvent & evt)
{
LadspaEffect *that = this;
int p = evt.GetId() - ID_Texts;
float val;
float lower = float(0.0);
float upper = float(10.0);
float range;
val = Internat::CompatibleToDouble(that->mFields[p]->GetValue());
LADSPA_PortRangeHint hint = that->mData->PortRangeHints[p];
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor))
lower = hint.LowerBound;
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor))
upper = hint.UpperBound;
if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor)) {
lower *= mSampleRate;
upper *= mSampleRate;
}
range = upper - lower;
if (val < lower)
val = lower;
if (val > upper)
val = upper;
mInputControls[p] = val;
that->mSliders[p]->SetValue((int)(((val-lower)/range) * 1000.0 + 0.5));
}
void LadspaEffect::RefreshControls(bool outputOnly)
{
if (!mParent)
{
return;
}
for (unsigned long p = 0; p < mData->PortCount; p++)
{
LADSPA_PortDescriptor d = mData->PortDescriptors[p];
if (!(LADSPA_IS_PORT_CONTROL(d)))
{
continue;
}
wxString fieldText;
LADSPA_PortRangeHint hint = mData->PortRangeHints[p];
bool forceint = false;
if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor))
{
forceint = true;
}
if (LADSPA_IS_PORT_OUTPUT(d))
{
continue;
}
if (outputOnly)
{
continue;
}
if (LADSPA_IS_HINT_TOGGLED(hint.HintDescriptor))
{
mToggles[p]->SetValue(mInputControls[p] > 0);
continue;
}
if (LADSPA_IS_HINT_INTEGER(hint.HintDescriptor) || forceint)
{
fieldText.Printf(wxT("%d"), (int)(mInputControls[p] + 0.5));
}
else
{
fieldText = Internat::ToDisplayString(mInputControls[p]);
}
// Set the textctrl value. This will trigger an event so OnTextCtrl()
// can update the slider.
mFields[p]->SetValue(fieldText);
}
}