Reggae tutorial: Playing a sound from memory

From MorphOS Library

Revision as of 08:08, 17 June 2010 by Krashan (talk | contribs) (Style.)

Grzegorz Kraszewski


While playing a sound from file is the most common way, there are applications where it has several disadvantages. When a sound is short, played many times and low latency is required, playing this sound from memory will be better option. Seeking in the sound, or restarting it will be substantially faster then, as it does not involve any disk activity.

On the other hand playing from memory should be used with care. Audio data are very space consuming usually. Five seconds audio effect stored as PCM in audio CD quality takes 861 kB of memory. A solution for this problem is to use compressed formats and let Reggae decompress it on the fly.

There are two ways of placing audio file in memory. Firstly, it can be loaded from disk. Secondly the audio file contents may be embedded into the executable file. The second way is a bit dangerous, as it can make the executable very big. On the other hand, such an application is more self contained. Audio file of any fomat can be converted to C code of a large table with BinToC tool. Generated source is added to the project and compiled. Then address of the table (denoted in C just as the table name) and its length in bytes, are passed as parameters to memory.stream object.

Reggae uses the memory.stream class to access data located in system memory. Its usage is similar to file.stream, there are some differencies however. The first one is stream name. For memory.stream it is a string containing stream address as a hexadecimal number, like for example "2749FA0C". MMA_StreamName attribute is not used often however. One usually has the address just as number, not as text. Converting it to text just to make Reggae to converting it back to number makes not much sense. Then MMA_StreamHandle attribute comes with help. It's value is just the address of stream, passed as number. Another very important attribute is MMA_StreamLength. Memory based streams have no "natural" end. When one is reading a file, DOS just reports EOF (end of file) condition, when the file ends. In memory one can read endlessly, until he hits end of physical memory space. That is why MMA_StreamLenght is a required attribute for memory streams. Reggae will refuse to create a stream object, if the attribute is not specified. Note also that the attribute in general is 64-bit one, and takes a pointer to 64-bit number. Passing just a 32-bit number as the value is a common mistake here. Code snippet below shows typical creation of memory stream object from a sound embedded in executable file:


CONST UBYTE SoundData [12837] = { /* audio data here */ }; /* The length is just example. */
QUAD length = 12837;
Object *sound;

sound = MediaNewObject(
  MMA_StreamType, "memory.stream",
  MMA_StreamHandle, SoundData,
  MMA_StreamLength, &length,
TAG_END);


When sound is buffered from file, one has to check the file size first, then allocate a buffer and load the file into it using usual dos.library calls, or C standard library calls. The process is shown in the complete example source code. After the buffer is loaded, memory.stream object is created the same way as above.

To play the sound, one connects created media object with an audio.output object, exactly the same as for playing from disk. There is also no difference in controlling the playback, or waiting for sound end. Thanks to stream abstraction Reggae "does not care" what the stream is. Just seek and retrigger operations are much faster. It is important for short sound effects retriggered many times (think of a shot sound in a game). The example code linked above allows user for retriggering the sound pressing ENTER key. It can be done very fast without delays, assuming some simple audio compression is used (just press and hold ENTER, retrigger rate will be as fast as key repetition rate set in system preferences).

The example shows also "launch and forget" strategy of playing sounds with Reggae. There is no check for sound end. MMM_Stop() just stops and does seek to the start. Then MMM_Play() starts playback. It does not matter if retrigger happens while previous sound is still playing or not. There is also no sound end check when user stops the program. Disposing an active (playing) audio.output object is perfectly safe.