/********************************************************************** Audacity: A Digital Audio Editor VampEffect.cpp Chris Cannam, with heavy borrowing from LadspaEffect.cpp Vamp is an audio analysis and feature extraction plugin API. http://www.vamp-plugins.org/ **********************************************************************/ #include "VampEffect.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include VampEffect::VampEffect(Vamp::HostExt::PluginLoader::PluginKey key, int output, bool hasParameters, wxString name, wxString category) : mKey(key), mOutput(output), mHasParameters(hasParameters), mName(name), mRate(0), mCategory(category), mPlugin(NULL) { SetEffectFlags(PLUGIN_EFFECT | ANALYZE_EFFECT); } VampEffect::~VampEffect() { delete mPlugin; mPlugin = NULL; } wxString VampEffect::GetEffectName() { if (mHasParameters) { return mName + LAT1CTOWX("..."); } else { return mName; } } std::set VampEffect::GetEffectCategories() { std::set result; if (mCategory != wxT("")) result.insert(mCategory); return result; } wxString VampEffect::GetEffectIdentifier() { return LAT1CTOWX(mKey.c_str()); } wxString VampEffect::GetEffectAction() { return wxString::Format(_("Extracting features: %s"), GetEffectName().c_str()); } bool VampEffect::Init() { Vamp::HostExt::PluginLoader *loader = Vamp::HostExt::PluginLoader::getInstance(); delete mPlugin; mPlugin = 0; TrackListOfKindIterator iter(Track::Wave, mTracks); WaveTrack *left = (WaveTrack *)iter.First(); mRate = 0.0; while (left) { if (mRate == 0.0) mRate = left->GetRate(); if (left->GetLinked()) { WaveTrack *right = (WaveTrack *)iter.Next(); if (left->GetRate() != right->GetRate()) { wxMessageBox(_("Sorry, Vamp Plug-ins cannot be run on stereo tracks where the individual channels of the track do not match.")); return false; } } left = (WaveTrack *)iter.Next(); } if (mRate <= 0.0) mRate = mProjectRate; mPlugin = loader->loadPlugin (mKey, mRate, Vamp::HostExt::PluginLoader::ADAPT_ALL); if (!mPlugin) { wxMessageBox(_("Sorry, failed to load Vamp Plug-in.")); return false; } return true; } bool VampEffect::PromptUser() { if (!mPlugin) return false; if (!mHasParameters) return true; VampEffectDialog dlog(this, mParent, mPlugin); dlog.CentreOnParent(); dlog.ShowModal(); if (!dlog.GetReturnCode()) return false; return true; } bool VampEffect::Process() { if (!mPlugin) return false; TrackListOfKindIterator iter(Track::Wave, mTracks); int count = 0; WaveTrack *left = (WaveTrack *)iter.First(); bool multiple = false; int prevTrackChannels = 0; if (GetNumWaveGroups() > 1) { // if there is another track beyond this one and any linked one, // then we're processing more than one track. That means we // should use the originating track name in each new label // track's name, to make clear which is which multiple = true; } while (left) { sampleCount lstart, rstart = 0; sampleCount len; GetSamples(left, &lstart, &len); WaveTrack *right = NULL; int channels = 1; if (left->GetLinked()) { right = (WaveTrack *)iter.Next(); channels = 2; GetSamples(right, &rstart, &len); } size_t step = mPlugin->getPreferredStepSize(); size_t block = mPlugin->getPreferredBlockSize(); bool initialiseRequired = true; if (block == 0) { if (step != 0) block = step; else block = 1024; } if (step == 0) { step = block; } if (prevTrackChannels > 0) { // Plugin has already been initialised, so if the number of // channels remains the same, we only need to do a reset. // Otherwise we need to re-construct the whole plugin, // because a Vamp plugin can't be re-initialised. if (prevTrackChannels == channels) { mPlugin->reset(); initialiseRequired = false; } else { //!!! todo: retain parameters previously set Init(); } } if (initialiseRequired) { if (!mPlugin->initialise(channels, step, block)) { wxMessageBox(_("Sorry, Vamp Plug-in failed to initialize.")); return false; } } LabelTrack *ltrack = mFactory->NewLabelTrack(); if (!multiple) { ltrack->SetName(GetEffectName()); } else { ltrack->SetName(wxString::Format(wxT("%s: %s"), left->GetName().c_str(), GetEffectName().c_str())); } mTracks->Add(ltrack); float **data = new float*[channels]; for (int c = 0; c < channels; ++c) data[c] = new float[block]; sampleCount originalLen = len; sampleCount ls = lstart; sampleCount rs = rstart; while (len) { int request = block; if (request > len) request = len; if (left) left->Get((samplePtr)data[0], floatSample, ls, request); if (right) right->Get((samplePtr)data[1], floatSample, rs, request); if (request < (int)block) { for (int c = 0; c < channels; ++c) { for (int i = request; i < (int)block; ++i) { data[c][i] = 0.f; } } } Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime (ls, (int)(mRate + 0.5)); Vamp::Plugin::FeatureSet features = mPlugin->process(data, timestamp); AddFeatures(ltrack, features); if (len > (int)step) len -= step; else len = 0; ls += step; rs += step; if (channels > 1) { if (TrackGroupProgress(count, (ls - lstart) / double(originalLen))) return false; } else { if (TrackProgress(count, (ls - lstart) / double(originalLen))) return false; } } Vamp::Plugin::FeatureSet features = mPlugin->getRemainingFeatures(); AddFeatures(ltrack, features); prevTrackChannels = channels; left = (WaveTrack *)iter.Next(); } return true; } void VampEffect::AddFeatures(LabelTrack *ltrack, Vamp::Plugin::FeatureSet &features) { for (Vamp::Plugin::FeatureList::iterator fli = features[mOutput].begin(); fli != features[mOutput].end(); ++fli) { Vamp::RealTime ftime0 = fli->timestamp; double ltime0 = ftime0.sec + (double(ftime0.nsec) / 1000000000.0); Vamp::RealTime ftime1 = ftime0; if (fli->hasDuration) ftime1 = ftime0 + fli->duration; double ltime1 = ftime1.sec + (double(ftime1.nsec) / 1000000000.0); wxString label = LAT1CTOWX(fli->label.c_str()); if (label == wxString()) { if (fli->values.empty()) { label = wxString::Format(LAT1CTOWX("%.3f"), ltime0); } else { label = wxString::Format(LAT1CTOWX("%.3f"), *fli->values.begin()); } } ltrack->AddLabel(ltime0, ltime1, label); } } void VampEffect::End() { delete mPlugin; mPlugin = 0; } BEGIN_EVENT_TABLE(VampEffectDialog, wxDialog) EVT_BUTTON(wxID_OK, VampEffectDialog::OnOK) EVT_BUTTON(wxID_CANCEL, VampEffectDialog::OnCancel) EVT_SLIDER(wxID_ANY, VampEffectDialog::OnSlider) EVT_TEXT(wxID_ANY, VampEffectDialog::OnTextCtrl) EVT_CHECKBOX(wxID_ANY, VampEffectDialog::OnCheckBox) EVT_COMBOBOX(wxID_ANY, VampEffectDialog::OnComboBox) END_EVENT_TABLE() IMPLEMENT_CLASS(VampEffectDialog, wxDialog) VampEffectDialog::VampEffectDialog(VampEffect *effect, wxWindow *parent, Vamp::Plugin *plugin) : wxDialog(parent, -1, effect->GetEffectName(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), mEffect(effect), mPlugin(plugin) { Vamp::Plugin::ProgramList programs = plugin->getPrograms(); mParameters = plugin->getParameterDescriptors(); #ifdef __WXMSW__ // On Windows, for some reason, wxWidgets calls OnTextCtrl during creation // of the text control, and VampEffectDialog::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; int count = mParameters.size(); toggles = new wxCheckBox*[count]; sliders = new wxSlider*[count]; fields = new wxTextCtrl*[count]; labels = new wxStaticText*[count]; combos = new wxComboBox*[count]; wxControl *item; wxBoxSizer *vSizer = new wxBoxSizer(wxVERTICAL); item = new wxStaticText(this, 0, LAT1CTOWX(plugin->getName().c_str()) + wxString(_(" - Vamp audio analysis plugin"))); vSizer->Add(item, 0, wxALL, 5); item = new wxStaticText(this, 0, LAT1CTOWX(plugin->getDescription().c_str())); vSizer->Add(item, 0, wxALL, 5); item = new wxStaticText(this, 0, wxString(_("Author: ")) + LAT1CTOWX(plugin->getMaker().c_str())); vSizer->Add(item, 0, wxALL, 5); item = new wxStaticText(this, 0, LAT1CTOWX(plugin->getCopyright().c_str())); 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(400, parent->GetSize().GetWidth() / 2), parent->GetSize().GetHeight() / 2)); w->SetScrollRate(0, 20); vSizer->Add(w, 1, wxEXPAND|wxALL, 5); vSizer->Add(CreateStdButtonSizer(this, eCancelButton|eOkButton), 0, wxEXPAND); SetSizer(vSizer); wxSizer *paramSizer = new wxStaticBoxSizer(wxVERTICAL, w, _("Plugin Settings")); wxFlexGridSizer *gridSizer = new wxFlexGridSizer(5, 0, 0); gridSizer->AddGrowableCol(3); programCombo = 0; if (!programs.empty()) { wxArrayString choices; wxString currentProgram = wxString(mPlugin->getCurrentProgram().c_str(), wxConvISO8859_1); for (size_t i = 0; i < programs.size(); ++i) { wxString choice = wxString(programs[i].c_str(), wxConvISO8859_1); choices.Add(choice); } gridSizer->Add(new wxStaticText(w, 0, _("Program")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); programCombo = new wxComboBox(w, 9999, currentProgram, wxDefaultPosition, wxDefaultSize, choices, wxCB_READONLY); programCombo->SetName(_("Program")); gridSizer->Add(1, 1, 0); gridSizer->Add(1, 1, 0); gridSizer->Add(programCombo, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5); ConnectFocus(programCombo); gridSizer->Add(1, 1, 0); } for (int p = 0; p < count; p++) { wxString labelText = LAT1CTOWX(mParameters[p].name.c_str()); item = new wxStaticText(w, 0, labelText + wxT(":")); item->SetName(labelText); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString fieldText; float value = mPlugin->getParameter(mParameters[p].identifier); toggles[p] = 0; combos[p] = 0; sliders[p] = 0; fields[p] = 0; if (mParameters[p].isQuantized && mParameters[p].quantizeStep == 1.0 && mParameters[p].minValue == 0.0 && mParameters[p].maxValue == 1.0) { toggles[p] = new wxCheckBox(w, p, wxT("")); toggles[p]->SetName(labelText); toggles[p]->SetValue(value > 0.5); 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 (mParameters[p].isQuantized && mParameters[p].quantizeStep == 1.0 && !mParameters[p].valueNames.empty()) { wxArrayString choices; wxString selected; for (size_t i = 0; i < mParameters[p].valueNames.size(); ++i) { wxString choice = wxString (mParameters[p].valueNames[i].c_str(), wxConvISO8859_1); if (size_t(value - mParameters[p].minValue + 0.5) == i) { selected = choice; } choices.Add(choice); } combos[p] = new wxComboBox(w, p, selected, wxDefaultPosition, wxDefaultSize, choices, wxCB_READONLY); combos[p]->SetName(labelText); gridSizer->Add(1, 1, 0); gridSizer->Add(1, 1, 0); gridSizer->Add(combos[p], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5); ConnectFocus(combos[p]); gridSizer->Add(1, 1, 0); } else { fieldText = Internat::ToDisplayString(value); 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 str = Internat::ToDisplayString(mParameters[p].minValue); item = new wxStaticText(w, 0, str); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, 5); sliders[p] = new wxSlider(w, p, 0, 0, 1000, wxDefaultPosition, wxSize(100, -1)); sliders[p]->SetName(labelText); gridSizer->Add(sliders[p], 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, 5); ConnectFocus(sliders[p]); str = Internat::ToDisplayString(mParameters[p].maxValue); item = new wxStaticText(w, 0, str); gridSizer->Add(item, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5); } } // 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()); } VampEffectDialog::~VampEffectDialog() { delete[] sliders; delete[] fields; delete[] labels; delete[] toggles; delete[] combos; } void VampEffectDialog::OnCheckBox(wxCommandEvent &event) { int p = event.GetId(); mPlugin->setParameter(mParameters[p].identifier, toggles[p]->GetValue()); } void VampEffectDialog::OnComboBox(wxCommandEvent &event) { int p = event.GetId(); if (p == 9999) { // special value for programs Vamp::Plugin::ProgramList programs = mPlugin->getPrograms(); for (size_t i = 0; i < programs.size(); ++i) { if (wxString(programs[i].c_str(), wxConvISO8859_1) == programCombo->GetValue()) { mPlugin->selectProgram(programs[i]); break; } } UpdateFromPlugin(); return; } int value = -1; for (size_t i = 0; i < mParameters[p].valueNames.size(); ++i) { if (wxString(mParameters[p].valueNames[i].c_str(), wxConvISO8859_1) == combos[p]->GetValue()) { value = i; break; } } if (value >= 0) { mPlugin->setParameter(mParameters[p].identifier, value); } } void VampEffectDialog::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 = mParameters[p].minValue; float upper = mParameters[p].maxValue; float range; range = upper - lower; val = (sliders[p]->GetValue() / 1000.0) * range; if (mParameters[p].isQuantized) { float qs = mParameters[p].quantizeStep; if (qs != 0.0) { val = int(val / qs + 0.5) * qs; } } val += lower; wxString str = Internat::ToDisplayString(val); fields[p]->SetValue(str); mPlugin->setParameter(mParameters[p].identifier, val); inSlider = false; } void VampEffectDialog::OnTextCtrl(wxCommandEvent & WXUNUSED(event)) { HandleText(); } void VampEffectDialog::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 < mParameters.size(); p++) { if (mParameters[p].isQuantized && mParameters[p].quantizeStep == 1.0) { if (mParameters[p].minValue == 0.0 && mParameters[p].maxValue == 1.0) { // toggle, not slider continue; } if (!mParameters[p].valueNames.empty()) { // combo, not slider continue; } } double dval; float val; float lower = mParameters[p].minValue; float upper = mParameters[p].maxValue; float range; dval = Internat::CompatibleToDouble(fields[p]->GetValue()); val = dval; range = upper - lower; if (val < lower) val = lower; if (val > upper) val = upper; if (mParameters[p].isQuantized) { float qs = mParameters[p].quantizeStep; if (qs != 0.0) { val = int((val - lower) / qs + 0.5) * qs + lower; } } mPlugin->setParameter(mParameters[p].identifier, val); sliders[p]->SetValue((int)(((val-lower)/range) * 1000.0 + 0.5)); } inText = false; } void VampEffectDialog::UpdateFromPlugin() { inSlider = true; for (unsigned long p = 0; p < mParameters.size(); p++) { float value = mPlugin->getParameter(mParameters[p].identifier); if (mParameters[p].isQuantized && mParameters[p].quantizeStep == 1.0 && mParameters[p].minValue == 0.0 && mParameters[p].maxValue == 1.0) { toggles[p]->SetValue(value > 0.5); } else if (mParameters[p].isQuantized && mParameters[p].quantizeStep == 1.0 && !mParameters[p].valueNames.empty()) { wxString selected; for (size_t i = 0; i < mParameters[p].valueNames.size(); ++i) { wxString choice = wxString (mParameters[p].valueNames[i].c_str(), wxConvISO8859_1); if (size_t(value - mParameters[p].minValue + 0.5) == i) { selected = choice; break; } } combos[p]->SetValue(selected); } else { fields[p]->SetValue(Internat::ToDisplayString(value)); } } inSlider = false; HandleText(); } void VampEffectDialog::OnOK(wxCommandEvent & WXUNUSED(event)) { EndModal(TRUE); } void VampEffectDialog::OnCancel(wxCommandEvent & WXUNUSED(event)) { EndModal(FALSE); } void VampEffectDialog::ConnectFocus(wxControl *c) { c->GetEventHandler()->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(VampEffectDialog::ControlSetFocus)); } void VampEffectDialog::DisconnectFocus(wxControl *c) { c->GetEventHandler()->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(VampEffectDialog::ControlSetFocus)); } void VampEffectDialog::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); }; // 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