diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 676b71140..a1e5ba2ed 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -824,9 +824,12 @@ set (PCH_SOURCES common/music/music_config.cpp common/music/music_midi_base.cpp common/music/s_advsound.cpp - common/music/backend/oalsound.cpp - common/music/backend/i_sound.cpp - + + common/sound/backend/oalsound.cpp + common/sound/backend/i_sound.cpp + common/sound/s_sound.cpp + common/sound/s_environment.cpp + common/menu/imagescroller.cpp common/menu/joystickmenu.cpp common/menu/listmenu.cpp @@ -899,6 +902,7 @@ include_directories( common/textures common/filesystem common/music + common/sound common/dobject common/menu platform @@ -1009,6 +1013,8 @@ source_group("Code\\Console" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/co source_group("Code\\Fonts" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/fonts/.+") source_group("Code\\File System" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/filesystem/.+") source_group("Code\\Music" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/music/.+") +source_group("Code\\Sound" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/sound/.+") +source_group("Code\\Sound\\Backend" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/sound/backend/.+") source_group("Code\\DObject" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/dobject/.+") source_group("Code\\Menu" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/menu/.+") source_group("Utility\\Audiolib" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/audiolib/.+") diff --git a/source/common/music/backend/efx.h b/source/common/sound/backend/efx.h similarity index 100% rename from source/common/music/backend/efx.h rename to source/common/sound/backend/efx.h diff --git a/source/common/music/backend/i_sound.cpp b/source/common/sound/backend/i_sound.cpp similarity index 97% rename from source/common/music/backend/i_sound.cpp rename to source/common/sound/backend/i_sound.cpp index 996e75bbe..0a1dfdd05 100644 --- a/source/common/music/backend/i_sound.cpp +++ b/source/common/sound/backend/i_sound.cpp @@ -249,6 +249,8 @@ void I_InitSound () { FModule_SetProgDir(progdir); /* Get command line options: */ + nosound = !!Args->CheckParm ("-nosound"); + nosfx = !!Args->CheckParm ("-nosfx"); GSnd = NULL; if (nosound) @@ -266,7 +268,7 @@ void I_InitSound () { I_CloseSound(); GSnd = new NullSoundRenderer; - Printf (TEXTCOLOR_RED"Music init failed. Using nosound.\n"); + Printf (TEXTCOLOR_RED"Sound init failed. Using nosound.\n"); } snd_sfxvolume.Callback (); } @@ -274,8 +276,8 @@ void I_InitSound () void I_CloseSound () { - // Free all loaded samples - //S_UnloadAllSounds(); + // Free all loaded samples. Beware that the sound engine may already have been deleted. + if (soundEngine) soundEngine->UnloadAllSounds(); delete GSnd; GSnd = NULL; diff --git a/source/common/music/backend/i_sound.h b/source/common/sound/backend/i_sound.h similarity index 96% rename from source/common/music/backend/i_sound.h rename to source/common/sound/backend/i_sound.h index 0b8685ae1..16c6cdcdd 100644 --- a/source/common/music/backend/i_sound.h +++ b/source/common/sound/backend/i_sound.h @@ -175,11 +175,7 @@ extern bool nosfx; extern bool nosound; void I_InitSound (); - -void S_ChannelEnded(FISoundChannel *schan); -void S_ChannelVirtualChanged(FISoundChannel *schan, bool is_virtual); -float S_GetRolloff(FRolloffInfo *rolloff, float distance, bool logarithmic); -FISoundChannel *S_GetChannel(void *syschan); +void I_CloseSound(); extern ReverbContainer *DefaultEnvironments[26]; diff --git a/source/common/music/backend/i_soundinternal.h b/source/common/sound/backend/i_soundinternal.h similarity index 99% rename from source/common/music/backend/i_soundinternal.h rename to source/common/sound/backend/i_soundinternal.h index d841693a7..1203012dd 100644 --- a/source/common/music/backend/i_soundinternal.h +++ b/source/common/sound/backend/i_soundinternal.h @@ -77,6 +77,7 @@ struct SoundListener bool underwater; bool valid; ReverbContainer *Environment; + void* ListenerObject; }; // Default rolloff information. diff --git a/source/common/music/backend/oalload.h b/source/common/sound/backend/oalload.h similarity index 100% rename from source/common/music/backend/oalload.h rename to source/common/sound/backend/oalload.h diff --git a/source/common/music/backend/oalsound.cpp b/source/common/sound/backend/oalsound.cpp similarity index 97% rename from source/common/music/backend/oalsound.cpp rename to source/common/sound/backend/oalsound.cpp index 5506e445b..aaeb0529a 100644 --- a/source/common/music/backend/oalsound.cpp +++ b/source/common/sound/backend/oalsound.cpp @@ -43,7 +43,6 @@ #include "cmdlib.h" #include "c_cvars.h" #include "printf.h" -#include "menu.h" #include "zmusic/sounddecoder.h" #include "filereadermusicinterface.h" @@ -54,6 +53,11 @@ FModule OpenALModule{"OpenAL"}; #include "oalload.h" +CUSTOM_CVAR(Int, snd_channels, 128, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // number of channels available +{ + if (self < 64) self = 64; +} +CVAR(Bool, snd_waterreverb, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR (String, snd_aldevice, "Default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, snd_efx, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_alresampler, "Default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) @@ -97,58 +101,6 @@ bool IsOpenALPresent() -void I_BuildALDeviceList(FOptionValues *opt) -{ - opt->mValues.Resize(1); - opt->mValues[0].TextValue = "Default"; - opt->mValues[0].Text = "Default"; - -#ifndef NO_OPENAL - if (IsOpenALPresent()) - { - const ALCchar *names = (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") ? - alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER) : - alcGetString(NULL, ALC_DEVICE_SPECIFIER)); - if (!names) - Printf("Failed to get device list: %s\n", alcGetString(NULL, alcGetError(NULL))); - else while (*names) - { - unsigned int i = opt->mValues.Reserve(1); - opt->mValues[i].TextValue = names; - opt->mValues[i].Text = names; - - names += strlen(names) + 1; - } - } -#endif -} - -void I_BuildALResamplersList(FOptionValues *opt) -{ - opt->mValues.Resize(1); - opt->mValues[0].TextValue = "Default"; - opt->mValues[0].Text = "Default"; - -#ifndef NO_OPENAL - if (!IsOpenALPresent()) - return; - if (!alcGetCurrentContext() || !alIsExtensionPresent("AL_SOFT_source_resampler")) - return; - - LPALGETSTRINGISOFT alGetStringiSOFT = reinterpret_cast(alGetProcAddress("alGetStringiSOFT")); - ALint num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT); - - unsigned int idx = opt->mValues.Reserve(num_resamplers); - for(ALint i = 0;i < num_resamplers;++i) - { - const ALchar *name = alGetStringiSOFT(AL_RESAMPLER_NAME_SOFT, i); - opt->mValues[idx].TextValue = name; - opt->mValues[idx].Text = name; - ++idx; - } -#endif -} - ReverbContainer *ForcedEnvironment; @@ -156,10 +108,7 @@ ReverbContainer *ForcedEnvironment; #ifndef NO_OPENAL -//EXTERN_CVAR (Int, snd_channels) -int snd_channels; EXTERN_CVAR (Int, snd_samplerate) -EXTERN_CVAR (Bool, snd_waterreverb) EXTERN_CVAR (Bool, snd_pitched) EXTERN_CVAR (Int, snd_hrtf) @@ -239,11 +188,10 @@ class OpenALSoundStream : public SoundStream /* Get a source, killing the farthest, lowest-priority sound if needed */ if(Renderer->FreeSfx.Size() == 0) { - /* FSoundChan *lowest = Renderer->FindLowestChannel(); if(lowest) Renderer->ForceStopChannel(lowest); - if(Renderer->FreeSfx.Size() == 0)*/ + if(Renderer->FreeSfx.Size() == 0) return false; } Renderer->FreeSfx.Pop(Source); @@ -547,26 +495,7 @@ static size_t GetChannelCount(ChannelConfig chans) static float GetRolloff(const FRolloffInfo *rolloff, float distance) { -#if 0 - if(distance <= rolloff->MinDistance) - return 1.f; - // Logarithmic rolloff has no max distance where it goes silent. - if(rolloff->RolloffType == ROLLOFF_Log) - return rolloff->MinDistance / - (rolloff->MinDistance + rolloff->RolloffFactor*(distance-rolloff->MinDistance)); - if(distance >= rolloff->MaxDistance) - return 0.f; - - float volume = (rolloff->MaxDistance - distance) / (rolloff->MaxDistance - rolloff->MinDistance); - if(rolloff->RolloffType == ROLLOFF_Linear) - return volume; - - if(rolloff->RolloffType == ROLLOFF_Custom && S_SoundCurve.Size() > 0) - return S_SoundCurve[int(S_SoundCurve.Size() * (1.f - volume))] / 127.f; - return (powf(10.f, volume) - 1.f) / 9.f; -#else - return 0; -#endif + return soundEngine->GetRolloff(rolloff, distance); } ALCdevice *OpenALSoundRenderer::InitDevice() @@ -988,10 +917,10 @@ void OpenALSoundRenderer::RemoveStream(OpenALSoundStream *stream) void OpenALSoundRenderer::SetSfxVolume(float volume) { -#if 0 SfxVolume = volume; - FSoundChan *schan = Channels; + if (!soundEngine) return; + FSoundChan *schan = soundEngine->GetChannels(); while(schan) { if(schan->SysChannel != NULL) @@ -1009,7 +938,6 @@ void OpenALSoundRenderer::SetSfxVolume(float volume) alProcessUpdatesSOFT(); getALError(); -#endif } void OpenALSoundRenderer::SetMusicVolume(float volume) @@ -1364,12 +1292,11 @@ std::pair OpenALSoundRenderer::LoadSoundBuffered(FSoundLoadBu void OpenALSoundRenderer::UnloadSound(SoundHandle sfx) { -#if 0 if(!sfx.data) return; ALuint buffer = GET_PTRID(sfx.data); - FSoundChan *schan = Channels; + FSoundChan *schan = soundEngine->GetChannels(); while(schan) { if(schan->SysChannel) @@ -1403,7 +1330,6 @@ void OpenALSoundRenderer::UnloadSound(SoundHandle sfx) alDeleteBuffers(1, &buffer); getALError(); -#endif } @@ -1422,7 +1348,6 @@ SoundStream *OpenALSoundRenderer::CreateStream(SoundStreamCallback callback, int FISoundChannel *OpenALSoundRenderer::StartSound(SoundHandle sfx, float vol, int pitch, int chanflags, FISoundChannel *reuse_chan) { -#if 0 if(FreeSfx.Size() == 0) { FSoundChan *lowest = FindLowestChannel(); @@ -1507,7 +1432,7 @@ FISoundChannel *OpenALSoundRenderer::StartSound(SoundHandle sfx, float vol, int FreeSfx.Pop(); FISoundChannel *chan = reuse_chan; - if(!chan) chan = S_GetChannel(MAKE_PTRID(source)); + if(!chan) chan = soundEngine->GetChannel(MAKE_PTRID(source)); else chan->SysChannel = MAKE_PTRID(source); chan->Rolloff.RolloffType = ROLLOFF_Log; @@ -1517,16 +1442,12 @@ FISoundChannel *OpenALSoundRenderer::StartSound(SoundHandle sfx, float vol, int chan->ManualRolloff = false; return chan; -#else - return 0; -#endif } FISoundChannel *OpenALSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener *listener, float vol, FRolloffInfo *rolloff, float distscale, int pitch, int priority, const FVector3 &pos, const FVector3 &vel, int channum, int chanflags, FISoundChannel *reuse_chan) { -#if 0 float dist_sqr = (float)(pos - listener->position).LengthSquared(); if(FreeSfx.Size() == 0) @@ -1605,7 +1526,7 @@ FISoundChannel *OpenALSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener * distance that corresponds to the area radius. */ alSourcef(source, AL_SOURCE_RADIUS, (chanflags&SNDF_AREA) ? // Clamp in case the max distance is <= the area radius - 1.f/MAX(GetRolloff(rolloff, AREA_SOUND_RADIUS), 0.00001f) : 0.f + 1.f/std::max(GetRolloff(rolloff, AREA_SOUND_RADIUS), 0.00001f) : 0.f ); } else if((chanflags&SNDF_AREA) && dist_sqr < AREA_SOUND_RADIUS*AREA_SOUND_RADIUS) @@ -1722,7 +1643,7 @@ FISoundChannel *OpenALSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener FreeSfx.Pop(); FISoundChannel *chan = reuse_chan; - if(!chan) chan = S_GetChannel(MAKE_PTRID(source)); + if(!chan) chan = soundEngine->GetChannel(MAKE_PTRID(source)); else chan->SysChannel = MAKE_PTRID(source); chan->Rolloff = *rolloff; @@ -1730,9 +1651,6 @@ FISoundChannel *OpenALSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener chan->ManualRolloff = manualRolloff; return chan; -#else - return 0; -#endif } void OpenALSoundRenderer::ChannelVolume(FISoundChannel *chan, float volume) @@ -1753,13 +1671,11 @@ void OpenALSoundRenderer::ChannelPitch(FISoundChannel *chan, float pitch) alDeferUpdatesSOFT(); -#if 0 ALuint source = GET_PTRID(chan->SysChannel); if (WasInWater && !(chan->ChanFlags & CHAN_UI)) alSourcef(source, AL_PITCH, std::max(pitch, 0.0001f)*PITCH_MULT); else alSourcef(source, AL_PITCH, std::max(pitch, 0.0001f)); -#endif } void OpenALSoundRenderer::FreeSource(ALuint source) @@ -1786,9 +1702,8 @@ void OpenALSoundRenderer::StopChannel(FISoundChannel *chan) ALuint source = GET_PTRID(chan->SysChannel); // Release first, so it can be properly marked as evicted if it's being killed -#if 0 - S_ChannelEnded(chan); -#endif + soundEngine->ChannelEnded(chan); + ALint state = AL_INITIAL; alGetSourcei(source, AL_SOURCE_STATE, &state); if(state != AL_PLAYING) @@ -1811,9 +1726,7 @@ void OpenALSoundRenderer::ForceStopChannel(FISoundChannel *chan) ALuint source = GET_PTRID(chan->SysChannel); if(!source) return; -#if 0 - S_ChannelEnded(chan); -#endif + soundEngine->ChannelEnded(chan); FreeSource(source); } @@ -1971,7 +1884,6 @@ void OpenALSoundRenderer::UpdateSoundParams3D(SoundListener *listener, FISoundCh void OpenALSoundRenderer::UpdateListener(SoundListener *listener) { -#if 0 if(!listener->valid) return; @@ -2034,7 +1946,7 @@ void OpenALSoundRenderer::UpdateListener(SoundListener *listener) alFilterf(EnvFilters[1], AL_LOWPASS_GAINHF, 1.f); // Apply the updated filters on the sources - FSoundChan *schan = Channels; + FSoundChan *schan = soundEngine->GetChannels(); while (schan) { ALuint source = GET_PTRID(schan->SysChannel); @@ -2047,7 +1959,7 @@ void OpenALSoundRenderer::UpdateListener(SoundListener *listener) } } - FSoundChan *schan = Channels; + FSoundChan *schan = soundEngine->GetChannels(); while (schan) { ALuint source = GET_PTRID(schan->SysChannel); @@ -2072,7 +1984,7 @@ void OpenALSoundRenderer::UpdateListener(SoundListener *listener) alFilterf(EnvFilters[1], AL_LOWPASS_GAIN, 1.f); alFilterf(EnvFilters[1], AL_LOWPASS_GAINHF, 1.f); - FSoundChan *schan = Channels; + FSoundChan *schan = soundEngine->GetChannels(); while (schan) { ALuint source = GET_PTRID(schan->SysChannel); @@ -2085,7 +1997,7 @@ void OpenALSoundRenderer::UpdateListener(SoundListener *listener) } } - FSoundChan *schan = Channels; + FSoundChan *schan = soundEngine->GetChannels(); while (schan) { ALuint source = GET_PTRID(schan->SysChannel); @@ -2095,7 +2007,6 @@ void OpenALSoundRenderer::UpdateListener(SoundListener *listener) } getALError(); } -#endif } void OpenALSoundRenderer::UpdateSounds() @@ -2118,7 +2029,7 @@ void OpenALSoundRenderer::UpdateSounds() ++iter; } } -#if 0 + if(ALC.EXT_disconnect) { ALCint connected = ALC_TRUE; @@ -2126,11 +2037,10 @@ void OpenALSoundRenderer::UpdateSounds() if(connected == ALC_FALSE) { Printf("Sound device disconnected; restarting...\n"); - S_SoundReset(); + soundEngine->Reset(); return; } } -#endif PurgeStoppedSources(); } @@ -2254,7 +2164,6 @@ void OpenALSoundRenderer::PrintDriversList() void OpenALSoundRenderer::PurgeStoppedSources() { -#if 0 // Release channels that are stopped for(uint32_t i = 0;i < SfxGroup.Size();++i) { @@ -2264,7 +2173,7 @@ void OpenALSoundRenderer::PurgeStoppedSources() if(state == AL_INITIAL || state == AL_PLAYING || state == AL_PAUSED) continue; - FSoundChan *schan = Channels; + FSoundChan *schan = soundEngine->GetChannels(); while(schan) { if(schan->SysChannel != NULL && src == GET_PTRID(schan->SysChannel)) @@ -2276,7 +2185,6 @@ void OpenALSoundRenderer::PurgeStoppedSources() } } getALError(); -#endif } void OpenALSoundRenderer::LoadReverb(const ReverbContainer *env) @@ -2387,8 +2295,7 @@ void OpenALSoundRenderer::LoadReverb(const ReverbContainer *env) FSoundChan *OpenALSoundRenderer::FindLowestChannel() { -#if 0 - FSoundChan *schan = Channels; + FSoundChan *schan = soundEngine->GetChannels(); FSoundChan *lowest = NULL; while(schan) { @@ -2402,9 +2309,62 @@ FSoundChan *OpenALSoundRenderer::FindLowestChannel() schan = schan->NextChan; } return lowest; -#else - return 0; +} + + +#include "menu/menu.h" + +void I_BuildALDeviceList(FOptionValues* opt) +{ + opt->mValues.Resize(1); + opt->mValues[0].TextValue = "Default"; + opt->mValues[0].Text = "Default"; + +#ifndef NO_OPENAL + if (IsOpenALPresent()) + { + const ALCchar* names = (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") ? + alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER) : + alcGetString(NULL, ALC_DEVICE_SPECIFIER)); + if (!names) + Printf("Failed to get device list: %s\n", alcGetString(NULL, alcGetError(NULL))); + else while (*names) + { + unsigned int i = opt->mValues.Reserve(1); + opt->mValues[i].TextValue = names; + opt->mValues[i].Text = names; + + names += strlen(names) + 1; + } + } #endif } +void I_BuildALResamplersList(FOptionValues* opt) +{ + opt->mValues.Resize(1); + opt->mValues[0].TextValue = "Default"; + opt->mValues[0].Text = "Default"; + +#ifndef NO_OPENAL + if (!IsOpenALPresent()) + return; + if (!alcGetCurrentContext() || !alIsExtensionPresent("AL_SOFT_source_resampler")) + return; + + LPALGETSTRINGISOFT alGetStringiSOFT = reinterpret_cast(alGetProcAddress("alGetStringiSOFT")); + ALint num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT); + + unsigned int idx = opt->mValues.Reserve(num_resamplers); + for (ALint i = 0; i < num_resamplers; ++i) + { + const ALchar* name = alGetStringiSOFT(AL_RESAMPLER_NAME_SOFT, i); + opt->mValues[idx].TextValue = name; + opt->mValues[idx].Text = name; + ++idx; + } +#endif +} + + #endif // NO_OPENAL diff --git a/source/common/music/backend/oalsound.h b/source/common/sound/backend/oalsound.h similarity index 99% rename from source/common/music/backend/oalsound.h rename to source/common/sound/backend/oalsound.h index 9624a2726..266d1f232 100644 --- a/source/common/music/backend/oalsound.h +++ b/source/common/sound/backend/oalsound.h @@ -8,7 +8,7 @@ #include #include "i_sound.h" -//#include "s_sound.h" +#include "s_soundinternal.h" #ifndef NO_OPENAL diff --git a/source/common/sound/s_environment.cpp b/source/common/sound/s_environment.cpp new file mode 100644 index 000000000..69c582c9c --- /dev/null +++ b/source/common/sound/s_environment.cpp @@ -0,0 +1,630 @@ +/* +** +** +**--------------------------------------------------------------------------- +** Copyright 2005-2016 Randy Heit +** Copyright 2005-2017 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "s_soundinternal.h" +#include "sc_man.h" +#include "templates.h" + + +FReverbField ReverbFields[] = +{ + { 0, 25, 0, &REVERB_PROPERTIES::Environment, 0 }, + { 1000, 100000, &REVERB_PROPERTIES::EnvSize, 0, 0 }, + { 0, 1000, &REVERB_PROPERTIES::EnvDiffusion, 0, 0 }, + { -10000, 0, 0, &REVERB_PROPERTIES::Room, 0 }, + { -10000, 0, 0, &REVERB_PROPERTIES::RoomHF, 0 }, + { -10000, 0, 0, &REVERB_PROPERTIES::RoomLF, 0 }, + { 100, 20000, &REVERB_PROPERTIES::DecayTime, 0, 0 }, + { 100, 2000, &REVERB_PROPERTIES::DecayHFRatio, 0, 0 }, + { 100, 2000, &REVERB_PROPERTIES::DecayLFRatio, 0, 0 }, + { -10000, 1000, 0, &REVERB_PROPERTIES::Reflections, 0 }, + { 0, 300, &REVERB_PROPERTIES::ReflectionsDelay, 0, 0 }, + { -2000000, 2000000, &REVERB_PROPERTIES::ReflectionsPan0, 0, 0 }, + { -2000000, 2000000, &REVERB_PROPERTIES::ReflectionsPan1, 0, 0 }, + { -2000000, 2000000, &REVERB_PROPERTIES::ReflectionsPan2, 0, 0 }, + { -10000, 2000, 0, &REVERB_PROPERTIES::Reverb, 0 }, + { 0, 100, &REVERB_PROPERTIES::ReverbDelay, 0, 0 }, + { -2000000, 2000000, &REVERB_PROPERTIES::ReverbPan0, 0, 0 }, + { -2000000, 2000000, &REVERB_PROPERTIES::ReverbPan1, 0, 0 }, + { -2000000, 2000000, &REVERB_PROPERTIES::ReverbPan2, 0, 0 }, + { 75, 250, &REVERB_PROPERTIES::EchoTime, 0, 0 }, + { 0, 1000, &REVERB_PROPERTIES::EchoDepth, 0, 0 }, + { 40, 4000, &REVERB_PROPERTIES::ModulationTime, 0, 0 }, + { 0, 1000, &REVERB_PROPERTIES::ModulationDepth, 0, 0 }, + { -100000, 0, &REVERB_PROPERTIES::AirAbsorptionHF, 0, 0 }, + { 1000000, 20000000, &REVERB_PROPERTIES::HFReference, 0, 0 }, + { 20000, 1000000, &REVERB_PROPERTIES::LFReference, 0, 0 }, + { 0, 10000, &REVERB_PROPERTIES::RoomRolloffFactor, 0, 0 }, + { 0, 100000, &REVERB_PROPERTIES::Diffusion, 0, 0 }, + { 0, 100000, &REVERB_PROPERTIES::Density, 0, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 2 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 5 }, + { 0, 0, 0, 0, 3 }, + { 0, 0, 0, 0, 4 }, + { 0, 0, 0, 0, 6 }, + { 0, 0, 0, 0, 7 } +}; +#define NUM_REVERB_FIELDS (int(countof(ReverbFields))) +int NumReverbs = NUM_REVERB_FIELDS; + +const char *ReverbFieldNames[NUM_REVERB_FIELDS+2] = +{ + "Environment", + "EnvironmentSize", + "EnvironmentDiffusion", + "Room", + "RoomHF", + "RoomLF", + "DecayTime", + "DecayHFRatio", + "DecayLFRatio", + "Reflections", + "ReflectionsDelay", + "ReflectionsPanX", + "ReflectionsPanY", + "ReflectionsPanZ", + "Reverb", + "ReverbDelay", + "ReverbPanX", + "ReverbPanY", + "ReverbPanZ", + "EchoTime", + "EchoDepth", + "ModulationTime", + "ModulationDepth", + "AirAbsorptionHF", + "HFReference", + "LFReference", + "RoomRolloffFactor", + "Diffusion", + "Density", + "bReflectionsScale", + "bReflectionsDelayScale", + "bDecayTimeScale", + "bDecayHFLimit", + "bReverbScale", + "bReverbDelayScale", + "bEchoTimeScale", + "bModulationTimeScale", + "}", + NULL +}; + +static const char *BoolNames[3] = { "False", "True", NULL }; + +static ReverbContainer DSPWater = +{ + // Based on the "off" reverb, this one uses the software water effect, + // which is completely independant from EAX-like reverb. + NULL, + "DSP Water", + 0xffff, + true, + false, + {0, 0, 7.5f, 1.00f, -10000, -10000, 0, 1.00f, 1.00f, 1.0f, -2602, 0.007f, 0.0f,0.0f,0.0f, 200, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 0.0f, 0.0f, 0x33f }, + true +}; + +static ReverbContainer Psychotic = +{ + &DSPWater, + "Psychotic", + 0x1900, + true, + false, + {0,25, 1.0f, 0.50f, -1000, -151, 0, 7.56f, 0.91f, 1.0f, -626, 0.020f, 0.0f,0.0f,0.0f, 774, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 4.00f, 1.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f }, + false +}; + +static ReverbContainer Dizzy = +{ + &Psychotic, + "Dizzy", + 0x1800, + true, + false, + {0,24, 1.8f, 0.60f, -1000, -400, 0, 17.23f, 0.56f, 1.0f, -1713, 0.020f, 0.0f,0.0f,0.0f, -613, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 1.00f, 0.81f, 0.310f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f }, + false +}; + +static ReverbContainer Drugged = +{ + &Dizzy, + "Drugged", + 0x1700, + true, + false, + {0,23, 1.9f, 0.50f, -1000, 0, 0, 8.39f, 1.39f, 1.0f, -115, 0.002f, 0.0f,0.0f,0.0f, 985, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 1.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f }, + false +}; + +static ReverbContainer Underwater = +{ + &Drugged, + "Underwater", + 0x1600, + true, + false, + {0,22, 1.8f, 1.00f, -1000, -4000, 0, 1.49f, 0.10f, 1.0f, -449, 0.007f, 0.0f,0.0f,0.0f, 1700, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 1.18f, 0.348f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer SewerPipe = +{ + &Underwater, + "Sewer Pipe", + 0x1500, + true, + false, + {0,21, 1.7f, 0.80f, -1000, -1000, 0, 2.81f, 0.14f, 1.0f, 429, 0.014f, 0.0f,0.0f,0.0f, 1023, 0.021f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 80.0f, 60.0f, 0x3f }, + false +}; + +static ReverbContainer ParkingLot = +{ + &SewerPipe, + "Parking Lot", + 0x1400, + true, + false, + {0,20, 8.3f, 1.00f, -1000, 0, 0, 1.65f, 1.50f, 1.0f, -1363, 0.008f, 0.0f,0.0f,0.0f, -1153, 0.012f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f }, + false +}; + +static ReverbContainer Plain = +{ + &ParkingLot, + "Plain", + 0x1300, + true, + false, + {0,19, 42.5f, 0.21f, -1000, -2000, 0, 1.49f, 0.50f, 1.0f, -2466, 0.179f, 0.0f,0.0f,0.0f, -1926, 0.100f, 0.0f,0.0f,0.0f, 0.250f, 1.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 21.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Quarry = +{ + &Plain, + "Quarry", + 0x1200, + true, + false, + {0,18, 17.5f, 1.00f, -1000, -1000, 0, 1.49f, 0.83f, 1.0f, -10000, 0.061f, 0.0f,0.0f,0.0f, 500, 0.025f, 0.0f,0.0f,0.0f, 0.125f, 0.70f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Mountains = +{ + &Quarry, + "Mountains", + 0x1100, + true, + false, + {0,17, 100.0f, 0.27f, -1000, -2500, 0, 1.49f, 0.21f, 1.0f, -2780, 0.300f, 0.0f,0.0f,0.0f, -1434, 0.100f, 0.0f,0.0f,0.0f, 0.250f, 1.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 27.0f, 100.0f, 0x1f }, + false +}; + +static ReverbContainer City = +{ + &Mountains, + "City", + 0x1000, + true, + false, + {0,16, 7.5f, 0.50f, -1000, -800, 0, 1.49f, 0.67f, 1.0f, -2273, 0.007f, 0.0f,0.0f,0.0f, -1691, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 50.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Forest = +{ + &City, + "Forest", + 0x0F00, + true, + false, + {0,15, 38.0f, 0.30f, -1000, -3300, 0, 1.49f, 0.54f, 1.0f, -2560, 0.162f, 0.0f,0.0f,0.0f, -229, 0.088f, 0.0f,0.0f,0.0f, 0.125f, 1.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 79.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Alley = +{ + &Forest, + "Alley", + 0x0E00, + true, + false, + {0,14, 7.5f, 0.30f, -1000, -270, 0, 1.49f, 0.86f, 1.0f, -1204, 0.007f, 0.0f,0.0f,0.0f, -4, 0.011f, 0.0f,0.0f,0.0f, 0.125f, 0.95f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer StoneCorridor = +{ + &Alley, + "Stone Corridor", + 0x0D00, + true, + false, + {0,13, 13.5f, 1.00f, -1000, -237, 0, 2.70f, 0.79f, 1.0f, -1214, 0.013f, 0.0f,0.0f,0.0f, 395, 0.020f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Hallway = +{ + &StoneCorridor, + "Hallway", + 0x0C00, + true, + false, + {0,12, 1.8f, 1.00f, -1000, -300, 0, 1.49f, 0.59f, 1.0f, -1219, 0.007f, 0.0f,0.0f,0.0f, 441, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer CarpettedHallway = +{ + &Hallway, + "Carpetted Hallway", + 0x0B00, + true, + false, + {0,11, 1.9f, 1.00f, -1000, -4000, 0, 0.30f, 0.10f, 1.0f, -1831, 0.002f, 0.0f,0.0f,0.0f, -1630, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Hangar = +{ + &CarpettedHallway, + "Hangar", + 0x0A00, + true, + false, + {0,10, 50.3f, 1.00f, -1000, -1000, 0, 10.05f, 0.23f, 1.0f, -602, 0.020f, 0.0f,0.0f,0.0f, 198, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Arena = +{ + &Hangar, + "Arena", + 0x0900, + true, + false, + {0, 9, 36.2f, 1.00f, -1000, -698, 0, 7.24f, 0.33f, 1.0f, -1166, 0.020f, 0.0f,0.0f,0.0f, 16, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Cave = +{ + &Arena, + "Cave", + 0x0800, + true, + false, + {0, 8, 14.6f, 1.00f, -1000, 0, 0, 2.91f, 1.30f, 1.0f, -602, 0.015f, 0.0f,0.0f,0.0f, -302, 0.022f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x1f }, + false +}; + +static ReverbContainer ConcertHall = +{ + &Cave, + "Concert Hall", + 0x0700, + true, + false, + {0, 7, 19.6f, 1.00f, -1000, -500, 0, 3.92f, 0.70f, 1.0f, -1230, 0.020f, 0.0f,0.0f,0.0f, -2, 0.029f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Auditorium = +{ + &ConcertHall, + "Auditorium", + 0x0600, + true, + false, + {0, 6, 21.6f, 1.00f, -1000, -476, 0, 4.32f, 0.59f, 1.0f, -789, 0.020f, 0.0f,0.0f,0.0f, -289, 0.030f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer StoneRoom = +{ + &Auditorium, + "Stone Room", + 0x0500, + true, + false, + {0, 5, 11.6f, 1.00f, -1000, -300, 0, 2.31f, 0.64f, 1.0f, -711, 0.012f, 0.0f,0.0f,0.0f, 83, 0.017f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer LivingRoom = +{ + &StoneRoom, + "Living Room", + 0x0400, + true, + false, + {0, 4, 2.5f, 1.00f, -1000, -6000, 0, 0.50f, 0.10f, 1.0f, -1376, 0.003f, 0.0f,0.0f,0.0f, -1104, 0.004f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Bathroom = +{ + &LivingRoom, + "Bathroom", + 0x0300, + true, + false, + {0, 3, 1.4f, 1.00f, -1000, -1200, 0, 1.49f, 0.54f, 1.0f, -370, 0.007f, 0.0f,0.0f,0.0f, 1030, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 60.0f, 0x3f }, + false +}; + +static ReverbContainer Room = +{ + &Bathroom, + "Room", + 0x0200, + true, + false, + {0, 2, 1.9f, 1.00f, -1000, -454, 0, 0.40f, 0.83f, 1.0f, -1646, 0.002f, 0.0f,0.0f,0.0f, 53, 0.003f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer PaddedCell = +{ + &Room, + "Padded Cell", + 0x0100, + true, + false, + {0, 1, 1.4f, 1.00f, -1000, -6000, 0, 0.17f, 0.10f, 1.0f, -1204, 0.001f, 0.0f,0.0f,0.0f, 207, 0.002f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Generic = +{ + &PaddedCell, + "Generic", + 0x0001, + true, + false, + {0, 0, 7.5f, 1.00f, -1000, -100, 0, 1.49f, 0.83f, 1.0f, -2602, 0.007f, 0.0f,0.0f,0.0f, 200, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 100.0f, 100.0f, 0x3f }, + false +}; + +static ReverbContainer Off = +{ + &Generic, + "Off", + 0x0000, + true, + false, + {0, 0, 7.5f, 1.00f, -10000, -10000, 0, 1.00f, 1.00f, 1.0f, -2602, 0.007f, 0.0f,0.0f,0.0f, 200, 0.011f, 0.0f,0.0f,0.0f, 0.250f, 0.00f, 0.25f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.0f, 0.0f, 0.0f, 0x33f }, + false +}; + +ReverbContainer *DefaultEnvironments[26] = +{ + &Off, &PaddedCell, &Room, &Bathroom, &LivingRoom, &StoneRoom, &Auditorium, + &ConcertHall, &Cave, &Arena, &Hangar, &CarpettedHallway, &Hallway, &StoneCorridor, + &Alley, &Forest, &City, &Mountains, &Quarry, &Plain, &ParkingLot, &SewerPipe, + &Underwater, &Drugged, &Dizzy, &Psychotic +}; + +ReverbContainer *Environments = &Off; + +ReverbContainer *S_FindEnvironment (const char *name) +{ + ReverbContainer *probe = Environments; + + if (name == NULL) + return NULL; + + while (probe != NULL) + { + if (stricmp (probe->Name, name) == 0) + { + return probe; + } + probe = probe->Next; + } + return NULL; +} + +ReverbContainer *S_FindEnvironment (int id) +{ + ReverbContainer *probe = Environments; + + while (probe != NULL && probe->ID < id) + { + probe = probe->Next; + } + return (probe && probe->ID == id ? probe : NULL); +} + +void S_AddEnvironment (ReverbContainer *settings) +{ + ReverbContainer *probe = Environments; + ReverbContainer **ptr = &Environments; + + while (probe != NULL && probe->ID < settings->ID) + { + ptr = &probe->Next; + probe = probe->Next; + } + + if (probe != NULL && probe->ID == settings->ID) + { + // Built-in environments cannot be changed + if (!probe->Builtin) + { + settings->Next = probe->Next; + *ptr = settings; + delete[] const_cast(probe->Name); + delete probe; + } + } + else + { + settings->Next = probe; + *ptr = settings; + } +} + +void S_ReadReverbDef (FScanner &sc) +{ + const ReverbContainer *def; + ReverbContainer *newenv; + REVERB_PROPERTIES props; + char *name; + int id1, id2, i, j; + bool inited[NUM_REVERB_FIELDS]; + uint8_t bools[32]; + + while (sc.GetString ()) + { + name = strdup (sc.String); + sc.MustGetNumber (); + id1 = sc.Number; + sc.MustGetNumber (); + id2 = sc.Number; + sc.MustGetStringName ("{"); + memset (inited, 0, sizeof(inited)); + props.Instance = 0; + props.Flags = 0; + while (sc.MustGetString (), NUM_REVERB_FIELDS > (i = sc.MustMatchString (ReverbFieldNames))) + { + if (ReverbFields[i].Float) + { + sc.MustGetFloat (); + props.*ReverbFields[i].Float = (float)clamp (sc.Float, + double(ReverbFields[i].Min)/1000, + double(ReverbFields[i].Max)/1000); + } + else if (ReverbFields[i].Int) + { + sc.MustGetNumber (); + props.*ReverbFields[i].Int = (j = clamp (sc.Number, + ReverbFields[i].Min, ReverbFields[i].Max)); + if (i == 0 && j != sc.Number) + { + sc.ScriptError ("The Environment field is out of range."); + } + } + else + { + sc.MustGetString (); + bools[ReverbFields[i].Flag] = sc.MustMatchString (BoolNames); + } + inited[i] = true; + } + if (!inited[0]) + { + sc.ScriptError ("Sound %s is missing an Environment field.", name); + } + + // Add the new environment to the list, filling in uninitialized fields + // with values from the standard environment specified. + def = DefaultEnvironments[props.Environment]; + for (i = 0; i < NUM_REVERB_FIELDS; ++i) + { + if (ReverbFields[i].Float) + { + if (!inited[i]) + { + props.*ReverbFields[i].Float = def->Properties.*ReverbFields[i].Float; + } + } + else if (ReverbFields[i].Int) + { + if (!inited[i]) + { + props.*ReverbFields[i].Int = def->Properties.*ReverbFields[i].Int; + } + } + else + { + if (!inited[i]) + { + int mask = 1 << ReverbFields[i].Flag; + if (def->Properties.Flags & mask) + { + props.Flags |= mask; + } + } + else + { + if (bools[ReverbFields[i].Flag]) + { + props.Flags |= 1 << ReverbFields[i].Flag; + } + } + } + } + + newenv = new ReverbContainer; + newenv->Next = NULL; + newenv->Name = name; + newenv->ID = (id1 << 8) | id2; + newenv->Builtin = false; + newenv->Properties = props; + newenv->SoftwareWater = false; + S_AddEnvironment (newenv); + } +} + +void S_UnloadReverbDef () +{ + ReverbContainer *probe = Environments; + ReverbContainer **pNext = NULL; + + while (probe != NULL) + { + ReverbContainer *next = probe->Next; + if (!probe->Builtin) + { + if (pNext != NULL) *pNext = probe->Next; + free(const_cast(probe->Name)); + delete probe; + } + else + { + pNext = &probe->Next; + } + probe = next; + } + Environments = &Off; +} + diff --git a/source/common/sound/s_sound.cpp b/source/common/sound/s_sound.cpp new file mode 100644 index 000000000..7f2cef683 --- /dev/null +++ b/source/common/sound/s_sound.cpp @@ -0,0 +1,1693 @@ +/* +** s_sound.cpp +** Main sound engine +** +**--------------------------------------------------------------------------- +** Copyright 1998-2016 Randy Heit +** Copyright 2002-2019 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include +#include +#ifdef _WIN32 +#include +#endif + +#include "s_soundinternal.h" +#include "m_swap.h" +#include "superfasthash.h" + +#ifdef DEFAULT_PITCH // undefine garbage from Windows.h +#undef DEFAULT_PITCH +#endif + +enum +{ + DEFAULT_PITCH = 128, +}; + +SoundEngine* soundEngine; +int sfx_empty = -1; + +//========================================================================== +// +// S_Init +// +//========================================================================== + +void SoundEngine::Init(TArray &curve) +{ + // Free all channels for use. + while (Channels != NULL) + { + ReturnChannel(Channels); + } + S_SoundCurve = std::move(curve); +} + +//========================================================================== +// +// SoundEngine::Clear +// +//========================================================================== + +void SoundEngine::Clear() +{ + StopAllChannels(); + UnloadAllSounds(); + GetSounds().Clear(); + ClearRandoms(); +} + +//========================================================================== +// +// S_Shutdown +// +//========================================================================== + +void SoundEngine::Shutdown () +{ + FSoundChan *chan, *next; + + StopAllChannels(); + + for (chan = FreeChannels; chan != NULL; chan = next) + { + next = chan->NextChan; + delete chan; + } + FreeChannels = NULL; +} + +//========================================================================== +// +// MarkUsed +// +//========================================================================== + +void SoundEngine::MarkUsed(int id) +{ + if ((unsigned)id < S_sfx.Size()) + { + S_sfx[id].bUsed = true; + } +} + +//========================================================================== +// +// Cache all marked sounds +// +//========================================================================== + +void SoundEngine::CacheMarkedSounds() +{ + // Don't unload sounds that are playing right now. + for (FSoundChan* chan = Channels; chan != nullptr; chan = chan->NextChan) + { + MarkUsed(chan->SoundID); + } + + for (unsigned i = 1; i < S_sfx.Size(); ++i) + { + if (S_sfx[i].bUsed) + { + CacheSound(&S_sfx[i]); + } + } + for (unsigned i = 1; i < S_sfx.Size(); ++i) + { + if (!S_sfx[i].bUsed && S_sfx[i].link == sfxinfo_t::NO_LINK) + { + UnloadSound(&S_sfx[i]); + } + } +} + +//========================================================================== +// +// S_CacheSound +// +//========================================================================== + +void SoundEngine::CacheSound (sfxinfo_t *sfx) +{ + if (GSnd) + { + if (sfx->bPlayerReserve) + { + return; + } + sfxinfo_t *orig = sfx; + while (!sfx->bRandomHeader && sfx->link != sfxinfo_t::NO_LINK) + { + sfx = &S_sfx[sfx->link]; + } + if (sfx->bRandomHeader) + { + CacheRandomSound(sfx); + } + else + { + // Since we do not know in what format the sound will be used, we have to cache both. + FSoundLoadBuffer SoundBuffer; + LoadSound(sfx, &SoundBuffer); + LoadSound3D(sfx, &SoundBuffer); + sfx->bUsed = true; + } + } +} + +//========================================================================== +// +// S_UnloadSound +// +//========================================================================== + +void SoundEngine::UnloadSound (sfxinfo_t *sfx) +{ + if (sfx->data3d.isValid() && sfx->data != sfx->data3d) + GSnd->UnloadSound(sfx->data3d); + if (sfx->data.isValid()) + GSnd->UnloadSound(sfx->data); + sfx->data.Clear(); + sfx->data3d.Clear(); +} + +//========================================================================== +// +// S_GetChannel +// +// Returns a free channel for the system sound interface. +// +//========================================================================== + +FSoundChan *SoundEngine::GetChannel(void *syschan) +{ + FSoundChan *chan; + + if (FreeChannels != NULL) + { + chan = FreeChannels; + UnlinkChannel(chan); + } + else + { + chan = new FSoundChan; + memset(chan, 0, sizeof(*chan)); + } + LinkChannel(chan, &Channels); + chan->SysChannel = syschan; + return chan; +} + +//========================================================================== +// +// S_ReturnChannel +// +// Returns a channel to the free pool. +// +//========================================================================== + +void SoundEngine::ReturnChannel(FSoundChan *chan) +{ + UnlinkChannel(chan); + memset(chan, 0, sizeof(*chan)); + LinkChannel(chan, &FreeChannels); +} + +//========================================================================== +// +// S_UnlinkChannel +// +//========================================================================== + +void SoundEngine::UnlinkChannel(FSoundChan *chan) +{ + *(chan->PrevChan) = chan->NextChan; + if (chan->NextChan != NULL) + { + chan->NextChan->PrevChan = chan->PrevChan; + } +} + +//========================================================================== +// +// S_LinkChannel +// +//========================================================================== + +void SoundEngine::LinkChannel(FSoundChan *chan, FSoundChan **head) +{ + chan->NextChan = *head; + if (chan->NextChan != NULL) + { + chan->NextChan->PrevChan = &chan->NextChan; + } + *head = chan; + chan->PrevChan = head; +} + +//========================================================================== +// +// +// +//========================================================================== + +TArray SoundEngine::AllActiveChannels() +{ + TArray chans; + + for (auto chan = Channels; chan != nullptr; chan = chan->NextChan) + { + // If the sound is forgettable, this is as good a time as + // any to forget about it. And if it's a UI sound, it shouldn't + // be stored in the savegame. + if (!(chan->ChanFlags & (CHAN_FORGETTABLE | CHAN_UI))) + { + chans.Push(chan); + } + } + return chans; +} + +//========================================================================== +// +// +// +//========================================================================== + +FString SoundEngine::ListSoundChannels() +{ + FString output; + FSoundChan* chan; + int count = 0; + for (chan = Channels; chan != nullptr; chan = chan->NextChan) + { + if (!(chan->ChanFlags & CHAN_EVICTED)) + { + FVector3 chanorigin; + + CalcPosVel(chan, &chanorigin, nullptr); + + output.AppendFormat("%s at (%1.5f, %1.5f, %1.5f)\n", (const char*)S_sfx[chan->SoundID].name.GetChars(), chanorigin.X, chanorigin.Y, chanorigin.Z); + count++; + } + } + output.AppendFormat("%d sounds playing\n", count); + return output; +} + +// [RH] Split S_StartSoundAtVolume into multiple parts so that sounds can +// be specified both by id and by name. Also borrowed some stuff from +// Hexen and parameters from Quake. + +//========================================================================== +// +// CalcPosVel +// +// Retrieves a sound's position and velocity for 3D sounds. This version +// is for an already playing sound. +// +//========================================================================= + +void SoundEngine::CalcPosVel(FSoundChan *chan, FVector3 *pos, FVector3 *vel) +{ + CalcPosVel(chan->SourceType, chan->Source, chan->Point, + chan->EntChannel, chan->ChanFlags, pos, vel); +} + +bool SoundEngine::ValidatePosVel(const FSoundChan* const chan, const FVector3& pos, const FVector3& vel) +{ + return ValidatePosVel(chan->SourceType, chan->Source, pos, vel); +} + +//========================================================================== +// +// +// +//========================================================================== + +FSoundID SoundEngine::ResolveSound(const void *, int, FSoundID soundid, float &attenuation) +{ + const sfxinfo_t &sfx = S_sfx[soundid]; + + if (sfx.bRandomHeader) + { + // Random sounds attenuate based on the original (random) sound as well as the chosen one. + attenuation *= sfx.Attenuation; + return PickReplacement (soundid); + } + else + { + return sfx.link; + } +} + +//========================================================================== +// +// S_StartSound +// +// 0 attenuation means full volume over whole primaryLevel-> +// 0 < attenuation means to scale the distance by that amount when +// calculating volume. +// +//========================================================================== + +FSoundChan *SoundEngine::StartSound(int type, const void *source, + const FVector3 *pt, int channel, FSoundID sound_id, float volume, float attenuation, + FRolloffInfo *forcedrolloff, float spitch) +{ + sfxinfo_t *sfx; + int chanflags; + int basepriority; + int org_id; + int pitch; + FSoundChan *chan; + FVector3 pos, vel; + FRolloffInfo *rolloff; + FSoundLoadBuffer SoundBuffer; + + if (sound_id <= 0 || volume <= 0 || nosfx || nosound ) + return NULL; + + // prevent crashes. + if (type == SOURCE_Unattached && pt == nullptr) type = SOURCE_None; + + org_id = sound_id; + chanflags = channel & ~7; + channel &= 7; + + CalcPosVel(type, source, &pt->X, channel, chanflags, &pos, &vel); + + if (!ValidatePosVel(type, source, pos, vel)) + { + return nullptr; + } + + sfx = &S_sfx[sound_id]; + + // Scale volume according to SNDINFO data. + volume = std::min(volume * sfx->Volume, 1.f); + if (volume <= 0) + return NULL; + + // When resolving a link we do not want to get the NearLimit of + // the referenced sound so some additional checks are required + int near_limit = sfx->NearLimit; + float limit_range = sfx->LimitRange; + auto pitchmask = sfx->PitchMask; + rolloff = &sfx->Rolloff; + + // Resolve player sounds, random sounds, and aliases + while (sfx->link != sfxinfo_t::NO_LINK) + { + sound_id = ResolveSound(source, type, sound_id, attenuation); + if (sound_id < 0) return nullptr; + auto newsfx = &S_sfx[sound_id]; + if (newsfx != sfx) + { + if (near_limit < 0) + { + near_limit = newsfx->NearLimit; + limit_range = newsfx->LimitRange; + } + if (rolloff->MinDistance == 0) + { + rolloff = &newsfx->Rolloff; + } + sfx = newsfx; + } + else return nullptr; // nothing got replaced, prevent an endless loop, + + } + + // Attenuate the attenuation based on the sound. + attenuation *= sfx->Attenuation; + + // The passed rolloff overrides any sound-specific rolloff. + if (forcedrolloff != NULL && forcedrolloff->MinDistance != 0) + { + rolloff = forcedrolloff; + } + + // If no valid rolloff was set, use the global default. + if (rolloff->MinDistance == 0) + { + rolloff = &S_Rolloff; + } + + // If this is a singular sound, don't play it if it's already playing. + if (sfx->bSingular && CheckSingular(sound_id)) + { + chanflags |= CHAN_EVICTED; + } + + // If the sound is unpositioned or comes from the listener, it is + // never limited. + if (type == SOURCE_None || source == listener.ListenerObject) + { + near_limit = 0; + } + + // If this sound doesn't like playing near itself, don't play it if + // that's what would happen. (Does this really need the SOURCE_Actor restriction?) + if (near_limit > 0 && CheckSoundLimit(sfx, pos, near_limit, limit_range, type, type == SOURCE_Actor? source : nullptr, channel)) + { + chanflags |= CHAN_EVICTED; + } + + // If the sound is blocked and not looped, return now. If the sound + // is blocked and looped, pretend to play it so that it can + // eventually play for real. + if ((chanflags & (CHAN_EVICTED | CHAN_LOOP)) == CHAN_EVICTED) + { + return NULL; + } + + // Make sure the sound is loaded. + sfx = LoadSound(sfx, &SoundBuffer); + + // The empty sound never plays. + if (sfx->lumpnum == sfx_empty) + { + return NULL; + } + + // Select priority. + if (type == SOURCE_None || source == listener.ListenerObject) + { + basepriority = 80; + } + else + { + basepriority = 0; + } + + int seen = 0; + if (source != NULL && channel == CHAN_AUTO) + { + // Select a channel that isn't already playing something. + // Try channel 0 first, then travel from channel 7 down. + if (!IsChannelUsed(type, source, 0, &seen)) + { + channel = 0; + } + else + { + for (channel = 7; channel > 0; --channel) + { + if (!IsChannelUsed(type, source, channel, &seen)) + { + break; + } + } + if (channel == 0) + { // Crap. No free channels. + return NULL; + } + } + } + + // If this actor is already playing something on the selected channel, stop it. + if (type != SOURCE_None && ((source == NULL && channel != CHAN_AUTO) || (source != NULL && IsChannelUsed(type, source, channel, &seen)))) + { + for (chan = Channels; chan != NULL; chan = chan->NextChan) + { + if (chan->SourceType == type && chan->EntChannel == channel) + { + const bool foundit = (type == SOURCE_Unattached) + ? (chan->Point[0] == pt->X && chan->Point[2] == pt->Z && chan->Point[1] == pt->Y) + : (chan->Source == source); + + if (foundit) + { + StopChannel(chan); + break; + } + } + } + } + + // sound is paused and a non-looped sound is being started. + // Such a sound would play right after unpausing which wouldn't sound right. + if (!(chanflags & CHAN_LOOP) && !(chanflags & (CHAN_UI|CHAN_NOPAUSE)) && SoundPaused) + { + return NULL; + } + + // Vary the sfx pitches. + if (pitchmask != 0) + { + pitch = DEFAULT_PITCH - (rand() & pitchmask) + (rand() & pitchmask); + } + else + { + pitch = DEFAULT_PITCH; + } + + if (chanflags & CHAN_EVICTED) + { + chan = NULL; + } + else + { + int startflags = 0; + if (chanflags & CHAN_LOOP) startflags |= SNDF_LOOP; + if (chanflags & CHAN_AREA) startflags |= SNDF_AREA; + if (chanflags & (CHAN_UI|CHAN_NOPAUSE)) startflags |= SNDF_NOPAUSE; + if (chanflags & CHAN_UI) startflags |= SNDF_NOREVERB; + + if (attenuation > 0) + { + LoadSound3D(sfx, &SoundBuffer); + chan = (FSoundChan*)GSnd->StartSound3D (sfx->data3d, &listener, float(volume), rolloff, float(attenuation), pitch, basepriority, pos, vel, channel, startflags, NULL); + } + else + { + chan = (FSoundChan*)GSnd->StartSound (sfx->data, float(volume), pitch, startflags, NULL); + } + } + if (chan == NULL && (chanflags & CHAN_LOOP)) + { + chan = (FSoundChan*)GetChannel(NULL); + GSnd->MarkStartTime(chan); + chanflags |= CHAN_EVICTED; + } + if (attenuation > 0) + { + chanflags |= CHAN_IS3D | CHAN_JUSTSTARTED; + } + else + { + chanflags |= CHAN_LISTENERZ | CHAN_JUSTSTARTED; + } + if (chan != NULL) + { + chan->SoundID = sound_id; + chan->OrgID = FSoundID(org_id); + chan->EntChannel = channel; + chan->Volume = float(volume); + chan->ChanFlags |= chanflags; + chan->NearLimit = near_limit; + chan->LimitRange = limit_range; + chan->Pitch = pitch; + chan->Priority = basepriority; + chan->DistanceScale = float(attenuation); + chan->SourceType = type; + if (type == SOURCE_Unattached) + { + chan->Point[0] = pt->X; chan->Point[1] = pt->Y; chan->Point[2] = pt->Z; + } + else if (type != SOURCE_None) + { + chan->Source = source; + } + + if (spitch > 0.0) + SetPitch(chan, spitch); + } + + return chan; +} + +//========================================================================== +// +// S_RestartSound +// +// Attempts to restart looping sounds that were evicted from their channels. +// +//========================================================================== + +void SoundEngine::RestartChannel(FSoundChan *chan) +{ + assert(chan->ChanFlags & CHAN_EVICTED); + + FSoundChan *ochan; + sfxinfo_t *sfx = &S_sfx[chan->SoundID]; + FSoundLoadBuffer SoundBuffer; + + // If this is a singular sound, don't play it if it's already playing. + if (sfx->bSingular && CheckSingular(chan->SoundID)) + return; + + sfx = LoadSound(sfx, &SoundBuffer); + + // The empty sound never plays. + if (sfx->lumpnum == sfx_empty) + { + return; + } + + int oldflags = chan->ChanFlags; + + int startflags = 0; + if (chan->ChanFlags & CHAN_LOOP) startflags |= SNDF_LOOP; + if (chan->ChanFlags & CHAN_AREA) startflags |= SNDF_AREA; + if (chan->ChanFlags & (CHAN_UI|CHAN_NOPAUSE)) startflags |= SNDF_NOPAUSE; + if (chan->ChanFlags & CHAN_ABSTIME) startflags |= SNDF_ABSTIME; + + if (chan->ChanFlags & CHAN_IS3D) + { + FVector3 pos, vel; + + CalcPosVel(chan, &pos, &vel); + + if (!ValidatePosVel(chan, pos, vel)) + { + return; + } + + // If this sound doesn't like playing near itself, don't play it if + // that's what would happen. + if (chan->NearLimit > 0 && CheckSoundLimit(&S_sfx[chan->SoundID], pos, chan->NearLimit, chan->LimitRange, 0, NULL, 0)) + { + return; + } + + LoadSound3D(sfx, &SoundBuffer); + chan->ChanFlags &= ~(CHAN_EVICTED|CHAN_ABSTIME); + ochan = (FSoundChan*)GSnd->StartSound3D(sfx->data3d, &listener, chan->Volume, &chan->Rolloff, chan->DistanceScale, chan->Pitch, + chan->Priority, pos, vel, chan->EntChannel, startflags, chan); + } + else + { + chan->ChanFlags &= ~(CHAN_EVICTED|CHAN_ABSTIME); + ochan = (FSoundChan*)GSnd->StartSound(sfx->data, chan->Volume, chan->Pitch, startflags, chan); + } + assert(ochan == NULL || ochan == chan); + if (ochan == NULL) + { + chan->ChanFlags = oldflags; + } +} + +//========================================================================== +// +// S_LoadSound +// +// Returns a pointer to the sfxinfo with the actual sound data. +// +//========================================================================== + +sfxinfo_t *SoundEngine::LoadSound(sfxinfo_t *sfx, FSoundLoadBuffer *pBuffer) +{ + if (GSnd->IsNull()) return sfx; + + while (!sfx->data.isValid()) + { + unsigned int i; + + // If the sound doesn't exist, replace it with the empty sound. + if (sfx->lumpnum == -1) + { + sfx->lumpnum = sfx_empty; + } + + // See if there is another sound already initialized with this lump. If so, + // then set this one up as a link, and don't load the sound again. + for (i = 0; i < S_sfx.Size(); i++) + { + if (S_sfx[i].data.isValid() && S_sfx[i].link == sfxinfo_t::NO_LINK && S_sfx[i].lumpnum == sfx->lumpnum) + { + //DPrintf (DMSG_NOTIFY, "Linked %s to %s (%d)\n", sfx->name.GetChars(), S_sfx[i].name.GetChars(), i); + sfx->link = i; + // This is necessary to avoid using the rolloff settings of the linked sound if its + // settings are different. + if (sfx->Rolloff.MinDistance == 0) sfx->Rolloff = S_Rolloff; + return &S_sfx[i]; + } + } + + //DPrintf(DMSG_NOTIFY, "Loading sound \"%s\" (%td)\n", sfx->name.GetChars(), sfx - &S_sfx[0]); + + auto sfxdata = ReadSound(sfx->lumpnum); + int size = sfxdata.Size(); + if (size > 8) + { + int32_t dmxlen = LittleLong(((int32_t *)sfxdata.Data())[1]); + std::pair snd; + + // If the sound is voc, use the custom loader. + if (strncmp ((const char *)sfxdata.Data(), "Creative Voice File", 19) == 0) + { + snd = GSnd->LoadSoundVoc(sfxdata.Data(), size); + } + // If the sound is raw, just load it as such. + else if (sfx->bLoadRAW) + { + snd = GSnd->LoadSoundRaw(sfxdata.Data(), size, sfx->RawRate, 1, 8, sfx->LoopStart); + } + // Otherwise, try the sound as DMX format. + else if (((uint8_t *)sfxdata.Data())[0] == 3 && ((uint8_t *)sfxdata.Data())[1] == 0 && dmxlen <= size - 8) + { + int frequency = LittleShort(((uint16_t *)sfxdata.Data())[1]); + if (frequency == 0) frequency = 11025; + snd = GSnd->LoadSoundRaw(sfxdata.Data()+8, dmxlen, frequency, 1, 8, sfx->LoopStart); + } + // If that fails, let the sound system try and figure it out. + else + { + snd = GSnd->LoadSound(sfxdata.Data(), size, false, pBuffer); + } + + sfx->data = snd.first; + if(snd.second) + sfx->data3d = sfx->data; + } + + if (!sfx->data.isValid()) + { + if (sfx->lumpnum != sfx_empty) + { + sfx->lumpnum = sfx_empty; + continue; + } + } + break; + } + return sfx; +} + +void SoundEngine::LoadSound3D(sfxinfo_t *sfx, FSoundLoadBuffer *pBuffer) +{ + if (GSnd->IsNull()) return; + + if(sfx->data3d.isValid()) + return; + + //DPrintf(DMSG_NOTIFY, "Loading monoized sound \"%s\" (%td)\n", sfx->name.GetChars(), sfx - &S_sfx[0]); + + std::pair snd; + + if (pBuffer->mBuffer.size() > 0) + { + snd = GSnd->LoadSoundBuffered(pBuffer, true); + } + else + { + auto sfxdata = ReadSound(sfx->lumpnum); + int size = sfxdata.Size(); + if (size <= 8) return; + int32_t dmxlen = LittleLong(((int32_t *)sfxdata.Data())[1]); + + // If the sound is voc, use the custom loader. + if (strncmp((const char *)sfxdata.Data(), "Creative Voice File", 19) == 0) + { + snd = GSnd->LoadSoundVoc(sfxdata.Data(), size, true); + } + // If the sound is raw, just load it as such. + else if (sfx->bLoadRAW) + { + snd = GSnd->LoadSoundRaw(sfxdata.Data(), size, sfx->RawRate, 1, 8, sfx->LoopStart, true); + } + // Otherwise, try the sound as DMX format. + else if (((uint8_t *)sfxdata.Data())[0] == 3 && ((uint8_t *)sfxdata.Data())[1] == 0 && dmxlen <= size - 8) + { + int frequency = LittleShort(((uint16_t *)sfxdata.Data())[1]); + if (frequency == 0) frequency = 11025; + snd = GSnd->LoadSoundRaw(sfxdata.Data() + 8, dmxlen, frequency, 1, 8, sfx->LoopStart, -1, true); + } + // If that fails, let the sound system try and figure it out. + else + { + snd = GSnd->LoadSound(sfxdata.Data(), size, true, pBuffer); + } + } + + sfx->data3d = snd.first; +} + +//========================================================================== +// +// S_CheckSingular +// +// Returns true if a copy of this sound is already playing. +// +//========================================================================== + +bool SoundEngine::CheckSingular(int sound_id) +{ + for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan) + { + if (chan->OrgID == sound_id) + { + return true; + } + } + return false; +} + +//========================================================================== +// +// S_CheckSoundLimit +// +// Limits the number of nearby copies of a sound that can play near +// each other. If there are NearLimit instances of this sound already +// playing within sqrt(limit_range) (typically 256 units) of the new sound, the +// new sound will not start. +// +// If an actor is specified, and it is already playing the same sound on +// the same channel, this sound will not be limited. In this case, we're +// restarting an already playing sound, so there's no need to limit it. +// +// Returns true if the sound should not play. +// +//========================================================================== + +bool SoundEngine::CheckSoundLimit(sfxinfo_t *sfx, const FVector3 &pos, int near_limit, float limit_range, + int sourcetype, const void *actor, int channel) +{ + FSoundChan *chan; + int count; + + for (chan = Channels, count = 0; chan != NULL && count < near_limit; chan = chan->NextChan) + { + if (!(chan->ChanFlags & CHAN_EVICTED) && &S_sfx[chan->SoundID] == sfx) + { + FVector3 chanorigin; + + if (actor != NULL && chan->EntChannel == channel && + chan->SourceType == sourcetype && chan->Source == actor) + { // We are restarting a playing sound. Always let it play. + return false; + } + + CalcPosVel(chan, &chanorigin, NULL); + if ((chanorigin - pos).LengthSquared() <= limit_range) + { + count++; + } + } + } + return count >= near_limit; +} + +//========================================================================== +// +// S_StopSound +// +// Stops an unpositioned sound from playing on a specific channel. +// +//========================================================================== + +void SoundEngine::StopSound (int channel) +{ + FSoundChan *chan = Channels; + while (chan != NULL) + { + FSoundChan *next = chan->NextChan; + if (chan->SourceType == SOURCE_None) + { + StopChannel(chan); + } + chan = next; + } +} + +//========================================================================== +// +// S_StopSound +// +// Stops a sound from a single actor from playing on a specific channel. +// +//========================================================================== + +void SoundEngine::StopSound(int sourcetype, const void* actor, int channel) +{ + FSoundChan* chan = Channels; + while (chan != NULL) + { + FSoundChan* next = chan->NextChan; + if (chan->SourceType == sourcetype && + chan->Source == actor && + (chan->EntChannel == channel || channel < 0)) + { + StopChannel(chan); + } + chan = next; + } +} + +//========================================================================== +// +// S_StopAllChannels +// +//========================================================================== + +void SoundEngine::StopAllChannels () +{ + FSoundChan *chan = Channels; + while (chan != NULL) + { + FSoundChan *next = chan->NextChan; + StopChannel(chan); + chan = next; + } + + if (GSnd) + GSnd->UpdateSounds(); +} + +//========================================================================== +// +// S_RelinkSound +// +// Moves all the sounds from one thing to another. If the destination is +// NULL, then the sound becomes a positioned sound. +//========================================================================== + +void SoundEngine::RelinkSound (int sourcetype, const void *from, const void *to, const FVector3 *optpos) +{ + if (from == NULL) + return; + + FSoundChan *chan = Channels; + while (chan != NULL) + { + FSoundChan *next = chan->NextChan; + if (chan->SourceType == sourcetype && chan->Source == from) + { + if (to != NULL) + { + chan->Source = to; + } + else if (!(chan->ChanFlags & CHAN_LOOP) && optpos) + { + chan->Source = NULL; + chan->SourceType = SOURCE_Unattached; + chan->Point[0] = optpos->X; + chan->Point[1] = optpos->Y; + chan->Point[2] = optpos->Z; + } + else + { + StopChannel(chan); + } + } + chan = next; + } +} + + +//========================================================================== +// +// S_ChangeSoundVolume +// +//========================================================================== + +void SoundEngine::ChangeSoundVolume(int sourcetype, const void *source, int channel, double dvolume) +{ + float volume = float(dvolume); + // don't let volume get out of bounds + if (volume < 0.0) + volume = 0.0; + else if (volume > 1.0) + volume = 1.0; + + for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan) + { + if (chan->SourceType == sourcetype && + chan->Source == source && + (chan->EntChannel == channel || channel == -1)) + { + GSnd->ChannelVolume(chan, volume); + chan->Volume = volume; + return; + } + } + return; +} + +//========================================================================== +// +// S_ChangeSoundPitch +// +//========================================================================== + +void SoundEngine::ChangeSoundPitch(int sourcetype, const void *source, int channel, double pitch) +{ + for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan) + { + if (chan->SourceType == sourcetype && + chan->Source == source && + chan->EntChannel == channel) + { + SetPitch(chan, (float)pitch); + return; + } + } + return; +} + +void SoundEngine::SetPitch(FSoundChan *chan, float pitch) +{ + assert(chan != nullptr); + GSnd->ChannelPitch(chan, std::max(0.0001f, pitch)); + chan->Pitch = std::max(1, int(float(DEFAULT_PITCH) * pitch)); +} + +//========================================================================== +// +// S_GetSoundPlayingInfo +// +// Is a sound being played by a specific emitter? +//========================================================================== + +bool SoundEngine::GetSoundPlayingInfo (int sourcetype, const void *source, int sound_id) +{ + if (sound_id > 0) + { + for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan) + { + if (chan->OrgID == sound_id && + chan->SourceType == sourcetype && + chan->Source == source) + { + return true; + } + } + } + return false; +} + +//========================================================================== +// +// S_IsChannelUsed +// +// Returns true if the channel is in use. Also fills in a bitmask of +// channels seen while scanning for this one, to make searching for unused +// channels faster. Initialize seen to 0 for the first call. +// +//========================================================================== + +bool SoundEngine::IsChannelUsed(int sourcetype, const void *actor, int channel, int *seen) +{ + if (*seen & (1 << channel)) + { + return true; + } + for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan) + { + if (chan->SourceType == sourcetype && chan->Source == actor) + { + *seen |= 1 << chan->EntChannel; + if (chan->EntChannel == channel) + { + return true; + } + } + } + return false; +} + +//========================================================================== +// +// S_IsActorPlayingSomething +// +//========================================================================== + +bool SoundEngine::IsSourcePlayingSomething (int sourcetype, const void *actor, int channel, int sound_id) +{ + for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan) + { + if (chan->SourceType == sourcetype && chan->Source == actor) + { + if (channel == 0 || chan->EntChannel == channel) + { + return sound_id <= 0 || chan->OrgID == sound_id; + } + } + } + return false; +} + +//========================================================================== +// +// S_EvictAllChannels +// +// Forcibly evicts all channels so that there are none playing, but all +// information needed to restart them is retained. +// +//========================================================================== + +void SoundEngine::EvictAllChannels() +{ + FSoundChan *chan, *next; + + for (chan = Channels; chan != NULL; chan = next) + { + next = chan->NextChan; + + if (!(chan->ChanFlags & CHAN_EVICTED)) + { + chan->ChanFlags |= CHAN_EVICTED; + if (chan->SysChannel != NULL) + { + if (!(chan->ChanFlags & CHAN_ABSTIME)) + { + chan->StartTime = GSnd ? GSnd->GetPosition(chan) : 0; + chan->ChanFlags |= CHAN_ABSTIME; + } + StopChannel(chan); + } +// assert(chan->NextChan == next); + } + } +} + +//========================================================================== +// +// S_RestoreEvictedChannel +// +// Recursive helper for S_RestoreEvictedChannels(). +// +//========================================================================== + +void SoundEngine::RestoreEvictedChannel(FSoundChan *chan) +{ + if (chan == NULL) + { + return; + } + RestoreEvictedChannel(chan->NextChan); + if (chan->ChanFlags & CHAN_EVICTED) + { + RestartChannel(chan); + if (!(chan->ChanFlags & CHAN_LOOP)) + { + if (chan->ChanFlags & CHAN_EVICTED) + { // Still evicted and not looping? Forget about it. + ReturnChannel(chan); + } + else if (!(chan->ChanFlags & CHAN_JUSTSTARTED)) + { // Should this sound become evicted again, it's okay to forget about it. + chan->ChanFlags |= CHAN_FORGETTABLE; + } + } + } + else if (chan->SysChannel == NULL && (chan->ChanFlags & (CHAN_FORGETTABLE | CHAN_LOOP)) == CHAN_FORGETTABLE) + { + ReturnChannel(chan); + } +} + +//========================================================================== +// +// S_RestoreEvictedChannels +// +// Restarts as many evicted channels as possible. Any channels that could +// not be started and are not looping are moved to the free pool. +// +//========================================================================== + +void SoundEngine::RestoreEvictedChannels() +{ + // Restart channels in the same order they were originally played. + RestoreEvictedChannel(Channels); +} + +//========================================================================== +// +// S_UpdateSounds +// +// Updates music & sounds +//========================================================================== + +void SoundEngine::UpdateSounds(int time) +{ + FVector3 pos, vel; + + for (FSoundChan* chan = Channels; chan != NULL; chan = chan->NextChan) + { + if ((chan->ChanFlags & (CHAN_EVICTED | CHAN_IS3D)) == CHAN_IS3D) + { + CalcPosVel(chan, &pos, &vel); + + if (ValidatePosVel(chan, pos, vel)) + { + GSnd->UpdateSoundParams3D(&listener, chan, !!(chan->ChanFlags & CHAN_AREA), pos, vel); + } + } + chan->ChanFlags &= ~CHAN_JUSTSTARTED; + } + + GSnd->UpdateListener(&listener); + GSnd->UpdateSounds(); + + if (time >= RestartEvictionsAt) + { + RestartEvictionsAt = 0; + RestoreEvictedChannels(); + } +} + +//========================================================================== +// +// S_GetRolloff +// +//========================================================================== + +float SoundEngine::GetRolloff(const FRolloffInfo* rolloff, float distance) +{ + if (rolloff == NULL) + { + return 0; + } + if (distance <= rolloff->MinDistance) + { + return 1.f; + } + // Logarithmic rolloff has no max distance where it goes silent. + if (rolloff->RolloffType == ROLLOFF_Log) + { + return rolloff->MinDistance / (rolloff->MinDistance + rolloff->RolloffFactor * (distance - rolloff->MinDistance)); + } + if (distance >= rolloff->MaxDistance) + { + return 0.f; + } + + float volume = (rolloff->MaxDistance - distance) / (rolloff->MaxDistance - rolloff->MinDistance); + if (rolloff->RolloffType == ROLLOFF_Linear) + { + return volume; + } + + if (rolloff->RolloffType == ROLLOFF_Custom && S_SoundCurve.Size() > 0) + { + return S_SoundCurve[int(S_SoundCurve.Size() * (1.f - volume))] / 127.f; + } + return (powf(10.f, volume) - 1.f) / 9.f; +} + +//========================================================================== +// +// S_ChannelEnded (callback for sound interface code) +// +//========================================================================== + +void SoundEngine::ChannelEnded(FISoundChannel *ichan) +{ + FSoundChan *schan = static_cast(ichan); + bool evicted; + + if (schan != NULL) + { + // If the sound was stopped with GSnd->StopSound(), then we know + // it wasn't evicted. Otherwise, if it's looping, it must have + // been evicted. If it's not looping, then it was evicted if it + // didn't reach the end of its playback. + if (schan->ChanFlags & CHAN_FORGETTABLE) + { + evicted = false; + } + else if (schan->ChanFlags & (CHAN_LOOP | CHAN_EVICTED)) + { + evicted = true; + } + else + { + unsigned int pos = GSnd->GetPosition(schan); + unsigned int len = GSnd->GetSampleLength(S_sfx[schan->SoundID].data); + if (pos == 0) + { + evicted = !!(schan->ChanFlags & CHAN_JUSTSTARTED); + } + else + { + evicted = (pos < len); + } + } + if (!evicted) + { + ReturnChannel(schan); + } + else + { + schan->ChanFlags |= CHAN_EVICTED; + schan->SysChannel = NULL; + } + } +} + +//========================================================================== +// +// S_ChannelVirtualChanged (callback for sound interface code) +// +//========================================================================== + +void SoundEngine::ChannelVirtualChanged(FISoundChannel *ichan, bool is_virtual) +{ + FSoundChan *schan = static_cast(ichan); + if (is_virtual) + { + schan->ChanFlags |= CHAN_VIRTUAL; + } + else + { + schan->ChanFlags &= ~CHAN_VIRTUAL; + } +} + +//========================================================================== +// +// StopChannel +// +//========================================================================== + +void SoundEngine::StopChannel(FSoundChan *chan) +{ + if (chan == NULL) + return; + + if (chan->SysChannel != NULL) + { + // S_EvictAllChannels() will set the CHAN_EVICTED flag to indicate + // that it wants to keep all the channel information around. + if (!(chan->ChanFlags & CHAN_EVICTED)) + { + chan->ChanFlags |= CHAN_FORGETTABLE; + if (chan->SourceType == SOURCE_Actor) + { + chan->Source = NULL; + } + } + GSnd->StopChannel(chan); + } + else + { + ReturnChannel(chan); + } +} + +void SoundEngine::UnloadAllSounds() +{ + for (unsigned i = 0; i < S_sfx.Size(); i++) + { + UnloadSound(&S_sfx[i]); + } +} + +void SoundEngine::Reset() +{ + EvictAllChannels(); + I_CloseSound(); + I_InitSound(); + RestoreEvictedChannels(); +} + + +//========================================================================== +// +// S_FindSound +// +// Given a logical name, find the sound's index in S_sfx. +//========================================================================== + +int SoundEngine::FindSound(const char* logicalname) +{ + int i; + + if (logicalname != NULL) + { + i = S_sfx[MakeKey(logicalname) % S_sfx.Size()].index; + + while ((i != 0) && stricmp(S_sfx[i].name, logicalname)) + i = S_sfx[i].next; + + return i; + } + else + { + return 0; + } +} + +int SoundEngine::FindSoundByResID(int resid) +{ + auto p = ResIdMap.CheckKey(resid); + return p ? *p : 0; +} + +//========================================================================== +// +// S_FindSoundNoHash +// +// Given a logical name, find the sound's index in S_sfx without +// using the hash table. +//========================================================================== + +int SoundEngine::FindSoundNoHash(const char* logicalname) +{ + unsigned int i; + + for (i = 1; i < S_sfx.Size(); i++) + { + if (stricmp(S_sfx[i].name, logicalname) == 0) + { + return i; + } + } + return 0; +} + +//========================================================================== +// +// S_FindSoundByLump +// +// Given a sound lump, find the sound's index in S_sfx. +//========================================================================== + +int SoundEngine::FindSoundByLump(int lump) +{ + if (lump != -1) + { + unsigned int i; + + for (i = 1; i < S_sfx.Size(); i++) + if (S_sfx[i].lumpnum == lump) + return i; + } + return 0; +} + +//========================================================================== +// +// S_AddSoundLump +// +// Adds a new sound mapping to S_sfx. +//========================================================================== + +int SoundEngine::AddSoundLump(const char* logicalname, int lump, int CurrentPitchMask, int resid) +{ + S_sfx.Reserve(1); + sfxinfo_t &newsfx = S_sfx.Last(); + + newsfx.data.Clear(); + newsfx.data3d.Clear(); + newsfx.name = logicalname; + newsfx.lumpnum = lump; + newsfx.next = 0; + newsfx.index = 0; + newsfx.Volume = 1; + newsfx.Attenuation = 1; + newsfx.PitchMask = CurrentPitchMask; + newsfx.NearLimit = 2; + newsfx.LimitRange = 256 * 256; + newsfx.bRandomHeader = false; + newsfx.bPlayerReserve = false; + newsfx.bLoadRAW = false; + newsfx.bPlayerCompat = false; + newsfx.b16bit = false; + newsfx.bUsed = false; + newsfx.bSingular = false; + newsfx.bTentative = false; + newsfx.bPlayerSilent = false; + newsfx.ResourceId = resid; + newsfx.RawRate = 0; + newsfx.link = sfxinfo_t::NO_LINK; + newsfx.Rolloff.RolloffType = ROLLOFF_Doom; + newsfx.Rolloff.MinDistance = 0; + newsfx.Rolloff.MaxDistance = 0; + newsfx.LoopStart = -1; + + if (resid >= 0) ResIdMap[resid] = S_sfx.Size() - 1; + return (int)S_sfx.Size()-1; +} + +//========================================================================== +// +// S_FindSoundTentative +// +// Given a logical name, find the sound's index in S_sfx without +// using the hash table. If it does not exist, a new sound without +// an associated lump is created. +//========================================================================== + +int SoundEngine::FindSoundTentative(const char* name) +{ + int id = FindSoundNoHash(name); + if (id == 0) + { + id = AddSoundLump(name, -1, 0); + S_sfx[id].bTentative = true; + } + return id; +} + + +//========================================================================== +// +// S_CacheRandomSound +// +// Loads all sounds a random sound might play. +// +//========================================================================== + +void SoundEngine::CacheRandomSound(sfxinfo_t* sfx) +{ + if (sfx->bRandomHeader) + { + const FRandomSoundList* list = &S_rnd[sfx->link]; + for (unsigned i = 0; i < list->Choices.Size(); ++i) + { + sfx = &S_sfx[list->Choices[i]]; + sfx->bUsed = true; + CacheSound(&S_sfx[list->Choices[i]]); + } + } +} + +//========================================================================== +// +// S_GetSoundMSLength +// +// Returns duration of sound +// GZDoom does not use this due to player sound handling +// +//========================================================================== + +unsigned int SoundEngine::GetMSLength(FSoundID sound) +{ + if ((unsigned int)sound >= S_sfx.Size()) + { + return 0; + } + + sfxinfo_t* sfx = &S_sfx[sound]; + + // Resolve player sounds, random sounds, and aliases + if (sfx->link != sfxinfo_t::NO_LINK) + { + if (sfx->bRandomHeader) + { + // Hm... What should we do here? + // Pick the longest or the shortest sound? + // I think the longest one makes more sense. + + int length = 0; + const FRandomSoundList* list = &S_rnd[sfx->link]; + + for (auto& me : list->Choices) + { + // unfortunately we must load all sounds to find the longest one... :( + int thislen = GetMSLength(me); + if (thislen > length) length = thislen; + } + return length; + } + else + { + sfx = &S_sfx[sfx->link]; + } + } + + sfx = LoadSound(sfx, nullptr); + if (sfx != NULL) return GSnd->GetMSLength(sfx->data); + else return 0; +} + +//========================================================================== +// +// S_PickReplacement +// +// Picks a replacement sound from the associated random list. If this sound +// is not the head of a random list, then the sound passed is returned. +//========================================================================== + +int SoundEngine::PickReplacement(int refid) +{ + while (S_sfx[refid].bRandomHeader) + { + const FRandomSoundList* list = &S_rnd[S_sfx[refid].link]; + refid = list->Choices[rand() % int(list->Choices.Size())]; + } + return refid; +} + +//========================================================================== +// +// S_HashSounds +// +// Fills in the next and index fields of S_sfx to form a working hash table. +//========================================================================== + +void SoundEngine::HashSounds() +{ + unsigned int i; + unsigned int j; + unsigned int size; + + S_sfx.ShrinkToFit(); + size = S_sfx.Size(); + + // Mark all buckets as empty + for (i = 0; i < size; i++) + S_sfx[i].index = 0; + + // Now set up the chains + for (i = 1; i < size; i++) + { + j = MakeKey(S_sfx[i].name) % size; + S_sfx[i].next = S_sfx[j].index; + S_sfx[j].index = i; + } + S_rnd.ShrinkToFit(); +} + +void SoundEngine::AddRandomSound(int Owner, TArray list) +{ + auto index = S_rnd.Reserve(1); + auto& random = S_rnd.Last(); + random.Choices = std::move(list); + random.Owner = Owner; + S_sfx[Owner].link = index; + S_sfx[Owner].bRandomHeader = true; + S_sfx[Owner].NearLimit = -1; +} diff --git a/source/common/sound/s_soundinternal.h b/source/common/sound/s_soundinternal.h new file mode 100644 index 000000000..e38053ad8 --- /dev/null +++ b/source/common/sound/s_soundinternal.h @@ -0,0 +1,401 @@ +#pragma once + +#include "backend/i_sound.h" + +struct FRandomSoundList +{ + TArray Choices; + uint32_t Owner = 0; +}; + +extern int sfx_empty; + +// +// SoundFX struct. +// +struct sfxinfo_t +{ + // Next field is for use by the system sound interface. + // A non-null data means the sound has been loaded. + SoundHandle data; + // Also for the sound interface. Used for 3D positional + // sounds, may be the same as data. + SoundHandle data3d; + + FString name; // [RH] Sound name defined in SNDINFO + int lumpnum; // lump number of sfx + + unsigned int next, index; // [RH] For hashing + float Volume; + + int ResourceId; // Resource ID as implemented by Blood. Not used by Doom but added for completeness. + uint8_t PitchMask; + int16_t NearLimit; // 0 means unlimited + float LimitRange; // Range for sound limiting (squared for faster computations) + + unsigned bRandomHeader:1; + unsigned bLoadRAW:1; + unsigned b16bit:1; + unsigned bUsed:1; + unsigned bSingular:1; + + unsigned bTentative:1; + unsigned bPlayerReserve : 1; + unsigned bPlayerCompat : 1; + unsigned bPlayerSilent:1; // This player sound is intentionally silent. + + int RawRate; // Sample rate to use when bLoadRAW is true + + int LoopStart; // -1 means no specific loop defined + + unsigned int link; + enum { NO_LINK = 0xffffffff }; + + FRolloffInfo Rolloff; + float Attenuation; // Multiplies the attenuation passed to S_Sound. + + void MarkUsed(); // Marks this sound as used. +}; + +// Rolloff types +enum +{ + ROLLOFF_Doom, // Linear rolloff with a logarithmic volume scale + ROLLOFF_Linear, // Linear rolloff with a linear volume scale + ROLLOFF_Log, // Logarithmic rolloff (standard hardware type) + ROLLOFF_Custom // Lookup volume from SNDCURVE +}; + +int S_FindSound(const char *logicalname); +int S_FindSoundByResID(int snd_id); + +// An index into the S_sfx[] array. +class FSoundID +{ +public: + FSoundID() = default; + + static FSoundID byResId(int ndx) + { + return FSoundID(S_FindSoundByResID(ndx)); + } + FSoundID(int id) + { + ID = id; + } + FSoundID(const char *name) + { + ID = S_FindSound(name); + } + FSoundID(const FString &name) + { + ID = S_FindSound(name.GetChars()); + } + FSoundID(const FSoundID &other) = default; + FSoundID &operator=(const FSoundID &other) = default; + FSoundID &operator=(const char *name) + { + ID = S_FindSound(name); + return *this; + } + FSoundID &operator=(const FString &name) + { + ID = S_FindSound(name.GetChars()); + return *this; + } + bool operator !=(FSoundID other) const + { + return ID != other.ID; + } + bool operator !=(int other) const + { + return ID != other; + } + operator int() const + { + return ID; + } +private: + int ID; +protected: + enum EDummy { NoInit }; + FSoundID(EDummy) {} +}; + + class FSoundIDNoInit : public FSoundID +{ +public: + FSoundIDNoInit() : FSoundID(NoInit) {} + using FSoundID::operator=; +}; + + + +struct FSoundChan : public FISoundChannel +{ + FSoundChan *NextChan; // Next channel in this list. + FSoundChan **PrevChan; // Previous channel in this list. + FSoundID SoundID; // Sound ID of playing sound. + FSoundID OrgID; // Sound ID of sound used to start this channel. + float Volume; + int16_t Pitch; // Pitch variation. + uint8_t EntChannel; // Actor's sound channel. + int8_t Priority; + int16_t NearLimit; + uint8_t SourceType; + float LimitRange; + union + { + const void *Source; + float Point[3]; // Sound is not attached to any source. + }; +}; + + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) always override a playing sound on that channel +// +// CHAN_AUTO searches down from channel 7 until it finds a channel not in use +// CHAN_WEAPON is for weapons +// CHAN_VOICE is for oof, sight, or other voice sounds +// CHAN_ITEM is for small things and item pickup +// CHAN_BODY is for generic body sounds +// CHAN_PICKUP can optionally be set as a local sound only for "compatibility" + +enum +{ + CHAN_AUTO = 0, + CHAN_WEAPON = 1, + CHAN_VOICE = 2, + CHAN_ITEM = 3, + CHAN_BODY = 4, + CHAN_5 = 5, + CHAN_6 = 6, + CHAN_7 = 7, + + // Channel alias for sector sounds. These define how listener height is + // used when calculating 3D sound volume. + CHAN_FLOOR = 1, // Sound comes from the floor. + CHAN_CEILING = 2, // Sound comes from the ceiling. + CHAN_FULLHEIGHT = 3, // Sound comes entire height of the sector. + CHAN_INTERIOR = 4, // Sound comes height between floor and ceiling. + + // modifier flags + CHAN_LISTENERZ = 8, + CHAN_MAYBE_LOCAL = 16, + CHAN_UI = 32, // Do not record sound in savegames. + CHAN_NOPAUSE = 64, // Do not pause this sound in menus. + CHAN_AREA = 128, // Sound plays from all around. Only valid with sector sounds. + CHAN_LOOP = 256, + + CHAN_PICKUP = (CHAN_ITEM|CHAN_MAYBE_LOCAL), + + CHAN_IS3D = 1, // internal: Sound is 3D. + CHAN_EVICTED = 2, // internal: Sound was evicted. + CHAN_FORGETTABLE = 4, // internal: Forget channel data when sound stops. + CHAN_JUSTSTARTED = 512, // internal: Sound has not been updated yet. + CHAN_ABSTIME = 1024, // internal: Start time is absolute and does not depend on current time. + CHAN_VIRTUAL = 2048, // internal: Channel is currently virtual + CHAN_NOSTOP = 4096, // only for A_PlaySound. Does not start if channel is playing something. +}; + +// sound attenuation values +#define ATTN_NONE 0.f // full volume the entire level +#define ATTN_NORM 1.f +#define ATTN_IDLE 1.001f +#define ATTN_STATIC 3.f // diminish very rapidly with distance + +enum // This cannot be remain as this, but for now it has to suffice. +{ + SOURCE_None, // Sound is always on top of the listener. + SOURCE_Actor, // Sound is coming from an actor. + SOURCE_Sector, // Sound is coming from a sector. + SOURCE_Polyobj, // Sound is coming from a polyobject. + SOURCE_Unattached, // Sound is not attached to any particular emitter. +}; + + +extern ReverbContainer *Environments; +extern ReverbContainer *DefaultEnvironments[26]; + +void S_ParseReverbDef (); +void S_UnloadReverbDef (); +void S_SetEnvironment (const ReverbContainer *settings); +ReverbContainer *S_FindEnvironment (const char *name); +ReverbContainer *S_FindEnvironment (int id); +void S_AddEnvironment (ReverbContainer *settings); + +class SoundEngine +{ +protected: + bool SoundPaused = false; // whether sound is paused + int RestartEvictionsAt = 0; // do not restart evicted channels before this time + SoundListener listener{}; + + FSoundChan* Channels = nullptr; + FSoundChan* FreeChannels = nullptr; + + // the complete set of sound effects + TArray S_sfx; + FRolloffInfo S_Rolloff; + TArray S_SoundCurve; + TMap ResIdMap; + TArray S_rnd; + +private: + void LoadSound3D(sfxinfo_t* sfx, FSoundLoadBuffer* pBuffer); + void LinkChannel(FSoundChan* chan, FSoundChan** head); + void UnlinkChannel(FSoundChan* chan); + void ReturnChannel(FSoundChan* chan); + void RestartChannel(FSoundChan* chan); + void RestoreEvictedChannel(FSoundChan* chan); + + bool IsChannelUsed(int sourcetype, const void* actor, int channel, int* seen); + // This is the actual sound positioning logic which needs to be provided by the client. + virtual void CalcPosVel(int type, const void* source, const float pt[3], int channel, int chanflags, FVector3* pos, FVector3* vel) = 0; + // This can be overridden by the clent to provide some diagnostics. The default lets everything pass. + virtual bool ValidatePosVel(int sourcetype, const void* source, const FVector3& pos, const FVector3& vel) { return true; } + + bool ValidatePosVel(const FSoundChan* const chan, const FVector3& pos, const FVector3& vel); + + // Checks if a copy of this sound is already playing. + bool CheckSingular(int sound_id); + bool CheckSoundLimit(sfxinfo_t* sfx, const FVector3& pos, int near_limit, float limit_range, int sourcetype, const void* actor, int channel); + virtual TArray ReadSound(int lumpnum) = 0; +protected: + virtual FSoundID ResolveSound(const void *ent, int srctype, FSoundID soundid, float &attenuation); + +public: + virtual ~SoundEngine() = default; + void EvictAllChannels(); + + void StopChannel(FSoundChan* chan); + sfxinfo_t* LoadSound(sfxinfo_t* sfx, FSoundLoadBuffer* pBuffer); + + // Initializes sound stuff, including volume + // Sets channels, SFX and music volume, + // allocates channel buffer, sets S_sfx lookup. + // + void Init(TArray &sndcurve); + void InitData(); + void Clear(); + void Shutdown(); + + void StopAllChannels(void); + void SetPitch(FSoundChan* chan, float dpitch); + + FSoundChan* GetChannel(void* syschan); + void RestoreEvictedChannels(); + void CalcPosVel(FSoundChan* chan, FVector3* pos, FVector3* vel); + + // Loads a sound, including any random sounds it might reference. + void CacheSound(sfxinfo_t* sfx); + void CacheSound(int sfx) { CacheSound(&S_sfx[sfx]); } + void UnloadSound(sfxinfo_t* sfx); + + void UpdateSounds(int time); + + FSoundChan* StartSound(int sourcetype, const void* source, + const FVector3* pt, int channel, FSoundID sound_id, float volume, float attenuation, FRolloffInfo* rolloff = nullptr, float spitch = 0.0f); + + // Stops an origin-less sound from playing from this channel. + void StopSound(int channel); + void StopSound(int sourcetype, const void* actor, int channel); + + void RelinkSound(int sourcetype, const void* from, const void* to, const FVector3* optpos); + void ChangeSoundVolume(int sourcetype, const void* source, int channel, double dvolume); + void ChangeSoundPitch(int sourcetype, const void* source, int channel, double pitch); + bool IsSourcePlayingSomething(int sourcetype, const void* actor, int channel, int sound_id); + + // Stop and resume music, during game PAUSE. + bool GetSoundPlayingInfo(int sourcetype, const void* source, int sound_id); + void UnloadAllSounds(); + void Reset(); + void MarkUsed(int num); + void CacheMarkedSounds(); + TArray AllActiveChannels(); + + void MarkAllUnused() + { + for (auto & s: S_sfx) s.bUsed = false; + } + + bool isListener(const void* object) const + { + return object && listener.ListenerObject == object; + } + bool isPlayerReserve(int snd_id) + { + return S_sfx[snd_id].bPlayerReserve; // Later this needs to be abstracted out of the engine itself. Right now that cannot be done. + } + void SetListener(SoundListener& l) + { + listener = l; + } + void SetRestartTime(int time) + { + RestartEvictionsAt = time; + } + void SetPaused(bool on) + { + SoundPaused = on; + } + FSoundChan* GetChannels() + { + return Channels; + } + const char *GetSoundName(FSoundID id) + { + return id == 0 ? "" : S_sfx[id].name.GetChars(); + } + TArray &GetSounds() //Thio should only be used for constructing the sound list or for diagnostics code prinring information about the sound list. + { + return S_sfx; + } + FRolloffInfo& GlobalRolloff() // like GetSounds this is meant for sound list generators, not for gaining cheap access to the sound engine's innards. + { + return S_Rolloff; + } + FRandomSoundList *ResolveRandomSound(sfxinfo_t* sfx) + { + return &S_rnd[sfx->link]; + } + void ClearRandoms() + { + S_rnd.Clear(); + } + + void ChannelVirtualChanged(FISoundChannel* ichan, bool is_virtual); + FString ListSoundChannels(); + + // Allow this to be overridden for special needs. + virtual float GetRolloff(const FRolloffInfo* rolloff, float distance); + virtual void ChannelEnded(FISoundChannel* ichan); // allows the client to do bookkeeping on the sound. + + // Lookup utilities. + int FindSound(const char* logicalname); + int FindSoundByResID(int rid); + int FindSoundNoHash(const char* logicalname); + int FindSoundByLump(int lump); + int AddSoundLump(const char* logicalname, int lump, int CurrentPitchMask, int resid = -1); + int FindSoundTentative(const char* name); + void CacheRandomSound(sfxinfo_t* sfx); + unsigned int GetMSLength(FSoundID sound); + int PickReplacement(int refid); + void HashSounds(); + void AddRandomSound(int Owner, TArray list); +}; + + +extern SoundEngine* soundEngine; + +struct FReverbField +{ + int Min, Max; + float REVERB_PROPERTIES::* Float; + int REVERB_PROPERTIES::* Int; + unsigned int Flag; +}; + + diff --git a/source/common/music/thirdparty/al.h b/source/common/sound/thirdparty/al.h similarity index 100% rename from source/common/music/thirdparty/al.h rename to source/common/sound/thirdparty/al.h diff --git a/source/common/music/thirdparty/alc.h b/source/common/sound/thirdparty/alc.h similarity index 100% rename from source/common/music/thirdparty/alc.h rename to source/common/sound/thirdparty/alc.h diff --git a/source/common/music/thirdparty/alext.h b/source/common/sound/thirdparty/alext.h similarity index 100% rename from source/common/music/thirdparty/alext.h rename to source/common/sound/thirdparty/alext.h diff --git a/source/duke3d/src/premap.cpp b/source/duke3d/src/premap.cpp index 16be9253a..3ddb516bc 100644 --- a/source/duke3d/src/premap.cpp +++ b/source/duke3d/src/premap.cpp @@ -1325,7 +1325,6 @@ void G_NewGame(int volumeNum, int levelNum, int skillNum) while (S_CheckSoundPlaying(ud.skill_voice)) gameHandleEvents(); } - ud.skill_voice = 0; ready2send = 0; diff --git a/source/duke3d/src/sounds_old.cpp b/source/duke3d/src/sounds_old.cpp new file mode 100644 index 000000000..48949bdee --- /dev/null +++ b/source/duke3d/src/sounds_old.cpp @@ -0,0 +1,720 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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 the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "ns.h" // Must come before everything else! + +#include "compat.h" + +#include "duke3d.h" +#include "renderlayer.h" // for win_gethwnd() +#include "al_midi.h" +#include "openaudio.h" +#include "z_music.h" +#include "mapinfo.h" +#include + +BEGIN_DUKE_NS + + +#define DQSIZE 256 + +int32_t g_numEnvSoundsPlaying, g_highestSoundIdx; + +static char *MusicPtr; + +static int32_t MusicIsWaveform; +static int32_t MusicVoice = -1; + +static bool MusicPaused; +static bool SoundPaused; + +static std::atomic dnum, dq[DQSIZE]; +static mutex_t m_callback; + +static inline void S_SetProperties(assvoice_t *snd, int const owner, int const voice, int const dist, int const clock) +{ + snd->owner = owner; + snd->id = voice; + snd->dist = dist; + snd->clock = clock; +} + +void S_SoundStartup(void) +{ +#ifdef _WIN32 + void *initdata = (void *) win_gethwnd(); // used for DirectSound +#else + void *initdata = NULL; +#endif + + initprintf("Initializing sound... "); + + int status = FX_Init(snd_numvoices, snd_numchannels, snd_mixrate, initdata); + if (status != FX_Ok) + { + initprintf("failed! %s\n", FX_ErrorString(status)); + return; + } + + initprintf("%d voices, %d channels, 16-bit %d Hz\n", *snd_numvoices, *snd_numchannels, *snd_mixrate); + + for (int i = 0; i <= g_highestSoundIdx; ++i) + { + for (auto & voice : g_sounds[i].voices) + { + g_sounds[i].num = 0; + S_SetProperties(&voice, -1, 0, UINT16_MAX, 0); + } + } + + snd_fxvolume.Callback(); + + snd_reversestereo.Callback(); + FX_SetCallBack(S_Callback); + FX_SetPrintf(OSD_Printf); +} + +void S_SoundShutdown(void) +{ + int status = FX_Shutdown(); + if (status != FX_Ok) + { + Bsprintf(tempbuf, "S_SoundShutdown(): error: %s", FX_ErrorString(status)); + G_GameExit(tempbuf); + } +} + +void S_PauseSounds(bool paused) +{ + if (SoundPaused == paused) + return; + + SoundPaused = paused; + + for (int i = 0; i <= g_highestSoundIdx; ++i) + { + for (auto & voice : g_sounds[i].voices) + if (voice.id > 0) + FX_PauseVoice(voice.id, paused); + } +} + +void S_MenuSound(void) +{ + static int SoundNum; + int const menusnds[] = { + LASERTRIP_EXPLODE, DUKE_GRUNT, DUKE_LAND_HURT, CHAINGUN_FIRE, SQUISHED, KICK_HIT, + PISTOL_RICOCHET, PISTOL_BODYHIT, PISTOL_FIRE, SHOTGUN_FIRE, BOS1_WALK, RPG_EXPLODE, + PIPEBOMB_BOUNCE, PIPEBOMB_EXPLODE, NITEVISION_ONOFF, RPG_SHOOT, SELECT_WEAPON, + }; + int s = VM_OnEventWithReturn(EVENT_OPENMENUSOUND, g_player[screenpeek].ps->i, screenpeek, FURY ? -1 : menusnds[SoundNum++ % ARRAY_SIZE(menusnds)]); + if (s != -1) + S_PlaySound(s); +} + + +static void S_SetMusicIndex(unsigned int m) +{ + ud.music_episode = m / MAXLEVELS; + ud.music_level = m % MAXLEVELS; +} + +void S_PlayLevelMusicOrNothing(unsigned int m) +{ + ud.returnvar[0] = m / MAXLEVELS; + ud.returnvar[1] = m % MAXLEVELS; + + int retval = VM_OnEvent(EVENT_PLAYLEVELMUSICSLOT, g_player[myconnectindex].ps->i, myconnectindex); + + if (retval >= 0) + { + // Thanks to scripting that stupid slot hijack cannot be refactored - but we'll store the real data elsewhere anyway! + auto &mr = m == USERMAPMUSICFAKESLOT ? userMapRecord : mapList[m]; + Mus_Play(mr.labelName, mr.music, true); + S_SetMusicIndex(m); + } +} + +int S_TryPlaySpecialMusic(unsigned int m) +{ + auto &musicfn = mapList[m].music; + if (musicfn.IsNotEmpty()) + { + if (!Mus_Play(nullptr, musicfn, true)) + { + S_SetMusicIndex(m); + return 0; + } + } + + return 1; +} + +void S_PlaySpecialMusicOrNothing(unsigned int m) +{ + if (S_TryPlaySpecialMusic(m)) + { + S_SetMusicIndex(m); + } +} + +void S_ContinueLevelMusic(void) +{ + VM_OnEvent(EVENT_CONTINUELEVELMUSICSLOT, g_player[myconnectindex].ps->i, myconnectindex); +} + +void S_Cleanup(void) +{ + static uint32_t ldnum = 0; + + while (ldnum < dnum) + { + uint32_t num = dq[ldnum++ & (DQSIZE - 1)]; + + // negative index is RTS playback + if ((int32_t)num < 0) + { + continue; + } + + // num + (MAXSOUNDS*MAXSOUNDINSTANCES) is a sound played globally + // for which there was no open slot to keep track of the voice + if (num >= (MAXSOUNDS*MAXSOUNDINSTANCES)) + { + continue; + } + + int const voiceindex = num & (MAXSOUNDINSTANCES - 1); + + num = (num - voiceindex) / MAXSOUNDINSTANCES; + + auto &snd = g_sounds[num]; + auto &voice = snd.voices[voiceindex]; + + int const spriteNum = voice.owner; + + if (EDUKE32_PREDICT_FALSE(snd.num > MAXSOUNDINSTANCES)) + OSD_Printf(OSD_ERROR "S_Cleanup(): num exceeds MAXSOUNDINSTANCES! g_sounds[%d].num %d wtf?\n", num, snd.num); + else if (snd.num > 0) + --snd.num; + + // MUSICANDSFX uses t_data[0] to control restarting the sound + // CLEAR_SOUND_T0 + if (spriteNum != -1 && S_IsAmbientSFX(spriteNum) && sector[SECT(spriteNum)].lotag < 3) // ST_2_UNDERWATER + actor[spriteNum].t_data[0] = 0; + + S_SetProperties(&voice, -1, 0, UINT16_MAX, 0); + + } +} + +// returns number of bytes read +int32_t S_LoadSound(int num) +{ + if ((unsigned)num > (unsigned)g_highestSoundIdx || EDUKE32_PREDICT_FALSE(g_sounds[num].filename == NULL)) + return 0; + + auto &snd = g_sounds[num]; + + auto fp = S_OpenAudio(snd.filename, 0, 0); + + if (!fp.isOpen()) + { + OSD_Printf(OSDTEXT_RED "Sound %s(#%d) not found!\n", snd.filename, num); + return 0; + } + + int32_t l = fp.GetLength(); + snd.siz = l; + cacheAllocateBlock((intptr_t *)&snd.ptr, l, nullptr); + l = fp.Read(snd.ptr, l); + + return l; +} + +void cacheAllSounds(void) +{ + for (int i=0, j=0; i <= g_highestSoundIdx; ++i) + { + if (g_sounds[i].ptr == 0) + { + j++; + if ((j&7) == 0) + gameHandleEvents(); + + S_LoadSound(i); + } + } +} + +static inline int S_GetPitch(int num) +{ + auto const &snd = g_sounds[num]; + int const range = klabs(snd.pe - snd.ps); + + return (range == 0) ? snd.ps : min(snd.ps, snd.pe) + rand() % range; +} + +static int S_TakeSlot(int soundNum) +{ + S_Cleanup(); + + uint16_t dist = 0; + uint16_t clock = 0; + + int bestslot = 0; + int slot = 0; + + auto &snd = g_sounds[soundNum]; + + while (slot < MAXSOUNDINSTANCES && snd.voices[slot].id > 0) + { + auto &voice = snd.voices[slot]; + + if (voice.dist > dist || (voice.dist == dist && voice.clock > clock)) + { + clock = voice.clock; + dist = voice.dist; + + bestslot = slot; + } + + slot++; + } + + if (slot != MAXSOUNDINSTANCES) + return slot; + + if (FX_SoundActive(snd.voices[bestslot].id)) + FX_StopSound(snd.voices[bestslot].id); + + mutex_lock(&m_callback); + unative_t const ldnum = dnum; + dq[ldnum & (DQSIZE-1)] = (soundNum * MAXSOUNDINSTANCES) + bestslot; + dnum++; + mutex_unlock(&m_callback); + + S_Cleanup(); + + return bestslot; +} + +static int S_GetSlot(int soundNum) +{ + int slot = 0; + + while (slot < MAXSOUNDINSTANCES && g_sounds[soundNum].voices[slot].id > 0) + slot++; + + return slot == MAXSOUNDINSTANCES ? S_TakeSlot(soundNum) : slot; +} + +static inline int S_GetAngle(int ang, const vec3_t *cam, const vec3_t *pos) +{ + return (2048 + ang - getangle(cam->x - pos->x, cam->y - pos->y)) & 2047; +} + +static int S_CalcDistAndAng(int spriteNum, int soundNum, int sectNum, int angle, + const vec3_t *cam, const vec3_t *pos, int *distPtr, int *angPtr) +{ + int sndang = 0, sndist = 0, explosion = 0; + + if (PN(spriteNum) == APLAYER && P_Get(spriteNum) == screenpeek) + goto sound_further_processing; + + sndang = S_GetAngle(angle, cam, pos); + sndist = FindDistance3D(cam->x-pos->x, cam->y-pos->y, (cam->z-pos->z)); + + if ((g_sounds[soundNum].m & (SF_GLOBAL|SF_DTAG)) != SF_GLOBAL && S_IsAmbientSFX(spriteNum) && (sector[SECT(spriteNum)].lotag&0xff) < 9) // ST_9_SLIDING_ST_DOOR + sndist = divscale14(sndist, SHT(spriteNum)+1); + +sound_further_processing: + sndist += g_sounds[soundNum].vo; + if (sndist < 0) + sndist = 0; + + if (!FURY && sectNum > -1 && sndist && PN(spriteNum) != MUSICANDSFX + && !cansee(cam->x, cam->y, cam->z - (24 << 8), sectNum, SX(spriteNum), SY(spriteNum), SZ(spriteNum) - (24 << 8), SECT(spriteNum))) + sndist += sndist>>5; + + if ((g_sounds[soundNum].m & (SF_GLOBAL|SF_DTAG)) == (SF_GLOBAL|SF_DTAG)) + { +boost: + int const sdist = g_sounds[soundNum].vo > 0 ? g_sounds[soundNum].vo : 6144; + + explosion = true; + + if (sndist > sdist) + sndist = sdist; + } + else if (!FURY) + { + switch (DYNAMICSOUNDMAP(soundNum)) + { + case PIPEBOMB_EXPLODE__STATIC: + case LASERTRIP_EXPLODE__STATIC: + case RPG_EXPLODE__STATIC: + goto boost; + } + } + + if ((g_sounds[soundNum].m & (SF_GLOBAL|SF_DTAG)) == SF_GLOBAL || sndist < ((255-LOUDESTVOLUME) << 6)) + sndist = ((255-LOUDESTVOLUME) << 6); + + *distPtr = sndist; + *angPtr = sndang; + + return explosion; +} + +int S_PlaySound3D(int num, int spriteNum, const vec3_t *pos) +{ + int32_t j = VM_OnEventWithReturn(EVENT_SOUND, spriteNum, screenpeek, num); + + if ((j == -1 && num != -1) || !SoundEnabled()) // check that the user returned -1, but only if -1 wasn't playing already (in which case, warn) + return -1; + + int const sndNum = j; + sound_t & snd = g_sounds[sndNum]; + + if (EDUKE32_PREDICT_FALSE((unsigned) sndNum > (unsigned) g_highestSoundIdx || snd.filename == NULL || snd.ptr == NULL)) + { + OSD_Printf("WARNING: invalid sound #%d\n", num); + return -1; + } + + auto const pPlayer = g_player[myconnectindex].ps; + + if (((snd.m & SF_ADULT) && adult_lockout) || (unsigned)spriteNum >= MAXSPRITES || (pPlayer->gm & MODE_MENU) || !FX_VoiceAvailable(snd.pr) + || (pPlayer->timebeforeexit > 0 && pPlayer->timebeforeexit <= GAMETICSPERSEC * 3)) + return -1; + + // Duke talk + if (snd.m & SF_TALK) + { + if ((g_netServer || ud.multimode > 1) && PN(spriteNum) == APLAYER && P_Get(spriteNum) != screenpeek) // other player sound + { + if ((snd_speech & 4) != 4) + return -1; + } + else if ((snd_speech & 1) != 1) + return -1; + + // don't play if any Duke talk sounds are already playing + for (j = 0; j <= g_highestSoundIdx; ++j) + if ((g_sounds[j].m & SF_TALK) && g_sounds[j].num > 0) + return -1; + } + else if ((snd.m & (SF_DTAG|SF_GLOBAL)) == SF_DTAG) // Duke-Tag sound + { + int const voice = S_PlaySound(sndNum); + + if (voice <= FX_Ok) + return -1; + + j = 0; + while (j < MAXSOUNDINSTANCES && snd.voices[j].id != voice) + j++; + + + snd.voices[j].owner = spriteNum; + + return voice; + } + + int32_t sndist, sndang; + int const explosionp = S_CalcDistAndAng(spriteNum, sndNum, CAMERA(sect), fix16_to_int(CAMERA(q16ang)), &CAMERA(pos), pos, &sndist, &sndang); + int pitch = S_GetPitch(sndNum); + auto const pOther = g_player[screenpeek].ps; + + + if (pOther->sound_pitch) + pitch += pOther->sound_pitch; + + if (explosionp) + { + if (pOther->cursectnum > -1 && sector[pOther->cursectnum].lotag == ST_2_UNDERWATER) + pitch -= 1024; + } + else + { + if (sndist > 32767 && PN(spriteNum) != MUSICANDSFX && (snd.m & (SF_LOOP|SF_MSFX)) == 0) + return -1; + + if (pOther->cursectnum > -1 && sector[pOther->cursectnum].lotag == ST_2_UNDERWATER + && (snd.m & SF_TALK) == 0) + pitch = -768; + } + + if (snd.num > 0 && PN(spriteNum) != MUSICANDSFX) + S_StopEnvSound(sndNum, spriteNum); + + int const sndSlot = S_GetSlot(sndNum); + + if (sndSlot >= MAXSOUNDINSTANCES) + { + return -1; + } + + int const repeatp = (snd.m & SF_LOOP); + + if (repeatp && (snd.m & SF_ONEINST_INTERNAL) && snd.num > 0) + { + return -1; + } + + int const voice = FX_Play3D(snd.ptr, snd.siz, repeatp ? FX_LOOP : FX_ONESHOT, pitch, sndang >> 4, sndist >> 6, + snd.pr, snd.volume, (sndNum * MAXSOUNDINSTANCES) + sndSlot); + + if (voice <= FX_Ok) + { + return -1; + } + + snd.num++; + + S_SetProperties(&snd.voices[sndSlot], spriteNum, voice, sndist >> 6, 0); + + return voice; +} + +int S_PlaySound(int num) +{ + int sndnum = VM_OnEventWithReturn(EVENT_SOUND, g_player[screenpeek].ps->i, screenpeek, num); + + if ((sndnum == -1 && num != -1) || !SoundEnabled()) // check that the user returned -1, but only if -1 wasn't playing already (in which case, warn) + return -1; + + num = sndnum; + + sound_t & snd = g_sounds[num]; + + if (EDUKE32_PREDICT_FALSE((unsigned)num > (unsigned)g_highestSoundIdx || snd.filename == NULL || snd.ptr == NULL)) + { + OSD_Printf("WARNING: invalid sound #%d\n",num); + return -1; + } + + if ((!(snd_speech & 1) && (snd.m & SF_TALK)) || ((snd.m & SF_ADULT) && adult_lockout) || !FX_VoiceAvailable(snd.pr)) + return -1; + + int const pitch = S_GetPitch(num); + + sndnum = S_GetSlot(num); + + if (sndnum >= MAXSOUNDINSTANCES) + { + return -1; + } + + int const voice = (snd.m & SF_LOOP) ? FX_Play(snd.ptr, snd.siz, 0, -1, pitch, LOUDESTVOLUME, LOUDESTVOLUME, + LOUDESTVOLUME, snd.siz, snd.volume, (num * MAXSOUNDINSTANCES) + sndnum) + : FX_Play3D(snd.ptr, snd.siz, FX_ONESHOT, pitch, 0, 255 - LOUDESTVOLUME, snd.pr, snd.volume, + (num * MAXSOUNDINSTANCES) + sndnum); + + if (voice <= FX_Ok) + { + return -1; + } + + snd.num++; + S_SetProperties(&snd.voices[sndnum], -1, voice, 255-LOUDESTVOLUME, 0); + + return voice; +} + +int A_PlaySound(int soundNum, int spriteNum) +{ + if (EDUKE32_PREDICT_FALSE((unsigned)soundNum > (unsigned)g_highestSoundIdx)) + return -1; + + return (unsigned)spriteNum >= MAXSPRITES ? S_PlaySound(soundNum) : + S_PlaySound3D(soundNum, spriteNum, &sprite[spriteNum].pos); +} + +void S_StopEnvSound(int sndNum, int sprNum) +{ + if (EDUKE32_PREDICT_FALSE((unsigned)sndNum > (unsigned)g_highestSoundIdx) || g_sounds[sndNum].num <= 0) + return; + + int j; + + do + { + for (j=0; j FX_Ok) || (sprNum != -1 && voice.owner == sprNum)) + { + if (voice.id > FX_Ok) + { + if (FX_SoundActive(voice.id)) + FX_StopSound(voice.id); + break; + } + } + } + } + while (j < MAXSOUNDINSTANCES); +} + +// Do not remove this or make it inline. +void S_StopAllSounds(void) +{ + FX_StopAllSounds(); +} + +void S_ChangeSoundPitch(int soundNum, int spriteNum, int pitchoffset) +{ + if ((unsigned)soundNum > (unsigned)g_highestSoundIdx || g_sounds[soundNum].num <= 0) + return; + + for (auto &voice : g_sounds[soundNum].voices) + { + if ((spriteNum == -1 && voice.id > FX_Ok) || (spriteNum != -1 && voice.owner == spriteNum)) + { + if (EDUKE32_PREDICT_FALSE(spriteNum >= 0 && voice.id <= FX_Ok)) + initprintf(OSD_ERROR "S_ChangeSoundPitch(): bad voice %d for sound ID %d!\n", voice.id, soundNum); + else if (voice.id > FX_Ok && FX_SoundActive(voice.id)) + FX_SetPitch(voice.id, pitchoffset); + break; + } + } +} + +void S_Update(void) +{ + if ((g_player[myconnectindex].ps->gm & (MODE_GAME|MODE_DEMO)) == 0) + return; + + g_numEnvSoundsPlaying = 0; + + const vec3_t *c; + int32_t ca,cs; + + if (ud.camerasprite == -1) + { + if (ud.overhead_on != 2) + { + c = &CAMERA(pos); + cs = CAMERA(sect); + ca = fix16_to_int(CAMERA(q16ang)); + } + else + { + auto pPlayer = g_player[screenpeek].ps; + c = &pPlayer->pos; + cs = pPlayer->cursectnum; + ca = fix16_to_int(pPlayer->q16ang); + } + } + else + { + c = &sprite[ud.camerasprite].pos; + cs = sprite[ud.camerasprite].sectnum; + ca = sprite[ud.camerasprite].ang; + } + + int sndnum = 0; + int const highest = g_highestSoundIdx; + + do + { + if (g_sounds[sndnum].num == 0) + continue; + + S_Cleanup(); + + for (auto &voice : g_sounds[sndnum].voices) + { + int const spriteNum = voice.owner; + + if ((unsigned)spriteNum >= MAXSPRITES || voice.id <= FX_Ok || !FX_SoundActive(voice.id)) + continue; + + int sndist, sndang; + + S_CalcDistAndAng(spriteNum, sndnum, cs, ca, c, (const vec3_t *)&sprite[spriteNum], &sndist, &sndang); + + if (S_IsAmbientSFX(spriteNum)) + g_numEnvSoundsPlaying++; + + // AMBIENT_SOUND + FX_Pan3D(voice.id, sndang >> 4, sndist >> 6); + voice.dist = sndist >> 6; + voice.clock++; + } + } while (++sndnum <= highest); +} + +// S_Callback() can be called from either the audio thread when a sound ends, or the main thread +// when playing back a new sound needs an existing sound to be stopped first +void S_Callback(intptr_t num) +{ + if ((int32_t)num == MUSIC_ID) + return; + + mutex_lock(&m_callback); + unative_t const ldnum = dnum; + dq[ldnum & (DQSIZE - 1)] = (uint32_t)num; + dnum++; + mutex_unlock(&m_callback); +} + +int A_CheckSoundPlaying(int spriteNum, int soundNum) +{ + if (EDUKE32_PREDICT_FALSE((unsigned)soundNum > (unsigned)g_highestSoundIdx)) return 0; + + if (g_sounds[soundNum].num > 0 && spriteNum >= 0) + { + for (auto &voice : g_sounds[soundNum].voices) + if (voice.owner == spriteNum) + return 1; + } + + return (spriteNum == -1) ? (g_sounds[soundNum].num != 0) : 0; +} + +// Check if actor is playing any sound. +int A_CheckAnySoundPlaying(int spriteNum) +{ + int const msp = g_highestSoundIdx; + + for (int j = 0; j <= msp; ++j) + { + for (auto &voice : g_sounds[j].voices) + if (voice.owner == spriteNum) + return 1; + } + + return 0; +} + +int S_CheckSoundPlaying(int soundNum) +{ + if (EDUKE32_PREDICT_FALSE((unsigned)soundNum > (unsigned)g_highestSoundIdx)) return false; + return (g_sounds[soundNum].num != 0); +} +END_DUKE_NS