(*
  this file is a part of audio components suite v 2.3.
  copyright (c) 2002-2005 andrei borovsky. all rights reserved.
  see the license file for more details.
  you can contact me at mail@z0m3ie.de
*)

{
$Log: acs_dxaudio.pas,v $
Revision 1.7  2006/08/31 20:10:56  z0m3ie
*** empty log message ***

Revision 1.6  2006/07/09 16:40:35  z0m3ie
*** empty log message ***

Revision 1.5  2006/07/04 17:12:45  z0m3ie
ACS 2.4 alt wiederhergestellt (unterschiedliche Sampleformate ...)

Revision 1.3  2006/01/01 18:46:40  z0m3ie
*** empty log message ***

Revision 1.2  2005/12/26 17:31:39  z0m3ie
fixed some problems in acs_dsfiles
fixed some problems in acs_vorbis
reworked all buffers

Revision 1.1  2005/12/19 18:36:05  z0m3ie
*** empty log message ***

Revision 1.9  2005/12/18 17:01:54  z0m3ie
delphi compatibility

Revision 1.8  2005/12/04 16:54:34  z0m3ie
All classes are renamed, Style TACS... than T... to avoid conflicts with other components (eg TMixer is TACSMixer now)

Revision 1.7  2005/10/02 16:51:31  z0m3ie
*** empty log message ***

Revision 1.6  2005/09/23 14:04:58  z0m3ie
*** empty log message ***

Revision 1.5  2005/09/18 19:28:59  z0m3ie
more progress on driver handling

Revision 1.4  2005/09/16 17:34:29  z0m3ie
*** empty log message ***

Revision 1.3  2005/09/15 20:59:38  z0m3ie
start translate the documentation in the source for pasdoc

Revision 1.2  2005/09/14 21:19:37  z0m3ie
*** empty log message ***

Revision 1.1  2005/09/13 21:53:45  z0m3ie
maked seperat driver (not complete jet)

Revision 1.1  2005/09/12 22:04:52  z0m3ie
modified structure again, fileformats are now in an sperat folder.
all File In/Out classes are capsulated from TFileIn and TFileOut

Revision 1.1  2005/08/25 20:18:00  z0m3ie
Version 2.4 restructure
TCDPlayer removed (fits not in component structure)
TMP3ToWavConverter removed (fits not in component structure)

Revision 1.3  2005/08/22 20:17:02  z0m3ie
changed Headers to log
changed mail adress

}

{$ifdef linux}{$message error 'unit not supported'}{$endif linux}

unit acs_dxaudio;

interface

uses
  ACS_Audio,SysUtils, Classes, Forms, ACS_Types, ACS_Classes, Windows,ACS_Strings;

const
  LATENCY = 25;
  DS_POLLING_INTERVAL = 400; //milliseconds

type

  TDSoundWrapper = record
    dsw_pDirectSound : Pointer;
    dsw_OutputBuffer : Pointer;
    dsw_WriteOffset : LongWord;
    dsw_OutputSize : Integer;
    dsw_BytesPerFrame : Integer;
    dsw_CounterTicksPerBuffer : Int64;
    dsw_LastPlayTime : Int64;
    dsw_LastPlayCursor : Int64;
    dsw_OutputUnderflows : Int64;
    dsw_OutputRunning : LongBool;
    dsw_FramesWritten : Double;
    dsw_FramesPlayed : Double;
    dsw_pDirectSoundCapture : Pointer;
    dsw_InputBuffer : Pointer;
    dsw_ReadOffset : LongWord;
    dsw_InputSize : LongWord;
  end;

  PDSoundWrapper = ^TDSoundWrapper;

  TDSW_DeviceInfo = record
    guid : TGUID;
    name : array[0..127] of char;
  end;

  TDSW_Devices = record
    devcount : Integer;
    dinfo : array [0..15] of TDSW_DeviceInfo;
  end;

  PDSW_Devices = ^TDSW_Devices;

  { TDXAudioOut }

  TDXAudioOut = class(TACSBaseAudioOut)
  private
    DSW : TDSoundWrapper;
    Devices : TDSW_Devices;
    Chan, SR, BPS : Integer;
    EndOfInput, StartInput : Boolean;
    FDeviceNumber : Integer;
    FDeviceCount : Integer;
    procedure SetDevice(Ch : Integer);override;
    function GetDeviceInfo : TACSDeviceInfo;override;
    function GetDeviceCount : Integer;override;
  protected
    procedure Done; override;
    function DoOutput(Abort : Boolean):Boolean; override;
    procedure Prepare; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Pause;override;
    procedure Resume;override;
  end;

  TDXAudioIn = class(TACSBaseAudioIn)
  private
    DSW : TDSoundWrapper;
    Devices : TDSW_Devices;
    FDeviceNumber : Integer;
    FDeviceCount : Integer;
    FBPS, FChan, FFreq : Integer;
    FOpened : Integer;
    FBytesToRead : Integer;
    FRecTime : Integer;
    procedure SetDevice(i : Integer);override;
    function GetDeviceName(Number : Integer) : String;
    procedure OpenAudio;
    procedure CloseAudio;
    function GetBPS : Integer; override;
    function GetCh : Integer; override;
    function GetSR : Integer; override;
    function GetTotalTime : real; override;
    procedure SetRecTime(aRecTime : Integer);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetData(Buffer : Pointer; BufferSize : Integer): Integer; override;
    procedure Init; override;
    procedure Flush; override;
    property DeviceCount : Integer read FDeviceCount;
    property DeviceName[Number : Integer] : String read GetDeviceName;
  end;

implementation

type

  DSW_Init_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl;
  DSW_Term_t =  procedure(dsw: PDSoundWrapper); cdecl;
  DSW_InitOutputDevice_t = function (dsw: PDSoundWrapper; const guid : TGUID) : HRESULT; cdecl;
  DSW_InitOutputBuffer_t = function (dsw: PDSoundWrapper; Wnd : HWND; bps, nFrameRate : LongWord; nChannels, bufSize : Integer): HRESULT; cdecl;
  DSW_StartOutput_t = function (dsw: PDSoundWrapper) : HRESULT; cdecl;
  DSW_StopOutput_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl;
  DSW_RestartOutput_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl;
  DSW_GetOutputStatus_t = function(dsw: PDSoundWrapper) : DWORD; cdecl;
  DSW_WriteBlock_t = function(dsw: PDSoundWrapper; buf : PByte; numBytes : Integer) : HRESULT; cdecl;
  DSW_ZeroEmptySpace_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl;
  DSW_QueryOutputSpace_t = function(dsw: PDSoundWrapper; var bytesEmpty ) : HRESULT; cdecl;
  DSW_Enumerate_t = function(var devices : TDSW_Devices) : HRESULT; cdecl;

  DSW_InitInputBuffer_t = function(dsw: PDSoundWrapper; bps, nFrameRate, nChannels, bufSize : Integer) : HRESULT; cdecl;
  DSW_InitInputDevice_t = function(dsw: PDSoundWrapper; const GUID : TGUID) : HRESULT; cdecl;
  DSW_StartInput_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl;
  DSW_StopInput_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl;
  DSW_ReadBlock_t = function(dsw: PDSoundWrapper; buf : PByte; numBytes : Integer) : HRESULT; cdecl;
  DSW_QueryInputFilled_t = function(dsw: PDSoundWrapper; var bytesFilled : Integer) : HRESULT; cdecl;

var

  LibdswLoaded : Boolean = False;

  DSW_Init : DSW_Init_t;
  DSW_Term : DSW_Term_t;
  DSW_InitOutputDevice : DSW_InitOutputDevice_t;
  DSW_InitOutputBuffer : DSW_InitOutputBuffer_t;
  DSW_StartOutput : DSW_StartOutput_t;
  DSW_StopOutput : DSW_StopOutput_t;
  DSW_RestartOutput : DSW_RestartOutput_t;
  DSW_GetOutputStatus : DSW_GetOutputStatus_t;
  DSW_WriteBlock : DSW_WriteBlock_t;
  DSW_ZeroEmptySpace : DSW_ZeroEmptySpace_t;
  DSW_QueryOutputSpace : DSW_QueryOutputSpace_t;
  DSW_Enumerate : DSW_Enumerate_t;

  DSW_InitInputBuffer : DSW_InitInputBuffer_t;
  DSW_InitInputDevice : DSW_InitInputDevice_t;
  DSW_StartInput : DSW_StartInput_t;
  DSW_StopInput : DSW_StopInput_t;
  DSW_ReadBlock : DSW_ReadBlock_t;
  DSW_QueryInputFilled : DSW_QueryInputFilled_t;

  Libhandle : HMODULE;


procedure TDXAudioOut.Prepare;
var
  Res : HResult;
  Wnd : HWND;
  Form : TForm;
begin
  if (FDeviceNumber >= FDeviceCount) then raise EACSException.Create(Format(strChannelnotavailable,[FDeviceNumber]));
  FInput.Init;
  FBuffer := AllocMem(FBufferSize);
  Chan := FInput.Channels;
  SR := FInput.SampleRate;
  BPS := FInput.BitsPerSample;
  DSW_Init(@DSW);
  Res := DSW_InitOutputDevice(@DSW, Devices.dinfo[FDeviceNumber].guid);
  if Res <> 0 then raise EACSException.Create(strFailedtoCreateDSdev);
{  if Owner is TForm then
  begin
    Form := Owner as TForm;
    Wnd := Form.Handle;
  end else }
  Wnd := 0;
  Res := DSW_InitOutputBuffer(@DSW, Wnd, BPS, SR, Chan, FBufferSize);
  if Res <> 0 then raise EACSException.Create(strFailedtoCreateDSbuf);
  StartInput := True;
  EndOfInput := False;
end;

procedure TDXAudioOut.Done;
begin
  Finput.Flush;
  DSW_Term(@DSW);
  FreeMem(FBuffer);
end;

function TDXAudioOut.DoOutput(Abort : Boolean):Boolean;
var
  Len, offs, lb : Integer;
  Stat : LongWord;
  Res : HRESULT;
  PlayTime, CTime : LongWord;
begin
  Result := True;
  if not Busy then Exit;
  if not CanOutput then
  begin
    Result := False;
    Exit;
  end;
  if Abort then
  begin
    DSW_StopOutput(@DSW);
    CanOutput := False;
    Result := False;
    Exit;
  end;
  if StartInput then
  begin
    Len := 0;
    while Len < FBufferSize do
    begin
      offs := FInput.GetData(@FBuffer^[Len], FBufferSize-Len);
      if offs = 0 then
      begin
        EndOfInput := True;
        Break;
      end;
      Inc(Len, offs);
    end;
    DSW_WriteBlock(@DSW, @FBuffer^, Len);
    DSW_StartOutput(@DSW);
    StartInput := False;
  end;
  if EndOfInput then
  begin
    CanOutput := False;
    PlayTime := Round(FBufferSize/(Chan*(BPS div 8)*SR))*1000;
    CTime := 0;
    while CTime < PlayTime do
    begin
      Sleep(100);
      DSW_ZeroEmptySpace(@DSW);
      Inc(CTime, 100);
    end;
    DSW_StopOutput(@DSW);
    Result := False;
    Exit;
  end;
  Sleep(DS_POLLING_INTERVAL);
  DSW_QueryOutputSpace(@DSW, lb);
  lb := lb - (lb mod 1024);
  Len := 0;
  while Len < lb do
  begin
    if FInput.Busy then
      begin
        try
          offs := Finput.GetData(@FBuffer^[Len], lb-Len);
        except
          DSW_StopOutput(@DSW);
          CanOutput := False;
          Result := False;
          Exit;
        end;
      end;
    if offs = 0 then Break;
    Inc(Len, offs);
  end;
  DSW_WriteBlock(@DSW, @Fbuffer^, Len);
  if offs = 0 then
  begin
    DSW_ZeroEmptySpace(@DSW);
    EndOfInput := True;
  end;
end;

constructor TDXAudioOut.Create;
begin
  inherited Create(AOwner);
  FBufferSize := $40000;
  if not (csDesigning	in ComponentState) then
  begin
    if not LibdswLoaded then
    raise EACSException.Create(Format(strCoudntloadlib,['dswrapper.dll']));
  end;
  if LibdswLoaded then DSW_Enumerate(Devices);
  FDeviceCount := Devices.devcount;
end;

destructor TDXAudioOut.Destroy;
begin
  if LibdswLoaded then DSW_Term(@DSW);
end;

procedure TDXAudioOut.Pause;
begin
  if EndOfInput then Exit;
  DSW_StopOutput(@DSW);
end;

procedure TDXAudioOut.Resume;
begin
  if EndOfInput then Exit;
  DSW_RestartOutput(@DSW);
end;

procedure TDXAudioOut.SetDevice(Ch: Integer);
begin
  FBaseChannel := Ch;
end;

function TDXAudioOut.GetDeviceInfo: TACSDeviceInfo;
begin
  if (FBaseChannel >= FDeviceCount) then
    exit;
  Result.DeviceName := PChar(@(Devices.dinfo[FBaseChannel].Name[0]));
end;

function TDXAudioOut.GetDeviceCount: Integer;
begin
  Result := FDeviceCount;
end;

constructor TDXAudioIn.Create;
begin
  inherited Create(AOwner);
  FBPS := 8;
  FChan := 1;
  FFreq := 8000;
  FSize := -1;
  BufferSize := $2000;
  if not (csDesigning	in ComponentState) then
  begin
    if not LibdswLoaded then
    raise EACSException.Create(Format(strCoudntloadlib,['dswrapper.dll']));
  end;
  if LibdswLoaded then DSW_Enumerate(Devices);
  FDeviceCount := Devices.devcount;
end;

destructor TDXAudioIn.Destroy;
begin
  if LibdswLoaded then DSW_Term(@DSW);
  inherited Destroy;
end;

procedure TDXAudioIn.OpenAudio;
var
  Res : HResult;
  BufSize : Integer;
begin
  BufSize := BufferSize;
  if FOpened = 0 then
  begin
    DSW_Init(@DSW);
    if not Assigned(DSW_InitInputDevice) then raise EACSException.Create(Format(strChannelNotAvailable,[FDeviceNumber]));
    Res := DSW_InitInputDevice(@DSW, Devices.dinfo[FDeviceNumber].guid);
    if Res <> 0 then raise EACSException.Create(strFailedtoCreateDSdev);
    Res := DSW_InitInputBuffer(@DSW, FBPS, FFreq, FChan, BufSize);
    if Res <> 0 then raise EACSException.Create(strFailedtoCreateDSbuf);
  end;
  Inc(FOpened);
end;

procedure TDXAudioIn.CloseAudio;
begin
  if FOpened = 1 then DSW_Term(@DSW);
  if FOpened > 0 then Dec(FOpened);
end;

function TDXAudioIn.GetBPS : Integer;
begin
  Result := FBPS;
end;

function TDXAudioIn.GetCh : Integer;
begin
  Result := FChan;
end;

function TDXAudioIn.GetSR : Integer;
begin
  Result := FFreq;
end;

procedure TDXAudioIn.Init;
begin
  if Busy then raise EACSException.Create(strBusy);
  if (FDeviceNumber >= FDeviceCount) then raise EACSException.Create(Format(strChannelnotavailable,[FDeviceNumber]));
  if FRecTime > 0 then FBytesToRead := FRecTime*FFreq*FChan*(FBPS div 8);
  BufEnd := 0;
  BufStart := 1;
  FPosition := 0;
  FBusy := True;
  FSize := FBytesToRead;
  OpenAudio;
  DSW_StartInput(@DSW);
end;

procedure TDXAudioIn.Flush;
begin
  DSW_StopInput(@DSW);
  CloseAudio;
  FBusy := False;
end;

function TDXAudioIn.GetData(Buffer : Pointer; BufferSize : Integer): Integer;
var
  l : Integer;
begin
  if not Busy then  raise EACSException.Create(strStreamnotopen);
  if  (FBytesToRead >=0) and (FPosition >= FBytesToRead) then
  begin
    Result := 0;
    Exit;
  end;
  if BufStart >= BufEnd then
  begin
    BufStart := 0;
    Sleep(DS_POLLING_INTERVAL);
    DSW_QueryInputFilled(@DSW, l);
    if l > BufferSize then
      l := BufferSize; (* We have lost some data.
                          Generally this shouldn't happen. *)
    l := l - (l mod 1024);
    DSW_ReadBlock(@DSW, @FBuffer, l);
    BufEnd := l;
  end;
  if BufferSize < (BufEnd - BufStart)
  then Result := BufferSize
  else Result := BufEnd - BufStart;
  Move(FBuffer[BufStart], Buffer^, Result);
  Inc(BufStart, Result);
  Inc(FPosition, Result);
end;

procedure TDXAudioIn.SetRecTime;
begin
  FRecTime := aRecTime;
  if FRecTime > 0 then
    FBytesToRead := FRecTime*FFreq*FChan*(FBPS div 8)
  else
    FBytesToRead := -1;
end;

procedure TDXAudioIn.SetDevice(i : Integer);
begin
  FDeviceNumber := i
end;

function TDXAudioIn.GetDeviceName(Number : Integer) : String;
begin
  if (Number < FDeviceCount) then Result := PChar(@(Devices.dinfo[Number].Name[0]))
  else Result := '';
end;

function TDXAudioIn.GetTotalTime : real;
var
  BytesPerSec : Integer;
begin
  BytesPerSec := FFreq*FChan*(FBPS div 8);
  Result := FBytesToRead/BytesPerSec;
end;

initialization

  Libhandle := LoadLibraryEx('dswrapper.dll', 0, 0);

  if Libhandle <> 0 then
  begin
    LibdswLoaded := True;

    DSW_Init := GetProcAddress(Libhandle, 'DSW_Init');
    DSW_Term := GetProcAddress(Libhandle, 'DSW_Term');
    DSW_InitOutputDevice := GetProcAddress(Libhandle, 'DSW_InitOutputDevice');
    DSW_InitOutputBuffer := GetProcAddress(Libhandle, 'DSW_InitOutputBuffer');
    DSW_StartOutput := GetProcAddress(Libhandle, 'DSW_StartOutput');
    DSW_StopOutput := GetProcAddress(Libhandle, 'DSW_StopOutput');
    DSW_RestartOutput := GetProcAddress(Libhandle, 'DSW_RestartOutput');
    DSW_GetOutputStatus := GetProcAddress(Libhandle, 'DSW_GetOutputStatus');
    DSW_WriteBlock := GetProcAddress(Libhandle, 'DSW_WriteBlock');
    DSW_ZeroEmptySpace := GetProcAddress(Libhandle, 'DSW_ZeroEmptySpace');
    DSW_QueryOutputSpace := GetProcAddress(Libhandle, 'DSW_QueryOutputSpace');
    DSW_Enumerate := GetProcAddress(Libhandle, 'DSW_Enumerate');
    DSW_InitInputDevice := GetProcAddress(Libhandle, 'DSW_InitInputDevice');
    DSW_InitInputBuffer := GetProcAddress(Libhandle, 'DSW_InitInputBuffer');
    DSW_StartInput := GetProcAddress(Libhandle, 'DSW_StartInput');
    DSW_StopInput := GetProcAddress(Libhandle, 'DSW_StopInput');
    DSW_ReadBlock := GetProcAddress(Libhandle, 'DSW_ReadBlock');
    DSW_QueryInputFilled := GetProcAddress(Libhandle, 'DSW_QueryInputFilled');

  end;
  
  RegisterAudioOut('DirectSound',TDXAudioOut,LATENCY);
  RegisterAudioIn('DirectSound',TDXAudioIn,LATENCY);

finalization

  if Libhandle <> 0 then FreeLibrary(Libhandle);

end.
