New module wwviaudio.
authorRusty Russell <rusty@rustcorp.com.au>
Thu, 9 Apr 2009 01:11:53 +0000 (10:41 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Thu, 9 Apr 2009 01:11:53 +0000 (10:41 +0930)
Also, add external depends to ogg_to_pcm.

ccan/ogg_to_pcm/_info.c
ccan/wwviaudio/_info.c [new file with mode: 0644]
ccan/wwviaudio/wwviaudio.c [new file with mode: 0644]
ccan/wwviaudio/wwviaudio.h [new file with mode: 0644]

index bf023c8b81b7bdf2af1f54fca6121d061aaec236..48d4da88beb8148ab2d8129a9fe7c7d8285b43c1 100644 (file)
@@ -32,8 +32,10 @@ int main(int argc, char *argv[])
        if (argc != 2)
                return 1;
 
-       if (strcmp(argv[1], "depends") == 0)
+       if (strcmp(argv[1], "depends") == 0) {
+               printf("libvorbis\n");
                return 0;
+       }
 
        if (strcmp(argv[1], "libs") == 0) {
                printf("vorbisfile\n");
diff --git a/ccan/wwviaudio/_info.c b/ccan/wwviaudio/_info.c
new file mode 100644 (file)
index 0000000..a25a227
--- /dev/null
@@ -0,0 +1,61 @@
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+
+/**
+ * wwviaudio - realtime playback and mixing of 16 bit signed PCM audio data.
+ *
+ * wwviaudio provides a set of functions for realtime playback and mixing
+ * of audio samples, e.g. music, sound effects, etc. as in a video game.
+ *
+ * Example:
+ *
+ *      something along these lines:
+ *
+ *      if (wwviaudio_initialize_portaudio() != 0)
+ *              bail_out_and_die();
+ *
+ *      You would probably use #defines or enums rather than bare ints...
+ *      wwviaudio_read_ogg_clip(1, "mysound1.ogg");
+ *      wwviaudio_read_ogg_clip(2, "mysound2.ogg");
+ *      wwviaudio_read_ogg_clip(3, "mysound3.ogg");
+ *      wwviaudio_read_ogg_clip(4, "mymusic.ogg");
+ *
+ *       ...
+ *
+ *      wwviaudio_play_music(4); <-- begins playing music in background, returns immediately 
+ *
+ *      while (program isn't done) {
+ *              do_stuff();
+ *              if (something happened)
+ *                      wwviaudio_add_sound(1);
+ *              if (something else happened)
+ *                      wwviaudio_add_sound(2);
+ *              time_passes();
+ *      }
+ *      
+ *      wwviaudio_cancel_all_sounds();
+ *      wwviaduio_stop_portaudio();
+ *
+ * Licence: LGPL (2 or any later version)
+ *
+ */
+int main(int argc, char *argv[])
+{
+       if (argc != 2)
+               return 1;
+
+       if (strcmp(argv[1], "depends") == 0) {
+               printf("ccan/ogg_to_pcm\n"
+                      "libvorbis\n"
+                      "portaudio\n");
+               return 0;
+       }
+
+       if (strcmp(argv[1], "libs") == 0) {
+               printf("vorbisfile\n"
+                      "portaudio\n");
+               return 0;
+       }
+       return 1;
+}
diff --git a/ccan/wwviaudio/wwviaudio.c b/ccan/wwviaudio/wwviaudio.c
new file mode 100644 (file)
index 0000000..870ffa5
--- /dev/null
@@ -0,0 +1,475 @@
+/*
+    (C) Copyright 2007,2008, Stephen M. Cameron.
+
+    This file is part of wordwarvi.
+
+    wordwarvi is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    wordwarvi is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with wordwarvi; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+ */
+
+#ifndef WWVIAUDIO_STUBS_ONLY
+
+#include <stdio.h>
+#include <stdint.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define WWVIAUDIO_DEFINE_GLOBALS
+#include "wwviaudio.h"
+#undef WWVIAUDIO_DEFINE_GLOBALS
+
+#include "portaudio.h"
+#include "ccan/ogg_to_pcm/ogg_to_pcm.h"
+
+#define FRAMES_PER_BUFFER  (1024)
+
+static PaStream *stream = NULL;
+static int audio_paused = 0;
+static int music_playing = 1;
+static int sound_working = 0;
+static int nomusic = 0;
+static int sound_effects_on = 1;
+static int sound_device = -1; /* default sound device for port audio. */
+static int max_concurrent_sounds = 0;
+static int max_sound_clips = 0;
+
+/* Pause all audio output, output silence. */
+void wwviaudio_pause_audio(void)
+{
+       audio_paused = 1;
+}
+
+/* Resume playing audio previously paused. */
+void wwviaudio_resume_audio(void)
+{
+       audio_paused = 0;
+}
+
+/* Silence the music channel */
+void wwviaudio_silence_music(void)
+{
+       music_playing = 0;
+}
+
+/* Resume volume on the music channel. */
+void wwviaudio_resume_music(void)
+{
+       music_playing = 1;
+}
+
+void wwviaudio_toggle_music(void)
+{
+       music_playing = !music_playing;
+}
+
+/* Silence the sound effects. */
+void wwviaudio_silence_sound_effects(void)
+{
+       sound_effects_on = 0;
+}
+
+/* Resume volume on the sound effects. */
+void wwviaudio_resume_sound_effects(void)
+{
+       sound_effects_on = 1;
+}
+
+void wwviaudio_toggle_sound_effects(void)
+{
+       sound_effects_on = !sound_effects_on;
+}
+
+void wwviaudio_set_nomusic(void)
+{
+       nomusic = 1;
+}
+
+static struct sound_clip {
+       int active;
+       int nsamples;
+       int pos;
+       int16_t *sample;
+} *clip = NULL;
+
+static struct sound_clip *audio_queue = NULL;
+
+int wwviaudio_read_ogg_clip(int clipnum, char *filename)
+{
+       uint64_t nframes;
+       char filebuf[PATH_MAX];
+       struct stat statbuf;
+       int samplesize, sample_rate;
+       int nchannels;
+       int rc;
+
+       if (clipnum >= max_sound_clips || clipnum < 0)
+               return -1;
+
+       strncpy(filebuf, filename, PATH_MAX);
+       rc = stat(filebuf, &statbuf);
+       if (rc != 0) {
+               snprintf(filebuf, PATH_MAX, "%s", filename);
+               rc = stat(filebuf, &statbuf);
+               if (rc != 0) {
+                       fprintf(stderr, "stat('%s') failed.\n", filebuf);
+                       return -1;
+               }
+       }
+/*
+       printf("Reading sound file: '%s'\n", filebuf);
+       printf("frames = %lld\n", sfinfo.frames);
+       printf("samplerate = %d\n", sfinfo.samplerate);
+       printf("channels = %d\n", sfinfo.channels);
+       printf("format = %d\n", sfinfo.format);
+       printf("sections = %d\n", sfinfo.sections);
+       printf("seekable = %d\n", sfinfo.seekable);
+*/
+       if (clip[clipnum].sample != NULL)
+               /* overwriting a previously read clip... */
+               free(clip[clipnum].sample);
+
+       rc = ogg_to_pcm(filebuf, &clip[clipnum].sample, &samplesize,
+               &sample_rate, &nchannels, &nframes);
+       if (clip[clipnum].sample == NULL) {
+               printf("Can't get memory for sound data for %llu frames in %s\n",
+                       nframes, filebuf);
+               goto error;
+       }
+
+       if (rc != 0) {
+               fprintf(stderr, "Error: ogg_to_pcm('%s') failed.\n",
+                       filebuf);
+               goto error;
+       }
+
+       clip[clipnum].nsamples = (int) nframes;
+       if (clip[clipnum].nsamples < 0)
+               clip[clipnum].nsamples = 0;
+
+       return 0;
+error:
+       return -1;
+}
+
+/* This routine will be called by the PortAudio engine when audio is needed.
+** It may called at interrupt level on some machines so don't do anything
+** that could mess up the system like calling malloc() or free().
+*/
+static int patestCallback(const void *inputBuffer, void *outputBuffer,
+       unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
+       PaStreamCallbackFlags statusFlags, __attribute__ ((unused)) void *userData )
+{
+       int i, j, sample, count;
+       float *out = NULL;
+       float output;
+       out = (float*) outputBuffer;
+       output = 0.0;
+       count = 0;
+
+       if (audio_paused) {
+               /* output silence when paused and
+                * don't advance any sound slot pointers
+                */
+               for (i = 0; i < framesPerBuffer; i++)
+                       *out++ = (float) 0;
+               return 0;
+       }
+
+       for (i = 0; i < framesPerBuffer; i++) {
+               output = 0.0;
+               count = 0;
+               for (j = 0; j < max_concurrent_sounds; j++) {
+                       if (!audio_queue[j].active ||
+                               audio_queue[j].sample == NULL)
+                               continue;
+                       sample = i + audio_queue[j].pos;
+                       count++;
+                       if (sample >= audio_queue[j].nsamples) {
+                               audio_queue[j].active = 0;
+                               continue;
+                       }
+                       if (j != WWVIAUDIO_MUSIC_SLOT && sound_effects_on)
+                               output += (float) audio_queue[j].sample[sample] * 0.5 / (float) (INT16_MAX);
+                       else if (j == WWVIAUDIO_MUSIC_SLOT && music_playing)
+                               output += (float) audio_queue[j].sample[sample] / (float) (INT16_MAX);
+               }
+               *out++ = (float) output / 2.0;
+        }
+       for (i = 0; i < max_concurrent_sounds; i++) {
+               if (!audio_queue[i].active)
+                       continue;
+               audio_queue[i].pos += framesPerBuffer;
+               if (audio_queue[i].pos >= audio_queue[i].nsamples)
+                       audio_queue[i].active = 0;
+       }
+       return 0; /* we're never finished */
+}
+
+
+static void decode_paerror(PaError rc)
+{
+       if (rc == paNoError)
+               return;
+       fprintf(stderr, "An error occured while using the portaudio stream\n");
+       fprintf(stderr, "Error number: %d\n", rc);
+       fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(rc));
+}
+
+static void wwviaudio_terminate_portaudio(PaError rc)
+{
+       Pa_Terminate();
+       decode_paerror(rc);
+}
+
+int wwviaudio_initialize_portaudio(int maximum_concurrent_sounds, int maximum_sound_clips)
+{
+       PaStreamParameters outparams;
+       PaError rc;
+       PaDeviceIndex device_count;
+
+       max_concurrent_sounds = maximum_concurrent_sounds;
+       max_sound_clips = maximum_sound_clips;
+
+       audio_queue = malloc(max_concurrent_sounds * sizeof(audio_queue[0]));
+       clip = malloc(max_sound_clips * sizeof(clip[0]));
+       if (audio_queue == NULL || clip == NULL)
+               return -1;
+
+       memset(audio_queue, 0, sizeof(audio_queue[0]) * max_concurrent_sounds);
+       memset(clip, 0, sizeof(clip[0]) * max_sound_clips);
+
+       rc = Pa_Initialize();
+       if (rc != paNoError)
+               goto error;
+
+       device_count = Pa_GetDeviceCount();
+       printf("Portaudio reports %d sound devices.\n", device_count);
+
+       if (device_count == 0) {
+               printf("There will be no audio.\n");
+               goto error;
+               rc = 0;
+       }
+       sound_working = 1;
+
+       outparams.device = Pa_GetDefaultOutputDevice();  /* default output device */
+
+       printf("Portaudio says the default device is: %d\n", outparams.device);
+
+       if (sound_device != -1) {
+               if (sound_device >= device_count)
+                       fprintf(stderr, "wordwarvi:  Invalid sound device "
+                               "%d specified, ignoring.\n", sound_device);
+               else
+                       outparams.device = sound_device;
+               printf("Using sound device %d\n", outparams.device);
+       }
+
+       if (outparams.device < 0 && device_count > 0) {
+               printf("Hmm, that's strange, portaudio says the default device is %d,\n"
+                       " but there are %d devices\n",
+                       outparams.device, device_count);
+               printf("I think we'll just skip sound for now.\n");
+               printf("You might try the '--sounddevice' option and see if that helps.\n");
+               sound_working = 0;
+               return -1;
+       }
+
+       outparams.channelCount = 1;                      /* mono output */
+       outparams.sampleFormat = paFloat32;              /* 32 bit floating point output */
+       outparams.suggestedLatency =
+               Pa_GetDeviceInfo(outparams.device)->defaultLowOutputLatency;
+       outparams.hostApiSpecificStreamInfo = NULL;
+
+       rc = Pa_OpenStream(&stream,
+               NULL,         /* no input */
+               &outparams, WWVIAUDIO_SAMPLE_RATE, FRAMES_PER_BUFFER,
+               paNoFlag, /* paClipOff, */   /* we won't output out of range samples so don't bother clipping them */
+               patestCallback, NULL /* cookie */);
+       if (rc != paNoError)
+               goto error;
+       if ((rc = Pa_StartStream(stream)) != paNoError)
+               goto error;
+       return rc;
+error:
+       wwviaudio_terminate_portaudio(rc);
+       return rc;
+}
+
+
+void wwviaudio_stop_portaudio(void)
+{
+       int i, rc;
+       
+       if (!sound_working)
+               return;
+       if ((rc = Pa_StopStream(stream)) != paNoError)
+               goto error;
+       rc = Pa_CloseStream(stream);
+error:
+       wwviaudio_terminate_portaudio(rc);
+       if (audio_queue) {
+               free(audio_queue);
+               audio_queue = NULL;
+               max_concurrent_sounds = 0;
+       }
+       if (clip) {
+               for (i = 0; i < max_sound_clips; i++) {
+                       if (clip[i].sample)
+                               free(clip[i].sample);
+               }
+               free(clip);
+               clip = NULL;
+               max_sound_clips = 0;
+       }
+       return;
+}
+
+static int wwviaudio_add_sound_to_slot(int which_sound, int which_slot)
+{
+       int i;
+
+       if (!sound_working)
+               return 0;
+
+       if (nomusic && which_slot == WWVIAUDIO_MUSIC_SLOT)
+               return 0;
+
+       if (which_slot != WWVIAUDIO_ANY_SLOT) {
+               if (audio_queue[which_slot].active)
+                       audio_queue[which_slot].active = 0;
+               audio_queue[which_slot].pos = 0;
+               audio_queue[which_slot].nsamples = 0;
+               /* would like to put a memory barrier here. */
+               audio_queue[which_slot].sample = clip[which_sound].sample;
+               audio_queue[which_slot].nsamples = clip[which_sound].nsamples;
+               /* would like to put a memory barrier here. */
+               audio_queue[which_slot].active = 1;
+               return which_slot;
+       }
+       for (i=1;i<max_concurrent_sounds;i++) {
+               if (audio_queue[i].active == 0) {
+                       audio_queue[i].nsamples = clip[which_sound].nsamples;
+                       audio_queue[i].pos = 0;
+                       audio_queue[i].sample = clip[which_sound].sample;
+                       audio_queue[i].active = 1;
+                       break;
+               }
+       }
+       return (i >= max_concurrent_sounds) ? -1 : i;
+}
+
+int wwviaudio_add_sound(int which_sound)
+{
+       return wwviaudio_add_sound_to_slot(which_sound, WWVIAUDIO_ANY_SLOT);
+}
+
+int wwviaudio_play_music(int which_sound)
+{
+       return wwviaudio_add_sound_to_slot(which_sound, WWVIAUDIO_MUSIC_SLOT);
+}
+
+
+void wwviaudio_add_sound_low_priority(int which_sound)
+{
+
+       /* adds a sound if there are at least 5 empty sound slots. */
+       int i;
+       int empty_slots = 0;
+       int last_slot;
+
+       if (!sound_working)
+               return;
+       last_slot = -1;
+       for (i = 1; i < max_concurrent_sounds; i++)
+               if (audio_queue[i].active == 0) {
+                       last_slot = i;
+                       empty_slots++;
+                       if (empty_slots >= 5)
+                               break;
+       }
+
+       if (empty_slots < 5)
+               return;
+       
+       i = last_slot;
+
+       if (audio_queue[i].active == 0) {
+               audio_queue[i].nsamples = clip[which_sound].nsamples;
+               audio_queue[i].pos = 0;
+               audio_queue[i].sample = clip[which_sound].sample;
+               audio_queue[i].active = 1;
+       }
+       return;
+}
+
+
+void wwviaudio_cancel_sound(int queue_entry)
+{
+       if (!sound_working)
+               return;
+       audio_queue[queue_entry].active = 0;
+}
+
+void wwviaudio_cancel_music(void)
+{
+       wwviaudio_cancel_sound(WWVIAUDIO_MUSIC_SLOT);
+}
+
+void wwviaudio_cancel_all_sounds(void)
+{
+       int i;
+       if (!sound_working)
+               return;
+       for (i = 0; i < max_concurrent_sounds; i++)
+               audio_queue[i].active = 0;
+}
+
+int wwviaudio_set_sound_device(int device)
+{
+       sound_device = device;
+       return 0;
+}
+
+#else /* stubs only... */
+
+int wwviaudio_initialize_portaudio() { return 0; }
+void wwviaudio_stop_portaudio() { return; }
+void wwviaudio_set_nomusic() { return; }
+int wwviaudio_read_ogg_clip(int clipnum, char *filename) { return 0; }
+
+void wwviaudio_pause_audio() { return; }
+void wwviaudio_resume_audio() { return; }
+
+void wwviaudio_silence_music() { return; }
+void wwviaudio_resume_music() { return; }
+void wwviaudio_silence_sound_effects() { return; }
+void wwviaudio_resume_sound_effects() { return; }
+void wwviaudio_toggle_sound_effects() { return; }
+
+int wwviaudio_play_music(int which_sound) { return 0; }
+void wwviaudio_cancel_music() { return; }
+void wwviaudio_toggle_music() { return; }
+int wwviaudio_add_sound(int which_sound) { return 0; }
+void wwviaudio_add_sound_low_priority(int which_sound) { return; }
+void wwviaudio_cancel_sound(int queue_entry) { return; }
+void wwviaudio_cancel_all_sounds() { return; }
+int wwviaudio_set_sound_device(int device) { return 0; }
+
+#endif
diff --git a/ccan/wwviaudio/wwviaudio.h b/ccan/wwviaudio/wwviaudio.h
new file mode 100644 (file)
index 0000000..53986c4
--- /dev/null
@@ -0,0 +1,171 @@
+#ifndef _WWVIAUDIO_H_
+#define _WWVIAUDIO_H_
+/*
+    (C) Copyright 2007,2008, Stephen M. Cameron.
+
+    This file is part of wordwarvi.
+
+    wordwarvi is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    wordwarvi is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with wordwarvi; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+ */
+
+#ifdef WWVIAUDIO_DEFINE_GLOBALS
+#define GLOBAL
+#else
+#define GLOBAL extern
+#endif
+
+#define WWVIAUDIO_MUSIC_SLOT (0)
+#define WWVIAUDIO_SAMPLE_RATE   (44100)
+#define WWVIAUDIO_ANY_SLOT (-1)
+
+/*
+ *             Configuration functions.
+ */
+/* Disables the music channel.  Meant to be called prior to
+ * wwviaudio_initialize_portaudio
+ */
+GLOBAL void wwviaudio_set_nomusic(void);
+
+/* Set the audio device number to the given value
+ * This is meant to be called prior to calling
+ * wwviaudio_initialize_portaudio.  If you don't use
+ * this function, portaudio's default device will be
+ * used.  This function provides a way to use a
+ * device other than the default
+ */
+GLOBAL int wwviaudio_set_sound_device(int device);
+
+/* Initialize portaudio and start the audio engine.
+ * Space will be allocated to allow for the specified
+ * number of concurrently playing sounds.  The 2nd parameter
+ * indicates how many different sound clips are to be held
+ * in memory at once.  e.g. if your app has five different
+ * sounds that it plays, then this would be 5.
+ * 0 is returned on success, -1 otherwise.
+ */
+GLOBAL int wwviaudio_initialize_portaudio(int maximum_concurrent_sounds,
+       int maximum_sound_clips);
+
+/* Stop portaudio and the audio engine. Space allocated
+ * during initialization is freed.
+ */
+GLOBAL void wwviaudio_stop_portaudio(void);
+
+/*
+ *             Audio data functions
+ */
+
+/* Read and decode an ogg vorbis audio file into a numbered buffer
+ * The sound_number parameter is used later with wwviaudio_play_music and
+ * wwviaudio_add_sound.  0 is returned on success, -1 otherwise.
+ * Audio files should be 44100Hz, MONO.  The sound number is one you
+ * provide which will then be associated with that sound.
+ */
+GLOBAL int wwviaudio_read_ogg_clip(int sound_number, char *filename);
+
+/*
+ *             Global sound control functions.
+ */
+
+/* Suspend all audio playback.  Silence is output. */
+GLOBAL void wwviaudio_pause_audio(void);
+
+/* Resume all previously playing audio from whence it was previusly paused. */
+GLOBAL void wwviaudio_resume_audio(void);
+
+/*
+ *             Music channel related functions
+ */
+
+/* Begin playing a numbered buffer into the mix on the music channel
+ * The channel number of the music channel is returned.
+ */
+GLOBAL int wwviaudio_play_music(int sound_number);
+
+/* Output silence on the music channel (pointer still advances though.) */
+GLOBAL void wwviaudio_silence_music(void);
+
+/* Unsilence the music channel */
+GLOBAL void wwviaudio_resume_music(void);
+
+/* Silence or unsilence the music channe. */
+GLOBAL void wwviaudio_toggle_music(void);
+
+/* Stop playing the playing buffer from the music channel */
+GLOBAL void wwviaudio_cancel_music(void);
+
+/*
+ *             Sound effect (not music) related functions
+ */
+
+/* Begin playing a sound on a non-music channel.  The channel is returned.
+ * sound_number refers to a sound previously associated with the number by
+ * wwviaudio_read_ogg_clip()
+ */
+GLOBAL /* channel */ int wwviaudio_add_sound(int sound_number);
+
+/* Begin playing a sound on a non-music channel.  The channel is returned.
+ * If fewer than five channels are open, the sound is not played, and -1
+ * is returned.
+ */
+GLOBAL void wwviaudio_add_sound_low_priority(int sound_number);
+
+/* Silence all channels but the music channel (pointers still advance though) */
+GLOBAL void wwviaudio_silence_sound_effects(void);
+
+/* Unsilence all channels but the music channel */
+GLOBAL void wwviaudio_resume_sound_effects(void);
+
+/* Either silence or unsilence all but the music channel */
+GLOBAL void wwviaudio_toggle_sound_effects(void);
+
+/* Stop playing the playing buffer from the given channel */
+GLOBAL void wwviaudio_cancel_sound(int channel);
+
+
+/* Stop playing the playing buffer from all channels */
+GLOBAL void wwviaudio_cancel_all_sounds(void);
+
+/*
+       Example usage, something along these lines:
+
+       if (wwviaudio_initialize_portaudio() != 0)
+               bail_out_and_die();
+
+       You would probably use #defines or enums rather than bare ints...
+       wwviaudio_read_ogg_clip(1, "mysound1.ogg");
+       wwviaudio_read_ogg_clip(2, "mysound2.ogg");
+       wwviaudio_read_ogg_clip(3, "mysound3.ogg");
+       wwviaudio_read_ogg_clip(4, "mymusic.ogg");
+
+       ...
+
+       wwviaudio_play_music(4); <-- begins playing music in background, returns immediately
+
+       while (program isn't done) {
+               do_stuff();
+               if (something happened)
+                       wwviaudio_add_sound(1);
+               if (something else happened)
+                       wwviaudio_add_sound(2);
+               time_passes();
+       }
+       
+       wwviaudio_cancel_all_sounds();
+       wwviaduio_stop_portaudio();
+*/
+#undef GLOBAL
+#endif