diff --git a/.gitmodules b/.gitmodules index 51c8c10..5694dc3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ path = third_party/tclap url = https://git.code.sf.net/p/tclap/code branch = 1.4 +[submodule "third_party/benchmark"] + path = third_party/benchmark + url = https://github.com/google/benchmark diff --git a/CMakeLists.txt b/CMakeLists.txt index d50ac21..22293fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.19) project(Canvas) +option(CANVAS_BENCHMARK "Create benchmark executable" OFF) + set(CMAKE_CXX_STANDARD 14) set( @@ -79,5 +81,17 @@ else() target_include_directories(canvas PRIVATE ${FFTW_INCLUDE_DIRS}) target_link_libraries(canvas PRIVATE ${FFTW_LIBRARIES}) - target_link_libraries(canvas PRIVATE portaudio_static) + target_link_libraries(canvas PRIVATE portaudio_static) +endif() + +if(CANVAS_BENCHMARK) + set(BENCHMARK_ENABLE_GTEST_TESTS OFF) + add_subdirectory(third_party/benchmark) + add_executable( + canvas_benchmark + src/benchmark/benchmark.cpp + src/Synth.hpp + src/Synth.cpp + ) + target_link_libraries(canvas_benchmark benchmark::benchmark portaudio_static) endif() diff --git a/src/Synth.cpp b/src/Synth.cpp index 5e2802a..d41be59 100644 --- a/src/Synth.cpp +++ b/src/Synth.cpp @@ -64,10 +64,11 @@ Oscillator::Oscillator(float sampleRate, float frequency, float phase) : m_sampleRate(sampleRate) , m_frequency(frequency) , m_phase(phase) + , m_phaseInc(m_frequency / m_sampleRate) { } -void Oscillator::processAdd(float* out1, float* out2, int blockSize) { +void Oscillator::processAddOriginal(float* out1, float* out2, int blockSize) { for (int i = 0; i < blockSize; i++) { m_phase = std::fmod(m_phase + m_frequency / m_sampleRate, 1.0); float distortedPhase = std::fmod(distortPhase(m_phase, m_pdMode, m_pdDistort), 1.0); @@ -93,6 +94,37 @@ void Oscillator::processAdd(float* out1, float* out2, int blockSize) { m_amplitudeRight = m_targetAmplitudeRight; } +void Oscillator::processAdd(float* out1, float* out2, int blockSize) { + float blockIncrement = 1.0 / blockSize; + for (int i = 0; i < blockSize; i++) { + m_phase += m_phaseInc; + while (m_phase > 1) { + m_phase -= 1; + } + float distortedPhase = distortPhase(m_phase, m_pdMode, m_pdDistort); + int integerPhase = distortedPhase * 2048; + float frac = distortedPhase * 2048 - integerPhase; + int integerPhase2 = (integerPhase + 1) % 2048; + float amplitudeLeft = ( + m_amplitudeLeft * (1 - i * blockIncrement) + + m_targetAmplitudeLeft * i * blockIncrement + ); + float amplitudeRight = ( + m_amplitudeRight * (1 - i * blockIncrement) + + m_targetAmplitudeRight * i * blockIncrement + ); + float outSample = ( + k_sineTable2048[integerPhase] * (1 - frac) + + k_sineTable2048[integerPhase2] * frac + ); + out1[i] += outSample * amplitudeLeft; + out2[i] += outSample * amplitudeRight; + } + m_amplitudeLeft = m_targetAmplitudeLeft; + m_amplitudeRight = m_targetAmplitudeRight; +} + + Synth::Synth(float sampleRate, std::mt19937& randomEngine) : m_sampleRate(sampleRate) { @@ -165,6 +197,25 @@ void Synth::process( } } +void Synth::processOriginal( + int output_channels, + float** output_buffer, + int frame_count +) { + if (output_channels != 2) { + std::cout << "Output channels is not 2. This shouldn't happen!" << std::endl; + exit(1); + } + + for (int j = 0; j < frame_count; j++) { + output_buffer[0][j] = 0; + output_buffer[1][j] = 0; + } + for (auto& oscillator : m_oscillators) { + oscillator->processAddOriginal(output_buffer[0], output_buffer[1], frame_count); + } +} + void Synth::processRealtime( int outputChannels, float** outputBuffer, diff --git a/src/Synth.hpp b/src/Synth.hpp index 1b9a91b..8978012 100644 --- a/src/Synth.hpp +++ b/src/Synth.hpp @@ -11,6 +11,7 @@ class Oscillator { public: Oscillator(float sampleRate, float frequency, float phase); void processAdd(float* out1, float* out2, int blockSize); + void processAddOriginal(float* out1, float* out2, int blockSize); void setTargetAmplitudeLeft(float amplitude) { m_targetAmplitudeLeft = amplitude; }; void setTargetAmplitudeRight(float amplitude) { m_targetAmplitudeRight = amplitude; }; @@ -21,6 +22,7 @@ class Oscillator { const float m_sampleRate; float m_phase = 0; const float m_frequency; + const float m_phaseInc; float m_amplitudeLeft = 0.0; float m_targetAmplitudeLeft = 0.0; float m_amplitudeRight = 0.0; @@ -48,6 +50,12 @@ class Synth { int frame_count ); + void processOriginal( + int output_channels, + float** output_buffer, + int frame_count + ); + void processRealtime( int output_channels, float** output_buffer, diff --git a/src/benchmark/benchmark.cpp b/src/benchmark/benchmark.cpp new file mode 100644 index 0000000..5f61bf6 --- /dev/null +++ b/src/benchmark/benchmark.cpp @@ -0,0 +1,44 @@ +#include +#include "../Synth.hpp" + +static void benchSynthProcess(benchmark::State& state) { + std::mt19937 randomEngine(0); + Synth synth(48000, randomEngine); + + for (int i = 0; i < synth.getNumOscillators(); i++) { + synth.setOscillatorAmplitude(i, 1, 1); + } + + int frameCount = 64; + float outputBufferLeft[64]; + float outputBufferRight[64]; + float* outputBuffer[2] = { outputBufferLeft, outputBufferRight }; + + for (auto _ : state) { + synth.process(2, outputBuffer, frameCount); + } +} +// Register the function as a benchmark +BENCHMARK(benchSynthProcess); + +static void benchSynthProcessOriginal(benchmark::State& state) { + std::mt19937 randomEngine(0); + Synth synth(48000, randomEngine); + + for (int i = 0; i < synth.getNumOscillators(); i++) { + synth.setOscillatorAmplitude(i, 1, 1); + } + + int frameCount = 64; + float outputBufferLeft[64]; + float outputBufferRight[64]; + float* outputBuffer[2] = { outputBufferLeft, outputBufferRight }; + + for (auto _ : state) { + synth.processOriginal(2, outputBuffer, frameCount); + } +} +// Register the function as a benchmark +BENCHMARK(benchSynthProcessOriginal); + +BENCHMARK_MAIN(); diff --git a/tests/test_canvas.py b/tests/test_canvas.py index c9f8572..47bf0db 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -162,4 +162,4 @@ def test_image_to_sound_regression(canvas, gradient_image, write_regtests): expected_sound, expected_rate = soundfile.read(regression_test_file) assert rate == expected_rate - np.testing.assert_allclose(sound, expected_sound) + np.testing.assert_allclose(sound, expected_sound, rtol=0, atol=1e-6) diff --git a/third_party/benchmark b/third_party/benchmark new file mode 160000 index 0000000..2d054b6 --- /dev/null +++ b/third_party/benchmark @@ -0,0 +1 @@ +Subproject commit 2d054b683f293dbee21f8fe8820d967360e6af83