#include #include #include #include #include #include #include #include #include #include #include #include #include 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(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()); } 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(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(); }