aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Config.h9
-rw-r--r--src/Main.c103
2 files changed, 112 insertions, 0 deletions
diff --git a/src/Config.h b/src/Config.h
new file mode 100644
index 0000000..f74c3a8
--- /dev/null
+++ b/src/Config.h
@@ -0,0 +1,9 @@
+#define VOLUME_THRESHOLD 400.0f
+#define MAX_VOLUME_REF 2500.0f
+#define SAMPLE_RATE 44100
+#define BUFFER_SIZE 256
+#define IDLE_BOB_SPEED 0.004f
+#define IDLE_BOB_AMP 15.0f
+#define IDLE_TILT_AMP 3.5f
+#define SQUASH_STRETCH_INTENSITY 0.25f
+#define LERP_SPEED 0.25f
diff --git a/src/Main.c b/src/Main.c
new file mode 100644
index 0000000..d83511a
--- /dev/null
+++ b/src/Main.c
@@ -0,0 +1,103 @@
+#include "Config.h"
+#include <SDL3/SDL.h>
+#include <SDL3_image/SDL_image.h>
+#include <math.h>
+#include <pulse/error.h>
+#include <pulse/simple.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+SDL_AtomicInt is_speaking;
+SDL_AtomicInt app_running;
+float shared_rms = 0.0f;
+
+int SDLCALL audio_thread_func(void *data) {
+ (void)data;
+ pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE, .rate = SAMPLE_RATE, .channels = 1};
+ pa_buffer_attr attr = {.maxlength = -1,
+ .tlength = -1,
+ .prebuf = -1,
+ .minreq = -1,
+ .fragsize = BUFFER_SIZE * 2};
+ int error;
+ pa_simple *s = pa_simple_new(NULL, "PNDacc", PA_STREAM_RECORD, NULL, "Record",
+ &ss, NULL, &attr, &error);
+ if (!s)
+ return -1;
+ int16_t buffer[BUFFER_SIZE];
+ while (SDL_GetAtomicInt(&app_running)) {
+ if (pa_simple_read(s, buffer, sizeof(buffer), &error) < 0)
+ break;
+ double sum_squares = 0;
+ for (int i = 0; i < BUFFER_SIZE; i++)
+ sum_squares += (double)buffer[i] * buffer[i];
+ float rms = (float)sqrt(sum_squares / BUFFER_SIZE);
+ shared_rms = rms;
+ SDL_SetAtomicInt(&is_speaking, rms > VOLUME_THRESHOLD ? 1 : 0);
+ }
+ pa_simple_free(s);
+ return 0;
+}
+
+int main(void) {
+ SDL_SetAtomicInt(&app_running, 1);
+ if (!SDL_Init(SDL_INIT_VIDEO))
+ return 1;
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ if (!SDL_CreateWindowAndRenderer("PNDacc", 600, 600, 0, &window, &renderer))
+ return 1;
+ SDL_Texture *tex = IMG_LoadTexture(renderer, "profile.png");
+ if (!tex)
+ fprintf(stderr, "[ERROR] 'profile.png' not found!\n");
+ SDL_Thread *audio_thread =
+ SDL_CreateThread(audio_thread_func, "AudioThread", NULL);
+ float visual_rms = 0.0f;
+ while (SDL_GetAtomicInt(&app_running)) {
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ if (event.type == SDL_EVENT_QUIT)
+ SDL_SetAtomicInt(&app_running, 0);
+ }
+ float target_rms = shared_rms;
+ if (target_rms < VOLUME_THRESHOLD)
+ target_rms = 0.0f;
+ visual_rms += (target_rms - visual_rms) * LERP_SPEED;
+ SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
+ SDL_RenderClear(renderer);
+ float volume_norm = visual_rms / MAX_VOLUME_REF;
+ if (volume_norm > 1.0f)
+ volume_norm = 1.0f;
+ float stretch_y = 1.0f + (volume_norm * SQUASH_STRETCH_INTENSITY);
+ float squash_x = 1.0f - (volume_norm * SQUASH_STRETCH_INTENSITY * 0.6f);
+ uint64_t ticks = SDL_GetTicks();
+ float bob = sinf(ticks * IDLE_BOB_SPEED) * IDLE_BOB_AMP;
+ float tilt = sinf(ticks * (IDLE_BOB_SPEED * 0.7f)) * IDLE_TILT_AMP;
+ if (tex) {
+ float img_w, img_h;
+ SDL_GetTextureSize(tex, &img_w, &img_h);
+ float scale = 450.0f / (img_h > img_w ? img_h : img_w);
+ float base_w = img_w * scale;
+ float base_h = img_h * scale;
+ float final_w = base_w * squash_x;
+ float final_h = base_h * stretch_y;
+ SDL_FRect dst = {300.0f - (final_w / 2.0f), 550.0f - final_h + bob,
+ final_w, final_h};
+ SDL_FPoint origin = {final_w / 2.0f, final_h};
+ SDL_RenderTextureRotated(renderer, tex, NULL, &dst, (double)tilt, &origin,
+ SDL_FLIP_NONE);
+ }
+ SDL_RenderPresent(renderer);
+ SDL_Delay(8);
+ }
+ SDL_SetAtomicInt(&app_running, 0);
+ SDL_WaitThread(audio_thread, NULL);
+ if (tex)
+ SDL_DestroyTexture(tex);
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ return 0;
+}