DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
Phasor.h
Go to the documentation of this file.
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
24#include "DspMath.h"
25#include "AudioSpec.h"
26
27#include <cmath>
28#include <limits>
29
30namespace dspark {
31
48template <FloatType T>
49class Phasor
50{
51public:
57 void prepare(double sampleRate) noexcept
58 {
59 if (!(sampleRate > 0.0)) return; // Reject non-positive and NaN
60
61 sampleRate_ = sampleRate;
62 invSampleRate_ = 1.0 / sampleRate;
63 updateIncrement();
64 }
65
70 void prepare(const AudioSpec& spec) noexcept { prepare(spec.sampleRate); }
71
77 void setFrequency(T frequencyHz) noexcept
78 {
79 if (frequencyHz != frequencyHz) return; // NaN would poison the accumulator
80 frequency_ = frequencyHz;
81 updateIncrement();
82 }
83
88 [[nodiscard]] T getFrequency() const noexcept { return frequency_; }
89
95 [[nodiscard]] T advance() noexcept
96 {
97 const T current = currentPhase();
98 // wrapUnit has a fast in-range exit, so the common case (|increment| < 1)
99 // costs one compare - but unlike an unbounded while-loop it can never
100 // hang on a pathological frequency (>> sample rate).
101 phase_ = wrapUnit(phase_ + increment_);
102 return current;
103 }
104
116 [[nodiscard]] T advanceWithFM(T fmHz) noexcept
117 {
118 const T current = currentPhase();
119
120 const double modulatedIncrement =
121 (static_cast<double>(frequency_) + static_cast<double>(fmHz)) * invSampleRate_;
122
123 phase_ = wrapUnit(phase_ + modulatedIncrement);
124
125 return current;
126 }
127
132 [[nodiscard]] T getPhase() const noexcept { return currentPhase(); }
133
138 void setPhase(T newPhase) noexcept
139 {
140 phase_ = wrapUnit(static_cast<double>(newPhase));
141 }
142
151 void hardSync(T fractionalOffset = T(0)) noexcept
152 {
153 phase_ = wrapUnit(static_cast<double>(fractionalOffset));
154 }
155
164 void softSync(T threshold = T(0.5)) noexcept
165 {
166 if (phase_ >= static_cast<double>(threshold))
167 phase_ = 0.0;
168 }
169
174 void reset(T startPhase = T(0)) noexcept
175 {
176 phase_ = wrapUnit(static_cast<double>(startPhase));
177 }
178
183 [[nodiscard]] T getIncrement() const noexcept { return static_cast<T>(increment_); }
184
185private:
189 void updateIncrement() noexcept
190 {
191 increment_ = static_cast<double>(frequency_) * invSampleRate_;
192 }
193
199 [[nodiscard]] T currentPhase() const noexcept
200 {
201 const T v = static_cast<T>(phase_);
202 constexpr T belowOne = T(1) - std::numeric_limits<T>::epsilon() / T(2);
203 return (v >= T(1)) ? belowOne : v;
204 }
205
211 static double wrapUnit(double p) noexcept
212 {
213 if (p >= 0.0 && p < 1.0) return p; // Fast exit
214 // Slow path (rare): sanitise NaN here, where it costs nothing in the
215 // common case - otherwise a single NaN input would stick in the
216 // recursive accumulator until the next reset.
217 if (p != p) return 0.0;
218 // std::trunc instead of a cast to int: casting is UB once |p| exceeds
219 // INT_MAX, which extreme FM excursions can reach.
220 double wrapped = p - std::trunc(p);
221 if (wrapped < 0.0) wrapped += 1.0;
222 // Guard the contract's half-open range: for tiny negative inputs the
223 // addition above rounds to exactly 1.0.
224 if (wrapped >= 1.0) wrapped -= 1.0;
225 return wrapped;
226 }
227
228 double sampleRate_ = 48000.0;
229 double invSampleRate_ = 1.0 / 48000.0;
230
231 T frequency_ = T(0);
232 double phase_ = 0.0;
233 double increment_ = 0.0;
234};
235
236} // namespace dspark
Describes the audio processing environment (sample rate, block size, channels).
Core mathematical utilities for digital signal processing.
Phase accumulator generating a [0, 1) ramp for oscillators and modulation.
Definition Phasor.h:50
T getFrequency() const noexcept
Returns the baseline frequency.
Definition Phasor.h:88
void prepare(const AudioSpec &spec) noexcept
Prepares from AudioSpec (unified API).
Definition Phasor.h:70
void reset(T startPhase=T(0)) noexcept
Resets the phasor to a specific initial state (default 0).
Definition Phasor.h:174
void setFrequency(T frequencyHz) noexcept
Sets the baseline oscillation frequency.
Definition Phasor.h:77
T advance() noexcept
Returns the phase for the current sample, then advances the accumulator (post-increment semantics).
Definition Phasor.h:95
void setPhase(T newPhase) noexcept
Forcibly overwrites the current phase.
Definition Phasor.h:138
T getIncrement() const noexcept
Returns the calculated phase increment per sample.
Definition Phasor.h:183
T getPhase() const noexcept
Returns the current phase without advancing the accumulator.
Definition Phasor.h:132
void prepare(double sampleRate) noexcept
Prepares the phasor for a given sample rate.
Definition Phasor.h:57
void softSync(T threshold=T(0.5)) noexcept
Soft syncs the oscillator conditionally.
Definition Phasor.h:164
T advanceWithFM(T fmHz) noexcept
Advances the phase with per-sample frequency modulation.
Definition Phasor.h:116
void hardSync(T fractionalOffset=T(0)) noexcept
Hard syncs the oscillator, optionally with a sub-sample offset.
Definition Phasor.h:151
Main namespace for the DSPark framework.
Describes the audio environment for a DSP processor.
Definition AudioSpec.h:35