feat: add audio system
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
#include <destrum/Components/AudioSource.h>
|
||||
|
||||
#include <destrum/Assets/AssetDatabase.h>
|
||||
#include <destrum/Assets/AssetTypes.h>
|
||||
#include <destrum/Audio/AudioSystem.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Util/ImGuiUtils.h>
|
||||
|
||||
#include <AL/al.h>
|
||||
#include <AL/alext.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] ALint SourceState(std::uint32_t source)
|
||||
{
|
||||
ALint state = AL_STOPPED;
|
||||
if (source != 0) {
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
AudioSource::AudioSource(GameObject& parent, std::string clipPath)
|
||||
: Component(parent, "AudioSource")
|
||||
, m_ClipPath(std::move(clipPath))
|
||||
{
|
||||
}
|
||||
|
||||
AudioSource::~AudioSource()
|
||||
{
|
||||
ReleaseSource();
|
||||
}
|
||||
|
||||
void AudioSource::Update(float)
|
||||
{
|
||||
if (m_AutoPlayed) {
|
||||
return;
|
||||
}
|
||||
m_AutoPlayed = true;
|
||||
if (m_PlayOnStart) {
|
||||
Play();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSource::LateUpdate(float dt)
|
||||
{
|
||||
SyncPosition(dt);
|
||||
}
|
||||
|
||||
void AudioSource::SetEnabled(bool enabled)
|
||||
{
|
||||
if (!enabled) {
|
||||
Stop();
|
||||
}
|
||||
Component::SetEnabled(enabled);
|
||||
}
|
||||
|
||||
void AudioSource::Play()
|
||||
{
|
||||
if (!EnsureSource()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_Clip || m_Clip->GetPath() != m_ClipPath) {
|
||||
alSourceStop(m_Source);
|
||||
alSourcei(m_Source, AL_BUFFER, 0);
|
||||
m_Clip = AudioSystem::GetInstance().LoadClip(m_ClipPath);
|
||||
if (!m_Clip) {
|
||||
return;
|
||||
}
|
||||
alSourcei(m_Source, AL_BUFFER, static_cast<ALint>(m_Clip->GetBuffer()));
|
||||
}
|
||||
|
||||
ApplyProperties();
|
||||
m_HasLastPosition = false;
|
||||
SyncPosition(0.0f);
|
||||
alSourcePlay(m_Source);
|
||||
}
|
||||
|
||||
void AudioSource::Pause()
|
||||
{
|
||||
if (SourceState(m_Source) == AL_PLAYING) {
|
||||
alSourcePause(m_Source);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSource::Stop()
|
||||
{
|
||||
if (m_Source != 0) {
|
||||
alSourceStop(m_Source);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioSource::IsPlaying() const
|
||||
{
|
||||
return SourceState(m_Source) == AL_PLAYING;
|
||||
}
|
||||
|
||||
void AudioSource::PlayOneShot(const std::string& clipPath, float volumeScale) const
|
||||
{
|
||||
AudioSystem::GetInstance().PlayOneShot(clipPath, GetTransform().GetWorldPosition(), {
|
||||
.volume = m_Volume * volumeScale,
|
||||
.pitch = m_Pitch,
|
||||
.spatial = m_Spatial,
|
||||
.minDistance = m_MinDistance,
|
||||
.maxDistance = m_MaxDistance,
|
||||
});
|
||||
}
|
||||
|
||||
void AudioSource::SetClip(const std::string& clipPath)
|
||||
{
|
||||
if (clipPath == m_ClipPath) {
|
||||
return;
|
||||
}
|
||||
m_ClipPath = clipPath;
|
||||
if (m_Source != 0) {
|
||||
alSourceStop(m_Source);
|
||||
alSourcei(m_Source, AL_BUFFER, 0);
|
||||
}
|
||||
m_Clip.reset();
|
||||
}
|
||||
|
||||
void AudioSource::SetVolume(float volume)
|
||||
{
|
||||
m_Volume = std::max(volume, 0.0f);
|
||||
ApplyProperties();
|
||||
}
|
||||
|
||||
void AudioSource::SetPitch(float pitch)
|
||||
{
|
||||
m_Pitch = std::max(pitch, 0.01f);
|
||||
ApplyProperties();
|
||||
}
|
||||
|
||||
void AudioSource::SetLoop(bool loop)
|
||||
{
|
||||
m_Loop = loop;
|
||||
ApplyProperties();
|
||||
}
|
||||
|
||||
void AudioSource::SetSpatial(bool spatial)
|
||||
{
|
||||
m_Spatial = spatial;
|
||||
ApplyProperties();
|
||||
SyncPosition(0.0f);
|
||||
}
|
||||
|
||||
void AudioSource::SetDistances(float minDistance, float maxDistance)
|
||||
{
|
||||
m_MinDistance = std::max(minDistance, 0.01f);
|
||||
m_MaxDistance = std::max(maxDistance, m_MinDistance);
|
||||
ApplyProperties();
|
||||
}
|
||||
|
||||
nlohmann::json AudioSource::Serialize() const
|
||||
{
|
||||
return {
|
||||
{"clip", m_ClipPath.empty() ? nlohmann::json(nullptr) : AssetDatabase::GetInstance().WriteReference(m_ClipPath)},
|
||||
{"volume", m_Volume},
|
||||
{"pitch", m_Pitch},
|
||||
{"loop", m_Loop},
|
||||
{"playOnStart", m_PlayOnStart},
|
||||
{"spatial", m_Spatial},
|
||||
{"minDistance", m_MinDistance},
|
||||
{"maxDistance", m_MaxDistance},
|
||||
};
|
||||
}
|
||||
|
||||
void AudioSource::Deserialize(const nlohmann::json& data)
|
||||
{
|
||||
if (const auto clip = data.find("clip"); clip != data.end()) {
|
||||
if (clip->is_string()) {
|
||||
SetClip(clip->get<std::string>());
|
||||
} else if (const auto reference = AssetDatabase::GetInstance().ReadReference(*clip)) {
|
||||
SetClip(reference->path);
|
||||
} else {
|
||||
SetClip({});
|
||||
}
|
||||
}
|
||||
|
||||
m_Loop = data.value("loop", m_Loop);
|
||||
m_PlayOnStart = data.value("playOnStart", m_PlayOnStart);
|
||||
m_Spatial = data.value("spatial", m_Spatial);
|
||||
SetVolume(data.value("volume", m_Volume));
|
||||
SetPitch(data.value("pitch", m_Pitch));
|
||||
SetDistances(data.value("minDistance", m_MinDistance), data.value("maxDistance", m_MaxDistance));
|
||||
}
|
||||
|
||||
void AudioSource::ImGuiInspector()
|
||||
{
|
||||
if (!AudioSystem::GetInstance().IsAvailable()) {
|
||||
ImGui::TextDisabled("No audio device, sound is disabled");
|
||||
}
|
||||
|
||||
std::string clipPath = m_ClipPath;
|
||||
if (ImGuiUtils::InputString("Clip", clipPath, 0, "game://sounds/clip.ogg")) {
|
||||
SetClip(clipPath);
|
||||
}
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(ImGuiUtils::AssetPayload)) {
|
||||
const std::string dropped = static_cast<const char*>(payload->Data);
|
||||
if (AssetTypes::IsAudio(dropped)) {
|
||||
SetClip(dropped);
|
||||
}
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
ImGui::SetItemTooltip("Drop a .wav or .ogg from the Assets window");
|
||||
|
||||
if (m_Clip && m_Clip->GetPath() == m_ClipPath) {
|
||||
ImGui::TextDisabled("%s, %d Hz, %.2f s", m_Clip->GetChannels() == 1 ? "Mono" : "Stereo",
|
||||
m_Clip->GetSampleRate(), m_Clip->GetDuration());
|
||||
}
|
||||
|
||||
float volume = m_Volume;
|
||||
if (ImGui::SliderFloat("Volume", &volume, 0.0f, 2.0f)) {
|
||||
SetVolume(volume);
|
||||
}
|
||||
float pitch = m_Pitch;
|
||||
if (ImGui::SliderFloat("Pitch", &pitch, 0.1f, 3.0f)) {
|
||||
SetPitch(pitch);
|
||||
}
|
||||
|
||||
bool loop = m_Loop;
|
||||
if (ImGui::Checkbox("Loop", &loop)) {
|
||||
SetLoop(loop);
|
||||
}
|
||||
ImGui::Checkbox("Play On Start", &m_PlayOnStart);
|
||||
bool spatial = m_Spatial;
|
||||
if (ImGui::Checkbox("Spatial", &spatial)) {
|
||||
SetSpatial(spatial);
|
||||
}
|
||||
|
||||
if (m_Spatial) {
|
||||
float minDistance = m_MinDistance;
|
||||
float maxDistance = m_MaxDistance;
|
||||
bool changed = ImGui::DragFloat("Min Distance", &minDistance, 0.05f, 0.01f, 1000.0f, "%.2f m");
|
||||
ImGui::SetItemTooltip("Full volume up to this distance");
|
||||
changed |= ImGui::DragFloat("Max Distance", &maxDistance, 0.1f, 0.01f, 10000.0f, "%.2f m");
|
||||
ImGui::SetItemTooltip("The sound stops getting quieter past this distance");
|
||||
if (changed) {
|
||||
SetDistances(minDistance, maxDistance);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(m_ClipPath.empty());
|
||||
if (ImGui::Button(IsPlaying() ? "Restart" : "Play")) {
|
||||
Stop();
|
||||
Play();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Stop")) {
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioSource::EnsureSource()
|
||||
{
|
||||
if (m_Source == 0) {
|
||||
m_Source = AudioSystem::GetInstance().CreateSource();
|
||||
}
|
||||
return m_Source != 0;
|
||||
}
|
||||
|
||||
void AudioSource::ApplyProperties()
|
||||
{
|
||||
if (m_Source == 0) {
|
||||
return;
|
||||
}
|
||||
alSourcef(m_Source, AL_GAIN, m_Volume);
|
||||
alSourcef(m_Source, AL_PITCH, m_Pitch);
|
||||
alSourcei(m_Source, AL_LOOPING, m_Loop ? AL_TRUE : AL_FALSE);
|
||||
alSourcei(m_Source, AL_SOURCE_RELATIVE, m_Spatial ? AL_FALSE : AL_TRUE);
|
||||
alSourcei(m_Source, AL_SOURCE_SPATIALIZE_SOFT, m_Spatial ? AL_TRUE : AL_FALSE);
|
||||
alSourcef(m_Source, AL_ROLLOFF_FACTOR, m_Spatial ? 1.0f : 0.0f);
|
||||
alSourcef(m_Source, AL_REFERENCE_DISTANCE, m_MinDistance);
|
||||
alSourcef(m_Source, AL_MAX_DISTANCE, m_MaxDistance);
|
||||
}
|
||||
|
||||
void AudioSource::SyncPosition(float dt)
|
||||
{
|
||||
if (m_Source == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_Spatial) {
|
||||
const glm::vec3 zero{0.0f};
|
||||
alSourcefv(m_Source, AL_POSITION, glm::value_ptr(zero));
|
||||
alSourcefv(m_Source, AL_VELOCITY, glm::value_ptr(zero));
|
||||
m_HasLastPosition = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const glm::vec3 position = GetTransform().GetWorldPosition();
|
||||
m_Velocity = m_HasLastPosition ? SmoothAudioVelocity(position, m_LastPosition, m_Velocity, dt) : glm::vec3{0.0f};
|
||||
m_LastPosition = position;
|
||||
m_HasLastPosition = true;
|
||||
|
||||
alSourcefv(m_Source, AL_POSITION, glm::value_ptr(position));
|
||||
alSourcefv(m_Source, AL_VELOCITY, glm::value_ptr(m_Velocity));
|
||||
}
|
||||
|
||||
void AudioSource::ReleaseSource()
|
||||
{
|
||||
AudioSystem::GetInstance().DestroySource(m_Source);
|
||||
m_Source = 0;
|
||||
m_Clip.reset();
|
||||
}
|
||||
Reference in New Issue
Block a user