Files
Destrum/destrum/src/Components/AudioSource.cpp
T

289 lines
7.7 KiB
C++

#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/Serialization/Reflect.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();
}
void AudioSource::Reflect(FieldVisitor& v)
{
v.Property("volume", m_Volume, [this](float volume) { SetVolume(volume); }, {.speed = 0.01f, .min = 0.0, .max = 2.0});
v.Property("pitch", m_Pitch, [this](float pitch) { SetPitch(pitch); }, {.speed = 0.01f, .min = 0.1, .max = 3.0});
v.Property("loop", m_Loop, [this](bool loop) { SetLoop(loop); });
v.Field("playOnStart", m_PlayOnStart);
v.Property("spatial", m_Spatial, [this](bool spatial) { SetSpatial(spatial); });
v.Property("minDistance", m_MinDistance, [this](float minDistance) { SetDistances(minDistance, m_MaxDistance); },
{.speed = 0.05f, .min = 0.01, .format = "%.2f m", .hidden = !m_Spatial, .tooltip = "Full volume up to this distance"});
v.Property("maxDistance", m_MaxDistance, [this](float maxDistance) { SetDistances(m_MinDistance, maxDistance); },
{.speed = 0.1f, .min = 0.01, .format = "%.2f m", .hidden = !m_Spatial,
.tooltip = "The sound stops getting quieter past this distance"});
}
nlohmann::json AudioSource::Serialize() const
{
nlohmann::json data = Component::Serialize();
data["clip"] = m_ClipPath.empty() ? nlohmann::json(nullptr) : AssetDatabase::GetInstance().WriteReference(m_ClipPath);
return data;
}
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({});
}
}
Component::Deserialize(data);
}
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());
}
Component::ImGuiInspector();
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();
}