1 /* Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved */
3 #define MA_NO_GENERATION
6 #include "dr_soft/miniaudio.h"
8 #define STB_VORBIS_MAX_CHANNELS 2
9 #include "stb/stb_vorbis.h"
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)
18 typedef struct sfx_vol_control sfx_vol_control
;
19 typedef struct sfx_system sfx_system
;
21 struct sfx_vol_control
29 sfx_system
*persisitent_source
;
32 /* Source buffer start */
33 float *source
, *replacement
;
35 u32 clip_start
, clip_end
, buffer_length
;
38 sfx_vol_control
*vol_src
;
48 u32 fadeout
, fadeout_current
;
54 /* Set of up to 8 sound effects packed into one */
55 typedef struct sfx_set sfx_set
;
61 u32 segments
[32]; /* from->to,from->to ... */
67 ma_device g_aud_device
;
68 ma_device_config g_aud_dconfig
;
71 * Thread 1 - audio engine ( spawned from miniaudio.h )
72 * ======================================================
74 sfx_system sfx_sys
[SFX_MAX_SYSTEMS
];
78 * Thread 0 - Critical transfer section
79 * ======================================================
81 MUTEX_TYPE sfx_mux_t01
; /* Resources share: 0 & 1 */
83 sfx_system
*sfx_q
[SFX_MAX_SYSTEMS
]; /* Stuff changed */
86 float g_master_volume
= 1.f
;
88 /* Decompress entire vorbis stream into buffer */
89 static float *sfx_vorbis_stream( const unsigned char *data
, int len
,
90 int channels
, u32
*samples
)
93 stb_vorbis
*pv
= stb_vorbis_open_memory( data
, len
, &err
, NULL
);
97 vg_error( "stb_vorbis_open_memory() failed with error code: %i\n", err
);
101 u32 length_samples
= stb_vorbis_stream_length_in_samples( pv
);
102 float *buffer
= (float *)malloc( length_samples
* channels
* sizeof(float));
106 stb_vorbis_close( pv
);
107 vg_error( "out of memory while allocating sound resource\n" );
111 int read_samples
= stb_vorbis_get_samples_float_interleaved(
112 pv
, channels
, buffer
, length_samples
* channels
);
114 if( read_samples
!= length_samples
)
116 vg_warn( "| warning: sample count mismatch. Expected %u got %i\n",
117 length_samples
, read_samples
);
118 length_samples
= read_samples
;
121 stb_vorbis_close( pv
);
122 *samples
= length_samples
;
126 static float *sfx_vorbis( const char *strFileName
, int channels
, u32
*samples
)
129 void *filedata
= vg_asset_read_s( strFileName
, &len
);
133 float *wav
= sfx_vorbis_stream( filedata
, len
, channels
, samples
);
139 vg_error( "OGG load failed\n" );
145 * thread 0 / client code
147 static int sfx_begin_edit( sfx_system
*sys
)
149 MUTEX_LOCK( sfx_mux_t01
);
151 if( sfx_q_len
>= SFX_MAX_SYSTEMS
&& !sys
->in_queue
)
153 MUTEX_UNLOCK( sfx_mux_t01
);
154 vg_warn( "Warning: No free space in sound queue\n" );
161 static void sfx_end_edit( sfx_system
*sys
)
163 MUTEX_UNLOCK( sfx_mux_t01
);
166 /* Mark change to be uploaded to queue system */
167 static int sfx_push( sfx_system
*sys
)
171 sfx_q
[ sfx_q_len
++ ] = sys
;
175 MUTEX_UNLOCK( sfx_mux_t01
);
180 /* Edit a volume float, has to be function wrapped because of mutex */
181 static void sfx_vol_fset( sfx_vol_control
*src
, float to
)
183 MUTEX_LOCK( sfx_mux_t01
);
187 MUTEX_UNLOCK( sfx_mux_t01
);
190 /* thread-safe get volume value */
191 static float sfx_vol_fget( sfx_vol_control
*src
)
195 MUTEX_LOCK( sfx_mux_t01
);
199 MUTEX_UNLOCK( sfx_mux_t01
);
204 /* thread-safe set master volume */
205 static void sfx_set_master( float to
)
207 MUTEX_LOCK( sfx_mux_t01
);
209 g_master_volume
= to
;
211 MUTEX_UNLOCK( sfx_mux_t01
);
214 /* thread-safe get master volume */
215 static float sfx_get_master(void)
219 MUTEX_LOCK( sfx_mux_t01
);
221 val
= g_master_volume
;
223 MUTEX_UNLOCK( sfx_mux_t01
);
228 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
,
229 const void *pInput
, ma_uint32 frameCount
);
231 static void vg_audio_init(void)
233 g_aud_dconfig
= ma_device_config_init( ma_device_type_playback
);
234 g_aud_dconfig
.playback
.format
= ma_format_f32
;
235 g_aud_dconfig
.playback
.channels
= 2;
236 g_aud_dconfig
.sampleRate
= 44100;
237 g_aud_dconfig
.dataCallback
= audio_mixer_callback
;
239 g_aud_dconfig
.pUserData
= NULL
;
241 vg_info( "Starting audio engine\n" );
243 if( ma_device_init( NULL
, &g_aud_dconfig
, &g_aud_device
) != MA_SUCCESS
)
245 vg_exiterr( "ma_device failed to initialize" );
249 if( ma_device_start( &g_aud_device
) != MA_SUCCESS
)
251 ma_device_uninit( &g_aud_device
);
252 vg_exiterr( "ma_device failed to start" );
257 static void vg_audio_free(void)
259 ma_device_uninit( &g_aud_device
);
266 static sfx_system
*sfx_alloc(void)
268 if( sfx_sys_len
>= SFX_MAX_SYSTEMS
)
272 * A conditional is done against this in localization step,
273 * Needs to be initialized.
275 sfx_sys
[ sfx_sys_len
].source
= NULL
;
277 return sfx_sys
+ (sfx_sys_len
++);
280 /* Fetch samples into pcf */
281 static void audio_mixer_getsamples( float *pcf
, float *source
, u32 cur
, u32 ch
)
285 pcf
[0] = source
[ cur
*2+0 ];
286 pcf
[1] = source
[ cur
*2+1 ];
290 pcf
[0] = source
[ cur
];
291 pcf
[1] = source
[ cur
];
296 * callback from miniaudio.h interface
298 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
,
299 const void *pInput
, ma_uint32 frameCount
)
301 /* Process incoming sound queue */
302 MUTEX_LOCK( sfx_mux_t01
);
304 while( sfx_q_len
--> 0 )
306 sfx_system
*src
= sfx_q
[sfx_q_len
];
314 /* Links need to exist on persistent sounds */
315 clone
->persisitent_source
= src
->flags
& SFX_FLAG_PERSISTENT
? src
: NULL
;
320 /* Volume modifiers */
321 for( int i
= 0; i
< sfx_sys_len
; i
++ )
323 sfx_system
*sys
= sfx_sys
+ i
;
325 /* Apply persistent volume if linked */
326 if( sys
->flags
& SFX_FLAG_PERSISTENT
)
328 sys
->vol
= sys
->persisitent_source
->vol
* g_master_volume
;
329 sys
->pan
= sys
->persisitent_source
->pan
;
331 /* Fadeout effect ( + remove ) */
332 if( sys
->persisitent_source
->fadeout
)
334 sys
->fadeout_current
= sys
->persisitent_source
->fadeout_current
;
335 sys
->fadeout
= sys
->persisitent_source
->fadeout
;
337 sys
->persisitent_source
= NULL
;
338 sys
->flags
&= ~SFX_FLAG_PERSISTENT
;
342 /* Apply volume slider if it has one linked */
344 sys
->cvol
= sys
->vol
* sys
->vol_src
->val
;
346 sys
->cvol
= sys
->vol
;
349 MUTEX_UNLOCK( sfx_mux_t01
);
352 float *pOut32F
= (float *)pOutBuf
;
353 for( int i
= 0; i
< frameCount
* 2; i
++ ){
357 for( int i
= 0; i
< sfx_sys_len
; i
++ )
359 sfx_system
*sys
= sfx_sys
+ i
;
361 u32 cursor
= sys
->cur
, buffer_pos
= 0;
362 float pcf
[2] = { 0.f
, 0.0f
};
364 u32 frames_write
= frameCount
;
365 float fadeout_divisor
= 1.0f
/ (float)sys
->fadeout
;
367 while( frames_write
)
369 u32 samples_this_run
= VG_MIN( frames_write
, sys
->end
- cursor
);
373 /* Force this system to be removed now */
374 if( sys
->fadeout_current
== 0 )
376 sys
->flags
&= 0x00000000;
381 samples_this_run
= VG_MIN( samples_this_run
, sys
->fadeout_current
);
384 for( u32 j
=0; j
<samples_this_run
; j
++ )
386 audio_mixer_getsamples( pcf
, sys
->source
, cursor
, sys
->ch
);
388 float vol
= sys
->cvol
;
392 vol
*= (float)sys
->fadeout_current
* fadeout_divisor
;
393 sys
->fadeout_current
--;
396 if( buffer_pos
>= frameCount
)
401 float sl
= 1.0f
-sys
->pan
,
404 pOut32F
[ buffer_pos
*2+0 ] += pcf
[0] * vol
* sl
;
405 pOut32F
[ buffer_pos
*2+1 ] += pcf
[1] * vol
* sr
;
411 frames_write
-= samples_this_run
;
413 if( sys
->flags
& SFX_FLAG_REPEAT
)
417 cursor
= sys
->clip_start
;
427 /* Redistribute sound systems */
428 MUTEX_LOCK( sfx_mux_t01
);
431 while( idx
!= sfx_sys_len
)
433 sfx_system
*src
= sfx_sys
+ idx
;
435 /* Keep only if cursor is before end or repeating */
436 if( src
->cur
< src
->end
|| (src
->flags
& SFX_FLAG_REPEAT
) )
438 sfx_sys
[ wr
++ ] = sfx_sys
[ idx
];
445 MUTEX_UNLOCK( sfx_mux_t01
);
451 * Load strings into sfx_set's memory
452 * String layout: "sounda.ogg\0soundb.ogg\0soundc.ogg\0\0"
454 static void sfx_set_strings( sfx_set
*dest
, char *strSources
,
455 u32 flags
, int bAsync
)
457 dest
->ch
= (flags
& SFX_FLAG_STEREO
)? 2: 1;
460 dest
->numsegments
= 0;
461 char *source
= strSources
;
465 while( (len
= strlen( source
)) )
468 float *sound
= sfx_vorbis( source
, dest
->ch
, &samples
);
473 dest
->numsegments
= 0;
479 float *nbuf
= realloc( dest
->main
, total
* dest
->ch
* sizeof(float) );
484 memcpy( dest
->main
+ (total
-samples
)*dest
->ch
,
485 sound
, samples
*dest
->ch
*sizeof(float) );
488 dest
->segments
[ dest
->numsegments
*2+0 ] = total
-samples
;
489 dest
->segments
[ dest
->numsegments
*2+1 ] = total
;
493 vg_error( "realloc() failed\n" );
499 dest
->numsegments
++;
503 static void sfx_set_init( sfx_set
*dest
, char *sources
)
506 sfx_set_strings( dest
, dest
->sources
, dest
->flags
, 0 );
508 sfx_set_strings( dest
, sources
, dest
->flags
, 0 );
511 static void sfx_set_play( sfx_set
*source
, sfx_system
*sys
, int id
)
513 if( sfx_begin_edit( sys
) )
516 sys
->fadeout_current
= 0;
517 sys
->source
= source
->main
;
518 sys
->cur
= source
->segments
[ id
*2 + 0 ];
519 sys
->end
= source
->segments
[ id
*2 + 1 ];
520 sys
->ch
= source
->ch
;
522 /* for diagnostics */
523 sys
->clip_start
= sys
->cur
;
524 sys
->clip_end
= sys
->end
;
525 sys
->buffer_length
= source
->segments
[ (source
->numsegments
-1)*2 + 1 ];
531 /* Pick a random sound from the buffer and play it into system */
532 static void sfx_set_playrnd( sfx_set
*source
, sfx_system
*sys
,
533 int min_id
, int max_id
)
535 if( !source
->numsegments
)
538 if( max_id
> source
->numsegments
)
540 vg_error( "Max ID out of range for playrnd\n" );
544 int pick
= (rand() % (max_id
-min_id
)) + min_id
;
546 sfx_set_play( source
, sys
, pick
);
549 static void sfx_system_fadeout( sfx_system
*sys
, u32 length_samples
)
551 if( sfx_begin_edit( sys
) )
553 sys
->fadeout_current
= length_samples
;
554 sys
->fadeout
= length_samples
;
560 static void sfx_set_free( sfx_set
*set
)