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();