diff --git a/src/app/app_core.c b/src/app/app_core.c index 826c35f5..60fe1e31 100644 --- a/src/app/app_core.c +++ b/src/app/app_core.c @@ -243,7 +243,7 @@ void P_AppStartup(String args_str) TTF_StartupReceipt ttf_sr = ttf_startup(); F_StartupReceipt font_sr = F_Startup(&asset_cache_sr, &ttf_sr); S_StartupReceipt sprite_sr = sprite_startup(); - M_StartupReceipt mixer_sr = mixer_startup(); + M_StartupReceipt mixer_sr = M_Startup(); SND_StartupReceipt sound_sr = sound_startup(&asset_cache_sr); D_StartupReceipt draw_sr = D_Startup(&font_sr); SimStartupReceipt sim_sr = sim_startup(); diff --git a/src/mixer/mixer_core.c b/src/mixer/mixer_core.c index 7ea40194..f64c7309 100644 --- a/src/mixer/mixer_core.c +++ b/src/mixer/mixer_core.c @@ -14,196 +14,173 @@ * - 2 32 bit float samples output by mixer and consumed by playback API, one sample for each audio channel */ -struct effect_data { - /* Spatialization */ - f32 spatial_volume; - f32 spatial_pan; -}; +M_SharedState M_shared_state = ZI; -struct mix { - M_Handle track_handle; - b32 track_finished; +//////////////////////////////// +//~ Startup - M_TrackDesc desc; - struct effect_data effect_data; - SND_Sound *source; - u64 source_pos; -}; - -struct track { - u64 gen; - - /* Controlled via interface */ - SND_Sound *sound; - M_TrackDesc desc; - - /* internal */ - struct mix mix; - - struct track *next; - struct track *prev; -}; - -Global struct { - P_Mutex mutex; - - /* Listener */ - Vec2 listener_pos; - Vec2 listener_dir; - - /* Track list */ - Arena *track_arena; - struct track *track_first_playing; - struct track *track_last_playing; - u64 track_playing_count; - struct track *track_first_free; -} G = ZI, DebugAlias(G, G_mixer); - -/* ========================== * - * Startup - * ========================== */ - -M_StartupReceipt mixer_startup(void) +M_StartupReceipt M_Startup(void) { __prof; - G.track_arena = AllocArena(Gibi(64)); - G.listener_pos = VEC2(0, 0); - G.listener_dir = VEC2(0, -1); + M_SharedState *g = &M_shared_state; + g->track_arena = AllocArena(Gibi(64)); + g->listener_pos = VEC2(0, 0); + g->listener_dir = VEC2(0, -1); return (M_StartupReceipt) { 0 }; } -/* ========================== * - * Track - * ========================== */ +//////////////////////////////// +//~ Track -internal M_Handle track_to_handle(struct track *track) +M_Handle M_HandleFromTrack(M_Track *track) { - return (M_Handle) { - .gen = track->gen, - .data = track - }; + M_Handle result = ZI; + result.gen = track->gen; + result.data = track; + return result; } -internal struct track *track_from_handle(M_Handle handle) +M_Track *M_TrackFromHandle(M_Handle handle) { - struct track *track = (struct track *)handle.data; - if (track && track->gen == handle.gen) { + M_Track *track = (M_Track *)handle.data; + if (track && track->gen == handle.gen) + { return track; - } else { + } + else + { return 0; } } -internal struct track *track_alloc_locked(P_Lock *lock, SND_Sound *sound) +M_Track *M_AllocTrackLocked(P_Lock *lock, SND_Sound *sound) { - P_AssertLockedE(lock, &G.mutex); + M_SharedState *g = &M_shared_state; + P_AssertLockedE(lock, &g->mutex); (UNUSED)lock; - struct track *track = 0; - if (G.track_first_free) { + M_Track *track = 0; + if (g->track_first_free) + { /* Take from free list */ - track = G.track_first_free; - struct track *next_free = track->next; - G.track_first_free = next_free; - if (next_free) { + track = g->track_first_free; + M_Track *next_free = track->next; + g->track_first_free = next_free; + if (next_free) + { next_free->prev = 0; } - *track = (struct track) { .gen = track->gen + 1 }; - } else { + *track = (M_Track) { .gen = track->gen + 1 }; + } + else + { /* Allocate new */ - track = PushStruct(G.track_arena, struct track); + track = PushStruct(g->track_arena, M_Track); track->gen = 1; } track->sound = sound; track->mix.source = sound; - track->mix.track_handle = track_to_handle(track); + track->mix.track_handle = M_HandleFromTrack(track); /* Append to playing list */ - struct track *prev = G.track_last_playing; - if (prev) { + M_Track *prev = g->track_last_playing; + if (prev) + { prev->next = track; - } else { - G.track_first_playing = track; } - G.track_last_playing = track; + else + { + g->track_first_playing = track; + } + g->track_last_playing = track; track->prev = prev; - ++G.track_playing_count; + ++g->track_playing_count; return track; } -internal void track_release_locked(P_Lock *lock, struct track *track) +void M_ReleaseTrackLocked(P_Lock *lock, M_Track *track) { - P_AssertLockedE(lock, &G.mutex); + M_SharedState *g = &M_shared_state; + P_AssertLockedE(lock, &g->mutex); (UNUSED)lock; /* Remove from playing list */ - struct track *prev = track->prev; - struct track *next = track->next; - if (prev) { + M_Track *prev = track->prev; + M_Track *next = track->next; + if (prev) + { prev->next = next; - } else { + } + else + { /* Track was first in list */ - G.track_first_playing = next; + g->track_first_playing = next; } - if (next) { + if (next) + { next->prev = prev; - } else { - /* Track was last in list */ - G.track_last_playing = prev; } - --G.track_playing_count; + else + { + /* Track was last in list */ + g->track_last_playing = prev; + } + --g->track_playing_count; ++track->gen; /* Add to free list */ track->prev = 0; - track->next = G.track_first_free; - if (G.track_first_free) { - G.track_first_free->prev = track; - } - G.track_first_free = track; -} - -/* ========================== * - * Interface - * ========================== */ - -/* TODO: Rework interface to take "mixer_cmd"s instead of - * directly modifying tracks. */ - -M_Handle mixer_play(SND_Sound *sound) -{ - return mixer_play_ex(sound, MIXER_DESC()); -} - -M_Handle mixer_play_ex(SND_Sound *sound, M_TrackDesc desc) -{ - struct track *track; + track->next = g->track_first_free; + if (g->track_first_free) { - P_Lock lock = P_LockE(&G.mutex); + g->track_first_free->prev = track; + } + g->track_first_free = track; +} + +//////////////////////////////// +//~ Mixer control + + /* TODO: Rework interface to be command based instead of directly modifying tracks. */ + +M_Handle M_PlaySound(SND_Sound *sound) +{ + return M_PlaySoundEx(sound, M_TRACKDESC()); +} + +M_Handle M_PlaySoundEx(SND_Sound *sound, M_TrackDesc desc) +{ + M_SharedState *g = &M_shared_state; + M_Track *track; + { + P_Lock lock = P_LockE(&g->mutex); { - track = track_alloc_locked(&lock, sound); + track = M_AllocTrackLocked(&lock, sound); track->desc = desc; } P_Unlock(&lock); } - return track_to_handle(track); + return M_HandleFromTrack(track); } /* NOTE: This is quite inefficient. */ -M_TrackDesc mixer_track_get(M_Handle handle) +M_TrackDesc M_TrackDescFromHandle(M_Handle handle) { + M_SharedState *g = &M_shared_state; M_TrackDesc result = ZI; - struct track *track = track_from_handle(handle); - if (track) { + M_Track *track = M_TrackFromHandle(handle); + if (track) + { /* TODO: Only lock mutex on track itself or something */ - P_Lock lock = P_LockE(&G.mutex); + P_Lock lock = P_LockE(&g->mutex); { /* Confirm handle is still valid now that we're locked */ - track = track_from_handle(handle); - if (track) { + track = M_TrackFromHandle(handle); + if (track) + { result = track->desc; } } @@ -214,16 +191,19 @@ M_TrackDesc mixer_track_get(M_Handle handle) } /* NOTE: This is quite inefficient. */ -void mixer_track_set(M_Handle handle, M_TrackDesc desc) +void M_UpdateTrack(M_Handle handle, M_TrackDesc desc) { - struct track *track = track_from_handle(handle); - if (track) { + M_SharedState *g = &M_shared_state; + M_Track *track = M_TrackFromHandle(handle); + if (track) + { /* TODO: Only lock mutex on track itself or something */ - P_Lock lock = P_LockE(&G.mutex); + P_Lock lock = P_LockE(&g->mutex); { /* Confirm handle is still valid now that we're locked */ - track = track_from_handle(handle); - if (track) { + track = M_TrackFromHandle(handle); + if (track) + { track->desc = desc; } } @@ -231,37 +211,42 @@ void mixer_track_set(M_Handle handle, M_TrackDesc desc) } } -void mixer_set_listener(Vec2 pos, Vec2 dir) +void M_UpdateListener(Vec2 pos, Vec2 dir) { - P_Lock lock = P_LockE(&G.mutex); + M_SharedState *g = &M_shared_state; + P_Lock lock = P_LockE(&g->mutex); { - G.listener_pos = pos; - G.listener_dir = NormVec2(dir); + g->listener_pos = pos; + g->listener_dir = NormVec2(dir); } P_Unlock(&lock); } -/* ========================== * - * Update - * ========================== */ +//////////////////////////////// +//~ Mix -internal i16 sample_sound(SND_Sound *sound, u64 sample_pos, b32 wrap) +i16 M_SampleSound(SND_Sound *sound, u64 sample_pos, b32 wrap) { - if (wrap) { + if (wrap) + { return sound->samples[sample_pos % sound->samples_count]; - } else if (sample_pos < sound->samples_count) { + } + else if (sample_pos < sound->samples_count) + { return sound->samples[sample_pos]; - } else { + } + else + { return 0; } } /* To be called once per audio playback interval */ -M_PcmF32 mixer_update(Arena *arena, u64 frame_count) +M_PcmF32 M_MixAllTracks(Arena *arena, u64 frame_count) { __prof; - TempArena scratch = BeginScratch(arena); + M_SharedState *g = &M_shared_state; M_PcmF32 result = ZI; result.count = frame_count * 2; @@ -270,21 +255,23 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) Vec2 listener_pos = VEC2(0, 0); Vec2 listener_dir = VEC2(0, 0); - /* Create temp array of mixes */ - struct mix **mixes = 0; + //- Create temp mix array + + M_MixData **mixes = 0; u64 mixes_count = 0; { - P_Lock lock = P_LockE(&G.mutex); + P_Lock lock = P_LockE(&g->mutex); /* Read listener info */ - listener_pos = G.listener_pos; - listener_dir = G.listener_dir; + listener_pos = g->listener_pos; + listener_dir = g->listener_dir; /* Update & read mixes */ - mixes = PushStructsNoZero(scratch.arena, struct mix *, G.track_playing_count); - for (struct track *track = G.track_first_playing; track; track = track->next) { + mixes = PushStructsNoZero(scratch.arena, M_MixData *, g->track_playing_count); + for (M_Track *track = g->track_first_playing; track; track = track->next) + { __profn("Prepare track"); - struct mix *mix = &track->mix; + M_MixData *mix = &track->mix; mix->desc = track->desc; mixes[mixes_count++] = mix; } @@ -292,29 +279,37 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) P_Unlock(&lock); } - for (u64 mix_index = 0; mix_index < mixes_count; ++mix_index) { - __profn("Mix track"); - struct mix *mix = mixes[mix_index]; + //- Process mix data - if (mix->source->samples_count <= 0) { + for (u64 mix_index = 0; mix_index < mixes_count; ++mix_index) + { + __profn("Mix track"); + M_MixData *mix = mixes[mix_index]; + + if (mix->source->samples_count <= 0) + { /* Skip empty sounds */ continue; } SND_Sound *source = mix->source; M_TrackDesc desc = mix->desc; - struct effect_data *effect_data = &mix->effect_data; + M_EffectData *effect_data = &mix->effect_data; b32 source_is_stereo = source->flags & SOUND_FLAG_STEREO; f32 speed = MaxF32(0, desc.speed); - /* Determine sample range */ + //- Determine sample range + u64 source_samples_count = 0; - if (source_is_stereo) { + if (source_is_stereo) + { source_samples_count = frame_count * 2; /* Round to nearest frame boundary (nearest multiple of 2) */ source_samples_count = (u64)CeilF32ToI32((f32)source_samples_count * speed); source_samples_count &= ~1; - } else { + } + else + { source_samples_count = frame_count; /* Round to nearest sample */ source_samples_count = (u64)RoundF32ToI32((f32)source_samples_count * speed); @@ -322,10 +317,14 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) u64 source_sample_pos_start = mix->source_pos; u64 source_sample_pos_end = source_sample_pos_start + source_samples_count; - if (source_sample_pos_end >= source->samples_count) { - if (desc.looping) { + if (source_sample_pos_end >= source->samples_count) + { + if (desc.looping) + { source_sample_pos_end = source_sample_pos_end % source->samples_count; - } else { + } + else + { source_sample_pos_end = source->samples_count; mix->track_finished = 1; } @@ -340,11 +339,9 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) .samples = PushStructs(scratch.arena, f32, result.count) }; - /* ========================== * - * Resample - * ========================== */ + //- Resample - /* Transform 16 bit source -> 32 bit stereo at output duration */ + /* Transform 16 bit source -> 32 bit stereo at output duration */ { __profn("Resample"); f32 *out_samples = mix_pcm.samples; @@ -353,18 +350,20 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) /* TODO: Fast path for 1:1 copy when speed = 1.0? */ /* TODO: Optimize */ - if (source_is_stereo) { + if (source_is_stereo) + { /* 16 bit Stereo -> 32 bit Stereo */ - for (u64 out_frame_pos = 0; out_frame_pos < out_frames_count; ++out_frame_pos) { + for (u64 out_frame_pos = 0; out_frame_pos < out_frames_count; ++out_frame_pos) + { f32 in_frame_pos_exact = source_frame_pos_start + (((f32)out_frame_pos / (f32)out_frames_count) * (f32)source_frames_count); u32 in_frame_pos_prev = FloorF32ToI32(in_frame_pos_exact); u32 in_frame_pos_next = CeilF32ToI32(in_frame_pos_exact); /* Sample source */ - f32 sample1_prev = sample_sound(source, (in_frame_pos_prev * 2) + 0, desc.looping) * (1.f / 32768.f); - f32 sample1_next = sample_sound(source, (in_frame_pos_next * 2) + 0, desc.looping) * (1.f / 32768.f); - f32 sample2_prev = sample_sound(source, (in_frame_pos_prev * 2) + 1, desc.looping) * (1.f / 32768.f); - f32 sample2_next = sample_sound(source, (in_frame_pos_next * 2) + 1, desc.looping) * (1.f / 32768.f); + f32 sample1_prev = M_SampleSound(source, (in_frame_pos_prev * 2) + 0, desc.looping) * (1.f / 32768.f); + f32 sample1_next = M_SampleSound(source, (in_frame_pos_next * 2) + 0, desc.looping) * (1.f / 32768.f); + f32 sample2_prev = M_SampleSound(source, (in_frame_pos_prev * 2) + 1, desc.looping) * (1.f / 32768.f); + f32 sample2_next = M_SampleSound(source, (in_frame_pos_next * 2) + 1, desc.looping) * (1.f / 32768.f); /* Lerp */ f32 t = in_frame_pos_exact - (f32)in_frame_pos_prev; @@ -374,16 +373,19 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) out_samples[(out_frame_pos * 2) + 0] += sample1; out_samples[(out_frame_pos * 2) + 1] += sample2; } - } else { + } + else + { /* 16 bit Mono -> 32 bit Stereo */ - for (u64 out_frame_pos = 0; out_frame_pos < out_frames_count; ++out_frame_pos) { + for (u64 out_frame_pos = 0; out_frame_pos < out_frames_count; ++out_frame_pos) + { f32 in_frame_pos_exact = source_frame_pos_start + (((f32)out_frame_pos / (f32)out_frames_count) * (f32)source_frames_count); u32 in_frame_pos_prev = FloorF32ToI32(in_frame_pos_exact); u32 in_frame_pos_next = CeilF32ToI32(in_frame_pos_exact); /* Sample source */ - f32 sample_prev = sample_sound(source, in_frame_pos_prev, desc.looping) * (1.f / 32768.f); - f32 sample_next = sample_sound(source, in_frame_pos_next, desc.looping) * (1.f / 32768.f); + f32 sample_prev = M_SampleSound(source, in_frame_pos_prev, desc.looping) * (1.f / 32768.f); + f32 sample_next = M_SampleSound(source, in_frame_pos_next, desc.looping) * (1.f / 32768.f); /* Lerp */ f32 t = (f32)in_frame_pos_exact - in_frame_pos_prev; @@ -395,11 +397,10 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) } } - /* ========================== * - * Spatialize - * ========================== */ + //- Spatialize - if (desc.flags & MIXER_FLAG_SPATIALIZE) { + if (desc.flags & M_TrackFlag_Spatialize) + { __profn("Spatialize"); /* Algorithm constants */ @@ -410,7 +411,8 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) Vec2 pos = desc.pos; /* If sound pos = listener pos, pretend sound is close in front of listener. */ - if (EqVec2(listener_pos, pos)) { + if (EqVec2(listener_pos, pos)) + { pos = AddVec2(listener_pos, MulVec2(listener_dir, 0.001f)); } Vec2 sound_rel = SubVec2(pos, listener_pos); @@ -436,7 +438,8 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) effect_data->spatial_pan = pan_end; /* Spatialize samples */ - for (u64 frame_pos = 0; frame_pos < frame_count; ++frame_pos) { + for (u64 frame_pos = 0; frame_pos < frame_count; ++frame_pos) + { f32 t = (f32)frame_pos / (f32)(frame_count - 1); f32 volume = LerpF32(volume_start, volume_end, t); f32 pan = LerpF32(pan_start, pan_end, t); @@ -451,25 +454,29 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count) } } - /* ========================== * - * Mix into result - * ========================== */ + //- Mix into result - for (u64 i = 0; i < mix_pcm.count; ++i) { + for (u64 i = 0; i < mix_pcm.count; ++i) + { result.samples[i] += mix_pcm.samples[i] * desc.volume; } } + //- Update track effect data + { __profn("Update track effect data"); - P_Lock lock = P_LockE(&G.mutex); - for (u64 i = 0; i < mixes_count; ++i) { - struct mix *mix = mixes[i]; - struct track *track = track_from_handle(mix->track_handle); - if (track) { - if (mix->track_finished) { + P_Lock lock = P_LockE(&g->mutex); + for (u64 i = 0; i < mixes_count; ++i) + { + M_MixData *mix = mixes[i]; + M_Track *track = M_TrackFromHandle(mix->track_handle); + if (track) + { + if (mix->track_finished) + { /* Release finished tracks */ - track_release_locked(&lock, track); + M_ReleaseTrackLocked(&lock, track); } } } diff --git a/src/mixer/mixer_core.h b/src/mixer/mixer_core.h index 1be2c6a7..3c4c9768 100644 --- a/src/mixer/mixer_core.h +++ b/src/mixer/mixer_core.h @@ -1,48 +1,125 @@ -#define MIXER_FLAG_NONE 0x0 -#define MIXER_FLAG_SPATIALIZE 0x1 +//////////////////////////////// +//~ Track types -#define MIXER_DESC(...) ((M_TrackDesc) { \ - .flags = 0, \ -\ - .volume = 1.0, \ - .speed = 1.0, \ - .looping = 0, \ -\ - .pos = VEC2(0, 0), \ -\ - __VA_ARGS__ \ -}) - -Struct(M_TrackDesc) { - u32 flags; - f32 volume; /* 0 -> 1.0+ */ - f32 speed; /* 0 -> 1.0+ */ - b32 looping; - - /* MIXER_FLAG_SPATIALIZE */ - Vec2 pos; +typedef u32 M_TrackFlag; enum +{ + M_TrackFlag_None = (0), + M_TrackFlag_Spatialize = (1 << 0) }; -Struct(M_Handle) { +Struct(M_Handle) +{ u64 gen; void *data; }; +Struct(M_TrackDesc) +{ + M_TrackFlag flags; + f32 volume; /* 0 -> 1.0+ */ + f32 speed; /* 0 -> 1.0+ */ + b32 looping; + + /* M_TrackFlag_Spatialize */ + Vec2 pos; +}; +#define M_TRACKDESC(...) ((M_TrackDesc) { \ + .flags = 0, \ + .volume = 1.0, \ + .speed = 1.0, \ + .looping = 0, \ + .pos = VEC2(0, 0), \ + __VA_ARGS__ \ +}) + +//////////////////////////////// +//~ Mix types + /* Stereo mix of 32 bit float samples */ -Struct(M_PcmF32) { +Struct(M_PcmF32) +{ u64 count; f32 *samples; }; +Struct(M_EffectData) +{ + /* Spatialization */ + f32 spatial_volume; + f32 spatial_pan; +}; + +Struct(M_MixData) +{ + M_Handle track_handle; + b32 track_finished; + + M_TrackDesc desc; + M_EffectData effect_data; + SND_Sound *source; + u64 source_pos; +}; + +Struct(M_Track) +{ + u64 gen; + /* Controlled via interface */ + SND_Sound *sound; + M_TrackDesc desc; + + /* Internal */ + M_MixData mix; + + M_Track *next; + M_Track *prev; +}; + +//////////////////////////////// +//~ Shared state + +Struct(M_SharedState) +{ + P_Mutex mutex; + + /* Listener */ + Vec2 listener_pos; + Vec2 listener_dir; + + /* Track list */ + Arena *track_arena; + M_Track *track_first_playing; + M_Track *track_last_playing; + u64 track_playing_count; + M_Track *track_first_free; +}; + +extern M_SharedState M_shared_state; + +//////////////////////////////// +//~ Startup + Struct(M_StartupReceipt) { i32 _; }; -M_StartupReceipt mixer_startup(void); +M_StartupReceipt M_Startup(void); -/* Interface */ -M_Handle mixer_play(SND_Sound *sound); -M_Handle mixer_play_ex(SND_Sound *sound, M_TrackDesc desc); -M_TrackDesc mixer_track_get(M_Handle handle); -void mixer_track_set(M_Handle handle, M_TrackDesc desc); -void mixer_set_listener(Vec2 pos, Vec2 dir); +//////////////////////////////// +//~ Track operations -/* Mixing */ -M_PcmF32 mixer_update(Arena *arena, u64 frame_request_count); +M_Handle M_HandleFromTrack(M_Track *track); +M_Track *M_TrackFromHandle(M_Handle handle); +M_Track *M_AllocTrackLocked(P_Lock *lock, SND_Sound *sound); +void M_ReleaseTrackLocked(P_Lock *lock, M_Track *track); + +//////////////////////////////// +//~ Mixer state operations + +M_Handle M_PlaySound(SND_Sound *sound); +M_Handle M_PlaySoundEx(SND_Sound *sound, M_TrackDesc desc); +M_TrackDesc M_TrackDescFromHandle(M_Handle handle); +void M_UpdateTrack(M_Handle handle, M_TrackDesc desc); +void M_UpdateListener(Vec2 pos, Vec2 dir); + +//////////////////////////////// +//~ Mixer update + +i16 M_SampleSound(SND_Sound *sound, u64 sample_pos, b32 wrap); +M_PcmF32 M_MixAllTracks(Arena *arena, u64 frame_count); diff --git a/src/playback/playback_core_win32.c b/src/playback/playback_core_win32.c index bf401be6..8622fe66 100644 --- a/src/playback/playback_core_win32.c +++ b/src/playback/playback_core_win32.c @@ -232,7 +232,7 @@ internal P_JobDef(playback_job, _) { __profn("Fill sample buffer"); struct wasapi_buffer wspbuf = wasapi_update_begin(); - M_PcmF32 pcm = mixer_update(scratch.arena, wspbuf.frames_count); + M_PcmF32 pcm = M_MixAllTracks(scratch.arena, wspbuf.frames_count); wasapi_update_end(&wspbuf, pcm); } EndScratch(scratch); diff --git a/src/user/user_core.c b/src/user/user_core.c index 924d6aee..e37639fd 100644 --- a/src/user/user_core.c +++ b/src/user/user_core.c @@ -1014,7 +1014,7 @@ internal void user_update(P_Window *window) Vec2 ui_center = MulVec2(G.ui_size, 0.5f); Vec2 listener_pos = InvertXformMulV2(G.world_to_ui_xf, ui_center); Vec2 listener_dir = NormVec2(InvertXformBasisMulV2(G.world_to_ui_xf, up)); - mixer_set_listener(listener_pos, listener_dir); + M_UpdateListener(listener_pos, listener_dir); } /* ========================== *