mixer layer refactor

This commit is contained in:
jacob 2025-07-30 19:13:22 -05:00
parent 9e353dd1c9
commit 07eb5db9df
5 changed files with 313 additions and 229 deletions

View File

@ -243,7 +243,7 @@ void P_AppStartup(String args_str)
TTF_StartupReceipt ttf_sr = ttf_startup(); TTF_StartupReceipt ttf_sr = ttf_startup();
F_StartupReceipt font_sr = F_Startup(&asset_cache_sr, &ttf_sr); F_StartupReceipt font_sr = F_Startup(&asset_cache_sr, &ttf_sr);
S_StartupReceipt sprite_sr = sprite_startup(); 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); SND_StartupReceipt sound_sr = sound_startup(&asset_cache_sr);
D_StartupReceipt draw_sr = D_Startup(&font_sr); D_StartupReceipt draw_sr = D_Startup(&font_sr);
SimStartupReceipt sim_sr = sim_startup(); SimStartupReceipt sim_sr = sim_startup();

View File

@ -14,196 +14,173 @@
* - 2 32 bit float samples output by mixer and consumed by playback API, one sample for each audio channel * - 2 32 bit float samples output by mixer and consumed by playback API, one sample for each audio channel
*/ */
struct effect_data { M_SharedState M_shared_state = ZI;
/* Spatialization */
f32 spatial_volume;
f32 spatial_pan;
};
struct mix { ////////////////////////////////
M_Handle track_handle; //~ Startup
b32 track_finished;
M_TrackDesc desc; M_StartupReceipt M_Startup(void)
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)
{ {
__prof; __prof;
G.track_arena = AllocArena(Gibi(64)); M_SharedState *g = &M_shared_state;
G.listener_pos = VEC2(0, 0); g->track_arena = AllocArena(Gibi(64));
G.listener_dir = VEC2(0, -1); g->listener_pos = VEC2(0, 0);
g->listener_dir = VEC2(0, -1);
return (M_StartupReceipt) { 0 }; 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) { M_Handle result = ZI;
.gen = track->gen, result.gen = track->gen;
.data = track 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; M_Track *track = (M_Track *)handle.data;
if (track && track->gen == handle.gen) { if (track && track->gen == handle.gen)
{
return track; return track;
} else { }
else
{
return 0; 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; (UNUSED)lock;
struct track *track = 0; M_Track *track = 0;
if (G.track_first_free) { if (g->track_first_free)
{
/* Take from free list */ /* Take from free list */
track = G.track_first_free; track = g->track_first_free;
struct track *next_free = track->next; M_Track *next_free = track->next;
G.track_first_free = next_free; g->track_first_free = next_free;
if (next_free) { if (next_free)
{
next_free->prev = 0; next_free->prev = 0;
} }
*track = (struct track) { .gen = track->gen + 1 }; *track = (M_Track) { .gen = track->gen + 1 };
} else { }
else
{
/* Allocate new */ /* Allocate new */
track = PushStruct(G.track_arena, struct track); track = PushStruct(g->track_arena, M_Track);
track->gen = 1; track->gen = 1;
} }
track->sound = sound; track->sound = sound;
track->mix.source = sound; track->mix.source = sound;
track->mix.track_handle = track_to_handle(track); track->mix.track_handle = M_HandleFromTrack(track);
/* Append to playing list */ /* Append to playing list */
struct track *prev = G.track_last_playing; M_Track *prev = g->track_last_playing;
if (prev) { if (prev)
{
prev->next = track; 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; track->prev = prev;
++G.track_playing_count; ++g->track_playing_count;
return track; 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; (UNUSED)lock;
/* Remove from playing list */ /* Remove from playing list */
struct track *prev = track->prev; M_Track *prev = track->prev;
struct track *next = track->next; M_Track *next = track->next;
if (prev) { if (prev)
{
prev->next = next; prev->next = next;
} else { }
else
{
/* Track was first in list */ /* Track was first in list */
G.track_first_playing = next; g->track_first_playing = next;
} }
if (next) { if (next)
{
next->prev = prev; 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; ++track->gen;
/* Add to free list */ /* Add to free list */
track->prev = 0; track->prev = 0;
track->next = G.track_first_free; track->next = g->track_first_free;
if (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;
{ {
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; track->desc = desc;
} }
P_Unlock(&lock); P_Unlock(&lock);
} }
return track_to_handle(track); return M_HandleFromTrack(track);
} }
/* NOTE: This is quite inefficient. */ /* 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; M_TrackDesc result = ZI;
struct track *track = track_from_handle(handle); M_Track *track = M_TrackFromHandle(handle);
if (track) { if (track)
{
/* TODO: Only lock mutex on track itself or something */ /* 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 */ /* Confirm handle is still valid now that we're locked */
track = track_from_handle(handle); track = M_TrackFromHandle(handle);
if (track) { if (track)
{
result = track->desc; result = track->desc;
} }
} }
@ -214,16 +191,19 @@ M_TrackDesc mixer_track_get(M_Handle handle)
} }
/* NOTE: This is quite inefficient. */ /* 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); M_SharedState *g = &M_shared_state;
if (track) { M_Track *track = M_TrackFromHandle(handle);
if (track)
{
/* TODO: Only lock mutex on track itself or something */ /* 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 */ /* Confirm handle is still valid now that we're locked */
track = track_from_handle(handle); track = M_TrackFromHandle(handle);
if (track) { if (track)
{
track->desc = desc; 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_pos = pos;
G.listener_dir = NormVec2(dir); g->listener_dir = NormVec2(dir);
} }
P_Unlock(&lock); 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]; 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]; return sound->samples[sample_pos];
} else { }
else
{
return 0; return 0;
} }
} }
/* To be called once per audio playback interval */ /* 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; __prof;
TempArena scratch = BeginScratch(arena); TempArena scratch = BeginScratch(arena);
M_SharedState *g = &M_shared_state;
M_PcmF32 result = ZI; M_PcmF32 result = ZI;
result.count = frame_count * 2; 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_pos = VEC2(0, 0);
Vec2 listener_dir = VEC2(0, 0); Vec2 listener_dir = VEC2(0, 0);
/* Create temp array of mixes */ //- Create temp mix array
struct mix **mixes = 0;
M_MixData **mixes = 0;
u64 mixes_count = 0; u64 mixes_count = 0;
{ {
P_Lock lock = P_LockE(&G.mutex); P_Lock lock = P_LockE(&g->mutex);
/* Read listener info */ /* Read listener info */
listener_pos = G.listener_pos; listener_pos = g->listener_pos;
listener_dir = G.listener_dir; listener_dir = g->listener_dir;
/* Update & read mixes */ /* Update & read mixes */
mixes = PushStructsNoZero(scratch.arena, struct mix *, G.track_playing_count); mixes = PushStructsNoZero(scratch.arena, M_MixData *, g->track_playing_count);
for (struct track *track = G.track_first_playing; track; track = track->next) { for (M_Track *track = g->track_first_playing; track; track = track->next)
{
__profn("Prepare track"); __profn("Prepare track");
struct mix *mix = &track->mix; M_MixData *mix = &track->mix;
mix->desc = track->desc; mix->desc = track->desc;
mixes[mixes_count++] = mix; mixes[mixes_count++] = mix;
} }
@ -292,29 +279,37 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count)
P_Unlock(&lock); P_Unlock(&lock);
} }
for (u64 mix_index = 0; mix_index < mixes_count; ++mix_index) { //- Process mix data
__profn("Mix track");
struct mix *mix = mixes[mix_index];
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 */ /* Skip empty sounds */
continue; continue;
} }
SND_Sound *source = mix->source; SND_Sound *source = mix->source;
M_TrackDesc desc = mix->desc; 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; b32 source_is_stereo = source->flags & SOUND_FLAG_STEREO;
f32 speed = MaxF32(0, desc.speed); f32 speed = MaxF32(0, desc.speed);
/* Determine sample range */ //- Determine sample range
u64 source_samples_count = 0; u64 source_samples_count = 0;
if (source_is_stereo) { if (source_is_stereo)
{
source_samples_count = frame_count * 2; source_samples_count = frame_count * 2;
/* Round <samples_count * speed> to nearest frame boundary (nearest multiple of 2) */ /* Round <samples_count * speed> to nearest frame boundary (nearest multiple of 2) */
source_samples_count = (u64)CeilF32ToI32((f32)source_samples_count * speed); source_samples_count = (u64)CeilF32ToI32((f32)source_samples_count * speed);
source_samples_count &= ~1; source_samples_count &= ~1;
} else { }
else
{
source_samples_count = frame_count; source_samples_count = frame_count;
/* Round <samples_count * speed> to nearest sample */ /* Round <samples_count * speed> to nearest sample */
source_samples_count = (u64)RoundF32ToI32((f32)source_samples_count * speed); 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_start = mix->source_pos;
u64 source_sample_pos_end = source_sample_pos_start + source_samples_count; u64 source_sample_pos_end = source_sample_pos_start + source_samples_count;
if (source_sample_pos_end >= source->samples_count) { if (source_sample_pos_end >= source->samples_count)
if (desc.looping) { {
if (desc.looping)
{
source_sample_pos_end = source_sample_pos_end % source->samples_count; source_sample_pos_end = source_sample_pos_end % source->samples_count;
} else { }
else
{
source_sample_pos_end = source->samples_count; source_sample_pos_end = source->samples_count;
mix->track_finished = 1; mix->track_finished = 1;
} }
@ -340,11 +339,9 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count)
.samples = PushStructs(scratch.arena, f32, result.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"); __profn("Resample");
f32 *out_samples = mix_pcm.samples; 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: Fast path for 1:1 copy when speed = 1.0? */
/* TODO: Optimize */ /* TODO: Optimize */
if (source_is_stereo) { if (source_is_stereo)
{
/* 16 bit Stereo -> 32 bit 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); 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_prev = FloorF32ToI32(in_frame_pos_exact);
u32 in_frame_pos_next = CeilF32ToI32(in_frame_pos_exact); u32 in_frame_pos_next = CeilF32ToI32(in_frame_pos_exact);
/* Sample source */ /* Sample source */
f32 sample1_prev = sample_sound(source, (in_frame_pos_prev * 2) + 0, 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 = sample_sound(source, (in_frame_pos_next * 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 = sample_sound(source, (in_frame_pos_prev * 2) + 1, 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 = sample_sound(source, (in_frame_pos_next * 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 */ /* Lerp */
f32 t = in_frame_pos_exact - (f32)in_frame_pos_prev; 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) + 0] += sample1;
out_samples[(out_frame_pos * 2) + 1] += sample2; out_samples[(out_frame_pos * 2) + 1] += sample2;
} }
} else { }
else
{
/* 16 bit Mono -> 32 bit Stereo */ /* 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); 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_prev = FloorF32ToI32(in_frame_pos_exact);
u32 in_frame_pos_next = CeilF32ToI32(in_frame_pos_exact); u32 in_frame_pos_next = CeilF32ToI32(in_frame_pos_exact);
/* Sample source */ /* Sample source */
f32 sample_prev = sample_sound(source, in_frame_pos_prev, 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 = sample_sound(source, in_frame_pos_next, desc.looping) * (1.f / 32768.f); f32 sample_next = M_SampleSound(source, in_frame_pos_next, desc.looping) * (1.f / 32768.f);
/* Lerp */ /* Lerp */
f32 t = (f32)in_frame_pos_exact - in_frame_pos_prev; 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"); __profn("Spatialize");
/* Algorithm constants */ /* Algorithm constants */
@ -410,7 +411,8 @@ M_PcmF32 mixer_update(Arena *arena, u64 frame_count)
Vec2 pos = desc.pos; Vec2 pos = desc.pos;
/* If sound pos = listener pos, pretend sound is close in front of listener. */ /* 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)); pos = AddVec2(listener_pos, MulVec2(listener_dir, 0.001f));
} }
Vec2 sound_rel = SubVec2(pos, listener_pos); 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; effect_data->spatial_pan = pan_end;
/* Spatialize samples */ /* 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 t = (f32)frame_pos / (f32)(frame_count - 1);
f32 volume = LerpF32(volume_start, volume_end, t); f32 volume = LerpF32(volume_start, volume_end, t);
f32 pan = LerpF32(pan_start, pan_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; result.samples[i] += mix_pcm.samples[i] * desc.volume;
} }
} }
//- Update track effect data
{ {
__profn("Update track effect data"); __profn("Update track effect data");
P_Lock lock = P_LockE(&G.mutex); P_Lock lock = P_LockE(&g->mutex);
for (u64 i = 0; i < mixes_count; ++i) { for (u64 i = 0; i < mixes_count; ++i)
struct mix *mix = mixes[i]; {
struct track *track = track_from_handle(mix->track_handle); M_MixData *mix = mixes[i];
if (track) { M_Track *track = M_TrackFromHandle(mix->track_handle);
if (mix->track_finished) { if (track)
{
if (mix->track_finished)
{
/* Release finished tracks */ /* Release finished tracks */
track_release_locked(&lock, track); M_ReleaseTrackLocked(&lock, track);
} }
} }
} }

View File

@ -1,48 +1,125 @@
#define MIXER_FLAG_NONE 0x0 ////////////////////////////////
#define MIXER_FLAG_SPATIALIZE 0x1 //~ Track types
#define MIXER_DESC(...) ((M_TrackDesc) { \ typedef u32 M_TrackFlag; enum
.flags = 0, \ {
\ M_TrackFlag_None = (0),
.volume = 1.0, \ M_TrackFlag_Spatialize = (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;
}; };
Struct(M_Handle) { Struct(M_Handle)
{
u64 gen; u64 gen;
void *data; 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 */ /* Stereo mix of 32 bit float samples */
Struct(M_PcmF32) { Struct(M_PcmF32)
{
u64 count; u64 count;
f32 *samples; 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 _; }; Struct(M_StartupReceipt) { i32 _; };
M_StartupReceipt mixer_startup(void); M_StartupReceipt M_Startup(void);
/* Interface */ ////////////////////////////////
M_Handle mixer_play(SND_Sound *sound); //~ Track operations
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);
/* Mixing */ M_Handle M_HandleFromTrack(M_Track *track);
M_PcmF32 mixer_update(Arena *arena, u64 frame_request_count); 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);

View File

@ -232,7 +232,7 @@ internal P_JobDef(playback_job, _)
{ {
__profn("Fill sample buffer"); __profn("Fill sample buffer");
struct wasapi_buffer wspbuf = wasapi_update_begin(); 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); wasapi_update_end(&wspbuf, pcm);
} }
EndScratch(scratch); EndScratch(scratch);

View File

@ -1014,7 +1014,7 @@ internal void user_update(P_Window *window)
Vec2 ui_center = MulVec2(G.ui_size, 0.5f); Vec2 ui_center = MulVec2(G.ui_size, 0.5f);
Vec2 listener_pos = InvertXformMulV2(G.world_to_ui_xf, ui_center); Vec2 listener_pos = InvertXformMulV2(G.world_to_ui_xf, ui_center);
Vec2 listener_dir = NormVec2(InvertXformBasisMulV2(G.world_to_ui_xf, up)); Vec2 listener_dir = NormVec2(InvertXformBasisMulV2(G.world_to_ui_xf, up));
mixer_set_listener(listener_pos, listener_dir); M_UpdateListener(listener_pos, listener_dir);
} }
/* ========================== * /* ========================== *