summaryrefslogtreecommitdiff
path: root/src/client/renderingengine.h
Commit message (Collapse)AuthorAge
* Remove remains of video mode queryingsfan52022-05-29
|
* Revert "Disable dynamic shadows for the 5.5.0 release" (#12032)rubenwardy2022-01-31
|
* Disable dynamic shadows for the 5.5.0 releaseSmallJoker2022-01-29
| | | | | | | | | The dynamic shadows are yet not in the desired state to justify the inclusion into version 5.5.0. A stable release is long overdue, hence this allows fixes to continue in 5.6.0-dev to finally release an acceptable version of the dynamic shadows feature. Reverting this commit is highly recommended to proceed in development.
* Refactor video driver name retrieval (#11413)hecks2021-07-11
| | | Co-authored-by: hecktest <>
* Drop --videomodes, fullscreen_bpp and high_precision_fpu settingssfan52021-06-16
| | | | These have been pointless for a while.
* Shadow mapping render pass (#11244)Liso2021-06-06
| | | Co-authored-by: x2048 <codeforsmile@gmail.com>
* fix: some code tidy about includes & irr namespacesLoic Blot2021-05-03
|
* refacto: remove get_gui_env & draw_load_screen from RenderingEngine singletonLoic Blot2021-05-03
|
* refacto: RenderingEngine::get_scene_manager() is now not callable from singletonLoic Blot2021-05-03
| | | | | | This permits to make evidence that we have some bad object passing on various code parts. I fixed majority of them to reduce the scope of passed objects Unfortunately, for some edge cases i should have to expose ISceneManager from client, this should be fixed in the future when our POO will be cleaner client side (we have a mix of rendering and processing in majority of the client objects, it works but it's not clean)
* refacto: RenderingEngine is now better hiddenLoic Blot2021-05-03
| | | | | | | | | | * No more access to the singleton instance from everywhere (RenderingEngine::get_instance dropped) * RenderingEngine::get_timer_time is now non static * RenderingEngine::draw_menu_scene is now non static * RenderingEngine::draw_scene is now non static * RenderingEngine::{initialize,finalize} are now non static * RenderingEngine::run is now non static * RenderingEngine::getWindowSize now have a static helper. It was mandatory to hide the global get_instance access
* refacto: hide mesh_cache inside the rendering engineLoic Blot2021-05-03
| | | | This permit cleaner access to meshCache and ensure we don't access to it from all the code
* refacto: add RenderingEngine::cleanupMeshCacheLoic Blot2021-05-03
| | | | This permits to prevent client to own the mesh cache cleanup logic. It's better in RenderingEngine
* refacto: rendering engine singleton removal step 1 (filesystem)Loic Blot2021-05-03
| | | | | | | | Make the RenderingEngine filesystem member non accessible from everywhere This permits also to determine that some lua code has directly a logic to extract zip file. Move this logic inside client, it's not the lua stack role to perform a such complex operation Found also another irrlicht <1.8 compat code to remove
* Provide Xorg/net wm process ID (#7445)thoughtjigs2018-06-17
| | | | | | Adding support for _NET_WM_PID as defined in Extended Window Manager Hints Move verbose messaging to setupXorgTopLevelWindow method as Xorg messages should only occur when running in Xorg env. Irrlicht returns the XDisplay as a void* and XWindow as an unsigned long so reinterpret those as the appropriate type. Also fixed a spaces for tab formating issue
* Add confirmation on new player registration (#6849)Muhammad Rifqi Priyo Susanto2018-01-13
| | | | | | | | | | | | | | * Attempt to add registration confirmation Using SRP auth mechanism, if server sent AUTH_MECHANISM_FIRST_SRP that means the player isn't exist. Also tell player about the server and chosen username. Local game has localhost as IP address of the server. Add RenderingEngine::draw_menu_scene() to draw GUI and clouds background. aborted -> connection_aborted * Rewrite information message text Client::promptConfirmRegister() -> Client::promptConfirmRegistration()
* Rewrite rendering engine (#6253)Vitaliy2017-10-31
| | | | | | | | | | | | * Clean draw_*() arguments * Split rendering core * Add anaglyph 3D * Interlaced 3D * Drop obsolete methods
* Isolate irrlicht references and use a singleton (#6041)Loïc Blot2017-06-26
* Add Device3D class which will contain IrrlichtDevice interface move getSupportedVideoDrivers to Device3D Add Device3D singleton & use it in various places Rename Device3D to Rendering engine & add helper functions to various device pointers More singleton work RenderingEngine owns draw_load_screen move draw functions to RenderingEngine Reduce IrrlichtDevice exposure and guienvironment RenderingEngine: Expose get_timer_time() to remove device from guiEngine Make irrlichtdevice & scene manager less exposed * Code style fixes * Move porting::getVideoDriverName, getVideoDriverFriendlyName, getDisplayDensity, getDisplaySize to RenderingEngine Fix XORG_USED macro -> RenderingEngine + create_engine_device from RenderingEngine constructor directly * enum paralax => enum parallax
' href='#n440'>440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
OpenAL support based on work by:
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "sound_openal.h"

#if defined(_WIN32)
	#include <al.h>
	#include <alc.h>
	//#include <alext.h>
#elif defined(__APPLE__)
	#define OPENAL_DEPRECATED
	#include <OpenAL/al.h>
	#include <OpenAL/alc.h>
	//#include <OpenAL/alext.h>
#else
	#include <AL/al.h>
	#include <AL/alc.h>
	#include <AL/alext.h>
#endif
#include <cmath>
#include <vorbis/vorbisfile.h>
#include <cassert>
#include "log.h"
#include "util/numeric.h" // myrand()
#include "porting.h"
#include <vector>
#include <fstream>
#include <unordered_map>
#include <unordered_set>

#define BUFFER_SIZE 30000

std::shared_ptr<SoundManagerSingleton> g_sound_manager_singleton;

typedef std::unique_ptr<ALCdevice, void (*)(ALCdevice *p)> unique_ptr_alcdevice;
typedef std::unique_ptr<ALCcontext, void(*)(ALCcontext *p)> unique_ptr_alccontext;

static void delete_alcdevice(ALCdevice *p)
{
	if (p)
		alcCloseDevice(p);
}

static void delete_alccontext(ALCcontext *p)
{
	if (p) {
		alcMakeContextCurrent(nullptr);
		alcDestroyContext(p);
	}
}

static const char *alErrorString(ALenum err)
{
	switch (err) {
	case AL_NO_ERROR:
		return "no error";
	case AL_INVALID_NAME:
		return "invalid name";
	case AL_INVALID_ENUM:
		return "invalid enum";
	case AL_INVALID_VALUE:
		return "invalid value";
	case AL_INVALID_OPERATION:
		return "invalid operation";
	case AL_OUT_OF_MEMORY:
		return "out of memory";
	default:
		return "<unknown OpenAL error>";
	}
}

static ALenum warn_if_error(ALenum err, const char *desc)
{
	if(err == AL_NO_ERROR)
		return err;
	warningstream<<desc<<": "<<alErrorString(err)<<std::endl;
	return err;
}

void f3_set(ALfloat *f3, v3f v)
{
	f3[0] = v.X;
	f3[1] = v.Y;
	f3[2] = v.Z;
}

struct SoundBuffer
{
	ALenum format;
	ALsizei freq;
	ALuint buffer_id;
	std::vector<char> buffer;
};

SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile,
		const std::string &filename_for_logging)
{
	int endian = 0; // 0 for Little-Endian, 1 for Big-Endian
	int bitStream;
	long bytes;
	char array[BUFFER_SIZE]; // Local fixed size array
	vorbis_info *pInfo;

	SoundBuffer *snd = new SoundBuffer;

	// Get some information about the OGG file
	pInfo = ov_info(oggFile, -1);

	// Check the number of channels... always use 16-bit samples
	if(pInfo->channels == 1)
		snd->format = AL_FORMAT_MONO16;
	else
		snd->format = AL_FORMAT_STEREO16;

	// The frequency of the sampling rate
	snd->freq = pInfo->rate;

	// Keep reading until all is read
	do
	{
		// Read up to a buffer's worth of decoded sound data
		bytes = ov_read(oggFile, array, BUFFER_SIZE, endian, 2, 1, &bitStream);

		if(bytes < 0)
		{
			ov_clear(oggFile);
			infostream << "Audio: Error decoding "
				<< filename_for_logging << std::endl;
			delete snd;
			return nullptr;
		}

		// Append to end of buffer
		snd->buffer.insert(snd->buffer.end(), array, array + bytes);
	} while (bytes > 0);

	alGenBuffers(1, &snd->buffer_id);
	alBufferData(snd->buffer_id, snd->format,
			&(snd->buffer[0]), snd->buffer.size(),
			snd->freq);

	ALenum error = alGetError();

	if(error != AL_NO_ERROR){
		infostream << "Audio: OpenAL error: " << alErrorString(error)
				<< "preparing sound buffer" << std::endl;
	}

	//infostream << "Audio file "
	//	<< filename_for_logging << " loaded" << std::endl;

	// Clean up!
	ov_clear(oggFile);

	return snd;
}

SoundBuffer *load_ogg_from_file(const std::string &path)
{
	OggVorbis_File oggFile;

	// Try opening the given file.
	// This requires libvorbis >= 1.3.2, as
	// previous versions expect a non-const char *
	if (ov_fopen(path.c_str(), &oggFile) != 0) {
		infostream << "Audio: Error opening " << path
			<< " for decoding" << std::endl;
		return nullptr;
	}

	return load_opened_ogg_file(&oggFile, path);
}

struct BufferSource {
	const char *buf;
	size_t cur_offset;
	size_t len;
};

size_t buffer_sound_read_func(void *ptr, size_t size, size_t nmemb, void *datasource)
{
	BufferSource *s = (BufferSource *)datasource;
	size_t copied_size = MYMIN(s->len - s->cur_offset, size);
	memcpy(ptr, s->buf + s->cur_offset, copied_size);
	s->cur_offset += copied_size;
	return copied_size;
}

int buffer_sound_seek_func(void *datasource, ogg_int64_t offset, int whence)
{
	BufferSource *s = (BufferSource *)datasource;
	if (whence == SEEK_SET) {
		if (offset < 0 || (size_t)MYMAX(offset, 0) >= s->len) {
			// offset out of bounds
			return -1;
		}
		s->cur_offset = offset;
		return 0;
	} else if (whence == SEEK_CUR) {
		if ((size_t)MYMIN(-offset, 0) > s->cur_offset
				|| s->cur_offset + offset > s->len) {
			// offset out of bounds
			return -1;
		}
		s->cur_offset += offset;
		return 0;
	}
	// invalid whence param (SEEK_END doesn't have to be supported)
	return -1;
}

long BufferSourceell_func(void *datasource)
{
	BufferSource *s = (BufferSource *)datasource;
	return s->cur_offset;
}

static ov_callbacks g_buffer_ov_callbacks = {
	&buffer_sound_read_func,
	&buffer_sound_seek_func,
	nullptr,
	&BufferSourceell_func
};

SoundBuffer *load_ogg_from_buffer(const std::string &buf, const std::string &id_for_log)
{
	OggVorbis_File oggFile;

	BufferSource s;
	s.buf = buf.c_str();
	s.cur_offset = 0;
	s.len = buf.size();

	if (ov_open_callbacks(&s, &oggFile, nullptr, 0, g_buffer_ov_callbacks) != 0) {
		infostream << "Audio: Error opening " << id_for_log
			<< " for decoding" << std::endl;
		return nullptr;
	}

	return load_opened_ogg_file(&oggFile, id_for_log);
}

struct PlayingSound
{
	ALuint source_id;
	bool loop;
};

class SoundManagerSingleton
{
public:
	unique_ptr_alcdevice  m_device;
	unique_ptr_alccontext m_context;
public:
	SoundManagerSingleton() :
		m_device(nullptr, delete_alcdevice),
		m_context(nullptr, delete_alccontext)
	{
	}

	bool init()
	{
		if (!(m_device = unique_ptr_alcdevice(alcOpenDevice(nullptr), delete_alcdevice))) {
			errorstream << "Audio: Global Initialization: Failed to open device" << std::endl;
			return false;
		}

		if (!(m_context = unique_ptr_alccontext(
				alcCreateContext(m_device.get(), nullptr), delete_alccontext))) {
			errorstream << "Audio: Global Initialization: Failed to create context" << std::endl;
			return false;
		}

		if (!alcMakeContextCurrent(m_context.get())) {
			errorstream << "Audio: Global Initialization: Failed to make current context" << std::endl;
			return false;
		}

		alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);

		if (alGetError() != AL_NO_ERROR) {
			errorstream << "Audio: Global Initialization: OpenAL Error " << alGetError() << std::endl;
			return false;
		}

		infostream << "Audio: Global Initialized: OpenAL " << alGetString(AL_VERSION)
			<< ", using " << alcGetString(m_device.get(), ALC_DEVICE_SPECIFIER)
			<< std::endl;

		return true;
	}

	~SoundManagerSingleton()
	{
		infostream << "Audio: Global Deinitialized." << std::endl;
	}
};

class OpenALSoundManager: public ISoundManager
{
private:
	OnDemandSoundFetcher *m_fetcher;
	ALCdevice *m_device;
	ALCcontext *m_context;
	u16 m_last_used_id = 0; // only access within getFreeId() !
	std::unordered_map<std::string, std::vector<SoundBuffer*>> m_buffers;
	std::unordered_map<int, PlayingSound*> m_sounds_playing;
	struct FadeState {
		FadeState() = default;

		FadeState(float step, float current_gain, float target_gain):
			step(step),
			current_gain(current_gain),
			target_gain(target_gain) {}
		float step;
		float current_gain;
		float target_gain;
	};

	std::unordered_map<int, FadeState> m_sounds_fading;
public:
	OpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher):
		m_fetcher(fetcher),
		m_device(smg->m_device.get()),
		m_context(smg->m_context.get())
	{
		infostream << "Audio: Initialized: OpenAL " << std::endl;
	}

	~OpenALSoundManager()
	{
		infostream << "Audio: Deinitializing..." << std::endl;

		std::unordered_set<int> source_del_list;

		for (const auto &sp : m_sounds_playing)
			source_del_list.insert(sp.first);

		for (const auto &id : source_del_list)
			deleteSound(id);

		for (auto &buffer : m_buffers) {
			for (SoundBuffer *sb : buffer.second) {
				alDeleteBuffers(1, &sb->buffer_id);

				ALenum error = alGetError();
				if (error != AL_NO_ERROR) {
					warningstream << "Audio: Failed to free stream for "
						<< buffer.first << ": " << alErrorString(error) << std::endl;
				}

				delete sb;
			}
			buffer.second.clear();
		}
		m_buffers.clear();

		infostream << "Audio: Deinitialized." << std::endl;
	}

	u16 getFreeId()
	{
		u16 startid = m_last_used_id;
		while (!isFreeId(++m_last_used_id)) {
			if (m_last_used_id == startid)
				return 0;
		}

		return m_last_used_id;
	}

	inline bool isFreeId(int id) const
	{
		return id > 0 && m_sounds_playing.find(id) == m_sounds_playing.end();
	}

	void step(float dtime)
	{
		doFades(dtime);
	}

	void addBuffer(const std::string &name, SoundBuffer *buf)
	{
		std::unordered_map<std::string, std::vector<SoundBuffer*>>::iterator i =
				m_buffers.find(name);
		if(i != m_buffers.end()){
			i->second.push_back(buf);
			return;
		}
		std::vector<SoundBuffer*> bufs;
		bufs.push_back(buf);
		m_buffers[name] = bufs;
	}

	SoundBuffer* getBuffer(const std::string &name)
	{
		std::unordered_map<std::string, std::vector<SoundBuffer*>>::iterator i =
				m_buffers.find(name);
		if(i == m_buffers.end())
			return nullptr;
		std::vector<SoundBuffer*> &bufs = i->second;
		int j = myrand() % bufs.size();
		return bufs[j];
	}

	PlayingSound* createPlayingSound(SoundBuffer *buf, bool loop,
			float volume, float pitch)
	{
		infostream << "OpenALSoundManager: Creating playing sound" << std::endl;
		assert(buf);
		PlayingSound *sound = new PlayingSound;
		assert(sound);
		warn_if_error(alGetError(), "before createPlayingSound");
		alGenSources(1, &sound->source_id);
		alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
		alSourcei(sound->source_id, AL_SOURCE_RELATIVE, true);
		alSource3f(sound->source_id, AL_POSITION, 0, 0, 0);
		alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
		alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
		volume = std::fmax(0.0f, volume);
		alSourcef(sound->source_id, AL_GAIN, volume);
		alSourcef(sound->source_id, AL_PITCH, pitch);
		alSourcePlay(sound->source_id);
		warn_if_error(alGetError(), "createPlayingSound");
		return sound;
	}

	PlayingSound* createPlayingSoundAt(SoundBuffer *buf, bool loop,
			float volume, v3f pos, float pitch)
	{
		infostream << "OpenALSoundManager: Creating positional playing sound"
				<< std::endl;
		assert(buf);
		PlayingSound *sound = new PlayingSound;

		warn_if_error(alGetError(), "before createPlayingSoundAt");
		alGenSources(1, &sound->source_id);
		alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
		alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
		alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
		alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
		// Use alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED) and set reference
		// distance to clamp gain at <1 node distance, to avoid excessive
		// volume when closer
		alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 10.0f);
		alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
		// Multiply by 3 to compensate for reducing AL_REFERENCE_DISTANCE from
		// the previous value of 30 to the new value of 10
		volume = std::fmax(0.0f, volume * 3.0f);
		alSourcef(sound->source_id, AL_GAIN, volume);
		alSourcef(sound->source_id, AL_PITCH, pitch);
		alSourcePlay(sound->source_id);
		warn_if_error(alGetError(), "createPlayingSoundAt");
		return sound;
	}

	int playSoundRaw(SoundBuffer *buf, bool loop, float volume, float pitch)
	{
		assert(buf);
		PlayingSound *sound = createPlayingSound(buf, loop, volume, pitch);
		if (!sound)
			return -1;

		int handle = getFreeId();
		m_sounds_playing[handle] = sound;
		return handle;
	}

	void deleteSound(int id)
	{
		auto i = m_sounds_playing.find(id);
		if(i == m_sounds_playing.end())
			return;
		PlayingSound *sound = i->second;

		alDeleteSources(1, &sound->source_id);

		delete sound;
		m_sounds_playing.erase(id);
	}

	/* If buffer does not exist, consult the fetcher */
	SoundBuffer* getFetchBuffer(const std::string &name)
	{
		SoundBuffer *buf = getBuffer(name);
		if(buf)
			return buf;
		if(!m_fetcher)
			return nullptr;
		std::set<std::string> paths;
		std::set<std::string> datas;
		m_fetcher->fetchSounds(name, paths, datas);
		for (const std::string &path : paths) {
			loadSoundFile(name, path);
		}
		for (const std::string &data : datas) {
			loadSoundData(name, data);
		}
		return getBuffer(name);
	}

	// Remove stopped sounds
	void maintain()
	{
		if (!m_sounds_playing.empty()) {
			verbosestream << "OpenALSoundManager::maintain(): "
					<< m_sounds_playing.size() <<" playing sounds, "
					<< m_buffers.size() <<" sound names loaded"<<std::endl;
		}
		std::unordered_set<int> del_list;
		for (const auto &sp : m_sounds_playing) {
			int id = sp.first;
			PlayingSound *sound = sp.second;
			// If not playing, remove it
			{
				ALint state;
				alGetSourcei(sound->source_id, AL_SOURCE_STATE, &state);
				if(state != AL_PLAYING){
					del_list.insert(id);
				}
			}
		}
		if(!del_list.empty())
			verbosestream<<"OpenALSoundManager::maintain(): deleting "
					<<del_list.size()<<" playing sounds"<<std::endl;
		for (int i : del_list) {
			deleteSound(i);
		}
	}

	/* Interface */

	bool loadSoundFile(const std::string &name,
			const std::string &filepath)
	{
		SoundBuffer *buf = load_ogg_from_file(filepath);
		if (buf)
			addBuffer(name, buf);
		return !!buf;
	}

	bool loadSoundData(const std::string &name,
			const std::string &filedata)
	{
		SoundBuffer *buf = load_ogg_from_buffer(filedata, name);
		if (buf)
			addBuffer(name, buf);
		return !!buf;
	}

	void updateListener(const v3f &pos, const v3f &vel, const v3f &at, const v3f &up)
	{
		alListener3f(AL_POSITION, pos.X, pos.Y, pos.Z);
		alListener3f(AL_VELOCITY, vel.X, vel.Y, vel.Z);
		ALfloat f[6];
		f3_set(f, at);
		f3_set(f+3, -up);
		alListenerfv(AL_ORIENTATION, f);
		warn_if_error(alGetError(), "updateListener");
	}

	void setListenerGain(float gain)
	{
		alListenerf(AL_GAIN, gain);
	}

	int playSound(const SimpleSoundSpec &spec)
	{
		maintain();
		if (spec.name.empty())
			return 0;
		SoundBuffer *buf = getFetchBuffer(spec.name);
		if(!buf){
			infostream << "OpenALSoundManager: \"" << spec.name << "\" not found."
					<< std::endl;
			return -1;
		}

		int handle = -1;
		if (spec.fade > 0) {
			handle = playSoundRaw(buf, spec.loop, 0.0f, spec.pitch);
			fadeSound(handle, spec.fade, spec.gain);
		} else {
			handle = playSoundRaw(buf, spec.loop, spec.gain, spec.pitch);
		}
		return handle;
	}

	int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos)
	{
		maintain();
		if (spec.name.empty())
			return 0;
		SoundBuffer *buf = getFetchBuffer(spec.name);
		if (!buf) {
			infostream << "OpenALSoundManager: \"" << spec.name << "\" not found."
					<< std::endl;
			return -1;
		}

		PlayingSound *sound = createPlayingSoundAt(buf, spec.loop, spec.gain, pos, spec.pitch);
		if (!sound)
			return -1;
		int handle = getFreeId();
		m_sounds_playing[handle] = sound;
		return handle;
	}

	void stopSound(int sound)
	{
		maintain();
		deleteSound(sound);
	}

	void fadeSound(int soundid, float step, float gain)
	{
		// Ignore the command if step isn't valid.
		if (step == 0 || soundid < 0)
			return;

		float current_gain = getSoundGain(soundid);
		step = gain - current_gain > 0 ? abs(step) : -abs(step);
		if (m_sounds_fading.find(soundid) != m_sounds_fading.end()) {
			auto current_fade = m_sounds_fading[soundid];
			// Do not replace the fade if it's equivalent.
			if (current_fade.target_gain == gain && current_fade.step == step)
				return;
			m_sounds_fading.erase(soundid);
		}
		gain = rangelim(gain, 0, 1);
		m_sounds_fading[soundid] = FadeState(step, current_gain, gain);
	}

	void doFades(float dtime)
	{
		for (auto i = m_sounds_fading.begin(); i != m_sounds_fading.end();) {
			FadeState& fade = i->second;
			assert(fade.step != 0);
			fade.current_gain += (fade.step * dtime);

			if (fade.step < 0.f)
				fade.current_gain = std::max(fade.current_gain, fade.target_gain);
			else
				fade.current_gain = std::min(fade.current_gain, fade.target_gain);

			if (fade.current_gain <= 0.f)
				stopSound(i->first);
			else
				updateSoundGain(i->first, fade.current_gain);

			// The increment must happen during the erase call, or else it'll segfault.
			if (fade.current_gain == fade.target_gain)
				m_sounds_fading.erase(i++);
			else
				i++;
		}
	}

	bool soundExists(int sound)
	{
		maintain();
		return (m_sounds_playing.count(sound) != 0);
	}

	void updateSoundPosition(int id, v3f pos)
	{
		auto i = m_sounds_playing.find(id);
		if (i == m_sounds_playing.end())
			return;
		PlayingSound *sound = i->second;

		alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
		alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
		alSource3f(sound->source_id, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
		alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 10.0f);
	}

	bool updateSoundGain(int id, float gain)
	{
		auto i = m_sounds_playing.find(id);
		if (i == m_sounds_playing.end())
			return false;

		PlayingSound *sound = i->second;
		alSourcef(sound->source_id, AL_GAIN, gain);
		return true;
	}

	float getSoundGain(int id)
	{
		auto i = m_sounds_playing.find(id);
		if (i == m_sounds_playing.end())
			return 0;

		PlayingSound *sound = i->second;
		ALfloat gain;
		alGetSourcef(sound->source_id, AL_GAIN, &gain);
		return gain;
	}
};

std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton()
{
	auto smg = std::make_shared<SoundManagerSingleton>();
	if (!smg->init()) {
		smg.reset();
	}
	return smg;
}

ISoundManager *createOpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher)
{
	return new OpenALSoundManager(smg, fetcher);
};