Audio plugin host https://kx.studio/carla
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.

carla-lv2-export.cpp 19KB

10 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. /*
  2. * Carla Native Plugins
  3. * Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the doc/GPL.txt file.
  16. */
  17. #define CARLA_NATIVE_PLUGIN_LV2
  18. #include "carla-base.cpp"
  19. #include "juce_core.h"
  20. #include "lv2/atom.h"
  21. #include "lv2/buf-size.h"
  22. #include "lv2/instance-access.h"
  23. #include "lv2/midi.h"
  24. #include "lv2/options.h"
  25. #include "lv2/port-props.h"
  26. #include "lv2/state.h"
  27. #include "lv2/time.h"
  28. #include "lv2/ui.h"
  29. #include "lv2/units.h"
  30. #include "lv2/urid.h"
  31. #include "lv2/lv2_external_ui.h"
  32. #include "lv2/lv2_programs.h"
  33. #include <fstream>
  34. #if defined(CARLA_OS_WIN)
  35. # define PLUGIN_EXT ".dll"
  36. #elif defined(JUCE_MAC)
  37. # define PLUGIN_EXT ".dylib"
  38. #else
  39. # define PLUGIN_EXT ".so"
  40. #endif
  41. using juce::String;
  42. using juce::StringArray;
  43. using juce::juce_wchar;
  44. // -----------------------------------------------------------------------
  45. // Converts a parameter name to an LV2 compatible symbol
  46. static StringArray gUsedSymbols;
  47. static const String nameToSymbol(const String& name, const uint32_t portIndex)
  48. {
  49. String symbol, trimmedName = name.trim().toLowerCase();
  50. if (trimmedName.isEmpty())
  51. {
  52. symbol += "lv2_port_";
  53. symbol += String(portIndex+1);
  54. }
  55. else
  56. {
  57. for (int i=0; i < trimmedName.length(); ++i)
  58. {
  59. #ifdef CARLA_OS_WIN
  60. const int32_t c = static_cast<int32_t>(trimmedName[i]);
  61. #else
  62. const juce_wchar c = trimmedName[i];
  63. #endif
  64. if (i == 0 && std::isdigit(c))
  65. symbol += "_";
  66. else if (std::isalpha(c) || std::isdigit(c))
  67. symbol += c;
  68. else
  69. symbol += "_";
  70. }
  71. }
  72. // Do not allow identical symbols
  73. if (gUsedSymbols.contains(symbol))
  74. {
  75. int offset = 2;
  76. String offsetStr = "_2";
  77. symbol += offsetStr;
  78. while (gUsedSymbols.contains(symbol))
  79. {
  80. offset += 1;
  81. String newOffsetStr = "_" + String(offset);
  82. symbol = symbol.replace(offsetStr, newOffsetStr);
  83. offsetStr = newOffsetStr;
  84. }
  85. }
  86. gUsedSymbols.add(symbol);
  87. return symbol;
  88. }
  89. // -----------------------------------------------------------------------
  90. static void writeManifestFile(PluginListManager& plm)
  91. {
  92. String text;
  93. // -------------------------------------------------------------------
  94. // Header
  95. text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
  96. text += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
  97. text += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
  98. text += "\n";
  99. // -------------------------------------------------------------------
  100. // Plugins
  101. for (LinkedList<const NativePluginDescriptor*>::Itenerator it = plm.descs.begin(); it.valid(); it.next())
  102. {
  103. const NativePluginDescriptor* const& pluginDesc(it.getValue());
  104. const String label(pluginDesc->label);
  105. text += "<http://kxstudio.sf.net/carla/plugins/" + label + ">\n";
  106. text += " a lv2:Plugin ;\n";
  107. text += " lv2:binary <carla" PLUGIN_EXT "> ;\n";
  108. text += " rdfs:seeAlso <" + label + ".ttl> .\n";
  109. text += "\n";
  110. }
  111. // -------------------------------------------------------------------
  112. // UI
  113. text += "<http://kxstudio.sf.net/carla/ui>\n";
  114. text += " a <" LV2_EXTERNAL_UI__Widget "> ;\n";
  115. text += " ui:binary <carla" PLUGIN_EXT "> ;\n";
  116. text += " lv2:extensionData ui:idleInterface ,\n";
  117. text += " ui:showInterface ,\n";
  118. text += " <" LV2_PROGRAMS__UIInterface "> ;\n";
  119. text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> .\n";
  120. // -------------------------------------------------------------------
  121. // Write file now
  122. std::fstream manifest("carla.lv2/manifest.ttl", std::ios::out);
  123. manifest << text.toRawUTF8();
  124. manifest.close();
  125. }
  126. // -----------------------------------------------------------------------
  127. static uint32_t host_getBufferSize(NativeHostHandle) { return 512; }
  128. static double host_getSampleRate(NativeHostHandle) { return 44100.0; }
  129. static bool host_isOffline(NativeHostHandle) { return true; }
  130. static intptr_t host_dispatcher(NativeHostHandle, NativeHostDispatcherOpcode, int32_t, intptr_t, void*, float) { return 0; }
  131. static void writePluginFile(const NativePluginDescriptor* const pluginDesc)
  132. {
  133. const String pluginLabel(pluginDesc->label);
  134. const String pluginFile("carla.lv2/" + pluginLabel + ".ttl");
  135. uint32_t portIndex = 0;
  136. String text;
  137. gUsedSymbols.clear();
  138. carla_stdout("Generating data for %s...", pluginDesc->name);
  139. // -------------------------------------------------------------------
  140. // Init plugin
  141. NativeHostDescriptor hostDesc;
  142. hostDesc.handle = nullptr;
  143. hostDesc.resourceDir = "";
  144. hostDesc.uiName = "";
  145. hostDesc.get_buffer_size = host_getBufferSize;
  146. hostDesc.get_sample_rate = host_getSampleRate;
  147. hostDesc.is_offline = host_isOffline;
  148. hostDesc.get_time_info = nullptr;
  149. hostDesc.write_midi_event = nullptr;
  150. hostDesc.ui_parameter_changed = nullptr;
  151. hostDesc.ui_midi_program_changed = nullptr;
  152. hostDesc.ui_custom_data_changed = nullptr;
  153. hostDesc.ui_closed = nullptr;
  154. hostDesc.ui_open_file = nullptr;
  155. hostDesc.ui_save_file = nullptr;
  156. hostDesc.dispatcher = host_dispatcher;
  157. NativePluginHandle pluginHandle = nullptr;
  158. if (! pluginLabel.startsWithIgnoreCase("carla"))
  159. {
  160. pluginHandle = pluginDesc->instantiate(&hostDesc);
  161. CARLA_SAFE_ASSERT_RETURN(pluginHandle != nullptr,)
  162. }
  163. // -------------------------------------------------------------------
  164. // Header
  165. text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n";
  166. text += "@prefix doap: <http://usefulinc.com/ns/doap#> .\n";
  167. text += "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n";
  168. text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
  169. text += "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n";
  170. text += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
  171. text += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
  172. text += "@prefix unit: <" LV2_UNITS_PREFIX "> .\n";
  173. text += "\n";
  174. // -------------------------------------------------------------------
  175. // Plugin URI
  176. text += "<http://kxstudio.sf.net/carla/plugins/" + pluginLabel + ">\n";
  177. // -------------------------------------------------------------------
  178. // Category
  179. switch (pluginDesc->category)
  180. {
  181. case NATIVE_PLUGIN_CATEGORY_SYNTH:
  182. text += " a lv2:InstrumentPlugin, lv2:Plugin ;\n";
  183. break;
  184. case NATIVE_PLUGIN_CATEGORY_DELAY:
  185. text += " a lv2:DelayPlugin, lv2:Plugin ;\n";
  186. break;
  187. case NATIVE_PLUGIN_CATEGORY_EQ:
  188. text += " a lv2:EQPlugin, lv2:Plugin ;\n";
  189. break;
  190. case NATIVE_PLUGIN_CATEGORY_FILTER:
  191. text += " a lv2:FilterPlugin, lv2:Plugin ;\n";
  192. break;
  193. case NATIVE_PLUGIN_CATEGORY_DYNAMICS:
  194. text += " a lv2:DynamicsPlugin, lv2:Plugin ;\n";
  195. break;
  196. case NATIVE_PLUGIN_CATEGORY_MODULATOR:
  197. text += " a lv2:ModulatorPlugin, lv2:Plugin ;\n";
  198. break;
  199. case NATIVE_PLUGIN_CATEGORY_UTILITY:
  200. text += " a lv2:UtilityPlugin, lv2:Plugin ;\n";
  201. break;
  202. default:
  203. text += " a lv2:Plugin ;\n";
  204. break;
  205. }
  206. text += "\n";
  207. // -------------------------------------------------------------------
  208. // Features
  209. // optional
  210. if (pluginDesc->hints & NATIVE_PLUGIN_IS_RTSAFE)
  211. text += " lv2:optionalFeature <" LV2_CORE__hardRTCapable "> ;\n\n";
  212. // required
  213. text += " lv2:requiredFeature <" LV2_BUF_SIZE__boundedBlockLength "> ,\n";
  214. if (pluginDesc->hints & NATIVE_PLUGIN_NEEDS_FIXED_BUFFERS)
  215. text += " <" LV2_BUF_SIZE__fixedBlockLength "> ,\n";
  216. text += " <" LV2_OPTIONS__options "> ,\n";
  217. text += " <" LV2_URID__map "> ;\n";
  218. text += "\n";
  219. // -------------------------------------------------------------------
  220. // Extensions
  221. text += " lv2:extensionData <" LV2_OPTIONS__interface "> ;";
  222. if (pluginDesc->hints & NATIVE_PLUGIN_USES_STATE)
  223. text += " lv2:extensionData <" LV2_STATE__interface "> ;";
  224. if (pluginDesc->category != NATIVE_PLUGIN_CATEGORY_SYNTH)
  225. text += " lv2:extensionData <" LV2_PROGRAMS__Interface "> ;\n";
  226. text += "\n";
  227. // -------------------------------------------------------------------
  228. // UIs
  229. if (pluginDesc->hints & NATIVE_PLUGIN_HAS_UI)
  230. {
  231. text += " ui:ui <http://kxstudio.sf.net/carla/ui> ;\n";
  232. text += "\n";
  233. }
  234. // -------------------------------------------------------------------
  235. // First MIDI/Time port
  236. if (pluginDesc->midiIns > 0 || (pluginDesc->hints & NATIVE_PLUGIN_USES_TIME) != 0)
  237. {
  238. text += " lv2:port [\n";
  239. text += " a lv2:InputPort, atom:AtomPort ;\n";
  240. text += " atom:bufferType atom:Sequence ;\n";
  241. if (pluginDesc->midiIns > 0 && (pluginDesc->hints & NATIVE_PLUGIN_USES_TIME) != 0)
  242. {
  243. text += " atom:supports <" LV2_MIDI__MidiEvent "> ,\n";
  244. text += " <" LV2_TIME__Position "> ;\n";
  245. }
  246. else if (pluginDesc->midiIns > 0)
  247. text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
  248. else
  249. text += " atom:supports <" LV2_TIME__Position "> ;\n";
  250. text += " lv2:designation lv2:control ;\n";
  251. text += " lv2:index " + String(portIndex++) + " ;\n";
  252. if (pluginDesc->midiIns > 1)
  253. {
  254. text += " lv2:symbol \"lv2_events_in_1\" ;\n";
  255. text += " lv2:name \"Events Input #1\" ;\n";
  256. }
  257. else
  258. {
  259. text += " lv2:symbol \"lv2_events_in\" ;\n";
  260. text += " lv2:name \"Events Input\" ;\n";
  261. }
  262. text += " ] ;\n\n";
  263. }
  264. // -------------------------------------------------------------------
  265. // MIDI inputs
  266. for (uint32_t i=1; i < pluginDesc->midiIns; ++i)
  267. {
  268. if (i == 1)
  269. text += " lv2:port [\n";
  270. text += " a lv2:InputPort, atom:AtomPort ;\n";
  271. text += " atom:bufferType atom:Sequence ;\n";
  272. text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
  273. text += " lv2:index " + String(portIndex++) + " ;\n";
  274. if (pluginDesc->midiIns > 1)
  275. {
  276. text += " lv2:symbol \"lv2_events_in_" + String(i+1) + "\" ;\n";
  277. text += " lv2:name \"Events Input #" + String(i+1) + "\" ;\n";
  278. }
  279. else
  280. {
  281. text += " lv2:symbol \"lv2_events_in\" ;\n";
  282. text += " lv2:name \"Events Input\" ;\n";
  283. }
  284. if (i+1 == pluginDesc->midiIns)
  285. text += " ] ;\n\n";
  286. else
  287. text += " ] , [\n";
  288. }
  289. // -------------------------------------------------------------------
  290. // MIDI outputs
  291. for (uint32_t i=0; i < pluginDesc->midiOuts; ++i)
  292. {
  293. if (i == 0)
  294. text += " lv2:port [\n";
  295. text += " a lv2:OutputPort, atom:AtomPort ;\n";
  296. text += " atom:bufferType atom:Sequence ;\n";
  297. text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
  298. text += " lv2:index " + String(portIndex++) + " ;\n";
  299. if (pluginDesc->midiOuts > 1)
  300. {
  301. text += " lv2:symbol \"lv2_midi_out_" + String(i+1) + "\" ;\n";
  302. text += " lv2:name \"MIDI Output #" + String(i+1) + "\" ;\n";
  303. }
  304. else
  305. {
  306. text += " lv2:symbol \"lv2_midi_out\" ;\n";
  307. text += " lv2:name \"MIDI Output\" ;\n";
  308. }
  309. if (i+1 == pluginDesc->midiOuts)
  310. text += " ] ;\n\n";
  311. else
  312. text += " ] , [\n";
  313. }
  314. // -------------------------------------------------------------------
  315. // Freewheel port
  316. text += " lv2:port [\n";
  317. text += " a lv2:InputPort, lv2:ControlPort ;\n";
  318. text += " lv2:index " + String(portIndex++) + " ;\n";
  319. text += " lv2:symbol \"lv2_freewheel\" ;\n";
  320. text += " lv2:name \"Freewheel\" ;\n";
  321. text += " lv2:default 0.0 ;\n";
  322. text += " lv2:minimum 0.0 ;\n";
  323. text += " lv2:maximum 1.0 ;\n";
  324. text += " lv2:designation <" LV2_CORE__freeWheeling "> ;\n";
  325. text += " lv2:portProperty lv2:toggled ;\n";
  326. text += " ] ;\n";
  327. text += "\n";
  328. // -------------------------------------------------------------------
  329. // Audio inputs
  330. for (uint32_t i=0; i < pluginDesc->audioIns; ++i)
  331. {
  332. if (i == 0)
  333. text += " lv2:port [\n";
  334. text += " a lv2:InputPort, lv2:AudioPort ;\n";
  335. text += " lv2:index " + String(portIndex++) + " ;\n";
  336. text += " lv2:symbol \"lv2_audio_in_" + String(i+1) + "\" ;\n";
  337. text += " lv2:name \"Audio Input " + String(i+1) + "\" ;\n";
  338. if (i+1 == pluginDesc->audioIns)
  339. text += " ] ;\n\n";
  340. else
  341. text += " ] , [\n";
  342. }
  343. // -------------------------------------------------------------------
  344. // Audio outputs
  345. for (uint32_t i=0; i < pluginDesc->audioOuts; ++i)
  346. {
  347. if (i == 0)
  348. text += " lv2:port [\n";
  349. text += " a lv2:OutputPort, lv2:AudioPort ;\n";
  350. text += " lv2:index " + String(portIndex++) + " ;\n";
  351. text += " lv2:symbol \"lv2_audio_out_" + String(i+1) + "\" ;\n";
  352. text += " lv2:name \"Audio Output " + String(i+1) + "\" ;\n";
  353. if (i+1 == pluginDesc->audioOuts)
  354. text += " ] ;\n\n";
  355. else
  356. text += " ] , [\n";
  357. }
  358. // -------------------------------------------------------------------
  359. // Parameters
  360. const uint32_t paramCount((pluginHandle != nullptr && pluginDesc->get_parameter_count != nullptr) ? pluginDesc->get_parameter_count(pluginHandle) : 0);
  361. if (paramCount > 0)
  362. {
  363. CARLA_SAFE_ASSERT_RETURN(pluginDesc->get_parameter_info != nullptr,)
  364. CARLA_SAFE_ASSERT_RETURN(pluginDesc->get_parameter_value != nullptr,)
  365. }
  366. for (uint32_t i=0; i < paramCount; ++i)
  367. {
  368. const NativeParameter* paramInfo(pluginDesc->get_parameter_info(pluginHandle, i));
  369. const String paramName(paramInfo->name != nullptr ? paramInfo->name : "");
  370. const String paramUnit(paramInfo->unit != nullptr ? paramInfo->unit : "");
  371. CARLA_SAFE_ASSERT_RETURN(paramInfo != nullptr,)
  372. if (i == 0)
  373. text += " lv2:port [\n";
  374. if (paramInfo->hints & NATIVE_PARAMETER_IS_OUTPUT)
  375. text += " a lv2:OutputPort, lv2:ControlPort ;\n";
  376. else
  377. text += " a lv2:InputPort, lv2:ControlPort ;\n";
  378. text += " lv2:index " + String(portIndex++) + " ;\n";
  379. text += " lv2:symbol \"" + nameToSymbol(paramName, i) + "\" ;\n";
  380. if (paramName.isNotEmpty())
  381. text += " lv2:name \"" + paramName + "\" ;\n";
  382. else
  383. text += " lv2:name \"Port " + String(i+1) + "\" ;\n";
  384. text += " lv2:default " + String::formatted("%f", paramInfo->ranges.def) + " ;\n";
  385. text += " lv2:minimum " + String::formatted("%f", paramInfo->ranges.min) + " ;\n";
  386. text += " lv2:maximum " + String::formatted("%f", paramInfo->ranges.max) + " ;\n";
  387. if (paramInfo->hints & NATIVE_PARAMETER_IS_ENABLED)
  388. {
  389. if ((paramInfo->hints & NATIVE_PARAMETER_IS_AUTOMABLE) == 0)
  390. text += " lv2:portProperty <" LV2_PORT_PROPS__expensive "> ;\n";
  391. if (paramInfo->hints & NATIVE_PARAMETER_IS_BOOLEAN)
  392. text += " lv2:portProperty lv2:toggled ;\n";
  393. if (paramInfo->hints & NATIVE_PARAMETER_IS_INTEGER)
  394. text += " lv2:portProperty lv2:integer ;\n";
  395. if (paramInfo->hints & NATIVE_PARAMETER_IS_LOGARITHMIC)
  396. text += " lv2:portProperty <" LV2_PORT_PROPS__logarithmic "> ;\n";
  397. if (paramInfo->hints & NATIVE_PARAMETER_USES_SAMPLE_RATE)
  398. text += " lv2:portProperty lv2:toggled ;\n";
  399. if (paramInfo->hints & NATIVE_PARAMETER_USES_SCALEPOINTS)
  400. text += " lv2:portProperty lv2:enumeration ;\n";
  401. if (paramInfo->hints & NATIVE_PARAMETER_USES_CUSTOM_TEXT)
  402. pass(); // TODO: custom lv2 extension for this
  403. }
  404. else
  405. {
  406. text += " lv2:portProperty <" LV2_PORT_PROPS__notOnGUI "> ;\n";
  407. }
  408. for (uint32_t j=0; j < paramInfo->scalePointCount; ++j)
  409. {
  410. const NativeParameterScalePoint* const scalePoint(&paramInfo->scalePoints[j]);
  411. if (j == 0)
  412. text += " lv2:scalePoint [ ";
  413. else
  414. text += " [ ";
  415. text += "rdfs:label \"" + String(scalePoint->label) + "\" ;\n";
  416. text += " rdf:value " + String::formatted("%f", scalePoint->value) + " ";
  417. if (j+1 == paramInfo->scalePointCount)
  418. text += "] ;\n";
  419. else
  420. text += "] ,\n";
  421. }
  422. if (paramUnit.isNotEmpty())
  423. {
  424. text += " unit:unit [\n";
  425. text += " a unit:Unit ;\n";
  426. text += " rdfs:label \"" + paramUnit + "\" ;\n";
  427. text += " unit:symbol \"" + paramUnit + "\" ;\n";
  428. text += " unit:render \"%f " + paramUnit + "\" ;\n";
  429. text += " ] ;\n";
  430. }
  431. if (i+1 == paramCount)
  432. text += " ] ;\n\n";
  433. else
  434. text += " ] , [\n";
  435. }
  436. text += " doap:name \"" + String(pluginDesc->name) + "\" ;\n";
  437. text += " doap:maintainer [ foaf:name \"" + String(pluginDesc->maker) + "\" ] .\n";
  438. // -------------------------------------------------------------------
  439. // Write file now
  440. std::fstream pluginStream(pluginFile.toRawUTF8(), std::ios::out);
  441. pluginStream << text.toRawUTF8();
  442. pluginStream.close();
  443. // -------------------------------------------------------------------
  444. // Cleanup plugin
  445. if (pluginHandle != nullptr && pluginDesc->cleanup != nullptr)
  446. pluginDesc->cleanup(pluginHandle);
  447. }
  448. // -----------------------------------------------------------------------
  449. int main()
  450. {
  451. PluginListManager& plm(PluginListManager::getInstance());
  452. writeManifestFile(plm);
  453. for (LinkedList<const NativePluginDescriptor*>::Itenerator it = plm.descs.begin(); it.valid(); it.next())
  454. {
  455. const NativePluginDescriptor* const& pluginDesc(it.getValue());
  456. writePluginFile(pluginDesc);
  457. }
  458. carla_stdout("Done.");
  459. return 0;
  460. }
  461. // -----------------------------------------------------------------------