1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
#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;
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);
atomic_store_float(&shared_rms_bits, 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 = 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;
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;
}
|