Deprecate the internal oversampling factor
- Removed it completely from the library - Kept the API for future work
This commit is contained in:
parent
752dd557e4
commit
2d6b99e1ab
19 changed files with 87 additions and 450 deletions
|
|
@ -1,191 +0,0 @@
|
|||
// 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 "Buffer.h"
|
||||
#include "SIMDHelpers.h"
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <sndfile.hh>
|
||||
#include "ghc/filesystem.hpp"
|
||||
#include "Oversampler.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include <iostream>
|
||||
|
||||
|
||||
constexpr std::array<double, 12> coeffsStage2x {
|
||||
0.036681502163648017,
|
||||
0.13654762463195771,
|
||||
0.27463175937945411,
|
||||
0.42313861743656667,
|
||||
0.56109869787919475,
|
||||
0.67754004997416162,
|
||||
0.76974183386322659,
|
||||
0.83988962484963803,
|
||||
0.89226081800387891,
|
||||
0.9315419599631839,
|
||||
0.96209454837808395,
|
||||
0.98781637073289708
|
||||
};
|
||||
|
||||
constexpr std::array<double, 4> coeffsStage4x {
|
||||
0.042448989488488006,
|
||||
0.17072114107630679,
|
||||
0.39329183835224008,
|
||||
0.74569514831986694
|
||||
};
|
||||
|
||||
constexpr std::array<double, 3> coeffsStage8x {
|
||||
0.055748680811302048,
|
||||
0.24305119574153092,
|
||||
0.6466991311926823
|
||||
};
|
||||
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#include "hiir/Upsampler2xSse.h"
|
||||
using Upsampler2x = hiir::Upsampler2xSse<coeffsStage2x.size()>;
|
||||
using Upsampler4x = hiir::Upsampler2xSse<coeffsStage4x.size()>;
|
||||
using Upsampler8x = hiir::Upsampler2xSse<coeffsStage8x.size()>;
|
||||
#elif defined(__arm__) || defined(__aarch64__)
|
||||
#include "hiir/Upsampler2xNeon.h"
|
||||
using Upsampler2x = hiir::Upsampler2xNeon<coeffsStage2x.size()>;
|
||||
using Upsampler4x = hiir::Upsampler2xNeon<coeffsStage4x.size()>;
|
||||
using Upsampler8x = hiir::Upsampler2xNeon<coeffsStage8x.size()>;
|
||||
#else
|
||||
#include "hiir/Upsampler2xFpu.h"
|
||||
using Upsampler2x = hiir::Upsampler2xFpu<coeffsStage2x.size()>;
|
||||
using Upsampler4x = hiir::Upsampler2xFpu<coeffsStage4x.size()>;
|
||||
using Upsampler8x = hiir::Upsampler2xFpu<coeffsStage8x.size()>;
|
||||
#endif
|
||||
|
||||
|
||||
class FileFixture : public benchmark::Fixture {
|
||||
public:
|
||||
void SetUp(const ::benchmark::State& /* state */) {
|
||||
rootPath = getPath() / "sample1.flac";
|
||||
if (!ghc::filesystem::exists(rootPath)) {
|
||||
#ifndef NDEBUG
|
||||
std::cerr << "Can't find path" << '\n';
|
||||
#endif
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
sndfile = SndfileHandle(rootPath.c_str());
|
||||
numFrames = static_cast<size_t>(sndfile.frames());
|
||||
output = absl::make_unique<sfz::AudioBuffer<float>>(sndfile.channels(), numFrames * 4);
|
||||
}
|
||||
|
||||
void TearDown(const ::benchmark::State& /* state */) {
|
||||
}
|
||||
|
||||
ghc::filesystem::path getPath()
|
||||
{
|
||||
#ifdef __linux__
|
||||
char buf[PATH_MAX + 1];
|
||||
if (readlink("/proc/self/exe", buf, sizeof(buf) - 1) == -1)
|
||||
return {};
|
||||
std::string str { buf };
|
||||
return str.substr(0, str.rfind('/'));
|
||||
#elif _WIN32
|
||||
return ghc::filesystem::current_path();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<sfz::AudioBuffer<float>> output;
|
||||
SndfileHandle sndfile;
|
||||
ghc::filesystem::path rootPath;
|
||||
size_t numFrames { 0 };
|
||||
};
|
||||
|
||||
BENCHMARK_DEFINE_F(FileFixture, NoResampling)(benchmark::State& state) {
|
||||
for (auto _ : state)
|
||||
{
|
||||
sfz::Buffer<float> buffer { numFrames * sndfile.channels() };
|
||||
sndfile.readf(buffer.data(), sndfile.frames());
|
||||
sfz::readInterleaved(buffer, output->getSpan(0), output->getSpan(1));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(FileFixture, ResampleAtOnce)(benchmark::State& state) {
|
||||
for (auto _ : state)
|
||||
{
|
||||
sfz::Buffer<float> buffer { numFrames * sndfile.channels() };
|
||||
sfz::Buffer<float> temp { numFrames * 4 };
|
||||
|
||||
Upsampler2x upsampler2x;
|
||||
Upsampler4x upsampler4x;
|
||||
upsampler2x.set_coefs(coeffsStage2x.data());
|
||||
upsampler4x.set_coefs(coeffsStage4x.data());
|
||||
|
||||
sndfile.readf(buffer.data(), numFrames);
|
||||
sfz::readInterleaved(buffer, output->getSpan(0), output->getSpan(1));
|
||||
|
||||
upsampler2x.process_block(temp.data(), output->channelReader(0), static_cast<long>(numFrames));
|
||||
upsampler4x.process_block(output->channelWriter(0), temp.data(), static_cast<long>(numFrames * 2));
|
||||
|
||||
upsampler2x.clear_buffers();
|
||||
upsampler4x.clear_buffers();
|
||||
|
||||
upsampler2x.process_block(temp.data(), output->channelReader(1), static_cast<long>(numFrames));
|
||||
upsampler4x.process_block(output->channelWriter(1), temp.data(), static_cast<long>(numFrames * 2));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(FileFixture, ResampleInChunks)(benchmark::State& state) {
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto chunkSize = static_cast<size_t>(state.range(0));
|
||||
sfz::Buffer<float> buffer { numFrames * sndfile.channels() };
|
||||
|
||||
sfz::Buffer<float> leftInput { chunkSize };
|
||||
sfz::Buffer<float> rightInput { chunkSize };
|
||||
sfz::Buffer<float> chunk { chunkSize * 2 };
|
||||
|
||||
sndfile.readf(buffer.data(), numFrames);
|
||||
|
||||
auto bufferSpan = absl::MakeSpan(buffer);
|
||||
auto leftSpan = absl::MakeSpan(leftInput);
|
||||
auto rightSpan = absl::MakeSpan(rightInput);
|
||||
auto chunkSpan = absl::MakeSpan(chunk);
|
||||
|
||||
Upsampler2x upsampler2xLeft;
|
||||
Upsampler2x upsampler2xRight;
|
||||
Upsampler4x upsampler4xLeft;
|
||||
Upsampler4x upsampler4xRight;
|
||||
upsampler2xLeft.set_coefs(coeffsStage2x.data());
|
||||
upsampler2xRight.set_coefs(coeffsStage2x.data());
|
||||
upsampler4xLeft.set_coefs(coeffsStage4x.data());
|
||||
upsampler4xRight.set_coefs(coeffsStage4x.data());
|
||||
|
||||
size_t inputFrameCounter { 0 };
|
||||
size_t outputFrameCounter { 0 };
|
||||
while(inputFrameCounter < numFrames)
|
||||
{
|
||||
// std::cout << "Input frames: " << inputFrameCounter << "/" << numFrames << '\n';
|
||||
const auto thisChunkSize = std::min(chunkSize, numFrames - inputFrameCounter);
|
||||
const auto bufferChunk = bufferSpan.subspan(
|
||||
inputFrameCounter * sndfile.channels(),
|
||||
thisChunkSize * sndfile.channels()
|
||||
);
|
||||
|
||||
sfz::readInterleaved(bufferChunk, leftSpan, rightSpan);
|
||||
|
||||
upsampler2xLeft.process_block(chunkSpan.data(), leftSpan.data(), static_cast<long>(thisChunkSize));
|
||||
upsampler4xLeft.process_block(output->channelWriter(0) + outputFrameCounter, chunkSpan.data(), static_cast<long>(thisChunkSize * 2));
|
||||
|
||||
upsampler2xRight.process_block(chunkSpan.data(), rightSpan.data(), static_cast<long>(thisChunkSize));
|
||||
upsampler4xRight.process_block(output->channelWriter(1) + outputFrameCounter, chunkSpan.data(), static_cast<long>(thisChunkSize * 2));
|
||||
|
||||
inputFrameCounter += chunkSize;
|
||||
outputFrameCounter += chunkSize * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(FileFixture, NoResampling);
|
||||
BENCHMARK_REGISTER_F(FileFixture, ResampleAtOnce);
|
||||
BENCHMARK_REGISTER_F(FileFixture, ResampleInChunks)->RangeMultiplier(4)->Range((1 << 4), (1 << 16));
|
||||
BENCHMARK_MAIN();
|
||||
|
|
@ -64,9 +64,6 @@ target_link_libraries(bm_readChunk PRIVATE sfizz::sndfile)
|
|||
sfizz_add_benchmark(bm_readChunkFlac BM_readChunkFlac.cpp)
|
||||
target_link_libraries(bm_readChunkFlac PRIVATE sfizz::sndfile)
|
||||
|
||||
sfizz_add_benchmark(bm_resampleChunk BM_resampleChunk.cpp)
|
||||
target_link_libraries(bm_resampleChunk PRIVATE sfizz::sndfile sfizz::hiir)
|
||||
|
||||
sfizz_add_benchmark(bm_interpolators BM_interpolators.cpp)
|
||||
|
||||
sfizz_add_benchmark(bm_filterModulation BM_filterModulation.cpp ../src/sfizz/SfzFilter.cpp)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ int main(int argc, char** argv)
|
|||
bool help { false };
|
||||
bool useEOT { false };
|
||||
int quality { 2 };
|
||||
int oversampling { 1 };
|
||||
|
||||
options.add_options()
|
||||
("sfz", "SFZ file", cxxopts::value<std::string>())
|
||||
|
|
@ -74,7 +73,6 @@ int main(int argc, char** argv)
|
|||
("wav", "Output wav file", cxxopts::value<std::string>())
|
||||
("b,blocksize", "Block size for the sfizz callbacks", cxxopts::value(blockSize))
|
||||
("s,samplerate", "Output sample rate", cxxopts::value(sampleRate))
|
||||
("oversampling", "Internal oversampling factor", cxxopts::value(oversampling))
|
||||
("q,quality", "Resampling quality", cxxopts::value(quality))
|
||||
("v,verbose", "Verbose output", cxxopts::value(verbose))
|
||||
("log", "Produce logs", cxxopts::value<std::string>())
|
||||
|
|
@ -126,23 +124,6 @@ int main(int argc, char** argv)
|
|||
if (params.count("log") > 0)
|
||||
synth.enableLogging(params["log"].as<std::string>());
|
||||
|
||||
const auto osFactor = [oversampling] {
|
||||
switch (oversampling){
|
||||
case 1:
|
||||
return sfz::Oversampling::x1;
|
||||
case 2:
|
||||
return sfz::Oversampling::x2;
|
||||
case 4:
|
||||
return sfz::Oversampling::x4;
|
||||
case 5:
|
||||
return sfz::Oversampling::x8;
|
||||
default:
|
||||
LOG_ERROR("Bad oversampling factor: " << oversampling);
|
||||
std::exit(-1);
|
||||
}
|
||||
}();
|
||||
synth.setOversamplingFactor(osFactor);
|
||||
|
||||
ERROR_IF(!synth.loadSfzFile(sfzPath), "There was an error loading the SFZ file.");
|
||||
LOG_INFO(synth.getNumRegions() << " regions in the SFZ.");
|
||||
|
||||
|
|
|
|||
|
|
@ -191,9 +191,9 @@ midnam:update a lv2:Feature .
|
|||
a lv2:InputPort, lv2:ControlPort ;
|
||||
lv2:index 7 ;
|
||||
lv2:symbol "oversampling" ;
|
||||
lv2:name "Internal oversampling factor",
|
||||
"Facteur de suréchantillonnage interne"@fr ,
|
||||
"Fattore Sovracampionamento Interno"@it ;
|
||||
lv2:name "Oversampling factor",
|
||||
"Facteur de suréchantillonnage"@fr ,
|
||||
"Fattore Sovracampionamento"@it ;
|
||||
pg:group <@LV2PLUGIN_URI@#config> ;
|
||||
lv2:portProperty pprop:notAutomatic ;
|
||||
lv2:portProperty pprop:expensive ;
|
||||
|
|
|
|||
16
src/sfizz.h
16
src/sfizz.h
|
|
@ -695,8 +695,8 @@ SFIZZ_EXPORTED_API void sfizz_set_preload_size(sfizz_synth_t* synth, unsigned in
|
|||
/**
|
||||
* @brief Get the internal oversampling rate.
|
||||
*
|
||||
* This is the sampling rate of the engine, not the output or expected rate of
|
||||
* the calling function. For the latter use the sfizz_get_sample_rate() function.
|
||||
* As of 1.0, This is an inactive stub for future work on oversampling in the engine.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param synth The synth.
|
||||
|
|
@ -706,17 +706,7 @@ SFIZZ_EXPORTED_API sfizz_oversampling_factor_t sfizz_get_oversampling_factor(sfi
|
|||
/**
|
||||
* @brief Set the internal oversampling rate.
|
||||
*
|
||||
* This is the sampling rate of the engine, not the output or expected rate of
|
||||
* the calling function. For the latter use the sfizz_set_sample_rate() function.
|
||||
*
|
||||
* Increasing this value (up to x8 oversampling) improves the quality of the
|
||||
* output at the expense of memory consumption and background loading speed.
|
||||
* The main render path still uses the same linear interpolation algorithm and
|
||||
* should not see its performance decrease, but the files are oversampled upon
|
||||
* loading which increases the stress on the background loader and reduce
|
||||
* the loading speed. You can tweak the size of the preloaded data to compensate
|
||||
* for the memory increase, but the full loading will need to take place anyway.
|
||||
*
|
||||
* As of 1.0, This is an inactive stub for future work on oversampling in the engine.
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param synth The synth.
|
||||
|
|
|
|||
|
|
@ -723,18 +723,8 @@ public:
|
|||
/**
|
||||
* @brief Set the oversampling factor to a new value.
|
||||
*
|
||||
* It will kill all the voices, and trigger a reloading of every file in
|
||||
* the FilePool under the new oversampling.
|
||||
* As of 1.0, This is an inactive stub for future work on oversampling in the engine.
|
||||
*
|
||||
* Increasing this value (up to x8 oversampling) improves the
|
||||
* quality of the output at the expense of memory consumption and
|
||||
* background loading speed. The main render path still uses the
|
||||
* same linear interpolation algorithm and should not see its
|
||||
* performance decrease, but the files are oversampled upon loading
|
||||
* which increases the stress on the background loader and reduce
|
||||
* the loading speed. You can tweak the size of the preloaded data
|
||||
* to compensate for the memory increase, but the full loading will
|
||||
* need to take place anyway.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
|
|
@ -751,6 +741,9 @@ public:
|
|||
/**
|
||||
* @brief Return the current oversampling factor.
|
||||
* @since 0.2.0
|
||||
*
|
||||
* As of 1.0, This is an inactive stub for future work on oversampling in the engine.
|
||||
*
|
||||
*/
|
||||
int getOversamplingFactor() const noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,6 @@
|
|||
#include <cstdint>
|
||||
|
||||
namespace sfz {
|
||||
enum class Oversampling: int {
|
||||
x1 = 1,
|
||||
x2 = 2,
|
||||
x4 = 4,
|
||||
x8 = 8
|
||||
};
|
||||
|
||||
enum ExtendedCCs {
|
||||
pitchBend = 128,
|
||||
|
|
@ -69,7 +63,6 @@ namespace config {
|
|||
constexpr float virtuallyZero { 0.001f };
|
||||
constexpr float fastReleaseDuration { 0.01f };
|
||||
constexpr char defineCharacter { '$' };
|
||||
constexpr Oversampling defaultOversamplingFactor { Oversampling::x1 };
|
||||
constexpr float A440 { 440.0 };
|
||||
constexpr size_t powerHistoryLength { 16 };
|
||||
constexpr size_t powerFollowerStep { 512 };
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@
|
|||
#include "AudioBuffer.h"
|
||||
#include "AudioSpan.h"
|
||||
#include "Config.h"
|
||||
#include "Oversampler.h"
|
||||
#include "utility/SwapAndPop.h"
|
||||
#include "utility/Debug.h"
|
||||
#include <ThreadPool.h>
|
||||
|
|
@ -91,29 +90,54 @@ void readBaseFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, uint32
|
|||
}
|
||||
}
|
||||
|
||||
sfz::FileAudioBuffer readFromFile(sfz::AudioReader& reader, uint32_t numFrames, sfz::Oversampling factor)
|
||||
sfz::FileAudioBuffer readFromFile(sfz::AudioReader& reader, uint32_t numFrames)
|
||||
{
|
||||
sfz::FileAudioBuffer baseBuffer;
|
||||
readBaseFile(reader, baseBuffer, numFrames);
|
||||
|
||||
if (factor == sfz::Oversampling::x1)
|
||||
return baseBuffer;
|
||||
|
||||
sfz::FileAudioBuffer outputBuffer { reader.channels(), numFrames * static_cast<int>(factor) };
|
||||
outputBuffer.clear();
|
||||
sfz::Oversampler oversampler { factor };
|
||||
oversampler.stream(baseBuffer, outputBuffer);
|
||||
return outputBuffer;
|
||||
return baseBuffer;
|
||||
}
|
||||
|
||||
void streamFromFile(sfz::AudioReader& reader, uint32_t numFrames, sfz::Oversampling factor, sfz::FileAudioBuffer& output, std::atomic<size_t>* filledFrames = nullptr)
|
||||
void streamFromFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, std::atomic<size_t>* filledFrames = nullptr)
|
||||
{
|
||||
const auto numFrames = static_cast<size_t>(reader.frames());
|
||||
const auto numChannels = reader.channels();
|
||||
const auto chunkSize = static_cast<size_t>(sfz::config::chunkSize);
|
||||
|
||||
output.reset();
|
||||
output.addChannels(reader.channels());
|
||||
output.resize(numFrames * static_cast<int>(factor));
|
||||
output.resize(numFrames);
|
||||
output.clear();
|
||||
sfz::Oversampler oversampler { factor };
|
||||
oversampler.stream(reader, output, filledFrames);
|
||||
|
||||
sfz::Buffer<float> fileBlock { chunkSize * numChannels };
|
||||
size_t inputFrameCounter { 0 };
|
||||
size_t outputFrameCounter { 0 };
|
||||
bool inputEof = false;
|
||||
|
||||
while (!inputEof && inputFrameCounter < numFrames)
|
||||
{
|
||||
auto thisChunkSize = std::min(chunkSize, numFrames - inputFrameCounter);
|
||||
const auto numFramesRead = static_cast<size_t>(
|
||||
reader.readNextBlock(fileBlock.data(), thisChunkSize));
|
||||
if (numFramesRead == 0)
|
||||
break;
|
||||
|
||||
if (numFramesRead < thisChunkSize) {
|
||||
inputEof = true;
|
||||
thisChunkSize = numFramesRead;
|
||||
}
|
||||
const auto outputChunkSize = thisChunkSize;
|
||||
|
||||
for (size_t chanIdx = 0; chanIdx < numChannels; chanIdx++) {
|
||||
const auto outputChunk = output.getSpan(chanIdx).subspan(outputFrameCounter, outputChunkSize);
|
||||
for (size_t i = 0; i < thisChunkSize; ++i)
|
||||
outputChunk[i] = fileBlock[i * numChannels + chanIdx];
|
||||
}
|
||||
inputFrameCounter += thisChunkSize;
|
||||
outputFrameCounter += outputChunkSize;
|
||||
|
||||
if (filledFrames != nullptr)
|
||||
filledFrames->fetch_add(outputChunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
sfz::FilePool::FilePool(sfz::Logger& logger)
|
||||
|
|
@ -294,16 +318,12 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
|
|||
if (existingFile != preloadedFiles.end()) {
|
||||
if (framesToLoad > existingFile->second.preloadedData.getNumFrames()) {
|
||||
preloadedFiles[fileId].information.maxOffset = maxOffset;
|
||||
preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad, oversamplingFactor);
|
||||
preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad);
|
||||
}
|
||||
} else {
|
||||
const auto factor = static_cast<double>(oversamplingFactor);
|
||||
fileInformation->sampleRate = factor * static_cast<double>(reader->sampleRate());
|
||||
fileInformation->end = static_cast<uint32_t>(factor * fileInformation->end);
|
||||
fileInformation->loopStart = static_cast<uint32_t>(factor * fileInformation->loopStart);
|
||||
fileInformation->loopEnd = static_cast<uint32_t>(factor * fileInformation->loopEnd);
|
||||
fileInformation->sampleRate = static_cast<double>(reader->sampleRate());
|
||||
auto insertedPair = preloadedFiles.insert_or_assign(fileId, {
|
||||
readFromFile(*reader, framesToLoad, oversamplingFactor),
|
||||
readFromFile(*reader, framesToLoad),
|
||||
*fileInformation
|
||||
});
|
||||
|
||||
|
|
@ -329,13 +349,9 @@ sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept
|
|||
if (existingFile != loadedFiles.end()) {
|
||||
return { &existingFile->second };
|
||||
} else {
|
||||
const auto factor = static_cast<double>(oversamplingFactor);
|
||||
fileInformation->sampleRate = factor * static_cast<double>(reader->sampleRate());
|
||||
fileInformation->end = static_cast<uint32_t>(factor * fileInformation->end);
|
||||
fileInformation->loopStart = static_cast<uint32_t>(factor * fileInformation->loopStart);
|
||||
fileInformation->loopEnd = static_cast<uint32_t>(factor * fileInformation->loopEnd);
|
||||
fileInformation->sampleRate = static_cast<double>(reader->sampleRate());
|
||||
auto insertedPair = preloadedFiles.insert_or_assign(fileId, {
|
||||
readFromFile(*reader, frames, oversamplingFactor),
|
||||
readFromFile(*reader, frames),
|
||||
*fileInformation
|
||||
});
|
||||
insertedPair.first->second.status = FileData::Status::Preloaded;
|
||||
|
|
@ -375,7 +391,7 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept
|
|||
const auto maxOffset = preloadedFile.second.information.maxOffset;
|
||||
fs::path file { rootDirectory / preloadedFile.first.filename() };
|
||||
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
|
||||
preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset, oversamplingFactor);
|
||||
preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -424,7 +440,7 @@ void sfz::FilePool::loadingJob(const QueuedFileData& data) noexcept
|
|||
return;
|
||||
|
||||
const auto frames = static_cast<uint32_t>(reader->frames());
|
||||
streamFromFile(*reader, frames, oversamplingFactor, data.data->fileData, &data.data->availableFrames);
|
||||
streamFromFile(*reader, data.data->fileData, &data.data->availableFrames);
|
||||
const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime;
|
||||
logger.logFileTime(waitDuration, loadDuration, frames, id->filename());
|
||||
|
||||
|
|
@ -444,45 +460,6 @@ void sfz::FilePool::clear()
|
|||
preloadedFiles.clear();
|
||||
}
|
||||
|
||||
void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept
|
||||
{
|
||||
float samplerateChange { static_cast<float>(factor) / static_cast<float>(this->oversamplingFactor) };
|
||||
for (auto& preloadedFile : preloadedFiles) {
|
||||
const auto framesToLoad = [&]() {
|
||||
if (loadInRam)
|
||||
return preloadedFile.second.information.end;
|
||||
else
|
||||
return min(
|
||||
preloadedFile.second.information.end,
|
||||
preloadedFile.second.information.maxOffset + preloadSize
|
||||
);
|
||||
}();
|
||||
|
||||
fs::path file { rootDirectory / preloadedFile.first.filename() };
|
||||
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
|
||||
preloadedFile.second.preloadedData = readFromFile(*reader, framesToLoad, factor);
|
||||
FileInformation& information = preloadedFile.second.information;
|
||||
information.sampleRate *= samplerateChange;
|
||||
information.end = static_cast<uint32_t>(samplerateChange * information.end);
|
||||
information.loopStart = static_cast<uint32_t>(samplerateChange * information.loopStart);
|
||||
information.loopEnd = static_cast<uint32_t>(samplerateChange * information.loopEnd);
|
||||
|
||||
if (preloadedFile.second.status == FileData::Status::Done) {
|
||||
const auto realFrames =
|
||||
preloadedFile.second.availableFrames.load() / static_cast<unsigned>(this->oversamplingFactor);
|
||||
preloadedFile.second.fileData = readFromFile(*reader, realFrames, factor);
|
||||
preloadedFile.second.availableFrames = realFrames * static_cast<unsigned>(factor);
|
||||
}
|
||||
}
|
||||
|
||||
this->oversamplingFactor = factor;
|
||||
}
|
||||
|
||||
sfz::Oversampling sfz::FilePool::getOversamplingFactor() const noexcept
|
||||
{
|
||||
return oversamplingFactor;
|
||||
}
|
||||
|
||||
uint32_t sfz::FilePool::getPreloadSize() const noexcept
|
||||
{
|
||||
return preloadSize;
|
||||
|
|
@ -578,8 +555,7 @@ void sfz::FilePool::setRamLoading(bool loadInRam) noexcept
|
|||
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
|
||||
preloadedFile.second.preloadedData = readFromFile(
|
||||
*reader,
|
||||
preloadedFile.second.information.end,
|
||||
oversamplingFactor
|
||||
preloadedFile.second.information.end
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -278,19 +278,6 @@ public:
|
|||
* @return uint32_t
|
||||
*/
|
||||
uint32_t getPreloadSize() const noexcept;
|
||||
/**
|
||||
* @brief Set the oversampling factor. This will trigger a full
|
||||
* reload of all samples so don't call it on the audio thread.
|
||||
*
|
||||
* @param factor
|
||||
*/
|
||||
void setOversamplingFactor(Oversampling factor) noexcept;
|
||||
/**
|
||||
* @brief Get the current oversampling factor
|
||||
*
|
||||
* @return Oversampling
|
||||
*/
|
||||
Oversampling getOversamplingFactor() const noexcept;
|
||||
/**
|
||||
* @brief Empty the file loading queues without actually loading
|
||||
* the files. All promises will be unfulfilled. Don't call this
|
||||
|
|
@ -331,7 +318,6 @@ private:
|
|||
|
||||
bool loadInRam { config::loadInRam };
|
||||
uint32_t preloadSize { config::preloadSize };
|
||||
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
|
||||
|
||||
// Signals
|
||||
volatile bool dispatchFlag { true };
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@
|
|||
namespace sfz {
|
||||
class AudioReader;
|
||||
|
||||
enum class Oversampling: int {
|
||||
x1 = 1,
|
||||
x2 = 2,
|
||||
x4 = 4,
|
||||
x8 = 8
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wraps the internal oversampler in a single function that takes an
|
||||
* AudioBuffer and oversamples it in another pre-allocated one. The
|
||||
|
|
|
|||
|
|
@ -1704,14 +1704,13 @@ float sfz::Region::getPhase() const noexcept
|
|||
return phase;
|
||||
}
|
||||
|
||||
uint64_t sfz::Region::getOffset(const MidiState& midiState, Oversampling factor) const noexcept
|
||||
uint64_t sfz::Region::getOffset(const MidiState& midiState) const noexcept
|
||||
{
|
||||
std::uniform_int_distribution<int64_t> offsetDistribution { 0, offsetRandom };
|
||||
uint64_t finalOffset = offset + offsetDistribution(Random::randomGenerator);
|
||||
for (const auto& mod: offsetCC)
|
||||
finalOffset += static_cast<uint64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
return Default::offset.bounds.clamp(finalOffset) * static_cast<uint64_t>(factor);
|
||||
return Default::offset.bounds.clamp(finalOffset);
|
||||
}
|
||||
|
||||
float sfz::Region::getDelay(const MidiState& midiState) const noexcept
|
||||
|
|
@ -1725,34 +1724,34 @@ float sfz::Region::getDelay(const MidiState& midiState) const noexcept
|
|||
return Default::delay.bounds.clamp(finalDelay);
|
||||
}
|
||||
|
||||
uint32_t sfz::Region::getSampleEnd(MidiState& midiState, Oversampling factor) const noexcept
|
||||
uint32_t sfz::Region::getSampleEnd(MidiState& midiState) const noexcept
|
||||
{
|
||||
int64_t end = sampleEnd;
|
||||
for (const auto& mod: endCC)
|
||||
end += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
end = clamp(end, int64_t { 0 }, sampleEnd);
|
||||
return static_cast<uint32_t>(end) * static_cast<uint32_t>(factor);
|
||||
return static_cast<uint32_t>(end);
|
||||
}
|
||||
|
||||
uint32_t sfz::Region::loopStart(MidiState& midiState, Oversampling factor) const noexcept
|
||||
uint32_t sfz::Region::loopStart(MidiState& midiState) const noexcept
|
||||
{
|
||||
auto start = loopRange.getStart();
|
||||
for (const auto& mod: loopStartCC)
|
||||
start += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
start = clamp(start, int64_t { 0 }, sampleEnd);
|
||||
return static_cast<uint32_t>(start) * static_cast<uint32_t>(factor);
|
||||
return static_cast<uint32_t>(start);
|
||||
}
|
||||
|
||||
uint32_t sfz::Region::loopEnd(MidiState& midiState, Oversampling factor) const noexcept
|
||||
uint32_t sfz::Region::loopEnd(MidiState& midiState) const noexcept
|
||||
{
|
||||
auto end = loopRange.getEnd();
|
||||
for (const auto& mod: loopEndCC)
|
||||
end += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
end = clamp(end, int64_t { 0 }, sampleEnd);
|
||||
return static_cast<uint32_t>(end) * static_cast<uint32_t>(factor);
|
||||
return static_cast<uint32_t>(end);
|
||||
}
|
||||
|
||||
float sfz::Region::getNoteGain(int noteNumber, float velocity) const noexcept
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ struct Region {
|
|||
* @param midiState
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint64_t getOffset(const MidiState& midiState, Oversampling factor = Oversampling::x1) const noexcept;
|
||||
uint64_t getOffset(const MidiState& midiState) const noexcept;
|
||||
/**
|
||||
* @brief Get the region delay in seconds
|
||||
*
|
||||
|
|
@ -180,7 +180,7 @@ struct Region {
|
|||
*
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t getSampleEnd(MidiState& midiState, Oversampling factor = Oversampling::x1) const noexcept;
|
||||
uint32_t getSampleEnd(MidiState& midiState) const noexcept;
|
||||
/**
|
||||
* @brief Parse a new opcode into the region to fill in the proper parameters.
|
||||
* This must be called multiple times for each opcode applying to this region.
|
||||
|
|
@ -260,8 +260,8 @@ struct Region {
|
|||
|
||||
void offsetAllKeys(int offset) noexcept;
|
||||
|
||||
uint32_t loopStart(MidiState& midiState, Oversampling factor = Oversampling::x1) const noexcept;
|
||||
uint32_t loopEnd(MidiState& midiState, Oversampling factor = Oversampling::x1) const noexcept;
|
||||
uint32_t loopStart(MidiState& midiState) const noexcept;
|
||||
uint32_t loopEnd(MidiState& midiState) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the gain this region contributes into the input of the Nth
|
||||
|
|
|
|||
|
|
@ -1849,28 +1849,6 @@ void Synth::Impl::setupModMatrix()
|
|||
mm.init();
|
||||
}
|
||||
|
||||
void Synth::setOversamplingFactor(Oversampling factor) noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
||||
// fast path
|
||||
if (factor == impl.oversamplingFactor_)
|
||||
return;
|
||||
|
||||
for (auto& voice : impl.voiceManager_)
|
||||
voice.reset();
|
||||
|
||||
impl.resources_.filePool.emptyFileLoadingQueues();
|
||||
impl.resources_.filePool.setOversamplingFactor(factor);
|
||||
impl.oversamplingFactor_ = factor;
|
||||
}
|
||||
|
||||
Oversampling Synth::getOversamplingFactor() const noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
return impl.oversamplingFactor_;
|
||||
}
|
||||
|
||||
void Synth::setPreloadSize(uint32_t preloadSize) noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
|
|
|||
|
|
@ -545,26 +545,6 @@ public:
|
|||
*/
|
||||
void setNumVoices(int numVoices) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Set the oversampling factor to a new value.
|
||||
* It will kill all the voices, and trigger a reloading of every file in
|
||||
* the FilePool under the new oversampling.
|
||||
* This function takes a lock and disables the callback; prefer calling
|
||||
* it out of the RT thread. It can also take a long time to return.
|
||||
* If the new oversampling factor is the same as the current one, it will
|
||||
* release the lock immediately and exit.
|
||||
*
|
||||
* @param factor
|
||||
*/
|
||||
void setOversamplingFactor(Oversampling factor) noexcept;
|
||||
|
||||
/**
|
||||
* @brief get the current oversampling factor
|
||||
*
|
||||
* @return Oversampling
|
||||
*/
|
||||
Oversampling getOversamplingFactor() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Set the preloaded file size.
|
||||
* This function takes a lock and disables the callback; prefer calling
|
||||
|
|
|
|||
|
|
@ -287,7 +287,6 @@ struct Synth::Impl final: public Parser::Listener {
|
|||
float sampleRate_ { config::defaultSampleRate };
|
||||
float volume_ { Default::globalVolume };
|
||||
int numVoices_ { config::numVoices };
|
||||
Oversampling oversamplingFactor_ { config::defaultOversamplingFactor };
|
||||
|
||||
// Distribution used to generate random value for the *rand opcodes
|
||||
std::uniform_real_distribution<float> randNoteDistribution_ { 0, 1 };
|
||||
|
|
|
|||
|
|
@ -466,7 +466,7 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
}
|
||||
impl.updateLoopInformation();
|
||||
impl.speedRatio_ = static_cast<float>(impl.currentPromise_->information.sampleRate / impl.sampleRate_);
|
||||
impl.sourcePosition_ = region.getOffset(resources.midiState, resources.filePool.getOversamplingFactor());
|
||||
impl.sourcePosition_ = region.getOffset(resources.midiState);
|
||||
}
|
||||
|
||||
// do Scala retuning and reconvert the frequency into a 12TET key number
|
||||
|
|
@ -498,7 +498,7 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
impl.triggerDelay_ = delay;
|
||||
impl.initialDelay_ = delay + static_cast<int>(region.getDelay(resources.midiState) * impl.sampleRate_);
|
||||
impl.baseFrequency_ = resources.tuning.getFrequencyOfKey(impl.triggerEvent_.number);
|
||||
impl.sampleEnd_ = int(region.getSampleEnd(resources.midiState, resources.filePool.getOversamplingFactor()));
|
||||
impl.sampleEnd_ = int(region.getSampleEnd(resources.midiState));
|
||||
impl.sampleSize_ = impl.sampleEnd_- impl.sourcePosition_ - 1;
|
||||
impl.bendSmoother_.setSmoothing(region.bendSmooth, impl.sampleRate_);
|
||||
impl.bendSmoother_.reset(region.getBendInCents(resources.midiState.getPitchBend()));
|
||||
|
|
@ -1676,11 +1676,10 @@ void Voice::Impl::updateLoopInformation() noexcept
|
|||
|
||||
Resources& resources = resources_;
|
||||
const FileInformation& info = currentPromise_->information;
|
||||
const Oversampling factor = resources_.filePool.getOversamplingFactor();
|
||||
const double rate = info.sampleRate;
|
||||
|
||||
loop_.start = static_cast<int>(region_->loopStart(resources.midiState, factor));
|
||||
loop_.end = max(static_cast<int>(region_->loopEnd(resources.midiState, factor)), loop_.start);
|
||||
loop_.start = static_cast<int>(region_->loopStart(resources.midiState));
|
||||
loop_.end = max(static_cast<int>(region_->loopEnd(resources.midiState)), loop_.start);
|
||||
loop_.size = loop_.end + 1 - loop_.start;
|
||||
loop_.xfSize = static_cast<int>(lroundPositive(region_->loopCrossfade * rate));
|
||||
// Clamp the crossfade to the part available before the loop starts
|
||||
|
|
|
|||
|
|
@ -265,32 +265,15 @@ void sfz::Sfizz::setNumVoices(int numVoices) noexcept
|
|||
synth->synth.setNumVoices(numVoices);
|
||||
}
|
||||
|
||||
bool sfz::Sfizz::setOversamplingFactor(int factor) noexcept
|
||||
bool sfz::Sfizz::setOversamplingFactor(int) noexcept
|
||||
{
|
||||
using sfz::Oversampling;
|
||||
switch(factor)
|
||||
{
|
||||
case 1:
|
||||
synth->synth.setOversamplingFactor(sfz::Oversampling::x1);
|
||||
return true;
|
||||
case 2:
|
||||
synth->synth.setOversamplingFactor(sfz::Oversampling::x2);
|
||||
return true;
|
||||
case 4:
|
||||
synth->synth.setOversamplingFactor(sfz::Oversampling::x4);
|
||||
return true;
|
||||
case 8:
|
||||
synth->synth.setOversamplingFactor(sfz::Oversampling::x8);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int sfz::Sfizz::getOversamplingFactor() const noexcept
|
||||
{
|
||||
return static_cast<int>(synth->synth.getOversamplingFactor());
|
||||
return 1;
|
||||
}
|
||||
|
||||
void sfz::Sfizz::setPreloadSize(uint32_t preloadSize) noexcept
|
||||
|
|
|
|||
|
|
@ -200,31 +200,14 @@ void sfizz_set_preload_size(sfizz_synth_t* synth, unsigned int preload_size)
|
|||
synth->synth.setPreloadSize(preload_size);
|
||||
}
|
||||
|
||||
sfizz_oversampling_factor_t sfizz_get_oversampling_factor(sfizz_synth_t* synth)
|
||||
sfizz_oversampling_factor_t sfizz_get_oversampling_factor(sfizz_synth_t*)
|
||||
{
|
||||
return static_cast<sfizz_oversampling_factor_t>(synth->synth.getOversamplingFactor());
|
||||
return SFIZZ_OVERSAMPLING_X1;
|
||||
}
|
||||
|
||||
bool sfizz_set_oversampling_factor(sfizz_synth_t* synth, sfizz_oversampling_factor_t oversampling)
|
||||
bool sfizz_set_oversampling_factor(sfizz_synth_t*, sfizz_oversampling_factor_t)
|
||||
{
|
||||
using sfz::Oversampling;
|
||||
switch(oversampling)
|
||||
{
|
||||
case SFIZZ_OVERSAMPLING_X1:
|
||||
synth->synth.setOversamplingFactor(sfz::Oversampling::x1);
|
||||
return true;
|
||||
case SFIZZ_OVERSAMPLING_X2:
|
||||
synth->synth.setOversamplingFactor(sfz::Oversampling::x2);
|
||||
return true;
|
||||
case SFIZZ_OVERSAMPLING_X4:
|
||||
synth->synth.setOversamplingFactor(sfz::Oversampling::x4);
|
||||
return true;
|
||||
case SFIZZ_OVERSAMPLING_X8:
|
||||
synth->synth.setOversamplingFactor(sfz::Oversampling::x8);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int sfizz_get_sample_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode)
|
||||
|
|
|
|||
|
|
@ -111,22 +111,6 @@ TEST_CASE("[Synth] Check that we can change the size of the preload before and a
|
|||
synth.renderBlock(buffer);
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Check that we can change the oversampling factor before and after loading")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.setOversamplingFactor(sfz::Oversampling::x2);
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz");
|
||||
synth.setOversamplingFactor(sfz::Oversampling::x4);
|
||||
|
||||
synth.noteOn(0, 36, 24);
|
||||
synth.noteOn(0, 36, 89);
|
||||
synth.renderBlock(buffer);
|
||||
synth.setOversamplingFactor(sfz::Oversampling::x2);
|
||||
synth.renderBlock(buffer);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("[Synth] All notes offs/all sounds off")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue