From 4ea11a31bfacf69246cb4d145585a900a64eb55d Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 00:20:11 +0200 Subject: [PATCH 01/22] Moved curves to resources --- src/sfizz/Resources.h | 2 ++ src/sfizz/Synth.cpp | 8 ++++---- src/sfizz/Synth.h | 4 ---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index 1367d714..2c6adcfc 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -11,6 +11,7 @@ #include "EQPool.h" #include "Logger.h" #include "Wavetables.h" +#include "Curve.h" namespace sfz { @@ -21,6 +22,7 @@ struct Resources BufferPool bufferPool; MidiState midiState; Logger logger; + CurveSet curves; FilePool filePool { logger }; FilterPool filterPool { midiState }; EQPool eqPool { midiState }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 0870905f..5e4d7fe9 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -71,7 +71,7 @@ void sfz::Synth::onParseFullBlock(const std::string& header, const std::vectorsetGainToMain(1.0); effectBuses[0]->setSamplesPerBlock(samplesPerBlock); effectBuses[0]->setSampleRate(sampleRate); - curves = CurveSet::createPredefined(); + resources.curves = CurveSet::createPredefined(); resources.filePool.clear(); resources.wavePool.clearFileWaves(); resources.logger.clear(); + resources.midiState.reset(); numGroups = 0; numMasters = 0; fileTicket = -1; defaultSwitch = absl::nullopt; defaultPath = ""; - resources.midiState.reset(); ccNames.clear(); globalOpcodes.clear(); masterOpcodes.clear(); @@ -814,7 +814,7 @@ int sfz::Synth::getNumMasters() const noexcept } int sfz::Synth::getNumCurves() const noexcept { - return static_cast(curves.getNumCurves()); + return static_cast(resources.curves.getNumCurves()); } std::string sfz::Synth::exportMidnam(absl::string_view model) const diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 6b3d4c37..6ee6da73 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -10,7 +10,6 @@ #include "Voice.h" #include "Region.h" #include "Effects.h" -#include "Curve.h" #include "LeakDetector.h" #include "MidiState.h" #include "AudioSpan.h" @@ -515,9 +514,6 @@ private: typedef std::unique_ptr EffectBusPtr; std::vector effectBuses; // 0 is "main", 1-N are "fx1"-"fxN" - // Curves - CurveSet curves; - int samplesPerBlock { config::defaultSamplesPerBlock }; float sampleRate { config::defaultSampleRate }; float volume { Default::globalVolume }; From 13edb6dfbab23e968d7a3f812c987a401eda2f1a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 00:29:42 +0200 Subject: [PATCH 02/22] Change the midiState observer to a resource observer for tests --- src/sfizz/Synth.h | 2 +- tests/FilesT.cpp | 5 +++-- tests/SynthT.cpp | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 6ee6da73..9f053cc9 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -339,7 +339,7 @@ public: */ void disableFreeWheeling() noexcept; - const MidiState& getMidiState() const noexcept { return resources.midiState; } + const Resources& getResources() const noexcept { return resources; } /** * @brief Check if the SFZ should be reloaded. diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 2442d8df..876aef89 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -394,9 +394,10 @@ TEST_CASE("[Files] Default path is ignored for generators") TEST_CASE("[Files] Set CC applies properly") { sfz::Synth synth; + const auto& midiState = synth.getResources().midiState; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_cc.sfz"); - REQUIRE(synth.getMidiState().getCCValue(142) == 63_norm); - REQUIRE(synth.getMidiState().getCCValue(61) == 122_norm); + REQUIRE(midiState.getCCValue(142) == 63_norm); + REQUIRE(midiState.getCCValue(61) == 122_norm); } TEST_CASE("[Files] Note and octave offsets") diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index bd0d28cc..29b55645 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -140,10 +140,11 @@ TEST_CASE("[Synth] All notes offs/all sounds off") TEST_CASE("[Synth] Reset all controllers") { sfz::Synth synth; + const auto& midiState = synth.getResources().midiState; synth.cc(0, 12, 64); - REQUIRE(synth.getMidiState().getCCValue(12) == 64_norm); + REQUIRE(midiState.getCCValue(12) == 64_norm); synth.cc(0, 121, 64); - REQUIRE(synth.getMidiState().getCCValue(12) == 0_norm); + REQUIRE(midiState.getCCValue(12) == 0_norm); } TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice") From 0fd1c235de2a50524161800f90187a15fbfce735 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 00:26:44 +0200 Subject: [PATCH 03/22] Added a basic curve test --- tests/SynthT.cpp | 14 ++++++++++++++ tests/TestFiles/curves.sfz | 10 ++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/TestFiles/curves.sfz diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 29b55645..6d7022ba 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -420,3 +420,17 @@ TEST_CASE("[Synth] Polyphony in master") synth.noteOn(0, 61, 64); REQUIRE(synth.getNumActiveVoices() == 3); } + +TEST_CASE("[Synth] Basic curves") +{ + sfz::Synth synth; + const auto& curves = synth.getResources().curves; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/curves.sfz"); + REQUIRE( synth.getNumCurves() == 19 ); + REQUIRE( curves.getCurve(18).evalCC7(127) == 1.0f ); + REQUIRE( curves.getCurve(18).evalCC7(95) == 0.5f ); + REQUIRE( curves.getCurve(17).evalCC7(100) == 1.0f ); + REQUIRE( curves.getCurve(17).evalCC7(95) == 0.5f ); + // Default linear + REQUIRE( curves.getCurve(16).evalCC7(63) == Approx(63_norm) ); +} diff --git a/tests/TestFiles/curves.sfz b/tests/TestFiles/curves.sfz new file mode 100644 index 00000000..8c24cca4 --- /dev/null +++ b/tests/TestFiles/curves.sfz @@ -0,0 +1,10 @@ + sample=*sine +curve_index=18 +v000=0 +v095=0.5 +v127=1 + +curve_index=17 +v000=0 +v095=0.5 +v100=1 From 61d1c568a8af657ffe4cdb1aa70a51c665e36094 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 01:19:40 +0200 Subject: [PATCH 04/22] Add a modifier structure and rename the ccvalue pair --- src/sfizz/CCMap.h | 18 +++++------ src/sfizz/Defaults.h | 1 - src/sfizz/EGDescription.h | 18 +++++------ src/sfizz/EQPool.cpp | 12 ++++---- src/sfizz/FilterPool.cpp | 12 ++++---- src/sfizz/Opcode.h | 2 +- src/sfizz/Region.cpp | 24 +++++++-------- src/sfizz/Region.h | 12 ++++---- src/sfizz/SfzHelpers.h | 41 ++++++++++--------------- src/sfizz/Voice.cpp | 20 ++++++------ tests/FilesT.cpp | 2 +- tests/RegionT.cpp | 64 +++++++++++++++++++-------------------- 12 files changed, 108 insertions(+), 118 deletions(-) diff --git a/src/sfizz/CCMap.h b/src/sfizz/CCMap.h index 09e8830c..1b44cf8c 100644 --- a/src/sfizz/CCMap.h +++ b/src/sfizz/CCMap.h @@ -44,11 +44,11 @@ public: */ const ValueType& getWithDefault(int index) const noexcept { - auto it = absl::c_lower_bound(container, index, CCValuePairComparator{}); + auto it = absl::c_lower_bound(container, index, CCDataComparator{}); if (it == container.end() || it->cc != index) { return defaultValue; } else { - return it->value; + return it->data; } } @@ -60,12 +60,12 @@ public: */ ValueType& operator[](const int& index) noexcept { - auto it = absl::c_lower_bound(container, index, CCValuePairComparator{}); + auto it = absl::c_lower_bound(container, index, CCDataComparator{}); if (it == container.end() || it->cc != index) { auto inserted = container.insert(it, { index, defaultValue }); - return inserted->value; + return inserted->data; } else { - return it->value; + return it->data; } } @@ -85,16 +85,16 @@ public: */ bool contains(int index) const noexcept { - return absl::c_binary_search(container, index, CCValuePairComparator{}); + return absl::c_binary_search(container, index, CCDataComparator{}); } - typename std::vector>::const_iterator begin() const { return container.cbegin(); } - typename std::vector>::const_iterator end() const { return container.cend(); } + typename std::vector>::const_iterator begin() const { return container.cbegin(); } + typename std::vector>::const_iterator end() const { return container.cend(); } private: // typename std::vector>::iterator begin() { return container.begin(); } // typename std::vector>::iterator end() { return container.end(); } const ValueType defaultValue; - std::vector> container; + std::vector> container; LEAK_DETECTOR(CCMap); }; } diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 158da597..a0cae7a1 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -58,7 +58,6 @@ namespace Default constexpr Range midi7Range { 0, 127 }; constexpr Range normalizedRange { 0.0f, 1.0f }; constexpr Range symmetricNormalizedRange { -1.0, 1.0 }; - constexpr float zeroModifier { 0.0f }; // Wavetable oscillator constexpr float oscillatorPhase { 0.0 }; diff --git a/src/sfizz/EGDescription.h b/src/sfizz/EGDescription.h index ee44f754..e64fa642 100644 --- a/src/sfizz/EGDescription.h +++ b/src/sfizz/EGDescription.h @@ -50,10 +50,10 @@ namespace sfz { * @param value * @return float */ -inline float ccSwitchedValue(const MidiState& state, const absl::optional>& ccSwitch, float value) noexcept +inline float ccSwitchedValue(const MidiState& state, const absl::optional>& ccSwitch, float value) noexcept { if (ccSwitch) - return value + ccSwitch->value * state.getCCValue(ccSwitch->cc); + return value + ccSwitch->data * state.getCCValue(ccSwitch->cc); else return value; } @@ -80,13 +80,13 @@ struct EGDescription { float vel2sustain { Default::vel2sustain }; int vel2depth { Default::depth }; - absl::optional> ccAttack; - absl::optional> ccDecay; - absl::optional> ccDelay; - absl::optional> ccHold; - absl::optional> ccRelease; - absl::optional> ccStart; - absl::optional> ccSustain; + absl::optional> ccAttack; + absl::optional> ccDecay; + absl::optional> ccDelay; + absl::optional> ccHold; + absl::optional> ccRelease; + absl::optional> ccStart; + absl::optional> ccSustain; /** * @brief Get the attack with possibly a CC modifier and a velocity modifier diff --git a/src/sfizz/EQPool.cpp b/src/sfizz/EQPool.cpp index 65d5866c..adfdfec7 100644 --- a/src/sfizz/EQPool.cpp +++ b/src/sfizz/EQPool.cpp @@ -29,17 +29,17 @@ void sfz::EQHolder::setup(const EQDescription& description, unsigned numChannels // Setup the modulated values lastFrequency = baseFrequency; for (const auto& mod : description.frequencyCC) - lastFrequency += midiState.getCCValue(mod.cc) * mod.value; + lastFrequency += midiState.getCCValue(mod.cc) * mod.data; lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); lastBandwidth = baseBandwidth; for (const auto& mod : description.bandwidthCC) - lastBandwidth += midiState.getCCValue(mod.cc) * mod.value; + lastBandwidth += midiState.getCCValue(mod.cc) * mod.data; lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); lastGain = baseGain; for (const auto& mod : description.gainCC) - lastGain += midiState.getCCValue(mod.cc) * mod.value; + lastGain += midiState.getCCValue(mod.cc) * mod.data; lastGain = Default::filterGainRange.clamp(lastGain); // Initialize the EQ @@ -62,17 +62,17 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF // For now we take the last value lastFrequency = baseFrequency; for (const auto& mod : description->frequencyCC) - lastFrequency += midiState.getCCValue(mod.cc) * mod.value; + lastFrequency += midiState.getCCValue(mod.cc) * mod.data; lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); lastBandwidth = baseBandwidth; for (const auto& mod : description->bandwidthCC) - lastBandwidth += midiState.getCCValue(mod.cc) * mod.value; + lastBandwidth += midiState.getCCValue(mod.cc) * mod.data; lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); lastGain = baseGain; for (const auto& mod : description->gainCC) - lastGain += midiState.getCCValue(mod.cc) * mod.value; + lastGain += midiState.getCCValue(mod.cc) * mod.data; lastGain = Default::filterGainRange.clamp(lastGain); if (lastGain == 0.0f) { diff --git a/src/sfizz/FilterPool.cpp b/src/sfizz/FilterPool.cpp index 9e4f9091..3b61276f 100644 --- a/src/sfizz/FilterPool.cpp +++ b/src/sfizz/FilterPool.cpp @@ -41,17 +41,17 @@ void sfz::FilterHolder::setup(const FilterDescription& description, unsigned num // Setup the modulated values lastCutoff = baseCutoff; for (const auto& mod : description.cutoffCC) - lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value); + lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.data); lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); lastResonance = baseResonance; for (const auto& mod : description.resonanceCC) - lastResonance += midiState.getCCValue(mod.cc) * mod.value; + lastResonance += midiState.getCCValue(mod.cc) * mod.data; lastResonance = Default::filterResonanceRange.clamp(lastResonance); lastGain = baseGain; for (const auto& mod : description.gainCC) - lastGain += midiState.getCCValue(mod.cc) * mod.value; + lastGain += midiState.getCCValue(mod.cc) * mod.data; lastGain = Default::filterGainRange.clamp(lastGain); // Initialize the filter @@ -71,17 +71,17 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned // TODO: the template deduction could be automatic here? lastCutoff = baseCutoff; for (const auto& mod : description->cutoffCC) - lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value); + lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.data); lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); lastResonance = baseResonance; for (const auto& mod : description->resonanceCC) - lastResonance += midiState.getCCValue(mod.cc) * mod.value; + lastResonance += midiState.getCCValue(mod.cc) * mod.data; lastResonance = Default::filterResonanceRange.clamp(lastResonance); lastGain = baseGain; for (const auto& mod : description->gainCC) - lastGain += midiState.getCCValue(mod.cc) * mod.value; + lastGain += midiState.getCCValue(mod.cc) * mod.data; lastGain = Default::filterGainRange.clamp(lastGain); filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames); diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index e4ff6c26..492cfb1e 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -185,7 +185,7 @@ inline void setRangeStartFromOpcode(const Opcode& opcode, Range& targ * @param validRange the range of admitted values used to clamp the opcode */ template -inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional>& target, const Range& validRange) +inline 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())) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 234878e9..85b014d2 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -303,7 +303,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (opcode.parameters.back() > config::numCCs) return false; if (auto value = readOpcode(opcode.value, Default::volumeCCRange)) - volumeCC[opcode.parameters.back()] = *value; + volumeCC[opcode.parameters.back()].value = *value; break; case hash("amplitude"): if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) @@ -314,7 +314,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (opcode.parameters.back() > config::numCCs) return false; if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) - amplitudeCC[opcode.parameters.back()] = normalizePercents(*value); + amplitudeCC[opcode.parameters.back()].value = normalizePercents(*value); break; case hash("pan"): if (auto value = readOpcode(opcode.value, Default::panRange)) @@ -325,7 +325,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (opcode.parameters.back() > config::numCCs) return false; if (auto value = readOpcode(opcode.value, Default::panCCRange)) - panCC[opcode.parameters.back()] = normalizePercents(*value); + panCC[opcode.parameters.back()].value = normalizePercents(*value); break; case hash("position"): if (auto value = readOpcode(opcode.value, Default::positionRange)) @@ -335,7 +335,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (opcode.parameters.back() > config::numCCs) return false; if (auto value = readOpcode(opcode.value, Default::positionCCRange)) - positionCC[opcode.parameters.back()] = normalizePercents(*value); + positionCC[opcode.parameters.back()].value = normalizePercents(*value); break; case hash("width"): if (auto value = readOpcode(opcode.value, Default::widthRange)) @@ -345,7 +345,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (opcode.parameters.back() > config::numCCs) return false; if (auto value = readOpcode(opcode.value, Default::widthCCRange)) - widthCC[opcode.parameters.back()] = normalizePercents(*value); + widthCC[opcode.parameters.back()].value = normalizePercents(*value); break; case hash("amp_keycenter"): setValueFromOpcode(opcode, ampKeycenter, Default::keyRange); @@ -733,7 +733,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (opcode.parameters.back() > config::numCCs) return false; if (auto value = readOpcode(opcode.value, Default::tuneCCRange)) - tuneCC[opcode.parameters.back()] = *value; + tuneCC[opcode.parameters.back()].value = *value; break; case hash("bend_up"): setValueFromOpcode(opcode, bendUp, Default::bendBoundRange); @@ -1078,15 +1078,15 @@ float sfz::Region::getCrossfadeGain() const noexcept float gain { 1.0f }; // Crossfades due to CC states - for (const auto& valuePair : crossfadeCCInRange) { - const auto ccValue = midiState.getCCValue(valuePair.cc); - const auto crossfadeRange = valuePair.value; + for (const auto& ccData : crossfadeCCInRange) { + const auto ccValue = midiState.getCCValue(ccData.cc); + const auto crossfadeRange = ccData.data; gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve); } - for (const auto& valuePair : crossfadeCCOutRange) { - const auto ccValue = midiState.getCCValue(valuePair.cc); - const auto crossfadeRange = valuePair.value; + for (const auto& ccData : crossfadeCCOutRange) { + const auto ccValue = midiState.getCCValue(ccData.cc); + const auto crossfadeRange = ccData.data; gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve); } diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 73f3bc53..572d3eeb 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -285,11 +285,11 @@ struct Region { float pan { normalizePercents(Default::pan) }; // pan float width { normalizePercents(Default::width) }; // width float position { normalizePercents(Default::position) }; // position - CCMap volumeCC { Default::zeroModifier }; // volume_oncc - CCMap amplitudeCC { Default::zeroModifier }; // amplitude_oncc - CCMap panCC { Default::zeroModifier }; // pan_oncc - CCMap widthCC { Default::zeroModifier }; // width_oncc - CCMap positionCC { Default::zeroModifier }; // position_oncc + CCMap volumeCC { {} }; // volume_oncc + CCMap amplitudeCC { {} }; // amplitude_oncc + CCMap panCC { {} }; // pan_oncc + CCMap widthCC { {} }; // width_oncc + CCMap positionCC { {} }; // position_oncc uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter float ampKeytrack { Default::ampKeytrack }; // amp_keytrack float ampVeltrack { Default::ampVeltrack }; // amp_keytrack @@ -317,7 +317,7 @@ struct Region { int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack int transpose { Default::transpose }; // transpose int tune { Default::tune }; // tune - CCMap tuneCC { Default::tune }; + CCMap tuneCC { {} }; int bendUp { Default::bendUp }; int bendDown { Default::bendDown }; int bendStep { Default::bendStep }; diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 64d6ed6c..fb1d0636 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -24,44 +24,35 @@ using CCNamePair = std::pair; template using MidiNoteArray = std::array; template -struct CCValuePair { +struct CCData { int cc; - ValueType value; + ValueType data; + static_assert(config::numCCs - 1 < std::numeric_limits::max()); }; -template -struct CCValuePairComparator { - bool operator()(const CCValuePair& valuePair, const int& cc) - { - return (valuePair.cc < cc); - } - - bool operator()(const int& cc, const CCValuePair& valuePair) - { - return (cc < valuePair.cc); - } - - bool operator()(const CCValuePair& lhs, const CCValuePair& rhs) - { - return (lhs.cc < rhs.cc); - } +struct Modifier { + float value { 0.0f }; + uint8_t curve { 0 }; + uint8_t steps { 0 }; + uint8_t smooth { 0 }; + static_assert(config::maxCurves - 1 <= std::numeric_limits::max()); }; template -struct CCValuePairComparator { - bool operator()(const CCValuePair& valuePair, const ValueType& value) +struct CCDataComparator { + bool operator()(const CCData& ccData, const int& cc) { - return (valuePair.value < value); + return (ccData.cc < cc); } - bool operator()(const ValueType& value, const CCValuePair& valuePair) + bool operator()(const int& cc, const CCData& ccData) { - return (value < valuePair.value); + return (cc < ccData.cc); } - bool operator()(const CCValuePair& lhs, const CCValuePair& rhs) + bool operator()(const CCData& lhs, const CCData& rhs) { - return (lhs.value < rhs.value); + return (lhs.cc < rhs.cc); } }; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index fcb1d9fd..370d29d8 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -265,19 +265,19 @@ void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept applyGain(baseGain, modulationSpan); for (const auto& mod : region->amplitudeCC) { const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); applyGain(*tempSpan, modulationSpan); } // Crossfade envelopes for (const auto& mod : region->crossfadeCCInRange) { const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); }); + linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.data, x, xfCurve); }); applyGain(*tempSpan, modulationSpan); } for (const auto& mod : region->crossfadeCCOutRange) { const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); }); + linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.data, x, xfCurve); }); applyGain(*tempSpan, modulationSpan); } @@ -285,7 +285,7 @@ void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept applyGain(db2mag(baseVolumedB), modulationSpan); for (const auto& mod : region->volumeCC) { const auto events = resources.midiState.getCCEvents(mod.cc); - multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.value); }); + multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.data.value); }); applyGain(*tempSpan, modulationSpan); } } @@ -338,7 +338,7 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept fill(*modulationSpan, region->pan); for (const auto& mod : region->panCC) { const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); @@ -361,7 +361,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept fill(*modulationSpan, region->pan); for (const auto& mod : region->panCC) { const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); @@ -371,7 +371,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept fill(*modulationSpan, region->width); for (const auto& mod : region->widthCC) { const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); add(*tempSpan, *modulationSpan); } width(*modulationSpan, leftBuffer, rightBuffer); @@ -380,7 +380,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept fill(*modulationSpan, region->position); for (const auto& mod : region->positionCC) { const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); @@ -458,7 +458,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept for (const auto& mod : region->tuneCC) { const auto events = resources.midiState.getCCEvents(mod.cc); - multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.value); }); + multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.data.value); }); applyGain(*bends, *jumps); } @@ -562,7 +562,7 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept for (const auto& mod : region->tuneCC) { const auto events = resources.midiState.getCCEvents(mod.cc); - multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.value); }); + multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.data.value); }); applyGain(*bends, *frequencies); } diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 876aef89..4dd43c8f 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -351,7 +351,7 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines") REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 ); REQUIRE(!synth.getRegionView(2)->amplitudeCC.empty()); REQUIRE(synth.getRegionView(2)->amplitudeCC.contains(10)); - REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10) == 0.34f); + REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10).value == 0.34f); } TEST_CASE("[Files] Specific bug: relative path with backslashes") diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 86fde09c..b98f9f51 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -470,7 +470,7 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.panCC.empty()); region.parseOpcode({ "pan_oncc45", "4.2" }); REQUIRE(region.panCC.contains(45)); - REQUIRE(region.panCC[45] == 0.042_a); + REQUIRE(region.panCC[45].value == 0.042_a); } SECTION("width") @@ -491,7 +491,7 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.widthCC.empty()); region.parseOpcode({ "width_oncc45", "4.2" }); REQUIRE(region.widthCC.contains(45)); - REQUIRE(region.widthCC[45] == 0.042_a); + REQUIRE(region.widthCC[45].value == 0.042_a); } SECTION("position") @@ -512,7 +512,7 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.positionCC.empty()); region.parseOpcode({ "position_oncc45", "4.2" }); REQUIRE(region.positionCC.contains(45)); - REQUIRE(region.positionCC[45] == 0.042_a); + REQUIRE(region.positionCC[45].value == 0.042_a); } SECTION("amp_keycenter") @@ -974,13 +974,13 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.amplitudeEG.ccRelease->cc == 5); REQUIRE(region.amplitudeEG.ccStart->cc == 6); REQUIRE(region.amplitudeEG.ccSustain->cc == 7); - REQUIRE(region.amplitudeEG.ccAttack->value == 1.0f); - REQUIRE(region.amplitudeEG.ccDecay->value == 2.0f); - REQUIRE(region.amplitudeEG.ccDelay->value == 3.0f); - REQUIRE(region.amplitudeEG.ccHold->value == 4.0f); - REQUIRE(region.amplitudeEG.ccRelease->value == 5.0f); - REQUIRE(region.amplitudeEG.ccStart->value == 6.0f); - REQUIRE(region.amplitudeEG.ccSustain->value == 7.0f); + REQUIRE(region.amplitudeEG.ccAttack->data == 1.0f); + REQUIRE(region.amplitudeEG.ccDecay->data == 2.0f); + REQUIRE(region.amplitudeEG.ccDelay->data == 3.0f); + REQUIRE(region.amplitudeEG.ccHold->data == 4.0f); + REQUIRE(region.amplitudeEG.ccRelease->data == 5.0f); + REQUIRE(region.amplitudeEG.ccStart->data == 6.0f); + REQUIRE(region.amplitudeEG.ccSustain->data == 7.0f); // region.parseOpcode({ "ampeg_attack_oncc1", "101" }); region.parseOpcode({ "ampeg_decay_oncc2", "101" }); @@ -989,13 +989,13 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "ampeg_release_oncc5", "101" }); region.parseOpcode({ "ampeg_start_oncc6", "101" }); region.parseOpcode({ "ampeg_sustain_oncc7", "101" }); - REQUIRE(region.amplitudeEG.ccAttack->value == 100.0f); - REQUIRE(region.amplitudeEG.ccDecay->value == 100.0f); - REQUIRE(region.amplitudeEG.ccDelay->value == 100.0f); - REQUIRE(region.amplitudeEG.ccHold->value == 100.0f); - REQUIRE(region.amplitudeEG.ccRelease->value == 100.0f); - REQUIRE(region.amplitudeEG.ccStart->value == 100.0f); - REQUIRE(region.amplitudeEG.ccSustain->value == 100.0f); + REQUIRE(region.amplitudeEG.ccAttack->data == 100.0f); + REQUIRE(region.amplitudeEG.ccDecay->data == 100.0f); + REQUIRE(region.amplitudeEG.ccDelay->data == 100.0f); + REQUIRE(region.amplitudeEG.ccHold->data == 100.0f); + REQUIRE(region.amplitudeEG.ccRelease->data == 100.0f); + REQUIRE(region.amplitudeEG.ccStart->data == 100.0f); + REQUIRE(region.amplitudeEG.ccSustain->data == 100.0f); // region.parseOpcode({ "ampeg_attack_oncc1", "-101" }); region.parseOpcode({ "ampeg_decay_oncc2", "-101" }); @@ -1004,13 +1004,13 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "ampeg_release_oncc5", "-101" }); region.parseOpcode({ "ampeg_start_oncc6", "-101" }); region.parseOpcode({ "ampeg_sustain_oncc7", "-101" }); - REQUIRE(region.amplitudeEG.ccAttack->value == -100.0f); - REQUIRE(region.amplitudeEG.ccDecay->value == -100.0f); - REQUIRE(region.amplitudeEG.ccDelay->value == -100.0f); - REQUIRE(region.amplitudeEG.ccHold->value == -100.0f); - REQUIRE(region.amplitudeEG.ccRelease->value == -100.0f); - REQUIRE(region.amplitudeEG.ccStart->value == -100.0f); - REQUIRE(region.amplitudeEG.ccSustain->value == -100.0f); + REQUIRE(region.amplitudeEG.ccAttack->data == -100.0f); + REQUIRE(region.amplitudeEG.ccDecay->data == -100.0f); + REQUIRE(region.amplitudeEG.ccDelay->data == -100.0f); + REQUIRE(region.amplitudeEG.ccHold->data == -100.0f); + REQUIRE(region.amplitudeEG.ccRelease->data == -100.0f); + REQUIRE(region.amplitudeEG.ccStart->data == -100.0f); + REQUIRE(region.amplitudeEG.ccSustain->data == -100.0f); } SECTION("sustain_sw and sostenuto_sw") @@ -1448,10 +1448,10 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.amplitudeCC.empty()); region.parseOpcode({ "amplitude_cc1", "40" }); REQUIRE(region.amplitudeCC.contains(1)); - REQUIRE(region.amplitudeCC[1] == 0.40_a); + REQUIRE(region.amplitudeCC[1].value == 0.40_a); region.parseOpcode({ "amplitude_oncc2", "30" }); REQUIRE(region.amplitudeCC.contains(2)); - REQUIRE(region.amplitudeCC[2] == 0.30_a); + REQUIRE(region.amplitudeCC[2].value == 0.30_a); } SECTION("volume_oncc/gain_cc") @@ -1459,13 +1459,13 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.volumeCC.empty()); region.parseOpcode({ "gain_cc1", "40" }); REQUIRE(region.volumeCC.contains(1)); - REQUIRE(region.volumeCC[1] == 40_a); + REQUIRE(region.volumeCC[1].value == 40_a); region.parseOpcode({ "volume_oncc2", "-76" }); REQUIRE(region.volumeCC.contains(2)); - REQUIRE(region.volumeCC[2] == -76.0_a); + REQUIRE(region.volumeCC[2].value == -76.0_a); region.parseOpcode({ "gain_oncc4", "-1" }); REQUIRE(region.volumeCC.contains(4)); - REQUIRE(region.volumeCC[4] == -1.0_a); + REQUIRE(region.volumeCC[4].value == -1.0_a); } SECTION("tune_cc/pitch_cc") @@ -1473,13 +1473,13 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.tuneCC.empty()); region.parseOpcode({ "pitch_cc1", "40" }); REQUIRE(region.tuneCC.contains(1)); - REQUIRE(region.tuneCC[1] == 40); + REQUIRE(region.tuneCC[1].value == 40.0); region.parseOpcode({ "tune_oncc2", "-76" }); REQUIRE(region.tuneCC.contains(2)); - REQUIRE(region.tuneCC[2] == -76.0); + REQUIRE(region.tuneCC[2].value == -76.0); region.parseOpcode({ "pitch_oncc4", "-1" }); REQUIRE(region.tuneCC.contains(4)); - REQUIRE(region.tuneCC[4] == -1.0); + REQUIRE(region.tuneCC[4].value == -1.0); } } From fbb9ebabb28057ed51e168404828598cc997852a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 01:22:14 +0200 Subject: [PATCH 05/22] Check that the index passed to the curves arenot too crazy --- src/sfizz/Config.h | 1 + src/sfizz/Curve.cpp | 6 ++++++ tests/CurveT.cpp | 11 +++++++++++ 3 files changed, 18 insertions(+) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 727a125f..48aeb70f 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -56,6 +56,7 @@ namespace config { constexpr size_t powerHistoryLength { 16 }; constexpr float voiceStealingThreshold { 0.00001f }; constexpr uint16_t numCCs { 512 }; + constexpr int maxCurves { 256 }; constexpr int chunkSize { 1024 }; constexpr int filtersInPool { maxVoices * 2 }; constexpr int filtersPerVoice { 2 }; diff --git a/src/sfizz/Curve.cpp b/src/sfizz/Curve.cpp index 44aa3c24..f349efad 100644 --- a/src/sfizz/Curve.cpp +++ b/src/sfizz/Curve.cpp @@ -196,6 +196,12 @@ void CurveSet::addCurve(const Curve& curve, int explicitIndex) { std::unique_ptr* slot; + if (explicitIndex < -1) + return; + + if (explicitIndex >= config::maxCurves) + return; + if (explicitIndex == -1) { if (_useExplicitIndexing) return; // reject implicit indices if any were explicit before diff --git a/tests/CurveT.cpp b/tests/CurveT.cpp index 9c8b054b..1aa9e161 100644 --- a/tests/CurveT.cpp +++ b/tests/CurveT.cpp @@ -199,6 +199,17 @@ TEST_CASE("[Curve] Add curves to CurveSet") REQUIRE( curveSet.getCurve(4).evalCC7(0) == 1.0f ); } +TEST_CASE("[Curve] Add bad indices") +{ + sfz::CurveSet curveSet; + curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), -2); + REQUIRE( curveSet.getNumCurves() == 0 ); + curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), 256); + REQUIRE( curveSet.getNumCurves() == 0 ); + curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), 512); + REQUIRE( curveSet.getNumCurves() == 0 ); +} + TEST_CASE("[Curve] Default CurveSet") { auto curveSet = sfz::CurveSet::createPredefined(); From 3620316caf0b45265bc65945f989e50810f46c56 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 01:40:17 +0200 Subject: [PATCH 06/22] Parse curve/step/smooth --- src/sfizz/Defaults.h | 3 ++ src/sfizz/Region.cpp | 112 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index a0cae7a1..822e9cd3 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -81,6 +81,9 @@ namespace Default // Region logic: MIDI conditions constexpr Range channelRange { 1, 16 }; constexpr Range midiChannelRange { 0, 15 }; + constexpr Range stepCCRange { 0, 127 }; + constexpr Range smoothCCRange { 0, 127 }; + constexpr Range curveCCRange { 0, 255 }; constexpr Range ccNumberRange { 0, config::numCCs }; constexpr auto ccValueRange = normalizedRange; constexpr Range bendRange = { -8192, 8192 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 85b014d2..f150728d 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -297,6 +297,24 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("volume"): setValueFromOpcode(opcode, volume, Default::volumeRange); break; + case hash("volume_curvecc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::curveCCRange)) + volumeCC[opcode.parameters.back()].curve = *value; + break; + case hash("volume_stepcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::stepCCRange)) + volumeCC[opcode.parameters.back()].steps = *value; + break; + case hash("volume_smoothcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) + volumeCC[opcode.parameters.back()].smooth = *value; + break; case hash("gain_cc&"): case hash("gain_oncc&"): // fallthrough case hash("volume_oncc&"): @@ -309,6 +327,24 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) amplitude = normalizePercents(*value); break; + case hash("amplitude_curvecc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::curveCCRange)) + amplitudeCC[opcode.parameters.back()].curve = *value; + break; + case hash("amplitude_stepcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::stepCCRange)) + amplitudeCC[opcode.parameters.back()].steps = *value; + break; + case hash("amplitude_smoothcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) + amplitudeCC[opcode.parameters.back()].smooth = *value; + break; case hash("amplitude_cc&"): // fallthrough case hash("amplitude_oncc&"): if (opcode.parameters.back() > config::numCCs) @@ -320,6 +356,24 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (auto value = readOpcode(opcode.value, Default::panRange)) pan = normalizePercents(*value); break; + case hash("pan_curvecc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::curveCCRange)) + panCC[opcode.parameters.back()].curve = *value; + break; + case hash("pan_stepcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::stepCCRange)) + panCC[opcode.parameters.back()].steps = *value; + break; + case hash("pan_smoothcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) + panCC[opcode.parameters.back()].smooth = *value; + break; case hash("pan_cc&"): case hash("pan_oncc&"): if (opcode.parameters.back() > config::numCCs) @@ -331,6 +385,25 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (auto value = readOpcode(opcode.value, Default::positionRange)) position = normalizePercents(*value); break; + case hash("position_curvecc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::curveCCRange)) + positionCC[opcode.parameters.back()].curve = *value; + break; + case hash("position_stepcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::stepCCRange)) + positionCC[opcode.parameters.back()].steps = *value; + break; + case hash("position_smoothcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) + positionCC[opcode.parameters.back()].smooth = *value; + break; + case hash("position_cc&"): // fallthrough case hash("position_oncc&"): if (opcode.parameters.back() > config::numCCs) return false; @@ -341,6 +414,24 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) if (auto value = readOpcode(opcode.value, Default::widthRange)) width = normalizePercents(*value); break; + case hash("width_curvecc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::curveCCRange)) + widthCC[opcode.parameters.back()].curve = *value; + break; + case hash("width_stepcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::stepCCRange)) + widthCC[opcode.parameters.back()].steps = *value; + break; + case hash("width_smoothcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) + widthCC[opcode.parameters.back()].smooth = *value; + break; case hash("width_oncc&"): if (opcode.parameters.back() > config::numCCs) return false; @@ -726,6 +817,27 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("pitch"): setValueFromOpcode(opcode, tune, Default::tuneRange); break; + case hash("pitch_curvecc&"): // fallthrough + case hash("tune_curvecc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::curveCCRange)) + tuneCC[opcode.parameters.back()].curve = *value; + break; + case hash("pitch_stepcc&"): // fallthrough + case hash("tune_stepcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::stepCCRange)) + tuneCC[opcode.parameters.back()].steps = *value; + break; + case hash("pitch_smoothcc&"): // fallthrough + case hash("tune_smoothcc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::smoothCCRange)) + tuneCC[opcode.parameters.back()].smooth = *value; + break; case hash("tune_cc&"): case hash("tune_oncc&"): case hash("pitch_cc&"): From 0aad8533eaae5a2ecd8fec293de582290cf15375 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 12:20:00 +0200 Subject: [PATCH 07/22] Add helpers to handle curve and cc modifiersfor both linear and multiplicative curves --- src/sfizz/Voice.cpp | 84 +++++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 370d29d8..ee3484d1 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -249,6 +249,51 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept #endif } +template +void linearModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData, F&& lambda) +{ + const auto events = resources.midiState.getCCEvents(ccData.cc); + const auto curve = resources.curves.getCurve(ccData.data.curve); + if (ccData.data.steps == 0) { + linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }); + } else { + const float stepSize { ccData.data.value / ccData.data.steps }; + linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }, stepSize); + } +} + +template +void multiplicativeModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData, F&& lambda) +{ + const auto events = resources.midiState.getCCEvents(ccData.cc); + const auto curve = resources.curves.getCurve(ccData.data.curve); + if (ccData.data.steps == 0) { + multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }); + } else { + // FIXME: not sure about this step size for multiplicative envelopes + const float stepSize { ccData.data.value / ccData.data.steps }; + multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }, stepSize); + } +} + +void linearModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData) +{ + linearModifier(resources, span, ccData, [](float x) { return x; }); +} + +void multiplicativeModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData) +{ + multiplicativeModifier(resources, span, ccData, [](float x) { return x; }); +} + void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept { const auto numSamples = modulationSpan.size(); @@ -264,28 +309,32 @@ void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept // Amplitude envelope applyGain(baseGain, modulationSpan); for (const auto& mod : region->amplitudeCC) { - const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); + linearModifier(resources, *tempSpan, mod); applyGain(*tempSpan, modulationSpan); } // Crossfade envelopes for (const auto& mod : region->crossfadeCCInRange) { const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.data, x, xfCurve); }); + linearEnvelope(events, *tempSpan, [&](float x) { + return crossfadeIn(mod.data, x, xfCurve); + }); applyGain(*tempSpan, modulationSpan); } for (const auto& mod : region->crossfadeCCOutRange) { const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.data, x, xfCurve); }); + linearEnvelope(events, *tempSpan, [&](float x) { + return crossfadeOut(mod.data, x, xfCurve); + }); applyGain(*tempSpan, modulationSpan); } // Volume envelope applyGain(db2mag(baseVolumedB), modulationSpan); for (const auto& mod : region->volumeCC) { - const auto events = resources.midiState.getCCEvents(mod.cc); - multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.data.value); }); + multiplicativeModifier(resources, *tempSpan, mod, [](float x) { + return db2mag(x); + }); applyGain(*tempSpan, modulationSpan); } } @@ -337,8 +386,7 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept // Apply panning fill(*modulationSpan, region->pan); for (const auto& mod : region->panCC) { - const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); + linearModifier(resources, *tempSpan, mod); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); @@ -357,30 +405,24 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept return; // Apply panning - // panningModulation(*modulationSpan); fill(*modulationSpan, region->pan); for (const auto& mod : region->panCC) { - const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); + linearModifier(resources, *tempSpan, mod); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); // Apply the width/position process - // widthModulation(*modulationSpan); fill(*modulationSpan, region->width); for (const auto& mod : region->widthCC) { - const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); + linearModifier(resources, *tempSpan, mod); add(*tempSpan, *modulationSpan); } width(*modulationSpan, leftBuffer, rightBuffer); - // positionModulation(*modulationSpan); fill(*modulationSpan, region->position); for (const auto& mod : region->positionCC) { - const auto events = resources.midiState.getCCEvents(mod.cc); - linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.data.value; }); + linearModifier(resources, *tempSpan, mod); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); @@ -457,8 +499,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept applyGain(*bends, *jumps); for (const auto& mod : region->tuneCC) { - const auto events = resources.midiState.getCCEvents(mod.cc); - multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.data.value); }); + multiplicativeModifier(resources, *bends, mod, [](float x) { return centsFactor(x); }); applyGain(*bends, *jumps); } @@ -561,8 +602,9 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept applyGain(*bends, *frequencies); for (const auto& mod : region->tuneCC) { - const auto events = resources.midiState.getCCEvents(mod.cc); - multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.data.value); }); + multiplicativeModifier(resources, *bends, mod, [](float x) { + return centsFactor(x); + }); applyGain(*bends, *frequencies); } From da6df7db8a87a6a690c8faf8c89cdb1c3f987f78 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 13:07:44 +0200 Subject: [PATCH 08/22] Try to solve the C++11 static asserts --- src/sfizz/SfzHelpers.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index fb1d0636..838ac863 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -27,7 +27,7 @@ template struct CCData { int cc; ValueType data; - static_assert(config::numCCs - 1 < std::numeric_limits::max()); + static_assert(config::numCCs - 1 < std::numeric_limits::max(), "The cc type in the CCData struct cannot support the required number of CCs"); }; struct Modifier { @@ -35,7 +35,7 @@ struct Modifier { uint8_t curve { 0 }; uint8_t steps { 0 }; uint8_t smooth { 0 }; - static_assert(config::maxCurves - 1 <= std::numeric_limits::max()); + static_assert(config::maxCurves - 1 <= std::numeric_limits::max(), "The curve type in the Modifier struct cannot support the required number of curves"); }; template From 434868e52e466fa189dc4a79b521616d60eaa67b Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 19:07:46 +0200 Subject: [PATCH 09/22] Add a clear() method in the resources --- src/sfizz/Resources.h | 9 +++++++++ src/sfizz/Synth.cpp | 7 ++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index 2c6adcfc..17f924a9 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -40,5 +40,14 @@ struct Resources bufferPool.setBufferSize(samplesPerBlock); midiState.setSamplesPerBlock(samplesPerBlock); } + + void clear() + { + curves = CurveSet::createPredefined(); + filePool.clear(); + wavePool.clearFileWaves(); + logger.clear(); + midiState.reset(); + } }; } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 5e4d7fe9..c618f154 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -130,17 +130,14 @@ void sfz::Synth::clear() list.clear(); for (auto& list : ccActivationLists) list.clear(); + regions.clear(); effectBuses.clear(); effectBuses.emplace_back(new EffectBus); effectBuses[0]->setGainToMain(1.0); effectBuses[0]->setSamplesPerBlock(samplesPerBlock); effectBuses[0]->setSampleRate(sampleRate); - resources.curves = CurveSet::createPredefined(); - resources.filePool.clear(); - resources.wavePool.clearFileWaves(); - resources.logger.clear(); - resources.midiState.reset(); + resources.clear(); numGroups = 0; numMasters = 0; fileTicket = -1; From 9d6b2ede2f490f9d5bd5cd5242931ac9b6bca206 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 19:11:13 +0200 Subject: [PATCH 10/22] Removed the deprecated fileTicket --- src/sfizz/Synth.cpp | 1 - src/sfizz/Synth.h | 1 - 2 files changed, 2 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index c618f154..19a3ba5b 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -140,7 +140,6 @@ void sfz::Synth::clear() resources.clear(); numGroups = 0; numMasters = 0; - fileTicket = -1; defaultSwitch = absl::nullopt; defaultPath = ""; ccNames.clear(); diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 9f053cc9..4d34d8e8 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -522,7 +522,6 @@ private: // Distribution used to generate random value for the *rand opcodes std::uniform_real_distribution randNoteDistribution { 0, 1 }; - unsigned fileTicket { 1 }; std::mutex callbackGuard; bool freeWheeling { false }; From 232eada0c4b72c85a94f4a1638f4f7c1aa2796ac Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 19:26:39 +0200 Subject: [PATCH 11/22] Add the modifier helpers to another file --- src/sfizz/ModifierHelpers.h | 234 ++++++++++++++++++++++++++++++++++++ src/sfizz/Region.cpp | 3 +- src/sfizz/Region.h | 4 +- src/sfizz/SfzHelpers.h | 195 ------------------------------ src/sfizz/Voice.cpp | 51 +------- src/sfizz/Voice.h | 5 +- tests/EventEnvelopesT.cpp | 2 +- 7 files changed, 240 insertions(+), 254 deletions(-) create mode 100644 src/sfizz/ModifierHelpers.h diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h new file mode 100644 index 00000000..5dd3fd78 --- /dev/null +++ b/src/sfizz/ModifierHelpers.h @@ -0,0 +1,234 @@ +#pragma once + +#include "Range.h" +#include "Defaults.h" +#include "SfzHelpers.h" +#include "Resources.h" +#include "absl/types/span.h" + +namespace sfz { + +/** + * @brief Compute a crossfade in value with respect to a crossfade range (note, velocity, cc, ...) + */ +template +float crossfadeIn(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCurve curve) +{ + if (value < crossfadeRange.getStart()) + return 0.0f; + + const auto length = static_cast(crossfadeRange.length()); + if (length == 0.0f) + return 1.0f; + + else if (value < crossfadeRange.getEnd()) { + const auto crossfadePosition = static_cast(value - crossfadeRange.getStart()) / length; + if (curve == SfzCrossfadeCurve::power) + return sqrt(crossfadePosition); + if (curve == SfzCrossfadeCurve::gain) + return crossfadePosition; + } + + return 1.0f; +} + +/** + * @brief Compute a crossfade out value with respect to a crossfade range (note, velocity, cc, ...) + */ +template +float crossfadeOut(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCurve curve) +{ + if (value > crossfadeRange.getEnd()) + return 0.0f; + + const auto length = static_cast(crossfadeRange.length()); + if (length == 0.0f) + return 1.0f; + + else if (value > crossfadeRange.getStart()) { + const auto crossfadePosition = static_cast(value - crossfadeRange.getStart()) / length; + if (curve == SfzCrossfadeCurve::power) + return std::sqrt(1 - crossfadePosition); + if (curve == SfzCrossfadeCurve::gain) + return 1 - crossfadePosition; + } + + return 1.0f; +} + +template +void linearEnvelope(const EventVector& events, absl::Span envelope, F&& lambda) +{ + ASSERT(events.size() > 0); + ASSERT(events[0].delay == 0); + if (envelope.size() == 0) + return; + + const auto maxDelay = static_cast(envelope.size() - 1); + + auto lastValue = lambda(events[0].value); + auto lastDelay = events[0].delay; + for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) { + const auto length = min(events[i].delay, maxDelay) - lastDelay; + const auto step = (lambda(events[i].value) - lastValue) / length; + lastValue = linearRamp(envelope.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(envelope.subspan(lastDelay), lastValue); +} + +template +void linearEnvelope(const EventVector& events, absl::Span envelope, F&& lambda, float step) +{ + ASSERT(events.size() > 0); + ASSERT(events[0].delay == 0); + ASSERT(step != 0.0); + + if (envelope.size() == 0) + return; + + auto quantize = [step](float value) -> float { + return std::round(value / step) * step; + }; + const auto maxDelay = static_cast(envelope.size() - 1); + + auto lastValue = quantize(lambda(events[0].value)); + auto lastDelay = events[0].delay; + for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) { + const auto nextValue = quantize(lambda(events[i].value)); + const auto difference = std::abs(nextValue - lastValue); + const auto length = min(events[i].delay, maxDelay) - lastDelay; + + if (difference < step) { + fill(envelope.subspan(lastDelay, length), lastValue); + lastValue = nextValue; + lastDelay += length; + continue; + } + + const auto numSteps = static_cast(difference / step); + const auto stepLength = static_cast(length / numSteps); + for (int i = 0; i < numSteps; ++i) { + fill(envelope.subspan(lastDelay, stepLength), lastValue); + lastValue += lastValue <= nextValue ? step : -step; + lastDelay += stepLength; + } + } + fill(envelope.subspan(lastDelay), lastValue); +} + +template +void multiplicativeEnvelope(const EventVector& events, absl::Span envelope, F&& lambda) +{ + ASSERT(events.size() > 0); + ASSERT(events[0].delay == 0); + + if (envelope.size() == 0) + return; + const auto maxDelay = static_cast(envelope.size() - 1); + + auto lastValue = lambda(events[0].value); + auto lastDelay = events[0].delay; + for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) { + const auto length = min(events[i].delay, maxDelay) - lastDelay; + const auto nextValue = lambda(events[i].value); + const auto step = std::exp((std::log(nextValue) - std::log(lastValue)) / length); + multiplicativeRamp(envelope.subspan(lastDelay, length), lastValue, step); + lastValue = nextValue; + lastDelay += length; + } + fill(envelope.subspan(lastDelay), lastValue); +} + +template +void multiplicativeEnvelope(const EventVector& events, absl::Span envelope, F&& lambda, float step) +{ + ASSERT(events.size() > 0); + ASSERT(events[0].delay == 0); + ASSERT(step != 0.0f); + + if (envelope.size() == 0) + return; + const auto maxDelay = static_cast(envelope.size() - 1); + + const auto logStep = std::log(step); + // If we assume that a = b.q^r for b in (1, q) then + // log a log b + // ----- = ----- + r + // log q log q + // and log(b)\log(q) is between 0 and 1. + auto quantize = [logStep](float value) -> float { + return std::exp(logStep * std::round(std::log(value) / logStep)); + }; + + auto lastValue = quantize(lambda(events[0].value)); + auto lastDelay = events[0].delay; + for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) { + const auto length = min(events[i].delay, maxDelay) - lastDelay; + const auto nextValue = quantize(lambda(events[i].value)); + const auto difference = nextValue > lastValue ? nextValue / lastValue : lastValue / nextValue; + + if (difference < step) { + fill(envelope.subspan(lastDelay, length), lastValue); + lastValue = nextValue; + lastDelay += length; + continue; + } + + const auto numSteps = static_cast(std::log(difference) / logStep); + const auto stepLength = static_cast(length / numSteps); + for (int i = 0; i < numSteps; ++i) { + fill(envelope.subspan(lastDelay, stepLength), lastValue); + lastValue = nextValue > lastValue ? lastValue * step : lastValue / step; + lastDelay += stepLength; + } + } + fill(envelope.subspan(lastDelay), lastValue); +} + +template +void linearModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData, F&& lambda) +{ + const auto events = resources.midiState.getCCEvents(ccData.cc); + const auto curve = resources.curves.getCurve(ccData.data.curve); + if (ccData.data.steps == 0) { + linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }); + } else { + const float stepSize { ccData.data.value / ccData.data.steps }; + linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }, stepSize); + } +} + +template +void multiplicativeModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData, F&& lambda) +{ + const auto events = resources.midiState.getCCEvents(ccData.cc); + const auto curve = resources.curves.getCurve(ccData.data.curve); + if (ccData.data.steps == 0) { + multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }); + } else { + // FIXME: not sure about this step size for multiplicative envelopes + const float stepSize { ccData.data.value / ccData.data.steps }; + multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }, stepSize); + } +} + +inline void linearModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData) +{ + linearModifier(resources, span, ccData, [](float x) { return x; }); +} + +inline void multiplicativeModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData) +{ + multiplicativeModifier(resources, span, ccData, [](float x) { return x; }); +} + +} diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index f150728d..066c75e3 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -5,13 +5,12 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "Region.h" -#include "Defaults.h" #include "MathHelpers.h" #include "Macros.h" #include "Debug.h" #include "Opcode.h" #include "StringViewHelpers.h" -#include "MidiState.h" +#include "ModifierHelpers.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_cat.h" #include "absl/algorithm/container.h" diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 572d3eeb..fa952675 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -14,10 +14,8 @@ #include "Opcode.h" #include "AudioBuffer.h" #include "MidiState.h" -#include "absl/strings/str_cat.h" +#include "absl/types/optional.h" #include -#include -#include #include #include diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 838ac863..de19d2ed 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -264,200 +264,5 @@ bool findDefine(absl::string_view line, absl::string_view& variable, absl::strin */ bool findInclude(absl::string_view line, std::string& path); -/** - * @brief multiply a value by a factor, in cents. To be used for pitch variations. - * - * @param base - * @param modifier - */ -inline CXX14_CONSTEXPR float multiplyByCentsModifier(int modifier, float base) -{ - return base * centsFactor(modifier); -} - -template -inline CXX14_CONSTEXPR float gainModifier(T modifier, float value) -{ - return value * modifier; -} - -/** - * @brief Compute a crossfade in value with respect to a crossfade range (note, velocity, cc, ...) - */ -template -float crossfadeIn(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCurve curve) -{ - if (value < crossfadeRange.getStart()) - return 0.0f; - - const auto length = static_cast(crossfadeRange.length()); - if (length == 0.0f) - return 1.0f; - - else if (value < crossfadeRange.getEnd()) { - const auto crossfadePosition = static_cast(value - crossfadeRange.getStart()) / length; - if (curve == SfzCrossfadeCurve::power) - return sqrt(crossfadePosition); - if (curve == SfzCrossfadeCurve::gain) - return crossfadePosition; - } - - return 1.0f; -} - -/** - * @brief Compute a crossfade out value with respect to a crossfade range (note, velocity, cc, ...) - */ -template -float crossfadeOut(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCurve curve) -{ - if (value > crossfadeRange.getEnd()) - return 0.0f; - - const auto length = static_cast(crossfadeRange.length()); - if (length == 0.0f) - return 1.0f; - - else if (value > crossfadeRange.getStart()) { - const auto crossfadePosition = static_cast(value - crossfadeRange.getStart()) / length; - if (curve == SfzCrossfadeCurve::power) - return std::sqrt(1 - crossfadePosition); - if (curve == SfzCrossfadeCurve::gain) - return 1 - crossfadePosition; - } - - return 1.0f; -} - -template -void linearEnvelope(const EventVector& events, absl::Span envelope, F&& lambda) -{ - ASSERT(events.size() > 0); - ASSERT(events[0].delay == 0); - if (envelope.size() == 0) - return; - - const auto maxDelay = static_cast(envelope.size() - 1); - - auto lastValue = lambda(events[0].value); - auto lastDelay = events[0].delay; - for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) { - const auto length = min(events[i].delay, maxDelay) - lastDelay; - const auto step = (lambda(events[i].value) - lastValue) / length; - lastValue = linearRamp(envelope.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(envelope.subspan(lastDelay), lastValue); -} - -template -void linearEnvelope(const EventVector& events, absl::Span envelope, F&& lambda, float step) -{ - ASSERT(events.size() > 0); - ASSERT(events[0].delay == 0); - ASSERT(step != 0.0); - - if (envelope.size() == 0) - return; - - auto quantize = [step](float value) -> float { - return std::round(value / step) * step; - }; - const auto maxDelay = static_cast(envelope.size() - 1); - - auto lastValue = quantize(lambda(events[0].value)); - auto lastDelay = events[0].delay; - for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) { - const auto nextValue = quantize(lambda(events[i].value)); - const auto difference = std::abs(nextValue - lastValue); - const auto length = min(events[i].delay, maxDelay) - lastDelay; - - if (difference < step) { - fill(envelope.subspan(lastDelay, length), lastValue); - lastValue = nextValue; - lastDelay += length; - continue; - } - - const auto numSteps = static_cast(difference / step); - const auto stepLength = static_cast(length / numSteps); - for (int i = 0; i < numSteps; ++i) { - fill(envelope.subspan(lastDelay, stepLength), lastValue); - lastValue += lastValue <= nextValue ? step : -step; - lastDelay += stepLength; - } - } - fill(envelope.subspan(lastDelay), lastValue); -} - -template -void multiplicativeEnvelope(const EventVector& events, absl::Span envelope, F&& lambda) -{ - ASSERT(events.size() > 0); - ASSERT(events[0].delay == 0); - - if (envelope.size() == 0) - return; - const auto maxDelay = static_cast(envelope.size() - 1); - - auto lastValue = lambda(events[0].value); - auto lastDelay = events[0].delay; - for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) { - const auto length = min(events[i].delay, maxDelay) - lastDelay; - const auto nextValue = lambda(events[i].value); - const auto step = std::exp((std::log(nextValue) - std::log(lastValue)) / length); - multiplicativeRamp(envelope.subspan(lastDelay, length), lastValue, step); - lastValue = nextValue; - lastDelay += length; - } - fill(envelope.subspan(lastDelay), lastValue); -} - -template -void multiplicativeEnvelope(const EventVector& events, absl::Span envelope, F&& lambda, float step) -{ - ASSERT(events.size() > 0); - ASSERT(events[0].delay == 0); - ASSERT(step != 0.0f); - - if (envelope.size() == 0) - return; - const auto maxDelay = static_cast(envelope.size() - 1); - - const auto logStep = std::log(step); - // If we assume that a = b.q^r for b in (1, q) then - // log a log b - // ----- = ----- + r - // log q log q - // and log(b)\log(q) is between 0 and 1. - auto quantize = [logStep](float value) -> float { - return std::exp(logStep * std::round(std::log(value) / logStep)); - }; - - auto lastValue = quantize(lambda(events[0].value)); - auto lastDelay = events[0].delay; - for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) { - const auto length = min(events[i].delay, maxDelay) - lastDelay; - const auto nextValue = quantize(lambda(events[i].value)); - const auto difference = nextValue > lastValue ? nextValue / lastValue : lastValue / nextValue; - - if (difference < step) { - fill(envelope.subspan(lastDelay, length), lastValue); - lastValue = nextValue; - lastDelay += length; - continue; - } - - const auto numSteps = static_cast(std::log(difference) / logStep); - const auto stepLength = static_cast(length / numSteps); - for (int i = 0; i < numSteps; ++i) { - fill(envelope.subspan(lastDelay, stepLength), lastValue); - lastValue = nextValue > lastValue ? lastValue * step : lastValue / step; - lastDelay += stepLength; - } - } - fill(envelope.subspan(lastDelay), lastValue); -} - } // namespace sfz diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index ee3484d1..d7deb618 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -4,16 +4,14 @@ // 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 "Macros.h" #include "Voice.h" -#include "AudioSpan.h" -#include "Config.h" +#include "Macros.h" #include "Defaults.h" +#include "ModifierHelpers.h" #include "MathHelpers.h" #include "SIMDHelpers.h" #include "SfzHelpers.h" #include "absl/algorithm/container.h" -#include sfz::Voice::Voice(sfz::Resources& resources) : resources(resources) @@ -249,51 +247,6 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept #endif } -template -void linearModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData, F&& lambda) -{ - const auto events = resources.midiState.getCCEvents(ccData.cc); - const auto curve = resources.curves.getCurve(ccData.data.curve); - if (ccData.data.steps == 0) { - linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { - return lambda(curve.evalNormalized(x) * ccData.data.value); - }); - } else { - const float stepSize { ccData.data.value / ccData.data.steps }; - linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { - return lambda(curve.evalNormalized(x) * ccData.data.value); - }, stepSize); - } -} - -template -void multiplicativeModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData, F&& lambda) -{ - const auto events = resources.midiState.getCCEvents(ccData.cc); - const auto curve = resources.curves.getCurve(ccData.data.curve); - if (ccData.data.steps == 0) { - multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { - return lambda(curve.evalNormalized(x) * ccData.data.value); - }); - } else { - // FIXME: not sure about this step size for multiplicative envelopes - const float stepSize { ccData.data.value / ccData.data.steps }; - multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { - return lambda(curve.evalNormalized(x) * ccData.data.value); - }, stepSize); - } -} - -void linearModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData) -{ - linearModifier(resources, span, ccData, [](float x) { return x; }); -} - -void multiplicativeModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData) -{ - multiplicativeModifier(resources, span, ccData, [](float x) { return x; }); -} - void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept { const auto numSamples = modulationSpan.size(); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 1723c416..b4e61c5e 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -10,13 +10,10 @@ #include "HistoricalBuffer.h" #include "Region.h" #include "AudioBuffer.h" -#include "MidiState.h" -#include "Wavetables.h" #include "Resources.h" #include "AudioSpan.h" #include "LeakDetector.h" -#include -#include +#include "absl/types/span.h" #include #include diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index 545ea594..10954504 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -4,7 +4,7 @@ // license. You should have receive a LICENSE.md file along with the code. // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz -#include "sfizz/SfzHelpers.h" +#include "sfizz/ModifierHelpers.h" #include "sfizz/Buffer.h" #include "catch2/catch.hpp" #include From 5e6584a51a61b2613788fbed65f2d7e8e9b382a0 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 19:41:10 +0200 Subject: [PATCH 12/22] Basic parsing tests --- tests/RegionT.cpp | 108 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index b98f9f51..31e58fcd 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -471,6 +471,24 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "pan_oncc45", "4.2" }); REQUIRE(region.panCC.contains(45)); REQUIRE(region.panCC[45].value == 0.042_a); + region.parseOpcode({ "pan_curvecc17", "18" }); + REQUIRE(region.panCC[17].curve == 18); + region.parseOpcode({ "pan_curvecc17", "15482" }); + REQUIRE(region.panCC[17].curve == 255); + region.parseOpcode({ "pan_curvecc17", "-2" }); + REQUIRE(region.panCC[17].curve == 0); + region.parseOpcode({ "pan_smoothcc14", "85" }); + REQUIRE(region.panCC[14].smooth == 85); + region.parseOpcode({ "pan_smoothcc14", "15482" }); + REQUIRE(region.panCC[14].smooth == 127); + region.parseOpcode({ "pan_smoothcc14", "-2" }); + REQUIRE(region.panCC[14].smooth == 0); + region.parseOpcode({ "pan_stepcc120", "24" }); + REQUIRE(region.panCC[120].steps == 24); + region.parseOpcode({ "pan_stepcc120", "15482" }); + REQUIRE(region.panCC[120].steps == 127); + region.parseOpcode({ "pan_stepcc120", "-2" }); + REQUIRE(region.panCC[120].steps == 0); } SECTION("width") @@ -492,6 +510,24 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "width_oncc45", "4.2" }); REQUIRE(region.widthCC.contains(45)); REQUIRE(region.widthCC[45].value == 0.042_a); + region.parseOpcode({ "width_curvecc17", "18" }); + REQUIRE(region.widthCC[17].curve == 18); + region.parseOpcode({ "width_curvecc17", "15482" }); + REQUIRE(region.widthCC[17].curve == 255); + region.parseOpcode({ "width_curvecc17", "-2" }); + REQUIRE(region.widthCC[17].curve == 0); + region.parseOpcode({ "width_smoothcc14", "85" }); + REQUIRE(region.widthCC[14].smooth == 85); + region.parseOpcode({ "width_smoothcc14", "15482" }); + REQUIRE(region.widthCC[14].smooth == 127); + region.parseOpcode({ "width_smoothcc14", "-2" }); + REQUIRE(region.widthCC[14].smooth == 0); + region.parseOpcode({ "width_stepcc120", "24" }); + REQUIRE(region.widthCC[120].steps == 24); + region.parseOpcode({ "width_stepcc120", "15482" }); + REQUIRE(region.widthCC[120].steps == 127); + region.parseOpcode({ "width_stepcc120", "-2" }); + REQUIRE(region.widthCC[120].steps == 0); } SECTION("position") @@ -513,6 +549,24 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "position_oncc45", "4.2" }); REQUIRE(region.positionCC.contains(45)); REQUIRE(region.positionCC[45].value == 0.042_a); + region.parseOpcode({ "position_curvecc17", "18" }); + REQUIRE(region.positionCC[17].curve == 18); + region.parseOpcode({ "position_curvecc17", "15482" }); + REQUIRE(region.positionCC[17].curve == 255); + region.parseOpcode({ "position_curvecc17", "-2" }); + REQUIRE(region.positionCC[17].curve == 0); + region.parseOpcode({ "position_smoothcc14", "85" }); + REQUIRE(region.positionCC[14].smooth == 85); + region.parseOpcode({ "position_smoothcc14", "15482" }); + REQUIRE(region.positionCC[14].smooth == 127); + region.parseOpcode({ "position_smoothcc14", "-2" }); + REQUIRE(region.positionCC[14].smooth == 0); + region.parseOpcode({ "position_stepcc120", "24" }); + REQUIRE(region.positionCC[120].steps == 24); + region.parseOpcode({ "position_stepcc120", "15482" }); + REQUIRE(region.positionCC[120].steps == 127); + region.parseOpcode({ "position_stepcc120", "-2" }); + REQUIRE(region.positionCC[120].steps == 0); } SECTION("amp_keycenter") @@ -1452,6 +1506,24 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "amplitude_oncc2", "30" }); REQUIRE(region.amplitudeCC.contains(2)); REQUIRE(region.amplitudeCC[2].value == 0.30_a); + region.parseOpcode({ "amplitude_curvecc17", "18" }); + REQUIRE(region.amplitudeCC[17].curve == 18); + region.parseOpcode({ "amplitude_curvecc17", "15482" }); + REQUIRE(region.amplitudeCC[17].curve == 255); + region.parseOpcode({ "amplitude_curvecc17", "-2" }); + REQUIRE(region.amplitudeCC[17].curve == 0); + region.parseOpcode({ "amplitude_smoothcc14", "85" }); + REQUIRE(region.amplitudeCC[14].smooth == 85); + region.parseOpcode({ "amplitude_smoothcc14", "15482" }); + REQUIRE(region.amplitudeCC[14].smooth == 127); + region.parseOpcode({ "amplitude_smoothcc14", "-2" }); + REQUIRE(region.amplitudeCC[14].smooth == 0); + region.parseOpcode({ "amplitude_stepcc120", "24" }); + REQUIRE(region.amplitudeCC[120].steps == 24); + region.parseOpcode({ "amplitude_stepcc120", "15482" }); + REQUIRE(region.amplitudeCC[120].steps == 127); + region.parseOpcode({ "amplitude_stepcc120", "-2" }); + REQUIRE(region.amplitudeCC[120].steps == 0); } SECTION("volume_oncc/gain_cc") @@ -1466,6 +1538,24 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "gain_oncc4", "-1" }); REQUIRE(region.volumeCC.contains(4)); REQUIRE(region.volumeCC[4].value == -1.0_a); + region.parseOpcode({ "volume_curvecc17", "18" }); + REQUIRE(region.volumeCC[17].curve == 18); + region.parseOpcode({ "volume_curvecc17", "15482" }); + REQUIRE(region.volumeCC[17].curve == 255); + region.parseOpcode({ "volume_curvecc17", "-2" }); + REQUIRE(region.volumeCC[17].curve == 0); + region.parseOpcode({ "volume_smoothcc14", "85" }); + REQUIRE(region.volumeCC[14].smooth == 85); + region.parseOpcode({ "volume_smoothcc14", "15482" }); + REQUIRE(region.volumeCC[14].smooth == 127); + region.parseOpcode({ "volume_smoothcc14", "-2" }); + REQUIRE(region.volumeCC[14].smooth == 0); + region.parseOpcode({ "volume_stepcc120", "24" }); + REQUIRE(region.volumeCC[120].steps == 24); + region.parseOpcode({ "volume_stepcc120", "15482" }); + REQUIRE(region.volumeCC[120].steps == 127); + region.parseOpcode({ "volume_stepcc120", "-2" }); + REQUIRE(region.volumeCC[120].steps == 0); } SECTION("tune_cc/pitch_cc") @@ -1480,6 +1570,24 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "pitch_oncc4", "-1" }); REQUIRE(region.tuneCC.contains(4)); REQUIRE(region.tuneCC[4].value == -1.0); + region.parseOpcode({ "tune_curvecc17", "18" }); + REQUIRE(region.tuneCC[17].curve == 18); + region.parseOpcode({ "pitch_curvecc17", "15482" }); + REQUIRE(region.tuneCC[17].curve == 255); + region.parseOpcode({ "tune_curvecc17", "-2" }); + REQUIRE(region.tuneCC[17].curve == 0); + region.parseOpcode({ "pitch_smoothcc14", "85" }); + REQUIRE(region.tuneCC[14].smooth == 85); + region.parseOpcode({ "tune_smoothcc14", "15482" }); + REQUIRE(region.tuneCC[14].smooth == 127); + region.parseOpcode({ "pitch_smoothcc14", "-2" }); + REQUIRE(region.tuneCC[14].smooth == 0); + region.parseOpcode({ "tune_stepcc120", "24" }); + REQUIRE(region.tuneCC[120].steps == 24); + region.parseOpcode({ "pitch_stepcc120", "15482" }); + REQUIRE(region.tuneCC[120].steps == 127); + region.parseOpcode({ "tune_stepcc120", "-2" }); + REQUIRE(region.tuneCC[120].steps == 0); } } From e975877569b9768e392d58b99cca330822e14fd6 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 6 Apr 2020 20:12:59 +0200 Subject: [PATCH 13/22] - Add some expected behavior tests- Correct the evalNormalized in curves- Remove the no-lambda multiplicative modifiers... it's a trap that makes you create nans and infs --- src/sfizz/Curve.h | 2 +- src/sfizz/ModifierHelpers.h | 8 +-- tests/CurveT.cpp | 8 +-- tests/EventEnvelopesT.cpp | 101 ++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 12 deletions(-) diff --git a/src/sfizz/Curve.h b/src/sfizz/Curve.h index ad9667a3..829a2d8b 100644 --- a/src/sfizz/Curve.h +++ b/src/sfizz/Curve.h @@ -45,7 +45,7 @@ public: */ float evalNormalized(float value) const { - return evalCC7(denormalize7Bits(value)); + return evalCC7(value * 127.0f); } /** diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h index 5dd3fd78..02502fcc 100644 --- a/src/sfizz/ModifierHelpers.h +++ b/src/sfizz/ModifierHelpers.h @@ -214,7 +214,7 @@ void multiplicativeModifier(const sfz::Resources& resources, absl::Span s }); } else { // FIXME: not sure about this step size for multiplicative envelopes - const float stepSize { ccData.data.value / ccData.data.steps }; + const float stepSize { lambda(ccData.data.value / ccData.data.steps) }; multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); }, stepSize); @@ -225,10 +225,4 @@ inline void linearModifier(const sfz::Resources& resources, absl::Span sp { linearModifier(resources, span, ccData, [](float x) { return x; }); } - -inline void multiplicativeModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData) -{ - multiplicativeModifier(resources, span, ccData, [](float x) { return x; }); -} - } diff --git a/tests/CurveT.cpp b/tests/CurveT.cpp index 1aa9e161..4c3c9f9d 100644 --- a/tests/CurveT.cpp +++ b/tests/CurveT.cpp @@ -33,7 +33,7 @@ TEST_CASE("[Curve] Bipolar -1 to 1") REQUIRE( curve.evalCC7(85) == Approx(0.3386).margin(1e-3) ); REQUIRE( curve.evalNormalized(0.0f) == -1.0f ); REQUIRE( curve.evalNormalized(1.0f) == 1.0f ); - REQUIRE( curve.evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) ); + REQUIRE( curve.evalNormalized(0.3f) == Approx(-0.4).margin(1e-3) ); } TEST_CASE("[Curve] Bipolar 1 to 0") @@ -59,7 +59,7 @@ TEST_CASE("[Curve] Bipolar 1 to -1") REQUIRE( curve.evalCC7(85) == Approx(-0.3386).margin(1e-3) ); REQUIRE( curve.evalNormalized(0.0f) == 1.0f ); REQUIRE( curve.evalNormalized(1.0f) == -1.0f ); - REQUIRE( curve.evalNormalized(0.3f) == Approx(0.402).margin(1e-3) ); + REQUIRE( curve.evalNormalized(0.3f) == Approx(0.4).margin(1e-3) ); } TEST_CASE("[Curve] x**2") @@ -220,7 +220,7 @@ TEST_CASE("[Curve] Default CurveSet") REQUIRE( curveSet.getCurve(1).evalNormalized(0.0f) == -1.0f ); REQUIRE( curveSet.getCurve(1).evalNormalized(1.0f) == 1.0f ); - REQUIRE( curveSet.getCurve(1).evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) ); + REQUIRE( curveSet.getCurve(1).evalNormalized(0.3f) == Approx(-0.4).margin(1e-3) ); REQUIRE( curveSet.getCurve(2).evalNormalized(0.0f) == 1.0f ); REQUIRE( curveSet.getCurve(2).evalNormalized(1.0f) == 0.0f ); @@ -228,7 +228,7 @@ TEST_CASE("[Curve] Default CurveSet") REQUIRE( curveSet.getCurve(3).evalNormalized(0.0f) == 1.0f ); REQUIRE( curveSet.getCurve(3).evalNormalized(1.0f) == -1.0f ); - REQUIRE( curveSet.getCurve(3).evalNormalized(0.3f) == Approx(0.402).margin(1e-3) ); + REQUIRE( curveSet.getCurve(3).evalNormalized(0.3f) == Approx(0.4).margin(1e-3) ); REQUIRE( curveSet.getCurve(4).evalNormalized(0.0f) == 0.0f ); REQUIRE( curveSet.getCurve(4).evalNormalized(1.0f) == 1.0f ); diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index 10954504..71df4a79 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -273,3 +273,104 @@ TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events") multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f); REQUIRE(output == expected); } + +TEST_CASE("[linearModifiers] Compare with envelopes") +{ + sfz::Resources resources; + resources.curves = sfz::CurveSet::createPredefined(); + + sfz::CCData ccData; + ccData.cc = 20; + ccData.data.value = 100.0f; + + resources.midiState.ccEvent(5, 20, 0.1); + resources.midiState.ccEvent(10, 20, 0.2); + + std::array output; + std::array envelope; + + linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { + return ccData.data.value * x; + }); + linearModifier(resources, absl::MakeSpan(output), ccData); + REQUIRE(approxEqual(output, envelope)); + + ccData.data.curve = 1; + linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { + return ccData.data.value * (2 * x - 1); + }); + linearModifier(resources, absl::MakeSpan(output), ccData); + REQUIRE(approxEqual(output, envelope)); + + ccData.data.curve = 3; + ccData.data.value = 10.0f; + linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { + return ccData.data.value * (1 - 2 * x); + }); + linearModifier(resources, absl::MakeSpan(output), ccData); + REQUIRE(approxEqual(output, envelope)); + + ccData.data.curve = 2; + ccData.data.value = 20.0f; + ccData.data.steps = 10; + linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { + return ccData.data.value * (1 - x); + }, ccData.data.value / ccData.data.steps); + linearModifier(resources, absl::MakeSpan(output), ccData); + REQUIRE(approxEqual(output, envelope)); +} + +TEST_CASE("[multiplicativeModifiers] Compare with envelopes") +{ + sfz::Resources resources; + resources.curves = sfz::CurveSet::createPredefined(); + + sfz::CCData ccData; + ccData.cc = 20; + ccData.data.value = 100.0f; + + resources.midiState.ccEvent(5, 20, 0.1); + resources.midiState.ccEvent(10, 20, 0.2); + + std::array output; + std::array envelope; + + multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { + return db2mag(ccData.data.value * x); + }); + multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) { + return db2mag(x); + }); + REQUIRE(approxEqual(output, envelope)); + + ccData.data.curve = 1; + multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { + return db2mag(ccData.data.value * (2 * x - 1)); + }); + multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) { + return db2mag(x); + }); + REQUIRE(approxEqual(output, envelope)); + + ccData.data.curve = 3; + ccData.data.value = 10.0f; + multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { + return db2mag(ccData.data.value * (1 - 2 * x)); + }); + multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) { + return db2mag(x); + }); + REQUIRE(approxEqual(output, envelope)); + + ccData.data.curve = 2; + ccData.data.value = 20.0f; + ccData.data.steps = 10; + multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { + return db2mag(ccData.data.value * (1 - x)); + }, db2mag(ccData.data.value / ccData.data.steps) ); + multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) { + return db2mag(x); + }); + REQUIRE(approxEqual(output, envelope)); +} + From b9ba7fab0b50d3053da88b9d25a857d7bc8d7a0b Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 7 Apr 2020 00:21:47 +0200 Subject: [PATCH 14/22] Match sforzando's behavior (use floor on steps) --- src/sfizz/ModifierHelpers.h | 12 ++++++------ tests/EventEnvelopesT.cpp | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h index 02502fcc..6aa757fe 100644 --- a/src/sfizz/ModifierHelpers.h +++ b/src/sfizz/ModifierHelpers.h @@ -88,7 +88,7 @@ void linearEnvelope(const EventVector& events, absl::Span envelope, F&& l return; auto quantize = [step](float value) -> float { - return std::round(value / step) * step; + return std::floor(value / step) * step; }; const auto maxDelay = static_cast(envelope.size() - 1); @@ -158,7 +158,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop // log q log q // and log(b)\log(q) is between 0 and 1. auto quantize = [logStep](float value) -> float { - return std::exp(logStep * std::round(std::log(value) / logStep)); + return std::exp(logStep * std::floor(std::log(value) / logStep)); }; auto lastValue = quantize(lambda(events[0].value)); @@ -191,12 +191,12 @@ void linearModifier(const sfz::Resources& resources, absl::Span span, con { const auto events = resources.midiState.getCCEvents(ccData.cc); const auto curve = resources.curves.getCurve(ccData.data.curve); - if (ccData.data.steps == 0) { + if (ccData.data.steps < 2) { linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); }); } else { - const float stepSize { ccData.data.value / ccData.data.steps }; + const float stepSize { ccData.data.value / (ccData.data.steps - 1) }; linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); }, stepSize); @@ -208,13 +208,13 @@ void multiplicativeModifier(const sfz::Resources& resources, absl::Span s { const auto events = resources.midiState.getCCEvents(ccData.cc); const auto curve = resources.curves.getCurve(ccData.data.curve); - if (ccData.data.steps == 0) { + if (ccData.data.steps < 2) { multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); }); } else { // FIXME: not sure about this step size for multiplicative envelopes - const float stepSize { lambda(ccData.data.value / ccData.data.steps) }; + const float stepSize { lambda(ccData.data.value / (ccData.data.steps - 1)) }; multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); }, stepSize); diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index 71df4a79..634d98d6 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -137,7 +137,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets") { 6, 1.9f } }; std::array output; - std::array expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f }; + std::array expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f); REQUIRE(output == expected); } @@ -315,7 +315,7 @@ TEST_CASE("[linearModifiers] Compare with envelopes") ccData.data.steps = 10; linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { return ccData.data.value * (1 - x); - }, ccData.data.value / ccData.data.steps); + }, ccData.data.value / (ccData.data.steps - 1)); linearModifier(resources, absl::MakeSpan(output), ccData); REQUIRE(approxEqual(output, envelope)); } @@ -367,7 +367,7 @@ TEST_CASE("[multiplicativeModifiers] Compare with envelopes") ccData.data.steps = 10; multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { return db2mag(ccData.data.value * (1 - x)); - }, db2mag(ccData.data.value / ccData.data.steps) ); + }, db2mag(ccData.data.value / (ccData.data.steps - 1)) ); multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) { return db2mag(x); }); From 6bce14673e6bb6439f10e688360acdbf473c9e73 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 7 Apr 2020 00:42:11 +0200 Subject: [PATCH 15/22] Saner behavior for the quantized envelopes with bends --- src/sfizz/ModifierHelpers.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h index 6aa757fe..0ee1e900 100644 --- a/src/sfizz/ModifierHelpers.h +++ b/src/sfizz/ModifierHelpers.h @@ -158,7 +158,10 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop // log q log q // and log(b)\log(q) is between 0 and 1. auto quantize = [logStep](float value) -> float { - return std::exp(logStep * std::floor(std::log(value) / logStep)); + if (value > 1) + return std::exp(logStep * std::floor(std::log(value) / logStep)); + else + return std::exp(logStep * std::ceil(std::log(value) / logStep)); }; auto lastValue = quantize(lambda(events[0].value)); From 872e7c68e05d781d6a1389e214fcfd9fca85e552 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 7 Apr 2020 00:50:30 +0200 Subject: [PATCH 16/22] Use round rather than trunc; more stable.. --- src/sfizz/ModifierHelpers.h | 5 +---- tests/EventEnvelopesT.cpp | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h index 0ee1e900..1a5d5aba 100644 --- a/src/sfizz/ModifierHelpers.h +++ b/src/sfizz/ModifierHelpers.h @@ -5,7 +5,6 @@ #include "SfzHelpers.h" #include "Resources.h" #include "absl/types/span.h" - namespace sfz { /** @@ -150,7 +149,6 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop if (envelope.size() == 0) return; const auto maxDelay = static_cast(envelope.size() - 1); - const auto logStep = std::log(step); // If we assume that a = b.q^r for b in (1, q) then // log a log b @@ -178,7 +176,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop continue; } - const auto numSteps = static_cast(std::log(difference) / logStep); + const auto numSteps = std::round(std::log(difference) / logStep); const auto stepLength = static_cast(length / numSteps); for (int i = 0; i < numSteps; ++i) { fill(envelope.subspan(lastDelay, stepLength), lastValue); @@ -216,7 +214,6 @@ void multiplicativeModifier(const sfz::Resources& resources, absl::Span s return lambda(curve.evalNormalized(x) * ccData.data.value); }); } else { - // FIXME: not sure about this step size for multiplicative envelopes const float stepSize { lambda(ccData.data.value / (ccData.data.steps - 1)) }; multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index 634d98d6..a2dc6b30 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -330,7 +330,7 @@ TEST_CASE("[multiplicativeModifiers] Compare with envelopes") ccData.data.value = 100.0f; resources.midiState.ccEvent(5, 20, 0.1); - resources.midiState.ccEvent(10, 20, 0.2); + resources.midiState.ccEvent(10, 20, 0.8); std::array output; std::array envelope; From e03f1a8038fde6d4f0ee0894b0562e400abe6ab1 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 7 Apr 2020 00:50:38 +0200 Subject: [PATCH 17/22] Repair benchmark --- benchmarks/BM_envelopes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/BM_envelopes.cpp b/benchmarks/BM_envelopes.cpp index 836d6b0a..1fa3ae91 100644 --- a/benchmarks/BM_envelopes.cpp +++ b/benchmarks/BM_envelopes.cpp @@ -10,7 +10,7 @@ #include #include #include -#include "SfzHelpers.h" +#include "ModifierHelpers.h" #include "absl/types/span.h" class EnvelopeFixture : public benchmark::Fixture { From b3dd8b3ce91ae8158a5b45cde8117aae3043dcb4 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Mon, 13 Apr 2020 23:33:57 +0200 Subject: [PATCH 18/22] Use trunc in modifiers --- src/sfizz/ModifierHelpers.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h index 1a5d5aba..4aaffa75 100644 --- a/src/sfizz/ModifierHelpers.h +++ b/src/sfizz/ModifierHelpers.h @@ -87,7 +87,7 @@ void linearEnvelope(const EventVector& events, absl::Span envelope, F&& l return; auto quantize = [step](float value) -> float { - return std::floor(value / step) * step; + return std::trunc(value / step) * step; }; const auto maxDelay = static_cast(envelope.size() - 1); @@ -156,10 +156,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop // log q log q // and log(b)\log(q) is between 0 and 1. auto quantize = [logStep](float value) -> float { - if (value > 1) - return std::exp(logStep * std::floor(std::log(value) / logStep)); - else - return std::exp(logStep * std::ceil(std::log(value) / logStep)); + return std::exp(logStep * std::trunc(std::log(value) / logStep)); }; auto lastValue = quantize(lambda(events[0].value)); From 6a7977b6c253ce4abc01b021b7a3c7213cc29831 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 14 Apr 2020 13:41:24 +0200 Subject: [PATCH 19/22] Stepcc actually gives the step and not the number of steps... --- src/sfizz/Defaults.h | 5 +++++ src/sfizz/ModifierHelpers.h | 8 ++++---- src/sfizz/Region.cpp | 24 +++++++++++------------ src/sfizz/SfzHelpers.h | 2 +- tests/EventEnvelopesT.cpp | 8 ++++---- tests/RegionT.cpp | 38 ++++++++++++++++++------------------- 6 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 822e9cd3..8506d90f 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -110,17 +110,21 @@ namespace Default constexpr float volume { 0.0f }; constexpr Range volumeRange { -144.0, 6.0 }; constexpr Range volumeCCRange { -144.0, 48.0 }; + constexpr Range volumeStepRange { 0, 48.0 }; constexpr float amplitude { 100.0 }; constexpr Range amplitudeRange { 0.0, 100.0 }; constexpr float pan { 0.0 }; constexpr Range panRange { -100.0, 100.0 }; constexpr Range panCCRange { -200.0, 200.0 }; + constexpr Range panStepRange { 0.0, 200.0 }; constexpr float position { 0.0 }; constexpr Range positionRange { -100.0, 100.0 }; constexpr Range positionCCRange { -200.0, 200.0 }; + constexpr Range positionStepRange { 0.0, 200.0 }; constexpr float width { 100.0 }; constexpr Range widthRange { -100.0, 100.0 }; constexpr Range widthCCRange { -200.0, 200.0 }; + constexpr Range widthStepRange { 0.0, 200.0 }; constexpr uint8_t ampKeycenter { 60 }; constexpr float ampKeytrack { 0.0 }; constexpr Range ampKeytrackRange { -96, 12 }; @@ -196,6 +200,7 @@ namespace Default constexpr int tune { 0 }; constexpr Range tuneRange { -9600, 9600 }; // ±100 in SFZv1, more in ARIA constexpr Range tuneCCRange { -9600, 9600 }; + constexpr Range tuneStepRange { 0, 9600 }; constexpr Range bendBoundRange { -9600, 9600 }; constexpr Range bendStepRange { 1, 1200 }; constexpr int bendUp { 200 }; // No range here because the bounds can be inverted diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h index 4aaffa75..b46bbc61 100644 --- a/src/sfizz/ModifierHelpers.h +++ b/src/sfizz/ModifierHelpers.h @@ -189,12 +189,12 @@ void linearModifier(const sfz::Resources& resources, absl::Span span, con { const auto events = resources.midiState.getCCEvents(ccData.cc); const auto curve = resources.curves.getCurve(ccData.data.curve); - if (ccData.data.steps < 2) { + if (ccData.data.step == 0.0f) { linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); }); } else { - const float stepSize { ccData.data.value / (ccData.data.steps - 1) }; + const float stepSize { lambda(ccData.data.step) }; linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); }, stepSize); @@ -206,12 +206,12 @@ void multiplicativeModifier(const sfz::Resources& resources, absl::Span s { const auto events = resources.midiState.getCCEvents(ccData.cc); const auto curve = resources.curves.getCurve(ccData.data.curve); - if (ccData.data.steps < 2) { + if (ccData.data.step == 0.0f) { multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); }); } else { - const float stepSize { lambda(ccData.data.value / (ccData.data.steps - 1)) }; + const float stepSize { lambda(ccData.data.step) }; multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { return lambda(curve.evalNormalized(x) * ccData.data.value); }, stepSize); diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 066c75e3..3411e915 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -305,8 +305,8 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("volume_stepcc&"): if (opcode.parameters.back() > config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::stepCCRange)) - volumeCC[opcode.parameters.back()].steps = *value; + if (auto value = readOpcode(opcode.value, Default::volumeStepRange)) + volumeCC[opcode.parameters.back()].step = *value; break; case hash("volume_smoothcc&"): if (opcode.parameters.back() > config::numCCs) @@ -335,8 +335,8 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("amplitude_stepcc&"): if (opcode.parameters.back() > config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::stepCCRange)) - amplitudeCC[opcode.parameters.back()].steps = *value; + if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) + amplitudeCC[opcode.parameters.back()].step = normalizePercents(*value); break; case hash("amplitude_smoothcc&"): if (opcode.parameters.back() > config::numCCs) @@ -364,8 +364,8 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("pan_stepcc&"): if (opcode.parameters.back() > config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::stepCCRange)) - panCC[opcode.parameters.back()].steps = *value; + if (auto value = readOpcode(opcode.value, Default::panStepRange)) + panCC[opcode.parameters.back()].step = normalizePercents(*value); break; case hash("pan_smoothcc&"): if (opcode.parameters.back() > config::numCCs) @@ -393,8 +393,8 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("position_stepcc&"): if (opcode.parameters.back() > config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::stepCCRange)) - positionCC[opcode.parameters.back()].steps = *value; + if (auto value = readOpcode(opcode.value, Default::positionStepRange)) + positionCC[opcode.parameters.back()].step = normalizePercents(*value); break; case hash("position_smoothcc&"): if (opcode.parameters.back() > config::numCCs) @@ -422,8 +422,8 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("width_stepcc&"): if (opcode.parameters.back() > config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::stepCCRange)) - widthCC[opcode.parameters.back()].steps = *value; + if (auto value = readOpcode(opcode.value, Default::widthStepRange)) + widthCC[opcode.parameters.back()].step = normalizePercents(*value); break; case hash("width_smoothcc&"): if (opcode.parameters.back() > config::numCCs) @@ -827,8 +827,8 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("tune_stepcc&"): if (opcode.parameters.back() > config::numCCs) return false; - if (auto value = readOpcode(opcode.value, Default::stepCCRange)) - tuneCC[opcode.parameters.back()].steps = *value; + if (auto value = readOpcode(opcode.value, Default::tuneStepRange)) + tuneCC[opcode.parameters.back()].step = *value; break; case hash("pitch_smoothcc&"): // fallthrough case hash("tune_smoothcc&"): diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index de19d2ed..60979282 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -32,8 +32,8 @@ struct CCData { struct Modifier { float value { 0.0f }; + float step { 0.0f }; uint8_t curve { 0 }; - uint8_t steps { 0 }; uint8_t smooth { 0 }; static_assert(config::maxCurves - 1 <= std::numeric_limits::max(), "The curve type in the Modifier struct cannot support the required number of curves"); }; diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index a2dc6b30..551cc807 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -312,10 +312,10 @@ TEST_CASE("[linearModifiers] Compare with envelopes") ccData.data.curve = 2; ccData.data.value = 20.0f; - ccData.data.steps = 10; + ccData.data.step = 2.0f; linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { return ccData.data.value * (1 - x); - }, ccData.data.value / (ccData.data.steps - 1)); + }, ccData.data.step); linearModifier(resources, absl::MakeSpan(output), ccData); REQUIRE(approxEqual(output, envelope)); } @@ -364,10 +364,10 @@ TEST_CASE("[multiplicativeModifiers] Compare with envelopes") ccData.data.curve = 2; ccData.data.value = 20.0f; - ccData.data.steps = 10; + ccData.data.step = 2.0f; multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { return db2mag(ccData.data.value * (1 - x)); - }, db2mag(ccData.data.value / (ccData.data.steps - 1)) ); + }, db2mag(ccData.data.step) ); multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) { return db2mag(x); }); diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 31e58fcd..f982c424 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -484,11 +484,11 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "pan_smoothcc14", "-2" }); REQUIRE(region.panCC[14].smooth == 0); region.parseOpcode({ "pan_stepcc120", "24" }); - REQUIRE(region.panCC[120].steps == 24); + REQUIRE(region.panCC[120].step == 0.24_a); region.parseOpcode({ "pan_stepcc120", "15482" }); - REQUIRE(region.panCC[120].steps == 127); + REQUIRE(region.panCC[120].step == 2.0_a); region.parseOpcode({ "pan_stepcc120", "-2" }); - REQUIRE(region.panCC[120].steps == 0); + REQUIRE(region.panCC[120].step == 0.0f); } SECTION("width") @@ -523,11 +523,11 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "width_smoothcc14", "-2" }); REQUIRE(region.widthCC[14].smooth == 0); region.parseOpcode({ "width_stepcc120", "24" }); - REQUIRE(region.widthCC[120].steps == 24); + REQUIRE(region.widthCC[120].step == 0.24_a); region.parseOpcode({ "width_stepcc120", "15482" }); - REQUIRE(region.widthCC[120].steps == 127); - region.parseOpcode({ "width_stepcc120", "-2" }); - REQUIRE(region.widthCC[120].steps == 0); + REQUIRE(region.widthCC[120].step == 2.0_a); + region.parseOpcode({ "width_stepcc120", "-20" }); + REQUIRE(region.widthCC[120].step == 0.0f); } SECTION("position") @@ -562,11 +562,11 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "position_smoothcc14", "-2" }); REQUIRE(region.positionCC[14].smooth == 0); region.parseOpcode({ "position_stepcc120", "24" }); - REQUIRE(region.positionCC[120].steps == 24); + REQUIRE(region.positionCC[120].step == 0.24_a); region.parseOpcode({ "position_stepcc120", "15482" }); - REQUIRE(region.positionCC[120].steps == 127); + REQUIRE(region.positionCC[120].step == 2.0_a); region.parseOpcode({ "position_stepcc120", "-2" }); - REQUIRE(region.positionCC[120].steps == 0); + REQUIRE(region.positionCC[120].step == 0.0f); } SECTION("amp_keycenter") @@ -1519,11 +1519,11 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "amplitude_smoothcc14", "-2" }); REQUIRE(region.amplitudeCC[14].smooth == 0); region.parseOpcode({ "amplitude_stepcc120", "24" }); - REQUIRE(region.amplitudeCC[120].steps == 24); + REQUIRE(region.amplitudeCC[120].step == 0.24_a); region.parseOpcode({ "amplitude_stepcc120", "15482" }); - REQUIRE(region.amplitudeCC[120].steps == 127); + REQUIRE(region.amplitudeCC[120].step == 1.0_a); region.parseOpcode({ "amplitude_stepcc120", "-2" }); - REQUIRE(region.amplitudeCC[120].steps == 0); + REQUIRE(region.amplitudeCC[120].step == 0.0f); } SECTION("volume_oncc/gain_cc") @@ -1551,11 +1551,11 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "volume_smoothcc14", "-2" }); REQUIRE(region.volumeCC[14].smooth == 0); region.parseOpcode({ "volume_stepcc120", "24" }); - REQUIRE(region.volumeCC[120].steps == 24); + REQUIRE(region.volumeCC[120].step == 24.0f); region.parseOpcode({ "volume_stepcc120", "15482" }); - REQUIRE(region.volumeCC[120].steps == 127); + REQUIRE(region.volumeCC[120].step == 48.0f); region.parseOpcode({ "volume_stepcc120", "-2" }); - REQUIRE(region.volumeCC[120].steps == 0); + REQUIRE(region.volumeCC[120].step == 0.0f); } SECTION("tune_cc/pitch_cc") @@ -1583,11 +1583,11 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "pitch_smoothcc14", "-2" }); REQUIRE(region.tuneCC[14].smooth == 0); region.parseOpcode({ "tune_stepcc120", "24" }); - REQUIRE(region.tuneCC[120].steps == 24); + REQUIRE(region.tuneCC[120].step == 24.0f); region.parseOpcode({ "pitch_stepcc120", "15482" }); - REQUIRE(region.tuneCC[120].steps == 127); + REQUIRE(region.tuneCC[120].step == 9600.0f); region.parseOpcode({ "tune_stepcc120", "-2" }); - REQUIRE(region.tuneCC[120].steps == 0); + REQUIRE(region.tuneCC[120].step == 0.0f); } } From aeba9dfaabece616bb04c910893b3385b94f3f4e Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 14 Apr 2020 13:52:18 +0200 Subject: [PATCH 20/22] Dispatch between "normal"multiplicativ eenvelopes and the pitch bends --- src/sfizz/Macros.h | 7 ++++++ src/sfizz/ModifierHelpers.h | 43 ++++++++++++++++++++++++++++--------- src/sfizz/Voice.cpp | 8 +++---- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/sfizz/Macros.h b/src/sfizz/Macros.h index 3769b34d..024dfe24 100644 --- a/src/sfizz/Macros.h +++ b/src/sfizz/Macros.h @@ -16,3 +16,10 @@ #define CXX11_MOVE(x) std::move(x) #endif +#if __cplusplus >= 201703L +#define IF_CONSTEXPR if constexpr +#else +#define IF_CONSTEXPR if +#endif + + diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h index b46bbc61..a476f6ae 100644 --- a/src/sfizz/ModifierHelpers.h +++ b/src/sfizz/ModifierHelpers.h @@ -139,7 +139,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop fill(envelope.subspan(lastDelay), lastValue); } -template +template void multiplicativeEnvelope(const EventVector& events, absl::Span envelope, F&& lambda, float step) { ASSERT(events.size() > 0); @@ -156,7 +156,14 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop // log q log q // and log(b)\log(q) is between 0 and 1. auto quantize = [logStep](float value) -> float { - return std::exp(logStep * std::trunc(std::log(value) / logStep)); + IF_CONSTEXPR(Round) + { + return std::exp(logStep * std::round(std::log(value) / logStep)); + } + else + { + return std::exp(logStep * std::trunc(std::log(value) / logStep)); + } }; auto lastValue = quantize(lambda(events[0].value)); @@ -184,7 +191,19 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop fill(envelope.subspan(lastDelay), lastValue); } -template +template +void pitchBendEnvelope(const EventVector& events, absl::Span envelope, F&& lambda, float step) +{ + multiplicativeEnvelope(events, envelope, std::forward(lambda), step); +} + +template +void pitchBendEnvelope(const EventVector& events, absl::Span envelope, F&& lambda) +{ + multiplicativeEnvelope(events, envelope, std::forward(lambda)); +} + +template void linearModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData, F&& lambda) { const auto events = resources.midiState.getCCEvents(ccData.cc); @@ -195,13 +214,15 @@ void linearModifier(const sfz::Resources& resources, absl::Span span, con }); } else { const float stepSize { lambda(ccData.data.step) }; - linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) { - return lambda(curve.evalNormalized(x) * ccData.data.value); - }, stepSize); + linearEnvelope( + events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }, + stepSize); } } -template +template void multiplicativeModifier(const sfz::Resources& resources, absl::Span span, const sfz::CCData& ccData, F&& lambda) { const auto events = resources.midiState.getCCEvents(ccData.cc); @@ -212,9 +233,11 @@ void multiplicativeModifier(const sfz::Resources& resources, absl::Span s }); } else { const float stepSize { lambda(ccData.data.step) }; - multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) { - return lambda(curve.evalNormalized(x) * ccData.data.value); - }, stepSize); + multiplicativeEnvelope( + events, span, [&ccData, &curve, &lambda](float x) { + return lambda(curve.evalNormalized(x) * ccData.data.value); + }, + stepSize); } } diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index d7deb618..cfa8bfbe 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -446,9 +446,9 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept }; if (region->bendStep > 1) - multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor); + pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor); else - multiplicativeEnvelope(events, *bends, bendLambda); + pitchBendEnvelope(events, *bends, bendLambda); applyGain(*bends, *jumps); for (const auto& mod : region->tuneCC) { @@ -549,9 +549,9 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept return centsFactor(bendInCents); }; if (region->bendStep > 1) - multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor); + pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor); else - multiplicativeEnvelope(events, *bends, bendLambda); + pitchBendEnvelope(events, *bends, bendLambda); applyGain(*bends, *frequencies); for (const auto& mod : region->tuneCC) { From d120c2bc9a5ecac373ef61ca72339936f894d75e Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 14 Apr 2020 17:59:12 +0200 Subject: [PATCH 21/22] Use rint, rounding modes, and corrected tests --- src/sfizz/MathHelpers.h | 92 ++++++++++++++++++++++++------------- src/sfizz/ModifierHelpers.h | 14 ++---- tests/EventEnvelopesT.cpp | 44 +++++++++--------- 3 files changed, 87 insertions(+), 63 deletions(-) diff --git a/src/sfizz/MathHelpers.h b/src/sfizz/MathHelpers.h index 9fa734f1..a70446ca 100644 --- a/src/sfizz/MathHelpers.h +++ b/src/sfizz/MathHelpers.h @@ -15,26 +15,27 @@ #include #include #include +#include -template +template constexpr T max(T op1, T op2) { return op1 > op2 ? op1 : op2; } -template +template constexpr T max(T op1, Args... rest) { return max(op1, max(rest...)); } -template +template constexpr T min(T op1, T op2) { return op1 > op2 ? op2 : op1; } -template +template constexpr T min(T op1, Args... rest) { return min(op1, min(rest...)); @@ -46,7 +47,7 @@ constexpr T min(T op1, Args... rest) * @param op * @return T */ -template +template constexpr T power2(T in) { return in * in; @@ -111,8 +112,8 @@ constexpr Type mag2db(Type in) * */ namespace Random { - static std::random_device randomDevice; - static std::minstd_rand randomGenerator { randomDevice() }; +static std::random_device randomDevice; +static std::minstd_rand randomGenerator { randomDevice() }; } // namespace Random /** @@ -135,42 +136,42 @@ inline float midiNoteFrequency(const int noteNumber) * @param hi * @return T */ -template -constexpr T clamp( T v, T lo, T hi ) +template +constexpr T clamp(T v, T lo, T hi) { return max(min(v, hi), lo); } -template +template inline CXX14_CONSTEXPR void incrementAll(T& only) { only += Increment; } -template +template inline CXX14_CONSTEXPR void incrementAll(T& first, Args&... rest) { first += Increment; incrementAll(rest...); } -template +template constexpr ValueType linearInterpolation(ValueType left, ValueType right, ValueType leftCoeff, ValueType rightCoeff) { return left * leftCoeff + right * rightCoeff; } -template +template constexpr Type pi() { return static_cast(3.141592653589793238462643383279502884); }; -template +template constexpr Type twoPi() { return pi() * 2; }; -template +template constexpr Type piTwo() { return pi() / 2; }; -template +template constexpr Type piFour() { return pi() / 4; }; -template +template constexpr Type sqrtTwo() { return static_cast(1.414213562373095048801688724209698078569671875376948073176); }; -template +template constexpr Type sqrtTwoInv() { return static_cast(0.707106781186547524400844362104849039284835937688474036588); }; /** @@ -205,23 +206,23 @@ inline Fraction::operator float() const noexcept template struct FP_traits; -template <> struct FP_traits -{ +template <> +struct FP_traits { typedef double type; typedef uint64_t same_size_int; static_assert(sizeof(type) == sizeof(same_size_int), - "Unexpected size of floating point type"); + "Unexpected size of floating point type"); static constexpr int e_bits = 11; static constexpr int m_bits = 52; static constexpr int e_offset = -1023; }; -template <> struct FP_traits -{ +template <> +struct FP_traits { typedef float type; typedef uint32_t same_size_int; static_assert(sizeof(type) == sizeof(same_size_int), - "Unexpected size of floating point type"); + "Unexpected size of floating point type"); static constexpr int e_bits = 8; static constexpr int m_bits = 23; static constexpr int e_offset = -127; @@ -237,7 +238,10 @@ template inline bool fp_sign(F x) { typedef FP_traits T; - union { F real; typename T::same_size_int integer; } u; + union { + F real; + typename T::same_size_int integer; + } u; u.real = x; return ((u.integer >> (T::e_bits + T::m_bits)) & 1) != 0; } @@ -254,7 +258,10 @@ template inline int fp_exponent(F x) { typedef FP_traits T; - union { F real; typename T::same_size_int integer; } u; + union { + F real; + typename T::same_size_int integer; + } u; u.real = x; int ex = (u.integer >> T::m_bits) & ((1u << T::e_bits) - 1); return ex + T::e_offset; @@ -269,10 +276,13 @@ template inline Fraction fp_mantissa(F x) { typedef FP_traits T; - union { F real; typename T::same_size_int integer; } u; + union { + F real; + typename T::same_size_int integer; + } u; u.real = x; Fraction f; - f.den = uint64_t{1} << T::m_bits; + f.den = uint64_t { 1 } << T::m_bits; f.num = u.integer & (f.den - 1); return f; } @@ -287,10 +297,11 @@ inline F fp_from_parts(bool sgn, int ex, uint64_t mant) { typedef FP_traits T; typedef typename T::same_size_int I; - union { F real; I integer; } u; - u.integer = mant | - (static_cast(ex - T::e_offset) << T::m_bits) | - (static_cast(sgn) << (T::e_bits + T::m_bits)); + union { + F real; + I integer; + } u; + u.integer = mant | (static_cast(ex - T::e_offset) << T::m_bits) | (static_cast(sgn) << (T::e_bits + T::m_bits)); return u.real; } @@ -328,3 +339,20 @@ bool isValidAudio(absl::Span span) return true; } + +class ScopedRoundingMode { +public: + ScopedRoundingMode() = delete; + ScopedRoundingMode(int newRoundingMode) + : savedFloatMode(std::fegetround()) + { + std::fesetround(newRoundingMode); + } + ~ScopedRoundingMode() + { + std::fesetround(savedFloatMode); + } + +private: + const int savedFloatMode; +}; diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h index a476f6ae..8e01610a 100644 --- a/src/sfizz/ModifierHelpers.h +++ b/src/sfizz/ModifierHelpers.h @@ -5,6 +5,7 @@ #include "SfzHelpers.h" #include "Resources.h" #include "absl/types/span.h" + namespace sfz { /** @@ -146,6 +147,8 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop ASSERT(events[0].delay == 0); ASSERT(step != 0.0f); + ScopedRoundingMode roundingMode { Round ? FE_TONEAREST : FE_TOWARDZERO }; + if (envelope.size() == 0) return; const auto maxDelay = static_cast(envelope.size() - 1); @@ -156,14 +159,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop // log q log q // and log(b)\log(q) is between 0 and 1. auto quantize = [logStep](float value) -> float { - IF_CONSTEXPR(Round) - { - return std::exp(logStep * std::round(std::log(value) / logStep)); - } - else - { - return std::exp(logStep * std::trunc(std::log(value) / logStep)); - } + return std::exp(logStep * std::rint(std::log(value) / logStep)); }; auto lastValue = quantize(lambda(events[0].value)); @@ -182,7 +178,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop const auto numSteps = std::round(std::log(difference) / logStep); const auto stepLength = static_cast(length / numSteps); - for (int i = 0; i < numSteps; ++i) { + for (int i = 0; i < static_cast(numSteps); ++i) { fill(envelope.subspan(lastDelay, stepLength), lastValue); lastValue = nextValue > lastValue ? lastValue * step : lastValue / step; lastDelay += stepLength; diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index 551cc807..8c7e2d55 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -42,13 +42,13 @@ TEST_CASE("[Envelopes] Empty") std::array expected { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; std::array expectedMul { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier); - REQUIRE(output == expectedMul); + REQUIRE(approxEqual(output, expectedMul)); multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier, 2.0f); - REQUIRE(output == expectedMul); + REQUIRE(approxEqual(output, expectedMul)); } TEST_CASE("[Envelopes] Linear basic") @@ -60,7 +60,7 @@ TEST_CASE("[Envelopes] Linear basic") std::array output; std::array expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[LinearEnvelope] 2 events, close") @@ -73,7 +73,7 @@ TEST_CASE("[LinearEnvelope] 2 events, close") std::array output; std::array expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[LinearEnvelope] 2 events, far") @@ -86,7 +86,7 @@ TEST_CASE("[LinearEnvelope] 2 events, far") std::array output; std::array expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[LinearEnvelope] 3 events, out of block") @@ -100,7 +100,7 @@ TEST_CASE("[LinearEnvelope] 3 events, out of block") std::array output; std::array expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[LinearEnvelope] 2 events, function") @@ -113,7 +113,7 @@ TEST_CASE("[LinearEnvelope] 2 events, function") std::array output; std::array expected { 0.0f, 1.0f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.0f, 4.0f }; linearEnvelope(events, absl::MakeSpan(output), twiceModifier); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[LinearEnvelope] Get quantized") @@ -126,7 +126,7 @@ TEST_CASE("[LinearEnvelope] Get quantized") std::array output; std::array expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets") @@ -139,7 +139,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets") std::array output; std::array expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[LinearEnvelope] Get quantized with 2 steps") @@ -152,7 +152,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps") std::array output; std::array expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of block step") @@ -166,7 +166,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of std::array output; std::array expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 4.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } @@ -180,7 +180,7 @@ TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps") std::array output; std::array expected { 3.0f, 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f }; linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[MultiplicativeEnvelope] Basic event") @@ -231,7 +231,7 @@ TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps") std::array output; std::array expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 4.0f }; multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of range step") @@ -245,7 +245,7 @@ TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of ran std::array output; std::array expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 8.0f }; multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps") @@ -258,7 +258,7 @@ TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps") std::array output; std::array expected { 4.0f, 4.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f }; multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events") @@ -271,7 +271,7 @@ TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events") std::array output; std::array expected { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f }; multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f); - REQUIRE(output == expected); + REQUIRE(approxEqual(output, expected)); } TEST_CASE("[linearModifiers] Compare with envelopes") @@ -284,7 +284,7 @@ TEST_CASE("[linearModifiers] Compare with envelopes") ccData.data.value = 100.0f; resources.midiState.ccEvent(5, 20, 0.1); - resources.midiState.ccEvent(10, 20, 0.2); + resources.midiState.ccEvent(10, 20, 0.8); std::array output; std::array envelope; @@ -312,7 +312,7 @@ TEST_CASE("[linearModifiers] Compare with envelopes") ccData.data.curve = 2; ccData.data.value = 20.0f; - ccData.data.step = 2.0f; + ccData.data.step = 2.5f; linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { return ccData.data.value * (1 - x); }, ccData.data.step); @@ -330,7 +330,7 @@ TEST_CASE("[multiplicativeModifiers] Compare with envelopes") ccData.data.value = 100.0f; resources.midiState.ccEvent(5, 20, 0.1); - resources.midiState.ccEvent(10, 20, 0.8); + resources.midiState.ccEvent(15, 20, 0.8); std::array output; std::array envelope; @@ -364,7 +364,7 @@ TEST_CASE("[multiplicativeModifiers] Compare with envelopes") ccData.data.curve = 2; ccData.data.value = 20.0f; - ccData.data.step = 2.0f; + ccData.data.step = 2.5f; multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) { return db2mag(ccData.data.value * (1 - x)); }, db2mag(ccData.data.step) ); From 442e15b8e920cd3636861914cbb7546e75ee4487 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 14 Apr 2020 23:08:28 +0200 Subject: [PATCH 22/22] Adapted the tests --- tests/EventEnvelopesT.cpp | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index 8c7e2d55..64bcb7e4 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -224,9 +224,9 @@ TEST_CASE("[MultiplicativeEnvelope] 2 events, far") TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps") { sfz::EventVector events { - { 0, 1.0f }, - { 2, 2.0f }, - { 6, 4.0f } + { 0, 1.3f }, + { 2, 2.1f }, + { 6, 4.2f } }; std::array output; std::array expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 4.0f }; @@ -237,9 +237,9 @@ TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps") TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of range step") { sfz::EventVector events { - { 0, 1.0f }, - { 2, 2.0f }, - { 6, 4.0f }, + { 0, 1.3f }, + { 2, 2.1f }, + { 6, 4.1f }, { 10, 8.2f } }; std::array output; @@ -251,9 +251,9 @@ TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of ran TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps") { sfz::EventVector events { - { 0, 4.0f }, - { 2, 2.0f }, - { 6, 0.5f } + { 0, 4.1f }, + { 2, 2.2f }, + { 6, 0.4f } }; std::array output; std::array expected { 4.0f, 4.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f }; @@ -261,19 +261,6 @@ TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps") REQUIRE(approxEqual(output, expected)); } -TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events") -{ - sfz::EventVector events { - { 0, 1.0f }, - { 2, 1.2f }, - { 6, 2.5f } - }; - std::array output; - std::array expected { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f }; - multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f); - REQUIRE(approxEqual(output, expected)); -} - TEST_CASE("[linearModifiers] Compare with envelopes") { sfz::Resources resources;