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_control sfx_vol_control
;
16 typedef struct sfx_system sfx_system
;
18 struct sfx_vol_control
26 // Source buffer start
27 float *source
, *replacement
;
30 sfx_vol_control
*vol_src
;
34 int ch
, end
, cur
, flags
;
37 u32 fadeout
, fadeout_length
, fadeout_cursor
;
39 sfx_system
*thread_clone
; // Memory of this structure is copied into thread 2
42 float signal_average
; // Current signal volume
46 // Set of up to 8 sound effects packed into one
47 typedef struct sfx_set sfx_set
;
53 u32 segments
[16]; //from->to,from->to ...
59 ma_device g_aud_device
;
60 ma_device_config g_aud_dconfig
;
62 // Thread 1 - audio engine ( spawned from miniaudio.h )
63 // ======================================================
64 sfx_system sfx_sys
[SFX_MAX_SYSTEMS
];
67 // Thread 0 - Critical transfer section
68 // ======================================================
69 MUTEX_TYPE sfx_mux_t01
; // Resources share: 0 & 1
71 sfx_system
*sfx_q
[SFX_MAX_SYSTEMS
]; // Stuff changed
72 int sfx_q_len
= 0; // How much
75 // ======================================================
77 // g_vol_master is never directly acessed by users
78 float g_master_volume
= 1.f
;
80 // Decompress entire vorbis stream into buffer
81 static float *sfx_vorbis_stream( const unsigned char *data
, int len
, int channels
, u32
*samples
)
84 stb_vorbis
*pv
= stb_vorbis_open_memory( data
, len
, &err
, NULL
);
88 vg_error( "stb_vorbis_open_memory() failed with error code: %i\n", err
);
92 u32 length_samples
= stb_vorbis_stream_length_in_samples( pv
);
93 float *buffer
= (float *)malloc( length_samples
* channels
* sizeof( float ));
97 stb_vorbis_close( pv
);
98 vg_error( "out of memory while allocating sound resource\n" );
102 int read_samples
= stb_vorbis_get_samples_float_interleaved( pv
, channels
, buffer
, length_samples
* channels
);
103 if( read_samples
!= length_samples
)
105 vg_warn( "| warning: sample count mismatch. Expected %u got %i\n", length_samples
, read_samples
);
106 length_samples
= read_samples
;
109 stb_vorbis_close( pv
);
110 *samples
= length_samples
;
114 static float *sfx_vorbis( const char *strFileName
, int channels
, u32
*samples
)
117 void *filedata
= vg_asset_read_s( strFileName
, &len
);
121 float *wav
= sfx_vorbis_stream( filedata
, len
, channels
, samples
);
127 vg_error( "OGG load failed\n" );
132 typedef struct sfx_bgload sfx_bgload_t
;
143 void(*OnComplete
)(sfx_bgload_t
*inf
);
146 // Thread worker for background load job
147 void *sfx_vorbis_a_t( void *_inf
)
149 sfx_bgload_t
*info
= _inf
;
152 info
->buffer
= sfx_vorbis( info
->path
, info
->channels
, &info
->samples
);
153 info
->OnComplete( info
);
158 // Asynchronous resource load
159 int sfx_vorbis_a( const char *path
, int channels
, void(*OnComplete
)(sfx_bgload_t
*inf
), void *user
)
161 vg_info( "background job started for: %s\n", path
);
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
;
168 params
->channels
= channels
;
170 return vg_thread_run( sfx_vorbis_a_t
, params
);
173 // Asynchronous load-to-system callback
174 struct sfx_vorbis_a_to_inf
180 #define SFX_A_FLAG_AUTOSTART 0x1
181 #define SFX_A_FLAG_AUTOFREE 0x2
183 static int sfx_save( sfx_system
*sys
);
185 // Asynchronous load-to-system callback
186 void sfx_vorbis_a_to_c( sfx_bgload_t
*loadinf
)
188 struct sfx_vorbis_a_to_inf
*inf
= loadinf
->user
;
190 // Mark buffer for deallocation if autofree is set
191 if( inf
->flags
& SFX_A_FLAG_AUTOFREE
)
192 inf
->sys
->replacement
= loadinf
->buffer
;
194 inf
->sys
->source
= loadinf
->buffer
;
196 inf
->sys
->end
= loadinf
->samples
;
198 if( inf
->flags
& SFX_A_FLAG_AUTOSTART
)
199 sfx_save( inf
->sys
);
201 free( loadinf
->path
);
206 // Asynchronous vorbis load into audio system
207 void sfx_vorbis_a_to( sfx_system
*sys
, const char *strFileName
, int channels
, u32 flags
)
209 struct sfx_vorbis_a_to_inf
*inf
= malloc( sizeof( struct sfx_vorbis_a_to_inf
) );
215 if( !sfx_vorbis_a( strFileName
, channels
, sfx_vorbis_a_to_c
, inf
) )
220 // ======================================================
222 // Mark change to be uploaded to queue system
223 static int sfx_save( sfx_system
*sys
)
225 MUTEX_LOCK( sfx_mux_t01
);
227 if( sfx_q_len
>= SFX_MAX_SYSTEMS
)
229 vg_error( "Warning: No free space in sound queue\n" );
231 MUTEX_UNLOCK( sfx_mux_t01
);
235 // Mark change in queue
236 sfx_q
[ sfx_q_len
++ ] = sys
;
238 MUTEX_UNLOCK( sfx_mux_t01
);
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
)
246 MUTEX_LOCK( sfx_mux_t01
);
250 MUTEX_UNLOCK( sfx_mux_t01
);
253 // thread-safe get volume value
254 static float sfx_vol_fget( sfx_vol_control
*src
)
258 MUTEX_LOCK( sfx_mux_t01
);
262 MUTEX_UNLOCK( sfx_mux_t01
);
267 // thread-safe set master volume
268 static void sfx_set_master( float to
)
270 MUTEX_LOCK( sfx_mux_t01
);
272 g_master_volume
= to
;
274 MUTEX_UNLOCK( sfx_mux_t01
);
277 // thread-safe get master volume
278 static float sfx_get_master(void)
282 MUTEX_LOCK( sfx_mux_t01
);
284 val
= g_master_volume
;
286 MUTEX_UNLOCK( sfx_mux_t01
);
291 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
);
294 static void vg_audio_init(void)
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
;
302 g_aud_dconfig
.pUserData
= NULL
;
304 vg_info( "Starting audio engine\n" );
306 if( ma_device_init( NULL
, &g_aud_dconfig
, &g_aud_device
) != MA_SUCCESS
)
308 vg_exiterr( "ma_device failed to initialize" );
312 if( ma_device_start( &g_aud_device
) != MA_SUCCESS
)
314 ma_device_uninit( &g_aud_device
);
315 vg_exiterr( "ma_device failed to start" );
320 #ifndef VYGER_RELEASE
321 u32 num_sfx_sets
= 0;
324 // Shutdown audio device
325 static void vg_audio_free(void)
327 ma_device_uninit( &g_aud_device
);
330 // (debug) make sure we are shutting down safely
331 static void sfx_sys_chkerr(void)
333 #ifndef VYGER_RELEASE
336 vg_error( "Leaked %u sfx sets\n", num_sfx_sets
);
342 // ======================================================
344 // Create and return slot for a sound
345 static sfx_system
*sfx_alloc(void)
347 if( sfx_sys_len
>= SFX_MAX_SYSTEMS
)
349 vg_error( "Warning: No free space in sound system\n" );
354 // A conditional is done against this in localization step,
355 // Needs to be initialized.
356 sfx_sys
[ sfx_sys_len
].source
= NULL
;
358 return sfx_sys
+ (sfx_sys_len
++);
361 // Fetch samples into pcf
362 static void audio_mixer_getsamples( float *pcf
, float *source
, u32 cur
, u32 ch
)
366 pcf
[0] = source
[ cur
*2+0 ];
367 pcf
[1] = source
[ cur
*2+1 ];
371 pcf
[0] = source
[ cur
];
372 pcf
[1] = source
[ cur
];
376 // miniaudio.h interface
377 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
)
379 // Process incoming sound queue
380 MUTEX_LOCK( sfx_mux_t01
);
382 while( sfx_q_len
--> 0 )
384 sfx_system
*src
= sfx_q
[sfx_q_len
];
386 // This is a 'new' sound if thread_clone not set.
387 if( !src
->thread_clone
|| src
->flags
& SFX_FLAG_ONESHOT
)
389 src
->thread_clone
= sfx_alloc();
392 // run replacement routine if one is waiting
393 if( src
->replacement
)
397 src
->source
= src
->replacement
;
398 src
->replacement
= NULL
;
401 src
->thread_clone
->source
= src
->source
;
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
;
413 // loopback pointer, mainly used for persistent sound handles
414 src
->thread_clone
->thread_clone
= src
;
419 // Pull in volume sliders
420 for( int i
= 0; i
< sfx_sys_len
; i
++ )
422 sfx_system
*sys
= sfx_sys
+ i
;
423 sys
->vol
= sys
->thread_clone
->vol
* g_master_volume
;
426 sys
->vol
*= sys
->vol_src
->val
;
429 MUTEX_UNLOCK( sfx_mux_t01
);
431 // Clear buffer ( is necessary ? )
432 float *pOut32F
= (float *)pOutBuf
;
433 for( int i
= 0; i
< frameCount
* 2; i
++ ){
437 // Do something with local..
438 for( int i
= 0; i
< sfx_sys_len
; i
++ )
440 sfx_system
*sys
= sfx_sys
+ i
;
442 u32 cursor
= sys
->cur
;
446 float pcf
[2] = { 0.f
, 0.0f
};
448 if( sys
->fadeout_cursor
!= sys
->cur
)
449 sys
->fadeout
= sys
->fadeout_length
;
451 while( cursor
< vg_min( sys
->cur
+ frameCount
, sys
->end
) )
453 audio_mixer_getsamples( pcf
, sys
->source
, cursor
, sys
->ch
);
455 avgvol
+= fabs( pcf
[0] * sys
->vol
);
456 avgvol
+= fabs( pcf
[1] * sys
->vol
);
458 pOut32F
[ bpos
*2+0 ] += pcf
[0] * sys
->vol
;
459 pOut32F
[ bpos
*2+1 ] += pcf
[1] * sys
->vol
;
461 // Blend the fadeout cursor in to prevent popping
464 if( sys
->fadeout_cursor
< sys
->end
)
466 audio_mixer_getsamples( pcf
, sys
->source
, sys
->fadeout_cursor
, sys
->ch
);
468 float mul
= (float)sys
->fadeout
* FADEOUT_DIVISOR
;
470 pOut32F
[ bpos
*2+0 ] += pcf
[0] * sys
->vol
* mul
;
471 pOut32F
[ bpos
*2+1 ] += pcf
[1] * sys
->vol
* mul
;
473 sys
->fadeout_cursor
++;
484 sys
->signal_average
= avgvol
/ (float)(bpos
*2);
485 sys
->cur
+= frameCount
;
486 sys
->fadeout_cursor
= sys
->cur
;
489 // Redistribute sound systems
490 MUTEX_LOCK( sfx_mux_t01
);
492 unsigned int idx
= 0, wr
= 0;
493 while( idx
!= sfx_sys_len
)
495 sfx_system
*src
= sfx_sys
+ idx
;
497 // Keep only if cursor is before end
498 if( src
->cur
< src
->end
)
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
;
504 sfx_sys
[ wr
++ ] = sfx_sys
[ idx
];
508 // Clear link on persistent sources (done playing)
509 if( !(src
->flags
& SFX_FLAG_ONESHOT
) )
510 src
->thread_clone
->thread_clone
= NULL
;
517 MUTEX_UNLOCK( sfx_mux_t01
);
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
)
526 printf( "Init sfx set\n| start | end | length | name \n" );
528 dest
->ch
= (flags
& SFX_FLAG_STEREO
)? 2: 1;
531 dest
->numsegments
= 0;
532 char *source
= strSources
;
536 while( (len
= strlen( source
)) )
539 float *sound
= sfx_vorbis( source
, dest
->ch
, &samples
);
544 dest
->numsegments
= 0;
550 float *nbuf
= realloc( dest
->main
, total
* dest
->ch
* sizeof(float) );
555 memcpy( dest
->main
+ (total
-samples
)*dest
->ch
, sound
, samples
*dest
->ch
*sizeof(float) );
558 dest
->segments
[ dest
->numsegments
*2+0 ] = total
-samples
;
559 dest
->segments
[ dest
->numsegments
*2+1 ] = total
;
561 printf( "| %09u | %09u | %09u | %s\n", total
-samples
, total
, samples
, source
);
565 vg_error( "realloc() failed\n" );
571 dest
->numsegments
++;
574 vg_info( "finished, numsegments: %u\n", dest
->numsegments
);
577 void sfx_set_init( sfx_set
*dest
, char *sources
)
579 #ifndef VYGER_RELEASE
585 sfx_set_strings( dest
, dest
->sources
, dest
->flags
, 0 );
589 sfx_set_strings( dest
, sources
, dest
->flags
, 0 );
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
)
596 if( !source
->numsegments
)
601 int pick
= (rand() % (max_id
-min_id
)) + min_id
;
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
;
611 // Free set resources
612 void sfx_set_free( sfx_set
*set
)
614 #ifndef VYGER_RELEASE