diff options
Diffstat (limited to 'lvz')
l--------- | lvz/AudioEffect.hpp | 1 | ||||
-rw-r--r-- | lvz/audioeffectx.h | 151 | ||||
-rw-r--r-- | lvz/gendata.cpp | 251 | ||||
-rw-r--r-- | lvz/wrapper.cpp | 216 |
4 files changed, 619 insertions, 0 deletions
diff --git a/lvz/AudioEffect.hpp b/lvz/AudioEffect.hpp new file mode 120000 index 0000000..df90333 --- /dev/null +++ b/lvz/AudioEffect.hpp @@ -0,0 +1 @@ +audioeffectx.h
\ No newline at end of file diff --git a/lvz/audioeffectx.h b/lvz/audioeffectx.h new file mode 100644 index 0000000..090bb60 --- /dev/null +++ b/lvz/audioeffectx.h @@ -0,0 +1,151 @@ +/* + LVZ - An ugly C++ interface for writing LV2 plugins. + Copyright 2008-2012 David Robillard <http://drobilla.net> + + This is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this software. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef LVZ_AUDIOEFFECTX_H +#define LVZ_AUDIOEFFECTX_H + +#include <stdint.h> +#include <string.h> + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +class AudioEffect; + +typedef int (*audioMasterCallback)(int, int ver, int, int, int, int); + +AudioEffect* createEffectInstance(audioMasterCallback audioMaster); + +enum LvzPinFlags { + kLvzPinIsActive = 1<<0, + kLvzPinIsStereo = 1<<1 +}; + +struct LvzPinProperties { + LvzPinProperties() : label(NULL), flags(0) {} + char* label; + int flags; +}; + +enum LvzEventTypes { + kLvzMidiType = 0 +}; + +struct LvzEvent { + int type; +}; + +struct LvzMidiEvent : public LvzEvent { + char* midiData; + int32_t deltaFrames; +}; + +struct LvzEvents { + int32_t numEvents; + LvzEvent** events; +}; + +#define DECLARE_LVZ_DEPRECATED(x) x + +class AudioEffect { +public: + AudioEffect() {} + virtual ~AudioEffect() {} + + virtual void setParameter(int32_t index, float value) = 0; + virtual void setParameterAutomated(int32_t index, float value) {} + virtual float getParameter(int32_t index) = 0; + + virtual void masterIdle() {} +}; + +class AudioEffectX : public AudioEffect { +public: + AudioEffectX(audioMasterCallback audioMaster, int32_t progs, int32_t params) + : URI("NIL") + , uniqueID("NIL") + , eventInput(NULL) + , sampleRate(44100) + , curProgram(0) + , numInputs(0) + , numOutputs(0) + , numParams(params) + , numPrograms(progs) + { + } + + virtual void process (float **inputs, float **outputs, int32_t nframes) {} + virtual void processReplacing(float **inputs, float **outputs, int32_t nframes) = 0; + + void setMidiEventType(LV2_URID urid) { midiEventType = urid; } + void setEventInput(const LV2_Atom_Sequence* seq) { eventInput = seq; } + + virtual int32_t processEvents(LvzEvents* ev) { return 0; } + + virtual const char* getURI() { return URI; } + virtual const char* getUniqueID() { return uniqueID; } + virtual float getSampleRate() { return sampleRate; } + virtual int32_t getNumInputs() { return numInputs; } + virtual int32_t getNumOutputs() { return numOutputs; } + virtual int32_t getNumParameters() { return numParams; } + virtual int32_t getNumPrograms() { return numPrograms; } + + virtual void getParameterName(int32_t index, char *label) = 0; + virtual bool getProductString(char* text) = 0; + virtual void getProgramName(char *name) { name[0] = '\0'; } + + virtual int32_t canDo(const char* text) { return false; } + virtual bool canHostDo(const char* act) { return false; } + virtual void canMono() {} + virtual void canProcessReplacing() {} + virtual void isSynth() {} + virtual void wantEvents() {} + + virtual void setBlockSize(int32_t size) {} + virtual void setNumInputs(int32_t num) { numInputs = num; } + virtual void setNumOutputs(int32_t num) { numOutputs = num; } + virtual void setSampleRate(float rate) { sampleRate = rate; } + virtual void setProgram(int32_t prog) { curProgram = prog; } + virtual void setURI(const char* uri) { URI = uri; } + virtual void setUniqueID(const char* id) { uniqueID = id; } + virtual void suspend() {} + virtual void beginEdit(int32_t index) {} + virtual void endEdit(int32_t index) {} + + virtual long dispatcher(long opCode, long index, long value, void *ptr, float opt) { + return 0; + } + +protected: + const char* URI; + const char* uniqueID; + const LV2_Atom_Sequence* eventInput; + LV2_URID midiEventType; + float sampleRate; + int32_t curProgram; + int32_t numInputs; + int32_t numOutputs; + int32_t numParams; + int32_t numPrograms; +}; + +extern "C" { +AudioEffectX* lvz_new_audioeffectx(); +} + +#endif // LVZ_AUDIOEFFECTX_H diff --git a/lvz/gendata.cpp b/lvz/gendata.cpp new file mode 100644 index 0000000..2bddf9d --- /dev/null +++ b/lvz/gendata.cpp @@ -0,0 +1,251 @@ +/* + LVZ - An ugly C++ interface for writing LV2 plugins. + Copyright 2008-2012 David Robillard <http://drobilla.net> + + This is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this software. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cassert> +#include <fstream> +#include <iostream> +#include <list> +#include <map> +#include <string> + +#include <dlfcn.h> + +#include "audioeffectx.h" + +using namespace std; + +// VST is so incredibly awful. Just.. wow. +#define MAX_NAME_LENGTH 1024 +char name_buf[MAX_NAME_LENGTH]; + +struct Record { + Record(const string& n) : base_name(n) {} + string base_name; + typedef list<string> UIs; + UIs uis; +}; + +typedef std::map<string, Record> Manifest; +Manifest manifest; + +string +symbolify(const char* name, char space_char='_') +{ + string str(name); + + // Like This -> Like_This + for (size_t i=0; i < str.length(); ++i) + if (str[i] == ' ') + str[i] = space_char; + + str[0] = std::tolower(str[0]); + + // LikeThis -> like_this + for (size_t i=1; i < str.length(); ++i) + if (str[i] >= 'A' && str[i] <= 'Z' + && str[i-1] >= 'a' && str[i-1] <= 'z' + && ((i == str.length() - 1) || (str[i+1] <= 'a' && str[i+1] >= 'Z')) + && (!(str[i-1] == 'd' && str[i] == 'B')) + && (!(str[i-1] == 'F' && str[i] == 'X')) + && (!(str[i-1] == 'D' && str[i] == 'C'))) + str = str.substr(0, i) + space_char + str.substr(i); + + // To lowercase, and skip invalids + for (size_t i=1; i < str.length(); ) { + if (std::isalpha(str[i]) || std::isdigit(str[i])) { + str[i] = std::tolower(str[i]); + ++i; + } else if (str[i-1] != space_char) { + str[i] = space_char; + ++i; + } else { + str = str.substr(0, i) + str.substr(i+1); + } + } + + return str; +} + +void +write_plugin(AudioEffectX* effect, const string& lib_file_name) +{ + const string base_name = lib_file_name.substr(0, lib_file_name.find_last_of(".")); + const string data_file_name = base_name + ".ttl"; + + fstream os(data_file_name.c_str(), ios::out); + effect->getProductString(name_buf); + + os << "@prefix lv2: <http://lv2plug.in/ns/lv2core#> ." << endl; + os << "@prefix doap: <http://usefulinc.com/ns/doap#> ." << endl << endl; + os << "<" << effect->getURI() << ">" << endl; + os << "\tlv2:symbol \"" << effect->getUniqueID() << "\" ;" << endl; + os << "\tdoap:name \"" << name_buf << "\" ;" << endl; + os << "\tdoap:license <http://usefulinc.com/doap/licenses/gpl> ;" << endl; + os << "\tlv2:pluginProperty lv2:hardRTCapable"; + + uint32_t num_params = effect->getNumParameters(); + uint32_t num_audio_ins = effect->getNumInputs(); + uint32_t num_audio_outs = effect->getNumOutputs(); + uint32_t num_ports = num_params + num_audio_ins + num_audio_outs; + + if (num_ports > 0) + os << " ;" << endl << "\tlv2:port [" << endl; + else + os << " ." << endl; + + uint32_t idx = 0; + + for (uint32_t i = idx; i < num_params; ++i, ++idx) { + effect->getParameterName(i, name_buf); + os << "\t\ta lv2:InputPort, lv2:ControlPort ;" << endl; + os << "\t\tlv2:index" << " " << idx << " ;" << endl; + os << "\t\tlv2:name \"" << name_buf << "\" ;" << endl; + os << "\t\tlv2:symbol \"" << symbolify(name_buf) << "\" ;" << endl; + os << "\t\tlv2:default " << effect->getParameter(i) << " ;" << endl; + os << "\t\tlv2:minimum 0.0 ;" << endl; + os << "\t\tlv2:maximum 1.0 ;" << endl; + os << ((idx == num_ports - 1) ? "\t] ." : "\t] , [") << endl; + } + + for (uint32_t i = 0; i < num_audio_ins; ++i, ++idx) { + os << "\t\ta lv2:InputPort, lv2:AudioPort ;" << endl; + os << "\t\tlv2:index" << " " << idx << " ;" << endl; + os << "\t\tlv2:symbol \"in" << i+1 << "\" ;" << endl; + os << "\t\tlv2:name \"Input " << i+1 << "\" ;" << endl; + os << ((idx == num_ports - 1) ? "\t] ." : "\t] , [") << endl; + } + + for (uint32_t i = 0; i < num_audio_outs; ++i, ++idx) { + os << "\t\ta lv2:OutputPort, lv2:AudioPort ;" << endl; + os << "\t\tlv2:index " << idx << " ;" << endl; + os << "\t\tlv2:symbol \"out" << i+1 << "\" ;" << endl; + os << "\t\tlv2:name \"Output " << i+1 << "\" ;" << endl; + os << ((idx == num_ports - 1) ? "\t] ." : "\t] , [") << endl; + } + + os.close(); + + Manifest::iterator i = manifest.find(effect->getURI()); + if (i != manifest.end()) { + i->second.base_name = base_name; + } else { + manifest.insert(std::make_pair(effect->getURI(), Record(base_name))); + } + + if (effect->getNumPrograms() > 1) { + std::string preset_file = base_name + "-presets.ttl"; + + fstream pos(preset_file.c_str(), ios::out); + pos << "@prefix lv2: <http://lv2plug.in/ns/lv2core#> ." << endl; + pos << "@prefix pset: <http://lv2plug.in/ns/ext/presets#> ." << endl; + pos << "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> ." << endl << endl; + for (int32_t i = 0; i < effect->getNumPrograms(); ++i) { + effect->setProgram(i); + effect->getProgramName(name_buf); + + std::string preset_uri = string(effect->getURI()) + + "#pset-" + symbolify(name_buf, '-'); + + // Write manifest entry + std::cout << "<" << preset_uri << ">" + << "\n\ta pset:Preset ;\n\tlv2:appliesTo <" + << effect->getURI() << "> ;\n\t" + << "rdfs:seeAlso <" << preset_file << "> .\n" << std::endl; + + // Write preset file + pos << "<" << preset_uri << ">" + << "\n\ta pset:Preset ;\n\tlv2:appliesTo <" + << effect->getURI() << "> ;\n\t" + << "rdfs:label \"" << name_buf << "\""; + for (uint32_t i = 0; i < num_params; ++i) { + effect->getParameterName(i, name_buf); + pos << " ;\n\tlv2:port [" << endl; + pos << "\t\tlv2:symbol \"" << symbolify(name_buf) << "\" ;" << endl; + pos << "\t\tpset:value " << effect->getParameter(i) << " ;" << endl; + pos << "\t]"; + } + pos << " .\n" << endl; + } + pos.close(); + } +} + +void +write_manifest(ostream& os) +{ + os << "@prefix lv2: <http://lv2plug.in/ns/lv2core#> ." << endl; + os << "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> ." << endl; + os << "@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> ." << endl << endl; + for (Manifest::iterator i = manifest.begin(); i != manifest.end(); ++i) { + Record& r = i->second; + os << "<" << i->first << ">\n\ta lv2:Plugin ;" << endl; + os << "\trdfs:seeAlso <" << r.base_name << ".ttl> ;" << endl; + os << "\tlv2:binary <" << r.base_name << ".so> "; + for (Record::UIs::iterator j = r.uis.begin(); j != r.uis.end(); ++j) + os << ";" << endl << "\tuiext:ui <" << *j << "> "; + os << "." << endl << endl; + } +} + +int +main(int argc, char** argv) +{ + if (argc == 0) { + cout << "Usage: gendata [PLUGINLIB1] [PLUGINLIB2]..." << endl; + cout << "Each argument is a path to a LVZ plugin library." << endl; + cout << "For each library an LV2 data file with the same name" << endl; + cout << "will be output containing the data for that plugin." << endl; + cout << "A manifest of the plugins found is written to stdout" << endl; + return 1; + } + + typedef AudioEffectX* (*new_effect_func)(); + typedef AudioEffectX* (*plugin_uri_func)(); + + new_effect_func constructor = NULL; + AudioEffectX* effect = NULL; + + for (int i = 1; i < argc; ++i) { + void* handle = dlopen(argv[i], RTLD_LAZY); + if (handle == NULL) { + cerr << "ERROR: " << argv[i] << ": " << dlerror() << " (ignoring)" << endl; + continue; + } + + string lib_path = argv[i]; + size_t last_slash = lib_path.find_last_of("/"); + if (last_slash != string::npos) + lib_path = lib_path.substr(last_slash + 1); + + constructor = (new_effect_func)dlsym(handle, "lvz_new_audioeffectx"); + if (constructor != NULL) { + effect = constructor(); + write_plugin(effect, lib_path); + } + + if (constructor == NULL) { + cerr << "ERROR: " << argv[i] << ": not an LVZ plugin library, ignoring" << endl; + } + + dlclose(handle); + } + + write_manifest(cout); + + return 0; +} diff --git a/lvz/wrapper.cpp b/lvz/wrapper.cpp new file mode 100644 index 0000000..6d265a7 --- /dev/null +++ b/lvz/wrapper.cpp @@ -0,0 +1,216 @@ +/* + LVZ - An ugly C++ interface for writing LV2 plugins. + Copyright 2008-2012 David Robillard <http://drobilla.net> + + This is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this software. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef PLUGIN_CLASS +#error "This file requires PLUGIN_CLASS to be defined" +#endif +#ifndef URI_PREFIX +#error "This file requires URI_PREFIX to be defined" +#endif +#ifndef PLUGIN_URI_SUFFIX +#error "This file requires PLUGIN_URI_SUFFIX to be defined" +#endif +#ifndef PLUGIN_HEADER +#error "This file requires PLUGIN_HEADER to be defined" +#endif + +#include <stdlib.h> +#include "audioeffectx.h" +#include "lv2.h" +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include PLUGIN_HEADER + +extern "C" { + +/* Plugin */ + +typedef struct { + PLUGIN_CLASS* effect; + float* controls; + float** control_buffers; + float** inputs; + float** outputs; +} LVZPlugin; + +static void +lvz_cleanup(LV2_Handle instance) +{ + LVZPlugin* plugin = (LVZPlugin*)instance; + free(plugin->controls); + free(plugin->control_buffers); + free(plugin->inputs); + free(plugin->outputs); + delete plugin->effect; + free(plugin); +} + +static void +lvz_connect_port(LV2_Handle instance, uint32_t port, void* data) +{ + LVZPlugin* plugin = (LVZPlugin*)instance; + const uint32_t num_params = plugin->effect->getNumParameters(); + const uint32_t num_inputs = plugin->effect->getNumInputs(); + const uint32_t num_outputs = plugin->effect->getNumOutputs(); + + if (port < num_params) { + plugin->control_buffers[port] = (float*)data; + } else if (port < num_params + num_inputs) { + plugin->inputs[port - num_params] = (float*)data; + } else if (port < num_params + num_inputs + num_outputs) { + plugin->outputs[port - num_params - num_inputs] = (float*)data; + } else if (port == num_params + num_inputs + num_outputs) { + plugin->effect->setEventInput((LV2_Atom_Sequence*)data); + } +} + +static int +master_callback(int, int ver, int, int, int, int) +{ + return 0; +} + +static LV2_Handle +lvz_instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature*const* features) +{ + PLUGIN_CLASS* effect = new PLUGIN_CLASS(master_callback); + effect->setURI(URI_PREFIX PLUGIN_URI_SUFFIX); + effect->setSampleRate(rate); + + uint32_t num_params = effect->getNumParameters(); + uint32_t num_inputs = effect->getNumInputs(); + uint32_t num_outputs = effect->getNumOutputs(); + + LVZPlugin* plugin = (LVZPlugin*)malloc(sizeof(LVZPlugin)); + plugin->effect = effect; + + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID__map)) { + LV2_URID_Map* map = (LV2_URID_Map*)features[i]->data; + plugin->effect->setMidiEventType( + map->map(map->handle, LV2_MIDI__MidiEvent)); + break; + } + } + + if (num_params > 0) { + plugin->controls = (float*)malloc(sizeof(float) * num_params); + plugin->control_buffers = (float**)malloc(sizeof(float*) * num_params); + for (uint32_t i = 0; i < num_params; ++i) { + plugin->controls[i] = effect->getParameter(i); + plugin->control_buffers[i] = NULL; + } + } else { + plugin->controls = NULL; + plugin->control_buffers = NULL; + } + + if (num_inputs > 0) { + plugin->inputs = (float**)malloc(sizeof(float*) * num_inputs); + for (uint32_t i = 0; i < num_inputs; ++i) { + plugin->inputs[i] = NULL; + } + } else { + plugin->inputs = NULL; + } + + if (num_outputs > 0) { + plugin->outputs = (float**)malloc(sizeof(float*) * num_outputs); + for (uint32_t i = 0; i < num_outputs; ++i) { + plugin->outputs[i] = NULL; + } + } else { + plugin->outputs = NULL; + } + + return (LV2_Handle)plugin; +} + +static void +lvz_run(LV2_Handle instance, uint32_t sample_count) +{ + LVZPlugin* plugin = (LVZPlugin*)instance; + + for (int32_t i = 0; i < plugin->effect->getNumParameters(); ++i) { + float val = plugin->control_buffers[i][0]; + if (val != plugin->controls[i]) { + plugin->effect->setParameter(i, val); + plugin->controls[i] = val; + } + } + + plugin->effect->processReplacing(plugin->inputs, plugin->outputs, sample_count); +} + +static const void* +lvz_extension_data(const char* uri) +{ + return NULL; +} + +static void +lvz_deactivate(LV2_Handle instance) +{ + LVZPlugin* plugin = (LVZPlugin*)instance; + plugin->effect->suspend(); +} + +/* Library */ + +static LV2_Descriptor descriptor; +static bool initialised = false; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + if (!initialised) { + descriptor.URI = URI_PREFIX PLUGIN_URI_SUFFIX; + descriptor.instantiate = lvz_instantiate; + descriptor.connect_port = lvz_connect_port; + descriptor.activate = NULL; + descriptor.run = lvz_run; + descriptor.deactivate = lvz_deactivate; + descriptor.cleanup = lvz_cleanup; + descriptor.extension_data = lvz_extension_data; + initialised = true; + } + + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} + +/** Entry point for LVZ gendata */ +LV2_SYMBOL_EXPORT +AudioEffectX* +lvz_new_audioeffectx() +{ + PLUGIN_CLASS* effect = new PLUGIN_CLASS(master_callback); + effect->setURI(URI_PREFIX PLUGIN_URI_SUFFIX); + return effect; +} + +} // extern "C" |