text: // Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
  // This file is part of the "Nazara Engine - Audio module"
  // For conditions of distribution and use, see copyright notice in Config.hpp
  
  #include <Nazara/Audio/OpenALSource.hpp>
  #include <Nazara/Audio/OpenALBuffer.hpp>
  #include <Nazara/Audio/OpenALLibrary.hpp>
  #include <Nazara/Core/Algorithm.hpp>
  #include <Nazara/Core/Error.hpp>
  #include <Nazara/Utils/StackArray.hpp>
  #include <Nazara/Audio/Debug.hpp>
  
  namespace Nz
  {
  	OpenALSource::~OpenALSource()
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alDeleteSources(1, &m_sourceId);
  	}
  
  	void OpenALSource::EnableLooping(bool loop)
  	{
  		GetDevice().MakeContextCurrent();
  		m_library.alSourcei(m_sourceId, AL_LOOPING, loop);
  	}
  
  	void OpenALSource::EnableSpatialization(bool spatialization)
  	{
  		GetDevice().MakeContextCurrent();
  		m_library.alSourcei(m_sourceId, AL_SOURCE_RELATIVE, !spatialization);
  	}
  
  	float OpenALSource::GetAttenuation() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALfloat attenuation;
  		m_library.alGetSourcefv(m_sourceId, AL_ROLLOFF_FACTOR, &attenuation);
  
  		return attenuation;
  	}
  
  	float OpenALSource::GetMinDistance() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALfloat minDistance;
  		m_library.alGetSourcefv(m_sourceId, AL_REFERENCE_DISTANCE, &minDistance);
  
  		return minDistance;
  	}
  
  	float OpenALSource::GetPitch() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALfloat pitch;
  		m_library.alGetSourcefv(m_sourceId, AL_PITCH, &pitch);
  
  		return pitch;
  	}
  
  	Vector3f OpenALSource::GetPosition() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		Vector3f position;
  		m_library.alGetSourcefv(m_sourceId, AL_POSITION, &position.x);
  
  		return position;
  	}
  
  	UInt32 OpenALSource::GetSampleOffset() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALint samples = 0;
  		m_library.alGetSourcei(m_sourceId, AL_SAMPLE_OFFSET, &samples);
  
  		return SafeCast<UInt32>(samples);
  	}
  
  	auto OpenALSource::GetSampleOffsetAndLatency() const -> OffsetWithLatency
  	{
  		OffsetWithLatency offsetWithLatency;
  		if (GetDevice().IsExtensionSupported(OpenALExtension::SourceLatency))
  		{
  			GetDevice().MakeContextCurrent();
  
  			std::array<ALint64SOFT, 2> values;
  			m_library.alGetSourcei64vSOFT(m_sourceId, AL_SAMPLE_OFFSET_LATENCY_SOFT, values.data());
  
  			offsetWithLatency.sampleOffset = ((values[0] & 0xFFFFFFFF00000000) >> 32) * 1'000;
  			offsetWithLatency.sourceLatency = values[1] / 1'000;
  
  		}
  		else
  		{
  			offsetWithLatency.sampleOffset = GetSampleOffset() * 1'000;
  			offsetWithLatency.sourceLatency = 0;
  		}
  
  		return offsetWithLatency;
  	}
  
  	Vector3f OpenALSource::GetVelocity() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		Vector3f velocity;
  		m_library.alGetSourcefv(m_sourceId, AL_VELOCITY, &velocity.x);
  
  		return velocity;
  	}
  
  	SoundStatus OpenALSource::GetStatus() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALint state;
  		m_library.alGetSourcei(m_sourceId, AL_SOURCE_STATE, &state);
  
  		switch (state)
  		{
  			case AL_INITIAL:
  			case AL_STOPPED:
  				return SoundStatus::Stopped;
  
  			case AL_PAUSED:
  				return SoundStatus::Paused;
  
  			case AL_PLAYING:
  				return SoundStatus::Playing;
  
  			default:
  				NazaraInternalError("Source state unrecognized");
  		}
  
  		return SoundStatus::Stopped;
  	}
  
  	float OpenALSource::GetVolume() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALfloat volume;
  		m_library.alGetSourcefv(m_sourceId, AL_GAIN, &volume);
  
  		return volume;
  	}
  
  	bool OpenALSource::IsLooping() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALint looping;
  		m_library.alGetSourcei(m_sourceId, AL_LOOPING, &looping);
  
  		return looping == AL_TRUE;
  	}
  
  	bool OpenALSource::IsSpatializationEnabled() const
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALint relative;
  		m_library.alGetSourcei(m_sourceId, AL_SOURCE_RELATIVE, &relative);
  
  		return relative == AL_FALSE;
  	}
  
  	void OpenALSource::QueueBuffer(std::shared_ptr<AudioBuffer> audioBuffer)
  	{
  		NazaraAssert(audioBuffer, "invalid buffer");
  		NazaraAssert(audioBuffer->IsCompatibleWith(*GetAudioDevice()), "incompatible buffer");
  
  		std::shared_ptr<OpenALBuffer> newBuffer = std::static_pointer_cast<OpenALBuffer>(std::move(audioBuffer));
  
  		GetDevice().MakeContextCurrent();
  
  		ALuint bufferId = newBuffer->GetBufferId();
  		m_library.alSourceQueueBuffers(m_sourceId, 1, &bufferId);
  
  		m_queuedBuffers.emplace_back(std::move(newBuffer));
  	}
  
  	void OpenALSource::Pause()
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSourcePause(m_sourceId);
  	}
  
  	void OpenALSource::Play()
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSourcePlay(m_sourceId);
  	}
  
  	void OpenALSource::SetAttenuation(float attenuation)
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSourcef(m_sourceId, AL_ROLLOFF_FACTOR, attenuation);
  	}
  
  	void OpenALSource::SetBuffer(std::shared_ptr<AudioBuffer> audioBuffer)
  	{
  		NazaraAssert(audioBuffer->IsCompatibleWith(*GetAudioDevice()), "incompatible buffer");
  
  		std::shared_ptr<OpenALBuffer> newBuffer = std::static_pointer_cast<OpenALBuffer>(std::move(audioBuffer));
  
  		GetDevice().MakeContextCurrent();
  
  		if (newBuffer)
  			m_library.alSourcei(m_sourceId, AL_BUFFER, newBuffer->GetBufferId());
  		else
  			m_library.alSourcei(m_sourceId, AL_BUFFER, AL_NONE);
  
  		m_currentBuffer = std::move(newBuffer);
  	}
  
  	void OpenALSource::SetMinDistance(float minDistance)
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSourcef(m_sourceId, AL_REFERENCE_DISTANCE, minDistance);
  	}
  
  	void OpenALSource::SetPitch(float pitch)
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSourcef(m_sourceId, AL_PITCH, pitch);
  	}
  
  	void OpenALSource::SetPosition(const Vector3f& position)
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSource3f(m_sourceId, AL_POSITION, position.x, position.y, position.z);
  	}
  
  	void OpenALSource::SetSampleOffset(UInt32 offset)
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSourcei(m_sourceId, AL_SAMPLE_OFFSET, offset);
  	}
  
  	void OpenALSource::SetVelocity(const Vector3f& velocity)
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSource3f(m_sourceId, AL_VELOCITY, velocity.x, velocity.y, velocity.z);
  	}
  
  	void OpenALSource::SetVolume(float volume)
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSourcef(m_sourceId, AL_GAIN, volume);
  	}
  
  	void OpenALSource::Stop()
  	{
  		GetDevice().MakeContextCurrent();
  
  		m_library.alSourceStop(m_sourceId);
  	}
  
  	std::shared_ptr<AudioBuffer> OpenALSource::TryUnqueueProcessedBuffer()
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALint processedCount = 0;
  		m_library.alGetSourcei(m_sourceId, AL_BUFFERS_PROCESSED, &processedCount);
  
  		if (processedCount == 0)
  			return {};
  
  		ALuint bufferId;
  		m_library.alSourceUnqueueBuffers(m_sourceId, 1, &bufferId);
  
  		auto it = std::find_if(m_queuedBuffers.begin(), m_queuedBuffers.end(), [=](const std::shared_ptr<OpenALBuffer>& alBuffer)
  		{
  			return alBuffer->GetBufferId() == bufferId;
  		});
  		assert(it != m_queuedBuffers.end());
  
  		std::shared_ptr<AudioBuffer> buffer = *it;
  		m_queuedBuffers.erase(it);
  
  		return buffer;
  	}
  
  	void OpenALSource::UnqueueAllBuffers()
  	{
  		GetDevice().MakeContextCurrent();
  
  		ALint queuedBufferCount = 0;
  		m_library.alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queuedBufferCount);
  
  		StackArray<ALuint> buffers = NazaraStackArrayNoInit(ALuint, queuedBufferCount);
  		m_library.alSourceUnqueueBuffers(m_sourceId, queuedBufferCount, buffers.data());
  
  		m_queuedBuffers.clear();
  	}
  
  	OpenALDevice& OpenALSource::GetDevice()
  	{
  		return SafeCast<OpenALDevice&>(*GetAudioDevice());
  	}
  
  	const OpenALDevice& OpenALSource::GetDevice() const
  	{
  		return SafeCast<OpenALDevice&>(*GetAudioDevice());
  	}
  }
  