As well as programming a large computer for audio output, it's often useful to be able to minimise the time and storage spent generating a sine-wave. Some devices contain very low power processors but might still need to implement audio-frequency oscillators; others may need to generate sine-waves at very high frequency sample rates. It is now possible to build FM receivers using software-only IF processing (at 10.7MHz) on a relatively modest dsp chip.
The size of the look-up table, even in its quarter-cycle version, would be regarded by hardware designers as a profligate waste of memory, when a simple piece of trig can cut the storage requirement dramatically.
sin(A+B) = sinAcosB
+ cosAsinB
sin(x) ≈ x;
cos(x) ≈ 1 - x^{2}/2
(for small x).
Here's the clever bit: let's store a smaller number of entries in the look-up table, and use the identity above to interpolate between them. So A will be a "big" angle (cos and sine obtained from the table) and B will be the difference between the value which can be obtained by the table lookup and the angle we need. B is a "small" angle, so the approximations can be used to calculate the final value.
Do the Maths Homework to find out how much of the table can be thrown away without compromising the accuracy of the result.
Before investing effort in coding up an interpolated solution, we'd better find out exactly how much storage can be saved from the look-up table.
You can find the roots of (most) differentiable polynomials iteratively using Newton's method. (Hint: it's easy to code in Python if you get bored using a calculator)
${x}_{n+1}={x}_{n}-\frac{f\left({x}_{n}\right)}{f\text{'}\left({x}_{n}\right)}$Modify the oscillator class you have already written so that it uses a smaller look-up table and interpolation.
As described in Chapter 2 of mcm.pdf the position of poles and zeros in Laplacian space characterises a system response. Poles on the left half of the s-plane represent a damped oscillatory response to an impulse stimulus, and poles on the right indicate unstable responses.
Discrete-time systems are characterised by the positions of poles and zeros in the z-plane. Poles inside the unit circle characterise damped responses, and poles outside it unstable ones.
If we could build a discrete-time system with poles on the unit circle at the appropriate frequency, it would be possible to create an oscillator with a single pair of poles (representing a second-order system).
The z-transform is as follows:
$X\left(z\right)=\sum _{n\ge 0}x\left[n\right]{z}^{-n}$An oscillator has a conjugate pair of poles on the unit circle. Moving the poles from z=1 to z=-1 increases the frequency of oscillation from 0Hz to the Nyquist frequency. Note that as the angle around the circle is increased further still, the poles appear to swap over and move back towards z=1, which is exactly the behaviour of the alias frequency which would be generated if an attempt was made to represent a frequency higher than half the sample rate.
The transfer function of a system with poles at a_{n} and zeros at b_{n} in the z-plane can be written out as follows:
$\begin{array}{lll}H\left(z\right)& =& \frac{Y\left(z\right)}{X\left(z\right)}\\ & =& \frac{A(z-{a}_{1})(z-{a}_{2})...}{(z-{b}_{1})(z-{b}_{2})...}\\ & =& \frac{\sum _{n=0}^{{n}_{z}}{u}_{n}{z}^{-n}}{1-\sum _{n=1}^{{n}_{p}}{v}_{n}{z}^{-n}}\end{array}$Considering the definition of the z-transform, it is clear that division by z is equivalent to a delay of one sample in time. The rearrangement into the quotient of two polynomials in z^{-1} therefore realises the oscillator directly.
Perform the following preliminary design for a 440Hz oscillator using a second-order (two-delay) section as follows.
This all sounds quite daunting, but it isn't hard after you've worked through what's happening once. Read Chapter 2 of mcm.pdf for more detail.
Implement a Python class for an oscillator which uses its previous two outputs to generate the next output. In DSP, this is called a "recursive oscillator" because previous results are used to generate a new result; this does not mean that the Python code has to be recursive.
__init__
, it's OK to "cheat"
by calling the sine function a couple
of times to set the previous samples (actually,
you only need to call it once if you take 0 as
the previous output).
Unless otherwise noted, all materials on these pages are licenced under a Creative Commons Licence.