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 stb_vorbis_close( pv
);
113 vg_error( "out of memory while allocating sound resource\n" );
117 int read_samples
= stb_vorbis_get_samples_float_interleaved( pv
, channels
, buffer
, length_samples
* channels
);
118 if( read_samples
!= length_samples
)
120 vg_warn( "| warning: sample count mismatch. Expected %u got %i\n", length_samples
, read_samples
);
121 length_samples
= read_samples
;
124 stb_vorbis_close( pv
);
125 *samples
= length_samples
;
129 float *sfx_vorbis( const char *strFileName
, int channels
, u32
*samples
)
132 void *filedata
= vg_asset_read_s( strFileName
, &len
);
136 float *wav
= sfx_vorbis_stream( filedata
, len
, channels
, samples
);
142 vg_error( "OGG load failed\n" );
147 typedef struct sfx_bgload sfx_bgload_t
;
158 void(*OnComplete
)(sfx_bgload_t
*inf
);
161 // Thread worker for background load job
162 void *sfx_vorbis_a_t( void *_inf
)
164 sfx_bgload_t
*info
= _inf
;
167 info
->buffer
= sfx_vorbis( info
->path
, info
->channels
, &info
->samples
);
168 info
->OnComplete( info
);
173 // Asynchronous resource load
174 int sfx_vorbis_a( const char *path
, int channels
, void(*OnComplete
)(sfx_bgload_t
*inf
), void *user
)
176 vg_info( "background job started for: %s\n", path
);
178 sfx_bgload_t
*params
= malloc( sizeof( sfx_bgload_t
) );
179 params
->path
= malloc( strlen( path
) + 1 );
180 strcpy( params
->path
, path
);
181 params
->OnComplete
= OnComplete
;
183 params
->channels
= channels
;
185 return vg_thread_run( sfx_vorbis_a_t
, params
);
188 // Asynchronous load-to-system callback
189 struct sfx_vorbis_a_to_inf
195 #define SFX_A_FLAG_AUTOSTART 0x1
196 #define SFX_A_FLAG_AUTOFREE 0x2
198 // Asynchronous load-to-system callback
199 void sfx_vorbis_a_to_c( sfx_bgload_t
*loadinf
)
201 struct sfx_vorbis_a_to_inf
*inf
= loadinf
->user
;
203 // Mark buffer for deallocation if autofree is set
204 if( inf
->flags
& SFX_A_FLAG_AUTOFREE
)
206 inf
->sys
->replacement
= loadinf
->buffer
;
210 inf
->sys
->source
= loadinf
->buffer
;
213 inf
->sys
->end
= loadinf
->samples
;
215 if( inf
->flags
& SFX_A_FLAG_AUTOSTART
)
217 sfx_save( inf
->sys
);
220 free( loadinf
->path
);
225 // Asynchronous vorbis load into audio system
226 void sfx_vorbis_a_to( sfx_system_t
*sys
, const char *strFileName
, int channels
, uint32_t flags
)
228 struct sfx_vorbis_a_to_inf
*inf
= malloc( sizeof( struct sfx_vorbis_a_to_inf
) );
234 if( !sfx_vorbis_a( strFileName
, channels
, sfx_vorbis_a_to_c
, inf
) )
241 // ======================================================
243 // Mark change to be uploaded to queue system
244 int sfx_save( sfx_system_t
*sys
)
246 MUTEX_LOCK( sfx_mux_t01
);
248 if( sfx_q_len
>= SFX_MAX_SYSTEMS
)
250 vg_error( "Warning: No free space in sound queue\n" );
252 MUTEX_UNLOCK( sfx_mux_t01
);
256 // Mark change in queue
257 sfx_q
[ sfx_q_len
++ ] = sys
;
259 MUTEX_UNLOCK( sfx_mux_t01
);
264 // Edit a volume float, has to be function round-tripped
266 void sfx_vol_fset( sfx_vol_t
*src
, float to
)
268 MUTEX_LOCK( sfx_mux_t01
);
271 src
->cmp
= g_master_volume
* to
;
273 MUTEX_UNLOCK( sfx_mux_t01
);
276 // thread-safe get volume value
277 float sfx_vol_fget( sfx_vol_t
*src
)
281 MUTEX_LOCK( sfx_mux_t01
);
285 MUTEX_UNLOCK( sfx_mux_t01
);
290 // thread-safe set master volume
291 void sfx_set_master( float to
)
293 MUTEX_LOCK( sfx_mux_t01
);
295 g_master_volume
= to
;
297 for( int i
= 0; i
< SFX_NUM_VOLUMES
; i
++ )
299 g_volumes
[ i
]->cmp
= g_volumes
[ i
]->val
* g_master_volume
;
302 MUTEX_UNLOCK( sfx_mux_t01
);
305 // thread-safe get master volume
306 float sfx_get_master(void)
310 MUTEX_LOCK( sfx_mux_t01
);
312 val
= g_master_volume
;
314 MUTEX_UNLOCK( sfx_mux_t01
);
320 void vg_audio_init(void)
322 // Setup volume values
323 // Todo: load these from config
325 g_vol_music
.val
= 1.f
;
326 sfx_set_master( 1.f
);
328 g_aud_dconfig
= ma_device_config_init( ma_device_type_playback
);
329 g_aud_dconfig
.playback
.format
= ma_format_f32
;
330 g_aud_dconfig
.playback
.channels
= 2;
331 g_aud_dconfig
.sampleRate
= 44100;
332 g_aud_dconfig
.dataCallback
= audio_mixer_callback
;
334 g_aud_dconfig
.pUserData
= NULL
;
336 vg_info( "Starting audio engine\n" );
338 if( ma_device_init( NULL
, &g_aud_dconfig
, &g_aud_device
) != MA_SUCCESS
)
340 vg_exiterr( "ma_device failed to initialize" );
344 if( ma_device_start( &g_aud_device
) != MA_SUCCESS
)
346 ma_device_uninit( &g_aud_device
);
347 vg_exiterr( "ma_device failed to start" );
352 #ifndef VYGER_RELEASE
353 uint32_t num_sfx_sets
= 0;
356 // Shutdown audio device
357 void vg_audio_free(void)
359 ma_device_uninit( &g_aud_device
);
362 // (debug) make sure we are shutting down safely
363 void sfx_sys_chkerr(void)
365 #ifndef VYGER_RELEASE
368 vg_error( "Leaked %u sfx sets\n", num_sfx_sets
);
374 // ======================================================
376 // Create and return slot for a sound
377 sfx_system_t
*sfx_alloc(void)
379 if( sfx_sys_len
>= SFX_MAX_SYSTEMS
)
381 vg_error( "Warning: No free space in sound system\n" );
386 // A conditional is done against this in localization step,
387 // Needs to be initialized.
388 sfx_sys
[ sfx_sys_len
].source
= NULL
;
390 return sfx_sys
+ (sfx_sys_len
++);
393 // Copy in data from all queued
394 void sfx_localize(void)
396 MUTEX_LOCK( sfx_mux_t01
);
398 while( sfx_q_len
--> 0 )
400 sfx_system_t
*src
= sfx_q
[sfx_q_len
];
402 // This is a 'new' sound if optr not set.
403 if( !src
->optr
|| src
->flags
& SFX_FLAG_ONESHOT
)
405 src
->optr
= sfx_alloc();
408 // run replacement routine if one is waiting
409 if( src
->replacement
)
413 printf( "Deallocating previous source buffer\n" );
418 src
->source
= src
->replacement
;
419 src
->replacement
= NULL
;
422 src
->optr
->source
= src
->source
;
424 // Localize data to thread 1's memory pool
425 // memcpy( src->optr, src, sizeof( sfx_system_t ) );
427 src
->optr
->spd
= src
->spd
;
428 src
->optr
->ch
= src
->ch
;
429 src
->optr
->end
= src
->end
;
430 src
->optr
->cur
= src
->cur
;
431 src
->optr
->flags
= src
->flags
;
432 // src->optr->sng = src->snh;
433 src
->optr
->fadeout
= src
->fadeout
;
434 // src->optr->optr = src->optr;
435 // src->optr->cvol = src->cvol;
436 src
->optr
->vol_src
= src
->vol_src
;
437 src
->optr
->name
= src
->name
;
439 // loopback pointer on system system
440 src
->optr
->optr
= src
;
444 // Pull in volume sliders
445 for( int i
= 0; i
< sfx_sys_len
; i
++ )
447 sfx_system_t
*sys
= sfx_sys
+ i
;
448 sys
->vol
= sys
->optr
->vol
;
449 if( sys
->vol_src
) { sys
->vol
*= sys
->vol_src
->cmp
; };
452 MUTEX_UNLOCK( sfx_mux_t01
);
455 // Send out updates to sources
456 void sfx_redist(void)
458 MUTEX_LOCK( sfx_mux_t01
);
460 unsigned int idx
= 0, wr
= 0;
461 while( idx
!= sfx_sys_len
)
463 sfx_system_t
*src
= sfx_sys
+ idx
;
465 // Keep only if cursor is before end
466 if( src
->cur
< src
->end
)
468 if( !(src
->flags
& SFX_FLAG_ONESHOT
) )
470 // Correct source pointer
471 src
->optr
->optr
= sfx_sys
+ wr
;
474 sfx_sys
[ wr
++ ] = sfx_sys
[ idx
];
478 if( !(src
->flags
& SFX_FLAG_ONESHOT
) )
480 // Clear link on source
481 src
->optr
->optr
= NULL
;
489 MUTEX_UNLOCK( sfx_mux_t01
);
492 // Fetch samples into pcf
493 void audio_mixer_getsamples( float *pcf
, float *source
, uint32_t cur
, uint32_t ch
)
497 pcf
[0] = source
[ cur
*2+0 ];
498 pcf
[1] = source
[ cur
*2+1 ];
502 pcf
[0] = source
[ cur
];
503 pcf
[1] = source
[ cur
];
507 // miniaudio.h interface
508 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
)
512 // Clear buffer ( is necessary ? )
513 float *pOut32F
= (float *)pOutBuf
;
514 for( int i
= 0; i
< frameCount
* 2; i
++ ){
518 // Do something with local..
519 for( int i
= 0; i
< sfx_sys_len
; i
++ )
521 sfx_system_t
*sys
= sfx_sys
+ i
;
523 uint32_t cursor
= sys
->cur
;
528 float pcf
[2] = { 0.f
};
530 while( cursor
< vg_min( sys
->cur
+ frameCount
, sys
->end
) )
532 audio_mixer_getsamples( pcf
, sys
->source
, cursor
, sys
->ch
);
534 avgvol
+= fabs( pcf
[0] * sys
->vol
);
535 avgvol
+= fabs( pcf
[1] * sys
->vol
);
537 pOut32F
[ bpos
*2+0 ] += pcf
[0] * sys
->vol
;
538 pOut32F
[ bpos
*2+1 ] += pcf
[1] * sys
->vol
;
540 // Blend the fadeout cursor in to prevent popping
541 // This lasts 441 samples
544 if( sys
->snh
< sys
->end
)
546 audio_mixer_getsamples( pcf
, sys
->source
, sys
->snh
, sys
->ch
);
548 float mul
= (float)sys
->fadeout
* FADEOUT_DIVISOR
;
550 pOut32F
[ bpos
*2+0 ] += pcf
[0] * sys
->vol
* mul
;
551 pOut32F
[ bpos
*2+1 ] += pcf
[1] * sys
->vol
* mul
;
571 sys
->cvol
= avgvol
/ (float)(bpos
*2);
572 sys
->cur
+= frameCount
;
580 // Set of up to 8 sound effects packed into one
581 typedef struct sfx_set sfx_set_t
;
587 uint32_t segments
[16]; //from->to,from->to ...
588 uint32_t numsegments
;
593 // Load strings into sfx_set's memory
594 // String layout: "sounda.ogg\0soundb.ogg\0soundc.ogg\0\0"
595 void sfx_set_strings( sfx_set_t
*dest
, char *strSources
, uint32_t flags
, int bAsync
)
597 printf( "Init sfx set\n| start | end | length | name \n" );
599 dest
->ch
= (flags
& SFX_FLAG_STEREO
)? 2: 1;
602 dest
->numsegments
= 0;
603 char *source
= strSources
;
607 while( (len
= strlen( source
)) )
610 float *sound
= sfx_vorbis( source
, dest
->ch
, &samples
);
615 dest
->numsegments
= 0;
621 float *nbuf
= realloc( dest
->main
, total
* dest
->ch
* sizeof(float) );
626 memcpy( dest
->main
+ (total
-samples
)*dest
->ch
, sound
, samples
*dest
->ch
*sizeof(float) );
629 dest
->segments
[ dest
->numsegments
*2+0 ] = total
-samples
;
630 dest
->segments
[ dest
->numsegments
*2+1 ] = total
;
632 printf( "| %09u | %09u | %09u | %s\n", total
-samples
, total
, samples
, source
);
636 vg_error( "realloc() failed\n" );
642 dest
->numsegments
++;
645 vg_info( "finished, numsegments: %u\n", dest
->numsegments
);
649 // If sources is non-null then it will try to pull from
650 // internally set string
652 // internal set string should be literal, otherwise leak if not
654 void sfx_set_init( sfx_set_t
*dest
, char *sources
)
656 #ifndef VYGER_RELEASE
662 sfx_set_strings( dest
, dest
->sources
, dest
->flags
, 0 );
666 sfx_set_strings( dest
, sources
, dest
->flags
, 0 );
670 // Pick a random sound from the buffer and play it into system
671 void sfx_set_playrnd( sfx_set_t
*source
, sfx_system_t
*sys
)
673 if( !source
->numsegments
)
678 int pick
= rand() % source
->numsegments
;
680 sys
->source
= source
->main
;
681 sys
->cur
= source
->segments
[ pick
*2 + 0 ];
682 sys
->end
= source
->segments
[ pick
*2 + 1 ];
683 sys
->ch
= source
->ch
;
688 // Free set resources
689 void sfx_set_free( sfx_set_t
*set
)
691 #ifndef VYGER_RELEASE