PyESD - A Python Interface to the ESound Daemon

The Enlightenment Sound Daemon (aka EsounD) was written by Eric B. Mitchell based on a prototype by Carsten Haitzler. It provides a sound interface for the Enlightenment window manager, part of the GNOME project. PyESD wraps the EsounD interface using the SWIG program. This version of PyESD is based on version 0.2.8 of EsounD.

In addition to providing a SWIGed wrapper around the standard ESD library, PyESD also provides a small class library to give a more standard object-oriented flavor to the API. So, for example, the following code:

   s = esd.esd_open_sound(server)
   esd.esd_sample_cache(s, esd.ESD_PLAY | esd.ESD_MONO, esd.ESD_DEFAULT_RATE,
                        len(waveString), "sample name"
                        )
   os.write(s, waveString)
   sampleId = esd.esd_confirm_sample_cache(s)
   esd.esd_sample_play(s, sampleId)

Can instead be written like this:

   s = esd.ServerSession(server)
   sample = esd.Sample(esd.ESD_PLAY | esd.ESD_MONO, esd.ESD_DEFAULT_RATE,
                       waveString
                       )
   sampleId = s.cacheSample(sample, "sample name")
   s.playSample(sampleId)

Project Status

I wrote PyESD because I needed a way to manipulate the ESound Daemon from Python programs and was unable to find one. At this time, I regard the project as pretty much complete. I don't anticipate adding anything to it at this time (except possibly a few minor features).

I will gladly implement bug-fixes, and will accept submitted enhancements. If anyone wants to take over the code, please contact me.

The only feature that is conspicuous in its absence is support for notification functions - callbacks that allow a client to be notified of interesting events.

At this time, the code is not reentrant because the error signaling uses a global variable. Exercise caution when working with threads.

Mini-Manual

For the native ESD functions, please refer to the documentation generated by SWIG. This is intended to be a brief discourse on the use of the classes that I have added to the system. For more in-depth coverage, "use the source, Luke".

The most important class in the system is ServerSession. This provides most of the API's standard functionality including sample management and information queries.

To create a ServerSession, just provide the host that you want to connect to:

   s = esd.ServerSession('localhost')

An important consideration is the fact that ESD maintains an authentication cookie in the ".esd_auth" file in your home directory. This is used to keep potential attackers from attaching to your sound daemon and doing evil things with it (like remotely monitoring your conversations). If you want to attach from a remote host, you will need to have the current server's cookie file in your home directory on the remote host.

To cache a sample, first create an instance of the Sample class:

   sample = esd.Sample(esd.ESD_PLAY | esd.ESD_MONO | esd.ESD_BITS8,
                       esd.ESD_DEFAULT_RATE,
                       sampleData
                       )

The first parameter of the constructor is the format. This is a bit-mask defining the format of the sample. Here we have specified ESD_PLAY (which should probably always be present) indicating that the sample can be played, and ESD_MONO | ESD_BITS8, indicating an 8 bit monophonic sample. In fact, 8 bits is the default, so we need not have specified this.

The second parameter is the sample rate. Here we have specified the default rate, about 44khz.

The third parameter is a string (usually a large one) containing the entire body of the sample. This can be read in from a file or generated within the program.

Now that we have a sample, we just have to cache it:

   sampleId = s.cacheSample(sample, 'sample name')

The cacheSample() method returns an integer sample id. The 'sample name' string will be associated with the sample in the server, so that other programs can check for the existence of a particular sample.

We can now play the sample as follows:

   s.playSample(sampleId)

Alternatively, we could have played the sample by its name:

   s.playSample('sample name')

All of the methods that accept a sample id will also accept a sample name, however, be advised that this mode of specification requires two server calls instead of just one.

We can also do things like loop the sample:


   # start looping the sample
   s.loopSample(sampleId)
   
   # let it loop for a minute
   time.sleep(60)
   
   # and stop it at the end of its cycle
   s.stopSample(sampleId)

When we're done using a sample, we can free it as follows:

   s.freeSample(sampleId)

This deletes the sample from the server, so it should not be done if the sample is to be used by more than one program.

The id of a sample that is already cached on the server can be obtained using the getSampleId() method:

   sampleId = s.getSampleId('sample name')

In addition to managing samples, the ServerSession object also allows us to check server information. The getServerInfo() and getAllInfo() methods return information structures describing what the server has got going on. See the SWIG generated docs for information on these structures.

Playing Streams

The ServerSession class is good for managing samples, but is not suitable for playing streams. To play long streams, use the Player class. Instances of this class are constructed with a format, rate, host name and player name. After construction, they can be written to just like a file object. For example, the following code plays a raw file with 2 channels of 16 bits each at 44khz:

   p = Player(esd.ESD_STEREO | esd.ESD_BITS16, esd.ESD_DEFAULT_RATE, 
              'localhost', 'player name')

   src = open('music.raw')
   
   while 1:
      data = src.read(esd.ESD_BUF_SIZE)
      if not data:
         break
      p.write(data)

The example above tends to stutter on my machine (a fast P2). I think that this has to do with the timing of the interpreter.