Using Faust in Cmajor

In this tutorial, we present how Faust can be used with Cmajor, a C like procedural high-performance language especially designed for audio processing, and with dynamic JIT based compilation. Compiling Faust DSP to cmajor code will allow to take profit of hundreds of DSP building blocks implemented in the Faust Libraries, ready to use Examples, any DSP program developed in more than 200 projects listed in the Powered By Faust page, or Faust DSP programs found on the net.

Who is this tutorial for?

The first section assumes a working Faust compiler installed on the machine, so is more designed for regular Faust users. The second section is better suited for Cmajor users who want to discover Faust.

Installing the required packages

Download the cmaj package and install to have it in your PATH.

Using command line tools

Generating Cmajor code

Assuming you've installed the faust compiler, starting from the following DSP osc.dsp program:


The Cmajor code can be generated using:

faust -lang cmajor osc.dsp -o osc.cmajor

This will generate a mydsp processor included in a namespace faust {...} with a set of methods to manipulate it. This API basically mimics the one defined for the C++ backend.

Looking at the generated code

The generated code contains input event definition for the three sliders defined in the DSP source code:

input event float32 eventfHslider1 [[ name: "freq1", group: "/v:Oscillator/freq1", min: 20.0f, max: 3000.0f, init: 1000.0f, step: 1.0f, meta_unit0: "Hz" ]];
input event float32 eventfHslider2 [[ name: "freq2", group: "/v:Oscillator/freq2", min: 20.0f, max: 3000.0f, init: 200.0f, step: 1.0f, meta_unit1: "Hz" ]];
input event float32 eventfHslider0 [[ name: "volume", group: "/v:Oscillator/volume", min: 0.0f, max: 1.0f, init: 0.5f, step: 0.01f ]];

The needed main function executes the DSP sample generation code:

void main()
{
    // DSP loop running forever...
    loop
    {
        if (fUpdated) { fUpdated = false; control(); }

        // Computes one sample
        fRec1[0] = fControl[1] + fRec1[1] - floor (fControl[1] + fRec1[1]);
        output0 <- float32 (fControl[0] * ftbl0mydspSIG0.at (int32 (65536.0f * fRec1[0])));
        fRec2[0] = fControl[2] + fRec2[1] - floor (fControl[2] + fRec2[1]);
        output1 <- float32 (fControl[0] * ftbl0mydspSIG0.at (int32 (65536.0f * fRec2[0])));
        fRec1[1] = fRec1[0];
        fRec2[1] = fRec2[0];

        // Moves all streams forward by one 'tick'
        advance();
    }
}

Note that the generated code uses the so-called scalar code generation model, the default one, where the compiled sample generation code is inlined on the Cmajor loop block.

We cannot directly play the generated osc.cmajor file since the cmaj program expects a osc.cmajorpatch to execute. A simple solution is to use the following command:

cmaj create --name=osc osc 

to create an osc folder with default osc.cmajor and osc.cmajorpatch files. Then using the command:

faust -lang cmajor osc.dsp -o osc/osc.cmajor

allows to simply replace the default osc.cmajor with our own Faust generated version.

The patchfile can now be compiled and executed using the cmaj program:

cmaj play osc/osc.cmajorpatch

The three declared sliders are automatically created and can be used to change the two channels frequencies and their volume.

The Cmajor processor code can directly be used in a more complex Cmajor program, possibly connected to other Faust generated or Cmajor hand-written processors. Note that the generated processor name can simply be changed using the Faust compiler -cn <name> option, so that several Faust generated processors can be distinguished by their names:

faust -lang cmajor -cn osc osc.dsp -o osc/osc.cmajor

Using the faust2cmajor tool

The faust2cmajor tool allows to automate calling the Faust compiler with the right options and interacting with the cmaj program:

faust2cmajor -h
Usage: faust2cmajor [options] [Faust options] <file.dsp>
Compiles Faust programs to Cmajor
Options:
-midi : activates MIDI control
-nvoices <num> : produces a polyphonic DSP with <num> voices, ready to be used with MIDI events
-effect <effect.dsp> : generates a polyphonic DSP connected to a global output effect, ready to be used with MIDI or OSC
-effect auto : generates a polyphonic DSP connected to a global output effect defined as 'effect' in <file.dsp>, ready to be used with MIDI or OSC
-juce : to create a JUCE project
-dsp : to create a 'dsp' compatible subclass
-play : to start the 'cmaj' runtime with the generated Cmajor file
Faust options : any option (e.g. -vec -vs 8...). See the Faust compiler documentation.

So the following command:

faust2cmajor -play osc.dsp 

will directly compile the osc.dsp file, generate the osc.cmajor and osc.cmajorpatch files:

{
    "CmajorVersion": 1,
    "ID": "grame.cmajor.osc",
    "version": "1.0",
    "name": "osc",
    "description": "Cmajor example",
    "category": "synth",
    "manufacturer": "GRAME",
    "website": "https://faust.grame.fr",
    "isInstrument": false,
    "source": "osc.cmajor"
}

And activate the cmaj program to run the processor.

The following polyphonic ready instrument DSP can be converted to a MIDI ready cmajor instrument:


The following command then opens the cmaj program and MDI events can be sent to control the instrument:

faust2cmajor -play -midi -nvoices 16 organ.dsp 

Note that the generated GUI is empty, since the generated processor cannot automatically reflects its controls in the main graph.

The following polyphonic ready instrument DSP, with an integrated effect, can be converted to a MIDI ready cmajor instrument:


The following command then opens the cmaj program and MDI events can be sent to control the instrument:

faust2cmajor -play -midi -nvoices 16 -effect auto clarinet.dsp 

Here again the generated GUI is empty.

Generating the Cmajor output using faustremote

A possibility is to use the faustremote script to convert a local DSP with the following commands:

faustremote cmajor cmajor foo.dsp
unzip binary.zip

Using the Faust Web IDE

Faust DSP program can be written, tested in the Faust Web IDE and generated as embeddable Cmajor code.

Generating the Cmajor output

The output as a Cmajor program can directly be generated using the Platform = cmajor and Architecture = cmajor export options. The resulting foo folder is self-contained, containing the foo.cmajor and foo.cmajorpatch files. The program can be executed using cmaj play foo/foo.cmajorpatch command or possibly converted as a JUCE plugin.

Exporting the code

Generating the Cmajor output in polyphonic mode

DSP programs following the polyphonic freq/gate/gain convention can be generated using the Platform = cmajor and Architecture = cmajor-poly export options. The resulting foo folder is self-contained, containing the foo.cmajor and foo.cmajorpatch files. The instrument can be executed using cmaj play foo/foo.cmajorpatch command and played with a MIDI device or possibly converted as a JUCE plugin.

Generating the Cmajor output in polyphonic mode with a global effect

DSP programs following the polyphonic freq/gate/gain convention with and an integrated effect can be generated using the Platform = cmajor and Architecture = cmajor-poly-effect export options. The resulting foo folder is self-contained, containing the foo.cmajor and foo.cmajorpatch files. The instrument can be executed using cmaj play foo/foo.cmajorpatch command and played with a MIDI device or possibly converted as a JUCE plugin.

Generating the Cmajor output from a Faust DSP program found in the web

Faust DSP programs found on the Web can also be converted:

Experimental Faust in Cmajor integration

With the release of the Cmajor source code, an experimental Faust in Cmajor integration has been started on a Cmajor project fork.

Known limitations

The Cmajor language generates some usefull API when the patch is exported in C++ or JavaScript. For example the getInputEndpoints/getOutputEndpoints and getEndpointHandle functions allow an external piece of code to enumerate and access all endpoints, to possibly control the program or build a GUI. But unfortunately those functions are not part of the language itself. Thus there is no way we know of:

  • to write a generic Cmajor program that would automatically connect some endpoints (assuming they would use some kind of MIDI describing metadata) to MIDI messages

  • to write a generic Cmajor program that would automatically wraps a voice generated from a Faust DSP program in a polyphonic and MIDI aware program, and expose the voice set of parameters at the main graph level, to be controlled globally for all voices

We think the language would gain expressive power by having some kind of reflexivity.