Sine waves versus duty cycle

I was working on a sound-synth program. This involves, among other things, oscillator modules, generating a waveform at a specific frequency. But, taking a leaf from real-world signal generators, I gave my oscillator modules three outputs: sine wave, triangle wave, and square wave.

But then I wanted to support duty cycles other than 50%. For triangle and "square" waves, this is easy, but it's not so simple for the sine-wave output.

I could have just ignored the issue and always produced a symmetric sine wave for that output. But I wanted to come up with something.

I considered just pasting together two sine-wave half-cycles, one from -1 to 1 and the other from 1 to -1. But the discontinuous second derivative of that alternative bothered me, even though it probably would be good enough in practice.

I finally came up with an algorithm which is satisfactory to the mathematician in me, is O(1) to compute if you assume that trig functions, square roots, and of course arithmetic, are constant-time, is numerically stable (no iterative anything), automatically turns into a pure sine wave when the corresponding parameter is set properly, and looks good when plotted.

Mathematically speaking, consider a point P1 moving with a constant angular speed around the standard unit circle C1. You can then get an ordinary sine wave by taking its Y coordinate (or, with a phase shift, its X coordinate).

Now, consider a second point, P2, strictly inside C1, and the unit circle, C2, centred on P2. Now, project P1, from P2, onto C2: connect P1 and P2 and extend the line if necessary until it intersects C2 (in, let us say, P3). Now, consider the X coordinate of P3 relative to P2. (Or its Y coordinate; with a time delay, and rotating P2 by 90 degrees around the origin, it amounts to the same thing.)

This is a lot like a sine wave: it runs from -1 to 1, it is smooth (infinitely differentiable, I think, though I'd have to think more to be certain), it becomes a sine wave if P2 is at the origin, and the distortion relative to a sine wave increases with P2's distance from the origin.

If you put P2 on the X axis and use the X coordinate of P3 (relative to P2), the zero crossings occur at predictable times; it is simple to compute P2's X coordinate for the positive and negative portions of the resulting curve to have a specified duty cycle. If you put P2 on the Y axis instead but still use the X coordinate, then you have a predictable duty cycle as measured from extremum to extremum instead of from zero-crossing to zero-crossing; this form looks superficially like my "paste together two half-cycles" idea (but doesn't have the discontinuity in the second derivative). Locations for P2 on neither axis give intermediate curves.

Curiously, they all sound very similar, to my ear at least. I synthesized a sound file based on P1 cycling at 400Hz while P2 moved in a smaller, concentric, circle at about .25Hz. My ear hears little to no quality change throughout P2's cycle.

Main