Merge pull request #294 from jpcima/incremental-file-load

Audio file incremental reading
This commit is contained in:
JP Cimalando 2020-08-04 18:14:30 +02:00 committed by GitHub
commit 73dd5da21f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 857 additions and 69 deletions

View file

@ -0,0 +1,229 @@
// 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 "AudioReader.h"
#include <benchmark/benchmark.h>
#include <sndfile.hh>
#include <ghc/fs_std.hpp>
#include <vector>
#include <memory>
#include <system_error>
#include <stdexcept>
#include <cstdio>
#include <cmath>
#ifndef _WIN32
#include <sys/types.h>
#include <unistd.h>
#else
#include <io.h>
#endif
///
struct AutoFD {
AutoFD() {}
~AutoFD() { reset(); }
AutoFD(const AutoFD&) = delete;
AutoFD &operator=(const AutoFD&) = delete;
AutoFD(AutoFD&& other) : fd_(other.fd_) { other.fd_ = -1; }
AutoFD &operator=(AutoFD&& other)
{
if (this == &other) return *this;
reset(other.fd_);
other.fd_ = -1;
return *this;
}
explicit operator bool() const noexcept { return fd_ != -1; }
int get() const noexcept { return fd_; }
int release()
{
int fd = fd_;
fd_ = -1;
return fd;
}
void reset(int fd = -1) noexcept
{
if (fd_ == fd) return;
if (fd_ != -1) close(fd_);
fd_ = fd;
}
private:
int fd_ = -1;
};
///
class AudioReaderFixture : public benchmark::Fixture {
public:
void SetUp(const ::benchmark::State& state) override
{
workBuffer.resize(2 * static_cast<size_t>(state.range(0)));
}
void TearDown(const ::benchmark::State& /* state */) override
{
}
static AutoFD createAudioFile(int format);
static AutoFD fileWav;
static AutoFD fileFlac;
static AutoFD fileOgg;
std::vector<float> workBuffer;
};
AutoFD AudioReaderFixture::fileWav = createAudioFile(SF_FORMAT_WAV|SF_FORMAT_PCM_16);
AutoFD AudioReaderFixture::fileFlac = createAudioFile(SF_FORMAT_FLAC|SF_FORMAT_PCM_16);
AutoFD AudioReaderFixture::fileOgg = createAudioFile(SF_FORMAT_OGG|SF_FORMAT_VORBIS);
AutoFD AudioReaderFixture::createAudioFile(int format)
{
constexpr unsigned sampleRate = 44100;
constexpr unsigned fileDuration = 10;
constexpr unsigned fileFrames = sampleRate * fileDuration;
// synth 2 channels of arbitrary waveform
std::unique_ptr<double[]> sndData { new double[2 * fileFrames] };
double phase = 0.0;
for (unsigned i = 0; i < fileFrames; ++i) {
sndData[2 * i ] = std::sin(2.0 * M_PI * phase);
sndData[2 * i + 1] = std::cos(2.0 * M_PI * phase);
phase += 440.0 * (1.0 / sampleRate);
phase -= static_cast<long>(phase);
}
// create anonymous temp file
FILE* file = tmpfile();
if (!file)
throw std::system_error(errno, std::generic_category());
// convert FILE to fd, for sndfile
AutoFD fd;
fd.reset(dup(fileno(file)));
if (!fd) {
fclose(file);
throw std::system_error(errno, std::generic_category());
}
fclose(file);
// write to fd
SndfileHandle snd(fd.get(), false, SFM_WRITE, format, 2, sampleRate);
if (snd.error())
throw std::runtime_error("cannot open sound file for writing");
snd.writef(sndData.get(), fileFrames);
snd = SndfileHandle();
return fd;
}
static void rewindFd(int fd)
{
#ifndef _WIN32
off_t off = lseek(fd, 0, SEEK_SET);
#else
off_t off = _lseek(fd, 0, SEEK_SET);
#endif
if (off == -1)
throw std::system_error(errno, std::generic_category());
}
static void doReaderBenchmark(int fd, std::vector<float> &buffer, sfz::AudioReaderType type)
{
rewindFd(fd);
sfz::AudioReaderPtr reader = sfz::createExplicitAudioReaderWithFd(fd, type);
while (reader->readNextBlock(buffer.data(), buffer.size() / 2) > 0);
}
static void doEntireRead(int fd)
{
rewindFd(fd);
SndfileHandle handle(fd, false);
if (handle.error())
throw std::runtime_error("cannot open sound file for reading");
std::vector<float> buffer(static_cast<size_t>(2 * handle.frames()));
handle.read(buffer.data(), buffer.size());
}
BENCHMARK_DEFINE_F(AudioReaderFixture, EntireWav)(benchmark::State& state)
{
for (auto _ : state) {
doEntireRead(fileWav.get());
}
}
BENCHMARK_DEFINE_F(AudioReaderFixture, ForwardWav)(benchmark::State& state)
{
for (auto _ : state) {
doReaderBenchmark(fileWav.get(), workBuffer, sfz::AudioReaderType::Forward);
}
}
BENCHMARK_DEFINE_F(AudioReaderFixture, ReverseWav)(benchmark::State& state)
{
for (auto _ : state) {
doReaderBenchmark(fileWav.get(), workBuffer, sfz::AudioReaderType::Reverse);
}
}
BENCHMARK_DEFINE_F(AudioReaderFixture, EntireFlac)(benchmark::State& state)
{
for (auto _ : state) {
doEntireRead(fileFlac.get());
}
}
BENCHMARK_DEFINE_F(AudioReaderFixture, ForwardFlac)(benchmark::State& state)
{
for (auto _ : state) {
doReaderBenchmark(fileFlac.get(), workBuffer, sfz::AudioReaderType::Forward);
}
}
BENCHMARK_DEFINE_F(AudioReaderFixture, ReverseFlac)(benchmark::State& state)
{
for (auto _ : state) {
doReaderBenchmark(fileFlac.get(), workBuffer, sfz::AudioReaderType::Reverse);
}
}
BENCHMARK_DEFINE_F(AudioReaderFixture, EntireOgg)(benchmark::State& state)
{
for (auto _ : state) {
doEntireRead(fileOgg.get());
}
}
BENCHMARK_DEFINE_F(AudioReaderFixture, ForwardOgg)(benchmark::State& state)
{
for (auto _ : state) {
doReaderBenchmark(fileOgg.get(), workBuffer, sfz::AudioReaderType::Forward);
}
}
//BENCHMARK_DEFINE_F(AudioReaderFixture, ReverseOgg)(benchmark::State& state)
//{
// for (auto _ : state) {
// doReaderBenchmark(fileOgg.get(), workBuffer, sfz::AudioReaderType::Reverse);
// }
//}
BENCHMARK_REGISTER_F(AudioReaderFixture, ForwardWav)->RangeMultiplier(2)->Range((1 << 6), (1 << 10));
BENCHMARK_REGISTER_F(AudioReaderFixture, ReverseWav)->RangeMultiplier(2)->Range((1 << 6), (1 << 10));
BENCHMARK_REGISTER_F(AudioReaderFixture, EntireWav)->Range(1, 1);
BENCHMARK_REGISTER_F(AudioReaderFixture, ForwardFlac)->RangeMultiplier(2)->Range((1 << 6), (1 << 10));
BENCHMARK_REGISTER_F(AudioReaderFixture, ReverseFlac)->RangeMultiplier(2)->Range((1 << 6), (1 << 10));
BENCHMARK_REGISTER_F(AudioReaderFixture, EntireFlac)->Range(1, 1);
BENCHMARK_REGISTER_F(AudioReaderFixture, ForwardOgg)->RangeMultiplier(2)->Range((1 << 6), (1 << 10));
//BENCHMARK_REGISTER_F(AudioReaderFixture, ReverseOgg)->RangeMultiplier(2)->Range((1 << 6), (1 << 10));
BENCHMARK_REGISTER_F(AudioReaderFixture, EntireOgg)->Range(1, 1);
BENCHMARK_MAIN();

View file

@ -78,6 +78,9 @@ target_link_libraries(bm_wavfile PRIVATE sfizz-sndfile)
sfizz_add_benchmark(bm_flacfile BM_flacfile.cpp)
target_link_libraries(bm_flacfile PRIVATE sfizz-sndfile)
sfizz_add_benchmark(bm_audioReaders BM_audioReaders.cpp ../src/sfizz/AudioReader.cpp)
target_link_libraries(bm_audioReaders PRIVATE sfizz-sndfile)
sfizz_add_benchmark(bm_readChunk BM_readChunk.cpp)
target_link_libraries(bm_readChunk PRIVATE sfizz-sndfile)
sfizz_add_benchmark(bm_readChunkFlac BM_readChunkFlac.cpp)

1
dpf.mk
View file

@ -56,6 +56,7 @@ sfizz-clean:
SFIZZ_SOURCES = \
src/sfizz/ADSREnvelope.cpp \
src/sfizz/AudioReader.cpp \
src/sfizz/Curve.cpp \
src/sfizz/effects/Apan.cpp \
src/sfizz/Effects.cpp \

View file

@ -93,6 +93,7 @@ set (SFIZZ_SOURCES
sfizz/FileId.cpp
sfizz/FilePool.cpp
sfizz/FileInstrument.cpp
sfizz/AudioReader.cpp
sfizz/FilterPool.cpp
sfizz/EQPool.cpp
sfizz/Region.cpp

387
src/sfizz/AudioReader.cpp Normal file
View file

@ -0,0 +1,387 @@
// 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 "AudioReader.h"
#include <sndfile.hh>
#include <algorithm>
namespace sfz {
class BasicSndfileReader : public AudioReader {
public:
explicit BasicSndfileReader(SndfileHandle handle) : handle_(handle) {}
virtual ~BasicSndfileReader() {}
int format() const override;
int64_t frames() const override;
unsigned channels() const override;
unsigned sampleRate() const override;
bool getInstrument(SF_INSTRUMENT* instrument) override;
protected:
SndfileHandle handle_;
};
int BasicSndfileReader::format() const
{
return handle_.format();
}
int64_t BasicSndfileReader::frames() const
{
return handle_.frames();
}
unsigned BasicSndfileReader::channels() const
{
return handle_.channels();
}
unsigned BasicSndfileReader::sampleRate() const
{
return handle_.samplerate();
}
bool BasicSndfileReader::getInstrument(SF_INSTRUMENT* instrument)
{
if (handle_.command(SFC_GET_INSTRUMENT, instrument, sizeof(SF_INSTRUMENT)) == SF_FALSE)
return false;
return true;
}
//------------------------------------------------------------------------------
/**
* @brief Audio file reader in forward direction
*/
class ForwardReader : public BasicSndfileReader {
public:
explicit ForwardReader(SndfileHandle handle);
AudioReaderType type() const override;
size_t readNextBlock(float* buffer, size_t frames) override;
};
ForwardReader::ForwardReader(SndfileHandle handle)
: BasicSndfileReader(handle)
{
}
AudioReaderType ForwardReader::type() const
{
return AudioReaderType::Forward;
}
size_t ForwardReader::readNextBlock(float* buffer, size_t frames)
{
sf_count_t readFrames = handle_.readf(buffer, frames);
if (frames <= 0)
return 0;
return readFrames;
}
//------------------------------------------------------------------------------
template <size_t N, class T = float>
struct AudioFrame {
T samples[N];
};
/**
* @brief Reorder a sequence of frames in reverse
*/
static void reverse_frames(float* data, sf_count_t frames, unsigned channels)
{
switch (channels) {
#define SPECIALIZE_FOR(N) \
case N: \
std::reverse( \
reinterpret_cast<AudioFrame<N> *>(data), \
reinterpret_cast<AudioFrame<N> *>(data) + frames); \
break
SPECIALIZE_FOR(1);
SPECIALIZE_FOR(2);
default:
for (sf_count_t i = 0; i < frames / 2; ++i) {
sf_count_t j = frames - 1 - i;
float* frame1 = &data[i * channels];
float* frame2 = &data[j * channels];
for (unsigned c = 0; c < channels; ++c)
std::swap(frame1[c], frame2[c]);
}
break;
#undef SPECIALIZE_FOR
}
}
//------------------------------------------------------------------------------
/**
* @brief Audio file reader in reverse direction, for fast-seeking formats
*/
class ReverseReader : public BasicSndfileReader {
public:
explicit ReverseReader(SndfileHandle handle);
AudioReaderType type() const override;
size_t readNextBlock(float* buffer, size_t frames) override;
private:
sf_count_t position_ {};
};
ReverseReader::ReverseReader(SndfileHandle handle)
: BasicSndfileReader(handle)
{
position_ = handle.seek(0, SF_SEEK_END);
}
AudioReaderType ReverseReader::type() const
{
return AudioReaderType::Reverse;
}
size_t ReverseReader::readNextBlock(float* buffer, size_t frames)
{
sf_count_t position = position_;
const unsigned channels = handle_.channels();
const sf_count_t readFrames = std::min<sf_count_t>(frames, position);
if (readFrames <= 0)
return false;
position -= readFrames;
if (handle_.seek(position, SEEK_SET) != position ||
handle_.readf(buffer, readFrames) != readFrames)
return false;
position_ = position;
reverse_frames(buffer, readFrames, channels);
return readFrames;
}
//------------------------------------------------------------------------------
/**
* @brief Audio file reader in reverse direction, for slow-seeking formats
*/
class NoSeekReverseReader : public BasicSndfileReader {
public:
explicit NoSeekReverseReader(SndfileHandle handle);
AudioReaderType type() const override;
size_t readNextBlock(float* buffer, size_t frames) override;
private:
void readWholeFile();
private:
std::unique_ptr<float[]> fileBuffer_;
sf_count_t fileFramesLeft_ { 0 };
};
NoSeekReverseReader::NoSeekReverseReader(SndfileHandle handle)
: BasicSndfileReader(handle)
{
}
AudioReaderType NoSeekReverseReader::type() const
{
return AudioReaderType::NoSeekReverse;
}
size_t NoSeekReverseReader::readNextBlock(float* buffer, size_t frames)
{
float* fileBuffer = fileBuffer_.get();
if (!fileBuffer) {
readWholeFile();
fileBuffer = fileBuffer_.get();
}
const unsigned channels = handle_.channels();
const sf_count_t fileFramesLeft = fileFramesLeft_;
sf_count_t readFrames = std::min<sf_count_t>(frames, fileFramesLeft);
if (readFrames <= 0)
return 0;
std::copy(
&fileBuffer[channels * (fileFramesLeft - readFrames)],
&fileBuffer[channels * fileFramesLeft], buffer);
reverse_frames(buffer, readFrames, channels);
fileFramesLeft_ = fileFramesLeft - readFrames;
return readFrames;
}
void NoSeekReverseReader::readWholeFile()
{
const sf_count_t frames = handle_.frames();
const unsigned channels = handle_.channels();
float* fileBuffer = new float[channels * frames];
fileBuffer_.reset(fileBuffer);
fileFramesLeft_ = handle_.readf(fileBuffer, frames);
}
//------------------------------------------------------------------------------
const std::error_category& sndfile_category()
{
class sndfile_category : public std::error_category {
public:
const char* name() const noexcept override
{
return "sndfile";
}
std::string message(int condition) const override
{
const char* str = sf_error_number(condition);
return str ? str : "";
}
};
static const sndfile_category cat;
return cat;
}
//------------------------------------------------------------------------------
class DummyAudioReader : public AudioReader {
public:
explicit DummyAudioReader(AudioReaderType type) : type_(type) {}
AudioReaderType type() const override { return type_; }
int format() const override { return 0; }
int64_t frames() const override { return 0; }
unsigned channels() const override { return 1; }
unsigned sampleRate() const override { return 44100; }
size_t readNextBlock(float*, size_t) override { return 0; }
bool getInstrument(SF_INSTRUMENT* ) override { return false; }
private:
AudioReaderType type_ {};
};
//------------------------------------------------------------------------------
static bool formatHasFastSeeking(int format)
{
bool fast;
const int type = format & SF_FORMAT_TYPEMASK;
const int subtype = format & SF_FORMAT_SUBMASK;
switch (type) {
case SF_FORMAT_WAV:
case SF_FORMAT_AIFF:
case SF_FORMAT_AU:
case SF_FORMAT_RAW:
case SF_FORMAT_WAVEX:
// TODO: list more PCM formats that support fast seeking
fast = subtype >= SF_FORMAT_PCM_S8 && subtype <= SF_FORMAT_DOUBLE;
break;
case SF_FORMAT_FLAC:
// seeking has acceptable overhead
fast = true;
break;
case SF_FORMAT_OGG:
// ogg is prohibitively slow at seeking (possibly others)
// cf. https://github.com/erikd/libsndfile/issues/491
fast = false;
break;
default:
fast = false;
break;
}
return fast;
}
static AudioReaderPtr createAudioReaderWithHandle(SndfileHandle handle, bool reverse, std::error_code* ec)
{
AudioReaderPtr reader;
if (ec)
ec->clear();
if (!handle) {
if (ec)
*ec = std::error_code(handle.error(), sndfile_category());
reader.reset(new DummyAudioReader(reverse ? AudioReaderType::Reverse : AudioReaderType::Forward));
}
else if (!reverse)
reader.reset(new ForwardReader(handle));
else if (formatHasFastSeeking(handle.format()))
reader.reset(new ReverseReader(handle));
else
reader.reset(new NoSeekReverseReader(handle));
return reader;
}
AudioReaderPtr createAudioReader(const fs::path& path, bool reverse, std::error_code* ec)
{
#if defined(_WIN32)
SndfileHandle handle(path.wstring().c_str());
#else
SndfileHandle handle(path.c_str());
#endif
return createAudioReaderWithHandle(handle, reverse, ec);
}
AudioReaderPtr createAudioReaderWithFd(int fd, bool reverse, std::error_code* ec)
{
SndfileHandle handle(fd, false);
return createAudioReaderWithHandle(handle, reverse, ec);
}
static AudioReaderPtr createExplicitAudioReaderWithHandle(SndfileHandle handle, AudioReaderType type, std::error_code* ec)
{
AudioReaderPtr reader;
if (ec)
ec->clear();
if (!handle) {
if (ec)
*ec = std::error_code(handle.error(), sndfile_category());
reader.reset(new DummyAudioReader(type));
}
else {
switch (type) {
case AudioReaderType::Forward:
reader.reset(new ForwardReader(handle));
break;
case AudioReaderType::Reverse:
reader.reset(new ReverseReader(handle));
break;
case AudioReaderType::NoSeekReverse:
reader.reset(new NoSeekReverseReader(handle));
break;
}
}
return reader;
}
AudioReaderPtr createExplicitAudioReader(const fs::path& path, AudioReaderType type, std::error_code* ec)
{
#if defined(_WIN32)
SndfileHandle handle(path.wstring().c_str());
#else
SndfileHandle handle(path.c_str());
#endif
return createExplicitAudioReaderWithHandle(handle, type, ec);
}
AudioReaderPtr createExplicitAudioReaderWithFd(int fd, AudioReaderType type, std::error_code* ec)
{
SndfileHandle handle(fd, false);
return createExplicitAudioReaderWithHandle(handle, type, ec);
}
} // namespace sfz

70
src/sfizz/AudioReader.h Normal file
View file

@ -0,0 +1,70 @@
// 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 "absl/types/span.h"
#include "ghc/fs_std.hpp"
#include <system_error>
#include <memory>
#include <cstdio>
#if defined(_WIN32)
#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
#include <windows.h>
#endif
#include <sndfile.h>
namespace sfz {
/**
* @brief Designation of a particular kind of audio reader
*/
enum class AudioReaderType {
//! Reader in forward direction
Forward,
//! Reader in reverse direction
Reverse,
//! Reader in reverse direction, operating on a whole file instead of seeking
NoSeekReverse,
};
/**
* @brief Reader of audio file data
*/
class AudioReader {
public:
virtual ~AudioReader() {}
virtual AudioReaderType type() const = 0;
virtual int format() const = 0;
virtual int64_t frames() const = 0;
virtual unsigned channels() const = 0;
virtual unsigned sampleRate() const = 0;
virtual size_t readNextBlock(float* buffer, size_t frames) = 0;
virtual bool getInstrument(SF_INSTRUMENT* instrument) = 0;
};
typedef std::unique_ptr<AudioReader> AudioReaderPtr;
/**
* @brief Create a file reader of detected type.
*/
AudioReaderPtr createAudioReader(const fs::path& path, bool reverse, std::error_code* ec = nullptr);
/**
* @brief Create a file reader of detected type.
*/
AudioReaderPtr createAudioReaderWithFd(int fd, bool reverse, std::error_code* ec = nullptr);
/**
* @brief Create a file reader of explicit type. (for testing purposes)
*/
AudioReaderPtr createExplicitAudioReader(const fs::path& path, AudioReaderType type, std::error_code* ec = nullptr);
/**
* @brief Create a file reader of explicit type. (for testing purposes)
*/
AudioReaderPtr createExplicitAudioReaderWithFd(int fd, AudioReaderType type, std::error_code* ec = nullptr);
} // namespace sfz

View file

@ -24,6 +24,7 @@
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "FilePool.h"
#include "AudioReader.h"
#include "FileInstrument.h"
#include "Buffer.h"
#include "AudioBuffer.h"
@ -46,69 +47,50 @@
#include <pthread.h>
#endif
void readBaseFile(SndfileHandle& sndFile, sfz::FileAudioBuffer& output, uint32_t numFrames, bool reverse)
void readBaseFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, uint32_t numFrames)
{
output.reset();
output.resize(numFrames);
if (reverse)
sndFile.seek(-static_cast<sf_count_t>(numFrames), SEEK_END);
const unsigned channels = sndFile.channels();
const unsigned channels = reader.channels();
if (channels == 1) {
output.addChannel();
output.clear();
sndFile.readf(output.channelWriter(0), numFrames);
reader.readNextBlock(output.channelWriter(0), numFrames);
} else if (channels == 2) {
output.addChannel();
output.addChannel();
output.clear();
sfz::Buffer<float> tempReadBuffer { 2 * numFrames };
sndFile.readf(tempReadBuffer.data(), numFrames);
reader.readNextBlock(tempReadBuffer.data(), numFrames);
sfz::readInterleaved(tempReadBuffer, output.getSpan(0), output.getSpan(1));
}
if (reverse) {
for (unsigned c = 0; c < channels; ++c) {
// TODO: consider optimizing with SIMD
absl::Span<float> channel = output.getSpan(c);
std::reverse(channel.begin(), channel.end());
}
}
}
std::unique_ptr<sfz::FileAudioBuffer> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse)
std::unique_ptr<sfz::FileAudioBuffer> readFromFile(sfz::AudioReader& reader, uint32_t numFrames, sfz::Oversampling factor)
{
auto baseBuffer = absl::make_unique<sfz::FileAudioBuffer>();
readBaseFile(sndFile, *baseBuffer, numFrames, reverse);
readBaseFile(reader, *baseBuffer, numFrames);
if (factor == sfz::Oversampling::x1)
return baseBuffer;
auto outputBuffer = absl::make_unique<sfz::FileAudioBuffer>(sndFile.channels(), numFrames * static_cast<int>(factor));
auto outputBuffer = absl::make_unique<sfz::FileAudioBuffer>(reader.channels(), numFrames * static_cast<int>(factor));
outputBuffer->clear();
sfz::Oversampler oversampler { factor };
oversampler.stream(*baseBuffer, *outputBuffer);
return outputBuffer;
}
void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse, sfz::FileAudioBuffer& output, std::atomic<size_t>* filledFrames = nullptr)
void streamFromFile(sfz::AudioReader& reader, uint32_t numFrames, sfz::Oversampling factor, sfz::FileAudioBuffer& output, std::atomic<size_t>* filledFrames = nullptr)
{
if (factor == sfz::Oversampling::x1) {
readBaseFile(sndFile, output, numFrames, reverse);
if (filledFrames != nullptr)
filledFrames->store(numFrames);
return;
}
auto baseBuffer = readFromFile(sndFile, numFrames, sfz::Oversampling::x1, reverse);
output.reset();
output.addChannels(baseBuffer->getNumChannels());
output.addChannels(reader.channels());
output.resize(numFrames * static_cast<int>(factor));
output.clear();
sfz::Oversampler oversampler { factor };
oversampler.stream(*baseBuffer, output, filledFrames);
oversampler.stream(reader, output, filledFrames);
}
sfz::FilePool::FilePool(sfz::Logger& logger)
@ -221,24 +203,26 @@ absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const Fil
if (!fs::exists(file))
return {};
SndfileHandle sndFile(file.string().c_str());
if (sndFile.channels() != 1 && sndFile.channels() != 2) {
AudioReaderPtr reader = createAudioReader(file, fileId.isReverse());
const unsigned channels = reader->channels();
if (channels != 1 && channels != 2) {
DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << fileId);
return {};
}
FileInformation returnedValue;
returnedValue.end = static_cast<uint32_t>(sndFile.frames()) - 1;
returnedValue.sampleRate = static_cast<double>(sndFile.samplerate());
returnedValue.numChannels = sndFile.channels();
returnedValue.end = static_cast<uint32_t>(reader->frames()) - 1;
returnedValue.sampleRate = static_cast<double>(reader->sampleRate());
returnedValue.numChannels = reader->channels();
SF_INSTRUMENT instrumentInfo {};
const int sndFormat = sndFile.format();
const int sndFormat = reader->format();
if ((sndFormat & SF_FORMAT_TYPEMASK) == SF_FORMAT_FLAC)
sfz::FileInstruments::extractFromFlac(file, instrumentInfo);
else
sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo));
reader->getInstrument(&instrumentInfo);
if (!fileId.isReverse()) {
if (instrumentInfo.loop_count > 0) {
@ -260,10 +244,10 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
return false;
const fs::path file { rootDirectory / fileId.filename() };
SndfileHandle sndFile(file.string().c_str());
AudioReaderPtr reader = createAudioReader(file, fileId.isReverse());
// FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same
const auto frames = static_cast<uint32_t>(sndFile.frames());
const auto frames = static_cast<uint32_t>(reader->frames());
const auto framesToLoad = [&]() {
if (preloadSize == 0)
return frames;
@ -274,12 +258,12 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
const auto existingFile = preloadedFiles.find(fileId);
if (existingFile != preloadedFiles.end()) {
if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) {
preloadedFiles[fileId].preloadedData = readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.isReverse());
preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad, oversamplingFactor);
}
} else {
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(reader->sampleRate());
FileDataHandle handle {
readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.isReverse()),
readFromFile(*reader, framesToLoad, oversamplingFactor),
*fileInformation
};
preloadedFiles.insert_or_assign(fileId, handle);
@ -294,17 +278,17 @@ absl::optional<sfz::FileDataHandle> sfz::FilePool::loadFile(const FileId& fileId
return {};
const fs::path file { rootDirectory / fileId.filename() };
SndfileHandle sndFile(file.string().c_str());
AudioReaderPtr reader = createAudioReader(file, fileId.isReverse());
// FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same
const auto frames = static_cast<uint32_t>(sndFile.frames());
const auto frames = static_cast<uint32_t>(reader->frames());
const auto existingFile = loadedFiles.find(fileId);
if (existingFile != loadedFiles.end()) {
return existingFile->second;
} else {
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(reader->sampleRate());
FileDataHandle handle {
readFromFile(sndFile, frames, oversamplingFactor, fileId.isReverse()),
readFromFile(*reader, frames, oversamplingFactor),
*fileInformation
};
loadedFiles.insert_or_assign(fileId, handle);
@ -353,8 +337,8 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(oversamplingFactor);
const auto maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
fs::path file { rootDirectory / preloadedFile.first.filename() };
SndfileHandle sndFile(file.string().c_str());
preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, oversamplingFactor, preloadedFile.first.isReverse());
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset, oversamplingFactor);
}
this->preloadSize = preloadSize;
}
@ -411,14 +395,15 @@ void sfz::FilePool::loadingThread() noexcept
const auto waitDuration = loadStartTime - promise->creationTime;
const fs::path file { rootDirectory / promise->fileId.filename() };
SndfileHandle sndFile(file.string().c_str());
if (sndFile.error() != 0) {
DBG("[sfizz] libsndfile errored for " << promise->fileId << " with message " << sndFile.strError());
std::error_code readError;
AudioReaderPtr reader = createAudioReader(file, promise->fileId.isReverse(), &readError);
if (readError) {
DBG("[sfizz] libsndfile errored for " << promise->fileId << " with message " << readError.what());
promise->dataStatus = FilePromise::DataStatus::Error;
continue;
}
const auto frames = static_cast<uint32_t>(sndFile.frames());
streamFromFile(sndFile, frames, oversamplingFactor, promise->fileId.isReverse(), promise->fileData, &promise->availableFrames);
const auto frames = static_cast<uint32_t>(reader->frames());
streamFromFile(*reader, frames, oversamplingFactor, promise->fileData, &promise->availableFrames);
promise->dataStatus = FilePromise::DataStatus::Ready;
const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime;
logger.logFileTime(waitDuration, loadDuration, frames, promise->fileId.filename());
@ -473,8 +458,8 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(this->oversamplingFactor);
const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
fs::path file { rootDirectory / preloadedFile.first.filename() };
SndfileHandle sndFile(file.string().c_str());
preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, factor, preloadedFile.first.isReverse());
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset, factor);
preloadedFile.second.information.sampleRate *= samplerateChange;
}

View file

@ -7,6 +7,7 @@
#include "Oversampler.h"
#include "Buffer.h"
#include "AudioSpan.h"
#include "AudioReader.h"
#include "SIMDConfig.h"
constexpr std::array<double, 12> coeffsStage2x {
@ -69,28 +70,29 @@ void sfz::Oversampler::stream(AudioSpan<float> input, AudioSpan<float> output, s
const auto numFrames = input.getNumFrames();
const auto numChannels = input.getNumChannels();
std::vector<Upsampler2x> upsampler2x (numChannels);
std::vector<Upsampler4x> upsampler4x (numChannels);
std::vector<Upsampler8x> upsampler8x (numChannels);
std::vector<Upsampler2x> upsampler2x;
std::vector<Upsampler4x> upsampler4x;
std::vector<Upsampler8x> upsampler8x;
switch(factor)
{
case Oversampling::x8:
upsampler8x.resize(numChannels);
for (auto& upsampler: upsampler8x)
upsampler.set_coefs(coeffsStage8x.data());
// fallthrough
case Oversampling::x4:
upsampler4x.resize(numChannels);
for (auto& upsampler: upsampler4x)
upsampler.set_coefs(coeffsStage4x.data());
// fallthrough
case Oversampling::x2:
upsampler2x.resize(numChannels);
for (auto& upsampler: upsampler2x)
upsampler.set_coefs(coeffsStage2x.data());
break;
case Oversampling::x1:
for (size_t i = 0; i < numChannels; ++i)
copy<float>(input.getConstSpan(i), output.getSpan(i).first(numFrames));
return;
break;
}
// Intermediate buffers
@ -109,20 +111,119 @@ void sfz::Oversampler::stream(AudioSpan<float> input, AudioSpan<float> output, s
for (size_t chanIdx = 0; chanIdx < numChannels; chanIdx++) {
const auto inputChunk = input.getSpan(chanIdx).subspan(inputFrameCounter, thisChunkSize);
const auto outputChunk = output.getSpan(chanIdx).subspan(outputFrameCounter, outputChunkSize);
if (factor == Oversampling::x2) {
switch (factor) {
case Oversampling::x1:
copy<float>(inputChunk, outputChunk);
break;
case Oversampling::x2:
upsampler2x[chanIdx].process_block(outputChunk.data(), inputChunk.data(), static_cast<long>(thisChunkSize));
continue;
}
if (factor == Oversampling::x4) {
break;
case Oversampling::x4:
upsampler2x[chanIdx].process_block(span1.data(), inputChunk.data(), static_cast<long>(thisChunkSize));
upsampler4x[chanIdx].process_block(outputChunk.data(), span1.data(), static_cast<long>(thisChunkSize * 2));
continue;
}
else if (factor == Oversampling::x8) {
break;
case Oversampling::x8:
upsampler2x[chanIdx].process_block(span1.data(), inputChunk.data(), static_cast<long>(thisChunkSize));
upsampler4x[chanIdx].process_block(span2.data(), span1.data(), static_cast<long>(thisChunkSize * 2));
upsampler8x[chanIdx].process_block(outputChunk.data(), span2.data(), static_cast<long>(thisChunkSize * 4));
continue;
break;
}
}
inputFrameCounter += thisChunkSize;
outputFrameCounter += outputChunkSize;
if (framesReady != nullptr)
framesReady->fetch_add(outputChunkSize);
}
}
void sfz::Oversampler::stream(AudioReader& input, AudioSpan<float> output, std::atomic<size_t>* framesReady)
{
ASSERT(output.getNumFrames() >= input.getNumFrames() * static_cast<int>(factor));
ASSERT(output.getNumChannels() == input.getNumChannels());
const auto numFrames = static_cast<size_t>(input.frames());
const auto numChannels = input.channels();
std::vector<Upsampler2x> upsampler2x;
std::vector<Upsampler4x> upsampler4x;
std::vector<Upsampler8x> upsampler8x;
switch(factor)
{
case Oversampling::x8:
upsampler8x.resize(numChannels);
for (auto& upsampler: upsampler8x)
upsampler.set_coefs(coeffsStage8x.data());
// fallthrough
case Oversampling::x4:
upsampler4x.resize(numChannels);
for (auto& upsampler: upsampler4x)
upsampler.set_coefs(coeffsStage4x.data());
// fallthrough
case Oversampling::x2:
upsampler2x.resize(numChannels);
for (auto& upsampler: upsampler2x)
upsampler.set_coefs(coeffsStage2x.data());
break;
case Oversampling::x1:
break;
}
// Intermediate buffers
sfz::Buffer<float> fileBlock { chunkSize * numChannels };
sfz::Buffer<float> buffer1 { chunkSize * 2 };
sfz::Buffer<float> buffer2 { chunkSize * 4 };
auto span1 = absl::MakeSpan(buffer1);
auto span2 = absl::MakeSpan(buffer2);
auto upsample2xFromInterleaved = [numChannels](
Upsampler2x& upsampler, float* output, const float* input,
size_t numInputFrames, unsigned chanIdx)
{
for (size_t i = 0; i < numInputFrames; ++i) {
float* outp = &output[2 * i];
const float* inp = &input[i * numChannels + chanIdx];
upsampler.process_sample(outp[0], outp[1], inp[0]);
}
};
size_t inputFrameCounter { 0 };
size_t outputFrameCounter { 0 };
bool inputEof = false;
while (!inputEof && inputFrameCounter < numFrames)
{
// std::cout << "Input frames: " << inputFrameCounter << "/" << numFrames << '\n';
auto thisChunkSize = std::min(chunkSize, numFrames - inputFrameCounter);
const auto numFramesRead = static_cast<size_t>(
input.readNextBlock(fileBlock.data(), thisChunkSize));
if (numFramesRead == 0)
break;
if (numFramesRead < thisChunkSize) {
inputEof = true;
thisChunkSize = numFramesRead;
}
const auto outputChunkSize = thisChunkSize * static_cast<int>(factor);
for (size_t chanIdx = 0; chanIdx < numChannels; chanIdx++) {
const auto outputChunk = output.getSpan(chanIdx).subspan(outputFrameCounter, outputChunkSize);
switch (factor) {
case Oversampling::x1:
for (size_t i = 0; i < thisChunkSize; ++i)
outputChunk[i] = fileBlock[i * numChannels + chanIdx];
break;
case Oversampling::x2:
upsample2xFromInterleaved(upsampler2x[chanIdx], outputChunk.data(), fileBlock.data(), thisChunkSize, chanIdx);
break;
case Oversampling::x4:
upsample2xFromInterleaved(upsampler2x[chanIdx], span1.data(), fileBlock.data(), thisChunkSize, chanIdx);
upsampler4x[chanIdx].process_block(outputChunk.data(), span1.data(), static_cast<long>(thisChunkSize * 2));
break;
case Oversampling::x8:
upsample2xFromInterleaved(upsampler2x[chanIdx], span1.data(), fileBlock.data(), thisChunkSize, chanIdx);
upsampler4x[chanIdx].process_block(span2.data(), span1.data(), static_cast<long>(thisChunkSize * 2));
upsampler8x[chanIdx].process_block(outputChunk.data(), span2.data(), static_cast<long>(thisChunkSize * 4));
break;
}
}
inputFrameCounter += thisChunkSize;
@ -131,5 +232,4 @@ void sfz::Oversampler::stream(AudioSpan<float> input, AudioSpan<float> output, s
if (framesReady != nullptr)
framesReady->fetch_add(outputChunkSize);
}
}

View file

@ -15,6 +15,8 @@
#include "Config.h"
namespace sfz {
class AudioReader;
/**
* @brief Wraps the internal oversampler in a single function that takes an
* AudioBuffer and oversamples it in another pre-allocated one. The
@ -41,6 +43,16 @@ public:
* @param framesReady an atomic counter for the ready frames. If null no signaling is done.
*/
void stream(AudioSpan<float> input, AudioSpan<float> output, std::atomic<size_t>* framesReady = nullptr);
/**
* @brief Stream the oversampling of an input AudioReader into an output
* one, possibly signaling the caller along the way of the number of
* frames that are written.
*
* @param input
* @param output
* @param framesReady an atomic counter for the ready frames. If null no signaling is done.
*/
void stream(AudioReader& input, AudioSpan<float> output, std::atomic<size_t>* framesReady = nullptr);
Oversampler() = delete;
Oversampler(const Oversampler&) = delete;