733 lines
15 KiB
C
733 lines
15 KiB
C
//-----------------------------------------------------------------------------
|
|
//
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
//
|
|
// 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.
|
|
//
|
|
// DESCRIPTION:
|
|
// System interface for sound.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include <math.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#ifdef WILDMIDI
|
|
#include "wildmidi_lib.h"
|
|
#endif
|
|
|
|
#include "z_zone.h"
|
|
|
|
#include "i_system.h"
|
|
#include "i_sound.h"
|
|
#include "ib_sound.h"
|
|
#include "m_argv.h"
|
|
#include "m_misc.h"
|
|
#include "w_wad.h"
|
|
|
|
#include "doomdef.h"
|
|
|
|
#include <dp_common.h>
|
|
|
|
#define NUM_CHANNELS 8
|
|
|
|
static unsigned int output_sample_rate;
|
|
|
|
|
|
// The actual lengths of all sound effects.
|
|
static size_t lengths[NUMSFX];
|
|
|
|
|
|
// The channel step amount...
|
|
static unsigned long channelstep[NUM_CHANNELS];
|
|
// ... and a 0.16 bit remainder of last step.
|
|
static unsigned long channelstepremainder[NUM_CHANNELS];
|
|
|
|
// The channel data pointers, start and end.
|
|
static unsigned char* channels[NUM_CHANNELS];
|
|
static unsigned char* channelsend[NUM_CHANNELS];
|
|
|
|
// Time/gametic that the channel started playing,
|
|
// used to determine oldest, which automatically
|
|
// has lowest priority.
|
|
// In case number of active sounds exceeds
|
|
// available channels.
|
|
static int channelstart[NUM_CHANNELS];
|
|
|
|
// The sound in channel handles,
|
|
// determined on registration.
|
|
static int channelhandles[NUM_CHANNELS];
|
|
|
|
// SFX id of the playing sound effect.
|
|
// Used to catch duplicates (like chainsaw).
|
|
static int channelids[NUM_CHANNELS];
|
|
|
|
// Hardware left and right channel volume lookup.
|
|
static short* channelleftvol_lookup[NUM_CHANNELS];
|
|
static short* channelrightvol_lookup[NUM_CHANNELS];
|
|
|
|
// Pitch to stepping lookup.
|
|
static long steptable[256];
|
|
|
|
// Volume lookups.
|
|
static short vol_lookup[128][256];
|
|
|
|
|
|
// Music stuff
|
|
|
|
// Maximum volume of music.
|
|
//static int snd_MusicVolume;
|
|
|
|
const char* wildmidi_config_path;
|
|
|
|
#ifdef WILDMIDI
|
|
static boolean music_initialised;
|
|
static midi* music_midi;
|
|
static boolean music_playing;
|
|
#endif
|
|
|
|
|
|
|
|
|
|
//
|
|
// This function loops all active (internal) sound
|
|
// channels, retrieves a given number of samples
|
|
// from the raw sound data, modifies it according
|
|
// to the current (internal) channel parameters,
|
|
// mixes the per channel samples into the global
|
|
// mixbuffer, clamping it to the allowed range,
|
|
// and sets up everything for transferring the
|
|
// contents of the mixbuffer to the (two)
|
|
// hardware channels (left and right, that is).
|
|
//
|
|
// This function currently supports only 16bit.
|
|
//
|
|
static void AudioCallback(short* output_buffer, size_t frames_to_do, void *user_data)
|
|
{
|
|
// Mix current sound data.
|
|
|
|
// Pointers in mixbuffer.
|
|
short* output_buffer_end;
|
|
|
|
size_t bytes_done;
|
|
|
|
const size_t bytes_to_do = frames_to_do * sizeof(short) * 2;
|
|
|
|
(void)user_data;
|
|
|
|
bytes_done = 0;
|
|
|
|
#ifdef WILDMIDI
|
|
if (music_playing)
|
|
{
|
|
bytes_done = (size_t)WildMidi_GetOutput(music_midi, (int8_t*)output_buffer, bytes_to_do);
|
|
|
|
if (bytes_done < bytes_to_do)
|
|
music_playing = false;
|
|
}
|
|
#endif
|
|
|
|
memset((char*)output_buffer + bytes_done, 0, bytes_to_do - bytes_done);
|
|
|
|
// Determine where the sample ends
|
|
output_buffer_end = output_buffer + frames_to_do * 2;
|
|
|
|
// Mix sounds into the mix buffer
|
|
while (output_buffer != output_buffer_end)
|
|
{
|
|
size_t chan;
|
|
|
|
// Obtain base values for mixing
|
|
long dl = output_buffer[0];
|
|
long dr = output_buffer[1];
|
|
|
|
for (chan = 0; chan < NUM_CHANNELS; ++chan)
|
|
{
|
|
// Check if channel is playing anything
|
|
if (channels[chan] != NULL)
|
|
{
|
|
// Get interpolated sample
|
|
const int interpolation_scale = channelstepremainder[chan] / (1 << 8);
|
|
const int sample = channels[chan][0] + (((channels[chan][1] - channels[chan][0]) * interpolation_scale) / (1 << 8));
|
|
|
|
// Add volume-adjusted sample to mix buffer
|
|
dl += channelleftvol_lookup[chan][sample];
|
|
dr += channelrightvol_lookup[chan][sample];
|
|
|
|
// Increment sample position
|
|
channelstepremainder[chan] += channelstep[chan];
|
|
channels[chan] += channelstepremainder[chan] / (1 << 16);
|
|
channelstepremainder[chan] %= 1 << 16;
|
|
|
|
// Disable channel if sound has finished
|
|
if (channels[chan] >= channelsend[chan])
|
|
channels[chan] = NULL;
|
|
}
|
|
}
|
|
|
|
#define CAP ((1 << 15) - 1)
|
|
|
|
// Clamp mixed samples to 16-bit range and write them back to the buffer
|
|
|
|
// Left channel
|
|
if (dl > CAP)
|
|
*output_buffer++ = CAP;
|
|
else if (dl < -CAP)
|
|
*output_buffer++ = -CAP;
|
|
else
|
|
*output_buffer++ = dl;
|
|
|
|
// Right channel
|
|
if (dr > CAP)
|
|
*output_buffer++ = CAP;
|
|
else if (dr < -CAP)
|
|
*output_buffer++ = -CAP;
|
|
else
|
|
*output_buffer++ = dr;
|
|
|
|
#undef CAP
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
static void UpdateSoundParams(int slot, int vol, int sep, int pitch)
|
|
{
|
|
int rightvol;
|
|
int leftvol;
|
|
|
|
// Set stepping
|
|
channelstep[slot] = S_sfx[channelids[slot]].sample_rate * steptable[pitch] / output_sample_rate;
|
|
channelstepremainder[slot] = 0;
|
|
|
|
// Separation, that is, orientation/stereo.
|
|
// range is: 1 - 256
|
|
sep += 1;
|
|
|
|
// Per left/right channel.
|
|
// x^2 seperation,
|
|
// adjust volume properly.
|
|
leftvol =
|
|
vol - ((vol*sep*sep) / (1 << 16)); ///(256*256);
|
|
sep = sep - 257;
|
|
rightvol =
|
|
vol - ((vol*sep*sep) / (1 << 16));
|
|
|
|
#ifdef RANGECHECK
|
|
// Sanity check, clamp volume.
|
|
if (rightvol < 0 || rightvol > 127)
|
|
I_Error("rightvol out of bounds");
|
|
|
|
if (leftvol < 0 || leftvol > 127)
|
|
I_Error("leftvol out of bounds");
|
|
#endif
|
|
|
|
// Get the proper lookup table piece
|
|
// for this volume level
|
|
channelleftvol_lookup[slot] = vol_lookup[leftvol];
|
|
channelrightvol_lookup[slot] = vol_lookup[rightvol];
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// This function loads the sound data from the WAD lump,
|
|
// for a single sound.
|
|
//
|
|
static void getsfx(sfxinfo_t* sfxinfo, size_t* len)
|
|
{
|
|
unsigned char* sfx;
|
|
size_t size;
|
|
char name[20];
|
|
int sfxlump;
|
|
|
|
// Get the sound data from the WAD, allocate lump
|
|
// in zone memory.
|
|
sprintf(name, "ds%s", sfxinfo->name);
|
|
|
|
// Now, there is a severe problem with the
|
|
// sound handling, in it is not (yet/anymore)
|
|
// gamemode aware. That means, sounds from
|
|
// DOOM II will be requested even with DOOM
|
|
// shareware.
|
|
// The sound list is wired into sounds.c,
|
|
// which sets the external variable.
|
|
// I do not do runtime patches to that
|
|
// variable. Instead, we will use a
|
|
// default sound for replacement.
|
|
if (W_CheckNumForName(name) == -1)
|
|
sfxlump = W_GetNumForName("dspistol");
|
|
else
|
|
sfxlump = W_GetNumForName(name);
|
|
|
|
size = W_LumpLength(sfxlump);
|
|
|
|
sfx = (unsigned char*)W_CacheLumpNum(sfxlump, PU_STATIC);
|
|
|
|
*len = size-8-16-16;
|
|
|
|
sfxinfo->data = (void*)(sfx+8+16);
|
|
|
|
sfxinfo->sample_rate = sfx[2] | (sfx[3] << 8);
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// SFX API
|
|
//
|
|
|
|
//
|
|
// This used to set DMX's internal
|
|
// channel count and sample rate, it seems.
|
|
//
|
|
void I_SetChannels(int channels)
|
|
{
|
|
(void)channels;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// Retrieve the raw data lump index
|
|
// for a given SFX name.
|
|
//
|
|
int I_GetSfxLumpNum(const sfxinfo_t* sfx)
|
|
{
|
|
char namebuf[9];
|
|
sprintf(namebuf, "ds%s", sfx->name);
|
|
return W_GetNumForName(namebuf);
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
int I_StartSound(int id, int vol, int sep, int pitch)
|
|
{
|
|
static unsigned short handlenums = 0;
|
|
|
|
int i;
|
|
int rc = -1;
|
|
|
|
int oldest = gametic;
|
|
int oldestnum = 0;
|
|
int slot;
|
|
|
|
IB_LockSound();
|
|
|
|
// Chainsaw troubles.
|
|
// Play these sound effects only one at a time.
|
|
if (id == sfx_sawup
|
|
|| id == sfx_sawidl
|
|
|| id == sfx_sawful
|
|
|| id == sfx_sawhit
|
|
|| id == sfx_stnmov
|
|
|| id == sfx_pistol)
|
|
{
|
|
// Loop all channels, check.
|
|
for (i=0 ; i<NUM_CHANNELS ; ++i)
|
|
{
|
|
// Active, and using the same SFX?
|
|
if (channels[i] != NULL && channelids[i] == id)
|
|
{
|
|
// Reset.
|
|
channels[i] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop all channels to find oldest SFX.
|
|
for (i=0; i<NUM_CHANNELS && channels[i] != NULL; ++i)
|
|
{
|
|
if (channelstart[i] < oldest)
|
|
{
|
|
oldestnum = i;
|
|
oldest = channelstart[i];
|
|
}
|
|
}
|
|
|
|
// Tales from the cryptic.
|
|
// If we found a channel, fine.
|
|
// If not, we simply overwrite the first one, 0.
|
|
// Probably only happens at startup.
|
|
if (i == NUM_CHANNELS)
|
|
slot = oldestnum;
|
|
else
|
|
slot = i;
|
|
|
|
// Okay, in the less recent channel,
|
|
// we will handle the new SFX.
|
|
// Set pointer to raw data.
|
|
channels[slot] = (unsigned char*)S_sfx[id].data;
|
|
// Set pointer to end of raw data.
|
|
channelsend[slot] = channels[slot] + lengths[id];
|
|
|
|
// Disable handle numbers 0-99 (were they error values in DMX?).
|
|
if (handlenums == 0)
|
|
handlenums = 100;
|
|
|
|
// Assign current handle number.
|
|
// Preserved so sounds could be stopped.
|
|
channelhandles[slot] = rc = handlenums++;
|
|
|
|
// Should be gametic, I presume.
|
|
channelstart[slot] = gametic;
|
|
|
|
// Preserve sound SFX id,
|
|
// e.g. for avoiding duplicates of chainsaw.
|
|
channelids[slot] = id;
|
|
|
|
UpdateSoundParams(slot, vol, sep, pitch);
|
|
|
|
IB_UnlockSound();
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
|
|
void I_StopSound(int handle)
|
|
{
|
|
int i;
|
|
|
|
for (i=0;i<NUM_CHANNELS;++i)
|
|
{
|
|
if (channelhandles[i] == handle)
|
|
{
|
|
IB_LockSound();
|
|
|
|
channels[i] = NULL;
|
|
|
|
IB_UnlockSound();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
boolean I_SoundIsPlaying(int handle)
|
|
{
|
|
int i;
|
|
|
|
for (i=0;i<NUM_CHANNELS;++i)
|
|
{
|
|
if (channelhandles[i] == handle)
|
|
{
|
|
IB_LockSound();
|
|
|
|
boolean playing = channels[i] != NULL;
|
|
|
|
IB_UnlockSound();
|
|
|
|
return playing;
|
|
}
|
|
}
|
|
|
|
return 0; // Sound doesn't exist
|
|
}
|
|
|
|
|
|
|
|
|
|
void I_UpdateSoundParams(int handle, int vol, int sep, int pitch)
|
|
{
|
|
int i;
|
|
|
|
for (i=0;i<NUM_CHANNELS;++i)
|
|
{
|
|
if (channelhandles[i] == handle)
|
|
{
|
|
IB_LockSound();
|
|
|
|
UpdateSoundParams(i, vol, sep, pitch);
|
|
|
|
IB_UnlockSound();
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
static void StartupCallback(unsigned int _output_sample_rate, void *user_data)
|
|
{
|
|
(void)user_data;
|
|
|
|
output_sample_rate = _output_sample_rate;
|
|
|
|
#ifdef WILDMIDI
|
|
if (WildMidi_Init(wildmidi_config_path, output_sample_rate, 0) == 0)
|
|
music_initialised = true;
|
|
else
|
|
fprintf(stderr, "I_StartupSound: Failed to initialize WildMIDI. Is %s missing?\n", wildmidi_config_path);
|
|
#endif
|
|
}
|
|
|
|
void I_StartupSound(void)
|
|
{
|
|
int i;
|
|
int j;
|
|
|
|
//
|
|
// Init internal lookups (raw data, mixing buffer, channels).
|
|
//
|
|
|
|
// This table provides step widths for pitch parameters.
|
|
for (i=0 ; i<256 ; ++i)
|
|
steptable[i] = (long)(pow(2.0, ((i-128)/64.0))*65536.0);
|
|
|
|
// Generates volume lookup tables
|
|
// which also turn the unsigned samples
|
|
// into signed samples.
|
|
for (i=0 ; i<128 ; ++i)
|
|
for (j=0 ; j<256 ; ++j)
|
|
vol_lookup[i][j] = (i*(j-128)*256)/127;
|
|
|
|
if (!IB_StartupSound(StartupCallback, AudioCallback, NULL))
|
|
{
|
|
//I_Error("I_StartupSound: Failed to initialize backend");
|
|
log_warn("I_StartupSound: Failed to initialize backend");
|
|
}
|
|
|
|
//
|
|
// Cache sounds.
|
|
//
|
|
|
|
// Initialize external data (all sounds) at start, keep static.
|
|
|
|
for (i=1 ; i<NUMSFX ; ++i)
|
|
{
|
|
// Alias? Example is the chaingun sound linked to pistol.
|
|
if (S_sfx[i].link == NULL)
|
|
{
|
|
// Load data from WAD file.
|
|
getsfx(&S_sfx[i], &lengths[i]);
|
|
}
|
|
else
|
|
{
|
|
// Previously loaded already?
|
|
S_sfx[i].data = S_sfx[i].link->data;
|
|
lengths[i] = lengths[(S_sfx[i].link - S_sfx)/sizeof(sfxinfo_t)];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void I_ShutdownSound(void)
|
|
{
|
|
IB_ShutdownSound();
|
|
|
|
#ifdef WILDMIDI
|
|
if (music_initialised)
|
|
{
|
|
WildMidi_Shutdown();
|
|
music_initialised = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// MUSIC API.
|
|
//
|
|
|
|
void I_PlaySong(int handle, boolean looping)
|
|
{
|
|
// UNUSED.
|
|
(void)handle;
|
|
|
|
#ifndef WILDMIDI
|
|
(void)looping;
|
|
#else
|
|
if (music_initialised)
|
|
{
|
|
IB_LockSound();
|
|
|
|
music_playing = true;
|
|
WildMidi_SetOption(music_midi, WM_MO_LOOP, looping ? WM_MO_LOOP : 0);
|
|
|
|
IB_UnlockSound();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
void I_PauseSong (int handle)
|
|
{
|
|
// UNUSED.
|
|
(void)handle;
|
|
|
|
#ifdef WILDMIDI
|
|
if (music_initialised)
|
|
{
|
|
IB_LockSound();
|
|
|
|
music_playing = false;
|
|
|
|
IB_UnlockSound();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
void I_ResumeSong (int handle)
|
|
{
|
|
// UNUSED.
|
|
(void)handle;
|
|
|
|
#ifdef WILDMIDI
|
|
if (music_initialised)
|
|
{
|
|
IB_LockSound();
|
|
|
|
music_playing = true;
|
|
|
|
IB_UnlockSound();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
void I_StopSong(int handle)
|
|
{
|
|
// UNUSED.
|
|
(void)handle;
|
|
|
|
#ifdef WILDMIDI
|
|
if (music_initialised)
|
|
{
|
|
IB_LockSound();
|
|
|
|
music_playing = false;
|
|
WildMidi_FastSeek(music_midi, 0);
|
|
|
|
IB_UnlockSound();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
void I_UnRegisterSong(int handle)
|
|
{
|
|
// UNUSED.
|
|
(void)handle;
|
|
|
|
#ifdef WILDMIDI
|
|
if (music_initialised)
|
|
{
|
|
IB_LockSound();
|
|
|
|
WildMidi_Close(music_midi);
|
|
|
|
IB_UnlockSound();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
int I_RegisterSong(const void* data, size_t size)
|
|
{
|
|
#ifndef WILDMIDI
|
|
(void)data;
|
|
(void)size;
|
|
#else
|
|
if (music_initialised)
|
|
{
|
|
IB_LockSound();
|
|
|
|
music_midi = WildMidi_OpenBuffer((void*)data, size);
|
|
|
|
IB_UnlockSound();
|
|
}
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
|
|
// Is the song playing? (unused)
|
|
int I_QrySongPlaying(int handle)
|
|
{
|
|
// UNUSED.
|
|
(void)handle;
|
|
|
|
#ifndef WILDMIDI
|
|
return 0;
|
|
#else
|
|
IB_LockSound();
|
|
|
|
boolean playing = music_playing;
|
|
|
|
IB_UnlockSound();
|
|
|
|
return playing;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
void I_SetMusicVolume(int volume)
|
|
{
|
|
// Internal state variable.
|
|
//snd_MusicVolume = volume;
|
|
// Now set volume on output device.
|
|
#ifndef WILDMIDI
|
|
(void)volume;
|
|
#else
|
|
if (music_initialised)
|
|
{
|
|
IB_LockSound();
|
|
|
|
WildMidi_MasterVolume(volume);
|
|
|
|
IB_UnlockSound();
|
|
}
|
|
#endif
|
|
}
|