/********************************************************************** Audacity: A Digital Audio Editor LV2Effect.h Audacity(R) is copyright (c) 1999-2008 Audacity Team. License: GPL v2. See License.txt. **********************************************************************/ #include #include #include "../Audacity.h" #include #include #include #include #include #include #include #include #include #include #include #include #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& 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 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& 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 : \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 : \n" "PREFIX pg: \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 portGroups; std::vector 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::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 : \n" "PREFIX pg: \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::iterator iter = portGroups.find(parent); std::map::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::iterator iter; for (iter = portGroups.begin(); iter != portGroups.end(); ++iter) mRootGroup.AddSubGroup(iter->second); mPortGroupsRetrieved = true; } std::queue groups; groups.push(&mRootGroup); while (!groups.empty()) { const LV2PortGroup* g = groups.front(); groups.pop(); const std::vector& subs = g->GetSubGroups(); for (std::vector::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 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::const_iterator iter; for (iter = pg->GetSubGroups().begin(); iter != pg->GetSubGroups().end(); ++iter) { groups.push(&*iter); } const std::vector& 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::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::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