The following is an explanation how to construct typical basic naive oscillator waveforms used in synthesis. The word 'naive' here means that we can get these waveforms easily and in the time domain, they look great. It also means that these waveforms actually should not be used in practice for audio synthesis (except the sine wave shown here).
The method for the saw, square and triangle waveforms presented here do have significant problems (aliasing), and should usually not be used for audio synthesis. Also, all of these oscillators have DC-Offsets. This is not corrected here (although it would be trivial) because this is the wrong way to create good oscillators anyways. Still, the following represents a simple introduction how to create waveforms in python.
# Imports
%pylab inline
style.use('website') #used to correct plot style for website. Ignore/Remove if you follow this tutorial.
# Timing constants etc. See article 01 for basic explanations.
sr = 44100 #Sampling Rate [Hz]
T = 3 #Duration [seconds]
N = int(sr*T) #Number of samples
ns = arange(N) #sample index
t = ns/sr #time index.
Populating the interactive namespace from numpy and matplotlib
A "phasor" is a waveform that looks like a sawtooth wave, is usually used as an LFO (Low frequency Oscillator), modulation source, or to look up something in a wave table. see
[phasor~]
[phasor~]
We can get something like a 1Hz phasor if we just take the time index $t$, apply a modulo operation on it by 1.
$$ p(t) = mod(t, 1)$$
In python (and many other languages) we can use %
to perform a modulo operation.
y = t%1
plot(t,y)
xlabel('$t$');
By introducing a multiplicator $f_0$ to the time, we can control the frequency. $$ y(t) = mod(t\cdot f_0, 1)$$
f0 = 3 # frequency in Hz
y = (t*f0)%1
plot(t,y);
We can do this in max/msp and pd as well.
In Max:
[phasor~]
|_____
|
[cycle~]
In pd:
[phasor~]
|_____
|
[osc~]
Note: the pasor must go in the second inlet of the [cyclce~]
object (which controls the phase). [cylce~]
's first inlet controls the frequency. This is not what we want.
Conceptually, one could say we are using the sine as a lookup table and the phasor as indexing input.
x = sin(y*2*pi)
plot(t, y, '-')
plot(t, x, 'r-')
axhline(lw='0.5', ls='--')
xlabel('$t$')
xlim([0, 0.5]);
We can modify our sawtooth to get a square wave $y_s(t)$ by just introducing one comparison: $$ y_s(t) = \left\{ \begin{array}{lr} 0, & \text{if } y\leq 0.5\\ 1, & \text{if } y\gt 0.5 \end{array} \right\}$$
In python, we can just take our sawtooth and compare it to 0.5 with > 0.5
. This results in an array that contains the boolean values True
and False
.
Multiplying this by 1
python automatically converts all True
s to 1
s and all False
values to zeros.
ys = (y>0.5)*1
plot(t, ys);
Alternatively, and somewhat uneccessarily complicated, we can arrive at the square wave by adding a phaseshifted and inverted version of our saw to itself.
y1 = t%1
y2 = ((t+0.5)%1)*-1
plot(t,y1, label='$y_1$')
plot(t,y2, label='$y_2$')
plot(t,y1+y2, label = '$y_1 + y_2$')
xlabel('$t$')
legend();
One version of implementing a naive triangle is using a square wave to switch between two saw waves, one ramping up and one ramping down.
y1 = t%1
c = y1>0.5
y2 = (t%1)*-1+1
y3 = y1*c + y2*(1-c)
plot(t,y1, label='$y_1$', lw=3)
plot(t,y2, label='$y_2$', lw= 3)
plot(t,y3,'r--', label = '$y_1 c + y_2 (1-c)$', lw=2)
plot(t,c, 'g-.',label='$c$')
xlabel('$t$')
legend();
Constructing naive waveforms can be useful. For Modulation SOurces, but also just tto train our intuition for working with signals. The 'saw' (phasor), square and triangle waves presented here should not be used for audio synthesis. We also haven't listened to them here, this is left as an exercise for the reader. For low frequencies, these waveforms can sound OK. When used with higher fundamental frequencies, they cause aliasing, a topic for a future article.