Reggae tutorial: Playing a sound from file

From MorphOS Library

Revision as of 18:31, 14 February 2010 by Krashan (talk | contribs) (Making noise: Added space.)

Playing a sound file from disk is one of most common media related tasks. Reggae can perform it with a few lines of code. Using Reggae for audio playback has several advantages:

  • Wide range of supported audio formats. A codec is selected and loaded by Reggae automatically.
  • Playback is asynchronous. Reggae offloads decoding and playback to a dedicated process. The main application may perform other tasks during playback. It gets informed when the playback ends.
  • Reggae streams audio from disk, so it does not load the whole file to memory. Doublebuffering is fully automatic.
  • Audio is played through selected unit of AHI. Multiple sounds (up to 32, depending on user settings of AHI) may be played simultaneously.

Playing audio directly from disk is best suited for long sounds without low latency requirements. A typical example is music player or playing background music in the game.

From Reggae point of view, the task of playing audio from disk can be divided in two major parts. The first one is to get raw audio samples out of encoded file. The second task is to feed audio data to the output.

Opening a sound file

This part of the job is highly automated. Reggae recognizes the file format and builds complete decoding pipeline for the file with a single function call. The result is returned to the application as one, opaque object (it may contain many objects inside, but it is irrelevant for application programmer).


Object* media;

media = MediaNewObject(
  MMA_StreamType, (ULONG)"file.stream",
  MMA_StreamName, (ULONG)"RAM:sound",
  MMA_MediaType, MMT_SOUND,
TAG_END);


This single call creates a complete decoding infrastructure for a specified file. Data source is specified by two tags, MMA_StreamName and MMA_StreamType. The first one is the name of the source. In case of files it is just path to the file, which may be absolute (as in the example), relative to the current directory, or relative to program executable location (using PROGDIR: assign). MMA_StreamType is used to specify which Reggae stream class (or "transport") should be used. Of course file.stream is for disk based files (and other things recognized by DOS as filesystems).

The last tag is a kind of filter. If Reggae recognizes the file, but it is not sound, the file will be rejected, and the function will return NULL. Of course if file is not recognized at all, NULL will be returned as well. Checking the result of MediaNewObject() against NULL is a very good idea.

In case of success media contains a pointer to Reggae object, having at least one output data port, port 0.

Creating output

The second step is to add audio output object to the Reggae processing pipeline. Then one can "run" the pipeline, which results in playing the file. The output object belongs to audio.output class. Before an object can be created, the class must be loaded from disk. It is done by opening the class with OpenLibrary().


struct Library* AudioOutputBase;

AudioOutputBase = OpenLibrary("multimedia/audio.output", 51);


It is worth noting that audio.output has no specific functions in its shared library API (it is true for all Reggae classes except of the main multimedia.class). Then, the name of variable holding the library base is completely irrelevant (as the name is never used implicitly), and may be anything, "hja76_d62eg" for example. The name used in the example is a bit more readable however.

After class opening, an instance of the class may be created:


Object* play;

play = NewObject(NULL, "audio.output", TAG_END);


The instance is created with generic NewObject() call. There are no tags for attributes. The output object will read all sound properties from media object when they are connected together. I remind again that checking return value here may be a good idea. If objects are ready, let's connect them:


MediaConnectTagList(media, 0, play, 0, NULL);


Output port 0 of media object is connected with input port 0 of play object. Both the objects form a complete Reggae processing pipeline. Now we are ready to play sound. The whole playback control is done by talking to output object.

Making noise

Playback is controlled with two methods: MMM_Play() and MMM_Stop() performed on the audio.output instance.

  • MMM_Play() starts playback if object is stopped, is ignored when object is playing.
  • MMM_Stop() stops playback and rewinds the audio stream to the start (if possible).

Both methods are performed immediately, so just


DoMethod(play, MMM_Play);


starts the playback and


DoMethod(play, MMM_Stop);


stops it at any time. Both methods are asynchronous to the caller and return immediately. Even if MMM_Play setup time is long (because of prebuffering for example), calling process is not stopped because setup is done by audio.output process.