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 = \
|
SFIZZ_SOURCES = \
|
||||||
src/sfizz/ADSREnvelope.cpp \
|
src/sfizz/ADSREnvelope.cpp \
|
||||||
|
src/sfizz/AudioReader.cpp \
|
||||||
src/sfizz/Curve.cpp \
|
src/sfizz/Curve.cpp \
|
||||||
src/sfizz/effects/Apan.cpp \
|
src/sfizz/effects/Apan.cpp \
|
||||||
src/sfizz/Effects.cpp \
|
src/sfizz/Effects.cpp \
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ set (SFIZZ_SOURCES
|
||||||
sfizz/FileId.cpp
|
sfizz/FileId.cpp
|
||||||
sfizz/FilePool.cpp
|
sfizz/FilePool.cpp
|
||||||
sfizz/FileInstrument.cpp
|
sfizz/FileInstrument.cpp
|
||||||
|
sfizz/AudioReader.cpp
|
||||||
sfizz/FilterPool.cpp
|
sfizz/FilterPool.cpp
|
||||||
sfizz/EQPool.cpp
|
sfizz/EQPool.cpp
|
||||||
sfizz/Region.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.
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include "FilePool.h"
|
#include "FilePool.h"
|
||||||
|
#include "AudioReader.h"
|
||||||
#include "FileInstrument.h"
|
#include "FileInstrument.h"
|
||||||
#include "Buffer.h"
|
#include "Buffer.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
|
|
@ -40,69 +41,50 @@
|
||||||
#include <sndfile.hh>
|
#include <sndfile.hh>
|
||||||
#include <thread>
|
#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.reset();
|
||||||
output.resize(numFrames);
|
output.resize(numFrames);
|
||||||
|
|
||||||
if (reverse)
|
const unsigned channels = reader.channels();
|
||||||
sndFile.seek(-static_cast<sf_count_t>(numFrames), SEEK_END);
|
|
||||||
|
|
||||||
const unsigned channels = sndFile.channels();
|
|
||||||
|
|
||||||
if (channels == 1) {
|
if (channels == 1) {
|
||||||
output.addChannel();
|
output.addChannel();
|
||||||
output.clear();
|
output.clear();
|
||||||
sndFile.readf(output.channelWriter(0), numFrames);
|
reader.readNextBlock(output.channelWriter(0), numFrames);
|
||||||
} else if (channels == 2) {
|
} else if (channels == 2) {
|
||||||
output.addChannel();
|
output.addChannel();
|
||||||
output.addChannel();
|
output.addChannel();
|
||||||
output.clear();
|
output.clear();
|
||||||
sfz::Buffer<float> tempReadBuffer { 2 * numFrames };
|
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));
|
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>();
|
auto baseBuffer = absl::make_unique<sfz::FileAudioBuffer>();
|
||||||
readBaseFile(sndFile, *baseBuffer, numFrames, reverse);
|
readBaseFile(reader, *baseBuffer, numFrames);
|
||||||
|
|
||||||
if (factor == sfz::Oversampling::x1)
|
if (factor == sfz::Oversampling::x1)
|
||||||
return baseBuffer;
|
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();
|
outputBuffer->clear();
|
||||||
sfz::Oversampler oversampler { factor };
|
sfz::Oversampler oversampler { factor };
|
||||||
oversampler.stream(*baseBuffer, *outputBuffer);
|
oversampler.stream(*baseBuffer, *outputBuffer);
|
||||||
return 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.reset();
|
||||||
output.addChannels(baseBuffer->getNumChannels());
|
output.addChannels(reader.channels());
|
||||||
output.resize(numFrames * static_cast<int>(factor));
|
output.resize(numFrames * static_cast<int>(factor));
|
||||||
output.clear();
|
output.clear();
|
||||||
sfz::Oversampler oversampler { factor };
|
sfz::Oversampler oversampler { factor };
|
||||||
oversampler.stream(*baseBuffer, output, filledFrames);
|
oversampler.stream(reader, output, filledFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
sfz::FilePool::FilePool(sfz::Logger& logger)
|
sfz::FilePool::FilePool(sfz::Logger& logger)
|
||||||
|
|
@ -210,24 +192,26 @@ absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const Fil
|
||||||
if (!fs::exists(file))
|
if (!fs::exists(file))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
AudioReaderPtr reader = createAudioReader(file, fileId.isReverse());
|
||||||
if (sndFile.channels() != 1 && sndFile.channels() != 2) {
|
const unsigned channels = reader->channels();
|
||||||
|
|
||||||
|
if (channels != 1 && channels != 2) {
|
||||||
DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << fileId);
|
DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << fileId);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
FileInformation returnedValue;
|
FileInformation returnedValue;
|
||||||
returnedValue.end = static_cast<uint32_t>(sndFile.frames()) - 1;
|
returnedValue.end = static_cast<uint32_t>(reader->frames()) - 1;
|
||||||
returnedValue.sampleRate = static_cast<double>(sndFile.samplerate());
|
returnedValue.sampleRate = static_cast<double>(reader->sampleRate());
|
||||||
returnedValue.numChannels = sndFile.channels();
|
returnedValue.numChannels = reader->channels();
|
||||||
|
|
||||||
SF_INSTRUMENT instrumentInfo {};
|
SF_INSTRUMENT instrumentInfo {};
|
||||||
|
|
||||||
const int sndFormat = sndFile.format();
|
const int sndFormat = reader->format();
|
||||||
if ((sndFormat & SF_FORMAT_TYPEMASK) == SF_FORMAT_FLAC)
|
if ((sndFormat & SF_FORMAT_TYPEMASK) == SF_FORMAT_FLAC)
|
||||||
sfz::FileInstruments::extractFromFlac(file, instrumentInfo);
|
sfz::FileInstruments::extractFromFlac(file, instrumentInfo);
|
||||||
else
|
else
|
||||||
sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo));
|
reader->getInstrument(&instrumentInfo);
|
||||||
|
|
||||||
if (!fileId.isReverse()) {
|
if (!fileId.isReverse()) {
|
||||||
if (instrumentInfo.loop_count > 0) {
|
if (instrumentInfo.loop_count > 0) {
|
||||||
|
|
@ -249,10 +233,10 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const fs::path file { rootDirectory / fileId.filename() };
|
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
|
// 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 = [&]() {
|
const auto framesToLoad = [&]() {
|
||||||
if (preloadSize == 0)
|
if (preloadSize == 0)
|
||||||
return frames;
|
return frames;
|
||||||
|
|
@ -263,12 +247,12 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
|
||||||
const auto existingFile = preloadedFiles.find(fileId);
|
const auto existingFile = preloadedFiles.find(fileId);
|
||||||
if (existingFile != preloadedFiles.end()) {
|
if (existingFile != preloadedFiles.end()) {
|
||||||
if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) {
|
if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) {
|
||||||
preloadedFiles[fileId].preloadedData = readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.isReverse());
|
preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad, oversamplingFactor);
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
FileDataHandle handle {
|
||||||
readFromFile(sndFile, framesToLoad, oversamplingFactor, fileId.isReverse()),
|
readFromFile(*reader, framesToLoad, oversamplingFactor),
|
||||||
*fileInformation
|
*fileInformation
|
||||||
};
|
};
|
||||||
preloadedFiles.insert_or_assign(fileId, handle);
|
preloadedFiles.insert_or_assign(fileId, handle);
|
||||||
|
|
@ -283,17 +267,17 @@ absl::optional<sfz::FileDataHandle> sfz::FilePool::loadFile(const FileId& fileId
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
const fs::path file { rootDirectory / fileId.filename() };
|
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
|
// 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);
|
const auto existingFile = loadedFiles.find(fileId);
|
||||||
if (existingFile != loadedFiles.end()) {
|
if (existingFile != loadedFiles.end()) {
|
||||||
return existingFile->second;
|
return existingFile->second;
|
||||||
} else {
|
} 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 {
|
FileDataHandle handle {
|
||||||
readFromFile(sndFile, frames, oversamplingFactor, fileId.isReverse()),
|
readFromFile(*reader, frames, oversamplingFactor),
|
||||||
*fileInformation
|
*fileInformation
|
||||||
};
|
};
|
||||||
loadedFiles.insert_or_assign(fileId, handle);
|
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 numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(oversamplingFactor);
|
||||||
const auto maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
|
const auto maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
|
||||||
fs::path file { rootDirectory / preloadedFile.first.filename() };
|
fs::path file { rootDirectory / preloadedFile.first.filename() };
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
|
||||||
preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, oversamplingFactor, preloadedFile.first.isReverse());
|
preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset, oversamplingFactor);
|
||||||
}
|
}
|
||||||
this->preloadSize = preloadSize;
|
this->preloadSize = preloadSize;
|
||||||
}
|
}
|
||||||
|
|
@ -392,14 +376,15 @@ void sfz::FilePool::loadingThread() noexcept
|
||||||
const auto waitDuration = loadStartTime - promise->creationTime;
|
const auto waitDuration = loadStartTime - promise->creationTime;
|
||||||
|
|
||||||
const fs::path file { rootDirectory / promise->fileId.filename() };
|
const fs::path file { rootDirectory / promise->fileId.filename() };
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
std::error_code readError;
|
||||||
if (sndFile.error() != 0) {
|
AudioReaderPtr reader = createAudioReader(file, promise->fileId.isReverse(), &readError);
|
||||||
DBG("[sfizz] libsndfile errored for " << promise->fileId << " with message " << sndFile.strError());
|
if (readError) {
|
||||||
|
DBG("[sfizz] libsndfile errored for " << promise->fileId << " with message " << readError.what());
|
||||||
promise->dataStatus = FilePromise::DataStatus::Error;
|
promise->dataStatus = FilePromise::DataStatus::Error;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto frames = static_cast<uint32_t>(sndFile.frames());
|
const auto frames = static_cast<uint32_t>(reader->frames());
|
||||||
streamFromFile(sndFile, frames, oversamplingFactor, promise->fileId.isReverse(), promise->fileData, &promise->availableFrames);
|
streamFromFile(*reader, frames, oversamplingFactor, promise->fileData, &promise->availableFrames);
|
||||||
promise->dataStatus = FilePromise::DataStatus::Ready;
|
promise->dataStatus = FilePromise::DataStatus::Ready;
|
||||||
const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime;
|
const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime;
|
||||||
logger.logFileTime(waitDuration, loadDuration, frames, promise->fileId.filename());
|
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 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;
|
const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
|
||||||
fs::path file { rootDirectory / preloadedFile.first.filename() };
|
fs::path file { rootDirectory / preloadedFile.first.filename() };
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
|
||||||
preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, factor, preloadedFile.first.isReverse());
|
preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset, factor);
|
||||||
preloadedFile.second.information.sampleRate *= samplerateChange;
|
preloadedFile.second.information.sampleRate *= samplerateChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include "Oversampler.h"
|
#include "Oversampler.h"
|
||||||
#include "Buffer.h"
|
#include "Buffer.h"
|
||||||
#include "AudioSpan.h"
|
#include "AudioSpan.h"
|
||||||
|
#include "AudioReader.h"
|
||||||
#include "SIMDConfig.h"
|
#include "SIMDConfig.h"
|
||||||
|
|
||||||
constexpr std::array<double, 12> coeffsStage2x {
|
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 numFrames = input.getNumFrames();
|
||||||
const auto numChannels = input.getNumChannels();
|
const auto numChannels = input.getNumChannels();
|
||||||
|
|
||||||
std::vector<Upsampler2x> upsampler2x (numChannels);
|
std::vector<Upsampler2x> upsampler2x;
|
||||||
std::vector<Upsampler4x> upsampler4x (numChannels);
|
std::vector<Upsampler4x> upsampler4x;
|
||||||
std::vector<Upsampler8x> upsampler8x (numChannels);
|
std::vector<Upsampler8x> upsampler8x;
|
||||||
|
|
||||||
switch(factor)
|
switch(factor)
|
||||||
{
|
{
|
||||||
case Oversampling::x8:
|
case Oversampling::x8:
|
||||||
|
upsampler8x.resize(numChannels);
|
||||||
for (auto& upsampler: upsampler8x)
|
for (auto& upsampler: upsampler8x)
|
||||||
upsampler.set_coefs(coeffsStage8x.data());
|
upsampler.set_coefs(coeffsStage8x.data());
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case Oversampling::x4:
|
case Oversampling::x4:
|
||||||
|
upsampler4x.resize(numChannels);
|
||||||
for (auto& upsampler: upsampler4x)
|
for (auto& upsampler: upsampler4x)
|
||||||
upsampler.set_coefs(coeffsStage4x.data());
|
upsampler.set_coefs(coeffsStage4x.data());
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case Oversampling::x2:
|
case Oversampling::x2:
|
||||||
|
upsampler2x.resize(numChannels);
|
||||||
for (auto& upsampler: upsampler2x)
|
for (auto& upsampler: upsampler2x)
|
||||||
upsampler.set_coefs(coeffsStage2x.data());
|
upsampler.set_coefs(coeffsStage2x.data());
|
||||||
break;
|
break;
|
||||||
case Oversampling::x1:
|
case Oversampling::x1:
|
||||||
for (size_t i = 0; i < numChannels; ++i)
|
break;
|
||||||
copy<float>(input.getConstSpan(i), output.getSpan(i).first(numFrames));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intermediate buffers
|
// 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++) {
|
for (size_t chanIdx = 0; chanIdx < numChannels; chanIdx++) {
|
||||||
const auto inputChunk = input.getSpan(chanIdx).subspan(inputFrameCounter, thisChunkSize);
|
const auto inputChunk = input.getSpan(chanIdx).subspan(inputFrameCounter, thisChunkSize);
|
||||||
const auto outputChunk = output.getSpan(chanIdx).subspan(outputFrameCounter, outputChunkSize);
|
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));
|
upsampler2x[chanIdx].process_block(outputChunk.data(), inputChunk.data(), static_cast<long>(thisChunkSize));
|
||||||
continue;
|
break;
|
||||||
}
|
case Oversampling::x4:
|
||||||
if (factor == Oversampling::x4) {
|
|
||||||
upsampler2x[chanIdx].process_block(span1.data(), inputChunk.data(), static_cast<long>(thisChunkSize));
|
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));
|
upsampler4x[chanIdx].process_block(outputChunk.data(), span1.data(), static_cast<long>(thisChunkSize * 2));
|
||||||
continue;
|
break;
|
||||||
}
|
case Oversampling::x8:
|
||||||
else if (factor == Oversampling::x8) {
|
|
||||||
upsampler2x[chanIdx].process_block(span1.data(), inputChunk.data(), static_cast<long>(thisChunkSize));
|
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));
|
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));
|
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;
|
inputFrameCounter += thisChunkSize;
|
||||||
|
|
@ -131,5 +232,4 @@ void sfz::Oversampler::stream(AudioSpan<float> input, AudioSpan<float> output, s
|
||||||
if (framesReady != nullptr)
|
if (framesReady != nullptr)
|
||||||
framesReady->fetch_add(outputChunkSize);
|
framesReady->fetch_add(outputChunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
class AudioReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Wraps the internal oversampler in a single function that takes an
|
* @brief Wraps the internal oversampler in a single function that takes an
|
||||||
* AudioBuffer and oversamples it in another pre-allocated one. The
|
* 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.
|
* @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);
|
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() = delete;
|
||||||
Oversampler(const Oversampler&) = delete;
|
Oversampler(const Oversampler&) = delete;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue