@Caleb .... that code is interesting! Should
Code:
p->phasor->imag = sin(s);
be
Code:
p->phasor->imag = sin(w);
Yes. I just typed that pseduo-code off the top of my head.
Also, pardon my (further) ignorance, but how does
Code:
p->state = p->state * p->phasor; // complex multiply
play out?? the state and phasor objects have two members, real and imaginary. Can * cope with that???
Well, in c++, you can make a complex number type that does the complex multiply.
But, a complex multiply goes like this: to multiply c = a * b where a, b and c are complex numbers goes like this:
Break the a and b into their components, and calling 'j' the imaginary sqrt(-1), you get this:
Code:
a = Ra + j*Ia
b = Rb + j*Ib
c = a * b = (Ra + j*Ia) * (Rb + j*Ib)
= (Ra*Rb) + (Ra * j*Ib) + (Rb * j*Ia) + (j*Ia * j*Ib)
= (Ra*Rb - Ia*Ib) + j*(Ra*Ib + Rb*Ia)
so the real part of c is: (Ra*Rb - Ia*Ib)
and the imaginary part of c is: (Ra*Ib + Rb*Ia)
So, where did the minus sign come from? It's because j = sqrt(-1), and j*j = sqrt(-1)*sqrt(-1) = -1!
Also, does sin/cos use a lookup table?
Not sure, but whatever it does, it should be pretty accurate
Is much gained over pahse accumlator approaches? (This is at the limits of my maths ability!!)
It definitely has an advantage over the lookup table version in that you can represent low frequencies accurately, which is pretty much impossible with a lookup table. In the case of the Teensy sin generator, it uses a table of 256 samples, which corresponds to a frequency of 44100/256 = 172.26 Hz.
what if you want to send a tone of 86.13 Hz? In that case the samples need to repeat twice before stepping. You could always do interpolation with the table based approach to help that though.
At even lower frequencies even interpolation really looses accuracy.
The hires sin generator is a neat approach, using taylor series. I haven't thought about them since college
It looks like that will be quite accurate. It does take a fair number of operations to get to the answer, so there's the question of mips there. If mips is a non-issue, then the hires version seems just dandy. It has the benefit of not drifting in amplitude over time so it doesn't need to be renormalized.
So... long story short, I don't know if there is a benefit to a phasor approach over the hires version or not. somebody would have to take a close look and do some analysis. MIPS wise, I think it's lower than taylor series (4 multiplies, 2 adds per sample), as opposed to what looks like an 'if' plus 12 multiplies, plus a shift per sample. Accuracy wise, it's definitely more accurate than a table based approach.
Here is some python code for you to play with
Code:
from numpy import *
from pylab import *
fs = 44100.0
frequency = 101.1 # some oddball frequency not a perfect multiple of fs.
state = complex64(1.0 + 1j*0) # use 32-bit floats
# compute this many samples
n = 100000
# cycles/second 2*pi radians radians
# w = ----------------- * ------------ = --------
# samples / second cycle sample
w = frequency/fs * 2 * pi
phasor = complex64(cos(w) + 1j * sin(w))
output = zeros(n, dtype=complex)
output[0] = state
for i in range(1, n):
output[i] = output[i-1] * phasor # <--- python is handling the complex multiply for me
plot(real(output), imag(output))
show()
That produces a plot that looks like this:
Which if you zoom in closely on the line, you'll see is not quite a perfect circle:
Which is why you need to renormalize the length of the state vector back to unity once in a while. Perhaps once per block. Perhaps once per several blocks. With floating point you can obviously go a long time (100,000 samples) without renormalizing, but with fixed point, you need to renormalize more often.