/********************************************************************** Audacity: A Digital Audio Editor LadspaEffect.cpp Dominic Mazzoni This class implements a Ladspa Plug-in effect. *******************************************************************//** \class LadspaEffect \brief An Effect that calls up a LADSPA plug in, i.e. many possible effects from this one class. *//****************************************************************//** \class LadspaEffectDialog \brief Dialog used with Effect *//*******************************************************************/ #include "../Audacity.h" #include "ladspa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../Effect.h" // Audacity Effect base class #include "LadspaEffect.h" // This class's header file #include "../Internat.h" LadspaEffect::LadspaEffect(const LADSPA_Descriptor *data, const std::set& categories) : mCategories(categories) { mData = data; pluginName = LAT1CTOWX(mData->Name); fInBuffer = NULL; fOutBuffer = NULL; inputs = 0; outputs = 0; numInputControls = 0; mLength = 0; unsigned long p; inputPorts = new unsigned long [mData->PortCount]; outputPorts = new unsigned long [mData->PortCount]; inputControls = new float [mData->PortCount]; outputControls = new float [mData->PortCount]; for(p=0; pPortCount; p++) { LADSPA_PortDescriptor d = mData->PortDescriptors[p]; if (LADSPA_IS_PORT_AUDIO(d)) { if (LADSPA_IS_PORT_INPUT(d)) { inputPorts[inputs] = p; inputs++; } else if (LADSPA_IS_PORT_OUTPUT(d)) { outputPorts[outputs] = p; outputs++; } } if (LADSPA_IS_PORT_CONTROL(d) && LADSPA_IS_PORT_INPUT(d)) { numInputControls++; float val = float(1.0); LADSPA_PortRangeHint hint = mData->PortRangeHints[p]; if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor) && val < hint.LowerBound) val = hint.LowerBound; if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor) && val > hint.UpperBound) val = hint.UpperBound; if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint.HintDescriptor)) val = hint.LowerBound; if (LADSPA_IS_HINT_DEFAULT_LOW(hint.HintDescriptor)) val = hint.LowerBound * 0.75f + hint.UpperBound * 0.25f; if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint.HintDescriptor)) val = hint.LowerBound * 0.5f + hint.UpperBound * 0.5f; if (LADSPA_IS_HINT_DEFAULT_HIGH(hint.HintDescriptor)) val = hint.LowerBound * 0.25f + hint.UpperBound * 0.75f; if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint.HintDescriptor)) val = hint.UpperBound; if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor)) val *= mProjectRate; if (LADSPA_IS_HINT_DEFAULT_0(hint.HintDescriptor)) val = 0.0f; if (LADSPA_IS_HINT_DEFAULT_1(hint.HintDescriptor)) val = 1.0f; if (LADSPA_IS_HINT_DEFAULT_100(hint.HintDescriptor)) val = 100.0f; if (LADSPA_IS_HINT_DEFAULT_440(hint.HintDescriptor)) val = 440.0f; inputControls[p] = val; } } int flags = PLUGIN_EFFECT; if (inputs == 0) flags |= INSERT_EFFECT; else if (outputs == 0) flags |= ANALYZE_EFFECT; else flags |= PROCESS_EFFECT; SetEffectFlags(flags); } LadspaEffect::~LadspaEffect() { delete[] inputPorts; delete[] outputPorts; delete[] inputControls; delete[] outputControls; } wxString LadspaEffect::GetEffectName() { if (numInputControls > 0) return pluginName + wxT("..."); else return pluginName; } std::set LadspaEffect::GetEffectCategories() { return mCategories; } wxString LadspaEffect::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 LadspaEffect::GetEffectAction() { return wxString::Format(_("Performing Effect: %s"), pluginName.c_str()); } bool LadspaEffect::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 LadspaEffect::PromptUser() { if (numInputControls > 0) { double length = mT1 > mT0 ? mT1 - mT0 : sDefaultGenerateLen; LadspaEffectDialog dlog(this, mParent, mData, inputControls, mainRate, length); dlog.CentreOnParent(); dlog.ShowModal(); if (!dlog.GetReturnCode()) return false; mLength = dlog.GetLength(); } return true; } bool LadspaEffect::Process() { this->CopyInputTracks(); // Set up mOutputTracks. bool bGoodResult = true; TrackListIterator iter(mOutputTracks); int count = 0; Track *left = iter.First(); Track *right; while(left) { sampleCount lstart = 0, rstart = 0; sampleCount len; GetSamples((WaveTrack *)left, &lstart, &len); right = NULL; if (left->GetLinked() && inputs>1) { right = iter.Next(); GetSamples((WaveTrack *)right, &rstart, &len); } if (inputs < 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->ReplaceProcessedTracks(bGoodResult); return bGoodResult; } bool LadspaEffect::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 *[inputs]; unsigned long i; for (i = 0; i < inputs; i++) fInBuffer[i] = new float[mBlockSize]; fOutBuffer = new float *[outputs]; for (i = 0; i < outputs; i++) fOutBuffer[i] = new float[mBlockSize]; } /* Instantiate the plugin */ unsigned long rate = (unsigned long)(left->GetRate() + 0.5); LADSPA_Handle handle = mData->instantiate(mData, rate); unsigned long p; for(p=0; pconnect_port(handle, inputPorts[p], fInBuffer[p]); } for(p=0; pconnect_port(handle, outputPorts[p], fOutBuffer[p]); } for(p=0; pPortCount; p++) { LADSPA_PortDescriptor d = mData->PortDescriptors[p]; if (LADSPA_IS_PORT_CONTROL(d)) { if (LADSPA_IS_PORT_INPUT(d)) { mData->connect_port(handle, p, &inputControls[p]); } else mData->connect_port(handle, p, &outputControls[p]); } } if (mData->activate) mData->activate(handle); // Actually perform the effect here sampleCount originalLen = len; sampleCount ls = lstart; sampleCount rs = rstart; while (len) { int block = mBlockSize; if (block > len) block = len; if (left && inputs > 0) { left->Get((samplePtr)fInBuffer[0], floatSample, ls, block); } if (right && inputs > 1) { right->Get((samplePtr)fInBuffer[1], floatSample, rs, block); } mData->run(handle, block); if (left && outputs > 0) { left->Set((samplePtr)fOutBuffer[0], floatSample, ls, block); } if (right && outputs > 1) { right->Set((samplePtr)fOutBuffer[1], floatSample, rs, block); } len -= block; ls += block; rs += block; if (inputs > 1) { if (TrackGroupProgress(count, (ls-lstart)/(double)originalLen)) return false; } else { if (TrackProgress(count, (ls-lstart)/(double)originalLen)) return false; } } if (mData->deactivate) mData->deactivate(handle); if (mData->cleanup) mData->cleanup(handle); return true; } void LadspaEffect::End() { unsigned long i; if (fInBuffer) { for (i = 0; i < inputs; i++) { if (fInBuffer[i]) { delete [] fInBuffer[i]; } } delete [] fInBuffer; fInBuffer = NULL; } if (fOutBuffer) { for (i = 0; i < outputs; i++) { if (fOutBuffer[i]) { delete [] fOutBuffer[i]; } } delete [] fOutBuffer; fOutBuffer = NULL; } } class Slider:public wxSlider { public: Slider(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(Slider, wxSlider) EVT_SET_FOCUS(Slider::OnSetFocus) END_EVENT_TABLE() class TextCtrl:public wxTextCtrl { public: TextCtrl(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(TextCtrl, wxTextCtrl) EVT_SET_FOCUS(TextCtrl::OnSetFocus) END_EVENT_TABLE() const int LADSPA_SECONDS_ID = 13101; BEGIN_EVENT_TABLE(LadspaEffectDialog, wxDialog) EVT_BUTTON(wxID_OK, LadspaEffectDialog::OnOK) EVT_BUTTON(wxID_CANCEL, LadspaEffectDialog::OnCancel) EVT_BUTTON(ID_EFFECT_PREVIEW, LadspaEffectDialog::OnPreview) EVT_SLIDER(wxID_ANY, LadspaEffectDialog::OnSlider) EVT_TEXT(wxID_ANY, LadspaEffectDialog::OnTextCtrl) EVT_CHECKBOX(wxID_ANY, LadspaEffectDialog::OnCheckBox) END_EVENT_TABLE() IMPLEMENT_CLASS(LadspaEffectDialog, wxDialog) LadspaEffectDialog::LadspaEffectDialog(LadspaEffect *eff, wxWindow * parent, const LADSPA_Descriptor *data, float *inputControls, int sampleRate, double length) :wxDialog(parent, -1, LAT1CTOWX(data->Name), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), effect(eff) { mLength = length; numParams = 0; this->mData = data; this->inputControls = inputControls; this->sampleRate = sampleRate; #ifdef __WXMSW__ // On Windows, for some reason, wxWidgets calls OnTextCtrl during creation // of the text control, and LadspaEffectDialog::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; toggles = new wxCheckBox*[mData->PortCount]; sliders = new wxSlider*[mData->PortCount]; fields = new wxTextCtrl*[mData->PortCount]; labels = new wxStaticText*[mData->PortCount]; ports = new unsigned long [mData->PortCount]; unsigned long p; for(p=0; pPortCount; p++) { LADSPA_PortDescriptor d = mData->PortDescriptors[p]; if (LADSPA_IS_PORT_CONTROL(d) && LADSPA_IS_PORT_INPUT(d)) { ports[numParams] = p; numParams++; } } wxControl *item; wxBoxSizer *vSizer = new wxBoxSizer(wxVERTICAL); if (mData->Maker && mData->Maker[0] && LAT1CTOWX(mData->Maker) != wxString(_("None"))) { item = new wxStaticText(this, 0, wxString(_("Author: "))+LAT1CTOWX(mData->Maker)); vSizer->Add(item, 0, wxALL, 5); } if (mData->Copyright && mData->Copyright[0] && LAT1CTOWX(mData->Copyright) != wxString(_("None"))) { item = new wxStaticText(this, 0, LAT1CTOWX(mData->Copyright)); vSizer->Add(item, 0, wxALL, 5); } 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); for (p = 0; p < numParams; p++) { wxString labelText = LAT1CTOWX(mData->PortNames[ports[p]]); item = new wxStaticText(w, 0, labelText + wxT(":")); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); wxString fieldText; LADSPA_PortRangeHint hint = mData->PortRangeHints[ports[p]]; if (LADSPA_IS_HINT_TOGGLED(hint.HintDescriptor)) { toggles[p] = new wxCheckBox(w, p, wxT("")); toggles[p]->SetName(labelText); toggles[p]->SetValue(inputControls[ports[p]] > 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 (LADSPA_IS_HINT_INTEGER(hint.HintDescriptor)) fieldText.Printf(wxT("%d"), (int)(inputControls[ports[p]] + 0.5)); else fieldText = Internat::ToDisplayString(inputControls[ports[p]]); 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; if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor)) { lower = hint.LowerBound; haslo = true; } if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor)) { upper = hint.UpperBound; hashi = true; } if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor)) { lower *= sampleRate * 1000; upper *= sampleRate; forceint = true; } wxString str; if (haslo) { if (LADSPA_IS_HINT_INTEGER(hint.HintDescriptor) || forceint) str.Printf(wxT("%d"), (int)(lower + 0.5)); else str = Internat::ToDisplayString(lower); item = 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) { if (LADSPA_IS_HINT_INTEGER(hint.HintDescriptor) || 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); } } } // 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); } // 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(); paramSizer->Add(gridSizer, 1, wxEXPAND | wxALL, 5); w->SetSizer(paramSizer); Layout(); Fit(); SetSizeHints(GetSize()); } LadspaEffectDialog::~LadspaEffectDialog() { delete[]toggles; delete[]sliders; delete[]fields; delete[]labels; delete[]ports; } void LadspaEffectDialog::OnCheckBox(wxCommandEvent &event) { int p = event.GetId(); inputControls[ports[p]] = toggles[p]->GetValue(); } void LadspaEffectDialog::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; LADSPA_PortRangeHint hint = mData->PortRangeHints[ports[p]]; if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor)) lower = hint.LowerBound; if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor)) upper = hint.UpperBound; if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor)) { lower *= sampleRate; upper *= sampleRate; forceint = true; } range = upper - lower; val = (sliders[p]->GetValue() / 1000.0) * range + lower; wxString str; if (LADSPA_IS_HINT_INTEGER(hint.HintDescriptor) || forceint) str.Printf(wxT("%d"), (int)(val + 0.5)); else str = Internat::ToDisplayString(val); fields[p]->SetValue(str); inputControls[ports[p]] = val; inSlider = false; } void LadspaEffectDialog::OnTextCtrl(wxCommandEvent & WXUNUSED(event)) { HandleText(); } void LadspaEffectDialog::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 (unsigned long p = 0; p < numParams; p++) { double dval; float val; float lower = float(0.0); float upper = float(10.0); float range; LADSPA_PortRangeHint hint = mData->PortRangeHints[ports[p]]; if (LADSPA_IS_HINT_TOGGLED(hint.HintDescriptor)) { continue; } dval = Internat::CompatibleToDouble(fields[p]->GetValue()); val = dval; if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor)) lower = hint.LowerBound; if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor)) upper = hint.UpperBound; if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor)) { lower *= sampleRate; upper *= sampleRate; } range = upper - lower; if (val < lower) val = lower; if (val > upper) val = upper; inputControls[ports[p]] = val; sliders[p]->SetValue((int)(((val-lower)/range) * 1000.0 + 0.5)); } inText = false; } void LadspaEffectDialog::OnOK(wxCommandEvent & WXUNUSED(event)) { EndModal(TRUE); } void LadspaEffectDialog::OnCancel(wxCommandEvent & WXUNUSED(event)) { EndModal(FALSE); } void LadspaEffectDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) { effect->Preview(); } void LadspaEffectDialog::ConnectFocus(wxControl *c) { c->GetEventHandler()->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(LadspaEffectDialog::ControlSetFocus)); } void LadspaEffectDialog::DisconnectFocus(wxControl *c) { c->GetEventHandler()->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(LadspaEffectDialog::ControlSetFocus)); } void LadspaEffectDialog::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 LadspaEffectDialog::GetLength() { if (effect->GetEffectFlags() & INSERT_EFFECT) { mLength = Internat::CompatibleToDouble(mSeconds->GetValue()); } return mLength; } // 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