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