Reggae tutorial: Playing a synthesized, continuous wave

From MorphOS Library

Grzegorz Kraszewski


Introduction

This tutorial shows how raw sound data may be played with Reggae. The example code synthesizes a 1 kHz sine wave and plays it continuously. The wave is synthesized into a table in memory. Then memory.stream is used to access it. The next object, an instance of rawaudio.filter, attaches audio parameters to the raw data. Finally audio.output plays the wave in a endless loop, using its looping feature.

Raw sine wave synthesis

In theory a single period of sine wave is enough to play it continuously. There are two reasons for using more periods however. The first is number of samples in one period. We want to play a 1 kHz wave sampled at 44.1 kHz. Then one period would contain 44.1 samples, which is not integer obviously. The second reason is processing overhead of tight loop. Every loop turn takes additional processing time, so it is better, when the loop is longer.

Taking both these reasons into account, 100 periods of the sine are generated into the table containing 4410 samples, which makes 0.1 second of sound. The generation is then straightforward and uses sin() function from the standard C math library. As the argument of sin() is in radians (so one period is 2π), it goes from 0 to 200π in 4410 steps (without the last value). The amplitude of the sine wave (which is normally 1.0) is scaled to 16-bit signed range by multiplying by 32767.

Using memory.stream for synthesis buffer

Basic usage of memory.stream has been discussed in "Playing a Sound From Memory" tutorial. MMA_StreamHandle attribute is used to pass the memory table address, MMA_StreamLength one takes the table size in bytes (note, it is not the same as the table size in the declaration, as every element of the table ocuppies two bytes). Note also, MMA_StreamLength is 64-bit attribute, and as such is passed as a pointer to a QUAD variable containing the value.

Applying audio parameters with rawaudio.filter

While the sine wave generation is done, the memory.stream output cannot be connected directly to audio.output object. This is because stream classes deliver just plain stream of bytes with no meaning assigned. In the previous tutorial Reggae was able to play this stream, because it has been self-descripting (contained a header, for example AIFF or WAVE one). Raw data are not self-descripting, so we have to describe it to Reggae ourselves. A "conversion" of stream into audio signal is done with rawaudio.filter object. Usage of quotes is intentional. In fact this class does not convert the data, it just attaches audio format and attributes to the data stream. Then the stream is recognized by Reggae as audio and may be further processed, which of course includes playing it with audio.output.

Applying audio information consists of two parts. Audio attributes (number of channels, sample rate and volume) are set during rawaudio.filter object creation. The samples format is set by setting it on the output port of created object:


Object *rawaudio;

rawaudio = NewObject(NULL, "rawaudio.filter",
  MMA_Sound_Channels, 1,
  MMA_Sound_SampleRate, 44100,
  MMA_Sound_Volume, 65536,
TAG_END);

MediaSetPort(rawaudio, 1, MMA_Port_Format, MMFC_AUDIO_INT16);


Most of the code above is redundant, as it happens that values set match default values for rawaudio.filter. Namely default number of channels is 1, default volume is 65536 ($10000 = full volume) and default sample format is 16-bit integer. Default sampling rate differs however, as it is 8000 Hz. Anyway all attributes have been set in the example code just for completness.

One can use any of the three Reggae audio formats for raw data. Except of 16-bit integers, Reggae handles 32-bit signed integers (MMFC_AUDIO_INT32) and 32-bit signle precision floats (MMFC_AUDIO_FLOAT32). Using these for playback makes not much sense however, as data will be converted to 16 bits anyway.

Rawaudio.filter allows also for using all formats supported by audiopcm.decoder and laws.decoder. It includes PCM 8/16/24/32 bits in both endians (8 bit data may be either signed or unsigned), 32-bit floats in both endians and 8-bit nonlinearly quantized data according to A-law or μ-law. In this case, proper decoder object must be created and connected to the filter output. This feature may be useful for loading raw data from files created by external applications. For internally generated data using of Reggae standard formats is recommended (it avoids additional conversions).

Looping the sound to make it continuous

This is the easy part. Looping feature is built into audio.output class. This feature is controlled with MMA_Sound_LoopedPlay attribute. When it is set to TRUE, audio.output, after encounering sound end, seeks to the start of stream and continues playback. Thanks to doublebuffering, there is no gap in playback, assuming seek is done fast enough (which is true for memory streams and file streams on local storage media). Then, in case of our sine wave, looping is seamless. As mentioned above, the loop should not be too short. For memory streams 0.1 second is enough, for disk based streams 0.5 second would be safe.

The MMA_Sound_LoopedPlay attribute may be either passed to NewObject() or set later with SetAttrs(), or MediaSetPort() on audio.output instance input port. The example code uses the second approach.