From d6b98d531da2d1d6d4adeb1b157344b783e5236a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 25 Jul 2020 21:39:27 +0200 Subject: [PATCH] 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_;