1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
3 #define MINIAUDIO_IMPLEMENTATION
4 #include "dr_soft/miniaudio.h"
6 #define STB_VORBIS_MAX_CHANNELS 2
7 #include "stb/stb_vorbis.h"
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)
15 typedef struct sfx_vol sfx_vol_t
;
16 typedef struct sfx_system sfx_system_t
;
26 // Source buffer start
38 u32 end
; // buffer end
39 u32 cur
; // cursor position
46 // The 'Opposite' pointer
50 float cvol
; // Current signal volume
55 int sfx_save( sfx_system_t
*sys
); // Mark change to be uploaded to queue system
56 void sfx_sys_init(void); // Miniaudio.h init
57 void sfx_sys_free(void); // Shutdown audio device
58 sfx_system_t
*sfx_alloc(void); // Create and return slot for a sound
61 void sfx_localize(void); // Copy in data from all queued
62 void sfx_redist(void); // Send out updates to sources
63 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
); // miniaudio.h interface
65 ma_device g_aud_device
;
66 ma_device_config g_aud_dconfig
;
68 // Thread 2 - background loader ( to be moved )
69 // ======================================================
71 // Thread 1 - audio engine ( spawned from miniaudio.h )
72 // ======================================================
73 sfx_system_t sfx_sys
[SFX_MAX_SYSTEMS
];
76 // Thread 0 - Critical transfer section
77 // ======================================================
78 MUTEX_TYPE sfx_mux_t01
; // Resources share: 0 & 1
80 sfx_system_t
*sfx_q
[SFX_MAX_SYSTEMS
]; // Stuff changed
81 int sfx_q_len
= 0; // How much
84 // ======================================================
86 // g_vol_master is never directly acessed by users
87 float g_master_volume
= 1.f
;
89 sfx_vol_t g_vol_music
;
92 #define SFX_NUM_VOLUMES 2
93 sfx_vol_t
*g_volumes
[] = { &g_vol_music
, &g_vol_sfx
};
95 // Decompress entire vorbis stream into buffer
96 float *sfx_vorbis_stream( const unsigned char *data
, int len
, int channels
, uint32_t *samples
)
99 stb_vorbis
*pv
= stb_vorbis_open_memory( data
, len
, &err
, NULL
);
103 vg_error( "stb_vorbis_open_memory() failed with error code: %i\n", err
);
107 u32 length_samples
= stb_vorbis_stream_length_in_samples( pv
);
108 float *buffer
= (float *)malloc( length_samples
* channels
* sizeof( float ));
112 vg_error( "out of memory while allocating sound resource\n" );
116 int read_samples
= stb_vorbis_get_samples_float_interleaved( pv
, channels
, buffer
, length_samples
* channels
);
117 if( read_samples
!= length_samples
)
119 vg_warn( "| warning: sample count mismatch. Expected %u got %i\n", length_samples
, read_samples
);
120 length_samples
= read_samples
;
123 *samples
= length_samples
;
127 float *sfx_vorbis( const char *strFileName
, int channels
, u32
*samples
)
130 void *filedata
= vg_asset_read_s( strFileName
, &len
);
134 float *wav
= sfx_vorbis_stream( filedata
, len
, channels
, samples
);
140 vg_error( "OGG load failed\n" );
145 typedef struct sfx_bgload sfx_bgload_t
;
156 void(*OnComplete
)(sfx_bgload_t
*inf
);
159 // Thread worker for background load job
160 void *sfx_vorbis_a_t( void *_inf
)
162 sfx_bgload_t
*info
= _inf
;
165 info
->buffer
= sfx_vorbis( info
->path
, info
->channels
, &info
->samples
);
166 info
->OnComplete( info
);
171 // Asynchronous resource load
172 int sfx_vorbis_a( const char *path
, int channels
, void(*OnComplete
)(sfx_bgload_t
*inf
), void *user
)
174 vg_info( "background job started for: %s\n", path
);
176 sfx_bgload_t
*params
= malloc( sizeof( sfx_bgload_t
) );
177 params
->path
= malloc( strlen( path
) + 1 );
178 strcpy( params
->path
, path
);
179 params
->OnComplete
= OnComplete
;
181 params
->channels
= channels
;
183 return vg_thread_run( sfx_vorbis_a_t
, params
);
186 // Asynchronous load-to-system callback
187 struct sfx_vorbis_a_to_inf
193 #define SFX_A_FLAG_AUTOSTART 0x1
194 #define SFX_A_FLAG_AUTOFREE 0x2
196 // Asynchronous load-to-system callback
197 void sfx_vorbis_a_to_c( sfx_bgload_t
*loadinf
)
199 struct sfx_vorbis_a_to_inf
*inf
= loadinf
->user
;
201 // Mark buffer for deallocation if autofree is set
202 if( inf
->flags
& SFX_A_FLAG_AUTOFREE
)
204 inf
->sys
->replacement
= loadinf
->buffer
;
208 inf
->sys
->source
= loadinf
->buffer
;
211 inf
->sys
->end
= loadinf
->samples
;
213 if( inf
->flags
& SFX_A_FLAG_AUTOSTART
)
215 sfx_save( inf
->sys
);
218 free( loadinf
->path
);
223 // Asynchronous vorbis load into audio system
224 void sfx_vorbis_a_to( sfx_system_t
*sys
, const char *strFileName
, int channels
, uint32_t flags
)
226 struct sfx_vorbis_a_to_inf
*inf
= malloc( sizeof( struct sfx_vorbis_a_to_inf
) );
232 if( !sfx_vorbis_a( strFileName
, channels
, sfx_vorbis_a_to_c
, inf
) )
239 // ======================================================
241 // Mark change to be uploaded to queue system
242 int sfx_save( sfx_system_t
*sys
)
244 MUTEX_LOCK( sfx_mux_t01
);
246 if( sfx_q_len
>= SFX_MAX_SYSTEMS
)
248 vg_error( "Warning: No free space in sound queue\n" );
250 MUTEX_UNLOCK( sfx_mux_t01
);
254 // Mark change in queue
255 sfx_q
[ sfx_q_len
++ ] = sys
;
257 MUTEX_UNLOCK( sfx_mux_t01
);
262 // Edit a volume float, has to be function round-tripped
264 void sfx_vol_fset( sfx_vol_t
*src
, float to
)
266 MUTEX_LOCK( sfx_mux_t01
);
269 src
->cmp
= g_master_volume
* to
;
271 MUTEX_UNLOCK( sfx_mux_t01
);
274 // thread-safe get volume value
275 float sfx_vol_fget( sfx_vol_t
*src
)
279 MUTEX_LOCK( sfx_mux_t01
);
283 MUTEX_UNLOCK( sfx_mux_t01
);
288 // thread-safe set master volume
289 void sfx_set_master( float to
)
291 MUTEX_LOCK( sfx_mux_t01
);
293 g_master_volume
= to
;
295 for( int i
= 0; i
< SFX_NUM_VOLUMES
; i
++ )
297 g_volumes
[ i
]->cmp
= g_volumes
[ i
]->val
* g_master_volume
;
300 MUTEX_UNLOCK( sfx_mux_t01
);
303 // thread-safe get master volume
304 float sfx_get_master(void)
308 MUTEX_LOCK( sfx_mux_t01
);
310 val
= g_master_volume
;
312 MUTEX_UNLOCK( sfx_mux_t01
);
318 void vg_audio_init(void)
320 // Setup volume values
321 // Todo: load these from config
323 g_vol_music
.val
= 1.f
;
324 sfx_set_master( 1.f
);
326 g_aud_dconfig
= ma_device_config_init( ma_device_type_playback
);
327 g_aud_dconfig
.playback
.format
= ma_format_f32
;
328 g_aud_dconfig
.playback
.channels
= 2;
329 g_aud_dconfig
.sampleRate
= 44100;
330 g_aud_dconfig
.dataCallback
= audio_mixer_callback
;
332 g_aud_dconfig
.pUserData
= NULL
;
334 vg_info( "Starting audio engine\n" );
336 if( ma_device_init( NULL
, &g_aud_dconfig
, &g_aud_device
) != MA_SUCCESS
)
338 vg_exiterr( "ma_device failed to initialize" );
342 if( ma_device_start( &g_aud_device
) != MA_SUCCESS
)
344 ma_device_uninit( &g_aud_device
);
345 vg_exiterr( "ma_device failed to start" );
350 #ifndef VYGER_RELEASE
351 uint32_t num_sfx_sets
= 0;
354 // Shutdown audio device
355 void vg_audio_free(void)
357 ma_device_uninit( &g_aud_device
);
360 // (debug) make sure we are shutting down safely
361 void sfx_sys_chkerr(void)
363 #ifndef VYGER_RELEASE
366 vg_error( "Leaked %u sfx sets\n", num_sfx_sets
);
372 // ======================================================
374 // Create and return slot for a sound
375 sfx_system_t
*sfx_alloc(void)
377 if( sfx_sys_len
>= SFX_MAX_SYSTEMS
)
379 vg_error( "Warning: No free space in sound system\n" );
384 // A conditional is done against this in localization step,
385 // Needs to be initialized.
386 sfx_sys
[ sfx_sys_len
].source
= NULL
;
388 return sfx_sys
+ (sfx_sys_len
++);
391 // Copy in data from all queued
392 void sfx_localize(void)
394 MUTEX_LOCK( sfx_mux_t01
);
396 while( sfx_q_len
--> 0 )
398 sfx_system_t
*src
= sfx_q
[sfx_q_len
];
400 // This is a 'new' sound if optr not set.
401 if( !src
->optr
|| src
->flags
& SFX_FLAG_ONESHOT
)
403 src
->optr
= sfx_alloc();
406 // run replacement routine if one is waiting
407 if( src
->replacement
)
411 printf( "Deallocating previous source buffer\n" );
416 src
->source
= src
->replacement
;
417 src
->replacement
= NULL
;
420 src
->optr
->source
= src
->source
;
422 // Localize data to thread 1's memory pool
423 // memcpy( src->optr, src, sizeof( sfx_system_t ) );
425 src
->optr
->spd
= src
->spd
;
426 src
->optr
->ch
= src
->ch
;
427 src
->optr
->end
= src
->end
;
428 src
->optr
->cur
= src
->cur
;
429 src
->optr
->flags
= src
->flags
;
430 // src->optr->sng = src->snh;
431 src
->optr
->fadeout
= src
->fadeout
;
432 // src->optr->optr = src->optr;
433 // src->optr->cvol = src->cvol;
434 src
->optr
->vol_src
= src
->vol_src
;
435 src
->optr
->name
= src
->name
;
437 // loopback pointer on system system
438 src
->optr
->optr
= src
;
442 // Pull in volume sliders
443 for( int i
= 0; i
< sfx_sys_len
; i
++ )
445 sfx_system_t
*sys
= sfx_sys
+ i
;
446 sys
->vol
= sys
->optr
->vol
;
447 if( sys
->vol_src
) { sys
->vol
*= sys
->vol_src
->cmp
; };
450 MUTEX_UNLOCK( sfx_mux_t01
);
453 // Send out updates to sources
454 void sfx_redist(void)
456 MUTEX_LOCK( sfx_mux_t01
);
458 unsigned int idx
= 0, wr
= 0;
459 while( idx
!= sfx_sys_len
)
461 sfx_system_t
*src
= sfx_sys
+ idx
;
463 // Keep only if cursor is before end
464 if( src
->cur
< src
->end
)
466 if( !(src
->flags
& SFX_FLAG_ONESHOT
) )
468 // Correct source pointer
469 src
->optr
->optr
= sfx_sys
+ wr
;
472 sfx_sys
[ wr
++ ] = sfx_sys
[ idx
];
476 if( !(src
->flags
& SFX_FLAG_ONESHOT
) )
478 // Clear link on source
479 src
->optr
->optr
= NULL
;
487 MUTEX_UNLOCK( sfx_mux_t01
);
490 // Fetch samples into pcf
491 void audio_mixer_getsamples( float *pcf
, float *source
, uint32_t cur
, uint32_t ch
)
495 pcf
[0] = source
[ cur
*2+0 ];
496 pcf
[1] = source
[ cur
*2+1 ];
500 pcf
[0] = source
[ cur
];
501 pcf
[1] = source
[ cur
];
505 // miniaudio.h interface
506 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
)
510 // Clear buffer ( is necessary ? )
511 float *pOut32F
= (float *)pOutBuf
;
512 for( int i
= 0; i
< frameCount
* 2; i
++ ){
516 // Do something with local..
517 for( int i
= 0; i
< sfx_sys_len
; i
++ )
519 sfx_system_t
*sys
= sfx_sys
+ i
;
521 uint32_t cursor
= sys
->cur
;
526 float pcf
[2] = { 0.f
};
528 while( cursor
< vg_min( sys
->cur
+ frameCount
, sys
->end
) )
530 audio_mixer_getsamples( pcf
, sys
->source
, cursor
, sys
->ch
);
532 avgvol
+= fabs( pcf
[0] * sys
->vol
);
533 avgvol
+= fabs( pcf
[1] * sys
->vol
);
535 pOut32F
[ bpos
*2+0 ] += pcf
[0] * sys
->vol
;
536 pOut32F
[ bpos
*2+1 ] += pcf
[1] * sys
->vol
;
538 // Blend the fadeout cursor in to prevent popping
539 // This lasts 441 samples
542 if( sys
->snh
< sys
->end
)
544 audio_mixer_getsamples( pcf
, sys
->source
, sys
->snh
, sys
->ch
);
546 float mul
= (float)sys
->fadeout
* FADEOUT_DIVISOR
;
548 pOut32F
[ bpos
*2+0 ] += pcf
[0] * sys
->vol
* mul
;
549 pOut32F
[ bpos
*2+1 ] += pcf
[1] * sys
->vol
* mul
;
569 sys
->cvol
= avgvol
/ (float)(bpos
*2);
570 sys
->cur
+= frameCount
;
578 // Set of up to 8 sound effects packed into one
579 typedef struct sfx_set sfx_set_t
;
585 uint32_t segments
[16]; //from->to,from->to ...
586 uint32_t numsegments
;
591 // Load strings into sfx_set's memory
592 // String layout: "sounda.ogg\0soundb.ogg\0soundc.ogg\0\0"
593 void sfx_set_strings( sfx_set_t
*dest
, char *strSources
, uint32_t flags
, int bAsync
)
595 printf( "Init sfx set\n| start | end | length | name \n" );
597 dest
->ch
= (flags
& SFX_FLAG_STEREO
)? 2: 1;
600 dest
->numsegments
= 0;
601 char *source
= strSources
;
605 while( (len
= strlen( source
)) )
608 float *sound
= sfx_vorbis( source
, dest
->ch
, &samples
);
613 dest
->numsegments
= 0;
619 float *nbuf
= realloc( dest
->main
, total
* dest
->ch
* sizeof(float) );
624 memcpy( dest
->main
+ (total
-samples
)*dest
->ch
, sound
, samples
*dest
->ch
*sizeof(float) );
627 dest
->segments
[ dest
->numsegments
*2+0 ] = total
-samples
;
628 dest
->segments
[ dest
->numsegments
*2+1 ] = total
;
630 printf( "| %09u | %09u | %09u | %s\n", total
-samples
, total
, samples
, source
);
634 vg_error( "realloc() failed\n" );
640 dest
->numsegments
++;
643 vg_info( "finished, numsegments: %u\n", dest
->numsegments
);
647 // If sources is non-null then it will try to pull from
648 // internally set string
650 // internal set string should be literal, otherwise leak if not
652 void sfx_set_init( sfx_set_t
*dest
, char *sources
)
654 #ifndef VYGER_RELEASE
660 sfx_set_strings( dest
, dest
->sources
, dest
->flags
, 0 );
664 sfx_set_strings( dest
, sources
, dest
->flags
, 0 );
668 // Pick a random sound from the buffer and play it into system
669 void sfx_set_playrnd( sfx_set_t
*source
, sfx_system_t
*sys
)
671 if( !source
->numsegments
)
676 int pick
= rand() % source
->numsegments
;
678 sys
->source
= source
->main
;
679 sys
->cur
= source
->segments
[ pick
*2 + 0 ];
680 sys
->end
= source
->segments
[ pick
*2 + 1 ];
681 sys
->ch
= source
->ch
;
686 // Free set resources
687 void sfx_set_free( sfx_set_t
*set
)
689 #ifndef VYGER_RELEASE