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