Merge pull request #1129 from lucianoiam/wavpack

Add WavPack audio files support
This commit is contained in:
Paul Ferrand 2023-05-31 17:25:36 +02:00 committed by GitHub
commit cfc39ca724
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 267 additions and 74 deletions

3
.gitmodules vendored
View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -9,6 +9,8 @@
#include "st_audiofile_libs.h"
#include <stdlib.h>
#define WAVPACK_MEMORY_ASSUMED_VERSION 5
struct st_audio_file {
int type;
union {
@ -17,12 +19,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 +177,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 +301,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 +349,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 +382,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 +410,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 +438,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 +466,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 +498,25 @@ 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:
{
uint32_t channels = af->cache.wv.channels;
int32_t* buf_i32 = (int32_t*)malloc(4 * channels * count);
if (!buf_i32) {
return 0;
}
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 {
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 +546,28 @@ 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:
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) {
return 0;
}
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;
}
return count;

View file

@ -31,6 +31,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;

View file

@ -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;

View file

@ -15,6 +15,7 @@
#endif
#include "stb_vorbis.c"
#include "libaiff/libaiff.h"
#include "wavpack.h"
#if defined(_WIN32)
#include <wchar.h>

@ -0,0 +1 @@
Subproject commit 36b08dbcb1de136e9ae477e9f1e2b57c958fff18

View file

@ -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",
};
///

71
tests/AudioFilesT.cpp Normal file
View file

@ -0,0 +1,71 @@
// 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 "absl/types/span.h"
#include "sfizz/Synth.h"
#include "TestHelpers.h"
#include "catch2/catch.hpp"
#include <algorithm>
#include <vector>
using namespace Catch::literals;
TEST_CASE("[AudioFiles] No leakage on right")
{
sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
std::vector<float> zeros(synth.getSamplesPerBlock(), 0);
sfz::AudioSpan<float> span { buffer };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/wavpack.sfz", R"(
<region> 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;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
sfz::AudioSpan<float> span { buffer };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/wavpack.sfz", R"(
<region> sample=kick.wav key=60 pan=-100
<region> 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)) );
}
}
TEST_CASE("[AudioFiles] Flac file")
{
sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
sfz::AudioSpan<float> span { buffer };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/wavpack.sfz", R"(
<region> sample=kick.wav key=60 pan=-100
<region> 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)) );
}
}

View file

@ -57,6 +57,7 @@ set(SFIZZ_TEST_SOURCES
MessagingT.cpp
OversamplerT.cpp
MemoryT.cpp
AudioFilesT.cpp
DataHelpers.h
DataHelpers.cpp
)

View file

@ -7,6 +7,7 @@
#include "sfizz/ModifierHelpers.h"
#include "sfizz/Buffer.h"
#include "catch2/catch.hpp"
#include "TestHelpers.h"
#include <absl/algorithm/container.h>
#include <absl/types/span.h>
#include <algorithm>
@ -14,20 +15,6 @@
#include <iostream>
using namespace Catch::literals;
template <class Type>
inline bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> 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; };

View file

@ -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 <absl/algorithm/container.h>
#include <absl/types/span.h>
@ -20,39 +21,9 @@ template <class T, std::size_t A = sfz::config::defaultAlignment>
using aligned_vector = std::vector<T, jsl::aligned_allocator<T, A>>;
constexpr int bigBufferSize { 4095 };
constexpr int medBufferSize { 127 };
constexpr int medBufferSize { 1023 };
constexpr float fillValue { 1.3f };
template <class Type>
inline bool approxEqualMargin(absl::Span<const Type> lhs, absl::Span<const Type> 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 <class Type>
inline bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> 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<float, 32> 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<float> outputScalar(bigBufferSize);
std::vector<float> outputSIMD(bigBufferSize);
std::vector<float> outputScalar(medBufferSize);
std::vector<float> outputSIMD(medBufferSize);
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::linearRamp, false);
sfz::linearRamp<float>(absl::MakeSpan(outputScalar), start, fillValue);
sfz::setSIMDOpStatus<float>(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<float> outputScalar(bigBufferSize);
std::vector<float> outputSIMD(bigBufferSize);
std::vector<float> outputScalar(medBufferSize);
std::vector<float> outputSIMD(medBufferSize);
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::linearRamp, false);
sfz::linearRamp<float>(absl::MakeSpan(outputScalar).subspan(1), start, fillValue);
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::linearRamp, true);

View file

@ -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 <absl/types/span.h>
@ -16,21 +17,6 @@
#include <iostream>
using namespace Catch::literals;
template <class Type, size_t N>
inline bool approxEqual(const std::array<Type, N>& lhs, const std::array<Type, N>& 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 <class Type, size_t N>
void testFilter(const std::array<Type, N>& input, const std::array<Type, N>& expectedLow, const std::array<Type, N>& expectedHigh, Type gain)
{
@ -43,19 +29,19 @@ void testFilter(const std::array<Type, N>& input, const std::array<Type, N>& exp
sfz::OnePoleFilter<Type> 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<float, 64> floatInput01 = {

BIN
tests/TestFiles/kick.flac Executable file

Binary file not shown.

BIN
tests/TestFiles/kick.wv Normal file

Binary file not shown.

View file

@ -12,6 +12,8 @@
#include "sfizz/Messaging.h"
#include "catch2/catch.hpp"
#include "sfizz/modulations/ModKey.h"
#include <iostream>
#include <iomanip>
class RegionCCView {
public:
@ -148,18 +150,52 @@ std::string createDefaultGraph(std::vector<std::string> lines, int numRegions =
std::string createModulationDotGraph(std::vector<std::string> lines);
template <class Type>
inline bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> rhs, Type eps = 1e-3)
bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> 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<const Type> 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;
}
/**