Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,8 @@ if(FT_MINECRAFT_FETCH_DEPS)
GIT_SHALLOW TRUE
)

FetchContent_Declare(
fastnoiselite
GIT_REPOSITORY https://github.com/Auburn/FastNoiseLite.git
GIT_TAG v1.1.1
GIT_SHALLOW TRUE
)

FetchContent_MakeAvailable(glfw glm)

FetchContent_GetProperties(fastnoiselite)
if(NOT fastnoiselite_POPULATED)
FetchContent_Populate(fastnoiselite)
endif()

add_library(ft_minecraft_fastnoise INTERFACE)
target_include_directories(
ft_minecraft_fastnoise
INTERFACE
${fastnoiselite_SOURCE_DIR}/Cpp
)

# ── miniaudio (single-header audio engine) ─────────────────────────────────
FetchContent_Declare(
miniaudio
Expand Down Expand Up @@ -139,7 +120,6 @@ target_link_libraries(
OpenGL::GL
glfw
glm::glm
ft_minecraft_fastnoise
Threads::Threads
${CMAKE_DL_LIBS} # miniaudio uses dlopen on Linux for audio backends
m # math library (miniaudio, stb_vorbis)
Expand Down
51 changes: 46 additions & 5 deletions srcs/audio/audio_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,12 @@ void AudioManager::update(float dt) {
}
}

// Reclaim SE pool slots that have finished playing
// Reclaim SE pool slots that have finished playing.
// 停止予約 (ma_sound_set_stop_time_*) で止まった音は at_end にならないため
// is_playing もあわせて確認する (start 直後の音は必ず is_playing == true)。
for (auto& se : d.se_pool)
if (se.initialized && ma_sound_at_end(&se.sound)) {
if (se.initialized &&
(ma_sound_at_end(&se.sound) || !ma_sound_is_playing(&se.sound))) {
ma_sound_uninit(&se.sound);
se.initialized = false;
}
Expand Down Expand Up @@ -366,20 +369,57 @@ void AudioManager::stopAmbient(float fade_out) {

// ── SE ────────────────────────────────────────────────────────────────────────

void AudioManager::playSe(SoundEvent event) {
// 再生長制限: max_duration_sec かけて 0 へフェードしつつ、終端で停止を予約する。
// ハードカットだとクリックノイズが出るため、全区間フェードで自然に減衰させる。
static void applyDurationLimit(ma_engine* engine, ma_sound* sound,
float volume, float max_duration_sec) {
if (max_duration_sec <= 0.0f) return;
const ma_uint64 sr = ma_engine_get_sample_rate(engine);
const ma_uint64 now = ma_engine_get_time_in_pcm_frames(engine);
const ma_uint64 dur = (ma_uint64)((double)max_duration_sec * (double)sr);
ma_sound_set_fade_in_milliseconds(
sound, volume, 0.0f, (ma_uint64)(max_duration_sec * 1000.0f));
ma_sound_set_stop_time_in_pcm_frames(sound, now + dur);
}

void AudioManager::playSe(SoundEvent event, float volume,
float max_duration_sec) {
Impl& d = *impl_;
if (!d.engine_ok) return;

int idx = static_cast<int>(event);
if (idx < 0 || idx >= static_cast<int>(SoundEvent::COUNT)) return;

std::string full = resolveAudioPath(d.assets_root + "/" + kSeStems[idx]);
if (ma_engine_play_sound(&d.engine, full.c_str(), &d.se_group) != MA_SUCCESS)

// 音量・再生長の指定がなければ従来どおり fire-and-forget
if (volume >= 0.999f && max_duration_sec <= 0.0f) {
if (ma_engine_play_sound(&d.engine, full.c_str(), &d.se_group) != MA_SUCCESS)
fprintf(stderr, "[Audio] playSe failed: %s\n", full.c_str());
return;
}

// 音量/再生長を制御するため SE プールの ma_sound を使う (非3D)
SeSlot* slot = nullptr;
for (auto& s : d.se_pool) if (!s.initialized) { slot = &s; break; }
if (!slot) return; // pool full; drop the sound

if (ma_sound_init_from_file(&d.engine, full.c_str(), 0,
&d.se_group, nullptr,
&slot->sound) != MA_SUCCESS) {
fprintf(stderr, "[Audio] playSe failed: %s\n", full.c_str());
return;
}
slot->initialized = true;
ma_sound_set_spatialization_enabled(&slot->sound, MA_FALSE);
ma_sound_set_volume(&slot->sound, volume);
ma_sound_set_looping(&slot->sound, MA_FALSE);
applyDurationLimit(&d.engine, &slot->sound, volume, max_duration_sec);
ma_sound_start(&slot->sound);
}

void AudioManager::playSe3D(SoundEvent event, float x, float y, float z,
float max_distance) {
float max_distance, float max_duration_sec) {
Impl& d = *impl_;
if (!d.engine_ok) return;

Expand Down Expand Up @@ -410,6 +450,7 @@ void AudioManager::playSe3D(SoundEvent event, float x, float y, float z,
ma_sound_set_min_distance(&slot->sound, 1.0f);
ma_sound_set_max_distance(&slot->sound, max_distance);
ma_sound_set_looping(&slot->sound, MA_FALSE);
applyDurationLimit(&d.engine, &slot->sound, 1.0f, max_duration_sec);
ma_sound_start(&slot->sound);
}

Expand Down
10 changes: 8 additions & 2 deletions srcs/audio/audio_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,19 @@ class AudioManager {
void stopAmbient(float fade_out = 2.0f);

// ── SE: 2D one-shot ──────────────────────────────────────────────────────
void playSe(SoundEvent event);
// volume: この再生のみのゲイン [0,1]。
// max_duration_sec: > 0 なら再生をこの秒数で打ち切る (フェードアウト付き)。
// 足音素材のように 1 ファイルに複数歩入っている長尺サンプルを
// 「1 歩分」だけ鳴らすために使う。0 なら全長再生。
void playSe(SoundEvent event, float volume = 1.0f,
float max_duration_sec = 0.0f);

// ── SE: 3D positional one-shot ───────────────────────────────────────────
// Skipped automatically when the source is farther than max_distance.
// Uses inverse-distance attenuation (OpenAL-style).
// max_duration_sec: playSe と同じ再生長制限。
void playSe3D(SoundEvent event, float x, float y, float z,
float max_distance = 32.0f);
float max_distance = 32.0f, float max_duration_sec = 0.0f);

// ── Internal access for SoundEmitter ────────────────────────────────────
// Returns the raw ma_engine* as void*. Used only by SoundEmitter.
Expand Down
Loading