ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
waveform.c
Go to the documentation of this file.
2
4
7
8#include <math.h>
9#include <string.h>
10
11static float waveform_spectral_centroid(const float *samples, size_t sample_count, size_t center) {
12 static const float frequencies[] = {55.0f, 110.0f, 220.0f, 440.0f, 880.0f, 1760.0f, 3520.0f, 7040.0f, 14080.0f};
13 const size_t window_size = 1024;
14 const size_t half_window = window_size / 2;
15 size_t start = center > half_window ? center - half_window : 0;
16 if (start + window_size > sample_count)
17 start = sample_count - window_size;
18
19 float weighted_log_frequency = 0.0f;
20 float total_weight = 0.0f;
21 for (size_t frequency_index = 0; frequency_index < sizeof(frequencies) / sizeof(frequencies[0]); frequency_index++) {
22 float coefficient = 2.0f * cosf(6.28318530718f * frequencies[frequency_index] / AUDIO_VISUALIZATION_SAMPLE_RATE);
23 float q1 = 0.0f, q2 = 0.0f;
24 for (size_t i = 0; i < window_size; i++) {
25 float position = (float)i / (float)(window_size - 1);
26 float taper = 1.0f - fabsf(2.0f * position - 1.0f);
27 float q0 = coefficient * q1 - q2 + samples[start + i] * taper;
28 q2 = q1;
29 q1 = q0;
30 }
31 float power = fmaxf(q1 * q1 + q2 * q2 - coefficient * q1 * q2, 0.0f);
32 float magnitude = sqrtf(power);
33 weighted_log_frequency += magnitude * logf(frequencies[frequency_index]);
34 total_weight += magnitude;
35 }
36 return total_weight > 0.0001f ? expf(weighted_log_frequency / total_weight) : 900.0f;
37}
38
40 bool use_color, int color_mode, bool flip_x, bool flip_y) {
43 return NULL;
44
45 /* Keep a fixed one-second view so new audio enters on the right smoothly. */
46 const size_t sample_count = VISUALIZATION_HISTORY;
47 float *samples = SAFE_MALLOC(sample_count * sizeof(float), float *);
48 if (!samples)
49 return NULL;
50 audio_visualization_read(source, samples, sample_count);
51
52 const unsigned int grid_height = GET_OPTION(render_mode) == RENDER_MODE_HALF_BLOCK ? height * 2 : height;
53 const unsigned int center = grid_height / 2;
54 const unsigned int half_height = grid_height - 1 - center;
55 const size_t cell_count = (size_t)width * grid_height;
56 char *cells = SAFE_MALLOC(cell_count, char *);
57 if (!cells) {
58 SAFE_FREE(samples);
59 return NULL;
60 }
61 memset(cells, ' ', cell_count);
62 unsigned char palette[MAX_TERMINAL_WIDTH][3] = {{0}};
63
64 float positive_peak = 0.0f;
65 float negative_peak = 0.0f;
66 size_t x = 0;
67
68 for (size_t i = 0; i < sample_count; i++) {
69 float sample = samples[i];
70 positive_peak = fmaxf(positive_peak, sample);
71 negative_peak = fmaxf(negative_peak, -sample);
72
73 size_t next_bucket = ((i + 1) * width) / sample_count;
74 if (next_bucket != x || i + 1 == sample_count) {
75 size_t begin = x * sample_count / width;
76 size_t end = (x + 1) * sample_count / width;
77 size_t bucket_center = begin + (end - begin) / 2;
78 float centroid = waveform_spectral_centroid(samples, sample_count, bucket_center);
79 /* A gentle square-root scale keeps speech visible without flattening loud peaks. */
80 float level = sqrtf(fminf(fmaxf(positive_peak, negative_peak), 1.0f));
81 visualization_color_for_level(centroid, level, color_mode, &palette[x][0], &palette[x][1], &palette[x][2]);
82 unsigned int positive_amplitude = (unsigned int)lrintf(sqrtf(fminf(positive_peak, 1.0f)) * (float)half_height);
83 unsigned int negative_amplitude = (unsigned int)lrintf(sqrtf(fminf(negative_peak, 1.0f)) * (float)half_height);
84 if (positive_amplitude > half_height)
85 positive_amplitude = half_height;
86 if (negative_amplitude > half_height)
87 negative_amplitude = half_height;
88 unsigned int top = center - positive_amplitude;
89 unsigned int bottom = center + negative_amplitude;
90 for (unsigned int y = top; y <= bottom; y++) {
91 char ch = (y == top || y == bottom) ? '+' : '#';
92 cells[(size_t)y * width + x] = ch;
93 }
94
95 /* Advance to the next time slice in the left-to-right history. */
96 positive_peak = 0.0f;
97 negative_peak = 0.0f;
98 x = next_bucket;
99 }
100 }
101
102 char *frame = visualization_encode(cells, &palette[0][0], true, width, grid_height, use_color, flip_x, flip_y);
104 SAFE_FREE(samples);
105 return frame;
106}
int color_mode
Definition fft.c:25
char cells[(512 *256 *2)]
Definition fft.c:21
audio_visualization_source_t source
Definition fft.c:24
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
🔢 Mathematical Utility Functions
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
🔤 String Manipulation and Shell Escaping Utilities
char * visualization_encode(const char *cells, const unsigned char *colors, bool column_colors, unsigned int width, unsigned int height, bool use_color, bool flip_x, bool flip_y)
void visualization_color_for_level(float frequency_hz, float level, int color_mode, unsigned char *red, unsigned char *green, unsigned char *blue)
void audio_visualization_read(audio_visualization_source_t source, float *samples, size_t count)
audio_visualization_source_t
@ AUDIO_VISUALIZATION_SOURCE_MIX
#define AUDIO_VISUALIZATION_SAMPLE_RATE
#define MAX_TERMINAL_WIDTH
#define VISUALIZATION_HISTORY
#define MAX_TERMINAL_HEIGHT
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 waveform.c:39