aboutsummaryrefslogblamecommitdiffstats
path: root/mediaplugin/src/base/UMusic.pas
blob: a4faa98a32c5527e107771845350a0cf0df3c90a (plain) (tree)













































































































































                                                                                                      
                                           











                                                                         
                           
















































































































































































                                                                                                                    



                                       
          


                                                         
 






                                                                                                                              
 



                                                     
 


                                                                                   















                                            
 


                                                                 





                                          





                                                          
 
                                      
 

                                                                         
 


                                        
 





                                                      

 










                                                                                                      







                                                      

                            
                                                                                                  







                                                            



                                                                       

                                                                      





















                                                       



                                            
                                                   

      
                                                 


                                            
                                              


                                            
 












































                                                                                                       
                                              
                                            




                                                              
                                            
                                            
                                                                                          
      
 
                                            



                                                               
                                          
                                            






                                         



                                              
 

                                                                          





































































                                                                                 






                                           

















                                        
                                       


                                        
                                         


























































                                                                                                    




                                        



















                                          




                                           




























                                                                                  
















                                                                       
                                 







                                       
                                         





                                                      
                                                                   











                                                                                  
                                                                   


                                           
                                                                    











                                                                                 

                                            
                                                                     











                                                                                  

                                        
                                                                 

























                                                                              
                                       





                                                
                                                                    


                                                         


                                                           


                                             
            




                                                                           

                                               
                                                                   











                                                                                  

                                               
                                                                         






























                                                                            
                               
                              
                             





                                                                            
                                                                    



                                                            
                                                                 



                                                       
                                                                   


                                                          
                                        
                                                                     


                                                     
                              
                                                                    


                                                    
                                      
                                                                   


                                                          
                                      
                                                                         
































                                                                              
                                                                                  
                                                                              
                                                                             





































































































                                                                                           
                       
 

                                                                    


                                         

    
                                         




















































































































































































































                                                                                                                                   
{* UltraStar Deluxe - Karaoke Game
 *
 * UltraStar Deluxe is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING. If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 *}

unit UMusic;

interface

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

{$I switches.inc}

uses
  SysUtils,
  Classes,
  UTime,
  UBeatTimer,
  UPath;

type
  TNoteType = (ntFreestyle, ntNormal, ntGolden);

  {**
   * acoStretch: Stretch to screen width and height
   *   - ignores aspect
   *   + no borders
   *   + no image data loss
   * acoCrop: Stretch to screen width or height, crop the other dimension
   *   + keeps aspect
   *   + no borders
   *   - frame borders are cropped (image data loss)
   * acoLetterBox: Stretch to screen width, add bars at or crop top and bottom
   *   + keeps aspect
   *   - borders at top and bottom
   *   o top/bottom is cropped if width < height (unusual)
   *}
  TAspectCorrection = (acoStretch, acoCrop, acoLetterBox);

  TRectCoords = record
    Left, Right:  double;
    Upper, Lower: double;
  end;

const
  // ScoreFactor defines how a notehit of a specified notetype is
  // measured in comparison to the other types
  // 0 means this notetype is not rated at all
  // 2 means a hit of this notetype will be rated w/ twice as much
  // points as a hit of a notetype w/ ScoreFactor 1
  ScoreFactor:         array[TNoteType] of integer = (0, 1, 2);

type
  (**
   * TLineFragment represents a fragment of a lyrics line.
   * This is a text-fragment (e.g. a syllable) assigned to a note pitch,
   * represented by a bar in the sing-screen.
   *)
  PLineFragment = ^TLineFragment;
  TLineFragment = record
    Color:      integer;
    Start:      integer;    // beat the fragment starts at
    Length:     integer;    // length in beats
    Tone:       integer;    // full range tone
    Text:       UTF8String; // text assigned to this fragment (a syllable, word, etc.)
    NoteType:   TNoteType;  // note-type: golden-note/freestyle etc.
  end;

  (**
   * TLine represents one lyrics line and consists of multiple
   * notes.
   *)
  PLine = ^TLine;
  TLine = record
    Start:      integer; // the start beat of this line (<> start beat of the first note of this line)
    Lyric:      UTF8String;
    //LyricWidth: real;    // @deprecated: width of the line in pixels.
                         // Do not use this as the width is not correct.
                         // Use TLyricsEngine.GetUpperLine().Width instead. 
    End_:       integer;
    BaseNote:   integer;
    HighNote:   integer; // index of last note in line (= High(Note)?)
    TotalNotes: integer; // value of all notes in the line
    LastLine:   boolean;
    Note:       array of TLineFragment;
  end;

  (**
   * TLines stores sets of lyric lines and information on them.
   * Normally just one set is defined but in duet mode it might for example
   * contain two sets.  
   *)
  TLines = record
    Current:    integer;  // for drawing of current line
    High:       integer;  // = High(Line)!
    Number:     integer;
    Resolution: integer;
    NotesGAP:   integer;
    ScoreValue: integer;
    Line:       array of TLine;
  end;

const
  FFTSize = 512; // size of FFT data (output: FFTSize/2 values)
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;

type
  TStreamStatus = (ssStopped, ssPlaying, ssPaused);
const
  StreamStatusStr:  array[TStreamStatus] of string =
    ('Stopped', 'Playing', 'Paused');

type
  TAudioSampleFormat = (
    asfUnknown,           // unknown format
    asfU8, asfS8,         // unsigned/signed  8 bits
    asfU16LSB, asfS16LSB, // unsigned/signed 16 bits (endianness: LSB)
    asfU16MSB, asfS16MSB, // unsigned/signed 16 bits (endianness: MSB)
    asfU16, asfS16,       // unsigned/signed 16 bits (endianness: System)
    asfS32,               // signed 32 bits (endianness: System)
    asfFloat,             // float
    asfDouble             // double
  );

const
  // Size of one sample (one channel only) in bytes
  AudioSampleSize: array[TAudioSampleFormat] of integer = (
    0,        // asfUnknown
    1, 1,     // asfU8, asfS8
    2, 2,     // asfU16LSB, asfS16LSB
    2, 2,     // asfU16MSB, asfS16MSB
    2, 2,     // asfU16,    asfS16
    3,        // asfS24
    4,        // asfS32
    4         // asfFloat
  );

const
  CHANNELMAP_LEFT  = 1;
  CHANNELMAP_RIGHT = 2;
  CHANNELMAP_FRONT = CHANNELMAP_LEFT or CHANNELMAP_RIGHT;

type
  TAudioFormatInfo = class
    private
      fSampleRate : double;
      fChannels   : byte;
      fFormat     : TAudioSampleFormat;
      fFrameSize  : integer;

      procedure SetChannels(Channels: byte);
      procedure SetFormat(Format: TAudioSampleFormat);
      procedure UpdateFrameSize();
      function GetBytesPerSec(): double;
    public
      constructor Create(Channels: byte; SampleRate: double; Format: TAudioSampleFormat);
      function Copy(): TAudioFormatInfo;

      (**
       * Returns the inverse ratio of the size of data in this format to its
       * size in a given target format.
       * Example: SrcSize*SrcInfo.GetRatio(TgtInfo) = TgtSize
       *)
      function GetRatio(TargetInfo: TAudioFormatInfo): double;

      property SampleRate: double read fSampleRate write fSampleRate;
      property Channels: byte read fChannels write SetChannels;
      property Format: TAudioSampleFormat read fFormat write SetFormat;
      property FrameSize: integer read fFrameSize;
      property BytesPerSec: double read GetBytesPerSec;
  end;

type
  TSoundEffect = class
    public
      EngineData: Pointer; // can be used for engine-specific data
      procedure Callback(Buffer: PByteArray; BufSize: integer); virtual; abstract;
  end;

  TVoiceRemoval = class(TSoundEffect)
    public
      procedure Callback(Buffer: PByteArray; BufSize: integer); override;
  end;

type
  TAudioProcessingStream = class;
  TOnCloseHandler = procedure(Stream: TAudioProcessingStream);

  TAudioProcessingStream = class
    protected
      OnCloseHandlers: array of TOnCloseHandler;

      function GetLength(): real;           virtual; abstract;
      function GetPosition(): real;         virtual; abstract;
      procedure SetPosition(Time: real);    virtual; abstract;
      function GetLoop(): boolean;          virtual; abstract;
      procedure SetLoop(Enabled: boolean);  virtual; abstract;

      procedure PerformOnClose();
    public
      function GetAudioFormatInfo(): TAudioFormatInfo; virtual; abstract;
      procedure Close(); virtual; abstract;

      (**
       * Adds a new OnClose action handler.
       * The handlers are performed in the order they were added.
       * If not stated explicitely, member-variables might have been invalidated
       * already. So do not use any member (variable/method/...) if you are not
       * sure it is valid.
       *)
      procedure AddOnCloseHandler(Handler: TOnCloseHandler);

      property Length: real read GetLength;
      property Position: real read GetPosition write SetPosition;
      property Loop: boolean read GetLoop write SetLoop;
  end;

  TAudioSourceStream = class(TAudioProcessingStream)
    protected
      function IsEOF(): boolean;            virtual; abstract;
      function IsError(): boolean;          virtual; abstract;
    public
      function ReadData(Buffer: PByteArray; BufferSize: integer): integer; virtual; abstract;

      property EOF: boolean read IsEOF;
      property Error: boolean read IsError;
  end;

  (*
   * State-Chart for playback-stream state transitions
   * []: Transition, (): State
   *
   *               /---[Play/FadeIn]--->-\  /-------[Pause]----->-\
   * -[Create]->(Stop)                  (Play)                 (Pause)
   *              \\-<-[Stop/EOF*/Error]-/  \-<---[Play/FadeIn]--//
   *               \-<------------[Stop/EOF*/Error]--------------/
   *
   * *: if not looped, otherwise stream is repeated
   * Note: SetPosition() does not change the state.
   *)

  TAudioPlaybackStream = class(TAudioProcessingStream)
    protected
      AvgSyncDiff: double;  //** average difference between stream and sync clock
      SyncSource: TSyncSource;
      SourceStream: TAudioSourceStream;

      function GetLatency(): double; virtual; abstract;
      function GetStatus(): TStreamStatus;  virtual; abstract;
      function GetVolume(): single;         virtual; abstract;
      procedure SetVolume(Volume: single);  virtual; abstract;
      function Synchronize(BufferSize: integer; FormatInfo: TAudioFormatInfo): integer;
      procedure FillBufferWithFrame(Buffer: PByteArray; BufferSize: integer; Frame: PByteArray; FrameSize: integer);
    public
      (**
       * Opens a SourceStream for playback.
       * Note that the caller (not the TAudioPlaybackStream) is responsible to
       * free the SourceStream after the Playback-Stream is closed.
       * You may use an OnClose-handler to achieve this. GetSourceStream()
       * guarantees to deliver this method's SourceStream parameter to
       * the OnClose-handler. Freeing SourceStream at OnClose is allowed. 
       *)
      function Open(SourceStream: TAudioSourceStream): boolean; virtual; abstract;

      procedure Play();                     virtual; abstract;
      procedure Pause();                    virtual; abstract;
      procedure Stop();                     virtual; abstract;
      procedure FadeIn(Time: real; TargetVolume: single);  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;

      procedure SetSyncSource(SyncSource: TSyncSource);
      function GetSourceStream(): TAudioSourceStream;

      property Status: TStreamStatus read GetStatus;
      property Volume: single read GetVolume write SetVolume;
  end;

  TAudioDecodeStream = class(TAudioSourceStream)
  end;

  TAudioVoiceStream = class(TAudioSourceStream)
    protected
      FormatInfo: TAudioFormatInfo;
      ChannelMap: integer;
    public
      destructor Destroy; override;

      function Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean; virtual;
      procedure Close(); override;

      procedure WriteData(Buffer: PByteArray; BufferSize: integer); virtual; abstract;
      function GetAudioFormatInfo(): TAudioFormatInfo; override;

      function GetLength(): real;           override;
      function GetPosition(): real;         override;
      procedure SetPosition(Time: real);    override;
      function GetLoop(): boolean;          override;
      procedure SetLoop(Enabled: boolean);  override;
  end;

  TAudioConvertStream = class
    protected
      fSrcFormatInfo: TAudioFormatInfo;
      fDstFormatInfo: TAudioFormatInfo;
    public
      constructor Create(SrcFormatInfo: TAudioFormatInfo;
                      DstFormatInfo: TAudioFormatInfo);
      destructor Destroy(); override;

      (**
       * Converts the InputBuffer and stores the result in OutputBuffer.
       * If the result is not -1, InputSize will be set to the actual number of
       * input-buffer bytes used.
       * Returns the number of bytes written to the output-buffer or -1 if an error occured.
       *)
      function Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; virtual; abstract;

      (**
       * Destination/Source size ratio
       *)
      function GetRatio(): double; virtual; abstract;

      function GetOutputBufferSize(InputSize: integer): integer; virtual; abstract;
      property SrcFormatInfo: TAudioFormatInfo read fSrcFormatInfo;
      property DstFormatInfo: TAudioFormatInfo read fDstFormatInfo;
  end;

  IVideo = interface
  ['{58DFC674-9168-41EA-B59D-A61307242B80}']
      procedure Play;
      procedure Pause;
      procedure Stop;

      procedure SetLoop(Enable: boolean);
      function GetLoop(): boolean;

      procedure SetPosition(Time: real);
      function GetPosition: real;

      procedure SetScreen(Screen: integer);
      function GetScreen(): integer;

      procedure SetScreenPosition(X, Y: double; Z: double = 0.0);
      procedure GetScreenPosition(var X, Y, Z: double);

      procedure SetWidth(Width: double);
      function GetWidth(): double;

      procedure SetHeight(Height: double);
      function GetHeight(): double;

      {**
       * Sub-image of the video frame to draw.
       * This can be used for zooming or similar purposes.
       *}
      procedure SetFrameRange(Range: TRectCoords);
      function GetFrameRange(): TRectCoords;

      function GetFrameAspect(): real;

      procedure SetAspectCorrection(AspectCorrection: TAspectCorrection);
      function GetAspectCorrection(): TAspectCorrection;


      procedure SetAlpha(Alpha: double);
      function GetAlpha(): double;

      procedure SetReflectionSpacing(Spacing: double);
      function GetReflectionSpacing(): double;

      procedure GetFrame(Time: Extended);
      procedure Draw();
      procedure DrawReflection();


      property Screen: integer read GetScreen;
      property Width: double read GetWidth write SetWidth;
      property Height: double read GetHeight write SetHeight;
      property Alpha: double read GetAlpha write SetAlpha;
      property ReflectionSpacing: double read GetReflectionSpacing write SetReflectionSpacing;
      property FrameAspect: real read GetFrameAspect;
      property AspectCorrection: TAspectCorrection read GetAspectCorrection write SetAspectCorrection;
      property Loop: boolean read GetLoop write SetLoop;
      property Position: real read GetPosition write SetPosition;
  end;

  TVideoFrameFormat = (
    vffUnknown,
    vffRGB,     // packed RGB  24bpp (R:8,G:8,B:8)
    vffRGBA,    // packed RGBA 32bpp (R:8,G:8,B:8,A:8)
    vffBGR,     // packed RGB  24bpp (B:8,G:8,R:8)
    vffBGRA     // packed BGRA 32bpp (B:8,G:8,R:8,A:8)
  );

  TVideoDecodeStream = class
    public
      function Open(const FileName: IPath; Format: TVideoFrameFormat): boolean; virtual; abstract;
      procedure Close; virtual; abstract;

      procedure SetLoop(Enable: boolean); virtual; abstract;
      function GetLoop(): boolean; virtual; abstract;

      procedure SetPosition(Time: real); virtual; abstract;
      function GetPosition: real; virtual; abstract;

      function GetFrame(Time: Extended): PByteArray; virtual; abstract;

      function GetFrameWidth(): integer; virtual; abstract;
      function GetFrameHeight(): integer; virtual; abstract;
      function GetFrameAspect(): real; virtual; abstract;
      function GetFrameFormat(): TVideoFrameFormat; virtual; abstract;
  end;

type
  // soundcard output-devices information
  TAudioOutputDevice = class
    public
      Name: UTF8String; // soundcard name
  end;
  TAudioOutputDeviceList = array of TAudioOutputDevice;

type
  IMediaInterface = interface
  ['{9C31F7EF-76DB-4EEC-949F-009A26D553A2}']
      function GetName: String;
      function GetPriority(): integer;
  end;

  IGenericPlayback = interface(IMediaInterface)
  ['{63A5EBC3-3F4D-4F23-8DFB-B5165FCE33DD}']
  end;

  IVideoPlayback = interface(IGenericPlayback)
  ['{3574C40C-28AE-4201-B3D1-3D1F0759B131}']
      function Init(): boolean;
      function Finalize: boolean;

      function Open(const FileName: IPath): IVideo;
  end;

  IVideoVisualization = interface(IVideoPlayback)
  ['{5AC17D60-B34D-478D-B632-EB00D4078017}']
  end;

  IAudioPlayback = interface(IGenericPlayback)
  ['{E4AE0B40-3C21-4DC5-847C-20A87E0DFB96}']
      function InitializePlayback: boolean;
      function FinalizePlayback: boolean;

      function GetOutputDeviceList(): TAudioOutputDeviceList;

      procedure SetAppVolume(Volume: single);
      procedure SetVolume(Volume: single);
      procedure SetLoop(Enabled: boolean);

      procedure FadeIn(Time: real; TargetVolume: single);
      procedure SetSyncSource(SyncSource: TSyncSource);

      procedure Rewind;
      function  Finished: boolean;
      function  Length: real;

      function Open(const Filename: IPath): boolean; // true if succeed
      procedure Close;

      procedure Play;
      procedure Pause;
      procedure Stop;

      procedure SetPosition(Time: real);
      function GetPosition: real;

      property Position: real read GetPosition write SetPosition;

      // Sounds
      // TODO:
      // add a TMediaDummyPlaybackStream implementation that will
      // be used by the TSoundLib whenever OpenSound() fails, so checking for
      // nil-pointers is not neccessary anymore.
      // PlaySound/StopSound will be removed then, OpenSound will be renamed to
      // CreateSound.
      function OpenSound(const Filename: IPath): TAudioPlaybackStream;
      procedure PlaySound(Stream: TAudioPlaybackStream);
      procedure StopSound(Stream: TAudioPlaybackStream);

      // Equalizer
      procedure GetFFTData(var Data: TFFTData);

      // Interface for Visualizer
      function GetPCMData(var Data: TPCMData): Cardinal;

      function CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
  end;

  IGenericDecoder = interface(IMediaInterface)
  ['{557B0E9A-604D-47E4-B826-13769F3E10B7}']
      function InitializeDecoder(): boolean;
      function FinalizeDecoder(): boolean;
      //function IsSupported(const Filename: string): boolean;
  end;

  IVideoDecoder = interface(IGenericDecoder)
  ['{2F184B2B-FE69-44D5-9031-0A2462391DCA}']
      function Open(const FileName: IPath; Format: TVideoFrameFormat): TVideoDecodeStream;
  end;

  IAudioDecoder = interface(IGenericDecoder)
  ['{AB47B1B6-2AA9-4410-BF8C-EC79561B5478}']
      function Open(const Filename: IPath): TAudioDecodeStream;
  end;

  IAudioInput = interface(IMediaInterface)
  ['{A5C8DA92-2A0C-4AB2-849B-2F7448C6003A}']
      function InitializeRecord: boolean;
      function FinalizeRecord(): boolean;

      procedure CaptureStart;
      procedure CaptureStop;
  end;

  IAudioConverter = interface(IMediaInterface)
  ['{01FB458C-D631-4CA2-9E39-BF1B0ACB1AD7}']
      function Init(): boolean;
      function Finalize(): boolean;

      function Open(SrcFormatInfo: TAudioFormatInfo;
                    DstFormatInfo: TAudioFormatInfo): TAudioConvertStream;
  end;

(* TODO
const
  SOUNDID_START    = 0;
  SOUNDID_BACK     = 1;
  SOUNDID_SWOOSH   = 2;
  SOUNDID_CHANGE   = 3;
  SOUNDID_OPTION   = 4;
  SOUNDID_CLICK    = 5;
  LAST_SOUNDID = SOUNDID_CLICK;

  BaseSoundFilenames: array[0..LAST_SOUNDID] of IPath = (
    '%SOUNDPATH%/Common start.mp3',                 // Start
    '%SOUNDPATH%/Common back.mp3',                  // Back
    '%SOUNDPATH%/menu swoosh.mp3',                  // Swoosh
    '%SOUNDPATH%/select music change music 50.mp3', // Change
    '%SOUNDPATH%/option change col.mp3',            // Option
    '%SOUNDPATH%/rimshot022b.mp3'                   // Click
    {
    '%SOUNDPATH%/bassdrumhard076b.mp3',             // Drum (unused)
    '%SOUNDPATH%/hihatclosed068b.mp3',              // Hihat (unused)
    '%SOUNDPATH%/claps050b.mp3',                    // Clap (unused)
    '%SOUNDPATH%/Shuffle.mp3'                       // Shuffle (unused)
    }
  );
*)

type
  TSoundLibrary = class
    private
      // TODO
      //Sounds: array of TAudioPlaybackStream;
    public
      // TODO: move sounds to the private section
      // and provide IDs instead.
      Start:   TAudioPlaybackStream;
      Back:    TAudioPlaybackStream;
      Swoosh:  TAudioPlaybackStream;
      Change:  TAudioPlaybackStream;
      Option:  TAudioPlaybackStream;
      Click:   TAudioPlaybackStream;
      BGMusic: TAudioPlaybackStream;

      constructor Create();
      destructor Destroy(); override;

      procedure LoadSounds();
      procedure UnloadSounds();

      procedure StartBgMusic();
      procedure PauseBgMusic();
      // TODO
      //function AddSound(Filename: IPath): integer;
      //procedure RemoveSound(ID: integer);
      //function GetSound(ID: integer): TAudioPlaybackStream;
      //property Sound[ID: integer]: TAudioPlaybackStream read GetSound; default;
  end;

var
  // TODO: JB --- THESE SHOULD NOT BE GLOBAL
  Lines: array of TLines;
  LyricsState: TLyricsState;
  SoundLib: TSoundLibrary;


procedure InitializeSound;
procedure InitializeVideo;
procedure FinalizeMedia;

function Visualization(): IVideoPlayback;
function VideoPlayback(): IVideoPlayback;
function VideoDecoder(): IVideoDecoder;
function AudioPlayback(): IAudioPlayback;
function AudioInput(): IAudioInput;
function AudioDecoders(): TInterfaceList;
function AudioConverter(): IAudioConverter;

function  MediaManager: TInterfaceList;

procedure DumpMediaInterfaces();

implementation

uses
  math,
  UIni,
  UNote,
  UCommandLine,
  URecord,
  ULog,
  UPathUtils;

var
  DefaultVideoPlayback : IVideoPlayback;
  DefaultVideoDecoder  : IVideoDecoder;
  DefaultVisualization : IVideoPlayback;
  DefaultAudioPlayback : IAudioPlayback;
  DefaultAudioInput    : IAudioInput;
  DefaultAudioConverter: IAudioConverter;
  AudioDecoderList     : TInterfaceList;
  MediaInterfaceList   : TInterfaceList;


constructor TAudioFormatInfo.Create(Channels: byte; SampleRate: double; Format: TAudioSampleFormat);
begin
  inherited Create();
  fChannels := Channels;
  fSampleRate := SampleRate;
  fFormat := Format;
  UpdateFrameSize();
end;

procedure TAudioFormatInfo.SetChannels(Channels: byte);
begin
  fChannels := Channels;
  UpdateFrameSize();
end;

procedure TAudioFormatInfo.SetFormat(Format: TAudioSampleFormat);
begin
  fFormat := Format;
  UpdateFrameSize();
end;

function TAudioFormatInfo.GetBytesPerSec(): double;
begin
  Result := FrameSize * SampleRate; 
end;

procedure TAudioFormatInfo.UpdateFrameSize();
begin
  fFrameSize := AudioSampleSize[fFormat] * fChannels;
end;

function TAudioFormatInfo.Copy(): TAudioFormatInfo;
begin
  Result := TAudioFormatInfo.Create(Self.Channels, Self.SampleRate, Self.Format);
end;

function TAudioFormatInfo.GetRatio(TargetInfo: TAudioFormatInfo): double;
begin
  Result := (TargetInfo.FrameSize / Self.FrameSize) *
            (TargetInfo.SampleRate / Self.SampleRate)
end;


function MediaManager: TInterfaceList;
begin
  if (not assigned(MediaInterfaceList)) then
    MediaInterfaceList := TInterfaceList.Create();
  Result := MediaInterfaceList;
end;

function  VideoPlayback(): IVideoPlayback;
begin
  Result := DefaultVideoPlayback;
end;

function  VideoDecoder(): IVideoDecoder;
begin
  Result := DefaultVideoDecoder;
end;

function  Visualization(): IVideoPlayback;
begin
  Result := DefaultVisualization;
end;

function AudioPlayback(): IAudioPlayback;
begin
  Result := DefaultAudioPlayback;
end;

function AudioInput(): IAudioInput;
begin
  Result := DefaultAudioInput;
end;

function AudioDecoders(): TInterfaceList;
begin
  Result := AudioDecoderList;
end;

function AudioConverter(): IAudioConverter;
begin
  Result := DefaultAudioConverter;
end;

{**
 * Sort media interfaces in the order of their priority.
 * The highest priority interface will be the first in list.
 *}
procedure SortMediaIFacesByPrio(List: TInterfaceList);
var
  I, N: integer;
  Sorted: boolean;
  IFace1, IFace2: IMediaInterface;
begin
  // this is not time critical, use a simple Bubblesort
  N := List.Count - 1;
  repeat
    Sorted := true;
    for I := 1 to N do
    begin
      IFace1 := (List[I-1] as IMediaInterface);
      IFace2 := (List[I] as IMediaInterface);
      if (IFace1.GetPriority < IFace2.GetPriority) then
      begin
        List.Exchange(I-1, I);
        Sorted := false;
      end;
    end;
    Dec(N);
  until Sorted;
end;

procedure FilterMediaIFaceList(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;
  SortMediaIFacesByPrio(OutList);
end;

procedure InitializeSound;
var
  i: integer;
  InterfaceList: TInterfaceList;
  CurrentAudioDecoder: IAudioDecoder;
  CurrentAudioPlayback: IAudioPlayback;
  CurrentAudioConverter: IAudioConverter;
  CurrentAudioInput: IAudioInput;
begin
  // create a temporary list for interface enumeration
  InterfaceList := TInterfaceList.Create();

  // initialize all audio-decoders first
  FilterMediaIFaceList(IAudioDecoder, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
  begin
    CurrentAudioDecoder := InterfaceList[i] as IAudioDecoder;
    if (not CurrentAudioDecoder.InitializeDecoder()) then
    begin
      Log.LogError('Initialize failed, Removing - '+ CurrentAudioDecoder.GetName);
      MediaManager.Remove(CurrentAudioDecoder);
    end;
  end;

  // create and setup decoder-list (see AudioDecoders())
  AudioDecoderList := TInterfaceList.Create;
  FilterMediaIFaceList(IAudioDecoder, MediaManager, AudioDecoders);

  // find and initialize playback interface
  DefaultAudioPlayback := nil;
  FilterMediaIFaceList(IAudioPlayback, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
  begin
    CurrentAudioPlayback := InterfaceList[i] as IAudioPlayback;
    if (CurrentAudioPlayback.InitializePlayback()) then
    begin
      DefaultAudioPlayback := CurrentAudioPlayback;
      break;
    end;
    Log.LogError('Initialize failed, Removing - '+ CurrentAudioPlayback.GetName);
    MediaManager.Remove(CurrentAudioPlayback);
  end;

  // find and initialize converter interface
  DefaultAudioConverter := nil;
  FilterMediaIFaceList(IAudioConverter, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
  begin
    CurrentAudioConverter := InterfaceList[i] as IAudioConverter;
    if (CurrentAudioConverter.Init()) then
    begin
      DefaultAudioConverter := CurrentAudioConverter;
      break;
    end;
    Log.LogError('Initialize failed, Removing - '+ CurrentAudioConverter.GetName);
    MediaManager.Remove(CurrentAudioConverter);
  end;

  // find and initialize input interface
  DefaultAudioInput := nil;
  FilterMediaIFaceList(IAudioInput, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
  begin
    CurrentAudioInput := InterfaceList[i] as IAudioInput;
    if (CurrentAudioInput.InitializeRecord()) then
    begin
      DefaultAudioInput := CurrentAudioInput;
      break;
    end;
    Log.LogError('Initialize failed, Removing - '+ CurrentAudioInput.GetName);
    MediaManager.Remove(CurrentAudioInput);
  end;

  InterfaceList.Free;

  // Update input-device list with registered devices
  AudioInputProcessor.UpdateInputDeviceConfig();

  // Load in-game sounds
  SoundLib := TSoundLibrary.Create;
end;

procedure InitializeVideo();
var
  i: integer;
  InterfaceList: TInterfaceList;
  VideoInterface: IVideoPlayback;
  VideoDecoderInterface: IVideoDecoder;
  VisualInterface: IVideoVisualization;
begin
  InterfaceList := TInterfaceList.Create;

  // initialize and set video-playback singleton
  DefaultVideoPlayback := nil;
  FilterMediaIFaceList(IVideoPlayback, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
  begin
    VideoInterface := InterfaceList[i] as IVideoPlayback;
    // ignore visualizations
    if (Supports(VideoInterface, IVideoVisualization)) then
      Continue;
    if (VideoInterface.Init()) then
    begin
      DefaultVideoPlayback := VideoInterface;
      Break;
    end;
    Log.LogError('Initialize failed, Removing - '+ VideoInterface.GetName);
    MediaManager.Remove(VideoInterface);
  end;

  // initialize and set video-decoder singleton
  DefaultVideoDecoder := nil;
  FilterMediaIFaceList(IVideoDecoder, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
  begin
    VideoDecoderInterface := InterfaceList[i] as IVideoDecoder;
    if (VideoDecoderInterface.InitializeDecoder()) then
    begin
      DefaultVideoDecoder := VideoDecoderInterface;
      break;
    end;
    Log.LogError('Initialize failed, Removing - '+ VideoDecoderInterface.GetName);
    MediaManager.Remove(VideoDecoderInterface);
  end;

  // initialize and set visualization singleton
  DefaultVisualization := nil;
  FilterMediaIFaceList(IVideoVisualization, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
  begin
    VisualInterface := InterfaceList[i] as IVideoVisualization;
    if (VisualInterface.Init()) then
    begin
      DefaultVisualization := VisualInterface;
      break;
    end;
    Log.LogError('Initialize failed, Removing - '+ VisualInterface.GetName);
    MediaManager.Remove(VisualInterface);
  end;

  InterfaceList.Free;

  // now that we have all interfaces, we can dump them
  // TODO: move this to another place
  if FindCmdLineSwitch(cMediaInterfaces) then
  begin
    DumpMediaInterfaces();
    halt;
  end;
end;

procedure UnloadMediaModules;
var
  i: integer;
  InterfaceList: TInterfaceList;
begin
  FreeAndNil(AudioDecoderList);
  DefaultAudioPlayback := nil;
  DefaultAudioInput := nil;
  DefaultAudioConverter := nil;
  DefaultVideoPlayback := nil;
  DefaultVideoDecoder := nil;
  DefaultVisualization := nil;

  // create temporary interface list
  InterfaceList := TInterfaceList.Create();

  // finalize audio playback interfaces (should be done before the decoders)
  FilterMediaIFaceList(IAudioPlayback, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
    (InterfaceList[i] as IAudioPlayback).FinalizePlayback();

  // finalize audio input interfaces
  FilterMediaIFaceList(IAudioInput, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
    (InterfaceList[i] as IAudioInput).FinalizeRecord();

  // finalize audio decoder interfaces
  FilterMediaIFaceList(IAudioDecoder, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
    (InterfaceList[i] as IAudioDecoder).FinalizeDecoder();

  // finalize audio converter interfaces
  FilterMediaIFaceList(IAudioConverter, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
    (InterfaceList[i] as IAudioConverter).Finalize();

  // finalize video interfaces
  FilterMediaIFaceList(IVideoPlayback, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
    (InterfaceList[i] as IVideoPlayback).Finalize();

  // finalize video decoder interfaces
  FilterMediaIFaceList(IVideoDecoder, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
    (InterfaceList[i] as IVideoDecoder).FinalizeDecoder();

  // finalize visualization interfaces
  FilterMediaIFaceList(IVideoVisualization, MediaManager, InterfaceList);
  for i := 0 to InterfaceList.Count-1 do
    (InterfaceList[i] as IVideoVisualization).Finalize();

  InterfaceList.Free;

  // finally free interfaces (by removing all references to them)
  FreeAndNil(MediaInterfaceList);
end;

procedure FinalizeMedia;
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;

  UnloadMediaModules();
end;

procedure DumpMediaInterfaces();
begin
  writeln( '' );
  writeln( '--------------------------------------------------------------' );
  writeln( '  In-use Media Interfaces                                     ' );
  writeln( '--------------------------------------------------------------' );
  writeln( 'Registered Audio Playback Interface : ' + AudioPlayback.GetName );
  writeln( 'Registered Audio Input    Interface : ' + AudioInput.GetName    );
  writeln( 'Registered Audio Converter Interface: ' + AudioConverter.GetName    );
  writeln( 'Registered Video Playback Interface : ' + VideoPlayback.GetName );
  writeln( 'Registered Video Decoder  Interface : ' + VideoDecoder.GetName );
  writeln( 'Registered Visualization  Interface : ' + Visualization.GetName );
  writeln( '--------------------------------------------------------------' );
  writeln( '' );
end;


{ TSoundLibrary }

constructor TSoundLibrary.Create();
begin
  inherited;
  LoadSounds();
end;

destructor TSoundLibrary.Destroy();
begin
  UnloadSounds();
  inherited;
end;

procedure TSoundLibrary.LoadSounds();
begin
  UnloadSounds();

  Start   := AudioPlayback.OpenSound(SoundPath.Append('Common start.mp3'));
  Back    := AudioPlayback.OpenSound(SoundPath.Append('Common back.mp3'));
  Swoosh  := AudioPlayback.OpenSound(SoundPath.Append('menu swoosh.mp3'));
  Change  := AudioPlayback.OpenSound(SoundPath.Append('select music change music 50.mp3'));
  Option  := AudioPlayback.OpenSound(SoundPath.Append('option change col.mp3'));
  Click   := AudioPlayback.OpenSound(SoundPath.Append('rimshot022b.mp3'));

  BGMusic := AudioPlayback.OpenSound(SoundPath.Append('background track.mp3'));

  if (BGMusic <> nil) then
    BGMusic.Loop := True;
end;

procedure TSoundLibrary.UnloadSounds();
begin
  FreeAndNil(Start);
  FreeAndNil(Back);
  FreeAndNil(Swoosh);
  FreeAndNil(Change);
  FreeAndNil(Option);
  FreeAndNil(Click);
  FreeAndNil(BGMusic);
end;

(* TODO
function TSoundLibrary.GetSound(ID: integer): TAudioPlaybackStream;
begin
  if ((ID >= 0) and (ID < Length(Sounds))) then
    Result := Sounds[ID]
  else
    Result := nil;
end;
*)

procedure TSoundLibrary.StartBgMusic();
begin
  if (TBackgroundMusicOption(Ini.BackgroundMusicOption) = bmoOn) and
    (Soundlib.BGMusic <> nil) and not (Soundlib.BGMusic.Status = ssPlaying) then
  begin
    AudioPlayback.PlaySound(Soundlib.BGMusic);
  end;
end;

procedure TSoundLibrary.PauseBgMusic();
begin
  If (Soundlib.BGMusic <> nil) then
  begin
    Soundlib.BGMusic.Pause;
  end;
end;

{ TVoiceRemoval }

procedure TVoiceRemoval.Callback(Buffer: PByteArray; 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(PByte(Buffer), FrameSize);
  end;
end;

{ TAudioConvertStream }

constructor TAudioConvertStream.Create(
  SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo);
begin
  fSrcFormatInfo := SrcFormatInfo.Copy();
  fDstFormatInfo := DstFormatInfo.Copy();
end;

destructor TAudioConvertStream.Destroy();
begin
  FreeAndNil(fSrcFormatInfo);
  FreeAndNil(fDstFormatInfo);
end;


{ TAudioProcessingStream }

procedure TAudioProcessingStream.AddOnCloseHandler(Handler: TOnCloseHandler);
begin
  if (@Handler <> nil) then
  begin
    SetLength(OnCloseHandlers, System.Length(OnCloseHandlers)+1);
    OnCloseHandlers[High(OnCloseHandlers)] := @Handler;
  end;
end;

procedure TAudioProcessingStream.PerformOnClose();
var i: integer;
begin
  for i := 0 to High(OnCloseHandlers) do
  begin
    OnCloseHandlers[i](Self);
  end;
end;


{ TAudioPlaybackStream }

function TAudioPlaybackStream.GetSourceStream(): TAudioSourceStream;
begin
  Result := SourceStream;
end;

procedure TAudioPlaybackStream.SetSyncSource(SyncSource: TSyncSource);
begin
  Self.SyncSource := SyncSource;
  AvgSyncDiff := -1;
end;

{.$DEFINE LOG_SYNC}

(*
 * Results an adjusted size of the input buffer size to keep the stream in sync
 * with the SyncSource. If no SyncSource was assigned to this stream, the
 * input buffer size will be returned, so this method will have no effect.
 *
 * These are the possible cases:
 *   - Result > BufferSize: stream is behind the sync-source (stream is too slow),
 *                          (Result-BufferSize) bytes of the buffer must be skipped.
 *   - Result = BufferSize: stream is in sync,
 *                          there is nothing to do.
 *   - Result < BufferSize: stream is ahead of the sync-source (stream is too fast),
 *                          (BufferSize-Result) bytes of the buffer must be padded.
 *)
function TAudioPlaybackStream.Synchronize(BufferSize: integer; FormatInfo: TAudioFormatInfo): integer;
var
  TimeDiff: double;
  FrameDiff: double;
  FrameSkip: integer;
  ReqFrames: integer;
  MasterClock: real;
  CurPosition: real;
const
  AVG_HISTORY_FACTOR = 0.7;
  SYNC_REPOS_THRESHOLD = 5.000;
  SYNC_SOFT_THRESHOLD  = 0.010;
begin
  Result := BufferSize;

  if (not assigned(SyncSource)) then
    Exit;

  if (BufferSize <= 0) then
    Exit;

  CurPosition := Position;
  MasterClock := SyncSource.GetClock();

  // difference between sync-source and stream position
  // (negative if the music-stream's position is ahead of the master clock)
  TimeDiff := MasterClock - CurPosition;

  // calculate average time difference (some sort of weighted mean).
  // The bigger AVG_HISTORY_FACTOR is, the smoother is the average diff.
  // This means that older diffs are weighted more with a higher history factor
  // than with a lower. Do not use a too low history factor. FFmpeg produces
  // very instable timestamps (pts) for ogg due to some bugs. They may differ
  // +-50ms from the real stream position. Without filtering those glitches we
  // would synch without any need, resulting in ugly plopping sounds.
  if (AvgSyncDiff = -1) then
    AvgSyncDiff := TimeDiff
  else
    AvgSyncDiff := TimeDiff * (1-AVG_HISTORY_FACTOR) +
                   AvgSyncDiff * AVG_HISTORY_FACTOR;

  {$IFDEF LOG_SYNC}
  //Log.LogError(Format('c:%.3f | p:%.3f | d:%.3f | a:%.3f',
  //    [MasterClock, CurPosition, TimeDiff, AvgSyncDiff]), 'Synch');
  {$ENDIF}

  // check if we are out of sync
  if (Abs(AvgSyncDiff) >= SYNC_REPOS_THRESHOLD) then
  begin
    {$IFDEF LOG_SYNC}
    Log.LogError(Format('ReposSynch: %.3f > %.3f',
        [Abs(AvgSyncDiff), SYNC_REPOS_THRESHOLD]), 'Synch');
    {$ENDIF}

    // diff far is too large -> reposition stream
    // (resulting position might still be out of sync)
    SetPosition(CurPosition + AvgSyncDiff);

    // reset sync info
    AvgSyncDiff := -1;
  end
  else if (Abs(AvgSyncDiff) >= SYNC_SOFT_THRESHOLD) then
  begin
    {$IFDEF LOG_SYNC}
    Log.LogError(Format('SoftSynch: %.3f > %.3f',
        [Abs(AvgSyncDiff), SYNC_SOFT_THRESHOLD]), 'Synch');
    {$ENDIF}

    // hard sync: directly jump to the current position
    FrameSkip := Round(AvgSyncDiff * FormatInfo.SampleRate);
    Result := BufferSize + FrameSkip * FormatInfo.FrameSize;
    if (Result < 0) then
      Result := 0;

    // reset sync info
    AvgSyncDiff := -1;
  end;
end;

(*
 * Fills a buffer with copies of the given Frame or with 0 if Frame is nil.
 *)
procedure TAudioPlaybackStream.FillBufferWithFrame(Buffer: PByteArray; BufferSize: integer; Frame: PByteArray; FrameSize: integer);
var
  i: integer;
  FrameCopyCount: integer;
begin
  // the buffer must at least contain place for one copy of the frame.
  if ((Buffer = nil) or (BufferSize <= 0) or (BufferSize < FrameSize)) then
    Exit;

  // no valid frame -> fill with 0
  if ((Frame = nil) or (FrameSize <= 0)) then
  begin
    FillChar(Buffer[0], BufferSize, 0);
    Exit;
  end;

  // number of frames to copy
  FrameCopyCount := BufferSize div FrameSize;
  // insert as many copies of frame into the buffer as possible
  for i := 0 to FrameCopyCount-1 do
    Move(Frame[0], Buffer[i*FrameSize], FrameSize);
end;

{ TAudioVoiceStream }

function TAudioVoiceStream.Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean;
begin
  Self.ChannelMap := ChannelMap;
  Self.FormatInfo := FormatInfo.Copy();
  // a voice stream is always mono, reassure the the format is correct
  Self.FormatInfo.Channels := 1;
  Result := true;
end;

destructor TAudioVoiceStream.Destroy;
begin
  Close();
  inherited;
end;

procedure TAudioVoiceStream.Close();
begin
  PerformOnClose();
  FreeAndNil(FormatInfo);
end;

function TAudioVoiceStream.GetAudioFormatInfo(): TAudioFormatInfo;
begin
  Result := FormatInfo;
end;

function TAudioVoiceStream.GetLength(): real;
begin
  Result := -1;
end;

function TAudioVoiceStream.GetPosition(): real;
begin
  Result := -1;
end;

procedure TAudioVoiceStream.SetPosition(Time: real);
begin
end;

function TAudioVoiceStream.GetLoop(): boolean;
begin
  Result := false;
end;

procedure TAudioVoiceStream.SetLoop(Enabled: boolean);
begin
end;


end.