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.
actorName
actorName
.
dsoName
dsoName
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.
level
level
= 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 aiffwhere
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.)
gear
gear
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.)
seconds
seconds
ClientSetTimeout 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 bool
act()
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 x
x
. Individual actors use this value as they see fit.
hActor
hActor
Generator 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 x
x
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 x
x
(-100 or less = silence, 0 = nominal).
hActor x
x
(-1 = hard left, 0 = centered, +1 = hard right).
hActor x
x
(-1 = hard down, 0 = level, +1 = hard up). Use +1 to give your sounds a subtle aura of impecuniosity.
hActor x
x
(in feet).
hActor x
x
(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 fInvert
fInvert
is true.
hActor x
timehActor x
timehActor x
timehActor x
timehActor x
timehActor x
timehActor fInvert
hActor
boolhActor x
x
(0 = silence, unity = nominal). Default is nominal.
hActor x
x
(-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 time
x
.
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 x
x
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 fInvert
fInvert
is true.
hSound
boolhSound
boolhSound
boolhSound numchans
numchans
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 n
n
. 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.