Generating sines and decaying sines

In this article, I will describe a simple algorithm that can be used to generate sine waves and decaying sine waves of a fixed frequency without computing any trigonometric functions. This algorithm is ideal for microcontrollers, where computing sines and cosines may be inefficient and storing tables of values may be impractical. The algorithm also involves no floating-point arithmetic.

Theory

The goal of this algorithm is to produce a series of values \(s_i=\sin\left(2\pi\frac{if}{f_s}\right)\), where \(f\) is the desired frequency of the sine wave, \(f_s\) is the sampling frequency, and \(i\ge 0\) is an integer. If this series of values is fed into a DAC at \(f_s\) samples per second, then the output of the DAC would be a sine wave with a frequency of \(f\).

Recall that \(e^{j\theta}=\cos\theta + j\sin\theta\), where \(j\) is the imaginary unit. Thus, if we can compute the value of \(e^{2\pi j\frac{if}{f_s}}\), we would have not only \(s_i=\sin\left(2\pi\frac{if}{f_s}\right)\), but also \(c_i=\cos\left(2\pi\frac{if}{f_s}\right)\). Note that: $$ \frac{c_{i+1}+js_{i+1}}{c_i + js_i} = \frac{e^{2\pi j\frac{\left(i+1\right)f}{f_s}}}{e^{2\pi j\frac{if}{f_s}}} = e^{2\pi j\frac{f}{f_s}} $$ $$ c_{i+1}+js_{i+1} = \left(c_{i}+js_{i}\right) e^{2\pi j\frac{f}{f_s}} $$ If we let \(c_0=k\) and \(s_0=0\), then we will get a sine and cosine wave with amplitude \(k\). Furthermore, if the frequency and sample rate are known ahead of time, the real and imaginary parts of \(e^{2\pi j\frac{f}{f_s}}\) can be precomputed, allowing for more efficient evaluation: $$ c_{i+1}+js_{i+1} = \left(c_{i}+js_{i}\right) \left(C + jS\right) $$

Limited precision

Theoretically, we can repeat the above recursive step infinitely many times and the sine wave will still have the same amplitude. However, since the precision of the stored value of \(s_i\) and \(c_i\) is limited, precision loss will cause the sine wave to either decay or grow exponentially. Suppose \(s_i\), \(c_i\), \(S\), and \(C\) are stored as \(n\)-bit signed integers. This means that these values must be scaled up by a factor of \(2^{n-1}\) in order to take advantage of all of the bits we have. In order for the iterative formula to still be true, we must divide each value by \(2^{n-1}\) $$ \frac{c_{i+1}}{2^{n-1}}+j\frac{s_{i+1}}{2^{n-1}} = \left(\frac{c_{i}}{2^{n-1}}+j\frac{s_{i}}{2^{n-1}}\right) \left(\frac{C}{2^{n-1}} + j\frac{S}{2^{n-1}}\right) $$ $$ c_{i+1}+js_{i+1} = \frac{\left(c_{i}+js_{i}\right) \left(C + jS\right)}{2^{n-1}} $$ The division by \(2^{n-1}\) can be efficiently implemented as a bitshift.

Complex multiplication

Normally, multiplying two complex numbers would require four multiplications. However, there is a trick that can be used to reduce this to three multiplications: $$ \begin{aligned} c_{i+1}+js_{i+1} &= \frac{\left(c_{i}+js_{i}\right) \left(C + jS\right)}{2^{n-1}} \\ &=\frac{\left(c_iC - s_iS\right) + \left(c_iS + s_iC\right)j}{2^{n-1}} \\ &=\frac{\left(c_iC + s_iC - s_iS - s_iC\right) + \left(c_iS - c_iC + c_iC + s_iC\right)j}{2^{n-1}} \\ &=\frac{\left(C\left(c_i + s_i\right) - s_i\left(C + S\right)\right) + \left(C\left(c_i + s_i\right) - c_i\left(C - S\right)\right)j}{2^{n-1}} \end{aligned}$$ Instead of requiring four multiplications and two additions, complex multiplication only requires three multiplications and three additions. The values of \(C + S\) and \(C - S\) can also be precomputed.

Analysis

As the algorithm runs, loss of precision will cause the sine wave to decrease or increase in amplitude. This is because the magnitude of \(C + Sj\) is not necessarily equal to \(2^{n-1}\). This will cause the magnitude of \(c_i + s_ij\) to change by a factor of \(\frac{\sqrt{C^2+S^2}}{2^{n-1}}\) per step. For given values of \(C\) and \(S\), we expect the amplitude to have changed by a factor of \(\left(\frac{\sqrt{C^2+S^2}}{2^{n-1}}\right)^{i}\). Since the elapsed time is equal to the sample number \(i\) divided by the sampling rate, the exponential decay or growth rate is \(f_s\ln\left(\frac{\sqrt{C^2+S^2}}{2^{n-1}}\right)\). This effect can also be exploited by deliberately increasing or decreasing the values of \(S\) and \(C\). For example, a decaying sine could be used to emulate a bell sound.

With every step of the algorithm, we expect the phase of \(c_i + s_ij\) to increase by the phase of \(C + Sj\), which is equal to \(\arctan\frac{S}{C}\). Since this happens \(f_s\) times per second, the frequency is \(f=\frac{\omega}{2\pi}=\frac{f_s\arctan\frac{S}{C}}{2\pi}\)

Calculator

Use this calculator to determine and compare values of \(C\) and \(S\) for a given expected frequency and decay rate.
Precision:
Sampling frequency:
Frequency:
Decay rate: (negative for decay, positive for growth)
Value of S
00
Value of C0
0

Comments

Popular posts from this blog

Improving and calibrating the capacitive water sensor

Controlling a ceiling fan and light with an Arduino

Turn a buck converter module into a buck-boost converter with only two components