/* * Carla JSFX utils * Copyright (C) 2021 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ #ifndef CARLA_JSFX_UTILS_HPP_INCLUDED #define CARLA_JSFX_UTILS_HPP_INCLUDED #include "CarlaDefines.h" #include "CarlaBackend.h" #include "CarlaUtils.hpp" #include "CarlaString.hpp" #include "CarlaBase64Utils.hpp" #include "CarlaJuceUtils.hpp" #include "water/files/File.h" #include "water/files/FileInputStream.h" #include "water/xml/XmlElement.h" #include "water/xml/XmlDocument.h" #include "water/streams/MemoryInputStream.h" #include "water/streams/MemoryOutputStream.h" #include "ysfx.h" #include class CarlaJsfxLogging { public: static void logAll(intptr_t, ysfx_log_level level, const char *message) { switch (level) { case ysfx_log_info: carla_stdout("%s: %s", ysfx_log_level_string(level), message); break; case ysfx_log_warning: case ysfx_log_error: carla_stderr("%s: %s", ysfx_log_level_string(level), message); break; } }; static void logErrorsOnly(intptr_t, ysfx_log_level level, const char *message) { switch (level) { case ysfx_log_info: break; case ysfx_log_warning: case ysfx_log_error: carla_stderr("%s: %s", ysfx_log_level_string(level), message); break; } }; }; // ------------------------------------------------------------------------------------------------------------------- class CarlaJsfxCategories { public: static CarlaBackend::PluginCategory getFromEffect(ysfx_t* effect) { CarlaBackend::PluginCategory category = CarlaBackend::PLUGIN_CATEGORY_OTHER; water::Array tags; int tagCount = (int)ysfx_get_tags(effect, nullptr, 0); tags.resize(tagCount); ysfx_get_tags(effect, tags.getRawDataPointer(), (uint32_t)tagCount); for (int i = 0; i < tagCount && category == CarlaBackend::PLUGIN_CATEGORY_OTHER; ++i) { water::CharPointer_UTF8 tag(tags[i]); CarlaBackend::PluginCategory current = getFromTag(tag); if (current != CarlaBackend::PLUGIN_CATEGORY_NONE) category = current; } return category; } static CarlaBackend::PluginCategory getFromTag(const water::CharPointer_UTF8 tag) { if (tag.compareIgnoreCase(water::CharPointer_UTF8("synthesis")) == 0) return CarlaBackend::PLUGIN_CATEGORY_SYNTH; if (tag.compareIgnoreCase(water::CharPointer_UTF8("delay")) == 0) return CarlaBackend::PLUGIN_CATEGORY_DELAY; if (tag.compareIgnoreCase(water::CharPointer_UTF8("equalizer")) == 0) return CarlaBackend::PLUGIN_CATEGORY_EQ; if (tag.compareIgnoreCase(water::CharPointer_UTF8("filter")) == 0) return CarlaBackend::PLUGIN_CATEGORY_FILTER; if (tag.compareIgnoreCase(water::CharPointer_UTF8("distortion")) == 0) return CarlaBackend::PLUGIN_CATEGORY_DISTORTION; if (tag.compareIgnoreCase(water::CharPointer_UTF8("dynamics")) == 0) return CarlaBackend::PLUGIN_CATEGORY_DYNAMICS; if (tag.compareIgnoreCase(water::CharPointer_UTF8("modulation")) == 0) return CarlaBackend::PLUGIN_CATEGORY_MODULATOR; if (tag.compareIgnoreCase(water::CharPointer_UTF8("utility")) == 0) return CarlaBackend::PLUGIN_CATEGORY_UTILITY; return CarlaBackend::PLUGIN_CATEGORY_NONE; } }; // ------------------------------------------------------------------------------------------------------------------- class CarlaJsfxState { public: static water::String convertToString(ysfx_state_t &state) { water::XmlElement root("JSFXState"); for (uint32_t i = 0; i < state.slider_count; ++i) { water::XmlElement *slider = new water::XmlElement("Slider"); slider->setAttribute("index", (int32_t)state.sliders[i].index); slider->setAttribute("value", state.sliders[i].value); root.addChildElement(slider); } { const CarlaString base64 = CarlaString::asBase64(state.data, state.data_size); water::XmlElement *var = new water::XmlElement("Serialization"); var->addTextElement(base64.buffer()); root.addChildElement(var); } water::MemoryOutputStream stream; root.writeToStream(stream, water::StringRef(), true); return stream.toUTF8(); } static ysfx_state_u convertFromString(const water::String& string) { std::unique_ptr root(water::XmlDocument::parse(string)); CARLA_SAFE_ASSERT_RETURN(root != nullptr, nullptr); CARLA_SAFE_ASSERT_RETURN(root->getTagName() == "JSFXState", nullptr); std::vector sliders; std::vector serialization; sliders.reserve(ysfx_max_sliders); int numChildren = root->getNumChildElements(); for (int i = 0; i < numChildren; ++i) { water::XmlElement* child = root->getChildElement(i); CARLA_SAFE_ASSERT_CONTINUE(child != nullptr); if (child->getTagName() == "Slider") { CARLA_SAFE_ASSERT_CONTINUE(child->hasAttribute("index")); CARLA_SAFE_ASSERT_CONTINUE(child->hasAttribute("value")); ysfx_state_slider_t item; int32_t index = child->getIntAttribute("index"); CARLA_SAFE_ASSERT_CONTINUE(index >= 0 && index < ysfx_max_sliders); item.index = (uint32_t)index; item.value = child->getDoubleAttribute("value"); sliders.push_back(item); } else if (child->getTagName() == "Serialization") { serialization = carla_getChunkFromBase64String(child->getAllSubText().toRawUTF8()); } else { CARLA_SAFE_ASSERT_CONTINUE(false); } } ysfx_state_t state; state.sliders = sliders.data(); state.slider_count = (uint32_t)sliders.size(); state.data = serialization.data(); state.data_size = (uint32_t)serialization.size(); return ysfx_state_u(ysfx_state_dup(&state)); } }; // ------------------------------------------------------------------------------------------------------------------- class CarlaJsfxUnit { public: CarlaJsfxUnit() = default; CarlaJsfxUnit(const water::File& rootPath, const water::File& filePath) : fRootPath(rootPath), fFileId(filePath.getRelativePathFrom(rootPath)) { #ifdef CARLA_OS_WIN fFileId.replaceCharacter('\\', '/'); #endif } explicit operator bool() const { return fFileId.isNotEmpty(); } const water::File& getRootPath() const { return fRootPath; } const water::String& getFileId() const { return fFileId; } water::File getFilePath() const { return fRootPath.getChildFile(fFileId); } private: water::File fRootPath; water::String fFileId; }; // ------------------------------------------------------------------------------------------------------------------- #endif // CARLA_JSFX_UTILS_HPP_INCLUDED