More generic utility to extract audio file metadata

This commit is contained in:
Jean Pierre Cimalando 2020-07-25 15:08:32 +02:00
parent 220f9acc1c
commit 4bf2a16f05
8 changed files with 326 additions and 182 deletions

2
dpf.mk
View file

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

View file

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

View file

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

View file

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

260
src/sfizz/FileMetadata.cpp Normal file
View file

@ -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 <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;
}
//------------------------------------------------------------------------------
struct FileMetadataReader::Impl {
FILE_u stream_;
std::vector<RiffChunkInfo> 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<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
{
const std::vector<RiffChunkInfo>& 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<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;
}
} // namespace sfz

45
src/sfizz/FileMetadata.h Normal file
View file

@ -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 <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;
};
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> impl_;
};
} // namespace sfz

View file

@ -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::FileInformation> 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) {

View file

@ -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 <sndfile.hh>
#include <cstdio>
@ -49,13 +49,13 @@ static void usage(const char* argv0)
stderr,
"Usage: %s [-s|-f] <sound-file>\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;
}