diff --git a/engine/include/XCEngine/Audio/AudioSystem.h b/engine/include/XCEngine/Audio/AudioSystem.h index 52ec907b..6819ed55 100644 --- a/engine/include/XCEngine/Audio/AudioSystem.h +++ b/engine/include/XCEngine/Audio/AudioSystem.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ namespace Audio { class AudioSystem { public: static AudioSystem& Get(); + static AudioSystem* TryGetExisting(); void Initialize(const AudioConfig& config); void Shutdown(); @@ -30,6 +32,9 @@ public: void SetBackend(std::unique_ptr backend); IAudioBackend* GetBackend() const { return m_backend.get(); } + [[nodiscard]] std::unique_lock AcquireStateLock() { + return std::unique_lock(m_stateMutex); + } AudioMixer& GetMasterMixer() { return m_masterMixer; } const AudioMixer& GetMasterMixer() const { return m_masterMixer; } @@ -80,8 +85,8 @@ public: const Stats& GetStats() const { return m_stats; } private: - AudioSystem() = default; - ~AudioSystem() = default; + AudioSystem(); + ~AudioSystem(); AudioSystem(const AudioSystem&) = delete; AudioSystem& operator=(const AudioSystem&) = delete; @@ -89,8 +94,10 @@ private: void ProcessSource(Components::AudioSourceComponent* source, float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate); void RenderAudioBlock(uint32 frameCount, uint32 channels, uint32 sampleRate); void DetachMixerReferences(AudioMixer* mixer); + void UpdateStats(); private: + mutable std::recursive_mutex m_stateMutex; std::unique_ptr m_backend; AudioMixer m_masterMixer; AudioMixer* m_listenerReverbMixer = nullptr; diff --git a/engine/include/XCEngine/Audio/IAudioBackend.h b/engine/include/XCEngine/Audio/IAudioBackend.h index aef2ca88..adb302f5 100644 --- a/engine/include/XCEngine/Audio/IAudioBackend.h +++ b/engine/include/XCEngine/Audio/IAudioBackend.h @@ -1,13 +1,17 @@ #pragma once #include "AudioConfig.h" +#include #include +#include namespace XCEngine { namespace Audio { class IAudioBackend { public: + using RenderCallback = std::function; + virtual ~IAudioBackend() = default; virtual bool Initialize(const AudioConfig& config) = 0; @@ -26,6 +30,8 @@ public: virtual void ProcessAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) = 0; + virtual bool UsesPullModel() const { return false; } + virtual void SetRenderCallback(RenderCallback callback) { (void)callback; } virtual bool IsRunning() const = 0; virtual AudioConfig GetConfig() const = 0; diff --git a/engine/include/XCEngine/Audio/WindowsAudioBackend.h b/engine/include/XCEngine/Audio/WindowsAudioBackend.h index b44c630b..002ae8fc 100644 --- a/engine/include/XCEngine/Audio/WindowsAudioBackend.h +++ b/engine/include/XCEngine/Audio/WindowsAudioBackend.h @@ -39,6 +39,8 @@ public: void ProcessAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) override; + bool UsesPullModel() const override { return true; } + void SetRenderCallback(RenderCallback callback) override; bool IsRunning() const override { return m_isRunning.load(); } AudioConfig GetConfig() const override { return m_config; } @@ -76,9 +78,6 @@ private: WAVEFORMATEX m_waveFormat = {}; HWAVEOUT m_hWaveOut = nullptr; - std::vector m_mixBuffer; - uint32 m_mixBufferSize = 0; - std::vector m_waveOutCaps; std::string m_deviceName; std::string m_requestedDeviceName; @@ -86,13 +85,13 @@ private: static constexpr size_t BufferSize = 8192; std::vector m_audioBuffer1; std::vector m_audioBuffer2; - std::vector m_pendingMixBuffer; - bool m_hasPendingMix = false; + std::vector m_renderBuffer; bool m_buffer1Available = true; bool m_buffer2Available = true; + RenderCallback m_renderCallback; std::mutex m_bufferMutex; - std::condition_variable m_dataReadyCond; + std::condition_variable m_bufferReadyCond; WAVEHDR m_waveHeader1 = {}; WAVEHDR m_waveHeader2 = {}; diff --git a/engine/src/Audio/AudioMixer.cpp b/engine/src/Audio/AudioMixer.cpp index 846f78a4..76024408 100644 --- a/engine/src/Audio/AudioMixer.cpp +++ b/engine/src/Audio/AudioMixer.cpp @@ -9,6 +9,13 @@ namespace Audio { namespace { +std::unique_lock LockAudioState() { + if (AudioSystem* audioSystem = AudioSystem::TryGetExisting()) { + return audioSystem->AcquireStateLock(); + } + return std::unique_lock(); +} + AudioChannel ResolveChannel(uint32 channelIndex) { switch (channelIndex) { case 0: return AudioChannel::FrontLeft; @@ -27,31 +34,39 @@ AudioChannel ResolveChannel(uint32 channelIndex) { AudioMixer::AudioMixer() { - AudioSystem::Get().RegisterMixer(this); + if (AudioSystem* audioSystem = AudioSystem::TryGetExisting()) { + audioSystem->RegisterMixer(this); + } for (int i = 0; i < static_cast(AudioChannel::SideRight) + 1; ++i) { m_channelVolumes[static_cast(i)] = ChannelVolume{1.0f, false}; } } AudioMixer::~AudioMixer() { - AudioSystem::Get().UnregisterMixer(this); + if (AudioSystem* audioSystem = AudioSystem::TryGetExisting()) { + audioSystem->UnregisterMixer(this); + } } void AudioMixer::SetVolume(float volume) { + auto audioStateLock = LockAudioState(); m_volume = std::max(0.0f, std::min(1.0f, volume)); } void AudioMixer::SetMute(bool mute) { + auto audioStateLock = LockAudioState(); m_mute = mute; } void AudioMixer::AddEffect(IAudioEffect* effect) { + auto audioStateLock = LockAudioState(); if (effect && std::find(m_effects.begin(), m_effects.end(), effect) == m_effects.end()) { m_effects.push_back(effect); } } void AudioMixer::RemoveEffect(IAudioEffect* effect) { + auto audioStateLock = LockAudioState(); if (!effect) return; auto it = std::find(m_effects.begin(), m_effects.end(), effect); if (it != m_effects.end()) { @@ -60,10 +75,12 @@ void AudioMixer::RemoveEffect(IAudioEffect* effect) { } void AudioMixer::ClearEffects() { + auto audioStateLock = LockAudioState(); m_effects.clear(); } void AudioMixer::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) { + auto audioStateLock = LockAudioState(); if (!buffer || frameCount == 0 || channels == 0) { return; } @@ -98,14 +115,17 @@ void AudioMixer::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels, } void AudioMixer::SetOutputMixer(AudioMixer* mixer) { + auto audioStateLock = LockAudioState(); m_outputMixer = mixer; } void AudioMixer::Set3DParams(const Audio3DParams& params) { + auto audioStateLock = LockAudioState(); m_3DParams = params; } void AudioMixer::SetChannelVolume(AudioChannel channel, float volume) { + auto audioStateLock = LockAudioState(); auto it = m_channelVolumes.find(channel); if (it != m_channelVolumes.end()) { it->second.volume = std::max(0.0f, std::min(1.0f, volume)); @@ -113,6 +133,7 @@ void AudioMixer::SetChannelVolume(AudioChannel channel, float volume) { } float AudioMixer::GetChannelVolume(AudioChannel channel) const { + auto audioStateLock = LockAudioState(); auto it = m_channelVolumes.find(channel); if (it != m_channelVolumes.end()) { return it->second.volume; diff --git a/engine/src/Audio/AudioSystem.cpp b/engine/src/Audio/AudioSystem.cpp index 65e6bdcf..8bb92b49 100644 --- a/engine/src/Audio/AudioSystem.cpp +++ b/engine/src/Audio/AudioSystem.cpp @@ -12,6 +12,8 @@ namespace Audio { namespace { +AudioSystem* g_audioSystemInstance = nullptr; + void MixBufferInto(const std::vector& source, std::vector& destination) { if (destination.size() < source.size()) { destination.resize(source.size(), 0.0f); @@ -93,79 +95,128 @@ AudioSystem& AudioSystem::Get() { return instance; } -void AudioSystem::Initialize(const AudioConfig& config) { - if (m_backend) { - Shutdown(); - } +AudioSystem* AudioSystem::TryGetExisting() { + return g_audioSystemInstance; +} - m_backend = std::make_unique(); - if (m_backend->Initialize(config)) { - m_backend->Start(); +AudioSystem::AudioSystem() { + g_audioSystemInstance = this; + RegisterMixer(&m_masterMixer); +} + +AudioSystem::~AudioSystem() { + g_audioSystemInstance = nullptr; +} + +void AudioSystem::Initialize(const AudioConfig& config) { + Shutdown(); + + auto backend = std::make_unique(); + backend->SetRenderCallback( + [this](float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) { + RenderAudio(buffer, frameCount, channels, sampleRate); + }); + + if (backend->Initialize(config)) { + IAudioBackend* backendPtr = backend.get(); + { + std::lock_guard lock(m_stateMutex); + m_backend = std::move(backend); + } + backendPtr->Start(); std::cout << "AudioSystem initialized successfully" << std::endl; } else { std::cout << "Failed to initialize AudioSystem" << std::endl; - m_backend.reset(); } } void AudioSystem::Shutdown() { - if (m_backend) { - m_backend->Stop(); - m_backend->Shutdown(); - m_backend.reset(); + std::unique_ptr backend; + { + std::lock_guard lock(m_stateMutex); + backend = std::move(m_backend); + } + + if (backend) { + if (backend->IsRunning()) { + backend->Stop(); + } + backend->Shutdown(); + } + + { + std::lock_guard lock(m_stateMutex); + m_registeredMixers.clear(); + m_registeredSourceComponents.clear(); + m_registeredListenerComponents.clear(); + m_activeSources.clear(); + m_listenerReverbMixer = nullptr; + m_listenerReverbLevel = 1.0f; + m_listenerDopplerLevel = 1.0f; + m_speedOfSound = 343.0f; + m_listenerPosition = Math::Vector3::Zero(); + m_listenerRotation = Math::Quaternion::Identity(); + m_listenerVelocity = Math::Vector3::Zero(); + m_activeSourceSnapshot.clear(); + m_mixScratchBuffer.clear(); + m_sourceScratchBuffer.clear(); + m_mixerScratchBuffers.clear(); + m_mixerScratchChildren.clear(); + m_mixerScratchActiveMixers.clear(); + m_mixerScratchVisiting.clear(); + m_mixerScratchRendered.clear(); + m_deltaTime = 0.0f; + m_stats = {}; } - m_registeredMixers.clear(); - m_registeredSourceComponents.clear(); - m_registeredListenerComponents.clear(); - m_activeSources.clear(); - m_listenerReverbMixer = nullptr; - m_listenerReverbLevel = 1.0f; - m_listenerDopplerLevel = 1.0f; - m_speedOfSound = 343.0f; - m_listenerPosition = Math::Vector3::Zero(); - m_listenerRotation = Math::Quaternion::Identity(); - m_listenerVelocity = Math::Vector3::Zero(); - m_activeSourceSnapshot.clear(); - m_mixScratchBuffer.clear(); - m_sourceScratchBuffer.clear(); - m_mixerScratchBuffers.clear(); - m_mixerScratchChildren.clear(); - m_mixerScratchActiveMixers.clear(); - m_mixerScratchVisiting.clear(); - m_mixerScratchRendered.clear(); } void AudioSystem::Update(float deltaTime) { + std::lock_guard lock(m_stateMutex); m_deltaTime = deltaTime; if (!m_backend || !m_backend->IsRunning()) { return; } - const auto& config = m_backend->GetConfig(); - const uint32 frameCount = config.bufferSize; - RenderAudioBlock(frameCount, config.channels, config.sampleRate); - m_backend->ProcessAudio( - m_mixScratchBuffer.data(), - frameCount, - config.channels, - config.sampleRate); - - uint32 activeCount = 0; - for (auto* source : m_activeSources) { - if (source && source->IsPlaying()) { - activeCount++; - } + if (!m_backend->UsesPullModel()) { + const auto& config = m_backend->GetConfig(); + const uint32 frameCount = config.bufferSize; + RenderAudioBlock(frameCount, config.channels, config.sampleRate); + m_backend->ProcessAudio( + m_mixScratchBuffer.data(), + frameCount, + config.channels, + config.sampleRate); + } else { + UpdateStats(); } - m_stats.activeSources = activeCount; - m_stats.totalSources = static_cast(m_activeSources.size()); } void AudioSystem::SetBackend(std::unique_ptr backend) { - m_backend = std::move(backend); + std::unique_ptr previousBackend; + if (backend) { + backend->SetRenderCallback( + [this](float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) { + RenderAudio(buffer, frameCount, channels, sampleRate); + }); + } + + { + std::lock_guard lock(m_stateMutex); + previousBackend = std::move(m_backend); + m_backend = std::move(backend); + } + + if (previousBackend) { + if (previousBackend->IsRunning()) { + previousBackend->Stop(); + } + previousBackend->Shutdown(); + } } std::string AudioSystem::GetCurrentDevice() const { + std::lock_guard lock(m_stateMutex); if (m_backend) { return m_backend->GetDeviceName(); } @@ -173,50 +224,61 @@ std::string AudioSystem::GetCurrentDevice() const { } void AudioSystem::SetDevice(const std::string& deviceName) { + std::lock_guard lock(m_stateMutex); if (m_backend) { m_backend->SetDevice(deviceName); } } void AudioSystem::GetAvailableDevices(std::vector& devices) { + std::lock_guard lock(m_stateMutex); if (m_backend) { m_backend->GetAvailableDevices(devices); } } float AudioSystem::GetMasterVolume() const { + std::lock_guard lock(m_stateMutex); return m_masterMixer.GetVolume(); } void AudioSystem::SetMasterVolume(float volume) { + std::lock_guard lock(m_stateMutex); m_masterMixer.SetVolume(volume); } bool AudioSystem::IsMuted() const { + std::lock_guard lock(m_stateMutex); return m_masterMixer.IsMute(); } void AudioSystem::SetMuted(bool muted) { + std::lock_guard lock(m_stateMutex); m_masterMixer.SetMute(muted); } void AudioSystem::SetListenerReverbMixer(AudioMixer* mixer) { + std::lock_guard lock(m_stateMutex); m_listenerReverbMixer = mixer; } void AudioSystem::SetListenerReverbLevel(float level) { + std::lock_guard lock(m_stateMutex); m_listenerReverbLevel = std::clamp(level, 0.0f, 1.0f); } void AudioSystem::SetListenerDopplerLevel(float level) { + std::lock_guard lock(m_stateMutex); m_listenerDopplerLevel = std::clamp(level, 0.0f, 5.0f); } void AudioSystem::SetSpeedOfSound(float metersPerSecond) { + std::lock_guard lock(m_stateMutex); m_speedOfSound = std::max(1.0f, metersPerSecond); } void AudioSystem::RenderAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) { + std::lock_guard lock(m_stateMutex); if (buffer == nullptr || frameCount == 0 || channels == 0 || sampleRate == 0) { return; } @@ -227,6 +289,7 @@ void AudioSystem::RenderAudio(float* buffer, uint32 frameCount, uint32 channels, } void AudioSystem::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels) { + std::lock_guard lock(m_stateMutex); if (m_backend) { const uint32 sampleRate = m_backend->GetConfig().sampleRate != 0 ? m_backend->GetConfig().sampleRate : 48000; @@ -235,15 +298,18 @@ void AudioSystem::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels } void AudioSystem::SetListenerTransform(const Math::Vector3& position, const Math::Quaternion& rotation) { + std::lock_guard lock(m_stateMutex); m_listenerPosition = position; m_listenerRotation = rotation; } void AudioSystem::SetListenerVelocity(const Math::Vector3& velocity) { + std::lock_guard lock(m_stateMutex); m_listenerVelocity = velocity; } void AudioSystem::RegisterSource(Components::AudioSourceComponent* source) { + std::lock_guard lock(m_stateMutex); if (!source) { return; } @@ -254,6 +320,7 @@ void AudioSystem::RegisterSource(Components::AudioSourceComponent* source) { } void AudioSystem::RegisterSourceComponent(Components::AudioSourceComponent* source) { + std::lock_guard lock(m_stateMutex); if (!source) { return; } @@ -265,6 +332,7 @@ void AudioSystem::RegisterSourceComponent(Components::AudioSourceComponent* sour } void AudioSystem::UnregisterSourceComponent(Components::AudioSourceComponent* source) { + std::lock_guard lock(m_stateMutex); if (!source) { return; } @@ -276,6 +344,7 @@ void AudioSystem::UnregisterSourceComponent(Components::AudioSourceComponent* so } void AudioSystem::RegisterListenerComponent(Components::AudioListenerComponent* listener) { + std::lock_guard lock(m_stateMutex); if (!listener) { return; } @@ -287,6 +356,7 @@ void AudioSystem::RegisterListenerComponent(Components::AudioListenerComponent* } void AudioSystem::UnregisterListenerComponent(Components::AudioListenerComponent* listener) { + std::lock_guard lock(m_stateMutex); if (!listener) { return; } @@ -297,6 +367,7 @@ void AudioSystem::UnregisterListenerComponent(Components::AudioListenerComponent } void AudioSystem::RegisterMixer(AudioMixer* mixer) { + std::lock_guard lock(m_stateMutex); if (!mixer) { return; } @@ -307,6 +378,7 @@ void AudioSystem::RegisterMixer(AudioMixer* mixer) { } void AudioSystem::UnregisterMixer(AudioMixer* mixer) { + std::lock_guard lock(m_stateMutex); if (!mixer) { return; } @@ -318,6 +390,7 @@ void AudioSystem::UnregisterMixer(AudioMixer* mixer) { } void AudioSystem::UnregisterSource(Components::AudioSourceComponent* source) { + std::lock_guard lock(m_stateMutex); if (!source) { return; } @@ -377,8 +450,7 @@ void AudioSystem::RenderAudioBlock(uint32 frameCount, uint32 channels, uint32 sa if (sampleCount == 0 || sampleRate == 0) { m_mixScratchBuffer.clear(); m_sourceScratchBuffer.clear(); - m_stats.activeSources = 0; - m_stats.totalSources = static_cast(m_activeSources.size()); + UpdateStats(); return; } @@ -481,6 +553,10 @@ void AudioSystem::RenderAudioBlock(uint32 frameCount, uint32 channels, uint32 sa channels, sampleRate); + UpdateStats(); +} + +void AudioSystem::UpdateStats() { uint32 activeCount = 0; for (auto* source : m_activeSources) { if (source && source->IsPlaying()) { diff --git a/engine/src/Audio/WindowsAudioBackend.cpp b/engine/src/Audio/WindowsAudioBackend.cpp index 50f1a292..b2bac3fd 100644 --- a/engine/src/Audio/WindowsAudioBackend.cpp +++ b/engine/src/Audio/WindowsAudioBackend.cpp @@ -64,8 +64,7 @@ bool WaveOutBackend::Initialize(const AudioConfig& config) { const size_t bufferSampleCount = static_cast(config.bufferSize) * config.channels; m_audioBuffer1.assign(bufferSampleCount, 0); m_audioBuffer2.assign(bufferSampleCount, 0); - m_pendingMixBuffer.assign(bufferSampleCount, 0.0f); - m_hasPendingMix = false; + m_renderBuffer.assign(bufferSampleCount, 0.0f); m_buffer1Available = true; m_buffer2Available = true; @@ -152,7 +151,7 @@ void WaveOutBackend::Start() { void WaveOutBackend::Stop() { m_isRunning = false; - m_dataReadyCond.notify_all(); + m_bufferReadyCond.notify_all(); if (m_hWaveOut != nullptr) { waveOutReset(m_hWaveOut); @@ -180,20 +179,15 @@ void WaveOutBackend::Resume() { void WaveOutBackend::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) { + (void)buffer; + (void)frameCount; + (void)channels; (void)sampleRate; +} - if (buffer == nullptr) { - return; - } - - const uint32 sampleCount = frameCount * channels; +void WaveOutBackend::SetRenderCallback(RenderCallback callback) { std::lock_guard lock(m_bufferMutex); - m_pendingMixBuffer.assign(m_pendingMixBuffer.size(), 0.0f); - for (uint32 i = 0; i < sampleCount && i < m_pendingMixBuffer.size(); ++i) { - m_pendingMixBuffer[i] = buffer[i]; - } - m_hasPendingMix = true; - m_dataReadyCond.notify_one(); + m_renderCallback = std::move(callback); } MMRESULT WaveOutBackend::InitDevice() { @@ -269,9 +263,8 @@ DWORD WINAPI WaveOutBackend::AudioThreadProc(LPVOID lpParameter) { void WaveOutBackend::AudioThread() { while (m_isRunning.load()) { std::unique_lock lock(m_bufferMutex); - m_dataReadyCond.wait(lock, [this] { - return !m_isRunning.load() || - (m_hasPendingMix && (m_buffer1Available || m_buffer2Available)); + m_bufferReadyCond.wait(lock, [this] { + return !m_isRunning.load() || m_buffer1Available || m_buffer2Available; }); if (!m_isRunning.load()) { @@ -293,10 +286,31 @@ void WaveOutBackend::AudioThread() { continue; } - FillPcm16Buffer(*targetBuffer, m_pendingMixBuffer); - m_hasPendingMix = false; + RenderCallback renderCallback; + const uint32 frameCount = m_config.bufferSize; + const uint32 channels = m_config.channels; + const uint32 sampleRate = m_config.sampleRate; + const size_t sampleCount = static_cast(frameCount) * channels; + + if (targetBuffer->size() != sampleCount) { + targetBuffer->assign(sampleCount, 0); + } else { + std::fill(targetBuffer->begin(), targetBuffer->end(), 0); + } + + if (m_renderBuffer.size() != sampleCount) { + m_renderBuffer.assign(sampleCount, 0.0f); + } else { + std::fill(m_renderBuffer.begin(), m_renderBuffer.end(), 0.0f); + } + renderCallback = m_renderCallback; lock.unlock(); + if (renderCallback && !m_renderBuffer.empty()) { + renderCallback(m_renderBuffer.data(), frameCount, channels, sampleRate); + } + FillPcm16Buffer(*targetBuffer, m_renderBuffer); + if (SubmitBuffer(*targetHeader, *targetBuffer) != MMSYSERR_NOERROR) { std::lock_guard restoreLock(m_bufferMutex); if (targetHeader == &m_waveHeader1) { @@ -304,6 +318,7 @@ void WaveOutBackend::AudioThread() { } else { m_buffer2Available = true; } + m_bufferReadyCond.notify_one(); } } } @@ -322,7 +337,7 @@ void WaveOutBackend::OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstan } else if (completedHeader == &m_waveHeader2) { m_buffer2Available = true; } - m_dataReadyCond.notify_one(); + m_bufferReadyCond.notify_one(); } } diff --git a/engine/src/Components/AudioListenerComponent.cpp b/engine/src/Components/AudioListenerComponent.cpp index 2d2d63fd..db378d63 100644 --- a/engine/src/Components/AudioListenerComponent.cpp +++ b/engine/src/Components/AudioListenerComponent.cpp @@ -7,6 +7,17 @@ namespace XCEngine { namespace Components { +namespace { + +std::unique_lock LockAudioState() { + if (Audio::AudioSystem* audioSystem = Audio::AudioSystem::TryGetExisting()) { + return audioSystem->AcquireStateLock(); + } + return std::unique_lock(); +} + +} // namespace + AudioListenerComponent::AudioListenerComponent() { Audio::AudioSystem::Get().RegisterListenerComponent(this); @@ -17,36 +28,43 @@ AudioListenerComponent::~AudioListenerComponent() { } void AudioListenerComponent::SetMasterVolume(float volume) { + auto audioStateLock = LockAudioState(); m_masterVolume = std::max(0.0f, std::min(1.0f, volume)); Audio::AudioSystem::Get().SetMasterVolume(m_masterVolume); } void AudioListenerComponent::SetMute(bool mute) { + auto audioStateLock = LockAudioState(); m_mute = mute; Audio::AudioSystem::Get().SetMuted(m_mute); } void AudioListenerComponent::SetDopplerLevel(float level) { + auto audioStateLock = LockAudioState(); m_dopplerLevel = std::max(0.0f, std::min(5.0f, level)); Audio::AudioSystem::Get().SetListenerDopplerLevel(m_dopplerLevel); } void AudioListenerComponent::SetSpeedOfSound(float metersPerSecond) { + auto audioStateLock = LockAudioState(); m_speedOfSound = std::max(1.0f, metersPerSecond); Audio::AudioSystem::Get().SetSpeedOfSound(m_speedOfSound); } void AudioListenerComponent::SetReverbLevel(float level) { + auto audioStateLock = LockAudioState(); m_reverbLevel = std::max(0.0f, std::min(1.0f, level)); Audio::AudioSystem::Get().SetListenerReverbLevel(m_reverbLevel); } void AudioListenerComponent::SetReverb(Audio::AudioMixer* reverb) { + auto audioStateLock = LockAudioState(); m_reverb = reverb; Audio::AudioSystem::Get().SetListenerReverbMixer(m_reverb); } void AudioListenerComponent::Update(float deltaTime) { + auto audioStateLock = LockAudioState(); if (!m_gameObject) { return; } @@ -67,6 +85,7 @@ void AudioListenerComponent::Update(float deltaTime) { } void AudioListenerComponent::Serialize(std::ostream& os) const { + auto audioStateLock = LockAudioState(); os << "masterVolume=" << m_masterVolume << ";"; os << "mute=" << (m_mute ? 1 : 0) << ";"; os << "dopplerLevel=" << m_dopplerLevel << ";"; @@ -75,6 +94,7 @@ void AudioListenerComponent::Serialize(std::ostream& os) const { } void AudioListenerComponent::Deserialize(std::istream& is) { + auto audioStateLock = LockAudioState(); SetMasterVolume(1.0f); SetMute(false); SetDopplerLevel(1.0f); diff --git a/engine/src/Components/AudioSourceComponent.cpp b/engine/src/Components/AudioSourceComponent.cpp index 46340f01..9d655fe3 100644 --- a/engine/src/Components/AudioSourceComponent.cpp +++ b/engine/src/Components/AudioSourceComponent.cpp @@ -144,6 +144,13 @@ void ApplyPanToBuffer(float* buffer, Audio::uint32 frameCount, Audio::uint32 cha } } +std::unique_lock LockAudioState() { + if (Audio::AudioSystem* audioSystem = Audio::AudioSystem::TryGetExisting()) { + return audioSystem->AcquireStateLock(); + } + return std::unique_lock(); +} + } // namespace AudioSourceComponent::AudioSourceComponent() { @@ -158,6 +165,7 @@ AudioSourceComponent::~AudioSourceComponent() { } void AudioSourceComponent::Play() { + auto audioStateLock = LockAudioState(); if (!m_clip || !m_clip->IsValid()) { return; } @@ -189,6 +197,7 @@ void AudioSourceComponent::Play() { } void AudioSourceComponent::Pause() { + auto audioStateLock = LockAudioState(); if (m_playState == Audio::PlayState::Playing) { m_playState = Audio::PlayState::Paused; Audio::AudioSystem::Get().UnregisterSource(this); @@ -196,6 +205,7 @@ void AudioSourceComponent::Pause() { } void AudioSourceComponent::Stop(Audio::StopMode mode) { + auto audioStateLock = LockAudioState(); if (m_playState != Audio::PlayState::Stopped) { m_playState = Audio::PlayState::Stopped; m_samplePosition = 0; @@ -209,6 +219,7 @@ void AudioSourceComponent::Stop(Audio::StopMode mode) { } void AudioSourceComponent::SetClip(Resources::AudioClip* clip) { + auto audioStateLock = LockAudioState(); m_clipHandle = Resources::ResourceHandle(clip); m_clip = clip; m_clipPath.clear(); @@ -235,6 +246,7 @@ void AudioSourceComponent::SetClip(Resources::AudioClip* clip) { } void AudioSourceComponent::SetClipPath(const std::string& clipPath) { + auto audioStateLock = LockAudioState(); m_clipRef.Reset(); m_clipPath = clipPath; if (!m_clipPath.empty() && @@ -266,24 +278,29 @@ void AudioSourceComponent::SetClipPath(const std::string& clipPath) { } void AudioSourceComponent::ClearClip() { + auto audioStateLock = LockAudioState(); m_clipRef.Reset(); m_clipPath.clear(); SetClip(nullptr); } void AudioSourceComponent::SetVolume(float volume) { + auto audioStateLock = LockAudioState(); m_volume = std::max(0.0f, std::min(1.0f, volume)); } void AudioSourceComponent::SetPitch(float pitch) { + auto audioStateLock = LockAudioState(); m_pitch = std::max(0.0f, std::min(3.0f, pitch)); } void AudioSourceComponent::SetPan(float pan) { + auto audioStateLock = LockAudioState(); m_pan = std::max(-1.0f, std::min(1.0f, pan)); } void AudioSourceComponent::SetHRTFEnabled(bool enabled) { + auto audioStateLock = LockAudioState(); m_useHRTF = enabled; if (!enabled) { m_hrtf.ResetState(); @@ -291,22 +308,27 @@ void AudioSourceComponent::SetHRTFEnabled(bool enabled) { } void AudioSourceComponent::SetHRTFCrossFeed(float crossFeed) { + auto audioStateLock = LockAudioState(); m_hrtf.SetCrossFeed(crossFeed); } void AudioSourceComponent::SetHRTFQuality(Audio::uint32 level) { + auto audioStateLock = LockAudioState(); m_hrtf.SetQualityLevel(level); } void AudioSourceComponent::SetLooping(bool loop) { + auto audioStateLock = LockAudioState(); m_isLooping = loop; } void AudioSourceComponent::SetSpatialize(bool spatialize) { + auto audioStateLock = LockAudioState(); m_spatialize = spatialize; } void AudioSourceComponent::Set3DParams(const Audio::Audio3DParams& params) { + auto audioStateLock = LockAudioState(); m_3DParams = params; m_3DParams.dopplerLevel = std::max(0.0f, m_3DParams.dopplerLevel); m_3DParams.speedOfSound = std::max(1.0f, m_3DParams.speedOfSound); @@ -318,22 +340,27 @@ void AudioSourceComponent::Set3DParams(const Audio::Audio3DParams& params) { } void AudioSourceComponent::SetDopplerLevel(float level) { + auto audioStateLock = LockAudioState(); m_3DParams.dopplerLevel = std::max(0.0f, level); } void AudioSourceComponent::SetSpread(float spread) { + auto audioStateLock = LockAudioState(); m_3DParams.spread = std::max(0.0f, std::min(1.0f, spread)); } void AudioSourceComponent::SetReverbZoneMix(float mix) { + auto audioStateLock = LockAudioState(); m_3DParams.reverbZoneMix = std::max(0.0f, std::min(1.0f, mix)); } void AudioSourceComponent::SetOutputMixer(Audio::AudioMixer* mixer) { + auto audioStateLock = LockAudioState(); m_outputMixer = mixer; } void AudioSourceComponent::SetTime(float seconds) { + auto audioStateLock = LockAudioState(); if (!m_clip || !m_clip->IsValid()) { return; } @@ -370,15 +397,18 @@ float AudioSourceComponent::GetDuration() const { } void AudioSourceComponent::StartEnergyDetect() { + auto audioStateLock = LockAudioState(); m_isEnergyDetecting = true; m_energyHistory.clear(); } void AudioSourceComponent::StopEnergyDetect() { + auto audioStateLock = LockAudioState(); m_isEnergyDetecting = false; } void AudioSourceComponent::Update(float deltaTime) { + auto audioStateLock = LockAudioState(); if (m_gameObject) { const Math::Vector3 position = transform().GetPosition(); if (m_hasLastPosition && deltaTime > 0.0f) { @@ -403,12 +433,14 @@ void AudioSourceComponent::Update(float deltaTime) { } void AudioSourceComponent::OnEnable() { + auto audioStateLock = LockAudioState(); if (m_playState == Audio::PlayState::Playing) { Audio::AudioSystem::Get().RegisterSource(this); } } void AudioSourceComponent::OnDisable() { + auto audioStateLock = LockAudioState(); if (m_playState == Audio::PlayState::Playing) { Audio::AudioSystem::Get().UnregisterSource(this); } @@ -419,6 +451,7 @@ void AudioSourceComponent::OnDestroy() { } void AudioSourceComponent::Serialize(std::ostream& os) const { + auto audioStateLock = LockAudioState(); Resources::AssetRef serializedClipRef = m_clipRef; std::string serializedClipPath = m_clipPath; if (serializedClipPath.empty() && m_clip != nullptr) { @@ -458,6 +491,7 @@ void AudioSourceComponent::Serialize(std::ostream& os) const { } void AudioSourceComponent::Deserialize(std::istream& is) { + auto audioStateLock = LockAudioState(); ClearClip(); SetVolume(1.0f); SetPitch(1.0f); @@ -555,6 +589,7 @@ void AudioSourceComponent::ProcessAudio(float* buffer, Audio::uint32 frameCount, float listenerDopplerLevel, float speedOfSound, Audio::uint32 outputSampleRate) { + auto audioStateLock = LockAudioState(); if (m_playState != Audio::PlayState::Playing || !m_clip) { return; } diff --git a/tests/Components/test_audio_system.cpp b/tests/Components/test_audio_system.cpp index c15ee080..e0fcdf87 100644 --- a/tests/Components/test_audio_system.cpp +++ b/tests/Components/test_audio_system.cpp @@ -78,6 +78,60 @@ public: uint32 lastSampleRate = 0; }; +class PullCaptureBackend final : public IAudioBackend { +public: + explicit PullCaptureBackend(const AudioConfig& inConfig) : config(inConfig) {} + + bool Initialize(const AudioConfig& inConfig) override { + config = inConfig; + return true; + } + + void Shutdown() override {} + + std::string GetDeviceName() const override { return "PullCaptureBackend"; } + void GetAvailableDevices(std::vector& devices) override { + devices = {"PullCaptureBackend"}; + } + bool SetDevice(const std::string& deviceName) override { return deviceName == "PullCaptureBackend"; } + + void Start() override { running = true; } + void Stop() override { running = false; } + void Suspend() override {} + void Resume() override {} + + void ProcessAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) override { + (void)buffer; + (void)frameCount; + (void)channels; + (void)sampleRate; + submissionCount++; + } + + bool UsesPullModel() const override { return true; } + void SetRenderCallback(RenderCallback callback) override { renderCallback = callback; } + + bool IsRunning() const override { return running; } + AudioConfig GetConfig() const override { return config; } + + bool RenderOnce() { + if (!renderCallback) { + return false; + } + + const size_t sampleCount = static_cast(config.bufferSize) * config.channels; + captured.assign(sampleCount, 0.0f); + renderCallback(captured.data(), config.bufferSize, config.channels, config.sampleRate); + return true; + } + + AudioConfig config{}; + std::vector captured; + RenderCallback renderCallback; + bool running = true; + uint32 submissionCount = 0; +}; + TEST(AudioSystem, MasterMixerProcessesDirectSources) { AudioSystem& system = AudioSystem::Get(); system.Shutdown(); @@ -318,6 +372,40 @@ TEST(AudioSystem, RenderAudioProducesMixedBlockWithoutBackendSubmission) { system.Shutdown(); } +TEST(AudioSystem, PullBackendRendersViaCallbackWithoutPushSubmission) { + AudioSystem& system = AudioSystem::Get(); + system.Shutdown(); + + AudioConfig config; + config.sampleRate = 4; + config.channels = 1; + config.bufferSize = 1; + + auto backend = std::make_unique(config); + PullCaptureBackend* backendPtr = backend.get(); + system.SetBackend(std::move(backend)); + system.SetMasterVolume(0.5f); + system.SetMuted(false); + system.GetMasterMixer().ClearEffects(); + + AudioClip clip = CreateMono16Clip({16384}, 4); + AudioSourceComponent source; + source.SetSpatialize(false); + source.SetClip(&clip); + source.Play(); + + system.Update(0.0f); + + EXPECT_EQ(backendPtr->submissionCount, 0u); + ASSERT_TRUE(backendPtr->RenderOnce()); + ASSERT_EQ(backendPtr->captured.size(), 1u); + EXPECT_NEAR(backendPtr->captured[0], 0.25f, 1e-5f); + + source.Stop(); + system.SetMasterVolume(1.0f); + system.Shutdown(); +} + TEST(AudioSystem, AudioListenerComponentSerializeRoundTripPreservesSettings) { AudioSystem& system = AudioSystem::Get(); system.Shutdown(); diff --git a/tests/Components/test_windows_audio_backend.cpp b/tests/Components/test_windows_audio_backend.cpp index ff83e722..f8ca1008 100644 --- a/tests/Components/test_windows_audio_backend.cpp +++ b/tests/Components/test_windows_audio_backend.cpp @@ -23,23 +23,40 @@ namespace { #ifdef _WIN32 -TEST(WaveOutBackend, ProcessAudioCopiesOnlyRequestedSamplesIntoPendingMixBuffer) { +TEST(WaveOutBackend, RenderCallbackCanDrivePcm16ConversionForPullModel) { WaveOut::WaveOutBackend backend; - backend.m_pendingMixBuffer.assign(8, 1.0f); + backend.m_config.bufferSize = 2; + backend.m_config.channels = 2; + backend.m_config.sampleRate = 48000; - float input[] = {0.25f, -0.5f, 0.75f, -1.0f}; - backend.ProcessAudio(input, 2, 2, 48000); + bool callbackInvoked = false; + backend.SetRenderCallback( + [&callbackInvoked](float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) { + callbackInvoked = true; + EXPECT_EQ(frameCount, 2u); + EXPECT_EQ(channels, 2u); + EXPECT_EQ(sampleRate, 48000u); - ASSERT_EQ(backend.m_pendingMixBuffer.size(), 8u); - EXPECT_TRUE(backend.m_hasPendingMix); - EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[0], 0.25f); - EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[1], -0.5f); - EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[2], 0.75f); - EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[3], -1.0f); - EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[4], 0.0f); - EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[5], 0.0f); - EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[6], 0.0f); - EXPECT_FLOAT_EQ(backend.m_pendingMixBuffer[7], 0.0f); + buffer[0] = 0.25f; + buffer[1] = -0.5f; + buffer[2] = 0.75f; + buffer[3] = -1.0f; + }); + + backend.m_renderBuffer.assign(4, 0.0f); + ASSERT_TRUE(static_cast(backend.m_renderCallback)); + backend.m_renderCallback(backend.m_renderBuffer.data(), 2, 2, 48000); + + std::vector output(4, 0); + WaveOut::WaveOutBackend::FillPcm16Buffer(output, backend.m_renderBuffer); + + EXPECT_TRUE(backend.UsesPullModel()); + EXPECT_TRUE(callbackInvoked); + ASSERT_EQ(output.size(), 4u); + EXPECT_EQ(output[0], 8191); + EXPECT_EQ(output[1], -16383); + EXPECT_EQ(output[2], 24575); + EXPECT_EQ(output[3], -32767); } TEST(WaveOutBackend, SetDeviceAcceptsDefaultDeviceBeforeInitialize) {