Two ways out of the UGen layer when it does not reach far enough: write FAUST source directly, or build FAUST boxes yourself through the API the UGens are themselves built on.
1. Three layers
Everything in Phausto compiles down to the same thing. Knowing which layer you are on tells you what is available and what it costs:
asBox; operators like => and , are box operations underneath.DSP, and both are driven by
the same DSP API. Dropping to a lower layer for one part of a
patch does not commit you to it everywhere.
2. Writing FAUST source
2.1 Creating a DSP from a string
content := 'import("stdfaust.lib");
tempo = hslider("tempo", 10000, 300, 20000, 100);
freq = hslider("freq", 300, 200, 900, 100);
process = ba.pulsen(1, tempo) : pm.djembe(freq, 0.3, 0.4, 1) <: dm.freeverb_demo;'.
x1 := DSP create: content.
x1 init.
x1 start.
x1 stop.
The resulting object is an ordinary DSP: displayUI,
setValue:parameter:, traceAllParams and the exporters all work
exactly as they do for a UGen-built patch.
DSP create:withName: names it.
2.2 The standard libraries
import("stdfaust.lib") brings in the whole FAUST standard library under short
prefixes. This is the real reason to drop to source: Phausto wraps a large part of it, but
not all of it.
| Prefix | Library |
|---|---|
os. | Oscillators |
no. | Noises |
fi. | Filters |
en. | Envelopes |
ef. | Misc effects |
re. | Reverbs |
co. | Compressors |
pm. | Physical modelling |
ba. | Basics |
ma. | Maths |
si. | Signals |
an. | Analysis |
dm. | Demos |
sf. | Synth files |
: sequential,
, parallel, <: split, :> merge, and
~ recursive. Phausto's =>, ,,
splitTo:, mergeWith: and ~ map onto them one for
one — see Connecting Unit Generators.
2.3 Reaching the controls
Controls declared in FAUST source behave like any other parameter. The label in the source becomes the parameter name:
s1 := x1 sliderFor: 'tempo'. s1 openInWindow.
s2 := x1 sliderFor: 'freq'. s2 openInWindow.
"Nested group paths for controls inside library modules"
s3 := x1 sliderFor: 'Freeverb/0x00/RoomSize'. s3 openInWindow.
x1 traceAllParams. "the authoritative list"
2.4 Stored FAUST programs
DSPCode is a small convenience class holding complete FAUST programs as
strings, reachable from the class side:
| Message | Returns |
|---|---|
| DSPCode basicKickWithADSR | A kick drum with an ADSR envelope |
| DSPCode basicHiHatFM | An FM hi-hat |
| DSPCode drumMachine7 | A seven-voice drum machine |
| DSPCode drumMachine8 | An eight-voice drum machine |
dsp := DSP create: DSPCode drumMachine8.
dsp init.
dsp start.
dsp displayUI.
These are also worth reading as examples of complete, working FAUST programs.
3. The box layer
3.1 PhBox
A box is FAUST's internal representation of a signal expression. Phausto objects
are always converted to a box before being assembled into a DSP — PhBox is
the Pharo-side container, and every UGen ultimately produces one.
| Message | Effect |
|---|---|
| asBox | The box for any UGen, number or widget |
| asFaustCode | The FAUST expression this box represents |
| fromString: | Build a box from a FAUST expression |
| asDsp / asDspWithName: | Compile the box into a DSP |
| asDspFile | Write the box out as a FAUST file |
| inputs / outputs | Channel counts |
| errorBuffer | The compiler's error output for this box |
| extractUIPrimitivesFromPrint | Recover the widgets a box declares |
PhBox also carries the whole operator set — =>,
,, arithmetic, comparison, ~, smoo,
db2linear and the rest — which is why those operators work on anything that
can become a box.
3.2 The concrete boxes
| Class | Role |
|---|---|
| Wire | A pass-through connection |
| Cut | Terminate a signal — discard a channel |
| Select2 | Two-way selector |
| PhSelect4 | Four-way selector |
| PhMultiplier | Multiplication box |
| PhIncrementer | Incrementing box |
| SoundFile | A sound file as a signal source |
| SamplePlayer | Sample playback box |
Cut, and channels that must pass
through untouched need Wire. Most channel-count errors are solved with one or
the other.
3.3 The library context
The FAUST compiler needs a library context to build boxes in. Phausto manages this for you at start-up, but if you are constructing boxes by hand the messages are:
| Message | Effect |
|---|---|
| BoxAPI new createLibContext | Create a compilation context |
| BoxAPI new destroyLibContext | Destroy it |
| PhBox libContext / libContext: | Whether a context is currently held |
| PhBox startUp: | Context handling at image start-up |
4. The Box API
BoxAPI is the FFI binding to the C functions of FAUST's box API, compiled into
libfaust. This is the floor of the system: every operator in Phausto eventually
becomes a call here.
4.1 Composition primitives
| Message | FAUST operator |
|---|---|
| boxSeqFrom:to: | : sequential composition |
| boxPar:and: | , parallel composition |
| boxSplit:with: | <: split |
| boxMerge:with: | :> merge |
| boxRecursive:with: | ~ recursive composition |
4.2 Values, wires and widgets
| Message | Produces |
|---|---|
| boxInt: / boxReal: | Integer and floating-point constants |
| boxWire / boxCut | A wire, and a terminated channel |
| boxButton: / boxCheckbox: | Discrete widgets |
| boxHslider:init:min:max:step: | A horizontal slider |
| boxVslider:init:min:max:step: | A vertical slider |
| boxNumEntry:init:min:max:step: | A numeric entry |
| boxHBarGraphAux:min:max:input: | A horizontal meter |
| boxVBarGraphAux:min:max:input: | A vertical meter |
| boxSoundFile:numChannels: | A sound file source |
| boxReadOnlyTable:init:index: | A read-only wavetable |
| boxSelect2Aux:input0:input1: | A two-way selector |
| boxFromString:inputs:outputs:buffer: | Compile a FAUST expression into a box |
| boxPrint: | Print a box for inspection |
4.3 Arithmetic and maths
| Group | Messages |
|---|---|
| Arithmetic | boxAdd:with:, boxSubtract:from:, boxMultiply:with:, boxDivide:by:, boxPower:exponent:, boxRem:with:, boxFModBetween:and: |
| Comparison | boxGt:than:, boxGE:than:, boxLT:than:, boxLE:than:, boxEQ:than:, boxNE:than: |
| Ranges | boxMinBetween:and:, boxMaxBetween:and: |
| Rounding | boxFloor, boxCeil, boxRound, boxRint, boxAbs |
| Transcendental | boxSin, boxCos, boxTan, boxAsin, boxAcos, boxAtan, boxExp, boxLog, boxLog10, boxSqrt |
5. Inspecting what Phausto generated
The most useful thing about the lower layers, for most people, is reading rather than writing them. Any DSP will show you the FAUST it compiled:
synth := PulseOsc new => ADSREnv new => MoogVcf new.
dsp := synth stereo asDsp.
dsp init.
dsp generatedCode. "the FAUST source Phausto built"
synth asBox asFaustCode. "the expression for one box"
generatedCode, change one operator, read it again. The
difference between , and +, or between applying
stereo before and after an effect, becomes obvious immediately in the generated
source — and is often hard to hear.
7. Troubleshooting
My FAUST string fails to compile and the error is unhelpful
Ask the engine directly: PhaustoDynamicEngine new getLastError returns the FAUST
compiler's own message, which names the line. See
Architecture & Internals.
A null box exception
A box expression produced nothing — nearly always a channel-count mismatch. Check
inputs and outputs on each part, and terminate surplus channels
with Cut — §3.2.
My FAUST code works in the online editor but not here
Check that process is defined and that every library you use is imported.
Phausto compiles exactly the string you give it, with no implicit preamble.
Controls declared in my FAUST source do not appear
A FAUST widget only exists if it is actually used in process. An unreferenced
slider is optimised away. §2.3.
Boxes stop working after saving and reopening the image
They hold pointers into the compiler's memory. Rebuild after a restart — §3.3.
I want a FAUST function Phausto has no UGen for
That is exactly what §2 is for — write the string and wrap it in a DSP.