Setting up Cmajor
To begin, you may want to follow this guide to setup Cmajor with command line tools. This will give you a whole guide to running your first Cmajor patch.
Download the latest release of Cmajor Binaries
Click here to download the latest binaries
On the Github releases page, you’ll find downloadable binaries for Mac, Windows and Linux. These provide:
- The command-line tool (
cmaj
orcmaj.exe
) which provides a compiler, utilities and can load and run Cmajor patches. - The redistributable libraries (
CmajPerformer.dll
orlibCmajPerformer.so
) which are needed if you build your own native app which incorporates the Cmajor JIT engine.
Using the cmaj
command-Line tool
The command line tool can create, build, test and run patches, as well as code-generation of C++ and plugin projects, and various other related utilities.
Installing the command-line app is simply a case of copying the cmaj
executable to a location of your choice, and running it from a terminal.
For help, run:
$ cmaj help
And it’ll show you the latest set of available arguments, e.g.
cmaj <command> [options] Runs the given command. Options can include the following:
-O0|1|2|3 Set the optimisation level to the given value
--debug Turn on debug output from the performer
--sessionID=n Set the session id to the given value
--engine=<type> Use the specified engine - e.g. llvm, wasm, cpp
Supported commands:
cmaj help Displays this help output
cmaj version Displays the current Cmajor version
cmaj licenses Print out legally required licensing details for 3rd-party
libraries that are used by this application.
cmaj play file [opts] Plays a .cmajorpatch, or executes a .js javascript file.
--no-gui Disable automatic launching of the UI in a web browser
--stop-on-error Exits the app if there's a compile error (default is to keep
running and retry when files are modified)
cmaj test [opts] <files> Runs one or more .cmajtest scripts, and print the aggregate results
for the tests. See the documentation for writing tests for more info.
--singleThread Use a single thread to run the tests
--threads=n Run with the given number of threads, defaults to the available cores
--runDisabled Run all tests including any marked disabled
--testToRun=n Only run the specified test number in the test files
--xmlOutput=file Generate a JUNIT compatible xml file containing the test results
--iterations=n How many times to repeat the tests
cmaj render [opts] <file> Renders the given file or patch
--length=<frames> The number of frames to render (optional if an input audio file is provided)
--rate=<rate> Use the specified sample rate (optional if an input audio file is provided)
--channels=<num> Number of output audio channels to render (default is 2 if omitted)
--blockSize=<size> Render in the given block size
--output=<file> Write the output to the given file
--input=<file> Use input from the given file
--midi=<file> Use input MIDI data from the given file
cmaj generate [opts] <file> Generates some code from the given file or patch
--target=<type> The type of code to generate - can be cpp|wasm|wast|plugin|module|syntaxtree
--output=<file> Write the generated runtime to the given file (or folder for a plugin)
--jucePath=<folder> If generating a JUCE plugin, this is the path to your JUCE folder
--cmajorIncludePath=<folder> If generating a plugin, this is the path to your cmajor/include folder
cmaj create [opts] <folder> Creates a folder containing files for a new empty patch
--name="name" Provides a name for the patch
cmaj unit-test Runs internal unit tests.
--iterations=n How many times to repeat the tests
Playing a patch with the console app
To get started, you might want to try some of the example patches using cmaj play
:
$ cmaj play /path-to-your-repo/examples/HelloWorld/HelloWorld.cmajorpatch
This will open a window to display any controls that the patch may provide, and you should hear a nice tune playing. If you’ve got a MIDI keyboard then it should be opened and sent to the running patch (if it needs MIDI input).
There are command-line options to give you a bit more control over which audio and MIDI devices are used, but the command-line app is only attempting to be a quick-and-dirty patch player. For more serious control, you probably want to load your patch into a proper audio host via our Cmajor JIT plugin, which will let you route it however you like, and provide any kind of i/o to it.
While running a patch, the console app will detect any file changes and recompile/reload, so you can quickly experiment with changes to your code.
Creating a new empty patch
To get started with a new patch, the cmaj
app can generate the basic boilerplate files needed. Just run:
$ cmaj create --name="Hello" MyNewPatchFolderName
This creates a new folder containing a patch called “Hello” that can be explored, renamed and built upon. For in-depth details on how these files work, see the Patch Format Guide
Coding in Cmajor: High-Level Overview
This is a very quick summary of the main concepts in Cmajor. For a deep dive on the language syntax, see the language guide
Programs vs Patches
A Cmajor program refers to any collection of processors, graphs and namespaces. A host app can load a Cmaj program, instantiate a processor and use it to render audio (or any other type of data).
A Cmajor patch is a bundle which has a form that makes it suitable for use as an audio plugin. A patch is folder containing metadata files, program files, and other resources (such as GUI scripts and audio files) which a DAW-like host can load and use like, in the same way they might load a traditional plugin such as a VST or AU.
The Cmajor tools include utilities to load patches via a VST/AU plugin, and can also code-generate a native VST/AU plugin from a patch.
Processors and Graphs
The main high-level structures in Cmajor that differ from other languages are the processor
and graph
objects.
graph
declarations
A graph
declares a set of nodes (which are either low-level processor
objects or other graphs), and a list of connections between them.
// Example of a graph declaration:
graph TwoGainsInSeries
{
// This section declares the inputs and outputs of the graph:
input stream float in;
output stream float out;
// This section declares the nodes:
node gain1 = GainProcessor; // declare two nodes, each one a GainProcessor
node gain2 = GainProcessor;
// And here we list the connections between the nodes:
connection in -> gain1 -> gain2 -> out; // send our input through both gains, and the result to our output
}
graph TD;
A(["in"])-->B(["gain1"]);
A-->C(["gain2"]);
B-->D(["out"]);
C-->D;
processor
declarations
Unlike a graph, a processor
contains functions to perform its number crunching, rather than nodes.
Every processor
must provide a main()
function. This usually contains an infinite loop which reads from its inputs, performs some kind of processing, writes to its outputs, and repeats this for the lifetime of the object.
// Example of a processor declaration:
processor GainProcessor
{
input stream float in; // declare an input and output stream of floats
output stream float out;
void main()
{
loop // infinite loop
{
out << in * 0.5f; // read our next input value, multiply by 0.5, and send it to our output
advance(); // advance to the next frame
}
}
}
A program can also contain namespace
declarations containing helper functions, data types, and constants that the processors can use.
Creating your first patch - Hello, World!
Let’s start with a simple sine-wave patch with a gain control, to demonstrate how to create a custom processor, connect it up inside a graph, and provide a parameter to control something from the GUI.
Step 1 - Create a New Patch
Start by using the command-line app to create a new, empty patch:
$ cmaj create AnnoyingBeep
This creates a basic patch in a folder called “AnnoyingBeep”. If you have a look inside at the file called AnnoyingBeep.cmajor
, it’ll look something like this:
graph AnnoyingBeep [[main]]
{
output stream float out;
node sine = std::oscillators::Sine (float, 440);
connection sine -> std::levels::ConstantGain (float, 0.15f) -> out;
}
If you start the patch running with:
$ cmaj AnnoyingBeep/AnnoyingBeep.cmajorpatch
Then you should hear a sine wave playing. While it’s running, try opening an editor on the AnnoyingBeep.cmajor
file, and modify the “440” to change its pitch. When you re-save the file, the player should pick this up and you’ll hear the sound changing.
As an example, let’s update the code to add a parameter for the volume. Try changing the code to:
graph AnnoyingBeep [[main]]
{
output stream float out;
input gain.volume;
node sine = std::oscillators::Sine (float, 440);
node gain = std::levels::SmoothedGain (float);
connection sine -> gain.in;
connection gain.out -> out;
}
..and you should see a “Volume” parameter appear on the GUI which controls the level, and you’ll be relieved to be able to turn it down.
If you type anything wrong, you’ll see the GUI go blank, and the command-line app will show you the compile errors.
Now let’s add a parameter to control the pitch:
graph AnnoyingBeep [[main]]
{
output stream float out;
input gain.volume;
input sine.frequencyIn;
node sine = std::oscillators::Sine (float, 440);
node gain = std::levels::SmoothedGain (float);
connection sine -> gain.in;
connection gain.out -> out;
}
..and when you save that, the GUI should update to now have two parameter controls for volume and pitch.
Have fun exploring - there are lots more examples to look at in the ‘examples’ folder!