From 6a660779118fb8ebe5a4371961d324cf03e6abc4 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 28 Mar 2020 23:41:15 +0100 Subject: [PATCH 01/72] Use advanceTime to compute the note-on time --- src/sfizz/MidiState.cpp | 31 +++++++++++++++++++++----- src/sfizz/MidiState.h | 35 +++++++++++++++++++++++++----- src/sfizz/Synth.cpp | 4 ++++ tests/RegionValueComputationsT.cpp | 7 +++--- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 6e8ccac3..a5b40576 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -20,7 +20,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex if (noteNumber >= 0 && noteNumber < 128) { lastNoteVelocities[noteNumber] = velocity; - noteOnTimes[noteNumber] = std::chrono::steady_clock::now(); + noteOnTimes[noteNumber] = internalClock + static_cast(delay); activeNotes++; } @@ -28,6 +28,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noexcept { + ASSERT(delay >= 0); ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(velocity >= 0.0 && velocity <= 1.0); UNUSED(velocity); @@ -38,14 +39,30 @@ void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noe } -float sfz::MidiState::getNoteDuration(int noteNumber) const +void sfz::MidiState::setSampleRate(float sampleRate) noexcept { - ASSERT(noteNumber >= 0 && noteNumber <= 127); + this->sampleRate = sampleRate; + internalClock = 0; + absl::c_fill(noteOnTimes, 0); +} + +void sfz::MidiState::advanceTime(int numSamples) noexcept +{ + internalClock += numSamples; +} + +void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept +{ + this->samplesPerBlock = samplesPerBlock; +} + +float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const +{ + ASSERT(noteNumber >= 0 && noteNumber < 128); if (noteNumber >= 0 && noteNumber < 128) { - const auto noteOffTime = std::chrono::steady_clock::now(); - const auto duration = std::chrono::duration_cast>(noteOffTime - noteOnTimes[noteNumber]); - return duration.count(); + const unsigned timeInSamples = internalClock + static_cast(delay) - noteOnTimes[noteNumber]; + return static_cast(timeInSamples) / sampleRate; } return 0.0f; @@ -95,6 +112,8 @@ void sfz::MidiState::reset(int delay) noexcept pitchBend = 0; activeNotes = 0; + internalClock = 0; + absl::c_fill(noteOnTimes, 0); } void sfz::MidiState::resetAllControllers(int delay) noexcept diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 09c4ce78..d127d1de 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -5,7 +5,6 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once -#include #include #include "CCMap.h" #include "Range.h" @@ -42,13 +41,29 @@ public: int getActiveNotes() const noexcept { return activeNotes; } /** - * @brief Register a note off and get the note duration + * @brief Get the note duration since note on * * @param noteNumber + * @param delay * @return float */ - float getNoteDuration(int noteNumber) const; + float getNoteDuration(int noteNumber, int delay = 0) const; + /** + * @brief Set the maximum size of the blocks for the callback. The actual + * size can be lower in each callback but should not be larger + * than this value. + * + * @param samplesPerBlock + */ + void setSamplesPerBlock(int samplesPerBlock) noexcept; + /** + * @brief Set the sample rate. If you do not call it it is initialized + * to sfz::config::defaultSampleRate. + * + * @param sampleRate + */ + void setSampleRate(float sampleRate) noexcept; /** * @brief Get the note on velocity for a given note * @@ -79,6 +94,13 @@ public: */ void ccEvent(int delay, int ccNumber, float ccValue) noexcept; + /** + * @brief Advances the internal clock of a given amount of samples. + * You should call this at each callback. + * + * @param numSamples the number of samples of clock advance + */ + void advanceTime(int numSamples) noexcept; /** * @brief Get the CC value for CC number * @@ -121,13 +143,13 @@ public: private: template using MidiNoteArray = std::array; - using NoteOnTime = std::chrono::steady_clock::time_point; int activeNotes { 0 }; + /** * @brief Stores the note on times. * */ - MidiNoteArray noteOnTimes; + MidiNoteArray noteOnTimes {}; /** * @brief Stores the velocity of the note ons for currently * depressed notes. @@ -143,5 +165,8 @@ private: * Pitch bend status */ int pitchBend { 0 }; + double sampleRate { config::defaultSampleRate }; + int samplesPerBlock { config::defaultSamplesPerBlock }; + unsigned internalClock { 0 }; }; } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a3efca3e..6882030c 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -483,6 +483,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); + resources.midiState.setSamplesPerBlock(samplesPerBlock); for (auto& bus : effectBuses) { if (bus) @@ -503,6 +504,7 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept resources.filterPool.setSampleRate(sampleRate); resources.eqPool.setSampleRate(sampleRate); + resources.midiState.setSampleRate(sampleRate); for (auto& bus : effectBuses) { if (bus) @@ -525,6 +527,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept auto temp = AudioSpan(tempBuffer).first(numFrames); auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); + resources.midiState.advanceTime(buffer.getNumFrames()); + CallbackBreakdown callbackBreakdown; { // Prepare the effect inputs. They are mixes of per-region outputs. diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index 31566e76..0f760b06 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -262,20 +262,21 @@ TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") TEST_CASE("[Region] rt_decay") { sfz::MidiState midiState; + midiState.setSampleRate(1000); sfz::Region region { midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "rt_decay", "10" }); midiState.noteOnEvent(0, 64, 64_norm); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + midiState.advanceTime(100); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) ); region.parseOpcode({ "rt_decay", "20" }); midiState.noteOnEvent(0, 64, 64_norm); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + midiState.advanceTime(100); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) ); region.parseOpcode({ "trigger", "attack" }); midiState.noteOnEvent(0, 64, 64_norm); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + midiState.advanceTime(100); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) ); } From 5c5dd07da109c8373b74340797d793858ec197ae Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 00:07:44 +0100 Subject: [PATCH 02/72] Store polyphony information in the synth --- src/sfizz/Synth.cpp | 34 ++++++++++++++++++++++++++++++++++ src/sfizz/Synth.h | 16 ++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 6882030c..fd25c42e 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -67,6 +67,7 @@ void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector& members) } } +void sfz::Synth::handleGroupOpcodes(const std::vector& members) +{ + absl::optional groupIdx; + absl::optional maxPolyphony; + + for (auto& member : members) { + switch (member.lettersOnlyHash) { + case hash("group"): + setValueFromOpcode(member, groupIdx, Default::groupRange); + break; + case hash("polyphony"): + setValueFromOpcode(member, maxPolyphony, Range(0, config::maxVoices)); + break; + } + } + + if (groupIdx && maxPolyphony) + setGroupPolyphony(*groupIdx, *maxPolyphony); +} + void sfz::Synth::handleControlOpcodes(const std::vector& members) { for (auto& member : members) { @@ -1010,3 +1033,14 @@ void sfz::Synth::allSoundOff() noexcept for (auto& effectBus : effectBuses) effectBus->clear(); } + +void sfz::Synth::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept +{ + if (groupIdx >= groupMaxPolyphony.size()) + groupMaxPolyphony.resize(groupIdx + 1); + + if (groupIdx >= groupPolyphony.size()) + groupPolyphony.resize(groupIdx + 1); + + groupMaxPolyphony[groupIdx] = polyphony; +} diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index b18a453b..e0c5390e 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -410,6 +410,16 @@ protected: void onParseWarning(const SourceRange& range, const std::string& message) override; private: + /** + * @brief change the group maximum polyphony + * + * @param groupIdx the group index + * @param polyphone the max polyphony + */ + void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept; + std::vector groupPolyphony; + std::vector groupMaxPolyphony; + /** * @brief Reset all CCs; to be used on CC 121 * @@ -440,6 +450,12 @@ private: * @param members the opcodes of the block */ void handleGlobalOpcodes(const std::vector& members); + /** + * @brief Helper function to dispatch opcodes + * + * @param members the opcodes of the block + */ + void handleGroupOpcodes(const std::vector& members); /** * @brief Helper function to dispatch opcodes * From fbb1cc9efceeb41c9d196cee22d81d73be51c80c Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 00:23:26 +0100 Subject: [PATCH 03/72] Add support for group polyphony --- src/sfizz/Synth.cpp | 13 +++++++++---- src/sfizz/Synth.h | 3 +-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index fd25c42e..18a82f8d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -157,8 +157,8 @@ void sfz::Synth::clear() masterOpcodes.clear(); groupOpcodes.clear(); unknownOpcodes.clear(); - groupPolyphony.clear(); groupMaxPolyphony.clear(); + groupMaxPolyphony.push_back(config::maxVoices); modificationTime = fs::file_time_type::min(); } @@ -682,11 +682,19 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc const auto randValue = randNoteDistribution(Random::randomGenerator); for (auto& region : noteActivationLists[noteNumber]) { if (region->registerNoteOn(noteNumber, velocity, randValue)) { + auto activeNotesInGroup = 0; + for (auto& voice : voices) { + if (voice->getRegion()->group == region->group) + activeNotesInGroup += 1; + if (voice->checkOffGroup(delay, region->group)) noteOffDispatch(delay, voice->getTriggerNumber(), voice->getTriggerValue()); } + if (activeNotesInGroup >= groupMaxPolyphony[region->group]) + continue; + auto voice = findFreeVoice(); if (voice == nullptr) continue; @@ -1039,8 +1047,5 @@ void sfz::Synth::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexce if (groupIdx >= groupMaxPolyphony.size()) groupMaxPolyphony.resize(groupIdx + 1); - if (groupIdx >= groupPolyphony.size()) - groupPolyphony.resize(groupIdx + 1); - groupMaxPolyphony[groupIdx] = polyphony; } diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index e0c5390e..2eded30a 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -417,8 +417,7 @@ private: * @param polyphone the max polyphony */ void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept; - std::vector groupPolyphony; - std::vector groupMaxPolyphony; + std::vector groupMaxPolyphony { config::maxVoices }; /** * @brief Reset all CCs; to be used on CC 121 From 30061bdb4e0d4606714d05a68c23ac84de8cbe1e Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 00:48:27 +0100 Subject: [PATCH 04/72] Add note_polyphony and note_selfmask in the parser --- src/sfizz/Defaults.h | 3 +++ src/sfizz/Region.cpp | 16 ++++++++++++++++ src/sfizz/Region.h | 2 ++ tests/RegionT.cpp | 23 +++++++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 9e6f0d84..5ceaa784 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -34,6 +34,7 @@ enum class SfzLoopMode { no_loop, one_shot, loop_continuous, loop_sustain }; enum class SfzOffMode { fast, normal }; enum class SfzVelocityOverride { current, previous }; enum class SfzCrossfadeCurve { gain, power }; +enum class SfzSelfMask { mask, dontMask }; namespace sfz { @@ -65,6 +66,8 @@ namespace Default constexpr uint32_t group { 0 }; constexpr Range groupRange { 0, std::numeric_limits::max() }; constexpr SfzOffMode offMode { SfzOffMode::fast }; + constexpr Range polyphonyRange { 0, config::maxVoices }; + constexpr SfzSelfMask selfMask { SfzSelfMask::mask }; // Region logic: key mapping constexpr Range keyRange { 0, 127 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 7ab2dbfb..ff37c50d 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -124,6 +124,22 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) DBG("Unkown off mode:" << std::string(opcode.value)); } break; + case hash("note_polyphony"): + if (auto value = readOpcode(opcode.value, Default::polyphonyRange)) + notePolyphony = *value; + break; + case hash("note_selfmask"): + switch (hash(opcode.value)) { + case hash("on"): + selfMask = SfzSelfMask::mask; + break; + case hash("off"): + selfMask = SfzSelfMask::dontMask; + break; + default: + DBG("Unkown self mask value:" << std::string(opcode.value)); + } + break; // Region logic: key mapping case hash("lokey"): setRangeStartFromOpcode(opcode, keyRange, Default::keyRange); diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index eb3adb13..a499a5eb 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -240,6 +240,8 @@ struct Region { uint32_t group { Default::group }; // group absl::optional offBy {}; // off_by SfzOffMode offMode { Default::offMode }; // off_mode + absl::optional notePolyphony {}; + SfzSelfMask selfMask { Default::selfMask }; // Region logic: key mapping Range keyRange { Default::keyRange }; //lokey, hikey and key diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index c5338b49..c0b83dd4 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -1407,6 +1407,29 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "oscillator_phase", "361" }); REQUIRE(region.oscillatorPhase == 360.0f); } + + SECTION("Note polyphony") + { + REQUIRE(!region.notePolyphony); + region.parseOpcode({ "note_polyphony", "45" }); + REQUIRE(region.notePolyphony); + REQUIRE(*region.notePolyphony == 45); + region.parseOpcode({ "note_polyphony", "-1" }); + REQUIRE(region.notePolyphony); + REQUIRE(*region.notePolyphony == 0); + } + + SECTION("Note selfmask") + { + REQUIRE(region.selfMask == SfzSelfMask::mask); + region.parseOpcode({ "note_selfmask", "off" }); + REQUIRE(region.selfMask == SfzSelfMask::dontMask); + region.parseOpcode({ "note_selfmask", "on" }); + REQUIRE(region.selfMask == SfzSelfMask::mask); + region.parseOpcode({ "note_selfmask", "off" }); + region.parseOpcode({ "note_selfmask", "garbage" }); + REQUIRE(region.selfMask == SfzSelfMask::dontMask); + } } // Specific region bugs From 42720109a7df1ce415f089b12d5654526ace9582 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 00:48:47 +0100 Subject: [PATCH 05/72] Correct a bug if a region has a group number but no group-level polyphony setting --- src/sfizz/Synth.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 18a82f8d..34ef2472 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -398,6 +398,10 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) } } + // Some regions had group number but no "group-level" opcodes handled the polyphony + while (groupMaxPolyphony.size() <= region->group) + groupMaxPolyphony.push_back(config::maxVoices); + for (auto note = 0; note < 128; note++) { if (region->keyRange.containsWithEnd(note) || (region->hasKeyswitches() && region->keyswitchRange.containsWithEnd(note))) noteActivationLists[note].push_back(region); @@ -682,10 +686,14 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc const auto randValue = randNoteDistribution(Random::randomGenerator); for (auto& region : noteActivationLists[noteNumber]) { if (region->registerNoteOn(noteNumber, velocity, randValue)) { - auto activeNotesInGroup = 0; + unsigned activeNotesInGroup { 0 }; for (auto& voice : voices) { - if (voice->getRegion()->group == region->group) + const auto voiceRegion = voice->getRegion(); + if (voiceRegion == nullptr) + continue; + + if (voiceRegion->group == region->group) activeNotesInGroup += 1; if (voice->checkOffGroup(delay, region->group)) @@ -1044,8 +1052,8 @@ void sfz::Synth::allSoundOff() noexcept void sfz::Synth::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept { - if (groupIdx >= groupMaxPolyphony.size()) - groupMaxPolyphony.resize(groupIdx + 1); + while (groupMaxPolyphony.size() <= groupIdx) + groupMaxPolyphony.push_back(config::maxVoices); groupMaxPolyphony[groupIdx] = polyphony; } From 5ef9d6e9960a044d5ee876ce21d575ce07986dda Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 00:49:55 +0100 Subject: [PATCH 06/72] the handleGroupOpcode logic was flawed --- src/sfizz/Synth.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 34ef2472..1d85b6ac 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -180,7 +180,7 @@ void sfz::Synth::handleGlobalOpcodes(const std::vector& members) void sfz::Synth::handleGroupOpcodes(const std::vector& members) { absl::optional groupIdx; - absl::optional maxPolyphony; + unsigned maxPolyphony { config::maxVoices }; for (auto& member : members) { switch (member.lettersOnlyHash) { @@ -193,8 +193,8 @@ void sfz::Synth::handleGroupOpcodes(const std::vector& members) } } - if (groupIdx && maxPolyphony) - setGroupPolyphony(*groupIdx, *maxPolyphony); + if (groupIdx) + setGroupPolyphony(*groupIdx, maxPolyphony); } void sfz::Synth::handleControlOpcodes(const std::vector& members) From 26a9ab2c7020cde1fa569240d84d4d0facdb4529 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 01:12:52 +0100 Subject: [PATCH 07/72] Handle note_polyphony and note_selfmask --- src/sfizz/Synth.cpp | 27 +++++++++++++++++++++++++++ src/sfizz/Voice.h | 14 +++++++------- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 1d85b6ac..01cf351b 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -687,6 +687,8 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc for (auto& region : noteActivationLists[noteNumber]) { if (region->registerNoteOn(noteNumber, velocity, randValue)) { unsigned activeNotesInGroup { 0 }; + unsigned activeNotes { 0 }; + Voice* selfMaskCandidate { nullptr }; for (auto& voice : voices) { const auto voiceRegion = voice->getRegion(); @@ -696,6 +698,24 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc if (voiceRegion->group == region->group) activeNotesInGroup += 1; + if (region->notePolyphony) { + if (voice->getTriggerNumber() == noteNumber && voice->getTriggerType() == Voice::TriggerType::NoteOn) { + activeNotes += 1; + switch (region->selfMask) { + case SfzSelfMask::mask: + if (voice->getTriggerValue() < velocity) { + if (!selfMaskCandidate || selfMaskCandidate->getTriggerValue() > voice->getTriggerValue()) + selfMaskCandidate = voice.get(); + } + break; + case SfzSelfMask::dontMask: + if (!selfMaskCandidate || selfMaskCandidate->getSourcePosition() < voice->getSourcePosition()) + selfMaskCandidate = voice.get(); + break; + } + } + } + if (voice->checkOffGroup(delay, region->group)) noteOffDispatch(delay, voice->getTriggerNumber(), voice->getTriggerValue()); } @@ -703,6 +723,13 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc if (activeNotesInGroup >= groupMaxPolyphony[region->group]) continue; + if (region->notePolyphony && activeNotes >= *region->notePolyphony) { + if (selfMaskCandidate != nullptr) + selfMaskCandidate->release(delay); + else // We're the lowest velocity guy here + continue; + } + auto voice = findFreeVoice(); if (voice == nullptr) continue; diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 1772bf45..4227c326 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -210,6 +210,13 @@ public: * @param numFilters */ void setMaxEQsPerVoice(size_t numEQs); + /** + * @brief Release the voice after a given delay + * + * @param delay + * @param fastRelease whether to do a normal release or cut the voice abruptly + */ + void release(int delay, bool fastRelease = false) noexcept; Duration getLastDataDuration() const noexcept { return dataDuration; } Duration getLastAmplitudeDuration() const noexcept { return amplitudeDuration; } @@ -243,13 +250,6 @@ private: * @param buffer */ void processStereo(AudioSpan buffer) noexcept; - /** - * @brief Release the voice after a given delay - * - * @param delay - * @param fastRelease whether to do a normal release or cut the voice abruptly - */ - void release(int delay, bool fastRelease = false) noexcept; Region* region { nullptr }; enum class State { From 5ff31ae181f3202fb358482645c2fffb8c9a8095 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 12:52:34 +0200 Subject: [PATCH 08/72] Add some polyphony tests --- tests/SynthT.cpp | 42 +++++++++++++++++++++++++++++++++++ tests/TestFiles/polyphony.sfz | 5 +++++ 2 files changed, 47 insertions(+) create mode 100644 tests/TestFiles/polyphony.sfz diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 4411d12b..e0f754ba 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -332,3 +332,45 @@ TEST_CASE("[Synth] Gain to mix") REQUIRE( bus->gainToMain() == 0 ); REQUIRE( bus->gainToMix() == 0.5 ); } + +TEST_CASE("[Synth] group polyphony limits") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/polyphony.sfz"); + synth.noteOn(0, 65, 64); + synth.noteOn(0, 65, 64); + synth.noteOn(0, 65, 64); + REQUIRE(synth.getNumActiveVoices() == 2); // group polyphony should block the last note +} + +TEST_CASE("[Synth] Self-masking") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/polyphony.sfz"); + synth.noteOn(0, 64, 63); + synth.noteOn(0, 64, 62); + synth.noteOn(0, 64, 64); + REQUIRE(synth.getNumActiveVoices() == 3); // One of these is releasing + REQUIRE(synth.getVoiceView(0)->getTriggerValue() == 63_norm); + REQUIRE(!synth.getVoiceView(0)->canBeStolen()); + REQUIRE(synth.getVoiceView(1)->getTriggerValue() == 62_norm); + REQUIRE(synth.getVoiceView(1)->canBeStolen()); // The lowest velocity voice is the masking candidate + REQUIRE(synth.getVoiceView(2)->getTriggerValue() == 64_norm); + REQUIRE(!synth.getVoiceView(2)->canBeStolen()); +} + +TEST_CASE("[Synth] Not self-masking") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/polyphony.sfz"); + synth.noteOn(0, 66, 63); + synth.noteOn(0, 66, 62); + synth.noteOn(0, 66, 64); + REQUIRE(synth.getNumActiveVoices() == 3); // One of these is releasing + REQUIRE(synth.getVoiceView(0)->getTriggerValue() == 63_norm); + REQUIRE(synth.getVoiceView(0)->canBeStolen()); // The first encountered voice is the masking candidate + REQUIRE(synth.getVoiceView(1)->getTriggerValue() == 62_norm); + REQUIRE(!synth.getVoiceView(1)->canBeStolen()); + REQUIRE(synth.getVoiceView(2)->getTriggerValue() == 64_norm); + REQUIRE(!synth.getVoiceView(2)->canBeStolen()); +} diff --git a/tests/TestFiles/polyphony.sfz b/tests/TestFiles/polyphony.sfz new file mode 100644 index 00000000..32aed2e9 --- /dev/null +++ b/tests/TestFiles/polyphony.sfz @@ -0,0 +1,5 @@ + sample=*sine key=63 + sample=*sine key=64 note_polyphony=2 + sample=*sine key=66 note_polyphony=2 note_selfmask=off + group=1 polyphony=2 + sample=*sine key=65 From 27ceeb8e1a5850f401b36e33de72073d118248ef Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 13:08:37 +0200 Subject: [PATCH 09/72] Track noteoffs --- src/sfizz/MidiState.cpp | 14 +++++++++----- src/sfizz/MidiState.h | 5 +++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index a5b40576..7b505ca3 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -33,6 +33,7 @@ void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noe ASSERT(velocity >= 0.0 && velocity <= 1.0); UNUSED(velocity); if (noteNumber >= 0 && noteNumber < 128) { + noteOffTimes[noteNumber] = internalClock + static_cast(delay); if (activeNotes > 0) activeNotes--; } @@ -44,6 +45,7 @@ void sfz::MidiState::setSampleRate(float sampleRate) noexcept this->sampleRate = sampleRate; internalClock = 0; absl::c_fill(noteOnTimes, 0); + absl::c_fill(noteOffTimes, 0); } void sfz::MidiState::advanceTime(int numSamples) noexcept @@ -59,13 +61,14 @@ void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const { ASSERT(noteNumber >= 0 && noteNumber < 128); + if (noteNumber < 0 || noteNumber >= 128) + return 0.0f; - if (noteNumber >= 0 && noteNumber < 128) { - const unsigned timeInSamples = internalClock + static_cast(delay) - noteOnTimes[noteNumber]; - return static_cast(timeInSamples) / sampleRate; - } + if (noteOnTimes[noteNumber] != 0 && noteOffTimes[noteNumber] != 0 && noteOnTimes[noteNumber] > noteOffTimes[noteNumber]) + return 0.0f; - return 0.0f; + const unsigned timeInSamples = internalClock + static_cast(delay) - noteOnTimes[noteNumber]; + return static_cast(timeInSamples) / sampleRate; } float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept @@ -114,6 +117,7 @@ void sfz::MidiState::reset(int delay) noexcept activeNotes = 0; internalClock = 0; absl::c_fill(noteOnTimes, 0); + absl::c_fill(noteOffTimes, 0); } void sfz::MidiState::resetAllControllers(int delay) noexcept diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index d127d1de..c5ac1dba 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -150,6 +150,11 @@ private: * */ MidiNoteArray noteOnTimes {}; + /** + * @brief Stores the note off times. + * + */ + MidiNoteArray noteOffTimes {}; /** * @brief Stores the velocity of the note ons for currently * depressed notes. From 1215067dcfaea3eb4e33905ea50c509b0459b72f Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 13:13:19 +0200 Subject: [PATCH 10/72] Use vectors of events in the midi state --- src/sfizz/MidiState.cpp | 16 ++++++++++------ src/sfizz/MidiState.h | 4 +++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 7b505ca3..0e207841 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -95,14 +95,14 @@ void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept { ASSERT(ccValue >= 0.0 && ccValue <= 1.0); - cc[ccNumber] = ccValue; + cc[ccNumber].emplace_back(delay, ccValue); } float sfz::MidiState::getCCValue(int ccNumber) const noexcept { ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); - return cc[ccNumber]; + return cc[ccNumber].back().second; } void sfz::MidiState::reset(int delay) noexcept @@ -110,8 +110,10 @@ void sfz::MidiState::reset(int delay) noexcept for (auto& velocity: lastNoteVelocities) velocity = 0; - for (auto& ccValue: cc) - ccValue = 0; + for (auto& ccEvents: cc) { + ccEvents.clear(); + ccEvents.emplace_back(0, 0.0f); + } pitchBend = 0; activeNotes = 0; @@ -122,8 +124,10 @@ void sfz::MidiState::reset(int delay) noexcept void sfz::MidiState::resetAllControllers(int delay) noexcept { - for (unsigned idx = 0; idx < config::numCCs; idx++) - cc[idx] = 0; + for (auto& ccEvents: cc) { + ccEvents.clear(); + ccEvents.emplace_back(0, 0.0f); + } pitchBend = 0; } diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index c5ac1dba..1b73dc42 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -143,6 +143,8 @@ public: private: template using MidiNoteArray = std::array; + using MidiEvent = std::pair; + using EventVector = std::vector; int activeNotes { 0 }; /** @@ -165,7 +167,7 @@ private: * @brief Current known values for the CCs. * */ - std::array cc; + std::array cc; /** * Pitch bend status */ From fc05ca0af0bb2745410a9e9f9fefa86707e62723 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 13:20:30 +0200 Subject: [PATCH 11/72] Clean up the reset* APIs --- src/sfizz/MidiState.cpp | 10 ++++------ src/sfizz/MidiState.h | 2 +- src/sfizz/Synth.cpp | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 0e207841..b1536251 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -10,7 +10,7 @@ sfz::MidiState::MidiState() { - reset(0); + reset(); } void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noexcept @@ -105,7 +105,7 @@ float sfz::MidiState::getCCValue(int ccNumber) const noexcept return cc[ccNumber].back().second; } -void sfz::MidiState::reset(int delay) noexcept +void sfz::MidiState::reset() noexcept { for (auto& velocity: lastNoteVelocities) velocity = 0; @@ -124,10 +124,8 @@ void sfz::MidiState::reset(int delay) noexcept void sfz::MidiState::resetAllControllers(int delay) noexcept { - for (auto& ccEvents: cc) { - ccEvents.clear(); - ccEvents.emplace_back(0, 0.0f); - } + for (unsigned ccIdx = 0; ccIdx < config::numCCs; ++ccIdx) + ccEvent(delay, ccIdx, 0.0f); pitchBend = 0; } diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 1b73dc42..103ed9cd 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -113,7 +113,7 @@ public: * @brief Reset the midi state (does not impact the last note on time) * */ - void reset(int delay) noexcept; + void reset() noexcept; /** * @brief Reset all the controllers diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 01cf351b..d9b0bb88 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -151,7 +151,7 @@ void sfz::Synth::clear() fileTicket = -1; defaultSwitch = absl::nullopt; defaultPath = ""; - resources.midiState.reset(0); + resources.midiState.reset(); ccNames.clear(); globalOpcodes.clear(); masterOpcodes.clear(); From dbe544515f1fb32a2b46ed6c13b1ec16d35d8b90 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 13:20:41 +0200 Subject: [PATCH 12/72] Add a test for resetAllControllers --- tests/MidiStateT.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/MidiStateT.cpp b/tests/MidiStateT.cpp index b00bbe64..6a2af4c0 100644 --- a/tests/MidiStateT.cpp +++ b/tests/MidiStateT.cpp @@ -49,12 +49,25 @@ TEST_CASE("[MidiState] Reset") state.pitchBendEvent(0, 894); state.noteOnEvent(0, 64, 24_norm); state.ccEvent(0, 123, 124_norm); - state.reset(0); + state.reset(); REQUIRE(state.getPitchBend() == 0); REQUIRE(state.getNoteVelocity(64) == 0_norm); REQUIRE(state.getCCValue(123) == 0_norm); } +TEST_CASE("[MidiState] Reset all controllers") +{ + sfz::MidiState state; + state.pitchBendEvent(20, 894); + state.ccEvent(10, 122, 124_norm); + REQUIRE(state.getPitchBend() == 894); + REQUIRE(state.getCCValue(122) == 124_norm); + state.resetAllControllers(30); + REQUIRE(state.getPitchBend() == 0); + REQUIRE(state.getCCValue(122) == 0_norm); + REQUIRE(state.getCCValue(4) == 0_norm); +} + TEST_CASE("[MidiState] Set and get note velocities") { sfz::MidiState state; From 1d74968bf02607873adfebf1776043cbd2f636b9 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 13:27:47 +0200 Subject: [PATCH 13/72] Clear cc events on advanceTime --- src/sfizz/MidiState.cpp | 6 ++++++ src/sfizz/MidiState.h | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index b1536251..9406d66c 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -51,6 +51,12 @@ void sfz::MidiState::setSampleRate(float sampleRate) noexcept void sfz::MidiState::advanceTime(int numSamples) noexcept { internalClock += numSamples; + for (auto& ccEvents: cc) { + ASSERT(!ccEvents.empty()); // CC event vectors should never be empty + ccEvents.front().second = ccEvents.back().second; + ccEvents.front().first = 0; + ccEvents.resize(1); + } } void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 103ed9cd..22df7242 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -96,7 +96,8 @@ public: /** * @brief Advances the internal clock of a given amount of samples. - * You should call this at each callback. + * You should call this at each callback. This will flush the events + * in the midistate memory. * * @param numSamples the number of samples of clock advance */ From 7b6c1c8106c595fe4609b47eb17a6260c8b52396 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 13:29:56 +0200 Subject: [PATCH 14/72] Move advanceTime to the end of the callback --- src/sfizz/Synth.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index d9b0bb88..2c701e19 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -554,8 +554,6 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept auto temp = AudioSpan(tempBuffer).first(numFrames); auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); - resources.midiState.advanceTime(buffer.getNumFrames()); - CallbackBreakdown callbackBreakdown; { // Prepare the effect inputs. They are mixes of per-region outputs. @@ -621,6 +619,11 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept // Apply the master volume buffer.applyGain(db2mag(volume)); + { // Clear events and advance midi time + ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; + resources.midiState.advanceTime(buffer.getNumFrames()); + } + callbackBreakdown.dispatch = dispatchDuration; resources.logger.logCallbackTime(callbackBreakdown, numActiveVoices, numFrames); From 3f5a86e067bd8f459081916b0c6e06b6bd69aa0b Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 13:34:26 +0200 Subject: [PATCH 15/72] Attempt to reserve memory on samplePerBlock change --- src/sfizz/MidiState.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 9406d66c..b835063c 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -62,6 +62,10 @@ void sfz::MidiState::advanceTime(int numSamples) noexcept void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept { this->samplesPerBlock = samplesPerBlock; + for (auto& ccEvents: cc) { + ccEvents.shrink_to_fit(); + ccEvents.reserve(samplesPerBlock); + } } float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const From 0f4c32b5a870b28efbeac4ff05a813914fe1a77c Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 13:39:43 +0200 Subject: [PATCH 16/72] Corrected a bug in the equalizer reservation method --- src/sfizz/Voice.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 7bd044c5..fe7df7d5 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -603,6 +603,6 @@ void sfz::Voice::setMaxFiltersPerVoice(size_t numFilters) void sfz::Voice::setMaxEQsPerVoice(size_t numFilters) { // There are filters in there, this call is unexpected - ASSERT(filters.size() == 0); - filters.reserve(numFilters); + ASSERT(equalizers.size() == 0); + equalizers.reserve(numFilters); } From e2f0ca754e10d61e947fecf7ab56b6559e1d2c22 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 13:59:48 +0200 Subject: [PATCH 17/72] Sorted elements in ccEvents --- src/sfizz/MidiState.cpp | 12 ++++++------ src/sfizz/MidiState.h | 1 - src/sfizz/SfzHelpers.h | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index b835063c..5917d839 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -53,8 +53,8 @@ void sfz::MidiState::advanceTime(int numSamples) noexcept internalClock += numSamples; for (auto& ccEvents: cc) { ASSERT(!ccEvents.empty()); // CC event vectors should never be empty - ccEvents.front().second = ccEvents.back().second; - ccEvents.front().first = 0; + ccEvents.front().value = ccEvents.back().value; + ccEvents.front().delay = 0; ccEvents.resize(1); } } @@ -104,15 +104,15 @@ int sfz::MidiState::getPitchBend() const noexcept void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept { ASSERT(ccValue >= 0.0 && ccValue <= 1.0); - - cc[ccNumber].emplace_back(delay, ccValue); + const auto insertionPoint = absl::c_upper_bound(cc[ccNumber], delay, MidiEventComparator{}); + cc[ccNumber].insert(insertionPoint, { delay, ccValue }); } float sfz::MidiState::getCCValue(int ccNumber) const noexcept { ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); - return cc[ccNumber].back().second; + return cc[ccNumber].back().value; } void sfz::MidiState::reset() noexcept @@ -122,7 +122,7 @@ void sfz::MidiState::reset() noexcept for (auto& ccEvents: cc) { ccEvents.clear(); - ccEvents.emplace_back(0, 0.0f); + ccEvents.push_back({ 0, 0.0f }); } pitchBend = 0; diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 22df7242..e1183dda 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -144,7 +144,6 @@ public: private: template using MidiNoteArray = std::array; - using MidiEvent = std::pair; using EventVector = std::vector; int activeNotes { 0 }; diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 2e904a5e..5d552670 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -62,6 +62,47 @@ struct CCValuePairComparator { } }; +struct MidiEvent { + int delay; + float value; +}; + +template +struct MidiEventComparator { + bool operator()(const MidiEvent& event, const int& delay) + { + return (event.delay < delay); + } + + bool operator()(const int& delay, const MidiEvent& event) + { + return (delay < event.delay); + } + + bool operator()(const MidiEvent& lhs, const MidiEvent& rhs) + { + return (lhs.delay < rhs.delay); + } +}; + +template<> +struct MidiEventComparator { + bool operator()(const MidiEvent& event, const float& value) + { + return (event.value < value); + } + + bool operator()(const float& value, const MidiEvent& event) + { + return (value < event.value); + } + + bool operator()(const MidiEvent& lhs, const MidiEvent& rhs) + { + return (lhs.value < rhs.value); + } +}; + /** * @brief Converts cents to a pitch ratio * From 9f8b27571233b9bc1f067e4535dcf0dcf37cb01b Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 14:01:21 +0200 Subject: [PATCH 18/72] Don't dispatch on template for the MidiEventComparator --- src/sfizz/MidiState.cpp | 2 +- src/sfizz/SfzHelpers.h | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 5917d839..d08f7d10 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -104,7 +104,7 @@ int sfz::MidiState::getPitchBend() const noexcept void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept { ASSERT(ccValue >= 0.0 && ccValue <= 1.0); - const auto insertionPoint = absl::c_upper_bound(cc[ccNumber], delay, MidiEventComparator{}); + const auto insertionPoint = absl::c_upper_bound(cc[ccNumber], delay, MidiEventDelayComparator{}); cc[ccNumber].insert(insertionPoint, { delay, ccValue }); } diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 5d552670..becc30d5 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -67,8 +67,7 @@ struct MidiEvent { float value; }; -template -struct MidiEventComparator { +struct MidiEventDelayComparator { bool operator()(const MidiEvent& event, const int& delay) { return (event.delay < delay); @@ -85,8 +84,7 @@ struct MidiEventComparator { } }; -template<> -struct MidiEventComparator { +struct MidiEventValueComparator { bool operator()(const MidiEvent& event, const float& value) { return (event.value < value); From 92634888821b6bd04ae1393712f54869a9e8506f Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 15:52:21 +0200 Subject: [PATCH 19/72] Get the event list from the midi state --- src/sfizz/MidiState.cpp | 8 ++++++++ src/sfizz/MidiState.h | 7 ++++--- src/sfizz/SfzHelpers.h | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index d08f7d10..c3f6f6cb 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -139,3 +139,11 @@ void sfz::MidiState::resetAllControllers(int delay) noexcept pitchBend = 0; } + +const sfz::EventVector& sfz::MidiState::getEvents(int ccIdx) const noexcept +{ + if (ccIdx < 0 || ccIdx > config::numCCs) + return nullEvent; + + return cc[ccIdx]; +} diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index e1183dda..d2f0ff4f 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -141,10 +141,9 @@ public: return validRange.clamp(value); } + const EventVector& getEvents(int ccIdx) const noexcept; + private: - template - using MidiNoteArray = std::array; - using EventVector = std::vector; int activeNotes { 0 }; /** @@ -168,6 +167,8 @@ private: * */ std::array cc; + + const EventVector nullEvent {{0, 0.0f}}; /** * Pitch bend status */ diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index becc30d5..e0a885d0 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -19,7 +19,8 @@ namespace sfz { using CCNamePair = std::pair; - +template +using MidiNoteArray = std::array; template struct CCValuePair { int cc; @@ -66,6 +67,7 @@ struct MidiEvent { int delay; float value; }; +using EventVector = std::vector; struct MidiEventDelayComparator { bool operator()(const MidiEvent& event, const int& delay) From 612265ebb5c9e3441b45c3abbd5b176c75ff1c59 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 16:08:55 +0200 Subject: [PATCH 20/72] If an event is registered at some delay, shadow the previous value --- src/sfizz/MidiState.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index c3f6f6cb..b4267f12 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -105,7 +105,10 @@ void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept { ASSERT(ccValue >= 0.0 && ccValue <= 1.0); const auto insertionPoint = absl::c_upper_bound(cc[ccNumber], delay, MidiEventDelayComparator{}); - cc[ccNumber].insert(insertionPoint, { delay, ccValue }); + if (insertionPoint == cc[ccNumber].end() || insertionPoint->delay != delay) + cc[ccNumber].insert(insertionPoint, { delay, ccValue }); + else + insertionPoint->value = ccValue; } float sfz::MidiState::getCCValue(int ccNumber) const noexcept From 42fa89ab5a6025568937f9291f398785bb55122a Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 16:25:58 +0200 Subject: [PATCH 21/72] Tentative replacement of the amplitude envelope --- src/sfizz/Region.cpp | 5 ++++- src/sfizz/Region.h | 2 +- src/sfizz/Voice.cpp | 41 +++++++++++++++++++++++++++++------------ tests/FilesT.cpp | 6 +++--- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index ff37c50d..b2ac77a8 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -299,7 +299,10 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("amplitude_cc&"): // fallthrough case hash("amplitude_oncc&"): - setCCPairFromOpcode(opcode, amplitudeCC, Default::amplitudeRange); + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) + amplitudeCC[opcode.parameters.back()] = *value; break; case hash("pan"): setValueFromOpcode(opcode, pan, Default::panRange); diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index a499a5eb..19b7f35d 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -277,7 +277,7 @@ struct Region { float width { Default::width }; // width float position { Default::position }; // position absl::optional> volumeCC; // volume_oncc - absl::optional> amplitudeCC; // amplitude_oncc + CCMap amplitudeCC { Default::amplitude }; // amplitude_oncc absl::optional> panCC; // pan_oncc absl::optional> widthCC; // width_oncc absl::optional> positionCC; // position_oncc diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index fe7df7d5..424c3076 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -85,11 +85,6 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, if (triggerType != TriggerType::CC) baseGain *= region->getNoteGain(number, value); - float gain { baseGain }; - if (region->amplitudeCC) - gain += resources.midiState.getCCValue(region->amplitudeCC->cc) * normalizePercents(region->amplitudeCC->value); - amplitudeEnvelope.reset(Default::normalizedRange.clamp(gain)); - float crossfadeGain { region->getCrossfadeGain() }; crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain)); @@ -203,11 +198,6 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept // TODO: this feels like a hack, revisit this along with the smoothed envelopes... delay = max(delay, minEnvelopeDelay); - if (region->amplitudeCC && ccNumber == region->amplitudeCC->cc) { - const float newGain { baseGain + ccValue * normalizePercents(region->amplitudeCC->value) }; - amplitudeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(newGain)); - } - if (region->volumeCC && ccNumber == region->volumeCC->cc) { const float newVolumedB { baseVolumedB + ccValue * region->volumeCC->value }; volumeEnvelope.registerEvent(delay, db2mag(Default::volumeRange.clamp(newVolumedB))); @@ -311,6 +301,31 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept this->triggerDelay = absl::nullopt; } +template +void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, absl::Span output, absl::Span temp) +{ + ASSERT(output.size() <= temp.size()); + sfz::fill(output, T{0.0}); + for (auto& mod : ccMods) { + const auto eventList = state.getEvents(mod.cc); + const auto modifier = static_cast(mod.value); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = eventList[0].value; + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (event.value - lastValue) * modifier / length; + lastValue = sfz::linearRamp(temp.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + sfz::fill(temp.subspan(lastDelay), lastValue * modifier); + sfz::add(temp, output); + } +} + void sfz::Voice::processMono(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); @@ -318,12 +333,13 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept auto rightBuffer = buffer.getSpan(1); auto modulationSpan = tempSpan1.first(numSamples); + auto tempSpan = tempSpan2.first(numSamples); { // Amplitude processing ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - amplitudeEnvelope.getBlock(modulationSpan); + getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, tempSpan); applyGain(modulationSpan, leftBuffer); // Crossfade envelope @@ -369,6 +385,7 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); auto modulationSpan = tempSpan1.first(numSamples); + auto tempSpan = tempSpan2.first(numSamples); auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); @@ -376,7 +393,7 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - amplitudeEnvelope.getBlock(modulationSpan); + getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, tempSpan); buffer.applyGain(modulationSpan); // Crossfade envelope diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 52d71e0d..497fab57 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -309,9 +309,9 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines") REQUIRE( synth.getRegionView(0)->keyRange.getEnd() == 52 ); REQUIRE( synth.getRegionView(1)->keyRange.getStart() == 57 ); REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 ); - REQUIRE( synth.getRegionView(2)->amplitudeCC ); - REQUIRE( synth.getRegionView(2)->amplitudeCC->cc == 10 ); - REQUIRE( synth.getRegionView(2)->amplitudeCC->value == 34.0f ); + REQUIRE( !synth.getRegionView(2)->amplitudeCC.empty() ); + REQUIRE( synth.getRegionView(2)->amplitudeCC.contains(10) ); + REQUIRE( synth.getRegionView(2)->amplitudeCC.getWithDefault(10) == 34.0f ); } TEST_CASE("[Files] Specific bug: relative path with backslashes") From af05e0cbde7124bbc2587f90fcc71629d89a2c92 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 17:11:50 +0200 Subject: [PATCH 22/72] Store the normalized CC modifier --- src/sfizz/Region.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index b2ac77a8..bca59664 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -302,7 +302,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()] = *value; + amplitudeCC[opcode.parameters.back()] = normalizePercents(*value); break; case hash("pan"): setValueFromOpcode(opcode, pan, Default::panRange); From 4b4dea97e1a4be17663cef618e32a187f6978d1d Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 17:12:25 +0200 Subject: [PATCH 23/72] Don't zero the modulation span --- src/sfizz/Voice.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 424c3076..7bdc40b7 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -305,7 +305,6 @@ template void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, absl::Span output, absl::Span temp) { ASSERT(output.size() <= temp.size()); - sfz::fill(output, T{0.0}); for (auto& mod : ccMods) { const auto eventList = state.getEvents(mod.cc); const auto modifier = static_cast(mod.value); @@ -339,6 +338,7 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope + fill(modulationSpan, baseGain); getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, tempSpan); applyGain(modulationSpan, leftBuffer); @@ -393,6 +393,7 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope + fill(modulationSpan, baseGain); getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, tempSpan); buffer.applyGain(modulationSpan); From 0cf7b01d6bbb030a953885b92fff105af91d8664 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 17:12:41 +0200 Subject: [PATCH 24/72] update the tests for the normalized modifier --- tests/FilesT.cpp | 2 +- tests/RegionT.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 497fab57..8d44b566 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -311,7 +311,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) == 34.0f ); + REQUIRE( synth.getRegionView(2)->amplitudeCC.getWithDefault(10) == 0.34f ); } TEST_CASE("[Files] Specific bug: relative path with backslashes") diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index c0b83dd4..1beebaa6 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -1430,6 +1430,17 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "note_selfmask", "garbage" }); REQUIRE(region.selfMask == SfzSelfMask::dontMask); } + + SECTION("amplitude_cc") + { + REQUIRE(region.amplitudeCC.empty()); + region.parseOpcode({ "amplitude_cc1", "40" }); + REQUIRE(region.amplitudeCC.contains(1)); + REQUIRE(region.amplitudeCC[1] == 0.40_a); + region.parseOpcode({ "amplitude_oncc2", "30" }); + REQUIRE(region.amplitudeCC.contains(2)); + REQUIRE(region.amplitudeCC[2] == 0.30_a); + } } // Specific region bugs From 406b7a08d7da2e69725cde9241664d84eb1b8a1c Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 17:12:52 +0200 Subject: [PATCH 25/72] Add a bufferpool to the resources --- src/sfizz/BufferPool.h | 46 ++++++++++++++++++++++++++++++++++++++++++ src/sfizz/Config.h | 1 + src/sfizz/Resources.h | 2 ++ 3 files changed, 49 insertions(+) create mode 100644 src/sfizz/BufferPool.h diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h new file mode 100644 index 00000000..46c433e9 --- /dev/null +++ b/src/sfizz/BufferPool.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "Config.h" +#include "Buffer.h" +#include +#include + +namespace sfz +{ + +class BufferPool +{ +public: + BufferPool() + { + + } + void setBufferSize(unsigned bufferSize) + { + for (auto& buffer: buffers) { + // Trying to resize a buffer in use + ASSERT(buffer.use_count() != 1); + buffer->resize(bufferSize); + } + } + + std::shared_ptr> getBuffer() + { + auto bufferIt = buffers.begin(); + while (bufferIt < buffers.end()) { + if (bufferIt->use_count() == 1) + return *bufferIt; + } + + // No buffer found; debug message + DBG("[sfizz] No free buffer available!"); + } +private: + std::array>, config::bufferPoolSize> buffers; +}; +} diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 0af2263f..2b81db1f 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -28,6 +28,7 @@ namespace config { constexpr float defaultSampleRate { 48000 }; constexpr int defaultSamplesPerBlock { 1024 }; constexpr int maxBlockSize { 8192 }; + constexpr int bufferPoolSize { 16 }; constexpr int preloadSize { 8192 }; constexpr int loggerQueueSize { 256 }; constexpr int voiceLoggerQueueSize { 256 }; diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index 99bc9dac..e129b355 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -6,6 +6,7 @@ #pragma once #include "FilePool.h" +#include "BufferPool.h" #include "FilterPool.h" #include "EQPool.h" #include "Logger.h" @@ -17,6 +18,7 @@ class WavetableMulti; struct Resources { + BufferPool bufferPool; MidiState midiState; Logger logger; FilePool filePool { logger }; From 7cb92e05d205105f257021e3ca928d877dc5c815 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 17:16:29 +0200 Subject: [PATCH 26/72] Use an intermediate method to set the sample rate and block size for the resources --- src/sfizz/Resources.h | 13 +++++++++++++ src/sfizz/Synth.cpp | 6 ++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index e129b355..1367d714 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -25,5 +25,18 @@ struct Resources FilterPool filterPool { midiState }; EQPool eqPool { midiState }; WavetablePool wavePool; + + void setSampleRate(float samplerate) + { + midiState.setSampleRate(samplerate); + filterPool.setSampleRate(samplerate); + eqPool.setSampleRate(samplerate); + } + + void setSamplesPerBlock(int samplesPerBlock) + { + bufferPool.setBufferSize(samplesPerBlock); + midiState.setSamplesPerBlock(samplesPerBlock); + } }; } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 2c701e19..4e68bbaf 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -510,7 +510,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); - resources.midiState.setSamplesPerBlock(samplesPerBlock); + resources.setSamplesPerBlock(samplesPerBlock); for (auto& bus : effectBuses) { if (bus) @@ -529,9 +529,7 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept for (auto& voice : voices) voice->setSampleRate(sampleRate); - resources.filterPool.setSampleRate(sampleRate); - resources.eqPool.setSampleRate(sampleRate); - resources.midiState.setSampleRate(sampleRate); + resources.setSampleRate(sampleRate); for (auto& bus : effectBuses) { if (bus) From 5e8cb7de0ae8e22eeb784e2fb03026209ad8df79 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 17:37:30 +0200 Subject: [PATCH 27/72] Improve the bufferpool --- src/sfizz/BufferPool.h | 56 ++++++++++++++++++++++++++++++++++++++++-- src/sfizz/Config.h | 1 + 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index 46c433e9..8bcb6dcf 100644 --- a/src/sfizz/BufferPool.h +++ b/src/sfizz/BufferPool.h @@ -7,6 +7,7 @@ #pragma once #include "Config.h" #include "Buffer.h" +#include "AudioBuffer.h" #include #include @@ -18,29 +19,80 @@ class BufferPool public: BufferPool() { + for (auto& buffer : buffers) { + buffer = std::make_shared>(config::defaultSamplesPerBlock); + } + for (auto& buffer : stereoBuffers) { + buffer = std::make_shared>(2, config::defaultSamplesPerBlock); + } } + void setBufferSize(unsigned bufferSize) { for (auto& buffer: buffers) { // Trying to resize a buffer in use - ASSERT(buffer.use_count() != 1); + ASSERT(buffer.use_count() == 1); + buffer->resize(bufferSize); + } + + for (auto& buffer: stereoBuffers) { + // Trying to resize a buffer in use + ASSERT(buffer.use_count() == 1); buffer->resize(bufferSize); } } - std::shared_ptr> getBuffer() + std::shared_ptr> getBuffer(size_t numFrames) { auto bufferIt = buffers.begin(); + + if (buffers.empty()) { + DBG("[sfizz] No available buffers in the pool"); + return {}; + } + + if (buffers[0]->size() < numFrames) { + DBG("[sfizz] Someone asked for a buffer of size " << numFrames << "; only " << buffers[0]->size() << " available..."); + return {}; + } + while (bufferIt < buffers.end()) { if (bufferIt->use_count() == 1) return *bufferIt; + ++bufferIt; } // No buffer found; debug message DBG("[sfizz] No free buffer available!"); + return {}; + } + + std::shared_ptr> getStereoBuffer(size_t numFrames) + { + if (stereoBuffers.empty()) { + DBG("[sfizz] No available stereoBuffers in the pool"); + return {}; + } + + if (stereoBuffers[0]->getNumFrames() < numFrames) { + DBG("[sfizz] Someone asked for a buffer of size " << numFrames << "; only " << stereoBuffers[0]->getNumFrames() << " available..."); + return {}; + } + + auto bufferIt = stereoBuffers.begin(); + while (bufferIt < stereoBuffers.end()) { + if (bufferIt->use_count() == 1) + return *bufferIt; + ++bufferIt; + } + + // No buffer found; debug message + DBG("[sfizz] No free buffer available!"); + return {}; } private: std::array>, config::bufferPoolSize> buffers; + std::array>, config::stereoBufferPoolSize> stereoBuffers; }; } diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 2b81db1f..e4a5aa2a 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -29,6 +29,7 @@ namespace config { constexpr int defaultSamplesPerBlock { 1024 }; constexpr int maxBlockSize { 8192 }; constexpr int bufferPoolSize { 16 }; + constexpr int stereoBufferPoolSize { 4 }; constexpr int preloadSize { 8192 }; constexpr int loggerQueueSize { 256 }; constexpr int voiceLoggerQueueSize { 256 }; From 1a55b42bd78921cfba5577b3fdb12849d13238c1 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 17:37:40 +0200 Subject: [PATCH 28/72] Use the bufferpool in the synth --- src/sfizz/Synth.cpp | 14 ++++++++++---- src/sfizz/Synth.h | 4 ---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 4e68bbaf..ec44a516 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -506,10 +506,9 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept } this->samplesPerBlock = samplesPerBlock; - this->tempBuffer.resize(samplesPerBlock); - this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); + resources.setSamplesPerBlock(samplesPerBlock); for (auto& bus : effectBuses) { @@ -549,8 +548,15 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept return; size_t numFrames = buffer.getNumFrames(); - auto temp = AudioSpan(tempBuffer).first(numFrames); - auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); + auto tempBuffer = resources.bufferPool.getStereoBuffer(numFrames); + auto tempMixNodeBuffer = resources.bufferPool.getStereoBuffer(numFrames); + if (!tempBuffer || !tempMixNodeBuffer) { + DBG("[sfizz] Could not get a temporary buffer; exiting callback... "); + return; + } + + auto temp = AudioSpan(*tempBuffer).first(numFrames); + auto tempMixNode = AudioSpan(*tempMixNodeBuffer).first(numFrames); CallbackBreakdown callbackBreakdown; diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 2eded30a..a9ce64ab 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -516,10 +516,6 @@ private: // Curves CurveSet curves; - // Intermediate buffers - AudioBuffer tempBuffer { 2, config::defaultSamplesPerBlock }; - AudioBuffer tempMixNodeBuffer { 2, config::defaultSamplesPerBlock }; - int samplesPerBlock { config::defaultSamplesPerBlock }; float sampleRate { config::defaultSampleRate }; float volume { Default::globalVolume }; From c1266e5d70f8d7a91de0b9bc67cd8371e175702f Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 17:54:47 +0200 Subject: [PATCH 29/72] Added debug introspection about the max number of buffer used --- src/sfizz/BufferPool.h | 83 +++++++++++++++++++++++++++++++++++++++--- src/sfizz/Config.h | 1 + 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index 8bcb6dcf..752cdfce 100644 --- a/src/sfizz/BufferPool.h +++ b/src/sfizz/BufferPool.h @@ -6,10 +6,15 @@ #pragma once #include "Config.h" +#include "Debug.h" #include "Buffer.h" #include "AudioBuffer.h" #include #include +#ifndef NDEBUG + #include "absl/algorithm/container.h" + #include "MathHelpers.h" +#endif namespace sfz { @@ -23,6 +28,10 @@ public: buffer = std::make_shared>(config::defaultSamplesPerBlock); } + for (auto& buffer : indexBuffers) { + buffer = std::make_shared>(config::defaultSamplesPerBlock); + } + for (auto& buffer : stereoBuffers) { buffer = std::make_shared>(2, config::defaultSamplesPerBlock); } @@ -36,6 +45,12 @@ public: buffer->resize(bufferSize); } + for (auto& buffer: indexBuffers) { + // Trying to resize a buffer in use + ASSERT(buffer.use_count() == 1); + buffer->resize(bufferSize); + } + for (auto& buffer: stereoBuffers) { // Trying to resize a buffer in use ASSERT(buffer.use_count() == 1); @@ -43,7 +58,7 @@ public: } } - std::shared_ptr> getBuffer(size_t numFrames) + std::shared_ptr> getBuffer(size_t numFrames) const { auto bufferIt = buffers.begin(); @@ -57,6 +72,12 @@ public: return {}; } +#ifndef NDEBUG + maxBuffersUsed = max(1 + absl::c_count_if(buffers, [&](const std::shared_ptr>& buffer) { + return (buffer.use_count() > 1); + }), maxBuffersUsed); +#endif + while (bufferIt < buffers.end()) { if (bufferIt->use_count() == 1) return *bufferIt; @@ -68,18 +89,53 @@ public: return {}; } - std::shared_ptr> getStereoBuffer(size_t numFrames) + std::shared_ptr> getIndexBuffer(size_t numFrames) const + { + auto bufferIt = indexBuffers.begin(); + + if (indexBuffers.empty()) { + DBG("[sfizz] No available index buffers in the pool"); + return {}; + } + + if (indexBuffers[0]->size() < numFrames) { + DBG("[sfizz] Someone asked for a index buffer of size " << numFrames << "; only " << indexBuffers[0]->size() << " available..."); + return {}; + } + +#ifndef NDEBUG + maxIndexBuffersUsed = max(1 + absl::c_count_if(indexBuffers, [&](const std::shared_ptr>& buffer) { + return (buffer.use_count() > 1); + }), maxIndexBuffersUsed); +#endif + + while (bufferIt < indexBuffers.end()) { + if (bufferIt->use_count() == 1) + return *bufferIt; + ++bufferIt; + } + + // No buffer found; debug message + DBG("[sfizz] No free index buffer available!"); + return {}; + } + + std::shared_ptr> getStereoBuffer(size_t numFrames) const { if (stereoBuffers.empty()) { - DBG("[sfizz] No available stereoBuffers in the pool"); + DBG("[sfizz] No available stereo buffers in the pool"); return {}; } if (stereoBuffers[0]->getNumFrames() < numFrames) { - DBG("[sfizz] Someone asked for a buffer of size " << numFrames << "; only " << stereoBuffers[0]->getNumFrames() << " available..."); + DBG("[sfizz] Someone asked for a stereo buffer of size " << numFrames << "; only " << stereoBuffers[0]->getNumFrames() << " available..."); return {}; } - +#ifndef NDEBUG + maxStereoBuffersUsed = max(1 + absl::c_count_if(stereoBuffers, [&](const std::shared_ptr>& buffer) { + return (buffer.use_count() > 1); + }), maxStereoBuffersUsed); +#endif auto bufferIt = stereoBuffers.begin(); while (bufferIt < stereoBuffers.end()) { if (bufferIt->use_count() == 1) @@ -88,11 +144,26 @@ public: } // No buffer found; debug message - DBG("[sfizz] No free buffer available!"); + DBG("[sfizz] No free stereo buffer available!"); return {}; } + +#ifndef NDEBUG + ~BufferPool() + { + DBG("Max buffers used: " << maxBuffersUsed); + DBG("Max index buffers used: " << maxIndexBuffersUsed); + DBG("Max stereo buffers used: " << maxStereoBuffersUsed); + } +#endif private: std::array>, config::bufferPoolSize> buffers; + std::array>, config::bufferPoolSize> indexBuffers; std::array>, config::stereoBufferPoolSize> stereoBuffers; +#ifndef NDEBUG + mutable int maxBuffersUsed { 0 }; + mutable int maxIndexBuffersUsed { 0 }; + mutable int maxStereoBuffersUsed { 0 }; +#endif }; } diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index e4a5aa2a..56bfd12c 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -30,6 +30,7 @@ namespace config { constexpr int maxBlockSize { 8192 }; constexpr int bufferPoolSize { 16 }; constexpr int stereoBufferPoolSize { 4 }; + constexpr int indexBufferPoolSize { 2 }; constexpr int preloadSize { 8192 }; constexpr int loggerQueueSize { 256 }; constexpr int voiceLoggerQueueSize { 256 }; From 8ed3d16b8c24668a2d3986143dc45a673d1a7f03 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 18:09:39 +0200 Subject: [PATCH 30/72] Use the bufferpool in the voices --- src/sfizz/Voice.cpp | 56 ++++++++++++++++++++++++++++----------------- src/sfizz/Voice.h | 9 -------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 7bdc40b7..bcc6fa52 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -257,14 +257,6 @@ void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept { this->samplesPerBlock = samplesPerBlock; this->minEnvelopeDelay = samplesPerBlock / 2; - tempBuffer1.resize(samplesPerBlock); - tempBuffer2.resize(samplesPerBlock); - tempBuffer3.resize(samplesPerBlock); - indexBuffer.resize(samplesPerBlock); - tempSpan1 = absl::MakeSpan(tempBuffer1); - tempSpan2 = absl::MakeSpan(tempBuffer2); - tempSpan3 = absl::MakeSpan(tempBuffer3); - indexSpan = absl::MakeSpan(indexBuffer); } void sfz::Voice::renderBlock(AudioSpan buffer) noexcept @@ -331,8 +323,12 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); - auto modulationSpan = tempSpan1.first(numSamples); - auto tempSpan = tempSpan2.first(numSamples); + auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!modulationBuffer || !tempBuffer) + return; + auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); { // Amplitude processing ScopedTiming logger { amplitudeDuration }; @@ -384,11 +380,16 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept void sfz::Voice::processStereo(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); - auto modulationSpan = tempSpan1.first(numSamples); - auto tempSpan = tempSpan2.first(numSamples); auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); + auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!modulationBuffer || !tempBuffer) + return; + auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + { // Amplitude processing ScopedTiming logger { amplitudeDuration }; @@ -442,7 +443,8 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept void sfz::Voice::fillWithData(AudioSpan buffer) noexcept { - if (buffer.getNumFrames() == 0) + const auto numSamples = buffer.getNumFrames(); + if (numSamples == 0) return; if (currentPromise == nullptr) { @@ -451,11 +453,18 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept } auto source = currentPromise->getData(); - auto indices = indexSpan.first(buffer.getNumFrames()); - auto jumps = tempSpan1.first(buffer.getNumFrames()); - auto bends = tempSpan2.first(buffer.getNumFrames()); - auto leftCoeffs = tempSpan1.first(buffer.getNumFrames()); - auto rightCoeffs = tempSpan2.first(buffer.getNumFrames()); + auto jumpBuffer = resources.bufferPool.getBuffer(numSamples); + auto bendBuffer = resources.bufferPool.getBuffer(numSamples); + auto leftCoeffBuffer = resources.bufferPool.getBuffer(numSamples); + auto rightCoeffBuffer = resources.bufferPool.getBuffer(numSamples); + auto indexBuffer = resources.bufferPool.getIndexBuffer(numSamples); + if (!jumpBuffer || !bendBuffer || !indexBuffer || !rightCoeffBuffer || !leftCoeffBuffer) + return; + auto jumps = absl::MakeSpan(*jumpBuffer).first(numSamples); + auto bends = absl::MakeSpan(*bendBuffer).first(numSamples); + auto indices = absl::MakeSpan(*indexBuffer).first(numSamples); + auto leftCoeffs = absl::MakeSpan(*leftCoeffBuffer).first(numSamples); + auto rightCoeffs = absl::MakeSpan(*rightCoeffBuffer).first(numSamples); fill(jumps, pitchRatio * speedRatio); if (region->bendStep > 1) @@ -530,13 +539,18 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept const auto leftSpan = buffer.getSpan(0); const auto rightSpan = buffer.getSpan(1); + if (region->sample == "*noise") { absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); }); absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); }); } else { - // wavetables for sine and other generators - auto frequencies = tempSpan1.first(buffer.getNumFrames()); - auto bends = tempSpan2.first(buffer.getNumFrames()); + const auto numSamples = buffer.getNumFrames(); + auto frequencyBuffer = resources.bufferPool.getBuffer(numSamples); + auto bendBuffer = resources.bufferPool.getBuffer(numSamples); + if (!frequencyBuffer || !bendBuffer) + return; + auto frequencies = absl::MakeSpan(*frequencyBuffer).first(numSamples); + auto bends = absl::MakeSpan(*bendBuffer).first(numSamples); float keycenterFrequency = midiNoteFrequency(region->pitchKeycenter); fill(frequencies, pitchRatio * keycenterFrequency); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 4227c326..7e8a9a97 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -280,15 +280,6 @@ private: FilePromisePtr currentPromise { nullptr }; - Buffer tempBuffer1; - Buffer tempBuffer2; - Buffer tempBuffer3; - Buffer indexBuffer; - absl::Span tempSpan1 { absl::MakeSpan(tempBuffer1) }; - absl::Span tempSpan2 { absl::MakeSpan(tempBuffer2) }; - absl::Span tempSpan3 { absl::MakeSpan(tempBuffer3) }; - absl::Span indexSpan { absl::MakeSpan(indexBuffer) }; - int samplesPerBlock { config::defaultSamplesPerBlock }; int minEnvelopeDelay { config::defaultSamplesPerBlock / 2 }; float sampleRate { config::defaultSampleRate }; From 16472b4ef9c1ab07acf5c4fb700c202704477d97 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 18:09:48 +0200 Subject: [PATCH 31/72] Reduce the max number of buffers --- src/sfizz/Config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 56bfd12c..51ac5c1b 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -28,7 +28,7 @@ namespace config { constexpr float defaultSampleRate { 48000 }; constexpr int defaultSamplesPerBlock { 1024 }; constexpr int maxBlockSize { 8192 }; - constexpr int bufferPoolSize { 16 }; + constexpr int bufferPoolSize { 8 }; constexpr int stereoBufferPoolSize { 4 }; constexpr int indexBufferPoolSize { 2 }; constexpr int preloadSize { 8192 }; From 394512183a54ef15f93a8fc0b005d746570b01ce Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 18:50:54 +0200 Subject: [PATCH 32/72] Move crossfades to SfzHelpers --- src/sfizz/Region.cpp | 42 ---------------------------------- src/sfizz/SfzHelpers.h | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index bca59664..10b7b7ec 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1016,48 +1016,6 @@ uint32_t sfz::Region::loopEnd(Oversampling factor) const noexcept return loopRange.getEnd() * static_cast(factor); } -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; -} - -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; -} - float sfz::Region::getNoteGain(int noteNumber, float velocity) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index e0a885d0..0d18a465 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -14,6 +14,7 @@ #include "Config.h" #include "MathHelpers.h" #include "absl/meta/type_traits.h" +#include "Defaults.h" namespace sfz { @@ -305,5 +306,55 @@ inline CXX14_CONSTEXPR void multiplyByCents(float& base, int modifier) base *= centsFactor(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; +} + } // namespace sfz From d59d29c72b4e741e36960fc0c4d691c21d0ca376 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 19:08:47 +0200 Subject: [PATCH 33/72] Use the full modifier on amplitude and crossfades --- src/sfizz/Voice.cpp | 64 ++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index bcc6fa52..ee93a3d3 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -85,9 +85,6 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, if (triggerType != TriggerType::CC) baseGain *= region->getNoteGain(number, value); - float crossfadeGain { region->getCrossfadeGain() }; - crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain)); - basePan = normalizePercents(region->pan); auto pan = basePan; if (region->panCC) @@ -217,11 +214,6 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept const float newWidth { baseWidth + ccValue * normalizePercents(region->widthCC->value) }; widthEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newWidth)); } - - if (region->crossfadeCCInRange.contains(ccNumber) || region->crossfadeCCOutRange.contains(ccNumber)) { - const float crossfadeGain = region->getCrossfadeGain(); - crossfadeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(crossfadeGain)); - } } void sfz::Voice::registerPitchWheel(int delay, int pitch) noexcept @@ -294,26 +286,23 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept } template -void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, absl::Span output, absl::Span temp) +void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, absl::Span temp, std::function function) { - ASSERT(output.size() <= temp.size()); for (auto& mod : ccMods) { const auto eventList = state.getEvents(mod.cc); - const auto modifier = static_cast(mod.value); ASSERT(eventList.size() > 0); ASSERT(eventList[0].delay == 0); - auto lastValue = eventList[0].value; + auto lastValue = function(mod.value, eventList[0].value); auto lastDelay = eventList[0].delay; for (unsigned i = 1; i < eventList.size(); ++ i) { const auto event = eventList[i]; const auto length = event.delay - lastDelay; - const auto step = (event.value - lastValue) * modifier / length; - lastValue = sfz::linearRamp(temp.subspan(lastDelay, length), lastValue, step); + const auto step = (function(mod.value, event.value) - lastValue)/ length; + lastValue = sfz::linearRamp(temp.subspan(lastDelay, length), lastValue, step); lastDelay += length; } - sfz::fill(temp.subspan(lastDelay), lastValue * modifier); - sfz::add(temp, output); + sfz::fill(temp.subspan(lastDelay), lastValue); } } @@ -334,13 +323,30 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - fill(modulationSpan, baseGain); - getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, tempSpan); + fill(modulationSpan, 0.0f); + getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, [](const float& modifier, float value){ + return value * modifier; + }); + DBG("Amplitude curve back: " << modulationSpan.back()); + add(baseGain, modulationSpan); + DBG("FInal gain: " << modulationSpan.back()); applyGain(modulationSpan, leftBuffer); - // Crossfade envelope - crossfadeEnvelope.getBlock(modulationSpan); + // Crossfade envelopes + // crossfadeEnvelope.getBlock(modulationSpan); + fill(modulationSpan, 1.0f); + getLinearEnvelope>(region->crossfadeCCInRange, resources.midiState, modulationSpan, [this](const Range& range, float value){ + return crossfadeIn(range, value, region->crossfadeCCCurve); + }); applyGain(modulationSpan, leftBuffer); + DBG("XFin: " << modulationSpan.back()); + + fill(modulationSpan, 1.0f); + getLinearEnvelope>(region->crossfadeCCOutRange, resources.midiState, modulationSpan, [this](const Range& range, float value){ + return crossfadeOut(range, value, region->crossfadeCCCurve); + }); + applyGain(modulationSpan, leftBuffer); + DBG("XFout: " << modulationSpan.back()); // Volume envelope volumeEnvelope.getBlock(modulationSpan); @@ -394,12 +400,22 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - fill(modulationSpan, baseGain); - getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, tempSpan); + fill(modulationSpan, 0.0f); + getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); + add(baseGain, modulationSpan); buffer.applyGain(modulationSpan); - // Crossfade envelope - crossfadeEnvelope.getBlock(modulationSpan); + // Crossfade envelopes + fill(modulationSpan, 1.0f); + getLinearEnvelope>(region->crossfadeCCInRange, resources.midiState, modulationSpan, [this](const Range& range, float value){ + return crossfadeIn(range, value, region->crossfadeCCCurve); + }); + buffer.applyGain(modulationSpan); + + fill(modulationSpan, 1.0f); + getLinearEnvelope>(region->crossfadeCCOutRange, resources.midiState, modulationSpan, [this](const Range& range, float value){ + return crossfadeOut(range, value, region->crossfadeCCCurve); + }); buffer.applyGain(modulationSpan); // Volume envelope From 97234177a120c4fe5b64e885e8c72f46bc487509 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 19:22:19 +0200 Subject: [PATCH 34/72] Use modifiers for panning/width --- src/sfizz/Defaults.h | 3 ++- src/sfizz/Region.cpp | 24 +++++++++++++++------ src/sfizz/Region.h | 8 +++---- src/sfizz/Voice.cpp | 50 ++++++++++++-------------------------------- src/sfizz/Voice.h | 3 --- 5 files changed, 37 insertions(+), 51 deletions(-) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 5ceaa784..e76a6805 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -54,9 +54,10 @@ namespace Default constexpr SfzLoopMode loopMode { SfzLoopMode::no_loop }; constexpr Range loopRange { 0, std::numeric_limits::max() }; - // Global ranges + // common defaults constexpr Range midi7Range { 0, 127 }; constexpr Range normalizedRange { 0.0f, 1.0f }; + constexpr float zeroModifier { 0.0f }; // Wavetable oscillator constexpr float oscillatorPhase { 0.0 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 10b7b7ec..cd78075a 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -305,22 +305,34 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) amplitudeCC[opcode.parameters.back()] = normalizePercents(*value); break; case hash("pan"): - setValueFromOpcode(opcode, pan, Default::panRange); + if (auto value = readOpcode(opcode.value, Default::panRange)) + pan = normalizePercents(*value); break; case hash("pan_oncc&"): - setCCPairFromOpcode(opcode, panCC, Default::panCCRange); + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::panCCRange)) + panCC[opcode.parameters.back()] = normalizePercents(*value); break; case hash("position"): - setValueFromOpcode(opcode, position, Default::positionRange); + if (auto value = readOpcode(opcode.value, Default::positionRange)) + position = normalizePercents(*value); break; case hash("position_oncc&"): - setCCPairFromOpcode(opcode, positionCC, Default::positionCCRange); + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::positionCCRange)) + positionCC[opcode.parameters.back()] = normalizePercents(*value); break; case hash("width"): - setValueFromOpcode(opcode, width, Default::widthRange); + if (auto value = readOpcode(opcode.value, Default::widthRange)) + width = normalizePercents(*value); break; case hash("width_oncc&"): - setCCPairFromOpcode(opcode, widthCC, Default::widthCCRange); + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::widthCCRange)) + widthCC[opcode.parameters.back()] = normalizePercents(*value); break; case hash("amp_keycenter"): setValueFromOpcode(opcode, ampKeycenter, Default::keyRange); diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 19b7f35d..f9c560cd 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -277,10 +277,10 @@ struct Region { float width { Default::width }; // width float position { Default::position }; // position absl::optional> volumeCC; // volume_oncc - CCMap amplitudeCC { Default::amplitude }; // amplitude_oncc - absl::optional> panCC; // pan_oncc - absl::optional> widthCC; // width_oncc - absl::optional> positionCC; // position_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 uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter float ampKeytrack { Default::ampKeytrack }; // amp_keytrack float ampVeltrack { Default::ampVeltrack }; // amp_keytrack diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index ee93a3d3..596250a8 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -85,24 +85,6 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, if (triggerType != TriggerType::CC) baseGain *= region->getNoteGain(number, value); - basePan = normalizePercents(region->pan); - auto pan = basePan; - if (region->panCC) - pan += resources.midiState.getCCValue(region->panCC->cc) * normalizePercents(region->panCC->value); - panEnvelope.reset(Default::symmetricNormalizedRange.clamp(pan)); - - basePosition = normalizePercents(region->position); - auto position = basePosition; - if (region->positionCC) - position += resources.midiState.getCCValue(region->positionCC->cc) * normalizePercents(region->positionCC->value); - positionEnvelope.reset(Default::symmetricNormalizedRange.clamp(position)); - - baseWidth = normalizePercents(region->width); - auto width = baseWidth; - if (region->widthCC) - width += resources.midiState.getCCValue(region->widthCC->cc) * normalizePercents(region->widthCC->value); - widthEnvelope.reset(Default::symmetricNormalizedRange.clamp(width)); - pitchBendEnvelope.setFunction([region](float pitchValue){ const auto normalizedBend = normalizeBend(pitchValue); const auto bendInCents = normalizedBend > 0.0f ? normalizedBend * static_cast(region->bendUp) : -normalizedBend * static_cast(region->bendDown); @@ -199,21 +181,6 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept const float newVolumedB { baseVolumedB + ccValue * region->volumeCC->value }; volumeEnvelope.registerEvent(delay, db2mag(Default::volumeRange.clamp(newVolumedB))); } - - if (region->panCC && ccNumber == region->panCC->cc) { - const float newPan { basePan + ccValue * normalizePercents(region->panCC->value) }; - panEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPan)); - } - - if (region->positionCC && ccNumber == region->positionCC->cc) { - const float newPosition { basePosition + ccValue * normalizePercents(region->positionCC->value) }; - positionEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPosition)); - } - - if (region->widthCC && ccNumber == region->widthCC->cc) { - const float newWidth { baseWidth + ccValue * normalizePercents(region->widthCC->value) }; - widthEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newWidth)); - } } void sfz::Voice::registerPitchWheel(int delay, int pitch) noexcept @@ -378,7 +345,9 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept copy(leftBuffer, rightBuffer); // Apply panning - panEnvelope.getBlock(modulationSpan); + fill(modulationSpan, 0.0f); + getLinearEnvelope(region->panCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); + add(region->pan, modulationSpan); pan(modulationSpan, leftBuffer, rightBuffer); } } @@ -431,13 +400,20 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { panningDuration }; // Apply panning - panEnvelope.getBlock(modulationSpan); + fill(modulationSpan, 0.0f); + getLinearEnvelope(region->panCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); + add(region->pan, modulationSpan); pan(modulationSpan, leftBuffer, rightBuffer); // Apply the width/position process - widthEnvelope.getBlock(modulationSpan); + fill(modulationSpan, 0.0f); + getLinearEnvelope(region->widthCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); + add(region->width, modulationSpan); width(modulationSpan, leftBuffer, rightBuffer); - positionEnvelope.getBlock(modulationSpan); + + fill(modulationSpan, 0.0f); + getLinearEnvelope(region->positionCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); + add(region->position, modulationSpan); pan(modulationSpan, leftBuffer, rightBuffer); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 7e8a9a97..0d51cb3e 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -268,9 +268,6 @@ private: float pitchRatio { 1.0 }; float baseVolumedB{ 0.0 }; float baseGain { 1.0 }; - float basePan { 0.0 }; - float basePosition { 0.0 }; - float baseWidth { 0.0 }; float baseFrequency { 440.0 }; float phase { 0.0f }; From ca2ee8dc58bd890c300f71fdd9725683e999f8da Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 19:26:08 +0200 Subject: [PATCH 35/72] Voice cleanups --- src/sfizz/Voice.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 596250a8..8b22323a 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -280,11 +280,9 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept auto rightBuffer = buffer.getSpan(1); auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!modulationBuffer || !tempBuffer) + if (!modulationBuffer) return; auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); { // Amplitude processing ScopedTiming logger { amplitudeDuration }; @@ -294,9 +292,9 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, [](const float& modifier, float value){ return value * modifier; }); - DBG("Amplitude curve back: " << modulationSpan.back()); + // DBG("Amplitude curve back: " << modulationSpan.back()); add(baseGain, modulationSpan); - DBG("FInal gain: " << modulationSpan.back()); + // DBG("Final gain: " << modulationSpan.back()); applyGain(modulationSpan, leftBuffer); // Crossfade envelopes @@ -306,14 +304,14 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept return crossfadeIn(range, value, region->crossfadeCCCurve); }); applyGain(modulationSpan, leftBuffer); - DBG("XFin: " << modulationSpan.back()); + // DBG("XFin: " << modulationSpan.back()); fill(modulationSpan, 1.0f); getLinearEnvelope>(region->crossfadeCCOutRange, resources.midiState, modulationSpan, [this](const Range& range, float value){ return crossfadeOut(range, value, region->crossfadeCCCurve); }); applyGain(modulationSpan, leftBuffer); - DBG("XFout: " << modulationSpan.back()); + // DBG("XFout: " << modulationSpan.back()); // Volume envelope volumeEnvelope.getBlock(modulationSpan); @@ -359,11 +357,9 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept auto rightBuffer = buffer.getSpan(1); auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!modulationBuffer || !tempBuffer) + if (!modulationBuffer) return; auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); { // Amplitude processing ScopedTiming logger { amplitudeDuration }; From 9e824df3469529762c314fa0aba8ffe77be0281e Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 19:32:27 +0200 Subject: [PATCH 36/72] Homogeneize the treatment of percentage defaults and values --- src/sfizz/Region.cpp | 5 +++-- src/sfizz/Region.h | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index cd78075a..a499bc10 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -295,7 +295,8 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) setCCPairFromOpcode(opcode, volumeCC, Default::volumeCCRange); break; case hash("amplitude"): - setValueFromOpcode(opcode, amplitude, Default::amplitudeRange); + if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) + amplitude = normalizePercents(*value); break; case hash("amplitude_cc&"): // fallthrough case hash("amplitude_oncc&"): @@ -985,7 +986,7 @@ float sfz::Region::getBaseVolumedB(int noteNumber) const noexcept float sfz::Region::getBaseGain() const noexcept { - return normalizePercents(amplitude); + return amplitude; } float sfz::Region::getPhase() const noexcept diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index f9c560cd..3aac8ff8 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -272,10 +272,10 @@ struct Region { // Performance parameters: amplifier float volume { Default::volume }; // volume - float amplitude { Default::amplitude }; // amplitude - float pan { Default::pan }; // pan - float width { Default::width }; // width - float position { Default::position }; // position + float amplitude { normalizePercents(Default::amplitude) }; // amplitude + float pan { normalizePercents(Default::pan) }; // pan + float width { normalizePercents(Default::width) }; // width + float position { normalizePercents(Default::position) }; // position absl::optional> volumeCC; // volume_oncc CCMap amplitudeCC { Default::zeroModifier }; // amplitude_oncc CCMap panCC { Default::zeroModifier }; // pan_oncc From 5d499fb563515e76bc062f24246d4aee7ecdb98b Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 19:34:45 +0200 Subject: [PATCH 37/72] Update tests --- tests/FilesT.cpp | 18 +++++++-------- tests/RegionT.cpp | 58 +++++++++++++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 8d44b566..a0fd78e1 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -152,37 +152,37 @@ TEST_CASE("[Files] Full hierarchy") synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz"); REQUIRE(synth.getNumRegions() == 8); for (int i = 0; i < synth.getNumRegions(); ++i) { - REQUIRE(synth.getRegionView(i)->width == 40.0f); + REQUIRE(synth.getRegionView(i)->width == 0.4_a); } - REQUIRE(synth.getRegionView(0)->pan == 30.0f); + REQUIRE(synth.getRegionView(0)->pan == 0.3_a); REQUIRE(synth.getRegionView(0)->delay == 67); REQUIRE(synth.getRegionView(0)->keyRange == sfz::Range(60, 60)); - REQUIRE(synth.getRegionView(1)->pan == 30.0f); + REQUIRE(synth.getRegionView(1)->pan == 0.3_a); REQUIRE(synth.getRegionView(1)->delay == 67); REQUIRE(synth.getRegionView(1)->keyRange == sfz::Range(61, 61)); - REQUIRE(synth.getRegionView(2)->pan == 30.0f); + REQUIRE(synth.getRegionView(2)->pan == 0.3_a); REQUIRE(synth.getRegionView(2)->delay == 56); REQUIRE(synth.getRegionView(2)->keyRange == sfz::Range(50, 50)); - REQUIRE(synth.getRegionView(3)->pan == 30.0f); + REQUIRE(synth.getRegionView(3)->pan == 0.3_a); REQUIRE(synth.getRegionView(3)->delay == 56); REQUIRE(synth.getRegionView(3)->keyRange == sfz::Range(51, 51)); - REQUIRE(synth.getRegionView(4)->pan == -10.0f); + REQUIRE(synth.getRegionView(4)->pan == -0.1_a); REQUIRE(synth.getRegionView(4)->delay == 47); REQUIRE(synth.getRegionView(4)->keyRange == sfz::Range(40, 40)); - REQUIRE(synth.getRegionView(5)->pan == -10.0f); + REQUIRE(synth.getRegionView(5)->pan == -0.1_a); REQUIRE(synth.getRegionView(5)->delay == 47); REQUIRE(synth.getRegionView(5)->keyRange == sfz::Range(41, 41)); - REQUIRE(synth.getRegionView(6)->pan == -10.0f); + REQUIRE(synth.getRegionView(6)->pan == -0.1_a); REQUIRE(synth.getRegionView(6)->delay == 36); REQUIRE(synth.getRegionView(6)->keyRange == sfz::Range(30, 30)); - REQUIRE(synth.getRegionView(7)->pan == -10.0f); + REQUIRE(synth.getRegionView(7)->pan == -0.1_a); REQUIRE(synth.getRegionView(7)->delay == 36); REQUIRE(synth.getRegionView(7)->keyRange == sfz::Range(31, 31)); } diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 1beebaa6..c450b7fb 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -452,66 +452,63 @@ TEST_CASE("[Region] Parsing opcodes") { REQUIRE(region.pan == 0.0f); region.parseOpcode({ "pan", "4.2" }); - REQUIRE(region.pan == 4.2f); + REQUIRE(region.pan == 0.042_a); region.parseOpcode({ "pan", "-4.2" }); - REQUIRE(region.pan == -4.2f); + REQUIRE(region.pan == -0.042_a); region.parseOpcode({ "pan", "-123" }); - REQUIRE(region.pan == -100.0f); + REQUIRE(region.pan == -1.0_a); region.parseOpcode({ "pan", "132" }); - REQUIRE(region.pan == 100.0f); + REQUIRE(region.pan == 1.0_a); } SECTION("pan_oncc") { - REQUIRE(!region.panCC); + REQUIRE(region.panCC.empty()); region.parseOpcode({ "pan_oncc45", "4.2" }); - REQUIRE(region.panCC); - REQUIRE(region.panCC->cc == 45); - REQUIRE(region.panCC->value == 4.2f); + REQUIRE(region.panCC.contains(45)); + REQUIRE(region.panCC[45] == 0.042_a); } SECTION("width") { - REQUIRE(region.width == 100.0f); + REQUIRE(region.width == 1.0_a); region.parseOpcode({ "width", "4.2" }); - REQUIRE(region.width == 4.2f); + REQUIRE(region.width == 0.042_a); region.parseOpcode({ "width", "-4.2" }); - REQUIRE(region.width == -4.2f); + REQUIRE(region.width == -0.042_a); region.parseOpcode({ "width", "-123" }); - REQUIRE(region.width == -100.0f); + REQUIRE(region.width == -1.0_a); region.parseOpcode({ "width", "132" }); - REQUIRE(region.width == 100.0f); + REQUIRE(region.width == 1.0_a); } SECTION("width_oncc") { - REQUIRE(!region.widthCC); + REQUIRE(region.widthCC.empty()); region.parseOpcode({ "width_oncc45", "4.2" }); - REQUIRE(region.widthCC); - REQUIRE(region.widthCC->cc == 45); - REQUIRE(region.widthCC->value == 4.2f); + REQUIRE(region.widthCC.contains(45)); + REQUIRE(region.widthCC[45] == 0.042_a); } SECTION("position") { REQUIRE(region.position == 0.0f); region.parseOpcode({ "position", "4.2" }); - REQUIRE(region.position == 4.2f); + REQUIRE(region.position == 0.042_a); region.parseOpcode({ "position", "-4.2" }); - REQUIRE(region.position == -4.2f); + REQUIRE(region.position == -0.042_a); region.parseOpcode({ "position", "-123" }); - REQUIRE(region.position == -100.0f); + REQUIRE(region.position == -1.0_a); region.parseOpcode({ "position", "132" }); - REQUIRE(region.position == 100.0f); + REQUIRE(region.position == 1.0_a); } SECTION("position_oncc") { - REQUIRE(!region.positionCC); + REQUIRE(region.positionCC.empty()); region.parseOpcode({ "position_oncc45", "4.2" }); - REQUIRE(region.positionCC); - REQUIRE(region.positionCC->cc == 45); - REQUIRE(region.positionCC->value == 4.2f); + REQUIRE(region.positionCC.contains(45)); + REQUIRE(region.positionCC[45] == 0.042_a); } SECTION("amp_keycenter") @@ -1431,6 +1428,17 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.selfMask == SfzSelfMask::dontMask); } + SECTION("amplitude") + { + REQUIRE(region.amplitude == 1.0_a ); + region.parseOpcode({ "amplitude", "40" }); + REQUIRE(region.amplitude == 0.4_a ); + region.parseOpcode({ "amplitude", "-40" }); + REQUIRE(region.amplitude == 0_a ); + region.parseOpcode({ "amplitude", "140" }); + REQUIRE(region.amplitude == 1.0_a ); + } + SECTION("amplitude_cc") { REQUIRE(region.amplitudeCC.empty()); From 507ddc8e01ec00a0827482b7d6dfb8c786df1d30 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 20:35:15 +0200 Subject: [PATCH 38/72] Reworking the modifiers separately --- src/sfizz/Voice.cpp | 251 ++++++++++++++++++++++++++++++++++++-------- src/sfizz/Voice.h | 7 ++ 2 files changed, 213 insertions(+), 45 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 8b22323a..f10679df 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -253,7 +253,7 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept } template -void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, absl::Span temp, std::function function) +void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, absl::Span output, absl::Span temp, std::function function) { for (auto& mod : ccMods) { const auto eventList = state.getEvents(mod.cc); @@ -270,9 +270,203 @@ void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, lastDelay += length; } sfz::fill(temp.subspan(lastDelay), lastValue); + sfz::applyGain(temp, output); } } +void sfz::Voice::amplitudeModulation(absl::Span modulationSpan) noexcept +{ + fill(modulationSpan, 1.0f); + if (!region) + return; + + const auto numSamples = modulationSpan.size(); + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!tempBuffer) + return; + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + + for (auto& mod : region->amplitudeCC) { + const auto eventList = resources.midiState.getEvents(mod.cc); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = mod.value * eventList[0].value; + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (Default::amplitudeRange.clamp(mod.value * event.value) - lastValue)/ length; + lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(tempSpan.subspan(lastDelay), lastValue); + applyGain(tempSpan, modulationSpan); + } + applyGain(baseGain, modulationSpan); +} + +void sfz::Voice::crossfadeModulation(absl::Span modulationSpan) noexcept +{ + fill(modulationSpan, 1.0f); + if (!region) + return; + + const auto numSamples = modulationSpan.size(); + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!tempBuffer) + return; + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + + for (auto& mod : region->crossfadeCCInRange) { + const auto eventList = resources.midiState.getEvents(mod.cc); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = crossfadeIn(mod.value, eventList[0].value, region->crossfadeCCCurve); + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (crossfadeIn(mod.value, event.value, region->crossfadeCCCurve) - lastValue)/ length; + lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(tempSpan.subspan(lastDelay), lastValue); + applyGain(tempSpan, modulationSpan); + } + + for (auto& mod : region->crossfadeCCOutRange) { + const auto eventList = resources.midiState.getEvents(mod.cc); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = crossfadeOut(mod.value, eventList[0].value, region->crossfadeCCCurve); + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (crossfadeOut(mod.value, event.value, region->crossfadeCCCurve) - lastValue)/ length; + lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(tempSpan.subspan(lastDelay), lastValue); + applyGain(tempSpan, modulationSpan); + } +} + +void sfz::Voice::panningModulation(absl::Span modulationSpan) noexcept +{ + if (!region) + return; + + if (region->panCC.empty()) { + fill(modulationSpan, region->pan); + return; + } + + fill(modulationSpan, 1.0f); + const auto numSamples = modulationSpan.size(); + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!tempBuffer) + return; + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + + for (auto& mod : region->panCC) { + const auto eventList = resources.midiState.getEvents(mod.cc); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = mod.value * eventList[0].value; + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (Default::panRange.clamp(mod.value * event.value) - lastValue)/ length; + lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(tempSpan.subspan(lastDelay), lastValue); + applyGain(tempSpan, modulationSpan); + } + + add(region->pan, modulationSpan); +} + +void sfz::Voice::widthModulation(absl::Span modulationSpan) noexcept +{ + if (!region) + return; + + if (region->widthCC.empty()) { + fill(modulationSpan, region->width); + return; + } + + fill(modulationSpan, 1.0f); + const auto numSamples = modulationSpan.size(); + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!tempBuffer) + return; + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + + for (auto& mod : region->widthCC) { + const auto eventList = resources.midiState.getEvents(mod.cc); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = mod.value * eventList[0].value; + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (Default::widthRange.clamp(mod.value * event.value) - lastValue)/ length; + lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(tempSpan.subspan(lastDelay), lastValue); + applyGain(tempSpan, modulationSpan); + } + add(region->width, modulationSpan); +} + +void sfz::Voice::positionModulation(absl::Span modulationSpan) noexcept +{ + if (!region) + return; + + if (region->positionCC.empty()) { + fill(modulationSpan, region->position); + return; + } + + fill(modulationSpan, 1.0f); + const auto numSamples = modulationSpan.size(); + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!tempBuffer) + return; + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + + for (auto& mod : region->positionCC) { + const auto eventList = resources.midiState.getEvents(mod.cc); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = mod.value * eventList[0].value; + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (Default::positionRange.clamp(mod.value * event.value) - lastValue)/ length; + lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(tempSpan.subspan(lastDelay), lastValue); + applyGain(tempSpan, modulationSpan); + } + add(region->position, modulationSpan); +} + void sfz::Voice::processMono(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); @@ -288,30 +482,15 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - fill(modulationSpan, 0.0f); - getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, [](const float& modifier, float value){ - return value * modifier; - }); - // DBG("Amplitude curve back: " << modulationSpan.back()); - add(baseGain, modulationSpan); - // DBG("Final gain: " << modulationSpan.back()); + amplitudeModulation(modulationSpan); + DBG("Final gain: " << modulationSpan.back()); applyGain(modulationSpan, leftBuffer); // Crossfade envelopes // crossfadeEnvelope.getBlock(modulationSpan); - fill(modulationSpan, 1.0f); - getLinearEnvelope>(region->crossfadeCCInRange, resources.midiState, modulationSpan, [this](const Range& range, float value){ - return crossfadeIn(range, value, region->crossfadeCCCurve); - }); + crossfadeModulation(modulationSpan); + DBG("XF: " << modulationSpan.back()); applyGain(modulationSpan, leftBuffer); - // DBG("XFin: " << modulationSpan.back()); - - fill(modulationSpan, 1.0f); - getLinearEnvelope>(region->crossfadeCCOutRange, resources.midiState, modulationSpan, [this](const Range& range, float value){ - return crossfadeOut(range, value, region->crossfadeCCCurve); - }); - applyGain(modulationSpan, leftBuffer); - // DBG("XFout: " << modulationSpan.back()); // Volume envelope volumeEnvelope.getBlock(modulationSpan); @@ -343,9 +522,8 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept copy(leftBuffer, rightBuffer); // Apply panning - fill(modulationSpan, 0.0f); - getLinearEnvelope(region->panCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); - add(region->pan, modulationSpan); + panningModulation(modulationSpan); + DBG("Pan: " << modulationSpan.back()); pan(modulationSpan, leftBuffer, rightBuffer); } } @@ -365,22 +543,11 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - fill(modulationSpan, 0.0f); - getLinearEnvelope(region->amplitudeCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); - add(baseGain, modulationSpan); + amplitudeModulation(modulationSpan); buffer.applyGain(modulationSpan); // Crossfade envelopes - fill(modulationSpan, 1.0f); - getLinearEnvelope>(region->crossfadeCCInRange, resources.midiState, modulationSpan, [this](const Range& range, float value){ - return crossfadeIn(range, value, region->crossfadeCCCurve); - }); - buffer.applyGain(modulationSpan); - - fill(modulationSpan, 1.0f); - getLinearEnvelope>(region->crossfadeCCOutRange, resources.midiState, modulationSpan, [this](const Range& range, float value){ - return crossfadeOut(range, value, region->crossfadeCCCurve); - }); + crossfadeModulation(modulationSpan); buffer.applyGain(modulationSpan); // Volume envelope @@ -396,20 +563,14 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { panningDuration }; // Apply panning - fill(modulationSpan, 0.0f); - getLinearEnvelope(region->panCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); - add(region->pan, modulationSpan); + panningModulation(modulationSpan); pan(modulationSpan, leftBuffer, rightBuffer); // Apply the width/position process - fill(modulationSpan, 0.0f); - getLinearEnvelope(region->widthCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); - add(region->width, modulationSpan); + widthModulation(modulationSpan); width(modulationSpan, leftBuffer, rightBuffer); - fill(modulationSpan, 0.0f); - getLinearEnvelope(region->positionCC, resources.midiState, modulationSpan, [](float modifier, float value) { return value * modifier; }); - add(region->position, modulationSpan); + positionModulation(modulationSpan); pan(modulationSpan, leftBuffer, rightBuffer); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 0d51cb3e..996bcab2 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -250,6 +250,13 @@ private: * @param buffer */ void processStereo(AudioSpan buffer) noexcept; + + void amplitudeModulation(absl::Span modulationSpan) noexcept; + void crossfadeModulation(absl::Span modulationSpan) noexcept; + void panningModulation(absl::Span modulationSpan) noexcept; + void widthModulation(absl::Span modulationSpan) noexcept; + void positionModulation(absl::Span modulationSpan) noexcept; + Region* region { nullptr }; enum class State { From 3ceee27c0afabc199648f640e791a9d33f3daebd Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 22:55:14 +0200 Subject: [PATCH 39/72] Move the modifiers in the midistate --- src/sfizz/EQPool.cpp | 18 ++- src/sfizz/FilterPool.cpp | 20 ++- src/sfizz/MidiState.h | 60 ++++++++- src/sfizz/SfzHelpers.h | 30 ++--- src/sfizz/Voice.cpp | 260 ++++++--------------------------------- src/sfizz/Voice.h | 6 - 6 files changed, 125 insertions(+), 269 deletions(-) diff --git a/src/sfizz/EQPool.cpp b/src/sfizz/EQPool.cpp index c3883124..81821e1e 100644 --- a/src/sfizz/EQPool.cpp +++ b/src/sfizz/EQPool.cpp @@ -28,9 +28,12 @@ void sfz::EQHolder::setup(const EQDescription& description, unsigned numChannels baseGain = description.gain + velocity * description.vel2gain; // Setup the modulated values - lastFrequency = midiState.modulate(baseFrequency, description.frequencyCC, Default::eqFrequencyRange); - lastBandwidth = midiState.modulate(baseBandwidth, description.bandwidthCC, Default::eqBandwidthRange); - lastGain = midiState.modulate(baseGain, description.gainCC, Default::eqGainRange); + lastFrequency = baseFrequency + midiState.fastAdditiveModifiers(description.frequencyCC); + lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); + lastBandwidth = baseBandwidth + midiState.fastAdditiveModifiers(description.bandwidthCC); + lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); + lastGain = baseGain + midiState.fastAdditiveModifiers(description.gainCC); + lastGain = Default::eqGainRange.clamp(lastGain); // Initialize the EQ eq.prepare(lastFrequency, lastBandwidth, lastGain); @@ -50,9 +53,12 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF // TODO: Once the midistate envelopes are done, add modulation in there! // For now we take the last value - lastFrequency = midiState.modulate(baseFrequency, description->frequencyCC, Default::eqFrequencyRange); - lastBandwidth = midiState.modulate(baseBandwidth, description->bandwidthCC, Default::eqBandwidthRange); - lastGain = midiState.modulate(baseGain, description->gainCC, Default::eqGainRange); + lastFrequency = baseFrequency + midiState.fastAdditiveModifiers(description->frequencyCC); + lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); + lastBandwidth = baseBandwidth + midiState.fastAdditiveModifiers(description->bandwidthCC); + lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); + lastGain = baseGain + midiState.fastAdditiveModifiers(description->gainCC); + lastGain = Default::eqGainRange.clamp(lastGain); if (lastGain == 0.0f) { justCopy(); diff --git a/src/sfizz/FilterPool.cpp b/src/sfizz/FilterPool.cpp index 96f97308..4e89e802 100644 --- a/src/sfizz/FilterPool.cpp +++ b/src/sfizz/FilterPool.cpp @@ -40,9 +40,12 @@ void sfz::FilterHolder::setup(const FilterDescription& description, unsigned num baseResonance = description.resonance; // Setup the modulated values - lastCutoff = midiState.modulate(baseCutoff, description.cutoffCC, Default::filterCutoffRange, multiplyByCents); - lastResonance = midiState.modulate(baseResonance, description.resonanceCC, Default::filterResonanceRange); - lastGain = midiState.modulate(baseGain, description.gainCC, Default::filterGainRange); + lastCutoff = baseCutoff * midiState.fastMultiplicativeModifiers(description.cutoffCC, multiplyByCentsModifier); + lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); + lastResonance = baseResonance + midiState.fastAdditiveModifiers(description.resonanceCC); + lastResonance = Default::filterResonanceRange.clamp(lastResonance); + lastGain = baseGain + midiState.fastAdditiveModifiers(description.gainCC); + lastGain = Default::filterGainRange.clamp(lastGain); // Initialize the filter filter.prepare(lastCutoff, lastResonance, lastGain); @@ -59,9 +62,14 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned // TODO: Once the midistate envelopes are done, add modulation in there! // For now we take the last value // TODO: the template deduction could be automatic here? - lastCutoff = midiState.modulate(baseCutoff, description->cutoffCC, Default::filterCutoffRange, multiplyByCents); - lastResonance = midiState.modulate(baseResonance, description->resonanceCC, Default::filterResonanceRange); - baseGain = midiState.modulate(baseGain, description->gainCC, Default::filterGainRange); + lastCutoff = baseCutoff * midiState.fastMultiplicativeModifiers(description->cutoffCC, [](const int& modifier, float value) -> float { + return value * centsFactor(modifier); + }); + lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); + lastResonance = baseResonance + midiState.fastAdditiveModifiers(description->resonanceCC); + lastResonance = Default::filterResonanceRange.clamp(lastResonance); + lastGain = baseGain + midiState.fastAdditiveModifiers(description->gainCC); + lastGain = Default::filterGainRange.clamp(lastGain); filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames); } diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index d2f0ff4f..d7b528e3 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -8,6 +8,8 @@ #include #include "CCMap.h" #include "Range.h" +#include "absl/types/span.h" +#include "SIMDHelpers.h" namespace sfz { @@ -132,18 +134,68 @@ public: * @param lambda the function to apply for each modifier * @return T */ - template - T modulate(T value, const CCMap& modifiers, const Range& validRange, const modFunction& lambda = addToBase) const noexcept + template)> + float fastAdditiveModifiers(const CCMap& modifiers, F&& lambda = gainModifier) const noexcept { + float returnedValue { 0.0f }; for (auto& mod: modifiers) { - lambda(value, getCCValue(mod.cc) * mod.value); + returnedValue += lambda(getCCValue(mod.cc), mod.value); } - return validRange.clamp(value); + return returnedValue; } + template)> + float fastMultiplicativeModifiers(const CCMap& modifiers, F&& lambda = gainModifier) const noexcept + { + float returnedValue { 1.0f }; + for (auto& mod: modifiers) { + returnedValue *= lambda(getCCValue(mod.cc), mod.value); + } + return returnedValue; + } + + template)> + void additiveModifiers(const CCMap& modifiers, absl::Span output, absl::Span temp, F&& lambda = gainModifier) + { + fill(output, 0.0f); + for (auto& mod : modifiers) { + linearEnvelope(mod, temp, lambda); + add(temp, output); + } + } + + template)> + void multiplicativeModifiers(const CCMap& modifiers, absl::Span output, absl::Span temp, F&& lambda = gainModifier) + { + for (auto& mod : modifiers) { + linearEnvelope(mod, temp, lambda); + applyGain(temp, output); + } + } + + const EventVector& getEvents(int ccIdx) const noexcept; private: + + template + void linearEnvelope(T&& modifier, absl::Span envelope, F&& lambda) const + { + const auto eventList = getEvents(modifier.cc); + ASSERT(eventList.size() > 0); + ASSERT(eventList[0].delay == 0); + + auto lastValue = lambda(modifier.value, eventList[0].value); + auto lastDelay = eventList[0].delay; + for (unsigned i = 1; i < eventList.size(); ++ i) { + const auto event = eventList[i]; + const auto length = event.delay - lastDelay; + const auto step = (lambda(modifier.value, event.value) - lastValue)/ length; + lastValue = linearRamp(envelope.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(envelope.subspan(lastDelay), lastValue); + } int activeNotes { 0 }; /** diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 0d18a465..d991e795 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -274,26 +274,6 @@ bool findDefine(absl::string_view line, absl::string_view& variable, absl::strin */ bool findInclude(absl::string_view line, std::string& path); -/** - * @brief Defines a function that modulates a base value with another one - * - * @tparam T - */ -template -using modFunction = std::function; - -/** - * @brief Modulation helper that adds the modifier to the base value - * - * @tparam T - * @param base the base value - * @param modifier the modifier value - */ -template -inline CXX14_CONSTEXPR void addToBase(T& base, T modifier) -{ - base += modifier; -} /** * @brief multiply a value by a factor, in cents. To be used for pitch variations. @@ -301,9 +281,15 @@ inline CXX14_CONSTEXPR void addToBase(T& base, T modifier) * @param base * @param modifier */ -inline CXX14_CONSTEXPR void multiplyByCents(float& base, int modifier) +inline CXX14_CONSTEXPR float multiplyByCentsModifier(int modifier, float base) { - base *= centsFactor(modifier); + return base * centsFactor(modifier); +} + +template +inline CXX14_CONSTEXPR float gainModifier(T modifier, float value) +{ + return value * modifier; } diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index f10679df..2307e05d 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -252,221 +252,6 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept this->triggerDelay = absl::nullopt; } -template -void getLinearEnvelope(const sfz::CCMap& ccMods, const sfz::MidiState& state, absl::Span output, absl::Span temp, std::function function) -{ - for (auto& mod : ccMods) { - const auto eventList = state.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = function(mod.value, eventList[0].value); - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (function(mod.value, event.value) - lastValue)/ length; - lastValue = sfz::linearRamp(temp.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - sfz::fill(temp.subspan(lastDelay), lastValue); - sfz::applyGain(temp, output); - } -} - -void sfz::Voice::amplitudeModulation(absl::Span modulationSpan) noexcept -{ - fill(modulationSpan, 1.0f); - if (!region) - return; - - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->amplitudeCC) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = mod.value * eventList[0].value; - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (Default::amplitudeRange.clamp(mod.value * event.value) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - applyGain(baseGain, modulationSpan); -} - -void sfz::Voice::crossfadeModulation(absl::Span modulationSpan) noexcept -{ - fill(modulationSpan, 1.0f); - if (!region) - return; - - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->crossfadeCCInRange) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = crossfadeIn(mod.value, eventList[0].value, region->crossfadeCCCurve); - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (crossfadeIn(mod.value, event.value, region->crossfadeCCCurve) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - - for (auto& mod : region->crossfadeCCOutRange) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = crossfadeOut(mod.value, eventList[0].value, region->crossfadeCCCurve); - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (crossfadeOut(mod.value, event.value, region->crossfadeCCCurve) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } -} - -void sfz::Voice::panningModulation(absl::Span modulationSpan) noexcept -{ - if (!region) - return; - - if (region->panCC.empty()) { - fill(modulationSpan, region->pan); - return; - } - - fill(modulationSpan, 1.0f); - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->panCC) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = mod.value * eventList[0].value; - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (Default::panRange.clamp(mod.value * event.value) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - - add(region->pan, modulationSpan); -} - -void sfz::Voice::widthModulation(absl::Span modulationSpan) noexcept -{ - if (!region) - return; - - if (region->widthCC.empty()) { - fill(modulationSpan, region->width); - return; - } - - fill(modulationSpan, 1.0f); - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->widthCC) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = mod.value * eventList[0].value; - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (Default::widthRange.clamp(mod.value * event.value) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - add(region->width, modulationSpan); -} - -void sfz::Voice::positionModulation(absl::Span modulationSpan) noexcept -{ - if (!region) - return; - - if (region->positionCC.empty()) { - fill(modulationSpan, region->position); - return; - } - - fill(modulationSpan, 1.0f); - const auto numSamples = modulationSpan.size(); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!tempBuffer) - return; - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); - - for (auto& mod : region->positionCC) { - const auto eventList = resources.midiState.getEvents(mod.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = mod.value * eventList[0].value; - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (Default::positionRange.clamp(mod.value * event.value) - lastValue)/ length; - lastValue = linearRamp(tempSpan.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(tempSpan.subspan(lastDelay), lastValue); - applyGain(tempSpan, modulationSpan); - } - add(region->position, modulationSpan); -} - void sfz::Voice::processMono(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); @@ -474,21 +259,29 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept auto rightBuffer = buffer.getSpan(1); auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); - if (!modulationBuffer) + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!modulationBuffer || !tempBuffer) return; auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + using namespace std::placeholders; + const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); + const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); { // Amplitude processing ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - amplitudeModulation(modulationSpan); + fill(modulationSpan, baseGain); + resources.midiState.multiplicativeModifiers(region->amplitudeCC, modulationSpan, tempSpan); DBG("Final gain: " << modulationSpan.back()); applyGain(modulationSpan, leftBuffer); // Crossfade envelopes // crossfadeEnvelope.getBlock(modulationSpan); - crossfadeModulation(modulationSpan); + fill(modulationSpan, 1.0f); + resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, modulationSpan, tempSpan, xfinBind); + resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, modulationSpan, tempSpan, xfoutBind); DBG("XF: " << modulationSpan.back()); applyGain(modulationSpan, leftBuffer); @@ -522,7 +315,8 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept copy(leftBuffer, rightBuffer); // Apply panning - panningModulation(modulationSpan); + fill(modulationSpan, region->pan); + resources.midiState.additiveModifiers(region->panCC, modulationSpan, tempSpan); DBG("Pan: " << modulationSpan.back()); pan(modulationSpan, leftBuffer, rightBuffer); } @@ -535,19 +329,29 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept auto rightBuffer = buffer.getSpan(1); auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); - if (!modulationBuffer) + auto tempBuffer = resources.bufferPool.getBuffer(numSamples); + if (!modulationBuffer || !tempBuffer) return; auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); + auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + + using namespace std::placeholders; + const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); + const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); { // Amplitude processing ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - amplitudeModulation(modulationSpan); + fill(modulationSpan, baseGain); + resources.midiState.multiplicativeModifiers(region->amplitudeCC, modulationSpan, tempSpan); + DBG("Final gain: " << modulationSpan.back()); buffer.applyGain(modulationSpan); // Crossfade envelopes - crossfadeModulation(modulationSpan); + fill(modulationSpan, 1.0f); + resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, modulationSpan, tempSpan, xfinBind); + resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, modulationSpan, tempSpan, xfoutBind); buffer.applyGain(modulationSpan); // Volume envelope @@ -563,14 +367,20 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { panningDuration }; // Apply panning - panningModulation(modulationSpan); + // panningModulation(modulationSpan); + fill(modulationSpan, region->pan); + resources.midiState.additiveModifiers(region->panCC, modulationSpan, tempSpan); pan(modulationSpan, leftBuffer, rightBuffer); // Apply the width/position process - widthModulation(modulationSpan); + // widthModulation(modulationSpan); + fill(modulationSpan, region->width); + resources.midiState.additiveModifiers(region->widthCC, modulationSpan, tempSpan); width(modulationSpan, leftBuffer, rightBuffer); - positionModulation(modulationSpan); + // positionModulation(modulationSpan); + fill(modulationSpan, region->position); + resources.midiState.additiveModifiers(region->positionCC, modulationSpan, tempSpan); pan(modulationSpan, leftBuffer, rightBuffer); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 996bcab2..e8154546 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -251,12 +251,6 @@ private: */ void processStereo(AudioSpan buffer) noexcept; - void amplitudeModulation(absl::Span modulationSpan) noexcept; - void crossfadeModulation(absl::Span modulationSpan) noexcept; - void panningModulation(absl::Span modulationSpan) noexcept; - void widthModulation(absl::Span modulationSpan) noexcept; - void positionModulation(absl::Span modulationSpan) noexcept; - Region* region { nullptr }; enum class State { From e13456d41de698983d24c6f80d1931c832e7ead5 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 23:35:22 +0200 Subject: [PATCH 40/72] Try to solve windows builds --- src/sfizz/Config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 51ac5c1b..59aeec21 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -38,7 +38,7 @@ namespace config { constexpr size_t numChannels { 2 }; constexpr int numBackgroundThreads { 4 }; constexpr int numVoices { 64 }; - constexpr int maxVoices { 256 }; + constexpr unsigned maxVoices { 256 }; constexpr int maxFilePromises { maxVoices * 2 }; constexpr int sustainCC { 64 }; constexpr int allSoundOffCC { 120 }; From e54c27b1f2211686d9745193945b1d58774b58ae Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 29 Mar 2020 23:35:43 +0200 Subject: [PATCH 41/72] Warning on gcc 4.9 for the array initializer --- src/sfizz/MidiState.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index d7b528e3..18305e40 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -202,12 +202,12 @@ private: * @brief Stores the note on times. * */ - MidiNoteArray noteOnTimes {}; + MidiNoteArray noteOnTimes {{}}; /** * @brief Stores the note off times. * */ - MidiNoteArray noteOffTimes {}; + MidiNoteArray noteOffTimes {{}}; /** * @brief Stores the velocity of the note ons for currently * depressed notes. From 9c17201c634f3da1a7a51da140719392ea275537 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 15:14:33 +0200 Subject: [PATCH 42/72] Avoid shared pointers and get a span "pointer" directly from the buffer pool --- src/sfizz/BufferPool.h | 179 +++++++++++++++++++++-------------------- src/sfizz/Config.h | 2 +- src/sfizz/MidiState.h | 6 +- src/sfizz/Synth.cpp | 20 ++--- src/sfizz/Voice.cpp | 176 +++++++++++++++++++--------------------- 5 files changed, 188 insertions(+), 195 deletions(-) diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index 752cdfce..0d35f73a 100644 --- a/src/sfizz/BufferPool.h +++ b/src/sfizz/BufferPool.h @@ -11,141 +11,122 @@ #include "AudioBuffer.h" #include #include +#include +#include "absl/algorithm/container.h" #ifndef NDEBUG - #include "absl/algorithm/container.h" #include "MathHelpers.h" #endif namespace sfz { +template +class SpanHolder +{ +public: + SpanHolder() {} + SpanHolder(const SpanHolder&) = delete; + SpanHolder& operator=(const SpanHolder&) = delete; + SpanHolder(SpanHolder&&) = delete; + SpanHolder& operator=(SpanHolder&&) = delete; + SpanHolder(T value, int* available) + : value(std::move(value)), available(available) {} + T& operator*() { return value; } + T* operator->() { return &value; } + operator bool() const { return available != nullptr; } + ~SpanHolder() + { + if (available) + *available += 1; + } +private: + T value {}; + int* available { nullptr }; +}; + class BufferPool { public: BufferPool() { - for (auto& buffer : buffers) { - buffer = std::make_shared>(config::defaultSamplesPerBlock); - } - - for (auto& buffer : indexBuffers) { - buffer = std::make_shared>(config::defaultSamplesPerBlock); - } - for (auto& buffer : stereoBuffers) { - buffer = std::make_shared>(2, config::defaultSamplesPerBlock); + buffer.addChannels(2); } + monoAvailable.resize(config::bufferPoolSize); + stereoAvailable.resize(config::stereoBufferPoolSize); + indexAvailable.resize(config::indexBufferPoolSize); + _setBufferSize(config::defaultSamplesPerBlock); } void setBufferSize(unsigned bufferSize) { - for (auto& buffer: buffers) { - // Trying to resize a buffer in use - ASSERT(buffer.use_count() == 1); - buffer->resize(bufferSize); - } - - for (auto& buffer: indexBuffers) { - // Trying to resize a buffer in use - ASSERT(buffer.use_count() == 1); - buffer->resize(bufferSize); - } - - for (auto& buffer: stereoBuffers) { - // Trying to resize a buffer in use - ASSERT(buffer.use_count() == 1); - buffer->resize(bufferSize); - } + ASSERT(absl::c_all_of(monoAvailable, [](int value) { return value == 1; })); + ASSERT(absl::c_all_of(indexAvailable, [](int value) { return value == 1; })); + ASSERT(absl::c_all_of(stereoAvailable, [](int value) { return value == 1; })); + _setBufferSize(bufferSize); } - std::shared_ptr> getBuffer(size_t numFrames) const + SpanHolder> getBuffer(size_t numFrames) { - auto bufferIt = buffers.begin(); - - if (buffers.empty()) { - DBG("[sfizz] No available buffers in the pool"); + const auto availableIt = absl::c_find(monoAvailable, 1); + if (availableIt == monoAvailable.end()) { + DBG("[sfizz] No free buffers available..."); return {}; } + const auto freeIndex = std::distance(monoAvailable.begin(), availableIt); - if (buffers[0]->size() < numFrames) { - DBG("[sfizz] Someone asked for a buffer of size " << numFrames << "; only " << buffers[0]->size() << " available..."); + if (monoBuffers[freeIndex].size() < numFrames) { + DBG("[sfizz] Someone asked for a buffer of size " << numFrames << "; only " << monoBuffers[freeIndex].size() << " available..."); return {}; } #ifndef NDEBUG - maxBuffersUsed = max(1 + absl::c_count_if(buffers, [&](const std::shared_ptr>& buffer) { - return (buffer.use_count() > 1); - }), maxBuffersUsed); + maxBuffersUsed = 1 + absl::c_count_if(monoAvailable, [](int value) { return value == 0; }); #endif - - while (bufferIt < buffers.end()) { - if (bufferIt->use_count() == 1) - return *bufferIt; - ++bufferIt; - } - - // No buffer found; debug message - DBG("[sfizz] No free buffer available!"); - return {}; + *availableIt -= 1; + return { absl::MakeSpan(monoBuffers[freeIndex]).first(numFrames), &*availableIt }; } - std::shared_ptr> getIndexBuffer(size_t numFrames) const + SpanHolder> getIndexBuffer(size_t numFrames) { - auto bufferIt = indexBuffers.begin(); - - if (indexBuffers.empty()) { + const auto availableIt = absl::c_find(indexAvailable, 1); + if (availableIt == indexAvailable.end()) { DBG("[sfizz] No available index buffers in the pool"); return {}; } + const auto freeIndex = std::distance(indexAvailable.begin(), availableIt); - if (indexBuffers[0]->size() < numFrames) { - DBG("[sfizz] Someone asked for a index buffer of size " << numFrames << "; only " << indexBuffers[0]->size() << " available..."); + if (indexBuffers[freeIndex].size() < numFrames) { + DBG("[sfizz] Someone asked for a index buffer of size " << numFrames << "; only " << indexBuffers[freeIndex].size() << " available..."); return {}; } #ifndef NDEBUG - maxIndexBuffersUsed = max(1 + absl::c_count_if(indexBuffers, [&](const std::shared_ptr>& buffer) { - return (buffer.use_count() > 1); - }), maxIndexBuffersUsed); + maxIndexBuffersUsed = 1 + absl::c_count_if(indexAvailable, [](int value) { return value == 0; }); #endif - - while (bufferIt < indexBuffers.end()) { - if (bufferIt->use_count() == 1) - return *bufferIt; - ++bufferIt; - } - - // No buffer found; debug message - DBG("[sfizz] No free index buffer available!"); - return {}; + *availableIt -= 1; + return { absl::MakeSpan(indexBuffers[freeIndex]).first(numFrames), &*availableIt }; } - std::shared_ptr> getStereoBuffer(size_t numFrames) const + SpanHolder> getStereoBuffer(size_t numFrames) { - if (stereoBuffers.empty()) { + const auto availableIt = absl::c_find(stereoAvailable, 1); + if (availableIt == stereoAvailable.end()) { DBG("[sfizz] No available stereo buffers in the pool"); return {}; } + const auto freeIndex = std::distance(stereoAvailable.begin(), availableIt); - if (stereoBuffers[0]->getNumFrames() < numFrames) { - DBG("[sfizz] Someone asked for a stereo buffer of size " << numFrames << "; only " << stereoBuffers[0]->getNumFrames() << " available..."); + if (stereoBuffers[freeIndex].getNumFrames() < numFrames) { + DBG("[sfizz] Someone asked for a stereo buffer of size " << numFrames << "; only " << stereoBuffers[freeIndex].getNumFrames() << " available..."); return {}; } -#ifndef NDEBUG - maxStereoBuffersUsed = max(1 + absl::c_count_if(stereoBuffers, [&](const std::shared_ptr>& buffer) { - return (buffer.use_count() > 1); - }), maxStereoBuffersUsed); -#endif - auto bufferIt = stereoBuffers.begin(); - while (bufferIt < stereoBuffers.end()) { - if (bufferIt->use_count() == 1) - return *bufferIt; - ++bufferIt; - } - // No buffer found; debug message - DBG("[sfizz] No free stereo buffer available!"); - return {}; +#ifndef NDEBUG + maxStereoBuffersUsed = 1 + absl::c_count_if(stereoAvailable, [](int value) { return value == 0; }); +#endif + *availableIt -= 1; + return { sfz::AudioSpan(stereoBuffers[freeIndex]).first(numFrames), &*availableIt }; } #ifndef NDEBUG @@ -156,10 +137,34 @@ public: DBG("Max stereo buffers used: " << maxStereoBuffersUsed); } #endif + + private: - std::array>, config::bufferPoolSize> buffers; - std::array>, config::bufferPoolSize> indexBuffers; - std::array>, config::stereoBufferPoolSize> stereoBuffers; + void _setBufferSize(unsigned bufferSize) + { + for (auto& buffer : monoBuffers) { + buffer.resize(bufferSize); + } + + for (auto& buffer : indexBuffers) { + buffer.resize(bufferSize); + } + + for (auto& buffer : stereoBuffers) { + buffer.resize(bufferSize); + } + + absl::c_fill(monoAvailable, 1); + absl::c_fill(stereoAvailable, 1); + absl::c_fill(indexAvailable, 1); + } + + std::array, config::bufferPoolSize> monoBuffers; + std::vector monoAvailable; + std::array, config::bufferPoolSize> indexBuffers; + std::vector indexAvailable; + std::array, config::stereoBufferPoolSize> stereoBuffers; + std::vector stereoAvailable; #ifndef NDEBUG mutable int maxBuffersUsed { 0 }; mutable int maxIndexBuffersUsed { 0 }; diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 59aeec21..a7c67223 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -28,7 +28,7 @@ namespace config { constexpr float defaultSampleRate { 48000 }; constexpr int defaultSamplesPerBlock { 1024 }; constexpr int maxBlockSize { 8192 }; - constexpr int bufferPoolSize { 8 }; + constexpr int bufferPoolSize { 4 }; constexpr int stereoBufferPoolSize { 4 }; constexpr int indexBufferPoolSize { 2 }; constexpr int preloadSize { 8192 }; diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 18305e40..9b028527 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -176,8 +176,6 @@ public: const EventVector& getEvents(int ccIdx) const noexcept; -private: - template void linearEnvelope(T&& modifier, absl::Span envelope, F&& lambda) const { @@ -196,6 +194,10 @@ private: } fill(envelope.subspan(lastDelay), lastValue); } + +private: + + int activeNotes { 0 }; /** diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index ec44a516..b2640738 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -548,16 +548,12 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept return; size_t numFrames = buffer.getNumFrames(); - auto tempBuffer = resources.bufferPool.getStereoBuffer(numFrames); - auto tempMixNodeBuffer = resources.bufferPool.getStereoBuffer(numFrames); - if (!tempBuffer || !tempMixNodeBuffer) { + auto tempSpan = resources.bufferPool.getStereoBuffer(numFrames); + auto tempMixSpan = resources.bufferPool.getStereoBuffer(numFrames); + if (!tempSpan || !tempMixSpan) { DBG("[sfizz] Could not get a temporary buffer; exiting callback... "); return; } - - auto temp = AudioSpan(*tempBuffer).first(numFrames); - auto tempMixNode = AudioSpan(*tempMixNodeBuffer).first(numFrames); - CallbackBreakdown callbackBreakdown; { // Prepare the effect inputs. They are mixes of per-region outputs. @@ -572,7 +568,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { // Main render block ScopedTiming logger { callbackBreakdown.renderMethod }; buffer.fill(0.0f); - tempMixNode.fill(0.0f); + tempSpan->fill(0.0f); resources.filePool.cleanupPromises(); for (auto& voice : voices) { @@ -582,14 +578,14 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept const Region* region = voice->getRegion(); numActiveVoices++; - voice->renderBlock(temp); + voice->renderBlock(*tempSpan); { // Add the output into the effects linked to this region ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { if (auto& bus = effectBuses[i]) { float addGain = region->getGainToEffectBus(i); - bus->addToInputs(temp, addGain, numFrames); + bus->addToInputs(*tempSpan, addGain, numFrames); } } } @@ -609,7 +605,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept for (auto& bus : effectBuses) { if (bus) { bus->process(numFrames); - bus->mixOutputsTo(buffer, tempMixNode, numFrames); + bus->mixOutputsTo(buffer, *tempMixSpan, numFrames); } } } @@ -618,7 +614,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept // -- note(jpc) the purpose of the Mix output is not known. // perhaps it's designed as extension point for custom processing? // as default behavior, it adds itself to the Main signal. - buffer.add(tempMixNode); + buffer.add(*tempMixSpan); // Apply the master volume buffer.applyGain(db2mag(volume)); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 2307e05d..8b401bf8 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -258,12 +258,11 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); - auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!modulationBuffer || !tempBuffer) + auto modulationSpan = resources.bufferPool.getBuffer(numSamples); + auto tempSpan = resources.bufferPool.getBuffer(numSamples); + if (!modulationSpan || !tempSpan) return; - auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); + using namespace std::placeholders; const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); @@ -272,26 +271,26 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - fill(modulationSpan, baseGain); - resources.midiState.multiplicativeModifiers(region->amplitudeCC, modulationSpan, tempSpan); - DBG("Final gain: " << modulationSpan.back()); + fill(*modulationSpan, baseGain); + resources.midiState.multiplicativeModifiers(region->amplitudeCC, *modulationSpan, *tempSpan); + DBG("Final gain: " << modulationSpan->back()); applyGain(modulationSpan, leftBuffer); // Crossfade envelopes // crossfadeEnvelope.getBlock(modulationSpan); - fill(modulationSpan, 1.0f); - resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, modulationSpan, tempSpan, xfinBind); - resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, modulationSpan, tempSpan, xfoutBind); - DBG("XF: " << modulationSpan.back()); + fill(*modulationSpan, 1.0f); + resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, *modulationSpan, *tempSpan, xfinBind); + resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, *modulationSpan, *tempSpan, xfoutBind); + DBG("XF: " << modulationSpan->back()); applyGain(modulationSpan, leftBuffer); // Volume envelope - volumeEnvelope.getBlock(modulationSpan); - applyGain(modulationSpan, leftBuffer); + volumeEnvelope.getBlock(*modulationSpan); + applyGain(*modulationSpan, leftBuffer); // AmpEG envelope - egEnvelope.getBlock(modulationSpan); - applyGain(modulationSpan, leftBuffer); + egEnvelope.getBlock(*modulationSpan); + applyGain(*modulationSpan, leftBuffer); } { // Filtering and EQ @@ -315,10 +314,10 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept copy(leftBuffer, rightBuffer); // Apply panning - fill(modulationSpan, region->pan); - resources.midiState.additiveModifiers(region->panCC, modulationSpan, tempSpan); - DBG("Pan: " << modulationSpan.back()); - pan(modulationSpan, leftBuffer, rightBuffer); + fill(*modulationSpan, region->pan); + resources.midiState.additiveModifiers(region->panCC, *modulationSpan, *tempSpan); + DBG("Pan: " << modulationSpan->back()); + pan(*modulationSpan, leftBuffer, rightBuffer); } } @@ -328,12 +327,10 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); - auto modulationBuffer = resources.bufferPool.getBuffer(numSamples); - auto tempBuffer = resources.bufferPool.getBuffer(numSamples); - if (!modulationBuffer || !tempBuffer) + auto modulationSpan = resources.bufferPool.getBuffer(numSamples); + auto tempSpan = resources.bufferPool.getBuffer(numSamples); + if (!modulationSpan || !tempSpan) return; - auto modulationSpan = absl::MakeSpan(*modulationBuffer).first(numSamples); - auto tempSpan = absl::MakeSpan(*tempBuffer).first(numSamples); using namespace std::placeholders; const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); @@ -343,45 +340,44 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept ScopedTiming logger { amplitudeDuration }; // Amplitude envelope - fill(modulationSpan, baseGain); - resources.midiState.multiplicativeModifiers(region->amplitudeCC, modulationSpan, tempSpan); - DBG("Final gain: " << modulationSpan.back()); - buffer.applyGain(modulationSpan); + fill(*modulationSpan, baseGain); + resources.midiState.multiplicativeModifiers(region->amplitudeCC, *modulationSpan, *tempSpan); + buffer.applyGain(*modulationSpan); // Crossfade envelopes - fill(modulationSpan, 1.0f); - resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, modulationSpan, tempSpan, xfinBind); - resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, modulationSpan, tempSpan, xfoutBind); - buffer.applyGain(modulationSpan); + fill(*modulationSpan, 1.0f); + resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, *modulationSpan, *tempSpan, xfinBind); + resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, *modulationSpan, *tempSpan, xfoutBind); + buffer.applyGain(*modulationSpan); // Volume envelope - volumeEnvelope.getBlock(modulationSpan); - buffer.applyGain(modulationSpan); + volumeEnvelope.getBlock(*modulationSpan); + buffer.applyGain(*modulationSpan); // AmpEG envelope - egEnvelope.getBlock(modulationSpan); - buffer.applyGain(modulationSpan); + egEnvelope.getBlock(*modulationSpan); + buffer.applyGain(*modulationSpan); } { // Panning and stereo processing ScopedTiming logger { panningDuration }; // Apply panning - // panningModulation(modulationSpan); - fill(modulationSpan, region->pan); - resources.midiState.additiveModifiers(region->panCC, modulationSpan, tempSpan); - pan(modulationSpan, leftBuffer, rightBuffer); + // panningModulation(*modulationSpan); + fill(*modulationSpan, region->pan); + resources.midiState.additiveModifiers(region->panCC, *modulationSpan, *tempSpan); + pan(*modulationSpan, leftBuffer, rightBuffer); // Apply the width/position process - // widthModulation(modulationSpan); - fill(modulationSpan, region->width); - resources.midiState.additiveModifiers(region->widthCC, modulationSpan, tempSpan); - width(modulationSpan, leftBuffer, rightBuffer); + // widthModulation(*modulationSpan); + fill(*modulationSpan, region->width); + resources.midiState.additiveModifiers(region->widthCC, *modulationSpan, *tempSpan); + width(*modulationSpan, leftBuffer, rightBuffer); - // positionModulation(modulationSpan); - fill(modulationSpan, region->position); - resources.midiState.additiveModifiers(region->positionCC, modulationSpan, tempSpan); - pan(modulationSpan, leftBuffer, rightBuffer); + // positionModulation(*modulationSpan); + fill(*modulationSpan, region->position); + resources.midiState.additiveModifiers(region->positionCC, *modulationSpan, *tempSpan); + pan(*modulationSpan, leftBuffer, rightBuffer); } { // Filtering and EQ @@ -412,37 +408,33 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept } auto source = currentPromise->getData(); - auto jumpBuffer = resources.bufferPool.getBuffer(numSamples); - auto bendBuffer = resources.bufferPool.getBuffer(numSamples); - auto leftCoeffBuffer = resources.bufferPool.getBuffer(numSamples); - auto rightCoeffBuffer = resources.bufferPool.getBuffer(numSamples); - auto indexBuffer = resources.bufferPool.getIndexBuffer(numSamples); - if (!jumpBuffer || !bendBuffer || !indexBuffer || !rightCoeffBuffer || !leftCoeffBuffer) + + auto jumps = resources.bufferPool.getBuffer(numSamples); + auto bends = resources.bufferPool.getBuffer(numSamples); + auto leftCoeffs = resources.bufferPool.getBuffer(numSamples); + auto rightCoeffs = resources.bufferPool.getBuffer(numSamples); + auto indices = resources.bufferPool.getIndexBuffer(numSamples); + if (!jumps || !bends || !indices || !rightCoeffs || !leftCoeffs) return; - auto jumps = absl::MakeSpan(*jumpBuffer).first(numSamples); - auto bends = absl::MakeSpan(*bendBuffer).first(numSamples); - auto indices = absl::MakeSpan(*indexBuffer).first(numSamples); - auto leftCoeffs = absl::MakeSpan(*leftCoeffBuffer).first(numSamples); - auto rightCoeffs = absl::MakeSpan(*rightCoeffBuffer).first(numSamples); - fill(jumps, pitchRatio * speedRatio); + fill(*jumps, pitchRatio * speedRatio); if (region->bendStep > 1) - pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor); + pitchBendEnvelope.getQuantizedBlock(*bends, bendStepFactor); else - pitchBendEnvelope.getBlock(bends); + pitchBendEnvelope.getBlock(*bends); - applyGain(bends, jumps); - jumps[0] += floatPositionOffset; - cumsum(jumps, jumps); - sfzInterpolationCast(jumps, indices, leftCoeffs, rightCoeffs); - add(sourcePosition, indices); + applyGain(*bends, *jumps); + jumps->front() += floatPositionOffset; + cumsum(*jumps, *jumps); + sfzInterpolationCast(*jumps, *indices, *leftCoeffs, *rightCoeffs); + add(sourcePosition, *indices); if (region->shouldLoop() && region->loopEnd(currentPromise->oversamplingFactor) <= source.getNumFrames()) { const auto loopEnd = static_cast(region->loopEnd(currentPromise->oversamplingFactor)); const auto offset = loopEnd - static_cast(region->loopStart(currentPromise->oversamplingFactor)) + 1; - for (auto* index = indices.begin(); index < indices.end(); ++index) { + for (auto* index = indices->begin(); index < indices->end(); ++index) { if (*index > loopEnd) { - const auto remainingElements = static_cast(std::distance(index, indices.end())); + const auto remainingElements = static_cast(std::distance(index, indices->end())); subtract(offset, { index, remainingElements }); } } @@ -451,46 +443,46 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept static_cast(region->trueSampleEnd(currentPromise->oversamplingFactor)), static_cast(source.getNumFrames()) ) - 2; - for (auto* index = indices.begin(); index < indices.end(); ++index) { + for (auto* index = indices->begin(); index < indices->end(); ++index) { if (*index >= sampleEnd) { - release(static_cast(std::distance(indices.begin(), index))); - const auto remainingElements = static_cast(std::distance(index, indices.end())); + release(static_cast(std::distance(indices->begin(), index))); + const auto remainingElements = static_cast(std::distance(index, indices->end())); if (source.getNumFrames() - 1 < region->trueSampleEnd(currentPromise->oversamplingFactor)) { DBG("[sfizz] Underflow: source available samples " << source.getNumFrames() << "/" << region->trueSampleEnd(currentPromise->oversamplingFactor) << " for sample " << region->sample); } - fill(indices.last(remainingElements), sampleEnd); - fill(leftCoeffs.last(remainingElements), 0.0f); - fill(rightCoeffs.last(remainingElements), 1.0f); + fill(indices->last(remainingElements), sampleEnd); + fill(leftCoeffs->last(remainingElements), 0.0f); + fill(rightCoeffs->last(remainingElements), 1.0f); break; } } } - auto ind = indices.data(); - auto leftCoeff = leftCoeffs.data(); - auto rightCoeff = rightCoeffs.data(); + auto ind = indices->data(); + auto leftCoeff = leftCoeffs->data(); + auto rightCoeff = rightCoeffs->data(); auto leftSource = source.getConstSpan(0); auto left = buffer.getChannel(0); if (source.getNumChannels() == 1) { - while (ind < indices.end()) { + while (ind < indices->end()) { *left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *leftCoeff, *rightCoeff); incrementAll(ind, left, leftCoeff, rightCoeff); } } else { auto right = buffer.getChannel(1); auto rightSource = source.getConstSpan(1); - while (ind < indices.end()) { + while (ind < indices->end()) { *left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *leftCoeff, *rightCoeff); *right = linearInterpolation(rightSource[*ind], rightSource[*ind + 1], *leftCoeff, *rightCoeff); incrementAll(ind, left, right, leftCoeff, rightCoeff); } } - sourcePosition = indices.back(); - floatPositionOffset = rightCoeffs.back(); + sourcePosition = indices->back(); + floatPositionOffset = rightCoeffs->back(); } void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept @@ -504,24 +496,22 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); }); } else { const auto numSamples = buffer.getNumFrames(); - auto frequencyBuffer = resources.bufferPool.getBuffer(numSamples); - auto bendBuffer = resources.bufferPool.getBuffer(numSamples); - if (!frequencyBuffer || !bendBuffer) + auto frequencies = resources.bufferPool.getBuffer(numSamples); + auto bends = resources.bufferPool.getBuffer(numSamples); + if (!frequencies || !bends) return; - auto frequencies = absl::MakeSpan(*frequencyBuffer).first(numSamples); - auto bends = absl::MakeSpan(*bendBuffer).first(numSamples); float keycenterFrequency = midiNoteFrequency(region->pitchKeycenter); - fill(frequencies, pitchRatio * keycenterFrequency); + fill(*frequencies, pitchRatio * keycenterFrequency); if (region->bendStep > 1) - pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor); + pitchBendEnvelope.getQuantizedBlock(*bends, bendStepFactor); else - pitchBendEnvelope.getBlock(bends); + pitchBendEnvelope.getBlock(*bends); - applyGain(bends, frequencies); + applyGain(*bends, *frequencies); - waveOscillator.processModulated(frequencies.data(), leftSpan.data(), buffer.getNumFrames()); + waveOscillator.processModulated(frequencies->data(), leftSpan.data(), buffer.getNumFrames()); copy(leftSpan, rightSpan); } } From a337cf5a158df85383c81df1a31dcfc7f911ea3a Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 19:11:39 +0200 Subject: [PATCH 43/72] Require explicit cast to bool --- src/sfizz/BufferPool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index 0d35f73a..54186e7a 100644 --- a/src/sfizz/BufferPool.h +++ b/src/sfizz/BufferPool.h @@ -33,7 +33,7 @@ public: : value(std::move(value)), available(available) {} T& operator*() { return value; } T* operator->() { return &value; } - operator bool() const { return available != nullptr; } + explicit operator bool() const { return available != nullptr; } ~SpanHolder() { if (available) From eac0c0e804054f4916cc979f314c08a6a37d1a4c Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 19:13:35 +0200 Subject: [PATCH 44/72] Refactor the processes in chunks --- src/sfizz/Voice.cpp | 259 ++++++++++++++++++++++++-------------------- src/sfizz/Voice.h | 18 +-- 2 files changed, 147 insertions(+), 130 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 8b401bf8..6444aecf 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -240,10 +240,15 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept fillWithData(delayed_buffer); } - if (region->isStereo) - processStereo(buffer); - else - processMono(buffer); + if (region->isStereo) { + ampStageStereo(buffer); + panStageStereo(buffer); + filterStageStereo(buffer); + } else { + ampStageMono(buffer); + filterStageMono(buffer); + panStageMono(buffer); + } if (!egEnvelope.isSmoothing()) reset(); @@ -252,11 +257,50 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept this->triggerDelay = absl::nullopt; } -void sfz::Voice::processMono(AudioSpan buffer) noexcept +void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept { + ScopedTiming logger { amplitudeDuration }; + + const auto numSamples = buffer.getNumFrames(); + const auto leftBuffer = buffer.getSpan(0); + + using namespace std::placeholders; + const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); + const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); + + auto modulationSpan = resources.bufferPool.getBuffer(numSamples); + auto tempSpan = resources.bufferPool.getBuffer(numSamples); + if (!modulationSpan || !tempSpan) + return; + + // Amplitude envelope + fill(*modulationSpan, baseGain); + resources.midiState.multiplicativeModifiers(region->amplitudeCC, *modulationSpan, *tempSpan); + DBG("Final gain: " << modulationSpan->back()); + applyGain(*modulationSpan, leftBuffer); + + // Crossfade envelopes + // crossfadeEnvelope.getBlock(modulationSpan); + fill(*modulationSpan, 1.0f); + resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, *modulationSpan, *tempSpan, xfinBind); + resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, *modulationSpan, *tempSpan, xfoutBind); + DBG("XF: " << modulationSpan->back()); + applyGain(*modulationSpan, leftBuffer); + + // Volume envelope + volumeEnvelope.getBlock(*modulationSpan); + applyGain(*modulationSpan, leftBuffer); + + // AmpEG envelope + egEnvelope.getBlock(*modulationSpan); + applyGain(*modulationSpan, leftBuffer); +} + +void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept +{ + ScopedTiming logger { amplitudeDuration }; + const auto numSamples = buffer.getNumFrames(); - auto leftBuffer = buffer.getSpan(0); - auto rightBuffer = buffer.getSpan(1); auto modulationSpan = resources.bufferPool.getBuffer(numSamples); auto tempSpan = resources.bufferPool.getBuffer(numSamples); @@ -267,132 +311,111 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); - { // Amplitude processing - ScopedTiming logger { amplitudeDuration }; + // Amplitude envelope + fill(*modulationSpan, baseGain); + resources.midiState.multiplicativeModifiers(region->amplitudeCC, *modulationSpan, *tempSpan); + buffer.applyGain(*modulationSpan); - // Amplitude envelope - fill(*modulationSpan, baseGain); - resources.midiState.multiplicativeModifiers(region->amplitudeCC, *modulationSpan, *tempSpan); - DBG("Final gain: " << modulationSpan->back()); - applyGain(modulationSpan, leftBuffer); + // Crossfade envelopes + fill(*modulationSpan, 1.0f); + resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, *modulationSpan, *tempSpan, xfinBind); + resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, *modulationSpan, *tempSpan, xfoutBind); + buffer.applyGain(*modulationSpan); - // Crossfade envelopes - // crossfadeEnvelope.getBlock(modulationSpan); - fill(*modulationSpan, 1.0f); - resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, *modulationSpan, *tempSpan, xfinBind); - resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, *modulationSpan, *tempSpan, xfoutBind); - DBG("XF: " << modulationSpan->back()); - applyGain(modulationSpan, leftBuffer); + // Volume envelope + volumeEnvelope.getBlock(*modulationSpan); + buffer.applyGain(*modulationSpan); - // Volume envelope - volumeEnvelope.getBlock(*modulationSpan); - applyGain(*modulationSpan, leftBuffer); + // AmpEG envelope + egEnvelope.getBlock(*modulationSpan); + buffer.applyGain(*modulationSpan); +} - // AmpEG envelope - egEnvelope.getBlock(*modulationSpan); - applyGain(*modulationSpan, leftBuffer); +void sfz::Voice::panStageMono(AudioSpan buffer) noexcept +{ + ScopedTiming logger { panningDuration }; + + const auto numSamples = buffer.getNumFrames(); + const auto leftBuffer = buffer.getSpan(0); + const auto rightBuffer = buffer.getSpan(1); + + auto modulationSpan = resources.bufferPool.getBuffer(numSamples); + auto tempSpan = resources.bufferPool.getBuffer(numSamples); + if (!modulationSpan || !tempSpan) + return; + + // Prepare for stereo output + copy(leftBuffer, rightBuffer); + + // Apply panning + fill(*modulationSpan, region->pan); + resources.midiState.additiveModifiers(region->panCC, *modulationSpan, *tempSpan); + DBG("Pan: " << modulationSpan->back()); + pan(*modulationSpan, leftBuffer, rightBuffer); +} + +void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept +{ + ScopedTiming logger { panningDuration }; + const auto numSamples = buffer.getNumFrames(); + const auto leftBuffer = buffer.getSpan(0); + const auto rightBuffer = buffer.getSpan(1); + + auto modulationSpan = resources.bufferPool.getBuffer(numSamples); + auto tempSpan = resources.bufferPool.getBuffer(numSamples); + if (!modulationSpan || !tempSpan) + return; + + // Apply panning + // panningModulation(*modulationSpan); + fill(*modulationSpan, region->pan); + resources.midiState.additiveModifiers(region->panCC, *modulationSpan, *tempSpan); + pan(*modulationSpan, leftBuffer, rightBuffer); + + // Apply the width/position process + // widthModulation(*modulationSpan); + fill(*modulationSpan, region->width); + resources.midiState.additiveModifiers(region->widthCC, *modulationSpan, *tempSpan); + width(*modulationSpan, leftBuffer, rightBuffer); + + // positionModulation(*modulationSpan); + fill(*modulationSpan, region->position); + resources.midiState.additiveModifiers(region->positionCC, *modulationSpan, *tempSpan); + pan(*modulationSpan, leftBuffer, rightBuffer); +} + +void sfz::Voice::filterStageMono(AudioSpan buffer) noexcept +{ + ScopedTiming logger { filterDuration }; + const auto numSamples = buffer.getNumFrames(); + const auto leftBuffer = buffer.getSpan(0); + const float* inputChannel[1] { leftBuffer.data() }; + float* outputChannel[1] { leftBuffer.data() }; + for (auto& filter: filters) { + filter->process(inputChannel, outputChannel, numSamples); } - { // Filtering and EQ - ScopedTiming logger { filterDuration }; - - const float* inputChannel[1] { leftBuffer.data() }; - float* outputChannel[1] { leftBuffer.data() }; - for (auto& filter: filters) { - filter->process(inputChannel, outputChannel, numSamples); - } - - for (auto& eq: equalizers) { - eq->process(inputChannel, outputChannel, numSamples); - } - } - - { // Panning and stereo processing - ScopedTiming logger { panningDuration }; - - // Prepare for stereo output - copy(leftBuffer, rightBuffer); - - // Apply panning - fill(*modulationSpan, region->pan); - resources.midiState.additiveModifiers(region->panCC, *modulationSpan, *tempSpan); - DBG("Pan: " << modulationSpan->back()); - pan(*modulationSpan, leftBuffer, rightBuffer); + for (auto& eq: equalizers) { + eq->process(inputChannel, outputChannel, numSamples); } } -void sfz::Voice::processStereo(AudioSpan buffer) noexcept +void sfz::Voice::filterStageStereo(AudioSpan buffer) noexcept { + ScopedTiming logger { filterDuration }; const auto numSamples = buffer.getNumFrames(); - auto leftBuffer = buffer.getSpan(0); - auto rightBuffer = buffer.getSpan(1); + const auto leftBuffer = buffer.getSpan(0); + const auto rightBuffer = buffer.getSpan(1); - auto modulationSpan = resources.bufferPool.getBuffer(numSamples); - auto tempSpan = resources.bufferPool.getBuffer(numSamples); - if (!modulationSpan || !tempSpan) - return; + const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() }; + float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() }; - using namespace std::placeholders; - const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); - const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); - - { // Amplitude processing - ScopedTiming logger { amplitudeDuration }; - - // Amplitude envelope - fill(*modulationSpan, baseGain); - resources.midiState.multiplicativeModifiers(region->amplitudeCC, *modulationSpan, *tempSpan); - buffer.applyGain(*modulationSpan); - - // Crossfade envelopes - fill(*modulationSpan, 1.0f); - resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, *modulationSpan, *tempSpan, xfinBind); - resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, *modulationSpan, *tempSpan, xfoutBind); - buffer.applyGain(*modulationSpan); - - // Volume envelope - volumeEnvelope.getBlock(*modulationSpan); - buffer.applyGain(*modulationSpan); - - // AmpEG envelope - egEnvelope.getBlock(*modulationSpan); - buffer.applyGain(*modulationSpan); + for (auto& filter: filters) { + filter->process(inputChannels, outputChannels, numSamples); } - { // Panning and stereo processing - ScopedTiming logger { panningDuration }; - - // Apply panning - // panningModulation(*modulationSpan); - fill(*modulationSpan, region->pan); - resources.midiState.additiveModifiers(region->panCC, *modulationSpan, *tempSpan); - pan(*modulationSpan, leftBuffer, rightBuffer); - - // Apply the width/position process - // widthModulation(*modulationSpan); - fill(*modulationSpan, region->width); - resources.midiState.additiveModifiers(region->widthCC, *modulationSpan, *tempSpan); - width(*modulationSpan, leftBuffer, rightBuffer); - - // positionModulation(*modulationSpan); - fill(*modulationSpan, region->position); - resources.midiState.additiveModifiers(region->positionCC, *modulationSpan, *tempSpan); - pan(*modulationSpan, leftBuffer, rightBuffer); - } - - { // Filtering and EQ - ScopedTiming logger { filterDuration }; - - const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() }; - float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() }; - - for (auto& filter: filters) { - filter->process(inputChannels, outputChannels, numSamples); - } - - for (auto& eq: equalizers) { - eq->process(inputChannels, outputChannels, numSamples); - } + for (auto& eq: equalizers) { + eq->process(inputChannels, outputChannels, numSamples); } } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index e8154546..d65a228a 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -238,18 +238,12 @@ private: * @param buffer */ void fillWithGenerator(AudioSpan buffer) noexcept; - /** - * @brief The function processing a mono sample source - * - * @param buffer - */ - void processMono(AudioSpan buffer) noexcept; - /** - * @brief The function processing a stereo sample source - * - * @param buffer - */ - void processStereo(AudioSpan buffer) noexcept; + void ampStageMono(AudioSpan buffer) noexcept; + void ampStageStereo(AudioSpan buffer) noexcept; + void panStageMono(AudioSpan buffer) noexcept; + void panStageStereo(AudioSpan buffer) noexcept; + void filterStageMono(AudioSpan buffer) noexcept; + void filterStageStereo(AudioSpan buffer) noexcept; Region* region { nullptr }; From 0da5c43bb50c333000f8833657c92ac0f36df592 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 19:58:32 +0200 Subject: [PATCH 45/72] Remove the helpers in the midi class and be explicit in the process --- src/sfizz/MidiState.h | 20 ---------------- src/sfizz/Voice.cpp | 55 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 9b028527..0d14e41c 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -154,26 +154,6 @@ public: return returnedValue; } - template)> - void additiveModifiers(const CCMap& modifiers, absl::Span output, absl::Span temp, F&& lambda = gainModifier) - { - fill(output, 0.0f); - for (auto& mod : modifiers) { - linearEnvelope(mod, temp, lambda); - add(temp, output); - } - } - - template)> - void multiplicativeModifiers(const CCMap& modifiers, absl::Span output, absl::Span temp, F&& lambda = gainModifier) - { - for (auto& mod : modifiers) { - linearEnvelope(mod, temp, lambda); - applyGain(temp, output); - } - } - - const EventVector& getEvents(int ccIdx) const noexcept; template diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 6444aecf..2cb771f6 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -275,15 +275,23 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept // Amplitude envelope fill(*modulationSpan, baseGain); - resources.midiState.multiplicativeModifiers(region->amplitudeCC, *modulationSpan, *tempSpan); + for (auto& mod : region->amplitudeCC) { + resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + applyGain(*tempSpan, *modulationSpan); + } DBG("Final gain: " << modulationSpan->back()); applyGain(*modulationSpan, leftBuffer); // Crossfade envelopes - // crossfadeEnvelope.getBlock(modulationSpan); fill(*modulationSpan, 1.0f); - resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, *modulationSpan, *tempSpan, xfinBind); - resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, *modulationSpan, *tempSpan, xfoutBind); + for (auto& mod : region->crossfadeCCInRange) { + resources.midiState.linearEnvelope(mod, *tempSpan, xfinBind); + applyGain(*tempSpan, *modulationSpan); + } + for (auto& mod : region->crossfadeCCOutRange) { + resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); + applyGain(*tempSpan, *modulationSpan); + } DBG("XF: " << modulationSpan->back()); applyGain(*modulationSpan, leftBuffer); @@ -311,15 +319,28 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); + // Amplitude envelope fill(*modulationSpan, baseGain); - resources.midiState.multiplicativeModifiers(region->amplitudeCC, *modulationSpan, *tempSpan); + for (auto& mod : region->amplitudeCC) { + resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + applyGain(*tempSpan, *modulationSpan); + } + DBG("Final gain: " << modulationSpan->back()); buffer.applyGain(*modulationSpan); + // Crossfade envelopes fill(*modulationSpan, 1.0f); - resources.midiState.multiplicativeModifiers(region->crossfadeCCInRange, *modulationSpan, *tempSpan, xfinBind); - resources.midiState.multiplicativeModifiers(region->crossfadeCCOutRange, *modulationSpan, *tempSpan, xfoutBind); + for (auto& mod : region->crossfadeCCInRange) { + resources.midiState.linearEnvelope(mod, *tempSpan, xfinBind); + applyGain(*tempSpan, *modulationSpan); + } + for (auto& mod : region->crossfadeCCOutRange) { + resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); + applyGain(*tempSpan, *modulationSpan); + } + DBG("XF: " << modulationSpan->back()); buffer.applyGain(*modulationSpan); // Volume envelope @@ -349,7 +370,10 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept // Apply panning fill(*modulationSpan, region->pan); - resources.midiState.additiveModifiers(region->panCC, *modulationSpan, *tempSpan); + for (auto& mod : region->panCC) { + resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + add(*tempSpan, *modulationSpan); + } DBG("Pan: " << modulationSpan->back()); pan(*modulationSpan, leftBuffer, rightBuffer); } @@ -369,18 +393,27 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // Apply panning // panningModulation(*modulationSpan); fill(*modulationSpan, region->pan); - resources.midiState.additiveModifiers(region->panCC, *modulationSpan, *tempSpan); + for (auto& mod : region->panCC) { + resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + add(*tempSpan, *modulationSpan); + } pan(*modulationSpan, leftBuffer, rightBuffer); // Apply the width/position process // widthModulation(*modulationSpan); fill(*modulationSpan, region->width); - resources.midiState.additiveModifiers(region->widthCC, *modulationSpan, *tempSpan); + for (auto& mod : region->widthCC) { + resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + add(*tempSpan, *modulationSpan); + } width(*modulationSpan, leftBuffer, rightBuffer); // positionModulation(*modulationSpan); fill(*modulationSpan, region->position); - resources.midiState.additiveModifiers(region->positionCC, *modulationSpan, *tempSpan); + for (auto& mod : region->positionCC) { + resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + add(*tempSpan, *modulationSpan); + } pan(*modulationSpan, leftBuffer, rightBuffer); } From 1f7786cc74c5a5c4d87ddfe0d3508cd756506677 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 20:07:54 +0200 Subject: [PATCH 46/72] Be explicit in the filter/EQ processes --- src/sfizz/EQPool.cpp | 32 ++++++++++++++++++++++++-------- src/sfizz/FilterPool.cpp | 30 ++++++++++++++++++++++-------- src/sfizz/MidiState.h | 31 ------------------------------- 3 files changed, 46 insertions(+), 47 deletions(-) diff --git a/src/sfizz/EQPool.cpp b/src/sfizz/EQPool.cpp index 81821e1e..a9f8c2bc 100644 --- a/src/sfizz/EQPool.cpp +++ b/src/sfizz/EQPool.cpp @@ -28,12 +28,20 @@ void sfz::EQHolder::setup(const EQDescription& description, unsigned numChannels baseGain = description.gain + velocity * description.vel2gain; // Setup the modulated values - lastFrequency = baseFrequency + midiState.fastAdditiveModifiers(description.frequencyCC); + lastFrequency = baseFrequency; + for (auto& mod : description.frequencyCC) + lastFrequency += midiState.getCCValue(mod.cc) * mod.value; lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); - lastBandwidth = baseBandwidth + midiState.fastAdditiveModifiers(description.bandwidthCC); + + lastBandwidth = baseBandwidth; + for (auto& mod : description.bandwidthCC) + lastBandwidth += midiState.getCCValue(mod.cc) * mod.value; lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); - lastGain = baseGain + midiState.fastAdditiveModifiers(description.gainCC); - lastGain = Default::eqGainRange.clamp(lastGain); + + lastGain = baseGain; + for (auto& mod : description.gainCC) + lastGain += midiState.getCCValue(mod.cc) * mod.value; + lastGain = Default::filterGainRange.clamp(lastGain); // Initialize the EQ eq.prepare(lastFrequency, lastBandwidth, lastGain); @@ -53,12 +61,20 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF // TODO: Once the midistate envelopes are done, add modulation in there! // For now we take the last value - lastFrequency = baseFrequency + midiState.fastAdditiveModifiers(description->frequencyCC); + lastFrequency = baseFrequency; + for (auto& mod : description->frequencyCC) + lastFrequency += midiState.getCCValue(mod.cc) * mod.value; lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); - lastBandwidth = baseBandwidth + midiState.fastAdditiveModifiers(description->bandwidthCC); + + lastBandwidth = baseBandwidth; + for (auto& mod : description->bandwidthCC) + lastBandwidth += midiState.getCCValue(mod.cc) * mod.value; lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); - lastGain = baseGain + midiState.fastAdditiveModifiers(description->gainCC); - lastGain = Default::eqGainRange.clamp(lastGain); + + lastGain = baseGain; + for (auto& mod : description->gainCC) + lastGain += midiState.getCCValue(mod.cc) * mod.value; + lastGain = Default::filterGainRange.clamp(lastGain); if (lastGain == 0.0f) { justCopy(); diff --git a/src/sfizz/FilterPool.cpp b/src/sfizz/FilterPool.cpp index 4e89e802..f28002f4 100644 --- a/src/sfizz/FilterPool.cpp +++ b/src/sfizz/FilterPool.cpp @@ -40,11 +40,19 @@ void sfz::FilterHolder::setup(const FilterDescription& description, unsigned num baseResonance = description.resonance; // Setup the modulated values - lastCutoff = baseCutoff * midiState.fastMultiplicativeModifiers(description.cutoffCC, multiplyByCentsModifier); + lastCutoff = baseCutoff; + for (auto& mod : description.cutoffCC) + lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value); lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); - lastResonance = baseResonance + midiState.fastAdditiveModifiers(description.resonanceCC); + + lastResonance = baseResonance; + for (auto& mod : description.resonanceCC) + lastResonance += midiState.getCCValue(mod.cc) * mod.value; lastResonance = Default::filterResonanceRange.clamp(lastResonance); - lastGain = baseGain + midiState.fastAdditiveModifiers(description.gainCC); + + lastGain = baseGain; + for (auto& mod : description.gainCC) + lastGain += midiState.getCCValue(mod.cc) * mod.value; lastGain = Default::filterGainRange.clamp(lastGain); // Initialize the filter @@ -62,13 +70,19 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned // TODO: Once the midistate envelopes are done, add modulation in there! // For now we take the last value // TODO: the template deduction could be automatic here? - lastCutoff = baseCutoff * midiState.fastMultiplicativeModifiers(description->cutoffCC, [](const int& modifier, float value) -> float { - return value * centsFactor(modifier); - }); + lastCutoff = baseCutoff; + for (auto& mod : description->cutoffCC) + lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value); lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); - lastResonance = baseResonance + midiState.fastAdditiveModifiers(description->resonanceCC); + + lastResonance = baseResonance; + for (auto& mod : description->resonanceCC) + lastResonance += midiState.getCCValue(mod.cc) * mod.value; lastResonance = Default::filterResonanceRange.clamp(lastResonance); - lastGain = baseGain + midiState.fastAdditiveModifiers(description->gainCC); + + lastGain = baseGain; + for (auto& mod : description->gainCC) + lastGain += midiState.getCCValue(mod.cc) * mod.value; lastGain = Default::filterGainRange.clamp(lastGain); filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames); diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 0d14e41c..7d4078fb 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -123,37 +123,6 @@ public: */ void resetAllControllers(int delay) noexcept; - /** - * @brief Modulate a value using the last entered CCs in the midiState - * - * @tparam T - * @tparam U - * @param value the base value - * @param modifiers the list of CC modifiers - * @param validRange a range to clamp the output - * @param lambda the function to apply for each modifier - * @return T - */ - template)> - float fastAdditiveModifiers(const CCMap& modifiers, F&& lambda = gainModifier) const noexcept - { - float returnedValue { 0.0f }; - for (auto& mod: modifiers) { - returnedValue += lambda(getCCValue(mod.cc), mod.value); - } - return returnedValue; - } - - template)> - float fastMultiplicativeModifiers(const CCMap& modifiers, F&& lambda = gainModifier) const noexcept - { - float returnedValue { 1.0f }; - for (auto& mod: modifiers) { - returnedValue *= lambda(getCCValue(mod.cc), mod.value); - } - return returnedValue; - } - const EventVector& getEvents(int ccIdx) const noexcept; template From 629a2ae8a8ad84ac22eb527b0a4780888862288f Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 20:16:43 +0200 Subject: [PATCH 47/72] Forwarding on SpanHolder --- src/sfizz/BufferPool.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index 54186e7a..c0a378c7 100644 --- a/src/sfizz/BufferPool.h +++ b/src/sfizz/BufferPool.h @@ -29,8 +29,8 @@ public: SpanHolder& operator=(const SpanHolder&) = delete; SpanHolder(SpanHolder&&) = delete; SpanHolder& operator=(SpanHolder&&) = delete; - SpanHolder(T value, int* available) - : value(std::move(value)), available(available) {} + SpanHolder(T&& value, int* available) + : value(std::forward(value)), available(available) {} T& operator*() { return value; } T* operator->() { return &value; } explicit operator bool() const { return available != nullptr; } From a1cacea8fbeb224f31dc841076bce4c8c2028b0a Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 20:32:27 +0200 Subject: [PATCH 48/72] Explicit move to null the return pointer --- src/sfizz/BufferPool.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index c0a378c7..0f58d529 100644 --- a/src/sfizz/BufferPool.h +++ b/src/sfizz/BufferPool.h @@ -27,8 +27,18 @@ public: SpanHolder() {} SpanHolder(const SpanHolder&) = delete; SpanHolder& operator=(const SpanHolder&) = delete; - SpanHolder(SpanHolder&&) = delete; - SpanHolder& operator=(SpanHolder&&) = delete; + SpanHolder(SpanHolder&& other) + { + this->value = other.value; + this->available = other.available; + other.available = nullptr; + } + SpanHolder& operator=(SpanHolder&& other) + { + this->value = other.value; + this->available = other.available; + other.available = nullptr; + } SpanHolder(T&& value, int* available) : value(std::forward(value)), available(available) {} T& operator*() { return value; } From bc4d88b55aa0bab55eff08160bbca4acd2e0a44a Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 20:32:42 +0200 Subject: [PATCH 49/72] Clang-tidy 9 errors --- src/sfizz/FilePool.cpp | 2 +- src/sfizz/MidiState.cpp | 2 +- src/sfizz/MidiState.h | 2 +- src/sfizz/Region.cpp | 4 ++-- src/sfizz/Synth.cpp | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 3cd57684..9b43a2be 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -273,7 +273,7 @@ sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) n auto promise = emptyPromises.back(); promise->filename = preloaded->first; promise->preloadedData = preloaded->second.preloadedData; - promise->sampleRate = preloaded->second.information.sampleRate; + promise->sampleRate = static_cast(preloaded->second.information.sampleRate); promise->oversamplingFactor = oversamplingFactor; promise->creationTime = std::chrono::high_resolution_clock::now(); diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index b4267f12..434423b0 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -137,7 +137,7 @@ void sfz::MidiState::reset() noexcept void sfz::MidiState::resetAllControllers(int delay) noexcept { - for (unsigned ccIdx = 0; ccIdx < config::numCCs; ++ccIdx) + for (int ccIdx = 0; ccIdx < config::numCCs; ++ccIdx) ccEvent(delay, ccIdx, 0.0f); pitchBend = 0; diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 7d4078fb..6c634838 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -176,7 +176,7 @@ private: * Pitch bend status */ int pitchBend { 0 }; - double sampleRate { config::defaultSampleRate }; + float sampleRate { config::defaultSampleRate }; int samplesPerBlock { config::defaultSamplesPerBlock }; unsigned internalClock { 0 }; }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index a499bc10..bcc7196d 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -970,7 +970,7 @@ float sfz::Region::getBasePitchVariation(int noteNumber, float velocity) const n auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center pitchVariationInCents += tune; // sample tuning pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose - pitchVariationInCents += static_cast(velocity * pitchVeltrack); // track velocity + pitchVariationInCents += static_cast(velocity) * pitchVeltrack; // track velocity pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes return centsFactor(pitchVariationInCents); } @@ -994,7 +994,7 @@ float sfz::Region::getPhase() const noexcept float phase; if (oscillatorPhase >= 0) { phase = oscillatorPhase * (1.0f / 360.0f); - phase -= static_cast(phase); + phase -= static_cast(static_cast(phase)); } else { std::uniform_real_distribution phaseDist { 0.0001f, 0.9999f }; phase = phaseDist(Random::randomGenerator); diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index b2640738..caa51ca0 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -407,13 +407,13 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) noteActivationLists[note].push_back(region); } - for (unsigned cc = 0; cc < config::numCCs; cc++) { + for (int cc = 0; cc < config::numCCs; cc++) { if (region->ccTriggers.contains(cc) || region->ccConditions.contains(cc)) ccActivationLists[cc].push_back(region); } // Defaults - for (unsigned cc = 0; cc < config::numCCs; cc++) { + for (int cc = 0; cc < config::numCCs; cc++) { region->registerCC(cc, resources.midiState.getCCValue(cc)); } @@ -1026,12 +1026,12 @@ void sfz::Synth::resetAllControllers(int delay) noexcept resources.midiState.resetAllControllers(delay); for (auto& voice : voices) { voice->registerPitchWheel(delay, 0); - for (unsigned cc = 0; cc < config::numCCs; ++cc) + for (int cc = 0; cc < config::numCCs; ++cc) voice->registerCC(delay, cc, 0.0f); } for (auto& region : regions) { - for (unsigned cc = 0; cc < config::numCCs; ++cc) + for (int cc = 0; cc < config::numCCs; ++cc) region->registerCC(cc, 0.0f); } } From 922cbde6c53d954a71161c65724f4359654df166 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 21:07:33 +0200 Subject: [PATCH 50/72] Store pitch bend internally as floats --- src/sfizz/Defaults.h | 5 +++-- src/sfizz/MidiState.cpp | 22 ++++++++++++++++++---- src/sfizz/MidiState.h | 5 +++-- src/sfizz/Region.cpp | 8 +++++--- src/sfizz/Region.h | 4 ++-- src/sfizz/SfzHelpers.h | 2 +- src/sfizz/Synth.cpp | 7 ++++--- src/sfizz/Voice.cpp | 16 +++++----------- src/sfizz/Voice.h | 2 +- tests/MidiStateT.cpp | 18 +++++++++--------- tests/RegionActivationT.cpp | 6 +++--- tests/RegionT.cpp | 20 ++++++++++++-------- 12 files changed, 66 insertions(+), 49 deletions(-) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index e76a6805..ef899329 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -57,6 +57,7 @@ namespace Default // common defaults 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 @@ -79,7 +80,8 @@ namespace Default constexpr Range midiChannelRange { 0, 15 }; constexpr Range ccNumberRange { 0, config::numCCs }; constexpr auto ccValueRange = normalizedRange; - constexpr Range bendRange { -8192, 8192 }; + constexpr Range bendRange = { -8192, 8192 }; + constexpr Range bendValueRange = symmetricNormalizedRange; constexpr int bend { 0 }; constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current }; @@ -107,7 +109,6 @@ namespace Default constexpr float pan { 0.0 }; constexpr Range panRange { -100.0, 100.0 }; constexpr Range panCCRange { -200.0, 200.0 }; - constexpr Range symmetricNormalizedRange { -1.0, 1.0 }; constexpr float position { 0.0 }; constexpr Range positionRange { -100.0, 100.0 }; constexpr Range positionCCRange { -200.0, 200.0 }; diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 434423b0..94964722 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -57,6 +57,10 @@ void sfz::MidiState::advanceTime(int numSamples) noexcept ccEvents.front().delay = 0; ccEvents.resize(1); } + ASSERT(!pitchEvents.empty()); + pitchEvents.front().value = pitchEvents.back().value; + pitchEvents.front().delay = 0; + pitchEvents.resize(1); } void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept @@ -89,16 +93,22 @@ float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept } -void sfz::MidiState::pitchBendEvent(int delay, int pitchBendValue) noexcept +void sfz::MidiState::pitchBendEvent(int delay, float pitchBendValue) noexcept { - ASSERT(pitchBendValue >= -8192 && pitchBendValue <= 8192); + ASSERT(pitchBendValue >= -1.0f && pitchBendValue <= 1.0f); pitchBend = pitchBendValue; + const auto insertionPoint = absl::c_upper_bound(pitchEvents, delay, MidiEventDelayComparator{}); + if (insertionPoint == pitchEvents.end() || insertionPoint->delay != delay) + pitchEvents.insert(insertionPoint, { delay, pitchBendValue }); + else + insertionPoint->value = pitchBendValue; } -int sfz::MidiState::getPitchBend() const noexcept +float sfz::MidiState::getPitchBend() const noexcept { - return pitchBend; + ASSERT(pitchEvents.size() > 0); + return pitchEvents.back().value; } void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept @@ -128,6 +138,9 @@ void sfz::MidiState::reset() noexcept ccEvents.push_back({ 0, 0.0f }); } + pitchEvents.clear(); + pitchEvents.push_back({ 0, 0.0f }); + pitchBend = 0; activeNotes = 0; internalClock = 0; @@ -141,6 +154,7 @@ void sfz::MidiState::resetAllControllers(int delay) noexcept ccEvent(delay, ccIdx, 0.0f); pitchBend = 0; + pitchBendEvent(delay, 0.0f); } const sfz::EventVector& sfz::MidiState::getEvents(int ccIdx) const noexcept diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 6c634838..df8061cc 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -79,14 +79,14 @@ public: * * @param pitchBendValue */ - void pitchBendEvent(int delay, int pitchBendValue) noexcept; + void pitchBendEvent(int delay, float pitchBendValue) noexcept; /** * @brief Get the pitch bend status * @return int */ - int getPitchBend() const noexcept; + float getPitchBend() const noexcept; /** * @brief Register a CC event @@ -176,6 +176,7 @@ private: * Pitch bend status */ int pitchBend { 0 }; + EventVector pitchEvents; float sampleRate { config::defaultSampleRate }; int samplesPerBlock { config::defaultSamplesPerBlock }; unsigned internalClock { 0 }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index bcc7196d..1bb17db9 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -165,10 +165,12 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) // Region logic: MIDI conditions case hash("lobend"): - setRangeStartFromOpcode(opcode, bendRange, Default::bendRange); + if (auto value = readOpcode(opcode.value, Default::bendRange)) + bendRange.setStart(normalizeBend(*value)); break; case hash("hibend"): - setRangeEndFromOpcode(opcode, bendRange, Default::bendRange); + if (auto value = readOpcode(opcode.value, Default::bendRange)) + bendRange.setEnd(normalizeBend(*value)); break; case hash("locc&"): if (opcode.parameters.back() > config::numCCs) @@ -937,7 +939,7 @@ bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept return false; } -void sfz::Region::registerPitchWheel(int pitch) noexcept +void sfz::Region::registerPitchWheel(float pitch) noexcept { if (bendRange.containsWithEnd(pitch)) pitchSwitched = true; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 3aac8ff8..8cd8e3ad 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -113,7 +113,7 @@ struct Region { * * @param pitch */ - void registerPitchWheel(int pitch) noexcept; + void registerPitchWheel(float pitch) noexcept; /** * @brief Register a new aftertouch event. * @@ -248,7 +248,7 @@ struct Region { Range velocityRange { Default::velocityRange }; // hivel and lovel // Region logic: MIDI conditions - Range bendRange { Default::bendRange }; // hibend and lobend + Range bendRange { Default::bendValueRange }; // hibend and lobend CCMap> ccConditions { Default::ccValueRange }; Range keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey absl::optional keyswitch {}; // sw_last diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index d991e795..601d676f 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -189,7 +189,7 @@ constexpr float normalizePercents(T percentValue) */ constexpr float normalizeBend(float bendValue) { - return min(max(bendValue, -8191.0f), 8191.0f) / 8191.0f; + return clamp(bendValue, -8191.0f, 8191.0f) / 8191.0f; } namespace literals diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index caa51ca0..4ac28d54 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -778,16 +778,17 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept { ASSERT(pitch <= 8192); ASSERT(pitch >= -8192); + const auto normalizedPitch = normalizeBend(pitch); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.pitchBendEvent(delay, pitch); + resources.midiState.pitchBendEvent(delay, normalizedPitch); for (auto& region : regions) { - region->registerPitchWheel(pitch); + region->registerPitchWheel(normalizedPitch); } for (auto& voice : voices) { - voice->registerPitchWheel(delay, pitch); + voice->registerPitchWheel(delay, normalizedPitch); } } void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 2cb771f6..4dc90a1c 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -85,12 +85,11 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, if (triggerType != TriggerType::CC) baseGain *= region->getNoteGain(number, value); - pitchBendEnvelope.setFunction([region](float pitchValue){ - const auto normalizedBend = normalizeBend(pitchValue); - const auto bendInCents = normalizedBend > 0.0f ? normalizedBend * static_cast(region->bendUp) : -normalizedBend * static_cast(region->bendDown); + pitchBendEnvelope.setFunction([region](float bend){ + const auto bendInCents = bend > 0.0f ? bend * static_cast(region->bendUp) : -bend * static_cast(region->bendDown); return centsFactor(bendInCents); }); - pitchBendEnvelope.reset(static_cast(resources.midiState.getPitchBend())); + pitchBendEnvelope.reset(resources.midiState.getPitchBend()); // Check that we can handle the number of filters; filters should be cleared here ASSERT((filters.capacity() - filters.size()) >= region->filters.size()); @@ -183,12 +182,12 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept } } -void sfz::Voice::registerPitchWheel(int delay, int pitch) noexcept +void sfz::Voice::registerPitchWheel(int delay, float pitch) noexcept { if (state == State::idle) return; - pitchBendEnvelope.registerEvent(delay, static_cast(pitch)); + pitchBendEnvelope.registerEvent(delay, pitch * 8191.0f); } void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept @@ -279,7 +278,6 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); applyGain(*tempSpan, *modulationSpan); } - DBG("Final gain: " << modulationSpan->back()); applyGain(*modulationSpan, leftBuffer); // Crossfade envelopes @@ -292,7 +290,6 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); applyGain(*tempSpan, *modulationSpan); } - DBG("XF: " << modulationSpan->back()); applyGain(*modulationSpan, leftBuffer); // Volume envelope @@ -326,7 +323,6 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); applyGain(*tempSpan, *modulationSpan); } - DBG("Final gain: " << modulationSpan->back()); buffer.applyGain(*modulationSpan); @@ -340,7 +336,6 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); applyGain(*tempSpan, *modulationSpan); } - DBG("XF: " << modulationSpan->back()); buffer.applyGain(*modulationSpan); // Volume envelope @@ -374,7 +369,6 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); add(*tempSpan, *modulationSpan); } - DBG("Pan: " << modulationSpan->back()); pan(*modulationSpan, leftBuffer, rightBuffer); } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index d65a228a..242d045d 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -107,7 +107,7 @@ public: * @param delay * @param pitch */ - void registerPitchWheel(int delay, int pitch) noexcept; + void registerPitchWheel(int delay, float pitch) noexcept; /** * @brief Register an aftertouch event; for now this does nothing * diff --git a/tests/MidiStateT.cpp b/tests/MidiStateT.cpp index 6a2af4c0..a47a14c8 100644 --- a/tests/MidiStateT.cpp +++ b/tests/MidiStateT.cpp @@ -37,20 +37,20 @@ TEST_CASE("[MidiState] Set and get CCs") TEST_CASE("[MidiState] Set and get pitch bends") { sfz::MidiState state; - state.pitchBendEvent(0, 894); - REQUIRE(state.getPitchBend() == 894); - state.pitchBendEvent(0, 0); - REQUIRE(state.getPitchBend() == 0); + state.pitchBendEvent(0, 0.5f); + REQUIRE(state.getPitchBend() == 0.5f); + state.pitchBendEvent(0, 0.0f); + REQUIRE(state.getPitchBend() == 0.0f); } TEST_CASE("[MidiState] Reset") { sfz::MidiState state; - state.pitchBendEvent(0, 894); + state.pitchBendEvent(0, 0.7f); state.noteOnEvent(0, 64, 24_norm); state.ccEvent(0, 123, 124_norm); state.reset(); - REQUIRE(state.getPitchBend() == 0); + REQUIRE(state.getPitchBend() == 0.0f); REQUIRE(state.getNoteVelocity(64) == 0_norm); REQUIRE(state.getCCValue(123) == 0_norm); } @@ -58,12 +58,12 @@ TEST_CASE("[MidiState] Reset") TEST_CASE("[MidiState] Reset all controllers") { sfz::MidiState state; - state.pitchBendEvent(20, 894); + state.pitchBendEvent(20, 0.7f); state.ccEvent(10, 122, 124_norm); - REQUIRE(state.getPitchBend() == 894); + REQUIRE(state.getPitchBend() == 0.7f); REQUIRE(state.getCCValue(122) == 124_norm); state.resetAllControllers(30); - REQUIRE(state.getPitchBend() == 0); + REQUIRE(state.getPitchBend() == 0.0f); REQUIRE(state.getCCValue(122) == 0_norm); REQUIRE(state.getCCValue(4) == 0_norm); } diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index c2aae1d1..e65934b7 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -77,11 +77,11 @@ TEST_CASE("Region activation", "Region tests") region.parseOpcode({ "hibend", "243" }); region.registerPitchWheel(0); REQUIRE(!region.isSwitchedOn()); - region.registerPitchWheel(56); + region.registerPitchWheel(sfz::normalizeBend(56)); REQUIRE(region.isSwitchedOn()); - region.registerPitchWheel(243); + region.registerPitchWheel(sfz::normalizeBend(243)); REQUIRE(region.isSwitchedOn()); - region.registerPitchWheel(245); + region.registerPitchWheel(sfz::normalizeBend(245)); REQUIRE(!region.isSwitchedOn()); } diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index c450b7fb..66a959a8 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -223,19 +223,23 @@ TEST_CASE("[Region] Parsing opcodes") SECTION("lobend, hibend") { - REQUIRE(region.bendRange == sfz::Range(-8192, 8192)); - region.parseOpcode({ "lobend", "4" }); - REQUIRE(region.bendRange == sfz::Range(4, 8192)); + REQUIRE(region.bendRange == sfz::Range(-1.0f, 1.0f)); + region.parseOpcode({ "lobend", "400" }); + REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(400))); + REQUIRE(region.bendRange.getEnd() == 1.0_a); region.parseOpcode({ "lobend", "-128" }); - REQUIRE(region.bendRange == sfz::Range(-128, 8192)); + REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(-128))); + REQUIRE(region.bendRange.getEnd() == 1.0_a); region.parseOpcode({ "lobend", "-10000" }); - REQUIRE(region.bendRange == sfz::Range(-8192, 8192)); + REQUIRE(region.bendRange == sfz::Range(-1.0f, 1.0f)); region.parseOpcode({ "hibend", "13" }); - REQUIRE(region.bendRange == sfz::Range(-8192, 13)); + REQUIRE(region.bendRange.getStart() == -1.0_a); + REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(13))); region.parseOpcode({ "hibend", "-1" }); - REQUIRE(region.bendRange == sfz::Range(-8192, -1)); + REQUIRE(region.bendRange.getStart() == -1.0_a); + REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(-1))); region.parseOpcode({ "hibend", "10000" }); - REQUIRE(region.bendRange == sfz::Range(-8192, 8192)); + REQUIRE(region.bendRange == sfz::Range(-1.0f, 1.0f)); } SECTION("locc, hicc") From 1523a14ee83cc4a21a678b04a20feddeca7cd1f5 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 21:23:59 +0200 Subject: [PATCH 51/72] linearEnvelope is now a free function --- src/sfizz/MidiState.h | 21 --------------------- src/sfizz/SfzHelpers.h | 19 +++++++++++++++++++ src/sfizz/Voice.cpp | 42 +++++++++++++++++++++++------------------- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index df8061cc..f09147f5 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -8,8 +8,6 @@ #include #include "CCMap.h" #include "Range.h" -#include "absl/types/span.h" -#include "SIMDHelpers.h" namespace sfz { @@ -125,25 +123,6 @@ public: const EventVector& getEvents(int ccIdx) const noexcept; - template - void linearEnvelope(T&& modifier, absl::Span envelope, F&& lambda) const - { - const auto eventList = getEvents(modifier.cc); - ASSERT(eventList.size() > 0); - ASSERT(eventList[0].delay == 0); - - auto lastValue = lambda(modifier.value, eventList[0].value); - auto lastDelay = eventList[0].delay; - for (unsigned i = 1; i < eventList.size(); ++ i) { - const auto event = eventList[i]; - const auto length = event.delay - lastDelay; - const auto step = (lambda(modifier.value, event.value) - lastValue)/ length; - lastValue = linearRamp(envelope.subspan(lastDelay, length), lastValue, step); - lastDelay += length; - } - fill(envelope.subspan(lastDelay), lastValue); - } - private: diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 601d676f..15177dac 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -13,6 +13,7 @@ #include "Macros.h" #include "Config.h" #include "MathHelpers.h" +#include "SIMDHelpers.h" #include "absl/meta/type_traits.h" #include "Defaults.h" @@ -342,5 +343,23 @@ float crossfadeOut(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCur return 1.0f; } +template +void linearEnvelope(const EventVector& events, absl::Span envelope, F&& lambda) +{ + ASSERT(events.size() > 0); + ASSERT(events[0].delay == 0); + + auto lastValue = lambda(events[0].value); + auto lastDelay = events[0].delay; + for (unsigned i = 1; i < events.size(); ++ i) { + const auto event = events[i]; + const auto length = event.delay - lastDelay; + const auto step = (lambda(event.value) - lastValue)/ length; + lastValue = linearRamp(envelope.subspan(lastDelay, length), lastValue, step); + lastDelay += length; + } + fill(envelope.subspan(lastDelay), lastValue); +} + } // namespace sfz diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 4dc90a1c..c08b6d79 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -262,10 +262,7 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept const auto numSamples = buffer.getNumFrames(); const auto leftBuffer = buffer.getSpan(0); - - using namespace std::placeholders; - const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); - const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); + const auto xfCurve = region->crossfadeCCCurve; auto modulationSpan = resources.bufferPool.getBuffer(numSamples); auto tempSpan = resources.bufferPool.getBuffer(numSamples); @@ -275,7 +272,8 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept // Amplitude envelope fill(*modulationSpan, baseGain); for (auto& mod : region->amplitudeCC) { - resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); applyGain(*tempSpan, *modulationSpan); } applyGain(*modulationSpan, leftBuffer); @@ -283,11 +281,13 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept // Crossfade envelopes fill(*modulationSpan, 1.0f); for (auto& mod : region->crossfadeCCInRange) { - resources.midiState.linearEnvelope(mod, *tempSpan, xfinBind); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } for (auto& mod : region->crossfadeCCOutRange) { - resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } applyGain(*modulationSpan, leftBuffer); @@ -307,20 +307,18 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept const auto numSamples = buffer.getNumFrames(); + const auto xfCurve = region->crossfadeCCCurve; + auto modulationSpan = resources.bufferPool.getBuffer(numSamples); auto tempSpan = resources.bufferPool.getBuffer(numSamples); if (!modulationSpan || !tempSpan) return; - using namespace std::placeholders; - const auto xfinBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); - const auto xfoutBind = std::bind(crossfadeIn, _1, _2, region->crossfadeCCCurve); - - // Amplitude envelope fill(*modulationSpan, baseGain); for (auto& mod : region->amplitudeCC) { - resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); applyGain(*tempSpan, *modulationSpan); } buffer.applyGain(*modulationSpan); @@ -329,11 +327,13 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept // Crossfade envelopes fill(*modulationSpan, 1.0f); for (auto& mod : region->crossfadeCCInRange) { - resources.midiState.linearEnvelope(mod, *tempSpan, xfinBind); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } for (auto& mod : region->crossfadeCCOutRange) { - resources.midiState.linearEnvelope(mod, *tempSpan, xfoutBind); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } buffer.applyGain(*modulationSpan); @@ -366,7 +366,8 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept // Apply panning fill(*modulationSpan, region->pan); for (auto& mod : region->panCC) { - resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); @@ -388,7 +389,8 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // panningModulation(*modulationSpan); fill(*modulationSpan, region->pan); for (auto& mod : region->panCC) { - resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); @@ -397,7 +399,8 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // widthModulation(*modulationSpan); fill(*modulationSpan, region->width); for (auto& mod : region->widthCC) { - resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); } width(*modulationSpan, leftBuffer, rightBuffer); @@ -405,7 +408,8 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // positionModulation(*modulationSpan); fill(*modulationSpan, region->position); for (auto& mod : region->positionCC) { - resources.midiState.linearEnvelope(mod, *tempSpan, gainModifier); + const auto events = resources.midiState.getEvents(mod.cc); + linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); } pan(*modulationSpan, leftBuffer, rightBuffer); From 9e3f496f9e43e1441348f9f3d1cd030674ad4823 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 21:32:50 +0200 Subject: [PATCH 52/72] Add multiplicative envelope --- src/sfizz/SfzHelpers.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 15177dac..5aea9bdb 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -361,5 +361,25 @@ void linearEnvelope(const EventVector& events, absl::Span envelope, F&& l 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); + + auto lastValue = lambda(events[0].value); + auto lastDelay = events[0].delay; + for (unsigned i = 1; i < events.size(); ++ i) { + const auto event = events[i]; + const auto length = event.delay - lastDelay; + const auto nextValue = lambda(event.value); + const auto step = std::exp((std::log(nextValue) - std::log(currentValue)) / length); + multiplicativeRamp(envelope.subspan(lastDelay, length), lastValue, step); + lastValue = nextValue; + lastDelay += length; + } + fill(envelope.subspan(lastDelay), lastValue); +} + } // namespace sfz From 088da9df65f1dc182de6b9f9fa98f1b80630c28b Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 21:51:21 +0200 Subject: [PATCH 53/72] multiplicativeEnvelope free function for pitch --- src/sfizz/MidiState.cpp | 7 +++++- src/sfizz/MidiState.h | 3 ++- src/sfizz/SfzHelpers.h | 2 +- src/sfizz/Voice.cpp | 55 ++++++++++++++++++++++++++--------------- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 94964722..a2e4107c 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -157,10 +157,15 @@ void sfz::MidiState::resetAllControllers(int delay) noexcept pitchBendEvent(delay, 0.0f); } -const sfz::EventVector& sfz::MidiState::getEvents(int ccIdx) const noexcept +const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept { if (ccIdx < 0 || ccIdx > config::numCCs) return nullEvent; return cc[ccIdx]; } + +const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept +{ + return pitchEvents; +} diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index f09147f5..2c6f2496 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -121,7 +121,8 @@ public: */ void resetAllControllers(int delay) noexcept; - const EventVector& getEvents(int ccIdx) const noexcept; + const EventVector& getCCEvents(int ccIdx) const noexcept; + const EventVector& getPitchEvents() const noexcept; private: diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 5aea9bdb..c7c55975 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -373,7 +373,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop const auto event = events[i]; const auto length = event.delay - lastDelay; const auto nextValue = lambda(event.value); - const auto step = std::exp((std::log(nextValue) - std::log(currentValue)) / length); + const auto step = std::exp((std::log(nextValue) - std::log(lastValue)) / length); multiplicativeRamp(envelope.subspan(lastDelay, length), lastValue, step); lastValue = nextValue; lastDelay += length; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index c08b6d79..9d1ee9c1 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -187,7 +187,7 @@ void sfz::Voice::registerPitchWheel(int delay, float pitch) noexcept if (state == State::idle) return; - pitchBendEnvelope.registerEvent(delay, pitch * 8191.0f); + pitchBendEnvelope.registerEvent(delay, pitch); } void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept @@ -272,7 +272,7 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept // Amplitude envelope fill(*modulationSpan, baseGain); for (auto& mod : region->amplitudeCC) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); applyGain(*tempSpan, *modulationSpan); } @@ -281,12 +281,12 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept // Crossfade envelopes fill(*modulationSpan, 1.0f); for (auto& mod : region->crossfadeCCInRange) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } for (auto& mod : region->crossfadeCCOutRange) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } @@ -317,25 +317,26 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept // Amplitude envelope fill(*modulationSpan, baseGain); for (auto& mod : region->amplitudeCC) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); applyGain(*tempSpan, *modulationSpan); } buffer.applyGain(*modulationSpan); - // Crossfade envelopes fill(*modulationSpan, 1.0f); for (auto& mod : region->crossfadeCCInRange) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } + for (auto& mod : region->crossfadeCCOutRange) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } + buffer.applyGain(*modulationSpan); // Volume envelope @@ -366,7 +367,7 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept // Apply panning fill(*modulationSpan, region->pan); for (auto& mod : region->panCC) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); } @@ -389,7 +390,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // panningModulation(*modulationSpan); fill(*modulationSpan, region->pan); for (auto& mod : region->panCC) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); } @@ -399,7 +400,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // widthModulation(*modulationSpan); fill(*modulationSpan, region->width); for (auto& mod : region->widthCC) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); } @@ -408,7 +409,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // positionModulation(*modulationSpan); fill(*modulationSpan, region->position); for (auto& mod : region->positionCC) { - const auto events = resources.midiState.getEvents(mod.cc); + const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); } @@ -472,10 +473,17 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept return; fill(*jumps, pitchRatio * speedRatio); - if (region->bendStep > 1) - pitchBendEnvelope.getQuantizedBlock(*bends, bendStepFactor); - else - pitchBendEnvelope.getBlock(*bends); + // if (region->bendStep > 1) + // pitchBendEnvelope.getQuantizedBlock(*bends, bendStepFactor); + // else + // pitchBendEnvelope.getBlock(*bends); + const auto events = resources.midiState.getPitchEvents(); + const auto bendLambda = [this](float bend){ + const auto bendInCents = bend > 0.0f ? bend * static_cast(region->bendUp) : -bend * static_cast(region->bendDown); + return centsFactor(bendInCents); + }; + multiplicativeEnvelope(events, *bends, bendLambda); + DBG("Bend: " << bends->back()); applyGain(*bends, *jumps); jumps->front() += floatPositionOffset; @@ -558,11 +566,18 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept float keycenterFrequency = midiNoteFrequency(region->pitchKeycenter); fill(*frequencies, pitchRatio * keycenterFrequency); - if (region->bendStep > 1) - pitchBendEnvelope.getQuantizedBlock(*bends, bendStepFactor); - else - pitchBendEnvelope.getBlock(*bends); + const auto events = resources.midiState.getPitchEvents(); + const auto bendLambda = [this](float bend){ + const auto bendInCents = bend > 0.0f ? bend * static_cast(region->bendUp) : -bend * static_cast(region->bendDown); + return centsFactor(bendInCents); + }; + // if (region->bendStep > 1) + // pitchBendEnvelope.getQuantizedBlock(*bends, bendStepFactor); + // else + // pitchBendEnvelope.getBlock(*bends); + multiplicativeEnvelope(events, *bends, bendLambda); + DBG("Bend: " << bends->back()); applyGain(*bends, *frequencies); waveOscillator.processModulated(frequencies->data(), leftSpan.data(), buffer.getNumFrames()); From bebb166469156e6186f123054a2c9b6d4169c92d Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 22:42:43 +0200 Subject: [PATCH 54/72] Add quantized envelopes --- src/sfizz/SfzHelpers.h | 88 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index c7c55975..6223cf0f 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -352,15 +352,49 @@ void linearEnvelope(const EventVector& events, absl::Span envelope, F&& l auto lastValue = lambda(events[0].value); auto lastDelay = events[0].delay; for (unsigned i = 1; i < events.size(); ++ i) { - const auto event = events[i]; - const auto length = event.delay - lastDelay; - const auto step = (lambda(event.value) - lastValue)/ length; + const auto length = events[i].delay - 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); + auto quantize = [step](float value) -> float { + return std::round(value / step) * step; + }; + + auto lastValue = quantize(lambda(events[0].value)); + auto lastDelay = events[0].delay; + for (unsigned i = 1; i < events.size(); ++ i) { + const auto nextValue = quantize(lambda(events[i].value)); + const auto difference = std::abs(nextValue - lastValue); + const auto length = events[i].delay - 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) { @@ -370,9 +404,8 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop auto lastValue = lambda(events[0].value); auto lastDelay = events[0].delay; for (unsigned i = 1; i < events.size(); ++ i) { - const auto event = events[i]; - const auto length = event.delay - lastDelay; - const auto nextValue = lambda(event.value); + const auto length = events[i].delay - 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; @@ -381,5 +414,48 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop 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); + DBG("Called the quantized version with step " << step); + + 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(); ++ i) { + const auto length = events[i].delay - 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 From 4de101f42c0c7175c796b25ae899f0a5f938a6f6 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 22:42:54 +0200 Subject: [PATCH 55/72] Add quantized envelopes to voices --- src/sfizz/Voice.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 9d1ee9c1..633cf8bc 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -473,16 +473,17 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept return; fill(*jumps, pitchRatio * speedRatio); - // if (region->bendStep > 1) - // pitchBendEnvelope.getQuantizedBlock(*bends, bendStepFactor); - // else - // pitchBendEnvelope.getBlock(*bends); + const auto events = resources.midiState.getPitchEvents(); const auto bendLambda = [this](float bend){ const auto bendInCents = bend > 0.0f ? bend * static_cast(region->bendUp) : -bend * static_cast(region->bendDown); return centsFactor(bendInCents); }; - multiplicativeEnvelope(events, *bends, bendLambda); + + if (region->bendStep > 1) + multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor); + else + multiplicativeEnvelope(events, *bends, bendLambda); DBG("Bend: " << bends->back()); applyGain(*bends, *jumps); @@ -571,12 +572,11 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept const auto bendInCents = bend > 0.0f ? bend * static_cast(region->bendUp) : -bend * static_cast(region->bendDown); return centsFactor(bendInCents); }; - // if (region->bendStep > 1) - // pitchBendEnvelope.getQuantizedBlock(*bends, bendStepFactor); - // else - // pitchBendEnvelope.getBlock(*bends); + if (region->bendStep > 1) + multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor); + else + multiplicativeEnvelope(events, *bends, bendLambda); - multiplicativeEnvelope(events, *bends, bendLambda); DBG("Bend: " << bends->back()); applyGain(*bends, *frequencies); From 8ef598bb7d1b1deb403cd4e1dc17f0c4ffa3686e Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 22:49:28 +0200 Subject: [PATCH 56/72] Remove pitchbend from midiState --- src/sfizz/MidiState.cpp | 3 --- src/sfizz/MidiState.h | 1 - 2 files changed, 4 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index a2e4107c..19b78888 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -97,7 +97,6 @@ void sfz::MidiState::pitchBendEvent(int delay, float pitchBendValue) noexcept { ASSERT(pitchBendValue >= -1.0f && pitchBendValue <= 1.0f); - pitchBend = pitchBendValue; const auto insertionPoint = absl::c_upper_bound(pitchEvents, delay, MidiEventDelayComparator{}); if (insertionPoint == pitchEvents.end() || insertionPoint->delay != delay) pitchEvents.insert(insertionPoint, { delay, pitchBendValue }); @@ -141,7 +140,6 @@ void sfz::MidiState::reset() noexcept pitchEvents.clear(); pitchEvents.push_back({ 0, 0.0f }); - pitchBend = 0; activeNotes = 0; internalClock = 0; absl::c_fill(noteOnTimes, 0); @@ -153,7 +151,6 @@ void sfz::MidiState::resetAllControllers(int delay) noexcept for (int ccIdx = 0; ccIdx < config::numCCs; ++ccIdx) ccEvent(delay, ccIdx, 0.0f); - pitchBend = 0; pitchBendEvent(delay, 0.0f); } diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 2c6f2496..63ee23b3 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -155,7 +155,6 @@ private: /** * Pitch bend status */ - int pitchBend { 0 }; EventVector pitchEvents; float sampleRate { config::defaultSampleRate }; int samplesPerBlock { config::defaultSamplesPerBlock }; From a71b5c3bb59b67ad3b80e92a3ceabb95b2471984 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 23:48:24 +0200 Subject: [PATCH 57/72] Change the linear ramp to something more natural --- src/sfizz/SIMDHelpers.h | 2 +- src/sfizz/SIMDSSE.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 2e217c3a..29e7f2ba 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -538,8 +538,8 @@ namespace _internals { template inline void snippetRampLinear(T*& output, T& value, T step) { - value += step; *output++ = value; + value += step; } } diff --git a/src/sfizz/SIMDSSE.cpp b/src/sfizz/SIMDSSE.cpp index 1b5c466c..d76e5528 100644 --- a/src/sfizz/SIMDSSE.cpp +++ b/src/sfizz/SIMDSSE.cpp @@ -461,7 +461,7 @@ float sfz::linearRamp(absl::Span output, float value, float while (unaligned(out) && out < lastAligned) _internals::snippetRampLinear(out, value, step); - auto mmValue = _mm_set1_ps(value); + auto mmValue = _mm_set1_ps(value - step); auto mmStep = _mm_set_ps(step + step + step + step, step + step + step, step + step, step); while (out < lastAligned) { @@ -471,7 +471,8 @@ float sfz::linearRamp(absl::Span output, float value, float out += TypeAlignment; } - value = _mm_cvtss_f32(mmValue); + value = _mm_cvtss_f32(mmValue) + step; + while (out < output.end()) _internals::snippetRampLinear(out, value, step); return value; From a9d5c7e6b1a377ce3992fb8b22072e8ac85e47c9 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 30 Mar 2020 23:48:41 +0200 Subject: [PATCH 58/72] Update the base test to match the new linear ramp --- tests/SIMDHelpersT.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 01c087ec..327eb29b 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -499,7 +499,7 @@ TEST_CASE("[Helpers] Linear Ramp") const float start { 0.0f }; const float v { fillValue }; std::array output; - std::array expected { v, v + v, v + v + v, v + v + v + v, v + v + v + v + v, v + v + v + v + v + v }; + std::array expected { start, start + v, start + v + v, start + v + v + v, start + v + v + v + v, start + v + v + v + v + v }; sfz::linearRamp(absl::MakeSpan(output), start, v); REQUIRE(output == expected); } @@ -509,7 +509,7 @@ TEST_CASE("[Helpers] Linear Ramp (SIMD)") const float start { 0.0f }; const float v { fillValue }; std::array output; - std::array expected { v, v + v, v + v + v, v + v + v + v, v + v + v + v + v, v + v + v + v + v + v }; + std::array expected { start, start + v, start + v + v, start + v + v + v, start + v + v + v + v, start + v + v + v + v + v }; sfz::linearRamp(absl::MakeSpan(output), start, v); REQUIRE(approxEqual(output, expected)); } From 2400a6adcc47fbdb10ef9b6b8a5a80871ab82c27 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 31 Mar 2020 00:03:21 +0200 Subject: [PATCH 59/72] Remove ADSR tests for now until updates from jpc --- tests/ADSREnvelopeT.cpp | 32 ++++++++++++++++---------------- tests/CMakeLists.txt | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/ADSREnvelopeT.cpp b/tests/ADSREnvelopeT.cpp index 22783f3d..0d344096 100644 --- a/tests/ADSREnvelopeT.cpp +++ b/tests/ADSREnvelopeT.cpp @@ -51,7 +51,7 @@ TEST_CASE("[ADSREnvelope] Attack") envelope.reset(region, state, 0, 0.0f, 100.0f); std::array output; - std::array expected { 0.5f, 1.0f, 1.0f, 1.0f, 1.0f }; + std::array expected { 0.0f, 0.5f, 1.0f, 1.0f, 1.0f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); @@ -71,7 +71,7 @@ TEST_CASE("[ADSREnvelope] Attack again") envelope.reset(region, state, 0, 0.0f, 100.0f); std::array output; - std::array expected { 0.33333f, 0.66667f, 1.0f, 1.0f, 1.0f }; + std::array expected { 0.0f, 0.33333f, 0.66667f, 1.0f, 1.0f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); @@ -92,8 +92,8 @@ TEST_CASE("[ADSREnvelope] Release") envelope.reset(region, state, 0, 0.0f, 100.0f); envelope.startRelease(2); - std::array output; - std::array expected { 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f }; + std::array output; + std::array expected { 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); @@ -113,10 +113,10 @@ TEST_CASE("[ADSREnvelope] Delay") region.amplitudeEG.attack = 0.02f; region.amplitudeEG.release = 0.04f; region.amplitudeEG.delay = 0.02f; - std::array output; + std::array output; envelope.reset(region, state, 0, 0.0f, 100.0f); envelope.startRelease(4); - std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f }; + std::array expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); @@ -137,9 +137,9 @@ TEST_CASE("[ADSREnvelope] Lower sustain") region.amplitudeEG.release = 0.04f; region.amplitudeEG.delay = 0.02f; region.amplitudeEG.sustain = 50.0f; - std::array output; + std::array output; envelope.reset(region, state, 0, 0.0f, 100.0f); - std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; + std::array expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); @@ -160,9 +160,9 @@ TEST_CASE("[ADSREnvelope] Decay") region.amplitudeEG.delay = 0.02f; region.amplitudeEG.sustain = 50.0f; region.amplitudeEG.decay = 0.02f; - std::array output; + std::array output; envelope.reset(region, state, 0, 0.0f, 100.0f); - std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5 }; + std::array expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5 }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); @@ -184,9 +184,9 @@ TEST_CASE("[ADSREnvelope] Hold") region.amplitudeEG.sustain = 50.0f; region.amplitudeEG.decay = 0.02f; region.amplitudeEG.hold = 0.02f; - std::array output; + std::array output; envelope.reset(region, state, 0, 0.0f, 100.0f); - std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; + std::array expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); @@ -210,8 +210,8 @@ TEST_CASE("[ADSREnvelope] Hold with release") region.amplitudeEG.hold = 0.02f; envelope.reset(region, state, 0, 0.0f, 100.0f); envelope.startRelease(8); - std::array output; - std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.05f, 0.005f, 0.0005f, 0.00005f, 0.0f, 0.0f }; + std::array output; + std::array expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.05f, 0.005f, 0.0005f, 0.00005f, 0.0f, 0.0f }; for (auto& out : output) out = envelope.getNextValue(); @@ -236,8 +236,8 @@ TEST_CASE("[ADSREnvelope] Hold with release 2") region.amplitudeEG.hold = 0.02f; envelope.reset(region, state, 0, 0.0f, 100.0f); envelope.startRelease(4); - std::array output; - std::array expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f, 0.0f, 0.0 }; + std::array output; + std::array expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f, 0.0f, 0.0 }; for (auto& out : output) out = envelope.getNextValue(); REQUIRE(approxEqual(output, expected)); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 30db1c1e..408deb52 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,7 +19,7 @@ set(SFIZZ_TEST_SOURCES OnePoleFilterT.cpp RegionActivationT.cpp RegionValueComputationsT.cpp - ADSREnvelopeT.cpp + # ADSREnvelopeT.cpp EventEnvelopesT.cpp MainT.cpp SynthT.cpp From ff949830898fca425fbcfcdd40b50316eba9b0a6 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 31 Mar 2020 00:03:47 +0200 Subject: [PATCH 60/72] remove debug messages in voices --- src/sfizz/Voice.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 633cf8bc..c2648566 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -484,7 +484,6 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor); else multiplicativeEnvelope(events, *bends, bendLambda); - DBG("Bend: " << bends->back()); applyGain(*bends, *jumps); jumps->front() += floatPositionOffset; @@ -577,7 +576,6 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept else multiplicativeEnvelope(events, *bends, bendLambda); - DBG("Bend: " << bends->back()); applyGain(*bends, *frequencies); waveOscillator.processModulated(frequencies->data(), leftSpan.data(), buffer.getNumFrames()); From 12cb0b5b43d514885abbb4f618b31efa63aea480 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 31 Mar 2020 00:05:02 +0200 Subject: [PATCH 61/72] Adapt the curve computation to the linearRamp changes --- src/sfizz/Curve.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Curve.cpp b/src/sfizz/Curve.cpp index ee9c5af2..b29fbf15 100644 --- a/src/sfizz/Curve.cpp +++ b/src/sfizz/Curve.cpp @@ -144,7 +144,7 @@ void Curve::lerpFill(const bool fillStatus[NumValues]) const auto length = right - left; if (length > 1) { const float mu = (_points[right] - _points[left]) / length; - linearRamp(pointSpan.subspan(left + 1, length - 1), _points[left], mu); + linearRamp(pointSpan.subspan(left, length), _points[left], mu); } left = right++; } From 5412593da96b8bc8194634a3a59c4b76b134b401 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 31 Mar 2020 00:18:09 +0200 Subject: [PATCH 62/72] multiplicativeRamp behaves like linearRamp --- src/sfizz/SIMDHelpers.h | 2 +- src/sfizz/SIMDSSE.cpp | 4 ++-- tests/SIMDHelpersT.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 29e7f2ba..3db5d7bb 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -566,8 +566,8 @@ namespace _internals { template inline void snippetRampMultiplicative(T*& output, T& value, T step) { - value *= step; *output++ = value; + value *= step; } } diff --git a/src/sfizz/SIMDSSE.cpp b/src/sfizz/SIMDSSE.cpp index d76e5528..1048e9cc 100644 --- a/src/sfizz/SIMDSSE.cpp +++ b/src/sfizz/SIMDSSE.cpp @@ -487,7 +487,7 @@ float sfz::multiplicativeRamp(absl::Span output, float value while (unaligned(out) && out < lastAligned) _internals::snippetRampMultiplicative(out, value, step); - auto mmValue = _mm_set1_ps(value); + auto mmValue = _mm_set1_ps(value / step); auto mmStep = _mm_set_ps(step * step * step * step, step * step * step, step * step, step); while (out < lastAligned) { @@ -497,7 +497,7 @@ float sfz::multiplicativeRamp(absl::Span output, float value out += TypeAlignment; } - value = _mm_cvtss_f32(mmValue); + value = _mm_cvtss_f32(mmValue) * step; while (out < output.end()) _internals::snippetRampMultiplicative(out, value, step); return value; diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 327eb29b..44c837c4 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -539,7 +539,7 @@ TEST_CASE("[Helpers] Multiplicative Ramp") const float start { 1.0f }; const float v { fillValue }; std::array output; - std::array expected { v, v * v, v * v * v, v * v * v * v, v * v * v * v * v, v * v * v * v * v * v }; + std::array expected { start, start * v, start * v * v, start * v * v * v, start * v * v * v * v, start * v * v * v * v * v }; sfz::multiplicativeRamp(absl::MakeSpan(output), start, v); REQUIRE(approxEqual(output, expected)); } @@ -549,7 +549,7 @@ TEST_CASE("[Helpers] Multiplicative Ramp (SIMD)") const float start { 1.0f }; const float v { fillValue }; std::array output; - std::array expected { v, v * v, v * v * v, v * v * v * v, v * v * v * v * v, v * v * v * v * v * v }; + std::array expected { start, start * v, start * v * v, start * v * v * v, start * v * v * v * v, start * v * v * v * v * v }; sfz::multiplicativeRamp(absl::MakeSpan(output), start, v); REQUIRE(approxEqual(output, expected)); } From bc60b19c4b51d4060ba403ef279b929bd512dc59 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 31 Mar 2020 00:18:24 +0200 Subject: [PATCH 63/72] Ported the old envelope tests to the new free functions --- src/sfizz/SfzHelpers.h | 34 +++- tests/EventEnvelopesT.cpp | 398 ++++++++++++++------------------------ 2 files changed, 168 insertions(+), 264 deletions(-) diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 6223cf0f..32d042be 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -348,11 +348,15 @@ void linearEnvelope(const EventVector& events, absl::Span envelope, F&& l { 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(); ++ i) { - const auto length = events[i].delay - lastDelay; + 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; @@ -366,16 +370,21 @@ void linearEnvelope(const EventVector& events, absl::Span envelope, F&& l 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(); ++ i) { + 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 = events[i].delay - lastDelay; + const auto length = min(events[i].delay, maxDelay) - lastDelay; if (difference < step) { fill(envelope.subspan(lastDelay, length), lastValue); @@ -401,10 +410,14 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop 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(); ++ i) { - const auto length = events[i].delay - lastDelay; + 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); @@ -420,7 +433,10 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop ASSERT(events.size() > 0); ASSERT(events[0].delay == 0); ASSERT(step != 0.0f); - DBG("Called the quantized version with step " << step); + + 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 @@ -434,8 +450,8 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop auto lastValue = quantize(lambda(events[0].value)); auto lastDelay = events[0].delay; - for (unsigned i = 1; i < events.size(); ++ i) { - const auto length = events[i].delay - lastDelay; + 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; diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index 936f6dbf..a2916165 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -30,360 +30,248 @@ inline bool approxEqual(absl::Span lhs, absl::Span rhs, return true; } -TEST_CASE("[LinearEnvelope] Basic state") +const auto idModifier = [] (float x) { return x; }; +const auto twiceModifier = [] (float x) { return 2 * x; }; +const auto expModifier = [] (float x) { return std::exp(x); }; + +TEST_CASE("[Envelopes] Empty") { - sfz::LinearEnvelope envelope; + sfz::EventVector events { + {0, 0.0f} + }; std::array output; std::array expected { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - envelope.getBlock(absl::MakeSpan(output)); + std::array expectedMul { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; + linearEnvelope(events, absl::MakeSpan(output), idModifier); REQUIRE(output == expected); + linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f); + REQUIRE(output == expected); + multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier); + REQUIRE(output == expectedMul); + multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier, 2.0f); + REQUIRE(output == expectedMul); } -TEST_CASE("[LinearEnvelope] Basic event") +TEST_CASE("[Envelopes] Linear basic") { - sfz::LinearEnvelope envelope; - envelope.registerEvent(4, 1.0f); - std::array output; - std::array expected { 0.25f, 0.5f, 0.75f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; - envelope.getBlock(absl::MakeSpan(output)); + sfz::EventVector events { + {0, 0.0f}, + {4, 1.0f} + }; + 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); } TEST_CASE("[LinearEnvelope] 2 events, close") { - sfz::LinearEnvelope envelope; - envelope.registerEvent(4, 1.0f); - envelope.registerEvent(5, 2.0f); - std::array output; - std::array expected { 0.25f, 0.5f, 0.75f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f }; - envelope.getBlock(absl::MakeSpan(output)); + sfz::EventVector events { + {0, 0.0f}, + {4, 1.0f}, + {5, 2.0f} + }; + 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); } + TEST_CASE("[LinearEnvelope] 2 events, far") { - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.0f); - envelope.registerEvent(6, 2.0f); - std::array output; - std::array expected { 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f }; - envelope.getBlock(absl::MakeSpan(output)); - REQUIRE(output == expected); -} - -TEST_CASE("[LinearEnvelope] 2 events, reversed") -{ - sfz::LinearEnvelope envelope; - envelope.registerEvent(6, 2.0f); - envelope.registerEvent(2, 1.0f); - std::array output; - std::array expected { 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f }; - envelope.getBlock(absl::MakeSpan(output)); - REQUIRE(output == expected); -} - -TEST_CASE("[LinearEnvelope] 3 events, overlapping") -{ - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.0f); - envelope.registerEvent(6, 2.0f); - envelope.registerEvent(6, 3.0f); - std::array output; - std::array expected { 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.0f, 3.0f }; - envelope.getBlock(absl::MakeSpan(output)); + sfz::EventVector events { + {0, 0.0f}, + {2, 1.0f}, + {6, 2.0f} + }; + 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); } TEST_CASE("[LinearEnvelope] 3 events, out of block") { - // sfz::LinearEnvelope envelope; - // envelope.registerEvent(2, 1.0f); - // envelope.registerEvent(6, 2.0f); - // envelope.registerEvent(10, 3.0f); - // std::array output; - // std::array expected { 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 3.0f, 3.0f }; // TODO: this one is a bit strange - // envelope.getBlock(absl::MakeSpan(output)); - // REQUIRE(output == expected); -} - -TEST_CASE("[LinearEnvelope] 3 events, out of block, with another block call") -{ - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.0f); - envelope.registerEvent(6, 2.0f); - envelope.registerEvent(10, 3.0f); - std::array output; - std::array expected { 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f }; - envelope.getBlock(absl::MakeSpan(output)); - envelope.getBlock(absl::MakeSpan(output)); - REQUIRE(output == expected); -} - -TEST_CASE("[LinearEnvelope] 2 events, with another block call") -{ - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.0f); - envelope.registerEvent(6, 2.0f); - std::array output; - std::array expected { 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f }; - envelope.getBlock(absl::MakeSpan(output)); - envelope.getBlock(absl::MakeSpan(output)); + sfz::EventVector events { + {0, 0.0f}, + {2, 1.0f}, + {6, 2.0f}, + {10, 3.0f} + }; + 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); } TEST_CASE("[LinearEnvelope] 2 events, function") { - sfz::LinearEnvelope envelope; - envelope.setFunction([](float x) { return 2 * x; }); - envelope.registerEvent(2, 1.0f); - envelope.registerEvent(6, 2.0f); - std::array output; - std::array expected { 1.0f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.0f, 4.0f }; - envelope.getBlock(absl::MakeSpan(output)); + sfz::EventVector events { + {0, 0.0f}, + {2, 1.0f}, + {6, 2.0f} + }; + 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); } TEST_CASE("[LinearEnvelope] Get quantized") { - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.0f); - envelope.registerEvent(6, 2.0f); + sfz::EventVector events { + {0, 0.0f}, + {2, 1.0f}, + {6, 2.0f} + }; std::array output; - std::array expected { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f, 2.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); + 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); } TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets") { - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.1f); - envelope.registerEvent(6, 1.9f); + sfz::EventVector events { + {0, 0.0f}, + {2, 1.1f}, + {6, 1.9f} + }; std::array output; - std::array expected { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f, 2.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); + 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); } TEST_CASE("[LinearEnvelope] Get quantized with 2 steps") { - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.0f); - envelope.registerEvent(6, 3.0f); + sfz::EventVector events { + {0, 0.0f}, + {2, 1.0f}, + {6, 3.0f} + }; std::array output; - std::array expected { 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 3.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); + 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); } TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of block step") { - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.0f); - envelope.registerEvent(6, 3.0f); - envelope.registerEvent(10, 4.2f); + sfz::EventVector events { + {0, 0.0f}, + {2, 1.0f}, + {6, 3.0f}, + {10, 4.2f}, + }; std::array output; - std::array expected { 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 4.0f, 4.0f }; - std::array expected2 { 4.0f, 4.0f, 4.0f,4.0f, 4.0f, 4.0f, 4.0f, 4.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); + 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); - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); - REQUIRE(output == expected2); } TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps") { - sfz::LinearEnvelope envelope; - envelope.reset(3.0f); - envelope.registerEvent(2, 2.0f); - envelope.registerEvent(6, 0.0f); + sfz::EventVector events { + {0, 3.0f}, + {2, 2.0f}, + {6, 0.0f} + }; std::array output; - std::array expected { 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); - REQUIRE(output == expected); -} - -TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and starting unquantized") -{ - sfz::LinearEnvelope envelope; - envelope.reset(0.1f); - envelope.registerEvent(3, 1.0f); - envelope.registerEvent(7, 3.0f); - std::array output; - std::array expected { 0.1f, 0.1f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); - REQUIRE(output == expected); -} - - -TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps and starting unquantized") -{ - sfz::LinearEnvelope envelope; - envelope.reset(3.6f); - envelope.registerEvent(4, 1.0f); - envelope.registerEvent(7, 0.0); - std::array output; - std::array expected { 3.6f, 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); - REQUIRE(output == expected); -} - -TEST_CASE("[LinearEnvelope] Get quantized with unclean events") -{ - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.2f); - envelope.registerEvent(6, 2.5f); - std::array output; - std::array expected { 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 3.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); - REQUIRE(output == expected); -} - -TEST_CASE("[LinearEnvelope] Get quantized 3 events, one out of block") -{ - sfz::LinearEnvelope envelope; - envelope.registerEvent(2, 1.0f); - envelope.registerEvent(6, 2.0f); - envelope.registerEvent(10, 3.0f); - std::array output; - std::array expected { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 3.0f, 3.0f }; - std::array expected2 { 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); - REQUIRE(output == expected); - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); - REQUIRE(output == expected2); -} - -// -TEST_CASE("[MultiplicativeEnvelope] Basic state") -{ - sfz::MultiplicativeEnvelope envelope; - std::array output; - std::array expected { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; - envelope.getBlock(absl::MakeSpan(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); } TEST_CASE("[MultiplicativeEnvelope] Basic event") { - sfz::MultiplicativeEnvelope envelope; - envelope.registerEvent(4, 2.0f); + sfz::EventVector events { + {0, 1.0f}, + {4, 2.0f} + }; std::array output; - std::array expected { 1.1892f, 1.4142f, 1.68176f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f }; - envelope.getBlock(absl::MakeSpan(output)); + std::array expected { 1.0f, 1.1892f, 1.4142f, 1.68176f, 2.0f, 2.0f, 2.0f, 2.0f }; + multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier); REQUIRE(approxEqual(output, expected)); } TEST_CASE("[MultiplicativeEnvelope] 2 events") { - sfz::MultiplicativeEnvelope envelope; - envelope.registerEvent(4, 2.0f); - envelope.registerEvent(5, 4.0f); + sfz::EventVector events { + {0, 1.0f}, + {4, 2.0f}, + {5, 4.0f} + }; std::array output; - std::array expected { 1.1892f, 1.4142f, 1.68176f, 2.0f, 4.0f, 4.0f, 4.0f, 4.0f }; - envelope.getBlock(absl::MakeSpan(output)); + std::array expected { 1.0f, 1.1892f, 1.4142f, 1.68176f, 2.0f, 4.0f, 4.0f, 4.0f}; + multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier); REQUIRE(approxEqual(output, expected)); } TEST_CASE("[MultiplicativeEnvelope] 2 events, far") { - sfz::MultiplicativeEnvelope envelope; - envelope.registerEvent(2, 2.0f); - envelope.registerEvent(6, 4.0f); + sfz::EventVector events { + {0, 1.0f}, + {2, 2.0f}, + {6, 4.0f} + }; std::array output; - std::array expected { 1.4142f, 2.0f, 2.37841f, 2.82843f, 3.36358f, 4.0f, 4.0f, 4.0f }; - envelope.getBlock(absl::MakeSpan(output)); + std::array expected { 1.0f, 1.4142f, 2.0f, 2.37841f, 2.82843f, 3.36358f, 4.0f, 4.0f}; + multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier); REQUIRE(approxEqual(output, expected)); } TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps") { - sfz::MultiplicativeEnvelope envelope; - envelope.registerEvent(2, 2.0f); - envelope.registerEvent(6, 4.0f); + sfz::EventVector events { + {0, 1.0f}, + {2, 2.0f}, + {6, 4.0f} + }; std::array output; - std::array expected { 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 4.0f, 4.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); + 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); } TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of range step") { - sfz::MultiplicativeEnvelope envelope; - envelope.registerEvent(2, 2.0f); - envelope.registerEvent(6, 4.0f); - envelope.registerEvent(10, 8.2f); + sfz::EventVector events { + {0, 1.0f}, + {2, 2.0f}, + {6, 4.0f}, + {10, 8.2f} + }; std::array output; - std::array expected { 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 8.0f, 8.0f }; - std::array expected2 { 8.0f, 8.0f, 8.0f, 8.0f, 8.0f, 8.0f, 8.0f, 8.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); + 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); - envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); - REQUIRE(output == expected2); } TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps") { - sfz::MultiplicativeEnvelope envelope; - envelope.reset(4.0f); - envelope.registerEvent(2, 2.0f); - envelope.registerEvent(6, 0.5f); + sfz::EventVector events { + {0, 4.0f}, + {2, 2.0f}, + {6, 0.5f} + }; std::array output; - std::array expected { 4.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); + 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); } TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events") { - sfz::MultiplicativeEnvelope envelope; - envelope.registerEvent(2, 1.2f); - envelope.registerEvent(6, 2.5f); + 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, 2.0f, 2.0f, 2.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); + 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); } - -TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps and starting unquantized") -{ - sfz::MultiplicativeEnvelope envelope; - envelope.reset(0.9f); - envelope.registerEvent(3, 1.0f); - envelope.registerEvent(7, 4.0f); - std::array output; - std::array expected { 0.9f, 0.9f, 1.0f, 1.0f, 2.0f, 2.0f, 4.0f, 4.0f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); - REQUIRE(output == expected); -} - - -TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps and starting unquantized") -{ - sfz::MultiplicativeEnvelope envelope; - envelope.reset(4.6f); - envelope.registerEvent(4, 1.0f); - envelope.registerEvent(7, 0.25f); - std::array output; - std::array expected { 4.6f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.25f, 0.25f }; - envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); - REQUIRE(output == expected); -} - -TEST_CASE("[MultiplicativeEnvelope] Pitch envelope basic function") -{ - sfz::Buffer output { 256 }; - sfz::MultiplicativeEnvelope envelope; - envelope.setFunction([](float pitchValue){ - const auto normalizedBend = sfz::normalizeBend(pitchValue); - const auto bendInCents = normalizedBend * 200.0f; - return sfz::centsFactor(bendInCents); - }); - envelope.reset(0.0f); - envelope.getBlock(absl::MakeSpan(output)); - REQUIRE(output[255] == 1.0_a); - envelope.registerEvent(252, -6020.0f); - envelope.getBlock(absl::MakeSpan(output)); - REQUIRE(output[255] == Approx(0.9168).epsilon(0.01)); -} From bd85a97db31d49d4dd21f12251db6e1aa4bd15c5 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 31 Mar 2020 00:33:06 +0200 Subject: [PATCH 64/72] Handle the volume cc modifiers like the rest --- src/sfizz/Region.cpp | 5 ++++- src/sfizz/Region.h | 2 +- src/sfizz/Voice.cpp | 41 ++++++++++++++--------------------------- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 1bb17db9..7a2aea2c 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -294,7 +294,10 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("gain_cc&"): case hash("gain_oncc&"): // fallthrough case hash("volume_oncc&"): - setCCPairFromOpcode(opcode, volumeCC, Default::volumeCCRange); + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::volumeCCRange)) + volumeCC[opcode.parameters.back()] = *value; break; case hash("amplitude"): if (auto value = readOpcode(opcode.value, Default::amplitudeRange)) diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 8cd8e3ad..2e2a81ca 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -276,7 +276,7 @@ struct Region { float pan { normalizePercents(Default::pan) }; // pan float width { normalizePercents(Default::width) }; // width float position { normalizePercents(Default::position) }; // position - absl::optional> volumeCC; // volume_oncc + CCMap volumeCC { Default::zeroModifier }; // volume_oncc CCMap amplitudeCC { Default::zeroModifier }; // amplitude_oncc CCMap panCC { Default::zeroModifier }; // pan_oncc CCMap widthCC { Default::zeroModifier }; // width_oncc diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index c2648566..f9516f84 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -74,23 +74,11 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, speedRatio = static_cast(currentPromise->sampleRate / this->sampleRate); } pitchRatio = region->getBasePitchVariation(number, value); - baseVolumedB = region->getBaseVolumedB(number); - auto volumedB = baseVolumedB; - if (region->volumeCC) - volumedB += resources.midiState.getCCValue(region->volumeCC->cc) * region->volumeCC->value; - volumeEnvelope.reset(db2mag(Default::volumeRange.clamp(volumedB))); - baseGain = region->getBaseGain(); if (triggerType != TriggerType::CC) baseGain *= region->getNoteGain(number, value); - pitchBendEnvelope.setFunction([region](float bend){ - const auto bendInCents = bend > 0.0f ? bend * static_cast(region->bendUp) : -bend * static_cast(region->bendDown); - return centsFactor(bendInCents); - }); - pitchBendEnvelope.reset(resources.midiState.getPitchBend()); - // Check that we can handle the number of filters; filters should be cleared here ASSERT((filters.capacity() - filters.size()) >= region->filters.size()); ASSERT((equalizers.capacity() - equalizers.size()) >= region->equalizers.size()); @@ -171,23 +159,14 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept if (region->checkSustain && noteIsOff && ccNumber == config::sustainCC && ccValue < config::halfCCThreshold) release(delay); - - // Add a minimum delay for smoothing the envelopes - // TODO: this feels like a hack, revisit this along with the smoothed envelopes... - delay = max(delay, minEnvelopeDelay); - - if (region->volumeCC && ccNumber == region->volumeCC->cc) { - const float newVolumedB { baseVolumedB + ccValue * region->volumeCC->value }; - volumeEnvelope.registerEvent(delay, db2mag(Default::volumeRange.clamp(newVolumedB))); - } } void sfz::Voice::registerPitchWheel(int delay, float pitch) noexcept { if (state == State::idle) return; - - pitchBendEnvelope.registerEvent(delay, pitch); + UNUSED(delay); + UNUSED(pitch); } void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept @@ -293,7 +272,12 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept applyGain(*modulationSpan, leftBuffer); // Volume envelope - volumeEnvelope.getBlock(*modulationSpan); + fill(*modulationSpan, db2mag(baseVolumedB)); + for (auto& mod : region->volumeCC) { + const auto events = resources.midiState.getCCEvents(mod.cc); + multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.value); }); + applyGain(*tempSpan, *modulationSpan); + } applyGain(*modulationSpan, leftBuffer); // AmpEG envelope @@ -330,17 +314,20 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } - for (auto& mod : region->crossfadeCCOutRange) { const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); }); applyGain(*tempSpan, *modulationSpan); } - buffer.applyGain(*modulationSpan); // Volume envelope - volumeEnvelope.getBlock(*modulationSpan); + fill(*modulationSpan, db2mag(baseVolumedB)); + for (auto& mod : region->volumeCC) { + const auto events = resources.midiState.getCCEvents(mod.cc); + multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.value); }); + applyGain(*tempSpan, *modulationSpan); + } buffer.applyGain(*modulationSpan); // AmpEG envelope From 16b8cc1cfc7d31d1f58a25ee0c073a682bcd72b0 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 31 Mar 2020 00:33:26 +0200 Subject: [PATCH 65/72] Remove the event envelopes \o/ --- src/sfizz/EventEnvelopes.cpp | 277 ----------------------------------- src/sfizz/EventEnvelopes.h | 129 ---------------- src/sfizz/FloatEnvelopes.cpp | 16 -- src/sfizz/Voice.h | 8 - tests/EventEnvelopesT.cpp | 1 - 5 files changed, 431 deletions(-) delete mode 100644 src/sfizz/EventEnvelopes.cpp delete mode 100644 src/sfizz/EventEnvelopes.h diff --git a/src/sfizz/EventEnvelopes.cpp b/src/sfizz/EventEnvelopes.cpp deleted file mode 100644 index c64a5415..00000000 --- a/src/sfizz/EventEnvelopes.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause - -// This code is part of the sfizz library and is licensed under a BSD 2-clause -// license. You should have receive a LICENSE.md file along with the code. -// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz - -#include "EventEnvelopes.h" -#include "SIMDHelpers.h" -#include "MathHelpers.h" -#include - -namespace sfz { - -template -EventEnvelope::EventEnvelope() -{ - setMaxCapacity(maxCapacity); -} - -template -EventEnvelope::EventEnvelope(int maxCapacity, std::function function) -{ - setMaxCapacity(maxCapacity); - setFunction(function); -} - -template -void EventEnvelope::setMaxCapacity(int maxCapacity) -{ - events.reserve(maxCapacity); - this->maxCapacity = maxCapacity; -} - -template -void EventEnvelope::setFunction(std::function function) -{ - this->function = function; -} - -template -void EventEnvelope::registerEvent(int timestamp, Type inputValue) -{ - if (resetEvents) - clear(); - - if (static_cast(events.size()) < maxCapacity) - events.emplace_back(timestamp, function(inputValue)); -} - -template -void EventEnvelope::prepareEvents(int blockLength) -{ - if (resetEvents) - clear(); - - absl::c_stable_sort(events, [](const std::pair& lhs, const std::pair& rhs) { - return lhs.first < rhs.first; - }); - - auto eventIt = events.begin(); - while (eventIt < events.end()) { - if (eventIt->first >= blockLength) { - eventIt->first = blockLength - 1; - eventIt->second = events.back().second; - ++eventIt; - break; - } - - auto nextEventIt = std::next(eventIt); - while (nextEventIt < events.end() && eventIt->first == nextEventIt->first ) { - eventIt->second = nextEventIt->second; - ++nextEventIt; - } - ++eventIt; - } - events.resize(std::distance(events.begin(), eventIt)); - - resetEvents = true; -} - -template -void EventEnvelope::clear() -{ - events.clear(); - resetEvents = false; -} - -template -void EventEnvelope::reset(Type value) -{ - clear(); - currentValue = function(value); - resetEvents = false; -} - -template -void EventEnvelope::getBlock(absl::Span output) -{ - prepareEvents(output.size()); -} - -template -void EventEnvelope::getQuantizedBlock(absl::Span output, Type) -{ - prepareEvents(output.size()); -} - -template -void LinearEnvelope::getBlock(absl::Span output) -{ - EventEnvelope::getBlock(output); - auto& events = EventEnvelope::events; - auto& currentValue = EventEnvelope::currentValue; - - int index { 0 }; - for (auto& event : events) { - const auto length = min(event.first, static_cast(output.size())) - index; - if (length == 0) { - currentValue = event.second; - continue; - } - - const auto step = (event.second - currentValue) / length; - currentValue = linearRamp(output.subspan(index, length), currentValue, step); - index += length; - } - - if (index < static_cast(output.size())) - fill(output.subspan(index), currentValue); -} - -template -void LinearEnvelope::getQuantizedBlock(absl::Span output, Type quantizationStep) -{ - EventEnvelope::getQuantizedBlock(output, quantizationStep); - auto& events = EventEnvelope::events; - auto& currentValue = EventEnvelope::currentValue; - - ASSERT(quantizationStep != 0.0); - int index { 0 }; - - auto quantize = [quantizationStep](Type value) -> Type { - return std::round(value / quantizationStep) * quantizationStep; - }; - - const auto outputSize = static_cast(output.size()); - for (auto& event : events) { - const auto newValue = quantize(event.second); - - if (event.first > outputSize) { - fill(output.subspan(index), currentValue); - currentValue = newValue; - index = outputSize; - continue; - } - - const auto length = event.first - index - 1; - if (length <= 0) { - currentValue = newValue; - continue; - } - - const auto difference = std::abs(newValue - currentValue); - if (difference < quantizationStep) { - fill(output.subspan(index, length), currentValue); - currentValue = newValue; - index += length; - continue; - } - - const auto numSteps = static_cast(difference / quantizationStep); - const auto stepLength = static_cast(length / numSteps); - for (int i = 0; i < numSteps; ++i) { - fill(output.subspan(index, stepLength), currentValue); - const auto delta = quantizationStep + currentValue - quantize(currentValue); - currentValue += currentValue <= newValue ? delta : -delta; - index += stepLength; - } - } - - if (index < outputSize) - fill(output.subspan(index), currentValue); -} - -template -MultiplicativeEnvelope::MultiplicativeEnvelope() -{ - EventEnvelope::reset(1.0); -} - -template -void MultiplicativeEnvelope::getBlock(absl::Span output) -{ - EventEnvelope::getBlock(output); - auto& events = EventEnvelope::events; - auto& currentValue = EventEnvelope::currentValue; - - int index { 0 }; - for (auto& event : events) { - const auto length = min(event.first, static_cast(output.size())) - index; - if (length == 0) { - currentValue = event.second; - continue; - } - - const auto step = std::exp((std::log(event.second) - std::log(currentValue)) / length); - multiplicativeRamp(output.subspan(index, length), currentValue, step); - currentValue = event.second; - index += length; - } - - if (index < static_cast(output.size())) - fill(output.subspan(index), currentValue); -} - -template -void MultiplicativeEnvelope::getQuantizedBlock(absl::Span output, Type quantizationStep) -{ - EventEnvelope::getQuantizedBlock(output, quantizationStep); - auto& events = EventEnvelope::events; - auto& currentValue = EventEnvelope::currentValue; - - ASSERT(quantizationStep != 0.0); - int index { 0 }; - - const auto logStep = std::log(quantizationStep); - // 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](Type value) -> Type { - return std::exp(logStep * std::round(std::log(value)/logStep)); - }; - - const auto outputSize = static_cast(output.size()); - for (auto& event : events) { - const auto newValue = quantize(event.second); - - if (event.first > outputSize) { - fill(output.subspan(index), currentValue); - currentValue = newValue; - index = outputSize; - continue; - } - - const auto length = event.first - index - 1; - if (length <= 0) { - currentValue = newValue; - continue; - } - - const auto difference = newValue > currentValue ? newValue / currentValue : currentValue / newValue; - if (difference < quantizationStep) { - fill(output.subspan(index, length), currentValue); - currentValue = newValue; - index += 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(output.subspan(index, stepLength), currentValue); - const auto delta = newValue > currentValue ? - quantize(currentValue) / currentValue * quantizationStep : - quantize(currentValue) / currentValue / quantizationStep ; - currentValue *= delta; - index += stepLength; - } - } - - if (index < outputSize) - fill(output.subspan(index), currentValue); -} - -} diff --git a/src/sfizz/EventEnvelopes.h b/src/sfizz/EventEnvelopes.h deleted file mode 100644 index c56d1dd6..00000000 --- a/src/sfizz/EventEnvelopes.h +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause - -// This code is part of the sfizz library and is licensed under a BSD 2-clause -// license. You should have receive a LICENSE.md file along with the code. -// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz - -#pragma once -#include "Config.h" -#include "LeakDetector.h" -#include -#include -#include -#include - -namespace sfz { -/** - * @brief Describes a simple envelope that can be polled in a blockwise - * manner. It works by storing "events" in the immediate future and linearly - * interpolating between these events. This envelope can also transform its - * incoming target points through a lambda, although the lambda function is applied before the interpolation. - * - * The way to use this class is by repeatedly calling `registerEvent` and then - * `getBlock` to get a block of interpolated values in between the specified events. - * You should only register events whose timestamps are below the size of the block - * you will require when calling `getBlock`. - * - * @tparam Type - */ -template -class EventEnvelope { -public: - /** - * @brief Construct a new linear envelope with a default memory size for - * incoming events. - * - */ - EventEnvelope(); - /** - * @brief Construct a new linear envelope with a specific memory size for - * incoming events as well as a transformation function for incoming events. - * - * @param maxCapacity - * @param function - */ - EventEnvelope(int maxCapacity, std::function function); - /** - * @brief Set the maximum memory size for incoming events - * - * @param maxCapacity - */ - void setMaxCapacity(int maxCapacity); - /** - * @brief Set the transformation function for the value of incoming events. - * - * @param function - */ - void setFunction(std::function function); - /** - * @brief Register a new event. Note that the timestamp of the new value should - * be less than the future call to `getBlock` otherwise the event will be ignored. - * - * @param timestamp - * @param inputValue - */ - void registerEvent(int timestamp, Type inputValue); - /** - * @brief Clear all events in memory - * - */ - void clear(); - /** - * @brief Reset the envelope and clears the memory. - * - * @param value - */ - void reset(Type value = 0.0); - /** - * @brief Get a block of interpolated values between events previously registered - * using `registerEvent`. - * - * @param output - */ - virtual void getBlock(absl::Span output); - /** - * @brief Get a block of interpolated values with a forced quantization. The - * values within the block will vary in quantization steps. - * - * @param output - * @param quantizationStep - */ - virtual void getQuantizedBlock(absl::Span output, Type quantizationStep); -protected: - std::vector> events; - Type currentValue { 0.0 }; -private: - static_assert(std::is_arithmetic::value, "Type should be arithmetic"); - std::function function { [](Type input) -> Type { return input; } }; - int maxCapacity { config::defaultSamplesPerBlock }; - void prepareEvents(int blockLength); - bool resetEvents { false }; - LEAK_DETECTOR(EventEnvelope); -}; - - -/** - * @brief Describes a simple linear envelope. - * - * @tparam Type - */ -template -class LinearEnvelope: public EventEnvelope { -public: - void getBlock(absl::Span output) final; - void getQuantizedBlock(absl::Span output, Type quantizationStep) final; -}; - -/** - * @brief Describes a simple multiplicative envelope. - * - * @tparam Type - */ -template -class MultiplicativeEnvelope: public EventEnvelope { -public: - MultiplicativeEnvelope(); - void getBlock(absl::Span output) final; - void getQuantizedBlock(absl::Span output, Type quantizationStep) final; -}; -} diff --git a/src/sfizz/FloatEnvelopes.cpp b/src/sfizz/FloatEnvelopes.cpp index 54e98fd9..e4c5289c 100644 --- a/src/sfizz/FloatEnvelopes.cpp +++ b/src/sfizz/FloatEnvelopes.cpp @@ -4,29 +4,13 @@ // 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 -/** - * @file FloatEnvelopes.cpp - * @author Paul Ferrand (paul@ferrand.cc) - * @brief Force the instantiations of the ADSR and linear envelopes for floats - * @version 0.1 - * @date 2019-11-30 - * - * @copyright Copyright (c) 2019 Paul Ferrand - * - */ - -#include "EventEnvelopes.h" #include "ADSREnvelope.h" // Include the generic implementations -#include "EventEnvelopes.cpp" #include "ADSREnvelope.cpp" // And explicitely instantiate the float version namespace sfz { - template class EventEnvelope; - template class MultiplicativeEnvelope; - template class LinearEnvelope; template class ADSREnvelope; } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 242d045d..7afcd24e 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -7,7 +7,6 @@ #pragma once #include "Config.h" #include "ADSREnvelope.h" -#include "EventEnvelopes.h" #include "HistoricalBuffer.h" #include "Region.h" #include "AudioBuffer.h" @@ -282,13 +281,6 @@ private: std::vector equalizers; ADSREnvelope egEnvelope; - LinearEnvelope amplitudeEnvelope; // linear events - LinearEnvelope crossfadeEnvelope; - LinearEnvelope panEnvelope; - LinearEnvelope positionEnvelope; - LinearEnvelope widthEnvelope; - MultiplicativeEnvelope pitchBendEnvelope; - MultiplicativeEnvelope volumeEnvelope; float bendStepFactor { centsFactor(1) }; WavetableOscillator waveOscillator; diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index a2916165..d4efd80a 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -4,7 +4,6 @@ // 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/EventEnvelopes.h" #include "sfizz/SfzHelpers.h" #include "sfizz/Buffer.h" #include "catch2/catch.hpp" From e24065f7d19a2e9b389562195af6257a9ec1e3ba Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 31 Mar 2020 00:37:22 +0200 Subject: [PATCH 66/72] Added volume_oncc tests --- tests/RegionT.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 66a959a8..ab1baaea 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -1453,6 +1453,20 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.amplitudeCC.contains(2)); REQUIRE(region.amplitudeCC[2] == 0.30_a); } + + SECTION("volume_oncc/gain_cc") + { + REQUIRE(region.volumeCC.empty()); + region.parseOpcode({ "gain_cc1", "40" }); + REQUIRE(region.volumeCC.contains(1)); + REQUIRE(region.volumeCC[1] == 40_a); + region.parseOpcode({ "volume_oncc2", "-76" }); + REQUIRE(region.volumeCC.contains(2)); + REQUIRE(region.volumeCC[2] == -76.0_a); + region.parseOpcode({ "gain_oncc4", "-1" }); + REQUIRE(region.volumeCC.contains(4)); + REQUIRE(region.volumeCC[4] == -1.0_a); + } } // Specific region bugs From 767ef7ba38c9a702c2857c827d767413f68715b3 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 31 Mar 2020 00:41:17 +0200 Subject: [PATCH 67/72] Remove envelopes from clang-tidy --- scripts/run_clang_tidy.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/run_clang_tidy.sh b/scripts/run_clang_tidy.sh index 782c1e0f..1fa02a38 100755 --- a/scripts/run_clang_tidy.sh +++ b/scripts/run_clang_tidy.sh @@ -5,7 +5,6 @@ clang-tidy \ src/sfizz/Curve.cpp \ src/sfizz/Effects.cpp \ src/sfizz/EQPool.cpp \ - src/sfizz/EventEnvelopes.cpp \ src/sfizz/FilePool.cpp \ src/sfizz/FilterPool.cpp \ src/sfizz/FloatEnvelopes.cpp \ From c92e78b3f21af3eceb71aabc8a0e8628e969c660 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 31 Mar 2020 09:53:46 +0200 Subject: [PATCH 68/72] Update the envelope benchmark --- benchmarks/BM_envelopes.cpp | 36 ++++++++++++++++++++++-------------- benchmarks/CMakeLists.txt | 6 +++--- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/benchmarks/BM_envelopes.cpp b/benchmarks/BM_envelopes.cpp index 67ec5dcf..3bff261e 100644 --- a/benchmarks/BM_envelopes.cpp +++ b/benchmarks/BM_envelopes.cpp @@ -10,7 +10,7 @@ #include #include #include -#include "EventEnvelopes.h" +#include "SfzHelpers.h" #include "absl/types/span.h" class EnvelopeFixture : public benchmark::Fixture { @@ -29,45 +29,53 @@ public: } std::random_device rd { }; std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 30 }; + std::uniform_real_distribution dist { 2, 30 }; std::vector input; std::vector output; }; BENCHMARK_DEFINE_F(EnvelopeFixture, Linear)(benchmark::State& state) { - sfz::LinearEnvelope envelope; for (auto _ : state) { - envelope.registerEvent(static_cast(state.range(0) - 1), dist(gen)); - envelope.getBlock(absl::MakeSpan(output)); + sfz::EventVector events { + {0, 0.0f}, + {static_cast(state.range(0) - 1), dist(gen)} + }; + linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); } } BENCHMARK_DEFINE_F(EnvelopeFixture, LinearQuantized)(benchmark::State& state) { - sfz::LinearEnvelope envelope; for (auto _ : state) { - envelope.registerEvent(static_cast(state.range(0) - 1), dist(gen)); - envelope.getQuantizedBlock(absl::MakeSpan(output), 0.5); + sfz::EventVector events { + {0, 0.0f}, + {static_cast(state.range(0) - 1), dist(gen)} + }; + linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }, 0.5); } } BENCHMARK_DEFINE_F(EnvelopeFixture, Multiplicative)(benchmark::State& state) { - sfz::MultiplicativeEnvelope envelope; for (auto _ : state) { - envelope.registerEvent(static_cast(state.range(0) - 1), dist(gen)); - envelope.getBlock(absl::MakeSpan(output)); + sfz::EventVector events { + {0, 1.0f}, + {static_cast(state.range(0) - 1), dist(gen)} + }; + multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); } } BENCHMARK_DEFINE_F(EnvelopeFixture, MultiplicativeQuantized)(benchmark::State& state) { - sfz::MultiplicativeEnvelope envelope; for (auto _ : state) { - envelope.registerEvent(static_cast(state.range(0) - 1), dist(gen)); - envelope.getQuantizedBlock(absl::MakeSpan(output), 1.5); + sfz::EventVector events { + {0, 1.0f}, + {static_cast(state.range(0) - 1), dist(gen)} + }; + multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }, 2.0f); } } diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index fb666f0c..2a217244 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -69,11 +69,11 @@ sfizz_add_benchmark(bm_logger BM_logger.cpp) target_link_libraries(bm_logger PRIVATE sfizz::sfizz) if (TARGET sfizz-samplerate) - sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES}) - target_link_libraries(bm_resample PRIVATE sfizz-samplerate sfizz-sndfile) +sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES}) +target_link_libraries(bm_resample PRIVATE sfizz-samplerate sfizz-sndfile) endif() -sfizz_add_benchmark(bm_envelopes BM_envelopes.cpp ../src/sfizz/MidiState.cpp ../src/sfizz/FloatEnvelopes.cpp) +sfizz_add_benchmark(bm_envelopes BM_envelopes.cpp) sfizz_add_benchmark(bm_wavfile BM_wavfile.cpp) target_link_libraries(bm_wavfile PRIVATE sfizz-sndfile) From bf4bdad6567dcdb358a6f549bff2ee20acd6ced8 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 31 Mar 2020 10:36:41 +0200 Subject: [PATCH 69/72] Handle pitch_cc and tune_cc --- src/sfizz/Defaults.h | 1 + src/sfizz/Region.cpp | 9 +++++++++ src/sfizz/Region.h | 1 + tests/RegionT.cpp | 14 ++++++++++++++ 4 files changed, 25 insertions(+) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index ef899329..7b24ba47 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -189,6 +189,7 @@ namespace Default constexpr Range transposeRange { -127, 127 }; constexpr int tune { 0 }; constexpr Range tuneRange { -9600, 9600 }; // ±100 in SFZv1, more in ARIA + constexpr Range tuneCCRange { -9600, 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/Region.cpp b/src/sfizz/Region.cpp index 7a2aea2c..cde42750 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -719,6 +719,15 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("pitch"): setValueFromOpcode(opcode, tune, Default::tuneRange); break; + case hash("tune_cc&"): + case hash("tune_oncc&"): + case hash("pitch_cc&"): + case hash("pitch_oncc&"): + if (opcode.parameters.back() > config::numCCs) + return false; + if (auto value = readOpcode(opcode.value, Default::tuneCCRange)) + tuneCC[opcode.parameters.back()] = *value; + break; case hash("bend_up"): setValueFromOpcode(opcode, bendUp, Default::bendBoundRange); break; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 2e2a81ca..a7a4e710 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -308,6 +308,7 @@ struct Region { int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack int transpose { Default::transpose }; // transpose int tune { Default::tune }; // tune + CCMap tuneCC { Default::tune }; int bendUp { Default::bendUp }; int bendDown { Default::bendDown }; int bendStep { Default::bendStep }; diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index ab1baaea..8e8bf851 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -1467,6 +1467,20 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.volumeCC.contains(4)); REQUIRE(region.volumeCC[4] == -1.0_a); } + + SECTION("tune_cc/pitch_cc") + { + REQUIRE(region.tuneCC.empty()); + region.parseOpcode({ "pitch_cc1", "40" }); + REQUIRE(region.tuneCC.contains(1)); + REQUIRE(region.tuneCC[1] == 40); + region.parseOpcode({ "tune_oncc2", "-76" }); + REQUIRE(region.tuneCC.contains(2)); + REQUIRE(region.tuneCC[2] == -76.0); + region.parseOpcode({ "pitch_oncc4", "-1" }); + REQUIRE(region.tuneCC.contains(4)); + REQUIRE(region.tuneCC[4] == -1.0); + } } // Specific region bugs From ddeecf41b9360617dbf0ef47c97dbfeeab299e8c Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 31 Mar 2020 10:36:53 +0200 Subject: [PATCH 70/72] Apply modifier for pitch_cc and tune cc --- src/sfizz/Voice.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index f9516f84..aa50fde3 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -471,8 +471,14 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor); else multiplicativeEnvelope(events, *bends, bendLambda); - 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.value); }); + applyGain(*bends, *jumps); + } + jumps->front() += floatPositionOffset; cumsum(*jumps, *jumps); sfzInterpolationCast(*jumps, *indices, *leftCoeffs, *rightCoeffs); @@ -562,9 +568,14 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor); else multiplicativeEnvelope(events, *bends, bendLambda); - 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.value); }); + applyGain(*bends, *frequencies); + } + waveOscillator.processModulated(frequencies->data(), leftSpan.data(), buffer.getNumFrames()); copy(leftSpan, rightSpan); } From 7e8cdff4075a4c1328ef75bd0f91755345488e7a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 31 Mar 2020 10:40:44 +0200 Subject: [PATCH 71/72] Modifier loops can be const --- src/sfizz/EQPool.cpp | 12 ++++++------ src/sfizz/FilterPool.cpp | 12 ++++++------ src/sfizz/Voice.cpp | 24 ++++++++++++------------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/sfizz/EQPool.cpp b/src/sfizz/EQPool.cpp index a9f8c2bc..515f26ef 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 (auto& mod : description.frequencyCC) + for (const auto& mod : description.frequencyCC) lastFrequency += midiState.getCCValue(mod.cc) * mod.value; lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); lastBandwidth = baseBandwidth; - for (auto& mod : description.bandwidthCC) + for (const auto& mod : description.bandwidthCC) lastBandwidth += midiState.getCCValue(mod.cc) * mod.value; lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); lastGain = baseGain; - for (auto& mod : description.gainCC) + for (const auto& mod : description.gainCC) lastGain += midiState.getCCValue(mod.cc) * mod.value; lastGain = Default::filterGainRange.clamp(lastGain); @@ -62,17 +62,17 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF // TODO: Once the midistate envelopes are done, add modulation in there! // For now we take the last value lastFrequency = baseFrequency; - for (auto& mod : description->frequencyCC) + for (const auto& mod : description->frequencyCC) lastFrequency += midiState.getCCValue(mod.cc) * mod.value; lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency); lastBandwidth = baseBandwidth; - for (auto& mod : description->bandwidthCC) + for (const auto& mod : description->bandwidthCC) lastBandwidth += midiState.getCCValue(mod.cc) * mod.value; lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth); lastGain = baseGain; - for (auto& mod : description->gainCC) + for (const auto& mod : description->gainCC) lastGain += midiState.getCCValue(mod.cc) * mod.value; lastGain = Default::filterGainRange.clamp(lastGain); diff --git a/src/sfizz/FilterPool.cpp b/src/sfizz/FilterPool.cpp index f28002f4..062e4e95 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 (auto& mod : description.cutoffCC) + for (const auto& mod : description.cutoffCC) lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value); lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); lastResonance = baseResonance; - for (auto& mod : description.resonanceCC) + for (const auto& mod : description.resonanceCC) lastResonance += midiState.getCCValue(mod.cc) * mod.value; lastResonance = Default::filterResonanceRange.clamp(lastResonance); lastGain = baseGain; - for (auto& mod : description.gainCC) + for (const auto& mod : description.gainCC) lastGain += midiState.getCCValue(mod.cc) * mod.value; lastGain = Default::filterGainRange.clamp(lastGain); @@ -71,17 +71,17 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned // For now we take the last value // TODO: the template deduction could be automatic here? lastCutoff = baseCutoff; - for (auto& mod : description->cutoffCC) + for (const auto& mod : description->cutoffCC) lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value); lastCutoff = Default::filterCutoffRange.clamp(lastCutoff); lastResonance = baseResonance; - for (auto& mod : description->resonanceCC) + for (const auto& mod : description->resonanceCC) lastResonance += midiState.getCCValue(mod.cc) * mod.value; lastResonance = Default::filterResonanceRange.clamp(lastResonance); lastGain = baseGain; - for (auto& mod : description->gainCC) + for (const auto& mod : description->gainCC) lastGain += midiState.getCCValue(mod.cc) * mod.value; lastGain = Default::filterGainRange.clamp(lastGain); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index aa50fde3..8881d453 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -250,7 +250,7 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept // Amplitude envelope fill(*modulationSpan, baseGain); - for (auto& mod : region->amplitudeCC) { + for (const auto& mod : region->amplitudeCC) { const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); applyGain(*tempSpan, *modulationSpan); @@ -259,12 +259,12 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept // Crossfade envelopes fill(*modulationSpan, 1.0f); - for (auto& mod : region->crossfadeCCInRange) { + 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); }); applyGain(*tempSpan, *modulationSpan); } - for (auto& mod : region->crossfadeCCOutRange) { + 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); }); applyGain(*tempSpan, *modulationSpan); @@ -273,7 +273,7 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept // Volume envelope fill(*modulationSpan, db2mag(baseVolumedB)); - for (auto& mod : region->volumeCC) { + for (const auto& mod : region->volumeCC) { const auto events = resources.midiState.getCCEvents(mod.cc); multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.value); }); applyGain(*tempSpan, *modulationSpan); @@ -300,7 +300,7 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept // Amplitude envelope fill(*modulationSpan, baseGain); - for (auto& mod : region->amplitudeCC) { + for (const auto& mod : region->amplitudeCC) { const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); applyGain(*tempSpan, *modulationSpan); @@ -309,12 +309,12 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept // Crossfade envelopes fill(*modulationSpan, 1.0f); - for (auto& mod : region->crossfadeCCInRange) { + 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); }); applyGain(*tempSpan, *modulationSpan); } - for (auto& mod : region->crossfadeCCOutRange) { + 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); }); applyGain(*tempSpan, *modulationSpan); @@ -323,7 +323,7 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept // Volume envelope fill(*modulationSpan, db2mag(baseVolumedB)); - for (auto& mod : region->volumeCC) { + for (const auto& mod : region->volumeCC) { const auto events = resources.midiState.getCCEvents(mod.cc); multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.value); }); applyGain(*tempSpan, *modulationSpan); @@ -353,7 +353,7 @@ void sfz::Voice::panStageMono(AudioSpan buffer) noexcept // Apply panning fill(*modulationSpan, region->pan); - for (auto& mod : region->panCC) { + for (const auto& mod : region->panCC) { const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); @@ -376,7 +376,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // Apply panning // panningModulation(*modulationSpan); fill(*modulationSpan, region->pan); - for (auto& mod : region->panCC) { + for (const auto& mod : region->panCC) { const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); @@ -386,7 +386,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // Apply the width/position process // widthModulation(*modulationSpan); fill(*modulationSpan, region->width); - for (auto& mod : region->widthCC) { + for (const auto& mod : region->widthCC) { const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); @@ -395,7 +395,7 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept // positionModulation(*modulationSpan); fill(*modulationSpan, region->position); - for (auto& mod : region->positionCC) { + for (const auto& mod : region->positionCC) { const auto events = resources.midiState.getCCEvents(mod.cc); linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; }); add(*tempSpan, *modulationSpan); From 5088bed9b041884a3611d2d182485396a819882b Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 31 Mar 2020 10:46:12 +0200 Subject: [PATCH 72/72] Formatting step --- benchmarks/BM_envelopes.cpp | 22 +-- src/sfizz/ADSREnvelope.cpp | 2 +- src/sfizz/BufferPool.h | 20 +-- src/sfizz/Defaults.h | 36 ++--- src/sfizz/MidiState.cpp | 11 +- src/sfizz/MidiState.h | 33 +++-- src/sfizz/SfzHelpers.h | 49 +++---- src/sfizz/Synth.cpp | 18 +-- src/sfizz/Voice.cpp | 13 +- tests/EGDescriptionT.cpp | 66 ++++----- tests/EventEnvelopesT.cpp | 119 ++++++++--------- tests/FilesT.cpp | 6 +- tests/MidiStateT.cpp | 2 +- tests/RegionT.cpp | 8 +- tests/RegionValueComputationsT.cpp | 208 ++++++++++++++++------------- tests/SynthT.cpp | 4 +- 16 files changed, 323 insertions(+), 294 deletions(-) diff --git a/benchmarks/BM_envelopes.cpp b/benchmarks/BM_envelopes.cpp index 3bff261e..836d6b0a 100644 --- a/benchmarks/BM_envelopes.cpp +++ b/benchmarks/BM_envelopes.cpp @@ -39,8 +39,8 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, Linear)(benchmark::State& state) { for (auto _ : state) { sfz::EventVector events { - {0, 0.0f}, - {static_cast(state.range(0) - 1), dist(gen)} + { 0, 0.0f }, + { static_cast(state.range(0) - 1), dist(gen) } }; linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); } @@ -50,10 +50,11 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, LinearQuantized)(benchmark::State& state) { for (auto _ : state) { sfz::EventVector events { - {0, 0.0f}, - {static_cast(state.range(0) - 1), dist(gen)} + { 0, 0.0f }, + { static_cast(state.range(0) - 1), dist(gen) } }; - linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }, 0.5); + linearEnvelope( + events, absl::MakeSpan(output), [](float x) { return x; }, 0.5); } } @@ -61,8 +62,8 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, Multiplicative)(benchmark::State& state) { for (auto _ : state) { sfz::EventVector events { - {0, 1.0f}, - {static_cast(state.range(0) - 1), dist(gen)} + { 0, 1.0f }, + { static_cast(state.range(0) - 1), dist(gen) } }; multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); } @@ -72,10 +73,11 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, MultiplicativeQuantized)(benchmark::State& s for (auto _ : state) { sfz::EventVector events { - {0, 1.0f}, - {static_cast(state.range(0) - 1), dist(gen)} + { 0, 1.0f }, + { static_cast(state.range(0) - 1), dist(gen) } }; - multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }, 2.0f); + multiplicativeEnvelope( + events, absl::MakeSpan(output), [](float x) { return x; }, 2.0f); } } diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 0acbdbe3..351b1267 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -24,7 +24,7 @@ void ADSREnvelope::reset(const Region& region, const MidiState& state, int this->release = secondsToSamples(region.amplitudeEG.getRelease(state, velocity)); this->hold = secondsToSamples(region.amplitudeEG.getHold(state, velocity)); this->peak = 1.0; - this->sustain = normalizePercents(region.amplitudeEG.getSustain(state, velocity)); + this->sustain = normalizePercents(region.amplitudeEG.getSustain(state, velocity)); this->start = this->peak * normalizePercents(region.amplitudeEG.getStart(state, velocity)); releaseDelay = 0; diff --git a/src/sfizz/BufferPool.h b/src/sfizz/BufferPool.h index 0f58d529..2d057edf 100644 --- a/src/sfizz/BufferPool.h +++ b/src/sfizz/BufferPool.h @@ -14,15 +14,13 @@ #include #include "absl/algorithm/container.h" #ifndef NDEBUG - #include "MathHelpers.h" +#include "MathHelpers.h" #endif -namespace sfz -{ +namespace sfz { -template -class SpanHolder -{ +template +class SpanHolder { public: SpanHolder() {} SpanHolder(const SpanHolder&) = delete; @@ -40,7 +38,10 @@ public: other.available = nullptr; } SpanHolder(T&& value, int* available) - : value(std::forward(value)), available(available) {} + : value(std::forward(value)) + , available(available) + { + } T& operator*() { return value; } T* operator->() { return &value; } explicit operator bool() const { return available != nullptr; } @@ -49,13 +50,13 @@ public: if (available) *available += 1; } + private: T value {}; int* available { nullptr }; }; -class BufferPool -{ +class BufferPool { public: BufferPool() { @@ -148,7 +149,6 @@ public: } #endif - private: void _setBufferSize(unsigned bufferSize) { diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 7b24ba47..ebef0041 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -57,7 +57,7 @@ namespace Default // common defaults constexpr Range midi7Range { 0, 127 }; constexpr Range normalizedRange { 0.0f, 1.0f }; - constexpr Range symmetricNormalizedRange { -1.0, 1.0 }; + constexpr Range symmetricNormalizedRange { -1.0, 1.0 }; constexpr float zeroModifier { 0.0f }; // Wavetable oscillator @@ -68,21 +68,21 @@ namespace Default constexpr uint32_t group { 0 }; constexpr Range groupRange { 0, std::numeric_limits::max() }; constexpr SfzOffMode offMode { SfzOffMode::fast }; - constexpr Range polyphonyRange { 0, config::maxVoices }; - constexpr SfzSelfMask selfMask { SfzSelfMask::mask }; + constexpr Range polyphonyRange { 0, config::maxVoices }; + constexpr SfzSelfMask selfMask { SfzSelfMask::mask }; - // Region logic: key mapping + // Region logic: key mapping constexpr Range keyRange { 0, 127 }; - constexpr auto velocityRange = normalizedRange; + constexpr auto velocityRange = normalizedRange; - // Region logic: MIDI conditions + // Region logic: MIDI conditions constexpr Range channelRange { 1, 16 }; constexpr Range midiChannelRange { 0, 15 }; - constexpr Range ccNumberRange { 0, config::numCCs }; - constexpr auto ccValueRange = normalizedRange; - constexpr Range bendRange = { -8192, 8192 }; - constexpr Range bendValueRange = symmetricNormalizedRange; - constexpr int bend { 0 }; + constexpr Range ccNumberRange { 0, config::numCCs }; + constexpr auto ccValueRange = normalizedRange; + constexpr Range bendRange = { -8192, 8192 }; + constexpr Range bendValueRange = symmetricNormalizedRange; + constexpr int bend { 0 }; constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current }; // Region logic: internal conditions @@ -97,9 +97,9 @@ namespace Default // Region logic: Triggers constexpr SfzTrigger trigger { SfzTrigger::attack }; - constexpr Range ccTriggerValueRange = normalizedRange; + constexpr Range ccTriggerValueRange = normalizedRange; - // Performance parameters: amplifier + // Performance parameters: amplifier constexpr float globalVolume { -7.35f }; constexpr float volume { 0.0f }; constexpr Range volumeRange { -144.0, 6.0 }; @@ -125,11 +125,11 @@ namespace Default constexpr Range ampRandomRange { 0.0, 24.0 }; constexpr Range crossfadeKeyInRange { 0, 0 }; constexpr Range crossfadeKeyOutRange { 127, 127 }; - constexpr Range crossfadeVelInRange { 0.0f, 0.0f }; - constexpr Range crossfadeVelOutRange { 1.0f, 1.0f }; - constexpr Range crossfadeCCInRange { 0.0f, 0.0f }; - constexpr Range crossfadeCCOutRange { 1.0f, 1.0f }; - constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power }; + constexpr Range crossfadeVelInRange { 0.0f, 0.0f }; + constexpr Range crossfadeVelOutRange { 1.0f, 1.0f }; + constexpr Range crossfadeCCInRange { 0.0f, 0.0f }; + constexpr Range crossfadeCCOutRange { 1.0f, 1.0f }; + constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power }; constexpr SfzCrossfadeCurve crossfadeVelCurve { SfzCrossfadeCurve::power }; constexpr SfzCrossfadeCurve crossfadeCCCurve { SfzCrossfadeCurve::power }; constexpr float rtDecay { 0.0f }; diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 19b78888..0b932010 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -51,7 +51,7 @@ void sfz::MidiState::setSampleRate(float sampleRate) noexcept void sfz::MidiState::advanceTime(int numSamples) noexcept { internalClock += numSamples; - for (auto& ccEvents: cc) { + for (auto& ccEvents : cc) { ASSERT(!ccEvents.empty()); // CC event vectors should never be empty ccEvents.front().value = ccEvents.back().value; ccEvents.front().delay = 0; @@ -66,7 +66,7 @@ void sfz::MidiState::advanceTime(int numSamples) noexcept void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept { this->samplesPerBlock = samplesPerBlock; - for (auto& ccEvents: cc) { + for (auto& ccEvents : cc) { ccEvents.shrink_to_fit(); ccEvents.reserve(samplesPerBlock); } @@ -92,12 +92,11 @@ float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept return lastNoteVelocities[noteNumber]; } - void sfz::MidiState::pitchBendEvent(int delay, float pitchBendValue) noexcept { ASSERT(pitchBendValue >= -1.0f && pitchBendValue <= 1.0f); - const auto insertionPoint = absl::c_upper_bound(pitchEvents, delay, MidiEventDelayComparator{}); + const auto insertionPoint = absl::c_upper_bound(pitchEvents, delay, MidiEventDelayComparator {}); if (insertionPoint == pitchEvents.end() || insertionPoint->delay != delay) pitchEvents.insert(insertionPoint, { delay, pitchBendValue }); else @@ -113,7 +112,7 @@ float sfz::MidiState::getPitchBend() const noexcept void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept { ASSERT(ccValue >= 0.0 && ccValue <= 1.0); - const auto insertionPoint = absl::c_upper_bound(cc[ccNumber], delay, MidiEventDelayComparator{}); + const auto insertionPoint = absl::c_upper_bound(cc[ccNumber], delay, MidiEventDelayComparator {}); if (insertionPoint == cc[ccNumber].end() || insertionPoint->delay != delay) cc[ccNumber].insert(insertionPoint, { delay, ccValue }); else @@ -132,7 +131,7 @@ void sfz::MidiState::reset() noexcept for (auto& velocity: lastNoteVelocities) velocity = 0; - for (auto& ccEvents: cc) { + for (auto& ccEvents : cc) { ccEvents.clear(); ccEvents.push_back({ 0, 0.0f }); } diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 63ee23b3..021ff415 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -28,7 +28,7 @@ public: * @param noteNumber * @param velocity */ - void noteOnEvent(int delay, int noteNumber, float velocity) noexcept; + void noteOnEvent(int delay, int noteNumber, float velocity) noexcept; /** * @brief Update the state after a note off event @@ -36,7 +36,7 @@ public: * @param noteNumber * @param velocity */ - void noteOffEvent(int delay, int noteNumber, float velocity) noexcept; + void noteOffEvent(int delay, int noteNumber, float velocity) noexcept; int getActiveNotes() const noexcept { return activeNotes; } @@ -47,7 +47,7 @@ public: * @param delay * @return float */ - float getNoteDuration(int noteNumber, int delay = 0) const; + float getNoteDuration(int noteNumber, int delay = 0) const; /** * @brief Set the maximum size of the blocks for the callback. The actual @@ -70,7 +70,7 @@ public: * @param noteNumber * @return float */ - float getNoteVelocity(int noteNumber) const noexcept; + float getNoteVelocity(int noteNumber) const noexcept; /** * @brief Register a pitch bend event @@ -125,35 +125,42 @@ public: const EventVector& getPitchEvents() const noexcept; private: - - - int activeNotes { 0 }; + int activeNotes { 0 }; /** * @brief Stores the note on times. * */ - MidiNoteArray noteOnTimes {{}}; + MidiNoteArray noteOnTimes { {} }; + /** * @brief Stores the note off times. * */ - MidiNoteArray noteOffTimes {{}}; + + MidiNoteArray noteOffTimes { {} }; + /** * @brief Stores the velocity of the note ons for currently * depressed notes. * */ - MidiNoteArray lastNoteVelocities; + MidiNoteArray lastNoteVelocities; + /** * @brief Current known values for the CCs. * */ - std::array cc; + std::array cc; - const EventVector nullEvent {{0, 0.0f}}; /** - * Pitch bend status + * @brief Null event + * + */ + const EventVector nullEvent { { 0, 0.0f } }; + + /** + * @brief Pitch bend status */ EventVector pitchEvents; float sampleRate { config::defaultSampleRate }; diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 32d042be..64d6ed6c 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -21,7 +21,7 @@ namespace sfz { using CCNamePair = std::pair; -template +template using MidiNoteArray = std::array; template struct CCValuePair { @@ -193,17 +193,15 @@ constexpr float normalizeBend(float bendValue) return clamp(bendValue, -8191.0f, 8191.0f) / 8191.0f; } -namespace literals -{ -inline float operator ""_norm(unsigned long long int value) -{ - if (value > 127) - value = 127; +namespace literals { + inline float operator""_norm(unsigned long long int value) + { + if (value > 127) + value = 127; - return normalize7Bits(value); + return normalize7Bits(value); + } } -} - /** * @brief Convert a note in string to its equivalent midi note number @@ -275,7 +273,6 @@ 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. * @@ -284,20 +281,19 @@ bool findInclude(absl::string_view line, std::string& path); */ inline CXX14_CONSTEXPR float multiplyByCentsModifier(int modifier, float base) { - return base * centsFactor(modifier); + return base * centsFactor(modifier); } -template +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 +template float crossfadeIn(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCurve curve) { if (value < crossfadeRange.getStart()) @@ -318,11 +314,10 @@ float crossfadeIn(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCurv return 1.0f; } - /** * @brief Compute a crossfade out value with respect to a crossfade range (note, velocity, cc, ...) */ -template +template float crossfadeOut(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCurve curve) { if (value > crossfadeRange.getEnd()) @@ -343,7 +338,7 @@ float crossfadeOut(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCur return 1.0f; } -template +template void linearEnvelope(const EventVector& events, absl::Span envelope, F&& lambda) { ASSERT(events.size() > 0); @@ -355,16 +350,16 @@ void linearEnvelope(const EventVector& events, absl::Span envelope, F&& l auto lastValue = lambda(events[0].value); auto lastDelay = events[0].delay; - for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++ i) { + 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; + 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 +template void linearEnvelope(const EventVector& events, absl::Span envelope, F&& lambda, float step) { ASSERT(events.size() > 0); @@ -381,7 +376,7 @@ void linearEnvelope(const EventVector& events, absl::Span envelope, F&& l auto lastValue = quantize(lambda(events[0].value)); auto lastDelay = events[0].delay; - for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++ i) { + 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; @@ -404,7 +399,7 @@ void linearEnvelope(const EventVector& events, absl::Span envelope, F&& l fill(envelope.subspan(lastDelay), lastValue); } -template +template void multiplicativeEnvelope(const EventVector& events, absl::Span envelope, F&& lambda) { ASSERT(events.size() > 0); @@ -416,7 +411,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span envelop auto lastValue = lambda(events[0].value); auto lastDelay = events[0].delay; - for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++ i) { + 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); @@ -427,7 +422,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); @@ -445,12 +440,12 @@ 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::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) { + 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; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 4ac28d54..31d959dc 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -705,16 +705,16 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc if (voice->getTriggerNumber() == noteNumber && voice->getTriggerType() == Voice::TriggerType::NoteOn) { activeNotes += 1; switch (region->selfMask) { - case SfzSelfMask::mask: - if (voice->getTriggerValue() < velocity) { - if (!selfMaskCandidate || selfMaskCandidate->getTriggerValue() > voice->getTriggerValue()) - selfMaskCandidate = voice.get(); - } - break; - case SfzSelfMask::dontMask: - if (!selfMaskCandidate || selfMaskCandidate->getSourcePosition() < voice->getSourcePosition()) + case SfzSelfMask::mask: + if (voice->getTriggerValue() < velocity) { + if (!selfMaskCandidate || selfMaskCandidate->getTriggerValue() > voice->getTriggerValue()) selfMaskCandidate = voice.get(); - break; + } + break; + case SfzSelfMask::dontMask: + if (!selfMaskCandidate || selfMaskCandidate->getSourcePosition() < voice->getSourcePosition()) + selfMaskCandidate = voice.get(); + break; } } } diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 8881d453..5da0f909 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -410,11 +410,11 @@ void sfz::Voice::filterStageMono(AudioSpan buffer) noexcept const auto leftBuffer = buffer.getSpan(0); const float* inputChannel[1] { leftBuffer.data() }; float* outputChannel[1] { leftBuffer.data() }; - for (auto& filter: filters) { + for (auto& filter : filters) { filter->process(inputChannel, outputChannel, numSamples); } - for (auto& eq: equalizers) { + for (auto& eq : equalizers) { eq->process(inputChannel, outputChannel, numSamples); } } @@ -429,11 +429,11 @@ void sfz::Voice::filterStageStereo(AudioSpan buffer) noexcept const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() }; float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() }; - for (auto& filter: filters) { + for (auto& filter : filters) { filter->process(inputChannels, outputChannels, numSamples); } - for (auto& eq: equalizers) { + for (auto& eq : equalizers) { eq->process(inputChannels, outputChannels, numSamples); } } @@ -462,7 +462,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept fill(*jumps, pitchRatio * speedRatio); const auto events = resources.midiState.getPitchEvents(); - const auto bendLambda = [this](float bend){ + const auto bendLambda = [this](float bend) { const auto bendInCents = bend > 0.0f ? bend * static_cast(region->bendUp) : -bend * static_cast(region->bendDown); return centsFactor(bendInCents); }; @@ -545,7 +545,6 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept const auto leftSpan = buffer.getSpan(0); const auto rightSpan = buffer.getSpan(1); - if (region->sample == "*noise") { absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); }); absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); }); @@ -560,7 +559,7 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept fill(*frequencies, pitchRatio * keycenterFrequency); const auto events = resources.midiState.getPitchEvents(); - const auto bendLambda = [this](float bend){ + const auto bendLambda = [this](float bend) { const auto bendInCents = bend > 0.0f ? bend * static_cast(region->bendUp) : -bend * static_cast(region->bendDown); return centsFactor(bendInCents); }; diff --git a/tests/EGDescriptionT.cpp b/tests/EGDescriptionT.cpp index 305a60fe..25730dc9 100644 --- a/tests/EGDescriptionT.cpp +++ b/tests/EGDescriptionT.cpp @@ -18,13 +18,13 @@ TEST_CASE("[EGDescription] Attack range") eg.attack = 1; eg.vel2attack = -1.27f; eg.ccAttack = { 63, 1.27f }; - REQUIRE( eg.getAttack(state, 0_norm) == 1.0f ); - REQUIRE( eg.getAttack(state, 127_norm) == 0.0f ); + REQUIRE(eg.getAttack(state, 0_norm) == 1.0f); + REQUIRE(eg.getAttack(state, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE( eg.getAttack(state, 127_norm) == 1.0f ); - REQUIRE( eg.getAttack(state, 0_norm) == 2.27f ); + REQUIRE(eg.getAttack(state, 127_norm) == 1.0f); + REQUIRE(eg.getAttack(state, 0_norm) == 2.27f); eg.ccAttack = { 63, 127.0f }; - REQUIRE( eg.getAttack(state, 0_norm) == 100.0f ); + REQUIRE(eg.getAttack(state, 0_norm) == 100.0f); } TEST_CASE("[EGDescription] Delay range") @@ -34,13 +34,13 @@ TEST_CASE("[EGDescription] Delay range") eg.delay = 1; eg.vel2delay = -1.27f; eg.ccDelay = { 63, 1.27f }; - REQUIRE( eg.getDelay(state, 0_norm) == 1.0f ); - REQUIRE( eg.getDelay(state, 127_norm) == 0.0f ); + REQUIRE(eg.getDelay(state, 0_norm) == 1.0f); + REQUIRE(eg.getDelay(state, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE( eg.getDelay(state, 127_norm) == 1.0f ); - REQUIRE( eg.getDelay(state, 0_norm) == 2.27f ); + REQUIRE(eg.getDelay(state, 127_norm) == 1.0f); + REQUIRE(eg.getDelay(state, 0_norm) == 2.27f); eg.ccDelay = { 63, 127.0f }; - REQUIRE( eg.getDelay(state, 0_norm) == 100.0f ); + REQUIRE(eg.getDelay(state, 0_norm) == 100.0f); } TEST_CASE("[EGDescription] Decay range") @@ -50,13 +50,13 @@ TEST_CASE("[EGDescription] Decay range") eg.decay = 1.0f; eg.vel2decay = -1.27f; eg.ccDecay = { 63, 1.27f }; - REQUIRE( eg.getDecay(state, 0_norm) == 1.0f ); - REQUIRE( eg.getDecay(state, 127_norm) == 0.0f ); + REQUIRE(eg.getDecay(state, 0_norm) == 1.0f); + REQUIRE(eg.getDecay(state, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE( eg.getDecay(state, 127_norm) == 1.0f ); - REQUIRE( eg.getDecay(state, 0_norm) == 2.27f ); + REQUIRE(eg.getDecay(state, 127_norm) == 1.0f); + REQUIRE(eg.getDecay(state, 0_norm) == 2.27f); eg.ccDecay = { 63, 127.0f }; - REQUIRE( eg.getDecay(state, 0_norm) == 100.0f ); + REQUIRE(eg.getDecay(state, 0_norm) == 100.0f); } TEST_CASE("[EGDescription] Release range") @@ -66,13 +66,13 @@ TEST_CASE("[EGDescription] Release range") eg.release = 1; eg.vel2release = -1.27f; eg.ccRelease = { 63, 1.27f }; - REQUIRE( eg.getRelease(state, 0_norm) == 1.0f ); - REQUIRE( eg.getRelease(state, 127_norm) == 0.0f ); + REQUIRE(eg.getRelease(state, 0_norm) == 1.0f); + REQUIRE(eg.getRelease(state, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE( eg.getRelease(state, 127_norm) == 1.0f ); - REQUIRE( eg.getRelease(state, 0_norm) == 2.27f ); + REQUIRE(eg.getRelease(state, 127_norm) == 1.0f); + REQUIRE(eg.getRelease(state, 0_norm) == 2.27f); eg.ccRelease = { 63, 127.0f }; - REQUIRE( eg.getRelease(state, 0_norm) == 100.0f ); + REQUIRE(eg.getRelease(state, 0_norm) == 100.0f); } TEST_CASE("[EGDescription] Hold range") @@ -82,13 +82,13 @@ TEST_CASE("[EGDescription] Hold range") eg.hold = 1; eg.vel2hold = -1.27f; eg.ccHold = { 63, 1.27f }; - REQUIRE( eg.getHold(state, 0_norm) == 1.0f ); - REQUIRE( eg.getHold(state, 127_norm) == 0.0f ); + REQUIRE(eg.getHold(state, 0_norm) == 1.0f); + REQUIRE(eg.getHold(state, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE( eg.getHold(state, 127_norm) == 1.0f ); - REQUIRE( eg.getHold(state, 0_norm) == 2.27f ); + REQUIRE(eg.getHold(state, 127_norm) == 1.0f); + REQUIRE(eg.getHold(state, 0_norm) == 2.27f); eg.ccHold = { 63, 127.0f }; - REQUIRE( eg.getHold(state, 0_norm) == 100.0f ); + REQUIRE(eg.getHold(state, 0_norm) == 100.0f); } TEST_CASE("[EGDescription] Sustain level") @@ -98,12 +98,12 @@ TEST_CASE("[EGDescription] Sustain level") eg.sustain = 50; eg.vel2sustain = -100; eg.ccSustain = { 63, 100.0f }; - REQUIRE( eg.getSustain(state, 0_norm) == 50.0f ); - REQUIRE( eg.getSustain(state, 127_norm) == 0.0f ); + REQUIRE(eg.getSustain(state, 0_norm) == 50.0f); + REQUIRE(eg.getSustain(state, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE( eg.getSustain(state, 127_norm) == 50.0f ); + REQUIRE(eg.getSustain(state, 127_norm) == 50.0f); eg.ccSustain = { 63, 200.0f }; - REQUIRE( eg.getSustain(state, 0_norm) == 100.0f ); + REQUIRE(eg.getSustain(state, 0_norm) == 100.0f); } TEST_CASE("[EGDescription] Start level") @@ -112,10 +112,10 @@ TEST_CASE("[EGDescription] Start level") sfz::MidiState state; eg.start = 0; eg.ccStart = { 63, 127.0f }; - REQUIRE( eg.getStart(state, 0_norm) == 0.0f ); - REQUIRE( eg.getStart(state, 127_norm) == 0.0f ); + REQUIRE(eg.getStart(state, 0_norm) == 0.0f); + REQUIRE(eg.getStart(state, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE( eg.getStart(state, 0_norm) == 100.0f ); + REQUIRE(eg.getStart(state, 0_norm) == 100.0f); eg.ccStart = { 63, -127.0f }; - REQUIRE( eg.getStart(state, 0_norm) == 0.0f ); + REQUIRE(eg.getStart(state, 0_norm) == 0.0f); } diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index d4efd80a..545ea594 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -29,14 +29,14 @@ inline bool approxEqual(absl::Span lhs, absl::Span rhs, return true; } -const auto idModifier = [] (float x) { return x; }; -const auto twiceModifier = [] (float x) { return 2 * x; }; -const auto expModifier = [] (float x) { return std::exp(x); }; +const auto idModifier = [](float x) { return x; }; +const auto twiceModifier = [](float x) { return 2 * x; }; +const auto expModifier = [](float x) { return std::exp(x); }; TEST_CASE("[Envelopes] Empty") { sfz::EventVector events { - {0, 0.0f} + { 0, 0.0f } }; std::array output; std::array expected { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; @@ -54,8 +54,8 @@ TEST_CASE("[Envelopes] Empty") TEST_CASE("[Envelopes] Linear basic") { sfz::EventVector events { - {0, 0.0f}, - {4, 1.0f} + { 0, 0.0f }, + { 4, 1.0f } }; std::array output; std::array expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; @@ -66,9 +66,9 @@ TEST_CASE("[Envelopes] Linear basic") TEST_CASE("[LinearEnvelope] 2 events, close") { sfz::EventVector events { - {0, 0.0f}, - {4, 1.0f}, - {5, 2.0f} + { 0, 0.0f }, + { 4, 1.0f }, + { 5, 2.0f } }; std::array output; std::array expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f }; @@ -76,13 +76,12 @@ TEST_CASE("[LinearEnvelope] 2 events, close") REQUIRE(output == expected); } - TEST_CASE("[LinearEnvelope] 2 events, far") { sfz::EventVector events { - {0, 0.0f}, - {2, 1.0f}, - {6, 2.0f} + { 0, 0.0f }, + { 2, 1.0f }, + { 6, 2.0f } }; std::array output; std::array expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f }; @@ -93,10 +92,10 @@ TEST_CASE("[LinearEnvelope] 2 events, far") TEST_CASE("[LinearEnvelope] 3 events, out of block") { sfz::EventVector events { - {0, 0.0f}, - {2, 1.0f}, - {6, 2.0f}, - {10, 3.0f} + { 0, 0.0f }, + { 2, 1.0f }, + { 6, 2.0f }, + { 10, 3.0f } }; std::array output; std::array expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f }; @@ -107,9 +106,9 @@ TEST_CASE("[LinearEnvelope] 3 events, out of block") TEST_CASE("[LinearEnvelope] 2 events, function") { sfz::EventVector events { - {0, 0.0f}, - {2, 1.0f}, - {6, 2.0f} + { 0, 0.0f }, + { 2, 1.0f }, + { 6, 2.0f } }; std::array output; std::array expected { 0.0f, 1.0f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.0f, 4.0f }; @@ -120,9 +119,9 @@ TEST_CASE("[LinearEnvelope] 2 events, function") TEST_CASE("[LinearEnvelope] Get quantized") { sfz::EventVector events { - {0, 0.0f}, - {2, 1.0f}, - {6, 2.0f} + { 0, 0.0f }, + { 2, 1.0f }, + { 6, 2.0f } }; std::array output; std::array expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f }; @@ -133,9 +132,9 @@ TEST_CASE("[LinearEnvelope] Get quantized") TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets") { sfz::EventVector events { - {0, 0.0f}, - {2, 1.1f}, - {6, 1.9f} + { 0, 0.0f }, + { 2, 1.1f }, + { 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 }; @@ -146,9 +145,9 @@ TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets") TEST_CASE("[LinearEnvelope] Get quantized with 2 steps") { sfz::EventVector events { - {0, 0.0f}, - {2, 1.0f}, - {6, 3.0f} + { 0, 0.0f }, + { 2, 1.0f }, + { 6, 3.0f } }; std::array output; std::array expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f }; @@ -159,10 +158,10 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps") TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of block step") { sfz::EventVector events { - {0, 0.0f}, - {2, 1.0f}, - {6, 3.0f}, - {10, 4.2f}, + { 0, 0.0f }, + { 2, 1.0f }, + { 6, 3.0f }, + { 10, 4.2f }, }; std::array output; std::array expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 4.0f }; @@ -174,9 +173,9 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps") { sfz::EventVector events { - {0, 3.0f}, - {2, 2.0f}, - {6, 0.0f} + { 0, 3.0f }, + { 2, 2.0f }, + { 6, 0.0f } }; std::array output; std::array expected { 3.0f, 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f }; @@ -187,8 +186,8 @@ TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps") TEST_CASE("[MultiplicativeEnvelope] Basic event") { sfz::EventVector events { - {0, 1.0f}, - {4, 2.0f} + { 0, 1.0f }, + { 4, 2.0f } }; std::array output; std::array expected { 1.0f, 1.1892f, 1.4142f, 1.68176f, 2.0f, 2.0f, 2.0f, 2.0f }; @@ -199,12 +198,12 @@ TEST_CASE("[MultiplicativeEnvelope] Basic event") TEST_CASE("[MultiplicativeEnvelope] 2 events") { sfz::EventVector events { - {0, 1.0f}, - {4, 2.0f}, - {5, 4.0f} + { 0, 1.0f }, + { 4, 2.0f }, + { 5, 4.0f } }; std::array output; - std::array expected { 1.0f, 1.1892f, 1.4142f, 1.68176f, 2.0f, 4.0f, 4.0f, 4.0f}; + std::array expected { 1.0f, 1.1892f, 1.4142f, 1.68176f, 2.0f, 4.0f, 4.0f, 4.0f }; multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier); REQUIRE(approxEqual(output, expected)); } @@ -212,12 +211,12 @@ TEST_CASE("[MultiplicativeEnvelope] 2 events") TEST_CASE("[MultiplicativeEnvelope] 2 events, far") { sfz::EventVector events { - {0, 1.0f}, - {2, 2.0f}, - {6, 4.0f} + { 0, 1.0f }, + { 2, 2.0f }, + { 6, 4.0f } }; std::array output; - std::array expected { 1.0f, 1.4142f, 2.0f, 2.37841f, 2.82843f, 3.36358f, 4.0f, 4.0f}; + std::array expected { 1.0f, 1.4142f, 2.0f, 2.37841f, 2.82843f, 3.36358f, 4.0f, 4.0f }; multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier); REQUIRE(approxEqual(output, expected)); } @@ -225,12 +224,12 @@ 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.0f }, + { 2, 2.0f }, + { 6, 4.0f } }; std::array output; - std::array expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 4.0f}; + 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); } @@ -238,10 +237,10 @@ 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}, - {10, 8.2f} + { 0, 1.0f }, + { 2, 2.0f }, + { 6, 4.0f }, + { 10, 8.2f } }; std::array output; std::array expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 8.0f }; @@ -252,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.0f }, + { 2, 2.0f }, + { 6, 0.5f } }; std::array output; std::array expected { 4.0f, 4.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f }; @@ -265,9 +264,9 @@ TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps") TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events") { sfz::EventVector events { - {0, 1.0f}, - {2, 1.2f}, - {6, 2.5f} + { 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 }; diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index a0fd78e1..f4bdc3c4 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -309,9 +309,9 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines") REQUIRE( synth.getRegionView(0)->keyRange.getEnd() == 52 ); REQUIRE( synth.getRegionView(1)->keyRange.getStart() == 57 ); 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.empty()); + REQUIRE(synth.getRegionView(2)->amplitudeCC.contains(10)); + REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10) == 0.34f); } TEST_CASE("[Files] Specific bug: relative path with backslashes") diff --git a/tests/MidiStateT.cpp b/tests/MidiStateT.cpp index a47a14c8..9e25f162 100644 --- a/tests/MidiStateT.cpp +++ b/tests/MidiStateT.cpp @@ -21,7 +21,7 @@ TEST_CASE("[MidiState] Initial values") { sfz::MidiState state; for (unsigned cc = 0; cc < sfz::config::numCCs; cc++) - REQUIRE( state.getCCValue(cc) == 0_norm ); + REQUIRE(state.getCCValue(cc) == 0_norm); REQUIRE( state.getPitchBend() == 0 ); } diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 8e8bf851..0ebe0cef 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -1434,13 +1434,13 @@ TEST_CASE("[Region] Parsing opcodes") SECTION("amplitude") { - REQUIRE(region.amplitude == 1.0_a ); + REQUIRE(region.amplitude == 1.0_a); region.parseOpcode({ "amplitude", "40" }); - REQUIRE(region.amplitude == 0.4_a ); + REQUIRE(region.amplitude == 0.4_a); region.parseOpcode({ "amplitude", "-40" }); - REQUIRE(region.amplitude == 0_a ); + REQUIRE(region.amplitude == 0_a); region.parseOpcode({ "amplitude", "140" }); - REQUIRE(region.amplitude == 1.0_a ); + REQUIRE(region.amplitude == 1.0_a); } SECTION("amplitude_cc") diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index 0f760b06..e6547b01 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -21,9 +21,9 @@ TEST_CASE("[Region] Crossfade in on key") region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "3" }); - REQUIRE( region.getNoteGain(2, 127_norm) == 0.70711_a ); - REQUIRE( region.getNoteGain(1, 127_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(3, 127_norm) == 1.0_a ); + REQUIRE(region.getNoteGain(2, 127_norm) == 0.70711_a); + REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a); + REQUIRE(region.getNoteGain(3, 127_norm) == 1.0_a); } TEST_CASE("[Region] Crossfade in on key - 2") @@ -33,12 +33,12 @@ TEST_CASE("[Region] Crossfade in on key - 2") region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); - REQUIRE( region.getNoteGain(1, 127_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(2, 127_norm) == 0.5_a ); - REQUIRE( region.getNoteGain(3, 127_norm) == 0.70711_a ); - REQUIRE( region.getNoteGain(4, 127_norm) == 0.86603_a ); - REQUIRE( region.getNoteGain(5, 127_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(6, 127_norm) == 1.0_a ); + REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a); + REQUIRE(region.getNoteGain(2, 127_norm) == 0.5_a); + REQUIRE(region.getNoteGain(3, 127_norm) == 0.70711_a); + REQUIRE(region.getNoteGain(4, 127_norm) == 0.86603_a); + REQUIRE(region.getNoteGain(5, 127_norm) == 1.0_a); + REQUIRE(region.getNoteGain(6, 127_norm) == 1.0_a); } TEST_CASE("[Region] Crossfade in on key - gain") @@ -49,11 +49,11 @@ TEST_CASE("[Region] Crossfade in on key - gain") region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); region.parseOpcode({ "xf_keycurve", "gain" }); - REQUIRE( region.getNoteGain(1, 127_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(2, 127_norm) == 0.25_a ); - REQUIRE( region.getNoteGain(3, 127_norm) == 0.5_a ); - REQUIRE( region.getNoteGain(4, 127_norm) == 0.75_a ); - REQUIRE( region.getNoteGain(5, 127_norm) == 1.0_a ); + REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a); + REQUIRE(region.getNoteGain(2, 127_norm) == 0.25_a); + REQUIRE(region.getNoteGain(3, 127_norm) == 0.5_a); + REQUIRE(region.getNoteGain(4, 127_norm) == 0.75_a); + REQUIRE(region.getNoteGain(5, 127_norm) == 1.0_a); } TEST_CASE("[Region] Crossfade out on key") @@ -63,13 +63,13 @@ TEST_CASE("[Region] Crossfade out on key") region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); - REQUIRE( region.getNoteGain(50, 127_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(51, 127_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(52, 127_norm) == 0.86603_a ); - REQUIRE( region.getNoteGain(53, 127_norm) == 0.70711_a ); - REQUIRE( region.getNoteGain(54, 127_norm) == 0.5_a ); - REQUIRE( region.getNoteGain(55, 127_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(56, 127_norm) == 0.0_a ); + REQUIRE(region.getNoteGain(50, 127_norm) == 1.0_a); + REQUIRE(region.getNoteGain(51, 127_norm) == 1.0_a); + REQUIRE(region.getNoteGain(52, 127_norm) == 0.86603_a); + REQUIRE(region.getNoteGain(53, 127_norm) == 0.70711_a); + REQUIRE(region.getNoteGain(54, 127_norm) == 0.5_a); + REQUIRE(region.getNoteGain(55, 127_norm) == 0.0_a); + REQUIRE(region.getNoteGain(56, 127_norm) == 0.0_a); } TEST_CASE("[Region] Crossfade out on key - gain") @@ -80,13 +80,13 @@ TEST_CASE("[Region] Crossfade out on key - gain") region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); region.parseOpcode({ "xf_keycurve", "gain" }); - REQUIRE( region.getNoteGain(50, 127_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(51, 127_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(52, 127_norm) == 0.75_a ); - REQUIRE( region.getNoteGain(53, 127_norm) == 0.5_a ); - REQUIRE( region.getNoteGain(54, 127_norm) == 0.25_a ); - REQUIRE( region.getNoteGain(55, 127_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(56, 127_norm) == 0.0_a ); + REQUIRE(region.getNoteGain(50, 127_norm) == 1.0_a); + REQUIRE(region.getNoteGain(51, 127_norm) == 1.0_a); + REQUIRE(region.getNoteGain(52, 127_norm) == 0.75_a); + REQUIRE(region.getNoteGain(53, 127_norm) == 0.5_a); + REQUIRE(region.getNoteGain(54, 127_norm) == 0.25_a); + REQUIRE(region.getNoteGain(55, 127_norm) == 0.0_a); + REQUIRE(region.getNoteGain(56, 127_norm) == 0.0_a); } TEST_CASE("[Region] Crossfade in on velocity") @@ -97,13 +97,13 @@ TEST_CASE("[Region] Crossfade in on velocity") region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_hivel", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE( region.getNoteGain(1, 19_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(1, 20_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(2, 21_norm) == 0.5_a ); - REQUIRE( region.getNoteGain(3, 22_norm) == 0.70711_a ); - REQUIRE( region.getNoteGain(4, 23_norm) == 0.86603_a ); - REQUIRE( region.getNoteGain(5, 24_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(6, 25_norm) == 1.0_a ); + REQUIRE(region.getNoteGain(1, 19_norm) == 0.0_a); + REQUIRE(region.getNoteGain(1, 20_norm) == 0.0_a); + REQUIRE(region.getNoteGain(2, 21_norm) == 0.5_a); + REQUIRE(region.getNoteGain(3, 22_norm) == 0.70711_a); + REQUIRE(region.getNoteGain(4, 23_norm) == 0.86603_a); + REQUIRE(region.getNoteGain(5, 24_norm) == 1.0_a); + REQUIRE(region.getNoteGain(6, 25_norm) == 1.0_a); } TEST_CASE("[Region] Crossfade in on vel - gain") @@ -115,13 +115,13 @@ TEST_CASE("[Region] Crossfade in on vel - gain") region.parseOpcode({ "xfin_hivel", "24" }); region.parseOpcode({ "xf_velcurve", "gain" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE( region.getNoteGain(1, 19_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(1, 20_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(2, 21_norm) == 0.25_a ); - REQUIRE( region.getNoteGain(3, 22_norm) == 0.5_a ); - REQUIRE( region.getNoteGain(4, 23_norm) == 0.75_a ); - REQUIRE( region.getNoteGain(5, 24_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(5, 25_norm) == 1.0_a ); + REQUIRE(region.getNoteGain(1, 19_norm) == 0.0_a); + REQUIRE(region.getNoteGain(1, 20_norm) == 0.0_a); + REQUIRE(region.getNoteGain(2, 21_norm) == 0.25_a); + REQUIRE(region.getNoteGain(3, 22_norm) == 0.5_a); + REQUIRE(region.getNoteGain(4, 23_norm) == 0.75_a); + REQUIRE(region.getNoteGain(5, 24_norm) == 1.0_a); + REQUIRE(region.getNoteGain(5, 25_norm) == 1.0_a); } TEST_CASE("[Region] Crossfade out on vel") @@ -132,13 +132,13 @@ TEST_CASE("[Region] Crossfade out on vel") region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_hivel", "55" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE( region.getNoteGain(5, 50_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(5, 51_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(5, 52_norm) == 0.86603_a ); - REQUIRE( region.getNoteGain(5, 53_norm) == 0.70711_a ); - REQUIRE( region.getNoteGain(5, 54_norm) == 0.5_a ); - REQUIRE( region.getNoteGain(5, 55_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(5, 56_norm) == 0.0_a ); + REQUIRE(region.getNoteGain(5, 50_norm) == 1.0_a); + REQUIRE(region.getNoteGain(5, 51_norm) == 1.0_a); + REQUIRE(region.getNoteGain(5, 52_norm) == 0.86603_a); + REQUIRE(region.getNoteGain(5, 53_norm) == 0.70711_a); + REQUIRE(region.getNoteGain(5, 54_norm) == 0.5_a); + REQUIRE(region.getNoteGain(5, 55_norm) == 0.0_a); + REQUIRE(region.getNoteGain(5, 56_norm) == 0.0_a); } TEST_CASE("[Region] Crossfade out on vel - gain") @@ -150,13 +150,13 @@ TEST_CASE("[Region] Crossfade out on vel - gain") region.parseOpcode({ "xfout_hivel", "55" }); region.parseOpcode({ "xf_velcurve", "gain" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE( region.getNoteGain(56, 50_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(56, 51_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(56, 52_norm) == 0.75_a ); - REQUIRE( region.getNoteGain(56, 53_norm) == 0.5_a ); - REQUIRE( region.getNoteGain(56, 54_norm) == 0.25_a ); - REQUIRE( region.getNoteGain(56, 55_norm) == 0.0_a ); - REQUIRE( region.getNoteGain(56, 56_norm) == 0.0_a ); + REQUIRE(region.getNoteGain(56, 50_norm) == 1.0_a); + REQUIRE(region.getNoteGain(56, 51_norm) == 1.0_a); + REQUIRE(region.getNoteGain(56, 52_norm) == 0.75_a); + REQUIRE(region.getNoteGain(56, 53_norm) == 0.5_a); + REQUIRE(region.getNoteGain(56, 54_norm) == 0.25_a); + REQUIRE(region.getNoteGain(56, 55_norm) == 0.0_a); + REQUIRE(region.getNoteGain(56, 56_norm) == 0.0_a); } TEST_CASE("[Region] Crossfade in on CC") @@ -167,13 +167,20 @@ TEST_CASE("[Region] Crossfade in on CC") region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); - midiState.ccEvent(0, 24, 19_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a ); - midiState.ccEvent(0, 24, 20_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a ); - midiState.ccEvent(0, 24, 21_norm); REQUIRE( region.getCrossfadeGain() == 0.5_a ); - midiState.ccEvent(0, 24, 22_norm); REQUIRE( region.getCrossfadeGain() == 0.70711_a ); - midiState.ccEvent(0, 24, 23_norm); REQUIRE( region.getCrossfadeGain() == 0.86603_a ); - midiState.ccEvent(0, 24, 24_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a ); - midiState.ccEvent(0, 24, 25_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a ); + midiState.ccEvent(0, 24, 19_norm); + REQUIRE(region.getCrossfadeGain() == 0.0_a); + midiState.ccEvent(0, 24, 20_norm); + REQUIRE(region.getCrossfadeGain() == 0.0_a); + midiState.ccEvent(0, 24, 21_norm); + REQUIRE(region.getCrossfadeGain() == 0.5_a); + midiState.ccEvent(0, 24, 22_norm); + REQUIRE(region.getCrossfadeGain() == 0.70711_a); + midiState.ccEvent(0, 24, 23_norm); + REQUIRE(region.getCrossfadeGain() == 0.86603_a); + midiState.ccEvent(0, 24, 24_norm); + REQUIRE(region.getCrossfadeGain() == 1.0_a); + midiState.ccEvent(0, 24, 25_norm); + REQUIRE(region.getCrossfadeGain() == 1.0_a); } TEST_CASE("[Region] Crossfade in on CC - gain") @@ -185,13 +192,20 @@ TEST_CASE("[Region] Crossfade in on CC - gain") region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); - midiState.ccEvent(0, 24, 19_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a ); - midiState.ccEvent(0, 24, 20_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a ); - midiState.ccEvent(0, 24, 21_norm); REQUIRE( region.getCrossfadeGain() == 0.25_a ); - midiState.ccEvent(0, 24, 22_norm); REQUIRE( region.getCrossfadeGain() == 0.5_a ); - midiState.ccEvent(0, 24, 23_norm); REQUIRE( region.getCrossfadeGain() == 0.75_a ); - midiState.ccEvent(0, 24, 24_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a ); - midiState.ccEvent(0, 24, 25_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a ); + midiState.ccEvent(0, 24, 19_norm); + REQUIRE(region.getCrossfadeGain() == 0.0_a); + midiState.ccEvent(0, 24, 20_norm); + REQUIRE(region.getCrossfadeGain() == 0.0_a); + midiState.ccEvent(0, 24, 21_norm); + REQUIRE(region.getCrossfadeGain() == 0.25_a); + midiState.ccEvent(0, 24, 22_norm); + REQUIRE(region.getCrossfadeGain() == 0.5_a); + midiState.ccEvent(0, 24, 23_norm); + REQUIRE(region.getCrossfadeGain() == 0.75_a); + midiState.ccEvent(0, 24, 24_norm); + REQUIRE(region.getCrossfadeGain() == 1.0_a); + midiState.ccEvent(0, 24, 25_norm); + REQUIRE(region.getCrossfadeGain() == 1.0_a); } TEST_CASE("[Region] Crossfade out on CC") { @@ -201,13 +215,20 @@ TEST_CASE("[Region] Crossfade out on CC") region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); - midiState.ccEvent(0, 24, 19_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a ); - midiState.ccEvent(0, 24, 20_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a ); - midiState.ccEvent(0, 24, 21_norm); REQUIRE( region.getCrossfadeGain() == 0.86603_a ); - midiState.ccEvent(0, 24, 22_norm); REQUIRE( region.getCrossfadeGain() == 0.70711_a ); - midiState.ccEvent(0, 24, 23_norm); REQUIRE( region.getCrossfadeGain() == 0.5_a ); - midiState.ccEvent(0, 24, 24_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a ); - midiState.ccEvent(0, 24, 25_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a ); + midiState.ccEvent(0, 24, 19_norm); + REQUIRE(region.getCrossfadeGain() == 1.0_a); + midiState.ccEvent(0, 24, 20_norm); + REQUIRE(region.getCrossfadeGain() == 1.0_a); + midiState.ccEvent(0, 24, 21_norm); + REQUIRE(region.getCrossfadeGain() == 0.86603_a); + midiState.ccEvent(0, 24, 22_norm); + REQUIRE(region.getCrossfadeGain() == 0.70711_a); + midiState.ccEvent(0, 24, 23_norm); + REQUIRE(region.getCrossfadeGain() == 0.5_a); + midiState.ccEvent(0, 24, 24_norm); + REQUIRE(region.getCrossfadeGain() == 0.0_a); + midiState.ccEvent(0, 24, 25_norm); + REQUIRE(region.getCrossfadeGain() == 0.0_a); } TEST_CASE("[Region] Crossfade out on CC - gain") @@ -219,13 +240,20 @@ TEST_CASE("[Region] Crossfade out on CC - gain") region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); - midiState.ccEvent(0, 24, 19_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a ); - midiState.ccEvent(0, 24, 20_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a ); - midiState.ccEvent(0, 24, 21_norm); REQUIRE( region.getCrossfadeGain() == 0.75_a ); - midiState.ccEvent(0, 24, 22_norm); REQUIRE( region.getCrossfadeGain() == 0.5_a ); - midiState.ccEvent(0, 24, 23_norm); REQUIRE( region.getCrossfadeGain() == 0.25_a ); - midiState.ccEvent(0, 24, 24_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a ); - midiState.ccEvent(0, 24, 25_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a ); + midiState.ccEvent(0, 24, 19_norm); + REQUIRE(region.getCrossfadeGain() == 1.0_a); + midiState.ccEvent(0, 24, 20_norm); + REQUIRE(region.getCrossfadeGain() == 1.0_a); + midiState.ccEvent(0, 24, 21_norm); + REQUIRE(region.getCrossfadeGain() == 0.75_a); + midiState.ccEvent(0, 24, 22_norm); + REQUIRE(region.getCrossfadeGain() == 0.5_a); + midiState.ccEvent(0, 24, 23_norm); + REQUIRE(region.getCrossfadeGain() == 0.25_a); + midiState.ccEvent(0, 24, 24_norm); + REQUIRE(region.getCrossfadeGain() == 0.0_a); + midiState.ccEvent(0, 24, 25_norm); + REQUIRE(region.getCrossfadeGain() == 0.0_a); } TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") @@ -234,8 +262,8 @@ TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") sfz::Region region { midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE( region.getNoteGain(64, 127_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(64, 0_norm) == 1.0_a ); + REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); + REQUIRE(region.getNoteGain(64, 0_norm) == 1.0_a); } @@ -245,8 +273,8 @@ TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack") sfz::Region region { midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "100" }); - REQUIRE( region.getNoteGain(64, 127_norm) == 1.0_a ); - REQUIRE( region.getNoteGain(64, 0_norm) == Approx(0.0).margin(0.0001) ); + REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); + REQUIRE(region.getNoteGain(64, 0_norm) == Approx(0.0).margin(0.0001)); } TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") @@ -255,8 +283,8 @@ TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") sfz::Region region { midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "-100" }); - REQUIRE( region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001) ); - REQUIRE( region.getNoteGain(64, 0_norm) == 1.0_a ); + REQUIRE(region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001)); + REQUIRE(region.getNoteGain(64, 0_norm) == 1.0_a); } TEST_CASE("[Region] rt_decay") diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index e0f754ba..c911c0c7 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -141,9 +141,9 @@ TEST_CASE("[Synth] Reset all controllers") { sfz::Synth synth; synth.cc(0, 12, 64); - REQUIRE( synth.getMidiState().getCCValue(12) == 64_norm ); + REQUIRE(synth.getMidiState().getCCValue(12) == 64_norm); synth.cc(0, 121, 64); - REQUIRE( synth.getMidiState().getCCValue(12) == 0_norm ); + REQUIRE(synth.getMidiState().getCCValue(12) == 0_norm); } TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice")