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.

312 lines
9.4KB

  1. /*
  2. * Carla JSFX utils
  3. * Copyright (C) 2021 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. #ifndef CARLA_JSFX_UTILS_HPP_INCLUDED
  18. #define CARLA_JSFX_UTILS_HPP_INCLUDED
  19. #include "CarlaDefines.h"
  20. #include "CarlaBackend.h"
  21. #include "CarlaUtils.hpp"
  22. #include "CarlaString.hpp"
  23. #include "CarlaBase64Utils.hpp"
  24. #include "CarlaJuceUtils.hpp"
  25. #include "water/files/File.h"
  26. #include "water/files/FileInputStream.h"
  27. #include "water/xml/XmlElement.h"
  28. #include "water/xml/XmlDocument.h"
  29. #include "water/streams/MemoryInputStream.h"
  30. #include "water/streams/MemoryOutputStream.h"
  31. #pragma GCC diagnostic push
  32. #pragma GCC diagnostic ignored "-Wunused-parameter"
  33. #pragma GCC diagnostic ignored "-Wignored-attributes"
  34. #include "jsusfx.h"
  35. #include "jsusfx_file.h"
  36. #include "jsusfx_gfx.h"
  37. #include "jsusfx_serialize.h"
  38. #pragma GCC diagnostic pop
  39. #include <memory>
  40. class CarlaJsusFx : public JsusFx
  41. {
  42. public:
  43. explicit CarlaJsusFx(JsusFxPathLibrary &pathLibrary)
  44. : JsusFx(pathLibrary)
  45. {
  46. }
  47. void setQuiet(bool quiet)
  48. {
  49. fQuiet = quiet;
  50. }
  51. void displayMsg(const char* fmt, ...) override
  52. {
  53. if (!fQuiet)
  54. {
  55. char msgBuf[256];
  56. ::va_list args;
  57. ::va_start(args, fmt);
  58. std::vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
  59. msgBuf[255] = 0;
  60. ::va_end(args);
  61. carla_stdout("%s", msgBuf);
  62. }
  63. }
  64. void displayError(const char* fmt, ...) override
  65. {
  66. if (!fQuiet)
  67. {
  68. char msgBuf[256];
  69. ::va_list args;
  70. ::va_start(args, fmt);
  71. std::vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
  72. msgBuf[255] = 0;
  73. ::va_end(args);
  74. carla_stderr("%s", msgBuf);
  75. }
  76. }
  77. static bool parsePseudoTags(const water::File& jsfxFile, water::String& author, CarlaBackend::PluginCategory& category)
  78. {
  79. namespace CB = CarlaBackend;
  80. water::FileInputStream stream(jsfxFile);
  81. if (stream.failedToOpen())
  82. return false;
  83. author.clear();
  84. category = CB::PLUGIN_CATEGORY_NONE;
  85. while ((author.isEmpty() || category == CB::PLUGIN_CATEGORY_NONE) && !stream.isExhausted())
  86. {
  87. const water::String line = stream.readNextLine().trim();
  88. const water::StringRef authorPrefix = "//author:";
  89. const water::StringRef tagsPrefix = "//tags:";
  90. if (author.isEmpty() && line.startsWith(authorPrefix))
  91. {
  92. author = line.substring(authorPrefix.length()).trim();
  93. }
  94. else if (category == CB::PLUGIN_CATEGORY_NONE && line.startsWith(tagsPrefix))
  95. {
  96. water::StringArray tags;
  97. tags.addTokens(line.substring(tagsPrefix.length()), " ", "");
  98. for (int i = 0; i < tags.size() && category == CB::PLUGIN_CATEGORY_NONE; ++i)
  99. {
  100. CB::PluginCategory currentCategory = getCategoryFromTag(tags[i]);
  101. if (currentCategory != CB::PLUGIN_CATEGORY_NONE)
  102. category = currentCategory;
  103. }
  104. }
  105. }
  106. if (category == CB::PLUGIN_CATEGORY_NONE)
  107. category = CB::PLUGIN_CATEGORY_OTHER;
  108. return true;
  109. }
  110. static CarlaBackend::PluginCategory getCategoryFromTag(const water::String& tag)
  111. {
  112. if (tag.equalsIgnoreCase("synthesis"))
  113. return CarlaBackend::PLUGIN_CATEGORY_SYNTH;
  114. if (tag.equalsIgnoreCase("delay"))
  115. return CarlaBackend::PLUGIN_CATEGORY_DELAY;
  116. if (tag.equalsIgnoreCase("equalizer"))
  117. return CarlaBackend::PLUGIN_CATEGORY_EQ;
  118. if (tag.equalsIgnoreCase("filter"))
  119. return CarlaBackend::PLUGIN_CATEGORY_FILTER;
  120. if (tag.equalsIgnoreCase("distortion"))
  121. return CarlaBackend::PLUGIN_CATEGORY_DISTORTION;
  122. if (tag.equalsIgnoreCase("dynamics"))
  123. return CarlaBackend::PLUGIN_CATEGORY_DYNAMICS;
  124. if (tag.equalsIgnoreCase("modulation"))
  125. return CarlaBackend::PLUGIN_CATEGORY_MODULATOR;
  126. if (tag.equalsIgnoreCase("utility"))
  127. return CarlaBackend::PLUGIN_CATEGORY_UTILITY;
  128. return CarlaBackend::PLUGIN_CATEGORY_NONE;
  129. }
  130. private:
  131. bool fQuiet = false;
  132. };
  133. // -------------------------------------------------------------------------------------------------------------------
  134. class CarlaJsfxUnit
  135. {
  136. public:
  137. CarlaJsfxUnit() = default;
  138. CarlaJsfxUnit(const water::File& rootPath, const water::File& filePath)
  139. : fRootPath(rootPath), fFileId(filePath.getRelativePathFrom(rootPath))
  140. {
  141. #ifdef CARLA_OS_WIN
  142. fFileId.replaceCharacter('\\', '/');
  143. #endif
  144. }
  145. explicit operator bool() const
  146. {
  147. return fFileId.isNotEmpty();
  148. }
  149. const water::File& getRootPath() const
  150. {
  151. return fRootPath;
  152. }
  153. const water::String& getFileId() const
  154. {
  155. return fFileId;
  156. }
  157. water::File getFilePath() const
  158. {
  159. return fRootPath.getChildFile(fFileId);
  160. }
  161. private:
  162. water::File fRootPath;
  163. water::String fFileId;
  164. };
  165. // -------------------------------------------------------------------------------------------------------------------
  166. class CarlaJsusFxPathLibrary : public JsusFxPathLibrary_Basic
  167. {
  168. public:
  169. explicit CarlaJsusFxPathLibrary(const CarlaJsfxUnit &unit)
  170. : JsusFxPathLibrary_Basic(unit.getRootPath().getFullPathName().toRawUTF8())
  171. {
  172. }
  173. };
  174. // -------------------------------------------------------------------------------------------------------------------
  175. class CarlaJsusFxFileAPI : public JsusFxFileAPI_Basic
  176. {
  177. public:
  178. };
  179. // -------------------------------------------------------------------------------------------------------------------
  180. class CarlaJsusFxSerializer : public JsusFxSerializer_Basic
  181. {
  182. public:
  183. explicit CarlaJsusFxSerializer(JsusFxSerializationData& data)
  184. : JsusFxSerializer_Basic(data)
  185. {
  186. }
  187. static water::String convertDataToString(const JsusFxSerializationData& data)
  188. {
  189. water::XmlElement root("JSFXState");
  190. std::size_t numSliders = data.sliders.size();
  191. for (std::size_t i = 0; i < numSliders; ++i)
  192. {
  193. water::XmlElement *slider = new water::XmlElement("Slider");
  194. slider->setAttribute("index", data.sliders[i].index);
  195. slider->setAttribute("value", data.sliders[i].value);
  196. root.addChildElement(slider);
  197. }
  198. std::size_t numVars = data.vars.size();
  199. if (numVars > 0)
  200. {
  201. water::MemoryOutputStream blob;
  202. for (std::size_t i = 0; i < numVars; ++i)
  203. {
  204. blob.writeFloat(data.vars[i]);
  205. }
  206. const CarlaString base64 = CarlaString::asBase64(blob.getData(), blob.getDataSize());
  207. water::XmlElement *var = new water::XmlElement("Serialization");
  208. var->addTextElement(base64.buffer());
  209. root.addChildElement(var);
  210. }
  211. water::MemoryOutputStream stream;
  212. root.writeToStream(stream, water::StringRef(), true);
  213. return stream.toUTF8();
  214. }
  215. static bool convertStringToData(const water::String& string, JsusFxSerializationData& data)
  216. {
  217. std::unique_ptr<water::XmlElement> root(water::XmlDocument::parse(string));
  218. CARLA_SAFE_ASSERT_RETURN(root != nullptr, false);
  219. CARLA_SAFE_ASSERT_RETURN(root->getTagName() == "JSFXState", false);
  220. data = JsusFxSerializationData();
  221. int numChildren = root->getNumChildElements();
  222. for (int i = 0; i < numChildren; ++i)
  223. {
  224. water::XmlElement* child = root->getChildElement(i);
  225. CARLA_SAFE_ASSERT_CONTINUE(child != nullptr);
  226. if (child->getTagName() == "Slider")
  227. {
  228. CARLA_SAFE_ASSERT_CONTINUE(child->hasAttribute("index"));
  229. CARLA_SAFE_ASSERT_CONTINUE(child->hasAttribute("value"));
  230. JsusFxSerializationData::Slider slider;
  231. slider.index = child->getIntAttribute("index");
  232. slider.value = child->getDoubleAttribute("value");
  233. data.sliders.push_back(slider);
  234. }
  235. else if (child->getTagName() == "Serialization")
  236. {
  237. std::vector<uint8_t> chunk = carla_getChunkFromBase64String(child->getAllSubText().toRawUTF8());
  238. water::MemoryInputStream blob(chunk.data(), chunk.size(), false);
  239. size_t numVars = chunk.size() / sizeof(float);
  240. data.vars.resize(numVars);
  241. for (std::size_t i = 0; i < numVars; ++i)
  242. {
  243. data.vars[i] = blob.readFloat();
  244. }
  245. }
  246. else
  247. {
  248. CARLA_SAFE_ASSERT_CONTINUE(false);
  249. }
  250. }
  251. return true;
  252. }
  253. };
  254. // -------------------------------------------------------------------------------------------------------------------
  255. #endif // CARLA_JSFX_UTILS_HPP_INCLUDED