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. It's better just to use a "string" declaration instead (no capital "S").
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;
}
}
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;
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.
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;
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:
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:
Let's look at a modified version of the RTcmix code in the
"granular.txt" example from above
to show how these $variables work:
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:
// 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.
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.
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.
(...)
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;
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
Each week of this syllabus links to class projects and resources showing
various uses of Unity and RTcmix.
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.
The main RTcmix page. Tutorials, code, useful information.