Audio file incremental reading
This commit is contained in:
parent
792b62cc78
commit
2873c970e8
7 changed files with 594 additions and 69 deletions
1
dpf.mk
1
dpf.mk
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -8,6 +8,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
|
||||
|
|
|
|||
367
src/sfizz/AudioReader.cpp
Normal file
367
src/sfizz/AudioReader.cpp
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
// 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;
|
||||
}
|
||||
|
||||
AudioReaderPtr createAudioReader(const fs::path& path, bool reverse, std::error_code* ec)
|
||||
{
|
||||
AudioReaderPtr reader;
|
||||
|
||||
if (ec)
|
||||
ec->clear();
|
||||
|
||||
#if defined(_WIN32)
|
||||
SndfileHandle handle(path.wstring().c_str());
|
||||
#else
|
||||
SndfileHandle handle(path.c_str());
|
||||
#endif
|
||||
|
||||
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 createExplicitAudioReader(const fs::path& path, AudioReaderType type, std::error_code* ec)
|
||||
{
|
||||
AudioReaderPtr reader;
|
||||
|
||||
if (ec)
|
||||
ec->clear();
|
||||
|
||||
#if defined(_WIN32)
|
||||
SndfileHandle handle(path.wstring().c_str());
|
||||
#else
|
||||
SndfileHandle handle(path.c_str());
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace sfz
|
||||
59
src/sfizz/AudioReader.h
Normal file
59
src/sfizz/AudioReader.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// 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>
|
||||
#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 explicit type. (for testing purposes)
|
||||
*/
|
||||
AudioReaderPtr createExplicitAudioReader(const fs::path& path, AudioReaderType type, std::error_code* ec = nullptr);
|
||||
|
||||
} // namespace sfz
|
||||
|
|
@ -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"
|
||||
|
|
@ -40,69 +41,50 @@
|
|||
#include <sndfile.hh>
|
||||
#include <thread>
|
||||
|
||||
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)
|
||||
|
|
@ -210,24 +192,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) {
|
||||
|
|
@ -249,10 +233,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;
|
||||
|
|
@ -263,12 +247,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);
|
||||
|
|
@ -283,17 +267,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);
|
||||
|
|
@ -342,8 +326,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;
|
||||
}
|
||||
|
|
@ -392,14 +376,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());
|
||||
|
|
@ -453,8 +438,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue