mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
engine: client: add sound api
This commit is contained in:
67
common/sound_api.h
Normal file
67
common/sound_api.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ));
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user