bad char
[vg.git] / src / vg / vg_audio.h
1 /* Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved */
2
3 #define MA_NO_GENERATION
4 #define MA_NO_DECODING
5 #define MA_NO_ENCODING
6 #include "dr_soft/miniaudio.h"
7
8 #define STB_VORBIS_MAX_CHANNELS 2
9 #include "stb/stb_vorbis.h"
10
11 #define SFX_MAX_SYSTEMS 32
12 #define SFX_FLAG_STEREO 0x2
13 #define SFX_FLAG_REPEAT 0x4
14 #define SFX_FLAG_PERSISTENT 0x8
15 #define FADEOUT_LENGTH 4410
16 #define FADEOUT_DIVISOR (1.f/(float)FADEOUT_LENGTH)
17
18 typedef struct sfx_vol_control sfx_vol_control;
19 typedef struct sfx_system sfx_system;
20
21 struct sfx_vol_control
22 {
23 float val;
24 const char *name;
25 };
26
27 struct sfx_system
28 {
29 sfx_system *persisitent_source;
30 int in_queue;
31
32 /* Source buffer start */
33 float *source, *replacement;
34
35 u32 clip_start, clip_end, buffer_length;
36
37 /* Modifiers */
38 sfx_vol_control *vol_src;
39 float vol, cvol, pan;
40
41 u32 delay;
42
43 /* Info */
44 u32 ch, end, cur;
45 u32 flags;
46
47 /* Effects */
48 u32 fadeout, fadeout_current;
49
50 /* Diagnostic */
51 const char *name;
52 };
53
54 /* Set of up to 8 sound effects packed into one */
55 typedef struct sfx_set sfx_set;
56 struct sfx_set
57 {
58 float *main;
59 char *sources;
60
61 u32 segments[32]; /* from->to,from->to ... */
62 u32 numsegments;
63 u32 ch;
64 u32 flags;
65 };
66
67 ma_device g_aud_device;
68 ma_device_config g_aud_dconfig;
69
70 /*
71 * Thread 1 - audio engine ( spawned from miniaudio.h )
72 * ======================================================
73 */
74 sfx_system sfx_sys[SFX_MAX_SYSTEMS];
75 int sfx_sys_len = 0;
76
77 /*
78 * Thread 0 - Critical transfer section
79 * ======================================================
80 */
81 MUTEX_TYPE sfx_mux_t01; /* Resources share: 0 & 1 */
82
83 sfx_system *sfx_q[SFX_MAX_SYSTEMS]; /* Stuff changed */
84 int sfx_q_len = 0;
85
86 float g_master_volume = 1.f;
87
88 /* Decompress entire vorbis stream into buffer */
89 static float *sfx_vorbis_stream( const unsigned char *data, int len,
90 int channels, u32 *samples )
91 {
92 int err;
93 stb_vorbis *pv = stb_vorbis_open_memory( data, len, &err, NULL );
94
95 if( !pv )
96 {
97 vg_error( "stb_vorbis_open_memory() failed with error code: %i\n", err );
98 return NULL;
99 }
100
101 u32 length_samples = stb_vorbis_stream_length_in_samples( pv );
102 float *buffer = (float *)malloc( length_samples * channels * sizeof(float));
103
104 if( !buffer )
105 {
106 stb_vorbis_close( pv );
107 vg_error( "out of memory while allocating sound resource\n" );
108 return NULL;
109 }
110
111 int read_samples = stb_vorbis_get_samples_float_interleaved(
112 pv, channels, buffer, length_samples * channels );
113
114 if( read_samples != length_samples )
115 {
116 vg_warn( "| warning: sample count mismatch. Expected %u got %i\n",
117 length_samples, read_samples );
118 length_samples = read_samples;
119 }
120
121 stb_vorbis_close( pv );
122 *samples = length_samples;
123 return buffer;
124 }
125
126 static float *sfx_vorbis( const char *strFileName, int channels, u32 *samples )
127 {
128 i64 len;
129 void *filedata = vg_asset_read_s( strFileName, &len );
130
131 if( filedata )
132 {
133 float *wav = sfx_vorbis_stream( filedata, len, channels, samples );
134 free( filedata );
135 return wav;
136 }
137 else
138 {
139 vg_error( "OGG load failed\n" );
140 return NULL;
141 }
142 }
143
144 /*
145 * thread 0 / client code
146 */
147 static int sfx_begin_edit( sfx_system *sys )
148 {
149 MUTEX_LOCK( sfx_mux_t01 );
150
151 if( sfx_q_len >= SFX_MAX_SYSTEMS && !sys->in_queue )
152 {
153 MUTEX_UNLOCK( sfx_mux_t01 );
154 vg_warn( "Warning: No free space in sound queue\n" );
155 return 0;
156 }
157
158 return 1;
159 }
160
161 static void sfx_end_edit( sfx_system *sys )
162 {
163 MUTEX_UNLOCK( sfx_mux_t01 );
164 }
165
166 /* Mark change to be uploaded to queue system */
167 static int sfx_push( sfx_system *sys )
168 {
169 if( !sys->in_queue )
170 {
171 sfx_q[ sfx_q_len ++ ] = sys;
172 sys->in_queue = 1;
173 }
174
175 MUTEX_UNLOCK( sfx_mux_t01 );
176
177 return 1;
178 }
179
180 /* Edit a volume float, has to be function wrapped because of mutex */
181 static void sfx_vol_fset( sfx_vol_control *src, float to )
182 {
183 MUTEX_LOCK( sfx_mux_t01 );
184
185 src->val = to;
186
187 MUTEX_UNLOCK( sfx_mux_t01 );
188 }
189
190 /* thread-safe get volume value */
191 static float sfx_vol_fget( sfx_vol_control *src )
192 {
193 float val;
194
195 MUTEX_LOCK( sfx_mux_t01 );
196
197 val = src->val;
198
199 MUTEX_UNLOCK( sfx_mux_t01 );
200
201 return val;
202 }
203
204 /* thread-safe set master volume */
205 static void sfx_set_master( float to )
206 {
207 MUTEX_LOCK( sfx_mux_t01 );
208
209 g_master_volume = to;
210
211 MUTEX_UNLOCK( sfx_mux_t01 );
212 }
213
214 /* thread-safe get master volume */
215 static float sfx_get_master(void)
216 {
217 float val;
218
219 MUTEX_LOCK( sfx_mux_t01 );
220
221 val = g_master_volume;
222
223 MUTEX_UNLOCK( sfx_mux_t01 );
224
225 return val;
226 }
227
228 void audio_mixer_callback( ma_device *pDevice, void *pOutBuf,
229 const void *pInput, ma_uint32 frameCount );
230
231 static void vg_audio_init(void)
232 {
233 g_aud_dconfig = ma_device_config_init( ma_device_type_playback );
234 g_aud_dconfig.playback.format = ma_format_f32;
235 g_aud_dconfig.playback.channels = 2;
236 g_aud_dconfig.sampleRate = 44100;
237 g_aud_dconfig.dataCallback = audio_mixer_callback;
238
239 g_aud_dconfig.pUserData = NULL;
240
241 vg_info( "Starting audio engine\n" );
242
243 if( ma_device_init( NULL, &g_aud_dconfig, &g_aud_device ) != MA_SUCCESS )
244 {
245 vg_exiterr( "ma_device failed to initialize" );
246 }
247 else
248 {
249 if( ma_device_start( &g_aud_device ) != MA_SUCCESS )
250 {
251 ma_device_uninit( &g_aud_device );
252 vg_exiterr( "ma_device failed to start" );
253 }
254 }
255 }
256
257 static void vg_audio_free(void)
258 {
259 ma_device_uninit( &g_aud_device );
260 }
261
262 /*
263 * thread 1
264 */
265
266 static sfx_system *sfx_alloc(void)
267 {
268 if( sfx_sys_len >= SFX_MAX_SYSTEMS )
269 return NULL;
270
271 /*
272 * A conditional is done against this in localization step,
273 * Needs to be initialized.
274 */
275 sfx_sys[ sfx_sys_len ].source = NULL;
276
277 return sfx_sys + (sfx_sys_len++);
278 }
279
280 /* Fetch samples into pcf */
281 static void audio_mixer_getsamples( float *pcf, float *source, u32 cur, u32 ch )
282 {
283 if( ch == 2 )
284 {
285 pcf[0] = source[ cur*2+0 ];
286 pcf[1] = source[ cur*2+1 ];
287 }
288 else
289 {
290 pcf[0] = source[ cur ];
291 pcf[1] = source[ cur ];
292 }
293 }
294
295 /*
296 * callback from miniaudio.h interface
297 */
298 void audio_mixer_callback( ma_device *pDevice, void *pOutBuf,
299 const void *pInput, ma_uint32 frameCount )
300 {
301 /* Process incoming sound queue */
302 MUTEX_LOCK( sfx_mux_t01 );
303
304 while( sfx_q_len --> 0 )
305 {
306 sfx_system *src = sfx_q[sfx_q_len];
307 sfx_system *clone;
308
309 src->in_queue = 0;
310
311 clone = sfx_alloc();
312 *clone = *src;
313
314 /* Links need to exist on persistent sounds */
315 clone->persisitent_source = src->flags & SFX_FLAG_PERSISTENT? src: NULL;
316 }
317
318 sfx_q_len = 0;
319
320 /* Volume modifiers */
321 for( int i = 0; i < sfx_sys_len; i ++ )
322 {
323 sfx_system *sys = sfx_sys + i;
324
325 /* Apply persistent volume if linked */
326 if( sys->flags & SFX_FLAG_PERSISTENT )
327 {
328 sys->vol = sys->persisitent_source->vol * g_master_volume;
329 sys->pan = sys->persisitent_source->pan;
330
331 /* Fadeout effect ( + remove ) */
332 if( sys->persisitent_source->fadeout )
333 {
334 sys->fadeout_current = sys->persisitent_source->fadeout_current;
335 sys->fadeout = sys->persisitent_source->fadeout;
336
337 sys->persisitent_source = NULL;
338 sys->flags &= ~SFX_FLAG_PERSISTENT;
339 }
340 }
341
342 /* Apply volume slider if it has one linked */
343 if( sys->vol_src )
344 sys->cvol = sys->vol * sys->vol_src->val;
345 else
346 sys->cvol = sys->vol;
347 }
348
349 MUTEX_UNLOCK( sfx_mux_t01 );
350
351 /* Clear buffer */
352 float *pOut32F = (float *)pOutBuf;
353 for( int i = 0; i < frameCount * 2; i ++ ){
354 pOut32F[i] = 0.f;
355 }
356
357 for( int i = 0; i < sfx_sys_len; i ++ )
358 {
359 sfx_system *sys = sfx_sys + i;
360
361 u32 cursor = sys->cur, buffer_pos = 0;
362 float pcf[2] = { 0.f, 0.0f };
363
364 u32 frames_write = frameCount;
365 float fadeout_divisor = 1.0f / (float)sys->fadeout;
366
367 while( frames_write )
368 {
369 u32 samples_this_run = VG_MIN( frames_write, sys->end - cursor );
370
371 if( sys->fadeout )
372 {
373 /* Force this system to be removed now */
374 if( sys->fadeout_current == 0 )
375 {
376 sys->flags &= 0x00000000;
377 sys->cur = sys->end;
378 break;
379 }
380
381 samples_this_run = VG_MIN( samples_this_run, sys->fadeout_current );
382 }
383
384 for( u32 j=0; j<samples_this_run; j++ )
385 {
386 audio_mixer_getsamples( pcf, sys->source, cursor, sys->ch );
387
388 float vol = sys->cvol;
389
390 if( sys->fadeout )
391 {
392 vol *= (float)sys->fadeout_current * fadeout_divisor;
393 sys->fadeout_current --;
394 }
395
396 if( buffer_pos >= frameCount )
397 {
398 break;
399 }
400
401 float sl = 1.0f-sys->pan,
402 sr = 1.0f+sys->pan;
403
404 pOut32F[ buffer_pos*2+0 ] += pcf[0] * vol * sl;
405 pOut32F[ buffer_pos*2+1 ] += pcf[1] * vol * sr;
406
407 cursor ++;
408 buffer_pos ++;
409 }
410
411 frames_write -= samples_this_run;
412
413 if( sys->flags & SFX_FLAG_REPEAT )
414 {
415 if( frames_write )
416 {
417 cursor = sys->clip_start;
418 continue;
419 }
420 }
421
422 sys->cur = cursor;
423 break;
424 }
425 }
426
427 /* Redistribute sound systems */
428 MUTEX_LOCK( sfx_mux_t01 );
429
430 u32 idx = 0, wr = 0;
431 while( idx != sfx_sys_len )
432 {
433 sfx_system *src = sfx_sys + idx;
434
435 /* Keep only if cursor is before end or repeating */
436 if( src->cur < src->end || (src->flags & SFX_FLAG_REPEAT) )
437 {
438 sfx_sys[ wr ++ ] = sfx_sys[ idx ];
439 }
440
441 idx ++ ;
442 }
443 sfx_sys_len = wr;
444
445 MUTEX_UNLOCK( sfx_mux_t01 );
446
447 (void)pInput;
448 }
449
450 /*
451 * Load strings into sfx_set's memory
452 * String layout: "sounda.ogg\0soundb.ogg\0soundc.ogg\0\0"
453 */
454 static void sfx_set_strings( sfx_set *dest, char *strSources,
455 u32 flags, int bAsync )
456 {
457 dest->ch = (flags & SFX_FLAG_STEREO)? 2: 1;
458
459 dest->main = NULL;
460 dest->numsegments = 0;
461 char *source = strSources;
462
463 u32 total = 0;
464 int len;
465 while( (len = strlen( source )) )
466 {
467 u32 samples;
468 float *sound = sfx_vorbis( source, dest->ch, &samples );
469
470 if( !sound )
471 {
472 free( dest->main );
473 dest->numsegments = 0;
474 return;
475 }
476
477 total += samples;
478
479 float *nbuf = realloc( dest->main, total * dest->ch * sizeof(float) );
480
481 if( nbuf )
482 {
483 dest->main = nbuf;
484 memcpy( dest->main + (total-samples)*dest->ch,
485 sound, samples*dest->ch*sizeof(float) );
486 free( sound );
487
488 dest->segments[ dest->numsegments*2+0 ] = total-samples;
489 dest->segments[ dest->numsegments*2+1 ] = total;
490 }
491 else
492 {
493 vg_error( "realloc() failed\n" );
494 free( sound );
495 return;
496 }
497
498 source += len +1;
499 dest->numsegments ++;
500 }
501 }
502
503 static void sfx_set_init( sfx_set *dest, char *sources )
504 {
505 if( !sources )
506 sfx_set_strings( dest, dest->sources, dest->flags, 0 );
507 else
508 sfx_set_strings( dest, sources, dest->flags, 0 );
509 }
510
511 static void sfx_set_play( sfx_set *source, sfx_system *sys, int id )
512 {
513 if( sfx_begin_edit( sys ) )
514 {
515 sys->fadeout = 0;
516 sys->fadeout_current = 0;
517 sys->source = source->main;
518 sys->cur = source->segments[ id*2 + 0 ];
519 sys->end = source->segments[ id*2 + 1 ];
520 sys->ch = source->ch;
521
522 /* for diagnostics */
523 sys->clip_start = sys->cur;
524 sys->clip_end = sys->end;
525 sys->buffer_length = source->segments[ (source->numsegments-1)*2 + 1 ];
526
527 sfx_push( sys );
528 }
529 }
530
531 /* Pick a random sound from the buffer and play it into system */
532 static void sfx_set_playrnd( sfx_set *source, sfx_system *sys,
533 int min_id, int max_id )
534 {
535 if( !source->numsegments )
536 return;
537
538 if( max_id > source->numsegments )
539 {
540 vg_error( "Max ID out of range for playrnd\n" );
541 return;
542 }
543
544 int pick = (rand() % (max_id-min_id)) + min_id;
545
546 sfx_set_play( source, sys, pick );
547 }
548
549 static void sfx_system_fadeout( sfx_system *sys, u32 length_samples )
550 {
551 if( sfx_begin_edit( sys ) )
552 {
553 sys->fadeout_current = length_samples;
554 sys->fadeout = length_samples;
555
556 sfx_end_edit( sys );
557 }
558 }
559
560 static void sfx_set_free( sfx_set *set )
561 {
562 free( set->main );
563 }