From 3b1f9e1767ef3042c91636dc432e6eb072ac10e4 Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Fri, 16 Oct 2026 07:23:41 +0800 Subject: [PATCH] Initialize oscillator history and AD envelope trigger state --- Source/Control/adenv.cpp | 2 + Source/Synthesis/oscillator.h | 1 + tests/initialization/README.md | 19 +++++++ tests/initialization/tst_initialization.cpp | 62 +++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 tests/initialization/README.md create mode 100644 tests/initialization/tst_initialization.cpp diff --git a/Source/Control/adenv.cpp b/Source/Control/adenv.cpp index 4e9568b7..422bcdcf 100644 --- a/Source/Control/adenv.cpp +++ b/Source/Control/adenv.cpp @@ -52,6 +52,8 @@ void AdEnv::Init(float sample_rate) { sample_rate_ = sample_rate; current_segment_ = ADENV_SEG_IDLE; + prev_segment_ = ADENV_SEG_IDLE; + trigger_ = 0; curve_scalar_ = 0.0f; // full linear phase_ = 0; min_ = 0.0f; diff --git a/Source/Synthesis/oscillator.h b/Source/Synthesis/oscillator.h index 469d09a0..87a552b9 100644 --- a/Source/Synthesis/oscillator.h +++ b/Source/Synthesis/oscillator.h @@ -56,6 +56,7 @@ class Oscillator pw_ = 0.5f; phase_ = 0.0f; phase_inc_ = CalcPhaseInc(freq_); + last_out_ = 0.0f; waveform_ = WAVE_SIN; eoc_ = true; eor_ = true; diff --git a/tests/initialization/README.md b/tests/initialization/README.md new file mode 100644 index 00000000..90b5fc10 --- /dev/null +++ b/tests/initialization/README.md @@ -0,0 +1,19 @@ +# Initialization regression tests + +These host-only tests exercise public oscillator and AD envelope initialization +with synthetic samples. They do not open audio devices or require libDaisy. + +From the repository root, using CMake and a C++14 compiler: + +```sh +cmake -S . -B build-host +cmake --build build-host +c++ -std=c++14 -I Source tests/initialization/tst_initialization.cpp build-host/libDaisySP.a -o test-initialization +./test-initialization +``` + +The tests process a polyBLEP triangle, reinitialize the oscillator, and check +that its integrator starts from zero. They also reinitialize an AD envelope +with a pending trigger and verify that processing leaves it idle. Static test +objects make the original failure deterministic without reading arbitrary +uninitialized memory. Both cases run at 44.1, 48, and 96 kHz. diff --git a/tests/initialization/tst_initialization.cpp b/tests/initialization/tst_initialization.cpp new file mode 100644 index 00000000..7bfbe781 --- /dev/null +++ b/tests/initialization/tst_initialization.cpp @@ -0,0 +1,62 @@ +#include "daisysp.h" +#include +#include + +using namespace daisysp; + +int main() +{ + // Static storage supplies a defined initial history, so the regression does + // not depend on the contents of uninitialized memory on the first run. + static Oscillator osc; + static AdEnv env; + int failures = 0; + + for(float sample_rate : {44100.f, 48000.f, 96000.f}) + { + osc.Init(sample_rate); + osc.SetWaveform(Oscillator::WAVE_POLYBLEP_TRI); + osc.SetFreq(220.f); + for(int i = 0; i < 64; ++i) + osc.Process(); + + osc.Init(sample_rate); + osc.SetWaveform(Oscillator::WAVE_POLYBLEP_TRI); + osc.SetFreq(220.f); + // At phase zero the corrected square input is zero. With the + // integrator initialized to zero, the first triangle sample is zero. + const float first = osc.Process(); + if(first != 0.f) + { + std::printf("FAIL: triangle after Init(%.0f): %g, expected 0\n", + sample_rate, + first); + ++failures; + } + + env.Init(sample_rate); + env.SetTime(ADENV_SEG_ATTACK, .01f); + env.Trigger(); + // Reinitializing before processing must discard a pending trigger: + // Init documents the initial segment as idle. + env.Init(sample_rate); + for(int i = 0; i < 16; ++i) + { + const float output = env.Process(); + if(output != 0.f || env.IsRunning()) + { + std::printf( + "FAIL: AD envelope after Init(%.0f): %g, segment %u\n", + sample_rate, + output, + unsigned(env.GetCurrentSegment())); + ++failures; + break; + } + } + } + + if(failures == 0) + std::puts("PASS: oscillator and AD envelope initialization"); + return failures == 0 ? 0 : 1; +}