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 32
10 #define SFX_FLAG_ONESHOT 0x1
11 #define SFX_FLAG_STEREO 0x2
12 #define SFX_FLAG_REPEAT 0x4
13 #define SFX_FLAG_GHOST 0x8
14 #define FADEOUT_LENGTH 4410
15 #define FADEOUT_DIVISOR (1.f/(float)FADEOUT_LENGTH)
17 typedef struct sfx_vol_control sfx_vol_control
;
18 typedef struct sfx_system sfx_system
;
20 struct sfx_vol_control
28 // Source buffer start
29 float *source
, *replacement
;
32 sfx_vol_control
*vol_src
;
42 u32 fadeout
, fadeout_current
;
44 sfx_system
*thread_clone
; // Memory of this structure is copied into thread 2
47 float signal_average
; // Current signal volume
51 // Set of up to 8 sound effects packed into one
52 typedef struct sfx_set sfx_set
;
58 u32 segments
[16]; //from->to,from->to ...
64 ma_device g_aud_device
;
65 ma_device_config g_aud_dconfig
;
67 // Thread 1 - audio engine ( spawned from miniaudio.h )
68 // ======================================================
69 sfx_system sfx_sys
[SFX_MAX_SYSTEMS
];
72 // Thread 0 - Critical transfer section
73 // ======================================================
74 MUTEX_TYPE sfx_mux_t01
; // Resources share: 0 & 1
76 sfx_system
*sfx_q
[SFX_MAX_SYSTEMS
]; // Stuff changed
77 int sfx_q_len
= 0; // How much
80 // ======================================================
82 // g_vol_master is never directly acessed by users
83 float g_master_volume
= 1.f
;
85 // Decompress entire vorbis stream into buffer
86 static float *sfx_vorbis_stream( const unsigned char *data
, int len
, int channels
, u32
*samples
)
89 stb_vorbis
*pv
= stb_vorbis_open_memory( data
, len
, &err
, NULL
);
93 vg_error( "stb_vorbis_open_memory() failed with error code: %i\n", err
);
97 u32 length_samples
= stb_vorbis_stream_length_in_samples( pv
);
98 float *buffer
= (float *)malloc( length_samples
* channels
* sizeof( float ));
102 stb_vorbis_close( pv
);
103 vg_error( "out of memory while allocating sound resource\n" );
107 int read_samples
= stb_vorbis_get_samples_float_interleaved( pv
, channels
, buffer
, length_samples
* channels
);
108 if( read_samples
!= length_samples
)
110 vg_warn( "| warning: sample count mismatch. Expected %u got %i\n", length_samples
, read_samples
);
111 length_samples
= read_samples
;
114 stb_vorbis_close( pv
);
115 *samples
= length_samples
;
119 static float *sfx_vorbis( const char *strFileName
, int channels
, u32
*samples
)
122 void *filedata
= vg_asset_read_s( strFileName
, &len
);
126 float *wav
= sfx_vorbis_stream( filedata
, len
, channels
, samples
);
132 vg_error( "OGG load failed\n" );
137 typedef struct sfx_bgload sfx_bgload_t
;
148 void(*OnComplete
)(sfx_bgload_t
*inf
);
151 // Thread worker for background load job
152 void *sfx_vorbis_a_t( void *_inf
)
154 sfx_bgload_t
*info
= _inf
;
157 info
->buffer
= sfx_vorbis( info
->path
, info
->channels
, &info
->samples
);
158 info
->OnComplete( info
);
163 // Asynchronous resource load
164 int sfx_vorbis_a( const char *path
, int channels
, void(*OnComplete
)(sfx_bgload_t
*inf
), void *user
)
166 vg_info( "background job started for: %s\n", path
);
168 sfx_bgload_t
*params
= malloc( sizeof( sfx_bgload_t
) );
169 params
->path
= malloc( strlen( path
) + 1 );
170 strcpy( params
->path
, path
);
171 params
->OnComplete
= OnComplete
;
173 params
->channels
= channels
;
175 return vg_thread_run( sfx_vorbis_a_t
, params
);
178 // Asynchronous load-to-system callback
179 struct sfx_vorbis_a_to_inf
185 #define SFX_A_FLAG_AUTOSTART 0x1
186 #define SFX_A_FLAG_AUTOFREE 0x2
189 static int sfx_save( sfx_system *sys );
191 // Asynchronous load-to-system callback
192 void sfx_vorbis_a_to_c( sfx_bgload_t *loadinf )
194 struct sfx_vorbis_a_to_inf *inf = loadinf->user;
196 // Mark buffer for deallocation if autofree is set
197 if( inf->flags & SFX_A_FLAG_AUTOFREE )
198 inf->sys->replacement = loadinf->buffer;
200 inf->sys->source = loadinf->buffer;
202 inf->sys->end = loadinf->samples;
204 if( inf->flags & SFX_A_FLAG_AUTOSTART )
205 sfx_save( inf->sys );
207 free( loadinf->path );
212 // Asynchronous vorbis load into audio system
213 void sfx_vorbis_a_to( sfx_system *sys, const char *strFileName, int channels, u32 flags )
215 struct sfx_vorbis_a_to_inf *inf = malloc( sizeof( struct sfx_vorbis_a_to_inf ) );
221 if( !sfx_vorbis_a( strFileName, channels, sfx_vorbis_a_to_c, inf ) )
226 // ======================================================
228 static int sfx_begin_edit( sfx_system
*sys
)
230 MUTEX_LOCK( sfx_mux_t01
);
234 MUTEX_UNLOCK( sfx_mux_t01
);
236 vg_warn( "Sfx system locked for writing.. Spam is being created!\n" );
244 // Mark change to be uploaded to queue system
245 static int sfx_save( sfx_system
*sys
)
247 if( sfx_q_len
>= SFX_MAX_SYSTEMS
)
249 vg_error( "Warning: No free space in sound queue\n" );
251 MUTEX_UNLOCK( sfx_mux_t01
);
255 // Mark change in queue
256 sfx_q
[ sfx_q_len
++ ] = sys
;
258 MUTEX_UNLOCK( sfx_mux_t01
);
263 // Edit a volume float, has to be function wrapped because of mutex
264 static void sfx_vol_fset( sfx_vol_control
*src
, float to
)
266 MUTEX_LOCK( sfx_mux_t01
);
270 MUTEX_UNLOCK( sfx_mux_t01
);
273 // thread-safe get volume value
274 static float sfx_vol_fget( sfx_vol_control
*src
)
278 MUTEX_LOCK( sfx_mux_t01
);
282 MUTEX_UNLOCK( sfx_mux_t01
);
287 // thread-safe set master volume
288 static void sfx_set_master( float to
)
290 MUTEX_LOCK( sfx_mux_t01
);
292 g_master_volume
= to
;
294 MUTEX_UNLOCK( sfx_mux_t01
);
297 // thread-safe get master volume
298 static float sfx_get_master(void)
302 MUTEX_LOCK( sfx_mux_t01
);
304 val
= g_master_volume
;
306 MUTEX_UNLOCK( sfx_mux_t01
);
311 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
);
314 static void vg_audio_init(void)
316 g_aud_dconfig
= ma_device_config_init( ma_device_type_playback
);
317 g_aud_dconfig
.playback
.format
= ma_format_f32
;
318 g_aud_dconfig
.playback
.channels
= 2;
319 g_aud_dconfig
.sampleRate
= 44100;
320 g_aud_dconfig
.dataCallback
= audio_mixer_callback
;
322 g_aud_dconfig
.pUserData
= NULL
;
324 vg_info( "Starting audio engine\n" );
326 if( ma_device_init( NULL
, &g_aud_dconfig
, &g_aud_device
) != MA_SUCCESS
)
328 vg_exiterr( "ma_device failed to initialize" );
332 if( ma_device_start( &g_aud_device
) != MA_SUCCESS
)
334 ma_device_uninit( &g_aud_device
);
335 vg_exiterr( "ma_device failed to start" );
340 // Shutdown audio device
341 static void vg_audio_free(void)
343 ma_device_uninit( &g_aud_device
);
347 // ======================================================
349 // Create and return slot for a sound
350 static sfx_system
*sfx_alloc(void)
352 if( sfx_sys_len
>= SFX_MAX_SYSTEMS
)
355 // A conditional is done against this in localization step,
356 // Needs to be initialized.
357 sfx_sys
[ sfx_sys_len
].source
= NULL
;
359 return sfx_sys
+ (sfx_sys_len
++);
362 // Fetch samples into pcf
363 static void audio_mixer_getsamples( float *pcf
, float *source
, u32 cur
, u32 ch
)
367 pcf
[0] = source
[ cur
*2+0 ];
368 pcf
[1] = source
[ cur
*2+1 ];
372 pcf
[0] = source
[ cur
];
373 pcf
[1] = source
[ cur
];
377 // miniaudio.h interface
378 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
)
380 // Process incoming sound queue
381 MUTEX_LOCK( sfx_mux_t01
);
383 while( sfx_q_len
--> 0 )
385 sfx_system
*src
= sfx_q
[sfx_q_len
];
390 // This is a 'new' sound if thread_clone not set.
391 if( !src
->thread_clone
|| (src
->flags
& SFX_FLAG_ONESHOT
) )
397 src
->thread_clone
= clone
;
401 clone
= src
->thread_clone
;
403 // Modifying an active system's cursor spawns a small fadeout ghost system
404 if( clone
->cur
!= src
->cur
)
406 sfx_system
*ghost_system
= sfx_alloc();
411 ghost_system
->source
= clone
->source
;
412 ghost_system
->ch
= clone
->ch
;
413 ghost_system
->end
= clone
->end
;
414 ghost_system
->cur
= clone
->cur
;
415 ghost_system
->flags
= SFX_FLAG_GHOST
;
416 ghost_system
->fadeout
= FADEOUT_LENGTH
;
417 ghost_system
->fadeout_current
= FADEOUT_LENGTH
;
418 ghost_system
->vol_src
= clone
->vol_src
;
419 ghost_system
->name
= clone
->name
;
420 ghost_system
->thread_clone
= src
;
424 // run replacement routine if one is waiting (todo: what is this?)
425 if( src
->replacement
)
429 src
->source
= src
->replacement
;
430 src
->replacement
= NULL
;
433 // Localize data to thread 1's memory pool
434 clone
->source
= src
->source
;
436 clone
->end
= src
->end
;
437 clone
->cur
= src
->cur
;
438 clone
->flags
= src
->flags
;
439 clone
->vol_src
= src
->vol_src
;
440 clone
->name
= src
->name
;
441 clone
->fadeout
= src
->fadeout
;
442 clone
->fadeout_current
= src
->fadeout_current
;
444 // loopback pointer, mainly used for persistent sound handles
445 clone
->thread_clone
= src
;
450 // Pull in volume sliders
451 for( int i
= 0; i
< sfx_sys_len
; i
++ )
453 sfx_system
*sys
= sfx_sys
+ i
;
454 sys
->vol
= sys
->thread_clone
->vol
* g_master_volume
;
457 sys
->vol
*= sys
->vol_src
->val
;
460 MUTEX_UNLOCK( sfx_mux_t01
);
462 // Clear buffer ( is necessary ? )
463 float *pOut32F
= (float *)pOutBuf
;
464 for( int i
= 0; i
< frameCount
* 2; i
++ ){
468 // Do something with local..
469 for( int i
= 0; i
< sfx_sys_len
; i
++ )
471 sfx_system
*sys
= sfx_sys
+ i
;
473 u32 cursor
= sys
->cur
, buffer_pos
= 0;
475 float pcf
[2] = { 0.f
, 0.0f
};
477 u32 frames_write
= frameCount
;
478 float fadeout_divisor
= 1.0f
/ (float)sys
->fadeout
;
480 while( frames_write
)
482 u32 samples_this_run
= VG_MIN( frames_write
, sys
->end
- cursor
);
486 // Force this system to be removed
487 if( sys
->fadeout_current
== 0 )
489 sys
->flags
= SFX_FLAG_GHOST
;
494 samples_this_run
= VG_MIN( samples_this_run
, sys
->fadeout_current
);
497 for( u32 j
= 0; j
< samples_this_run
; j
++ )
499 audio_mixer_getsamples( pcf
, sys
->source
, cursor
, sys
->ch
);
501 float vol
= sys
->vol
;
505 vol
*= (float)sys
->fadeout_current
* fadeout_divisor
;
506 sys
->fadeout_current
--;
509 if( buffer_pos
>= frameCount
)
514 pOut32F
[ buffer_pos
*2+0 ] += pcf
[0] * vol
;
515 pOut32F
[ buffer_pos
*2+1 ] += pcf
[1] * vol
;
517 avgvol
+= fabs( pcf
[0] * vol
);
518 avgvol
+= fabs( pcf
[1] * vol
);
524 frames_write
-= samples_this_run
;
526 if( sys
->flags
& SFX_FLAG_REPEAT
)
536 sys
->signal_average
= avgvol
/ (float)(buffer_pos
*2);
542 // Redistribute sound systems
543 MUTEX_LOCK( sfx_mux_t01
);
546 while( idx
!= sfx_sys_len
)
548 sfx_system
*src
= sfx_sys
+ idx
;
550 // Keep only if cursor is before end or repeating
551 if( src
->cur
< src
->end
|| (src
->flags
& SFX_FLAG_REPEAT
) )
553 // Correct source pointer on persisitent originals since we shifted ID's
554 if( !(src
->flags
& (SFX_FLAG_ONESHOT
|SFX_FLAG_GHOST
)) )
556 src
->thread_clone
->thread_clone
= sfx_sys
+ wr
;
559 sfx_sys
[ wr
++ ] = sfx_sys
[ idx
];
563 // Clear link on persistent sources (done playing)
564 if( !(src
->flags
& (SFX_FLAG_ONESHOT
|SFX_FLAG_GHOST
)) )
565 src
->thread_clone
->thread_clone
= NULL
;
572 MUTEX_UNLOCK( sfx_mux_t01
);
577 // Load strings into sfx_set's memory
578 // String layout: "sounda.ogg\0soundb.ogg\0soundc.ogg\0\0"
579 static void sfx_set_strings( sfx_set
*dest
, char *strSources
, u32 flags
, int bAsync
)
581 printf( "Init sfx set\n| start | end | length | name \n" );
583 dest
->ch
= (flags
& SFX_FLAG_STEREO
)? 2: 1;
586 dest
->numsegments
= 0;
587 char *source
= strSources
;
591 while( (len
= strlen( source
)) )
594 float *sound
= sfx_vorbis( source
, dest
->ch
, &samples
);
599 dest
->numsegments
= 0;
605 float *nbuf
= realloc( dest
->main
, total
* dest
->ch
* sizeof(float) );
610 memcpy( dest
->main
+ (total
-samples
)*dest
->ch
, sound
, samples
*dest
->ch
*sizeof(float) );
613 dest
->segments
[ dest
->numsegments
*2+0 ] = total
-samples
;
614 dest
->segments
[ dest
->numsegments
*2+1 ] = total
;
616 printf( "| %09u | %09u | %09u | %s\n", total
-samples
, total
, samples
, source
);
620 vg_error( "realloc() failed\n" );
626 dest
->numsegments
++;
629 vg_info( "finished, numsegments: %u\n", dest
->numsegments
);
632 static void sfx_set_init( sfx_set
*dest
, char *sources
)
635 sfx_set_strings( dest
, dest
->sources
, dest
->flags
, 0 );
637 sfx_set_strings( dest
, sources
, dest
->flags
, 0 );
640 // Pick a random sound from the buffer and play it into system
641 static void sfx_set_playrnd( sfx_set
*source
, sfx_system
*sys
, int min_id
, int max_id
)
643 if( !source
->numsegments
)
646 int pick
= (rand() % (max_id
-min_id
)) + min_id
;
648 if( sfx_begin_edit( sys
) )
651 sys
->source
= source
->main
;
652 sys
->cur
= source
->segments
[ pick
*2 + 0 ];
653 sys
->end
= source
->segments
[ pick
*2 + 1 ];
654 sys
->ch
= source
->ch
;
660 static void sfx_system_fadeout( sfx_system
*sys
, u32 length_samples
)
662 if( sfx_begin_edit( sys
) )
664 sys
->fadeout_current
= length_samples
;
665 sys
->fadeout
= length_samples
;
667 if( sys
->thread_clone
)
668 sys
->cur
= sys
->thread_clone
->cur
;
674 // Free set resources
675 static void sfx_set_free( sfx_set
*set
)