14.4. Standard Interfaces

The whole point of a middleware such as MCOP is to make objects talk to each other to fulfill their task. The following are some of the interfaces that are the most important to get started.

14.4.1. The SimpleSoundServer Interface

The SimpleSoundServer interface is the interface that the KDE soundserver artsd provides when running. To connect to it, you can simply use the following lines:

   2 SimpleSoundServer server(
   3     Reference("global:Arts_SimpleSoundServer"));
   5 if(server.isNull()) { /* error handling */ }

Make sure not to access functions of the server after you find out isNull() is true. So what does it offer? First, it offers the most basic command, playing some file (which may be .wav or any other format aRts can understand), with the simple method:

   2 long play(string filename);

Therefore, in a few lines, you can write a client that plays wave files. If you already have a SimpleSoundServer called server, its just

   2 server.play("/var/share/sounds/asound.wav");


It is necessary here to pass a full path, because it is very likely that your program doesn't have the same working directory as artsd. Thus, calling play with an unqualified name will mostly fail. For instance, if you are in /var/share/sounds and artsd is in /home/kde2, if you write play ("asound.wav"), the server would try to play /home/kde2/asound.wav.

The play method returns a long value with an ID. If playing succeeded, you can use this to stop the sound again with

   2 void stop(long ID);

if not, the ID is 0.

Then, there is another set of methods to attach or detach streaming-sound sources, such as games that do their own sound mixing (Quake, for instance). They are called

   2 void attach(ByteSoundProducer producer);
   3 void detach(ByteSoundProducer producer);

If you want to use these, the way to go is to implement a ByteSoundProducer object. This has an outgoing asynchronous byte stream, which can be used to send the data as signed 16-bit little endian stereo. Then, simply create such an object inside your process. For adapting Quake, the ByteSoundProducer object should be created inside the Quake process, and all audio output should be put into the data packets sent via the asynchronous streaming mechanism. Finally, a call to attach() with the object is enough to start streaming.

When you're done, call detach(). An example showing how to implement a ByteSoundProducer is in the kdelibs/arts/examples directory. But in most cases, a simpler way is possible. For porting games such as Quake, there is also the C API, which encapsulates the aRts functionality. Thus, there are routines similar to those needed to access the operating system audio drivers, like OSS (open sound system, the Linux sound drivers). These are called arts_open(), arts_write(), arts_close(), and so on, which, in turn, call the things that ought to happen in the background.

Whether a layer will be written to simplify the usage of the streaming API for KDE 2.0 apps remains to be seen. If there is time to do a KAudioStream, which handles all attach/detach and packet production, it will go into some KDE library.

Finally, two functions are left. One is

   2 object createObject(string name);

It can be used to create an arbitrary object on the soundserver. Therefore, if you need an Example_ADD for some reason—and it shouldn't be running inside your process, but inside the soundserver process—a call looking like this:

   2 Example_ADD e = DynamicCast(server.createObject("Example_ADD"));
   3 if(e.isNull()) { /* fail */ }

should do the trick. As you see, you can easily cast Object to Example_ADD using DynamicCast.

Just a few words explaining why you may want to create something on the server. Imagine that you want to develop a 3D game, but you are missing 3D capabilities inside aRts, such as creating moving sound sources and things like that. Of course, you can render all that locally (inside the game process) and transfer the result via streaming to the soundserver. However, a latency penalty and a performance penalty are associated with that.

The latency penalty is this: you need to do streaming in packets, which have a certain size. If you want to have no dropouts when your game doesn't get the CPU for a few milliseconds, you need to dimension these like four packets with 2048 bytes each, or something like that. Although the resulting total time needed to replay all packets of 47 milliseconds protects you from dropouts, it also means that after a player shoots, you'll have a 47-millisecond delay until the 3D sound system reacts. On the other hand, if your 3D sound system runs inside the server, the time to tell it "player shoots now" would normally be around 1 millisecond (because it is one oneway remote invocation). Thus, you can reduce the latency by 47 milliseconds by creating things server side.

The performance penalty, on the other hand, is clear. Putting all that stuff into packets and taking it out again takes CPU time. With very small latencies (small packets), you need more packets per second, and thus, the performance penalty increases. So for real-time applications such as games, running things server side is the most important.

Last but not least, let's take a look at effects. The server allows inserting effects between the downmixed signal of all clients and the output. That is possible with the attribute

   2 readonly attribute StereoEffectStack outstack;

As you see, you get a StereoEffectStack, for which the interface will be described soon. It can be used to add effects to the chain.

14.4.2. The KMedia2 Interfaces

KMedia2 is nothing but a big remote control. It allows you to create objects that play some kind of media (such as .wavs, MP3s, but—at least from what the interfaces allow—also CDs or streams from URLs). This is achieved through one interface, called PlayObject, and it looks like the following:

   2 interface PlayObject : PlayObject_private {
   3     attribute string description;
   4     attribute poTime currentTime;
   5     readonly attribute poTime overallTime;
   6     readonly attribute poCapabilities capabilities;
   7     readonly attribute string mediaName;
   8     readonly attribute poState state;
   9     void play();
  10     void seek(poTime newTime);
  11     void pause();
  12 };

As you can see, this is enough for telling the object to play, pause, and seek anytime. After you have a PlayObject, you should have no difficulties dealing with it. There is something to be said about the poTime type, which is used to represent custom times. For instance, a mod player could count in patterns internally, while also doing calculations in seconds (which is more appropriate for the user to read). Thus, poTime allows you to define custom times, like this:

   2 struct poTime {
   3     long ms, seconds;  // -1 if undefined
   4     float custom;      // some custom time unit
   5                        // -1 if undefined
   6     string customUnit; // for instance "pattern"
   7 };

PlayObjects are allowed to define either the "normal" time, the "custom" time, or both, just as they please. Also, seeking can be done only on the time type the PlayObject understands. (For example, if a mod player understands only patterns, you can seek only with patterns). Then there are capabilities, which can be used for the PlayObject to say, "Well, I am a stream from an URL; you can't seek me at all." They look like this:

   2 enum poCapabilities { capSeek = 1, capPause = 2 };

and finally the different states the PlayObject are:

   2 enum poState { posPlaying, posFinished, posPaused };

Still, some part is missing. You not only need to know how to talk to PlayObjects, you need to know how to create them in the first place. For that, there is PlayObjectFactory, which looks like the following:

   2 interface PlayObjectFactory {
   3     PlayObject createPlayObject(string filename);
   4 };

That's it. You have a factory for creating PlayObjects, and you know that they will disappear automatically, as soon as you no longer reference them. And the last missing piece, "Where do I get that PlayObjectFactory from?" is simple: it's a global reference, and it is called Arts_PlayObjectFactory, so it works the same as with the SimpleSoundServer interface.

14.4.3. Stereo Effects/Effectstacks

Let's take a closer look at another interface that is used to put effects in a chain. Basically, a StereoEffectStack looks like Figure 14.6:

Figure 14.6. How a StereoEffectStack works.

Each of the inserted effects should have the following interface:

   2 interface StereoEffect : SynthModule {
   3     default in audio stream inleft, inright;
   4     default out audio stream outleft, outright;
   5 };

and all normal StereoEffects derive from that. For example, two of them are StereoVolumeControl and StereoFFTScope. However, there is the StereoEffectStack interface itself, which looks like this:

   2 interface StereoEffectStack : StereoEffect {
   3     long insertTop(StereoEffect effect, string name);
   4     long insertBottom(StereoEffect effect, string name);
   6     void remove(long ID);
   7 };

As you can see, the StereoEffectStack is a StereoEffect itself, which means it has the same inleft, inright, outleft, and outright streams. Thus, you can set the inputs and outputs by connecting these. You can also insert effects at the top or at the bottom. If you have no effects, the inputs and outputs will simply get connected. Finally, you can remove effects again by ID.

SimpleSoundServer provides you with a StereoEffectStack that is between the sound mixing and the output. Initially, it's empty. If you want effects, you can insert them into the stack, and if you want to remove them, that's also no issue.

And that is what you're going to do next.