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.

567 lines
20KB

  1. /*
  2. * Carla State utils
  3. * Copyright (C) 2012-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. #include "CarlaStateUtils.hpp"
  18. #include "CarlaBackendUtils.hpp"
  19. #include "CarlaMathUtils.hpp"
  20. #include "CarlaMIDI.h"
  21. #include "juce_core.h"
  22. using juce::String;
  23. using juce::XmlElement;
  24. CARLA_BACKEND_START_NAMESPACE
  25. // -----------------------------------------------------------------------
  26. // xmlSafeString
  27. static String xmlSafeString(const String& string, const bool toXml)
  28. {
  29. String newString(string);
  30. if (toXml)
  31. return newString.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("'","&apos;").replace("\"","&quot;");
  32. else
  33. return newString.replace("&lt;","<").replace("&gt;",">").replace("&apos;","'").replace("&quot;","\"").replace("&amp;","&");
  34. }
  35. // -----------------------------------------------------------------------
  36. // xmlSafeStringCharDup
  37. static const char* xmlSafeStringCharDup(const String& string, const bool toXml)
  38. {
  39. return carla_strdup(xmlSafeString(string, toXml).toRawUTF8());
  40. }
  41. // -----------------------------------------------------------------------
  42. // StateParameter
  43. StateParameter::StateParameter() noexcept
  44. : isInput(true),
  45. index(-1),
  46. name(nullptr),
  47. symbol(nullptr),
  48. value(0.0f),
  49. midiChannel(0),
  50. midiCC(-1) {}
  51. StateParameter::~StateParameter() noexcept
  52. {
  53. if (name != nullptr)
  54. {
  55. delete[] name;
  56. name = nullptr;
  57. }
  58. if (symbol != nullptr)
  59. {
  60. delete[] symbol;
  61. symbol = nullptr;
  62. }
  63. }
  64. // -----------------------------------------------------------------------
  65. // StateCustomData
  66. StateCustomData::StateCustomData() noexcept
  67. : type(nullptr),
  68. key(nullptr),
  69. value(nullptr) {}
  70. StateCustomData::~StateCustomData() noexcept
  71. {
  72. if (type != nullptr)
  73. {
  74. delete[] type;
  75. type = nullptr;
  76. }
  77. if (key != nullptr)
  78. {
  79. delete[] key;
  80. key = nullptr;
  81. }
  82. if (value != nullptr)
  83. {
  84. delete[] value;
  85. value = nullptr;
  86. }
  87. }
  88. // -----------------------------------------------------------------------
  89. // StateSave
  90. StateSave::StateSave() noexcept
  91. : type(nullptr),
  92. name(nullptr),
  93. label(nullptr),
  94. binary(nullptr),
  95. uniqueId(0),
  96. active(false),
  97. dryWet(1.0f),
  98. volume(1.0f),
  99. balanceLeft(-1.0f),
  100. balanceRight(1.0f),
  101. panning(0.0f),
  102. ctrlChannel(-1),
  103. currentProgramIndex(-1),
  104. currentProgramName(nullptr),
  105. currentMidiBank(-1),
  106. currentMidiProgram(-1),
  107. chunk(nullptr) {}
  108. StateSave::~StateSave() noexcept
  109. {
  110. clear();
  111. }
  112. void StateSave::clear() noexcept
  113. {
  114. if (type != nullptr)
  115. {
  116. delete[] type;
  117. type = nullptr;
  118. }
  119. if (name != nullptr)
  120. {
  121. delete[] name;
  122. name = nullptr;
  123. }
  124. if (label != nullptr)
  125. {
  126. delete[] label;
  127. label = nullptr;
  128. }
  129. if (binary != nullptr)
  130. {
  131. delete[] binary;
  132. binary = nullptr;
  133. }
  134. if (currentProgramName != nullptr)
  135. {
  136. delete[] currentProgramName;
  137. currentProgramName = nullptr;
  138. }
  139. if (chunk != nullptr)
  140. {
  141. delete[] chunk;
  142. chunk = nullptr;
  143. }
  144. uniqueId = 0;
  145. active = false;
  146. dryWet = 1.0f;
  147. volume = 1.0f;
  148. balanceLeft = -1.0f;
  149. balanceRight = 1.0f;
  150. panning = 0.0f;
  151. ctrlChannel = -1;
  152. currentProgramIndex = -1;
  153. currentMidiBank = -1;
  154. currentMidiProgram = -1;
  155. for (StateParameterItenerator it = parameters.begin(); it.valid(); it.next())
  156. {
  157. StateParameter* const stateParameter(it.getValue());
  158. delete stateParameter;
  159. }
  160. for (StateCustomDataItenerator it = customData.begin(); it.valid(); it.next())
  161. {
  162. StateCustomData* const stateCustomData(it.getValue());
  163. delete stateCustomData;
  164. }
  165. parameters.clear();
  166. customData.clear();
  167. }
  168. // -----------------------------------------------------------------------
  169. // fillFromXmlElement
  170. bool StateSave::fillFromXmlElement(const XmlElement* const xmlElement)
  171. {
  172. CARLA_SAFE_ASSERT_RETURN(xmlElement != nullptr, false);
  173. clear();
  174. for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement())
  175. {
  176. const String& tagName(elem->getTagName());
  177. // ---------------------------------------------------------------
  178. // Info
  179. if (tagName.equalsIgnoreCase("info"))
  180. {
  181. for (XmlElement* xmlInfo = elem->getFirstChildElement(); xmlInfo != nullptr; xmlInfo = xmlInfo->getNextElement())
  182. {
  183. const String& tag(xmlInfo->getTagName());
  184. const String text(xmlInfo->getAllSubText().trim());
  185. if (tag.equalsIgnoreCase("type"))
  186. type = xmlSafeStringCharDup(text, false);
  187. else if (tag.equalsIgnoreCase("name"))
  188. name = xmlSafeStringCharDup(text, false);
  189. else if (tag.equalsIgnoreCase("label") || tag.equalsIgnoreCase("uri"))
  190. label = xmlSafeStringCharDup(text, false);
  191. else if (tag.equalsIgnoreCase("binary") || tag.equalsIgnoreCase("bundle") || tag.equalsIgnoreCase("filename"))
  192. binary = xmlSafeStringCharDup(text, false);
  193. else if (tag.equalsIgnoreCase("uniqueid"))
  194. uniqueId = text.getLargeIntValue();
  195. }
  196. }
  197. // ---------------------------------------------------------------
  198. // Data
  199. else if (tagName.equalsIgnoreCase("data"))
  200. {
  201. for (XmlElement* xmlData = elem->getFirstChildElement(); xmlData != nullptr; xmlData = xmlData->getNextElement())
  202. {
  203. const String& tag(xmlData->getTagName());
  204. const String text(xmlData->getAllSubText().trim());
  205. // -------------------------------------------------------
  206. // Internal Data
  207. if (tag.equalsIgnoreCase("active"))
  208. {
  209. active = (text.equalsIgnoreCase("yes") || text.equalsIgnoreCase("true"));
  210. }
  211. else if (tag.equalsIgnoreCase("drywet"))
  212. {
  213. dryWet = carla_fixValue(0.0f, 1.0f, text.getFloatValue());
  214. }
  215. else if (tag.equalsIgnoreCase("volume"))
  216. {
  217. volume = carla_fixValue(0.0f, 1.27f, text.getFloatValue());
  218. }
  219. else if (tag.equalsIgnoreCase("balanceleft") || tag.equalsIgnoreCase("balance-left"))
  220. {
  221. balanceLeft = carla_fixValue(-1.0f, 1.0f, text.getFloatValue());
  222. }
  223. else if (tag.equalsIgnoreCase("balanceright") || tag.equalsIgnoreCase("balance-right"))
  224. {
  225. balanceRight = carla_fixValue(-1.0f, 1.0f, text.getFloatValue());
  226. }
  227. else if (tag.equalsIgnoreCase("panning"))
  228. {
  229. panning = carla_fixValue(-1.0f, 1.0f, text.getFloatValue());
  230. }
  231. else if (tag.equalsIgnoreCase("controlchannel") || tag.equalsIgnoreCase("control-channel"))
  232. {
  233. const int value(text.getIntValue());
  234. if (value >= 1 && value <= MAX_MIDI_CHANNELS)
  235. ctrlChannel = static_cast<int8_t>(value-1);
  236. }
  237. // -------------------------------------------------------
  238. // Program (current)
  239. else if (tag.equalsIgnoreCase("currentprogramindex") || tag.equalsIgnoreCase("current-program-index"))
  240. {
  241. const int value(text.getIntValue());
  242. if (value >= 1)
  243. currentProgramIndex = value-1;
  244. }
  245. else if (tag.equalsIgnoreCase("currentprogramname") || tag.equalsIgnoreCase("current-program-name"))
  246. {
  247. currentProgramName = xmlSafeStringCharDup(text, false);
  248. }
  249. // -------------------------------------------------------
  250. // Midi Program (current)
  251. else if (tag.equalsIgnoreCase("currentmidibank") || tag.equalsIgnoreCase("current-midi-bank"))
  252. {
  253. const int value(text.getIntValue());
  254. if (value >= 1)
  255. currentMidiBank = value-1;
  256. }
  257. else if (tag.equalsIgnoreCase("currentmidiprogram") || tag.equalsIgnoreCase("current-midi-program"))
  258. {
  259. const int value(text.getIntValue());
  260. if (value >= 1)
  261. currentMidiProgram = value-1;
  262. }
  263. // -------------------------------------------------------
  264. // Parameters
  265. else if (tag.equalsIgnoreCase("parameter"))
  266. {
  267. StateParameter* const stateParameter(new StateParameter());
  268. for (XmlElement* xmlSubData = xmlData->getFirstChildElement(); xmlSubData != nullptr; xmlSubData = xmlSubData->getNextElement())
  269. {
  270. const String& pTag(xmlSubData->getTagName());
  271. const String pText(xmlSubData->getAllSubText().trim());
  272. if (pTag.equalsIgnoreCase("index"))
  273. {
  274. const int index(pText.getIntValue());
  275. if (index >= 0)
  276. stateParameter->index = index;
  277. }
  278. else if (pTag.equalsIgnoreCase("name"))
  279. {
  280. stateParameter->name = xmlSafeStringCharDup(pText, false);
  281. }
  282. else if (pTag.equalsIgnoreCase("symbol"))
  283. {
  284. stateParameter->symbol = xmlSafeStringCharDup(pText, false);
  285. }
  286. else if (pTag.equalsIgnoreCase("value"))
  287. {
  288. stateParameter->value = pText.getFloatValue();
  289. }
  290. else if (pTag.equalsIgnoreCase("midichannel") || pTag.equalsIgnoreCase("midi-channel"))
  291. {
  292. const int channel(pText.getIntValue());
  293. if (channel >= 1 && channel <= MAX_MIDI_CHANNELS)
  294. stateParameter->midiChannel = static_cast<uint8_t>(channel-1);
  295. }
  296. else if (pTag.equalsIgnoreCase("midicc") || pTag.equalsIgnoreCase("midi-cc"))
  297. {
  298. const int cc(pText.getIntValue());
  299. if (cc >= 1 && cc < 0x5F)
  300. stateParameter->midiCC = static_cast<int16_t>(cc);
  301. }
  302. }
  303. parameters.append(stateParameter);
  304. }
  305. // -------------------------------------------------------
  306. // Custom Data
  307. else if (tag.equalsIgnoreCase("customdata") || tag.equalsIgnoreCase("custom-data"))
  308. {
  309. StateCustomData* const stateCustomData(new StateCustomData());
  310. for (XmlElement* xmlSubData = xmlData->getFirstChildElement(); xmlSubData != nullptr; xmlSubData = xmlSubData->getNextElement())
  311. {
  312. const String& cTag(xmlSubData->getTagName());
  313. const String cText(xmlSubData->getAllSubText().trim());
  314. if (cTag.equalsIgnoreCase("type"))
  315. stateCustomData->type = xmlSafeStringCharDup(cText, false);
  316. else if (cTag.equalsIgnoreCase("key"))
  317. stateCustomData->key = xmlSafeStringCharDup(cText, false);
  318. else if (cTag.equalsIgnoreCase("value"))
  319. stateCustomData->value = xmlSafeStringCharDup(cText, false);
  320. }
  321. customData.append(stateCustomData);
  322. }
  323. // -------------------------------------------------------
  324. // Chunk
  325. else if (tag.equalsIgnoreCase("chunk"))
  326. {
  327. chunk = xmlSafeStringCharDup(text, false);
  328. }
  329. }
  330. }
  331. }
  332. return true;
  333. }
  334. // -----------------------------------------------------------------------
  335. // fillXmlStringFromStateSave
  336. String StateSave::toString() const
  337. {
  338. String content;
  339. {
  340. String infoXml(" <Info>\n");
  341. infoXml << " <Type>" << String(type != nullptr ? type : "") << "</Type>\n";
  342. infoXml << " <Name>" << xmlSafeString(name, true) << "</Name>\n";
  343. switch (getPluginTypeFromString(type))
  344. {
  345. case PLUGIN_NONE:
  346. break;
  347. case PLUGIN_INTERNAL:
  348. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  349. break;
  350. case PLUGIN_LADSPA:
  351. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  352. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  353. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  354. break;
  355. case PLUGIN_DSSI:
  356. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  357. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  358. break;
  359. case PLUGIN_LV2:
  360. infoXml << " <Bundle>" << xmlSafeString(binary, true) << "</Bundle>\n";
  361. infoXml << " <URI>" << xmlSafeString(label, true) << "</URI>\n";
  362. break;
  363. case PLUGIN_VST:
  364. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  365. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  366. break;
  367. case PLUGIN_VST3:
  368. // TODO?
  369. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  370. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  371. break;
  372. case PLUGIN_AU:
  373. // TODO?
  374. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  375. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  376. break;
  377. case PLUGIN_GIG:
  378. case PLUGIN_SF2:
  379. infoXml << " <Filename>" << xmlSafeString(binary, true) << "</Filename>\n";
  380. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  381. break;
  382. case PLUGIN_SFZ:
  383. infoXml << " <Filename>" << xmlSafeString(binary, true) << "</Filename>\n";
  384. break;
  385. }
  386. infoXml << " </Info>\n\n";
  387. content << infoXml;
  388. }
  389. content << " <Data>\n";
  390. {
  391. String dataXml;
  392. dataXml << " <Active>" << (active ? "Yes" : "No") << "</Active>\n";
  393. if (dryWet != 1.0f)
  394. dataXml << " <DryWet>" << String(dryWet, 7) << "</DryWet>\n";
  395. if (volume != 1.0f)
  396. dataXml << " <Volume>" << String(volume, 7) << "</Volume>\n";
  397. if (balanceLeft != -1.0f)
  398. dataXml << " <Balance-Left>" << String(balanceLeft, 7) << "</Balance-Left>\n";
  399. if (balanceRight != 1.0f)
  400. dataXml << " <Balance-Right>" << String(balanceRight, 7) << "</Balance-Right>\n";
  401. if (panning != 0.0f)
  402. dataXml << " <Panning>" << String(panning, 7) << "</Panning>\n";
  403. if (ctrlChannel < 0)
  404. dataXml << " <ControlChannel>N</ControlChannel>\n";
  405. else
  406. dataXml << " <ControlChannel>" << int(ctrlChannel+1) << "</ControlChannel>\n";
  407. content << dataXml;
  408. }
  409. for (StateParameterItenerator it = parameters.begin(); it.valid(); it.next())
  410. {
  411. StateParameter* const stateParameter(it.getValue());
  412. String parameterXml("\n"" <Parameter>\n");
  413. parameterXml << " <Index>" << String(stateParameter->index) << "</Index>\n";
  414. parameterXml << " <Name>" << xmlSafeString(stateParameter->name, true) << "</Name>\n";
  415. if (stateParameter->symbol != nullptr && stateParameter->symbol[0] != '\0')
  416. parameterXml << " <Symbol>" << xmlSafeString(stateParameter->symbol, true) << "</Symbol>\n";
  417. if (stateParameter->isInput)
  418. parameterXml << " <Value>" << String(stateParameter->value, 15) << "</Value>\n";
  419. if (stateParameter->midiCC > 0)
  420. {
  421. parameterXml << " <MidiCC>" << stateParameter->midiCC << "</MidiCC>\n";
  422. parameterXml << " <MidiChannel>" << stateParameter->midiChannel+1 << "</MidiChannel>\n";
  423. }
  424. parameterXml << " </Parameter>\n";
  425. content << parameterXml;
  426. }
  427. if (currentProgramIndex >= 0 && currentProgramName != nullptr && currentProgramName[0] != '\0')
  428. {
  429. // ignore 'default' program
  430. if (currentProgramIndex > 0 || ! String(currentProgramName).equalsIgnoreCase("default"))
  431. {
  432. String programXml("\n");
  433. programXml << " <CurrentProgramIndex>" << currentProgramIndex+1 << "</CurrentProgramIndex>\n";
  434. programXml << " <CurrentProgramName>" << xmlSafeString(currentProgramName, true) << "</CurrentProgramName>\n";
  435. content << programXml;
  436. }
  437. }
  438. if (currentMidiBank >= 0 && currentMidiProgram >= 0)
  439. {
  440. String midiProgramXml("\n");
  441. midiProgramXml << " <CurrentMidiBank>" << currentMidiBank+1 << "</CurrentMidiBank>\n";
  442. midiProgramXml << " <CurrentMidiProgram>" << currentMidiProgram+1 << "</CurrentMidiProgram>\n";
  443. content << midiProgramXml;
  444. }
  445. for (StateCustomDataItenerator it = customData.begin(); it.valid(); it.next())
  446. {
  447. StateCustomData* const stateCustomData(it.getValue());
  448. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->type != nullptr && stateCustomData->type[0] != '\0');
  449. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->key != nullptr && stateCustomData->key[0] != '\0');
  450. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->value != nullptr);
  451. String customDataXml("\n"" <CustomData>\n");
  452. customDataXml << " <Type>" << xmlSafeString(stateCustomData->type, true) << "</Type>\n";
  453. customDataXml << " <Key>" << xmlSafeString(stateCustomData->key, true) << "</Key>\n";
  454. if (std::strcmp(stateCustomData->type, CUSTOM_DATA_TYPE_CHUNK) == 0 || std::strlen(stateCustomData->value) >= 128)
  455. {
  456. customDataXml << " <Value>\n";
  457. customDataXml << xmlSafeString(stateCustomData->value, true);
  458. customDataXml << "\n </Value>\n";
  459. }
  460. else
  461. {
  462. customDataXml << " <Value>";
  463. customDataXml << xmlSafeString(stateCustomData->value, true);
  464. customDataXml << "</Value>\n";
  465. }
  466. customDataXml << " </CustomData>\n";
  467. content << customDataXml;
  468. }
  469. if (chunk != nullptr && chunk[0] != '\0')
  470. {
  471. String chunkXml("\n"" <Chunk>\n");
  472. chunkXml << chunk << "\n </Chunk>\n";
  473. content << chunkXml;
  474. }
  475. content << " </Data>\n";
  476. return content;
  477. }
  478. // -----------------------------------------------------------------------
  479. CARLA_BACKEND_END_NAMESPACE