Audio: Add dummy device (in case OpenAL fails to load) and unifiate unit tests

This commit is contained in:
Jérôme Leclercq 2022-03-18 19:03:57 +01:00
parent efa2c0a253
commit 82641c6653
30 changed files with 809 additions and 107 deletions

View File

@ -24,7 +24,10 @@ int main()
if (!std::filesystem::is_directory(resourceDir) && std::filesystem::is_directory(".." / resourceDir))
resourceDir = ".." / resourceDir;
Nz::Modules<Nz::Audio, Nz::Platform> audio;
Nz::Audio::Config config;
config.noAudio = true;
Nz::Modules<Nz::Audio, Nz::Platform> audio(config);
Nz::Sound sound;
if (!sound.LoadFromFile(resourceDir / "siren.wav"))

View File

@ -18,7 +18,10 @@ int main()
if (!std::filesystem::is_directory(resourceDir) && std::filesystem::is_directory(".." / resourceDir))
resourceDir = ".." / resourceDir;
Nz::Modules<Nz::Audio> audio;
Nz::Audio::Config config;
config.noAudio = true;
Nz::Modules<Nz::Audio> audio(config);
Nz::SoundStreamParams streamParams;
streamParams.forceMono = false;

View File

@ -23,9 +23,9 @@ namespace Nz
public:
using Dependencies = TypeList<Core>;
struct Config {};
struct Config;
Audio(Config /*config*/);
Audio(Config config);
Audio(const Audio&) = delete;
Audio(Audio&&) = delete;
~Audio();
@ -45,10 +45,17 @@ namespace Nz
Audio& operator=(const Audio&) = delete;
Audio& operator=(Audio&&) = delete;
struct Config
{
bool allowDummyDevice = true;
bool noAudio = false;
};
private:
std::shared_ptr<AudioDevice> m_defaultDevice;
SoundBufferLoader m_soundBufferLoader;
SoundStreamLoader m_soundStreamLoader;
bool m_hasDummyDevice;
static Audio* s_instance;
};

View File

@ -25,8 +25,8 @@ namespace Nz
virtual ~AudioBuffer();
inline const std::shared_ptr<AudioDevice>& GetAudioDevice() const;
virtual UInt32 GetSampleCount() const = 0;
virtual UInt32 GetSize() const = 0;
virtual UInt64 GetSampleCount() const = 0;
virtual UInt64 GetSize() const = 0;
virtual UInt32 GetSampleRate() const = 0;
virtual bool IsCompatibleWith(const AudioDevice& device) const = 0;

View File

@ -0,0 +1,47 @@
// 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
#pragma once
#ifndef NAZARA_AUDIO_DUMMYAUDIOBUFFER_HPP
#define NAZARA_AUDIO_DUMMYAUDIOBUFFER_HPP
#include <Nazara/Prerequisites.hpp>
#include <Nazara/Audio/AudioBuffer.hpp>
#include <Nazara/Audio/Config.hpp>
#include <Nazara/Audio/Enums.hpp>
namespace Nz
{
class NAZARA_AUDIO_API DummyAudioBuffer final : public AudioBuffer
{
public:
using AudioBuffer::AudioBuffer;
DummyAudioBuffer(const DummyAudioBuffer&) = delete;
DummyAudioBuffer(DummyAudioBuffer&&) = delete;
~DummyAudioBuffer() = default;
AudioFormat GetAudioFormat() const;
UInt32 GetDuration() const;
UInt64 GetSampleCount() const override;
UInt64 GetSize() const override;
UInt32 GetSampleRate() const override;
bool IsCompatibleWith(const AudioDevice& device) const override;
bool Reset(AudioFormat format, UInt64 sampleCount, UInt32 sampleRate, const void* samples) override;
DummyAudioBuffer& operator=(const DummyAudioBuffer&) = delete;
DummyAudioBuffer& operator=(DummyAudioBuffer&&) = delete;
private:
AudioFormat m_format;
UInt64 m_sampleCount;
UInt32 m_sampleRate;
};
}
#include <Nazara/Audio/DummyAudioBuffer.inl>
#endif // NAZARA_AUDIO_DUMMYAUDIOBUFFER_HPP

View File

@ -0,0 +1,12 @@
// 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/DummyAudioBuffer.hpp>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
}
#include <Nazara/Audio/DebugOff.hpp>

View File

@ -0,0 +1,63 @@
// 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
#pragma once
#ifndef NAZARA_AUDIO_DUMMYAUDIODEVICE_HPP
#define NAZARA_AUDIO_DUMMYAUDIODEVICE_HPP
#include <Nazara/Prerequisites.hpp>
#include <Nazara/Audio/AudioDevice.hpp>
#include <Nazara/Audio/Config.hpp>
#include <Nazara/Audio/Enums.hpp>
#include <array>
#include <string>
namespace Nz
{
class NAZARA_AUDIO_API DummyAudioDevice : public AudioDevice
{
public:
DummyAudioDevice();
DummyAudioDevice(const DummyAudioDevice&) = delete;
DummyAudioDevice(DummyAudioDevice&&) = default;
~DummyAudioDevice() = default;
std::shared_ptr<AudioBuffer> CreateBuffer() override;
std::shared_ptr<AudioSource> CreateSource() override;
float GetDopplerFactor() const override;
float GetGlobalVolume() const override;
Vector3f GetListenerDirection(Vector3f* up = nullptr) const override;
Vector3f GetListenerPosition() const override;
Quaternionf GetListenerRotation() const override;
Vector3f GetListenerVelocity() const override;
float GetSpeedOfSound() const override;
const void* GetSubSystemIdentifier() const override;
bool IsFormatSupported(AudioFormat format) const override;
void SetDopplerFactor(float dopplerFactor) override;
void SetGlobalVolume(float volume) override;
void SetListenerDirection(const Vector3f& direction, const Vector3f& up = Vector3f::Up()) override;
void SetListenerPosition(const Vector3f& position) override;
void SetListenerVelocity(const Vector3f& velocity) override;
void SetSpeedOfSound(float speed) override;
DummyAudioDevice& operator=(const DummyAudioDevice&) = delete;
DummyAudioDevice& operator=(DummyAudioDevice&&) = default;
private:
Quaternionf m_listenerRotation;
Vector3f m_listenerVelocity;
Vector3f m_listenerPosition;
float m_dopplerFactor;
float m_globalVolume;
float m_speedOfSound;
};
}
#include <Nazara/Audio/DummyAudioDevice.inl>
#endif // NAZARA_AUDIO_DUMMYAUDIODEVICE_HPP

View File

@ -0,0 +1,12 @@
// 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/DummyAudioDevice.hpp>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
}
#include <Nazara/Audio/DebugOff.hpp>

View File

@ -0,0 +1,86 @@
// 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
#pragma once
#ifndef NAZARA_AUDIO_DUMMYAUDIOSOURCE_HPP
#define NAZARA_AUDIO_DUMMYAUDIOSOURCE_HPP
#include <Nazara/Prerequisites.hpp>
#include <Nazara/Audio/AudioSource.hpp>
#include <Nazara/Audio/Config.hpp>
#include <Nazara/Core/Clock.hpp>
namespace Nz
{
class DummyAudioBuffer;
class NAZARA_AUDIO_API DummyAudioSource final : public AudioSource
{
public:
inline DummyAudioSource(std::shared_ptr<AudioDevice> device);
DummyAudioSource(const DummyAudioSource&) = delete;
DummyAudioSource(DummyAudioSource&&) = delete;
~DummyAudioSource() = default;
void EnableLooping(bool loop) override;
void EnableSpatialization(bool spatialization) override;
float GetAttenuation() const override;
float GetMinDistance() const override;
float GetPitch() const override;
Vector3f GetPosition() const override;
UInt32 GetSampleOffset() const override;
Vector3f GetVelocity() const override;
SoundStatus GetStatus() const override;
float GetVolume() const override;
bool IsLooping() const override;
bool IsSpatializationEnabled() const override;
void QueueBuffer(std::shared_ptr<AudioBuffer> audioBuffer) override;
void Pause() override;
void Play() override;
void SetAttenuation(float attenuation) override;
void SetBuffer(std::shared_ptr<AudioBuffer> audioBuffer) override;
void SetMinDistance(float minDistance) override;
void SetPitch(float pitch) override;
void SetPosition(const Vector3f& position) override;
void SetSampleOffset(UInt32 offset) override;
void SetVelocity(const Vector3f& velocity) override;
void SetVolume(float volume) override;
void Stop() override;
std::shared_ptr<AudioBuffer> TryUnqueueProcessedBuffer() override;
void UnqueueAllBuffers() override;
DummyAudioSource& operator=(const DummyAudioSource&) = delete;
DummyAudioSource& operator=(DummyAudioSource&&) = delete;
private:
void RequeueBuffers();
UInt64 UpdateTime() const;
mutable std::vector<std::shared_ptr<DummyAudioBuffer>> m_queuedBuffers;
mutable std::vector<std::shared_ptr<DummyAudioBuffer>> m_processedBuffers;
mutable Clock m_playClock;
mutable SoundStatus m_status;
Vector3f m_position;
Vector3f m_velocity;
bool m_isLooping;
bool m_isSpatialized;
float m_attenuation;
float m_minDistance;
float m_pitch;
float m_volume;
};
}
#include <Nazara/Audio/DummyAudioSource.inl>
#endif // NAZARA_AUDIO_DUMMYAUDIOSOURCE_HPP

View File

@ -0,0 +1,26 @@
// 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/DummyAudioSource.hpp>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
inline DummyAudioSource::DummyAudioSource(std::shared_ptr<AudioDevice> device) :
AudioSource(std::move(device)),
m_playClock(0, true),
m_status(SoundStatus::Stopped),
m_position(Vector3f::Zero()),
m_velocity(Vector3f::Zero()),
m_isLooping(false),
m_isSpatialized(true),
m_attenuation(1.f),
m_minDistance(1.f),
m_pitch(1.f),
m_volume(1.f)
{
}
}
#include <Nazara/Audio/DebugOff.hpp>

View File

@ -73,7 +73,8 @@ namespace Nz
bool m_looping;
bool FillAndQueueBuffer(std::shared_ptr<AudioBuffer> buffer);
void MusicThread(std::condition_variable& cv, std::mutex& m, std::exception_ptr& err);
void MusicThread(std::condition_variable& cv, std::mutex& m, std::exception_ptr& err, bool startPaused);
void StartThread(bool startPaused);
void StopThread();
};
}

View File

@ -28,8 +28,8 @@ namespace Nz
~OpenALBuffer();
inline ALuint GetBufferId() const;
UInt32 GetSampleCount() const override;
UInt32 GetSize() const override;
UInt64 GetSampleCount() const override;
UInt64 GetSize() const override;
UInt32 GetSampleRate() const override;
bool IsCompatibleWith(const AudioDevice& device) const override;

View File

@ -28,6 +28,8 @@ namespace Nz
OpenALLibrary(OpenALLibrary&&) = delete;
inline ~OpenALLibrary();
inline bool IsLoaded() const;
bool Load();
std::vector<std::string> QueryInputDevices();
@ -49,6 +51,7 @@ namespace Nz
std::vector<std::string> ParseDevices(const char* deviceString);
DynLib m_library;
bool m_hasCaptureSupport;
};
}

View File

@ -11,6 +11,11 @@ namespace Nz
{
Unload();
}
inline bool OpenALLibrary::IsLoaded() const
{
return m_library.IsLoaded();
}
}
#include <Nazara/Audio/DebugOff.hpp>

View File

@ -26,7 +26,7 @@ namespace Nz
bool IsPaused() const;
void Pause();
UInt64 Restart();
UInt64 Restart(UInt64 startingValue = 0, bool paused = false);
void Unpause();
Clock& operator=(const Clock& clock) = default;

View File

@ -43,7 +43,7 @@ namespace Nz
struct Config
{
Nz::RenderAPI preferredAPI = RenderAPI::Unknown;
RenderAPI preferredAPI = RenderAPI::Unknown;
};
private:

View File

@ -6,6 +6,7 @@
#include <Nazara/Audio/AudioBuffer.hpp>
#include <Nazara/Audio/AudioSource.hpp>
#include <Nazara/Audio/Config.hpp>
#include <Nazara/Audio/DummyAudioDevice.hpp>
#include <Nazara/Audio/Enums.hpp>
#include <Nazara/Audio/OpenALLibrary.hpp>
#include <Nazara/Audio/Formats/drwavLoader.hpp>
@ -32,11 +33,12 @@ namespace Nz
* \brief Audio class that represents the module initializer of Audio
*/
Audio::Audio(Config /*config*/) :
ModuleBase("Audio", this)
Audio::Audio(Config config) :
ModuleBase("Audio", this),
m_hasDummyDevice(config.allowDummyDevice)
{
// Load OpenAL
if (!s_openalLibrary.Load())
if (!config.noAudio && !s_openalLibrary.Load())
throw std::runtime_error("failed to load OpenAL");
// Loaders
@ -49,7 +51,10 @@ namespace Nz
m_soundBufferLoader.RegisterLoader(Loaders::GetSoundBufferLoader_minimp3());
m_soundStreamLoader.RegisterLoader(Loaders::GetSoundStreamLoader_minimp3());
m_defaultDevice = s_openalLibrary.OpenDevice();
if (s_openalLibrary.IsLoaded())
m_defaultDevice = s_openalLibrary.OpenDevice();
else
m_defaultDevice = std::make_shared<DummyAudioDevice>();
}
Audio::~Audio()
@ -101,17 +106,30 @@ namespace Nz
std::shared_ptr<AudioDevice> Audio::OpenOutputDevice(const std::string& deviceName)
{
if (deviceName == "dummy")
return std::make_shared<DummyAudioDevice>();
return s_openalLibrary.OpenDevice(deviceName.c_str());
}
std::vector<std::string> Audio::QueryInputDevices() const
{
if (!s_openalLibrary.IsLoaded())
return {};
return s_openalLibrary.QueryInputDevices();
}
std::vector<std::string> Audio::QueryOutputDevices() const
{
return s_openalLibrary.QueryOutputDevices();
std::vector<std::string> outputDevices;
if (s_openalLibrary.IsLoaded())
outputDevices = s_openalLibrary.QueryOutputDevices();
if (m_hasDummyDevice)
outputDevices.push_back("dummy");
return outputDevices;
}
Audio* Audio::s_instance = nullptr;

View File

@ -0,0 +1,50 @@
// 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/DummyAudioBuffer.hpp>
#include <Nazara/Audio/Algorithm.hpp>
#include <Nazara/Audio/AudioDevice.hpp>
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
AudioFormat DummyAudioBuffer::GetAudioFormat() const
{
return m_format;
}
UInt32 DummyAudioBuffer::GetDuration() const
{
return SafeCast<UInt32>((1000ULL * m_sampleCount / (GetChannelCount(m_format) * m_sampleRate)));
}
UInt64 DummyAudioBuffer::GetSampleCount() const
{
return m_sampleCount;
}
UInt64 DummyAudioBuffer::GetSize() const
{
return m_sampleCount * sizeof(Int16);
}
UInt32 DummyAudioBuffer::GetSampleRate() const
{
return m_sampleRate;
}
bool DummyAudioBuffer::IsCompatibleWith(const AudioDevice& device) const
{
return GetAudioDevice()->GetSubSystemIdentifier() == device.GetSubSystemIdentifier();
}
bool DummyAudioBuffer::Reset(AudioFormat format, UInt64 sampleCount, UInt32 sampleRate, const void* /*samples*/)
{
m_format = format;
m_sampleCount = sampleCount;
m_sampleRate = sampleRate;
return true;
}
}

View File

@ -0,0 +1,110 @@
// 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/DummyAudioDevice.hpp>
#include <Nazara/Audio/DummyAudioBuffer.hpp>
#include <Nazara/Audio/DummyAudioSource.hpp>
#include <cstring>
#include <stdexcept>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
DummyAudioDevice::DummyAudioDevice() :
m_listenerRotation(Quaternionf::Identity()),
m_listenerPosition(Vector3f::Zero()),
m_dopplerFactor(1.f),
m_globalVolume(1.f),
m_speedOfSound(343.3f)
{
}
std::shared_ptr<AudioBuffer> DummyAudioDevice::CreateBuffer()
{
return std::make_shared<DummyAudioBuffer>(shared_from_this());
}
std::shared_ptr<AudioSource> DummyAudioDevice::CreateSource()
{
return std::make_shared<DummyAudioSource>(shared_from_this());
}
float DummyAudioDevice::GetDopplerFactor() const
{
return m_dopplerFactor;
}
float DummyAudioDevice::GetGlobalVolume() const
{
return m_globalVolume;
}
Vector3f DummyAudioDevice::GetListenerDirection(Vector3f* up) const
{
if (up)
*up = m_listenerRotation * Vector3f::Up();
return m_listenerRotation * Vector3f::Forward();
}
Vector3f DummyAudioDevice::GetListenerPosition() const
{
return m_listenerPosition;
}
Quaternionf DummyAudioDevice::GetListenerRotation() const
{
return m_listenerRotation;
}
Vector3f DummyAudioDevice::GetListenerVelocity() const
{
return m_listenerVelocity;
}
float DummyAudioDevice::GetSpeedOfSound() const
{
return m_speedOfSound;
}
const void* DummyAudioDevice::GetSubSystemIdentifier() const
{
return this;
}
bool DummyAudioDevice::IsFormatSupported(AudioFormat /*format*/) const
{
return true;
}
void DummyAudioDevice::SetDopplerFactor(float dopplerFactor)
{
m_dopplerFactor = dopplerFactor;
}
void DummyAudioDevice::SetGlobalVolume(float volume)
{
m_globalVolume = volume;
}
void DummyAudioDevice::SetListenerDirection(const Vector3f& direction, const Vector3f& up)
{
m_listenerRotation = Quaternionf::LookAt(direction, up);
}
void DummyAudioDevice::SetListenerPosition(const Vector3f& position)
{
m_listenerPosition = position;
}
void DummyAudioDevice::SetListenerVelocity(const Vector3f& velocity)
{
m_listenerVelocity = velocity;
}
void DummyAudioDevice::SetSpeedOfSound(float speed)
{
m_speedOfSound = speed;
}
}

View File

@ -0,0 +1,262 @@
// 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/DummyAudioSource.hpp>
#include <Nazara/Audio/Algorithm.hpp>
#include <Nazara/Audio/DummyAudioBuffer.hpp>
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Core/StackArray.hpp>
#include <algorithm>
#include <Nazara/Audio/Debug.hpp>
namespace Nz
{
void DummyAudioSource::EnableLooping(bool loop)
{
m_isLooping = loop;
}
void DummyAudioSource::EnableSpatialization(bool spatialization)
{
m_isSpatialized = spatialization;
}
float DummyAudioSource::GetAttenuation() const
{
return m_attenuation;
}
float DummyAudioSource::GetMinDistance() const
{
return m_minDistance;
}
float DummyAudioSource::GetPitch() const
{
return m_pitch;
}
Vector3f DummyAudioSource::GetPosition() const
{
return m_position;
}
UInt32 DummyAudioSource::GetSampleOffset() const
{
UInt64 bufferTime = UpdateTime();
UInt64 sampleOffset = 0;
// All processed buffers count in sample offset
for (const auto& processedBuffer : m_processedBuffers)
sampleOffset += processedBuffer->GetSampleCount() / GetChannelCount(processedBuffer->GetAudioFormat());
if (!m_queuedBuffers.empty())
{
auto& frontBuffer = m_queuedBuffers.front();
UInt64 bufferOffset = bufferTime * frontBuffer->GetSampleRate() / 1000;
UInt64 bufferDuration = frontBuffer->GetSampleCount() / GetChannelCount(frontBuffer->GetAudioFormat());
sampleOffset += std::min(bufferOffset, bufferDuration);
}
return SafeCast<UInt32>(sampleOffset);
}
Vector3f DummyAudioSource::GetVelocity() const
{
return m_velocity;
}
SoundStatus DummyAudioSource::GetStatus() const
{
UpdateTime();
return m_status;
}
float DummyAudioSource::GetVolume() const
{
return m_volume;
}
bool DummyAudioSource::IsLooping() const
{
return m_isLooping;
}
bool DummyAudioSource::IsSpatializationEnabled() const
{
return m_isSpatialized;
}
void DummyAudioSource::QueueBuffer(std::shared_ptr<AudioBuffer> audioBuffer)
{
NazaraAssert(audioBuffer, "invalid buffer");
NazaraAssert(audioBuffer->IsCompatibleWith(*GetAudioDevice()), "incompatible buffer");
m_queuedBuffers.emplace_back(std::static_pointer_cast<DummyAudioBuffer>(audioBuffer));
}
void DummyAudioSource::Pause()
{
m_playClock.Pause();
m_status = SoundStatus::Paused;
}
void DummyAudioSource::Play()
{
if (m_status == SoundStatus::Paused)
m_playClock.Unpause();
else
{
// playing or stopped, restart
RequeueBuffers();
m_playClock.Restart();
}
m_status = SoundStatus::Playing;
}
void DummyAudioSource::SetAttenuation(float attenuation)
{
m_attenuation = attenuation;
}
void DummyAudioSource::SetBuffer(std::shared_ptr<AudioBuffer> audioBuffer)
{
NazaraAssert(audioBuffer->IsCompatibleWith(*GetAudioDevice()), "incompatible buffer");
m_queuedBuffers.clear();
m_queuedBuffers.emplace_back(std::static_pointer_cast<DummyAudioBuffer>(audioBuffer));
m_processedBuffers.clear();
}
void DummyAudioSource::SetMinDistance(float minDistance)
{
m_minDistance = minDistance;
}
void DummyAudioSource::SetPitch(float pitch)
{
m_pitch = pitch;
}
void DummyAudioSource::SetPosition(const Vector3f& position)
{
m_position = position;
}
void DummyAudioSource::SetSampleOffset(UInt32 offset)
{
RequeueBuffers();
if (m_queuedBuffers.empty())
return;
std::size_t processedBufferIndex = 0;
for (; processedBufferIndex < m_queuedBuffers.size(); ++processedBufferIndex)
{
UInt32 bufferFrameCount = m_queuedBuffers[processedBufferIndex]->GetSampleCount() / GetChannelCount(m_queuedBuffers[processedBufferIndex]->GetAudioFormat());
if (offset < bufferFrameCount)
break;
offset -= bufferFrameCount;
m_processedBuffers.emplace_back(std::move(m_queuedBuffers[processedBufferIndex]));
}
m_queuedBuffers.erase(m_queuedBuffers.begin(), m_queuedBuffers.begin() + processedBufferIndex);
assert(!m_queuedBuffers.empty());
UInt64 timeOffset = 1'000'000ULL * offset / m_queuedBuffers.front()->GetSampleRate();
m_playClock.Restart(timeOffset, m_playClock.IsPaused());
}
void DummyAudioSource::SetVelocity(const Vector3f& velocity)
{
m_velocity = velocity;
}
void DummyAudioSource::SetVolume(float volume)
{
m_volume = volume;
}
void DummyAudioSource::Stop()
{
m_playClock.Restart(0, true);
}
std::shared_ptr<AudioBuffer> DummyAudioSource::TryUnqueueProcessedBuffer()
{
UpdateTime();
if (m_processedBuffers.empty())
return {};
auto processedBuffer = std::move(m_processedBuffers.front());
m_processedBuffers.erase(m_processedBuffers.begin());
return processedBuffer;
}
void DummyAudioSource::UnqueueAllBuffers()
{
m_processedBuffers.clear();
m_queuedBuffers.clear();
Stop();
}
void DummyAudioSource::RequeueBuffers()
{
// Put back all processed buffers in the queued buffer queue (for simplicity)
if (!m_processedBuffers.empty())
{
m_queuedBuffers.resize(m_processedBuffers.size() + m_queuedBuffers.size());
std::move(m_queuedBuffers.begin(), m_queuedBuffers.begin() + m_processedBuffers.size(), m_queuedBuffers.begin() + m_processedBuffers.size());
std::move(m_processedBuffers.begin(), m_processedBuffers.end(), m_queuedBuffers.begin());
m_processedBuffers.clear();
}
}
UInt64 DummyAudioSource::UpdateTime() const
{
UInt64 currentTime = m_playClock.GetMilliseconds();
while (!m_queuedBuffers.empty() && currentTime >= m_queuedBuffers.front()->GetDuration())
{
auto processedBuffer = std::move(m_queuedBuffers.front());
m_queuedBuffers.erase(m_queuedBuffers.begin());
currentTime -= processedBuffer->GetDuration();
m_processedBuffers.emplace_back(std::move(processedBuffer));
}
if (m_queuedBuffers.empty())
{
// If looping, replay processed buffers
if (m_isLooping)
{
while (!m_processedBuffers.empty())
{
auto queuedBuffer = std::move(m_processedBuffers.front());
m_processedBuffers.erase(m_processedBuffers.begin());
m_queuedBuffers.emplace_back(std::move(queuedBuffer));
if (m_queuedBuffers.back()->GetDuration() > currentTime)
break;
currentTime -= m_queuedBuffers.back()->GetDuration();
}
}
else
m_status = SoundStatus::Stopped;
}
m_playClock.Restart(currentTime * 1000, m_playClock.IsPaused()); //< Adjust time
return currentTime;
}
}

View File

@ -133,7 +133,6 @@ namespace Nz
std::lock_guard<std::mutex> lock(m_bufferLock);
UInt32 sampleOffset = m_source->GetSampleOffset();
return static_cast<UInt32>((1000ULL * (sampleOffset + (m_processedSamples / GetChannelCount(m_stream->GetFormat())))) / m_sampleRate);
}
@ -173,6 +172,8 @@ namespace Nz
{
NazaraAssert(m_stream, "Music not created");
std::lock_guard<std::mutex> lock(m_bufferLock);
SoundStatus status = m_source->GetStatus();
// To compensate any delays (or the timelaps between Play() and the thread startup)
@ -250,6 +251,8 @@ namespace Nz
*/
void Music::Pause()
{
std::lock_guard<std::mutex> lock(m_bufferLock);
m_source->Pause();
}
@ -285,24 +288,7 @@ namespace Nz
}
}
else
{
std::mutex mutex;
std::condition_variable cv;
// Starting streaming thread
m_streaming = true;
std::exception_ptr exceptionPtr;
std::unique_lock<std::mutex> lock(mutex);
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);
if (exceptionPtr)
std::rethrow_exception(exceptionPtr);
}
StartThread(false);
}
/*!
@ -319,9 +305,10 @@ namespace Nz
NazaraAssert(m_stream, "Music not created");
bool isPlaying = m_streaming;
bool isPaused = GetStatus() == SoundStatus::Paused;
if (isPlaying)
Stop();
StopThread();
UInt64 sampleOffset = UInt64(offset) * m_sampleRate * GetChannelCount(m_stream->GetFormat()) / 1000ULL;
@ -329,7 +316,7 @@ namespace Nz
m_streamOffset = sampleOffset;
if (isPlaying)
Play();
StartThread(isPaused);
}
/*!
@ -380,7 +367,7 @@ namespace Nz
return sampleRead != sampleCount; // End of stream (Does not happen when looping)
}
void Music::MusicThread(std::condition_variable& cv, std::mutex& m, std::exception_ptr& err)
void Music::MusicThread(std::condition_variable& cv, std::mutex& m, std::exception_ptr& err, bool startPaused)
{
// Allocation of streaming buffers
CallOnExit unqueueBuffers([&]
@ -406,8 +393,14 @@ namespace Nz
cv.notify_all();
return;
}
m_source->Play();
if (startPaused)
{
// little hack to start paused (required by SetPlayingOffset)
m_source->Pause();
m_source->SetSampleOffset(0);
}
CallOnExit stopSource([&]
{
@ -424,6 +417,11 @@ namespace Nz
// Reading loop (Filling new buffers as playing)
while (m_streaming)
{
// Wait until buffers are processed
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::lock_guard<std::mutex> lock(m_bufferLock);
SoundStatus status = m_source->GetStatus();
if (status == SoundStatus::Stopped)
{
@ -432,24 +430,37 @@ namespace Nz
break;
}
// We treat read buffers
while (std::shared_ptr<AudioBuffer> buffer = m_source->TryUnqueueProcessedBuffer())
{
std::lock_guard<std::mutex> lock(m_bufferLock);
m_processedSamples += buffer->GetSampleCount();
// We treat read buffers
while (std::shared_ptr<AudioBuffer> buffer = m_source->TryUnqueueProcessedBuffer())
{
m_processedSamples += buffer->GetSampleCount();
if (FillAndQueueBuffer(std::move(buffer)))
break;
}
if (FillAndQueueBuffer(std::move(buffer)))
break;
}
// We go back to sleep
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void Music::StartThread(bool startPaused)
{
std::mutex mutex;
std::condition_variable cv;
// Starting streaming thread
m_streaming = true;
std::exception_ptr exceptionPtr;
std::unique_lock<std::mutex> lock(mutex);
m_thread = std::thread(&Music::MusicThread, this, std::ref(cv), std::ref(mutex), std::ref(exceptionPtr), startPaused);
// Wait until thread signal it has properly started (or an error occurred)
cv.wait(lock);
if (exceptionPtr)
std::rethrow_exception(exceptionPtr);
}
void Music::StopThread()
{
if (m_streaming)

View File

@ -17,7 +17,7 @@ namespace Nz
m_library.alDeleteBuffers(1, &m_bufferId);
}
UInt32 OpenALBuffer::GetSampleCount() const
UInt64 OpenALBuffer::GetSampleCount() const
{
GetDevice().MakeContextCurrent();
@ -25,21 +25,21 @@ namespace Nz
m_library.alGetBufferi(m_bufferId, AL_BITS, &bits);
m_library.alGetBufferi(m_bufferId, AL_SIZE, &size);
UInt32 sampleCount = 0;
UInt64 sampleCount = 0;
if (bits != 0)
sampleCount += (8 * SafeCast<UInt32>(size)) / SafeCast<UInt32>(bits);
sampleCount += (8 * SafeCast<UInt64>(size)) / SafeCast<UInt64>(bits);
return sampleCount;
}
UInt32 OpenALBuffer::GetSize() const
UInt64 OpenALBuffer::GetSize() const
{
GetDevice().MakeContextCurrent();
ALint size;
m_library.alGetBufferi(m_bufferId, AL_SIZE, &size);
return SafeCast<UInt32>(size);
return SafeCast<UInt64>(size);
}
UInt32 OpenALBuffer::GetSampleRate() const
@ -49,7 +49,7 @@ namespace Nz
ALint sampleRate;
m_library.alGetBufferi(m_bufferId, AL_FREQUENCY, &sampleRate);
return SafeCast<UInt32>(sampleRate);
return SafeCast<UInt64>(sampleRate);
}
bool OpenALBuffer::IsCompatibleWith(const AudioDevice& device) const

View File

@ -100,7 +100,7 @@ namespace Nz
/*!
* \brief Gets the global volume
* \return Float between [0, inf) with 100.f being the default
* \return Float between [0, inf) with 1.f being the default
*/
float OpenALDevice::GetGlobalVolume() const
{
@ -109,7 +109,7 @@ namespace Nz
ALfloat gain = 0.f;
m_library.alGetListenerf(AL_GAIN, &gain);
return gain * 100.f;
return gain;
}
/*!

View File

@ -4,6 +4,7 @@
#include <Nazara/Audio/OpenALLibrary.hpp>
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <Nazara/Core/Log.hpp>
@ -20,6 +21,8 @@ namespace Nz
{
Unload();
CallOnExit unloadOnFailure([this] { Unload(); });
#if defined(NAZARA_PLATFORM_WINDOWS)
std::array libs{
"soft_oal.dll",
@ -69,15 +72,21 @@ namespace Nz
continue;
}
unloadOnFailure.Reset();
return true;
}
m_hasCaptureSupport = alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE");
NazaraError("failed to load OpenAL library");
return false;
}
std::vector<std::string> OpenALLibrary::QueryInputDevices()
{
if (!m_hasCaptureSupport)
return {};
return ParseDevices(alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER));
}

View File

@ -131,10 +131,10 @@ namespace Nz
{
GetDevice().MakeContextCurrent();
ALint relative;
m_library.alGetSourcei(m_sourceId, AL_LOOPING, &relative);
ALint looping;
m_library.alGetSourcei(m_sourceId, AL_LOOPING, &looping);
return relative == AL_FALSE;
return looping == AL_TRUE;
}
bool OpenALSource::IsSpatializationEnabled() const

View File

@ -125,7 +125,7 @@ namespace Nz
* Restarts the clock, putting it's time counter back to zero (as if the clock got constructed).
* It also compute the elapsed microseconds since the last Restart() call without any time loss (a problem that the combination of GetElapsedMicroseconds and Restart have).
*/
UInt64 Clock::Restart()
UInt64 Clock::Restart(UInt64 startingValue, bool paused)
{
Nz::UInt64 now = GetElapsedMicroseconds();
@ -133,9 +133,9 @@ namespace Nz
if (!m_paused)
elapsedTime += (now - m_refTime);
m_elapsedTime = 0;
m_elapsedTime = startingValue;
m_refTime = now;
m_paused = false;
m_paused = paused;
return elapsedTime;
}

View File

@ -34,15 +34,24 @@ SCENARIO("Music", "[AUDIO][MUSIC]")
Nz::Audio::Instance()->GetDefaultDevice()->SetGlobalVolume(0.f);
music.Play();
CHECK(music.GetStatus() == Nz::SoundStatus::Playing);
std::this_thread::sleep_for(std::chrono::seconds(1));
REQUIRE(music.GetPlayingOffset() >= 950);
CHECK(music.GetPlayingOffset() >= 950);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
REQUIRE(music.GetPlayingOffset() <= 1300);
CHECK(music.GetPlayingOffset() <= 1300);
music.SetPlayingOffset(4200);
CHECK(music.GetStatus() == Nz::SoundStatus::Playing);
CHECK(music.GetPlayingOffset() >= 4150);
CHECK(music.GetPlayingOffset() < 4500);
CHECK(music.GetStatus() == Nz::SoundStatus::Playing);
music.Pause();
REQUIRE(music.GetStatus() == Nz::SoundStatus::Paused);
CHECK(music.GetStatus() == Nz::SoundStatus::Paused);
music.SetPlayingOffset(3500);
REQUIRE(music.GetPlayingOffset() >= 3500);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
CHECK(music.GetPlayingOffset() == 3500);
Nz::Audio::Instance()->GetDefaultDevice()->SetGlobalVolume(100.f);
}

View File

@ -1,6 +1,7 @@
#define CATCH_CONFIG_RUNNER
#include <catch2/catch.hpp>
#include <Nazara/Audio/Audio.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Core/AbstractLogger.hpp>
#include <Nazara/Core/Modules.hpp>
@ -12,7 +13,7 @@
int main(int argc, char* argv[])
{
Nz::Modules<Nz::Network, Nz::Physics2D, Nz::Shader, Nz::Utility> nazaza;
Nz::Modules<Nz::Audio, Nz::Network, Nz::Physics2D, Nz::Shader, Nz::Utility> nazaza;
if (!glslang::InitializeProcess())
return EXIT_FAILURE;

View File

@ -1,26 +0,0 @@
#define CATCH_CONFIG_RUNNER
#include <catch2/catch.hpp>
#include <Nazara/Audio/Audio.hpp>
#include <Nazara/Core/AbstractLogger.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Core/Modules.hpp>
#include <Nazara/Network/Network.hpp>
#include <Nazara/Physics2D/Physics2D.hpp>
#include <Nazara/Shader/Shader.hpp>
#include <Nazara/Utility/Utility.hpp>
#include <glslang/Public/ShaderLang.h>
int main(int argc, char* argv[])
{
Nz::Modules<Nz::Audio, Nz::Network, Nz::Physics2D, Nz::Shader, Nz::Utility> nazaza;
if (!glslang::InitializeProcess())
return EXIT_FAILURE;
int result = Catch::Session().run(argc, argv);
glslang::FinalizeProcess();
return result;
}

View File

@ -17,7 +17,7 @@ if has_config("tests") then
set_group("Tests")
set_kind("binary")
add_deps("NazaraCore", "NazaraNetwork", "NazaraPhysics2D", "NazaraShader")
add_deps("NazaraAudio", "NazaraCore", "NazaraNetwork", "NazaraPhysics2D", "NazaraShader")
add_packages("catch2", "entt", "glslang", "spirv-tools")
add_headerfiles("Engine/**.hpp")
add_files("resources.cpp")
@ -28,18 +28,8 @@ if has_config("tests") then
add_rules("c++.unity_build")
end
target("NazaraClientUnitTests")
add_deps("NazaraAudio")
add_files("main_client.cpp", {unity_ignored = true})
if has_config("usepch") then
set_pcxxheader("Engine/ClientModules.hpp")
end
target("NazaraUnitTests")
add_files("main.cpp", {unity_ignored = true})
remove_headerfiles("Engine/Audio/**")
remove_files("Engine/Audio/**")
if has_config("usepch") then
set_pcxxheader("Engine/Modules.hpp")