[sdiy] Approximating sine with plain integer math
rsdio at audiobanshee.com
rsdio at audiobanshee.com
Thu Apr 7 05:00:53 CEST 2016
I hope the image attachments make it through the mailing list, they're only 1.2 MB for all.
On Apr 6, 2016, at 5:06 PM, John Ames <commodorejohn at gmail.com> wrote:
> On 4/6/16, Richie Burnett <rburnett at richieburnett.co.uk> wrote:
>> Neat. It's got quite a high distortion for a sinewave as you said, and has
>> only odd harmonics present. The waveform you have actually generated is the
>> integral of a triangle waveform.
>> As a triangle is the integral of a squarewave and has a steeper spectral
>> roll-off, your waveform is the integral of the triangle waveform, and has an
>> even steeper spectral roll-off:
> Hah, interesting. I figured from looking at a graph that it might be
> something in the square/triangle family, but I'm not up on my
> higher-level math enough to work out the details as you did.
A long time ago, I wrote some Mac OS X software to generate various waveforms using harmonic series of sine waves. Various controls allowed selecting the number of harmonics; whether odd, even, or all harmonics were included; whether the amplitudes were evenly weighted, 1/N or 1/(N*N), where N is the harmonic number; and one other control that turned out to be necessary. The obvious wave shapes such as square (odd harmonics weighted 1/N) and ramp/sawtooth (all harmonics weighted 1/N) were easily obtained. However, I couldn't create triangle (odd harmonics weighted 1/(N*N)) until I guessed that every other harmonic needed to be inverted in polarity (I don't like to call polarity inversion "phase shift" because it's not the same as a variable filter phase delay).
Here's the interesting part: if all of the harmonics of a triangle wave have the same polarity, rather than alternating polarity, the resulting shape looks like a sine that is more circular. I called it a "circle wave" at the time. This wave shape sounds identical to a triangle wave even though it looks very different. The human hearing system cannot distinguish polarity. Analog circuits like an integrator can synthesize a triangle wave much more easily than this "circle wave" but software DSP can create either one with the same number of calculations.
This story is my long-winded way of saying that I had a hunch that your approximate sine has more harmonic distortion than a pure sine wave, because I've seen the shape before. Your method of calculation is probably a lot faster than mine, since I allowed for an arbitrary number of sin() function calls and summing of all harmonics (along with Automatic Gain Control to prevent clipping).
By the way, once I introduced the option of inverting every other harmonic, I tried that variation on all of the other popular waveforms like square, sawtooth, etc. There are some very interesting shapes! Unfortunately, they all sound exactly like their counterparts that don't have inverted harmonics, but it's still educational to see.
Also: If you start with a triangle wave and add even harmonics (so that the waveform includes all harmonics), then you end up with what I like to call an "italic sine wave." It looks basically like a regular sine wave except that the peaks lean over to one side. Flipping every other harmonic causes the wave to lean the other way.
I'll send some links to images of these waveforms in a future email.
>> You can play similar tricks with generating successive integrals of a
>> sawtooth waveform, to get from sawtooth to parabola and then to something
>> that looks sine-like and has a -18dB/oct rolloff, but has both odd and even
>> harmonics present. Technically inferior to your approximation because the
>> THD is higher, but might sound nicer with even harmonics present!?!?
> Interesting notion. For my purposes I like my "sine" to sound closer
> to a triangle than not, so I suspect that I'd prefer it without the
> even harmonics, but it'd be interesting to compare.
>> Did you consider a lookup-table with linear interpolation? ...that would be
>> my method of choice for sinewave generation if I wanted low distortion. You
>> can also get excellent results with low-order polynomial approximations with
>> or without range-reduction techniques to exploit the inherent symmetries of
>> the sine function. These can all be implemented with fixed-point
>> arithmetic. No need to go to floating point.
> Yeah, those are valid approaches as well, but my other goals for the
> project are (A) pure algorithmic synthesis (so no lookup tables -
> again, no particular reason other than just because it's my whim) and
> (B) a little deviation from strictly-mathematically-accurate (just
> enough to give it a little unique character; I don't care much for
> softsynths that sound too vanilla "perfect," which is why I was trying
> to come up with a simple method to distort the sawtooth in the first
> place.) So I think the method I stumbled upon is probably well-suited
> to my design goals.
I've been using a State Variable Filter (a.k.a. Quadrature Oscillator) with fixed resonance to generate sine waves at arbitrary frequencies. You should be able to implement the SVF in fixed point or integer math, avoiding floating point as you desire.
Your latest code uses 3 multiply, 1 divide, 1 subtract, and 2 branches (the ?: operator).
I believe that the SVF can be implemented with only 2 multiply and 1 add. It's rather significant that there are no branches in the SVF calculations, because most pipelined processors lose performance when branching. Some processor instruction sets, such as the PowerPC AltiVec, have single opcodes that can pick one of two results based on a condition, allowing things like "(saw < 0) ? -1 : 1" to be implemented without flushing the pipeline, but it's far easier to work with algorithms that don't branch at all, assuming you want performance.
Granted, one of your multiply operations can be implemented as a 2-bit shift (i.e. * 4). However, that divide instruction could be expensive unless you use fixed point fractions and multiply by the equivalent of 1/INT_MAX. That makes your algorithm 3 multiple, 1 2-bit shift, 1 subtract, and 2 branches. Still a lot more than the SVF.
My understanding is that using the SVF as a sine oscillator is fairly low distortion, but the distortion increases as the frequency approaches Nyquist. I have never measured the distortion of this code, but it would be vary interesting to compare to your 5% odd-harmonic distortion, especially if the distortion can be mapped versus frequency.
I've used SVF sine generation for FM, but not for pure synthesis. Does anyone have experience with the SVF in this application? I optimized my code to remove all of the excess calculations needed to produce all 4 outputs of the SVF, since I only needed the 1 output.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the Synth-diy