1 /*
2  * DISTRHO Plugin Framework (DPF)
3  * Copyright (C) 2012-2020 Filipe Coelho <falktx@falktx.com>
4  *
5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
6  * or without fee is hereby granted, provided that the above copyright notice and this
7  * permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include "DistrhoPluginInternal.hpp"
18 
19 #include "lv2/atom.h"
20 #include "lv2/buf-size.h"
21 #include "lv2/data-access.h"
22 #include "lv2/instance-access.h"
23 #include "lv2/midi.h"
24 #include "lv2/options.h"
25 #include "lv2/patch.h"
26 #include "lv2/port-props.h"
27 #include "lv2/presets.h"
28 #include "lv2/resize-port.h"
29 #include "lv2/state.h"
30 #include "lv2/time.h"
31 #include "lv2/ui.h"
32 #include "lv2/units.h"
33 #include "lv2/urid.h"
34 #include "lv2/worker.h"
35 #include "lv2/lv2_kxstudio_properties.h"
36 #include "lv2/lv2_programs.h"
37 
38 #ifdef DISTRHO_PLUGIN_LICENSED_FOR_MOD
39 # include "mod-license.h"
40 #endif
41 
42 #include <fstream>
43 #include <iostream>
44 
45 #ifndef DISTRHO_PLUGIN_URI
46 # error DISTRHO_PLUGIN_URI undefined!
47 #endif
48 
49 #ifndef DISTRHO_PLUGIN_LV2_STATE_PREFIX
50 # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:"
51 #endif
52 
53 #ifndef DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE
54 # define DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE 2048
55 #endif
56 
57 #ifndef DISTRHO_PLUGIN_USES_MODGUI
58 # define DISTRHO_PLUGIN_USES_MODGUI 0
59 #endif
60 
61 #if DISTRHO_PLUGIN_HAS_EMBED_UI
62 # if DISTRHO_OS_HAIKU
63 #  define DISTRHO_LV2_UI_TYPE "BeUI"
64 # elif DISTRHO_OS_MAC
65 #  define DISTRHO_LV2_UI_TYPE "CocoaUI"
66 # elif DISTRHO_OS_WINDOWS
67 #  define DISTRHO_LV2_UI_TYPE "WindowsUI"
68 # else
69 #  define DISTRHO_LV2_UI_TYPE "X11UI"
70 # endif
71 #else
72 # define DISTRHO_LV2_UI_TYPE "UI"
73 #endif
74 
75 #define DISTRHO_LV2_USE_EVENTS_IN  (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI))
76 #define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI))
77 
78 #define DISTRHO_BYPASS_PARAMETER_NAME "lv2_enabled"
79 
80 // -----------------------------------------------------------------------
81 static const char* const lv2ManifestPluginExtensionData[] =
82 {
83     "opts:interface",
84 #if DISTRHO_PLUGIN_WANT_STATE
85     LV2_STATE__interface,
86     LV2_WORKER__interface,
87 #endif
88 #if DISTRHO_PLUGIN_WANT_PROGRAMS
89     LV2_PROGRAMS__Interface,
90 #endif
91 #ifdef DISTRHO_PLUGIN_LICENSED_FOR_MOD
92     MOD_LICENSE__interface,
93 #endif
94     nullptr
95 };
96 
97 static const char* const lv2ManifestPluginOptionalFeatures[] =
98 {
99 #if DISTRHO_PLUGIN_IS_RT_SAFE
100     LV2_CORE__hardRTCapable,
101 #endif
102     LV2_BUF_SIZE__boundedBlockLength,
103     nullptr
104 };
105 
106 static const char* const lv2ManifestPluginRequiredFeatures[] =
107 {
108     "opts:options",
109     LV2_URID__map,
110 #if DISTRHO_PLUGIN_WANT_STATE
111     LV2_WORKER__schedule,
112 #endif
113 #ifdef DISTRHO_PLUGIN_LICENSED_FOR_MOD
114     MOD_LICENSE__feature,
115 #endif
116     nullptr
117 };
118 
119 static const char* const lv2ManifestPluginSupportedOptions[] =
120 {
121     LV2_BUF_SIZE__nominalBlockLength,
122     LV2_BUF_SIZE__maxBlockLength,
123     LV2_PARAMETERS__sampleRate,
124     nullptr
125 };
126 
127 #if DISTRHO_PLUGIN_HAS_UI
128 static const char* const lv2ManifestUiExtensionData[] =
129 {
130     "opts:interface",
131     "ui:idleInterface",
132     "ui:showInterface",
133     "ui:resize",
134 #if DISTRHO_PLUGIN_WANT_PROGRAMS
135     LV2_PROGRAMS__UIInterface,
136 #endif
137     nullptr
138 };
139 
140 static const char* const lv2ManifestUiOptionalFeatures[] =
141 {
142 #if DISTRHO_PLUGIN_HAS_EMBED_UI
143 # if !DISTRHO_UI_USER_RESIZABLE
144     "ui:noUserResize",
145 # endif
146     "ui:parent",
147     "ui:resize",
148     "ui:touch",
149 #endif
150 #if DISTRHO_PLUGIN_WANT_STATEFILES
151     "ui:requestValue",
152 #endif
153     nullptr
154 };
155 
156 static const char* const lv2ManifestUiRequiredFeatures[] =
157 {
158     "opts:options",
159     "ui:idleInterface",
160 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
161     LV2_DATA_ACCESS_URI,
162     LV2_INSTANCE_ACCESS_URI,
163 #endif
164     LV2_URID__map,
165     nullptr
166 };
167 
168 static const char* const lv2ManifestUiSupportedOptions[] =
169 {
170     LV2_PARAMETERS__sampleRate,
171     nullptr
172 };
173 #endif // DISTRHO_PLUGIN_HAS_UI
174 
addAttribute(DISTRHO_NAMESPACE::String & text,const char * const attribute,const char * const values[],const uint indent,const bool endInDot=false)175 static void addAttribute(DISTRHO_NAMESPACE::String& text,
176                          const char* const attribute,
177                          const char* const values[],
178                          const uint indent,
179                          const bool endInDot = false)
180 {
181     if (values[0] == nullptr)
182     {
183         if (endInDot)
184         {
185             bool found;
186             const size_t index = text.rfind(';', &found);
187             if (found) text[index] = '.';
188         }
189         return;
190     }
191 
192     const size_t attributeLength = std::strlen(attribute);
193 
194     for (uint i = 0; values[i] != nullptr; ++i)
195     {
196         for (uint j = 0; j < indent; ++j)
197             text += " ";
198 
199         if (i == 0)
200         {
201             text += attribute;
202         }
203         else
204         {
205             for (uint j = 0; j < attributeLength; ++j)
206                 text += " ";
207         }
208 
209         text += " ";
210 
211         const bool isUrl = std::strstr(values[i], "://") != nullptr || std::strncmp(values[i], "urn:", 4) == 0;
212         if (isUrl) text += "<";
213         text += values[i];
214         if (isUrl) text += ">";
215         text += values[i + 1] ? " ,\n" : (endInDot ? " .\n\n" : " ;\n\n");
216     }
217 }
218 
219 // -----------------------------------------------------------------------
220 
221 DISTRHO_PLUGIN_EXPORT
lv2_generate_ttl(const char * const basename)222 void lv2_generate_ttl(const char* const basename)
223 {
224     USE_NAMESPACE_DISTRHO
225 
226     // Dummy plugin to get data from
227     d_lastBufferSize = 512;
228     d_lastSampleRate = 44100.0;
229     PluginExporter plugin(nullptr, nullptr);
230     d_lastBufferSize = 0;
231     d_lastSampleRate = 0.0;
232 
233     const String pluginDLL(basename);
234     const String pluginTTL(pluginDLL + ".ttl");
235 
236 #if DISTRHO_PLUGIN_HAS_UI
237     String pluginUI(pluginDLL);
238 # if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
239     pluginUI.truncate(pluginDLL.rfind("_dsp"));
240     pluginUI += "_ui";
241     const String uiTTL(pluginUI + ".ttl");
242 # endif
243 #endif
244 
245     // ---------------------------------------------
246 
247     {
248         std::cout << "Writing manifest.ttl..."; std::cout.flush();
249         std::fstream manifestFile("manifest.ttl", std::ios::out);
250 
251         String manifestString;
252         manifestString += "@prefix lv2:  <" LV2_CORE_PREFIX "> .\n";
253         manifestString += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
254 #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
255         manifestString += "@prefix opts: <" LV2_OPTIONS_PREFIX "> .\n";
256 #endif
257 #if DISTRHO_PLUGIN_WANT_PROGRAMS
258         manifestString += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n";
259 #endif
260 #if DISTRHO_PLUGIN_HAS_UI
261         manifestString += "@prefix ui:   <" LV2_UI_PREFIX "> .\n";
262 #endif
263         manifestString += "\n";
264 
265         manifestString += "<" DISTRHO_PLUGIN_URI ">\n";
266         manifestString += "    a lv2:Plugin ;\n";
267         manifestString += "    lv2:binary <" + pluginDLL + "." DISTRHO_DLL_EXTENSION "> ;\n";
268 #if DISTRHO_PLUGIN_USES_MODGUI
269         manifestString += "    rdfs:seeAlso <" + pluginTTL + "> ,\n";
270         manifestString += "                 <modgui.ttl> .\n";
271 #else
272         manifestString += "    rdfs:seeAlso <" + pluginTTL + "> .\n";
273 #endif
274         manifestString += "\n";
275 
276 #if DISTRHO_PLUGIN_HAS_UI
277         manifestString += "<" DISTRHO_UI_URI ">\n";
278         manifestString += "    a ui:" DISTRHO_LV2_UI_TYPE " ;\n";
279         manifestString += "    ui:binary <" + pluginUI + "." DISTRHO_DLL_EXTENSION "> ;\n";
280 # if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
281         addAttribute(manifestString, "lv2:extensionData", lv2ManifestUiExtensionData, 4);
282         addAttribute(manifestString, "lv2:optionalFeature", lv2ManifestUiOptionalFeatures, 4);
283         addAttribute(manifestString, "lv2:requiredFeature", lv2ManifestUiRequiredFeatures, 4);
284         addAttribute(manifestString, "opts:supportedOption", lv2ManifestUiSupportedOptions, 4, true);
285 # else // DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
286         manifestString += "    rdfs:seeAlso <" + uiTTL + "> .\n";
287 # endif // DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
288         manifestString += "\n";
289 #endif
290 
291 #if DISTRHO_PLUGIN_WANT_PROGRAMS
292         const String presetSeparator(std::strstr(DISTRHO_PLUGIN_URI, "#") != nullptr ? ":" : "#");
293 
294         char strBuf[0xff+1];
295         strBuf[0xff] = '\0';
296 
297         String presetString;
298 
299         // Presets
300         for (uint32_t i = 0; i < plugin.getProgramCount(); ++i)
301         {
302             std::snprintf(strBuf, 0xff, "%03i", i+1);
303 
304             presetString  = "<" DISTRHO_PLUGIN_URI + presetSeparator + "preset" + strBuf + ">\n";
305             presetString += "    a pset:Preset ;\n";
306             presetString += "    lv2:appliesTo <" DISTRHO_PLUGIN_URI "> ;\n";
307             presetString += "    rdfs:label \"" + plugin.getProgramName(i) + "\" ;\n";
308             presetString += "    rdfs:seeAlso <presets.ttl> .\n";
309             presetString += "\n";
310 
311             manifestString += presetString;
312         }
313 #endif
314 
315         manifestFile << manifestString << std::endl;
316         manifestFile.close();
317         std::cout << " done!" << std::endl;
318     }
319 
320     // ---------------------------------------------
321 
322     {
323         std::cout << "Writing " << pluginTTL << "..."; std::cout.flush();
324         std::fstream pluginFile(pluginTTL, std::ios::out);
325 
326         String pluginString;
327 
328         // header
329 #if DISTRHO_LV2_USE_EVENTS_IN || DISTRHO_LV2_USE_EVENTS_OUT
330         pluginString += "@prefix atom:  <" LV2_ATOM_PREFIX "> .\n";
331 #endif
332         pluginString += "@prefix doap:  <http://usefulinc.com/ns/doap#> .\n";
333         pluginString += "@prefix foaf:  <http://xmlns.com/foaf/0.1/> .\n";
334         pluginString += "@prefix lv2:   <" LV2_CORE_PREFIX "> .\n";
335 #ifdef DISTRHO_PLUGIN_BRAND
336         pluginString += "@prefix mod:   <http://moddevices.com/ns/mod#> .\n";
337 #endif
338         pluginString += "@prefix opts:  <" LV2_OPTIONS_PREFIX "> .\n";
339         pluginString += "@prefix patch: <" LV2_PATCH_PREFIX "> .\n";
340         pluginString += "@prefix rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n";
341         pluginString += "@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .\n";
342 #if DISTRHO_LV2_USE_EVENTS_IN || DISTRHO_LV2_USE_EVENTS_OUT
343         pluginString += "@prefix rsz:   <" LV2_RESIZE_PORT_PREFIX "> .\n";
344 #endif
345 #if DISTRHO_PLUGIN_HAS_UI
346         pluginString += "@prefix ui:    <" LV2_UI_PREFIX "> .\n";
347 #endif
348         pluginString += "@prefix unit:  <" LV2_UNITS_PREFIX "> .\n";
349         pluginString += "\n";
350 
351 #if DISTRHO_PLUGIN_WANT_STATEFILES
352         // define writable states as lv2 parameters
353         bool hasStateFiles = false;
354 
355         for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
356         {
357             if (! plugin.isStateFile(i))
358                 continue;
359 
360             const String& key(plugin.getStateKey(i));
361             pluginString += "<" DISTRHO_PLUGIN_URI "#" + key + ">\n";
362             pluginString += "    a lv2:Parameter ;\n";
363             pluginString += "    rdfs:label \"" + key + "\" ;\n";
364             pluginString += "    rdfs:range atom:Path .\n\n";
365             hasStateFiles = true;
366         }
367 #endif
368 
369         // plugin
370         pluginString += "<" DISTRHO_PLUGIN_URI ">\n";
371 #ifdef DISTRHO_PLUGIN_LV2_CATEGORY
372         pluginString += "    a " DISTRHO_PLUGIN_LV2_CATEGORY ", lv2:Plugin ;\n";
373 #elif DISTRHO_PLUGIN_IS_SYNTH
374         pluginString += "    a lv2:InstrumentPlugin, lv2:Plugin ;\n";
375 #else
376         pluginString += "    a lv2:Plugin ;\n";
377 #endif
378         pluginString += "\n";
379 
380         addAttribute(pluginString, "lv2:extensionData", lv2ManifestPluginExtensionData, 4);
381         addAttribute(pluginString, "lv2:optionalFeature", lv2ManifestPluginOptionalFeatures, 4);
382         addAttribute(pluginString, "lv2:requiredFeature", lv2ManifestPluginRequiredFeatures, 4);
383         addAttribute(pluginString, "opts:supportedOption", lv2ManifestPluginSupportedOptions, 4);
384 
385 #if DISTRHO_PLUGIN_WANT_STATEFILES
386         if (hasStateFiles)
387         {
388             for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
389             {
390                 if (! plugin.isStateFile(i))
391                     continue;
392 
393                 const String& key(plugin.getStateKey(i));
394                 pluginString += "    patch:writable <" DISTRHO_PLUGIN_URI "#" + key + ">;\n";
395             }
396             pluginString += "\n";
397         }
398 #endif
399 
400         // UI
401 #if DISTRHO_PLUGIN_HAS_UI
402         pluginString += "    ui:ui <" DISTRHO_UI_URI "> ;\n";
403         pluginString += "\n";
404 #endif
405 
406         {
407             uint32_t portIndex = 0;
408 
409 #if DISTRHO_PLUGIN_NUM_INPUTS > 0
410             for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i, ++portIndex)
411             {
412                 const AudioPort& port(plugin.getAudioPort(true, i));
413 
414                 if (i == 0)
415                     pluginString += "    lv2:port [\n";
416                 else
417                     pluginString += "    [\n";
418 
419                 if (port.hints & kAudioPortIsCV)
420                     pluginString += "        a lv2:InputPort, lv2:CVPort ;\n";
421                 else
422                     pluginString += "        a lv2:InputPort, lv2:AudioPort ;\n";
423 
424                 pluginString += "        lv2:index " + String(portIndex) + " ;\n";
425                 pluginString += "        lv2:symbol \"lv2_" + port.symbol + "\" ;\n";
426                 pluginString += "        lv2:name \"" + port.name + "\" ;\n";
427 
428                 if (port.hints & kAudioPortIsSidechain)
429                     pluginString += "        lv2:portProperty lv2:isSideChain;\n";
430 
431                 if (i+1 == DISTRHO_PLUGIN_NUM_INPUTS)
432                     pluginString += "    ] ;\n";
433                 else
434                     pluginString += "    ] ,\n";
435             }
436             pluginString += "\n";
437 #endif
438 
439 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
440             for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i, ++portIndex)
441             {
442                 const AudioPort& port(plugin.getAudioPort(false, i));
443 
444                 if (i == 0)
445                     pluginString += "    lv2:port [\n";
446                 else
447                     pluginString += "    [\n";
448 
449                 if (port.hints & kAudioPortIsCV)
450                     pluginString += "        a lv2:OutputPort, lv2:CVPort ;\n";
451                 else
452                     pluginString += "        a lv2:OutputPort, lv2:AudioPort ;\n";
453 
454                 pluginString += "        lv2:index " + String(portIndex) + " ;\n";
455                 pluginString += "        lv2:symbol \"lv2_" + port.symbol + "\" ;\n";
456                 pluginString += "        lv2:name \"" + port.name + "\" ;\n";
457 
458                 if (port.hints & kAudioPortIsSidechain)
459                     pluginString += "        lv2:portProperty lv2:isSideChain;\n";
460 
461                 if (i+1 == DISTRHO_PLUGIN_NUM_OUTPUTS)
462                     pluginString += "    ] ;\n";
463                 else
464                     pluginString += "    ] ,\n";
465             }
466             pluginString += "\n";
467 #endif
468 
469 #if DISTRHO_LV2_USE_EVENTS_IN
470             pluginString += "    lv2:port [\n";
471             pluginString += "        a lv2:InputPort, atom:AtomPort ;\n";
472             pluginString += "        lv2:index " + String(portIndex) + " ;\n";
473             pluginString += "        lv2:name \"Events Input\" ;\n";
474             pluginString += "        lv2:symbol \"lv2_events_in\" ;\n";
475             pluginString += "        rsz:minimumSize " + String(DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE) + " ;\n";
476             pluginString += "        atom:bufferType atom:Sequence ;\n";
477 # if (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)
478             pluginString += "        atom:supports <" LV2_ATOM__String "> ;\n";
479 # endif
480 # if DISTRHO_PLUGIN_WANT_MIDI_INPUT
481             pluginString += "        atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
482 # endif
483 # if DISTRHO_PLUGIN_WANT_TIMEPOS
484             pluginString += "        atom:supports <" LV2_TIME__Position "> ;\n";
485 # endif
486             pluginString += "    ] ;\n\n";
487             ++portIndex;
488 #endif
489 
490 #if DISTRHO_LV2_USE_EVENTS_OUT
491             pluginString += "    lv2:port [\n";
492             pluginString += "        a lv2:OutputPort, atom:AtomPort ;\n";
493             pluginString += "        lv2:index " + String(portIndex) + " ;\n";
494             pluginString += "        lv2:name \"Events Output\" ;\n";
495             pluginString += "        lv2:symbol \"lv2_events_out\" ;\n";
496             pluginString += "        rsz:minimumSize " + String(DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE) + " ;\n";
497             pluginString += "        atom:bufferType atom:Sequence ;\n";
498 # if (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)
499             pluginString += "        atom:supports <" LV2_ATOM__String "> ;\n";
500 # endif
501 # if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
502             pluginString += "        atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
503 # endif
504             pluginString += "    ] ;\n\n";
505             ++portIndex;
506 #endif
507 
508 #if DISTRHO_PLUGIN_WANT_LATENCY
509             pluginString += "    lv2:port [\n";
510             pluginString += "        a lv2:OutputPort, lv2:ControlPort ;\n";
511             pluginString += "        lv2:index " + String(portIndex) + " ;\n";
512             pluginString += "        lv2:name \"Latency\" ;\n";
513             pluginString += "        lv2:symbol \"lv2_latency\" ;\n";
514             pluginString += "        lv2:designation lv2:latency ;\n";
515             pluginString += "        lv2:portProperty lv2:reportsLatency, lv2:integer, <" LV2_PORT_PROPS__notOnGUI "> ;\n";
516             pluginString += "    ] ;\n\n";
517             ++portIndex;
518 #endif
519 
520             for (uint32_t i=0, count=plugin.getParameterCount(); i < count; ++i, ++portIndex)
521             {
522                 if (i == 0)
523                     pluginString += "    lv2:port [\n";
524                 else
525                     pluginString += "    [\n";
526 
527                 if (plugin.isParameterOutput(i))
528                     pluginString += "        a lv2:OutputPort, lv2:ControlPort ;\n";
529                 else
530                     pluginString += "        a lv2:InputPort, lv2:ControlPort ;\n";
531 
532                 pluginString += "        lv2:index " + String(portIndex) + " ;\n";
533 
534                 bool designated = false;
535 
536                 // designation
537                 if (plugin.isParameterInput(i))
538                 {
539                     switch (plugin.getParameterDesignation(i))
540                     {
541                     case kParameterDesignationNull:
542                         break;
543                     case kParameterDesignationBypass:
544                         designated = true;
545                         pluginString += "        lv2:name \"Enabled\" ;\n";
546                         pluginString += "        lv2:symbol \"" DISTRHO_BYPASS_PARAMETER_NAME "\" ;\n";
547                         pluginString += "        lv2:default 1 ;\n";
548                         pluginString += "        lv2:minimum 0 ;\n";
549                         pluginString += "        lv2:maximum 1 ;\n";
550                         pluginString += "        lv2:portProperty lv2:toggled , lv2:integer ;\n";
551                         pluginString += "        lv2:designation lv2:enabled ;\n";
552                         break;
553                     }
554                 }
555 
556                 if (! designated)
557                 {
558                     // name and symbol
559                     pluginString += "        lv2:name \"\"\"" + plugin.getParameterName(i) + "\"\"\" ;\n";
560 
561                     String symbol(plugin.getParameterSymbol(i));
562 
563                     if (symbol.isEmpty())
564                         symbol = "lv2_port_" + String(portIndex-1);
565 
566                     pluginString += "        lv2:symbol \"" + symbol + "\" ;\n";
567 
568                     // short name
569                     const String& shortName(plugin.getParameterShortName(i));
570 
571                     if (shortName.isNotEmpty())
572                         pluginString += "        lv2:shortName \"\"\"" + shortName + "\"\"\" ;\n";
573 
574                     // ranges
575                     const ParameterRanges& ranges(plugin.getParameterRanges(i));
576 
577                     if (plugin.getParameterHints(i) & kParameterIsInteger)
578                     {
579                         if (plugin.isParameterInput(i))
580                             pluginString += "        lv2:default " + String(int(ranges.def)) + " ;\n";
581                         pluginString += "        lv2:minimum " + String(int(ranges.min)) + " ;\n";
582                         pluginString += "        lv2:maximum " + String(int(ranges.max)) + " ;\n";
583                     }
584                     else
585                     {
586                         if (plugin.isParameterInput(i))
587                             pluginString += "        lv2:default " + String(ranges.def) + " ;\n";
588                         pluginString += "        lv2:minimum " + String(ranges.min) + " ;\n";
589                         pluginString += "        lv2:maximum " + String(ranges.max) + " ;\n";
590                     }
591 
592                     // enumeration
593                     const ParameterEnumerationValues& enumValues(plugin.getParameterEnumValues(i));
594 
595                     if (enumValues.count > 0)
596                     {
597                         if (enumValues.count >= 2 && enumValues.restrictedMode)
598                             pluginString += "        lv2:portProperty lv2:enumeration ;\n";
599 
600                         for (uint8_t j=0; j < enumValues.count; ++j)
601                         {
602                             const ParameterEnumerationValue& enumValue(enumValues.values[j]);
603 
604                             if (j == 0)
605                                 pluginString += "        lv2:scalePoint [\n";
606                             else
607                                 pluginString += "        [\n";
608 
609                             pluginString += "            rdfs:label  \"\"\"" + enumValue.label + "\"\"\" ;\n";
610 
611                             if (plugin.getParameterHints(i) & kParameterIsInteger) {
612                                 const int roundedValue = (int)(enumValue.value + 0.5f);
613                                 pluginString += "            rdf:value " + String(roundedValue) + " ;\n";
614                             }
615                             else {
616                                 pluginString += "            rdf:value " + String(enumValue.value) + " ;\n";
617                             }
618 
619                             if (j+1 == enumValues.count)
620                                 pluginString += "        ] ;\n\n";
621                             else
622                                 pluginString += "        ] ,\n";
623                         }
624                     }
625 
626                     // unit
627                     const String& unit(plugin.getParameterUnit(i));
628 
629                     if (unit.isNotEmpty() && ! unit.contains(" "))
630                     {
631                         String lunit(unit);
632                         lunit.toLower();
633 
634                         /**/ if (lunit == "db")
635                         {
636                             pluginString += "        unit:unit unit:db ;\n";
637                         }
638                         else if (lunit == "hz")
639                         {
640                             pluginString += "        unit:unit unit:hz ;\n";
641                         }
642                         else if (lunit == "khz")
643                         {
644                             pluginString += "        unit:unit unit:khz ;\n";
645                         }
646                         else if (lunit == "mhz")
647                         {
648                             pluginString += "        unit:unit unit:mhz ;\n";
649                         }
650                         else if (lunit == "ms")
651                         {
652                             pluginString += "        unit:unit unit:ms ;\n";
653                         }
654                         else if (lunit == "s")
655                         {
656                             pluginString += "        unit:unit unit:s ;\n";
657                         }
658                         else if (lunit == "%")
659                         {
660                             pluginString += "        unit:unit unit:pc ;\n";
661                         }
662                         else
663                         {
664                             pluginString += "        unit:unit [\n";
665                             pluginString += "            a unit:Unit ;\n";
666                             pluginString += "            rdfs:label  \"" + unit + "\" ;\n";
667                             pluginString += "            unit:symbol \"" + unit + "\" ;\n";
668                             pluginString += "            unit:render \"%f " + unit + "\" ;\n";
669                             pluginString += "        ] ;\n";
670                         }
671                     }
672 
673                     // comment
674                     const String& comment(plugin.getParameterDescription(i));
675 
676                     if (comment.isNotEmpty())
677                         pluginString += "        rdfs:comment \"\"\"" + comment + "\"\"\" ;\n";
678 
679                     // hints
680                     const uint32_t hints(plugin.getParameterHints(i));
681 
682                     if (hints & kParameterIsBoolean)
683                     {
684                         if ((hints & kParameterIsTrigger) == kParameterIsTrigger)
685                             pluginString += "        lv2:portProperty <" LV2_PORT_PROPS__trigger "> ;\n";
686                         pluginString += "        lv2:portProperty lv2:toggled ;\n";
687                     }
688                     if (hints & kParameterIsInteger)
689                         pluginString += "        lv2:portProperty lv2:integer ;\n";
690                     if (hints & kParameterIsLogarithmic)
691                         pluginString += "        lv2:portProperty <" LV2_PORT_PROPS__logarithmic "> ;\n";
692                     if ((hints & kParameterIsAutomable) == 0 && plugin.isParameterInput(i))
693                     {
694                         pluginString += "        lv2:portProperty <" LV2_PORT_PROPS__expensive "> ,\n";
695                         pluginString += "                         <" LV2_KXSTUDIO_PROPERTIES__NonAutomable "> ;\n";
696                     }
697                 } // ! designated
698 
699                 if (i+1 == count)
700                     pluginString += "    ] ;\n\n";
701                 else
702                     pluginString += "    ] ,\n";
703             }
704         }
705 
706         // comment
707         {
708             const String comment(plugin.getDescription());
709 
710             if (comment.isNotEmpty())
711                 pluginString += "    rdfs:comment \"\"\"" + comment + "\"\"\" ;\n\n";
712         }
713 
714 #ifdef DISTRHO_PLUGIN_BRAND
715         // MOD
716         pluginString += "    mod:brand \"" DISTRHO_PLUGIN_BRAND "\" ;\n";
717         pluginString += "    mod:label \"" DISTRHO_PLUGIN_NAME "\" ;\n\n";
718 #endif
719 
720         // name
721         pluginString += "    doap:name \"\"\"" + String(plugin.getName()) + "\"\"\" ;\n";
722 
723         // license
724         {
725             const String license(plugin.getLicense());
726 
727             if (license.contains("://"))
728                 pluginString += "    doap:license <" +  license + "> ;\n\n";
729             else
730                 pluginString += "    doap:license \"\"\"" +  license + "\"\"\" ;\n\n";
731         }
732 
733         // developer
734         {
735             const String homepage(plugin.getHomePage());
736 
737             pluginString += "    doap:maintainer [\n";
738             pluginString += "        foaf:name \"\"\"" + String(plugin.getMaker()) + "\"\"\" ;\n";
739 
740             if (homepage.isNotEmpty())
741                 pluginString += "        foaf:homepage <" + homepage + "> ;\n";
742 
743             pluginString += "    ] ;\n\n";
744         }
745 
746         {
747             const uint32_t version(plugin.getVersion());
748 
749             const uint32_t majorVersion = (version & 0xFF0000) >> 16;
750             const uint32_t microVersion = (version & 0x00FF00) >> 8;
751             /* */ uint32_t minorVersion = (version & 0x0000FF) >> 0;
752 
753             // NOTE: LV2 ignores 'major' version and says 0 for minor is pre-release/unstable.
754             if (majorVersion > 0)
755                 minorVersion += 2;
756 
757             pluginString += "    lv2:microVersion " + String(microVersion) + " ;\n";
758             pluginString += "    lv2:minorVersion " + String(minorVersion) + " .\n";
759         }
760 
761         pluginFile << pluginString << std::endl;
762         pluginFile.close();
763         std::cout << " done!" << std::endl;
764     }
765 
766     // ---------------------------------------------
767 
768 #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
769     {
770         std::cout << "Writing " << uiTTL << "..."; std::cout.flush();
771         std::fstream uiFile(uiTTL, std::ios::out);
772 
773         String uiString;
774         uiString += "@prefix lv2:  <" LV2_CORE_PREFIX "> .\n";
775         uiString += "@prefix ui:   <" LV2_UI_PREFIX "> .\n";
776         uiString += "@prefix opts: <" LV2_OPTIONS_PREFIX "> .\n";
777         uiString += "\n";
778 
779         uiString += "<" DISTRHO_UI_URI ">\n";
780 
781         addAttribute(uiString, "lv2:extensionData", lv2ManifestUiExtensionData, 4);
782         addAttribute(uiString, "lv2:optionalFeature", lv2ManifestUiOptionalFeatures, 4);
783         addAttribute(uiString, "lv2:requiredFeature", lv2ManifestUiRequiredFeatures, 4);
784         addAttribute(uiString, "opts:supportedOption", lv2ManifestUiSupportedOptions, 4, true);
785 
786         uiFile << uiString << std::endl;
787         uiFile.close();
788         std::cout << " done!" << std::endl;
789     }
790 #endif
791 
792     // ---------------------------------------------
793 
794 #if DISTRHO_PLUGIN_WANT_PROGRAMS
795     {
796         std::cout << "Writing presets.ttl..."; std::cout.flush();
797         std::fstream presetsFile("presets.ttl", std::ios::out);
798 
799         String presetsString;
800         presetsString += "@prefix lv2:   <" LV2_CORE_PREFIX "> .\n";
801         presetsString += "@prefix pset:  <" LV2_PRESETS_PREFIX "> .\n";
802 # if DISTRHO_PLUGIN_WANT_STATE
803         presetsString += "@prefix state: <" LV2_STATE_PREFIX "> .\n";
804 # endif
805         presetsString += "\n";
806 
807         const uint32_t numParameters = plugin.getParameterCount();
808         const uint32_t numPrograms   = plugin.getProgramCount();
809 # if DISTRHO_PLUGIN_WANT_FULL_STATE
810         const uint32_t numStates     = plugin.getStateCount();
811 # endif
812 
813         const String presetSeparator(std::strstr(DISTRHO_PLUGIN_URI, "#") != nullptr ? ":" : "#");
814 
815         char strBuf[0xff+1];
816         strBuf[0xff] = '\0';
817 
818         String presetString;
819 
820         for (uint32_t i=0; i<numPrograms; ++i)
821         {
822             std::snprintf(strBuf, 0xff, "%03i", i+1);
823 
824             plugin.loadProgram(i);
825 
826             presetString = "<" DISTRHO_PLUGIN_URI + presetSeparator + "preset" + strBuf + ">\n";
827 
828 # if DISTRHO_PLUGIN_WANT_FULL_STATE
829             if (numParameters == 0 && numStates == 0)
830 #else
831             if (numParameters == 0)
832 #endif
833             {
834                 presetString += "    .";
835                 presetsString += presetString;
836                 continue;
837             }
838 
839 # if DISTRHO_PLUGIN_WANT_FULL_STATE
840             presetString += "    state:state [\n";
841             for (uint32_t j=0; j<numStates; ++j)
842             {
843                 const String key   = plugin.getStateKey(j);
844                 const String value = plugin.getState(key);
845 
846                 presetString += "        <" DISTRHO_PLUGIN_LV2_STATE_PREFIX + key + ">";
847 
848                 if (value.length() < 10)
849                     presetString += " \"" + value + "\" ;\n";
850                 else
851                     presetString += "\n\"\"\"" + value + "\"\"\" ;\n";
852             }
853 
854             if (numParameters > 0)
855                 presetString += "    ] ;\n\n";
856             else
857                 presetString += "    ] .\n\n";
858 # endif
859 
860             bool firstParameter = true;
861 
862             for (uint32_t j=0; j <numParameters; ++j)
863             {
864                 if (plugin.isParameterOutput(j))
865                     continue;
866 
867                 if (firstParameter)
868                 {
869                     presetString += "    lv2:port [\n";
870                     firstParameter = false;
871                 }
872                 else
873                 {
874                     presetString += "    [\n";
875                 }
876 
877                 String parameterSymbol = plugin.getParameterSymbol(j);
878                 float parameterValue = plugin.getParameterValue(j);
879 
880                 if (plugin.getParameterDesignation(j) == kParameterDesignationBypass)
881                 {
882                     parameterSymbol = DISTRHO_BYPASS_PARAMETER_NAME;
883                     parameterValue = 1.0f - parameterValue;
884                 }
885 
886                 presetString += "        lv2:symbol \"" + parameterSymbol + "\" ;\n";
887 
888                 if (plugin.getParameterHints(j) & kParameterIsInteger)
889                     presetString += "        pset:value " + String(int(parameterValue)) + " ;\n";
890                 else
891                     presetString += "        pset:value " + String(parameterValue) + " ;\n";
892 
893                 if (j+1 == numParameters || plugin.isParameterOutput(j+1))
894                     presetString += "    ] .\n\n";
895                 else
896                     presetString += "    ] ,\n";
897             }
898 
899             presetsString += presetString;
900         }
901 
902         presetsFile << presetsString << std::endl;
903         presetsFile.close();
904         std::cout << " done!" << std::endl;
905     }
906 #endif
907 }
908