LV2: HACK: forbid unloading the .so until lifecycle issues get resolved.
[calf.git] / src / makerdf.cpp
blob117a7e844c11e1fd29fdb82dc678dd07da541cde
1 /* Calf DSP Library
2 * RDF file generator for LV2 plugins.
3 * Copyright (C) 2007-2011 Krzysztof Foltman and others.
4 * See AUTHORS file for a complete list.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 #include <calf/giface.h>
22 #include <calf/preset.h>
23 #include <calf/utils.h>
24 #if USE_LV2
25 #include <lv2.h>
26 #include <calf/lv2_state.h>
27 #include <calf/lv2_urid.h>
28 #endif
29 #include <getopt.h>
30 #include <string.h>
31 #include <set>
33 using namespace std;
34 using namespace calf_utils;
35 using namespace calf_plugins;
37 static struct option long_options[] = {
38 {"help", 0, 0, 'h'},
39 {"version", 0, 0, 'v'},
40 {"mode", 1, 0, 'm'},
41 {"path", 1, 0, 'p'},
42 #if USE_LV2
43 {"data-dir", 1, 0, 'd'},
44 #endif
45 {0,0,0,0},
48 static FILE *open_and_check(const std::string &filename)
50 FILE *f = fopen(filename.c_str(), "w");
51 if (!f)
53 fprintf(stderr, "Cannot write file '%s': %s\n", filename.c_str(), strerror(errno));
54 exit(1);
56 return f;
59 #if USE_LV2
60 static void add_port(string &ports, const char *symbol, const char *name, const char *direction, int pidx, const char *type = "lv2:AudioPort", bool optional = false)
62 stringstream ss;
63 const char *ind = " ";
66 if (ports != "") ports += " , ";
67 ss << "[\n";
68 if (direction) ss << ind << "a lv2:" << direction << "Port ;\n";
69 ss << ind << "a " << type << " ;\n";
70 ss << ind << "lv2:index " << pidx << " ;\n";
71 ss << ind << "lv2:symbol \"" << symbol << "\" ;\n";
72 ss << ind << "lv2:name \"" << name << "\" ;\n";
73 if (optional)
74 ss << ind << "lv2:portProperty lv2:connectionOptional ;\n";
75 if (!strcmp(type, "atom:AtomPort")) {
76 ss << ind << "atom:bufferType atom:Sequence ;\n"
77 << ind << "atom:supports lv2midi:MidiEvent ;" << endl;
79 if (!strcmp(std::string(symbol, 0, 4).c_str(), "in_l"))
80 ss << ind << "lv2:designation pg:left ;\n"
81 << ind << "pg:group :in ;" << endl;
82 else
83 if (!strcmp(std::string(symbol, 0, 4).c_str(), "in_r"))
84 ss << ind << "lv2:designation pg:right ;\n"
85 << ind << "pg:group :in ;" << endl;
86 else
87 if (!strcmp(std::string(symbol, 0, 5).c_str(), "out_l"))
88 ss << ind << "lv2:designation pg:left ;\n"
89 << ind << "pg:group :out ;" << endl;
90 else
91 if (!strcmp(std::string(symbol, 0, 5).c_str(), "out_r"))
92 ss << ind << "lv2:designation pg:right ;\n"
93 << ind << "pg:group :out ;" << endl;
94 ss << " ]";
95 ports += ss.str();
98 static const char *units[] = {
99 "ue:db",
100 "ue:coef",
101 "ue:hz",
102 "ue:s",
103 "ue:ms",
104 "ue:cent",
105 "ue:semitone12TET",
106 "ue:bpm",
107 "ue:degree",
108 "ue:midiNote",
109 NULL // rotations per minute
112 //////////////// To all haters: calm down, I'll rewrite it to use the new interface one day
114 static bool add_ctl_port(string &ports, const parameter_properties &pp, int pidx, const plugin_metadata_iface *pmi, int param)
116 stringstream ss;
117 const char *ind = " ";
119 parameter_flags type = (parameter_flags)(pp.flags & PF_TYPEMASK);
120 uint8_t unit = (pp.flags & PF_UNITMASK) >> 24;
122 if (ports != "") ports += " , ";
123 ss << "[\n";
124 if (pp.flags & PF_PROP_OUTPUT)
125 ss << ind << "a lv2:OutputPort ;\n";
126 else
127 ss << ind << "a lv2:InputPort ;\n";
128 ss << ind << "a lv2:ControlPort ;\n";
129 ss << ind << "lv2:index " << pidx << " ;\n";
130 ss << ind << "lv2:symbol \"" << pp.short_name << "\" ;\n";
131 ss << ind << "lv2:name \"" << pp.name << "\" ;\n";
132 if ((pp.flags & PF_CTLMASK) == PF_CTL_BUTTON)
133 ss << ind << "lv2:portProperty epp:trigger ;\n";
134 if (!(pp.flags & PF_PROP_NOBOUNDS))
135 ss << ind << "lv2:portProperty epp:hasStrictBounds ;\n";
136 if (pp.flags & PF_PROP_EXPENSIVE)
137 ss << ind << "lv2:portProperty epp:expensive ;\n";
138 if (pp.flags & PF_PROP_OPTIONAL)
139 ss << ind << "lv2:portProperty lv2:connectionOptional ;\n";
140 if (pmi->is_noisy(param))
141 ss << ind << "lv2:portProperty epp:causesArtifacts ;\n";
142 if (!pmi->is_cv(param))
143 ss << ind << "lv2:portProperty epp:notAutomatic ;\n";
144 if (pp.flags & PF_PROP_OUTPUT_GAIN)
145 ss << ind << "lv2:designation param:gain ;\n";
146 if (type == PF_BOOL)
147 ss << ind << "lv2:portProperty lv2:toggled ;\n";
148 else if (type == PF_ENUM)
150 ss << ind << "lv2:portProperty lv2:integer ;\n";
151 ss << ind << "lv2:portProperty lv2:enumeration ;\n";
152 for (int i = (int)pp.min; i <= (int)pp.max; i++)
153 ss << ind << "lv2:scalePoint [ rdfs:label \"" << pp.choices[i - (int)pp.min] << "\"; rdf:value " << i <<" ] ;\n";
155 else if (type == PF_INT || type == PF_ENUM_MULTI)
156 ss << ind << "lv2:portProperty lv2:integer ;\n";
157 else if ((pp.flags & PF_SCALEMASK) == PF_SCALE_LOG)
158 ss << ind << "lv2:portProperty epp:logarithmic ;\n";
159 ss << showpoint;
160 if (!(pp.flags & PF_PROP_OUTPUT))
161 ss << ind << "lv2:default " << pp.def_value << " ;\n";
162 ss << ind << "lv2:minimum " << pp.min << " ;\n";
163 ss << ind << "lv2:maximum " << pp.max << " ;\n";
164 // XXX This value does not seem to match the definition of the property
165 //if (pp.step > 1)
166 // ss << ind << "epp:rangeSteps " << pp.step << " ;\n";
167 if (((pp.flags & PF_SCALEMASK) != PF_SCALE_GAIN) && unit > 0 && unit < (sizeof(units) / sizeof(char *)) && units[unit - 1] != NULL)
168 ss << ind << "ue:unit " << units[unit - 1] << " ;\n";
170 // for now I assume that the only tempo passed is the tempo the plugin should operate with
171 // this may change as more complex plugins are added
173 if ((pp.flags & PF_SYNC_BPM))
174 ss << ind << "lv2:designation <http://lv2plug.in/ns/ext/time#beatsPerMinute> ;\n";
176 ss << " ]";
177 ports += ss.str();
178 return true;
181 void make_ttl(string path_prefix, const string *data_dir)
183 if (path_prefix.empty())
185 fprintf(stderr, "Path parameter is required for TTL mode\n");
186 exit(1);
188 string header;
190 header =
191 "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n"
192 "@prefix lv2: <http://lv2plug.in/ns/lv2core#> .\n"
193 "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n"
194 "@prefix dct: <http://purl.org/dc/terms/> .\n"
195 "@prefix doap: <http://usefulinc.com/ns/doap#> .\n"
196 "@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> .\n"
197 "@prefix atom: <http://lv2plug.in/ns/ext/atom#> .\n"
198 "@prefix lv2midi: <http://lv2plug.in/ns/ext/midi#> .\n"
199 "@prefix lv2ctx: <http://lv2plug.in/ns/dev/contexts#> .\n"
200 "@prefix strport: <http://lv2plug.in/ns/dev/string-port#> .\n"
201 "@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .\n"
202 "@prefix ue: <http://lv2plug.in/ns/extensions/units#> .\n"
203 "@prefix epp: <http://lv2plug.in/ns/ext/port-props#> .\n"
204 "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n"
205 "@prefix param: <http://lv2plug.in/ns/ext/parameters#> .\n"
207 "\n"
210 printf("Retrieving plugin list\n");
211 fflush(stdout);
212 const plugin_registry::plugin_vector &plugins = plugin_registry::instance().get_all();
214 map<string, string> classes;
216 printf("Creating a list of plugin classes\n");
217 fflush(stdout);
218 const char *ptypes[] = {
219 "Flanger", "Reverb", "Generator", "Instrument", "Oscillator",
220 "Utility", "Converter", "Analyser", "Mixer", "Simulator",
221 "Delay", "Modulator", "Phaser", "Chorus", "Filter",
222 "Lowpass", "Highpass", "Bandpass", "Comb", "Allpass",
223 "Amplifier", "Distortion", "Waveshaper", "Dynamics", "Compressor",
224 "Expander", "Limiter", "Gate", "EQ", "ParaEQ", "MultiEQ", "Spatial",
225 "Spectral", "Pitch", "Envelope", "Function", "Constant",
226 NULL
229 for(const char **p = ptypes; *p; p++) {
230 string name = string(*p) + "Plugin";
231 classes[name] = "lv2:" + name;
233 classes["SynthesizerPlugin"] = "lv2:InstrumentPlugin";
235 string plugin_uri_prefix = "http://calf.sourceforge.net/plugins/";
237 string gui_header;
239 #if USE_LV2_GUI
240 string gtkgui_uri = "<http://calf.sourceforge.net/plugins/gui/gtk2-gui>";
241 gui_header = gtkgui_uri + "\n"
242 " a uiext:GtkUI ;\n"
243 " lv2:extensionData uiext:showInterface ;\n"
244 " lv2:requiredFeature uiext:makeResident ;\n"
245 " uiext:binary <calflv2gui.so> .\n"
246 "\n"
248 #endif
250 map<string, pair<string, string> > id_to_info;
252 for (unsigned int i = 0; i < plugins.size(); i++) {
253 const plugin_metadata_iface *pi = plugins[i];
254 const ladspa_plugin_info &lpi = pi->get_plugin_info();
255 printf("Generating a .ttl file for plugin '%s'\n", lpi.label);
256 fflush(stdout);
257 string unquoted_uri = plugin_uri_prefix + string(lpi.label);
258 string uri = string("<" + unquoted_uri + ">");
259 id_to_info[pi->get_id()] = make_pair(lpi.label, uri);
260 string ttl;
261 ttl = "@prefix : <" + unquoted_uri + "#> .\n" + header + gui_header;
263 #if USE_LV2_GUI
264 for (int j = 0; j < pi->get_param_count(); j++)
266 const parameter_properties &props = *pi->get_param_props(j);
267 if (props.flags & PF_PROP_OUTPUT)
269 string portnot = " uiext:portNotification [\n uiext:plugin " + uri + " ;\n uiext:portIndex " + i2s(j) + "\n] .\n\n";
270 ttl += gtkgui_uri + portnot;
273 #endif
275 if(pi->get_input_count() == 1) {
276 ttl += ":in a pg:MonoGroup , pg:InputGroup ;\n"
277 " lv2:symbol \"in\" ;\n"
278 " rdfs:label \"Input\" .\n\n";
279 } else if(pi->get_input_count() >= 2) {
280 ttl += ":in a pg:StereoGroup , pg:InputGroup ;\n"
281 " lv2:symbol \"in\" ;\n"
282 " rdfs:label \"Input\" .\n\n";
284 if(pi->get_output_count() >= 2) {
285 ttl += ":out a pg:StereoGroup , pg:OutputGroup ;\n"
286 " lv2:symbol \"out\" ;\n"
287 " rdfs:label \"Output\" .\n\n";
290 ttl += uri + " a lv2:Plugin ;\n";
292 if (classes.count(lpi.plugin_type))
293 ttl += " a " + classes[lpi.plugin_type]+" ;\n";
296 ttl += " doap:name \""+string(lpi.name)+"\" ;\n";
297 ttl += " doap:maintainer [ foaf:name \""+string(lpi.maker)+"\" ; ] ;\n";
299 #if USE_LV2_GUI
300 ttl += " uiext:ui <http://calf.sourceforge.net/plugins/gui/gtk2-gui> ;\n";
301 ttl += " lv2:optionalFeature <http://lv2plug.in/ns/ext/instance-access> ;\n";
302 ttl += " lv2:optionalFeature <http://lv2plug.in/ns/ext/data-access> ;\n";
303 #endif
305 ttl += " doap:license <http://usefulinc.com/doap/licenses/lgpl> ;\n";
306 ttl += " dct:replaces <urn:ladspa:" + i2s(lpi.unique_id) + "> ;\n";
307 // XXXKF not really optional for now, to be honest
308 ttl += " lv2:optionalFeature epp:supportsStrictBounds ;\n";
309 if (pi->is_rt_capable())
310 ttl += " lv2:optionalFeature lv2:hardRTCapable ;\n";
311 if (pi->get_midi())
313 if (pi->requires_midi()) {
314 ttl += " lv2:requiredFeature <" LV2_URID_MAP_URI "> ;\n";
316 else {
317 ttl += " lv2:optionalFeature <" LV2_URID_MAP_URI "> ;\n";
321 vector<string> configure_keys;
322 pi->get_configure_vars(configure_keys);
323 if (!configure_keys.empty())
325 ttl += " lv2:extensionData <" LV2_STATE__interface "> ;\n";
328 if(pi->get_input_count() >= 1) {
329 ttl += " pg:mainInput :in ;\n";
331 if(pi->get_output_count() >= 2) {
332 ttl += " pg:mainOutput :out ;\n";
335 string ports = "";
336 int pn = 0;
337 const char *in_symbols[] = { "in_l", "in_r", "sidechain", "sidechain2" };
338 const char *in_names[] = { "In L", "In R", "Sidechain", "Sidechain 2" };
339 const char *out_symbols[] = { "out_l", "out_r", "out_l_2", "out_r_2", "out_l_3", "out_r_3", "out_l_4", "out_r_4" };
340 const char *out_names[] = { "Out L", "Out R", "Out L 2", "Out R 2", "Out L 3", "Out R 3", "Out L 4", "Out R 4" };
341 for (int i = 0; i < pi->get_input_count(); i++)
342 if(i <= pi->get_input_count() - pi->get_inputs_optional() - 1 && !(i == 1 && pi->get_simulate_stereo_input()))
343 add_port(ports, in_symbols[i], in_names[i], "Input", pn++);
344 else
345 add_port(ports, in_symbols[i], in_names[i], "Input", pn++, "lv2:AudioPort", true);
346 for (int i = 0; i < pi->get_output_count(); i++)
347 if(i <= pi->get_output_count() - pi->get_outputs_optional() - 1)
348 add_port(ports, out_symbols[i], out_names[i], "Output", pn++);
349 else
350 add_port(ports, out_symbols[i], out_names[i], "Output", pn++, "lv2:AudioPort", true);
351 for (int i = 0; i < pi->get_param_count(); i++)
352 if (add_ctl_port(ports, *pi->get_param_props(i), pn, pi, i))
353 pn++;
354 if (pi->get_midi()) {
355 add_port(ports, "midi_in", "MIDI", "Input", pn++, "atom:AtomPort", true);
357 if (!ports.empty())
358 ttl += " lv2:port " + ports + "\n";
359 ttl += ".\n\n";
361 FILE *f = open_and_check(path_prefix+string(lpi.label)+".ttl");
362 fprintf(f, "%s\n", ttl.c_str());
363 fclose(f);
365 // Generate a manifest
366 printf("Writing presets\n");
367 fflush(stdout);
369 // Prefixes for the preset TTL
370 string presets_ttl_head =
371 "@prefix lv2: <http://lv2plug.in/ns/lv2core#> .\n"
372 "@prefix lv2p: <http://lv2plug.in/ns/ext/presets#> .\n"
373 "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n"
374 "@prefix dct: <http://purl.org/dc/terms/> .\n"
375 "\n"
378 // Prefixes for the manifest TTL
379 string ttl = presets_ttl_head;
381 calf_plugins::get_builtin_presets().load_defaults(true, data_dir);
382 calf_plugins::preset_vector &factory_presets = calf_plugins::get_builtin_presets().presets;
384 ttl += "\n";
386 // We'll collect TTL for presets here, maps plugin label to TTL generated so far
387 map<string, string> preset_data;
389 for (unsigned int i = 0; i < factory_presets.size(); i++)
391 plugin_preset &pr = factory_presets[i];
392 map<string, pair<string, string> >::iterator ilm = id_to_info.find(pr.plugin);
393 if (ilm == id_to_info.end())
394 continue;
396 string presets_ttl;
397 // if this is the first preset, add a header
398 if (!preset_data.count(ilm->second.first))
399 presets_ttl = presets_ttl_head;
401 string uri = "<http://calf.sourceforge.net/factory_presets#"
402 + pr.plugin + "_" + pr.get_safe_name()
403 + ">";
404 ttl += ilm->second.second + " lv2p:hasPreset\n " + uri + " .\n";
406 presets_ttl += uri +
407 " a lv2p:Preset ;\n"
408 " rdfs:label \"" + pr.name + "\" ;\n"
409 " lv2:appliesTo " + ilm->second.second + " ;\n"
410 " lv2:port \n"
413 unsigned int count = min(pr.param_names.size(), pr.values.size());
414 for (unsigned int j = 0; j < count; j++)
416 presets_ttl += " [ lv2:symbol \"" + pr.param_names[j] + "\" ; lv2p:value " + ff2s(pr.values[j]) + " ] ";
417 if (j < count - 1)
418 presets_ttl += ',';
419 presets_ttl += '\n';
421 presets_ttl += ".\n\n";
423 preset_data[ilm->second.first] += presets_ttl;
425 for (map<string, string>::iterator i = preset_data.begin(); i != preset_data.end(); i++)
427 FILE *f = open_and_check(path_prefix + "presets-" + i->first + ".ttl");
428 fprintf(f, "%s\n", i->second.c_str());
429 fclose(f);
432 printf("Generating a manifest file\n");
433 fflush(stdout);
434 for (unsigned int i = 0; i < plugins.size(); i++)
436 string label = plugins[i]->get_plugin_info().label;
437 ttl += string("<" + plugin_uri_prefix)
438 + string(plugins[i]->get_plugin_info().label)
439 + "> a lv2:Plugin ;\n dct:replaces <urn:ladspa:"
440 + i2s(plugins[i]->get_plugin_info().unique_id) + "> ;\n "
441 + "lv2:binary <calf.so> ; rdfs:seeAlso <" + label + ".ttl> ";
442 if (preset_data.count(label))
443 ttl += ", <presets-" + label + ".ttl>";
444 ttl += ".\n";
447 FILE *f = open_and_check(path_prefix+"manifest.ttl");
448 fprintf(f, "%s\n", ttl.c_str());
449 fclose(f);
453 #else
454 void make_ttl(string tmp)
456 fprintf(stderr, "LV2 not supported.\n");
459 #endif
461 void make_gui(string path_prefix)
463 if (path_prefix.empty())
465 fprintf(stderr, "Path parameter is required for GUI mode\n");
466 exit(1);
468 const plugin_registry::plugin_vector &plugins = plugin_registry::instance().get_all();
469 path_prefix += "/gui-";
470 for (unsigned int i = 0; i < plugins.size(); i++)
472 const plugin_metadata_iface *pi = plugins[i];
474 // check for empty item after all the params
475 assert(pi->get_id());
476 assert(pi->get_param_props(pi->get_param_count())->short_name == NULL);
477 assert(pi->get_param_props(pi->get_param_count())->name == NULL);
479 stringstream xml;
480 int graphs = 0;
481 for (int j = 0; j < pi->get_param_count(); j++)
483 const parameter_properties &props = *pi->get_param_props(j);
484 if (props.flags & PF_PROP_GRAPH)
485 graphs++;
487 xml << "<table rows=\"" << pi->get_param_count() << "\" cols=\"" << (graphs ? "4" : "3") << "\">\n";
488 for (int j = 0; j < pi->get_param_count(); j++)
490 if (j)
491 xml << "\n <!-- -->\n\n";
492 const parameter_properties &props = *pi->get_param_props(j);
493 if (!props.short_name)
495 fprintf(stderr, "Plugin %s is missing short name for parameter %d\n", pi->get_name(), j);
496 exit(1);
498 string expand_x = "expand-x=\"1\" ";
499 string fill_x = "fill-x=\"1\" ";
500 string shrink_x = "shrink-x=\"1\" ";
501 string pad_x = "pad-x=\"10\" ";
502 string attach_x = "attach-x=\"1\" attach-w=\"2\" ";
503 string attach_y = "attach-y=\"" + i2s(j) + "\" ";
504 string param = "param=\"" + string(props.short_name) + "\" ";
505 string label = " <align attach-x=\"0\" " + attach_y + " " + " align-x=\"1\"><label " + param + " /></align>\n";
506 string value = " <value " + param + " " + "attach-x=\"2\" " + attach_y + pad_x + "/>\n";
507 string attach_xv = "attach-x=\"1\" attach-w=\"1\" ";
508 string ctl;
509 if ((props.flags & PF_TYPEMASK) == PF_ENUM &&
510 (props.flags & PF_CTLMASK) == PF_CTL_COMBO)
512 ctl = " <combo " + attach_x + attach_y + param + expand_x + pad_x + " />\n";
514 else if ((props.flags & PF_TYPEMASK) == PF_BOOL &&
515 (props.flags & PF_CTLMASK) == PF_CTL_TOGGLE)
517 ctl = " <align " + attach_x + attach_y + expand_x + fill_x + pad_x + "><toggle " + param + "/></align>\n";
519 else if ((props.flags & PF_TYPEMASK) == PF_BOOL &&
520 (props.flags & PF_CTLMASK) == PF_CTL_BUTTON)
522 ctl = " <button attach-x=\"0\" attach-w=\"3\" " + expand_x + attach_y + param + pad_x + "/>\n";
523 label.clear();
525 else if ((props.flags & PF_TYPEMASK) == PF_BOOL &&
526 (props.flags & PF_CTLMASK) == PF_CTL_LED)
528 ctl = " <led " + attach_x + attach_y + param + shrink_x + pad_x + " />\n";
530 else if ((props.flags & PF_CTLMASK) == PF_CTL_METER)
532 if (props.flags & PF_CTLO_LABEL) {
533 attach_x = attach_xv;
534 pad_x.clear();
536 if (props.flags & PF_CTLO_REVERSE)
537 ctl = " <vumeter " + attach_x + attach_y + expand_x + fill_x + param + pad_x + "mode=\"2\"/>\n";
538 else
539 ctl = " <vumeter " + attach_x + attach_y + expand_x + fill_x + param + pad_x + "/>\n";
540 if (props.flags & PF_CTLO_LABEL)
541 ctl += value;
543 else if ((props.flags & PF_CTLMASK) != PF_CTL_FADER)
545 if ((props.flags & PF_UNITMASK) == PF_UNIT_DEG)
546 ctl = " <knob " + attach_xv + attach_y + shrink_x + param + " type=\"3\"/>\n";
547 else
548 ctl = " <knob " + attach_xv + attach_y + shrink_x + param + " />\n";
549 ctl += value;
551 else
553 ctl += " <hscale " + param + " " + attach_x + attach_y + " pad-x=\"10\"/>";
555 if (!label.empty())
556 xml << label;
557 if (!ctl.empty())
558 xml << ctl;
560 if (graphs)
562 xml << " <if cond=\"directlink\">" << endl;
563 xml << " <vbox expand-x=\"1\" fill-x=\"1\" attach-x=\"3\" attach-y=\"0\" attach-h=\"" << pi->get_param_count() << "\">" << endl;
564 for (int j = 0; j < pi->get_param_count(); j++)
566 const parameter_properties &props = *pi->get_param_props(j);
567 if (props.flags & PF_PROP_GRAPH)
569 xml << " <line-graph refresh=\"1\" width=\"160\" param=\"" << props.short_name << "\"/>\n" << endl;
572 xml << " </vbox>" << endl;
573 xml << " </if>" << endl;
575 xml << "</table>\n";
576 FILE *f = open_and_check(path_prefix+string(pi->get_id())+".xml");
577 fprintf(f, "%s\n", xml.str().c_str());
578 fclose(f);
582 int main(int argc, char *argv[])
584 string mode = "rdf";
585 string path_prefix;
586 #if USE_LV2
587 string pkglibdir_path;
588 #endif
589 while(1) {
590 int option_index;
591 int c = getopt_long(argc, argv, "hvm:p:"
592 #if USE_LV2
593 "d:"
594 #endif
595 , long_options, &option_index);
596 if (c == -1)
597 break;
598 switch(c) {
599 case 'h':
600 case '?':
601 printf("LV2 TTL / XML GUI generator for Calf plugin pack\nSyntax: %s [--help] [--version] [--mode rdf|ttl|gui] [--path <path>]\n", argv[0]);
602 return 0;
603 case 'v':
604 printf("%s\n", PACKAGE_STRING);
605 return 0;
606 case 'm':
607 mode = optarg;
608 if (mode != "rdf" && mode != "ttl" && mode != "gui") {
609 fprintf(stderr, "calfmakerdf: Invalid mode %s\n", optarg);
610 return 1;
612 break;
613 #if USE_LV2
614 case 'd':
615 pkglibdir_path = optarg;
616 if (pkglibdir_path.empty())
618 fprintf(stderr, "calfmakerdf: Data directory must not be empty\n");
619 exit(1);
621 if (pkglibdir_path[path_prefix.length() - 1] != '/')
622 pkglibdir_path += '/';
623 break;
624 #endif
625 case 'p':
626 path_prefix = optarg;
627 if (path_prefix.empty())
629 fprintf(stderr, "calfmakerdf: Path prefix must not be empty\n");
630 exit(1);
632 if (path_prefix[path_prefix.length() - 1] != '/')
633 path_prefix += '/';
634 break;
637 #if USE_LV2
638 if (mode == "ttl")
639 make_ttl(path_prefix, !pkglibdir_path.empty() ? &pkglibdir_path : NULL);
640 else
641 #endif
642 if (mode == "gui")
643 make_gui(path_prefix);
644 else
646 fprintf(stderr, "calfmakerdf: Mode '%s' unsupported in this version\n", mode.c_str());
647 return 1;
649 return 0;