Subtractive, additive, modal and FM synthesis, each as a complete runnable patch — the four approaches that account for most of the sound design you will ever do.
1. Basic waveforms and selection
The four classic oscillator shapes differ in harmonic content, and that difference is the starting point of subtractive synthesis: you cannot filter out harmonics that were never there.
| Class | Waveform | Character |
|---|---|---|
| SineOsc | Sine | Pure tone, no harmonics |
| TriOsc | Triangle | Odd harmonics, weak; soft and hollow |
| SquareOsc | Square | Odd harmonics, strong; bright and reedy |
| SawOsc | Sawtooth | All harmonics; rich and buzzy |
| PulseOsc | Pulse | Square with a variable duty cycle |
| Noise | White noise | All frequencies at equal power |
Beyond these, Phausto carries 42 oscillators in total, including the thirteen Casio CZ phase-distortion shapes and a family of antialiased variants. See the UGen Library.
1.1 Switching waveforms with PhSelect4
Combine four oscillators in parallel with the comma operator and use a
PhNumEntry to choose between them at runtime:
"Create the four oscillators"
sine := SineOsc new.
tri := TriOsc new.
sqr := SquareOsc new.
saw := SawOsc new.
"A numerical UI parameter — the index of 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.
index , oscillators connectTo: … mirrors FAUST's own routing syntax closely.
It is available when you need it, but the Phausto API in §1.2 is easier to read and should
be preferred.
1.2 More than four with PhSelectN
When you need more than four options — adding Noise as a fifth source, say —
PhSelectN takes any number of inputs:
sine := SineOsc new.
tri := TriOsc new.
sqr := SquareOsc new.
saw := SawOsc new.
noise := Noise new.
"five sources, so the index runs 0 to 4"
index := PhNumEntry new label: 'Wave' init: 0 min: 0 max: 4 step: 1.
oscillators := sine , tri , sqr , saw , noise.
selector := (PhSelectN new: oscillators) index: index.
dsp := selector stereo asDsp.
dsp init.
dsp start.
dsp displayUI.
dsp stop.
dsp destroy.
max: is smaller than the number of sources, the extra ones are simply
unreachable — a silent bug, since nothing errors. Five sources need
min: 0 max: 4.
2. Subtractive synthesis
Start from a harmonically rich source, shape its amplitude with an envelope, then sculpt its timbre with a filter. This is the classic analogue synthesiser model, and in Phausto it is three objects and two operators:
"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 the osc by the 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 in reading order, which is exactly what you
want. More on this in Connecting Unit Generators.
Swap MoogVcf for any of the sixty filters — DiodeLadder,
Korg35LPF, OberheimBPF, PhSvfLp — and the character
changes completely while the patch stays the same. See Filters.
3. Additive synthesis
Additive synthesis builds complex timbres by summing 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 sinusoids. Hammond organs and the Telharmonium are additive instruments built in hardware.
3.1 Stacking with a loop
"Start with one oscillator and add nine 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 ten detuned SineOscs"
dsp := sine1 stereo asDsp.
dsp init.
dsp start.
dsp stop.
Change detuning and listen. At 90 Hz the partials are far apart and you hear
a chord; at 2 Hz they beat against each other and you hear a single thick tone.
3.2 Using asSumOfUGen
The same patch, expressed as a collection rather than an accumulator:
detuning := 14.
groupOfSine := (1 to: 10) collect: [ :i |
SineOsc new freq: 200 + (i * detuning); uLevel: 0.05
].
"asSumOfUGen reduces the collection by summing every element"
dsp := groupOfSine asSumOfUGen stereo asDsp.
dsp init.
dsp start.
dsp stop.
Sent to any SequenceableCollection of UGens; returns a single UGen that is
the mix of every element. Its sibling asChainOfUGen connects them in series
instead — see Connecting Unit Generators.
uLevel: 0.5 would sum to 5.0 and clip hard. Divide your
budget: for n equal sources, start around 1/n.
4. Modal synthesis
Modal synthesis models the resonant modes of a physical object — bells, drums, struck bars. Each mode is a resonant filter with a frequency, an amplitude and a decay time (t60, the time to fall 60 dB). An impulse excites them all at once, and the way they decay at different rates is what makes the result sound like a real object.
"A pulse fed through an impulsifier gives a clean click to excite the modes"
trig := (Pulse new period: 0.18) => PhImpulsify new.
"Mode frequencies, amplitudes, and 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).
"One resonant filter per mode, all excited by the same trigger"
modes := (1 to: 4) collect: [ :i |
PhModeFilter new
freq: (freqs at: i);
gain: (gains at: i);
t60: (t60s at: i);
input: trig
].
"Sum the modes to form the instrument"
synth := modes asSumOfUGen.
dsp := synth asDsp.
dsp init.
dsp start.
dsp displayUI.
dsp stop.
| Parameter | Meaning |
|---|---|
| freq: | Resonant frequency of this mode, in hertz |
| gain: | Amplitude of this mode, 0–1 |
| t60: | Decay time in seconds — time to fall 60 dB |
| input: | Excitation signal: an impulse, a click, a burst of noise |
PhModeFilter and PhImpulsify, with the
Ph prefix. Older tutorial material occasionally shows ModeFilter
and Impulsify; those names do not exist and will raise a
doesNotUnderstand.
Inharmonic frequency ratios give bells, near-harmonic ratios give tuned percussion. Phausto
ships ready-made modal instruments too — Marimba, EnglishBell,
FrenchBell, RussianBell, GermanBell — described in
Physical Modelling.
5. FM synthesis
Frequency modulation uses one oscillator, the modulator, to vary the frequency or phase of another, the carrier. Two operators are enough for an enormous timbral range — this is the principle the Yamaha DX7 made famous, and Phausto includes DX7-style operators directly.
"A slider for the base frequency of both operators"
frequencyKnob := PhHSlider new label: 'Freq' values: #(100 20 4000 0.1).
"The ratio between carrier and modulator frequency"
ratioKnob := PhHSlider new label: 'Ratio' values: #(1 0.1 32 0.1).
"The modulator: a sine at frequency × ratio"
modulator := SineOsc new freq: frequencyKnob * ratioKnob.
"The carrier: a Dx7 operator whose phase the modulator drives"
carrier := Dx7Op new phaseMod: modulator; freq: frequencyKnob.
dsp := carrier stereo asDsp.
dsp init.
dsp start.
dsp displayUI. "sweep Freq and Ratio to explore the space"
dsp stop.
dsp destroy.
5.1 The PhHSlider values: array
values: takes a four-element array in a fixed order. Getting the order wrong
is a common source of sliders that appear to do nothing.
| Position | Meaning |
|---|---|
| 1st | Initial value |
| 2nd | Minimum |
| 3rd | Maximum |
| 4th | Step size |
So #(100 20 4000 0.1) reads: start at 100, range 20 to 4000, in steps of
0.1. The equivalent long form is
label:init:min:max:step:, which PhNumEntry uses above. See
UI Primitives.
Dx7Op exposes the full DX7 operator model — envelope rates and levels,
breakpoints, keyboard scaling, LFO sensitivity — around ninety setters in all. For four-
operator arrangements see Fm4Op, and for the two-operator shorthand,
Fm2Op in TurboPhausto.
6. Pitch envelopes
Envelopes are not only for amplitude. Routed to frequency, an ADSREnv produces
the pitch drop that defines a kick drum, or the swell of a tom:
"Scale the ADSR output to a 200 Hz sweep range"
pitchEnv := 200 asBox * ADSREnv new.
"Offset it from a 100 Hz base frequency"
osc := TriOsc new freq: (100 asBox + pitchEnv).
dsp := osc stereo asDsp.
dsp init.
dsp start.
"Open the UI — press the trigger to hear the sweep"
dsp displayUI.
dsp stop.
asBox wraps a Pharo Number as a constant signal. FAUST arithmetic
needs signals on both sides of an operator, so a bare number added to a UGen has to be
promoted first. 100 asBox + pitchEnv is a signal; 100 + pitchEnv
asks a SmallInteger to add a UGen to itself, which fails.
SineOsc new freq: 440 is fine.
asBox is only needed when the number is one operand of an arithmetic expression
whose other operand is a signal.
The envelope family, and modulation more generally, is covered in Envelopes & Modulation.
8. Troubleshooting
The subtractive patch is silent no matter what I do
An ADSREnv outputs zero until it is gated. Open dsp displayUI and
press the trigger button, or gate it from code — see
Parameters & Control.
My additive stack is distorted and crackly
Summed amplitudes exceed 1.0 and are clipping. See the headroom note in §3.2.
The waveform selector only reaches some of my oscillators
The index range is too small. Five sources need max: 4 — see §1.2.
ModeFilter raises doesNotUnderstand
The class is PhModeFilter, and the impulsifier is PhImpulsify. See §4.
Moving the FM sliders changes nothing
Check the order of the values: array — a minimum above the maximum, or a step
larger than the range, produces a slider that cannot move. See §5.1.
The modal patch clicks but never rings
The t60: values are too short, or the excitation is a continuous signal rather
than an impulse. Pulse => PhImpulsify gives a single-sample click; feeding a
raw Pulse keeps the modes driven and smears the decay.