1 /*
2 * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
3 * (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
4 *
5 * This file is part of lsp-plugins
6 * Created on: 15 июл. 2019 г.
7 *
8 * lsp-plugins is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * any later version.
12 *
13 * lsp-plugins is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22 #include <stdio.h>
23 #include <string.h>
24
25 #include <core/types.h>
26 #include <core/lib.h>
27
28 #include <metadata/metadata.h>
29 #include <plugins/plugins.h>
30 #include <utils/common.h>
31
32 #include <container/lv2/extensions.h>
33
34 #define LSP_LV2_EMIT_HEADER(count, text) \
35 if (count == 0) \
36 { \
37 fputs(text, out); \
38 fputc(' ', out); \
39 }
40
41 #define LSP_LV2_EMIT_OPTION(count, condition, text) \
42 if (condition) \
43 { \
44 if (count++) \
45 fputs(", ", out); \
46 fputs(text, out); \
47 }
48
49 #define LSP_LV2_EMIT_END(count) \
50 if (count > 0) \
51 { \
52 fprintf(out, " ;\n"); \
53 count = 0; \
54 }
55
56 namespace lsp
57 {
58 enum lv2_requirements
59 {
60 REQ_PATCH = 1 << 0,
61 REQ_STATE = 1 << 1,
62 REQ_LV2UI = 1 << 2,
63 REQ_PORT_GROUPS = 1 << 3,
64 REQ_WORKER = 1 << 4,
65 REQ_MIDI_IN = 1 << 5,
66 REQ_MIDI_OUT = 1 << 6,
67 REQ_PATCH_WR = 1 << 7,
68 REQ_INSTANCE = 1 << 8,
69 REQ_TIME = 1 << 9,
70 REQ_IDISPLAY = 1 << 10,
71 REQ_OSC_IN = 1 << 11,
72 REQ_OSC_OUT = 1 << 12,
73 REQ_MAP_PATH = 1 << 13,
74
75 REQ_PATH_MASK = REQ_PATCH | REQ_STATE | REQ_MAP_PATH | REQ_WORKER | REQ_PATCH_WR,
76 REQ_MIDI = REQ_MIDI_IN | REQ_MIDI_OUT
77 };
78
79 typedef struct lv2_plugin_group_t
80 {
81 int id;
82 const char *name;
83 } lv2_plugin_group_t;
84
85 const lv2_plugin_group_t lv2_plugin_groups[] =
86 {
87 { C_DELAY, "DelayPlugin" },
88 { C_REVERB, "ReverbPlugin" },
89 { C_DISTORTION, "DistortionPlugin" },
90 { C_WAVESHAPER, "WaveshaperPlugin" },
91 { C_DYNAMICS, "DynamicsPlugin" },
92 { C_AMPLIFIER, "AmplifierPlugin" },
93 { C_COMPRESSOR, "CompressorPlugin" },
94 { C_ENVELOPE, "EnvelopePlugin" },
95 { C_EXPANDER, "ExpanderPlugin" },
96 { C_GATE, "GatePlugin" },
97 { C_LIMITER, "LimiterPlugin" },
98 { C_FILTER, "FilterPlugin" },
99 { C_ALLPASS, "AllpassPlugin" },
100 { C_BANDPASS, "BandpassPlugin" },
101 { C_COMB, "CombPlugin" },
102 { C_EQ, "EQPlugin" },
103 { C_MULTI_EQ, "MultiEQPlugin" },
104 { C_PARA_EQ, "ParaEQPlugin" },
105 { C_HIGHPASS, "HighpassPlugin" },
106 { C_LOWPASS, "LowpassPlugin" },
107 { C_GENERATOR, "GeneratorPlugin" },
108 { C_CONSTANT, "ConstantPlugin" },
109 // InstrumentPlugin has extra processing.
110 // { C_INSTRUMENT, "InstrumentPlugin" },
111 { C_OSCILLATOR, "OscillatorPlugin" },
112 { C_MODULATOR, "ModulatorPlugin" },
113 { C_CHORUS, "ChorusPlugin" },
114 { C_FLANGER, "FlangerPlugin" },
115 { C_PHASER, "PhaserPlugin" },
116 { C_SIMULATOR, "SimulatorPlugin" },
117 { C_SPATIAL, "SpatialPlugin" },
118 { C_SPECTRAL, "SpectralPlugin" },
119 { C_PITCH, "PitchPlugin" },
120 { C_UTILITY, "UtilityPlugin" },
121 { C_ANALYSER, "AnalyserPlugin" },
122 { C_CONVERTER, "ConverterPlugin" },
123 { C_FUNCTION, "FunctionPlugin" },
124 { C_MIXER, "MixerPlugin" },
125 { -1, NULL }
126 };
127
128 typedef struct lv2_plugin_unit_t
129 {
130 int id;
131 const char *name;
132 const char *label;
133 const char *render;
134 } lv2_plugin_unit_t;
135
136 const lv2_plugin_unit_t lv2_plugin_units[] =
137 {
138 { U_PERCENT, "pc" },
139 { U_MM, "mm" },
140 { U_CM, "cm" },
141 { U_M, "m" },
142 { U_INCH, "inch" },
143 { U_KM, "km" },
144 { U_HZ, "hz" },
145 { U_KHZ, "khz" },
146 { U_MHZ, "mhz" },
147 { U_BPM, "bpm" },
148 { U_CENT, "cent" },
149 { U_BAR, "bar" },
150 { U_BEAT, "beat" },
151 { U_SEC, "s" },
152 { U_MSEC, "ms" },
153 { U_DB, "db" },
154 { U_MIN, "min" },
155 { U_DEG, "degree" },
156 { U_OCTAVES, "oct" },
157 { U_SEMITONES, "semitone12TET" },
158
159 { U_SAMPLES, NULL, "samples", "%.0f" },
160 { U_GAIN_AMP, NULL, "gain", "%.8f" },
161 { U_GAIN_POW, NULL, "gain", "%.8f" },
162
163 { U_DEG_CEL, NULL, "degrees Celsium", "%.2f" },
164 { U_DEG_FAR, NULL, "degrees Fahrenheit", "%.2f" },
165 { U_DEG_K, NULL, "degrees Kelvin", "%.2f" },
166 { U_DEG_R, NULL, "degrees Rankine", "%.2f" },
167
168 { U_BYTES, NULL, "Bytes", "%.0f" },
169 { U_KBYTES, NULL, "Kilobytes", "%.2f" },
170 { U_MBYTES, NULL, "Megabytes", "%.2f" },
171 { U_GBYTES, NULL, "Gigabytes", "%.2f" },
172 { U_TBYTES, NULL, "Terabytes", "%.2f" },
173
174 { -1, NULL }
175 };
176
is_instrument_plugin(const plugin_metadata_t & m)177 bool is_instrument_plugin(const plugin_metadata_t &m)
178 {
179 for (const int *c = m.classes; ((c != NULL) && ((*c) >= 0)); ++c)
180 {
181 if (*c == C_INSTRUMENT)
182 return true;
183 }
184 return false;
185 }
186
print_additional_groups(FILE * out,const plugin_metadata_t & m)187 static void print_additional_groups(FILE *out, const plugin_metadata_t &m)
188 {
189 for (const int *c = m.classes; ((c != NULL) && ((*c) >= 0)); ++c)
190 {
191 const lv2_plugin_group_t *grp = lv2_plugin_groups;
192
193 while ((grp != NULL) && (grp->id >= 0))
194 {
195 if (grp->id == *c)
196 {
197 fprintf(out, ", lv2:%s", grp->name);
198 break;
199 }
200 grp++;
201 }
202 }
203 }
204
print_units(FILE * out,int unit)205 static void print_units(FILE *out, int unit)
206 {
207 const lv2_plugin_unit_t *u = lv2_plugin_units;
208
209 while ((u != NULL) && (u->id >= 0))
210 {
211 if (u->id == unit)
212 {
213 // Check that lv2 contains name
214 if (u->name != NULL)
215 fprintf(out, "\t\tunits:unit units:%s ;\n", u->name);
216 else
217 {
218 const char *symbol = encode_unit(unit);
219
220 // Build custom type
221 if (symbol != NULL)
222 {
223 fprintf(out, "\t\tunits:unit [\n");
224 fprintf(out, "\t\t\ta units:Unit ;\n");
225 fprintf(out, "\t\t\trdfs:label \"%s\" ;\n", u->label);
226 fprintf(out, "\t\t\tunits:symbol \"%s\" ;\n", symbol);
227 fprintf(out, "\t\t\tunits:render \"%s %s\" ;\n", u->render, symbol);
228 fprintf(out, "\t\t] ;\n");
229 }
230 }
231
232 return;
233 }
234 u++;
235 }
236 }
237
gen_plugin_ui_ttl(FILE * out,size_t requirements,const plugin_metadata_t & m,const char * name,const char * ui_uri,const char * uri)238 void gen_plugin_ui_ttl (FILE *out, size_t requirements, const plugin_metadata_t &m, const char *name, const char *ui_uri, const char *uri)
239 {
240 fprintf(out, LSP_PREFIX "_ui:%s\n", name);
241 fprintf(out, "\ta ui:" LSP_LV2UI_CLASS " ;\n");
242 fprintf(out, "\tlv2:minorVersion %d ;\n", int(LSP_VERSION_MINOR(m.version)));
243 fprintf(out, "\tlv2:microVersion %d ;\n", int(LSP_VERSION_MICRO(m.version)));
244 fprintf(out, "\tlv2:requiredFeature urid:map, ui:idleInterface ;\n");
245 {
246 size_t count = 1;
247 // fprintf(out, "\tlv2:optionalFeature ui:parent, ui:resize, ui:noUserResize");
248 fprintf(out, "\tlv2:optionalFeature ui:parent, ui:resize");
249 LSP_LV2_EMIT_OPTION(count, requirements & REQ_INSTANCE, "lv2ext:instance-access");
250 fprintf(out, " ;\n");
251 }
252 fprintf(out, "\tlv2:extensionData ui:idleInterface, ui:resize ;\n");
253 fprintf(out, "\tui:binary <" LSP_ARTIFACT_ID "-lv2.so> ;\n");
254 fprintf(out, "\n");
255
256 size_t ports = 0;
257 size_t port_id = 0;
258
259 for (const port_t *p = m.ports; (p->id != NULL) && (p->name != NULL); ++p)
260 {
261 // Skip virtual ports
262 switch (p->role)
263 {
264 case R_UI_SYNC:
265 case R_MESH:
266 case R_STREAM:
267 case R_FBUFFER:
268 case R_PATH:
269 case R_PORT_SET:
270 case R_MIDI:
271 case R_OSC:
272 continue;
273 case R_AUDIO:
274 port_id++;
275 continue;
276 default:
277 break;
278 }
279
280 fprintf(out, "%s [\n", (ports == 0) ? "\tui:portNotification" : ",");
281 fprintf(out, "\t\tui:plugin " LSP_PREFIX ":%s ;\n", name);
282 fprintf(out, "\t\tui:portIndex %d ;\n", int(port_id));
283
284 switch (p->role)
285 {
286 case R_METER:
287 if (p->flags & F_PEAK)
288 fprintf(out, "\t\tui:protocol ui:peakProtocol ;\n");
289 else
290 fprintf(out, "\t\tui:protocol ui:floatProtocol ;\n");
291 break;
292 default:
293 fprintf(out, "\t\tui:protocol ui:floatProtocol ;\n");
294 break;
295 }
296
297 fprintf(out, "\t] ");
298
299 ports++;
300 port_id++;
301 }
302
303 // Add atom ports for state serialization
304 for (size_t i=0; i<2; ++i)
305 {
306 fprintf(out, "%s [\n", (ports == 0) ? "\tui:portNotification" : " ,");
307 fprintf(out, "\t\tui:plugin " LSP_PREFIX ":%s ;\n", name);
308 fprintf(out, "\t\tui:portIndex %d ;\n", int(port_id));
309 fprintf(out, "\t\tui:protocol atom:eventTransfer ;\n");
310 fprintf(out, "\t\tui:notifyType atom:Sequence ;\n");
311 fprintf(out, "\t]");
312
313 ports++;
314 port_id++;
315 }
316
317 // Add latency report port
318 {
319 const port_t *p = &lv2_latency_port;
320 if ((p->id != NULL) && (p->name != NULL))
321 {
322 fprintf(out, "%s [\n", (ports == 0) ? "\tui:portNotification" : " ,");
323 fprintf(out, "\t\tui:plugin " LSP_PREFIX ":%s ;\n", name);
324 fprintf(out, "\t\tui:portIndex %d ;\n", int(port_id));
325 fprintf(out, "\t\tui:protocol ui:floatProtocol ;\n");
326 fprintf(out, "\t]");
327
328 ports++;
329 port_id++;
330 }
331 }
332
333 // Finish port list
334 fprintf(out, "\n\t.\n\n");
335 }
336
print_port_groups(FILE * out,const port_t * port,const port_group_t * pg)337 static void print_port_groups(FILE *out, const port_t *port, const port_group_t *pg)
338 {
339 // For each group
340 while ((pg != NULL) && (pg->id != NULL))
341 {
342 // Scan list of port
343 for (const port_group_item_t *p = pg->items; p->id != NULL; ++p)
344 {
345 if (!strcmp(p->id, port->id))
346 {
347 fprintf(out, "\t\tpg:group lsp_pg:%s ;\n", pg->id);
348 const char *role = NULL;
349 switch (p->role)
350 {
351 case PGR_CENTER: role = "center"; break;
352 case PGR_CENTER_LEFT: role = "centerLeft"; break;
353 case PGR_CENTER_RIGHT: role = "centerRight"; break;
354 case PGR_LEFT: role = "left"; break;
355 case PGR_LO_FREQ: role = "lowFrequencyEffects"; break;
356 case PGR_REAR_CENTER: role = "rearCenter"; break;
357 case PGR_REAR_LEFT: role = "rearLeft"; break;
358 case PGR_REAR_RIGHT: role = "rearRight"; break;
359 case PGR_RIGHT: role = "right"; break;
360 case PGR_MS_SIDE: role = "side"; break;
361 case PGR_SIDE_LEFT: role = "sideLeft"; break;
362 case PGR_SIDE_RIGHT: role = "sideRight"; break;
363 case PGR_MS_MIDDLE: role = "center"; break;
364 default:
365 break;
366 }
367 if (role != NULL)
368 fprintf(out, "\t\tlv2:designation pg:%s ;\n", role);
369 break;
370 }
371 }
372
373 pg++;
374 }
375 }
376
scan_port_requirements(const port_t * meta)377 static size_t scan_port_requirements(const port_t *meta)
378 {
379 size_t result = REQ_TIME;
380 for (const port_t *p = meta; p->id != NULL; ++p)
381 {
382 switch (p->role)
383 {
384 case R_PATH:
385 result |= REQ_PATH_MASK | REQ_INSTANCE;
386 break;
387 case R_MESH:
388 case R_STREAM:
389 case R_FBUFFER:
390 result |= REQ_INSTANCE;
391 break;
392 case R_MIDI:
393 if (IS_OUT_PORT(p))
394 result |= REQ_MIDI_OUT;
395 else
396 result |= REQ_MIDI_IN;
397 break;
398 case R_OSC:
399 if (IS_OUT_PORT(p))
400 result |= REQ_OSC_OUT;
401 else
402 result |= REQ_OSC_IN;
403 break;
404 case R_PORT_SET:
405 if ((p->members != NULL) && (p->items != NULL))
406 result |= scan_port_requirements(p->members);
407 result |= REQ_INSTANCE;
408 break;
409 default:
410 break;
411 }
412 }
413 return result;
414 }
415
scan_requirements(const plugin_metadata_t & m)416 static size_t scan_requirements(const plugin_metadata_t &m)
417 {
418 size_t result = 0;
419
420 #ifndef LSP_NO_LV2_UI
421 if (m.lv2_uid != NULL)
422 {
423 if (m.ui_resource != NULL)
424 result |= REQ_LV2UI;
425 if (m.extensions & E_INLINE_DISPLAY)
426 result |= REQ_IDISPLAY;
427 }
428 #endif
429
430 result |= scan_port_requirements(m.ports);
431
432 if ((m.port_groups != NULL) && (m.port_groups->id != NULL))
433 result |= REQ_PORT_GROUPS;
434
435 return result;
436 }
437
gen_plugin_ttl(const char * path,const plugin_metadata_t & m,const char * uri)438 void gen_plugin_ttl(const char *path, const plugin_metadata_t &m, const char *uri)
439 {
440 char fname[PATH_MAX];
441 FILE *out = NULL;
442 snprintf(fname, sizeof(fname)-1, "%s/%s.ttl", path, m.lv2_uid);
443 size_t requirements = scan_requirements(m);
444
445 // Generate manifest.ttl
446 if (!(out = fopen(fname, "w+")))
447 return;
448 printf("Writing file %s\n", fname);
449
450 // Output header
451 fprintf(out, "@prefix doap: <http://usefulinc.com/ns/doap#> .\n");
452 fprintf(out, "@prefix dc: <http://purl.org/dc/terms/> .\n");
453 fprintf(out, "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n");
454 fprintf(out, "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n");
455 fprintf(out, "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n");
456 fprintf(out, "@prefix lv2: <" LV2_CORE_PREFIX "> .\n");
457 if (requirements & REQ_INSTANCE)
458 fprintf(out, "@prefix lv2ext: <http://lv2plug.in/ns/ext/> .\n");
459 fprintf(out, "@prefix pp: <" LV2_PORT_PROPS_PREFIX "> .\n");
460 if (requirements & REQ_PORT_GROUPS)
461 fprintf(out, "@prefix pg: <" LV2_PORT_GROUPS_PREFIX "> .\n");
462 if (requirements & REQ_LV2UI)
463 fprintf(out, "@prefix ui: <" LV2_UI_PREFIX "> .\n");
464 fprintf(out, "@prefix units: <" LV2_UNITS_PREFIX "> .\n");
465 fprintf(out, "@prefix atom: <" LV2_ATOM_PREFIX "> .\n");
466 fprintf(out, "@prefix urid: <" LV2_URID_PREFIX "> .\n");
467 fprintf(out, "@prefix opts: <" LV2_OPTIONS_PREFIX "> .\n");
468 fprintf(out, "@prefix work: <" LV2_WORKER_PREFIX "> .\n");
469 fprintf(out, "@prefix rsz: <" LV2_RESIZE_PORT_PREFIX "> .\n");
470 if (requirements & REQ_PATCH)
471 fprintf(out, "@prefix patch: <" LV2_PATCH_PREFIX "> .\n");
472 fprintf(out, "@prefix state: <" LV2_STATE_PREFIX "> .\n");
473 if (requirements & REQ_MIDI)
474 fprintf(out, "@prefix midi: <" LV2_MIDI_PREFIX "> .\n");
475 if (requirements & REQ_TIME)
476 fprintf(out, "@prefix time: <" LV2_TIME_URI "#> .\n");
477 fprintf(out, "@prefix hcid: <" LV2_INLINEDISPLAY_PREFIX "> .\n");
478
479 fprintf(out, "@prefix " LSP_PREFIX ": <" LSP_URI(lv2) "> .\n");
480 if (requirements & REQ_PORT_GROUPS)
481 fprintf(out, "@prefix lsp_pg: <%s%s/port_groups#> .\n", LSP_URI(lv2), m.lv2_uid);
482 if (requirements & REQ_LV2UI)
483 fprintf(out, "@prefix " LSP_PREFIX "_ui: <" LSP_UI_URI(lv2) "> .\n");
484 fprintf(out, "@prefix " LSP_PREFIX "_dev: <" LSP_DEVELOPERS_URI "> .\n");
485 if (requirements & REQ_PATCH)
486 fprintf(out, "@prefix lsp_p: <%s%s/ports#> .\n", LSP_URI(lv2), m.lv2_uid);
487
488 fprintf(out, "\n\n");
489
490 fprintf(out, "hcid:queue_draw\n\ta lv2:Feature\n\t.\n");
491 fprintf(out, "hcid:interface\n\ta lv2:ExtensionData\n\t.\n\n");
492
493 // Output developer and maintainer objects
494 const person_t *dev = m.developer;
495 if ((dev != NULL) && (dev->uid != NULL))
496 {
497 fprintf(out, LSP_PREFIX "_dev:%s\n", dev->uid);
498 fprintf(out, "\ta foaf:Person");
499 if (dev->name != NULL)
500 fprintf(out, " ;\n\tfoaf:name \"%s\"", dev->name);
501 if (dev->nick != NULL)
502 fprintf(out, " ;\n\tfoaf:nick \"%s\"", dev->nick);
503 if (dev->mailbox != NULL)
504 fprintf(out, " ;\n\tfoaf:mbox <mailto:%s>", dev->mailbox);
505 if (dev->homepage != NULL)
506 fprintf(out, " ;\n\tfoaf:homepage <%s#%s>", dev->homepage, dev->uid);
507 fprintf(out, "\n\t.\n\n");
508 }
509
510 fprintf(out, LSP_PREFIX "_dev:lsp\n");
511 fprintf(out, "\ta foaf:Person");
512 fprintf(out, " ;\n\tfoaf:name \"" LSP_ACRONYM " LV2\"");
513 fprintf(out, " ;\n\tfoaf:mbox <mailto:%s>", LSP_PLUGINS_MAILBOX);
514 fprintf(out, " ;\n\tfoaf:homepage <" LSP_BASE_URI "#lsp>");
515 fprintf(out, "\n\t.\n\n");
516
517 // Output port groups
518 const port_group_t *pg_main_in = NULL, *pg_main_out = NULL;
519
520 if (requirements & REQ_PORT_GROUPS)
521 {
522 for (const port_group_t *pg = m.port_groups; (pg != NULL) && (pg->id != NULL); pg++)
523 {
524 const char *grp_type = NULL, *grp_dir = (pg->flags & PGF_OUT) ? "OutputGroup" : "InputGroup";
525 switch (pg->type)
526 {
527 case GRP_1_0: grp_type = "MonoGroup"; break;
528 case GRP_2_0: grp_type = "StereoGroup"; break;
529 case GRP_MS: grp_type = "MidSideGroup"; break;
530 case GRP_3_0: grp_type = "ThreePointZeroGroup"; break;
531 case GRP_4_0: grp_type = "FourPointZeroGroup"; break;
532 case GRP_5_0: grp_type = "FivePointZeroGroup"; break;
533 case GRP_5_1: grp_type = "FivePointOneGroup"; break;
534 case GRP_6_1: grp_type = "SixPointOneGroup"; break;
535 case GRP_7_1: grp_type = "SevenPointOneGroup"; break;
536 case GRP_7_1W: grp_type = "SevenPointOneWideGroup"; break;
537 default:
538 break;
539 }
540
541 fprintf(out, "lsp_pg:%s\n", pg->id);
542 if (grp_type != NULL)
543 fprintf(out, "\ta pg:%s, pg:%s ;\n", grp_type, grp_dir);
544 else
545 fprintf(out, "\ta pg:%s ;\n", grp_dir);
546
547 if (pg->flags & PGF_SIDECHAIN)
548 fprintf(out, "\tpg:sideChainOf lsp_pg:%s ;\n", pg->parent_id);
549 if (pg->flags & PGF_MAIN)
550 {
551 if (pg->flags & PGF_OUT)
552 pg_main_out = pg;
553 else
554 pg_main_in = pg;
555 }
556
557 fprintf(out, "\tlv2:symbol \"%s\" ;\n", pg->id);
558 fprintf(out, "\trdfs:label \"%s\"\n", pg->name);
559 fprintf(out, "\t.\n\n");
560 }
561 }
562
563 // Output special parameters
564 for (const port_t *p = m.ports; p->id != NULL; ++p)
565 {
566 switch (p->role)
567 {
568 case R_PATH:
569 {
570 if (requirements & REQ_PATCH)
571 {
572 fprintf(out, "lsp_p:%s\n", p->id);
573 fprintf(out, "\ta lv2:Parameter ;\n");
574 fprintf(out, "\trdfs:label \"%s\" ;\n", p->name);
575 fprintf(out, "\trdfs:range atom:Path\n");
576 fprintf(out, "\t.\n\n");
577 }
578 break;
579 }
580 default:
581 break;
582 }
583 }
584
585 // Output plugin
586 fprintf(out, LSP_PREFIX ":%s\n", m.lv2_uid);
587 fprintf(out, "\ta %s, doap:Project", (is_instrument_plugin(m)) ? "lv2:InstrumentPlugin" : "lv2:Plugin" );
588 print_additional_groups(out, m);
589 fprintf(out, " ;\n");
590 fprintf(out, "\tdoap:name \"" LSP_ACRONYM " %s\" ;\n", m.description);
591 fprintf(out, "\tlv2:minorVersion %d ;\n", int(LSP_VERSION_MINOR(m.version)));
592 fprintf(out, "\tlv2:microVersion %d ;\n", int(LSP_VERSION_MICRO(m.version)));
593 if ((dev != NULL) && (dev->uid != NULL))
594 fprintf(out, "\tdoap:developer " LSP_PREFIX "_dev:%s ;\n", m.developer->uid);
595 fprintf(out, "\tdoap:maintainer " LSP_PREFIX "_dev:lsp ;\n");
596 fprintf(out, "\tdoap:license <http://usefulinc.com/doap/licenses/lgpl> ;\n");
597 fprintf(out, "\tlv2:binary <" LSP_ARTIFACT_ID "-lv2.so> ;\n");
598 if (requirements & REQ_LV2UI)
599 fprintf(out, "\tui:ui " LSP_PREFIX "_ui:%s ;\n", m.lv2_uid);
600
601 fprintf(out, "\n");
602
603 // Emit required features
604 fprintf(out, "\tlv2:requiredFeature urid:map ;\n");
605
606 // Emit optional features
607 fprintf(out, "\tlv2:optionalFeature lv2:hardRTCapable, hcid:queue_draw, work:schedule, opts:options, state:mapPath ;\n");
608
609 // Emit extension data
610 fprintf(out, "\tlv2:extensionData state:interface, work:interface, hcid:interface ;\n");
611
612 // Different supported options
613 if (requirements & REQ_LV2UI)
614 fprintf(out, "\topts:supportedOption ui:updateRate ;\n\n");
615
616 if (pg_main_in != NULL)
617 fprintf(out, "\tpg:mainInput lsp_pg:%s ;\n", pg_main_in->id);
618 if (pg_main_out != NULL)
619 fprintf(out, "\tpg:mainOutput lsp_pg:%s ;\n", pg_main_out->id);
620
621 // Replacement for LADSPA plugin
622 if (m.ladspa_id > 0)
623 fprintf(out, "\n\tdc:replaces <urn:ladspa:%ld> ;\n", long(m.ladspa_id));
624
625 // Separator
626 fprintf(out, "\n");
627
628 size_t port_id = 0;
629
630 // Output special parameters
631 if (requirements & REQ_PATCH_WR)
632 {
633 size_t count = 0;
634 const port_t *first = NULL;
635 for (const port_t *p = m.ports; p->id != NULL; ++p)
636 {
637 switch (p->role)
638 {
639 case R_PATH:
640 count++;
641 if (first == NULL)
642 first = p;
643 break;
644 default:
645 break;
646 }
647 }
648
649 if (first != NULL)
650 {
651 fprintf(out, "\tpatch:writable");
652 if (count > 1)
653 {
654 fprintf(out, "\n");
655 for (const port_t *p = m.ports; (p->id != NULL) && (p->name != NULL); ++p)
656 {
657 switch (p->role)
658 {
659 case R_PATH:
660 {
661 fprintf(out, "\t\tlsp_p:%s", p->id);
662 if (--count)
663 fprintf(out, " ,\n");
664 break;
665 }
666 default:
667 break;
668 }
669 }
670 fprintf(out, " ;\n\n");
671 }
672 else
673 fprintf(out, " lsp_p:%s ;\n\n", first->id);
674 }
675 }
676
677 for (const port_t *p = m.ports; (p->id != NULL) && (p->name != NULL); ++p)
678 {
679 // Skip virtual ports
680 switch (p->role)
681 {
682 case R_UI_SYNC:
683 case R_MESH:
684 case R_STREAM:
685 case R_FBUFFER:
686 case R_PATH:
687 case R_PORT_SET:
688 case R_MIDI:
689 case R_OSC:
690 continue;
691 default:
692 break;
693 }
694
695 fprintf(out, "%s [\n", (port_id == 0) ? "\tlv2:port" : " ,");
696 fprintf(out, "\t\ta lv2:%s, ", (p->flags & F_OUT) ? "OutputPort" : "InputPort");
697
698 switch (p->role)
699 {
700 case R_AUDIO:
701 fprintf(out, "lv2:AudioPort ;\n");
702 break;
703 case R_CONTROL:
704 case R_BYPASS:
705 case R_METER:
706 fprintf(out, "lv2:ControlPort ;\n");
707 break;
708 default:
709 break;
710 }
711
712 fprintf(out, "\t\tlv2:index %d ;\n", (int)port_id);
713 fprintf(out, "\t\tlv2:symbol \"%s\" ;\n", (p->role == R_BYPASS) ? "enabled" : p->id);
714 fprintf(out, "\t\tlv2:name \"%s\" ;\n", (p->role == R_BYPASS) ? "Enabled" : p->name);
715 if (p->role == R_BYPASS)
716 fprintf(out, "\t\tlv2:designation lv2:enabled ;\n");
717
718 print_units(out, p->unit);
719
720 size_t p_prop = 0;
721
722 if (p->flags & F_LOG)
723 {
724 LSP_LV2_EMIT_HEADER(p_prop, "\t\tlv2:portProperty");
725 LSP_LV2_EMIT_OPTION(p_prop, true, "pp:logarithmic");
726 }
727
728 if (p->unit == U_BOOL)
729 {
730 LSP_LV2_EMIT_HEADER(p_prop, "\t\tlv2:portProperty");
731 LSP_LV2_EMIT_OPTION(p_prop, true, "lv2:toggled");
732 LSP_LV2_EMIT_END(p_prop);
733 // if (p->flags & F_TRG)
734 // fprintf(out, "\t\tlv2:portProperty pp:trigger ;\n");
735 fprintf(out, "\t\tlv2:minimum %d ;\n", 0);
736 fprintf(out, "\t\tlv2:maximum %d ;\n", 1);
737 fprintf(out, "\t\tlv2:default %d ;\n", (p->role == R_BYPASS) ? (1 - int(p->start)) : int(p->start));
738 }
739 else if (p->unit == U_ENUM)
740 {
741 LSP_LV2_EMIT_HEADER(p_prop, "\t\tlv2:portProperty");
742 LSP_LV2_EMIT_OPTION(p_prop, true, "lv2:integer");
743 LSP_LV2_EMIT_OPTION(p_prop, true, "lv2:enumeration");
744 LSP_LV2_EMIT_OPTION(p_prop, true, "pp:hasStrictBounds");
745 LSP_LV2_EMIT_END(p_prop);
746
747 int min = (p->flags & F_LOWER) ? p->min : 0;
748 int curr = min;
749 size_t count = list_size(p->items);
750 int max = min + list_size(p->items) - 1;
751
752 const port_item_t *list = p->items;
753 if (count > 1)
754 {
755 fprintf(out, "\t\tlv2:scalePoint\n");
756 for ( ; list->text != NULL; ++list)
757 {
758 fprintf(out, "\t\t\t[ rdfs:label \"%s\"; rdf:value %d ]", list->text, curr);
759 if (--count)
760 fprintf(out, " ,\n");
761 else
762 fprintf(out, " ;\n");
763 curr ++;
764 }
765 } else if (count > 0)
766 fprintf(out, "\t\tlv2:scalePoint [ rdfs:label \"%s\"; rdf:value %d ] ;\n", list->text, curr);
767
768 fprintf(out, "\t\tlv2:minimum %d ;\n", min);
769 fprintf(out, "\t\tlv2:maximum %d ;\n", max);
770 fprintf(out, "\t\tlv2:default %d ;\n", int(p->start));
771 }
772 else if (p->unit == U_SAMPLES)
773 {
774 LSP_LV2_EMIT_HEADER(p_prop, "\t\tlv2:portProperty");
775 LSP_LV2_EMIT_OPTION(p_prop, true, "lv2:integer");
776 if ((p->flags & (F_LOWER | F_UPPER)) == (F_LOWER | F_UPPER))
777 LSP_LV2_EMIT_OPTION(p_prop, true, "pp:hasStrictBounds");
778 LSP_LV2_EMIT_END(p_prop);
779
780 if (p->flags & F_LOWER)
781 fprintf(out, "\t\tlv2:minimum %d ;\n", int(p->min));
782 if (p->flags & F_UPPER)
783 fprintf(out, "\t\tlv2:maximum %d ;\n", int(p->max));
784 fprintf(out, "\t\tlv2:default %d ;\n", int(p->start));
785 }
786 else if (p->flags & F_INT)
787 {
788 LSP_LV2_EMIT_HEADER(p_prop, "\t\tlv2:portProperty");
789 LSP_LV2_EMIT_OPTION(p_prop, true, "lv2:integer");
790 if ((p->flags & (F_LOWER | F_UPPER)) == (F_LOWER | F_UPPER))
791 LSP_LV2_EMIT_OPTION(p_prop, true, "pp:hasStrictBounds");
792 LSP_LV2_EMIT_END(p_prop);
793
794 if (p->flags & F_LOWER)
795 fprintf(out, "\t\tlv2:minimum %d ;\n", int(p->min));
796 if (p->flags & F_UPPER)
797 fprintf(out, "\t\tlv2:maximum %d ;\n", int(p->max));
798 if ((p->role == R_CONTROL) || (p->role == R_METER))
799 fprintf(out, "\t\tlv2:default %d ;\n", int(p->start));
800 }
801 else
802 {
803 if ((p->flags & (F_LOWER | F_UPPER)) == (F_LOWER | F_UPPER))
804 {
805 LSP_LV2_EMIT_HEADER(p_prop, "\t\tlv2:portProperty");
806 LSP_LV2_EMIT_OPTION(p_prop, true, "pp:hasStrictBounds");
807 LSP_LV2_EMIT_END(p_prop);
808 }
809
810 if (p->flags & F_LOWER)
811 fprintf(out, "\t\tlv2:minimum %.6f ;\n", p->min);
812 if (p->flags & F_UPPER)
813 fprintf(out, "\t\tlv2:maximum %.6f ;\n", p->max);
814 if ((p->role == R_CONTROL) || (p->role == R_METER))
815 fprintf(out, "\t\tlv2:default %.6f ;\n", p->start);
816 }
817
818 LSP_LV2_EMIT_END(p_prop);
819
820 // Output all port groups of the port
821 if (requirements & REQ_PORT_GROUPS)
822 print_port_groups(out, p, m.port_groups);
823
824 fprintf(out, "\t]");
825 port_id++;
826 }
827
828 // Add atom ports for state serialization
829 for (size_t i=0; i<2; ++i)
830 {
831 const port_t *p = &lv2_atom_ports[i];
832
833 fprintf(out, "%s [\n", (port_id == 0) ? "\tlv2:port" : " ,");
834 fprintf(out, "\t\ta lv2:%s, atom:AtomPort ;\n", (i > 0) ? "OutputPort" : "InputPort");
835 fprintf(out, "\t\tatom:bufferType atom:Sequence ;\n");
836
837 fprintf(out, "\t\tatom:supports atom:Sequence");
838 if (requirements & REQ_PATCH)
839 fprintf(out, ", patch:Message");
840 if (requirements & REQ_TIME)
841 fprintf(out, ", time:Position");
842 if ((i == 0) && (requirements & REQ_MIDI_IN))
843 fprintf(out, ", midi:MidiEvent");
844 else if ((i == 1) && (requirements & REQ_MIDI_OUT))
845 fprintf(out, ", midi:MidiEvent");
846 fprintf(out, " ;\n");
847
848 const char *p_id = p->id;
849 const char *p_name = p->name;
850 const char *comm = NULL;
851 if (IS_IN_PORT(p))
852 {
853 comm = "UI -> DSP communication";
854 if (requirements & REQ_MIDI_IN)
855 {
856 p_id = LSP_LV2_MIDI_PORT_IN;
857 p_name = "MIDI Input, UI Input";
858 comm = "MIDI IN, UI -> DSP communication";
859 }
860 }
861 else
862 {
863 comm = "DSP -> UI communication";
864 if (requirements & REQ_MIDI_IN)
865 {
866 p_id = LSP_LV2_MIDI_PORT_OUT;
867 p_name = "MIDI Output, UI Output";
868 comm = "MIDI OUT, DSP -> UI communication";
869 }
870 }
871
872 long bufsize = lv2_all_port_sizes(m.ports, IS_IN_PORT(p), IS_OUT_PORT(p));
873 if (m.extensions & E_KVT_SYNC)
874 bufsize += OSC_BUFFER_MAX;
875
876 if (!(requirements & REQ_MIDI))
877 fprintf(out, "\t\tlv2:portProperty lv2:connectionOptional ;\n");
878 fprintf(out, "\t\tlv2:designation lv2:control ;\n");
879 fprintf(out, "\t\tlv2:index %d ;\n", int(port_id));
880 fprintf(out, "\t\tlv2:symbol \"%s\" ;\n", p_id);
881 fprintf(out, "\t\tlv2:name \"%s\" ;\n", p_name);
882 fprintf(out, "\t\trdfs:comment \"%s\" ;\n", comm);
883 fprintf(out, "\t\trsz:minimumSize %ld ;\n", bufsize * 2);
884 fprintf(out, "\t]");
885
886 port_id++;
887 }
888
889 // Add latency reporting port
890 {
891 const port_t *p = &lv2_latency_port;
892 if ((p->id != NULL) && (p->name != NULL))
893 {
894 fprintf(out, "%s [\n", (port_id == 0) ? "\tlv2:port" : " ,");
895 fprintf(out, "\t\ta lv2:%s, lv2:ControlPort ;\n", (p->flags & F_OUT) ? "OutputPort" : "InputPort");
896 fprintf(out, "\t\tlv2:index %d ;\n", int(port_id));
897 fprintf(out, "\t\tlv2:symbol \"%s\" ;\n", p->id);
898 fprintf(out, "\t\tlv2:name \"%s\" ;\n", p->name);
899 fprintf(out, "\t\trdfs:comment \"DSP -> Host latency report\" ;\n");
900
901 if ((p->flags & (F_LOWER | F_UPPER)) == (F_LOWER | F_UPPER))
902 fprintf(out, "\t\tlv2:portProperty pp:hasStrictBounds ;\n");
903 if (p->flags & F_INT)
904 fprintf(out, "\t\tlv2:portProperty lv2:integer ;\n");
905 fprintf(out, "\t\tlv2:portProperty lv2:reportsLatency ;\n");
906
907 if (p->flags & F_LOWER)
908 fprintf(out, "\t\tlv2:minimum %d ;\n", int(p->min));
909 if (p->flags & F_UPPER)
910 fprintf(out, "\t\tlv2:maximum %d ;\n", int(p->max));
911 fprintf(out, "\t\tlv2:default %d ;\n", int(p->start));
912 fprintf(out, "\t]");
913
914 port_id++;
915 }
916 }
917
918 // Finish port list
919 fprintf(out, "\n\t.\n\n");
920
921 // Output plugin UIs
922 if (requirements & REQ_LV2UI)
923 {
924 char *ui_uri = NULL, *plugin_uri = NULL;
925 int n = asprintf(&ui_uri, LSP_PLUGIN_UI_URI(lv2, "%s"), m.lv2_uid);
926 if (n >= 0)
927 n = asprintf(&plugin_uri, LSP_PLUGIN_URI(lv2, "%s"), m.lv2_uid);
928
929 if ((n >= 0) && (ui_uri != NULL) && (plugin_uri != NULL))
930 gen_plugin_ui_ttl(out, requirements, m, m.lv2_uid, ui_uri, plugin_uri);
931
932 if (ui_uri != NULL)
933 free(ui_uri);
934 if (plugin_uri != NULL)
935 free(plugin_uri);
936 }
937
938 fclose(out);
939 }
940
gen_manifest(const char * path)941 void gen_manifest(const char *path)
942 {
943 char fname[2048];
944 snprintf(fname, sizeof(fname)-1, "%s/manifest.ttl", path);
945 FILE *out = NULL;
946
947 // Generate manifest.ttl
948 if (!(out = fopen(fname, "w+")))
949 return;
950 printf("Writing file %s\n", fname);
951
952 fprintf(out, "@prefix lv2: <http://lv2plug.in/ns/lv2core#> .\n");
953 fprintf(out, "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n");
954 fprintf(out, "@prefix " LSP_PREFIX ": <" LSP_URI(lv2) "> .\n\n");
955
956 #define MOD_PLUGIN(plugin, ui) \
957 if (plugin::metadata.lv2_uid != NULL) \
958 { \
959 fprintf(out, LSP_PREFIX ":" #plugin "\n"); \
960 fprintf(out, "\ta lv2:Plugin ;\n"); \
961 fprintf(out, "\tlv2:binary <" LSP_ARTIFACT_ID "-lv2.so> ;\n"); \
962 fprintf(out, "\trdfs:seeAlso <%s.ttl> .\n\n", #plugin); \
963 }
964 #include <metadata/modules.h>
965 fclose(out);
966 }
967
gen_ttl(const char * path)968 void gen_ttl(const char *path)
969 {
970 gen_manifest(path);
971
972 // Output plugins
973 size_t id = 0;
974 #define MOD_PLUGIN(plugin, ui) \
975 if (plugin::metadata.lv2_uid != NULL) \
976 { \
977 gen_plugin_ttl(path, plugin::metadata, LSP_PLUGIN_URI(lv2, plugin)); \
978 id++; \
979 }
980 #include <metadata/modules.h>
981 }
982 }
983
984 #ifndef LSP_IDE_DEBUG
main(int argc,const char ** argv)985 int main(int argc, const char **argv)
986 {
987 if (argc <= 0)
988 fprintf(stderr, "required destination path");
989 lsp::gen_ttl(argv[1]);
990
991 return 0;
992 }
993 #endif /* LSP_IDE_DEBUG */
994