I/Q oscillator without trig function calls.
f(t) = cos(θ0 + ωt) = cos Φ(t),
an oscillator is defined here to evaluate f(0), f(1), f(2), ... in sequence to generate a sinusoidal signal. Further, a quadrature oscillator also evaluates g(t) = sin Φ(t) at each step for the quadrature signal.
Calling out to these trig functions at each evaluation of f(t) and g(t) can be costly with a high sample rate or within a tight loop. As an alternative, this crate implements a quadrature oscillator that replaces the 2 trig function calls at each evaluation with 6 arithmetic operations (4 multiplies, 1 addition, and 1 subtraction.)
Using the definition of f(t) = cos Φ(t) from above, notice that
Φ(0) = θ0
Φ(1) = θ0 + ω = Φ(0) + ω
Φ(2) = θ0 + 2ω = Φ(1) + ω
And in general,
Φ(t) = Φ(t - 1) + ω, t > 0
With this and the trig identities
cos(u + v) = cos(u)cos(v) - sin(u)sin(v)
sin(u + v) = sin(u)cos(v) + cos(u)sin(v)
we can then write f(t) as
f(t) = cos(Φ(t - 1) + ω) = cos(Φ(t - 1))cos(ω) - sin(Φ(t - 1))sin(ω)
and similarly for the quadrature signal,
g(t) = sin(Φ(t - 1) + ω) = sin(Φ(t - 1))cos(ω) + cos(Φ(t - 1))sin(ω)
If we compute sin ω, cos ω, sin θ0, and cos θ0 at initialization, then we can compute f(t) and g(t) for t = 0, 1, 2, ... using just the arithmetic in the above equations.
Due to the accumulation of floating-point roundoff errors, the accuracy of returned
sine/cosine evaluations will slowly degrade over phase steps. Using a very small phase
step or running an
IQOsc through many, many cycles will make this problem more
pronounced. As a workaround, the double-precision
IQOsc<f64> can be used, which
provides a significant increase in accuracy across phase steps and has relatively
little impact on speed – compare
bench_osc64 in the output of
cargo bench (example output is given below.)
test bench_osc32 ... bench: 55,043 ns/iter (+/- 4,479) test bench_osc64 ... bench: 62,170 ns/iter (+/- 30,989) test bench_trig32 ... bench: 490,407 ns/iter (+/- 90,148) test bench_trig64 ... bench: 2,365,592 ns/iter (+/- 148,062)
Quadrature oscillator with current phase Φ(t) and phase step ω.