From 21a38fb31bd66d69dba1c1011e14f5ce9f32b84f Mon Sep 17 00:00:00 2001 From: SirLynix Date: Fri, 6 May 2022 12:41:02 +0200 Subject: [PATCH] Audio: Add some fixes and tests --- src/Nazara/Audio/DummyAudioSource.cpp | 27 ++++++++++++++---- src/Nazara/Audio/Music.cpp | 26 +++++++++++++++-- tests/Engine/Audio/MusicTest.cpp | 41 +++++++++++++++++++++++++++ tests/Engine/Audio/SoundTest.cpp | 41 +++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 8 deletions(-) diff --git a/src/Nazara/Audio/DummyAudioSource.cpp b/src/Nazara/Audio/DummyAudioSource.cpp index e3578fcb1..a6269fb60 100644 --- a/src/Nazara/Audio/DummyAudioSource.cpp +++ b/src/Nazara/Audio/DummyAudioSource.cpp @@ -46,6 +46,9 @@ namespace Nz UInt32 DummyAudioSource::GetSampleOffset() const { + if (m_status == SoundStatus::Stopped) + return 0; //< Always return 0 when stopped, to mimic OpenAL behavior + UInt64 bufferTime = UpdateTime(); UInt64 sampleOffset = 0; @@ -114,7 +117,12 @@ namespace Nz { // playing or stopped, restart RequeueBuffers(); - m_playClock.Restart(); + + // special case, we are stopped but SetSampleOffset has been called + if (m_status == SoundStatus::Stopped && m_playClock.GetMilliseconds() != 0) + m_playClock.Unpause(); + else + m_playClock.Restart(); //< already playing or stopped, restart from beginning } m_status = SoundStatus::Playing; @@ -168,10 +176,13 @@ namespace Nz } 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()); + if (!m_queuedBuffers.empty()) + { + UInt64 timeOffset = 1'000'000ULL * offset / m_queuedBuffers.front()->GetSampleRate(); + m_playClock.Restart(timeOffset, m_playClock.IsPaused()); + } + else + Stop(); } void DummyAudioSource::SetVelocity(const Vector3f& velocity) @@ -216,7 +227,11 @@ namespace Nz 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()); + + // Move currently queued buffers to the end of the queue + if (m_queuedBuffers.size() > m_processedBuffers.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(); } diff --git a/src/Nazara/Audio/Music.cpp b/src/Nazara/Audio/Music.cpp index 566a778d3..5742bc7a4 100644 --- a/src/Nazara/Audio/Music.cpp +++ b/src/Nazara/Audio/Music.cpp @@ -90,6 +90,8 @@ namespace Nz */ void Music::EnableLooping(bool loop) { + std::lock_guard lock(m_bufferLock); + m_looping = loop; } @@ -129,11 +131,24 @@ namespace Nz { NazaraAssert(m_stream, "Music not created"); + if (!m_streaming) + return 0; + // Prevent music thread from enqueuing new buffers while we're getting the count std::lock_guard lock(m_bufferLock); UInt32 sampleOffset = m_source->GetSampleOffset(); - return static_cast((1000ULL * (sampleOffset + (m_processedSamples / GetChannelCount(m_stream->GetFormat())))) / m_sampleRate); + UInt32 playingOffset = SafeCast((1000ULL * (sampleOffset + (m_processedSamples / GetChannelCount(m_stream->GetFormat())))) / m_sampleRate); + UInt32 duration = m_stream->GetDuration(); + if (playingOffset > duration) + { + if (m_looping) + playingOffset %= duration; + else + playingOffset = 0; //< stopped + } + + return playingOffset; } /*! @@ -191,6 +206,8 @@ namespace Nz */ bool Music::IsLooping() const { + std::lock_guard lock(m_bufferLock); + return m_looping; } @@ -290,7 +307,12 @@ namespace Nz else { // Ensure we're restarting - Stop(); + StopThread(); + + // Special case of SetPlayingOffset(end) before Play(), restart from beginning + if (m_streamOffset >= m_stream->GetSampleCount()) + m_streamOffset = 0; + StartThread(false); } } diff --git a/tests/Engine/Audio/MusicTest.cpp b/tests/Engine/Audio/MusicTest.cpp index b52beb039..30619fdc0 100644 --- a/tests/Engine/Audio/MusicTest.cpp +++ b/tests/Engine/Audio/MusicTest.cpp @@ -53,12 +53,53 @@ SCENARIO("Music", "[AUDIO][MUSIC]") CHECK(music.GetStatus() == Nz::SoundStatus::Playing); music.Pause(); + Nz::UInt32 playingOffset = music.GetPlayingOffset(); CHECK(music.GetStatus() == Nz::SoundStatus::Paused); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + CHECK(music.GetStatus() == Nz::SoundStatus::Paused); + CHECK(music.GetPlayingOffset() == playingOffset); music.SetPlayingOffset(3500); std::this_thread::sleep_for(std::chrono::milliseconds(200)); CHECK(music.GetPlayingOffset() == 3500); + music.Play(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + CHECK(music.GetPlayingOffset() >= 3650); + + AND_WHEN("We let the sound stop by itself") + { + REQUIRE(music.GetDuration() == 63059); + + music.SetPlayingOffset(62900); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + CHECK(music.GetStatus() == Nz::SoundStatus::Stopped); + CHECK(music.GetPlayingOffset() == 0); + + music.SetPlayingOffset(64000); + music.Play(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + CHECK(music.GetStatus() == Nz::SoundStatus::Playing); + CHECK(music.GetPlayingOffset() < 100); + + music.Stop(); + music.SetPlayingOffset(62900); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + CHECK(music.GetStatus() == Nz::SoundStatus::Stopped); + CHECK(music.GetPlayingOffset() == 0); //< playing offset has no effect until Play() + + AND_WHEN("We enable looping") + { + music.EnableLooping(true); + CHECK(music.IsLooping()); + music.Play(); + CHECK(music.GetStatus() == Nz::SoundStatus::Playing); + CHECK(music.GetPlayingOffset() >= 62900); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + CHECK(music.GetStatus() == Nz::SoundStatus::Playing); + CHECK(music.GetPlayingOffset() < 300); + } + } Nz::Audio::Instance()->GetDefaultDevice()->SetGlobalVolume(100.f); } } diff --git a/tests/Engine/Audio/SoundTest.cpp b/tests/Engine/Audio/SoundTest.cpp index ff71f57c0..99e29a93d 100644 --- a/tests/Engine/Audio/SoundTest.cpp +++ b/tests/Engine/Audio/SoundTest.cpp @@ -41,11 +41,52 @@ SCENARIO("Sound", "[AUDIO][SOUND]") std::this_thread::sleep_for(std::chrono::milliseconds(200)); CHECK(sound.GetPlayingOffset() <= 1500); sound.Pause(); + Nz::UInt32 playingOffset = sound.GetPlayingOffset(); CHECK(sound.GetStatus() == Nz::SoundStatus::Paused); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + CHECK(sound.GetStatus() == Nz::SoundStatus::Paused); + CHECK(sound.GetPlayingOffset() == playingOffset); sound.SetPlayingOffset(3500); CHECK(sound.GetPlayingOffset() == 3500); + sound.Play(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + CHECK(sound.GetPlayingOffset() >= 1650); + + AND_WHEN("We let the sound stop by itself") + { + REQUIRE(sound.GetDuration() == 8192); + + sound.SetPlayingOffset(8000); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + CHECK(sound.GetStatus() == Nz::SoundStatus::Stopped); + CHECK(sound.GetPlayingOffset() == 0); + + sound.SetPlayingOffset(9000); + sound.Play(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + CHECK(sound.GetStatus() == Nz::SoundStatus::Playing); + + sound.Stop(); + sound.SetPlayingOffset(8000); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + CHECK(sound.GetStatus() == Nz::SoundStatus::Stopped); + CHECK(sound.GetPlayingOffset() == 0); //< playing offset has no effect until Play() + + AND_WHEN("We enable looping") + { + sound.EnableLooping(true); + CHECK(sound.IsLooping()); + sound.Play(); + CHECK(sound.GetStatus() == Nz::SoundStatus::Playing); + CHECK(sound.GetPlayingOffset() >= 8000); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + CHECK(sound.GetStatus() == Nz::SoundStatus::Playing); + CHECK(sound.GetPlayingOffset() < 300); + } + } + Nz::Audio::Instance()->GetDefaultDevice()->SetGlobalVolume(100.f); } }