ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
audio.c File Reference

🔊 Session-level audio coordination implementation More...

Go to the source code of this file.

Data Structures

struct  session_audio_source_t
 Per-source audio buffer info for mixing. More...
 
struct  session_audio_ctx
 Internal session audio context structure. More...
 

Macros

#define SESSION_AUDIO_MAX_SOURCES   32
 Maximum number of audio sources for mixing.
 

Functions

session_audio_ctx_t * session_audio_create (bool is_host)
 
void session_audio_destroy (session_audio_ctx_t *ctx)
 
asciichat_error_t session_audio_start_capture (session_audio_ctx_t *ctx)
 
asciichat_error_t session_audio_start_playback (session_audio_ctx_t *ctx)
 
asciichat_error_t session_audio_start_duplex (session_audio_ctx_t *ctx)
 
void session_audio_stop (session_audio_ctx_t *ctx)
 
bool session_audio_is_running (session_audio_ctx_t *ctx)
 
size_t session_audio_read_captured (session_audio_ctx_t *ctx, float *buffer, size_t num_samples)
 
asciichat_error_t session_audio_write_playback (session_audio_ctx_t *ctx, const float *buffer, size_t num_samples)
 
asciichat_error_t session_audio_add_source (session_audio_ctx_t *ctx, uint32_t source_id)
 
void session_audio_remove_source (session_audio_ctx_t *ctx, uint32_t source_id)
 
asciichat_error_t session_audio_write_source (session_audio_ctx_t *ctx, uint32_t source_id, const float *samples, size_t num_samples)
 
size_t session_audio_mix_excluding (session_audio_ctx_t *ctx, uint32_t exclude_id, float *output, size_t num_samples)
 

Detailed Description

🔊 Session-level audio coordination implementation

Implements the session audio wrapper for coordinating audio capture, playback, and mixing at the session level.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
January 2026

Definition in file lib/session/audio.c.

Macro Definition Documentation

◆ SESSION_AUDIO_MAX_SOURCES

#define SESSION_AUDIO_MAX_SOURCES   32

Maximum number of audio sources for mixing.

Definition at line 27 of file lib/session/audio.c.

Function Documentation

◆ session_audio_add_source()

asciichat_error_t session_audio_add_source ( session_audio_ctx_t *  ctx,
uint32_t  source_id 
)

Definition at line 225 of file lib/session/audio.c.

225 {
226 if (!ctx || !ctx->initialized) {
227 return SET_ERRNO(ERROR_INVALID_PARAM, "session_audio_add_source: invalid context");
228 }
229
230 if (!ctx->is_host) {
231 return SET_ERRNO(ERROR_INVALID_STATE, "session_audio_add_source: not a host context");
232 }
233
234 // Check if source already exists
235 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
236 if (ctx->sources[i].active && ctx->sources[i].source_id == source_id) {
237 return ASCIICHAT_OK; // Already registered
238 }
239 }
240
241 // Find empty slot
242 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
243 if (!ctx->sources[i].active) {
244 ctx->sources[i].source_id = source_id;
245 ctx->sources[i].active = true;
246 ctx->sources[i].buffer = audio_ring_buffer_create();
247 if (!ctx->sources[i].buffer) {
248 ctx->sources[i].active = false;
249 return SET_ERRNO(ERROR_MEMORY, "Failed to create audio buffer for source");
250 }
251 ctx->source_count++;
252 return ASCIICHAT_OK;
253 }
254 }
255
256 return SET_ERRNO(ERROR_RESOURCE_EXHAUSTED, "Maximum audio sources reached");
257}
audio_ring_buffer_t * audio_ring_buffer_create(void)
#define SESSION_AUDIO_MAX_SOURCES
Maximum number of audio sources for mixing.

References audio_ring_buffer_create(), and SESSION_AUDIO_MAX_SOURCES.

◆ session_audio_create()

session_audio_ctx_t * session_audio_create ( bool  is_host)

Definition at line 74 of file lib/session/audio.c.

74 {
75 // Allocate context
76 session_audio_ctx_t *ctx = SAFE_CALLOC(1, sizeof(session_audio_ctx_t), session_audio_ctx_t *);
77
78 ctx->is_host = is_host;
79 ctx->running = false;
80
81 // Initialize underlying audio context
82 asciichat_error_t result = audio_init(&ctx->audio_ctx);
83 if (result != ASCIICHAT_OK) {
84 log_error("Failed to initialize audio context: %d", result);
85 SAFE_FREE(ctx);
86 return NULL;
87 }
88
89 // Initialize mixing resources for host
90 if (is_host) {
91 // Allocate mix buffer (enough for one audio buffer worth)
92 ctx->mix_buffer_size = AUDIO_BUFFER_SIZE * 4; // Extra space for safety
93 ctx->mix_buffer = SAFE_CALLOC(ctx->mix_buffer_size, sizeof(float), float *);
94
95 // Initialize source array
96 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
97 ctx->sources[i].source_id = 0;
98 ctx->sources[i].active = false;
99 ctx->sources[i].buffer = NULL;
100 }
101 ctx->source_count = 0;
102 }
103
104 ctx->initialized = true;
105 return ctx;
106}
asciichat_error_t audio_init(audio_context_t *ctx)

References audio_init(), and SESSION_AUDIO_MAX_SOURCES.

Referenced by session_host_start_render(), and session_participant_start_audio_capture().

◆ session_audio_destroy()

void session_audio_destroy ( session_audio_ctx_t *  ctx)

Definition at line 108 of file lib/session/audio.c.

108 {
109 if (!ctx) {
110 return;
111 }
112
113 // Stop audio if running
114 if (ctx->running) {
116 }
117
118 // Cleanup host-specific resources
119 if (ctx->is_host) {
120 // Destroy source buffers
121 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
122 if (ctx->sources[i].buffer) {
123 audio_ring_buffer_destroy(ctx->sources[i].buffer);
124 ctx->sources[i].buffer = NULL;
125 }
126 }
127
128 // Free mix buffer
129 if (ctx->mix_buffer) {
130 SAFE_FREE(ctx->mix_buffer);
131 }
132 }
133
134 // Destroy underlying audio context
135 audio_destroy(&ctx->audio_ctx);
136
137 ctx->initialized = false;
138 SAFE_FREE(ctx);
139}
void audio_ring_buffer_destroy(audio_ring_buffer_t *rb)
void audio_destroy(audio_context_t *ctx)
void session_audio_stop(session_audio_ctx_t *ctx)

References audio_destroy(), audio_ring_buffer_destroy(), SESSION_AUDIO_MAX_SOURCES, and session_audio_stop().

Referenced by session_host_destroy(), session_host_start_render(), session_host_stop_render(), and session_participant_destroy().

◆ session_audio_is_running()

bool session_audio_is_running ( session_audio_ctx_t *  ctx)

Definition at line 189 of file lib/session/audio.c.

189 {
190 if (!ctx || !ctx->initialized) {
191 return false;
192 }
193 return ctx->running;
194}

◆ session_audio_mix_excluding()

size_t session_audio_mix_excluding ( session_audio_ctx_t *  ctx,
uint32_t  exclude_id,
float *  output,
size_t  num_samples 
)

Definition at line 301 of file lib/session/audio.c.

301 {
302 if (!ctx || !ctx->initialized || !output || num_samples == 0) {
303 return 0;
304 }
305
306 if (!ctx->is_host) {
307 return 0;
308 }
309
310 // Initialize output to silence
311 memset(output, 0, num_samples * sizeof(float));
312
313 // Ensure we have a mix buffer
314 if (!ctx->mix_buffer || num_samples > ctx->mix_buffer_size) {
315 return 0;
316 }
317
318 int sources_mixed = 0;
319
320 // Mix all active sources except the excluded one
321 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
322 if (!ctx->sources[i].active || ctx->sources[i].source_id == exclude_id) {
323 continue;
324 }
325
326 if (!ctx->sources[i].buffer) {
327 continue;
328 }
329
330 // Read samples from this source
331 size_t samples_read = audio_ring_buffer_read(ctx->sources[i].buffer, ctx->mix_buffer, num_samples);
332
333 if (samples_read == 0) {
334 continue;
335 }
336
337 // Add to output mix
338 for (size_t j = 0; j < samples_read; j++) {
339 output[j] += ctx->mix_buffer[j];
340 }
341
342 sources_mixed++;
343 }
344
345 // Apply simple clipping prevention if multiple sources were mixed
346 if (sources_mixed > 1) {
347 float scale = 1.0f / (float)sources_mixed;
348 for (size_t i = 0; i < num_samples; i++) {
349 output[i] *= scale;
350 // Soft clipping
351 if (output[i] > 1.0f) {
352 output[i] = 1.0f;
353 } else if (output[i] < -1.0f) {
354 output[i] = -1.0f;
355 }
356 }
357 }
358
359 return num_samples;
360}
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)

References audio_ring_buffer_read(), and SESSION_AUDIO_MAX_SOURCES.

◆ session_audio_read_captured()

size_t session_audio_read_captured ( session_audio_ctx_t *  ctx,
float *  buffer,
size_t  num_samples 
)

Definition at line 200 of file lib/session/audio.c.

200 {
201 if (!ctx || !ctx->initialized || !buffer || num_samples == 0) {
202 return 0;
203 }
204
205 asciichat_error_t result = audio_read_samples(&ctx->audio_ctx, buffer, (int)num_samples);
206 if (result != ASCIICHAT_OK) {
207 return 0;
208 }
209
210 return num_samples;
211}
asciichat_error_t audio_read_samples(audio_context_t *ctx, float *buffer, int num_samples)

References audio_read_samples().

◆ session_audio_remove_source()

void session_audio_remove_source ( session_audio_ctx_t *  ctx,
uint32_t  source_id 
)

Definition at line 259 of file lib/session/audio.c.

259 {
260 if (!ctx || !ctx->initialized || !ctx->is_host) {
261 return;
262 }
263
264 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
265 if (ctx->sources[i].active && ctx->sources[i].source_id == source_id) {
266 if (ctx->sources[i].buffer) {
267 audio_ring_buffer_destroy(ctx->sources[i].buffer);
268 ctx->sources[i].buffer = NULL;
269 }
270 ctx->sources[i].active = false;
271 ctx->sources[i].source_id = 0;
272 ctx->source_count--;
273 return;
274 }
275 }
276}

References audio_ring_buffer_destroy(), and SESSION_AUDIO_MAX_SOURCES.

◆ session_audio_start_capture()

asciichat_error_t session_audio_start_capture ( session_audio_ctx_t *  ctx)

Definition at line 145 of file lib/session/audio.c.

145 {
146 if (!ctx || !ctx->initialized) {
147 return SET_ERRNO(ERROR_INVALID_PARAM, "session_audio_start_capture: invalid context");
148 }
149
150 // Use full duplex for proper echo cancellation
151 return session_audio_start_duplex(ctx);
152}
asciichat_error_t session_audio_start_duplex(session_audio_ctx_t *ctx)

References session_audio_start_duplex().

◆ session_audio_start_duplex()

asciichat_error_t session_audio_start_duplex ( session_audio_ctx_t *  ctx)

Definition at line 163 of file lib/session/audio.c.

163 {
164 if (!ctx || !ctx->initialized) {
165 return SET_ERRNO(ERROR_INVALID_PARAM, "session_audio_start_duplex: invalid context");
166 }
167
168 if (ctx->running) {
169 return ASCIICHAT_OK; // Already running
170 }
171
172 asciichat_error_t result = audio_start_duplex(&ctx->audio_ctx);
173 if (result == ASCIICHAT_OK) {
174 ctx->running = true;
175 }
176
177 return result;
178}
asciichat_error_t audio_start_duplex(audio_context_t *ctx)

References audio_start_duplex().

Referenced by session_audio_start_capture(), session_audio_start_playback(), and session_participant_start_audio_capture().

◆ session_audio_start_playback()

asciichat_error_t session_audio_start_playback ( session_audio_ctx_t *  ctx)

Definition at line 154 of file lib/session/audio.c.

154 {
155 if (!ctx || !ctx->initialized) {
156 return SET_ERRNO(ERROR_INVALID_PARAM, "session_audio_start_playback: invalid context");
157 }
158
159 // Use full duplex for proper echo cancellation
160 return session_audio_start_duplex(ctx);
161}

References session_audio_start_duplex().

◆ session_audio_stop()

void session_audio_stop ( session_audio_ctx_t *  ctx)

Definition at line 180 of file lib/session/audio.c.

180 {
181 if (!ctx || !ctx->initialized || !ctx->running) {
182 return;
183 }
184
185 (void)audio_stop_duplex(&ctx->audio_ctx);
186 ctx->running = false;
187}
asciichat_error_t audio_stop_duplex(audio_context_t *ctx)

References audio_stop_duplex().

Referenced by session_audio_destroy(), session_participant_start_audio_capture(), and session_participant_stop_audio_capture().

◆ session_audio_write_playback()

asciichat_error_t session_audio_write_playback ( session_audio_ctx_t *  ctx,
const float *  buffer,
size_t  num_samples 
)

Definition at line 213 of file lib/session/audio.c.

213 {
214 if (!ctx || !ctx->initialized || !buffer) {
215 return SET_ERRNO(ERROR_INVALID_PARAM, "session_audio_write_playback: invalid parameter");
216 }
217
218 return audio_write_samples(&ctx->audio_ctx, buffer, (int)num_samples);
219}
asciichat_error_t audio_write_samples(audio_context_t *ctx, const float *buffer, int num_samples)

References audio_write_samples().

◆ session_audio_write_source()

asciichat_error_t session_audio_write_source ( session_audio_ctx_t *  ctx,
uint32_t  source_id,
const float *  samples,
size_t  num_samples 
)

Definition at line 278 of file lib/session/audio.c.

279 {
280 if (!ctx || !ctx->initialized || !samples) {
281 return SET_ERRNO(ERROR_INVALID_PARAM, "session_audio_write_source: invalid parameter");
282 }
283
284 if (!ctx->is_host) {
285 return SET_ERRNO(ERROR_INVALID_STATE, "session_audio_write_source: not a host context");
286 }
287
288 // Find the source
289 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
290 if (ctx->sources[i].active && ctx->sources[i].source_id == source_id) {
291 if (ctx->sources[i].buffer) {
292 return audio_ring_buffer_write(ctx->sources[i].buffer, samples, (int)num_samples);
293 }
294 return SET_ERRNO(ERROR_INVALID_STATE, "Source buffer not initialized");
295 }
296 }
297
298 return SET_ERRNO(ERROR_NOT_FOUND, "Audio source not found: %u", source_id);
299}
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)

References audio_ring_buffer_write(), and SESSION_AUDIO_MAX_SOURCES.