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.

480 lines
16KB

  1. /*
  2. ==============================================================================
  3. Juce LV2 Wrapper (TTL Exporter)
  4. ==============================================================================
  5. */
  6. // NOTE: This file is only meant to be used when included byce juce_LV2_Wrapper.cpp!
  7. #if JUCE_MAC
  8. #define PLUGIN_EXT ".dylib"
  9. #elif JUCE_LINUX
  10. #define PLUGIN_EXT ".so"
  11. #elif JUCE_WINDOWS
  12. #define PLUGIN_EXT ".dll"
  13. #endif
  14. /** Returns plugin type, defined in AppConfig.h or JucePluginCharacteristics.h */
  15. static const String getPluginType()
  16. {
  17. String pluginType;
  18. #ifdef JucePlugin_LV2Category
  19. pluginType = "lv2:" JucePlugin_LV2Category;
  20. pluginType += ", ";
  21. #elif JucePlugin_IsSynth
  22. pluginType = "lv2:InstrumentPlugin, ";
  23. #endif
  24. pluginType += "lv2:Plugin";
  25. return pluginType;
  26. }
  27. static Array<String> usedSymbols;
  28. /** Converts a parameter name to an LV2 compatible symbol. */
  29. static const String nameToSymbol (const String& name, const uint32 portIndex)
  30. {
  31. String symbol, trimmedName = name.trimStart().trimEnd().toLowerCase();
  32. if (trimmedName.isEmpty())
  33. {
  34. symbol += "lv2_port_";
  35. symbol += String(portIndex+1);
  36. }
  37. else
  38. {
  39. for (int i=0; i < trimmedName.length(); ++i)
  40. {
  41. const juce_wchar c = trimmedName[i];
  42. if (i == 0 && std::isdigit(c))
  43. symbol += "_";
  44. else if (std::isalpha(c) || std::isdigit(c))
  45. symbol += c;
  46. else
  47. symbol += "_";
  48. }
  49. }
  50. // Do not allow identical symbols
  51. if (usedSymbols.contains(symbol))
  52. {
  53. int offset = 2;
  54. String offsetStr = "_2";
  55. symbol += offsetStr;
  56. while (usedSymbols.contains(symbol))
  57. {
  58. offset += 1;
  59. String newOffsetStr = "_" + String(offset);
  60. symbol = symbol.replace(offsetStr, newOffsetStr);
  61. offsetStr = newOffsetStr;
  62. }
  63. }
  64. usedSymbols.add(symbol);
  65. return symbol;
  66. }
  67. /** Prevents NaN or out of 0.0<->1.0 bounds parameter values. */
  68. static float safeParamValue (float value)
  69. {
  70. if (std::isnan(value))
  71. value = 0.0f;
  72. else if (value < 0.0f)
  73. value = 0.0f;
  74. else if (value > 1.0f)
  75. value = 1.0f;
  76. return value;
  77. }
  78. /** Create the manifest.ttl file contents */
  79. static const String makeManifestFile (AudioProcessor* const filter, const String& binary)
  80. {
  81. const String& pluginURI(getPluginURI());
  82. String text;
  83. // Header
  84. text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
  85. text += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n";
  86. text += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
  87. text += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
  88. text += "\n";
  89. // Plugin
  90. text += "<" + pluginURI + ">\n";
  91. text += " a lv2:Plugin ;\n";
  92. text += " lv2:binary <" + binary + PLUGIN_EXT "> ;\n";
  93. text += " rdfs:seeAlso <" + binary + ".ttl> .\n";
  94. text += "\n";
  95. #if ! JUCE_AUDIOPROCESSOR_NO_GUI
  96. // UIs
  97. if (filter->hasEditor())
  98. {
  99. text += "<" + pluginURI + "#ExternalUI>\n";
  100. text += " a <" LV2_EXTERNAL_UI__Widget "> ;\n";
  101. text += " ui:binary <" + binary + PLUGIN_EXT "> ;\n";
  102. text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> ;\n";
  103. text += " lv2:optionalFeature ui:touch .\n";
  104. text += "\n";
  105. text += "<" + pluginURI + "#ParentUI>\n";
  106. #if JUCE_MAC
  107. text += " a ui:CocoaUI ;\n";
  108. #elif JUCE_LINUX
  109. text += " a ui:X11UI ;\n";
  110. #elif JUCE_WINDOWS
  111. text += " a ui:WindowsUI ;\n";
  112. #endif
  113. text += " ui:binary <" + binary + PLUGIN_EXT "> ;\n";
  114. text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> ;\n";
  115. text += " lv2:optionalFeature ui:idleInterface, ui:noUserResize, ui:touch ;\n";
  116. text += " lv2:extensionData ui:idleInterface .\n";
  117. text += "\n";
  118. }
  119. #endif
  120. #if JucePlugin_WantsLV2Presets
  121. const String presetSeparator(pluginURI.contains("#") ? ":" : "#");
  122. // Presets
  123. for (int i = 0; i < filter->getNumPrograms(); ++i)
  124. {
  125. text += "<" + pluginURI + presetSeparator + "preset" + String::formatted("%03i", i+1) + ">\n";
  126. text += " a pset:Preset ;\n";
  127. text += " lv2:appliesTo <" + pluginURI + "> ;\n";
  128. text += " rdfs:label \"" + filter->getProgramName(i) + "\" ;\n";
  129. text += " rdfs:seeAlso <presets.ttl> .\n";
  130. text += "\n";
  131. }
  132. #endif
  133. return text;
  134. }
  135. /** Create the -plugin-.ttl file contents */
  136. static const String makePluginFile (AudioProcessor* const filter, const int maxNumInputChannels, const int maxNumOutputChannels)
  137. {
  138. const String& pluginURI(getPluginURI());
  139. String text;
  140. // Header
  141. text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n";
  142. text += "@prefix doap: <http://usefulinc.com/ns/doap#> .\n";
  143. text += "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n";
  144. text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
  145. text += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
  146. text += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
  147. text += "\n";
  148. // Plugin
  149. text += "<" + pluginURI + ">\n";
  150. text += " a " + getPluginType() + " ;\n";
  151. text += " lv2:requiredFeature <" LV2_BUF_SIZE__boundedBlockLength "> ,\n";
  152. #if JucePlugin_WantsLV2FixedBlockSize
  153. text += " <" LV2_BUF_SIZE__fixedBlockLength "> ,\n";
  154. #endif
  155. text += " <" LV2_URID__map "> ;\n";
  156. text += " lv2:extensionData <" LV2_OPTIONS__interface "> ,\n";
  157. #if JucePlugin_WantsLV2State
  158. text += " <" LV2_STATE__interface "> ,\n";
  159. #endif
  160. text += " <" LV2_PROGRAMS__Interface "> ;\n";
  161. text += "\n";
  162. #ifdef JucePlugin_VersionCode
  163. // Version
  164. {
  165. const uint32_t version = JucePlugin_VersionCode;
  166. const uint32_t majorVersion = (version & 0xFF0000) >> 16;
  167. const uint32_t microVersion = (version & 0x00FF00) >> 8;
  168. /* */ uint32_t minorVersion = (version & 0x0000FF) >> 0;
  169. // NOTE: LV2 ignores 'major' version and says 0 for minor is pre-release/unstable.
  170. if (majorVersion > 0)
  171. minorVersion += 2;
  172. text += " lv2:microVersion " + String(microVersion) + " ;\n";
  173. text += " lv2:minorVersion " + String(minorVersion) + " ;\n";
  174. text += "\n";
  175. }
  176. #endif
  177. #if ! JUCE_AUDIOPROCESSOR_NO_GUI
  178. // UIs
  179. if (filter->hasEditor())
  180. {
  181. text += " ui:ui <" + pluginURI + "#ExternalUI> ,\n";
  182. text += " <" + pluginURI + "#ParentUI> ;\n";
  183. text += "\n";
  184. }
  185. #endif
  186. uint32 portIndex = 0;
  187. #if (JucePlugin_WantsMidiInput || JucePlugin_WantsLV2TimePos)
  188. // MIDI input
  189. text += " lv2:port [\n";
  190. text += " a lv2:InputPort, atom:AtomPort ;\n";
  191. text += " atom:bufferType atom:Sequence ;\n";
  192. #if JucePlugin_WantsMidiInput
  193. text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
  194. #endif
  195. #if JucePlugin_WantsLV2TimePos
  196. text += " atom:supports <" LV2_TIME__Position "> ;\n";
  197. #endif
  198. text += " lv2:index " + String(portIndex++) + " ;\n";
  199. text += " lv2:symbol \"lv2_events_in\" ;\n";
  200. text += " lv2:name \"Events Input\" ;\n";
  201. text += " lv2:designation lv2:control ;\n";
  202. #if ! JucePlugin_IsSynth
  203. text += " lv2:portProperty lv2:connectionOptional ;\n";
  204. #endif
  205. text += " ] ;\n";
  206. text += "\n";
  207. #endif
  208. #if JucePlugin_ProducesMidiOutput
  209. // MIDI output
  210. text += " lv2:port [\n";
  211. text += " a lv2:OutputPort, atom:AtomPort ;\n";
  212. text += " atom:bufferType atom:Sequence ;\n";
  213. text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
  214. text += " lv2:index " + String(portIndex++) + " ;\n";
  215. text += " lv2:symbol \"lv2_midi_out\" ;\n";
  216. text += " lv2:name \"MIDI Output\" ;\n";
  217. text += " ] ;\n";
  218. text += "\n";
  219. #endif
  220. // Freewheel port
  221. text += " lv2:port [\n";
  222. text += " a lv2:InputPort, lv2:ControlPort ;\n";
  223. text += " lv2:index " + String(portIndex++) + " ;\n";
  224. text += " lv2:symbol \"lv2_freewheel\" ;\n";
  225. text += " lv2:name \"Freewheel\" ;\n";
  226. text += " lv2:default 0.0 ;\n";
  227. text += " lv2:minimum 0.0 ;\n";
  228. text += " lv2:maximum 1.0 ;\n";
  229. text += " lv2:designation <" LV2_CORE__freeWheeling "> ;\n";
  230. text += " lv2:portProperty lv2:toggled, <" LV2_PORT_PROPS__notOnGUI "> ;\n";
  231. text += " ] ;\n";
  232. text += "\n";
  233. #if JucePlugin_WantsLV2Latency
  234. // Latency port
  235. text += " lv2:port [\n";
  236. text += " a lv2:OutputPort, lv2:ControlPort ;\n";
  237. text += " lv2:index " + String(portIndex++) + " ;\n";
  238. text += " lv2:symbol \"lv2_latency\" ;\n";
  239. text += " lv2:name \"Latency\" ;\n";
  240. text += " lv2:designation <" LV2_CORE__latency "> ;\n";
  241. text += " lv2:portProperty lv2:reportsLatency, lv2:integer ;\n";
  242. text += " ] ;\n";
  243. text += "\n";
  244. #endif
  245. // Audio inputs
  246. for (int i=0; i < maxNumInputChannels; ++i)
  247. {
  248. if (i == 0)
  249. text += " lv2:port [\n";
  250. else
  251. text += " [\n";
  252. text += " a lv2:InputPort, lv2:AudioPort ;\n";
  253. text += " lv2:index " + String(portIndex++) + " ;\n";
  254. text += " lv2:symbol \"lv2_audio_in_" + String(i+1) + "\" ;\n";
  255. text += " lv2:name \"Audio Input " + String(i+1) + "\" ;\n";
  256. if (i+1 == maxNumInputChannels)
  257. text += " ] ;\n\n";
  258. else
  259. text += " ] ,\n";
  260. }
  261. // Audio outputs
  262. for (int i=0; i < maxNumOutputChannels; ++i)
  263. {
  264. if (i == 0)
  265. text += " lv2:port [\n";
  266. else
  267. text += " [\n";
  268. text += " a lv2:OutputPort, lv2:AudioPort ;\n";
  269. text += " lv2:index " + String(portIndex++) + " ;\n";
  270. text += " lv2:symbol \"lv2_audio_out_" + String(i+1) + "\" ;\n";
  271. text += " lv2:name \"Audio Output " + String(i+1) + "\" ;\n";
  272. if (i+1 == maxNumOutputChannels)
  273. text += " ] ;\n\n";
  274. else
  275. text += " ] ,\n";
  276. }
  277. // Parameters
  278. for (int i=0; i < filter->getNumParameters(); ++i)
  279. {
  280. if (i == 0)
  281. text += " lv2:port [\n";
  282. else
  283. text += " [\n";
  284. text += " a lv2:InputPort, lv2:ControlPort ;\n";
  285. text += " lv2:index " + String(portIndex++) + " ;\n";
  286. text += " lv2:symbol \"" + nameToSymbol(filter->getParameterName(i), i) + "\" ;\n";
  287. if (filter->getParameterName(i).isNotEmpty())
  288. text += " lv2:name \"" + filter->getParameterName(i) + "\" ;\n";
  289. else
  290. text += " lv2:name \"Port " + String(i+1) + "\" ;\n";
  291. text += " lv2:default " + String::formatted("%f", safeParamValue(filter->getParameter(i))) + " ;\n";
  292. text += " lv2:minimum 0.0 ;\n";
  293. text += " lv2:maximum 1.0 ;\n";
  294. if (! filter->isParameterAutomatable(i))
  295. text += " lv2:portProperty <" LV2_PORT_PROPS__expensive "> ;\n";
  296. if (i+1 == filter->getNumParameters())
  297. text += " ] ;\n\n";
  298. else
  299. text += " ] ,\n";
  300. }
  301. text += " doap:name \"" + filter->getName() + "\" ;\n";
  302. text += " doap:maintainer [ foaf:name \"" JucePlugin_Manufacturer "\" ] .\n";
  303. return text;
  304. }
  305. #if JucePlugin_WantsLV2Presets
  306. /** Create the presets.ttl file contents */
  307. static const String makePresetsFile (AudioProcessor* const filter)
  308. {
  309. const String& pluginURI(getPluginURI());
  310. String text;
  311. // Header
  312. text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n";
  313. text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
  314. text += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n";
  315. text += "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n";
  316. text += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
  317. text += "@prefix state: <" LV2_STATE_PREFIX "> .\n";
  318. text += "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n";
  319. text += "\n";
  320. // Presets
  321. const int numPrograms = filter->getNumPrograms();
  322. const String presetSeparator(pluginURI.contains("#") ? ":" : "#");
  323. for (int i = 0; i < numPrograms; ++i)
  324. {
  325. std::cout << "\nSaving preset " << i+1 << "/" << numPrograms+1 << "...";
  326. std::cout.flush();
  327. String preset;
  328. // Label
  329. filter->setCurrentProgram(i);
  330. preset += "<" + pluginURI + presetSeparator + "preset" + String::formatted("%03i", i+1) + "> a pset:Preset ;\n";
  331. // State
  332. #if JucePlugin_WantsLV2State
  333. preset += " state:state [\n";
  334. #if JucePlugin_WantsLV2StateString
  335. preset += " <" JUCE_LV2_STATE_STRING_URI ">\n";
  336. preset += "\"\"\"\n";
  337. preset += filter->getStateInformationString().replace("\r\n","\n");
  338. preset += "\"\"\"\n";
  339. #else
  340. MemoryBlock chunkMemory;
  341. filter->getCurrentProgramStateInformation(chunkMemory);
  342. const String chunkString(Base64::toBase64(chunkMemory.getData(), chunkMemory.getSize()));
  343. preset += " <" JUCE_LV2_STATE_BINARY_URI "> [\n";
  344. preset += " a atom:Chunk ;\n";
  345. preset += " rdf:value \"" + chunkString + "\"^^xsd:base64Binary ;\n";
  346. preset += " ] ;\n";
  347. #endif
  348. if (filter->getNumParameters() == 0)
  349. {
  350. preset += " ] .\n\n";
  351. continue;
  352. }
  353. preset += " ] ;\n\n";
  354. #endif
  355. // Port values
  356. usedSymbols.clear();
  357. for (int j=0; j < filter->getNumParameters(); ++j)
  358. {
  359. if (j == 0)
  360. preset += " lv2:port [\n";
  361. else
  362. preset += " [\n";
  363. preset += " lv2:symbol \"" + nameToSymbol(filter->getParameterName(j), j) + "\" ;\n";
  364. preset += " pset:value " + String::formatted("%f", safeParamValue(filter->getParameter(j))) + " ;\n";
  365. if (j+1 == filter->getNumParameters())
  366. preset += " ] ";
  367. else
  368. preset += " ] ,\n";
  369. }
  370. preset += ".\n\n";
  371. text += preset;
  372. }
  373. return text;
  374. }
  375. #endif
  376. /** Creates manifest.ttl, plugin.ttl and presets.ttl files */
  377. static void createLv2Files(const char* basename)
  378. {
  379. const ScopedJuceInitialiser_GUI juceInitialiser;
  380. ScopedPointer<AudioProcessor> filter(createPluginFilterOfType (AudioProcessor::wrapperType_LV2));
  381. int maxNumInputChannels, maxNumOutputChannels;
  382. findMaxTotalChannels(filter, maxNumInputChannels, maxNumOutputChannels);
  383. String binary(basename);
  384. String binaryTTL(binary + ".ttl");
  385. std::cout << "Writing manifest.ttl..."; std::cout.flush();
  386. std::fstream manifest("manifest.ttl", std::ios::out);
  387. manifest << makeManifestFile(filter, binary) << std::endl;
  388. manifest.close();
  389. std::cout << " done!" << std::endl;
  390. std::cout << "Writing " << binary << ".ttl..."; std::cout.flush();
  391. std::fstream plugin(binaryTTL.toUTF8(), std::ios::out);
  392. plugin << makePluginFile(filter, maxNumInputChannels, maxNumOutputChannels) << std::endl;
  393. plugin.close();
  394. std::cout << " done!" << std::endl;
  395. #if JucePlugin_WantsLV2Presets
  396. std::cout << "Writing presets.ttl..."; std::cout.flush();
  397. std::fstream presets("presets.ttl", std::ios::out);
  398. presets << makePresetsFile(filter) << std::endl;
  399. presets.close();
  400. std::cout << " done!" << std::endl;
  401. #endif
  402. }
  403. //==============================================================================
  404. // startup code..
  405. JUCE_EXPORTED_FUNCTION void lv2_generate_ttl (const char* basename);
  406. JUCE_EXPORTED_FUNCTION void lv2_generate_ttl (const char* basename)
  407. {
  408. createLv2Files (basename);
  409. }