Sound and music

The process of creating a sound channel, playing sound effects and music in it, and using entry points to manage it should look very familiar to those who've read the section on implementing graphics windows. Those who skipped straight to this section are strongly advised to read through the sections on graphics first, especially the bit about graphics window handling.

Also required reading is the section on Blorb, since you use Blorb to make sound files available to your game just as you do with image files. The sound formats currently supported by Glk and thus by Glulx Inform are AIFF and MOD. The former is an uncompressed sound format, a sort of cross-platform equivalent of the more familiar Microsoft WAV, producing files that, at the right settings, are sufficiently faithful to the original sound that they can be burned to an audio CD. (The drawback is that compared to a lossy format like MP3, they're quite huge.) MOD is quite different. MOD files are created with programs called trackers, in which you load in some short sound samples (a drumbeat, a piano note, a guitar string being plucked, a barking dog, you name it) and then compose a sort of sheet music, placing notes on a grid at the appropriate pitches, selecting a tempo, applying special effects and so forth. Since the file stores not the entire musical piece but merely the building blocks and the instructions for how to play it, MOD files are quite small. (They're also a lot of fun to put together and play around with. If you're using a Windows machine, I highly recommend the Modplug Tracker -- though naturally the site became radically less usable mere hours after this link was first posted. Sigh.)

So let's say that you're coding up an elevator, and you decide that when the player character walks into it, elevator music should play. The process is as follows. First, we need to create a sound channel, and give it a rock value 410 or above (in this case, we'll choose 410.) We drop the following lines into the program:

   Constant GG_MUSICCHAN_ROCK 410;
   Global gg_musicchan = 0; 

And while we're at it, let's add a global to store the music which should be playing at any given point:

   Global current_music = 0; 

And of course, at some point we need to pop over to the Blorb resource file and give the sound file a name we can use in our source code. Let's say that we feel like scoffing at trademark law and call our file "Muzak". How to indicate this will vary; if you're using iblorb, the line would be something like:

   SOUND Muzak elevmus2.mod 

With that preparation out of the way, we can now tend to the business of opening the sound channel and sending music to it. Opening it is easy: just drop the following line into Initialise():

   if (gg_musicchan == 0) {
     gg_musicchan = glk_schannel_create(GG_MUSICCHAN_ROCK);

(Customize this for your own programs in the obvious manner.) So, the channel is now open. Next up: playing sounds in it. This time the key command is "glk_schannel_play_ext()", which takes four arguments:

1) The name of the sound channel. Note that you can have multiple sound channels -- say, one for special effects, and one for music -- but on any platform, you'll eventually run into some limit. Playing multiple MODs at once is a particularly dubious prospect.

2) The name of the sound to be played (but see below)

3) The number of times the sound should be repeated. If you want it to repeat forever until you tell it to stop (or quit the game), put -1.

4) This should be 0, unless you want to send an evtype_SoundNotify to your HandleGlkEvent() routine when the sound finishes playing. For instance, you might want to implement a haunted house with spooky monster noises played at random. The code might look like this:

   [ HandleGlkEvent ev context new_sound;
      context = 0; ! suppress ignored warning
      switch (ev-->0) {
            new_sound = random(Howl, Screech, Growl);
            glk_schannel_play_ext(gg_musicchan, new_sound, 1, 1);

But now we're getting a bit ahead of ourselves, so let's back up.

As with drawing graphics to graphics windows, we don't want to just play a piece of music directly to a sound channel, especially if it's being looped. If we did, the player might have her character step into the elevator, then restore a saved game to a point where the character is in the middle of a war zone -- and elevator music will still be playing! To avoid these potential blunders, we use the global variable we created earlier, and write a routine like so:

   [ MyRestartMusicChannel;
      if (gg_musicchan) {
         if (current_music == 0) glk_schannel_stop(gg_musicchan);
         else glk_schannel_play_ext(gg_musicchan, current_music, -1, 0);

The code inside the braces plays whichever tune should currently be playing, or stops playing altogether if the current_music variable is set to 0; of course, if there is no sound channel open, it doesn't attempt to play anything. Now we can create an elevator object, with a before block like this:

   before [;
      Enter: current_music = Muzak;
             print "You step into the elevator. Today's music selection:
                Korn arranged for xylophone and pan flute.^";

And lastly, an IdentifyGlkObject() routine to make sure everything gets reset correctly after a restore or undo. If you already have one, conflate it with this one:

   [ IdentifyGlkObject phase type ref rock res id;
      if (phase == 0) { ! Zero out references to our objects.
         gg_musicchan = 0;

      if (phase == 1) { ! Nothing to do for sound in this phase.

      if (phase == 2) {
         ! Iterate through all the existing channels -- there should
         ! be either none or one -- and identify ours.
         id = glk_schannel_iterate(0, gg_arguments);
         while (id) {
            switch (gg_arguments-->0) {
               GG_MUSICCHAN_ROCK: gg_musicchan = id;
            id = glk_schannel_iterate(id, gg_arguments);
         ! Now, we just changed game state, so we might need to change
         ! the music or turn it off altogether.

The code in phase 2 does much the same thing here as the code in phase 1 did in the graphics window section; the difference is that a sound channel is neither a window, a stream nor a fileref, so phase 1 doesn't know how to deal with it.

And that should do it! Of course, if you try this at home, you may be in for a disappointment -- as of this writing, only a couple of platforms currently have MOD support (Windows and DOS, and the DOS MOD code is buggy.) But other platforms should get on the ball soon enough.

Next section: Mouse input
Or return to the table of contents