diff --git a/source/main/Application.cpp b/source/main/Application.cpp index 0f08ca7004..f2426ee684 100644 --- a/source/main/Application.cpp +++ b/source/main/Application.cpp @@ -210,6 +210,7 @@ CVar* audio_master_volume; CVar* audio_enable_creak; CVar* audio_enable_obstruction; CVar* audio_enable_occlusion; +CVar* audio_enable_directed_sounds; CVar* audio_enable_reflection_panning; CVar* audio_enable_efx; CVar* audio_engine_controls_environmental_audio; diff --git a/source/main/Application.h b/source/main/Application.h index 42119e2d33..b4b93e6cac 100644 --- a/source/main/Application.h +++ b/source/main/Application.h @@ -455,6 +455,7 @@ extern CVar* audio_master_volume; extern CVar* audio_enable_creak; extern CVar* audio_enable_obstruction; extern CVar* audio_enable_occlusion; +extern CVar* audio_enable_directed_sounds; extern CVar* audio_enable_reflection_panning; extern CVar* audio_enable_efx; extern CVar* audio_engine_controls_environmental_audio; diff --git a/source/main/audio/SoundManager.cpp b/source/main/audio/SoundManager.cpp index 1d5734d728..4c4bab3d11 100644 --- a/source/main/audio/SoundManager.cpp +++ b/source/main/audio/SoundManager.cpp @@ -23,6 +23,7 @@ #include "SoundManager.h" +#include "AeroEngine.h" #include "Application.h" #include "IWater.h" #include "Sound.h" @@ -425,6 +426,11 @@ void SoundManager::Update(const float dt_sec) this->UpdateListenerEffectSlot(dt_sec); } + + if (App::audio_enable_directed_sounds->getBool()) + { + this->UpdateDirectedSounds(); + } } void SoundManager::SetListener(Ogre::Vector3 position, Ogre::Vector3 direction, Ogre::Vector3 up, Ogre::Vector3 velocity) @@ -1079,6 +1085,138 @@ bool SoundManager::UpdateOcclusionFilter(const int hardware_index, const ALuint return occlusion_detected; } +void SoundManager::UpdateDirectedSounds() const +{ + for (int hardware_index = 0; hardware_index < hardware_sources_num; hardware_index++) + { + if (hardware_sources_map[hardware_index] == -1) { continue;; } // no sound assigned to hardware source at this index + + const SoundPtr& corresponding_sound = audio_sources[hardware_sources_map[hardware_index]]; + const ActorPtrVec& actors = App::GetGameContext()->GetActorManager()->GetActors(); + + for (const ActorPtr& actor : actors) + { + NodeNum_t sound_node = 0; + bool sound_belongs_to_current_actor = false; + + // check if the sound corresponding to this hardware source belongs to the actor + for (int soundsource_index = 0; soundsource_index < actor->ar_num_soundsources; ++soundsource_index) + { + const soundsource_t& soundsource = actor->ar_soundsources[soundsource_index]; + const int num_sounds = soundsource.ssi->getTemplate()->getNumSounds(); + for (int num_sound = 0; num_sound < num_sounds; ++num_sound) + { + if (soundsource.ssi->getSound(num_sound) == corresponding_sound) + { + sound_node = soundsource.nodenum; + } + } + if (sound_node > 0) { break; } + } + + // if the sound does not belong to a node of the current actor, there is no need for further checks + if (sound_node == 0) { continue; } + + // Check if the sound corresponding to the hardware source is attached to an exhaust node of the actor + const std::vector& exhausts = actor->exhausts; + for (const exhaust_t& exhaust : exhausts) + { + if ( sound_node == exhaust.emitterNode + || sound_node == exhaust.directionNode) + { + const Ogre::Vector3 emitter_node_pos = actor->getNodePosition(exhaust.emitterNode); + const Ogre::Vector3 direction_node_pos = actor->getNodePosition(exhaust.directionNode); + + this->UpdateConeProperties( + hardware_sources[hardware_index], + emitter_node_pos - direction_node_pos, + 60.0f, + 170.0f, + 0.85f, + 0.80f); + + break; + } + } + + // Check if the sound corresponding to the hardware source is attached to an AeroEngine + for (int engine_num = 0; engine_num < actor->ar_num_aeroengines; ++engine_num) + { + const auto& aero_engine = actor->ar_aeroengines[engine_num]; + + if (aero_engine->getType() == AeroEngineType::AE_XPROP) + { + if ( sound_node == aero_engine->getNoderef() + || sound_node == aero_engine->GetBackNode()) + { + const Ogre::Vector3 aero_engine_ref_node = actor->getNodePosition(aero_engine->getNoderef()); + const Ogre::Vector3 aero_engine_back_node = actor->getNodePosition(aero_engine->GetBackNode()); + + this->UpdateConeProperties( + hardware_sources[hardware_index], + aero_engine_ref_node - aero_engine_back_node, + 170.0f, + 270.0f, + 0.85f, + 0.70f); + + break; + } + } + else if (aero_engine->getType() == AeroEngineType::AE_TURBOJET) + { + /* + * Since turbojets currently have no high-pitched noise sounds + * for the air intake, we currently assume all sounds are + * directed rearwards of the engine. + * Should air intake noises be added, a front-directed cone should + * be set for them with significant high-frequency dropoff outside. + */ + + if ( sound_node == aero_engine->getNoderef() + || sound_node == aero_engine->GetFrontNode()) + { + const Ogre::Vector3 aero_engine_ref_node = actor->getNodePosition(aero_engine->getNoderef()); + const Ogre::Vector3 aero_engine_front_node = actor->getNodePosition(aero_engine->GetFrontNode()); + + this->UpdateConeProperties( + hardware_sources[hardware_index], + aero_engine_ref_node - aero_engine_front_node, + 60.0f, + 240.0f, + 0.60f, + 0.60f); + } + + break; + } + else { continue; } + } + } + } +} + +void SoundManager::UpdateConeProperties( + const ALuint source, + const Ogre::Vector3& cone_direction, + const float cone_inner_angle, + const float cone_outer_angle, + const float cone_outer_gain, + const float cone_outer_gain_hf + ) const +{ + alSource3f(source, AL_DIRECTION, cone_direction.x, cone_direction.y, cone_direction.z); + + alSourcef (source, AL_CONE_INNER_ANGLE, cone_inner_angle); + alSourcef (source, AL_CONE_OUTER_ANGLE, cone_outer_angle); + alSourcef (source, AL_CONE_OUTER_GAIN, cone_outer_gain); + + if (App::audio_enable_efx->getBool()) + { + alSourcef(source, AL_CONE_OUTER_GAINHF, cone_outer_gain_hf); + } +} + void SoundManager::recomputeSource(int source_index, int reason, float vfl, Vector3* vvec) { if (!audio_device) diff --git a/source/main/audio/SoundManager.h b/source/main/audio/SoundManager.h index 823bfc5e9c..0f7f9d054e 100644 --- a/source/main/audio/SoundManager.h +++ b/source/main/audio/SoundManager.h @@ -352,6 +352,29 @@ class SoundManager * @return True if occlusion was detected, false otherwise. */ bool UpdateOcclusionFilter(const int hardware_index, const ALuint effect_slot_id, const EFXEAXREVERBPROPERTIES* reference_efx_reverb_properties) const; + + /** + * Updates AL Cones for sources of directed sound emissions (exhausts, turboprops and turbojets). + */ + void UpdateDirectedSounds() const; + + /** + * Updates the Cone properties for the hardware source. + * @param source The AL source of which the cone properties will be modified. + * @param cone_direction The direction the cone will face. + * @param cone_inner_angle Angle for the inner zone. + * @param cone_outer_angle Angle that marks the border between transitional and outside zone of the cone + * @param cone_outer_gain Gain for the outside zone. + * @param cone_outer_gain_hf High-frequency gain for the outside zone. + */ + void UpdateConeProperties( + const ALuint source, + const Ogre::Vector3& cone_direction, + const float cone_inner_angle, + const float cone_outer_angle, + const float cone_outer_gain, + const float cone_outer_gain_hf + ) const; }; /// @} diff --git a/source/main/gui/panels/GUI_GameSettings.cpp b/source/main/gui/panels/GUI_GameSettings.cpp index e782134dac..691638232e 100644 --- a/source/main/gui/panels/GUI_GameSettings.cpp +++ b/source/main/gui/panels/GUI_GameSettings.cpp @@ -334,9 +334,10 @@ void GameSettings::DrawAudioSettings() if (App::audio_enable_efx->getBool()) { - DrawGCombo(App::audio_efx_reverb_engine, _LC("GameSettings", "OpenAL Reverb engine"), m_combo_items_efx_reverb_engine.c_str()); - DrawGCheckbox(App::audio_enable_obstruction, _LC("GameSettings", "Sound obstruction")); - DrawGCheckbox(App::audio_enable_occlusion, _LC("GameSettings", "Sound occlusion")); + DrawGCombo(App::audio_efx_reverb_engine, _LC("GameSettings", "OpenAL Reverb engine"), m_combo_items_efx_reverb_engine.c_str()); + DrawGCheckbox(App::audio_enable_obstruction, _LC("GameSettings", "Sound obstruction")); + DrawGCheckbox(App::audio_enable_occlusion, _LC("GameSettings", "Sound occlusion")); + DrawGCheckbox(App::audio_enable_directed_sounds, _LC("GameSettings", "Directed sounds (exhausts etc.)")); if (App::audio_efx_reverb_engine->getEnum() == EfxReverbEngine::EAXREVERB) { DrawGCheckbox(App::audio_enable_reflection_panning, _LC("GameSettings", "Early reflections panning (experimental)")); diff --git a/source/main/physics/air/AeroEngine.h b/source/main/physics/air/AeroEngine.h index e58a1b6c5a..4d4dac121b 100644 --- a/source/main/physics/air/AeroEngine.h +++ b/source/main/physics/air/AeroEngine.h @@ -63,6 +63,8 @@ class AeroEngine virtual bool getIgnition() =0; virtual void setIgnition(bool val) =0; virtual int getNoderef() =0; + virtual NodeNum_t GetFrontNode() const =0; + virtual NodeNum_t GetBackNode() const =0; virtual bool getWarmup() =0; virtual float getRadius() =0; diff --git a/source/main/physics/air/TurboJet.h b/source/main/physics/air/TurboJet.h index 424f35d2b1..70254c8dcb 100644 --- a/source/main/physics/air/TurboJet.h +++ b/source/main/physics/air/TurboJet.h @@ -91,6 +91,8 @@ class Turbojet: public AeroEngine float getThrottle(); float getpropwash() { return m_propwash; }; int getNoderef() { return m_node_back; }; + NodeNum_t GetFrontNode() const override { return m_node_front; }; + NodeNum_t GetBackNode() const override { return m_node_back; }; AeroEngineType getType() { return AeroEngineType::AE_TURBOJET; }; // AeroEngine visuals diff --git a/source/main/physics/air/TurboProp.h b/source/main/physics/air/TurboProp.h index f5616270f0..5b31a87b5d 100644 --- a/source/main/physics/air/TurboProp.h +++ b/source/main/physics/air/TurboProp.h @@ -86,6 +86,8 @@ class Turboprop: public AeroEngine bool getIgnition() { return ignition; }; void setIgnition(bool val) { ignition = val; }; int getNoderef() { return noderef; }; + NodeNum_t GetFrontNode() const override { return RoR::NODENUM_INVALID; }; // turboprops have no front node + NodeNum_t GetBackNode() const override { return nodeback; }; bool getWarmup() { return warmup; }; float getRadius() { return radius; }; diff --git a/source/main/system/CVar.cpp b/source/main/system/CVar.cpp index 28b94be48c..6fef8fbb3b 100644 --- a/source/main/system/CVar.cpp +++ b/source/main/system/CVar.cpp @@ -153,6 +153,7 @@ void Console::cVarSetupBuiltins() App::audio_enable_creak = this->cVarCreate("audio_enable_creak", "Creak Sound", CVAR_ARCHIVE | CVAR_TYPE_BOOL, "false"); App::audio_enable_obstruction = this->cVarCreate("audio_enable_obstruction", "Obstruction of sounds", CVAR_ARCHIVE | CVAR_TYPE_BOOL, "false"); App::audio_enable_occlusion = this->cVarCreate("audio_enable_occlusion", "Occlusion of sounds", CVAR_ARCHIVE | CVAR_TYPE_BOOL, "false"); + App::audio_enable_directed_sounds = this->cVarCreate("audio_enable_directed_sounds", "Directed sounds", CVAR_ARCHIVE | CVAR_TYPE_BOOL, "false"); App::audio_enable_reflection_panning = this->cVarCreate("audio_enable_reflection_panning", "Pan reflections", CVAR_ARCHIVE | CVAR_TYPE_BOOL, "false"); App::audio_device_name = this->cVarCreate("audio_device_name", "AudioDevice", CVAR_ARCHIVE); App::audio_doppler_factor = this->cVarCreate("audio_doppler_factor", "Doppler Factor", CVAR_ARCHIVE | CVAR_TYPE_FLOAT, "1.0");