bring audio code inline with current standards
[fishladder.git] / vg / vg_audio.h
1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
2
3 #define MINIAUDIO_IMPLEMENTATION
4 #include "dr_soft/miniaudio.h"
5
6 #define STB_VORBIS_MAX_CHANNELS 2
7 #include "stb/stb_vorbis.h"
8
9 #define SFX_MAX_SYSTEMS 16
10 #define SFX_FLAG_ONESHOT 0x1
11 #define SFX_FLAG_STEREO 0x2
12 #define FADEOUT_LENGTH 441
13 #define FADEOUT_DIVISOR (1.f/(float)FADEOUT_LENGTH)
14
15 typedef struct sfx_vol_control sfx_vol_control;
16 typedef struct sfx_system sfx_system;
17
18 struct sfx_vol_control
19 {
20 float val;
21 const char *name;
22 };
23
24 struct sfx_system
25 {
26 // Source buffer start
27 float *source, *replacement;
28
29 // Modifiers
30 sfx_vol_control *vol_src;
31 float vol;
32
33 // Info
34 int ch, end, cur, flags;
35
36 // Effects
37 u32 fadeout, fadeout_length, fadeout_cursor;
38
39 sfx_system *thread_clone; // Memory of this structure is copied into thread 2
40
41 // Diagnostic
42 float signal_average; // Current signal volume
43 const char *name;
44 };
45
46 // Set of up to 8 sound effects packed into one
47 typedef struct sfx_set sfx_set;
48 struct sfx_set
49 {
50 float *main;
51 char *sources;
52
53 u32 segments[16]; //from->to,from->to ...
54 u32 numsegments;
55 u32 ch;
56 u32 flags;
57 };
58
59 ma_device g_aud_device;
60 ma_device_config g_aud_dconfig;
61
62 // Thread 1 - audio engine ( spawned from miniaudio.h )
63 // ======================================================
64 sfx_system sfx_sys[SFX_MAX_SYSTEMS];
65 int sfx_sys_len = 0;
66
67 // Thread 0 - Critical transfer section
68 // ======================================================
69 MUTEX_TYPE sfx_mux_t01; // Resources share: 0 & 1
70
71 sfx_system *sfx_q[SFX_MAX_SYSTEMS]; // Stuff changed
72 int sfx_q_len = 0; // How much
73
74 // x / 2
75 // ======================================================
76
77 // g_vol_master is never directly acessed by users
78 float g_master_volume = 1.f;
79
80 // Decompress entire vorbis stream into buffer
81 static float *sfx_vorbis_stream( const unsigned char *data, int len, int channels, u32 *samples )
82 {
83 int err;
84 stb_vorbis *pv = stb_vorbis_open_memory( data, len, &err, NULL );
85
86 if( !pv )
87 {
88 vg_error( "stb_vorbis_open_memory() failed with error code: %i\n", err );
89 return NULL;
90 }
91
92 u32 length_samples = stb_vorbis_stream_length_in_samples( pv );
93 float *buffer = (float *)malloc( length_samples * channels * sizeof( float ));
94
95 if( !buffer )
96 {
97 stb_vorbis_close( pv );
98 vg_error( "out of memory while allocating sound resource\n" );
99 return NULL;
100 }
101
102 int read_samples = stb_vorbis_get_samples_float_interleaved( pv, channels, buffer, length_samples * channels );
103 if( read_samples != length_samples )
104 {
105 vg_warn( "| warning: sample count mismatch. Expected %u got %i\n", length_samples, read_samples );
106 length_samples = read_samples;
107 }
108
109 stb_vorbis_close( pv );
110 *samples = length_samples;
111 return buffer;
112 }
113
114 static float *sfx_vorbis( const char *strFileName, int channels, u32 *samples )
115 {
116 i64 len;
117 void *filedata = vg_asset_read_s( strFileName, &len );
118
119 if( filedata )
120 {
121 float *wav = sfx_vorbis_stream( filedata, len, channels, samples );
122 free( filedata );
123 return wav;
124 }
125 else
126 {
127 vg_error( "OGG load failed\n" );
128 return NULL;
129 }
130 }
131
132 typedef struct sfx_bgload sfx_bgload_t;
133 struct sfx_bgload
134 {
135 char *path;
136 u32 channels;
137
138 float *buffer;
139 u32 samples;
140
141 void *user;
142
143 void(*OnComplete)(sfx_bgload_t *inf);
144 };
145
146 // Thread worker for background load job
147 void *sfx_vorbis_a_t( void *_inf )
148 {
149 sfx_bgload_t *info = _inf;
150
151 // Load the ogg clip
152 info->buffer = sfx_vorbis( info->path, info->channels, &info->samples );
153 info->OnComplete( info );
154
155 return NULL;
156 }
157
158 // Asynchronous resource load
159 int sfx_vorbis_a( const char *path, int channels, void(*OnComplete)(sfx_bgload_t *inf), void *user )
160 {
161 vg_info( "background job started for: %s\n", path );
162
163 sfx_bgload_t *params = malloc( sizeof( sfx_bgload_t ) );
164 params->path = malloc( strlen( path ) + 1 );
165 strcpy( params->path, path );
166 params->OnComplete = OnComplete;
167 params->user = user;
168 params->channels = channels;
169
170 return vg_thread_run( sfx_vorbis_a_t, params );
171 }
172
173 // Asynchronous load-to-system callback
174 struct sfx_vorbis_a_to_inf
175 {
176 sfx_system *sys;
177 u32 flags;
178 };
179
180 #define SFX_A_FLAG_AUTOSTART 0x1
181 #define SFX_A_FLAG_AUTOFREE 0x2
182
183 static int sfx_save( sfx_system *sys );
184
185 // Asynchronous load-to-system callback
186 void sfx_vorbis_a_to_c( sfx_bgload_t *loadinf )
187 {
188 struct sfx_vorbis_a_to_inf *inf = loadinf->user;
189
190 // Mark buffer for deallocation if autofree is set
191 if( inf->flags & SFX_A_FLAG_AUTOFREE )
192 inf->sys->replacement = loadinf->buffer;
193 else
194 inf->sys->source = loadinf->buffer;
195
196 inf->sys->end = loadinf->samples;
197
198 if( inf->flags & SFX_A_FLAG_AUTOSTART )
199 sfx_save( inf->sys );
200
201 free( loadinf->path );
202 free( loadinf );
203 free( inf );
204 }
205
206 // Asynchronous vorbis load into audio system
207 void sfx_vorbis_a_to( sfx_system *sys, const char *strFileName, int channels, u32 flags )
208 {
209 struct sfx_vorbis_a_to_inf *inf = malloc( sizeof( struct sfx_vorbis_a_to_inf ) );
210 inf->flags = flags;
211 inf->sys = sys;
212
213 sys->ch = channels;
214
215 if( !sfx_vorbis_a( strFileName, channels, sfx_vorbis_a_to_c, inf ) )
216 free( inf );
217 }
218
219 // 0
220 // ======================================================
221
222 // Mark change to be uploaded to queue system
223 static int sfx_save( sfx_system *sys )
224 {
225 MUTEX_LOCK( sfx_mux_t01 );
226
227 if( sfx_q_len >= SFX_MAX_SYSTEMS )
228 {
229 vg_error( "Warning: No free space in sound queue\n" );
230
231 MUTEX_UNLOCK( sfx_mux_t01 );
232 return 0;
233 }
234
235 // Mark change in queue
236 sfx_q[ sfx_q_len ++ ] = sys;
237
238 MUTEX_UNLOCK( sfx_mux_t01 );
239
240 return 1;
241 }
242
243 // Edit a volume float, has to be function wrapped because of mutex
244 static void sfx_vol_fset( sfx_vol_control *src, float to )
245 {
246 MUTEX_LOCK( sfx_mux_t01 );
247
248 src->val = to;
249
250 MUTEX_UNLOCK( sfx_mux_t01 );
251 }
252
253 // thread-safe get volume value
254 static float sfx_vol_fget( sfx_vol_control *src )
255 {
256 float val;
257
258 MUTEX_LOCK( sfx_mux_t01 );
259
260 val = src->val;
261
262 MUTEX_UNLOCK( sfx_mux_t01 );
263
264 return val;
265 }
266
267 // thread-safe set master volume
268 static void sfx_set_master( float to )
269 {
270 MUTEX_LOCK( sfx_mux_t01 );
271
272 g_master_volume = to;
273
274 MUTEX_UNLOCK( sfx_mux_t01 );
275 }
276
277 // thread-safe get master volume
278 static float sfx_get_master(void)
279 {
280 float val;
281
282 MUTEX_LOCK( sfx_mux_t01 );
283
284 val = g_master_volume;
285
286 MUTEX_UNLOCK( sfx_mux_t01 );
287
288 return val;
289 }
290
291 void audio_mixer_callback( ma_device *pDevice, void *pOutBuf, const void *pInput, ma_uint32 frameCount );
292
293 // Miniaudio.h init
294 static void vg_audio_init(void)
295 {
296 g_aud_dconfig = ma_device_config_init( ma_device_type_playback );
297 g_aud_dconfig.playback.format = ma_format_f32;
298 g_aud_dconfig.playback.channels = 2;
299 g_aud_dconfig.sampleRate = 44100;
300 g_aud_dconfig.dataCallback = audio_mixer_callback;
301
302 g_aud_dconfig.pUserData = NULL;
303
304 vg_info( "Starting audio engine\n" );
305
306 if( ma_device_init( NULL, &g_aud_dconfig, &g_aud_device ) != MA_SUCCESS )
307 {
308 vg_exiterr( "ma_device failed to initialize" );
309 }
310 else
311 {
312 if( ma_device_start( &g_aud_device ) != MA_SUCCESS )
313 {
314 ma_device_uninit( &g_aud_device );
315 vg_exiterr( "ma_device failed to start" );
316 }
317 }
318 }
319
320 #ifndef VYGER_RELEASE
321 u32 num_sfx_sets = 0;
322 #endif
323
324 // Shutdown audio device
325 static void vg_audio_free(void)
326 {
327 ma_device_uninit( &g_aud_device );
328 }
329
330 // (debug) make sure we are shutting down safely
331 static void sfx_sys_chkerr(void)
332 {
333 #ifndef VYGER_RELEASE
334 if( num_sfx_sets )
335 {
336 vg_error( "Leaked %u sfx sets\n", num_sfx_sets );
337 }
338 #endif
339 }
340
341 // 1
342 // ======================================================
343
344 // Create and return slot for a sound
345 static sfx_system *sfx_alloc(void)
346 {
347 if( sfx_sys_len >= SFX_MAX_SYSTEMS )
348 {
349 vg_error( "Warning: No free space in sound system\n" );
350
351 return NULL;
352 }
353
354 // A conditional is done against this in localization step,
355 // Needs to be initialized.
356 sfx_sys[ sfx_sys_len ].source = NULL;
357
358 return sfx_sys + (sfx_sys_len++);
359 }
360
361 // Fetch samples into pcf
362 static void audio_mixer_getsamples( float *pcf, float *source, u32 cur, u32 ch )
363 {
364 if( ch == 2 )
365 {
366 pcf[0] = source[ cur*2+0 ];
367 pcf[1] = source[ cur*2+1 ];
368 }
369 else
370 {
371 pcf[0] = source[ cur ];
372 pcf[1] = source[ cur ];
373 }
374 }
375
376 // miniaudio.h interface
377 void audio_mixer_callback( ma_device *pDevice, void *pOutBuf, const void *pInput, ma_uint32 frameCount )
378 {
379 // Process incoming sound queue
380 MUTEX_LOCK( sfx_mux_t01 );
381
382 while( sfx_q_len --> 0 )
383 {
384 sfx_system *src = sfx_q[sfx_q_len];
385
386 // This is a 'new' sound if thread_clone not set.
387 if( !src->thread_clone || src->flags & SFX_FLAG_ONESHOT )
388 {
389 src->thread_clone = sfx_alloc();
390 }
391
392 // run replacement routine if one is waiting
393 if( src->replacement )
394 {
395 free( src->source );
396
397 src->source = src->replacement;
398 src->replacement = NULL;
399 }
400
401 src->thread_clone->source = src->source;
402
403 // Localize data to thread 1's memory pool
404 src->thread_clone->ch = src->ch;
405 src->thread_clone->end = src->end;
406 src->thread_clone->cur = src->cur;
407 src->thread_clone->flags = src->flags;
408 src->thread_clone->fadeout = src->fadeout;
409 src->thread_clone->fadeout_length = src->fadeout_length;
410 src->thread_clone->vol_src = src->vol_src;
411 src->thread_clone->name = src->name;
412
413 // loopback pointer, mainly used for persistent sound handles
414 src->thread_clone->thread_clone = src;
415 }
416
417 sfx_q_len = 0;
418
419 // Pull in volume sliders
420 for( int i = 0; i < sfx_sys_len; i ++ )
421 {
422 sfx_system *sys = sfx_sys + i;
423 sys->vol = sys->thread_clone->vol * g_master_volume;
424
425 if( sys->vol_src )
426 sys->vol *= sys->vol_src->val;
427 }
428
429 MUTEX_UNLOCK( sfx_mux_t01 );
430
431 // Clear buffer ( is necessary ? )
432 float *pOut32F = (float *)pOutBuf;
433 for( int i = 0; i < frameCount * 2; i ++ ){
434 pOut32F[i] = 0.f;
435 }
436
437 // Do something with local..
438 for( int i = 0; i < sfx_sys_len; i ++ )
439 {
440 sfx_system *sys = sfx_sys + i;
441
442 u32 cursor = sys->cur;
443 u32 bpos = 0;
444
445 float avgvol = 0.f;
446 float pcf[2] = { 0.f, 0.0f };
447
448 if( sys->fadeout_cursor != sys->cur )
449 sys->fadeout = sys->fadeout_length;
450
451 while( cursor < vg_min( sys->cur + frameCount, sys->end ) )
452 {
453 audio_mixer_getsamples( pcf, sys->source, cursor, sys->ch );
454
455 avgvol += fabs( pcf[0] * sys->vol );
456 avgvol += fabs( pcf[1] * sys->vol );
457
458 pOut32F[ bpos*2+0 ] += pcf[0] * sys->vol;
459 pOut32F[ bpos*2+1 ] += pcf[1] * sys->vol;
460
461 // Blend the fadeout cursor in to prevent popping
462 if( sys->fadeout )
463 {
464 if( sys->fadeout_cursor < sys->end )
465 {
466 audio_mixer_getsamples( pcf, sys->source, sys->fadeout_cursor, sys->ch );
467
468 float mul = (float)sys->fadeout * FADEOUT_DIVISOR;
469
470 pOut32F[ bpos*2+0 ] += pcf[0] * sys->vol * mul;
471 pOut32F[ bpos*2+1 ] += pcf[1] * sys->vol * mul;
472
473 sys->fadeout_cursor ++;
474 sys->fadeout --;
475 }
476 else
477 sys->fadeout = 0;
478 }
479
480 cursor ++;
481 bpos ++;
482 }
483
484 sys->signal_average = avgvol / (float)(bpos*2);
485 sys->cur += frameCount;
486 sys->fadeout_cursor = sys->cur;
487 }
488
489 // Redistribute sound systems
490 MUTEX_LOCK( sfx_mux_t01 );
491
492 unsigned int idx = 0, wr = 0;
493 while( idx != sfx_sys_len )
494 {
495 sfx_system *src = sfx_sys + idx;
496
497 // Keep only if cursor is before end
498 if( src->cur < src->end )
499 {
500 // Correct source pointer on persisitent originals since we shifted ID's
501 if( !(src->flags & SFX_FLAG_ONESHOT) )
502 src->thread_clone->thread_clone = sfx_sys + wr;
503
504 sfx_sys[ wr ++ ] = sfx_sys[ idx ];
505 }
506 else
507 {
508 // Clear link on persistent sources (done playing)
509 if( !(src->flags & SFX_FLAG_ONESHOT) )
510 src->thread_clone->thread_clone = NULL;
511 }
512
513 idx ++ ;
514 }
515 sfx_sys_len = wr;
516
517 MUTEX_UNLOCK( sfx_mux_t01 );
518
519 (void)pInput;
520 }
521
522 // Load strings into sfx_set's memory
523 // String layout: "sounda.ogg\0soundb.ogg\0soundc.ogg\0\0"
524 void sfx_set_strings( sfx_set *dest, char *strSources, u32 flags, int bAsync )
525 {
526 printf( "Init sfx set\n| start | end | length | name \n" );
527
528 dest->ch = (flags & SFX_FLAG_STEREO)? 2: 1;
529
530 dest->main = NULL;
531 dest->numsegments = 0;
532 char *source = strSources;
533
534 u32 total = 0;
535 int len;
536 while( (len = strlen( source )) )
537 {
538 u32 samples;
539 float *sound = sfx_vorbis( source, dest->ch, &samples );
540
541 if( !sound )
542 {
543 free( dest->main );
544 dest->numsegments = 0;
545 return;
546 }
547
548 total += samples;
549
550 float *nbuf = realloc( dest->main, total * dest->ch * sizeof(float) );
551
552 if( nbuf )
553 {
554 dest->main = nbuf;
555 memcpy( dest->main + (total-samples)*dest->ch, sound, samples*dest->ch*sizeof(float) );
556 free( sound );
557
558 dest->segments[ dest->numsegments*2+0 ] = total-samples;
559 dest->segments[ dest->numsegments*2+1 ] = total;
560
561 printf( "| %09u | %09u | %09u | %s\n", total-samples, total, samples, source );
562 }
563 else
564 {
565 vg_error( "realloc() failed\n" );
566 free( sound );
567 return;
568 }
569
570 source += len +1;
571 dest->numsegments ++;
572 }
573
574 vg_info( "finished, numsegments: %u\n", dest->numsegments );
575 }
576
577 void sfx_set_init( sfx_set *dest, char *sources )
578 {
579 #ifndef VYGER_RELEASE
580 num_sfx_sets ++;
581 #endif
582
583 if( !sources )
584 {
585 sfx_set_strings( dest, dest->sources, dest->flags, 0 );
586 }
587 else
588 {
589 sfx_set_strings( dest, sources, dest->flags, 0 );
590 }
591 }
592
593 // Pick a random sound from the buffer and play it into system
594 void sfx_set_playrnd( sfx_set *source, sfx_system *sys, int min_id, int max_id )
595 {
596 if( !source->numsegments )
597 {
598 return;
599 }
600
601 int pick = (rand() % (max_id-min_id)) + min_id;
602
603 sys->source = source->main;
604 sys->cur = source->segments[ pick*2 + 0 ];
605 sys->end = source->segments[ pick*2 + 1 ];
606 sys->ch = source->ch;
607
608 sfx_save( sys );
609 }
610
611 // Free set resources
612 void sfx_set_free( sfx_set *set )
613 {
614 #ifndef VYGER_RELEASE
615 num_sfx_sets --;
616 #endif
617 free( set->main );
618 }