feat(audio): add footstep sounds and fix audio listener orientation

This commit is contained in:
2026-07-07 11:20:17 +08:00
parent efd7af2c62
commit 68ddd78a6f
19 changed files with 137 additions and 27 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -26,8 +26,10 @@ public:
void init(); void init();
void play_bgm(); void play_bgm();
void play_3d(const std::string& sound, const glm::vec3& pos); void play_3d(const std::string& sound, const glm::vec3& pos,
void update_listener(glm::vec3 listener_pos); bool check = false);
void update_listener(const glm::vec3& pos, const glm::vec3& forward,
const glm::vec3& up);
void update(float dt); void update(float dt);
private: private:

View File

@@ -0,0 +1,15 @@
#pragma once
#include "Cubed/tools/log.hpp"
#include <AL/al.h>
namespace Cubed {
inline void check_al_error(
std::source_location loaction = std::source_location::current()) {
ALenum error = alGetError();
if (error != AL_NO_ERROR) {
Logger::error("File {} Line {} Function {} OpenAL Error {} ",
loaction.file_name(), loaction.line(),
loaction.function_name(), alGetString(error));
}
}
} // namespace Cubed

View File

@@ -123,6 +123,8 @@ private:
float m_angle{0.0f}; float m_angle{0.0f};
float m_walk_time{0.0f}; float m_walk_time{0.0f};
float m_footstep_timer = 0.0f;
mutable std::shared_mutex m_player_pos_mutex; mutable std::shared_mutex m_player_pos_mutex;
mutable std::shared_mutex m_chunk_pos_mutex; mutable std::shared_mutex m_chunk_pos_mutex;
ChunkPosSet m_player_chunk_pos_set; ChunkPosSet m_player_chunk_pos_set;
@@ -134,6 +136,7 @@ private:
void update_y_move(glm::vec3& player_pos); void update_y_move(glm::vec3& player_pos);
void update_z_move(glm::vec3& player_pos); void update_z_move(glm::vec3& player_pos);
void update_player_chunk(); void update_player_chunk();
void play_walk_sound(float dt);
Gait compute_gait() const; Gait compute_gait() const;
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -90,7 +90,7 @@ public:
bool is_receive_exit(); bool is_receive_exit();
int chunk_size() const; int chunk_size() const;
static AABB get_block_aabb(const glm::ivec3& pos); static AABB get_block_aabb(const glm::ivec3& pos);
AudioEngine& get_audio();
template <typename Fn> template <typename Fn>
void register_timer(std::string_view id, TickType threshold, Fn&& f) { void register_timer(std::string_view id, TickType threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct, m_timers.emplace(std::piecewise_construct,

View File

@@ -359,7 +359,8 @@ void App::update() {
m_renderer.update_fov(fov + 5.0f); m_renderer.update_fov(fov + 5.0f);
} }
} }
m_audio.update_listener(m_camera.get_camera_pos()); m_audio.update_listener(m_camera.get_camera_pos(),
m_camera.get_camera_front(), glm::vec3(0, 1, 0));
m_audio.update(dt); m_audio.update(dt);
m_renderer.update(dt); m_renderer.update(dt);
} }

View File

@@ -1,5 +1,6 @@
#include "Cubed/audio/audio_buffer.hpp" #include "Cubed/audio/audio_buffer.hpp"
#include "Cubed/audio/audio_error.hpp"
namespace Cubed { namespace Cubed {
AudioBuffer::AudioBuffer(const AudioData& data) { AudioBuffer::AudioBuffer(const AudioData& data) {
alGenBuffers(1, &m_buffer); alGenBuffers(1, &m_buffer);
@@ -12,6 +13,9 @@ float AudioBuffer::duration() const { return m_duration; }
uint32_t AudioBuffer::channels() const { return m_channels; } uint32_t AudioBuffer::channels() const { return m_channels; }
void AudioBuffer::set_data(const AudioData& data) { void AudioBuffer::set_data(const AudioData& data) {
Logger::info("buffer={} valid={}", m_buffer, (int)alIsBuffer(m_buffer));
Logger::info("buffer={} isBuffer={}", m_buffer, (int)alIsBuffer(m_buffer));
ALenum format; ALenum format;
if (data.channels == 1) { if (data.channels == 1) {
format = AL_FORMAT_MONO16; format = AL_FORMAT_MONO16;
@@ -19,8 +23,19 @@ void AudioBuffer::set_data(const AudioData& data) {
format = AL_FORMAT_STEREO16; format = AL_FORMAT_STEREO16;
} }
m_channels = data.channels; m_channels = data.channels;
alBufferData(m_buffer, format, data.pcm.data(), data.pcm.size(),
data.sample_rate); Logger::info("channels={} rate={} samples={} bytes={} format={}",
data.channels, data.sample_rate, data.pcm.size(),
data.pcm.size() * sizeof(int16_t), format);
alBufferData(m_buffer, format, data.pcm.data(),
static_cast<ALsizei>(data.pcm.size() * sizeof(int16_t)),
static_cast<ALsizei>(data.sample_rate));
check_al_error();
ALint size = 0;
alGetBufferi(m_buffer, AL_SIZE, &size);
check_al_error();
Logger::info("buffer size={}", size);
m_duration = static_cast<float>(data.pcm.size()) / m_duration = static_cast<float>(data.pcm.size()) /
(data.channels * data.sample_rate); (data.channels * data.sample_rate);
} }

View File

@@ -1,5 +1,6 @@
#include "Cubed/audio/audio_engine.hpp" #include "Cubed/audio/audio_engine.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp" #include "Cubed/tools/log.hpp"
#include <stdexcept> #include <stdexcept>
@@ -58,7 +59,8 @@ void AudioEngine::init() {
void AudioEngine::play_bgm() { m_bgm->play(); } void AudioEngine::play_bgm() { m_bgm->play(); }
void AudioEngine::play_3d(const std::string& sound, const glm::vec3& pos) { void AudioEngine::play_3d(const std::string& sound, const glm::vec3& pos,
bool check) {
if (!m_pool) { if (!m_pool) {
Logger::error("Source Pool is nullptr"); Logger::error("Source Pool is nullptr");
return; return;
@@ -67,11 +69,28 @@ void AudioEngine::play_3d(const std::string& sound, const glm::vec3& pos) {
if (!source) { if (!source) {
Logger::error("Source is Full"); Logger::error("Source is Full");
} }
auto& buffer = m_sounds.get_buffer(sound);
source->play_3d(buffer, pos); try {
auto& buffer = m_sounds.get_buffer(sound);
source->play_3d(buffer, pos);
} catch (const std::exception& e) {
if (check) {
ASSERT_MSG(false, e.what());
}
}
} }
void AudioEngine::update_listener(glm::vec3 listener_pos) {} void AudioEngine::update_listener(const glm::vec3& pos,
const glm::vec3& forward,
const glm::vec3& up) {
alListener3f(AL_POSITION, pos.x, pos.y, pos.z);
float orientation[] = {forward.x, forward.y, forward.z,
up.x, up.y, up.z};
alListenerfv(AL_ORIENTATION, orientation);
}
void AudioEngine::update(float dt) { void AudioEngine::update(float dt) {
for (auto& [key, fade] : m_fade_map) { for (auto& [key, fade] : m_fade_map) {

View File

@@ -1,6 +1,6 @@
#include "Cubed/audio/audio_loader.hpp" #include "Cubed/audio/audio_loader.hpp"
#include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/log.hpp"
#define STB_VORBIS_IMPLEMENTATION #define STB_VORBIS_IMPLEMENTATION
#include "stb/stb_vorbis.h" #include "stb/stb_vorbis.h"
@@ -14,7 +14,7 @@ namespace Cubed {
AudioData AudioLoader::load(const std::filesystem::path& path) { AudioData AudioLoader::load(const std::filesystem::path& path) {
if (!fs::is_regular_file(path)) { if (!fs::is_regular_file(path)) {
std::string err = std::format("Path {} is not a file", path.string()); std::string err = std::format("Path {} is not a file", path.string());
ASSERT_MSG(false, err);
throw std::runtime_error(err); throw std::runtime_error(err);
} }
auto ext = path.extension().string(); auto ext = path.extension().string();
@@ -46,6 +46,8 @@ AudioData AudioLoader::load_wav(const std::filesystem::path& path) {
drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, data.pcm.data()); drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, data.pcm.data());
drwav_uninit(&wav); drwav_uninit(&wav);
Logger::info("{} channels={} rate={} samples={}", path.filename().string(),
data.channels, data.sample_rate, data.pcm.size());
return data; return data;
} }
@@ -68,7 +70,8 @@ AudioData AudioLoader::load_mp3(const std::filesystem::path& path) {
data.pcm.assign(pcm, pcm + frame_count * config.channels); data.pcm.assign(pcm, pcm + frame_count * config.channels);
drmp3_free(pcm, nullptr); drmp3_free(pcm, nullptr);
Logger::info("{} channels={} rate={} samples={}", path.filename().string(),
data.channels, data.sample_rate, data.pcm.size());
return data; return data;
} }
@@ -90,7 +93,8 @@ AudioData AudioLoader::load_flac(const std::filesystem::path& path) {
data.pcm.assign(pcm, pcm + frame_count * channels); data.pcm.assign(pcm, pcm + frame_count * channels);
drflac_free(pcm, nullptr); drflac_free(pcm, nullptr);
Logger::info("{} channels={} rate={} samples={}", path.filename().string(),
data.channels, data.sample_rate, data.pcm.size());
return data; return data;
} }
@@ -117,7 +121,7 @@ AudioData AudioLoader::load_ogg(const std::filesystem::path& path) {
std::vector<int16_t> pcm(total_frames * channels); std::vector<int16_t> pcm(total_frames * channels);
int frames_read = stb_vorbis_get_samples_short_interleaved( int frames_read = stb_vorbis_get_samples_short_interleaved(
vorbis, channels, pcm.data(), total_frames); vorbis, channels, pcm.data(), pcm.size());
if (frames_read < total_frames) { if (frames_read < total_frames) {
pcm.resize(frames_read * channels); pcm.resize(frames_read * channels);
@@ -129,6 +133,10 @@ AudioData AudioLoader::load_ogg(const std::filesystem::path& path) {
data.channels = channels; data.channels = channels;
data.sample_rate = sample_rate; data.sample_rate = sample_rate;
data.pcm = std::move(pcm); data.pcm = std::move(pcm);
Logger::info("{} channels={} rate={} samples={}", path.filename().string(),
data.channels, data.sample_rate, data.pcm.size());
return data; return data;
} }

View File

@@ -1,5 +1,6 @@
#include "Cubed/audio/audio_source.hpp" #include "Cubed/audio/audio_source.hpp"
#include "Cubed/audio/audio_error.hpp"
#include "Cubed/tools/log.hpp" #include "Cubed/tools/log.hpp"
#include <algorithm> #include <algorithm>
@@ -35,9 +36,20 @@ void AudioSource::set_buffer_3d(const AudioBuffer& buffer,
stop(); stop();
} }
m_duration = buffer.duration(); m_duration = buffer.duration();
Logger::info("buffer={}", buffer.buffer());
Logger::info("source={} isSource={}", m_source, alIsSource(m_source));
Logger::info("buffer={} isBuffer={}", buffer.buffer(),
alIsBuffer(buffer.buffer()));
alSourcei(m_source, AL_BUFFER, buffer.buffer()); alSourcei(m_source, AL_BUFFER, buffer.buffer());
check_al_error();
alSource3f(m_source, AL_POSITION, pos.x, pos.y, pos.z); alSource3f(m_source, AL_POSITION, pos.x, pos.y, pos.z);
check_al_error();
alSourcef(m_source, AL_REFERENCE_DISTANCE, 4.0f);
alSourcef(m_source, AL_ROLLOFF_FACTOR, 1.0f);
alSourcef(m_source, AL_MAX_DISTANCE, 100.0f);
} }
void AudioSource::set_loop(bool on) { void AudioSource::set_loop(bool on) {
@@ -64,6 +76,7 @@ void AudioSource::play_2d(const AudioBuffer& buffer) {
void AudioSource::play_3d(const AudioBuffer& buffer, const glm::vec3& pos) { void AudioSource::play_3d(const AudioBuffer& buffer, const glm::vec3& pos) {
set_buffer_3d(buffer, pos); set_buffer_3d(buffer, pos);
play(); play();
check_al_error();
} }
void AudioSource::stop() { alSourceStop(m_source); } void AudioSource::stop() { alSourceStop(m_source); }

View File

@@ -1,7 +1,7 @@
#include "Cubed/audio/sound_manager.hpp" #include "Cubed/audio/sound_manager.hpp"
#include "Cubed/audio/audio_loader.hpp" #include "Cubed/audio/audio_loader.hpp"
#include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/log.hpp"
#include <filesystem> #include <filesystem>
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -38,7 +38,7 @@ const AudioBuffer& SoundManager::get_buffer(const std::string& name) {
return load(name); return load(name);
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::string err = std::format("Can't Find Buffer {}", name); std::string err = std::format("Can't Find Buffer {}", name);
ASSERT_MSG(false, err);
throw std::runtime_error(err); throw std::runtime_error(err);
} }
} }

View File

@@ -1,9 +1,9 @@
#include "Cubed/gameplay/client_player.hpp" #include "Cubed/gameplay/client_player.hpp"
#include "Cubed/audio/audio_engine.hpp"
#include "Cubed/config.hpp" #include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp" #include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/client_world.hpp" #include "Cubed/gameplay/client_world.hpp"
namespace Cubed { namespace Cubed {
ClientPlayer::ClientPlayer(ClientWorld& world) : m_world(world) {} ClientPlayer::ClientPlayer(ClientWorld& world) : m_world(world) {}
ClientPlayer::~ClientPlayer() {} ClientPlayer::~ClientPlayer() {}
@@ -144,42 +144,35 @@ void ClientPlayer::update_player_move_state(int key, int action) {
case GLFW_KEY_W: case GLFW_KEY_W:
if (action == GLFW_PRESS) { if (action == GLFW_PRESS) {
m_move_state.forward = true; m_move_state.forward = true;
m_moving = true;
} }
if (action == GLFW_RELEASE) { if (action == GLFW_RELEASE) {
m_move_state.forward = false; m_move_state.forward = false;
m_moving = false;
m_sprinting = false; m_sprinting = false;
} }
break; break;
case GLFW_KEY_S: case GLFW_KEY_S:
if (action == GLFW_PRESS) { if (action == GLFW_PRESS) {
m_move_state.back = true; m_move_state.back = true;
m_moving = true;
} }
if (action == GLFW_RELEASE) { if (action == GLFW_RELEASE) {
m_move_state.back = false; m_move_state.back = false;
m_moving = false;
} }
break; break;
case GLFW_KEY_A: case GLFW_KEY_A:
if (action == GLFW_PRESS) { if (action == GLFW_PRESS) {
m_move_state.left = true; m_move_state.left = true;
m_moving = true;
} }
if (action == GLFW_RELEASE) { if (action == GLFW_RELEASE) {
m_move_state.left = false; m_move_state.left = false;
m_moving = false;
} }
break; break;
case GLFW_KEY_D: case GLFW_KEY_D:
if (action == GLFW_PRESS) { if (action == GLFW_PRESS) {
m_move_state.right = true; m_move_state.right = true;
m_moving = true;
} }
if (action == GLFW_RELEASE) { if (action == GLFW_RELEASE) {
m_move_state.right = false; m_move_state.right = false;
m_moving = false;
} }
break; break;
case GLFW_KEY_SPACE: case GLFW_KEY_SPACE:
@@ -223,6 +216,8 @@ void ClientPlayer::update_player_move_state(int key, int action) {
} }
break; break;
} }
m_moving = m_move_state.forward || m_move_state.back || m_move_state.left ||
m_move_state.right;
} }
void ClientPlayer::update_front_vec(float offset_x, float offset_y) { void ClientPlayer::update_front_vec(float offset_x, float offset_y) {
@@ -391,6 +386,7 @@ void ClientPlayer::update_move(float delta_time) {
m_player_pos = player_pos; m_player_pos = player_pos;
} }
update_player_chunk(); update_player_chunk();
play_walk_sound(delta_time);
} }
void ClientPlayer::update_x_move(glm::vec3& player_pos) { void ClientPlayer::update_x_move(glm::vec3& player_pos) {
@@ -503,6 +499,42 @@ void ClientPlayer::update_player_chunk() {
} }
} }
void ClientPlayer::play_walk_sound(float dt) {
if (!m_moving || is_fly) {
return;
}
m_footstep_timer += dt;
if (m_sprinting) {
const float WALK_INTERVAL = 0.3f;
if (m_footstep_timer < WALK_INTERVAL) {
return;
}
} else {
const float WALK_INTERVAL = 0.45f;
if (m_footstep_timer < WALK_INTERVAL) {
return;
}
}
m_footstep_timer = 0.0f;
glm::ivec3 block = glm::floor(m_player_pos);
block.y -= 1;
BlockType id = m_world.get_block_tpye(block);
Logger::info("player Block {} Walk Sound", id);
if (id == 0) {
return;
}
std::string name = BlockManager::name_form_id(id);
std::string sound = "block/" + name + "/walk.ogg";
auto& audio = m_world.get_audio();
audio.play_3d(sound, m_player_pos);
Logger::info("Player block {} walk sound", name);
}
Gait ClientPlayer::compute_gait() const { Gait ClientPlayer::compute_gait() const {
if (m_xz_speed < 0.01f) if (m_xz_speed < 0.01f)
return Gait::STOP; return Gait::STOP;

View File

@@ -607,6 +607,8 @@ AABB ClientWorld::get_block_aabb(const glm::ivec3& pos) {
static_cast<float>(z + 1)}}; static_cast<float>(z + 1)}};
} }
AudioEngine& ClientWorld::get_audio() { return m_audio; }
void ClientWorld::request_exit() { void ClientWorld::request_exit() {
if (m_receive_exit) { if (m_receive_exit) {
return; return;