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

Anonymous CCs with modulation matrix #351

Merged
merged 1 commit into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/sfizz/Synth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1195,14 +1195,26 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const
}

{
auto anonymousCCs = getUsedCCs();

pugi::xml_node cns = device.append_child("ControlNameList");
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());
}

for (unsigned i = 0; i < anonymousCCs.size(); ++i) {
if (anonymousCCs[i]) {
pugi::xml_node cn = cns.append_child("Control");
cn.append_attribute("Type").set_value("7bit");
cn.append_attribute("Number").set_value(std::to_string(i).c_str());
cn.append_attribute("Name").set_value(("Unnamed CC " + std::to_string(i)).c_str());
}
}
}

{
Expand Down Expand Up @@ -1581,3 +1593,64 @@ void sfz::Synth::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexce

polyphonyGroups[groupIdx].setPolyphonyLimit(polyphony);
}

std::bitset<sfz::config::numCCs> sfz::Synth::getUsedCCs() const noexcept
{
std::bitset<sfz::config::numCCs> used;
for (const RegionPtr& region : regions)
updateUsedCCsFromRegion(used, *region);
updateUsedCCsFromModulations(used, resources.modMatrix);
return used;
}

void sfz::Synth::updateUsedCCsFromRegion(std::bitset<sfz::config::numCCs>& usedCCs, const Region& region)
{
updateUsedCCsFromCCMap(usedCCs, region.offsetCC);
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccAttack);
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccRelease);
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccDecay);
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccDelay);
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccHold);
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccStart);
updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccSustain);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccAttack);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccRelease);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccDecay);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccDelay);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccHold);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccStart);
updateUsedCCsFromCCMap(usedCCs, region.pitchEG.ccSustain);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccAttack);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccRelease);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccDecay);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccDelay);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccHold);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccStart);
updateUsedCCsFromCCMap(usedCCs, region.filterEG.ccSustain);
updateUsedCCsFromCCMap(usedCCs, region.ccConditions);
updateUsedCCsFromCCMap(usedCCs, region.ccTriggers);
updateUsedCCsFromCCMap(usedCCs, region.crossfadeCCInRange);
updateUsedCCsFromCCMap(usedCCs, region.crossfadeCCOutRange);
}

void sfz::Synth::updateUsedCCsFromModulations(std::bitset<sfz::config::numCCs>& usedCCs, const ModMatrix& mm)
{
class CCSourceCollector : public ModMatrix::KeyVisitor {
public:
explicit CCSourceCollector(std::bitset<sfz::config::numCCs>& used)
: used_(used)
{
}

bool visit(const ModKey& key) override
{
if (key.id() == ModId::Controller)
used_.set(key.parameters().cc);
return true;
}
std::bitset<sfz::config::numCCs>& used_;
};

CCSourceCollector vtor(usedCCs);
mm.visitSources(vtor);
}
16 changes: 16 additions & 0 deletions src/sfizz/Synth.h
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,13 @@ class Synth final : public Voice::StateListener, public Parser::Listener {
*/
const std::vector<CCNamePair>& getCCLabels() const noexcept { return ccLabels; }

/**
* @brief Get the used CCs
*
* @return const std::bitset<config::numCCs>&
*/
std::bitset<config::numCCs> getUsedCCs() const noexcept;

protected:
/**
* @brief The voice callback which is called during a change of state.
Expand Down Expand Up @@ -697,6 +704,15 @@ class Synth final : public Voice::StateListener, public Parser::Listener {
void noteOnDispatch(int delay, int noteNumber, float velocity) noexcept;
void noteOffDispatch(int delay, int noteNumber, float velocity) noexcept;

template<class T>
static void updateUsedCCsFromCCMap(std::bitset<sfz::config::numCCs>& usedCCs, const CCMap<T> map)
{
for (auto& mod : map)
usedCCs[mod.cc] = true;
}
static void updateUsedCCsFromRegion(std::bitset<sfz::config::numCCs>& usedCCs, const Region& region);
static void updateUsedCCsFromModulations(std::bitset<sfz::config::numCCs>& usedCCs, const ModMatrix& mm);

// Opcode memory; these are used to build regions, as a new region
// will integrate opcodes from the group, master and global block
std::vector<Opcode> globalOpcodes;
Expand Down
24 changes: 24 additions & 0 deletions src/sfizz/modulations/ModMatrix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,4 +428,28 @@ std::string ModMatrix::toDotGraph() const
return dot;
}

bool ModMatrix::visitSources(KeyVisitor& vtor) const
{
const Impl& impl = *impl_;

for (const Impl::Source& item : impl.sources_) {
if (!vtor.visit(item.key))
return false;
}

return true;
}

bool ModMatrix::visitTargets(KeyVisitor& vtor) const
{
const Impl& impl = *impl_;

for (const Impl::Target& item : impl.targets_) {
if (!vtor.visit(item.key))
return false;
}

return true;
}

} // namespace sfz
28 changes: 28 additions & 0 deletions src/sfizz/modulations/ModMatrix.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,34 @@ class ModMatrix {
*/
std::string toDotGraph() const;

class KeyVisitor {
public:
virtual ~KeyVisitor() {}
/**
* @brief Visit a key of the modulation matrix.
*
* @param key
* @return true to continue visiting, false to stop
*/
virtual bool visit(const ModKey& key) = 0;
};

/**
* @brief Visit the keys of all the sources in the matrix.
*
* @param vtor a visitor object
* @return last return code from the visitor
*/
bool visitSources(KeyVisitor& vtor) const;

/**
* @brief Visit the keys of all the sources in the matrix.
*
* @param vtor a visitor object
* @return last return code from the visitor
*/
bool visitTargets(KeyVisitor& vtor) const;

private:
struct Impl;
std::unique_ptr<Impl> impl_;
Expand Down
88 changes: 88 additions & 0 deletions tests/SynthT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -981,3 +981,91 @@ TEST_CASE("[Synth] sw_default works at a group level")
synth.noteOn(0, 62, 85);
REQUIRE( synth.getNumActiveVoices(true) == 1 );
}

TEST_CASE("[Synth] Used CCs")
{
sfz::Synth synth;
REQUIRE( !synth.getUsedCCs().any() );
synth.loadSfzString(fs::current_path(), R"(
<global> amplitude_cc1=100
<group> volume_oncc2=5
<region> locc4=64 hicc67=32 pan_cc5=200 sample=*sine
<region> width_cc98=200 sample=*sine
<region> position_cc42=200 pitch_oncc56=200 sample=*sine
<region> start_locc44=200 hikey=-1 sample=*sine
)");
auto usedCCs = synth.getUsedCCs();
REQUIRE( usedCCs[1] );
REQUIRE( usedCCs[2] );
REQUIRE( !usedCCs[3] );
REQUIRE( usedCCs[4] );
REQUIRE( usedCCs[5] );
REQUIRE( !usedCCs[6] );
REQUIRE( usedCCs[42] );
REQUIRE( usedCCs[44] );
REQUIRE( usedCCs[56] );
REQUIRE( usedCCs[67] );
REQUIRE( usedCCs[98] );
REQUIRE( !usedCCs[127] );
}

TEST_CASE("[Synth] Used CCs EGs")
{
sfz::Synth synth;
REQUIRE( !synth.getUsedCCs().any() );
synth.loadSfzString(fs::current_path(), R"(
<region>
ampeg_attack_oncc1=1
ampeg_sustain_oncc2=2
ampeg_start_oncc3=3
ampeg_hold_oncc4=4
ampeg_decay_oncc5=5
ampeg_delay_oncc6=6
ampeg_release_oncc7=7
sample=*sine
<region>
pitcheg_attack_oncc11=11
pitcheg_sustain_oncc12=12
pitcheg_start_oncc13=13
pitcheg_hold_oncc14=14
pitcheg_decay_oncc15=15
pitcheg_delay_oncc16=16
pitcheg_release_oncc17=17
sample=*sine
<region>
fileg_attack_oncc21=21
fileg_sustain_oncc22=22
fileg_start_oncc23=23
fileg_hold_oncc24=24
fileg_decay_oncc25=25
fileg_delay_oncc26=26
fileg_release_oncc27=27
sample=*sine
)");
auto usedCCs = synth.getUsedCCs();
REQUIRE( usedCCs[1] );
REQUIRE( usedCCs[2] );
REQUIRE( usedCCs[3] );
REQUIRE( usedCCs[4] );
REQUIRE( usedCCs[5] );
REQUIRE( usedCCs[6] );
REQUIRE( usedCCs[7] );
// FIXME: enable when supported
// REQUIRE( !usedCCs[8] );
// REQUIRE( usedCCs[11] );
// REQUIRE( usedCCs[12] );
// REQUIRE( usedCCs[13] );
// REQUIRE( usedCCs[14] );
// REQUIRE( usedCCs[15] );
// REQUIRE( usedCCs[16] );
// REQUIRE( usedCCs[17] );
// REQUIRE( !usedCCs[18] );
// REQUIRE( usedCCs[21] );
// REQUIRE( usedCCs[22] );
// REQUIRE( usedCCs[23] );
// REQUIRE( usedCCs[24] );
// REQUIRE( usedCCs[25] );
// REQUIRE( usedCCs[26] );
// REQUIRE( usedCCs[27] );
// REQUIRE( !usedCCs[28] );
}