ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
visualization.c
Go to the documentation of this file.
2
9
10#include <math.h>
11#include <stdio.h>
12#include <string.h>
13
15
21typedef struct {
22 float frequency;
23 unsigned char red;
24 unsigned char green;
25 unsigned char blue;
27
29static size_t write_position[AUDIO_VISUALIZATION_SOURCE_COUNT];
30static static_mutex_t history_mutex = STATIC_MUTEX_INIT;
31
32static void visualization_color_for_frequency(float centroid_hz, unsigned char *red, unsigned char *green,
33 unsigned char *blue) {
34 static const waveform_color_stop_t stops[] = {
35 {45.0f, 255, 72, 176}, {100.0f, 255, 54, 106}, {220.0f, 255, 104, 72}, {350.0f, 190, 86, 220},
36 {700.0f, 74, 112, 255}, {1400.0f, 55, 190, 255}, {2800.0f, 55, 226, 180}, {5200.0f, 150, 244, 92},
37 {10000.0f, 255, 224, 76}, {18000.0f, 255, 246, 150},
38 };
39 const size_t stop_count = sizeof(stops) / sizeof(stops[0]);
40 if (centroid_hz <= stops[0].frequency) {
41 *red = stops[0].red;
42 *green = stops[0].green;
43 *blue = stops[0].blue;
44 return;
45 }
46 for (size_t i = 1; i < stop_count; i++) {
47 if (centroid_hz <= stops[i].frequency) {
48 float low = logf(stops[i - 1].frequency);
49 float high = logf(stops[i].frequency);
50 float amount = (logf(centroid_hz) - low) / (high - low);
51 *red = (unsigned char)lrintf((float)stops[i - 1].red + amount * ((float)stops[i].red - stops[i - 1].red));
52 *green = (unsigned char)lrintf((float)stops[i - 1].green + amount * ((float)stops[i].green - stops[i - 1].green));
53 *blue = (unsigned char)lrintf((float)stops[i - 1].blue + amount * ((float)stops[i].blue - stops[i - 1].blue));
54 return;
55 }
56 }
57 *red = stops[stop_count - 1].red;
58 *green = stops[stop_count - 1].green;
59 *blue = stops[stop_count - 1].blue;
60}
61
62void visualization_color_for_level(float frequency_hz, float level, int color_mode, unsigned char *red,
63 unsigned char *green, unsigned char *blue) {
64 visualization_color_for_frequency(frequency_hz, red, green, blue);
65 float brightness = 0.5f + 0.5f * level;
66 float white_mix = 0.0f;
68 brightness = 0.24f + 0.76f * level;
70 brightness = 0.62f + 0.38f * level;
71 white_mix = 0.10f;
72 }
73 *red = (unsigned char)lrintf(fminf(255.0f, *red * brightness + 255.0f * white_mix));
74 *green = (unsigned char)lrintf(fminf(255.0f, *green * brightness + 255.0f * white_mix));
75 *blue = (unsigned char)lrintf(fminf(255.0f, *blue * brightness + 255.0f * white_mix));
76}
77
79 if (!samples || count % 2)
80 return;
81 float mono[480];
82 for (size_t offset = 0; offset < count;) {
83 size_t frames = (count - offset) / 2;
84 if (frames > 480)
85 frames = 480;
86 for (size_t i = 0; i < frames; i++)
87 mono[i] = samples[offset + i * 2] * 0.5f + samples[offset + i * 2 + 1] * 0.5f;
89 offset += frames * 2;
90 }
91}
92
93void audio_visualization_submit(audio_visualization_source_t source, const float *samples, size_t count) {
94 if ((!GET_OPTION(waveform) && !GET_OPTION(fft)) || !samples || source < AUDIO_VISUALIZATION_SOURCE_MIC ||
96 return;
97 static_mutex_lock_named(&history_mutex, "audio_visualization_history");
98 size_t position = write_position[source];
99 for (size_t i = 0; i < count; i++) {
100 float sample = samples[i];
101 history[source][position] = isfinite(sample) ? fmaxf(-1.0f, fminf(1.0f, sample)) : 0.0f;
102 position = (position + 1) % VISUALIZATION_HISTORY;
103 }
104 write_position[source] = position;
105 static_mutex_unlock(&history_mutex);
106}
107
109 if (!samples)
110 return;
112 memset(samples, 0, count * sizeof(float));
113 return;
114 }
115 static_mutex_lock_named(&history_mutex, "audio_visualization_history");
116 size_t available_count = count < VISUALIZATION_HISTORY ? count : VISUALIZATION_HISTORY;
117 size_t output_offset = count - available_count;
118 for (size_t i = 0; i < count; i++)
119 samples[i] = 0.0f;
120 size_t first_source = source;
121 size_t last_source = source;
123 first_source = AUDIO_VISUALIZATION_SOURCE_MIC;
126 first_source = AUDIO_VISUALIZATION_SOURCE_MIC;
128 }
129 for (size_t current = first_source; current <= last_source; current++) {
130 size_t start = (write_position[current] + VISUALIZATION_HISTORY - available_count) % VISUALIZATION_HISTORY;
131 for (size_t i = 0; i < available_count; i++)
132 samples[output_offset + i] += history[current][(start + i) % VISUALIZATION_HISTORY];
133 }
134 static_mutex_unlock(&history_mutex);
135 for (size_t i = 0; i < count; i++)
136 samples[i] = fmaxf(-1.0f, fminf(1.0f, samples[i]));
137}
138
139static char *visualization_color(char *out, const unsigned char *color, bool background, int color_mode) {
140 if (color_mode == COLOR_MODE_16) {
141 uint8_t index = rgb_to_16color(color[0], color[1], color[2]);
142 return background ? append_16color_bg(out, index) : append_16color_fg(out, index);
143 }
144 if (color_mode == COLOR_MODE_256) {
145 uint8_t index = rgb_to_256color(color[0], color[1], color[2]);
146 return background ? append_256color_bg(out, index) : append_256color_fg(out, index);
147 }
148 return background ? append_truecolor_bg(out, color[0], color[1], color[2])
149 : append_truecolor_fg(out, color[0], color[1], color[2]);
150}
151
152/* Encode the same visualization grid for native terminals and browser rasterization.
153 * Half-block grids have two vertical samples per terminal row. */
154char *visualization_encode(const char *cells, const unsigned char *colors, bool column_colors, unsigned int width,
155 unsigned int height, bool use_color, bool flip_x, bool flip_y) {
156 int mode = GET_OPTION(render_mode);
160 use_color = use_color && color_mode != COLOR_MODE_NONE;
161 palette_type_t palette_type = (palette_type_t)GET_OPTION(palette_type);
162 const palette_def_t *builtin = get_builtin_palette(palette_type);
163 const char *palette =
164 palette_type == PALETTE_CUSTOM ? GET_OPTION(palette_custom) : (builtin ? builtin->chars : PALETTE_CHARS_STANDARD);
165 if (!palette || !*palette)
166 palette = PALETTE_CHARS_STANDARD;
167 const char *characters[256];
168 size_t lengths[256], count = 0;
169 for (const char *p = palette; *p && count < 256;) {
170 uint32_t codepoint;
171 int length = utf8_decode((const uint8_t *)p, &codepoint);
172 if (length <= 0)
173 break;
174 characters[count] = p;
175 lengths[count++] = (size_t)length;
176 p += length;
177 }
178 bool half = mode == RENDER_MODE_HALF_BLOCK;
179 unsigned int rows = half ? height / 2 : height;
180 size_t capacity = (size_t)width * rows * 64 + rows + 1;
181 char *frame = SAFE_MALLOC(capacity, char *);
182 if (!frame)
183 return NULL;
184 size_t used = 0;
185 for (unsigned int y = 0; y < rows; y++) {
186 for (unsigned int x = 0; x < width; x++) {
187 unsigned int sx = flip_x ? width - x - 1 : x;
188 unsigned int sy = half ? y * 2 : y;
189 unsigned int top = flip_y ? height - sy - 1 : sy;
190 size_t cell = (size_t)top * width + sx;
191 const unsigned char *color = colors + (column_colors ? sx : cell) * 3;
192 char ch = cells[cell];
193 if (half) {
194 unsigned int bottom = flip_y ? height - sy - 2 : sy + 1;
195 size_t lower = (size_t)bottom * width + sx;
196 const unsigned char *lower_color = colors + (column_colors ? sx : lower) * 3;
197 bool upper_on = ch != ' ', lower_on = cells[lower] != ' ';
198 if (use_color) {
199 const unsigned char black[3] = {0};
200 char *out = visualization_color(frame + used, upper_on ? color : black, false, color_mode);
201 out = visualization_color(out, lower_on ? lower_color : black, true, color_mode);
202 memcpy(out, "▀\033[0m", sizeof("▀\033[0m") - 1);
203 used = (size_t)(out - frame) + sizeof("▀\033[0m") - 1;
204 } else {
205 const char *glyph = upper_on ? (lower_on ? "█" : "▀") : (lower_on ? "▄" : " ");
206 size_t length = strlen(glyph);
207 memcpy(frame + used, glyph, length);
208 used += length;
209 }
210 } else if (ch != ' ') {
211 char *out = frame + used;
212 if (use_color)
213 out = visualization_color(out, color, mode == RENDER_MODE_BACKGROUND, color_mode);
214 if (mode == RENDER_MODE_BACKGROUND && use_color) {
215 *out++ = ' ';
216 } else if (count) {
217 unsigned int luminance = 2126u * color[0] + 7152u * color[1] + 722u * color[2];
218 size_t index = (luminance * (count - 1) + 1275000u) / 2550000u;
219 memcpy(out, characters[index], lengths[index]);
220 out += lengths[index];
221 } else {
222 *out++ = ch;
223 }
224 if (use_color) {
225 memcpy(out, "\033[0m", 4);
226 out += 4;
227 }
228 used = (size_t)(out - frame);
229 } else {
230 frame[used++] = ch;
231 }
232 }
233 frame[used++] = '\n';
234 }
235 frame[used] = '\0';
236 return frame;
237}
ANSI escape sequence utilities and fast color code generation.
int color_mode
Definition fft.c:25
char cells[(512 *256 *2)]
Definition fft.c:21
unsigned char colors[(512 *256 *2) *3]
Definition fft.c:22
audio_visualization_source_t source
Definition fft.c:24
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
char * append_16color_fg(char *dst, uint8_t color_index)
Definition ansi.c:322
char * append_16color_bg(char *dst, uint8_t color_index)
Definition ansi.c:338
char * append_256color_bg(char *dst, uint8_t index)
Definition ansi.c:243
char * append_truecolor_fg(char *dst, uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:141
uint8_t rgb_to_16color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:354
char * append_256color_fg(char *dst, uint8_t index)
Definition ansi.c:236
uint8_t rgb_to_256color(uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:251
char * append_truecolor_bg(char *dst, uint8_t r, uint8_t g, uint8_t b)
Definition ansi.c:145
unsigned int uint32_t
Definition common.h:62
#define SAFE_MALLOC(size, cast)
Definition common.h:265
unsigned char uint8_t
Definition common.h:60
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
#define COLOR_MODE_TRUECOLOR
24-bit truecolor mode
Definition options.h:228
#define COLOR_MODE_AUTO
Backward compatibility aliases for color mode enum values.
Definition options.h:222
#define COLOR_MODE_16
16-color mode (alias)
Definition options.h:224
#define COLOR_MODE_NONE
Monochrome mode.
Definition options.h:223
#define COLOR_MODE_256
256-color mode (alias)
Definition options.h:226
const palette_def_t * get_builtin_palette(palette_type_t type)
Get built-in palette definition.
Definition palette.c:53
palette_type_t
Built-in palette type enumeration.
Definition palette.h:90
#define PALETTE_CHARS_STANDARD
Standard ASCII palette character string.
Definition palette.h:171
const char * chars
Character sequence (ordered from dark to light).
Definition palette.h:128
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:104
#define STATIC_MUTEX_INIT
int utf8_decode(const uint8_t *s, uint32_t *codepoint)
Decode a UTF-8 sequence to a Unicode codepoint.
Definition utf8.c:27
🔢 Mathematical Utility Functions
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
ASCII Palette Management for Video-to-ASCII Conversion.
static bool size_t index
Definition queries.h:8
static bool size_t gotel_query_t * out
Definition queries.h:8
🔤 String Manipulation and Shell Escaping Utilities
Validated luminance palette used to turn brightness into characters.
Definition palette.h:124
Static mutex structure for global mutexes requiring static initialization.
Definition common/sync.h:45
waveform color stop keeps the values that move through the subsystem: frequency, red,...
🔤 UTF-8 Encoding and Decoding Utilities
void audio_visualization_submit(audio_visualization_source_t source, const float *samples, size_t count)
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 audio_visualization_submit_stereo(audio_visualization_source_t source, const float *samples, size_t count)
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
@ AUDIO_VISUALIZATION_SOURCE_MEDIA
@ AUDIO_VISUALIZATION_SOURCE_LOCAL_MIX
@ AUDIO_VISUALIZATION_SOURCE_COUNT
@ AUDIO_VISUALIZATION_SOURCE_MIC
@ AUDIO_VISUALIZATION_SOURCE_REMOTE
#define AUDIO_VISUALIZATION_COLOR_BRIGHTER
#define AUDIO_VISUALIZATION_COLOR_DARKER
#define VISUALIZATION_HISTORY