Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an opcode for note names, and API bindings for note and cc names #174

Merged
merged 13 commits into from
Apr 18, 2020
49 changes: 49 additions & 0 deletions src/sfizz.h
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ SFIZZ_EXPORTED_API void sfizz_all_sound_off(sfizz_synth_t* synth);
* @brief Add external definitions prior to loading;
* Note that these do not get reset by loading or resetting the synth.
* You need to call sfizz_clear_external_definitions() to erase them.
* @since 0.4.0-dev
*
* @param synth
* @param id
Expand All @@ -389,11 +390,59 @@ SFIZZ_EXPORTED_API void sfizz_add_external_definitions(sfizz_synth_t* synth, con

/**
* @brief Clears external definitions for the next file loading.
* @since 0.4.0-dev
*
* @param synth
*/
SFIZZ_EXPORTED_API void sfizz_clear_external_definitions(sfizz_synth_t* synth);

#define SFIZZ_OUT_OF_BOUNDS_LABEL_INDEX -1

/**
* @brief Get the number of key labels registered in the current sfz file
* @since 0.4.0-dev
*/
SFIZZ_EXPORTED_API unsigned int sfizz_get_num_key_labels(sfizz_synth_t* synth);

/**
* @brief Get the key number for the label registered at index label_index.
* @since 0.4.0-dev
*
* @returns the number or SFIZZ_OUT_OF_BOUNDS_LABEL_INDEX if the index is out of bounds.
*/
SFIZZ_EXPORTED_API int sfizz_get_key_label_number(sfizz_synth_t* synth, int label_index);

/**
* @brief Get the key text for the label registered at index label_index.
* @since 0.4.0-dev
*
* @returns the label or NULL if the index is out of bounds.
*/
SFIZZ_EXPORTED_API const char * sfizz_get_key_label_text(sfizz_synth_t* synth, int label_index);

/**
* @brief Get the number of CC labels registered in the current sfz file
* @since 0.4.0-dev
*
*/
SFIZZ_EXPORTED_API unsigned int sfizz_get_num_cc_labels(sfizz_synth_t* synth);

/**
* @brief Get the CC number for the label registered at index label_index.
* @since 0.4.0-dev
*
* @returns the number or SFIZZ_OUT_OF_BOUNDS_LABEL_INDEX if the index is out of bounds.
*/

SFIZZ_EXPORTED_API int sfizz_get_cc_label_number(sfizz_synth_t* synth, int label_index);
/**
* @brief Get the CC text for the label registered at index label_index.
* @since 0.4.0-dev
*
* @returns the label or NULL if the index is out of bounds.
*/
SFIZZ_EXPORTED_API const char * sfizz_get_cc_label_text(sfizz_synth_t* synth, int label_index);

#ifdef __cplusplus
}
#endif
18 changes: 17 additions & 1 deletion src/sfizz.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

#include <string>
#include <utility>
#include <vector>
#include <memory>

Expand Down Expand Up @@ -287,7 +288,8 @@ class SFIZZ_EXPORTED_API Sfizz
void setLoggingPrefix(const std::string& prefix) noexcept;

/**
* @brief Disable logging.
* @brief
*
*/
void disableLogging() noexcept;

Expand All @@ -300,6 +302,7 @@ class SFIZZ_EXPORTED_API Sfizz
* @brief Add external definitions prior to loading;
* Note that these do not get reset by loading or resetting the synth.
* You need to call clearExternalDefintions() to erase them.
* @since 0.4.0-dev
*
* @param id
* @param value
Expand All @@ -308,10 +311,23 @@ class SFIZZ_EXPORTED_API Sfizz

/**
* @brief Clears external definitions for the next file loading.
* @since 0.4.0-dev
*
*/
void clearExternalDefinitions();

/**
* @brief Get the key labels, if any
* @since 0.4.0-dev
*
*/
const std::vector<std::pair<uint8_t, std::string>>& getKeyLabels() const noexcept;
/**
* @brief Get the CC labels, if any
* @since 0.4.0-dev
*
*/
const std::vector<std::pair<uint16_t, std::string>>& getCCLabels() const noexcept;
private:
std::unique_ptr<sfz::Synth> synth;
};
Expand Down
3 changes: 3 additions & 0 deletions src/sfizz/Region.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
setValueFromOpcode(opcode, keyswitch, Default::keyRange);
keySwitched = false;
break;
case hash("sw_label"):
keyswitchLabel = opcode.value;
break;
case hash("sw_down"):
setValueFromOpcode(opcode, keyswitchDown, Default::keyRange);
keySwitched = false;
Expand Down
1 change: 1 addition & 0 deletions src/sfizz/Region.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ struct Region {
CCMap<Range<float>> ccConditions { Default::ccValueRange };
Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey
absl::optional<uint8_t> keyswitch {}; // sw_last
absl::optional<std::string> keyswitchLabel {};
absl::optional<uint8_t> keyswitchUp {}; // sw_up
absl::optional<uint8_t> keyswitchDown {}; // sw_down
absl::optional<uint8_t> previousNote {}; // sw_previous
Expand Down
2 changes: 2 additions & 0 deletions src/sfizz/SfzHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ namespace sfz
{

using CCNamePair = std::pair<uint16_t, std::string>;
using NoteNamePair = std::pair<uint8_t, std::string>;

template <class T>
using MidiNoteArray = std::array<T, 128>;
template<class ValueType>
Expand Down
34 changes: 31 additions & 3 deletions src/sfizz/Synth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ void sfz::Synth::clear()
numMasters = 0;
defaultSwitch = absl::nullopt;
defaultPath = "";
ccNames.clear();
resources.midiState.reset();
ccLabels.clear();
keyLabels.clear();
globalOpcodes.clear();
masterOpcodes.clear();
groupOpcodes.clear();
Expand Down Expand Up @@ -208,7 +210,11 @@ void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& 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_key&"):
if (Default::keyRange.containsWithEnd(member.parameters.back()))
keyLabels.emplace_back(member.parameters.back(), std::string(member.value));
break;
case hash("Default_path"):
// fallthrough
Expand Down Expand Up @@ -390,6 +396,10 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
}
}

if (region->keyswitchLabel && region->keyswitch)
keyswitchLabels.push_back({ *region->keyswitch, *region->keyswitchLabel });


// Some regions had group number but no "group-level" opcodes handled the polyphony
while (groupMaxPolyphony.size() <= region->group)
groupMaxPolyphony.push_back(config::maxVoices);
Expand Down Expand Up @@ -866,19 +876,37 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const
chns.append_child("UsesControlNameList")
.append_attribute("Name")
.set_value("Controls");
chns.append_child("UsesNoteNameList")
.append_attribute("Name")
.set_value("Notes");
}

{
pugi::xml_node cns = device.append_child("ControlNameList");
cns.append_attribute("Name").set_value("Controls");
for (const CCNamePair& pair : ccNames) {
for (const auto& 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());
cn.append_attribute("Name").set_value(pair.second.c_str());
}
}

{
pugi::xml_node nnl = device.append_child("NoteNameList");
nnl.append_attribute("Name").set_value("Notes");
for (const auto& pair : keyswitchLabels) {
pugi::xml_node nn = nnl.append_child("Note");
nn.append_attribute("Number").set_value(std::to_string(pair.first).c_str());
nn.append_attribute("Name").set_value(pair.second.c_str());
}
for (const auto& pair : keyLabels) {
pugi::xml_node nn = nnl.append_child("Note");
nn.append_attribute("Number").set_value(std::to_string(pair.first).c_str());
nn.append_attribute("Name").set_value(pair.second.c_str());
}
}

///
struct string_writer : pugi::xml_writer {
std::string result;
Expand Down
21 changes: 19 additions & 2 deletions src/sfizz/Synth.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,19 @@ class Synth final : public Parser::Listener {
*/
const Parser& getParser() const noexcept { return parser; }

/**
* @brief Get the key labels, if any
*
* @return const std::vector<NoteNamePair>&
*/
const std::vector<NoteNamePair>& getKeyLabels() const noexcept { return keyLabels; }
/**
* @brief Get the CC labels, if any
*
* @return const std::vector<NoteNamePair>&
*/
const std::vector<CCNamePair>& getCCLabels() const noexcept { return ccLabels; }

protected:
/**
* @brief The parser callback; this is called by the parent object each time
Expand Down Expand Up @@ -494,8 +507,12 @@ class Synth final : public Parser::Listener {
* @return Voice*
*/
Voice* findFreeVoice() noexcept;
// Names for the cc as set by the label_cc opcode
std::vector<CCNamePair> ccNames;

// Names for the CC and notes as set by label_cc and label_key
std::vector<CCNamePair> ccLabels;
std::vector<NoteNamePair> keyLabels;
std::vector<NoteNamePair> keyswitchLabels;

// Default active switch if multiple keyswitchable regions are present
absl::optional<uint8_t> defaultSwitch;
std::vector<std::string> unknownOpcodes;
Expand Down
10 changes: 10 additions & 0 deletions src/sfizz/sfizz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,13 @@ void sfz::Sfizz::clearExternalDefinitions()
{
synth->getParser().clearExternalDefinitions();
}

const std::vector<std::pair<uint8_t, std::string>>& sfz::Sfizz::getKeyLabels() const noexcept
{
return synth->getKeyLabels();
}

const std::vector<std::pair<uint16_t, std::string>>& sfz::Sfizz::getCCLabels() const noexcept
{
return synth->getCCLabels();
}
75 changes: 75 additions & 0 deletions src/sfizz/sfizz_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "Macros.h"
#include "Synth.h"
#include "sfizz.h"
#include <limits>

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -268,6 +269,80 @@ void sfizz_clear_external_definitions(sfizz_synth_t* synth)
self->getParser().clearExternalDefinitions();
}

unsigned int sfizz_get_num_key_labels(sfizz_synth_t* synth)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
return self->getKeyLabels().size();
}

int sfizz_get_key_label_number(sfizz_synth_t* synth, int label_index)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
const auto keyLabels = self->getKeyLabels();
if (label_index < 0)
return SFIZZ_OUT_OF_BOUNDS_LABEL_INDEX;

if (static_cast<unsigned int>(label_index) >= keyLabels.size())
return SFIZZ_OUT_OF_BOUNDS_LABEL_INDEX;

// Sanity checks for the future or platforms
static_assert(
std::numeric_limits<sfz::NoteNamePair::first_type>::max() < std::numeric_limits<int>::max(),
"The C API sends back an int but the note index in NoteNamePair can overflow it on this platform"
);
return static_cast<int>(keyLabels[label_index].first);
}

const char * sfizz_get_key_label_text(sfizz_synth_t* synth, int label_index)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
const auto keyLabels = self->getKeyLabels();
if (label_index < 0)
return NULL;

if (static_cast<unsigned int>(label_index) >= keyLabels.size())
return NULL;

return keyLabels[label_index].second.c_str();
}

unsigned int sfizz_get_num_cc_labels(sfizz_synth_t* synth)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
return self->getCCLabels().size();
}

int sfizz_get_cc_label_number(sfizz_synth_t* synth, int label_index)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
const auto ccLabels = self->getCCLabels();
if (label_index < 0)
return SFIZZ_OUT_OF_BOUNDS_LABEL_INDEX;

if (static_cast<unsigned int>(label_index) >= ccLabels.size())
return SFIZZ_OUT_OF_BOUNDS_LABEL_INDEX;

// Sanity checks for the future or platforms
static_assert(
std::numeric_limits<sfz::CCNamePair::first_type>::max() < std::numeric_limits<int>::max(),
"The C API sends back an int but the cc index in CCNamePair can overflow it on this platform"
);
return static_cast<int>(ccLabels[label_index].first);
}

const char * sfizz_get_cc_label_text(sfizz_synth_t* synth, int label_index)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
const auto ccLabels = self->getCCLabels();
if (label_index < 0)
return NULL;

if (static_cast<unsigned int>(label_index) >= ccLabels.size())
return NULL;

return ccLabels[label_index].second.c_str();
}

#ifdef __cplusplus
}
#endif
33 changes: 33 additions & 0 deletions tests/FilesT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,3 +563,36 @@ 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 keyLabels = synth.getKeyLabels();
auto ccLabels = synth.getCCLabels();
REQUIRE( keyLabels.size() == 2);
REQUIRE( keyLabels[0].first == 12 );
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" );
const std::string xmlMidnam = synth.exportMidnam();
REQUIRE(xmlMidnam.find("<Note Number=\"12\" Name=\"Cymbals\" />") != xmlMidnam.npos);
REQUIRE(xmlMidnam.find("<Note Number=\"65\" Name=\"Crash\" />") != xmlMidnam.npos);
REQUIRE(xmlMidnam.find("<Control Type=\"7bit\" Number=\"54\" Name=\"Gain\" />") != xmlMidnam.npos);
REQUIRE(xmlMidnam.find("<Control Type=\"7bit\" Number=\"2\" Name=\"Other\" />") != xmlMidnam.npos);
}

TEST_CASE("[Files] Switch labels")
{
sfz::Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/labels_sw.sfz");
const std::string xmlMidnam = synth.exportMidnam();
REQUIRE(xmlMidnam.find("<Note Number=\"36\" Name=\"Sine\" />") != xmlMidnam.npos);
REQUIRE(xmlMidnam.find("<Note Number=\"38\" Name=\"Triangle\" />") != xmlMidnam.npos);
REQUIRE(xmlMidnam.find("<Note Number=\"40\" Name=\"Saw\" />") != xmlMidnam.npos);
}
Loading