From 8b8d7801c662cc86ad1bf29b33ab6bebbaabe39d Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Wed, 4 Dec 2024 12:18:32 -0500 Subject: [PATCH 1/2] Add metadata into the wt with lua script 1. wt format gets metadata chunk and flag which we can read/write 2. wav file gets `wtmd` riff chunk which we can read/wrote 3. load_wt variants save and restore metadata 4. metadata is a xml blob optionally populated 5. if there's a script, its populated with the script 6. script clear on watetable change Closes #7897 --- src/common/SurgePatch.cpp | 137 ++--------- src/common/SurgeStorage.cpp | 227 +++++++++++++++++- src/common/SurgeStorage.h | 11 +- src/common/WAVFileSupport.cpp | 20 +- src/surge-testrunner/UnitTestsIO.cpp | 8 +- .../gui/widgets/OscillatorWaveformDisplay.cpp | 10 +- 6 files changed, 284 insertions(+), 129 deletions(-) diff --git a/src/common/SurgePatch.cpp b/src/common/SurgePatch.cpp index 40e7c21baa2..68a40d8aa2b 100644 --- a/src/common/SurgePatch.cpp +++ b/src/common/SurgePatch.cpp @@ -1110,100 +1110,6 @@ void SurgePatch::update_controls( } } -// BASE 64 SUPPORT, THANKS TO: -// https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp -static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - -static inline bool is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } - -std::string base64_encode(unsigned char const *bytes_to_encode, unsigned int in_len) -{ - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; - - while (in_len--) - { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) - { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (i = 0; (i < 4); i++) - ret += base64_chars[char_array_4[i]]; - i = 0; - } - } - - if (i) - { - for (j = i; j < 3; j++) - char_array_3[j] = '\0'; - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - - for (j = 0; (j < i + 1); j++) - ret += base64_chars[char_array_4[j]]; - - while ((i++ < 3)) - ret += '='; - } - - return ret; -} - -std::string base64_decode(std::string const &encoded_string) -{ - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - - while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) - { - char_array_4[i++] = encoded_string[in_]; - in_++; - if (i == 4) - { - for (i = 0; i < 4; i++) - char_array_4[i] = base64_chars.find(char_array_4[i]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (i = 0; (i < 3); i++) - ret += char_array_3[i]; - i = 0; - } - } - - if (i) - { - for (j = 0; j < i; j++) - char_array_4[j] = base64_chars.find(char_array_4[j]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - - for (j = 0; (j < i - 1); j++) - ret += char_array_3[j]; - } - - return ret; -} - void SurgePatch::load_patch(const void *data, int datasize, bool preset) { using namespace sst::io; @@ -2344,7 +2250,7 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset) int wfi; scene[ssc].osc[sos].wavetable_formula = - base64_decode(lkid->Attribute("wavetable_formula")); + Surge::Storage::base64_decode(lkid->Attribute("wavetable_formula")); if (lkid->QueryIntAttribute("wavetable_formula_nframes", &wfi) == TIXML_SUCCESS) { @@ -2656,7 +2562,7 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset) if (pt && (td = pt->Attribute("v"))) { - auto tc = base64_decode(td); + auto tc = Surge::Storage::base64_decode(td); patchTuning.tuningStoredInPatch = true; patchTuning.scaleContents = tc; @@ -2664,7 +2570,7 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset) if (pt && (td = pt->Attribute("m"))) { - auto tc = base64_decode(td); + auto tc = Surge::Storage::base64_decode(td); patchTuning.tuningStoredInPatch = true; patchTuning.mappingContents = tc; @@ -3118,7 +3024,7 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset) if (p && (td = p->Attribute("v"))) { - auto tc = base64_decode(td); + auto tc = Surge::Storage::base64_decode(td); dawExtraState.scaleContents = tc; } @@ -3137,7 +3043,7 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset) if (p && (td = p->Attribute("v"))) { - auto tc = base64_decode(td); + auto tc = Surge::Storage::base64_decode(td); dawExtraState.mappingContents = tc; } @@ -3501,8 +3407,9 @@ unsigned int SurgePatch::save_xml(void **data) // allocates mem, must be freed b auto wtfo = scene[sc].osc[os].wavetable_formula; auto wtfol = wtfo.length(); - on.SetAttribute("wavetable_formula", - base64_encode((unsigned const char *)wtfo.c_str(), wtfol)); + on.SetAttribute( + "wavetable_formula", + Surge::Storage::base64_encode((unsigned const char *)wtfo.c_str(), wtfol)); on.SetAttribute("wavetable_formula_nframes", scene[sc].osc[os].wavetable_formula_nframes); on.SetAttribute("wavetable_formula_res_base", @@ -3663,13 +3570,14 @@ unsigned int SurgePatch::save_xml(void **data) // allocates mem, must be freed b if (patchTuning.tuningStoredInPatch) { TiXmlElement pt("patchTuning"); - pt.SetAttribute("v", base64_encode((unsigned const char *)patchTuning.scaleContents.c_str(), - patchTuning.scaleContents.size())); + pt.SetAttribute("v", Surge::Storage::base64_encode( + (unsigned const char *)patchTuning.scaleContents.c_str(), + patchTuning.scaleContents.size())); if (patchTuning.mappingContents.size() > 0) { - pt.SetAttribute( - "m", base64_encode((unsigned const char *)patchTuning.mappingContents.c_str(), - patchTuning.mappingContents.size())); + pt.SetAttribute("m", Surge::Storage::base64_encode( + (unsigned const char *)patchTuning.mappingContents.c_str(), + patchTuning.mappingContents.size())); pt.SetAttribute("mname", patchTuning.mappingName); } @@ -3862,9 +3770,9 @@ unsigned int SurgePatch::save_xml(void **data) // allocates mem, must be freed b ** so just protect ourselves with a base 64 encoding. */ TiXmlElement tnc("tuningContents"); - tnc.SetAttribute("v", - base64_encode((unsigned const char *)dawExtraState.scaleContents.c_str(), - dawExtraState.scaleContents.size())); + tnc.SetAttribute("v", Surge::Storage::base64_encode( + (unsigned const char *)dawExtraState.scaleContents.c_str(), + dawExtraState.scaleContents.size())); dawExtraXML.InsertEndChild(tnc); TiXmlElement hmp("hasMapping"); @@ -3877,9 +3785,9 @@ unsigned int SurgePatch::save_xml(void **data) // allocates mem, must be freed b ** so just protect ourselves with a base 64 encoding. */ TiXmlElement mpc("mappingContents"); - mpc.SetAttribute("v", - base64_encode((unsigned const char *)dawExtraState.mappingContents.c_str(), - dawExtraState.mappingContents.size())); + mpc.SetAttribute("v", Surge::Storage::base64_encode( + (unsigned const char *)dawExtraState.mappingContents.c_str(), + dawExtraState.mappingContents.size())); dawExtraXML.InsertEndChild(mpc); TiXmlElement mpn("mappingName"); @@ -4184,7 +4092,8 @@ void SurgePatch::stepSeqFromXmlElement(StepSequencerStorage *ss, TiXmlElement *p void SurgePatch::formulaToXMLElement(FormulaModulatorStorage *fs, TiXmlElement &parent) const { - parent.SetAttribute("formula", base64_encode((unsigned const char *)fs->formulaString.c_str(), + parent.SetAttribute( + "formula", Surge::Storage::base64_encode((unsigned const char *)fs->formulaString.c_str(), fs->formulaString.length())); parent.SetAttribute("interpreter", (int)fs->interpreter); } @@ -4192,7 +4101,7 @@ void SurgePatch::formulaToXMLElement(FormulaModulatorStorage *fs, TiXmlElement & void SurgePatch::formulaFromXMLElement(FormulaModulatorStorage *fs, TiXmlElement *parent) const { auto fb64 = parent->Attribute("formula"); - fs->setFormula(base64_decode(fb64)); + fs->setFormula(Surge::Storage::base64_decode(fb64)); int interp; fs->interpreter = FormulaModulatorStorage::LUA; diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index aebecdaa0ba..bdb9e339e3d 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -438,7 +438,8 @@ SurgeStorage::SurgeStorage(const SurgeStorage::SurgeStorageConfig &config) : oth #else if (fs::exists(datapath / "windows.wt")) { - if (!load_wt_wt(path_to_string(datapath / "windows.wt"), &WindowWT)) + std::string metadata; + if (!load_wt_wt(path_to_string(datapath / "windows.wt"), &WindowWT, metadata)) { WindowWT.size = 0; std::ostringstream oss; @@ -1353,14 +1354,15 @@ void SurgeStorage::load_wt(string filename, Wavetable *wt, OscillatorStorage *os } bool loaded = false; + std::string metadata; if (extension.compare(".wt") == 0) { - loaded = load_wt_wt(filename, wt); + loaded = load_wt_wt(filename, wt, metadata); } else if (extension.compare(".wav") == 0) { - loaded = load_wt_wav_portable(filename, wt); + loaded = load_wt_wav_portable(filename, wt, metadata); } else { @@ -1380,14 +1382,29 @@ void SurgeStorage::load_wt(string filename, Wavetable *wt, OscillatorStorage *os osc->wavetable_display_name = fnnoext; } - // osc->wavetable_formula = {}; - // osc->wavetable_formula_res_base = 5; - // osc->wavetable_formula_nframes = 10; + if (metadata.empty()) + { + osc->wavetable_formula = {}; + osc->wavetable_formula_res_base = 5; + osc->wavetable_formula_nframes = 10; + } + else + { + if (!parse_wt_metadata(metadata, osc)) + { + reportError("Unable to parse metadata", "WaveTable Load"); + std::cerr << metadata << std::endl; + osc->wavetable_formula = {}; + osc->wavetable_formula_res_base = 5; + osc->wavetable_formula_nframes = 10; + } + } } } -bool SurgeStorage::load_wt_wt(string filename, Wavetable *wt) +bool SurgeStorage::load_wt_wt(string filename, Wavetable *wt, std::string &metadata) { + metadata = {}; std::filebuf f; if (!f.open(string_to_path(filename), std::ios::binary | std::ios::in)) @@ -1436,6 +1453,21 @@ bool SurgeStorage::load_wt_wt(string filename, Wavetable *wt) memset(dpad, 0, drest); } + if (mech::endian_read_int16LE(wh.flags) & wtf_has_metadata) + { + std::ostringstream xml; + char buffer[1024]; // Adjust buffer size as needed + std::streamsize bytesRead; + + while ((bytesRead = f.sgetn(buffer, sizeof(buffer)))) + { + // Process the data read into 'buffer' + xml.write(buffer, bytesRead); + } + + metadata = xml.str(); + } + waveTableDataMutex.lock(); bool wasBuilt = wt->BuildWT(data.get(), wh, false); waveTableDataMutex.unlock(); @@ -1562,11 +1594,98 @@ bool SurgeStorage::export_wt_wt_portable(const fs::path &fname, Wavetable *wt, } } + if (!metadata.empty()) + { + wfp.sputn(metadata.c_str(), metadata.length() + 1); // include null term + } + wfp.close(); return true; } +std::string SurgeStorage::make_wt_metadata(OscillatorStorage *oscdata) +{ + bool hasMeta{false}; + TiXmlDocument doc("wtmeta"); + TiXmlElement root("wtmeta"); + TiXmlElement surge("surge"); + if (!oscdata->wavetable_formula.empty()) + { + TiXmlElement script("script"); + + auto wtfo = oscdata->wavetable_formula; + auto wtfol = wtfo.length(); + + script.SetAttribute( + "lua", Surge::Storage::base64_encode((unsigned const char *)wtfo.c_str(), wtfol)); + script.SetAttribute("nframes", oscdata->wavetable_formula_nframes); + script.SetAttribute("res_base", oscdata->wavetable_formula_res_base); + surge.InsertEndChild(script); + hasMeta = true; + } + + root.InsertEndChild(surge); + doc.InsertEndChild(root); + if (hasMeta) + { + std::string res; + res << doc; + return res; + } + return {}; +} +bool SurgeStorage::parse_wt_metadata(const std::string &m, OscillatorStorage *oscdata) +{ + TiXmlDocument doc("wtmeta"); + doc.Parse(m.c_str()); + + auto root = TINYXML_SAFE_TO_ELEMENT(doc.FirstChildElement("wtmeta")); + if (!root) + { + std::cout << "NO ROOT" << std::endl; + return false; + } + + auto surge = TINYXML_SAFE_TO_ELEMENT(root->FirstChildElement("surge")); + if (!surge) + { + std::cout << "NO SURGE" << std::endl; + return false; + } + + auto script = TINYXML_SAFE_TO_ELEMENT(surge->FirstChildElement("script")); + if (!script) + { + std::cout << "NO SCRIPT" << std::endl; + return false; + } + + int n, s; + if (script->QueryIntAttribute("nframes", &n) != TIXML_SUCCESS) + { + std::cout << "NO NFRAMES" << std::endl; + return false; + } + if (script->QueryIntAttribute("res_base", &s) != TIXML_SUCCESS) + { + std::cout << "NO RES_BASE" << std::endl; + return false; + } + auto bscript = script->Attribute("lua"); + if (!bscript) + { + std::cout << "NO LUA" << std::endl; + return false; + } + + oscdata->wavetable_formula = Surge::Storage::base64_decode(bscript); + oscdata->wavetable_formula_nframes = n; + oscdata->wavetable_formula_res_base = s; + + return true; +} + bool SurgeStorage::getOverrideDataHome(std::string &v) { bool r = false; @@ -3252,6 +3371,100 @@ string findReplaceSubstring(string &source, const string &from, const string &to return newString; } +// BASE 64 SUPPORT, THANKS TO: +// https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp +static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +inline bool is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } + +inline std::string base64_encode(unsigned char const *bytes_to_encode, unsigned int in_len) +{ + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) + { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) + { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for (j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while ((i++ < 3)) + ret += '='; + } + + return ret; +} + +std::string base64_decode(std::string const &encoded_string) +{ + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) + { + char_array_4[i++] = encoded_string[in_]; + in_++; + if (i == 4) + { + for (i = 0; i < 4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) + { + for (j = 0; j < i; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + + for (j = 0; (j < i - 1); j++) + ret += char_array_3[j]; + } + + return ret; +} + Surge::Storage::ScenesOutputData::ScenesOutputData() { for (int i = 0; i < n_scenes; i++) diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index 2e614923b61..1c0a7881870 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -1388,15 +1388,18 @@ class alignas(16) SurgeStorage void load_wt(int id, Wavetable *wt, OscillatorStorage *); void load_wt(std::string filename, Wavetable *wt, OscillatorStorage *); - bool load_wt_wt(std::string filename, Wavetable *wt); + bool load_wt_wt(std::string filename, Wavetable *wt, std::string &metadata); bool load_wt_wt_mem(const char *data, const size_t dataSize, Wavetable *wt); - bool load_wt_wav_portable(std::string filename, Wavetable *wt); + bool load_wt_wav_portable(std::string filename, Wavetable *wt, std::string &metadata); std::string export_wt_wav_portable(const std::string &fbase, Wavetable *wt, const std::string &metadata); std::string export_wt_wav_portable(const fs::path &fpath, Wavetable *wt, const std::string &metadata); bool export_wt_wt_portable(const fs::path &fpath, Wavetable *wt, const std::string &metadata); + std::string make_wt_metadata(OscillatorStorage *); + bool parse_wt_metadata(const std::string &, OscillatorStorage *); + void clipboard_copy(int type, int scene, int entry, modsources ms = ms_original); // this function is a bit of a hack to stop me having a reference to SurgeSynth here // and also to stop me having to move all of isValidModulation and its buddies onto SurgeStorage @@ -1812,6 +1815,10 @@ bool isValidUTF8(const std::string &testThis); std::string findReplaceSubstring(std::string &source, const std::string &from, const std::string &to); +bool is_base64(unsigned char c); +std::string base64_encode(unsigned char const *bytes_to_encode, unsigned int in_len); +std::string base64_decode(std::string const &encoded_string); + } // namespace Storage } // namespace Surge diff --git a/src/common/WAVFileSupport.cpp b/src/common/WAVFileSupport.cpp index 33feabac57a..4b6a66a3358 100644 --- a/src/common/WAVFileSupport.cpp +++ b/src/common/WAVFileSupport.cpp @@ -44,10 +44,12 @@ bool four_chars(char *v, char a, char b, char c, char d) return v[0] == a && v[1] == b && v[2] == c && v[3] == d; } -bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) +bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt, std::string &metadata) { std::string uitag = "Wavetable Import Error"; + metadata = {}; + #if WAV_STDOUT_INFO std::cout << "Loading wt_wav_portable" << std::endl; std::cout << " fn = '" << fn << "'" << std::endl; @@ -279,6 +281,12 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) datasamples = cs * 8 / bitsPerSample / numChannels; wavdata = data; } + else if (four_chars(chunkType, 'w', 't', 'm', 'd')) + { + datasz = cs; + metadata = std::string(data); + free(data); + } else if (four_chars(chunkType, 's', 'm', 'p', 'l')) { char *dp = data; @@ -619,6 +627,9 @@ std::string SurgeStorage::export_wt_wav_portable(const fs::path &fname, Wavetabl 4 + 4 + tableSize + // data chunk 4 + 4 + 8; // srgo/srge chunk + if (!metadata.empty()) + dataSize += 4 + 4 + metadata.length() + 1; // null term + w4i(dataSize); wfp.sputn("WAVE", 4); @@ -655,6 +666,13 @@ std::string SurgeStorage::export_wt_wav_portable(const fs::path &fname, Wavetabl wfp.sputn(reinterpret_cast(wt->TableF32WeakPointers[0][i]), wt->size * bitsPerSample / 8); } + + if (!metadata.empty()) + { + wfp.sputn("wtmd", 4); + w4i(metadata.length() + 1); + wfp.sputn(metadata.c_str(), metadata.length() + 1); + } } refresh_wtlist(); diff --git a/src/surge-testrunner/UnitTestsIO.cpp b/src/surge-testrunner/UnitTestsIO.cpp index 28430208aed..f5fe3b187a6 100644 --- a/src/surge-testrunner/UnitTestsIO.cpp +++ b/src/surge-testrunner/UnitTestsIO.cpp @@ -51,10 +51,12 @@ TEST_CASE("We Can Read Wavetables", "[io]") auto surge = Surge::Headless::createSurge(44100); REQUIRE(surge.get()); + std::string metadata; + SECTION("Wavetable.wav") { auto wt = &(surge->storage.getPatch().scene[0].osc[0].wt); - surge->storage.load_wt_wav_portable("resources/test-data/wav/Wavetable.wav", wt); + surge->storage.load_wt_wav_portable("resources/test-data/wav/Wavetable.wav", wt, metadata); REQUIRE(wt->size == 2048); REQUIRE(wt->n_tables == 256); REQUIRE((wt->flags & wtf_is_sample) == 0); @@ -63,7 +65,7 @@ TEST_CASE("We Can Read Wavetables", "[io]") SECTION("05_BELL.WAV") { auto wt = &(surge->storage.getPatch().scene[0].osc[0].wt); - surge->storage.load_wt_wav_portable("resources/test-data/wav/05_BELL.WAV", wt); + surge->storage.load_wt_wav_portable("resources/test-data/wav/05_BELL.WAV", wt, metadata); REQUIRE(wt->size == 2048); REQUIRE(wt->n_tables == 33); REQUIRE((wt->flags & wtf_is_sample) == 0); @@ -72,7 +74,7 @@ TEST_CASE("We Can Read Wavetables", "[io]") SECTION("pluckalgo.wav") { auto wt = &(surge->storage.getPatch().scene[0].osc[0].wt); - surge->storage.load_wt_wav_portable("resources/test-data/wav/pluckalgo.wav", wt); + surge->storage.load_wt_wav_portable("resources/test-data/wav/pluckalgo.wav", wt, metadata); REQUIRE(wt->size == 2048); REQUIRE(wt->n_tables == 9); REQUIRE((wt->flags & wtf_is_sample) == 0); diff --git a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp index fbb5e7da1f7..c818b9f7c32 100644 --- a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp +++ b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp @@ -670,10 +670,16 @@ void OscillatorWaveformDisplay::populateMenu(juce::PopupMenu &contextMenu, int s { } + auto startNm = path / oscdata->wavetable_display_name; + if (isWav) + startNm = startNm.replace_extension(".wav"); + else + startNm = startNm.replace_extension(".wt"); + auto nm = isWav ? "Export WAV Wavetable" : "Export WT Wavetable"; auto that = this; // i hate msvc sge->fileChooser = - std::make_unique(nm, juce::File(path.u8string().c_str())); + std::make_unique(nm, juce::File(startNm.u8string().c_str())); sge->fileChooser->launchAsync( juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::canSelectFiles | juce::FileBrowserComponent::warnAboutOverwriting, @@ -685,7 +691,7 @@ void OscillatorWaveformDisplay::populateMenu(juce::PopupMenu &contextMenu, int s } auto fsp = fs::path{result[0].getFullPathName().toStdString()}; - std::string metadata{}; + std::string metadata = w->storage->make_wt_metadata(w->oscdata); if (isWav) { if (fsp.extension() != ".wav") From f8298b5634fc82419d67ae952357d1e85c36498b Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 4 Dec 2024 14:32:05 -0500 Subject: [PATCH 2/2] Update SurgeStorage.cpp --- src/common/SurgeStorage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index bdb9e339e3d..fe88866ea0b 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -3377,9 +3377,9 @@ static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; -inline bool is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } +bool is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } -inline std::string base64_encode(unsigned char const *bytes_to_encode, unsigned int in_len) +std::string base64_encode(unsigned char const *bytes_to_encode, unsigned int in_len) { std::string ret; int i = 0;