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