From e32618e4201769211f026a60b263cdc9183ae6db Mon Sep 17 00:00:00 2001 From: Python Blue Date: Mon, 7 Aug 2023 11:24:00 +0200 Subject: [PATCH] Fix modulation matrix when CC modulations are per voice --- src/Config.h.in | 15 ---- src/sfizz/Config.h | 15 ---- src/sfizz/MidiState.cpp | 4 +- src/sfizz/Region.cpp | 44 ++++++------ src/sfizz/SfzHelpers.h | 72 ++++++++++++++++++++ src/sfizz/Voice.cpp | 2 +- src/sfizz/modulations/sources/Controller.cpp | 4 +- tests/CMakeLists.txt | 1 + tests/SfzHelpersT.cpp | 40 +++++++++++ 9 files changed, 142 insertions(+), 55 deletions(-) create mode 100644 tests/SfzHelpersT.cpp diff --git a/src/Config.h.in b/src/Config.h.in index 1d89dfacd..bd13a70b5 100644 --- a/src/Config.h.in +++ b/src/Config.h.in @@ -18,21 +18,6 @@ namespace sfz { -enum ExtendedCCs { - pitchBend = 128, - channelAftertouch = 129, - polyphonicAftertouch = 130, - noteOnVelocity = 131, - noteOffVelocity = 132, - keyboardNoteNumber = 133, - keyboardNoteGate = 134, - unipolarRandom = 135, - bipolarRandom = 136, - alternate = 137, - keydelta = 140, - absoluteKeydelta = 141, -}; - namespace config { constexpr float defaultSampleRate { 48000 }; constexpr float maxSampleRate { 192000 }; diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 5aa96541f..5722f68c7 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -18,21 +18,6 @@ namespace sfz { -enum ExtendedCCs { - pitchBend = 128, - channelAftertouch = 129, - polyphonicAftertouch = 130, - noteOnVelocity = 131, - noteOffVelocity = 132, - keyboardNoteNumber = 133, - keyboardNoteGate = 134, - unipolarRandom = 135, - bipolarRandom = 136, - alternate = 137, - keydelta = 140, - absoluteKeydelta = 141, -}; - namespace config { constexpr float defaultSampleRate { 48000 }; constexpr float maxSampleRate { 192000 }; diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 354750c08..6a162c1e6 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -36,8 +36,8 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex ccEvent(delay, ExtendedCCs::unipolarRandom, unipolarDist(Random::randomGenerator)); ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator)); ccEvent(delay, ExtendedCCs::keyboardNoteGate, activeNotes > 0 ? 1.0f : 0.0f); - ccEvent(delay, ExtendedCCs::keydelta, keydelta); - ccEvent(delay, ExtendedCCs::absoluteKeydelta, std::abs(keydelta)); + ccEvent(delay, AriaExtendedCCs::keydelta, keydelta); + ccEvent(delay, AriaExtendedCCs::absoluteKeydelta, std::abs(keydelta)); activeNotes++; ccEvent(delay, ExtendedCCs::alternate, alternate); diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 4dc7de70d..fe274fe86 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1690,8 +1690,13 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec spec, // search an existing connection of same CC number and target // if it exists, modify, otherwise create auto it = std::find_if(connections.begin(), connections.end(), - [ccNumber, &target](const Connection& x) -> bool + [ccNumber, &target, this](const Connection& x) -> bool { + if (ccModulationIsPerVoice(ccNumber)) + return x.source.id() == ModId::PerVoiceController && + x.source.region() == id && + x.source.parameters().cc == ccNumber && + x.target == target; return x.source.id() == ModId::Controller && x.source.parameters().cc == ccNumber && x.target == target; @@ -1730,22 +1735,11 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec spec, break; } - switch (p.cc) { - case ExtendedCCs::noteOnVelocity: // fallthrough - case ExtendedCCs::noteOffVelocity: // fallthrough - case ExtendedCCs::keyboardNoteNumber: // fallthrough - case ExtendedCCs::keyboardNoteGate: // fallthrough - case ExtendedCCs::unipolarRandom: // fallthrough - case ExtendedCCs::bipolarRandom: // fallthrough - case ExtendedCCs::alternate: - case ExtendedCCs::keydelta: - case ExtendedCCs::absoluteKeydelta: + if (ccModulationIsPerVoice(p.cc)) { conn->source = ModKey(ModId::PerVoiceController, id, p); - break; - default: + } else { conn->source = ModKey(ModId::Controller, {}, p); - break; - } + } } return true; @@ -1850,11 +1844,21 @@ sfz::Region::Connection& sfz::Region::getOrCreateConnection(const ModKey& source sfz::Region::Connection* sfz::Region::getConnectionFromCC(int sourceCC, const ModKey& target) { - for (sfz::Region::Connection& conn : connections) { - if (conn.source.id() == sfz::ModId::Controller && conn.target == target) { - auto p = conn.source.parameters(); - if (p.cc == sourceCC) - return &conn; + if (ccModulationIsPerVoice(sourceCC)) { + for (sfz::Region::Connection& conn : connections) { + if (conn.source.id() == sfz::ModId::PerVoiceController && conn.target == target && conn.source.region() == id) { + const auto& p = conn.source.parameters(); + if (p.cc == sourceCC) + return &conn; + } + } + } else { + for (sfz::Region::Connection& conn : connections) { + if (conn.source.id() == sfz::ModId::Controller && conn.target == target) { + const auto& p = conn.source.parameters(); + if (p.cc == sourceCC) + return &conn; + } } } return nullptr; diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 079835f37..d6bfc670d 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -210,6 +210,78 @@ inline CXX14_CONSTEXPR uint8_t offsetAndClampKey(uint8_t key, int offset) return clamp(static_cast(offsetKey), 0, 127); } +enum ExtendedCCs { + pitchBend = 128, + channelAftertouch, + polyphonicAftertouch, + noteOnVelocity, + noteOffVelocity, + keyboardNoteNumber, + keyboardNoteGate, + unipolarRandom, + bipolarRandom, + alternate, + extendedCCupperBound +}; + +enum AriaExtendedCCs { + keydelta = 140, + absoluteKeydelta, + ariaCCupperBound +}; + +/** + * @brief Check if a CC is an ARIA-defined extended CC + * + * @param cc + * @return bool + */ +inline CXX14_CONSTEXPR bool isAriaExtendedCC(int cc) { + if (cc >= AriaExtendedCCs::keydelta && cc < AriaExtendedCCs::ariaCCupperBound) + return true; + + return false; +} + +/** + * @brief Check if a CC is an extended CC (ARIA or not) + * + * @param cc + * @return bool + */ +inline CXX14_CONSTEXPR bool isExtendedCC(int cc) { + if (isAriaExtendedCC(cc)) + return true; + + if (cc >= ExtendedCCs::pitchBend && cc < ExtendedCCs::extendedCCupperBound) + return true; + + return false; +} + +/** + * @brief Check if a CC applies per voice modulation + * + * @param cc + * @return bool + */ +inline CXX14_CONSTEXPR bool ccModulationIsPerVoice(int cc) { + switch (cc) { + case ExtendedCCs::noteOnVelocity: // fallthrough + case ExtendedCCs::noteOffVelocity: // fallthrough + case ExtendedCCs::keyboardNoteNumber: // fallthrough + case ExtendedCCs::keyboardNoteGate: // fallthrough + case ExtendedCCs::unipolarRandom: // fallthrough + case ExtendedCCs::bipolarRandom: // fallthrough + case ExtendedCCs::alternate: + case AriaExtendedCCs::keydelta: + case AriaExtendedCCs::absoluteKeydelta: + return true; + } + + return false; +} + namespace literals { inline float operator""_norm(unsigned long long int value) { diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 6380e26d8..a6541fb31 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -401,7 +401,7 @@ void Voice::Impl::updateExtendedCCValues() noexcept extendedCCValues_.bipolar = midiState.getCCValue(ExtendedCCs::bipolarRandom); extendedCCValues_.alternate = midiState.getCCValue(ExtendedCCs::alternate); extendedCCValues_.noteGate = midiState.getCCValue(ExtendedCCs::keyboardNoteGate); - extendedCCValues_.keydelta = midiState.getCCValue(ExtendedCCs::keydelta); + extendedCCValues_.keydelta = midiState.getCCValue(AriaExtendedCCs::keydelta); } bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept diff --git a/src/sfizz/modulations/sources/Controller.cpp b/src/sfizz/modulations/sources/Controller.cpp index a5d7db93d..fa461f425 100644 --- a/src/sfizz/modulations/sources/Controller.cpp +++ b/src/sfizz/modulations/sources/Controller.cpp @@ -170,14 +170,14 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId voiceI canShortcut = true; break; } - case ExtendedCCs::keydelta: { + case AriaExtendedCCs::keydelta: { const auto voice = impl_->voiceManager_->getVoiceById(voiceId); const float fillValue = voice ? voice->getExtendedCCValues().keydelta : 0.0f; sfz::fill(buffer, quantize(fillValue)); canShortcut = true; break; } - case ExtendedCCs::absoluteKeydelta: { + case AriaExtendedCCs::absoluteKeydelta: { const auto voice = impl_->voiceManager_->getVoiceById(voiceId); const float fillValue = voice ? std::abs(voice->getExtendedCCValues().keydelta) : 0.0f; sfz::fill(buffer, quantize(fillValue)); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index db17ba1dc..f2a709422 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -36,6 +36,7 @@ set(SFIZZ_TEST_SOURCES PolyphonyT.cpp RegionActivationT.cpp RegionValueComputationsT.cpp + SfzHelpersT.cpp # If we're tweaking the curves this kind of tests does not make sense # Use integration tests with comparison curves # ADSREnvelopeT.cpp diff --git a/tests/SfzHelpersT.cpp b/tests/SfzHelpersT.cpp new file mode 100644 index 000000000..f704318ae --- /dev/null +++ b/tests/SfzHelpersT.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "catch2/catch.hpp" +#include "sfizz/SfzHelpers.h" +#include + +template +void checkAllCCs(const std::set& validCCs, F&& check) +{ + for (int cc = 0; cc < sfz::config::numCCs; ++cc) { + INFO("The number is " << cc); + if (validCCs.find(cc) != validCCs.end()) + REQUIRE(check(cc)); + else + REQUIRE_FALSE(check(cc)); + } +} + +TEST_CASE("[CC] Extended CCs") +{ + std::set supportedExtendedCCs { 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 140, 141}; + checkAllCCs(supportedExtendedCCs, sfz::isExtendedCC); +} + +TEST_CASE("[CC] ARIA Extended CCs") +{ + std::set supportedAriaExtendedCCs { 140, 141 }; + checkAllCCs(supportedAriaExtendedCCs, sfz::isAriaExtendedCC); +} + +TEST_CASE("[CC] Per-Voice Extended CCs") +{ + std::set perVoiceSupportedCCs { 131, 132, 133, 134, 135, 136, 137, 140, 141 }; + checkAllCCs(perVoiceSupportedCCs, sfz::ccModulationIsPerVoice); +} +