From e6ffec58857345a80023cbd5955e3798065b89ee Mon Sep 17 00:00:00 2001 From: paulfd Date: Sun, 15 Sep 2019 23:03:40 +0200 Subject: [PATCH] Better handling of the source position. The integer part and the floating point part are separated, the solution before lost precision in the interpolation for longer samples. --- sfizz/Voice.cpp | 84 ++++++++++++++++++++++++++++++------------------- sfizz/Voice.h | 7 +++-- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/sfizz/Voice.cpp b/sfizz/Voice.cpp index 51008612..a59c8e5b 100644 --- a/sfizz/Voice.cpp +++ b/sfizz/Voice.cpp @@ -94,8 +94,8 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, widthEnvelope.reset(width); // DBG("Base width: " << baseWidth << " - with modifier: " << width); - floatPosition = static_cast(region->getOffset()); - // DBG("Offset: " << floatPosition); + sourcePosition = region->getOffset(); + // DBG("Offset: " << sourcePosition); initialDelay = delay + static_cast(region->getDelay() / sampleRate); baseFrequency = midiNoteFrequency(number) * pitchRatio; prepareEGEnvelope(delay, value); @@ -177,17 +177,17 @@ void sfz::Voice::registerCC(int delay, int channel [[maybe_unused]], int ccNumbe } if (region->panCC && ccNumber == region->panCC->first) { - const float newPan { basePan + normalizeCC(ccValue) * normalizeNegativePercents(region->panCC->second)}; + const float newPan { basePan + normalizeCC(ccValue) * normalizeNegativePercents(region->panCC->second) }; panEnvelope.registerEvent(delay, newPan); } if (region->positionCC && ccNumber == region->positionCC->first) { - const float newPosition { basePosition + normalizeCC(ccValue) * normalizeNegativePercents(region->positionCC->second)}; + const float newPosition { basePosition + normalizeCC(ccValue) * normalizeNegativePercents(region->positionCC->second) }; positionEnvelope.registerEvent(delay, newPosition); } if (region->widthCC && ccNumber == region->widthCC->first) { - const float newWidth { baseWidth + normalizeCC(ccValue) * normalizeNegativePercents(region->widthCC->second)}; + const float newWidth { baseWidth + normalizeCC(ccValue) * normalizeNegativePercents(region->widthCC->second) }; widthEnvelope.registerEvent(delay, newWidth); } } @@ -244,10 +244,10 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept processStereo(buffer); else processMono(buffer); - + if (!egEnvelope.isSmoothing()) reset(); - + powerHistory.push(buffer.meanSquared()); } @@ -256,7 +256,7 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept const auto numSamples = buffer.getNumFrames(); auto leftBuffer = buffer.getSpan(0); auto rightBuffer = buffer.getSpan(1); - + auto span1 = tempSpan1.first(numSamples); auto span2 = tempSpan2.first(numSamples); @@ -299,7 +299,7 @@ void sfz::Voice::processStereo(AudioSpan buffer) noexcept // Amplitude envelope amplitudeEnvelope.getBlock(span1); buffer.applyGain(span1); - + // AmpEG envelope egEnvelope.getBlock(span1); buffer.applyGain(span1); @@ -356,33 +356,41 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept auto rightCoeffs = tempSpan2.first(buffer.getNumFrames()); ::fill(jumps, pitchRatio * speedRatio); + jumps[0] += floatPositionOffset; + ::cumsum(jumps, jumps); + ::sfzInterpolationCast(jumps, indices, leftCoeffs, rightCoeffs); + ::add(sourcePosition, indices); - if (region->shouldLoop() && region->trueSampleEnd() <= source.getNumFrames()) { - floatPosition = ::loopingSFZIndex( - jumps, - leftCoeffs, - rightCoeffs, - indices, - floatPosition, - region->trueSampleEnd() - 1, - region->loopRange.getStart()); + //FIXME : all this casting is driving me crazy + const auto sampleEnd = static_cast(region->trueSampleEnd()) - 1; + if (region->shouldLoop() && static_cast(sampleEnd) <= source.getNumFrames()) { + const auto offset = sampleEnd - static_cast(region->loopRange.getStart()); + for (auto* index = indices.begin(); index < indices.end(); ++index) { + if (*index > sampleEnd) { + const auto remainingElements = static_cast(std::distance(index, indices.end())); + ::subtract(offset, { index, remainingElements }); + } + } } else { - floatPosition = ::saturatingSFZIndex( - jumps, - leftCoeffs, - rightCoeffs, - indices, - floatPosition, - source.getNumFrames() - 1); + for (auto* index = indices.begin(); index < indices.end(); ++index) { + if (*index > sampleEnd) { + const auto remainingElements = static_cast(std::distance(index, indices.end())); + ::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 left = buffer.getChannel(0); + auto leftSource = source.getChannel(0); if (source.getNumChannels() == 1) { while (ind < indices.end()) { - *left = source.getChannel(0)[*ind] * (*leftCoeff) + source.getChannel(0)[*ind + 1] * (*rightCoeff); + *left = leftSource[*ind] * (*leftCoeff) + leftSource[*ind + 1] * (*rightCoeff); left++; ind++; leftCoeff++; @@ -390,9 +398,10 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept } } else { auto right = buffer.getChannel(1); + auto rightSource = source.getChannel(1); while (ind < indices.end()) { - *left = source.getChannel(0)[*ind] * (*leftCoeff) + source.getChannel(0)[*ind + 1] * (*rightCoeff); - *right = source.getChannel(1)[*ind] * (*leftCoeff) + source.getChannel(1)[*ind + 1] * (*rightCoeff); + *left = leftSource[*ind] * (*leftCoeff) + leftSource[*ind + 1] * (*rightCoeff); + *right = rightSource[*ind] * (*leftCoeff) + rightSource[*ind + 1] * (*rightCoeff); left++; right++; ind++; @@ -401,10 +410,14 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept } } - if (!region->shouldLoop() && (floatPosition + 1.01) > source.getNumFrames()) { + sourcePosition = indices.back(); + floatPositionOffset = rightCoeffs.back(); + + if (state != State::release && !region->shouldLoop() && sourcePosition == sampleEnd) { DBG("Releasing " << region->sample); - auto last = std::distance(indices.begin(), absl::c_find(indices, region->trueSampleEnd() - 1)); + auto last = std::distance(indices.begin(), absl::c_find(indices, sampleEnd)); release(last); + buffer.subspan(last).fill(0.0f); } } @@ -418,6 +431,10 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept ::sin(tempSpan1.first(buffer.getNumFrames()), buffer.getSpan(0)); ::copy(buffer.getSpan(0), buffer.getSpan(1)); + + // Wrap the phase so we don't loose too much precision on longer notes + const auto numTwoPiWraps = static_cast(phase / twoPi); + phase -= twoPi * static_cast(numTwoPiWraps); } bool sfz::Voice::checkOffGroup(int delay, uint32_t group) noexcept @@ -458,7 +475,8 @@ void sfz::Voice::reset() noexcept if (region != nullptr) { DBG("Reset voice with sample " << region->sample); } - floatPosition = 0.0f; + sourcePosition = 0; + floatPositionOffset = 0.0f; region = nullptr; noteIsOff = false; } @@ -484,7 +502,7 @@ bool sfz::Voice::canBeStolen() const noexcept return state == State::release; } -float sfz::Voice::getSourcePosition() const noexcept +uint32_t sfz::Voice::getSourcePosition() const noexcept { - return floatPosition; + return sourcePosition; } diff --git a/sfizz/Voice.h b/sfizz/Voice.h index e5eca6ea..86fa6b80 100644 --- a/sfizz/Voice.h +++ b/sfizz/Voice.h @@ -70,7 +70,7 @@ public: void garbageCollect() noexcept; float getMeanSquaredAverage() const noexcept; - float getSourcePosition() const noexcept; + uint32_t getSourcePosition() const noexcept; private: void fillWithData(AudioSpan buffer) noexcept; void fillWithGenerator(AudioSpan buffer) noexcept; @@ -103,8 +103,9 @@ private: float baseFrequency { 440.0 }; float phase { 0.0f }; - float floatPosition { 0.0f }; - uint32_t initialDelay { 0 }; + float floatPositionOffset { 0.0f }; + int sourcePosition { 0 }; + int initialDelay { 0 }; std::atomic dataReady { false }; std::unique_ptr> fileData { nullptr };