diff options
author | Mitya Selivanov <automainint@guattari.tech> | 2024-11-17 19:00:08 +0100 |
---|---|---|
committer | Mitya Selivanov <automainint@guattari.tech> | 2024-11-17 19:00:08 +0100 |
commit | f99fd0ce62375bd0f994861768c36de6920e822e (patch) | |
tree | bc5f37b18d778160b71176a38f784efa71ff59d1 | |
parent | 94598aa337e0fd02a4bfd38929acfdb8296ee23b (diff) | |
download | reduced_system_layer-f99fd0ce62375bd0f994861768c36de6920e822e.zip |
Add ALSA impl; Sine Wave example
-rwxr-xr-x | examples/sinewave.c | 58 | ||||
-rwxr-xr-x | reduced_system_layer.c | 272 |
2 files changed, 276 insertions, 54 deletions
diff --git a/examples/sinewave.c b/examples/sinewave.c new file mode 100755 index 0000000..6beed29 --- /dev/null +++ b/examples/sinewave.c @@ -0,0 +1,58 @@ +#if 0 /* +#/ ================================================================ +#/ +#/ sinewave.c +#/ +#/ ================================================================ +#/ +#/ Self-compilation shell script +#/ +SRC=${0##*./} +BIN=${SRC%.*} +gcc \ + -Wall -Wextra -Werror -pedantic \ + -Wno-old-style-declaration \ + -Wno-missing-braces \ + -Wno-unused-variable \ + -Wno-unused-but-set-variable \ + -Wno-unused-parameter \ + -Wno-overlength-strings \ + -O3 \ + -fsanitize=undefined,address,leak \ + -lX11 -lm -lasound \ + -o $BIN $SRC && \ + ./$BIN $@ && rm $BIN +exit $? # */ +#endif + +#include "../reduced_system_layer.c" + +void update_and_render_frame(void) { + // Nothing +} + +i32 main(i32 argc, c8 **argv) { + (void) argc; + (void) argv; + + platform.graceful_exit = 1; + + f64 frequency = 440. * 4; + + f32 frames[AUDIO_SAMPLE_RATE * AUDIO_NUM_CHANNELS]; + for (i64 i = 0; i < AUDIO_SAMPLE_RATE; ++i) { + f64 t = ((f64) i) / AUDIO_SAMPLE_RATE; + f64 x = sin(t * frequency); + if (t < .1) + x *= t / .1; + if (t > .7) + x *= (1. - t) / .3; + frames[i * 2] = (f32) x * .5; + frames[i * 2 + 1] = (f32) x * .5; + } + + p_queue_sound(0, AUDIO_SAMPLE_RATE, frames); + p_handle_audio(AUDIO_SAMPLE_RATE); + p_cleanup(); + return 0; +} diff --git a/reduced_system_layer.c b/reduced_system_layer.c index 7b18780..953a8c1 100755 --- a/reduced_system_layer.c +++ b/reduced_system_layer.c @@ -40,12 +40,15 @@ #/ - Wayland #/ - Windows graphics #/ - Sound -#/ - ALSA -- https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_min_8c-example.html #/ - Windows audio #/ - WebAssembly audio +#/ - Recording #/ - Networking +#/ - TCP #/ - Windows sockets #/ - Web Sockets +#/ - Logging +#/ - Parsing #/ #/ Done #/ @@ -54,12 +57,17 @@ #/ - UI #/ - Particles #/ - Graph +#/ - Sine Wave #/ - Graphics #/ - Font #/ - X11 #/ - WebAssembly +#/ - Sound +#/ - ALSA #/ - Networking #/ - Unix UDP sockets +#/ - UTF-8 +#/ - Testing library #/ #/ ---------------------------------------------------------------- #/ @@ -82,7 +90,7 @@ gcc \ -O3 \ -fsanitize=undefined,address,leak \ -D REDUCED_SYSTEM_LAYER_EXAMPLE \ - -lX11 -lm \ + -lX11 -lm -lasound \ -o $BIN $SRC && \ ./$BIN $@ && rm $BIN exit $? # */ @@ -141,15 +149,17 @@ extern "C" { #endif enum { - MAX_NUM_PIXELS = 10 * 1024 * 1024, - MAX_INPUT_SIZE = 256, - MAX_CLIPBOARD_SIZE = 10 * 1024 * 1024, - MAX_NUM_AUDIO_SAMPLES = 0, - MAX_NUM_SOCKETS = 64, - MAX_NUM_KEYS = 512, + MAX_NUM_PIXELS = 10 * 1024 * 1024, + MAX_INPUT_SIZE = 256, + MAX_CLIPBOARD_SIZE = 10 * 1024 * 1024, + MAX_NUM_SOCKETS = 64, + MAX_NUM_KEYS = 512, AUDIO_NUM_CHANNELS = 2, AUDIO_SAMPLE_RATE = 44100, + AUDIO_AVAIL_MIN = 64, + + MAX_NUM_AUDIO_FRAMES = 10 * AUDIO_SAMPLE_RATE * AUDIO_NUM_CHANNELS, // 10 seconds IPv4_UDP = 1, IPv6_UDP = 2, @@ -352,8 +362,8 @@ void p_event_loop(void); void p_clipboard_write(i64 size, c8 *data); // Sound -void p_handle_audio(i64 time_elapsed); -void p_queue_sound(i64 delay, i64 num_samples, f32 *samples); +void p_handle_audio(i64 samples_elapsed); +void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames); // UDP sockets i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port, IP_Address *remote_address); @@ -590,18 +600,18 @@ typedef struct { IP_Address address; } Socket_Slot; -b8 _sockets_init = 0; +b8 _sockets_ready = 0; Socket_Slot _sockets[MAX_NUM_SOCKETS] = {0}; -void sockets_initialize(void) { - if (_sockets_init) +static void sockets_init(void) { + if (_sockets_ready) return; signal(SIGPIPE, SIG_IGN); - _sockets_init = 1; + _sockets_ready = 1; } -void sockets_cleanup(void) { +static void sockets_cleanup(void) { for (i64 i = 0; i < MAX_NUM_SOCKETS; ++i) if (_sockets[i].ready) { close(_sockets[i].socket); @@ -609,8 +619,8 @@ void sockets_cleanup(void) { } } -b8 sockets_open(u16 slot, IP_Address address, u16 *local_port) { - sockets_initialize(); +static b8 sockets_open(u16 slot, IP_Address address, u16 *local_port) { + sockets_init(); b8 change_address = !_sockets[slot].ready @@ -687,12 +697,12 @@ b8 sockets_open(u16 slot, IP_Address address, u16 *local_port) { i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port, IP_Address *remote_address) { if (slot >= MAX_NUM_SOCKETS) { - fprintf(stderr, "%s:%d, %s: Invalid slot %d.\n", __FILE__, __LINE__, __FUNCTION__, (i32) (u32) slot); + fprintf(stderr, "%s:%d, %s: Invalid slot %d.\n", __FILE__, __LINE__, __func__, (i32) (u32) slot); return 0; } if (address.protocol != IPv4_UDP && address.protocol != IPv6_UDP) { - fprintf(stderr, "%s:%d, %s: Invalid address protocol %d.\n", __FILE__, __LINE__, __FUNCTION__, (i32) (u32) address.protocol); + fprintf(stderr, "%s:%d, %s: Invalid address protocol %d.\n", __FILE__, __LINE__, __func__, (i32) (u32) address.protocol); return 0; } @@ -728,7 +738,7 @@ i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port, IP if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; - fprintf(stderr, "%s:%d, %s: recvfrom failed (errno %d).\n", __FILE__, __LINE__, __FUNCTION__, errno); + fprintf(stderr, "%s:%d, %s: recvfrom failed (errno %d).\n", __FILE__, __LINE__, __func__, errno); return 0; } @@ -750,12 +760,12 @@ i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port, IP i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port) { if (slot >= MAX_NUM_SOCKETS) { - fprintf(stderr, "%s:%d, %s: Invalid slot %d.\n", __FILE__, __LINE__, __FUNCTION__, (i32) (u32) slot); + fprintf(stderr, "%s:%d, %s: Invalid slot %d.\n", __FILE__, __LINE__, __func__, (i32) (u32) slot); return 0; } if (address.protocol != IPv4_UDP && address.protocol != IPv6_UDP) { - fprintf(stderr, "%s:%d, %s: Invalid address protocol %d.\n", __FILE__, __LINE__, __FUNCTION__, (i32) (u32) address.protocol); + fprintf(stderr, "%s:%d, %s: Invalid address protocol %d.\n", __FILE__, __LINE__, __func__, (i32) (u32) address.protocol); return 0; } @@ -802,7 +812,7 @@ i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port) { if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; - fprintf(stderr, "%s:%d, %s: sendto failed (errno %d).\n", __FILE__, __LINE__, __FUNCTION__, errno); + fprintf(stderr, "%s:%d, %s: sendto failed (errno %d).\n", __FILE__, __LINE__, __func__, errno); return 0; } @@ -813,6 +823,181 @@ i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port) { // ================================================================ // +// ALSA +// +// ================================================================ + +#if defined(__linux__) + +#include <alsa/asoundlib.h> + +static b8 _sound_ready = 0; +static i64 _sound_position = 0; +static f32 _sound_ring[MAX_NUM_AUDIO_FRAMES] = {0}; +static snd_pcm_t *_sound_out = NULL; + +static void sound_init(void) { + if (_sound_ready) + return; + + _sound_position = 0; + + i32 s; + + s = snd_pcm_open(&_sound_out, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (s < 0) { + fprintf(stderr, "%s:%d, %s: snd_pcm_open failed: %s", __FILE__, __LINE__, __func__, snd_strerror(s)); + return; + } + + snd_pcm_hw_params_t *hw_params; + snd_pcm_sw_params_t *sw_params; + + snd_pcm_hw_params_alloca(&hw_params); + + s = snd_pcm_hw_params_any(_sound_out, hw_params); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_hw_params_any failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_hw_params_set_access(_sound_out, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_hw_params_set_access failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_hw_params_set_format(_sound_out, hw_params, SND_PCM_FORMAT_FLOAT_LE); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_hw_params_set_format failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_hw_params_set_rate(_sound_out, hw_params, AUDIO_SAMPLE_RATE, 0); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_hw_params_set_rate failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_hw_params_set_channels(_sound_out, hw_params, AUDIO_NUM_CHANNELS); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_hw_params_set_channels failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_hw_params(_sound_out, hw_params); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_hw_params failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + snd_pcm_sw_params_alloca(&sw_params); + + s = snd_pcm_sw_params_current(_sound_out, sw_params); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_sw_params_current failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_sw_params_set_avail_min(_sound_out, sw_params, AUDIO_AVAIL_MIN); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_sw_params_set_avail_min failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_sw_params(_sound_out, sw_params); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_sw_params failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_prepare(_sound_out); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_prepare failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + _sound_ready = 1; +} + +static void sound_cleanup(void) { + if (!_sound_ready) + return; + + i32 s; + + s = snd_pcm_nonblock(_sound_out, 0); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_nonblock failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_drain(_sound_out); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_drain failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + // FIXME Memory leaks, seems to be an ALSA bug. + // snd_pcm_close(_sound_out); + // snd_config_update_free_global(); + + _sound_ready = 0; +} + +void p_handle_audio(i64 samples_elapsed) { + sound_init(); + + i64 num_frames = samples_elapsed * AUDIO_NUM_CHANNELS; + + if (num_frames > MAX_NUM_AUDIO_FRAMES) { + fprintf(stderr, "%s:%d, %s: Sound buffer overflow.\n", __FILE__, __LINE__, __func__); + num_frames = MAX_NUM_AUDIO_FRAMES; + } + + i32 s; + + if (num_frames <= MAX_NUM_AUDIO_FRAMES - _sound_position) { + s = snd_pcm_writei(_sound_out, _sound_ring + _sound_position, num_frames); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_writei failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + memset(_sound_ring + _sound_position, 0, num_frames); + _sound_position += num_frames; + } else { + i64 part_one = MAX_NUM_AUDIO_FRAMES - _sound_position; + i64 part_two = num_frames - part_one; + + s = snd_pcm_writei(_sound_out, _sound_ring + _sound_position, part_one); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_writei failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + s = snd_pcm_writei(_sound_out, _sound_ring, part_two); + if (s < 0) + fprintf(stderr, "%s:%d, %s: snd_pcm_writei failed: %s\n", __FILE__, __LINE__, __func__, snd_strerror(s)); + + memset(_sound_ring + _sound_position, 0, part_one); + memset(_sound_ring, 0, part_two); + _sound_position = part_two; + } +} + +void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { + if (num_samples < 0) + fprintf(stderr, "%s:%d, %s: Invalid num samples %lld.", __FILE__, __LINE__, __func__, num_samples); + if (frames == NULL) + return; + + if (delay_in_samples < 0) { + num_samples -= delay_in_samples; + delay_in_samples = 0; + } + + if (num_samples <= 0) + return; + + i64 num_frames = num_samples * AUDIO_NUM_CHANNELS; + + if (num_frames > MAX_NUM_AUDIO_FRAMES) { + fprintf(stderr, "%s:%d, %s: Sound buffer overflow.\n", __FILE__, __LINE__, __func__); + return; + } + + sound_init(); + + i64 begin = (_sound_position + delay_in_samples) % MAX_NUM_AUDIO_FRAMES; + + if (num_frames <= MAX_NUM_AUDIO_FRAMES - begin) + memcpy(_sound_ring + begin, frames, num_frames * sizeof *frames); + else { + i64 part_one = MAX_NUM_AUDIO_FRAMES - begin; + i64 part_two = num_frames - part_one; + + memcpy(_sound_ring + begin, frames, part_one * sizeof *frames); + memcpy(_sound_ring, frames + part_one, part_two * sizeof *frames); + } +} + +#endif // defined(__linux__) + +// ================================================================ +// // X11 // // ================================================================ @@ -844,7 +1029,7 @@ void p_init(void) { _display = XOpenDisplay(NULL); if (_display == NULL) { - fprintf(stderr, "%s:%d, %s: XOpenDisplay failed.\n", __FILE__, __LINE__, __FUNCTION__); + fprintf(stderr, "%s:%d, %s: XOpenDisplay failed.\n", __FILE__, __LINE__, __func__); return; } @@ -957,36 +1142,36 @@ void p_init(void) { _gc = DefaultGC(_display, screen); if (_gc == NULL) { - fprintf(stderr, "%s:%d, %s: DefaultGC failed.\n", __FILE__, __LINE__, __FUNCTION__); + fprintf(stderr, "%s:%d, %s: DefaultGC failed.\n", __FILE__, __LINE__, __func__); return; } XSetGraphicsExposures(_display, _gc, False); - i32 _display_width = DisplayWidth (_display, screen); - i32 _display_height = DisplayHeight(_display, screen); + i32 display_width = DisplayWidth (_display, screen); + i32 display_height = DisplayHeight(_display, screen); if (platform.frame_width <= 0) platform.frame_width = 400; if (platform.frame_height <= 0) platform.frame_height = 300; - i32 x = (_display_width - platform.frame_width) / 2; - i32 y = (_display_height - platform.frame_height) / 2; + i32 x = (display_width - platform.frame_width) / 2; + i32 y = (display_height - platform.frame_height) / 2; _window = XCreateWindow(_display, XDefaultRootWindow(_display), x, y, platform.frame_width, platform.frame_height, 0, depth, InputOutput, visual, CWEventMask, &(XSetWindowAttributes) { .event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | SubstructureNotifyMask, }); _im = XOpenIM(_display, NULL, NULL, NULL); if (_im == NULL) { - fprintf(stderr, "%s:%d, %s: XOpenIM failed.\n", __FILE__, __LINE__, __FUNCTION__); + fprintf(stderr, "%s:%d, %s: XOpenIM failed.\n", __FILE__, __LINE__, __func__); return; } _ic = XCreateIC(_im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, _window, NULL); if (_ic == NULL) { - fprintf(stderr, "%s:%d, %s: XCreateIC failed.\n", __FILE__, __LINE__, __FUNCTION__); + fprintf(stderr, "%s:%d, %s: XCreateIC failed.\n", __FILE__, __LINE__, __func__); return; } @@ -1044,6 +1229,7 @@ void p_cleanup(void) { _window = 0; sockets_cleanup(); + sound_cleanup(); } i32 p_handle_events(void) { @@ -1344,7 +1530,7 @@ void p_render_frame(void) { void p_clipboard_write(i64 size, c8 *data) { if (size > MAX_CLIPBOARD_SIZE) { - fprintf(stderr, "%s:%d, %s: Size is too big %lld.\n", __FILE__, __LINE__, __FUNCTION__, size); + fprintf(stderr, "%s:%d, %s: Size is too big %lld.\n", __FILE__, __LINE__, __func__, size); return; } @@ -1359,28 +1545,6 @@ void p_clipboard_write(i64 size, c8 *data) { // ================================================================ // -// ALSA -// -// ================================================================ - -#if defined(__linux__) - -void p_handle_audio(i64 time_elapsed) { - (void) time_elapsed; - // TODO -} - -void p_queue_sound(i64 delay, i64 num_samples, f32 *samples) { - (void) delay; - (void) num_samples; - (void) samples; - // TODO -} - -#endif // defined(__linux__) - -// ================================================================ -// // WebAssembly // // ================================================================ |