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); }