1620 lines
40 KiB
C++
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::GetPath()
|
|
{
|
|
return GetString(lilv_plugin_get_uri(mData));
|
|
}
|
|
|
|
wxString LV2Effect::GetSymbol()
|
|
{
|
|
return pluginName;
|
|
}
|
|
|
|
wxString LV2Effect::GetName()
|
|
{
|
|
return GetSymbol();
|
|
}
|
|
|
|
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
|