//---------------------------------------------------------------------------- // Sound Blitter //---------------------------------------------------------------------------- // // Copyright (c) 1999-2007 The EDGE Team. // // 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. // //---------------------------------------------------------------------------- // // Based on the DOOM source code, released by Id Software under the // following copyright: // // Copyright (C) 1993-1996 by id Software, Inc. // //---------------------------------------------------------------------------- #include "i_defs.h" #include "i_sdlinc.h" #include "s_sound.h" #include "s_cache.h" #include "s_blit.h" #include "s_music.h" #include "m_misc.h" #include "r_main.h" // R_PointToAngle #include "p_local.h" // P_ApproxDistance #include #include // Sound must be clipped to prevent distortion (clipping is // a kind of distortion of course, but it's much better than // the "white noise" you get when values overflow). // // The more safe bits there are, the less likely the final // output sum will overflow into white noise, but the less // precision you have left for the volume multiplier. #define SAFE_BITS 3 #define CLIP_THRESHHOLD ((1L << (31-SAFE_BITS)) - 1) // This value is how much breathing room there is until // sounds start clipping. More bits means less chance // of clipping, but every extra bit makes the sound half // as loud as before. #define QUIET_BITS 0 #define MIN_CHANNELS 8 #define MAX_CHANNELS 128 mix_channel_c *mix_chan[MAX_CHANNELS]; int num_chan; static int *mix_buffer; static int mix_buf_len; #define MAX_QUEUE_BUFS 16 static std::list free_qbufs; static std::list playing_qbufs; static mix_channel_c *queue_chan; int sfx_volume = 0; static bool sfxpaused = false; // these are analogous to viewx/y/z/angle float listen_x; float listen_y; float listen_z; angle_t listen_angle; // -AJA- 2005/02/26: table to convert slider position to GAIN. // Curve was hand-crafted to give useful distinctions of // volume levels at the quiet end. Entry zero always // means total silence (in the table for completeness). float slider_to_gain[SND_SLIDER_NUM] = { 0.00000, 0.00200, 0.00400, 0.00800, 0.01600, 0.03196, 0.05620, 0.08886, 0.12894, 0.17584, 0.22855, 0.28459, 0.34761, 0.41788, 0.49553, 0.58075, 0.67369, 0.77451, 0.88329, 1.00000 }; // FIXME: extern == hack extern int dev_freq; extern int dev_bits; extern int dev_bytes_per_sample; extern int dev_frag_pairs; extern bool dev_signed; extern bool dev_stereo; mix_channel_c::mix_channel_c() : state(CHAN_Empty), data(NULL) { } mix_channel_c::~mix_channel_c() { } void mix_channel_c::ComputeDelta() { // frequency close enough ? if (data->freq > (dev_freq - dev_freq/100) && data->freq < (dev_freq + dev_freq/100)) { delta = (1 << 10); } else { delta = (fixed22_t) floor((float)data->freq * 1024.0f / dev_freq); } } void mix_channel_c::ComputeVolume() { float sep = 0.5f; float mul = 1.0f; if (pos && category >= SNCAT_Opponent) { if (dev_stereo) { angle_t angle = R_PointToAngle(listen_x, listen_y, pos->x, pos->y); // same equation from original DOOM sep = 0.5f - 0.38f * M_Sin(angle - listen_angle); } if (! boss) { float dist = P_ApproxDistance(listen_x - pos->x, listen_y - pos->y, listen_z - pos->z); // -AJA- this equation was chosen to mimic the DOOM falloff // function, but not cutting out @ dist=1600, instead // tapering off exponentially. mul = exp(-MAX(1.0f, dist - S_CLOSE_DIST) / 800.0f); } } float MAX_VOL = (1 << (16 - SAFE_BITS - MAX(0,var_quiet_factor-1))) - 3; if (var_quiet_factor == 0) MAX_VOL *= 2.0; MAX_VOL = MAX_VOL * mul * slider_to_gain[sfx_volume]; if (def) MAX_VOL *= PERCENT_2_FLOAT(def->volume); // strictly linear equations volume_L = (int) (MAX_VOL * (1.0 - sep)); volume_R = (int) (MAX_VOL * (0.0 + sep)); if (var_sound_stereo == 2) /* SWAP ! */ { int tmp = volume_L; volume_L = volume_R; volume_R = tmp; } } void mix_channel_c::ComputeMusicVolume() { float MAX_VOL = (1 << (16 - SAFE_BITS - var_quiet_factor)) - 3; MAX_VOL = MAX_VOL * slider_to_gain[mus_volume]; volume_L = (int) MAX_VOL; volume_R = (int) MAX_VOL; } //---------------------------------------------------------------------------- static void BlitToU8(const int *src, u8_t *dest, int length) { const int *s_end = src + length; while (src < s_end) { int val = *src++; if (val > CLIP_THRESHHOLD) val = CLIP_THRESHHOLD; else if (val < -CLIP_THRESHHOLD) val = -CLIP_THRESHHOLD; *dest++ = (u8_t) ((val >> (24-SAFE_BITS)) ^ 0x80); } } static void BlitToS8(const int *src, s8_t *dest, int length) { const int *s_end = src + length; while (src < s_end) { int val = *src++; if (val > CLIP_THRESHHOLD) val = CLIP_THRESHHOLD; else if (val < -CLIP_THRESHHOLD) val = -CLIP_THRESHHOLD; *dest++ = (s8_t) (val >> (24-SAFE_BITS)); } } static void BlitToU16(const int *src, u16_t *dest, int length) { const int *s_end = src + length; while (src < s_end) { int val = *src++; if (val > CLIP_THRESHHOLD) val = CLIP_THRESHHOLD; else if (val < -CLIP_THRESHHOLD) val = -CLIP_THRESHHOLD; *dest++ = (u16_t) ((val >> (16-SAFE_BITS)) ^ 0x8000); } } static void BlitToS16(const int *src, s16_t *dest, int length) { const int *s_end = src + length; while (src < s_end) { int val = *src++; if (val > CLIP_THRESHHOLD) val = CLIP_THRESHHOLD; else if (val < -CLIP_THRESHHOLD) val = -CLIP_THRESHHOLD; *dest++ = (s16_t) (val >> (16-SAFE_BITS)); } } static void MixMono(mix_channel_c *chan, int *dest, int pairs) { SYS_ASSERT(pairs > 0); const s16_t *src_L = chan->data->data_L; int *d_pos = dest; int *d_end = d_pos + pairs; fixed22_t offset = chan->offset; while (d_pos < d_end) { *d_pos++ += src_L[offset >> 10] * chan->volume_L; offset += chan->delta; } chan->offset = offset; SYS_ASSERT(offset - chan->delta < chan->length); } static void MixStereo(mix_channel_c *chan, int *dest, int pairs) { SYS_ASSERT(pairs > 0); const s16_t *src_L = chan->data->data_L; const s16_t *src_R = chan->data->data_R; int *d_pos = dest; int *d_end = d_pos + pairs * 2; fixed22_t offset = chan->offset; while (d_pos < d_end) { *d_pos++ += src_L[offset >> 10] * chan->volume_L; *d_pos++ += src_R[offset >> 10] * chan->volume_R; offset += chan->delta; } chan->offset = offset; SYS_ASSERT(offset - chan->delta < chan->length); } static void MixInterleaved(mix_channel_c *chan, int *dest, int pairs) { if (! dev_stereo) I_Error("INTERNAL ERROR: tried to mix an interleaved buffer in MONO mode.\n"); SYS_ASSERT(pairs > 0); const s16_t *src_L = chan->data->data_L; int *d_pos = dest; int *d_end = d_pos + pairs * 2; fixed22_t offset = chan->offset; while (d_pos < d_end) { register fixed22_t pos = (offset >> 9) & ~1; *d_pos++ += src_L[pos ] * chan->volume_L; *d_pos++ += src_L[pos|1] * chan->volume_R; offset += chan->delta; } chan->offset = offset; SYS_ASSERT(offset - chan->delta < chan->length); } static void MixOneChannel(mix_channel_c *chan, int pairs) { if (sfxpaused && chan->category >= SNCAT_Player) return; if (chan->volume_L == 0 && chan->volume_R == 0) return; SYS_ASSERT(chan->offset < chan->length); int *dest = mix_buffer; while (pairs > 0) { int count = pairs; // check if enough sound data is left if (chan->offset + count * chan->delta >= chan->length) { // find minimum number of samples we can play double avail = (chan->length - chan->offset + chan->delta - 1) / (double)chan->delta; count = (int)floor(avail); SYS_ASSERT(count > 0); SYS_ASSERT(count <= pairs); SYS_ASSERT(chan->offset + count * chan->delta >= chan->length); } if (chan->data->mode == SBUF_Interleaved) MixInterleaved(chan, dest, count); else if (dev_stereo) MixStereo(chan, dest, count); else MixMono(chan, dest, count); if (chan->offset >= chan->length) { if (! chan->loop) { chan->state = CHAN_Finished; break; } // we are looping, so clear flag. The sound needs to // be "pumped" (played again) to continue looping. chan->loop = false; chan->offset = 0; } dest += count * (dev_stereo ? 2 : 1); pairs -= count; } } static bool QueueNextBuffer(void) { if (playing_qbufs.empty()) { queue_chan->state = CHAN_Finished; queue_chan->data = NULL; return false; } fx_data_c *buf = playing_qbufs.front(); queue_chan->data = buf; queue_chan->offset = 0; queue_chan->length = buf->length << 10; queue_chan->ComputeDelta(); queue_chan->state = CHAN_Playing; return true; } static void MixQueues(int pairs) { mix_channel_c *chan = queue_chan; if (! chan || chan->state != CHAN_Playing) return; if (chan->volume_L == 0 && chan->volume_R == 0) return; SYS_ASSERT(chan->offset < chan->length); int *dest = mix_buffer; while (pairs > 0) { int count = pairs; // check if enough sound data is left if (chan->offset + count * chan->delta >= chan->length) { // find minimum number of samples we can play double avail = (chan->length - chan->offset + chan->delta - 1) / (double)chan->delta; count = (int)floor(avail); SYS_ASSERT(count > 0); SYS_ASSERT(count <= pairs); SYS_ASSERT(chan->offset + count * chan->delta >= chan->length); } if (chan->data->mode == SBUF_Interleaved) MixInterleaved(chan, dest, count); else if (dev_stereo) MixStereo(chan, dest, count); else MixMono(chan, dest, count); if (chan->offset >= chan->length) { // reached end of current queued buffer. // Place current buffer onto free list, // and enqueue the next buffer to play. SYS_ASSERT(! playing_qbufs.empty()); fx_data_c *buf = playing_qbufs.front(); playing_qbufs.pop_front(); free_qbufs.push_back(buf); if (! QueueNextBuffer()) break; } dest += count * (dev_stereo ? 2 : 1); pairs -= count; } } void S_MixAllChannels(void *stream, int len) { if (nosound || len <= 0) return; int pairs = len / dev_bytes_per_sample; int samples = pairs; if (dev_stereo) samples *= 2; // check that we're not getting too much data SYS_ASSERT(pairs <= dev_frag_pairs); SYS_ASSERT(mix_buffer && samples <= mix_buf_len); // clear mixer buffer memset(mix_buffer, 0, mix_buf_len * sizeof(int)); #if 0 // TESTING.. TESTING.. mix_buffer[ 0] = CLIP_THRESHHOLD; mix_buffer[33] = -CLIP_THRESHHOLD; #endif // add each channel for (int i=0; i < num_chan; i++) { if (mix_chan[i]->state == CHAN_Playing) { MixOneChannel(mix_chan[i], pairs); } } MixQueues(pairs); // blit to the SDL stream if (dev_bits == 8) { if (dev_signed) BlitToS8(mix_buffer, (s8_t *)stream, samples); else BlitToU8(mix_buffer, (u8_t *)stream, samples); } else { if (dev_signed) BlitToS16(mix_buffer, (s16_t *)stream, samples); else BlitToU16(mix_buffer, (u16_t *)stream, samples); } } //---------------------------------------------------------------------------- void S_InitChannels(int total) { // NOTE: assumes audio is locked! SYS_ASSERT(total >= MIN_CHANNELS); SYS_ASSERT(total <= MAX_CHANNELS); num_chan = total; for (int i = 0; i < num_chan; i++) mix_chan[i] = new mix_channel_c(); // allocate mixer buffer mix_buf_len = dev_frag_pairs * (dev_stereo ? 2 : 1); mix_buffer = new int[mix_buf_len]; } void S_FreeChannels(void) { // NOTE: assumes audio is locked! for (int i = 0; i < num_chan; i++) { mix_channel_c *chan = mix_chan[i]; if (chan && chan->data) { S_CacheRelease(chan->data); chan->data = NULL; } delete chan; } memset(mix_chan, 0, sizeof(mix_chan)); } void S_KillChannel(int k) { mix_channel_c *chan = mix_chan[k]; if (chan->state != CHAN_Empty) { S_CacheRelease(chan->data); chan->data = NULL; chan->state = CHAN_Empty; } } void S_ReallocChannels(int total) { // NOTE: assumes audio is locked! SYS_ASSERT(total >= MIN_CHANNELS); SYS_ASSERT(total <= MAX_CHANNELS); if (total > num_chan) { for (int i = num_chan; i < total; i++) mix_chan[i] = new mix_channel_c(); } if (total < num_chan) { // kill all non-UI sounds, pack the UI sounds into the // remaining slots (normally there will be enough), and // delete the unused channels int i, j; for (i = 0; i < num_chan; i++) { mix_channel_c *chan = mix_chan[i]; if (chan->state == CHAN_Playing) { if (chan->category != SNCAT_UI) S_KillChannel(i); } } for (i = j = 0; i < num_chan; i++) { if (mix_chan[i]) { /* SWAP ! */ mix_channel_c *tmp = mix_chan[j]; mix_chan[j] = mix_chan[i]; mix_chan[i] = tmp; } } for (i = total; i < num_chan; i++) { if (mix_chan[i]->state == CHAN_Playing) S_KillChannel(i); delete mix_chan[i]; mix_chan[i] = NULL; } } num_chan = total; } void S_UpdateSounds(position_c *listener, angle_t angle) { // NOTE: assume SDL_LockAudio has been called listen_x = listener ? listener->x : 0; listen_y = listener ? listener->y : 0; listen_z = listener ? listener->z : 0; listen_angle = angle; for (int i = 0; i < num_chan; i++) { mix_channel_c *chan = mix_chan[i]; if (chan->state == CHAN_Playing) chan->ComputeVolume(); else if (chan->state == CHAN_Finished) S_KillChannel(i); } if (queue_chan) queue_chan->ComputeMusicVolume(); } ///---int S_GetSoundVolume(void) ///---{ ///--- return sfxvolume; ///---} void S_ChangeSoundVolume(void) { /* nothing to do */ } void S_PauseSound(void) { sfxpaused = true; } void S_ResumeSound(void) { sfxpaused = false; } //---------------------------------------------------------------------------- void S_QueueInit(void) { if (nosound) return; I_LockAudio(); { if (free_qbufs.empty()) { for (int i=0; i < MAX_QUEUE_BUFS; i++) { free_qbufs.push_back(new fx_data_c()); } } if (! queue_chan) queue_chan = new mix_channel_c(); queue_chan->state = CHAN_Empty; queue_chan->data = NULL; queue_chan->ComputeMusicVolume(); } I_UnlockAudio(); } void S_QueueShutdown(void) { if (nosound) return; I_LockAudio(); { if (queue_chan) { // free all data on the playing / free lists. // The fx_data_c destructor takes care of data_L/R. for (; ! playing_qbufs.empty(); playing_qbufs.pop_front()) { delete playing_qbufs.front(); } for (; ! free_qbufs.empty(); free_qbufs.pop_front()) { delete free_qbufs.front(); } queue_chan->data = NULL; delete queue_chan; queue_chan = NULL; } } I_UnlockAudio(); } void S_QueueStop(void) { if (nosound) return; SYS_ASSERT(queue_chan); I_LockAudio(); { for (; ! playing_qbufs.empty(); playing_qbufs.pop_front()) { free_qbufs.push_back(playing_qbufs.front()); } queue_chan->state = CHAN_Finished; queue_chan->data = NULL; } I_UnlockAudio(); } fx_data_c * S_QueueGetFreeBuffer(int samples, int buf_mode) { if (nosound) return NULL; fx_data_c *buf = NULL; I_LockAudio(); { if (! free_qbufs.empty()) { buf = free_qbufs.front(); free_qbufs.pop_front(); buf->Allocate(samples, buf_mode); } } I_UnlockAudio(); return buf; } void S_QueueAddBuffer(fx_data_c *buf, int freq) { SYS_ASSERT(! nosound); SYS_ASSERT(buf); I_LockAudio(); { buf->freq = freq; playing_qbufs.push_back(buf); if (queue_chan->state != CHAN_Playing) { QueueNextBuffer(); } } I_UnlockAudio(); } void S_QueueReturnBuffer(fx_data_c *buf) { SYS_ASSERT(! nosound); SYS_ASSERT(buf); I_LockAudio(); { free_qbufs.push_back(buf); } I_UnlockAudio(); } //--- editor settings --- // vi:ts=4:sw=4:noexpandtab