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