text: /*
   * Copyright (C) 2005-2008 Claudio Ciccani <klan@users.sf.net>
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
   *
   */
  
  #include <config.h>
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <fcntl.h>
  #include <unistd.h>
  #include <errno.h>
  
  #include <math.h>
  
  #include <fusionsound.h>
  #include <fusionsound_limits.h>
  
  #include <direct/types.h>
  #include <direct/clock.h>
  #include <direct/mem.h>
  #include <direct/memcpy.h>
  #include <direct/stream.h>
  #include <direct/thread.h>
  #include <direct/util.h>
  
  #include <media/ifusionsoundmusicprovider.h>
  
  #include <misc/sound_util.h>
  
  #ifdef USE_TREMOR
  # include <tremor/ivorbiscodec.h>
  # include <tremor/ivorbisfile.h>
  #else
  # include <vorbis/codec.h>
  # include <vorbis/vorbisfile.h>
  #endif
  
  static DirectResult
  Probe( IFusionSoundMusicProvider_ProbeContext *ctx );
  
  static DirectResult
  Construct( IFusionSoundMusicProvider *thiz,
             const char                *filename,
             DirectStream              *stream );
  
  #include <direct/interface_implementation.h>
  
  DIRECT_INTERFACE_IMPLEMENTATION( IFusionSoundMusicProvider, Vorbis )
  
  /*
   * private data struct of IFusionSoundMusicProvider
   */
  typedef struct {
       int                           ref;       /* reference counter */
  
       DirectStream                 *stream;
  
       OggVorbis_File                vf;
       vorbis_info                  *info;
  
       FSMusicProviderPlaybackFlags  flags;
  
       DirectThread                 *thread;
       pthread_mutex_t               lock;
       pthread_cond_t                cond;
  
       FSMusicProviderStatus         status;
       int                           finished;     
       int                           seeked;
  
       struct {
            IFusionSoundStream      *stream;
            IFusionSoundBuffer      *buffer;
            FSSampleFormat           format;
            FSChannelMode            mode;
            int                      length;
       } dest;
  
       FMBufferCallback              callback;
       void                         *ctx;
  } IFusionSoundMusicProvider_Vorbis_data;
  
  
  /* Mixing Routines */
  
  typedef struct {
  #ifdef WORDS_BIGENDIAN
       s8 c;
       u8 b;
       u8 a;
  #else
       u8 a;
       u8 b;
       s8 c;
  #endif
  } __attribute__((packed)) s24;
  
  
  #ifdef USE_TREMOR
  
  #define VORBIS_MIX_LOOP() \
   do { \
       int n; \
       if (d_n == s_n) { \
            if (sizeof(TYPE) == sizeof(s16)) { \
                 direct_memcpy( dst, src, len*s_n*sizeof(s16) ); \
            } \
            else { \
                 TYPE *d = (TYPE *)dst; \
                 for (n = len*s_n; n; n--) { \
                      *d++ = CONV(*src); \
                      src++; \
                 } \
            } \
            break; \
       } \
       else if (d_n < s_n) { \
            if (d_n == 1 && s_n == 2) { \
                 /* Downmix to stereo to mono */ \
                 TYPE *d = (TYPE *)dst; \
                 for (n = len; n; n--) { \
                      *d++ = CONV((src[0]+src[1])>>1); \
                      src += 2; \
                 } \
                 break; \
            } \
       } \
       else if (d_n > s_n) { \
            if (d_n == 2 && s_n == 1) { \
                 /* Upmix stereo to mono */ \
                 TYPE *d = (TYPE *)dst; \
                 for (n = len; n; n--) { \
                      d[0] = d[1] = CONV(*src); \
                      d += 2; \
                      src++; \
                 } \
                 break; \
            } \
            memset( dst, 0, len * d_n * sizeof(TYPE) ); \
       } \
       for (i = 0; i < MIN(s_n,d_n); i++) { \
            s16  *s = &src[i]; \
            TYPE *d = &((TYPE *)dst)[i]; \
            int   n; \
            for (n = len; n; n--) { \
                 *d  = CONV(*s); \
                  d += d_n; \
                  s += s_n; \
            } \
       } \
   } while (0)
  
  static void
  vorbis_mix_audio( s16 *src, void *dst, int pos, int len,
                    FSSampleFormat format, int src_channels, FSChannelMode dst_mode )
  {
       int s_n = src_channels;
       int d_n = FS_CHANNELS_FOR_MODE(dst_mode);
       int i;
       
       if (pos)
            src += pos * s_n;
  
       switch (format) {
            case FSSF_U8:
                 #define TYPE u8
                 #define CONV(s) (((s)>>8)+128)
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            case FSSF_S16:
                 #define TYPE s16
                 #define CONV(s) (s)
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            case FSSF_S24:
                 #define TYPE s24
                 #define CONV(s) ({ s16 t=(s); (s24){a:0, b:t, c:t>>8}; })
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            case FSSF_S32:
                 #define TYPE s32
                 #define CONV(s) ((s)<<8)
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            case FSSF_FLOAT:
                 #define TYPE float
                 #define CONV(s) ((float)(s)/32768.f)
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            default:
                 D_BUG( "unexpected sample format" );
                 break;
       }
  }
  
  #else /* !USE_TREMOR */
  
  static __inline__ u8
  FtoU8( float s )
  {
       int d;
       d  = s * 128.f + 128.5f;
       return CLAMP( d, 0, 255 );
  }
  
  static __inline__ s16
  FtoS16( float s )
  {
       int d;
       d = s * 32768.f + 0.5f;
       return CLAMP( d, -32768, 32767 );
  }
  
  static __inline__ s24
  FtoS24( float s )
  {
       int d;
       d = s * 8388608.f + 0.5f;
       d = CLAMP( d, -8388608, 8388607 );
       return (s24) { a:d, b:d>>8, c:d>>16 };
  }
  
  static __inline__ s32
  FtoS32( float s )
  {
       s = CLAMP( s, -1.f, 1.f );
       return s * 2147483647.f;
  }
  
  static __inline__ float
  FtoF32( float s )
  {
       return CLAMP( s, -1.f, 1.f );
  }
  
  #define VORBIS_MIX_LOOP() \
   do { \
       int i; \
       if (fs_mode_for_channels(s_n) == dst_mode) { \
            for (i = 0; i < s_n; i++) { \
                 float *s = src[i] + pos; \
                 TYPE  *d = &((TYPE *)dst)[i]; \
                 int    n; \
                 for (n = len; n; n--) { \
                      *d = CONV(*s); \
                      d += d_n; \
                      s++; \
                 } \
            } \
       } \
       else { \
            float c[6] = { /*L*/0, /*C*/0, /*R*/0, /*Rl*/0, /*Rr*/0, /*LFE*/0 }; \
            TYPE *d    = (TYPE *)dst; \
            for (i = pos; i < pos+len; i++) { \
                 float s; \
                 switch (s_n) { \
                      case 1: \
                           c[0] = c[2] = src[0][i]; \
                           break; \
                      case 4: \
                           c[3] = src[2][i]; \
                           c[4] = src[3][i]; \
                      case 2: \
                           c[0] = src[0][i]; \
                           c[2] = src[1][i]; \
                           break; \
                      case 6: \
                           c[5] = src[5][i]; \
                      case 5: \
                           c[3] = src[3][i]; \
                           c[4] = src[4][i]; \
                      case 3: \
                           c[0] = src[0][i]; \
                           c[1] = src[1][i]; \
                           c[2] = src[2][i]; \
                           break; \
                      default: \
                           break; \
                 } \
                 switch (dst_mode) { \
                      case FSCM_MONO: \
                           s = c[0] + c[2]; \
                           if (s_n > 2) s += (c[1]*2+c[3]+c[4])*0.7079f; \
                           s *= 0.5f; \
                           *d++ = CONV(s); \
                           break; \
                      case FSCM_STEREO: \
                      case FSCM_STEREO21: \
                           s = c[0]; \
                           if (s_n > 2) s += (c[1]+c[3])*0.7079f; \
                           *d++ = CONV(s); \
                           s = c[2]; \
                           if (s_n > 2) s += (c[1]+c[4])*0.7079f; \
                           *d++ = CONV(s); \
                           if (FS_MODE_HAS_LFE(dst_mode)) \
                                *d++ = CONV(c[5]); \
                           break; \
                      case FSCM_STEREO30: \
                      case FSCM_STEREO31: \
                           s = c[0] + c[3]*0.7079f; \
                           *d++ = CONV(s); \
                           s = (s_n == 2 || s_n == 4) ? ((c[0]+c[2])*0.5f) : c[1]; \
                           *d++ = CONV(s); \
                           s = c[2] + c[4]*0.7079f; \
                           *d++ = CONV(s); \
                           if (FS_MODE_HAS_LFE(dst_mode)) \
                                *d++ = CONV(c[5]); \
                           break; \
                      default: \
                           if (FS_MODE_HAS_CENTER(dst_mode)) { \
                                *d++ = CONV(c[0]); \
                                if (s_n == 2 || s_n == 4) { \
                                     s = (c[0] + c[2]) * 0.5f; \
                                     *d++ = CONV(s); \
                                } else { \
                                     *d++ = CONV(c[1]); \
                                } \
                                *d++ = CONV(c[2]); \
                           } else { \
                                s = c[0] + c[1]*0.7079f; \
                                *d++ = CONV(s); \
                                s = c[2] + c[1]*0.7079f; \
                                *d++ = CONV(s); \
                           } \
                           if (FS_MODE_NUM_REARS(dst_mode) == 1) { \
                                s = (c[3] + c[4]) * 0.5f; \
                                *d++ = CONV(s); \
                           } else { \
                                *d++ = CONV(c[3]); \
                                *d++ = CONV(c[4]); \
                           } \
                           if (FS_MODE_HAS_LFE(dst_mode)) \
                                *d++ = CONV(c[5]); \
                           break; \
                 } \
            } \
       } \
   } while (0)
    
  
  static void
  vorbis_mix_audio( float **src, void *dst, int pos, int len,
                    FSSampleFormat format, int src_channels, FSChannelMode dst_mode )
  {
       int s_n = src_channels;
       int d_n = FS_CHANNELS_FOR_MODE(dst_mode);
  
       switch (format) {
            case FSSF_U8:
                 #define TYPE u8
                 #define CONV FtoU8
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            case FSSF_S16:
                 #define TYPE s16
                 #define CONV FtoS16
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            case FSSF_S24:
                 #define TYPE s24
                 #define CONV FtoS24
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            case FSSF_S32:
                 #define TYPE s32
                 #define CONV FtoS32
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            case FSSF_FLOAT:
                 #define TYPE float
                 #define CONV FtoF32
                 VORBIS_MIX_LOOP();
                 #undef TYPE
                 #undef CONV
                 break;
  
            default:
                 D_BUG( "unexpected sample format" );
                 break;
       }
  }
  
  #endif /* USE_TREMOR */
  
  /*****************************************************************************/
  
  static size_t
  ov_read_callback( void *dst, size_t size, size_t nmemb, void *ctx )
  {
       DirectStream *stream = ctx;
       size_t        total  = size * nmemb;
       size_t        length = 0;
       DirectResult  ret;
  
       while (length < total) {
            unsigned int read = 0;
            direct_stream_wait( stream, total-length, NULL );
            ret = direct_stream_read( stream, total-length, dst+length, &read );
            if (ret) {
                 memset( dst+length, 0, total-length );
                 if (!length)
                      return (ret == DR_EOF) ? 0 : -1;
                 break;
            }
            length += read;
       }
  
       return length / size;
  }
  
  static int
  ov_seek_callback( void *ctx, ogg_int64_t offset, int whence )
  {
       DirectStream *stream = ctx;
       unsigned int  pos    = 0;
       DirectResult  ret    = DR_UNSUPPORTED;
  
       if (!direct_stream_seekable( stream ) || direct_stream_remote( stream ))
            return -1;
  
       switch (whence) {
            case SEEK_SET:
                 break;
            case SEEK_CUR:
                 pos = direct_stream_offset( stream );
                 offset += pos;
                 break;
            case SEEK_END:
                 pos = direct_stream_length( stream );
                 if (offset < 0)
                      return pos;
                 offset = pos-offset;
                 break;
            default:
                 offset = -1;
                 break;
       }
  
       if (offset >= 0)
            ret = direct_stream_seek( stream, offset );
       if (ret) {
            errno = -1;
            return -1;
       }
  
       errno = 0;
       return direct_stream_offset( stream );
  }
  
  static long
  ov_tell_callback( void *ctx )
  {
       DirectStream *stream = ctx;
  
       return direct_stream_offset( stream );
  }
  
  static int
  ov_close_callback( void *ctx )
  {
       return 0;
  }
  
  /*****************************************************************************/
  
  static void
  Vorbis_Stop( IFusionSoundMusicProvider_Vorbis_data *data, bool now )
  {
       data->status = FMSTATE_STOP;
  
       /* stop thread */
       if (data->thread) {
            if (!direct_thread_is_joined( data->thread )) {
                 if (now) {
                      direct_thread_cancel( data->thread );
                      direct_thread_join( data->thread );
                 }
                 else {
                      /* mutex must be already locked */
                      pthread_mutex_unlock( &data->lock );
                      direct_thread_join( data->thread );
                      pthread_mutex_lock( &data->lock );
                 }
            }
            direct_thread_destroy( data->thread );
            data->thread = NULL;
       }
  
       /* release previous destination stream */
       if (data->dest.stream) {
            data->dest.stream->Release( data->dest.stream );
            data->dest.stream = NULL;
       }
  
       /* release previous destination buffer */
       if (data->dest.buffer) {
            data->dest.buffer->Release( data->dest.buffer );
            data->dest.buffer = NULL;
       }
  }
  
  static void
  IFusionSoundMusicProvider_Vorbis_Destruct( IFusionSoundMusicProvider *thiz )
  {
       IFusionSoundMusicProvider_Vorbis_data *data = thiz->priv;
       
       Vorbis_Stop( data, true );
  
       ov_clear( &data->vf );
  
       if (data->stream)
            direct_stream_destroy( data->stream );
  
       pthread_cond_destroy( &data->cond );
       pthread_mutex_destroy( &data->lock );
  
       DIRECT_DEALLOCATE_INTERFACE( thiz );
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_AddRef( IFusionSoundMusicProvider *thiz )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       data->ref++;
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_Release( IFusionSoundMusicProvider *thiz )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (--data->ref == 0)
            IFusionSoundMusicProvider_Vorbis_Destruct( thiz );
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_GetCapabilities( IFusionSoundMusicProvider   *thiz,
                                                    FSMusicProviderCapabilities *caps )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (!caps)
            return DR_INVARG;
  
  #ifdef USE_TREMOR
       *caps = FMCAPS_BASIC;
  #else
       *caps = FMCAPS_BASIC | FMCAPS_HALFRATE;
  #endif
       if (direct_stream_seekable( data->stream ))
            *caps |= FMCAPS_SEEK;
  
       return DR_OK;
  }
  
  static inline float
  vorbis_compute_gain( const char *rg_gain, const char *rg_peak )
  {
       float gain, peak = 1.0f;
  
       if (rg_peak)
            peak = atof( rg_peak ) ? : 1.0f;
  
       gain = pow( 10.0f, atof( rg_gain ) / 20.0f );
       if (gain*peak > 1.0f)
            gain = 1.0f / peak;
  
       return gain;
  }
  
  static void
  vorbis_get_metadata( OggVorbis_File     *vf,
                       FSTrackDescription *desc )
  {
       vorbis_comment   *vc        = ov_comment( vf, -1 );
       char           **ptr        = vc->user_comments;
       char            *track_gain = NULL;
       char            *track_peak = NULL;
       char            *album_gain = NULL;
       char            *album_peak = NULL;
  
       memset( desc, 0, sizeof(FSTrackDescription) );
  
       while (*ptr) {
            char *comment = *ptr;
  
            if (!strncasecmp( comment, "ARTIST=", sizeof("ARTIST=")-1 )) {
                 strncpy( desc->artist,
                          comment + sizeof("ARTIST=") - 1,
                          FS_TRACK_DESC_ARTIST_LENGTH - 1 );
            }
            else if (!strncasecmp( comment, "TITLE=", sizeof("TITLE=")-1 )) {
                 strncpy( desc->title,
                          comment + sizeof("TITLE=") - 1,
                          FS_TRACK_DESC_TITLE_LENGTH - 1 );
            }
            else if (!strncasecmp( comment, "ALBUM=", sizeof("ALBUM=")-1 )) {
                 strncpy( desc->album,
                          comment + sizeof("ALBUM=") - 1,
                          FS_TRACK_DESC_ALBUM_LENGTH - 1 );
            }
            else if (!strncasecmp( comment, "GENRE=", sizeof("GENRE=")-1 )) {
                 strncpy( desc->genre,
                          comment + sizeof("GENRE=") - 1,
                          FS_TRACK_DESC_GENRE_LENGTH - 1 );
            }
            else if (!strncasecmp( comment, "DATE=", sizeof("DATE=")-1 )) {
                 desc->year = atoi( comment + sizeof("DATE=") );
            }
            else if (!strncasecmp( comment, "REPLAYGAIN_TRACK_GAIN=",
                                     sizeof("REPLAYGAIN_TRACK_GAIN=")-1 )) {
                 track_gain = comment + sizeof("REPLAYGAIN_TRACK_GAIN=") - 1;
            }
            else if (!strncasecmp( comment, "REPLAYGAIN_ALBUM_GAIN=",
                                     sizeof("REPLAYGAIN_ALBUM_GAIN=")-1 )) {
                 album_gain = comment + sizeof("REPLAYGAIN_ALBUM_GAIN=") - 1;
            }
            else if (!strncasecmp( comment, "REPLAYGAIN_TRACK_PEAK=",
                                     sizeof("REPLAYGAIN_TRACK_PEAK=")-1 )) {
                 track_peak = comment + sizeof("REPLAYGAIN_TRACK_PEAK=") - 1;
            }
            else if (!strncasecmp( comment, "REPLAYGAIN_ALBUM_PEAK=",
                                     sizeof("REPLAYGAIN_ALBUM_PEAK=")-1 )) {
                 album_peak = comment + sizeof("REPLAYGAIN_ALBUM_PEAK=") - 1;
            }
  
            ptr++;
       }
  
       snprintf( desc->encoding,
                 FS_TRACK_DESC_ENCODING_LENGTH, "Vorbis" );
  
       desc->bitrate = ov_bitrate( vf, -1 ) ? : ov_bitrate_instant( vf );
       if (track_gain)
            desc->replaygain = vorbis_compute_gain( track_gain, track_peak );
       if (album_gain)
            desc->replaygain_album = vorbis_compute_gain( album_gain, album_peak );
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_GetTrackDescription( IFusionSoundMusicProvider *thiz,
                                                        FSTrackDescription        *desc )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (!desc)
            return DR_INVARG;
  
       vorbis_get_metadata( &data->vf, desc );
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_GetStreamDescription( IFusionSoundMusicProvider *thiz,
                                                         FSStreamDescription       *desc )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (!desc)
            return DR_INVARG;
  
       desc->flags        = FSSDF_SAMPLERATE   | FSSDF_CHANNELS  |
                            FSSDF_SAMPLEFORMAT | FSSDF_BUFFERSIZE;
       desc->samplerate   = data->info->rate;
       desc->channels     = MIN(data->info->channels, FS_MAX_CHANNELS);
  #ifdef USE_TREMOR
       desc->sampleformat = FSSF_S16;
  #else
       desc->sampleformat = FSSF_FLOAT;
  #endif
       desc->buffersize   = desc->samplerate/8;
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_GetBufferDescription( IFusionSoundMusicProvider *thiz,
                                                         FSBufferDescription       *desc )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (!desc)
            return DR_INVARG;
  
       desc->flags        = FSBDF_SAMPLERATE   | FSBDF_CHANNELS |
                            FSBDF_SAMPLEFORMAT | FSBDF_LENGTH;
       desc->samplerate   = data->info->rate;
       desc->channels     = MIN(data->info->channels, FS_MAX_CHANNELS);
  #ifdef USE_TREMOR
       desc->sampleformat = FSSF_S16;
  #else
       desc->sampleformat = FSSF_FLOAT;
  #endif
       desc->length       = MIN(ov_pcm_total( &data->vf, -1 ), FS_MAX_FRAMES);
  
       return DR_OK;
  }
  
  static void*
  VorbisStreamThread( DirectThread *thread, void *ctx )
  {
       IFusionSoundMusicProvider_Vorbis_data *data =
            (IFusionSoundMusicProvider_Vorbis_data*) ctx;
  
  #ifdef USE_TREMOR
       s16     src[2048]; // interleaved
  #else
       float **src;      // non-interleaved
  #endif
       int     section = 0;
  
       while (data->status == FMSTATE_PLAY) {
            int length;
            int pos = 0;
  
            pthread_mutex_lock( &data->lock );
  
            if (data->status != FMSTATE_PLAY) {
                 pthread_mutex_unlock( &data->lock );
                 break;
            }
            
            if (data->seeked) {
                 data->dest.stream->Flush( data->dest.stream );
                 data->seeked = false;
            }
  
  #ifdef USE_TREMOR
            length = ov_read( &data->vf, (char*)&src[0], sizeof(src), &section );
            length = (length > 0) ? (length/(data->info->channels*2)) : length;
  #else
            length = ov_read_float( &data->vf, &src, data->dest.length, &section );
  #endif
            if (length == 0) {
                 if (data->flags & FMPLAY_LOOPING) {
                      if (direct_stream_remote( data->stream ))
                           direct_stream_seek( data->stream, 0 );
                      else
                           ov_time_seek( &data->vf, 0 );
                 }
                 else {
                      data->finished = true;
                      data->status = FMSTATE_FINISHED;
                      pthread_cond_broadcast( &data->cond );
                 }
            }
  
            pthread_mutex_unlock( &data->lock );
  
            while (pos < length) {
                 void *dst;
                 int   len;
                      
                 if (data->dest.stream->Access( data->dest.stream, &dst, &len ))
                      break;
                           
                 if (len > length-pos)
                      len = length-pos;
                           
                 vorbis_mix_audio( src, dst, pos, len, data->dest.format,
                                   data->info->channels, data->dest.mode );
  
                 data->dest.stream->Commit( data->dest.stream, len );
                      
                 pos += len;
            }
       }
  
       return NULL;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_PlayToStream( IFusionSoundMusicProvider *thiz,
                                                 IFusionSoundStream        *destination )
  {
       FSStreamDescription desc;
  
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (!destination)
            return DR_INVARG;
            
       if (data->dest.stream == destination)
            return DR_OK;
  
       destination->GetDescription( destination, &desc );
  
       /* check if destination samplerate is supported */
  #ifdef USE_TREMOR
       if (desc.samplerate != data->info->rate)
            return DR_UNSUPPORTED;
  #else
       if (desc.samplerate != data->info->rate &&
           desc.samplerate != data->info->rate/2)
            return DR_UNSUPPORTED;
  #endif
  
       /* check if destination format is supported */
       switch (desc.sampleformat) {
            case FSSF_U8:
            case FSSF_S16:
            case FSSF_S24:
            case FSSF_S32:
            case FSSF_FLOAT:
                 break;
            default:
                 return DR_UNSUPPORTED;
       }
       
       /* check if destination mode is supported */
       switch (desc.channelmode) {
            case FSCM_MONO:
            case FSCM_STEREO:
            case FSCM_STEREO21:
            case FSCM_STEREO30:
            case FSCM_STEREO31:
            case FSCM_SURROUND30:
            case FSCM_SURROUND31:
            case FSCM_SURROUND40_2F2R:
            case FSCM_SURROUND41_2F2R:
            case FSCM_SURROUND40_3F1R:
            case FSCM_SURROUND41_3F1R:
            case FSCM_SURROUND50:
            case FSCM_SURROUND51:
                 break;
            default:
                 return DR_UNSUPPORTED;
       }
  
       pthread_mutex_lock( &data->lock );
       
       Vorbis_Stop( data, false );
       
  #ifndef USE_TREMOR
       if (desc.samplerate == data->info->rate/2) {
            if (ov_halfrate( &data->vf, 1 )) {
                 pthread_mutex_unlock( &data->lock );
                 return DR_UNSUPPORTED;
            }
       } else {
            ov_halfrate( &data->vf, 0 );
       }
  #endif
  
       /* reference destination stream */
       destination->AddRef( destination );
       data->dest.stream = destination;
       data->dest.format = desc.sampleformat;
       data->dest.mode   = desc.channelmode;
       data->dest.length = desc.buffersize;
  
       if (data->finished) {
            if (direct_stream_remote( data->stream ))
                 direct_stream_seek( data->stream, 0 );
            else
                 ov_time_seek( &data->vf, 0 );
            
            data->finished = false;
       }
       
       data->status = FMSTATE_PLAY;
       pthread_cond_broadcast( &data->cond );
  
       /* start thread */
       data->thread = direct_thread_create( DTT_DEFAULT, VorbisStreamThread, data, "Vorbis" );
  
       pthread_mutex_unlock( &data->lock );
  
       return DR_OK;
  }
  
  static void*
  VorbisBufferThread( DirectThread *thread, void *ctx )
  {
       IFusionSoundMusicProvider_Vorbis_data *data =
            (IFusionSoundMusicProvider_Vorbis_data*) ctx;
  
       IFusionSoundBuffer *buffer    = data->dest.buffer;
       int                 section   = 0;
       int                 blocksize = FS_CHANNELS_FOR_MODE(data->dest.mode) *
                                       FS_BYTES_PER_SAMPLE(data->dest.format);
  
       while (data->status == FMSTATE_PLAY) {
  #ifdef USE_TREMOR
            s16     src[2048];
  #else
            float **src;
  #endif
            char   *dst;
            long    len;
            int     pos = 0;
            int     size;
  
            pthread_mutex_lock( &data->lock );
  
            if (data->status != FMSTATE_PLAY) {
                 pthread_mutex_unlock( &data->lock );
                 break;
            }
  
            if (buffer->Lock( buffer, (void*)&dst, &size, 0 ) != DR_OK) {
                 D_ERROR( "IFusionSoundMusicProvider_Vorbis: "
                          "Couldn't lock buffer!\n" );
                 pthread_mutex_unlock( &data->lock );
                 break;
            }
  
            do {
  #ifdef USE_TREMOR
                 len = ov_read( &data->vf, (char*)&src[0], sizeof(src), &section );
                 len = (len > 0) ? (len/(data->info->channels*2)) : len;
  #else
                 len = ov_read_float( &data->vf, &src, size-pos, &section );
  #endif
                 if (len == 0) {
                      if (data->flags & FMPLAY_LOOPING) {
                           if (direct_stream_remote( data->stream ))
                                direct_stream_seek( data->stream, 0 );
                           else
                                ov_time_seek( &data->vf, 0 );
                      }
                      else {
                           data->finished = true;
                           data->status = FMSTATE_FINISHED;
                           pthread_cond_broadcast( &data->cond );
                      }
                      continue;
                 }
  
                 if (len > 0) {
                      int n;
                      do {
                           n = MIN( len, size-pos );
                           vorbis_mix_audio( src, &dst[pos*blocksize], 0, n,
                                             data->dest.format,
                                             data->info->channels,
                                             data->dest.mode );
                           pos += n;
                           len -= n;
                      } while (n > 0);
                 }
            } while (pos < size && data->status != FMSTATE_FINISHED);
  
            buffer->Unlock( buffer );
  
            pthread_mutex_unlock( &data->lock );
  
            if (data->callback) {
                 if (data->callback( pos, data->ctx )) {
                      data->status = FMSTATE_STOP;
                      pthread_cond_broadcast( &data->cond );
                 }
            }
       }
  
       return NULL;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_PlayToBuffer( IFusionSoundMusicProvider *thiz,
                                                 IFusionSoundBuffer        *destination,
                                                 FMBufferCallback           callback,
                                                 void                      *ctx )
  {
       FSBufferDescription desc;
  
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (!destination)
            return DR_INVARG;
            
       if (data->dest.buffer == destination)
            return DR_OK;
  
       destination->GetDescription( destination, &desc );
  
       /* check if destination samplerate is supported */
  #ifdef USE_TREMOR
       if (desc.samplerate != data->info->rate)
            return DR_UNSUPPORTED;
  #else
       if (desc.samplerate != data->info->rate &&
           desc.samplerate != data->info->rate/2)
            return DR_UNSUPPORTED;
  #endif
  
       /* check if destination format is supported */
       switch (desc.sampleformat) {
            case FSSF_U8:
            case FSSF_S16:
            case FSSF_S24:
            case FSSF_S32:
            case FSSF_FLOAT:
                 break;
            default:
                 return DR_UNSUPPORTED;
       }
       
       /* check if destination mode is supported */
       switch (desc.channelmode) {
            case FSCM_MONO:
            case FSCM_STEREO:
            case FSCM_STEREO21:
            case FSCM_STEREO30:
            case FSCM_STEREO31:
            case FSCM_SURROUND30:
            case FSCM_SURROUND31:
            case FSCM_SURROUND40_2F2R:
            case FSCM_SURROUND41_2F2R:
            case FSCM_SURROUND40_3F1R:
            case FSCM_SURROUND41_3F1R:
            case FSCM_SURROUND50:
            case FSCM_SURROUND51:
                 break;
            default:
                 return DR_UNSUPPORTED;
       }
  
       pthread_mutex_lock( &data->lock );
       
       Vorbis_Stop( data, false );
       
  #ifndef USE_TREMOR
       if (desc.samplerate == data->info->rate/2) {
            if (ov_halfrate( &data->vf, 1 )) {
                 pthread_mutex_unlock( &data->lock );
                 return DR_UNSUPPORTED;
            }
       } else {
            ov_halfrate( &data->vf, 0 );
       }
  #endif
  
       /* reference destination stream */
       destination->AddRef( destination );
       data->dest.buffer = destination;
       data->dest.format = desc.sampleformat;
       data->dest.mode   = desc.channelmode;
       data->dest.length = desc.length;
  
       /* register new callback */
       data->callback = callback;
       data->ctx      = ctx;
  
       if (data->finished) {
            if (direct_stream_remote( data->stream ))
                 direct_stream_seek( data->stream, 0 );
            else
                 ov_time_seek( &data->vf, 0 );
                 
            data->finished = false;
       }
       
       data->status = FMSTATE_PLAY;
       pthread_cond_broadcast( &data->cond );
  
       /* start thread */
       data->thread = direct_thread_create( DTT_DEFAULT, VorbisBufferThread, data, "Vorbis" );
  
       pthread_mutex_unlock( &data->lock );
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_Stop( IFusionSoundMusicProvider *thiz )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       pthread_mutex_lock( &data->lock );
  
       Vorbis_Stop( data, false );
       
       pthread_cond_broadcast( &data->cond );
  
       pthread_mutex_unlock( &data->lock );
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_GetStatus( IFusionSoundMusicProvider *thiz,
                                              FSMusicProviderStatus     *status )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (!status)
            return DR_INVARG;
  
       *status = data->status;
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_SeekTo( IFusionSoundMusicProvider *thiz,
                                           double                     seconds )
  {
       DirectResult ret = DR_OK;
  
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (seconds < 0.0)
            return DR_INVARG;
  
       pthread_mutex_lock( &data->lock );
  
       if (direct_stream_remote( data->stream )) {
            unsigned int off;
  
            if (!data->info->bitrate_nominal)
                 return DR_UNSUPPORTED;
  
            off = seconds * (double)(data->info->bitrate_nominal >> 3);
            ret = direct_stream_seek( data->stream, off );
       }
       else {
  #ifdef USE_TREMOR
            seconds *= 1000;
  #endif
            if (ov_time_seek( &data->vf, seconds ))
                 ret = DR_FAILURE;
       }
       
       if (ret == DR_OK) {
            data->seeked   = true;
            data->finished = false;
       }
  
       pthread_mutex_unlock( &data->lock );
  
       return ret;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_GetPos( IFusionSoundMusicProvider *thiz,
                                           double                    *seconds )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (!seconds)
            return DR_INVARG;
  
  #ifdef USE_TREMOR
       *seconds = (double)ov_time_tell( &data->vf ) / 1000.0;
  #else
       *seconds = ov_time_tell( &data->vf );
  #endif
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_GetLength( IFusionSoundMusicProvider *thiz,
                                              double                    *seconds )
  {
       double length;
  
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (!seconds)
            return DR_INVARG;
  
  #ifdef USE_TREMOR
       length = (double)ov_time_total( &data->vf, -1 ) / 1000.0;
  #else
       length = ov_time_total( &data->vf, -1 );
  #endif
       if (length < 0) {
            if (data->info->bitrate_nominal) {
                 length = direct_stream_length( data->stream ) /
                          (double)(data->info->bitrate_nominal >> 3);
            }
       }
  
       *seconds = length;
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_SetPlaybackFlags( IFusionSoundMusicProvider    *thiz,
                                                     FSMusicProviderPlaybackFlags  flags )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis )
  
       if (flags & ~FMPLAY_LOOPING)
            return DR_UNSUPPORTED;
  
       if (flags & FMPLAY_LOOPING && !direct_stream_seekable( data->stream ))
            return DR_UNSUPPORTED;
  
       data->flags = flags;
  
       return DR_OK;
  }
  
  static DirectResult
  IFusionSoundMusicProvider_Vorbis_WaitStatus( IFusionSoundMusicProvider *thiz,
                                               FSMusicProviderStatus      mask,
                                               unsigned int               timeout )
  {
       DIRECT_INTERFACE_GET_DATA( IFusionSoundMusicProvider_Vorbis)
       
       if (!mask || mask & ~FMSTATE_ALL)
            return DR_INVARG;
            
       if (timeout) {
            struct timespec t;
            long long       s;
            
            s = direct_clock_get_abs_micros() + timeout * 1000ll;
            t.tv_sec  = (s / 1000000);
            t.tv_nsec = (s % 1000000) * 1000;
            
  #ifdef HAVE_PTHREAD_MUTEX_TIMEDLOCK
            if (pthread_mutex_timedlock( &data->lock, &t ))
                 return DR_TIMEOUT;
  #else          
            while (pthread_mutex_trylock( &data->lock )) {
                 usleep( 1000 );
                 if (direct_clock_get_abs_micros() >= s)
                      return DR_TIMEOUT;
            }
  #endif  
            while (!(data->status & mask)) {
                 if (pthread_cond_timedwait( &data->cond, &data->lock, &t ) == ETIMEDOUT) {
                      pthread_mutex_unlock( &data->lock );
                      return DR_TIMEOUT;
                 }
            }
       }
       else {
            pthread_mutex_lock( &data->lock );
            
            while (!(data->status & mask))
                 pthread_cond_wait( &data->cond, &data->lock );
       }
       
       pthread_mutex_unlock( &data->lock );
       
       return DR_OK;
  }
  
  
  /* exported symbols */
  
  static DirectResult
  Probe( IFusionSoundMusicProvider_ProbeContext *ctx )
  {
       if (!memcmp( &ctx->header[0], "OggS", 4 ) &&
           !memcmp( &ctx->header[29], "vorbis", 6 ))
            return DR_OK;
  
       return DR_UNSUPPORTED;
  }
  
  static DirectResult
  Construct( IFusionSoundMusicProvider *thiz,
             const char                *filename,
             DirectStream              *stream )
  {
       ov_callbacks cb;
  
       DIRECT_ALLOCATE_INTERFACE_DATA( thiz, IFusionSoundMusicProvider_Vorbis )
  
       data->ref    = 1;
       data->stream = direct_stream_dup( stream );
       data->status = FMSTATE_STOP;
  
       cb.read_func  = ov_read_callback;
       cb.seek_func  = ov_seek_callback;
       cb.tell_func  = ov_tell_callback;
       cb.close_func = ov_close_callback;
  
       if (ov_open_callbacks( data->stream, &data->vf, NULL, 0, cb ) < 0) {
            D_ERROR( "IFusionSoundMusicProvider_Vorbis: "
                     "Error opening ogg/vorbis stream!\n" );
            IFusionSoundMusicProvider_Vorbis_Destruct( thiz );
            return DR_FAILURE;
       }
  
       data->info = ov_info( &data->vf, -1 );
       if (!data->info) {
            D_ERROR( "IFusionSoundMusicProvider_Vorbis: "
                     "Error getting stream informations!\n" );
            IFusionSoundMusicProvider_Vorbis_Destruct( thiz );
            return DR_FAILURE;
       }
  
       direct_util_recursive_pthread_mutex_init( &data->lock );
       pthread_cond_init( &data->cond, NULL );
  
       /* initialize function pointers */
       thiz->AddRef               = IFusionSoundMusicProvider_Vorbis_AddRef;
       thiz->Release              = IFusionSoundMusicProvider_Vorbis_Release;
       thiz->GetCapabilities      = IFusionSoundMusicProvider_Vorbis_GetCapabilities;
       thiz->GetTrackDescription  = IFusionSoundMusicProvider_Vorbis_GetTrackDescription;
       thiz->GetStreamDescription = IFusionSoundMusicProvider_Vorbis_GetStreamDescription;
       thiz->GetBufferDescription = IFusionSoundMusicProvider_Vorbis_GetBufferDescription;
       thiz->PlayToStream         = IFusionSoundMusicProvider_Vorbis_PlayToStream;
       thiz->PlayToBuffer         = IFusionSoundMusicProvider_Vorbis_PlayToBuffer;
       thiz->Stop                 = IFusionSoundMusicProvider_Vorbis_Stop;
       thiz->GetStatus            = IFusionSoundMusicProvider_Vorbis_GetStatus;
       thiz->SeekTo               = IFusionSoundMusicProvider_Vorbis_SeekTo;
       thiz->GetPos               = IFusionSoundMusicProvider_Vorbis_GetPos;
       thiz->GetLength            = IFusionSoundMusicProvider_Vorbis_GetLength;
       thiz->SetPlaybackFlags     = IFusionSoundMusicProvider_Vorbis_SetPlaybackFlags;
       thiz->WaitStatus           = IFusionSoundMusicProvider_Vorbis_WaitStatus;
  
       return DR_OK;
  }
  
  