From b1f7a4bd660d2ce616996c61f38e39e2c7045a24 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 8 Nov 2020 10:26:45 +0100 Subject: [PATCH] Change the way the defaults and bounds for the opcodes are handled and read --- benchmarks/BM_opcodeSpec.cpp | 72 ++++ benchmarks/BM_opcodeSpec.h | 14 + benchmarks/BM_opcodeSpec_def.cpp | 3 + benchmarks/CMakeLists.txt | 1 + src/CMakeLists.txt | 1 + src/sfizz/Config.h | 4 + src/sfizz/Curve.cpp | 6 +- src/sfizz/Defaults.cpp | 143 ++++++++ src/sfizz/Defaults.h | 417 ++++++++++------------- src/sfizz/EGDescription.h | 44 +-- src/sfizz/EQDescription.h | 10 +- src/sfizz/EQPool.h | 6 +- src/sfizz/Effects.h | 5 +- src/sfizz/FilePool.h | 6 +- src/sfizz/FilterDescription.h | 14 +- src/sfizz/FilterPool.cpp | 4 +- src/sfizz/FilterPool.h | 8 +- src/sfizz/FlexEGDescription.h | 10 +- src/sfizz/LFODescription.h | 21 +- src/sfizz/Opcode.cpp | 235 +++++++------ src/sfizz/Opcode.h | 88 +---- src/sfizz/Region.cpp | 510 ++++++++++++++++------------- src/sfizz/Region.h | 116 +++---- src/sfizz/SfzHelpers.h | 9 +- src/sfizz/Smoothers.cpp | 6 +- src/sfizz/Smoothers.h | 5 - src/sfizz/Synth.cpp | 65 ++-- src/sfizz/SynthConfig.h | 4 +- src/sfizz/SynthMessaging.cpp | 71 +++- src/sfizz/SynthPrivate.h | 4 +- src/sfizz/Voice.cpp | 7 +- src/sfizz/effects/Apan.cpp | 12 +- src/sfizz/effects/Apan.h | 16 +- src/sfizz/effects/Compressor.cpp | 12 +- src/sfizz/effects/Disto.cpp | 27 +- src/sfizz/effects/Eq.cpp | 9 +- src/sfizz/effects/Filter.cpp | 9 +- src/sfizz/effects/Fverb.cpp | 29 +- src/sfizz/effects/Gain.cpp | 3 +- src/sfizz/effects/Gate.cpp | 8 +- src/sfizz/effects/Lofi.cpp | 6 +- src/sfizz/effects/Rectify.cpp | 3 +- src/sfizz/effects/Strings.cpp | 6 +- src/sfizz/effects/Strings.h | 4 +- src/sfizz/effects/Width.cpp | 3 +- tests/FilesT.cpp | 15 +- tests/OpcodeT.cpp | 328 ++++++++++++++----- tests/RegionValueComputationsT.cpp | 6 +- tests/RegionValuesT.cpp | 335 ++++++++++++------- tests/SynthT.cpp | 6 +- tests/TestFiles/note_offset.sfz | 12 - 51 files changed, 1623 insertions(+), 1135 deletions(-) create mode 100644 benchmarks/BM_opcodeSpec.cpp create mode 100644 benchmarks/BM_opcodeSpec.h create mode 100644 benchmarks/BM_opcodeSpec_def.cpp create mode 100644 src/sfizz/Defaults.cpp delete mode 100644 tests/TestFiles/note_offset.sfz diff --git a/benchmarks/BM_opcodeSpec.cpp b/benchmarks/BM_opcodeSpec.cpp new file mode 100644 index 00000000..da31a30a --- /dev/null +++ b/benchmarks/BM_opcodeSpec.cpp @@ -0,0 +1,72 @@ +// 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 "BM_opcodeSpec.h" +#include +#include +#include +#include +#include +#include + +class OpcodeSpecFixture : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + std::random_device rd { }; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 0.0f, 1.0f }; + value = dist(gen); + } + + void TearDown(const ::benchmark::State& /* state */) { + + } + + float value; + float returned; +}; + +BENCHMARK_DEFINE_F(OpcodeSpecFixture, ConstexprClamp)(benchmark::State& state) { + for (auto _ : state) + { + if (constexprSpec.flags | (1 << 2)) + returned = constexprSpec.bounds.clamp(value); + benchmark::DoNotOptimize(returned); + } +} + +BENCHMARK_DEFINE_F(OpcodeSpecFixture, ConstexprDontClamp)(benchmark::State& state) { + for (auto _ : state) + { + if (constexprSpec.flags | (1 << 1)) + returned = constexprSpec.bounds.clamp(value); + benchmark::DoNotOptimize(returned); + } +} + +BENCHMARK_DEFINE_F(OpcodeSpecFixture, ConstClamp)(benchmark::State& state) { + for (auto _ : state) + { + if (constSpec.flags | (1 << 2)) + returned = constSpec.bounds.clamp(value); + benchmark::DoNotOptimize(returned); + } +} + +BENCHMARK_DEFINE_F(OpcodeSpecFixture, ConstDontClamp)(benchmark::State& state) { + for (auto _ : state) + { + if (constSpec.flags | (1 << 1)) + returned = constSpec.bounds.clamp(value); + benchmark::DoNotOptimize(returned); + } +} + +BENCHMARK_REGISTER_F(OpcodeSpecFixture, ConstexprClamp); +BENCHMARK_REGISTER_F(OpcodeSpecFixture, ConstexprDontClamp); +BENCHMARK_REGISTER_F(OpcodeSpecFixture, ConstClamp); +BENCHMARK_REGISTER_F(OpcodeSpecFixture, ConstDontClamp); +BENCHMARK_MAIN(); diff --git a/benchmarks/BM_opcodeSpec.h b/benchmarks/BM_opcodeSpec.h new file mode 100644 index 00000000..8efcbe43 --- /dev/null +++ b/benchmarks/BM_opcodeSpec.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Range.h" + +template +struct OpcodeSpec +{ + T defaultValue; + sfz::Range bounds; + int flags { 0 }; +}; + +constexpr OpcodeSpec constexprSpec { 0.0f, sfz::Range(0.0f, 0.5f), 1 << 2 }; +extern const OpcodeSpec constSpec; diff --git a/benchmarks/BM_opcodeSpec_def.cpp b/benchmarks/BM_opcodeSpec_def.cpp new file mode 100644 index 00000000..076dd27d --- /dev/null +++ b/benchmarks/BM_opcodeSpec_def.cpp @@ -0,0 +1,3 @@ +#include "BM_opcodeSpec.h" + +const OpcodeSpec constSpec { 0.0f, sfz::Range(0.0f, 0.5f), 1 << 2 }; diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 89d7c8e3..77652c3e 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -38,6 +38,7 @@ sfizz_add_benchmark(bm_mapVsArray BM_mapVsArray.cpp) sfizz_add_benchmark(bm_random BM_random.cpp) sfizz_add_benchmark(bm_clamp BM_clamp.cpp) sfizz_add_benchmark(bm_allWithin BM_allWithin.cpp) +sfizz_add_benchmark(bm_opcodeSpec BM_opcodeSpec.cpp BM_opcodeSpec_def.cpp) sfizz_add_benchmark(bm_logger BM_logger.cpp) sfizz_add_benchmark(bm_smoothers BM_smoothers.cpp) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d548788c..d3c1f2bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -137,6 +137,7 @@ set(SFIZZ_SOURCES sfizz/Wavetables.cpp sfizz/Tuning.cpp sfizz/RegionSet.cpp + sfizz/Defaults.cpp sfizz/PolyphonyGroup.cpp sfizz/VoiceManager.cpp sfizz/VoiceStealing.cpp diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 451a5bcc..faa06de3 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -150,6 +150,10 @@ namespace config { (int(polyphony * config::overflowVoiceMultiplier) < int(config::maxVoices)) ? int(polyphony * config::overflowVoiceMultiplier) : int(config::maxVoices); } + /** + * @brief The smoothing time constant per "smooth" steps + */ + constexpr float smoothTauPerStep { 3e-3 }; } // namespace config } // namespace sfz diff --git a/src/sfizz/Curve.cpp b/src/sfizz/Curve.cpp index 8d204a39..b4dcb93f 100644 --- a/src/sfizz/Curve.cpp +++ b/src/sfizz/Curve.cpp @@ -21,7 +21,7 @@ Curve Curve::buildCurveFromHeader( { Curve curve; bool fillStatus[NumValues] = {}; - const Range fullRange { -HUGE_VALF, +HUGE_VALF }; + const OpcodeSpec fullRange {0.0f, Range(-HUGE_VALF, +HUGE_VALF), 0 }; auto setPoint = [&curve, &fillStatus](int i, float x) { curve._points[i] = x; @@ -40,7 +40,7 @@ Curve Curve::buildCurveFromHeader( if (index >= NumValues) continue; - auto valueOpt = readOpcode(opc.value, fullRange); + auto valueOpt = opc.read(fullRange); if (!valueOpt) continue; @@ -268,7 +268,7 @@ void CurveSet::addCurveFromHeader(absl::Span members) Curve::Interpolator itp = Curve::Interpolator::Linear; if (const Opcode* opc = findOpcode(hash("curve_index"))) { - if (auto opt = readOpcode(opc->value, {0, 255})) + if (auto opt = opc->read(Default::curveCC)) curveIndex = *opt; else DBG("Invalid value for curve index: " << opc->value); diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp new file mode 100644 index 00000000..b7a77769 --- /dev/null +++ b/src/sfizz/Defaults.cpp @@ -0,0 +1,143 @@ +#include "Defaults.h" + +namespace sfz { + +namespace Default { +constexpr auto uint32_t_max = std::numeric_limits::max(); + +extern const OpcodeSpec delay { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec delayRandom { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec offset { 0, Range(0, uint32_t_max), kEnforceLowerBound }; +extern const OpcodeSpec offsetMod { 0, Range(0, uint32_t_max), kEnforceLowerBound }; +extern const OpcodeSpec offsetRandom { 0, Range(0, uint32_t_max), kEnforceLowerBound }; +extern const OpcodeSpec sampleEnd { uint32_t_max, Range(0, uint32_t_max), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec sampleCount { 0, Range(0, uint32_t_max), 0 }; +extern const OpcodeSpec loopRange { 0, Range(0, uint32_t_max), 0 }; +extern const OpcodeSpec loopCrossfade { 1e-3, Range(1e-3, 1.0f), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec oscillatorPhase { 0.0f, Range(0.0f, 1.0f), 0 }; +extern const OpcodeSpec oscillatorMode { 0, Range(0, 2), kIgnoreOOB }; +extern const OpcodeSpec oscillatorMulti { 1, Range(1, config::oscillatorsPerVoice), kIgnoreOOB }; +extern const OpcodeSpec oscillatorDetune { 0.0f, Range(-12000.0f, 12000.0f), 0 }; +extern const OpcodeSpec oscillatorDetuneMod { 0.0f, Range(-12000.0f, 12000.0f), 0 }; +extern const OpcodeSpec oscillatorModDepth { 0.0f, Range(0.0f, 10000.0f), kEnforceLowerBound }; +extern const OpcodeSpec oscillatorModDepthMod { 0.0f, Range(0.0f, 10000.0f), kEnforceLowerBound }; +extern const OpcodeSpec oscillatorQuality { 1, Range(0, 3), kIgnoreOOB }; +extern const OpcodeSpec group { 0, Range(0, uint32_t_max), 0 }; +extern const OpcodeSpec offTime { 6e-3f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec polyphony { config::maxVoices, Range(0, config::maxVoices), 0 }; +extern const OpcodeSpec notePolyphony { config::maxVoices, Range(0, config::maxVoices), 0 }; +extern const OpcodeSpec key { 60, Range(0, 127), kIgnoreOOB | kCanBeNote }; +extern const OpcodeSpec midi7 { 0, Range(0, 127), kIgnoreOOB }; +extern const OpcodeSpec float7 { 0.0f , Range(0.0f, 127.0f), kIgnoreOOB }; +extern const OpcodeSpec bend { 0.0f, Range(-8192.0f, 8192.0f), kIgnoreOOB }; +extern const OpcodeSpec normalized { 0.0f, Range(0.0f, 1.0f), kIgnoreOOB }; +extern const OpcodeSpec bipolar { 0.0f, Range(-1.0f, 1.0f), kIgnoreOOB }; +extern const OpcodeSpec ccNumber { 0, Range(0, config::numCCs), kIgnoreOOB }; +extern const OpcodeSpec smoothCC { 0, Range(0, 100), kIgnoreOOB }; +extern const OpcodeSpec curveCC { 0, Range(0, 255), kIgnoreOOB }; +extern const OpcodeSpec sustainCC { 64, Range(0, 127), kIgnoreOOB }; +extern const OpcodeSpec sustainThreshold { 0.0039f, Range(0.0f, 1.0f), kIgnoreOOB }; +extern const OpcodeSpec bpm { 0.0f, Range(0.0f, 500.0f), kEnforceLowerBound }; +extern const OpcodeSpec sequence { 1, Range(1, 100), kIgnoreOOB }; +extern const OpcodeSpec volume { 0.0f, Range(-144.0f, 48.0f), 0 }; +extern const OpcodeSpec volumeMod { 0.0f, Range(-144.0f, 48.0f), 0 }; +extern const OpcodeSpec amplitude { 100.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec amplitudeMod { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec pan { 0.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec panMod { 0.0f, Range(-200.0f, 200.0f), 0 }; +extern const OpcodeSpec position { 0.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec positionMod { 0.0f, Range(-200.0f, 200.0f), 0 }; +extern const OpcodeSpec width { 100.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec widthMod { 0.0f, Range(-200.0f, 200.0f), 0 }; +extern const OpcodeSpec crossfadeIn { 0, Range(0, 127), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec crossfadeInNorm { 0.0f, Range(0.0f, 1.0f), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec crossfadeOut { 127, Range(0, 127), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec crossfadeOutNorm { 1.0f, Range(0.0f, 1.0f), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec ampKeytrack { 0.0f, Range(-96.0f, 12.0f), 0 }; +extern const OpcodeSpec ampVeltrack { 100.0f, Range(-100.0f, 100.0f), kIgnoreOOB }; +extern const OpcodeSpec ampVelcurve { 0.0f, Range(0.0f, 1.0f), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec ampRandom { 0.0f, Range(0.0f, 24.0f), kEnforceLowerBound }; +extern const OpcodeSpec rtDecay { 0.0f, Range(0.0f, 200.0f), kEnforceLowerBound }; +extern const OpcodeSpec filterCutoff { 0.0f, Range(0.0f, 20000.0f), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec filterCutoffMod { 0.0f, Range(-12000.0f, 12000.0f), kEnforceLowerBound }; +extern const OpcodeSpec filterResonance { 0.0f, Range(0.0f, 96.0f), kEnforceLowerBound }; +extern const OpcodeSpec filterResonanceMod { 0.0f, Range(0.0f, 96.0f), kEnforceLowerBound }; +extern const OpcodeSpec filterGain { 0.0f, Range(-96.0f, 96.0f), 0 }; +extern const OpcodeSpec filterGainMod { 0.0f, Range(-96.0f, 96.0f), 0 }; +extern const OpcodeSpec filterRandom { 0.0f, Range(0.0f, 12000.0f), kEnforceLowerBound }; +extern const OpcodeSpec filterKeytrack { 0, Range(0, 1200), kEnforceLowerBound }; +extern const OpcodeSpec filterVeltrack { 0, Range(-12000, 12000), 0 }; +extern const OpcodeSpec eqBandwidth { 1.0f, Range(0.001f, 4.0f), kEnforceLowerBound }; +extern const OpcodeSpec eqBandwidthMod { 0.0f, Range(-4.0f, 4.0f), 0 }; +extern const OpcodeSpec eqFrequency { 0.0f, Range(0.0f, 30000.0f), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec eqFrequencyMod { 0.0f, Range(-30000.0f, 30000.0f), 0 }; +extern const OpcodeSpec eqGain { 0.0f, Range(-96.0f, 96.0f), 0 }; +extern const OpcodeSpec eqGainMod { 0.0f, Range(-96.0f, 96.0f), 0 }; +extern const OpcodeSpec eqVel2Frequency { 0.0f, Range(-30000.0f, 30000.0f), 0 }; +extern const OpcodeSpec eqVel2Gain { 0.0f, Range(-96.0f, 96.0f), 0 }; +extern const OpcodeSpec pitchKeytrack { 100, Range(-1200, 1200), 0 }; +extern const OpcodeSpec pitchRandom { 0.0f, Range(0.0f, 12000.0f), kEnforceLowerBound }; +extern const OpcodeSpec pitchVeltrack { 0, Range(-12000, 12000), 0 }; +extern const OpcodeSpec transpose { 0, Range(-127, 127), kIgnoreOOB }; +extern const OpcodeSpec pitch { 0.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec pitchMod { 0.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec bendUp { 200.0f, Range(-12000.0f, 12000.0f), 0 }; +extern const OpcodeSpec bendDown { -200.0f, Range(-12000.0f, 12000.0f), 0 }; +extern const OpcodeSpec bendStep { 1.0f, Range(1.0f, 1200.0f), kIgnoreOOB }; +extern const OpcodeSpec lfoFreq { 0.0f, Range(0.0f, 100.0f), 0 }; +extern const OpcodeSpec lfoFreqMod { 0.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec lfoBeats { 0.0f, Range(0.0f, 1000.0f), 0 }; +extern const OpcodeSpec lfoBeatsMod { 0.0f, Range(-1000.0f, 1000.0f), 0 }; +extern const OpcodeSpec lfoPhase { 0.0f, Range(0.0f, 1.0f), 0 }; +extern const OpcodeSpec lfoDelay { 0.0f, Range(0.0f, 30.0f), 0 }; +extern const OpcodeSpec lfoFade { 0.0f, Range(0.0f, 30.0f), 0 }; +extern const OpcodeSpec lfoCount { 0, Range(0, 1000), 0 }; +extern const OpcodeSpec lfoSteps { 0, Range(0, static_cast(config::maxLFOSteps)), 0 }; +extern const OpcodeSpec lfoStepX { 0.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec lfoWave { 0, Range(0, 15), 0 }; +extern const OpcodeSpec lfoOffset { 0.0f, Range(-1.0f, 1.0f), 0 }; +extern const OpcodeSpec lfoRatio { 1.0f, Range(0.0f, 100.0f), 0 }; +extern const OpcodeSpec lfoScale { 1.0f, Range(0.0f, 1.0f), 0 }; +extern const OpcodeSpec egTime { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec egRelease { 0.001f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec egTimeMod { 0.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec egPercent { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec egPercentMod { 0.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec egDepth { 0.0f, Range(-12000.0f, 12000.0f), 0 }; +extern const OpcodeSpec egVel2Depth { 0.0f, Range(-12000.0f, 12000.0f), 0 }; +extern const OpcodeSpec flexEGDynamic { 0, Range(0, 1), kIgnoreOOB }; +extern const OpcodeSpec flexEGSustain { 0, Range(0, 100), kIgnoreOOB }; +extern const OpcodeSpec flexEGPointTime { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec flexEGPointLevel { 0.0f, Range(-1.0f, 1.0f), kIgnoreOOB }; +extern const OpcodeSpec flexEGPointShape { 0.0f, Range(-100.0f, 100.0f), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec sampleQuality { 1, Range(1, 10), kIgnoreOOB }; +extern const OpcodeSpec octaveOffset { 0, Range(-10, 10), 0 }; +extern const OpcodeSpec noteOffset { 0, Range(-127, 127), 0 }; +extern const OpcodeSpec effect { 0.0f, Range(0.0f, 100.0f), 0 }; +extern const OpcodeSpec apanWaveform { 0, Range(0, std::numeric_limits::max()), 0 }; +extern const OpcodeSpec apanFrequency { 0.0f, Range(0.0f, std::numeric_limits::max()), kEnforceLowerBound }; +extern const OpcodeSpec apanPhase { 0.5f, Range(0.0f, 1.0f), 0 }; +extern const OpcodeSpec apanLevel { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec distoTone { 100.0f, Range(0.0f, 100.0f), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec distoDepth { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound | kEnforceUpperBound }; +extern const OpcodeSpec distoStages { 1, Range(1, maxDistoStages), kEnforceLowerBound }; +extern const OpcodeSpec compAttack { 0.005f, Range(0.0f, 10.0f), kEnforceLowerBound }; +extern const OpcodeSpec compRelease { 0.05f, Range(0.0f, 10.0f), kEnforceLowerBound }; +extern const OpcodeSpec compThreshold { 0.0f, Range(-100.0f, 0.0f), kIgnoreOOB }; +extern const OpcodeSpec compRatio { 1.0f, Range(1.0f, 50.0f), kIgnoreOOB }; +extern const OpcodeSpec compGain { 0.0f, Range(-100.0f, 100.0f), 0 }; +extern const OpcodeSpec fverbSize { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; +extern const OpcodeSpec fverbPredelay { 0.0f, Range(0.0f, 10.0f), kEnforceLowerBound }; +extern const OpcodeSpec fverbTone { 100.0f, Range(0.0f, 100.0f), kIgnoreOOB }; +extern const OpcodeSpec fverbDamp { 0.0f, Range(0.0f, 100.0f), kIgnoreOOB }; +extern const OpcodeSpec gateAttack { 0.005f, Range(0.0f, 10.0f), kEnforceLowerBound }; +extern const OpcodeSpec gateRelease { 0.05f, Range(0.0f, 10.0f), kEnforceLowerBound }; +extern const OpcodeSpec gateHold { 0.0f, Range(0.0f, 10.0f), kEnforceLowerBound }; +extern const OpcodeSpec gateThreshold { 0.0f, Range(-100.0f, 0.0f), kIgnoreOOB }; +extern const OpcodeSpec lofiBitred { 0.0f, Range(0.0f, 100.0f), kIgnoreOOB }; +extern const OpcodeSpec lofiDecim { 0.0f, Range(0.0f, 100.0f), kIgnoreOOB }; +extern const OpcodeSpec rectify { 0.0f, Range(0.0f, 100.0f), kIgnoreOOB }; +extern const OpcodeSpec stringsNumber { maxStrings, Range(0, maxStrings), kEnforceLowerBound }; +} // namespace Default + +} // namespace sfz diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 2ebca118..899094d1 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -38,253 +38,196 @@ enum class SfzSelfMask { mask, dontMask }; namespace sfz { + +enum OpcodeFlags : int { + kIgnoreOOB = 1, + kEnforceLowerBound = 1 << 1, + kEnforceUpperBound = 1 << 2, + kCanBeNote = 1 << 3, +}; + +template +struct OpcodeSpec +{ + T value; + Range bounds; + int flags; +}; + namespace Default { - // The categories match http://sfzformat.com/ - // ******* SFZ 1 ******* - // Sound source: sample playback - constexpr float delay { 0.0 }; - constexpr float delayRandom { 0.0 }; - constexpr Range delayRange { 0.0, 100.0 }; - constexpr int64_t offset { 0 }; - constexpr int64_t offsetRandom { 0 }; - constexpr Range offsetRange { 0, std::numeric_limits::max() }; - constexpr Range offsetCCRange = offsetRange; - constexpr Range sampleEndRange { 0, std::numeric_limits::max() }; - constexpr Range sampleCountRange { 0, std::numeric_limits::max() }; - constexpr SfzLoopMode loopMode { SfzLoopMode::no_loop }; - constexpr Range loopRange { 0, std::numeric_limits::max() }; - constexpr float loopCrossfade { 1e-3 }; - constexpr Range loopCrossfadeRange { loopCrossfade, 1.0 }; + extern const OpcodeSpec delay; + extern const OpcodeSpec delayRandom; + extern const OpcodeSpec offset; + extern const OpcodeSpec offsetMod; + extern const OpcodeSpec offsetRandom; + extern const OpcodeSpec sampleEnd; + extern const OpcodeSpec sampleCount; + extern const OpcodeSpec loopRange; + extern const OpcodeSpec loopCrossfade; + extern const OpcodeSpec oscillatorPhase; + extern const OpcodeSpec oscillatorMode; + extern const OpcodeSpec oscillatorMulti; + extern const OpcodeSpec oscillatorDetune; + extern const OpcodeSpec oscillatorDetuneMod; + extern const OpcodeSpec oscillatorModDepth; + extern const OpcodeSpec oscillatorModDepthMod; + extern const OpcodeSpec oscillatorQuality; + extern const OpcodeSpec group; + extern const OpcodeSpec offTime; + extern const OpcodeSpec polyphony; + extern const OpcodeSpec notePolyphony; + extern const OpcodeSpec key; + extern const OpcodeSpec midi7; + extern const OpcodeSpec float7; + extern const OpcodeSpec bend; + extern const OpcodeSpec normalized; + extern const OpcodeSpec bipolar; + extern const OpcodeSpec ccNumber; + extern const OpcodeSpec curveCC; + extern const OpcodeSpec smoothCC; + extern const OpcodeSpec sustainCC; + extern const OpcodeSpec sustainThreshold; + extern const OpcodeSpec bpm; + extern const OpcodeSpec sequence; + extern const OpcodeSpec volume; + extern const OpcodeSpec volumeMod; + extern const OpcodeSpec amplitude; + extern const OpcodeSpec amplitudeMod; + extern const OpcodeSpec pan; + extern const OpcodeSpec panMod; + extern const OpcodeSpec position; + extern const OpcodeSpec positionMod; + extern const OpcodeSpec width; + extern const OpcodeSpec widthMod; + extern const OpcodeSpec crossfadeIn; + extern const OpcodeSpec crossfadeInNorm; + extern const OpcodeSpec crossfadeOut; + extern const OpcodeSpec crossfadeOutNorm; + extern const OpcodeSpec ampKeytrack; + extern const OpcodeSpec ampVeltrack; + extern const OpcodeSpec ampVelcurve; + extern const OpcodeSpec ampRandom; + extern const OpcodeSpec rtDecay; + extern const OpcodeSpec filterCutoff; + extern const OpcodeSpec filterCutoffMod; + extern const OpcodeSpec filterResonance; + extern const OpcodeSpec filterResonanceMod; + extern const OpcodeSpec filterGain; + extern const OpcodeSpec filterGainMod; + extern const OpcodeSpec filterRandom; + extern const OpcodeSpec filterKeytrack; + extern const OpcodeSpec filterVeltrack; + extern const OpcodeSpec eqBandwidth; + extern const OpcodeSpec eqBandwidthMod; + extern const OpcodeSpec eqFrequency; + extern const OpcodeSpec eqFrequencyMod; + extern const OpcodeSpec eqGain; + extern const OpcodeSpec eqGainMod; + extern const OpcodeSpec eqVel2Frequency; + extern const OpcodeSpec eqVel2Gain; + extern const OpcodeSpec pitchKeytrack; + extern const OpcodeSpec pitchRandom; + extern const OpcodeSpec pitchVeltrack; + extern const OpcodeSpec transpose; + extern const OpcodeSpec pitch; + extern const OpcodeSpec pitchMod; + extern const OpcodeSpec bendUp; + extern const OpcodeSpec bendDown; + extern const OpcodeSpec bendStep; + extern const OpcodeSpec lfoFreq; + extern const OpcodeSpec lfoFreqMod; + extern const OpcodeSpec lfoBeats; + extern const OpcodeSpec lfoBeatsMod; + extern const OpcodeSpec lfoPhase; + extern const OpcodeSpec lfoDelay; + extern const OpcodeSpec lfoFade; + extern const OpcodeSpec lfoCount; + extern const OpcodeSpec lfoSteps; + extern const OpcodeSpec lfoStepX; + extern const OpcodeSpec lfoWave; + extern const OpcodeSpec lfoOffset; + extern const OpcodeSpec lfoRatio; + extern const OpcodeSpec lfoScale; + extern const OpcodeSpec egTime; + extern const OpcodeSpec egRelease; + extern const OpcodeSpec egTimeMod; + extern const OpcodeSpec egPercent; + extern const OpcodeSpec egPercentMod; + extern const OpcodeSpec egDepth; + extern const OpcodeSpec egVel2Depth; + extern const OpcodeSpec flexEGDynamic; + extern const OpcodeSpec flexEGSustain; + extern const OpcodeSpec flexEGPointTime; + extern const OpcodeSpec flexEGPointLevel; + extern const OpcodeSpec flexEGPointShape; + extern const OpcodeSpec sampleQuality; + extern const OpcodeSpec octaveOffset; + extern const OpcodeSpec noteOffset; + extern const OpcodeSpec effect; + extern const OpcodeSpec apanWaveform; + extern const OpcodeSpec apanFrequency; + extern const OpcodeSpec apanPhase; + extern const OpcodeSpec apanLevel; + extern const OpcodeSpec distoTone; + extern const OpcodeSpec distoDepth; + extern const OpcodeSpec distoStages; + extern const OpcodeSpec compAttack; + extern const OpcodeSpec compRelease; + extern const OpcodeSpec compThreshold; + extern const OpcodeSpec compRatio; + extern const OpcodeSpec compGain; + extern const OpcodeSpec fverbSize; + extern const OpcodeSpec fverbPredelay; + extern const OpcodeSpec fverbTone; + extern const OpcodeSpec fverbDamp; + extern const OpcodeSpec gateAttack; + extern const OpcodeSpec gateRelease; + extern const OpcodeSpec gateHold; + extern const OpcodeSpec gateThreshold; + extern const OpcodeSpec lofiBitred; + extern const OpcodeSpec lofiDecim; + extern const OpcodeSpec rectify; + extern const OpcodeSpec stringsNumber; - // common defaults - constexpr Range midi7Range { 0, 127 }; - constexpr Range float7Range { 0.0f, 127.0f }; - constexpr Range normalizedRange { 0.0f, 1.0f }; - constexpr Range symmetricNormalizedRange { -1.0, 1.0 }; + // Boolean default values + constexpr bool rtDead { false }; + constexpr bool checkSustain { true }; // sustain_sw + constexpr bool checkSostenuto { true }; // sostenuto_sw - // Wavetable oscillator - constexpr float oscillatorPhase { 0.0 }; - constexpr Range oscillatorPhaseRange { -1.0, 1.0 }; - constexpr int oscillatorMode { 0 }; - constexpr int oscillatorMulti { 1 }; - constexpr Range oscillatorModeRange { 0, 2 }; - constexpr Range oscillatorMultiRange { 1, config::oscillatorsPerVoice }; - constexpr float oscillatorDetune { 0 }; - constexpr Range oscillatorDetuneRange { -12000, 12000 }; - constexpr Range oscillatorDetuneCCRange { -12000, 12000 }; - constexpr float oscillatorModDepth { 0 }; - constexpr Range oscillatorModDepthRange { 0, 10000 }; // depth%, allowed to be >100 for FM - constexpr Range oscillatorModDepthCCRange { 0, 10000 }; - constexpr int oscillatorQuality { 1 }; - constexpr Range oscillatorQualityRange { 0, 3 }; + // Default/max count for objects + constexpr int numEQs { 3 }; + constexpr int numFilters { 2 }; + constexpr int numFlexEGs { 4 }; + constexpr int numFlexEGPoints { 8 }; + constexpr int numLFOs { 4 }; + constexpr int numLFOSubs { 2 }; + constexpr int numLFOSteps { 8 }; + constexpr int maxDistoStages { 4 }; + constexpr unsigned maxStrings { 88 }; - // Instrument setting: voice lifecycle - constexpr uint32_t group { 0 }; - constexpr Range groupRange { 0, std::numeric_limits::max() }; - constexpr SfzOffMode offMode { SfzOffMode::fast }; - constexpr float offTime { 6e-3f }; - constexpr Range polyphonyRange { 0, config::maxVoices }; + // Default values for enums + constexpr SfzTrigger trigger { SfzTrigger::attack }; + constexpr SfzOffMode offMode { SfzOffMode::fast }; + constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current }; constexpr SfzSelfMask selfMask { SfzSelfMask::mask }; + constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power }; + constexpr SfzCrossfadeCurve crossfadeVelCurve { SfzCrossfadeCurve::power }; + constexpr SfzCrossfadeCurve crossfadeCCCurve { SfzCrossfadeCurve::power }; - // Region logic: key mapping - constexpr Range keyRange { 0, 127 }; - constexpr auto velocityRange = normalizedRange; - - // Region logic: MIDI conditions - constexpr Range channelRange { 1, 16 }; - constexpr Range midiChannelRange { 0, 15 }; - constexpr Range smoothCCRange { 0, 100 }; - constexpr float smoothTauPerStep { 3e-3 }; - constexpr Range curveCCRange { 0, 255 }; - constexpr Range ccNumberRange { 0, config::numCCs }; - constexpr auto ccValueRange = normalizedRange; - constexpr Range bendRange = { -8192, 8192 }; - constexpr Range bendValueRange = symmetricNormalizedRange; - constexpr int bend { 0 }; - constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current }; - - // Region logic: internal conditions - constexpr Range randRange { 0.0, 1.0 }; - constexpr Range aftertouchRange { 0, 127 }; - constexpr uint8_t aftertouch { 0 }; - constexpr Range bpmRange { 0.0, 500.0 }; - constexpr float bpm { 120.0 }; - constexpr uint8_t sequenceLength{ 1 }; - constexpr uint8_t sequencePosition{ 1 }; - constexpr Range sequenceRange { 1, 100 }; - - // Region logic: Triggers - constexpr SfzTrigger trigger { SfzTrigger::attack }; - constexpr Range ccTriggerValueRange = normalizedRange; - - // Performance parameters: amplifier - constexpr float globalVolume { -7.35f }; - constexpr float volume { 0.0f }; - constexpr Range volumeRange { -144.0, 48.0 }; - constexpr Range volumeCCRange { -144.0, 48.0 }; - constexpr float amplitude { 100.0 }; - constexpr Range amplitudeRange { 0.0, 1e8 }; - constexpr float pan { 0.0 }; - constexpr Range panRange { -100.0, 100.0 }; - constexpr Range panCCRange { -200.0, 200.0 }; - constexpr float position { 0.0 }; - constexpr Range positionRange { -100.0, 100.0 }; - constexpr Range positionCCRange { -200.0, 200.0 }; - constexpr float width { 100.0 }; - constexpr Range widthRange { -100.0, 100.0 }; - constexpr Range widthCCRange { -200.0, 200.0 }; - constexpr uint8_t ampKeycenter { 60 }; - constexpr float ampKeytrack { 0.0 }; - constexpr Range ampKeytrackRange { -96, 12 }; - constexpr float ampVeltrack { 100.0 }; - constexpr Range ampVeltrackRange { -100.0, 100.0 }; - constexpr Range ampVelcurveRange { 0.0, 1.0 }; - constexpr float ampRandom { 0.0 }; - constexpr Range ampRandomRange { 0.0, 24.0 }; - constexpr Range crossfadeKeyInRange { 0, 0 }; - constexpr Range crossfadeKeyOutRange { 127, 127 }; + // Default values for ranges + constexpr Range crossfadeKeyInRange { 0, 0 }; + constexpr Range crossfadeKeyOutRange { 127, 127 }; constexpr Range crossfadeVelInRange { 0.0f, 0.0f }; constexpr Range crossfadeVelOutRange { 1.0f, 1.0f }; constexpr Range crossfadeCCInRange { 0.0f, 0.0f }; constexpr Range crossfadeCCOutRange { 1.0f, 1.0f }; - constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power }; - constexpr SfzCrossfadeCurve crossfadeVelCurve { SfzCrossfadeCurve::power }; - constexpr SfzCrossfadeCurve crossfadeCCCurve { SfzCrossfadeCurve::power }; - constexpr float rtDecay { 0.0f }; - constexpr bool rtDead { false }; - constexpr Range rtDecayRange { 0.0f, 200.0f }; - // Performance parameters: Filters - constexpr int numFilters { 2 }; - constexpr float filterCutoff { 0 }; - constexpr float filterResonance { 0 }; - constexpr float filterGain { 0 }; - constexpr int filterKeytrack { 0 }; - constexpr uint8_t filterKeycenter { 60 }; - constexpr float filterRandom { 0 }; - constexpr int filterVeltrack { 0 }; - constexpr float filterCutoffCC { 0 }; - constexpr float filterResonanceCC { 0 }; - constexpr float filterGainCC { 0 }; - constexpr Range filterCutoffRange { 0.0f, 20000.0f }; - constexpr Range filterCutoffModRange { -12000, 12000 }; - constexpr Range filterGainRange { -96.0f, 96.0f }; - constexpr Range filterGainModRange { -96.0f, 96.0f }; - constexpr Range filterKeytrackRange { 0, 1200 }; - constexpr Range filterRandomRange { 0, 12000 }; - constexpr Range filterVeltrackRange { -12000, 12000 }; - constexpr Range filterResonanceRange { 0.0f, 96.0f }; - constexpr Range filterResonanceModRange { 0.0f, 96.0f }; + // Various defaut values + // e.g. "additional" or multiple defautl values + constexpr int freewheelingQuality { 10 }; + constexpr float globalVolume { -7.35f }; + constexpr float defaultEQFreq [numEQs] { 50.0f, 500.0f, 5000.0f }; +} // namespace Default - // Performance parameters: EQ - constexpr int numEQs { 3 }; - constexpr float eqBandwidth { 1.0f }; - constexpr float eqBandwidthCC { 0.0f }; - constexpr float eqFrequencyUnset { 0.0f }; - constexpr float eqFrequency1 { 50.0f }; - constexpr float eqFrequency2 { 500.0f }; - constexpr float eqFrequency3 { 5000.0f }; - constexpr float eqFrequencyCC { 0.0f }; - constexpr float eqGain { 0.0f }; - constexpr float eqGainCC { 0.0f }; - constexpr float eqVel2frequency { 0.0f }; - constexpr float eqVel2gain { 0.0f }; - constexpr Range eqBandwidthRange { 0.001f, 4.0f }; - constexpr Range eqBandwidthModRange { -4.0f, 4.0f }; - constexpr Range eqFrequencyRange { 0.0f, 30000.0f }; - constexpr Range eqFrequencyModRange { -30000.0f, 30000.0f }; - constexpr Range eqGainRange { -96.0f, 96.0f }; - constexpr Range eqGainModRange { -96.0f, 96.0f }; - - // Performance parameters: pitch - constexpr uint8_t pitchKeycenter { 60 }; - constexpr int pitchKeytrack { 100 }; - constexpr Range pitchKeytrackRange { -1200, 1200 }; - constexpr float pitchRandom { 0 }; - constexpr Range pitchRandomRange { 0, 12000 }; - constexpr int pitchVeltrack { 0 }; - constexpr Range pitchVeltrackRange { -12000, 12000 }; - constexpr int transpose { 0 }; - constexpr Range transposeRange { -127, 127 }; - constexpr float tune { 0 }; - constexpr Range tuneRange { -12000, 12000 }; // ±100 in SFZv1, more in ARIA - constexpr Range tuneCCRange { -12000, 12000 }; - constexpr Range bendBoundRange { -12000, 12000 }; - constexpr Range bendStepRange { 1, 1200 }; - constexpr int bendUp { 200 }; // No range here because the bounds can be inverted - constexpr int bendDown { -200 }; - constexpr int bendStep { 1 }; - constexpr uint8_t bendSmooth { 0 }; - - // Modulation: LFO - constexpr int numLFOs { 4 }; - constexpr int numLFOSubs { 2 }; - constexpr int numLFOSteps { 8 }; - constexpr Range lfoFreqRange { 0.0, 100.0 }; - constexpr Range lfoFreqModRange { -100.0, 100.0 }; - constexpr Range lfoBeatsRange { 0.0, 1000.0 }; - constexpr Range lfoBeatsModRange { -1000.0, 1000.0 }; - constexpr Range lfoPhaseRange { 0.0, 1.0 }; - constexpr Range lfoDelayRange { 0.0, 30.0 }; - constexpr Range lfoFadeRange { 0.0, 30.0 }; - constexpr Range lfoCountRange { 0, 1000 }; - constexpr Range lfoStepsRange { 0, static_cast(config::maxLFOSteps) }; - constexpr Range lfoStepXRange { -100.0, 100.0 }; - constexpr Range lfoWaveRange { 0, 15 }; - constexpr Range lfoOffsetRange { -1.0, 1.0 }; - constexpr Range lfoRatioRange { 0.0, 100.0 }; - constexpr Range lfoScaleRange { 0.0, 1.0 }; - - // Envelope generators - constexpr float attack { 0 }; - constexpr float decay { 0 }; - constexpr float delayEG { 0 }; - constexpr float hold { 0 }; - constexpr float release { 0 }; - constexpr float ampegRelease { 0.001 }; // Default release to avoid clicks - constexpr float vel2release { 0.0f }; - constexpr float start { 0.0 }; - constexpr float sustain { 100.0 }; - constexpr uint16_t sustainCC { 64 }; - constexpr float sustainThreshold { 0.0039f }; // sforzando default (0.5f/127.0f) - constexpr float vel2sustain { 0.0 }; - constexpr int depth { 0 }; - constexpr Range egTimeRange { 0.0, 100.0 }; - constexpr Range egPercentRange { 0.0, 100.0 }; - constexpr Range egDepthRange { -12000, 12000 }; - constexpr Range egOnCCTimeRange { -100.0, 100.0 }; - constexpr Range egOnCCPercentRange { -100.0, 100.0 }; - constexpr Range pitchEgDepthRange { -12000.0, 12000.0 }; - constexpr Range filterEgDepthRange { -12000.0, 12000.0 }; - - // Flex envelope generators - constexpr int numFlexEGs { 4 }; - constexpr int numFlexEGPoints { 8 }; - constexpr int flexEGDynamic { 0 }; - constexpr int flexEGSustain { 0 }; - constexpr float flexEGPointTime { 0 }; - constexpr float flexEGPointLevel { 0 }; - constexpr float flexEGPointShape { 0 }; - constexpr Range flexEGDynamicRange { 0, 1 }; - constexpr Range flexEGSustainRange { 0, 100 }; - constexpr Range flexEGPointTimeRange { 0.0f, 100.0f }; - constexpr Range flexEGPointLevelRange { -1.0f, 1.0f }; - constexpr Range flexEGPointShapeRange { -100.0f, 100.0f }; - - // ***** SFZ v2 ******** - constexpr int sampleQuality { 1 }; - constexpr int sampleQualityInFreewheelingMode { 10 }; // for future use, possibly excessive - constexpr Range sampleQualityRange { 1, 10 }; // sample_quality - - constexpr bool checkSustain { true }; // sustain_sw - constexpr bool checkSostenuto { true }; // sostenuto_sw - constexpr Range octaveOffsetRange { -10, 10 }; // octave_offset - constexpr Range noteOffsetRange { -127, 127 }; // note_offset - - constexpr Range apanWaveformRange { 0, std::numeric_limits::max() }; - constexpr Range apanFrequencyRange { 0, std::numeric_limits::max() }; - constexpr Range apanPhaseRange { 0.0, 1.0 }; - constexpr Range apanLevelRange { 0.0, 100.0 }; -} -} +} // namespace sfz diff --git a/src/sfizz/EGDescription.h b/src/sfizz/EGDescription.h index 5cfe0d94..89edfde2 100644 --- a/src/sfizz/EGDescription.h +++ b/src/sfizz/EGDescription.h @@ -66,21 +66,21 @@ struct EGDescription { EGDescription& operator=(const EGDescription&) = default; EGDescription& operator=(EGDescription&&) = default; - float attack { Default::attack }; - float decay { Default::decay }; - float delay { Default::delayEG }; - float hold { Default::hold }; - float release { Default::release }; - float start { Default::start }; - float sustain { Default::sustain }; - int depth { Default::depth }; - float vel2attack { Default::attack }; - float vel2decay { Default::decay }; - float vel2delay { Default::delayEG }; - float vel2hold { Default::hold }; - float vel2release { Default::vel2release }; - float vel2sustain { Default::vel2sustain }; - int vel2depth { Default::depth }; + float attack { Default::egTime.value }; + float decay { Default::egTime.value }; + float delay { Default::egTime.value }; + float hold { Default::egTime.value }; + float release { Default::egTime.value }; + float start { Default::egPercent.bounds.getStart() }; + float sustain { Default::egPercent.bounds.getEnd() }; + float depth { Default::egDepth.value }; + float vel2attack { Default::egTimeMod.value }; + float vel2decay { Default::egTimeMod.value }; + float vel2delay { Default::egTimeMod.value }; + float vel2hold { Default::egTimeMod.value }; + float vel2release { Default::egPercentMod.value }; + float vel2sustain { Default::egPercentMod.value }; + float vel2depth { Default::egVel2Depth.value }; CCMap ccAttack; CCMap ccDecay; @@ -104,7 +104,7 @@ struct EGDescription { for (auto& mod: ccAttack) { returnedValue += state.getCCValue(mod.cc) * mod.data; } - return Default::egTimeRange.clamp(returnedValue); + return Default::egTime.bounds.clamp(returnedValue); } /** * @brief Get the decay with possibly a CC modifier and a velocity modifier @@ -120,7 +120,7 @@ struct EGDescription { for (auto& mod: ccDecay) { returnedValue += state.getCCValue(mod.cc) * mod.data; } - return Default::egTimeRange.clamp(returnedValue); + return Default::egTime.bounds.clamp(returnedValue); } /** * @brief Get the delay with possibly a CC modifier and a velocity modifier @@ -136,7 +136,7 @@ struct EGDescription { for (auto& mod: ccDelay) { returnedValue += state.getCCValue(mod.cc) * mod.data; } - return Default::egTimeRange.clamp(returnedValue); + return Default::egTime.bounds.clamp(returnedValue); } /** * @brief Get the holding duration with possibly a CC modifier and a velocity modifier @@ -152,7 +152,7 @@ struct EGDescription { for (auto& mod: ccHold) { returnedValue += state.getCCValue(mod.cc) * mod.data; } - return Default::egTimeRange.clamp(returnedValue); + return Default::egTime.bounds.clamp(returnedValue); } /** * @brief Get the release duration with possibly a CC modifier and a velocity modifier @@ -168,7 +168,7 @@ struct EGDescription { for (auto& mod: ccRelease) { returnedValue += state.getCCValue(mod.cc) * mod.data; } - return Default::egTimeRange.clamp(returnedValue); + return Default::egTime.bounds.clamp(returnedValue); } /** * @brief Get the starting level with possibly a CC modifier and a velocity modifier @@ -184,7 +184,7 @@ struct EGDescription { for (auto& mod: ccStart) { returnedValue += state.getCCValue(mod.cc) * mod.data; } - return Default::egPercentRange.clamp(returnedValue); + return Default::egPercent.bounds.clamp(returnedValue); } /** * @brief Get the sustain level with possibly a CC modifier and a velocity modifier @@ -200,7 +200,7 @@ struct EGDescription { for (auto& mod: ccSustain) { returnedValue += state.getCCValue(mod.cc) * mod.data; } - return Default::egPercentRange.clamp(returnedValue); + return Default::egPercent.bounds.clamp(returnedValue); } LEAK_DETECTOR(EGDescription); }; diff --git a/src/sfizz/EQDescription.h b/src/sfizz/EQDescription.h index c109a4a0..246d545b 100644 --- a/src/sfizz/EQDescription.h +++ b/src/sfizz/EQDescription.h @@ -14,11 +14,11 @@ namespace sfz { struct EQDescription { - float bandwidth { Default::eqBandwidth }; - float frequency { Default::eqFrequencyUnset }; - float gain { Default::eqGain }; - float vel2frequency { Default::eqVel2frequency }; - float vel2gain { Default::eqVel2gain }; + float bandwidth { Default::eqBandwidth.value }; + float frequency { Default::eqFrequency.value }; + float gain { Default::eqGain.value }; + float vel2frequency { Default::eqVel2Frequency.value }; + float vel2gain { Default::eqVel2Gain.value }; EqType type { EqType::kEqPeak }; }; } diff --git a/src/sfizz/EQPool.h b/src/sfizz/EQPool.h index 4670daac..63ba5dde 100644 --- a/src/sfizz/EQPool.h +++ b/src/sfizz/EQPool.h @@ -43,9 +43,9 @@ private: Resources& resources; const EQDescription* description; std::unique_ptr eq; - float baseBandwidth { Default::eqBandwidth }; - float baseFrequency { Default::eqFrequency1 }; - float baseGain { Default::eqGain }; + float baseBandwidth { Default::eqBandwidth.value }; + float baseFrequency { Default::eqFrequency.value }; + float baseGain { Default::eqGain.value }; bool prepared { false }; ModMatrix::TargetId gainTarget; ModMatrix::TargetId frequencyTarget; diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 2056cac2..838557a8 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -6,6 +6,7 @@ #pragma once #include "AudioBuffer.h" +#include "Defaults.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" #include @@ -178,8 +179,8 @@ private: std::vector> _effects; AudioBuffer _inputs { EffectChannels, config::defaultSamplesPerBlock }; AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; - float _gainToMain = 0.0; - float _gainToMix = 0.0; + float _gainToMain { Default::effect.value }; + float _gainToMix { Default::effect.value }; }; } // namespace sfz diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index fa67db72..2c77f6d9 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -51,10 +51,10 @@ using FileAudioBuffer = AudioBuffer; struct FileInformation { - uint32_t end { Default::sampleEndRange.getEnd() }; + uint32_t end { Default::sampleEnd.value }; uint32_t maxOffset { 0 }; - uint32_t loopBegin { Default::loopRange.getStart() }; - uint32_t loopEnd { Default::loopRange.getEnd() }; + uint32_t loopBegin { Default::loopRange.bounds.getStart() }; + uint32_t loopEnd { Default::loopRange.bounds.getEnd() }; bool hasLoop { false }; double sampleRate { config::defaultSampleRate }; int numChannels { 0 }; diff --git a/src/sfizz/FilterDescription.h b/src/sfizz/FilterDescription.h index 2f3193a0..7bdc39b6 100644 --- a/src/sfizz/FilterDescription.h +++ b/src/sfizz/FilterDescription.h @@ -14,13 +14,13 @@ namespace sfz { struct FilterDescription { - float cutoff { Default::filterCutoff }; - float resonance { Default::filterCutoff }; - float gain { Default::filterGain }; - int keytrack { Default::filterKeytrack }; - uint8_t keycenter { Default::filterKeycenter }; - int veltrack { Default::filterVeltrack }; - float random { Default::filterRandom }; + float cutoff { Default::filterCutoff.value }; + float resonance { Default::filterCutoff.value }; + float gain { Default::filterGain.value }; + int keytrack { Default::filterKeytrack.value }; + uint8_t keycenter { Default::key.value }; + int veltrack { Default::filterVeltrack.value }; + float random { Default::filterRandom.value }; FilterType type { FilterType::kFilterLpf2p }; }; } diff --git a/src/sfizz/FilterPool.cpp b/src/sfizz/FilterPool.cpp index d8c16e40..46b38d41 100644 --- a/src/sfizz/FilterPool.cpp +++ b/src/sfizz/FilterPool.cpp @@ -37,7 +37,7 @@ void sfz::FilterHolder::setup(const Region& region, unsigned filterId, int noteN baseCutoff *= centsFactor(keytrack); const auto veltrack = static_cast(description->veltrack) * velocity; baseCutoff *= centsFactor(veltrack); - baseCutoff = Default::filterCutoffRange.clamp(baseCutoff); + baseCutoff = Default::filterCutoff.bounds.clamp(baseCutoff); baseGain = description->gain; baseResonance = description->resonance; @@ -75,7 +75,7 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned for (size_t i = 0; i < numFrames; ++i) (*cutoffSpan)[i] *= centsFactor(mod[i]); } - sfz::clampAll(*cutoffSpan, Default::filterCutoffRange); + sfz::clampAll(*cutoffSpan, Default::filterCutoff.bounds); fill(*resonanceSpan, baseResonance); if (float* mod = mm.getModulation(resonanceTarget)) diff --git a/src/sfizz/FilterPool.h b/src/sfizz/FilterPool.h index d995e0db..13ecbb94 100644 --- a/src/sfizz/FilterPool.h +++ b/src/sfizz/FilterPool.h @@ -22,7 +22,7 @@ public: * @param noteNumber the triggering note number * @param velocity the triggering note velocity/value */ - void setup(const Region& region, unsigned filterId, int noteNumber = static_cast(Default::filterKeycenter), float velocity = 0); + void setup(const Region& region, unsigned filterId, int noteNumber = static_cast(Default::key.value), float velocity = 0); /** * @brief Process a block of stereo inputs * @@ -45,9 +45,9 @@ private: Resources& resources; const FilterDescription* description; std::unique_ptr filter; - float baseCutoff { Default::filterCutoff }; - float baseResonance { Default::filterResonance }; - float baseGain { Default::filterGain }; + float baseCutoff { Default::filterCutoff.value }; + float baseResonance { Default::filterResonance.value }; + float baseGain { Default::filterGain.value }; ModMatrix::TargetId gainTarget; ModMatrix::TargetId cutoffTarget; ModMatrix::TargetId resonanceTarget; diff --git a/src/sfizz/FlexEGDescription.h b/src/sfizz/FlexEGDescription.h index b8a0f59b..84d9078c 100644 --- a/src/sfizz/FlexEGDescription.h +++ b/src/sfizz/FlexEGDescription.h @@ -18,21 +18,21 @@ namespace FlexEGs { }; struct FlexEGPoint { - float time { Default::flexEGPointTime }; // duration until next step (s) - float level { Default::flexEGPointLevel }; // normalized amplitude + float time { Default::flexEGPointTime.value }; // duration until next step (s) + float level { Default::flexEGPointLevel.value }; // normalized amplitude void setShape(float shape); float shape() const noexcept { return shape_; } const Curve& curve() const; private: - float shape_ { Default::flexEGPointShape }; // 0: linear, positive: exp, negative: log + float shape_ { Default::flexEGPointShape.value }; // 0: linear, positive: exp, negative: log std::shared_ptr shapeCurve_; }; struct FlexEGDescription { - int dynamic { Default::flexEGDynamic }; // whether parameters can be modulated while EG runs - int sustain { Default::flexEGSustain }; // index of the sustain point (default to 0 in ARIA) + int dynamic { Default::flexEGDynamic.value }; // whether parameters can be modulated while EG runs + int sustain { Default::flexEGSustain.value }; // index of the sustain point (default to 0 in ARIA) std::vector points; // ARIA bool ampeg = false; // replaces the SFZv1 AmpEG (lowest with this bit wins) diff --git a/src/sfizz/LFODescription.h b/src/sfizz/LFODescription.h index 2628430f..ea312bca 100644 --- a/src/sfizz/LFODescription.h +++ b/src/sfizz/LFODescription.h @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once +#include "Defaults.h" #include #include @@ -27,17 +28,17 @@ struct LFODescription { LFODescription(); ~LFODescription(); static const LFODescription& getDefault(); - float freq = 0; // lfoN_freq - float beats = 0; // lfoN_beats - float phase0 = 0; // lfoN_phase - float delay = 0; // lfoN_delay - float fade = 0; // lfoN_fade - unsigned count = 0; // lfoN_count + float freq { Default::lfoFreq.value }; // lfoN_freq + float beats { Default::lfoBeats.value }; // lfoN_beats + float phase0 { Default::lfoPhase.value }; // lfoN_phase + float delay { Default::lfoDelay.value }; // lfoN_delay + float fade { Default::lfoFade.value }; // lfoN_fade + unsigned count { Default::lfoCount.value }; // lfoN_count struct Sub { - LFOWave wave = LFOWave::Triangle; // lfoN_wave[X] - float offset = 0; // lfoN_offset[X] - float ratio = 1; // lfoN_ratio[X] - float scale = 1; // lfoN_scale[X] + LFOWave wave { static_cast(Default::lfoWave.value) }; // lfoN_wave[X] + float offset { Default::lfoOffset.value }; // lfoN_offset[X] + float ratio { Default::lfoRatio.value }; // lfoN_ratio[X] + float scale { Default::lfoScale.value }; // lfoN_scale[X] }; struct StepSequence { std::vector steps {}; // lfoN_stepX - normalized to unity diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index 1a60ff78..87f2e109 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -117,6 +117,116 @@ OpcodeCategory Opcode::identifyCategory(absl::string_view name) return category; } +template +absl::optional readInt_(OpcodeSpec spec, absl::string_view v) +{ + size_t numberEnd = 0; + + if (numberEnd < v.size() && (v[numberEnd] == '+' || v[numberEnd] == '-')) + ++numberEnd; + + while (numberEnd < v.size() && absl::ascii_isdigit(v[numberEnd])) + ++numberEnd; + + if (numberEnd == 0 && (spec.flags & kCanBeNote)) + return readNoteValue(v); + + v = v.substr(0, numberEnd); + + int64_t returnedValue; + if (!absl::SimpleAtoi(v, &returnedValue)) + return absl::nullopt; + + if (returnedValue > static_cast(spec.bounds.getEnd())) { + if (spec.flags & kEnforceUpperBound) + return spec.bounds.getEnd(); + + if (spec.flags & kIgnoreOOB) + return {}; + } + + if (returnedValue < static_cast(spec.bounds.getStart())) { + if (spec.flags & kEnforceLowerBound) + return spec.bounds.getStart(); + + if (spec.flags & kIgnoreOOB) + return {}; + } + + T castValue = static_cast(returnedValue); + if ((castValue != returnedValue) & kIgnoreOOB) + return {}; + + return castValue; +} + +#define INSTANTIATE_FOR_INTEGRAL(T) \ + template <> \ + absl::optional Opcode::read(OpcodeSpec spec) const \ + { \ + return readInt_(spec, value); \ + } + +INSTANTIATE_FOR_INTEGRAL(uint8_t) +INSTANTIATE_FOR_INTEGRAL(uint16_t) +INSTANTIATE_FOR_INTEGRAL(uint32_t) +INSTANTIATE_FOR_INTEGRAL(int8_t) +INSTANTIATE_FOR_INTEGRAL(int16_t) +INSTANTIATE_FOR_INTEGRAL(int32_t) +INSTANTIATE_FOR_INTEGRAL(int64_t) + + +template +absl::optional readFloat_(OpcodeSpec spec, absl::string_view v) +{ + size_t numberEnd = 0; + + if (numberEnd < v.size() && (v[numberEnd] == '+' || v[numberEnd] == '-')) + ++numberEnd; + while (numberEnd < v.size() && absl::ascii_isdigit(v[numberEnd])) + ++numberEnd; + + if (numberEnd < v.size() && v[numberEnd] == '.') { + ++numberEnd; + while (numberEnd < v.size() && absl::ascii_isdigit(v[numberEnd])) + ++numberEnd; + } + + v = v.substr(0, numberEnd); + + float returnedValue; + if (!absl::SimpleAtof(v, &returnedValue)) + return absl::nullopt; + + if (returnedValue > static_cast(spec.bounds.getEnd())) { + if (spec.flags & kEnforceUpperBound) + return spec.bounds.getEnd(); + + if (spec.flags & kIgnoreOOB) + return {}; + } + + if (returnedValue < static_cast(spec.bounds.getStart())) { + if (spec.flags & kEnforceLowerBound) + return spec.bounds.getStart(); + + if (spec.flags & kIgnoreOOB) + return {}; + } + + return returnedValue; +} + +#define INSTANTIATE_FOR_FLOATING_POINT(T) \ + template <> \ + absl::optional Opcode::read(OpcodeSpec spec) const \ + { \ + return readFloat_(spec, value); \ + } + +INSTANTIATE_FOR_FLOATING_POINT(float) +INSTANTIATE_FOR_FLOATING_POINT(double) + absl::optional readNoteValue(absl::string_view value) { char noteLetter = absl::ascii_tolower(value.empty() ? '\0' : value.front()); @@ -167,56 +277,6 @@ absl::optional readNoteValue(absl::string_view value) return static_cast(noteNumber); } -/// -template ::value, int>> -absl::optional readOpcode(absl::string_view value, const Range& validRange) -{ - size_t numberEnd = 0; - - if (numberEnd < value.size() && (value[numberEnd] == '+' || value[numberEnd] == '-')) - ++numberEnd; - while (numberEnd < value.size() && absl::ascii_isdigit(value[numberEnd])) - ++numberEnd; - - value = value.substr(0, numberEnd); - - int64_t returnedValue; - if (!absl::SimpleAtoi(value, &returnedValue)) - return absl::nullopt; - - if (returnedValue > std::numeric_limits::max()) - returnedValue = std::numeric_limits::max(); - if (returnedValue < std::numeric_limits::min()) - returnedValue = std::numeric_limits::min(); - - return validRange.clamp(static_cast(returnedValue)); -} - -template ::value, int>> -absl::optional readOpcode(absl::string_view value, const Range& validRange) -{ - size_t numberEnd = 0; - - if (numberEnd < value.size() && (value[numberEnd] == '+' || value[numberEnd] == '-')) - ++numberEnd; - while (numberEnd < value.size() && absl::ascii_isdigit(value[numberEnd])) - ++numberEnd; - - if (numberEnd < value.size() && value[numberEnd] == '.') { - ++numberEnd; - while (numberEnd < value.size() && absl::ascii_isdigit(value[numberEnd])) - ++numberEnd; - } - - value = value.substr(0, numberEnd); - - float returnedValue; - if (!absl::SimpleAtof(value, &returnedValue)) - return absl::nullopt; - - return validRange.clamp(returnedValue); -} - absl::optional readBooleanFromOpcode(const Opcode& opcode) { // Cakewalk-style booleans, case-insensitive @@ -227,84 +287,13 @@ absl::optional readBooleanFromOpcode(const Opcode& opcode) // ARIA-style booleans? (seen in egN_dynamic=1 for example) // TODO check this - if (auto value = readOpcode(opcode.value, Range::wholeRange())) + const OpcodeSpec fullInt64 { 0, Range::wholeRange(), 0 }; + if (auto value = opcode.read(fullInt64)) return *value != 0; return absl::nullopt; } -template -void setValueFromOpcode(const Opcode& opcode, ValueType& target, const Range& validRange) -{ - auto value = readOpcode(opcode.value, validRange); - if (!value) // Try and read a note rather than a number - value = readNoteValue(opcode.value); - if (value) - target = *value; -} - -template -inline void setValueFromOpcode(const Opcode& opcode, absl::optional& target, const Range& validRange) -{ - auto value = readOpcode(opcode.value, validRange); - if (!value) // Try and read a note rather than a number - value = readNoteValue(opcode.value); - if (value) - target = *value; -} - -template -void setRangeEndFromOpcode(const Opcode& opcode, Range& target, const Range& validRange) -{ - auto value = readOpcode(opcode.value, validRange); - if (!value) // Try and read a note rather than a number - value = readNoteValue(opcode.value); - if (value) - target.setEnd(*value); -} - -template -void setRangeStartFromOpcode(const Opcode& opcode, Range& target, const Range& validRange) -{ - auto value = readOpcode(opcode.value, validRange); - if (!value) // Try and read a note rather than a number - value = readNoteValue(opcode.value); - if (value) - target.setStart(*value); -} - -template -void setCCPairFromOpcode(const Opcode& opcode, absl::optional>& target, const Range& validRange) -{ - auto value = readOpcode(opcode.value, validRange); - if (value && Default::ccNumberRange.containsWithEnd(opcode.parameters.back())) - target = { opcode.parameters.back(), *value }; - else - target = {}; -} - -/// -#define INSTANCIATE_FOR(T) \ - template absl::optional readOpcode(absl::string_view value, const Range& validRange); /*NOLINT(bugprone-macro-parentheses)*/ \ - template void setValueFromOpcode(const Opcode& opcode, T& target, const Range& validRange); /*NOLINT(bugprone-macro-parentheses)*/ \ - template void setValueFromOpcode(const Opcode& opcode, absl::optional& target, const Range& validRange); /*NOLINT(bugprone-macro-parentheses)*/ \ - template void setRangeEndFromOpcode(const Opcode& opcode, Range& target, const Range& validRange); /*NOLINT(bugprone-macro-parentheses)*/ \ - template void setRangeStartFromOpcode(const Opcode& opcode, Range& target, const Range& validRange); /*NOLINT(bugprone-macro-parentheses)*/ \ - template void setCCPairFromOpcode(const Opcode& opcode, absl::optional>& target, const Range& validRange); /*NOLINT(bugprone-macro-parentheses)*/ - -INSTANCIATE_FOR(float) -INSTANCIATE_FOR(double) -INSTANCIATE_FOR(int8_t) -INSTANCIATE_FOR(int16_t) -INSTANCIATE_FOR(int32_t) -INSTANCIATE_FOR(int64_t) -INSTANCIATE_FOR(uint8_t) -INSTANCIATE_FOR(uint16_t) -INSTANCIATE_FOR(uint32_t) -//INSTANCIATE_FOR(uint64_t) - -#undef INSTANCIATE_FOR - } // namespace sfz std::ostream &operator<<(std::ostream &os, const sfz::Opcode &opcode) diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index a6c3a1d6..51ff6088 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -101,6 +101,9 @@ struct Opcode { category == kOpcodeStepCcN || category == kOpcodeSmoothCcN; } + template + absl::optional read(OpcodeSpec spec) const; + private: static OpcodeCategory identifyCategory(absl::string_view name); LEAK_DETECTOR(Opcode); @@ -114,96 +117,11 @@ private: */ absl::optional readNoteValue(absl::string_view value); -/** - * @brief Read a value from the sfz file and cast it to the destination parameter along - * with a proper clamping into range if needed. This particular template version acts on - * integral target types, but can accept floats as an input. - * - * @tparam ValueType the target casting type - * @param value the string value to be read and stored - * @param validRange the range of admitted values - * @return absl::optional the cast value, or null - */ -template ::value, int> = 0> -absl::optional readOpcode(absl::string_view value, const Range& validRange); - -/** - * @brief Read a value from the sfz file and cast it to the destination parameter along - * with a proper clamping into range if needed. This particular template version acts on - * floating types. - * - * @tparam ValueType the target casting type - * @param value the string value to be read and stored - * @param validRange the range of admitted values - * @return absl::optional the cast value, or null - */ -template ::value, int> = 0> -absl::optional readOpcode(absl::string_view value, const Range& validRange); - /** * @brief Read a boolean value from the sfz file and cast it to the destination parameter. */ absl::optional readBooleanFromOpcode(const Opcode& opcode); -/** - * @brief Set a target parameter from an opcode value, with possibly a textual note rather - * than a number - * - * @tparam ValueType - * @param opcode the source opcode - * @param target the value to update - * @param validRange the range of admitted values used to clamp the opcode - */ -template -void setValueFromOpcode(const Opcode& opcode, ValueType& target, const Range& validRange); - -/** - * @brief Set a target parameter from an opcode value, with possibly a textual note rather - * than a number - * - * @tparam ValueType - * @param opcode the source opcode - * @param target the value to update - * @param validRange the range of admitted values used to clamp the opcode - */ -template -void setValueFromOpcode(const Opcode& opcode, absl::optional& target, const Range& validRange); - -/** - * @brief Set a target end of a range from an opcode value, with possibly a textual note rather - * than a number - * - * @tparam ValueType - * @param opcode the source opcode - * @param target the value to update - * @param validRange the range of admitted values used to clamp the opcode - */ -template -void setRangeEndFromOpcode(const Opcode& opcode, Range& target, const Range& validRange); - -/** - * @brief Set a target beginning of a range from an opcode value, with possibly a textual note rather - * than a number - * - * @tparam ValueType - * @param opcode the source opcode - * @param target the value to update - * @param validRange the range of admitted values used to clamp the opcode - */ -template -void setRangeStartFromOpcode(const Opcode& opcode, Range& target, const Range& validRange); - -/** - * @brief Set a CC modulation parameter from an opcode value. - * - * @tparam ValueType - * @param opcode the source opcode - * @param target the new CC modulation parameter - * @param validRange the range of admitted values used to clamp the opcode - */ -template -void setCCPairFromOpcode(const Opcode& opcode, absl::optional>& target, const Range& validRange); - } std::ostream &operator<<(std::ostream &os, const sfz::Opcode &opcode); diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index fbbee939..d84fbc52 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -33,6 +33,18 @@ bool extendIfNecessary(std::vector& vec, unsigned size, unsigned defaultCapac return true; } +sfz::Region::Region(int regionNumber, const MidiState& midiState, absl::string_view defaultPath) +: id{regionNumber}, midiState(midiState), defaultPath(std::move(defaultPath)) +{ + ccSwitched.set(); + + gainToEffect.reserve(5); // sufficient room for main and fx1-4 + gainToEffect.push_back(1.0); // contribute 100% into the main bus + + // Default amplitude release + amplitudeEG.release = Default::egRelease.value; +} + bool sfz::Region::parseOpcode(const Opcode& rawOpcode) { const Opcode opcode = rawOpcode.cleanUp(kOpcodeScopeRegion); @@ -45,7 +57,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash(x "_stepcc&"): \ case hash(x "_smoothcc&") - #define LFO_EG_filter_EQ_target(sourceKey, targetKey, range) \ + #define LFO_EG_filter_EQ_target(sourceKey, targetKey, spec) \ { \ const auto number = opcode.parameters.front(); \ if (number == 0) \ @@ -55,7 +67,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (!extendIfNecessary(filters, index + 1, Default::numFilters)) \ return false; \ \ - if (auto value = readOpcode(opcode.value, range)) { \ + if (auto value = opcode.read(spec)) { \ const ModKey source = ModKey::createNXYZ(sourceKey, id, number - 1); \ const ModKey target = ModKey::createNXYZ(targetKey, id, index); \ getOrCreateConnection(source, target).sourceDepth = *value; \ @@ -82,8 +94,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) { if (opcode.value == "-1") sampleQuality.reset(); - else - setValueFromOpcode(opcode, sampleQuality, Default::sampleQualityRange); + else if (auto value = opcode.read(Default::sampleQuality)) + sampleQuality = *value; break; } break; @@ -91,28 +103,29 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) *sampleId = sampleId->reversed(opcode.value == "reverse"); break; case hash("delay"): - setValueFromOpcode(opcode, delay, Default::delayRange); + delay = opcode.read(Default::delay).value_or(delay); break; case hash("delay_random"): - setValueFromOpcode(opcode, delayRandom, Default::delayRange); + delayRandom = opcode.read(Default::delayRandom).value_or(delayRandom); break; case hash("offset"): - setValueFromOpcode(opcode, offset, Default::offsetRange); + offset = opcode.read(Default::offset).value_or(offset); break; case hash("offset_random"): - setValueFromOpcode(opcode, offsetRandom, Default::offsetRange); + offsetRandom = opcode.read(Default::offsetRandom).value_or(offsetRandom); break; case hash("offset_oncc&"): // also offset_cc& if (opcode.parameters.back() > config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::offsetCCRange)) + if (auto value = opcode.read(Default::offsetMod)) offsetCC[opcode.parameters.back()] = *value; break; case hash("end"): - setValueFromOpcode(opcode, sampleEnd, Default::sampleEndRange); + sampleEnd = opcode.read(Default::sampleEnd).value_or(sampleEnd); break; case hash("count"): - setValueFromOpcode(opcode, sampleCount, Default::sampleCountRange); + if (auto value = opcode.read(Default::sampleCount)) + sampleCount = *value; break; case hash("loop_mode"): // also loopmode switch (hash(opcode.value)) { @@ -133,18 +146,20 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) } break; case hash("loop_end"): // also loopend - setRangeEndFromOpcode(opcode, loopRange, Default::loopRange); + if (auto value = opcode.read(Default::loopRange)) + loopRange.setEnd(*value); break; case hash("loop_start"): // also loopstart - setRangeStartFromOpcode(opcode, loopRange, Default::loopRange); + if (auto value = opcode.read(Default::loopRange)) + loopRange.setStart(*value); break; case hash("loop_crossfade"): - setValueFromOpcode(opcode, loopCrossfade, Default::loopCrossfadeRange); + loopCrossfade = opcode.read(Default::loopCrossfade).value_or(loopCrossfade); break; // Wavetable oscillator case hash("oscillator_phase"): - if (auto value = readOpcode(opcode.value, Default::oscillatorPhaseRange)) + if (auto value = opcode.read(Default::oscillatorPhase)) oscillatorPhase = (*value >= 0) ? wrapPhase(*value) : -1.0f; break; case hash("oscillator"): @@ -152,40 +167,42 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) oscillatorEnabled = *value ? OscillatorEnabled::On : OscillatorEnabled::Off; break; case hash("oscillator_mode"): - setValueFromOpcode(opcode, oscillatorMode, Default::oscillatorModeRange); + oscillatorMode = opcode.read(Default::oscillatorMode).value_or(oscillatorMode); break; case hash("oscillator_multi"): - setValueFromOpcode(opcode, oscillatorMulti, Default::oscillatorMultiRange); + oscillatorMulti = opcode.read(Default::oscillatorMulti).value_or(oscillatorMulti); break; case hash("oscillator_detune"): - setValueFromOpcode(opcode, oscillatorDetune, Default::oscillatorDetuneRange); + oscillatorDetune = opcode.read(Default::oscillatorDetune).value_or(oscillatorDetune); break; case_any_ccN("oscillator_detune"): - processGenericCc(opcode, Default::oscillatorDetuneCCRange, ModKey::createNXYZ(ModId::OscillatorDetune, id)); + processGenericCc(opcode, Default::oscillatorDetuneMod, + ModKey::createNXYZ(ModId::OscillatorDetune, id)); break; case hash("oscillator_mod_depth"): - if (auto value = readOpcode(opcode.value, Default::oscillatorModDepthRange)) + if (auto value = opcode.read(Default::oscillatorModDepth)) oscillatorModDepth = normalizePercents(*value); break; case_any_ccN("oscillator_mod_depth"): - processGenericCc(opcode, Default::oscillatorModDepthCCRange, ModKey::createNXYZ(ModId::OscillatorModDepth, id)); + processGenericCc(opcode, Default::oscillatorModDepthMod, + ModKey::createNXYZ(ModId::OscillatorModDepth, id)); break; case hash("oscillator_quality"): if (opcode.value == "-1") oscillatorQuality.reset(); - else - setValueFromOpcode(opcode, oscillatorQuality, Default::oscillatorQualityRange); + else if (auto value = opcode.read(Default::oscillatorQuality)) + oscillatorQuality = *value; break; // Instrument settings: voice lifecycle case hash("group"): // also polyphony_group - setValueFromOpcode(opcode, group, Default::groupRange); + group = opcode.read(Default::group).value_or(group); break; case hash("off_by"): // also offby if (opcode.value == "-1") offBy.reset(); - else - setValueFromOpcode(opcode, offBy, Default::groupRange); + else if (auto value = opcode.read(Default::group)) + offBy = *value; break; case hash("off_mode"): // also offmode switch (hash(opcode.value)) { @@ -204,14 +221,13 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; case hash("off_time"): offMode = SfzOffMode::time; - setValueFromOpcode(opcode, offTime, Default::egTimeRange); + offTime = opcode.read(Default::offTime).value_or(offTime); break; case hash("polyphony"): - if (auto value = readOpcode(opcode.value, Default::polyphonyRange)) - polyphony = *value; + polyphony = opcode.read(Default::polyphony).value_or(polyphony); break; case hash("note_polyphony"): - if (auto value = readOpcode(opcode.value, Default::polyphonyRange)) + if (auto value = opcode.read(Default::notePolyphony)) notePolyphony = *value; break; case hash("note_selfmask"): @@ -237,72 +253,79 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; // Region logic: key mapping case hash("lokey"): - triggerOnNote = true; - setRangeStartFromOpcode(opcode, keyRange, Default::keyRange); + if (auto value = opcode.read(Default::key)) { + triggerOnNote = true; + keyRange.setStart(*value); + } break; case hash("hikey"): triggerOnNote = (opcode.value != "-1"); - setRangeEndFromOpcode(opcode, keyRange, Default::keyRange); + if (auto value = opcode.read(Default::key)) + keyRange.setEnd(*value); break; case hash("key"): triggerOnNote = (opcode.value != "-1"); - setRangeStartFromOpcode(opcode, keyRange, Default::keyRange); - setRangeEndFromOpcode(opcode, keyRange, Default::keyRange); - setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange); + if (auto value = opcode.read(Default::key)) { + keyRange.setStart(*value); + keyRange.setEnd(*value); + pitchKeycenter = *value; + } break; case hash("lovel"): - if (auto value = readOpcode(opcode.value, Default::midi7Range)) + if (auto value = opcode.read(Default::midi7)) velocityRange.setStart(normalizeVelocity(*value)); break; case hash("hivel"): - if (auto value = readOpcode(opcode.value, Default::midi7Range)) + if (auto value = opcode.read(Default::midi7)) velocityRange.setEnd(normalizeVelocity(*value)); break; // Region logic: MIDI conditions case hash("lobend"): - if (auto value = readOpcode(opcode.value, Default::bendRange)) + if (auto value = opcode.read(Default::bend)) bendRange.setStart(normalizeBend(*value)); break; case hash("hibend"): - if (auto value = readOpcode(opcode.value, Default::bendRange)) + if (auto value = opcode.read(Default::bend)) bendRange.setEnd(normalizeBend(*value)); break; case hash("locc&"): if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::midi7Range)) + if (auto value = opcode.read(Default::midi7)) 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)) + if (auto value = opcode.read(Default::midi7)) 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)) + if (auto value = opcode.read(Default::normalized)) 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)) + if (auto value = opcode.read(Default::normalized)) ccConditions[opcode.parameters.back()].setEnd(*value); break; case hash("sw_lokey"): // fallthrough case hash("sw_hikey"): break; case hash("sw_last"): - if (!lastKeyswitchRange) { - setValueFromOpcode(opcode, lastKeyswitch, Default::keyRange); - keySwitched = false; + if (auto value = opcode.read(Default::key)) { + if (!lastKeyswitchRange) { + lastKeyswitch = *value; + keySwitched = false; + } } break; case hash("sw_lolast"): - if (auto value = readOpcode(opcode.value, Default::keyRange)) { + if (auto value = opcode.read(Default::key)) { if (!lastKeyswitchRange) lastKeyswitchRange.emplace(*value, *value); else @@ -313,7 +336,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) } break; case hash("sw_hilast"): - if (auto value = readOpcode(opcode.value, Default::keyRange)) { + if (auto value = opcode.read(Default::key)) { if (!lastKeyswitchRange) lastKeyswitchRange.emplace(*value, *value); else @@ -327,15 +350,21 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) keyswitchLabel = opcode.value; break; case hash("sw_down"): - setValueFromOpcode(opcode, downKeyswitch, Default::keyRange); - keySwitched = false; + if (auto value = opcode.read(Default::key)) { + downKeyswitch = *value; + keySwitched = false; + } break; case hash("sw_up"): - setValueFromOpcode(opcode, upKeyswitch, Default::keyRange); + if (auto value = opcode.read(Default::key)) { + upKeyswitch = *value; + } break; case hash("sw_previous"): - setValueFromOpcode(opcode, previousKeyswitch, Default::keyRange); - previousKeySwitched = false; + if (auto value = opcode.read(Default::key)) { + previousKeyswitch = *value; + previousKeySwitched = false; + } break; case hash("sw_vel"): switch (hash(opcode.value)) { @@ -351,12 +380,11 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; case hash("sustain_cc"): - setValueFromOpcode(opcode, sustainCC, Default::ccNumberRange); + sustainCC = opcode.read(Default::sustainCC).value_or(sustainCC); break; case hash("sustain_lo"): - if (auto value = readOpcode(opcode.value, Default::float7Range)) { + if (auto value = opcode.read(Default::float7)) sustainThreshold = normalizeCC(*value); - } break; case hash("sustain_sw"): checkSustain = readBooleanFromOpcode(opcode).value_or(Default::checkSustain); @@ -366,28 +394,34 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; // Region logic: internal conditions case hash("lochanaft"): - setRangeStartFromOpcode(opcode, aftertouchRange, Default::aftertouchRange); + if (auto value = opcode.read(Default::midi7)) + aftertouchRange.setStart(*value); break; case hash("hichanaft"): - setRangeEndFromOpcode(opcode, aftertouchRange, Default::aftertouchRange); + if (auto value = opcode.read(Default::midi7)) + aftertouchRange.setEnd(*value); break; case hash("lobpm"): - setRangeStartFromOpcode(opcode, bpmRange, Default::bpmRange); + if (auto value = opcode.read(Default::bpm)) + bpmRange.setStart(*value); break; case hash("hibpm"): - setRangeEndFromOpcode(opcode, bpmRange, Default::bpmRange); + if (auto value = opcode.read(Default::bpm)) + bpmRange.setEnd(*value); break; case hash("lorand"): - setRangeStartFromOpcode(opcode, randRange, Default::randRange); + if (auto value = opcode.read(Default::normalized)) + randRange.setStart(*value); break; case hash("hirand"): - setRangeEndFromOpcode(opcode, randRange, Default::randRange); + if (auto value = opcode.read(Default::normalized)) + randRange.setEnd(*value); break; case hash("seq_length"): - setValueFromOpcode(opcode, sequenceLength, Default::sequenceRange); + sequenceLength = opcode.read(Default::sequence).value_or(sequenceLength); break; case hash("seq_position"): - setValueFromOpcode(opcode, sequencePosition, Default::sequenceRange); + sequencePosition = opcode.read(Default::sequence).value_or(sequencePosition); sequenceSwitched = false; break; // Region logic: triggers @@ -415,7 +449,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("start_locc&"): // also on_locc& if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::midi7Range)) { + if (auto value = opcode.read(Default::midi7)) { triggerOnCC = true; ccTriggers[opcode.parameters.back()].setStart(normalizeCC(*value)); } @@ -423,7 +457,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("start_hicc&"): // also on_hicc& if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::midi7Range)) { + if (auto value = opcode.read(Default::midi7)) { triggerOnCC = true; ccTriggers[opcode.parameters.back()].setEnd(normalizeCC(*value)); } @@ -431,7 +465,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("start_lohdcc&"): // also on_lohdcc& if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::normalizedRange)) { + if (auto value = opcode.read(Default::normalized)) { triggerOnCC = true; ccTriggers[opcode.parameters.back()].setStart(*value); } @@ -439,7 +473,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("start_hihdcc&"): // also on_hihdcc& if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::normalizedRange)) { + if (auto value = opcode.read(Default::normalized)) { triggerOnCC = true; ccTriggers[opcode.parameters.back()].setEnd(*value); } @@ -447,55 +481,55 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) // Performance parameters: amplifier case hash("volume"): // also gain - setValueFromOpcode(opcode, volume, Default::volumeRange); + volume = opcode.read(Default::volume).value_or(volume); break; case_any_ccN("volume"): // also gain - processGenericCc(opcode, Default::volumeCCRange, ModKey::createNXYZ(ModId::Volume, id)); + processGenericCc(opcode, Default::volumeMod, ModKey::createNXYZ(ModId::Volume, id)); break; case hash("amplitude"): - if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) + if (auto value = opcode.read(Default::amplitude)) amplitude = normalizePercents(*value); break; case_any_ccN("amplitude"): - processGenericCc(opcode, Default::amplitudeRange, ModKey::createNXYZ(ModId::Amplitude, id)); + processGenericCc(opcode, Default::amplitudeMod, ModKey::createNXYZ(ModId::Amplitude, id)); break; case hash("pan"): - if (auto value = readOpcode(opcode.value, Default::panRange)) + if (auto value = opcode.read(Default::pan)) pan = normalizePercents(*value); break; case_any_ccN("pan"): - processGenericCc(opcode, Default::panCCRange, ModKey::createNXYZ(ModId::Pan, id)); + processGenericCc(opcode, Default::panMod, ModKey::createNXYZ(ModId::Pan, id)); break; case hash("position"): - if (auto value = readOpcode(opcode.value, Default::positionRange)) + if (auto value = opcode.read(Default::position)) position = normalizePercents(*value); break; case_any_ccN("position"): - processGenericCc(opcode, Default::positionCCRange, ModKey::createNXYZ(ModId::Position, id)); + processGenericCc(opcode, Default::positionMod, ModKey::createNXYZ(ModId::Position, id)); break; case hash("width"): - if (auto value = readOpcode(opcode.value, Default::widthRange)) + if (auto value = opcode.read(Default::width)) width = normalizePercents(*value); break; case_any_ccN("width"): - processGenericCc(opcode, Default::widthCCRange, ModKey::createNXYZ(ModId::Width, id)); + processGenericCc(opcode, Default::widthMod, ModKey::createNXYZ(ModId::Width, id)); break; case hash("amp_keycenter"): - setValueFromOpcode(opcode, ampKeycenter, Default::keyRange); + ampKeycenter = opcode.read(Default::key).value_or(ampKeycenter); break; case hash("amp_keytrack"): - setValueFromOpcode(opcode, ampKeytrack, Default::ampKeytrackRange); + ampKeytrack = opcode.read(Default::ampKeytrack).value_or(ampKeytrack); break; case hash("amp_veltrack"): - if (auto value = readOpcode(opcode.value, Default::ampVeltrackRange)) + if (auto value = opcode.read(Default::ampVeltrack)) ampVeltrack = normalizePercents(*value); break; case hash("amp_random"): - setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange); + ampRandom = opcode.read(Default::ampRandom).value_or(ampRandom); break; case hash("amp_velcurve_&"): { - auto value = readOpcode(opcode.value, Default::ampVelcurveRange); + auto value = opcode.read(Default::ampVelcurve); if (opcode.parameters.back() > 127) return false; @@ -505,31 +539,35 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) } break; case hash("xfin_lokey"): - setRangeStartFromOpcode(opcode, crossfadeKeyInRange, Default::keyRange); + if (auto value = opcode.read(Default::key)) + crossfadeKeyInRange.setStart(*value); break; case hash("xfin_hikey"): - setRangeEndFromOpcode(opcode, crossfadeKeyInRange, Default::keyRange); + if (auto value = opcode.read(Default::key)) + crossfadeKeyInRange.setEnd(*value); break; case hash("xfout_lokey"): - setRangeStartFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange); + if (auto value = opcode.read(Default::key)) + crossfadeKeyOutRange.setStart(*value); break; case hash("xfout_hikey"): - setRangeEndFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange); + if (auto value = opcode.read(Default::key)) + crossfadeKeyOutRange.setEnd(*value); break; case hash("xfin_lovel"): - if (auto value = readOpcode(opcode.value, Default::midi7Range)) + if (auto value = opcode.read(Default::crossfadeIn)) crossfadeVelInRange.setStart(normalizeVelocity(*value)); break; case hash("xfin_hivel"): - if (auto value = readOpcode(opcode.value, Default::midi7Range)) + if (auto value = opcode.read(Default::crossfadeIn)) crossfadeVelInRange.setEnd(normalizeVelocity(*value)); break; case hash("xfout_lovel"): - if (auto value = readOpcode(opcode.value, Default::midi7Range)) + if (auto value = opcode.read(Default::crossfadeOut)) crossfadeVelOutRange.setStart(normalizeVelocity(*value)); break; case hash("xfout_hivel"): - if (auto value = readOpcode(opcode.value, Default::midi7Range)) + if (auto value = opcode.read(Default::crossfadeOut)) crossfadeVelOutRange.setEnd(normalizeVelocity(*value)); break; case hash("xf_keycurve"): @@ -559,25 +597,25 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("xfin_locc&"): if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::midi7Range)) + if (auto value = opcode.read(Default::crossfadeIn)) 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)) + if (auto value = opcode.read(Default::crossfadeIn)) 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)) + if (auto value = opcode.read(Default::crossfadeOut)) 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)) + if (auto value = opcode.read(Default::crossfadeOut)) crossfadeCCOutRange[opcode.parameters.back()].setEnd(normalizeCC(*value)); break; case hash("xf_cccurve"): @@ -593,28 +631,28 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) } break; case hash("rt_decay"): - setValueFromOpcode(opcode, rtDecay, Default::rtDecayRange); + rtDecay = opcode.read(Default::rtDecay).value_or(rtDecay); break; case hash("global_amplitude"): - if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) + if (auto value = opcode.read(Default::amplitude)) globalAmplitude = normalizePercents(*value); break; case hash("master_amplitude"): - if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) + if (auto value = opcode.read(Default::amplitude)) masterAmplitude = normalizePercents(*value); break; case hash("group_amplitude"): - if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) + if (auto value = opcode.read(Default::amplitude)) groupAmplitude = normalizePercents(*value); break; case hash("global_volume"): - setValueFromOpcode(opcode, globalVolume, Default::volumeRange); + globalVolume = opcode.read(Default::volume).value_or(globalVolume); break; case hash("master_volume"): - setValueFromOpcode(opcode, masterVolume, Default::volumeRange); + masterVolume = opcode.read(Default::volume).value_or(masterVolume); break; case hash("group_volume"): - setValueFromOpcode(opcode, groupVolume, Default::volumeRange); + groupVolume = opcode.read(Default::volume).value_or(groupVolume); break; // Performance parameters: filters @@ -623,7 +661,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) 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); + if (auto value = opcode.read(Default::filterCutoff)) + filters[filterIndex].cutoff = *value; } break; case hash("resonance&"): // also resonance @@ -631,7 +670,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) 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); + if (auto value = opcode.read(Default::filterResonance)) + filters[filterIndex].resonance = *value; } break; case_any_ccN("cutoff&"): // also cutoff_oncc&, cutoff_cc&, cutoff&_cc& @@ -640,7 +680,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; - processGenericCc(opcode, Default::filterCutoffModRange, ModKey::createNXYZ(ModId::FilCutoff, id, filterIndex)); + processGenericCc(opcode, Default::filterCutoffMod, ModKey::createNXYZ(ModId::FilCutoff, id, filterIndex)); } break; case_any_ccN("resonance&"): // also resonance_oncc&, resonance_cc&, resonance&_cc& @@ -649,7 +689,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; - processGenericCc(opcode, Default::filterResonanceModRange, ModKey::createNXYZ(ModId::FilResonance, id, filterIndex)); + processGenericCc(opcode, Default::filterResonanceMod, ModKey::createNXYZ(ModId::FilResonance, id, filterIndex)); } break; case hash("fil&_keytrack"): // also fil_keytrack @@ -657,8 +697,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto filterIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; - - setValueFromOpcode(opcode, filters[filterIndex].keytrack, Default::filterKeytrackRange); + if (auto value = opcode.read(Default::filterKeytrack)) + filters[filterIndex].keytrack = *value; } break; case hash("fil&_keycenter"): // also fil_keycenter @@ -666,8 +706,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto filterIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; - - setValueFromOpcode(opcode, filters[filterIndex].keycenter, Default::keyRange); + if (auto value = opcode.read(Default::key)) + filters[filterIndex].keycenter = *value; } break; case hash("fil&_veltrack"): // also fil_veltrack @@ -675,8 +715,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto filterIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; - - setValueFromOpcode(opcode, filters[filterIndex].veltrack, Default::filterVeltrackRange); + if (auto value = opcode.read(Default::filterVeltrack)) + filters[filterIndex].veltrack = *value; } break; case hash("fil&_random"): // also fil_random, cutoff_random, cutoff&_random @@ -684,8 +724,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto filterIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; - - setValueFromOpcode(opcode, filters[filterIndex].random, Default::filterRandomRange); + if (auto value = opcode.read(Default::filterRandom)) + filters[filterIndex].random = *value; } break; case hash("fil&_gain"): // also fil_gain @@ -693,8 +733,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto filterIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; - - setValueFromOpcode(opcode, filters[filterIndex].gain, Default::filterGainRange); + if (auto value = opcode.read(Default::filterGain)) + filters[filterIndex].gain = *value; } break; case_any_ccN("fil&_gain"): // also fil_gain_oncc& @@ -703,7 +743,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; - processGenericCc(opcode, Default::filterGainModRange, ModKey::createNXYZ(ModId::FilGain, id, filterIndex)); + processGenericCc(opcode, Default::filterGainMod, ModKey::createNXYZ(ModId::FilGain, id, filterIndex)); } break; case hash("fil&_type"): // also fil_type, filtype @@ -729,8 +769,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto eqIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs)) return false; - - setValueFromOpcode(opcode, equalizers[eqIndex].bandwidth, Default::eqBandwidthRange); + if (auto value = opcode.read(Default::eqBandwidth)) + equalizers[eqIndex].bandwidth = *value; } break; case_any_ccN("eq&_bw"): // also eq&_bwcc& @@ -739,7 +779,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs)) return false; - processGenericCc(opcode, Default::eqBandwidthModRange, ModKey::createNXYZ(ModId::EqBandwidth, id, eqIndex)); + processGenericCc(opcode, Default::eqBandwidthMod, ModKey::createNXYZ(ModId::EqBandwidth, id, eqIndex)); } break; case hash("eq&_freq"): @@ -747,7 +787,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto eqIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[eqIndex].frequency, Default::eqFrequencyRange); + if (auto value = opcode.read(Default::eqFrequency)) + equalizers[eqIndex].frequency = *value; } break; case_any_ccN("eq&_freq"): // also eq&_freqcc& @@ -756,7 +797,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs)) return false; - processGenericCc(opcode, Default::eqFrequencyModRange, ModKey::createNXYZ(ModId::EqFrequency, id, eqIndex)); + processGenericCc(opcode, Default::eqFrequencyMod, ModKey::createNXYZ(ModId::EqFrequency, id, eqIndex)); } break; case hash("eq&_veltofreq"): // also eq&_vel2freq @@ -764,8 +805,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto eqIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs)) return false; - - setValueFromOpcode(opcode, equalizers[eqIndex].vel2frequency, Default::eqFrequencyModRange); + if (auto value = opcode.read(Default::eqVel2Frequency)) + equalizers[eqIndex].vel2frequency = *value; } break; case hash("eq&_gain"): @@ -773,7 +814,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto eqIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[eqIndex].gain, Default::eqGainRange); + if (auto value = opcode.read(Default::eqGain)) + equalizers[eqIndex].gain = *value; } break; case_any_ccN("eq&_gain"): // also eq&_gaincc& @@ -782,7 +824,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs)) return false; - processGenericCc(opcode, Default::eqGainModRange, ModKey::createNXYZ(ModId::EqGain, id, eqIndex)); + processGenericCc(opcode, Default::eqGainMod, ModKey::createNXYZ(ModId::EqGain, id, eqIndex)); } break; case hash("eq&_veltogain"): // also eq&_vel2gain @@ -790,8 +832,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto eqIndex = opcode.parameters.front() - 1; if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs)) return false; - - setValueFromOpcode(opcode, equalizers[eqIndex].vel2gain, Default::eqGainModRange); + if (auto value = opcode.read(Default::eqVel2Gain)) + equalizers[eqIndex].vel2gain = *value; } break; case hash("eq&_type"): @@ -817,38 +859,38 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) pitchKeycenterFromSample = true; else { pitchKeycenterFromSample = false; - setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange); + pitchKeycenter = opcode.read(Default::key).value_or(pitchKeycenter); } break; case hash("pitch_keytrack"): - setValueFromOpcode(opcode, pitchKeytrack, Default::pitchKeytrackRange); + pitchKeytrack = opcode.read(Default::pitchKeytrack).value_or(pitchKeytrack); break; case hash("pitch_veltrack"): - setValueFromOpcode(opcode, pitchVeltrack, Default::pitchVeltrackRange); + pitchVeltrack = opcode.read(Default::pitchVeltrack).value_or(pitchVeltrack); break; case hash("pitch_random"): - setValueFromOpcode(opcode, pitchRandom, Default::pitchRandomRange); + pitchRandom = opcode.read(Default::pitchRandom).value_or(pitchRandom); break; case hash("transpose"): - setValueFromOpcode(opcode, transpose, Default::transposeRange); + transpose = opcode.read(Default::transpose).value_or(transpose); break; case hash("pitch"): // also tune - setValueFromOpcode(opcode, tune, Default::tuneRange); + pitch = opcode.read(Default::pitch).value_or(pitch); break; case_any_ccN("pitch"): // also tune - processGenericCc(opcode, Default::tuneCCRange, ModKey::createNXYZ(ModId::Pitch, id)); + processGenericCc(opcode, Default::pitchMod, ModKey::createNXYZ(ModId::Pitch, id)); break; case hash("bend_up"): // also bendup - setValueFromOpcode(opcode, bendUp, Default::bendBoundRange); + bendUp = opcode.read(Default::bendUp).value_or(bendUp); break; case hash("bend_down"): // also benddown - setValueFromOpcode(opcode, bendDown, Default::bendBoundRange); + bendDown = opcode.read(Default::bendDown).value_or(bendDown); break; case hash("bend_step"): - setValueFromOpcode(opcode, bendStep, Default::bendStepRange); + bendStep = opcode.read(Default::bendStep).value_or(bendStep); break; case hash("bend_smooth"): - setValueFromOpcode(opcode, bendSmooth, Default::smoothCCRange); + bendSmooth = opcode.read(Default::smoothCC).value_or(bendSmooth); break; // Modulation: LFO @@ -859,7 +901,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - setValueFromOpcode(opcode, lfos[lfoNumber - 1].freq, Default::lfoFreqRange); + if (auto value = opcode.read(Default::lfoFreq)) + lfos[lfoNumber - 1].freq = *value; } break; case_any_ccN("lfo&_freq"): @@ -869,7 +912,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - processGenericCc(opcode, Default::lfoFreqModRange, ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber - 1)); + processGenericCc(opcode, Default::lfoFreqMod, ModKey::createNXYZ(ModId::LFOFrequency, id, lfoNumber - 1)); } break; case hash("lfo&_beats"): @@ -879,7 +922,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - setValueFromOpcode(opcode, lfos[lfoNumber - 1].beats, Default::lfoBeatsRange); + if (auto value = opcode.read(Default::lfoBeats)) + lfos[lfoNumber - 1].beats = *value; } break; case_any_ccN("lfo&_beats"): @@ -889,7 +933,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - processGenericCc(opcode, Default::lfoBeatsModRange, ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber - 1)); + processGenericCc(opcode, Default::lfoBeatsMod, ModKey::createNXYZ(ModId::LFOBeats, id, lfoNumber - 1)); } break; case hash("lfo&_phase"): @@ -899,7 +943,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - if (auto value = readOpcode(opcode.value, Default::lfoPhaseRange)) + if (auto value = opcode.read(Default::lfoPhase)) lfos[lfoNumber - 1].phase0 = wrapPhase(*value); } break; @@ -910,7 +954,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - setValueFromOpcode(opcode, lfos[lfoNumber - 1].delay, Default::lfoDelayRange); + if (auto value = opcode.read(Default::lfoDelay)) + lfos[lfoNumber - 1].delay = *value; } break; case hash("lfo&_fade"): @@ -920,7 +965,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - setValueFromOpcode(opcode, lfos[lfoNumber - 1].fade, Default::lfoFadeRange); + if (auto value = opcode.read(Default::lfoFade)) + lfos[lfoNumber - 1].fade = *value; } break; case hash("lfo&_count"): @@ -930,7 +976,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - setValueFromOpcode(opcode, lfos[lfoNumber - 1].count, Default::lfoCountRange); + if (auto value = opcode.read(Default::lfoCount)) + lfos[lfoNumber - 1].count = *value; } break; case hash("lfo&_steps"): @@ -940,7 +987,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - if (auto value = readOpcode(opcode.value, Default::lfoStepsRange)) { + if (auto value = opcode.read(Default::lfoSteps)) { if (!lfos[lfoNumber - 1].seq) lfos[lfoNumber - 1].seq = LFODescription::StepSequence(); lfos[lfoNumber - 1].seq->steps.resize(*value); @@ -955,7 +1002,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - if (auto value = readOpcode(opcode.value, Default::lfoStepXRange)) { + if (auto value = opcode.read(Default::lfoStepX)) { if (!lfos[lfoNumber - 1].seq) lfos[lfoNumber - 1].seq = LFODescription::StepSequence(); if (!extendIfNecessary(lfos[lfoNumber - 1].seq->steps, stepNumber, Default::numLFOSteps)) @@ -972,7 +1019,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - if (auto value = readOpcode(opcode.value, Default::lfoWaveRange)) { + if (auto value = opcode.read(Default::lfoWave)) { if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs)) return false; lfos[lfoNumber - 1].sub[subNumber - 1].wave = static_cast(*value); @@ -987,7 +1034,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - if (auto value = readOpcode(opcode.value, Default::lfoOffsetRange)) { + if (auto value = opcode.read(Default::lfoOffset)) { if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs)) return false; lfos[lfoNumber - 1].sub[subNumber - 1].offset = *value; @@ -1002,7 +1049,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - if (auto value = readOpcode(opcode.value, Default::lfoRatioRange)) { + if (auto value = opcode.read(Default::lfoRatio)) { if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs)) return false; lfos[lfoNumber - 1].sub[subNumber - 1].ratio = *value; @@ -1017,7 +1064,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) return false; if (!extendIfNecessary(lfos, lfoNumber, Default::numLFOs)) return false; - if (auto value = readOpcode(opcode.value, Default::lfoScaleRange)) { + if (auto value = opcode.read(Default::lfoScale)) { if (!extendIfNecessary(lfos[lfoNumber - 1].sub, subNumber, Default::numLFOSubs)) return false; lfos[lfoNumber - 1].sub[subNumber - 1].scale = *value; @@ -1031,7 +1078,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto lfoNumber = opcode.parameters.front(); if (lfoNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) { + if (auto value = opcode.read(Default::amplitudeMod)) { const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Amplitude, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1043,7 +1090,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto lfoNumber = opcode.parameters.front(); if (lfoNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::panCCRange)) { + if (auto value = opcode.read(Default::panMod)) { const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Pan, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1055,7 +1102,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto lfoNumber = opcode.parameters.front(); if (lfoNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::widthCCRange)) { + if (auto value = opcode.read(Default::widthMod)) { const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Width, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1067,7 +1114,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto lfoNumber = opcode.parameters.front(); if (lfoNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::positionCCRange)) { + if (auto value = opcode.read(Default::positionMod)) { const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Position, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1079,7 +1126,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto lfoNumber = opcode.parameters.front(); if (lfoNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::tuneCCRange)) { + if (auto value = opcode.read(Default::pitchMod)) { const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Pitch, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1091,7 +1138,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto lfoNumber = opcode.parameters.front(); if (lfoNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::volumeCCRange)) { + if (auto value = opcode.read(Default::volumeMod)) { const ModKey source = ModKey::createNXYZ(ModId::LFO, id, lfoNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Volume, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1099,22 +1146,22 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) } break; case hash("lfo&_cutoff&"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilCutoff, Default::filterCutoffModRange); + LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilCutoff, Default::filterCutoffMod); break; case hash("lfo&_resonance&"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilResonance, Default::filterResonanceModRange); + LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilResonance, Default::filterResonanceMod); break; case hash("lfo&_fil&gain"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilGain, Default::filterGainModRange); + LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilGain, Default::filterGainMod); break; case hash("lfo&_eq&gain"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqGain, Default::eqGainModRange); + LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqGain, Default::eqGainMod); break; case hash("lfo&_eq&freq"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqFrequency, Default::eqFrequencyModRange); + LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqFrequency, Default::eqFrequencyMod); break; case hash("lfo&_eq&bw"): - LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqBandwidth, Default::eqBandwidthModRange); + LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqBandwidth, Default::eqBandwidthMod); break; // Modulation: Flex EG (targets) @@ -1123,7 +1170,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto egNumber = opcode.parameters.front(); if (egNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) { + if (auto value = opcode.read(Default::amplitudeMod)) { const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Amplitude, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1135,7 +1182,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto egNumber = opcode.parameters.front(); if (egNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::panCCRange)) { + if (auto value = opcode.read(Default::panMod)) { const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Pan, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1147,7 +1194,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto egNumber = opcode.parameters.front(); if (egNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::widthCCRange)) { + if (auto value = opcode.read(Default::widthMod)) { const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Width, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1159,7 +1206,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto egNumber = opcode.parameters.front(); if (egNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::positionCCRange)) { + if (auto value = opcode.read(Default::positionMod)) { const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Position, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1171,7 +1218,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto egNumber = opcode.parameters.front(); if (egNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::tuneCCRange)) { + if (auto value = opcode.read(Default::pitchMod)) { const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Pitch, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1183,7 +1230,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto egNumber = opcode.parameters.front(); if (egNumber == 0) return false; - if (auto value = readOpcode(opcode.value, Default::volumeCCRange)) { + if (auto value = opcode.read(Default::volumeMod)) { const ModKey source = ModKey::createNXYZ(ModId::Envelope, id, egNumber - 1); const ModKey target = ModKey::createNXYZ(ModId::Volume, id); getOrCreateConnection(source, target).sourceDepth = *value; @@ -1191,22 +1238,22 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) } break; case hash("eg&_cutoff&"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilCutoff, Default::filterCutoffModRange); + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilCutoff, Default::filterCutoffMod); break; case hash("eg&_resonance&"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilResonance, Default::filterResonanceModRange); + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilResonance, Default::filterResonanceMod); break; case hash("eg&_fil&gain"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilGain, Default::filterGainModRange); + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilGain, Default::filterGainMod); break; case hash("eg&_eq&gain"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqGain, Default::eqGainModRange); + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqGain, Default::eqGainMod); break; case hash("eg&_eq&freq"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqFrequency, Default::eqFrequencyModRange); + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqFrequency, Default::eqFrequencyMod); break; case hash("eg&_eq&bw"): - LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqBandwidth, Default::eqBandwidthModRange); + LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqBandwidth, Default::eqBandwidthMod); break; case hash("eg&_ampeg"): @@ -1307,26 +1354,26 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; case hash("pitcheg_depth"): - if (auto value = readOpcode(opcode.value, Default::pitchEgDepthRange)) + if (auto value = opcode.read(Default::egDepth)) getOrCreateConnection( ModKey::createNXYZ(ModId::PitchEG, id), ModKey::createNXYZ(ModId::Pitch, id)).sourceDepth = *value; break; case hash("fileg_depth"): - if (auto value = readOpcode(opcode.value, Default::filterEgDepthRange)) + if (auto value = opcode.read(Default::egDepth)) getOrCreateConnection( ModKey::createNXYZ(ModId::FilEG, id), ModKey::createNXYZ(ModId::FilCutoff, id)).sourceDepth = *value; break; case hash("pitcheg_veltodepth"): // also pitcheg_vel2depth - if (auto value = readOpcode(opcode.value, Default::pitchEgDepthRange)) + if (auto value = opcode.read(Default::egVel2Depth)) getOrCreateConnection( ModKey::createNXYZ(ModId::PitchEG, id), ModKey::createNXYZ(ModId::Pitch, id)).velToDepth = *value; break; case hash("fileg_veltodepth"): // also fileg_vel2depth - if (auto value = readOpcode(opcode.value, Default::filterEgDepthRange)) + if (auto value = opcode.read(Default::egVel2Depth)) getOrCreateConnection( ModKey::createNXYZ(ModId::FilEG, id), ModKey::createNXYZ(ModId::FilCutoff, id)).velToDepth = *value; @@ -1341,7 +1388,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) return false; auto& eg = flexEGs[egNumber - 1]; - setValueFromOpcode(opcode, eg.dynamic, Default::flexEGDynamicRange); + eg.dynamic = opcode.read(Default::flexEGDynamic).value_or(eg.dynamic); } break; case hash("eg&_sustain"): @@ -1352,7 +1399,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) if (!extendIfNecessary(flexEGs, egNumber, Default::numFlexEGs)) return false; auto& eg = flexEGs[egNumber - 1]; - setValueFromOpcode(opcode, eg.sustain, Default::flexEGSustainRange); + eg.sustain = opcode.read(Default::flexEGSustain).value_or(eg.sustain); } break; case hash("eg&_time&"): @@ -1366,7 +1413,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto pointNumber = opcode.parameters[1]; if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) return false; - setValueFromOpcode(opcode, eg.points[pointNumber].time, Default::flexEGPointTimeRange); + if (auto value = opcode.read(Default::flexEGPointTime)) + eg.points[pointNumber].time = *value; } break; case hash("eg&_level&"): @@ -1380,7 +1428,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto pointNumber = opcode.parameters[1]; if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) return false; - setValueFromOpcode(opcode, eg.points[pointNumber].level, Default::flexEGPointLevelRange); + if (auto value = opcode.read(Default::flexEGPointLevel)) + eg.points[pointNumber].level = *value; } break; case hash("eg&_shape&"): @@ -1394,7 +1443,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto pointNumber = opcode.parameters[1]; if (!extendIfNecessary(eg.points, pointNumber + 1, Default::numFlexEGPoints)) return false; - if (auto value = readOpcode(opcode.value, Default::flexEGPointShapeRange)) + if (auto value = opcode.read(Default::flexEGPointShape)) eg.points[pointNumber].setShape(*value); } break; @@ -1404,7 +1453,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) const auto effectNumber = opcode.parameters.back(); if (!effectNumber || effectNumber < 1 || effectNumber > config::maxEffectBuses) break; - auto value = readOpcode(opcode.value, { 0, 100 }); + auto value = opcode.read(Default::effect); if (!value) break; if (static_cast(effectNumber + 1) > gainToEffect.size()) @@ -1413,7 +1462,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; } case hash("sw_default"): - setValueFromOpcode(opcode, defaultSwitch, Default::keyRange); + if (auto value = opcode.read(Default::key)) + defaultSwitch = *value; break; // Ignored opcodes @@ -1441,49 +1491,49 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) switch (opcode.lettersOnlyHash) { case_any_eg("attack"): - setValueFromOpcode(opcode, eg.attack, Default::egTimeRange); + eg.attack = opcode.read(Default::egTime).value_or(eg.attack); break; case_any_eg("decay"): - setValueFromOpcode(opcode, eg.decay, Default::egTimeRange); + eg.decay = opcode.read(Default::egTime).value_or(eg.decay); break; case_any_eg("delay"): - setValueFromOpcode(opcode, eg.delay, Default::egTimeRange); + eg.delay = opcode.read(Default::egTime).value_or(eg.delay); break; case_any_eg("hold"): - setValueFromOpcode(opcode, eg.hold, Default::egTimeRange); + eg.hold = opcode.read(Default::egTime).value_or(eg.hold); break; case_any_eg("release"): - setValueFromOpcode(opcode, eg.release, Default::egTimeRange); + eg.release = opcode.read(Default::egRelease).value_or(eg.release); break; case_any_eg("start"): - setValueFromOpcode(opcode, eg.start, Default::egPercentRange); + eg.start = opcode.read(Default::egPercent).value_or(eg.start); break; case_any_eg("sustain"): - setValueFromOpcode(opcode, eg.sustain, Default::egPercentRange); + eg.sustain = opcode.read(Default::egPercent).value_or(eg.sustain); break; case_any_eg("veltoattack"): // also vel2attack - setValueFromOpcode(opcode, eg.vel2attack, Default::egOnCCTimeRange); + eg.vel2attack = opcode.read(Default::egTimeMod).value_or(eg.vel2attack); break; case_any_eg("veltodecay"): // also vel2decay - setValueFromOpcode(opcode, eg.vel2decay, Default::egOnCCTimeRange); + eg.vel2decay = opcode.read(Default::egTimeMod).value_or(eg.vel2decay); break; case_any_eg("veltodelay"): // also vel2delay - setValueFromOpcode(opcode, eg.vel2delay, Default::egOnCCTimeRange); + eg.vel2delay = opcode.read(Default::egTimeMod).value_or(eg.vel2delay); break; case_any_eg("veltohold"): // also vel2hold - setValueFromOpcode(opcode, eg.vel2hold, Default::egOnCCTimeRange); + eg.vel2hold = opcode.read(Default::egTimeMod).value_or(eg.vel2hold); break; case_any_eg("veltorelease"): // also vel2release - setValueFromOpcode(opcode, eg.vel2release, Default::egOnCCTimeRange); + eg.vel2release = opcode.read(Default::egTimeMod).value_or(eg.vel2release); break; case_any_eg("veltosustain"): // also vel2sustain - setValueFromOpcode(opcode, eg.vel2sustain, Default::egOnCCPercentRange); + eg.vel2sustain = opcode.read(Default::egPercentMod).value_or(eg.vel2sustain); break; case_any_eg("attack_oncc&"): // also attackcc& if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + if (auto value = opcode.read(Default::egTimeMod)) eg.ccAttack[opcode.parameters.back()] = *value; break; @@ -1491,7 +1541,7 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + if (auto value = opcode.read(Default::egTimeMod)) eg.ccDecay[opcode.parameters.back()] = *value; break; @@ -1499,7 +1549,7 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + if (auto value = opcode.read(Default::egTimeMod)) eg.ccDelay[opcode.parameters.back()] = *value; break; @@ -1507,7 +1557,7 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + if (auto value = opcode.read(Default::egTimeMod)) eg.ccHold[opcode.parameters.back()] = *value; break; @@ -1515,7 +1565,7 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::egOnCCTimeRange)) + if (auto value = opcode.read(Default::egTimeMod)) eg.ccRelease[opcode.parameters.back()] = *value; break; @@ -1523,7 +1573,7 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::egOnCCPercentRange)) + if (auto value = opcode.read(Default::egPercentMod)) eg.ccStart[opcode.parameters.back()] = *value; break; @@ -1531,7 +1581,7 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) if (opcode.parameters.back() >= config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::egOnCCPercentRange)) + if (auto value = opcode.read(Default::egPercentMod)) eg.ccSustain[opcode.parameters.back()] = *value; break; @@ -1557,7 +1607,7 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, absl::optional range, const ModKey& target) +bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec spec, const ModKey& target) { if (!opcode.isAnyCcN()) return false; @@ -1591,19 +1641,21 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, Range range, con ModKey::Parameters p = conn->source.parameters(); switch (opcode.category) { case kOpcodeOnCcN: - setValueFromOpcode(opcode, conn->sourceDepth, range); + conn->sourceDepth = opcode.read(spec).value_or(conn->sourceDepth); break; case kOpcodeCurveCcN: - setValueFromOpcode(opcode, p.curve, Default::curveCCRange); + p.curve = opcode.read(Default::curveCC).value_or(p.curve); break; case kOpcodeStepCcN: { - const Range stepCCRange { 0.0f, std::max(std::abs(range.getStart()), std::abs(range.getEnd())) }; - setValueFromOpcode(opcode, p.step, stepCCRange); + const float maxStep = + max(std::abs(spec.bounds.getStart()), std::abs(spec.bounds.getEnd())); + const OpcodeSpec stepCC { 0.0f, Range(0.0f, maxStep), kEnforceLowerBound | kEnforceUpperBound }; + p.step = opcode.read(stepCC).value_or(p.step); } break; case kOpcodeSmoothCcN: - setValueFromOpcode(opcode, p.smooth, Default::smoothCCRange); + p.smooth = opcode.read(Default::smoothCC).value_or(p.smooth); break; default: assert(false); @@ -1734,7 +1786,7 @@ float sfz::Region::getBasePitchVariation(float noteNumber, float velocity) const fast_real_distribution pitchDistribution { -pitchRandom, pitchRandom }; auto pitchVariationInCents = pitchKeytrack * (noteNumber - pitchKeycenter); // note difference with pitch center - pitchVariationInCents += tune; // sample tuning + pitchVariationInCents += pitch; // sample tuning pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose pitchVariationInCents += velocity * pitchVeltrack; // track velocity pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes @@ -1782,7 +1834,7 @@ uint64_t sfz::Region::getOffset(Oversampling factor) const noexcept uint64_t finalOffset = offset + offsetDistribution(Random::randomGenerator); for (const auto& mod: offsetCC) finalOffset += static_cast(mod.data * midiState.getCCValue(mod.cc)); - return Default::offsetRange.clamp(finalOffset) * static_cast(factor); + return Default::offset.bounds.clamp(finalOffset) * static_cast(factor); } float sfz::Region::getDelay() const noexcept @@ -1871,37 +1923,37 @@ float sfz::Region::velocityCurve(float velocity) const noexcept void sfz::Region::offsetAllKeys(int offset) noexcept { // Offset key range - if (keyRange != Default::keyRange) { + if (keyRange != Default::key.bounds) { const auto start = keyRange.getStart(); const auto end = keyRange.getEnd(); - keyRange.setStart(offsetAndClampKey(start, offset, Default::keyRange)); - keyRange.setEnd(offsetAndClampKey(end, offset, Default::keyRange)); + keyRange.setStart(offsetAndClampKey(start, offset)); + keyRange.setEnd(offsetAndClampKey(end, offset)); } - pitchKeycenter = offsetAndClampKey(pitchKeycenter, offset, Default::keyRange); + pitchKeycenter = offsetAndClampKey(pitchKeycenter, offset); // Offset key switches if (upKeyswitch) - upKeyswitch = offsetAndClampKey(*upKeyswitch, offset, Default::keyRange); + upKeyswitch = offsetAndClampKey(*upKeyswitch, offset); if (lastKeyswitch) - lastKeyswitch = offsetAndClampKey(*lastKeyswitch, offset, Default::keyRange); + lastKeyswitch = offsetAndClampKey(*lastKeyswitch, offset); if (downKeyswitch) - downKeyswitch = offsetAndClampKey(*downKeyswitch, offset, Default::keyRange); + downKeyswitch = offsetAndClampKey(*downKeyswitch, offset); if (previousKeyswitch) - previousKeyswitch = offsetAndClampKey(*previousKeyswitch, offset, Default::keyRange); + previousKeyswitch = offsetAndClampKey(*previousKeyswitch, offset); // Offset crossfade ranges if (crossfadeKeyInRange != Default::crossfadeKeyInRange) { const auto start = crossfadeKeyInRange.getStart(); const auto end = crossfadeKeyInRange.getEnd(); - crossfadeKeyInRange.setStart(offsetAndClampKey(start, offset, Default::keyRange)); - crossfadeKeyInRange.setEnd(offsetAndClampKey(end, offset, Default::keyRange)); + crossfadeKeyInRange.setStart(offsetAndClampKey(start, offset)); + crossfadeKeyInRange.setEnd(offsetAndClampKey(end, offset)); } if (crossfadeKeyOutRange != Default::crossfadeKeyOutRange) { const auto start = crossfadeKeyOutRange.getStart(); const auto end = crossfadeKeyOutRange.getEnd(); - crossfadeKeyOutRange.setStart(offsetAndClampKey(start, offset, Default::keyRange)); - crossfadeKeyOutRange.setEnd(offsetAndClampKey(end, offset, Default::keyRange)); + crossfadeKeyOutRange.setStart(offsetAndClampKey(start, offset)); + crossfadeKeyOutRange.setEnd(offsetAndClampKey(end, offset)); } } diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 92ecf255..87f39984 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -43,17 +43,7 @@ class RegionSet; * */ struct Region { - Region(int regionNumber, const MidiState& midiState, absl::string_view defaultPath = "") - : id{regionNumber}, midiState(midiState), defaultPath(std::move(defaultPath)) - { - ccSwitched.set(); - - gainToEffect.reserve(5); // sufficient room for main and fx1-4 - gainToEffect.push_back(1.0); // contribute 100% into the main bus - - // Default amplitude release - amplitudeEG.release = Default::ampegRelease; - } + Region(int regionNumber, const MidiState& midiState, absl::string_view defaultPath = ""); Region(const Region&) = default; ~Region() = default; @@ -280,12 +270,12 @@ struct Region { * @brief Process a generic CC opcode, and fill the modulation parameters. * * @param opcode - * @param range + * @param spec * @param target * @return true if the opcode was properly read and stored. * @return false */ - bool processGenericCc(const Opcode& opcode, Range range, const ModKey& target); + bool processGenericCc(const Opcode& opcode, OpcodeSpec spec, const ModKey& target); void offsetAllKeys(int offset) noexcept; @@ -329,45 +319,45 @@ struct Region { // Sound source: sample playback std::shared_ptr sampleId { new FileId }; // Sample absl::optional sampleQuality {}; - float delay { Default::delay }; // delay - float delayRandom { Default::delayRandom }; // delay_random - int64_t offset { Default::offset }; // offset - int64_t offsetRandom { Default::offsetRandom }; // offset_random - CCMap offsetCC { Default::offset }; - uint32_t sampleEnd { Default::sampleEndRange.getEnd() }; // end + float delay { Default::delay.value }; // delay + float delayRandom { Default::delayRandom.value }; // delay_random + int64_t offset { Default::offset.value }; // offset + int64_t offsetRandom { Default::offsetRandom.value }; // offset_random + CCMap offsetCC { Default::offsetMod.value }; + uint32_t sampleEnd { Default::sampleEnd.value }; // end absl::optional sampleCount {}; // count absl::optional loopMode {}; // loopmode - Range loopRange { Default::loopRange }; //loopstart and loopend - float loopCrossfade { Default::loopCrossfade }; // loop_crossfade + Range loopRange { Default::loopRange.bounds }; //loopstart and loopend + float loopCrossfade { Default::loopCrossfade.value }; // loop_crossfade // Wavetable oscillator - float oscillatorPhase { Default::oscillatorPhase }; + float oscillatorPhase { Default::oscillatorPhase.value }; enum class OscillatorEnabled { Auto = -1, Off = 0, On = 1 }; - OscillatorEnabled oscillatorEnabled = OscillatorEnabled::Auto; // oscillator - bool hasWavetableSample = false; // (set according to sample file) - int oscillatorMode = Default::oscillatorMode; - int oscillatorMulti = Default::oscillatorMulti; - float oscillatorDetune = Default::oscillatorDetune; - float oscillatorModDepth = Default::oscillatorModDepth; + OscillatorEnabled oscillatorEnabled { OscillatorEnabled::Auto }; // oscillator + bool hasWavetableSample { false }; // (set according to sample file) + int oscillatorMode { Default::oscillatorMode.value }; + int oscillatorMulti { Default::oscillatorMulti.value }; + float oscillatorDetune { Default::oscillatorDetune.value }; + float oscillatorModDepth { Default::oscillatorModDepth.value }; absl::optional oscillatorQuality; // Instrument settings: voice lifecycle - uint32_t group { Default::group }; // group + uint32_t group { Default::group.value }; // group absl::optional offBy {}; // off_by SfzOffMode offMode { Default::offMode }; // off_mode - float offTime { Default::offTime }; // off_mode + float offTime { Default::offTime.value }; // off_mode absl::optional notePolyphony {}; // note_polyphony - unsigned polyphony { config::maxVoices }; // polyphony + uint32_t polyphony { config::maxVoices }; // polyphony SfzSelfMask selfMask { Default::selfMask }; bool rtDead { Default::rtDead }; // Region logic: key mapping - Range keyRange { Default::keyRange }; //lokey, hikey and key - Range velocityRange { Default::velocityRange }; // hivel and lovel + Range keyRange { Default::key.bounds }; //lokey, hikey and key + Range velocityRange { Default::normalized.bounds }; // hivel and lovel // Region logic: MIDI conditions - Range bendRange { Default::bendValueRange }; // hibend and lobend - CCMap> ccConditions { Default::ccValueRange }; + Range bendRange { Default::bipolar.bounds }; // hibend and lobend + CCMap> ccConditions { Default::normalized.bounds }; absl::optional lastKeyswitch {}; // sw_last absl::optional> lastKeyswitchRange {}; // sw_last absl::optional keyswitchLabel {}; @@ -378,32 +368,32 @@ struct Region { SfzVelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel bool checkSustain { Default::checkSustain }; // sustain_sw bool checkSostenuto { Default::checkSostenuto }; // sostenuto_sw - uint16_t sustainCC { Default::sustainCC }; // sustain_cc - float sustainThreshold { Default::sustainThreshold }; // sustain_cc + uint16_t sustainCC { Default::sustainCC.value }; // sustain_cc + float sustainThreshold { Default::sustainThreshold.value }; // sustain_cc // Region logic: internal conditions - Range aftertouchRange { Default::aftertouchRange }; // hichanaft and lochanaft - Range bpmRange { Default::bpmRange }; // hibpm and lobpm - Range randRange { Default::randRange }; // hirand and lorand - uint8_t sequenceLength { Default::sequenceLength }; // seq_length - uint8_t sequencePosition { Default::sequencePosition }; // seq_position + Range aftertouchRange { Default::midi7.bounds }; // hichanaft and lochanaft + Range bpmRange { Default::bpm.bounds }; // hibpm and lobpm + Range randRange { Default::normalized.bounds }; // hirand and lorand + uint8_t sequenceLength { Default::sequence.value }; // seq_length + uint8_t sequencePosition { Default::sequence.value }; // seq_position // Region logic: triggers SfzTrigger trigger { Default::trigger }; // trigger - CCMap> ccTriggers { Default::ccTriggerValueRange }; // on_loccN on_hiccN + CCMap> ccTriggers { Default::normalized.bounds }; // on_loccN on_hiccN // Performance parameters: amplifier - float volume { Default::volume }; // volume - float amplitude { normalizePercents(Default::amplitude) }; // amplitude - float pan { normalizePercents(Default::pan) }; // pan - float width { normalizePercents(Default::width) }; // width - float position { normalizePercents(Default::position) }; // position - uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter - float ampKeytrack { Default::ampKeytrack }; // amp_keytrack - float ampVeltrack { normalizePercents(Default::ampVeltrack) }; // amp_keytrack + float volume { Default::volume.value }; // volume + float amplitude { normalizePercents(Default::amplitude.value) }; // amplitude + float pan { normalizePercents(Default::pan.value) }; // pan + float width { normalizePercents(Default::width.value) }; // width + float position { normalizePercents(Default::position.value) }; // position + uint8_t ampKeycenter { Default::key.value }; // amp_keycenter + float ampKeytrack { Default::ampKeytrack.value }; // amp_keytrack + float ampVeltrack { normalizePercents(Default::ampVeltrack.value) }; // amp_veltrack std::vector> velocityPoints; // amp_velcurve_N absl::optional velCurve {}; - float ampRandom { Default::ampRandom }; // amp_random + float ampRandom { Default::ampRandom.value }; // amp_random Range crossfadeKeyInRange { Default::crossfadeKeyInRange }; Range crossfadeKeyOutRange { Default::crossfadeKeyOutRange }; Range crossfadeVelInRange { Default::crossfadeVelInRange }; @@ -413,7 +403,7 @@ struct Region { SfzCrossfadeCurve crossfadeCCCurve { Default::crossfadeCCCurve }; CCMap> crossfadeCCInRange { Default::crossfadeCCInRange }; // xfin_loccN xfin_hiccN CCMap> crossfadeCCOutRange { Default::crossfadeCCOutRange }; // xfout_loccN xfout_hiccN - float rtDecay { Default::rtDecay }; // rt_decay + float rtDecay { Default::rtDecay.value }; // rt_decay float globalAmplitude { 1.0 }; // global_amplitude float masterAmplitude { 1.0 }; // master_amplitude @@ -427,17 +417,17 @@ struct Region { std::vector filters; // Performance parameters: pitch - uint8_t pitchKeycenter { Default::pitchKeycenter }; // pitch_keycenter + uint8_t pitchKeycenter { Default::key.value }; // pitch_keycenter bool pitchKeycenterFromSample { false }; - int pitchKeytrack { Default::pitchKeytrack }; // pitch_keytrack - float pitchRandom { Default::pitchRandom }; // pitch_random - int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack - int transpose { Default::transpose }; // transpose - float tune { Default::tune }; // tune - int bendUp { Default::bendUp }; - int bendDown { Default::bendDown }; - int bendStep { Default::bendStep }; - uint8_t bendSmooth { Default::bendSmooth }; + int pitchKeytrack { Default::pitchKeytrack.value }; // pitch_keytrack + float pitchRandom { Default::pitchRandom.value }; // pitch_random + int pitchVeltrack { Default::pitchVeltrack.value }; // pitch_veltrack + int transpose { Default::transpose.value }; // transpose + float pitch { Default::pitch.value }; // tune + float bendUp { Default::bendUp.value }; + float bendDown { Default::bendDown.value }; + float bendStep { Default::bendStep.value }; + uint8_t bendSmooth { Default::smoothCC.value }; // Envelopes EGDescription amplitudeEG; diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index b26f6560..7577d60d 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -182,18 +182,17 @@ constexpr float normalizeBend(float bendValue) * * @param key * @param offset - * @param range * @return uint8_t */ -inline CXX14_CONSTEXPR uint8_t offsetAndClampKey(uint8_t key, int offset, sfz::Range range) +inline CXX14_CONSTEXPR uint8_t offsetAndClampKey(uint8_t key, int offset) { const int offsetKey { key + offset }; if (offsetKey > std::numeric_limits::max()) - return range.getEnd(); + return Default::key.bounds.getEnd(); if (offsetKey < std::numeric_limits::min()) - return range.getStart(); + return Default::key.bounds.getStart(); - return range.clamp(static_cast(offsetKey)); + return Default::key.bounds.clamp(static_cast(offsetKey)); } namespace literals { diff --git a/src/sfizz/Smoothers.cpp b/src/sfizz/Smoothers.cpp index fda61be0..c2f6e2fb 100644 --- a/src/sfizz/Smoothers.cpp +++ b/src/sfizz/Smoothers.cpp @@ -5,6 +5,10 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "Smoothers.h" +#include "Config.h" +#include "MathHelpers.h" +#include "SfzHelpers.h" +#include "SIMDHelpers.h" namespace sfz { @@ -16,7 +20,7 @@ void Smoother::setSmoothing(uint8_t smoothValue, float sampleRate) { smoothing = (smoothValue > 0); if (smoothing) { - filter.setGain(std::tan(1.0f / (2 * Default::smoothTauPerStep * smoothValue * sampleRate))); + filter.setGain(std::tan(1.0f / (2 * config::smoothTauPerStep * smoothValue * sampleRate))); } } diff --git a/src/sfizz/Smoothers.h b/src/sfizz/Smoothers.h index 598dd203..50f99821 100644 --- a/src/sfizz/Smoothers.h +++ b/src/sfizz/Smoothers.h @@ -6,12 +6,7 @@ #pragma once -#include "Config.h" -#include "Defaults.h" -#include "MathHelpers.h" -#include "SfzHelpers.h" #include "OnePoleFilter.h" -#include "SIMDHelpers.h" #include namespace sfz { diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index b82cb372..43ac6241 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -191,7 +191,7 @@ void Synth::Impl::buildRegion(const std::vector& regionOpcodes) currentSwitch_ = *lastRegion->defaultSwitch; // There was a combination of group= and polyphony= on a region, so set the group polyphony - if (lastRegion->group != Default::group && lastRegion->polyphony != config::maxVoices) { + if (lastRegion->group != Default::group.value && lastRegion->polyphony != config::maxVoices) { voiceManager_.setGroupPolyphony(lastRegion->group, lastRegion->polyphony); } else { // Just check that there are enough polyphony groups @@ -276,11 +276,12 @@ void Synth::Impl::handleMasterOpcodes(const std::vector& members) switch (member.lettersOnlyHash) { case hash("polyphony"): ASSERT(currentSet_ != nullptr); - if (auto value = readOpcode(member.value, Default::polyphonyRange)) + if (auto value = member.read(Default::polyphony)) currentSet_->setPolyphonyLimit(*value); break; case hash("sw_default"): - setValueFromOpcode(member, currentSwitch_, Default::keyRange); + if (auto value = member.read(Default::key)) + currentSwitch_ = *value; break; } } @@ -294,15 +295,16 @@ void Synth::Impl::handleGlobalOpcodes(const std::vector& members) switch (member.lettersOnlyHash) { case hash("polyphony"): ASSERT(currentSet_ != nullptr); - if (auto value = readOpcode(member.value, Default::polyphonyRange)) + if (auto value = member.read(Default::polyphony)) currentSet_->setPolyphonyLimit(*value); break; case hash("sw_default"): - setValueFromOpcode(member, currentSwitch_, Default::keyRange); + if (auto value = member.read(Default::key)) + currentSwitch_ = *value; break; case hash("volume"): // FIXME : Probably best not to mess with this and let the host control the volume - // setValueFromOpcode(member, volume, Default::volumeRange); + // setValueFromOpcode(member, volume, OldDefault::volumeRange); break; } } @@ -318,13 +320,16 @@ void Synth::Impl::handleGroupOpcodes(const std::vector& members, const s switch (member.lettersOnlyHash) { case hash("group"): - setValueFromOpcode(member, groupIdx, Default::groupRange); + if (auto value = member.read(Default::group)) + groupIdx = *value; break; case hash("polyphony"): - setValueFromOpcode(member, maxPolyphony, Default::polyphonyRange); + if (auto value = member.read(Default::polyphony)) + maxPolyphony = *value; break; case hash("sw_default"): - setValueFromOpcode(member, currentSwitch_, Default::keyRange); + if (auto value = member.read(Default::key)) + currentSwitch_ = *value; break; } }; @@ -352,15 +357,15 @@ void Synth::Impl::handleControlOpcodes(const std::vector& members) switch (member.lettersOnlyHash) { case hash("set_cc&"): - if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) { - const auto ccValue = readOpcode(member.value, Default::midi7Range); + if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) { + const auto ccValue = member.read(Default::midi7); if (ccValue) setDefaultHdcc(member.parameters.back(), normalizeCC(*ccValue)); } break; case hash("set_hdcc&"): - if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) { - const auto ccValue = readOpcode(member.value, Default::normalizedRange); + if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) { + const auto ccValue = member.read(Default::normalized); if (ccValue) setDefaultHdcc(member.parameters.back(), *ccValue); } @@ -370,7 +375,7 @@ void Synth::Impl::handleControlOpcodes(const std::vector& members) setCCLabel(member.parameters.back(), std::string(member.value)); break; case hash("label_key&"): - if (member.parameters.back() <= Default::keyRange.getEnd()) { + if (member.parameters.back() <= Default::key.bounds.getEnd()) { const auto noteNumber = static_cast(member.parameters.back()); insertPairUniquely(keyLabels_, noteNumber, std::string(member.value)); } @@ -380,10 +385,10 @@ void Synth::Impl::handleControlOpcodes(const std::vector& members) DBG("Changing default sample path to " << defaultPath_); break; case hash("note_offset"): - setValueFromOpcode(member, noteOffset_, Default::noteOffsetRange); + noteOffset_ = member.read(Default::noteOffset).value_or(noteOffset_); break; case hash("octave_offset"): - setValueFromOpcode(member, octaveOffset_, Default::octaveOffsetRange); + octaveOffset_ = member.read(Default::octaveOffset).value_or(octaveOffset_); break; case hash("hint_ram_based"): if (member.value == "1") @@ -446,21 +451,21 @@ void Synth::Impl::handleEffectOpcodes(const std::vector& rawMembers) // note(jpc): gain opcodes are linear volumes in % units case hash("directtomain"): - if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) + if (auto valueOpt = opcode.read(Default::effect)) getOrCreateBus(0).setGainToMain(*valueOpt / 100); break; case hash("fx&tomain"): // fx&tomain if (opcode.parameters.front() < 1 || opcode.parameters.front() > config::maxEffectBuses) break; - if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) + if (auto valueOpt = opcode.read(Default::effect)) getOrCreateBus(opcode.parameters.front()).setGainToMain(*valueOpt / 100); break; case hash("fx&tomix"): // fx&tomix if (opcode.parameters.front() < 1 || opcode.parameters.front() > config::maxEffectBuses) break; - if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) + if (auto valueOpt = opcode.read(Default::effect)) getOrCreateBus(opcode.parameters.front()).setGainToMix(*valueOpt / 100); break; } @@ -584,10 +589,10 @@ void Synth::Impl::finalizeSfzLoad() region->sampleEnd = std::min(region->sampleEnd, fileInformation->end); if (fileInformation->hasLoop) { - if (region->loopRange.getStart() == Default::loopRange.getStart()) + if (region->loopRange.getStart() == Default::loopRange.bounds.getStart()) region->loopRange.setStart(fileInformation->loopBegin); - if (region->loopRange.getEnd() == Default::loopRange.getEnd()) + if (region->loopRange.getEnd() == Default::loopRange.bounds.getEnd()) region->loopRange.setEnd(fileInformation->loopEnd); if (!region->loopMode) @@ -597,7 +602,7 @@ void Synth::Impl::finalizeSfzLoad() if (region->isRelease() && !region->loopMode) region->loopMode = SfzLoopMode::one_shot; - if (region->loopRange.getEnd() == Default::loopRange.getEnd()) + if (region->loopRange.getEnd() == Default::loopRange.bounds.getEnd()) region->loopRange.setEnd(region->sampleEnd); if (fileInformation->numChannels == 2) @@ -611,7 +616,7 @@ void Synth::Impl::finalizeSfzLoad() uint64_t sumOffsetCC = region->offset + region->offsetRandom; for (const auto& offsets : region->offsetCC) sumOffsetCC += offsets.data; - return Default::offsetCCRange.clamp(sumOffsetCC); + return Default::offsetMod.bounds.clamp(sumOffsetCC); }(); if (!resources_.filePool.preloadFile(*region->sampleId, maxOffset)) @@ -663,14 +668,14 @@ void Synth::Impl::finalizeSfzLoad() // Set the default frequencies on equalizers if needed if (region->equalizers.size() > 0 - && region->equalizers[0].frequency == Default::eqFrequencyUnset) { - region->equalizers[0].frequency = Default::eqFrequency1; + && region->equalizers[0].frequency == Default::eqFrequency.value) { + region->equalizers[0].frequency = Default::defaultEQFreq[0]; if (region->equalizers.size() > 1 - && region->equalizers[1].frequency == Default::eqFrequencyUnset) { - region->equalizers[1].frequency = Default::eqFrequency2; + && region->equalizers[1].frequency == Default::eqFrequency.value) { + region->equalizers[1].frequency = Default::defaultEQFreq[1]; if (region->equalizers.size() > 2 - && region->equalizers[2].frequency == Default::eqFrequencyUnset) { - region->equalizers[2].frequency = Default::eqFrequency3; + && region->equalizers[2].frequency == Default::eqFrequency.value) { + region->equalizers[2].frequency = Default::defaultEQFreq[2]; } } } @@ -1479,7 +1484,7 @@ float Synth::getVolume() const noexcept void Synth::setVolume(float volume) noexcept { Impl& impl = *impl_; - impl.volume_ = Default::volumeRange.clamp(volume); + impl.volume_ = Default::volume.bounds.clamp(volume); } int Synth::getNumVoices() const noexcept diff --git a/src/sfizz/SynthConfig.h b/src/sfizz/SynthConfig.h index 3f710f89..47d265e6 100644 --- a/src/sfizz/SynthConfig.h +++ b/src/sfizz/SynthConfig.h @@ -13,8 +13,8 @@ struct SynthConfig { bool freeWheeling { false }; - int liveSampleQuality { sfz::Default::sampleQuality }; - int freeWheelingSampleQuality { sfz::Default::sampleQualityInFreewheelingMode }; + int liveSampleQuality { Default::sampleQuality.value }; + int freeWheelingSampleQuality { Default::freewheelingQuality }; int currentSampleQuality() const noexcept { diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 9902b58b..0f6bd10b 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -124,6 +124,24 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; + MATCH("/region&/trigger_on_note", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.triggerOnNote) { + client.receive<'T'>(delay, path, {}); + } else { + client.receive<'F'>(delay, path, {}); + } + } break; + + MATCH("/region&/trigger_on_cc", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.triggerOnCC) { + client.receive<'T'>(delay, path, {}); + } else { + client.receive<'F'>(delay, path, {}); + } + } break; + MATCH("/region&/count", "") { GET_REGION_OR_BREAK(indices[0]) if (!region.sampleCount) { @@ -761,12 +779,12 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'i'>(delay, path, region.transpose); } break; - MATCH("/region&/tune", "") { + MATCH("/region&/pitch", "") { GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.tune); + client.receive<'f'>(delay, path, region.pitch); } break; - MATCH("/region&/tune_cc&", "") { + MATCH("/region&/pitch_cc&", "") { GET_REGION_OR_BREAK(indices[0]) auto value = region.ccModDepth(indices[1], ModId::Pitch); if (value) { @@ -776,7 +794,7 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; - MATCH("/region&/tune_stepcc&", "") { + MATCH("/region&/pitch_stepcc&", "") { GET_REGION_OR_BREAK(indices[0]) auto params = region.ccModParameters(indices[1], ModId::Pitch); if (params) { @@ -786,7 +804,7 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; - MATCH("/region&/tune_smoothcc&", "") { + MATCH("/region&/pitch_smoothcc&", "") { GET_REGION_OR_BREAK(indices[0]) auto params = region.ccModParameters(indices[1], ModId::Pitch); if (params) { @@ -796,7 +814,7 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; - MATCH("/region&/tune_curvecc&", "") { + MATCH("/region&/pitch_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) auto params = region.ccModParameters(indices[1], ModId::Pitch); if (params) { @@ -808,17 +826,17 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/region&/bend_up", "") { GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.bendUp); + client.receive<'f'>(delay, path, region.bendUp); } break; MATCH("/region&/bend_down", "") { GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.bendDown); + client.receive<'f'>(delay, path, region.bendDown); } break; MATCH("/region&/bend_step", "") { GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.bendStep); + client.receive<'f'>(delay, path, region.bendStep); } break; MATCH("/region&/bend_smooth", "") { @@ -863,7 +881,7 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/region&/ampeg_depth", "") { GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.amplitudeEG.depth); + client.receive<'f'>(delay, path, region.amplitudeEG.depth); } break; MATCH("/region&/ampeg_vel&attack", "") { @@ -912,7 +930,7 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co GET_REGION_OR_BREAK(indices[0]) if (indices[1] != 2) break; - client.receive<'i'>(delay, path, region.amplitudeEG.vel2depth); + client.receive<'f'>(delay, path, region.amplitudeEG.vel2depth); } break; MATCH("/region&/note_polyphony", "") { @@ -978,6 +996,37 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'f'>(delay, path, region.oscillatorPhase); } break; + MATCH("/region&/oscillator_quality", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.oscillatorQuality) { + client.receive<'i'>(delay, path, *region.oscillatorQuality); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/oscillator_mode", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.oscillatorMode); + } break; + + MATCH("/region&/oscillator_multi", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, region.oscillatorMulti); + } break; + + MATCH("/region&/oscillator_detune", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.oscillatorDetune); + } break; + + MATCH("/region&/oscillator_mod_depth", "") { + GET_REGION_OR_BREAK(indices[0]) + client.receive<'f'>(delay, path, region.oscillatorModDepth * 100.0f); + } break; + + // TODO: detune cc, mod depth cc + MATCH("/region&/effect&", "") { GET_REGION_OR_BREAK(indices[0]) auto effectIdx = indices[1]; diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index 0e251024..bd34ec22 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -257,8 +257,8 @@ struct Synth::Impl final: public Parser::Listener { // Control opcodes std::string defaultPath_ { "" }; - int noteOffset_ { 0 }; - int octaveOffset_ { 0 }; + int noteOffset_ { Default::noteOffset.value }; + int octaveOffset_ { Default::octaveOffset.value }; // Modulation source generators std::unique_ptr genController_; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 64ddebe9..4ba91ba1 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -370,7 +370,8 @@ void Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noe } } const float phase = region->getPhase(); - const int quality = region->oscillatorQuality.value_or(Default::oscillatorQuality); + const int quality = + region->oscillatorQuality.value_or(Default::oscillatorQuality.value); for (WavetableOscillator& osc : impl.waveOscillators_) { osc.setWavetable(wave); osc.setPhase(phase); @@ -464,7 +465,7 @@ void Voice::off(int delay, bool fast) noexcept Impl& impl = *impl_; if (!impl.region_->flexAmpEG) { if (impl.region_->offMode == SfzOffMode::fast || fast) { - impl.egAmplitude_.setReleaseTime(Default::offTime); + impl.egAmplitude_.setReleaseTime(Default::offTime.value); } else if (impl.region_->offMode == SfzOffMode::time) { impl.egAmplitude_.setReleaseTime(impl.region_->offTime); } @@ -1652,7 +1653,7 @@ void Voice::Impl::pitchEnvelope(absl::Span pitchSpan) noexcept return centsFactor(region_->getBendInCents(bend)); }; - if (region_->bendStep > 1) + if (region_->bendStep > 1.0f) pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor_); else pitchBendEnvelope(events, *bends, bendLambda); diff --git a/src/sfizz/effects/Apan.cpp b/src/sfizz/effects/Apan.cpp index a51f4a4e..9151188a 100644 --- a/src/sfizz/effects/Apan.cpp +++ b/src/sfizz/effects/Apan.cpp @@ -81,27 +81,27 @@ namespace fx { for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { case hash("apan_waveform"): - if (auto value = readOpcode(opc.value, Default::apanWaveformRange)) + if (auto value = opc.read(Default::apanWaveform)) apan->_lfoWave = *value; break; case hash("apan_freq"): - if (auto value = readOpcode(opc.value, Default::apanFrequencyRange)) + if (auto value = opc.read(Default::apanFrequency)) apan->_lfoFrequency = *value; break; case hash("apan_phase"): - if (auto value = readOpcode(opc.value, Default::apanPhaseRange)) + if (auto value = opc.read(Default::apanPhase)) apan->_lfoPhaseOffset = wrapPhase(*value); break; case hash("apan_dry"): - if (auto value = readOpcode(opc.value, Default::apanLevelRange)) + if (auto value = opc.read(Default::apanLevel)) apan->_dry = *value / 100.0f; break; case hash("apan_wet"): - if (auto value = readOpcode(opc.value, Default::apanLevelRange)) + if (auto value = opc.read(Default::apanLevel)) apan->_wet = *value / 100.0f; break; case hash("apan_depth"): - if (auto value = readOpcode(opc.value, Default::apanLevelRange)) + if (auto value = opc.read(Default::apanLevel)) apan->_depth = *value / 100.0f; break; } diff --git a/src/sfizz/effects/Apan.h b/src/sfizz/effects/Apan.h index 52b85dc6..ac72e524 100644 --- a/src/sfizz/effects/Apan.h +++ b/src/sfizz/effects/Apan.h @@ -47,20 +47,20 @@ namespace fx { template void computeLfos(float* left, float* right, unsigned nframes); private: - float _samplePeriod = 0.0; + float _samplePeriod { 0.0f }; sfz::Buffer _lfoOutLeft { config::defaultSamplesPerBlock }; sfz::Buffer _lfoOutRight { config::defaultSamplesPerBlock }; // Controls - float _dry = 0.0; - float _wet = 0.0; - float _depth = 0.0; - int _lfoWave = 0; - float _lfoFrequency = 0.0; - float _lfoPhaseOffset = 0.5; + float _dry { Default::apanLevel.value }; + float _wet { Default::apanLevel.value }; + float _depth { Default::apanLevel.value }; + int _lfoWave { Default::apanWaveform.value }; + float _lfoFrequency { Default::apanFrequency.value }; + float _lfoPhaseOffset { Default::apanPhase.value }; // State - float _lfoPhase = 0.0; + float _lfoPhase { 0.0f }; }; } // namespace fx diff --git a/src/sfizz/effects/Compressor.cpp b/src/sfizz/effects/Compressor.cpp index 3aaf04a3..06b805ee 100644 --- a/src/sfizz/effects/Compressor.cpp +++ b/src/sfizz/effects/Compressor.cpp @@ -32,7 +32,7 @@ namespace fx { struct Compressor::Impl { faustCompressor _compressor[2]; bool _stlink = false; - float _inputGain = 1.0; + float _inputGain { Default::compGain.value }; AudioBuffer _tempBuffer2x { 2, _oversampling * config::defaultSamplesPerBlock }; AudioBuffer _gain2x { 2, _oversampling * config::defaultSamplesPerBlock }; hiir::Downsampler2xFpu<12> _downsampler2x[EffectChannels]; @@ -163,31 +163,31 @@ namespace fx { for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { case hash("comp_attack"): - if (auto value = readOpcode(opc.value, {0.0, 10.0})) { + if (auto value = opc.read(Default::compAttack)) { for (size_t c = 0; c < 2; ++c) impl.set_Attack(c, *value); } break; case hash("comp_release"): - if (auto value = readOpcode(opc.value, {0.0, 10.0})) { + if (auto value = opc.read(Default::compRelease)) { for (size_t c = 0; c < 2; ++c) impl.set_Release(c, *value); } break; case hash("comp_threshold"): - if (auto value = readOpcode(opc.value, {-100.0, 0.0})) { + if (auto value = opc.read(Default::compThreshold)) { for (size_t c = 0; c < 2; ++c) impl.set_Threshold(c, *value); } break; case hash("comp_ratio"): - if (auto value = readOpcode(opc.value, {1.0, 50.0})) { + if (auto value = opc.read(Default::compRatio)) { for (size_t c = 0; c < 2; ++c) impl.set_Ratio(c, *value); } break; case hash("comp_gain"): - if (auto value = readOpcode(opc.value, {-100.0, 100.0})) + if (auto value = opc.read(Default::compGain)) impl._inputGain = db2mag(*value); break; case hash("comp_stlink"): diff --git a/src/sfizz/effects/Disto.cpp b/src/sfizz/effects/Disto.cpp index 9e83856a..c3939f9e 100644 --- a/src/sfizz/effects/Disto.cpp +++ b/src/sfizz/effects/Disto.cpp @@ -37,15 +37,15 @@ namespace fx { struct Disto::Impl { enum { maxStages = 4 }; - float _samplePeriod = 1.0 / config::defaultSampleRate; - float _tone = 100.0; - float _depth = 0.0; - float _dry = 0.0; - float _wet = 0.0; - unsigned _numStages = 1; + float _samplePeriod { 1.0f / config::defaultSampleRate }; + float _tone { Default::distoTone.value }; + float _depth { Default::distoDepth.value }; + float _dry { Default::effect.value }; + float _wet { Default::effect.value }; + unsigned _numStages = { Default::distoStages.value }; float _toneLpfMem[EffectChannels] = {}; - faustDisto _stages[EffectChannels][maxStages]; + faustDisto _stages[EffectChannels][Default::maxDistoStages]; hiir::Upsampler2xFpu<12> _up2x[EffectChannels]; hiir::Upsampler2xFpu<4> _up4x[EffectChannels]; @@ -205,20 +205,23 @@ std::unique_ptr Disto::makeInstance(absl::Span members) for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { case hash("disto_tone"): - setValueFromOpcode(opc, impl._tone, {0.0f, 100.0f}); + if (auto value = opc.read(Default::distoTone)) + impl._tone = *value; break; case hash("disto_depth"): - setValueFromOpcode(opc, impl._depth, {0.0f, 100.0f}); + if (auto value = opc.read(Default::distoDepth)) + impl._depth = *value; break; case hash("disto_stages"): - setValueFromOpcode(opc, impl._numStages, {1, Impl::maxStages}); + if (auto value = opc.read(Default::distoStages)) + impl._numStages = *value; break; case hash("disto_dry"): - if (auto value = readOpcode(opc.value, {0.0f, 100.0f})) + if (auto value = opc.read(Default::effect)) impl._dry = *value * 0.01f; break; case hash("disto_wet"): - if (auto value = readOpcode(opc.value, {0.0f, 100.0f})) + if (auto value = opc.read(Default::effect)) impl._wet = *value * 0.01f; break; } diff --git a/src/sfizz/effects/Eq.cpp b/src/sfizz/effects/Eq.cpp index 1eda27e4..7c9b5594 100644 --- a/src/sfizz/effects/Eq.cpp +++ b/src/sfizz/effects/Eq.cpp @@ -70,13 +70,16 @@ namespace fx { for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { case hash("eq_freq"): - setValueFromOpcode(opc, desc.frequency, Default::eqFrequencyRange); + if (auto value = opc.read(Default::eqFrequency)) + desc.frequency = *value; break; case hash("eq_bw"): - setValueFromOpcode(opc, desc.bandwidth, Default::eqBandwidthRange); + if (auto value = opc.read(Default::eqBandwidth)) + desc.bandwidth = *value; break; case hash("eq_gain"): - setValueFromOpcode(opc, desc.gain, Default::eqGainRange); + if (auto value = opc.read(Default::eqGain)) + desc.gain = *value; break; case hash("eq_type"): { diff --git a/src/sfizz/effects/Filter.cpp b/src/sfizz/effects/Filter.cpp index d39d74f1..836306f3 100644 --- a/src/sfizz/effects/Filter.cpp +++ b/src/sfizz/effects/Filter.cpp @@ -72,10 +72,12 @@ namespace fx { for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { case hash("filter_cutoff"): - setValueFromOpcode(opc, desc.cutoff, Default::filterCutoffRange); + if (auto value = opc.read(Default::filterCutoff)) + desc.cutoff = *value; break; case hash("filter_resonance"): - setValueFromOpcode(opc, desc.resonance, Default::filterResonanceRange); + if (auto value = opc.read(Default::filterResonance)) + desc.resonance = *value; break; case hash("filter_type"): { @@ -90,7 +92,8 @@ namespace fx { } // extension case hash("sfizz:filter_gain"): - setValueFromOpcode(opc, desc.gain, Default::filterGainRange); + if (auto value = opc.read(Default::filterGain)) + desc.gain = *value; break; } } diff --git a/src/sfizz/effects/Fverb.cpp b/src/sfizz/effects/Fverb.cpp index 71e73efa..c3a1f408 100644 --- a/src/sfizz/effects/Fverb.cpp +++ b/src/sfizz/effects/Fverb.cpp @@ -180,13 +180,13 @@ namespace fx { std::unique_ptr fx { reverb }; const Impl::Profile* profile = &Impl::largeHall; - float dry = 0; - float wet = 0; - float input = 0; - float size = 0; - float predelay = 0; - float tone = 100; - float damp = 0; + float dry { Default::effect.value }; + float wet { Default::effect.value }; + float input { Default::effect.value }; + float size { Default::fverbSize.value }; + float predelay { Default::fverbPredelay.value }; + float tone { Default::fverbTone.value }; + float damp { Default::fverbDamp.value }; for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { @@ -211,25 +211,26 @@ namespace fx { } break; case hash("reverb_dry"): - setValueFromOpcode(opc, dry, {0.0f, 100.0f}); + + dry = opc.read(Default::effect).value_or(dry); break; case hash("reverb_wet"): - setValueFromOpcode(opc, wet, {0.0f, 100.0f}); + wet = opc.read(Default::effect).value_or(wet); break; case hash("reverb_input"): - setValueFromOpcode(opc, input, {0.0f, 100.0f}); + input = opc.read(Default::effect).value_or(input); break; case hash("reverb_size"): - setValueFromOpcode(opc, size, {0.0f, 100.0f}); + size = opc.read(Default::fverbSize).value_or(size); break; case hash("reverb_predelay"): - setValueFromOpcode(opc, predelay, {0.0f, 10.0f}); + predelay = opc.read(Default::fverbPredelay).value_or(predelay); break; case hash("reverb_tone"): - setValueFromOpcode(opc, tone, {0.0f, 100.0f}); + tone = opc.read(Default::fverbTone).value_or(tone); break; case hash("reverb_damp"): - setValueFromOpcode(opc, damp, {0.0f, 100.0f}); + damp = opc.read(Default::fverbDamp).value_or(damp); break; } } diff --git a/src/sfizz/effects/Gain.cpp b/src/sfizz/effects/Gain.cpp index 04cca832..7ad9fb9f 100644 --- a/src/sfizz/effects/Gain.cpp +++ b/src/sfizz/effects/Gain.cpp @@ -62,7 +62,8 @@ namespace fx { for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { case hash("gain"): - setValueFromOpcode(opc, gain->_gain, {-96.0f, 96.0f}); + if (auto value = opc.read(Default::volume)) + gain->_gain = *value; break; } } diff --git a/src/sfizz/effects/Gate.cpp b/src/sfizz/effects/Gate.cpp index b7a819d5..792060e6 100644 --- a/src/sfizz/effects/Gate.cpp +++ b/src/sfizz/effects/Gate.cpp @@ -166,25 +166,25 @@ namespace fx { for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { case hash("gate_attack"): - if (auto value = readOpcode(opc.value, {0.0, 10.0})) { + if (auto value = opc.read(Default::gateAttack)) { for (size_t c = 0; c < 2; ++c) impl.set_Attack(c, *value); } break; case hash("gate_hold"): - if (auto value = readOpcode(opc.value, {0.0, 10.0})) { + if (auto value = opc.read(Default::gateHold)) { for (size_t c = 0; c < 2; ++c) impl.set_Hold(c, *value); } break; case hash("gate_release"): - if (auto value = readOpcode(opc.value, {0.0, 10.0})) { + if (auto value = opc.read(Default::gateRelease)) { for (size_t c = 0; c < 2; ++c) impl.set_Release(c, *value); } break; case hash("gate_threshold"): - if (auto value = readOpcode(opc.value, {-100.0, 0.0})) { + if (auto value = opc.read(Default::gateThreshold)) { for (size_t c = 0; c < 2; ++c) impl.set_Threshold(c, *value); } diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 12d7759f..00a11128 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -85,10 +85,12 @@ namespace fx { for (const Opcode& opcode : members) { switch (opcode.lettersOnlyHash) { case hash("bitred"): - setValueFromOpcode(opcode, lofi->_bitred_depth, { 0.0, 100.0 }); + if (auto value = opcode.read(Default::lofiBitred)) + lofi->_bitred_depth = *value; break; case hash("decim"): - setValueFromOpcode(opcode, lofi->_decim_depth, { 0.0, 100.0 }); + if (auto value = opcode.read(Default::lofiDecim)) + lofi->_decim_depth = *value; break; } } diff --git a/src/sfizz/effects/Rectify.cpp b/src/sfizz/effects/Rectify.cpp index 56be4682..f8d271dd 100644 --- a/src/sfizz/effects/Rectify.cpp +++ b/src/sfizz/effects/Rectify.cpp @@ -95,7 +95,8 @@ namespace fx { rectify->_full = false; break; case hash("rectify"): - setValueFromOpcode(opc, rectify->_amount, { 0.0, 100.0 }); + if (auto value = opc.read(Default::rectify)) + rectify->_amount = *value; break; } } diff --git a/src/sfizz/effects/Strings.cpp b/src/sfizz/effects/Strings.cpp index 9e7295d4..6538b68a 100644 --- a/src/sfizz/effects/Strings.cpp +++ b/src/sfizz/effects/Strings.cpp @@ -132,10 +132,12 @@ namespace fx { for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { case hash("strings_number"): - setValueFromOpcode(opc, strings->_numStrings, {0, MaximumNumStrings}); + if (auto value = opc.read(Default::stringsNumber)) + strings->_numStrings = *value; break; case hash("strings_wet"): - setValueFromOpcode(opc, strings->_wet, {0.0f, 100.0f}); + if (auto value = opc.read(Default::effect)) + strings->_wet = *value; break; } } diff --git a/src/sfizz/effects/Strings.h b/src/sfizz/effects/Strings.h index 2f46254b..f20ae8a8 100644 --- a/src/sfizz/effects/Strings.h +++ b/src/sfizz/effects/Strings.h @@ -51,8 +51,8 @@ namespace fx { private: enum { MaximumNumStrings = 88 }; - unsigned _numStrings = MaximumNumStrings; - float _wet = 0; + unsigned _numStrings { Default::maxStrings }; + float _wet { Default::effect.value }; std::unique_ptr _stringsArray; diff --git a/src/sfizz/effects/Width.cpp b/src/sfizz/effects/Width.cpp index a9c3c202..2a3cdc92 100644 --- a/src/sfizz/effects/Width.cpp +++ b/src/sfizz/effects/Width.cpp @@ -69,7 +69,8 @@ namespace fx { for (const Opcode& opc : members) { switch (opc.lettersOnlyHash) { case hash("width"): - setValueFromOpcode(opc, width->_width, {-100.0f, 100.0f}); + if (auto value = opc.read(Default::width)) + width->_width = *value; break; } } diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 28c41dd9..1072d188 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -447,7 +447,20 @@ TEST_CASE("[Files] Set RealCC applies properly") TEST_CASE("[Files] Note and octave offsets") { Synth synth; - synth.loadSfzFile(fs::current_path() / "tests/TestFiles/note_offset.sfz"); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/note_offset.sfz", R"( + note_offset=1 + key=63 sample=*sine + lokey=50 hikey=55 pitch_keycenter=50 sample=*sine + lokey=40 hikey=44 pitch_keycenter=40 xfin_lokey=36 xfin_hikey=40 xfout_lokey=44 xfout_hikey=48 sample=*sine + note_offset=-1 + key=63 sw_lokey=24 sw_hikey=28 sw_last=25 sw_up=25 sw_down=25 sw_previous=62 sample=*sine + note_offset=1 octave_offset=1 + key=63 sample=*sine + note_offset=-1 octave_offset=-1 + key=63 sample=*sine + // Check that this does not reset either note or octave offset + key=63 sample=*sine + )"); REQUIRE( synth.getNumRegions() == 7 ); REQUIRE(synth.getRegionView(0)->keyRange == Range(64, 64)); diff --git a/tests/OpcodeT.cpp b/tests/OpcodeT.cpp index 24d6a98c..61971b39 100644 --- a/tests/OpcodeT.cpp +++ b/tests/OpcodeT.cpp @@ -7,12 +7,13 @@ #include "sfizz/Region.h" #include "catch2/catch.hpp" using namespace Catch::literals; +using namespace sfz; TEST_CASE("[Opcode] Construction") { SECTION("Normal construction") { - sfz::Opcode opcode { "sample", "dummy" }; + Opcode opcode { "sample", "dummy" }; REQUIRE(opcode.opcode == "sample"); REQUIRE(opcode.lettersOnlyHash == hash("sample")); REQUIRE(opcode.parameters.empty()); @@ -21,7 +22,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Normal construction with underscore") { - sfz::Opcode opcode { "sample_underscore", "dummy" }; + Opcode opcode { "sample_underscore", "dummy" }; REQUIRE(opcode.opcode == "sample_underscore"); REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); REQUIRE(opcode.parameters.empty()); @@ -30,7 +31,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Normal construction with ampersand") { - sfz::Opcode opcode { "sample&_ampersand", "dummy" }; + Opcode opcode { "sample&_ampersand", "dummy" }; REQUIRE(opcode.opcode == "sample&_ampersand"); REQUIRE(opcode.lettersOnlyHash == hash("sample_ampersand")); REQUIRE(opcode.parameters.empty()); @@ -39,7 +40,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Normal construction with multiple ampersands") { - sfz::Opcode opcode { "&sample&_ampersand&", "dummy" }; + Opcode opcode { "&sample&_ampersand&", "dummy" }; REQUIRE(opcode.opcode == "&sample&_ampersand&"); REQUIRE(opcode.lettersOnlyHash == hash("sample_ampersand")); REQUIRE(opcode.parameters.empty()); @@ -48,7 +49,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Parameterized opcode") { - sfz::Opcode opcode { "sample123", "dummy" }; + Opcode opcode { "sample123", "dummy" }; REQUIRE(opcode.opcode == "sample123"); REQUIRE(opcode.lettersOnlyHash == hash("sample&")); REQUIRE(opcode.value == "dummy"); @@ -58,7 +59,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Parameterized opcode with ampersand") { - sfz::Opcode opcode { "sample&123", "dummy" }; + Opcode opcode { "sample&123", "dummy" }; REQUIRE(opcode.opcode == "sample&123"); REQUIRE(opcode.lettersOnlyHash == hash("sample&")); REQUIRE(opcode.value == "dummy"); @@ -68,7 +69,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Parameterized opcode with underscore") { - sfz::Opcode opcode { "sample_underscore123", "dummy" }; + Opcode opcode { "sample_underscore123", "dummy" }; REQUIRE(opcode.opcode == "sample_underscore123"); REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore&")); REQUIRE(opcode.value == "dummy"); @@ -77,7 +78,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Parameterized opcode within the opcode") { - sfz::Opcode opcode { "sample1_underscore", "dummy" }; + Opcode opcode { "sample1_underscore", "dummy" }; REQUIRE(opcode.opcode == "sample1_underscore"); REQUIRE(opcode.lettersOnlyHash == hash("sample&_underscore")); REQUIRE(opcode.value == "dummy"); @@ -86,7 +87,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Parameterized opcode within the opcode") { - sfz::Opcode opcode { "sample123_underscore", "dummy" }; + Opcode opcode { "sample123_underscore", "dummy" }; REQUIRE(opcode.opcode == "sample123_underscore"); REQUIRE(opcode.lettersOnlyHash == hash("sample&_underscore")); REQUIRE(opcode.value == "dummy"); @@ -96,7 +97,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Parameterized opcode within the opcode twice") { - sfz::Opcode opcode { "sample123_double44_underscore", "dummy" }; + Opcode opcode { "sample123_double44_underscore", "dummy" }; REQUIRE(opcode.opcode == "sample123_double44_underscore"); REQUIRE(opcode.lettersOnlyHash == hash("sample&_double&_underscore")); REQUIRE(opcode.value == "dummy"); @@ -108,7 +109,7 @@ TEST_CASE("[Opcode] Construction") SECTION("Parameterized opcode within the opcode twice, with a back parameter") { - sfz::Opcode opcode { "sample123_double44_underscore23", "dummy" }; + Opcode opcode { "sample123_double44_underscore23", "dummy" }; REQUIRE(opcode.opcode == "sample123_double44_underscore23"); REQUIRE(opcode.lettersOnlyHash == hash("sample&_double&_underscore&")); REQUIRE(opcode.value == "dummy"); @@ -119,86 +120,86 @@ TEST_CASE("[Opcode] Construction") TEST_CASE("[Opcode] Note values") { - auto noteValue = sfz::readNoteValue("c-1"); + auto noteValue = readNoteValue("c-1"); REQUIRE(noteValue); REQUIRE(*noteValue == 0); - noteValue = sfz::readNoteValue("C-1"); + noteValue = readNoteValue("C-1"); REQUIRE(noteValue); REQUIRE(*noteValue == 0); - noteValue = sfz::readNoteValue("g9"); + noteValue = readNoteValue("g9"); REQUIRE(noteValue); REQUIRE(*noteValue == 127); - noteValue = sfz::readNoteValue("G9"); + noteValue = readNoteValue("G9"); REQUIRE(noteValue); REQUIRE(*noteValue == 127); - noteValue = sfz::readNoteValue("c#4"); + noteValue = readNoteValue("c#4"); REQUIRE(noteValue); REQUIRE(*noteValue == 61); - noteValue = sfz::readNoteValue(u8"c♯4"); + noteValue = readNoteValue(u8"c♯4"); REQUIRE(noteValue); REQUIRE(*noteValue == 61); - noteValue = sfz::readNoteValue("C#4"); + noteValue = readNoteValue("C#4"); REQUIRE(noteValue); REQUIRE(*noteValue == 61); - noteValue = sfz::readNoteValue(u8"C♯4"); + noteValue = readNoteValue(u8"C♯4"); REQUIRE(noteValue); REQUIRE(*noteValue == 61); - noteValue = sfz::readNoteValue("e#4"); + noteValue = readNoteValue("e#4"); REQUIRE(!noteValue); - noteValue = sfz::readNoteValue(u8"e♯4"); + noteValue = readNoteValue(u8"e♯4"); REQUIRE(!noteValue); - noteValue = sfz::readNoteValue("E#4"); + noteValue = readNoteValue("E#4"); REQUIRE(!noteValue); - noteValue = sfz::readNoteValue(u8"E♯4"); + noteValue = readNoteValue(u8"E♯4"); REQUIRE(!noteValue); - noteValue = sfz::readNoteValue("db4"); + noteValue = readNoteValue("db4"); REQUIRE(noteValue); REQUIRE(*noteValue == 61); - noteValue = sfz::readNoteValue(u8"d♭4"); + noteValue = readNoteValue(u8"d♭4"); REQUIRE(noteValue); REQUIRE(*noteValue == 61); - noteValue = sfz::readNoteValue("Db4"); + noteValue = readNoteValue("Db4"); REQUIRE(noteValue); REQUIRE(*noteValue == 61); - noteValue = sfz::readNoteValue(u8"D♭4"); + noteValue = readNoteValue(u8"D♭4"); REQUIRE(noteValue); REQUIRE(*noteValue == 61); - noteValue = sfz::readNoteValue("fb4"); + noteValue = readNoteValue("fb4"); REQUIRE(!noteValue); - noteValue = sfz::readNoteValue(u8"f♭4"); + noteValue = readNoteValue(u8"f♭4"); REQUIRE(!noteValue); - noteValue = sfz::readNoteValue("Fb4"); + noteValue = readNoteValue("Fb4"); REQUIRE(!noteValue); - noteValue = sfz::readNoteValue(u8"F♭4"); + noteValue = readNoteValue(u8"F♭4"); REQUIRE(!noteValue); } TEST_CASE("[Opcode] Categories") { - REQUIRE(sfz::Opcode("sample", "").category == sfz::kOpcodeNormal); - REQUIRE(sfz::Opcode("amplitude_oncc11", "").category == sfz::kOpcodeOnCcN); - REQUIRE(sfz::Opcode("cutoff_cc22", "").category == sfz::kOpcodeOnCcN); - REQUIRE(sfz::Opcode("lfo01_pitch_curvecc33", "").category == sfz::kOpcodeCurveCcN); - REQUIRE(sfz::Opcode("pan_stepcc44", "").category == sfz::kOpcodeStepCcN); - REQUIRE(sfz::Opcode("noise_level_smoothcc55", "").category == sfz::kOpcodeSmoothCcN); + REQUIRE(Opcode("sample", "").category == kOpcodeNormal); + REQUIRE(Opcode("amplitude_oncc11", "").category == kOpcodeOnCcN); + REQUIRE(Opcode("cutoff_cc22", "").category == kOpcodeOnCcN); + REQUIRE(Opcode("lfo01_pitch_curvecc33", "").category == kOpcodeCurveCcN); + REQUIRE(Opcode("pan_stepcc44", "").category == kOpcodeStepCcN); + REQUIRE(Opcode("noise_level_smoothcc55", "").category == kOpcodeSmoothCcN); } TEST_CASE("[Opcode] Derived names") { - REQUIRE(sfz::Opcode("sample", "").getDerivedName(sfz::kOpcodeNormal) == "sample"); - REQUIRE(sfz::Opcode("cutoff_cc22", "").getDerivedName(sfz::kOpcodeNormal) == "cutoff"); - REQUIRE(sfz::Opcode("lfo01_pitch_curvecc33", "").getDerivedName(sfz::kOpcodeOnCcN) == "lfo01_pitch_oncc33"); - REQUIRE(sfz::Opcode("pan_stepcc44", "").getDerivedName(sfz::kOpcodeCurveCcN) == "pan_curvecc44"); - REQUIRE(sfz::Opcode("noise_level_smoothcc55", "").getDerivedName(sfz::kOpcodeStepCcN) == "noise_level_stepcc55"); - REQUIRE(sfz::Opcode("sample", "").getDerivedName(sfz::kOpcodeSmoothCcN, 66) == "sample_smoothcc66"); + REQUIRE(Opcode("sample", "").getDerivedName(kOpcodeNormal) == "sample"); + REQUIRE(Opcode("cutoff_cc22", "").getDerivedName(kOpcodeNormal) == "cutoff"); + REQUIRE(Opcode("lfo01_pitch_curvecc33", "").getDerivedName(kOpcodeOnCcN) == "lfo01_pitch_oncc33"); + REQUIRE(Opcode("pan_stepcc44", "").getDerivedName(kOpcodeCurveCcN) == "pan_curvecc44"); + REQUIRE(Opcode("noise_level_smoothcc55", "").getDerivedName(kOpcodeStepCcN) == "noise_level_stepcc55"); + REQUIRE(Opcode("sample", "").getDerivedName(kOpcodeSmoothCcN, 66) == "sample_smoothcc66"); } TEST_CASE("[Opcode] Normalization") { // *_ccN - REQUIRE(sfz::Opcode("foo_cc7", "").cleanUp(sfz::kOpcodeScopeRegion).opcode == "foo_oncc7"); - REQUIRE(sfz::Opcode("foo_cc7", "").cleanUp(sfz::kOpcodeScopeControl).opcode == "foo_cc7"); + REQUIRE(Opcode("foo_cc7", "").cleanUp(kOpcodeScopeRegion).opcode == "foo_oncc7"); + REQUIRE(Opcode("foo_cc7", "").cleanUp(kOpcodeScopeControl).opcode == "foo_cc7"); // @@ -274,8 +275,8 @@ TEST_CASE("[Opcode] Normalization") for (auto pair : regionSpecific) { absl::string_view input = pair.first; absl::string_view expected = pair.second; - REQUIRE(sfz::Opcode(input, "").cleanUp(sfz::kOpcodeScopeRegion).opcode == expected); - REQUIRE(sfz::Opcode(input, "").cleanUp(sfz::kOpcodeScopeGeneric).opcode == input); + REQUIRE(Opcode(input, "").cleanUp(kOpcodeScopeRegion).opcode == expected); + REQUIRE(Opcode(input, "").cleanUp(kOpcodeScopeGeneric).opcode == input); } // @@ -288,42 +289,221 @@ TEST_CASE("[Opcode] Normalization") for (auto pair : controlSpecific) { absl::string_view input = pair.first; absl::string_view expected = pair.second; - REQUIRE(sfz::Opcode(input, "").cleanUp(sfz::kOpcodeScopeControl).opcode == expected); - REQUIRE(sfz::Opcode(input, "").cleanUp(sfz::kOpcodeScopeGeneric).opcode == input); + REQUIRE(Opcode(input, "").cleanUp(kOpcodeScopeControl).opcode == expected); + REQUIRE(Opcode(input, "").cleanUp(kOpcodeScopeGeneric).opcode == input); } // case - REQUIRE(sfz::Opcode("SaMpLe", "").cleanUp(sfz::kOpcodeScopeRegion).opcode == "sample"); + REQUIRE(Opcode("SaMpLe", "").cleanUp(kOpcodeScopeRegion).opcode == "sample"); } -TEST_CASE("[Opcode] readOpcode") +TEST_CASE("[Opcode] opcode read (uint8_t)") { - REQUIRE( sfz::readOpcode("16", sfz::Range(0, 100)).value() == 16 ); - REQUIRE( sfz::readOpcode("+16", sfz::Range(0, 100)).value() == 16 ); - REQUIRE( sfz::readOpcode("110", sfz::Range(0, 100)).value() == 100 ); - REQUIRE( sfz::readOpcode("-1", sfz::Range(0, 100)).value() == 0 ); - REQUIRE( sfz::readOpcode("12.5", sfz::Range(-100, 100)).value() == 12 ); - REQUIRE( sfz::readOpcode("+12.5", sfz::Range(-100, 100)).value() == 12 ); - REQUIRE( sfz::readOpcode("-40", sfz::Range(-100, 100)).value() == -40 ); - REQUIRE( sfz::readOpcode("-140", sfz::Range(-100, 100)).value() == -100 ); - REQUIRE( sfz::readOpcode("12.5", sfz::Range(0.0f, 100.0f)).value() == 12.5_a ); - REQUIRE( sfz::readOpcode("+12.5", sfz::Range(0.0f, 100.0f)).value() == 12.5_a ); - REQUIRE( sfz::readOpcode("-22.5", sfz::Range(-20.0f, 100.0f)).value() == -20.0_a ); - REQUIRE( sfz::readOpcode("150.5", sfz::Range(-20.0f, 100.0f)).value() == 100.0_a ); - REQUIRE( sfz::readOpcode("50.25garbage", sfz::Range(-20.0f, 100.0f)).value() == 50.25_a ); - REQUIRE( sfz::readOpcode("50.25garbage", sfz::Range(-20, 100)).value() == 50 ); - REQUIRE( !sfz::readOpcode("garbage50.25", sfz::Range(-20, 100)) ); - REQUIRE( !sfz::readOpcode("garbage", sfz::Range(-20, 100)) ); + SECTION("Basic") + { + Opcode opcode { "", "16" }; + OpcodeSpec spec { 0, Range(0, 100), 0 }; + REQUIRE( opcode.read(spec) == 16); + } + + SECTION("Sign") + { + Opcode opcode { "", "+16" }; + OpcodeSpec spec { 0, Range(0, 100), 0 }; + REQUIRE( opcode.read(spec) == 16); + } + + SECTION("Ignore") + { + Opcode opcode { "", "110" }; + OpcodeSpec spec { 0, Range(0, 100), kIgnoreOOB }; + REQUIRE( !opcode.read(spec) ); + } + + SECTION("Clamp upper") + { + Opcode opcode { "", "110" }; + OpcodeSpec spec { 0, Range(0, 100), kEnforceUpperBound }; + REQUIRE( opcode.read(spec) == 100 ); + } + + SECTION("Clamp lower") + { + Opcode opcode { "", "10" }; + OpcodeSpec spec { 0, Range(20, 100), kEnforceLowerBound }; + REQUIRE( opcode.read(spec) == 20 ); + } + + SECTION("Floating point") + { + Opcode opcode { "", "10.5" }; + OpcodeSpec spec { 0, Range(0, 100), 0 }; + REQUIRE( opcode.read(spec) == 10 ); + } + + SECTION("Text after") + { + Opcode opcode { "", "10garbage" }; + OpcodeSpec spec { 0, Range(20, 100), kEnforceLowerBound }; + REQUIRE( opcode.read(spec) == 20 ); + } + + SECTION("Text before") + { + Opcode opcode { "", "garbage10" }; + OpcodeSpec spec { 0, Range(20, 100), 0 }; + REQUIRE( !opcode.read(spec) ); + } + + SECTION("Can be note") + { + Opcode opcode { "", "c4" }; + OpcodeSpec spec { 0, Range(20, 100), kCanBeNote }; + REQUIRE( opcode.read(spec) == 60 ); + } +} + +TEST_CASE("[Opcode] opcode read (int)") +{ + SECTION("Basic") + { + Opcode opcode { "", "16" }; + OpcodeSpec spec { 0, Range(-100, 100), 0 }; + REQUIRE( opcode.read(spec) == 16); + } + + SECTION("Sign") + { + Opcode opcode { "", "+16" }; + OpcodeSpec spec { 0, Range(-100, 100), 0 }; + REQUIRE( opcode.read(spec) == 16); + } + + SECTION("Sign") + { + Opcode opcode { "", "-16" }; + OpcodeSpec spec { 0, Range(-100, 100), 0 }; + REQUIRE( opcode.read(spec) == -16); + } + + SECTION("Ignore") + { + Opcode opcode { "", "110" }; + OpcodeSpec spec { 0, Range(-100, 100), kIgnoreOOB }; + REQUIRE( !opcode.read(spec) ); + } + + SECTION("Clamp upper") + { + Opcode opcode { "", "110" }; + OpcodeSpec spec { 0, Range(-100, 100), kEnforceUpperBound }; + REQUIRE( opcode.read(spec) == 100 ); + } + + SECTION("Clamp lower") + { + Opcode opcode { "", "-110" }; + OpcodeSpec spec { 0, Range(-100, 100), kEnforceLowerBound }; + REQUIRE( opcode.read(spec) == -100 ); + } + + SECTION("Floating point") + { + Opcode opcode { "", "10.5" }; + OpcodeSpec spec { 0, Range(-100, 100), 0 }; + REQUIRE( opcode.read(spec) == 10 ); + } + + SECTION("Text after") + { + Opcode opcode { "", "10garbage" }; + OpcodeSpec spec { 0, Range(20, 100), kEnforceLowerBound }; + REQUIRE( opcode.read(spec) == 20 ); + } + + SECTION("Text before") + { + Opcode opcode { "", "garbage10" }; + OpcodeSpec spec { 0, Range(20, 100), 0 }; + REQUIRE( !opcode.read(spec) ); + } + + SECTION("Can be note") + { + Opcode opcode { "", "c4" }; + OpcodeSpec spec { 0, Range(20, 100), kCanBeNote }; + REQUIRE( opcode.read(spec) == 60 ); + } +} + + +TEST_CASE("[Opcode] opcode read (float)") +{ + SECTION("Basic") + { + Opcode opcode { "", "16.4" }; + OpcodeSpec spec { 0.0f, Range(-100.0f, 100.0f), 0 }; + REQUIRE( opcode.read(spec) == 16.4_a); + } + + SECTION("Plus sign") + { + Opcode opcode { "", "+16.4" }; + OpcodeSpec spec { 0.0f, Range(-100.0f, 100.0f), 0 }; + REQUIRE( opcode.read(spec) == 16.4_a); + } + + SECTION("Minus sign") + { + Opcode opcode { "", "-16.4" }; + OpcodeSpec spec { 0.0f, Range(-100.0f, 100.0f), 0 }; + REQUIRE( opcode.read(spec) == -16.4_a); + } + + SECTION("Ignore") + { + Opcode opcode { "", "110" }; + OpcodeSpec spec { 0.0f, Range(-100.0f, 100.0f), kIgnoreOOB }; + REQUIRE( !opcode.read(spec) ); + } + + SECTION("Clamp upper") + { + Opcode opcode { "", "110" }; + OpcodeSpec spec { 0.0f, Range(-100.0f, 100.0f), kEnforceUpperBound }; + REQUIRE( opcode.read(spec) == 100.0f ); + } + + SECTION("Clamp lower") + { + Opcode opcode { "", "-110" }; + OpcodeSpec spec { 0.0f, Range(-100.0f, 100.0f), kEnforceLowerBound }; + REQUIRE( opcode.read(spec) == -100.0f ); + } + + SECTION("Text after") + { + Opcode opcode { "", "10.5garbage" }; + OpcodeSpec spec { 0.0f, Range(0.0f, 100.0f), kEnforceLowerBound }; + REQUIRE( opcode.read(spec) == 10.5f ); + } + + SECTION("Text before") + { + Opcode opcode { "", "garbage10" }; + OpcodeSpec spec { 0.0f, Range(0.0f, 100.0f), 0 }; + REQUIRE( !opcode.read(spec) ); + } } TEST_CASE("[Opcode] readBooleanFromOpcode") { - REQUIRE(sfz::readBooleanFromOpcode({"", "1"}) == true); - REQUIRE(sfz::readBooleanFromOpcode({"", "0"}) == false); - REQUIRE(sfz::readBooleanFromOpcode({"", "777"}) == true); - REQUIRE(sfz::readBooleanFromOpcode({"", "on"}) == true); - REQUIRE(sfz::readBooleanFromOpcode({"", "off"}) == false); - REQUIRE(sfz::readBooleanFromOpcode({"", "On"}) == true); - REQUIRE(sfz::readBooleanFromOpcode({"", "oFf"}) == false); + REQUIRE(readBooleanFromOpcode({"", "1"}) == true); + REQUIRE(readBooleanFromOpcode({"", "0"}) == false); + REQUIRE(readBooleanFromOpcode({"", "777"}) == true); + REQUIRE(readBooleanFromOpcode({"", "on"}) == true); + REQUIRE(readBooleanFromOpcode({"", "off"}) == false); + REQUIRE(readBooleanFromOpcode({"", "On"}) == true); + REQUIRE(readBooleanFromOpcode({"", "oFf"}) == false); } diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index f3b1abca..39412b95 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -299,15 +299,15 @@ TEST_CASE("[Region] rt_decay") region.parseOpcode({ "rt_decay", "10" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume - 1.0f).margin(0.1) ); + REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume.value - 1.0f).margin(0.1) ); region.parseOpcode({ "rt_decay", "20" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume - 2.0f).margin(0.1) ); + REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume.value - 2.0f).margin(0.1) ); region.parseOpcode({ "trigger", "attack" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume).margin(0.1) ); + REQUIRE( region.getBaseVolumedB(64) == Approx(Default::volume.value).margin(0.1) ); } TEST_CASE("[Region] Base delay") diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index f0274fd1..0e2a38c0 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -250,7 +250,7 @@ TEST_CASE("[Values] Count") std::vector expected { "/region0/count,N : { }", "/region1/count,h : { 2 }", - "/region2/count,h : { 0 }", + "/region2/count,N : { }", }; REQUIRE(messageList == expected); } @@ -324,6 +324,7 @@ TEST_CASE("[Values] Loop range") Client client(&messageList); client.setReceiveCallback(&simpleMessageReceiver); synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav sample=kick.wav loop_start=10 loop_end=100 sample=kick.wav loopstart=10 loopend=100 sample=kick.wav loop_start=-1 loopend=-100 @@ -331,10 +332,12 @@ TEST_CASE("[Values] Loop range") synth.dispatchMessage(client, 0, "/region0/loop_range", "", nullptr); synth.dispatchMessage(client, 0, "/region1/loop_range", "", nullptr); synth.dispatchMessage(client, 0, "/region2/loop_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/loop_range", "", nullptr); std::vector expected { - "/region0/loop_range,hh : { 10, 100 }", + "/region0/loop_range,hh : { 0, 44011 }", // Default loop points in the file "/region1/loop_range,hh : { 10, 100 }", - "/region2/loop_range,hh : { 0, 0 }", + "/region2/loop_range,hh : { 10, 100 }", + "/region3/loop_range,hh : { 0, 44011 }", }; REQUIRE(messageList == expected); } @@ -487,7 +490,7 @@ TEST_CASE("[Values] Key range") "/region1/key_range,ii : { 34, 60 }", "/region2/key_range,ii : { 60, 83 }", "/region3/key_range,ii : { 0, 60 }", - "/region4/key_range,ii : { 0, 0 }", + "/region4/key_range,ii : { 0, 127 }", "/region0/pitch_keycenter,i : { 60 }", "/region5/pitch_keycenter,i : { 32 }", // "/region6/pitch_keycenter,i : { 60 }", @@ -497,6 +500,32 @@ TEST_CASE("[Values] Key range") REQUIRE(messageList == expected); } +TEST_CASE("[Values] Triggers on note") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav hikey=-1 + sample=kick.wav key=-1 + sample=kick.wav hikey=-1 lokey=12 + )"); + synth.dispatchMessage(client, 0, "/region0/trigger_on_note", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/trigger_on_note", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/trigger_on_note", "", nullptr); + // TODO: Double check with Sforzando/rgc + synth.dispatchMessage(client, 0, "/region3/trigger_on_note", "", nullptr); + std::vector expected { + "/region0/trigger_on_note,T : { }", + "/region1/trigger_on_note,F : { }", + "/region2/trigger_on_note,F : { }", + "/region3/trigger_on_note,T : { }", + }; + REQUIRE(messageList == expected); +} + TEST_CASE("[Values] Velocity range") { Synth synth; @@ -517,7 +546,7 @@ TEST_CASE("[Values] Velocity range") "/region0/vel_range,ff : { 0, 1 }", "/region1/vel_range,ff : { 0.267717, 0.472441 }", "/region2/vel_range,ff : { 0, 0.472441 }", - "/region3/vel_range,ff : { 0, 0 }", + "/region3/vel_range,ff : { 0, 1 }", }; REQUIRE(messageList == expected); } @@ -542,7 +571,7 @@ TEST_CASE("[Values] Bend range") "/region0/bend_range,ff : { -1, 1 }", "/region1/bend_range,ff : { 0.108778, 0.24417 }", "/region2/bend_range,ff : { -0.108778, 0.108778 }", - "/region3/bend_range,ff : { -1, -1 }", + "/region3/bend_range,ff : { -1, 1 }", }; REQUIRE(messageList == expected); } @@ -572,7 +601,7 @@ TEST_CASE("[Values] CC condition range") "/region1/cc_range1,ff : { 0, 0.425197 }", "/region2/cc_range1,ff : { 0, 0.425197 }", "/region2/cc_range2,ff : { 0.015748, 0.0787402 }", - "/region3/cc_range1,ff : { 0, 0 }", + "/region3/cc_range1,ff : { 0.0787402, 1 }", }; REQUIRE(messageList == expected); } @@ -595,7 +624,7 @@ TEST_CASE("[Values] CC condition range") "/region1/cc_range1,ff : { 0, 0.1 }", "/region2/cc_range1,ff : { 0, 0.1 }", "/region2/cc_range2,ff : { 0.1, 0.2 }", - "/region3/cc_range1,ff : { 0, 0 }", + "/region3/cc_range1,ff : { 0.1, 1 }", }; REQUIRE(messageList == expected); } @@ -618,7 +647,7 @@ TEST_CASE("[Values] CC condition range") "/region1/cc_range1,ff : { 0, 0.1 }", "/region2/cc_range1,ff : { 0, 0.1 }", "/region2/cc_range2,ff : { 0.1, 0.2 }", - "/region3/cc_range1,ff : { 0, 0 }", + "/region3/cc_range1,ff : { 0.1, 1 }", }; REQUIRE(messageList == expected); } @@ -708,19 +737,17 @@ TEST_CASE("[Values] Upswitch") )"); synth.dispatchMessage(client, 0, "/region0/sw_up", "", nullptr); synth.dispatchMessage(client, 0, "/region1/sw_up", "", nullptr); - // TODO: activate for the new region parser; ignore oob - // synth.dispatchMessage(client, 0, "/region2/sw_up", "", nullptr); - // synth.dispatchMessage(client, 0, "/region3/sw_up", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/sw_up", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/sw_up", "", nullptr); synth.dispatchMessage(client, 0, "/region4/sw_up", "", nullptr); - // TODO: activate for the new region parser; ignore the second value - // synth.dispatchMessage(client, 0, "/region5/sw_up", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/sw_up", "", nullptr); std::vector expected { "/region0/sw_up,N : { }", "/region1/sw_up,i : { 16 }", - // "/region2/sw_up,N : { }", - // "/region3/sw_up,N : { }", + "/region2/sw_up,N : { }", + "/region3/sw_up,N : { }", "/region4/sw_up,i : { 60 }", - // "/region5/sw_up,i : { 64 }", + "/region5/sw_up,i : { 64 }", }; REQUIRE(messageList == expected); } @@ -741,19 +768,17 @@ TEST_CASE("[Values] Downswitch") )"); synth.dispatchMessage(client, 0, "/region0/sw_down", "", nullptr); synth.dispatchMessage(client, 0, "/region1/sw_down", "", nullptr); - // TODO: activate for the new region parser; ignore oob - // synth.dispatchMessage(client, 0, "/region2/sw_down", "", nullptr); - // synth.dispatchMessage(client, 0, "/region3/sw_down", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/sw_down", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/sw_down", "", nullptr); synth.dispatchMessage(client, 0, "/region4/sw_down", "", nullptr); - // TODO: activate for the new region parser; ignore the second value - // synth.dispatchMessage(client, 0, "/region5/sw_down", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/sw_down", "", nullptr); std::vector expected { "/region0/sw_down,N : { }", "/region1/sw_down,i : { 16 }", - // "/region2/sw_down,N : { }", - // "/region3/sw_down,N : { }", + "/region2/sw_down,N : { }", + "/region3/sw_down,N : { }", "/region4/sw_down,i : { 60 }", - // "/region5/sw_down,i : { 64 }", + "/region5/sw_down,i : { 64 }", }; REQUIRE(messageList == expected); } @@ -774,19 +799,17 @@ TEST_CASE("[Values] Previous keyswitch") )"); synth.dispatchMessage(client, 0, "/region0/sw_previous", "", nullptr); synth.dispatchMessage(client, 0, "/region1/sw_previous", "", nullptr); - // TODO: activate for the new region parser; ignore oob - // synth.dispatchMessage(client, 0, "/region2/sw_previous", "", nullptr); - // synth.dispatchMessage(client, 0, "/region3/sw_previous", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/sw_previous", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/sw_previous", "", nullptr); synth.dispatchMessage(client, 0, "/region4/sw_previous", "", nullptr); - // TODO: activate for the new region parser; ignore the second value - // synth.dispatchMessage(client, 0, "/region5/sw_previous", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/sw_previous", "", nullptr); std::vector expected { "/region0/sw_previous,N : { }", "/region1/sw_previous,i : { 16 }", - // "/region2/sw_previous,N : { }", - // "/region3/sw_previous,N : { }", + "/region2/sw_previous,N : { }", + "/region3/sw_previous,N : { }", "/region4/sw_previous,i : { 60 }", - // "/region5/sw_previous,i : { 64 }", + "/region5/sw_previous,i : { 64 }", }; REQUIRE(messageList == expected); } @@ -838,7 +861,7 @@ TEST_CASE("[Values] Aftertouch range") "/region0/chanaft_range,ii : { 0, 127 }", "/region1/chanaft_range,ii : { 34, 60 }", "/region2/chanaft_range,ii : { 0, 60 }", - "/region3/chanaft_range,ii : { 0, 0 }", + "/region3/chanaft_range,ii : { 20, 127 }", "/region4/chanaft_range,ii : { 10, 10 }", }; REQUIRE(messageList == expected); @@ -894,7 +917,7 @@ TEST_CASE("[Values] Rand range") "/region0/rand_range,ff : { 0, 1 }", "/region1/rand_range,ff : { 0.2, 0.4 }", "/region2/rand_range,ff : { 0, 0.4 }", - "/region3/rand_range,ff : { 0, 0 }", + "/region3/rand_range,ff : { 0.2, 1 }", "/region4/rand_range,ff : { 0.1, 0.1 }", }; REQUIRE(messageList == expected); @@ -1582,16 +1605,15 @@ TEST_CASE("[Values] Crossfade key range") )"); synth.dispatchMessage(client, 0, "/region0/xfin_key_range", "", nullptr); synth.dispatchMessage(client, 0, "/region1/xfin_key_range", "", nullptr); - // TODO: activate for the new region parser ; parse note value - // synth.dispatchMessage(client, 0, "/region2/xfin_key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/xfin_key_range", "", nullptr); synth.dispatchMessage(client, 0, "/region3/xfin_key_range", "", nullptr); synth.dispatchMessage(client, 0, "/region4/xfin_key_range", "", nullptr); std::vector expected { "/region0/xfin_key_range,ii : { 0, 0 }", "/region1/xfin_key_range,ii : { 10, 40 }", - // "/region2/xfin_key_range,ii : { 60, 83 }", + "/region2/xfin_key_range,ii : { 60, 83 }", "/region3/xfin_key_range,ii : { 0, 40 }", - "/region4/xfin_key_range,ii : { 10, 127 }", + "/region4/xfin_key_range,ii : { 10, 10 }", }; REQUIRE(messageList == expected); } @@ -1607,15 +1629,14 @@ TEST_CASE("[Values] Crossfade key range") )"); synth.dispatchMessage(client, 0, "/region0/xfout_key_range", "", nullptr); synth.dispatchMessage(client, 0, "/region1/xfout_key_range", "", nullptr); - // TODO: activate for the new region parser ; parse note value - // synth.dispatchMessage(client, 0, "/region2/xfout_key_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/xfout_key_range", "", nullptr); synth.dispatchMessage(client, 0, "/region3/xfout_key_range", "", nullptr); synth.dispatchMessage(client, 0, "/region4/xfout_key_range", "", nullptr); std::vector expected { "/region0/xfout_key_range,ii : { 127, 127 }", "/region1/xfout_key_range,ii : { 10, 40 }", - // "/region2/xfout_key_range,ii : { 60, 83 }", - "/region3/xfout_key_range,ii : { 0, 40 }", + "/region2/xfout_key_range,ii : { 60, 83 }", + "/region3/xfout_key_range,ii : { 40, 40 }", "/region4/xfout_key_range,ii : { 10, 127 }", }; REQUIRE(messageList == expected); @@ -1968,13 +1989,13 @@ TEST_CASE("[Values] Pitch/Tune") sample=kick.wav pitch=4.2 sample=kick.wav tune=-200 )"); - synth.dispatchMessage(client, 0, "/region0/tune", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/tune", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/tune", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitch", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pitch", "", nullptr); std::vector expected { - "/region0/tune,f : { 0 }", - "/region1/tune,f : { 4.2 }", - "/region2/tune,f : { -200 }", + "/region0/pitch,f : { 0 }", + "/region1/pitch,f : { 4.2 }", + "/region2/pitch,f : { -200 }", }; REQUIRE(messageList == expected); } @@ -1983,16 +2004,16 @@ TEST_CASE("[Values] Pitch/Tune") { synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( sample=kick.wav - sample=kick.wav tune_oncc42=4.2 + sample=kick.wav pitch_oncc42=4.2 sample=kick.wav pitch_oncc2=-10 )"); - synth.dispatchMessage(client, 0, "/region0/tune_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/tune_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/tune_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitch_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_cc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pitch_cc2", "", nullptr); std::vector expected { - "/region0/tune_cc42,N : { }", - "/region1/tune_cc42,f : { 4.2 }", - "/region2/tune_cc2,f : { -10 }", + "/region0/pitch_cc42,N : { }", + "/region1/pitch_cc42,f : { 4.2 }", + "/region2/pitch_cc2,f : { -10 }", }; REQUIRE(messageList == expected); } @@ -2001,33 +2022,33 @@ TEST_CASE("[Values] Pitch/Tune") { synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( sample=kick.wav - sample=kick.wav tune_stepcc42=4.2 - sample=kick.wav tune_smoothcc42=4 - sample=kick.wav tune_curvecc42=2 - sample=kick.wav tune_stepcc42=-1 - sample=kick.wav tune_smoothcc42=-4 - sample=kick.wav tune_curvecc42=300 + sample=kick.wav pitch_stepcc42=4.2 + sample=kick.wav pitch_smoothcc42=4 + sample=kick.wav pitch_curvecc42=2 + sample=kick.wav pitch_stepcc42=-1 + sample=kick.wav pitch_smoothcc42=-4 + sample=kick.wav pitch_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/tune_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/tune_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/tune_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/tune_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/tune_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/tune_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitch_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitch_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitch_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pitch_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/pitch_curvecc42", "", nullptr); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/tune_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/tune_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/tune_curvecc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region4/pitch_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/pitch_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/pitch_curvecc42", "", nullptr); std::vector expected { - "/region0/tune_stepcc42,N : { }", - "/region0/tune_smoothcc42,N : { }", - "/region0/tune_curvecc42,N : { }", - "/region1/tune_stepcc42,f : { 4.2 }", - "/region2/tune_smoothcc42,i : { 4 }", - "/region3/tune_curvecc42,i : { 2 }", - // "/region4/tune_stepcc42,N : { }", - // "/region5/tune_smoothcc42,N : { }", - // "/region6/tune_curvecc42,N : { }", + "/region0/pitch_stepcc42,N : { }", + "/region0/pitch_smoothcc42,N : { }", + "/region0/pitch_curvecc42,N : { }", + "/region1/pitch_stepcc42,f : { 4.2 }", + "/region2/pitch_smoothcc42,i : { 4 }", + "/region3/pitch_curvecc42,i : { 2 }", + // "/region4/pitch_stepcc42,N : { }", + // "/region5/pitch_smoothcc42,N : { }", + // "/region6/pitch_curvecc42,N : { }", }; REQUIRE(messageList == expected); } @@ -2043,26 +2064,26 @@ TEST_CASE("[Values] Pitch/Tune") sample=kick.wav pitch_smoothcc42=-4 sample=kick.wav pitch_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/tune_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/tune_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/tune_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/tune_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/tune_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/tune_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitch_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitch_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitch_curvecc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_stepcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pitch_smoothcc42", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/pitch_curvecc42", "", nullptr); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/tune_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/tune_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/tune_curvecc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region4/pitch_stepcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region5/pitch_smoothcc42", "", nullptr); + // synth.dispatchMessage(client, 0, "/region6/pitch_curvecc42", "", nullptr); std::vector expected { - "/region0/tune_stepcc42,N : { }", - "/region0/tune_smoothcc42,N : { }", - "/region0/tune_curvecc42,N : { }", - "/region1/tune_stepcc42,f : { 4.2 }", - "/region2/tune_smoothcc42,i : { 4 }", - "/region3/tune_curvecc42,i : { 2 }", - // "/region4/tune_stepcc42,N : { }", - // "/region5/tune_smoothcc42,N : { }", - // "/region6/tune_curvecc42,N : { }", + "/region0/pitch_stepcc42,N : { }", + "/region0/pitch_smoothcc42,N : { }", + "/region0/pitch_curvecc42,N : { }", + "/region1/pitch_stepcc42,f : { 4.2 }", + "/region2/pitch_smoothcc42,i : { 4 }", + "/region3/pitch_curvecc42,i : { 2 }", + // "/region4/pitch_stepcc42,N : { }", + // "/region5/pitch_smoothcc42,N : { }", + // "/region6/pitch_curvecc42,N : { }", }; REQUIRE(messageList == expected); } @@ -2093,17 +2114,17 @@ TEST_CASE("[Values] Bend behavior") synth.dispatchMessage(client, 0, "/region2/bend_step", "", nullptr); synth.dispatchMessage(client, 0, "/region2/bend_smooth", "", nullptr); std::vector expected { - "/region0/bend_up,i : { 200 }", - "/region0/bend_down,i : { -200 }", - "/region0/bend_step,i : { 1 }", + "/region0/bend_up,f : { 200 }", + "/region0/bend_down,f : { -200 }", + "/region0/bend_step,f : { 1 }", "/region0/bend_smooth,i : { 0 }", - "/region1/bend_up,i : { 100 }", - "/region1/bend_down,i : { -400 }", - "/region1/bend_step,i : { 10 }", + "/region1/bend_up,f : { 100 }", + "/region1/bend_down,f : { -400 }", + "/region1/bend_step,f : { 10 }", "/region1/bend_smooth,i : { 10 }", - "/region2/bend_up,i : { -100 }", - "/region2/bend_down,i : { 400 }", - "/region2/bend_step,i : { 1 }", + "/region2/bend_up,f : { -100 }", + "/region2/bend_down,f : { 400 }", + "/region2/bend_step,f : { 1 }", "/region2/bend_smooth,i : { 0 }", }; REQUIRE(messageList == expected); @@ -2162,7 +2183,7 @@ TEST_CASE("[Values] ampeg") "/region0/ampeg_release,f : { 0.001 }", "/region0/ampeg_start,f : { 0 }", "/region0/ampeg_sustain,f : { 100 }", - "/region0/ampeg_depth,i : { 0 }", + "/region0/ampeg_depth,f : { 0 }", "/region1/ampeg_attack,f : { 1 }", "/region1/ampeg_delay,f : { 2 }", "/region1/ampeg_decay,f : { 3 }", @@ -2170,7 +2191,7 @@ TEST_CASE("[Values] ampeg") "/region1/ampeg_release,f : { 5 }", "/region1/ampeg_start,f : { 6 }", "/region1/ampeg_sustain,f : { 7 }", - "/region1/ampeg_depth,i : { 0 }", + "/region1/ampeg_depth,f : { 0 }", // "/region2/ampeg_attack,f : { 0 }", // "/region2/ampeg_delay,f : { 0 }", // "/region2/ampeg_decay,f : { 0 }", @@ -2178,7 +2199,7 @@ TEST_CASE("[Values] ampeg") // "/region2/ampeg_release,f : { 0.001 }", // "/region2/ampeg_start,f : { 0 }", // "/region2/ampeg_sustain,f : { 100 }", - // "/region2/ampeg_depth,i : { 0 }", + // "/region2/ampeg_depth,f : { 0 }", }; REQUIRE(messageList == expected); } @@ -2213,14 +2234,14 @@ TEST_CASE("[Values] ampeg") "/region0/ampeg_vel2hold,f : { 0 }", "/region0/ampeg_vel2release,f : { 0 }", "/region0/ampeg_vel2sustain,f : { 0 }", - "/region0/ampeg_vel2depth,i : { 0 }", + "/region0/ampeg_vel2depth,f : { 0 }", "/region1/ampeg_vel2attack,f : { 1 }", "/region1/ampeg_vel2delay,f : { 2 }", "/region1/ampeg_vel2decay,f : { 3 }", "/region1/ampeg_vel2hold,f : { 4 }", "/region1/ampeg_vel2release,f : { 5 }", "/region1/ampeg_vel2sustain,f : { 7 }", - "/region1/ampeg_vel2depth,i : { 0 }", + "/region1/ampeg_vel2depth,f : { 0 }", }; REQUIRE(messageList == expected); } @@ -2420,18 +2441,102 @@ TEST_CASE("[Values] Oscillator phase") )"); synth.dispatchMessage(client, 0, "/region0/oscillator_phase", "", nullptr); synth.dispatchMessage(client, 0, "/region1/oscillator_phase", "", nullptr); - // TODO: activate for the new region parser ; properly wrap - // synth.dispatchMessage(client, 0, "/region2/oscillator_phase", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/oscillator_phase", "", nullptr); synth.dispatchMessage(client, 0, "/region3/oscillator_phase", "", nullptr); std::vector expected { "/region0/oscillator_phase,f : { 0 }", "/region1/oscillator_phase,f : { 0.1 }", - // "/region2/oscillator_phase,f : { 0.1 }", + "/region2/oscillator_phase,f : { 0.1 }", "/region3/oscillator_phase,f : { -1 }", }; REQUIRE(messageList == expected); } +TEST_CASE("[Values] Oscillator quality") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav oscillator_quality=2 + sample=kick.wav oscillator_quality=0 oscillator_quality=-2 + )"); + synth.dispatchMessage(client, 0, "/region0/oscillator_quality", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/oscillator_quality", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/oscillator_quality", "", nullptr); + std::vector expected { + "/region0/oscillator_quality,N : { }", + "/region1/oscillator_quality,i : { 2 }", + "/region2/oscillator_quality,i : { 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Oscillator mode/multi") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav oscillator_mode=2 + sample=kick.wav oscillator_mode=1 oscillator_mode=-2 + sample=kick.wav oscillator_multi=9 + sample=kick.wav oscillator_multi=-2 + )"); + synth.dispatchMessage(client, 0, "/region0/oscillator_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/oscillator_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/oscillator_mode", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/oscillator_multi", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/oscillator_multi", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/oscillator_multi", "", nullptr); + std::vector expected { + "/region0/oscillator_mode,i : { 0 }", + "/region1/oscillator_mode,i : { 2 }", + "/region2/oscillator_mode,i : { 1 }", + "/region0/oscillator_multi,i : { 1 }", + "/region3/oscillator_multi,i : { 9 }", + "/region4/oscillator_multi,i : { 1 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Oscillator detune/mod depth") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav oscillator_detune=9.2 + sample=kick.wav oscillator_detune=-1200.2 + sample=kick.wav oscillator_mod_depth=1564.75 + sample=kick.wav oscillator_mod_depth=-2.2 + )"); + synth.dispatchMessage(client, 0, "/region0/oscillator_detune", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/oscillator_detune", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/oscillator_detune", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/oscillator_mod_depth", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/oscillator_mod_depth", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/oscillator_mod_depth", "", nullptr); + std::vector expected { + "/region0/oscillator_detune,f : { 0 }", + "/region1/oscillator_detune,f : { 9.2 }", + "/region2/oscillator_detune,f : { -1200.2 }", + "/region0/oscillator_mod_depth,f : { 0 }", + "/region3/oscillator_mod_depth,f : { 1564.75 }", + "/region4/oscillator_mod_depth,f : { 0 }", + }; + REQUIRE(messageList == expected); +} + TEST_CASE("[Values] Effect sends") { Synth synth; diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index b9bf7ab8..fa5cb076 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -572,14 +572,14 @@ TEST_CASE("[Synth] sample quality") // default sample quality synth.noteOn(0, 60, 100); REQUIRE(synth.getNumActiveVoices() == 1); - REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::sampleQuality); + REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::sampleQuality.value); synth.allSoundOff(); // default sample quality, freewheeling synth.enableFreeWheeling(); synth.noteOn(0, 60, 100); REQUIRE(synth.getNumActiveVoices() == 1); - REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::sampleQualityInFreewheelingMode); + REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::freewheelingQuality); synth.allSoundOff(); synth.disableFreeWheeling(); @@ -1085,7 +1085,7 @@ TEST_CASE("[Synth] Used CCs") locc4=64 hicc67=32 pan_cc5=200 sample=*sine width_cc98=200 sample=*sine position_cc42=200 pitch_oncc56=200 sample=*sine - start_locc44=200 hikey=-1 sample=*sine + start_locc44=120 hikey=-1 sample=*sine )"); auto usedCCs = synth.getUsedCCs(); REQUIRE( usedCCs.test(1) ); diff --git a/tests/TestFiles/note_offset.sfz b/tests/TestFiles/note_offset.sfz deleted file mode 100644 index 40760e49..00000000 --- a/tests/TestFiles/note_offset.sfz +++ /dev/null @@ -1,12 +0,0 @@ - note_offset=1 - key=63 sample=*sine - lokey=50 hikey=55 pitch_keycenter=50 sample=*sine - lokey=40 hikey=44 pitch_keycenter=40 xfin_lokey=36 xfin_hikey=40 xfout_lokey=44 xfout_hikey=48 sample=*sine - note_offset=-1 - key=63 sw_lokey=24 sw_hikey=28 sw_last=25 sw_up=25 sw_down=25 sw_previous=62 sample=*sine - note_offset=1 octave_offset=1 - key=63 sample=*sine - note_offset=-1 octave_offset=-1 - key=63 sample=*sine - // Check that this does not reset either note or octave offset - key=63 sample=*sine