Fix modulation matrix when CC modulations are per voice
This commit is contained in:
parent
d470f7004e
commit
507c61d458
9 changed files with 142 additions and 55 deletions
|
|
@ -18,21 +18,6 @@
|
||||||
|
|
||||||
namespace sfz {
|
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 {
|
namespace config {
|
||||||
constexpr float defaultSampleRate { 48000 };
|
constexpr float defaultSampleRate { 48000 };
|
||||||
constexpr float maxSampleRate { 192000 };
|
constexpr float maxSampleRate { 192000 };
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,6 @@
|
||||||
|
|
||||||
namespace sfz {
|
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 {
|
namespace config {
|
||||||
constexpr float defaultSampleRate { 48000 };
|
constexpr float defaultSampleRate { 48000 };
|
||||||
constexpr float maxSampleRate { 192000 };
|
constexpr float maxSampleRate { 192000 };
|
||||||
|
|
|
||||||
|
|
@ -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::unipolarRandom, unipolarDist(Random::randomGenerator));
|
||||||
ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator));
|
ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator));
|
||||||
ccEvent(delay, ExtendedCCs::keyboardNoteGate, activeNotes > 0 ? 1.0f : 0.0f);
|
ccEvent(delay, ExtendedCCs::keyboardNoteGate, activeNotes > 0 ? 1.0f : 0.0f);
|
||||||
ccEvent(delay, ExtendedCCs::keydelta, keydelta);
|
ccEvent(delay, AriaExtendedCCs::keydelta, keydelta);
|
||||||
ccEvent(delay, ExtendedCCs::absoluteKeydelta, std::abs(keydelta));
|
ccEvent(delay, AriaExtendedCCs::absoluteKeydelta, std::abs(keydelta));
|
||||||
activeNotes++;
|
activeNotes++;
|
||||||
|
|
||||||
ccEvent(delay, ExtendedCCs::alternate, alternate);
|
ccEvent(delay, ExtendedCCs::alternate, alternate);
|
||||||
|
|
|
||||||
|
|
@ -1690,8 +1690,13 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec<float> spec,
|
||||||
// search an existing connection of same CC number and target
|
// search an existing connection of same CC number and target
|
||||||
// if it exists, modify, otherwise create
|
// if it exists, modify, otherwise create
|
||||||
auto it = std::find_if(connections.begin(), connections.end(),
|
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 &&
|
return x.source.id() == ModId::Controller &&
|
||||||
x.source.parameters().cc == ccNumber &&
|
x.source.parameters().cc == ccNumber &&
|
||||||
x.target == target;
|
x.target == target;
|
||||||
|
|
@ -1730,22 +1735,11 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec<float> spec,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (p.cc) {
|
if (ccModulationIsPerVoice(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:
|
|
||||||
conn->source = ModKey(ModId::PerVoiceController, id, p);
|
conn->source = ModKey(ModId::PerVoiceController, id, p);
|
||||||
break;
|
} else {
|
||||||
default:
|
|
||||||
conn->source = ModKey(ModId::Controller, {}, p);
|
conn->source = ModKey(ModId::Controller, {}, p);
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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)
|
sfz::Region::Connection* sfz::Region::getConnectionFromCC(int sourceCC, const ModKey& target)
|
||||||
{
|
{
|
||||||
for (sfz::Region::Connection& conn : connections) {
|
if (ccModulationIsPerVoice(sourceCC)) {
|
||||||
if (conn.source.id() == sfz::ModId::Controller && conn.target == target) {
|
for (sfz::Region::Connection& conn : connections) {
|
||||||
auto p = conn.source.parameters();
|
if (conn.source.id() == sfz::ModId::PerVoiceController && conn.target == target && conn.source.region() == id) {
|
||||||
if (p.cc == sourceCC)
|
const auto& p = conn.source.parameters();
|
||||||
return &conn;
|
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;
|
return nullptr;
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,78 @@ inline CXX14_CONSTEXPR uint8_t offsetAndClampKey(uint8_t key, int offset)
|
||||||
return clamp<uint8_t>(static_cast<uint8_t>(offsetKey), 0, 127);
|
return clamp<uint8_t>(static_cast<uint8_t>(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 {
|
namespace literals {
|
||||||
inline float operator""_norm(unsigned long long int value)
|
inline float operator""_norm(unsigned long long int value)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -401,7 +401,7 @@ void Voice::Impl::updateExtendedCCValues() noexcept
|
||||||
extendedCCValues_.bipolar = midiState.getCCValue(ExtendedCCs::bipolarRandom);
|
extendedCCValues_.bipolar = midiState.getCCValue(ExtendedCCs::bipolarRandom);
|
||||||
extendedCCValues_.alternate = midiState.getCCValue(ExtendedCCs::alternate);
|
extendedCCValues_.alternate = midiState.getCCValue(ExtendedCCs::alternate);
|
||||||
extendedCCValues_.noteGate = midiState.getCCValue(ExtendedCCs::keyboardNoteGate);
|
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
|
bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept
|
||||||
|
|
|
||||||
|
|
@ -170,14 +170,14 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceI
|
||||||
canShortcut = true;
|
canShortcut = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ExtendedCCs::keydelta: {
|
case AriaExtendedCCs::keydelta: {
|
||||||
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
const float fillValue = voice ? voice->getExtendedCCValues().keydelta : 0.0f;
|
const float fillValue = voice ? voice->getExtendedCCValues().keydelta : 0.0f;
|
||||||
sfz::fill(buffer, quantize(fillValue));
|
sfz::fill(buffer, quantize(fillValue));
|
||||||
canShortcut = true;
|
canShortcut = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ExtendedCCs::absoluteKeydelta: {
|
case AriaExtendedCCs::absoluteKeydelta: {
|
||||||
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
const auto voice = impl_->voiceManager_->getVoiceById(voiceId);
|
||||||
const float fillValue = voice ? std::abs(voice->getExtendedCCValues().keydelta) : 0.0f;
|
const float fillValue = voice ? std::abs(voice->getExtendedCCValues().keydelta) : 0.0f;
|
||||||
sfz::fill(buffer, quantize(fillValue));
|
sfz::fill(buffer, quantize(fillValue));
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ set(SFIZZ_TEST_SOURCES
|
||||||
PolyphonyT.cpp
|
PolyphonyT.cpp
|
||||||
RegionActivationT.cpp
|
RegionActivationT.cpp
|
||||||
RegionValueComputationsT.cpp
|
RegionValueComputationsT.cpp
|
||||||
|
SfzHelpersT.cpp
|
||||||
# If we're tweaking the curves this kind of tests does not make sense
|
# If we're tweaking the curves this kind of tests does not make sense
|
||||||
# Use integration tests with comparison curves
|
# Use integration tests with comparison curves
|
||||||
# ADSREnvelopeT.cpp
|
# ADSREnvelopeT.cpp
|
||||||
|
|
|
||||||
40
tests/SfzHelpersT.cpp
Normal file
40
tests/SfzHelpersT.cpp
Normal file
|
|
@ -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 <set>
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
void checkAllCCs(const std::set<int>& 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<int> 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<int> supportedAriaExtendedCCs { 140, 141 };
|
||||||
|
checkAllCCs(supportedAriaExtendedCCs, sfz::isAriaExtendedCC);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[CC] Per-Voice Extended CCs")
|
||||||
|
{
|
||||||
|
std::set<int> perVoiceSupportedCCs { 131, 132, 133, 134, 135, 136, 137, 140, 141 };
|
||||||
|
checkAllCCs(perVoiceSupportedCCs, sfz::ccModulationIsPerVoice);
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue