Basic Waveforms & Selection
Lesson 6 The four classic oscillator shapes — sine, triangle, square, sawtooth — have distinct harmonic content and timbre.
| Class | Waveform | Character |
|---|---|---|
| SineOsc | Sine | Pure tone, no harmonics |
| TriOsc | Triangle | Odd harmonics, soft and hollow |
| SquareOsc | Square | Odd harmonics, bright and reedy |
| SawOsc | Sawtooth | All harmonics, rich and buzzy |
| PulseOsc | Pulse | Variable duty cycle square |
| Noise | White noise | All frequencies equal power |
Switching waveforms with PhSelect4
Combine four oscillators in parallel with the comma operator , and use a PhNumEntry UI parameter to switch between them in real time:
"Create the four oscillators"
sine := SineOsc new.
tri := TriOsc new.
sqr := SquareOsc new.
saw := SawOsc new.
"A numerical UI parameter — the index to select the active oscillator"
index := PhNumEntry new label: 'Wave' init: 0 min: 0 max: 3 step: 1.
"Combine the oscillators in parallel using the comma operator"
oscillators := sine , tri , sqr , saw.
"Connect index + oscillators to the 4-input selector"
selector := index , oscillators connectTo: PhSelect4 new.
dsp := selector stereo asDsp.
dsp init.
dsp start.
"Open the UI — use the 'Wave' entry to switch waveform (0–3)"
dsp displayUI.
dsp stop.
dsp destroy.
Selecting from more than 4 with PhSelectN
When you need more than 4 options — for example adding Noise as a fifth source — use PhSelectN:
sine := SineOsc new.
tri := TriOsc new.
sqr := SquareOsc new.
saw := SawOsc new.
noise := Noise new.
index := PhNumEntry new label: 'Wave' init: 0 min: 0 max: 4 step: 1.
oscillators := sine , tri , sqr , saw , noise.
"PhSelectN accepts any number of inputs"
selector := (PhSelectN new: oscillators) index: index.
dsp := selector stereo asDsp.
dsp init.
dsp start.
dsp displayUI.
dsp stop.
dsp destroy.
, operator in Phausto places UGens in parallel (side-by-side channels). This is distinct from the => ChucK operator, which chains UGens in series.
Subtractive Synthesis
Lesson 7 Start with a harmonically rich source (pulse, saw, noise), shape its amplitude with an envelope, then sculpt the timbre with a filter. This is the classic analogue synthesiser model.
"1. An oscillator — the sound source"
oscillator := PulseOsc new.
"2. An ADSR envelope — shapes amplitude over time"
envelope := ADSREnv new.
"3. A Moog VCF low-pass filter — removes high frequencies"
filter := MoogVcf new.
"Chain them with the ChucK operator =>
oscillator => envelope multiplies osc by envelope signal
=> filter feeds the result into the filter input"
synth := oscillator => envelope => filter.
dsp := synth stereo asDsp.
dsp init.
dsp start.
"Open the UI — press the gate button to trigger the envelope"
dsp displayUI.
"Experiment with attack, decay, sustain, release, and filter cutoff"
dsp stop.
a => b => c is (a => b) => c — signal flows left to right, exactly as written.
Additive Synthesis
Lessons 8 & 9 Additive synthesis builds complex timbres by summing multiple sine waves — each with its own frequency, amplitude, and phase. It is the direct expression of Fourier's theorem: any periodic waveform is a sum of sine waves.
Stacking with a loop
"Start with one oscillator and add 9 more, each detuned by 90 Hz"
sine1 := SineOsc new freq: 200; uLevel: 0.5.
detuning := 90.
(1 to: 9) do: [ :i |
sine1 := sine1 + (SineOsc new freq: 200 + (i * detuning); uLevel: 0.05)
].
"sine1 is now the sum of 10 detuned SineOscs"
dsp := sine1 stereo asDsp.
dsp init.
dsp start.
dsp stop.
Using asSumOfUGen
Create an Array of UGens and sum them in one message:
detuning := 14.
groupOfSine := (1 to: 10) collect: [ :i |
SineOsc new freq: 200 + (i * detuning); uLevel: 0.05
].
"asSumOfUGen reduces the Array by summing all elements"
dsp := groupOfSine asSumOfUGen stereo asDsp.
dsp init.
dsp start.
dsp stop.
"Sent to a Collection of UGens.
Returns a single UGen that is the sum (mix) of all elements."
arrayOfUGens asSumOfUGen.
uLevel values small when stacking many oscillators to avoid clipping. For 10 oscillators at equal amplitude, start around uLevel: 0.05.
Modal Synthesis
Lesson 10 Modal synthesis models the resonant modes of physical objects — bells, drums, strings, metal bars. Each mode is a resonant filter with its own frequency, amplitude, and decay time (t60). An impulse triggers all modes simultaneously.
"A pulse generator fed through an impulsifier to create a click trigger"
trig := (Pulse new period: 0.18) => PhImpulsify new.
"Define mode properties — frequencies, amplitudes, decay times"
freqs := #(230 600 700 920).
gains := #(0.5 0.4 0.2 0.5).
t60s := #(0.2 0.5 0.3 0.6).
"Create a resonant mode filter for each mode"
modes := (1 to: 4) collect: [ :i |
PhModeFilter new
freq: (freqs at: i);
gain: (gains at: i);
t60: (t60s at: i);
input: trig
].
"Sum all modes to form the final synth"
synth := modes asSumOfUGen.
dsp := synth asDsp.
dsp init.
dsp start.
dsp displayUI.
dsp stop.
| Parameter | Class | Description |
|---|---|---|
| freq: | PhModeFilter | Resonant frequency of this mode (Hz) |
| gain: | PhModeFilter | Amplitude of this mode (0–1) |
| t60: | PhModeFilter | Decay time in seconds (time to -60 dB) |
| input: | PhModeFilter | Excitation signal (trigger, noise, etc.) |
FM Synthesis
Lesson 11 Frequency Modulation (FM) synthesis uses one oscillator (the modulator) to vary the frequency or phase of another (the carrier). Even two operators can produce a rich spectrum of timbres.
"A slider to set the base frequency of both operators"
frequencyKnob := PhHSlider new label: 'Freq' values: #(100 20 4000 0.1).
"Ratio between carrier and modulator frequency"
ratioKnob := PhHSlider new label: 'Ratio' values: #(1 0.1 32 0.1).
"The modulator: a SineOsc at frequency * ratio"
modulator := SineOsc new freq: frequencyKnob * ratioKnob.
"The carrier: a Dx7Op whose phase is modulated by the SineOsc"
carrier := Dx7Op new phaseMod: modulator; freq: frequencyKnob.
dsp := carrier stereo asDsp.
dsp init.
dsp start.
dsp displayUI. "Sweep Freq and Ratio to explore the timbral space"
dsp stop.
dsp destroy.
PhHSlider values: array format
| Index | Meaning |
|---|---|
| #( init … ) | Initial value |
| #( … min … ) | Minimum value |
| #( … max … ) | Maximum value |
| #( … step ) | Step size |
Pitch Envelope
Lesson 12 Use an ADSREnv to modulate pitch over time — essential for percussion synthesis (kick drum pitch drops, tom swells) and expressive melodic timbres.
"Scale the ADSR output to control pitch range"
"asBox wraps a number as a constant signal (a 'box' in FAUST terminology)"
pitchEnv := 200 asBox * ADSREnv new.
"Add the pitch envelope to a base frequency offset"
osc := TriOsc new freq: (100 asBox + pitchEnv).
dsp := osc stereo asDsp.
dsp init.
dsp start.
"Open the UI — press the ADSR trigger to hear the pitch sweep"
dsp displayUI.
dsp stop.
"Wraps a Pharo Number as a constant FAUST signal.
Required when mixing a Number with a UGen in an arithmetic expression,
because FAUST operators expect signal inputs on both sides."
100 asBox + pitchEnv. "→ a signal: constant 100 + ADSR signal"
asBox does this conversion. Without it, Pharo will try to add a Number to a UGen object, which will fail.