mirror of
https://github.com/cookiengineer/audacity
synced 2025-07-26 09:28:07 +02:00
1240 lines
37 KiB
C++
1240 lines
37 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 <cmath>
|
|
#include <queue>
|
|
|
|
#include "../Audacity.h"
|
|
|
|
#include <wx/wxprec.h>
|
|
#include <wx/button.h>
|
|
#include <wx/checkbox.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>
|
|
|
|
#if defined(USE_SLV2)
|
|
|
|
#include "../Effect.h"
|
|
#include "LoadLV2.h"
|
|
#include "LV2Effect.h"
|
|
#include "LV2PortGroup.h"
|
|
#include "../Internat.h"
|
|
#include "lv2_event_helpers.h"
|
|
|
|
LV2Effect::LV2Effect(SLV2Plugin data,
|
|
const std::set<wxString>& categories)
|
|
: mValid(true),
|
|
mCategories(categories),
|
|
mMidiInput(0),
|
|
mScalePointsRetrieved(false),
|
|
mPortGroupsRetrieved(false) {
|
|
|
|
// We don't support any features at all, so if the plugin requires
|
|
// any we skip it.
|
|
SLV2Values req = slv2_plugin_get_required_features(data);
|
|
size_t nFeatures = slv2_values_size(req);
|
|
slv2_values_free(req);
|
|
if (nFeatures > 0) {
|
|
mValid = false;
|
|
return;
|
|
}
|
|
|
|
mData = data;
|
|
pluginName =
|
|
wxString::FromUTF8(slv2_value_as_string(slv2_plugin_get_name(mData)));
|
|
|
|
fInBuffer = NULL;
|
|
fOutBuffer = NULL;
|
|
|
|
mLength = 0;
|
|
|
|
uint32_t p;
|
|
|
|
// Allocate buffers for the port indices and the default control values
|
|
uint32_t numPorts = slv2_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)
|
|
slv2_plugin_get_port_ranges_float(mData, minimumValues,
|
|
maximumValues, defaultValues);
|
|
|
|
// Get info about all ports
|
|
for(p = 0; p < numPorts; p++) {
|
|
SLV2Port port = slv2_plugin_get_port_by_index(mData, p);
|
|
LV2Port internalPort;
|
|
internalPort.mIndex = p;
|
|
|
|
// Get the port name
|
|
SLV2Value tmpName = slv2_port_get_name(data, port);
|
|
internalPort.mName = LAT1CTOWX(slv2_value_as_string(tmpName));
|
|
slv2_value_free(tmpName);
|
|
|
|
// Get the port type
|
|
if (slv2_port_is_a(mData, port, gAudioPortClass)) {
|
|
if (slv2_port_is_a(mData, port, gInputPortClass))
|
|
mAudioInputs.push_back(internalPort);
|
|
else if (slv2_port_is_a(mData, port, gOutputPortClass))
|
|
mAudioOutputs.push_back(internalPort);
|
|
}
|
|
|
|
else if (slv2_port_is_a(mData, port, gControlPortClass) &&
|
|
slv2_port_is_a(mData, port, gInputPortClass)) {
|
|
internalPort.mControlBuffer = float(1.0);
|
|
internalPort.mMin = minimumValues[p];
|
|
internalPort.mMax = maximumValues[p];
|
|
internalPort.mDefault = defaultValues[p];
|
|
if (std::isfinite(defaultValues[p]))
|
|
internalPort.mControlBuffer = defaultValues[p];
|
|
else if (std::isfinite(minimumValues[p]))
|
|
internalPort.mControlBuffer = minimumValues[p];
|
|
else if (std::isfinite(maximumValues[p]))
|
|
internalPort.mControlBuffer = maximumValues[p];
|
|
if (slv2_port_has_property(data, port, gPortToggled))
|
|
internalPort.mToggle = true;
|
|
if (slv2_port_has_property(data, port, gPortIsInteger))
|
|
internalPort.mInteger = true;
|
|
if (slv2_port_has_property(data, port, gPortIsSampleRate))
|
|
internalPort.mSampleRate = true;
|
|
|
|
mControlInputs.push_back(internalPort);
|
|
}
|
|
|
|
else if (slv2_port_is_a(mData, port, gMidiPortClass) &&
|
|
slv2_port_is_a(mData, port, gInputPortClass)) {
|
|
// If there are more than one MIDI input ports, 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.size() > 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.size() == 0)
|
|
flags |= INSERT_EFFECT;
|
|
else if (mAudioOutputs.size() == 0)
|
|
flags |= ANALYZE_EFFECT;
|
|
else
|
|
flags |= PROCESS_EFFECT;
|
|
|
|
SetEffectFlags(flags);
|
|
}
|
|
|
|
LV2Effect::~LV2Effect()
|
|
{
|
|
if (mMidiInput)
|
|
delete mMidiInput;
|
|
}
|
|
|
|
wxString LV2Effect::GetEffectName()
|
|
{
|
|
if (mControlInputs.size() > 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.size() > 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;
|
|
|
|
mLength = dlog.GetLength();
|
|
mNoteLength = dlog.GetNoteLength();
|
|
mNoteVelocity = dlog.GetNoteVelocity();
|
|
mNoteKey = dlog.GetNoteKey();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LV2Effect::Process()
|
|
{
|
|
this->CopyInputWaveTracks(); // Set up mOutputWaveTracks.
|
|
bool bGoodResult = true;
|
|
|
|
TrackListIterator iter(mOutputWaveTracks);
|
|
int count = 0;
|
|
Track *left = iter.First();
|
|
Track *right;
|
|
while(left) {
|
|
sampleCount lstart, rstart;
|
|
sampleCount len;
|
|
GetSamples((WaveTrack *)left, &lstart, &len);
|
|
|
|
right = NULL;
|
|
if (left->GetLinked() && mAudioInputs.size() > 1) {
|
|
right = iter.Next();
|
|
GetSamples((WaveTrack *)right, &rstart, &len);
|
|
}
|
|
|
|
if (mAudioInputs.size() < 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++;
|
|
}
|
|
|
|
this->ReplaceProcessedWaveTracks(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.size()];
|
|
unsigned long i;
|
|
for (i = 0; i < mAudioInputs.size(); i++)
|
|
fInBuffer[i] = new float[mBlockSize];
|
|
fOutBuffer = new float *[mAudioOutputs.size()];
|
|
for (i = 0; i < mAudioOutputs.size(); i++)
|
|
fOutBuffer[i] = new float[mBlockSize];
|
|
}
|
|
|
|
/* Instantiate the plugin */
|
|
SLV2Instance handle = slv2_plugin_instantiate(mData, left->GetRate(),
|
|
gLV2Features);
|
|
|
|
/* Write the Note On to the MIDI event buffer and connect it */
|
|
LV2_Event_Buffer* midiBuffer;
|
|
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);
|
|
}
|
|
slv2_instance_connect_port(handle, mMidiInput->mIndex, midiBuffer);
|
|
}
|
|
|
|
unsigned long p;
|
|
for(p = 0; p < mAudioInputs.size(); p++) {
|
|
slv2_instance_connect_port(handle, mAudioInputs[p].mIndex, fInBuffer[p]);
|
|
}
|
|
for(p = 0; p < mAudioOutputs.size(); p++) {
|
|
slv2_instance_connect_port(handle, mAudioOutputs[p].mIndex, fOutBuffer[p]);
|
|
}
|
|
for (p = 0; p < mControlInputs.size(); p++) {
|
|
slv2_instance_connect_port(handle, mControlInputs[p].mIndex,
|
|
&mControlInputs[p].mControlBuffer);
|
|
}
|
|
for (p = 0; p < mControlOutputs.size(); p++) {
|
|
slv2_instance_connect_port(handle, mControlOutputs[p].mIndex,
|
|
&mControlOutputs[p].mControlBuffer);
|
|
}
|
|
|
|
slv2_instance_activate(handle);
|
|
|
|
// Actually perform the effect here
|
|
|
|
sampleCount originalLen = len;
|
|
sampleCount ls = lstart;
|
|
sampleCount rs = rstart;
|
|
bool noteOver = false;
|
|
while (len) {
|
|
int block = mBlockSize;
|
|
if (block > len)
|
|
block = len;
|
|
|
|
if (left && mAudioInputs.size() > 0) {
|
|
left->Get((samplePtr)fInBuffer[0], floatSample, ls, block);
|
|
}
|
|
if (right && mAudioInputs.size() > 1) {
|
|
right->Get((samplePtr)fInBuffer[1], floatSample, rs, block);
|
|
}
|
|
|
|
slv2_instance_run(handle, block);
|
|
|
|
if (left && mAudioOutputs.size() > 0) {
|
|
left->Set((samplePtr)fOutBuffer[0], floatSample, ls, block);
|
|
}
|
|
|
|
if (right && mAudioOutputs.size() > 1) {
|
|
right->Set((samplePtr)fOutBuffer[1], floatSample, rs, block);
|
|
}
|
|
|
|
len -= block;
|
|
noteOffTime -= block;
|
|
ls += block;
|
|
rs += 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;
|
|
}
|
|
}
|
|
|
|
if (mAudioInputs.size() > 1) {
|
|
if (TrackGroupProgress(count, (ls-lstart)/(double)originalLen))
|
|
return false;
|
|
}
|
|
else {
|
|
if (TrackProgress(count, (ls-lstart)/(double)originalLen))
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
slv2_instance_deactivate(handle);
|
|
slv2_instance_free(handle);
|
|
|
|
return true;
|
|
}
|
|
|
|
void LV2Effect::End()
|
|
{
|
|
unsigned long i;
|
|
|
|
if (fInBuffer) {
|
|
for (i = 0; i < mAudioInputs.size(); i++) {
|
|
if (fInBuffer[i]) {
|
|
delete [] fInBuffer[i];
|
|
}
|
|
}
|
|
delete [] fInBuffer;
|
|
fInBuffer = NULL;
|
|
}
|
|
|
|
if (fOutBuffer) {
|
|
for (i = 0; i < mAudioOutputs.size(); i++) {
|
|
if (fOutBuffer[i]) {
|
|
delete [] fOutBuffer[i];
|
|
}
|
|
}
|
|
delete [] fOutBuffer;
|
|
fOutBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
bool LV2Effect::IsValid() {
|
|
return mValid;
|
|
}
|
|
|
|
|
|
std::vector<LV2Port>& 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 ScalePointMap& LV2Effect::GetScalePoints() {
|
|
|
|
if (!mScalePointsRetrieved) {
|
|
|
|
char scalePointQuery[] =
|
|
"PREFIX : <http://lv2plug.in/ns/lv2core#>\n"
|
|
"SELECT ?index, ?value, ?label WHERE {\n"
|
|
"<> :port ?port.\n"
|
|
"?port a :ControlPort.\n"
|
|
"?port a :InputPort.\n"
|
|
"?port :index ?index.\n"
|
|
"?port :scalePoint ?point.\n"
|
|
"?point rdf:value ?value.\n"
|
|
"?point rdfs:label ?label.\n"
|
|
"}";
|
|
|
|
SLV2Values portIndices = slv2_plugin_query_variable(mData,
|
|
scalePointQuery, 0);
|
|
SLV2Values pointValues = slv2_plugin_query_variable(mData,
|
|
scalePointQuery, 1);
|
|
SLV2Values pointLabels = slv2_plugin_query_variable(mData,
|
|
scalePointQuery, 2);
|
|
|
|
size_t nScalePoints = slv2_values_size(portIndices);
|
|
for (size_t i = 0; i < nScalePoints; ++i) {
|
|
uint32_t idx = slv2_value_as_int(slv2_values_get_at(portIndices, i));
|
|
float value = slv2_value_as_float(slv2_values_get_at(pointValues, i));
|
|
wxString label = wxString::FromUTF8(slv2_value_as_string(slv2_values_get_at(pointLabels, i)));
|
|
mScalePoints[idx][value] = label;
|
|
}
|
|
slv2_values_free(portIndices);
|
|
slv2_values_free(pointValues);
|
|
slv2_values_free(pointLabels);
|
|
|
|
mScalePointsRetrieved = true;
|
|
}
|
|
|
|
return mScalePoints;
|
|
}
|
|
|
|
|
|
const LV2PortGroup& LV2Effect::GetPortGroups() {
|
|
|
|
if (!mPortGroupsRetrieved) {
|
|
|
|
// Find all port groups with ports in them.
|
|
char portGroupQuery[] =
|
|
"PREFIX : <http://lv2plug.in/ns/lv2core#>\n"
|
|
"PREFIX pg: <http://ll-plugins.nongnu.org/lv2/ext/portgroups#>\n"
|
|
"SELECT ?index, ?uri, ?label WHERE {\n"
|
|
"<> :port ?port.\n"
|
|
"?port :index ?index.\n"
|
|
"?port pg:membership ?ms.\n"
|
|
"?ms pg:group ?uri.\n"
|
|
"?uri rdfs:label ?label.\n"
|
|
"}";
|
|
|
|
SLV2Values portIndices = slv2_plugin_query_variable(mData,
|
|
portGroupQuery, 0);
|
|
SLV2Values groupUris = slv2_plugin_query_variable(mData,
|
|
portGroupQuery, 1);
|
|
SLV2Values groupLabels = slv2_plugin_query_variable(mData,
|
|
portGroupQuery, 2);
|
|
|
|
std::map<wxString, LV2PortGroup> portGroups;
|
|
std::vector<bool> inGroup(mControlInputs.size(), false);
|
|
size_t nMemberships = slv2_values_size(portIndices);
|
|
for (size_t i = 0; i < nMemberships; ++i) {
|
|
uint32_t idx = slv2_value_as_int(slv2_values_get_at(portIndices, i));
|
|
uint32_t p;
|
|
for (p = 0; p < mControlInputs.size(); ++p) {
|
|
if (mControlInputs[p].mIndex == idx)
|
|
break;
|
|
}
|
|
if (p == mControlInputs.size())
|
|
continue;
|
|
wxString uri = wxString::FromUTF8(slv2_value_as_string(slv2_values_get_at(groupUris, i)));
|
|
wxString label = wxString::FromUTF8(slv2_value_as_string(slv2_values_get_at(groupLabels, i)));
|
|
std::map<wxString, LV2PortGroup>::iterator iter =
|
|
portGroups.find(uri);
|
|
if (iter == portGroups.end())
|
|
portGroups[uri] = LV2PortGroup(label);
|
|
portGroups[uri].AddParameter(p);
|
|
inGroup[p] = true;
|
|
}
|
|
slv2_values_free(portIndices);
|
|
slv2_values_free(groupUris);
|
|
slv2_values_free(groupLabels);
|
|
|
|
// Add all ports that aren't in any port groups to the root group.
|
|
for (uint32_t p = 0; p < mControlInputs.size(); ++p) {
|
|
if (!inGroup[p])
|
|
mRootGroup.AddParameter(p);
|
|
}
|
|
|
|
// Find all subgroup relationships.
|
|
char subGroupQuery[] =
|
|
"PREFIX : <http://lv2plug.in/ns/lv2core#>\n"
|
|
"PREFIX pg: <http://ll-plugins.nongnu.org/lv2/ext/portgroups#>\n"
|
|
"SELECT ?sub, ?parent WHERE {\n"
|
|
"?sub pg:subgroupOf ?parent.\n"
|
|
"}";
|
|
|
|
SLV2Values subs = slv2_plugin_query_variable(mData, subGroupQuery, 0);
|
|
SLV2Values parents = slv2_plugin_query_variable(mData, subGroupQuery, 1);
|
|
size_t nSubgroups = slv2_values_size(subs);
|
|
for (size_t i = 0; i < nSubgroups; ++i) {
|
|
wxString parent =
|
|
wxString::FromUTF8(slv2_value_as_uri(slv2_values_get_at(parents, i)));
|
|
wxString sub =
|
|
wxString::FromUTF8(slv2_value_as_uri(slv2_values_get_at(subs, i)));
|
|
std::map<wxString, LV2PortGroup>::iterator iter =
|
|
portGroups.find(parent);
|
|
std::map<wxString, LV2PortGroup>::iterator iter2 =
|
|
portGroups.find(sub);
|
|
if (iter != portGroups.end() && iter2 != portGroups.end()) {
|
|
iter->second.AddSubGroup(iter2->second);
|
|
}
|
|
}
|
|
slv2_values_free(subs);
|
|
slv2_values_free(parents);
|
|
|
|
// Make all groups subgroups of the root group.
|
|
std::map<wxString, LV2PortGroup>::iterator iter;
|
|
for (iter = portGroups.begin(); iter != portGroups.end(); ++iter)
|
|
mRootGroup.AddSubGroup(iter->second);
|
|
|
|
mPortGroupsRetrieved = true;
|
|
}
|
|
|
|
std::queue<const LV2PortGroup*> groups;
|
|
groups.push(&mRootGroup);
|
|
while (!groups.empty()) {
|
|
const LV2PortGroup* g = groups.front();
|
|
groups.pop();
|
|
const std::vector<LV2PortGroup>& subs = g->GetSubGroups();
|
|
for (std::vector<LV2PortGroup>::const_iterator iter = subs.begin();
|
|
iter != subs.end(); ++iter)
|
|
groups.push(&*iter);
|
|
}
|
|
|
|
return mRootGroup;
|
|
}
|
|
|
|
|
|
// 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);
|
|
};
|
|
|
|
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);
|
|
};
|
|
|
|
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)
|
|
END_EVENT_TABLE()
|
|
|
|
IMPLEMENT_CLASS(LV2EffectDialog, wxDialog)
|
|
|
|
LV2EffectDialog::LV2EffectDialog(LV2Effect *eff,
|
|
wxWindow * parent,
|
|
SLV2Plugin data,
|
|
int sampleRate,
|
|
double length,
|
|
double noteLength,
|
|
unsigned char noteVelocity,
|
|
unsigned char noteKey)
|
|
:wxDialog(parent, -1,
|
|
LAT1CTOWX(slv2_value_as_string(slv2_plugin_get_name(data))),
|
|
wxDefaultPosition, wxDefaultSize,
|
|
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
|
effect(eff),
|
|
mControls(eff->GetControls())
|
|
{
|
|
mLength = length;
|
|
this->mData = data;
|
|
this->sampleRate = sampleRate;
|
|
#ifdef __WXMSW__
|
|
// On Windows, for some reason, 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;
|
|
|
|
// Allocate memory for the user parameter controls
|
|
toggles = new wxCheckBox*[mControls.size()];
|
|
sliders = new wxSlider*[mControls.size()];
|
|
fields = new wxTextCtrl*[mControls.size()];
|
|
labels = new wxStaticText*[mControls.size()];
|
|
|
|
wxControl *item;
|
|
|
|
wxBoxSizer *vSizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
// Add information about the plugin
|
|
SLV2Value tmpValue = slv2_plugin_get_author_name(data);
|
|
if (tmpValue) {
|
|
const char* author = slv2_value_as_string(tmpValue);
|
|
item = new wxStaticText(this, 0,
|
|
wxString(_("Author: "))+LAT1CTOWX(author));
|
|
vSizer->Add(item, 0, wxALL, 5);
|
|
slv2_value_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);
|
|
|
|
const LV2PortGroup& rootGroup = eff->GetPortGroups();
|
|
const ScalePointMap& scalePoints = eff->GetScalePoints();
|
|
|
|
// Now add the length control
|
|
if (effect->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 (effect->IsSynth()) {
|
|
|
|
// Note length control
|
|
item = new wxStaticText(w, 0, _("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, 0, _("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, 0, _("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(&rootGroup);
|
|
|
|
while (!groups.empty()) {
|
|
|
|
const LV2PortGroup* pg = groups.front();
|
|
groups.pop();
|
|
|
|
if (pg->GetName() != wxT("")) {
|
|
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);
|
|
}
|
|
|
|
std::vector<LV2PortGroup>::const_iterator iter;
|
|
for (iter = pg->GetSubGroups().begin(); iter != pg->GetSubGroups().end();
|
|
++iter) {
|
|
groups.push(&*iter);
|
|
}
|
|
|
|
const std::vector<uint32_t>& params = pg->GetParameters();
|
|
for (uint32_t k = 0; k < params.size(); ++k) {
|
|
uint32_t p = params[k];
|
|
|
|
wxString labelText = mControls[p].mName;
|
|
item = new wxStaticText(w, 0, labelText + wxT(":"));
|
|
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
|
|
|
|
wxString fieldText;
|
|
|
|
if (mControls[p].mToggle) {
|
|
toggles[p] = new wxCheckBox(w, p, wxT(""));
|
|
toggles[p]->SetName(labelText);
|
|
toggles[p]->SetValue(mControls[p].mControlBuffer > 0);
|
|
gridSizer->Add(toggles[p], 0, wxALL, 5);
|
|
ConnectFocus(toggles[p]);
|
|
|
|
gridSizer->Add(1, 1, 0);
|
|
gridSizer->Add(1, 1, 0);
|
|
gridSizer->Add(1, 1, 0);
|
|
}
|
|
|
|
else {
|
|
if (mControls[p].mInteger)
|
|
fieldText.Printf(wxT("%d"), (int)(mControls[p].mControlBuffer + 0.5));
|
|
else
|
|
fieldText = Internat::ToDisplayString(mControls[p].mControlBuffer);
|
|
|
|
fields[p] = new wxTextCtrl(w, p, fieldText);
|
|
fields[p]->SetName(labelText);
|
|
gridSizer->Add(fields[p], 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
ConnectFocus(fields[p]);
|
|
|
|
wxString bound;
|
|
double lower = 0.0;
|
|
double upper = 0.0;
|
|
bool haslo = false;
|
|
bool hashi = false;
|
|
bool forceint = false;
|
|
wxString loLabel;
|
|
wxString hiLabel;
|
|
|
|
ScalePointMap::const_iterator iter =
|
|
scalePoints.find(mControls[p].mIndex);
|
|
|
|
if (!std::isnan(mControls[p].mMin)) {
|
|
lower = mControls[p].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 (!std::isnan(mControls[p].mMax)) {
|
|
upper = mControls[p].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;
|
|
}
|
|
}
|
|
|
|
if (mControls[p].mSampleRate) {
|
|
lower *= sampleRate * 1000;
|
|
upper *= sampleRate;
|
|
forceint = true;
|
|
}
|
|
|
|
wxString str;
|
|
if (haslo) {
|
|
str = loLabel;
|
|
if (str.IsEmpty()) {
|
|
if (mControls[p].mInteger || forceint)
|
|
str.Printf(wxT("%d"), (int)(lower + 0.5));
|
|
else
|
|
str = Internat::ToDisplayString(lower);
|
|
}
|
|
item = new wxStaticText(w, 0, str);
|
|
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5);
|
|
}
|
|
else {
|
|
gridSizer->Add(1, 1, 0);
|
|
}
|
|
|
|
sliders[p] =
|
|
new wxSlider(w, p,
|
|
0, 0, 1000,
|
|
wxDefaultPosition,
|
|
wxSize(200, -1));
|
|
sliders[p]->SetName(labelText);
|
|
gridSizer->Add(sliders[p], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5);
|
|
ConnectFocus(sliders[p]);
|
|
|
|
if (hashi) {
|
|
str = hiLabel;
|
|
if (str.IsEmpty()) {
|
|
if (mControls[p].mInteger || forceint)
|
|
str.Printf(wxT("%d"), (int)(upper + 0.5));
|
|
else
|
|
str = Internat::ToDisplayString(upper);
|
|
}
|
|
item = new wxStaticText(w, 0, str);
|
|
gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5);
|
|
}
|
|
else {
|
|
gridSizer->Add(1, 1, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set all of the sliders based on the value in the
|
|
// text fields
|
|
inSlider = false; // Now we're ready for HandleText to actually do something.
|
|
HandleText();
|
|
|
|
w->SetSizer(paramSizer);
|
|
|
|
Layout();
|
|
Fit();
|
|
SetSizeHints(GetSize());
|
|
}
|
|
|
|
LV2EffectDialog::~LV2EffectDialog()
|
|
{
|
|
delete[]sliders;
|
|
delete[]fields;
|
|
delete[]labels;
|
|
}
|
|
|
|
void LV2EffectDialog::OnCheckBox(wxCommandEvent &event)
|
|
{
|
|
int p = event.GetId();
|
|
|
|
mControls[p].mControlBuffer = toggles[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 (std::isfinite(mControls[p].mMin))
|
|
lower = mControls[p].mMin;
|
|
if (std::isfinite(mControls[p].mMax))
|
|
upper = mControls[p].mMax;
|
|
if (mControls[p].mSampleRate) {
|
|
lower *= sampleRate;
|
|
upper *= sampleRate;
|
|
forceint = true;
|
|
}
|
|
|
|
range = upper - lower;
|
|
|
|
val = (sliders[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);
|
|
|
|
fields[p]->SetValue(str);
|
|
|
|
mControls[p].mControlBuffer = val;
|
|
|
|
inSlider = false;
|
|
}
|
|
|
|
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.size(); p++) {
|
|
|
|
double dval;
|
|
float val;
|
|
float lower = float(0.0);
|
|
float upper = float(10.0);
|
|
float range;
|
|
|
|
if (mControls[p].mToggle)
|
|
continue;
|
|
|
|
dval = Internat::CompatibleToDouble(fields[p]->GetValue());
|
|
val = dval;
|
|
|
|
|
|
|
|
if (!std::isnan(mControls[p].mMin))
|
|
lower = mControls[p].mMin;
|
|
if (!std::isnan(mControls[p].mMax))
|
|
upper = mControls[p].mMax;
|
|
if (mControls[p].mSampleRate) {
|
|
lower *= sampleRate;
|
|
upper *= sampleRate;
|
|
}
|
|
range = upper - lower;
|
|
|
|
if (val < lower)
|
|
val = lower;
|
|
if (val > upper)
|
|
val = upper;
|
|
|
|
mControls[p].mControlBuffer = val;
|
|
|
|
sliders[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))
|
|
{
|
|
effect->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 (effect->GetEffectFlags() & INSERT_EFFECT) {
|
|
mLength = Internat::CompatibleToDouble(mSeconds->GetValue());
|
|
}
|
|
|
|
return mLength;
|
|
}
|
|
|
|
|
|
double LV2EffectDialog::GetNoteLength() {
|
|
if (effect->IsSynth()) {
|
|
return Internat::CompatibleToDouble(mNoteSeconds->GetValue());
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
unsigned char LV2EffectDialog::GetNoteVelocity() {
|
|
if (effect->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 (effect->IsSynth()) {
|
|
double key =
|
|
Internat::CompatibleToDouble(mNoteKey->GetValue());
|
|
if (key < 1)
|
|
return 1;
|
|
if (key > 127)
|
|
return 127;
|
|
return (unsigned char)key;
|
|
}
|
|
return 64;
|
|
}
|
|
|
|
#endif
|
|
|
|
// Indentation settings for Vim and Emacs and unique identifier for Arch, a
|
|
// version control system. Please do not modify past this point.
|
|
//
|
|
// Local Variables:
|
|
// c-basic-offset: 3
|
|
// indent-tabs-mode: nil
|
|
// End:
|
|
//
|
|
// vim: et sts=3 sw=3
|
|
// arch-tag: 7e4a0346-c3ec-45de-9f71-818c6e34a094
|
|
|