From bca192de54d68c1bf21563ca6768725617291521 Mon Sep 17 00:00:00 2001 From: Luciano Iam Date: Wed, 28 Sep 2022 13:22:35 +0200 Subject: [PATCH 1/9] Add WavPack library --- .gitmodules | 3 +++ external/st_audiofile/CMakeLists.txt | 3 +++ external/st_audiofile/thirdparty/wavpack | 1 + 3 files changed, 7 insertions(+) create mode 160000 external/st_audiofile/thirdparty/wavpack diff --git a/.gitmodules b/.gitmodules index 525dc6a4..c3f22321 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "external/simde"] path = external/simde url = https://github.com/simd-everywhere/simde.git +[submodule "external/st_audiofile/thirdparty/wavpack"] + path = external/st_audiofile/thirdparty/wavpack + url = https://github.com/dbry/WavPack.git diff --git a/external/st_audiofile/CMakeLists.txt b/external/st_audiofile/CMakeLists.txt index 06b5251b..4fcceabd 100644 --- a/external/st_audiofile/CMakeLists.txt +++ b/external/st_audiofile/CMakeLists.txt @@ -22,6 +22,9 @@ target_include_directories(st_audiofile_formats add_subdirectory("thirdparty/libaiff" EXCLUDE_FROM_ALL) target_link_libraries(st_audiofile_formats PUBLIC aiff::aiff) +add_subdirectory("thirdparty/wavpack" EXCLUDE_FROM_ALL) +target_link_libraries(st_audiofile_formats PUBLIC wavpack) + ### if(NOT ST_AUDIO_FILE_USE_SNDFILE) target_link_libraries(st_audiofile PRIVATE st_audiofile_formats) diff --git a/external/st_audiofile/thirdparty/wavpack b/external/st_audiofile/thirdparty/wavpack new file mode 160000 index 00000000..36b08dbc --- /dev/null +++ b/external/st_audiofile/thirdparty/wavpack @@ -0,0 +1 @@ +Subproject commit 36b08dbcb1de136e9ae477e9f1e2b57c958fff18 From a35110c70666e7471fffd6baf8eb1dc8b744f0f3 Mon Sep 17 00:00:00 2001 From: Luciano Iam Date: Wed, 28 Sep 2022 23:09:50 +0200 Subject: [PATCH 2/9] Support WavPack in st_audiofile --- external/st_audiofile/src/st_audiofile.c | 77 +++++++++++++++++++ external/st_audiofile/src/st_audiofile.h | 3 + .../st_audiofile/src/st_audiofile_common.c | 3 + external/st_audiofile/src/st_audiofile_libs.h | 1 + 4 files changed, 84 insertions(+) diff --git a/external/st_audiofile/src/st_audiofile.c b/external/st_audiofile/src/st_audiofile.c index 0659c3e7..2d3cc578 100644 --- a/external/st_audiofile/src/st_audiofile.c +++ b/external/st_audiofile/src/st_audiofile.c @@ -17,12 +17,14 @@ struct st_audio_file { AIFF_Ref aiff; drmp3 *mp3; stb_vorbis* ogg; + WavpackContext* wv; }; union { struct { uint32_t channels; float sample_rate; uint64_t frames; } aiff; struct { uint64_t frames; } mp3; struct { uint32_t channels; float sample_rate; uint64_t frames; } ogg; + struct { uint32_t channels; float sample_rate; uint64_t frames; int bitrate; int mode; } wv; } cache; union { @@ -173,6 +175,25 @@ static st_audio_file* st_generic_open_file(const void* filename, int widepath) } } + // Try WV + { + af->wv = +#if defined(_WIN32) + WavpackOpenFileInput((const char*)filename, NULL, OPEN_FILE_UTF8, 0); +#else + WavpackOpenFileInput((const char*)filename, NULL, 0, 0); +#endif + if (af->wv) { + af->cache.wv.channels = (uint32_t)WavpackGetNumChannels(af->wv); + af->cache.wv.sample_rate = (float)WavpackGetSampleRate(af->wv); + af->cache.wv.frames = (uint64_t)WavpackGetNumSamples64(af->wv); + af->cache.wv.bitrate = WavpackGetBitsPerSample(af->wv); + af->cache.wv.mode = WavpackGetMode(af->wv); + af->type = st_audio_file_wv; + return af; + } + } + free(af); return NULL; } @@ -278,6 +299,17 @@ st_audio_file* st_open_memory(const void* memory, size_t length) } } + // Try WV + { + af->wv = + WavpackOpenRawDecoder((void*)memory, (int32_t)length, NULL, 0, + WAVPACK_MEMORY_ASSUMED_VERSION, NULL, 0, 0); + if (af->wv) { + af->type = st_audio_file_wv; + return af; + } + } + free(af); return NULL; } @@ -315,6 +347,9 @@ void st_close(st_audio_file* af) drmp3_uninit(af->mp3); free(af->mp3); break; + case st_audio_file_wv: + WavpackCloseFile(af->wv); + break; } free(af); @@ -345,6 +380,9 @@ uint32_t st_get_channels(st_audio_file* af) case st_audio_file_mp3: channels = af->mp3->channels; break; + case st_audio_file_wv: + channels = af->cache.wv.channels; + break; } return channels; @@ -370,6 +408,9 @@ float st_get_sample_rate(st_audio_file* af) case st_audio_file_mp3: sample_rate = af->mp3->sampleRate; break; + case st_audio_file_wv: + sample_rate = af->cache.wv.sample_rate; + break; } return sample_rate; @@ -395,6 +436,9 @@ uint64_t st_get_frame_count(st_audio_file* af) case st_audio_file_mp3: frames = af->cache.mp3.frames; break; + case st_audio_file_wv: + frames = af->cache.wv.frames; + break; } return frames; @@ -420,6 +464,9 @@ bool st_seek(st_audio_file* af, uint64_t frame) case st_audio_file_mp3: success = drmp3_seek_to_pcm_frame(af->mp3, frame); break; + case st_audio_file_wv: + success = WavpackSeekSample64(af->wv, (int64_t)frame); + break; } return success; @@ -449,6 +496,24 @@ uint64_t st_read_s16(st_audio_file* af, int16_t* buffer, uint64_t count) case st_audio_file_mp3: count = drmp3_read_pcm_frames_s16(af->mp3, count, buffer); break; + case st_audio_file_wv: + { + int32_t* buf_i32 = (int32_t*)malloc(4 * count); + if (!buf_i32) { + return 0; + } + count = WavpackUnpackSamples(af->wv, buf_i32, (uint32_t)count); + if (af->cache.wv.mode & MODE_FLOAT) { + drwav_f32_to_s16((drwav_int16*)buffer, (float*)buf_i32, (size_t)count); + } else { + int d = af->cache.wv.bitrate - 16; + for (uint64_t i = 0; i < count; i++) { + buffer[i] = (int16_t)(buf_i32[i] >> d); + } + } + free(buf_i32); + } + break; } return count; @@ -478,6 +543,18 @@ uint64_t st_read_f32(st_audio_file* af, float* buffer, uint64_t count) case st_audio_file_mp3: count = drmp3_read_pcm_frames_f32(af->mp3, count, buffer); break; + case st_audio_file_wv: + count = WavpackUnpackSamples(af->wv, (int32_t*)buffer, (uint32_t)count); + if (!(af->cache.wv.mode & MODE_FLOAT)) { + if (af->cache.wv.bitrate < 32) { + int d = 32 - af->cache.wv.bitrate; + for (uint64_t i = 0; i < count; i++) { + ((int32_t*)buffer)[i] <<= d; + } + } + drwav_s32_to_f32(buffer, (drwav_int32*)buffer, (size_t)count); + } + break; } return count; diff --git a/external/st_audiofile/src/st_audiofile.h b/external/st_audiofile/src/st_audiofile.h index f4f7d252..6822cd0e 100644 --- a/external/st_audiofile/src/st_audiofile.h +++ b/external/st_audiofile/src/st_audiofile.h @@ -19,6 +19,8 @@ #include #endif +#define WAVPACK_MEMORY_ASSUMED_VERSION 5 + #ifdef __cplusplus extern "C" { #endif @@ -31,6 +33,7 @@ typedef enum st_audio_file_type { st_audio_file_aiff, st_audio_file_ogg, st_audio_file_mp3, + st_audio_file_wv, st_audio_file_other, } st_audio_file_type; diff --git a/external/st_audiofile/src/st_audiofile_common.c b/external/st_audiofile/src/st_audiofile_common.c index c3e14297..0ab77bc4 100644 --- a/external/st_audiofile/src/st_audiofile_common.c +++ b/external/st_audiofile/src/st_audiofile_common.c @@ -32,6 +32,9 @@ const char* st_type_string(int type) case st_audio_file_mp3: type_string = "MP3"; break; + case st_audio_file_wv: + type_string = "WV"; + break; case st_audio_file_other: type_string = "other"; break; diff --git a/external/st_audiofile/src/st_audiofile_libs.h b/external/st_audiofile/src/st_audiofile_libs.h index 67aacaba..f60b9f6b 100644 --- a/external/st_audiofile/src/st_audiofile_libs.h +++ b/external/st_audiofile/src/st_audiofile_libs.h @@ -15,6 +15,7 @@ #endif #include "stb_vorbis.c" #include "libaiff/libaiff.h" +#include "wavpack.h" #if defined(_WIN32) #include From 2e388848d3d5090ec618de4ddf7c4b37d4327bfd Mon Sep 17 00:00:00 2001 From: Luciano Iam Date: Wed, 28 Sep 2022 23:41:53 +0200 Subject: [PATCH 3/9] Support audio files with .wv extension --- src/sfizz/import/foreign_instruments/AudioFile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/import/foreign_instruments/AudioFile.cpp b/src/sfizz/import/foreign_instruments/AudioFile.cpp index 4fa897ea..65457cb1 100644 --- a/src/sfizz/import/foreign_instruments/AudioFile.cpp +++ b/src/sfizz/import/foreign_instruments/AudioFile.cpp @@ -14,7 +14,7 @@ namespace sfz { static const char* kRecognizedAudioExtensions[] = { - ".wav", ".flac", ".ogg", ".mp3", ".aif", ".aiff", ".aifc", + ".wav", ".flac", ".ogg", ".mp3", ".aif", ".aiff", ".aifc", ".wv", }; /// From d1158bae66a96b5287c3af30ed939c827afc31a1 Mon Sep 17 00:00:00 2001 From: Luciano Iam Date: Thu, 29 Sep 2022 09:08:32 +0200 Subject: [PATCH 4/9] Deal with multichannel WavPack files --- external/st_audiofile/src/st_audiofile.c | 29 ++++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/external/st_audiofile/src/st_audiofile.c b/external/st_audiofile/src/st_audiofile.c index 2d3cc578..85e0542a 100644 --- a/external/st_audiofile/src/st_audiofile.c +++ b/external/st_audiofile/src/st_audiofile.c @@ -498,11 +498,12 @@ uint64_t st_read_s16(st_audio_file* af, int16_t* buffer, uint64_t count) break; case st_audio_file_wv: { - int32_t* buf_i32 = (int32_t*)malloc(4 * count); + uint32_t channels = af->cache.wv.channels; + int32_t* buf_i32 = (int32_t*)malloc(4 * channels * count); if (!buf_i32) { return 0; } - count = WavpackUnpackSamples(af->wv, buf_i32, (uint32_t)count); + count = channels * WavpackUnpackSamples(af->wv, buf_i32, (uint32_t)count); if (af->cache.wv.mode & MODE_FLOAT) { drwav_f32_to_s16((drwav_int16*)buffer, (float*)buf_i32, (size_t)count); } else { @@ -544,15 +545,23 @@ uint64_t st_read_f32(st_audio_file* af, float* buffer, uint64_t count) count = drmp3_read_pcm_frames_f32(af->mp3, count, buffer); break; case st_audio_file_wv: - count = WavpackUnpackSamples(af->wv, (int32_t*)buffer, (uint32_t)count); - if (!(af->cache.wv.mode & MODE_FLOAT)) { - if (af->cache.wv.bitrate < 32) { - int d = 32 - af->cache.wv.bitrate; - for (uint64_t i = 0; i < count; i++) { - ((int32_t*)buffer)[i] <<= d; - } + { + uint32_t channels = af->cache.wv.channels; + int32_t* buf_i32 = (int32_t*)malloc(4 * channels * count); + if (!buf_i32) { + return 0; } - drwav_s32_to_f32(buffer, (drwav_int32*)buffer, (size_t)count); + count = channels * WavpackUnpackSamples(af->wv, buf_i32, (uint32_t)count); + if (!(af->cache.wv.mode & MODE_FLOAT)) { + if (af->cache.wv.bitrate < 32) { + int d = 32 - af->cache.wv.bitrate; + for (uint64_t i = 0; i < count; i++) { + buf_i32[i] <<= d; + } + } + drwav_s32_to_f32(buffer, (drwav_int32*)buf_i32, (size_t)count); + } + free(buf_i32); } break; } From e59a41be6994ae0b9dca6e82b688a74d21e87638 Mon Sep 17 00:00:00 2001 From: Luciano Iam Date: Fri, 30 Sep 2022 14:11:42 +0200 Subject: [PATCH 5/9] Properly load float WavPack audio --- external/st_audiofile/src/st_audiofile.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/external/st_audiofile/src/st_audiofile.c b/external/st_audiofile/src/st_audiofile.c index 85e0542a..452a1bee 100644 --- a/external/st_audiofile/src/st_audiofile.c +++ b/external/st_audiofile/src/st_audiofile.c @@ -545,7 +545,9 @@ uint64_t st_read_f32(st_audio_file* af, float* buffer, uint64_t count) count = drmp3_read_pcm_frames_f32(af->mp3, count, buffer); break; case st_audio_file_wv: - { + if (af->cache.wv.mode & MODE_FLOAT) { + count = WavpackUnpackSamples(af->wv, (int32_t*)buffer, (uint32_t)count); + } else { uint32_t channels = af->cache.wv.channels; int32_t* buf_i32 = (int32_t*)malloc(4 * channels * count); if (!buf_i32) { From 8e4e6460d8fd505feec47cf57b7cdbb27b46b345 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 28 May 2023 16:37:23 +0200 Subject: [PATCH 6/9] Add a test --- tests/AudioFilesT.cpp | 31 +++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/TestFiles/kick.wv | Bin 0 -> 25154 bytes 3 files changed, 32 insertions(+) create mode 100644 tests/AudioFilesT.cpp create mode 100644 tests/TestFiles/kick.wv diff --git a/tests/AudioFilesT.cpp b/tests/AudioFilesT.cpp new file mode 100644 index 00000000..8046ca71 --- /dev/null +++ b/tests/AudioFilesT.cpp @@ -0,0 +1,31 @@ +// 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 + +#include "AudioSpan.h" +#include "sfizz/Synth.h" +#include "TestHelpers.h" +#include "catch2/catch.hpp" +#include +using namespace Catch::literals; + +TEST_CASE("[AudioFiles] WavPack file") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + sfz::AudioSpan span { buffer }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/wavpack.sfz", R"( + sample=kick.wav key=60 pan=-100 + sample=kick.wv key=60 pan=100 + )"); + synth.noteOn(0, 60, 127); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 2 ); + REQUIRE( approxEqual(span.getConstSpan(0), span.getConstSpan(1)) ); + while (numPlayingVoices(synth) > 0) { + synth.renderBlock(buffer); + REQUIRE( approxEqual(span.getConstSpan(0), span.getConstSpan(1)) ); + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8553aca9..db17ba1d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,6 +57,7 @@ set(SFIZZ_TEST_SOURCES MessagingT.cpp OversamplerT.cpp MemoryT.cpp + AudioFilesT.cpp DataHelpers.h DataHelpers.cpp ) diff --git a/tests/TestFiles/kick.wv b/tests/TestFiles/kick.wv new file mode 100644 index 0000000000000000000000000000000000000000..a00e5d9fa65e2ed39bfc9ea61bb96b6aefb14c1f GIT binary patch literal 25154 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
-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 literal 0 HcmV?d00001 From 121d7955599fcb1404a8bf963afde3163d5a2271 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 29 May 2023 14:02:16 +0200 Subject: [PATCH 7/9] Cover flac since we're at it --- tests/AudioFilesT.cpp | 40 +++++++++++++++++++++++++++++++++ tests/EventEnvelopesT.cpp | 15 +------------ tests/SIMDHelpersT.cpp | 41 +++++---------------------------- tests/SmoothersT.cpp | 24 +++++--------------- tests/TestFiles/kick.flac | Bin 0 -> 28122 bytes tests/TestHelpers.h | 46 +++++++++++++++++++++++++++++++++----- 6 files changed, 93 insertions(+), 73 deletions(-) create mode 100755 tests/TestFiles/kick.flac diff --git a/tests/AudioFilesT.cpp b/tests/AudioFilesT.cpp index 8046ca71..f888c650 100644 --- a/tests/AudioFilesT.cpp +++ b/tests/AudioFilesT.cpp @@ -5,12 +5,33 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "AudioSpan.h" +#include "absl/types/span.h" #include "sfizz/Synth.h" #include "TestHelpers.h" #include "catch2/catch.hpp" #include +#include using namespace Catch::literals; +TEST_CASE("[AudioFiles] No leakage on right") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + std::vector zeros(synth.getSamplesPerBlock(), 0); + sfz::AudioSpan span { buffer }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/wavpack.sfz", R"( + sample=kick.wav key=60 pan=-100 + )"); + synth.noteOn(0, 60, 127); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 1 ); + REQUIRE( approxEqual(span.getConstSpan(1), absl::MakeConstSpan(zeros), 1e-2f) ); + while (numPlayingVoices(synth) > 0) { + synth.renderBlock(buffer); + REQUIRE( approxEqual(span.getConstSpan(1), absl::MakeConstSpan(zeros), 1e-2f) ); + } +} + TEST_CASE("[AudioFiles] WavPack file") { sfz::Synth synth; @@ -29,3 +50,22 @@ TEST_CASE("[AudioFiles] WavPack file") REQUIRE( approxEqual(span.getConstSpan(0), span.getConstSpan(1)) ); } } + +TEST_CASE("[AudioFiles] Flac file") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + sfz::AudioSpan span { buffer }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/wavpack.sfz", R"( + sample=kick.wav key=60 pan=-100 + sample=kick.flac key=60 pan=100 + )"); + synth.noteOn(0, 60, 127); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 2 ); + REQUIRE( approxEqual(span.getConstSpan(0), span.getConstSpan(1)) ); + while (numPlayingVoices(synth) > 0) { + synth.renderBlock(buffer); + REQUIRE( approxEqual(span.getConstSpan(0), span.getConstSpan(1)) ); + } +} diff --git a/tests/EventEnvelopesT.cpp b/tests/EventEnvelopesT.cpp index 01287d3e..60bd9830 100644 --- a/tests/EventEnvelopesT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -7,6 +7,7 @@ #include "sfizz/ModifierHelpers.h" #include "sfizz/Buffer.h" #include "catch2/catch.hpp" +#include "TestHelpers.h" #include #include #include @@ -14,20 +15,6 @@ #include using namespace Catch::literals; -template -inline bool approxEqual(absl::Span lhs, absl::Span rhs, Type eps = 1e-3) -{ - if (lhs.size() != rhs.size()) - return false; - - for (size_t i = 0; i < rhs.size(); ++i) - if (rhs[i] != Approx(lhs[i]).epsilon(eps)) { - std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n'; - return false; - } - - return true; -} const auto idModifier = [](float x) { return x; }; const auto twiceModifier = [](float x) { return 2 * x; }; diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 6bdf8cf6..4bac07b1 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -7,6 +7,7 @@ #include "sfizz/simd/Common.h" #include "sfizz/SIMDHelpers.h" #include "sfizz/Panning.h" +#include "TestHelpers.h" #include "catch2/catch.hpp" #include #include @@ -20,39 +21,9 @@ template using aligned_vector = std::vector>; constexpr int bigBufferSize { 4095 }; -constexpr int medBufferSize { 127 }; +constexpr int medBufferSize { 1023 }; constexpr float fillValue { 1.3f }; -template -inline bool approxEqualMargin(absl::Span lhs, absl::Span rhs, Type eps = 1e-3) -{ - if (lhs.size() != rhs.size()) - return false; - - for (size_t i = 0; i < rhs.size(); ++i) - if (rhs[i] != Approx(lhs[i]).margin(eps)) { - std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n'; - return false; - } - - return true; -} - -template -inline bool approxEqual(absl::Span lhs, absl::Span rhs, Type eps = 1e-3) -{ - if (lhs.size() != rhs.size()) - return false; - - for (size_t i = 0; i < rhs.size(); ++i) - if (rhs[i] != Approx(lhs[i]).epsilon(eps)) { - std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n'; - return false; - } - - return true; -} - TEST_CASE("[Helpers] willAlign, prevAligned and unaligned tests") { aligned_vector array(16); @@ -430,8 +401,8 @@ TEST_CASE("[Helpers] Linear Ramp (SIMD)") TEST_CASE("[Helpers] Linear Ramp (SIMD vs scalar)") { const float start { 0.0f }; - std::vector outputScalar(bigBufferSize); - std::vector outputSIMD(bigBufferSize); + std::vector outputScalar(medBufferSize); + std::vector outputSIMD(medBufferSize); sfz::setSIMDOpStatus(sfz::SIMDOps::linearRamp, false); sfz::linearRamp(absl::MakeSpan(outputScalar), start, fillValue); sfz::setSIMDOpStatus(sfz::SIMDOps::linearRamp, true); @@ -442,8 +413,8 @@ TEST_CASE("[Helpers] Linear Ramp (SIMD vs scalar)") TEST_CASE("[Helpers] Linear Ramp unaligned (SIMD vs scalar)") { const float start { 0.0f }; - std::vector outputScalar(bigBufferSize); - std::vector outputSIMD(bigBufferSize); + std::vector outputScalar(medBufferSize); + std::vector outputSIMD(medBufferSize); sfz::setSIMDOpStatus(sfz::SIMDOps::linearRamp, false); sfz::linearRamp(absl::MakeSpan(outputScalar).subspan(1), start, fillValue); sfz::setSIMDOpStatus(sfz::SIMDOps::linearRamp, true); diff --git a/tests/SmoothersT.cpp b/tests/SmoothersT.cpp index 63c05141..342da8e3 100644 --- a/tests/SmoothersT.cpp +++ b/tests/SmoothersT.cpp @@ -7,6 +7,7 @@ #include "sfizz/OnePoleFilter.h" #include "sfizz/Smoothers.h" #include "catch2/catch.hpp" +#include "TestHelpers.h" // #include "cnpy.h" // #include "ghc/fs_std.hpp" #include @@ -16,21 +17,6 @@ #include using namespace Catch::literals; -template -inline bool approxEqual(const std::array& lhs, const std::array& rhs, Type epsilon = 1e-3) -{ - if (lhs.size() != rhs.size()) - return false; - - for (size_t i = 0; i < rhs.size(); ++i) - if (lhs[i] != Approx(rhs[i]).epsilon(epsilon)) { - std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n'; - return false; - } - - return true; -} - template void testFilter(const std::array& input, const std::array& expectedLow, const std::array& expectedHigh, Type gain) { @@ -43,19 +29,19 @@ void testFilter(const std::array& input, const std::array& exp sfz::OnePoleFilter filter; filter.setGain(gain); filter.processLowpass(input, outputSpan); - REQUIRE(approxEqual(output, expectedLow)); + REQUIRE(approxEqual(absl::MakeConstSpan(output), absl::MakeConstSpan(expectedLow))); filter.reset(); filter.processLowpass(input, outputSpan, gains); - REQUIRE(approxEqual(output, expectedLow)); + REQUIRE(approxEqual(absl::MakeConstSpan(output), absl::MakeConstSpan(expectedLow))); filter.reset(); filter.processHighpass(input, outputSpan); - REQUIRE(approxEqual(output, expectedHigh)); + REQUIRE(approxEqual(absl::MakeConstSpan(output), absl::MakeConstSpan(expectedHigh))); filter.reset(); filter.processHighpass(input, outputSpan, gains); - REQUIRE(approxEqual(output, expectedHigh)); + REQUIRE(approxEqual(absl::MakeConstSpan(output), absl::MakeConstSpan(expectedHigh))); } constexpr std::array floatInput01 = { diff --git a/tests/TestFiles/kick.flac b/tests/TestFiles/kick.flac new file mode 100755 index 0000000000000000000000000000000000000000..79a2f4e6cd8b9d429dad9f833db4b26cb6f2809d GIT binary patch literal 28122 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 F{{m5EtQi0R literal 0 HcmV?d00001 diff --git a/tests/TestHelpers.h b/tests/TestHelpers.h index 8faa28b5..37d10de1 100644 --- a/tests/TestHelpers.h +++ b/tests/TestHelpers.h @@ -12,6 +12,8 @@ #include "sfizz/Messaging.h" #include "catch2/catch.hpp" #include "sfizz/modulations/ModKey.h" +#include +#include class RegionCCView { public: @@ -148,18 +150,52 @@ std::string createDefaultGraph(std::vector lines, int numRegions = std::string createModulationDotGraph(std::vector lines); template -inline bool approxEqual(absl::Span lhs, absl::Span rhs, Type eps = 1e-3) +bool approxEqual(absl::Span lhs, absl::Span rhs, Type eps = 1e-3) { if (lhs.size() != rhs.size()) return false; + bool different = false; for (size_t i = 0; i < rhs.size(); ++i) - if (rhs[i] != Approx(lhs[i]).epsilon(eps)) { - std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n'; - return false; + if (rhs[i] != Approx(lhs[i]).margin(eps)) { + std::cerr << lhs[i] << " != " << rhs[i] << " (delta " << std::abs(rhs[i] - lhs[i]) << ") at index " << i << '\n'; + different = true; + break; } - return true; + auto print_span = [](absl::Span span) { + if (span.empty()) { + std::cerr << "{ }" << '\n'; + return; + } + + std::cerr << "{ "; + if (span.size() < 16) { + for (unsigned i = 0; i < span.size() - 1; ++i) { + std::cerr << span[i] << ", "; + } + } else { + for (unsigned i = 0; i < 8; ++i) { + std::cerr << span[i] << ", "; + } + std::cerr << "..., "; + for (unsigned i = span.size() - 8; i < span.size() - 1; ++i) { + std::cerr << span[i] << ", "; + } + } + std::cerr << span.back() << " }" << '\n'; + }; + + if (different) { + std::cerr << std::setprecision(3) << std::fixed; + std::cerr << "Differences between spans" << '\n'; + std::cerr << "lhs: "; + print_span(lhs); + std::cerr << "rhs: "; + print_span(rhs); + } + + return !different; } /** From 17584a414a60e489dae1328c2d7959590aceece1 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 29 May 2023 14:51:44 +0200 Subject: [PATCH 8/9] Fix makefile --- common.mk | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/common.mk b/common.mk index ebcdf76b..27576171 100644 --- a/common.mk +++ b/common.mk @@ -171,6 +171,45 @@ SFIZZ_CXX_FLAGS += \ -I$(SFIZZ_DIR)/external/st_audiofile/thirdparty/dr_libs \ -I$(SFIZZ_DIR)/external/st_audiofile/thirdparty/stb_vorbis +# Wavpack support +SFIZZ_SOURCES += \ + external/st_audiofile/thirdparty/wavpack/src/common_utils.c \ + external/st_audiofile/thirdparty/wavpack/src/decorr_utils.c \ + external/st_audiofile/thirdparty/wavpack/src/entropy_utils.c \ + external/st_audiofile/thirdparty/wavpack/src/extra1.c \ + external/st_audiofile/thirdparty/wavpack/src/extra2.c \ + external/st_audiofile/thirdparty/wavpack/src/open_utils.c \ + external/st_audiofile/thirdparty/wavpack/src/open_filename.c \ + external/st_audiofile/thirdparty/wavpack/src/open_legacy.c \ + external/st_audiofile/thirdparty/wavpack/src/open_raw.c \ + external/st_audiofile/thirdparty/wavpack/src/pack.c \ + external/st_audiofile/thirdparty/wavpack/src/pack_dns.c \ + external/st_audiofile/thirdparty/wavpack/src/pack_floats.c \ + external/st_audiofile/thirdparty/wavpack/src/pack_utils.c \ + external/st_audiofile/thirdparty/wavpack/src/read_words.c \ + external/st_audiofile/thirdparty/wavpack/src/tags.c \ + external/st_audiofile/thirdparty/wavpack/src/tag_utils.c \ + external/st_audiofile/thirdparty/wavpack/src/unpack.c \ + external/st_audiofile/thirdparty/wavpack/src/unpack_floats.c \ + external/st_audiofile/thirdparty/wavpack/src/unpack_seek.c \ + external/st_audiofile/thirdparty/wavpack/src/unpack_utils.c \ + external/st_audiofile/thirdparty/wavpack/src/write_words.c \ + external/st_audiofile/thirdparty/wavpack/src/pack_dsd.c \ + external/st_audiofile/thirdparty/wavpack/src/unpack_dsd.c \ + external/st_audiofile/thirdparty/wavpack/src/unpack3.c \ + external/st_audiofile/thirdparty/wavpack/src/unpack3_open.c \ + external/st_audiofile/thirdparty/wavpack/src/unpack3_seek.c + +SFIZZ_C_FLAGS += \ + -I$(SFIZZ_DIR)/external/st_audiofile/thirdparty/wavpack/include \ + -I$(SFIZZ_DIR)/external/st_audiofile/thirdparty/wavpack/src \ + -DENABLE_DSD -DENABLE_LEGACY + +SFIZZ_CXX_FLAGS += \ + -I$(SFIZZ_DIR)/external/st_audiofile/thirdparty/wavpack/include \ + -I$(SFIZZ_DIR)/external/st_audiofile/thirdparty/wavpack/src \ + -DENABLE_DSD -DENABLE_LEGACY + ifeq ($(SFIZZ_USE_SNDFILE),1) SFIZZ_C_FLAGS += $(SFIZZ_SNDFILE_C_FLAGS) -DST_AUDIO_FILE_USE_SNDFILE=1 SFIZZ_CXX_FLAGS += $(SFIZZ_SNDFILE_CXX_FLAGS) -DST_AUDIO_FILE_USE_SNDFILE=1 From c3c416496f26b41b27c82d8e1adf750e4e24ed27 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 30 May 2023 09:23:51 +0200 Subject: [PATCH 9/9] Move define --- external/st_audiofile/src/st_audiofile.c | 2 ++ external/st_audiofile/src/st_audiofile.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/st_audiofile/src/st_audiofile.c b/external/st_audiofile/src/st_audiofile.c index 452a1bee..1abc51da 100644 --- a/external/st_audiofile/src/st_audiofile.c +++ b/external/st_audiofile/src/st_audiofile.c @@ -9,6 +9,8 @@ #include "st_audiofile_libs.h" #include +#define WAVPACK_MEMORY_ASSUMED_VERSION 5 + struct st_audio_file { int type; union { diff --git a/external/st_audiofile/src/st_audiofile.h b/external/st_audiofile/src/st_audiofile.h index 6822cd0e..787cc332 100644 --- a/external/st_audiofile/src/st_audiofile.h +++ b/external/st_audiofile/src/st_audiofile.h @@ -19,8 +19,6 @@ #include #endif -#define WAVPACK_MEMORY_ASSUMED_VERSION 5 - #ifdef __cplusplus extern "C" { #endif