uRTcmix Tutorial
installing the uRTcmix package
click
uRTcmix.unitypackage
to download the uRTcmix package with supporting files
Note: If you are installing [rtcmix~] on macOS machines running Catalina
(10.15.x) or later, you will probably get the Apple 'gatekeeper' message about
the library/application not able to run because it isn't from a
'signed developer'. One of these days We'll wade through the impenetrable
documentation about how to do this, but for now you can
follow the instructions here
to get it going.
To import uRTcmix for use, start a new Unity project. Drag the
uRTcmix.unitypackage into the Assets of your project.
The resulting "uRTcmix/" folder will have three subfolders:
libs/ -- this contains the loadable lib files for RTcmix to run
prefabs/ -- this contains two 'prefabs' beep.prefab
and RTcmixmain.prefab. Their use is explained below.
scripts/ -- this contains the C# source code for the beep
prefab and the RTcmixmain prefab. The rtcmixmain
script contains the C# definitions of all the usaable uRTcmix
functions. The uRTcmixScriptTemplate C# script shows
how the uRTcmix functions can be used. Most of them are commented-out
because you will use them only for specific purposes.
Drag the RTcmixmain.prefab object from your Assets up to the
GameObject-instantiation hierarchy tab (where the "Directional Light" and
"Main Camera" objects are located).
At this point, you will probably get an error when you add the
RTcmixmain.prefab
object to your object list/hierarchy:
"Unsafe code requires the `unsafe' command line option to be specified.
Enable "Allow 'unsafe' code" in Player Settings to fix this error."
To correct this, from your top menus
go to Edit->Project Settings->Player and scroll down a bit
in your Inspector window. Open the "Other Settings"
tab (if it isn't already). Scroll down and check the box next to
the "Allow 'unsafe' Code" setting.
You're now set to use uRTcmix!
make a beep
Drag the beep.prefab object from your Assets up to the
GameObject-instantiation hierarchy tab. Hit the 'play' button
and you should hear an 8.7-second sine wave sound with a pitch of
G-above-middle-C.
inside the "beep" object
Open up the "beep.cs" script to see how the "beep" object works.
At the top of the file, you should see this
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
Those lines of code allow the C# compiler to access the methods it
needs to run the script in Unity properly. NOTE: If you use any
"String" operations with Unity, you will need to add:
using System;
to the above set of "using ..." lines.
Next you will see these lines:
public class beep : MonoBehaviour {
int objno = 0;
rtcmixmain RTcmix;
private bool did_start = false;
The int objno = 0; declares
the objno variable and sets it to
0. This is how we keep track of different instantiations of RTcmix
within Unity. Each RTcmix process we attach to a game object can
have a different objno, keeping
it separate from other executing RTcmixes.
The rtcmixmain RTcmix; statement declares
a variable that we will
use to access the RTcmix functions from the RTcmixmain object.
The private bool did_start = false; statement
declares a boolean (true/false) variable,
did_start;
and sets it initially to "false". This is so that the "beep" script
won't try to run audio until RTcmix is initialized.
The objno,
RTcmix and
did_start
variables are declared here at the top of the class definition, within
the "beep" class but outside any of the "beep" methods
because they will be used throught the entire "beep" class.
Next we add a new method,
Awake()
and a new line of code to the Start() method:
private void Awake()
{
// find the RTcmixmain object with the RTcmix function definitions
RTcmix = GameObject.Find("RTcmixmain").GetComponent<rtcmixmain>();
}
// Use this for initialization
void Start () {
RTcmix.initRTcmix (objno);
The first assignment of the RTcmix
variable finds the RTcmixmain game object
and then accesses
the "rtcmixmain" script on that object.
Once we have made that connection, we can use
the RTcmix variable to
invoke the functions defined in the "rtcmixmain.cs" script. We do
this in the very first line of the
Start() method:
RTcmix.initRTcmix (objno); to initialize
the RTcmix process for the objno we want
to use (in this case, "0").
We then add a single line to our
Start()
ethod that will send a very simple (one line!) script to RTcmix:
RTcmix.SendScore("WAVETABLE(0, 8.7, 20000, 8.07, 0.5)", objno);
[note: we are assuming at least a basic grasp of the RTcmix language.
See the
rtcmix.org
web site for tutorials and information about RTcmix.]
This sends the String (the score) in the double-quotes to RTcmix
for parsing and execution. The final parameter, objno, does what
it did in the RTcmix.initRTcmix (objno) case;
it designates which RTcmix process to use.
Finally, because we have now fully initialized RTcmix, we set
did_start = true;
to allow audio processing in this script to take place.
Our finished Start() method then looks like this:
// Use this for initialization
void Start () {
// initialize RTcmix
RTcmix.initRTcmix (objno);
// send a note using the WAVETABLE() instrument
RTcmix.SendScore("WAVETABLE(0, 8.7, 20000, 8.07, 0.5)", objno);
did_start = true;
}
The
RTcmix.SendScore(...)
will trigger the 'beep'.
But you will need to add two additional methods to the "beep"
class for this to work:
void OnAudioFilterRead(float[] data, int channels)
{
if (!did_start) return;
// compute sound samples
RTcmix.runRTcmix(data, objno, 0);
}
void OnApplicationQuit()
{
did_start = false;
RTcmix = null;
}
The initial
if (!did_start) return;
line will prevent audio processing from occuring if RTcmix has
not been initialized yet.
The OnAudioFilterRead() method passes audio
data to and from the
"beep" class through the data
array parameter. We hand that off
to RTcmix with the
RTcmix.runRTcmix (data, objno, 0); line,
invoking the runRTcmix() method
from the "rtcmixmain.cs" file.
runRTcmix() will fill the
data array with one buffers-worth
of sound samples from the RTcmix process (usually 512 samples in Unity).
Note the use of objno again.
The final "0" after the objno
variable signals that we are
only synthesizing sound, not processing any sound that comes
into the 'beep' class from a previous sound-generating
object in Unity. A final "1" in the
RTcmix.runRTcmix() would
allow us to do input signal-processing. We will discuss this later.
The OnApplicationQuit() method resets
RTcmix by destroying the
particular instantiation
and setting did_start to "false"
so that the next time we run the scene
(and the Start() method is
called), everything will be set up properly.
Our final "beep.cs" file looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// a very simple script to make a G-above-middle-C 'beep' on startup
public class beep : MonoBehaviour {
int objno = 0;
rtcmixmain RTcmix;
private bool did_start = false;
private void Awake()
{
// find the RTcmixmain object with the RTcmix function definitions
RTcmix = GameObject.Find("RTcmixmain").GetComponent<rtcmixmain>();
}
// Use this for initialization
void Start ()
{
// initialize RTcmix
RTcmix.initRTcmix(objno);
// send a note using the WAVETABLE() instrument
RTcmix.SendScore("WAVETABLE(0, 8.7, 20000, 8.07, 0.5)", objno);
did_start = true;
}
// Update is called once per frame
void Update ()
{
}
void OnAudioFilterRead(float[] data, int channels)
{
if (!did_start) return;
// compute sound samples
RTcmix.runRTcmix(data, objno, 0);
}
void OnApplicationQuit()
{
did_start = false;
RTcmix = null;
}
}
beeping...
To make a repeating beep pattern is very simple in RTcmix.
We can set RTcmix to repeat the score indefinitely using the
ability of RTcmix to generate a 'bang' to re-trigger the score.
(A 'bang' is a notion that comes from Max/MSP and the [rtcmix~]
object; it is used to trigger various operations in Max.)
For this we will use the MAXBANG() RTcmix instrument. MAXBANG()
takes a single parameter -- how many seconds from "now" do we generate
a 'bang'.
This requires only two alterations in our original "beep" script. One
is to the initial
RTcmix.SendScore()
score that we send to include a MAXBANG() scheduled 'bang' in the future, and
one to detect when that 'bang' occurs and respond appropriately.
Our revised
Start()
method looks like this:
// Use this for initialization
void Start ()
{
// initialize RTcmix
RTcmix.initRTcmix(objno);
// send a note using the WAVETABLE() instrument
RTcmix.SendScore("WAVETABLE(0, 0.2, 20000, 8.07, 0.5) MAXBANG(0.3)", objno);
did_start = true;
}
Note that we have reduced the duration of the WAVETABLE() to 0.2 seconds.
This is because we are scheduling a 'bang' at 0.3 seconds after the
score is sent: MAXBANG(0.3). We will have 0.1 seconds of silence
between the stop of that
first WAVETABLE() note and the interception of the 'bang'.
We will detect that MAXBANG()-scheduled 'bang' within the
OnAudioFilterRead()
method. We accomplish this by using the uRTcmix function
RTcmix.checkbangRTcmix().
This function will return "1" when a 'bang' is detected
at a scheduled time. The
OnAudioFilterRead()
method now looks like this:
void OnAudioFilterRead(float[] data, int channels)
{
RTcmix.runRTcmix (data, objno, 0);
if (RTcmix.checkbangRTcmix (objno) == 1) {
RTcmix.SendScore("WAVETABLE(0, 0.2, 20000, 8.07, 0.5) MAXBANG(0.3)", objno);
}
}
By sending a MAXBANG() again when we generate the next WAVETABLE() beep,
we will then intercept another 'bang' in the future, and the retriggering
will continue indefinitely.
reading RTcmix scorefiles
Let's suppose we've developed a more complex RTcmix score that instantiates
a simple granular synthesis process:
reset(44100)
hifreq = 900
lowfreq = 500
amp = 15000
ampenv = maketable("window", 1000, "hanning")
start = 0
dur = 0.05
for (i = 0; i < 20; i = i+1) {
freq = irand(hifreq, lowfreq)
WAVETABLE(start, dur, amp*ampenv, freq)
start = start + (dur/2)
}
If we wanted to send this using our 'imbedded string' approach we've been
employing with
RTcmix.SendScore(),
the code would look something like this:
RTcmix.SendScore("reset(44100) " +
"hifreq = 900 " +
"lowfreq = 500 " +
"amp = 15000 " +
"ampenv = maketable(\"window\", 1000, \"hanning\") " +
"start = 0 " +
"dur = 0.05 " +
"for (i = 0; i < 20; i = i+1) { " +
" freq = irand(hifreq, lowfreq) " +
" WAVETABLE(start, dur, amp*ampenv, freq) " +
" start = start + (dur/2) " +
"} ";
which is pretty ugly and fairly difficult to modify/debug. We
can make things a little easier by using the
scoralyzer
command to pre-format our RTcmix script into a valid C# string
variable, but it would still be annoying to edit. There is
a better way:
if we save the original RTcmix code for the granular process into
a text file, perhaps "granular.txt", we can add it as an asset
to our Unity project and load, read, use, and edit the file from
within our Unity development environment. NOTE: These files
have to have the ".txt" extension for Unity to recognize them
as valid text files.
If we add the file "granular.txt" containing the RTcmix code above
to our Unity assets, we can edit
it the same way we edit C# scripts that work as assets in Unity.
To access the RTcmix code in the file in our game object script,
you can take advantage of the Unity/C# "TextAsset" class.
Essentially, you declare a public variable that will 'hold'
your TextAsset scorefile, and you can then extract a string
from that TextAsset for use by the
RTcmix.SendScore()
method. Here is how to set it up:
using UnityEngine;
using System.Collections;
using System; // this is necessary for String operations
public class granularize : MonoBehaviour {
int objno = 0;
rtcmixmain RTcmix;
private bool did_start = false;
public TextAsset grantextfile;
String granscore;
private void Awake()
{
// find the RTcmixmain object with the RTcmix function definitions
RTcmix = GameObject.Find("RTcmixmain").GetComponent<rtcmixmain>();
}
// Use this for initialization
void Start () {
RTcmix.initRTcmix (objno);
granscore = grantextfile.text;
...
After this setup, the
granscore
variable (which is a 'global' variable in the class because -- like with
the int objno = 0; etc. global variables -- we
declared it at the top of the class definition, outside any class
methods) is the RTcmix scorefile string that you can send for
parsing/execution in the appropriate place in your code:
RTcmix.SendScore (granscore, objno);
The only trickiness is to recognize that by declaring
public TextAsset grantextfile;
at the top of the class definition, it exposes
a slot in the Unity inspector of the associated game object for
that
grantextfile
variable.
You need to drag the "granular.txt" file from your Assets
into the slot so that the public
grantextfile
variable will reference the "granular.txt" file with the
RTcmix code in it.
alter a beep
RTcmix has two different ways for allowing data from Unity to change
the parameters of a sound being synthesized or processed.
One way is to change parameters
of an executing RTcmix instrument dynamically
while it is making sound. RTcmix has 'special' parameters
for all of it's synthesis/signal-processing instruments called
"PFields" that allow you to do this. These can be addressed from
within Unity. We will alter the non-repeatig 'beep' script to show this.
In the 'beep' class, we will use another variable,
frequency,
declared globally (at the top of the class definition)
to set the frequency of the repeating
WAVETABLE()
RTcmix instrument. Our setup now looks like this:
using UnityEngine;
using System.Collections;
using System;
public class beep : MonoBehaviour {
int objno = 0;
rtcmixmain RTcmix;
private bool did_start = false;
float frequency;
private void Awake()
{
// find the RTcmixmain object with the RTcmix function definitions
RTcmix = GameObject.Find("RTcmixmain").GetComponent<rtcmixmain>();
}
// Use this for initialization
void Start () {
// initialize RTcmix
RTcmix.initRTcmix (objno);
RTcmix.SendScore("freqval = makeconnection(\"inlet\", 1, 700.0) " +
"WAVETABLE(0, 99999, 20000, freqval, 0.5)", objno);
frequency = 700.0f;
did_start = true;
}
We set an initial value (700.0f) for the frequency
variable in our
Start()
method. It corresponds to the "700.0"
that was set as the default value in the RTcmix
makeconnection()
scorefile command. We're setting
frequency
as a 'float' variable, but it could be cast as a 'double' or an 'int'.
RTcmix will accept them all.
The
WAVETABLE()
is started with a duration of "99999" -- this is just to insure that the
note continues to play so that we may alter the frequency when we
want. That value is arbitrary, and if 99999 seconds is too short
it can be extended.
Next we add script code to the "beep" class definition to
alter the value of the
frequency
variable. We do this in our
Update()
method by seeing if the user has typed an Up or Down arrow:
// Update is called once per frame
void Update () {
if (Input.GetKey (KeyCode.UpArrow)) {
frequency = frequency + 10.0f;
}
if (Input.GetKey (KeyCode.DownArrow)) {
frequency = frequency - 10.0f;
}
RTcmix.setpfieldRTcmix (1, frequency, objno);
}
If the Up arrow is pushed, 10.0 will be added to the value of
frequency.
The Down arrow will subtract 10.0 Hz. The modified value is
then sent to the executing
WAVETABLE()
instrument via the
RTcmix.setpfieldRTcmix (1, frequency, objno);
method. The "1" is the inlet number, set by the
makeconnection()
command in the RTcmix score.
This will work, but it's not the most efficient way of doing the
task. The
Update()
method will be called at the frame rate of Unity, usually 60 times/second.
We only need to update our
WAVETABLE()
frequency value when the user presses one of the arrow keys -- probably
not 60 times/second. A better approach would be this:
// Update is called once per frame
void Update () {
if (Input.anyKey) {
if (Input.GetKey (KeyCode.UpArrow)) {
frequency = frequency + 10.0f;
}
if (Input.GetKey (KeyCode.DownArrow)) {
frequency = frequency - 10.0f;
}
RTcmix.setpfieldRTcmix (1, frequency, objno);
}
}
which would only call the
RTcmix.setpfieldRTcmix()
method when a key was pressed.
modifying RTcmix scorefiles
RTcmix scores themselves can also be altered dynamically to
open up another possibility for interactively
sending data from Unity into RTcmix. Sscorefile variable
values can be reassigned before
the score is sent to RTcmix. The "rtcmixmain.cs" file
contains a utility function to accomplish this.
To take advantage of this capability, the RTcmix score needs to include
special variables with a"$" as the first character and a number
as the second (this is also from the [rtcmix~] object in
Max/MSP). The "$" signals that this variable needs to have a
value assigned from "outside" RTcmix, and the number indicates
the ordering of the assignments.
For example, the small RTcmix score:
WAVETABLE(0, 7, $1, $2)
will expect that the value for the amplitude parameter
($1)
and the frequency parameter
($2)
will be set from Unity values "outside" RTcmix. The utility function
setscorevalsRTcmix()
included in the "rtcmixmain.cs" file does this by
taking a score string with the $variables embedded
within and returning a score string with substitutions made.
Let's look at a modified version of the RTcmix code in the
"granular.txt" example from above
to show how these $variables work:
reset(44100)
hifreq = $1
lowfreq = $2
amp = 15000
ampenv = maketable("window", 1000, "hanning")
start = 0
dur = 0.05
for (i = 0; i < 20; i = i+1) {
freq = irand(hifreq, lowfreq)
WAVETABLE(start, dur, amp*ampenv, freq)
start = start + (dur/2)
}
MAXBANG(start)
The values for the
hifreq
and
lowfreq
variables can be set using the
setscorevalsRTcmix()
utility function. This function takes a score string with
$variables and substitutes them with values from a list of parameters
passed with the function. It then returns a string with the values
set for RTcmix to parse and execute.
If we took the granular synthesis score above with the $variables set
for
hifreq
and
lowfreq
and put it in our "granular.txt" file, we can do this substitution with
the following code:
(...)
public TextAsset grantextfile;
String granscore;
// Use this for initialization
void Start () {
(...)
granscore = grantextfile.text; // assumes that "grantextfile" has been set to the "granular.txt" scorefile
String granscore2 = RTcmix.setscorevalsRTcmix (granscore, 900.0f, 500.0f);
// "granscore2" now has the values 900.0 and 500.0 for the "hifreq" and "lowfreq" variables
RTcmix.SendScore (granscore2, objno);
}
This $variable substitution gives us another opening for transferring
values from Unity into RTcmix. If we use the recurring
MAXBANG()
scheme for re-triggering the score, we can set new values for the
$variables every time the score is passed into RTcmix. The following
code shows how to do this, using the Up and Down arrow keys to
modify the value sent for
$1
(hifreq)
and the Right and Left arrow keys setting the value for
$2
(lowfreq):
using UnityEngine;
using System.Collections;
using System;
public class beep : MonoBehaviour {
int objno = 0;
rtcmixmain RTcmix;
public TextAsset grantextfile;
float hifreqval, lowfreqval;
String granscore, granscore2;
// Use this for initialization
void Start () {
RTcmix = GameObject.Find ("RTcmixmain").GetComponent<rtcmixmain>();
RTcmix.initRTcmix (objno);
granscore = grantextfile.text;
// set default values for "hifreqval" and "lowfreqval"
hifreqval = 900.0f;
lowfreqval = 500.0f;
granscore2 = RTcmix.setscorevalsRTcmix (granscore, hifreqval, lowfreqval);
RTcmix.SendScore (granscore2, objno);
}
// Update is called once per frame
void Update () {
if (Input.GetKey (KeyCode.UpArrow)) {
hifreqval = hifreqval + 10.0f;
}
if (Input.GetKey (KeyCode.DownArrow)) {
hifreqval = hifreqval - 10.0f;
}
if (Input.GetKey (KeyCode.RightArrow)) {
lowfreqval = lowfreqval + 10.0f;
}
if (Input.GetKey (KeyCode.LeftArrow)) {
lowfreqval = lowfreqval - 10.0f;
}
}
void OnAudioFilterRead(float[] data, int channels) {
RTcmix.runRTcmix (data, objno, 0);
if (RTcmix.checkbangRTcmix (objno) == 1) {
granscore2 = RTcmix.setscorevalsRTcmix (granscore, hifreqval, lowfreqval);
RTcmix.SendScore (granscore2, objno);
}
}
void OnApplicationQuit() {
RTcmix.destroy (objno);
}
}
Be aware that depending on how often a new score is sent to RTcmix, a
latency between setting the values in Unity and having them take effect
in RTcmix will occur.
links
- Sound: Advanced Topics I (Fall 2019)
Each week of this syllabus links to class projects and resources showing
various uses of Unity and RTcmix.
- Sound: Advanced Topics I (Fall 2017)
An older version of the previous link, with somewhat simpler projects.
These use an older version of uRTcmix, but the projects should still
run and the general demonstrations might be useful.
- RTcmix.org
The main RTcmix page. Tutorials, code, useful information.
|