summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2024-11-17 19:00:08 +0100
committerMitya Selivanov <automainint@guattari.tech>2024-11-17 19:00:08 +0100
commitf99fd0ce62375bd0f994861768c36de6920e822e (patch)
treebc5f37b18d778160b71176a38f784efa71ff59d1
parent94598aa337e0fd02a4bfd38929acfdb8296ee23b (diff)
downloadreduced_system_layer-f99fd0ce62375bd0f994861768c36de6920e822e.zip
Add ALSA impl; Sine Wave example
-rwxr-xr-xexamples/sinewave.c58
-rwxr-xr-xreduced_system_layer.c272
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
//
// ================================================================