A patch built in a Playground does not have to stay there. Phausto can emit a CMajor patch, a complete JUCE plugin project, or C++ for a Bela board — from the same object you have been listening to.

1. Why export at all

Pharo is an excellent place to design a sound and a poor place to ship one. Exporting closes that gap: the instrument you prototyped interactively becomes a plugin your collaborators can load, or a binary on a board that boots without a computer attached.

Everything rests on the same fact — a Phausto patch is a FAUST program. The exporters take the FAUST that generatedCode would give you and wrap it in whatever scaffolding the target needs.

Export is a code generator, not a compiler Phausto writes source files. Building them is a separate step with the target's own toolchain — the CMajor runtime, JUCE and CMake, or the Bela toolchain.

2. Plain FAUST files

The simplest export is the FAUST source itself, which is also the most portable — anything that accepts FAUST will take it.

MessageWrites
createFileNamed: aStringThe DSP as a FAUST file
createFaustSynthFileNamed: aNameA synth-shaped FAUST file
createFaustPolySynthFileNamed: aNameA polyphonic synth file
createFaustSynthHeaderFileNamed: aNameThe accompanying header
createFaustHeaderFileNamed: aName for: aPluginTypeA header for a given plugin type
generatedCodeThe source as a string, without writing anything
writeAsPosixText: aString to: aFileReferenceWrite text with POSIX line endings
synth := SawOsc new => ADSREnv new => MoogVcf new.
dsp := synth stereo asDsp.
dsp init.

dsp generatedCode.                        "inspect it first"
dsp createFaustSynthFileNamed: 'MySynth'.

3. CMajor patches

CMajor is a language and runtime for audio, with its own patch format that loads in a host or standalone player. Phausto emits complete patches, including the interface.

MessageProduces
createCmajorPatchNamed: aNameAn instrument patch
createCmajorFXPatchNamed: aNameAn effect patch — audio in, audio out
createCmajorMIDIPatchNamed: aNameA MIDI-driven instrument patch
dsp createCmajorMIDIPatchNamed: 'MyInstrument'.

3.1 The CMajor classes

The export is modelled by objects rather than string templating, so it can be extended:

ClassRole
CmajorPatchA patch
CmajorFXPatchAn effect patch
CmajorMIDIPatchA MIDI instrument patch
CmajorViewThe patch's interface
CmajorUIElementSuperclass of the view elements
CmajorKnobA knob
CmajorFaderA fader
CmajorBackgroundThe view background

Your UI primitives become CMajor endpoints and view elements automatically — a PhHSlider in the patch becomes a control in the exported interface, with the range you declared.

3.2 Polyphonic wrapping

A Phausto patch is monophonic by construction. To make a CMajor instrument polyphonic it has to be wrapped in a voice allocator, and Phausto generates that wrapper:

MessageProduces
asCmajorPolyWrapperFor: aNameA polyphonic voice wrapper
aDictionary asCmajorPolyWrapperThe wrapper from an endpoint description
aDictionary asCmajorEventEndpointAn event endpoint
aDictionary asCmajorEventsToVoicesConnectionEvent-to-voice routing
aDictionary asCmajorListOfUIEndpointsThe UI endpoint list
Design for one voice Build and tune the instrument as a single voice. Polyphony is added at export, and a patch that sounds right monophonically will almost always sound right multiplied.

4. JUCE plugins

The most substantial exporter writes a complete JUCE project: the audio processor, its parameter tree, an editor, a CMake build and a script that runs the build with one command. The result compiles into a VST3, an Audio Unit (on macOS) and a standalone app.

MessageWrites
export2JuceWithName: aString type: aPluginTypeThe project, with the generic editor
export2JuceWithName: aString type: aPluginType editor: anEditorExportThe project, with the editor you choose (§4.3)
fx := (PhResonLp new frequency: #Cutoff; q: #Resonance; yourself) stereo asDsp.
fx init.
fx export2JuceWithName: 'ResonFilter' type: PhEffectPluginType new.

The name becomes the plugin's product name and its CMake target, so use letters and digits only. The project lands in Documents/faust2Juce/ResonFilter/, and cmake -P build.cmake in that folder builds it (§4.5).

Once, before your first export Copy the architecture folder from the root of the Phausto repository into your Documents folder. The exporter compiles your FAUST program against Documents/architecture/faustMinimalInlined.h. Without it the export still finishes, but leaves out the FAUST header, and the build stops at the missing file.
Export checks the patch first A soundfile, more channels than the plugin type takes (§4.1), two controls with the same name, or a label containing a double quote or a backslash stops the export with an error that says which, before any file is written. Exported plugins cannot load soundfiles yet.

4.1 Plugin types

The type: argument is an object, not a string. It answers everything that differs between an effect and an instrument (the buses, the MIDI handling, the flags the host reads, which controls become parameters), so the exporter never tests which kind of plugin it is writing. A misspelt type fails when you accept the code, not halfway through an export.

TypeHost seesMIDIPatch channels
PhEffectPluginType newAn effect: audio in, audio outNoneUp to 2 in, up to 2 out
PhMonoSynthPluginType newAn instrument: MIDI in, audio outNotes drive freq, gate and gainNo inputs, up to 2 out

The buses are stereo, and a host may also choose mono. A mono patch is fine either way: the generated PhaustoChannelAdapter.h repeats the last channel when the bus has more and averages when it has fewer, so a one-channel program feeds both sides.

An instrument is played through three controls named exactly freq, gate and gain, the FAUST convention. A note-on sets freq from the note number and gain from the velocity, then opens gate; the matching note-off closes it. The last note wins, and MIDI is read once per audio block. Because MIDI drives those three, they never become host parameters; the patch's other controls (here the envelope's attack and release) do.

lead := ((SquareOsc new freq: #freq)
    => (AREnv new trigger: #gate; release: #Release; yourself))
    * (PhHSlider new label: #gain).
dsp := lead stereo asDsp.
dsp init.
dsp export2JuceWithName: 'SquareLead' type: PhMonoSynthPluginType new.
Polyphony is in development A third type, PhPolySynthPluginType voices: 8, wraps the same single-voice patch in FAUST's own voice allocator, with voice stealing, and every knob drives every voice. It is not usable yet, so for now a JUCE instrument plays one note at a time. For a polyphonic instrument today, export to CMajor with the poly wrapper (§3.2).

4.2 Parameters and the APVTS

JUCE's AudioProcessorValueTreeState is how a plugin exposes automatable parameters to a host. Phausto builds it from the compiled program: every control in the DSP's FAUST UI description becomes one row of a generated table, PhaustoParams.h, holding its id, FAUST path, label, range, default and step. The processor creates its parameters from that table, and the generic editor creates its widgets from the same table, so parameters and controls cannot drift apart.

In the patchIn the plugin
PhHSlider, PhVSlider, PhNumEntryAn AudioParameterFloat with the range, step and default you declared
PhButton, PhCheckboxAn AudioParameterBool
PhHBarGraph, PhVBarGraphNothing: a bar graph is an output, not a parameter
freq, gate, gain (instruments)Nothing: MIDI drives them (§4.1)
A soundfile, as in samplersThe export stops

A parameter's id is its control's short name, and the host displays its label. Once per audio block the processor writes each parameter's current value into the DSP through its FAUST path, so automation runs at block rate. The values are saved with the host session and restored when it reopens.

Names are ids Renaming a control renames its parameter, and a session saved with the old plugin loses that parameter's value and automation. Settle the names before you share a plugin.

To see what the host will get without exporting anything:

MessageReturns
apvtsControlsEvery control of the compiled DSP, one dictionary each
apvtsParameterControlsThe controls that become parameters in an effect
apvtsParameterControlsFrom: aJsonString for: aPluginTypeThe same for any type, from a UI description
asPhaustoParamsHeaderFrom: aJsonString for: aPluginTypeThe text of PhaustoParams.h
dsp apvtsParameterControlsFrom: dsp getJSON for: PhMonoSynthPluginType new.
dsp asPhaustoParamsHeaderFrom: dsp getJSON for: PhMonoSynthPluginType new.
The compiled program is the source of truth The table is read from the FAUST JSON of the compiled DSP (getJSON, see DSP & Parameter API §7), not from Pharo objects. A patch built from UGens, from raw FAUST or with the Box API exports the same way, and the plugin exposes exactly what the program declares.

4.3 Editors

The editor: argument decides what the plugin window shows. It writes only PluginEditor.h and PluginEditor.cpp: the processor and the rest of the project are the same whichever editor you choose.

EditorGives you
PhGenericEditorExport newThe default. One labelled control per parameter: a rotary slider, or a toggle for buttons and checkboxes. Built from PhaustoParams.h at run time, and rewritten on every export.
PhSkeletonEditorExport newAn empty editor for a UI you write in C++. Written once: exporting again never overwrites it.
fx export2JuceWithName: 'ResonFilter'
  type: PhEffectPluginType new
  editor: PhSkeletonEditorExport new.

In the skeleton, parameters are reached through audioProcessor.apvts, and their ids are listed in PhaustoParams.h. Bind a JUCE control to one with an attachment, declared after the control so that it is destroyed first:

// PluginEditor.h, private members
using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;
juce::Slider cutoff;
std::unique_ptr<SliderAttachment> cutoffAttachment;

// PluginEditor.cpp, in the constructor
addAndMakeVisible (cutoff);
cutoffAttachment = std::make_unique<SliderAttachment> (
    audioProcessor.apvts, "Cutoff", cutoff);

// PluginEditor.cpp, in resized()
cutoff.setBounds (getLocalBounds().reduced (20));

Keep the class name and constructor the skeleton gives you (EffectAudioProcessorEditor or MonoSynthAudioProcessorEditor, built from the processor): the generated createEditor() relies on both. To generate an editor of your own from Pharo, subclass PhEditorExport and answer headerFor: and implementationFor:; override write:to: to change when the files are overwritten.

Coming next: an editor designed in Bloc JuBloc will add a third editor, so that a UI designed live in Pharo is emitted as JUCE components bound to these same parameter ids. It ships with JuBloc, so Phausto itself never depends on Bloc.

4.4 The generated project

Everything is written to Documents/faust2Juce/<Name>/:

FileHolds (written by)
CMakeLists.txtThe JUCE build: formats, plugin codes, JUCE version (createCMakeFileNamed:for:)
build.cmakeThe one-command build, §4.5 (createBuildScriptNamed:)
<Name>.jucerThe same project for the Projucer (createJucerFileNamed:for:)
Source/Faust<Type>.hYour FAUST program, compiled to C++ (createFaustHeaderFileNamed:for:)
Source/PhaustoParams.hThe parameter table, §4.2 (createPhaustoParamsHeaderNamed:for:)
Source/PhaustoChannelAdapter.hThe mapping between program channels and buses, §4.1 (createChannelAdapterNamed:)
Source/PluginProcessor.h, .cppThe audio processor: parameters, MIDI, saved state (createHeaderAPVTSFilesNamed:for:, createCppAPVTSFilesNamed:for:)
Source/PluginEditor.h, .cppThe plugin window, §4.3 (the editor export)

<Type> is Effect or MonoSynth, which is also the prefix of the processor class (EffectAudioProcessor). Every file but the FAUST header also has a query that answers its text without writing anything, such as asCMakeListsFor:type: or asPluginProcessorCppAPVTSFor:: inspect those to read the C++ before you export.

Exporting again overwrites Every file above is rewritten on each export, except an editor written by PhSkeletonEditorExport. Keep hand-written C++ in a skeleton editor, never in the generated files.

The export name also decides how hosts tell the plugin apart:

From 'ResonFilter'Used as
ResonFilterProduct name and CMake target
ResoPlugin code: the first four letters, one of them a capital (the AU rule)
Phausto, PhauManufacturer and its code, the same for every Phausto plugin
com.phausto.resonfilterBundle id
The first four letters must differ A host identifies a plugin by its manufacturer code and plugin code, and the manufacturer is shared. Two names that start with the same four letters, such as ResonFilter and ResonDrive, give two plugins the host takes for one. (PhPluginIdentity productName: 'ResonFilter') pluginCode shows the code before you export.

4.5 Building

You need CMake 3.22 or newer and a C++ compiler: Xcode on macOS, Visual Studio on Windows, GCC or Clang on Linux together with the packages in JUCE's Linux dependencies. Then, in a terminal on any of the three:

cd ~/Documents/faust2Juce/ResonFilter
cmake -P build.cmake
CommandDoes
cmake -P build.cmakeConfigures once, builds Release on every core, installs the plugins
cmake -DJUCE_PATH=/path/to/JUCE -P build.cmakeThe same with your own JUCE instead of a download (a JUCE_PATH environment variable works too)

JUCE copies the VST3 and the Audio Unit into the system plug-in folders, and the standalone app stays in build/ResonFilter_artefacts/Release/Standalone/. Rescan plug-ins in your host to find the new one. After a change in Pharo, export again and rerun the script: only what changed is rebuilt.

The first build downloads JUCE Without JUCE_PATH, CMake fetches JUCE 8.0.4 with git, which takes a few minutes once. The script configures only on its first run, so delete the build folder before switching between the download and your own JUCE.

The script is a convenience, not a requirement. CMakeLists.txt is an ordinary JUCE CMake project that any CMake-aware IDE opens, and the .jucer is there if you prefer the Projucer.

5. Bela

Bela is an embedded platform for low-latency audio. Exporting to it produces a C++ file to build on the board:

MessageWrites
createBelaCppFileNamed: aNameA Bela-ready C++ file

Bela's analogue inputs are reached through widget metadata: declaring belaPin: on a UI primitive binds that control to a physical input. A potentiometer wired to the board then drives the parameter directly, with no code in between.

"This slider becomes a hardware input on the board"
cutoff := PhHSlider new
  label: 'Cutoff' values: #(800 40 12000 1);
  belaPin: 0.
Prototype with the mouse, deploy to the knob The same declaration is a slider in Pharo and an analogue pin on the board. You can develop the instrument entirely on your laptop and only then move it, without changing the patch.

6. Writing a patch that travels

Some habits cost nothing while prototyping and save real trouble at export. All of them amount to the same principle: put information in the patch rather than in the Playground around it.

Use widgets, not numbers — a value fixed as a literal is invisible to every exporter. A PhHSlider becomes a plugin parameter, a CMajor endpoint or a Bela pin.
Declare real ranges — the minimum, maximum, step and default travel with the parameter and become what the host shows. Sloppy ranges become a sloppy plugin.
Name things deliberately — parameter names become the names musicians see. Relabel your UGens, as in Unit Generators §2.2.
Express time in seconds — a delay fixed in samples changes pitch at a different sample rate. Use PhSec2Samp or PhSampleRate; see Math §6.
Keep sequencing outside the patch — forked Pharo loops do not export. Rhythm that must survive belongs in the signal graph, as pulses and counters; see Sequencing §1.
Declare MIDI bindings on the widget: midiCtrl: is honoured by the FAUST runtime and by the board. A JUCE plugin leaves controller mapping to the host, where you MIDI-learn the parameter instead.
The one that catches everyone A patch driven by [ ... ] fork and dsp setValue:parameter: makes sound in Pharo and silence everywhere else. Pharo is the sequencer in that arrangement, and Pharo does not come with the plugin.

7. Where to go next

DocumentWhat it covers
UI PrimitivesDeclaring the controls that become plugin parameters.
DSP & Parameter APIThe parameter model the exporters read.
Raw FAUST & the Box APIReading and shaping the FAUST that gets exported.
MIDI & Playing NotesMaking an instrument playable before you export it.

8. Troubleshooting

The exported plugin has no parameters

The program declares no control the host can see: its values are fixed inside the signal graph, or it is an instrument whose only controls are freq, gate and gain, which MIDI drives (§4.1). Expose what should be automatable as widgets (§6). Give a JUCE plugin at least one parameter: one with none does not compile with Visual Studio.

The plugin loads but is silent

If the sequencing lived in a forked Pharo loop, it did not come with you. Move the rhythm into the signal graph, or drive the plugin from the host (§6).

The instrument ignores the notes I play

MIDI reaches only controls named exactly freq, gate and gain. List the names with dsp traceAllParams (see DSP & Parameter API §4.2), relabel what differs, and export again with PhMonoSynthPluginType (§4.1).

A controller bound with midiCtrl: does nothing in the plugin

A JUCE plugin does not read MIDI metadata. Map the controller to the parameter in your host, with its MIDI learn (§6).

Parameter ranges in the host look wrong

They are whatever you declared on the widget. Check the values: array order: initial value first. See UI Primitives §2.1.

The exported instrument is monophonic

Phausto patches are single-voice by construction, and JUCE export plays one note at a time until the polyphonic type lands (§4.1). For polyphony today, use the CMajor poly wrapper (§3.2).

The export stops with an error

The message names the check that failed, and no file was written: a soundfile, more channels than the plugin type takes, two controls with the same name, or a double quote or backslash in a label (§4). A stereo unit fed from a mono chain can declare more inputs than it seems to; compare dsp numberOfInputs and dsp numberOfOutputs with the limits in §4.1.

The build cannot find FaustEffect.h or FaustMonoSynth.h

The FAUST program was never compiled because Documents/architecture is missing. Copy the folder from the Phausto repository and export again (§4).

The generated project will not build

Export writes source, not binaries: the target's toolchain does the rest (§1). For JUCE, check CMake 3.22 or newer, a C++ compiler, and either git with a network connection for the JUCE download or a valid JUCE_PATH (§4.5).

On Windows, the build fails while installing the plugin

The system VST3 folder may not be writable without administrator rights. Run the terminal as administrator, or take the plugin from build/<Name>_artefacts/Release/VST3/ yourself (§4.5).

The new plugin does not appear in the host

Rescan plug-ins, or restart the host: Ableton Live may list a new plugin only after a restart. On macOS, auval -a lists every Audio Unit the system can see.

Two exported plugins show up as one

Their names start with the same four letters, so they share a plugin code. Rename one and export again (§4.4).

My changes to PluginEditor.cpp disappeared

The generic editor rewrites its files on every export. Write your own UI with PhSkeletonEditorExport, whose files are never overwritten (§4.3).

A new control is missing from my hand-written editor

That is by design: export never touches a skeleton editor. Add the control in C++, with the id listed in PhaustoParams.h (§4.3).

The exported patch sounds different from the one in Pharo

Most often a sample-rate assumption. Anything specified in samples will shift; express times in seconds (§6).