summaryrefslogtreecommitdiff
path: root/src/i_sdlsound.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/i_sdlsound.c')
-rw-r--r--src/i_sdlsound.c447
1 files changed, 447 insertions, 0 deletions
diff --git a/src/i_sdlsound.c b/src/i_sdlsound.c
new file mode 100644
index 00000000..920e74d2
--- /dev/null
+++ b/src/i_sdlsound.c
@@ -0,0 +1,447 @@
+// Emacs style mode select -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005 Simon Howard
+//
+// This program 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.
+//
+// This program 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 this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// DESCRIPTION:
+// System interface for sound.
+//
+//-----------------------------------------------------------------------------
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "SDL.h"
+#include "SDL_mixer.h"
+
+#include "deh_main.h"
+#include "s_sound.h"
+#include "m_argv.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+#include "doomdef.h"
+
+#define NUM_CHANNELS 16
+
+static boolean sound_initialised = false;
+
+static Mix_Chunk sound_chunks[NUMSFX];
+static int channels_playing[NUM_CHANNELS];
+
+static int mixer_freq;
+static Uint16 mixer_format;
+static int mixer_channels;
+
+// When a sound stops, check if it is still playing. If it is not,
+// we can mark the sound data as CACHE to be freed back for other
+// means.
+
+static void ReleaseSoundOnChannel(int channel)
+{
+ int i;
+ int id = channels_playing[channel];
+
+ if (!id)
+ {
+ return;
+ }
+
+ channels_playing[channel] = sfx_None;
+
+ for (i=0; i<NUM_CHANNELS; ++i)
+ {
+ // Playing on this channel? if so, don't release.
+
+ if (channels_playing[i] == id)
+ return;
+ }
+
+ // Not used on any channel, and can be safely released
+
+ Z_ChangeTag(sound_chunks[id].abuf, PU_CACHE);
+}
+
+static boolean ConvertibleRatio(int freq1, int freq2)
+{
+ int ratio;
+
+ if (freq1 > freq2)
+ {
+ return ConvertibleRatio(freq2, freq1);
+ }
+ else if ((freq2 % freq1) != 0)
+ {
+ // Not in a direct ratio
+
+ return false;
+ }
+ else
+ {
+ // Check the ratio is a power of 2
+
+ ratio = freq2 / freq1;
+
+ while ((ratio & 1) == 0)
+ {
+ ratio = ratio >> 1;
+ }
+
+ return ratio == 1;
+ }
+}
+
+// Generic sound expansion function for any sample rate
+
+static void ExpandSoundData(byte *data,
+ int samplerate,
+ int length,
+ Mix_Chunk *destination)
+{
+ SDL_AudioCVT convertor;
+
+ if (samplerate <= mixer_freq
+ && ConvertibleRatio(samplerate, mixer_freq)
+ && SDL_BuildAudioCVT(&convertor,
+ AUDIO_U8, 1, samplerate,
+ mixer_format, mixer_channels, mixer_freq))
+ {
+ convertor.buf = destination->abuf;
+ convertor.len = length;
+ memcpy(convertor.buf, data, length);
+
+ SDL_ConvertAudio(&convertor);
+ }
+ else
+ {
+ Sint16 *expanded = (Sint16 *) destination->abuf;
+ int expanded_length;
+ int expand_ratio;
+ int i;
+
+ // Generic expansion if conversion does not work:
+ //
+ // SDL's audio conversion only works for rate conversions that are
+ // powers of 2; if the two formats are not in a direct power of 2
+ // ratio, do this naive conversion instead.
+
+ // number of samples in the converted sound
+
+ expanded_length = (length * mixer_freq) / samplerate;
+ expand_ratio = (length << 8) / expanded_length;
+
+ for (i=0; i<expanded_length; ++i)
+ {
+ Sint16 sample;
+ int src;
+
+ src = (i * expand_ratio) >> 8;
+
+ sample = data[src] | (data[src] << 8);
+ sample -= 32768;
+
+ // expand 8->16 bits, mono->stereo
+
+ expanded[i * 2] = expanded[i * 2 + 1] = sample;
+ }
+ }
+}
+
+// Load and convert a sound effect
+// Returns true if successful
+
+static boolean CacheSFX(int sound)
+{
+ int lumpnum;
+ unsigned int lumplen;
+ int samplerate;
+ unsigned int length;
+ unsigned int expanded_length;
+ byte *data;
+
+ // need to load the sound
+
+ lumpnum = S_sfx[sound].lumpnum;
+ data = W_CacheLumpNum(lumpnum, PU_STATIC);
+ lumplen = W_LumpLength(lumpnum);
+
+ // Check the header, and ensure this is a valid sound
+
+ if (lumplen < 8
+ || data[0] != 0x03 || data[1] != 0x00)
+ {
+ // Invalid sound
+
+ return false;
+ }
+
+ // 16 bit sample rate field, 32 bit length field
+
+ samplerate = (data[3] << 8) | data[2];
+ length = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4];
+
+ // If the header specifies that the length of the sound is greater than
+ // the length of the lump itself, this is an invalid sound lump
+
+ if (length - 8 > lumplen)
+ {
+ return false;
+ }
+
+ expanded_length = (uint32_t) ((((uint64_t) length) * 4 * mixer_freq) / samplerate);
+
+ sound_chunks[sound].allocated = 1;
+ sound_chunks[sound].alen = expanded_length;
+ sound_chunks[sound].abuf
+ = Z_Malloc(expanded_length, PU_STATIC, &sound_chunks[sound].abuf);
+ sound_chunks[sound].volume = MIX_MAX_VOLUME;
+
+ ExpandSoundData(data + 8,
+ samplerate,
+ length - 8,
+ &sound_chunks[sound]);
+
+ // don't need the original lump any more
+
+ Z_ChangeTag(data, PU_CACHE);
+
+ return true;
+}
+
+static Mix_Chunk *GetSFXChunk(int sound_id)
+{
+ if (sound_chunks[sound_id].abuf == NULL)
+ {
+ if (!CacheSFX(sound_id))
+ return NULL;
+ }
+ else
+ {
+ // don't free the sound while it is playing!
+
+ Z_ChangeTag(sound_chunks[sound_id].abuf, PU_STATIC);
+ }
+
+ return &sound_chunks[sound_id];
+}
+
+
+//
+// Retrieve the raw data lump index
+// for a given SFX name.
+//
+
+static int I_SDL_GetSfxLumpNum(sfxinfo_t* sfx)
+{
+ char namebuf[9];
+
+ sprintf(namebuf, "ds%s", DEH_String(sfx->name));
+
+ return W_GetNumForName(namebuf);
+}
+
+static void I_SDL_UpdateSoundParams(int handle, int vol, int sep)
+{
+ int left, right;
+
+ if (!sound_initialised)
+ {
+ return;
+ }
+
+ left = ((254 - sep) * vol) / 127;
+ right = ((sep) * vol) / 127;
+
+ Mix_SetPanning(handle, left, right);
+}
+
+
+//
+// Starting a sound means adding it
+// to the current list of active sounds
+// in the internal channels.
+// As the SFX info struct contains
+// e.g. a pointer to the raw data,
+// it is ignored.
+// As our sound handling does not handle
+// priority, it is ignored.
+// Pitching (that is, increased speed of playback)
+// is set, but currently not used by mixing.
+//
+
+static int I_SDL_StartSound(int id, int channel, int vol, int sep)
+{
+ Mix_Chunk *chunk;
+
+ if (!sound_initialised)
+ {
+ return -1;
+ }
+
+ // Release a sound effect if there is already one playing
+ // on this channel
+
+ ReleaseSoundOnChannel(channel);
+
+ // Get the sound data
+
+ chunk = GetSFXChunk(id);
+
+ if (chunk == NULL)
+ {
+ return -1;
+ }
+
+ // play sound
+
+ Mix_PlayChannelTimed(channel, chunk, 0, -1);
+
+ channels_playing[channel] = id;
+
+ // set separation, etc.
+
+ I_SDL_UpdateSoundParams(channel, vol, sep);
+
+ return channel;
+}
+
+static void I_SDL_StopSound (int handle)
+{
+ if (!sound_initialised)
+ {
+ return;
+ }
+
+ Mix_HaltChannel(handle);
+
+ // Sound data is no longer needed; release the
+ // sound data being used for this channel
+
+ ReleaseSoundOnChannel(handle);
+}
+
+
+static boolean I_SDL_SoundIsPlaying(int handle)
+{
+ if (handle < 0)
+ {
+ return false;
+ }
+
+ return Mix_Playing(handle);
+}
+
+//
+// Periodically called to update the sound system
+//
+
+static void I_SDL_UpdateSound(void)
+{
+ int i;
+
+ // Check all channels to see if a sound has finished
+
+ for (i=0; i<NUM_CHANNELS; ++i)
+ {
+ if (channels_playing[i] && !I_SDL_SoundIsPlaying(i))
+ {
+ // Sound has finished playing on this channel,
+ // but sound data has not been released to cache
+
+ ReleaseSoundOnChannel(i);
+ }
+ }
+}
+
+static void I_SDL_ShutdownSound(void)
+{
+ if (!sound_initialised)
+ {
+ return;
+ }
+
+ Mix_CloseAudio();
+ SDL_QuitSubSystem(SDL_INIT_AUDIO);
+
+ sound_initialised = false;
+}
+
+static boolean I_SDL_InitSound()
+{
+ int i;
+
+ // No sounds yet
+
+ for (i=0; i<NUMSFX; ++i)
+ {
+ sound_chunks[i].abuf = NULL;
+ }
+
+ for (i=0; i<NUM_CHANNELS; ++i)
+ {
+ channels_playing[i] = sfx_None;
+ }
+
+ if (SDL_Init(SDL_INIT_AUDIO) < 0)
+ {
+ fprintf(stderr, "Unable to set up sound.\n");
+ return false;
+ }
+
+ if (Mix_OpenAudio(snd_samplerate, AUDIO_S16SYS, 2, 1024) < 0)
+ {
+ fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError());
+ return false;
+ }
+
+ Mix_QuerySpec(&mixer_freq, &mixer_format, &mixer_channels);
+
+ Mix_AllocateChannels(NUM_CHANNELS);
+
+ SDL_PauseAudio(0);
+
+ sound_initialised = true;
+
+ return true;
+}
+
+static snddevice_t sound_sdl_devices[] =
+{
+ SNDDEVICE_SB,
+ SNDDEVICE_PAS,
+ SNDDEVICE_GUS,
+ SNDDEVICE_WAVEBLASTER,
+ SNDDEVICE_SOUNDCANVAS,
+ SNDDEVICE_AWE32,
+};
+
+sound_module_t sound_sdl_module =
+{
+ sound_sdl_devices,
+ sizeof(sound_sdl_devices) / sizeof(*sound_sdl_devices),
+ I_SDL_InitSound,
+ I_SDL_ShutdownSound,
+ I_SDL_GetSfxLumpNum,
+ I_SDL_UpdateSound,
+ I_SDL_UpdateSoundParams,
+ I_SDL_StartSound,
+ I_SDL_StopSound,
+ I_SDL_SoundIsPlaying,
+};
+