From b5a738fa52c8b0f2212deb5febd2d7f0b8f6544f Mon Sep 17 00:00:00 2001
From: tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>
Date: Fri, 9 May 2008 19:19:28 +0000
Subject: - input-source selection works now (with bass or portaudio with
 portmixer) - audio-effects (DSP) interface for audio-playback plus a simple
 voice removal example (does not sound that good) - FFMpeg support for BASS -
 audio-clock for FFMpeg for GetPosition and synchronisation - more compatible
 seeking in FFMpeg - clean termination of the audio interfaces/streams
 (especially ffmpeg) - Audio output device enumeration (selection will be
 added later to the sounds option screen) - display of threshold and volume in
 the record-options screen - threshold and volume can be changed with the 'T'
 (threshold) and '+'/'-' (source volume) keys - added a FadeIn() method to the
 IAudioPlayback interface - some minor changes to the audio classes/screens -
 new base-class for audio-playback classes (used by bass, portaudio and sdl)

git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1078 b956fd51-792f-4845-bead-9b4dfca2ff2c
---
 Game/Code/Classes/UAudioCore_Bass.pas          |  27 +-
 Game/Code/Classes/UAudioCore_Portaudio.pas     |  25 +-
 Game/Code/Classes/UAudioDecoder_FFMpeg.pas     | 546 +++++++++++++++---------
 Game/Code/Classes/UAudioInput_Bass.pas         | 329 +++++++++++----
 Game/Code/Classes/UAudioInput_Portaudio.pas    | 255 +++++++++---
 Game/Code/Classes/UAudioPlaybackBase.pas       | 220 ++++++++++
 Game/Code/Classes/UAudioPlayback_Bass.pas      | 549 ++++++++++++++++---------
 Game/Code/Classes/UAudioPlayback_Portaudio.pas | 248 +++++++++--
 Game/Code/Classes/UAudioPlayback_SDL.pas       |  46 ++-
 Game/Code/Classes/UAudioPlayback_SoftMixer.pas | 370 ++++++++---------
 Game/Code/Classes/UIni.pas                     | 166 +++-----
 Game/Code/Classes/UMain.pas                    |  11 +-
 Game/Code/Classes/UMedia_dummy.pas             |  38 +-
 Game/Code/Classes/UMusic.pas                   | 162 +++++++-
 Game/Code/Classes/URecord.pas                  | 167 +++++---
 15 files changed, 2189 insertions(+), 970 deletions(-)
 create mode 100644 Game/Code/Classes/UAudioPlaybackBase.pas

(limited to 'Game/Code/Classes')

diff --git a/Game/Code/Classes/UAudioCore_Bass.pas b/Game/Code/Classes/UAudioCore_Bass.pas
index 9c13b461..442c999b 100644
--- a/Game/Code/Classes/UAudioCore_Bass.pas
+++ b/Game/Code/Classes/UAudioCore_Bass.pas
@@ -9,8 +9,12 @@ interface
 {$I switches.inc}
 
 uses
+  {$IFDEF MSWINDOWS}
+  Windows,
+  {$ENDIF}
   Classes,
-  SysUtils;
+  SysUtils,
+  UMusic;
 
 type
   TAudioCore_Bass = class
@@ -18,6 +22,7 @@ type
     public
       class function ErrorGetString(): string; overload;
       class function ErrorGetString(errCode: integer): string; overload;
+      class function ConvertAudioFormatToBASSFlags(Format: TAudioSampleFormat; out Flags: DWORD): boolean;
   end;
 
   
@@ -113,4 +118,22 @@ begin
     end;
 end;
 
-end.
\ No newline at end of file
+class function TAudioCore_Bass.ConvertAudioFormatToBASSFlags(Format: TAudioSampleFormat; out Flags: DWORD): boolean;
+begin
+  case Format of
+    asfS16:
+      Flags := 0;
+    asfFloat:
+      Flags := BASS_SAMPLE_FLOAT;
+    asfU8:
+      Flags := BASS_SAMPLE_8BITS;
+    else begin
+      Result := false;
+      Exit;
+    end;
+  end;
+
+  Result := true;
+end;
+
+end.
diff --git a/Game/Code/Classes/UAudioCore_Portaudio.pas b/Game/Code/Classes/UAudioCore_Portaudio.pas
index bb0635b3..cd228982 100644
--- a/Game/Code/Classes/UAudioCore_Portaudio.pas
+++ b/Game/Code/Classes/UAudioCore_Portaudio.pas
@@ -16,9 +16,12 @@ uses
 
 type
   TAudioCore_Portaudio = class
+    private
+      constructor Create();
     public
-      class function GetPreferredApiIndex(): TPaHostApiIndex;
-      class function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
+      class function GetInstance(): TAudioCore_Portaudio;
+      function GetPreferredApiIndex(): TPaHostApiIndex;
+      function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
   end;
 
 implementation
@@ -57,10 +60,24 @@ const
     array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
 {$IFEND}
 
+var
+  Instance: TAudioCore_Portaudio;
 
 { TAudioInput_Portaudio }
 
-class function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
+constructor TAudioCore_Portaudio.Create();
+begin
+  inherited;
+end;
+
+class function TAudioCore_Portaudio.GetInstance(): TAudioCore_Portaudio;
+begin
+  if not assigned(Instance) then
+    Instance := TAudioCore_Portaudio.Create();
+  Result := Instance;
+end;
+
+function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
 var
   i: integer;
   apiIndex: TPaHostApiIndex;
@@ -148,7 +165,7 @@ end;
  *   So we have to provide the possibility to manually select an output device
  *   in the UltraStar options if we want to use portaudio instead of SDL.
  *)
-class function TAudioCore_Portaudio.TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
+function TAudioCore_Portaudio.TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
 var
   stream: PPaStream;
   err: TPaError;
diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
index 209e1838..f1c9b364 100644
--- a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
+++ b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
@@ -36,8 +36,10 @@ uses
   avutil,
   avio,        // used for url_ferror
   mathematics, // used for av_rescale_q
+  rational,
   SDL,
   ULog,
+  UCommon,
   UConfig;
 
 type
@@ -50,35 +52,49 @@ type
       size       : integer;
       mutex      : PSDL_Mutex;
       cond       : PSDL_Cond;
-      quit       : boolean;
+      abortRequest: boolean;
 
     public
       constructor Create();
       destructor Destroy(); override;
 
       function Put(pkt : PAVPacket): integer;
+      function PutStatus(statusFlag: integer; statusInfo: Pointer): integer;
       function Get(var pkt: TAVPacket; block: boolean): integer;
       procedure Flush();
+      procedure Abort();
   end;
 
 const
   MAX_AUDIOQ_SIZE = (5 * 16 * 1024);
 
-var
-  EOFPacket:   TAVPacket;
-  FlushPacket: TAVPacket;
+const
+  STATUS_PACKET: PChar = 'STATUS_PACKET';
+const
+  PKT_STATUS_FLAG_EOF   = 1;
+  PKT_STATUS_FLAG_FLUSH = 2;
+  PKT_STATUS_FLAG_ERROR = 3;
 
 type
   PAudioBuffer = ^TAudioBuffer;
+  // TODO: should (or must?) be aligned at a 2-byte boundary.
+  //   ffmpeg provides a C-macro called DECLARE_ALIGNED for this.
+  //   Records can be aligned with the $PACKRECORDS compiler-directive but
+  //   are already aligned at a 2-byte boundary by default in FPC.
+  //   But what about arrays, are they aligned by a 2-byte boundary too?
+  //   Or maybe we have to define a fake record with only an array in it?
   TAudioBuffer = array[0 .. (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3 div 2)-1] of byte;
 
 type
   TFFMpegDecodeStream = class(TAudioDecodeStream)
     private
-      _EOF: boolean; // end-of-stream flag
-      _EOF_lock : PSDL_Mutex;
+      decoderLock : PSDL_Mutex;
+      parserLock : PSDL_Mutex;
+      myint: integer;
+
+      EOFState: boolean; // end-of-stream flag
+      ErrorState: boolean;
 
-      internalLock : PSDL_Mutex;
       resumeCond   : PSDL_Cond;
 
       quitRequest : boolean;
@@ -99,23 +115,30 @@ type
       ffmpegStreamIndex : Integer;
       ffmpegStream      : PAVStream;
 
+      audioClock: double; // stream position in seconds
+
       // state-vars for DecodeFrame
       pkt             : TAVPacket;
       audio_pkt_data  : PChar;
       audio_pkt_size  : integer;
 
       // state-vars for AudioCallback
-      audio_buf_index : cardinal;
-      audio_buf_size  : cardinal;
+      audio_buf_index : integer;
+      audio_buf_size  : integer;
       audio_buf       : TAudioBuffer;
 
-      procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
-      procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
-      function GetLockMutex(): PSDL_Mutex; {$IFDEF HasInline}inline;{$ENDIF}
+      procedure LockParser();   {$IFDEF HasInline}inline;{$ENDIF}
+      procedure UnlockParser(); {$IFDEF HasInline}inline;{$ENDIF}
+      function GetParserMutex(): PSDL_Mutex; {$IFDEF HasInline}inline;{$ENDIF}
+
+      procedure LockDecoder();   {$IFDEF HasInline}inline;{$ENDIF}
+      procedure UnlockDecoder(); {$IFDEF HasInline}inline;{$ENDIF}
+
+      procedure SetEOF(state: boolean);   {$IFDEF HasInline}inline;{$ENDIF}
+      procedure SetError(state: boolean); {$IFDEF HasInline}inline;{$ENDIF}
 
       procedure ParseAudio();
       function DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
-      procedure SetEOF(state: boolean);
     public
       constructor Create(pFormatCtx: PAVFormatContext;
                          pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
@@ -129,6 +152,7 @@ type
       function GetPosition: real;            override;
       procedure SetPosition(Time: real);     override;
       function IsEOF(): boolean;             override;
+      function IsError(): boolean;           override;
 
       function ReadData(Buffer: PChar; BufSize: integer): integer; override;
   end;
@@ -141,6 +165,7 @@ type
       function GetName: String;
 
       function InitializeDecoder(): boolean;
+      function FinalizeDecoder(): boolean;
       function Open(const Filename: string): TAudioDecodeStream;
   end;
 
@@ -155,6 +180,8 @@ var
 constructor TFFMpegDecodeStream.Create(pFormatCtx: PAVFormatContext;
                    pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
                    ffmpegStreamIndex : Integer; ffmpegStream: PAVStream);
+var
+  sampleFormat: TAudioSampleFormat;
 begin
   inherited Create();
 
@@ -174,41 +201,45 @@ begin
   Self.ffmpegStreamIndex := ffmpegStreamIndex;
   Self.ffmpegStream      := ffmpegStream;
 
+  case pCodecCtx^.sample_fmt of
+    SAMPLE_FMT_U8:  sampleFormat := asfU8;
+    SAMPLE_FMT_S16: sampleFormat := asfS16;
+    SAMPLE_FMT_S24: sampleFormat := asfS24;
+    SAMPLE_FMT_S32: sampleFormat := asfS32;
+    SAMPLE_FMT_FLT: sampleFormat := asfFloat;
+    else            sampleFormat := asfS16; // try standard format
+  end;
+
   formatInfo := TAudioFormatInfo.Create(
     pCodecCtx^.channels,
     pCodecCtx^.sample_rate,
-    // pCodecCtx^.sample_fmt not yet used by FFMpeg -> use FFMpeg's standard format
-    asfS16
+    sampleFormat
   );
 
-  _EOF := false;
-  _EOF_lock := SDL_CreateMutex();
-
-  internalLock := SDL_CreateMutex();
+  EOFState := false;
+  ErrorState := false;
+  decoderLock := SDL_CreateMutex();
+  parserLock := SDL_CreateMutex();
   resumeCond := SDL_CreateCond();
 
   parseThread := SDL_CreateThread(@DecodeThreadMain, Self);
 end;
 
+{*
+ * Frees the decode-stream data.
+ * IMPORTANT: call Close() before freeing the decode-stream to avoid dead-locks.
+ * This wakes-up every waiting audio-thread waiting in the packet-queue while
+ * performing a ReadData() request.
+ * Then assure that no thread uses ReadData anymore (e.g. by stopping the audio-callback).
+ * Now you can free the decode-stream.
+ *}
 destructor TFFMpegDecodeStream.Destroy();
 begin
+  // wake-up and terminate threads
+  // Note: should be called by the caller before Destroy() was called instead
+  //   to wake-up a waiting audio-callback thread in the packet-queue.
+  //   Otherwise dead-locks are possible.
   Close();
-  inherited;
-end;
-
-procedure TFFMpegDecodeStream.Close();
-var
-  status: integer;
-begin
-  Lock();
-  quitRequest := true;
-  SDL_CondSignal(resumeCond);
-  Unlock();
-
-  if (parseThread <> nil) then
-  begin
-    SDL_WaitThread(parseThread, status);
-  end;
 
   // Close the codec
   if (pCodecCtx <> nil) then
@@ -226,21 +257,64 @@ begin
 
   FreeAndNil(packetQueue);
   FreeAndNil(formatInfo);
+
+  SDL_DestroyMutex(decoderLock);
+  decoderLock := nil;
+  SDL_DestroyMutex(parserLock);
+  parserLock := nil;
+  SDL_DestroyCond(resumeCond);
+  resumeCond := nil;
+
+  inherited;
 end;
 
-procedure TFFMpegDecodeStream.Lock();
+procedure TFFMpegDecodeStream.Close();
+var
+  status: integer;
 begin
-  SDL_mutexP(internalLock);
+  // wake threads waiting for packet-queue data
+  packetQueue.Abort();
+
+  // abort parse-thread
+  LockParser();
+  quitRequest := true;
+  SDL_CondSignal(resumeCond);
+  UnlockParser();
+  // and wait until it terminates
+  if (parseThread <> nil) then
+  begin
+    SDL_WaitThread(parseThread, status);
+    parseThread := nil;
+  end;
+
+  // NOTE: we cannot free the codecCtx or formatCtx here because
+  // a formerly waiting thread in the packet-queue might require them
+  // and crash if it tries to access them.
+end;
+
+procedure TFFMpegDecodeStream.LockParser();
+begin
+  SDL_mutexP(parserLock);
+end;
+
+procedure TFFMpegDecodeStream.UnlockParser();
+begin
+  SDL_mutexV(parserLock);
 end;
 
-procedure TFFMpegDecodeStream.Unlock();
+function TFFMpegDecodeStream.GetParserMutex(): PSDL_Mutex;
 begin
-  SDL_mutexV(internalLock);
+  Result := parserLock;
 end;
 
-function TFFMpegDecodeStream.GetLockMutex(): PSDL_Mutex;
+procedure TFFMpegDecodeStream.LockDecoder();
 begin
-  Result := internalLock;
+  SDL_mutexP(decoderLock);
+end;
+
+procedure TFFMpegDecodeStream.UnlockDecoder();
+begin
+  SDL_mutexV(decoderLock);
 end;
 
 function TFFMpegDecodeStream.GetLength(): real;
@@ -255,37 +329,59 @@ end;
 
 function TFFMpegDecodeStream.IsEOF(): boolean;
 begin
-  SDL_mutexP(_EOF_lock);
-  Result := _EOF;
-  SDL_mutexV(_EOF_lock);
+  LockDecoder();
+  Result := EOFState;
+  UnlockDecoder();
 end;
 
 procedure TFFMpegDecodeStream.SetEOF(state: boolean);
 begin
-  SDL_mutexP(_EOF_lock);
-  _EOF := state;
-  SDL_mutexV(_EOF_lock);
+  LockDecoder();
+  EOFState := state;
+  UnlockDecoder();
+end;
+
+function TFFMpegDecodeStream.IsError(): boolean;
+begin
+  LockDecoder();
+  Result := ErrorState;
+  UnlockDecoder();
+end;
+
+procedure TFFMpegDecodeStream.SetError(state: boolean);
+begin
+  LockDecoder();
+  ErrorState := state;
+  UnlockDecoder();
 end;
 
 function TFFMpegDecodeStream.GetPosition(): real;
-//var
-//  bytes: integer;
 begin
+  // FIXME: the audio-clock might not be that accurate
   // see: tutorial on synching (audio-clock)
-  Result := 0;
+  Result := audioClock;
 end;
 
 procedure TFFMpegDecodeStream.SetPosition(Time: real);
-//var
-//  bytes:    integer;
 begin
-  Lock();
+  LockParser();
+
   seekPos := Trunc(Time * AV_TIME_BASE);
-  // FIXME: seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0
-  seekFlags := 0;//AVSEEK_FLAG_BACKWARD;
+
+  seekFlags := 0;
+  // Note: the BACKWARD-flag seeks to the first position <= the position
+  // searched for. Otherwise e.g. position 0 might not be seeked correct.
+  // For some reason ffmpeg sometimes doesn't use position 0 but the key-frame
+  // following. In streams with few key-frames (like many flv-files) the next
+  // key-frame after 0 might be 5secs ahead.
+  if (Time < audioClock) then
+    seekFlags := AVSEEK_FLAG_BACKWARD;
+  seekFlags := AVSEEK_FLAG_ANY;
+
   seekRequest := true;
   SDL_CondSignal(resumeCond);
-  Unlock();
+
+  UnlockParser();
 end;
 
 function DecodeThreadMain(streamPtr: Pointer): integer; cdecl;
@@ -300,68 +396,71 @@ end;
 procedure TFFMpegDecodeStream.ParseAudio();
 var
   packet: TAVPacket;
+  statusPacket: PAVPacket;
   seekTarget: int64;
-  eofState: boolean;
+  stopParsing: boolean;
   pbIOCtx: PByteIOContext;
+  err: integer;
+  index: integer;
 begin
-  eofState := false;
+  stopParsing := false;
 
   while (true) do
   begin
-    Lock();
+    LockParser();
+
     // wait if end-of-file reached
-    if (eofState) then
+    if (stopParsing) then
     begin
-      if (not (seekRequest or quitRequest)) then
-      begin
-        // signal end-of-file
-        packetQueue.put(@EOFPacket);
-        // wait for reuse or destruction of stream
-        repeat
-          SDL_CondWait(resumeCond, GetLockMutex());
-        until (seekRequest or quitRequest);
-      end;
-      eofState := false;
-      SetEOF(false);
+      // wait for reuse or destruction of stream
+      while not (seekRequest or quitRequest) do
+        SDL_CondWait(resumeCond, GetParserMutex());
     end;
 
     if (quitRequest) then
     begin
+      UnlockParser();
       break;
     end;
 
     // handle seek-request
-    if(seekRequest) then
+    if (seekRequest) then
     begin
-      // TODO: Do we need this?
-      //       The position is converted to AV_TIME_BASE and then to the stream-specific base.
-      //       Why not convert to the stream-specific one from the beginning.
+      // reset status
+      SetEOF(false);
+      SetError(false);
+      stopParsing := false;
+
       seekTarget := av_rescale_q(seekPos, AV_TIME_BASE_Q, ffmpegStream^.time_base);
-      if(av_seek_frame(pFormatCtx, ffmpegStreamIndex,
-          seekTarget, seekFlags) < 0) then
+      err := av_seek_frame(pFormatCtx, ffmpegStreamIndex, seekTarget, seekFlags);
+      // seeking failed -> retry with the default stream (necessary for flv-videos and some ogg-files)
+      if (err < 0) then
+        err := av_seek_frame(pFormatCtx, -1, seekPos, seekFlags);
+      // check if seeking failed
+      if (err < 0) then
       begin
-        // this will crash in FPC due to a bug
-        //Log.LogStatus({pFormatCtx^.filename +} ': error while seeking', 'UAudioDecoder_FFMpeg');
+        Log.LogStatus('Seek Error in "'+pFormatCtx^.filename+'"', 'UAudioDecoder_FFMpeg');
       end
       else
       begin
         packetQueue.Flush();
-        packetQueue.Put(@FlushPacket);
+        packetQueue.PutStatus(PKT_STATUS_FLAG_FLUSH, nil);
       end;
       seekRequest := false;
     end;
 
-    Unlock();
+    UnlockParser();
 
-    
-    if(packetQueue.size > MAX_AUDIOQ_SIZE) then
+    if (packetQueue.size > MAX_AUDIOQ_SIZE) then
     begin
       SDL_Delay(10);
       continue;
     end;
 
-    if(av_read_frame(pFormatCtx, packet) < 0) then
+    if (av_read_frame(pFormatCtx, packet) < 0) then
     begin
+      // failed to read a frame, check reason
+
       {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)}
       pbIOCtx := pFormatCtx^.pb;
       {$ELSE}
@@ -369,39 +468,30 @@ begin
       {$IFEND}
 
       // check for end-of-file (eof is not an error)
-      if(url_feof(pbIOCtx) <> 0) then
+      if (url_feof(pbIOCtx) <> 0) then
       begin
-        {$IFDEF DebugFFMpegDecode}
-        DebugWriteln('feof');
-        {$ENDIF}
-        eofState := true;
+        // signal end-of-file
+        packetQueue.putStatus(PKT_STATUS_FLAG_EOF, nil);
+        stopParsing := true;
         continue;
       end;
 
       // check for errors
-      if(url_ferror(pbIOCtx) = 0) then
+      if (url_ferror(pbIOCtx) <> 0) then
       begin
-        {$IFDEF DebugFFMpegDecode}
-        DebugWriteln('Errorf');
-        {$ENDIF}
-        // no error -> wait for user input
-        SDL_Delay(100);
-        continue;
-      end
-      else
-      begin
-        // an error occured -> abort
-        // TODO: eof or quit?
-        eofState := true;
+        // an error occured -> abort and wait for repositioning or termination
+        packetQueue.putStatus(PKT_STATUS_FLAG_ERROR, nil);
+        stopParsing := true;
         continue;
       end;
-    end;
 
-    //DebugWriteln( 'ffmpeg - av_read_frame' );
+      // no error -> wait for user input
+      SDL_Delay(100);
+      continue;
+    end;
 
-    if(packet.stream_index = ffmpegStreamIndex) then
+    if (packet.stream_index = ffmpegStreamIndex) then
     begin
-      //DebugWriteln( 'packet_queue_put' );
       packetQueue.put(@packet);
     end
     else
@@ -425,20 +515,22 @@ begin
   begin
     while (audio_pkt_size > 0) do
     begin
-      //DebugWriteln( 'got audio packet' );
       data_size := bufSize;
 
-      {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0
-      len1 := avcodec_decode_audio2(pCodecCtx, @buffer,
-                  data_size, audio_pkt_data, audio_pkt_size);
-      {$ELSE}
-      // FIXME: with avcodec_decode_audio a package could contain several frames
-      //        this is not handled yet
-      len1 := avcodec_decode_audio(pCodecCtx, @buffer,
-                  data_size, audio_pkt_data, audio_pkt_size);
-      {$IFEND}
-
-      //DebugWriteln('avcodec_decode_audio : ' + inttostr( len1 ));
+      try
+        {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0
+        len1 := avcodec_decode_audio2(pCodecCtx, @buffer,
+                    data_size, audio_pkt_data, audio_pkt_size);
+        {$ELSE}
+        // FIXME: with avcodec_decode_audio a package could contain several frames
+        //        this is not handled yet
+        len1 := avcodec_decode_audio(pCodecCtx, @buffer,
+                    data_size, audio_pkt_data, audio_pkt_size);
+        {$IFEND}
+      except
+        Log.LogError('Exception at avcodec_decode_audio(2)!', 'TFFMpegDecodeStream.DecodeFrame');
+        len1 := -1;
+      end;
 
       if(len1 < 0) then
       begin
@@ -459,6 +551,10 @@ begin
         continue;
       end;
 
+      //pts := audioClock;
+      audioClock := audioClock + data_size /
+          (1.0 * formatInfo.FrameSize * formatInfo.SampleRate);
+
       // we have data, return it and come back for more later
       result := data_size;
       exit;
@@ -469,87 +565,104 @@ begin
       av_free_packet(@pkt);
     end;
 
-    if (packetQueue.quit) then
+    // do not use an aborted queue
+    if (packetQueue.abortRequest) then
       exit;
 
+    // request a new packet and block if non available.
+    // If this fails, the queue was aborted.
     if (packetQueue.Get(pkt, true) < 0) then
       exit;
 
-    audio_pkt_data := PChar(pkt.data);
-    audio_pkt_size := pkt.size;
-
-    if (audio_pkt_data = PChar(FlushPacket.data)) then
+    // handle Status-packet
+    if (PChar(pkt.data) = STATUS_PACKET) then
     begin
-      avcodec_flush_buffers(pCodecCtx);
-      {$IFDEF DebugFFMpegDecode}
-      DebugWriteln('Flush');
-      {$ENDIF}
+      pkt.data := nil;
+      audio_pkt_data := nil;
+      audio_pkt_size := 0;
+
+      case (pkt.flags) of
+        PKT_STATUS_FLAG_FLUSH:
+        begin
+          avcodec_flush_buffers(pCodecCtx);
+        end;
+        PKT_STATUS_FLAG_EOF: // end-of-file
+        begin
+          SetEOF(true);
+          // buffer contains no data -> result = -1
+          exit;
+        end;
+        PKT_STATUS_FLAG_ERROR:
+        begin
+          SetError(true);
+          Log.LogStatus('I/O Error', 'TFFMpegDecodeStream.DecodeFrame');
+          exit;
+        end;
+        else
+        begin
+          Log.LogStatus('Unknown status', 'TFFMpegDecodeStream.DecodeFrame');
+        end;
+      end;
+
       continue;
     end;
 
-    // check for end-of-file
-    if (audio_pkt_data = PChar(EOFPacket.data)) then
+    audio_pkt_data := PChar(pkt.data);
+    audio_pkt_size := pkt.size;
+
+    // if available, update the audio clock with pts
+    if(pkt.pts <> AV_NOPTS_VALUE) then
     begin
-      // end-of-file reached -> set EOF-flag
-      SetEOF(true);
-      {$IFDEF DebugFFMpegDecode}
-      DebugWriteln('EOF');
-      {$ENDIF}
-      // note: buffer is not (even partially) filled -> no data to return
-      exit;
+      audioClock := av_q2d(ffmpegStream^.time_base) * pkt.pts;
     end;
-
-    //DebugWriteln( 'Audio Packet Size - ' + inttostr(audio_pkt_size) );
   end;
 end;
 
 function TFFMpegDecodeStream.ReadData(Buffer : PChar; BufSize: integer): integer;
 var
-  //outStream       : TFFMpegDecodeStream;
-  len1,
-  audio_size      : integer;
-  len             : integer;
+  nBytesCopy:   integer; // number of bytes to copy
+  nBytesRemain: integer; // number of bytes left (remaining) to read
 begin
-  len := BufSize;
   result := -1;
 
-  // end-of-file reached
+  // init number of bytes left to copy to the output buffer
+  nBytesRemain := BufSize;
+
+  // leave if end-of-file was reached previously
   if EOF then
     exit;
 
-  while (len > 0) do begin
+  // copy data to output buffer
+  while (nBytesRemain > 0) do begin
+    // check if we need more data
     if (audio_buf_index >= audio_buf_size) then
     begin
       // we have already sent all our data; get more
-      audio_size := DecodeFrame(audio_buf, sizeof(TAudioBuffer));
-      //DebugWriteln('audio_decode_frame : '+ inttostr(audio_size));
-
-      if(audio_size < 0) then
+      audio_buf_size := DecodeFrame(audio_buf, sizeof(TAudioBuffer));
+      // check for errors or EOF
+      if(audio_buf_size < 0) then
       begin
-        // if error, output silence
+        // fill decode-buffer with silence
         audio_buf_size := 1024;
         FillChar(audio_buf, audio_buf_size, #0);
-        //DebugWriteln( 'Silence' );
-      end
-      else
-      begin
-        audio_buf_size := audio_size;
       end;
       audio_buf_index := 0;
     end;
 
-    len1 := audio_buf_size - audio_buf_index;
-    if (len1 > len) then
-      len1 := len;
+    // calc number of new bytes in the decode-buffer
+    nBytesCopy := audio_buf_size - audio_buf_index;
+    // resize copy-count if more bytes available than needed (remaining bytes are used the next time)
+    if (nBytesCopy > nBytesRemain) then
+      nBytesCopy := nBytesRemain;
 
-    Move(audio_buf[audio_buf_index], Buffer[0], len1);
+    Move(audio_buf[audio_buf_index], Buffer[0], nBytesCopy);
 
-    Dec(len, len1);
-    Inc(Buffer, len1);
-    Inc(audio_buf_index, len1);
+    Dec(nBytesRemain,    nBytesCopy);
+    Inc(Buffer,          nBytesCopy);
+    Inc(audio_buf_index, nBytesCopy);
   end;
 
-  result := BufSize;
+  Result := BufSize;
 end;
 
 
@@ -557,7 +670,7 @@ end;
 
 function TAudioDecoder_FFMpeg.GetName: String;
 begin
-  result := 'FFMpeg_Decoder';
+  Result := 'FFMpeg_Decoder';
 end;
 
 function TAudioDecoder_FFMpeg.InitializeDecoder: boolean;
@@ -566,15 +679,12 @@ begin
 
   av_register_all();
 
-  // init end-of-file package
-  av_init_packet(EOFPacket);
-  EOFPacket.data := Pointer(PChar('EOF'));
-
-  // init flush package
-  av_init_packet(FlushPacket);
-  FlushPacket.data := Pointer(PChar('FLUSH'));
+  Result := true;
+end;
 
-  result := true;
+function TAudioDecoder_FFMpeg.FinalizeDecoder(): boolean;
+begin
+  Result := true;
 end;
 
 class function TAudioDecoder_FFMpeg.FindAudioStreamIndex(pFormatCtx : PAVFormatContext): integer;
@@ -599,7 +709,7 @@ begin
     end;
   end;
 
-  result := streamIndex;
+  Result := streamIndex;
 end;
 
 function TAudioDecoder_FFMpeg.Open(const Filename: string): TAudioDecodeStream;
@@ -609,11 +719,9 @@ var
   pCodec         : PAVCodec;
   ffmpegStreamID : Integer;
   ffmpegStream   : PAVStream;
-//  wanted_spec,
-  //csIndex        : integer;
   stream         : TFFMpegDecodeStream;
 begin
-  result := nil;
+  Result := nil;
 
   if (not FileExists(Filename)) then
   begin
@@ -622,19 +730,24 @@ begin
   end;
 
   // open audio file
-  if (av_open_input_file(pFormatCtx, PChar(Filename), nil, 0, nil) > 0) then
+  if (av_open_input_file(pFormatCtx, PChar(Filename), nil, 0, nil) <> 0) then
     exit;
 
+  // TODO: do we need to generate PTS values if they do not exist?
+  //pFormatCtx^.flags := pFormatCtx^.flags or AVFMT_FLAG_GENPTS;
+  
   // retrieve stream information
   if (av_find_stream_info(pFormatCtx) < 0) then
     exit;
 
+  // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end
+  pFormatCtx^.pb.eof_reached := 0;
+
   {$IFDEF DebugFFMpegDecode}
   dump_format(pFormatCtx, 0, pchar(Filename), 0);
   {$ENDIF}
 
   ffmpegStreamID := FindAudioStreamIndex(pFormatCtx);
-  //Writeln('ID: ' + inttostr(ffmpegStreamID));
   if (ffmpegStreamID < 0) then
     exit;
 
@@ -656,26 +769,25 @@ begin
 
   // detect bug-workarounds automatically
   pCodecCtx^.workaround_bugs := FF_BUG_AUTODETECT;
+  // error resilience strategy (careful/compliant/agressive/very_aggressive)
+  //pCodecCtx^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT;
+  // allow non spec compliant speedup tricks.
+  //pCodecCtx^.flags2 := pCodecCtx^.flags2 or CODEC_FLAG2_FAST;
 
-  // TODO: Not sure if these fields are for audio too
-  //pCodecCtx^.lowres := lowres;
-  //if (fast) then pCodecCtx^.flags2 := pCodecCtx^.flags2 or CODEC_FLAG2_FAST;
-  //pCodecCtx^.skip_frame := skip_frame;
-  //pCodecCtx^.skip_loop_filter := skip_loop_filter;
-  //pCodecCtx^.error_resilience := error_resilience;
-  //pCodecCtx^.error_concealment := error_concealment;
-
+  // Note: avcodec_open() is not thread-safe!
   if (avcodec_open(pCodecCtx, pCodec) < 0) then
   begin
     Log.LogStatus('avcodec_open failed!', 'UAudio_FFMpeg');
     exit;
   end;
-  //WriteLn( 'Opened the codec' );
+
+  // TODO: what about pCodecCtx^.start_time? Should we seek to this position here?
+  // ...
 
   stream := TFFMpegDecodeStream.Create(pFormatCtx, pCodecCtx, pCodec,
               ffmpegStreamID, ffmpegStream);
 
-  result := stream;
+  Result := stream;
 end;
 
 
@@ -702,15 +814,30 @@ begin
   inherited;
 end;
 
+procedure TPacketQueue.Abort();
+begin
+  SDL_LockMutex(mutex);
+
+  abortRequest := true;
+
+  SDL_CondSignal(cond);
+  SDL_UnlockMutex(mutex);
+end;
+
 function TPacketQueue.Put(pkt : PAVPacket): integer;
 var
   pkt1 : PAVPacketList;
 begin
   result := -1;
 
-  if ((pkt <> @EOFPacket) and (pkt <> @FlushPacket)) then
+  if (pkt = nil) then
+    exit;
+  
+  if (PChar(pkt^.data) <> STATUS_PACKET) then
+  begin
     if (av_dup_packet(pkt) < 0) then
       exit;
+  end;
 
   pkt1 := av_malloc(sizeof(TAVPacketList));
   if (pkt1 = nil) then
@@ -719,10 +846,8 @@ begin
   pkt1^.pkt  := pkt^;
   pkt1^.next := nil;
 
-
   SDL_LockMutex(Self.mutex);
   try
-
     if (Self.lastPkt = nil) then
       Self.firstPkt := pkt1
     else
@@ -731,42 +856,58 @@ begin
     Self.lastPkt := pkt1;
     inc(Self.nbPackets);
 
-    //DebugWriteln('Put: ' + inttostr(nbPackets));
-
     Self.size := Self.size + pkt1^.pkt.size;
     SDL_CondSignal(Self.cond);
-
   finally
     SDL_UnlockMutex(Self.mutex);
   end;
 
-  result := 0;
+  Result := 0;
+end;
+
+function TPacketQueue.PutStatus(statusFlag: integer; statusInfo: Pointer): integer;
+var
+  pkt: PAVPacket;
+begin
+  // create temp. package
+  pkt := av_malloc(SizeOf(TAVPacket));
+  if (pkt = nil) then
+  begin
+    Result := -1;
+    Exit;
+  end;
+  // init package
+  av_init_packet(pkt^);
+  pkt^.data  := Pointer(STATUS_PACKET);
+  pkt^.flags := statusFlag;
+  pkt^.priv  := statusInfo;
+  // put a copy of the package into the queue
+  Result := Put(pkt);
+  // data has been copied -> delete temp. package
+  av_free(pkt);
 end;
 
 function TPacketQueue.Get(var pkt: TAVPacket; block: boolean): integer;
 var
   pkt1 : PAVPacketList;
 begin
-  result := -1;
+  Result := -1;
 
   SDL_LockMutex(Self.mutex);
   try
     while true do
     begin
-      if (quit) then
+      if (abortRequest) then
         exit;
 
       pkt1 := Self.firstPkt;
-
       if (pkt1 <> nil) then
       begin
-        Self.firstPkt := pkt1.next;
+        Self.firstPkt := pkt1^.next;
         if (Self.firstPkt = nil) then
           Self.lastPkt := nil;
         dec(Self.nbPackets);
 
-        //DebugWriteln('Get: ' + inttostr(nbPackets));
-
         Self.size := Self.size - pkt1^.pkt.size;
         pkt := pkt1^.pkt;
         av_free(pkt1);
@@ -774,8 +915,7 @@ begin
         result := 1;
         break;
       end
-      else
-      if (not block) then
+      else if (not block) then
       begin
         result := 0;
         break;
diff --git a/Game/Code/Classes/UAudioInput_Bass.pas b/Game/Code/Classes/UAudioInput_Bass.pas
index a62ff22e..db49749e 100644
--- a/Game/Code/Classes/UAudioInput_Bass.pas
+++ b/Game/Code/Classes/UAudioInput_Bass.pas
@@ -27,21 +27,32 @@ uses
 
 type
   TAudioInput_Bass = class(TAudioInputBase)
+    private
+      function EnumDevices(): boolean;
     public
       function GetName: String; override;
       function InitializeRecord: boolean; override;
-      destructor Destroy; override;
+      function FinalizeRecord: boolean; override;
   end;
 
   TBassInputDevice = class(TAudioInputDevice)
-    public
-      DeviceIndex: integer;  // index in TAudioInputProcessor.Device[]
-      BassDeviceID: integer; // DeviceID used by BASS
+    private
       RecordStream: HSTREAM;
+      BassDeviceID: DWORD; // DeviceID used by BASS
+      SingleIn: boolean;
+
+      DeviceIndex: integer;  // index in TAudioInputProcessor.Device[]
 
-      function Init(): boolean;
+      function SetInputSource(SourceIndex: integer): boolean;
+      function GetInputSource(): integer;
+    public
+      function Open(): boolean;
+      function Close(): boolean;
       function Start(): boolean; override;
-      procedure Stop();  override;
+      function Stop(): boolean;  override;
+
+      function GetVolume(): integer;        override;
+      procedure SetVolume(Volume: integer); override;
   end;
 
 var
@@ -62,83 +73,226 @@ function MicrophoneCallback(stream: HSTREAM; buffer: Pointer;
     len: Cardinal; Card: Cardinal): boolean; stdcall;
 begin
   AudioInputProcessor.HandleMicrophoneData(buffer, len,
-      AudioInputProcessor.Device[Card]);
+      AudioInputProcessor.DeviceList[Card]);
   Result := true;
 end;
 
 
 { TBassInputDevice }
 
-function TBassInputDevice.Init(): boolean;
+function TBassInputDevice.GetInputSource(): integer;
+var
+  SourceCnt: integer;
+  i: integer;
+begin
+  // get input-source config (subtract virtual device to get BASS indices)
+  SourceCnt := Length(Source)-1;
+
+  // find source
+  Result := -1;
+  for i := 0 to SourceCnt-1 do
+  begin
+    // check if current source is selected
+    if ((BASS_RecordGetInput(i) and BASS_INPUT_OFF) = 0) then
+    begin
+      // selected source found
+      Result := i;
+      Exit;
+    end;
+  end;
+end;
+
+function TBassInputDevice.SetInputSource(SourceIndex: integer): boolean;
+var
+  SourceCnt: integer;
+  i: integer;
 begin
   Result := false;
 
-  // TODO: Call once. Otherwise it's to slow
-  if not BASS_RecordInit(BassDeviceID) then
+  // check for invalid source index
+  if (SourceIndex < 0) then
+    Exit;
+
+  // get input-source config (subtract virtual device to get BASS indices)
+  SourceCnt := Length(Source)-1;
+
+  // turn on selected source (turns off the others for single-in devices)
+  if (not BASS_RecordSetInput(SourceIndex, BASS_INPUT_ON)) then
   begin
-    Log.LogError('TBassInputDevice.Start: Error initializing device['+IntToStr(DeviceIndex)+']: ' +
-                 TAudioCore_Bass.ErrorGetString());
+    Log.LogError('BASS_RecordSetInput: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Start');
     Exit;
   end;
 
+  // turn off all other sources (not needed for single-in devices)
+  if (not SingleIn) then
+  begin
+    for i := 0 to SourceCnt-1 do
+    begin
+      if (i = SourceIndex) then
+        continue;
+      // deselect source if selected
+      if ((BASS_RecordGetInput(i) and BASS_INPUT_OFF) = 0) then
+        BASS_RecordSetInput(i, BASS_INPUT_OFF);
+    end;
+  end;
+
   Result := true;
 end;
 
-{*
- * Start input-capturing on this device.
- * TODO: call BASS_RecordInit only once
- *}
-function TBassInputDevice.Start(): boolean;
+function TBassInputDevice.Open(): boolean;
 var
-  flags: Word;
+  FormatFlags: DWORD;
+  SourceIndex: integer;
 const
   latency = 20; // 20ms callback period (= latency)
 begin
   Result := false;
 
-  // recording already started -> stop first
-  if (RecordStream <> 0) then
-    Stop();
-
-  if not Init() then
+  if not BASS_RecordInit(BassDeviceID) then
+  begin
+    Log.LogError('BASS_RecordInit[device:'+IntToStr(DeviceIndex)+']: ' +
+                 TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Open');
     Exit;
+  end;
 
-  case AudioFormat.Format of
-    asfS16:   flags := 0;
-    asfFloat: flags := BASS_SAMPLE_FLOAT;
-    asfU8:    flags := BASS_SAMPLE_8BITS;
-    else begin
-      Log.LogError('Unhandled sample-format', 'TBassInputDevice.Start');
-      Exit;
-    end;
+  if (not TAudioCore_Bass.ConvertAudioFormatToBASSFlags(AudioFormat.Format, FormatFlags)) then
+  begin
+    Log.LogError('Unhandled sample-format', 'TBassInputDevice.Open');
+    Exit;
   end;
 
-  // start capturing
+  // start capturing in paused state
   RecordStream := BASS_RecordStart(Round(AudioFormat.SampleRate), AudioFormat.Channels,
-                    MakeLong(flags, latency),
+                    MakeLong(FormatFlags or BASS_RECORD_PAUSE, latency),
                     @MicrophoneCallback, DeviceIndex);
   if (RecordStream = 0) then
   begin
+    Log.LogError('BASS_RecordStart: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Open');
     BASS_RecordFree;
     Exit;
   end;
 
+  // save current source selection and select new source
+  SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+  if (SourceIndex = -1) then
+  begin
+    // nothing to do if default source is used
+    SourceRestore := -1;
+  end
+  else
+  begin
+    // store current source-index and select new source
+    SourceRestore := GetInputSource();
+    SetInputSource(SourceIndex);
+  end;
+
   Result := true;
 end;
 
-{*
- * Stop input-capturing on this device.
- *}
-procedure TBassInputDevice.Stop();
+{* Start input-capturing on this device. *}
+function TBassInputDevice.Start(): boolean;
+begin
+  Result := false;
+
+  // recording already started -> stop first
+  if (RecordStream <> 0) then
+    Stop();
+
+  // TODO: Do not open the device here (takes too much time).
+  if not Open() then
+    Exit;
+
+  if (not BASS_ChannelPlay(RecordStream, true)) then
+  begin
+    Log.LogError('BASS_ChannelPlay: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Start');
+    Exit;
+  end;
+
+  Result := true;
+end;
+
+{* Stop input-capturing on this device. *}
+function TBassInputDevice.Stop(): boolean;
 begin
+  Result := false;
+
   if (RecordStream = 0) then
     Exit;
-  // TODO: Don't free the device. Do this on close
-  if (BASS_RecordSetDevice(BassDeviceID)) then
-    BASS_RecordFree;
+  if (not BASS_RecordSetDevice(BassDeviceID)) then
+    Exit;
+
+  if (not BASS_ChannelStop(RecordStream)) then
+  begin
+    Log.LogError('BASS_ChannelStop: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Stop');
+  end;
+
+  // TODO: Do not close the device here (takes too much time).
+  Result := Close();
+end;
+
+function TBassInputDevice.Close(): boolean;
+begin
+  // restore source selection
+  if (SourceRestore >= 0) then
+  begin
+    SetInputSource(SourceRestore);
+  end;
+
+  // free data
+  if (not BASS_RecordFree()) then
+  begin
+    Log.LogError('BASS_RecordFree: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Close');
+    Result := false;
+  end
+  else
+  begin
+    Result := true;
+  end;
+
   RecordStream := 0;
 end;
 
+function TBassInputDevice.GetVolume(): integer;
+var
+  SourceIndex: integer;
+begin
+  SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+  if (SourceIndex = -1) then
+  begin
+    // if default source used find selected source
+    SourceIndex := GetInputSource();
+    if (SourceIndex = -1) then
+    begin
+      Result := 0;
+      Exit;
+    end;
+  end;
+
+  Result := LOWORD(BASS_RecordGetInput(SourceIndex));
+end;
+
+procedure TBassInputDevice.SetVolume(Volume: integer);
+var
+  SourceIndex: integer;
+begin
+  SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+  if (SourceIndex = -1) then
+  begin
+    // if default source used find selected source
+    SourceIndex := GetInputSource();
+    if (SourceIndex = -1) then
+      Exit;
+  end;
+
+  // clip volume to valid range
+  if (Volume > 100) then
+    Volume := 100
+  else if (Volume < 0) then
+    Volume := 0;
+
+  BASS_RecordSetInput(SourceIndex, BASS_INPUT_LEVEL or Volume);
+end;
+
 
 { TAudioInput_Bass }
 
@@ -147,7 +301,7 @@ begin
   result := 'BASS_Input';
 end;
 
-function TAudioInput_Bass.InitializeRecord(): boolean;
+function TAudioInput_Bass.EnumDevices(): boolean;
 var
   Descr:      PChar;
   SourceName: PChar;
@@ -157,12 +311,13 @@ var
   DeviceIndex:  integer;
   SourceIndex:  integer;
   RecordInfo: BASS_RECORDINFO;
+  SelectedSourceIndex: integer;
 begin
   result := false;
 
   DeviceIndex := 0;
   BassDeviceID := 0;
-  SetLength(AudioInputProcessor.Device, 0);
+  SetLength(AudioInputProcessor.DeviceList, 0);
 
   // checks for recording devices and puts them into an array
   while true do
@@ -171,7 +326,7 @@ begin
     if (Descr = nil) then
       break;
 
-    // try to intialize the device
+    // try to initialize the device
     if not BASS_RecordInit(BassDeviceID) then
     begin
       Log.LogStatus('Failed to initialize BASS Capture-Device['+inttostr(BassDeviceID)+']',
@@ -179,26 +334,25 @@ begin
     end
     else
     begin
-      SetLength(AudioInputProcessor.Device, DeviceIndex+1);
+      SetLength(AudioInputProcessor.DeviceList, DeviceIndex+1);
 
       // TODO: free object on termination
       BassDevice := TBassInputDevice.Create();
-      AudioInputProcessor.Device[DeviceIndex] := BassDevice;
+      AudioInputProcessor.DeviceList[DeviceIndex] := BassDevice;
 
       BassDevice.DeviceIndex := DeviceIndex;
       BassDevice.BassDeviceID := BassDeviceID;
-      BassDevice.Description := UnifyDeviceName(Descr, DeviceIndex);
+      BassDevice.Name := UnifyDeviceName(Descr, DeviceIndex);
 
       // retrieve recording device info
       BASS_RecordGetInfo(RecordInfo);
 
-      // FIXME: does BASS use LSB/MSB or system integer values for 16bit?
-
       // check if BASS has capture-freq. info
       if (RecordInfo.freq > 0) then
       begin
         // use current input sample rate (available only on Windows Vista and OSX).
         // Recording at this rate will give the best quality and performance, as no resampling is required.
+        // FIXME: does BASS use LSB/MSB or system integer values for 16bit?
         BassDevice.AudioFormat := TAudioFormatInfo.Create(2, RecordInfo.freq, asfS16)
       end
       else
@@ -209,36 +363,59 @@ begin
         BassDevice.AudioFormat := TAudioFormatInfo.Create(2, 44100, asfS16)
       end;
 
+      // get info if multiple input-sources can be selected at once
+      BassDevice.SingleIn := RecordInfo.singlein;
+
+      // init list for capture buffers per channel
       SetLength(BassDevice.CaptureChannel, BassDevice.AudioFormat.Channels);
 
-      // get input sources
-      SourceIndex := 0;
-      BassDevice.MicSource := 0;
+      BassDevice.MicSource := -1;
+      BassDevice.SourceRestore := -1;
+
+      // add a virtual default source (will not change mixer-settings)
+      SetLength(BassDevice.Source, 1);
+      BassDevice.Source[0].Name := DEFAULT_SOURCE_NAME;
+
+      // add real input sources
+      SourceIndex := 1;
 
       // process each input
       while true do
       begin
-        SourceName := BASS_RecordGetInputName(SourceIndex);
-		{$IFDEF DARWIN}
-		  // Patch for SingStar USB-Microphones:
-	      if ((SourceName = nil) and (SourceIndex = 0) and (Pos('Serial#', Descr) > 0)) then
-			SourceName := 'Microphone'
-		  else
-            break;
-		{$ELSE}
-	      if (SourceName = nil) then
-            break;
-		{$ENDIF}
-
-        SetLength(BassDevice.Source, SourceIndex+1);
-        BassDevice.Source[SourceIndex].Name :=
-          UnifyDeviceSourceName(SourceName, BassDevice.Description);
-
-        // set mic index
+        SourceName := BASS_RecordGetInputName(SourceIndex-1);
+
+        {$IFDEF DARWIN}
+        // Under MacOSX the SingStar Mics have an empty InputName.
+        // So, we have to add a hard coded Workaround for this problem
+        // FIXME: - Do we need this anymore? Doesn't the (new) default source already solve this problem?
+        //        - Normally a nil return value of BASS_RecordGetInputName() means end-of-list, so maybe
+        //          BASS is not able to detect any mic-sources (the default source will work then).
+        //        - Does BASS_RecordGetInfo() return true or false? If it returns true in this case
+        //          we could use this value to check if the device exists.
+        //          Please check that, eddie.
+        //          If it returns false, then the source is not detected and it does not make sense to add a second
+        //          fake device here.
+        //          What about BASS_RecordGetInput()? Does it return a value <> -1?
+        //        - Does it even work at all with this fake source-index, now that input switching works?
+        //          This info was not used before (sources were never switched), so it did not matter what source-index was used.
+        //          But now BASS_RecordSetInput() will probably fail.
+        if ((SourceName = nil) and (SourceIndex = 1) and (Pos('USBMIC Serial#', Descr) > 0)) then
+          SourceName := 'Microphone'
+        {$ENDIF}
+        
+        if (SourceName = nil) then
+          break;
+
+        SetLength(BassDevice.Source, Length(BassDevice.Source)+1);
+        BassDevice.Source[SourceIndex].Name := SourceName;
+
+        // get input-source info
         Flags := BASS_RecordGetInput(SourceIndex);
-        if ((Flags <> -1) and ((Flags and BASS_INPUT_TYPE_MIC) <> 0)) then
+        if (Flags <> -1) then
         begin
-          BassDevice.MicSource := SourceIndex;
+          // is the current source a mic-source?
+          if ((Flags and BASS_INPUT_TYPE_MIC) <> 0) then
+            BassDevice.MicSource := SourceIndex;
         end;
 
         Inc(SourceIndex);
@@ -250,16 +427,22 @@ begin
 
       Inc(DeviceIndex);
     end;
-    
+
     Inc(BassDeviceID);
   end;
 
   result := true;
 end;
 
-destructor TAudioInput_Bass.Destroy;
+function TAudioInput_Bass.InitializeRecord(): boolean;
+begin
+  Result := EnumDevices();
+end;
+
+function TAudioInput_Bass.FinalizeRecord(): boolean;
 begin
-  inherited;
+  CaptureStop;
+  Result := inherited FinalizeRecord;
 end;
 
 
diff --git a/Game/Code/Classes/UAudioInput_Portaudio.pas b/Game/Code/Classes/UAudioInput_Portaudio.pas
index 90ac41b1..183c482d 100644
--- a/Game/Code/Classes/UAudioInput_Portaudio.pas
+++ b/Game/Code/Classes/UAudioInput_Portaudio.pas
@@ -29,19 +29,30 @@ uses
 
 type
   TAudioInput_Portaudio = class(TAudioInputBase)
+    private
+      AudioCore: TAudioCore_Portaudio;
+      function EnumDevices(): boolean;
     public
       function GetName: String; override;
       function InitializeRecord: boolean; override;
-      destructor Destroy; override;
+      function FinalizeRecord: boolean; override;
   end;
 
   TPortaudioInputDevice = class(TAudioInputDevice)
-    public
-      RecordStream:   PPaStream;
+    private
+      RecordStream: PPaStream;
+      {$IFDEF UsePortmixer}
+      Mixer: PPxMixer;
+      {$ENDIF}
       PaDeviceIndex:  TPaDeviceIndex;
-
+    public
+      function Open(): boolean;
+      function Close(): boolean;
       function Start(): boolean; override;
-      procedure Stop();  override;
+      function Stop(): boolean;  override;
+
+      function GetVolume(): integer;        override;
+      procedure SetVolume(Volume: integer); override;
   end;
 
 function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
@@ -58,12 +69,12 @@ var
 
 { TPortaudioInputDevice }
 
-function TPortaudioInputDevice.Start(): boolean;
+function TPortaudioInputDevice.Open(): boolean;
 var
   Error:       TPaError;
-  ErrorMsg:    string;
   inputParams: TPaStreamParameters;
   deviceInfo:  PPaDeviceInfo;
+  SourceIndex: integer;
 begin
   Result := false;
 
@@ -90,35 +101,148 @@ begin
       @MicrophoneCallback, Pointer(Self));
   if(Error <> paNoError) then
   begin
-    ErrorMsg := Pa_GetErrorText(Error);
-    Log.LogError('Error opening stream: ' + ErrorMsg, 'TPortaudioInputDevice.Start');
+    Log.LogError('Error opening stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Open');
     Exit;
   end;
 
+  {$IFDEF UsePortmixer}
+    // open default mixer
+    Mixer := Px_OpenMixer(RecordStream, 0);
+    if (Mixer = nil) then
+    begin
+      Log.LogError('Error opening mixer: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Open');
+    end
+    else
+    begin
+      // save current source selection and select new source
+      SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+      if (SourceIndex = -1) then
+      begin
+        // nothing to do if default source is used
+        SourceRestore := -1;
+      end
+      else
+      begin
+        // store current source-index and select new source
+        SourceRestore := Px_GetCurrentInputSource(Mixer); // -1 in error case
+        Px_SetCurrentInputSource(Mixer, SourceIndex);
+      end;
+    end;
+  {$ENDIF}
+
+  Result := true;
+end;
+
+function TPortaudioInputDevice.Start(): boolean;
+var
+  Error:       TPaError;
+begin
+  Result := false;
+
+  // recording already started -> stop first
+  if (RecordStream <> nil) then
+    Stop();
+
+  // TODO: Do not open the device here (takes too much time).
+  if (not Open()) then
+    Exit;
+
   // start capture
   Error := Pa_StartStream(RecordStream);
   if(Error <> paNoError) then
   begin
-    Pa_CloseStream(RecordStream);
-    ErrorMsg := Pa_GetErrorText(Error);
-    Log.LogError('Error starting stream: ' + ErrorMsg, 'TPortaudioInputDevice.Start');
+    Log.LogError('Error starting stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Start');
+    Close();
+    RecordStream := nil;
     Exit;
   end;
-  
+
   Result := true;
 end;
 
-procedure TPortaudioInputDevice.Stop();
+function TPortaudioInputDevice.Stop(): boolean;
+var
+  Error: TPaError;
 begin
-  if assigned(RecordStream) then
+  Result := false;
+
+  if (RecordStream = nil) then
+    Exit;
+
+  // Note: do NOT call Pa_StopStream here!
+  // It gets stuck on devices with non-working callback as Pa_StopStream
+  // waits until all buffers have been handled (which never occurs in that case).
+  Error := Pa_AbortStream(RecordStream);
+  if (Error <> paNoError) then
   begin
-    // Note: do NOT call Pa_StopStream here!
-    // It gets stuck on devices with non-working callback as Pa_StopStream
-    // waits until all buffers have been handled (which never occurs in that
-    // case).
-    // Pa_CloseStream internally calls Pa_AbortStream which works as expected.
-    Pa_CloseStream(RecordStream);
+    Log.LogError('Pa_AbortStream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Stop');
   end;
+
+  Result := Close();
+end;
+
+function TPortaudioInputDevice.Close(): boolean;
+var
+  Error: TPaError;
+begin
+  {$IFDEF UsePortmixer}
+    if (Mixer <> nil) then
+    begin
+      // restore source selection
+      if (SourceRestore >= 0) then
+      begin
+        Px_SetCurrentInputSource(Mixer, SourceRestore);
+      end;
+
+      // close mixer
+      Px_CloseMixer(Mixer);
+      Mixer := nil;
+    end;
+  {$ENDIF}
+
+  Error := Pa_CloseStream(RecordStream);
+  if (Error <> paNoError) then
+  begin
+    Log.LogError('Pa_CloseStream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Close');
+    Result := false;
+  end
+  else
+  begin
+    Result := true;
+  end;
+
+  RecordStream := nil;
+end;
+
+function TPortaudioInputDevice.GetVolume(): integer;
+begin
+  Result := 0;
+  {$IFDEF UsePortmixer}
+    if (Mixer <> nil) then
+    begin
+      Result := Round(Px_GetInputVolume(Mixer) * 100);
+      // clip to valid range
+      if (Result > 100) then
+        Result := 100
+      else if (Result < 0) then
+        Result := 0;
+    end;
+  {$ENDIF}
+end;
+
+procedure TPortaudioInputDevice.SetVolume(Volume: integer);
+begin
+  {$IFDEF UsePortmixer}
+    if (Mixer <> nil) then
+    begin
+      // clip to valid range
+      if (Volume > 100) then
+        Volume := 100
+      else if (Volume < 0) then
+        Volume := 0;
+      Px_SetInputVolume(Mixer, Volume / 100);
+    end;
+  {$ENDIF}
 end;
 
 
@@ -129,7 +253,7 @@ begin
   result := 'Portaudio';
 end;
 
-function TAudioInput_Portaudio.InitializeRecord(): boolean;
+function TAudioInput_Portaudio.EnumDevices(): boolean;
 var
   i:           integer;
   paApiIndex:  TPaHostApiIndex;
@@ -148,29 +272,21 @@ var
   sampleRate:  double;
   latency:     TPaTime;
   {$IFDEF UsePortmixer}
-  sourceIndex: integer;
   mixer:       PPxMixer;
   sourceCnt:   integer;
+  sourceIndex: integer;
   sourceName:  string;
   {$ENDIF}
   cbPolls: integer;
   cbWorks: boolean;
 begin
-  result := false;
-
-  // initialize portaudio
-  err := Pa_Initialize();
-  if(err <> paNoError) then
-  begin
-    Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord');
-    Exit;
-  end;
+  Result := false;
 
   // choose the best available Audio-API
-  paApiIndex := TAudioCore_Portaudio.GetPreferredApiIndex();
+  paApiIndex := AudioCore.GetPreferredApiIndex();
   if(paApiIndex = -1) then
   begin
-    Log.LogError('No working Audio-API found', 'TAudioInput_Portaudio.InitializeRecord');
+    Log.LogError('No working Audio-API found', 'TAudioInput_Portaudio.EnumDevices');
     Exit;
   end;
 
@@ -179,8 +295,8 @@ begin
   SC := 0;
 
   // init array-size to max. input-devices count
-  SetLength(AudioInputProcessor.Device, paApiInfo^.deviceCount);
-  for i:= 0 to High(AudioInputProcessor.Device) do
+  SetLength(AudioInputProcessor.DeviceList, paApiInfo^.deviceCount);
+  for i:= 0 to High(AudioInputProcessor.DeviceList) do
   begin
     // convert API-specific device-index to global index
     deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
@@ -199,11 +315,11 @@ begin
       channelCnt := 2;
 
     paDevice := TPortaudioInputDevice.Create();
-    AudioInputProcessor.Device[SC] := paDevice;
+    AudioInputProcessor.DeviceList[SC] := paDevice;
 
     // retrieve device-name
     deviceName := deviceInfo^.name;
-    paDevice.Description := deviceName;
+    paDevice.Name := deviceName;
     paDevice.PaDeviceIndex := deviceIndex;
 
     sampleRate := deviceInfo^.defaultSampleRate;
@@ -213,6 +329,8 @@ begin
     latency := deviceInfo^.defaultLowInputLatency;
 
     // setup desired input parameters
+    // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
+    //       not be set correctly in OSS)
     with inputParams do
     begin
       device := deviceIndex;
@@ -223,10 +341,10 @@ begin
     end;
 
     // check souncard and adjust sample-rate
-    if (not TAudioCore_Portaudio.TestDevice(@inputParams, nil, sampleRate)) then
+    if (not AudioCore.TestDevice(@inputParams, nil, sampleRate)) then
     begin
       // ignore device if it does not work
-      Log.LogError('Device "'+paDevice.Description+'" does not work',
+      Log.LogError('Device "'+paDevice.Name+'" does not work',
                    'TAudioInput_Portaudio.EnumDevices');
       paDevice.Free();
       continue;
@@ -266,11 +384,19 @@ begin
     );
     SetLength(paDevice.CaptureChannel, paDevice.AudioFormat.Channels);
 
-    Log.LogStatus('InputDevice "'+paDevice.Description+'"@' +
+    Log.LogStatus('InputDevice "'+paDevice.Name+'"@' +
         IntToStr(paDevice.AudioFormat.Channels)+'x'+
         FloatToStr(paDevice.AudioFormat.SampleRate)+'Hz ('+
         FloatTostr(inputParams.suggestedLatency)+'sec)' ,
-        'Portaudio.InitializeRecord');
+        'Portaudio.EnumDevices');
+
+    // portaudio does not provide a source-type check
+    paDevice.MicSource := -1;
+    paDevice.SourceRestore := -1;
+
+    // add a virtual default source (will not change mixer-settings)
+    SetLength(paDevice.Source, 1);
+    paDevice.Source[0].Name := DEFAULT_SOURCE_NAME;
 
     {$IFDEF UsePortmixer}
       // use default mixer
@@ -278,52 +404,55 @@ begin
 
       // get input count
       sourceCnt := Px_GetNumInputSources(mixer);
-      SetLength(paDevice.Source, sourceCnt);
+      SetLength(paDevice.Source, sourceCnt+1);
 
       // get input names
-      for sourceIndex := 0 to sourceCnt-1 do
+      for sourceIndex := 1 to sourceCnt do
       begin
-        sourceName := Px_GetInputSourceName(mixer, sourceIndex);
+        sourceName := Px_GetInputSourceName(mixer, sourceIndex-1);
         paDevice.Source[sourceIndex].Name := sourceName;
       end;
 
       Px_CloseMixer(mixer);
-    {$ELSE} // not UsePortmixer
-      // create a standard input source
-      SetLength(paDevice.Source, 1);
-      paDevice.Source[0].Name := 'Standard';
     {$ENDIF}
 
     // close test-stream
     Pa_CloseStream(stream);
 
-    // use default input source
-    paDevice.SourceSelected := 0;
-
     Inc(SC);
   end;
 
   // adjust size to actual input-device count
-  SetLength(AudioInputProcessor.Device, SC);
+  SetLength(AudioInputProcessor.DeviceList, SC);
 
-  Log.LogStatus('#Soundcards: ' + inttostr(SC), 'Portaudio');
+  Log.LogStatus('#Input-Devices: ' + inttostr(SC), 'Portaudio');
 
-  result := true;
+  Result := true;
 end;
 
-destructor TAudioInput_Portaudio.Destroy;
+function TAudioInput_Portaudio.InitializeRecord(): boolean;
 var
-  i: integer;
-  //paSoundCard: TPortaudioInputDevice;
+  err: TPaError;
 begin
-  Pa_Terminate();
-  for i := 0 to High(AudioInputProcessor.Device) do
+  AudioCore := TAudioCore_Portaudio.GetInstance();
+
+  // initialize portaudio
+  err := Pa_Initialize();
+  if(err <> paNoError) then
   begin
-    AudioInputProcessor.Device[i].Free();
+    Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord');
+    Result := false;
+    Exit;
   end;
-  AudioInputProcessor.Device := nil;
 
-  inherited Destroy;
+  Result := EnumDevices();
+end;
+
+function TAudioInput_Portaudio.FinalizeRecord: boolean;
+begin
+  CaptureStop;
+  Pa_Terminate();
+  Result := inherited FinalizeRecord();
 end;
 
 {*
diff --git a/Game/Code/Classes/UAudioPlaybackBase.pas b/Game/Code/Classes/UAudioPlaybackBase.pas
new file mode 100644
index 00000000..f1fe26f9
--- /dev/null
+++ b/Game/Code/Classes/UAudioPlaybackBase.pas
@@ -0,0 +1,220 @@
+unit UAudioPlaybackBase;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  UMusic;
+
+type
+  TAudioPlaybackBase = class(TInterfacedObject, IAudioPlayback)
+    protected
+      OutputDeviceList: TAudioOutputDeviceList;
+      MusicStream: TAudioPlaybackStream;
+      // open sound or music stream (used by Open() and OpenSound())
+      function OpenStream(const Filename: string): TAudioPlaybackStream; virtual; abstract;
+      procedure ClearOutputDeviceList();
+    public
+      function GetName: String; virtual; abstract;
+
+      function  Open(const Filename: string): boolean; // true if succeed
+      procedure Close;
+
+      procedure Play;
+      procedure Pause;
+      procedure Stop;
+      procedure FadeIn(Time: real; TargetVolume: integer);
+
+      procedure SetPosition(Time: real);
+      function  GetPosition: real;
+
+      function InitializePlayback: boolean; virtual; abstract;
+      function FinalizePlayback: boolean; virtual;
+
+      //      function SetOutputDevice(Device: TAudioOutputDevice): boolean;
+      function GetOutputDeviceList(): TAudioOutputDeviceList;
+
+      procedure SetAppVolume(Volume: integer); virtual; abstract;
+      procedure SetVolume(Volume: integer);
+      procedure SetLoop(Enabled: boolean);
+
+      procedure Rewind;
+      function  Finished: boolean;
+      function  Length: real;
+
+      // Sounds
+      function OpenSound(const Filename: String): TAudioPlaybackStream;
+      procedure PlaySound(stream: TAudioPlaybackStream);
+      procedure StopSound(stream: TAudioPlaybackStream);
+
+      // Equalizer
+      procedure GetFFTData(var data: TFFTData);
+
+      // Interface for Visualizer
+      function GetPCMData(var data: TPCMData): Cardinal;
+  end;
+
+
+implementation
+
+uses
+  SysUtils;
+
+{ TAudioPlaybackBase }
+
+function TAudioPlaybackBase.FinalizePlayback: boolean;
+begin
+  FreeAndNil(MusicStream);
+  ClearOutputDeviceList();
+end;
+
+function TAudioPlaybackBase.Open(const Filename: string): boolean;
+begin
+  // free old MusicStream
+  MusicStream.Free;
+
+  MusicStream := OpenStream(Filename);
+  if not assigned(MusicStream) then
+  begin
+    Result := false;
+    Exit;
+  end;
+
+  //MusicStream.AddSoundEffect(TVoiceRemoval.Create());
+
+  Result := true;
+end;
+
+procedure TAudioPlaybackBase.Close;
+begin
+  if assigned(MusicStream) then
+    MusicStream.Close();
+end;
+
+procedure TAudioPlaybackBase.Play;
+begin
+  if assigned(MusicStream) then
+    MusicStream.Play();
+end;
+
+procedure TAudioPlaybackBase.Pause;
+begin
+  if assigned(MusicStream) then
+    MusicStream.Pause();
+end;
+
+procedure TAudioPlaybackBase.Stop;
+begin
+  if assigned(MusicStream) then
+    MusicStream.Stop();
+end;
+
+function TAudioPlaybackBase.Length: real;
+begin
+  if assigned(MusicStream) then
+    Result := MusicStream.Length
+  else
+    Result := 0;
+end;
+
+function TAudioPlaybackBase.GetPosition: real;
+begin
+  if assigned(MusicStream) then
+    Result := MusicStream.Position
+  else
+    Result := 0;
+end;
+
+procedure TAudioPlaybackBase.SetPosition(Time: real);
+begin
+  if assigned(MusicStream) then
+    MusicStream.Position := Time;
+end;
+
+procedure TAudioPlaybackBase.Rewind;
+begin
+  SetPosition(0);
+end;
+
+function TAudioPlaybackBase.Finished: boolean;
+begin
+  if assigned(MusicStream) then
+    Result := (MusicStream.Status = ssStopped)
+  else
+    Result := true;
+end;
+
+procedure TAudioPlaybackBase.SetVolume(Volume: Integer);
+begin
+  if assigned(MusicStream) then
+    MusicStream.Volume := Volume;
+end;
+
+procedure TAudioPlaybackBase.FadeIn(Time: real; TargetVolume: integer);
+begin
+  if assigned(MusicStream) then
+    MusicStream.FadeIn(Time, TargetVolume);
+end;
+
+procedure TAudioPlaybackBase.SetLoop(Enabled: boolean);
+begin
+  if assigned(MusicStream) then
+    MusicStream.Loop := Enabled;
+end;
+
+// Equalizer
+procedure TAudioPlaybackBase.GetFFTData(var data: TFFTData);
+begin
+  if assigned(MusicStream) then
+    MusicStream.GetFFTData(data);
+end;
+
+{*
+ * Copies interleaved PCM SInt16 stereo samples into data.
+ * Returns the number of frames
+ *}
+function TAudioPlaybackBase.GetPCMData(var data: TPCMData): Cardinal;
+begin
+  if assigned(MusicStream) then
+    Result := MusicStream.GetPCMData(data)
+  else
+    Result := 0;
+end;
+
+function TAudioPlaybackBase.OpenSound(const Filename: string): TAudioPlaybackStream;
+begin
+  Result := OpenStream(Filename);
+end;
+
+procedure TAudioPlaybackBase.PlaySound(stream: TAudioPlaybackStream);
+begin
+  if assigned(stream) then
+    stream.Play();
+end;
+
+procedure TAudioPlaybackBase.StopSound(stream: TAudioPlaybackStream);
+begin
+  if assigned(stream) then
+    stream.Stop();
+end;
+
+procedure TAudioPlaybackBase.ClearOutputDeviceList();
+var
+  DeviceIndex: integer;
+begin
+  for DeviceIndex := 0 to High(OutputDeviceList) do
+    OutputDeviceList[DeviceIndex].Free();
+  SetLength(OutputDeviceList, 0);
+end;
+
+function TAudioPlaybackBase.GetOutputDeviceList(): TAudioOutputDeviceList;
+begin
+  Result := OutputDeviceList;
+end;
+
+end.
diff --git a/Game/Code/Classes/UAudioPlayback_Bass.pas b/Game/Code/Classes/UAudioPlayback_Bass.pas
index d47990a8..0fca9c72 100644
--- a/Game/Code/Classes/UAudioPlayback_Bass.pas
+++ b/Game/Code/Classes/UAudioPlayback_Bass.pas
@@ -8,36 +8,40 @@ interface
 
 {$I switches.inc}
 
+implementation
 
 uses
   Classes,
   SysUtils,
-  UMusic;
-
-implementation
-
-uses
   UIni,
   UMain,
-  ULog,
+  UMusic,
+  UAudioPlaybackBase,
   UAudioCore_Bass,
+  ULog,
   bass;
 
 type
+  PHDSP = ^HDSP;
+
+type
+  // Playback-stream decoded internally by BASS
   TBassPlaybackStream = class(TAudioPlaybackStream)
     private
       Handle: HSTREAM;
-      Loop:   boolean;
     public
-      constructor Create(); overload;
-      constructor Create(stream: HSTREAM); overload;
+      constructor Create(stream: HSTREAM);
+      destructor Destroy(); override;
 
       procedure Reset();
 
       procedure Play();                     override;
       procedure Pause();                    override;
       procedure Stop();                     override;
+      procedure FadeIn(Time: real; TargetVolume: integer); override;
+
       procedure Close();                    override;
+
       function GetLoop(): boolean;          override;
       procedure SetLoop(Enabled: boolean);  override;
       function GetLength(): real;           override;
@@ -45,79 +49,95 @@ type
       function GetVolume(): integer;        override;
       procedure SetVolume(volume: integer); override;
 
-      function GetPosition: real;
-      procedure SetPosition(Time: real);
+      procedure AddSoundEffect(effect: TSoundEffect);    override;
+      procedure RemoveSoundEffect(effect: TSoundEffect); override;
+
+      function GetPosition: real;           override;
+      procedure SetPosition(Time: real);    override;
 
-      function IsLoaded(): boolean;
+      procedure GetFFTData(var data: TFFTData);           override;
+      function  GetPCMData(var data: TPCMData): Cardinal; override;
   end;
 
-type
-  TAudioPlayback_Bass = class( TInterfacedObject, IAudioPlayback)
+  // Playback-stream decoded by an external decoder e.g. FFmpeg
+  TBassExtDecoderPlaybackStream = class(TBassPlaybackStream)
     private
-      MusicStream: TBassPlaybackStream;
-
-      function Load(const Filename: string): TBassPlaybackStream;
+      DecodeStream: TAudioDecodeStream;
     public
-      function  GetName: String;
-
-      {IAudioOutput interface}
-
-      function InitializePlayback(): boolean;
-      procedure SetVolume(Volume: integer);
-      procedure SetMusicVolume(Volume: integer);
-      procedure SetLoop(Enabled: boolean);
-
-      function Open(const Filename: string): boolean; // true if succeed
-
-      procedure Rewind;
-      procedure Play;
-      procedure Pause; //Pause Mod
-      procedure Stop;
-      procedure Close;
-      function Finished: boolean;
-      function Length: real;
-      function GetPosition: real;
-      procedure SetPosition(Time: real);
+      procedure Stop();                     override;
+      procedure Close();                    override;
+      function GetLength(): real;           override;
+      function GetPosition: real;           override;
+      procedure SetPosition(Time: real);    override;
 
-      //Equalizer
-      procedure GetFFTData(var data: TFFTData);
+      function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
+  end;
 
-      // Interface for Visualizer
-      function GetPCMData(var data: TPCMData): Cardinal;
+type
+  TAudioPlayback_Bass = class(TAudioPlaybackBase)
+    private
+      function EnumDevices(): boolean;
+    protected
+      function OpenStream(const Filename: string): TAudioPlaybackStream; override;
+    public
+      function GetName: String; override;
+      function InitializePlayback(): boolean; override;
+      function FinalizePlayback: boolean; override;
+      procedure SetAppVolume(Volume: integer); override;
+  end;
 
-      // Sounds
-      function OpenSound(const Filename: String): TAudioPlaybackStream;
-      procedure PlaySound(stream: TAudioPlaybackStream);
-      procedure StopSound(stream: TAudioPlaybackStream);
+  TBassOutputDevice = class(TAudioOutputDevice)
+    private
+      BassDeviceID: DWORD; // DeviceID used by BASS
   end;
 
 var
   singleton_AudioPlaybackBass : IAudioPlayback;
 
 
-constructor TBassPlaybackStream.Create();
+{ TBassPlaybackStream }
+
+constructor TBassPlaybackStream.Create(stream: HSTREAM);
 begin
-  inherited;
+  inherited Create();
   Reset();
+  Handle := stream;
 end;
 
-constructor TBassPlaybackStream.Create(stream: HSTREAM);
+destructor TBassPlaybackStream.Destroy();
 begin
-  Create();
-  Handle := stream;
+  Close();
+  inherited;
 end;
 
 procedure TBassPlaybackStream.Reset();
 begin
-  Loop := false;
   if (Handle <> 0) then
+  begin
     Bass_StreamFree(Handle);
+  end;
   Handle := 0;
 end;
 
 procedure TBassPlaybackStream.Play();
+var
+  restart: boolean;
 begin
-  BASS_ChannelPlay(Handle, Loop);
+  if (BASS_ChannelIsActive(Handle) = BASS_ACTIVE_PAUSED) then
+    restart := false // resume from last position
+  else
+    restart := true; // start from the beginning
+
+  BASS_ChannelPlay(Handle, restart);
+end;
+
+procedure TBassPlaybackStream.FadeIn(Time: real; TargetVolume: integer);
+begin
+  // start stream
+  BASS_ChannelPlay(Handle, true);
+
+  // start fade-in: slide from fadeStart- to fadeEnd-volume in FadeInTime
+  BASS_ChannelSlideAttributes(Handle, -1, TargetVolume, -101, Trunc(Time * 1000));
 end;
 
 procedure TBassPlaybackStream.Pause();
@@ -136,9 +156,11 @@ begin
 end;
 
 function TBassPlaybackStream.GetVolume(): integer;
+var
+  volume: cardinal;
 begin
-  Result := 0;
-  BASS_ChannelSetAttributes(Handle, PInteger(nil)^, Result, PInteger(nil)^);
+  BASS_ChannelGetAttributes(Handle, PCardinal(nil)^, volume, PInteger(nil)^);
+  Result := volume;
 end;
 
 procedure TBassPlaybackStream.SetVolume(volume: integer);
@@ -168,19 +190,9 @@ begin
   BASS_ChannelSetPosition(Handle, bytes);
 end;
 
-function TBassPlaybackStream.GetLoop(): boolean;
-begin
-  result := Loop;
-end;
-
-procedure TBassPlaybackStream.SetLoop(Enabled: boolean);
-begin
-  Loop := Enabled;
-end;
-
 function TBassPlaybackStream.GetLength(): real;
 var
-  bytes:    integer;
+  bytes: integer;
 begin
   bytes  := BASS_ChannelGetLength(Handle);
   Result := BASS_ChannelBytes2Seconds(Handle, bytes);
@@ -205,212 +217,367 @@ begin
   end;
 end;
 
-function TBassPlaybackStream.IsLoaded(): boolean;
-begin
-  Result := (Handle <> 0);
-end;
-
-
-function  TAudioPlayback_Bass.GetName: String;
+function TBassPlaybackStream.GetLoop(): boolean;
+var
+  info: BASS_CHANNELINFO;
 begin
-  result := 'BASS_Playback';
+  if not BASS_ChannelGetInfo(Handle, info) then
+  begin
+    Log.LogError('BASS_ChannelGetInfo: ' + TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.GetLoop');
+    Result := false;
+    Exit;
+  end;
+  Result := (info.flags and BASS_SAMPLE_LOOP) <> 0;
 end;
 
-function TAudioPlayback_Bass.InitializePlayback(): boolean;
+procedure TBassPlaybackStream.SetLoop(Enabled: boolean);
 var
-  Pet:  integer;
-  S:    integer;
+  info: BASS_CHANNELINFO;
 begin
-  result := false;
-
-  //Log.BenchmarkStart(4);
-  //Log.LogStatus('Initializing Playback Subsystem', 'Music Initialize');
-
-  if not BASS_Init(1, 44100, 0, 0, nil) then
+  // retrieve old flag-bits
+  if not BASS_ChannelGetInfo(Handle, info) then
   begin
-    Log.LogError('Could not initialize BASS', 'Error');
+    Log.LogError('BASS_ChannelGetInfo:' + TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.SetLoop');
     Exit;
   end;
 
-  //Log.BenchmarkEnd(4); Log.LogBenchmark('--> Bass Init', 4);
+  // set/unset loop-flag
+  if (Enabled) then
+    info.flags := info.flags or BASS_SAMPLE_LOOP
+  else
+    info.flags := info.flags and not BASS_SAMPLE_LOOP;
 
-  // config playing buffer
-  //BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
-  //BASS_SetConfig(BASS_CONFIG_BUFFER, 100);
+  // set new flag-bits
+  if not BASS_ChannelSetFlags(Handle, info.flags) then
+  begin
+    Log.LogError('BASS_ChannelSetFlags: ' + TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.SetLoop');
+    Exit;
+  end;
+end;
 
-  result := true;
+procedure DSPProcHandler(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
+var
+  effect: TSoundEffect;
+begin
+  effect := TSoundEffect(user);
+  if assigned(effect) then
+    effect.Callback(buffer, length);
 end;
 
-function TAudioPlayback_Bass.Load(const Filename: string): TBassPlaybackStream;
+procedure TBassPlaybackStream.AddSoundEffect(effect: TSoundEffect);
 var
-  L: Integer;
-  stream: HSTREAM;
+  dspHandle: HDSP;
 begin
-  Result := nil;
+  if assigned(effect.engineData) then
+  begin
+    Log.LogError('TSoundEffect.engineData already set', 'TBassPlaybackStream.AddSoundEffect');
+    Exit;
+  end;
 
-  //Log.LogStatus('Loading Sound: "' + Filename + '"', 'LoadSoundFromFile');
-  stream := BASS_StreamCreateFile(False, pchar(Filename), 0, 0, 0);
-  if (stream = 0) then
+  // FIXME: casting of a pointer to Uint32 will fail on 64bit systems
+  dspHandle := BASS_ChannelSetDSP(Handle, @DSPProcHandler, DWORD(effect), 0);
+  if (dspHandle = 0) then
   begin
-    Log.LogError('Failed to open "' + Filename + '", ' +
-                 TAudioCore_Bass.ErrorGetString(BASS_ErrorGetCode()), 'TAudioPlayback_Bass.Load');
+    Log.LogError(TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.AddSoundEffect');
     Exit;
   end;
 
-  Result := TBassPlaybackStream.Create(stream);
+  GetMem(effect.engineData, SizeOf(HDSP));
+  PHDSP(effect.engineData)^ := dspHandle;
 end;
 
-procedure TAudioPlayback_Bass.SetVolume(Volume: integer);
+procedure TBassPlaybackStream.RemoveSoundEffect(effect: TSoundEffect);
 begin
-  //Old Sets Wave Volume
-  //BASS_SetVolume(Volume);
-  //New: Sets Volume only for this Application
-  BASS_SetConfig(BASS_CONFIG_GVOL_SAMPLE, Volume);
-  BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Volume);
-  BASS_SetConfig(BASS_CONFIG_GVOL_MUSIC, Volume);
-end;
+  if not assigned(effect.EngineData) then
+  begin
+    Log.LogError('TSoundEffect.engineData invalid', 'TBassPlaybackStream.RemoveSoundEffect');
+    Exit;
+  end;
 
-procedure TAudioPlayback_Bass.SetMusicVolume(Volume: Integer);
-begin
-  if assigned(MusicStream) then
-    MusicStream.SetVolume(Volume);
+  if not BASS_ChannelRemoveDSP(Handle, PHDSP(effect.EngineData)^) then
+  begin
+    Log.LogError(TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.RemoveSoundEffect');
+    Exit;
+  end;
+
+  FreeMem(effect.engineData);
+  effect.engineData := nil;
 end;
 
-procedure TAudioPlayback_Bass.SetLoop(Enabled: boolean);
+procedure TBassPlaybackStream.GetFFTData(var data: TFFTData);
 begin
-  if assigned(MusicStream) then
-    MusicStream.Loop := Enabled;
+  // Get Channel Data Mono and 256 Values
+  BASS_ChannelGetData(Handle, @data, BASS_DATA_FFT512);
 end;
 
-function TAudioPlayback_Bass.Open(const Filename: string): boolean;
+{*
+ * Copies interleaved PCM SInt16 stereo samples into data.
+ * Returns the number of frames
+ *}
+function TBassPlaybackStream.GetPCMData(var data: TPCMData): Cardinal;
 var
-  stream: HSTREAM;
+  info: BASS_CHANNELINFO;
+  nBytes: DWORD;
 begin
-  Result := false;
+  Result := 0;
 
-  // free old MusicStream
-  if assigned(MusicStream) then
-    MusicStream.Free;
+  // Get Channel Data Mono and 256 Values
+  BASS_ChannelGetInfo(Handle, info);
+  FillChar(data, sizeof(TPCMData), 0);
 
-  MusicStream := Load(Filename);
-  if not assigned(MusicStream) then
+  // no support for non-stereo files at the moment
+  if (info.chans <> 2) then
     Exit;
 
-  //Set Max Volume
-  SetMusicVolume(100);
-
-  Result := true;
+  nBytes := BASS_ChannelGetData(Handle, @data, sizeof(TPCMData));
+  if(nBytes <= 0) then
+    result := 0
+  else
+    result := nBytes div sizeof(TPCMStereoSample);
 end;
 
-procedure TAudioPlayback_Bass.Rewind;
-begin
-  SetPosition(0);
-end;
 
-procedure TAudioPlayback_Bass.Play;
-begin
-  if assigned(MusicStream) then
-    MusicStream.Play();
-end;
+{ TBassExtDecoderPlaybackStream }
 
-procedure TAudioPlayback_Bass.Pause;
+procedure TBassExtDecoderPlaybackStream.Stop();
 begin
-  if assigned(MusicStream) then
-    MusicStream.Pause();
+  inherited;
+  // rewind
+  if assigned(DecodeStream) then
+    DecodeStream.Position := 0;
 end;
 
-procedure TAudioPlayback_Bass.Stop;
+procedure TBassExtDecoderPlaybackStream.Close();
 begin
-  if assigned(MusicStream) then
-    MusicStream.Stop();
+  // wake-up waiting audio-callback threads in the ReadData()-function
+  if assigned(decodeStream) then
+    DecodeStream.Close();
+  // stop audio-callback on this stream
+  inherited;
+  // free decoder-data
+  FreeAndNil(DecodeStream);
 end;
 
-procedure TAudioPlayback_Bass.Close;
+function TBassExtDecoderPlaybackStream.GetLength(): real;
 begin
-  if assigned(MusicStream) then
-    MusicStream.Close();
+  if assigned(DecodeStream) then
+    result := DecodeStream.Length
+  else
+    result := -1;
 end;
 
-function TAudioPlayback_Bass.Length: real;
-var
-  bytes:    integer;
+function TBassExtDecoderPlaybackStream.GetPosition: real;
 begin
-  if assigned(MusicStream) then
-    Result := MusicStream.GetLength()
+  if assigned(DecodeStream) then
+    result := DecodeStream.Position
   else
-    Result := -1;
+    result := -1;
 end;
 
-function TAudioPlayback_Bass.GetPosition: real;
+procedure TBassExtDecoderPlaybackStream.SetPosition(Time: real);
 begin
-  if assigned(MusicStream) then
-    Result := MusicStream.GetPosition()
-  else
-    Result := -1;
+  if assigned(DecodeStream) then
+    DecodeStream.Position := Time;
 end;
 
-procedure TAudioPlayback_Bass.SetPosition(Time: real);
+function TBassExtDecoderPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
 begin
-  if assigned(MusicStream) then
-    MusicStream.SetPosition(Time);
+  result := false;
+
+  BASS_ChannelStop(Handle);
+
+  if not assigned(decodeStream) then
+    Exit;
+  Self.DecodeStream := decodeStream;
+
+  result := true;
 end;
 
-function TAudioPlayback_Bass.Finished: boolean;
+
+{ TAudioPlayback_Bass }
+
+function  TAudioPlayback_Bass.GetName: String;
 begin
-  if assigned(MusicStream) then
-    Result := (MusicStream.GetStatus() = ssStopped)
-  else
-    Result := true;
+  result := 'BASS_Playback';
 end;
 
-// Equalizer
-procedure TAudioPlayback_Bass.GetFFTData(var data: TFFTData);
+function TAudioPlayback_Bass.EnumDevices(): boolean;
+var
+  BassDeviceID: DWORD;
+  DeviceIndex: integer;
+  Device: TBassOutputDevice;
+  Description: PChar;
 begin
-  // Get Channel Data Mono and 256 Values
-  BASS_ChannelGetData(MusicStream.Handle, @data, BASS_DATA_FFT512);
+  ClearOutputDeviceList();
+
+  // skip "no sound"-device (ID = 0)
+  BassDeviceID := 1;
+
+  while true do
+  begin
+    // Check for device
+    Description := BASS_GetDeviceDescription(BassDeviceID);
+    if (Description = nil) then
+      break;
+
+    // Set device info
+    Device := TBassOutputDevice.Create();
+    Device.Name := Description;
+    Device.BassDeviceID := BassDeviceID;
+
+    // Add device to list
+    SetLength(OutputDeviceList, BassDeviceID);
+    OutputDeviceList[BassDeviceID-1] := Device;
+
+    Inc(BassDeviceID);
+  end;
 end;
 
-{*
- * Copies interleaved PCM 16bit uint (maybe fake) stereo samples into data.
- * Returns the number of frames (= stereo/mono sample)
- *}
-function TAudioPlayback_Bass.GetPCMData(var data: TPCMData): Cardinal;
+function TAudioPlayback_Bass.InitializePlayback(): boolean;
 var
-  info: BASS_CHANNELINFO;
-  nBytes: DWORD;
+  Pet:  integer;
+  S:    integer;
 begin
-  Result := 0;
+  result := false;
 
-  // Get Channel Data Mono and 256 Values
-  BASS_ChannelGetInfo(MusicStream.Handle, info);
-  FillChar(data, sizeof(TPCMData), 0);
+  EnumDevices();
 
-  // no support for non-stereo files at the moment
-  if (info.chans <> 2) then
+  //Log.BenchmarkStart(4);
+  //Log.LogStatus('Initializing Playback Subsystem', 'Music Initialize');
+
+  if not BASS_Init(1, 44100, 0, 0, nil) then
+  begin
+    Log.LogError('Could not initialize BASS', 'Error');
     Exit;
+  end;
 
-  nBytes := BASS_ChannelGetData(MusicStream.Handle, @data, sizeof(TPCMData));
-  if(nBytes <= 0) then
-    result := 0
+  //Log.BenchmarkEnd(4); Log.LogBenchmark('--> Bass Init', 4);
+
+  // config playing buffer
+  //BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
+  //BASS_SetConfig(BASS_CONFIG_BUFFER, 100);
+
+  result := true;
+end;
+
+function DecodeStreamHandler(handle: HSTREAM; buffer: Pointer; length: DWORD; user: DWORD): DWORD; stdcall;
+var
+  decodeStream: TAudioDecodeStream;
+  bytes: integer;
+begin
+  decodeStream := TAudioDecodeStream(user);
+  bytes := decodeStream.ReadData(buffer, length);
+  // handle errors
+  if (bytes < 0) then
+    Result := BASS_STREAMPROC_END
+  // handle EOF
+  else if (DecodeStream.EOF) then
+    Result := bytes or BASS_STREAMPROC_END
   else
-    result := nBytes div sizeof(TPCMStereoSample);
+    Result := bytes;
 end;
 
-function TAudioPlayback_Bass.OpenSound(const Filename: string): TAudioPlaybackStream;
+function TAudioPlayback_Bass.FinalizePlayback(): boolean;
 begin
-  result := Load(Filename);
+  Close;
+  BASS_Free;
+  inherited FinalizePlayback();
+  Result := true;
 end;
 
-procedure TAudioPlayback_Bass.PlaySound(stream: TAudioPlaybackStream);
+function TAudioPlayback_Bass.OpenStream(const Filename: string): TAudioPlaybackStream;
+var
+  L: Integer;
+  stream: HSTREAM;
+  playbackStream: TBassExtDecoderPlaybackStream;
+  decodeStream: TAudioDecodeStream;
+  formatInfo: TAudioFormatInfo;
+  formatFlags: DWORD;
+  channelInfo: BASS_CHANNELINFO;
+  fileExt: string;
 begin
-  if assigned(stream) then
-    stream.Play();
+  Result := nil;
+
+  //Log.LogStatus('Loading Sound: "' + Filename + '"', 'LoadSoundFromFile');
+  stream := BASS_StreamCreateFile(False, PChar(Filename), 0, 0, 0);
+
+  // check if BASS opened some erroneously recognized file-formats
+  if (stream <> 0) then
+  begin
+    if BASS_ChannelGetInfo(stream, channelInfo) then
+    begin
+      fileExt := ExtractFileExt(Filename);
+      // BASS opens FLV-files although it cannot handle them
+      if ((fileExt = '.flv') and (channelInfo.ctype = BASS_CTYPE_STREAM_MP1)) then
+      begin
+        BASS_StreamFree(stream);
+        stream := 0;
+      end;
+    end;
+  end;
+
+  // Check if BASS can handle the format or try another decoder otherwise
+  if (stream <> 0) then
+  begin
+    Result := TBassPlaybackStream.Create(stream);
+  end
+  else
+  begin
+    if (AudioDecoder = nil) then
+    begin
+      Log.LogError('Failed to open "' + Filename + '", ' +
+                   TAudioCore_Bass.ErrorGetString(BASS_ErrorGetCode()), 'TAudioPlayback_Bass.Load');
+      Exit;
+    end;
+
+    decodeStream := AudioDecoder.Open(Filename);
+    if not assigned(decodeStream) then
+    begin
+      Log.LogStatus('Sound not found "' + Filename + '"', 'TAudioPlayback_Bass.Load');
+      Exit;
+    end;
+
+    formatInfo := decodeStream.GetAudioFormatInfo();
+    if (not TAudioCore_Bass.ConvertAudioFormatToBASSFlags(formatInfo.Format, formatFlags)) then
+    begin
+      Log.LogError('Unhandled sample-format in "' + Filename + '"', 'TAudioPlayback_Bass.Load');
+      FreeAndNil(decodeStream);
+      Exit;
+    end;
+
+    // FIXME: casting of a pointer to Uint32 will fail on 64bit systems
+    stream := BASS_StreamCreate(Round(formatInfo.SampleRate), formatInfo.Channels, formatFlags,
+        @DecodeStreamHandler, DWORD(decodeStream));
+    if (stream = 0) then
+    begin
+      Log.LogError('Failed to open "' + Filename + '", ' +
+                   TAudioCore_Bass.ErrorGetString(BASS_ErrorGetCode()), 'TAudioPlayback_Bass.Load');
+      FreeAndNil(decodeStream);
+      Exit;
+    end;
+
+    playbackStream := TBassExtDecoderPlaybackStream.Create(stream);
+    if (not assigned(playbackStream)) then
+    begin
+      FreeAndNil(decodeStream);
+      Exit;
+    end;
+
+    if (not playbackStream.SetDecodeStream(decodeStream)) then
+    begin
+      FreeAndNil(playbackStream);
+      FreeAndNil(decodeStream);
+      Exit;
+    end;
+
+    Result := playbackStream;
+  end;
 end;
 
-procedure TAudioPlayback_Bass.StopSound(stream: TAudioPlaybackStream);
+procedure TAudioPlayback_Bass.SetAppVolume(Volume: integer);
 begin
-  if assigned(stream) then
-    stream.Stop();
+  // Sets Volume only for this Application
+  BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Volume);
 end;
 
 
diff --git a/Game/Code/Classes/UAudioPlayback_Portaudio.pas b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
index 431fbd43..2c52c41e 100644
--- a/Game/Code/Classes/UAudioPlayback_Portaudio.pas
+++ b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
@@ -28,12 +28,21 @@ type
   TAudioPlayback_Portaudio = class(TAudioPlayback_SoftMixer)
     private
       paStream:  PPaStream;
+      AudioCore: TAudioCore_Portaudio;
+      function OpenDevice(deviceIndex: TPaDeviceIndex): boolean;
+      function EnumDevices(): boolean;
     protected
       function InitializeAudioPlaybackEngine(): boolean; override;
       function StartAudioPlaybackEngine(): boolean;      override;
       procedure StopAudioPlaybackEngine();               override;
+      function FinalizeAudioPlaybackEngine(): boolean;   override;
     public
-      function  GetName: String;                         override;
+      function GetName: String;                          override;
+  end;
+
+  TPortaudioOutputDevice = class(TAudioOutputDevice)
+    private
+      PaDeviceIndex:  TPaDeviceIndex;
   end;
 
 var
@@ -58,50 +67,39 @@ begin
   result := 'Portaudio_Playback';
 end;
 
-function TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine(): boolean;
+function TAudioPlayback_Portaudio.OpenDevice(deviceIndex: TPaDeviceIndex): boolean;
 var
-  paApiIndex      : TPaHostApiIndex;
-  paApiInfo       : PPaHostApiInfo;
-  paOutParams     : TPaStreamParameters;
-  paOutDevice     : TPaDeviceIndex;
-  paOutDeviceInfo : PPaDeviceInfo;
-  err             : TPaError;
-  sampleRate      : double;
+  deviceInfo : PPaDeviceInfo;
+  sampleRate : double;
+  outParams  : TPaStreamParameters;
+  err        : TPaError;
 begin
-  result := false;
+  Result := false;
 
-  Pa_Initialize();
+  deviceInfo := Pa_GetDeviceInfo(deviceIndex);
 
-  paApiIndex := TAudioCore_Portaudio.GetPreferredApiIndex();
-  if(paApiIndex = -1) then
-  begin
-    Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine');
-    Exit;
-  end;
-
-  paApiInfo := Pa_GetHostApiInfo(paApiIndex);
-  paOutDevice     := paApiInfo^.defaultOutputDevice;
-  paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice);
+  Log.LogInfo('Audio-Output Device: ' + deviceInfo^.name, 'TAudioPlayback_Portaudio.OpenDevice');
 
-  sampleRate := paOutDeviceInfo^.defaultSampleRate;
+  sampleRate := deviceInfo^.defaultSampleRate;
 
-  with paOutParams do begin
-    device := paOutDevice;
+  with outParams do
+  begin
+    device := deviceIndex;
     channelCount := 2;
     sampleFormat := paInt16;
-    suggestedLatency := paOutDeviceInfo^.defaultLowOutputLatency;
+    suggestedLatency := deviceInfo^.defaultLowOutputLatency;
     hostApiSpecificStreamInfo := nil;
   end;
 
   // check souncard and adjust sample-rate
-  if not TAudioCore_Portaudio.TestDevice(nil, @paOutParams, sampleRate) then
+  if not AudioCore.TestDevice(nil, @outParams, sampleRate) then
   begin
     Log.LogStatus('TestDevice failed!', 'TAudioPlayback_Portaudio.OpenDevice');
     exit;
   end;
 
   // open output stream
-  err := Pa_OpenStream(paStream, nil, @paOutParams, sampleRate,
+  err := Pa_OpenStream(paStream, nil, @outParams, sampleRate,
           paFramesPerBufferUnspecified,
           paNoFlag, @PortaudioAudioCallback, Self);
   if(err <> paNoError) then
@@ -112,12 +110,199 @@ begin
   end;
   
   FormatInfo := TAudioFormatInfo.Create(
-    paOutParams.channelCount,
+    outParams.channelCount,
     sampleRate,
     asfS16 // FIXME: is paInt16 system-dependant or -independant?
   );
 
-  Log.LogStatus('Opened audio device', 'UAudioPlayback_Portaudio');
+  Result := true;
+end;
+
+function TAudioPlayback_Portaudio.EnumDevices(): boolean;
+var
+  i:           integer;
+  paApiIndex:  TPaHostApiIndex;
+  paApiInfo:   PPaHostApiInfo;
+  deviceName:  string;
+  deviceIndex: TPaDeviceIndex;
+  deviceInfo:  PPaDeviceInfo;
+  channelCnt:  integer;
+  SC:          integer; // soundcard
+  err:         TPaError;
+  errMsg:      string;
+  paDevice:    TPortaudioOutputDevice;
+  inputParams: TPaStreamParameters;
+  stream:      PPaStream;
+  streamInfo:  PPaStreamInfo;
+  sampleRate:  double;
+  latency:     TPaTime;
+  cbPolls: integer;
+  cbWorks: boolean;
+begin
+  Result := false;
+(*
+  // choose the best available Audio-API
+  paApiIndex := AudioCore.GetPreferredApiIndex();
+  if(paApiIndex = -1) then
+  begin
+    Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.EnumDevices');
+    Exit;
+  end;
+
+  paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+
+  SC := 0;
+
+  // init array-size to max. output-devices count
+  SetLength(OutputDeviceList, paApiInfo^.deviceCount);
+  for i:= 0 to High(OutputDeviceList) do
+  begin
+    // convert API-specific device-index to global index
+    deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
+    deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+
+    channelCnt := deviceInfo^.maxOutputChannels;
+
+    // current device is no output device -> skip
+    if (channelCnt <= 0) then
+      continue;
+
+    // portaudio returns a channel-count of 128 for some devices
+    // (e.g. the "default"-device), so we have to detect those
+    // fantasy channel counts.
+    if (channelCnt > 8) then
+      channelCnt := 2;
+
+    paDevice := TPortaudioOutputDevice.Create();
+    OutputDeviceList[SC] := paDevice;
+
+    // retrieve device-name
+    deviceName := deviceInfo^.name;
+    paDevice.Name := deviceName;
+    paDevice.PaDeviceIndex := deviceIndex;
+
+    if (deviceInfo^.defaultSampleRate > 0) then
+      sampleRate := deviceInfo^.defaultSampleRate
+    else
+      sampleRate := 44100;
+
+    // on vista and xp the defaultLowInputLatency may be set to 0 but it works.
+    // TODO: correct too low latencies (what is a too low latency, maybe < 10ms?)
+    latency := deviceInfo^.defaultLowInputLatency;
+
+    // setup desired input parameters
+    // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
+    //       not be set correctly in OSS)
+    with inputParams do
+    begin
+      device := deviceIndex;
+      channelCount := channelCnt;
+      sampleFormat := paInt16;
+      suggestedLatency := latency;
+      hostApiSpecificStreamInfo := nil;
+    end;
+
+    // check if mic-callback works (might not be called on some devices)
+    if (not TAudioCore_Portaudio.TestDevice(@inputParams, nil, sampleRate)) then
+    begin
+      // ignore device if callback did not work
+      Log.LogError('Device "'+paDevice.Name+'" does not respond',
+                   'TAudioInput_Portaudio.InitializeRecord');
+      paDevice.Free();
+      continue;
+    end;
+
+    // open device for further info
+    err := Pa_OpenStream(stream, @inputParams, nil, sampleRate,
+        paFramesPerBufferUnspecified, paNoFlag, @MicrophoneTestCallback, nil);
+    if(err <> paNoError) then
+    begin
+      // unable to open device -> skip
+      errMsg := Pa_GetErrorText(err);
+      Log.LogError('Device error: "'+ deviceName +'" ('+ errMsg +')',
+                   'TAudioInput_Portaudio.InitializeRecord');
+      paDevice.Free();
+      continue;
+    end;
+
+    // adjust sample-rate (might be changed by portaudio)
+    streamInfo := Pa_GetStreamInfo(stream);
+    if (streamInfo <> nil) then
+    begin
+      if (sampleRate <> streamInfo^.sampleRate) then
+      begin
+        Log.LogStatus('Portaudio changed Samplerate from ' + FloatToStr(sampleRate) +
+            ' to ' + FloatToStr(streamInfo^.sampleRate),
+            'TAudioInput_Portaudio.InitializeRecord');
+        sampleRate := streamInfo^.sampleRate;
+      end;
+    end;
+    
+    // create audio-format info and resize capture-buffer array
+    paDevice.AudioFormat := TAudioFormatInfo.Create(
+        channelCnt,
+        sampleRate,
+        asfS16
+    );
+    SetLength(paDevice.CaptureChannel, paDevice.AudioFormat.Channels);
+
+    Log.LogStatus('InputDevice "'+paDevice.Name+'"@' +
+        IntToStr(paDevice.AudioFormat.Channels)+'x'+
+        FloatToStr(paDevice.AudioFormat.SampleRate)+'Hz ('+
+        FloatTostr(inputParams.suggestedLatency)+'sec)' ,
+        'Portaudio.InitializeRecord');
+
+    // close test-stream
+    Pa_CloseStream(stream);
+
+    Inc(SC);
+  end;
+
+  // adjust size to actual input-device count
+  SetLength(OutputDeviceList, SC);
+
+  Log.LogStatus('#Output-Devices: ' + inttostr(SC), 'Portaudio');
+
+  Result := true;
+  *)
+end;
+
+function TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine(): boolean;
+var
+  paApiIndex      : TPaHostApiIndex;
+  paApiInfo       : PPaHostApiInfo;
+  paOutDevice     : TPaDeviceIndex;
+  err: TPaError;
+begin
+  result := false;
+
+  AudioCore := TAudioCore_Portaudio.GetInstance();
+
+  // initialize portaudio
+  err := Pa_Initialize();
+  if(err <> paNoError) then
+  begin
+    Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord');
+    Exit;
+  end;
+
+  paApiIndex := AudioCore.GetPreferredApiIndex();
+  if(paApiIndex = -1) then
+  begin
+    Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine');
+    Exit;
+  end;
+
+  EnumDevices();
+
+  paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+  Log.LogInfo('Audio-Output API-Type: ' + paApiInfo^.name, 'TAudioPlayback_Portaudio.OpenDevice');
+
+  paOutDevice := paApiInfo^.defaultOutputDevice;
+  if (not OpenDevice(paOutDevice)) then
+  begin
+    Exit;
+  end;
 
   result := true;
 end;
@@ -147,6 +332,11 @@ begin
     Pa_StopStream(paStream);
 end;
 
+function TAudioPlayback_Portaudio.FinalizeAudioPlaybackEngine(): boolean;
+begin
+  Pa_Terminate();
+  Result := true;
+end;
 
 
 initialization
diff --git a/Game/Code/Classes/UAudioPlayback_SDL.pas b/Game/Code/Classes/UAudioPlayback_SDL.pas
index 6fc22242..ed5a208b 100644
--- a/Game/Code/Classes/UAudioPlayback_SDL.pas
+++ b/Game/Code/Classes/UAudioPlayback_SDL.pas
@@ -25,12 +25,15 @@ uses
 
 type
   TAudioPlayback_SDL = class(TAudioPlayback_SoftMixer)
+    private
+      function EnumDevices(): boolean;
     protected
       function InitializeAudioPlaybackEngine(): boolean; override;
       function StartAudioPlaybackEngine(): boolean;      override;
       procedure StopAudioPlaybackEngine();               override;
+      function FinalizeAudioPlaybackEngine(): boolean;   override;
     public
-      function  GetName: String;                         override;
+      function GetName: String;                          override;
       procedure MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer); override;
   end;
 
@@ -53,14 +56,37 @@ begin
   result := 'SDL_Playback';
 end;
 
+function TAudioPlayback_SDL.EnumDevices(): boolean;
+begin
+  // Note: SDL does not provide Device-Selection capabilities (will be introduced in 1.3)
+  ClearOutputDeviceList();
+  SetLength(OutputDeviceList, 1);
+  OutputDeviceList[0] := TAudioOutputDevice.Create();
+  OutputDeviceList[0].Name := '[SDL Default-Device]';
+  Result := true;
+end;
+
 function TAudioPlayback_SDL.InitializeAudioPlaybackEngine(): boolean;
 var
   desiredAudioSpec, obtainedAudioSpec: TSDL_AudioSpec;
-// err: integer; // Auto Removed, Unused Variable
+  SampleBufferSize: integer;
 begin
   result := false;
 
-  SDL_InitSubSystem(SDL_INIT_AUDIO);
+  EnumDevices();
+
+  if (SDL_InitSubSystem(SDL_INIT_AUDIO) = -1) then
+  begin
+    Log.LogError('SDL_InitSubSystem failed!', 'TAudioPlayback_SDL.InitializeAudioPlaybackEngine');
+    exit;
+  end;
+
+  SampleBufferSize := IAudioOutputBufferSizeVals[Ini.AudioOutputBufferSizeIndex];
+  if (SampleBufferSize <= 0) then
+  begin
+    // Automatic setting defaults to 1024 samples
+    SampleBufferSize := 1024;
+  end;
 
   FillChar(desiredAudioSpec, sizeof(desiredAudioSpec), 0);
   with desiredAudioSpec do
@@ -68,14 +94,14 @@ begin
     freq := 44100;
     format := AUDIO_S16SYS;
     channels := 2;
-    samples := Ini.SDLBufferSize;
+    samples := SampleBufferSize;
     callback := @SDLAudioCallback;
     userdata := Self;
   end;
 
   if(SDL_OpenAudio(@desiredAudioSpec, @obtainedAudioSpec) = -1) then
   begin
-    Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'UAudioPlayback_SDL');
+    Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'TAudioPlayback_SDL.InitializeAudioPlaybackEngine');
     exit;
   end;
 
@@ -85,7 +111,7 @@ begin
     asfS16
   );
 
-  Log.LogStatus('Opened audio device', 'UAudioPlayback_SDL');
+  Log.LogStatus('Opened audio device', 'TAudioPlayback_SDL.InitializeAudioPlaybackEngine');
 
   result := true;
 end;
@@ -97,8 +123,15 @@ begin
 end;
 
 procedure TAudioPlayback_SDL.StopAudioPlaybackEngine();
+begin
+  SDL_PauseAudio(1);
+end;
+
+function TAudioPlayback_SDL.FinalizeAudioPlaybackEngine(): boolean;
 begin
   SDL_CloseAudio();
+  SDL_QuitSubSystem(SDL_INIT_AUDIO);
+  Result := true;
 end;
 
 procedure TAudioPlayback_SDL.MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer);
@@ -116,5 +149,4 @@ initialization
 finalization
   AudioManager.Remove( singleton_AudioPlaybackSDL );
 
-
 end.
diff --git a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
index f65b3d9a..431653d0 100644
--- a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
+++ b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
@@ -13,7 +13,8 @@ uses
   Classes,
   SysUtils,
   sdl,
-  UMusic;
+  UMusic,
+  UAudioPlaybackBase;
 
 type
   TAudioPlayback_SoftMixer = class;
@@ -35,9 +36,14 @@ type
 
       InternalLock: PSDL_Mutex;
 
+      SoundEffects: TList;
+
+      FadeInStartTime, FadeInTime: cardinal;
+      FadeInStartVolume, FadeInTargetVolume: integer;
+
       procedure Reset();
 
-      class function ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16;
+      class function ConvertAudioFormatToSDL(Format: TAudioSampleFormat; out SDLFormat: UInt16): boolean;
       function InitFormatConversion(): boolean;
 
       procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
@@ -51,24 +57,26 @@ type
       procedure Play();                     override;
       procedure Pause();                    override;
       procedure Stop();                     override;
+      procedure FadeIn(Time: real; TargetVolume: integer); override;
+
       procedure Close();                    override;
-      function GetLoop(): boolean;          override;
-      procedure SetLoop(Enabled: boolean);  override;
+
       function GetLength(): real;           override;
       function GetStatus(): TStreamStatus;  override;
-
-      function IsLoaded(): boolean;
-
       function GetVolume(): integer;        override;
       procedure SetVolume(Volume: integer); override;
+      function GetLoop(): boolean;          override;
+      procedure SetLoop(Enabled: boolean);  override;
+      function GetPosition: real;           override;
+      procedure SetPosition(Time: real);    override;
 
-      // functions delegated to the decode stream
-      function GetPosition: real;
-      procedure SetPosition(Time: real);
       function ReadData(Buffer: PChar; BufSize: integer): integer;
 
-      function GetPCMData(var data: TPCMData): Cardinal;
-      procedure GetFFTData(var data: TFFTData);
+      function GetPCMData(var data: TPCMData): Cardinal; override;
+      procedure GetFFTData(var data: TFFTData);          override;
+
+      procedure AddSoundEffect(effect: TSoundEffect);    override;
+      procedure RemoveSoundEffect(effect: TSoundEffect); override;
   end;
 
   TAudioMixerStream = class
@@ -79,7 +87,7 @@ type
       mixerBuffer: PChar;
       internalLock: PSDL_Mutex;
 
-      _volume: integer;
+      appVolume: integer;
 
       procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
       procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
@@ -96,9 +104,8 @@ type
       property Volume: integer READ GetVolume WRITE SetVolume;
   end;
 
-  TAudioPlayback_SoftMixer = class( TInterfacedObject, IAudioPlayback )
+  TAudioPlayback_SoftMixer = class(TAudioPlaybackBase)
     private
-      MusicStream: TGenericPlaybackStream;
       MixerStream: TAudioMixerStream;
     protected
       FormatInfo: TAudioFormatInfo;
@@ -106,45 +113,21 @@ type
       function InitializeAudioPlaybackEngine(): boolean; virtual; abstract;
       function StartAudioPlaybackEngine(): boolean;      virtual; abstract;
       procedure StopAudioPlaybackEngine();               virtual; abstract;
+      function FinalizeAudioPlaybackEngine(): boolean;   virtual; abstract;
       procedure AudioCallback(buffer: PChar; size: integer); {$IFDEF HasInline}inline;{$ENDIF}
-    public
-      function  GetName: String;                         virtual; abstract;
-
-      function InitializePlayback(): boolean;
-      destructor Destroy; override;
 
-      function Load(const Filename: String): TGenericPlaybackStream;
-
-      procedure SetVolume(Volume: integer);
-      procedure SetMusicVolume(Volume: integer);
-      procedure SetLoop(Enabled: boolean);
-      function Open(const Filename: string): boolean; // true if succeed
-      procedure Rewind;
-      procedure SetPosition(Time: real);
-      procedure Play;
-      procedure Pause;
-
-      procedure Stop;
-      procedure Close;
-      function Finished: boolean;
-      function Length: real;
-      function GetPosition: real;
-
-      // Equalizer
-      procedure GetFFTData(var data: TFFTData);
+      function OpenStream(const Filename: String): TAudioPlaybackStream; override;
+    public
+      function GetName: String; override; abstract;
+      function InitializePlayback(): boolean; override;
+      function FinalizePlayback: boolean; override;
 
-      // Interface for Visualizer
-      function GetPCMData(var data: TPCMData): Cardinal;
+      procedure SetAppVolume(Volume: integer); override;
 
       function GetMixer(): TAudioMixerStream; {$IFDEF HasInline}inline;{$ENDIF}
       function GetAudioFormatInfo(): TAudioFormatInfo;
 
       procedure MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer); virtual;
-
-      // Sounds
-      function OpenSound(const Filename: String): TAudioPlaybackStream;
-      procedure PlaySound(stream: TAudioPlaybackStream);
-      procedure StopSound(stream: TAudioPlaybackStream);
   end;
 
 implementation
@@ -161,11 +144,13 @@ uses
 
 constructor TAudioMixerStream.Create(Engine: TAudioPlayback_SoftMixer);
 begin
+  inherited Create();
+
   Self.Engine := Engine;
 
   activeStreams := TList.Create;
   internalLock := SDL_CreateMutex();
-  _volume := 100;
+  appVolume := 100;
 end;
 
 destructor TAudioMixerStream.Destroy();
@@ -174,6 +159,7 @@ begin
     Freemem(mixerBuffer);
   activeStreams.Free;
   SDL_DestroyMutex(internalLock);
+  inherited;
 end;
 
 procedure TAudioMixerStream.Lock();
@@ -189,14 +175,14 @@ end;
 function TAudioMixerStream.GetVolume(): integer;
 begin
   Lock();
-  result := _volume;
+  result := appVolume;
   Unlock();
 end;
 
 procedure TAudioMixerStream.SetVolume(volume: integer);
 begin
   Lock();
-  _volume := volume;
+  appVolume := volume;
   Unlock();
 end;
 
@@ -270,8 +256,8 @@ begin
     if (size > 0) then
     begin
       // mix stream-data with mixer-buffer
-      // Note: use _volume (Application-Volume) instead of Volume to prevent recursive locking
-      Engine.MixBuffers(Buffer, mixerBuffer, size, _volume * stream.Volume div 100);
+      // Note: use Self.appVolume instead of Self.Volume to prevent recursive locking
+      Engine.MixBuffers(Buffer, mixerBuffer, size, appVolume * stream.Volume div 100);
     end;
   end;
 
@@ -292,6 +278,8 @@ begin
   inherited Create();
   Self.Engine := Engine;
   internalLock := SDL_CreateMutex();
+  SoundEffects := TList.Create;
+  Status := ssStopped;
   Reset();
 end;
 
@@ -299,20 +287,34 @@ destructor TGenericPlaybackStream.Destroy();
 begin
   Close();
   SDL_DestroyMutex(internalLock);
-  inherited Destroy();
+  FreeAndNil(SoundEffects);
+  inherited;
 end;
 
 procedure TGenericPlaybackStream.Reset();
 begin
+  // wake-up sleeping audio-callback threads in the ReadData()-function
+  if assigned(decodeStream) then
+    decodeStream.Close();
+
+  // stop audio-callback on this stream
   Stop();
+
+  // reset and/or free data
+  
   Loop := false;
+
   // TODO: use DecodeStream.Unref() instead of Free();
   FreeAndNil(DecodeStream);
+
   FreeMem(SampleBuffer);
   SampleBuffer := nil;
   SampleBufferPos := 0;
   BytesAvail := 0;
+  
   _volume := 0;
+  SoundEffects.Clear;
+  FadeInTime := 0;
 end;
 
 procedure TGenericPlaybackStream.Lock();
@@ -325,24 +327,27 @@ begin
   SDL_mutexV(internalLock);
 end;
 
-class function TGenericPlaybackStream.ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16;
+class function TGenericPlaybackStream.ConvertAudioFormatToSDL(Format: TAudioSampleFormat; out SDLFormat: UInt16): boolean;
 begin
-  case fmt of
-    asfU8:     Result := AUDIO_U8;
-    asfS8:     Result := AUDIO_S8;
-    asfU16LSB: Result := AUDIO_U16LSB;
-    asfS16LSB: Result := AUDIO_S16LSB;
-    asfU16MSB: Result := AUDIO_U16MSB;
-    asfS16MSB: Result := AUDIO_S16MSB;
-    asfU16:    Result := AUDIO_U16;
-    asfS16:    Result := AUDIO_S16;
-    else       Result := 0;
+  case Format of
+    asfU8:     SDLFormat := AUDIO_U8;
+    asfS8:     SDLFormat := AUDIO_S8;
+    asfU16LSB: SDLFormat := AUDIO_U16LSB;
+    asfS16LSB: SDLFormat := AUDIO_S16LSB;
+    asfU16MSB: SDLFormat := AUDIO_U16MSB;
+    asfS16MSB: SDLFormat := AUDIO_S16MSB;
+    asfU16:    SDLFormat := AUDIO_U16;
+    asfS16:    SDLFormat := AUDIO_S16;
+    else begin
+      Result := false;
+      Exit;
+    end;
   end;
+  Result := true;
 end;
 
 function TGenericPlaybackStream.InitFormatConversion(): boolean;
 var
-  //err: integer;
   srcFormat: UInt16;
   dstFormat: UInt16;
   srcFormatInfo: TAudioFormatInfo;
@@ -353,10 +358,8 @@ begin
   srcFormatInfo := DecodeStream.GetAudioFormatInfo();
   dstFormatInfo := Engine.GetAudioFormatInfo();
 
-  srcFormat := ConvertAudioFormatToSDL(srcFormatInfo.Format);
-  dstFormat := ConvertAudioFormatToSDL(dstFormatInfo.Format);
-
-  if ((srcFormat = 0) or (dstFormat = 0)) then
+  if (not ConvertAudioFormatToSDL(srcFormatInfo.Format, srcFormat) or
+      not ConvertAudioFormatToSDL(dstFormatInfo.Format, dstFormat)) then
   begin
     Log.LogError('Audio-format not supported by SDL', 'TSoftMixerPlaybackStream.InitFormatConversion');
     Exit;
@@ -399,7 +402,7 @@ procedure TGenericPlaybackStream.Play();
 var
   mixer: TAudioMixerStream;
 begin
-  if (status <> ssPaused) then
+  if (status = ssPlaying) then
   begin
     // rewind
     if assigned(DecodeStream) then
@@ -412,6 +415,15 @@ begin
     mixer.AddStream(Self);
 end;
 
+procedure TGenericPlaybackStream.FadeIn(Time: real; TargetVolume: integer);
+begin
+  FadeInTime := Trunc(Time * 1000);
+  FadeInStartTime := SDL_GetTicks();
+  FadeInStartVolume := _volume;
+  FadeInTargetVolume := TargetVolume;
+  Play();
+end;
+
 procedure TGenericPlaybackStream.Pause();
 var
   mixer: TAudioMixerStream;
@@ -427,16 +439,18 @@ procedure TGenericPlaybackStream.Stop();
 var
   mixer: TAudioMixerStream;
 begin
+  if (status = ssStopped) then
+    Exit;
+
   status := ssStopped;
 
   mixer := Engine.GetMixer();
   if (mixer <> nil) then
     mixer.RemoveStream(Self);
-end;
 
-function TGenericPlaybackStream.IsLoaded(): boolean;
-begin
-  result := assigned(DecodeStream);
+  // rewind (note: DecodeStream might be closed already, but this is not a problem)
+  if assigned(DecodeStream) then
+    DecodeStream.Position := 0;
 end;
 
 function TGenericPlaybackStream.GetLoop(): boolean;
@@ -480,6 +494,7 @@ var
   remFrameBytes: integer;
   copyCnt: integer;
   BytesNeeded: integer;
+  i: integer;
 begin
   Result := -1;
 
@@ -543,7 +558,9 @@ begin
 
     // end-of-file reached -> stop playback
     if (DecodeStream.EOF) then
+    begin
       Stop();
+    end;
 
     // resample decoded data
     cvt.buf := PUint8(SampleBuffer);
@@ -552,6 +569,15 @@ begin
       Exit;
 
     SampleBufferCount := cvt.len_cvt;
+
+    // apply effects
+    for i := 0 to SoundEffects.Count-1 do
+    begin
+      if (SoundEffects[i] <> nil) then
+      begin
+        TSoundEffect(SoundEffects[i]).Callback(SampleBuffer, SampleBufferCount);
+      end;
+    end;
   finally
     Unlock();
   end;
@@ -664,11 +690,28 @@ begin
   // resize data to a 0..1 range
   for i := 0 to High(TFFTData) do
   begin
-    // TODO: this might need some work
     data[i] := Sqrt(data[i]) / 100;
   end;
 end;
 
+procedure TGenericPlaybackStream.AddSoundEffect(effect: TSoundEffect);
+begin
+  if (not assigned(effect)) then
+    Exit;
+  Lock();
+  // check if effect is already in list to avoid duplicates
+  if (SoundEffects.IndexOf(Pointer(effect)) = -1) then
+    SoundEffects.Add(Pointer(effect));
+  Unlock();
+end;
+
+procedure TGenericPlaybackStream.RemoveSoundEffect(effect: TSoundEffect);
+begin
+  Lock();
+  SoundEffects.Remove(effect);
+  Unlock();
+end;
+
 function TGenericPlaybackStream.GetPosition: real;
 begin
   if assigned(DecodeStream) then
@@ -684,12 +727,37 @@ begin
 end;
 
 function TGenericPlaybackStream.GetVolume(): integer;
+var
+  FadeAmount: Single;
 begin
-  result := _volume;
+  Lock();
+  // adjust volume if fading is enabled
+  if (FadeInTime > 0) then
+  begin
+    FadeAmount := (SDL_GetTicks() - FadeInStartTime) / FadeInTime;
+    // check if fade-target is reached
+    if (FadeAmount >= 1) then
+    begin
+      // target reached -> stop fading
+      FadeInTime := 0;
+      _volume := FadeInTargetVolume;
+    end
+    else
+    begin
+      // fading in progress
+      _volume := Trunc(FadeAmount*FadeInTargetVolume + (1-FadeAmount)*FadeInStartVolume);
+    end;
+  end;
+  // return current volume
+  Result := _volume;
+  Unlock();
 end;
 
 procedure TGenericPlaybackStream.SetVolume(volume: integer);
 begin
+  Lock();
+  // stop fading
+  FadeInTime := 0;
   // clamp volume
   if (volume > 100) then
     _volume := 100
@@ -697,6 +765,7 @@ begin
     _volume := 0
   else
     _volume := volume;
+  Unlock();
 end;
 
 
@@ -719,15 +788,17 @@ begin
   result := true;
 end;
 
-destructor TAudioPlayback_SoftMixer.Destroy;
+function TAudioPlayback_SoftMixer.FinalizePlayback: boolean;
 begin
+  Close;
   StopAudioPlaybackEngine();
 
-  FreeAndNil(MusicStream);
   FreeAndNil(MixerStream);
   FreeAndNil(FormatInfo);
 
-  inherited Destroy();
+  FinalizeAudioPlaybackEngine();
+  inherited FinalizePlayback;
+  Result := true;
 end;
 
 procedure TAudioPlayback_SoftMixer.AudioCallback(buffer: PChar; size: integer);
@@ -745,13 +816,16 @@ begin
   Result := FormatInfo;
 end;
 
-function TAudioPlayback_SoftMixer.Load(const Filename: String): TGenericPlaybackStream;
+function TAudioPlayback_SoftMixer.OpenStream(const Filename: String): TAudioPlaybackStream;
 var
   decodeStream: TAudioDecodeStream;
   playbackStream: TGenericPlaybackStream;
 begin
   Result := nil;
 
+  if (AudioDecoder = nil) then
+    Exit;
+
   decodeStream := AudioDecoder.Open(Filename);
   if not assigned(decodeStream) then
   begin
@@ -760,110 +834,28 @@ begin
   end;
 
   playbackStream := TGenericPlaybackStream.Create(Self);
+  if (not assigned(playbackStream)) then
+  begin
+    FreeAndNil(decodeStream);
+    Exit;
+  end;
+
   if (not playbackStream.SetDecodeStream(decodeStream)) then
+  begin
+    FreeAndNil(playbackStream);
+    FreeAndNil(decodeStream);
     Exit;
+  end;
 
   result := playbackStream;
 end;
 
-procedure TAudioPlayback_SoftMixer.SetVolume(Volume: integer);
+procedure TAudioPlayback_SoftMixer.SetAppVolume(Volume: integer);
 begin
   // sets volume only for this application
   MixerStream.Volume := Volume;
 end;
 
-procedure TAudioPlayback_SoftMixer.SetMusicVolume(Volume: Integer);
-begin
-  if assigned(MusicStream) then
-    MusicStream.Volume := Volume;
-end;
-
-procedure TAudioPlayback_SoftMixer.SetLoop(Enabled: boolean);
-begin
-  if assigned(MusicStream) then
-    MusicStream.SetLoop(Enabled);
-end;
-
-function TAudioPlayback_SoftMixer.Open(const Filename: string): boolean;
-//var
-//  decodeStream: TAudioDecodeStream;
-begin
-  Result := false;
-
-  // free old MusicStream
-  MusicStream.Free();
-  // and load new one
-  MusicStream := Load(Filename);
-  if not assigned(MusicStream) then
-    Exit;
-
-  //Set Max Volume
-  SetMusicVolume(100);
-
-  Result := true;
-end;
-
-procedure TAudioPlayback_SoftMixer.Rewind;
-begin
-  SetPosition(0);
-end;
-
-procedure TAudioPlayback_SoftMixer.SetPosition(Time: real);
-begin
-  if assigned(MusicStream) then
-    MusicStream.SetPosition(Time);
-end;
-
-function TAudioPlayback_SoftMixer.GetPosition: real;
-begin
-  if assigned(MusicStream) then
-    Result := MusicStream.GetPosition()
-  else
-    Result := -1;
-end;
-
-function TAudioPlayback_SoftMixer.Length: real;
-begin
-  if assigned(MusicStream) then
-    Result := MusicStream.GetLength()
-  else
-    Result := -1;
-end;
-
-procedure TAudioPlayback_SoftMixer.Play;
-begin
-  if assigned(MusicStream) then
-    MusicStream.Play();
-end;
-
-procedure TAudioPlayback_SoftMixer.Pause;
-begin
-  if assigned(MusicStream) then
-    MusicStream.Pause();
-end;
-
-procedure TAudioPlayback_SoftMixer.Stop;
-begin
-  if assigned(MusicStream) then
-    MusicStream.Stop();
-end;
-
-procedure TAudioPlayback_SoftMixer.Close;
-begin
-  if assigned(MusicStream) then
-  begin
-    MusicStream.Close();
-  end;
-end;
-
-function TAudioPlayback_SoftMixer.Finished: boolean;
-begin
-  if assigned(MusicStream) then
-    Result := (MusicStream.GetStatus() = ssStopped)
-  else
-    Result := true;
-end;
-
 procedure TAudioPlayback_SoftMixer.MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer);
 var
   SampleIndex: Cardinal;
@@ -916,38 +908,4 @@ begin
   end;
 end;
 
-//Equalizer
-procedure TAudioPlayback_SoftMixer.GetFFTData(var data: TFFTData);
-begin
-  if assigned(MusicStream) then
-    MusicStream.GetFFTData(data);
-end;
-
-// Interface for Visualizer
-function TAudioPlayback_SoftMixer.GetPCMData(var data: TPCMData): Cardinal;
-begin
-  if assigned(MusicStream) then
-    Result := MusicStream.GetPCMData(data)
-  else
-    Result := 0;
-end;
-
-function TAudioPlayback_SoftMixer.OpenSound(const Filename: String): TAudioPlaybackStream;
-begin
-  Result := Load(Filename);
-end;
-
-procedure TAudioPlayback_SoftMixer.PlaySound(stream: TAudioPlaybackStream);
-begin
-  if assigned(stream) then
-    stream.Play();
-end;
-
-procedure TAudioPlayback_SoftMixer.StopSound(stream: TAudioPlaybackStream);
-begin
-  if assigned(stream) then
-    stream.Stop();
-end;
-
-
 end.
diff --git a/Game/Code/Classes/UIni.pas b/Game/Code/Classes/UIni.pas
index 292a485a..bed93a83 100644
--- a/Game/Code/Classes/UIni.pas
+++ b/Game/Code/Classes/UIni.pas
@@ -65,8 +65,8 @@ type
       ClickAssist:    integer;
       BeatClick:      integer;
       SavePlayback:   integer;
-      Threshold:      integer;
-      SDLBufferSize:  integer;
+      ThresholdIndex: integer;
+      AudioOutputBufferSizeIndex:  integer;
 
       //Song Preview
       PreviewVolume: integer;
@@ -105,14 +105,11 @@ type
       LPT:            integer;
 
       procedure Load();
-      procedure LoadSoundSettings();
-      
       procedure Save();
       procedure SaveNames;
       procedure SaveLevel;
   end;
 
-
 var
   Ini:    TIni;
   IResolution:    array of string;
@@ -121,7 +118,9 @@ var
   ISkin:          array of string;
 
 const
-  IPlayers:       array[0..4] of string = ('1', '2', '3', '4', '6');
+  IPlayers:       array[0..4] of string  = ('1', '2', '3', '4', '6');
+  IPlayersVals:   array[0..4] of integer = ( 1 ,  2 ,  3 ,  4 ,  6 );
+
   IDifficulty:    array[0..2] of string = ('Easy', 'Medium', 'Hard');
   ITabs:          array[0..1] of string = ('Off', 'On');
 
@@ -151,16 +150,25 @@ const
   ISpectrograph:  array[0..1] of string = ('Off', 'On');
   IMovieSize:     array[0..2] of string = ('Half', 'Full [Vid]', 'Full [BG+Vid]');
 
-  IMicBoost:      array[0..3] of string = ('Off', '+6dB', '+12dB', '+18dB');
   IClickAssist:   array[0..1] of string = ('Off', 'On');
   IBeatClick:     array[0..1] of string = ('Off', 'On');
   ISavePlayback:  array[0..1] of string = ('Off', 'On');
+
   IThreshold:     array[0..3] of string = ('5%', '10%', '15%', '20%');
-  ISDLBufferSize:    array[0..8] of string = ('256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
-  
+  IThresholdVals: array[0..3] of single = (0.05, 0.10,  0.15,  0.20);
+
+  IAudioOutputBufferSize:     array[0..9] of string  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
+  IAudioOutputBufferSizeVals: array[0..9] of integer = ( 0,      256,   512 ,  1024 ,  2048 ,  4096 ,  8192 ,  16384 ,  32768 ,  65536 );
+
+  IAudioInputBufferSize:     array[0..9] of string  = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
+  IAudioInputBufferSizeVals: array[0..9] of integer = ( 0,      256,   512 ,  1024 ,  2048 ,  4096 ,  8192 ,  16384 ,  32768 ,  65536 );
+
   //Song Preview
-  IPreviewVolume: array[0..10] of string = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%');
-  IPreviewFading: array[0..5] of string  = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs');
+  IPreviewVolume:     array[0..10] of string = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%');
+  IPreviewVolumeVals: array[0..10] of single = ( 0,   0.10,  0.20,  0.30,  0.40,  0.50,  0.60,  0.70,  0.80,  0.90,   1.00  );
+
+  IPreviewFading:     array[0..5] of string  = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs');
+  IPreviewFadingVals: array[0..5] of integer = ( 0,     1,       2,        3,        4,        5      );
 
 
   ILyricsFont:    array[0..2] of string = ('Plain', 'OLine1', 'OLine2');
@@ -182,7 +190,9 @@ const
   IJoypad:        array[0..1] of string = ('Off', 'On');
   ILPT:           array[0..2] of string = ('Off', 'LCD', 'Lights');
 
-  IChannel:       array[0..6] of string = ('Off', '1', '2', '3', '4', '5', '6');
+  // Recording options
+  IChannelPlayer: array[0..6] of string = ('Off', '1', '2', '3', '4', '5', '6');
+  IMicBoost:      array[0..3] of string = ('Off', '+6dB', '+12dB', '+18dB');
 
 implementation
 
@@ -232,12 +242,10 @@ procedure TIni.LoadInputDeviceCfg(IniFile: TMemIniFile);
 var
   deviceIndex: integer;
   deviceCfg: PInputDeviceConfig;
-  device: TAudioInputDevice;
   deviceIniIndex: integer;
   deviceIniStr: string;
   channelCount: integer;
   channelIndex: integer;
-  newDevice: boolean;
   recordKeys: TStringList;
   i: integer;
 begin
@@ -289,63 +297,10 @@ begin
 
   recordKeys.Free();
 
-  // Input devices - append detected soundcards
-  for deviceIndex := 0 to High(AudioInputProcessor.Device) do
-  begin
-    newDevice := true;
-    for deviceIniIndex := 0 to High(InputDeviceConfig) do
-    begin //Search for Card in List
-      deviceCfg := @InputDeviceConfig[deviceIniIndex];
-      device := AudioInputProcessor.Device[deviceIndex];
-
-      if (deviceCfg.Name = Trim(device.Description)) then
-      begin
-        newDevice := false;
-
-        // store highest channel index as an offset for the new channels
-        channelIndex := High(deviceCfg.ChannelToPlayerMap);
-        // add missing channels or remove non-existing ones
-        SetLength(deviceCfg.ChannelToPlayerMap, device.AudioFormat.Channels);
-        // initialize added channels to 0
-        for i := channelIndex+1 to High(deviceCfg.ChannelToPlayerMap) do
-        begin
-          deviceCfg.ChannelToPlayerMap[i] := 0;
-        end;
-
-        // associate ini-index with device
-        device.CfgIndex := deviceIniIndex;
-        break;
-      end;
-    end;
-
-    //If not in List -> Add
-    if newDevice then
-    begin
-      // resize list
-      SetLength(InputDeviceConfig, Length(InputDeviceConfig)+1);
-      deviceCfg := @InputDeviceConfig[High(InputDeviceConfig)];
-      device := AudioInputProcessor.Device[deviceIndex];
-      
-      // associate ini-index with device
-      device.CfgIndex := High(InputDeviceConfig);
-
-      deviceCfg.Name := Trim(device.Description);
-      deviceCfg.Input := 0;
-        
-      channelCount := device.AudioFormat.Channels;
-      SetLength(deviceCfg.ChannelToPlayerMap, channelCount);
-
-      for channelIndex := 0 to channelCount-1 do
-      begin
-        // set default at first start of USDX (1st device, 1st channel -> player1)
-        if ((channelIndex = 0) and (device.CfgIndex = 0)) then
-          deviceCfg.ChannelToPlayerMap[0] := 1
-        else
-          deviceCfg.ChannelToPlayerMap[channelIndex] := 0;
-      end;
-    end;
-  end;
-
+  // MicBoost
+  //MicBoost := GetArrayIndex(IMicBoost, IniFile.ReadString('Record', 'MicBoost', 'Off'));
+  // Threshold
+  //  ThresholdIndex := GetArrayIndex(IThreshold, IniFile.ReadString('Record', 'Threshold', IThreshold[1]));
 end;
 
 procedure TIni.SaveInputDeviceCfg(IniFile: TIniFile);
@@ -369,6 +324,11 @@ begin
                           IntToStr(InputDeviceConfig[deviceIndex].ChannelToPlayerMap[channelIndex]));
     end;
   end;
+
+  // MicBoost
+  //IniFile.WriteString('Record', 'MicBoost', IMicBoost[MicBoost]);
+  // Threshold
+  //IniFile.WriteString('Record', 'Threshold', IThreshold[ThresholdIndex]);
 end;
 
 procedure TIni.Load();
@@ -385,14 +345,14 @@ var
   //Result := copy (S,0,StrRScan (PChar(S),char('.'))+1);
     Result := copy (S,0,Pos ('.ini',S)-1);
   end;
-  
+
   // get index of value V in array a, returns -1 if value is not in array
   function GetArrayIndex(const A: array of String; V: String; caseInsensitiv: Boolean = False): Integer;
   var
     i: Integer;
   begin
     Result := -1;
-    
+
     for i := 0 To High(A) do
       if (A[i] = V) or (caseInsensitiv and (UpperCase(A[i]) = UpperCase(V))) then
       begin
@@ -400,7 +360,20 @@ var
         break;
       end;
   end;
-  
+
+  // get index of IniSeaction:IniProperty in array SearchArray or return the default value
+  function ReadArrayIndex(const SearchArray: array of String; IniSection: String; IniProperty: String; Default: Integer): Integer;
+  var
+    StrValue: string;
+  begin
+    StrValue := IniFile.ReadString('Sound', 'AudioOutputBufferSize', SearchArray[Default]);
+    Result := GetArrayIndex(SearchArray, StrValue);
+    if (Result = -1) then
+    begin
+      Result := Default;
+    end;
+  end;
+
   // swap two strings
   procedure swap(var s1, s2: String);
   var
@@ -564,9 +537,6 @@ begin
   // MovieSize
   MovieSize := GetArrayIndex(IMovieSize, IniFile.ReadString('Graphics', 'MovieSize', IMovieSize[2]));
 
-  // MicBoost
-  MicBoost := GetArrayIndex(IMicBoost, IniFile.ReadString('Sound', 'MicBoost', 'Off'));
-
   // ClickAssist
   ClickAssist := GetArrayIndex(IClickAssist, IniFile.ReadString('Sound', 'ClickAssist', 'Off'));
 
@@ -575,17 +545,10 @@ begin
 
   // SavePlayback
   SavePlayback := GetArrayIndex(ISavePlayback, IniFile.ReadString('Sound', 'SavePlayback', ISavePlayback[0]));
-  
-  // Threshold
-  Threshold := GetArrayIndex(IThreshold, IniFile.ReadString('Sound', 'Threshold', IThreshold[1]));
 
-  // SDLBufferSize
-  SDLBufferSize := GetArrayIndex(ISDLBufferSize, IniFile.ReadString('Sound', 'SDLBufferSize', '1024'));
-  if (SDLBufferSize = -1) then
-    SDLBufferSize := 1024
-  else
-    SDLBufferSize := StrToInt(ISDLBufferSize[SDLBufferSize]);
-       
+  // AudioOutputBufferSize
+  AudioOutputBufferSizeIndex := ReadArrayIndex(IAudioOutputBufferSize, 'Sound', 'AudioOutputBufferSize', 0);
+
   //Preview Volume
   PreviewVolume := GetArrayIndex(IPreviewVolume, IniFile.ReadString('Sound', 'PreviewVolume', IPreviewVolume[7]));
   
@@ -638,15 +601,17 @@ begin
   Theme := GetArrayIndex(ITheme, IniFile.ReadString('Themes', 'Theme', 'DELUXE'), True);
   if (Theme = -1) then
        Theme := 0;
- 
+
   // Skin
   Skin.onThemeChange;
-  
+
   SkinNo := GetArrayIndex(ISkin, IniFile.ReadString('Themes',    'Skin',   ISkin[0]));
 
   // Color
   Color := GetArrayIndex(IColor, IniFile.ReadString('Themes',    'Color',   IColor[0]));
-  
+
+  LoadInputDeviceCfg(IniFile);
+
   // LoadAnimation
   LoadAnimation := GetArrayIndex(ILoadAnimation, IniFile.ReadString('Advanced', 'LoadAnimation', 'On'));
 
@@ -740,20 +705,14 @@ begin
     // Movie Size
     IniFile.WriteString('Graphics', 'MovieSize', IMovieSize[MovieSize]);
 
-    // MicBoost
-    IniFile.WriteString('Sound', 'MicBoost', IMicBoost[MicBoost]);
-
     // ClickAssist
     IniFile.WriteString('Sound', 'ClickAssist', IClickAssist[ClickAssist]);
 
     // BeatClick
     IniFile.WriteString('Sound', 'BeatClick', IBeatClick[BeatClick]);
 
-    // Threshold
-    IniFile.WriteString('Sound', 'Threshold', IThreshold[Threshold]);
-    
-    // SDLBufferSize
-    IniFile.WriteString('Sound', 'SDLBufferSize', IntToStr(SDLBufferSize));
+    // AudioOutputBufferSize
+    IniFile.WriteString('Sound', 'AudioOutputBufferSize', IAudioOutputBufferSize[AudioOutputBufferSizeIndex]);
 
     // Song Preview
     IniFile.WriteString('Sound', 'PreviewVolume', IPreviewVolume[PreviewVolume]);
@@ -813,17 +772,8 @@ begin
     IniFile.WriteString('Controller', 'Joypad', IJoypad[Joypad]);
 
     IniFile.Free;
-    
-  end;
-end;
 
-procedure TIni.LoadSoundSettings;
-var
-  IniFile:	  TMemIniFile;
-begin
-  IniFile := TMemIniFile.Create(Filename);
-  LoadInputDeviceCfg(IniFile);
-  IniFile.Free;
+  end;
 end;
 
 procedure TIni.SaveNames;
diff --git a/Game/Code/Classes/UMain.pas b/Game/Code/Classes/UMain.pas
index 39265a6d..9c867a3c 100644
--- a/Game/Code/Classes/UMain.pas
+++ b/Game/Code/Classes/UMain.pas
@@ -223,13 +223,6 @@ begin
     Log.BenchmarkEnd(1);
     Log.LogBenchmark('Initializing Sound', 1);
 
-    // Load Sound Settings from Ini
-    Log.BenchmarkStart(1);
-    Log.LogStatus('Load Sound Settings', 'Initialization');
-    Ini.LoadSoundSettings;
-    Log.BenchmarkEnd(1);
-    Log.LogBenchmark('Load Sound Settings', 1);
-
     // Theme
     Log.BenchmarkStart(1);
     Log.LogStatus('Load Themes', 'Initialization');
@@ -352,7 +345,9 @@ begin
     // call an uninitialize routine for every initialize step
     // or at least use the corresponding Free-Methods
 
-    //TTF_quit();
+    FinalizeSound();
+
+    TTF_Quit();
     SDL_Quit();
 
     (*
diff --git a/Game/Code/Classes/UMedia_dummy.pas b/Game/Code/Classes/UMedia_dummy.pas
index dcdcd710..ad3aa94e 100644
--- a/Game/Code/Classes/UMedia_dummy.pas
+++ b/Game/Code/Classes/UMedia_dummy.pas
@@ -30,8 +30,9 @@ var
   singleton_dummy : IVideoPlayback;
 
 type
-    Tmedia_dummy = class( TInterfacedObject, IVideoPlayback, IVideoVisualization, IAudioPlayback, IAudioInput )
+    TMedia_dummy = class( TInterfacedObject, IVideoPlayback, IVideoVisualization, IAudioPlayback, IAudioInput )
     private
+      DummyOutputDeviceList: TAudioOutputDeviceList;
     public
       constructor create();
       function  GetName: String;
@@ -53,6 +54,7 @@ type
 
       // IAudioInput
       function InitializeRecord: boolean;
+      function FinalizeRecord: boolean;
       procedure CaptureStart;
       procedure CaptureStop;
       procedure GetFFTData(var data: TFFTData);
@@ -60,8 +62,12 @@ type
 
       // IAudioPlayback
       function InitializePlayback: boolean;
+      function FinalizePlayback: boolean;
+
+      function GetOutputDeviceList(): TAudioOutputDeviceList;
+      procedure FadeIn(Time: real; TargetVolume: integer);
+      procedure SetAppVolume(Volume: integer);
       procedure SetVolume(Volume: integer);
-      procedure SetMusicVolume(Volume: integer);
       procedure SetLoop(Enabled: boolean);
       procedure Rewind;
 
@@ -124,7 +130,7 @@ procedure Tmedia_dummy.SetPosition(Time: real);
 begin
 end;
 
-function  Tmedia_dummy.getPosition: real;
+function  Tmedia_dummy.GetPosition: real;
 begin
   result := 0;
 end;
@@ -135,6 +141,11 @@ begin
   result := true;
 end;
 
+function Tmedia_dummy.FinalizeRecord: boolean;
+begin
+  result := true;
+end;
+
 procedure Tmedia_dummy.CaptureStart;
 begin
 end;
@@ -155,14 +166,27 @@ end;
 // IAudioPlayback
 function Tmedia_dummy.InitializePlayback: boolean;
 begin
+  SetLength(DummyOutputDeviceList, 1);
+  DummyOutputDeviceList[0] := TAudioOutputDevice.Create();
+  DummyOutputDeviceList[0].Name := '[Dummy Device]';
   result := true;
 end;
 
-procedure Tmedia_dummy.SetVolume(Volume: integer);
+function Tmedia_dummy.FinalizePlayback: boolean;
+begin
+  result := true;
+end;
+
+function Tmedia_dummy.GetOutputDeviceList(): TAudioOutputDeviceList;
+begin
+  Result := DummyOutputDeviceList;
+end;
+
+procedure Tmedia_dummy.SetAppVolume(Volume: integer);
 begin
 end;
 
-procedure Tmedia_dummy.SetMusicVolume(Volume: integer);
+procedure Tmedia_dummy.SetVolume(Volume: integer);
 begin
 end;
 
@@ -170,6 +194,10 @@ procedure Tmedia_dummy.SetLoop(Enabled: boolean);
 begin
 end;
 
+procedure Tmedia_dummy.FadeIn(Time: real; TargetVolume: integer);
+begin
+end;
+
 procedure Tmedia_dummy.Rewind;
 begin
 end;
diff --git a/Game/Code/Classes/UMusic.pas b/Game/Code/Classes/UMusic.pas
index 4e0291cf..9977661f 100644
--- a/Game/Code/Classes/UMusic.pas
+++ b/Game/Code/Classes/UMusic.pas
@@ -80,6 +80,7 @@ type
   TFFTData  = array[0..(FFTSize div 2)-1] of Single;
 
 type
+  PPCMStereoSample = ^TPCMStereoSample;
   TPCMStereoSample = array[0..1] of SmallInt;
   TPCMData  = array[0..511] of TPCMStereoSample;
 
@@ -123,6 +124,18 @@ type
       constructor Create(Channels: byte; SampleRate: double; Format: TAudioSampleFormat);
   end;
 
+type
+  TSoundEffect = class
+    public
+      EngineData: Pointer; // can be used for engine-specific data
+      procedure Callback(Buffer: PChar; BufSize: integer); virtual; abstract;
+  end;
+
+  TVoiceRemoval = class(TSoundEffect)
+    public
+      procedure Callback(Buffer: PChar; BufSize: integer); override;
+  end;
+
 type
   TAudioProcessingStream = class
     public
@@ -131,40 +144,40 @@ type
 
   TAudioPlaybackStream = class(TAudioProcessingStream)
     protected
-      function GetLoop(): boolean;          virtual; abstract;
-      procedure SetLoop(Enabled: boolean);  virtual; abstract;
+      function GetPosition: real;           virtual; abstract;
+      procedure SetPosition(Time: real);    virtual; abstract;
       function GetLength(): real;           virtual; abstract;
       function GetStatus(): TStreamStatus;  virtual; abstract;
       function GetVolume(): integer;        virtual; abstract;
-      procedure SetVolume(volume: integer); virtual; abstract;
+      procedure SetVolume(Volume: integer); virtual; abstract;
+      function GetLoop(): boolean;          virtual; abstract;
+      procedure SetLoop(Enabled: boolean);  virtual; abstract;
     public
       procedure Play();                     virtual; abstract;
       procedure Pause();                    virtual; abstract;
       procedure Stop();                     virtual; abstract;
+      procedure FadeIn(Time: real; TargetVolume: integer);  virtual; abstract;
+
+      procedure GetFFTData(var data: TFFTData);          virtual; abstract;
+      function GetPCMData(var data: TPCMData): Cardinal; virtual; abstract;
+
+      procedure AddSoundEffect(effect: TSoundEffect);    virtual; abstract;
+      procedure RemoveSoundEffect(effect: TSoundEffect); virtual; abstract;
 
-      property Loop: boolean READ GetLoop WRITE SetLoop;
       property Length: real READ GetLength;
+      property Position: real READ GetPosition WRITE SetPosition;
       property Status: TStreamStatus READ GetStatus;
       property Volume: integer READ GetVolume WRITE SetVolume;
+      property Loop: boolean READ GetLoop WRITE SetLoop;
   end;
 
-  (*
-  TAudioMixerStream = class(TAudioProcessingStream)
-    procedure AddStream(stream: TAudioProcessingStream);
-    procedure RemoveStream(stream: TAudioProcessingStream);
-    procedure SetMasterVolume(volume: cardinal);
-    function GetMasterVolume(): cardinal;
-    procedure SetStreamVolume(stream: TAudioProcessingStream; volume: cardinal);
-    function GetStreamVolume(stream: TAudioProcessingStream): cardinal;
-  end;
-  *)
-
   TAudioDecodeStream = class(TAudioProcessingStream)
     protected
       function GetLength(): real;           virtual; abstract;
       function GetPosition(): real;         virtual; abstract;
       procedure SetPosition(Time: real);    virtual; abstract;
       function IsEOF(): boolean;            virtual; abstract;
+      function IsError(): boolean;          virtual; abstract;
     public
       function ReadData(Buffer: PChar; BufSize: integer): integer; virtual; abstract;
       function GetAudioFormatInfo(): TAudioFormatInfo; virtual; abstract;
@@ -174,6 +187,19 @@ type
       property EOF: boolean READ IsEOF;
   end;
 
+type
+  TAudioVoiceStream = class(TAudioProcessingStream)
+    public
+      function ReadData(Buffer: PChar; BufSize: integer): integer; virtual; abstract;
+      function GetAudioFormatInfo(): TAudioFormatInfo; virtual; abstract;
+  end;
+  // soundcard output-devices information
+  TAudioOutputDevice = class
+    public
+      Name: string; // soundcard name
+  end;
+  TAudioOutputDeviceList = array of TAudioOutputDevice;
+
 type
   IGenericPlayback = Interface
   ['{63A5EBC3-3F4D-4F23-8DFB-B5165FCE33DD}']
@@ -208,9 +234,13 @@ type
   IAudioPlayback = Interface( IGenericPlayback )
   ['{E4AE0B40-3C21-4DC5-847C-20A87E0DFB96}']
       function InitializePlayback: boolean;
+      function FinalizePlayback: boolean;
+      
+      function GetOutputDeviceList(): TAudioOutputDeviceList;
+      procedure SetAppVolume(Volume: integer);
       procedure SetVolume(Volume: integer);
-      procedure SetMusicVolume(Volume: integer);
       procedure SetLoop(Enabled: boolean);
+      procedure FadeIn(Time: real; TargetVolume: integer);
 
       procedure Rewind;
       function  Finished: boolean;
@@ -232,6 +262,7 @@ type
   ['{557B0E9A-604D-47E4-B826-13769F3E10B7}']
       function GetName(): String;
       function InitializeDecoder(): boolean;
+      function FinalizeDecoder(): boolean;
       //function IsSupported(const Filename: string): boolean;
   end;
 
@@ -251,6 +282,7 @@ type
   ['{A5C8DA92-2A0C-4AB2-849B-2F7448C6003A}']
       function GetName: String;
       function InitializeRecord: boolean;
+      function FinalizeRecord(): boolean;
 
       procedure CaptureStart;
       procedure CaptureStop;
@@ -288,6 +320,7 @@ var // TODO : JB --- THESE SHOULD NOT BE GLOBAL
 
 
 procedure InitializeSound;
+procedure FinalizeSound;
 
 function  Visualization(): IVideoPlayback;
 function  VideoPlayback(): IVideoPlayback;
@@ -360,14 +393,32 @@ begin
   result := singleton_AudioDecoder;
 end;
 
-procedure AssignSingletonObjects(); 
+procedure FilterInterfaceList(const IID: TGUID; InList, OutList: TInterfaceList);
+var
+  i: integer;
+  obj: IInterface;
+begin
+  if (not assigned(OutList)) then
+    Exit;
+
+  OutList.Clear;
+  for i := 0 to InList.Count-1 do
+  begin
+    if assigned(InList[i]) then
+    begin
+      // add object to list if it implements the interface searched for
+      if (InList[i].QueryInterface(IID, obj) = 0) then
+        OutList.Add(obj);
+    end;
+  end;
+end;
+
+procedure AssignSingletonObjects();
 var
   lTmpInterface : IInterface;
   iCount        : Integer;
 begin
   lTmpInterface := nil;
-  
-
 
   for iCount := 0 to AudioManager.Count - 1 do
   begin
@@ -410,7 +461,6 @@ begin
 
     end;
   end;
-
 end;
 
 procedure InitializeSound;
@@ -460,6 +510,8 @@ begin
     end;    
   end;
 
+  // Update input-device list with registered devices
+  AudioInputProcessor.UpdateInputDeviceConfig();
   // Load in-game sounds
   SoundLib := TSoundLibrary.Create;
 
@@ -480,6 +532,50 @@ begin
   end;
 end;
 
+procedure FinalizeSound;
+var
+  i: integer;
+  AudioIntfList: TInterfaceList;
+begin
+  // stop, close and free sounds
+  SoundLib.Free;
+
+  // stop and close music stream
+  if (AudioPlayback <> nil) then
+    AudioPlayback.Close;
+
+  // stop any active captures
+  if (AudioInput <> nil) then
+    AudioInput.CaptureStop;
+
+  singleton_AudioPlayback := nil;
+  singleton_AudioDecoder := nil;
+  singleton_AudioInput := nil;
+
+  // create temporary interface list
+  AudioIntfList := TInterfaceList.Create();
+
+  // finalize audio playback interfaces (should be done before the decoders) 
+  FilterInterfaceList(IAudioPlayback, AudioManager, AudioIntfList);
+  for i := 0 to AudioIntfList.Count-1 do
+    IAudioPlayback(AudioIntfList[i]).FinalizePlayback();
+
+  // finalize audio input interfaces
+  FilterInterfaceList(IAudioInput, AudioManager, AudioIntfList);
+  for i := 0 to AudioIntfList.Count-1 do
+    IAudioInput(AudioIntfList[i]).FinalizeRecord();
+
+  // finalize audio decoder interfaces
+  FilterInterfaceList(IAudioDecoder, AudioManager, AudioIntfList);
+  for i := 0 to AudioIntfList.Count-1 do
+    IAudioDecoder(AudioIntfList[i]).FinalizeDecoder();
+
+  AudioIntfList.Free;
+
+  // free audio interfaces
+  while (AudioManager.Count > 0) do
+    AudioManager.Delete(0);
+end;
 
 { TSoundLibrary }
 
@@ -535,6 +631,32 @@ begin
   //Shuffle.Free;
 end;
 
+procedure TVoiceRemoval.Callback(Buffer: PChar; BufSize: integer);
+var
+  FrameIndex, FrameSize: integer;
+  Value: integer;
+  Sample: PPCMStereoSample;
+begin
+  FrameSize := 2 * SizeOf(SmallInt);
+  for FrameIndex := 0 to (BufSize div FrameSize)-1 do
+  begin
+    Sample := PPCMStereoSample(Buffer);
+    // channel difference
+    Value := Sample[0] - Sample[1];
+    // clip
+    if (Value > High(SmallInt)) then
+      Value := High(SmallInt)
+    else if (Value < Low(SmallInt)) then
+      Value := Low(SmallInt);
+    // assign result
+    Sample[0] := Value;
+    Sample[1] := Value;
+    // increase to next frame
+    Inc(Buffer, FrameSize);
+  end;
+end;
+
+
 initialization
 begin
   singleton_AudioManager := TInterfaceList.Create();
diff --git a/Game/Code/Classes/URecord.pas b/Game/Code/Classes/URecord.pas
index 6faac2b6..55dedd1f 100644
--- a/Game/Code/Classes/URecord.pas
+++ b/Game/Code/Classes/URecord.pas
@@ -55,7 +55,11 @@ type
       property ToneString: string READ GetToneString;
   end;
 
-  TAudioInputDeviceSource = record
+const
+  DEFAULT_SOURCE_NAME = '[Default]';
+
+type
+  TAudioInputSource = record
     Name:   string;
   end;
 
@@ -63,10 +67,10 @@ type
   TAudioInputDevice = class
     public
       CfgIndex:        integer;   // index of this device in Ini.InputDeviceConfig
-      Description:     string;    // soundcard name/description
-      Source:          array of TAudioInputDeviceSource; // soundcard input(-source)s
-      SourceSelected:  integer;  // unused. What is this good for?
-      MicSource:       integer;  // unused. What is this good for?
+      Name:            string;    // soundcard name
+      Source:          array of TAudioInputSource; // soundcard input-sources
+      SourceRestore:   integer;  // source-index that will be selected after capturing (-1: not detected)
+      MicSource:       integer;  // source-index of mic (-1: none detected)
 
       AudioFormat:     TAudioFormatInfo; // capture format info (e.g. 44.1kHz SInt16 stereo)
       CaptureChannel:  array of TCaptureBuffer; // sound-buffer references used for mono or stereo channel's capture data
@@ -74,18 +78,26 @@ type
       destructor Destroy; override;
 
       procedure LinkCaptureBuffer(ChannelIndex: integer; Sound: TCaptureBuffer);
-      
+
+      // TODO: add Open/Close functions so Start/Stop becomes faster
+      //function Open(): boolean;  virtual; abstract;
+      //function Close(): boolean; virtual; abstract;
       function Start(): boolean; virtual; abstract;
-      procedure Stop();  virtual; abstract;
+      function Stop(): boolean;  virtual; abstract;
+
+      function GetVolume(): integer;        virtual; abstract;
+      procedure SetVolume(Volume: integer); virtual; abstract;
   end;
 
   TAudioInputProcessor = class
     public
       Sound:  array of TCaptureBuffer; // sound-buffers for every player
-      Device: array of TAudioInputDevice;
+      DeviceList: array of TAudioInputDevice;
 
       constructor Create;
 
+      procedure UpdateInputDeviceConfig;
+
       // handle microphone input
       procedure HandleMicrophoneData(Buffer: Pointer; Size: Cardinal;
                                      InputDevice: TAudioInputDevice);
@@ -96,10 +108,10 @@ type
       Started: boolean;
     protected
       function UnifyDeviceName(const name: string; deviceIndex: integer): string;
-      function UnifyDeviceSourceName(const name: string; const deviceName: string): string;
     public
       function GetName: String;           virtual; abstract;
       function InitializeRecord: boolean; virtual; abstract;
+      function FinalizeRecord: boolean;   virtual;
 
       procedure CaptureStart;
       procedure CaptureStop;
@@ -140,8 +152,6 @@ end;
 { TAudioInputDevice }
 
 destructor TAudioInputDevice.Destroy;
-//var
-// i: integer; // Auto Removed, Unused Variable
 begin
   Stop();
   Source := nil;
@@ -214,6 +224,7 @@ begin
   end;
 
   // move old samples to the beginning of the array (if necessary)
+  // TODO: should be a ring-buffer instead
   for SampleIndex := NumSamples to High(BufferArray) do
     BufferArray[SampleIndex-NumSamples] := BufferArray[SampleIndex];
 
@@ -225,6 +236,10 @@ begin
   // save capture-data to BufferLong if neccessary
   if (Ini.SavePlayback = 1) then
   begin
+    // this is just for debugging (approx 15MB per player for a 3min song!!!)
+    // For an in-game replay-mode we need to compress data so we do not
+    // waste that much memory. Maybe ogg-vorbis with voice-preset in fast-mode?
+    // Or we could use a faster but not that efficient lossless compression.
     BufferNew.Seek(0, soBeginning);
     BufferLong.CopyFrom(BufferNew, BufferNew.Size);
   end;
@@ -250,14 +265,8 @@ begin
        MaxVolume := Volume;
   end;
 
-  case Ini.Threshold of
-    0:   Threshold := 0.05;
-    1:   Threshold := 0.1;
-    2:   Threshold := 0.15;
-    3:   Threshold := 0.2;
-    else Threshold := 0.1;
-  end;
-
+  Threshold := IThresholdVals[Ini.ThresholdIndex];
+  
   // check if signal has an acceptable volume (ignore background-noise)
   if MaxVolume >= Threshold then
   begin
@@ -279,6 +288,7 @@ const
 begin
   // prepare to analyze
   MaxWeight := -1;
+  MaxTone := 0; // this is not needed, but it satifies the compiler
 
   // analyze halftones
   // Note: at the lowest tone (~65Hz) and a buffer-size of 4096
@@ -376,6 +386,78 @@ begin
   end;
 end;
 
+// updates InputDeviceConfig with current input-device information
+// See: TIni.LoadInputDeviceCfg()
+procedure TAudioInputProcessor.UpdateInputDeviceConfig;
+var
+  deviceIndex: integer;
+  newDevice: boolean;
+  deviceIniIndex: integer;
+  deviceCfg: PInputDeviceConfig;
+  device: TAudioInputDevice;
+  channelCount: integer;
+  channelIndex: integer;
+  i: integer;
+begin
+  // Input devices - append detected soundcards
+  for deviceIndex := 0 to High(DeviceList) do
+  begin
+    newDevice := true;
+    //Search for Card in List
+    for deviceIniIndex := 0 to High(Ini.InputDeviceConfig) do
+    begin
+      deviceCfg := @Ini.InputDeviceConfig[deviceIniIndex];
+      device := DeviceList[deviceIndex];
+
+      if (deviceCfg.Name = Trim(device.Name)) then
+      begin
+        newDevice := false;
+
+        // store highest channel index as an offset for the new channels
+        channelIndex := High(deviceCfg.ChannelToPlayerMap);
+        // add missing channels or remove non-existing ones
+        SetLength(deviceCfg.ChannelToPlayerMap, device.AudioFormat.Channels);
+        // initialize added channels to 0
+        for i := channelIndex+1 to High(deviceCfg.ChannelToPlayerMap) do
+        begin
+          deviceCfg.ChannelToPlayerMap[i] := 0;
+        end;
+
+        // associate ini-index with device
+        device.CfgIndex := deviceIniIndex;
+        break;
+      end;
+    end;
+
+    //If not in List -> Add
+    if newDevice then
+    begin
+      // resize list
+      SetLength(Ini.InputDeviceConfig, Length(Ini.InputDeviceConfig)+1);
+      deviceCfg := @Ini.InputDeviceConfig[High(Ini.InputDeviceConfig)];
+      device := DeviceList[deviceIndex];
+
+      // associate ini-index with device
+      device.CfgIndex := High(Ini.InputDeviceConfig);
+
+      deviceCfg.Name := Trim(device.Name);
+      deviceCfg.Input := 0;
+
+      channelCount := device.AudioFormat.Channels;
+      SetLength(deviceCfg.ChannelToPlayerMap, channelCount);
+
+      for channelIndex := 0 to channelCount-1 do
+      begin
+        // set default at first start of USDX (1st device, 1st channel -> player1)
+        if ((channelIndex = 0) and (device.CfgIndex = 0)) then
+          deviceCfg.ChannelToPlayerMap[0] := 1
+        else
+          deviceCfg.ChannelToPlayerMap[channelIndex] := 0;
+      end;
+    end;
+  end;
+end;
+
 {*
  * Handle captured microphone input data.
  * Params:
@@ -391,9 +473,7 @@ var
   ChannelBuffer: PChar;         // buffer handled as array of bytes (offset relative to channel)
   SampleBuffer: PSmallIntArray; // buffer handled as array of samples
   Boost:  byte;
-// ChannelCount: integer; // Auto Removed, Unused Variable
   ChannelIndex: integer;
-// ChannelOffset: integer; // Auto Removed, Unused Variable
   CaptureChannel: TCaptureBuffer;
   AudioFormat: TAudioFormatInfo;
   FrameSize: integer;
@@ -418,7 +498,7 @@ begin
   // results in the analysis phase otherwise)
   if (AudioFormat.Format <> asfS16) then
   begin
-    // this only occurs if a developer choosed a wrong input sample-format
+    // this only occurs if a developer choosed an unsupported input sample-format
     Log.CriticalError('TAudioInputProcessor.HandleMicrophoneData: Wrong sample-format');
     Exit;
   end;
@@ -472,6 +552,15 @@ end;
 
 { TAudioInputBase }
 
+function TAudioInputBase.FinalizeRecord: boolean;
+var
+  i: integer;
+begin
+  for i := 0 to High(AudioInputProcessor.DeviceList) do
+    AudioInputProcessor.DeviceList[i].Free();
+  AudioInputProcessor.DeviceList := nil;
+end;
+
 {*
  * Start capturing on all used input-device.
  *}
@@ -493,9 +582,9 @@ begin
     AudioInputProcessor.Sound[S].Clear;
 
   // start capturing on each used device
-  for DeviceIndex := 0 to High(AudioInputProcessor.Device) do
+  for DeviceIndex := 0 to High(AudioInputProcessor.DeviceList) do
   begin
-    Device := AudioInputProcessor.Device[DeviceIndex];
+    Device := AudioInputProcessor.DeviceList[DeviceIndex];
     if not assigned(Device) then
       continue;
     DeviceCfg := @Ini.InputDeviceConfig[Device.CfgIndex];
@@ -536,13 +625,11 @@ end;
 procedure TAudioInputBase.CaptureStop;
 var
   DeviceIndex: integer;
-// Player:  integer; // Auto Removed, Unused Variable
   Device: TAudioInputDevice;
-// DeviceCfg: PInputDeviceConfig; // Auto Removed, Unused Variable
 begin
-  for DeviceIndex := 0 to High(AudioInputProcessor.Device) do
+  for DeviceIndex := 0 to High(AudioInputProcessor.DeviceList) do
   begin
-    Device := AudioInputProcessor.Device[DeviceIndex];
+    Device := AudioInputProcessor.DeviceList[DeviceIndex];
     if not assigned(Device) then
       continue;
     Device.Stop();
@@ -563,7 +650,7 @@ var
     // search devices with same description
     For i := 0 to deviceIndex-1 do
     begin
-      if (AudioInputProcessor.Device[i].Description = name) then
+      if (AudioInputProcessor.DeviceList[i].Name = name) then
       begin
         Result := True;
         Break;
@@ -583,28 +670,6 @@ begin
   end;
 end;
 
-{*
- * Unifies an input-device's source name.
- * Note: the description member of the device must already be set when
- * calling this function.
- *}
-function TAudioInputBase.UnifyDeviceSourceName(const name: string; const deviceName: string): string;
-//var
-// Descr: string; // Auto Removed, Unused Variable
-begin
-  result := name;
-
-  {$IFDEF DARWIN}
-    // Under MacOSX the SingStar Mics have an empty
-    // InputName. So, we have to add a hard coded
-    // Workaround for this problem
-    if (name = '') and (Pos( 'USBMIC Serial#', deviceName) > 0) then
-    begin
-      result := 'Microphone';
-    end;
-  {$ENDIF}
-end;
-
 end.
 
 
-- 
cgit v1.2.3