Files
XCEngine/tests/Components/test_audio_system.cpp

336 lines
10 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include <XCEngine/Audio/AudioSystem.h>
#include <XCEngine/Audio/IAudioBackend.h>
#include <XCEngine/Audio/AudioMixer.h>
#include <XCEngine/Components/AudioListenerComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/AudioSourceComponent.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Resources/AudioClip/AudioClip.h>
#include <sstream>
using namespace XCEngine::Audio;
using namespace XCEngine::Components;
using namespace XCEngine::Resources;
namespace {
AudioClip CreateMono16Clip(std::initializer_list<int16_t> samples, XCEngine::Core::uint32 sampleRate = 4) {
AudioClip clip;
XCEngine::Containers::Array<XCEngine::Core::uint8> pcmData;
pcmData.ResizeUninitialized(samples.size() * sizeof(int16_t));
size_t byteOffset = 0;
for (const int16_t sample : samples) {
const uint16_t encoded = static_cast<uint16_t>(sample);
pcmData[byteOffset++] = static_cast<XCEngine::Core::uint8>(encoded & 0xFFu);
pcmData[byteOffset++] = static_cast<XCEngine::Core::uint8>((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<std::string>& 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<size_t>(frameCount) * channels);
}
bool IsRunning() const override { return running; }
AudioConfig GetConfig() const override { return config; }
AudioConfig config{};
std::vector<float> 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<CaptureBackend>(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<CaptureBackend>(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<CaptureBackend>(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<CaptureBackend>(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<AudioListenerComponent>();
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<CaptureBackend>(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, RenderAudioProducesMixedBlockWithoutBackendSubmission) {
AudioSystem& system = AudioSystem::Get();
system.Shutdown();
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();
float buffer[1] = {};
system.RenderAudio(buffer, 1, 1, 4);
EXPECT_NEAR(buffer[0], 0.25f, 1e-5f);
EXPECT_EQ(system.GetStats().activeSources, 1u);
EXPECT_EQ(system.GetStats().totalSources, 1u);
source.Stop();
system.SetMasterVolume(1.0f);
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