X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;ds=sidebyside;f=vg%2Fvg_audio.h;fp=vg%2Fvg_audio.h;h=e62d5845447e35f09c3e3b6536911a38ff2e3e42;hb=6836e834f8db725e08015a98401f2be97e5b9849;hp=0000000000000000000000000000000000000000;hpb=b5740880fe3ffe59546bb80173ad3a6e1312648e;p=vg.git diff --git a/vg/vg_audio.h b/vg/vg_audio.h new file mode 100644 index 0000000..e62d584 --- /dev/null +++ b/vg/vg_audio.h @@ -0,0 +1,637 @@ +// Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved + +#define MINIAUDIO_IMPLEMENTATION +#include "dr_soft/miniaudio.h" + +#define STB_VORBIS_MAX_CHANNELS 2 +#include "stb/stb_vorbis.h" + +#define SFX_MAX_SYSTEMS 32 +//#define SFX_FLAG_ONESHOT 0x1 +#define SFX_FLAG_STEREO 0x2 +#define SFX_FLAG_REPEAT 0x4 +#define SFX_FLAG_PERSISTENT 0x8 +#define FADEOUT_LENGTH 4410 +#define FADEOUT_DIVISOR (1.f/(float)FADEOUT_LENGTH) + +typedef struct sfx_vol_control sfx_vol_control; +typedef struct sfx_system sfx_system; + +struct sfx_vol_control +{ + float val; + const char *name; +}; + +struct sfx_system +{ + sfx_system *persisitent_source; + int in_queue; + + // Source buffer start + float *source, *replacement; + + u32 clip_start, clip_end, buffer_length; + + // Modifiers + sfx_vol_control *vol_src; + float vol, cvol; + + // Info + u32 ch, end, cur; + u32 flags; + + // Effects + u32 fadeout, fadeout_current; + + // Diagnostic + const char *name; +}; + +// Set of up to 8 sound effects packed into one +typedef struct sfx_set sfx_set; +struct sfx_set +{ + float *main; + char *sources; + + u32 segments[32]; //from->to,from->to ... + u32 numsegments; + u32 ch; + u32 flags; +}; + +ma_device g_aud_device; +ma_device_config g_aud_dconfig; + +// Thread 1 - audio engine ( spawned from miniaudio.h ) +// ====================================================== +sfx_system sfx_sys[SFX_MAX_SYSTEMS]; +int sfx_sys_len = 0; + +// Thread 0 - Critical transfer section +// ====================================================== +MUTEX_TYPE sfx_mux_t01; // Resources share: 0 & 1 + +sfx_system *sfx_q[SFX_MAX_SYSTEMS]; // Stuff changed +int sfx_q_len = 0; // How much + +// x / 2 +// ====================================================== + +// g_vol_master is never directly acessed by users +float g_master_volume = 1.f; + +// Decompress entire vorbis stream into buffer +static float *sfx_vorbis_stream( const unsigned char *data, int len, int channels, u32 *samples ) +{ + int err; + stb_vorbis *pv = stb_vorbis_open_memory( data, len, &err, NULL ); + + if( !pv ) + { + vg_error( "stb_vorbis_open_memory() failed with error code: %i\n", err ); + return NULL; + } + + u32 length_samples = stb_vorbis_stream_length_in_samples( pv ); + float *buffer = (float *)malloc( length_samples * channels * sizeof( float )); + + if( !buffer ) + { + stb_vorbis_close( pv ); + vg_error( "out of memory while allocating sound resource\n" ); + return NULL; + } + + int read_samples = stb_vorbis_get_samples_float_interleaved( pv, channels, buffer, length_samples * channels ); + if( read_samples != length_samples ) + { + vg_warn( "| warning: sample count mismatch. Expected %u got %i\n", length_samples, read_samples ); + length_samples = read_samples; + } + + stb_vorbis_close( pv ); + *samples = length_samples; + return buffer; +} + +static float *sfx_vorbis( const char *strFileName, int channels, u32 *samples ) +{ + i64 len; + void *filedata = vg_asset_read_s( strFileName, &len ); + + if( filedata ) + { + float *wav = sfx_vorbis_stream( filedata, len, channels, samples ); + free( filedata ); + return wav; + } + else + { + vg_error( "OGG load failed\n" ); + return NULL; + } +} + +typedef struct sfx_bgload sfx_bgload_t; +struct sfx_bgload +{ + char *path; + u32 channels; + + float *buffer; + u32 samples; + + void *user; + + void(*OnComplete)(sfx_bgload_t *inf); +}; + +// Thread worker for background load job +void *sfx_vorbis_a_t( void *_inf ) +{ + sfx_bgload_t *info = _inf; + + // Load the ogg clip + info->buffer = sfx_vorbis( info->path, info->channels, &info->samples ); + info->OnComplete( info ); + + return NULL; +} + +// Asynchronous resource load +int sfx_vorbis_a( const char *path, int channels, void(*OnComplete)(sfx_bgload_t *inf), void *user ) +{ + vg_info( "background job started for: %s\n", path ); + + sfx_bgload_t *params = malloc( sizeof( sfx_bgload_t ) ); + params->path = malloc( strlen( path ) + 1 ); + strcpy( params->path, path ); + params->OnComplete = OnComplete; + params->user = user; + params->channels = channels; + + return vg_thread_run( sfx_vorbis_a_t, params ); +} + +// Asynchronous load-to-system callback +struct sfx_vorbis_a_to_inf +{ + sfx_system *sys; + u32 flags; +}; + +#define SFX_A_FLAG_AUTOSTART 0x1 +#define SFX_A_FLAG_AUTOFREE 0x2 + +/* +static int sfx_save( sfx_system *sys ); + +// Asynchronous load-to-system callback +void sfx_vorbis_a_to_c( sfx_bgload_t *loadinf ) +{ + struct sfx_vorbis_a_to_inf *inf = loadinf->user; + + // Mark buffer for deallocation if autofree is set + if( inf->flags & SFX_A_FLAG_AUTOFREE ) + inf->sys->replacement = loadinf->buffer; + else + inf->sys->source = loadinf->buffer; + + inf->sys->end = loadinf->samples; + + if( inf->flags & SFX_A_FLAG_AUTOSTART ) + sfx_save( inf->sys ); + + free( loadinf->path ); + free( loadinf ); + free( inf ); +} + +// Asynchronous vorbis load into audio system +void sfx_vorbis_a_to( sfx_system *sys, const char *strFileName, int channels, u32 flags ) +{ + struct sfx_vorbis_a_to_inf *inf = malloc( sizeof( struct sfx_vorbis_a_to_inf ) ); + inf->flags = flags; + inf->sys = sys; + + sys->ch = channels; + + if( !sfx_vorbis_a( strFileName, channels, sfx_vorbis_a_to_c, inf ) ) + free( inf ); +}*/ + +// 0 +// ====================================================== + +static int sfx_begin_edit( sfx_system *sys ) +{ + MUTEX_LOCK( sfx_mux_t01 ); + + if( sfx_q_len >= SFX_MAX_SYSTEMS && !sys->in_queue ) + { + MUTEX_UNLOCK( sfx_mux_t01 ); + vg_warn( "Warning: No free space in sound queue\n" ); + return 0; + } + + return 1; +} + +static void sfx_end_edit( sfx_system *sys ) +{ + MUTEX_UNLOCK( sfx_mux_t01 ); +} + +// Mark change to be uploaded to queue system +static int sfx_push( sfx_system *sys ) +{ + if( !sys->in_queue ) + { + // Mark change in queue + sfx_q[ sfx_q_len ++ ] = sys; + sys->in_queue = 1; + } + + MUTEX_UNLOCK( sfx_mux_t01 ); + + return 1; +} + +// Edit a volume float, has to be function wrapped because of mutex +static void sfx_vol_fset( sfx_vol_control *src, float to ) +{ + MUTEX_LOCK( sfx_mux_t01 ); + + src->val = to; + + MUTEX_UNLOCK( sfx_mux_t01 ); +} + +// thread-safe get volume value +static float sfx_vol_fget( sfx_vol_control *src ) +{ + float val; + + MUTEX_LOCK( sfx_mux_t01 ); + + val = src->val; + + MUTEX_UNLOCK( sfx_mux_t01 ); + + return val; +} + +// thread-safe set master volume +static void sfx_set_master( float to ) +{ + MUTEX_LOCK( sfx_mux_t01 ); + + g_master_volume = to; + + MUTEX_UNLOCK( sfx_mux_t01 ); +} + +// thread-safe get master volume +static float sfx_get_master(void) +{ + float val; + + MUTEX_LOCK( sfx_mux_t01 ); + + val = g_master_volume; + + MUTEX_UNLOCK( sfx_mux_t01 ); + + return val; +} + +void audio_mixer_callback( ma_device *pDevice, void *pOutBuf, const void *pInput, ma_uint32 frameCount ); + +// Miniaudio.h init +static void vg_audio_init(void) +{ + g_aud_dconfig = ma_device_config_init( ma_device_type_playback ); + g_aud_dconfig.playback.format = ma_format_f32; + g_aud_dconfig.playback.channels = 2; + g_aud_dconfig.sampleRate = 44100; + g_aud_dconfig.dataCallback = audio_mixer_callback; + + g_aud_dconfig.pUserData = NULL; + + vg_info( "Starting audio engine\n" ); + + if( ma_device_init( NULL, &g_aud_dconfig, &g_aud_device ) != MA_SUCCESS ) + { + vg_exiterr( "ma_device failed to initialize" ); + } + else + { + if( ma_device_start( &g_aud_device ) != MA_SUCCESS ) + { + ma_device_uninit( &g_aud_device ); + vg_exiterr( "ma_device failed to start" ); + } + } +} + +// Shutdown audio device +static void vg_audio_free(void) +{ + ma_device_uninit( &g_aud_device ); +} + +// 1 +// ====================================================== + +// Create and return slot for a sound +static sfx_system *sfx_alloc(void) +{ + if( sfx_sys_len >= SFX_MAX_SYSTEMS ) + return NULL; + + // A conditional is done against this in localization step, + // Needs to be initialized. + sfx_sys[ sfx_sys_len ].source = NULL; + + return sfx_sys + (sfx_sys_len++); +} + +// Fetch samples into pcf +static void audio_mixer_getsamples( float *pcf, float *source, u32 cur, u32 ch ) +{ + if( ch == 2 ) + { + pcf[0] = source[ cur*2+0 ]; + pcf[1] = source[ cur*2+1 ]; + } + else + { + pcf[0] = source[ cur ]; + pcf[1] = source[ cur ]; + } +} + +// miniaudio.h interface +void audio_mixer_callback( ma_device *pDevice, void *pOutBuf, const void *pInput, ma_uint32 frameCount ) +{ + // Process incoming sound queue + MUTEX_LOCK( sfx_mux_t01 ); + + while( sfx_q_len --> 0 ) + { + sfx_system *src = sfx_q[sfx_q_len]; + sfx_system *clone; + + src->in_queue = 0; + + // Copy + clone = sfx_alloc(); + *clone = *src; + + // Links need to exist on persistent sounds + clone->persisitent_source = src->flags & SFX_FLAG_PERSISTENT? src: NULL; + } + + sfx_q_len = 0; + + // Volume modifiers + for( int i = 0; i < sfx_sys_len; i ++ ) + { + sfx_system *sys = sfx_sys + i; + + // Apply persistent volume if linked + if( sys->flags & SFX_FLAG_PERSISTENT ) + { + sys->vol = sys->persisitent_source->vol * g_master_volume; + + // Persistent triggers + // ------------------- + + // Fadeout effect ( + remove ) + if( sys->persisitent_source->fadeout ) + { + sys->fadeout_current = sys->persisitent_source->fadeout_current; + sys->fadeout = sys->persisitent_source->fadeout; + + sys->persisitent_source = NULL; + sys->flags &= ~SFX_FLAG_PERSISTENT; + } + } + + // Apply volume slider if it has one linked + if( sys->vol_src ) + sys->cvol = sys->vol * sys->vol_src->val; + else + sys->cvol = sys->vol; + } + + MUTEX_UNLOCK( sfx_mux_t01 ); + + // Clear buffer + float *pOut32F = (float *)pOutBuf; + for( int i = 0; i < frameCount * 2; i ++ ){ + pOut32F[i] = 0.f; + } + + for( int i = 0; i < sfx_sys_len; i ++ ) + { + sfx_system *sys = sfx_sys + i; + + u32 cursor = sys->cur, buffer_pos = 0; + float pcf[2] = { 0.f, 0.0f }; + + u32 frames_write = frameCount; + float fadeout_divisor = 1.0f / (float)sys->fadeout; + + while( frames_write ) + { + u32 samples_this_run = VG_MIN( frames_write, sys->end - cursor ); + + if( sys->fadeout ) + { + // Force this system to be removed now + if( sys->fadeout_current == 0 ) + { + sys->flags &= 0x00000000; + sys->cur = sys->end; + break; + } + + samples_this_run = VG_MIN( samples_this_run, sys->fadeout_current ); + } + + for( u32 j = 0; j < samples_this_run; j ++ ) + { + audio_mixer_getsamples( pcf, sys->source, cursor, sys->ch ); + + float vol = sys->vol; + + if( sys->fadeout ) + { + vol *= (float)sys->fadeout_current * fadeout_divisor; + sys->fadeout_current --; + } + + if( buffer_pos >= frameCount ) + { + break; + } + + pOut32F[ buffer_pos*2+0 ] += pcf[0] * vol; + pOut32F[ buffer_pos*2+1 ] += pcf[1] * vol; + + cursor ++; + buffer_pos ++; + } + + frames_write -= samples_this_run; + + if( sys->flags & SFX_FLAG_REPEAT ) + { + if( frames_write ) + { + cursor = 0; + continue; + } + } + + sys->cur = cursor; + break; + } + } + + // Redistribute sound systems + MUTEX_LOCK( sfx_mux_t01 ); + + u32 idx = 0, wr = 0; + while( idx != sfx_sys_len ) + { + sfx_system *src = sfx_sys + idx; + + // Keep only if cursor is before end or repeating + if( src->cur < src->end || (src->flags & SFX_FLAG_REPEAT) ) + { + sfx_sys[ wr ++ ] = sfx_sys[ idx ]; + } + + idx ++ ; + } + sfx_sys_len = wr; + + MUTEX_UNLOCK( sfx_mux_t01 ); + + (void)pInput; +} + +// Load strings into sfx_set's memory +// String layout: "sounda.ogg\0soundb.ogg\0soundc.ogg\0\0" +static void sfx_set_strings( sfx_set *dest, char *strSources, u32 flags, int bAsync ) +{ + dest->ch = (flags & SFX_FLAG_STEREO)? 2: 1; + + dest->main = NULL; + dest->numsegments = 0; + char *source = strSources; + + u32 total = 0; + int len; + while( (len = strlen( source )) ) + { + u32 samples; + float *sound = sfx_vorbis( source, dest->ch, &samples ); + + if( !sound ) + { + free( dest->main ); + dest->numsegments = 0; + return; + } + + total += samples; + + float *nbuf = realloc( dest->main, total * dest->ch * sizeof(float) ); + + if( nbuf ) + { + dest->main = nbuf; + memcpy( dest->main + (total-samples)*dest->ch, sound, samples*dest->ch*sizeof(float) ); + free( sound ); + + dest->segments[ dest->numsegments*2+0 ] = total-samples; + dest->segments[ dest->numsegments*2+1 ] = total; + } + else + { + vg_error( "realloc() failed\n" ); + free( sound ); + return; + } + + source += len +1; + dest->numsegments ++; + } +} + +static void sfx_set_init( sfx_set *dest, char *sources ) +{ + if( !sources ) + sfx_set_strings( dest, dest->sources, dest->flags, 0 ); + else + sfx_set_strings( dest, sources, dest->flags, 0 ); +} + +static void sfx_set_play( sfx_set *source, sfx_system *sys, int id ) +{ + if( sfx_begin_edit( sys ) ) + { + sys->fadeout = 0; + sys->fadeout_current = 0; + sys->source = source->main; + sys->cur = source->segments[ id*2 + 0 ]; + sys->end = source->segments[ id*2 + 1 ]; + sys->ch = source->ch; + + // Diagnostics + sys->clip_start = sys->cur; + sys->clip_end = sys->end; + sys->buffer_length = source->segments[ (source->numsegments-1)*2 + 1 ]; + + sfx_push( sys ); + } +} + +// Pick a random sound from the buffer and play it into system +static void sfx_set_playrnd( sfx_set *source, sfx_system *sys, int min_id, int max_id ) +{ + if( !source->numsegments ) + return; + + if( max_id > source->numsegments ) + { + vg_error( "Max ID out of range for playrnd\n" ); + return; + } + + int pick = (rand() % (max_id-min_id)) + min_id; + + sfx_set_play( source, sys, pick ); +} + +static void sfx_system_fadeout( sfx_system *sys, u32 length_samples ) +{ + if( sfx_begin_edit( sys ) ) + { + sys->fadeout_current = length_samples; + sys->fadeout = length_samples; + + sfx_end_edit( sys ); + } +} + +// Free set resources +static void sfx_set_free( sfx_set *set ) +{ + free( set->main ); +}