Add the support of FLAC loops
This commit is contained in:
parent
0cdb1f7044
commit
17fbff5ab5
7 changed files with 290 additions and 2 deletions
1
dpf.mk
1
dpf.mk
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
143
src/sfizz/FileInstrument.cpp
Normal file
143
src/sfizz/FileInstrument.cpp
Normal file
|
|
@ -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.native().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(
|
||||
absl::MakeConstSpan(chunk.get(), riffChunkSize), ins);
|
||||
}
|
||||
}
|
||||
|
||||
if (fseek(stream.get(), off_next_block, SEEK_SET) != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace sfz
|
||||
24
src/sfizz/FileInstrument.h
Normal file
24
src/sfizz/FileInstrument.h
Normal file
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
tests/FileInstrument.cpp
Normal file
109
tests/FileInstrument.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue