rnd-20040820-1-src
[rocksndiamonds.git] / src / game_em / sound.c
diff --git a/src/game_em/sound.c b/src/game_em/sound.c
new file mode 100644 (file)
index 0000000..6740728
--- /dev/null
@@ -0,0 +1,342 @@
+/* 2000-08-10T17:39:15Z
+ *
+ * handle sounds in emerald mine
+ */
+
+#include "../libgame/platform.h"
+
+
+#if defined(TARGET_X11)
+
+#if defined(PLATFORM_LINUX) || defined(PLATFORM_BSD)
+
+#ifdef PLATFORM_LINUX
+#include <sys/soundcard.h>
+#endif
+
+#ifdef PLATFORM_BSD
+#include <soundcard.h>
+#endif
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "global.h"
+#include "sample.h"
+
+static char audioname[] = "/dev/audio";
+
+static const int sound_priority[SAMPLE_MAX] = {
+       SAMPLE_exit, SAMPLE_die, SAMPLE_time, SAMPLE_boom, SAMPLE_tick,
+       SAMPLE_collect, SAMPLE_roll, SAMPLE_push, SAMPLE_dynamite, SAMPLE_press,
+       SAMPLE_door, SAMPLE_dirt, SAMPLE_blank, SAMPLE_android, SAMPLE_ball,
+       SAMPLE_grow, SAMPLE_squash, SAMPLE_crack, SAMPLE_slurp, SAMPLE_drip,
+       SAMPLE_wonder, SAMPLE_wheel, SAMPLE_stone, SAMPLE_spring, SAMPLE_diamond,
+       SAMPLE_nut, SAMPLE_bug, SAMPLE_tank, SAMPLE_eater, SAMPLE_alien,
+       SAMPLE_acid
+};
+
+int sound_thread(void)
+{
+       int audio_fd; /* file descriptor of /dev/audio or -1 if not open */
+       int audio_format;
+       int sample_rate;
+       int fragment_size;
+       unsigned char *audio_buffer; /* actual buffer pumped to /dev/audio */
+       short *mix_buffer;
+
+       char sound_play[SAMPLE_MAX]; /* if set, we should be playing these sounds */
+       long sound_pos[SAMPLE_MAX]; /* position in the sound */
+       int mix_play[MIXER_MAX]; /* which sounds we have chosen to mix (calculated each time) */
+       int mix_count;
+
+       int i;
+
+loop:
+       audio_fd = -1;
+       audio_format = AUDIO_ULAW; /* defaults for non-OSS /dev/audio */
+       sample_rate = 8000;
+       fragment_size = 256;
+       audio_buffer = 0;
+       mix_buffer = 0;
+       mix_count = 0;
+
+       memset(sound_play, 0, sizeof(sound_play)); /* not playing any sounds */
+
+       for(;;) {
+               for(;;) {
+
+/* pick sounds to play, if any */
+                       if(sound_play[SAMPLE_exit] || sound_play[SAMPLE_die]) sound_play[SAMPLE_boom] = 0; /* no explosions if player goes home */
+                       mix_count = 0;
+                       for(i = 0; i < SAMPLE_MAX; i++) {
+                               if(sound_play[sound_priority[i]]) {
+                                       mix_play[mix_count++] = sound_priority[i];
+                                       if(mix_count == MIXER_MAX) break; /* cant mix too many sounds at once */
+                               }
+                       }
+
+/* check for incoming messages */
+                       if(mix_count || audio_fd != -1) { /* dont block if we are playing sounds */
+                               fd_set rfds;
+                               struct timeval tv;
+                               FD_ZERO(&rfds);
+                               FD_SET(sound_pipe[0], &rfds);
+                               tv.tv_sec = 0;
+                               tv.tv_usec = 0; /* (900000 * fragment_size / sample_rate) */
+                               i = select(sound_pipe[0] + 1, &rfds, 0, 0, &tv); /* dont block */
+                               if(i == -1) {
+                                       fprintf(stderr, "%s: %s: %s\n", progname, "select failed", strerror(errno));
+                                       goto fail;
+                               }
+                               if(i == 0) break; /* no messages */
+                       }
+
+/* get a message and start a sound */
+                       i = read(sound_pipe[0], &play, sizeof(play));
+                       if(i == -1) {
+                               fprintf(stderr, "%s: %s: %s\n", progname, "read failed", strerror(errno));
+                               goto fail;
+                       }
+                       if(i == 0) {
+                               fprintf(stderr, "%s: %s: %s\n", progname, "read sound", "Broken pipe");
+                               goto fail;
+                       }
+                       if(i != sizeof(play)) {
+                               fprintf(stderr, "%s: %s\n", progname, "bad message length");
+                               goto fail;
+                       }
+                       for(i = 0; i < SAMPLE_MAX; i++) {
+                               if(play[i]) {
+                                       sound_play[i] = 1; /* play this sound */
+                                       sound_pos[i] = 0; /* start it from the start */
+                               }
+                       }
+               }
+
+/* open the audio device if there are sounds to play */
+               if(mix_count && audio_fd == -1) {
+                       audio_fd = open(audioname, O_WRONLY);
+                       if(audio_fd == -1) goto reset;
+#ifdef OPEN_SOUND_SYSTEM
+                       i = 0x00020008;
+                       if(ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &i) == -1) {
+                               fprintf(stderr, "%s: \"%s\": %s (%d): %s\n", progname, audioname, "unable to set fragment size", 512, strerror(errno));
+                               goto reset;
+                       }
+                       if(ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &i) == -1) {
+                               fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, audioname, "unable to query audio format", strerror(errno));
+                               goto reset;
+                       }
+                       audio_format = (i & AFMT_U8) ? AFMT_U8 : AFMT_MU_LAW; /* prefer 8 bit unsigned and fall back on mu-law */
+                       i = audio_format;
+                       if(ioctl(audio_fd, SNDCTL_DSP_SETFMT, &i) == -1) {
+                               fprintf(stderr, "%s: \"%s\": %s (%d): %s\n", progname, audioname, "unable to set audio format", audio_format, strerror(errno));
+                               goto reset;
+                       }
+                       if(i == AFMT_MU_LAW) {
+                               audio_format = AUDIO_ULAW;
+                       } else if(i == AFMT_U8) {
+                               audio_format = AUDIO_U8;
+                       } else {
+                               fprintf(stderr, "%s: \"%s\": %s (%d)\n", progname, audioname, "audio format required by device not supported", i);
+                               goto reset;
+                       }
+                       i = 1;
+                       if(ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &i) == -1) {
+                               fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, audioname, "unable to set channels to mono", strerror(errno));
+                               goto reset;
+                       }
+                       if(i != 1) {
+                               fprintf(stderr, "%s: \"%s\": %s (%d)\n", progname, audioname, "channels required by device not supported", i);
+                               goto reset;
+                       }
+                       i = 8000;
+                       if(ioctl(audio_fd, SNDCTL_DSP_SPEED, &i) == -1) {
+                               fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, audioname, "unable to set sampling rate", strerror(errno));
+                               goto reset;
+                       }
+                       sample_rate = i;
+                       if(ioctl(audio_fd, SNDCTL_DSP_GETBLKSIZE, &i) == -1) {
+                               fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, audioname, "unable to get block size", strerror(errno));
+                               goto reset;
+                       }
+                       fragment_size = i;
+#else
+                       if(fcntl(audio_fd, F_SETFL, O_NONBLOCK) == -1) {
+                               fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, audioname, "unable to make audio non blocking", strerror(errno));
+                               goto reset;
+                       }
+#endif /* OPEN_SOUND_SYSTEM */
+                       audio_buffer = malloc(fragment_size * sizeof(*audio_buffer));
+                       if(audio_buffer == 0) {
+                               fprintf(stderr, "%s: %s (%d): %s\n", progname, "unable to malloc audio buffer", fragment_size * sizeof(*audio_buffer), strerror(errno));
+                               goto fail;
+                       }
+                       mix_buffer = malloc(fragment_size * sizeof(*mix_buffer));
+                       if(mix_buffer == 0) {
+                               fprintf(stderr, "%s: %s (%d): %s\n", progname, "unable to malloc mixing buffer", fragment_size * sizeof(*mix_buffer), strerror(errno));
+                               goto fail;
+                       }
+               }
+
+/* close the audio device if no sounds are playing */
+               if(mix_count == 0 && audio_fd != -1) {
+                       close(audio_fd);
+                       free(audio_buffer);
+                       free(mix_buffer);
+                       audio_fd = -1;
+                       audio_buffer = 0;
+                       mix_buffer = 0;
+               }
+
+/* if we are playing sounds and the audio device is open, mix them */
+               if(mix_count && audio_fd != -1) {
+
+                       memset(mix_buffer, 0, fragment_size * sizeof(*mix_buffer)); /* prepare mix buffer */
+
+                       for(i = 0; i < mix_count; i++) {
+                               register short *mix_ptr = mix_buffer;
+                               register short *sound_ptr = sound_data[mix_play[i]] + sound_pos[mix_play[i]];
+                               register long count = sound_length[mix_play[i]] - sound_pos[mix_play[i]];
+                               if(count > fragment_size) count = fragment_size;
+                               while(count--) *mix_ptr++ += *sound_ptr++; /* mix the sounds in */
+                       }
+                       switch(audio_format) {
+                       case AUDIO_ULAW:
+                               for(i = 0; i < fragment_size; i++) audio_buffer[i] = linear_to_ulaw[mix_buffer[i] + 32768];
+                               break;
+                       case AUDIO_U8:
+                               for(i = 0; i < fragment_size; i++) audio_buffer[i] = (mix_buffer[i] + 32768) >> 8;
+                               break;
+                       }
+
+/* advance sound pointers */
+                       for(i = 0; i < SAMPLE_MAX; i++) {
+                               if(sound_play[i]) {
+                                       if(sound_pos[i] + fragment_size < sound_length[i]) {
+                                               sound_pos[i] += fragment_size;
+                                       } else {
+                                               sound_play[i] = 0;
+                                       }
+                               }
+                       }
+
+/* send the data to the audio device */
+                       i = write(audio_fd, audio_buffer, fragment_size);
+                       if(i == -1) {
+                               fprintf(stderr, "%s: %s: %s\n", progname, "write error", strerror(errno));
+                               goto reset;
+                       }
+                       if(i != fragment_size) {
+                               fprintf(stderr, "%s: %s\n", progname, "bad write length");
+                               goto reset;
+                       }
+               }
+       } /* for */
+
+reset:
+       if(audio_fd != -1) close(audio_fd);
+       if(audio_buffer) free(audio_buffer);
+       if(mix_buffer) free(mix_buffer);
+       goto loop; /* back to top */
+
+fail:
+       if(audio_fd != -1) close(audio_fd);
+       if(audio_buffer) free(audio_buffer);
+       if(mix_buffer) free(mix_buffer);
+       return(0);
+}
+
+int read_sample(char *name, short **data, long *length)
+{
+       int result;
+       FILE *file = 0;
+       short *dataptr = 0;
+       long datalength;
+
+       int i, actual, ch;
+       unsigned char buffer[24];
+       unsigned long temp;
+
+       file = fopen(name, "rb");
+       if(file == 0) {
+               fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, name, "open error", strerror(errno));
+               result = 1; goto fail;
+       }
+       actual = fread(buffer, 1, 24, file);
+       if(actual == -1) {
+               fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, name, "read error", strerror(errno));
+               result = 1; goto fail;
+       }
+       if(actual < 24) {
+               fprintf(stderr, "%s: \"%s\": %s\n", progname, name, "premature eof");
+               result = 1; goto fail;
+       }
+       temp = buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]; /* magic */
+       if(temp != 0x2e736e64) {
+               fprintf(stderr, "%s: \"%s\": %s\n", progname, name, "unrecognized file format");
+               result = 1; goto fail;
+       }
+       temp = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]; /* header length */
+       if(temp < 24) {
+               fprintf(stderr, "%s: \"%s\": %s\n", progname, name, "bad header length");
+               result = 1; goto fail;
+       }
+       actual = temp;
+       for(i = 24; i < actual; i++) { /* skip the rest of the header */
+               ch = fgetc(file);
+               if(ch == EOF) break;
+       }
+
+       temp = buffer[8] << 24 | buffer[9] << 16 | buffer[10] << 8 | buffer[11]; /* data length */
+       datalength = temp;
+       temp = buffer[12] << 24 | buffer[13] << 16 | buffer[14] << 8 | buffer[15]; /* encoding */
+       if(temp != 1) {
+               fprintf(stderr, "%s: \"%s\": %s (%ld != 1)\n", progname, name, "bad encoding type", temp);
+               result = 1; goto fail;
+       }
+       temp = buffer[16] << 24 | buffer[17] << 16 | buffer[18] << 8 | buffer[19]; /* sample rate */
+       if(temp != 8000) {
+               fprintf(stderr, "%s: \"%s\": %s (%ld != 8000)\n", progname, name, "bad sample rate", temp);
+               result = 1; goto fail;
+       }
+       temp = buffer[20] << 24 | buffer[21] << 16 | buffer[22] << 8 | buffer[23]; /* channels */
+       if(temp != 1) {
+               fprintf(stderr, "%s: \"%s\": %s (%ld != 1)\n", progname, name, "unsupported channels", temp);
+               result = 1; goto fail;
+       }
+
+       dataptr = malloc(datalength * sizeof(*dataptr));
+       if(dataptr == 0) {
+               fprintf(stderr, "%s: \"%s\": %s (%ld): %s\n", progname, name, "unable to malloc buffer", datalength * sizeof(*dataptr), strerror(errno));
+               result = 1; goto fail;
+       }
+
+       for(i = 0; i < datalength; i++) {
+               ch = fgetc(file);
+               if(ch == EOF) break;
+               dataptr[i] = ulaw_to_linear[ch];
+       }
+       fclose(file);
+       file = 0;
+
+       *data = dataptr;
+       *length = datalength;
+       result = 0;
+fail:
+       if(file) fclose(file);
+       return(result);
+}
+
+#endif /* defined(PLATFORM_LINUX) || defined(PLATFORM_BSD) */
+
+#endif