From 4bf2a16f05e385325c8f832cb61d33f86b0ab3ad Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 25 Jul 2020 15:08:32 +0200 Subject: [PATCH 1/7] More generic utility to extract audio file metadata --- dpf.mk | 2 +- src/CMakeLists.txt | 4 +- src/sfizz/FileInstrument.cpp | 143 ------------------- src/sfizz/FileInstrument.h | 24 ---- src/sfizz/FileMetadata.cpp | 260 +++++++++++++++++++++++++++++++++++ src/sfizz/FileMetadata.h | 45 ++++++ src/sfizz/FilePool.cpp | 13 +- tests/FileInstrument.cpp | 17 ++- 8 files changed, 326 insertions(+), 182 deletions(-) delete mode 100644 src/sfizz/FileInstrument.cpp delete mode 100644 src/sfizz/FileInstrument.h create mode 100644 src/sfizz/FileMetadata.cpp create mode 100644 src/sfizz/FileMetadata.h diff --git a/dpf.mk b/dpf.mk index 7dd64e45..51e19b50 100644 --- a/dpf.mk +++ b/dpf.mk @@ -77,7 +77,7 @@ SFIZZ_SOURCES = \ src/sfizz/effects/Width.cpp \ src/sfizz/EQPool.cpp \ src/sfizz/FileId.cpp \ - src/sfizz/FileInstrument.cpp \ + src/sfizz/FileMetadata.cpp \ src/sfizz/FilePool.cpp \ src/sfizz/FilterPool.cpp \ src/sfizz/FloatEnvelopes.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a3e8b802..2c963d78 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,7 +44,7 @@ set (SFIZZ_HEADERS sfizz/EQDescription.h sfizz/EQPool.h sfizz/FileId.h - sfizz/FileInstrument.h + sfizz/FileMetadata.h sfizz/FilePool.h sfizz/FilterDescription.h sfizz/FilterPool.h @@ -93,7 +93,7 @@ set (SFIZZ_SOURCES sfizz/Synth.cpp sfizz/FileId.cpp sfizz/FilePool.cpp - sfizz/FileInstrument.cpp + sfizz/FileMetadata.cpp sfizz/AudioReader.cpp sfizz/FilterPool.cpp sfizz/EQPool.cpp diff --git a/src/sfizz/FileInstrument.cpp b/src/sfizz/FileInstrument.cpp deleted file mode 100644 index 8f4885fe..00000000 --- a/src/sfizz/FileInstrument.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// 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 "FileInstrument.h" -#include "absl/types/span.h" -#include -#include -#include - -namespace sfz { - -// Utility: file cleanup - -struct FILE_deleter { - void operator()(FILE* x) const noexcept { fclose(x); } -}; -typedef std::unique_ptr FILE_u; - -// Utility: binary file IO - -static bool fread_u32le(FILE* stream, uint32_t& value) -{ - uint8_t bytes[4]; - if (fread(bytes, 4, 1, stream) != 1) - return false; - value = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); - return true; -} - -static bool fread_u32be(FILE* stream, uint32_t& value) -{ - uint8_t bytes[4]; - if (fread(bytes, 4, 1, stream) != 1) - return false; - value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; - return true; -} - -/** - * @brief Extract the instrument data from the RIFF sampler block - * - * @param data sampler block data, except the 8 leading bytes 'smpl' + size - * @param ins destination instrument - */ -static bool extractSamplerChunkInstrument( - absl::Span data, SF_INSTRUMENT& ins) -{ - auto extractU32 = [&data](const uint32_t offset) -> uint32_t { - const uint8_t* bytes = &data[offset]; - if (bytes + 4 > data.end()) - return 0; - return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); - }; - - ins.gain = 1; - ins.basenote = extractU32(0x14 - 8); - ins.detune = static_cast( // Q0,32 semitones to cents - (static_cast(extractU32(0x18 - 8)) * 100) >> 32); - ins.velocity_lo = 0; - ins.velocity_hi = 127; - ins.key_lo = 0; - ins.key_hi = 127; - - const uint32_t numLoops = std::min(16u, extractU32(0x24 - 8)); - ins.loop_count = numLoops; - - for (uint32_t i = 0; i < numLoops; ++i) { - const uint32_t loopOffset = 0x2c - 8 + i * 24; - - switch (extractU32(loopOffset + 0x04)) { - default: - ins.loops[i].mode = SF_LOOP_NONE; - break; - case 0: - ins.loops[i].mode = SF_LOOP_FORWARD; - break; - case 1: - ins.loops[i].mode = SF_LOOP_ALTERNATING; - break; - case 2: - ins.loops[i].mode = SF_LOOP_BACKWARD; - break; - } - - ins.loops[i].start = extractU32(loopOffset + 0x08); - ins.loops[i].end = extractU32(loopOffset + 0x0c) + 1; - ins.loops[i].count = extractU32(loopOffset + 0x14); - } - - return true; -} - -bool FileInstruments::extractFromFlac(const fs::path& path, SF_INSTRUMENT& ins) -{ - memset(&ins, 0, sizeof(SF_INSTRUMENT)); - -#if !defined(_WIN32) - FILE_u stream(fopen(path.c_str(), "rb")); -#else - FILE_u stream(_wfopen(path.wstring().c_str(), L"rb")); -#endif - - char magic[4]; - if (fread(magic, 4, 1, stream.get()) != 1 || memcmp(magic, "fLaC", 4) != 0) - return false; - - uint32_t header = 0; - while (((header >> 31) & 1) != 1) { - if (!fread_u32be(stream.get(), header)) - return false; - - const uint32_t block_type = (header >> 24) & 0x7f; - const uint32_t block_size = header & ((1 << 24) - 1); - - const off_t off_start_block = ftell(stream.get()); - const off_t off_next_block = off_start_block + block_size; - - if (block_type == 2) { // APPLICATION block - char blockId[4]; - char riffId[4]; - uint32_t riffChunkSize; - if (fread(blockId, 4, 1, stream.get()) == 1 && memcmp(blockId, "riff", 4) == 0 && - fread(riffId, 4, 1, stream.get()) == 1 && memcmp(riffId, "smpl", 4) == 0 && - fread_u32le(stream.get(), riffChunkSize) && riffChunkSize <= block_size - 12) - { - std::unique_ptr chunk { new uint8_t[riffChunkSize] }; - if (fread(chunk.get(), riffChunkSize, 1, stream.get()) == 1) - return extractSamplerChunkInstrument( - { chunk.get(), riffChunkSize }, ins); - } - } - - if (fseek(stream.get(), off_next_block, SEEK_SET) != 0) - return false; - } - - return false; -} - -} // namespace sfz diff --git a/src/sfizz/FileInstrument.h b/src/sfizz/FileInstrument.h deleted file mode 100644 index 1c75e988..00000000 --- a/src/sfizz/FileInstrument.h +++ /dev/null @@ -1,24 +0,0 @@ -// 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 "ghc/fs_std.hpp" -#include - -namespace sfz { - -class FileInstruments { -public: -/** - * @brief Extract the loop information of a FLAC file, using RIFF foreign data. - * - * This feature lacks support in libsndfile (as of version 1.0.28). - * see https://github.com/erikd/libsndfile/issues/59 - */ -static bool extractFromFlac(const fs::path& path, SF_INSTRUMENT& ins); -}; - -} // namespace sfz diff --git a/src/sfizz/FileMetadata.cpp b/src/sfizz/FileMetadata.cpp new file mode 100644 index 00000000..6af27921 --- /dev/null +++ b/src/sfizz/FileMetadata.cpp @@ -0,0 +1,260 @@ +// 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 "FileMetadata.h" +#include +#include + +namespace sfz { + +// Utility: file cleanup + +struct FILE_deleter { + void operator()(FILE* x) const noexcept { fclose(x); } +}; +typedef std::unique_ptr FILE_u; + +// Utility: binary file IO + +static bool fread_u32le(FILE* stream, uint32_t& value) +{ + uint8_t bytes[4]; + if (fread(bytes, 4, 1, stream) != 1) + return false; + value = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); + return true; +} + +static bool fread_u32be(FILE* stream, uint32_t& value) +{ + uint8_t bytes[4]; + if (fread(bytes, 4, 1, stream) != 1) + return false; + value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; + return true; +} + +//------------------------------------------------------------------------------ + +struct FileMetadataReader::Impl { + FILE_u stream_; + std::vector riffChunks_; + + bool openFlac(); + bool openRiff(); +}; + +FileMetadataReader::FileMetadataReader() + : impl_(new Impl) +{ + impl_->riffChunks_.reserve(16); +} + +FileMetadataReader::~FileMetadataReader() +{ +} + +bool FileMetadataReader::open(const fs::path& path) +{ + close(); + +#if !defined(_WIN32) + FILE* stream = fopen(path.c_str(), "rb"); +#else + FILE* stream = _wfopen(path.wstring().c_str(), L"rb"); +#endif + + if (!stream) + return false; + + impl_->stream_.reset(stream); + + char magic[4]; + size_t count = fread(magic, 1, sizeof(magic), stream); + + if (count >= 4 && !memcmp(magic, "fLaC", 4)) { + if (!impl_->openFlac()) { + close(); + return false; + } + } + else if (count >= 4 && !memcmp(magic, "RIFF", 4)) { + if (!impl_->openRiff()) { + close(); + return false; + } + } + + return true; +} + +void FileMetadataReader::close() +{ + impl_->stream_.reset(); + impl_->riffChunks_.clear(); +} + +bool FileMetadataReader::Impl::openFlac() +{ + FILE* stream = stream_.get(); + std::vector& riffChunks = riffChunks_; + + if (fseek(stream, 4, SEEK_SET) != 0) + return false; + + uint32_t header = 0; + while (((header >> 31) & 1) != 1) { + if (!fread_u32be(stream, header)) + return false; + + const uint32_t blockType = (header >> 24) & 0x7f; + const uint32_t blockSize = header & ((1 << 24) - 1); + + const off_t offStartBlock = ftell(stream); + const off_t offNextBlock = offStartBlock + blockSize; + + if (blockType == 2) { // APPLICATION block + char blockId[4]; + char riffId[4]; + uint32_t riffChunkSize; + if (fread(blockId, 4, 1, stream) == 1 && memcmp(blockId, "riff", 4) == 0 && + fread(riffId, 4, 1, stream) == 1 && + fread_u32le(stream, riffChunkSize) && riffChunkSize <= blockSize - 12) + { + RiffChunkInfo info; + info.index = riffChunks.size(); + info.fileOffset = ftell(stream); + memcpy(info.id.data(), riffId, 4); + info.length = riffChunkSize; + riffChunks.push_back(info); + } + } + + if (fseek(stream, offNextBlock, SEEK_SET) != 0) + return false; + } + + return true; +} + +bool FileMetadataReader::Impl::openRiff() +{ + FILE* stream = stream_.get(); + std::vector& riffChunks = riffChunks_; + + if (fseek(stream, 12, SEEK_SET) != 0) + return false; + + char riffId[4]; + uint32_t riffChunkSize; + while (fread(riffId, 4, 1, stream) == 1 && fread_u32le(stream, riffChunkSize)) { + RiffChunkInfo info; + info.index = riffChunks.size(); + info.fileOffset = ftell(stream); + memcpy(info.id.data(), riffId, 4); + info.length = riffChunkSize; + riffChunks.push_back(info); + + if (fseek(stream, riffChunkSize, SEEK_CUR) != 0) + return false; + } + + return true; +} + +size_t FileMetadataReader::riffChunkCount() const +{ + return impl_->riffChunks_.size(); +} + +const RiffChunkInfo* FileMetadataReader::riffChunk(size_t index) const +{ + const std::vector& riffChunks = impl_->riffChunks_; + return (index < riffChunks.size()) ? &riffChunks[index] : nullptr; +} + +const RiffChunkInfo* FileMetadataReader::riffChunkById(RiffChunkId id) const +{ + for (const RiffChunkInfo& riff : impl_->riffChunks_) { + if (riff.id == id) + return &riff; + } + return nullptr; +} + +size_t FileMetadataReader::readRiffData(size_t index, void* buffer, size_t count) +{ + const RiffChunkInfo* riff = riffChunk(index); + if (!riff) + return 0; + + count = (count < riff->length) ? count : riff->length; + + FILE* stream = impl_->stream_.get(); + if (fseek(stream, riff->fileOffset, SEEK_SET) != 0) + return 0; + + return fread(buffer, 1, count, stream); +} + +bool FileMetadataReader::extractRiffInstrument(SF_INSTRUMENT& ins) +{ + const RiffChunkInfo* riff = riffChunkById(RiffChunkId{'s', 'm', 'p', 'l'}); + if (!riff) + return 0; + + constexpr uint32_t maxLoops = 16; + constexpr uint32_t maxChunkSize = 9 * 4 + maxLoops * 6 * 4; + + uint8_t data[maxChunkSize]; + uint32_t length = readRiffData(riff->index, data, sizeof(data)); + + auto extractU32 = [&data, length](const uint32_t offset) -> uint32_t { + const uint8_t* bytes = &data[offset]; + if (bytes + 4 > data + length) + return 0; + return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); + }; + + ins.gain = 1; + ins.basenote = extractU32(0x14 - 8); + ins.detune = static_cast( // Q0,32 semitones to cents + (static_cast(extractU32(0x18 - 8)) * 100) >> 32); + ins.velocity_lo = 0; + ins.velocity_hi = 127; + ins.key_lo = 0; + ins.key_hi = 127; + + const uint32_t numLoops = std::min(maxLoops, extractU32(0x24 - 8)); + ins.loop_count = numLoops; + + for (uint32_t i = 0; i < numLoops; ++i) { + const uint32_t loopOffset = 0x2c - 8 + i * 24; + + switch (extractU32(loopOffset + 0x04)) { + default: + ins.loops[i].mode = SF_LOOP_NONE; + break; + case 0: + ins.loops[i].mode = SF_LOOP_FORWARD; + break; + case 1: + ins.loops[i].mode = SF_LOOP_ALTERNATING; + break; + case 2: + ins.loops[i].mode = SF_LOOP_BACKWARD; + break; + } + + ins.loops[i].start = extractU32(loopOffset + 0x08); + ins.loops[i].end = extractU32(loopOffset + 0x0c) + 1; + ins.loops[i].count = extractU32(loopOffset + 0x14); + } + + return true; +} + +} // namespace sfz diff --git a/src/sfizz/FileMetadata.h b/src/sfizz/FileMetadata.h new file mode 100644 index 00000000..f703bb76 --- /dev/null +++ b/src/sfizz/FileMetadata.h @@ -0,0 +1,45 @@ +// 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 "ghc/fs_std.hpp" +#include +#include +#include +#include + +namespace sfz { + +typedef std::array RiffChunkId; + +struct RiffChunkInfo { + size_t index; + off_t fileOffset; + RiffChunkId id; + uint32_t length; +}; + +class FileMetadataReader { +public: + FileMetadataReader(); + ~FileMetadataReader(); + + bool open(const fs::path& path); + void close(); + + size_t riffChunkCount() const; + const RiffChunkInfo* riffChunk(size_t index) const; + const RiffChunkInfo* riffChunkById(RiffChunkId id) const; + size_t readRiffData(size_t index, void* buffer, size_t count); + + bool extractRiffInstrument(SF_INSTRUMENT& ins); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace sfz diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 57e0e691..ab013a90 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -25,7 +25,7 @@ #include "FilePool.h" #include "AudioReader.h" -#include "FileInstrument.h" +#include "FileMetadata.h" #include "Buffer.h" #include "AudioBuffer.h" #include "AudioSpan.h" @@ -218,11 +218,12 @@ absl::optional sfz::FilePool::getFileInformation(const Fil SF_INSTRUMENT instrumentInfo {}; - const int sndFormat = reader->format(); - if ((sndFormat & SF_FORMAT_TYPEMASK) == SF_FORMAT_FLAC) - sfz::FileInstruments::extractFromFlac(file, instrumentInfo); - else - reader->getInstrument(&instrumentInfo); + if (!reader->getInstrument(&instrumentInfo)) { + // if no instrument, then try extracting from embedded RIFF chunks (flac) + FileMetadataReader reader; + if (reader.open(file)) + reader.extractRiffInstrument(instrumentInfo); + } if (!fileId.isReverse()) { if (instrumentInfo.loop_count > 0) { diff --git a/tests/FileInstrument.cpp b/tests/FileInstrument.cpp index b6e21c3d..98643be2 100644 --- a/tests/FileInstrument.cpp +++ b/tests/FileInstrument.cpp @@ -4,7 +4,7 @@ // 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 "sfizz/FileInstrument.h" +#include "sfizz/FileMetadata.h" #include "absl/strings/string_view.h" #include #include @@ -49,13 +49,13 @@ static void usage(const char* argv0) stderr, "Usage: %s [-s|-f] \n" " -s: extract the instrument using libsndfile\n" - " -f: extract the instrument using FLAC metadata\n", + " -f: extract the instrument using RIFF metadata\n", argv0); } enum FileMethod { kMethodSndfile, - kMethodFlac, + kMethodRiff, }; int main(int argc, char *argv[]) @@ -71,7 +71,7 @@ int main(int argc, char *argv[]) if (flag == "-s") method = kMethodSndfile; else if (flag == "-f") - method = kMethodFlac; + method = kMethodRiff; else { usage(argv[0]); return 1; @@ -85,8 +85,13 @@ int main(int argc, char *argv[]) SF_INSTRUMENT ins {}; - if (method == kMethodFlac) { - if (!sfz::FileInstruments::extractFromFlac(path, ins)) { + if (method == kMethodRiff) { + sfz::FileMetadataReader reader; + if (!reader.open(path)) { + fprintf(stderr, "Cannot open file\n"); + return 1; + } + if (!reader.extractRiffInstrument(ins)) { fprintf(stderr, "Cannot get instrument\n"); return 1; } From d6b98d531da2d1d6d4adeb1b157344b783e5236a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 25 Jul 2020 21:39:27 +0200 Subject: [PATCH 2/7] Add extraction of wavetable info --- src/sfizz/FileMetadata.cpp | 141 +++++++++++++++++++++++++++++++++++-- src/sfizz/FileMetadata.h | 45 ++++++++++++ 2 files changed, 180 insertions(+), 6 deletions(-) diff --git a/src/sfizz/FileMetadata.cpp b/src/sfizz/FileMetadata.cpp index 6af27921..87379796 100644 --- a/src/sfizz/FileMetadata.cpp +++ b/src/sfizz/FileMetadata.cpp @@ -4,7 +4,16 @@ // 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 +// Note: Based on some format research from Surge synthesizer +// made by Paul Walker and Mario Kruselj +// cf. Surge src/common/WavSupport.cpp + #include "FileMetadata.h" +#include +#include +#include +#include +#include #include #include @@ -19,12 +28,22 @@ typedef std::unique_ptr FILE_u; // Utility: binary file IO +static uint32_t u32le(const uint8_t *bytes) +{ + return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); +} + +static uint32_t u32be(const uint8_t *bytes) +{ + return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; +} + static bool fread_u32le(FILE* stream, uint32_t& value) { uint8_t bytes[4]; if (fread(bytes, 4, 1, stream) != 1) return false; - value = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); + value = u32le(bytes); return true; } @@ -33,7 +52,7 @@ static bool fread_u32be(FILE* stream, uint32_t& value) uint8_t bytes[4]; if (fread(bytes, 4, 1, stream) != 1) return false; - value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; + value = u32be(bytes); return true; } @@ -45,6 +64,14 @@ struct FileMetadataReader::Impl { bool openFlac(); bool openRiff(); + + bool extractClmWavetable(WavetableInfo &wt); + bool extractSurgeWavetable(WavetableInfo &wt); + bool extractUheWavetable(WavetableInfo &wt); + + const RiffChunkInfo* riffChunk(size_t index) const; + const RiffChunkInfo* riffChunkById(RiffChunkId id) const; + size_t readRiffData(size_t index, void* buffer, size_t count); }; FileMetadataReader::FileMetadataReader() @@ -172,13 +199,23 @@ size_t FileMetadataReader::riffChunkCount() const const RiffChunkInfo* FileMetadataReader::riffChunk(size_t index) const { - const std::vector& riffChunks = impl_->riffChunks_; + return impl_->riffChunk(index); +} + +const RiffChunkInfo* FileMetadataReader::Impl::riffChunk(size_t index) const +{ + const std::vector& riffChunks = riffChunks_; return (index < riffChunks.size()) ? &riffChunks[index] : nullptr; } const RiffChunkInfo* FileMetadataReader::riffChunkById(RiffChunkId id) const { - for (const RiffChunkInfo& riff : impl_->riffChunks_) { + return impl_->riffChunkById(id); +} + +const RiffChunkInfo* FileMetadataReader::Impl::riffChunkById(RiffChunkId id) const +{ + for (const RiffChunkInfo& riff : riffChunks_) { if (riff.id == id) return &riff; } @@ -186,6 +223,11 @@ const RiffChunkInfo* FileMetadataReader::riffChunkById(RiffChunkId id) const } size_t FileMetadataReader::readRiffData(size_t index, void* buffer, size_t count) +{ + return impl_->readRiffData(index, buffer, count); +} + +size_t FileMetadataReader::Impl::readRiffData(size_t index, void* buffer, size_t count) { const RiffChunkInfo* riff = riffChunk(index); if (!riff) @@ -193,7 +235,7 @@ size_t FileMetadataReader::readRiffData(size_t index, void* buffer, size_t count count = (count < riff->length) ? count : riff->length; - FILE* stream = impl_->stream_.get(); + FILE* stream = stream_.get(); if (fseek(stream, riff->fileOffset, SEEK_SET) != 0) return 0; @@ -204,7 +246,7 @@ bool FileMetadataReader::extractRiffInstrument(SF_INSTRUMENT& ins) { const RiffChunkInfo* riff = riffChunkById(RiffChunkId{'s', 'm', 'p', 'l'}); if (!riff) - return 0; + return false; constexpr uint32_t maxLoops = 16; constexpr uint32_t maxChunkSize = 9 * 4 + maxLoops * 6 * 4; @@ -257,4 +299,91 @@ bool FileMetadataReader::extractRiffInstrument(SF_INSTRUMENT& ins) return true; } +bool FileMetadataReader::extractWavetableInfo(WavetableInfo& wt) +{ + if (impl_->extractClmWavetable(wt)) + return true; + + if (impl_->extractSurgeWavetable(wt)) + return true; + + if (impl_->extractUheWavetable(wt)) + return true; + + // there also exists a method based on cue chunks used in Surge + // files possibly already covered by the Native case + // otherwise do later when I will have a few samples at hand + + return false; +} + +bool FileMetadataReader::Impl::extractClmWavetable(WavetableInfo &wt) +{ + const RiffChunkInfo* clm = riffChunkById(RiffChunkId{'c', 'l', 'm', ' '}); + if (!clm) + return false; + + char data[16] {}; + if (readRiffData(clm->index, data, sizeof(data)) != sizeof(data)) + return false; + + // 0-2 are "" + // 3-6 is the decimal table size written in ASCII (most likely "2048") + // 7 is a space character + // 8-15 are flags as ASCII digit characters (eg. "01000000") + // 16-end "wavetable ()" + + if (!absl::SimpleAtoi(absl::string_view(data + 3, 4), &wt.tableSize)) + return false; + + int cti = static_cast(data[8]); + if (cti >= '0' && cti <= '4') + cti -= '0'; + else + cti = 0; // unknown interpolation + wt.crossTableInterpolation = cti; + + wt.oneShot = false; + + return true; +} + +bool FileMetadataReader::Impl::extractSurgeWavetable(WavetableInfo &wt) +{ + const RiffChunkInfo* srge; + + if ((srge = riffChunkById(RiffChunkId{'s', 'r', 'g', 'e'}))) + wt.oneShot = false; + else if ((srge = riffChunkById(RiffChunkId{'s', 'r', 'g', 'o'}))) + wt.oneShot = true; + else + return false; + + uint8_t data[8]; + if (readRiffData(srge->index, data, sizeof(data)) != sizeof(data)) + return false; + + //const uint32_t version = u32le(data); + wt.tableSize = u32le(data + 4); + + wt.crossTableInterpolation = 0; + + return true; +} + +bool FileMetadataReader::Impl::extractUheWavetable(WavetableInfo &wt) +{ + const RiffChunkInfo* uhwt = riffChunkById(RiffChunkId{'u', 'h', 'W', 'T'}); + if (!uhwt) + return false; + + // u-he Hive: no idea what is inside this one, 2048 assumed + + wt.tableSize = 2048; + wt.crossTableInterpolation = 0; + wt.oneShot = false; + + return true; +} + } // namespace sfz diff --git a/src/sfizz/FileMetadata.h b/src/sfizz/FileMetadata.h index f703bb76..f4e30bd7 100644 --- a/src/sfizz/FileMetadata.h +++ b/src/sfizz/FileMetadata.h @@ -22,21 +22,66 @@ struct RiffChunkInfo { uint32_t length; }; +struct WavetableInfo { + /** + @brief Size of each successive table in the file + */ + uint32_t tableSize; + /** + * @brief Mode of interpolation between multiple tables + * + * 0: none, 1: crossfade, 2: spectral, + * 3: spectral with fundamental phase set to zero + * 4: spectral with all phases set to zero + */ + int crossTableInterpolation; + /** + * @brief Whether the wavetable is one-shot (does not cycle) + */ + bool oneShot; +}; + class FileMetadataReader { public: FileMetadataReader(); ~FileMetadataReader(); + /** + * @brief Open an audio file of supported format and read internal structures + */ bool open(const fs::path& path); + /** + * @brief Close an audio file + */ void close(); + /** + * @brief Get the number of RIFF chunks in the file + */ size_t riffChunkCount() const; + /** + * @brief Get the information regarding the n-th RIFF chunk + */ const RiffChunkInfo* riffChunk(size_t index) const; + /** + * @brief Get the information regarding the RIFF chunk of given identifier + */ const RiffChunkInfo* riffChunkById(RiffChunkId id) const; + /** + * @brief Read the RIFF data up to the size given (header not included) + */ size_t readRiffData(size_t index, void* buffer, size_t count); + /** + * @brief Extract the RIFF 'smpl' data and convert it to sndfile instrument + */ bool extractRiffInstrument(SF_INSTRUMENT& ins); + /** + * @brief Extract the wavetable information from various relevant RIFF chunks + */ + bool extractWavetableInfo(WavetableInfo& wt); + private: struct Impl; std::unique_ptr impl_; From 2eb12daf0cb41789c23ebbb69741131c2a038c39 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sun, 9 Aug 2020 22:36:57 +0200 Subject: [PATCH 3/7] Add a test for surge and clm wavetable files --- tests/TestFiles/wavetables/clm.wav | Bin 0 -> 2152 bytes tests/TestFiles/wavetables/surge.wav | Bin 0 -> 1080 bytes tests/WavetablesT.cpp | 32 +++++++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 tests/TestFiles/wavetables/clm.wav create mode 100644 tests/TestFiles/wavetables/surge.wav diff --git a/tests/TestFiles/wavetables/clm.wav b/tests/TestFiles/wavetables/clm.wav new file mode 100644 index 0000000000000000000000000000000000000000..07f44c4230d8da4269a8d0bfba400798c132a34e GIT binary patch literal 2152 zcmXBUVNBF@9LMorfOeA64ahNO?qY^Sjt)|W#{GY%A)ZbhsUtFDha%n#kmMpllH0V6 z2d=Tjnikcqxn+$tY(8KG|KF`}QDYM~9x(F(53IRziJm~#FwwM3i@&Et+-|y$6 z?%!2k9}gJw%C^QGZ5_!_zA+}-JYwobjR{`KGC3y1H*GCPlQ~>XmNBg@$ri&C`Wegp zsW$vyEVAOu4AahrM4vN_%tt zqh5L4xcB6rKX^Z%zV4;&-1Odh;WzL1Lw|W2USIMeBiZ)je**UAmONXYD6pA-u>Ct{v)!K9V*mX*V!P(I+3edpZ2pa1HtVy!HnR6+TlvdD zdmz?ii-%vcEBt1A;aZD*^{ZAp|Hg6q#i|pw`E0xWbiUIDR=sI=7Ixdm9whCf-}c!1 z2YT%%KfYuA!qc|w#eO@w^K%0sy*ALXRHl$-KX zj>=QHDqrQSyp_B1R}bn#y{I4cq`uUf`cseUQ@yHR^{l?tdsXG*&v2dK^CaI-^7~2d zo8*3<=X{>`ImhQbpL2cA_j!-c`+VN(^M0Q>eCF|)%V$2HIeq4J%+H$6k*8 z9D6$Ub?oif-;skO4@WMJd>lDB@^a+n$j_0ZBTq-Jj(i3N-_^K`Dx*L(Cny;two z9GXXSX+F)Vc{R7@*B;tOduc!IseQG#_E!$dL%Ap)<)plnoAOhR%2T;2U*)X4mAmp+ z59&j`s2}yDzSNugQ;+IXy{cdJtiB)q>tFt}mK>0Qi5L??6mg_5gfzyG!7LWA2;&(O zKoCU;p$uWvAc`0o(1bV==t3`2IEO);#}I~b0V7D`5-wvDW4MZOOdx|POk)PKn8Q47 zVF9;s2Y0cEd$^A!82%5)LJn3UfLyFb9)ie6A=aP>#aN4V2%!Y)QHnB@V#x^N0f^q?1g=*JnP z99Q(C554F?5~t9GPPF3$5@QRU7h{8iHwxR~r z*n}#CQGtypM;S`79wi829oC{4MOcGEo@2RvKR%K!iX literal 0 HcmV?d00001 diff --git a/tests/TestFiles/wavetables/surge.wav b/tests/TestFiles/wavetables/surge.wav new file mode 100644 index 0000000000000000000000000000000000000000..18ff6c51d95d2e74c2be5ee4be4e7b0e94a9ecf2 GIT binary patch literal 1080 zcmXAndsGty7{%|WfYK<$B8Yg1sEsV*Q9O#m8KNXwiXagY*g)bD1k>>t+kk=Z?ANKBKg zSO$Ph3f2OpQG`WUhOErp2}^JF|A#nU62Gw6#8|a>lSQ(n#;Vb}XZeKfyuHBD!NqN* zaP=CY%UT|8)WXXm^@joI!BeAA{Fg5+tZr0BXc4nHoZ5^Ej2BIi@greUvexD%@vV=hmRGmQcryxxoV>yP(KGO~4u7F`t{BHXbf(*9e8H-Bvod z2ps3_Cv1C`H(G0~k}Wq`tTHWdexYst&<)9K#C89x&QnV+84VXN==CF`qq_0oYeVLP zu6-fBlJ16cSI&BOl(bK_g`Mtd7M<#EO3(@#I_l*ox74^Fx2?85W?$uXB>HegrM|+i z{KP@$1KRzYH49~(>dI2tK52=nxK^braw?J*8VbY(bINLEv{I-PD9L1@iK~@!N^yaq zKw9Wjq${dbsfwi~vVE1Mo$7_M%^K}~=L07W`jzV|Dk`H7dmXW_vOZ>8?S6bqjr?Rs zy`UjM+utNQ)zut!da|vg-Miz;*@knH?vP&BKJ&qAL*v7vx{*=6{^A9rVaX-uDgUbx z*Cn%uZnVwc`obbCh8fIZfhAZ9OWXzltZ+N-fVJs3Hdu~3aTjcHH|$`Kd*Fb3;Rq)< z!v!ngij{D~Dy+sD+y^1taX;4L0Xzr~JOod8!5cn!7>~dge(*;C9>rq_#N&7ZL0HG@ zSj6l3Ne<=>9KsuU6NmCsyqUvz3vcD8`5E5E+c}(fa0Ey4PLASdIhvp27=E5(`2~*S zc;3Y?vX~P%k#}2%ER7S6ohW68Ibbt=h>r_q^^afSZAv#QN(h+)#s^};kqqnJ=-l5}kf@-Li zPEsA!)4TK@HBcjI>3wRV59mWWMITW!wNNXarjMzOKA|(zPM^|e)Ips%i!OYQbNB*Z zq8neK2fg?j-=Giu7{DOD#Sp&3_ZY?x_z^mc;3tgYXPk!~zhDd(FpdcrFo|Dr5tnco lS1^TX{D!Oe9oKLjGx!6u_!CClz#RU @@ -54,3 +55,34 @@ TEST_CASE("[Wavetables] Octave number lookup") REQUIRE(oct == Approx(ref).margin(0.03f)); } } + +TEST_CASE("[Wavetables] Wavetable sound files: Surge") +{ + sfz::FileMetadataReader reader; + sfz::WavetableInfo wt; + + REQUIRE(reader.open("tests/TestFiles/wavetables/surge.wav")); + REQUIRE(reader.extractWavetableInfo(wt)); + + REQUIRE(wt.tableSize == 256); +} + +TEST_CASE("[Wavetables] Wavetable sound files: Clm") +{ + sfz::FileMetadataReader reader; + sfz::WavetableInfo wt; + + REQUIRE(reader.open("tests/TestFiles/wavetables/clm.wav")); + REQUIRE(reader.extractWavetableInfo(wt)); + + REQUIRE(wt.tableSize == 256); +} + +TEST_CASE("[Wavetables] Non-wavetable sound files") +{ + sfz::FileMetadataReader reader; + sfz::WavetableInfo wt; + + REQUIRE(reader.open("tests/TestFiles/snare.wav")); + REQUIRE(!reader.extractWavetableInfo(wt)); +} From 34d6ed02918620ad4437bfcd12f4e4b6d0c7c1cc Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sun, 9 Aug 2020 23:45:19 +0200 Subject: [PATCH 4/7] Add the program to display file wavetable info --- tests/CMakeLists.txt | 3 +++ tests/FileWavetable.cpp | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/FileWavetable.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 31e134c8..08623b6d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -93,6 +93,9 @@ target_link_libraries(sfizz_plot_wavetables PRIVATE sfizz::sfizz) add_executable(sfizz_file_instrument FileInstrument.cpp) target_link_libraries(sfizz_file_instrument PRIVATE sfizz::sfizz) +add_executable(sfizz_file_wavetable FileWavetable.cpp) +target_link_libraries(sfizz_file_wavetable PRIVATE sfizz::sfizz) + add_executable(sfizz_tuning Tuning.cpp) target_link_libraries(sfizz_tuning PRIVATE sfizz::sfizz) diff --git a/tests/FileWavetable.cpp b/tests/FileWavetable.cpp new file mode 100644 index 00000000..9e1fa597 --- /dev/null +++ b/tests/FileWavetable.cpp @@ -0,0 +1,52 @@ +// 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 "sfizz/FileMetadata.h" +#include "absl/strings/string_view.h" +#include + +static void printWavetable(const sfz::WavetableInfo& wt) +{ + printf("Table size: %u\n", wt.tableSize); + printf("Cross-table interpolation: %d\n", wt.crossTableInterpolation); + printf("One-shot: %d\n", wt.oneShot); +} + +static void usage(const char* argv0) +{ + fprintf( + stderr, + "Usage: %s \n", + argv0); +} + +int main(int argc, char *argv[]) +{ + fs::path path; + + if (argc == 2) + path = argv[1]; + else { + usage(argv[0]); + return 1; + } + + sfz::WavetableInfo wt {}; + + sfz::FileMetadataReader reader; + if (!reader.open(path)) { + fprintf(stderr, "Cannot open file\n"); + return 1; + } + if (!reader.extractWavetableInfo(wt)) { + fprintf(stderr, "Cannot get wavetable info\n"); + return 1; + } + + printWavetable(wt); + + return 0; +} From 71ae5943c97066e7f22a5d76c49929da48ef01bf Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 10 Aug 2020 00:01:21 +0200 Subject: [PATCH 5/7] Extract the table size off U-he metadata --- src/sfizz/FileMetadata.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/sfizz/FileMetadata.cpp b/src/sfizz/FileMetadata.cpp index 87379796..a03069c8 100644 --- a/src/sfizz/FileMetadata.cpp +++ b/src/sfizz/FileMetadata.cpp @@ -377,9 +377,16 @@ bool FileMetadataReader::Impl::extractUheWavetable(WavetableInfo &wt) if (!uhwt) return false; - // u-he Hive: no idea what is inside this one, 2048 assumed + // zeros (chunk version?), 4 bytes LE + // number of tables, 4 bytes LE + // table size, 4 bytes LE + + uint8_t data[12]; + if (readRiffData(uhwt->index, data, sizeof(data)) != sizeof(data)) + return false; + + wt.tableSize = u32le(data + 8); - wt.tableSize = 2048; wt.crossTableInterpolation = 0; wt.oneShot = false; From c8c60be0dda175ae78f60c85f2e670e9d4e14ce6 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 10 Aug 2020 18:30:29 +0200 Subject: [PATCH 6/7] Avoid shadowing an existing variable --- src/sfizz/FilePool.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index ab013a90..0778026e 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -220,9 +220,9 @@ absl::optional sfz::FilePool::getFileInformation(const Fil if (!reader->getInstrument(&instrumentInfo)) { // if no instrument, then try extracting from embedded RIFF chunks (flac) - FileMetadataReader reader; - if (reader.open(file)) - reader.extractRiffInstrument(instrumentInfo); + FileMetadataReader mdReader; + if (mdReader.open(file)) + mdReader.extractRiffInstrument(instrumentInfo); } if (!fileId.isReverse()) { From 23107ee17d329be97c4b1fea04a12b4c7395ac9f Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 10 Aug 2020 19:01:30 +0200 Subject: [PATCH 7/7] Rewrite the access function riffChunk in one line --- src/sfizz/FileMetadata.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sfizz/FileMetadata.cpp b/src/sfizz/FileMetadata.cpp index a03069c8..e57f70d5 100644 --- a/src/sfizz/FileMetadata.cpp +++ b/src/sfizz/FileMetadata.cpp @@ -204,8 +204,7 @@ const RiffChunkInfo* FileMetadataReader::riffChunk(size_t index) const const RiffChunkInfo* FileMetadataReader::Impl::riffChunk(size_t index) const { - const std::vector& riffChunks = riffChunks_; - return (index < riffChunks.size()) ? &riffChunks[index] : nullptr; + return (index < riffChunks_.size()) ? &riffChunks_[index] : nullptr; } const RiffChunkInfo* FileMetadataReader::riffChunkById(RiffChunkId id) const