rts

rts.git
git clone git://git.lenczewski.org/rts.git
Log | Files | Refs

commit 72329c47a394fd2980694abab1a076fb38d3a499
parent 973b00e231554a33e79b152c324301c13ce5414e
Author: MikoĊ‚aj Lenczewski <mblenczewski@gmail.com>
Date:   Mon,  3 Mar 2025 00:39:34 +0000

Prototype sound mixer

Diffstat:
M.gitignore | 2++
Adebug.sh | 5+++++
Mplatform/linux.c | 20++++++++++----------
Mplatform/renderer.c | 4+++-
Mrts/api.h | 45+++++++++++++++++++++++++++++++++++++++------
Mrts/rts.c | 230++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
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)