From 336a4357732a8059558b1b73ddeb542e93fea333 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 13:51:37 +0200 Subject: [PATCH 1/3] Add the sample quality API --- src/sfizz.h | 34 +++++++++++++++++++++++++++++++++ src/sfizz.hpp | 33 ++++++++++++++++++++++++++++++++ src/sfizz/Defaults.h | 1 + src/sfizz/Region.cpp | 5 ++++- src/sfizz/Region.h | 2 +- src/sfizz/Resources.h | 2 ++ src/sfizz/Synth.cpp | 38 ++++++++++++++++++++++++++++++++----- src/sfizz/Synth.h | 25 +++++++++++++++++++++++- src/sfizz/SynthConfig.h | 24 +++++++++++++++++++++++ src/sfizz/Voice.cpp | 3 ++- src/sfizz/sfizz.cpp | 10 ++++++++++ src/sfizz/sfizz_wrapper.cpp | 12 ++++++++++++ 12 files changed, 180 insertions(+), 9 deletions(-) create mode 100644 src/sfizz/SynthConfig.h diff --git a/src/sfizz.h b/src/sfizz.h index 69bff64b6..705ed4687 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -40,6 +40,13 @@ typedef enum { SFIZZ_OVERSAMPLING_X4 = 4, SFIZZ_OVERSAMPLING_X8 = 8 } sfizz_oversampling_factor_t; +/** + * @brief Processing mode + */ +typedef enum { + SFIZZ_PROCESS_LIVE, + SFIZZ_PROCESS_FREEWHEELING, +} sfizz_process_mode_t; /** * @brief Creates a sfizz synth. This object has to be freed by the caller @@ -366,6 +373,33 @@ SFIZZ_EXPORTED_API sfizz_oversampling_factor_t sfizz_get_oversampling_factor(sfi */ SFIZZ_EXPORTED_API bool sfizz_set_oversampling_factor(sfizz_synth_t* synth, sfizz_oversampling_factor_t oversampling); +/** + * @brief Get the default resampling quality. This is the quality setting + * which the engine uses when the instrument does not use the + * opcode `sample_quality`. The engine uses distinct default quality + * settings for live mode and freewheeling mode, which both can be + * accessed by the means of this function. + * + * @param synth The synth. + * @param[in] mode The processing mode. + * + * @return The sample quality for the given mode, in the range 1 to 10. + */ +SFIZZ_EXPORTED_API int sfizz_get_sample_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode); + +/** + * @brief Set the default resampling quality. This is the quality setting + * which the engine uses when the instrument does not use the + * opcode `sample_quality`. The engine uses distinct default quality + * settings for live mode and freewheeling mode, which both can be + * accessed by the means of this function. + * + * @param synth The synth. + * @param[in] mode The processing mode. + * @param[in] quality The desired sample quality, in the range 1 to 10. + */ +SFIZZ_EXPORTED_API void sfizz_set_sample_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode, int quality); + /** * @brief Set the global instrument volume. * diff --git a/src/sfizz.hpp b/src/sfizz.hpp index 8dbffc365..6547715f1 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -43,6 +43,14 @@ class SFIZZ_EXPORTED_API Sfizz Sfizz(); ~Sfizz(); + /** + * @brief Processing mode + */ + enum ProcessMode { + ProcessLive, + ProcessFreewheeling, + }; + /** * @brief Empties the current regions and load a new SFZ file into the synth. * @@ -174,6 +182,31 @@ class SFIZZ_EXPORTED_API Sfizz */ void setSampleRate(float sampleRate) noexcept; + /** + * @brief Get the default resampling quality. This is the quality setting + * which the engine uses when the instrument does not use the + * opcode `sample_quality`. The engine uses distinct default quality + * settings for live mode and freewheeling mode, which both can be + * accessed by the means of this function. + * + * @param[in] mode The processing mode. + * + * @return The sample quality for the given mode, in the range 1 to 10. + */ + int getSampleQuality(ProcessMode mode); + + /** + * @brief Set the default resampling quality. This is the quality setting + * which the engine uses when the instrument does not use the + * opcode `sample_quality`. The engine uses distinct default quality + * settings for live mode and freewheeling mode, which both can be + * accessed by the means of this function. + * + * @param[in] mode The processing mode. + * @param[in] quality The desired sample quality, in the range 1 to 10. + */ + void setSampleQuality(ProcessMode mode, int quality); + /** * @brief Return the current value for the volume, in dB. */ diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 4ebe7577e..0b92670c4 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -222,6 +222,7 @@ namespace Default // ***** SFZ v2 ******** constexpr int sampleQuality { 2 }; + constexpr int sampleQualityInFreewheelingMode { 10 }; // for future use, possibly excessive constexpr Range sampleQualityRange { 1, 10 }; // sample_quality constexpr bool checkSustain { true }; // sustain_sw diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index dd032c8f1..e772b734c 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -62,7 +62,10 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) break; case hash("sample_quality"): { - setValueFromOpcode(opcode, sampleQuality, Default::sampleQualityRange); + if (opcode.value == "-1") + sampleQuality.reset(); + else + setValueFromOpcode(opcode, sampleQuality, Default::sampleQualityRange); break; } break; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index ad81f97e3..e363116b0 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -251,7 +251,7 @@ struct Region { // Sound source: sample playback FileId sampleId {}; // Sample - int sampleQuality { Default::sampleQuality }; + absl::optional sampleQuality {}; float delay { Default::delay }; // delay float delayRandom { Default::delayRandom }; // delay_random int64_t offset { Default::offset }; // offset diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index 933dde27e..0c28427e0 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once +#include "SynthConfig.h" #include "FilePool.h" #include "BufferPool.h" #include "FilterPool.h" @@ -21,6 +22,7 @@ class WavetableMulti; struct Resources { + SynthConfig synthConfig; BufferPool bufferPool; MidiState midiState; Logger logger; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index e7307cf14..457ab208c 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -667,7 +667,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept buffer.fill(0.0f); } - if (freeWheeling) + if (resources.synthConfig.freeWheeling) resources.filePool.waitForBackgroundLoading(); const std::unique_lock lock { callbackGuard, std::try_to_lock }; @@ -1112,6 +1112,34 @@ size_t sfz::Synth::getNumPreloadedSamples() const noexcept return resources.filePool.getNumPreloadedSamples(); } +int sfz::Synth::getSampleQuality(ProcessMode mode) +{ + switch (mode) { + case ProcessLive: + return resources.synthConfig.liveSampleQuality; + case ProcessFreewheeling: + return resources.synthConfig.freeWheelingSampleQuality; + default: + CHECK(false); + return 0; + } +} + +void sfz::Synth::setSampleQuality(ProcessMode mode, int quality) +{ + switch (mode) { + case ProcessLive: + resources.synthConfig.liveSampleQuality = quality; + break; + case ProcessFreewheeling: + resources.synthConfig.freeWheelingSampleQuality = quality; + break; + default: + CHECK(false); + break; + } +} + float sfz::Synth::getVolume() const noexcept { return volume; @@ -1200,15 +1228,15 @@ uint32_t sfz::Synth::getPreloadSize() const noexcept void sfz::Synth::enableFreeWheeling() noexcept { - if (!freeWheeling) { - freeWheeling = true; + if (!resources.synthConfig.freeWheeling) { + resources.synthConfig.freeWheeling = true; DBG("Enabling freewheeling"); } } void sfz::Synth::disableFreeWheeling() noexcept { - if (freeWheeling) { - freeWheeling = false; + if (resources.synthConfig.freeWheeling) { + resources.synthConfig.freeWheeling = false; DBG("Disabling freewheeling"); } } diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 7c905d604..00372288b 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -78,6 +78,15 @@ class Synth final : public Voice::StateListener, public Parser::Listener { * @param numVoices */ Synth(int numVoices); + + /** + * @brief Processing mode + */ + enum ProcessMode { + ProcessLive, + ProcessFreewheeling, + }; + /** * @brief Empties the current regions and load a new SFZ file into the synth. * @@ -255,6 +264,21 @@ class Synth final : public Voice::StateListener, public Parser::Listener { * @param sampleRate */ void setSampleRate(float sampleRate) noexcept; + /** + * @brief Get the default resampling quality for the given mode. + * + * @param mode the processing mode + * + * @return the quality setting + */ + int getSampleQuality(ProcessMode mode); + /** + * @brief Set the default resampling quality for the given mode. + * + * @param mode the processing mode + * @param quality the quality setting + */ + void setSampleQuality(ProcessMode mode, int quality); /** * @brief Get the current value for the volume, in dB. * @@ -669,7 +693,6 @@ class Synth final : public Voice::StateListener, public Parser::Listener { std::uniform_real_distribution randNoteDistribution { 0, 1 }; std::mutex callbackGuard; - bool freeWheeling { false }; // Singletons passed as references to the voices Resources resources; diff --git a/src/sfizz/SynthConfig.h b/src/sfizz/SynthConfig.h new file mode 100644 index 000000000..3f710f89e --- /dev/null +++ b/src/sfizz/SynthConfig.h @@ -0,0 +1,24 @@ +// 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 "Defaults.h" + +namespace sfz +{ +struct SynthConfig +{ + bool freeWheeling { false }; + + int liveSampleQuality { sfz::Default::sampleQuality }; + int freeWheelingSampleQuality { sfz::Default::sampleQualityInFreewheelingMode }; + + int currentSampleQuality() const noexcept + { + return freeWheeling ? freeWheelingSampleQuality : liveSampleQuality; + } +}; +} diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 2493ce5b5..f95394d28 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -511,7 +511,8 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept } } - const int quality = region->sampleQuality; + const int quality = region->sampleQuality ? + *region->sampleQuality : resources.synthConfig.currentSampleQuality(); switch (quality) { default: diff --git a/src/sfizz/sfizz.cpp b/src/sfizz/sfizz.cpp index b110b4e1a..fbdd041cf 100644 --- a/src/sfizz/sfizz.cpp +++ b/src/sfizz/sfizz.cpp @@ -103,6 +103,16 @@ void sfz::Sfizz::setSampleRate(float sampleRate) noexcept synth->setSampleRate(sampleRate); } +int sfz::Sfizz::getSampleQuality(ProcessMode mode) +{ + return synth->getSampleQuality(static_cast(mode)); +} + +void sfz::Sfizz::setSampleQuality(ProcessMode mode, int quality) +{ + synth->setSampleQuality(static_cast(mode), quality); +} + float sfz::Sfizz::getVolume() const noexcept { return synth->getVolume(); diff --git a/src/sfizz/sfizz_wrapper.cpp b/src/sfizz/sfizz_wrapper.cpp index 15310942a..de33d8159 100644 --- a/src/sfizz/sfizz_wrapper.cpp +++ b/src/sfizz/sfizz_wrapper.cpp @@ -210,6 +210,18 @@ bool sfizz_set_oversampling_factor(sfizz_synth_t* synth, sfizz_oversampling_fact } } +int sfizz_get_sample_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode) +{ + auto self = reinterpret_cast(synth); + return self->getSampleQuality(static_cast(mode)); +} + +void sfizz_set_sample_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode, int quality) +{ + auto self = reinterpret_cast(synth); + return self->setSampleQuality(static_cast(mode), quality); +} + void sfizz_set_volume(sfizz_synth_t* synth, float volume) { auto self = reinterpret_cast(synth); From 90d556a98506061953deb488a43a57315b01009c Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 14:02:30 +0200 Subject: [PATCH 2/3] Clamp sample quality in the validity domain --- src/sfizz/Synth.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 457ab208c..3c35b6ee1 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1127,6 +1127,9 @@ int sfz::Synth::getSampleQuality(ProcessMode mode) void sfz::Synth::setSampleQuality(ProcessMode mode, int quality) { + CHECK(quality >= 1 && quality <= 10); + quality = clamp(quality, 1, 10); + switch (mode) { case ProcessLive: resources.synthConfig.liveSampleQuality = quality; From b144ec0cb8825d3b0e7082af52bfc1ba54c52d60 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 13 Jun 2020 22:58:57 +0200 Subject: [PATCH 3/3] Add sample quality tests --- src/sfizz/Voice.cpp | 9 ++++++-- src/sfizz/Voice.h | 7 ++++++ tests/SynthT.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index f95394d28..768be01b2 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -122,6 +122,12 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, egEnvelope.reset(region->amplitudeEG, *region, resources.midiState, delay, value, sampleRate); } +int sfz::Voice::getCurrentSampleQuality() const noexcept +{ + return (region && region->sampleQuality) ? + *region->sampleQuality : resources.synthConfig.currentSampleQuality(); +} + bool sfz::Voice::isFree() const noexcept { return (state == State::idle); @@ -511,8 +517,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept } } - const int quality = region->sampleQuality ? - *region->sampleQuality : resources.synthConfig.currentSampleQuality(); + const int quality = getCurrentSampleQuality(); switch (quality) { default: diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 515feb128..0dd5557ac 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -107,6 +107,13 @@ class Voice { */ void startVoice(Region* region, int delay, int number, float value, TriggerType triggerType) noexcept; + /** + * @brief Get the sample quality determined by the active region. + * + * @return int + */ + int getCurrentSampleQuality() const noexcept; + /** * @brief Register a note-off event; this may trigger a release. * diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 112acb0dd..08112befd 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -486,3 +486,57 @@ TEST_CASE("[Synth] Region by identifier") REQUIRE(nullptr == synth.getRegionById(NumericId{6})); REQUIRE(nullptr == synth.getRegionById(NumericId{})); } + +TEST_CASE("[Synth] sample quality") +{ + sfz::Synth synth; + + synth.loadSfzString("tests/TestFiles/sampleQuality.sfz", R"( + sample=kick.wav key=60 + sample=kick.wav key=61 sample_quality=5 +)"); + + // default sample quality + synth.noteOn(0, 60, 100); + REQUIRE(synth.getNumActiveVoices() == 1); + REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::sampleQuality); + synth.allSoundOff(); + + // default sample quality, freewheeling + synth.enableFreeWheeling(); + synth.noteOn(0, 60, 100); + REQUIRE(synth.getNumActiveVoices() == 1); + REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::sampleQualityInFreewheelingMode); + synth.allSoundOff(); + synth.disableFreeWheeling(); + + // user-defined sample quality + synth.setSampleQuality(sfz::Synth::ProcessLive, 3); + synth.noteOn(0, 60, 100); + REQUIRE(synth.getNumActiveVoices() == 1); + REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == 3); + synth.allSoundOff(); + + // user-defined sample quality, freewheeling + synth.enableFreeWheeling(); + synth.setSampleQuality(sfz::Synth::ProcessFreewheeling, 8); + synth.noteOn(0, 60, 100); + REQUIRE(synth.getNumActiveVoices() == 1); + REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == 8); + synth.allSoundOff(); + synth.disableFreeWheeling(); + + // region sample quality + synth.noteOn(0, 61, 100); + REQUIRE(synth.getNumActiveVoices() == 1); + REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == 5); + synth.allSoundOff(); + + // region sample quality, freewheeling + synth.enableFreeWheeling(); + synth.noteOn(0, 61, 100); + REQUIRE(synth.getNumActiveVoices() == 1); + REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == 5); + synth.allSoundOff(); + synth.disableFreeWheeling(); +}