1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #include "Sound.h"
4 
5 #include <cstdlib>
6 #include <cmath>
7 #include <alc.h>
8 #ifndef ALC_ALL_DEVICES_SPECIFIER
9 //needed for ALC_ALL_DEVICES_SPECIFIER on some special *nix
10 #include <alext.h>
11 #endif
12 #include <boost/cstdint.hpp>
13 #include <boost/thread/thread.hpp>
14 
15 #include "System/Sound/ISoundChannels.h"
16 #include "System/Sound/SoundLog.h"
17 #include "SoundSource.h"
18 #include "SoundBuffer.h"
19 #include "SoundItem.h"
20 #include "ALShared.h"
21 #include "EFX.h"
22 #include "EFXPresets.h"
23 
24 #include "System/TimeProfiler.h"
25 #include "System/Config/ConfigHandler.h"
26 #include "System/Exceptions.h"
27 #include "System/FileSystem/FileHandler.h"
28 #include "Lua/LuaParser.h"
29 #include "Map/Ground.h"
30 #include "Sim/Misc/GlobalConstants.h"
31 #include "System/myMath.h"
32 #include "System/Util.h"
33 #include "System/Platform/Watchdog.h"
34 
35 #include "System/float3.h"
36 
37 CONFIG(int, MaxSounds).defaultValue(128).minimumValue(0).description("Maximum parallel played sounds.");
38 CONFIG(bool, PitchAdjust).defaultValue(false).description("When enabled adjust sound speed/pitch to game speed.");
39 CONFIG(int, snd_volmaster).defaultValue(60).minimumValue(0).maximumValue(200).description("Master sound volume.");
40 CONFIG(int, snd_volgeneral).defaultValue(100).minimumValue(0).maximumValue(200).description("Volume for \"general\" sound channel.");
41 CONFIG(int, snd_volunitreply).defaultValue(100).minimumValue(0).maximumValue(200).description("Volume for \"unit reply\" sound channel.");
42 CONFIG(int, snd_volbattle).defaultValue(100).minimumValue(0).maximumValue(200).description("Volume for \"battle\" sound channel.");
43 CONFIG(int, snd_volui).defaultValue(100).minimumValue(0).maximumValue(200).description("Volume for \"ui\" sound channel.");
44 CONFIG(int, snd_volmusic).defaultValue(100).minimumValue(0).maximumValue(200).description("Volume for \"music\" sound channel.");
45 CONFIG(std::string, snd_device).defaultValue("").description("Sets the used output device. See \"Available Devices\" section in infolog.txt.");
46 
47 boost::recursive_mutex soundMutex;
48 
49 
CSound()50 CSound::CSound()
51 	: myPos(ZeroVector)
52 	, prevVelocity(ZeroVector)
53 	, soundThread(NULL)
54 	, soundThreadQuit(false)
55 {
56 	boost::recursive_mutex::scoped_lock lck(soundMutex);
57 	mute = false;
58 	appIsIconified = false;
59 	int maxSounds = configHandler->GetInt("MaxSounds");
60 	pitchAdjust = configHandler->GetBool("PitchAdjust");
61 
62 	masterVolume = configHandler->GetInt("snd_volmaster") * 0.01f;
63 	Channels::General->SetVolume(configHandler->GetInt("snd_volgeneral") * 0.01f);
64 	Channels::UnitReply->SetVolume(configHandler->GetInt("snd_volunitreply") * 0.01f);
65 	Channels::UnitReply->SetMaxConcurrent(1);
66 	Channels::UnitReply->SetMaxEmmits(1);
67 	Channels::Battle->SetVolume(configHandler->GetInt("snd_volbattle") * 0.01f);
68 	Channels::UserInterface->SetVolume(configHandler->GetInt("snd_volui") * 0.01f);
69 	Channels::BGMusic->SetVolume(configHandler->GetInt("snd_volmusic") * 0.01f);
70 
71 	SoundBuffer::Initialise();
72 	soundItemDef temp;
73 	temp["name"] = "EmptySource";
74 	sounds.push_back(NULL);
75 
76 	if (maxSounds <= 0) {
77 		LOG_L(L_WARNING, "MaxSounds set to 0, sound is disabled");
78 	} else {
79 		soundThread = new boost::thread(boost::bind(&CSound::StartThread, this, maxSounds));
80 	}
81 
82 	configHandler->NotifyOnChange(this);
83 }
84 
~CSound()85 CSound::~CSound()
86 {
87 	soundThreadQuit = true;
88 	configHandler->RemoveObserver(this);
89 
90 	LOG_L(L_INFO, "[%s][1] soundThread=%p", __FUNCTION__, soundThread);
91 
92 	if (soundThread != NULL) {
93 		soundThread->join();
94 		delete soundThread;
95 		soundThread = NULL;
96 	}
97 
98 	LOG_L(L_INFO, "[%s][2]", __FUNCTION__);
99 
100 	for (soundVecT::iterator it = sounds.begin(); it != sounds.end(); ++it)
101 		delete *it;
102 
103 	sounds.clear();
104 	SoundBuffer::Deinitialise();
105 
106 	LOG_L(L_INFO, "[%s][3]", __FUNCTION__);
107 }
108 
HasSoundItem(const std::string & name) const109 bool CSound::HasSoundItem(const std::string& name) const
110 {
111 	soundMapT::const_iterator it = soundMap.find(name);
112 	if (it != soundMap.end())
113 	{
114 		return true;
115 	}
116 	else
117 	{
118 		soundItemDefMap::const_iterator it = soundItemDefs.find(StringToLower(name));
119 		if (it != soundItemDefs.end())
120 			return true;
121 		else
122 			return false;
123 	}
124 }
125 
GetSoundId(const std::string & name)126 size_t CSound::GetSoundId(const std::string& name)
127 {
128 	boost::recursive_mutex::scoped_lock lck(soundMutex);
129 
130 	if (sources.empty())
131 		return 0;
132 
133 	soundMapT::const_iterator it = soundMap.find(name);
134 	if (it != soundMap.end()) {
135 		// sounditem found
136 		return it->second;
137 	} else {
138 		soundItemDefMap::const_iterator itemDefIt = soundItemDefs.find(StringToLower(name));
139 		if (itemDefIt != soundItemDefs.end()) {
140 			return MakeItemFromDef(itemDefIt->second);
141 		} else {
142 			if (LoadSoundBuffer(name) > 0) // maybe raw filename?
143 			{
144 				soundItemDef temp = defaultItem;
145 				temp["file"] = name;
146 				return MakeItemFromDef(temp);
147 			} else {
148 				LOG_L(L_ERROR, "CSound::GetSoundId: could not find sound: %s", name.c_str());
149 				return 0;
150 			}
151 		}
152 	}
153 }
154 
GetSoundItem(size_t id) const155 SoundItem* CSound::GetSoundItem(size_t id) const {
156 	//! id==0 is a special id and invalid
157 	if (id == 0 || id >= sounds.size())
158 		return NULL;
159 	return sounds[id];
160 }
161 
GetNextBestSource(bool lock)162 CSoundSource* CSound::GetNextBestSource(bool lock)
163 {
164 	boost::recursive_mutex::scoped_lock lck(soundMutex, boost::defer_lock);
165 	if (lock)
166 		lck.lock();
167 
168 	if (sources.empty())
169 		return NULL;
170 
171 	CSoundSource* bestPos = NULL;
172 	for (sourceVecT::iterator it = sources.begin(); it != sources.end(); ++it)
173 	{
174 		if (!it->IsPlaying())
175 		{
176 			return &(*it);
177 		}
178 		else if (it->GetCurrentPriority() <= (bestPos ? bestPos->GetCurrentPriority() : INT_MAX))
179 		{
180 			bestPos = &(*it);
181 		}
182 	}
183 	return bestPos;
184 }
185 
PitchAdjust(const float newPitch)186 void CSound::PitchAdjust(const float newPitch)
187 {
188 	boost::recursive_mutex::scoped_lock lck(soundMutex);
189 	if (pitchAdjust)
190 		CSoundSource::SetPitch(newPitch);
191 }
192 
ConfigNotify(const std::string & key,const std::string & value)193 void CSound::ConfigNotify(const std::string& key, const std::string& value)
194 {
195 	boost::recursive_mutex::scoped_lock lck(soundMutex);
196 	if (key == "snd_volmaster")
197 	{
198 		masterVolume = std::atoi(value.c_str()) * 0.01f;
199 		if (!mute && !appIsIconified)
200 			alListenerf(AL_GAIN, masterVolume);
201 	}
202 	else if (key == "snd_eaxpreset")
203 	{
204 		efx->SetPreset(value);
205 	}
206 	else if (key == "snd_filter")
207 	{
208 		float gainlf = 1.f;
209 		float gainhf = 1.f;
210 		sscanf(value.c_str(), "%f %f", &gainlf, &gainhf);
211 		efx->sfxProperties->filter_properties_f[AL_LOWPASS_GAIN]   = gainlf;
212 		efx->sfxProperties->filter_properties_f[AL_LOWPASS_GAINHF] = gainhf;
213 		efx->CommitEffects();
214 	}
215 	else if (key == "UseEFX")
216 	{
217 		bool enable = (std::atoi(value.c_str()) != 0);
218 		if (enable)
219 			efx->Enable();
220 		else
221 			efx->Disable();
222 	}
223 	else if (key == "snd_volgeneral")
224 	{
225 		Channels::General->SetVolume(std::atoi(value.c_str()) * 0.01f);
226 	}
227 	else if (key == "snd_volunitreply")
228 	{
229 		Channels::UnitReply->SetVolume(std::atoi(value.c_str()) * 0.01f);
230 	}
231 	else if (key == "snd_volbattle")
232 	{
233 		Channels::Battle->SetVolume(std::atoi(value.c_str()) * 0.01f);
234 	}
235 	else if (key == "snd_volui")
236 	{
237 		Channels::UserInterface->SetVolume(std::atoi(value.c_str()) * 0.01f);
238 	}
239 	else if (key == "snd_volmusic")
240 	{
241 		Channels::BGMusic->SetVolume(std::atoi(value.c_str()) * 0.01f);
242 	}
243 	else if (key == "PitchAdjust")
244 	{
245 		bool tempPitchAdjust = (std::atoi(value.c_str()) != 0);
246 		if (!tempPitchAdjust)
247 			PitchAdjust(1.0);
248 		pitchAdjust = tempPitchAdjust;
249 	}
250 }
251 
Mute()252 bool CSound::Mute()
253 {
254 	boost::recursive_mutex::scoped_lock lck(soundMutex);
255 	mute = !mute;
256 	if (mute)
257 		alListenerf(AL_GAIN, 0.0);
258 	else
259 		alListenerf(AL_GAIN, masterVolume);
260 	return mute;
261 }
262 
IsMuted() const263 bool CSound::IsMuted() const
264 {
265 	return mute;
266 }
267 
Iconified(bool state)268 void CSound::Iconified(bool state)
269 {
270 	boost::recursive_mutex::scoped_lock lck(soundMutex);
271 	if (appIsIconified != state && !mute)
272 	{
273 		if (state == false)
274 			alListenerf(AL_GAIN, masterVolume);
275 		else if (state == true)
276 			alListenerf(AL_GAIN, 0.0);
277 	}
278 	appIsIconified = state;
279 }
280 
281 __FORCE_ALIGN_STACK__
StartThread(int maxSounds)282 void CSound::StartThread(int maxSounds)
283 {
284 	{
285 		boost::recursive_mutex::scoped_lock lck(soundMutex);
286 
287 		// alc... will create its own thread it will copy the name from the current thread.
288 		// Later we finally rename `our` audio thread.
289 		Threading::SetThreadName("openal");
290 
291 		// NULL -> default device
292 		const ALchar* deviceName = NULL;
293 		std::string configDeviceName = "";
294 
295 		// we do not want to set a default for snd_device,
296 		// so we do it like this ...
297 		if (configHandler->IsSet("snd_device"))
298 		{
299 			configDeviceName = configHandler->GetString("snd_device");
300 			deviceName = configDeviceName.c_str();
301 		}
302 
303 		ALCdevice* device = alcOpenDevice(deviceName);
304 
305 		if ((device == NULL) && (deviceName != NULL))
306 		{
307 			LOG_L(L_WARNING,
308 					"Could not open the sound device \"%s\", trying the default device ...",
309 					deviceName);
310 			configDeviceName = "";
311 			deviceName = NULL;
312 			device = alcOpenDevice(deviceName);
313 		}
314 
315 		if (device == NULL)
316 		{
317 			LOG_L(L_ERROR, "Could not open a sound device, disabling sounds");
318 			CheckError("CSound::InitAL");
319 			return;
320 		}
321 		else
322 		{
323 			ALCcontext *context = alcCreateContext(device, NULL);
324 			if (context != NULL)
325 			{
326 				alcMakeContextCurrent(context);
327 				CheckError("CSound::CreateContext");
328 			}
329 			else
330 			{
331 				alcCloseDevice(device);
332 				LOG_L(L_ERROR, "Could not create OpenAL audio context");
333 				return;
334 			}
335 		}
336 		maxSounds = GetMaxMonoSources(device, maxSounds);
337 
338 		LOG("OpenAL info:");
339 		if(alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
340 		{
341 			LOG("  Available Devices:");
342 			const char* deviceSpecifier = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
343 			while (*deviceSpecifier != '\0') {
344 				LOG("              %s", deviceSpecifier);
345 				while (*deviceSpecifier++ != '\0')
346 					;
347 			}
348 			LOG("  Device:     %s", (const char*)alcGetString(device, ALC_DEVICE_SPECIFIER));
349 		}
350 		LOG("  Vendor:         %s", (const char*)alGetString(AL_VENDOR));
351 		LOG("  Version:        %s", (const char*)alGetString(AL_VERSION));
352 		LOG("  Renderer:       %s", (const char*)alGetString(AL_RENDERER));
353 		LOG("  AL Extensions:  %s", (const char*)alGetString(AL_EXTENSIONS));
354 		LOG("  ALC Extensions: %s", (const char*)alcGetString(device, ALC_EXTENSIONS));
355 
356 		// Init EFX
357 		efx = new CEFX(device);
358 
359 		// Generate sound sources
360 		for (int i = 0; i < maxSounds; i++)
361 		{
362 			CSoundSource* thenewone = new CSoundSource();
363 			if (thenewone->IsValid()) {
364 				sources.push_back(thenewone);
365 			} else {
366 				maxSounds = std::max(i-1, 0);
367 				LOG_L(L_WARNING,
368 						"Your hardware/driver can not handle more than %i soundsources",
369 						maxSounds);
370 				delete thenewone;
371 				break;
372 			}
373 		}
374 		LOG("  Max Sounds: %i", maxSounds);
375 
376 		// Set distance model (sound attenuation)
377 		alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
378 		alDopplerFactor(0.2f);
379 
380 		alListenerf(AL_GAIN, masterVolume);
381 	}
382 
383 	Threading::SetThreadName("audio");
384 	Watchdog::RegisterThread(WDT_AUDIO);
385 
386 	while (!soundThreadQuit) {
387 		boost::this_thread::sleep(boost::posix_time::millisec(50)); //! 20Hz
388 		Watchdog::ClearTimer(WDT_AUDIO);
389 		Update();
390 	}
391 
392 	Watchdog::DeregisterThread(WDT_AUDIO);
393 
394 	sources.clear(); // delete all sources
395 	delete efx; // must happen after sources and before context
396 	efx = NULL;
397 	ALCcontext* curcontext = alcGetCurrentContext();
398 	ALCdevice* curdevice = alcGetContextsDevice(curcontext);
399 	alcMakeContextCurrent(NULL);
400 	alcDestroyContext(curcontext);
401 	alcCloseDevice(curdevice);
402 }
403 
Update()404 void CSound::Update()
405 {
406 	boost::recursive_mutex::scoped_lock lck(soundMutex); // lock
407 	for (sourceVecT::iterator it = sources.begin(); it != sources.end(); ++it)
408 		it->Update();
409 	CheckError("CSound::Update");
410 }
411 
MakeItemFromDef(const soundItemDef & itemDef)412 size_t CSound::MakeItemFromDef(const soundItemDef& itemDef)
413 {
414 	//! MakeItemFromDef is private. Only caller is LoadSoundDefs and it sets the mutex itself.
415 	//boost::recursive_mutex::scoped_lock lck(soundMutex);
416 	const size_t newid = sounds.size();
417 	soundItemDef::const_iterator it = itemDef.find("file");
418 	if (it == itemDef.end())
419 		return 0;
420 
421 	boost::shared_ptr<SoundBuffer> buffer = SoundBuffer::GetById(LoadSoundBuffer(it->second));
422 
423 	if (!buffer)
424 		return 0;
425 
426 	SoundItem* buf = new SoundItem(buffer, itemDef);
427 	sounds.push_back(buf);
428 	soundMap[buf->Name()] = newid;
429 	return newid;
430 }
431 
UpdateListener(const float3 & campos,const float3 & camdir,const float3 & camup,float lastFrameTime)432 void CSound::UpdateListener(const float3& campos, const float3& camdir, const float3& camup, float lastFrameTime)
433 {
434 	boost::recursive_mutex::scoped_lock lck(soundMutex);
435 	if (sources.empty())
436 		return;
437 
438 	myPos = campos;
439 	const float3 myPosInMeters = myPos * ELMOS_TO_METERS;
440 	alListener3f(AL_POSITION, myPosInMeters.x, myPosInMeters.y, myPosInMeters.z);
441 
442 	//! reduce the rolloff when the camera is high above the ground (so we still hear something in tab mode or far zoom)
443 	//! for altitudes up to and including 600 elmos, the rolloff is always clamped to 1
444 	const float camHeight = std::max(1.0f, campos.y - CGround::GetHeightAboveWater(campos.x, campos.z));
445 	const float newMod = std::min(600.0f / camHeight, 1.0f);
446 
447 	CSoundSource::SetHeightRolloffModifer(newMod);
448 	efx->SetHeightRolloffModifer(newMod);
449 
450 	//! Result were bad with listener related doppler effects.
451 	//! The user experiences the camera/listener not as a world-interacting object.
452 	//! So changing sounds on camera movements were irritating, esp. because zooming with the mouse wheel
453 	//! often is faster than the speed of sound, causing very high frequencies.
454 	//! Note: soundsource related doppler effects are not deactivated by this! Flying cannon shoots still change their frequencies.
455 	//! Note2: by not updating the listener velocity soundsource related velocities are calculated wrong,
456 	//! so even if the camera is moving with a cannon shoot the frequency gets changed.
457 	/*
458 	const float3 velocity = (myPos - prevPos) / (lastFrameTime);
459 	float3 velocityAvg = velocity * 0.6f + prevVelocity * 0.4f;
460 	prevVelocity = velocityAvg;
461 	velocityAvg *= ELMOS_TO_METERS;
462 	velocityAvg.y *= 0.001f; //! scale vertical axis separatly (zoom with mousewheel is faster than speed of sound!)
463 	velocityAvg *= 0.15f;
464 	alListener3f(AL_VELOCITY, velocityAvg.x, velocityAvg.y, velocityAvg.z);
465 	*/
466 
467 	ALfloat ListenerOri[] = {camdir.x, camdir.y, camdir.z, camup.x, camup.y, camup.z};
468 	alListenerfv(AL_ORIENTATION, ListenerOri);
469 	CheckError("CSound::UpdateListener");
470 }
471 
PrintDebugInfo()472 void CSound::PrintDebugInfo()
473 {
474 	boost::recursive_mutex::scoped_lock lck(soundMutex);
475 
476 	LOG_L(L_DEBUG, "OpenAL Sound System:");
477 	LOG_L(L_DEBUG, "# SoundSources: %i", (int)sources.size());
478 	LOG_L(L_DEBUG, "# SoundBuffers: %i", (int)SoundBuffer::Count());
479 
480 	LOG_L(L_DEBUG, "# reserved for buffers: %i kB", (int)(SoundBuffer::AllocedSize() / 1024));
481 	LOG_L(L_DEBUG, "# PlayRequests for empty sound: %i", numEmptyPlayRequests);
482 	LOG_L(L_DEBUG, "# Samples disrupted: %i", numAbortedPlays);
483 	LOG_L(L_DEBUG, "# SoundItems: %i", (int)sounds.size());
484 }
485 
LoadSoundDefsImpl(const std::string & fileName)486 bool CSound::LoadSoundDefsImpl(const std::string& fileName)
487 {
488 	//! can be called from LuaUnsyncedCtrl too
489 	boost::recursive_mutex::scoped_lock lck(soundMutex);
490 
491 	LuaParser parser(fileName, SPRING_VFS_MOD, SPRING_VFS_ZIP);
492 	parser.Execute();
493 	if (!parser.IsValid())
494 	{
495 		LOG_L(L_WARNING, "Could not load %s: %s",
496 				fileName.c_str(), parser.GetErrorLog().c_str());
497 		return false;
498 	}
499 	else
500 	{
501 		const LuaTable soundRoot = parser.GetRoot();
502 		const LuaTable soundItemTable = soundRoot.SubTable("SoundItems");
503 		if (!soundItemTable.IsValid())
504 		{
505 			LOG_L(L_WARNING, "CSound(): could not parse SoundItems table in %s", fileName.c_str());
506 			return false;
507 		}
508 		else
509 		{
510 			std::vector<std::string> keys;
511 			soundItemTable.GetKeys(keys);
512 			for (std::vector<std::string>::const_iterator it = keys.begin(); it != keys.end(); ++it)
513 			{
514 				std::string name(*it);
515 
516 				soundItemDef bufmap;
517 				const LuaTable buf(soundItemTable.SubTable(name));
518 				buf.GetMap(bufmap);
519 				bufmap["name"] = name;
520 				soundItemDefMap::const_iterator sit = soundItemDefs.find(name);
521 
522 				if (name == "default") {
523 					defaultItem = bufmap;
524 					defaultItem.erase("name"); //must be empty for default item
525 					defaultItem.erase("file");
526 					continue;
527 				}
528 
529 				if (sit != soundItemDefs.end())
530 					LOG_L(L_WARNING, "Sound %s gets overwritten by %s", name.c_str(), fileName.c_str());
531 
532 				if (!buf.KeyExists("file")) {
533 					// no file, drop
534 					LOG_L(L_WARNING, "Sound %s is missing file tag (ignoring)", name.c_str());
535 					continue;
536 				} else {
537 					soundItemDefs[name] = bufmap;
538 				}
539 
540 				if (buf.KeyExists("preload")) {
541 					MakeItemFromDef(bufmap);
542 				}
543 			}
544 			LOG(" parsed %i sounds from %s", (int)keys.size(), fileName.c_str());
545 		}
546 	}
547 
548 	//FIXME why do sounds w/o an own soundItemDef create (!=pointer) a new one from the defaultItem?
549 	for (soundItemDefMap::iterator it = soundItemDefs.begin(); it != soundItemDefs.end(); ++it) {
550 		soundItemDef& snddef = it->second;
551 		if (snddef.find("name") == snddef.end()) {
552 			// uses defaultItem! update it!
553 			const std::string file = snddef["file"];
554 			snddef = defaultItem;
555 			snddef["file"] = file;
556 		}
557 	}
558 
559 	return true;
560 }
561 
562 //! only used internally, locked in caller's scope
LoadSoundBuffer(const std::string & path)563 size_t CSound::LoadSoundBuffer(const std::string& path)
564 {
565 	const size_t id = SoundBuffer::GetId(path);
566 
567 	if (id > 0) {
568 		return id; // file is loaded already
569 	} else {
570 		CFileHandler file(path);
571 
572 		if (!file.FileExists()) {
573 			LOG_L(L_ERROR, "Unable to open audio file: %s", path.c_str());
574 			return 0;
575 		}
576 
577 		std::vector<boost::uint8_t> buf(file.FileSize());
578 		file.Read(&buf[0], file.FileSize());
579 
580 		boost::shared_ptr<SoundBuffer> buffer(new SoundBuffer());
581 		bool success = false;
582 		const std::string ending = file.GetFileExt();
583 		if (ending == "wav") {
584 			success = buffer->LoadWAV(path, buf);
585 		} else if (ending == "ogg") {
586 			success = buffer->LoadVorbis(path, buf);
587 		} else {
588 			LOG_L(L_WARNING, "CSound::LoadALBuffer: unknown audio format: %s",
589 					ending.c_str());
590 		}
591 
592 		CheckError("CSound::LoadALBuffer");
593 		if (!success) {
594 			LOG_L(L_WARNING, "Failed to load file: %s", path.c_str());
595 			return 0;
596 		}
597 
598 		return SoundBuffer::Insert(buffer);
599 	}
600 }
601 
NewFrame()602 void CSound::NewFrame()
603 {
604 	Channels::General->UpdateFrame();
605 	Channels::Battle->UpdateFrame();
606 	Channels::UnitReply->UpdateFrame();
607 	Channels::UserInterface->UpdateFrame();
608 }
609 
610 
611 // try to get the maximum number of supported sounds, this is similar to code CSound::StartThread
612 // but should be more safe
GetMaxMonoSources(ALCdevice * device,int maxSounds)613 int CSound::GetMaxMonoSources(ALCdevice* device, int maxSounds)
614 {
615 	ALCint size;
616 	alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size);
617 	std::vector<ALCint> attrs(size);
618 	alcGetIntegerv(device, ALC_ALL_ATTRIBUTES, size, &attrs[0] );
619 	for (int i=0; i<attrs.size(); ++i){
620 		if (attrs[i] == ALC_MONO_SOURCES) {
621 			const int maxMonoSources = attrs.at(i + 1);
622 			if (maxMonoSources < maxSounds) {
623 				LOG_L(L_WARNING, "Hardware supports only %d Sound sources, MaxSounds=%d, using Hardware Limit", maxMonoSources, maxSounds);
624 			}
625 			return std::min(maxSounds, maxMonoSources);
626 		}
627 	}
628 	return maxSounds;
629 }
630 
631