Audio file incremental reading

This commit is contained in:
Jean Pierre Cimalando 2020-06-24 05:51:20 +02:00
parent 792b62cc78
commit 2873c970e8
7 changed files with 594 additions and 69 deletions

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

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

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"
@ -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;
}

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;