Search guides and the APICtrl K
  1. API reference
  2. DSP

phase.h

A phase accumulator that does not drift.

#include <sonore/phase.h>
On this page

Design notes

Every oscillator here keeps its phase as phase += inc; if (phase >= 1) phase -= 1; in float, and for a note that lasts a few seconds that is fine. It is not fine for anything that has to stay IN STEP with something else for a long time -- an LFO locked to the song position, two oscillators meant to beat at exactly 0.5 Hz, a test tone compared against a reference -- because the float accumulator does not merely round, it DRIFTS. The increment is rounded once, and that error is added every sample; after an hour at 48 kHz a 440 Hz float accumulator has slid a measurable fraction of a cycle (the test prints by how much), and nothing about it resets.

So the phase is an unsigned 64-bit integer counting 2^-64 of a turn.

  • The addition is EXACT. Integer adds do not round, so the only error is the increment's own: hz / sr rounded once to a double (about 1e-18 of a turn for a 440 Hz tone) and truncated to 2^-64. The test holds a 440 Hz phase against the exact rational answer for 10^8 samples -- 35 minutes at 48 kHz -- and it is out by under 1e-10 of a cycle.
  • The wrap is FREE and exact: unsigned overflow is defined in C++ and is precisely "subtract one whole turn". There is no compare, no branch, and no way for a large increment to land outside [0, 1).
  • A negative increment is the same add in two's complement. Running backwards needs no second code path.

The value comes out as turns in [0, 1) or radians in [0, 2pi), and the conversion is built so it cannot round up onto the far end: a phase of 0.99999999999 that comes back as 1.0 is a table index one past the end.

Nothing here allocates; advance() is an add and a shift.

Types 1

A drift-free phase accumulator, in TURNS (0..1 is one cycle).

The reference's Phase wraps a floating-point value at 2pi and returns the phase before each step; advance() here keeps that shape -- it returns the phase BEFORE stepping -- with turns as the unit, because that is what fastmath::sinTurns and every wavetable lookup here take.

↑↓ to move↵ to openEvery public name in the headers, and every guide