comparison DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoPluginLV2export.cpp @ 3:84e66ea83026

DPF-Prymula-audioplugins-0.231015-2
author prymula <prymula76@outlook.com>
date Mon, 16 Oct 2023 21:53:34 +0200
parents
children
comparison
equal deleted inserted replaced
2:cf2cb71d31dd 3:84e66ea83026
1 /*
2 * DISTRHO Plugin Framework (DPF)
3 * Copyright (C) 2012-2023 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 #include "../DistrhoPluginUtils.hpp"
19
20 #include "lv2/atom.h"
21 #include "lv2/buf-size.h"
22 #include "lv2/data-access.h"
23 #include "lv2/instance-access.h"
24 #include "lv2/midi.h"
25 #include "lv2/options.h"
26 #include "lv2/parameters.h"
27 #include "lv2/patch.h"
28 #include "lv2/port-groups.h"
29 #include "lv2/port-props.h"
30 #include "lv2/presets.h"
31 #include "lv2/resize-port.h"
32 #include "lv2/state.h"
33 #include "lv2/time.h"
34 #include "lv2/ui.h"
35 #include "lv2/units.h"
36 #include "lv2/urid.h"
37 #include "lv2/worker.h"
38 #include "lv2/lv2_kxstudio_properties.h"
39 #include "lv2/lv2_programs.h"
40
41 #ifdef DISTRHO_PLUGIN_LICENSED_FOR_MOD
42 # include "mod-license.h"
43 #endif
44
45 #ifdef DISTRHO_OS_WINDOWS
46 # include <direct.h>
47 #else
48 # include <sys/stat.h>
49 # include <sys/types.h>
50 # include <unistd.h>
51 #endif
52
53 #include <fstream>
54 #include <iostream>
55
56 #ifndef DISTRHO_PLUGIN_URI
57 # error DISTRHO_PLUGIN_URI undefined!
58 #endif
59
60 #ifndef DISTRHO_PLUGIN_LV2_STATE_PREFIX
61 # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:"
62 #endif
63
64 #ifndef DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE
65 # define DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE 2048
66 #endif
67
68 #ifndef DISTRHO_PLUGIN_USES_MODGUI
69 # define DISTRHO_PLUGIN_USES_MODGUI 0
70 #endif
71
72 #ifndef DISTRHO_PLUGIN_USES_CUSTOM_MODGUI
73 # define DISTRHO_PLUGIN_USES_CUSTOM_MODGUI 0
74 #endif
75
76 #if DISTRHO_PLUGIN_HAS_EMBED_UI
77 # if DISTRHO_OS_HAIKU
78 # define DISTRHO_LV2_UI_TYPE "BeUI"
79 # elif DISTRHO_OS_MAC
80 # define DISTRHO_LV2_UI_TYPE "CocoaUI"
81 # elif DISTRHO_OS_WINDOWS
82 # define DISTRHO_LV2_UI_TYPE "WindowsUI"
83 # else
84 # define DISTRHO_LV2_UI_TYPE "X11UI"
85 # endif
86 #else
87 # define DISTRHO_LV2_UI_TYPE "UI"
88 #endif
89
90 #define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
91 #define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)
92
93 #define DISTRHO_BYPASS_PARAMETER_NAME "lv2_enabled"
94
95 // -----------------------------------------------------------------------
96 static const char* const lv2ManifestPluginExtensionData[] =
97 {
98 "opts:interface",
99 #if DISTRHO_PLUGIN_WANT_STATE
100 LV2_STATE__interface,
101 LV2_WORKER__interface,
102 #endif
103 #if DISTRHO_PLUGIN_WANT_PROGRAMS
104 LV2_PROGRAMS__Interface,
105 #endif
106 #ifdef DISTRHO_PLUGIN_LICENSED_FOR_MOD
107 MOD_LICENSE__interface,
108 #endif
109 nullptr
110 };
111
112 static const char* const lv2ManifestPluginOptionalFeatures[] =
113 {
114 #if DISTRHO_PLUGIN_IS_RT_SAFE
115 LV2_CORE__hardRTCapable,
116 #endif
117 LV2_BUF_SIZE__boundedBlockLength,
118 nullptr
119 };
120
121 static const char* const lv2ManifestPluginRequiredFeatures[] =
122 {
123 "opts:options",
124 LV2_URID__map,
125 #if DISTRHO_PLUGIN_WANT_STATE
126 LV2_WORKER__schedule,
127 #endif
128 #ifdef DISTRHO_PLUGIN_LICENSED_FOR_MOD
129 MOD_LICENSE__feature,
130 #endif
131 nullptr
132 };
133
134 static const char* const lv2ManifestPluginSupportedOptions[] =
135 {
136 LV2_BUF_SIZE__nominalBlockLength,
137 LV2_BUF_SIZE__maxBlockLength,
138 LV2_PARAMETERS__sampleRate,
139 nullptr
140 };
141
142 #if DISTRHO_PLUGIN_HAS_UI
143 static const char* const lv2ManifestUiExtensionData[] =
144 {
145 "opts:interface",
146 "ui:idleInterface",
147 "ui:showInterface",
148 #if DISTRHO_PLUGIN_WANT_PROGRAMS
149 LV2_PROGRAMS__UIInterface,
150 #endif
151 nullptr
152 };
153
154 static const char* const lv2ManifestUiOptionalFeatures[] =
155 {
156 #if DISTRHO_PLUGIN_HAS_EMBED_UI
157 # if !DISTRHO_UI_USER_RESIZABLE
158 "ui:noUserResize",
159 # endif
160 "ui:parent",
161 "ui:touch",
162 #endif
163 "ui:requestValue",
164 nullptr
165 };
166
167 static const char* const lv2ManifestUiRequiredFeatures[] =
168 {
169 "opts:options",
170 "ui:idleInterface",
171 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
172 LV2_DATA_ACCESS_URI,
173 LV2_INSTANCE_ACCESS_URI,
174 #endif
175 LV2_URID__map,
176 nullptr
177 };
178
179 static const char* const lv2ManifestUiSupportedOptions[] =
180 {
181 LV2_PARAMETERS__sampleRate,
182 nullptr
183 };
184 #endif // DISTRHO_PLUGIN_HAS_UI
185
186 static void addAttribute(DISTRHO_NAMESPACE::String& text,
187 const char* const attribute,
188 const char* const values[],
189 const uint indent,
190 const bool endInDot = false)
191 {
192 if (values[0] == nullptr)
193 {
194 if (endInDot)
195 {
196 bool found;
197 const size_t index = text.rfind(';', &found);
198 if (found) text[index] = '.';
199 }
200 return;
201 }
202
203 const size_t attributeLength = std::strlen(attribute);
204
205 for (uint i = 0; values[i] != nullptr; ++i)
206 {
207 for (uint j = 0; j < indent; ++j)
208 text += " ";
209
210 if (i == 0)
211 {
212 text += attribute;
213 }
214 else
215 {
216 for (uint j = 0; j < attributeLength; ++j)
217 text += " ";
218 }
219
220 text += " ";
221
222 const bool isUrl = std::strstr(values[i], "://") != nullptr || std::strncmp(values[i], "urn:", 4) == 0;
223 if (isUrl) text += "<";
224 text += values[i];
225 if (isUrl) text += ">";
226 text += values[i + 1] ? " ,\n" : (endInDot ? " .\n\n" : " ;\n\n");
227 }
228 }
229
230 // -----------------------------------------------------------------------
231
232 DISTRHO_PLUGIN_EXPORT
233 void lv2_generate_ttl(const char* const basename)
234 {
235 USE_NAMESPACE_DISTRHO
236
237 String bundlePath(getBinaryFilename());
238 if (bundlePath.isNotEmpty())
239 {
240 bundlePath.truncate(bundlePath.rfind(DISTRHO_OS_SEP));
241 }
242 #ifndef DISTRHO_OS_WINDOWS
243 else if (char* const cwd = ::getcwd(nullptr, 0))
244 {
245 bundlePath = cwd;
246 std::free(cwd);
247 }
248 #endif
249 d_nextBundlePath = bundlePath.buffer();
250
251 // Dummy plugin to get data from
252 d_nextBufferSize = 512;
253 d_nextSampleRate = 44100.0;
254 d_nextPluginIsDummy = true;
255 PluginExporter plugin(nullptr, nullptr, nullptr, nullptr);
256 d_nextBufferSize = 0;
257 d_nextSampleRate = 0.0;
258 d_nextPluginIsDummy = false;
259
260 const String pluginDLL(basename);
261 const String pluginTTL(pluginDLL + ".ttl");
262
263 #if DISTRHO_PLUGIN_HAS_UI
264 String pluginUI(pluginDLL);
265 # if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
266 pluginUI.truncate(pluginDLL.rfind("_dsp"));
267 pluginUI += "_ui";
268 const String uiTTL(pluginUI + ".ttl");
269 # endif
270 #endif
271
272 // ---------------------------------------------
273
274 {
275 std::cout << "Writing manifest.ttl..."; std::cout.flush();
276 std::fstream manifestFile("manifest.ttl", std::ios::out);
277
278 String manifestString;
279 manifestString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
280 manifestString += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
281 #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
282 manifestString += "@prefix opts: <" LV2_OPTIONS_PREFIX "> .\n";
283 #endif
284 #if DISTRHO_PLUGIN_WANT_PROGRAMS
285 manifestString += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n";
286 #endif
287 #if DISTRHO_PLUGIN_HAS_UI
288 manifestString += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
289 #endif
290 manifestString += "\n";
291
292 manifestString += "<" DISTRHO_PLUGIN_URI ">\n";
293 manifestString += " a lv2:Plugin ;\n";
294 manifestString += " lv2:binary <" + pluginDLL + "." DISTRHO_DLL_EXTENSION "> ;\n";
295 #if DISTRHO_PLUGIN_USES_MODGUI
296 manifestString += " rdfs:seeAlso <" + pluginTTL + "> ,\n";
297 manifestString += " <modgui.ttl> .\n";
298 #else
299 manifestString += " rdfs:seeAlso <" + pluginTTL + "> .\n";
300 #endif
301 manifestString += "\n";
302
303 #if DISTRHO_PLUGIN_HAS_UI
304 manifestString += "<" DISTRHO_UI_URI ">\n";
305 manifestString += " a ui:" DISTRHO_LV2_UI_TYPE " ;\n";
306 manifestString += " ui:binary <" + pluginUI + "." DISTRHO_DLL_EXTENSION "> ;\n";
307 # if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
308 addAttribute(manifestString, "lv2:extensionData", lv2ManifestUiExtensionData, 4);
309 addAttribute(manifestString, "lv2:optionalFeature", lv2ManifestUiOptionalFeatures, 4);
310 addAttribute(manifestString, "lv2:requiredFeature", lv2ManifestUiRequiredFeatures, 4);
311 addAttribute(manifestString, "opts:supportedOption", lv2ManifestUiSupportedOptions, 4, true);
312 # else // DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
313 manifestString += " rdfs:seeAlso <" + uiTTL + "> .\n";
314 # endif // DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
315 manifestString += "\n";
316 #endif
317
318 #if DISTRHO_PLUGIN_WANT_PROGRAMS
319 const String presetSeparator(std::strstr(DISTRHO_PLUGIN_URI, "#") != nullptr ? ":" : "#");
320
321 char strBuf[0xff+1];
322 strBuf[0xff] = '\0';
323
324 String presetString;
325
326 // Presets
327 for (uint32_t i = 0; i < plugin.getProgramCount(); ++i)
328 {
329 std::snprintf(strBuf, 0xff, "%03i", i+1);
330
331 presetString = "<" DISTRHO_PLUGIN_URI + presetSeparator + "preset" + strBuf + ">\n";
332 presetString += " a pset:Preset ;\n";
333 presetString += " lv2:appliesTo <" DISTRHO_PLUGIN_URI "> ;\n";
334 presetString += " rdfs:label \"" + plugin.getProgramName(i) + "\" ;\n";
335 presetString += " rdfs:seeAlso <presets.ttl> .\n";
336 presetString += "\n";
337
338 manifestString += presetString;
339 }
340 #endif
341
342 manifestFile << manifestString;
343 manifestFile.close();
344 std::cout << " done!" << std::endl;
345 }
346
347 // ---------------------------------------------
348
349 {
350 std::cout << "Writing " << pluginTTL << "..."; std::cout.flush();
351 std::fstream pluginFile(pluginTTL, std::ios::out);
352
353 String pluginString;
354
355 // header
356 #if DISTRHO_LV2_USE_EVENTS_IN || DISTRHO_LV2_USE_EVENTS_OUT
357 pluginString += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n";
358 #endif
359 pluginString += "@prefix doap: <http://usefulinc.com/ns/doap#> .\n";
360 pluginString += "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n";
361 pluginString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
362 pluginString += "@prefix midi: <" LV2_MIDI_PREFIX "> .\n";
363 pluginString += "@prefix mod: <http://moddevices.com/ns/mod#> .\n";
364 pluginString += "@prefix opts: <" LV2_OPTIONS_PREFIX "> .\n";
365 pluginString += "@prefix pg: <" LV2_PORT_GROUPS_PREFIX "> .\n";
366 pluginString += "@prefix patch: <" LV2_PATCH_PREFIX "> .\n";
367 pluginString += "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n";
368 pluginString += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
369 #if DISTRHO_LV2_USE_EVENTS_IN || DISTRHO_LV2_USE_EVENTS_OUT
370 pluginString += "@prefix rsz: <" LV2_RESIZE_PORT_PREFIX "> .\n";
371 #endif
372 pluginString += "@prefix spdx: <http://spdx.org/rdf/terms#> .\n";
373 #if DISTRHO_PLUGIN_HAS_UI
374 pluginString += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
375 #endif
376 pluginString += "@prefix unit: <" LV2_UNITS_PREFIX "> .\n";
377 pluginString += "\n";
378
379 #if DISTRHO_PLUGIN_WANT_STATE
380 bool hasHostVisibleState = false;
381
382 for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
383 {
384 const uint32_t hints = plugin.getStateHints(i);
385
386 if ((hints & kStateIsHostReadable) == 0x0)
387 continue;
388
389 pluginString += "<" DISTRHO_PLUGIN_URI "#" + plugin.getStateKey(i) + ">\n";
390 pluginString += " a lv2:Parameter ;\n";
391 pluginString += " rdfs:label \"" + plugin.getStateLabel(i) + "\" ;\n";
392
393 const String& comment(plugin.getStateDescription(i));
394
395 if (comment.isNotEmpty())
396 {
397 if (comment.contains('"') || comment.contains('\n'))
398 pluginString += " rdfs:comment \"\"\"" + comment + "\"\"\" ;\n";
399 else
400 pluginString += " rdfs:comment \"" + comment + "\" ;\n";
401 }
402
403 if ((hints & kStateIsFilenamePath) == kStateIsFilenamePath)
404 {
405 #ifdef __MOD_DEVICES__
406 const String& fileTypes(plugin.getStateFileTypes(i));
407 if (fileTypes.isNotEmpty())
408 pluginString += " mod:fileTypes \"" + fileTypes + "\" ; \n";
409 #endif
410 pluginString += " rdfs:range atom:Path .\n\n";
411 }
412 else
413 {
414 pluginString += " rdfs:range atom:String .\n\n";
415 }
416
417 hasHostVisibleState = true;
418 }
419 #endif
420
421 // plugin
422 pluginString += "<" DISTRHO_PLUGIN_URI ">\n";
423 #ifdef DISTRHO_PLUGIN_LV2_CATEGORY
424 pluginString += " a " DISTRHO_PLUGIN_LV2_CATEGORY ", lv2:Plugin, doap:Project ;\n";
425 #elif DISTRHO_PLUGIN_IS_SYNTH
426 pluginString += " a lv2:InstrumentPlugin, lv2:Plugin, doap:Project ;\n";
427 #else
428 pluginString += " a lv2:Plugin, doap:Project ;\n";
429 #endif
430 pluginString += "\n";
431
432 addAttribute(pluginString, "lv2:extensionData", lv2ManifestPluginExtensionData, 4);
433 addAttribute(pluginString, "lv2:optionalFeature", lv2ManifestPluginOptionalFeatures, 4);
434 addAttribute(pluginString, "lv2:requiredFeature", lv2ManifestPluginRequiredFeatures, 4);
435 addAttribute(pluginString, "opts:supportedOption", lv2ManifestPluginSupportedOptions, 4);
436
437 #if DISTRHO_PLUGIN_WANT_STATE
438 if (hasHostVisibleState)
439 {
440 for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
441 {
442 const uint32_t hints = plugin.getStateHints(i);
443
444 if ((hints & kStateIsHostReadable) == 0x0)
445 continue;
446
447 const String& key(plugin.getStateKey(i));
448
449 if ((hints & kStateIsHostWritable) == kStateIsHostWritable)
450 pluginString += " patch:writable <" DISTRHO_PLUGIN_URI "#" + key + "> ;\n";
451 else
452 pluginString += " patch:readable <" DISTRHO_PLUGIN_URI "#" + key + "> ;\n";
453 }
454 pluginString += "\n";
455 }
456 #endif
457
458 // UI
459 #if DISTRHO_PLUGIN_HAS_UI
460 pluginString += " ui:ui <" DISTRHO_UI_URI "> ;\n";
461 pluginString += "\n";
462 #endif
463
464 {
465 uint32_t portIndex = 0;
466
467 #if DISTRHO_PLUGIN_NUM_INPUTS > 0
468 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i, ++portIndex)
469 {
470 const AudioPort& port(plugin.getAudioPort(true, i));
471 const bool cvPortScaled = port.hints & kCVPortHasScaledRange;
472
473 if (i == 0)
474 pluginString += " lv2:port [\n";
475 else
476 pluginString += " [\n";
477
478 if (cvPortScaled)
479 pluginString += " a lv2:InputPort, lv2:CVPort, mod:CVPort ;\n";
480 else if (port.hints & kAudioPortIsCV)
481 pluginString += " a lv2:InputPort, lv2:CVPort ;\n";
482 else
483 pluginString += " a lv2:InputPort, lv2:AudioPort ;\n";
484
485 pluginString += " lv2:index " + String(portIndex) + " ;\n";
486 pluginString += " lv2:symbol \"lv2_" + port.symbol + "\" ;\n";
487 pluginString += " lv2:name \"" + port.name + "\" ;\n";
488
489 if (port.hints & kAudioPortIsSidechain)
490 pluginString += " lv2:portProperty lv2:isSideChain;\n";
491
492 if (port.groupId != kPortGroupNone)
493 {
494 pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_"
495 + plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n";
496
497 switch (port.groupId)
498 {
499 case kPortGroupMono:
500 pluginString += " lv2:designation pg:center ;\n";
501 break;
502 case kPortGroupStereo:
503 if (i == 1)
504 pluginString += " lv2:designation pg:right ;\n";
505 else
506 pluginString += " lv2:designation pg:left ;\n";
507 break;
508 }
509 }
510
511 // set ranges
512 if (port.hints & kCVPortHasBipolarRange)
513 {
514 if (cvPortScaled)
515 {
516 pluginString += " lv2:minimum -5.0 ;\n";
517 pluginString += " lv2:maximum 5.0 ;\n";
518 }
519 else
520 {
521 pluginString += " lv2:minimum -1.0 ;\n";
522 pluginString += " lv2:maximum 1.0 ;\n";
523 }
524 }
525 else if (port.hints & kCVPortHasNegativeUnipolarRange)
526 {
527 if (cvPortScaled)
528 {
529 pluginString += " lv2:minimum -10.0 ;\n";
530 pluginString += " lv2:maximum 0.0 ;\n";
531 }
532 else
533 {
534 pluginString += " lv2:minimum -1.0 ;\n";
535 pluginString += " lv2:maximum 0.0 ;\n";
536 }
537 }
538 else if (port.hints & kCVPortHasPositiveUnipolarRange)
539 {
540 if (cvPortScaled)
541 {
542 pluginString += " lv2:minimum 0.0 ;\n";
543 pluginString += " lv2:maximum 10.0 ;\n";
544 }
545 else
546 {
547 pluginString += " lv2:minimum 0.0 ;\n";
548 pluginString += " lv2:maximum 1.0 ;\n";
549 }
550 }
551
552 if ((port.hints & (kAudioPortIsCV|kCVPortIsOptional)) == (kAudioPortIsCV|kCVPortIsOptional))
553 pluginString += " lv2:portProperty lv2:connectionOptional;\n";
554
555 if (i+1 == DISTRHO_PLUGIN_NUM_INPUTS)
556 pluginString += " ] ;\n";
557 else
558 pluginString += " ] ,\n";
559 }
560 pluginString += "\n";
561 #endif
562
563 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
564 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i, ++portIndex)
565 {
566 const AudioPort& port(plugin.getAudioPort(false, i));
567 const bool cvPortScaled = port.hints & kCVPortHasScaledRange;
568
569 if (i == 0)
570 pluginString += " lv2:port [\n";
571 else
572 pluginString += " [\n";
573
574 if (cvPortScaled)
575 pluginString += " a lv2:OutputPort, lv2:CVPort, mod:CVPort ;\n";
576 else if (port.hints & kAudioPortIsCV)
577 pluginString += " a lv2:OutputPort, lv2:CVPort ;\n";
578 else
579 pluginString += " a lv2:OutputPort, lv2:AudioPort ;\n";
580
581 pluginString += " lv2:index " + String(portIndex) + " ;\n";
582 pluginString += " lv2:symbol \"lv2_" + port.symbol + "\" ;\n";
583 pluginString += " lv2:name \"" + port.name + "\" ;\n";
584
585 if (port.hints & kAudioPortIsSidechain)
586 pluginString += " lv2:portProperty lv2:isSideChain;\n";
587
588 if (port.groupId != kPortGroupNone)
589 {
590 pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_"
591 + plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n";
592
593 switch (port.groupId)
594 {
595 case kPortGroupMono:
596 pluginString += " lv2:designation pg:center ;\n";
597 break;
598 case kPortGroupStereo:
599 if (i == 1)
600 pluginString += " lv2:designation pg:right ;\n";
601 else
602 pluginString += " lv2:designation pg:left ;\n";
603 break;
604 }
605 }
606
607 // set ranges
608 if (port.hints & kCVPortHasBipolarRange)
609 {
610 if (cvPortScaled)
611 {
612 pluginString += " lv2:minimum -5.0 ;\n";
613 pluginString += " lv2:maximum 5.0 ;\n";
614 }
615 else
616 {
617 pluginString += " lv2:minimum -1.0 ;\n";
618 pluginString += " lv2:maximum 1.0 ;\n";
619 }
620 }
621 else if (port.hints & kCVPortHasNegativeUnipolarRange)
622 {
623 if (cvPortScaled)
624 {
625 pluginString += " lv2:minimum -10.0 ;\n";
626 pluginString += " lv2:maximum 0.0 ;\n";
627 }
628 else
629 {
630 pluginString += " lv2:minimum -1.0 ;\n";
631 pluginString += " lv2:maximum 0.0 ;\n";
632 }
633 }
634 else if (port.hints & kCVPortHasPositiveUnipolarRange)
635 {
636 if (cvPortScaled)
637 {
638 pluginString += " lv2:minimum 0.0 ;\n";
639 pluginString += " lv2:maximum 10.0 ;\n";
640 }
641 else
642 {
643 pluginString += " lv2:minimum 0.0 ;\n";
644 pluginString += " lv2:maximum 1.0 ;\n";
645 }
646 }
647
648 if (i+1 == DISTRHO_PLUGIN_NUM_OUTPUTS)
649 pluginString += " ] ;\n";
650 else
651 pluginString += " ] ,\n";
652 }
653 pluginString += "\n";
654 #endif
655
656 #if DISTRHO_LV2_USE_EVENTS_IN
657 pluginString += " lv2:port [\n";
658 pluginString += " a lv2:InputPort, atom:AtomPort ;\n";
659 pluginString += " lv2:index " + String(portIndex) + " ;\n";
660 pluginString += " lv2:name \"Events Input\" ;\n";
661 pluginString += " lv2:symbol \"lv2_events_in\" ;\n";
662 pluginString += " rsz:minimumSize " + String(DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE) + " ;\n";
663 pluginString += " atom:bufferType atom:Sequence ;\n";
664 # if (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)
665 pluginString += " atom:supports atom:String ;\n";
666 # endif
667 # if DISTRHO_PLUGIN_WANT_MIDI_INPUT
668 pluginString += " atom:supports midi:MidiEvent ;\n";
669 # endif
670 # if DISTRHO_PLUGIN_WANT_TIMEPOS
671 pluginString += " atom:supports <" LV2_TIME__Position "> ;\n";
672 # endif
673 # if DISTRHO_PLUGIN_WANT_STATE
674 if (hasHostVisibleState)
675 {
676 pluginString += " atom:supports <" LV2_PATCH__Message "> ;\n";
677 pluginString += " lv2:designation lv2:control ;\n";
678 }
679 # endif
680 pluginString += " ] ;\n\n";
681 ++portIndex;
682 #endif
683
684 #if DISTRHO_LV2_USE_EVENTS_OUT
685 pluginString += " lv2:port [\n";
686 pluginString += " a lv2:OutputPort, atom:AtomPort ;\n";
687 pluginString += " lv2:index " + String(portIndex) + " ;\n";
688 pluginString += " lv2:name \"Events Output\" ;\n";
689 pluginString += " lv2:symbol \"lv2_events_out\" ;\n";
690 pluginString += " rsz:minimumSize " + String(DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE) + " ;\n";
691 pluginString += " atom:bufferType atom:Sequence ;\n";
692 # if (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)
693 pluginString += " atom:supports atom:String ;\n";
694 # endif
695 # if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
696 pluginString += " atom:supports midi:MidiEvent ;\n";
697 # endif
698 # if DISTRHO_PLUGIN_WANT_STATE
699 if (hasHostVisibleState)
700 {
701 pluginString += " atom:supports <" LV2_PATCH__Message "> ;\n";
702 pluginString += " lv2:designation lv2:control ;\n";
703 }
704 # endif
705 pluginString += " ] ;\n\n";
706 ++portIndex;
707 #endif
708
709 #if DISTRHO_PLUGIN_WANT_LATENCY
710 pluginString += " lv2:port [\n";
711 pluginString += " a lv2:OutputPort, lv2:ControlPort ;\n";
712 pluginString += " lv2:index " + String(portIndex) + " ;\n";
713 pluginString += " lv2:name \"Latency\" ;\n";
714 pluginString += " lv2:symbol \"lv2_latency\" ;\n";
715 pluginString += " lv2:designation lv2:latency ;\n";
716 pluginString += " lv2:portProperty lv2:reportsLatency, lv2:integer, <" LV2_PORT_PROPS__notOnGUI "> ;\n";
717 pluginString += " ] ;\n\n";
718 ++portIndex;
719 #endif
720
721 for (uint32_t i=0, count=plugin.getParameterCount(); i < count; ++i, ++portIndex)
722 {
723 if (i == 0)
724 pluginString += " lv2:port [\n";
725 else
726 pluginString += " [\n";
727
728 if (plugin.isParameterOutput(i))
729 pluginString += " a lv2:OutputPort, lv2:ControlPort ;\n";
730 else
731 pluginString += " a lv2:InputPort, lv2:ControlPort ;\n";
732
733 pluginString += " lv2:index " + String(portIndex) + " ;\n";
734
735 bool designated = false;
736
737 // designation
738 if (plugin.isParameterInput(i))
739 {
740 switch (plugin.getParameterDesignation(i))
741 {
742 case kParameterDesignationNull:
743 break;
744 case kParameterDesignationBypass:
745 designated = true;
746 pluginString += " lv2:name \"Enabled\" ;\n";
747 pluginString += " lv2:symbol \"" DISTRHO_BYPASS_PARAMETER_NAME "\" ;\n";
748 pluginString += " lv2:default 1 ;\n";
749 pluginString += " lv2:minimum 0 ;\n";
750 pluginString += " lv2:maximum 1 ;\n";
751 pluginString += " lv2:portProperty lv2:toggled , lv2:integer ;\n";
752 pluginString += " lv2:designation lv2:enabled ;\n";
753 break;
754 }
755 }
756
757 if (! designated)
758 {
759 const uint32_t hints = plugin.getParameterHints(i);
760
761 // name and symbol
762 const String& paramName(plugin.getParameterName(i));
763
764 if (paramName.contains('"'))
765 pluginString += " lv2:name \"\"\"" + paramName + "\"\"\" ;\n";
766 else
767 pluginString += " lv2:name \"" + paramName + "\" ;\n";
768
769 String symbol(plugin.getParameterSymbol(i));
770
771 if (symbol.isEmpty())
772 symbol = "lv2_port_" + String(portIndex-1);
773
774 pluginString += " lv2:symbol \"" + symbol + "\" ;\n";
775
776 // short name
777 const String& shortName(plugin.getParameterShortName(i));
778
779 if (shortName.isNotEmpty())
780 pluginString += " lv2:shortName \"\"\"" + shortName + "\"\"\" ;\n";
781
782 // ranges
783 const ParameterRanges& ranges(plugin.getParameterRanges(i));
784
785 if (hints & kParameterIsInteger)
786 {
787 if (plugin.isParameterInput(i))
788 pluginString += " lv2:default " + String(int(ranges.def)) + " ;\n";
789 pluginString += " lv2:minimum " + String(int(ranges.min)) + " ;\n";
790 pluginString += " lv2:maximum " + String(int(ranges.max)) + " ;\n";
791 }
792 else if (hints & kParameterIsLogarithmic)
793 {
794 if (plugin.isParameterInput(i))
795 {
796 if (d_isNotZero(ranges.def))
797 pluginString += " lv2:default " + String(ranges.def) + " ;\n";
798 else if (d_isEqual(ranges.def, ranges.max))
799 pluginString += " lv2:default -0.0001 ;\n";
800 else
801 pluginString += " lv2:default 0.0001 ;\n";
802 }
803
804 if (d_isNotZero(ranges.min))
805 pluginString += " lv2:minimum " + String(ranges.min) + " ;\n";
806 else
807 pluginString += " lv2:minimum 0.0001 ;\n";
808
809 if (d_isNotZero(ranges.max))
810 pluginString += " lv2:maximum " + String(ranges.max) + " ;\n";
811 else
812 pluginString += " lv2:maximum -0.0001 ;\n";
813 }
814 else
815 {
816 if (plugin.isParameterInput(i))
817 pluginString += " lv2:default " + String(ranges.def) + " ;\n";
818 pluginString += " lv2:minimum " + String(ranges.min) + " ;\n";
819 pluginString += " lv2:maximum " + String(ranges.max) + " ;\n";
820 }
821
822 // enumeration
823 const ParameterEnumerationValues& enumValues(plugin.getParameterEnumValues(i));
824
825 if (enumValues.count > 0)
826 {
827 if (enumValues.count >= 2 && enumValues.restrictedMode)
828 pluginString += " lv2:portProperty lv2:enumeration ;\n";
829
830 for (uint8_t j=0; j < enumValues.count; ++j)
831 {
832 const ParameterEnumerationValue& enumValue(enumValues.values[j]);
833
834 if (j == 0)
835 pluginString += " lv2:scalePoint [\n";
836 else
837 pluginString += " [\n";
838
839 if (enumValue.label.contains('"'))
840 pluginString += " rdfs:label \"\"\"" + enumValue.label + "\"\"\" ;\n";
841 else
842 pluginString += " rdfs:label \"" + enumValue.label + "\" ;\n";
843
844 if (hints & kParameterIsInteger)
845 {
846 const int rounded = (int)(enumValue.value + (enumValue.value < 0.0f ? -0.5f : 0.5f));
847 pluginString += " rdf:value " + String(rounded) + " ;\n";
848 }
849 else
850 {
851 pluginString += " rdf:value " + String(enumValue.value) + " ;\n";
852 }
853
854 if (j+1 == enumValues.count)
855 pluginString += " ] ;\n";
856 else
857 pluginString += " ] ,\n";
858 }
859 }
860
861 // MIDI CC binding
862 if (const uint8_t midiCC = plugin.getParameterMidiCC(i))
863 {
864 pluginString += " midi:binding [\n";
865 pluginString += " a midi:Controller ;\n";
866 pluginString += " midi:controllerNumber " + String(midiCC) + " ;\n";
867 pluginString += " ] ;\n";
868 }
869
870 // unit
871 const String& unit(plugin.getParameterUnit(i));
872
873 if (unit.isNotEmpty() && ! unit.contains(' '))
874 {
875 String lunit(unit);
876 lunit.toLower();
877
878 /**/ if (lunit == "db")
879 {
880 pluginString += " unit:unit unit:db ;\n";
881 }
882 else if (lunit == "hz")
883 {
884 pluginString += " unit:unit unit:hz ;\n";
885 }
886 else if (lunit == "khz")
887 {
888 pluginString += " unit:unit unit:khz ;\n";
889 }
890 else if (lunit == "mhz")
891 {
892 pluginString += " unit:unit unit:mhz ;\n";
893 }
894 else if (lunit == "ms")
895 {
896 pluginString += " unit:unit unit:ms ;\n";
897 }
898 else if (lunit == "s")
899 {
900 pluginString += " unit:unit unit:s ;\n";
901 }
902 else if (lunit == "%")
903 {
904 pluginString += " unit:unit unit:pc ;\n";
905 }
906 else
907 {
908 pluginString += " unit:unit [\n";
909 pluginString += " a unit:Unit ;\n";
910 pluginString += " rdfs:label \"" + unit + "\" ;\n";
911 pluginString += " unit:symbol \"" + unit + "\" ;\n";
912 if (hints & kParameterIsInteger)
913 pluginString += " unit:render \"%d " + unit + "\" ;\n";
914 else
915 pluginString += " unit:render \"%f " + unit + "\" ;\n";
916 pluginString += " ] ;\n";
917 }
918 }
919
920 // comment
921 const String& comment(plugin.getParameterDescription(i));
922
923 if (comment.isNotEmpty())
924 {
925 if (comment.contains('"') || comment.contains('\n'))
926 pluginString += " rdfs:comment \"\"\"" + comment + "\"\"\" ;\n";
927 else
928 pluginString += " rdfs:comment \"" + comment + "\" ;\n";
929 }
930
931 // hints
932 if (hints & kParameterIsBoolean)
933 {
934 if ((hints & kParameterIsTrigger) == kParameterIsTrigger)
935 pluginString += " lv2:portProperty <" LV2_PORT_PROPS__trigger "> ;\n";
936 pluginString += " lv2:portProperty lv2:toggled ;\n";
937 }
938 if (hints & kParameterIsInteger)
939 pluginString += " lv2:portProperty lv2:integer ;\n";
940 if (hints & kParameterIsLogarithmic)
941 pluginString += " lv2:portProperty <" LV2_PORT_PROPS__logarithmic "> ;\n";
942 if (hints & kParameterIsHidden)
943 pluginString += " lv2:portProperty <" LV2_PORT_PROPS__notOnGUI "> ;\n";
944 if ((hints & kParameterIsAutomatable) == 0 && plugin.isParameterInput(i))
945 {
946 pluginString += " lv2:portProperty <" LV2_PORT_PROPS__expensive "> ,\n";
947 pluginString += " <" LV2_KXSTUDIO_PROPERTIES__NonAutomatable "> ;\n";
948 }
949
950 // group
951 const uint32_t groupId = plugin.getParameterGroupId(i);
952
953 if (groupId != kPortGroupNone)
954 pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_"
955 + plugin.getPortGroupSymbolForId(groupId) + "> ;\n";
956
957 } // ! designated
958
959 if (i+1 == count)
960 pluginString += " ] ;\n\n";
961 else
962 pluginString += " ] ,\n";
963 }
964 }
965
966 // comment
967 {
968 const String comment(plugin.getDescription());
969
970 if (comment.isNotEmpty())
971 {
972 if (comment.contains('"') || comment.contains('\n'))
973 pluginString += " rdfs:comment \"\"\"" + comment + "\"\"\" ;\n\n";
974 else
975 pluginString += " rdfs:comment \"" + comment + "\" ;\n\n";
976 }
977 }
978
979 #ifdef DISTRHO_PLUGIN_BRAND
980 // MOD
981 pluginString += " mod:brand \"" DISTRHO_PLUGIN_BRAND "\" ;\n";
982 pluginString += " mod:label \"" DISTRHO_PLUGIN_NAME "\" ;\n\n";
983 #endif
984
985 // name
986 {
987 const String name(plugin.getName());
988
989 if (name.contains('"'))
990 pluginString += " doap:name \"\"\"" + name + "\"\"\" ;\n";
991 else
992 pluginString += " doap:name \"" + name + "\" ;\n";
993 }
994
995 // license
996 {
997 const String license(plugin.getLicense());
998
999 // Using URL as license
1000 if (license.contains("://"))
1001 {
1002 pluginString += " doap:license <" + license + "> ;\n\n";
1003 }
1004 // String contaning quotes, use as-is
1005 else if (license.contains('"'))
1006 {
1007 pluginString += " doap:license \"\"\"" + license + "\"\"\" ;\n\n";
1008 }
1009 // Regular license string, convert to URL as much as we can
1010 else
1011 {
1012 const String uplicense(license.asUpper());
1013
1014 // for reference, see https://spdx.org/licenses/
1015
1016 // common licenses
1017 /**/ if (uplicense == "AGPL-1.0-ONLY" ||
1018 uplicense == "AGPL1" ||
1019 uplicense == "AGPLV1")
1020 {
1021 pluginString += " doap:license <http://spdx.org/licenses/AGPL-1.0-only.html> ;\n\n";
1022 }
1023 else if (uplicense == "AGPL-1.0-OR-LATER" ||
1024 uplicense == "AGPL1+" ||
1025 uplicense == "AGPLV1+")
1026 {
1027 pluginString += " doap:license <http://spdx.org/licenses/AGPL-1.0-or-later.html> ;\n\n";
1028 }
1029 else if (uplicense == "AGPL-3.0-ONLY" ||
1030 uplicense == "AGPL3" ||
1031 uplicense == "AGPLV3")
1032 {
1033 pluginString += " doap:license <http://spdx.org/licenses/AGPL-3.0-only.html> ;\n\n";
1034 }
1035 else if (uplicense == "AGPL-3.0-OR-LATER" ||
1036 uplicense == "AGPL3+" ||
1037 uplicense == "AGPLV3+")
1038 {
1039 pluginString += " doap:license <http://spdx.org/licenses/AGPL-3.0-or-later.html> ;\n\n";
1040 }
1041 else if (uplicense == "APACHE-2.0" ||
1042 uplicense == "APACHE2" ||
1043 uplicense == "APACHE-2")
1044 {
1045 pluginString += " doap:license <http://spdx.org/licenses/Apache-2.0.html> ;\n\n";
1046 }
1047 else if (uplicense == "BSD-2-CLAUSE" ||
1048 uplicense == "BSD2" ||
1049 uplicense == "BSD-2")
1050 {
1051 pluginString += " doap:license <http://spdx.org/licenses/BSD-2-Clause.html> ;\n\n";
1052 }
1053 else if (uplicense == "BSD-3-CLAUSE" ||
1054 uplicense == "BSD3" ||
1055 uplicense == "BSD-3")
1056 {
1057 pluginString += " doap:license <http://spdx.org/licenses/BSD-3-Clause.html> ;\n\n";
1058 }
1059 else if (uplicense == "GPL-2.0-ONLY" ||
1060 uplicense == "GPL2" ||
1061 uplicense == "GPLV2")
1062 {
1063 pluginString += " doap:license <http://spdx.org/licenses/GPL-2.0-only.html> ;\n\n";
1064 }
1065 else if (uplicense == "GPL-2.0-OR-LATER" ||
1066 uplicense == "GPL2+" ||
1067 uplicense == "GPLV2+" ||
1068 uplicense == "GPLV2.0+" ||
1069 uplicense == "GPL V2+")
1070 {
1071 pluginString += " doap:license <http://spdx.org/licenses/GPL-2.0-or-later.html> ;\n\n";
1072 }
1073 else if (uplicense == "GPL-3.0-ONLY" ||
1074 uplicense == "GPL3" ||
1075 uplicense == "GPLV3")
1076 {
1077 pluginString += " doap:license <http://spdx.org/licenses/GPL-3.0-only.html> ;\n\n";
1078 }
1079 else if (uplicense == "GPL-3.0-OR-LATER" ||
1080 uplicense == "GPL3+" ||
1081 uplicense == "GPLV3+" ||
1082 uplicense == "GPLV3.0+" ||
1083 uplicense == "GPL V3+")
1084 {
1085 pluginString += " doap:license <http://spdx.org/licenses/GPL-3.0-or-later.html> ;\n\n";
1086 }
1087 else if (uplicense == "ISC")
1088 {
1089 pluginString += " doap:license <http://spdx.org/licenses/ISC.html> ;\n\n";
1090 }
1091 else if (uplicense == "LGPL-2.0-ONLY" ||
1092 uplicense == "LGPL2" ||
1093 uplicense == "LGPLV2")
1094 {
1095 pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.0-only.html> ;\n\n";
1096 }
1097 else if (uplicense == "LGPL-2.0-OR-LATER" ||
1098 uplicense == "LGPL2+" ||
1099 uplicense == "LGPLV2+")
1100 {
1101 pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.0-or-later.html> ;\n\n";
1102 }
1103 else if (uplicense == "LGPL-2.1-ONLY" ||
1104 uplicense == "LGPL2.1" ||
1105 uplicense == "LGPLV2.1")
1106 {
1107 pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.1-only.html> ;\n\n";
1108 }
1109 else if (uplicense == "LGPL-2.1-OR-LATER" ||
1110 uplicense == "LGPL2.1+" ||
1111 uplicense == "LGPLV2.1+")
1112 {
1113 pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.1-or-later.html> ;\n\n";
1114 }
1115 else if (uplicense == "LGPL-3.0-ONLY" ||
1116 uplicense == "LGPL3" ||
1117 uplicense == "LGPLV3")
1118 {
1119 pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.0-only.html> ;\n\n";
1120 }
1121 else if (uplicense == "LGPL-3.0-OR-LATER" ||
1122 uplicense == "LGPL3+" ||
1123 uplicense == "LGPLV3+")
1124 {
1125 pluginString += " doap:license <http://spdx.org/licenses/LGPL-3.0-or-later.html> ;\n\n";
1126 }
1127 else if (uplicense == "MIT")
1128 {
1129 pluginString += " doap:license <http://spdx.org/licenses/MIT.html> ;\n\n";
1130 }
1131
1132 // generic fallbacks
1133 else if (uplicense.startsWith("GPL"))
1134 {
1135 pluginString += " doap:license <http://opensource.org/licenses/gpl-license> ;\n\n";
1136 }
1137 else if (uplicense.startsWith("LGPL"))
1138 {
1139 pluginString += " doap:license <http://opensource.org/licenses/lgpl-license> ;\n\n";
1140 }
1141
1142 // unknown or not handled yet, log a warning
1143 else
1144 {
1145 d_stderr("Unknown license string '%s'", license.buffer());
1146 pluginString += " doap:license \"" + license + "\" ;\n\n";
1147 }
1148 }
1149 }
1150
1151 // developer
1152 {
1153 const String homepage(plugin.getHomePage());
1154 const String maker(plugin.getMaker());
1155
1156 pluginString += " doap:maintainer [\n";
1157
1158 if (maker.contains('"'))
1159 pluginString += " foaf:name \"\"\"" + maker + "\"\"\" ;\n";
1160 else
1161 pluginString += " foaf:name \"" + maker + "\" ;\n";
1162
1163 if (homepage.isNotEmpty())
1164 pluginString += " foaf:homepage <" + homepage + "> ;\n";
1165
1166 pluginString += " ] ;\n\n";
1167 }
1168
1169 {
1170 const uint32_t version(plugin.getVersion());
1171
1172 const uint32_t majorVersion = (version & 0xFF0000) >> 16;
1173 /* */ uint32_t minorVersion = (version & 0x00FF00) >> 8;
1174 const uint32_t microVersion = (version & 0x0000FF) >> 0;
1175
1176 // NOTE: LV2 ignores 'major' version and says 0 for minor is pre-release/unstable.
1177 if (majorVersion > 0)
1178 minorVersion += 2;
1179
1180 pluginString += " lv2:microVersion " + String(microVersion) + " ;\n";
1181 pluginString += " lv2:minorVersion " + String(minorVersion) + " .\n";
1182 }
1183
1184 // port groups
1185 if (const uint32_t portGroupCount = plugin.getPortGroupCount())
1186 {
1187 bool isInput, isOutput;
1188
1189 for (uint32_t i = 0; i < portGroupCount; ++i)
1190 {
1191 const PortGroupWithId& portGroup(plugin.getPortGroupByIndex(i));
1192 DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.groupId != kPortGroupNone);
1193 DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.symbol.isNotEmpty());
1194
1195 pluginString += "\n<" DISTRHO_PLUGIN_URI "#portGroup_" + portGroup.symbol + ">\n";
1196 isInput = isOutput = false;
1197
1198 #if DISTRHO_PLUGIN_NUM_INPUTS > 0
1199 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS && !isInput; ++i)
1200 isInput = plugin.getAudioPort(true, i).groupId == portGroup.groupId;
1201 #endif
1202
1203 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
1204 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS && !isOutput; ++i)
1205 isOutput = plugin.getAudioPort(false, i).groupId == portGroup.groupId;
1206 #endif
1207
1208 for (uint32_t i=0, count=plugin.getParameterCount(); i < count && (!isInput || !isOutput); ++i)
1209 {
1210 if (plugin.getParameterGroupId(i) == portGroup.groupId)
1211 {
1212 isInput = isInput || plugin.isParameterInput(i);
1213 isOutput = isOutput || plugin.isParameterOutput(i);
1214 }
1215 }
1216
1217 pluginString += " a ";
1218
1219 if (isInput && !isOutput)
1220 pluginString += "pg:InputGroup";
1221 else if (isOutput && !isInput)
1222 pluginString += "pg:OutputGroup";
1223 else
1224 pluginString += "pg:Group";
1225
1226 switch (portGroup.groupId)
1227 {
1228 case kPortGroupMono:
1229 pluginString += " , pg:MonoGroup";
1230 break;
1231 case kPortGroupStereo:
1232 pluginString += " , pg:StereoGroup";
1233 break;
1234 }
1235
1236 pluginString += " ;\n";
1237
1238 // pluginString += " rdfs:label \"" + portGroup.name + "\" ;\n";
1239 pluginString += " lv2:name \"" + portGroup.name + "\" ;\n";
1240 pluginString += " lv2:symbol \"" + portGroup.symbol + "\" .\n";
1241 }
1242 }
1243
1244 pluginFile << pluginString;
1245 pluginFile.close();
1246 std::cout << " done!" << std::endl;
1247 }
1248
1249 #if DISTRHO_PLUGIN_USES_MODGUI && !DISTRHO_PLUGIN_USES_CUSTOM_MODGUI
1250 {
1251 std::cout << "Writing modgui.ttl..."; std::cout.flush();
1252 std::fstream modguiFile("modgui.ttl", std::ios::out);
1253
1254 String modguiString;
1255 modguiString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
1256 modguiString += "@prefix modgui: <http://moddevices.com/ns/modgui#> .\n";
1257 modguiString += "\n";
1258
1259 modguiString += "<" DISTRHO_PLUGIN_URI ">\n";
1260 modguiString += " modgui:gui [\n";
1261 #ifdef DISTRHO_PLUGIN_BRAND
1262 modguiString += " modgui:brand \"" DISTRHO_PLUGIN_BRAND "\" ;\n";
1263 #endif
1264 modguiString += " modgui:label \"" DISTRHO_PLUGIN_NAME "\" ;\n";
1265 modguiString += " modgui:resourcesDirectory <modgui> ;\n";
1266 modguiString += " modgui:iconTemplate <modgui/icon.html> ;\n";
1267 modguiString += " modgui:javascript <modgui/javascript.js> ;\n";
1268 modguiString += " modgui:stylesheet <modgui/stylesheet.css> ;\n";
1269 modguiString += " modgui:screenshot <modgui/screenshot.png> ;\n";
1270 modguiString += " modgui:thumbnail <modgui/thumbnail.png> ;\n";
1271
1272 uint32_t numParametersOutputs = 0;
1273 for (uint32_t i=0, count=plugin.getParameterCount(); i < count; ++i)
1274 {
1275 if (plugin.isParameterOutput(i))
1276 ++numParametersOutputs;
1277 }
1278 if (numParametersOutputs != 0)
1279 {
1280 modguiString += " modgui:monitoredOutputs [\n";
1281 for (uint32_t i=0, j=0, count=plugin.getParameterCount(); i < count; ++i)
1282 {
1283 if (!plugin.isParameterOutput(i))
1284 continue;
1285 modguiString += " lv2:symbol \"" + plugin.getParameterSymbol(i) + "\" ;\n";
1286 if (++j != numParametersOutputs)
1287 modguiString += " ] , [\n";
1288 }
1289 modguiString += " ] ;\n";
1290 }
1291
1292 modguiString += " ] .\n";
1293
1294 modguiFile << modguiString;
1295 modguiFile.close();
1296 std::cout << " done!" << std::endl;
1297 }
1298
1299 #ifdef DISTRHO_OS_WINDOWS
1300 ::_mkdir("modgui");
1301 #else
1302 ::mkdir("modgui", 0755);
1303 #endif
1304
1305 {
1306 std::cout << "Writing modgui/javascript.js..."; std::cout.flush();
1307 std::fstream jsFile("modgui/javascript.js", std::ios::out);
1308
1309 String jsString;
1310 jsString += "function(e,f){\n";
1311 jsString += "'use strict';\nvar ps=[";
1312
1313 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
1314 jsString += "'lv2_" + plugin.getAudioPort(false, i).symbol + "',";
1315 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
1316 jsString += "'lv2_" + plugin.getAudioPort(true, i).symbol + "',";
1317 #if DISTRHO_LV2_USE_EVENTS_IN
1318 jsString += "'lv2_events_in',";
1319 #endif
1320 #if DISTRHO_LV2_USE_EVENTS_OUT
1321 jsString += "'lv2_events_out',";
1322 #endif
1323 #if DISTRHO_PLUGIN_WANT_LATENCY
1324 jsString += "'lv2_latency',";
1325 #endif
1326
1327 int32_t enabledIndex = INT32_MAX;
1328 for (uint32_t i=0, count=plugin.getParameterCount(); i < count; ++i)
1329 {
1330 jsString += "'" + plugin.getParameterSymbol(i) + "',";
1331 if (plugin.getParameterDesignation(i) == kParameterDesignationBypass)
1332 enabledIndex = i;
1333 }
1334 jsString += "];\n";
1335 jsString += "var ei=" + String(enabledIndex != INT32_MAX ? enabledIndex : -1) + ";\n\n";
1336 jsString += "if(e.type==='start'){\n";
1337 jsString += "e.data.p={p:{},c:{},};\n\n";
1338 jsString += "var err=[];\n";
1339 jsString += "if(typeof(WebAssembly)==='undefined'){err.push('WebAssembly unsupported');}\n";
1340 jsString += "else{\n";
1341 jsString += "if(!WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,5,3,1,0,1,10,14,1,12,0,65,0,65,0,65,0,252,10,0,0,11])))";
1342 jsString += "err.push('Bulk Memory Operations unsupported');\n";
1343 jsString += "if(!WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,2,8,1,1,97,1,98,3,127,1,6,6,1,127,1,65,0,11,7,5,1,1,97,3,1])))";
1344 jsString += "err.push('Importable/Exportable mutable globals unsupported');\n";
1345 jsString += "}\n";
1346 jsString += "if(err.length!==0){e.icon.find('.canvas_wrapper').html('<h2>'+err.join('<br>')+'</h2>');return;}\n\n";
1347 jsString += "var s=document.createElement('script');\n";
1348 jsString += "s.setAttribute('async',true);\n";
1349 jsString += "s.setAttribute('src',e.api_version>=3?f.get_custom_resource_filename('module.js'):('/resources/module.js?uri='+escape(\"" DISTRHO_PLUGIN_URI "\")+'&r='+VERSION));\n";
1350 jsString += "s.setAttribute('type','text/javascript');\n";
1351 jsString += "s.onload=function(){\n";
1352 jsString += " Module_" DISTRHO_PLUGIN_MODGUI_CLASS_NAME "({\n";
1353 jsString += " locateFile: function(p,_){return e.api_version>=3?f.get_custom_resource_filename(p):('/resources/'+p+'?uri='+escape(\"" DISTRHO_PLUGIN_URI "\")+'&r='+VERSION)},\n";
1354 jsString += " postRun:function(m){\n";
1355 jsString += " var cn=e.icon.attr('mod-instance').replaceAll('/','_');\n";
1356 jsString += " var cnl=m.lengthBytesUTF8(cn) + 1;\n";
1357 jsString += " var cna=m._malloc(cnl);\n";
1358 jsString += " m.stringToUTF8(cn, cna, cnl);\n";
1359 jsString += " e.icon.find('canvas')[0].id=cn;\n";
1360 jsString += " var a=m.addFunction(function(i,v){f.set_port_value(ps[i],v);},'vif');\n";
1361 jsString += " var b=m.addFunction(function(u,v){f.patch_set(m.UTF8ToString(u),'s',m.UTF8ToString(v));},'vpp');\n";
1362 jsString += " var h=m._modgui_init(cna,a,b);\n";
1363 jsString += " m._free(cna);\n";
1364 jsString += " e.data.h=h;\n";
1365 jsString += " e.data.m=m;\n";
1366 jsString += " for(var u in e.data.p.p){\n";
1367 jsString += " var ul=m.lengthBytesUTF8(u)+1,ua=m._malloc(ul),v=e.data.p.p[u],vl=m.lengthBytesUTF8(v)+1,va=m._malloc(vl);\n";
1368 jsString += " m.stringToUTF8(u,ua,ul);\n";
1369 jsString += " m.stringToUTF8(v,va,vl);\n";
1370 jsString += " m._modgui_patch_set(h, ua, va);\n";
1371 jsString += " m._free(ua);\n";
1372 jsString += " m._free(va);\n";
1373 jsString += " }\n";
1374 jsString += " for(var symbol in e.data.p.c){m._modgui_param_set(h,ps.indexOf(symbol),e.data.p.c[symbol]);}\n";
1375 jsString += " delete e.data.p;\n";
1376 jsString += " window.dispatchEvent(new Event('resize'));\n";
1377 jsString += " },\n";
1378 jsString += " canvas:(function(){var c=e.icon.find('canvas')[0];c.addEventListener('webglcontextlost',function(e2){alert('WebGL context lost. You will need to reload the page.');e2.preventDefault();},false);return c;})(),\n";
1379 jsString += " });\n";
1380 jsString += "};\n";
1381 jsString += "document.head.appendChild(s);\n\n";
1382 jsString += "}else if(e.type==='change'){\n\n";
1383 jsString += "if(e.data.h && e.data.m){\n";
1384 jsString += " var m=e.data.m;\n";
1385 jsString += " if(e.uri){\n";
1386 jsString += " var ul=m.lengthBytesUTF8(e.uri)+1,ua=m._malloc(ul),vl=m.lengthBytesUTF8(e.value)+1,va=m._malloc(vl);\n";
1387 jsString += " m.stringToUTF8(e.uri,ua,ul);\n";
1388 jsString += " m.stringToUTF8(e.value,va,vl);\n";
1389 jsString += " m._modgui_patch_set(e.data.h,ua,va);\n";
1390 jsString += " m._free(ua);\n";
1391 jsString += " m._free(va);\n";
1392 jsString += " }else if(e.symbol===':bypass'){return;\n";
1393 jsString += " }else{m._modgui_param_set(e.data.h,ps.indexOf(e.symbol),e.value);}\n";
1394 jsString += "}else{\n";
1395 jsString += " if(e.symbol===':bypass')return;\n";
1396 jsString += " if(e.uri){e.data.p.p[e.uri]=e.value;}else{e.data.p.c[e.symbol]=e.value;}\n";
1397 jsString += "}\n\n";
1398 jsString += "}else if(e.type==='end'){\n";
1399 jsString += " if(e.data.h && e.data.m){\n";
1400 jsString += " var h = e.data.h;\n";
1401 jsString += " var m = e.data.m;\n";
1402 jsString += " e.data.h = e.data.m = null;\n";
1403 jsString += " m._modgui_cleanup(h);\n";
1404 jsString += "}\n\n";
1405 jsString += "}\n}\n";
1406 jsFile << jsString;
1407 jsFile.close();
1408 std::cout << " done!" << std::endl;
1409 }
1410
1411 {
1412 std::cout << "Writing modgui/icon.html..."; std::cout.flush();
1413 std::fstream iconFile("modgui/icon.html", std::ios::out);
1414
1415 iconFile << "<div class='" DISTRHO_PLUGIN_MODGUI_CLASS_NAME " mod-pedal'>" << std::endl;
1416 iconFile << " <div mod-role='drag-handle' class='mod-drag-handle'></div>" << std::endl;
1417 iconFile << " <div class='mod-plugin-title'><h1>{{#brand}}{{brand}} | {{/brand}}{{label}}</h1></div>" << std::endl;
1418 iconFile << " <div class='mod-light on' mod-role='bypass-light'></div>" << std::endl;
1419 iconFile << " <div class='mod-control-group mod-switch'>" << std::endl;
1420 iconFile << " <div class='mod-control-group mod-switch-image mod-port transport' mod-role='bypass' mod-widget='film'></div>" << std::endl;
1421 iconFile << " </div>" << std::endl;
1422 iconFile << " <div class='canvas_wrapper'>" << std::endl;
1423 iconFile << " <canvas oncontextmenu='event.preventDefault()' tabindex=-1></canvas>" << std::endl;
1424 iconFile << " </div>" << std::endl;
1425 iconFile << " <div class='mod-pedal-input'>" << std::endl;
1426 iconFile << " {{#effect.ports.audio.input}}" << std::endl;
1427 iconFile << " <div class='mod-input mod-input-disconnected' title='{{name}}' mod-role='input-audio-port' mod-port-symbol='{{symbol}}'>" << std::endl;
1428 iconFile << " <div class='mod-pedal-input-image'></div>" << std::endl;
1429 iconFile << " </div>" << std::endl;
1430 iconFile << " {{/effect.ports.audio.input}}" << std::endl;
1431 iconFile << " {{#effect.ports.midi.input}}" << std::endl;
1432 iconFile << " <div class='mod-input mod-input-disconnected' title='{{name}}' mod-role='input-midi-port' mod-port-symbol='{{symbol}}'>" << std::endl;
1433 iconFile << " <div class='mod-pedal-input-image'></div>" << std::endl;
1434 iconFile << " </div>" << std::endl;
1435 iconFile << " {{/effect.ports.midi.input}}" << std::endl;
1436 iconFile << " {{#effect.ports.cv.input}}" << std::endl;
1437 iconFile << " <div class='mod-input mod-input-disconnected' title='{{name}}' mod-role='input-cv-port' mod-port-symbol='{{symbol}}'>" << std::endl;
1438 iconFile << " <div class='mod-pedal-input-image'></div>" << std::endl;
1439 iconFile << " </div>" << std::endl;
1440 iconFile << " {{/effect.ports.cv.input}}" << std::endl;
1441 iconFile << " </div>" << std::endl;
1442 iconFile << " <div class='mod-pedal-output'>" << std::endl;
1443 iconFile << " {{#effect.ports.audio.output}}" << std::endl;
1444 iconFile << " <div class='mod-output mod-output-disconnected' title='{{name}}' mod-role='output-audio-port' mod-port-symbol='{{symbol}}'>" << std::endl;
1445 iconFile << " <div class='mod-pedal-output-image'></div>" << std::endl;
1446 iconFile << " </div>" << std::endl;
1447 iconFile << " {{/effect.ports.audio.output}}" << std::endl;
1448 iconFile << " {{#effect.ports.midi.output}}" << std::endl;
1449 iconFile << " <div class='mod-output mod-output-disconnected' title='{{name}}' mod-role='output-midi-port' mod-port-symbol='{{symbol}}'>" << std::endl;
1450 iconFile << " <div class='mod-pedal-output-image'></div>" << std::endl;
1451 iconFile << " </div>" << std::endl;
1452 iconFile << " {{/effect.ports.midi.output}}" << std::endl;
1453 iconFile << " {{#effect.ports.cv.output}}" << std::endl;
1454 iconFile << " <div class='mod-output mod-output-disconnected' title='{{name}}' mod-role='output-cv-port' mod-port-symbol='{{symbol}}'>" << std::endl;
1455 iconFile << " <div class='mod-pedal-output-image'></div>" << std::endl;
1456 iconFile << " </div>" << std::endl;
1457 iconFile << " {{/effect.ports.cv.output}}" << std::endl;
1458 iconFile << " </div>" << std::endl;
1459 iconFile << "</div>" << std::endl;
1460
1461 iconFile.close();
1462 std::cout << " done!" << std::endl;
1463 }
1464
1465 {
1466 std::cout << "Writing modgui/stylesheet.css..."; std::cout.flush();
1467 std::fstream stylesheetFile("modgui/stylesheet.css", std::ios::out);
1468
1469 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal{" << std::endl;
1470 stylesheetFile << " padding:0;" << std::endl;
1471 stylesheetFile << " margin:0;" << std::endl;
1472 stylesheetFile << " width:" + String(DISTRHO_UI_DEFAULT_WIDTH) + "px;" << std::endl;
1473 stylesheetFile << " height:" + String(DISTRHO_UI_DEFAULT_HEIGHT + 50) + "px;" << std::endl;
1474 stylesheetFile << " background:#2a2e32;" << std::endl;
1475 stylesheetFile << " border-radius:20px 20px 0 0;" << std::endl;
1476 stylesheetFile << " color:#fff;" << std::endl;
1477 stylesheetFile << "}" << std::endl;
1478 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .canvas_wrapper{" << std::endl;
1479 stylesheetFile << " --device-pixel-ratio:1;" << std::endl;
1480 stylesheetFile << " /*image-rendering:pixelated;*/" << std::endl;
1481 stylesheetFile << " /*image-rendering:crisp-edges;*/" << std::endl;
1482 stylesheetFile << " background:#000;" << std::endl;
1483 stylesheetFile << " position:absolute;" << std::endl;
1484 stylesheetFile << " top:50px;" << std::endl;
1485 stylesheetFile << " transform-origin:0 0 0;" << std::endl;
1486 stylesheetFile << " transform:scale(calc(1/var(--device-pixel-ratio)));" << std::endl;
1487 stylesheetFile << " width:" + String(DISTRHO_UI_DEFAULT_WIDTH) + "px;" << std::endl;
1488 stylesheetFile << " height:" + String(DISTRHO_UI_DEFAULT_HEIGHT) + "px;" << std::endl;
1489 stylesheetFile << " text-align:center;" << std::endl;
1490 stylesheetFile << " z-index:21;" << std::endl;
1491 stylesheetFile << "}" << std::endl;
1492 stylesheetFile << "/*" << std::endl;
1493 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .canvas_wrapper:focus-within{" << std::endl;
1494 stylesheetFile << " z-index:21;" << std::endl;
1495 stylesheetFile << "}" << std::endl;
1496 stylesheetFile << "*/" << std::endl;
1497 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-plugin-title{" << std::endl;
1498 stylesheetFile << " position:absolute;" << std::endl;
1499 stylesheetFile << " text-align:center;" << std::endl;
1500 stylesheetFile << " width:100%;" << std::endl;
1501 stylesheetFile << "}" << std::endl;
1502 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal h1{" << std::endl;
1503 stylesheetFile << " font-size:20px;" << std::endl;
1504 stylesheetFile << " font-weight:bold;" << std::endl;
1505 stylesheetFile << " line-height:50px;" << std::endl;
1506 stylesheetFile << " margin:0;" << std::endl;
1507 stylesheetFile << "}" << std::endl;
1508 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-control-group{" << std::endl;
1509 stylesheetFile << " position:absolute;" << std::endl;
1510 stylesheetFile << " left:5px;" << std::endl;
1511 stylesheetFile << " z-index:35;" << std::endl;
1512 stylesheetFile << "}" << std::endl;
1513 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-pedal-input," << std::endl;
1514 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-pedal-output{" << std::endl;
1515 stylesheetFile << " top:75px;" << std::endl;
1516 stylesheetFile << "}" << std::endl;
1517 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-audio-input," << std::endl;
1518 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-audio-output{" << std::endl;
1519 stylesheetFile << " margin-bottom:25px;" << std::endl;
1520 stylesheetFile << "}" << std::endl;
1521 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .jack-disconnected{" << std::endl;
1522 stylesheetFile << " top:0px!important;" << std::endl;
1523 stylesheetFile << "}" << std::endl;
1524 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-switch-image{" << std::endl;
1525 stylesheetFile << " background-image: url(/img/switch.png);" << std::endl;
1526 stylesheetFile << " background-position: left center;" << std::endl;
1527 stylesheetFile << " background-repeat: no-repeat;" << std::endl;
1528 stylesheetFile << " background-size: auto 50px;" << std::endl;
1529 stylesheetFile << " font-weight: bold;" << std::endl;
1530 stylesheetFile << " width: 100px;" << std::endl;
1531 stylesheetFile << " height: 50px;" << std::endl;
1532 stylesheetFile << " cursor: pointer;" << std::endl;
1533 stylesheetFile << "}" << std::endl;
1534 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-switch-image.off{" << std::endl;
1535 stylesheetFile << " background-position: right center !important;" << std::endl;
1536 stylesheetFile << "}" << std::endl;
1537 stylesheetFile << "." DISTRHO_PLUGIN_MODGUI_CLASS_NAME ".mod-pedal .mod-switch-image.on{" << std::endl;
1538 stylesheetFile << " background-position: left center !important;" << std::endl;
1539 stylesheetFile << "}" << std::endl;
1540
1541 stylesheetFile.close();
1542 std::cout << " done!" << std::endl;
1543 }
1544 #endif // DISTRHO_PLUGIN_USES_MODGUI && !DISTRHO_PLUGIN_USES_CUSTOM_MODGUI
1545
1546 // ---------------------------------------------
1547
1548 #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
1549 {
1550 std::cout << "Writing " << uiTTL << "..."; std::cout.flush();
1551 std::fstream uiFile(uiTTL, std::ios::out);
1552
1553 String uiString;
1554 uiString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
1555 uiString += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
1556 uiString += "@prefix opts: <" LV2_OPTIONS_PREFIX "> .\n";
1557 uiString += "\n";
1558
1559 uiString += "<" DISTRHO_UI_URI ">\n";
1560
1561 addAttribute(uiString, "lv2:extensionData", lv2ManifestUiExtensionData, 4);
1562 addAttribute(uiString, "lv2:optionalFeature", lv2ManifestUiOptionalFeatures, 4);
1563 addAttribute(uiString, "lv2:requiredFeature", lv2ManifestUiRequiredFeatures, 4);
1564 addAttribute(uiString, "opts:supportedOption", lv2ManifestUiSupportedOptions, 4, true);
1565
1566 uiFile << uiString;
1567 uiFile.close();
1568 std::cout << " done!" << std::endl;
1569 }
1570 #endif
1571
1572 // ---------------------------------------------
1573
1574 #if DISTRHO_PLUGIN_WANT_PROGRAMS
1575 {
1576 std::cout << "Writing presets.ttl..."; std::cout.flush();
1577 std::fstream presetsFile("presets.ttl", std::ios::out);
1578
1579 String presetsString;
1580 presetsString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
1581 presetsString += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n";
1582 # if DISTRHO_PLUGIN_WANT_STATE
1583 presetsString += "@prefix owl: <http://www.w3.org/2002/07/owl#> .\n";
1584 presetsString += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
1585 presetsString += "@prefix state: <" LV2_STATE_PREFIX "> .\n";
1586 presetsString += "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n";
1587 # endif
1588 presetsString += "\n";
1589
1590 const uint32_t numParameters = plugin.getParameterCount();
1591 const uint32_t numPrograms = plugin.getProgramCount();
1592 # if DISTRHO_PLUGIN_WANT_FULL_STATE
1593 const uint32_t numStates = plugin.getStateCount();
1594 const bool valid = numParameters != 0 || numStates != 0;
1595 # else
1596 const bool valid = numParameters != 0;
1597 # endif
1598
1599 DISTRHO_CUSTOM_SAFE_ASSERT_RETURN("Programs require parameters or full state", valid, presetsFile.close());
1600
1601 const String presetSeparator(std::strstr(DISTRHO_PLUGIN_URI, "#") != nullptr ? ":" : "#");
1602
1603 char strBuf[0xff+1];
1604 strBuf[0xff] = '\0';
1605
1606 String presetString;
1607
1608 # if DISTRHO_PLUGIN_WANT_FULL_STATE
1609 for (uint32_t i=0; i<numStates; ++i)
1610 {
1611 if (plugin.getStateHints(i) & kStateIsHostReadable)
1612 continue;
1613
1614 // readable states are defined as lv2 parameters.
1615 // non-readable states have no definition, but one is needed for presets and ttl validation.
1616 presetString = "<" DISTRHO_PLUGIN_LV2_STATE_PREFIX + plugin.getStateKey(i) + ">\n";
1617 presetString += " a owl:DatatypeProperty ;\n";
1618 presetString += " rdfs:label \"Plugin state key-value string pair\" ;\n";
1619 presetString += " rdfs:domain state:State ;\n";
1620 presetString += " rdfs:range xsd:string .\n\n";
1621 presetsString += presetString;
1622 }
1623 # endif
1624
1625 for (uint32_t i=0; i<numPrograms; ++i)
1626 {
1627 std::snprintf(strBuf, 0xff, "%03i", i+1);
1628
1629 plugin.loadProgram(i);
1630
1631 presetString = "<" DISTRHO_PLUGIN_URI + presetSeparator + "preset" + strBuf + ">\n";
1632
1633 # if DISTRHO_PLUGIN_WANT_FULL_STATE
1634 presetString += " state:state [\n";
1635 for (uint32_t j=0; j<numStates; ++j)
1636 {
1637 const String key = plugin.getStateKey(j);
1638 const String value = plugin.getStateValue(key);
1639
1640 presetString += " <";
1641
1642 if (plugin.getStateHints(j) & kStateIsHostReadable)
1643 presetString += DISTRHO_PLUGIN_URI "#";
1644 else
1645 presetString += DISTRHO_PLUGIN_LV2_STATE_PREFIX;
1646
1647 presetString += key + ">";
1648
1649 if (value.length() < 10)
1650 presetString += " \"" + value + "\" ;\n";
1651 else
1652 presetString += "\n\"\"\"" + value + "\"\"\" ;\n";
1653 }
1654
1655 if (numParameters > 0)
1656 presetString += " ] ;\n\n";
1657 else
1658 presetString += " ] .\n\n";
1659 # endif
1660
1661 bool firstParameter = true;
1662
1663 for (uint32_t j=0; j <numParameters; ++j)
1664 {
1665 if (plugin.isParameterOutput(j))
1666 continue;
1667
1668 if (firstParameter)
1669 {
1670 presetString += " lv2:port [\n";
1671 firstParameter = false;
1672 }
1673 else
1674 {
1675 presetString += " [\n";
1676 }
1677
1678 String parameterSymbol = plugin.getParameterSymbol(j);
1679 float parameterValue = plugin.getParameterValue(j);
1680
1681 if (plugin.getParameterDesignation(j) == kParameterDesignationBypass)
1682 {
1683 parameterSymbol = DISTRHO_BYPASS_PARAMETER_NAME;
1684 parameterValue = 1.0f - parameterValue;
1685 }
1686
1687 presetString += " lv2:symbol \"" + parameterSymbol + "\" ;\n";
1688
1689 if (plugin.getParameterHints(j) & kParameterIsInteger)
1690 presetString += " pset:value " + String(int(parameterValue)) + " ;\n";
1691 else
1692 presetString += " pset:value " + String(parameterValue) + " ;\n";
1693
1694 if (j+1 == numParameters || plugin.isParameterOutput(j+1))
1695 presetString += " ] .\n\n";
1696 else
1697 presetString += " ] ,\n";
1698 }
1699
1700 presetsString += presetString;
1701 }
1702
1703 presetsFile << presetsString;
1704 presetsFile.close();
1705 std::cout << " done!" << std::endl;
1706 }
1707 #endif
1708 }