sfizz/src/sfizz/Region.cpp
JP Cimalando 296080bb56
Merge pull request #436 from jpcima/oscillators-fm
Add FM synthesis with 2 operators
2020-09-23 18:09:46 +02:00

1809 lines
68 KiB
C++

// 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 "Region.h"
#include "MathHelpers.h"
#include "Macros.h"
#include "Debug.h"
#include "Opcode.h"
#include "StringViewHelpers.h"
#include "ModifierHelpers.h"
#include "modulations/ModId.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_cat.h"
#include "absl/algorithm/container.h"
#include <random>
#include <cassert>
template<class T>
bool extendIfNecessary(std::vector<T>& vec, unsigned size, unsigned defaultCapacity)
{
if (size == 0)
return false;
if (vec.capacity() == 0)
vec.reserve(defaultCapacity);
if (vec.size() < size)
vec.resize(size);
return true;
}
bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
{
const Opcode opcode = rawOpcode.cleanUp(kOpcodeScopeRegion);
switch (opcode.lettersOnlyHash) {
// Helper for ccN processing
#define case_any_ccN(x) \
case hash(x "_oncc&"): \
case hash(x "_curvecc&"): \
case hash(x "_stepcc&"): \
case hash(x "_smoothcc&")
#define LFO_EG_filter_EQ_target(sourceKey, targetKey, range) \
{ \
const auto number = opcode.parameters.front(); \
if (number == 0) \
return false; \
\
const auto index = opcode.parameters.size() == 2 ? opcode.parameters.back() - 1 : 0; \
if (!extendIfNecessary(filters, index + 1, Default::numFilters)) \
return false; \
\
if (auto value = readOpcode(opcode.value, range)) { \
const ModKey source = ModKey::createNXYZ(sourceKey, id, number - 1); \
const ModKey target = ModKey::createNXYZ(targetKey, id, index); \
getOrCreateConnection(source, target).sourceDepth = *value; \
} \
}
// Sound source: sample playback
case hash("sample"):
{
const auto trimmedSample = trim(opcode.value);
if (trimmedSample.empty())
break;
std::string filename;
if (trimmedSample[0] == '*')
filename = std::string(trimmedSample);
else
filename = absl::StrCat(defaultPath, absl::StrReplaceAll(trimmedSample, { { "\\", "/" } }));
sampleId = FileId(std::move(filename), sampleId.isReverse());
}
break;
case hash("sample_quality"):
{
if (opcode.value == "-1")
sampleQuality.reset();
else
setValueFromOpcode(opcode, sampleQuality, Default::sampleQualityRange);
break;
}
break;
case hash("direction"):
sampleId = sampleId.reversed(opcode.value == "reverse");
break;
case hash("delay"):
setValueFromOpcode(opcode, delay, Default::delayRange);
break;
case hash("delay_random"):
setValueFromOpcode(opcode, delayRandom, Default::delayRange);
break;
case hash("offset"):
setValueFromOpcode(opcode, offset, Default::offsetRange);
break;
case hash("offset_random"):
setValueFromOpcode(opcode, offsetRandom, Default::offsetRange);
break;
case hash("offset_oncc&"): // also offset_cc&
if (opcode.parameters.back() > config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::offsetCCRange))
offsetCC[opcode.parameters.back()] = *value;
break;
case hash("end"):
setValueFromOpcode(opcode, sampleEnd, Default::sampleEndRange);
break;
case hash("count"):
setValueFromOpcode(opcode, sampleCount, Default::sampleCountRange);
break;
case hash("loop_mode"): // also loopmode
switch (hash(opcode.value)) {
case hash("no_loop"):
loopMode = SfzLoopMode::no_loop;
break;
case hash("one_shot"):
loopMode = SfzLoopMode::one_shot;
break;
case hash("loop_continuous"):
loopMode = SfzLoopMode::loop_continuous;
break;
case hash("loop_sustain"):
loopMode = SfzLoopMode::loop_sustain;
break;
default:
DBG("Unkown loop mode:" << opcode.value);
}
break;
case hash("loop_end"): // also loopend
setRangeEndFromOpcode(opcode, loopRange, Default::loopRange);
break;
case hash("loop_start"): // also loopstart
setRangeStartFromOpcode(opcode, loopRange, Default::loopRange);
break;
// Wavetable oscillator
case hash("oscillator_phase"):
setValueFromOpcode(opcode, oscillatorPhase, Default::oscillatorPhaseRange);
break;
case hash("oscillator"):
if (auto value = readBooleanFromOpcode(opcode))
oscillatorEnabled = *value ? OscillatorEnabled::On : OscillatorEnabled::Off;
break;
case hash("oscillator_mode"):
setValueFromOpcode(opcode, oscillatorMode, Default::oscillatorModeRange);
break;
case hash("oscillator_multi"):
setValueFromOpcode(opcode, oscillatorMulti, Default::oscillatorMultiRange);
break;
case hash("oscillator_detune"):
setValueFromOpcode(opcode, oscillatorDetune, Default::oscillatorDetuneRange);
break;
case_any_ccN("oscillator_detune"):
processGenericCc(opcode, Default::oscillatorDetuneCCRange, ModKey::createNXYZ(ModId::OscillatorDetune, id));
break;
case hash("oscillator_mod_depth"):
if (auto value = readOpcode(opcode.value, Default::oscillatorModDepthRange))
oscillatorModDepth = normalizePercents(*value);
break;
case_any_ccN("oscillator_mod_depth"):
processGenericCc(opcode, Default::oscillatorModDepthCCRange, ModKey::createNXYZ(ModId::OscillatorModDepth, id));
break;
case hash("oscillator_quality"):
if (opcode.value == "-1")
oscillatorQuality.reset();
else
setValueFromOpcode(opcode, oscillatorQuality, Default::oscillatorQualityRange);
break;
// Instrument settings: voice lifecycle
case hash("group"): // also polyphony_group
setValueFromOpcode(opcode, group, Default::groupRange);
break;
case hash("off_by"): // also offby
if (opcode.value == "-1")
offBy.reset();
else
setValueFromOpcode(opcode, offBy, Default::groupRange);
break;
case hash("off_mode"): // also offmode
switch (hash(opcode.value)) {
case hash("fast"):
offMode = SfzOffMode::fast;
break;
case hash("normal"):
offMode = SfzOffMode::normal;
break;
case hash("time"):
offMode = SfzOffMode::time;
break;
default:
DBG("Unkown off mode:" << opcode.value);
}
break;
case hash("off_time"):
offMode = SfzOffMode::time;
setValueFromOpcode(opcode, offTime, Default::egTimeRange);
break;
case hash("polyphony"):
if (auto value = readOpcode(opcode.value, Default::polyphonyRange))
polyphony = *value;
break;
case hash("note_polyphony"):
if (auto value = readOpcode(opcode.value, Default::polyphonyRange))
notePolyphony = *value;
break;
case hash("note_selfmask"):
switch (hash(opcode.value)) {
case hash("on"):
selfMask = SfzSelfMask::mask;
break;
case hash("off"):
selfMask = SfzSelfMask::dontMask;
break;
default:
DBG("Unkown self mask value:" << opcode.value);
}
break;
case hash("rt_dead"):
if (opcode.value == "on") {
rtDead = true;
} else if (opcode.value == "off") {
rtDead = false;
} else {
DBG("Unkown rt_dead value:" << opcode.value);
}
break;
// Region logic: key mapping
case hash("lokey"):
triggerOnNote = true;
setRangeStartFromOpcode(opcode, keyRange, Default::keyRange);
break;
case hash("hikey"):
triggerOnNote = (opcode.value != "-1");
setRangeEndFromOpcode(opcode, keyRange, Default::keyRange);
break;
case hash("key"):
triggerOnNote = (opcode.value != "-1");
setRangeStartFromOpcode(opcode, keyRange, Default::keyRange);
setRangeEndFromOpcode(opcode, keyRange, Default::keyRange);
setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange);
break;
case hash("lovel"):
if (auto value = readOpcode(opcode.value, Default::midi7Range))
velocityRange.setStart(normalizeVelocity(*value));
break;
case hash("hivel"):
if (auto value = readOpcode(opcode.value, Default::midi7Range))
velocityRange.setEnd(normalizeVelocity(*value));
break;
// Region logic: MIDI conditions
case hash("lobend"):
if (auto value = readOpcode(opcode.value, Default::bendRange))
bendRange.setStart(normalizeBend(*value));
break;
case hash("hibend"):
if (auto value = readOpcode(opcode.value, Default::bendRange))
bendRange.setEnd(normalizeBend(*value));
break;
case hash("locc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
ccConditions[opcode.parameters.back()].setStart(normalizeCC(*value));
break;
case hash("hicc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
ccConditions[opcode.parameters.back()].setEnd(normalizeCC(*value));
break;
case hash("lohdcc&"): // also lorealcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::normalizedRange))
ccConditions[opcode.parameters.back()].setStart(*value);
break;
case hash("hihdcc&"): // also hirealcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::normalizedRange))
ccConditions[opcode.parameters.back()].setEnd(*value);
break;
case hash("sw_lokey"):
setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange);
break;
case hash("sw_hikey"):
setRangeEndFromOpcode(opcode, keyswitchRange, Default::keyRange);
break;
case hash("sw_last"):
setValueFromOpcode(opcode, keyswitch, Default::keyRange);
keySwitched = false;
break;
case hash("sw_label"):
keyswitchLabel = opcode.value;
break;
case hash("sw_down"):
setValueFromOpcode(opcode, keyswitchDown, Default::keyRange);
keySwitched = false;
break;
case hash("sw_up"):
setValueFromOpcode(opcode, keyswitchUp, Default::keyRange);
break;
case hash("sw_previous"):
setValueFromOpcode(opcode, previousNote, Default::keyRange);
previousKeySwitched = false;
break;
case hash("sw_vel"):
switch (hash(opcode.value)) {
case hash("current"):
velocityOverride = SfzVelocityOverride::current;
break;
case hash("previous"):
velocityOverride = SfzVelocityOverride::previous;
break;
default:
DBG("Unknown velocity mode: " << opcode.value);
}
break;
case hash("sustain_cc"):
setValueFromOpcode(opcode, sustainCC, Default::ccNumberRange);
break;
case hash("sustain_lo"):
if (auto value = readOpcode(opcode.value, Default::float7Range)) {
sustainThreshold = normalizeCC(*value);
}
break;
case hash("sustain_sw"):
checkSustain = readBooleanFromOpcode(opcode).value_or(Default::checkSustain);
break;
case hash("sostenuto_sw"):
checkSostenuto = readBooleanFromOpcode(opcode).value_or(Default::checkSostenuto);
break;
// Region logic: internal conditions
case hash("lochanaft"):
setRangeStartFromOpcode(opcode, aftertouchRange, Default::aftertouchRange);
break;
case hash("hichanaft"):
setRangeEndFromOpcode(opcode, aftertouchRange, Default::aftertouchRange);
break;
case hash("lobpm"):
setRangeStartFromOpcode(opcode, bpmRange, Default::bpmRange);
break;
case hash("hibpm"):
setRangeEndFromOpcode(opcode, bpmRange, Default::bpmRange);
break;
case hash("lorand"):
setRangeStartFromOpcode(opcode, randRange, Default::randRange);
break;
case hash("hirand"):
setRangeEndFromOpcode(opcode, randRange, Default::randRange);
break;
case hash("seq_length"):
setValueFromOpcode(opcode, sequenceLength, Default::sequenceRange);
break;
case hash("seq_position"):
setValueFromOpcode(opcode, sequencePosition, Default::sequenceRange);
sequenceSwitched = (opcode.value == "1");
break;
// Region logic: triggers
case hash("trigger"):
switch (hash(opcode.value)) {
case hash("attack"):
trigger = SfzTrigger::attack;
break;
case hash("first"):
trigger = SfzTrigger::first;
break;
case hash("legato"):
trigger = SfzTrigger::legato;
break;
case hash("release"):
trigger = SfzTrigger::release;
break;
case hash("release_key"):
trigger = SfzTrigger::release_key;
break;
default:
DBG("Unknown trigger mode: " << opcode.value);
}
break;
case hash("start_locc&"): // also on_locc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range)) {
triggerOnCC = true;
ccTriggers[opcode.parameters.back()].setStart(normalizeCC(*value));
}
break;
case hash("start_hicc&"): // also on_hicc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range)) {
triggerOnCC = true;
ccTriggers[opcode.parameters.back()].setEnd(normalizeCC(*value));
}
break;
case hash("start_lohdcc&"): // also on_lohdcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::normalizedRange)) {
triggerOnCC = true;
ccTriggers[opcode.parameters.back()].setStart(*value);
}
break;
case hash("start_hihdcc&"): // also on_hihdcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::normalizedRange)) {
triggerOnCC = true;
ccTriggers[opcode.parameters.back()].setEnd(*value);
}
break;
// Performance parameters: amplifier
case hash("volume"): // also gain
setValueFromOpcode(opcode, volume, Default::volumeRange);
break;
case_any_ccN("volume"): // also gain
processGenericCc(opcode, Default::volumeCCRange, ModKey::createNXYZ(ModId::Volume, id));
break;
case hash("amplitude"):
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
amplitude = normalizePercents(*value);
break;
case_any_ccN("amplitude"):
processGenericCc(opcode, Default::amplitudeRange, ModKey::createNXYZ(ModId::Amplitude, id));
break;
case hash("pan"):
if (auto value = readOpcode(opcode.value, Default::panRange))
pan = normalizePercents(*value);
break;
case_any_ccN("pan"):
processGenericCc(opcode, Default::panCCRange, ModKey::createNXYZ(ModId::Pan, id));
break;
case hash("position"):
if (auto value = readOpcode(opcode.value, Default::positionRange))
position = normalizePercents(*value);
break;
case_any_ccN("position"):
processGenericCc(opcode, Default::positionCCRange, ModKey::createNXYZ(ModId::Position, id));
break;
case hash("width"):
if (auto value = readOpcode(opcode.value, Default::widthRange))
width = normalizePercents(*value);
break;
case_any_ccN("width"):
processGenericCc(opcode, Default::widthCCRange, ModKey::createNXYZ(ModId::Width, id));
break;
case hash("amp_keycenter"):
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
break;
case hash("amp_keytrack"):
setValueFromOpcode(opcode, ampKeytrack, Default::ampKeytrackRange);
break;
case hash("amp_veltrack"):
if (auto value = readOpcode(opcode.value, Default::ampVeltrackRange))
ampVeltrack = normalizePercents(*value);
break;
case hash("amp_random"):
setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange);
break;
case hash("amp_velcurve_&"):
{
auto value = readOpcode(opcode.value, Default::ampVelcurveRange);
if (opcode.parameters.back() > 127)
return false;
const auto inputVelocity = static_cast<uint8_t>(opcode.parameters.back());
if (value)
velocityPoints.emplace_back(inputVelocity, *value);
}
break;
case hash("xfin_lokey"):
setRangeStartFromOpcode(opcode, crossfadeKeyInRange, Default::keyRange);
break;
case hash("xfin_hikey"):
setRangeEndFromOpcode(opcode, crossfadeKeyInRange, Default::keyRange);
break;
case hash("xfout_lokey"):
setRangeStartFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange);
break;
case hash("xfout_hikey"):
setRangeEndFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange);
break;
case hash("xfin_lovel"):
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeVelInRange.setStart(normalizeVelocity(*value));
break;
case hash("xfin_hivel"):
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeVelInRange.setEnd(normalizeVelocity(*value));
break;
case hash("xfout_lovel"):
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeVelOutRange.setStart(normalizeVelocity(*value));
break;
case hash("xfout_hivel"):
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeVelOutRange.setEnd(normalizeVelocity(*value));
break;
case hash("xf_keycurve"):
switch (hash(opcode.value)) {
case hash("power"):
crossfadeKeyCurve = SfzCrossfadeCurve::power;
break;
case hash("gain"):
crossfadeKeyCurve = SfzCrossfadeCurve::gain;
break;
default:
DBG("Unknown crossfade power curve: " << opcode.value);
}
break;
case hash("xf_velcurve"):
switch (hash(opcode.value)) {
case hash("power"):
crossfadeVelCurve = SfzCrossfadeCurve::power;
break;
case hash("gain"):
crossfadeVelCurve = SfzCrossfadeCurve::gain;
break;
default:
DBG("Unknown crossfade power curve: " << opcode.value);
}
break;
case hash("xfin_locc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeCCInRange[opcode.parameters.back()].setStart(normalizeCC(*value));
break;
case hash("xfin_hicc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeCCInRange[opcode.parameters.back()].setEnd(normalizeCC(*value));
break;
case hash("xfout_locc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeCCOutRange[opcode.parameters.back()].setStart(normalizeCC(*value));
break;
case hash("xfout_hicc&"):
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::midi7Range))
crossfadeCCOutRange[opcode.parameters.back()].setEnd(normalizeCC(*value));
break;
case hash("xf_cccurve"):
switch (hash(opcode.value)) {
case hash("power"):
crossfadeCCCurve = SfzCrossfadeCurve::power;
break;
case hash("gain"):
crossfadeCCCurve = SfzCrossfadeCurve::gain;
break;
default:
DBG("Unknown crossfade power curve: " << opcode.value);
}
break;
case hash("rt_decay"):
setValueFromOpcode(opcode, rtDecay, Default::rtDecayRange);
break;
case hash("global_amplitude"):
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
globalAmplitude = normalizePercents(*value);
break;
case hash("master_amplitude"):
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
masterAmplitude = normalizePercents(*value);
break;
case hash("group_amplitude"):
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
groupAmplitude = normalizePercents(*value);
break;
case hash("global_volume"):
setValueFromOpcode(opcode, globalVolume, Default::volumeRange);
break;
case hash("master_volume"):
setValueFromOpcode(opcode, masterVolume, Default::volumeRange);
break;
case hash("group_volume"):
setValueFromOpcode(opcode, groupVolume, Default::volumeRange);
break;
// Performance parameters: filters
case hash("cutoff&"): // also cutoff
{
const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.back() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
setValueFromOpcode(opcode, filters[filterIndex].cutoff, Default::filterCutoffRange);
}
break;
case hash("resonance&"): // also resonance
{
const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.back() - 1);
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
setValueFromOpcode(opcode, filters[filterIndex].resonance, Default::filterResonanceRange);
}
break;
case_any_ccN("cutoff&"): // also cutoff_oncc&, cutoff_cc&, cutoff&_cc&
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
processGenericCc(opcode, Default::filterCutoffModRange, ModKey::createNXYZ(ModId::FilCutoff, id, filterIndex));
}
break;
case_any_ccN("resonance&"): // also resonance_oncc&, resonance_cc&, resonance&_cc&
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
processGenericCc(opcode, Default::filterResonanceModRange, ModKey::createNXYZ(ModId::FilResonance, id, filterIndex));
}
break;
case hash("fil&_keytrack"): // also fil_keytrack
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
setValueFromOpcode(opcode, filters[filterIndex].keytrack, Default::filterKeytrackRange);
}
break;
case hash("fil&_keycenter"): // also fil_keycenter
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
setValueFromOpcode(opcode, filters[filterIndex].keycenter, Default::keyRange);
}
break;
case hash("fil&_veltrack"): // also fil_veltrack
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
setValueFromOpcode(opcode, filters[filterIndex].veltrack, Default::filterVeltrackRange);
}
break;
case hash("fil&_random"): // also fil_random
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
setValueFromOpcode(opcode, filters[filterIndex].random, Default::filterRandomRange);
}
break;
case hash("fil&_gain"): // also fil_gain
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
setValueFromOpcode(opcode, filters[filterIndex].gain, Default::filterGainRange);
}
break;
case_any_ccN("fil&_gain"): // also fil_gain_oncc&
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
processGenericCc(opcode, Default::filterGainModRange, ModKey::createNXYZ(ModId::FilGain, id, filterIndex));
}
break;
case hash("fil&_type"): // also fil_type, filtype
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
absl::optional<FilterType> ftype = Filter::typeFromName(opcode.value);
if (ftype)
filters[filterIndex].type = *ftype;
else {
filters[filterIndex].type = FilterType::kFilterNone;
DBG("Unknown filter type: " << opcode.value);
}
}
break;
// Performance parameters: EQ
case hash("eq&_bw"):
{
const auto eqIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
return false;
setValueFromOpcode(opcode, equalizers[eqIndex].bandwidth, Default::eqBandwidthRange);
}
break;
case_any_ccN("eq&_bw"): // also eq&_bwcc&
{
const auto eqIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
return false;
processGenericCc(opcode, Default::eqBandwidthModRange, ModKey::createNXYZ(ModId::EqBandwidth, id, eqIndex));
}
break;
case hash("eq&_freq"):
{
const auto eqIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
return false;
setValueFromOpcode(opcode, equalizers[eqIndex].frequency, Default::eqFrequencyRange);
}
break;
case_any_ccN("eq&_freq"): // also eq&_freqcc&
{
const auto eqIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
return false;
processGenericCc(opcode, Default::eqFrequencyModRange, ModKey::createNXYZ(ModId::EqFrequency, id, eqIndex));
}
break;
case hash("eq&_vel&freq"):
{
const auto eqIndex = opcode.parameters.front() - 1;
if (opcode.parameters[1] != 2)
return false; // was eqN_vel3freq or something else than eqN_vel2freq
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
return false;
setValueFromOpcode(opcode, equalizers[eqIndex].vel2frequency, Default::eqFrequencyModRange);
}
break;
case hash("eq&_gain"):
{
const auto eqIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
return false;
setValueFromOpcode(opcode, equalizers[eqIndex].gain, Default::eqGainRange);
}
break;
case_any_ccN("eq&_gain"): // also eq&_gaincc&
{
const auto eqIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
return false;
processGenericCc(opcode, Default::eqGainModRange, ModKey::createNXYZ(ModId::EqGain, id, eqIndex));
}
break;
case hash("eq&_vel&gain"):
{
const auto eqIndex = opcode.parameters.front() - 1;
if (opcode.parameters[1] != 2)
return false; // was eqN_vel3gain or something else than eqN_vel2gain
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
return false;
setValueFromOpcode(opcode, equalizers[eqIndex].vel2gain, Default::eqGainModRange);
}
break;
case hash("eq&_type"):
{
const auto eqIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
return false;
absl::optional<EqType> ftype = FilterEq::typeFromName(opcode.value);
if (ftype)
equalizers[eqIndex].type = *ftype;
else {
equalizers[eqIndex].type = EqType::kEqNone;
DBG("Unknown EQ type: " << opcode.value);
}
}
break;
// Performance parameters: pitch
case hash("pitch_keycenter"):
if (opcode.value == "sample")
pitchKeycenterFromSample = true;
else {
pitchKeycenterFromSample = false;
setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange);
}
break;
case hash("pitch_keytrack"):
setValueFromOpcode(opcode, pitchKeytrack, Default::pitchKeytrackRange);
break;
case hash("pitch_veltrack"):
setValueFromOpcode(opcode, pitchVeltrack, Default::pitchVeltrackRange);
break;
case hash("pitch_random"):
setValueFromOpcode(opcode, pitchRandom, Default::pitchRandomRange);
break;
case hash("transpose"):
setValueFromOpcode(opcode, transpose, Default::transposeRange);
break;
case hash("pitch"): // also tune
setValueFromOpcode(opcode, tune, Default::tuneRange);
break;
case_any_ccN("pitch"): // also tune
processGenericCc(opcode, Default::tuneCCRange, ModKey::createNXYZ(ModId::Pitch, id));
break;
case hash("bend_up"): // also bendup
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
break;
case hash("bend_down"): // also benddown
setValueFromOpcode(opcode, bendDown, Default::bendBoundRange);
break;
case hash("bend_step"):
setValueFromOpcode(opcode, bendStep, Default::bendStepRange);
break;
case hash("bend_smooth"):
setValueFromOpcode(opcode, bendSmooth, Default::smoothCCRange);
break;
// Modulation: LFO
case hash("lfo&_freq"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
setValueFromOpcode(opcode, lfos[lfoNumber - 1].freq, Default::lfoFreqRange);
}
break;
case hash("lfo&_phase"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoPhaseRange)) {
float normalPhase = *value * (1.0 / 360.0);
normalPhase -= int(normalPhase);
normalPhase += (normalPhase < 0) ? 1 : 0;
lfos[lfoNumber - 1].phase0 = normalPhase;
}
}
break;
case hash("lfo&_delay"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
setValueFromOpcode(opcode, lfos[lfoNumber - 1].delay, Default::lfoDelayRange);
}
break;
case hash("lfo&_fade"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
setValueFromOpcode(opcode, lfos[lfoNumber - 1].fade, Default::lfoFadeRange);
}
break;
case hash("lfo&_count"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
setValueFromOpcode(opcode, lfos[lfoNumber - 1].count, Default::lfoCountRange);
}
break;
case hash("lfo&_steps"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoStepsRange)) {
if (!lfos[lfoNumber - 1].seq)
lfos[lfoNumber - 1].seq = LFODescription::StepSequence();
lfos[lfoNumber - 1].seq->steps.resize(*value);
}
}
break;
case hash("lfo&_step&"):
{
const auto lfoNumber = opcode.parameters.front();
const auto stepNumber = opcode.parameters[1];
if (lfoNumber == 0 || stepNumber == 0 || stepNumber > config::maxLFOSteps)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoStepXRange)) {
if (!lfos[lfoNumber - 1].seq)
lfos[lfoNumber - 1].seq = LFODescription::StepSequence();
if (!extendIfNecessary(lfos[lfoNumber - 1].seq->steps, stepNumber, Default::numLFOSteps))
return false;
lfos[lfoNumber - 1].seq->steps[stepNumber - 1] = *value * 0.01f;
}
}
break;
case hash("lfo&_wave&"): // also lfo&_wave
{
const auto lfoNumber = opcode.parameters.front();
const auto subNumber = opcode.parameters[1];
if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoWaveRange)) {
if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs))
return false;
lfos[lfoNumber - 1].sub[subNumber - 1].wave = static_cast<LFOWave>(*value);
}
}
break;
case hash("lfo&_offset&"): // also lfo&_offset
{
const auto lfoNumber = opcode.parameters.front();
const auto subNumber = opcode.parameters[1];
if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoOffsetRange)) {
if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs))
return false;
lfos[lfoNumber - 1].sub[subNumber - 1].offset = *value;
}
}
break;
case hash("lfo&_ratio&"): // also lfo&_ratio
{
const auto lfoNumber = opcode.parameters.front();
const auto subNumber = opcode.parameters[1];
if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoRatioRange)) {
if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs))
return false;
lfos[lfoNumber - 1].sub[subNumber - 1].ratio = *value;
}
}
break;
case hash("lfo&_scale&"): // also lfo&_scale
{
const auto lfoNumber = opcode.parameters.front();
const auto subNumber = opcode.parameters[1];
if (lfoNumber == 0 || subNumber == 0 || subNumber > config::maxLFOSubs)
return false;
if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs))
return false;
if (auto value = readOpcode(opcode.value, Default::lfoScaleRange)) {
if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs))
return false;
lfos[lfoNumber - 1].sub[subNumber - 1].scale = *value;
}
}
break;
// Modulation: LFO (targets)
case hash("lfo&_amplitude"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) {
const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Amplitude, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_pan"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::panCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Pan, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_width"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::widthCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Width, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_position"): // sfizz extension
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::positionCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Position, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_pitch"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::tuneCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Pitch, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_volume"):
{
const auto lfoNumber = opcode.parameters.front();
if (lfoNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::volumeCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Volume, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("lfo&_cutoff&"):
LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilCutoff, Default::filterCutoffModRange);
break;
case hash("lfo&_resonance&"):
LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilResonance, Default::filterResonanceModRange);
break;
case hash("lfo&_fil&_gain"):
LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilGain, Default::filterGainModRange);
break;
case hash("lfo&_eq&gain"):
LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqGain, Default::eqGainModRange);
break;
case hash("lfo&_eq&freq"):
LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqFrequency, Default::eqFrequencyModRange);
break;
case hash("lfo&_eq&bw"):
LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqBandwidth, Default::eqBandwidthModRange);
break;
// Modulation: Flex EG (targets)
case hash("eg&_amplitude"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) {
const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Amplitude, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("eg&_pan"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::panCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Pan, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("eg&_width"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::widthCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Width, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("eg&_position"): // sfizz extension
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::positionCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Position, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("eg&_pitch"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::tuneCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Pitch, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("eg&_volume"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (auto value = readOpcode(opcode.value, Default::volumeCCRange)) {
const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1);
const ModKey target = ModKey::createNXYZ(ModId::Volume, id);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("eg&_cutoff&"):
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilCutoff, Default::filterCutoffModRange);
break;
case hash("eg&_resonance&"):
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilResonance, Default::filterResonanceModRange);
break;
case hash("eg&_fil&_gain"):
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilGain, Default::filterGainModRange);
break;
case hash("eg&_eq&gain"):
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqGain, Default::eqGainModRange);
break;
case hash("eg&_eq&freq"):
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqFrequency, Default::eqFrequencyModRange);
break;
case hash("eg&_eq&bw"):
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqBandwidth, Default::eqBandwidthModRange);
break;
// Amplitude Envelope
case hash("ampeg_attack"):
setValueFromOpcode(opcode, amplitudeEG.attack, Default::egTimeRange);
break;
case hash("ampeg_decay"):
setValueFromOpcode(opcode, amplitudeEG.decay, Default::egTimeRange);
break;
case hash("ampeg_delay"):
setValueFromOpcode(opcode, amplitudeEG.delay, Default::egTimeRange);
break;
case hash("ampeg_hold"):
setValueFromOpcode(opcode, amplitudeEG.hold, Default::egTimeRange);
break;
case hash("ampeg_release"):
setValueFromOpcode(opcode, amplitudeEG.release, Default::egTimeRange);
break;
case hash("ampeg_start"):
setValueFromOpcode(opcode, amplitudeEG.start, Default::egPercentRange);
break;
case hash("ampeg_sustain"):
setValueFromOpcode(opcode, amplitudeEG.sustain, Default::egPercentRange);
break;
case hash("ampeg_vel&attack"):
if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange);
break;
case hash("ampeg_vel&decay"):
if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange);
break;
case hash("ampeg_vel&delay"):
if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange);
break;
case hash("ampeg_vel&hold"):
if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange);
break;
case hash("ampeg_vel&release"):
if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange);
break;
case hash("ampeg_vel&sustain"):
if (opcode.parameters.front() != 2)
return false; // Was not vel2...
setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange);
break;
case hash("ampeg_attack_oncc&"): // also ampeg_attackcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange))
amplitudeEG.ccAttack[opcode.parameters.back()] = *value;
break;
case hash("ampeg_decay_oncc&"): // also ampeg_decaycc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange))
amplitudeEG.ccDecay[opcode.parameters.back()] = *value;
break;
case hash("ampeg_delay_oncc&"): // also ampeg_delaycc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange))
amplitudeEG.ccDelay[opcode.parameters.back()] = *value;
break;
case hash("ampeg_hold_oncc&"): // also ampeg_holdcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange))
amplitudeEG.ccHold[opcode.parameters.back()] = *value;
break;
case hash("ampeg_release_oncc&"): // also ampeg_releasecc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange))
amplitudeEG.ccRelease[opcode.parameters.back()] = *value;
break;
case hash("ampeg_start_oncc&"): // also ampeg_startcc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::egOnCCPercentRange))
amplitudeEG.ccStart[opcode.parameters.back()] = *value;
break;
case hash("ampeg_sustain_oncc&"): // also ampeg_sustaincc&
if (opcode.parameters.back() >= config::numCCs)
return false;
if (auto value = readOpcode(opcode.value, Default::egOnCCPercentRange))
amplitudeEG.ccSustain[opcode.parameters.back()] = *value;
break;
// Flex envelopes
case hash("eg&_dynamic"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs))
return false;
auto& eg = flexEGs[egNumber - 1];
setValueFromOpcode(opcode, eg.dynamic, Default::flexEGDynamicRange);
}
break;
case hash("eg&_sustain"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs))
return false;
auto& eg = flexEGs[egNumber - 1];
setValueFromOpcode(opcode, eg.sustain, Default::flexEGSustainRange);
}
break;
case hash("eg&_time&"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs))
return false;
auto& eg = flexEGs[egNumber - 1];
const auto pointNumber = opcode.parameters[1];
if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints))
return false;
setValueFromOpcode(opcode, eg.points[pointNumber].time, Default::flexEGPointTimeRange);
}
break;
case hash("eg&_level&"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs))
return false;
auto& eg = flexEGs[egNumber - 1];
const auto pointNumber = opcode.parameters[1];
if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints))
return false;
setValueFromOpcode(opcode, eg.points[pointNumber].level, Default::flexEGPointLevelRange);
}
break;
case hash("eg&_shape&"):
{
const auto egNumber = opcode.parameters.front();
if (egNumber == 0)
return false;
if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs))
return false;
auto& eg = flexEGs[egNumber - 1];
const auto pointNumber = opcode.parameters[1];
if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints))
return false;
if (auto value = readOpcode(opcode.value, Default::flexEGPointShapeRange))
eg.points[pointNumber].setShape(*value);
}
break;
case hash("effect&"):
{
const auto effectNumber = opcode.parameters.back();
if (!effectNumber || effectNumber < 1 || effectNumber > config::maxEffectBuses)
break;
auto value = readOpcode<float>(opcode.value, { 0, 100 });
if (!value)
break;
if (static_cast<size_t>(effectNumber + 1) > gainToEffect.size())
gainToEffect.resize(effectNumber + 1);
gainToEffect[effectNumber] = *value / 100;
break;
}
// Ignored opcodes
case hash("hichan"):
case hash("lochan"):
case hash("sw_default"):
case hash("ampeg_depth"):
case hash("ampeg_vel&depth"):
break;
default:
return false;
#undef case_any_ccN
#undef LFO_EG_filter_EQ_target
}
return true;
}
bool sfz::Region::processGenericCc(const Opcode& opcode, Range<float> range, const ModKey& target)
{
if (!opcode.isAnyCcN())
return false;
const auto ccNumber = opcode.parameters.back();
if (ccNumber >= config::numCCs)
return false;
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.source.id() == ModId::Controller &&
x.source.parameters().cc == ccNumber &&
x.target == target;
});
Connection *conn;
if (it != connections.end())
conn = &*it;
else {
connections.emplace_back();
conn = &connections.back();
conn->source = ModKey::createCC(ccNumber, 0, 0, 0, 0);
conn->target = target;
}
//
ModKey::Parameters p = conn->source.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->source = ModKey(ModId::Controller, {}, p);
}
return true;
}
bool sfz::Region::isSwitchedOn() const noexcept
{
return keySwitched && previousKeySwitched && sequenceSwitched && pitchSwitched && bpmSwitched && aftertouchSwitched && ccSwitched.all();
}
bool sfz::Region::registerNoteOn(int noteNumber, float velocity, float randValue) noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
if (keyswitchRange.containsWithEnd(noteNumber)) {
if (keyswitch) {
if (*keyswitch == noteNumber)
keySwitched = true;
else
keySwitched = false;
}
if (keyswitchDown && *keyswitchDown == noteNumber)
keySwitched = true;
if (keyswitchUp && *keyswitchUp == noteNumber)
keySwitched = false;
}
const bool keyOk = keyRange.containsWithEnd(noteNumber);
if (keyOk) {
// Sequence activation
sequenceCounter += 1;
if ((sequenceCounter % sequenceLength) == sequencePosition - 1)
sequenceSwitched = true;
else
sequenceSwitched = false;
if (previousNote) {
if (*previousNote == noteNumber)
previousKeySwitched = true;
else
previousKeySwitched = false;
}
}
if (!isSwitchedOn())
return false;
if (!triggerOnNote)
return false;
if (previousNote && !(previousKeySwitched && noteNumber != *previousNote))
return false;
const bool velOk = velocityRange.containsWithEnd(velocity);
const bool randOk = randRange.contains(randValue) || (randValue == 1.0f && randRange.getEnd() == 1.0f);
const bool firstLegatoNote = (trigger == SfzTrigger::first && midiState.getActiveNotes() == 1);
const bool attackTrigger = (trigger == SfzTrigger::attack);
const bool notFirstLegatoNote = (trigger == SfzTrigger::legato && midiState.getActiveNotes() > 1);
return keyOk && velOk && randOk && (attackTrigger || firstLegatoNote || notFirstLegatoNote);
}
bool sfz::Region::registerNoteOff(int noteNumber, float velocity, float randValue) noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
if (keyswitchRange.containsWithEnd(noteNumber)) {
if (keyswitchDown && *keyswitchDown == noteNumber)
keySwitched = false;
if (keyswitchUp && *keyswitchUp == noteNumber)
keySwitched = true;
}
if (!isSwitchedOn())
return false;
if (!triggerOnNote)
return false;
// Prerequisites
const bool keyOk = keyRange.containsWithEnd(noteNumber);
const bool velOk = velocityRange.containsWithEnd(velocity);
const bool randOk = randRange.contains(randValue);
if (!(velOk && keyOk && randOk))
return false;
// Release logic
if (trigger == SfzTrigger::release_key)
return true;
if (trigger == SfzTrigger::release) {
if (midiState.getCCValue(sustainCC) < sustainThreshold)
return true;
// If we reach this part, we're storing the notes to delay their release on CC up
// This is handled by the Synth object
delayedReleases.emplace_back(noteNumber, midiState.getNoteVelocity(noteNumber));
}
return false;
}
bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept
{
ASSERT(ccValue >= 0.0f && ccValue <= 1.0f);
if (ccConditions.getWithDefault(ccNumber).containsWithEnd(ccValue))
ccSwitched.set(ccNumber, true);
else
ccSwitched.set(ccNumber, false);
if (!isSwitchedOn())
return false;
if (!triggerOnCC)
return false;
if (ccTriggers.contains(ccNumber) && ccTriggers[ccNumber].containsWithEnd(ccValue))
return true;
return false;
}
void sfz::Region::registerPitchWheel(float pitch) noexcept
{
if (bendRange.containsWithEnd(pitch))
pitchSwitched = true;
else
pitchSwitched = false;
}
void sfz::Region::registerAftertouch(uint8_t aftertouch) noexcept
{
if (aftertouchRange.containsWithEnd(aftertouch))
aftertouchSwitched = true;
else
aftertouchSwitched = false;
}
void sfz::Region::registerTempo(float secondsPerQuarter) noexcept
{
const float bpm = 60.0f / secondsPerQuarter;
if (bpmRange.containsWithEnd(bpm))
bpmSwitched = true;
else
bpmSwitched = false;
}
float sfz::Region::getBasePitchVariation(float noteNumber, float velocity) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
std::uniform_int_distribution<int> pitchDistribution { -pitchRandom, pitchRandom };
auto pitchVariationInCents = pitchKeytrack * (noteNumber - pitchKeycenter); // note difference with pitch center
pitchVariationInCents += tune; // sample tuning
pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose
pitchVariationInCents += static_cast<int>(velocity) * pitchVeltrack; // track velocity
pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes
return centsFactor(pitchVariationInCents);
}
float sfz::Region::getBaseVolumedB(int noteNumber) const noexcept
{
fast_real_distribution<float> volumeDistribution { -ampRandom, ampRandom };
auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator);
baseVolumedB += globalVolume;
baseVolumedB += masterVolume;
baseVolumedB += groupVolume;
if (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key)
baseVolumedB -= rtDecay * midiState.getNoteDuration(noteNumber);
return baseVolumedB;
}
float sfz::Region::getBaseGain() const noexcept
{
float baseGain = amplitude;
baseGain *= globalAmplitude;
baseGain *= masterAmplitude;
baseGain *= groupAmplitude;
return baseGain;
}
float sfz::Region::getPhase() const noexcept
{
float phase;
if (oscillatorPhase >= 0) {
phase = oscillatorPhase * (1.0f / 360.0f);
phase -= static_cast<float>(static_cast<int>(phase));
} else {
fast_real_distribution<float> phaseDist { 0.0001f, 0.9999f };
phase = phaseDist(Random::randomGenerator);
}
return phase;
}
uint64_t sfz::Region::getOffset(Oversampling factor) const noexcept
{
std::uniform_int_distribution<int64_t> offsetDistribution { 0, offsetRandom };
uint64_t finalOffset = offset + offsetDistribution(Random::randomGenerator);
for (const auto& mod: offsetCC)
finalOffset += static_cast<uint64_t>(mod.data * midiState.getCCValue(mod.cc));
return Default::offsetRange.clamp(finalOffset) * static_cast<uint64_t>(factor);
}
float sfz::Region::getDelay() const noexcept
{
fast_real_distribution<float> delayDistribution { 0, delayRandom };
return delay + delayDistribution(Random::randomGenerator);
}
uint32_t sfz::Region::trueSampleEnd(Oversampling factor) const noexcept
{
if (sampleEnd <= 0)
return 0;
return min(static_cast<uint32_t>(sampleEnd), loopRange.getEnd()) * static_cast<uint32_t>(factor);
}
uint32_t sfz::Region::loopStart(Oversampling factor) const noexcept
{
return loopRange.getStart() * static_cast<uint32_t>(factor);
}
uint32_t sfz::Region::loopEnd(Oversampling factor) const noexcept
{
return loopRange.getEnd() * static_cast<uint32_t>(factor);
}
float sfz::Region::getNoteGain(int noteNumber, float velocity) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float baseGain { 1.0f };
// Amplitude key tracking
baseGain *= db2mag(ampKeytrack * static_cast<float>(noteNumber - ampKeycenter));
// Crossfades related to the note number
baseGain *= crossfadeIn(crossfadeKeyInRange, noteNumber, crossfadeKeyCurve);
baseGain *= crossfadeOut(crossfadeKeyOutRange, noteNumber, crossfadeKeyCurve);
// Amplitude velocity tracking
baseGain *= velocityCurve(velocity);
// Crossfades related to velocity
baseGain *= crossfadeIn(crossfadeVelInRange, velocity, crossfadeVelCurve);
baseGain *= crossfadeOut(crossfadeVelOutRange, velocity, crossfadeVelCurve);
return baseGain;
}
float sfz::Region::getCrossfadeGain() const noexcept
{
float gain { 1.0f };
// Crossfades due to CC states
for (const auto& ccData : crossfadeCCInRange) {
const auto ccValue = midiState.getCCValue(ccData.cc);
const auto crossfadeRange = ccData.data;
gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve);
}
for (const auto& ccData : crossfadeCCOutRange) {
const auto ccValue = midiState.getCCValue(ccData.cc);
const auto crossfadeRange = ccData.data;
gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve);
}
return gain;
}
float sfz::Region::velocityCurve(float velocity) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float gain;
if (velCurve)
gain = velCurve->evalNormalized(velocity);
else
gain = velocity * velocity;
gain = std::fabs(ampVeltrack) * (1.0f - gain);
gain = (ampVeltrack < 0) ? gain : (1.0f - gain);
return gain;
}
uint8_t offsetAndClamp(uint8_t key, int offset, sfz::Range<uint8_t> range)
{
const int offsetKey { key + offset };
if (offsetKey > std::numeric_limits<uint8_t>::max())
return range.getEnd();
if (offsetKey < std::numeric_limits<uint8_t>::min())
return range.getStart();
return range.clamp(static_cast<uint8_t>(offsetKey));
}
void sfz::Region::offsetAllKeys(int offset) noexcept
{
// Offset key range
if (keyRange != Default::keyRange) {
const auto start = keyRange.getStart();
const auto end = keyRange.getEnd();
keyRange.setStart(offsetAndClamp(start, offset, Default::keyRange));
keyRange.setEnd(offsetAndClamp(end, offset, Default::keyRange));
}
pitchKeycenter = offsetAndClamp(pitchKeycenter, offset, Default::keyRange);
// Offset key switches
if (keyswitchRange != Default::keyRange) {
const auto start = keyswitchRange.getStart();
const auto end = keyswitchRange.getEnd();
keyswitchRange.setStart(offsetAndClamp(start, offset, Default::keyRange));
keyswitchRange.setEnd(offsetAndClamp(end, offset, Default::keyRange));
}
if (keyswitchUp)
keyswitchUp = offsetAndClamp(*keyswitchUp, offset, Default::keyRange);
if (keyswitch)
keyswitch = offsetAndClamp(*keyswitch, offset, Default::keyRange);
if (keyswitchDown)
keyswitchDown = offsetAndClamp(*keyswitchDown, offset, Default::keyRange);
if (previousNote)
previousNote = offsetAndClamp(*previousNote, offset, Default::keyRange);
// Offset crossfade ranges
if (crossfadeKeyInRange != Default::crossfadeKeyInRange) {
const auto start = crossfadeKeyInRange.getStart();
const auto end = crossfadeKeyInRange.getEnd();
crossfadeKeyInRange.setStart(offsetAndClamp(start, offset, Default::keyRange));
crossfadeKeyInRange.setEnd(offsetAndClamp(end, offset, Default::keyRange));
}
if (crossfadeKeyOutRange != Default::crossfadeKeyOutRange) {
const auto start = crossfadeKeyOutRange.getStart();
const auto end = crossfadeKeyOutRange.getEnd();
crossfadeKeyOutRange.setStart(offsetAndClamp(start, offset, Default::keyRange));
crossfadeKeyOutRange.setEnd(offsetAndClamp(end, offset, Default::keyRange));
}
}
float sfz::Region::getGainToEffectBus(unsigned number) const noexcept
{
if (number >= gainToEffect.size())
return 0.0;
return gainToEffect[number];
}
float sfz::Region::getBendInCents(float bend) const noexcept
{
return bend > 0.0f ? bend * static_cast<float>(bendUp) : -bend * static_cast<float>(bendDown);
}
sfz::Region::Connection& sfz::Region::getOrCreateConnection(const ModKey& source, const ModKey& target)
{
auto pred = [&source, &target](const Connection& c)
{
return c.source == source && c.target == target;
};
auto it = std::find_if(connections.begin(), connections.end(), pred);
if (it != connections.end())
return *it;
sfz::Region::Connection c;
c.source = source;
c.target = target;
connections.push_back(c);
return connections.back();
}
bool sfz::Region::disabled() const noexcept
{
return (sampleEnd == 0);
}