DISTRHO Plugin Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1718 lines
72KB

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