gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit f8843247b181d93f77095cbc994fda11feb7ccd7
parent 32df65f65e3b9f57fffc7d94d39e19cf2c290704
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sat, 28 Sep 2024 02:13:05 +0200

add helper class to export preset banks as lv2 presets

Diffstat:
Msource/synthLib/CMakeLists.txt | 1+
Asource/synthLib/lv2PresetExport.cpp | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/synthLib/lv2PresetExport.h | 29+++++++++++++++++++++++++++++
3 files changed, 219 insertions(+), 0 deletions(-)

diff --git a/source/synthLib/CMakeLists.txt b/source/synthLib/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCES deviceException.cpp deviceException.h deviceTypes.h dspMemoryPatch.cpp dspMemoryPatch.h + lv2PresetExport.cpp lv2PresetExport.h midiBufferParser.cpp midiBufferParser.h midiClock.cpp midiClock.h midiToSysex.cpp midiToSysex.h diff --git a/source/synthLib/lv2PresetExport.cpp b/source/synthLib/lv2PresetExport.cpp @@ -0,0 +1,189 @@ +#include "lv2PresetExport.h" + +#include <fstream> +#include <map> + +#include "os.h" + +namespace synthLib +{ + namespace + { + const std::string g_keyBankName = "%BANKNAME%"; + const std::string g_keyBankId = "%BANKID%"; + const std::string g_keyPluginId = "%PLUGINID%"; + const std::string g_keyPresetFilename = "%PRESETFILENAME%"; + const std::string g_keyPresetName = "%PRESETNAME%"; + const std::string g_keyPresetData = "%PRESETDATA%"; + + const std::string g_manifestHeader = + "@prefix atom: <http://lv2plug.in/ns/ext/atom#> .\n" + "@prefix lv2: <http://lv2plug.in/ns/lv2core#> .\n" + "@prefix pset: <http://lv2plug.in/ns/ext/presets#> .\n" + "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" + "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" + "@prefix state: <http://lv2plug.in/ns/ext/state#> .\n" + "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" + "\n"; + + const std::string g_manifestBankEntry = + "<" + g_keyBankId + ">\n" + " lv2:appliesTo <" + g_keyPluginId + "> ;\n" + " a pset:Bank ;\n" + " rdfs:label \"" + g_keyBankName + "\" .\n" + "\n"; + + const std::string g_manifestPresetEntry = + "<" + g_keyPresetFilename + ">\n" + " lv2:appliesTo <" + g_keyPluginId + "> ;\n" + " a pset:Preset ;\n" + " pset:bank <" + g_keyBankId + "> ;\n" + " rdfs:seeAlso <" + g_keyPresetFilename + "> .\n" + "\n"; + + const std::string g_presetFile = + "@prefix atom: <http://lv2plug.in/ns/ext/atom#> .\n" + "@prefix lv2: <http://lv2plug.in/ns/lv2core#> .\n" + "@prefix pset: <http://lv2plug.in/ns/ext/presets#> .\n" + "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" + "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" + "@prefix state: <http://lv2plug.in/ns/ext/state#> .\n" + "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" + "\n" + "<>\n" + " a pset:Preset ;\n" + " lv2:appliesTo <" + g_keyPluginId + "> ;\n" + " rdfs:label \"" + g_keyPresetName + "\" ;\n" + " state:state [\n" + " <" + g_keyPluginId + ":StateString> \"" + g_keyPresetData + "\"\n" + " ] .\n" + "\n"; + + std::string toFilename(std::string _name) + { + for (char& c : _name) + { + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + continue; + c = '_'; + } + return _name; + } + + std::string replaceVariables(std::string _string, const std::map<std::string, std::string>& _variables) + { + for (const auto& [key, value] : _variables) + { + while(true) + { + const auto pos = _string.find(key); + if(pos == std::string::npos) + break; + _string.erase(pos, key.size()); + _string.insert(pos, value); + } + } + return _string; + } + + int getBitRange (const std::vector<uint8_t>& _data, size_t bitRangeStart, size_t numBits) + { + int res = 0; + + auto byte = bitRangeStart >> 3; + auto offsetInByte = bitRangeStart & 7; + size_t bitsSoFar = 0; + + while (numBits > 0 && (size_t) byte < _data.size()) + { + const auto bitsThisTime = std::min(numBits, 8 - offsetInByte); + const int mask = (0xff >> (8 - bitsThisTime)) << offsetInByte; + + res |= (((_data[byte] & mask) >> offsetInByte) << bitsSoFar); + + bitsSoFar += bitsThisTime; + numBits -= bitsThisTime; + ++byte; + offsetInByte = 0; + } + + return res; + } + + constexpr char base64EncodingTable[] = ".ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+"; + + std::string dataToJuceEncodedString(const std::vector<uint8_t>& _data) + { + const auto numChars = ((_data.size() << 3) + 5) / 6; + + // store the length, followed by a '.', and then the data + std::string destString = std::to_string(numChars); + destString.reserve(_data.size()); + + const auto initialLen = destString.size(); + destString.reserve(initialLen + 2 + numChars); + + destString.push_back('.'); + + for (size_t i = 0; i < numChars; ++i) + destString.push_back(base64EncodingTable[getBitRange (_data, i * 6, 6)]); + + return destString; + } + } + + bool Lv2PresetExport::exportPresets(const std::string& _outputPath, const std::string& _pluginId, const std::vector<Bank>& _banks) + { + const auto path = validatePath(_outputPath); + createDirectory(path); + + for (const auto& bank : _banks) + { + if(!exportPresets(path + toFilename(bank.name) + ".lv2/", _pluginId, bank)) + return false; + } + return true; + } + + bool Lv2PresetExport::exportPresets(const std::string& _outputPath, const std::string& _pluginId, const Bank& _bank) + { + const auto path = validatePath(_outputPath); + createDirectory(path); + std::ofstream manifest(path + "manifest.ttl"); + if(!manifest.is_open()) + return false; + + std::map<std::string, std::string> bankVars; + + bankVars.insert({g_keyPluginId, _pluginId}); + bankVars.insert({g_keyBankId, toFilename(_bank.name)}); + bankVars.insert({g_keyBankName, _bank.name}); + + manifest << replaceVariables(g_manifestHeader, bankVars); + manifest << replaceVariables(g_manifestBankEntry, bankVars); + + for (const auto& preset : _bank.presets) + { + const auto presetFilename = toFilename(preset.name) + ".ttl"; + + auto presetVars = bankVars; + + presetVars.insert({g_keyPresetName, preset.name}); + presetVars.insert({g_keyPresetFilename, presetFilename}); + presetVars.insert({g_keyPresetData, dataToJuceEncodedString(preset.data)}); + + std::ofstream presetFile(path + presetFilename); + + if(!presetFile.is_open()) + return false; + + manifest << replaceVariables(g_manifestPresetEntry, presetVars); + + presetFile << replaceVariables(g_presetFile, presetVars); + + presetFile.close(); + } + + return true; + } +} diff --git a/source/synthLib/lv2PresetExport.h b/source/synthLib/lv2PresetExport.h @@ -0,0 +1,29 @@ +#pragma once + +#include <cstdint> +#include <string> +#include <vector> + +namespace synthLib +{ + class Lv2PresetExport + { + public: + using PresetData = std::vector<uint8_t>; + + struct Preset + { + std::string name; + PresetData data; + }; + + struct Bank + { + std::string name; + std::vector<Preset> presets; + }; + + static bool exportPresets(const std::string& _outputPath, const std::string& _pluginId, const std::vector<Bank>& _banks); + static bool exportPresets(const std::string& _outputPath, const std::string& _pluginId, const Bank& _bank); + }; +}