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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
#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);
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_Texture *anger_tex = IMG_LoadTexture(renderer, "assets/emotions/anger.png");
if (!anger_tex)
fprintf(stderr, "[ERROR] 'assets/emotions/anger.png' not found!\n");
SDL_Thread *audio_thread =
SDL_CreateThread(audio_thread_func, "AudioThread", NULL);
float visual_rms = 0.0f;
float anger_alpha = 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);
float char_x = 0, char_y = 0, char_w = 0, char_h = 0;
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;
char_x = 300.0f - (final_w / 2.0f);
char_y = 550.0f - final_h + bob;
char_w = final_w;
char_h = final_h;
SDL_FRect dst = {char_x, char_y, 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);
}
float target_alpha = visual_rms > SCREAM_THRESHOLD ? 255.0f : 0.0f;
anger_alpha += (target_alpha - anger_alpha) * ANGER_FADE_SPEED;
if (anger_tex && anger_alpha > 1.0f) {
float anger_w, anger_h;
SDL_GetTextureSize(anger_tex, &anger_w, &anger_h);
float anger_size = char_w * 0.35f;
float anger_scale = anger_size / (anger_h > anger_w ? anger_h : anger_w);
float anger_x = char_x + char_w * 0.06f;
float anger_y = char_y + char_h * 0.08f;
SDL_FRect anger_dst = {anger_x, anger_y,
anger_w * anger_scale, anger_h * anger_scale};
SDL_FPoint anger_origin = {char_w * 0.44f, char_h * 0.92f};
SDL_SetTextureAlphaMod(anger_tex, (uint8_t)anger_alpha);
SDL_RenderTextureRotated(renderer, anger_tex, NULL, &anger_dst, (double)tilt,
&anger_origin, SDL_FLIP_NONE);
}
SDL_RenderPresent(renderer);
SDL_Delay(8);
}
SDL_SetAtomicInt(&app_running, 0);
SDL_WaitThread(audio_thread, NULL);
if (anger_tex)
SDL_DestroyTexture(anger_tex);
if (tex)
SDL_DestroyTexture(tex);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
|