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
;
46 u32 fadeout
, fadeout_current
;
52 /* Set of up to 8 sound effects packed into one */
53 typedef struct sfx_set sfx_set
;
59 u32 segments
[32]; /* from->to,from->to ... */
65 ma_device g_aud_device
;
66 ma_device_config g_aud_dconfig
;
69 * Thread 1 - audio engine ( spawned from miniaudio.h )
70 * ======================================================
72 sfx_system sfx_sys
[SFX_MAX_SYSTEMS
];
76 * Thread 0 - Critical transfer section
77 * ======================================================
79 MUTEX_TYPE sfx_mux_t01
; /* Resources share: 0 & 1 */
81 sfx_system
*sfx_q
[SFX_MAX_SYSTEMS
]; /* Stuff changed */
84 float g_master_volume
= 1.f
;
86 /* Decompress entire vorbis stream into buffer */
87 static float *sfx_vorbis_stream( const unsigned char *data
, int len
,
88 int channels
, u32
*samples
)
91 stb_vorbis
*pv
= stb_vorbis_open_memory( data
, len
, &err
, NULL
);
95 vg_error( "stb_vorbis_open_memory() failed with error code: %i\n", err
);
99 u32 length_samples
= stb_vorbis_stream_length_in_samples( pv
);
100 float *buffer
= (float *)malloc( length_samples
* channels
* sizeof(float));
104 stb_vorbis_close( pv
);
105 vg_error( "out of memory while allocating sound resource\n" );
109 int read_samples
= stb_vorbis_get_samples_float_interleaved(
110 pv
, channels
, buffer
, length_samples
* channels
);
112 if( read_samples
!= length_samples
)
114 vg_warn( "| warning: sample count mismatch. Expected %u got %i\n",
115 length_samples
, read_samples
);
116 length_samples
= read_samples
;
119 stb_vorbis_close( pv
);
120 *samples
= length_samples
;
124 static float *sfx_vorbis( const char *strFileName
, int channels
, u32
*samples
)
127 void *filedata
= vg_asset_read_s( strFileName
, &len
);
131 float *wav
= sfx_vorbis_stream( filedata
, len
, channels
, samples
);
137 vg_error( "OGG load failed\n" );
143 * thread 0 / client code
145 static int sfx_begin_edit( sfx_system
*sys
)
147 MUTEX_LOCK( sfx_mux_t01
);
149 if( sfx_q_len
>= SFX_MAX_SYSTEMS
&& !sys
->in_queue
)
151 MUTEX_UNLOCK( sfx_mux_t01
);
152 vg_warn( "Warning: No free space in sound queue\n" );
159 static void sfx_end_edit( sfx_system
*sys
)
161 MUTEX_UNLOCK( sfx_mux_t01
);
164 /* Mark change to be uploaded to queue system */
165 static int sfx_push( sfx_system
*sys
)
169 sfx_q
[ sfx_q_len
++ ] = sys
;
173 MUTEX_UNLOCK( sfx_mux_t01
);
178 /* Edit a volume float, has to be function wrapped because of mutex */
179 static void sfx_vol_fset( sfx_vol_control
*src
, float to
)
181 MUTEX_LOCK( sfx_mux_t01
);
185 MUTEX_UNLOCK( sfx_mux_t01
);
188 /* thread-safe get volume value */
189 static float sfx_vol_fget( sfx_vol_control
*src
)
193 MUTEX_LOCK( sfx_mux_t01
);
197 MUTEX_UNLOCK( sfx_mux_t01
);
202 /* thread-safe set master volume */
203 static void sfx_set_master( float to
)
205 MUTEX_LOCK( sfx_mux_t01
);
207 g_master_volume
= to
;
209 MUTEX_UNLOCK( sfx_mux_t01
);
212 /* thread-safe get master volume */
213 static float sfx_get_master(void)
217 MUTEX_LOCK( sfx_mux_t01
);
219 val
= g_master_volume
;
221 MUTEX_UNLOCK( sfx_mux_t01
);
226 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
);
228 static void vg_audio_init(void)
230 g_aud_dconfig
= ma_device_config_init( ma_device_type_playback
);
231 g_aud_dconfig
.playback
.format
= ma_format_f32
;
232 g_aud_dconfig
.playback
.channels
= 2;
233 g_aud_dconfig
.sampleRate
= 44100;
234 g_aud_dconfig
.dataCallback
= audio_mixer_callback
;
236 g_aud_dconfig
.pUserData
= NULL
;
238 vg_info( "Starting audio engine\n" );
240 if( ma_device_init( NULL
, &g_aud_dconfig
, &g_aud_device
) != MA_SUCCESS
)
242 vg_exiterr( "ma_device failed to initialize" );
246 if( ma_device_start( &g_aud_device
) != MA_SUCCESS
)
248 ma_device_uninit( &g_aud_device
);
249 vg_exiterr( "ma_device failed to start" );
254 static void vg_audio_free(void)
256 ma_device_uninit( &g_aud_device
);
263 static sfx_system
*sfx_alloc(void)
265 if( sfx_sys_len
>= SFX_MAX_SYSTEMS
)
269 * A conditional is done against this in localization step,
270 * Needs to be initialized.
272 sfx_sys
[ sfx_sys_len
].source
= NULL
;
274 return sfx_sys
+ (sfx_sys_len
++);
277 /* Fetch samples into pcf */
278 static void audio_mixer_getsamples( float *pcf
, float *source
, u32 cur
, u32 ch
)
282 pcf
[0] = source
[ cur
*2+0 ];
283 pcf
[1] = source
[ cur
*2+1 ];
287 pcf
[0] = source
[ cur
];
288 pcf
[1] = source
[ cur
];
293 * callback from miniaudio.h interface
295 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
,
296 const void *pInput
, ma_uint32 frameCount
)
298 /* Process incoming sound queue */
299 MUTEX_LOCK( sfx_mux_t01
);
301 while( sfx_q_len
--> 0 )
303 sfx_system
*src
= sfx_q
[sfx_q_len
];
311 /* Links need to exist on persistent sounds */
312 clone
->persisitent_source
= src
->flags
& SFX_FLAG_PERSISTENT
? src
: NULL
;
317 /* Volume modifiers */
318 for( int i
= 0; i
< sfx_sys_len
; i
++ )
320 sfx_system
*sys
= sfx_sys
+ i
;
322 /* Apply persistent volume if linked */
323 if( sys
->flags
& SFX_FLAG_PERSISTENT
)
325 sys
->vol
= sys
->persisitent_source
->vol
* g_master_volume
;
327 /* Fadeout effect ( + remove ) */
328 if( sys
->persisitent_source
->fadeout
)
330 sys
->fadeout_current
= sys
->persisitent_source
->fadeout_current
;
331 sys
->fadeout
= sys
->persisitent_source
->fadeout
;
333 sys
->persisitent_source
= NULL
;
334 sys
->flags
&= ~SFX_FLAG_PERSISTENT
;
338 /* Apply volume slider if it has one linked */
340 sys
->cvol
= sys
->vol
* sys
->vol_src
->val
;
342 sys
->cvol
= sys
->vol
;
345 MUTEX_UNLOCK( sfx_mux_t01
);
348 float *pOut32F
= (float *)pOutBuf
;
349 for( int i
= 0; i
< frameCount
* 2; i
++ ){
353 for( int i
= 0; i
< sfx_sys_len
; i
++ )
355 sfx_system
*sys
= sfx_sys
+ i
;
357 u32 cursor
= sys
->cur
, buffer_pos
= 0;
358 float pcf
[2] = { 0.f
, 0.0f
};
360 u32 frames_write
= frameCount
;
361 float fadeout_divisor
= 1.0f
/ (float)sys
->fadeout
;
363 while( frames_write
)
365 u32 samples_this_run
= VG_MIN( frames_write
, sys
->end
- cursor
);
369 /* Force this system to be removed now */
370 if( sys
->fadeout_current
== 0 )
372 sys
->flags
&= 0x00000000;
377 samples_this_run
= VG_MIN( samples_this_run
, sys
->fadeout_current
);
380 for( u32 j
= 0; j
< samples_this_run
; j
++ )
382 audio_mixer_getsamples( pcf
, sys
->source
, cursor
, sys
->ch
);
384 float vol
= sys
->cvol
;
388 vol
*= (float)sys
->fadeout_current
* fadeout_divisor
;
389 sys
->fadeout_current
--;
392 if( buffer_pos
>= frameCount
)
397 pOut32F
[ buffer_pos
*2+0 ] += pcf
[0] * vol
;
398 pOut32F
[ buffer_pos
*2+1 ] += pcf
[1] * vol
;
404 frames_write
-= samples_this_run
;
406 if( sys
->flags
& SFX_FLAG_REPEAT
)
420 /* Redistribute sound systems */
421 MUTEX_LOCK( sfx_mux_t01
);
424 while( idx
!= sfx_sys_len
)
426 sfx_system
*src
= sfx_sys
+ idx
;
428 /* Keep only if cursor is before end or repeating */
429 if( src
->cur
< src
->end
|| (src
->flags
& SFX_FLAG_REPEAT
) )
431 sfx_sys
[ wr
++ ] = sfx_sys
[ idx
];
438 MUTEX_UNLOCK( sfx_mux_t01
);
444 * Load strings into sfx_set's memory
445 * String layout: "sounda.ogg\0soundb.ogg\0soundc.ogg\0\0"
447 static void sfx_set_strings( sfx_set
*dest
, char *strSources
,
448 u32 flags
, int bAsync
)
450 dest
->ch
= (flags
& SFX_FLAG_STEREO
)? 2: 1;
453 dest
->numsegments
= 0;
454 char *source
= strSources
;
458 while( (len
= strlen( source
)) )
461 float *sound
= sfx_vorbis( source
, dest
->ch
, &samples
);
466 dest
->numsegments
= 0;
472 float *nbuf
= realloc( dest
->main
, total
* dest
->ch
* sizeof(float) );
477 memcpy( dest
->main
+ (total
-samples
)*dest
->ch
,
478 sound
, samples
*dest
->ch
*sizeof(float) );
481 dest
->segments
[ dest
->numsegments
*2+0 ] = total
-samples
;
482 dest
->segments
[ dest
->numsegments
*2+1 ] = total
;
486 vg_error( "realloc() failed\n" );
492 dest
->numsegments
++;
496 static void sfx_set_init( sfx_set
*dest
, char *sources
)
499 sfx_set_strings( dest
, dest
->sources
, dest
->flags
, 0 );
501 sfx_set_strings( dest
, sources
, dest
->flags
, 0 );
504 static void sfx_set_play( sfx_set
*source
, sfx_system
*sys
, int id
)
506 if( sfx_begin_edit( sys
) )
509 sys
->fadeout_current
= 0;
510 sys
->source
= source
->main
;
511 sys
->cur
= source
->segments
[ id
*2 + 0 ];
512 sys
->end
= source
->segments
[ id
*2 + 1 ];
513 sys
->ch
= source
->ch
;
515 /* for diagnostics */
516 sys
->clip_start
= sys
->cur
;
517 sys
->clip_end
= sys
->end
;
518 sys
->buffer_length
= source
->segments
[ (source
->numsegments
-1)*2 + 1 ];
524 /* Pick a random sound from the buffer and play it into system */
525 static void sfx_set_playrnd( sfx_set
*source
, sfx_system
*sys
,
526 int min_id
, int max_id
)
528 if( !source
->numsegments
)
531 if( max_id
> source
->numsegments
)
533 vg_error( "Max ID out of range for playrnd\n" );
537 int pick
= (rand() % (max_id
-min_id
)) + min_id
;
539 sfx_set_play( source
, sys
, pick
);
542 static void sfx_system_fadeout( sfx_system
*sys
, u32 length_samples
)
544 if( sfx_begin_edit( sys
) )
546 sys
->fadeout_current
= length_samples
;
547 sys
->fadeout
= length_samples
;
553 static void sfx_set_free( sfx_set
*set
)