Good News!
Auto-generating turtle files work!
Here is the output of the JUCE demo plugin, converted to ttl for LV2:
manifest.ttl:
[code]@prefix lv2: http://lv2plug.in/ns/lv2core# .
@prefix rdfs: http://www.w3.org/2000/01/rdf-schema# .
urn:Raw_Material_Software:Juce_Demo_Plugin:1.0.0
a lv2:Plugin ;
lv2:binary <Juce_Demo_Plugin.so> ;
rdfs:seeAlso <Juce_Demo_Plugin.ttl> .[/code]
Juce_Demo_Plugin.ttl:
[code]@prefix doap: http://usefulinc.com/ns/doap# .
@prefix lv2: http://lv2plug.in/ns/lv2core# .
@prefix lv2ev: http://lv2plug.in/ns/ext/event# .
@prefix lv2ui: http://lv2plug.in/ns/extensions/ui# .
urn:Raw_Material_Software:Juce_Demo_Plugin:JUCE-Native-UI
a lv2ui:JUCEUI ;
lv2ui:binary <Raw_Material_Software.so> .
urn:Raw_Material_Software:Juce_Demo_Plugin:JUCE-External-UI
a uiext:external ;
lv2ui:binary <Raw_Material_Software.so> .
urn:Raw_Material_Software:Juce_Demo_Plugin:1.0.0
a lv2:Plugin ;
lv2:port [
a lv2:InputPort, lv2ev:EventPort;
lv2ev:supportsEvent <http://lv2plug.in/ns/ext/midi#MidiEvent> ;
lv2:index 0;
lv2:symbol "midi_in";
lv2:name "MIDI Input";
] ;
lv2:port [
a lv2:OutputPort, lv2ev:EventPort;
lv2ev:supportsEvent <http://lv2plug.in/ns/ext/midi#MidiEvent> ;
lv2:index 1;
lv2:symbol "midi_out";
lv2:name "MIDI Output";
] ;
lv2:port [
a lv2:InputPort, lv2:AudioPort;
lv2:index 2;
lv2:symbol "audio_in_0";
lv2:name "Audio Input 0";
],
[
a lv2:InputPort, lv2:AudioPort;
lv2:index 3;
lv2:symbol "audio_in_1";
lv2:name "Audio Input 1";
] ;
lv2:port [
a lv2:OutputPort, lv2:AudioPort;
lv2:index 4;
lv2:symbol "audio_out_0";
lv2:name "Audio Output 0";
],
[
a lv2:OutputPort, lv2:AudioPort;
lv2:index 5;
lv2:symbol "audio_out_1";
lv2:name "Audio Output 1";
] ;
lv2:port [
a lv2:InputPort;
a lv2:ControlPort;
lv2:index 6;
lv2:symbol gain";
lv2:name gain;
lv2:default 1.0;
lv2:minimum 0.0;
lv2:maximum 1.0;
],
[
a lv2:InputPort;
a lv2:ControlPort;
lv2:index 7;
lv2:symbol delay";
lv2:name delay;
lv2:default 0.5;
lv2:minimum 0.0;
lv2:maximum 1.0;
] ;
doap:name "Juce Demo Plugin" ;
doap:creator "Raw Material Software" .[/code]
There a few things missing (presets and units), but I’ll get there later.
My current code requires some changes to JUCE plugins though, in JucePluginCharacteristics.h, I added2 more fields:
#define JucePlugin_LV2Includes "PluginProcessor.h"
#define JucePlugin_LV2ClassName JuceDemoPluginAudioProcessor
This is required to build the *.ttl files, otherwise we would need to compile the plugin binary first, and somehow extract the info from it.
I would like some opinions in here though…
Here’s my ttl-generator code so far:
[code]/*
- LV2 ttl generator for JUCE Plugins
*/
#include
#include
#include <stdint.h>
#include “JuceHeader.h”
#include “JucePluginCharacteristics.h”
#include JucePlugin_LV2Includes
// These are dummy values!
enum FakePlugCategory
{
kPlugCategUnknown,
kPlugCategEffect,
kPlugCategSynth,
kPlugCategAnalysis,
kPlugCategMastering,
kPlugCategSpacializer,
kPlugCategRoomFx,
kPlugSurroundFx,
kPlugCategRestoration,
kPlugCategOfflineProcess,
kPlugCategGenerator
};
String name_to_symbol(String Name)
{
String Symbol = Name.trimStart().trimEnd().replace(" ", “_”).toLowerCase();
for (int i=0; i < Symbol.length(); i++) {
if (std::isalpha(Symbol[i]) || std::isdigit(Symbol[i]) || Symbol[i] == '_') {
// nothing
} else {
Symbol[i] == '_';
}
}
return Symbol;
}
String float_to_string(float value)
{
if (value < 0.0f || value > 1.0f) {
std::cerr << “WARNING - Parameter uses out-of-bounds default value -> " << value << std::endl;
}
String string(value);
if (!string.contains(”.")) {
string += “.0”;
}
return string;
}
String get_uri()
{
return String(“urn:” JucePlugin_Manufacturer “:” JucePlugin_Name “:” JucePlugin_VersionString).replace(" ", “_”);
}
String get_juce_ui_uri()
{
return String(“urn:” JucePlugin_Manufacturer “:” JucePlugin_Name “:JUCE-Native-UI”).replace(" ", “_”);
}
String get_external_ui_uri()
{
return String(“urn:” JucePlugin_Manufacturer “:” JucePlugin_Name “:JUCE-External-UI”).replace(" ", “_”);
}
String get_binary_name()
{
return String(JucePlugin_Name).replace(" ", “_”);
}
String get_plugin_type()
{
String ptype;
switch (JucePlugin_VSTCategory) {
case kPlugCategSynth:
ptype += "lv2:InstrumentPlugin";
break;
case kPlugCategAnalysis:
ptype += "lv2:AnalyserPlugin";
break;
case kPlugCategMastering:
ptype += "lv2:DynamicsPlugin";
break;
case kPlugCategSpacializer:
ptype += "lv2:SpatialPlugin";
break;
case kPlugCategRoomFx:
ptype += "lv2:ModulatorPlugin";
break;
case kPlugCategRestoration:
ptype += "lv2:UtilityPlugin";
break;
case kPlugCategGenerator:
ptype += "lv2:GeneratorPlugin";
break;
}
if (ptype.isNotEmpty()) {
ptype += ", ";
}
ptype += "lv2:Plugin";
return ptype;
}
String get_manifest_ttl(String URI, String Binary)
{
String manifest;
manifest += “@prefix lv2: http://lv2plug.in/ns/lv2core# .\n”;
manifest += “@prefix rdfs: http://www.w3.org/2000/01/rdf-schema# .\n”;
manifest += “\n”;
manifest += “<” + URI + “>\n”;
manifest += " a lv2:Plugin ;\n";
manifest += " lv2:binary <" + Binary + “.so> ;\n”;
manifest += " rdfs:seeAlso <" + Binary +".ttl> .\n";
return manifest;
}
String get_plugin_ttl(String URI, String Binary)
{
// Testing, need another way to do this!!
JucePlugin_LV2ClassName* JucePlugin = new JucePlugin_LV2ClassName();
String plugin;
plugin += "@prefix doap: <http://usefulinc.com/ns/doap#> .\n";
//plugin += "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n";
//plugin += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
plugin += "@prefix lv2: <http://lv2plug.in/ns/lv2core#> .\n";
plugin += "@prefix lv2ev: <http://lv2plug.in/ns/ext/event#> .\n";
plugin += "@prefix lv2ui: <http://lv2plug.in/ns/extensions/ui#> .\n";
plugin += "\n";
if (JucePlugin->hasEditor()) {
plugin += "<" + get_juce_ui_uri() + ">\n";
plugin += " a lv2ui:JUCEUI ;\n";
plugin += " lv2ui:binary <" + Binary + ".so> .\n";
plugin += "<" + get_external_ui_uri() + ">\n";
plugin += " a uiext:external ;\n";
plugin += " lv2ui:binary <" + Binary + ".so> .\n";
plugin += "\n";
}
plugin += "<" + URI + ">\n";
plugin += " a " + get_plugin_type() + " ;\n";
plugin += "\n";
uint32_t i, port_index = 0;
#if JucePlugin_WantsMidiInput
plugin += " lv2:port [\n";
plugin += " a lv2:InputPort, lv2ev:EventPort;\n";
plugin += " lv2ev:supportsEvent http://lv2plug.in/ns/ext/midi#MidiEvent ;\n";
plugin += " lv2:index " + String(port_index) + “;\n”;
plugin += " lv2:symbol “midi_in”;\n";
plugin += " lv2:name “MIDI Input”;\n";
plugin += " ] ;\n";
port_index++;
#endif
#if JucePlugin_ProducesMidiOutput
plugin += " lv2:port [\n";
plugin += " a lv2:OutputPort, lv2ev:EventPort;\n";
plugin += " lv2ev:supportsEvent http://lv2plug.in/ns/ext/midi#MidiEvent ;\n";
plugin += " lv2:index " + String(port_index) + “;\n”;
plugin += " lv2:symbol “midi_out”;\n";
plugin += " lv2:name “MIDI Output”;\n";
plugin += " ] ;\n";
port_index++;
#endif
#if JucePlugin_WantsMidiInput || JucePlugin_ProducesMidiOutput
plugin += “\n”;
#endif
for (i=0; i<JucePlugin_MaxNumInputChannels; i++) {
if (i == 0) {
plugin += " lv2:port [\n";
} else {
plugin += " [\n";
}
plugin += " a lv2:InputPort, lv2:AudioPort;\n";
//plugin += " lv2:datatype lv2:float;\n";
plugin += " lv2:index " + String(port_index) + ";\n";
plugin += " lv2:symbol \"audio_in_" + String(i) + "\";\n";
plugin += " lv2:name \"Audio Input " + String(i) + "\";\n";
if (i == JucePlugin_MaxNumInputChannels-1) {
plugin += " ] ;\n";
} else {
plugin += " ],\n";
}
port_index++;
}
for (i=0; i<JucePlugin_MaxNumOutputChannels; i++) {
if (i == 0) {
plugin += " lv2:port [\n";
} else {
plugin += " [\n";
}
plugin += " a lv2:OutputPort, lv2:AudioPort;\n";
//plugin += " lv2:datatype lv2:float;\n";
plugin += " lv2:index " + String(port_index) + ";\n";
plugin += " lv2:symbol \"audio_out_" + String(i) + "\";\n";
plugin += " lv2:name \"Audio Output " + String(i) + "\";\n";
if (i == JucePlugin_MaxNumOutputChannels-1) {
plugin += " ] ;\n";
} else {
plugin += " ],\n";
}
port_index++;
}
#if JucePlugin_MaxNumInputChannels > 0 || JucePlugin_MaxNumOutputChannels > 0
plugin += “\n”;
#endif
for (i=0; i < JucePlugin->getNumParameters(); i++) {
if (i == 0) {
plugin += " lv2:port [\n";
} else {
plugin += " [\n";
}
plugin += " a lv2:InputPort;\n";
plugin += " a lv2:ControlPort;\n";
//plugin += " lv2:datatype lv2:float;\n";
plugin += " lv2:index " + String(port_index) + ";\n";
plugin += " lv2:symbol " + name_to_symbol(JucePlugin->getParameterName(i)) + "\";\n";
plugin += " lv2:name " + JucePlugin->getParameterName(i) + ";\n";
plugin += " lv2:default " + float_to_string(JucePlugin->getParameter(i)) + ";\n";
plugin += " lv2:minimum 0.0;\n";
plugin += " lv2:maximum 1.0;\n";
// TODO - units
if (i == JucePlugin_MaxNumOutputChannels-1) {
plugin += " ] ;\n";
} else {
plugin += " ],\n";
}
port_index++;
}
if (JucePlugin->getNumParameters() > 0) {
plugin += "\n";
}
plugin += " doap:name \"" + String(JucePlugin_Name) + "\" ;\n";
plugin += " doap:creator \"" + String(JucePlugin_Manufacturer) + "\" .\n";
delete JucePlugin;
return plugin;
}
int main(int argc, char *argv[])
{
String URI = get_uri();
String Binary = get_binary_name();
String BinaryTTL = Binary + “.ttl”;
std::cout << "Writing manifest.ttl...";
std::fstream manifest("manifest.ttl", std::ios::out);
manifest << get_manifest_ttl(URI, Binary) << std::endl;
manifest.close();
std::cout << " done!" << std::endl;
std::cout << "Writing " << BinaryTTL;
std::fstream plugin(BinaryTTL.toUTF8(), std::ios::out);
plugin << get_plugin_ttl(URI, Binary) << std::endl;
plugin.close();
std::cout << " done!" << std::endl;
return 0;
}
[/code]
I’ll keep working on this and keep you guys posted.