From 35998bce8172613e35d301e95e0f98dd3e0cd4ff Mon Sep 17 00:00:00 2001 From: TheEVolk Date: Sat, 4 Apr 2026 16:08:31 +0300 Subject: [PATCH 1/7] engine: client: add sound api --- common/sound_api.h | 67 ++++++++++++++++++++++++++ engine/cdll_exp.h | 8 ++++ engine/client/cl_game.c | 1 + engine/client/cl_main.c | 5 +- engine/client/client.h | 2 + engine/client/s_load.c | 5 ++ engine/client/s_main.c | 103 +++++++++++++++++++++++++++++++++++++++- engine/client/sound.h | 4 +- 8 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 common/sound_api.h diff --git a/common/sound_api.h b/common/sound_api.h new file mode 100644 index 00000000..dcb0956e --- /dev/null +++ b/common/sound_api.h @@ -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 diff --git a/engine/cdll_exp.h b/engine/cdll_exp.h index 240d9c25..3751b7e1 100644 --- a/engine/cdll_exp.h +++ b/engine/cdll_exp.h @@ -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 diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 592b705c..c86faad0 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -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 diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 5c881008..3580bf4a 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -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(); diff --git a/engine/client/client.h b/engine/client/client.h index 013edf37..ff8b861e 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -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 diff --git a/engine/client/s_load.c b/engine/client/s_load.c index 4dbed9e3..0e094220 100644 --- a/engine/client/s_load.c +++ b/engine/client/s_load.c @@ -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 )); diff --git a/engine/client/s_main.c b/engine/client/s_main.c index b2f0888c..3b6f1282 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -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 (); diff --git a/engine/client/sound.h b/engine/client/sound.h index 47962b13..dea7fb5d 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -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 From d151c48d1fd81a54ef36d21f8bac7889e18c561b Mon Sep 17 00:00:00 2001 From: TheEVolk Date: Sat, 4 Apr 2026 17:47:54 +0300 Subject: [PATCH 2/7] engine: client: fix api issues --- common/sound_api.h | 33 +++++++++++++----- engine/client/s_load.c | 3 +- engine/client/s_main.c | 79 ++++++++++++++++++++++-------------------- 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/common/sound_api.h b/common/sound_api.h index dcb0956e..0648263d 100644 --- a/common/sound_api.h +++ b/common/sound_api.h @@ -1,16 +1,31 @@ /* sound_api.h - Xash3D extension for client sound interface -Copyright (C) 2026 +Copyright (C) 2026 Xash3D FWGS Developers -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 is free and unencumbered software released into the public domain. -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. +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to */ #ifndef SOUND_API_H diff --git a/engine/client/s_load.c b/engine/client/s_load.c index 0e094220..f227cc25 100644 --- a/engine/client/s_load.c +++ b/engine/client/s_load.c @@ -232,7 +232,8 @@ void S_FreeSound( sfx_t *sfx ) prev = &hashSfx->hashNext; } - if( clgame.soundFuncs.pfnS_FreeSound ) { + if( clgame.soundFuncs.pfnS_FreeSound ) + { clgame.soundFuncs.pfnS_FreeSound( sfx, sfx - s_knownSfx ); return; } diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 3b6f1282..3a1cc799 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -45,7 +45,6 @@ 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" ); @@ -188,9 +187,7 @@ 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 ); + S_NotifyChannelUpdate( ch - channels, NULL, -1 ); ch->sfx = NULL; ch->name[0] = '\0'; @@ -575,7 +572,8 @@ SND_Spatialize */ static void SND_Spatialize( channel_t *ch ) { - if( clgame.soundFuncs.pfnS_Spatialize ) { + if( clgame.soundFuncs.pfnS_Spatialize ) + { clgame.soundFuncs.pfnS_Spatialize( ch ); return; } @@ -741,8 +739,7 @@ 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 ); + S_NotifyChannelUpdate( target_chan - channels, target_chan, handle ); // Init client entity mouth movement vars SND_InitMouth( ent, chan ); @@ -1612,9 +1609,8 @@ void SND_UpdateSound( void ) if( !dma.initialized ) return; - if( clgame.soundFuncs.pfnS_UpdateSound ) { + 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 @@ -1970,18 +1966,6 @@ 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 @@ -1997,6 +1981,40 @@ static void S_FillSndState( snd_interface_state_t *st ) st->max_raw_channels = MAX_RAW_CHANNELS; } +static const sound_api_t s_clientSoundAPI = { + CL_GetEntitySpatialization, + S_GetSfxByHandle, +}; + +/* +================ +S_InitSoundAPI +================ +*/ +static qboolean S_InitSoundAPI( void ) +{ + // make sure what sound functions is cleared + memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs )); + + S_FillSndState( &s_sndState ); + + if( clgame.dllFuncs.pfnGetSoundInterface ) + { + if( clgame.dllFuncs.pfnGetSoundInterface( CL_SOUND_INTERFACE_VERSION, &s_clientSoundAPI, &clgame.soundFuncs )) + { + Con_Reportf( "%s: ^2initailized extended SoundAPI ^7ver. %i\n", __func__, CL_SOUND_INTERFACE_VERSION ); + return true; + } + + Con_Reportf( "%s: ^1failed to initialize extended SoundAPI ^7ver. %i\n", __func__, CL_SOUND_INTERFACE_VERSION ); + + // make sure what sound functions is cleared + memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs )); + } + + return false; +} + /* ================ S_Init @@ -2058,21 +2076,10 @@ qboolean S_Init( void ) S_InitSounds (); VOX_Init (); - memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs )); + S_InitSoundAPI(); - 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 ); - } - } - } + if( clgame.soundFuncs.pfnS_Init ) + clgame.soundFuncs.pfnS_Init( &s_sndState ); return true; } @@ -2099,9 +2106,7 @@ void S_Shutdown( void ) Cmd_RemoveCommand( "spk" ); if( clgame.soundFuncs.pfnS_Shutdown ) - { clgame.soundFuncs.pfnS_Shutdown(); - } S_StopAllSounds (false); S_FreeRawChannels (); From 2c5e2e1ded25b7e0621ffd3a43b50ffd78b8869c Mon Sep 17 00:00:00 2001 From: TheEVolk Date: Sat, 4 Apr 2026 18:30:14 +0300 Subject: [PATCH 3/7] engine: client: remove sound_t definition from sound.h --- common/sound_api.h | 1 - engine/client/sound.h | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common/sound_api.h b/common/sound_api.h index 0648263d..04161b49 100644 --- a/common/sound_api.h +++ b/common/sound_api.h @@ -35,7 +35,6 @@ For more information, please refer to #define CL_SOUND_INTERFACE_VERSION 1 -// sound handle (same as engine's sound_t) typedef int sound_t; struct dma_api_s; diff --git a/engine/client/sound.h b/engine/client/sound.h index dea7fb5d..4d9108de 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -19,6 +19,7 @@ GNU General Public License for more details. extern poolhandle_t sndpool; #include "xash3d_mathlib.h" +#include "sound_api.h" // sound engine rate defines #define SOUND_11k 11025 // 11khz sample rate @@ -140,8 +141,6 @@ typedef struct qboolean stream_paused; // pause only background track } listener_t; -typedef int sound_t; - //==================================================================== #define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) From d92b3a730cea8ad3bf1ed5f7271a0cacbbae2669 Mon Sep 17 00:00:00 2001 From: TheEVolk Date: Wed, 8 Apr 2026 14:02:10 +0300 Subject: [PATCH 4/7] engine: client: adapt sound api to snd globals --- common/sound_api.h | 131 ++++++++++++++++++++++++++++++++++------ engine/client/cl_game.c | 2 +- engine/client/s_main.c | 32 ++-------- engine/client/sound.h | 115 ----------------------------------- 4 files changed, 120 insertions(+), 160 deletions(-) diff --git a/common/sound_api.h b/common/sound_api.h index 04161b49..6a9094c3 100644 --- a/common/sound_api.h +++ b/common/sound_api.h @@ -37,23 +37,118 @@ For more information, please refer to 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 +typedef struct portable_samplepair_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; + int left; + int right; +} portable_samplepair_t; + +typedef struct sfx_s +{ + char name[MAX_QPATH]; + wavdata_t *cache; + + int servercount; + uint hashValue; + struct sfx_s *hashNext; +} sfx_t; + +typedef struct voxword_s +{ + sfx_t *sfx; + uint16_t volume; // volume percent + uint16_t pitch; // pitch shift percent (keep large for extra chipmunk fun) + uint8_t timecompress; // percent of skipped data (speeds up playback without pitch shift) + uint8_t start; // percent at which playback starts + uint8_t end; // percent at which playback ends + uint8_t flags; +} voxword_t; + +typedef struct channel_s +{ + char name[16]; // keep sentence name + sfx_t *sfx; // sfx number + + vec3_t origin; // only use if fixed_origin is set + float dist_mult; // distance multiplier (attenuation/clipK) + + int entchannel; // sound channel (CHAN_STREAM, CHAN_VOICE, etc.) + uint flags; + short entnum; // entity soundsource + short master_vol; // 0-255 master volume + short leftvol; // 0-255 left volume + short rightvol; // 0-255 right volume + short basePitch; // base pitch percent (100% is normal pitch playback) + byte word_index; + + // HACKHACK: count when this channel became inaudible + // to not free it when it could be respatialized soon +#define MAX_CHANNEL_INAUDIBLE_TIME 0.1f + float inauduble_free_time; + + double sample; + double forced_end; + wavdata_t *data; + voxword_t *words; // dynamically allocated, (num_words + 1) entries, null sfx terminates + + uintptr_t engine_reserved[8]; // only for engine developers + uintptr_t game_reserved[8]; // free space for game developers +} channel_t; + +typedef struct rawchan_s +{ + short entnum; + short master_vol; + short leftvol; // 0-255 left volume + short rightvol; // 0-255 right volume + float dist_mult; // distance multiplier (attenuation/clipK) + vec3_t origin; // only use if fixed_origin is set + volatile uint s_rawend; + float oldtime; // catch time jumps + + uintptr_t engine_reserved[8]; // only for engine developers + uintptr_t game_reserved[8]; // free space for game developers + + size_t max_samples; // buffer length + portable_samplepair_t rawsamples[]; // variable sized +} rawchan_t; + +typedef struct snd_format_s +{ + uint speed; + byte width; + byte channels; +} snd_format_t; + +typedef struct snd_globals_s +{ + // dma + const char *backend_name; + byte *buffer; + snd_format_t format; + qboolean initialized; // sound engine is active + int samples; // mono samples in buffer + int samplepos; // in mono samples + + int paintedtime; // total samples that have been mixed at speed + int soundtime; // total samples that have been played out to hardware at dma speed + + // listener (client, camera, etc) + vec3_t origin; + vec3_t forward, right, up; + int entnum; + qboolean streaming; // playing AVI-file + qboolean stream_paused; // pause only background track + + // SoundAPI shared pointers + channel_t *const channels; + int max_channels; + int total_channels; + rawchan_t **const raw_channels; + int max_raw_channels; + sound_t ambient_sfx[NUM_AMBIENTS]; + qboolean have_ambient_sfx; +} snd_globals_t; // API from engine to client (client calls these) typedef struct sound_api_s @@ -67,11 +162,11 @@ typedef struct sound_interface_s { int version; - qboolean (*pfnS_Init)( snd_interface_state_t *state ); + qboolean (*pfnS_Init)( snd_globals_t *globals ); 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_PaintChannels)( int endtime ); 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 ); diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 1116b6cf..2b9d7c43 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -101,11 +101,11 @@ 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 { "IN_ClientLookEvent", (void **)&clgame.dllFuncs.pfnLookEvent}, // Xash3D FWGS ext +{ "HUD_GetSoundInterface", (void **)&clgame.dllFuncs.pfnGetSoundInterface }, // Xash3D ext }; static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc ); diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 33d2ef96..349b8392 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -44,8 +44,6 @@ snd_globals_t snd = .max_raw_channels = MAX_RAW_CHANNELS, }; -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" ); @@ -185,7 +183,7 @@ S_FreeChannel */ void S_FreeChannel( channel_t *ch ) { - S_NotifyChannelUpdate( ch - channels, NULL, -1 ); + S_NotifyChannelUpdate( ch - snd.channels, NULL, -1 ); // free the currently loaded word's audio cache before nuking the channel if( ch->words ) @@ -735,7 +733,7 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv } } - S_NotifyChannelUpdate( target_chan - channels, target_chan, handle ); + S_NotifyChannelUpdate( target_chan - snd.channels, target_chan, handle ); // Init client entity mouth movement vars SND_InitMouth( ent, chan ); @@ -851,7 +849,7 @@ 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 ); + S_NotifyChannelUpdate( target_chan - snd.channels, target_chan, handle ); // Init client entity mouth movement vars SND_InitMouth( ent, chan ); @@ -946,7 +944,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 ); + S_NotifyChannelUpdate( ch - snd.channels, ch, handle ); SND_Spatialize( ch ); } @@ -1564,9 +1562,8 @@ static void S_UpdateChannels( void ) endtime -= ( endtime - snd.paintedtime ) & 0x3; } - s_sndState.total_channels = total_channels; if( clgame.soundFuncs.pfnS_PaintChannels ) - clgame.soundFuncs.pfnS_PaintChannels( endtime, &dma, &paintedtime ); + clgame.soundFuncs.pfnS_PaintChannels( endtime ); else S_PaintChannels( endtime ); @@ -1938,21 +1935,6 @@ static void S_VoiceRecordStop_f( void ) Voice_RecordStop(); } -/* -================ -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; -} - static const sound_api_t s_clientSoundAPI = { CL_GetEntitySpatialization, S_GetSfxByHandle, @@ -1967,8 +1949,6 @@ static qboolean S_InitSoundAPI( void ) { // make sure what sound functions is cleared memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs )); - - S_FillSndState( &s_sndState ); if( clgame.dllFuncs.pfnGetSoundInterface ) { @@ -2050,7 +2030,7 @@ qboolean S_Init( void ) S_InitSoundAPI(); if( clgame.soundFuncs.pfnS_Init ) - clgame.soundFuncs.pfnS_Init( &s_sndState ); + clgame.soundFuncs.pfnS_Init( &snd ); return true; } diff --git a/engine/client/sound.h b/engine/client/sound.h index a596f2f4..95eaebd7 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -39,129 +39,14 @@ extern poolhandle_t sndpool; #define CLIP16( x ) bound( SHRT_MIN + 8, x, SHRT_MAX - 8 ) -typedef struct portable_samplepair_s -{ - int left; - int right; -} portable_samplepair_t; - -typedef struct sfx_s -{ - char name[MAX_QPATH]; - wavdata_t *cache; - - int servercount; - uint hashValue; - struct sfx_s *hashNext; -} sfx_t; - #define FL_VOXWORD_IN_CACHE BIT( 0 ) // if set, it was loaded prior and shouldn't be freed -typedef struct voxword_s -{ - sfx_t *sfx; - uint16_t volume; // volume percent - uint16_t pitch; // pitch shift percent (keep large for extra chipmunk fun) - uint8_t timecompress; // percent of skipped data (speeds up playback without pitch shift) - uint8_t start; // percent at which playback starts - uint8_t end; // percent at which playback ends - uint8_t flags; -} voxword_t; - -typedef struct snd_format_s -{ - uint speed; - byte width; - byte channels; -} snd_format_t; - -typedef struct rawchan_s -{ - short entnum; - short master_vol; - short leftvol; // 0-255 left volume - short rightvol; // 0-255 right volume - float dist_mult; // distance multiplier (attenuation/clipK) - vec3_t origin; // only use if fixed_origin is set - volatile uint s_rawend; - float oldtime; // catch time jumps - - uintptr_t engine_reserved[8]; // only for engine developers - uintptr_t game_reserved[8]; // free space for game developers - - size_t max_samples; // buffer length - portable_samplepair_t rawsamples[]; // variable sized -} rawchan_t; - #define FL_CHAN_USE_LOOP BIT( 0 ) // don't loop default and local sounds #define FL_CHAN_STATIC_SOUND BIT( 1 ) // use origin instead of fetching entnum's origin #define FL_CHAN_LOCAL_SOUND BIT( 2 ) // it's a local menu sound (not looped, not paused) #define FL_CHAN_SENTENCE_FINISHED BIT( 4 ) // if set, finished playing sentence #define FL_CHAN_FINISHED BIT( 5 ) // if set, finished playing single word -typedef struct channel_s -{ - char name[16]; // keep sentence name - sfx_t *sfx; // sfx number - - vec3_t origin; // only use if fixed_origin is set - float dist_mult; // distance multiplier (attenuation/clipK) - - int entchannel; // sound channel (CHAN_STREAM, CHAN_VOICE, etc.) - uint flags; - short entnum; // entity soundsource - short master_vol; // 0-255 master volume - short leftvol; // 0-255 left volume - short rightvol; // 0-255 right volume - short basePitch; // base pitch percent (100% is normal pitch playback) - byte word_index; - - // HACKHACK: count when this channel became inaudible - // to not free it when it could be respatialized soon -#define MAX_CHANNEL_INAUDIBLE_TIME 0.1f - float inauduble_free_time; - - double sample; - double forced_end; - wavdata_t *data; - voxword_t *words; // dynamically allocated, (num_words + 1) entries, null sfx terminates - - uintptr_t engine_reserved[8]; // only for engine developers - uintptr_t game_reserved[8]; // free space for game developers -} channel_t; - -typedef int sound_t; - -typedef struct snd_globals_s -{ - // dma - const char *backend_name; - byte *buffer; - snd_format_t format; - qboolean initialized; // sound engine is active - int samples; // mono samples in buffer - int samplepos; // in mono samples - - int paintedtime; // total samples that have been mixed at speed - int soundtime; // total samples that have been played out to hardware at dma speed - - // listener (client, camera, etc) - vec3_t origin; - vec3_t forward, right, up; - int entnum; - qboolean streaming; // playing AVI-file - qboolean stream_paused; // pause only background track - - // SoundAPI shared pointers - channel_t *const channels; - int max_channels; - int total_channels; - rawchan_t **const raw_channels; - int max_raw_channels; - sound_t ambient_sfx[NUM_AMBIENTS]; - qboolean have_ambient_sfx; -} snd_globals_t; - //==================================================================== #define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) From cd85762b0998d45bada86328c986f12f7c56e28b Mon Sep 17 00:00:00 2001 From: TheEVolk Date: Wed, 8 Apr 2026 14:11:44 +0300 Subject: [PATCH 5/7] engine: client: fix arm builds --- common/sound_api.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/sound_api.h b/common/sound_api.h index 6a9094c3..4c6034ab 100644 --- a/common/sound_api.h +++ b/common/sound_api.h @@ -167,10 +167,10 @@ typedef struct sound_interface_s 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 ); - 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 ); + void (*pfnS_UpdateChannel)( int ch_idx, const channel_t *ch, sound_t handle ); // ch=NULL -> channel freed + void (*pfnS_UpdateRawChannel)( int raw_idx, rawchan_t *ch ); // ch=NULL -> channel freed + void (*pfnS_Spatialize)( channel_t *ch ); + void (*pfnS_FreeSound)( sfx_t *sfx, sound_t handle ); } sound_interface_t; #endif // SOUND_API_H From da3960c4f0d8423fb7dec6fc33bcc9ed450b0909 Mon Sep 17 00:00:00 2001 From: TheEVolk Date: Wed, 8 Apr 2026 14:15:16 +0300 Subject: [PATCH 6/7] engine: client: move bitflag definitions to sound_api --- common/sound_api.h | 9 +++++++++ engine/client/sound.h | 8 -------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/common/sound_api.h b/common/sound_api.h index 4c6034ab..77d6e58a 100644 --- a/common/sound_api.h +++ b/common/sound_api.h @@ -33,8 +33,17 @@ For more information, please refer to #include "xash3d_types.h" +// Experimental implementation, backward compatibility is not guaranteed. #define CL_SOUND_INTERFACE_VERSION 1 +#define FL_VOXWORD_IN_CACHE BIT( 0 ) // if set, it was loaded prior and shouldn't be freed + +#define FL_CHAN_USE_LOOP BIT( 0 ) // don't loop default and local sounds +#define FL_CHAN_STATIC_SOUND BIT( 1 ) // use origin instead of fetching entnum's origin +#define FL_CHAN_LOCAL_SOUND BIT( 2 ) // it's a local menu sound (not looped, not paused) +#define FL_CHAN_SENTENCE_FINISHED BIT( 4 ) // if set, finished playing sentence +#define FL_CHAN_FINISHED BIT( 5 ) // if set, finished playing single word + typedef int sound_t; typedef struct portable_samplepair_s diff --git a/engine/client/sound.h b/engine/client/sound.h index 95eaebd7..4c8ea4c7 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -39,14 +39,6 @@ extern poolhandle_t sndpool; #define CLIP16( x ) bound( SHRT_MIN + 8, x, SHRT_MAX - 8 ) -#define FL_VOXWORD_IN_CACHE BIT( 0 ) // if set, it was loaded prior and shouldn't be freed - -#define FL_CHAN_USE_LOOP BIT( 0 ) // don't loop default and local sounds -#define FL_CHAN_STATIC_SOUND BIT( 1 ) // use origin instead of fetching entnum's origin -#define FL_CHAN_LOCAL_SOUND BIT( 2 ) // it's a local menu sound (not looped, not paused) -#define FL_CHAN_SENTENCE_FINISHED BIT( 4 ) // if set, finished playing sentence -#define FL_CHAN_FINISHED BIT( 5 ) // if set, finished playing single word - //==================================================================== #define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) From 386ca1b9bb0218172a9399ddc88019fe455eabca Mon Sep 17 00:00:00 2001 From: TheEVolk Date: Wed, 8 Apr 2026 14:27:24 +0300 Subject: [PATCH 7/7] engine: client: make sound api const --- common/sound_api.h | 4 ++-- engine/cdll_exp.h | 2 +- engine/client/s_main.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/sound_api.h b/common/sound_api.h index 77d6e58a..d25c18be 100644 --- a/common/sound_api.h +++ b/common/sound_api.h @@ -162,8 +162,8 @@ typedef struct snd_globals_s // 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 ); + qboolean (*CL_GetEntitySpatialization)( channel_t *ch ); + sfx_t* (*S_GetSfxByHandle)( sound_t handle ); } sound_api_t; // Callbacks from client to engine (engine calls these when custom sound is active) diff --git a/engine/cdll_exp.h b/engine/cdll_exp.h index 3751b7e1..0cd04636 100644 --- a/engine/cdll_exp.h +++ b/engine/cdll_exp.h @@ -81,7 +81,7 @@ typedef struct cldll_func_s 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 ); + int (*pfnGetSoundInterface)( int version, const sound_api_t *api, sound_interface_t *callback ); } cldll_func_t; #endif//CDLL_EXP_H diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 349b8392..a4e38723 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -1935,7 +1935,7 @@ static void S_VoiceRecordStop_f( void ) Voice_RecordStop(); } -static const sound_api_t s_clientSoundAPI = { +static const sound_api_t gSoundAPI = { CL_GetEntitySpatialization, S_GetSfxByHandle, }; @@ -1952,7 +1952,7 @@ static qboolean S_InitSoundAPI( void ) if( clgame.dllFuncs.pfnGetSoundInterface ) { - if( clgame.dllFuncs.pfnGetSoundInterface( CL_SOUND_INTERFACE_VERSION, &s_clientSoundAPI, &clgame.soundFuncs )) + if( clgame.dllFuncs.pfnGetSoundInterface( CL_SOUND_INTERFACE_VERSION, &gSoundAPI, &clgame.soundFuncs )) { Con_Reportf( "%s: ^2initailized extended SoundAPI ^7ver. %i\n", __func__, CL_SOUND_INTERFACE_VERSION ); return true;