1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
3 #define MA_NO_GENERATION
6 #define MINIAUDIO_IMPLEMENTATION
7 #include "dr_soft/miniaudio.h"
9 #define STB_VORBIS_MAX_CHANNELS 2
10 #include "stb/stb_vorbis.h"
12 #define SFX_MAX_SYSTEMS 32
13 //#define SFX_FLAG_ONESHOT 0x1
14 #define SFX_FLAG_STEREO 0x2
15 #define SFX_FLAG_REPEAT 0x4
16 #define SFX_FLAG_PERSISTENT 0x8
17 #define FADEOUT_LENGTH 4410
18 #define FADEOUT_DIVISOR (1.f/(float)FADEOUT_LENGTH)
20 typedef struct sfx_vol_control sfx_vol_control
;
21 typedef struct sfx_system sfx_system
;
23 struct sfx_vol_control
31 sfx_system
*persisitent_source
;
34 // Source buffer start
35 float *source
, *replacement
;
37 u32 clip_start
, clip_end
, buffer_length
;
40 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
;
70 // Thread 1 - audio engine ( spawned from miniaudio.h )
71 // ======================================================
72 sfx_system sfx_sys
[SFX_MAX_SYSTEMS
];
75 // Thread 0 - Critical transfer section
76 // ======================================================
77 MUTEX_TYPE sfx_mux_t01
; // Resources share: 0 & 1
79 sfx_system
*sfx_q
[SFX_MAX_SYSTEMS
]; // Stuff changed
80 int sfx_q_len
= 0; // How much
83 // ======================================================
85 // g_vol_master is never directly acessed by users
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
, int channels
, u32
*samples
)
92 stb_vorbis
*pv
= stb_vorbis_open_memory( data
, len
, &err
, NULL
);
96 vg_error( "stb_vorbis_open_memory() failed with error code: %i\n", err
);
100 u32 length_samples
= stb_vorbis_stream_length_in_samples( pv
);
101 float *buffer
= (float *)malloc( length_samples
* channels
* sizeof( float ));
105 stb_vorbis_close( pv
);
106 vg_error( "out of memory while allocating sound resource\n" );
110 int read_samples
= stb_vorbis_get_samples_float_interleaved( pv
, channels
, buffer
, length_samples
* channels
);
111 if( read_samples
!= length_samples
)
113 vg_warn( "| warning: sample count mismatch. Expected %u got %i\n", length_samples
, read_samples
);
114 length_samples
= read_samples
;
117 stb_vorbis_close( pv
);
118 *samples
= length_samples
;
122 static float *sfx_vorbis( const char *strFileName
, int channels
, u32
*samples
)
125 void *filedata
= vg_asset_read_s( strFileName
, &len
);
129 float *wav
= sfx_vorbis_stream( filedata
, len
, channels
, samples
);
135 vg_error( "OGG load failed\n" );
140 typedef struct sfx_bgload sfx_bgload_t
;
151 void(*OnComplete
)(sfx_bgload_t
*inf
);
154 // Thread worker for background load job
155 void *sfx_vorbis_a_t( void *_inf
)
157 sfx_bgload_t
*info
= _inf
;
160 info
->buffer
= sfx_vorbis( info
->path
, info
->channels
, &info
->samples
);
161 info
->OnComplete( info
);
166 // Asynchronous resource load
167 int sfx_vorbis_a( const char *path
, int channels
, void(*OnComplete
)(sfx_bgload_t
*inf
), void *user
)
169 vg_info( "background job started for: %s\n", path
);
171 sfx_bgload_t
*params
= malloc( sizeof( sfx_bgload_t
) );
172 params
->path
= malloc( strlen( path
) + 1 );
173 strcpy( params
->path
, path
);
174 params
->OnComplete
= OnComplete
;
176 params
->channels
= channels
;
178 return vg_thread_run( sfx_vorbis_a_t
, params
);
181 // Asynchronous load-to-system callback
182 struct sfx_vorbis_a_to_inf
188 #define SFX_A_FLAG_AUTOSTART 0x1
189 #define SFX_A_FLAG_AUTOFREE 0x2
192 static int sfx_save( sfx_system *sys );
194 // Asynchronous load-to-system callback
195 void sfx_vorbis_a_to_c( sfx_bgload_t *loadinf )
197 struct sfx_vorbis_a_to_inf *inf = loadinf->user;
199 // Mark buffer for deallocation if autofree is set
200 if( inf->flags & SFX_A_FLAG_AUTOFREE )
201 inf->sys->replacement = loadinf->buffer;
203 inf->sys->source = loadinf->buffer;
205 inf->sys->end = loadinf->samples;
207 if( inf->flags & SFX_A_FLAG_AUTOSTART )
208 sfx_save( inf->sys );
210 free( loadinf->path );
215 // Asynchronous vorbis load into audio system
216 void sfx_vorbis_a_to( sfx_system *sys, const char *strFileName, int channels, u32 flags )
218 struct sfx_vorbis_a_to_inf *inf = malloc( sizeof( struct sfx_vorbis_a_to_inf ) );
224 if( !sfx_vorbis_a( strFileName, channels, sfx_vorbis_a_to_c, inf ) )
229 // ======================================================
231 static int sfx_begin_edit( sfx_system
*sys
)
233 MUTEX_LOCK( sfx_mux_t01
);
235 if( sfx_q_len
>= SFX_MAX_SYSTEMS
&& !sys
->in_queue
)
237 MUTEX_UNLOCK( sfx_mux_t01
);
238 vg_warn( "Warning: No free space in sound queue\n" );
245 static void sfx_end_edit( sfx_system
*sys
)
247 MUTEX_UNLOCK( sfx_mux_t01
);
250 // Mark change to be uploaded to queue system
251 static int sfx_push( sfx_system
*sys
)
255 // Mark change in queue
256 sfx_q
[ sfx_q_len
++ ] = sys
;
260 MUTEX_UNLOCK( sfx_mux_t01
);
265 // Edit a volume float, has to be function wrapped because of mutex
266 static void sfx_vol_fset( sfx_vol_control
*src
, float to
)
268 MUTEX_LOCK( sfx_mux_t01
);
272 MUTEX_UNLOCK( sfx_mux_t01
);
275 // thread-safe get volume value
276 static float sfx_vol_fget( sfx_vol_control
*src
)
280 MUTEX_LOCK( sfx_mux_t01
);
284 MUTEX_UNLOCK( sfx_mux_t01
);
289 // thread-safe set master volume
290 static void sfx_set_master( float to
)
292 MUTEX_LOCK( sfx_mux_t01
);
294 g_master_volume
= to
;
296 MUTEX_UNLOCK( sfx_mux_t01
);
299 // thread-safe get master volume
300 static float sfx_get_master(void)
304 MUTEX_LOCK( sfx_mux_t01
);
306 val
= g_master_volume
;
308 MUTEX_UNLOCK( sfx_mux_t01
);
313 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
);
316 static void vg_audio_init(void)
318 g_aud_dconfig
= ma_device_config_init( ma_device_type_playback
);
319 g_aud_dconfig
.playback
.format
= ma_format_f32
;
320 g_aud_dconfig
.playback
.channels
= 2;
321 g_aud_dconfig
.sampleRate
= 44100;
322 g_aud_dconfig
.dataCallback
= audio_mixer_callback
;
324 g_aud_dconfig
.pUserData
= NULL
;
326 vg_info( "Starting audio engine\n" );
328 if( ma_device_init( NULL
, &g_aud_dconfig
, &g_aud_device
) != MA_SUCCESS
)
330 vg_exiterr( "ma_device failed to initialize" );
334 if( ma_device_start( &g_aud_device
) != MA_SUCCESS
)
336 ma_device_uninit( &g_aud_device
);
337 vg_exiterr( "ma_device failed to start" );
342 // Shutdown audio device
343 static void vg_audio_free(void)
345 ma_device_uninit( &g_aud_device
);
349 // ======================================================
351 // Create and return slot for a sound
352 static sfx_system
*sfx_alloc(void)
354 if( sfx_sys_len
>= SFX_MAX_SYSTEMS
)
357 // A conditional is done against this in localization step,
358 // Needs to be initialized.
359 sfx_sys
[ sfx_sys_len
].source
= NULL
;
361 return sfx_sys
+ (sfx_sys_len
++);
364 // Fetch samples into pcf
365 static void audio_mixer_getsamples( float *pcf
, float *source
, u32 cur
, u32 ch
)
369 pcf
[0] = source
[ cur
*2+0 ];
370 pcf
[1] = source
[ cur
*2+1 ];
374 pcf
[0] = source
[ cur
];
375 pcf
[1] = source
[ cur
];
379 // miniaudio.h interface
380 void audio_mixer_callback( ma_device
*pDevice
, void *pOutBuf
, const void *pInput
, ma_uint32 frameCount
)
382 // Process incoming sound queue
383 MUTEX_LOCK( sfx_mux_t01
);
385 while( sfx_q_len
--> 0 )
387 sfx_system
*src
= sfx_q
[sfx_q_len
];
396 // Links need to exist on persistent sounds
397 clone
->persisitent_source
= src
->flags
& SFX_FLAG_PERSISTENT
? src
: NULL
;
403 for( int i
= 0; i
< sfx_sys_len
; i
++ )
405 sfx_system
*sys
= sfx_sys
+ i
;
407 // Apply persistent volume if linked
408 if( sys
->flags
& SFX_FLAG_PERSISTENT
)
410 sys
->vol
= sys
->persisitent_source
->vol
* g_master_volume
;
412 // Persistent triggers
413 // -------------------
415 // Fadeout effect ( + remove )
416 if( sys
->persisitent_source
->fadeout
)
418 sys
->fadeout_current
= sys
->persisitent_source
->fadeout_current
;
419 sys
->fadeout
= sys
->persisitent_source
->fadeout
;
421 sys
->persisitent_source
= NULL
;
422 sys
->flags
&= ~SFX_FLAG_PERSISTENT
;
426 // Apply volume slider if it has one linked
428 sys
->cvol
= sys
->vol
* sys
->vol_src
->val
;
430 sys
->cvol
= sys
->vol
;
433 MUTEX_UNLOCK( sfx_mux_t01
);
436 float *pOut32F
= (float *)pOutBuf
;
437 for( int i
= 0; i
< frameCount
* 2; i
++ ){
441 for( int i
= 0; i
< sfx_sys_len
; i
++ )
443 sfx_system
*sys
= sfx_sys
+ i
;
445 u32 cursor
= sys
->cur
, buffer_pos
= 0;
446 float pcf
[2] = { 0.f
, 0.0f
};
448 u32 frames_write
= frameCount
;
449 float fadeout_divisor
= 1.0f
/ (float)sys
->fadeout
;
451 while( frames_write
)
453 u32 samples_this_run
= VG_MIN( frames_write
, sys
->end
- cursor
);
457 // Force this system to be removed now
458 if( sys
->fadeout_current
== 0 )
460 sys
->flags
&= 0x00000000;
465 samples_this_run
= VG_MIN( samples_this_run
, sys
->fadeout_current
);
468 for( u32 j
= 0; j
< samples_this_run
; j
++ )
470 audio_mixer_getsamples( pcf
, sys
->source
, cursor
, sys
->ch
);
472 float vol
= sys
->vol
;
476 vol
*= (float)sys
->fadeout_current
* fadeout_divisor
;
477 sys
->fadeout_current
--;
480 if( buffer_pos
>= frameCount
)
485 pOut32F
[ buffer_pos
*2+0 ] += pcf
[0] * vol
;
486 pOut32F
[ buffer_pos
*2+1 ] += pcf
[1] * vol
;
492 frames_write
-= samples_this_run
;
494 if( sys
->flags
& SFX_FLAG_REPEAT
)
508 // Redistribute sound systems
509 MUTEX_LOCK( sfx_mux_t01
);
512 while( idx
!= sfx_sys_len
)
514 sfx_system
*src
= sfx_sys
+ idx
;
516 // Keep only if cursor is before end or repeating
517 if( src
->cur
< src
->end
|| (src
->flags
& SFX_FLAG_REPEAT
) )
519 sfx_sys
[ wr
++ ] = sfx_sys
[ idx
];
526 MUTEX_UNLOCK( sfx_mux_t01
);
531 // Load strings into sfx_set's memory
532 // String layout: "sounda.ogg\0soundb.ogg\0soundc.ogg\0\0"
533 static void sfx_set_strings( sfx_set
*dest
, char *strSources
, u32 flags
, int bAsync
)
535 dest
->ch
= (flags
& SFX_FLAG_STEREO
)? 2: 1;
538 dest
->numsegments
= 0;
539 char *source
= strSources
;
543 while( (len
= strlen( source
)) )
546 float *sound
= sfx_vorbis( source
, dest
->ch
, &samples
);
551 dest
->numsegments
= 0;
557 float *nbuf
= realloc( dest
->main
, total
* dest
->ch
* sizeof(float) );
562 memcpy( dest
->main
+ (total
-samples
)*dest
->ch
, sound
, samples
*dest
->ch
*sizeof(float) );
565 dest
->segments
[ dest
->numsegments
*2+0 ] = total
-samples
;
566 dest
->segments
[ dest
->numsegments
*2+1 ] = total
;
570 vg_error( "realloc() failed\n" );
576 dest
->numsegments
++;
580 static void sfx_set_init( sfx_set
*dest
, char *sources
)
583 sfx_set_strings( dest
, dest
->sources
, dest
->flags
, 0 );
585 sfx_set_strings( dest
, sources
, dest
->flags
, 0 );
588 static void sfx_set_play( sfx_set
*source
, sfx_system
*sys
, int id
)
590 if( sfx_begin_edit( sys
) )
593 sys
->fadeout_current
= 0;
594 sys
->source
= source
->main
;
595 sys
->cur
= source
->segments
[ id
*2 + 0 ];
596 sys
->end
= source
->segments
[ id
*2 + 1 ];
597 sys
->ch
= source
->ch
;
600 sys
->clip_start
= sys
->cur
;
601 sys
->clip_end
= sys
->end
;
602 sys
->buffer_length
= source
->segments
[ (source
->numsegments
-1)*2 + 1 ];
608 // Pick a random sound from the buffer and play it into system
609 static void sfx_set_playrnd( sfx_set
*source
, sfx_system
*sys
, int min_id
, int max_id
)
611 if( !source
->numsegments
)
614 if( max_id
> source
->numsegments
)
616 vg_error( "Max ID out of range for playrnd\n" );
620 int pick
= (rand() % (max_id
-min_id
)) + min_id
;
622 sfx_set_play( source
, sys
, pick
);
625 static void sfx_system_fadeout( sfx_system
*sys
, u32 length_samples
)
627 if( sfx_begin_edit( sys
) )
629 sys
->fadeout_current
= length_samples
;
630 sys
->fadeout
= length_samples
;
636 // Free set resources
637 static void sfx_set_free( sfx_set
*set
)