diff -ur xglk/Makefile xglk+hack/Makefile
--- xglk/Makefile       Sat Apr 15 21:13:28 2000
+++ xglk+hack/Makefile  Wed Aug  8 22:53:51 2001
@@ -37,8 +37,9 @@
# definitions for SGI / Irix
#SYSTEMFLAGS =

-# definitions for Linux
-#SYSTEMFLAGS =
+# definitions for Linux. _BSD_SOURCE may be necessary for struct timezone.
+# At least that is my experience with Debian and glibc 2.2.3.
+SYSTEMFLAGS = -D_BSD_SOURCE

# --------------------

@@ -54,13 +55,13 @@
# directories could be just about anywhere. Sigh.

# for Debian Linux
-#XINCLUDE = -I/usr/X11R6/include/X11
-#XLIB = -L/usr/X11R6/lib -lX11
-
-# for Red Hat Linux
XINCLUDE = -I/usr/X11R6/include/X11
XLIB = -L/usr/X11R6/lib -lX11

+# for Red Hat Linux
+#XINCLUDE = -I/usr/X11R6/include/X11
+#XLIB = -L/usr/X11R6/lib -lX11
+
# for SparcStation / Solaris
#XINCLUDE = -I/usr/openwin/include
#XLIB = -R/usr/openwin/lib -L/usr/openwin/lib/ -lX11
@@ -78,15 +79,21 @@
# If there is no JPEG lib available, uncomment this line.
# JPEGFLAG = -DNO_JPEG_AVAILABLE

+# definitions for the MikMod library. Will MIKMODCFLAGS have to be included
+# in the Make.xglk file? How?
+MIKMODCFLAGS = \$$\(shell libmikmod-config --cflags\)
+MIKMODLDFLAGS = \$$\(shell libmikmod-config --ldadd\)
+MIKMODLIB = \$$\(shell libmikmod-config --libs\)
+
# --------------------

# Pick a C compiler.
#CC = cc
CC = gcc

-CFLAGS = -O -ansi $(PNGFLAG) $(JPEGFLAG) $(PNGINCLUDE) $(JPEGINCLUDE) -Wall -Wmissing-prototypes -Wstrict-prototypes -Wno-unused -Wbad-function-cast $(SYSTEMFLAGS) $(XINCLUDE)
-LDFLAGS =
-LIBS = $(XLIB) $(PNGLIB) $(JPEGLIB) $(SYSTEMLIBS)
+CFLAGS = -O -ansi $(PNGFLAG) $(JPEGFLAG) $(PNGINCLUDE) $(JPEGINCLUDE) $(MIKMODINCLUDE) -Wall -Wmissing-prototypes -Wstrict-prototypes -Wno-unused -Wbad-function-cast $(SYSTEMFLAGS) $(XINCLUDE)
+LDFLAGS = $(MIKMODLDFLAGS)
+LIBS = $(XLIB) $(PNGLIB) $(JPEGLIB) $(MIKMODLIB) $(SYSTEMLIBS)

OBJS = main.o xglk.o xglk_vars.o xglk_prefs.o xglk_loop.o xglk_init.o \
  xglk_scrap.o xglk_msg.o xglk_key.o xglk_weggie.o xglk_pict.o \
diff -ur xglk/xg_event.c xglk+hack/xg_event.c
--- xglk/xg_event.c     Sun Jun  6 18:07:45 1999
+++ xglk+hack/xg_event.c        Wed Aug  8 22:51:49 2001
@@ -38,5 +38,5 @@

void glk_tick()
{
-  /* Nothing for us to do. */
+  gli_tick_schannels();
}
diff -ur xglk/xg_gestalt.c xglk+hack/xg_gestalt.c
--- xglk/xg_gestalt.c   Tue Jul 11 04:04:16 2000
+++ xglk+hack/xg_gestalt.c      Wed Aug  8 21:50:52 2001
@@ -91,7 +91,7 @@
  case gestalt_SoundVolume:
  case gestalt_SoundNotify:
  case gestalt_SoundMusic:
-    return FALSE;
+    return TRUE;

  case gestalt_Hyperlinks: {
    return TRUE;
diff -ur xglk/xg_internal.h xglk+hack/xg_internal.h
--- xglk/xg_internal.h  Thu Apr 13 02:51:23 2000
+++ xglk+hack/xg_internal.h     Thu Aug  9 22:36:45 2001
@@ -2,6 +2,11 @@
#define _XG_INTERNAL_H

#include <X11/X.h>
+
+#ifdef GLK_MODULE_SOUND
+#include <mikmod.h>
+#endif
+
#include "gi_dispa.h"

/* --- General declarations --- */
@@ -12,6 +17,7 @@
typedef struct glk_window_struct window_t;
typedef struct glk_stream_struct stream_t;
typedef struct glk_fileref_struct fileref_t;
+typedef struct glk_schannel_struct channel_t;
typedef struct stylehints_struct stylehints_t;

extern int gli_special_typable_table[keycode_MAXVAL+1];
@@ -131,6 +137,25 @@

extern int init_gli_filerefs(void);

+/* --- Sound channels --- */
+
+struct glk_schannel_struct {
+  glui32 rock;
+
+  MODULE *module;
+  glui32 snd;
+  glui32 vol;
+  glui32 notify;
+
+  gidispatch_rock_t disprock;
+  channel_t *chain_next, *chain_prev;
+};
+
+extern int init_gli_schannels(void);
+extern void exit_gli_schannels(void);
+extern void gli_tick_schannels(void);
+extern Bool gli_eventloop_schannels(void);
+
/* --- Styles --- */

typedef struct styleval_struct {
@@ -155,6 +180,18 @@
extern void gli_styles_compute(fontset_t *font, stylehints_t *hints);

/* --- Events --- */
+
+/* This is the granularity at which we check for timer events; measured
+   in microseconds. 1/20 second sounds good, but if we have sound support
+   this may be too long. The documentation is a bit vague, but does say
+   that we never need to do more than a few hundred updates per second.
+   Based on the MikMod example programs, I've decided to set it at 1/100
+   seconds. */
+#ifdef GLK_MODULE_SOUND
+#define TICKLENGTH (10000)
+#else
+#define TICKLENGTH (50000)
+#endif

extern void eventloop_setevent(glui32 type, window_t *win,
  glui32 val1, glui32 val2);
diff -ur xglk/xg_misc.c xglk+hack/xg_misc.c
--- xglk/xg_misc.c      Sun Jun  6 18:07:45 1999
+++ xglk+hack/xg_misc.c Wed Aug  8 21:49:04 2001
@@ -123,6 +123,8 @@
    (*func)();
  }

+  exit_gli_schannels();
+
  exit(1);
}

@@ -176,6 +178,8 @@
    return ((stream_t *)obj)->disprock;
  case gidisp_Class_Fileref:
    return ((fileref_t *)obj)->disprock;
+  case gidisp_Class_Schannel:
+    return ((channel_t *)obj)->disprock;
  default: {
      gidispatch_rock_t dummy;
      dummy.num = 0;
diff -ur xglk/xg_schan.c xglk+hack/xg_schan.c
--- xglk/xg_schan.c     Thu Jul 22 00:49:01 1999
+++ xglk+hack/xg_schan.c        Fri Aug 10 00:42:46 2001
@@ -1,61 +1,351 @@
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/time.h>
#include "xglk.h"
#include "xg_internal.h"

-/* The whole sound-channel situation is very simple for us;
-   we don't support it. */
-
#ifdef GLK_MODULE_SOUND

-schanid_t glk_schannel_create(glui32 rock)
+#define DEBUG_MODULE_SOUND 1
+
+/* TODO: The code assumes in several places that only the MOD player will
+         be active. Obviously this has to change when we add the ability to
+         play samples. */
+
+/* Partial sound support, courtesy of MikMod (http://www.mikmod.org) */
+
+#include "gi_blorb.h"
+
+#define giblorb_ID_MOD      (giblorb_make_id('M', 'O', 'D', ' '))
+
+static channel_t *gli_channellist = NULL;
+static struct timeval last_update = { 0, 0 };
+
+int init_gli_schannels()
{
-  return NULL;
+#if DEBUG_MODULE_SOUND
+  char *buf;
+#endif
+
+  gli_channellist = NULL;
+
+  MikMod_RegisterAllDrivers();
+  MikMod_RegisterAllLoaders();
+  if (MikMod_Init("")) {
+    fprintf(stderr, "Could not initialize sound, reason: %s\n",
+      MikMod_strerror(MikMod_errno));
+    return FALSE;
+  }
+
+#if DEBUG_MODULE_SOUND
+  printf("MikMod version %ld.%ld.%ld\n",
+    LIBMIKMOD_VERSION_MAJOR,
+    LIBMIKMOD_VERSION_MINOR,
+    LIBMIKMOD_REVISION);
+  buf = MikMod_InfoDriver();
+  if (buf) {
+    printf("Available drivers:\n%s\n", buf);
+    free(buf);
+  }
+  printf("Using: %s\n", md_driver->Name);
+#endif
+
+  return TRUE;
+}
+
+void exit_gli_schannels()
+{
+  MikMod_Exit();
+}
+
+#if DEBUG_MODULE_SOUND
+static long last_message_sec = 0;
+static long tick_count = 0;
+static long update_count = 0;
+static long pot_update_count = 0;
+#endif
+
+void gli_tick_schannels()
+{
+  struct timeval curtime;
+  struct timezone tz;
+  long difftime;
+
+  /* We need to call MikMod_Update() often enough, or sound will be choppy.
+
+     Damn, this is much harder than I thought. I want there to be a nice and
+     steady stream of potential updates, and it works fine most of the time.
+
+     The scrolling map in the Zeta Space demo is a problem. Here I will get
+     large number of ticks, meaning that the VM is under heavier-than-usual
+     load, but relatively few potential updates.
+
+     Despite several rewrites, I have not been able to do anything about
+     this. By the time it is discovered that too much time has passed since
+     the last update, it's already too late to compensate for it.
+
+     I guess we have a bottleneck somewhere in the graphics handling. Perhaps
+     it just isn't possible to get this right without a separate thread... */
+
+  gettimeofday(&curtime, &tz);
+
+#if DEBUG_MODULE_SOUND
+  tick_count++;
+#endif
+
+  if (curtime.tv_sec - last_update.tv_sec <= 1) {
+    if (curtime.tv_sec == last_update.tv_sec)
+      difftime = curtime.tv_usec - last_update.tv_usec;
+    else
+      difftime = curtime.tv_usec + (1000000 - last_update.tv_usec);
+  } else
+    difftime = TICKLENGTH;
+
+  if (difftime >= (9 * TICKLENGTH) / 10) {
+    if (Player_Active()) {
+      MikMod_Update();
+#if DEBUG_MODULE_SOUND
+      update_count++;
+#endif
+    }
+
+    last_update.tv_sec = curtime.tv_sec;
+    last_update.tv_usec = curtime.tv_usec;
+#if DEBUG_MODULE_SOUND
+    pot_update_count++;
+#endif
+  }
+
+#if DEBUG_MODULE_SOUND
+  if (curtime.tv_sec - last_message_sec >= 20) {
+    fprintf(stderr,
+      "%ld.%06ld: %ld ticks, %ld (%ld) updates\n",
+      curtime.tv_sec, curtime.tv_usec, tick_count, pot_update_count,
+      update_count);
+    tick_count = 0;
+    update_count = 0;
+    pot_update_count = 0;
+    last_message_sec = curtime.tv_sec;
+  }
+#endif
+}
+
+/* It is assumed that this function is only called from the event loop.
+   Break the assumption if you like, but don't blame me if you lose sound
+   events... */
+
+Bool gli_eventloop_schannels()
+{
+  channel_t *chan = gli_channellist;
+
+  gli_tick_schannels();
+
+  if (Player_Active())
+    return FALSE;
+
+  while (chan) {
+    if (chan->module) {
+      Player_Free(chan->module);
+      chan->module = NULL;
+      if (chan->notify != 0) {
+        eventloop_setevent(evtype_SoundNotify, NULL, chan->snd, chan->notify);
+        chan->notify = 0;
+        return TRUE;
+      }
+    }
+    chan = chan->chain_next;
+  }
+
+  return FALSE;
}

-void glk_schannel_destroy(schanid_t chan)
+channel_t *glk_schannel_create(glui32 rock)
{
+  channel_t *chan = (channel_t *)malloc(sizeof(channel_t));
+
+  if (!chan)
+    return NULL;
+
+  chan->rock = rock;
+  chan->module = NULL;
+  chan->vol = 0x10000;
+
+  chan->chain_prev = NULL;
+  chan->chain_next = gli_channellist;
+  gli_channellist = chan;
+  if (chan->chain_next) {
+    chan->chain_next->chain_prev = chan;
+  }
+
+  if (gli_register_obj)
+    chan->disprock = (*gli_register_obj)(chan, gidisp_Class_Schannel);
+  else
+    chan->disprock.ptr = NULL;
+
+  return chan;
+}
+
+void glk_schannel_destroy(channel_t *chan)
+{
+  channel_t *prev, *next;
+
+  if (!chan) {
+    gli_strict_warning("schannel_destroy: invalid id.");
+    return;
+  }
+
+  if (gli_unregister_obj)
+    (*gli_unregister_obj)(chan, gidisp_Class_Schannel, chan->disprock);
+
+  if (chan->module) {
+    Player_Free(chan->module);
+    chan->module = NULL;
+  }
+
+  prev = chan->chain_prev;
+  next = chan->chain_next;
+  chan->chain_prev = NULL;
+  chan->chain_next = NULL;
+
+  if (prev)
+    prev->chain_next = next;
+  else
+    gli_channellist = next;
+  if (next)
+    next->chain_prev = prev;
+
+  free(chan);
}

-schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
+channel_t *glk_schannel_iterate(channel_t *chan, glui32 *rock)
{
-  if (rockptr)
-    *rockptr = 0;
+  if (!chan) {
+    chan = gli_channellist;
+  } else {
+    chan = chan->chain_next;
+  }
+
+  if (chan) {
+    if (rock)
+      *rock = chan->rock;
+    return chan;
+  }
+
+  if (rock)
+    *rock = 0;
  return NULL;
}

-glui32 glk_schannel_get_rock(schanid_t chan)
+glui32 glk_schannel_get_rock(channel_t *chan)
{
-  gli_strict_warning("schannel_get_rock: invalid id.");
-  return 0;
+  if (!chan) {
+    gli_strict_warning("schannel_get_rock: invalid id.");
+    return 0;
+  }
+  return chan->rock;
}

glui32 glk_schannel_play(schanid_t chan, glui32 snd)
{
-  gli_strict_warning("schannel_play: invalid id.");
-  return 0;
+  /* Error messages will be slightly wrong, but I'm lazy... */
+  return glk_schannel_play_ext(chan, snd, 1, 0);
}

glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats,
    glui32 notify)
{
-  gli_strict_warning("schannel_play_ext: invalid id.");
-  return 0;
+  FILE *fl;
+  long pos;
+  glui32 chunktype;
+
+  if (!chan) {
+    gli_strict_warning("schannel_play_ext: invalid id.");
+    return 0;
+  }
+
+  /* Another channel is already playing? */
+  if (!chan->module && Player_Active()) {
+    gli_strict_warning("schannel_play_exit: player can only handle one MOD at a time");
+    return 0;
+  }
+
+  /* TODO: Add picture_find()-style reading and caching, but that's beyond
+           the scope of this implementation. */
+  if (!xres_is_resource_map()) {
+    gli_strict_warning("schannel_play_ext: no resource map.");
+    return 0;
+  }
+
+  xres_get_resource(giblorb_ID_Snd, snd, &fl, &pos, NULL, &chunktype);
+
+  if (!fl) {
+    gli_strict_warning("schannel_play_ext: internal error - no file pointer.");
+    return 0;
+  }
+
+  /* MikMod can play samples. This may or may not include AIFF, but at
+     present I only have test data for MOD. I'm not even going to try and
+     implement SONG... */
+  if (chunktype != giblorb_ID_MOD) {
+    gli_strict_warning("schannel_play_ext: unsupported sound format");
+    return 0;
+  }
+
+  if (chan->module) {
+    Player_Free(chan->module);
+    chan->module = NULL;
+  }
+
+  if (repeats != 0) {
+    fseek(fl, pos, 0);
+    chan->module = Player_LoadFP(fl, 64, 0);
+    chan->snd = snd;
+    chan->notify = notify;
+    if (!chan->module) {
+      gli_strict_warning("schannel_play_ext: module loader failure");
+      return 0;
+    }
+
+    /* For now, only handle infinite looping. Laziness strikes again... */
+    if (repeats == -1)
+      chan->module->wrap = 1;
+
+    Player_Start(chan->module);
+    Player_SetVolume(chan->vol / 512);
+  }
+
+  return 1;
}

void glk_schannel_stop(schanid_t chan)
{
-  gli_strict_warning("schannel_stop: invalid id.");
+  if (!chan) {
+    gli_strict_warning("schannel_stop: invalid id.");
+    return;
+  }
+
+  if (chan->module) {
+    Player_Free(chan->module);
+    chan->module = NULL;
+  }
}

void glk_schannel_set_volume(schanid_t chan, glui32 vol)
{
-  gli_strict_warning("schannel_set_volume: invalid id.");
+  if (!chan) {
+    gli_strict_warning("schannel_set_volume: invalid id.");
+    return;
+  }
+
+  chan->vol = vol;
+  if (chan->module)
+    Player_SetVolume(chan->vol / 512);
}

void glk_sound_load_hint(glui32 snd, glui32 flag)
{
-  gli_strict_warning("schannel_sound_load_hint: invalid id.");
+  /* I doubt this will make any difference, so make it a no-op for now. */
}

#endif /* GLK_MODULE_SOUND */
diff -ur xglk/xglk.c xglk+hack/xglk.c
--- xglk/xglk.c Thu Apr 13 05:00:50 2000
+++ xglk+hack/xglk.c    Wed Aug  8 21:49:04 2001
@@ -58,6 +58,8 @@
    return FALSE;
  if (!init_gli_filerefs())
    return FALSE;
+  if (!init_gli_schannels())
+    return FALSE;
  if (!init_gli_windows())
    return FALSE;

diff -ur xglk/xglk_loop.c xglk+hack/xglk_loop.c
--- xglk/xglk_loop.c    Sun Jun  6 18:07:45 1999
+++ xglk+hack/xglk_loop.c       Thu Aug  9 23:35:38 2001
@@ -7,10 +7,6 @@
#include "xglk.h"
#include "xg_internal.h"

-/* This is the granularity at which we check for timer events; measured
-   in microseconds. 1/20 second sounds good. */
-#define TICKLENGTH (50000)
-
static event_t *eventloop_event = NULL;
static struct timeval lasttime = {0, 0};

@@ -78,6 +74,9 @@
  }

  while (ev->type == evtype_None) {
+
+    if (gli_eventloop_schannels())
+      continue;

    if (xio_any_invalid) {
      xglk_redraw();