#include "Config.h" #include #include #include #include #include #include #include #include SDL_AtomicInt is_speaking; SDL_AtomicInt app_running; SDL_AtomicInt shared_rms_bits; static inline void atomic_store_float(SDL_AtomicInt *a, float f) { union { float f; int i; } u; u.f = f; SDL_SetAtomicInt(a, u.i); } static inline float atomic_load_float(SDL_AtomicInt *a) { union { float f; int i; } u; u.i = SDL_GetAtomicInt(a); return u.f; } 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); int speaking = rms > VOLUME_THRESHOLD ? 1 : 0; atomic_store_float(&shared_rms_bits, rms); SDL_SetAtomicInt(&is_speaking, speaking); fprintf(stderr, "[MIC] rms=%-8.2f threshold=%-8.2f %s\n", rms, (float)VOLUME_THRESHOLD, speaking ? "SPEAKING" : "silent "); } 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 = atomic_load_float(&shared_rms_bits); 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; fprintf(stderr, "[VIS] vol_norm=%-5.2f stretch_y=%-5.2f squash_x=%-5.2f bob=%-6.1f " "tilt=%-5.1f\n", volume_norm, stretch_y, squash_x, bob, tilt); 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; }