8: Sound

As with graphics, so with sound. Sounds, however, obviously don't appear in windows. To play a sound in Glk, you must first create a sound channel to hold it. This is an entirely new class of opaque objects; there are create and destroy and iterate and get_rock functions for channels, just as there are for windows and streams and filerefs.

A channel can be playing exactly one sound at a time. If you want to play more than one sound simultaneously, you need more than one sound channel. On the other hand, a single sound can be played on several channels at the same time, or overlapping itself.

Sound is an optional capability in Glk.

8.1: Sound Resources

As with images, sounds are kept in resources, and your program does not have to worry about the formatting or storage. A resource is referred to by an integer identifier.

A resource can theoretically contain any kind of sound data, of any length. A resource can even be infinitely long. [This would be represented by some sound encoding with a built-in repeat-forever flag -- but that is among the details which are hidden from you.] A resource can also contain two or more channels of sound (stereo data). Do not confuse such in-sound channels with Glk sound channels. A single Glk sound channel suffices to play any sound, even stereo sounds.

[Again, Blorb is the official resource-storage format of Glk. Sounds in Blorb files can be encoded as Ogg, AIFF, or MOD. See the Blorb specification for details.]

8.2: Creating and Destroying Sound Channels

schanid_t glk_schannel_create(glui32 rock);
schanid_t glk_schannel_create_ext(glui32 rock, glui32 volume);

This creates a sound channel, about as you'd expect.

Remember that it is possible that the library will be unable to create a new channel, in which case glk_schannel_create() will return NULL.

When you create a channel using glk_schannel_create(), it has full volume, represented by the value 0x10000. Half volume would be 0x8000, three-quarters volume would be 0xC000, and so on. A volume of zero represents silence. The glk_schannel_create_ext() call lets you create a channel with the volume already set at a given level.

You can overdrive the volume of a channel by setting a volume greater than 0x10000. However, this is not recommended; the library may be unable to increase the volume past full, or the sound may become distorted. You should always create sound resources with the maximum volume you will need, and then reduce the volume when appropriate using the channel-volume calls.

[Mathematically, these volume changes should be taken as linear multiplication of a waveform represented as linear samples. As I understand it, linear PCM encodes the sound pressure, and therefore a volume of 0x8000 should represent a 6 dB drop.]

Not all libraries support glk_schannel_create_ext(). You should test the gestalt_Sound2 selector before you rely on it; see section 8.5, "Testing for Sound Capabilities".

void glk_schannel_destroy(schanid_t chan);

Destroy the channel. If the channel is playing a sound, the sound stops immediately (with no notification event).

8.3: Playing Sounds

glui32 glk_schannel_play(schanid_t chan, glui32 snd)

Begin playing the given sound on the channel. If the channel was already playing a sound (even the same one), the old sound is stopped (with no notification event).

This returns 1 if the sound actually started playing, and 0 if there was any problem. [The most obvious problem is if there is no sound resource with the given identifier. But other problems can occur. For example, the MOD-playing facility in a library might be unable to handle two MODs at the same time, in which case playing a MOD resource would fail if one was already playing.]

glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify);

This works the same as glk_schannel_play(), but lets you specify additional options. glk_schannel_play(chan, snd) is exactly equivalent to glk_schannel_play_ext(chan, snd, 1, 0).

The repeats value is the number of times the sound should be repeated. A repeat value of -1 (or rather 0xFFFFFFFF) means that the sound should repeat forever. A repeat value of 0 means that the sound will not be played at all; nothing happens. (Although a previous sound on the channel will be stopped, and the function will return 1.)

The notify value should be nonzero in order to request a sound notification event. If you do this, when the sound is completed, you will get an event with type evtype_SoundNotify. The window will be NULL, val1 will be the sound's resource id, and val2 will be the nonzero value you passed as notify.

If you request sound notification, and the repeat value is greater than one, you will get the event only after the last repetition. If the repeat value is 0 or -1, you will never get a notification event at all. Similarly, if the sound is stopped or interrupted, or if the channel is destroyed while the sound is playing, there will be no notification event.

Not all libraries support sound notification. You should test the gestalt_Sound2 selector before you rely on it; see section 8.5, "Testing for Sound Capabilities".

Note that you can play a sound on a channel whose volume is zero. This has no audible result, unless you later change the volume; but it produces notifications as usual. You can also play a sound on a paused channel; the sound is paused immediately, and does not progress.

glui32 glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray, glui32 soundcount, glui32 notify);

This works the same as glk_schannel_play_ext(), except that you can specify more than one sound. The channel references and sound resource numbers are given as two arrays, which must be the same length. The notify argument applies to all the sounds; the repeats value for all the sounds is 1.

All the sounds will begin at exactly the same time.

This returns the number of sounds that began playing correctly. (This will be a number from 0 to soundcount.)

[If the notify argument is nonzero, you will get a separate sound notification event as each sound finishes. They will all have the same val2 value.]

[Note that you have to supply chancount and soundcount as separate arguments, even though they are required to be the same. This is an awkward consequence of the way array arguments are dispatched in Glulx.]

void glk_schannel_stop(schanid_t chan);

Stops any sound playing in the channel. No notification event is generated, even if you requested one. If no sound is playing, this has no effect.

void glk_schannel_pause(schanid_t chan);

Pause any sound playing in the channel. This does not generate any notification events. If the channel is already paused, this does nothing.

New sounds started in a paused channel are paused immediately.

A volume change in progress is not paused, and may proceed to completion, generating a notification if appropriate.

void glk_schannel_unpause(schanid_t chan);

Unpause the channel. Any paused sounds begin playing where they left off. If the channel is not already paused, this does nothing.

[This means, for example, that you can pause a channel that is currently not playing any sounds. If you then add a sound to the channel, it will not start playing; it will be paused at its beginning. If you later unpause the channel, the sound will commence.]

void glk_schannel_set_volume(schanid_t chan, glui32 vol);
void glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32 notify);

Sets the volume in the channel, from 0 (silence) to 0x10000 (full volume). Again, you can overdrive the volume by setting a value greater than 0x10000, but this is not recommended.

If the duration is zero, the change is immediate. Otherwise, the change begins immediately, and occurs smoothly over the next duration milliseconds.

The notify value should be nonzero in order to request a volume notification event. If you do this, when the volume change is completed, you will get an event with type evtype_VolumeNotify. The window will be NULL, val1 will be zero, and val2 will be the nonzero value you passed as notify.

The glk_schannel_set_volume() does not include duration and notify values. Both are assumed to be zero: immediate change, no notification.

You can call these functions between sounds, or while a sound is playing. However, a zero-duration change while a sound is playing may produce unpleasant clicks.

At most one volume change can be occurring on a sound channel at any time. If you call one of these functions while a previous volume change is in progress, the previous change is interrupted. The beginning point of the new volume change should be wherever the previous volume change was interrupted (rather than the previous change's beginning or ending point).

Not all libraries support thse functions. You should test the appropriate gestalt selectors before you rely on them; see section 8.5, "Testing for Sound Capabilities".

void glk_sound_load_hint(glui32 snd, glui32 flag);

This gives the library a hint about whether the given sound should be loaded or not. If the flag is nonzero, the library may preload the sound or do other initialization, so that glk_schannel_play() will be faster. If the flag is zero, the library may release memory or other resources associated with the sound. Calling this function is always optional, and it has no effect on what the library actually plays.

8.4: Other Sound Channel Functions

schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr);

This function can be used to iterate through the list of all open channels. See section 1.6.2, "Iterating Through Opaque Objects".

As that section describes, the order in which channels are returned is arbitrary.

glui32 glk_schannel_get_rock(schanid_t chan);

This retrieves the channel's rock value. See section 1.6.1, "Rocks".

8.5: Testing for Sound Capabilities

Before calling Glk sound functions, you should use the following gestalt selectors.

glui32 res;
res = glk_gestalt(gestalt_Sound2, 0);

This returns 1 if the overall suite of sound functions is available. This includes all the functions defined in this chapter. It also includes the capabilities described below under gestalt_SoundMusic, gestalt_SoundVolume, and gestalt_SoundNotify.

If you are writing a C program, there is an additional complication. A library which does not support sound may not implement the sound functions at all. Even if you put gestalt tests around your sound calls, you may get link-time errors. If the glk.h file is so old that it does not declare the sound functions and constants, you may even get compile-time errors.

To avoid this, you can perform a preprocessor test for the existence of GLK_MODULE_SOUND2. If this is defined, so are all the functions and constants described in this section. If not, not.

Earlier versions of the Glk spec defined separate selectors for various optional capabilities. This has proven to be an unnecessarily confusing strategy, and is no longer used. The following selectors still exist, but you should not need to test them; the gestalt_Sound2 selector covers all of them.

res = glk_gestalt(gestalt_Sound, 0);

This returns 1 if the older (pre-0.7.3) suite of sound functions is available. This includes glk_schannel_create(), glk_schannel_destroy(), glk_schannel_iterate(), glk_schannel_get_rock(), glk_schannel_play(), glk_schannel_play_ext(), glk_schannel_stop(), glk_schannel_set_volume(), and glk_sound_load_hint().

If this selector returns 0, you should not try to call these functions. They may have no effect, or they may cause a run-time error.

This selector is guaranteed to return 1 if gestalt_Sound2 does.

You can perform a preprocessor test for the existence of GLK_MODULE_SOUND. If this is defined, so are the functions listed above.

res = glk_gestalt(gestalt_SoundMusic, 0);

This returns 1 if the library is capable of playing music sound resources. If it returns 0, only sampled sounds can be played.["Music sound resources" means MOD songs -- the only music format that Blorb currently supports. The presence of this selector is, of course, an ugly hack. It is a concession to the current state of the Glk libraries, some of which can handle AIFF but not MOD sounds.]

This selector is guaranteed to return 1 if gestalt_Sound2 does.

res = glk_gestalt(gestalt_SoundVolume, 0);

This selector returns 1 if the glk_schannel_set_volume() function works. If it returns zero, glk_schannel_set_volume() has no effect.

This selector is guaranteed to return 1 if gestalt_Sound2 does.

res = glk_gestalt(gestalt_SoundNotify, 0);

This selector returns 1 if the library supports sound notification events. If it returns zero, you will never get such events.

This selector is guaranteed to return 1 if gestalt_Sound2 does.

Up to top Previous chapter Next chapter