adamstan wrote:The language itself has nothing to do with it. It's the algorithm used that matters.
Right. Nobody would use a programming language if you got the wrong results from a given algorithm when using it. But there's tons of different algorithms you can use to produce a waveform doing anything from the pure mathematical representations to tweaked "analog-ier" versions to stuff that doesn't even have a representation in conventional analog oscillators.
For example, you might start with a phase accumulator
as the fundamental unit. By itself, this produces what is essentially a "perfect" sawtooth waveform (not exactly
perfect, because there's a certain margin of error when it rolls over, but close enough.) You can derive a perfect square wave from this very easily with a simple comparison: if the value of the phase accumulator is greater than half the maximum value of the phase accumulator, output the negative maximum, while if it's less than or equal, output the positive maximum. (Or vice-versa.) You can change the pulse width by comparing it to a value greater or lesser than half the maximum. That gives you the two most fundamental subtractive waveforms in "perfect" form with simple integer math. (I'm going to leave out triangle here, but it's also dead simple.) Sine is a bit more complicated, because it's trigonometry, but since any serious high-level language will have trig functions built-in, it's really just as simple as scaling the value of the phase accumulator to fit the range of the input to the sine function and then scaling the output of the sine function to fit the range of the oscillator output.
But say you don't like those results. I, for example, find "perfect" mathematical sawtooth and pulse to be a bit overly harsh and thin, and "perfect" sine to be a little too bland for my liking. So, while working on a softsynth for a project I'm developing, I spent some time looking for simple ways to derive non-"perfect" versions of these waveforms from a simple phase accumulator.
The first version of alt-sawtooth I came up with is pretty simple - multiply the value of the phase accumulator by its absolute value (and scale it back down to the output range of the oscillator,) and you get an x
-squared progression that inverts on the negative half of the cycle so that it resembles a sawtooth with a noticeable S-curve bent to it. This sounded a little nicer to me than plain sawtooth, but when looking at oscilloscope displays of the sawtooth from the Minimoog, I noticed that it's much closer to a quarter-circle shape. Luckily, I had just stumbled across a way to generate a sine-ish waveform without using trig functions, so I tweaked that algorithm so that it never left the 0-90 degree range (positive-going positive-side) and normalized the output to fit the full range - resulting in a nice curvy "sawtooth" that sounds better still to my ears.
As mentioned, while messing around with the first alt-sawtooth, I stumbled across an algorithm that generates quite a satisfactory "sine" waveform - it's a bit "fuller" than a true sine (somebody on the sdiy list tells me it's a something something math terminology version of a triangle waveform, which sounds about right.) It involves multiplying the value of the phase accumulator by its absolute value and scaling the result back down (as with the first alt-sawtooth,) then subtracting the value of the phase accumulator, and scaling the result up to the full range (the initial result ranges from -1/4 to +1/4 of maximum volume.)
As for square/pulse, I'm doing a really simple modelling of the way a real analog pulse waveform "decays" towards 0 over time. It uses the initial if-then from the perfect pulse-wave algorithm to determine what the sign of the output should be. If it's different than the sign of the current output value, the current output is reset to the negative maximum or positive maximum (depending on which phase of the pulse cycle it's supposed to be in.) If not, the current output value is reduced by a fraction of itself (in my implementation, it's output = output - (output / 512) which seems to give a nice manageable decay at audio rates.)
(For the curious, I have code and a demo here
. A rate of 128 will fit everything neatly in the window.)
So there's any number of ways you can tweak an "oscillator" in software to give something that's different from the "ideal" but still recognizable. But it has nothing to do with the language, just the specific software and whatever algorithms the programmer used to generate the waveforms.