Add modulation matrix
This commit is contained in:
parent
fe077d56a9
commit
675d7f3fc5
21 changed files with 1291 additions and 57 deletions
5
dpf.mk
5
dpf.mk
|
|
@ -60,6 +60,11 @@ SFIZZ_SOURCES = \
|
||||||
src/sfizz/Curve.cpp \
|
src/sfizz/Curve.cpp \
|
||||||
src/sfizz/effects/Apan.cpp \
|
src/sfizz/effects/Apan.cpp \
|
||||||
src/sfizz/Effects.cpp \
|
src/sfizz/Effects.cpp \
|
||||||
|
src/sfizz/modulations/ModId.cpp \
|
||||||
|
src/sfizz/modulations/ModKey.cpp \
|
||||||
|
src/sfizz/modulations/ModKeyHash.cpp \
|
||||||
|
src/sfizz/modulations/ModMatrix.cpp \
|
||||||
|
src/sfizz/modulations/sources/Controller.cpp \
|
||||||
src/sfizz/effects/Compressor.cpp \
|
src/sfizz/effects/Compressor.cpp \
|
||||||
src/sfizz/effects/Disto.cpp \
|
src/sfizz/effects/Disto.cpp \
|
||||||
src/sfizz/effects/Eq.cpp \
|
src/sfizz/effects/Eq.cpp \
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@ set (SFIZZ_HEADERS
|
||||||
sfizz/Debug.h
|
sfizz/Debug.h
|
||||||
sfizz/utility/SpinMutex.h
|
sfizz/utility/SpinMutex.h
|
||||||
sfizz/utility/SpinMutex.cpp
|
sfizz/utility/SpinMutex.cpp
|
||||||
|
sfizz/modulations/ModId.h
|
||||||
|
sfizz/modulations/ModKey.h
|
||||||
|
sfizz/modulations/ModKeyHash.h
|
||||||
|
sfizz/modulations/ModMatrix.h
|
||||||
|
sfizz/modulations/ModGenerator.h
|
||||||
|
sfizz/modulations/sources/Controller.h
|
||||||
sfizz/effects/impl/ResonantArray.h
|
sfizz/effects/impl/ResonantArray.h
|
||||||
sfizz/effects/impl/ResonantArrayAVX.h
|
sfizz/effects/impl/ResonantArrayAVX.h
|
||||||
sfizz/effects/impl/ResonantArraySSE.h
|
sfizz/effects/impl/ResonantArraySSE.h
|
||||||
|
|
@ -128,6 +134,11 @@ set (SFIZZ_SOURCES
|
||||||
sfizz/RTSemaphore.cpp
|
sfizz/RTSemaphore.cpp
|
||||||
sfizz/Panning.cpp
|
sfizz/Panning.cpp
|
||||||
sfizz/Effects.cpp
|
sfizz/Effects.cpp
|
||||||
|
sfizz/modulations/ModId.cpp
|
||||||
|
sfizz/modulations/ModKey.cpp
|
||||||
|
sfizz/modulations/ModKeyHash.cpp
|
||||||
|
sfizz/modulations/ModMatrix.cpp
|
||||||
|
sfizz/modulations/sources/Controller.cpp
|
||||||
sfizz/effects/Nothing.cpp
|
sfizz/effects/Nothing.cpp
|
||||||
sfizz/effects/Filter.cpp
|
sfizz/effects/Filter.cpp
|
||||||
sfizz/effects/Eq.cpp
|
sfizz/effects/Eq.cpp
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "Opcode.h"
|
#include "Opcode.h"
|
||||||
#include "StringViewHelpers.h"
|
#include "StringViewHelpers.h"
|
||||||
#include "ModifierHelpers.h"
|
#include "ModifierHelpers.h"
|
||||||
|
#include "modulations/ModId.h"
|
||||||
#include "absl/strings/str_replace.h"
|
#include "absl/strings/str_replace.h"
|
||||||
#include "absl/strings/str_cat.h"
|
#include "absl/strings/str_cat.h"
|
||||||
#include "absl/algorithm/container.h"
|
#include "absl/algorithm/container.h"
|
||||||
|
|
@ -378,35 +379,35 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
||||||
setValueFromOpcode(opcode, volume, Default::volumeRange);
|
setValueFromOpcode(opcode, volume, Default::volumeRange);
|
||||||
break;
|
break;
|
||||||
case_any_ccN("volume"): // also gain
|
case_any_ccN("volume"): // also gain
|
||||||
processGenericCc(opcode, Default::volumeCCRange, &modifiers[Mod::volume]);
|
processGenericCc(opcode, Default::volumeCCRange, &modifiers[Mod::volume], ModKey::createNXYZ(ModId::Volume, id));
|
||||||
break;
|
break;
|
||||||
case hash("amplitude"):
|
case hash("amplitude"):
|
||||||
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
||||||
amplitude = normalizePercents(*value);
|
amplitude = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case_any_ccN("amplitude"):
|
case_any_ccN("amplitude"):
|
||||||
processGenericCc(opcode, Default::amplitudeRange, &modifiers[Mod::amplitude]);
|
processGenericCc(opcode, Default::amplitudeRange, &modifiers[Mod::amplitude], ModKey::createNXYZ(ModId::Amplitude, id));
|
||||||
break;
|
break;
|
||||||
case hash("pan"):
|
case hash("pan"):
|
||||||
if (auto value = readOpcode(opcode.value, Default::panRange))
|
if (auto value = readOpcode(opcode.value, Default::panRange))
|
||||||
pan = normalizePercents(*value);
|
pan = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case_any_ccN("pan"):
|
case_any_ccN("pan"):
|
||||||
processGenericCc(opcode, Default::panCCRange, &modifiers[Mod::pan]);
|
processGenericCc(opcode, Default::panCCRange, &modifiers[Mod::pan], ModKey::createNXYZ(ModId::Pan, id));
|
||||||
break;
|
break;
|
||||||
case hash("position"):
|
case hash("position"):
|
||||||
if (auto value = readOpcode(opcode.value, Default::positionRange))
|
if (auto value = readOpcode(opcode.value, Default::positionRange))
|
||||||
position = normalizePercents(*value);
|
position = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case_any_ccN("position"):
|
case_any_ccN("position"):
|
||||||
processGenericCc(opcode, Default::positionCCRange, &modifiers[Mod::position]);
|
processGenericCc(opcode, Default::positionCCRange, &modifiers[Mod::position], ModKey::createNXYZ(ModId::Position, id));
|
||||||
break;
|
break;
|
||||||
case hash("width"):
|
case hash("width"):
|
||||||
if (auto value = readOpcode(opcode.value, Default::widthRange))
|
if (auto value = readOpcode(opcode.value, Default::widthRange))
|
||||||
width = normalizePercents(*value);
|
width = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case_any_ccN("width"):
|
case_any_ccN("width"):
|
||||||
processGenericCc(opcode, Default::widthCCRange, &modifiers[Mod::width]);
|
processGenericCc(opcode, Default::widthCCRange, &modifiers[Mod::width], ModKey::createNXYZ(ModId::Width, id));
|
||||||
break;
|
break;
|
||||||
case hash("amp_keycenter"):
|
case hash("amp_keycenter"):
|
||||||
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
|
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
|
||||||
|
|
@ -770,7 +771,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
||||||
setValueFromOpcode(opcode, tune, Default::tuneRange);
|
setValueFromOpcode(opcode, tune, Default::tuneRange);
|
||||||
break;
|
break;
|
||||||
case_any_ccN("pitch"): // also tune
|
case_any_ccN("pitch"): // also tune
|
||||||
processGenericCc(opcode, Default::tuneCCRange, &modifiers[Mod::pitch]);
|
processGenericCc(opcode, Default::tuneCCRange, &modifiers[Mod::pitch], ModKey::createNXYZ(ModId::Pitch, id));
|
||||||
break;
|
break;
|
||||||
case hash("bend_up"): // also bendup
|
case hash("bend_up"): // also bendup
|
||||||
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
|
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
|
||||||
|
|
@ -924,7 +925,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sfz::Region::processGenericCc(const Opcode& opcode, Range<float> range, CCMap<Modifier> *ccMap)
|
bool sfz::Region::processGenericCc(const Opcode& opcode, Range<float> range, CCMap<Modifier> *ccMap, const ModKey& target)
|
||||||
{
|
{
|
||||||
if (!opcode.isAnyCcN())
|
if (!opcode.isAnyCcN())
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -933,6 +934,7 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, Range<float> range, CCM
|
||||||
if (ccNumber >= config::numCCs)
|
if (ccNumber >= config::numCCs)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// TODO obsolete after implementing mod matrix
|
||||||
if (ccMap) {
|
if (ccMap) {
|
||||||
Modifier& modifier = (*ccMap)[ccNumber];
|
Modifier& modifier = (*ccMap)[ccNumber];
|
||||||
switch (opcode.category) {
|
switch (opcode.category) {
|
||||||
|
|
@ -955,7 +957,53 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, Range<float> range, CCM
|
||||||
assert(false);
|
assert(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
return x.first.id() == ModId::Controller &&
|
||||||
|
x.first.parameters().cc == ccNumber &&
|
||||||
|
x.second == target;
|
||||||
|
});
|
||||||
|
|
||||||
|
Connection *conn;
|
||||||
|
if (it != connections.end())
|
||||||
|
conn = &*it;
|
||||||
|
else {
|
||||||
|
connections.emplace_back();
|
||||||
|
conn = &connections.back();
|
||||||
|
conn->first = ModKey::createCC(ccNumber, 0, 0, 0, 0);
|
||||||
|
conn->second = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
ModKey::Parameters p = conn->first.parameters();
|
||||||
|
switch (opcode.category) {
|
||||||
|
case kOpcodeOnCcN:
|
||||||
|
setValueFromOpcode(opcode, p.value, range);
|
||||||
|
break;
|
||||||
|
case kOpcodeCurveCcN:
|
||||||
|
setValueFromOpcode(opcode, p.curve, Default::curveCCRange);
|
||||||
|
break;
|
||||||
|
case kOpcodeStepCcN:
|
||||||
|
{
|
||||||
|
const Range<float> stepCCRange { 0.0f, std::max(std::abs(range.getStart()), std::abs(range.getEnd())) };
|
||||||
|
setValueFromOpcode(opcode, p.step, stepCCRange);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case kOpcodeSmoothCcN:
|
||||||
|
setValueFromOpcode(opcode, p.smooth, Default::smoothCCRange);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
conn->first = ModKey(ModId::Controller, {}, p);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
#include "FileId.h"
|
#include "FileId.h"
|
||||||
#include "NumericId.h"
|
#include "NumericId.h"
|
||||||
#include "Modifiers.h"
|
#include "Modifiers.h"
|
||||||
|
#include "modulations/ModKey.h"
|
||||||
#include "absl/types/optional.h"
|
#include "absl/types/optional.h"
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -241,10 +242,11 @@ struct Region {
|
||||||
* @param opcode
|
* @param opcode
|
||||||
* @param range
|
* @param range
|
||||||
* @param ccMap
|
* @param ccMap
|
||||||
|
* @param target
|
||||||
* @return true if the opcode was properly read and stored.
|
* @return true if the opcode was properly read and stored.
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
bool processGenericCc(const Opcode& opcode, Range<float> range, CCMap<Modifier> *ccMap);
|
bool processGenericCc(const Opcode& opcode, Range<float> range, CCMap<Modifier> *ccMap, const ModKey& target);
|
||||||
|
|
||||||
void offsetAllKeys(int offset) noexcept;
|
void offsetAllKeys(int offset) noexcept;
|
||||||
|
|
||||||
|
|
@ -374,6 +376,10 @@ struct Region {
|
||||||
bool triggerOnCC { false }; // whether the region triggers on CC events or note events
|
bool triggerOnCC { false }; // whether the region triggers on CC events or note events
|
||||||
bool triggerOnNote { true };
|
bool triggerOnNote { true };
|
||||||
|
|
||||||
|
// Modulation matrix connections
|
||||||
|
typedef std::pair<ModKey, ModKey> Connection;
|
||||||
|
std::vector<Connection> connections;
|
||||||
|
|
||||||
// Parent
|
// Parent
|
||||||
RegionSet* parent { nullptr };
|
RegionSet* parent { nullptr };
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include "Wavetables.h"
|
#include "Wavetables.h"
|
||||||
#include "Curve.h"
|
#include "Curve.h"
|
||||||
#include "Tuning.h"
|
#include "Tuning.h"
|
||||||
|
#include "modulations/ModMatrix.h"
|
||||||
#include "absl/types/optional.h"
|
#include "absl/types/optional.h"
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
|
|
@ -33,18 +34,21 @@ struct Resources
|
||||||
WavetablePool wavePool;
|
WavetablePool wavePool;
|
||||||
Tuning tuning;
|
Tuning tuning;
|
||||||
absl::optional<StretchTuning> stretch;
|
absl::optional<StretchTuning> stretch;
|
||||||
|
ModMatrix modMatrix;
|
||||||
|
|
||||||
void setSampleRate(float samplerate)
|
void setSampleRate(float samplerate)
|
||||||
{
|
{
|
||||||
midiState.setSampleRate(samplerate);
|
midiState.setSampleRate(samplerate);
|
||||||
filterPool.setSampleRate(samplerate);
|
filterPool.setSampleRate(samplerate);
|
||||||
eqPool.setSampleRate(samplerate);
|
eqPool.setSampleRate(samplerate);
|
||||||
|
modMatrix.setSampleRate(samplerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSamplesPerBlock(int samplesPerBlock)
|
void setSamplesPerBlock(int samplesPerBlock)
|
||||||
{
|
{
|
||||||
bufferPool.setBufferSize(samplesPerBlock);
|
bufferPool.setBufferSize(samplesPerBlock);
|
||||||
midiState.setSamplesPerBlock(samplesPerBlock);
|
midiState.setSamplesPerBlock(samplesPerBlock);
|
||||||
|
modMatrix.setSamplesPerBlock(samplesPerBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear()
|
void clear()
|
||||||
|
|
@ -54,6 +58,7 @@ struct Resources
|
||||||
wavePool.clearFileWaves();
|
wavePool.clearFileWaves();
|
||||||
logger.clear();
|
logger.clear();
|
||||||
midiState.reset();
|
midiState.reset();
|
||||||
|
modMatrix.clear();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@
|
||||||
#include "ModifierHelpers.h"
|
#include "ModifierHelpers.h"
|
||||||
#include "ScopedFTZ.h"
|
#include "ScopedFTZ.h"
|
||||||
#include "StringViewHelpers.h"
|
#include "StringViewHelpers.h"
|
||||||
|
#include "modulations/ModMatrix.h"
|
||||||
|
#include "modulations/ModKey.h"
|
||||||
|
#include "modulations/ModId.h"
|
||||||
|
#include "modulations/sources/Controller.h"
|
||||||
#include "pugixml.hpp"
|
#include "pugixml.hpp"
|
||||||
#include "absl/algorithm/container.h"
|
#include "absl/algorithm/container.h"
|
||||||
#include "absl/memory/memory.h"
|
#include "absl/memory/memory.h"
|
||||||
|
|
@ -35,6 +39,9 @@ sfz::Synth::Synth(int numVoices)
|
||||||
effectFactory.registerStandardEffectTypes();
|
effectFactory.registerStandardEffectTypes();
|
||||||
effectBuses.reserve(5); // sufficient room for main and fx1-4
|
effectBuses.reserve(5); // sufficient room for main and fx1-4
|
||||||
resetVoices(numVoices);
|
resetVoices(numVoices);
|
||||||
|
|
||||||
|
// modulation sources
|
||||||
|
genController.reset(new ControllerSource(resources));
|
||||||
}
|
}
|
||||||
|
|
||||||
sfz::Synth::~Synth()
|
sfz::Synth::~Synth()
|
||||||
|
|
@ -570,6 +577,8 @@ void sfz::Synth::finalizeSfzLoad()
|
||||||
settingsPerVoice.maxModifiers = maxModifiers;
|
settingsPerVoice.maxModifiers = maxModifiers;
|
||||||
|
|
||||||
applySettingsPerVoice();
|
applySettingsPerVoice();
|
||||||
|
|
||||||
|
setupModMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sfz::Synth::loadScalaFile(const fs::path& path)
|
bool sfz::Synth::loadScalaFile(const fs::path& path)
|
||||||
|
|
@ -717,6 +726,8 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModMatrix& mm = resources.modMatrix;
|
||||||
|
|
||||||
activeVoices = 0;
|
activeVoices = 0;
|
||||||
{ // Main render block
|
{ // Main render block
|
||||||
ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration };
|
||||||
|
|
@ -724,6 +735,8 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
tempMixSpan->fill(0.0f);
|
tempMixSpan->fill(0.0f);
|
||||||
resources.filePool.cleanupPromises();
|
resources.filePool.cleanupPromises();
|
||||||
|
|
||||||
|
mm.beginCycle(numFrames);
|
||||||
|
|
||||||
// Ramp out whatever is in the buffer at this point; should only be killed voice data
|
// Ramp out whatever is in the buffer at this point; should only be killed voice data
|
||||||
linearRamp<float>(*rampSpan, 1.0f, -1.0f / static_cast<float>(numFrames));
|
linearRamp<float>(*rampSpan, 1.0f, -1.0f / static_cast<float>(numFrames));
|
||||||
for (size_t i = 0, n = effectBuses.size(); i < n; ++i) {
|
for (size_t i = 0, n = effectBuses.size(); i < n; ++i) {
|
||||||
|
|
@ -736,6 +749,8 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
if (voice->isFree())
|
if (voice->isFree())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
mm.beginVoice(voice->getId());
|
||||||
|
|
||||||
activeVoices++;
|
activeVoices++;
|
||||||
renderVoiceToOutputs(*voice, *tempSpan);
|
renderVoiceToOutputs(*voice, *tempSpan);
|
||||||
callbackBreakdown.data += voice->getLastDataDuration();
|
callbackBreakdown.data += voice->getLastDataDuration();
|
||||||
|
|
@ -1335,6 +1350,52 @@ void sfz::Synth::applySettingsPerVoice()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::setupModMatrix()
|
||||||
|
{
|
||||||
|
ModMatrix& mm = resources.modMatrix;
|
||||||
|
|
||||||
|
for (const RegionPtr& region : regions) {
|
||||||
|
for (const Region::Connection& conn : region->connections) {
|
||||||
|
ModGenerator* gen = nullptr;
|
||||||
|
|
||||||
|
switch (conn.first.id()) {
|
||||||
|
case ModId::Controller:
|
||||||
|
gen = genController.get();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBG("[sfizz] Have unknown type of source generator");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(gen);
|
||||||
|
if (!gen)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ModMatrix::SourceId source = mm.registerSource(conn.first, *gen);
|
||||||
|
ModMatrix::TargetId target = mm.registerTarget(conn.second);
|
||||||
|
|
||||||
|
ASSERT(source);
|
||||||
|
if (!source) {
|
||||||
|
DBG("[sfizz] Failed to register modulation source");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(target);
|
||||||
|
if (!source) {
|
||||||
|
DBG("[sfizz] Failed to register modulation target");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mm.connect(source, target)) {
|
||||||
|
DBG("[sfizz] Failed to connect modulation source and target");
|
||||||
|
ASSERTFALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mm.init();
|
||||||
|
}
|
||||||
|
|
||||||
void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept
|
void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept
|
||||||
{
|
{
|
||||||
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
|
const std::lock_guard<SpinMutex> disableCallback { callbackGuard };
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
class ControllerSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief This class is the core of the sfizz library. In C++ it is the main point
|
* @brief This class is the core of the sfizz library. In C++ it is the main point
|
||||||
* of entry and in C the interface basically maps the functions of the class into
|
* of entry and in C the interface basically maps the functions of the class into
|
||||||
|
|
@ -677,6 +679,11 @@ private:
|
||||||
*/
|
*/
|
||||||
void applySettingsPerVoice();
|
void applySettingsPerVoice();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establish all connections of the modulation matrix.
|
||||||
|
*/
|
||||||
|
void setupModMatrix();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Render the voice to its designated outputs and effect busses.
|
* @brief Render the voice to its designated outputs and effect busses.
|
||||||
*
|
*
|
||||||
|
|
@ -758,6 +765,9 @@ private:
|
||||||
int noteOffset { 0 };
|
int noteOffset { 0 };
|
||||||
int octaveOffset { 0 };
|
int octaveOffset { 0 };
|
||||||
|
|
||||||
|
// Modulation source generators
|
||||||
|
std::unique_ptr<ControllerSource> genController;
|
||||||
|
|
||||||
// Settings per voice
|
// Settings per voice
|
||||||
struct SettingsPerVoice {
|
struct SettingsPerVoice {
|
||||||
size_t maxFilters { 0 };
|
size_t maxFilters { 0 };
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
#include "Panning.h"
|
#include "Panning.h"
|
||||||
#include "SfzHelpers.h"
|
#include "SfzHelpers.h"
|
||||||
|
#include "modulations/ModId.h"
|
||||||
|
#include "modulations/ModKey.h"
|
||||||
|
#include "modulations/ModMatrix.h"
|
||||||
#include "Interpolators.h"
|
#include "Interpolators.h"
|
||||||
#include "absl/algorithm/container.h"
|
#include "absl/algorithm/container.h"
|
||||||
|
|
||||||
|
|
@ -164,6 +167,8 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
|
||||||
smoother.setSmoothing(mod.data.smooth, sampleRate);
|
smoother.setSmoothing(mod.data.smooth, sampleRate);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resources.modMatrix.initVoice(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int sfz::Voice::getCurrentSampleQuality() const noexcept
|
int sfz::Voice::getCurrentSampleQuality() const noexcept
|
||||||
|
|
@ -374,30 +379,24 @@ void sfz::Voice::amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept
|
||||||
{
|
{
|
||||||
const auto numSamples = modulationSpan.size();
|
const auto numSamples = modulationSpan.size();
|
||||||
|
|
||||||
auto tempSpan = resources.bufferPool.getBuffer(numSamples);
|
ModMatrix& mm = resources.modMatrix;
|
||||||
if (!tempSpan)
|
const ModKey volumeKey = ModKey::createNXYZ(ModId::Volume, region->getId());
|
||||||
return;
|
const ModKey amplitudeKey = ModKey::createNXYZ(ModId::Amplitude, region->getId());
|
||||||
|
|
||||||
// AmpEG envelope
|
// AmpEG envelope
|
||||||
egEnvelope.getBlock(modulationSpan);
|
egEnvelope.getBlock(modulationSpan);
|
||||||
|
|
||||||
// Amplitude envelope
|
// Amplitude envelope
|
||||||
applyGain1<float>(baseGain, modulationSpan);
|
if (float* mod = mm.getModulationByKey(amplitudeKey)) {
|
||||||
forEachWithSmoother(Mod::amplitude, [&](const CCData<Modifier>& mod, Smoother& smoother) {
|
for (size_t i = 0; i < numSamples; ++i)
|
||||||
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
|
modulationSpan[i] *= normalizePercents(mod[i]);
|
||||||
smoother.process(*tempSpan, *tempSpan);
|
}
|
||||||
applyGain<float>(*tempSpan, modulationSpan);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Volume envelope
|
// Volume envelope
|
||||||
applyGain1<float>(db2mag(baseVolumedB), modulationSpan);
|
if (float* mod = mm.getModulationByKey(volumeKey)) {
|
||||||
forEachWithSmoother(Mod::volume, [&](const CCData<Modifier>& mod, Smoother& smoother) {
|
for (size_t i = 0; i < numSamples; ++i)
|
||||||
multiplicativeModifier(resources, *tempSpan, mod, [](float x) {
|
modulationSpan[i] *= db2mag(mod[i]);
|
||||||
return db2mag(x);
|
}
|
||||||
});
|
|
||||||
smoother.process(*tempSpan, *tempSpan);
|
|
||||||
applyGain<float>(*tempSpan, modulationSpan);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Smooth the gain transitions
|
// Smooth the gain transitions
|
||||||
gainSmoother.process(modulationSpan, modulationSpan);
|
gainSmoother.process(modulationSpan, modulationSpan);
|
||||||
|
|
@ -442,20 +441,21 @@ void sfz::Voice::panStageMono(AudioSpan<float> buffer) noexcept
|
||||||
const auto rightBuffer = buffer.getSpan(1);
|
const auto rightBuffer = buffer.getSpan(1);
|
||||||
|
|
||||||
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
|
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
auto tempSpan = resources.bufferPool.getBuffer(numSamples);
|
if (!modulationSpan)
|
||||||
if (!modulationSpan || !tempSpan)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
ModMatrix& mm = resources.modMatrix;
|
||||||
|
const ModKey panKey = ModKey::createNXYZ(ModId::Pan, region->getId());
|
||||||
|
|
||||||
// Prepare for stereo output
|
// Prepare for stereo output
|
||||||
copy<float>(leftBuffer, rightBuffer);
|
copy<float>(leftBuffer, rightBuffer);
|
||||||
|
|
||||||
// Apply panning
|
// Apply panning
|
||||||
fill(*modulationSpan, region->pan);
|
fill(*modulationSpan, region->pan);
|
||||||
forEachWithSmoother(Mod::pan, [&](const CCData<Modifier>& mod, Smoother& smoother) {
|
if (float* mod = mm.getModulationByKey(panKey)) {
|
||||||
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
|
for (size_t i = 0; i < numSamples; ++i)
|
||||||
smoother.process(*tempSpan, *tempSpan);
|
(*modulationSpan)[i] += normalizePercents(mod[i]);
|
||||||
add<float>(*tempSpan, *modulationSpan);
|
}
|
||||||
});
|
|
||||||
pan(*modulationSpan, leftBuffer, rightBuffer);
|
pan(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -467,34 +467,35 @@ void sfz::Voice::panStageStereo(AudioSpan<float> buffer) noexcept
|
||||||
const auto rightBuffer = buffer.getSpan(1);
|
const auto rightBuffer = buffer.getSpan(1);
|
||||||
|
|
||||||
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
|
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
auto tempSpan = resources.bufferPool.getBuffer(numSamples);
|
if (!modulationSpan)
|
||||||
if (!modulationSpan || !tempSpan)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
ModMatrix& mm = resources.modMatrix;
|
||||||
|
const ModKey panKey = ModKey::createNXYZ(ModId::Pan, region->getId());
|
||||||
|
const ModKey widthKey = ModKey::createNXYZ(ModId::Width, region->getId());
|
||||||
|
const ModKey positionKey = ModKey::createNXYZ(ModId::Position, region->getId());
|
||||||
|
|
||||||
// Apply panning
|
// Apply panning
|
||||||
fill(*modulationSpan, region->pan);
|
fill(*modulationSpan, region->pan);
|
||||||
forEachWithSmoother(Mod::pan, [&](const CCData<Modifier>& mod, Smoother& smoother) {
|
if (float* mod = mm.getModulationByKey(panKey)) {
|
||||||
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
|
for (size_t i = 0; i < numSamples; ++i)
|
||||||
smoother.process(*tempSpan, *tempSpan);
|
(*modulationSpan)[i] += normalizePercents(mod[i]);
|
||||||
add<float>(*tempSpan, *modulationSpan);
|
}
|
||||||
});
|
|
||||||
pan(*modulationSpan, leftBuffer, rightBuffer);
|
pan(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
|
||||||
// Apply the width/position process
|
// Apply the width/position process
|
||||||
fill(*modulationSpan, region->width);
|
fill(*modulationSpan, region->width);
|
||||||
forEachWithSmoother(Mod::width, [&](const CCData<Modifier>& mod, Smoother& smoother) {
|
if (float* mod = mm.getModulationByKey(widthKey)) {
|
||||||
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
|
for (size_t i = 0; i < numSamples; ++i)
|
||||||
smoother.process(*tempSpan, *tempSpan);
|
(*modulationSpan)[i] += normalizePercents(mod[i]);
|
||||||
add<float>(*tempSpan, *modulationSpan);
|
}
|
||||||
});
|
|
||||||
width(*modulationSpan, leftBuffer, rightBuffer);
|
width(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
|
||||||
fill(*modulationSpan, region->position);
|
fill(*modulationSpan, region->position);
|
||||||
forEachWithSmoother(Mod::position, [&](const CCData<Modifier>& mod, Smoother& smoother) {
|
if (float* mod = mm.getModulationByKey(positionKey)) {
|
||||||
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
|
for (size_t i = 0; i < numSamples; ++i)
|
||||||
smoother.process(*tempSpan, *tempSpan);
|
(*modulationSpan)[i] += normalizePercents(mod[i]);
|
||||||
add<float>(*tempSpan, *modulationSpan);
|
}
|
||||||
});
|
|
||||||
pan(*modulationSpan, leftBuffer, rightBuffer);
|
pan(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -906,13 +907,13 @@ void sfz::Voice::pitchEnvelope(absl::Span<float> pitchSpan) noexcept
|
||||||
bendSmoother.process(*bends, *bends);
|
bendSmoother.process(*bends, *bends);
|
||||||
applyGain<float>(*bends, pitchSpan);
|
applyGain<float>(*bends, pitchSpan);
|
||||||
|
|
||||||
forEachWithSmoother(Mod::pitch, [&](const CCData<Modifier>& mod, Smoother& smoother) {
|
ModMatrix& mm = resources.modMatrix;
|
||||||
multiplicativeModifier(resources, *bends, mod, [](float x) {
|
const ModKey pitchKey = ModKey::createNXYZ(ModId::Pitch, region->getId());
|
||||||
return centsFactor(x);
|
|
||||||
});
|
if (float* mod = mm.getModulationByKey(pitchKey)) {
|
||||||
smoother.process(*bends, *bends);
|
for (size_t i = 0; i < numFrames; ++i)
|
||||||
applyGain<float>(*bends, pitchSpan);
|
pitchSpan[i] *= centsFactor(mod[i]);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::resetSmoothers() noexcept
|
void sfz::Voice::resetSmoothers() noexcept
|
||||||
|
|
|
||||||
52
src/sfizz/modulations/ModGenerator.h
Normal file
52
src/sfizz/modulations/ModGenerator.h
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../NumericId.h"
|
||||||
|
#include <absl/types/span.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
class ModKey;
|
||||||
|
class Voice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generator for modulation sources
|
||||||
|
*/
|
||||||
|
class ModGenerator {
|
||||||
|
public:
|
||||||
|
virtual ~ModGenerator() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the sample rate
|
||||||
|
*/
|
||||||
|
virtual void setSampleRate(double sampleRate) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the maximum block size
|
||||||
|
*/
|
||||||
|
virtual void setSamplesPerBlock(unsigned count) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the generator.
|
||||||
|
*
|
||||||
|
* @param sourceKey identifier of the source to initialize
|
||||||
|
* @param voiceId the particular voice to initialize, if per-voice
|
||||||
|
*/
|
||||||
|
virtual void init(const ModKey& sourceKey, NumericId<Voice> voiceId) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate a cycle of the modulator
|
||||||
|
*
|
||||||
|
* @param sourceKey source key
|
||||||
|
* @param voiceNum voice number if the generator is per-voice, otherwise undefined
|
||||||
|
* @param buffer output buffer
|
||||||
|
*/
|
||||||
|
virtual void generate(const ModKey& sourceKey, NumericId<Voice> voiceNum, absl::Span<float> buffer) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
54
src/sfizz/modulations/ModId.cpp
Normal file
54
src/sfizz/modulations/ModId.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
// 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 "ModId.h"
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
bool ModIds::isSource(ModId id) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<int>(id) >= static_cast<int>(ModId::_SourcesStart) &&
|
||||||
|
static_cast<int>(id) < static_cast<int>(ModId::_SourcesEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModIds::isTarget(ModId id) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<int>(id) >= static_cast<int>(ModId::_TargetsStart) &&
|
||||||
|
static_cast<int>(id) < static_cast<int>(ModId::_TargetsEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ModIds::flags(ModId id) noexcept
|
||||||
|
{
|
||||||
|
switch (id) {
|
||||||
|
// sources
|
||||||
|
case ModId::Controller:
|
||||||
|
return kModIsPerCycle;
|
||||||
|
case ModId::Envelope:
|
||||||
|
return kModIsPerVoice;
|
||||||
|
case ModId::LFO:
|
||||||
|
return kModIsPerVoice;
|
||||||
|
|
||||||
|
// targets
|
||||||
|
case ModId::Amplitude:
|
||||||
|
return kModIsPerVoice|kModIsPercentMultiplicative;
|
||||||
|
case ModId::Pan:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::Width:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::Position:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::Pitch:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
case ModId::Volume:
|
||||||
|
return kModIsPerVoice|kModIsAdditive;
|
||||||
|
|
||||||
|
// unknown
|
||||||
|
default:
|
||||||
|
return kModFlagsInvalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
87
src/sfizz/modulations/ModId.h
Normal file
87
src/sfizz/modulations/ModId.h
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic identifier of a kind of modulation source or target,
|
||||||
|
* not necessarily unique per SFZ instrument
|
||||||
|
*/
|
||||||
|
enum class ModId : int {
|
||||||
|
Undefined,
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Sources
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
_SourcesStart,
|
||||||
|
|
||||||
|
Controller = _SourcesStart,
|
||||||
|
Envelope,
|
||||||
|
LFO,
|
||||||
|
|
||||||
|
_SourcesEnd,
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Targets
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
_TargetsStart = _SourcesEnd,
|
||||||
|
|
||||||
|
Amplitude = _TargetsStart,
|
||||||
|
Pan,
|
||||||
|
Width,
|
||||||
|
Position,
|
||||||
|
Pitch,
|
||||||
|
Volume,
|
||||||
|
|
||||||
|
_TargetsEnd,
|
||||||
|
// [/targets] --------------------------------------------------------------
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modulation bit flags (S=source, T=target, ST=either)
|
||||||
|
*/
|
||||||
|
enum ModFlags : int {
|
||||||
|
//! This modulation is invalid. (ST)
|
||||||
|
kModFlagsInvalid = -1,
|
||||||
|
|
||||||
|
//! This modulation is global (the default). (ST)
|
||||||
|
kModIsPerCycle = 1 << 1,
|
||||||
|
//! This modulation is updated separately for every region of every voice (ST)
|
||||||
|
kModIsPerVoice = 1 << 2,
|
||||||
|
|
||||||
|
//! This target is additive. (T)
|
||||||
|
kModIsAdditive = 1 << 3,
|
||||||
|
//! This target is multiplicative (T)
|
||||||
|
kModIsMultiplicative = 1 << 4,
|
||||||
|
//! This target is %-multiplicative (T)
|
||||||
|
kModIsPercentMultiplicative = 1 << 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace ModIds {
|
||||||
|
|
||||||
|
bool isSource(ModId id) noexcept;
|
||||||
|
bool isTarget(ModId id) noexcept;
|
||||||
|
int flags(ModId id) noexcept;
|
||||||
|
|
||||||
|
template <class F> inline void forEachSourceId(F&& f)
|
||||||
|
{
|
||||||
|
for (int i = static_cast<int>(ModId::_SourcesStart);
|
||||||
|
i < static_cast<int>(ModId::_SourcesEnd); ++i)
|
||||||
|
f(static_cast<ModId>(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F> inline void forEachTargetId(F&& f)
|
||||||
|
{
|
||||||
|
for (int i = static_cast<int>(ModId::_TargetsStart);
|
||||||
|
i < static_cast<int>(ModId::_TargetsEnd); ++i)
|
||||||
|
f(static_cast<ModId>(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ModIds
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
93
src/sfizz/modulations/ModKey.cpp
Normal file
93
src/sfizz/modulations/ModKey.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
// 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 "ModKey.h"
|
||||||
|
#include "ModId.h"
|
||||||
|
#include "../Debug.h"
|
||||||
|
#include <absl/strings/str_cat.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
ModKey ModKey::createCC(uint8_t cc, uint8_t curve, uint8_t smooth, float value, float step)
|
||||||
|
{
|
||||||
|
ModKey::Parameters p;
|
||||||
|
p.cc = cc;
|
||||||
|
p.curve = curve;
|
||||||
|
p.smooth = smooth;
|
||||||
|
p.value = value;
|
||||||
|
p.step = step;
|
||||||
|
return ModKey(ModId::Controller, {}, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModKey ModKey::createNXYZ(ModId id, NumericId<Region> region, uint8_t N, uint8_t X, uint8_t Y, uint8_t Z)
|
||||||
|
{
|
||||||
|
ASSERT(id != ModId::Controller);
|
||||||
|
ModKey::Parameters p;
|
||||||
|
p.N = N;
|
||||||
|
p.X = X;
|
||||||
|
p.Y = Y;
|
||||||
|
p.Z = Z;
|
||||||
|
return ModKey(id, region, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModKey::isSource() const noexcept
|
||||||
|
{
|
||||||
|
return ModIds::isSource(id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModKey::isTarget() const noexcept
|
||||||
|
{
|
||||||
|
return ModIds::isTarget(id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ModKey::flags() const noexcept
|
||||||
|
{
|
||||||
|
return ModIds::flags(id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ModKey::toString() const
|
||||||
|
{
|
||||||
|
switch (id_) {
|
||||||
|
case ModId::Controller:
|
||||||
|
return absl::StrCat("Controller ", params_.cc,
|
||||||
|
" {curve=", params_.curve, ", smooth=", params_.smooth,
|
||||||
|
", value=", params_.value, ", step=", params_.value, "}");
|
||||||
|
case ModId::Envelope:
|
||||||
|
return absl::StrCat("EG ", 1 + params_.N);
|
||||||
|
case ModId::LFO:
|
||||||
|
return absl::StrCat("LFO ", 1 + params_.N);
|
||||||
|
|
||||||
|
case ModId::Amplitude:
|
||||||
|
return "Amplitude";
|
||||||
|
case ModId::Pan:
|
||||||
|
return "Pan";
|
||||||
|
case ModId::Width:
|
||||||
|
return "Width";
|
||||||
|
case ModId::Position:
|
||||||
|
return "Position";
|
||||||
|
case ModId::Pitch:
|
||||||
|
return "Pitch";
|
||||||
|
case ModId::Volume:
|
||||||
|
return "Volume";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
|
|
||||||
|
bool sfz::ModKey::operator==(const ModKey &other) const noexcept
|
||||||
|
{
|
||||||
|
return id_ == other.id_ && region_ && other.region_ &&
|
||||||
|
!std::memcmp(¶meters(), &other.parameters(), sizeof(ModKey::Parameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sfz::ModKey::operator!=(const ModKey &other) const noexcept
|
||||||
|
{
|
||||||
|
return !this->operator==(other);
|
||||||
|
}
|
||||||
68
src/sfizz/modulations/ModKey.h
Normal file
68
src/sfizz/modulations/ModKey.h
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "ModKeyHash.h"
|
||||||
|
#include "../NumericId.h"
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
struct Region;
|
||||||
|
|
||||||
|
enum class ModId : int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Identifier of a single modulation source or target within a SFZ instrument
|
||||||
|
*/
|
||||||
|
class ModKey {
|
||||||
|
public:
|
||||||
|
struct Parameters;
|
||||||
|
|
||||||
|
ModKey() = default;
|
||||||
|
explicit ModKey(ModId id, NumericId<Region> region = {}, Parameters params = {})
|
||||||
|
: id_(id), region_(region), params_(params) {}
|
||||||
|
|
||||||
|
static ModKey createCC(uint8_t cc, uint8_t curve, uint8_t smooth, float value, float step);
|
||||||
|
static ModKey createNXYZ(ModId id, NumericId<Region> region, uint8_t N = 0, uint8_t X = 0, uint8_t Y = 0, uint8_t Z = 0);
|
||||||
|
|
||||||
|
explicit operator bool() const noexcept { return id_ != ModId(); }
|
||||||
|
|
||||||
|
const ModId& id() const noexcept { return id_; }
|
||||||
|
NumericId<Region> region() const noexcept { return region_; }
|
||||||
|
const Parameters& parameters() const noexcept { return params_; }
|
||||||
|
|
||||||
|
bool isSource() const noexcept;
|
||||||
|
bool isTarget() const noexcept;
|
||||||
|
int flags() const noexcept;
|
||||||
|
std::string toString() const;
|
||||||
|
|
||||||
|
struct Parameters {
|
||||||
|
Parameters() { std::memset(this, 0, sizeof(*this)); }
|
||||||
|
union {
|
||||||
|
//! Parameters if this key identifies a CC source
|
||||||
|
struct { uint8_t cc, curve, smooth; float value, step; };
|
||||||
|
//! Parameters otherwise, based on the related opcode
|
||||||
|
// eg. `N` in `lfoN`, `N, X` in `lfoN_eqX`
|
||||||
|
struct { uint8_t N, X, Y, Z; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool operator==(const ModKey &other) const noexcept;
|
||||||
|
bool operator!=(const ModKey &other) const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! Identifier
|
||||||
|
ModId id_ {};
|
||||||
|
//! Region identifier, only applicable if the modulation is per-voice
|
||||||
|
NumericId<Region> region_;
|
||||||
|
//! List of values which identify the key uniquely, along with the hash and region
|
||||||
|
Parameters params_ {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
34
src/sfizz/modulations/ModKeyHash.cpp
Normal file
34
src/sfizz/modulations/ModKeyHash.cpp
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// 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 "ModKeyHash.h"
|
||||||
|
#include "ModKey.h"
|
||||||
|
#include "ModId.h"
|
||||||
|
#include "StringViewHelpers.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
size_t std::hash<sfz::ModKey>::operator()(const sfz::ModKey &key) const
|
||||||
|
{
|
||||||
|
uint64_t k = hashNumber(static_cast<int>(key.id()));
|
||||||
|
const sfz::ModKey::Parameters& p = key.parameters();
|
||||||
|
|
||||||
|
switch (key.id()) {
|
||||||
|
case sfz::ModId::Controller:
|
||||||
|
k = hashNumber(p.cc, k);
|
||||||
|
k = hashNumber(p.curve, k);
|
||||||
|
k = hashNumber(p.smooth, k);
|
||||||
|
k = hashNumber(p.value, k);
|
||||||
|
k = hashNumber(p.step, k);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
k = hashNumber(p.N, k);
|
||||||
|
k = hashNumber(p.X, k);
|
||||||
|
k = hashNumber(p.Y, k);
|
||||||
|
k = hashNumber(p.Z, k);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return k;
|
||||||
|
}
|
||||||
17
src/sfizz/modulations/ModKeyHash.h
Normal file
17
src/sfizz/modulations/ModKeyHash.h
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace sfz { class ModKey; }
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template <class> struct hash;
|
||||||
|
template <> struct hash<sfz::ModKey> {
|
||||||
|
size_t operator()(const sfz::ModKey &key) const;
|
||||||
|
};
|
||||||
|
}
|
||||||
319
src/sfizz/modulations/ModMatrix.cpp
Normal file
319
src/sfizz/modulations/ModMatrix.cpp
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
// 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 "ModMatrix.h"
|
||||||
|
#include "ModId.h"
|
||||||
|
#include "ModKey.h"
|
||||||
|
#include "ModGenerator.h"
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "SIMDHelpers.h"
|
||||||
|
#include "Debug.h"
|
||||||
|
#include <absl/container/flat_hash_map.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
struct ModMatrix::Impl {
|
||||||
|
double sampleRate_ {};
|
||||||
|
uint32_t samplesPerBlock_ {};
|
||||||
|
|
||||||
|
uint32_t numFrames_ {};
|
||||||
|
NumericId<Voice> voiceId_ {};
|
||||||
|
|
||||||
|
struct Source {
|
||||||
|
ModKey key;
|
||||||
|
ModGenerator* gen {};
|
||||||
|
bool bufferReady {};
|
||||||
|
Buffer<float> buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConnectionData {
|
||||||
|
// nothing
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Target {
|
||||||
|
ModKey key;
|
||||||
|
uint32_t region {};
|
||||||
|
absl::flat_hash_map<uint32_t, ConnectionData> connectedSources;
|
||||||
|
bool bufferReady {};
|
||||||
|
Buffer<float> buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
absl::flat_hash_map<ModKey, uint32_t> sourceIndex_;
|
||||||
|
absl::flat_hash_map<ModKey, uint32_t> targetIndex_;
|
||||||
|
|
||||||
|
std::vector<Source> sources_;
|
||||||
|
std::vector<Target> targets_;
|
||||||
|
|
||||||
|
Buffer<float> temp_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ModMatrix::ModMatrix()
|
||||||
|
: impl_(new Impl)
|
||||||
|
{
|
||||||
|
setSampleRate(config::defaultSampleRate);
|
||||||
|
setSamplesPerBlock(config::defaultSamplesPerBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModMatrix::~ModMatrix()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModMatrix::clear()
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
impl.sourceIndex_.clear();
|
||||||
|
impl.targetIndex_.clear();
|
||||||
|
impl.sources_.clear();
|
||||||
|
impl.targets_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModMatrix::setSampleRate(double sampleRate)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
if (impl.sampleRate_ == sampleRate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl.sampleRate_ = sampleRate;
|
||||||
|
|
||||||
|
for (Impl::Source &source : impl.sources_)
|
||||||
|
source.gen->setSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModMatrix::setSamplesPerBlock(unsigned samplesPerBlock)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
if (impl.samplesPerBlock_ == samplesPerBlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl.samplesPerBlock_ = samplesPerBlock;
|
||||||
|
|
||||||
|
for (Impl::Source &source : impl.sources_) {
|
||||||
|
source.buffer.resize(samplesPerBlock);
|
||||||
|
source.gen->setSamplesPerBlock(samplesPerBlock);
|
||||||
|
}
|
||||||
|
for (Impl::Target &target : impl.targets_)
|
||||||
|
target.buffer.resize(samplesPerBlock);
|
||||||
|
|
||||||
|
impl.temp_.resize(samplesPerBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModMatrix::SourceId ModMatrix::registerSource(const ModKey& key, ModGenerator& gen)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
auto it = impl.sourceIndex_.find(key);
|
||||||
|
if (it != impl.sourceIndex_.end()) {
|
||||||
|
ASSERT(&gen == impl.sources_[it->second].gen);
|
||||||
|
return SourceId(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceId id(static_cast<int>(impl.sources_.size()));
|
||||||
|
impl.sources_.emplace_back();
|
||||||
|
|
||||||
|
Impl::Source &source = impl.sources_.back();
|
||||||
|
source.key = key;
|
||||||
|
source.gen = &gen;
|
||||||
|
source.bufferReady = false;
|
||||||
|
source.buffer.resize(impl.samplesPerBlock_);
|
||||||
|
|
||||||
|
impl.sourceIndex_[key] = id.number();
|
||||||
|
|
||||||
|
gen.setSampleRate(impl.sampleRate_);
|
||||||
|
gen.setSamplesPerBlock(impl.samplesPerBlock_);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModMatrix::TargetId ModMatrix::registerTarget(const ModKey& key)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
auto it = impl.targetIndex_.find(key);
|
||||||
|
if (it != impl.targetIndex_.end())
|
||||||
|
return TargetId(it->second);
|
||||||
|
|
||||||
|
TargetId id(static_cast<int>(impl.targets_.size()));
|
||||||
|
impl.targets_.emplace_back();
|
||||||
|
|
||||||
|
Impl::Target &target = impl.targets_.back();
|
||||||
|
target.key = key;
|
||||||
|
target.bufferReady = false;
|
||||||
|
target.buffer.resize(impl.samplesPerBlock_);
|
||||||
|
|
||||||
|
impl.targetIndex_[key] = id.number();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModMatrix::SourceId ModMatrix::findSource(const ModKey& key)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
auto it = impl.sourceIndex_.find(key);
|
||||||
|
if (it == impl.sourceIndex_.end())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return SourceId(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModMatrix::TargetId ModMatrix::findTarget(const ModKey& key)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
auto it = impl.targetIndex_.find(key);
|
||||||
|
if (it == impl.targetIndex_.end())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return TargetId(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModMatrix::connect(SourceId sourceId, TargetId targetId)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
unsigned sourceIndex = sourceId.number();
|
||||||
|
unsigned targetIndex = targetId.number();
|
||||||
|
|
||||||
|
if (sourceIndex >= impl.sources_.size() || targetIndex >= impl.targets_.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Impl::Target& target = impl.targets_[targetIndex];
|
||||||
|
/*Impl::ConnectionData& conn =*/ target.connectedSources[sourceIndex];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModMatrix::init()
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
for (Impl::Source &source : impl.sources_) {
|
||||||
|
int flags = source.key.flags();
|
||||||
|
if (flags & kModIsPerCycle)
|
||||||
|
source.gen->init(source.key, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModMatrix::initVoice(NumericId<Voice> voiceId)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
for (Impl::Source &source : impl.sources_) {
|
||||||
|
int flags = source.key.flags();
|
||||||
|
if (flags & kModIsPerVoice)
|
||||||
|
source.gen->init(source.key, voiceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModMatrix::beginCycle(unsigned numFrames)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
impl.numFrames_ = numFrames;
|
||||||
|
|
||||||
|
for (Impl::Source &source : impl.sources_)
|
||||||
|
source.bufferReady = false;
|
||||||
|
for (Impl::Target &target : impl.targets_)
|
||||||
|
target.bufferReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModMatrix::beginVoice(NumericId<Voice> voiceId)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
impl.voiceId_ = voiceId;
|
||||||
|
|
||||||
|
for (Impl::Source &source : impl.sources_) {
|
||||||
|
const int flags = source.key.flags();
|
||||||
|
if (flags & kModIsPerVoice)
|
||||||
|
source.bufferReady = false;
|
||||||
|
}
|
||||||
|
for (Impl::Target &target : impl.targets_) {
|
||||||
|
const int flags = target.key.flags();
|
||||||
|
if (flags & kModIsPerVoice)
|
||||||
|
target.bufferReady = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float* ModMatrix::getModulation(TargetId targetId)
|
||||||
|
{
|
||||||
|
if (!validTarget(targetId))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
const uint32_t targetIndex = targetId.number();
|
||||||
|
Impl::Target &target = impl.targets_[targetIndex];
|
||||||
|
const int flags = target.key.flags();
|
||||||
|
|
||||||
|
const uint32_t numFrames = impl.numFrames_;
|
||||||
|
absl::Span<float> buffer(target.buffer.data(), numFrames);
|
||||||
|
|
||||||
|
// check if already processed
|
||||||
|
if (target.bufferReady)
|
||||||
|
return buffer.data();
|
||||||
|
|
||||||
|
// set the ready flag to prevent a cycle
|
||||||
|
// in case there is, be sure to initialize the buffer
|
||||||
|
target.bufferReady = true;
|
||||||
|
if (flags & kModIsMultiplicative)
|
||||||
|
sfz::fill(buffer, 1.0f);
|
||||||
|
else if (flags & kModIsPercentMultiplicative)
|
||||||
|
sfz::fill(buffer, 100.0f);
|
||||||
|
else {
|
||||||
|
ASSERT(flags & kModIsAdditive);
|
||||||
|
sfz::fill(buffer, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sourcesPos = target.connectedSources.begin();
|
||||||
|
auto sourcesEnd = target.connectedSources.end();
|
||||||
|
|
||||||
|
// generate the first source in buffer
|
||||||
|
if (sourcesPos != sourcesEnd) {
|
||||||
|
Impl::Source &source = impl.sources_[sourcesPos->first];
|
||||||
|
source.gen->generate(source.key, impl.voiceId_, buffer);
|
||||||
|
++sourcesPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate next sources in temporary buffer
|
||||||
|
// then add or multiply, depending on target flags
|
||||||
|
absl::Span<float> temp(impl.temp_.data(), numFrames);
|
||||||
|
while (sourcesPos != sourcesEnd) {
|
||||||
|
Impl::Source &source = impl.sources_[sourcesPos->first];
|
||||||
|
source.gen->generate(source.key, impl.voiceId_, temp);
|
||||||
|
if (flags & kModIsMultiplicative) {
|
||||||
|
for (uint32_t i = 0; i < numFrames; ++i)
|
||||||
|
buffer[i] *= temp[i];
|
||||||
|
}
|
||||||
|
else if (flags & kModIsPercentMultiplicative) {
|
||||||
|
for (uint32_t i = 0; i < numFrames; ++i)
|
||||||
|
buffer[i] *= 0.01f * temp[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ASSERT(flags & kModIsAdditive);
|
||||||
|
for (uint32_t i = 0; i < numFrames; ++i)
|
||||||
|
buffer[i] += temp[i];
|
||||||
|
}
|
||||||
|
++sourcesPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModMatrix::validTarget(TargetId id) const
|
||||||
|
{
|
||||||
|
return static_cast<unsigned>(id.number()) < impl_->targets_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModMatrix::validSource(SourceId id) const
|
||||||
|
{
|
||||||
|
return static_cast<unsigned>(id.number()) < impl_->sources_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
160
src/sfizz/modulations/ModMatrix.h
Normal file
160
src/sfizz/modulations/ModMatrix.h
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../NumericId.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
class ModKey;
|
||||||
|
class ModGenerator;
|
||||||
|
class Voice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modulation matrix
|
||||||
|
*/
|
||||||
|
class ModMatrix {
|
||||||
|
public:
|
||||||
|
ModMatrix();
|
||||||
|
~ModMatrix();
|
||||||
|
|
||||||
|
struct SourceIdTag;
|
||||||
|
struct TargetIdTag;
|
||||||
|
|
||||||
|
//! Identifier of a modulation source
|
||||||
|
typedef NumericId<SourceIdTag> SourceId;
|
||||||
|
|
||||||
|
//! Identifier of a modulation target
|
||||||
|
typedef NumericId<TargetIdTag> TargetId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the matrix to the empty state.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Change the sample rate.
|
||||||
|
*
|
||||||
|
* @param sampleRate new sample rate
|
||||||
|
*/
|
||||||
|
void setSampleRate(double sampleRate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resize the modulation buffers.
|
||||||
|
*
|
||||||
|
* @param samplesPerBlock new block size
|
||||||
|
*/
|
||||||
|
void setSamplesPerBlock(unsigned samplesPerBlock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a modulation source inside the matrix.
|
||||||
|
* If it is already present, it just returns the existing id.
|
||||||
|
*
|
||||||
|
* @param key source key
|
||||||
|
* @param gen generator
|
||||||
|
* @param flags source flags
|
||||||
|
*/
|
||||||
|
SourceId registerSource(const ModKey& key, ModGenerator& gen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a modulation target inside the matrix.
|
||||||
|
*
|
||||||
|
* @param key target key
|
||||||
|
* @param region target region
|
||||||
|
* @param flags target flags
|
||||||
|
*/
|
||||||
|
TargetId registerTarget(const ModKey& key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Look up a source by key.
|
||||||
|
*
|
||||||
|
* @param key source key
|
||||||
|
*/
|
||||||
|
SourceId findSource(const ModKey& key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Look up a target by key.
|
||||||
|
*
|
||||||
|
* @param key target key
|
||||||
|
*/
|
||||||
|
TargetId findTarget(const ModKey& key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connect a source and a destination inside the matrix.
|
||||||
|
*
|
||||||
|
* @param sourceId source of the connection
|
||||||
|
* @param targetId target of the connection
|
||||||
|
* @return true if the connection was successfully made, otherwise false
|
||||||
|
*/
|
||||||
|
bool connect(SourceId sourceId, TargetId targetId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reinitialize modulation sources overall.
|
||||||
|
* This must be called once after setting up the matrix.
|
||||||
|
*/
|
||||||
|
void init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reinitialize modulation source for a given voice.
|
||||||
|
* This must be called first after a voice enters active state.
|
||||||
|
*/
|
||||||
|
void initVoice(NumericId<Voice> voiceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start modulation processing for the entire cycle.
|
||||||
|
* This clears all the buffers.
|
||||||
|
*
|
||||||
|
* @param numFrames
|
||||||
|
*/
|
||||||
|
void beginCycle(unsigned numFrames);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start modulation processing for a given voice.
|
||||||
|
* This clears all the buffers which are per-voice.
|
||||||
|
*
|
||||||
|
* @param voiceId the identifier of the current voice
|
||||||
|
*/
|
||||||
|
void beginVoice(NumericId<Voice> voiceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the modulation buffer for the given target.
|
||||||
|
* If the target does not exist, the result is null.
|
||||||
|
*
|
||||||
|
* @param targetId identifier of the modulation target
|
||||||
|
*/
|
||||||
|
float* getModulation(TargetId targetId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the modulation buffer for the given target.
|
||||||
|
* Same as `getModulation`, but accepting a key directly.
|
||||||
|
*
|
||||||
|
* @param targetKey key of the modulation target
|
||||||
|
*/
|
||||||
|
float* getModulationByKey(const ModKey& targetKey)
|
||||||
|
{ return getModulation(findTarget(targetKey)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return whether the target identifier is valid.
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
bool validTarget(TargetId id) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return whether the source identifier is valid.
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
bool validSource(SourceId id) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
std::unique_ptr<Impl> impl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
97
src/sfizz/modulations/sources/Controller.cpp
Normal file
97
src/sfizz/modulations/sources/Controller.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
// 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 "Controller.h"
|
||||||
|
#include "../ModKey.h"
|
||||||
|
#include "../../Smoothers.h"
|
||||||
|
#include "../../ModifierHelpers.h"
|
||||||
|
#include "../../Resources.h"
|
||||||
|
#include "../../Config.h"
|
||||||
|
#include "../../Debug.h"
|
||||||
|
#include <absl/container/flat_hash_map.h>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
struct ControllerSource::Impl {
|
||||||
|
double sampleRate_ = config::defaultSampleRate;
|
||||||
|
Resources* res_ = nullptr;
|
||||||
|
absl::flat_hash_map<ModKey, Smoother> smoother_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ControllerSource::ControllerSource(Resources& res)
|
||||||
|
: impl_(new Impl)
|
||||||
|
{
|
||||||
|
impl_->res_ = &res;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerSource::~ControllerSource()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerSource::setSampleRate(double sampleRate)
|
||||||
|
{
|
||||||
|
if (impl_->sampleRate_ == sampleRate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl_->sampleRate_ = sampleRate;
|
||||||
|
|
||||||
|
for (auto& item : impl_->smoother_) {
|
||||||
|
const ModKey::Parameters p = item.first.parameters();
|
||||||
|
item.second.setSmoothing(p.smooth, sampleRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerSource::setSamplesPerBlock(unsigned count)
|
||||||
|
{
|
||||||
|
(void)count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId)
|
||||||
|
{
|
||||||
|
(void)voiceId;
|
||||||
|
|
||||||
|
const ModKey::Parameters p = sourceKey.parameters();
|
||||||
|
if (p.smooth > 0) {
|
||||||
|
Smoother s;
|
||||||
|
s.setSmoothing(p.smooth, impl_->sampleRate_);
|
||||||
|
impl_->smoother_[sourceKey] = s;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
impl_->smoother_.erase(sourceKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||||
|
{
|
||||||
|
(void)voiceId;
|
||||||
|
|
||||||
|
const ModKey::Parameters p = sourceKey.parameters();
|
||||||
|
const Resources& res = *impl_->res_;
|
||||||
|
const Curve& curve = res.curves.getCurve(p.curve);
|
||||||
|
const MidiState& ms = res.midiState;
|
||||||
|
const EventVector& events = ms.getCCEvents(p.cc);
|
||||||
|
|
||||||
|
auto transformValue = [p, &curve](float x) {
|
||||||
|
return curve.evalNormalized(x) * p.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (p.step > 0.0f)
|
||||||
|
linearEnvelope(events, buffer, transformValue, p.step);
|
||||||
|
else
|
||||||
|
linearEnvelope(events, buffer, transformValue);
|
||||||
|
|
||||||
|
auto it = impl_->smoother_.find(sourceKey);
|
||||||
|
if (it != impl_->smoother_.end()) {
|
||||||
|
Smoother& s = it->second;
|
||||||
|
|
||||||
|
#pragma message("TODO: implement CC shortcut")
|
||||||
|
bool canShortcut = false;
|
||||||
|
|
||||||
|
s.process(buffer, buffer, canShortcut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
29
src/sfizz/modulations/sources/Controller.h
Normal file
29
src/sfizz/modulations/sources/Controller.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../ModGenerator.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
struct Resources;
|
||||||
|
|
||||||
|
class ControllerSource : public ModGenerator {
|
||||||
|
public:
|
||||||
|
explicit ControllerSource(Resources& res);
|
||||||
|
~ControllerSource();
|
||||||
|
void setSampleRate(double sampleRate) override;
|
||||||
|
void setSamplesPerBlock(unsigned count) override;
|
||||||
|
void init(const ModKey& sourceKey, NumericId<Voice> voiceId) override;
|
||||||
|
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
std::unique_ptr<Impl> impl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
|
|
@ -36,6 +36,7 @@ set(SFIZZ_TEST_SOURCES
|
||||||
SwapAndPopT.cpp
|
SwapAndPopT.cpp
|
||||||
TuningT.cpp
|
TuningT.cpp
|
||||||
ConcurrencyT.cpp
|
ConcurrencyT.cpp
|
||||||
|
ModulationsT.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES})
|
add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES})
|
||||||
|
|
|
||||||
76
tests/ModulationsT.cpp
Normal file
76
tests/ModulationsT.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
// 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 "sfizz/modulations/ModId.h"
|
||||||
|
#include "sfizz/modulations/ModKey.h"
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("[Modulations] Identifiers")
|
||||||
|
{
|
||||||
|
// check that modulations are well defined as either source and target
|
||||||
|
// and all targets have their default value defined
|
||||||
|
|
||||||
|
sfz::ModIds::forEachSourceId([](sfz::ModId id)
|
||||||
|
{
|
||||||
|
REQUIRE(sfz::ModIds::isSource(id));
|
||||||
|
REQUIRE(!sfz::ModIds::isTarget(id));
|
||||||
|
});
|
||||||
|
|
||||||
|
sfz::ModIds::forEachTargetId([](sfz::ModId id)
|
||||||
|
{
|
||||||
|
REQUIRE(sfz::ModIds::isTarget(id));
|
||||||
|
REQUIRE(!sfz::ModIds::isSource(id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Modulations] Flags")
|
||||||
|
{
|
||||||
|
// check validity of modulation flags
|
||||||
|
|
||||||
|
static auto* checkBasicFlags = +[](int flags)
|
||||||
|
{
|
||||||
|
REQUIRE(flags != sfz::kModFlagsInvalid);
|
||||||
|
REQUIRE(((flags & sfz::kModIsPerCycle) ^
|
||||||
|
(flags & sfz::kModIsPerVoice)) != 0);
|
||||||
|
};
|
||||||
|
static auto* checkSourceFlags = +[](int flags)
|
||||||
|
{
|
||||||
|
checkBasicFlags(flags);
|
||||||
|
// nothing else
|
||||||
|
};
|
||||||
|
static auto* checkTargetFlags = +[](int flags)
|
||||||
|
{
|
||||||
|
checkBasicFlags(flags);
|
||||||
|
REQUIRE(((flags & sfz::kModIsAdditive) ^
|
||||||
|
(flags & sfz::kModIsMultiplicative) ^
|
||||||
|
(flags & sfz::kModIsPercentMultiplicative)) != 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
sfz::ModIds::forEachSourceId([](sfz::ModId id)
|
||||||
|
{
|
||||||
|
checkSourceFlags(sfz::ModIds::flags(id));
|
||||||
|
});
|
||||||
|
|
||||||
|
sfz::ModIds::forEachTargetId([](sfz::ModId id)
|
||||||
|
{
|
||||||
|
checkTargetFlags(sfz::ModIds::flags(id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Modulations] Display names")
|
||||||
|
{
|
||||||
|
// check all modulations are implemented in `toString`
|
||||||
|
|
||||||
|
sfz::ModIds::forEachSourceId([](sfz::ModId id)
|
||||||
|
{
|
||||||
|
REQUIRE(!sfz::ModKey(id).toString().empty());
|
||||||
|
});
|
||||||
|
|
||||||
|
sfz::ModIds::forEachTargetId([](sfz::ModId id)
|
||||||
|
{
|
||||||
|
REQUIRE(!sfz::ModKey(id).toString().empty());
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue