diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 31ea49c88..416b23041 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -7,6 +7,7 @@ #pragma once #include #include +#include //#include #include #include @@ -202,6 +203,34 @@ inline CXX14_CONSTEXPR Type vaGain(Type cutoff, Type sampleRate) return std::tan(cutoff / sampleRate * pi()); } +/** + * @brief Insert an item uniquely into a vector of pairs. + * + * @param pairVector the vector of pairs + * @param key the unique key + * @param value the value + * @param replace whether to replace the value if the key is already present + * @return whether the item was inserted + */ +template +bool insertPairUniquely(std::vector

& pairVector, const T& key, U value, bool replace = true) +{ + bool result = false; + auto it = absl::c_find_if( + pairVector, [&key](const P& pair) { return pair.first == key; }); + if (it != pairVector.end()) { + if (replace) { + it->second = std::move(value); + result = true; + } + } + else { + pairVector.emplace_back(key, std::move(value)); + result = true; + } + return result; +} + /** * @brief From a source view, find the next sfz header and its members and * return them, while updating the source by removing this header diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index beae4e64b..bd4f177c2 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -231,6 +231,10 @@ void sfz::Synth::clear() // set default controllers cc(0, 7, 100); // volume hdcc(0, 10, 0.5f); // pan + + // set default controller labels + insertPairUniquely(ccLabels, 7, "Volume"); + insertPairUniquely(ccLabels, 10, "Pan"); } void sfz::Synth::handleMasterOpcodes(const std::vector& members) @@ -332,12 +336,12 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) break; case hash("label_cc&"): if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) - ccLabels.emplace_back(member.parameters.back(), std::string(member.value)); + insertPairUniquely(ccLabels, member.parameters.back(), std::string(member.value)); break; case hash("label_key&"): if (member.parameters.back() <= Default::keyRange.getEnd()) { const auto noteNumber = static_cast(member.parameters.back()); - keyLabels.emplace_back(noteNumber, std::string(member.value)); + insertPairUniquely(keyLabels, noteNumber, std::string(member.value)); } break; case hash("default_path"): @@ -541,7 +545,7 @@ void sfz::Synth::finalizeSfzLoad() } if (region->keyswitchLabel && region->keyswitch) - keyswitchLabels.push_back({ *region->keyswitch, *region->keyswitchLabel }); + insertPairUniquely(keyswitchLabels, *region->keyswitch, *region->keyswitchLabel); // Some regions had group number but no "group-level" opcodes handled the polyphony while (polyphonyGroups.size() <= region->group) { @@ -1257,13 +1261,15 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const cns.append_attribute("Name").set_value("Controls"); for (const auto& pair : ccLabels) { anonymousCCs.set(pair.first, false); - pugi::xml_node cn = cns.append_child("Control"); - cn.append_attribute("Type").set_value("7bit"); - cn.append_attribute("Number").set_value(std::to_string(pair.first).c_str()); - cn.append_attribute("Name").set_value(pair.second.c_str()); + if (pair.first < 128) { + pugi::xml_node cn = cns.append_child("Control"); + cn.append_attribute("Type").set_value("7bit"); + cn.append_attribute("Number").set_value(std::to_string(pair.first).c_str()); + cn.append_attribute("Name").set_value(pair.second.c_str()); + } } - for (unsigned i = 0; i < anonymousCCs.size(); ++i) { + for (unsigned i = 0, n = std::min(128, anonymousCCs.size()); i < n; ++i) { if (anonymousCCs[i]) { pugi::xml_node cn = cns.append_child("Control"); cn.append_attribute("Type").set_value("7bit"); diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index f257c4108..e5a7c77fe 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -591,11 +591,9 @@ TEST_CASE("[Files] Labels") REQUIRE( keyLabels[0].second == "Cymbals" ); REQUIRE( keyLabels[1].first == 65 ); REQUIRE( keyLabels[1].second == "Crash" ); - REQUIRE( ccLabels.size() == 2); - REQUIRE( ccLabels[0].first == 54 ); - REQUIRE( ccLabels[0].second == "Gain" ); - REQUIRE( ccLabels[1].first == 2 ); - REQUIRE( ccLabels[1].second == "Other" ); + REQUIRE( ccLabels.size() >= 2); + REQUIRE( absl::c_find(ccLabels, CCNamePair { 54, "Gain" }) != ccLabels.end() ); + REQUIRE( absl::c_find(ccLabels, CCNamePair { 2, "Other" }) != ccLabels.end() ); const std::string xmlMidnam = synth.exportMidnam(); REQUIRE(xmlMidnam.find("") != xmlMidnam.npos); REQUIRE(xmlMidnam.find("") != xmlMidnam.npos); @@ -612,3 +610,24 @@ TEST_CASE("[Files] Switch labels") REQUIRE(xmlMidnam.find("") != xmlMidnam.npos); REQUIRE(xmlMidnam.find("") != xmlMidnam.npos); } + +TEST_CASE("[Files] Duplicate labels") +{ + sfz::Synth synth; + synth.loadSfzString( + fs::current_path() / "tests/TestFiles/labels.sfz", + R"( label_key60=Baz label_key60=Quux + label_cc20=Foo label_cc20=Bar + sample=*sine)"); + + auto keyLabels = synth.getKeyLabels(); + auto ccLabels = synth.getCCLabels(); + REQUIRE( keyLabels.size() == 1); + REQUIRE( keyLabels[0].first == 60 ); + REQUIRE( keyLabels[0].second == "Quux" ); + REQUIRE( ccLabels.size() >= 1); + REQUIRE( absl::c_find(ccLabels, CCNamePair { 20, "Bar" }) != ccLabels.end() ); + const std::string xmlMidnam = synth.exportMidnam(); + REQUIRE(xmlMidnam.find("") != xmlMidnam.npos); + REQUIRE(xmlMidnam.find("") != xmlMidnam.npos); +}