diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 60979282..57bf109f 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -20,7 +20,9 @@ namespace sfz { -using CCNamePair = std::pair; +using CCNamePair = std::pair; +using NoteNamePair = std::pair; + template using MidiNoteArray = std::array; template diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index fd12b218..fd3b5587 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -142,7 +142,9 @@ void sfz::Synth::clear() numMasters = 0; defaultSwitch = absl::nullopt; defaultPath = ""; - ccNames.clear(); + resources.midiState.reset(); + ccLabels.clear(); + noteLabels.clear(); globalOpcodes.clear(); masterOpcodes.clear(); groupOpcodes.clear(); @@ -208,7 +210,11 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) case hash("Label_cc&"): // fallthrough case hash("label_cc&"): if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) - ccNames.emplace_back(member.parameters.back(), std::string(member.value)); + ccLabels.emplace_back(member.parameters.back(), std::string(member.value)); + break; + case hash("label_note&"): + if (Default::keyRange.containsWithEnd(member.parameters.back())) + noteLabels.emplace_back(member.parameters.back(), std::string(member.value)); break; case hash("Default_path"): // fallthrough @@ -871,7 +877,7 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const { pugi::xml_node cns = device.append_child("ControlNameList"); cns.append_attribute("Name").set_value("Controls"); - for (const CCNamePair& pair : ccNames) { + for (const CCNamePair& pair : ccLabels) { 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()); diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 4d34d8e8..0f34fdb7 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -388,6 +388,19 @@ public: */ const Parser& getParser() const noexcept { return parser; } + /** + * @brief Get the note labels, if any + * + * @return const std::vector& + */ + const std::vector& getNoteLabels() const noexcept { return noteLabels; } + /** + * @brief Get the CC labels, if any + * + * @return const std::vector& + */ + const std::vector& getCCLabels() const noexcept { return ccLabels; } + protected: /** * @brief The parser callback; this is called by the parent object each time @@ -494,8 +507,11 @@ private: * @return Voice* */ Voice* findFreeVoice() noexcept; - // Names for the cc as set by the label_cc opcode - std::vector ccNames; + + // Names for the CC and notes as set by label_cc and label_note + std::vector ccLabels; + std::vector noteLabels; + // Default active switch if multiple keyswitchable regions are present absl::optional defaultSwitch; std::vector unknownOpcodes; diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 4dd43c8f..d7bedade 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -563,3 +563,21 @@ TEST_CASE("[Files] Empty file") REQUIRE(!synth.loadSfzFile({})); REQUIRE(parser.getIncludedFiles().empty()); } + +TEST_CASE("[Files] Labels") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/labels.sfz"); + auto noteLabels = synth.getNoteLabels(); + auto ccLabels = synth.getCCLabels(); + REQUIRE( noteLabels.size() == 2); + REQUIRE( noteLabels[0].first == 12 ); + REQUIRE( noteLabels[0].second == "Cymbals" ); + REQUIRE( noteLabels[1].first == 65 ); + REQUIRE( noteLabels[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" ); +} diff --git a/tests/TestFiles/labels.sfz b/tests/TestFiles/labels.sfz new file mode 100644 index 00000000..49f51ed5 --- /dev/null +++ b/tests/TestFiles/labels.sfz @@ -0,0 +1,7 @@ + +label_cc54=Gain +label_cc2=Other +label_note12=Cymbals +label_note65=Crash +label_note128=Ignored + sample=*sine