quakeforge/libs/audio/renderer/snd_dma.c
Bill Currie 91140acfee [sound] Access the buffer directly from the channel
This improves the locality of reference when mixing and removes the
proxy sfx for streamed sounds.

The buffer for streamed sounds is allocated when the stream is opened
(since streamed sounds can't share buffers), and freed when the stream
is closed.

For block sounds, the buffer is reference counted (with the sfx holding
one reference, so currently block buffers never get freed), with their
reference count getting incremented on open and decremented on close.
That the reference counts get to 1 has been confirmed, so all that
should be needed is proper destruction of the sfx instances.

Still need to sort out just why channels leak across level changes.
2022-06-06 12:39:54 +09:00

608 lines
13 KiB
C

/*
snd_default.c
main control for any streaming sound output device
Copyright (C) 1996-1997 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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <inttypes.h>
#include <stdlib.h>
#include "winquake.h"
#include "QF/cmd.h"
#include "QF/cvar.h"
#include "QF/dstring.h"
#include "QF/model.h"
#include "QF/qargs.h"
#include "QF/sys.h"
#include "QF/sound.h"
#include "QF/plugin.h"
#include "QF/va.h"
#include "QF/quakefs.h"
#include "snd_internal.h"
static int snd_blocked = 0;
static unsigned soundtime; // sample PAIRS
static int sound_started = 0;
float snd_volume;
static cvar_t snd_volume_cvar = {
.name = "volume",
.description =
"Set the volume for sound playback",
.default_value = "0.7",
.flags = CVAR_ARCHIVE,
.value = { .type = &cexpr_float, .value = &snd_volume },
};
static int nosound;
static cvar_t nosound_cvar = {
.name = "nosound",
.description =
"Set to turn sound off",
.default_value = "0",
.flags = CVAR_NONE,
.value = { .type = &cexpr_int, .value = &nosound },
};
static float snd_mixahead;
static cvar_t snd_mixahead_cvar = {
.name = "snd_mixahead",
.description =
"Delay time for sounds",
.default_value = "0.1",
.flags = CVAR_ARCHIVE,
.value = { .type = &cexpr_float, .value = &snd_mixahead },
};
static int snd_noextraupdate;
static cvar_t snd_noextraupdate_cvar = {
.name = "snd_noextraupdate",
.description =
"Toggles the correct value display in host_speeds. Usually messes up "
"sound playback when in effect",
.default_value = "0",
.flags = CVAR_NONE,
.value = { .type = &cexpr_int, .value = &snd_noextraupdate },
};
static int snd_show;
static cvar_t snd_show_cvar = {
.name = "snd_show",
.description =
"Toggles display of sounds currently being played",
.default_value = "0",
.flags = CVAR_NONE,
.value = { .type = &cexpr_int, .value = &snd_show },
};
static general_data_t plugin_info_general_data;
static snd_output_funcs_t *snd_output_funcs;
static snd_output_data_t *snd_output_data;
static snd_t snd = {
.finish_channels = SND_FinishChannels,
.paint_channels = SND_PaintChannels,
};
static int snd_shutdown = 0;
static void
s_xfer_paint_buffer (snd_t *snd, portable_samplepair_t *paintbuffer, int count,
float volume)
{
int out_idx, out_max, step, val;
float *p;
p = (float *) paintbuffer;
count *= snd->channels;
out_max = (snd->frames * snd->channels) - 1;
out_idx = snd->paintedtime * snd->channels;
while (out_idx > out_max)
out_idx -= out_max + 1;
step = 3 - snd->channels;
if (snd->samplebits == 16) {
short *out = (short *) snd->buffer;
while (count--) {
val = (*p * volume) * 0x8000;
p += step;
if (val > 0x7fff)
val = 0x7fff;
else if (val < -0x8000)
val = -0x8000;
out[out_idx++] = val;
if (out_idx > out_max)
out_idx = 0;
}
} else if (snd->samplebits == 8) {
unsigned char *out = (unsigned char *) snd->buffer;
while (count--) {
val = (*p * volume) * 128;
p += step;
if (val > 0x7f)
val = 0x7f;
else if (val < -0x80)
val = -0x80;
out[out_idx++] = val + 0x80;
if (out_idx > out_max)
out_idx = 0;
}
}
}
static void
s_clear_buffer (snd_t *snd)
{
int clear, i;
int count;
if (!sound_started || !snd || !snd->buffer)
return;
if (snd->samplebits == 8)
clear = 0x80;
else
clear = 0;
count = snd->frames * snd->channels * snd->samplebits / 8;
for (i = 0; i < count; i++)
snd->buffer[i] = clear;
}
static void
s_stop_all_sounds (void)
{
SND_StopAllSounds (&snd);
SND_ScanChannels (&snd, 0);
s_clear_buffer (&snd);
}
//=============================================================================
static void
s_get_soundtime (void)
{
int frames, framepos;
static int buffers, oldframepos;
frames = snd.frames;
// it is possible to miscount buffers if it has wrapped twice between
// calls to s_update. Oh well.
if ((framepos = snd_output_funcs->get_dma_pos (&snd)) == -1)
return;
if (framepos < oldframepos) {
buffers++; // buffer wrapped
if (snd.paintedtime > 0x40000000) { // time to chop things off to avoid
// 32 bit limits
buffers = 0;
snd.paintedtime = frames;
s_stop_all_sounds ();
}
}
oldframepos = framepos;
soundtime = buffers * frames + framepos;
}
static void
s_update_ (void)
{
unsigned int endtime, samps;
if (!sound_started || (snd_blocked > 0))
return;
// Updates DMA time
s_get_soundtime ();
// check to make sure that we haven't overshot
if (snd.paintedtime < soundtime) {
// Sys_Printf ("S_Update_ : overflow\n");
snd.paintedtime = soundtime;
}
// mix ahead of current position
endtime = soundtime + snd_mixahead * snd.speed;
samps = snd.frames;
if (endtime - soundtime > samps)
endtime = soundtime + samps;
SND_PaintChannels (&snd, endtime);
snd_output_funcs->submit (&snd);
}
/*
s_update
Called once each time through the main loop
*/
static void
s_update (struct transform_s *ear, const byte *ambient_sound_level)
{
if (!sound_started || (snd_blocked > 0))
return;
if (snd_output_funcs->on_update) {
snd_output_funcs->on_update (&snd);
}
SND_SetListener (&snd, ear, ambient_sound_level);
if (snd_output_data->model == som_push) {
// mix some sound
s_update_ ();
SND_ScanChannels (&snd, 0);
}
}
static void
s_extra_update (void)
{
if (snd_output_data->model == som_push) {
if (!sound_started || snd_noextraupdate)
return; // don't pollute timings
s_update_ ();
}
}
static void
s_block_sound (void)
{
if (++snd_blocked == 1) {
snd_output_funcs->block_sound (&snd);
s_clear_buffer (&snd);
}
}
static void
s_unblock_sound (void)
{
if (!snd_blocked)
return;
if (!--snd_blocked) {
s_clear_buffer (&snd);
snd_output_funcs->unblock_sound (&snd);
}
}
/* console functions */
static void
s_soundinfo_f (void)
{
if (!sound_started) {
Sys_Printf ("sound system not started\n");
return;
}
Sys_Printf ("%5d channels\n", snd.channels);
Sys_Printf ("%5d frames (%.1fms)\n", snd.frames,
1000.0 * snd.frames / snd.speed);
Sys_Printf ("%5d framepos\n", snd.framepos);
Sys_Printf ("%5d samplebits\n", snd.samplebits);
Sys_Printf ("%5d submission_chunk (%.1fms)\n", snd.submission_chunk,
1000.0 * snd.submission_chunk / snd.speed);
Sys_Printf ("%5d speed\n", snd.speed);
Sys_Printf ("0x%"PRIxPTR" dma buffer\n", (intptr_t) snd.buffer);
Sys_Printf ("%5d total_channels\n", snd_total_channels);
}
static void
s_stop_all_sounds_f (void)
{
s_stop_all_sounds ();
}
static void
s_startup (void)
{
if (!SND_Memory_Init ()) {
return;
}
if (!snd_output_funcs->init (&snd)) {
Sys_Printf ("S_Startup: output init failed.\n");
return;
}
if (!snd.xfer)
snd.xfer = s_xfer_paint_buffer;
sound_started = 1;
}
static void
s_snd_force_unblock (void)
{
snd_blocked = 1;
s_unblock_sound ();
}
static void
s_init_cvars (void)
{
Cvar_Register (&nosound_cvar, 0, 0);
Cvar_Register (&snd_volume_cvar, 0, 0);
Cvar_Register (&snd_mixahead_cvar, 0, 0);
Cvar_Register (&snd_noextraupdate_cvar, 0, 0);
Cvar_Register (&snd_show_cvar, 0, 0);
SND_Memory_Init_Cvars ();
}
static void
s_init (void)
{
snd_output_funcs = snd_render_data.output->functions->snd_output;
snd_output_data = snd_render_data.output->data->snd_output;
snd_render_data.soundtime = &soundtime;
Sys_Printf ("\nSound Initialization\n");
Cmd_AddCommand ("stopsound", s_stop_all_sounds_f,
"Stops all sounds currently being played");
Cmd_AddCommand ("soundinfo", s_soundinfo_f,
"Report information on the sound system");
Cmd_AddCommand ("snd_force_unblock", s_snd_force_unblock,
"fix permanently blocked sound");
s_startup ();
if (sound_started == 0) // sound startup failed? Bail out.
return;
SND_SFX_Init (&snd);
SND_Channels_Init (&snd);
s_stop_all_sounds ();
}
static void
s_shutdown (void)
{
if (!sound_started)
return;
sound_started = 0;
snd_shutdown = 1;
snd_output_funcs->shutdown (&snd);
}
static void
s_ambient_off (void)
{
if (!sound_started)
return;
SND_AmbientOff (&snd);
}
static void
s_ambient_on (void)
{
if (!sound_started)
return;
SND_AmbientOn (&snd);
}
static void
s_static_sound (sfx_t *sfx, vec4f_t origin, float vol,
float attenuation)
{
if (!sound_started)
return;
SND_StaticSound (&snd, sfx, origin, vol, attenuation);
}
static void
s_start_sound (int entnum, int entchannel, sfx_t *sfx, vec4f_t origin,
float vol, float attenuation)
{
if (!sound_started)
return;
if (!snd_shutdown)
SND_StartSound (&snd, entnum, entchannel, sfx, origin, vol,
attenuation);
}
static void
s_stop_sound (int entnum, int entchannel)
{
if (!sound_started)
return;
SND_StopSound (&snd, entnum, entchannel);
}
static sfx_t *
s_precache_sound (const char *name)
{
if (!sound_started)
return 0;
return SND_PrecacheSound (&snd, name);
}
static sfx_t *
s_load_sound (const char *name)
{
if (!sound_started)
return 0;
return SND_LoadSound (&snd, name);
}
static void
s_channel_free (channel_t *chan)
{
if (!sound_started)
return;
SND_ChannelStop (&snd, chan);
}
static int
s_channel_set_sfx (channel_t *chan, sfx_t *sfx)
{
sfxbuffer_t *buffer = sfx->open (sfx);
if (!buffer) {
return 0;
}
chan->buffer = buffer;
return 1;
}
static void
s_channel_set_paused (channel_t *chan, int paused)
{
chan->pause = paused != 0;
}
static void
s_channel_set_looping (channel_t *chan, int looping)
{
// FIXME implement
}
static chan_state
s_channel_get_state (channel_t *chan)
{
// stop means the channel has been "freed" and is waiting for the mixer
// thread to be done with it, thus putting the channel in an invalid state
// from the user's point of view. ie, don't touch (user should set channel
// pointer to null).
if (!chan->stop) {
if (chan->done) {
// The mixer has finished mixing the channel (come to the end).
return chan_done;
}
if (!chan->buffer) {
// channel has not been started yet
return chan_pending;
}
if (chan->pause) {
return chan_paused;
}
return chan_playing;
}
return chan_invalid;
}
static void
s_channel_set_volume (channel_t *chan, float volume)
{
SND_ChannelSetVolume (chan, volume);
}
static void
s_local_sound (const char *sound)
{
if (!sound_started)
return;
if (!snd_shutdown)
SND_LocalSound (&snd, sound);
}
static channel_t *
s_alloc_channel (void)
{
if (!sound_started)
return 0;
if (!snd_shutdown)
return SND_AllocChannel (&snd);
return 0;
}
static general_funcs_t plugin_info_general_funcs = {
.init = s_init_cvars,
.shutdown = s_shutdown,
};
static snd_render_funcs_t plugin_info_render_funcs = {
.init = s_init,
.ambient_off = s_ambient_off,
.ambient_on = s_ambient_on,
.static_sound = s_static_sound,
.start_sound = s_start_sound,
.local_sound = s_local_sound,
.stop_sound = s_stop_sound,
.alloc_channel = s_alloc_channel,
.channel_free = s_channel_free,
.channel_set_sfx = s_channel_set_sfx,
.channel_set_paused = s_channel_set_paused,
.channel_set_looping = s_channel_set_looping,
.channel_get_state = s_channel_get_state,
.channel_set_volume = s_channel_set_volume,
.precache_sound = s_precache_sound,
.load_sound = s_load_sound,
.update = s_update,
.stop_all_sounds = s_stop_all_sounds,
.extra_update = s_extra_update,
.block_sound = s_block_sound,
.unblock_sound = s_unblock_sound,
};
static plugin_funcs_t plugin_info_funcs = {
.general = &plugin_info_general_funcs,
.snd_render = &plugin_info_render_funcs,
};
snd_render_data_t snd_render_data = {
.paintedtime = &snd.paintedtime,
};
static plugin_data_t plugin_info_data = {
.general = &plugin_info_general_data,
.snd_render = &snd_render_data,
};
static plugin_t plugin_info = {
qfp_snd_render,
0,
QFPLUGIN_VERSION,
"0.1",
"Sound Renderer",
"Copyright (C) 1996-1997 id Software, Inc.\n"
"Copyright (C) 1999,2000,2001 contributors of the QuakeForge "
"project\n"
"Please see the file \"AUTHORS\" for a list of contributors",
&plugin_info_funcs,
&plugin_info_data,
};
PLUGIN_INFO(snd_render, default)
{
return &plugin_info;
}