Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Source/Control/adenv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions Source/Synthesis/oscillator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 19 additions & 0 deletions tests/initialization/README.md
Original file line number Diff line number Diff line change
@@ -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.
62 changes: 62 additions & 0 deletions tests/initialization/tst_initialization.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "daisysp.h"
#include <cstdio>
#include <initializer_list>

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;
}