1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-04 22:29:27 +02:00
audacity/src/effects/lv2/LV2Effect.cpp
lllucius 41083f74cc Another round of effects bashing.
I've added some of the new plugin stuff to LV2, Nyquist, and
Vamp so that they play better in the new system.  They no
longer get bunched in with the Audacity effects when sorting
or grouping the menus.  They have not been fully converted
but they should be good for 2.1.0.

Nyquist plugins now include ";author" and ";copyright"
statements.

Added the 4 new Nyquist plugins to the Windows build.

Audiounits are still coming...had to push them to the back
burner to get this other stuff out of the way.

Scanning for new plugins has been improved so that newly
discovered ones will be shown to the user when Audacity starts.

Effects menu sorting has been fixed and improved.

Disabling effect types in Preferences works again and you
no longer have to restart Audacity for them the change to work.

Effect usage in chains works again.

Plugin registration dialog code simplified a bit.

Group names in the pluginregistry are now base64 encoded.  I
never really thought about it, but wxFileConfig group names
are case insensitive and since I was using the group name as
the plugin ID, I ran into a conflict on Linux where there
were two plugins with the same name, just different case.  (And
they were different plugins.)  Hoping all of this will change
when/if the config file gets converted to XML.  (wx3 if finally
including XML support)

A fair amount of cleanup of this new code has been done and
will continue as more stuff is converted.
2014-11-19 06:58:44 +00:00

1620 lines
40 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
LV2Effect.h
Audacity(R) is copyright (c) 1999-2008 Audacity Team.
License: GPL v2. See License.txt.
**********************************************************************/
#include "../../Audacity.h"
#if defined(USE_LV2)
#include <queue>
#if defined(__WXMSW__)
#include <float.h>
#define isfinite _finite
#define isnan _isnan
#elif defined(__WXMAC__)
#else
//#define isfinite std::isfinite
//#define isnam std::isnan
#endif
#include <wx/wxprec.h>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/dynarray.h>
#include <wx/msgdlg.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 "../Effect.h"
#include "LoadLV2.h"
#include "LV2Effect.h"
#include "LV2PortGroup.h"
#include "../../Internat.h"
#include "lv2_event_helpers.h"
#include "lilv/lilv.h"
#include <wx/arrimpl.cpp>
///////////////////////////////////////////////////////////////////////////////
//
// LV2Effect
//
///////////////////////////////////////////////////////////////////////////////
WX_DEFINE_OBJARRAY(LV2PortArray);
LV2Effect::LV2Effect(const LilvPlugin *data,
const std::set<wxString> & categories)
: mValid(true),
mCategories(categories),
mMidiInput(0),
mLatencyPortIndex(-1)
{
// We don't support any features at all, so if the plugin requires
// any we skip it.
LilvNodes *req = lilv_plugin_get_required_features(data);
size_t nFeatures = lilv_nodes_size(req);
lilv_nodes_free(req);
if (nFeatures > 0)
{
mValid = false;
return;
}
mData = data;
pluginName = GetString(lilv_plugin_get_name(mData), true);
fInBuffer = NULL;
fOutBuffer = NULL;
mDuration = 0;
// Allocate buffers for the port indices and the default control values
int numPorts = lilv_plugin_get_num_ports(mData);
float *minimumValues = new float [numPorts];
float *maximumValues = new float [numPorts];
float *defaultValues = new float [numPorts];
// Retrieve the port ranges for all ports (some values may be NaN)
lilv_plugin_get_port_ranges_float(mData, minimumValues,
maximumValues, defaultValues);
// Get info about all ports
for (int i = 0; i < numPorts; i++)
{
const LilvPort *port = lilv_plugin_get_port_by_index(mData, i);
LV2Port internalPort;
internalPort.mIndex = lilv_port_get_index(mData, port);
// Get the port name
LilvNode *tmpName = lilv_port_get_name(mData, port);
internalPort.mName = GetString(tmpName);
lilv_node_free(tmpName);
// Get the scale points
LilvScalePoints* points = lilv_port_get_scale_points(mData, port);
LILV_FOREACH(scale_points, j, points)
{
const LilvScalePoint *point = lilv_scale_points_get(points, j);
internalPort.mScaleValues.Add(lilv_node_as_float(lilv_scale_point_get_value(point)));
internalPort.mScaleLabels.Add(GetString(lilv_scale_point_get_label(point)));
}
lilv_scale_points_free(points);
// Get the groups
LilvNodes *groups = lilv_port_get_value(mData, port, gPortGroup);
if (groups)
{
LilvNode *group = lilv_nodes_get_first(groups);
wxString uri = GetString(group);
wxString label;
const LilvNode *name = lilv_world_get(gWorld, group, gName, NULL);
if (name)
{
label = GetString(name);
}
else
{
// Shouldn't happen, but provide something
label = uri;
}
lilv_nodes_free(groups);
// Check for new group
if (mPortGroups.find(uri) == mPortGroups.end())
{
mPortGroups[uri] = LV2PortGroup(label);
}
#if 0
// Get subgroup
//
// LLL: This isn't right...must find or construct a plugin with
// subgroups.
LilvNodes *subgroup = lilv_node_get_value(mData, port, gSubGroupOf);
if (subgroups)
{
LilvNode *subgroup = lilv_nodes_get_first(subgroups);
wxString uri = GetString(subgroup);
const LilvNode *subgroup = lilv_world_get(gWorld, group, gSubGroupOf, NULL);
wxString label = GetString(name);
lilv_nodes_free(subgroup);
}
else
#endif
{
mRootGroup.AddSubGroup(mPortGroups[uri]);
}
mPortGroups[uri].AddParameter(i);
}
else
{
mRootGroup.AddParameter(i);
}
// Get the port type
if (lilv_port_is_a(mData, port, gAudioPortClass))
{
if (lilv_port_is_a(mData, port, gInputPortClass))
{
mAudioInputs.Add(internalPort);
}
else if (lilv_port_is_a(mData, port, gOutputPortClass))
{
mAudioOutputs.Add(internalPort);
}
}
else if (lilv_port_is_a(mData, port, gControlPortClass) &&
lilv_port_is_a(mData, port, gInputPortClass))
{
internalPort.mControlBuffer = float(1.0);
internalPort.mMin = minimumValues[i];
internalPort.mMax = maximumValues[i];
internalPort.mDefault = defaultValues[i];
if (isfinite(defaultValues[i]))
{
internalPort.mControlBuffer = defaultValues[i];
}
else if (isfinite(minimumValues[i]))
{
internalPort.mControlBuffer = minimumValues[i];
}
else if (isfinite(maximumValues[i]))
{
internalPort.mControlBuffer = maximumValues[i];
}
if (lilv_port_has_property(mData, port, gPortToggled))
{
internalPort.mToggle = true;
}
else if (lilv_port_has_property(mData, port, gPortIsInteger))
{
internalPort.mInteger = true;
}
else if (lilv_port_has_property(mData, port, gPortIsSampleRate))
{
internalPort.mSampleRate = true;
}
else if (lilv_port_has_property(mData, port, gPortIsEnumeration))
{
internalPort.mEnumeration = true;
}
mControlInputs.Add(internalPort);
}
else if (lilv_port_is_a(mData, port, gControlPortClass) &&
lilv_port_is_a(mData, port, gOutputPortClass))
{
// If there is more than one latency port, the plugin is invalid
if (lilv_port_has_property(mData, port, gPortIsLatency))
{
if (mLatencyPortIndex >= 0)
{
mValid = false;
continue;
}
mLatencyPortIndex = i;
}
else if (!lilv_port_has_property(mData, port, gPortIsOptional))
{
mControlOutputs.Add(internalPort);
}
}
else if (lilv_port_is_a(mData, port, gMidiPortClass) &&
lilv_port_is_a(mData, port, gInputPortClass))
{
// If there is more than one MIDI input port, the plugin is invalid
if (mMidiInput)
{
mValid = false;
continue;
}
mMidiInput = new LV2Port(internalPort);
}
else
{
// Unknown port type, we set the invalid flag
// mValid = false;
}
}
delete [] minimumValues;
delete [] maximumValues;
delete [] defaultValues;
// MIDI synths may not have any audio inputs.
if (mMidiInput && mAudioInputs.GetCount() > 0)
{
mValid = false;
}
// Determine whether the plugin is a generator, effect or analyser
// depending on the number of ports of each type (not completely accurate,
// but works most of the time)
int flags = PLUGIN_EFFECT;
if (mAudioInputs.GetCount() == 0)
{
flags |= INSERT_EFFECT;
}
else if (mAudioOutputs.GetCount() == 0)
{
flags |= ANALYZE_EFFECT;
}
else
{
flags |= PROCESS_EFFECT;
}
SetEffectFlags(flags);
}
LV2Effect::~LV2Effect()
{
if (mMidiInput)
{
delete mMidiInput;
}
}
// ============================================================================
// IdentInterface implementation
// ============================================================================
wxString LV2Effect::GetID()
{
return GetString(lilv_plugin_get_uri(mData));
}
wxString LV2Effect::GetPath()
{
return GetString(lilv_plugin_get_bundle_uri(mData));
}
wxString LV2Effect::GetName()
{
return pluginName;
}
wxString LV2Effect::GetVendor()
{
wxString vendor = GetString(lilv_plugin_get_author_name(mData), true);
if (vendor.IsEmpty())
{
vendor = wxT("N/A");
}
return vendor;
}
wxString LV2Effect::GetVersion()
{
return wxT("N/A");
}
wxString LV2Effect::GetDescription()
{
return wxT("N/A");
}
// ============================================================================
// EffectIdentInterface implementation
// ============================================================================
EffectType LV2Effect::GetType()
{
// For now, relegate to Effect()
return Effect::GetType();
}
wxString LV2Effect::GetFamily()
{
return LV2EFFECTS_FAMILY;
}
bool LV2Effect::IsInteractive()
{
// For now, relegate to Effect()
return Effect::IsInteractive();
}
bool LV2Effect::IsDefault()
{
return false;
}
bool LV2Effect::IsLegacy()
{
return true;
}
bool LV2Effect::SupportsRealtime()
{
return false;
}
bool LV2Effect::SupportsAutomation()
{
return true;
}
// ============================================================================
// Effect Implementation
// ============================================================================
wxString LV2Effect::GetEffectName()
{
if (mControlInputs.GetCount() > 0)
{
return pluginName + wxT("...");
}
else
{
return pluginName;
}
}
std::set<wxString> LV2Effect::GetEffectCategories()
{
return mCategories;
}
wxString LV2Effect::GetEffectIdentifier()
{
wxStringTokenizer st(pluginName, wxT(" "));
wxString id;
// CamelCase the name
while (st.HasMoreTokens())
{
wxString tok = st.GetNextToken();
id += tok.Left(1).MakeUpper() + tok.Mid(1);
}
return id;
}
wxString LV2Effect::GetEffectAction()
{
return wxString::Format(_("Performing Effect: %s"),
pluginName.c_str());
}
bool LV2Effect::Init()
{
mBlockSize = 0;
mainRate = 0;
TrackListOfKindIterator iter(Track::Wave, mTracks);
Track *left = iter.First();
while(left)
{
if (mainRate == 0)
{
mainRate = (int)(((WaveTrack *)left)->GetRate() + 0.5);
}
if (left->GetLinked())
{
Track *right = iter.Next();
if (((WaveTrack *)left)->GetRate() !=
((WaveTrack *)right)->GetRate())
{
wxMessageBox(_("Sorry, Plug-in Effects cannot be performed on stereo tracks where the individual channels of the track do not match."));
return false;
}
}
left = iter.Next();
}
if (mainRate <= 0)
{
mainRate = (int)(mProjectRate + 0.5);
}
return true;
}
bool LV2Effect::PromptUser()
{
if (mControlInputs.GetCount() > 0)
{
double length = mT1 > mT0 ? mT1 - mT0 : sDefaultGenerateLen;
double noteLength = length / 2;
unsigned char noteVelocity = 64;
unsigned char noteKey = 64;
LV2EffectDialog dlog(this, mParent, mData, mainRate, length,
noteLength, noteVelocity, noteKey);
dlog.CentreOnParent();
dlog.ShowModal();
if (!dlog.GetReturnCode())
{
return false;
}
mDuration = dlog.GetLength();
mNoteLength = dlog.GetNoteLength();
mNoteVelocity = dlog.GetNoteVelocity();
mNoteKey = dlog.GetNoteKey();
}
return true;
}
bool LV2Effect::Process()
{
CopyInputTracks();
bool bGoodResult = true;
TrackListIterator iter(mOutputTracks);
int count = 0;
Track *left = iter.First();
Track *right = NULL;
while (left)
{
sampleCount lstart = 0, rstart = 0;
sampleCount len;
GetSamples((WaveTrack *)left, &lstart, &len);
right = NULL;
if (left->GetLinked() && mAudioInputs.GetCount() > 1)
{
right = iter.Next();
GetSamples((WaveTrack *)right, &rstart, &len);
}
if (mAudioInputs.GetCount() < 2 && right)
{
// If the effect is mono, apply to each channel separately
bGoodResult = ProcessStereo(count, (WaveTrack *)left, NULL,
lstart, 0, len) &&
ProcessStereo(count, (WaveTrack *)right, NULL,
rstart, 0, len);
}
else
{
bGoodResult = ProcessStereo(count,
(WaveTrack *)left, (WaveTrack *)right,
lstart, rstart, len);
}
if (!bGoodResult)
{
break;
}
left = iter.Next();
count++;
}
ReplaceProcessedTracks(bGoodResult);
return bGoodResult;
}
bool LV2Effect::ProcessStereo(int count,
WaveTrack *left,
WaveTrack *right,
sampleCount lstart,
sampleCount rstart,
sampleCount len)
{
/* Allocate buffers */
if (mBlockSize == 0)
{
mBlockSize = left->GetMaxBlockSize() * 2;
fInBuffer = new float *[mAudioInputs.GetCount()];
for (size_t i = 0; i < mAudioInputs.GetCount(); i++)
{
fInBuffer[i] = new float[mBlockSize];
}
fOutBuffer = new float *[mAudioOutputs.GetCount()];
for (size_t i = 0; i < mAudioOutputs.GetCount(); i++)
{
fOutBuffer[i] = new float[mBlockSize];
}
}
/* Instantiate the plugin */
LilvInstance *handle = lilv_plugin_instantiate(mData,
left->GetRate(),
gLV2Features);
if (!handle)
{
wxMessageBox(wxString::Format(_("Unable to load plug-in %s"), pluginName.c_str()));
return false;
}
/* Write the Note On to the MIDI event buffer and connect it */
LV2_Event_Buffer *midiBuffer = NULL;
int noteOffTime;
if (mMidiInput)
{
midiBuffer = lv2_event_buffer_new(40, 2);
LV2_Event_Iterator iter;
lv2_event_begin(&iter, midiBuffer);
uint8_t noteOn[] = { 0x90, mNoteKey, mNoteVelocity };
lv2_event_write(&iter, 0, 0, 1, 3, noteOn);
noteOffTime = mNoteLength * left->GetRate();
if (noteOffTime < len && noteOffTime < mBlockSize) {
uint8_t noteOff[] = { 0x80, mNoteKey, 64 };
lv2_event_write(&iter, noteOffTime, 0, 1, 3, noteOff);
}
lilv_instance_connect_port(handle, mMidiInput->mIndex, midiBuffer);
}
for (size_t p = 0; p < mAudioInputs.GetCount(); p++)
{
lilv_instance_connect_port(handle, mAudioInputs[p].mIndex, fInBuffer[p]);
}
for (size_t p = 0; p < mAudioOutputs.GetCount(); p++)
{
lilv_instance_connect_port(handle, mAudioOutputs[p].mIndex, fOutBuffer[p]);
}
for (size_t p = 0; p < mControlInputs.GetCount(); p++)
{
lilv_instance_connect_port(handle, mControlInputs[p].mIndex,
&mControlInputs[p].mControlBuffer);
}
for (size_t p = 0; p < mControlOutputs.GetCount(); p++)
{
lilv_instance_connect_port(handle, mControlOutputs[p].mIndex,
&mControlOutputs[p].mControlBuffer);
}
float latency = 0.0;
if (mLatencyPortIndex >= 0)
{
lilv_instance_connect_port(handle, mLatencyPortIndex, &latency);
}
lilv_instance_activate(handle);
// Actually perform the effect here
sampleCount originalLen = len;
sampleCount ls = lstart;
sampleCount rs = rstart;
sampleCount ols = ls;
sampleCount ors = rs;
bool noteOver = false;
sampleCount delayed = 0;
sampleCount delay = 0;
bool cleared = false;
while (len || delayed)
{
int block = mBlockSize;
if (len)
{
if (block > len)
{
block = len;
}
if (left && mAudioInputs.GetCount() > 0)
{
left->Get((samplePtr)fInBuffer[0], floatSample, ls, block);
}
if (right && mAudioInputs.GetCount() > 1)
{
right->Get((samplePtr)fInBuffer[1], floatSample, rs, block);
}
}
else if (delayed)
{
// At the end if we don't have enough left for a whole block
if (block > delayed)
{
block = delayed;
}
// Clear the input buffer so that we only pass zeros to the effect.
if (!cleared)
{
for (int i = 0; i < mBlockSize; i++)
{
fInBuffer[0][i] = 0.0;
}
if (right)
{
memcpy(fInBuffer[1], fOutBuffer[0], mBlockSize);
}
cleared = true;
}
}
lilv_instance_run(handle, block);
if (delayed == 0 && latency != 0)
{
delayed = delay = latency;
}
if (delay >= block)
{
delay -= block;
}
else if (delay > 0)
{
sampleCount oblock = block - delay;
if (left && mAudioOutputs.GetCount() > 0)
{
left->Set((samplePtr)(fOutBuffer[0] + delay), floatSample, ols, oblock);
}
if (right && mAudioOutputs.GetCount() > 1)
{
right->Set((samplePtr)(fOutBuffer[1] + delay), floatSample, ors, oblock);
}
ols += oblock;
ors += oblock;
delay = 0;
}
else
{
if (left && mAudioOutputs.GetCount() > 0)
{
left->Set((samplePtr)fOutBuffer[0], floatSample, ols, block);
}
if (right && mAudioOutputs.GetCount() > 1)
{
right->Set((samplePtr)fOutBuffer[1], floatSample, ors, block);
}
ols += block;
ors += block;
}
if (len)
{
len -= block;
noteOffTime -= block;
// Clear the event buffer and add the note off event if needed
if (mMidiInput)
{
lv2_event_buffer_reset(midiBuffer, 1,
(uint8_t *)midiBuffer +
sizeof(LV2_Event_Buffer));
if (!noteOver && noteOffTime < len && noteOffTime < block)
{
LV2_Event_Iterator iter;
lv2_event_begin(&iter, midiBuffer);
uint8_t noteOff[] = { 0x80, mNoteKey, 64 };
lv2_event_write(&iter, noteOffTime, 0, 1, 3, noteOff);
noteOver = true;
}
}
}
else if (delayed)
{
delayed -= block;
}
ls += block;
rs += block;
if (mAudioInputs.GetCount() > 1)
{
if (TrackGroupProgress(count, (ls-lstart)/(double)originalLen))
{
return false;
}
}
else
{
if (TrackProgress(count, (ls-lstart)/(double)originalLen))
{
return false;
}
}
}
lilv_instance_deactivate(handle);
lilv_instance_free(handle);
return true;
}
void LV2Effect::End()
{
unsigned long i;
if (fInBuffer)
{
for (i = 0; i < mAudioInputs.GetCount(); i++)
{
if (fInBuffer[i])
{
delete [] fInBuffer[i];
}
}
delete [] fInBuffer;
fInBuffer = NULL;
}
if (fOutBuffer)
{
for (i = 0; i < mAudioOutputs.GetCount(); i++)
{
if (fOutBuffer[i])
{
delete [] fOutBuffer[i];
}
}
delete [] fOutBuffer;
fOutBuffer = NULL;
}
}
// ============================================================================
// LV2Effect Implementation
// ============================================================================
bool LV2Effect::IsValid()
{
return mValid;
}
LV2PortArray & LV2Effect::GetControls()
{
return mControlInputs;
}
bool LV2Effect::IsSynth()
{
return (mMidiInput != 0);
}
bool LV2Effect::SetNote(sampleCount len,
unsigned char velocity, unsigned char key)
{
if (velocity == 0 || velocity > 127 || key > 127)
{
return false;
}
mNoteLength = len;
mNoteVelocity = velocity;
mNoteKey = key;
return true;
}
const LV2PortGroup & LV2Effect::GetRootGroup()
{
return mRootGroup;
}
wxString LV2Effect::GetString(const LilvNode *node)
{
return wxString::FromUTF8(lilv_node_as_string(node));
}
wxString LV2Effect::GetString(LilvNode *node, bool free)
{
wxString str = GetString(node);
if (free)
{
lilv_node_free(node);
}
return str;
}
// This should be moved to its own source file, it's in LadspaEffect.cpp
// as well
class LV2Slider:public wxSlider
{
public:
LV2Slider(wxWindow *parent, wxWindowID id,
int value, int minValue, int maxValue,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = wxSL_HORIZONTAL,
const wxValidator & validator = wxDefaultValidator,
const wxString & name = wxSliderNameStr)
: wxSlider(parent, id, value, minValue, maxValue,
pos, size, style, validator, name)
{
};
void OnSetFocus(wxFocusEvent & event)
{
wxScrolledWindow *p = (wxScrolledWindow *) GetParent();
wxRect r = GetRect();
wxRect rv = p->GetRect();
rv.y = 0;
event.Skip();
int y;
int yppu;
p->GetScrollPixelsPerUnit(NULL, &yppu);
if (r.y >= rv.y && r.GetBottom() <= rv.GetBottom())
{
return;
}
if (r.y < rv.y)
{
p->CalcUnscrolledPosition(0, r.y, NULL, &r.y);
y = r.y / yppu;
}
else
{
p->CalcUnscrolledPosition(0, r.y, NULL, &r.y);
y = (r.GetBottom() - rv.GetBottom() + yppu) / yppu;
}
p->Scroll(-1, y);
};
private:
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(LV2Slider, wxSlider)
EVT_SET_FOCUS(LV2Slider::OnSetFocus)
END_EVENT_TABLE()
class LV2TextCtrl:public wxTextCtrl
{
public:
LV2TextCtrl(wxWindow *parent, wxWindowID id,
const wxString & value = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0,
const wxValidator & validator = wxDefaultValidator,
const wxString & name = wxTextCtrlNameStr)
: wxTextCtrl(parent, id, value,
pos, size, style, validator, name)
{
};
void OnSetFocus(wxFocusEvent & event)
{
wxScrolledWindow *p = (wxScrolledWindow *) GetParent();
wxRect r = GetRect();
wxRect rv = p->GetRect();
rv.y = 0;
event.Skip();
int y;
int yppu;
p->GetScrollPixelsPerUnit(NULL, &yppu);
if (r.y >= rv.y && r.GetBottom() <= rv.GetBottom())
{
return;
}
if (r.y < rv.y)
{
p->CalcUnscrolledPosition(0, r.y, NULL, &r.y);
y = r.y / yppu;
}
else
{
p->CalcUnscrolledPosition(0, r.y, NULL, &r.y);
y = (r.GetBottom() - rv.GetBottom() + yppu) / yppu;
}
p->Scroll(-1, y);
};
private:
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(LV2TextCtrl, wxTextCtrl)
EVT_SET_FOCUS(LV2TextCtrl::OnSetFocus)
END_EVENT_TABLE()
static const int LADSPA_SECONDS_ID = 13101;
BEGIN_EVENT_TABLE(LV2EffectDialog, wxDialog)
EVT_BUTTON(wxID_OK, LV2EffectDialog::OnOK)
EVT_BUTTON(wxID_CANCEL, LV2EffectDialog::OnCancel)
EVT_BUTTON(ID_EFFECT_PREVIEW, LV2EffectDialog::OnPreview)
EVT_SLIDER(wxID_ANY, LV2EffectDialog::OnSlider)
EVT_TEXT(wxID_ANY, LV2EffectDialog::OnTextCtrl)
EVT_CHECKBOX(wxID_ANY, LV2EffectDialog::OnCheckBox)
EVT_CHOICE(wxID_ANY, LV2EffectDialog::OnChoiceCtrl)
END_EVENT_TABLE()
IMPLEMENT_CLASS(LV2EffectDialog, wxDialog)
LV2EffectDialog::LV2EffectDialog(LV2Effect *effect,
wxWindow *parent,
const LilvPlugin *data,
int sampleRate,
double length,
double WXUNUSED(noteLength),
unsigned char WXUNUSED(noteVelocity),
unsigned char WXUNUSED(noteKey))
: wxDialog(parent, wxID_ANY,
mEffect->GetString(lilv_plugin_get_name(data)),
wxDefaultPosition, wxSize(500, -1),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
mEffect(effect),
mData(data),
mControls(effect->GetControls()),
mSampleRate(sampleRate),
mLength(length)
{
#if defined(__WXMSW__) || (defined(__WXGTK__) && wxCHECK_VERSION(3, 0, 0))
// In some environments wxWindows calls OnTextCtrl during creation
// of the text control, and LV2EffectDialog::OnTextCtrl calls HandleText,
// which assumes all the fields have been initialized.
// This can give us a bad pointer crash, so manipulate inSlider to
// no-op HandleText during creation.
inSlider = true;
#else
inSlider = false;
#endif
inText = false;
inText = true;
// Allocate memory for the user parameter controls
int ctrlcnt = (int) mControls.GetCount();
mToggles = new wxCheckBox*[ctrlcnt];
mSliders = new wxSlider*[ctrlcnt];
mFields = new wxTextCtrl*[ctrlcnt];
mLabels = new wxStaticText*[ctrlcnt];
mEnums = new wxChoice*[ctrlcnt];
wxControl *item;
wxBoxSizer *vSizer = new wxBoxSizer(wxVERTICAL);
// Add information about the plugin
LilvNode *tmpValue = lilv_plugin_get_author_name(data);
if (tmpValue)
{
wxString author(_("Author: ") + mEffect->GetString(tmpValue));
item = new wxStaticText(this, wxID_ANY, author);
vSizer->Add(item, 0, wxALL, 5);
lilv_node_free(tmpValue);
}
wxScrolledWindow *w = new wxScrolledWindow(this,
wxID_ANY,
wxDefaultPosition,
wxDefaultSize,
wxVSCROLL | wxTAB_TRAVERSAL);
// Try to give the window a sensible default/minimum size
w->SetMinSize(wxSize(
wxMax(600, parent->GetSize().GetWidth() * 2/3),
parent->GetSize().GetHeight() / 2));
w->SetScrollRate(0, 20);
vSizer->Add(w, 1, wxEXPAND|wxALL, 5);
// Preview, OK, & Cancel buttons
vSizer->Add(CreateStdButtonSizer(this, ePreviewButton|eCancelButton|eOkButton), 0, wxEXPAND);
SetSizer(vSizer);
wxSizer *paramSizer =
new wxStaticBoxSizer(wxVERTICAL, w, _("Effect Settings"));
wxFlexGridSizer *gridSizer =
new wxFlexGridSizer(5, 0, 0);
gridSizer->AddGrowableCol(3);
// Now add the length control
if (mEffect->GetEffectFlags() & INSERT_EFFECT)
{
item = new wxStaticText(w, 0, _("Length (seconds)"));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
mSeconds = new wxTextCtrl(w, LADSPA_SECONDS_ID, Internat::ToDisplayString(length));
mSeconds->SetName(_("Length (seconds)"));
gridSizer->Add(mSeconds, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
ConnectFocus(mSeconds);
}
// The note controls if the plugin is a synth
if (mEffect->IsSynth())
{
// Note length control
item = new wxStaticText(w, wxID_ANY, _("Note length (seconds)"));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
mNoteSeconds = new wxTextCtrl(w, LADSPA_SECONDS_ID, Internat::ToDisplayString(length / 2));
mNoteSeconds->SetName(_("Note length (seconds)"));
gridSizer->Add(mNoteSeconds, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
ConnectFocus(mNoteSeconds);
// Note velocity control
item = new wxStaticText(w, wxID_ANY, _("Note velocity"));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
mNoteVelocity = new wxTextCtrl(w, LADSPA_SECONDS_ID, Internat::ToDisplayString(64));
mNoteVelocity->SetName(_("Note velocity"));
gridSizer->Add(mNoteVelocity, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
ConnectFocus(mNoteVelocity);
// Note key control
item = new wxStaticText(w, wxID_ANY, _("Note key"));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
mNoteKey = new wxTextCtrl(w, LADSPA_SECONDS_ID, Internat::ToDisplayString(64));
mNoteKey->SetName(_("Note key"));
gridSizer->Add(mNoteKey, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
ConnectFocus(mNoteKey);
}
paramSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5);
// Create user parameter controls
std::queue<const LV2PortGroup *> groups;
groups.push(&mEffect->GetRootGroup());
while (!groups.empty())
{
const LV2PortGroup* pg = groups.front();
groups.pop();
if (pg->GetName() != wxEmptyString)
{
wxSizer *groupSizer =
new wxStaticBoxSizer(wxVERTICAL, w, pg->GetName());
paramSizer->Add(groupSizer, 0, wxEXPAND | wxALL, 5);
gridSizer = new wxFlexGridSizer(5, 0, 0);
gridSizer->AddGrowableCol(3);
groupSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5);
}
const LV2PortGroupArray & subgroups = pg->GetSubGroups();
LV2PortGroupArray::const_iterator si;
for (si = subgroups.begin(); si != subgroups.end(); si++)
{
if ((*si)->GetParameters().GetCount() != 0)
{
groups.push(*si);
}
}
const wxArrayInt & params = pg->GetParameters();
for (size_t pi = 0; pi < params.GetCount(); pi++)
{
int p = params[pi];
if (p >= ctrlcnt)
{
continue;
}
LV2Port & port = mControls[p];
wxString labelText = port.mName;
item = new wxStaticText(w, 0, labelText + wxT(":"));
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
wxString fieldText;
if (port.mToggle)
{
mToggles[p] = new wxCheckBox(w, p, wxT(""));
mToggles[p]->SetName(labelText);
mToggles[p]->SetValue(port.mControlBuffer > 0);
gridSizer->Add(mToggles[p], 0, wxALL, 5);
ConnectFocus(mToggles[p]);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
}
else if (port.mEnumeration)
{
mEnums[p] = new wxChoice(w, p);
mEnums[p]->SetName(labelText);
mEnums[p]->Append(port.mScaleLabels);
int s;
for (s = (int) port.mScaleValues.GetCount() - 1; s >= 0; s--)
{
if (port.mControlBuffer >= port.mScaleValues[s])
{
break;
}
}
if (s < 0)
{
s = 0;
}
mEnums[p]->SetSelection(s);
gridSizer->Add(mEnums[p], 0, wxALL | wxEXPAND, 5);
ConnectFocus(mEnums[p]);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
gridSizer->Add(1, 1, 0);
}
else
{
if (port.mInteger)
{
fieldText.Printf(wxT("%d"), (int)(port.mControlBuffer + 0.5));
}
else
{
fieldText = Internat::ToDisplayString(port.mControlBuffer);
}
mFields[p] = new wxTextCtrl(w, p, fieldText);
mFields[p]->SetName(labelText);
gridSizer->Add(mFields[p], 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
ConnectFocus(mFields[p]);
wxString bound;
double lower = 0.0;
double upper = 0.0;
bool haslo = false;
bool hashi = false;
bool forceint = false;
wxString loLabel;
wxString hiLabel;
#if 0
ScalePointMap::const_iterator iter =
scalePoints.find(port.mIndex);
if (!wxNaN(port.mMin))
{
lower = port.mMin;
haslo = true;
if (iter != scalePoints.end())
{
std::map<float, wxString>::const_iterator iter2 =
iter->second.find(lower);
if (iter2 != iter->second.end())
{
loLabel = iter2->second;
}
}
}
if (!isnan(port.mMax))
{
upper = port.mMax;
hashi = true;
if (iter != scalePoints.end())
{
std::map<float, wxString>::const_iterator iter2 =
iter->second.find(upper);
if (iter2 != iter->second.end())
{
hiLabel = iter2->second;
}
}
}
#endif
if (port.mSampleRate)
{
lower *= mSampleRate * 1000;
upper *= mSampleRate;
forceint = true;
}
wxString str;
if (haslo)
{
str = loLabel;
if (str.IsEmpty())
{
if (port.mInteger || forceint)
{
str.Printf(wxT("%d"), (int)(lower + 0.5));
}
else
{
str = Internat::ToDisplayString(lower);
}
}
item = new wxStaticText(w, wxID_ANY, str);
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
}
else
{
gridSizer->Add(1, 1, 0);
}
mSliders[p] =
new wxSlider(w, p,
0, 0, 1000,
wxDefaultPosition,
wxSize(200, -1));
mSliders[p]->SetName(labelText);
gridSizer->Add(mSliders[p], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5);
ConnectFocus(mSliders[p]);
if (hashi)
{
str = hiLabel;
if (str.IsEmpty())
{
if (port.mInteger || forceint)
{
str.Printf(wxT("%d"), (int)(upper + 0.5));
}
else
{
str = Internat::ToDisplayString(upper);
}
}
item = new wxStaticText(w, wxID_ANY, str);
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5);
}
else
{
gridSizer->Add(1, 1, 0);
}
}
}
}
// Set all of the mSliders based on the value in the
// text mFields
inSlider = false; // Now we're ready for HandleText to actually do something.
HandleText();
w->SetSizer(paramSizer);
Layout();
Fit();
SetSizeHints(GetSize());
}
LV2EffectDialog::~LV2EffectDialog()
{
delete [] mToggles;
delete [] mSliders;
delete [] mFields;
delete [] mLabels;
delete [] mEnums;
}
void LV2EffectDialog::OnCheckBox(wxCommandEvent &event)
{
int p = event.GetId();
mControls[p].mControlBuffer = mToggles[p]->GetValue();
}
void LV2EffectDialog::OnSlider(wxCommandEvent &event)
{
int p = event.GetId();
// if we don't add the following three lines, changing
// the value of the slider will change the text, which
// will change the slider, and so on. This gets rid of
// the implicit loop.
if (inText)
return;
inSlider = true;
float val;
float lower = float(0.0);
float upper = float(10.0);
float range;
bool forceint = false;
if (isfinite(mControls[p].mMin))
{
lower = mControls[p].mMin;
}
if (isfinite(mControls[p].mMax))
{
upper = mControls[p].mMax;
}
if (mControls[p].mSampleRate)
{
lower *= mSampleRate;
upper *= mSampleRate;
forceint = true;
}
range = upper - lower;
val = (mSliders[p]->GetValue() / 1000.0) * range + lower;
// Force the value to an integer if requested
wxString str;
if (mControls[p].mInteger || forceint)
{
str.Printf(wxT("%d"), (int)(val + 0.5));
}
else
{
str = Internat::ToDisplayString(val);
}
mFields[p]->SetValue(str);
mControls[p].mControlBuffer = val;
inSlider = false;
}
void LV2EffectDialog::OnChoiceCtrl(wxCommandEvent & WXUNUSED(event))
{
// HandleText();
}
void LV2EffectDialog::OnTextCtrl(wxCommandEvent & WXUNUSED(event))
{
HandleText();
}
void LV2EffectDialog::HandleText()
{
// if we don't add the following three lines, changing
// the value of the slider will change the text, which
// will change the slider, and so on. This gets rid of
// the implicit loop.
if (inSlider)
{
return;
}
inText = true;
for (uint32_t p = 0; p < mControls.GetCount(); p++)
{
double dval;
float val;
float lower = float(0.0);
float upper = float(10.0);
float range;
if (mControls[p].mToggle)
{
continue;
}
if (mControls[p].mEnumeration)
{
continue;
}
dval = Internat::CompatibleToDouble(mFields[p]->GetValue());
val = dval;
if (!isnan(mControls[p].mMin))
{
lower = mControls[p].mMin;
}
if (!isnan(mControls[p].mMax))
{
upper = mControls[p].mMax;
}
if (mControls[p].mSampleRate)
{
lower *= mSampleRate;
upper *= mSampleRate;
}
range = upper - lower;
if (val < lower)
{
val = lower;
}
if (val > upper)
{
val = upper;
}
mControls[p].mControlBuffer = val;
mSliders[p]->SetValue((int)(((val-lower)/range) * 1000.0 + 0.5));
}
inText = false;
}
void LV2EffectDialog::OnOK(wxCommandEvent & WXUNUSED(event))
{
EndModal(TRUE);
}
void LV2EffectDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
{
EndModal(FALSE);
}
void LV2EffectDialog::OnPreview(wxCommandEvent & WXUNUSED(event))
{
mEffect->Preview();
}
void LV2EffectDialog::ConnectFocus(wxControl *c)
{
c->GetEventHandler()->Connect(wxEVT_SET_FOCUS,
wxFocusEventHandler(LV2EffectDialog::ControlSetFocus));
}
void LV2EffectDialog::DisconnectFocus(wxControl *c)
{
c->GetEventHandler()->Disconnect(wxEVT_SET_FOCUS,
wxFocusEventHandler(LV2EffectDialog::ControlSetFocus));
}
void LV2EffectDialog::ControlSetFocus(wxFocusEvent &event)
{
wxControl *c = (wxControl *) event.GetEventObject();
wxScrolledWindow *p = (wxScrolledWindow *) c->GetParent();
wxRect r = c->GetRect();
wxRect rv = p->GetRect();
rv.y = 0;
event.Skip();
int y;
int yppu;
p->GetScrollPixelsPerUnit(NULL, &yppu);
if (r.y >= rv.y && r.GetBottom() <= rv.GetBottom())
{
return;
}
if (r.y < rv.y)
{
p->CalcUnscrolledPosition(0, r.y, NULL, &r.y);
y = r.y / yppu;
}
else
{
p->CalcUnscrolledPosition(0, r.y, NULL, &r.y);
y = (r.GetBottom() - rv.GetBottom() + yppu) / yppu;
}
p->Scroll(-1, y);
};
double LV2EffectDialog::GetLength()
{
if (mEffect->GetEffectFlags() & INSERT_EFFECT)
{
mLength = Internat::CompatibleToDouble(mSeconds->GetValue());
}
return mLength;
}
double LV2EffectDialog::GetNoteLength()
{
if (mEffect->IsSynth())
{
return Internat::CompatibleToDouble(mNoteSeconds->GetValue());
}
return 0;
}
unsigned char LV2EffectDialog::GetNoteVelocity()
{
if (mEffect->IsSynth())
{
double velocity =
Internat::CompatibleToDouble(mNoteVelocity->GetValue());
if (velocity < 1)
{
return 1;
}
if (velocity > 127)
{
return 127;
}
return (unsigned char)velocity;
}
return 64;
}
unsigned char LV2EffectDialog::GetNoteKey()
{
if (mEffect->IsSynth())
{
double key =
Internat::CompatibleToDouble(mNoteKey->GetValue());
if (key < 1)
{
return 1;
}
if (key > 127)
{
return 127;
}
return (unsigned char)key;
}
return 64;
}
#endif