Audio: Rewrite audio module

This commit is contained in:
Jérôme Leclercq
2022-03-17 09:07:52 +01:00
parent eb4629947e
commit 6165b3a101
43 changed files with 1898 additions and 1611 deletions

View File

@@ -3,10 +3,11 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Audio/Audio.hpp>
#include <Nazara/Audio/AudioBuffer.hpp>
#include <Nazara/Audio/AudioSource.hpp>
#include <Nazara/Audio/Config.hpp>
#include <Nazara/Audio/Enums.hpp>
#include <Nazara/Audio/OpenAL.hpp>
#include <Nazara/Audio/SoundBuffer.hpp>
#include <Nazara/Audio/OpenALLibrary.hpp>
#include <Nazara/Audio/Formats/drwavLoader.hpp>
#include <Nazara/Audio/Formats/libflacLoader.hpp>
#include <Nazara/Audio/Formats/libvorbisLoader.hpp>
@@ -20,6 +21,11 @@
namespace Nz
{
namespace
{
OpenALLibrary s_openalLibrary;
}
/*!
* \ingroup audio
* \class Nz::Audio
@@ -29,12 +35,9 @@ namespace Nz
Audio::Audio(Config /*config*/) :
ModuleBase("Audio", this)
{
// Initialisation of OpenAL
if (!OpenAL::Initialize())
throw std::runtime_error("failed to initialize OpenAL");
// Definition of the orientation by default
SetListenerDirection(Vector3f::Forward());
// Load OpenAL
if (!s_openalLibrary.Load())
throw std::runtime_error("failed to load OpenAL");
// Loaders
m_soundBufferLoader.RegisterLoader(Loaders::GetSoundBufferLoader_drwav());
@@ -45,88 +48,19 @@ namespace Nz
m_soundStreamLoader.RegisterLoader(Loaders::GetSoundStreamLoader_libvorbis());
m_soundBufferLoader.RegisterLoader(Loaders::GetSoundBufferLoader_minimp3());
m_soundStreamLoader.RegisterLoader(Loaders::GetSoundStreamLoader_minimp3());
m_defaultDevice = s_openalLibrary.OpenDevice();
}
Audio::~Audio()
{
OpenAL::Uninitialize();
m_defaultDevice.reset();
s_openalLibrary.Unload();
}
/*!
* \brief Gets the factor of the doppler effect
* \return Global factor of the doppler effect
*/
float Audio::GetDopplerFactor() const
const std::shared_ptr<AudioDevice>& Audio::GetDefaultDevice() const
{
return alGetFloat(AL_DOPPLER_FACTOR);
}
/*!
* \brief Gets the global volume
* \return Float between [0, inf) with 100.f being the default
*/
float Audio::GetGlobalVolume() const
{
ALfloat gain = 0.f;
alGetListenerf(AL_GAIN, &gain);
return gain * 100.f;
}
/*!
* \brief Gets the direction of the listener
* \return Direction of the listener, in front of the listener
*
* \see GetListenerRotation
*/
Vector3f Audio::GetListenerDirection() const
{
ALfloat orientation[6];
alGetListenerfv(AL_ORIENTATION, orientation);
return Vector3f(orientation[0], orientation[1], orientation[2]);
}
/*!
* \brief Gets the position of the listener
* \return Position of the listener
*
* \see GetListenerVelocity
*/
Vector3f Audio::GetListenerPosition() const
{
Vector3f position;
alGetListenerfv(AL_POSITION, &position.x);
return position;
}
/*!
* \brief Gets the rotation of the listener
* \return Rotation of the listener
*/
Quaternionf Audio::GetListenerRotation() const
{
ALfloat orientation[6];
alGetListenerfv(AL_ORIENTATION, orientation);
Vector3f forward(orientation[0], orientation[1], orientation[2]);
return Quaternionf::RotationBetween(Vector3f::Forward(), forward);
}
/*!
* \brief Gets the velocity of the listener
* \return Velocity of the listener
*
* \see GetListenerPosition
*/
Vector3f Audio::GetListenerVelocity() const
{
Vector3f velocity;
alGetListenerfv(AL_VELOCITY, &velocity.x);
return velocity;
return m_defaultDevice;
}
/*!
@@ -165,174 +99,19 @@ namespace Nz
return m_soundStreamLoader;
}
/*!
* \brief Gets the speed of sound
* \return Speed of sound
*/
float Audio::GetSpeedOfSound() const
std::shared_ptr<AudioDevice> Audio::OpenOutputDevice(const std::string& deviceName)
{
return alGetFloat(AL_SPEED_OF_SOUND);
return s_openalLibrary.OpenDevice(deviceName.c_str());
}
/*!
* \brief Checks whether the format is supported by the engine
* \return true if it is the case
*
* \param format Format to check
*/
bool Audio::IsFormatSupported(AudioFormat format) const
std::vector<std::string> Audio::QueryInputDevices() const
{
if (format == AudioFormat::Unknown)
return false;
return OpenAL::AudioFormat[UnderlyingCast(format)] != 0;
return s_openalLibrary.QueryInputDevices();
}
/*!
* \brief Sets the factor of the doppler effect
*
* \param dopplerFactor Global factor of the doppler effect
*/
void Audio::SetDopplerFactor(float dopplerFactor)
std::vector<std::string> Audio::QueryOutputDevices() const
{
alDopplerFactor(dopplerFactor);
}
/*!
* \brief Sets the global volume
*
* \param volume Float between [0, inf) with 100.f being the default
*/
void Audio::SetGlobalVolume(float volume)
{
alListenerf(AL_GAIN, volume * 0.01f);
}
/*!
* \brief Sets the direction of the listener
*
* \param direction Direction of the listener, in front of the listener
*
* \see SetListenerDirection, SetListenerRotation
*/
void Audio::SetListenerDirection(const Vector3f& direction)
{
Vector3f up = Vector3f::Up();
ALfloat orientation[6] =
{
direction.x, direction.y, direction.z,
up.x, up.y, up.z
};
alListenerfv(AL_ORIENTATION, orientation);
}
/*!
* \brief Sets the direction of the listener
*
* \param (dirX, dirY, dirZ) Direction of the listener, in front of the listener
*
* \see SetListenerDirection, SetListenerRotation
*/
void Audio::SetListenerDirection(float dirX, float dirY, float dirZ)
{
Vector3f up = Vector3f::Up();
ALfloat orientation[6] =
{
dirX, dirY, dirZ,
up.x, up.y, up.z
};
alListenerfv(AL_ORIENTATION, orientation);
}
/*!
* \brief Sets the position of the listener
*
* \param position Position of the listener
*
* \see SetListenerVelocity
*/
void Audio::SetListenerPosition(const Vector3f& position)
{
alListenerfv(AL_POSITION, &position.x);
}
/*!
* \brief Sets the position of the listener
*
* \param (x, y, z) Position of the listener
*
* \see SetListenerVelocity
*/
void Audio::SetListenerPosition(float x, float y, float z)
{
alListener3f(AL_POSITION, x, y, z);
}
/*!
* \brief Sets the rotation of the listener
*
* \param rotation Rotation of the listener
*/
void Audio::SetListenerRotation(const Quaternionf& rotation)
{
Vector3f forward = rotation * Vector3f::Forward();
Vector3f up = Vector3f::Up();
ALfloat orientation[6] =
{
forward.x, forward.y, forward.z,
up.x, up.y, up.z
};
alListenerfv(AL_ORIENTATION, orientation);
}
/*!
* \brief Sets the velocity of the listener
*
* \param velocity Velocity of the listener
*
* \see SetListenerPosition
*/
void Audio::SetListenerVelocity(const Vector3f& velocity)
{
alListenerfv(AL_VELOCITY, &velocity.x);
}
/*!
* \brief Sets the velocity of the listener
*
* \param (velX, velY, velZ) Velocity of the listener
*
* \see SetListenerPosition
*/
void Audio::SetListenerVelocity(float velX, float velY, float velZ)
{
alListener3f(AL_VELOCITY, velX, velY, velZ);
}
/*!
* \brief Sets the speed of sound
*
* \param speed Speed of sound
*/
void Audio::SetSpeedOfSound(float speed)
{
alSpeedOfSound(speed);
return s_openalLibrary.QueryOutputDevices();
}
Audio* Audio::s_instance = nullptr;

View File

@@ -0,0 +1,11 @@
// 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/AudioBuffer.hpp>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
AudioBuffer::~AudioBuffer() = default;
}

View File

@@ -0,0 +1,11 @@
// 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/AudioDevice.hpp>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
AudioDevice::~AudioDevice() = default;
}

View File

@@ -0,0 +1,11 @@
// 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/AudioSource.hpp>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
AudioSource::~AudioSource() = default;
}

View File

@@ -4,15 +4,14 @@
#include <Nazara/Audio/Music.hpp>
#include <Nazara/Audio/Algorithm.hpp>
#include <Nazara/Audio/OpenAL.hpp>
#include <Nazara/Audio/Audio.hpp>
#include <Nazara/Audio/AudioBuffer.hpp>
#include <Nazara/Audio/AudioDevice.hpp>
#include <Nazara/Audio/AudioSource.hpp>
#include <Nazara/Audio/SoundStream.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <array>
#include <atomic>
#include <chrono>
#include <memory>
#include <thread>
#include <vector>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
@@ -25,22 +24,18 @@ namespace Nz
* \remark Module Audio needs to be initialized to use this class
*/
struct MusicImpl
Music::Music() :
Music(*Audio::Instance()->GetDefaultDevice())
{
ALenum audioFormat;
std::atomic_bool streaming = false;
std::atomic<UInt64> processedSamples;
std::vector<Int16> chunkSamples;
std::mutex bufferLock;
std::shared_ptr<SoundStream> stream;
std::thread thread;
UInt64 streamOffset;
bool loop = false;
unsigned int sampleRate;
};
Music::Music() = default;
Music::Music(Music&&) noexcept = default;
}
Music::Music(AudioDevice& device) :
SoundEmitter(device),
m_streaming(false),
m_bufferCount(2),
m_looping(false)
{
}
/*!
* \brief Destructs the object and calls Destroy
@@ -54,11 +49,10 @@ namespace Nz
/*!
* \brief Creates a music with a sound stream
* \return true if creation was succesful
* \return true if creation was successful
*
* \param soundStream Sound stream which is the source for the music
*/
bool Music::Create(std::shared_ptr<SoundStream> soundStream)
{
NazaraAssert(soundStream, "Invalid stream");
@@ -67,11 +61,10 @@ namespace Nz
AudioFormat format = soundStream->GetFormat();
m_impl = std::make_unique<MusicImpl>();
m_impl->sampleRate = soundStream->GetSampleRate();
m_impl->audioFormat = OpenAL::AudioFormat[UnderlyingCast(format)];
m_impl->chunkSamples.resize(GetChannelCount(format) * m_impl->sampleRate); // One second of samples
m_impl->stream = std::move(soundStream);
m_sampleRate = soundStream->GetSampleRate();
m_audioFormat = soundStream->GetFormat();
m_chunkSamples.resize(GetChannelCount(format) * m_sampleRate); // One second of samples
m_stream = std::move(soundStream);
SetPlayingOffset(0);
@@ -85,12 +78,7 @@ namespace Nz
*/
void Music::Destroy()
{
if (m_impl)
{
StopThread();
m_impl.reset();
}
StopThread();
}
/*!
@@ -102,9 +90,7 @@ namespace Nz
*/
void Music::EnableLooping(bool loop)
{
NazaraAssert(m_impl, "Music not created");
m_impl->loop = loop;
m_looping = loop;
}
/*!
@@ -115,9 +101,9 @@ namespace Nz
*/
UInt32 Music::GetDuration() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
return m_impl->stream->GetDuration();
return m_stream->GetDuration();
}
/*!
@@ -128,9 +114,9 @@ namespace Nz
*/
AudioFormat Music::GetFormat() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
return m_impl->stream->GetFormat();
return m_stream->GetFormat();
}
/*!
@@ -141,15 +127,14 @@ namespace Nz
*/
UInt32 Music::GetPlayingOffset() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
// Prevent music thread from enqueing new buffers while we're getting the count
std::lock_guard<std::mutex> lock(m_impl->bufferLock);
// Prevent music thread from enqueuing new buffers while we're getting the count
std::lock_guard<std::mutex> lock(m_bufferLock);
ALint samples = 0;
alGetSourcei(m_source, AL_SAMPLE_OFFSET, &samples);
UInt32 sampleOffset = m_source->GetSampleOffset();
return static_cast<UInt32>((1000ULL * (samples + (m_impl->processedSamples / GetChannelCount(m_impl->stream->GetFormat())))) / m_impl->sampleRate);
return static_cast<UInt32>((1000ULL * (sampleOffset + (m_processedSamples / GetChannelCount(m_stream->GetFormat())))) / m_sampleRate);
}
/*!
@@ -160,9 +145,9 @@ namespace Nz
*/
UInt64 Music::GetSampleCount() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
return m_impl->stream->GetSampleCount();
return m_stream->GetSampleCount();
}
/*!
@@ -173,9 +158,9 @@ namespace Nz
*/
UInt32 Music::GetSampleRate() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
return m_impl->sampleRate;
return m_sampleRate;
}
/*!
@@ -186,12 +171,12 @@ namespace Nz
*/
SoundStatus Music::GetStatus() const
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
SoundStatus status = GetInternalStatus();
SoundStatus status = m_source->GetStatus();
// To compensate any delays (or the timelaps between Play() and the thread startup)
if (m_impl->streaming && status == SoundStatus::Stopped)
if (m_streaming && status == SoundStatus::Stopped)
status = SoundStatus::Playing;
return status;
@@ -205,9 +190,7 @@ namespace Nz
*/
bool Music::IsLooping() const
{
NazaraAssert(m_impl, "Music not created");
return m_impl->loop;
return m_looping;
}
/*!
@@ -267,9 +250,7 @@ namespace Nz
*/
void Music::Pause()
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcePause(m_source);
m_source->Pause();
}
/*!
@@ -284,10 +265,10 @@ namespace Nz
*/
void Music::Play()
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
// Maybe we are already playing
if (m_impl->streaming)
if (m_streaming)
{
switch (GetStatus())
{
@@ -296,7 +277,7 @@ namespace Nz
break;
case SoundStatus::Paused:
alSourcePlay(m_source);
m_source->Play();
break;
default:
@@ -309,12 +290,12 @@ namespace Nz
std::condition_variable cv;
// Starting streaming thread
m_impl->streaming = true;
m_streaming = true;
std::exception_ptr exceptionPtr;
std::unique_lock<std::mutex> lock(mutex);
m_impl->thread = std::thread(&Music::MusicThread, this, std::ref(cv), std::ref(mutex), std::ref(exceptionPtr));
m_thread = std::thread(&Music::MusicThread, this, std::ref(cv), std::ref(mutex), std::ref(exceptionPtr));
// Wait until thread signal it has properly started (or an error occurred)
cv.wait(lock);
@@ -335,17 +316,17 @@ namespace Nz
*/
void Music::SetPlayingOffset(UInt32 offset)
{
NazaraAssert(m_impl, "Music not created");
NazaraAssert(m_stream, "Music not created");
bool isPlaying = m_impl->streaming;
bool isPlaying = m_streaming;
if (isPlaying)
Stop();
UInt64 sampleOffset = UInt64(offset) * m_impl->sampleRate * GetChannelCount(m_impl->stream->GetFormat()) / 1000ULL;
UInt64 sampleOffset = UInt64(offset) * m_sampleRate * GetChannelCount(m_stream->GetFormat()) / 1000ULL;
m_impl->processedSamples = sampleOffset;
m_impl->streamOffset = sampleOffset;
m_processedSamples = sampleOffset;
m_streamOffset = sampleOffset;
if (isPlaying)
Play();
@@ -358,31 +339,27 @@ namespace Nz
*/
void Music::Stop()
{
NazaraAssert(m_impl, "Music not created");
StopThread();
SetPlayingOffset(0);
}
Music& Music::operator=(Music&&) noexcept = default;
bool Music::FillAndQueueBuffer(unsigned int buffer)
bool Music::FillAndQueueBuffer(std::shared_ptr<AudioBuffer> buffer)
{
std::size_t sampleCount = m_impl->chunkSamples.size();
std::size_t sampleCount = m_chunkSamples.size();
std::size_t sampleRead = 0;
{
std::lock_guard<std::mutex> lock(m_impl->stream->GetMutex());
std::lock_guard<std::mutex> lock(m_stream->GetMutex());
m_impl->stream->Seek(m_impl->streamOffset);
m_stream->Seek(m_streamOffset);
// Fill the buffer by reading from the stream
for (;;)
{
sampleRead += m_impl->stream->Read(&m_impl->chunkSamples[sampleRead], sampleCount - sampleRead);
if (sampleRead < sampleCount && m_impl->loop)
sampleRead += m_stream->Read(&m_chunkSamples[sampleRead], sampleCount - sampleRead);
if (sampleRead < sampleCount && m_looping)
{
// In case we read less than expected, assume we reached the end of the stream and seek back to the beginning
m_impl->stream->Seek(0);
m_stream->Seek(0);
continue;
}
@@ -390,14 +367,14 @@ namespace Nz
break;
}
m_impl->streamOffset = m_impl->stream->Tell();
m_streamOffset = m_stream->Tell();
}
// Update the buffer (send it to OpenAL) and queue it if we got any data
// Update the buffer on the AudioDevice and queue it if we got any data
if (sampleRead > 0)
{
alBufferData(buffer, m_impl->audioFormat, &m_impl->chunkSamples[0], static_cast<ALsizei>(sampleRead*sizeof(Int16)), static_cast<ALsizei>(m_impl->sampleRate));
alSourceQueueBuffers(m_source, 1, &buffer);
buffer->Reset(m_audioFormat, sampleRead, m_sampleRate, &m_chunkSamples[0]);
m_source->QueueBuffer(buffer);
}
return sampleRead != sampleCount; // End of stream (Does not happen when looping)
@@ -406,20 +383,18 @@ namespace Nz
void Music::MusicThread(std::condition_variable& cv, std::mutex& m, std::exception_ptr& err)
{
// Allocation of streaming buffers
std::array<ALuint, NAZARA_AUDIO_STREAMED_BUFFER_COUNT> buffers;
alGenBuffers(NAZARA_AUDIO_STREAMED_BUFFER_COUNT, buffers.data());
CallOnExit freebuffers([&]
CallOnExit unqueueBuffers([&]
{
alDeleteBuffers(NAZARA_AUDIO_STREAMED_BUFFER_COUNT, buffers.data());
m_source->UnqueueAllBuffers();
});
try
{
for (ALuint buffer : buffers)
for (std::size_t i = 0; i < m_bufferCount; ++i)
{
if (FillAndQueueBuffer(buffer))
std::shared_ptr<AudioBuffer> buffer = m_source->GetAudioDevice()->CreateBuffer();
if (FillAndQueueBuffer(std::move(buffer)))
break; // We have reached the end of the stream, there is no use to add new buffers
}
}
@@ -432,23 +407,12 @@ namespace Nz
return;
}
CallOnExit unqueueBuffers([&]
{
// We delete buffers from the stream
ALint queuedBufferCount;
alGetSourcei(m_source, AL_BUFFERS_QUEUED, &queuedBufferCount);
m_source->Play();
ALuint buffer;
for (ALint i = 0; i < queuedBufferCount; ++i)
alSourceUnqueueBuffers(m_source, 1, &buffer);
});
alSourcePlay(m_source);
CallOnExit stopSource([&]
{
// Stop playing of the sound (in the case where it has not been already done)
alSourceStop(m_source);
m_source->Stop();
});
// Signal we're good
@@ -458,35 +422,25 @@ namespace Nz
} // m & cv no longer exists from here
// Reading loop (Filling new buffers as playing)
while (m_impl->streaming)
while (m_streaming)
{
// The reading has stopped, we have reached the end of the stream
SoundStatus status = GetInternalStatus();
SoundStatus status = m_source->GetStatus();
if (status == SoundStatus::Stopped)
{
m_impl->streaming = false;
// The reading has stopped, we have reached the end of the stream
m_streaming = false;
break;
}
{
std::lock_guard<std::mutex> lock(m_impl->bufferLock);
std::lock_guard<std::mutex> lock(m_bufferLock);
// We treat read buffers
ALint processedCount = 0;
alGetSourcei(m_source, AL_BUFFERS_PROCESSED, &processedCount);
while (processedCount--)
while (std::shared_ptr<AudioBuffer> buffer = m_source->TryUnqueueProcessedBuffer())
{
ALuint buffer;
alSourceUnqueueBuffers(m_source, 1, &buffer);
m_processedSamples += buffer->GetSampleCount();
ALint bits, size;
alGetBufferi(buffer, AL_BITS, &bits);
alGetBufferi(buffer, AL_SIZE, &size);
if (bits != 0)
m_impl->processedSamples += (8 * size) / bits;
if (FillAndQueueBuffer(buffer))
if (FillAndQueueBuffer(std::move(buffer)))
break;
}
}
@@ -498,10 +452,10 @@ namespace Nz
void Music::StopThread()
{
if (m_impl->streaming)
m_impl->streaming = false;
if (m_streaming)
m_streaming = false;
if (m_impl->thread.joinable())
m_impl->thread.join();
if (m_thread.joinable())
m_thread.join();
}
}

View File

@@ -1,577 +0,0 @@
// 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/OpenAL.hpp>
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Core/DynLib.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Core/StringExt.hpp>
#include <cstring>
#include <sstream>
#include <stdexcept>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
namespace
{
DynLib s_openalLbrary;
std::string s_openalDeviceName;
std::string s_openalRndererName;
std::string s_openalVendorName;
ALCdevice* s_openalDevice = nullptr;
ALCcontext* s_openalContext = nullptr;
unsigned int s_openalVersion;
std::size_t ParseOpenALDevices(const char* deviceString, std::vector<std::string>& devices)
{
if (!deviceString)
return 0;
std::size_t startSize = devices.size();
std::size_t length;
while ((length = std::strlen(deviceString)) > 0)
{
devices.emplace_back(deviceString, length);
deviceString += length + 1;
}
return devices.size() - startSize;
}
}
/*!
* \ingroup audio
* \class Nz::OpenAL
* \brief Audio class that represents the link with OpenAL
*
* \remark This class is meant to be used by Module Audio
*/
/*!
* \brief Gets the entry for the function name
* \return Pointer to the function
*
* \param entryPoint Name of the entry
*
* \remark This does not produces a NazaraError if entry does not exist
*/
OpenALFunc OpenAL::GetEntry(const std::string& entryPoint)
{
return LoadEntry(entryPoint.data(), false);
}
/*!
* \brief Gets the name of the renderer
* \return Name of the renderer
*/
std::string OpenAL::GetRendererName()
{
return s_openalRndererName;
}
/*!
* \brief Gets the name of the vendor
* \return Name of the vendor
*/
std::string OpenAL::GetVendorName()
{
return s_openalVendorName;
}
/*!
* \brief Gets the version of OpenAL
* \return Version of OpenAL
*/
unsigned int OpenAL::GetVersion()
{
return s_openalVersion;
}
/*!
* \brief Initializes the module OpenAL
* \return true if initialization is successful
*
* \param openDevice True to get information from the device
*
* \remark Produces a NazaraError if one of the entry failed
* \remark Produces a NazaraError if opening device failed with openDevice parameter set to true
*/
bool OpenAL::Initialize(bool openDevice)
{
if (IsInitialized())
return true;
#if defined(NAZARA_PLATFORM_WINDOWS)
///FIXME: Is OpenAL Soft a better implementation than Creative ?
/// If we could use OpenAL Soft everytime, this would allow us to use sonorous extensions
/// and give us more technical possibilities with audio
const char* libs[] = {
"soft_oal.dll",
"wrap_oal.dll",
"openal32.dll"
};
#elif defined(NAZARA_PLATFORM_LINUX)
const char* libs[] = {
"libopenal.so.1",
"libopenal.so.0",
"libopenal.so"
};
#elif defined(NAZARA_PLATFORM_MACOSX)
const char* libs[] = {
"libopenal.dylib",
"libopenal.1.dylib",
};
#else
NazaraError("Unknown OS");
return false;
#endif
bool succeeded = false;
for (const char* path : libs)
{
ErrorFlags errFlags(ErrorMode::Silent);
std::filesystem::path libPath(path);
if (!s_openalLbrary.Load(libPath))
continue;
errFlags.SetFlags(0);
try
{
// al
alBuffer3f = reinterpret_cast<OpenALDetail::LPALBUFFER3F>(LoadEntry("alBuffer3f"));
alBuffer3i = reinterpret_cast<OpenALDetail::LPALBUFFER3I>(LoadEntry("alBuffer3i"));
alBufferData = reinterpret_cast<OpenALDetail::LPALBUFFERDATA>(LoadEntry("alBufferData"));
alBufferf = reinterpret_cast<OpenALDetail::LPALBUFFERF>(LoadEntry("alBufferf"));
alBufferfv = reinterpret_cast<OpenALDetail::LPALBUFFERFV>(LoadEntry("alBufferfv"));
alBufferi = reinterpret_cast<OpenALDetail::LPALBUFFERI>(LoadEntry("alBufferi"));
alBufferiv = reinterpret_cast<OpenALDetail::LPALBUFFERIV>(LoadEntry("alBufferiv"));
alDeleteBuffers = reinterpret_cast<OpenALDetail::LPALDELETEBUFFERS>(LoadEntry("alDeleteBuffers"));
alDeleteSources = reinterpret_cast<OpenALDetail::LPALDELETESOURCES>(LoadEntry("alDeleteSources"));
alDisable = reinterpret_cast<OpenALDetail::LPALDISABLE>(LoadEntry("alDisable"));
alDistanceModel = reinterpret_cast<OpenALDetail::LPALDISTANCEMODEL>(LoadEntry("alDistanceModel"));
alDopplerFactor = reinterpret_cast<OpenALDetail::LPALDOPPLERFACTOR>(LoadEntry("alDopplerFactor"));
alDopplerVelocity = reinterpret_cast<OpenALDetail::LPALDOPPLERVELOCITY>(LoadEntry("alDopplerVelocity"));
alEnable = reinterpret_cast<OpenALDetail::LPALENABLE>(LoadEntry("alEnable"));
alGenBuffers = reinterpret_cast<OpenALDetail::LPALGENBUFFERS>(LoadEntry("alGenBuffers"));
alGenSources = reinterpret_cast<OpenALDetail::LPALGENSOURCES>(LoadEntry("alGenSources"));
alGetBoolean = reinterpret_cast<OpenALDetail::LPALGETBOOLEAN>(LoadEntry("alGetBoolean"));
alGetBooleanv = reinterpret_cast<OpenALDetail::LPALGETBOOLEANV>(LoadEntry("alGetBooleanv"));
alGetBuffer3f = reinterpret_cast<OpenALDetail::LPALGETBUFFER3F>(LoadEntry("alGetBuffer3f"));
alGetBuffer3i = reinterpret_cast<OpenALDetail::LPALGETBUFFER3I>(LoadEntry("alGetBuffer3i"));
alGetBufferf = reinterpret_cast<OpenALDetail::LPALGETBUFFERF>(LoadEntry("alGetBufferf"));
alGetBufferfv = reinterpret_cast<OpenALDetail::LPALGETBUFFERFV>(LoadEntry("alGetBufferfv"));
alGetBufferi = reinterpret_cast<OpenALDetail::LPALGETBUFFERI>(LoadEntry("alGetBufferi"));
alGetBufferiv = reinterpret_cast<OpenALDetail::LPALGETBUFFERIV>(LoadEntry("alGetBufferiv"));
alGetDouble = reinterpret_cast<OpenALDetail::LPALGETDOUBLE>(LoadEntry("alGetDouble"));
alGetDoublev = reinterpret_cast<OpenALDetail::LPALGETDOUBLEV>(LoadEntry("alGetDoublev"));
alGetEnumValue = reinterpret_cast<OpenALDetail::LPALGETENUMVALUE>(LoadEntry("alGetEnumValue"));
alGetError = reinterpret_cast<OpenALDetail::LPALGETERROR>(LoadEntry("alGetError"));
alGetFloat = reinterpret_cast<OpenALDetail::LPALGETFLOAT>(LoadEntry("alGetFloat"));
alGetFloatv = reinterpret_cast<OpenALDetail::LPALGETFLOATV>(LoadEntry("alGetFloatv"));
alGetInteger = reinterpret_cast<OpenALDetail::LPALGETINTEGER>(LoadEntry("alGetInteger"));
alGetIntegerv = reinterpret_cast<OpenALDetail::LPALGETINTEGERV>(LoadEntry("alGetIntegerv"));
alGetListener3f = reinterpret_cast<OpenALDetail::LPALGETLISTENER3F>(LoadEntry("alGetListener3f"));
alGetListener3i = reinterpret_cast<OpenALDetail::LPALGETLISTENER3I>(LoadEntry("alGetListener3i"));
alGetListenerf = reinterpret_cast<OpenALDetail::LPALGETLISTENERF>(LoadEntry("alGetListenerf"));
alGetListenerfv = reinterpret_cast<OpenALDetail::LPALGETLISTENERFV>(LoadEntry("alGetListenerfv"));
alGetListeneri = reinterpret_cast<OpenALDetail::LPALGETLISTENERI>(LoadEntry("alGetListeneri"));
alGetListeneriv = reinterpret_cast<OpenALDetail::LPALGETLISTENERIV>(LoadEntry("alGetListeneriv"));
alGetProcAddress = reinterpret_cast<OpenALDetail::LPALGETPROCADDRESS>(LoadEntry("alGetProcAddress"));
alGetSource3f = reinterpret_cast<OpenALDetail::LPALGETSOURCE3F>(LoadEntry("alGetSource3f"));
alGetSource3i = reinterpret_cast<OpenALDetail::LPALGETSOURCE3I>(LoadEntry("alGetSource3i"));
alGetSourcef = reinterpret_cast<OpenALDetail::LPALGETSOURCEF>(LoadEntry("alGetSourcef"));
alGetSourcefv = reinterpret_cast<OpenALDetail::LPALGETSOURCEFV>(LoadEntry("alGetSourcefv"));
alGetSourcei = reinterpret_cast<OpenALDetail::LPALGETSOURCEI>(LoadEntry("alGetSourcei"));
alGetSourceiv = reinterpret_cast<OpenALDetail::LPALGETSOURCEIV>(LoadEntry("alGetSourceiv"));
alGetString = reinterpret_cast<OpenALDetail::LPALGETSTRING>(LoadEntry("alGetString"));
alIsBuffer = reinterpret_cast<OpenALDetail::LPALISBUFFER>(LoadEntry("alIsBuffer"));
alIsEnabled = reinterpret_cast<OpenALDetail::LPALISENABLED>(LoadEntry("alIsEnabled"));
alIsExtensionPresent = reinterpret_cast<OpenALDetail::LPALISEXTENSIONPRESENT>(LoadEntry("alIsExtensionPresent"));
alIsSource = reinterpret_cast<OpenALDetail::LPALISSOURCE>(LoadEntry("alIsSource"));
alListener3f = reinterpret_cast<OpenALDetail::LPALLISTENER3F>(LoadEntry("alListener3f"));
alListener3i = reinterpret_cast<OpenALDetail::LPALLISTENER3I>(LoadEntry("alListener3i"));
alListenerf = reinterpret_cast<OpenALDetail::LPALLISTENERF>(LoadEntry("alListenerf"));
alListenerfv = reinterpret_cast<OpenALDetail::LPALLISTENERFV>(LoadEntry("alListenerfv"));
alListeneri = reinterpret_cast<OpenALDetail::LPALLISTENERI>(LoadEntry("alListeneri"));
alListeneriv = reinterpret_cast<OpenALDetail::LPALLISTENERIV>(LoadEntry("alListeneriv"));
alSource3f = reinterpret_cast<OpenALDetail::LPALSOURCE3F>(LoadEntry("alSource3f"));
alSource3i = reinterpret_cast<OpenALDetail::LPALSOURCE3I>(LoadEntry("alSource3i"));
alSourcef = reinterpret_cast<OpenALDetail::LPALSOURCEF>(LoadEntry("alSourcef"));
alSourcefv = reinterpret_cast<OpenALDetail::LPALSOURCEFV>(LoadEntry("alSourcefv"));
alSourcei = reinterpret_cast<OpenALDetail::LPALSOURCEI>(LoadEntry("alSourcei"));
alSourceiv = reinterpret_cast<OpenALDetail::LPALSOURCEIV>(LoadEntry("alSourceiv"));
alSourcePause = reinterpret_cast<OpenALDetail::LPALSOURCEPAUSE>(LoadEntry("alSourcePause"));
alSourcePausev = reinterpret_cast<OpenALDetail::LPALSOURCEPAUSEV>(LoadEntry("alSourcePausev"));
alSourcePlay = reinterpret_cast<OpenALDetail::LPALSOURCEPLAY>(LoadEntry("alSourcePlay"));
alSourcePlayv = reinterpret_cast<OpenALDetail::LPALSOURCEPLAYV>(LoadEntry("alSourcePlayv"));
alSourceQueueBuffers = reinterpret_cast<OpenALDetail::LPALSOURCEQUEUEBUFFERS>(LoadEntry("alSourceQueueBuffers"));
alSourceRewind = reinterpret_cast<OpenALDetail::LPALSOURCEREWIND>(LoadEntry("alSourceRewind"));
alSourceRewindv = reinterpret_cast<OpenALDetail::LPALSOURCEREWINDV>(LoadEntry("alSourceRewindv"));
alSourceStop = reinterpret_cast<OpenALDetail::LPALSOURCESTOP>(LoadEntry("alSourceStop"));
alSourceStopv = reinterpret_cast<OpenALDetail::LPALSOURCESTOPV>(LoadEntry("alSourceStopv"));
alSourceUnqueueBuffers = reinterpret_cast<OpenALDetail::LPALSOURCEUNQUEUEBUFFERS>(LoadEntry("alSourceUnqueueBuffers"));
alSpeedOfSound = reinterpret_cast<OpenALDetail::LPALSPEEDOFSOUND>(LoadEntry("alSpeedOfSound"));
// alc
alcCaptureCloseDevice = reinterpret_cast<OpenALDetail::LPALCCAPTURECLOSEDEVICE>(LoadEntry("alcCaptureCloseDevice"));
alcCaptureOpenDevice = reinterpret_cast<OpenALDetail::LPALCCAPTUREOPENDEVICE>(LoadEntry("alcCaptureOpenDevice"));
alcCaptureSamples = reinterpret_cast<OpenALDetail::LPALCCAPTURESAMPLES>(LoadEntry("alcCaptureSamples"));
alcCaptureStart = reinterpret_cast<OpenALDetail::LPALCCAPTURESTART>(LoadEntry("alcCaptureStart"));
alcCaptureStop = reinterpret_cast<OpenALDetail::LPALCCAPTURESTOP>(LoadEntry("alcCaptureStop"));
alcCloseDevice = reinterpret_cast<OpenALDetail::LPALCCLOSEDEVICE>(LoadEntry("alcCloseDevice"));
alcCreateContext = reinterpret_cast<OpenALDetail::LPALCCREATECONTEXT>(LoadEntry("alcCreateContext"));
alcDestroyContext = reinterpret_cast<OpenALDetail::LPALCDESTROYCONTEXT>(LoadEntry("alcDestroyContext"));
alcGetContextsDevice = reinterpret_cast<OpenALDetail::LPALCGETCONTEXTSDEVICE>(LoadEntry("alcGetContextsDevice"));
alcGetCurrentContext = reinterpret_cast<OpenALDetail::LPALCGETCURRENTCONTEXT>(LoadEntry("alcGetCurrentContext"));
alcGetEnumValue = reinterpret_cast<OpenALDetail::LPALCGETENUMVALUE>(LoadEntry("alcGetEnumValue"));
alcGetError = reinterpret_cast<OpenALDetail::LPALCGETERROR>(LoadEntry("alcGetError"));
alcGetIntegerv = reinterpret_cast<OpenALDetail::LPALCGETINTEGERV>(LoadEntry("alcGetIntegerv"));
alcGetProcAddress = reinterpret_cast<OpenALDetail::LPALCGETPROCADDRESS>(LoadEntry("alcGetProcAddress"));
alcGetString = reinterpret_cast<OpenALDetail::LPALCGETSTRING>(LoadEntry("alcGetString"));
alcIsExtensionPresent = reinterpret_cast<OpenALDetail::LPALCISEXTENSIONPRESENT>(LoadEntry("alcIsExtensionPresent"));
alcMakeContextCurrent = reinterpret_cast<OpenALDetail::LPALCMAKECONTEXTCURRENT>(LoadEntry("alcMakeContextCurrent"));
alcOpenDevice = reinterpret_cast<OpenALDetail::LPALCOPENDEVICE>(LoadEntry("alcOpenDevice"));
alcProcessContext = reinterpret_cast<OpenALDetail::LPALCPROCESSCONTEXT>(LoadEntry("alcProcessContext"));
alcSuspendContext = reinterpret_cast<OpenALDetail::LPALCSUSPENDCONTEXT>(LoadEntry("alcSuspendContext"));
succeeded = true;
break;
}
catch (const std::exception& e)
{
NazaraWarning(libPath.generic_u8string() + " loading failed: " + std::string(e.what()));
continue;
}
}
if (!succeeded)
{
NazaraError("Failed to load OpenAL");
Uninitialize();
return false;
}
if (openDevice)
OpenDevice();
return true;
}
/*!
* \brief Checks whether the module is initialized
* \return true if it is the case
*/
bool OpenAL::IsInitialized()
{
return s_openalLbrary.IsLoaded();
}
/*!
* \brief Queries the input devices
* \return Number of devices
*
* \param devices List of names of the input devices
*/
std::size_t OpenAL::QueryInputDevices(std::vector<std::string>& devices)
{
const char* deviceString = reinterpret_cast<const char*>(alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER));
if (!deviceString)
return 0;
return ParseOpenALDevices(deviceString, devices);
}
/*!
* \brief Queries the output devices
* \return Number of devices
*
* \param devices List of names of the output devices
*/
std::size_t OpenAL::QueryOutputDevices(std::vector<std::string>& devices)
{
const char* deviceString = reinterpret_cast<const char*>(alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER));
if (!deviceString)
return 0;
return ParseOpenALDevices(deviceString, devices);
}
/*!
* \brief Sets the active device
* \return true if device is successfully opened
*
* \param deviceName Name of the device
*/
bool OpenAL::SetDevice(const std::string& deviceName)
{
s_openalDeviceName = deviceName;
if (IsInitialized())
{
CloseDevice();
return OpenDevice();
}
else
return true;
}
/*!
* \brief Uninitializes the module
*/
void OpenAL::Uninitialize()
{
CloseDevice();
s_openalRndererName.clear();
s_openalVendorName.clear();
s_openalLbrary.Unload();
}
ALenum OpenAL::AudioFormat[AudioFormatCount] = {0}; // Added values with loading of OpenAL
/*!
* \brief Closes the device
*
* \remark Produces a NazaraWarning if you try to close an active device
*/
void OpenAL::CloseDevice()
{
if (s_openalDevice)
{
if (s_openalContext)
{
alcMakeContextCurrent(nullptr);
alcDestroyContext(s_openalContext);
s_openalContext = nullptr;
}
if (!alcCloseDevice(s_openalDevice))
// We could not close the close, this means that it's still in use
NazaraWarning("Failed to close device");
s_openalDevice = nullptr;
}
}
/*!
* \brief Opens the device
* \return true if open is successful
*
* \remark Produces a NazaraError if it could not create the context
*/
bool OpenAL::OpenDevice()
{
// Initialisation of the module
s_openalDevice = alcOpenDevice(s_openalDeviceName.empty() ? nullptr : s_openalDeviceName.data()); // We choose the default device
if (!s_openalDevice)
{
NazaraError("Failed to open default device");
return false;
}
// One context is enough
s_openalContext = alcCreateContext(s_openalDevice, nullptr);
if (!s_openalContext)
{
NazaraError("Failed to create context");
return false;
}
if (!alcMakeContextCurrent(s_openalContext))
{
NazaraError("Failed to activate context");
return false;
}
s_openalRndererName = reinterpret_cast<const char*>(alGetString(AL_RENDERER));
s_openalVendorName = reinterpret_cast<const char*>(alGetString(AL_VENDOR));
const ALchar* version = alGetString(AL_VERSION);
if (version)
{
unsigned int major = version[0] - '0';
unsigned int minor = version[2] - '0';
if (major != 0 && major <= 9)
{
if (minor > 9)
{
NazaraWarning("Unable to retrieve OpenAL minor version (using 0)");
minor = 0;
}
s_openalVersion = major*100 + minor*10;
NazaraDebug("OpenAL version: " + NumberToString(major) + '.' + NumberToString(minor));
}
else
{
NazaraDebug("Unable to retrieve OpenAL major version");
s_openalVersion = 0;
}
}
else
{
NazaraDebug("Unable to retrieve OpenAL version");
s_openalVersion = 0;
}
// We complete the formats table
AudioFormat[UnderlyingCast(AudioFormat::I16_Mono)] = AL_FORMAT_MONO16;
AudioFormat[UnderlyingCast(AudioFormat::I16_Stereo)] = AL_FORMAT_STEREO16;
// "The presence of an enum value does not guarantee the applicability of an extension to the current context."
if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
AudioFormat[UnderlyingCast(AudioFormat::I16_Quad)] = alGetEnumValue("AL_FORMAT_QUAD16");
AudioFormat[UnderlyingCast(AudioFormat::I16_5_1)] = alGetEnumValue("AL_FORMAT_51CHN16");
AudioFormat[UnderlyingCast(AudioFormat::I16_6_1)] = alGetEnumValue("AL_FORMAT_61CHN16");
AudioFormat[UnderlyingCast(AudioFormat::I16_7_1)] = alGetEnumValue("AL_FORMAT_71CHN16");
}
else if (alIsExtensionPresent("AL_LOKI_quadriphonic"))
AudioFormat[UnderlyingCast(AudioFormat::I16_Quad)] = alGetEnumValue("AL_FORMAT_QUAD16_LOKI");
return true;
}
/*!
* \brief Loads the entry for the function name
* \return Pointer to the function
*
* \param name Name of the entry
* \param throwException Should throw exception if failed ?
*
* \remark Produces a std::runtime_error if entry does not exist and throwException is set to true
*/
OpenALFunc OpenAL::LoadEntry(const char* name, bool throwException)
{
OpenALFunc entry = reinterpret_cast<OpenALFunc>(s_openalLbrary.GetSymbol(name));
if (!entry && throwException)
{
std::ostringstream oss;
oss << "failed to load \"" << name << '"';
throw std::runtime_error(oss.str());
}
return entry;
}
// al
OpenALDetail::LPALBUFFER3F alBuffer3f = nullptr;
OpenALDetail::LPALBUFFER3I alBuffer3i = nullptr;
OpenALDetail::LPALBUFFERDATA alBufferData = nullptr;
OpenALDetail::LPALBUFFERF alBufferf = nullptr;
OpenALDetail::LPALBUFFERFV alBufferfv = nullptr;
OpenALDetail::LPALBUFFERI alBufferi = nullptr;
OpenALDetail::LPALBUFFERIV alBufferiv = nullptr;
OpenALDetail::LPALDELETEBUFFERS alDeleteBuffers = nullptr;
OpenALDetail::LPALDELETESOURCES alDeleteSources = nullptr;
OpenALDetail::LPALDISABLE alDisable = nullptr;
OpenALDetail::LPALDISTANCEMODEL alDistanceModel = nullptr;
OpenALDetail::LPALDOPPLERFACTOR alDopplerFactor = nullptr;
OpenALDetail::LPALDOPPLERVELOCITY alDopplerVelocity = nullptr;
OpenALDetail::LPALENABLE alEnable = nullptr;
OpenALDetail::LPALGENBUFFERS alGenBuffers = nullptr;
OpenALDetail::LPALGENSOURCES alGenSources = nullptr;
OpenALDetail::LPALGETBOOLEAN alGetBoolean = nullptr;
OpenALDetail::LPALGETBOOLEANV alGetBooleanv = nullptr;
OpenALDetail::LPALGETBUFFER3F alGetBuffer3f = nullptr;
OpenALDetail::LPALGETBUFFER3I alGetBuffer3i = nullptr;
OpenALDetail::LPALGETBUFFERF alGetBufferf = nullptr;
OpenALDetail::LPALGETBUFFERFV alGetBufferfv = nullptr;
OpenALDetail::LPALGETBUFFERI alGetBufferi = nullptr;
OpenALDetail::LPALGETBUFFERIV alGetBufferiv = nullptr;
OpenALDetail::LPALGETDOUBLE alGetDouble = nullptr;
OpenALDetail::LPALGETDOUBLEV alGetDoublev = nullptr;
OpenALDetail::LPALGETENUMVALUE alGetEnumValue = nullptr;
OpenALDetail::LPALGETERROR alGetError = nullptr;
OpenALDetail::LPALGETFLOAT alGetFloat = nullptr;
OpenALDetail::LPALGETFLOATV alGetFloatv = nullptr;
OpenALDetail::LPALGETINTEGER alGetInteger = nullptr;
OpenALDetail::LPALGETINTEGERV alGetIntegerv = nullptr;
OpenALDetail::LPALGETLISTENER3F alGetListener3f = nullptr;
OpenALDetail::LPALGETLISTENER3I alGetListener3i = nullptr;
OpenALDetail::LPALGETLISTENERF alGetListenerf = nullptr;
OpenALDetail::LPALGETLISTENERFV alGetListenerfv = nullptr;
OpenALDetail::LPALGETLISTENERI alGetListeneri = nullptr;
OpenALDetail::LPALGETLISTENERIV alGetListeneriv = nullptr;
OpenALDetail::LPALGETPROCADDRESS alGetProcAddress = nullptr;
OpenALDetail::LPALGETSOURCE3F alGetSource3f = nullptr;
OpenALDetail::LPALGETSOURCE3I alGetSource3i = nullptr;
OpenALDetail::LPALGETSOURCEF alGetSourcef = nullptr;
OpenALDetail::LPALGETSOURCEFV alGetSourcefv = nullptr;
OpenALDetail::LPALGETSOURCEI alGetSourcei = nullptr;
OpenALDetail::LPALGETSOURCEIV alGetSourceiv = nullptr;
OpenALDetail::LPALGETSTRING alGetString = nullptr;
OpenALDetail::LPALISBUFFER alIsBuffer = nullptr;
OpenALDetail::LPALISENABLED alIsEnabled = nullptr;
OpenALDetail::LPALISEXTENSIONPRESENT alIsExtensionPresent = nullptr;
OpenALDetail::LPALISSOURCE alIsSource = nullptr;
OpenALDetail::LPALLISTENER3F alListener3f = nullptr;
OpenALDetail::LPALLISTENER3I alListener3i = nullptr;
OpenALDetail::LPALLISTENERF alListenerf = nullptr;
OpenALDetail::LPALLISTENERFV alListenerfv = nullptr;
OpenALDetail::LPALLISTENERI alListeneri = nullptr;
OpenALDetail::LPALLISTENERIV alListeneriv = nullptr;
OpenALDetail::LPALSOURCE3F alSource3f = nullptr;
OpenALDetail::LPALSOURCE3I alSource3i = nullptr;
OpenALDetail::LPALSOURCEF alSourcef = nullptr;
OpenALDetail::LPALSOURCEFV alSourcefv = nullptr;
OpenALDetail::LPALSOURCEI alSourcei = nullptr;
OpenALDetail::LPALSOURCEIV alSourceiv = nullptr;
OpenALDetail::LPALSOURCEPAUSE alSourcePause = nullptr;
OpenALDetail::LPALSOURCEPAUSEV alSourcePausev = nullptr;
OpenALDetail::LPALSOURCEPLAY alSourcePlay = nullptr;
OpenALDetail::LPALSOURCEPLAYV alSourcePlayv = nullptr;
OpenALDetail::LPALSOURCEQUEUEBUFFERS alSourceQueueBuffers = nullptr;
OpenALDetail::LPALSOURCEREWIND alSourceRewind = nullptr;
OpenALDetail::LPALSOURCEREWINDV alSourceRewindv = nullptr;
OpenALDetail::LPALSOURCESTOP alSourceStop = nullptr;
OpenALDetail::LPALSOURCESTOPV alSourceStopv = nullptr;
OpenALDetail::LPALSOURCEUNQUEUEBUFFERS alSourceUnqueueBuffers = nullptr;
OpenALDetail::LPALSPEEDOFSOUND alSpeedOfSound = nullptr;
// alc
OpenALDetail::LPALCCAPTURECLOSEDEVICE alcCaptureCloseDevice = nullptr;
OpenALDetail::LPALCCAPTUREOPENDEVICE alcCaptureOpenDevice = nullptr;
OpenALDetail::LPALCCAPTURESAMPLES alcCaptureSamples = nullptr;
OpenALDetail::LPALCCAPTURESTART alcCaptureStart = nullptr;
OpenALDetail::LPALCCAPTURESTOP alcCaptureStop = nullptr;
OpenALDetail::LPALCCLOSEDEVICE alcCloseDevice = nullptr;
OpenALDetail::LPALCCREATECONTEXT alcCreateContext = nullptr;
OpenALDetail::LPALCDESTROYCONTEXT alcDestroyContext = nullptr;
OpenALDetail::LPALCGETCONTEXTSDEVICE alcGetContextsDevice = nullptr;
OpenALDetail::LPALCGETCURRENTCONTEXT alcGetCurrentContext = nullptr;
OpenALDetail::LPALCGETENUMVALUE alcGetEnumValue = nullptr;
OpenALDetail::LPALCGETERROR alcGetError = nullptr;
OpenALDetail::LPALCGETINTEGERV alcGetIntegerv = nullptr;
OpenALDetail::LPALCGETPROCADDRESS alcGetProcAddress = nullptr;
OpenALDetail::LPALCGETSTRING alcGetString = nullptr;
OpenALDetail::LPALCISEXTENSIONPRESENT alcIsExtensionPresent = nullptr;
OpenALDetail::LPALCMAKECONTEXTCURRENT alcMakeContextCurrent = nullptr;
OpenALDetail::LPALCOPENDEVICE alcOpenDevice = nullptr;
OpenALDetail::LPALCPROCESSCONTEXT alcProcessContext = nullptr;
OpenALDetail::LPALCSUSPENDCONTEXT alcSuspendContext = nullptr;
}

View File

@@ -0,0 +1,92 @@
// 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/OpenALBuffer.hpp>
#include <Nazara/Audio/OpenALDevice.hpp>
#include <Nazara/Audio/OpenALLibrary.hpp>
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
OpenALBuffer::~OpenALBuffer()
{
GetDevice().MakeContextCurrent();
m_library.alDeleteBuffers(1, &m_bufferId);
}
UInt32 OpenALBuffer::GetSampleCount() const
{
GetDevice().MakeContextCurrent();
ALint bits, size;
m_library.alGetBufferi(m_bufferId, AL_BITS, &bits);
m_library.alGetBufferi(m_bufferId, AL_SIZE, &size);
UInt32 sampleCount = 0;
if (bits != 0)
sampleCount += (8 * SafeCast<UInt32>(size)) / SafeCast<UInt32>(bits);
return sampleCount;
}
UInt32 OpenALBuffer::GetSize() const
{
GetDevice().MakeContextCurrent();
ALint size;
m_library.alGetBufferi(m_bufferId, AL_SIZE, &size);
return SafeCast<UInt32>(size);
}
UInt32 OpenALBuffer::GetSampleRate() const
{
GetDevice().MakeContextCurrent();
ALint sampleRate;
m_library.alGetBufferi(m_bufferId, AL_FREQUENCY, &sampleRate);
return SafeCast<UInt32>(sampleRate);
}
bool OpenALBuffer::Reset(AudioFormat format, UInt64 sampleCount, UInt32 sampleRate, const void* samples)
{
OpenALDevice& device = GetDevice();
ALenum alFormat = device.TranslateAudioFormat(format);
if (!alFormat)
{
NazaraError("unsupported format");
return false;
}
device.MakeContextCurrent();
// We empty the error stack
while (m_library.alGetError() != AL_NO_ERROR);
// TODO: Use SafeCast
m_library.alBufferData(m_bufferId, alFormat, samples, static_cast<ALsizei>(sampleCount * sizeof(Int16)), static_cast<ALsizei>(sampleRate));
if (ALenum lastError = m_library.alGetError(); lastError != AL_NO_ERROR)
{
NazaraError("failed to reset OpenAL buffer: " + std::to_string(lastError));
return false;
}
return true;
}
OpenALDevice& OpenALBuffer::GetDevice()
{
return SafeCast<OpenALDevice&>(*GetAudioDevice());
}
const OpenALDevice& OpenALBuffer::GetDevice() const
{
return SafeCast<OpenALDevice&>(*GetAudioDevice());
}
}

View File

@@ -0,0 +1,309 @@
// 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/OpenALDevice.hpp>
#include <Nazara/Audio/OpenALBuffer.hpp>
#include <Nazara/Audio/OpenALLibrary.hpp>
#include <Nazara/Audio/OpenALSource.hpp>
#include <cstring>
#include <stdexcept>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
namespace
{
thread_local ALCcontext* s_currentContext;
}
OpenALDevice::OpenALDevice(OpenALLibrary& library, ALCdevice* device) :
m_library(library),
m_device(device)
{
m_context = m_library.alcCreateContext(device, nullptr);
if (!m_context)
throw std::runtime_error("failed to create OpenAL context");
MakeContextCurrent();
m_renderer = reinterpret_cast<const char*>(m_library.alGetString(AL_RENDERER));
m_vendor = reinterpret_cast<const char*>(m_library.alGetString(AL_VENDOR));
// We complete the formats table
m_audioFormatValues.fill(0);
m_audioFormatValues[UnderlyingCast(AudioFormat::I16_Mono)] = AL_FORMAT_MONO16;
m_audioFormatValues[UnderlyingCast(AudioFormat::I16_Stereo)] = AL_FORMAT_STEREO16;
// "The presence of an enum value does not guarantee the applicability of an extension to the current context."
if (library.alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
m_audioFormatValues[UnderlyingCast(AudioFormat::I16_Quad)] = m_library.alGetEnumValue("AL_FORMAT_QUAD16");
m_audioFormatValues[UnderlyingCast(AudioFormat::I16_5_1)] = m_library.alGetEnumValue("AL_FORMAT_51CHN16");
m_audioFormatValues[UnderlyingCast(AudioFormat::I16_6_1)] = m_library.alGetEnumValue("AL_FORMAT_61CHN16");
m_audioFormatValues[UnderlyingCast(AudioFormat::I16_7_1)] = m_library.alGetEnumValue("AL_FORMAT_71CHN16");
}
else if (library.alIsExtensionPresent("AL_LOKI_quadriphonic"))
m_audioFormatValues[UnderlyingCast(AudioFormat::I16_Quad)] = m_library.alGetEnumValue("AL_FORMAT_QUAD16_LOKI");
SetListenerDirection(Vector3f::Forward());
}
OpenALDevice::~OpenALDevice()
{
MakeContextCurrent();
m_library.alcDestroyContext(m_context);
m_library.alcCloseDevice(m_device);
if (s_currentContext == m_context)
s_currentContext = nullptr;
}
std::shared_ptr<AudioBuffer> OpenALDevice::CreateBuffer()
{
MakeContextCurrent();
ALuint bufferId = 0;
m_library.alGenBuffers(1, &bufferId);
if (bufferId == 0)
return {};
return std::make_shared<OpenALBuffer>(shared_from_this(), m_library, bufferId);
}
std::shared_ptr<AudioSource> OpenALDevice::CreateSource()
{
MakeContextCurrent();
ALuint sourceId = 0;
m_library.alGenSources(1, &sourceId);
if (sourceId == 0)
return {};
return std::make_shared<OpenALSource>(shared_from_this(), m_library, sourceId);
}
/*!
* \brief Gets the factor of the Doppler effect
* \return Global factor of the Doppler effect
*/
float OpenALDevice::GetDopplerFactor() const
{
MakeContextCurrent();
return m_library.alGetFloat(AL_DOPPLER_FACTOR);
}
/*!
* \brief Gets the global volume
* \return Float between [0, inf) with 100.f being the default
*/
float OpenALDevice::GetGlobalVolume() const
{
MakeContextCurrent();
ALfloat gain = 0.f;
m_library.alGetListenerf(AL_GAIN, &gain);
return gain * 100.f;
}
/*!
* \brief Gets the direction of the listener
* \return Direction of the listener, in front of the listener
*
* \param up Current up direction
*
* \see GetListenerRotation
*/
Vector3f OpenALDevice::GetListenerDirection(Vector3f* up) const
{
MakeContextCurrent();
ALfloat orientation[6];
m_library.alGetListenerfv(AL_ORIENTATION, orientation);
if (up)
up->Set(orientation[3], orientation[4], orientation[5]);
return Vector3f(orientation[0], orientation[1], orientation[2]);
}
/*!
* \brief Gets the position of the listener
* \return Position of the listener
*
* \see GetListenerVelocity
*/
Vector3f OpenALDevice::GetListenerPosition() const
{
MakeContextCurrent();
Vector3f position;
m_library.alGetListenerfv(AL_POSITION, &position.x);
return position;
}
/*!
* \brief Gets the rotation of the listener
* \return Rotation of the listener
*
* \param up Current up direction
*
* \see GetListenerDirection
*/
Quaternionf OpenALDevice::GetListenerRotation(Vector3f* up) const
{
MakeContextCurrent();
ALfloat orientation[6];
m_library.alGetListenerfv(AL_ORIENTATION, orientation);
Vector3f forward(orientation[0], orientation[1], orientation[2]);
if (up)
up->Set(orientation[3], orientation[4], orientation[5]);
return Quaternionf::RotationBetween(Vector3f::Forward(), forward);
}
/*!
* \brief Gets the velocity of the listener
* \return Velocity of the listener
*
* \see GetListenerPosition
*/
Vector3f OpenALDevice::GetListenerVelocity() const
{
MakeContextCurrent();
Vector3f velocity;
m_library.alGetListenerfv(AL_VELOCITY, &velocity.x);
return velocity;
}
void OpenALDevice::MakeContextCurrent() const
{
if (s_currentContext != m_context)
{
m_library.alcMakeContextCurrent(m_context);
s_currentContext = m_context;
}
}
/*!
* \brief Gets the speed of sound
* \return Speed of sound
*/
float OpenALDevice::GetSpeedOfSound() const
{
MakeContextCurrent();
return m_library.alGetFloat(AL_SPEED_OF_SOUND);
}
/*!
* \brief Checks whether the format is supported by the engine
* \return true if it is the case
*
* \param format Format to check
*/
bool OpenALDevice::IsFormatSupported(AudioFormat format) const
{
if (format == AudioFormat::Unknown)
return false;
return m_audioFormatValues[UnderlyingCast(format)] != 0;
}
/*!
* \brief Sets the factor of the doppler effect
*
* \param dopplerFactor Global factor of the doppler effect
*/
void OpenALDevice::SetDopplerFactor(float dopplerFactor)
{
MakeContextCurrent();
m_library.alDopplerFactor(dopplerFactor);
}
/*!
* \brief Sets the global volume
*
* \param volume Float between [0, inf) with 1.f being the default
*/
void OpenALDevice::SetGlobalVolume(float volume)
{
MakeContextCurrent();
m_library.alListenerf(AL_GAIN, volume);
}
/*!
* \brief Sets the direction of the listener
*
* \param direction Direction of the listener, in front of the listener
* \param up Up vector
*
* \see SetListenerDirection, SetListenerRotation
*/
void OpenALDevice::SetListenerDirection(const Vector3f& direction, const Vector3f& up)
{
MakeContextCurrent();
ALfloat orientation[6] =
{
direction.x, direction.y, direction.z,
up.x, up.y, up.z
};
m_library.alListenerfv(AL_ORIENTATION, orientation);
}
/*!
* \brief Sets the position of the listener
*
* \param position Position of the listener
*
* \see SetListenerVelocity
*/
void OpenALDevice::SetListenerPosition(const Vector3f& position)
{
MakeContextCurrent();
m_library.alListenerfv(AL_POSITION, &position.x);
}
/*!
* \brief Sets the velocity of the listener
*
* \param velocity Velocity of the listener
*
* \see SetListenerPosition
*/
void OpenALDevice::SetListenerVelocity(const Vector3f& velocity)
{
MakeContextCurrent();
m_library.alListenerfv(AL_VELOCITY, &velocity.x);
}
/*!
* \brief Sets the speed of sound
*
* \param speed Speed of sound
*/
void OpenALDevice::SetSpeedOfSound(float speed)
{
MakeContextCurrent();
m_library.alSpeedOfSound(speed);
}
}

View File

@@ -0,0 +1,126 @@
// 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/OpenALLibrary.hpp>
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Core/StringExt.hpp>
#include <array>
#include <cstring>
#include <sstream>
#include <stdexcept>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
bool OpenALLibrary::Load()
{
Unload();
#if defined(NAZARA_PLATFORM_WINDOWS)
std::array libs{
"soft_oal.dll",
"wrap_oal.dll",
"openal32.dll"
};
#elif defined(NAZARA_PLATFORM_LINUX)
std::array libs {
"libopenal.so.1",
"libopenal.so.0",
"libopenal.so"
};
#elif defined(NAZARA_PLATFORM_MACOSX)
std::array libs {
"libopenal.dylib",
"libopenal.1.dylib",
};
#else
NazaraError("unhandled OS");
return false;
#endif
for (const char* libname : libs)
{
if (!m_library.Load(libname))
continue;
auto LoadSymbol = [this](const char* name)
{
DynLibFunc funcPtr = m_library.GetSymbol(name);
if (!funcPtr)
throw std::runtime_error(std::string("failed to load ") + name);
return funcPtr;
};
try
{
#define NAZARA_AUDIO_FUNC(name, sig) name = reinterpret_cast<sig>(LoadSymbol(#name));
NAZARA_AUDIO_FOREACH_AL_FUNC(NAZARA_AUDIO_FUNC)
NAZARA_AUDIO_FOREACH_ALC_FUNC(NAZARA_AUDIO_FUNC)
#undef NAZARA_AUDIO_FUNC
}
catch (const std::exception& e)
{
NazaraWarning(std::string("failed to load ") + libname + ": " + e.what());
continue;
}
return true;
}
NazaraError("failed to load OpenAL library");
return false;
}
std::vector<std::string> OpenALLibrary::QueryInputDevices()
{
return ParseDevices(alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER));
}
std::vector<std::string> OpenALLibrary::QueryOutputDevices()
{
return ParseDevices(alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER));
}
std::shared_ptr<OpenALDevice> OpenALLibrary::OpenDevice(const char* name)
{
ALCdevice* device = alcOpenDevice(name);
if (!device)
throw std::runtime_error("failed to open device");
return std::make_shared<OpenALDevice>(*this, device);
}
void OpenALLibrary::Unload()
{
if (!m_library.IsLoaded())
return;
#define NAZARA_AUDIO_FUNC(name, sig) name = nullptr;
NAZARA_AUDIO_FOREACH_AL_FUNC(NAZARA_AUDIO_FUNC)
NAZARA_AUDIO_FOREACH_ALC_FUNC(NAZARA_AUDIO_FUNC)
#undef NAZARA_AUDIO_FUNC
m_library.Unload();
}
std::vector<std::string> OpenALLibrary::ParseDevices(const char* deviceString)
{
if (!deviceString)
return {};
std::vector<std::string> devices;
std::size_t length;
while ((length = std::strlen(deviceString)) > 0)
{
devices.emplace_back(deviceString, length);
deviceString += length + 1;
}
return devices;
}
}

View File

@@ -0,0 +1,298 @@
// 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/Core/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);
}
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 relative;
m_library.alGetSourcei(m_sourceId, AL_LOOPING, &relative);
return relative == AL_FALSE;
}
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->GetAudioDevice() == 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->GetAudioDevice() == 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());
}
}

View File

@@ -3,8 +3,10 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Audio/Sound.hpp>
#include <Nazara/Audio/Audio.hpp>
#include <Nazara/Audio/AudioSource.hpp>
#include <Nazara/Audio/Config.hpp>
#include <Nazara/Audio/OpenAL.hpp>
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Audio/Debug.hpp>
@@ -18,12 +20,18 @@ namespace Nz
* \remark Module Audio needs to be initialized to use this class
*/
Sound::Sound() :
Sound(*Audio::Instance()->GetDefaultDevice())
{
}
/*!
* \brief Constructs a Sound object
*
* \param soundBuffer Buffer to read sound from
*/
Sound::Sound(std::shared_ptr<const SoundBuffer> soundBuffer)
Sound::Sound(AudioDevice& audioDevice, std::shared_ptr<SoundBuffer> soundBuffer) :
Sound(audioDevice)
{
SetBuffer(std::move(soundBuffer));
}
@@ -45,16 +53,14 @@ namespace Nz
*/
void Sound::EnableLooping(bool loop)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcei(m_source, AL_LOOPING, loop);
m_source->EnableLooping(loop);
}
/*!
* \brief Gets the internal buffer
* \return Internal buffer
*/
const std::shared_ptr<const SoundBuffer>& Sound::GetBuffer() const
const std::shared_ptr<SoundBuffer>& Sound::GetBuffer() const
{
return m_buffer;
}
@@ -78,12 +84,8 @@ namespace Nz
*/
UInt32 Sound::GetPlayingOffset() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
ALint samples = 0;
alGetSourcei(m_source, AL_SAMPLE_OFFSET, &samples);
return static_cast<UInt32>(1000ULL * samples / m_buffer->GetSampleRate());
UInt32 sampleCount = m_source->GetSampleOffset();
return SafeCast<UInt32>(1000ULL * sampleCount / m_buffer->GetSampleRate());
}
/*!
@@ -92,7 +94,7 @@ namespace Nz
*/
SoundStatus Sound::GetStatus() const
{
return GetInternalStatus();
return m_source->GetStatus();
}
/*!
@@ -101,19 +103,13 @@ namespace Nz
*/
bool Sound::IsLooping() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
ALint loop;
alGetSourcei(m_source, AL_LOOPING, &loop);
return loop != AL_FALSE;
return m_source->IsLooping();
}
/*!
* \brief Checks whether the sound is playable
* \return true if it is the case
*/
bool Sound::IsPlayable() const
{
return m_buffer != nullptr;
@@ -137,7 +133,7 @@ namespace Nz
return false;
}
SetBuffer(buffer);
SetBuffer(std::move(buffer));
return true;
}
@@ -160,7 +156,7 @@ namespace Nz
return false;
}
SetBuffer(buffer);
SetBuffer(std::move(buffer));
return true;
}
@@ -182,7 +178,7 @@ namespace Nz
return false;
}
SetBuffer(buffer);
SetBuffer(std::move(buffer));
return true;
}
@@ -191,9 +187,7 @@ namespace Nz
*/
void Sound::Pause()
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcePause(m_source);
m_source->Pause();
}
/*!
@@ -203,10 +197,9 @@ namespace Nz
*/
void Sound::Play()
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
NazaraAssert(IsPlayable(), "Music is not playable");
NazaraAssert(IsPlayable(), "Sound is not playable");
alSourcePlay(m_source);
m_source->Play();
}
/*!
@@ -216,10 +209,9 @@ namespace Nz
*
* \remark Produces a NazaraError if buffer is invalid with NAZARA_AUDIO_SAFE defined
*/
void Sound::SetBuffer(std::shared_ptr<const SoundBuffer> buffer)
void Sound::SetBuffer(std::shared_ptr<SoundBuffer> buffer)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
NazaraAssert(!buffer || buffer->IsValid(), "Invalid sound buffer");
NazaraAssert(buffer, "Invalid sound buffer");
if (m_buffer == buffer)
return;
@@ -227,11 +219,7 @@ namespace Nz
Stop();
m_buffer = std::move(buffer);
if (m_buffer)
alSourcei(m_source, AL_BUFFER, m_buffer->GetOpenALBuffer());
else
alSourcei(m_source, AL_BUFFER, AL_NONE);
m_source->SetBuffer(m_buffer->GetBuffer(m_source->GetAudioDevice().get()));
}
/*!
@@ -241,9 +229,7 @@ namespace Nz
*/
void Sound::SetPlayingOffset(UInt32 offset)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcei(m_source, AL_SAMPLE_OFFSET, static_cast<ALint>(offset/1000.f * m_buffer->GetSampleRate()));
m_source->SetSampleOffset(SafeCast<UInt32>(UInt64(offset) * m_buffer->GetSampleRate() / 1000));
}
/*!
@@ -253,7 +239,6 @@ namespace Nz
*/
void Sound::Stop()
{
if (m_source != InvalidSource)
alSourceStop(m_source);
m_source->Stop();
}
}

View File

@@ -5,17 +5,14 @@
#include <Nazara/Audio/SoundBuffer.hpp>
#include <Nazara/Audio/Algorithm.hpp>
#include <Nazara/Audio/Audio.hpp>
#include <Nazara/Audio/AudioBuffer.hpp>
#include <Nazara/Audio/Config.hpp>
#include <Nazara/Audio/OpenAL.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <Nazara/Core/Error.hpp>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <Nazara/Audio/Debug.hpp>
///FIXME: Adapt the creation
namespace Nz
{
/*!
@@ -36,18 +33,6 @@ namespace Nz
return true;
}
struct SoundBufferImpl
{
ALuint buffer;
AudioFormat format;
UInt32 duration;
std::unique_ptr<Int16[]> samples;
UInt64 sampleCount;
UInt32 sampleRate;
};
SoundBuffer::SoundBuffer() = default;
/*!
* \brief Constructs a SoundBuffer object
*
@@ -58,197 +43,41 @@ namespace Nz
*
* \remark Produces a NazaraError if creation went wrong with NAZARA_AUDIO_SAFE defined
* \remark Produces a std::runtime_error if creation went wrong with NAZARA_AUDIO_SAFE defined
*
* \see Create
*/
SoundBuffer::SoundBuffer(AudioFormat format, UInt64 sampleCount, UInt32 sampleRate, const Int16* samples)
{
Create(format, sampleCount, sampleRate, samples);
NazaraAssert(sampleCount > 0, "sample count must be different from zero");
NazaraAssert(sampleRate > 0, "sample rate must be different from zero");
NazaraAssert(samples, "invalid samples");
#ifdef NAZARA_DEBUG
if (!m_impl)
{
NazaraError("Failed to create sound buffer");
throw std::runtime_error("Constructor failed");
}
#endif
m_duration = SafeCast<UInt32>((1000ULL*sampleCount / (GetChannelCount(format) * sampleRate)));
m_format = format;
m_sampleCount = sampleCount;
m_sampleRate = sampleRate;
m_samples = std::make_unique<Int16[]>(sampleCount);
std::memcpy(&m_samples[0], samples, sampleCount * sizeof(Int16));
}
SoundBuffer::~SoundBuffer() = default;
/*!
* \brief Creates the SoundBuffer object
* \return true if creation is successful
*
* \param format Format for the audio
* \param sampleCount Number of samples
* \param sampleRate Rate of samples
* \param samples Samples raw data
*
* \remark Produces a NazaraError if creation went wrong with NAZARA_AUDIO_SAFE defined,
* this could happen if parameters are invalid or creation of OpenAL buffers failed
*/
bool SoundBuffer::Create(AudioFormat format, UInt64 sampleCount, UInt32 sampleRate, const Int16* samples)
const std::shared_ptr<AudioBuffer>& SoundBuffer::GetBuffer(AudioDevice* device)
{
Destroy();
#if NAZARA_AUDIO_SAFE
if (!IsFormatSupported(format))
auto it = m_audioBufferByDevice.find(device);
if (it == m_audioBufferByDevice.end())
{
NazaraError("Audio format is not supported");
return false;
auto audioBuffer = device->CreateBuffer();
if (!audioBuffer->Reset(m_format, m_sampleCount, m_sampleRate, m_samples.get()))
throw std::runtime_error("failed to initialize audio buffer");
it = m_audioBufferByDevice.emplace(device, AudioDeviceEntry{}).first;
AudioDeviceEntry& entry = it->second;
entry.audioBuffer = std::move(audioBuffer);
entry.audioDeviceReleaseSlot.Connect(device->OnAudioDeviceRelease, [this](AudioDevice* device)
{
m_audioBufferByDevice.erase(device);
});
}
if (sampleCount == 0)
{
NazaraError("Sample rate must be different from zero");
return false;
}
if (sampleRate == 0)
{
NazaraError("Sample rate must be different from zero");
return false;
}
if (!samples)
{
NazaraError("Invalid sample source");
return false;
}
#endif
// We empty the error stack
while (alGetError() != AL_NO_ERROR);
ALuint buffer;
alGenBuffers(1, &buffer);
if (alGetError() != AL_NO_ERROR)
{
NazaraError("Failed to create OpenAL buffer");
return false;
}
CallOnExit clearBufferOnExit([buffer] () { alDeleteBuffers(1, &buffer); });
alBufferData(buffer, OpenAL::AudioFormat[UnderlyingCast(format)], samples, static_cast<ALsizei>(sampleCount*sizeof(Int16)), static_cast<ALsizei>(sampleRate));
if (alGetError() != AL_NO_ERROR)
{
NazaraError("Failed to set OpenAL buffer");
return false;
}
m_impl = std::make_unique<SoundBufferImpl>();
m_impl->buffer = buffer;
m_impl->duration = static_cast<UInt32>((1000ULL*sampleCount / (GetChannelCount(format) * sampleRate)));
m_impl->format = format;
m_impl->sampleCount = sampleCount;
m_impl->sampleRate = sampleRate;
m_impl->samples = std::make_unique<Int16[]>(sampleCount);
std::memcpy(&m_impl->samples[0], samples, sampleCount*sizeof(Int16));
clearBufferOnExit.Reset();
return true;
}
/*!
* \brief Destroys the current sound buffer and frees resources
*/
void SoundBuffer::Destroy()
{
m_impl.reset();
}
/*!
* \brief Gets the duration of the sound buffer
* \return Duration of the sound buffer in milliseconds
*
* \remark Produces a NazaraError if there is no sound buffer with NAZARA_AUDIO_SAFE defined
*/
UInt32 SoundBuffer::GetDuration() const
{
NazaraAssert(m_impl, "Sound buffer not created");
return m_impl->duration;
}
/*!
* \brief Gets the format of the sound buffer
* \return Enumeration of type AudioFormat (mono, stereo, ...)
*
* \remark Produces a NazaraError if there is no sound buffer with NAZARA_AUDIO_SAFE defined
*/
AudioFormat SoundBuffer::GetFormat() const
{
NazaraAssert(m_impl, "Sound buffer not created");
return m_impl->format;
}
/*!
* \brief Gets the internal raw samples
* \return Pointer to raw data
*
* \remark Produces a NazaraError if there is no sound buffer with NAZARA_AUDIO_SAFE defined
*/
const Int16* SoundBuffer::GetSamples() const
{
NazaraAssert(m_impl, "Sound buffer not created");
return m_impl->samples.get();
}
/*!
* \brief Gets the number of samples in the sound buffer
* \return Count of samples (number of seconds * sample rate * channel count)
*
* \remark Produces a NazaraError if there is no sound buffer with NAZARA_AUDIO_SAFE defined
*/
UInt64 SoundBuffer::GetSampleCount() const
{
NazaraAssert(m_impl, "Sound buffer not created");
return m_impl->sampleCount;
}
/*!
* \brief Gets the rates of sample in the sound buffer
* \return Rate of sample in Hertz (Hz)
*
* \remark Produces a NazaraError if there is no sound buffer with NAZARA_AUDIO_SAFE defined
*/
UInt32 SoundBuffer::GetSampleRate() const
{
NazaraAssert(m_impl, "Sound buffer not created");
return m_impl->sampleRate;
}
/*!
* \brief Checks whether the sound buffer is valid
* \return true if it is the case
*/
bool SoundBuffer::IsValid() const
{
return m_impl != nullptr;
}
/*!
* \brief Checks whether the format is supported by the engine
* \return true if it is the case
*
* \param format Format to check
*/
bool SoundBuffer::IsFormatSupported(AudioFormat format)
{
Audio* audio = Audio::Instance();
NazaraAssert(audio, "Audio module has not been initialized");
return audio->IsFormatSupported(format);
return it->second.audioBuffer;
}
/*!
@@ -296,23 +125,4 @@ namespace Nz
return audio->GetSoundBufferLoader().LoadFromStream(stream, params);
}
/*!
* \brief Gets the internal OpenAL buffer
* \return The index of the OpenAL buffer
*
* \remark Produces a NazaraError if there is no sound buffer with NAZARA_AUDIO_SAFE defined
*/
unsigned int SoundBuffer::GetOpenALBuffer() const
{
#ifdef NAZARA_DEBUG
if (!m_impl)
{
NazaraInternalError("Sound buffer not created");
return AL_NONE;
}
#endif
return m_impl->buffer;
}
}

View File

@@ -2,10 +2,9 @@
// This file is part of the "Nazara Engine - Audio module"
// For conditions of distribution and use, see copyright notice in Config.hpp
// http://connect.creativelabs.com/openal/Documentation/OpenAL_Programmers_Guide.pdf
#include <Nazara/Audio/SoundEmitter.hpp>
#include <Nazara/Audio/OpenAL.hpp>
#include <Nazara/Audio/AudioDevice.hpp>
#include <Nazara/Audio/AudioSource.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Audio/Debug.hpp>
@@ -23,173 +22,87 @@ namespace Nz
/*!
* \brief Constructs a SoundEmitter object
*/
SoundEmitter::SoundEmitter()
SoundEmitter::SoundEmitter(AudioDevice& audioDevice) :
m_source(audioDevice.CreateSource())
{
alGenSources(1, &m_source);
}
/*!
* \brief Constructs a SoundEmitter object which is a copy of another
*
* \param emitter SoundEmitter to copy
*
* \remark Position and velocity are not copied
*/
SoundEmitter::SoundEmitter(const SoundEmitter& emitter)
{
if (emitter.m_source != InvalidSource)
{
alGenSources(1, &m_source);
SetAttenuation(emitter.GetAttenuation());
SetMinDistance(emitter.GetMinDistance());
SetPitch(emitter.GetPitch());
// No copy for position or velocity
SetVolume(emitter.GetVolume());
}
else
m_source = InvalidSource;
}
/*!
* \brief Constructs a SoundEmitter object by moving another
*
* \param emitter SoundEmitter to move
*
* \remark The moved sound emitter cannot be used after being moved
*/
SoundEmitter::SoundEmitter(SoundEmitter&& emitter) noexcept :
m_source(emitter.m_source)
{
emitter.m_source = InvalidSource;
}
/*!
* \brief Destructs the object
*/
SoundEmitter::~SoundEmitter()
{
if (m_source != InvalidSource)
alDeleteSources(1, &m_source);
}
SoundEmitter::~SoundEmitter() = default;
/*!
* \brief Enables spatialization
*
* \param spatialization True if spatialization is enabled
*/
void SoundEmitter::EnableSpatialization(bool spatialization)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcei(m_source, AL_SOURCE_RELATIVE, !spatialization);
m_source->EnableSpatialization(spatialization);
}
/*!
* \brief Gets the attenuation
* \return Amount that your sound will drop off as by the inverse square law
*/
float SoundEmitter::GetAttenuation() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
ALfloat attenuation;
alGetSourcef(m_source, AL_ROLLOFF_FACTOR, &attenuation);
return attenuation;
return m_source->GetAttenuation();
}
/*!
* \brief Gets the minimum distance to hear
* \return Distance to begin to hear
*/
float SoundEmitter::GetMinDistance() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
ALfloat distance;
alGetSourcef(m_source, AL_REFERENCE_DISTANCE, &distance);
return distance;
return m_source->GetMinDistance();
}
/*!
* \brief Gets the pitch
* \return Pitch of the sound
*/
float SoundEmitter::GetPitch() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
ALfloat pitch;
alGetSourcef(m_source, AL_PITCH, &pitch);
return pitch;
return m_source->GetPitch();
}
/*!
* \brief Gets the position of the emitter
* \return Position of the sound
*/
Vector3f SoundEmitter::GetPosition() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
Vector3f position;
alGetSourcefv(m_source, AL_POSITION, &position.x);
return position;
return m_source->GetPosition();
}
/*!
* \brief Gets the velocity of the emitter
* \return Velocity of the sound
*/
Vector3f SoundEmitter::GetVelocity() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
Vector3f velocity;
alGetSourcefv(m_source, AL_VELOCITY, &velocity.x);
return velocity;
return m_source->GetVelocity();
}
/*!
* \brief Gets the volume of the emitter
* \param volume Float between [0, inf) with 100.f being the default
*/
float SoundEmitter::GetVolume() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
ALfloat gain;
alGetSourcef(m_source, AL_GAIN, &gain);
return gain * 100.f;
return m_source->GetVolume();
}
/*!
* \brief Checks whether the sound emitter has spatialization enabled
* \return true if it the case
*/
bool SoundEmitter::IsSpatialized() const
bool SoundEmitter::IsSpatializationEnabled() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
ALint relative;
alGetSourcei(m_source, AL_SOURCE_RELATIVE, &relative);
return relative == AL_FALSE;
return m_source->IsSpatializationEnabled();
}
/*!
@@ -197,12 +110,9 @@ namespace Nz
*
* \param attenuation Amount that your sound will drop off as by the inverse square law
*/
void SoundEmitter::SetAttenuation(float attenuation)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcef(m_source, AL_ROLLOFF_FACTOR, attenuation);
m_source->SetAttenuation(attenuation);
}
/*!
@@ -210,12 +120,9 @@ namespace Nz
*
* \param minDistance to begin to hear
*/
void SoundEmitter::SetMinDistance(float minDistance)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcef(m_source, AL_REFERENCE_DISTANCE, minDistance);
m_source->SetMinDistance(minDistance);
}
/*!
@@ -223,12 +130,9 @@ namespace Nz
*
* \param pitch of the sound
*/
void SoundEmitter::SetPitch(float pitch)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcef(m_source, AL_PITCH, pitch);
m_source->SetPitch(pitch);
}
/*!
@@ -236,25 +140,9 @@ namespace Nz
*
* \param position Position of the sound
*/
void SoundEmitter::SetPosition(const Vector3f& position)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcefv(m_source, AL_POSITION, &position.x);
}
/*!
* \brief Sets the position of the emitter
*
* \param position Position of the sound with (x, y, z)
*/
void SoundEmitter::SetPosition(float x, float y, float z)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSource3f(m_source, AL_POSITION, x, y, z);
m_source->SetPosition(position);
}
/*!
@@ -262,25 +150,9 @@ namespace Nz
*
* \param velocity Velocity of the sound
*/
void SoundEmitter::SetVelocity(const Vector3f& velocity)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcefv(m_source, AL_VELOCITY, &velocity.x);
}
/*!
* \brief Sets the velocity of the emitter
*
* \param velocity Velocity with (velX, velY, velZ)
*/
void SoundEmitter::SetVelocity(float velX, float velY, float velZ)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSource3f(m_source, AL_VELOCITY, velX, velY, velZ);
m_source->SetVelocity(velocity);
}
/*!
@@ -288,56 +160,8 @@ namespace Nz
*
* \param volume Float between [0, inf) with 100.f being the default
*/
void SoundEmitter::SetVolume(float volume)
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
alSourcef(m_source, AL_GAIN, volume * 0.01f);
}
/*!
* \brief Assign a sound emitter by moving it
*
* \param emitter SoundEmitter to move
*
* \return *this
*/
SoundEmitter& SoundEmitter::operator=(SoundEmitter&& emitter) noexcept
{
std::swap(m_source, emitter.m_source);
return *this;
}
/*!
* \brief Gets the status of the sound emitter
* \return Enumeration of type SoundStatus (Playing, Stopped, ...)
*/
SoundStatus SoundEmitter::GetInternalStatus() const
{
NazaraAssert(m_source != InvalidSource, "Invalid sound emitter");
ALint state;
alGetSourcei(m_source, 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;
m_source->SetVolume(volume);
}
}