1 stable release
Uses old Rust 2015
3.0.0 | Jan 2, 2018 |
---|
#17 in #oscillator
22KB
318 lines
iq_osc.rs – I/Q oscillator without trig function calls
Given 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.
Computing 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.)
Usage
This crate can be used through cargo by
adding it as a dependency in Cargo.toml
:
[dependencies]
iq_osc = "3.0.0"
and importing it in the crate root:
extern crate iq_osc;
lib.rs
:
I/Q oscillator without trig function calls.
Given
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.)
Theory
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.
Error Accumulation
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_osc32
and 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)
Dependencies
~240KB