diff --git a/src/sfizz/AudioSpan.h b/src/sfizz/AudioSpan.h index e11e2ebb..8f9f0228 100644 --- a/src/sfizz/AudioSpan.h +++ b/src/sfizz/AudioSpan.h @@ -246,7 +246,7 @@ public: * @param channelIndex the channel * @return absl::Span */ - absl::Span getConstSpan(size_t channelIndex) + absl::Span getConstSpan(size_t channelIndex) const { ASSERT(channelIndex < numChannels); if (channelIndex < numChannels) @@ -382,7 +382,7 @@ public: * * @returns size_type the number of frames in the AudioSpan */ - size_type getNumFrames() + size_type getNumFrames() const { return numFrames; } @@ -392,7 +392,7 @@ public: * * @returns size_type the number of channels in the AudioSpan */ - size_t getNumChannels() + size_t getNumChannels() const { return numChannels; } diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 6796380f..4ebe7577 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -221,6 +221,9 @@ namespace Default constexpr Range egOnCCPercentRange { -100.0, 100.0 }; // ***** SFZ v2 ******** + constexpr int sampleQuality { 2 }; + constexpr Range sampleQualityRange { 1, 10 }; // sample_quality + constexpr bool checkSustain { true }; // sustain_sw constexpr bool checkSostenuto { true }; // sostenuto_sw constexpr Range octaveOffsetRange { -10, 10 }; // octave_offset diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 20d44048..35e6b0fc 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -60,6 +60,12 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) sampleId = FileId(std::move(filename), sampleId.isReverse()); } break; + case hash("sample_quality"): + { + setValueFromOpcode(opcode, sampleQuality, Default::sampleQualityRange); + break; + } + break; case hash("direction"): sampleId = sampleId.reversed(opcode.value == "reverse"); break; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 1332d1ec..5ce49ff8 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -240,6 +240,7 @@ struct Region { // Sound source: sample playback FileId sampleId {}; // Sample + int sampleQuality { Default::sampleQuality }; float delay { Default::delay }; // delay float delayRandom { Default::delayRandom }; // delay_random int64_t offset { Default::offset }; // offset diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index d198b22f..6ae90db2 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -506,23 +506,25 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept } } - auto ind = indices->data(); - auto coeff = coeffs->data(); - auto leftSource = source.getConstSpan(0); - auto left = buffer.getChannel(0); - if (source.getNumChannels() == 1) { - while (ind < indices->end()) { - *left = interpolate(&leftSource[*ind], *coeff); - incrementAll(ind, left, coeff); - } - } else { - auto right = buffer.getChannel(1); - auto rightSource = source.getConstSpan(1); - while (ind < indices->end()) { - *left = interpolate(&leftSource[*ind], *coeff); - *right = interpolate(&rightSource[*ind], *coeff); - incrementAll(ind, left, right, coeff); - } + const int quality = region->sampleQuality; + + switch (quality) { + default: + if (quality > 2) + goto high; // TODO sinc, not implemented + // fall through + case 1: + fillInterpolated(source, buffer, *indices, *coeffs); + break; + case 2: high: +#if 1 + // B-spline response has faster decay of aliasing, but not zero-crossings at integer positions + fillInterpolated(source, buffer, *indices, *coeffs); +#else + // Hermite polynomial + fillInterpolated(source, buffer, *indices, *coeffs); +#endif + break; } sourcePosition = indices->back(); @@ -536,6 +538,31 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept #endif } +template +void sfz::Voice::fillInterpolated( + const sfz::AudioSpan& source, sfz::AudioSpan& dest, + absl::Span indices, absl::Span coeffs) +{ + auto ind = indices.data(); + auto coeff = coeffs.data(); + auto leftSource = source.getConstSpan(0); + auto left = dest.getChannel(0); + if (source.getNumChannels() == 1) { + while (ind < indices.end()) { + *left = sfz::interpolate(&leftSource[*ind], *coeff); + incrementAll(ind, left, coeff); + } + } else { + auto right = dest.getChannel(1); + auto rightSource = source.getConstSpan(1); + while (ind < indices.end()) { + *left = sfz::interpolate(&leftSource[*ind], *coeff); + *right = sfz::interpolate(&rightSource[*ind], *coeff); + incrementAll(ind, left, right, coeff); + } + } +} + void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept { const auto leftSpan = buffer.getSpan(0); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 46b3a3f0..b1ff292a 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -19,6 +19,7 @@ #include namespace sfz { +enum InterpolatorModel : int; /** * @brief The SFZ voice are the polyphony holders. They get activated by the synth * and tasked to play a given region until the end, stopping on note-offs, off-groups @@ -242,6 +243,20 @@ private: * @param buffer */ void fillWithGenerator(AudioSpan buffer) noexcept; + + /** + * @brief Fill a destination with an interpolated source. + * + * @param source the source sample + * @param dest the destination buffer + * @param indices the integral parts of the source positions + * @param coeffs the fractional parts of the source positions + */ + template + static void fillInterpolated( + const sfz::AudioSpan& source, sfz::AudioSpan& dest, + absl::Span indices, absl::Span coeffs); + void amplitudeEnvelope(absl::Span modulationSpan) noexcept; void ampStageMono(AudioSpan buffer) noexcept; void ampStageStereo(AudioSpan buffer) noexcept;