/* =========================================================================== Copyright (C) 2024 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code 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. OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "snd_local.h" #include "snd_openal_new.h" #include "client.h" #include "cl_ui.h" typedef struct { vec3_t origin; vec3_t velocity; sfx_t *sfx; int mergeFrame; } loopSound_t; qboolean s_bLastInitSound = qfalse; qboolean s_bSoundStarted = qfalse; qboolean s_bSoundPaused = qfalse; qboolean s_bTryUnpause = qfalse; int s_iListenerNumber = 0; float s_fAmbientVolume = 1.f; int number_of_sfx_infos = 0; cvar_t *s_volume; cvar_t *s_loadas8bit; cvar_t *s_khz; cvar_t *s_show; cvar_t *s_testsound; cvar_t *s_separation; cvar_t *s_musicVolume; cvar_t *s_ambientVolume; sfx_info_t sfx_infos[MAX_SFX_INFOS]; sfx_t s_knownSfx[MAX_SFX]; int s_numSfx; s_entity_t s_entity[MAX_GENTITIES]; static int s_registrationSequence; static qboolean s_inRegistration; cvar_t *s_mixPreStep; cvar_t *s_dialogscale; int numLoopSounds; loopSound_t loopSounds[MAX_LOOP_SOUNDS]; /* ============== S_SoundInfo_f ============== */ void S_SoundInfo_f() { S_PrintInfo(); } /* ============== S_SoundDump_f ============== */ void S_SoundDump_f() { S_DumpInfo(); } /* ============== S_Init ============== */ void S_Init(qboolean full_startup) { int iStart, iEnd; cvar_t *cv; Com_Printf("------- Sound Initialization (%s) -------\n", full_startup ? "full" : "partial"); iStart = Sys_Milliseconds(); s_volume = Cvar_Get("s_volume", "0.9", CVAR_ARCHIVE); s_musicVolume = Cvar_Get("s_musicvolume", "0.9", CVAR_ARCHIVE); s_ambientVolume = Cvar_Get("s_ambientvolume", "1.00", CVAR_ARCHIVE); s_separation = Cvar_Get("s_separation", "0.5", CVAR_ARCHIVE); s_khz = Cvar_Get("s_khz", "44", CVAR_ARCHIVE | CVAR_SOUND_LATCH); s_loadas8bit = Cvar_Get("s_loadas8bit", "0", CVAR_ARCHIVE | CVAR_LATCH); s_mixPreStep = Cvar_Get("s_mixPreStep", "0.05", CVAR_ARCHIVE); s_show = Cvar_Get("s_show", "0", CVAR_CHEAT); s_testsound = Cvar_Get("s_testsound", "0", CVAR_CHEAT); s_dialogscale = Cvar_Get("s_dialogscale", "1", CVAR_ARCHIVE); s_bLastInitSound = false; cv = Cvar_Get("s_initsound", "1", 0); if (cv->integer) { Cmd_AddCommand("play", S_Play); Cmd_AddCommand("soundlist", S_SoundList); Cmd_AddCommand("soundinfo", S_SoundInfo_f); Cmd_AddCommand("sounddump", S_SoundDump_f); if (S_Driver_Init()) { s_bLastInitSound = true; s_bSoundStarted = true; if (full_startup) { s_numSfx = 0; S_StopAllSounds(true); } S_FindName("***DEFAULT***", -1); S_SoundInfo_f(); } } else { Com_Printf("Sound Not Initializing.\n"); s_bSoundStarted = 0; } iEnd = Sys_Milliseconds(); Com_Printf("------- Sound Initialization Complete ------- %i ms\n", iEnd - iStart); } /* ============== S_Shutdown ============== */ void S_Shutdown(qboolean full_shutdown) { int i; sfx_t *sfx; if (!s_bSoundStarted) { return; } Com_Printf("------- Sound Shutdown (%s) -------\n", full_shutdown ? "full" : "partial"); S_Driver_Shutdown(); s_bSoundStarted = false; Cmd_RemoveCommand("play"); Cmd_RemoveCommand("music"); Cmd_RemoveCommand("stopsound"); Cmd_RemoveCommand("soundlist"); Cmd_RemoveCommand("soundinfo"); if (full_shutdown) { for (i = 0; i < s_numSfx; i++) { sfx = &s_knownSfx[i]; if (sfx->name[0]) { if (sfx->data) { Z_Free(sfx->data); } *sfx = {}; } } s_numSfx = 0; } Com_Printf("------- Sound Shutdown Complete -------\n"); } /* ============== S_NameExists ============== */ qboolean S_NameExists(const char *name) { int i; if (strlen(name) >= MAX_RES_NAME) { Com_DPrintf("Sound name too long: %s", name); return qfalse; } for (i = 0; i < s_numSfx; i++) { if (!strcmp(s_knownSfx[i].name, name)) { return qtrue; } } return qfalse; } /* ============== S_FindName ============== */ sfx_t *S_FindName(const char *name, int sequenceNumber) { int i; sfx_t *sfx; if (!name) { Com_DPrintf("S_FindName: NULL\n"); return NULL; } if (!name[0]) { Com_DPrintf("S_FindName: empty name\n"); return NULL; } if (strlen(name) >= MAX_RES_NAME) { Com_DPrintf("Sound name too long: %s", name); return NULL; } for (i = 0; i < s_numSfx; i++) { sfx = &s_knownSfx[i]; if (!strcmp(sfx->name, name)) { if (sfx->registration_sequence != -1) { sfx->registration_sequence = sequenceNumber; } return sfx; } } sfx = &s_knownSfx[0]; for (i = 0; i < s_numSfx; i++) { sfx = &s_knownSfx[i]; if (!sfx->name[0]) { break; } } if (i == s_numSfx) { if (i == MAX_SFX) { Com_Error(ERR_FATAL, "S_FindName: out of sfx_t"); } sfx = &s_knownSfx[s_numSfx]; s_numSfx++; } *sfx = {}; // Fixed in OPM // strcpy was used before //strcpy(sfx->name, name); Q_strncpyz(sfx->name, name, sizeof(sfx->name)); sfx->registration_sequence = sequenceNumber; return sfx; } /* ============== S_BeginRegistration ============== */ void S_BeginRegistration() { int i; Com_Printf("------- Sound Begin Registration -------\n"); S_StopAllSounds(true); s_registrationSequence++; s_inRegistration = true; s_knownSfx[0].registration_sequence = s_registrationSequence; Com_Printf("------- Sound Begin Registration Complete -------\n"); // Added in OPM // Reset all sound entities // OG doesn't do this but this makes sure // entities don't leak info from previous sessions for (i = 0; i < ARRAY_LEN(s_entity); i++) { s_entity[i] = s_entity_t{}; } } /* ============== S_DefaultSound ============== */ void S_DefaultSound(sfx_t *sfx) { sfx->data = NULL; sfx->iFlags |= SFX_FLAG_DEFAULT_SOUND; } /* ============== S_IsSoundRegistered ============== */ qboolean S_IsSoundRegistered(const char *name) { char szCacheName[MAX_QPATH]; int i; if (!s_bSoundStarted) { return qfalse; } for (i = 0; name[i] && i < MAX_QPATH; i++) { szCacheName[i] = tolower(name[i]); } szCacheName[i] = 0; if (i >= MAX_QPATH) { Com_Printf("Sound name exceeds MAX_QPATH\n"); return qfalse; } return S_NameExists(szCacheName); } /* ============== S_RegisterSound ============== */ sfxHandle_t S_RegisterSound(const char *name, int streamed, qboolean force_load) { char szCacheName[MAX_QPATH]; sfx_t *sfx; int i; if (!s_bSoundStarted) { Com_Printf("ERROR: Trying to register sound %s when soundsystem hasn't started.\n", name); return 0; } if (!name) { return 0; } for (i = 0; name[i] && i < MAX_QPATH; i++) { szCacheName[i] = tolower(name[i]); } szCacheName[i] = 0; if (i >= MAX_QPATH) { Com_Printf("Sound name exceeds MAX_QPATH\n"); return 0; } sfx = S_FindName(szCacheName, s_registrationSequence); if (!sfx) { return 0; } if (!sfx->data) { if (!S_LoadSound(sfx->name, sfx, streamed, force_load)) { Com_DPrintf("Couldn't load sound: %s\n", sfx->name); if (!S_LoadSound("sound/default.wav", sfx, streamed, 0)) { Com_DPrintf("Couldn't load sound: sound/default.wav\n"); sfx->iFlags |= SFX_FLAG_DEFAULT_SOUND; return 0; } } sfx->iFlags &= ~SFX_FLAG_DEFAULT_SOUND; sfx->sfx_info_index = 0; for (i = 0; i < number_of_sfx_infos; i++) { if (!Q_stricmp(sfx->name, sfx_infos[i].name)) { sfx->sfx_info_index = i; break; } } } return sfx - s_knownSfx; } /* ============== S_GetSoundTime ============== */ float S_GetSoundTime(sfxHandle_t handle) { return s_knownSfx[handle].time_length; } /* ============== S_EndRegistration ============== */ void S_EndRegistration() { int i; sfx_t *sfx; Com_Printf("------- Sound End Registration -------\n"); s_inRegistration = qfalse; for (i = 0; i < s_numSfx; i++) { sfx = &s_knownSfx[i]; if (!sfx->name[0]) { continue; } if (sfx->registration_sequence && sfx->registration_sequence != s_registrationSequence) { if (sfx->data) { Z_Free(sfx->data); } *sfx = {}; } } Com_Printf("------- Sound End Registration Complete -------\n"); } /* ============== S_UpdateEntity ============== */ void S_UpdateEntity(int entityNum, const vec3_t origin, const vec3_t vel, qboolean use_listener) { if (entityNum > MAX_GENTITIES) { Com_Error(ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum); } VectorCopy(origin, s_entity[entityNum].position); VectorCopy(vel, s_entity[entityNum].velocity); s_entity[entityNum].use_listener = use_listener; s_entity[entityNum].time = cl.serverTime; } /* ============== S_SetGlobalAmbientVolumeLevel ============== */ void S_SetGlobalAmbientVolumeLevel(float volume) { s_fAmbientVolume = volume; } /* ============== S_StartSound ============== */ void S_StartSound( const vec3_t origin, int entnum, int entchannel, sfxHandle_t sfxHandle, float volume, float min_dist, float pitch, float maxDist, int streamed ) { if (s_show_sounds->integer > 0 && sfxHandle > 0 && sfxHandle < s_numSfx) { Com_DPrintf("S_StartSound: %s\n", s_knownSfx[sfxHandle].name); } if (!s_bSoundStarted) { if (s_show_sounds->integer > 0) { Com_DPrintf("^~^~^ sound system not yet started\n"); } return; } if (server_loading) { if (s_show_sounds->integer > 0) { Com_DPrintf("^~^~^ cannot start sounds while loading a server\n"); } return; } if (sfxHandle < 0 || sfxHandle >= s_numSfx) { Com_Error(ERR_DROP, "S_StartSound: handle %i out of range", sfxHandle); } if (!sfxHandle) { if (s_show_sounds->integer > 0) { Com_DPrintf("^~^~^ null handle\n"); } return; } if (entnum && (entchannel == CHAN_DIALOG || entchannel == CHAN_DIALOG_SECONDARY) && s_dialogscale->value > 0) { volume *= s_dialogscale->value; } S_Driver_StartSound(origin, entnum, entchannel, sfxHandle, volume, min_dist, pitch, maxDist, streamed); } /* ============== S_StartLocalSound ============== */ void S_StartLocalSound(const char *sound_name, qboolean force_load) { sfxHandle_t sfxHandle; const char *name; AliasListNode_t *pSoundAlias = NULL; if (!s_bSoundStarted) { return; } if (!sound_name) { return; } name = Alias_FindRandom(sound_name, &pSoundAlias); if (!name) { name = sound_name; } sfxHandle = S_RegisterSound(name, pSoundAlias ? pSoundAlias->streamed : qfalse, force_load); if (!pSoundAlias) { S_StartSound(NULL, s_iListenerNumber, CHAN_MENU, sfxHandle, -1.0, -1.0, 1.0, -1.0, qfalse); return; } S_StartSound( NULL, s_iListenerNumber, CHAN_MENU, sfxHandle, pSoundAlias->volume + random() * pSoundAlias->volumeMod, -1.0, pSoundAlias->pitch + random() * pSoundAlias->pitchMod, pSoundAlias->maxDist, pSoundAlias->streamed ); } /* ============== S_StartLocalSoundChannel Added in 2.0 Starts a local sound, with the specified channel ============== */ void S_StartLocalSoundChannel(const char *sound_name, qboolean force_load, soundChannel_t channel) { sfxHandle_t sfxHandle; const char *name; AliasListNode_t *pSoundAlias = NULL; if (!s_bSoundStarted) { return; } if (!sound_name) { return; } name = Alias_FindRandom(sound_name, &pSoundAlias); if (!name) { name = sound_name; } sfxHandle = S_RegisterSound(name, pSoundAlias ? pSoundAlias->streamed : qfalse, force_load); if (!pSoundAlias) { S_StartSound(NULL, s_iListenerNumber, channel, sfxHandle, -1.0, -1.0, 1.0, -1.0, qfalse); return; } S_StartSound( 0, s_iListenerNumber, channel, sfxHandle, pSoundAlias->volume + random() * pSoundAlias->volumeMod, -1.0, pSoundAlias->pitch + random() * pSoundAlias->pitchMod, pSoundAlias->maxDist, pSoundAlias->streamed ); } /* ============== S_StopAllSounds ============== */ void S_StopAllSounds(qboolean stop_music) { if (!s_bSoundStarted) { return; } Com_Printf("------- S_StopAllSounds (%s) -------\n", stop_music ? "stop music" : "don't stop music"); // Tell the driver to stop all sounds S_Driver_StopAllSounds(stop_music); Com_Printf("------- S_StopAllSounds Complete-------\n"); } /* ============== S_ClearLoopingSounds ============== */ void S_ClearLoopingSounds() { if (!s_bSoundStarted) { return; } S_Driver_ClearLoopingSounds(); } /* ============== S_AddLoopingSound ============== */ void S_AddLoopingSound( const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle, float volume, float min_dist, float max_dist, float pitch, int flags ) { if (!s_bSoundStarted) { return; } if (sfxHandle < 0 || sfxHandle >= s_numSfx) { Com_Error(ERR_DROP, "S_AddLoopingSound: handle %i out of range", sfxHandle); } if (!sfxHandle) { return; } S_Driver_AddLoopingSound(origin, velocity, sfxHandle, volume, min_dist, max_dist, pitch, flags); } /* ============== S_Respatialize ============== */ void S_Respatialize(int entityNum, const vec3_t head, vec3_t axis[3]) { if (!s_bSoundStarted) { return; } S_Driver_Respatialize(entityNum, head, axis); } /* ============== S_Update ============== */ void S_Update() { if (!s_bSoundStarted) { return; } if (server_loading) { return; } S_Driver_Update(); } /* ============== S_StopSound ============== */ void S_StopSound(int entnum, int channel) { if (!s_bSoundStarted) { return; } if (channel == CHAN_AUTO) { return; } S_Driver_StopSound(entnum, channel); } /* ============== S_SetReverb ============== */ void S_SetReverb(int reverb_type, float reverb_level) { if (!s_bSoundStarted) { return; } S_Driver_SetReverb(reverb_type, reverb_level); } /* ============== S_Play ============== */ void S_Play() { int i; char name[MAX_QPATH]; if (Cmd_Argc() < 2) { return; } for (i = 1; i < Cmd_Argc(); i++) { if (strrchr(Cmd_Argv(i), '.')) { Q_strncpyz(name, Cmd_Argv(i), sizeof(name)); } else { Com_sprintf(name, sizeof(name), "%s.wav", Cmd_Argv(1)); } } S_StartLocalSound(name, qfalse); } /* ============== S_SoundList ============== */ void S_SoundList() { int i; sfx_t *sfx; int size; int total = 0; for (i = 0; i < s_numSfx; i++) { sfx = &s_knownSfx[i]; if (sfx->registration_sequence) { size = sfx->length * sfx->width; total += size; Com_Printf("(%2db) %6i : %s\n", sfx->width * 8, size, sfx->name); } } Com_Printf("Total resident: %i\n", total); } /* ============== S_ClearSoundBuffer ============== */ void S_ClearSoundBuffer() { // TODO: Remove once AL is fully implemented } /* ============== S_GetMusicFilename ============== */ const char* S_GetMusicFilename() { return S_Driver_GetMusicFilename(); } /* ============== S_GetMusicLoopCount ============== */ int S_GetMusicLoopCount() { return S_Driver_GetMusicLoopCount(); } /* ============== S_GetMusicOffset ============== */ unsigned int S_GetMusicOffset() { return S_Driver_GetMusicOffset(); }