commit 72329c47a394fd2980694abab1a076fb38d3a499
parent 973b00e231554a33e79b152c324301c13ce5414e
Author: MikoĊaj Lenczewski <mblenczewski@gmail.com>
Date: Mon, 3 Mar 2025 00:39:34 +0000
Prototype sound mixer
Diffstat:
6 files changed, 210 insertions(+), 96 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,4 +1,6 @@
bin/
dep/
+imgui.ini
+
**/.*.swp
diff --git a/debug.sh b/debug.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -ex
+
+lldbgui -- bin/linux_rts $@
diff --git a/platform/linux.c b/platform/linux.c
@@ -271,7 +271,7 @@ init_audio(struct arena *arena, struct linux_audio *audio,
audio->audio_buffer.channels = RTS_AUDIO_CHANNELS;
audio->audio_buffer.sample_rate = RTS_AUDIO_SAMPLE_RATE;
audio->audio_buffer.sample_bits = RTS_AUDIO_SAMPLE_BITS;
- audio->audio_buffer.samples = audio->buffer_frames;
+ audio->audio_buffer.frames = audio->buffer_frames;
audio->enabled = true;
@@ -300,27 +300,27 @@ update_expected_frames(struct linux_audio *audio, f32 dt)
dbglog(DEBUG, "total: %ld, expected: %ld, avail: %ld, pending: %ld\n",
total, expected, avail, delay);
- audio->audio_buffer.samples = avail;
+ audio->audio_buffer.frames = avail;
}
internal void
play_audio(struct linux_audio *audio)
{
- s32 written_samples = snd_pcm_writei(audio->handle,
- audio->audio_buffer.buffer,
- audio->audio_buffer.samples);
+ s32 written_frames = snd_pcm_writei(audio->handle,
+ audio->audio_buffer.buffer,
+ audio->audio_buffer.frames);
- if (written_samples < 0) {
- written_samples = snd_pcm_recover(audio->handle, written_samples, 0);
- } if ((u32) written_samples < audio->audio_buffer.samples) {
+ if (written_frames < 0) {
+ written_frames = snd_pcm_recover(audio->handle, written_frames, 0);
+ } if ((u32) written_frames < audio->audio_buffer.frames) {
dbglog(DEBUG, "underrun! wrote %d/%" PRIu32 " samples\n",
- written_samples, audio->audio_buffer.samples);
+ written_frames, audio->audio_buffer.frames);
}
snd_pcm_sframes_t total = audio->buffer_frames, avail, delay;
snd_pcm_avail_delay(audio->handle, &avail, &delay);
dbglog(DEBUG, "total: %ld, written: %d, new avail: %ld, new pending: %ld\n",
- total, written_samples, avail, delay);
+ total, written_frames, avail, delay);
}
internal void
diff --git a/platform/renderer.c b/platform/renderer.c
@@ -18,7 +18,9 @@ create_renderer(struct arena *arena, u32 width, u32 height)
RENDERER_BEGIN_FRAME(renderer_begin_frame)
{
- return NULL;
+ // TODO: implement real frame begin logic, instead of just satisfying
+ // an ASSERT()
+ return (void *) renderer;
}
RENDERER_BEGIN_RENDER_PASS(renderer_begin_render_pass)
diff --git a/rts/api.h b/rts/api.h
@@ -54,10 +54,11 @@ typedef unsigned char c8;
#define ALIGN_PREV(v, align) ((v) & ~((align) - 1))
#define ALIGN_NEXT(v, align) ALIGN_PREV((v) + (align) - 1, (align))
-#define TO_PARENT(ptr, T, member) ((T *) ((uintptr_t) ptr - offsetof(T, member)))
+#define TO_PARENT(ptr, T, member) \
+ ((ptr) ? ((T *) ((uintptr_t) (ptr) - offsetof(T, member))) : NULL)
-#ifdef STARFIELD_DEBUG
-# define ASSERT(cond) (!(cond) && *((volatile u8 *) 0) = 0)
+#ifdef RTS_DEBUG
+# define ASSERT(cond) (!(cond) && (*((volatile u8 *) 0) = 0))
#else
# define ASSERT(cond)
#endif
@@ -149,10 +150,14 @@ struct list_node {
#define FROM_NODE(node, T, member) TO_PARENT(node, T, member)
#define LIST_NODE_ITER(node) \
- for (struct list_node *it = (node); it; it = it->next)
+ for (struct list_node *it = (node), *next = it ? it->next : NULL; \
+ it; \
+ it = next, next = it ? it->next : NULL)
#define LIST_NODE_RITER(node) \
- for (struct list_node *it = (node); it; it = it->prev)
+ for (struct list_node *it = (node), *prev = it ? it->prev : NULL; \
+ it; \
+ it = prev, prev = it ? it->prev : NULL)
struct list {
struct list_node *head, *tail;
@@ -196,7 +201,12 @@ list_pop_head(struct list *list)
if (!list->head)
return NULL;
+ if (list->head == list->tail)
+ list->tail = NULL;
+
struct list_node *node = list->head;
+ if (node->next)
+ node->next->prev = NULL;
list->head = node->next;
return node;
}
@@ -207,11 +217,28 @@ list_pop_tail(struct list *list)
if (!list->tail)
return NULL;
+ if (list->tail == list->head)
+ list->head = NULL;
+
struct list_node *node = list->tail;
+ if (node->prev)
+ node->prev->next = NULL;
list->tail = node->prev;
return node;
}
+inline struct list_node *
+freelist_alloc(struct list *freelist)
+{
+ return list_pop_head(freelist);
+}
+
+inline void
+freelist_free(struct list *freelist, struct list_node *node)
+{
+ list_push_tail(freelist, node);
+}
+
/* platform api definitions
* ===========================================================================
*/
@@ -377,7 +404,7 @@ struct audio {
u32 channels;
u32 sample_rate;
u32 sample_bits;
- u32 samples;
+ u32 frames;
};
#define RTS_INIT(name) \
@@ -454,4 +481,10 @@ list_pop_head(struct list *list);
extern inline struct list_node *
list_pop_tail(struct list *list);
+extern inline struct list_node *
+freelist_alloc(struct list *freelist);
+
+extern inline void
+freelist_free(struct list *freelist, struct list_node *node);
+
#endif
diff --git a/rts/rts.c b/rts/rts.c
@@ -8,8 +8,6 @@ memset(void *dst, unsigned char val, size_t len)
return dst;
}
-#if 0
-
static inline void *
memcpy(void *dst, void *src, size_t len)
{
@@ -18,31 +16,93 @@ memcpy(void *dst, void *src, size_t len)
return dst;
}
-#endif
-
struct sound {
- s32 hz;
- s32 volume;
- u32 samples;
+ s32 *buffer;
+ u32 channels;
+ u32 frames;
};
+static inline void
+fill_sound_samples(struct sound *sound, u32 tone_hz, u32 tone_volume)
+{
+ const f32 PI = 3.1415926535f;
+ const u32 wave_period = RTS_AUDIO_SAMPLE_RATE / tone_hz;
+ f64 tone_t = 0;
+
+ s32 *ptr = sound->buffer;
+ for (u32 i = 0; i < sound->frames; i++) {
+ f64 v = 0;
+ __asm__ volatile ("fsin" : "=t" (v) : "0" (tone_t));
+
+ int32_t sample_value = (int32_t) (v * tone_volume);
+ for (u32 j = 0; j < sound->channels; j++)
+ *ptr++ = sample_value;
+
+ tone_t += 2 * PI / wave_period;
+ if (tone_t >= 2 * PI)
+ tone_t -= 2 * PI;
+ }
+}
+
struct mixer_sound {
- struct sound sound;
+ struct sound *sound;
u32 ctr;
struct list_node list_node;
};
+#define MIXER_MAX_PLAYING_SOUNDS 64
+
struct mixer {
- struct list sounds;
+ struct {
+ struct mixer_sound buffer[MIXER_MAX_PLAYING_SOUNDS];
+ struct list freelist;
+ struct list playing;
+ } sounds;
};
+static inline void
+enqueue_mixer_sound(struct mixer *mixer, struct sound *sound)
+{
+ ASSERT(sound);
+
+ struct list_node *alloc = freelist_alloc(&mixer->sounds.freelist);
+ if (!alloc)
+ return;
+
+ struct mixer_sound *info = FROM_NODE(alloc, struct mixer_sound, list_node);
+
+ info->sound = sound;
+ info->ctr = 0;
+ info->list_node.prev = info->list_node.next = NULL;
+
+ list_push_tail(&mixer->sounds.playing, &info->list_node);
+}
+
+static inline void
+dequeue_mixer_sound(struct mixer *mixer, struct mixer_sound *info)
+{
+ if (info->list_node.prev)
+ info->list_node.prev->next = info->list_node.next;
+
+ if (info->list_node.next)
+ info->list_node.next->prev = info->list_node.prev;
+
+ if (mixer->sounds.playing.head == &info->list_node)
+ mixer->sounds.playing.head = info->list_node.next;
+
+ if (mixer->sounds.playing.tail == &info->list_node)
+ mixer->sounds.playing.tail = info->list_node.prev;
+
+ info->list_node.prev = info->list_node.next = NULL;
+
+ freelist_free(&mixer->sounds.freelist, &info->list_node);
+}
+
struct rts_state {
- s32 tone_hz;
- s32 tone_volume;
- f32 tone_t;
+ struct arena permanent_arena, temporary_arena;
- struct mixer_sound sound_a, sound_b;
+ struct sound sound_a, sound_b;
struct mixer mixer;
};
@@ -57,23 +117,40 @@ RTS_INIT(rts_init)
{
struct rts_state *state = memory->ptr;
- state->tone_hz = 0;
- state->tone_volume = 0;
- state->tone_t = 0;
+ u8 *base = (u8 *) memory->ptr;
+ size_t len = memory->len;
+
+ state->permanent_arena.ptr = base;
+ state->permanent_arena.cap = len / 2;
+ state->permanent_arena.len = sizeof *state;
+
+ state->temporary_arena.ptr = base + len / 2;
+ state->temporary_arena.cap = len / 2;
+ state->temporary_arena.len = 0;
+
+ platform->printf("memory: %p -> %p, permanent arena: %p -> %p, temporary arena: %p -> %p\n",
+ base, base + (len - 1),
+ state->permanent_arena.ptr, state->permanent_arena.ptr + (state->permanent_arena.cap - 1),
+ state->temporary_arena.ptr, state->temporary_arena.ptr + (state->temporary_arena.cap - 1));
- state->sound_a.sound.hz = 256;
- state->sound_a.sound.volume = INT32_MAX / 16;
- state->sound_a.sound.samples= RTS_AUDIO_SAMPLE_RATE;
- state->sound_a.ctr = 0;
- state->sound_a.list_node.prev = state->sound_a.list_node.next = NULL;
+ state->sound_a.channels = RTS_AUDIO_CHANNELS;
+ state->sound_a.frames = RTS_AUDIO_SAMPLE_RATE;
+ state->sound_a.buffer = ALLOC_ARRAY(&state->permanent_arena, s32, state->sound_a.channels * state->sound_a.frames);
+ fill_sound_samples(&state->sound_a, 256, TONE_VOLUME_MAX / 4);
- state->sound_b.sound.hz = 1024;
- state->sound_b.sound.volume = INT32_MAX / 24;
- state->sound_b.sound.samples= RTS_AUDIO_SAMPLE_RATE / 2;
- state->sound_b.ctr = 0;
- state->sound_b.list_node.prev = state->sound_b.list_node.next = NULL;
+ state->sound_b.channels = RTS_AUDIO_CHANNELS;
+ state->sound_b.frames = RTS_AUDIO_SAMPLE_RATE * 2;
+ state->sound_b.buffer = ALLOC_ARRAY(&state->permanent_arena, s32, state->sound_a.channels * state->sound_a.frames);
+ fill_sound_samples(&state->sound_b, 512, TONE_VOLUME_MAX / 4);
- state->mixer.sounds.head = state->mixer.sounds.tail = NULL;
+ state->mixer.sounds.freelist.head = state->mixer.sounds.freelist.tail = NULL;
+ for (size_t i = 0; i < ARRLEN(state->mixer.sounds.buffer); i++)
+ freelist_free(&state->mixer.sounds.freelist, &state->mixer.sounds.buffer[i].list_node);
+
+ state->mixer.sounds.playing.head = state->mixer.sounds.playing.tail = NULL;
+
+ enqueue_mixer_sound(&state->mixer, &state->sound_a);
+ enqueue_mixer_sound(&state->mixer, &state->sound_a);
return true;
}
@@ -87,82 +164,77 @@ RTS_UPDATE(rts_update)
struct rts_state *state = memory->ptr;
if (input->keyboard.buttons[KEYBOARD_Q].was_pressed)
- list_push_tail(&state->mixer.sounds, &state->sound_a.list_node);
+ enqueue_mixer_sound(&state->mixer, &state->sound_a);
if (input->keyboard.buttons[KEYBOARD_E].was_pressed)
- list_push_tail(&state->mixer.sounds, &state->sound_b.list_node);
+ enqueue_mixer_sound(&state->mixer, &state->sound_b);
- if (input->keyboard.buttons[KEYBOARD_R].was_pressed)
- state->mixer.sounds.head = state->mixer.sounds.tail = NULL;
+ if (input->keyboard.buttons[KEYBOARD_R].was_pressed) {
+ LIST_ITER(&state->mixer.sounds.playing)
+ dequeue_mixer_sound(&state->mixer, FROM_NODE(it, struct mixer_sound, list_node));
+
+ state->mixer.sounds.playing.head = state->mixer.sounds.playing.tail = NULL;
+ }
}
-RTS_SAMPLE(rts_sample)
+void
+print_list(struct platform_api *platform, struct list *list)
{
- struct rts_state *state = memory->ptr;
-
- const f32 PI = 3.1415926535f;
+ size_t i = 0;
+ LIST_ITER(list) {
+ platform->printf("\t%zu : { %p, prev: %p, next: %p},\n",
+ i++, it, it->prev, it->next);
+ }
+ platform->printf("\n");
+}
+static void
+mix(s32 *dst, s32 *src, size_t frames, size_t channels)
+{
#if 1
- memset(audio->buffer, 0, audio->samples * RTS_AUDIO_FRAME_BYTES);
+ // TODO: saturating SIMD arithmetic for real mixing
+ for (size_t i = 0; i < frames; i++) {
+ for (size_t j = 0; j < channels; j++) {
+ *dst = *dst + *src++;
+ dst++;
+ }
+ }
+#else
+ memcpy(audio->buffer, src, RTS_AUDIO_FRAME_BYTES * frames);
+#endif
+}
- struct list new_sound_list;
- memset(&new_sound_list, 0, sizeof new_sound_list);
+RTS_SAMPLE(rts_sample)
+{
+ struct rts_state *state = memory->ptr;
+
+ memset(audio->buffer, 0, audio->frames * RTS_AUDIO_FRAME_BYTES);
size_t active_sounds = 0;
- LIST_ITER(&state->mixer.sounds) {
+ LIST_ITER(&state->mixer.sounds.playing) {
struct mixer_sound *sound_info = FROM_NODE(it, struct mixer_sound, list_node);
- u32 available_samples = MIN(audio->samples, sound_info->sound.samples);
- u32 wave_period = audio->sample_rate / sound_info->sound.hz;
+ u32 frames = MIN(audio->frames, sound_info->sound->frames - sound_info->ctr);
- f64 t = 0, hz = sound_info->sound.hz;
- s32 *ptr = audio->buffer;
-
- platform->printf("sound %zu: samples: %u/%u\n",
+ platform->printf("sound %zu: playing: %u, total: %u/%u\n",
active_sounds,
- available_samples, sound_info->sound.samples);
-
-#if 0
- for (size_t i = 0; i < available_samples; i++) {
- f64 v = 0;
- __asm__ volatile ("fsin" : "=t" (v) : "0" (hz));
+ frames, sound_info->ctr, sound_info->sound->frames);
- s32 pcm = v * sound_info->sound.volume;
- for (u32 j = 0; j < audio->channels; j++)
- *ptr++ += pcm;
+ ASSERT(sound_info->sound->channels == audio->channels);
- t += 2 * PI / wave_period;
- if (t >= 2 * PI)
- t -= 2 * PI;
- }
-#endif
+ s32 *dst = audio->buffer;
+ s32 *src = sound_info->sound->buffer + (sound_info->ctr * sound_info->sound->channels);
+ mix(dst, src, frames, audio->channels);
- sound_info->ctr += available_samples;
- if (sound_info->ctr < sound_info->sound.samples) {
+ sound_info->ctr += frames;
+ if (sound_info->ctr < sound_info->sound->frames) {
active_sounds++;
} else {
- // TODO: mark sound as inactive
+ dequeue_mixer_sound(&state->mixer, sound_info);
}
}
platform->printf("active sounds: %zu\n", active_sounds);
-
- state->mixer.sounds = new_sound_list;
-#else
- s32 *sample = audio->buffer;
- for (u32 i = 0; i < audio->samples; i++) {
- f64 v = 0;
- __asm__ volatile ("fsin" : "=t" (v) : "0" (state->tone_t));
-
- int32_t sample_value = (int32_t) (v * state->tone_volume);
- for (u32 j = 0; j < audio->channels; j++)
- *sample++ = sample_value;
-
- state->tone_t += 2 * PI / wave_period;
- if (state->tone_t >= 2 * PI)
- state->tone_t -= 2 * PI;
- }
-#endif
}
RTS_RENDER(rts_render)