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.

482 lines
16KB

  1. /*
  2. * Carla Native Plugins
  3. * Copyright (C) 2013 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. #include "carla-native-base.cpp"
  18. #include "JuceHeader.h"
  19. #include "lv2/atom.h"
  20. #include "lv2/buf-size.h"
  21. #include "lv2/instance-access.h"
  22. #include "lv2/midi.h"
  23. #include "lv2/options.h"
  24. #include "lv2/state.h"
  25. #include "lv2/time.h"
  26. #include "lv2/ui.h"
  27. #include "lv2/units.h"
  28. #include "lv2/urid.h"
  29. #include "lv2/lv2_external_ui.h"
  30. #include <fstream>
  31. #if JUCE_WINDOWS
  32. # define PLUGIN_EXT ".dll"
  33. #elif JUCE_MAC
  34. # define PLUGIN_EXT ".dylib"
  35. #else
  36. # define PLUGIN_EXT ".so"
  37. #endif
  38. using juce::String;
  39. using juce::StringArray;
  40. using juce::juce_wchar;
  41. // -----------------------------------------------------------------------
  42. // Converts a parameter name to an LV2 compatible symbol
  43. static StringArray gUsedSymbols;
  44. const String nameToSymbol(const String& name, const uint32_t portIndex)
  45. {
  46. String symbol, trimmedName = name.trimStart().trimEnd().toLowerCase();
  47. if (trimmedName.isEmpty())
  48. {
  49. symbol += "lv2_port_";
  50. symbol += String(portIndex+1);
  51. }
  52. else
  53. {
  54. for (int i=0; i < trimmedName.length(); ++i)
  55. {
  56. const juce_wchar c = trimmedName[i];
  57. if (i == 0 && std::isdigit(c))
  58. symbol += "_";
  59. else if (std::isalpha(c) || std::isdigit(c))
  60. symbol += c;
  61. else
  62. symbol += "_";
  63. }
  64. }
  65. // Do not allow identical symbols
  66. if (gUsedSymbols.contains(symbol))
  67. {
  68. int offset = 2;
  69. String offsetStr = "_2";
  70. symbol += offsetStr;
  71. while (gUsedSymbols.contains(symbol))
  72. {
  73. offset += 1;
  74. String newOffsetStr = "_" + String(offset);
  75. symbol = symbol.replace(offsetStr, newOffsetStr);
  76. offsetStr = newOffsetStr;
  77. }
  78. }
  79. gUsedSymbols.add(symbol);
  80. return symbol;
  81. }
  82. // -----------------------------------------------------------------------
  83. void writeManifestFile()
  84. {
  85. String text;
  86. // -------------------------------------------------------------------
  87. // Header
  88. text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
  89. text += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
  90. text += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
  91. text += "\n";
  92. // -------------------------------------------------------------------
  93. // Plugins
  94. for (NonRtList<const PluginDescriptor*>::Itenerator it = sPluginDescsMgr.descs.begin(); it.valid(); it.next())
  95. {
  96. const PluginDescriptor*& pluginDesc(*it);
  97. const String label(pluginDesc->label);
  98. if (label == "carla")
  99. text += "<http://kxstudio.sf.net/carla>\n";
  100. else
  101. text += "<http://kxstudio.sf.net/carla/plugins/" + label + ">\n";
  102. text += " a lv2:Plugin ;\n";
  103. text += " lv2:binary <carla-native" PLUGIN_EXT "> ;\n";
  104. text += " rdfs:seeAlso <" + label + ".ttl> .\n";
  105. text += "\n";
  106. }
  107. // -------------------------------------------------------------------
  108. // UI
  109. text += "<http://kxstudio.sf.net/carla#UI>\n";
  110. text += " a <" LV2_EXTERNAL_UI__Widget "> ;\n";
  111. text += " ui:binary <carla-native" PLUGIN_EXT "> ;\n";
  112. text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> .\n";
  113. text += "\n";
  114. text += "<http://kxstudio.sf.net/carla#UIold>\n";
  115. text += " a <" LV2_EXTERNAL_UI_DEPRECATED_URI "> ;\n";
  116. text += " ui:binary <carla-native" PLUGIN_EXT "> ;\n";
  117. text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> .\n";
  118. // -------------------------------------------------------------------
  119. // Write file now
  120. std::fstream manifest("carla-native.lv2/manifest.ttl", std::ios::out);
  121. manifest << text.toRawUTF8();
  122. manifest.close();
  123. }
  124. // -----------------------------------------------------------------------
  125. static uint32_t host_getBufferSize(HostHandle) { return 512; }
  126. static double host_getSampleRate(HostHandle) { return 44100.0; }
  127. static bool host_isOffline(HostHandle) { return true; }
  128. static intptr_t host_dispatcher(HostHandle, HostDispatcherOpcode, int32_t, intptr_t, void*, float) { return 0; }
  129. void writePluginFile(const PluginDescriptor* const pluginDesc)
  130. {
  131. const String pluginLabel(pluginDesc->label);
  132. const String pluginFile("carla-native.lv2/" + pluginLabel + ".ttl");
  133. uint32_t portIndex = 0;
  134. String text;
  135. gUsedSymbols.clear();
  136. carla_stdout("Generating data for %s...", pluginDesc->name);
  137. // -------------------------------------------------------------------
  138. // Init plugin
  139. HostDescriptor hostDesc;
  140. hostDesc.handle = nullptr;
  141. hostDesc.resourceDir = "";
  142. hostDesc.uiName = "";
  143. hostDesc.get_buffer_size = host_getBufferSize;
  144. hostDesc.get_sample_rate = host_getSampleRate;
  145. hostDesc.is_offline = host_isOffline;
  146. hostDesc.get_time_info = nullptr;
  147. hostDesc.write_midi_event = nullptr;
  148. hostDesc.ui_parameter_changed = nullptr;
  149. hostDesc.ui_midi_program_changed = nullptr;
  150. hostDesc.ui_custom_data_changed = nullptr;
  151. hostDesc.ui_closed = nullptr;
  152. hostDesc.ui_open_file = nullptr;
  153. hostDesc.ui_save_file = nullptr;
  154. hostDesc.dispatcher = host_dispatcher;
  155. PluginHandle pluginHandle = pluginDesc->instantiate(&hostDesc);
  156. CARLA_SAFE_ASSERT_RETURN(pluginHandle != nullptr,)
  157. // -------------------------------------------------------------------
  158. // Header
  159. text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n";
  160. text += "@prefix doap: <http://usefulinc.com/ns/doap#> .\n";
  161. text += "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n";
  162. text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
  163. text += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
  164. text += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
  165. text += "@prefix unit: <" LV2_UNITS_PREFIX "> .\n";
  166. text += "\n";
  167. // -------------------------------------------------------------------
  168. // Plugin
  169. if (pluginLabel == "carla")
  170. text += "<http://kxstudio.sf.net/carla>\n";
  171. else
  172. text += "<http://kxstudio.sf.net/carla/plugins/" + pluginLabel + ">\n";
  173. //text += " a " + getPluginType() + " ;\n";
  174. text += " lv2:requiredFeature <" LV2_BUF_SIZE__boundedBlockLength "> ,\n";
  175. text += " <" LV2_URID__map "> ;\n";
  176. text += " lv2:extensionData <" LV2_OPTIONS__interface "> ,\n";
  177. text += " <" LV2_STATE__interface "> ;\n";
  178. text += "\n";
  179. // -------------------------------------------------------------------
  180. // UIs
  181. if (pluginDesc->hints & PLUGIN_HAS_GUI)
  182. {
  183. text += " ui:ui <http://kxstudio.sf.net/carla#UI> ,\n";
  184. text += " <http://kxstudio.sf.net/carla#UIold> ;\n";
  185. text += "\n";
  186. }
  187. // -------------------------------------------------------------------
  188. // First MIDI/Time port
  189. text += " lv2:port [\n";
  190. text += " a lv2:InputPort, atom:AtomPort ;\n";
  191. text += " atom:bufferType atom:Sequence ;\n";
  192. if (pluginDesc->midiIns > 0)
  193. {
  194. text += " atom:supports <" LV2_MIDI__MidiEvent "> ,\n";
  195. text += " <" LV2_TIME__Position "> ;\n";
  196. }
  197. else
  198. {
  199. text += " atom:supports <" LV2_TIME__Position "> ;\n";
  200. }
  201. text += " lv2:designation lv2:control ;\n";
  202. text += " lv2:index 0" + String(portIndex++) + " ;\n";
  203. if (pluginDesc->midiIns > 1)
  204. {
  205. text += " lv2:symbol \"lv2_events_in_1\" ;\n";
  206. text += " lv2:name \"Events Input #1\" ;\n";
  207. }
  208. else
  209. {
  210. text += " lv2:symbol \"lv2_events_in\" ;\n";
  211. text += " lv2:name \"Events Input\" ;\n";
  212. }
  213. text += " ] ;\n\n";
  214. // -------------------------------------------------------------------
  215. // MIDI inputs
  216. for (uint32_t i=1; i < pluginDesc->midiIns; ++i)
  217. {
  218. if (i == 1)
  219. text += " lv2:port [\n";
  220. else
  221. text += " [\n";
  222. text += " a lv2:InputPort, atom:AtomPort ;\n";
  223. text += " atom:bufferType atom:Sequence ;\n";
  224. text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
  225. text += " lv2:index " + String(portIndex++) + " ;\n";
  226. if (pluginDesc->midiIns > 1)
  227. {
  228. text += " lv2:symbol \"lv2_events_in_" + String(i+1) + "\" ;\n";
  229. text += " lv2:name \"Events Input #" + String(i+1) + "\" ;\n";
  230. }
  231. else
  232. {
  233. text += " lv2:symbol \"lv2_events_in\" ;\n";
  234. text += " lv2:name \"Events Input\" ;\n";
  235. }
  236. if (i+1 == pluginDesc->midiIns)
  237. text += " ] ;\n\n";
  238. else
  239. text += " ] ,\n";
  240. }
  241. // -------------------------------------------------------------------
  242. // MIDI outputs
  243. for (uint32_t i=0; i < pluginDesc->midiOuts; ++i)
  244. {
  245. if (i == 0)
  246. text += " lv2:port [\n";
  247. else
  248. text += " [\n";
  249. text += " a lv2:OutputPort, atom:AtomPort ;\n";
  250. text += " atom:bufferType atom:Sequence ;\n";
  251. text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
  252. text += " lv2:index " + String(portIndex++) + " ;\n";
  253. if (pluginDesc->midiOuts > 1)
  254. {
  255. text += " lv2:symbol \"lv2_midi_out_" + String(i+1) + "\" ;\n";
  256. text += " lv2:name \"MIDI Output #" + String(i+1) + "\" ;\n";
  257. }
  258. else
  259. {
  260. text += " lv2:symbol \"lv2_midi_out\" ;\n";
  261. text += " lv2:name \"MIDI Output\" ;\n";
  262. }
  263. if (i+1 == pluginDesc->midiOuts)
  264. text += " ] ;\n\n";
  265. else
  266. text += " ] ,\n";
  267. }
  268. // -------------------------------------------------------------------
  269. // Freewheel port
  270. text += " lv2:port [\n";
  271. text += " a lv2:InputPort, lv2:ControlPort ;\n";
  272. text += " lv2:index " + String(portIndex++) + " ;\n";
  273. text += " lv2:symbol \"lv2_freewheel\" ;\n";
  274. text += " lv2:name \"Freewheel\" ;\n";
  275. text += " lv2:default 0.0 ;\n";
  276. text += " lv2:minimum 0.0 ;\n";
  277. text += " lv2:maximum 1.0 ;\n";
  278. text += " lv2:designation <" LV2_CORE__freeWheeling "> ;\n";
  279. text += " lv2:portProperty lv2:toggled ;\n";
  280. text += " ] ;\n";
  281. text += "\n";
  282. // -------------------------------------------------------------------
  283. // Audio inputs
  284. for (uint32_t i=0; i < pluginDesc->audioIns; ++i)
  285. {
  286. if (i == 0)
  287. text += " lv2:port [\n";
  288. else
  289. text += " [\n";
  290. text += " a lv2:InputPort, lv2:AudioPort ;\n";
  291. text += " lv2:index " + String(portIndex++) + " ;\n";
  292. text += " lv2:symbol \"lv2_audio_in_" + String(i+1) + "\" ;\n";
  293. text += " lv2:name \"Audio Input " + String(i+1) + "\" ;\n";
  294. if (i+1 == pluginDesc->audioIns)
  295. text += " ] ;\n\n";
  296. else
  297. text += " ] ,\n";
  298. }
  299. // -------------------------------------------------------------------
  300. // Audio outputs
  301. for (uint32_t i=0; i < pluginDesc->audioOuts; ++i)
  302. {
  303. if (i == 0)
  304. text += " lv2:port [\n";
  305. else
  306. text += " [\n";
  307. text += " a lv2:OutputPort, lv2:AudioPort ;\n";
  308. text += " lv2:index " + String(portIndex++) + " ;\n";
  309. text += " lv2:symbol \"lv2_audio_out_" + String(i+1) + "\" ;\n";
  310. text += " lv2:name \"Audio Output " + String(i+1) + "\" ;\n";
  311. if (i+1 == pluginDesc->audioOuts)
  312. text += " ] ;\n\n";
  313. else
  314. text += " ] ,\n";
  315. }
  316. // -------------------------------------------------------------------
  317. // Parameters
  318. const uint32_t paramCount(pluginDesc->get_parameter_count != nullptr ? pluginDesc->get_parameter_count(pluginHandle) : 0);
  319. if (paramCount > 0)
  320. {
  321. CARLA_SAFE_ASSERT_RETURN(pluginDesc->get_parameter_info != nullptr,)
  322. CARLA_SAFE_ASSERT_RETURN(pluginDesc->get_parameter_value != nullptr,)
  323. }
  324. for (uint32_t i=0; i < paramCount; ++i)
  325. {
  326. const Parameter* paramInfo(pluginDesc->get_parameter_info(pluginHandle, i));
  327. const String paramName(paramInfo->name != nullptr ? paramInfo->name : "");
  328. const String paramUnit(paramInfo->unit != nullptr ? paramInfo->unit : "");
  329. CARLA_SAFE_ASSERT_RETURN(paramInfo != nullptr,)
  330. if (i == 0)
  331. text += " lv2:port [\n";
  332. else
  333. text += " [\n";
  334. if (paramInfo->hints & PARAMETER_IS_OUTPUT)
  335. text += " a lv2:OutputPort, lv2:ControlPort ;\n";
  336. else
  337. text += " a lv2:InputPort, lv2:ControlPort ;\n";
  338. text += " lv2:index " + String(portIndex++) + " ;\n";
  339. text += " lv2:symbol \"" + nameToSymbol(paramName, i) + "\" ;\n";
  340. if (paramName.isNotEmpty())
  341. text += " lv2:name \"" + paramName + "\" ;\n";
  342. else
  343. text += " lv2:name \"Port " + String(i+1) + "\" ;\n";
  344. text += " lv2:default " + String::formatted("%f", paramInfo->ranges.def) + " ;\n";
  345. text += " lv2:minimum " + String::formatted("%f", paramInfo->ranges.min) + " ;\n";
  346. text += " lv2:maximum " + String::formatted("%f", paramInfo->ranges.max) + " ;\n";
  347. if (paramUnit.isNotEmpty())
  348. {
  349. text += " units:unit [\n";
  350. text += " a units:Unit ;\n";
  351. text += " rdfs:label \"" + paramUnit + "\" ;\n";
  352. text += " units:symbol \"" + paramUnit + "\" ;\n";
  353. text += " units:render \"%f " + paramUnit + "\" ;\n";
  354. text += " ] ;\n";
  355. }
  356. // if (! filter->isParameterAutomatable(i))
  357. // text += " lv2:portProperty <" LV2_PORT_PROPS__expensive "> ;\n";
  358. if (i+1 == paramCount)
  359. text += " ] ;\n\n";
  360. else
  361. text += " ] ,\n";
  362. }
  363. text += " doap:name \"" + String(pluginDesc->name) + "\" ;\n";
  364. text += " doap:maintainer [ foaf:name \"" + String(pluginDesc->maker) + "\" ] .\n";
  365. // -------------------------------------------------------------------
  366. // Write file now
  367. std::fstream pluginStream(pluginFile.toRawUTF8(), std::ios::out);
  368. pluginStream << text.toRawUTF8();
  369. pluginStream.close();
  370. // -------------------------------------------------------------------
  371. // Cleanup plugin
  372. if (pluginDesc->cleanup != nullptr)
  373. pluginDesc->cleanup(pluginHandle);
  374. }
  375. // -----------------------------------------------------------------------
  376. int main()
  377. {
  378. writeManifestFile();
  379. for (NonRtList<const PluginDescriptor*>::Itenerator it = sPluginDescsMgr.descs.begin(); it.valid(); it.next())
  380. {
  381. const PluginDescriptor*& pluginDesc(*it);
  382. writePluginFile(pluginDesc);
  383. }
  384. carla_stdout("Done.");
  385. return 0;
  386. }
  387. // -----------------------------------------------------------------------