34
$\begingroup$

I am able to write a basic sine wave generator for audio, but I want it to be able to smoothly transition from one frequency to another. If I just stop generating one frequency and immediately switch to another there will be a discontinuity in the signal and a "click" will be heard.

My question is, what is a good algorithm to generate a wave that starts at, say 250Hz, and then transitions to 300Hz, without introducing any clicks. If the algorithm includes an optional glide/portamento time, then so much the better.

I can think of a few possible approaches such as oversampling followed by a low pass filter, or maybe using a wavetable, but I am sure this is a common enough problem that there is a standard way of tackling it.

$\endgroup$
1
  • 2
    $\begingroup$ Why didn't you just used the linear frequency transition over the transition period. For example, you need to transit from frequency f0 at time t0 to frequency f1 at time t1, then why not just introduce a transition frequency f(t)=f0*(1-q)+f1*q, where q=(t-t0)/(t1-t0), then produce a signal A(t)=sin(2*Pi*f(t)*t)? $\endgroup$
    – mbaitoff
    Commented Jan 9, 2012 at 7:53

5 Answers 5

30
$\begingroup$

One approach that I have used in the past is to maintain a phase accumulator which is used as an index into a waveform lookup table. A phase delta value is added to the accumulator at each sample interval:

phase_index += phase_delta

To change frequency you change the phase delta that is added to the phase accumulator at each sample, e.g.

phase_delta = N * f / Fs

where:

phase_delta is the number of LUT samples to increment
freq is the desired output frequency
Fs is the sample rate
N is the size of the LUT

This guarantees that the output waveform is continuous even if you change phase_delta dynamically, e.g. for frequency changes, FM, etc.

For smoother changes in frequency (portamento) you can ramp the phase_delta value between its old value and new value over a suitable number of samples intervals rather than just changing it instantaneously.

Note that phase_index and phase_delta both have an integer and a fractional component, i.e. they need to be floating point or fixed point. The integer part of phase_index (modulo table size) is used as an index into the waveform LUT, and the fractional part may optionally be used for interpolation between adjacent LUT values for higher quality output and/or smaller LUT size.

$\endgroup$
7
  • 1
    $\begingroup$ thanks, I was expecting that the answer might involve LUTs. I was thinking of going with a LUT that contains one waveform at 1Hz (i.e. Fs entries). Is there a rule of thumb governing the optimal size of the LUT? $\endgroup$
    – Mark Heath
    Commented Dec 16, 2011 at 10:13
  • 4
    $\begingroup$ It depends on various factors: what SNR you are looking for, whether it's a pure sine wave or a more complex waveform, whether you plan to interpolate between adjacent LUT entries or just truncate, etc. It also depends on whether you are just going to have a single quadrant table and handle the indexing arithmetic and sign inversion yourself, or have a full four quadrant table. Personally I would start with a 1024 point (NB: 2^N is good for modulo indexing) four quadrant table with no interpolation as this is very simple and should give good results for e.g. 16 bit "consumer" audio. $\endgroup$
    – Paul R
    Commented Dec 16, 2011 at 10:42
  • 1
    $\begingroup$ Good answer, Paul. There's also a similar question on the topic that was posted a while back; more info always helps. $\endgroup$
    – Jason R
    Commented Dec 16, 2011 at 15:01
  • 4
    $\begingroup$ Another way of looking at this approach is an emulation of a voltage-controlled oscillator (VCO). The output frequency of a VCO depends on the input voltage (usually a linear function of input voltage) but the output signal has continuous phase even if the input voltage switches instantaneously. The output is $$\sin(\phi(t)) = \sin\left(\int_0^{t}\omega_0 + k\cdot x(\tau)\mathrm d\tau \right)$$ where $\phi(t)$ is a continuous function of time, while the output frequency is the derivative of the phase, and equals $$\omega_0 + k\cdot x(t)$$ where $\omega_0$ is the quiescent frequency. $\endgroup$ Commented Dec 16, 2011 at 16:54
  • 1
    $\begingroup$ I had the same problem, thanks for the accumulator idea (I was using direct calculation, which didn't work because of approximations) : jsfiddle.net/sebpiq/p3ND5/12 $\endgroup$
    – sebpiq
    Commented Mar 31, 2012 at 18:36
14
$\begingroup$

One of the best ways to create a sine wave is to use a complex phasor with recursive updating. I.e.

$$z[n+1] = z[n]\Omega$$

where z[n] is the phasor, $\Omega = \exp(j\omega)$, with $\omega$ being the angular frequency of the oscillator in radians and $n$ the sample index. Both real and imaginary part of $z[n]$ are sine waves, they are 90 degrees out of phase. Very convenient if you need both sine and cosine. A single sample calculation only requires 4 multiples and 4 adds and is MUCH cheaper than anything containing sin() cos() or lookup tables. The potential problem is that the amplitude may drift over time due to numerical precision issues. However there is a fairly straight forward to repair that. Let's say that $z[n] = a + jb$. We know that $z[n]$ should have unity magnitude, i.e.

$$a\cdot a + b\cdot b = 1$$

So we can check every once in a while if that is still the case and correct accordingly. The exact correction would be

$$z'[n] = \frac{z[n]}{\sqrt{a\cdot a + b\cdot b}}$$

That is an awkward calculation but since $a\cdot a + b\cdot b$ is very close to unity you can approximate the $1/\sqrt{x}$ terms with a Taylor expansion around $x = 1$ and we get

$$\frac{1}{\sqrt{x}} \cong \frac{3-x}{2}$$

so the correction simplifies to

$$z'[n] = z[n]\frac{3-a^2-b^2}{2}$$

Applying this simple correction every few hundred samples will keep the oscillator stable forever.

To vary the frequency continuously the multiplier W needs to be updated accordingly. Even a non-continuous change in the multiplier will maintain a continuous oscillator function. If frequency ramping is required the update can either be broken down into a few steps or you can use the same oscillator algorithm to update the multiplier itself (since it's also a unity gain complex phasor).

$\endgroup$
5
  • $\begingroup$ thanks for this answer, will probably take me a little while to understand well enough to turn into some real world code, but it looks like an interesting alternative to try. $\endgroup$
    – Mark Heath
    Commented Jan 6, 2012 at 18:54
  • 2
    $\begingroup$ I implemented this solution in golang for reference: github.com/rmichela/Acoustico/blob/… $\endgroup$ Commented Mar 12, 2017 at 8:12
  • 1
    $\begingroup$ This is a beautiful solution that, unfortunately, only works well if using a constant time base. If not, you need to compute a sin and a cos to compute the correct complex rotation. $\endgroup$ Commented Nov 16, 2019 at 21:51
  • $\begingroup$ A beautiful solution and explanation that surely deserves many more upvotes. I wonder if anybody knows of any C/C++ libraries that implement this oscillator? All library's that I've found use wavetables or other direct sine approximations. $\endgroup$ Commented Dec 9, 2019 at 19:38
  • $\begingroup$ Still interested in libraries implementing this. May be useful for others to know there's a closely related numerically-stable method known as "modified coupled form", which is described by Gordon, 1995 (A Sine Generation Algorithm for VLSI Application), and is also (very) briefly summarised here: ccrma.stanford.edu/~jos/pasp/Digital_Sinusoid_Generators.html $\endgroup$ Commented Aug 26, 2020 at 1:02
4
$\begingroup$

I agree with the previous suggestions of using a phase accumulator. Essentially the control input is the amount of phase advance per step or per clock period (or per interrupt or whatever), so that changing that value changes the frequency without a discontinuity in the phase. The wave amplitude is then determined from the accumulated phase value either via a LUT or just computation of sin(theta) or cos(theta).

This is essentially what is commonly known as a Numerically Controlled Oscillator (NCO) or a Direct Digital Synthesizer (DDS). Doing a web search on those terms will probably yield more than you want to know on the theory and practice of making them work well.

Adding an additional accumulator can allow seamless transitions between frequencies, as you suggested, if that's desired as well, by controlling the rate of change of the phase advance value. This is sometimes called a Digital Differential Analyzer, or DDA.

$\endgroup$
1
  • $\begingroup$ Good additional information. Glad to see you around here, Eric; we could use a Minister of Algorithms. $\endgroup$
    – Jason R
    Commented Dec 31, 2011 at 1:29
2
$\begingroup$

From this site:

In order to create a smooth transition from one frequency to another or one amplitude to another, an incomplete sine wave must be modified with an appended section so that the resulting wave after each iteration of the while loop ends at the x-axis.

Sounds like it should work.

(Actually, if they are both synchronized at the x-axis upon transition, I suppose a gradual transition isn't necessary.)

$\endgroup$
1
  • 1
    $\begingroup$ This says, wait for the current sinusoid at frequency $\omega_0$ to complete one cycle and arrive at $0$ and then switch to the other sinusoid at frequency $\omega_1$. This effectively maintains the continuity of phase and is likely to be OK for audio applications in which the few milliseconds or microseconds delay between the desired switch time (now) and the implemented switch time (when my sinusoid completes a cycle) is inconsequential. However, the difference might give trouble in other applications. Just remember a sinusoid is $0$ twice in one cycle and be sure to pick the right one! $\endgroup$ Commented Dec 17, 2011 at 2:48
1
$\begingroup$

1st order, you should adjust the starting phase of the new frequency sinusoid so that it is the same as what would be the phase of the previous sinusoid at the 1st transition sample point. Compute the first frequency and use its phase for the second frequency.

2nd option might be to ramp d_phase from that of one frequency to the next over several samples. This will clean up the continuity of the 1st derivative and provide a glide.

3rd option might be to use a smoothing window, such as a raised-cosine, on the d_phase ramping rate.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.