Skip to content

Commit

Permalink
Merge pull request #242 from jpcima/flac-loops
Browse files Browse the repository at this point in the history
Add the support of FLAC loops
  • Loading branch information
jpcima authored May 26, 2020
2 parents b6263c8 + 393aef5 commit 769186a
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 2 deletions.
1 change: 1 addition & 0 deletions dpf.mk
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ SFIZZ_SOURCES = \
src/sfizz/effects/Width.cpp \
src/sfizz/EQPool.cpp \
src/sfizz/FileId.cpp \
src/sfizz/FileInstrument.cpp \
src/sfizz/FilePool.cpp \
src/sfizz/FilterPool.cpp \
src/sfizz/FloatEnvelopes.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ set (SFIZZ_SOURCES
sfizz/Synth.cpp
sfizz/FileId.cpp
sfizz/FilePool.cpp
sfizz/FileInstrument.cpp
sfizz/FilterPool.cpp
sfizz/EQPool.cpp
sfizz/Region.cpp
Expand Down
143 changes: 143 additions & 0 deletions src/sfizz/FileInstrument.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// 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
24 changes: 24 additions & 0 deletions src/sfizz/FileInstrument.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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
11 changes: 9 additions & 2 deletions src/sfizz/FilePool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "FilePool.h"
#include "FileInstrument.h"
#include "Buffer.h"
#include "AudioBuffer.h"
#include "Config.h"
Expand Down Expand Up @@ -217,9 +218,15 @@ absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const Fil
returnedValue.sampleRate = static_cast<double>(sndFile.samplerate());
returnedValue.numChannels = sndFile.channels();

if (!fileId.isReverse()) {
SF_INSTRUMENT instrumentInfo;
SF_INSTRUMENT instrumentInfo {};

const int sndFormat = sndFile.format();
if ((sndFormat & SF_FORMAT_TYPEMASK) == SF_FORMAT_FLAC)
sfz::FileInstruments::extractFromFlac(file, instrumentInfo);
else
sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo));

if (!fileId.isReverse()) {
if (instrumentInfo.loop_count > 0) {
returnedValue.loopBegin = instrumentInfo.loops[0].start;
returnedValue.loopEnd = min(returnedValue.end, instrumentInfo.loops[0].end - 1);
Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,7 @@ target_link_libraries(sfizz_plot_curve PRIVATE sfizz::sfizz)
add_executable(sfizz_plot_wavetables PlotWavetables.cpp)
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)

file(COPY "." DESTINATION ${CMAKE_BINARY_DIR}/tests)
109 changes: 109 additions & 0 deletions tests/FileInstrument.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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/FileInstrument.h"
#include "absl/strings/string_view.h"
#include <sndfile.hh>
#include <cstdio>

static const char* modeString(int mode, const char* valueFallback = nullptr)
{
switch (mode) {
case SF_LOOP_NONE:
return "none";
case SF_LOOP_FORWARD:
return "forward";
case SF_LOOP_BACKWARD:
return "backward";
case SF_LOOP_ALTERNATING:
return "alternating";
default:
return valueFallback;
}
}

static void printInstrument(const SF_INSTRUMENT& ins)
{
printf("Gain: %d\n", ins.gain);
printf("Base note: %d\n", ins.basenote);
printf("Detune: %d\n", ins.detune);
printf("Velocity: %d:%d\n", ins.velocity_lo, ins.velocity_hi);
printf("Key: %d:%d\n", ins.key_lo, ins.key_hi);
printf("Loop count: %d\n", ins.loop_count);

for (int i = 0; i < ins.loop_count; ++i) {
printf("\nLoop %d:\n", i + 1);
printf("\tMode: %s\n", modeString(ins.loops[i].mode, "(unknown)"));
printf("\tStart: %u\n", ins.loops[i].start);
printf("\tEnd: %u\n", ins.loops[i].end);
printf("\tCount: %u\n", ins.loops[i].count);
}
}

static void usage(const char* argv0)
{
fprintf(
stderr,
"Usage: %s [-s|-f] <sound-file>\n"
" -s: extract the instrument using libsndfile\n"
" -f: extract the instrument using FLAC metadata\n",
argv0);
}

enum FileMethod {
kMethodSndfile,
kMethodFlac,
};

int main(int argc, char *argv[])
{
fs::path path;
FileMethod method = kMethodSndfile;

if (argc == 2) {
path = argv[1];
}
else if (argc == 3) {
absl::string_view flag = argv[1];
if (flag == "-s")
method = kMethodSndfile;
else if (flag == "-f")
method = kMethodFlac;
else {
usage(argv[0]);
return 1;
}
path = argv[2];
}
else {
usage(argv[0]);
return 1;
}

SF_INSTRUMENT ins {};

if (method == kMethodFlac) {
if (!sfz::FileInstruments::extractFromFlac(path, ins)) {
fprintf(stderr, "Cannot get instrument\n");
return 1;
}
}
else {
SndfileHandle sndFile(path);
if (!sndFile) {
fprintf(stderr, "Cannot open file\n");
return 1;
}
if (sndFile.command(SFC_GET_INSTRUMENT, &ins, sizeof(ins)) != 1) {
fprintf(stderr, "Cannot get instrument\n");
return 1;
}
}

printInstrument(ins);

return 0;
}

0 comments on commit 769186a

Please sign in to comment.