ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
visualization.h File Reference

Go to the source code of this file.

Macros

#define AUDIO_VISUALIZATION_SAMPLE_RATE   48000
 
#define AUDIO_VISUALIZATION_COLOR_STANDARD   0
 
#define AUDIO_VISUALIZATION_COLOR_DARKER   1
 
#define AUDIO_VISUALIZATION_COLOR_BRIGHTER   2
 

Enumerations

enum  audio_visualization_source_t {
  AUDIO_VISUALIZATION_SOURCE_MIC = 0 , AUDIO_VISUALIZATION_SOURCE_MEDIA , AUDIO_VISUALIZATION_SOURCE_REMOTE , AUDIO_VISUALIZATION_SOURCE_LOCAL_MIX ,
  AUDIO_VISUALIZATION_SOURCE_MIX , AUDIO_VISUALIZATION_SOURCE_COUNT
}
 

Functions

void audio_visualization_submit (audio_visualization_source_t source, const float *samples, size_t count)
 
void audio_visualization_read (audio_visualization_source_t source, float *samples, size_t count)
 
char * audio_visualization_render_waveform (unsigned int width, unsigned int height, audio_visualization_source_t source, bool use_color, int color_mode, bool flip_x, bool flip_y)
 
char * audio_visualization_render_fft (unsigned int width, unsigned int height, audio_visualization_source_t source, bool use_color, int color_mode, bool flip_x, bool flip_y)
 

Macro Definition Documentation

◆ AUDIO_VISUALIZATION_COLOR_BRIGHTER

#define AUDIO_VISUALIZATION_COLOR_BRIGHTER   2

Definition at line 20 of file visualization.h.

◆ AUDIO_VISUALIZATION_COLOR_DARKER

#define AUDIO_VISUALIZATION_COLOR_DARKER   1

Definition at line 19 of file visualization.h.

◆ AUDIO_VISUALIZATION_COLOR_STANDARD

#define AUDIO_VISUALIZATION_COLOR_STANDARD   0

Definition at line 18 of file visualization.h.

◆ AUDIO_VISUALIZATION_SAMPLE_RATE

#define AUDIO_VISUALIZATION_SAMPLE_RATE   48000

Definition at line 16 of file visualization.h.

Enumeration Type Documentation

◆ audio_visualization_source_t

Enumerator
AUDIO_VISUALIZATION_SOURCE_MIC 
AUDIO_VISUALIZATION_SOURCE_MEDIA 
AUDIO_VISUALIZATION_SOURCE_REMOTE 
AUDIO_VISUALIZATION_SOURCE_LOCAL_MIX 
AUDIO_VISUALIZATION_SOURCE_MIX 
AUDIO_VISUALIZATION_SOURCE_COUNT 

Definition at line 7 of file visualization.h.

Function Documentation

◆ audio_visualization_read()

void audio_visualization_read ( audio_visualization_source_t  source,
float *  samples,
size_t  count 
)

Definition at line 115 of file visualization.c.

115 {
116 if (!samples)
117 return;
118 if (source < AUDIO_VISUALIZATION_SOURCE_MIC || source >= AUDIO_VISUALIZATION_SOURCE_COUNT) {
119 memset(samples, 0, count * sizeof(float));
120 return;
121 }
122 static_mutex_lock(&history_mutex);
123 size_t available_count = count < VISUALIZATION_HISTORY ? count : VISUALIZATION_HISTORY;
124 size_t output_offset = count - available_count;
125 for (size_t i = 0; i < count; i++)
126 samples[i] = 0.0f;
127 size_t first_source = source;
128 size_t last_source = source;
129 if (source == AUDIO_VISUALIZATION_SOURCE_MIX) {
130 first_source = AUDIO_VISUALIZATION_SOURCE_MIC;
132 } else if (source == AUDIO_VISUALIZATION_SOURCE_LOCAL_MIX) {
133 first_source = AUDIO_VISUALIZATION_SOURCE_MIC;
135 }
136 for (size_t current = first_source; current <= last_source; current++) {
137 size_t start = (write_position[current] + VISUALIZATION_HISTORY - available_count) % VISUALIZATION_HISTORY;
138 for (size_t i = 0; i < available_count; i++)
139 samples[output_offset + i] += history[current][(start + i) % VISUALIZATION_HISTORY];
140 }
141 static_mutex_unlock(&history_mutex);
142 for (size_t i = 0; i < count; i++)
143 samples[i] = fmaxf(-1.0f, fminf(1.0f, samples[i]));
144}
#define VISUALIZATION_HISTORY

References AUDIO_VISUALIZATION_SOURCE_COUNT, AUDIO_VISUALIZATION_SOURCE_LOCAL_MIX, AUDIO_VISUALIZATION_SOURCE_MEDIA, AUDIO_VISUALIZATION_SOURCE_MIC, AUDIO_VISUALIZATION_SOURCE_MIX, AUDIO_VISUALIZATION_SOURCE_REMOTE, and VISUALIZATION_HISTORY.

Referenced by audio_visualization_render_fft(), and audio_visualization_render_waveform().

◆ audio_visualization_render_fft()

char * audio_visualization_render_fft ( unsigned int  width,
unsigned int  height,
audio_visualization_source_t  source,
bool  use_color,
int  color_mode,
bool  flip_x,
bool  flip_y 
)

Definition at line 275 of file visualization.c.

276 {
277 if (width < 16 || height < 6 || width > MAX_TERMINAL_WIDTH || height > MAX_TERMINAL_HEIGHT ||
278 source < AUDIO_VISUALIZATION_SOURCE_MIC || source > AUDIO_VISUALIZATION_SOURCE_MIX)
279 return NULL;
280
281 float *samples = SAFE_MALLOC(VISUALIZATION_HISTORY * sizeof(float), float *);
282 if (!samples)
283 return NULL;
285 size_t cell_count = (size_t)width * height;
286 char *cells = SAFE_MALLOC(cell_count, char *);
287 unsigned char *colors = SAFE_MALLOC(cell_count * 3, unsigned char *);
288 if (!cells || !colors) {
289 SAFE_FREE(colors);
290 SAFE_FREE(cells);
291 SAFE_FREE(samples);
292 return NULL;
293 }
294 memset(cells, ' ', cell_count);
295 memset(colors, 0, cell_count * 3);
296
297 /* Each column is a 21 ms Hann-windowed spectrum; adjacent columns overlap heavily. */
298 float real[FFT_SIZE], imaginary[FFT_SIZE];
299 static const char levels[] = " .:-=+*#%@";
300 const unsigned int spectrum_height = height;
301 for (unsigned int x = 0; x < width; x++) {
302 size_t center = (size_t)x * (VISUALIZATION_HISTORY - 1) / (width - 1);
303 size_t start = center > FFT_SIZE / 2 ? center - FFT_SIZE / 2 : 0;
304 if (start + FFT_SIZE > VISUALIZATION_HISTORY)
306 for (size_t i = 0; i < FFT_SIZE; i++) {
307 float window = 0.5f - 0.5f * cosf(6.28318530718f * (float)i / (float)(FFT_SIZE - 1));
308 real[i] = samples[start + i] * window;
309 imaginary[i] = 0.0f;
310 }
311 visualization_fft(real, imaginary);
312
313 for (unsigned int row = 0; row < spectrum_height; row++) {
314 float top_fraction = (float)(spectrum_height - row - 1) / (float)spectrum_height;
315 float bottom_fraction = (float)(spectrum_height - row) / (float)spectrum_height;
316 float low_hz = 35.0f * powf(18000.0f / 35.0f, top_fraction);
317 float high_hz = 35.0f * powf(18000.0f / 35.0f, bottom_fraction);
318 size_t low_bin = (size_t)fmaxf(1.0f, floorf(low_hz * FFT_SIZE / AUDIO_VISUALIZATION_SAMPLE_RATE));
319 size_t high_bin =
320 (size_t)fmaxf((float)(low_bin + 1), ceilf(high_hz * FFT_SIZE / AUDIO_VISUALIZATION_SAMPLE_RATE));
321 if (high_bin > FFT_SIZE / 2)
322 high_bin = FFT_SIZE / 2;
323 float power = 0.0f;
324 size_t bins = 0;
325 for (size_t bin = low_bin; bin < high_bin; bin++) {
326 float magnitude = hypotf(real[bin], imaginary[bin]) / (FFT_SIZE * 0.25f);
327 power += magnitude * magnitude;
328 bins++;
329 }
330 float magnitude = bins ? sqrtf(power / (float)bins) : 0.0f;
331 /* dB scaling leaves room for quiet speech while keeping loud music detailed. */
332 float db = 20.0f * log10f(fmaxf(magnitude, 0.00001f));
333 float intensity = fmaxf(0.0f, fminf(1.0f, (db + 58.0f) / 52.0f));
334 int level = (int)lrintf(intensity * (float)(sizeof(levels) - 2));
335 unsigned int source_x = flip_x ? width - x - 1 : x;
336 unsigned int source_row = flip_y ? height - row - 1 : row;
337 size_t cell = (size_t)source_row * width + source_x;
338 cells[cell] = levels[level];
339 float band_hz = sqrtf(low_hz * high_hz);
340 visualization_color_for_level(band_hz, intensity, color_mode, &colors[cell * 3], &colors[cell * 3 + 1],
341 &colors[cell * 3 + 2]);
342 }
343 }
344
345 size_t capacity = cell_count * (use_color ? 24U : 1U) + (size_t)height + 1;
346 char *frame = SAFE_MALLOC(capacity, char *);
347 if (!frame) {
348 SAFE_FREE(colors);
349 SAFE_FREE(cells);
350 SAFE_FREE(samples);
351 return NULL;
352 }
353 size_t used = 0;
354 for (unsigned int row = 0; row < height; row++) {
355 for (unsigned int x = 0; x < width; x++) {
356 size_t cell = (size_t)row * width + x;
357 char ch = cells[cell];
358 if (use_color && ch != ' ') {
359 used += (size_t)snprintf(frame + used, capacity - used, "\033[38;2;%u;%u;%um%c\033[0m", colors[cell * 3],
360 colors[cell * 3 + 1], colors[cell * 3 + 2], ch);
361 } else {
362 frame[used++] = ch;
363 }
364 }
365 frame[used++] = '\n';
366 }
367 frame[used] = '\0';
368 SAFE_FREE(colors);
369 SAFE_FREE(cells);
370 SAFE_FREE(samples);
371 return frame;
372}
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
int frame
Definition splash.c:99
#define FFT_SIZE
#define MAX_TERMINAL_WIDTH
#define MAX_TERMINAL_HEIGHT
void audio_visualization_read(audio_visualization_source_t source, float *samples, size_t count)
#define AUDIO_VISUALIZATION_SAMPLE_RATE

References audio_visualization_read(), AUDIO_VISUALIZATION_SAMPLE_RATE, AUDIO_VISUALIZATION_SOURCE_MIX, FFT_SIZE, frame, MAX_TERMINAL_HEIGHT, MAX_TERMINAL_WIDTH, SAFE_FREE, SAFE_MALLOC, and VISUALIZATION_HISTORY.

Referenced by mirror_render_audio_visualization().

◆ audio_visualization_render_waveform()

char * audio_visualization_render_waveform ( unsigned int  width,
unsigned int  height,
audio_visualization_source_t  source,
bool  use_color,
int  color_mode,
bool  flip_x,
bool  flip_y 
)

Definition at line 146 of file visualization.c.

147 {
148 if (width < 8 || height < 4 || width > MAX_TERMINAL_WIDTH || height > MAX_TERMINAL_HEIGHT ||
149 source < AUDIO_VISUALIZATION_SOURCE_MIC || source > AUDIO_VISUALIZATION_SOURCE_MIX)
150 return NULL;
151
152 /* Keep a fixed one-second view so new audio enters on the right smoothly. */
153 const size_t sample_count = VISUALIZATION_HISTORY;
154 float *samples = SAFE_MALLOC(sample_count * sizeof(float), float *);
155 if (!samples)
156 return NULL;
157 audio_visualization_read(source, samples, sample_count);
158
159 const unsigned int grid_height = height;
160 const unsigned int center = grid_height / 2;
161 const unsigned int half_height = grid_height - 1 - center;
162 const size_t cell_count = (size_t)width * grid_height;
163 char *cells = SAFE_MALLOC(cell_count, char *);
164 if (!cells) {
165 SAFE_FREE(samples);
166 return NULL;
167 }
168 memset(cells, ' ', cell_count);
169 unsigned char palette[MAX_TERMINAL_WIDTH][3] = {{0}};
170
171 float positive_peak = 0.0f;
172 float negative_peak = 0.0f;
173 size_t x = 0;
174
175 for (size_t i = 0; i < sample_count; i++) {
176 float sample = samples[i];
177 positive_peak = fmaxf(positive_peak, sample);
178 negative_peak = fmaxf(negative_peak, -sample);
179
180 size_t next_bucket = ((i + 1) * width) / sample_count;
181 if (next_bucket != x || i + 1 == sample_count) {
182 size_t begin = x * sample_count / width;
183 size_t end = (x + 1) * sample_count / width;
184 size_t bucket_center = begin + (end - begin) / 2;
185 float centroid = waveform_spectral_centroid(samples, sample_count, bucket_center);
186 /* A gentle square-root scale keeps speech visible without flattening loud peaks. */
187 float level = sqrtf(fminf(fmaxf(positive_peak, negative_peak), 1.0f));
188 visualization_color_for_level(centroid, level, color_mode, &palette[x][0], &palette[x][1], &palette[x][2]);
189 unsigned int positive_amplitude = (unsigned int)lrintf(sqrtf(fminf(positive_peak, 1.0f)) * (float)half_height);
190 unsigned int negative_amplitude = (unsigned int)lrintf(sqrtf(fminf(negative_peak, 1.0f)) * (float)half_height);
191 if (positive_amplitude > half_height)
192 positive_amplitude = half_height;
193 if (negative_amplitude > half_height)
194 negative_amplitude = half_height;
195 unsigned int top = center - positive_amplitude;
196 unsigned int bottom = center + negative_amplitude;
197 for (unsigned int y = top; y <= bottom; y++) {
198 char ch = (y == top || y == bottom) ? '+' : '#';
199 cells[(size_t)y * width + x] = ch;
200 }
201
202 /* Advance to the next time slice in the left-to-right history. */
203 positive_peak = 0.0f;
204 negative_peak = 0.0f;
205 x = next_bucket;
206 }
207 }
208
209 size_t capacity = cell_count * (use_color ? 24U : 1U) + (size_t)height + 1;
210 char *frame = SAFE_MALLOC(capacity, char *);
211 if (!frame) {
212 SAFE_FREE(cells);
213 SAFE_FREE(samples);
214 return NULL;
215 }
216 size_t used = 0;
217 for (unsigned int y = 0; y < grid_height; y++) {
218 for (unsigned int x = 0; x < width; x++) {
219 unsigned int source_x = flip_x ? width - x - 1 : x;
220 unsigned int source_y = flip_y ? grid_height - y - 1 : y;
221 char ch = cells[(size_t)source_y * width + source_x];
222 if (use_color && ch != ' ') {
223 used += (size_t)snprintf(frame + used, capacity - used, "\033[38;2;%u;%u;%um%c\033[0m", palette[source_x][0],
224 palette[source_x][1], palette[source_x][2], ch);
225 } else {
226 frame[used++] = ch;
227 }
228 }
229 frame[used++] = '\n';
230 }
231 frame[used] = '\0';
232 SAFE_FREE(cells);
233 SAFE_FREE(samples);
234 return frame;
235}

References audio_visualization_read(), AUDIO_VISUALIZATION_SOURCE_MIX, frame, MAX_TERMINAL_HEIGHT, MAX_TERMINAL_WIDTH, SAFE_FREE, SAFE_MALLOC, and VISUALIZATION_HISTORY.

Referenced by mirror_render_audio_visualization().

◆ audio_visualization_submit()

void audio_visualization_submit ( audio_visualization_source_t  source,
const float *  samples,
size_t  count 
)

Definition at line 100 of file visualization.c.

100 {
101 if ((!GET_OPTION(waveform) && !GET_OPTION(fft)) || !samples || source < AUDIO_VISUALIZATION_SOURCE_MIC ||
103 return;
104 static_mutex_lock(&history_mutex);
105 size_t position = write_position[source];
106 for (size_t i = 0; i < count; i++) {
107 float sample = samples[i];
108 history[source][position] = isfinite(sample) ? fmaxf(-1.0f, fminf(1.0f, sample)) : 0.0f;
109 position = (position + 1) % VISUALIZATION_HISTORY;
110 }
111 write_position[source] = position;
112 static_mutex_unlock(&history_mutex);
113}
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)

References AUDIO_VISUALIZATION_SOURCE_MIC, AUDIO_VISUALIZATION_SOURCE_REMOTE, GET_OPTION, and VISUALIZATION_HISTORY.

Referenced by audio_write_samples(), and mirror_submit_audio_samples().