This is the reference manual for version 3.1 of VSS, the "Virtual Sound Server". Developed at NCSA and ISL, VSS is multiplatform software for data-driven sound production controlled from interactive applications.
VSS controls, coordinates, and synchronizes sounds produced by direct software synthesis, sample playback, and external synthesizers such as MIDI, Max, and Open Sound Control.
VSS's original SGI audio scheduler is based on HTM, a real-time sound synthesizer developed by Adrian Freed at the Center for New Music and Audio Technologies.
VSS comes in several distributions:
Each distribution includes the reference manual and the tutorial, also available here. (The distribution now omits the tutorials because they're too big.)
A client first makes a connection to VSS. It then requests the server to make a sound, whereupon the server does so and replies with a "handle" for the sound. The client can then send further messages using this sound handle to modify the sound and eventually delete it. Finally the client breaks the connection with the server.
Interaction between the client and sounds is coordinated through entities called actors. Actors are requested by the client and then manipulated using actor handles, in similar manner to sounds. Actors are classified by the types of actions they perform, including the types of sound synthesis being used to generate the sounds. As with an object-oriented class hierarchy, in the actor hierarchy, an actor may control several related sounds, so that they all may be manipulated at once with a single message to the actor. An actor accepts messages from VSS clients or from other actors, and can send messages to other actors. Some actors process real-time input and generate real-time output in various formats, primarily audio and MIDI.
Run VSS on an SGI computer with audio hardware and IRIX 5.3 or later,
by typing "vss" at a shell prompt. Run a client on the same host,
or on a different host accessible to the server's host over the
network. In the latter case, tell the client where to
VSS is, with a shell command like export SOUNDSERVER=myvsshost.foo.bar.edu.
Run only one instance of VSS per machine. If you find a VSS already running,
either use it, or, if you must run your own copy, first type vsskill
to terminate the current one.
If the client can't connect to the server, it times out after a few seconds and prints an error message.
When VSS exits, it emails to audio@ncsa.uiuc.edu a report consisting
of which actors were loaded and how many of each. (This is so only
for the licensed, non-demo versions.) These reports help the VSS
team set priorities on which features to add, what code is really worth
optimizing, and so on. If you prefer not to have these reports sent,
set the environment variable VSSMAIL to "0" (export VSSMAIL=0).
If you export VSSMAIL=foo@bar.com, the email will be copied to that address.
-chans 2into2:1,0
-chans 8into2:6,7
(or loosely build up an 8-channel mix from several 2-channel hosts)
-chans 2into4:0,0,1,1
setpriority(PRIO_PROCESS, 0, -10) and mlockall(MCL_FUTURE)).
mplockpid).
#include "vssClient.h"
main()
{
if (!BeginSoundServer()) {
printf("UDP connection to sound server failed\n");
exit(2);
}
/* Break connection to sound server */
EndSoundServer();
}
To build this client, set the directories in which to find vssClient.h and libsnd.a:
CC -c foo.c -IVssIncludeDir -fullwarn CC -o foo foo.o -LVssLibDir -lsnd -ll -lm
or
CC foo.c -IVssIncludeDir -LVssLibDir -lsnd -ll -lm -fullwarn
To compile VSS clients, try gcc and g++. On Irix, Mipspro C and C++ work well too.
Link with CC or perhaps cc -lC, but definitely not cc or ld.
The latter may mystify you with errors like __vec_new and __nw__FUi.
If you use several versions of VSS and their components get mismatched, reconcile their versions thusly:
strings libsnd.a | grep Version.
Compare this with the version number reported by VSS at startup.
The client calls to GetVssLibVersion() and GetVssLibDate() provide the formal way to check this:
const char* GetVssLibVersion(void);const char* GetVssLibDate(void);The .aud file provides an interface through which the bulk of an application's audio code may be isolated from the compiled code. While the events which may trigger sound originate from within the client application, the specific way in which those events ultimately map to sound are described through the .aud file. This interface then allows the sound to be tweaked independently of the application, e.g. without recompiling the application, thereby simplifying the development and the debugging of the complete, sonified application. The interface will also help trivialize the process of porting the audio component of the app to other apps or platforms.
Three main functions implement the client-side interface to the .aud file:
int AUDinit(const char *filename);filename is the name of the .aud file, e.g. "../AUDFILES/foo.aud".
float AUDupdate(int handle, char *msgGroupName, int numFloats, float *floatArray);handle returned
by AUDinit(), by sending an array of float data floatArray of length
numFloats to the Message Group msgGroupName. For trigger
and flag-type events, numFloats may be zero and floatArray NULL.
(Typically you can ignore the float return value. But if you know that the message group
*msgGroupName contains a BeginSound statement, the return
value will be a handle to that sound (the last one, if there are several).
This is useful if you don't know at compile time how many sounds you'll need simultaneously.)
void AUDterminate(int handle);AUDinit().
A shortcut form of AUDupdate(), float AUDupdateFloats(int handle, char *msgGroupName, int numFloats, float f1, float f2, ...); ,
conveniently lets you dispense with declaring and stuffing floatArray. But it's unwieldy for very large numbers of arguments (though it'll accept a thousand), and the arguments must have type float or double (an int like 42 instead of 42.0 produces garbage).
For special circumstances there are a few other functions: AUDreset(), AUDupdateTwo(), AUDupdateMany(), AUDqueue(), and AUDflushQueue().
This code builds upon the previous example, putting these three functions in context:
#include "vssClient.h"
void UpdateState(float *data)
{
/* generate three numbers between 0 and 1 */
data[0] = drand48();
data[1] = drand48();
data[2] = drand48();
/* rescale two numbers */
data[1] *= 20;
data[2] *= 20;
}
void main()
{
int i;
int handle;
float data[3];
if (!BeginSoundServer()) {
printf("UDP connection to sound server failed\n");
exit(2);
}
handle = AUDinit("example.aud");
if (handle < 0) {
printf("Failed to load audfile example.aud\n");
exit(3);
}
/* Send state messages to VSS */
for (i=0; i<10; i++) {
UpdateState(data); /* run my simulation */
AUDupdate(handle, "Whack", 3, data); /* send message group 0 */
sleep(1); /* wait a sec */
AUDupdate(handle, "Clang", 3, data); /* send message group 1 */
sleep(2);
/* data[] maps to the "*" parameters in the .aud file
(see below, "Messages in .aud files"). In the .aud file immediately
below, data[0] sets the amplitudes of sounds in all three BeginSound
messages; data[1] and data [2] set the tone color of sounds in two
BeginSound messages that are synchronized.
*/
}
AUDterminate(handle);
EndSoundServer();
}
The client connects to VSS. Then it calls AUDinit(),
which reads the .aud file example.aud (shown below), parses out the
message strings, and then sends the messages to VSS. VSS also returns
a file handle to reference example.aud and to interact with actors which
example.aud caused to be created.
The client then updates the state of a data array, here 3 floats, and
then makes successive calls to AUDupdate(). Each call passes new data values to each of the
two Message Groups named Whack and Clang. The Message Groups are created from messages
within the .aud file. (Details of .aud file messages are covered later.)
Here is the corresponding example .aud file, example.aud:
// Have the server echo messages
SetPrintCommands 1;
// Load the DSOs that we will need
LoadDSO msgGroup.so;
LoadDSO marimba.so;
LoadDSO tubebell.so;
// Create two Message Groups
Whack = Create MessageGroup;
Clang = Create MessageGroup;
// Create generator actors for the message
// groups to route messages to
Woody = Create MarimbaActor;
Belly = Create TubeBellActor;
// Create list of messages for "Whack"
AddMessage Whack BeginSound Woody SetAmp *0, SetStickHardness 3.0;
// Create list of messages for "Clang"
AddMessage Clang BeginSound Belly SetAmp *0, SetModIndex *1;
AddMessage Clang BeginSound Belly SetAmp *0, SetModIndex *2;
Again, .aud file messages are detailed below; only certain aspects are pointed out here.
Dynamically Shared Objects (DSOs) are loaded with the LoadDSO message.
A DSO contains the code for one or more actors used by VSS, so it must
be loaded before these actors can be used. Therefore, LoadDSO messages
are usually placed near the start of the .aud file.
The Message Groups are then created. The names of the Message Groups
within the .aud file must match the names used in the client's AUDupdate()
calls: these names are the interface between client and server.
Messages are then added to each Message Group. The messages define the group of actions
to be taken by VSS when a particular Message Group is triggered by a client-side
AUDupdate(). In this case, the message "Whack", upon receipt by the server, will begin
a marimba sound with stick-harness of 3.0; the message "Clang" will begin two Tubular
Bell sounds with two different degrees of tonal brightness. The set of actions taken
by a given Message Group occur in the order in which they appear in the .aud file.
Through this grouping of messages, one AUDupdate() can trigger many, possibly
diverse, actions. Diversity of actions is accomodated by allowing an array
of data values to be passed through the call to AUDupdate(). From within the Message
Group, individual elements of the array may then be referenced and used, say, to set
different sound parameters. For example, in the line
AddMessage Whack BeginSound Woody SetAmp *0, SetStickHardness 3.0;
the element array[0] is referenced through the notation *0 (generally, array[N] is
referenced through *N) to set the amplitude value of the Marimba sound to be played.
A .aud file has no variables per se, no "scope". All the state of a running .aud
file is hidden inside its actors. Therefore, at first blush if you use BeginSound from inside
a message group
AddMessage Clang BeginSound Belly;then there's no way to get at the handle returned by BeginSound (if you want to modify or end that sound). The following doesn't work:
AddMessage Clang myBell = BeginSound Belly; // "myBell =" is illegal AddMessage Clang SetAmp myBell 0.1;Message Groups have a special syntax to handle this case, where the message group itself begins a sound instead of modifying an already existing sound. The syntax is similar to *0 *1 etc.: "*?" stands for the handle to the most recent BeginSound. So the previous example becomes, correctly:
AddMessage Clang BeginSound Belly; AddMessage Clang SetAmp *? 0.1;You can modify the note at some point in the future by using the LaterActor, as follows:
myLaterActor = Create LaterActor;
...
AddMessage Clang BeginSound Belly SetAmp 0.1;
// Might as well put the SetAmp in the same line.
AddMessage Clang AddMessage myLaterActor 0.5 SetModIndex *? *1;
// Wait 0.5 seconds, then SetModIndex.
AddMessage Clang AddMessage myLaterActor 1.5 SetModIndex *? *2;
// Do it again, 1 more second later.
AddMessage Clang AddMessage myLaterActor 5.0 Delete *?;
// End the sound after 5 seconds.
If your C program uses only a single .aud file, it can use a simplified form of the C
functions AUDinit(), AUDupdate()/AUDupdateFloats(), and AUDterminate():
AUDinit(). Or just test that it's nonnegative, to ensure that the .aud file was valid.
AUDupdate(). Instead, call AUDupdateSimple().
AUDupdateFloats(). Omit the handle and instead call AUDupdateSimpleFloats().
AUDterminate(). EndSoundServer() calls that for you.
So a simple client using this syntax is:
#include "vssClient.h"
main()
{
if (!BeginSoundServer()) /* connect to VSS */
return -1;
AUDinit("foo.aud"); /* start making a sound */
AUDupdateSimpleFloats("foo", 1, 42.0); /* change the sound */
sginap(1000); /* wait 10 seconds */
AUDupdateSimple("bar", 0, NULL); /* change the sound again */
sginap(1000); /* wait 10 seconds */
EndSoundServer(); /* disconnect from VSS */
}
===Basic .aud File Message Syntax===
Messages in .aud files have one of two forms:
name = message, arg1, arg2, arg3,... ;
message, arg1, arg2, arg3,... ;
AUDinit() reports a syntax error, look for missing semicolons near the line with the reported error.
/*C*/ or //C++ style.
*_ syntax to indicate numbers that index
into the array passed to the Message Group. This defines a mapping
from the data array passed in AUDupdate() to arguments in the Message Group.
After mastering the basics, you may find this to be a useful way to manage very large .aud files.
In Irix, filter programs can modify a .aud file before it gets parsed. Filters read text from standard input and emit (modified) text on standard output. Examples of these are the C preprocessor, perl, m4, and handwritten C programs. To invoke a filter, make the first line of the .aud file
//pragma filter "myprogramname"
where myprogramname is the name of the filter (technically,
anything that /bin/sh can parse). The C preprocessor, for example:
//pragma filter "/usr/lib/cpp -P"
This line can't have leading or trailing whitespace, or trailing comments. Also note that because the filtered output text isn't stored, line numbers of any reported syntax errors will probably be incorrect.
In special circumstances you may want to call C functions directly from your VSS client application, instead of going through a .aud file. Here are some of the functions you can use then (these are the same functions which are called by the parser of .aud files). Beware of subtle errors, though. This fragment of documentation doesn't pretend to adequately explain this interface, out of laziness and out of discouraging its use.
int BeginSoundServer(void); int BeginSoundServerAt(char * hostName); void EndSoundServer(void); int PingSoundServer(void); void actorMessage(char* messagename); void actorMessageF(char* messagename, float); void actorMessageFD(char* messagename, float, int); void actorMessageFDD(char* messagename, float, int, int); float actorMessageRetval(char* messagename); void MsgsendObj(OBJ obj, struct sockaddr_in *paddr, mm* pmm); OBJ BgnMsgsend(char *szHostname, int channel); void Msgsend(struct sockaddr_in *paddr, mm* pmm); void MsgsendArgs1(struct sockaddr_in *paddr, mm* pmm, const char* msg, float z0); void MsgsendArgs2(struct sockaddr_in *paddr, mm* pmm, const char* msg, float z0, float z1); void clientMessageCall(char* Message); float actorGetReply(void); float createActor(const char* actorType); void deleteActor(const float actorHandle); void setActorActive(const float actorHandle, const int active); float beginSound(const float hactor); void killSoundServer();
VSS itself understands these messages, independent of whatever actors it has created.
actorNameactorName.
dsoNamedsoName refers to a file in, or path/file relative to,
vss, or
VSS searches these directories in this order. LoadDSO fails if the dso is not in one of these directories. In Windows, DSO's (actually, DLL's) are searched for in the following directories, in this order:
Note that the LIBPATH environment variable is not used.
levellevel = 1, cause VSS to print all the messages it receives from clients (in green).
When 2 or more, print messages that actors receive from other actors (in blue).
When 0 or less, print none of the above.
filename"
sfconvert filename.raw filename.aiff -i rate 44100 int 16 2 chan 2 end format aiff
where 44100 is the sampling rate VSS was running at, and the
number 2 after chan is the number of channels VSS was running at.
(A "filename" optionally follows the 0. This filename is ignored,
but allowed for the convenience of tools like audpanel.)
geargear takes one of three values: PRNDL_Parked, PRNDL_Low, PRNDL_Drive.
PRNDL_Parked suspends computation of samples, and is appropriate
during initialization (often the bulk of a .aud file, creating actors and
sequences). PRNDL_Low is the default.
PRNDL_Drive is appropriate when the app is running smoothly and sending only
parameter-update messages. PRNDL_Drive handles a flood of incoming messages
more gracefully than PRNDL_Low.
The following look like messages when written in a .aud file, but in fact
do not cause any message to be sent to the VSS server. They only affect
the client that loaded the .aud file. (So don't put them in a message
group; they are not messages, are not sent to any actor, not sent to
VSS, not seen by AUDupdate(). They happen only during AUDinit();
they're useful only during the initialization part of a .aud file.)
secondssecondsClientSetTimeout 60; LoadFile ... ; LoadFile ... ; LoadFile ... ; dummy = Create SampleActor; Delete dummy; ClientSetTimeout 2.5;
string"Actors have a class hierarchy. They respond to messages at different levels, depending upon the actor type and the functionality needed.
Actors fall into three primary types.
Two examples explain these concepts. First, the LaterActor is a Control Actor:
The Create message instantiates a LaterActor.
VSS returns the handle act_handle1 so further messages may refer to that particular instance.
During usage, we may desire to delay, say by 0.5 seconds, the sending of a message
message to an instance act_handle2 of the generator actor FMActor.
We may accomplish this by issuing an AddMessage message using
act_handle1, where the message contents use act_handle2. Thus,
the LaterActor passes the contents of message from where it originated
(e.g. the client or another top-level actor) to another actor, in this
case a generator actor. So, the LaterActor works only at the top level.
On the other hand, the FMActor is a Generator Actor and operates at all three levels:
Again, a Create messages creates an instance act_handle2 of the FMActor.
Two sounds are begun using the BeginSound
message and act_handle2. This invokes the FMHandler, creating the
two sound instances sound_handle1 and sound_handle2. For these
sounds, the FM synthesis algorithm is used with default parameters.
The internal state information for the sound synthesis is hidden
within each existing sound, so that the sound generation may proceed
independently among all sounds. (Each sound can then be called a child
of its referring actor instance, or parent actor.)
Messages may then be sent to existing instances of the Actor or
Handler, with the different levels of interaction resulting in differing
behavior. For example, SetAllAmp act_handle2 0.5; assigns 0.5 to the
amplitudes of both sounds sound_handle1 and sound_handle2,
but SetAmp sound_handle2 0.5; affects only the latter.
hActor boolact() method is being called.
Certain uses of the EnvelopeActor require the Active message to be sent manually.
VSS sends this internally, to silence an actor just before deleting it.
hActor xx. Individual actors use this value as they see fit.
hActorhActorGenerator actors also understand the following messages. Italic arguments are optional. [Params] can be any number of parameter-setting messages appropriate to that actor.
hActor [params]hActor [params]hActor xx be the default amplitude for all future handlers created by this actor (0 = silent, 1 = nominal).
hActor x timex and set the default amplitude for all future handlers.
If time is specified, modulate handlers to the new value over the specified duration.
Regardless of time, the default value is set immediately,
hActor xx (-100 or less = silence, 0 = nominal).
hActor xx (-1 = hard left, 0 = centered, +1 = hard right).
hActor xx (-1 = hard down, 0 = level, +1 = hard up). Use +1 to give your sounds a subtle aura of impecuniosity.
hActor xx (in feet).
hActor xx (in feet), if you need to tweak how the distance cues like SetXYZ work.
hActor x y z(x,y,z) (in feet, standard cave-coords).
hActor fInvertfInvert is true.
hActor x timehActor x timehActor x timehActor x timehActor x timehActor x timehActor fInverthActor boolhActor xx (0 = silence, unity = nominal). Default is nominal.
hActor xx (-100 or less = silence, 0 = nominal). Default is nominal.
hActor x timehActor x timeHandlers also understand the following messages. Italic arguments are optional.
hSound x timex. If time is specified, modulate to the new value over the specified duration.
hSound x timex.
THe secondary amplitude defaults to unity. It is provided if you need to control amplitude in a separate way from the main amplitude.
hSound x timex. If time is specified, modulate
to the new value over the specified duration.
hSound x timex.
The secondary amplitude defaults to +0 dB.
hSound x timex. If time is specified, modulate to the new value over the specified duration.
Pan from hard left to hard right as x varies from -1 to 1.
In 4-channel mode, hard left and hard right meet directly behind the listener,
and mod-2 arithmetic is used (-3 is also directly behind; 2 is also directly in front).
Panning over a time interval in 4-channel mode is done "the shortest way around the circle".
8-channel is analogous to 4-channel.
hSound x timex. If time is specified, modulate to the new value over the specified duration.
Move from "hard down" to "hard up" as x varies from -1 to 1. This message
has an audible effect only if VSS is running with 8 channels.
hSound x timex feet. If time is specified, modulate to the new value over the specified duration.
hSound xx feet, if you need
to tweak how the distance cues like SetXYZ work. No time may be
specified here.
hSound x y z time(x,y,z) feet, in standard cave-coords.
If time is specified, modulate to the new value over the specified duration.
Note that SetXYZ is implemented in terms of SetPan SetElev SetDistance.
This means that, for a given handler, you can use either SetXYZ or these other three.
If you combine them, the acoustic result is undefined (though probably entertaining).
But you can use SetXYZ for some sounds you wish to localize in threespace,
and use SetPan etc. to position other sounds to taste, with no problems.
You might also want to do SetDistanceHorizon hSound x (as above)
where x is the largest distance you expect to a sound source.
hSound fInvertfInvert is true.
hSound boolhSound boolhSound boolhSound numchansnumchans be how many channels of audio this handler computes.
This should be 1, 2, 4, or 8. (8 requires Irix 6.3 or later; Windows
requires 1 or 2.) Most handlers default to 1, or, if they listen
with SetInput, their input's number of channels. A handler can have
a different number of channels than the number of channels VSS itself
is running at (although more channels is inefficient and doubtfully useful).
hSound nn. The value of n should range
from zero to one less than the number of channels VSS is running at.
This is useful if the loudspeakers aren't arranged in a spatial array and
need to be addressed individually.
hSound [amp0 amp1 ...]hActor x timex (0 = silence, unity = nominal). Default is nominal.
If time is specified, modulate to the new value over the specified duration.
hActor x timex (-100 or less = silence, 0 = nominal). Default is nominal.
If time is specified, modulate to the new value over the specified duration.
These messages are summarized in the following slightly obsolete diagram, drawn to resemble
an audio mixing console.
Every "sound", i.e., handler of a generator actor, runs unless it got
a SetPause message (taking a union break during a rehearsal). If it's
not paused, it normally outputs to VSS's "main bus." But this can be
interrupted with the SetMute message, for instance if you want to use
only the "side" output which other actors (processor actors, typically)
can grab with a SetInput message. Two independent gain controls
are provided, so you can for example control them independently from
two message groups. Finally, SetPan (and SetElev) sets the relative
levels going to each channel of VSS's audio output (the "main bus").
The number of channels here can be one, two or four, set by the "-chans"
command-line argument or from the control panel.