diff --git a/src/engine/audio/Audio.cpp b/src/engine/audio/Audio.cpp index c4ae0e2bf1..ef18ca622a 100644 --- a/src/engine/audio/Audio.cpp +++ b/src/engine/audio/Audio.cpp @@ -58,16 +58,70 @@ namespace Audio { // in a frame, it means it sould be destroyed. struct entityLoop_t { bool addedThisFrame; + bool persistent; std::shared_ptr sound; sfxHandle_t newSfx; sfxHandle_t oldSfx; }; - static entityLoop_t entityLoops[MAX_GENTITIES]; + + struct EntityMultiLoop { + entityLoop_t loops[MAX_ENTITY_SOUNDS]; + + entityLoop_t& FindSlot( const sfxHandle_t sfx ) { + uint32_t bestSlot = 0; + float minGain = FLT_MAX; + + for ( entityLoop_t& loop : loops ) { + if ( sfx == loop.oldSfx ) { + return loop; + } + + if ( !loop.sound ) { + return loop; + } + + if ( loop.sound->currentGain < minGain ) { + bestSlot = &loop - loops; + minGain = loop.sound->currentGain; + } + } + + return loops[bestSlot]; + } + + void StopAll() { + for ( entityLoop_t& loop : loops ) { + if ( loop.sound ) { + loop.sound->Stop(); + } + + loop = { false, false, nullptr, -1, -1 }; + } + } + + void ResetAll() { + for ( entityLoop_t& loop : loops ) { + loop = { false, false, nullptr, -1, -1 }; + } + } + + void ClearLoopingSounds() { + for ( entityLoop_t& loop : loops ) { + if ( loop.sound ) { + loop.addedThisFrame = false; + } + } + } + }; + + static EntityMultiLoop entityLoops[MAX_GENTITIES]; static std::shared_ptr streams[N_STREAMS]; static bool initialized = false; + int playerClientNum; + static AL::Device* device; static AL::Context* context; @@ -147,8 +201,8 @@ namespace Audio { UpdateListenerGain(); - for (auto &loop : entityLoops) { - loop = {false, nullptr, -1, -1}; + for ( EntityMultiLoop& loop : entityLoops ) { + loop.ResetAll(); } return true; @@ -160,11 +214,8 @@ namespace Audio { } // Shuts down the wrapper - for (auto &loop : entityLoops) { - if (loop.sound) { - loop.sound->Stop(); - } - loop = {false, nullptr, -1, -1}; + for ( EntityMultiLoop& loop : entityLoops ) { + loop.StopAll(); } StopMusic(); @@ -193,22 +244,29 @@ namespace Audio { return; } - for (int i = 0; i < MAX_GENTITIES; i++) { - auto& loop = entityLoops[i]; - if (loop.sound and not loop.addedThisFrame) { - // The loop wasn't added this frame, that means it has to be removed. - loop.sound->FadeOutAndDie(); - loop = {false, nullptr, -1, -1}; - - } else if (loop.oldSfx != loop.newSfx) { - // The last sfx added in the frame is not the current one being played - // To mimic the previous sound system's behavior we sart playing the new one. - loop.sound->FadeOutAndDie(); - - int newSfx = loop.newSfx; - loop = {false, nullptr, -1, -1}; - - AddEntityLoopingSound(i, newSfx); + for ( uint32_t i = 0; i < MAX_GENTITIES; i++ ) { + EntityMultiLoop& multiLoop = entityLoops[i]; + + for ( entityLoop_t& loop : multiLoop.loops ) { + if ( loop.sound and not loop.addedThisFrame ) { + if ( loop.persistent ) { + loop.sound->soundGain = 0; + } else { + // The loop wasn't added this frame, that means it has to be removed. + loop.sound->FadeOutAndDie(); + loop = { false, false, nullptr, -1, -1 }; + } + } else if ( loop.oldSfx != loop.newSfx ) { + // The last sfx added in the frame is not the current one being played + // To mimic the previous sound system's behavior we sart playing the new one. + loop.sound->FadeOutAndDie(); + + int newSfx = loop.newSfx; + bool persistent = loop.persistent; + loop = { false, false, nullptr, -1, -1 }; + + AddEntityLoopingSound( i, newSfx, persistent ); + } } } @@ -219,26 +277,30 @@ namespace Audio { UpdateEmitters(); UpdateSounds(); - for (auto &loop : entityLoops) { - loop.addedThisFrame = false; - // if we are the unique owner of a loop pointer, then it means it was stopped, free it. - if (loop.sound.use_count() == 1) { - loop = {false, nullptr, -1, -1}; + for ( EntityMultiLoop& multiLoop : entityLoops ) { + for ( entityLoop_t& loop : multiLoop.loops ) { + loop.addedThisFrame = false; + // if we are the unique owner of a loop pointer, then it means it was stopped, free it. + if ( loop.sound.use_count() == 1 ) { + loop = { false, false, nullptr, -1, -1 }; + } } } - for (auto &stream : streams) { + for ( std::shared_ptr &stream : streams ) { if (stream and stream.use_count() == 1) { stream = nullptr; } } } - void BeginRegistration() { + void BeginRegistration( const int playerNum ) { if (not initialized) { return; } + playerClientNum = playerNum; + BeginSampleRegistration(); } @@ -259,6 +321,10 @@ namespace Audio { EndSampleRegistration(); } + static int GetSoundPriorityForEntity( const int entityNum ) { + return entityNum < MAX_CLIENTS ? CLIENT : ANY; + } + void StartSound(int entityNum, Vec3 origin, sfxHandle_t sfx) { if (not initialized or not Sample::IsValidHandle(sfx)) { return; @@ -277,7 +343,7 @@ namespace Audio { return; } - AddSound(emitter, std::make_shared(Sample::FromHandle(sfx)), 1); + AddSound( emitter, std::make_shared( Sample::FromHandle( sfx ) ), GetSoundPriorityForEntity( entityNum ) ); } void StartLocalSound(sfxHandle_t sfx) { @@ -285,23 +351,25 @@ namespace Audio { return; } - AddSound(GetLocalEmitter(), std::make_shared(Sample::FromHandle(sfx)), 1); + AddSound( GetLocalEmitter(), std::make_shared( Sample::FromHandle( sfx ) ), ANY ); } - void AddEntityLoopingSound(int entityNum, sfxHandle_t sfx) { + void AddEntityLoopingSound(int entityNum, sfxHandle_t sfx, bool persistent) { if (not initialized or not Sample::IsValidHandle(sfx) or not IsValidEntity(entityNum)) { return; } - entityLoop_t& loop = entityLoops[entityNum]; + entityLoop_t& loop = entityLoops[entityNum].FindSlot( sfx ); // If we have no sound we can play the loop directly if (not loop.sound) { loop.sound = std::make_shared(Sample::FromHandle(sfx)); loop.oldSfx = sfx; - AddSound(GetEmitterForEntity(entityNum), loop.sound, 1); + AddSound( GetEmitterForEntity( entityNum ), loop.sound, GetSoundPriorityForEntity( entityNum ) ); } + loop.addedThisFrame = true; + loop.persistent = persistent; // We remember what is the last sfx asked because cgame expects the sfx added last in the frame to be played loop.newSfx = sfx; @@ -322,9 +390,7 @@ namespace Audio { return; } - if (entityLoops[entityNum].sound) { - entityLoops[entityNum].addedThisFrame = false; - } + entityLoops[entityNum].ClearLoopingSounds(); } void StartMusic(Str::StringRef leadingSound, Str::StringRef loopSound) { @@ -343,8 +409,8 @@ namespace Audio { StopMusic(); music = std::make_shared(loopingSample, leadingSample); - music->SetVolumeModifier(musicVolume); - AddSound(GetLocalEmitter(), music, 1); + music->volumeModifier = &musicVolume; + AddSound( GetLocalEmitter(), music, ANY ); } void StopMusic() { @@ -375,13 +441,13 @@ namespace Audio { if (not streams[streamNum]) { streams[streamNum] = std::make_shared(); if (IsValidEntity(entityNum)) { - AddSound(GetEmitterForEntity(entityNum), streams[streamNum], 1); + AddSound( GetEmitterForEntity( entityNum ), streams[streamNum], GetSoundPriorityForEntity( entityNum ) ); } else { - AddSound(GetLocalEmitter(), streams[streamNum], 1); + AddSound( GetLocalEmitter(), streams[streamNum], ANY ); } } - streams[streamNum]->SetGain(volume); + streams[streamNum]->soundGain = volume; AudioData audioData(rate, width, channels, (width * numSamples * channels), reinterpret_cast(data)); diff --git a/src/engine/audio/Audio.h b/src/engine/audio/Audio.h index cbd30c989a..cce810af9b 100644 --- a/src/engine/audio/Audio.h +++ b/src/engine/audio/Audio.h @@ -43,14 +43,14 @@ namespace Audio { void Shutdown(); void Update(); - void BeginRegistration(); + void BeginRegistration( const int playerNum ); sfxHandle_t RegisterSFX(Str::StringRef filename); void EndRegistration(); void StartSound(int entityNum, Vec3 origin, sfxHandle_t sfx); void StartLocalSound(int entityNum); - void AddEntityLoopingSound(int entityNum, sfxHandle_t sfx); + void AddEntityLoopingSound(int entityNum, sfxHandle_t sfx, bool persistent); void ClearAllLoopingSounds(); void ClearLoopingSoundsForEntity(int entityNum); diff --git a/src/engine/audio/AudioPrivate.h b/src/engine/audio/AudioPrivate.h index 0bbde3377b..6ccc1665a4 100644 --- a/src/engine/audio/AudioPrivate.h +++ b/src/engine/audio/AudioPrivate.h @@ -63,6 +63,23 @@ namespace Audio { // There is only a small number of reverb slots because by default we can create only 4 AuxEffects CONSTEXPR int N_REVERB_SLOTS = 3; + constexpr uint32_t MAX_ENTITY_SOUNDS = 4; + + extern int playerClientNum; + + struct entityData_t { + Vec3 position; + Vec3 velocity; + float occlusion; + }; + + extern entityData_t entities[MAX_GENTITIES]; + + enum EmitterPriority { + ANY, + CLIENT + }; + // Tweaks the value given by the audio slider float SliderToAmplitude(float slider); diff --git a/src/engine/audio/Emitter.cpp b/src/engine/audio/Emitter.cpp index e388545f87..6c3302d2ff 100644 --- a/src/engine/audio/Emitter.cpp +++ b/src/engine/audio/Emitter.cpp @@ -31,18 +31,33 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AudioPrivate.h" namespace Audio { + entityData_t entities[MAX_GENTITIES]; + static int listenerEntity = -1; + + struct EntityMultiEmitter { + std::shared_ptr emitters[MAX_ENTITY_SOUNDS]; + + std::shared_ptr& FindSlot( const int entityNum ) { + for ( std::shared_ptr& emitter : emitters ) { + if ( !emitter ) { + emitter = std::make_shared( entityNum ); + return emitter; + } + } + + return emitters[0]; + } - // Structures to keep the state of entities we were given - struct entityData_t { - Vec3 position; - Vec3 velocity; - float occlusion; + void Shutdown() { + for ( std::shared_ptr& emitter : emitters ) { + if ( emitter ) { + emitter = nullptr; + } + } + } }; - static entityData_t entities[MAX_GENTITIES]; - static int listenerEntity = -1; - // Keep entity Emitters in an array because there is at most one per entity. - static std::shared_ptr entityEmitters[MAX_GENTITIES]; + static EntityMultiEmitter entityEmitters[MAX_GENTITIES]; // Position Emitters can be reused so we keep the list of all of them // this is not very efficient but we cannot have more position emitters @@ -100,15 +115,13 @@ namespace Audio { localEmitter = nullptr; - for (auto &slot : reverbSlots) { + for ( ReverbSlot& slot : reverbSlots ) { delete slot.effect; slot.effect = nullptr; } - for (auto &emitter : entityEmitters) { - if ( emitter ) { - emitter = nullptr; - } + for ( EntityMultiEmitter& emitter : entityEmitters ) { + emitter.Shutdown(); } posEmitters.clear(); @@ -124,16 +137,18 @@ namespace Audio { // Both PositionEmitters and EntityEmitters are ref-counted. // If we hold the only reference to them then no sound is still using // the Emitter that can be destroyed. - for (auto &emitter : entityEmitters) { - if (not emitter) { - continue; - } + for ( EntityMultiEmitter& multiEmitter : entityEmitters ) { + for ( std::shared_ptr& emitter : multiEmitter.emitters ) { + if ( not emitter ) { + continue; + } - emitter->Update(); + emitter->Update(); - // No sound is using this emitter, destroy it - if (emitter.use_count() == 1) { - emitter = nullptr; + // No sound is using this emitter, destroy it + if ( emitter.use_count() == 1 ) { + emitter = nullptr; + } } } @@ -165,11 +180,7 @@ namespace Audio { } std::shared_ptr GetEmitterForEntity(int entityNum) { - if (not entityEmitters[entityNum]) { - entityEmitters[entityNum] = std::make_shared(entityNum); - } - - return entityEmitters[entityNum]; + return entityEmitters[entityNum].FindSlot( entityNum ); } std::shared_ptr GetEmitterForPosition(Vec3 position) { @@ -261,7 +272,7 @@ namespace Audio { Emitter::~Emitter() = default; void Emitter::SetupSound(Sound& sound) { - sound.GetSource().SetReferenceDistance(120.0f); + sound.source->SetReferenceDistance(120.0f); InternalSetupSound(sound); UpdateSound(sound); } @@ -278,7 +289,7 @@ namespace Audio { } void EntityEmitter::UpdateSound(Sound& sound) { - AL::Source& source = sound.GetSource(); + AL::Source& source = *sound.source; if (entityNum == listenerEntity) { MakeLocal(source); @@ -288,11 +299,15 @@ namespace Audio { } void EntityEmitter::InternalSetupSound(Sound& sound) { - AL::Source& source = sound.GetSource(); + AL::Source& source = *sound.source; Make3D(source, entities[entityNum].position, entities[entityNum].velocity); } + Vec3 EntityEmitter::GetPosition() const { + return entities[entityNum].position; + } + // Implementation of PositionEmitter PositionEmitter::PositionEmitter(Vec3 position){ @@ -306,13 +321,13 @@ namespace Audio { } void PositionEmitter::UpdateSound(Sound& sound) { - AL::Source& source = sound.GetSource(); + AL::Source& source = *sound.source; Make3D(source, position, origin); } void PositionEmitter::InternalSetupSound(Sound& sound) { - AL::Source& source = sound.GetSource(); + AL::Source& source = *sound.source; Make3D(source, position, origin); } @@ -334,11 +349,15 @@ namespace Audio { } void LocalEmitter::InternalSetupSound(Sound& sound) { - AL::Source& source = sound.GetSource(); + AL::Source& source = *sound.source; MakeLocal(source); } + Vec3 LocalEmitter::GetPosition() const { + return Vec3 {}; + } + class TestReverbCmd : public Cmd::StaticCmd { public: TestReverbCmd(): StaticCmd("testReverb", Cmd::AUDIO, "Tests a reverb preset.") { diff --git a/src/engine/audio/Emitter.h b/src/engine/audio/Emitter.h index ab0a9d6c8e..a4ee1b37cb 100644 --- a/src/engine/audio/Emitter.h +++ b/src/engine/audio/Emitter.h @@ -64,11 +64,13 @@ namespace Audio { void SetupSound(Sound& sound); // Called each frame before any UpdateSound is called, used to factor computations - void virtual Update() = 0; + virtual void Update() = 0; // Update the Sound's source's spatialization virtual void UpdateSound(Sound& sound) = 0; // Setup a source for the spatialization of this Emitter virtual void InternalSetupSound(Sound& sound) = 0; + + virtual Vec3 GetPosition() const = 0; }; // An Emitter that will follow an entity @@ -81,6 +83,8 @@ namespace Audio { virtual void UpdateSound(Sound& sound) override; virtual void InternalSetupSound(Sound& sound) override; + Vec3 GetPosition() const override; + private: int entityNum; }; @@ -95,7 +99,7 @@ namespace Audio { virtual void UpdateSound(Sound& sound) override; virtual void InternalSetupSound(Sound& sound) override; - Vec3 GetPosition() const; + Vec3 GetPosition() const override; private: Vec3 position; @@ -110,6 +114,8 @@ namespace Audio { void virtual Update() override; virtual void UpdateSound(Sound& sound) override; virtual void InternalSetupSound(Sound& sound) override; + + Vec3 GetPosition() const override; }; } diff --git a/src/engine/audio/Sound.cpp b/src/engine/audio/Sound.cpp index a474d7479f..1451e3039f 100644 --- a/src/engine/audio/Sound.cpp +++ b/src/engine/audio/Sound.cpp @@ -49,8 +49,6 @@ namespace Audio { static sourceRecord_t* sources = nullptr; static CONSTEXPR int nSources = 128; //TODO see what's the limit for OpenAL soft - sourceRecord_t* GetSource(int priority); - static bool initialized = false; void InitSounds() { @@ -85,18 +83,18 @@ namespace Audio { for (int i = 0; i < nSources; i++) { if (sources[i].active) { - auto sound = sources[i].usingSound; + std::shared_ptr sound = sources[i].usingSound; // Update and Emitter::UpdateSound can call Sound::Stop - if (not sound->IsStopped()) { + if ( sound->playing ) { sound->Update(); } - if (not sound->IsStopped()) { - sound->GetEmitter()->UpdateSound(*sound); + if ( sound->playing ) { + sound->emitter->UpdateSound(*sound); } - if (sound->IsStopped()) { + if ( !sound->playing ) { sources[i].active = false; sources[i].usingSound = nullptr; } @@ -116,34 +114,40 @@ namespace Audio { } } - void AddSound(std::shared_ptr emitter, std::shared_ptr sound, int priority) { - if (not initialized) { - return; - } + static Cvar::Range> a_clientSoundPriorityMaxDistance( "a_clientSoundPriorityMaxDistance", + "Sounds emitted by players/bots within this distance (in qu) will have higher priority than other sounds" + " (multiplier: a_clientSoundPriorityMultiplier)", Cvar::NONE, 32 * 32, 0, BIT( 16 ) ); - sourceRecord_t* source = GetSource(priority); + static Cvar::Range> a_clientSoundPriorityMultiplier( "a_clientSoundPriorityMultiplier", + "Sounds emitted by players/bots within a_clientSoundPriorityMaxDistance" + " will use this value as their priority multiplier", + Cvar::NONE, 2.0f, 0.0f, 1024.0f ); - if (source) { - // Make the source forget if it was a "static" or a "streaming" source. - source->source.ResetBuffer(); - sound->SetEmitter(emitter); - sound->AcquireSource(source->source); - source->usingSound = sound; - source->priority = priority; - source->active = true; + static float GetAdjustedVolumeForPosition( const Vec3& origin, const Vec3& src, const bool isClient ) { + vec3_t v0 { origin.Data()[0], origin.Data()[1], origin.Data()[2] }; + vec3_t v1 { src.Data()[0], src.Data()[1], src.Data()[2] }; - sound->FinishSetup(); - sound->Play(); + float totalPriority = VectorDistanceSquared( v0, v1 ); + + const float distanceThreshold = a_clientSoundPriorityMaxDistance.Get(); + if ( isClient && totalPriority < distanceThreshold * distanceThreshold ) { + totalPriority *= 1.0f / a_clientSoundPriorityMultiplier.Get(); } + + return 1.0f / totalPriority; } // Finds a inactive or low-priority source to play a new sound. - sourceRecord_t* GetSource(int priority) { - //TODO make a better heuristic? (take into account the distance / the volume /... ?) + static sourceRecord_t* GetSource( const Vec3& position, int priority, const float currentGain ) { int best = -1; - int bestPriority = priority; - // Gets the minimum sound by comparing activity first then priority + const Vec3& playerPos = entities[playerClientNum].position; + + // Sound volume is inversely proportional to distance to the source + const float adjustedVolume = Q_rsqrt_fast( currentGain ) + * GetAdjustedVolumeForPosition( playerPos, position, priority == CLIENT ); + + // Gets the minimum sound by comparing activity first, then the adjusted volume for (int i = 0; i < nSources; i++) { sourceRecord_t& source = sources[i]; @@ -151,9 +155,14 @@ namespace Audio { return &source; } - if (source.priority < bestPriority || (best < 0 && source.priority <= priority)) { + const Vec3& sourcePos = source.usingSound->emitter->GetPosition(); + + // Sound volume is inversely proportional to distance to the source + const float adjustedSourceVolume = Q_rsqrt_fast( source.usingSound->currentGain ) + * GetAdjustedVolumeForPosition( playerPos, sourcePos, source.priority == CLIENT ); + + if ( adjustedVolume > adjustedSourceVolume ) { best = i; - bestPriority = source.priority; continue; } } @@ -171,6 +180,30 @@ namespace Audio { } } + void AddSound( std::shared_ptr emitter, std::shared_ptr sound, int priority ) { + if ( not initialized ) { + return; + } + + const Vec3& position = emitter->GetPosition(); + const float currentGain = sound->positionalGain * sound->soundGain + * SliderToAmplitude( sound->volumeModifier->Get() ); + sourceRecord_t* source = GetSource( position, priority, currentGain ); + + if ( source ) { + // Make the source forget if it was a "static" or a "streaming" source. + source->source.ResetBuffer(); + sound->emitter = emitter; + sound->AcquireSource( source->source ); + source->usingSound = sound; + source->priority = priority; + source->active = true; + + sound->FinishSetup(); + sound->Play(); + } + } + // Implementation of Sound Sound::Sound() : positionalGain(1.0f), soundGain(1.0f), currentGain(1.0f), @@ -188,40 +221,6 @@ namespace Audio { playing = false; } - bool Sound::IsStopped() { - return not playing; - } - - void Sound::SetPositionalGain(float gain) { - positionalGain = gain; - } - - void Sound::SetSoundGain(float gain) { - soundGain = gain; - } - - float Sound::GetCurrentGain() { - return currentGain; - } - - void Sound::SetVolumeModifier(const Cvar::Range>& volumeMod) - { - this->volumeModifier = &volumeMod; - } - - float Sound::GetVolumeModifier() const - { - return volumeModifier->Get(); - } - - void Sound::SetEmitter(std::shared_ptr emitter) { - this->emitter = emitter; - } - - std::shared_ptr Sound::GetEmitter() { - return emitter; - } - void Sound::AcquireSource(AL::Source& source) { this->source = &source; @@ -231,19 +230,15 @@ namespace Audio { emitter->SetupSound(*this); } - AL::Source& Sound::GetSource() { - return *source; - } - // Set the gain before the source is started to avoid having a few milliseconds of very loud sound void Sound::FinishSetup() { - currentGain = positionalGain * soundGain * SliderToAmplitude(GetVolumeModifier()); + currentGain = positionalGain * soundGain * SliderToAmplitude(volumeModifier->Get()); source->SetGain(currentGain); } void Sound::Update() { // Fade the Gain update to avoid "ticking" sounds when there is a gain discontinuity - float targetGain = positionalGain * soundGain * SliderToAmplitude(GetVolumeModifier()); + float targetGain = positionalGain * soundGain * SliderToAmplitude(volumeModifier->Get()); //TODO make it framerate independent and fade out in about 1/8 seconds ? if (currentGain > targetGain) { @@ -267,15 +262,15 @@ namespace Audio { void OneShotSound::SetupSource(AL::Source& source) { source.SetBuffer(sample->GetBuffer()); - SetSoundGain(GetVolumeModifier()); + soundGain = volumeModifier->Get(); } void OneShotSound::InternalUpdate() { - if (GetSource().IsStopped()) { + if ( source->IsStopped() ) { Stop(); return; } - SetSoundGain(GetVolumeModifier()); + soundGain = volumeModifier->Get(); } // Implementation of LoopingSound @@ -289,7 +284,7 @@ namespace Audio { void LoopingSound::FadeOutAndDie() { fadingOut = true; - SetSoundGain(0.0f); + soundGain = 0.0f; } void LoopingSound::SetupSource(AL::Source& source) { @@ -298,23 +293,23 @@ namespace Audio { } else { SetupLoopingSound(source); } - SetSoundGain(GetVolumeModifier()); + soundGain = volumeModifier->Get(); } void LoopingSound::InternalUpdate() { - if (fadingOut and GetCurrentGain() == 0.0f) { + if (fadingOut and currentGain == 0.0f) { Stop(); } if (not fadingOut) { if (leadingSample) { - if (GetSource().IsStopped()) { - SetupLoopingSound(GetSource()); - GetSource().Play(); + if ( source->IsStopped() ) { + SetupLoopingSound( *source ); + source->Play(); leadingSample = nullptr; } } - SetSoundGain(GetVolumeModifier()); + soundGain = volumeModifier->Get(); } } @@ -335,32 +330,25 @@ namespace Audio { } void StreamingSound::InternalUpdate() { - AL::Source& source = GetSource(); - - while (source.GetNumProcessedBuffers() > 0) { - source.PopBuffer(); + while ( source->GetNumProcessedBuffers() > 0 ) { + source->PopBuffer(); } - if (source.GetNumQueuedBuffers() == 0) { + if ( source->GetNumQueuedBuffers() == 0 ) { Stop(); } } //TODO somehow try to catch back when data is coming faster than we consume (e.g. capture data) void StreamingSound::AppendBuffer(AL::Buffer buffer) { - if (IsStopped()) { + if ( !playing ) { return; } - AL::Source& source = GetSource(); - source.QueueBuffer(std::move(buffer)); + source->QueueBuffer(std::move(buffer)); - if (source.IsStopped()) { - source.Play(); + if ( source->IsStopped() ) { + source->Play(); } } - - void StreamingSound::SetGain(float gain) { - SetSoundGain(gain); - } } diff --git a/src/engine/audio/Sound.h b/src/engine/audio/Sound.h index fa2f3a1415..1bcc28ffef 100644 --- a/src/engine/audio/Sound.h +++ b/src/engine/audio/Sound.h @@ -54,29 +54,24 @@ namespace Audio { //TODO sound.mute class Sound { public: + float positionalGain; + float soundGain; + float currentGain; + + bool playing; + const Cvar::Range>* volumeModifier; + + AL::Source* source; + std::shared_ptr emitter; + Sound(); virtual ~Sound(); void Play(); // Stop the source and marks the sound for deletion. void Stop(); - bool IsStopped(); - - // The is attenuated because of its inherent porperties and because of its position. - // Each attenuation can be set separately. - void SetPositionalGain(float gain); - void SetSoundGain(float gain); - float GetCurrentGain(); - - // sfx vs. music - void SetVolumeModifier(const Cvar::Range>& volumeMod); - float GetVolumeModifier() const; - - void SetEmitter(std::shared_ptr emitter); - std::shared_ptr GetEmitter(); void AcquireSource(AL::Source& source); - AL::Source& GetSource(); // Used to setup a source for a specific kind of sound and to start the sound. virtual void SetupSource(AL::Source& source) = 0; @@ -85,16 +80,6 @@ namespace Audio { void Update(); // Called each frame, after emitters have been updated. virtual void InternalUpdate() = 0; - - private: - float positionalGain; - float soundGain; - float currentGain; - - bool playing; - std::shared_ptr emitter; - const Cvar::Range>* volumeModifier; - AL::Source* source; }; // A sound that is played once. @@ -139,7 +124,6 @@ namespace Audio { virtual void InternalUpdate() override; void AppendBuffer(AL::Buffer buffer); - void SetGain(float gain); }; } diff --git a/src/engine/client/cg_msgdef.h b/src/engine/client/cg_msgdef.h index 9918f5b3d3..760f2a4621 100644 --- a/src/engine/client/cg_msgdef.h +++ b/src/engine/client/cg_msgdef.h @@ -282,7 +282,7 @@ namespace Audio { using StartSoundMsg = IPC::Message, bool, Vec3, int, int>; using StartLocalSoundMsg = IPC::Message, int>; using ClearLoopingSoundsMsg = IPC::Message>; - using AddLoopingSoundMsg = IPC::Message, int, int>; + using AddLoopingSoundMsg = IPC::Message, int, int, bool>; using StopLoopingSoundMsg = IPC::Message, int>; using UpdateEntityPositionMsg = IPC::Message, int, Vec3>; using RespatializeMsg = IPC::Message, int, std::array>; @@ -291,7 +291,7 @@ namespace Audio { using UpdateEntityVelocityMsg = IPC::Message, int, Vec3>; using UpdateEntityPositionVelocityMsg = IPC::Message, int, Vec3, Vec3>; using SetReverbMsg = IPC::Message, int, std::string, float>; - using BeginRegistrationMsg = IPC::Message>; + using BeginRegistrationMsg = IPC::Message, int>; using EndRegistrationMsg = IPC::Message>; } diff --git a/src/engine/client/cl_cgame.cpp b/src/engine/client/cl_cgame.cpp index b1e4791324..86acfd49cb 100644 --- a/src/engine/client/cl_cgame.cpp +++ b/src/engine/client/cl_cgame.cpp @@ -1506,8 +1506,8 @@ void CGameVM::CmdBuffer::HandleCommandBufferSyscall(int major, int minor, Util:: break; case CG_S_ADDLOOPINGSOUND: - HandleMsg(std::move(reader), [this] (int entityNum, int sfx) { - Audio::AddEntityLoopingSound(entityNum, sfx); + HandleMsg(std::move(reader), [this] (int entityNum, int sfx, bool persistent) { + Audio::AddEntityLoopingSound(entityNum, sfx, persistent); }); break; @@ -1561,8 +1561,8 @@ void CGameVM::CmdBuffer::HandleCommandBufferSyscall(int major, int minor, Util:: break; case CG_S_BEGINREGISTRATION: - HandleMsg(std::move(reader), [this] { - Audio::BeginRegistration(); + HandleMsg(std::move(reader), [this] ( const int playerNum ) { + Audio::BeginRegistration( playerNum ); }); break; diff --git a/src/engine/null/NullAudio.cpp b/src/engine/null/NullAudio.cpp index 3b96d95e52..6e4a5e77a5 100644 --- a/src/engine/null/NullAudio.cpp +++ b/src/engine/null/NullAudio.cpp @@ -47,7 +47,7 @@ namespace Audio { } - void BeginRegistration() { + void BeginRegistration( const int ) { } sfxHandle_t RegisterSFX(Str::StringRef) { @@ -65,7 +65,7 @@ namespace Audio { } - void AddEntityLoopingSound(int, sfxHandle_t) { + void AddEntityLoopingSound(int, sfxHandle_t, bool) { } void ClearAllLoopingSounds() { diff --git a/src/engine/server/sg_api.h b/src/engine/server/sg_api.h index 7ba16db755..db1d211ee8 100644 --- a/src/engine/server/sg_api.h +++ b/src/engine/server/sg_api.h @@ -44,6 +44,7 @@ along with this program. If not, see . #define SVF_SELF_PORTAL_EXCLUSIVE 0x00010000 #define SVF_RIGID_BODY 0x00020000 // ignored by the engine #define SVF_CLIENTS_IN_RANGE 0x00040000 // clients within range +#define SVF_BROADCAST_ONCE 0x00040000 // broadcasted to newly connecting clients, and once to connected clients when spawned #define MAX_ENT_CLUSTERS 16 diff --git a/src/engine/server/sv_snapshot.cpp b/src/engine/server/sv_snapshot.cpp index 4bb29b85d5..f9a29fccf8 100644 --- a/src/engine/server/sv_snapshot.cpp +++ b/src/engine/server/sv_snapshot.cpp @@ -341,7 +341,7 @@ static void SV_AddEntToSnapshot( svEntity_t *svEnt, sharedEntity_t *gEnt, SV_AddEntitiesVisibleFromPoint =============== */ -static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame, +static void SV_AddEntitiesVisibleFromPoint( client_t* client, vec3_t origin, clientSnapshot_t *frame, // snapshotEntityNumbers_t *eNums, bool portal, clientSnapshot_t *oldframe, bool localClient ) { // snapshotEntityNumbers_t *eNums, bool portal ) { snapshotEntityNumbers_t *eNums /*, bool portal, bool localClient */ ) @@ -379,7 +379,7 @@ static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *fra if ( playerEnt->r.svFlags & SVF_SELF_PORTAL ) { - SV_AddEntitiesVisibleFromPoint( playerEnt->s.origin2, frame, eNums ); + SV_AddEntitiesVisibleFromPoint( client, playerEnt->s.origin2, frame, eNums ); } for ( e = 0; e < sv.num_entities; e++ ) @@ -462,6 +462,11 @@ static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *fra continue; } + if ( ( ent->r.svFlags & SVF_BROADCAST_ONCE ) && !client->reliableAcknowledge ) { + SV_AddEntToSnapshot( svEnt, ent, eNums ); + continue; + } + // send entity if the client is in range if ( (ent->r.svFlags & SVF_CLIENTS_IN_RANGE) && Distance( ent->s.origin, playerEnt->s.origin ) <= ent->r.clientRadius ) @@ -637,7 +642,7 @@ static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *fra } // SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, true, oldframe, localClient ); - SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums /*, true, localClient */ ); + SV_AddEntitiesVisibleFromPoint( client, ent->s.origin2, frame, eNums /*, true, localClient */ ); } continue; @@ -720,7 +725,7 @@ static void SV_BuildClientSnapshot( client_t *client ) // add all the entities directly visible to the eye, which // may include portal entities that merge other viewpoints - SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers /*, false, client->netchan.remoteAddress.type == NA_LOOPBACK */ ); + SV_AddEntitiesVisibleFromPoint( client, org, frame, &entityNumbers /*, false, client->netchan.remoteAddress.type == NA_LOOPBACK */ ); // if there were portals visible, there may be out of order entities // in the list which will need to be resorted for the delta compression diff --git a/src/shared/client/cg_api.cpp b/src/shared/client/cg_api.cpp index 9eea1f66be..dc9bb6cce8 100644 --- a/src/shared/client/cg_api.cpp +++ b/src/shared/client/cg_api.cpp @@ -153,7 +153,7 @@ void trap_S_ClearLoopingSounds( bool ) cmdBuffer.SendMsg(); } -void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx, bool persistent ) { if (origin) { trap_S_UpdateEntityPosition(entityNum, origin); @@ -161,12 +161,7 @@ void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t ve if (velocity) { trap_S_UpdateEntityVelocity(entityNum, velocity); } - cmdBuffer.SendMsg(entityNum, sfx); -} - -void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) -{ - trap_S_AddLoopingSound(entityNum, origin, velocity, sfx); + cmdBuffer.SendMsg( entityNum, sfx, persistent ); } void trap_S_StopLoopingSound( int entityNum ) @@ -223,9 +218,9 @@ void trap_S_SetReverb( int slotNum, const char* name, float ratio ) cmdBuffer.SendMsg(slotNum, name, ratio); } -void trap_S_BeginRegistration() +void trap_S_BeginRegistration( const int playerNum ) { - cmdBuffer.SendMsg(); + cmdBuffer.SendMsg( playerNum ); } void trap_S_EndRegistration() diff --git a/src/shared/client/cg_api.h b/src/shared/client/cg_api.h index 4e2781c3d7..b748cf11d6 100644 --- a/src/shared/client/cg_api.h +++ b/src/shared/client/cg_api.h @@ -53,8 +53,8 @@ void trap_CM_BatchMarkFragments( void trap_S_StartSound( vec3_t origin, int entityNum, soundChannel_t entchannel, sfxHandle_t sfx ); void trap_S_StartLocalSound( sfxHandle_t sfx, soundChannel_t channelNum ); void trap_S_ClearLoopingSounds( bool killall ); -void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); -void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx, + bool persistent = false ); void trap_S_StopLoopingSound( int entityNum ); void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[ 3 ], int inwater ); @@ -138,7 +138,7 @@ void trap_R_SetAltShaderTokens( const char * ); void trap_S_UpdateEntityVelocity( int entityNum, const vec3_t velocity ); void trap_S_UpdateEntityPositionVelocity( int entityNum, const vec3_t position, const vec3_t velocity ); void trap_S_SetReverb( int slotNum, const char* presetName, float ratio ); -void trap_S_BeginRegistration(); +void trap_S_BeginRegistration( const int playerNum ); void trap_S_EndRegistration(); #endif