commit
9cac220de1
15 changed files with 414 additions and 83 deletions
51
benchmarks/BM_clock.cpp
Normal file
51
benchmarks/BM_clock.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
// 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 <benchmark/benchmark.h>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
class Clock : public benchmark::Fixture {
|
||||||
|
public:
|
||||||
|
void SetUp(const ::benchmark::State& state) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown(const ::benchmark::State& state [[maybe_unused]]) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::time_point<std::chrono::high_resolution_clock> hires;
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> steady;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
BENCHMARK_DEFINE_F(Clock, HighRes)(benchmark::State& state) {
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
hires = std::chrono::high_resolution_clock::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_DEFINE_F(Clock, Steady)(benchmark::State& state) {
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
steady = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just checking that stuff happens..
|
||||||
|
BENCHMARK_DEFINE_F(Clock, Both)(benchmark::State& state) {
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
hires = std::chrono::high_resolution_clock::now();
|
||||||
|
steady = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_REGISTER_F(Clock, HighRes);
|
||||||
|
BENCHMARK_REGISTER_F(Clock, Steady);
|
||||||
|
BENCHMARK_REGISTER_F(Clock, Both);
|
||||||
|
BENCHMARK_MAIN();
|
||||||
48
benchmarks/BM_logger.cpp
Normal file
48
benchmarks/BM_logger.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// 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 <benchmark/benchmark.h>
|
||||||
|
#include "Logger.h"
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
class Logger : public benchmark::Fixture {
|
||||||
|
public:
|
||||||
|
void SetUp(const ::benchmark::State& state) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown(const ::benchmark::State& state [[maybe_unused]]) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
BENCHMARK_DEFINE_F(Logger, Baseline)(benchmark::State& state) {
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
sfz::Logger logger {};
|
||||||
|
benchmark::DoNotOptimize(logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_DEFINE_F(Logger, ProcessingTime)(benchmark::State& state) {
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
sfz::Logger logger {};
|
||||||
|
sfz::CallbackBreakdown breakdown;
|
||||||
|
breakdown.data = sfz::Duration(1);
|
||||||
|
breakdown.renderMethod = sfz::Duration(1);
|
||||||
|
breakdown.dispatch = sfz::Duration(1);
|
||||||
|
breakdown.panning = sfz::Duration(1);
|
||||||
|
breakdown.filters = sfz::Duration(1);
|
||||||
|
breakdown.amplitude = sfz::Duration(1);
|
||||||
|
logger.logCallbackTime(std::move(breakdown), 16, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_REGISTER_F(Logger, Baseline);
|
||||||
|
BENCHMARK_REGISTER_F(Logger, ProcessingTime);
|
||||||
|
BENCHMARK_MAIN();
|
||||||
|
|
@ -32,6 +32,7 @@ macro(sfizz_add_benchmark TARGET)
|
||||||
endmacro()
|
endmacro()
|
||||||
|
|
||||||
sfizz_add_benchmark(bm_opf_high_vs_low BM_OPF_high_vs_low.cpp)
|
sfizz_add_benchmark(bm_opf_high_vs_low BM_OPF_high_vs_low.cpp)
|
||||||
|
sfizz_add_benchmark(bm_clock BM_clock.cpp)
|
||||||
sfizz_add_benchmark(bm_write BM_writeInterleaved.cpp)
|
sfizz_add_benchmark(bm_write BM_writeInterleaved.cpp)
|
||||||
sfizz_add_benchmark(bm_read BM_readInterleaved.cpp)
|
sfizz_add_benchmark(bm_read BM_readInterleaved.cpp)
|
||||||
sfizz_add_benchmark(bm_fill BM_fill.cpp)
|
sfizz_add_benchmark(bm_fill BM_fill.cpp)
|
||||||
|
|
@ -57,6 +58,9 @@ sfizz_add_benchmark(bm_widthPos BM_widthPos.cpp)
|
||||||
sfizz_add_benchmark(bm_interpolationCast BM_interpolationCast.cpp)
|
sfizz_add_benchmark(bm_interpolationCast BM_interpolationCast.cpp)
|
||||||
sfizz_add_benchmark(bm_pointerIterationOrOffsets BM_pointerIterationOrOffsets.cpp)
|
sfizz_add_benchmark(bm_pointerIterationOrOffsets BM_pointerIterationOrOffsets.cpp)
|
||||||
|
|
||||||
|
sfizz_add_benchmark(bm_logger BM_logger.cpp)
|
||||||
|
target_link_libraries(bm_logger PRIVATE sfizz::sfizz)
|
||||||
|
|
||||||
if (TARGET sfizz-samplerate)
|
if (TARGET sfizz-samplerate)
|
||||||
sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES})
|
sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES})
|
||||||
target_link_libraries(bm_resample PRIVATE sfizz-samplerate sfizz-sndfile)
|
target_link_libraries(bm_resample PRIVATE sfizz-samplerate sfizz-sndfile)
|
||||||
|
|
@ -86,6 +90,7 @@ add_custom_target(sfizz_benchmarks)
|
||||||
add_dependencies(sfizz_benchmarks
|
add_dependencies(sfizz_benchmarks
|
||||||
bm_opf_high_vs_low
|
bm_opf_high_vs_low
|
||||||
bm_write
|
bm_write
|
||||||
|
bm_clock
|
||||||
bm_pointerIterationOrOffsets
|
bm_pointerIterationOrOffsets
|
||||||
bm_read
|
bm_read
|
||||||
bm_mean
|
bm_mean
|
||||||
|
|
@ -102,6 +107,7 @@ add_dependencies(sfizz_benchmarks
|
||||||
bm_ramp
|
bm_ramp
|
||||||
bm_ADSR
|
bm_ADSR
|
||||||
bm_add
|
bm_add
|
||||||
|
bm_logger
|
||||||
bm_pan
|
bm_pan
|
||||||
bm_subtract
|
bm_subtract
|
||||||
bm_multiplyAdd
|
bm_multiplyAdd
|
||||||
|
|
|
||||||
15
src/sfizz.h
15
src/sfizz.h
|
|
@ -361,6 +361,21 @@ SFIZZ_EXPORTED_API char* sfizz_get_unknown_opcodes(sfizz_synth_t* synth);
|
||||||
*/
|
*/
|
||||||
SFIZZ_EXPORTED_API bool sfizz_should_reload_file(sfizz_synth_t* synth);
|
SFIZZ_EXPORTED_API bool sfizz_should_reload_file(sfizz_synth_t* synth);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enable logging of timings to sidecar CSV files. This can produce
|
||||||
|
* many outputs so use with caution.
|
||||||
|
*
|
||||||
|
* @param synth
|
||||||
|
*/
|
||||||
|
SFIZZ_EXPORTED_API void sfizz_enable_logging(sfizz_synth_t* synth);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Disable logging
|
||||||
|
*
|
||||||
|
* @param synth
|
||||||
|
*/
|
||||||
|
SFIZZ_EXPORTED_API void sfizz_disable_logging(sfizz_synth_t* synth);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,17 @@ public:
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
bool shouldReloadFile();
|
bool shouldReloadFile();
|
||||||
|
/**
|
||||||
|
* @brief Enable logging of timings to sidecar CSV files. This can produce
|
||||||
|
* many outputs so use with caution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void enableLogging() noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Disable logging;
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void disableLogging() noexcept;
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<sfz::Synth> synth;
|
std::unique_ptr<sfz::Synth> synth;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ namespace config {
|
||||||
constexpr int maxBlockSize { 8192 };
|
constexpr int maxBlockSize { 8192 };
|
||||||
constexpr int preloadSize { 8192 };
|
constexpr int preloadSize { 8192 };
|
||||||
constexpr int loggerQueueSize { 16 };
|
constexpr int loggerQueueSize { 16 };
|
||||||
|
constexpr int voiceLoggerQueueSize { 256 };
|
||||||
constexpr bool loggingEnabled { false };
|
constexpr bool loggingEnabled { false };
|
||||||
constexpr size_t numChannels { 2 };
|
constexpr size_t numChannels { 2 };
|
||||||
constexpr int numBackgroundThreads { 4 };
|
constexpr int numBackgroundThreads { 4 };
|
||||||
|
|
|
||||||
|
|
@ -324,8 +324,6 @@ void sfz::FilePool::loadingThread() noexcept
|
||||||
|
|
||||||
threadsLoading--;
|
threadsLoading--;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
while (!filledPromiseQueue.try_push(promise)) {
|
while (!filledPromiseQueue.try_push(promise)) {
|
||||||
DBG("[sfizz] Error enqueuing the promise for " << promise->filename << " in the filledPromiseQueue");
|
DBG("[sfizz] Error enqueuing the promise for " << promise->filename << " in the filledPromiseQueue");
|
||||||
std::this_thread::sleep_for(1ms);
|
std::this_thread::sleep_for(1ms);
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ sfz::Logger::~Logger()
|
||||||
<< prefix
|
<< prefix
|
||||||
<< "_file_log.csv";
|
<< "_file_log.csv";
|
||||||
fs::path fileLogPath{ fs::current_path() / fileLogFilename.str() };
|
fs::path fileLogPath{ fs::current_path() / fileLogFilename.str() };
|
||||||
DBG("Logging file times to " << fileLogPath.filename());
|
std::cout << "Logging " << fileTimes.size() << " file times to " << fileLogPath.filename() << '\n';
|
||||||
std::ofstream loadLogFile { fileLogPath.string() };
|
std::ofstream loadLogFile { fileLogPath.string() };
|
||||||
loadLogFile << "WaitDuration,LoadDuration,FileSize,FileName" << '\n';
|
loadLogFile << "WaitDuration,LoadDuration,FileSize,FileName" << '\n';
|
||||||
for (auto& time: fileTimes)
|
for (auto& time: fileTimes)
|
||||||
|
|
@ -81,23 +81,28 @@ sfz::Logger::~Logger()
|
||||||
<< prefix
|
<< prefix
|
||||||
<< "_callback_log.csv";
|
<< "_callback_log.csv";
|
||||||
fs::path callbackLogPath{ fs::current_path() / callbackLogFilename.str() };
|
fs::path callbackLogPath{ fs::current_path() / callbackLogFilename.str() };
|
||||||
DBG("Logging callback times to " << callbackLogPath.filename());
|
std::cout << "Logging " << callbackTimes.size() << " callback times to " << callbackLogPath.filename() << '\n';
|
||||||
std::ofstream callbackLogFile { callbackLogPath.string() };
|
std::ofstream callbackLogFile { callbackLogPath.string() };
|
||||||
callbackLogFile << "Duration,NumVoices,NumSamples" << '\n';
|
callbackLogFile << "Dispatch,RenderMethod,Data,Amplitude,Filters,Panning,NumVoices,NumSamples" << '\n';
|
||||||
for (auto& time: callbackTimes)
|
for (auto& time: callbackTimes)
|
||||||
callbackLogFile << time.duration.count() << ','
|
callbackLogFile << time.breakdown.dispatch.count() << ','
|
||||||
|
<< time.breakdown.renderMethod.count() << ','
|
||||||
|
<< time.breakdown.data.count() << ','
|
||||||
|
<< time.breakdown.amplitude.count() << ','
|
||||||
|
<< time.breakdown.filters.count() << ','
|
||||||
|
<< time.breakdown.panning.count() << ','
|
||||||
<< time.numVoices << ','
|
<< time.numVoices << ','
|
||||||
<< time.numSamples << '\n';
|
<< time.numSamples << '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void sfz::Logger::logCallbackTime(std::chrono::duration<double> duration, int numVoices, size_t numSamples)
|
void sfz::Logger::logCallbackTime(CallbackBreakdown&& breakdown, int numVoices, size_t numSamples)
|
||||||
{
|
{
|
||||||
if (!loggingEnabled)
|
if (!loggingEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
callbackTimeQueue.try_push<CallbackTime>({ duration, numVoices, numSamples });
|
callbackTimeQueue.try_push<CallbackTime>({ std::move(breakdown), numVoices, numSamples });
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Logger::logFileTime(std::chrono::duration<double> waitDuration, std::chrono::duration<double> loadDuration, uint32_t fileSize, absl::string_view filename)
|
void sfz::Logger::logFileTime(std::chrono::duration<double> waitDuration, std::chrono::duration<double> loadDuration, uint32_t fileSize, absl::string_view filename)
|
||||||
|
|
@ -126,7 +131,7 @@ void sfz::Logger::moveEvents() noexcept
|
||||||
while (callbackTimeQueue.try_pop(callbackTime))
|
while (callbackTimeQueue.try_pop(callbackTime))
|
||||||
callbackTimes.push_back(callbackTime);
|
callbackTimes.push_back(callbackTime);
|
||||||
|
|
||||||
sfz::FileTime fileTime;
|
FileTime fileTime;
|
||||||
while (fileTimeQueue.try_pop(fileTime))
|
while (fileTimeQueue.try_pop(fileTime))
|
||||||
fileTimes.push_back(fileTime);
|
fileTimes.push_back(fileTime);
|
||||||
|
|
||||||
|
|
@ -135,7 +140,7 @@ void sfz::Logger::moveEvents() noexcept
|
||||||
callbackTimes.clear();
|
callbackTimes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(50ms);
|
std::this_thread::sleep_for(10ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,3 +154,22 @@ void sfz::Logger::disableLogging()
|
||||||
loggingEnabled = false;
|
loggingEnabled = false;
|
||||||
clearFlag.clear();
|
clearFlag.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sfz::ScopedTiming::ScopedTiming(Duration& targetDuration, Operation operation)
|
||||||
|
: targetDuration(targetDuration), operation(operation)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sfz::ScopedTiming::~ScopedTiming()
|
||||||
|
{
|
||||||
|
switch(operation)
|
||||||
|
{
|
||||||
|
case(Operation::replaceDuration):
|
||||||
|
targetDuration = std::chrono::high_resolution_clock::now() - creationTime;
|
||||||
|
break;
|
||||||
|
case(Operation::addToDuration):
|
||||||
|
targetDuration += std::chrono::high_resolution_clock::now() - creationTime;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,23 +10,62 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <functional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include "absl/strings/string_view.h"
|
#include "absl/strings/string_view.h"
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
||||||
|
using Duration = std::chrono::duration<double>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates an RAII logger which fills or adds to a duration on destruction
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
struct ScopedTiming
|
||||||
|
{
|
||||||
|
using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock>;
|
||||||
|
enum class Operation
|
||||||
|
{
|
||||||
|
addToDuration,
|
||||||
|
replaceDuration
|
||||||
|
};
|
||||||
|
ScopedTiming() = delete;
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Scoped Logger object
|
||||||
|
*
|
||||||
|
* @param targetDuration
|
||||||
|
* @param op
|
||||||
|
*/
|
||||||
|
ScopedTiming(Duration& targetDuration, Operation op = Operation::replaceDuration);
|
||||||
|
~ScopedTiming();
|
||||||
|
Duration& targetDuration;
|
||||||
|
const Operation operation;
|
||||||
|
const TimePoint creationTime { std::chrono::high_resolution_clock::now() };
|
||||||
|
};
|
||||||
|
|
||||||
struct FileTime
|
struct FileTime
|
||||||
{
|
{
|
||||||
std::chrono::duration<double> waitDuration;
|
Duration waitDuration;
|
||||||
std::chrono::duration<double> loadDuration;
|
Duration loadDuration;
|
||||||
uint32_t fileSize;
|
uint32_t fileSize;
|
||||||
absl::string_view filename;
|
absl::string_view filename;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CallbackBreakdown
|
||||||
|
{
|
||||||
|
Duration dispatch { 0 };
|
||||||
|
Duration renderMethod { 0 };
|
||||||
|
Duration data { 0 };
|
||||||
|
Duration amplitude { 0 };
|
||||||
|
Duration filters { 0 };
|
||||||
|
Duration panning { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
struct CallbackTime
|
struct CallbackTime
|
||||||
{
|
{
|
||||||
std::chrono::duration<double> duration;
|
CallbackBreakdown breakdown;
|
||||||
int numVoices;
|
int numVoices;
|
||||||
size_t numSamples;
|
size_t numSamples;
|
||||||
};
|
};
|
||||||
|
|
@ -36,13 +75,53 @@ class Logger
|
||||||
public:
|
public:
|
||||||
Logger();
|
Logger();
|
||||||
~Logger();
|
~Logger();
|
||||||
|
/**
|
||||||
|
* @brief Set the prefix for the output log files
|
||||||
|
*
|
||||||
|
* @param prefix
|
||||||
|
*/
|
||||||
void setPrefix(const std::string& prefix);
|
void setPrefix(const std::string& prefix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes all logged data
|
||||||
|
*
|
||||||
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enables logging and writing to log files on destruction
|
||||||
|
*
|
||||||
|
*/
|
||||||
void enableLogging();
|
void enableLogging();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Disables logging and writing to log files on destruction
|
||||||
|
*
|
||||||
|
*/
|
||||||
void disableLogging();
|
void disableLogging();
|
||||||
void logCallbackTime(std::chrono::duration<double> duration, int numVoices, size_t numSamples);
|
|
||||||
void logFileTime(std::chrono::duration<double> waitDuration, std::chrono::duration<double> loadDuration, uint32_t fileSize, absl::string_view filename);
|
/**
|
||||||
|
* @brief Logs the callback duration, with breakdown per operations
|
||||||
|
*
|
||||||
|
* @param breakdown The different timings for the callback
|
||||||
|
* @param numVoices The number of active voices
|
||||||
|
* @param numSamples The number of samples in the callback
|
||||||
|
*/
|
||||||
|
void logCallbackTime(CallbackBreakdown&& breakdown, int numVoices, size_t numSamples);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Log a file loading and waiting duration
|
||||||
|
*
|
||||||
|
* @param waitDuration The time spent waiting before loading the file
|
||||||
|
* @param loadDuration The time it took to load the file
|
||||||
|
* @param fileSize The file size
|
||||||
|
* @param filename The file name
|
||||||
|
*/
|
||||||
|
void logFileTime(Duration waitDuration, Duration loadDuration, uint32_t fileSize, absl::string_view filename);
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Move all events from the real time queues to the non-realtime vectors
|
||||||
|
*/
|
||||||
void moveEvents() noexcept;
|
void moveEvents() noexcept;
|
||||||
bool loggingEnabled { config::loggingEnabled };
|
bool loggingEnabled { config::loggingEnabled };
|
||||||
std::string prefix { "" };
|
std::string prefix { "" };
|
||||||
|
|
|
||||||
|
|
@ -410,11 +410,7 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept
|
||||||
void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
{
|
{
|
||||||
ScopedFTZ ftz;
|
ScopedFTZ ftz;
|
||||||
const auto callbackStartTime = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
buffer.fill(0.0f);
|
|
||||||
|
|
||||||
resources.filePool.cleanupPromises();
|
|
||||||
|
|
||||||
if (freeWheeling)
|
if (freeWheeling)
|
||||||
resources.filePool.waitForBackgroundLoading();
|
resources.filePool.waitForBackgroundLoading();
|
||||||
|
|
@ -423,20 +419,35 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
if (!canEnterCallback)
|
if (!canEnterCallback)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto tempSpan = AudioSpan<float>(tempBuffer).first(buffer.getNumFrames());
|
CallbackBreakdown callbackBreakdown;
|
||||||
int numActiveVoices { 0 };
|
int numActiveVoices { 0 };
|
||||||
for (auto& voice : voices) {
|
{ // Main render block
|
||||||
if (!voice->isFree()) {
|
ScopedTiming logger { callbackBreakdown.renderMethod };
|
||||||
numActiveVoices++;
|
buffer.fill(0.0f);
|
||||||
voice->renderBlock(tempSpan);
|
resources.filePool.cleanupPromises();
|
||||||
buffer.add(tempSpan);
|
|
||||||
|
|
||||||
|
auto tempSpan = AudioSpan<float>(tempBuffer).first(buffer.getNumFrames());
|
||||||
|
for (auto& voice : voices) {
|
||||||
|
if (!voice->isFree()) {
|
||||||
|
numActiveVoices++;
|
||||||
|
voice->renderBlock(tempSpan);
|
||||||
|
buffer.add(tempSpan);
|
||||||
|
callbackBreakdown.data += voice->getLastDataDuration();
|
||||||
|
callbackBreakdown.amplitude += voice->getLastAmplitudeDuration();
|
||||||
|
callbackBreakdown.filters += voice->getLastFilterDuration();
|
||||||
|
callbackBreakdown.panning += voice->getLastPanningDuration();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer.applyGain(db2mag(volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.applyGain(db2mag(volume));
|
callbackBreakdown.dispatch = dispatchDuration;
|
||||||
|
resources.logger.logCallbackTime(std::move(callbackBreakdown), numActiveVoices, buffer.getNumFrames());
|
||||||
|
|
||||||
const auto callbackDuration = std::chrono::high_resolution_clock::now() - callbackStartTime;
|
// Reset the dispatch counter
|
||||||
resources.logger.logCallbackTime(callbackDuration, numActiveVoices, buffer.getNumFrames());
|
dispatchDuration = Duration(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept
|
void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept
|
||||||
|
|
@ -444,6 +455,7 @@ void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept
|
||||||
ASSERT(noteNumber < 128);
|
ASSERT(noteNumber < 128);
|
||||||
ASSERT(noteNumber >= 0);
|
ASSERT(noteNumber >= 0);
|
||||||
|
|
||||||
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
resources.midiState.noteOnEvent(noteNumber, velocity);
|
resources.midiState.noteOnEvent(noteNumber, velocity);
|
||||||
|
|
||||||
AtomicGuard callbackGuard { inCallback };
|
AtomicGuard callbackGuard { inCallback };
|
||||||
|
|
@ -458,6 +470,7 @@ void sfz::Synth::noteOff(int delay, int noteNumber, uint8_t velocity [[maybe_unu
|
||||||
ASSERT(noteNumber < 128);
|
ASSERT(noteNumber < 128);
|
||||||
ASSERT(noteNumber >= 0);
|
ASSERT(noteNumber >= 0);
|
||||||
|
|
||||||
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
resources.midiState.noteOffEvent(noteNumber, velocity);
|
resources.midiState.noteOffEvent(noteNumber, velocity);
|
||||||
|
|
||||||
AtomicGuard callbackGuard { inCallback };
|
AtomicGuard callbackGuard { inCallback };
|
||||||
|
|
@ -513,6 +526,8 @@ void sfz::Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept
|
||||||
ASSERT(ccNumber < config::numCCs);
|
ASSERT(ccNumber < config::numCCs);
|
||||||
ASSERT(ccNumber >= 0);
|
ASSERT(ccNumber >= 0);
|
||||||
|
|
||||||
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
|
|
||||||
resources.midiState.ccEvent(ccNumber, ccValue);
|
resources.midiState.ccEvent(ccNumber, ccValue);
|
||||||
|
|
||||||
AtomicGuard callbackGuard { inCallback };
|
AtomicGuard callbackGuard { inCallback };
|
||||||
|
|
@ -543,6 +558,8 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept
|
||||||
ASSERT(pitch <= 8192);
|
ASSERT(pitch <= 8192);
|
||||||
ASSERT(pitch >= -8192);
|
ASSERT(pitch >= -8192);
|
||||||
|
|
||||||
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
|
|
||||||
resources.midiState.pitchBendEvent(pitch);
|
resources.midiState.pitchBendEvent(pitch);
|
||||||
|
|
||||||
for (auto& region: regions) {
|
for (auto& region: regions) {
|
||||||
|
|
@ -555,10 +572,11 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept
|
||||||
}
|
}
|
||||||
void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept
|
void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept
|
||||||
{
|
{
|
||||||
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
}
|
}
|
||||||
void sfz::Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept
|
void sfz::Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept
|
||||||
{
|
{
|
||||||
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
}
|
}
|
||||||
|
|
||||||
int sfz::Synth::getNumRegions() const noexcept
|
int sfz::Synth::getNumRegions() const noexcept
|
||||||
|
|
@ -806,3 +824,13 @@ bool sfz::Synth::shouldReloadFile()
|
||||||
{
|
{
|
||||||
return (checkModificationTime() > modificationTime);
|
return (checkModificationTime() > modificationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::enableLogging() noexcept
|
||||||
|
{
|
||||||
|
resources.logger.enableLogging();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::disableLogging() noexcept
|
||||||
|
{
|
||||||
|
resources.logger.disableLogging();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,17 @@ public:
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
bool shouldReloadFile();
|
bool shouldReloadFile();
|
||||||
|
/**
|
||||||
|
* @brief Enable logging of timings to sidecar CSV files. This can produce
|
||||||
|
* many outputs so use with caution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void enableLogging() noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Disable logging;
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void disableLogging() noexcept;
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* @brief The parser callback; this is called by the parent object each time
|
* @brief The parser callback; this is called by the parent object each time
|
||||||
|
|
@ -456,6 +467,8 @@ private:
|
||||||
int noteOffset { 0 };
|
int noteOffset { 0 };
|
||||||
int octaveOffset { 0 };
|
int octaveOffset { 0 };
|
||||||
|
|
||||||
|
Duration dispatchDuration { 0 };
|
||||||
|
|
||||||
fs::file_time_type modificationTime { };
|
fs::file_time_type modificationTime { };
|
||||||
|
|
||||||
LEAK_DETECTOR(Synth);
|
LEAK_DETECTOR(Synth);
|
||||||
|
|
|
||||||
|
|
@ -250,10 +250,13 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
auto delayed_buffer = buffer.subspan(delay);
|
auto delayed_buffer = buffer.subspan(delay);
|
||||||
initialDelay -= static_cast<int>(delay);
|
initialDelay -= static_cast<int>(delay);
|
||||||
|
|
||||||
if (region->isGenerator())
|
{ // Fill buffer with raw data
|
||||||
fillWithGenerator(delayed_buffer);
|
ScopedTiming logger { dataDuration };
|
||||||
else
|
if (region->isGenerator())
|
||||||
fillWithData(delayed_buffer);
|
fillWithGenerator(delayed_buffer);
|
||||||
|
else
|
||||||
|
fillWithData(delayed_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
if (region->isStereo)
|
if (region->isStereo)
|
||||||
processStereo(buffer);
|
processStereo(buffer);
|
||||||
|
|
@ -275,39 +278,50 @@ void sfz::Voice::processMono(AudioSpan<float> buffer) noexcept
|
||||||
|
|
||||||
auto modulationSpan = tempSpan1.first(numSamples);
|
auto modulationSpan = tempSpan1.first(numSamples);
|
||||||
|
|
||||||
// Amplitude envelope
|
{ // Amplitude processing
|
||||||
amplitudeEnvelope.getBlock(modulationSpan);
|
ScopedTiming logger { amplitudeDuration };
|
||||||
applyGain<float>(modulationSpan, leftBuffer);
|
|
||||||
|
|
||||||
// Crossfade envelope
|
// Amplitude envelope
|
||||||
crossfadeEnvelope.getBlock(modulationSpan);
|
amplitudeEnvelope.getBlock(modulationSpan);
|
||||||
applyGain<float>(modulationSpan, leftBuffer);
|
applyGain<float>(modulationSpan, leftBuffer);
|
||||||
|
|
||||||
// Volume envelope
|
// Crossfade envelope
|
||||||
volumeEnvelope.getBlock(modulationSpan);
|
crossfadeEnvelope.getBlock(modulationSpan);
|
||||||
applyGain<float>(modulationSpan, leftBuffer);
|
applyGain<float>(modulationSpan, leftBuffer);
|
||||||
|
|
||||||
// AmpEG envelope
|
// Volume envelope
|
||||||
egEnvelope.getBlock(modulationSpan);
|
volumeEnvelope.getBlock(modulationSpan);
|
||||||
applyGain<float>(modulationSpan, leftBuffer);
|
applyGain<float>(modulationSpan, leftBuffer);
|
||||||
|
|
||||||
// Filtering and EQ
|
// AmpEG envelope
|
||||||
const float* inputChannel[1] { leftBuffer.data() };
|
egEnvelope.getBlock(modulationSpan);
|
||||||
float* outputChannel[1] { leftBuffer.data() };
|
applyGain<float>(modulationSpan, leftBuffer);
|
||||||
for (auto& filter: filters) {
|
|
||||||
filter->process(inputChannel, outputChannel, numSamples);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& eq: equalizers) {
|
{ // Filtering and EQ
|
||||||
eq->process(inputChannel, outputChannel, numSamples);
|
ScopedTiming logger { filterDuration };
|
||||||
|
|
||||||
|
const float* inputChannel[1] { leftBuffer.data() };
|
||||||
|
float* outputChannel[1] { leftBuffer.data() };
|
||||||
|
for (auto& filter: filters) {
|
||||||
|
filter->process(inputChannel, outputChannel, numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& eq: equalizers) {
|
||||||
|
eq->process(inputChannel, outputChannel, numSamples);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare for stereo output
|
{ // Panning and stereo processing
|
||||||
copy<float>(leftBuffer, rightBuffer);
|
ScopedTiming logger { panningDuration };
|
||||||
|
|
||||||
// Apply panning
|
// Prepare for stereo output
|
||||||
panEnvelope.getBlock(modulationSpan);
|
copy<float>(leftBuffer, rightBuffer);
|
||||||
pan<float>(modulationSpan, leftBuffer, rightBuffer);
|
|
||||||
|
// Apply panning
|
||||||
|
panEnvelope.getBlock(modulationSpan);
|
||||||
|
pan<float>(modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::processStereo(AudioSpan<float> buffer) noexcept
|
void sfz::Voice::processStereo(AudioSpan<float> buffer) noexcept
|
||||||
|
|
@ -317,38 +331,49 @@ void sfz::Voice::processStereo(AudioSpan<float> buffer) noexcept
|
||||||
auto leftBuffer = buffer.getSpan(0);
|
auto leftBuffer = buffer.getSpan(0);
|
||||||
auto rightBuffer = buffer.getSpan(1);
|
auto rightBuffer = buffer.getSpan(1);
|
||||||
|
|
||||||
// Amplitude envelope
|
{ // Amplitude processing
|
||||||
amplitudeEnvelope.getBlock(modulationSpan);
|
ScopedTiming logger { amplitudeDuration };
|
||||||
buffer.applyGain(modulationSpan);
|
|
||||||
|
|
||||||
// Crossfade envelope
|
// Amplitude envelope
|
||||||
crossfadeEnvelope.getBlock(modulationSpan);
|
amplitudeEnvelope.getBlock(modulationSpan);
|
||||||
buffer.applyGain(modulationSpan);
|
buffer.applyGain(modulationSpan);
|
||||||
|
|
||||||
// Volume envelope
|
// Crossfade envelope
|
||||||
volumeEnvelope.getBlock(modulationSpan);
|
crossfadeEnvelope.getBlock(modulationSpan);
|
||||||
buffer.applyGain(modulationSpan);
|
buffer.applyGain(modulationSpan);
|
||||||
|
|
||||||
// AmpEG envelope
|
// Volume envelope
|
||||||
egEnvelope.getBlock(modulationSpan);
|
volumeEnvelope.getBlock(modulationSpan);
|
||||||
buffer.applyGain(modulationSpan);
|
buffer.applyGain(modulationSpan);
|
||||||
|
|
||||||
// Apply the width/position process
|
// AmpEG envelope
|
||||||
widthEnvelope.getBlock(modulationSpan);
|
egEnvelope.getBlock(modulationSpan);
|
||||||
width<float>(modulationSpan, leftBuffer, rightBuffer);
|
buffer.applyGain(modulationSpan);
|
||||||
positionEnvelope.getBlock(modulationSpan);
|
|
||||||
pan<float>(modulationSpan, leftBuffer, rightBuffer);
|
|
||||||
|
|
||||||
// Filtering and EQ
|
|
||||||
const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() };
|
|
||||||
float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() };
|
|
||||||
|
|
||||||
for (auto& filter: filters) {
|
|
||||||
filter->process(inputChannels, outputChannels, numSamples);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& eq: equalizers) {
|
{ // Panning and stereo processing
|
||||||
eq->process(inputChannels, outputChannels, numSamples);
|
ScopedTiming logger { panningDuration };
|
||||||
|
|
||||||
|
// Apply the width/position process
|
||||||
|
widthEnvelope.getBlock(modulationSpan);
|
||||||
|
width<float>(modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
positionEnvelope.getBlock(modulationSpan);
|
||||||
|
pan<float>(modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Filtering and EQ
|
||||||
|
ScopedTiming logger { filterDuration };
|
||||||
|
|
||||||
|
const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() };
|
||||||
|
float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() };
|
||||||
|
|
||||||
|
for (auto& filter: filters) {
|
||||||
|
filter->process(inputChannels, outputChannels, numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& eq: equalizers) {
|
||||||
|
eq->process(inputChannels, outputChannels, numSamples);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,12 @@ public:
|
||||||
* @param numFilters
|
* @param numFilters
|
||||||
*/
|
*/
|
||||||
void setMaxEQsPerVoice(size_t numEQs);
|
void setMaxEQsPerVoice(size_t numEQs);
|
||||||
|
|
||||||
|
Duration getLastDataDuration() const noexcept { return dataDuration; }
|
||||||
|
Duration getLastAmplitudeDuration() const noexcept { return amplitudeDuration; }
|
||||||
|
Duration getLastFilterDuration() const noexcept { return filterDuration; }
|
||||||
|
Duration getLastPanningDuration() const noexcept { return panningDuration; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief Fill a span with data from a file source. This is the first step
|
* @brief Fill a span with data from a file source. This is the first step
|
||||||
|
|
@ -301,6 +307,11 @@ private:
|
||||||
MultiplicativeEnvelope<float> volumeEnvelope;
|
MultiplicativeEnvelope<float> volumeEnvelope;
|
||||||
float bendStepFactor { centsFactor(1) };
|
float bendStepFactor { centsFactor(1) };
|
||||||
|
|
||||||
|
Duration dataDuration;
|
||||||
|
Duration amplitudeDuration;
|
||||||
|
Duration panningDuration;
|
||||||
|
Duration filterDuration;
|
||||||
|
|
||||||
std::normal_distribution<float> noiseDist { 0, config::noiseVariance };
|
std::normal_distribution<float> noiseDist { 0, config::noiseVariance };
|
||||||
|
|
||||||
HistoricalBuffer<float> powerHistory { config::powerHistoryLength };
|
HistoricalBuffer<float> powerHistory { config::powerHistoryLength };
|
||||||
|
|
|
||||||
|
|
@ -184,3 +184,13 @@ bool sfz::Sfizz::shouldReloadFile()
|
||||||
{
|
{
|
||||||
return synth->shouldReloadFile();
|
return synth->shouldReloadFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::Sfizz::enableLogging() noexcept
|
||||||
|
{
|
||||||
|
synth->enableLogging();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Sfizz::disableLogging() noexcept
|
||||||
|
{
|
||||||
|
synth->disableLogging();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,17 @@ bool sfizz_should_reload_file(sfizz_synth_t* synth)
|
||||||
return self->shouldReloadFile();
|
return self->shouldReloadFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfizz_enable_logging(sfizz_synth_t* synth)
|
||||||
|
{
|
||||||
|
auto self = reinterpret_cast<sfz::Synth*>(synth);
|
||||||
|
return self->enableLogging();
|
||||||
|
}
|
||||||
|
void sfizz_disable_logging(sfizz_synth_t* synth)
|
||||||
|
{
|
||||||
|
auto self = reinterpret_cast<sfz::Synth*>(synth);
|
||||||
|
return self->disableLogging();
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue