RTcmix for Linux: Part 1

In the first part of this three-part series on real-time audio synthesis, we take you through the history and basis of RTcmix.
Making “Music”

The MINC scripting language provides both a powerful and easy-to-learn interface to RTcmix. More terminology: a MINC script is referred to as a scorefile. If you understand C, using MINC will be trivial. Some major differences are no semicolons at the end of each line and no functions (sorry). The art of using MINC (RTcmix) to make music is the primary focus of several semester-long college and graduate courses offered at several higher education institutions around the world. There's no way I can do it complete justice here. Instead, we'll take a look at some scorefiles which illustrate various features.

To execute a scorefile, simply pipe it into CMIX with stdin.

CMIX < scorefile

from the command line will fire it off. Internally, this syntax passes the scorefile to a parser (compiled by yacc/bison) which in turn passes events to a scheduler. When used in real-time performance mode (i.e., with external control sources), the RTcmix parser listens to a TCP socket, parsing data on the fly. In this instance, parser and scheduler are implemented using the linuxthreads library. The following scorefiles are included with version 3.0 of RTcmix and were designed by John Gibson.

Listing 1.

Listing 1 illustrates a reasonably simple scorefile which uses two RTcmix instruments. There are dozens more. WAVETABLE, as the name implies, is a wavetable lookup instrument. REVERBIT, also descriptive, runs a reverberation effect on an audio stream. In this example, the output of WAVETABLE is piped into REVERBIT, then out to your audio device and/or an audio file.

Let's step through the scorefile a bit:

rtsetparams(44100, 2)

This command is necessary for all RTcmix scorefiles. It sets up the audio device to run in stereo at 44100hz. An optional third parameter allows you to configure the audio buffer size to reduce latency in performance (e.g., when controlling RTcmix from another application). Default is 8,192 frames.

Next we declare which “instruments” we're going to use:


As mentioned above, RTcmix supports the dynamic loading of libraries. These commands load the instruments at run-time. This allows for a smaller memory footprint than loading all the instruments simultaneously. It also allows you to write a score that uses multiple instruments.

The next few lines are commented out. Uncommenting them will allow audio to be written to a soundfile as well as the audio device. The set_option command (de)activates various RTcmix features. For example: set_option("AUDIO_OFF") will turn off writing to the audio device—useful when your scorefile forces your computer to stretch beyond its means.

In order to map the audio from one source to another, eventually to your speakers or a soundfile, we call:

bus_config("WAVETABLE", "aux 0-1 out")
bus_config("REVERBIT", "aux 0-1 in", "out 0-1")

As mentioned earlier, one of RTcmix's newest features is the ability to route audio streams. In an attempt to keep tradition with established mixing convention, we've adopted a “bus” paradigm. Internal buses are labeled as “aux”, which can be read from (as “in”) or written to (as “out”). Input devices are labeled “in”, and output buses (audio devices) “out.” The convention for bus designation is: bus_type “number” [in or out for aux]. So “out 0” would be an output channel (e.g., a speaker) and “in 0” a line in or mic. This is not to be confused with “aux 0 in” which is the specification of an internal bus that is being read from by a particular instrument.

So, the statements above first route WAVETABLE's output to aux (internal bus) 0 and 1. Then REVERBIT's bus_config is set up to read from aux 0 and 1 (WAVETABLE's output) and write its output to a stereo audio device (out 0-1). It is possible to have multiple bus configurations for the same instrument. Each inherits the most recent call.

The next line in the score sets a global variable totdur. Variables are auto declared at init time (i.e., when the RTcmix parser reads your file). There are no reserved global variable names.

In order to set “control rate” update of internal synthesis parameters (e.g., amplitude envelope updates) we use the reset command. So reset(2000) updates 2,000 times every 44,100 samples—every 20.5 samples.

The following two lines are a bit less self-descriptive:

setline(0,0, 1,1, 5,0)
makegen(2, 10, 10000, 1,.5,.3,.1)

As with many packages that have developed over a number of years, RTcmix has its share of legacy issues. Unit generators in Cmix are defined by the makegen statement. Makegens are used for various internal aspects of a particular instrument: an amplitude envelope, waveform for a lookup, pitch vibrato, and so forth. The setline command is a related statement, used specifically to set an amplitude envelope. The time-value pairs set an amplitude of 0 at time 0, 1 at time 1 and zero at time 5. Time in this sense is relative and will be stretched accordingly to the duration of your “note” (see below). The WAVETABLE instrument needs some kind of waveform to read in order to make sound. It stores this information in “slot 2”. Slot 1 is used for the amplitude envelope defined by the setline. Yes, you could substitute a makegen command for that setline (gen24). Gen10 specifies loading a sinusoid into the respective slot (in this case 2). The size (in samples) of the wavetable in this case is 10,000. Remaining arguments are amplitudes for successive harmonics. That's about as complicated as it gets, at least in this article.

After assigning some more global variables, we seed the random number generator with:


The actual music making is done by the following lines:

for (st = 0; st < totdur; st = st + .45)
    WAVETABLE(st, dur, amp, freq, random())
This is a simple for loop which executes until st is greater than totdur (defined earlier). The parameters to WAVETABLE (as well as all other RTcmix functions and instruments) are referred to as p-fields. In the case of WAVETABLE:
  • p0 = start time

  • p1 = duration

  • p2 = digital amplitude (in this case between 0 and 32767)

  • p3 = frequency (in hz)

  • p4 = stereo spread

For p4 = 0 100% of the output will go to channel 0 (defined by bus_config above) and pf4 = 1 sends it all to channel 1. A value of 0.5 splits between both channels evenly. Note that the random() function call returns a floating point value between 0 and 1u.

The last few lines of the score reset the amplitude envelope with setline(0,1, 1,1), because we want a slightly different one for our REVERBIT function, which we call with: REVERBIT(st=0, insk=0, totdur, amp, revtime, revpct, rtchandel, cf).

The p-fields for REVERBIT are:

  • p0 = start time

  • p1 = inskip, which is the time to start reading input. (Use 0 for bus_configs; some other value when reading in from a file.)

  • p2 = duration

  • p3 = amplitude multiplier (generally between 0 and 1)

  • p4 = reverb time

  • p5 = reverb percentage

  • p6 = right channel delay

  • p7 = cutoff frequency for low-pass filter (in hz)

  • p8 (optional) = apply DC blocking filter (boolean)

That's it. Simply type the following to execute the score:

Listing 2 is a musically simpler example, but illustrates the powerful ability of RTcmix to process an incoming audio stream in real time.

Listing 2.

To set up our audio device for simultaneous reading and writing, we use the set_option command again:


It's necessary to have this command executed before the rtsetparams call, as it sets an internal flag that rtsetparams uses when performing actual setup of the audio device. As before, we set up the device for 44K stereo, but this time decreasing the internal buffer size for real-time performance to 512 frames.

To tell RTcmix to read from the audio device, we use:

rtinput("AUDIO", "MIC")

The MIC argument is necessary only in IRIX versions of RTcmix. We could just as well say:

to read an audio file instead.