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