From f2a79723a7039d2da8b7af865fd3b697df7b7a25 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Tue, 23 Feb 2021 12:25:25 +0100 Subject: [PATCH 1/6] Add support for count --- src/sfizz/Defaults.cpp | 3 ++- src/sfizz/Defaults.h | 1 + src/sfizz/Region.cpp | 6 +++++- src/sfizz/Region.h | 3 ++- src/sfizz/SynthMessaging.cpp | 3 ++- src/sfizz/Voice.cpp | 41 +++++++++++++++++++++++++++++++----- tests/RegionValuesT.cpp | 2 -- 7 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp index 792983ff..b30272dc 100644 --- a/src/sfizz/Defaults.cpp +++ b/src/sfizz/Defaults.cpp @@ -12,9 +12,10 @@ extern const OpcodeSpec offset { 0, Range(0, uint32_t_max), 0 extern const OpcodeSpec offsetMod { 0, Range(0, uint32_t_max), 0 }; extern const OpcodeSpec offsetRandom { 0, Range(0, uint32_t_max), 0 }; extern const OpcodeSpec sampleEnd { uint32_t_max, Range(0, uint32_t_max), kEnforceLowerBound }; -extern const OpcodeSpec sampleCount { 1, Range(1, uint32_t_max), 0 }; +extern const OpcodeSpec sampleCount { 0, Range(0, uint32_t_max), 0 }; extern const OpcodeSpec loopStart { 0, Range(0, uint32_t_max), 0 }; extern const OpcodeSpec loopEnd { uint32_t_max, Range(0, uint32_t_max), 0 }; +extern const OpcodeSpec loopCount { 0, Range(0, uint32_t_max), 0 }; extern const OpcodeSpec loopCrossfade { 1e-3, Range(1e-3, 1.0f), 0 }; extern const OpcodeSpec oscillator { OscillatorEnabled::Auto, Range(OscillatorEnabled::Auto, OscillatorEnabled::On), 0 }; extern const OpcodeSpec oscillatorPhase { 0.0f, Range(-1000.0f, 1000.0f), 0 }; diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index f778e09e..2290052b 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -134,6 +134,7 @@ namespace Default extern const OpcodeSpec sampleCount; extern const OpcodeSpec loopStart; extern const OpcodeSpec loopEnd; + extern const OpcodeSpec loopCount; extern const OpcodeSpec loopCrossfade; extern const OpcodeSpec oscillatorPhase; extern const OpcodeSpec oscillator; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 4fed8896..88948362 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -122,7 +122,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) sampleEnd = opcode.read(Default::sampleEnd); break; case hash("count"): - sampleCount = opcode.read(Default::sampleCount); + sampleCount = opcode.readOptional(Default::sampleCount); + loopMode = LoopMode::one_shot; break; case hash("loop_mode"): // also loopmode loopMode = opcode.readOptional(Default::loopMode); @@ -130,6 +131,9 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) case hash("loop_end"): // also loopend loopRange.setEnd(opcode.read(Default::loopEnd)); break; + case hash("loop_count"): + loopCount = opcode.readOptional(Default::loopCount); + break; case hash("loop_start"): // also loopstart loopRange.setStart(opcode.read(Default::loopStart)); break; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 98dda576..bb72cfd4 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -326,9 +326,10 @@ struct Region { int64_t offsetRandom { Default::offsetRandom }; // offset_random CCMap offsetCC { Default::offsetMod }; uint32_t sampleEnd { Default::sampleEnd }; // end - uint32_t sampleCount { Default::sampleCount }; // count + absl::optional sampleCount {}; // count absl::optional loopMode {}; // loopmode Range loopRange { Default::loopStart, Default::loopEnd }; //loopstart and loopend + absl::optional loopCount {}; // count float loopCrossfade { Default::loopCrossfade }; // loop_crossfade // Wavetable oscillator diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 67407e3c..0c73a454 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -186,7 +186,8 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/region&/count", "") { GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.sampleCount); + if (region.sampleCount) + client.receive<'h'>(delay, path, *region.sampleCount); } break; MATCH("/region&/loop_range", "") { diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 20e84be0..4d76c6f7 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -192,6 +192,9 @@ struct Voice::Impl int sourcePosition_ { 0 }; int initialDelay_ { 0 }; int age_ { 0 }; + uint32_t count_ { 1 }; + int sampleSize_ { 0 }; + struct { int start { 0 }; int end { 0 }; @@ -199,6 +202,7 @@ struct Voice::Impl int xfSize { 0 }; int xfOutStart { 0 }; int xfInStart { 0 }; + uint32_t restarts { 0 }; } loop_; FileDataHolder currentPromise_; @@ -416,6 +420,7 @@ void Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noe impl.triggerDelay_ = delay; impl.initialDelay_ = delay + static_cast(region->getDelay() * impl.sampleRate_); impl.baseFrequency_ = impl.resources_.tuning.getFrequencyOfKey(impl.triggerEvent_.number); + impl.sampleSize_ = region->trueSampleEnd(impl.resources_.filePool.getOversamplingFactor()) - impl.sourcePosition_ - 1; impl.bendStepFactor_ = centsFactor(region->bendStep); impl.bendSmoother_.setSmoothing(region->bendSmooth, impl.sampleRate_); impl.bendSmoother_.reset(centsFactor(region->getBendInCents(impl.resources_.midiState.getPitchBend()))); @@ -924,12 +929,22 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept if (isLooping) { int oldIndex {}; int oldPartitionType {}; + int blockRestarts { 0 }; for (unsigned i = 0; i < numSamples; ++i) { - int index = (*indices)[i]; - - // wrap indices post loop-entry around the loop segment - int wrappedIndex = (index <= loop.end) ? index : - (loop.start + (index - loop.start) % loop.size); + int index = (*indices)[i] - loop.size * blockRestarts; + int wrappedIndex = index; + // wrap indices post loop-entry around the loop segment and increment the counter if necessary + if (index > loop.end) { + if (region_->loopCount && loop_.restarts >= *region_->loopCount) { + wrappedIndex = loop.end; + egAmplitude_.setReleaseTime(0.0f); + egAmplitude_.startRelease(i); + } else { + wrappedIndex -= loop.size; + blockRestarts += 1; + loop_.restarts += 1; + } + } (*indices)[i] = wrappedIndex; // identify the partition this index is in @@ -956,7 +971,12 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept int(source.getNumFrames())) - 1; + int blockRestarts { 0 }; + for (unsigned i = 0; i < numSamples; ++i) { + ASSERT((*indices)[i] - sampleSize_ * blockRestarts >= 0); + (*indices)[i] -= sampleSize_ * blockRestarts; + if ((*indices)[i] >= sampleEnd) { #ifndef NDEBUG // Check for underflow @@ -967,6 +987,14 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept << " for sample " << *region_->sampleId); } #endif + if (region_->sampleCount && count_ < *region_->sampleCount) { + (*indices)[i] -= sampleSize_; + ASSERT((*indices)[i] >= 0); + blockRestarts += 1; + count_ += 1; + continue; + } + if (!region_->flexAmpEG) { egAmplitude_.setReleaseTime(0.0f); egAmplitude_.startRelease(i); @@ -975,6 +1003,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept // TODO(jpc): Flex AmpEG flexEGs_[*region_->flexAmpEG]->release(i); } + fill(indices->subspan(i), sampleEnd); fill(coeffs->subspan(i), 1.0f); break; @@ -1416,6 +1445,7 @@ void Voice::reset() noexcept impl.currentPromise_.reset(); impl.sourcePosition_ = 0; impl.age_ = 0; + impl.count_ = 1; impl.floatPositionOffset_ = 0.0f; impl.noteIsOff_ = false; @@ -1440,6 +1470,7 @@ void Voice::Impl::resetLoopInformation() noexcept loop_.xfSize = 0; loop_.xfOutStart = 0; loop_.xfInStart = 0; + loop_.restarts = 0; } void Voice::Impl::updateLoopInformation() noexcept diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index 24467ea3..e321d558 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -237,9 +237,7 @@ TEST_CASE("[Values] Count") synth.dispatchMessage(client, 0, "/region1/count", "", nullptr); synth.dispatchMessage(client, 0, "/region2/count", "", nullptr); std::vector expected { - "/region0/count,h : { 1 }", "/region1/count,h : { 2 }", - "/region2/count,h : { 1 }", }; REQUIRE(messageList == expected); } From 4a3c1f36e5d50af839d859d84f5fc55ca5264b40 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 5 Mar 2021 00:21:35 +0100 Subject: [PATCH 2/6] Handle the different types of looping --- src/sfizz/Voice.cpp | 226 +++++++++++++++++++++++++++----------------- src/sfizz/Voice.h | 1 - 2 files changed, 139 insertions(+), 88 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 4d76c6f7..7b009771 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -171,6 +171,30 @@ struct Voice::Impl */ void updateLoopInformation() noexcept; + /** + * @brief Check whether the voice is released + * + * @return true + * @return false + */ + bool released() const noexcept; + + /** + * @brief Release the voice after a given delay + * + * @param delay + */ + void release(int delay) noexcept; + + /** + * @brief Off the voice (steal). This will respect the off mode of the region + * and set the envelopes if necessary. + * + * @param delay + * @param fast whether to apply a fast release regardless of the off mode + */ + void off(int delay, bool fast = false) noexcept; + const NumericId id_; StateListener* stateListener_ = nullptr; @@ -450,29 +474,39 @@ bool Voice::isFree() const noexcept void Voice::release(int delay) noexcept { Impl& impl = *impl_; - if (impl.state_ != State::playing) + impl.release(delay); +} + +void Voice::Impl::release(int delay) noexcept +{ + if (state_ != State::playing) return; - if (!impl.region_->flexAmpEG) { - if (impl.egAmplitude_.getRemainingDelay() > delay) - impl.switchState(State::cleanMeUp); + if (!region_->flexAmpEG) { + if (egAmplitude_.getRemainingDelay() > delay) + switchState(State::cleanMeUp); } else { - if (impl.flexEGs_[*impl.region_->flexAmpEG]->getRemainingDelay() > static_cast(delay)) - impl.switchState(State::cleanMeUp); + if (flexEGs_[*region_->flexAmpEG]->getRemainingDelay() > static_cast(delay)) + switchState(State::cleanMeUp); } - impl.resources_.modMatrix.releaseVoice(impl.id_, impl.region_->getId(), delay); + resources_.modMatrix.releaseVoice(id_, region_->getId(), delay); } void Voice::off(int delay, bool fast) noexcept { Impl& impl = *impl_; - if (!impl.region_->flexAmpEG) { - if (impl.region_->offMode == OffMode::fast || fast) { - impl.egAmplitude_.setReleaseTime(Default::offTime); - } else if (impl.region_->offMode == OffMode::time) { - impl.egAmplitude_.setReleaseTime(impl.region_->offTime); + impl.off(delay, fast); +} + +void Voice::Impl::off(int delay, bool fast) noexcept +{ + if (!region_->flexAmpEG) { + if (region_->offMode == OffMode::fast || fast) { + egAmplitude_.setReleaseTime(Default::offTime); + } else if (region_->offMode == OffMode::time) { + egAmplitude_.setReleaseTime(region_->offTime); } } else { @@ -884,8 +918,9 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept // calculate loop characteristics const auto loop = this->loop_; - const bool isLooping = region_->shouldLoop() - && (static_cast(loop.end) < source.getNumFrames()); + const bool hasLoopSamples = static_cast(loop.end) < source.getNumFrames(); + const bool loopContinuous = hasLoopSamples && (region_->loopMode == LoopMode::loop_continuous); + const bool loopSustain = hasLoopSamples && (region_->loopMode == LoopMode::loop_sustain) && !released(); /* loop start loop end @@ -907,13 +942,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept enum PartitionType { kPartitionNormal, kPartitionLoopXfade }; SpanHolder> partitionBuffers[2]; - if (!isLooping) { - static const int starts[1] = { 0 }; - static const int types[1] = { kPartitionNormal }; - partitionStarts = absl::MakeSpan(const_cast(starts), 1); - partitionTypes = absl::MakeSpan(const_cast(types), 1); - numPartitions = 1; - } else { + if (loopSustain || loopContinuous) { for (auto& buf : partitionBuffers) { buf = resources_.bufferPool.getIndexBuffer(numSamples); if (!buf) @@ -923,87 +952,104 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept partitionTypes = *partitionBuffers[1]; // Note: partitions will be alternance of Normal/Xfade // computed along with index processing below + } else { + static const int starts[1] = { 0 }; + static const int types[1] = { kPartitionNormal }; + partitionStarts = absl::MakeSpan(const_cast(starts), 1); + partitionTypes = absl::MakeSpan(const_cast(types), 1); + numPartitions = 1; } - // index preprocessing for loops - if (isLooping) { - int oldIndex {}; - int oldPartitionType {}; - int blockRestarts { 0 }; - for (unsigned i = 0; i < numSamples; ++i) { - int index = (*indices)[i] - loop.size * blockRestarts; - int wrappedIndex = index; - // wrap indices post loop-entry around the loop segment and increment the counter if necessary - if (index > loop.end) { - if (region_->loopCount && loop_.restarts >= *region_->loopCount) { - wrappedIndex = loop.end; - egAmplitude_.setReleaseTime(0.0f); - egAmplitude_.startRelease(i); - } else { - wrappedIndex -= loop.size; - blockRestarts += 1; - loop_.restarts += 1; - } - } - (*indices)[i] = wrappedIndex; - - // identify the partition this index is in - bool xfading = wrappedIndex >= loop.start && wrappedIndex >= loop.xfOutStart; - int partitionType = xfading ? kPartitionLoopXfade : kPartitionNormal; - // if looping or entering a different type, start a new partition - bool start = i == 0 || wrappedIndex < oldIndex || partitionType != oldPartitionType; - if (start) { - partitionStarts[numPartitions] = i; - partitionTypes[numPartitions] = partitionType; - ++numPartitions; - } - - oldIndex = wrappedIndex; - oldPartitionType = partitionType; - } - } - // index preprocessing for one-shots - else { - // cut short the voice at the instant of reaching end of sample - const auto sampleEnd = min( + const auto sampleEnd = min( int(region_->trueSampleEnd(resources_.filePool.getOversamplingFactor())), int(currentPromise_->information.end), int(source.getNumFrames())) - 1; - int blockRestarts { 0 }; + int blockRestarts { 0 }; + int oldIndex {}; + int oldPartitionType {}; + + const auto addPartitionIfNecessary = [&] (unsigned blockIndex, int wrappedIndex, bool wrapped) { + const bool xfading = wrappedIndex >= loop.start && wrappedIndex >= loop.xfOutStart; + const int partitionType = xfading ? kPartitionLoopXfade : kPartitionNormal; + + // if looping or entering a different type, start a new partition + bool start = blockIndex == 0 || wrapped || partitionType != oldPartitionType; + if (start) { + partitionStarts[numPartitions] = blockIndex; + partitionTypes[numPartitions] = partitionType; + ++numPartitions; + } + + oldIndex = wrappedIndex; + oldPartitionType = partitionType; + }; + + if (loopContinuous) { for (unsigned i = 0; i < numSamples; ++i) { - ASSERT((*indices)[i] - sampleSize_ * blockRestarts >= 0); + int wrappedIndex = (*indices)[i] - loop.size * blockRestarts; + if (wrappedIndex > loop.end) { + wrappedIndex -= loop.size; + blockRestarts += 1; + loop_.restarts += 1; + } + (*indices)[i] = wrappedIndex; + const bool wrapped = wrappedIndex < oldIndex; + addPartitionIfNecessary(i, wrappedIndex, wrapped); + + // Release if we reached the loop count + if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount && !released()) { + release(i); + } + } + } else if (loopSustain) { + unsigned i = 0; + while (i < numSamples) { + int wrappedIndex = (*indices)[i] - loop.size * blockRestarts; + if (wrappedIndex > loop.end) { + wrappedIndex -= loop.size; + blockRestarts += 1; + loop_.restarts += 1; + } + (*indices)[i] = wrappedIndex; + const bool wrapped = wrappedIndex < oldIndex; + + // identify the partition this index is in + addPartitionIfNecessary(i, wrappedIndex, wrapped); + + i++; + + // Release if we reached the loop count and break + if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount) { + release(i - 1); + break; + } + } + + while (i < numSamples) { // In case we released within the block, continue as if it were a one-shot + (*indices)[i] -= loop.size * blockRestarts; + if ((*indices)[i] >= sampleEnd) { + fill(indices->subspan(i), sampleEnd); + fill(coeffs->subspan(i), 1.0f); + break; + } + i++; + } + } else { // One shots and loop_sustain that have ended + for (unsigned i = 0; i < numSamples; ++i) { (*indices)[i] -= sampleSize_ * blockRestarts; if ((*indices)[i] >= sampleEnd) { -#ifndef NDEBUG - // Check for underflow - if (source.getNumFrames() - 1 < currentPromise_->information.end) { - DBG("[sfizz] Underflow: source available samples " - << source.getNumFrames() << "/" - << currentPromise_->information.end - << " for sample " << *region_->sampleId); - } -#endif if (region_->sampleCount && count_ < *region_->sampleCount) { (*indices)[i] -= sampleSize_; - ASSERT((*indices)[i] >= 0); blockRestarts += 1; count_ += 1; continue; } - if (!region_->flexAmpEG) { - egAmplitude_.setReleaseTime(0.0f); - egAmplitude_.startRelease(i); - } - else { - // TODO(jpc): Flex AmpEG - flexEGs_[*region_->flexAmpEG]->release(i); - } - + release(i); fill(indices->subspan(i), sampleEnd); fill(coeffs->subspan(i), 1.0f); break; @@ -1421,6 +1467,14 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept #endif } +bool Voice::Impl::released() const noexcept +{ + if (!region_->flexAmpEG) + return egAmplitude_.isReleased(); + else + return flexEGs_[*region_->flexAmpEG]->isReleased(); +} + bool Voice::checkOffGroup(const Region* other, int delay, int noteNumber) noexcept { Impl& impl = *impl_; @@ -1528,10 +1582,8 @@ bool Voice::releasedOrFree() const noexcept Impl& impl = *impl_; if (impl.state_ != State::playing) return true; - if (!impl.region_->flexAmpEG) - return impl.egAmplitude_.isReleased(); - else - return impl.flexEGs_[*impl.region_->flexAmpEG]->isReleased(); + + return impl.released(); } void Voice::setMaxFiltersPerVoice(size_t numFilters) diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index b6a07d38..34885cbb 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -309,7 +309,6 @@ public: * @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) noexcept; From a98e909eebffc4127f8c099fb4cc4a1361a0554d Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 5 Mar 2021 13:02:03 +0100 Subject: [PATCH 3/6] Tweaks --- src/sfizz/Region.cpp | 2 +- src/sfizz/Voice.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 88948362..6283cbf4 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1661,7 +1661,7 @@ uint32_t sfz::Region::trueSampleEnd(Oversampling factor) const noexcept if (sampleEnd <= 0) return 0; - return min(static_cast(sampleEnd), loopRange.getEnd()) * static_cast(factor); + return static_cast(sampleEnd) * static_cast(factor); } uint32_t sfz::Region::loopStart(Oversampling factor) const noexcept diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 7b009771..d3b04c2f 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -987,7 +987,6 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept }; if (loopContinuous) { - for (unsigned i = 0; i < numSamples; ++i) { int wrappedIndex = (*indices)[i] - loop.size * blockRestarts; if (wrappedIndex > loop.end) { @@ -1000,9 +999,8 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept addPartitionIfNecessary(i, wrappedIndex, wrapped); // Release if we reached the loop count - if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount && !released()) { + if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount && !released()) release(i); - } } } else if (loopSustain) { unsigned i = 0; @@ -1037,19 +1035,21 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept } i++; } - } else { // One shots and loop_sustain that have ended + } else { // One shots and loop_sustain that have released or ended for (unsigned i = 0; i < numSamples; ++i) { (*indices)[i] -= sampleSize_ * blockRestarts; if ((*indices)[i] >= sampleEnd) { - if (region_->sampleCount && count_ < *region_->sampleCount) { + if (region_->sampleCount && count_ < *region_->sampleCount && !region_->shouldLoop()) { (*indices)[i] -= sampleSize_; blockRestarts += 1; count_ += 1; continue; } - release(i); + if (!released()) + release(i); + fill(indices->subspan(i), sampleEnd); fill(coeffs->subspan(i), 1.0f); break; From 5d8eda74285f4ce999469a76c61e17b9bd63d695 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 5 Mar 2021 13:03:52 +0100 Subject: [PATCH 4/6] Better messaging --- src/sfizz/SynthMessaging.cpp | 10 ++++++++++ tests/RegionValuesT.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 0c73a454..3b4b18bf 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -188,6 +188,8 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co GET_REGION_OR_BREAK(indices[0]) if (region.sampleCount) client.receive<'h'>(delay, path, *region.sampleCount); + else + client.receive<'N'>(delay, path, {}); } break; MATCH("/region&/loop_range", "") { @@ -226,6 +228,14 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'f'>(delay, path, region.loopCrossfade); } break; + MATCH("/region&/loop_count", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.loopCount) + client.receive<'h'>(delay, path, *region.loopCount); + else + client.receive<'N'>(delay, path, {}); + } break; + MATCH("/region&/group", "") { GET_REGION_OR_BREAK(indices[0]) client.receive<'h'>(delay, path, region.group); diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index e321d558..91df9ba0 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -237,7 +237,9 @@ TEST_CASE("[Values] Count") synth.dispatchMessage(client, 0, "/region1/count", "", nullptr); synth.dispatchMessage(client, 0, "/region2/count", "", nullptr); std::vector expected { + "/region0/count,N : { }", "/region1/count,h : { 2 }", + "/region2/count,N : { }", }; REQUIRE(messageList == expected); } @@ -348,6 +350,29 @@ TEST_CASE("[Values] Loop crossfade") REQUIRE(messageList == expected); } +TEST_CASE("[Values] Loop count") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav loop_count=2 + sample=kick.wav loop_count=-1 + )"); + synth.dispatchMessage(client, 0, "/region0/loop_count", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/loop_count", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/loop_count", "", nullptr); + std::vector expected { + "/region0/loop_count,N : { }", + "/region1/loop_count,h : { 2 }", + "/region2/loop_count,N : { }", + }; + REQUIRE(messageList == expected); +} + + TEST_CASE("[Values] Group") { Synth synth; From f1d4dfb823222e3500fd429d7082642007eb0793 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 7 Mar 2021 18:37:38 +0100 Subject: [PATCH 5/6] Fine tune the looping behavior --- src/sfizz/Voice.cpp | 39 ++++++++++++--------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index d3b04c2f..b3d86310 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -918,9 +918,13 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept // calculate loop characteristics const auto loop = this->loop_; + + // Looping logic const bool hasLoopSamples = static_cast(loop.end) < source.getNumFrames(); - const bool loopContinuous = hasLoopSamples && (region_->loopMode == LoopMode::loop_continuous); - const bool loopSustain = hasLoopSamples && (region_->loopMode == LoopMode::loop_sustain) && !released(); + const bool loopCountReached = region_->loopCount && loop_.restarts >= *region_->loopCount; + const bool loopContinuous = (region_->loopMode == LoopMode::loop_continuous); + const bool loopSustain = (region_->loopMode == LoopMode::loop_sustain) && !released(); + const bool shouldLoop = hasLoopSamples && (loopSustain || loopContinuous) && !loopCountReached; /* loop start loop end @@ -942,7 +946,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept enum PartitionType { kPartitionNormal, kPartitionLoopXfade }; SpanHolder> partitionBuffers[2]; - if (loopSustain || loopContinuous) { + if (shouldLoop) { for (auto& buf : partitionBuffers) { buf = resources_.bufferPool.getIndexBuffer(numSamples); if (!buf) @@ -986,23 +990,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept oldPartitionType = partitionType; }; - if (loopContinuous) { - for (unsigned i = 0; i < numSamples; ++i) { - int wrappedIndex = (*indices)[i] - loop.size * blockRestarts; - if (wrappedIndex > loop.end) { - wrappedIndex -= loop.size; - blockRestarts += 1; - loop_.restarts += 1; - } - (*indices)[i] = wrappedIndex; - const bool wrapped = wrappedIndex < oldIndex; - addPartitionIfNecessary(i, wrappedIndex, wrapped); - - // Release if we reached the loop count - if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount && !released()) - release(i); - } - } else if (loopSustain) { + if (shouldLoop) { unsigned i = 0; while (i < numSamples) { int wrappedIndex = (*indices)[i] - loop.size * blockRestarts; @@ -1016,14 +1004,11 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept // identify the partition this index is in addPartitionIfNecessary(i, wrappedIndex, wrapped); - i++; - // Release if we reached the loop count and break - if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount) { - release(i - 1); + // Break if we reached the loop count + if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount) break; - } } while (i < numSamples) { // In case we released within the block, continue as if it were a one-shot @@ -1035,7 +1020,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept } i++; } - } else { // One shots and loop_sustain that have released or ended + } else { // One shots and loops that have released (for loop sustain) or ended (with loop counts) for (unsigned i = 0; i < numSamples; ++i) { (*indices)[i] -= sampleSize_ * blockRestarts; @@ -1048,7 +1033,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept } if (!released()) - release(i); + off(i, true); fill(indices->subspan(i), sampleEnd); fill(coeffs->subspan(i), 1.0f); From 89b09d298503b8d6fae06cc7ae5ca8c42177ca37 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Mon, 8 Mar 2021 16:24:12 +0100 Subject: [PATCH 6/6] Update src/sfizz/Voice.cpp --- src/sfizz/Voice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index b3d86310..e5fc2ddf 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -1015,7 +1015,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept (*indices)[i] -= loop.size * blockRestarts; if ((*indices)[i] >= sampleEnd) { fill(indices->subspan(i), sampleEnd); - fill(coeffs->subspan(i), 1.0f); + fill(coeffs->subspan(i), 0x1.fffffep-1); break; } i++;