{$I deng_defines.inc}
{$MODESWITCH AutoDeref}
unit DAudio;

interface

uses
  dvector3f,
{$IFDEF DENG_USE_AUDIO_MMSYSTEM}
  windows,
{$ENDIF}
{$IFDEF DENG_USE_AUDIO_OPENAL}
  dvector4f,
  dopenal,
{$ENDIF}
{$IFDEF DENG_USE_AUDIO_OPENSL}
  dvector4f,
  OpenSLES,
{$ENDIF}
  dstrnum,
  dstream,
  dresman;

type
{$IF Defined(DENG_USE_NOAUDIO)}
TSoundDeprecated = LongInt;
{$ELSEIF Defined(DENG_USE_AUDIO_OPENAL)}
// TODO: struct!
TSoundDeprecated = record
  Buffer: TALuint;
  Id: TALuint;
end;
{$ELSEIF Defined(DENG_USE_AUDIO_OPENSL)}
TSoundDeprecated = record
end;
{$ELSEIF Defined(DENG_USE_AUDIO_MMSYSTEM)}
TSoundDeprecated = String;
{$ENDIF}

PAudio = ^TAudio;
TAudio = object
private
  FReady: Boolean;
{$IFDEF DENG_USE_AUDIO_OPENAL}
  FDevice: TALCdevice;
  FContext: TALCContext;
{$ENDIF}
public
  constructor Init;
  destructor Done; virtual;
  //function Create: PAudio; inline; static;
  //procedure Free;
  procedure OnInit;
  procedure OnDone;
  procedure PlayFile(const Filename: String);
  function LoadSound(const Filename: String): TSoundDeprecated;
  procedure FreeSound(Sound: TSoundDeprecated);
  procedure Play(Sound: TSoundDeprecated);
  procedure SetSoundVolume(Volume: Single);
  function IsPlaying(Sound: TSoundDeprecated): Boolean;
  procedure SetListenerPos(const Pos: TVec3f); inline;
  procedure SetListenerDir(const Dir, Up: TVec3f); inline;
  procedure SetListenerVel(const Vel: TVec3f); inline;
end;

PSoundParameters = ^TSoundParameters;
TSoundParameters = object
private
  Data: TData;
public
  function GetKey: AnsiString;
end;

PSoundMan = ^TSoundMan;

PSoundHolder = ^TSoundHolder;
TSoundHolder = object(TResourceHolder)
  Man: PSoundMan;
  Key: AnsiString;
{$IFDEF DENG_USE_AUDIO_OPENAL}
  Buffer: TALuint;
  Id: TALuint;
{$ENDIF}
  constructor Init(_Man: PSoundMan; const Parameters: TSoundParameters);
  destructor Done; virtual;
  function GetKey: AnsiString;
end;

TSoundMan = object
private
  FAudio: PAudio;
  FResMan: dresman.TManager;
public
  constructor Init(Audio: PAudio);
  destructor Done; virtual;
  function GetResource(const Key: AnsiString): PSoundHolder;
  procedure AddResource(const Key: AnsiString; Resource: PSoundHolder);
end;

PSound = ^TSound;
TSound = object
{$IFDEF DENG_USE_AUDIO_OPENAL}
  // should be first for fastest access
  Id: TALuint;
{$ENDIF}
  Holder: PSoundHolder;
  procedure Init(Man: PSoundMan; Data: TData);
  procedure Done;
  procedure Play;
  procedure Stop;
end;

TSoundSource = object
private
{$IF Defined(DENG_USE_NOAUDIO)}
{$ELSEIF Defined(DENG_USE_AUDIO_OPENAL)}
  FId: ALuint;
{$ENDIF}
public
  procedure Init(Eng: Pointer); inline;
  procedure Done; inline;
  procedure Play(const Sound: TSound);
  procedure Stop;
  procedure SetPos(const Pos: TVec3f); inline;
  procedure SetDir(const Dir: TVec3f); inline;
  procedure SetVel(const Vel: TVec3f); inline;
  function IsPlaying: Boolean; inline;
  function GetLooping: Boolean; inline;
  procedure SetLooping(Looping: Boolean); inline;
  procedure SetVolume(Volume: LongInt); inline;
  procedure SetPitch(Pitch: Single); inline;
  property Looping: Boolean read GetLooping write SetLooping;
end;

var
  SoundMan: TSoundMan;

implementation

uses
  dstreamfile,
  DEng,
  DWinApi;

function GetActualManager(Man: PSoundMan): PSoundMan;
begin
  Result := Man;
  if Result = nil then
    Result := @SoundMan;
end;

type
TWavHeader = record
  RIFFHeader: array [1..4] of Char;
  FileSize: LongInt;
  WAVEHeader: array [1..4] of Char;
  FormatHeader: array [1..4] of Char;
  FormatHeaderSize: LongInt;
  FormatCode: Word;
  ChannelNumber: Word;
  SampleRate: LongInt;
  BytesPerSecond: LongInt;
  BytesPerSample: Word;
  BitsPerSample: Word;
end;

const
  WAV_STANDARD  = $0001;
  WAV_IMA_ADPCM = $0011;
  WAV_MP3       = $0055;

  AL_FORMAT_MONO8                           =$1100;
  AL_FORMAT_MONO16                          =$1101;
  AL_FORMAT_STEREO8                         =$1102;
  AL_FORMAT_STEREO16                        =$1103;

// TODO Вынести это в отдельную либу для чтения wav-файлов
function LoadWavStream(Stream: PStream; var Format: LongInt; var Data: Pointer; var Size: Cardinal; var Freq: Cardinal; var loop: LongInt): Boolean;
var
  WavHeader: TWavHeader;
  ReadName: array[0..3] of AnsiChar;
  Name: AnsiString;
  ReadInt: LongInt;
begin
  Result:=False;

  Stream.Read(@WavHeader, sizeof(TWavHeader));

  Freq := WavHeader.SampleRate;
  if WavHeader.ChannelNumber = 1 then
    case WavHeader.BitsPerSample of
      8: Format := AL_FORMAT_MONO8;
      16: Format := AL_FORMAT_MONO16;
    end;

  if WavHeader.ChannelNumber = 2 then
    case WavHeader.BitsPerSample of
      8: Format := AL_FORMAT_STEREO8;
      16: Format := AL_FORMAT_STEREO16;
    end;

  Log(PAnsiChar('WavHeader.FormatHeaderSize: ' + IntToStr(WavHeader.FormatHeaderSize)));
  //FIXME Android Stream.Seek((8 - 44) + 12 + 4 + WavHeader.FormatHeaderSize + 4, SO_CURRENT);
  Stream.Seek(sizeof(TWavHeader) + (8 - 44) + 12 + 4 + WavHeader.FormatHeaderSize + 4, SO_BEGINNING);
  repeat
    stream.Read(@ReadName[0], 4);
    Name := ReadName[0] + ReadName[1] + ReadName[2] + ReadName[3];
    if Name = 'data' then begin
      stream.Read(@ReadInt, SizeOf(LongInt));
      size := ReadInt;
      if WavHeader.BitsPerSample = 8 then
        size := size + 1; //fix for 8bit???

      GetMem(Data, Size);
      stream.Read(Data, Size);

      //Decode wave data if needed
      if WavHeader.FormatCode=WAV_IMA_ADPCM then
      begin
        //TODO: add code to decompress IMA ADPCM data
      end;
      if WavHeader.FormatCode=WAV_MP3 then
      begin
        //TODO: add code to decompress MP3 data
      end;
      Result:=True;
    end else begin
      //Skip unknown chunk(s)
      stream.Read(@ReadInt, 4);
      Stream.Position := Stream.Position + ReadInt;
    end;
  until Stream.Position >= Stream.Size;
end;

procedure UnloadWav(Format: Cardinal; Data: Pointer; Size: Cardinal; Freq: Cardinal);
begin
  if Data <> nil then
    FreeMem(Data);
end;

constructor TAudio.Init;
begin
end;

destructor TAudio.Done;
begin
end;

//function TAudio.Create: PAudio;
//begin
//  New(PAudio(Result), Init);
//end;

//procedure TAudio.Free;
//begin
//  Dispose(PAudio(@Self), Done);
//end;

{$IFDEF DENG_USE_NOAUDIO}
{$I DAudio_no.inc}
{$ENDIF}

{$IFDEF DENG_USE_AUDIO_MMSYSTEM}
{$I DAudio_mmsystem.inc}
{$ENDIF}

{$IFDEF DENG_USE_AUDIO_OPENAL}
{$I DAudio_openal.inc}
{$ENDIF}

{$IFDEF DENG_USE_AUDIO_OPENSL}
{$I DAudio_opensl.inc}
{$ENDIF}

function TSoundParameters.GetKey: AnsiString;
begin
  Result := 'sound from data: ' + Data.Stream.Name;
end;

function TSoundHolder.GetKey: AnsiString;
begin
  Result := Key;
end;

constructor TSoundMan.Init(Audio: PAudio);
begin
  FAudio := Audio;
  FResMan.Init;
end;

destructor TSoundMan.Done;
begin
  FResMan.Done;
end;

function TSoundMan.GetResource(const Key: AnsiString): PSoundHolder;
begin
  Result := PSoundHolder(FResMan.GetResource(Key));
end;

procedure TSoundMan.AddResource(const Key: AnsiString; Resource: PSoundHolder);
begin
  FResMan.AddResource(Key, Resource);
end;

procedure TSound.Init(Man: PSoundMan; Data: TData);
var
  Parameters: TSoundParameters;
begin
  Man := GetActualManager(Man);
  Parameters.Data := Data;
  Holder := Man^.GetResource(Parameters.GetKey);
  if Holder = nil then begin
    Holder := New(PSoundHolder, Init(Man, Parameters));
  end else
    Parameters.Data.Done;
{$IFDEF DENG_USE_AUDIO_OPENAL}
  Id := Holder^.Id;
{$ENDIF}
end;

procedure TSound.Done;
begin
  Stop;
  Holder^.DelRef;
end;

initialization
  // deng.CallInitialization;
  SoundMan.Init(Eng.Audio);
finalization
  SoundMan.Done;
end.
