starfield

starfield.git
git clone git://git.lenczewski.org/starfield.git
Log | Files | Refs | Submodules | README | LICENSE

commit 3356dd454a8be73f353319b038e4e5f9f1df99ec
parent 48e5f70fd4904cb9b6c9b7b4251e304aa870cdc2
Author: MikoĊ‚aj Lenczewski <mblenczewski@gmail.com>
Date:   Thu,  9 May 2024 00:50:33 +0000

Implement prototype alsa support

Diffstat:
Mbuild_linux.sh | 2+-
Mcommon/starfield_api.h | 44++++++++++++++++++++++++++++++++++++++++++--
Adebug_linux.sh | 7+++++++
Mplatform/linux_starfield.c | 302+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mplatform/linux_starfield.h | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mstarfield/starfield.c | 21+++++++++++++++++++++
6 files changed, 443 insertions(+), 57 deletions(-)

diff --git a/build_linux.sh b/build_linux.sh @@ -16,4 +16,4 @@ $CC -shared -o bin/starfield.so starfield/starfield.c \ # TODO: how to build a static binary that supports loading starfield.so? $CC -o bin/linux_starfield platform/linux_starfield.c \ - $WARNINGS $CFLAGS $CPPFLAGS + $WARNINGS $CFLAGS $CPPFLAGS -lc -ldl diff --git a/common/starfield_api.h b/common/starfield_api.h @@ -39,6 +39,10 @@ struct str_t { #define GiB (1024 * MiB) #define TiB (1024 * GiB) +#define MSECS (1000ULL) +#define USECS (MSECS * 1000) +#define NSECS (USECS * 1000) + #define ARRLEN(arr) (sizeof (arr) / sizeof (arr)[0]) #define MIN(a, b) ((a) < (b) ? (a) : (b)) @@ -105,6 +109,29 @@ struct starfield_arena { u64 cap, len; }; +inline void +arena_reset(struct starfield_arena *arena) +{ + arena->len = 0; +} + +inline void * +arena_alloc(struct starfield_arena *arena, u64 size, u64 alignment) +{ + u64 aligned_len = ALIGN_NEXT(arena->len, alignment); + + if (arena->cap < aligned_len + size) + return NULL; + + void *ptr = (u8 *) arena->ptr + aligned_len; + arena->len = aligned_len + size; + + return ptr; +} + +#define PUSH_SIZED(arena, T) arena_alloc(arena, sizeof(T), alignof(T)) +#define PUSH_ARRAY(arena, T, n) arena_alloc(arena, sizeof(T) * (n), alignof(T)) + struct starfield_memory { struct starfield_arena permanent, temporary; }; @@ -161,17 +188,30 @@ struct starfield_input { struct starfield_keyboard_input keyboard; }; +#define STARFIELD_VIDEO_HRES 1024 // 1920 +#define STARFIELD_VIDEO_VRES 576 // 1080 + +#define STARFIELD_VIDEO_BYTES_PER_PIXEL 4 + struct starfield_video_buffer { void *buffer; u32 width, height; u32 pitch; }; +#define STARFIELD_AUDIO_CHANNELS 2 +#define STARFIELD_AUDIO_SAMPLE_RATE 48000 +#define STARFIELD_AUDIO_SAMPLE_BITS 32 + +#define STARFIELD_AUDIO_FRAME_BYTES \ + (STARFIELD_AUDIO_CHANNELS * (STARFIELD_AUDIO_SAMPLE_BITS / 8)) + struct starfield_audio_buffer { void *buffer; - u32 sample_rate; u32 channels; - u32 expected_samples; + u32 sample_rate; + u32 sample_bits; + u32 expected_frames; }; #define STARFIELD_INIT(name) \ diff --git a/debug_linux.sh b/debug_linux.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +ROOT="$(dirname $0)" + +set -ex + +lldb -- $ROOT/bin/linux_starfield $@ diff --git a/platform/linux_starfield.c b/platform/linux_starfield.c @@ -1,6 +1,6 @@ #include "linux_starfield.h" -internal struct linux_starfield_state state; +internal struct linux_state state; int main(int argc, char **argv, char **envp) @@ -9,18 +9,6 @@ main(int argc, char **argv, char **envp) (void) argc; (void) envp; - struct platform_api platform_api = load_platform_api(); - - struct starfield_thread thread = { - .id = 0, - }; - - struct starfield_memory memory; - if (!init_starfield_memory(256 * MiB, 256 * MiB, &memory)) { - fprintf(stderr, "Failed to allocate starfield memory\n"); - _exit(1); - } - /* NOTE: we expect the library to be alongside our platform-specific * executable. the unix loader does not search this path by default, * so we build the absolute path to the library here as a workaround @@ -29,49 +17,159 @@ main(int argc, char **argv, char **envp) strcpy(starfield_library_path, argv[0]); strcpy(strrchr(starfield_library_path, '/'), "/starfield.so"); - struct starfield_api starfield_api; - if (!load_starfield_api(starfield_library_path, &starfield_api)) { + if (!load_starfield_api(starfield_library_path, &state.starfield_api)) { fprintf(stderr, "Failed to load starfield library: %s\n", starfield_library_path); _exit(1); } - starfield_api.init(&thread, &memory, &platform_api); + state.platform_api = load_platform_api(); + + struct starfield_thread thread = { + .id = 0, + }; + + if (!init_memory(&state.platform_memory, 64 * MiB, 64 * MiB)) { + fprintf(stderr, "Failed to initialise platform memory\n"); + _exit(1); + } + + if (!init_memory(&state.starfield_memory, 64 * MiB, 64 * MiB)) { + fprintf(stderr, "Failed to initialise starfield memory\n"); + _exit(1); + } + + f32 target_render_rate = 60.0, target_physics_rate = 60.0; + + u32 target_us_per_frame = USECS / target_render_rate; + u32 target_us_per_tick = USECS / target_physics_rate; + + (void) target_us_per_tick; + + u32 period_us = target_us_per_frame, buffered_periods = 2; + if (!init_audio(&state.audio, period_us, buffered_periods)) { + fprintf(stderr, "Failed to initialise platform audio\n"); + } + + if (!init_video(&state.video)) { + fprintf(stderr, "Failed to initialise platform video\n"); + _exit(1); + } + + state.starfield_api.init(&thread, &state.starfield_memory, &state.platform_api); state.running = 1; + + f32 dt = 1.0f / target_render_rate; while (state.running) { - f32 dt = 0; + struct timespec start_nanos, end_nanos; + clock_gettime(CLOCK_MONOTONIC, &start_nanos); struct starfield_input input = { 0, }; - starfield_api.update(&thread, &memory, &platform_api, &input, dt); + state.starfield_api.update(&thread, &state.starfield_memory, + &state.platform_api, &input, dt); struct starfield_video_buffer video = { 0, }; - starfield_api.render(&thread, &memory, &platform_api, &video, dt); + state.starfield_api.render(&thread, &state.starfield_memory, + &state.platform_api, &video, dt); - struct starfield_audio_buffer audio = { - 0, - }; + if (state.audio.enabled) { + state.starfield_api.sample(&thread, &state.starfield_memory, + &state.platform_api, &state.audio.buffer, dt); + +#ifdef STARFIELD_DEBUG + /* TODO: temporary test tone */ + s32 tone_hz = 256; + s32 tone_volume = INT32_MAX / 16; + func_local f32 tone_t = 0.0f; + + const f32 PI = 3.1415926535f; + const u32 wave_period = state.audio.buffer.sample_rate / tone_hz; + + s32 *sample = state.audio.buffer.buffer; + for (u32 i = 0; i < state.audio.buffer.expected_frames; i++) { + f32 v = sinf(tone_t); + + int32_t sample_value = (int32_t) (v * tone_volume); + for (u32 j = 0; j < state.audio.buffer.channels; j++) + *sample++ = sample_value; + + tone_t += 2 * PI / wave_period; + if (tone_t >= 2 * PI) + tone_t -= 2 * PI; + } +#endif + + play_audio(&state.audio); + } + + clock_gettime(CLOCK_MONOTONIC, &end_nanos); - starfield_api.sample(&thread, &memory, &platform_api, &audio, dt); + u64 elapsed_us = linux_elapsed_ns(&start_nanos, &end_nanos) / 1000; + if (elapsed_us < target_us_per_frame) { + struct timespec req = { + .tv_sec = 0, + .tv_nsec = (target_us_per_frame - elapsed_us) * 1000, + }; - sleep(1); + while (nanosleep(&req, &req) < 0); + + clock_gettime(CLOCK_MONOTONIC, &end_nanos); + elapsed_us = linux_elapsed_ns(&start_nanos, &end_nanos) / 1000; + } else { + /* TODO: missed frame */ + } + + dt = (f32) elapsed_us / USECS; } - starfield_api.free(&thread, &memory, &platform_api); + state.starfield_api.free(&thread, &state.starfield_memory, &state.platform_api); _exit(0); } -b32 -init_starfield_memory(u64 permanent_memory_cap, u64 temporary_memory_cap, - struct starfield_memory *memory) +internal u64 +linux_elapsed_ns(struct timespec *restrict start, struct timespec *restrict end) +{ + return ((end->tv_sec - start->tv_sec) * NSECS + (end->tv_nsec - start->tv_nsec)); +} + +#define LOAD_SYMBOL(loc, T, sym) \ + ((loc) = (T *) dlsym(library, sym)) + +internal b32 +load_starfield_api(char const *filename, struct starfield_api *api) +{ + void *library = dlopen(filename, RTLD_NOW); + if (!library) return false; + + if (!LOAD_SYMBOL(api->init, starfield_api_init_t, "starfield_init")) + return false; + + if (!LOAD_SYMBOL(api->update, starfield_api_update_t, "starfield_update")) + return false; + + if (!LOAD_SYMBOL(api->render, starfield_api_render_t, "starfield_render")) + return false; + + if (!LOAD_SYMBOL(api->sample, starfield_api_sample_t, "starfield_sample")) + return false; + + if (!LOAD_SYMBOL(api->free, starfield_api_free_t, "starfield_free")) + return false; + + return true; +} + +internal b32 +init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 temporary_memory_cap) { -#if STARFIELD_DEBUG +#ifdef STARFIELD_DEBUG void *base_addr = (void *) (2 * TiB); #else void *base_addr = NULL; @@ -79,13 +177,19 @@ init_starfield_memory(u64 permanent_memory_cap, u64 temporary_memory_cap, u64 len = permanent_memory_cap + temporary_memory_cap; - /* TODO: add hugetlb support? seems to require a kernel commandline arg */ - void *ptr = mmap(base_addr, len, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, - -1, 0); + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + +#ifdef STARFIELD_DEBUG + flags |= MAP_FIXED; +#endif + + void *ptr = mmap(base_addr, len, prot, flags | MAP_HUGETLB | MAP_HUGE_2MB, -1, 0); + if (ptr == MAP_FAILED) + ptr = mmap(base_addr, len, prot, flags, -1, 0); - if (ptr == MAP_FAILED) return false; + if (ptr == MAP_FAILED) + return false; /* TODO: can we make this faster? can we avoid this entirely? */ memset(base_addr, 0, len); @@ -101,27 +205,130 @@ init_starfield_memory(u64 permanent_memory_cap, u64 temporary_memory_cap, return true; } -b32 -load_starfield_api(char const *filename, struct starfield_api *api) +internal b32 +init_audio(struct linux_audio *audio, u32 period_us, u32 buffered_periods) { - void *library = dlopen(filename, RTLD_LAZY); + audio->enabled = false; + + void *library = dlopen("libasound.so", RTLD_NOW); if (!library) return false; - if (!(api->init = (starfield_api_init_t *) dlsym(library, "starfield_init"))) + if (!LOAD_SYMBOL(audio->api.snd_strerror, alsa_snd_strerror_t, "snd_strerror")) + return false; + + if (!LOAD_SYMBOL(audio->api.snd_pcm_open, alsa_snd_pcm_open_t, "snd_pcm_open")) + return false; + + if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_sizeof, + alsa_snd_pcm_hw_params_sizeof_t, + "snd_pcm_hw_params_sizeof")) + return false; + + if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_any, + alsa_snd_pcm_hw_params_any_t, + "snd_pcm_hw_params_any")) + return false; + + if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_access, + alsa_snd_pcm_hw_params_set_access_t, + "snd_pcm_hw_params_set_access")) + return false; + + if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_format, + alsa_snd_pcm_hw_params_set_format_t, + "snd_pcm_hw_params_set_format")) return false; - if (!(api->update = (starfield_api_update_t *) dlsym(library, "starfield_update"))) + if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_channels, + alsa_snd_pcm_hw_params_set_channels_t, + "snd_pcm_hw_params_set_channels")) return false; - if (!(api->render = (starfield_api_render_t *) dlsym(library, "starfield_render"))) + if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_rate, + alsa_snd_pcm_hw_params_set_rate_t, + "snd_pcm_hw_params_set_rate")) return false; - if (!(api->sample = (starfield_api_sample_t *) dlsym(library, "starfield_sample"))) + if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_periods, + alsa_snd_pcm_hw_params_set_periods_t, + "snd_pcm_hw_params_set_periods")) return false; - if (!(api->free = (starfield_api_free_t *) dlsym(library, "starfield_free"))) + if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_period_time, + alsa_snd_pcm_hw_params_set_period_time_t, + "snd_pcm_hw_params_set_period_time")) return false; + if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params, + alsa_snd_pcm_hw_params_t, + "snd_pcm_hw_params")) + return false; + + if (!LOAD_SYMBOL(audio->api.snd_pcm_writei, + alsa_snd_pcm_writei_t, + "snd_pcm_writei")) + return false; + + /* initialise audio */ + int err; + if ((err = audio->api.snd_pcm_open(&audio->handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "snd_pcm_open: %s\n", audio->api.snd_strerror(err)); + return false; + } + + snd_pcm_hw_params_t *hw_params = alloca(audio->api.snd_pcm_hw_params_sizeof()); + assert(hw_params); + + audio->api.snd_pcm_hw_params_any(audio->handle, hw_params); + audio->api.snd_pcm_hw_params_set_access(audio->handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + audio->api.snd_pcm_hw_params_set_format(audio->handle, hw_params, SND_PCM_FORMAT_S32_LE); + audio->api.snd_pcm_hw_params_set_channels(audio->handle, hw_params, STARFIELD_AUDIO_CHANNELS); + audio->api.snd_pcm_hw_params_set_rate(audio->handle, hw_params, STARFIELD_AUDIO_SAMPLE_RATE, 0); + audio->api.snd_pcm_hw_params_set_periods(audio->handle, hw_params, buffered_periods, 0); + audio->api.snd_pcm_hw_params_set_period_time(audio->handle, hw_params, period_us, 0); + + if ((err = audio->api.snd_pcm_hw_params(audio->handle, hw_params)) < 0) { + fprintf(stderr, "and_pcm_hw_params: %s\n", audio->api.snd_strerror(err)); + return false; + } + + u64 frames = (period_us * STARFIELD_AUDIO_SAMPLE_RATE * STARFIELD_AUDIO_FRAME_BYTES) / USECS; + +#ifdef STARFIELD_DEBUG + fprintf(stderr, "Audio buffer period (us): %" PRIu32 "\n", period_us); + fprintf(stderr, "Audio buffer frames: %" PRIu64 "\n", frames); +#endif + + audio->buffer.buffer = PUSH_ARRAY(&state.platform_memory.permanent, s32, frames); + assert(audio->buffer.buffer); + + audio->buffer.channels = STARFIELD_AUDIO_CHANNELS; + audio->buffer.sample_rate = STARFIELD_AUDIO_SAMPLE_RATE; + audio->buffer.sample_bits = STARFIELD_AUDIO_SAMPLE_BITS; + audio->buffer.expected_frames = frames; + + audio->enabled = true; + + return true; +} + +internal void +play_audio(struct linux_audio *audio) +{ + void *buf = audio->buffer.buffer; + u32 frames = audio->buffer.expected_frames; + + int err; + if ((err = audio->api.snd_pcm_writei(audio->handle, buf, frames)) < 0) { + fprintf(stderr, "snd_pcm_write: %s\n", audio->api.snd_strerror(err)); + } +} + +internal b32 +init_video(struct linux_video *video) +{ + (void) video; + return true; } @@ -154,7 +361,7 @@ DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUG_platform_free_file_memory) #endif -struct platform_api +internal struct platform_api load_platform_api(void) { return (struct platform_api) { @@ -164,4 +371,9 @@ load_platform_api(void) .DEBUG_free_file_memory = DEBUG_platform_free_file_memory, #endif }; -} + +/* utils + * =========================================================================== + */ + +#include "starfield_api_inlines.c" diff --git a/platform/linux_starfield.h b/platform/linux_starfield.h @@ -2,8 +2,8 @@ #define LINUX_STARFIELD_H #define _XOPEN_SOURCE 700 - -#include "starfield_api.h" +#define _GNU_SOURCE +#define _DEFAULT_SOURCE #include <stdio.h> #include <string.h> @@ -13,22 +13,128 @@ #include <dlfcn.h> +#include <alsa/asoundlib.h> + +#include <wayland-client.h> + +#include "starfield_api.h" + struct starfield_thread { u32 id; }; -struct linux_starfield_state { +/* alsa state */ +#define ALSA_SND_STRERROR(name) \ + char const *name(int) + +#define ALSA_SND_PCM_OPEN(name) \ + int name(snd_pcm_t **, char const *, snd_pcm_stream_t, int) + +#define ALSA_SND_PCM_HW_PARAMS_SIZEOF(name) \ + size_t name(void) + +#define ALSA_SND_PCM_HW_PARAMS_ANY(name) \ + int name(snd_pcm_t *, snd_pcm_hw_params_t *) + +#define ALSA_SND_PCM_HW_PARAMS_SET_ACCESS(name) \ + int name(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t) + +#define ALSA_SND_PCM_HW_PARAMS_SET_FORMAT(name) \ + int name(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t) + +#define ALSA_SND_PCM_HW_PARAMS_SET_CHANNELS(name) \ + int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int) + +#define ALSA_SND_PCM_HW_PARAMS_SET_RATE(name) \ + int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int) + +#define ALSA_SND_PCM_HW_PARAMS_SET_PERIODS(name) \ + int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int) + +#define ALSA_SND_PCM_HW_PARAMS_SET_PERIOD_TIME(name) \ + int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int) + +#define ALSA_SND_PCM_HW_PARAMS(name) \ + int name(snd_pcm_t *, snd_pcm_hw_params_t *) + +#define ALSA_SND_PCM_WRITEI(name) \ + int name(snd_pcm_t *, void const *, snd_pcm_uframes_t) + +typedef ALSA_SND_STRERROR(alsa_snd_strerror_t); +typedef ALSA_SND_PCM_OPEN(alsa_snd_pcm_open_t); +typedef ALSA_SND_PCM_HW_PARAMS_SIZEOF(alsa_snd_pcm_hw_params_sizeof_t); +typedef ALSA_SND_PCM_HW_PARAMS_ANY(alsa_snd_pcm_hw_params_any_t); +typedef ALSA_SND_PCM_HW_PARAMS_SET_ACCESS(alsa_snd_pcm_hw_params_set_access_t); +typedef ALSA_SND_PCM_HW_PARAMS_SET_FORMAT(alsa_snd_pcm_hw_params_set_format_t); +typedef ALSA_SND_PCM_HW_PARAMS_SET_CHANNELS(alsa_snd_pcm_hw_params_set_channels_t); +typedef ALSA_SND_PCM_HW_PARAMS_SET_RATE(alsa_snd_pcm_hw_params_set_rate_t); +typedef ALSA_SND_PCM_HW_PARAMS_SET_PERIODS(alsa_snd_pcm_hw_params_set_periods_t); +typedef ALSA_SND_PCM_HW_PARAMS_SET_PERIOD_TIME(alsa_snd_pcm_hw_params_set_period_time_t); +typedef ALSA_SND_PCM_HW_PARAMS(alsa_snd_pcm_hw_params_t); +typedef ALSA_SND_PCM_WRITEI(alsa_snd_pcm_writei_t); + +struct linux_audio { + /* api reference: + * https://www.alsa-project.org/alsa-doc/alsa-lib/modules.html + */ + struct { + alsa_snd_strerror_t *snd_strerror; + alsa_snd_pcm_open_t *snd_pcm_open; + alsa_snd_pcm_hw_params_sizeof_t *snd_pcm_hw_params_sizeof; + alsa_snd_pcm_hw_params_any_t *snd_pcm_hw_params_any; + alsa_snd_pcm_hw_params_set_access_t *snd_pcm_hw_params_set_access; + alsa_snd_pcm_hw_params_set_format_t *snd_pcm_hw_params_set_format; + alsa_snd_pcm_hw_params_set_channels_t *snd_pcm_hw_params_set_channels; + alsa_snd_pcm_hw_params_set_rate_t *snd_pcm_hw_params_set_rate; + alsa_snd_pcm_hw_params_set_periods_t *snd_pcm_hw_params_set_periods; + alsa_snd_pcm_hw_params_set_period_time_t *snd_pcm_hw_params_set_period_time; + alsa_snd_pcm_hw_params_t *snd_pcm_hw_params; + alsa_snd_pcm_writei_t *snd_pcm_writei; + } api; + + snd_pcm_t *handle; + + struct starfield_audio_buffer buffer; + + b32 enabled; +}; + +/* wayland state */ +struct linux_video { + u8 pad; +}; + +struct linux_state { + struct starfield_memory platform_memory, starfield_memory; + + struct linux_audio audio; + struct linux_video video; + + struct platform_api platform_api; + struct starfield_api starfield_api; + b32 running; }; -struct platform_api +internal u64 +linux_elapsed_ns(struct timespec *restrict start, struct timespec *restrict end); + +internal b32 +load_starfield_api(char const *filename, struct starfield_api *api); + +internal struct platform_api load_platform_api(void); -b32 -init_starfield_memory(u64 permanent_memory_cap, u64 temporary_memory_cap, - struct starfield_memory *memory); +internal b32 +init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 temporary_memory_cap); -b32 -load_starfield_api(char const *filename, struct starfield_api *api); +internal b32 +init_audio(struct linux_audio *audio, u32 period_us, u32 buffered_periods); + +internal void +play_audio(struct linux_audio *audio); + +internal b32 +init_video(struct linux_video *video); #endif /* LINUX_STARFIELD_H */ diff --git a/starfield/starfield.c b/starfield/starfield.c @@ -36,6 +36,27 @@ STARFIELD_SAMPLE(starfield_sample) (void) platform; (void) audio; (void) dt; + + /* TODO: temporary test tone */ + s32 tone_hz = 256; + s32 tone_volume = INT32_MAX / 16; + func_local f32 tone_t = 0.0f; + + const f32 PI = 3.1415926535f; + const u32 wave_period = audio->sample_rate / tone_hz; + + s32 *sample = audio->buffer; + for (u32 i = 0; i < audio->expected_frames; i++) { + f32 v = 0.0; // sinf(tone_t); + + int32_t sample_value = (int32_t) (v * tone_volume); + for (u32 j = 0; j < audio->channels; j++) + *sample++ = sample_value; + + tone_t += 2 * PI / wave_period; + if (tone_t >= 2 * PI) + tone_t -= 2 * PI; + } } global