#include #include #include #include #include #include #include #include #include #include #include using namespace XCEngine::Audio; using namespace XCEngine::Components; using namespace XCEngine::Resources; namespace { AudioClip CreateMono16Clip(std::initializer_list samples, XCEngine::Core::uint32 sampleRate = 4) { AudioClip clip; XCEngine::Containers::Array pcmData; pcmData.ResizeUninitialized(samples.size() * sizeof(int16_t)); size_t byteOffset = 0; for (const int16_t sample : samples) { const uint16_t encoded = static_cast(sample); pcmData[byteOffset++] = static_cast(encoded & 0xFFu); pcmData[byteOffset++] = static_cast((encoded >> 8) & 0xFFu); } clip.SetSampleRate(sampleRate); clip.SetChannels(1); clip.SetBitsPerSample(16); clip.SetAudioFormat(XCEngine::Resources::AudioFormat::WAV); clip.SetPCMData(pcmData); clip.m_isValid = true; return clip; } class CaptureBackend final : public IAudioBackend { public: explicit CaptureBackend(const AudioConfig& inConfig) : config(inConfig) {} bool Initialize(const AudioConfig& inConfig) override { config = inConfig; return true; } void Shutdown() override {} std::string GetDeviceName() const override { return "CaptureBackend"; } void GetAvailableDevices(std::vector& devices) override { devices = {"CaptureBackend"}; } bool SetDevice(const std::string& deviceName) override { return deviceName == "CaptureBackend"; } 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 { lastFrameCount = frameCount; lastChannels = channels; lastSampleRate = sampleRate; captured.assign(buffer, buffer + static_cast(frameCount) * channels); } bool IsRunning() const override { return running; } AudioConfig GetConfig() const override { return config; } AudioConfig config{}; std::vector captured; bool running = true; uint32 lastFrameCount = 0; uint32 lastChannels = 0; uint32 lastSampleRate = 0; }; TEST(AudioSystem, MasterMixerProcessesDirectSources) { AudioSystem& system = AudioSystem::Get(); system.Shutdown(); AudioConfig config; config.sampleRate = 4; config.channels = 1; config.bufferSize = 1; auto backend = std::make_unique(config); CaptureBackend* backendPtr = backend.get(); system.SetBackend(std::move(backend)); system.GetMasterMixer().SetVolume(0.5f); system.GetMasterMixer().SetMute(false); system.GetMasterMixer().ClearEffects(); AudioClip clip = CreateMono16Clip({16384}, 4); AudioSourceComponent source; source.SetSpatialize(false); source.SetClip(&clip); source.Play(); system.Update(0.0f); ASSERT_EQ(backendPtr->captured.size(), 1u); EXPECT_NEAR(backendPtr->captured[0], 0.25f, 1e-5f); source.Stop(); system.GetMasterMixer().SetVolume(1.0f); system.Shutdown(); } TEST(AudioSystem, SourceOutputMixerRoutesIntoBackend) { AudioSystem& system = AudioSystem::Get(); system.Shutdown(); AudioConfig config; config.sampleRate = 4; config.channels = 1; config.bufferSize = 1; auto backend = std::make_unique(config); CaptureBackend* backendPtr = backend.get(); system.SetBackend(std::move(backend)); system.GetMasterMixer().SetVolume(1.0f); system.GetMasterMixer().SetMute(false); system.GetMasterMixer().ClearEffects(); AudioMixer mixer; mixer.SetVolume(0.5f); AudioClip clip = CreateMono16Clip({16384}, 4); AudioSourceComponent source; source.SetSpatialize(false); source.SetOutputMixer(&mixer); source.SetClip(&clip); source.Play(); system.Update(0.0f); ASSERT_EQ(backendPtr->captured.size(), 1u); EXPECT_NEAR(backendPtr->captured[0], 0.25f, 1e-5f); source.Stop(); system.Shutdown(); } TEST(AudioSystem, SetMasterVolumeControlsMasterMixerOutput) { AudioSystem& system = AudioSystem::Get(); system.Shutdown(); AudioConfig config; config.sampleRate = 4; config.channels = 1; config.bufferSize = 1; auto backend = std::make_unique(config); CaptureBackend* backendPtr = backend.get(); system.SetBackend(std::move(backend)); system.SetMasterVolume(0.25f); 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); ASSERT_EQ(backendPtr->captured.size(), 1u); EXPECT_NEAR(backendPtr->captured[0], 0.125f, 1e-5f); EXPECT_FLOAT_EQ(system.GetMasterVolume(), 0.25f); EXPECT_FALSE(system.IsMuted()); source.Stop(); system.SetMasterVolume(1.0f); system.Shutdown(); } TEST(AudioSystem, ListenerReverbMixerReceivesSend) { AudioSystem& system = AudioSystem::Get(); system.Shutdown(); AudioConfig config; config.sampleRate = 4; config.channels = 1; config.bufferSize = 1; auto backend = std::make_unique(config); CaptureBackend* backendPtr = backend.get(); system.SetBackend(std::move(backend)); system.SetMasterVolume(1.0f); system.SetMuted(false); system.GetMasterMixer().ClearEffects(); AudioMixer reverbMixer; reverbMixer.SetVolume(1.0f); system.SetListenerReverbMixer(&reverbMixer); system.SetListenerReverbLevel(0.5f); AudioClip clip = CreateMono16Clip({16384}, 4); AudioSourceComponent source; source.SetSpatialize(false); source.SetReverbZoneMix(0.5f); source.SetClip(&clip); source.Play(); system.Update(0.0f); ASSERT_EQ(backendPtr->captured.size(), 1u); EXPECT_NEAR(backendPtr->captured[0], 0.625f, 1e-5f); source.Stop(); system.SetListenerReverbMixer(nullptr); system.SetListenerReverbLevel(1.0f); system.Shutdown(); } TEST(AudioSystem, ListenerComponentPublishesVelocityAndDopplerSettings) { AudioSystem& system = AudioSystem::Get(); system.Shutdown(); GameObject listenerObject("Listener"); auto* listener = listenerObject.AddComponent(); listener->SetDopplerLevel(2.0f); listener->SetSpeedOfSound(200.0f); listenerObject.GetTransform()->SetPosition(XCEngine::Math::Vector3::Zero()); listener->Update(0.0f); listenerObject.GetTransform()->SetPosition(XCEngine::Math::Vector3(10.0f, 0.0f, 0.0f)); listener->Update(0.5f); EXPECT_FLOAT_EQ(system.GetListenerDopplerLevel(), 2.0f); EXPECT_FLOAT_EQ(system.GetSpeedOfSound(), 200.0f); EXPECT_EQ(system.GetListenerPosition(), XCEngine::Math::Vector3(10.0f, 0.0f, 0.0f)); EXPECT_EQ(system.GetListenerVelocity(), XCEngine::Math::Vector3(20.0f, 0.0f, 0.0f)); EXPECT_FLOAT_EQ(system.GetListenerRotation().x, 0.0f); EXPECT_FLOAT_EQ(system.GetListenerRotation().y, 0.0f); EXPECT_FLOAT_EQ(system.GetListenerRotation().z, 0.0f); EXPECT_FLOAT_EQ(system.GetListenerRotation().w, 1.0f); system.Shutdown(); } TEST(AudioSystem, ProcessAudioUsesBackendConfigSampleRateForDirectSubmission) { AudioSystem& system = AudioSystem::Get(); system.Shutdown(); AudioConfig config; config.sampleRate = 22050; config.channels = 1; config.bufferSize = 1; auto backend = std::make_unique(config); CaptureBackend* backendPtr = backend.get(); system.SetBackend(std::move(backend)); float buffer[1] = {0.5f}; system.ProcessAudio(buffer, 1, 1); ASSERT_EQ(backendPtr->captured.size(), 1u); EXPECT_FLOAT_EQ(backendPtr->captured[0], 0.5f); EXPECT_EQ(backendPtr->lastSampleRate, 22050u); system.Shutdown(); } TEST(AudioSystem, AudioListenerComponentSerializeRoundTripPreservesSettings) { AudioSystem& system = AudioSystem::Get(); system.Shutdown(); AudioListenerComponent source; source.SetMasterVolume(0.6f); source.SetMute(true); source.SetDopplerLevel(2.5f); source.SetSpeedOfSound(250.0f); source.SetReverbLevel(0.35f); std::stringstream stream; source.Serialize(stream); AudioListenerComponent target; target.Deserialize(stream); EXPECT_FLOAT_EQ(target.GetMasterVolume(), 0.6f); EXPECT_TRUE(target.IsMute()); EXPECT_FLOAT_EQ(target.GetDopplerLevel(), 2.5f); EXPECT_FLOAT_EQ(target.GetSpeedOfSound(), 250.0f); EXPECT_FLOAT_EQ(target.GetReverbLevel(), 0.35f); EXPECT_FLOAT_EQ(system.GetMasterVolume(), 0.6f); EXPECT_TRUE(system.IsMuted()); EXPECT_FLOAT_EQ(system.GetListenerDopplerLevel(), 2.5f); EXPECT_FLOAT_EQ(system.GetSpeedOfSound(), 250.0f); EXPECT_FLOAT_EQ(system.GetListenerReverbLevel(), 0.35f); system.SetMasterVolume(1.0f); system.SetMuted(false); system.SetListenerDopplerLevel(1.0f); system.SetSpeedOfSound(343.0f); system.SetListenerReverbLevel(1.0f); system.Shutdown(); } } // namespace