From 82641c6653bf11f4d13504d053c46a2fb37b8c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Fri, 18 Mar 2022 19:03:57 +0100 Subject: [PATCH] Audio: Add dummy device (in case OpenAL fails to load) and unifiate unit tests --- examples/DopplerEffect/main.cpp | 5 +- examples/PlayMusic/main.cpp | 5 +- include/Nazara/Audio/Audio.hpp | 11 +- include/Nazara/Audio/AudioBuffer.hpp | 4 +- include/Nazara/Audio/DummyAudioBuffer.hpp | 47 ++++ include/Nazara/Audio/DummyAudioBuffer.inl | 12 + include/Nazara/Audio/DummyAudioDevice.hpp | 63 ++++++ include/Nazara/Audio/DummyAudioDevice.inl | 12 + include/Nazara/Audio/DummyAudioSource.hpp | 86 +++++++ include/Nazara/Audio/DummyAudioSource.inl | 26 +++ include/Nazara/Audio/Music.hpp | 3 +- include/Nazara/Audio/OpenALBuffer.hpp | 4 +- include/Nazara/Audio/OpenALLibrary.hpp | 3 + include/Nazara/Audio/OpenALLibrary.inl | 5 + include/Nazara/Core/Clock.hpp | 2 +- include/Nazara/Renderer/Renderer.hpp | 2 +- src/Nazara/Audio/Audio.cpp | 28 ++- src/Nazara/Audio/DummyAudioBuffer.cpp | 50 +++++ src/Nazara/Audio/DummyAudioDevice.cpp | 110 +++++++++ src/Nazara/Audio/DummyAudioSource.cpp | 262 ++++++++++++++++++++++ src/Nazara/Audio/Music.cpp | 81 ++++--- src/Nazara/Audio/OpenALBuffer.cpp | 12 +- src/Nazara/Audio/OpenALDevice.cpp | 4 +- src/Nazara/Audio/OpenALLibrary.cpp | 9 + src/Nazara/Audio/OpenALSource.cpp | 6 +- src/Nazara/Core/Clock.cpp | 6 +- tests/Engine/Audio/MusicTest.cpp | 17 +- tests/main.cpp | 3 +- tests/main_client.cpp | 26 --- tests/xmake.lua | 12 +- 30 files changed, 809 insertions(+), 107 deletions(-) create mode 100644 include/Nazara/Audio/DummyAudioBuffer.hpp create mode 100644 include/Nazara/Audio/DummyAudioBuffer.inl create mode 100644 include/Nazara/Audio/DummyAudioDevice.hpp create mode 100644 include/Nazara/Audio/DummyAudioDevice.inl create mode 100644 include/Nazara/Audio/DummyAudioSource.hpp create mode 100644 include/Nazara/Audio/DummyAudioSource.inl create mode 100644 src/Nazara/Audio/DummyAudioBuffer.cpp create mode 100644 src/Nazara/Audio/DummyAudioDevice.cpp create mode 100644 src/Nazara/Audio/DummyAudioSource.cpp delete mode 100644 tests/main_client.cpp diff --git a/examples/DopplerEffect/main.cpp b/examples/DopplerEffect/main.cpp index 8dfe7a4de..d5dd980dc 100644 --- a/examples/DopplerEffect/main.cpp +++ b/examples/DopplerEffect/main.cpp @@ -24,7 +24,10 @@ int main() if (!std::filesystem::is_directory(resourceDir) && std::filesystem::is_directory(".." / resourceDir)) resourceDir = ".." / resourceDir; - Nz::Modules audio; + Nz::Audio::Config config; + config.noAudio = true; + + Nz::Modules audio(config); Nz::Sound sound; if (!sound.LoadFromFile(resourceDir / "siren.wav")) diff --git a/examples/PlayMusic/main.cpp b/examples/PlayMusic/main.cpp index 97c1048a7..247422ef0 100644 --- a/examples/PlayMusic/main.cpp +++ b/examples/PlayMusic/main.cpp @@ -18,7 +18,10 @@ int main() if (!std::filesystem::is_directory(resourceDir) && std::filesystem::is_directory(".." / resourceDir)) resourceDir = ".." / resourceDir; - Nz::Modules audio; + Nz::Audio::Config config; + config.noAudio = true; + + Nz::Modules audio(config); Nz::SoundStreamParams streamParams; streamParams.forceMono = false; diff --git a/include/Nazara/Audio/Audio.hpp b/include/Nazara/Audio/Audio.hpp index d3e31983c..f851f2c3c 100644 --- a/include/Nazara/Audio/Audio.hpp +++ b/include/Nazara/Audio/Audio.hpp @@ -23,9 +23,9 @@ namespace Nz public: using Dependencies = TypeList; - 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 m_defaultDevice; SoundBufferLoader m_soundBufferLoader; SoundStreamLoader m_soundStreamLoader; + bool m_hasDummyDevice; static Audio* s_instance; }; diff --git a/include/Nazara/Audio/AudioBuffer.hpp b/include/Nazara/Audio/AudioBuffer.hpp index aa35de110..f1ae4ab25 100644 --- a/include/Nazara/Audio/AudioBuffer.hpp +++ b/include/Nazara/Audio/AudioBuffer.hpp @@ -25,8 +25,8 @@ namespace Nz virtual ~AudioBuffer(); inline const std::shared_ptr& 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; diff --git a/include/Nazara/Audio/DummyAudioBuffer.hpp b/include/Nazara/Audio/DummyAudioBuffer.hpp new file mode 100644 index 000000000..9d47a13d3 --- /dev/null +++ b/include/Nazara/Audio/DummyAudioBuffer.hpp @@ -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 +#include +#include +#include + +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 + +#endif // NAZARA_AUDIO_DUMMYAUDIOBUFFER_HPP diff --git a/include/Nazara/Audio/DummyAudioBuffer.inl b/include/Nazara/Audio/DummyAudioBuffer.inl new file mode 100644 index 000000000..392a9c2cc --- /dev/null +++ b/include/Nazara/Audio/DummyAudioBuffer.inl @@ -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 +#include + +namespace Nz +{ +} + +#include diff --git a/include/Nazara/Audio/DummyAudioDevice.hpp b/include/Nazara/Audio/DummyAudioDevice.hpp new file mode 100644 index 000000000..f3b64cc13 --- /dev/null +++ b/include/Nazara/Audio/DummyAudioDevice.hpp @@ -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 +#include +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_AUDIO_API DummyAudioDevice : public AudioDevice + { + public: + DummyAudioDevice(); + DummyAudioDevice(const DummyAudioDevice&) = delete; + DummyAudioDevice(DummyAudioDevice&&) = default; + ~DummyAudioDevice() = default; + + std::shared_ptr CreateBuffer() override; + std::shared_ptr 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 + +#endif // NAZARA_AUDIO_DUMMYAUDIODEVICE_HPP diff --git a/include/Nazara/Audio/DummyAudioDevice.inl b/include/Nazara/Audio/DummyAudioDevice.inl new file mode 100644 index 000000000..9ce83627e --- /dev/null +++ b/include/Nazara/Audio/DummyAudioDevice.inl @@ -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 +#include + +namespace Nz +{ +} + +#include diff --git a/include/Nazara/Audio/DummyAudioSource.hpp b/include/Nazara/Audio/DummyAudioSource.hpp new file mode 100644 index 000000000..3fa879772 --- /dev/null +++ b/include/Nazara/Audio/DummyAudioSource.hpp @@ -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 +#include +#include +#include + +namespace Nz +{ + class DummyAudioBuffer; + + class NAZARA_AUDIO_API DummyAudioSource final : public AudioSource + { + public: + inline DummyAudioSource(std::shared_ptr 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) override; + + void Pause() override; + void Play() override; + + void SetAttenuation(float attenuation) override; + void SetBuffer(std::shared_ptr 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 TryUnqueueProcessedBuffer() override; + + void UnqueueAllBuffers() override; + + DummyAudioSource& operator=(const DummyAudioSource&) = delete; + DummyAudioSource& operator=(DummyAudioSource&&) = delete; + + private: + void RequeueBuffers(); + UInt64 UpdateTime() const; + + mutable std::vector> m_queuedBuffers; + mutable std::vector> 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 + +#endif // NAZARA_AUDIO_DUMMYAUDIOSOURCE_HPP diff --git a/include/Nazara/Audio/DummyAudioSource.inl b/include/Nazara/Audio/DummyAudioSource.inl new file mode 100644 index 000000000..762e0eead --- /dev/null +++ b/include/Nazara/Audio/DummyAudioSource.inl @@ -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 +#include + +namespace Nz +{ + inline DummyAudioSource::DummyAudioSource(std::shared_ptr 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 diff --git a/include/Nazara/Audio/Music.hpp b/include/Nazara/Audio/Music.hpp index ff792c1e0..72738e83a 100644 --- a/include/Nazara/Audio/Music.hpp +++ b/include/Nazara/Audio/Music.hpp @@ -73,7 +73,8 @@ namespace Nz bool m_looping; bool FillAndQueueBuffer(std::shared_ptr 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(); }; } diff --git a/include/Nazara/Audio/OpenALBuffer.hpp b/include/Nazara/Audio/OpenALBuffer.hpp index af1861b11..132b2be4b 100644 --- a/include/Nazara/Audio/OpenALBuffer.hpp +++ b/include/Nazara/Audio/OpenALBuffer.hpp @@ -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; diff --git a/include/Nazara/Audio/OpenALLibrary.hpp b/include/Nazara/Audio/OpenALLibrary.hpp index ab460a760..d0d6f0806 100644 --- a/include/Nazara/Audio/OpenALLibrary.hpp +++ b/include/Nazara/Audio/OpenALLibrary.hpp @@ -28,6 +28,8 @@ namespace Nz OpenALLibrary(OpenALLibrary&&) = delete; inline ~OpenALLibrary(); + inline bool IsLoaded() const; + bool Load(); std::vector QueryInputDevices(); @@ -49,6 +51,7 @@ namespace Nz std::vector ParseDevices(const char* deviceString); DynLib m_library; + bool m_hasCaptureSupport; }; } diff --git a/include/Nazara/Audio/OpenALLibrary.inl b/include/Nazara/Audio/OpenALLibrary.inl index 681f45256..05e08e4e0 100644 --- a/include/Nazara/Audio/OpenALLibrary.inl +++ b/include/Nazara/Audio/OpenALLibrary.inl @@ -11,6 +11,11 @@ namespace Nz { Unload(); } + + inline bool OpenALLibrary::IsLoaded() const + { + return m_library.IsLoaded(); + } } #include diff --git a/include/Nazara/Core/Clock.hpp b/include/Nazara/Core/Clock.hpp index 504442466..d54698991 100644 --- a/include/Nazara/Core/Clock.hpp +++ b/include/Nazara/Core/Clock.hpp @@ -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; diff --git a/include/Nazara/Renderer/Renderer.hpp b/include/Nazara/Renderer/Renderer.hpp index d5c2628bd..29cd17967 100644 --- a/include/Nazara/Renderer/Renderer.hpp +++ b/include/Nazara/Renderer/Renderer.hpp @@ -43,7 +43,7 @@ namespace Nz struct Config { - Nz::RenderAPI preferredAPI = RenderAPI::Unknown; + RenderAPI preferredAPI = RenderAPI::Unknown; }; private: diff --git a/src/Nazara/Audio/Audio.cpp b/src/Nazara/Audio/Audio.cpp index cb74009ad..3a0b35704 100644 --- a/src/Nazara/Audio/Audio.cpp +++ b/src/Nazara/Audio/Audio.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -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(); } Audio::~Audio() @@ -101,17 +106,30 @@ namespace Nz std::shared_ptr Audio::OpenOutputDevice(const std::string& deviceName) { + if (deviceName == "dummy") + return std::make_shared(); + return s_openalLibrary.OpenDevice(deviceName.c_str()); } std::vector Audio::QueryInputDevices() const { + if (!s_openalLibrary.IsLoaded()) + return {}; + return s_openalLibrary.QueryInputDevices(); } std::vector Audio::QueryOutputDevices() const { - return s_openalLibrary.QueryOutputDevices(); + std::vector outputDevices; + if (s_openalLibrary.IsLoaded()) + outputDevices = s_openalLibrary.QueryOutputDevices(); + + if (m_hasDummyDevice) + outputDevices.push_back("dummy"); + + return outputDevices; } Audio* Audio::s_instance = nullptr; diff --git a/src/Nazara/Audio/DummyAudioBuffer.cpp b/src/Nazara/Audio/DummyAudioBuffer.cpp new file mode 100644 index 000000000..fa86cecc7 --- /dev/null +++ b/src/Nazara/Audio/DummyAudioBuffer.cpp @@ -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 +#include +#include +#include +#include + +namespace Nz +{ + AudioFormat DummyAudioBuffer::GetAudioFormat() const + { + return m_format; + } + + UInt32 DummyAudioBuffer::GetDuration() const + { + return SafeCast((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; + } +} diff --git a/src/Nazara/Audio/DummyAudioDevice.cpp b/src/Nazara/Audio/DummyAudioDevice.cpp new file mode 100644 index 000000000..e82c9483c --- /dev/null +++ b/src/Nazara/Audio/DummyAudioDevice.cpp @@ -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 +#include +#include +#include +#include +#include + +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 DummyAudioDevice::CreateBuffer() + { + return std::make_shared(shared_from_this()); + } + + std::shared_ptr DummyAudioDevice::CreateSource() + { + return std::make_shared(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; + } +} diff --git a/src/Nazara/Audio/DummyAudioSource.cpp b/src/Nazara/Audio/DummyAudioSource.cpp new file mode 100644 index 000000000..5b0993420 --- /dev/null +++ b/src/Nazara/Audio/DummyAudioSource.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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(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) + { + NazaraAssert(audioBuffer, "invalid buffer"); + NazaraAssert(audioBuffer->IsCompatibleWith(*GetAudioDevice()), "incompatible buffer"); + + m_queuedBuffers.emplace_back(std::static_pointer_cast(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) + { + NazaraAssert(audioBuffer->IsCompatibleWith(*GetAudioDevice()), "incompatible buffer"); + + m_queuedBuffers.clear(); + m_queuedBuffers.emplace_back(std::static_pointer_cast(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 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; + } +} diff --git a/src/Nazara/Audio/Music.cpp b/src/Nazara/Audio/Music.cpp index 17cda430b..b5a75b549 100644 --- a/src/Nazara/Audio/Music.cpp +++ b/src/Nazara/Audio/Music.cpp @@ -133,7 +133,6 @@ namespace Nz std::lock_guard lock(m_bufferLock); UInt32 sampleOffset = m_source->GetSampleOffset(); - return static_cast((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 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 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 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 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 buffer = m_source->TryUnqueueProcessedBuffer()) { - std::lock_guard lock(m_bufferLock); + m_processedSamples += buffer->GetSampleCount(); - // We treat read buffers - while (std::shared_ptr 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 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) diff --git a/src/Nazara/Audio/OpenALBuffer.cpp b/src/Nazara/Audio/OpenALBuffer.cpp index 90d108673..f4bc1b542 100644 --- a/src/Nazara/Audio/OpenALBuffer.cpp +++ b/src/Nazara/Audio/OpenALBuffer.cpp @@ -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(size)) / SafeCast(bits); + sampleCount += (8 * SafeCast(size)) / SafeCast(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(size); + return SafeCast(size); } UInt32 OpenALBuffer::GetSampleRate() const @@ -49,7 +49,7 @@ namespace Nz ALint sampleRate; m_library.alGetBufferi(m_bufferId, AL_FREQUENCY, &sampleRate); - return SafeCast(sampleRate); + return SafeCast(sampleRate); } bool OpenALBuffer::IsCompatibleWith(const AudioDevice& device) const diff --git a/src/Nazara/Audio/OpenALDevice.cpp b/src/Nazara/Audio/OpenALDevice.cpp index 1ac5fce2b..4d5afb3c7 100644 --- a/src/Nazara/Audio/OpenALDevice.cpp +++ b/src/Nazara/Audio/OpenALDevice.cpp @@ -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; } /*! diff --git a/src/Nazara/Audio/OpenALLibrary.cpp b/src/Nazara/Audio/OpenALLibrary.cpp index eccdd16b1..a84d1741c 100644 --- a/src/Nazara/Audio/OpenALLibrary.cpp +++ b/src/Nazara/Audio/OpenALLibrary.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -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 OpenALLibrary::QueryInputDevices() { + if (!m_hasCaptureSupport) + return {}; + return ParseDevices(alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER)); } diff --git a/src/Nazara/Audio/OpenALSource.cpp b/src/Nazara/Audio/OpenALSource.cpp index 44b1f98d3..4f91fc4d9 100644 --- a/src/Nazara/Audio/OpenALSource.cpp +++ b/src/Nazara/Audio/OpenALSource.cpp @@ -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 diff --git a/src/Nazara/Core/Clock.cpp b/src/Nazara/Core/Clock.cpp index cc6f69e9e..40c1c5476 100644 --- a/src/Nazara/Core/Clock.cpp +++ b/src/Nazara/Core/Clock.cpp @@ -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; } diff --git a/tests/Engine/Audio/MusicTest.cpp b/tests/Engine/Audio/MusicTest.cpp index 70e266eff..05569ee18 100644 --- a/tests/Engine/Audio/MusicTest.cpp +++ b/tests/Engine/Audio/MusicTest.cpp @@ -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); } diff --git a/tests/main.cpp b/tests/main.cpp index 38e3ef663..4184253a2 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,6 +1,7 @@ #define CATCH_CONFIG_RUNNER #include +#include #include #include #include @@ -12,7 +13,7 @@ int main(int argc, char* argv[]) { - Nz::Modules nazaza; + Nz::Modules nazaza; if (!glslang::InitializeProcess()) return EXIT_FAILURE; diff --git a/tests/main_client.cpp b/tests/main_client.cpp deleted file mode 100644 index 7afe76354..000000000 --- a/tests/main_client.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#define CATCH_CONFIG_RUNNER -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char* argv[]) -{ - Nz::Modules nazaza; - - if (!glslang::InitializeProcess()) - return EXIT_FAILURE; - - int result = Catch::Session().run(argc, argv); - - glslang::FinalizeProcess(); - - return result; -} diff --git a/tests/xmake.lua b/tests/xmake.lua index dbf8ffd12..1d8e6a80e 100644 --- a/tests/xmake.lua +++ b/tests/xmake.lua @@ -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")