Basic Waveforms & Selection

Lesson 6 The four classic oscillator shapes — sine, triangle, square, sawtooth — have distinct harmonic content and timbre.

ClassWaveformCharacter
SineOscSinePure tone, no harmonics
TriOscTriangleOdd harmonics, soft and hollow
SquareOscSquareOdd harmonics, bright and reedy
SawOscSawtoothAll harmonics, rich and buzzy
PulseOscPulseVariable duty cycle square
NoiseWhite noiseAll 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.
The comma operator
The , 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.
Reading the => chain
In Pharo, binary operators have left-to-right precedence, so 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.
method asSumOfUGen
"Sent to a Collection of UGens.
 Returns a single UGen that is the sum (mix) of all elements."
arrayOfUGens asSumOfUGen.
Design tip
Keep individual uLevel values small when stacking many oscillators to avoid clipping. For 10 oscillators at equal amplitude, start around uLevel: 0.05.

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.
ParameterClassDescription
freq:PhModeFilterResonant frequency of this mode (Hz)
gain:PhModeFilterAmplitude of this mode (0–1)
t60:PhModeFilterDecay time in seconds (time to -60 dB)
input:PhModeFilterExcitation 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

IndexMeaning
#( init … )Initial value
#( … min … )Minimum value
#( … max … )Maximum value
#( … step )Step size
FM Tip
Integer ratios (1, 2, 3…) produce harmonic spectra. Non-integer ratios (1.41, 2.7…) produce inharmonic, bell-like or metallic timbres. Try extreme ratios above 16 for noise-like textures.

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.
method asBox
"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"
Why asBox?
In FAUST, arithmetic between a constant and a signal requires explicit wrapping of the constant as a signal. asBox does this conversion. Without it, Pharo will try to add a Number to a UGen object, which will fail.