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 + +#ifdef GLK_MODULE_SOUND +#include +#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 #include #include +#include #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();