A project to minimally embed the chuck engine in a Max/MSP external.
It currently has one external (chuck~
) with the following features and limitations:
-
Generate and process audio via an embedded chuck engine by running chuck files with
global
parameters controlled and adjusted in realtime by Max messages. -
Layer sounds from a single instance by running multiple chuck files concurrently.
-
Add, remove, replace audio and audio processes on the fly using Chuck messages via Max messages.
-
Includes most of the base ccrma chugins including
WarpBuf
andFauck
orFaust
except for the following:- Fluidsynth
- Ladspa
-
Note that
chuck-max
has a sibling in the pd-chuck project.
This project is currently built on the chuck 1.5.2.5-dev (chai) engine.
-
The
chuck~
object can take the following arguments:[chuck~]
: single channel in/out, no default chuck file[chuck~ <N>]
: N channel in/out, no default chuck file[chuck~ <filename>]
: single channel in/out with default chuck file[chuck~ <N> <filename>]
: N channels with default chuck file
It is recommended to choose 2 channels for stereo configuration. If a <filename>
argument is given it will be searched for according to the following search rules:
-
Assume it's an absolute path, use it if it exists.
-
Assume that it's a partial path with the package's
examples
folder as a prefix. So ifstk/flute.ck
is given as<filename>
, the absolute path of the packageexamples
folder is prepended to it and if the resulting path exists, it is used. -
Assume
<filename>
exists in the parent patcher's directory. If so, use it. This is useful if you want to package patchers and chuck files together. -
Use Max's
locatefile_extended
search function to search for the<filename>
in the Max search path. The first successul result will be used.
As of the current version, chuck~
implements the core Chuck vm messages as Max messages:
Action | Max msg | Max msg (alias) |
---|---|---|
Add shred | add <file> |
+ <filepath> |
Remove shred | remove <shredID> |
- <shredID> |
Remove last shred | remove last |
-- |
Remove all shreds | remove all |
|
Replace shred | replace <shredID> <file> |
= <shredID> <file> |
VM status | status |
^ |
Clear vm | clear vm |
reset |
Clear globals | clear globals |
|
Reset id | reset id |
|
Time | time |
It's worth reading the ChucK Language Specification's section on Concurrency and Shreds to get a sense of what the above means. The first paragraph will be quoted here since it's quite informative:
ChucK is able to run many processes concurrently (the process behave as if they are running in parallel). A ChucKian process is called a shred. To spork a shred means creating and adding a new process to the virtual machine. Shreds may be sporked from a variety of places, and may themselves spork new shreds.
The core set of chuck vm messesages is also extended in chuck-max
with the following utility messages:
Action | Max msg |
---|---|
Set file attribute (does not run) | file <path> |
Set full path to editor attribute | editor <path> |
Prevent running shreds when dsp is off | run_needs_audio |
Open file in external editor | edit <path> |
Probe chugins | chugins |
List of running shreds | info |
Get/set loglevel (0-10) | loglevel & loglevel <n> |
Get state of chuck vm | vm |
Launch chuck docs in a browser | docs |
Once a shred is running you can change its parameters by sending values from Max to the chuck~
object. To this end, ChucK makes available three mechanisms: global variables, global events, and callbacks which are triggered by events. chuck~
maps these chuck language elements to corresponding Max/MSP constructs as per the following table:
Action | ChucK | Max msg |
---|---|---|
Change param value (untyped) | global variable | <name> <value> |
Dump global variables to console | global variable | globals |
Trigger named event | global event | sig <name> |
Trigger named event all shreds | global event | broadcast <name> |
You change a global variable by sending a <variable-name> <value>
message to a chuck~
instance where the value
can be an int
, float
, string
, array of ints
or floats
, etc. You can also trigger events by sending sig
or signal messages, broadcast
messages as per the above table.
Note: You can't use the ChucK types of dur
or time
in Max. Also, while in the above case, the Max msg seems untyped, it must match the type of the chuck global variable. So if you connect a Max number or flownum object to a message box, it needs to match the type of the global variable (int/float).
See help/chuck~.maxhelp
and patchers in the patchers/tests
directory for a demonstration of current features.
In addition to the typical way of changing parameters there is also an extensive callback system which includes listening / stop-listening for events associated with callbacks, triggering them via sig
and broadcast
messages and also setting typed global variables via messages and symmetrically getting their values via typed callbacks:
Action | ChucK | Max msg |
---|---|---|
Listen to event (one shot) | global event | listen <name> or listen <name> 0 |
Listen to event (forever) | global event | listen <name> 1 |
Stop listening to event | global event | unlisten <name> |
Trigger named callback | global event | sig <name> |
Trigger named callback all shreds | global event | broadcast <name> |
Get int variable | global variable | get int <name> |
Get float variable | global variable | get float <name> |
Get string variable | global variable | get string <name> |
Get int array | global variable | get int[] <name> |
Get float array | global variable | get float[] <name> |
Get int array indexed value | global variable | get int[i] <name> <index> |
Get float array indexed value | global variable | get float[i] <name> <index> |
Get int associative array value | global variable | get int[k] <name> <key> |
Get float associative array value | global variable | get float[k] <name> <key> |
Set int variable | global variable | set int <name> <value> |
Set float variable | global variable | set float <name> <value> |
Set string variable | global variable | set string <name> <value> |
Set int array | global variable | set int[] <name> v1, v2, .. |
Set float array | global variable | set float[] <name> v1, v2, .. |
Set int array indexed value | global variable | set int[i] <name> <index> <value> |
Set float array indexed value | global variable | set float[i] <name> <index> <value> |
Set int associative array value | global variable | set int[k] <name> <key> <value> |
Set float associative array value | global variable | set float[k] <name> <key> <value> |
Developer Note
In order to customize the current set of callbacks (which currently just post the value of the parameters to the Max console), an advanced user will want to modify them to do something other than the default and then re-compile the external.
In practice, callbacks in chuck-max
are constrained by what their function signatures allow. To do something useful one will typically want to access the pointer to an instance of the chuck~
object which is not directly available as an argument to any the callbacks. For example, in the case of the cb_get_int
callback, one only has the parameter name and value:
void cb_get_int(const char* name, long val)
{
post("cb_get_int: name: %s value: %d", name, val);
}
To get around this limitation, one can use the knowledge that chuck~
instances are given the scripting name chuck-<x>
where x
is the order of instanciation and that one can get the retrieve the relevant object pointer by using void *object_findregistered(t_symbol *name_space, t_symbol *s)
as in:
void cb_get_int(const char* name, t_CKINT val)
{
t_object* x;
for (auto name : CK_INSTANCE_NAMES) {
x = (t_object*)object_findregistered(CLASS_BOX, gensym(name.c_str()));
object_post(x, (char*)"name: %s value: %d", name.c_str(), val);
}
}
It's not elegant, but it works until something better comes along.
Note that this external is currently only developed and tested on macOS, although a Windows version is on the TODO list.
It requires the following to be available on your system:
-
Xcode from the App store or Xcode Command Line Tools via
xcode-select –install
-
cmake
-
bison
-
flex
The last three can be installed using Homebrew as follows:
brew install cmake bison flex
Currently chuck~
can only be built on macOS. Of course, any help to progrss a windows version variant would be welcome!
The buildsystem consists of a minimal Makefile frontend with CMake driving the build on the backend.
To get up and running:
git clone https://github.com/shakfu/chuck-max.git
cd chuck-max
make setup
Note: make setup
does two things: (1) retrieve max-sdk-base
via a git submodule and makes the package and its contents available to be used by Max by creating a symlink of the chuck-max
folder in $HOME/Documents/Max 8/Packages
.
From this point you have three options:
-
make
: Base system: (external + base ccrma chugins excludingFauck
orFaust
andWarpBuf
)* -
make full
: Base system +Faust
andWarpbuf
chugins with fulllibsndfile
format support -
make light
: Base system +Faust
andWarpbuf
chugins withlibsndfile
support only.wav
files.
[*
] The Fauck or Faust
chugin will be referred to by either of these names.
Also note that by default make
builds the external according to the native architecture of the mac it is compiled on. You can build the base system with universal support by typing make universal
instead.
If there's a need to update and re-build the external just type the following in the root of the project.
git pull
make
With WarpBuf
you can time-stretch and independently transpose the pitch of an audio file.
This chugin can be built by make full
instead of make
in the build process above or if you are just using cmake then set option -DENABLE_WARPBUF=ON
The fauck chugin contains the full llvm-based faust engine and dsp platform which makes it very powerful and also quite large compared to other chugins (at around 45 MB stripped down). It requires at least 2 output channels to work properly.
This chugin can be built by make full
and make light
options instead of make
in the build process above or if you are just using cmake then set option -DENABLE_FAUCK=ON
A future aim is to include a stripped down version of this fauck chugin which only supports .wav files and and only contains the faust standard library in an official chuck-max
package.
CAVEAT: the Faust chugin has unresolved cleanup bug which may cause Max to crash after all patch windows are closed and Max is exited.
Open the help file help/chuck~.maxhelp
for a demo. Check out the patchers
folders for further examples of use.
-
If a chuck file contains a custom event and the Max patch sends a
clear vm
orreset
before running the chuck file while Max audio is turned off, it may cause Max to crash. See github issue #11 for updates on this. The interim solution is to only run chuck files with Max audio on, and there's an attributerun_needs_audio
to force this and prevent the crash. Thanks to @HighHarmonics2 for discovering this one. -
Use of the
Fauck
orFaust
chugin will cause Max to crash when the user quits Max after all patch windows are closed.
This project thanks the following:
-
Professors GE Wang and Perry Cook and all chuck and chuggin contributors for creating the amazing ChucK language and chuggin ecosystem!
-
Professor Perry Cook for co-authoring Chuck and creating the Synthesis Toolkit which is integrated with chuck.
-
Professor Brad Garton for creating the original chuck~ external for Max 5. My failure to build it and run it on Max 8 motivated me to start this project.
-
David Braun, the author of the very cool and excellent DawDreamer project, for creating the excellent ChucKDesigner project which embeds chuck in a Touch Designer plugin. His project provided a super-clear blueprint on how to embed
libchuck
in another host or plugin system and was essential to this project.