engine: client: add sound api

This commit is contained in:
TheEVolk
2026-04-04 16:08:31 +03:00
parent 255c9bcaed
commit 35998bce81
8 changed files with 190 additions and 5 deletions

67
common/sound_api.h Normal file
View File

@@ -0,0 +1,67 @@
/*
sound_api.h - Xash3D extension for client sound interface
Copyright (C) 2026
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 3 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.
*/
#ifndef SOUND_API_H
#define SOUND_API_H
#include "xash3d_types.h"
#define CL_SOUND_INTERFACE_VERSION 1
// sound handle (same as engine's sound_t)
typedef int sound_t;
struct dma_api_s;
struct listener_s;
// Forward decls (full def in engine/client/sound.h)
struct channel_s;
struct rawchan_s;
struct portable_samplepair_s;
// Engine-owned state: client reads only. Filled by engine, passed to pfnS_Init.
typedef struct snd_interface_state_s
{
const struct listener_s *listener;
const struct channel_s *channels; // &channels[0], contiguous
int total_channels;
struct rawchan_s *const *raw_channels; // rawchan_t*[]
int max_raw_channels;
} snd_interface_state_t;
// API from engine to client (client calls these)
typedef struct sound_api_s
{
qboolean (*CL_GetEntitySpatialization)( struct channel_s *ch );
struct sfx_s* (*S_GetSfxByHandle)( sound_t handle );
} sound_api_t;
// Callbacks from client to engine (engine calls these when custom sound is active)
typedef struct sound_interface_s
{
int version;
qboolean (*pfnS_Init)( snd_interface_state_t *state );
void (*pfnS_Shutdown)( void );
void (*pfnS_UpdateSound)( void );
/* Full paint: endtime (sample pairs), dma buffer, paintedtime in/out. Client does mix + transfer to dma.buffer. */
void (*pfnS_PaintChannels)( int endtime, struct dma_api_s *dma, int *paintedtime );
void (*pfnS_UpdateChannel)( int ch_idx, const struct channel_s *ch, sound_t handle ); // ch=NULL -> channel freed
void (*pfnS_UpdateRawChannel)( int raw_idx, struct rawchan_s *ch ); // ch=NULL -> channel freed
void (*pfnS_Spatialize)( struct channel_s *ch );
void (*pfnS_FreeSound)( struct sfx_s *sfx, sound_t handle );
} sound_interface_t;
#endif // SOUND_API_H

View File

@@ -23,6 +23,12 @@ struct mstudioevent_s;
struct engine_studio_api_s;
struct r_studio_interface_s;
struct sound_api_s;
struct sound_interface_s;
typedef struct sound_api_s sound_api_t;
typedef struct sound_interface_s sound_interface_t;
// NOTE: ordering is important!
typedef struct cldll_func_s
{
@@ -74,6 +80,8 @@ typedef struct cldll_func_s
int (*pfnTouchEvent)( int type, int fingerID, float x, float y, float dx, float dy );
void (*pfnMoveEvent)( float forwardmove, float sidemove );
void (*pfnLookEvent)( float relyaw, float relpitch );
// Sound API
int (*pfnGetSoundInterface)( int version, sound_api_t *api, sound_interface_t *callback );
} cldll_func_t;
#endif//CDLL_EXP_H

View File

@@ -92,6 +92,7 @@ static const dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and high
{ "HUD_VoiceStatus", (void **)&clgame.dllFuncs.pfnVoiceStatus },
{ "HUD_ChatInputPosition", (void **)&clgame.dllFuncs.pfnChatInputPosition },
{ "HUD_GetRenderInterface", (void **)&clgame.dllFuncs.pfnGetRenderInterface }, // Xash3D ext
{ "HUD_GetSoundInterface", (void **)&clgame.dllFuncs.pfnGetSoundInterface }, // Xash3D ext
{ "HUD_ClipMoveToEntity", (void **)&clgame.dllFuncs.pfnClipMoveToEntity }, // Xash3D ext
{ "IN_ClientTouchEvent", (void **)&clgame.dllFuncs.pfnTouchEvent}, // Xash3D FWGS ext
{ "IN_ClientMoveEvent", (void **)&clgame.dllFuncs.pfnMoveEvent}, // Xash3D FWGS ext

View File

@@ -3678,8 +3678,6 @@ void CL_Init( void )
CL_InitLocal();
VID_Init(); // init video
S_Init(); // init sound
Voice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device)
// unreliable buffer. unsed for unreliable commands and voice stream
MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf ));
@@ -3689,6 +3687,9 @@ void CL_Init( void )
if( !CL_LoadProgs( libpath ))
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( ));
S_Init(); // init sound
Voice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device)
ID_Init();
SteamBroker_Init();

View File

@@ -26,6 +26,7 @@ GNU General Public License for more details.
#include "pm_defs.h"
#include "ref_params.h"
#include "render_api.h"
#include "sound_api.h"
#include "cdll_exp.h"
#include "screenfade.h"
#include "protocol.h"
@@ -449,6 +450,7 @@ typedef struct
void *hInstance; // pointer to client.dll
cldll_func_t dllFuncs; // dll exported funcs
render_interface_t drawFuncs; // custom renderer support
sound_interface_t soundFuncs; // custom sound support
poolhandle_t mempool; // client edicts pool
string mapname; // map name
string maptitle; // display map title

View File

@@ -232,6 +232,11 @@ void S_FreeSound( sfx_t *sfx )
prev = &hashSfx->hashNext;
}
if( clgame.soundFuncs.pfnS_FreeSound ) {
clgame.soundFuncs.pfnS_FreeSound( sfx, sfx - s_knownSfx );
return;
}
if( sfx->cache )
FS_FreeSound( sfx->cache );
memset( sfx, 0, sizeof( *sfx ));

View File

@@ -45,6 +45,9 @@ int total_channels;
int soundtime; // sample PAIRS
int paintedtime; // sample PAIRS
static sound_api_t s_clientSoundAPI;
static snd_interface_state_t s_sndState;
static CVAR_DEFINE( s_volume, "volume", "0.7", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "sound volume" );
CVAR_DEFINE( s_musicvolume, "MP3Volume", "1.0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "background music volume" );
static CVAR_DEFINE( s_mixahead, "_snd_mixahead", "0.12", FCVAR_FILTERABLE, "how much sound to mix ahead of time" );
@@ -161,6 +164,18 @@ static qboolean S_IsClient( int entnum )
return entnum == s_listener.entnum;
}
/*
=================
S_NotifyChannelUpdate
=================
*/
static void S_NotifyChannelUpdate( int ch_idx, const channel_t *ch, sound_t handle )
{
if( !clgame.soundFuncs.pfnS_UpdateChannel )
return;
clgame.soundFuncs.pfnS_UpdateChannel( ch_idx, ch, handle );
}
// free channel so that it may be allocated by the
// next request to play a sound. If sound is a
@@ -173,6 +188,10 @@ S_FreeChannel
*/
void S_FreeChannel( channel_t *ch )
{
int ch_idx = ch - channels;
if( ch_idx >= 0 && ch_idx < MAX_CHANNELS )
S_NotifyChannelUpdate( ch_idx, NULL, -1 );
ch->sfx = NULL;
ch->name[0] = '\0';
ch->use_loop = false;
@@ -184,6 +203,19 @@ void S_FreeChannel( channel_t *ch )
SND_CloseMouth( ch );
}
/*
=================
S_NotifyRawChannelUpdate
=================
*/
static void S_NotifyRawChannelUpdate( int raw_idx, rawchan_t *ch )
{
if( !clgame.soundFuncs.pfnS_UpdateRawChannel )
return;
clgame.soundFuncs.pfnS_UpdateRawChannel( raw_idx, ch );
}
/*
=================
S_UpdateSoundFade
@@ -543,6 +575,11 @@ SND_Spatialize
*/
static void SND_Spatialize( channel_t *ch )
{
if( clgame.soundFuncs.pfnS_Spatialize ) {
clgame.soundFuncs.pfnS_Spatialize( ch );
return;
}
// anything coming from the view entity will allways be full volume
if( S_IsClient( ch->entnum ))
{
@@ -704,6 +741,9 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv
}
}
ch_idx = target_chan - channels;
S_NotifyChannelUpdate( ch_idx, target_chan, handle );
// Init client entity mouth movement vars
SND_InitMouth( ent, chan );
}
@@ -810,6 +850,8 @@ void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float
target_chan->sample = sample;
target_chan->forced_end = end;
S_NotifyChannelUpdate( target_chan - channels, target_chan, handle );
// Init client entity mouth movement vars
SND_InitMouth( ent, chan );
}
@@ -898,6 +940,7 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa
ch->entchannel = CHAN_STATIC;
ch->basePitch = pitch;
S_NotifyChannelUpdate( ch - channels, ch, handle );
SND_Spatialize( ch );
}
@@ -1161,6 +1204,8 @@ rawchan_t *S_FindRawChannel( int entnum, qboolean create )
ch->entnum = entnum;
ch->s_rawend = 0;
S_NotifyRawChannelUpdate( best, ch );
return ch;
}
@@ -1513,7 +1558,11 @@ static void S_UpdateChannels( void )
endtime -= ( endtime - paintedtime ) & 0x3;
}
S_PaintChannels( endtime );
s_sndState.total_channels = total_channels;
if( clgame.soundFuncs.pfnS_PaintChannels )
clgame.soundFuncs.pfnS_PaintChannels( endtime, &dma, &paintedtime );
else
S_PaintChannels( endtime );
SNDDMA_Submit();
}
@@ -1563,6 +1612,10 @@ void SND_UpdateSound( void )
if( !dma.initialized ) return;
if( clgame.soundFuncs.pfnS_UpdateSound ) {
clgame.soundFuncs.pfnS_UpdateSound();
}
// if the loading plaque is up, clear everything
// out to make sure we aren't looping a dirty
// dma buffer while loading
@@ -1917,6 +1970,33 @@ static void S_VoiceRecordStop_f( void )
Voice_RecordStop();
}
/*
================
S_FillSoundAPI
================
*/
static void S_FillSoundAPI( sound_api_t *api )
{
memset( api, 0, sizeof( *api ));
api->CL_GetEntitySpatialization = CL_GetEntitySpatialization;
api->S_GetSfxByHandle = S_GetSfxByHandle;
}
/*
================
S_FillSndState
================
*/
static void S_FillSndState( snd_interface_state_t *st )
{
memset( st, 0, sizeof( *st ));
st->listener = &s_listener;
st->channels = channels;
st->total_channels = total_channels;
st->raw_channels = raw_channels;
st->max_raw_channels = MAX_RAW_CHANNELS;
}
/*
================
S_Init
@@ -1978,6 +2058,22 @@ qboolean S_Init( void )
S_InitSounds ();
VOX_Init ();
memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs ));
if( clgame.dllFuncs.pfnGetSoundInterface )
{
S_FillSoundAPI( &s_clientSoundAPI );
S_FillSndState( &s_sndState );
if( clgame.dllFuncs.pfnGetSoundInterface( CL_SOUND_INTERFACE_VERSION, &s_clientSoundAPI, &clgame.soundFuncs ))
{
if( clgame.soundFuncs.pfnS_Init && clgame.soundFuncs.pfnS_Init( &s_sndState ))
{
Con_Reportf( "%s: ^2initialized extended SoundAPI ^7ver. %i\n", __func__, CL_SOUND_INTERFACE_VERSION );
}
}
}
return true;
}
@@ -2002,6 +2098,11 @@ void S_Shutdown( void )
Cmd_RemoveCommand( "speak" );
Cmd_RemoveCommand( "spk" );
if( clgame.soundFuncs.pfnS_Shutdown )
{
clgame.soundFuncs.pfnS_Shutdown();
}
S_StopAllSounds (false);
S_FreeRawChannels ();
S_FreeSounds ();

View File

@@ -38,7 +38,7 @@ extern poolhandle_t sndpool;
#define CLIP16( x ) bound( SHRT_MIN + 8, x, SHRT_MAX - 8 )
typedef struct
typedef struct portable_samplepair_s
{
int left;
int right;
@@ -72,7 +72,7 @@ typedef struct snd_format_s
byte channels;
} snd_format_t;
typedef struct
typedef struct dma_api_s
{
snd_format_t format;
int samples; // mono samples in buffer