// 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 = SafeCast(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); m_status = SoundStatus::Stopped; } 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; } }