X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fvst_plugin.cc;h=ad5ecb40c60903448c3276bef271fe0d34755f7e;hb=9458fce148c862f5504d88754d5418972dd4046b;hp=6465344d6eb7b3fafcd16d1435786bf361ad1546;hpb=8af0757b61990767f2a85e68f535a5af9976fd79;p=ardour.git diff --git a/libs/ardour/vst_plugin.cc b/libs/ardour/vst_plugin.cc index 6465344d6e..ad5ecb40c6 100644 --- a/libs/ardour/vst_plugin.cc +++ b/libs/ardour/vst_plugin.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2004 Paul Davis + Copyright (C) 2010 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,137 +15,106 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ -#include -#include -#include -#include +#include +#include -#include -#include // so libraptor doesn't complain -#include -#include -#include // for memmove -#include -#include +#include +#include -#include -#include +#include "pbd/floating.h" +#include "pbd/locale_guard.h" -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include - -#include +#include "ardour/vst_plugin.h" +#include "ardour/vestige/aeffectx.h" +#include "ardour/session.h" +#include "ardour/vst_types.h" +#include "ardour/filesystem_paths.h" +#include "ardour/audio_buffer.h" #include "i18n.h" -#include +using namespace std; +using namespace PBD; using namespace ARDOUR; -using std::min; -using std::max; -VSTPlugin::VSTPlugin (AudioEngine& e, Session& session, FSTHandle* h) - : Plugin (e, session) +VSTPlugin::VSTPlugin (AudioEngine& engine, Session& session, VSTHandle* handle) + : Plugin (engine, session) + , _handle (handle) + , _state (0) + , _plugin (0) { - handle = h; - - if ((_fst = fst_instantiate (handle, Session::vst_callback, this)) == 0) { - throw failed_constructor(); - } - - _plugin = _fst->plugin; - _plugin->user = this; - /* set rate and blocksize */ +} - _plugin->dispatcher (_plugin, effSetSampleRate, 0, 0, NULL, - (float) session.frame_rate()); - _plugin->dispatcher (_plugin, effSetBlockSize, 0, - session.get_block_size(), NULL, 0.0f); - - /* set program to zero */ +VSTPlugin::~VSTPlugin () +{ - _plugin->dispatcher (_plugin, effSetProgram, 0, 0, NULL, 0.0f); - - Plugin::setup_midi_controls (); } -VSTPlugin::VSTPlugin (const VSTPlugin &other) - : Plugin (other) +void +VSTPlugin::set_plugin (AEffect* e) { - handle = other.handle; + _plugin = e; + _plugin->user = this; - if ((_fst = fst_instantiate (handle, Session::vst_callback, this)) == 0) { - throw failed_constructor(); - } - _plugin = _fst->plugin; + /* set rate and blocksize */ - Plugin::setup_midi_controls (); + _plugin->dispatcher (_plugin, effSetSampleRate, 0, 0, NULL, (float) _session.frame_rate()); + _plugin->dispatcher (_plugin, effSetBlockSize, 0, _session.get_block_size(), NULL, 0.0f); } -VSTPlugin::~VSTPlugin () +void +VSTPlugin::deactivate () { - deactivate (); - GoingAway (this); /* EMIT SIGNAL */ - fst_close (_fst); + _plugin->dispatcher (_plugin, effMainsChanged, 0, 0, NULL, 0.0f); } void -VSTPlugin::set_block_size (jack_nframes_t nframes) +VSTPlugin::activate () { - deactivate (); - _plugin->dispatcher (_plugin, effSetBlockSize, 0, nframes, NULL, 0.0f); - activate (); + _plugin->dispatcher (_plugin, effMainsChanged, 0, 1, NULL, 0.0f); } -void -VSTPlugin::store_state (PluginState& state) +int +VSTPlugin::set_block_size (pframes_t nframes) { + deactivate (); + _plugin->dispatcher (_plugin, effSetBlockSize, 0, nframes, NULL, 0.0f); + activate (); + return 0; } -void -VSTPlugin::restore_state (PluginState& state) +float +VSTPlugin::default_value (uint32_t) { + return 0; } float -VSTPlugin::default_value (uint32_t port) +VSTPlugin::get_parameter (uint32_t which) const { - return 0; -} + return _plugin->getParameter (_plugin, which); +} void -VSTPlugin::set_parameter (uint32_t which, float val) +VSTPlugin::set_parameter (uint32_t which, float newval) { - _plugin->setParameter (_plugin, which, val); - ParameterChanged (which, val); /* EMIT SIGNAL */ + float oldval = get_parameter (which); - if (session().get_midi_feedback()) { - - if (which < parameter_count() && midi_controls[which]) { - midi_controls[which]->send_feedback (val); - } + if (PBD::floateq (oldval, newval, 1)) { + return; } -} -float -VSTPlugin::get_parameter (uint32_t which) const -{ - return _plugin->getParameter (_plugin, which); - + _plugin->setParameter (_plugin, which, newval); + + float curval = get_parameter (which); + + if (!PBD::floateq (curval, oldval, 1)) { + /* value has changed, follow rest of the notification path */ + Plugin::set_parameter (which, newval); + } } uint32_t @@ -155,127 +124,140 @@ VSTPlugin::nth_parameter (uint32_t n, bool& ok) const return n; } -XMLNode& -VSTPlugin::get_state() +/** Get VST chunk as base64-encoded data. + * @param single true for single program, false for all programs. + * @return 0-terminated base64-encoded data; must be passed to g_free () by caller. + */ +gchar * +VSTPlugin::get_chunk (bool single) const { - XMLNode *root = new XMLNode (state_node_name()); - LocaleGuard lg (X_("POSIX")); - - if (_plugin->flags & effFlagsProgramChunks) { - - /* fetch the current chunk */ - - void* data; - long data_size; - - if ((data_size = _plugin->dispatcher (_plugin, effGetChunk, 0, 0, &data, false)) == 0) { - return *root; - } - - /* save it to a file */ - - string path; - struct stat sbuf; + guchar* data; + int32_t data_size = _plugin->dispatcher (_plugin, 23 /* effGetChunk */, single ? 1 : 0, 0, &data, 0); + if (data_size == 0) { + return 0; + } - path = getenv ("HOME"); - path += "/.ardour/vst"; + return g_base64_encode (data, data_size); +} - if (stat (path.c_str(), &sbuf)) { - if (errno == ENOENT) { - if (mkdir (path.c_str(), 0600)) { - error << compose (_("cannot create VST chunk directory: %1"), - strerror (errno)) - << endmsg; - return *root; - } +/** Set VST chunk from base64-encoded data. + * @param 0-terminated base64-encoded data. + * @param single true for single program, false for all programs. + * @return 0 on success, non-0 on failure + */ +int +VSTPlugin::set_chunk (gchar const * data, bool single) +{ + gsize size = 0; + guchar* raw_data = g_base64_decode (data, &size); + int const r = _plugin->dispatcher (_plugin, 24 /* effSetChunk */, single ? 1 : 0, size, raw_data, 0); + g_free (raw_data); + return r; +} - } else { +void +VSTPlugin::add_state (XMLNode* root) const +{ + LocaleGuard lg (X_("C")); - error << compose (_("cannot check VST chunk directory: %1"), - strerror (errno)) - << endmsg; - return *root; - } + if (_plugin->flags & 32 /* effFlagsProgramsChunks */) { - } else if (!S_ISDIR (sbuf.st_mode)) { - error << compose (_("%1 exists but is not a directory"), path) - << endmsg; - return *root; + gchar* data = get_chunk (false); + if (data == 0) { + return; } - - path += "something"; - + /* store information */ XMLNode* chunk_node = new XMLNode (X_("chunk")); - chunk_node->add_property ("path", path); - + + chunk_node->add_content (data); + g_free (data); + root->add_child_nocopy (*chunk_node); - + } else { XMLNode* parameters = new XMLNode ("parameters"); - for (long n = 0; n < _plugin->numParams; ++n) { + for (int32_t n = 0; n < _plugin->numParams; ++n) { char index[64]; char val[32]; - snprintf (index, sizeof (index), "param_%ld", n); - snprintf (val, sizeof (val), "%f", _plugin->getParameter (_plugin, n)); + snprintf (index, sizeof (index), "param-%d", n); + snprintf (val, sizeof (val), "%.12g", _plugin->getParameter (_plugin, n)); parameters->add_property (index, val); } root->add_child_nocopy (*parameters); } - - return *root; } int -VSTPlugin::set_state(const XMLNode& node) +VSTPlugin::set_state (const XMLNode& node, int version) { - LocaleGuard lg (X_("POSIX")); + LocaleGuard lg (X_("C")); + int ret = -1; if (node.name() != state_node_name()) { error << _("Bad node sent to VSTPlugin::set_state") << endmsg; return 0; } +#ifndef NO_PLUGIN_STATE XMLNode* child; - if ((child = find_named_node (node, X_("chunks"))) != 0) { + if ((child = find_named_node (node, X_("chunk"))) != 0) { - return 0; + XMLPropertyList::const_iterator i; + XMLNodeList::const_iterator n; + + for (n = child->children ().begin (); n != child->children ().end (); ++n) { + if ((*n)->is_content ()) { + /* XXX: this may be dubious for the same reasons that we delay + execution of load_preset. + */ + ret = set_chunk ((*n)->content().c_str(), false); + } + } } else if ((child = find_named_node (node, X_("parameters"))) != 0) { - + XMLPropertyList::const_iterator i; for (i = child->properties().begin(); i != child->properties().end(); ++i) { - long param; + int32_t param; float val; - sscanf ((*i)->name().c_str(), "param_%ld", ¶m); + + sscanf ((*i)->name().c_str(), "param-%d", ¶m); sscanf ((*i)->value().c_str(), "%f", &val); _plugin->setParameter (_plugin, param, val); } - return 0; + ret = 0; + } +#endif - return -1; + Plugin::set_state (node, version); + return ret; } + int VSTPlugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& desc) const { VstParameterProperties prop; + memset (&prop, 0, sizeof (VstParameterProperties)); desc.min_unbound = false; desc.max_unbound = false; + prop.flags = 0; if (_plugin->dispatcher (_plugin, effGetParameterProperties, which, 0, &prop, 0)) { /* i have yet to find or hear of a VST plugin that uses this */ + /* RG: faust2vsti does use this :) */ if (prop.flags & kVstParameterUsesIntegerMinMax) { desc.lower = prop.minInteger; @@ -284,28 +266,32 @@ VSTPlugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& desc) desc.lower = 0; desc.upper = 1.0; } - + if (prop.flags & kVstParameterUsesIntStep) { - + desc.step = prop.stepInteger; desc.smallstep = prop.stepInteger; desc.largestep = prop.stepInteger; - + } else if (prop.flags & kVstParameterUsesFloatStep) { - + desc.step = prop.stepFloat; desc.smallstep = prop.smallStepFloat; desc.largestep = prop.largeStepFloat; - + } else { - + float range = desc.upper - desc.lower; - + desc.step = range / 100.0f; desc.smallstep = desc.step / 2.0f; desc.largestep = desc.step * 10.0f; } - + + if (strlen(prop.label) == 0) { + _plugin->dispatcher (_plugin, effGetParamName, which, 0, prop.label, 0); + } + desc.toggled = prop.flags & kVstParameterIsSwitch; desc.logarithmic = false; desc.sr_dependent = false; @@ -316,7 +302,8 @@ VSTPlugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& desc) /* old style */ char label[64]; - label[0] = '\0'; + /* some VST plugins expect this buffer to be zero-filled */ + memset (label, 0, sizeof (label)); _plugin->dispatcher (_plugin, effGetParamName, which, 0, label, 0); @@ -336,124 +323,322 @@ VSTPlugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& desc) } bool -VSTPlugin::load_preset (string name) +VSTPlugin::load_preset (PresetRecord r) { - if (_plugin->flags & effFlagsProgramChunks) { - error << _("no support for presets using chunks at this time") - << endmsg; - return false; + bool s; + + if (r.user) { + s = load_user_preset (r); + } else { + s = load_plugin_preset (r); + } + + if (s) { + Plugin::load_preset (r); } - return Plugin::load_preset (name); + + return s; } bool -VSTPlugin::save_preset (string name) +VSTPlugin::load_plugin_preset (PresetRecord r) { - if (_plugin->flags & effFlagsProgramChunks) { - error << _("no support for presets using chunks at this time") - << endmsg; + /* This is a plugin-provided preset. + We can't dispatch directly here; too many plugins expects only one GUI thread. + */ + + /* Extract the index of this preset from the URI */ + int id; + int index; +#ifndef NDEBUG + int const p = sscanf (r.uri.c_str(), "VST:%d:%d", &id, &index); + assert (p == 2); +#else + sscanf (r.uri.c_str(), "VST:%d:%d", &id, &index); +#endif + _state->want_program = index; + return true; +} + +bool +VSTPlugin::load_user_preset (PresetRecord r) +{ + /* This is a user preset; we load it, and this code also knows about the + non-direct-dispatch thing. + */ + + boost::shared_ptr t (presets_tree ()); + if (t == 0) { return false; } - return Plugin::save_preset (name, "vst"); + + XMLNode* root = t->root (); + + for (XMLNodeList::const_iterator i = root->children().begin(); i != root->children().end(); ++i) { + XMLProperty* label = (*i)->property (X_("label")); + + assert (label); + + if (label->value() != r.label) { + continue; + } + + if (_plugin->flags & 32 /* effFlagsProgramsChunks */) { + + /* Load a user preset chunk from our XML file and send it via a circuitous route to the plugin */ + + if (_state->wanted_chunk) { + g_free (_state->wanted_chunk); + } + + for (XMLNodeList::const_iterator j = (*i)->children().begin(); j != (*i)->children().end(); ++j) { + if ((*j)->is_content ()) { + /* we can't dispatch directly here; too many plugins expect only one GUI thread */ + gsize size = 0; + guchar* raw_data = g_base64_decode ((*j)->content().c_str(), &size); + _state->wanted_chunk = raw_data; + _state->wanted_chunk_size = size; + _state->want_chunk = 1; + return true; + } + } + + return false; + + } else { + + for (XMLNodeList::const_iterator j = (*i)->children().begin(); j != (*i)->children().end(); ++j) { + if ((*j)->name() == X_("Parameter")) { + XMLProperty* index = (*j)->property (X_("index")); + XMLProperty* value = (*j)->property (X_("value")); + + assert (index); + assert (value); + + set_parameter (atoi (index->value().c_str()), atof (value->value().c_str ())); + } + } + return true; + } + } + return false; } string -VSTPlugin::describe_parameter (uint32_t param) +VSTPlugin::do_save_preset (string name) +{ + boost::shared_ptr t (presets_tree ()); + if (t == 0) { + return ""; + } + + XMLNode* p = 0; + /* XXX: use of _presets.size() + 1 for the unique ID here is dubious at best */ + string const uri = string_compose (X_("VST:%1:%2"), unique_id (), _presets.size() + 1); + + if (_plugin->flags & 32 /* effFlagsProgramsChunks */) { + + p = new XMLNode (X_("ChunkPreset")); + p->add_property (X_("uri"), uri); + p->add_property (X_("label"), name); + gchar* data = get_chunk (true); + p->add_content (string (data)); + g_free (data); + + } else { + + p = new XMLNode (X_("Preset")); + p->add_property (X_("uri"), uri); + p->add_property (X_("label"), name); + + for (uint32_t i = 0; i < parameter_count(); ++i) { + if (parameter_is_input (i)) { + XMLNode* c = new XMLNode (X_("Parameter")); + c->add_property (X_("index"), string_compose ("%1", i)); + c->add_property (X_("value"), string_compose ("%1", get_parameter (i))); + p->add_child_nocopy (*c); + } + } + } + + t->root()->add_child_nocopy (*p); + + std::string f = Glib::build_filename (ARDOUR::user_config_directory (), "presets"); + f = Glib::build_filename (f, presets_file ()); + + t->write (f); + return uri; +} + +void +VSTPlugin::do_remove_preset (string name) +{ + boost::shared_ptr t (presets_tree ()); + if (t == 0) { + return; + } + + t->root()->remove_nodes_and_delete (X_("label"), name); + + std::string f = Glib::build_filename (ARDOUR::user_config_directory (), "presets"); + f = Glib::build_filename (f, presets_file ()); + + t->write (f); +} + +string +VSTPlugin::describe_parameter (Evoral::Parameter param) { char name[64]; - _plugin->dispatcher (_plugin, effGetParamName, param, 0, name, 0); + memset (name, 0, sizeof (name)); + + /* some VST plugins expect this buffer to be zero-filled */ + + _plugin->dispatcher (_plugin, effGetParamName, param.id(), 0, name, 0); + + if (name[0] == '\0') { + strcpy (name, _("Unknown")); + } + return name; } -jack_nframes_t -VSTPlugin::latency () const +framecnt_t +VSTPlugin::signal_latency () const { - return _plugin->initialDelay; + if (_user_latency) { + return _user_latency; + } + + return *((int32_t *) (((char *) &_plugin->flags) + 12)); /* initialDelay */ } -set +set VSTPlugin::automatable () const { - set ret; + set ret; - for (uint32_t i = 0; i < parameter_count(); ++i){ - ret.insert (ret.end(), i); + for (uint32_t i = 0; i < parameter_count(); ++i) { + ret.insert (ret.end(), Evoral::Parameter(PluginAutomation, 0, i)); } return ret; } int -VSTPlugin::connect_and_run (vector& bufs, uint32_t maxbuf, int32_t& in_index, int32_t& out_index, jack_nframes_t nframes, jack_nframes_t offset) +VSTPlugin::connect_and_run (BufferSet& bufs, + ChanMapping in_map, ChanMapping out_map, + pframes_t nframes, framecnt_t offset) { - float *ins[_plugin->numInputs]; - float *outs[_plugin->numOutputs]; + Plugin::connect_and_run (bufs, in_map, out_map, nframes, offset); + + ChanCount bufs_count; + bufs_count.set(DataType::AUDIO, 1); + bufs_count.set(DataType::MIDI, 1); + _midi_out_buf = 0; + + BufferSet& silent_bufs = _session.get_silent_buffers(bufs_count); + BufferSet& scratch_bufs = _session.get_scratch_buffers(bufs_count); + + /* VC++ doesn't support the C99 extension that allows + + typeName foo[variableDefiningSize]; + + Use alloca instead of dynamic array (rather than std::vector which + allocs on the heap) because this is realtime code. + */ + + float** ins = (float**)alloca(_plugin->numInputs*sizeof(float*)); + float** outs = (float**)alloca(_plugin->numOutputs*sizeof(float*)); + int32_t i; + uint32_t in_index = 0; for (i = 0; i < (int32_t) _plugin->numInputs; ++i) { - ins[i] = bufs[min((uint32_t) in_index,maxbuf)]; - in_index++; + uint32_t index; + bool valid = false; + index = in_map.get(DataType::AUDIO, in_index++, &valid); + ins[i] = (valid) + ? bufs.get_audio(index).data(offset) + : silent_bufs.get_audio(0).data(offset); } + uint32_t out_index = 0; for (i = 0; i < (int32_t) _plugin->numOutputs; ++i) { - outs[i] = bufs[min((uint32_t) out_index,maxbuf)]; - - /* unbelievably, several VST plugins still rely on Cubase - behaviour and do not silence the buffer in processReplacing - when they have no output. - */ - - // memset (outs[i], 0, sizeof (Sample) * nframes); - out_index++; + outs[i] = scratch_bufs.get_audio(i).data(offset); } + if (bufs.count().n_midi() > 0) { + VstEvents* v = 0; + bool valid = false; + const uint32_t buf_index_in = in_map.get(DataType::MIDI, 0, &valid); + if (valid) { + v = bufs.get_vst_midi (buf_index_in); + } + valid = false; + const uint32_t buf_index_out = out_map.get(DataType::MIDI, 0, &valid); + if (valid) { + _midi_out_buf = &bufs.get_midi(buf_index_out); + _midi_out_buf->silence(0, 0); + } else { + _midi_out_buf = 0; + } + if (v) { + _plugin->dispatcher (_plugin, effProcessEvents, 0, 0, v, 0); + } + } /* we already know it can support processReplacing */ + _plugin->processReplacing (_plugin, &ins[0], &outs[0], nframes); + _midi_out_buf = 0; - _plugin->processReplacing (_plugin, ins, outs, nframes); - + out_index = 0; + for (i = 0; i < (int32_t) _plugin->numOutputs; ++i) { + uint32_t index; + bool valid = false; + index = out_map.get(DataType::AUDIO, out_index++, &valid); + if (!valid) { + continue; + } + copy_vector (bufs.get_audio(index).data(offset), outs[i], nframes); + } return 0; } -void -VSTPlugin::deactivate () +string +VSTPlugin::unique_id () const { - _plugin->dispatcher (_plugin, effMainsChanged, 0, 0, NULL, 0.0f); -} + char buf[32]; -void -VSTPlugin::activate () -{ - _plugin->dispatcher (_plugin, effMainsChanged, 0, 1, NULL, 0.0f); -} + snprintf (buf, sizeof (buf), "%d", _plugin->uniqueID); -uint32_t -VSTPlugin::unique_id() const -{ - return _plugin->uniqueID; + return string (buf); } const char * VSTPlugin::name () const { - return handle->name; + if (!_info->name.empty ()) { + return _info->name.c_str(); + } + return _handle->name; } const char * VSTPlugin::maker () const { - return "imadeit"; + return _info->creator.c_str(); } const char * VSTPlugin::label () const { - return handle->name; + return _handle->name; } uint32_t -VSTPlugin::parameter_count() const +VSTPlugin::parameter_count () const { return _plugin->numParams; } @@ -465,13 +650,11 @@ VSTPlugin::has_editor () const } void -VSTPlugin::print_parameter (uint32_t param, char *buf, uint32_t len) const +VSTPlugin::print_parameter (uint32_t param, char *buf, uint32_t /*len*/) const { - char lab[9]; char *first_nonws; - _plugin->dispatcher (_plugin, effGetParamLabel, param, 0, lab, 0); - _plugin->dispatcher (_plugin, effGetParamDisplay, param, 0, buf, 0); + _plugin->dispatcher (_plugin, 7 /* effGetParamDisplay */, param, 0, buf, 0); if (buf[0] == '\0') { return; @@ -481,9 +664,100 @@ VSTPlugin::print_parameter (uint32_t param, char *buf, uint32_t len) const while (*first_nonws && isspace (*first_nonws)) { first_nonws++; } + if (*first_nonws == '\0') { return; } memmove (buf, first_nonws, strlen (buf) - (first_nonws - buf) + 1); } + +void +VSTPlugin::find_presets () +{ + /* Built-in presets */ + + int const vst_version = _plugin->dispatcher (_plugin, effGetVstVersion, 0, 0, NULL, 0); + for (int i = 0; i < _plugin->numPrograms; ++i) { + PresetRecord r (string_compose (X_("VST:%1:%2"), unique_id (), i), "", -1, false); + + if (vst_version >= 2) { + char buf[256]; + if (_plugin->dispatcher (_plugin, 29, i, 0, buf, 0) == 1) { + r.label = buf; + } else { + r.label = string_compose (_("Preset %1"), i); + } + } else { + r.label = string_compose (_("Preset %1"), i); + } + + _presets.insert (make_pair (r.uri, r)); + } + + /* User presets from our XML file */ + + boost::shared_ptr t (presets_tree ()); + + if (t) { + XMLNode* root = t->root (); + for (XMLNodeList::const_iterator i = root->children().begin(); i != root->children().end(); ++i) { + + XMLProperty* uri = (*i)->property (X_("uri")); + XMLProperty* label = (*i)->property (X_("label")); + + assert (uri); + assert (label); + + PresetRecord r (uri->value(), label->value(), -1, true); + _presets.insert (make_pair (r.uri, r)); + } + } + +} + +/** @return XMLTree with our user presets; could be a new one if no existing + * one was found, or 0 if one was present but badly-formatted. + */ +XMLTree * +VSTPlugin::presets_tree () const +{ + XMLTree* t = new XMLTree; + + std::string p = Glib::build_filename (ARDOUR::user_config_directory (), "presets"); + + if (!Glib::file_test (p, Glib::FILE_TEST_IS_DIR)) { + if (g_mkdir_with_parents (p.c_str(), 0755) != 0) { + error << _("Unable to make VST presets directory") << endmsg; + }; + } + + p = Glib::build_filename (p, presets_file ()); + + if (!Glib::file_test (p, Glib::FILE_TEST_EXISTS)) { + t->set_root (new XMLNode (X_("VSTPresets"))); + return t; + } + + t->set_filename (p); + if (!t->read ()) { + delete t; + return 0; + } + + return t; +} + +/** @return Index of the first user preset in our lists */ +int +VSTPlugin::first_user_preset_index () const +{ + return _plugin->numPrograms; +} + +string +VSTPlugin::presets_file () const +{ + return string_compose ("vst-%1", unique_id ()); +} +