Advanced sound techniques using VSS

Camille Goudeseune

1. Overview

This document explains how to use more advanced features of VSS such as modifying sounds, message groups, and so forth.
It assumes that your application is written in C or in C++, and that you have a copy of VSS available, version 3.1 dated 1/1/1999 or later.
It assumes that you've managed to get parambient sound running as described here.

2. Other ways to connect your application to VSS

Several applications running on several computers (or on the same computer!) can connect to a single copy of VSS and make sounds at the same time. Except for implicitly shared resources like finite CPU power and finite signal level, the sounds will not interact. None of the client computers knows that other clients are connected to "its" copy of VSS.

Conversely, a single application can make sound with several copies of VSS (one copy of VSS per computer), by connecting to them individually with int BeginSoundServerAt(char* hostname). Sending a message simultaneously to all these copies of VSS will produce a synchronized result (to within a few centiseconds if network load and CPU load isn't heavy).

A single client connected to a single copy of VSS can load several .aud files. This design may be useful if your sound description falls into several disjoint components which are controlled by different parts of your application. But then you have to remember the value each AUDinit() returns, and pass that value to AUDupdate...() and AUDterminate().

In rare cases it may work better to weave the entire sound description into your application in the form of dozens of C function calls, instead of calling AUDinit("foo.aud"). We encourage the use of .aud files because this puts the sound description in one place and lets you unit-test the sound without the entire application (using audpanel). But if this isn't working for you, see this section of the VSS reference manual.

3. Messages and .aud files

The general format of messages sent to VSS, whether from .aud files or from C code, is:
command actor_handle arguments.
The command is usually something like SetFoo.
The actor handle in a .aud file is a name (preferably not too short, so you can search for it with a text editor); in a C call it's a floating-point number. The arguments vary: sometimes none, sometimes a few floating-point numbers, sometimes an array of numbers enclosed by square brackets.

A .aud file is merely a sequence of messages. But certain structures are worth mentioning. This is a useful format:

  1. LoadDSO foo.so; // load the dso's we need
    ...
  2. aFoo = Create FooActor; // create actors from those dso's
    ...
  3. sFoo = BeginSound aFoo; // make sounds
You can put all the LoadDSO's together at the top, followed by all the Create's, followed by all the BeginSound's; or you can group together related LoadDSO/Create/BeginSound's. That's a matter of taste. You'll usually want to Create your message groups near the top of the file, though.

The message BeginSound is special in the kind of arguments it takes. It accepts name-value pairs. The next two examples are equivalent:

	sFoo = BeginSound aFoo, SetAmp 0.99, SetPan -1;
and
	sFoo = BeginSound aFoo;
	SetAmp sFoo 0.99;
	SetPan sFoo -1;
Well, they're almost equivalent. VSS runs in real time, so what the second example would do is begin sound sFoo with a default amplitude and pan position, and a few milliseconds later change sFoo's amplitude and pan position. If you wanted the sound to being silently (change 0.99 to 0 here), you'd get a blip of sound before SetAmp sFoo 0 was sent to VSS. The next two examples are truly equivalent:
	sFoo = BeginSound aFoo, SetAmp 0.99, SetPan -1;
and
	sFoo = BeginSoundPaused aFoo;
	SetAmp sFoo 0.99;
	SetPan sFoo -1;
	SetPause sFoo 0;
A paused sound has its computation suspended, so BeginSoundPaused will not let any sound be heard until SetPause 0 unpauses the sound.

4. Message Groups

A message group in a .aud file is rather like a function in a conventional programming language. It is an actor insofar as you create one with the command foo = Create MessageGroup, but it is so specialized that it's easier to think of it as a container of messages to be sent to other actors.

You put stuff into a message group with the AddMessage command (illustrated in many of the examples shown already).

When you invoke a message group, you pass it an array of (floating-point) values. To get at these values, use the special syntax *0 *1 *2 ... in your AddMessage commands. These are expanded into the zeroth, first, second,... elements of this array. (Think of argv[0], argv[1],... in C.)

You invoke a message group in one of two ways. Most commonly, your application calls one of the AUDupdate...() C functions (illustrated in many of the examples shown already). But if you want to call a message group foo from within your .aud file, use the SendData command. The following two fragments do the same thing:

	s = BeginSound ... ;
	m = Create MessageGroup;
	AddMessage m SetFoo s *0;
	AddMessage m SetBar s *1;
	...
	SendData m [ 300 20 ];
and
	s = BeginSound ... ;
	...
	SetFoo s 300;
	SetBar s 20;

5. Modifying sounds with Processor actors

A Processor actor takes sound as input from some other actor and emits a modification of that sound. Sometimes you want to hear the original sound also; that's easy, just don't do anything. But if you want to hear only the modified sound, you have to explicitly quieten the original sound. You can do this with SetAmp 0, or more typically with SetMute 1.

Here's a .aud file which sends white noise through a filter, so you hear only the filtered sound and not the original.
Note the phrase SetInput sNoise, which connects the filter to its source.

	LoadDSO noise.so;
	LoadDSO filter.so;

	aNoise = Create NoiseActor;
	sNoise = BeginSound aNoise SetCutoff 5000 SetOrder 1;
	sleep 2;                  // Now we hear unfiltered noise.

	aFilter = Create Order1FilterActor;
	sFilter = BeginSound aFilter SetInput sNoise;
	sleep 2;                  // Now we hear unfiltered AND filtered noise.

	SetMute sNoise 1;
	sleep 2;                  // Now we hear only filtered noise.

Here you can adjust how loud the original and the modified sounds are:

	LoadDSO noise.so;
	LoadDSO filter.so;

	aNoise = Create NoiseActor;
	sNoise = BeginSound aNoise SetCutoff 5000 SetOrder 1;

	aFilter = Create Order1FilterActor;
	sFilter = BeginSound aFilter SetInput sNoise;

	SetAmp sNoise .1;    SetAmp sFilter .9;   // mostly filtered
	sleep 2;
	SetAmp sNoise .2;    SetAmp sFilter .8;   // mostly unfiltered
	sleep 2;
	SetAmp sNoise 0;     SetAmp sFilter 0;    // silent
	sleep 2;
	SetAmp sNoise .6;    SetAmp sFilter .6;   // both, but too loud!
	sleep 2;

The last setting will probably result in clipping, because .6 + .6 > 1.

Finally, a chain of processors (FM into a filter into a delay into a reverb). In a chain you'll usually want to mute all but the last processor.

	LoadDSO fm.so;
	LoadDSO filter.so;
	LoadDSO delay.so;
	LoadDSO reverb.so;

	aFM = Create FmActor;
	sFM = BeginSound aFM SetMute 1;

	aFilter = Create Order1FilterActor;
	sFilter = BeginSound aFilter SetInput sFM SetMute 1;

	aDelay = Create DelayActor;
	sDelay = BeginSound aDelay SetInput sFilter SetMute 1;

	aReverb = Create ReverbActor;
	sReverb = BeginSound aReverb SetInput sDelay; // No SetMute here!

[ up to main page ]

Back: Ambient sound.

Back: Parambient sound.

Back: Sonifying your data.