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.

451 lines
14KB

  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. // -------------------------------------------------------------------
  114. // Write file now
  115. std::fstream manifest("carla-native.lv2/manifest.ttl", std::ios::out);
  116. manifest << text.toRawUTF8();
  117. manifest.close();
  118. }
  119. // -----------------------------------------------------------------------
  120. static uint32_t host_getBufferSize(HostHandle) { return 512; }
  121. static double host_getSampleRate(HostHandle) { return 44100.0; }
  122. static bool host_isOffline(HostHandle) { return true; }
  123. static intptr_t host_dispatcher(HostHandle, HostDispatcherOpcode, int32_t, intptr_t, void*, float) { return 0; }
  124. void writePluginFile(const PluginDescriptor* const pluginDesc)
  125. {
  126. const String pluginLabel(pluginDesc->label);
  127. const String pluginFile("carla-native.lv2/" + pluginLabel + ".ttl");
  128. uint32_t portIndex = 0;
  129. String text;
  130. gUsedSymbols.clear();
  131. carla_stdout("Generating data for %s...", pluginDesc->name);
  132. // -------------------------------------------------------------------
  133. // Init plugin
  134. HostDescriptor hostDesc;
  135. hostDesc.handle = nullptr;
  136. hostDesc.resourceDir = "";
  137. hostDesc.uiName = "";
  138. hostDesc.get_buffer_size = host_getBufferSize;
  139. hostDesc.get_sample_rate = host_getSampleRate;
  140. hostDesc.is_offline = host_isOffline;
  141. hostDesc.get_time_info = nullptr;
  142. hostDesc.write_midi_event = nullptr;
  143. hostDesc.ui_parameter_changed = nullptr;
  144. hostDesc.ui_midi_program_changed = nullptr;
  145. hostDesc.ui_custom_data_changed = nullptr;
  146. hostDesc.ui_closed = nullptr;
  147. hostDesc.ui_open_file = nullptr;
  148. hostDesc.ui_save_file = nullptr;
  149. hostDesc.dispatcher = host_dispatcher;
  150. PluginHandle pluginHandle = pluginDesc->instantiate(&hostDesc);
  151. CARLA_SAFE_ASSERT_RETURN(pluginHandle != nullptr,)
  152. // -------------------------------------------------------------------
  153. // Header
  154. text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n";
  155. text += "@prefix doap: <http://usefulinc.com/ns/doap#> .\n";
  156. text += "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n";
  157. text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
  158. text += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
  159. text += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
  160. text += "@prefix unit: <" LV2_UNITS_PREFIX "> .\n";
  161. text += "\n";
  162. // -------------------------------------------------------------------
  163. // Plugin
  164. if (pluginLabel == "carla")
  165. text += "<http://kxstudio.sf.net/carla>\n";
  166. else
  167. text += "<http://kxstudio.sf.net/carla/plugins/" + pluginLabel + ">\n";
  168. //text += " a " + getPluginType() + " ;\n";
  169. text += " lv2:requiredFeature <" LV2_BUF_SIZE__boundedBlockLength "> ,\n";
  170. text += " <" LV2_URID__map "> ;\n";
  171. text += " lv2:extensionData <" LV2_OPTIONS__interface "> ,\n";
  172. text += " <" LV2_STATE__interface "> ;\n";
  173. text += "\n";
  174. // -------------------------------------------------------------------
  175. // UIs
  176. if (pluginDesc->hints & PLUGIN_HAS_GUI)
  177. {
  178. text += " ui:ui <http://kxstudio.sf.net/carla#UI> ;\n";
  179. text += "\n";
  180. }
  181. // -------------------------------------------------------------------
  182. // MIDI inputs
  183. for (uint32_t i=0; i < pluginDesc->midiIns; ++i)
  184. {
  185. if (i == 0)
  186. text += " lv2:port [\n";
  187. else
  188. text += " [\n";
  189. text += " a lv2:InputPort, atom:AtomPort ;\n";
  190. text += " atom:bufferType atom:Sequence ;\n";
  191. if (i == 0)
  192. {
  193. text += " atom:supports <" LV2_MIDI__MidiEvent "> ,\n";
  194. text += " <" LV2_TIME__Position "> ;\n";
  195. text += " lv2:designation lv2:control ;\n";
  196. }
  197. else
  198. {
  199. text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
  200. }
  201. text += " lv2:index " + String(portIndex++) + " ;\n";
  202. if (pluginDesc->midiIns > 1)
  203. {
  204. text += " lv2:symbol \"lv2_events_in_" + String(i+1) + "\" ;\n";
  205. text += " lv2:name \"Events Input #" + String(i+1) + "\" ;\n";
  206. }
  207. else
  208. {
  209. text += " lv2:symbol \"lv2_events_in\" ;\n";
  210. text += " lv2:name \"Events Input\" ;\n";
  211. }
  212. if (i+1 == pluginDesc->midiIns)
  213. text += " ] ;\n\n";
  214. else
  215. text += " ] ,\n";
  216. }
  217. // -------------------------------------------------------------------
  218. // MIDI outputs
  219. for (uint32_t i=0; i < pluginDesc->midiOuts; ++i)
  220. {
  221. if (i == 0)
  222. text += " lv2:port [\n";
  223. else
  224. text += " [\n";
  225. text += " a lv2:OutputPort, atom:AtomPort ;\n";
  226. text += " atom:bufferType atom:Sequence ;\n";
  227. text += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
  228. text += " lv2:index " + String(portIndex++) + " ;\n";
  229. if (pluginDesc->midiOuts > 1)
  230. {
  231. text += " lv2:symbol \"lv2_midi_out_" + String(i+1) + "\" ;\n";
  232. text += " lv2:name \"MIDI Output #" + String(i+1) + "\" ;\n";
  233. }
  234. else
  235. {
  236. text += " lv2:symbol \"lv2_midi_out\" ;\n";
  237. text += " lv2:name \"MIDI Output\" ;\n";
  238. }
  239. if (i+1 == pluginDesc->midiOuts)
  240. text += " ] ;\n\n";
  241. else
  242. text += " ] ,\n";
  243. }
  244. // -------------------------------------------------------------------
  245. // Freewheel port
  246. text += " lv2:port [\n";
  247. text += " a lv2:InputPort, lv2:ControlPort ;\n";
  248. text += " lv2:index " + String(portIndex++) + " ;\n";
  249. text += " lv2:symbol \"lv2_freewheel\" ;\n";
  250. text += " lv2:name \"Freewheel\" ;\n";
  251. text += " lv2:default 0.0 ;\n";
  252. text += " lv2:minimum 0.0 ;\n";
  253. text += " lv2:maximum 1.0 ;\n";
  254. text += " lv2:designation <" LV2_CORE__freeWheeling "> ;\n";
  255. text += " lv2:portProperty lv2:toggled ;\n";
  256. text += " ] ;\n";
  257. text += "\n";
  258. // -------------------------------------------------------------------
  259. // Audio inputs
  260. for (uint32_t i=0; i < pluginDesc->audioIns; ++i)
  261. {
  262. if (i == 0)
  263. text += " lv2:port [\n";
  264. else
  265. text += " [\n";
  266. text += " a lv2:InputPort, lv2:AudioPort ;\n";
  267. text += " lv2:index " + String(portIndex++) + " ;\n";
  268. text += " lv2:symbol \"lv2_audio_in_" + String(i+1) + "\" ;\n";
  269. text += " lv2:name \"Audio Input " + String(i+1) + "\" ;\n";
  270. if (i+1 == pluginDesc->audioIns)
  271. text += " ] ;\n\n";
  272. else
  273. text += " ] ,\n";
  274. }
  275. // -------------------------------------------------------------------
  276. // Audio outputs
  277. for (uint32_t i=0; i < pluginDesc->audioOuts; ++i)
  278. {
  279. if (i == 0)
  280. text += " lv2:port [\n";
  281. else
  282. text += " [\n";
  283. text += " a lv2:OutputPort, lv2:AudioPort ;\n";
  284. text += " lv2:index " + String(portIndex++) + " ;\n";
  285. text += " lv2:symbol \"lv2_audio_out_" + String(i+1) + "\" ;\n";
  286. text += " lv2:name \"Audio Output " + String(i+1) + "\" ;\n";
  287. if (i+1 == pluginDesc->audioOuts)
  288. text += " ] ;\n\n";
  289. else
  290. text += " ] ,\n";
  291. }
  292. // -------------------------------------------------------------------
  293. // Parameters
  294. const uint32_t paramCount(pluginDesc->get_parameter_count != nullptr ? pluginDesc->get_parameter_count(pluginHandle) : 0);
  295. if (paramCount > 0)
  296. {
  297. CARLA_SAFE_ASSERT_RETURN(pluginDesc->get_parameter_info != nullptr,)
  298. CARLA_SAFE_ASSERT_RETURN(pluginDesc->get_parameter_value != nullptr,)
  299. }
  300. for (uint32_t i=0; i < paramCount; ++i)
  301. {
  302. const Parameter* paramInfo(pluginDesc->get_parameter_info(pluginHandle, i));
  303. const String paramName(paramInfo->name);
  304. const String paramUnit(paramInfo->unit != nullptr ? paramInfo->unit : "");
  305. const float paramValue(pluginDesc->get_parameter_value(pluginHandle, i));
  306. CARLA_SAFE_ASSERT_RETURN(paramInfo != nullptr,)
  307. if (i == 0)
  308. text += " lv2:port [\n";
  309. else
  310. text += " [\n";
  311. text += " a lv2:InputPort, lv2:ControlPort ;\n";
  312. text += " lv2:index " + String(portIndex++) + " ;\n";
  313. text += " lv2:symbol \"" + nameToSymbol(paramName, i) + "\" ;\n";
  314. if (paramName.isNotEmpty())
  315. text += " lv2:name \"" + paramName + "\" ;\n";
  316. else
  317. text += " lv2:name \"Port " + String(i+1) + "\" ;\n";
  318. text += " lv2:default " + String::formatted("%f", paramValue) + " ;\n";
  319. text += " lv2:minimum 0.0 ;\n";
  320. text += " lv2:maximum 1.0 ;\n";
  321. if (paramUnit.isNotEmpty())
  322. {
  323. text += " units:unit [\n";
  324. text += " a units:Unit ;\n";
  325. text += " rdfs:label \"" + paramUnit + "\" ;\n";
  326. text += " units:symbol \"" + paramUnit + "\" ;\n";
  327. text += " units:render \"%f " + paramUnit + "\" ;\n";
  328. text += " ] ;\n";
  329. }
  330. // if (! filter->isParameterAutomatable(i))
  331. // text += " lv2:portProperty <" LV2_PORT_PROPS__expensive "> ;\n";
  332. if (i+1 == paramCount)
  333. text += " ] ;\n\n";
  334. else
  335. text += " ] ,\n";
  336. }
  337. text += " doap:name \"" + String(pluginDesc->name) + "\" ;\n";
  338. text += " doap:maintainer [ foaf:name \"" + String(pluginDesc->maker) + "\" ] .\n";
  339. // -------------------------------------------------------------------
  340. // Write file now
  341. std::fstream pluginStream(pluginFile.toRawUTF8(), std::ios::out);
  342. pluginStream << text.toRawUTF8();
  343. pluginStream.close();
  344. // -------------------------------------------------------------------
  345. // Cleanup plugin
  346. if (pluginDesc->cleanup != nullptr)
  347. pluginDesc->cleanup(pluginHandle);
  348. }
  349. // -----------------------------------------------------------------------
  350. int main()
  351. {
  352. writeManifestFile();
  353. for (NonRtList<const PluginDescriptor*>::Itenerator it = sPluginDescsMgr.descs.begin(); it.valid(); it.next())
  354. {
  355. const PluginDescriptor*& pluginDesc(*it);
  356. writePluginFile(pluginDesc);
  357. }
  358. carla_stdout("Done.");
  359. return 0;
  360. }
  361. // -----------------------------------------------------------------------