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