Fix major overstep with last commit
[vg.git] / src / vg / vg_audio.h
index bb4d0920feb889361506d16db83393fefd861138..ae37b2e5078000476f2d2f879fbec4d872817609 100644 (file)
@@ -6,7 +6,16 @@
 #define MA_NO_GENERATION
 #define MA_NO_DECODING
 #define MA_NO_ENCODING
+#define MA_NO_WAV
+#define MA_NO_FLAC
+#define MA_NO_MP3
+#define MA_NO_ENGINE
+#define MA_NO_NODE_GRAPH
+#define MA_NO_RESOURCE_MANAGER
+
 #include "dr_soft/miniaudio.h"
+
+
 #include "vg/vg.h"
 #include "vg/vg_stdint.h"
 #include "vg/vg_platform.h"
 
 #include <time.h>
 
+#ifdef __GNUC__
+  #pragma GCC push_options
+  #pragma GCC optimize ("O3")
+#endif
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
 #define STB_VORBIS_MAX_CHANNELS 2
 #include "stb/stb_vorbis.h"
 
+#pragma GCC diagnostic pop 
+
+#ifdef __GNUC__
+  #pragma GCC pop_options
+#endif
+
 #define SFX_MAX_SYSTEMS       32
 #define AUDIO_FLAG_LOOP       0x1
 #define AUDIO_FLAG_ONESHOT    0x2
 #define AUDIO_FLAG_SPACIAL_3D 0x4
 
-#define FADEOUT_LENGTH        4410
+#define FADEOUT_LENGTH        1100
 #define FADEOUT_DIVISOR       (1.0f/(float)FADEOUT_LENGTH)
 
 #define AUDIO_DECODE_SIZE     (1024*256)  /* 256 kb decoding buffers */
@@ -34,8 +57,7 @@
 enum audio_source_mode
 {
    k_audio_source_mono,
-   k_audio_source_mono_compressed,
-   k_audio_source_stereo_compressed
+   k_audio_source_compressed,
 };
 
 typedef struct audio_clip audio_clip;
@@ -65,7 +87,7 @@ struct audio_player
 {
    aatree_ptr active_entity;  /* non-nil if currently playing */
    audio_mix_info info;
-   int enqued;
+   int enqued, init;
    
    /* Diagnostic */
    const char *name;
@@ -103,8 +125,9 @@ static struct vg_audio_system
 
    /* synchro */
    int               sync_locked;
-   MUTEX_TYPE        mutex_checker;
-   MUTEX_TYPE        mutex_sync;
+
+   vg_mutex          mux_checker,
+                     mux_sync;
 
    /* Audio engine, thread 1 */
    struct active_audio_player
@@ -128,11 +151,19 @@ static struct vg_audio_system
    audio_entity      entity_queue[SFX_MAX_SYSTEMS];
    int               queue_len;
 
-   char              performance_info[128];
+   char              performance_info[128],
+                     performance_sub0[64],
+                     performance_sub1[64];
+
    int               debug_ui;
 
    v3f               listener_pos,
                      listener_ears;
+
+   double            perf_ms_decode,
+                     perf_ms_mix;
+
+   u32               perf_measurements;
 }
 vg_audio;
 
@@ -140,11 +171,7 @@ static void *audio_alloc( u32 size )
 {
    u32 new_current = vg_audio.mem_current + size;
    if( new_current > vg_audio.mem_total )
-   {
-      vg_error( "audio pool over budget!\n" );
-      free( vg_audio.mem );
-      return NULL;
-   }
+      vg_fatal_exit_loop( "Audio pool ran out of memory" );
 
    void *ptr = vg_audio.mem + vg_audio.mem_current;
    vg_audio.mem_current = new_current;
@@ -160,17 +187,17 @@ static void *audio_alloc( u32 size )
 static int audio_lock_checker_load(void)
 {
    int value;
-   MUTEX_LOCK( vg_audio.mutex_checker );
+   vg_mutex_lock( &vg_audio.mux_checker );
    value = vg_audio.sync_locked;
-   MUTEX_UNLOCK( vg_audio.mutex_checker );
+   vg_mutex_unlock( &vg_audio.mux_checker );
    return value;
 }
 
 static void audio_lock_checker_store( int value )
 {
-   MUTEX_LOCK( vg_audio.mutex_checker );
+   vg_mutex_lock( &vg_audio.mux_checker );
    vg_audio.sync_locked = value;
-   MUTEX_UNLOCK( vg_audio.mutex_checker );
+   vg_mutex_unlock( &vg_audio.mux_checker );
 }
 
 static void audio_require_lock(void)
@@ -178,27 +205,31 @@ static void audio_require_lock(void)
    if( audio_lock_checker_load() )
       return;
 
-   vg_exiterr( "Modifying sound effects systems requires locking\n" );
+   vg_error( "Modifying sound effects systems requires locking\n" );
+   abort();
 }
 
 static void audio_lock(void)
 {
-   MUTEX_LOCK( vg_audio.mutex_sync );
+   vg_mutex_lock( &vg_audio.mux_sync );
    audio_lock_checker_store(1);
 }
 
 static void audio_unlock(void)
 {
    audio_lock_checker_store(0);
-   MUTEX_UNLOCK( vg_audio.mutex_sync );
+   vg_mutex_unlock( &vg_audio.mux_sync );
 }
 
 
 static void audio_mixer_callback( ma_device *pDevice, void *pOutBuf,
                                     const void *pInput, ma_uint32 frameCount );
 
-static int vg_audio_init(void)
+static void vg_audio_init(void)
 {
+   vg_mutex_init( &vg_audio.mux_checker );
+   vg_mutex_init( &vg_audio.mux_sync );
+
    vg_convar_push( (struct vg_convar){
       .name = "debug_audio",
       .data = &vg_audio.debug_ui,
@@ -210,7 +241,7 @@ static int vg_audio_init(void)
    u32 decode_region    = AUDIO_DECODE_SIZE * SFX_MAX_SYSTEMS;
    vg_audio.mem_total   = 1024*1024*32;
    vg_audio.mem_current = 0;
-   vg_audio.mem         = malloc( vg_audio.mem_total + decode_region );
+   vg_audio.mem         = vg_alloc( vg_audio.mem_total + decode_region );
    vg_audio.decode_mem  = &((u8 *)vg_audio.mem)[vg_audio.mem_total];
 
    /* setup pool */
@@ -224,11 +255,12 @@ static int vg_audio_init(void)
    ma_device_config *dconf  = &vg_audio.miniaudio_dconfig;
    ma_device        *device = &vg_audio.miniaudio_device;
 
-    *dconf = ma_device_config_init( ma_device_type_playback );
+   *dconf = ma_device_config_init( ma_device_type_playback );
    dconf->playback.format    = ma_format_f32;
    dconf->playback.channels  = 2;
    dconf->sampleRate         = 44100;
    dconf->dataCallback       = audio_mixer_callback;
+   dconf->periodSizeInFrames = 441;
 
    dconf->pUserData = NULL;
 
@@ -236,28 +268,27 @@ static int vg_audio_init(void)
    
    if( ma_device_init( NULL, dconf, device) != MA_SUCCESS )
    {
-      vg_error( "ma_device failed to initialize" );
-      return 0;
+      vg_fatal_exit_loop( "(audio) ma_device failed to initialize" );
    } 
    else 
    {   
       if( ma_device_start( device ) != MA_SUCCESS )
       {
          ma_device_uninit( device );
-         vg_error( "ma_device failed to start" );
-         return 0;
+         vg_fatal_exit_loop( "(audio) ma_device failed to start" );
       }
    }
-
-   return 1;
+   
+   vg_success( "Ready\n" );
 }
 
-static void vg_audio_free(void)
+static void vg_audio_free(void * nothing)
 {
    ma_device        *device = &vg_audio.miniaudio_device;
    ma_device_uninit( device );
 
-   free( vg_audio.mem );
+   vg_free( vg_audio.mem );
+   vg_audio.mem = NULL;
 }
 
 /* 
@@ -309,7 +340,7 @@ static void audio_entity_start( audio_entity *src )
    audio_entity *ent = &vg_audio.active_players[ entid ].ent;
 
    ent->info = src->info;
-   ent->name = "todo";
+   ent->name = src->info.source->path;
    ent->cur  = 0;
    ent->player = src->player;
 
@@ -323,8 +354,7 @@ static void audio_entity_start( audio_entity *src )
       src->player->active_entity = entid;
    }
 
-   if( src->info.source->source_mode == k_audio_source_mono_compressed ||
-       src->info.source->source_mode == k_audio_source_stereo_compressed )
+   if( src->info.source->source_mode == k_audio_source_compressed )
    {
       /* Setup vorbis decoder */
       struct active_audio_player *aap = &vg_audio.active_players[ entid ];
@@ -387,7 +417,7 @@ static void audio_system_enque(void)
             audio_entity *ent = &vg_audio.active_players[ entid ].ent;
             if( !ent->fadeout )
             {
-               ent->fadeout = 1;
+               ent->fadeout = FADEOUT_LENGTH;
                ent->fadeout_current = FADEOUT_LENGTH;
             }
 
@@ -454,16 +484,32 @@ static void audio_system_cleanup(void)
  */
 static void audio_entity_spacialize( audio_entity *ent, float *vol, float *pan )
 {
+   if( ent->info.vol < 0.01f )
+   {
+      *vol = ent->info.vol;
+      *pan = 0.0f;
+      return;
+   }
+
    v3f delta;
    v3_sub( ent->info.world_position, vg_audio.listener_pos, delta );
 
-   float dist = v3_length( delta ),
-         attn = (dist / ent->info.vol) +1.0f;
+   float dist2 = v3_length2( delta );
 
-   v3_muls( delta, 1.0f/dist, delta );
+   if( dist2 < 0.0001f )
+   {
+      *pan = 0.0f;
+      *vol = 1.0f;
+   }
+   else
+   {
+      float dist = sqrtf( dist2 ),
+            attn = (dist / ent->info.vol) +1.0f;
 
-   *pan = v3_dot( vg_audio.listener_ears, delta );
-   *vol = 1.0f/(attn*attn);
+      v3_muls( delta, 1.0f/dist, delta );
+      *pan = v3_dot( vg_audio.listener_ears, delta );
+      *vol = 1.0f/(attn*attn);
+   }
 }
 
 static void audio_decode_uncompressed_mono( float *src, u32 count, float *dst )
@@ -475,8 +521,47 @@ static void audio_decode_uncompressed_mono( float *src, u32 count, float *dst )
    }
 }
 
+/* 
+ * adapted from stb_vorbis.h, since the original does not handle mono->stereo
+ */
+static int 
+stb_vorbis_get_samples_float_interleaved_stereo( stb_vorbis *f, float *buffer, 
+                                                 int len )
+{
+   int n = 0,
+       c = VG_MIN( 1, f->channels - 1 );
+
+   while( n < len ) 
+   {
+      int k = f->channel_buffer_end - f->channel_buffer_start;
+
+      if( n+k >= len ) 
+         k = len - n;
+
+      for( int j=0; j < k; ++j ) 
+      {
+         *buffer++ = f->channel_buffers[ 0 ][f->channel_buffer_start+j];
+         *buffer++ = f->channel_buffers[ c ][f->channel_buffer_start+j];
+      }
+
+      n += k;
+      f->channel_buffer_start += k;
+
+      if( n == len )
+         break;
+
+      if( !stb_vorbis_get_frame_float( f, NULL, NULL ))
+         break;
+   }
+
+   return n;
+}
+
 static void audio_entity_get_samples( aatree_ptr id, u32 count, float *buf )
 {
+   struct timespec time_start, time_end;
+   clock_gettime( CLOCK_REALTIME, &time_start );
+
    struct active_audio_player *aap = &vg_audio.active_players[id];
    audio_entity *ent = &aap->ent;
 
@@ -490,19 +575,20 @@ static void audio_entity_get_samples( aatree_ptr id, u32 count, float *buf )
       remaining -= samples_this_run;
 
       float *dst = &buf[ buffer_pos * 2 ]; 
+
+      int source_mode = ent->info.source->source_mode;
       
-      if( ent->info.source->source_mode == k_audio_source_mono )
+      if( source_mode == k_audio_source_mono )
       {
          float *src = &((float *)ent->info.source->data)[ cursor ];
          audio_decode_uncompressed_mono( src, samples_this_run, dst );
       }
-      else if( ent->info.source->source_mode == k_audio_source_mono_compressed )
+      else if( source_mode == k_audio_source_compressed )
       {
-         int read_samples = stb_vorbis_get_samples_float_interleaved( 
+         int read_samples = stb_vorbis_get_samples_float_interleaved_stereo
                aap->vorbis_handle,
-               2,
                dst,
-               samples_this_run * 2 );
+               samples_this_run  );
 
          if( read_samples != samples_this_run )
          {
@@ -515,8 +601,7 @@ static void audio_entity_get_samples( aatree_ptr id, u32 count, float *buf )
       
       if( (ent->info.flags & AUDIO_FLAG_LOOP) && remaining )
       {
-         if( ent->info.source->source_mode == k_audio_source_mono_compressed ||
-             ent->info.source->source_mode == k_audio_source_stereo_compressed )
+         if( source_mode == k_audio_source_compressed )
          {
             stb_vorbis_seek_start( aap->vorbis_handle );
          }
@@ -538,6 +623,9 @@ static void audio_entity_get_samples( aatree_ptr id, u32 count, float *buf )
    }
 
    ent->cur = cursor;
+
+   clock_gettime( CLOCK_REALTIME, &time_end );
+   vg_audio.perf_ms_decode += vg_time_diff( time_start, time_end );
 }
 
 static void audio_entity_mix( aatree_ptr id, float *buffer, 
@@ -556,6 +644,9 @@ static void audio_entity_mix( aatree_ptr id, float *buffer,
 
    audio_entity_get_samples( id, frame_count, pcf );
 
+   struct timespec time_start, time_end;
+   clock_gettime( CLOCK_REALTIME, &time_start );
+
    if( ent->info.flags & AUDIO_FLAG_SPACIAL_3D )
       audio_entity_spacialize( ent, &vol, &pan );
 
@@ -585,15 +676,9 @@ static void audio_entity_mix( aatree_ptr id, float *buffer,
       
       buffer_pos ++;
    }
-}
 
-static void vg_sleep_ms( long msec )
-{
-    struct timespec ts;
-
-    ts.tv_sec = msec / 1000;
-    ts.tv_nsec = (msec % 1000) * 1000000;
-    nanosleep( &ts, &ts );
+   clock_gettime( CLOCK_REALTIME, &time_end );
+   vg_audio.perf_ms_mix += vg_time_diff( time_start, time_end );
 }
 
 /*
@@ -618,7 +703,9 @@ static void audio_mixer_callback( ma_device *pDevice, void *pOutBuf,
       struct active_audio_player *aap = &vg_audio.active_players[i];
 
       if( aap->active )
+      {
          audio_entity_mix( i, pOut32F, frame_count );
+      }
    }
    
 #if 0
@@ -636,14 +723,28 @@ static void audio_mixer_callback( ma_device *pDevice, void *pOutBuf,
     */
    clock_gettime( CLOCK_REALTIME, &time_end );
 
-   double elapsed = 1000.0*time_end.tv_sec + 1e-6*time_end.tv_nsec
-                       - (1000.0*time_start.tv_sec + 1e-6*time_start.tv_nsec),
+   double elapsed = vg_time_diff( time_start, time_end ),
           budget  = ((double)frame_count / 44100.0) * 1000.0,
           percent = (elapsed/budget) * 100.0;
 
    snprintf( vg_audio.performance_info, 127, 
-                  "%.1fms/%.1fms (%.1f%%) (%u frames)",
+                  "%.2fms/%.2fms (%.1f%%) (%u frames)",
                   elapsed, budget, percent, frame_count );
+
+   vg_audio.perf_measurements ++;
+   if( vg_audio.perf_measurements >= 30 )
+   {
+      double ms_decode = vg_audio.perf_ms_decode * (1.0/30.0),
+             ms_mix    = vg_audio.perf_ms_mix    * (1.0/30.0);
+      
+      snprintf( vg_audio.performance_sub0, 63, "Decode %.2fms", ms_decode );
+      snprintf( vg_audio.performance_sub1, 63, "mix    %.2fms", ms_mix );
+
+      vg_audio.perf_ms_decode = 0.0;
+      vg_audio.perf_ms_mix = 0.0;
+
+      vg_audio.perf_measurements = 0;
+   }
 }
 
 /* Decompress entire vorbis stream into buffer */
@@ -670,7 +771,8 @@ static float *audio_decompress_vorbis( const unsigned char *data, int len,
    if( !buffer )
    {
       stb_vorbis_close( pv );
-      vg_exit();
+      vg_error( "Failed to allocated memory for audio\n" );
+      return NULL;
    }
    
    int read_samples = stb_vorbis_get_samples_float_interleaved( 
@@ -702,7 +804,7 @@ static int audio_clip_load( audio_clip *clip )
 
    if( clip->source_mode == k_audio_source_mono )
    {
-      u32 samples;
+      u32 samples = 0;
       float *sound = audio_decompress_vorbis( filedata, file_len, 1, &samples );
       clip->data = sound;
       clip->len = samples;
@@ -713,7 +815,7 @@ static int audio_clip_load( audio_clip *clip )
       vg_info( "Loaded audio clip[mono] '%s' (%.1fs, %.1fmb)\n", 
                                  clip->path, seconds, mb );
    }
-   else if( clip->source_mode == k_audio_source_mono_compressed )
+   else if( clip->source_mode == k_audio_source_compressed )
    {
       void *data = audio_alloc( file_len );
       memcpy( data, filedata, file_len );
@@ -722,19 +824,9 @@ static int audio_clip_load( audio_clip *clip )
       clip->len = file_len;
 
       float mb = (float)(file_len) / (1024.0f*1024.0f);
-      vg_info( "Loaded audio clip[mono_compressed] '%s' (%.1fmb)\n", 
+      vg_info( "Loaded audio clip[compressed] '%s' (%.1fmb)\n", 
                                           clip->path, mb );
    }
-   else if( clip->source_mode == k_audio_source_stereo_compressed )
-   {
-      /* ... */
-
-      clip->data = NULL;
-      clip->len = 0;
-
-      vg_error( "Source mode (%u) currently unsupported\n", clip->source_mode );
-      return 0;
-   }
    else
    {
       /* ... */
@@ -755,33 +847,6 @@ static void audio_clip_loadn( audio_clip *arr, int count )
       audio_clip_load( &arr[i] );
 }
 
-#if 0
-/*
- * Client code
- */
-static void audio_pack_play( audio_pack *source, audio_player *sys, int id )
-{
-   audio_require_lock();
-
-   sys->fadeout = 0;
-   sys->fadeout_current = 0;
-   sys->source = source->data;
-   sys->cur    = source->segments[ id*2 + 0 ];
-   sys->end    = source->segments[ id*2 + 1 ];
-   sys->ch     = source->ch;
-   sys->source_mode = source->source_mode;
-   
-   /* for diagnostics */
-   sys->clip_start = sys->cur;
-   sys->clip_end = sys->end;
-   sys->buffer_length = source->segments[ (source->numsegments-1)*2 + 1 ];
-   sys->is_playing = 1;
-   
-   audio_player_push( sys );
-}
-
-#endif
-
 /* Mark change to be uploaded through queue system */
 static void audio_player_commit( audio_player *sys )
 {
@@ -803,7 +868,22 @@ static void audio_player_commit( audio_player *sys )
    audio_entity *ent = &vg_audio.entity_queue[ vg_audio.queue_len ++ ];
    ent->info = sys->info;
    ent->player = sys;
-   sys->active_entity = AATREE_PTR_NIL;
+}
+
+static void audio_require_init( audio_player *player )
+{
+   if( player->init )
+      return;
+
+   vg_fatal_exit_loop( "Must init audio player before playing! \n" );
+}
+
+static void audio_require_clip_loaded( audio_clip *clip )
+{
+   if( clip->data )
+      return;
+
+   vg_fatal_exit_loop( "Must load audio clip before playing! \n" );
 }
 
 /* Play a clip using player. If its already playing something, it will 
@@ -811,14 +891,45 @@ static void audio_player_commit( audio_player *sys )
 static void audio_player_playclip( audio_player *player, audio_clip *clip )
 {
    audio_require_lock();
+   audio_require_init( player );
+   audio_require_clip_loaded( clip );
 
    player->info.source = clip;
    audio_player_commit( player );
 }
 
+#if 0
 static void audio_player_playoneshot( audio_player *player, audio_clip *clip )
 {
-   
+   audio_require_lock();
+   audio_require_init( player );
+}
+#endif
+
+static void audio_play_oneshot( audio_clip *clip, float volume )
+{
+   audio_require_lock();
+   audio_require_clip_loaded( clip );
+
+   if( vg_audio.queue_len >= vg_list_size( vg_audio.entity_queue ) )
+   {
+      vg_warn( "Audio commit queue full\n" );
+      return;
+   }
+
+   audio_entity *ent = &vg_audio.entity_queue[ vg_audio.queue_len ++ ];
+
+   ent->info.flags = AUDIO_FLAG_ONESHOT;
+   ent->info.pan = 0.0f;
+   ent->info.source = clip;
+   ent->info.vol = volume;
+   ent->player = NULL;
+}
+
+static void audio_player_init( audio_player *player )
+{
+   player->active_entity = AATREE_PTR_NIL;
+   player->init = 1;
 }
 
 /*
@@ -829,6 +940,16 @@ static void audio_player_playoneshot( audio_player *player, audio_clip *clip )
  * Safety enforced Get/set attributes
  */
 
+static int audio_player_is_playing( audio_player *sys )
+{
+   audio_require_lock();
+
+   if( sys->active_entity != AATREE_PTR_NIL )
+      return 1;
+   else 
+      return 0;
+}
+
 static void audio_player_set_position( audio_player *sys, v3f pos )
 {
    audio_require_lock();
@@ -876,7 +997,7 @@ static u32 audio_player_get_flags( audio_player *sys )
  * Debugging
  */
 
-static void audio_debug_ui(void)
+static void audio_debug_ui( m4x4f mtx_pv )
 {
    if( !vg_audio.debug_ui )
       return;
@@ -886,12 +1007,15 @@ static void audio_debug_ui(void)
        {
                const char *name;
                u32 cursor, flags, length;
+      v3f pos;
       float vol;
        }
        infos[ SFX_MAX_SYSTEMS ];
        int num_systems = 0;
 
-   char perf[128];
+   char perf[128],
+        psub0[64],
+        psub1[64];
        
    audio_lock();
        
@@ -910,8 +1034,12 @@ static void audio_debug_ui(void)
                snd->flags = ent->info.flags;
                snd->length = ent->length;
       snd->vol = ent->info.vol*100.0f;
+      v3_copy( ent->info.world_position, snd->pos );
        }
    strcpy( perf, vg_audio.performance_info );
+   strcpy( psub0, vg_audio.performance_sub0 );
+   strcpy( psub1, vg_audio.performance_sub1 );
+
    audio_unlock();
 
    /* Draw UI */
@@ -919,25 +1047,34 @@ static void audio_debug_ui(void)
    ui_global_ctx.cursor[1] = 10;
    ui_global_ctx.cursor[2] = 150;
    ui_global_ctx.cursor[3] = 12;
+
    ui_text( &ui_global_ctx, ui_global_ctx.cursor, perf, 1, 0 );
+   ui_global_ctx.cursor[1] += 20;
+
+   ui_text( &ui_global_ctx, ui_global_ctx.cursor, psub0, 1, 0 );
+   ui_global_ctx.cursor[1] += 20;
+
+   ui_text( &ui_global_ctx, ui_global_ctx.cursor, psub1, 1, 0 );
+   ui_global_ctx.cursor[1] += 20;
    
    float usage = (float)vg_audio.mem_current / (1024.0f*1024.0f),
          total = (float)vg_audio.mem_total   / (1024.0f*1024.0f),
          percent = (usage/total) * 100.0f;
    snprintf( perf, 127, "Mem: %.1f/%.1fmb (%.1f%%)\n", usage, total, percent );
 
-   ui_global_ctx.cursor[1] += 20;
    ui_text( &ui_global_ctx, ui_global_ctx.cursor, perf, 1, 0 );
-
    ui_global_ctx.cursor[1] += 20;
 
+   ui_rect overlap_buffer[ SFX_MAX_SYSTEMS ];
+   u32 overlap_length = 0;
+
        /* Draw audio stack */
        for( int i=0; i<num_systems; i ++ )
        {
       struct sound_info *inf = &infos[i];
 
-               ui_global_ctx.cursor[2] = 150;
-               ui_global_ctx.cursor[3] = 12;
+               ui_global_ctx.cursor[2] = 200;
+               ui_global_ctx.cursor[3] = 18;
                
                u32 alpha = 0xa0000000;
 
@@ -946,7 +1083,7 @@ static void audio_debug_ui(void)
                        ui_fill_rect( &ui_global_ctx, ui_global_ctx.cursor, 0x00333333|alpha );
 
                        ui_px baseline = ui_global_ctx.cursor[0],
-               w  = 150,
+               w  = 200,
                c  = baseline + ((float)inf->cursor / (float)inf->length) * w;
                        
                        /* cursor */
@@ -958,8 +1095,54 @@ static void audio_debug_ui(void)
                        ui_global_ctx.cursor[1] += 2;
          snprintf( perf, 127, "%s %.1f%%", infos[i].name, infos[i].vol );
                        ui_text( &ui_global_ctx, ui_global_ctx.cursor, perf, 1, 0 );
+         
+         if( inf->flags & AUDIO_FLAG_SPACIAL_3D )
+         {
+            v4f wpos;
+            v3_copy( inf->pos, wpos );
+            wpos[3] = 1.0f;
+            m4x4_mulv( mtx_pv, wpos, wpos );
+
+            if( wpos[3] < 0.0f )
+               goto projected_behind;
+
+            v2_muls( wpos, (1.0f/wpos[3]) * 0.5f, wpos );
+            v2_add( wpos, (v2f){ 0.5f, 0.5f }, wpos );
+            
+            ui_rect wr;
+            wr[0] = wpos[0] * vg_window_x;
+            wr[1] = (1.0f-wpos[1]) * vg_window_y;
+            wr[2] = 100;
+            wr[3] = 17;
+            
+            for( int j=0; j<12; j++ )
+            {
+               int collide = 0;
+               for( int k=0; k<overlap_length; k++ )
+               {
+                  ui_px *wk = overlap_buffer[k];
+                  if( ((wr[0] <= wk[0]+wk[2]) && (wr[0]+wr[2] >= wk[0])) &&
+                      ((wr[1] <= wk[1]+wk[3]) && (wr[1]+wr[3] >= wk[1])) )
+                  {
+                     collide = 1;
+                     break;
+                  }
+               }
+
+               if( !collide )
+                  break;
+               else
+                  wr[1] += 18;
+            }
+
+            ui_text( &ui_global_ctx, wr, perf, 1, 0 );
+
+            ui_rect_copy( wr, overlap_buffer[ overlap_length ++ ] );
+         }
                }
 
+projected_behind:
+
                ui_end_down( &ui_global_ctx );
                ui_global_ctx.cursor[1] += 1;
        }