Rigs of Rods 2023.09
Soft-body Physics Simulation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Loading...
Searching...
No Matches
SoundManager.cpp
Go to the documentation of this file.
1/*
2 This source file is part of Rigs of Rods
3 Copyright 2005-2012 Pierre-Michel Ricordel
4 Copyright 2007-2012 Thomas Fischer
5 Copyright 2013-2020 Petr Ohlidal
6
7 For more information, see http://www.rigsofrods.org/
8
9 Rigs of Rods is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License version 3, as
11 published by the Free Software Foundation.
12
13 Rigs of Rods is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#ifdef USE_OPENAL
23
24#include "SoundManager.h"
25
26#include "AeroEngine.h"
27#include "Application.h"
28#include "IGfxWater.h"
29#include "ScrewProp.h"
30#include "Sound.h"
31
32#include <OgreResourceGroupManager.h>
33
34#define LOGSTREAM Ogre::LogManager::getSingleton().stream() << "[RoR|Audio] "
35
36bool _checkALErrors(const char* filename, int linenum)
37{
38 int err = alGetError();
39 if (err != AL_NO_ERROR)
40 {
41 char buf[1000] = {};
42 snprintf(buf, 1000, "OpenAL Error: %s (0x%x), @ %s:%d", alGetString(err), err, filename, linenum);
43 LOGSTREAM << buf;
44 return true;
45 }
46 return false;
47}
48
49#define hasALErrors() _checkALErrors(__FILE__, __LINE__)
50
51using namespace RoR;
52using namespace Ogre;
53
54const float SoundManager::MAX_DISTANCE = 500.0f;
55const float SoundManager::ROLLOFF_FACTOR = 1.0f;
56const float SoundManager::REFERENCE_DISTANCE = 7.5f;
57
59{
60 if (App::audio_device_name->getStr() == "")
61 {
62 LOGSTREAM << "No audio device configured, opening default.";
63 audio_device = alcOpenDevice(nullptr);
64 }
65 else
66 {
67 audio_device = alcOpenDevice(App::audio_device_name->getStr().c_str());
68 if (!audio_device)
69 {
70 LOGSTREAM << "Failed to open configured audio device \"" << App::audio_device_name->getStr() << "\", opening default.";
72 audio_device = alcOpenDevice(nullptr);
73 }
74 }
75
76 if (!audio_device)
77 {
78 LOGSTREAM << "Failed to open default audio device. Sound disabled.";
80 return;
81 }
82
83 sound_context = alcCreateContext(audio_device, NULL);
84
85 if (!sound_context)
86 {
87 alcCloseDevice(audio_device);
88 audio_device = NULL;
90 return;
91 }
92
93 alcMakeContextCurrent(sound_context);
94
95 if (alGetString(AL_VENDOR)) LOG("SoundManager: OpenAL vendor is: " + String(alGetString(AL_VENDOR)));
96 if (alGetString(AL_VERSION)) LOG("SoundManager: OpenAL version is: " + String(alGetString(AL_VERSION)));
97 if (alGetString(AL_RENDERER)) LOG("SoundManager: OpenAL renderer is: " + String(alGetString(AL_RENDERER)));
98 if (alGetString(AL_EXTENSIONS)) LOG("SoundManager: OpenAL extensions are: " + String(alGetString(AL_EXTENSIONS)));
99 if (alcGetString(audio_device, ALC_DEVICE_SPECIFIER)) LOG("SoundManager: OpenAL device is: " + String(alcGetString(audio_device, ALC_DEVICE_SPECIFIER)));
100 if (alcGetString(audio_device, ALC_EXTENSIONS)) LOG("SoundManager: OpenAL ALC extensions are: " + String(alcGetString(audio_device, ALC_EXTENSIONS)));
101
102 // initialize use of OpenAL EFX extensions
103 m_efx_is_available = alcIsExtensionPresent(audio_device, "ALC_EXT_EFX");
105 {
106 LOG("SoundManager: Found OpenAL EFX extension");
107
108 // Get OpenAL function pointers
109 alGenEffects = (LPALGENEFFECTS)alGetProcAddress("alGenEffects");
110 alDeleteEffects = (LPALDELETEEFFECTS)alGetProcAddress("alDeleteEffects");
111 alIsEffect = (LPALISEFFECT)alGetProcAddress("alIsEffect");
112 alEffecti = (LPALEFFECTI)alGetProcAddress("alEffecti");
113 alEffectf = (LPALEFFECTF)alGetProcAddress("alEffectf");
114 alEffectfv = (LPALEFFECTFV)alGetProcAddress("alEffectfv");
115 alGenFilters = (LPALGENFILTERS)alGetProcAddress("alGenFilters");
116 alDeleteFilters = (LPALDELETEFILTERS)alGetProcAddress("alDeleteFilters");
117 alIsFilter = (LPALISFILTER)alGetProcAddress("alIsFilter");
118 alFilteri = (LPALFILTERI)alGetProcAddress("alFilteri");
119 alFilterf = (LPALFILTERF)alGetProcAddress("alFilterf");
120 alGenAuxiliaryEffectSlots = (LPALGENAUXILIARYEFFECTSLOTS)alGetProcAddress("alGenAuxiliaryEffectSlots");
121 alDeleteAuxiliaryEffectSlots = (LPALDELETEAUXILIARYEFFECTSLOTS)alGetProcAddress("alDeleteAuxiliaryEffectSlots");
122 alIsAuxiliaryEffectSlot = (LPALISAUXILIARYEFFECTSLOT)alGetProcAddress("alIsAuxiliaryEffectSlot");
123 alAuxiliaryEffectSloti = (LPALAUXILIARYEFFECTSLOTI)alGetProcAddress("alAuxiliaryEffectSloti");
124 alAuxiliaryEffectSlotf = (LPALAUXILIARYEFFECTSLOTF)alGetProcAddress("alAuxiliaryEffectSlotf");
125 alAuxiliaryEffectSlotfv = (LPALAUXILIARYEFFECTSLOTFV)alGetProcAddress("alAuxiliaryEffectSlotfv");
126
127 if (App::audio_enable_efx->getBool())
128 {
129 // allow user to change reverb engines at will
130 switch (App::audio_efx_reverb_engine->getEnum<EfxReverbEngine>())
131 {
134 default:
136 LOG("SoundManager: Reverb engine disabled");
137 }
138
140 {
141 if (alGetEnumValue("AL_EFFECT_EAXREVERB") != 0)
142 {
143 LOG("SoundManager: OpenAL driver supports AL_EFFECT_EAXREVERB, using it");
144 }
145 else
146 {
147 LOG("SoundManager: AL_EFFECT_EAXREVERB requested but OpenAL driver does not support it, falling back to standard reverb. Advanced features, such as reflection panning, will not be available");
149 }
150 }
152 {
153 LOG("SoundManager: Using OpenAL standard reverb");
154 }
155
156 // create effect slot for the listener
158 {
159 alGetError();
160
162 ALuint error = alGetError();
163
164 if (error != AL_NO_ERROR)
165 {
166 LOG("SoundManager: alGenAuxiliaryEffectSlots for listener_slot failed: " + TOSTRING(alGetString(error)));
167 m_listener_slot = AL_EFFECTSLOT_NULL;
168 }
169 }
170
172
173 /*
174 Create filter for obstruction
175 Currently we don't check for how much high-frequency content the obstacle
176 lets through. We assume it's a hard surface with significant absorption
177 of high frequencies (which should be true for trucks, buildings and terrain).
178 */
179 alGetError();
180
182 ALuint e = alGetError();
183
184 if (e != AL_NO_ERROR)
185 {
187 }
188 else
189 {
190 this->alFilteri(m_efx_outdoor_obstruction_lowpass_filter_id, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
191 this->alFilterf(m_efx_outdoor_obstruction_lowpass_filter_id, AL_LOWPASS_GAIN, 0.33f);
192 this->alFilterf(m_efx_outdoor_obstruction_lowpass_filter_id, AL_LOWPASS_GAINHF, 0.25f);
193 }
194
195 /*
196 Create wet path filter for occlusion
197 Currently we don't check for how much high-frequency content the encompassing walls
198 let through. We assume it's a hard surface with significant absorption
199 of high frequencies (which should be true for most types of buildings).
200 */
201 alGetError();
202
204 e = alGetError();
205
206 if (e != AL_NO_ERROR)
207 {
209 }
210 else
211 {
212 this->alFilteri(m_efx_occlusion_wet_path_lowpass_filter_id, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
213 this->alFilterf(m_efx_occlusion_wet_path_lowpass_filter_id, AL_LOWPASS_GAIN, 0.33f);
214 this->alFilterf(m_efx_occlusion_wet_path_lowpass_filter_id, AL_LOWPASS_GAINHF, 0.25f);
215 }
216 }
217 }
218 else
219 {
220 LOG("SoundManager: OpenAL EFX extension not found, disabling EFX");
222 }
223
224 // generate the AL sources
226 {
227 alGetError();
228 alGenSources(1, &hardware_sources[hardware_sources_num]);
229 if (alGetError() != AL_NO_ERROR)
230 break;
231 alSourcef(hardware_sources[hardware_sources_num], AL_REFERENCE_DISTANCE, REFERENCE_DISTANCE);
232 alSourcef(hardware_sources[hardware_sources_num], AL_ROLLOFF_FACTOR, ROLLOFF_FACTOR);
233 alSourcef(hardware_sources[hardware_sources_num], AL_MAX_DISTANCE, MAX_DISTANCE);
234
235 // connect source to listener slot effect
236 if (App::audio_enable_efx->getBool())
237 {
238 alSource3i(hardware_sources[hardware_sources_num], AL_AUXILIARY_SEND_FILTER, m_listener_slot, 0, AL_FILTER_NULL);
239 }
240 }
241
242 alDopplerFactor(App::audio_doppler_factor->getFloat());
243 alSpeedOfSound(343.3f);
244
245 for (int i = 0; i < MAX_HARDWARE_SOURCES; i++)
246 {
247 hardware_sources_map[i] = -1;
248 }
249
250
251}
252
254{
255 // delete the sources and buffers
256 alDeleteSources(MAX_HARDWARE_SOURCES, hardware_sources);
257 alDeleteBuffers(MAX_AUDIO_BUFFERS, audio_buffers);
258
260 {
262 {
264 }
265
267 {
269 }
270
272 {
273 this->alAuxiliaryEffectSloti(m_listener_slot, AL_EFFECTSLOT_EFFECT, AL_EFFECTSLOT_NULL);
275 m_listener_slot = 0;
276 }
277 }
278
279 CleanUp();
280
281 // destroy the sound context and device
282 sound_context = alcGetCurrentContext();
283 audio_device = alcGetContextsDevice(sound_context);
284 alcMakeContextCurrent(NULL);
285 alcDestroyContext(sound_context);
286 if (audio_device)
287 {
288 alcCloseDevice(audio_device);
289 }
290 LOG("SoundManager destroyed.");
291}
292
294{
296 {
299 {
300 this->alAuxiliaryEffectSloti(m_listener_slot, AL_EFFECTSLOT_EFFECT, AL_EFFECTSLOT_NULL);
301 }
302
303 for (auto it = m_efx_effect_id_map.begin(); it != m_efx_effect_id_map.end();)
304 {
305 this->DeleteAlEffect(it->second);
306 it = m_efx_effect_id_map.erase(it);
307 }
308 }
309
310 // TODO: Delete Sounds and buffers
311}
312
313const EFXEAXREVERBPROPERTIES* SoundManager::GetEfxProperties(const std::string& efx_preset_name) const
314{
315 const auto it = m_efx_properties_map.find(efx_preset_name);
316
317 if (it != m_efx_properties_map.end())
318 {
319 return &it->second;
320 }
321 else
322 {
323 return nullptr;
324 }
325}
326
328{
329 m_efx_properties_map["EFX_REVERB_PRESET_GENERIC"] = EFX_REVERB_PRESET_GENERIC;
330 m_efx_properties_map["EFX_REVERB_PRESET_ROOM"] = EFX_REVERB_PRESET_ROOM;
331 m_efx_properties_map["EFX_REVERB_PRESET_CAVE"] = EFX_REVERB_PRESET_CAVE;
332 m_efx_properties_map["EFX_REVERB_PRESET_ARENA"] = EFX_REVERB_PRESET_ARENA;
333 m_efx_properties_map["EFX_REVERB_PRESET_HANGAR"] = EFX_REVERB_PRESET_HANGAR;
334 m_efx_properties_map["EFX_REVERB_PRESET_ALLEY"] = EFX_REVERB_PRESET_ALLEY;
335 m_efx_properties_map["EFX_REVERB_PRESET_HALLWAY"] = EFX_REVERB_PRESET_HALLWAY;
336 m_efx_properties_map["EFX_REVERB_PRESET_FOREST"] = EFX_REVERB_PRESET_FOREST;
337 m_efx_properties_map["EFX_REVERB_PRESET_CITY"] = EFX_REVERB_PRESET_CITY;
338 m_efx_properties_map["EFX_REVERB_PRESET_MOUNTAINS"] = EFX_REVERB_PRESET_MOUNTAINS;
339 m_efx_properties_map["EFX_REVERB_PRESET_QUARRY"] = EFX_REVERB_PRESET_QUARRY;
340 m_efx_properties_map["EFX_REVERB_PRESET_PLAIN"] = EFX_REVERB_PRESET_PLAIN;
341 m_efx_properties_map["EFX_REVERB_PRESET_PARKINGLOT"] = EFX_REVERB_PRESET_PARKINGLOT;
342 m_efx_properties_map["EFX_REVERB_PRESET_UNDERWATER"] = EFX_REVERB_PRESET_UNDERWATER;
343 m_efx_properties_map["EFX_REVERB_PRESET_DRUGGED"] = EFX_REVERB_PRESET_DRUGGED;
344 m_efx_properties_map["EFX_REVERB_PRESET_DIZZY"] = EFX_REVERB_PRESET_DIZZY;
345 m_efx_properties_map["EFX_REVERB_PRESET_CASTLE_SHORTPASSAGE"] = EFX_REVERB_PRESET_CASTLE_SHORTPASSAGE;
346 m_efx_properties_map["EFX_REVERB_PRESET_CASTLE_LARGEROOM"] = EFX_REVERB_PRESET_CASTLE_LARGEROOM;
347 m_efx_properties_map["EFX_REVERB_PRESET_CASTLE_LONGPASSAGE"] = EFX_REVERB_PRESET_CASTLE_LONGPASSAGE;
348 m_efx_properties_map["EFX_REVERB_PRESET_CASTLE_HALL"] = EFX_REVERB_PRESET_CASTLE_HALL;
349 m_efx_properties_map["EFX_REVERB_PRESET_CASTLE_COURTYARD"] = EFX_REVERB_PRESET_CASTLE_COURTYARD;
350 m_efx_properties_map["EFX_REVERB_PRESET_FACTORY_SMALLROOM"] = EFX_REVERB_PRESET_FACTORY_SMALLROOM;
351 m_efx_properties_map["EFX_REVERB_PRESET_FACTORY_SHORTPASSAGE"] = EFX_REVERB_PRESET_FACTORY_SHORTPASSAGE;
352 m_efx_properties_map["EFX_REVERB_PRESET_FACTORY_MEDIUMROOM"] = EFX_REVERB_PRESET_FACTORY_MEDIUMROOM;
353 m_efx_properties_map["EFX_REVERB_PRESET_FACTORY_LARGEROOM"] = EFX_REVERB_PRESET_FACTORY_LARGEROOM;
354 m_efx_properties_map["EFX_REVERB_PRESET_FACTORY_LONGPASSAGE"] = EFX_REVERB_PRESET_FACTORY_LONGPASSAGE;
355 m_efx_properties_map["EFX_REVERB_PRESET_FACTORY_HALL"] = EFX_REVERB_PRESET_FACTORY_HALL;
356 m_efx_properties_map["EFX_REVERB_PRESET_FACTORY_COURTYARD"] = EFX_REVERB_PRESET_FACTORY_COURTYARD;
357 m_efx_properties_map["EFX_REVERB_PRESET_FACTORY_ALCOVE"] = EFX_REVERB_PRESET_FACTORY_ALCOVE;
358 m_efx_properties_map["EFX_REVERB_PRESET_SPACESTATION_SMALLROOM"] = EFX_REVERB_PRESET_SPACESTATION_SMALLROOM;
359 m_efx_properties_map["EFX_REVERB_PRESET_SPACESTATION_SHORTPASSAGE"] = EFX_REVERB_PRESET_SPACESTATION_SHORTPASSAGE;
360 m_efx_properties_map["EFX_REVERB_PRESET_SPACESTATION_MEDIUMROOM"] = EFX_REVERB_PRESET_SPACESTATION_MEDIUMROOM;
361 m_efx_properties_map["EFX_REVERB_PRESET_SPACESTATION_LARGEROOM"] = EFX_REVERB_PRESET_SPACESTATION_LARGEROOM;
362 m_efx_properties_map["EFX_REVERB_PRESET_SPACESTATION_LONGPASSAGE"] = EFX_REVERB_PRESET_SPACESTATION_LONGPASSAGE;
363 m_efx_properties_map["EFX_REVERB_PRESET_SPACESTATION_HALL"] = EFX_REVERB_PRESET_SPACESTATION_HALL;
364 m_efx_properties_map["EFX_REVERB_PRESET_WOODEN_SMALLROOM"] = EFX_REVERB_PRESET_WOODEN_SMALLROOM;
365 m_efx_properties_map["EFX_REVERB_PRESET_WOODEN_SHORTPASSAGE"] = EFX_REVERB_PRESET_WOODEN_SHORTPASSAGE;
366 m_efx_properties_map["EFX_REVERB_PRESET_WOODEN_MEDIUMROOM"] = EFX_REVERB_PRESET_WOODEN_MEDIUMROOM;
367 m_efx_properties_map["EFX_REVERB_PRESET_WOODEN_LARGEROOM"] = EFX_REVERB_PRESET_WOODEN_LARGEROOM;
368 m_efx_properties_map["EFX_REVERB_PRESET_WOODEN_LONGPASSAGE"] = EFX_REVERB_PRESET_WOODEN_LONGPASSAGE;
369 m_efx_properties_map["EFX_REVERB_PRESET_WOODEN_HALL"] = EFX_REVERB_PRESET_WOODEN_HALL;
370 m_efx_properties_map["EFX_REVERB_PRESET_WOODEN_COURTYARD"] = EFX_REVERB_PRESET_WOODEN_COURTYARD;
371 m_efx_properties_map["EFX_REVERB_PRESET_WOODEN_ALCOVE"] = EFX_REVERB_PRESET_WOODEN_ALCOVE;
372 m_efx_properties_map["EFX_REVERB_PRESET_SPORT_EMPTYSTADIUM"] = EFX_REVERB_PRESET_SPORT_EMPTYSTADIUM;
373 m_efx_properties_map["EFX_REVERB_PRESET_SPORT_FULLSTADIUM"] = EFX_REVERB_PRESET_SPORT_FULLSTADIUM;
374 m_efx_properties_map["EFX_REVERB_PRESET_SPORT_STADIUMTANNOY"] = EFX_REVERB_PRESET_SPORT_STADIUMTANNOY;
375 m_efx_properties_map["EFX_REVERB_PRESET_PREFAB_WORKSHOP"] = EFX_REVERB_PRESET_PREFAB_WORKSHOP;
376 m_efx_properties_map["EFX_REVERB_PRESET_PREFAB_OUTHOUSE"] = EFX_REVERB_PRESET_PREFAB_OUTHOUSE;
377 m_efx_properties_map["EFX_REVERB_PRESET_PREFAB_CARAVAN"] = EFX_REVERB_PRESET_PREFAB_CARAVAN;
378 m_efx_properties_map["EFX_REVERB_PRESET_PIPE_LARGE"] = EFX_REVERB_PRESET_PIPE_LARGE;
379 m_efx_properties_map["EFX_REVERB_PRESET_PIPE_LONGTHIN"] = EFX_REVERB_PRESET_PIPE_LONGTHIN;
380 m_efx_properties_map["EFX_REVERB_PRESET_PIPE_RESONANT"] = EFX_REVERB_PRESET_PIPE_RESONANT;
381 m_efx_properties_map["EFX_REVERB_PRESET_OUTDOORS_BACKYARD"] = EFX_REVERB_PRESET_OUTDOORS_BACKYARD;
382 m_efx_properties_map["EFX_REVERB_PRESET_OUTDOORS_ROLLINGPLAINS"] = EFX_REVERB_PRESET_OUTDOORS_ROLLINGPLAINS;
383 m_efx_properties_map["EFX_REVERB_PRESET_OUTDOORS_DEEPCANYON"] = EFX_REVERB_PRESET_OUTDOORS_DEEPCANYON;
384 m_efx_properties_map["EFX_REVERB_PRESET_OUTDOORS_CREEK"] = EFX_REVERB_PRESET_OUTDOORS_CREEK;
385 m_efx_properties_map["EFX_REVERB_PRESET_OUTDOORS_VALLEY"] = EFX_REVERB_PRESET_OUTDOORS_VALLEY;
386 m_efx_properties_map["EFX_REVERB_PRESET_MOOD_HEAVEN"] = EFX_REVERB_PRESET_MOOD_HEAVEN;
387 m_efx_properties_map["EFX_REVERB_PRESET_MOOD_HELL"] = EFX_REVERB_PRESET_MOOD_HELL;
388 m_efx_properties_map["EFX_REVERB_PRESET_MOOD_MEMORY"] = EFX_REVERB_PRESET_MOOD_MEMORY;
389 m_efx_properties_map["EFX_REVERB_PRESET_DRIVING_COMMENTATOR"] = EFX_REVERB_PRESET_DRIVING_COMMENTATOR;
390 m_efx_properties_map["EFX_REVERB_PRESET_DRIVING_PITGARAGE"] = EFX_REVERB_PRESET_DRIVING_PITGARAGE;
391 m_efx_properties_map["EFX_REVERB_PRESET_DRIVING_INCAR_RACER"] = EFX_REVERB_PRESET_DRIVING_INCAR_RACER;
392 m_efx_properties_map["EFX_REVERB_PRESET_DRIVING_INCAR_SPORTS"] = EFX_REVERB_PRESET_DRIVING_INCAR_SPORTS;
393 m_efx_properties_map["EFX_REVERB_PRESET_DRIVING_INCAR_LUXURY"] = EFX_REVERB_PRESET_DRIVING_INCAR_LUXURY;
394 m_efx_properties_map["EFX_REVERB_PRESET_DRIVING_FULLGRANDSTAND"] = EFX_REVERB_PRESET_DRIVING_FULLGRANDSTAND;
395 m_efx_properties_map["EFX_REVERB_PRESET_DRIVING_EMPTYGRANDSTAND"] = EFX_REVERB_PRESET_DRIVING_EMPTYGRANDSTAND;
396 m_efx_properties_map["EFX_REVERB_PRESET_DRIVING_TUNNEL"] = EFX_REVERB_PRESET_DRIVING_TUNNEL;
397 m_efx_properties_map["EFX_REVERB_PRESET_CITY_STREETS"] = EFX_REVERB_PRESET_CITY_STREETS;
398 m_efx_properties_map["EFX_REVERB_PRESET_CITY_SUBWAY"] = EFX_REVERB_PRESET_CITY_SUBWAY;
399 m_efx_properties_map["EFX_REVERB_PRESET_CITY_UNDERPASS"] = EFX_REVERB_PRESET_CITY_UNDERPASS;
400 m_efx_properties_map["EFX_REVERB_PRESET_CITY_ABANDONED"] = EFX_REVERB_PRESET_CITY_ABANDONED;
401}
402
403void SoundManager::Update(const float dt)
404{
405 if (!audio_device)
406 return;
407
408 const auto water = App::GetGameContext()->GetTerrain()->getWater();
409 m_listener_is_underwater = (water != nullptr ? water->IsUnderWater(m_listener_position) : false);
410
412 this->recomputeAllSources();
413 this->UpdateAlListener();
416}
417
419{
420 if (App::GetGameContext()->GetActorManager()->IsSimulationPaused() && App::audio_sim_pause_disables_doppler_effect->getBool())
421 {
422 /*
423 If the simulation is paused, the listener's velocity suddenly becomes close to zero, which could be a large
424 difference to before if the listener was following a fast vehicle. Additionally, users might be confused when
425 they rotate around objects moving at least as fast as Mach 1 since the Doppler shift changes significantly and
426 there would be no sound in front of the object.
427 Hence, we provide an option for disabling the Doppler effect while the simulation is paused.
428 */
429 this->SetDopplerFactor(0);
430 }
431 else {
433 }
434}
435
437{
438 if (!App::audio_enable_efx->getBool()) { return; }
439
440 for (int hardware_index = 0; hardware_index < hardware_sources_num; ++hardware_index)
441 {
442 this->UpdateSourceSpecificDopplerFactor(hardware_index);
443
444 // update air absorption factor
445 alSourcef(hardware_sources[hardware_index], AL_AIR_ABSORPTION_FACTOR, m_air_absorption_factor);
446
447 this->UpdateSourceFilters(hardware_index);
448 }
449
450 this->UpdateListenerEffectSlot(dt);
451
453 {
454 this->UpdateDirectedSounds();
455 }
456}
457
458void SoundManager::UpdateSourceSpecificDopplerFactor(const int hardware_index) const
459{
460 const SoundPtr& corresponding_sound = audio_sources[hardware_sources_map[hardware_index]];
462
463 // identify actor to which the Sound instance corresponding to the hardware source belongs
464 for (const ActorPtr& actor : actors)
465 {
466 for (int soundsource_index = 0; soundsource_index < actor->ar_num_soundsources; ++soundsource_index)
467 {
468 const soundsource_t& soundsource = actor->ar_soundsources[soundsource_index];
469 const int num_sounds_of_ss = soundsource.ssi->getTemplate()->getNumSounds();
470
471 for (int num_sound = 0; num_sound < num_sounds_of_ss; ++num_sound)
472 {
473 // update the Doppler factor if the Sound belongs to the actor
474 if (soundsource.ssi->getSound(num_sound) == corresponding_sound)
475 {
477 || !actor->ar_physics_paused)
478 {
479 this->SetDopplerFactor(hardware_sources[hardware_index], 1.0f);
480 }
481 else {
482 this->SetDopplerFactor(hardware_sources[hardware_index], 0.0f);
483 }
484
485 return;
486 }
487 }
488 }
489 }
490}
491
492void SoundManager::SetListener(Ogre::Vector3 position, Ogre::Vector3 direction, Ogre::Vector3 up, Ogre::Vector3 velocity)
493{
494 m_listener_position = position;
495 m_listener_direction = direction;
496 m_listener_up = up;
497 m_listener_velocity = velocity;
498}
499
500void SoundManager::SetDopplerFactor(const ALuint hardware_source, const float doppler_factor) const
501{
502 App::audio_enable_efx->getBool() ? alSourcef(hardware_source, AL_DOPPLER_FACTOR, doppler_factor) : void();
503}
504
506{
507 const EFXEAXREVERBPROPERTIES* listener_reverb_properties = nullptr;
508
510 {
511 if (this->ListenerIsUnderwater())
512 {
513 this->SetSpeedOfSound(1522.0f); // assume listener is in sea water (i.e. salt water)
514 /*
515 * According to the Francois-Garrison formula for frequency-dependant absorption at 5kHz in seawater,
516 * the absorption should be 0.334 dB/km. OpenAL multiplies the Air Absorption Factor with an internal
517 * value of 0.05dB/m, so we need a factor of 0.00668f.
518 */
519 this->SetAirAbsorptionFactor(0.00668f);
520 }
521 else
522 {
523 this->SetSpeedOfSound(343.3f); // assume listener is in air at 20° celsius
524 this->SetAirAbsorptionFactor(1.0f);
525 }
526
527 if (App::audio_enable_efx->getBool())
528 {
530 }
531 }
532}
533
535{
536 float orientation[6];
537 // direction
538 orientation[0] = m_listener_direction.x;
539 orientation[1] = m_listener_direction.y;
540 orientation[2] = m_listener_direction.z;
541 // up
542 orientation[3] = m_listener_up.x;
543 orientation[4] = m_listener_up.y;
544 orientation[5] = m_listener_up.z;
545
546 alListener3f(AL_POSITION, m_listener_position.x, m_listener_position.y, m_listener_position.z);
547 alListener3f(AL_VELOCITY, m_listener_velocity.x, m_listener_velocity.y, m_listener_velocity.z);
548 alListenerfv(AL_ORIENTATION, orientation);
549}
550
551const EFXEAXREVERBPROPERTIES* SoundManager::GetReverbPresetAt(const Ogre::Vector3 position) const
552{
553 // for the listener we do additional checks
554 if (position == m_listener_position)
555 {
556 if (!App::audio_force_listener_efx_preset->getStr().empty())
557 {
559 }
560 }
561
562 const auto water = App::GetGameContext()->GetTerrain()->getWater();
563 bool position_is_underwater = (water != nullptr ? water->IsUnderWater(position) : false);
564 if (position_is_underwater)
565 {
566 return this->GetEfxProperties("EFX_REVERB_PRESET_UNDERWATER");
567 }
568
569 // check if position is inside a collision box with a reverb_preset assigned to it
571 {
572 if (!collision_box.reverb_preset_name.empty())
573 {
574 const Ogre::AxisAlignedBox collision_box_aab = Ogre::AxisAlignedBox(collision_box.lo, collision_box.hi);
575
576 if (collision_box_aab.contains(position))
577 {
578 return this->GetEfxProperties(collision_box.reverb_preset_name);
579 }
580 }
581 }
582
583 if (!App::audio_default_efx_preset->getStr().empty())
584 {
585 return this->GetEfxProperties(App::audio_default_efx_preset->getStr());
586 }
587 else
588 {
589 return nullptr;
590 }
591}
592
594{
596 {
598 return;
599 }
600
601 EFXEAXREVERBPROPERTIES current_environmental_properties = *m_listener_efx_reverb_properties;
602
603 // early reflections panning, delay and strength
605 {
606 std::tuple<Ogre::Vector3, float, float> target_early_reflections_properties = this->ComputeEarlyReflectionsProperties();
607
608 // convert panning vector from RHS to EAXREVERB's LHS
609 current_environmental_properties.flReflectionsPan[0] = std::get<0>(target_early_reflections_properties).x;
610 current_environmental_properties.flReflectionsPan[1] = 0;
611 current_environmental_properties.flReflectionsPan[2] = -std::get<0>(target_early_reflections_properties).z;
612
613 current_environmental_properties.flReflectionsGain = std::get<1>(target_early_reflections_properties);
614 current_environmental_properties.flReflectionsDelay = std::get<2>(target_early_reflections_properties);
615 }
616
617 this->SmoothlyUpdateAlAuxiliaryEffectSlot(dt, m_listener_slot, &current_environmental_properties);
618}
619
620void SoundManager::SmoothlyUpdateAlAuxiliaryEffectSlot(const float dt, const ALuint slot_id, const EFXEAXREVERBPROPERTIES* target_efx_properties)
621{
622 const float time_to_target = 0.333f; // seconds to reach the target properties from the current properties
623 // ensure to not exceed limits of reverb parameters if timestep is too large
624 const float step = std::min(dt / time_to_target, 0.5f);
625 static std::map<ALuint, EFXEAXREVERBPROPERTIES> current_efx_properties_of_slot;
626
627 if (target_efx_properties == nullptr)
628 {
629 this->alAuxiliaryEffectSloti(slot_id, AL_EFFECTSLOT_EFFECT, AL_EFFECTSLOT_NULL);
630 return;
631 }
632
633 const auto it = current_efx_properties_of_slot.find(slot_id);
634 if (it == current_efx_properties_of_slot.end())
635 {
636 // previously unseen effect slot, set a starting point
637 current_efx_properties_of_slot[slot_id] = *target_efx_properties;
638 }
639
640 ALuint efx_effect_id;
641 // create new AL effect if not existing
642 if (m_efx_effect_id_map.find(slot_id) == m_efx_effect_id_map.end())
643 {
644 efx_effect_id = this->CreateAlEffect(target_efx_properties);
645 m_efx_effect_id_map[slot_id] = efx_effect_id;
646 }
647 else
648 {
649 efx_effect_id = m_efx_effect_id_map.find(slot_id)->second;
650 }
651
652 // compute intermediate step between current and target properties using linear interpolation based on time step
653 current_efx_properties_of_slot[slot_id] =
654 {
655 current_efx_properties_of_slot[slot_id].flDensity + step * (target_efx_properties->flDensity - current_efx_properties_of_slot[slot_id].flDensity),
656 current_efx_properties_of_slot[slot_id].flDiffusion + step * (target_efx_properties->flDiffusion - current_efx_properties_of_slot[slot_id].flDiffusion),
657 current_efx_properties_of_slot[slot_id].flGain + step * (target_efx_properties->flGain - current_efx_properties_of_slot[slot_id].flGain),
658 current_efx_properties_of_slot[slot_id].flGainHF + step * (target_efx_properties->flGainHF - current_efx_properties_of_slot[slot_id].flGainHF),
659 current_efx_properties_of_slot[slot_id].flGainLF + step * (target_efx_properties->flGainLF - current_efx_properties_of_slot[slot_id].flGainLF),
660 current_efx_properties_of_slot[slot_id].flDecayTime + step * (target_efx_properties->flDecayTime - current_efx_properties_of_slot[slot_id].flDecayTime),
661 current_efx_properties_of_slot[slot_id].flDecayHFRatio + step * (target_efx_properties->flDecayHFRatio - current_efx_properties_of_slot[slot_id].flDecayHFRatio),
662 current_efx_properties_of_slot[slot_id].flDecayLFRatio + step * (target_efx_properties->flDecayLFRatio - current_efx_properties_of_slot[slot_id].flDecayLFRatio),
663 current_efx_properties_of_slot[slot_id].flReflectionsGain + step * (target_efx_properties->flReflectionsGain - current_efx_properties_of_slot[slot_id].flReflectionsGain),
664 current_efx_properties_of_slot[slot_id].flReflectionsDelay + step * (target_efx_properties->flReflectionsDelay - current_efx_properties_of_slot[slot_id].flReflectionsDelay),
665 current_efx_properties_of_slot[slot_id].flReflectionsPan[0] + step * (target_efx_properties->flReflectionsPan[0] - current_efx_properties_of_slot[slot_id].flReflectionsPan[0]),
666 current_efx_properties_of_slot[slot_id].flReflectionsPan[1] + step * (target_efx_properties->flReflectionsPan[1] - current_efx_properties_of_slot[slot_id].flReflectionsPan[1]),
667 current_efx_properties_of_slot[slot_id].flReflectionsPan[2] + step * (target_efx_properties->flReflectionsPan[2] - current_efx_properties_of_slot[slot_id].flReflectionsPan[2]),
668 current_efx_properties_of_slot[slot_id].flLateReverbGain + step * (target_efx_properties->flLateReverbGain - current_efx_properties_of_slot[slot_id].flLateReverbGain),
669 current_efx_properties_of_slot[slot_id].flLateReverbDelay + step * (target_efx_properties->flLateReverbDelay - current_efx_properties_of_slot[slot_id].flLateReverbDelay),
670 current_efx_properties_of_slot[slot_id].flLateReverbPan[0] + step * (target_efx_properties->flLateReverbPan[0] - current_efx_properties_of_slot[slot_id].flLateReverbPan[0]),
671 current_efx_properties_of_slot[slot_id].flLateReverbPan[1] + step * (target_efx_properties->flLateReverbPan[1] - current_efx_properties_of_slot[slot_id].flLateReverbPan[1]),
672 current_efx_properties_of_slot[slot_id].flLateReverbPan[2] + step * (target_efx_properties->flLateReverbPan[2] - current_efx_properties_of_slot[slot_id].flLateReverbPan[2]),
673 current_efx_properties_of_slot[slot_id].flEchoTime + step * (target_efx_properties->flEchoTime - current_efx_properties_of_slot[slot_id].flEchoTime),
674 current_efx_properties_of_slot[slot_id].flEchoDepth + step * (target_efx_properties->flEchoDepth - current_efx_properties_of_slot[slot_id].flEchoDepth),
675 current_efx_properties_of_slot[slot_id].flModulationTime + step * (target_efx_properties->flModulationTime - current_efx_properties_of_slot[slot_id].flModulationTime),
676 current_efx_properties_of_slot[slot_id].flModulationDepth + step * (target_efx_properties->flModulationDepth - current_efx_properties_of_slot[slot_id].flModulationDepth),
677 current_efx_properties_of_slot[slot_id].flAirAbsorptionGainHF + step * (target_efx_properties->flAirAbsorptionGainHF - current_efx_properties_of_slot[slot_id].flAirAbsorptionGainHF),
678 current_efx_properties_of_slot[slot_id].flHFReference + step * (target_efx_properties->flHFReference - current_efx_properties_of_slot[slot_id].flHFReference),
679 current_efx_properties_of_slot[slot_id].flLFReference + step * (target_efx_properties->flLFReference - current_efx_properties_of_slot[slot_id].flLFReference),
680 current_efx_properties_of_slot[slot_id].flRoomRolloffFactor + step * (target_efx_properties->flRoomRolloffFactor - current_efx_properties_of_slot[slot_id].flRoomRolloffFactor),
681 static_cast<int>(std::round(current_efx_properties_of_slot[slot_id].iDecayHFLimit + step * (target_efx_properties->iDecayHFLimit - current_efx_properties_of_slot[slot_id].iDecayHFLimit))),
682 };
683
684 // update AL effect to intermediate values
685 switch (m_efx_reverb_engine)
686 {
688 this->alEffectf( efx_effect_id, AL_EAXREVERB_DENSITY, current_efx_properties_of_slot[slot_id].flDensity);
689 this->alEffectf( efx_effect_id, AL_EAXREVERB_DIFFUSION, current_efx_properties_of_slot[slot_id].flDiffusion);
690 this->alEffectf( efx_effect_id, AL_EAXREVERB_GAIN, current_efx_properties_of_slot[slot_id].flGain);
691 this->alEffectf( efx_effect_id, AL_EAXREVERB_GAINHF, current_efx_properties_of_slot[slot_id].flGainHF);
692 this->alEffectf( efx_effect_id, AL_EAXREVERB_GAINLF, current_efx_properties_of_slot[slot_id].flGainLF);
693 this->alEffectf( efx_effect_id, AL_EAXREVERB_DECAY_TIME, current_efx_properties_of_slot[slot_id].flDecayTime);
694 this->alEffectf( efx_effect_id, AL_EAXREVERB_DECAY_HFRATIO, current_efx_properties_of_slot[slot_id].flDecayHFRatio);
695 this->alEffectf( efx_effect_id, AL_EAXREVERB_DECAY_LFRATIO, current_efx_properties_of_slot[slot_id].flDecayLFRatio);
696 this->alEffectf( efx_effect_id, AL_EAXREVERB_REFLECTIONS_GAIN, current_efx_properties_of_slot[slot_id].flReflectionsGain);
697 this->alEffectf( efx_effect_id, AL_EAXREVERB_REFLECTIONS_DELAY, current_efx_properties_of_slot[slot_id].flReflectionsDelay);
698 this->alEffectfv(efx_effect_id, AL_EAXREVERB_REFLECTIONS_PAN, current_efx_properties_of_slot[slot_id].flReflectionsPan);
699 this->alEffectf( efx_effect_id, AL_EAXREVERB_LATE_REVERB_GAIN, current_efx_properties_of_slot[slot_id].flLateReverbGain);
700 this->alEffectf( efx_effect_id, AL_EAXREVERB_LATE_REVERB_DELAY, current_efx_properties_of_slot[slot_id].flLateReverbDelay);
701 this->alEffectfv(efx_effect_id, AL_EAXREVERB_LATE_REVERB_PAN, current_efx_properties_of_slot[slot_id].flLateReverbPan);
702 this->alEffectf( efx_effect_id, AL_EAXREVERB_ECHO_TIME, current_efx_properties_of_slot[slot_id].flEchoTime);
703 this->alEffectf( efx_effect_id, AL_EAXREVERB_ECHO_DEPTH, current_efx_properties_of_slot[slot_id].flEchoDepth);
704 this->alEffectf( efx_effect_id, AL_EAXREVERB_MODULATION_TIME, current_efx_properties_of_slot[slot_id].flModulationTime);
705 this->alEffectf( efx_effect_id, AL_EAXREVERB_MODULATION_DEPTH, current_efx_properties_of_slot[slot_id].flModulationDepth);
706 this->alEffectf( efx_effect_id, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, current_efx_properties_of_slot[slot_id].flAirAbsorptionGainHF);
707 this->alEffectf( efx_effect_id, AL_EAXREVERB_HFREFERENCE, current_efx_properties_of_slot[slot_id].flHFReference);
708 this->alEffectf( efx_effect_id, AL_EAXREVERB_LFREFERENCE, current_efx_properties_of_slot[slot_id].flLFReference);
709 this->alEffectf( efx_effect_id, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, current_efx_properties_of_slot[slot_id].flRoomRolloffFactor);
710 this->alEffecti( efx_effect_id, AL_EAXREVERB_DECAY_HFLIMIT, current_efx_properties_of_slot[slot_id].iDecayHFLimit);
711 break;
712
714 this->alEffectf( efx_effect_id, AL_REVERB_DENSITY, current_efx_properties_of_slot[slot_id].flDensity);
715 this->alEffectf( efx_effect_id, AL_REVERB_DIFFUSION, current_efx_properties_of_slot[slot_id].flDiffusion);
716 this->alEffectf( efx_effect_id, AL_REVERB_GAIN, current_efx_properties_of_slot[slot_id].flGain);
717 this->alEffectf( efx_effect_id, AL_REVERB_GAINHF, current_efx_properties_of_slot[slot_id].flGainHF);
718 this->alEffectf( efx_effect_id, AL_REVERB_DECAY_TIME, current_efx_properties_of_slot[slot_id].flDecayTime);
719 this->alEffectf( efx_effect_id, AL_REVERB_DECAY_HFRATIO, current_efx_properties_of_slot[slot_id].flDecayHFRatio);
720 this->alEffectf( efx_effect_id, AL_REVERB_REFLECTIONS_GAIN, current_efx_properties_of_slot[slot_id].flReflectionsGain);
721 this->alEffectf( efx_effect_id, AL_REVERB_REFLECTIONS_DELAY, current_efx_properties_of_slot[slot_id].flReflectionsDelay);
722 this->alEffectf( efx_effect_id, AL_REVERB_LATE_REVERB_GAIN, current_efx_properties_of_slot[slot_id].flLateReverbGain);
723 this->alEffectf( efx_effect_id, AL_REVERB_LATE_REVERB_DELAY, current_efx_properties_of_slot[slot_id].flLateReverbDelay);
724 this->alEffectf( efx_effect_id, AL_REVERB_AIR_ABSORPTION_GAINHF, current_efx_properties_of_slot[slot_id].flAirAbsorptionGainHF);
725 this->alEffectf( efx_effect_id, AL_REVERB_ROOM_ROLLOFF_FACTOR, current_efx_properties_of_slot[slot_id].flRoomRolloffFactor);
726 this->alEffectf( efx_effect_id, AL_REVERB_DECAY_HFLIMIT, current_efx_properties_of_slot[slot_id].iDecayHFLimit);
727 break;
728
730 this->alAuxiliaryEffectSloti(slot_id, AL_EFFECTSLOT_EFFECT, AL_EFFECTSLOT_NULL);
731 return;
732 }
733
734 // make the slot use the updated AL effect
735 this->alAuxiliaryEffectSloti(slot_id, AL_EFFECTSLOT_EFFECT, efx_effect_id);
736}
737
738std::tuple<Ogre::Vector3, float, float> SoundManager::ComputeEarlyReflectionsProperties() const
739{
740 const float max_distance = 2.0f;
741 const float reflections_gain_boost_max = 2.0f; // 6.32 dB
742 float early_reflections_gain;
743 float early_reflections_delay;
744 float magnitude = 0;
745 Ogre::Vector3 early_reflections_pan = { 0.0f, 0.0f, 0.0f};
746
747 /*
748 * To detect surfaces around the listener within the vicinity of
749 * max_distance, we cast rays counter-clockwise in a 360° circle
750 * around the listener on a horizontal plane realative to the listener.
751 */
752 bool nearby_surface_detected = false;
753 const float angle_step_size = 90;
754 float closest_surface_distance = std::numeric_limits<float>::max();
755
756 for (float angle = 0; angle < 360; angle += angle_step_size)
757 {
758 float closest_surface_distance_in_this_direction = std::numeric_limits<float>::max();
759 Ogre::Vector3 raycast_direction = Quaternion(Ogre::Degree(angle), m_listener_up) * m_listener_direction;
760 raycast_direction.normalise();
761 // accompany direction vector for how the intersectsTris function works
762
763 // check for nearby collision meshes
764 Ray ray = Ray(m_listener_position, raycast_direction * max_distance * App::GetGameContext()->GetTerrain()->GetCollisions()->GetCellSize());
765 std::pair<bool, Ogre::Real> intersection = App::GetGameContext()->GetTerrain()->GetCollisions()->intersectsTris(ray);
766
767 if (intersection.first)
768 {
769 closest_surface_distance_in_this_direction = intersection.second * max_distance;
770 }
771
772 ray.setDirection(ray.getDirection().normalisedCopy());
773
774 // check for nearby collision boxes
776 {
777 if (!collision_box.enabled || collision_box.virt) { continue; }
778 intersection = ray.intersects(Ogre::AxisAlignedBox(collision_box.lo, collision_box.hi));
779 if (intersection.first && intersection.second <= max_distance)
780 {
781 closest_surface_distance_in_this_direction = std::min(closest_surface_distance_in_this_direction, intersection.second);
782 }
783 }
784
785 // check for nearby actors
787 for (const ActorPtr& actor : actors)
788 {
789 // ignore own truck if player is driving one
790 if (actor == App::GetGameContext()->GetPlayerCharacter()->GetActorCoupling()) { continue; }
791
792 intersection = ray.intersects(actor->ar_bounding_box);
793 if (intersection.first && intersection.second <= max_distance)
794 {
795 closest_surface_distance_in_this_direction = std::min(closest_surface_distance_in_this_direction, intersection.second);
796 }
797 }
798
799 closest_surface_distance = std::min(closest_surface_distance, closest_surface_distance_in_this_direction);
800
801 if (closest_surface_distance_in_this_direction <= max_distance)
802 {
803 early_reflections_pan += raycast_direction * (max_distance - closest_surface_distance_in_this_direction);
804 }
805 }
806
807 nearby_surface_detected = closest_surface_distance <= max_distance;
808
809 // TODO vertical raycasts
810
811 if (!nearby_surface_detected)
812 {
813 // reset values to the original values of the preset
814 early_reflections_delay = m_listener_efx_reverb_properties->flReflectionsDelay;
815 early_reflections_gain = m_listener_efx_reverb_properties->flReflectionsGain;
816 }
817 else // at least one nearby surface was detected
818 {
819 // we assume that surfaces further away cause less focussed reflections
820 magnitude = 1.0f - early_reflections_pan.length() / Ogre::Math::Sqrt(2.0f * Ogre::Math::Pow(max_distance, 2));
821
822 // set delay based on distance to the closest surface
823 early_reflections_delay = closest_surface_distance / this->GetSpeedOfSound();
824
825 early_reflections_gain = std::min(
826 (m_listener_efx_reverb_properties->flReflectionsGain
827 + reflections_gain_boost_max
828 - (reflections_gain_boost_max * (magnitude))),
829 AL_EAXREVERB_MAX_REFLECTIONS_GAIN);
830 }
831
832 // transform the pan vector from being listener-relative to being user-relative
833
834 // determine the rotation of the listener direction from straight-ahead vector
835 // work around Quaternion quirks at around 180° rotation
836 Ogre::Quaternion horizontal_rotation;
837 if (m_listener_direction.z > 0.0f)
838 {
839 horizontal_rotation = Quaternion(Ogre::Degree(180), m_listener_up) * m_listener_direction.getRotationTo(Ogre::Vector3::UNIT_Z);
840 }
841 else
842 {
843 horizontal_rotation = m_listener_direction.getRotationTo(Ogre::Vector3::NEGATIVE_UNIT_Z);
844 }
845
846 early_reflections_pan = horizontal_rotation * early_reflections_pan;
847 early_reflections_pan.normalise();
848
849 early_reflections_pan = magnitude * early_reflections_pan;
850
851 return std::make_tuple(early_reflections_pan, early_reflections_gain, early_reflections_delay);
852}
853
854ALuint SoundManager::CreateAlEffect(const EFXEAXREVERBPROPERTIES* efx_properties) const
855{
856 ALuint effect = 0;
857 ALenum error;
858
859 this->alGenEffects(1, &effect);
860
861 switch (m_efx_reverb_engine)
862 {
864 this->alEffecti( effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
865
866 this->alEffectf( effect, AL_EAXREVERB_DENSITY, efx_properties->flDensity);
867 this->alEffectf( effect, AL_EAXREVERB_DIFFUSION, efx_properties->flDiffusion);
868 this->alEffectf( effect, AL_EAXREVERB_GAIN, efx_properties->flGain);
869 this->alEffectf( effect, AL_EAXREVERB_GAINHF, efx_properties->flGainHF);
870 this->alEffectf( effect, AL_EAXREVERB_GAINLF, efx_properties->flGainLF);
871 this->alEffectf( effect, AL_EAXREVERB_DECAY_TIME, efx_properties->flDecayTime);
872 this->alEffectf( effect, AL_EAXREVERB_DECAY_HFRATIO, efx_properties->flDecayHFRatio);
873 this->alEffectf( effect, AL_EAXREVERB_DECAY_LFRATIO, efx_properties->flDecayLFRatio);
874 this->alEffectf( effect, AL_EAXREVERB_REFLECTIONS_GAIN, efx_properties->flReflectionsGain);
875 this->alEffectf( effect, AL_EAXREVERB_REFLECTIONS_DELAY, efx_properties->flReflectionsDelay);
876 this->alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, efx_properties->flReflectionsPan);
877 this->alEffectf( effect, AL_EAXREVERB_LATE_REVERB_GAIN, efx_properties->flLateReverbGain);
878 this->alEffectf( effect, AL_EAXREVERB_LATE_REVERB_DELAY, efx_properties->flLateReverbDelay);
879 this->alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, efx_properties->flLateReverbPan);
880 this->alEffectf( effect, AL_EAXREVERB_ECHO_TIME, efx_properties->flEchoTime);
881 this->alEffectf( effect, AL_EAXREVERB_ECHO_DEPTH, efx_properties->flEchoDepth);
882 this->alEffectf( effect, AL_EAXREVERB_MODULATION_TIME, efx_properties->flModulationTime);
883 this->alEffectf( effect, AL_EAXREVERB_MODULATION_DEPTH, efx_properties->flModulationDepth);
884 this->alEffectf( effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, efx_properties->flAirAbsorptionGainHF);
885 this->alEffectf( effect, AL_EAXREVERB_HFREFERENCE, efx_properties->flHFReference);
886 this->alEffectf( effect, AL_EAXREVERB_LFREFERENCE, efx_properties->flLFReference);
887 this->alEffectf( effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, efx_properties->flRoomRolloffFactor);
888 this->alEffecti( effect, AL_EAXREVERB_DECAY_HFLIMIT, efx_properties->iDecayHFLimit);
889
890 break;
892 this->alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
893
894 this->alEffectf(effect, AL_REVERB_DENSITY, efx_properties->flDensity);
895 this->alEffectf(effect, AL_REVERB_DIFFUSION, efx_properties->flDiffusion);
896 this->alEffectf(effect, AL_REVERB_GAIN, efx_properties->flGain);
897 this->alEffectf(effect, AL_REVERB_GAINHF, efx_properties->flGainHF);
898 this->alEffectf(effect, AL_REVERB_DECAY_TIME, efx_properties->flDecayTime);
899 this->alEffectf(effect, AL_REVERB_DECAY_HFRATIO, efx_properties->flDecayHFRatio);
900 this->alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, efx_properties->flReflectionsGain);
901 this->alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, efx_properties->flReflectionsDelay);
902 this->alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, efx_properties->flLateReverbGain);
903 this->alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, efx_properties->flLateReverbDelay);
904 this->alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, efx_properties->flAirAbsorptionGainHF);
905 this->alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, efx_properties->flRoomRolloffFactor);
906 this->alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, efx_properties->iDecayHFLimit);
907
908 break;
910 default:
911 LOG("SoundManager: No usable reverb engine set, not creating reverb effect");
912 }
913
914 error = alGetError();
915 if (error != AL_NO_ERROR)
916 {
917 LOG("SoundManager: Could not create EFX effect:" + TOSTRING(alGetString(error)));
918
919 if (this->alIsEffect(effect))
920 this->alDeleteEffects(1, &effect);
921 return 0;
922 }
923
924 return effect;
925}
926
927void SoundManager::DeleteAlEffect(const ALuint efx_effect_id) const
928{
929 ALenum error;
930 alGetError();
931
932 this->alDeleteEffects(1, &efx_effect_id);
933
934 error = alGetError();
935 if (error != AL_NO_ERROR)
936 {
937 LOG("SoundManager: Could not delete EFX effect: " + TOSTRING(alGetString(error)));
938 }
939}
940
941bool compareByAudibility(std::pair<int, float> a, std::pair<int, float> b)
942{
943 return a.second > b.second;
944}
945
946// called when the camera moves
948{
949 // Creates this issue: https://github.com/RigsOfRods/rigs-of-rods/issues/1054
950#if 0
951 if (!audio_device) return;
952
953 for (int i=0; i < m_audio_sources_in_use_count; i++)
954 {
956 audio_sources_most_audible[i].first = i;
958 }
959 // sort first 'num_hardware_sources' sources by audibility
960 // see: https://en.wikipedia.org/wiki/Selection_algorithm
962 {
964 }
965 // retire out of range sources first
966 for (int i=0; i < m_audio_sources_in_use_count; i++)
967 {
968 if (audio_sources[audio_sources_most_audible[i].first]->hardware_index != -1 && (i >= hardware_sources_num || audio_sources_most_audible[i].second == 0))
970 }
971 // assign new sources
972 for (int i=0; i < std::min(m_audio_sources_in_use_count, hardware_sources_num); i++)
973 {
974 if (audio_sources[audio_sources_most_audible[i].first]->hardware_index == -1 && audio_sources_most_audible[i].second > 0)
975 {
976 for (int j=0; j < hardware_sources_num; j++)
977 {
978 if (hardware_sources_map[j] == -1)
979 {
981 break;
982 }
983 }
984 }
985 }
986#endif
987}
988
989void SoundManager::UpdateSourceFilters(const int hardware_index) const
990{
991 bool source_is_obstructed = this->IsHardwareSourceObstructed(hardware_index);
992
993 this->UpdateObstructionFilter(hardware_index, source_is_obstructed);
994
995 /*
996 * If an obstruction was detected, also check for occlusions.
997 * The current implementation ignores the case of exclusion since
998 * it compares the reverb environments of the source and listener.
999 * This would not be enough to reliably detect exclusion in outdoor environments.
1000 */
1001 if (source_is_obstructed)
1002 {
1004 }
1005 else
1006 {
1007 // disable occlusion filter just in case it was previously active
1008 alSource3i(hardware_sources[hardware_index], AL_AUXILIARY_SEND_FILTER, m_listener_slot, m_efx_occlusion_wet_path_send_id, AL_FILTER_NULL);
1009 }
1010}
1011
1012bool SoundManager::IsHardwareSourceObstructed(const int hardware_index) const
1013{
1014 if ( hardware_sources_map[hardware_index] == -1 // no sound assigned to hardware source
1015 || App::app_state->getEnum<AppState>() != AppState::SIMULATION) // this is necessary to prevent a crash with enabled main menu music
1016 { return false; }
1017
1018 /*
1019 * There is no simple way to know whether a truck has a closed cabin or not; hence
1020 * provide an option to always force obstruction if the player is inside a vehicle.
1021 */
1022 if
1023 (
1025 && App::GetGameContext()->GetPlayerCharacter()->GetActorCoupling() != nullptr
1026 && App::GetGameContext()->GetPlayerCharacter()->GetActorCoupling()->ar_bounding_box.contains(m_listener_position)
1027 )
1028 {
1029 return true;
1030 }
1031
1032 /*
1033 * Perform various line of sight checks until either a collision was detected
1034 * and the filter has to be applied or no obstruction was detected.
1035 */
1036
1037 std::pair<bool, Ogre::Real> intersection;
1038 const SoundPtr& corresponding_sound = audio_sources[hardware_sources_map[hardware_index]];
1039 const Ogre::Vector3 direction_to_sound = corresponding_sound->getPosition() - m_listener_position;
1040 const Ogre::Ray direct_path_to_sound = Ray(m_listener_position, direction_to_sound);
1041 bool obstruction_detected = false;
1042
1043 // perform line of sight check against collision meshes
1044 // for this to work correctly, the direction vector of the ray must have
1045 // the length of the distance from the listener to the sound
1046 intersection = App::GetGameContext()->GetTerrain()->GetCollisions()->intersectsTris(direct_path_to_sound);
1047 obstruction_detected = intersection.first;
1048
1049 if (!obstruction_detected)
1050 {
1051 // perform line of sight check agains collision boxes
1052 for (const collision_box_t& collision_box : App::GetGameContext()->GetTerrain()->GetCollisions()->getCollisionBoxes())
1053 {
1054 if (!collision_box.enabled || collision_box.virt) { continue; }
1055
1056 Ogre::AxisAlignedBox collision_box_aab = Ogre::AxisAlignedBox(collision_box.lo, collision_box.hi);
1057
1058 // Skip cases where the obstruction filter detection becomes unstable
1059 if ( collision_box_aab.contains(corresponding_sound->getPosition())
1060 || collision_box_aab.distance(corresponding_sound->getPosition()) < 0.1f)
1061 {
1062 continue;
1063 }
1064
1065 intersection = direct_path_to_sound.intersects(collision_box_aab);
1066 obstruction_detected = intersection.first && intersection.second <= 1.0f;
1067 if (obstruction_detected)
1068 {
1069 break;
1070 }
1071 }
1072 }
1073
1074 if (!obstruction_detected)
1075 {
1076 // perform line of sight check against actors
1078 bool soundsource_belongs_to_current_actor = false;
1079 for (const ActorPtr& actor : actors)
1080 {
1081 // Trucks shouldn't obstruct their own sound sources since the
1082 // obstruction is most likely already contained in the recording.
1083 for (int soundsource_index = 0; soundsource_index < actor->ar_num_soundsources; ++soundsource_index)
1084 {
1085 const soundsource_t& soundsource = actor->ar_soundsources[soundsource_index];
1086 const int num_sounds = soundsource.ssi->getTemplate()->getNumSounds();
1087 for (int num_sound = 0; num_sound < num_sounds; ++num_sound)
1088 {
1089 if (soundsource.ssi->getSound(num_sound) == corresponding_sound)
1090 {
1091 soundsource_belongs_to_current_actor = true;
1092 }
1093 }
1094 if (soundsource_belongs_to_current_actor) { break; }
1095 }
1096
1097 if (soundsource_belongs_to_current_actor)
1098 {
1099 continue;
1100 }
1101
1102 intersection = direct_path_to_sound.intersects(actor->ar_bounding_box);
1103 obstruction_detected = intersection.first && intersection.second <= 1.0f;
1104 if (obstruction_detected)
1105 {
1106 break;
1107 }
1108 }
1109 }
1110
1111 if (!obstruction_detected)
1112 {
1113 // perform line of sight check against terrain
1114 intersection = App::GetGameContext()->GetTerrain()->GetCollisions()->intersectsTerrain(direct_path_to_sound);
1115 obstruction_detected = intersection.first;
1116 }
1117
1118 return obstruction_detected;
1119}
1120
1121void SoundManager::UpdateObstructionFilter(const int hardware_index, const bool enable_obstruction_filter) const
1122{
1123 if (!App::audio_enable_obstruction->getBool() || !enable_obstruction_filter)
1124 {
1125 // detach the obstruction filter in case it was attached when the feature was previously enabled
1126 alSourcei(hardware_sources[hardware_index], AL_DIRECT_FILTER, AL_FILTER_NULL);
1127 }
1128 else {
1129 // Apply obstruction filter to the source
1130 alSourcei(hardware_sources[hardware_index], AL_DIRECT_FILTER, m_efx_outdoor_obstruction_lowpass_filter_id);
1131 }
1132}
1133
1134bool SoundManager::UpdateOcclusionFilter(const int hardware_index, const ALuint effect_slot_id, const EFXEAXREVERBPROPERTIES* reference_efx_reverb_properties) const
1135{
1136 if (hardware_sources_map[hardware_index] == -1) { return false; } // no sound assigned to hardware source
1137
1138 if (!App::audio_enable_occlusion->getBool())
1139 {
1140 // detach the occlusion filter in case it was attached when the feature was previously enabled
1141 alSource3i(hardware_sources[hardware_index], AL_AUXILIARY_SEND_FILTER, effect_slot_id, m_efx_occlusion_wet_path_send_id, AL_FILTER_NULL);
1142 return false;
1143 }
1144
1145 const SoundPtr& corresponding_sound = audio_sources[hardware_sources_map[hardware_index]];
1146 bool occlusion_detected = this->GetReverbPresetAt(corresponding_sound->getPosition()) != reference_efx_reverb_properties;
1147
1148 if (occlusion_detected)
1149 {
1150 alSource3i(hardware_sources[hardware_index], AL_AUXILIARY_SEND_FILTER, effect_slot_id, m_efx_occlusion_wet_path_send_id, m_efx_occlusion_wet_path_lowpass_filter_id);
1151 }
1152 else
1153 {
1154 alSource3i(hardware_sources[hardware_index], AL_AUXILIARY_SEND_FILTER, effect_slot_id, m_efx_occlusion_wet_path_send_id, AL_FILTER_NULL);
1155 }
1156
1157 return occlusion_detected;
1158}
1159
1161{
1162 for (int hardware_index = 0; hardware_index < hardware_sources_num; hardware_index++)
1163 {
1164 if (hardware_sources_map[hardware_index] == -1) { continue;; } // no sound assigned to hardware source at this index
1165
1166 const SoundPtr& corresponding_sound = audio_sources[hardware_sources_map[hardware_index]];
1168
1169 for (const ActorPtr& actor : actors)
1170 {
1171 NodeNum_t sound_node = RoR::NODENUM_INVALID;
1172
1173 // check if the sound corresponding to this hardware source belongs to the actor
1174 for (int soundsource_index = 0; soundsource_index < actor->ar_num_soundsources; ++soundsource_index)
1175 {
1176 const soundsource_t& soundsource = actor->ar_soundsources[soundsource_index];
1177 const int num_sounds = soundsource.ssi->getTemplate()->getNumSounds();
1178 for (int num_sound = 0; num_sound < num_sounds; ++num_sound)
1179 {
1180 if (soundsource.ssi->getSound(num_sound) == corresponding_sound)
1181 {
1182 sound_node = soundsource.nodenum;
1183 break;
1184 }
1185 }
1186 }
1187
1188 if ( sound_node == RoR::NODENUM_INVALID // if the sound does not belong to a node of the current actor, there is no need for further checks
1189 || sound_node == 0) // node 0 might have several default (and undirected) sounds assigned, so skip it
1190 {
1191 continue;
1192 }
1193
1194 // Update directivity if the sound corresponding to the hardware source is attached to an exhaust node of the actor
1195 const std::vector<Exhaust>& exhausts = actor->GetGfxActor()->getExhausts();
1196 for (const Exhaust& exhaust : exhausts)
1197 {
1198 if ( sound_node == exhaust.emitterNode
1199 || sound_node == exhaust.directionNode)
1200 {
1201 const Ogre::Vector3 emitter_node_pos = actor->getNodePosition(exhaust.emitterNode);
1202 const Ogre::Vector3 direction_node_pos = actor->getNodePosition(exhaust.directionNode);
1203
1205 hardware_sources[hardware_index],
1206 emitter_node_pos - direction_node_pos,
1207 60.0f,
1208 170.0f,
1209 0.85f,
1210 0.80f);
1211
1212 break;
1213 }
1214 }
1215
1216 switch(actor->getTruckType())
1217 {
1219 // Update directivity if the sound corresponding to the hardware source is attached to an AeroEngine
1220 for (int engine_num = 0; engine_num < actor->ar_num_aeroengines; ++engine_num)
1221 {
1222 const auto& aero_engine = actor->ar_aeroengines[engine_num];
1223
1224 switch(aero_engine->getType())
1225 {
1227 if ( sound_node == aero_engine->getNoderef()
1228 || sound_node == aero_engine->GetBackNode())
1229 {
1230 const Ogre::Vector3 aero_engine_ref_node = actor->getNodePosition(aero_engine->getNoderef());
1231 const Ogre::Vector3 aero_engine_back_node = actor->getNodePosition(aero_engine->GetBackNode());
1232
1234 hardware_sources[hardware_index],
1235 aero_engine_ref_node - aero_engine_back_node,
1236 170.0f,
1237 270.0f,
1238 0.85f,
1239 0.70f);
1240 }
1241
1242 break;
1243
1245 /*
1246 * Since turbojets currently have no high-pitched noise sounds
1247 * for the air intake, we currently assume all sounds are
1248 * directed rearwards of the engine.
1249 * Should air intake noises be added, a front-directed cone should
1250 * be set for them with significant high-frequency dropoff outside.
1251 */
1252
1253 if ( sound_node == aero_engine->getNoderef()
1254 || sound_node == aero_engine->GetFrontNode())
1255 {
1256 const Ogre::Vector3 aero_engine_ref_node = actor->getNodePosition(aero_engine->getNoderef());
1257 const Ogre::Vector3 aero_engine_front_node = actor->getNodePosition(aero_engine->GetFrontNode());
1258
1260 hardware_sources[hardware_index],
1261 aero_engine_ref_node - aero_engine_front_node,
1262 60.0f,
1263 240.0f,
1264 0.60f,
1265 0.60f);
1266 }
1267
1268 break;
1269
1270 default: continue;
1271 }
1272 }
1273 break;
1274
1275 case ActorType::BOAT:
1276 // Update directivity if the sound corresponding to the hardware source is attached to a Screwprop
1277 for (int screwprop_num = 0; screwprop_num < actor->ar_num_screwprops; ++screwprop_num)
1278 {
1279 const auto& screwprop = actor->ar_screwprops[screwprop_num];
1280
1281 if ( sound_node == screwprop->GetRefNode()
1282 || sound_node == screwprop->GetBackNode())
1283 {
1284 const Ogre::Vector3 screwprop_ref_node = actor->getNodePosition(screwprop->GetRefNode());
1285 const Ogre::Vector3 screwprop_back_node = actor->getNodePosition(screwprop->GetBackNode());
1286
1288 hardware_sources[hardware_index],
1289 screwprop_ref_node - screwprop_back_node,
1290 70.0f,
1291 170.0f,
1292 0.80f,
1293 0.70f);
1294
1295 break;
1296 }
1297 }
1298 break;
1299
1300 default: continue;
1301 }
1302 }
1303 }
1304}
1305
1307 const ALuint source,
1308 const Ogre::Vector3& cone_direction,
1309 const float cone_inner_angle,
1310 const float cone_outer_angle,
1311 const float cone_outer_gain,
1312 const float cone_outer_gain_hf
1313 ) const
1314{
1315 alSource3f(source, AL_DIRECTION, cone_direction.x, cone_direction.y, cone_direction.z);
1316
1317 alSourcef (source, AL_CONE_INNER_ANGLE, cone_inner_angle);
1318 alSourcef (source, AL_CONE_OUTER_ANGLE, cone_outer_angle);
1319 alSourcef (source, AL_CONE_OUTER_GAIN, cone_outer_gain);
1320
1321 if (App::audio_enable_efx->getBool())
1322 {
1323 alSourcef(source, AL_CONE_OUTER_GAINHF, cone_outer_gain_hf);
1324 }
1325}
1326
1327void SoundManager::recomputeSource(int source_index, int reason, float vfl, Vector3* vvec)
1328{
1329 if (!audio_device)
1330 return;
1332
1333 if (audio_sources[source_index]->audibility == 0.0f)
1334 {
1335 if (audio_sources[source_index]->hardware_index != -1)
1336 {
1337 // retire the source if it is currently assigned
1338 retire(source_index);
1339 }
1340 }
1341 else
1342 {
1343 // this is a potentially audible m_audio_sources[source_index]
1344 if (audio_sources[source_index]->hardware_index != -1)
1345 {
1346 ALuint hw_source = hardware_sources[audio_sources[source_index]->hardware_index];
1347 // m_audio_sources[source_index] already playing
1348 // update the AL settings
1349 switch (reason)
1350 {
1351 case Sound::REASON_PLAY:
1352 this->UpdateSourceFilters(audio_sources[source_index]->hardware_index);
1353 alSourcePlay(hw_source);
1354 break;
1355 case Sound::REASON_STOP: alSourceStop(hw_source);
1356 break;
1357 case Sound::REASON_GAIN: alSourcef(hw_source, AL_GAIN, vfl * App::audio_master_volume->getFloat());
1358 break;
1359 case Sound::REASON_LOOP: alSourcei(hw_source, AL_LOOPING, (vfl > 0.5) ? AL_TRUE : AL_FALSE);
1360 break;
1361 case Sound::REASON_PTCH: alSourcef(hw_source, AL_PITCH, vfl);
1362 break;
1363 case Sound::REASON_POSN: alSource3f(hw_source, AL_POSITION, vvec->x, vvec->y, vvec->z);
1364 break;
1365 case Sound::REASON_VLCT: alSource3f(hw_source, AL_VELOCITY, vvec->x, vvec->y, vvec->z);
1366 break;
1367 default: break;
1368 }
1369 }
1370 else
1371 {
1372 // try to make it play by the hardware
1373 // check if there is one free m_audio_sources[source_index] in the pool
1375 {
1376 for (int i = 0; i < hardware_sources_num; i++)
1377 {
1378 if (hardware_sources_map[i] == -1)
1379 {
1380 assign(source_index, i);
1381 break;
1382 }
1383 }
1384 }
1385 else
1386 {
1387 // now, compute who is the faintest
1388 // note: we know the table m_hardware_sources_map is full!
1389 float fv = 1.0f;
1390 int al_faintest = 0;
1391 for (int i = 0; i < hardware_sources_num; i++)
1392 {
1393 if (hardware_sources_map[i] >= 0 && audio_sources[hardware_sources_map[i]]->audibility < fv)
1394 {
1396 al_faintest = i;
1397 }
1398 }
1399 // check to ensure that the sound is louder than the faintest sound currently playing
1400 if (fv < audio_sources[source_index]->audibility)
1401 {
1402 // this new m_audio_sources[source_index] is louder than the faintest!
1403 retire(hardware_sources_map[al_faintest]);
1404 assign(source_index, al_faintest);
1405 }
1406 // else this m_audio_sources[source_index] is too faint, we don't play it!
1407 }
1408 }
1409 }
1410}
1411
1412void SoundManager::assign(int source_index, int hardware_index)
1413{
1414 if (!audio_device)
1415 return;
1416 audio_sources[source_index]->hardware_index = hardware_index;
1417 hardware_sources_map[hardware_index] = source_index;
1418
1419 ALuint hw_source = hardware_sources[hardware_index];
1420 SoundPtr& audio_source = audio_sources[source_index];
1421
1422 // the hardware source is supposed to be stopped!
1423 alSourcei(hw_source, AL_BUFFER, audio_source->buffer);
1424 alSourcef(hw_source, AL_GAIN, audio_source->gain * App::audio_master_volume->getFloat());
1425 alSourcei(hw_source, AL_LOOPING, (audio_source->loop) ? AL_TRUE : AL_FALSE);
1426 alSourcef(hw_source, AL_PITCH, audio_source->pitch);
1427 alSource3f(hw_source, AL_POSITION, audio_source->position.x, audio_source->position.y, audio_source->position.z);
1428 alSource3f(hw_source, AL_VELOCITY, audio_source->velocity.x, audio_source->velocity.y, audio_source->velocity.z);
1429
1430 if (audio_source->should_play)
1431 {
1432 this->UpdateSourceFilters(audio_sources[source_index]->hardware_index);
1433 alSourcePlay(hw_source);
1434 }
1435
1437}
1438
1439void SoundManager::retire(int source_index)
1440{
1441 if (!audio_device)
1442 return;
1443 if (audio_sources[source_index]->hardware_index == -1)
1444 return;
1445 alSourceStop(hardware_sources[audio_sources[source_index]->hardware_index]);
1447 audio_sources[source_index]->hardware_index = -1;
1449}
1450
1452{
1453 if (!audio_device)
1454 return;
1455 // no mutex needed
1456 alListenerf(AL_GAIN, 0.0f);
1457}
1458
1460{
1461 if (!audio_device)
1462 return;
1463 // no mutex needed
1464 alListenerf(AL_GAIN, App::audio_master_volume->getFloat());
1465}
1466
1468{
1469 if (!audio_device)
1470 return;
1471 // no mutex needed
1472 App::audio_master_volume->setVal(v); // TODO: Use 'pending' mechanism and set externally, only 'apply' here.
1473 alListenerf(AL_GAIN, v);
1474}
1475
1476SoundPtr SoundManager::createSound(String filename, Ogre::String resource_group_name /* = "" */)
1477{
1478 if (!audio_device)
1479 return NULL;
1480
1482 {
1483 LOG("SoundManager: Reached MAX_AUDIO_BUFFERS limit (" + TOSTRING(MAX_AUDIO_BUFFERS) + ")");
1484 return NULL;
1485 }
1486
1487 ALuint buffer = 0;
1488
1489 // is the file already loaded?
1490 for (int i = 0; i < audio_buffers_in_use_count; i++)
1491 {
1492 if (filename == audio_buffer_file_name[i])
1493 {
1494 buffer = audio_buffers[i];
1495 break;
1496 }
1497 }
1498
1499 if (!buffer)
1500 {
1501 // load the file
1502 alGenBuffers(1, &audio_buffers[audio_buffers_in_use_count]);
1503 if (loadWAVFile(filename, audio_buffers[audio_buffers_in_use_count], resource_group_name))
1504 {
1505 // there was an error!
1506 alDeleteBuffers(1, &audio_buffers[audio_buffers_in_use_count]);
1508 return NULL;
1509 }
1513 }
1514
1516
1518}
1519
1520bool SoundManager::loadWAVFile(String filename, ALuint buffer, Ogre::String resource_group_name /*= ""*/)
1521{
1522 if (!audio_device)
1523 return true;
1524 LOG("Loading WAV file "+filename);
1525
1526 // create the Stream
1527 ResourceGroupManager* rgm = ResourceGroupManager::getSingletonPtr();
1528 if (resource_group_name == "")
1529 {
1530 resource_group_name = rgm->findGroupContainingResource(filename);
1531 }
1532 DataStreamPtr stream = rgm->openResource(filename, resource_group_name);
1533
1534 // load RIFF/WAVE
1535 char magic[5];
1536 magic[4] = 0;
1537 unsigned int lbuf; // uint32_t
1538 unsigned short sbuf; // uint16_t
1539
1540 // check magic
1541 if (stream->read(magic, 4) != 4)
1542 {
1543 LOG("Could not read file "+filename);
1544 return true;
1545 }
1546 if (String(magic) != String("RIFF"))
1547 {
1548 LOG("Invalid WAV file (no RIFF): "+filename);
1549 return true;
1550 }
1551 // skip 4 bytes (magic)
1552 stream->skip(4);
1553 // check file format
1554 if (stream->read(magic, 4) != 4)
1555 {
1556 LOG("Could not read file "+filename);
1557 return true;
1558 }
1559 if (String(magic) != String("WAVE"))
1560 {
1561 LOG("Invalid WAV file (no WAVE): "+filename);
1562 return true;
1563 }
1564 // check 'fmt ' sub chunk (1)
1565 if (stream->read(magic, 4) != 4)
1566 {
1567 LOG("Could not read file "+filename);
1568 return true;
1569 }
1570 if (String(magic) != String("fmt "))
1571 {
1572 LOG("Invalid WAV file (no fmt): "+filename);
1573 return true;
1574 }
1575 // read (1)'s size
1576 if (stream->read(&lbuf, 4) != 4)
1577 {
1578 LOG("Could not read file "+filename);
1579 return true;
1580 }
1581 unsigned long subChunk1Size = lbuf;
1582 if (subChunk1Size < 16)
1583 {
1584 LOG("Invalid WAV file (invalid subChunk1Size): "+filename);
1585 return true;
1586 }
1587 // check PCM audio format
1588 if (stream->read(&sbuf, 2) != 2)
1589 {
1590 LOG("Could not read file "+filename);
1591 return true;
1592 }
1593 unsigned short audioFormat = sbuf;
1594 if (audioFormat != 1)
1595 {
1596 LOG("Invalid WAV file (invalid audioformat "+TOSTRING(audioFormat)+"): "+filename);
1597 return true;
1598 }
1599 // read number of channels
1600 if (stream->read(&sbuf, 2) != 2)
1601 {
1602 LOG("Could not read file "+filename);
1603 return true;
1604 }
1605 unsigned short channels = sbuf;
1606 // read frequency (sample rate)
1607 if (stream->read(&lbuf, 4) != 4)
1608 {
1609 LOG("Could not read file "+filename);
1610 return true;
1611 }
1612 unsigned long freq = lbuf;
1613 // skip 6 bytes (Byte rate (4), Block align (2))
1614 stream->skip(6);
1615 // read bits per sample
1616 if (stream->read(&sbuf, 2) != 2)
1617 {
1618 LOG("Could not read file "+filename);
1619 return true;
1620 }
1621 unsigned short bps = sbuf;
1622 // check 'data' sub chunk (2)
1623 if (stream->read(magic, 4) != 4)
1624 {
1625 LOG("Could not read file "+filename);
1626 return true;
1627 }
1628 if (String(magic) != String("data") && String(magic) != String("fact"))
1629 {
1630 LOG("Invalid WAV file (no data/fact): "+filename);
1631 return true;
1632 }
1633 // fact is an option section we don't need to worry about
1634 if (String(magic) == String("fact"))
1635 {
1636 stream->skip(8);
1637 // now we should hit the data chunk
1638 if (stream->read(magic, 4) != 4)
1639 {
1640 LOG("Could not read file "+filename);
1641 return true;
1642 }
1643 if (String(magic) != String("data"))
1644 {
1645 LOG("Invalid WAV file (no data): "+filename);
1646 return true;
1647 }
1648 }
1649 // the next four bytes are the remaining size of the file
1650 if (stream->read(&lbuf, 4) != 4)
1651 {
1652 LOG("Could not read file "+filename);
1653 return true;
1654 }
1655
1656 unsigned long dataSize = lbuf;
1657 int format = 0;
1658
1659 if (channels == 1 && bps == 8)
1660 format = AL_FORMAT_MONO8;
1661 else if (channels == 1 && bps == 16)
1662 format = AL_FORMAT_MONO16;
1663 else if (channels == 2 && bps == 8)
1664 format = AL_FORMAT_STEREO16;
1665 else if (channels == 2 && bps == 16)
1666 format = AL_FORMAT_STEREO16;
1667 else
1668 {
1669 LOG("Invalid WAV file (wrong channels/bps): "+filename);
1670 return true;
1671 }
1672
1673 if (channels != 1) LOG("Invalid WAV file: the file needs to be mono, and nothing else. Will try to continue anyways ...");
1674
1675 // ok, creating buffer
1676 void* bdata = malloc(dataSize);
1677 if (!bdata)
1678 {
1679 LOG("Memory error reading file "+filename);
1680 return true;
1681 }
1682 if (stream->read(bdata, dataSize) != dataSize)
1683 {
1684 LOG("Could not read file "+filename);
1685 free(bdata);
1686 return true;
1687 }
1688
1689 //LOG("alBufferData: format "+TOSTRING(format)+" size "+TOSTRING(dataSize)+" freq "+TOSTRING(freq));
1690 alGetError(); // Reset errors
1691 ALint error;
1692 alBufferData(buffer, format, bdata, dataSize, freq);
1693 error = alGetError();
1694
1695 free(bdata);
1696 // stream will be closed by itself
1697
1698 if (error != AL_NO_ERROR)
1699 {
1700 LOG("OpenAL error while loading buffer for "+filename+" : "+TOSTRING(error));
1701 return true;
1702 }
1703
1704 return false;
1705}
1706
1707#endif // USE_OPENAL
Central state/object manager and communications hub.
#define TOSTRING(x)
Definition Application.h:57
void LOG(const char *msg)
Legacy alias - formerly a macro.
bool compareByAudibility(std::pair< int, float > a, std::pair< int, float > b)
bool _checkALErrors(const char *filename, int linenum)
#define hasALErrors()
#define LOGSTREAM
ActorPtrVec & GetActors()
float getFloat() const
Definition CVar.h:96
std::string const & getStr() const
Definition CVar.h:95
bool getBool() const
Definition CVar.h:98
void setStr(std::string const &str)
Definition CVar.h:83
void setVal(T val)
Definition CVar.h:72
ActorPtr GetActorCoupling()
std::pair< bool, Ogre::Real > intersectsTris(Ogre::Ray ray)
std::pair< bool, Ogre::Real > intersectsTerrain(Ogre::Ray ray)
Checks whether a Ray intersects the terrain.
CollisionBoxVec const & getCollisionBoxes() const
Definition Collisions.h:226
Character * GetPlayerCharacter()
const TerrainPtr & GetTerrain()
ActorManager * GetActorManager()
Ogre::Vector3 position
Definition Sound.h:93
void computeAudibility(Ogre::Vector3 pos)
Definition Sound.cpp:45
@ REASON_POSN
Definition Sound.h:75
@ REASON_STOP
Definition Sound.h:71
@ REASON_LOOP
Definition Sound.h:73
@ REASON_PTCH
Definition Sound.h:74
@ REASON_PLAY
Definition Sound.h:70
@ REASON_VLCT
Definition Sound.h:76
@ REASON_GAIN
Definition Sound.h:72
float gain
Definition Sound.h:83
float audibility
Definition Sound.h:82
ALuint buffer
Definition Sound.h:91
Ogre::Vector3 getPosition()
Definition Sound.h:64
bool loop
Definition Sound.h:85
float pitch
Definition Sound.h:84
Ogre::Vector3 velocity
Definition Sound.h:94
bool should_play
Definition Sound.h:87
int hardware_index
Definition Sound.h:90
void SetDopplerFactor(const float doppler_factor) const
Updates the global Doppler factor in OpenAL with the provided value.
static const float MAX_DISTANCE
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
Updates the Cone properties for the hardware source.
void Update(const float dt)
Does the per-frame update of sounds and listener environment.
LPALISFILTER alIsFilter
std::map< ALuint, ALuint > m_efx_effect_id_map
LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti
void recomputeSource(int source_index, int reason, float vfl, Ogre::Vector3 *vvec)
Computes audibility of an audio source and retires it if it is inaudible.
void PrepopulateEfxPropertiesMap()
Helper function that fills the m_efx_properties_map with presets provided by OpenAL's efx-presets....
ALuint audio_buffers[MAX_AUDIO_BUFFERS]
ALuint m_efx_outdoor_obstruction_lowpass_filter_id
float m_air_absorption_factor
SoundPtr audio_sources[MAX_AUDIO_BUFFERS]
LPALEFFECTI alEffecti
void UpdateGlobalDopplerFactor() const
Updates the global Doppler factor based on CVar settings and the state of the physics simulation.
void pauseAllSounds()
Unlike the name suggests, this sets the listener's gain to 0, essentially muting all sounds.
bool loadWAVFile(Ogre::String filename, ALuint buffer, Ogre::String resource_group_name="")
bool UpdateOcclusionFilter(const int hardware_index, const ALuint effect_slot_id, const EFXEAXREVERBPROPERTIES *reference_efx_reverb_properties) const
Applies an occlusion filter to the provided source if certain conditions apply.
void UpdateListenerEnvironment()
Determines several properties of the environment of the listener and updates OpenAL to use them.
static const unsigned int MAX_AUDIO_BUFFERS
ALuint m_efx_occlusion_wet_path_lowpass_filter_id
LPALDELETEFILTERS alDeleteFilters
SoundPtr createSound(Ogre::String filename, Ogre::String resource_group_name="")
LPALFILTERF alFilterf
LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv
ALuint CreateAlEffect(const EFXEAXREVERBPROPERTIES *efx_properties) const
Creates an OpenAL effect based on the parameters of an efx/eax reverb preset.
friend class Sound
Ogre::String audio_buffer_file_name[MAX_AUDIO_BUFFERS]
int hardware_sources_in_use_count
void assign(int source_index, int hardware_index)
Adds an audio source to hardware source.
Ogre::Vector3 m_listener_direction
EfxReverbEngine m_efx_reverb_engine
std::map< std::string, EFXEAXREVERBPROPERTIES > m_efx_properties_map
const EFXEAXREVERBPROPERTIES * m_listener_efx_reverb_properties
int hardware_sources_map[MAX_HARDWARE_SOURCES]
maps from the index of a hardware source to the index of the audio source currently assigned to the c...
void SetAirAbsorptionFactor(const float air_absorption_factor)
Sets the air absorptions factor for the direct path of all sounds.
void UpdateObstructionFilter(const int hardware_index, const bool enable_obstruction_filter) const
Applies an obstruction filter to the provided hardware source.
ALuint hardware_sources[MAX_HARDWARE_SOURCES]
void setMasterVolume(float v)
Updates both CVar audio_master_volume and the listener's gain to the provided value.
void UpdateEfxSpecificProperties(const float dt)
Updates properties of OpenAL facilities that are only available with EFX.
void UpdateSourceFilters(const int hardware_index) const
Helper function to call several other functions to update source filters.
static const float REFERENCE_DISTANCE
Ogre::Vector3 m_listener_position
int hardware_sources_num
total number of allocated hardware sources (<= MAX_HARDWARE_SOURCES)
LPALGENEFFECTS alGenEffects
void SetSpeedOfSound(const float speed_of_sound) const
Updates the speed of sound in OpenAL with the provided value.
static const float ROLLOFF_FACTOR
LPALEFFECTFV alEffectfv
std::pair< int, float > audio_sources_most_audible[MAX_AUDIO_BUFFERS]
bool ListenerIsUnderwater() const
void DeleteAlEffect(const ALuint efx_effect_id) const
Deletes an OpenAL effect.
void UpdateDirectedSounds() const
Updates AL Cones for sources of directed sound emissions (exhausts, turboprops and turbojets).
ALuint m_efx_occlusion_wet_path_send_id
ALCdevice * audio_device
Ogre::Vector3 m_listener_up
LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots
void resumeAllSounds()
Unlike the name suggests, this sets the listener's gain to the value of the CVar audio_master_volume.
void UpdateSourceSpecificDopplerFactor(const int hardware_index) const
Identifies the actor to which the sound corresponding to a hardware source belongs and updates the Do...
LPALFILTERI alFilteri
LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots
LPALEFFECTF alEffectf
Ogre::Vector3 m_listener_velocity
bool IsHardwareSourceObstructed(const int hardware_index) const
Performs various checks against the environment of the listener to determine whether the sound belong...
float GetSpeedOfSound() const
LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf
void SetListener(Ogre::Vector3 position, Ogre::Vector3 direction, Ogre::Vector3 up, Ogre::Vector3 velocity)
Sets position and speed of the listener.
LPALDELETEEFFECTS alDeleteEffects
LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot
LPALISEFFECT alIsEffect
std::tuple< Ogre::Vector3, float, float > ComputeEarlyReflectionsProperties() const
Detects surfaces close to the listener and calculates a user-relative (as opposed to listener-relativ...
const EFXEAXREVERBPROPERTIES * GetReverbPresetAt(Ogre::Vector3 position) const
Determines which reverb preset corresponds to the provided position and returns its properties.
static const unsigned int MAX_HARDWARE_SOURCES
ALCcontext * sound_context
void retire(int source_index)
Stops and the removes an audio source from hardware source.
void UpdateListenerEffectSlot(const float dt)
Dynamically adjusts some parameters of the currently active reverb preset based on the current enviro...
void SmoothlyUpdateAlAuxiliaryEffectSlot(const float dt, const ALuint slot_id, const EFXEAXREVERBPROPERTIES *target_efx_properties)
This performs a smooth update of the efx properties of an OpenAL Auxiliary Effect slot using linear i...
LPALGENFILTERS alGenFilters
void CleanUp()
Cleans up various objects that should be reset when returning from a terrain to the main menu.
void UpdateAlListener()
Updates the listener's position, orientation and velocity vectors in OpenAL.
const EFXEAXREVERBPROPERTIES * GetEfxProperties(const std::string &efx_preset_name) const
Returns a pointer to properties of an EFX preset stored in the EFX properties map.
SoundScriptTemplatePtr & getTemplate()
const SoundPtr & getSound(int pos)
Collisions * GetCollisions()
Definition Terrain.h:86
Wavefield * getWater()
Definition Terrain.h:87
@ BOAT
its a boat
Definition SimData.h:87
@ AIRPLANE
its an airplane
Definition SimData.h:86
CVar * audio_force_listener_efx_preset
CVar * audio_efx_reverb_engine
CVar * audio_enable_directed_sounds
CVar * audio_enable_obstruction
CVar * audio_doppler_factor
CVar * audio_default_efx_preset
CVar * audio_enable_reflection_panning
CVar * audio_sim_pause_disables_doppler_effect
CVar * audio_force_obstruction_inside_vehicles
CVar * audio_master_volume
GameContext * GetGameContext()
CVar * app_state
CVar * audio_device_name
CVar * audio_enable_efx
CVar * audio_enable_occlusion
CVar * audio_engine_controls_environmental_audio
static const NodeNum_t NODENUM_INVALID
std::vector< ActorPtr > ActorPtrVec
uint16_t NodeNum_t
Node position within Actor::ar_nodes; use RoR::NODENUM_INVALID as empty value.
NodeNum_t nodenum
Definition SimData.h:395
SoundScriptInstancePtr ssi
Definition SimData.h:394