commit
95bf4fc480
13 changed files with 593 additions and 182 deletions
2
dpf.mk
2
dpf.mk
|
|
@ -86,7 +86,7 @@ SFIZZ_SOURCES = \
|
||||||
src/sfizz/effects/Width.cpp \
|
src/sfizz/effects/Width.cpp \
|
||||||
src/sfizz/EQPool.cpp \
|
src/sfizz/EQPool.cpp \
|
||||||
src/sfizz/FileId.cpp \
|
src/sfizz/FileId.cpp \
|
||||||
src/sfizz/FileInstrument.cpp \
|
src/sfizz/FileMetadata.cpp \
|
||||||
src/sfizz/FilePool.cpp \
|
src/sfizz/FilePool.cpp \
|
||||||
src/sfizz/FilterPool.cpp \
|
src/sfizz/FilterPool.cpp \
|
||||||
src/sfizz/FloatEnvelopes.cpp \
|
src/sfizz/FloatEnvelopes.cpp \
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ set (SFIZZ_HEADERS
|
||||||
sfizz/EQDescription.h
|
sfizz/EQDescription.h
|
||||||
sfizz/EQPool.h
|
sfizz/EQPool.h
|
||||||
sfizz/FileId.h
|
sfizz/FileId.h
|
||||||
sfizz/FileInstrument.h
|
sfizz/FileMetadata.h
|
||||||
sfizz/FilePool.h
|
sfizz/FilePool.h
|
||||||
sfizz/FilterDescription.h
|
sfizz/FilterDescription.h
|
||||||
sfizz/FilterPool.h
|
sfizz/FilterPool.h
|
||||||
|
|
@ -110,7 +110,7 @@ set (SFIZZ_SOURCES
|
||||||
sfizz/Synth.cpp
|
sfizz/Synth.cpp
|
||||||
sfizz/FileId.cpp
|
sfizz/FileId.cpp
|
||||||
sfizz/FilePool.cpp
|
sfizz/FilePool.cpp
|
||||||
sfizz/FileInstrument.cpp
|
sfizz/FileMetadata.cpp
|
||||||
sfizz/AudioReader.cpp
|
sfizz/AudioReader.cpp
|
||||||
sfizz/FilterPool.cpp
|
sfizz/FilterPool.cpp
|
||||||
sfizz/EQPool.cpp
|
sfizz/EQPool.cpp
|
||||||
|
|
|
||||||
|
|
@ -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 <memory>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace sfz {
|
|
||||||
|
|
||||||
// Utility: file cleanup
|
|
||||||
|
|
||||||
struct FILE_deleter {
|
|
||||||
void operator()(FILE* x) const noexcept { fclose(x); }
|
|
||||||
};
|
|
||||||
typedef std::unique_ptr<FILE, FILE_deleter> 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<const uint8_t> 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<unsigned char>( // Q0,32 semitones to cents
|
|
||||||
(static_cast<uint64_t>(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<uint8_t[]> 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
|
|
||||||
|
|
@ -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 <sndfile.h>
|
|
||||||
|
|
||||||
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
|
|
||||||
395
src/sfizz/FileMetadata.cpp
Normal file
395
src/sfizz/FileMetadata.cpp
Normal file
|
|
@ -0,0 +1,395 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// 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 <absl/strings/ascii.h>
|
||||||
|
#include <absl/strings/numbers.h>
|
||||||
|
#include <absl/strings/string_view.h>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
// Utility: file cleanup
|
||||||
|
|
||||||
|
struct FILE_deleter {
|
||||||
|
void operator()(FILE* x) const noexcept { fclose(x); }
|
||||||
|
};
|
||||||
|
typedef std::unique_ptr<FILE, FILE_deleter> 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 = u32le(bytes);
|
||||||
|
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 = u32be(bytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct FileMetadataReader::Impl {
|
||||||
|
FILE_u stream_;
|
||||||
|
std::vector<RiffChunkInfo> riffChunks_;
|
||||||
|
|
||||||
|
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()
|
||||||
|
: 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<RiffChunkInfo>& 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<RiffChunkInfo>& 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
|
||||||
|
{
|
||||||
|
return impl_->riffChunk(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RiffChunkInfo* FileMetadataReader::Impl::riffChunk(size_t index) const
|
||||||
|
{
|
||||||
|
return (index < riffChunks_.size()) ? &riffChunks_[index] : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RiffChunkInfo* FileMetadataReader::riffChunkById(RiffChunkId id) const
|
||||||
|
{
|
||||||
|
return impl_->riffChunkById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RiffChunkInfo* FileMetadataReader::Impl::riffChunkById(RiffChunkId id) const
|
||||||
|
{
|
||||||
|
for (const RiffChunkInfo& riff : riffChunks_) {
|
||||||
|
if (riff.id == id)
|
||||||
|
return &riff;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
count = (count < riff->length) ? count : riff->length;
|
||||||
|
|
||||||
|
FILE* stream = 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 false;
|
||||||
|
|
||||||
|
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<unsigned char>( // Q0,32 semitones to cents
|
||||||
|
(static_cast<uint64_t>(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (<maker name>)"
|
||||||
|
|
||||||
|
if (!absl::SimpleAtoi(absl::string_view(data + 3, 4), &wt.tableSize))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int cti = static_cast<unsigned char>(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;
|
||||||
|
|
||||||
|
// 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.crossTableInterpolation = 0;
|
||||||
|
wt.oneShot = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
90
src/sfizz/FileMetadata.h
Normal file
90
src/sfizz/FileMetadata.h
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
// 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 <sndfile.h>
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
typedef std::array<char, 4> RiffChunkId;
|
||||||
|
|
||||||
|
struct RiffChunkInfo {
|
||||||
|
size_t index;
|
||||||
|
off_t fileOffset;
|
||||||
|
RiffChunkId id;
|
||||||
|
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> impl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
#include "FilePool.h"
|
#include "FilePool.h"
|
||||||
#include "AudioReader.h"
|
#include "AudioReader.h"
|
||||||
#include "FileInstrument.h"
|
#include "FileMetadata.h"
|
||||||
#include "Buffer.h"
|
#include "Buffer.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
#include "AudioSpan.h"
|
#include "AudioSpan.h"
|
||||||
|
|
@ -218,11 +218,12 @@ absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const Fil
|
||||||
|
|
||||||
SF_INSTRUMENT instrumentInfo {};
|
SF_INSTRUMENT instrumentInfo {};
|
||||||
|
|
||||||
const int sndFormat = reader->format();
|
if (!reader->getInstrument(&instrumentInfo)) {
|
||||||
if ((sndFormat & SF_FORMAT_TYPEMASK) == SF_FORMAT_FLAC)
|
// if no instrument, then try extracting from embedded RIFF chunks (flac)
|
||||||
sfz::FileInstruments::extractFromFlac(file, instrumentInfo);
|
FileMetadataReader mdReader;
|
||||||
else
|
if (mdReader.open(file))
|
||||||
reader->getInstrument(&instrumentInfo);
|
mdReader.extractRiffInstrument(instrumentInfo);
|
||||||
|
}
|
||||||
|
|
||||||
if (!fileId.isReverse()) {
|
if (!fileId.isReverse()) {
|
||||||
if (instrumentInfo.loop_count > 0) {
|
if (instrumentInfo.loop_count > 0) {
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,9 @@ target_link_libraries(sfizz_plot_wavetables PRIVATE sfizz::sfizz)
|
||||||
add_executable(sfizz_file_instrument FileInstrument.cpp)
|
add_executable(sfizz_file_instrument FileInstrument.cpp)
|
||||||
target_link_libraries(sfizz_file_instrument PRIVATE sfizz::sfizz)
|
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)
|
add_executable(sfizz_tuning Tuning.cpp)
|
||||||
target_link_libraries(sfizz_tuning PRIVATE sfizz::sfizz)
|
target_link_libraries(sfizz_tuning PRIVATE sfizz::sfizz)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
// license. You should have receive a LICENSE.md file along with the code.
|
// 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
|
// 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 "absl/strings/string_view.h"
|
||||||
#include <sndfile.hh>
|
#include <sndfile.hh>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
@ -49,13 +49,13 @@ static void usage(const char* argv0)
|
||||||
stderr,
|
stderr,
|
||||||
"Usage: %s [-s|-f] <sound-file>\n"
|
"Usage: %s [-s|-f] <sound-file>\n"
|
||||||
" -s: extract the instrument using libsndfile\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);
|
argv0);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FileMethod {
|
enum FileMethod {
|
||||||
kMethodSndfile,
|
kMethodSndfile,
|
||||||
kMethodFlac,
|
kMethodRiff,
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
|
|
@ -71,7 +71,7 @@ int main(int argc, char *argv[])
|
||||||
if (flag == "-s")
|
if (flag == "-s")
|
||||||
method = kMethodSndfile;
|
method = kMethodSndfile;
|
||||||
else if (flag == "-f")
|
else if (flag == "-f")
|
||||||
method = kMethodFlac;
|
method = kMethodRiff;
|
||||||
else {
|
else {
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -85,8 +85,13 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
SF_INSTRUMENT ins {};
|
SF_INSTRUMENT ins {};
|
||||||
|
|
||||||
if (method == kMethodFlac) {
|
if (method == kMethodRiff) {
|
||||||
if (!sfz::FileInstruments::extractFromFlac(path, ins)) {
|
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");
|
fprintf(stderr, "Cannot get instrument\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
52
tests/FileWavetable.cpp
Normal file
52
tests/FileWavetable.cpp
Normal file
|
|
@ -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 <cstdio>
|
||||||
|
|
||||||
|
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 <sound-file>\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;
|
||||||
|
}
|
||||||
BIN
tests/TestFiles/wavetables/clm.wav
Normal file
BIN
tests/TestFiles/wavetables/clm.wav
Normal file
Binary file not shown.
BIN
tests/TestFiles/wavetables/surge.wav
Normal file
BIN
tests/TestFiles/wavetables/surge.wav
Normal file
Binary file not shown.
|
|
@ -5,6 +5,7 @@
|
||||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#include "sfizz/Wavetables.h"
|
#include "sfizz/Wavetables.h"
|
||||||
|
#include "sfizz/FileMetadata.h"
|
||||||
#include "sfizz/MathHelpers.h"
|
#include "sfizz/MathHelpers.h"
|
||||||
#include "catch2/catch.hpp"
|
#include "catch2/catch.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
@ -54,3 +55,34 @@ TEST_CASE("[Wavetables] Octave number lookup")
|
||||||
REQUIRE(oct == Approx(ref).margin(0.03f));
|
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));
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue