-/*
+/*
* Copyright (C) 2003 Robert Kooima
*
* NEVERBALL is free software; you can redistribute it and/or modify
*/
#include <SDL.h>
-#include <SDL_mixer.h>
+
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
+
#include <string.h>
+#include <stdlib.h>
#include "config.h"
#include "audio.h"
+#include "common.h"
+#include "fs.h"
+#include "fs_ov.h"
/*---------------------------------------------------------------------------*/
-static int audio_state = 0;
-
-static char name[MAXSND][MAXSTR];
-static int chan[MAXSND];
-static Mix_Chunk *buff[MAXSND];
-static Mix_Music *song;
-
-static char curr_bgm[MAXSTR];
-static char next_bgm[MAXSTR];
+#define AUDIO_RATE 44100
+#define AUDIO_CHAN 2
-static float fade_volume = 1.0f;
-static float fade_rate = 0.0f;
+struct voice
+{
+ OggVorbis_File vf;
+ float amp;
+ float damp;
+ int chan;
+ int play;
+ int loop;
+ char *name;
+ struct voice *next;
+};
+
+static int audio_state = 0;
+static float sound_vol = 1.0f;
+static float music_vol = 1.0f;
+
+static SDL_AudioSpec spec;
+
+static struct voice *music = NULL;
+static struct voice *queue = NULL;
+static struct voice *voices = NULL;
+static short *buffer = NULL;
+
+static ov_callbacks callbacks = {
+ fs_ov_read, fs_ov_seek, fs_ov_close, fs_ov_tell
+};
/*---------------------------------------------------------------------------*/
-void audio_init(void)
+#define MIX(d, s) { \
+ int n = (int) (d) + (int) (s); \
+ if (n > 32767) (d) = 32767; \
+ else if (n < -32768) (d) = -32768; \
+ else (d) = (short) n; \
+ }
+
+static int voice_step(struct voice *V, float volume, Uint8 *stream, int length)
{
- int r = config_get_d(CONFIG_AUDIO_RATE);
- int b = config_get_d(CONFIG_AUDIO_BUFF);
- int i;
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ int order = 1;
+#else
+ int order = 0;
+#endif
- memset(curr_bgm, 0, MAXSTR);
- memset(next_bgm, 0, MAXSTR);
+ short *obuf = (short *) stream;
+ char *ibuf = (char *) buffer;
- if (audio_state == 0)
+ int i, b = 0, n = 1, c = 0, r = 0;
+
+ /* Compute the total request size for the current stream. */
+
+ if (V->chan == 1) r = length / 2;
+ if (V->chan == 2) r = length ;
+
+ /* While data is coming in and data is still needed... */
+
+ while (n > 0 && r > 0)
{
- if (Mix_OpenAudio(r, MIX_DEFAULT_FORMAT, 1, b) == 0)
+ /* Read audio from the stream. */
+
+ if ((n = (int) ov_read(&V->vf, ibuf, r, order, 2, 1, &b)) > 0)
{
- for (i = 0; i < MAXSND; i++)
- if (chan[i])
- buff[i] = Mix_LoadWAV(config_data(name[i]));
+ /* Mix mono audio. */
- audio_state = 1;
+ if (V->chan == 1)
+ for (i = 0; i < n / 2; i += 1)
+ {
+ short M = (short) (V->amp * volume * buffer[i]);
+
+ MIX(obuf[c], M); c++;
+ MIX(obuf[c], M); c++;
+
+ V->amp += V->damp;
- audio_volume(config_get_d(CONFIG_SOUND_VOLUME),
- config_get_d(CONFIG_MUSIC_VOLUME));
+ if (V->amp < 0.0) V->amp = 0.0;
+ if (V->amp > 1.0) V->amp = 1.0;
+ }
+
+ /* Mix stereo audio. */
+
+ if (V->chan == 2)
+ for (i = 0; i < n / 2; i += 2)
+ {
+ short L = (short) (V->amp * volume * buffer[i + 0]);
+ short R = (short) (V->amp * volume * buffer[i + 1]);
+
+ MIX(obuf[c], L); c++;
+ MIX(obuf[c], R); c++;
+
+ V->amp += V->damp;
+
+ if (V->amp < 0.0) V->amp = 0.0;
+ if (V->amp > 1.0) V->amp = 1.0;
+ }
+
+ r -= n;
}
else
{
- fprintf(stderr, "Sound disabled\n");
- audio_state = 0;
+ /* We're at EOF. Loop or end the voice. */
+
+ if (V->loop)
+ {
+ ov_raw_seek(&V->vf, 0);
+ n = 1;
+ }
+ else return 1;
}
}
+ return 0;
}
-void audio_free(void)
+static struct voice *voice_init(const char *filename, float a)
{
- int i;
+ struct voice *V;
+ fs_file fp;
+
+ /* Allocate and initialize a new voice structure. */
- if (audio_state == 1)
+ if ((V = (struct voice *) calloc(1, sizeof (struct voice))))
{
- Mix_CloseAudio();
+ /* Note the name. */
+
+ V->name = strdup(filename);
+
+ /* Attempt to open the named Ogg stream. */
- for (i = 0; i < MAXSND; i++)
- if (buff[i])
+ if ((fp = fs_open(filename, "r")))
+ {
+ if (ov_open_callbacks(fp, &V->vf, NULL, 0, callbacks) == 0)
{
- Mix_FreeChunk(buff[i]);
+ vorbis_info *info = ov_info(&V->vf, -1);
- buff[i] = NULL;
- chan[i] = 0;
- }
+ /* On success, configure the voice. */
+
+ V->amp = a;
+ V->damp = 0;
+ V->chan = info->channels;
+ V->play = 1;
+ V->loop = 0;
+
+ if (V->amp > 1.0) V->amp = 1.0;
+ if (V->amp < 0.0) V->amp = 0.0;
- audio_state = 0;
+ /* The file will be closed when the Ogg is cleared. */
+ }
+ else fs_close(fp);
+ }
}
+ return V;
}
-void audio_bind(int i, int c, const char *filename)
+static void voice_free(struct voice *V)
{
- strncpy(name[i], filename, MAXSTR);
- chan[i] = c;
+ ov_clear(&V->vf);
+
+ free(V->name);
+ free(V);
}
-void audio_play(int i, float v)
+/*---------------------------------------------------------------------------*/
+
+static void audio_step(void *data, Uint8 *stream, int length)
{
- if (audio_state == 1 && buff[i])
+ struct voice *V = voices;
+ struct voice *P = NULL;
+
+ /* Zero the output buffer. */
+
+ memset(stream, 0, length);
+
+ /* Mix the background music. */
+
+ if (music)
{
- Mix_VolumeChunk(buff[i], (int) (v * MIX_MAX_VOLUME));
- Mix_PlayChannel(chan[i], buff[i], 0);
+ voice_step(music, music_vol, stream, length);
+
+ /* If the track has faded out, move to a queued track. */
+
+ if (music->amp <= 0.0f && music->damp < 0.0f && queue)
+ {
+ voice_free(music);
+ music = queue;
+ queue = NULL;
+ }
+ }
+
+ /* Iterate over all active voices. */
+
+ while (V)
+ {
+ /* Mix this voice. */
+
+ if (V->play && voice_step(V, sound_vol, stream, length))
+ {
+ /* Delete a finished voice... */
+
+ struct voice *T = V;
+
+ if (P)
+ V = P->next = V->next;
+ else
+ V = voices = V->next;
+
+ voice_free(T);
+ }
+ else
+ {
+ /* ... or continue to the next. */
+
+ P = V;
+ V = V->next;
+ }
}
}
/*---------------------------------------------------------------------------*/
-void audio_music_play(const char *filename)
+void audio_init(void)
{
- if (audio_state)
+ audio_state = 0;
+
+ /* Configure the audio. */
+
+ spec.format = AUDIO_S16SYS;
+ spec.channels = AUDIO_CHAN;
+ spec.samples = config_get_d(CONFIG_AUDIO_BUFF);
+ spec.freq = AUDIO_RATE;
+ spec.callback = audio_step;
+
+ /* Allocate an input buffer. */
+
+ if ((buffer = (short *) malloc(spec.samples * 4)))
{
- audio_music_stop();
+ /* Start the audio thread. */
- if ((config_get_d(CONFIG_MUSIC_VOLUME) > 0) &&
- (song = Mix_LoadMUS(config_data(filename))))
+ if (SDL_OpenAudio(&spec, NULL) == 0)
{
- Mix_PlayMusic(song, -1);
- strcpy(curr_bgm, filename);
+ audio_state = 1;
+ SDL_PauseAudio(0);
}
+ else fprintf(stderr, "%s\n", SDL_GetError());
}
+
+ /* Set the initial volumes. */
+
+ audio_volume(config_get_d(CONFIG_SOUND_VOLUME),
+ config_get_d(CONFIG_MUSIC_VOLUME));
}
-void audio_music_queue(const char *filename)
+void audio_free(void)
{
- if (audio_state)
- {
- if (strlen(curr_bgm) == 0 || strcmp(filename, curr_bgm) != 0)
- {
- Mix_VolumeMusic(0);
- fade_volume = 0.0f;
+ /* Halt the audio thread. */
- audio_music_play(filename);
- strcpy(curr_bgm, filename);
+ SDL_CloseAudio();
- Mix_PauseMusic();
- }
- }
+ /* Release the input buffer. */
+
+ free(buffer);
+
+ /* Ogg streams and voice structure remain open to allow quality setting. */
}
-void audio_music_stop(void)
+void audio_play(const char *filename, float a)
{
if (audio_state)
{
- if (Mix_PlayingMusic())
- Mix_HaltMusic();
+ struct voice *V;
- if (song)
- Mix_FreeMusic(song);
+ /* If we're already playing this sound, preempt the running copy. */
+
+ SDL_LockAudio();
+ {
+ for (V = voices; V; V = V->next)
+ if (strcmp(V->name, filename) == 0)
+ {
+ ov_raw_seek(&V->vf, 0);
- song = NULL;
+ V->amp = a;
+
+ if (V->amp > 1.0) V->amp = 1.0;
+ if (V->amp < 0.0) V->amp = 0.0;
+
+ SDL_UnlockAudio();
+ return;
+ }
+ }
+ SDL_UnlockAudio();
+
+ /* Create a new voice structure. */
+
+ V = voice_init(filename, a);
+
+ /* Add it to the list of sounding voices. */
+
+ SDL_LockAudio();
+ {
+ V->next = voices;
+ voices = V;
+ }
+ SDL_UnlockAudio();
}
}
/*---------------------------------------------------------------------------*/
-/*
- * SDL_mixer already provides music fading. Unfortunately, it halts playback
- * at the end of a fade. We need to be able to fade music back in from the
- * point where it stopped. So, we reinvent this wheel.
- */
-void audio_timer(float dt)
+void audio_music_play(const char *filename)
{
if (audio_state)
{
- if (fade_rate > 0.0f || fade_rate < 0.0f)
- fade_volume += dt / fade_rate;
+ audio_music_stop();
- if (fade_volume < 0.0f)
+ SDL_LockAudio();
{
- fade_volume = 0.0f;
-
- if (strlen(next_bgm) == 0)
+ if ((music = voice_init(filename, 0.0f)))
{
- fade_rate = 0.0f;
- if (Mix_PlayingMusic())
- Mix_PauseMusic();
+ music->loop = 1;
}
- else
+ }
+ SDL_UnlockAudio();
+ }
+}
+
+void audio_music_queue(const char *filename, float t)
+{
+ if (audio_state)
+ {
+ SDL_LockAudio();
+ {
+ if ((queue = voice_init(filename, 0.0f)))
{
- fade_rate = -fade_rate;
- audio_music_queue(next_bgm);
+ queue->loop = 1;
+
+ if (t > 0.0f)
+ queue->damp = +1.0f / (AUDIO_RATE * t);
}
}
+ SDL_UnlockAudio();
+ }
+}
- if (fade_volume > 1.0f)
+void audio_music_stop(void)
+{
+ if (audio_state)
+ {
+ SDL_LockAudio();
{
- fade_rate = 0.0f;
- fade_volume = 1.0f;
- }
-
- if (Mix_PausedMusic() && fade_rate > 0.0f)
- Mix_ResumeMusic();
-
- if (Mix_PlayingMusic())
- Mix_VolumeMusic(config_get_d(CONFIG_MUSIC_VOLUME) *
- (int) (fade_volume * MIX_MAX_VOLUME) / 10);
+ if (music)
+ {
+ voice_free(music);
+ }
+ music = NULL;
+ }
+ SDL_UnlockAudio();
}
}
+/*---------------------------------------------------------------------------*/
+
void audio_music_fade_out(float t)
{
- fade_rate = -t;
- strcpy(next_bgm, "");
+ SDL_LockAudio();
+ {
+ if (music) music->damp = -1.0f / (AUDIO_RATE * t);
+ }
+ SDL_UnlockAudio();
}
void audio_music_fade_in(float t)
{
- fade_rate = +t;
- strcpy(next_bgm, "");
+ SDL_LockAudio();
+ {
+ if (music) music->damp = +1.0f / (AUDIO_RATE * t);
+ }
+ SDL_UnlockAudio();
}
void audio_music_fade_to(float t, const char *filename)
{
- if (fade_volume > 0)
+ if (music)
{
- if (strlen(curr_bgm) == 0 || strcmp(filename, curr_bgm) != 0)
+ if (strcmp(filename, music->name) != 0)
+ {
+ audio_music_fade_out(t);
+ audio_music_queue(filename, t);
+ }
+ else
{
- strcpy(next_bgm, filename);
- fade_rate = -t;
+ /*
+ * We're fading to the current track. Chances are,
+ * whatever track is still in the queue, we don't want to
+ * hear it anymore.
+ */
+
+ if (queue)
+ {
+ voice_free(queue);
+ queue = NULL;
+ }
+
+ audio_music_fade_in(t);
}
- else fade_rate = t;
}
else
{
- audio_music_queue(filename);
+ audio_music_play(filename);
audio_music_fade_in(t);
}
}
void audio_volume(int s, int m)
{
- if (audio_state)
- {
- Mix_Volume(-1, s * MIX_MAX_VOLUME / 10);
- Mix_VolumeMusic(m * MIX_MAX_VOLUME / 10);
- }
+ sound_vol = (float) s / 10.0f;
+ music_vol = (float) m / 10.0f;
}
/*---------------------------------------------------------------------------*/