unit _Asio_Moonlight;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, openasio, _asio_const;

{$WARN UNIT_PLATFORM OFF}
{$WARN SYMBOL_PLATFORM OFF}

const
  // private message
  PM_ASIO=WM_User+1652;// unique we hope
  PM_UpdateSamplePos=PM_ASIO+1; // sample pos in wParam (hi) and lParam (lo)

  // asio message(s), as wParam for PM_ASIO
  AM_ResetRequest=0;
  AM_BufferSwitch=1; // new buffer index in lParam
  AM_BufferSwitchTimeInfo=2; // new buffer index in lParam
  // timepassed in MainForm.BufferTime
  AM_LatencyChanged=3;

const
  ASIOSTInt16MSB   = 0;
  ASIOSTInt24MSB   = 1;		// used for 20 bits as well
  ASIOSTInt32MSB   = 2;
  ASIOSTFloat32MSB = 3;		// IEEE 754 32 bit float
  ASIOSTFloat64MSB = 4;		// IEEE 754 64 bit double float

  // these are used for 32 bit data buffer, with different alignment of the data inside
  // 32 bit PCI bus systems can be more easily used with these
  ASIOSTInt32MSB16 = 8;		// 32 bit data with 18 bit alignment
  ASIOSTInt32MSB18 = 9;		// 32 bit data with 18 bit alignment
  ASIOSTInt32MSB20 = 10;		// 32 bit data with 20 bit alignment
  ASIOSTInt32MSB24 = 11;		// 32 bit data with 24 bit alignment

  ASIOSTInt16LSB   = 16;
  ASIOSTInt24LSB   = 17;		// used for 20 bits as well
  ASIOSTInt32LSB   = 18;
  ASIOSTFloat32LSB = 19;		// IEEE 754 32 bit float, as found on Intel x86 architecture
  ASIOSTFloat64LSB = 20; 		// IEEE 754 64 bit double float, as found on Intel x86 architecture

  // these are used for 32 bit data buffer, with different alignment of the data inside
  // 32 bit PCI bus systems can more easily used with these
  ASIOSTInt32LSB16 = 24;		// 32 bit data with 18 bit alignment
  ASIOSTInt32LSB18 = 25;		// 32 bit data with 18 bit alignment
  ASIOSTInt32LSB20 = 26;		// 32 bit data with 20 bit alignment
  ASIOSTInt32LSB24 = 27;    // 32 bit data with 24 bit alignment

type
  TFreqInfo=record
    Freq:integer;
    Supported:boolean;
  end;

type
  TOutASIO = class
  private
    { Private 宣言 }
    MemSync:TMultiReadExclusiveWriteSynchronizer;
    SafeTimeMS,PCMRate:dword;
    BufferTime:TAsioTime;
    callbacks:TASIOCallbacks;
    CurrentDriver:integer;
    CurrentChannelType:integer;
    BufferSamplesCount:integer;
    CurrentTimeInfo:string;
    BufferInfo:PAsioBufferInfo;
    function RestartDriver:boolean;
    procedure WndProc(var Msg:TMessage);
    procedure PMAsio(var Message: TMessage);
    procedure PMUpdateSamplePos(var Message: TMessage);
    procedure BufferSwitch(index: integer);
    procedure BufferSwitchTimeInfo(index: integer; const params: TAsioTime);
    function OpenDriver(idx:integer):boolean;
    procedure CloseDriver;
    function CreateBuffer(LeftCh,RightCh:integer;LatencyBuffer:integer):boolean;
    procedure FreeBuffer;
    function CanSampleRate(freq:integer):boolean;
  public
    { Public 宣言 }
    Driver:IOpenAsio;
    DriverListCount:integer;
    DriverList:TAsioDriverList;
    FreqInfoCount:integer;
    FreqInfo:array of TFreqInfo;
    ChannelCount:integer;
    ChannelInfo:array of TASIOChannelInfo;
    constructor Create;
    destructor Destroy; override;
    function isAssignedDriver:boolean;
    function Play(_SafeTimeMS,_PCMRate:dword;DriverIndex:integer;lch,rch:integer;LatencyBuffer:integer):string;
    procedure Stop;
    procedure OpenSetting(DriverIndex:integer);
    function GetDriverInfo(DriverIndex:integer):string;
    procedure GetBufferSize(DriverIndex:integer;var min,max,pref,gran:integer);
    function GetCurrentSampleTimeInfo:string;
    procedure SetBuffer(var buf:array of smallint;ChannelType:integer;BufferSamplesCount:integer;ch0buf,ch1buf:Pointer);
    function EnableSampleRate(freq:integer):boolean;
    function FindSampleRate:integer;
  end;

var
  OutASIO:TOutASIO;

function ASIO_GetOpenAsioLoaded:boolean;
function ASIO_ChannelTypeToString(vType:TAsioSampleType):string;

// -----------------------------------
// ASIO Callbacks
// -----------------------------------

procedure CB_BufferSwitch(doubleBufferIndex: longint; directProcess: TASIOBool); cdecl;
procedure CB_SampleRateDidChange(sRate: TASIOSampleRate); cdecl;
function CB_Message(selector, value: longint; message: pointer; opt: pdouble): longint; cdecl;
function CB_BufferSwitchTimeInfo(var params: TASIOTime; doubleBufferIndex: longint; directProcess: TASIOBool): PASIOTime; cdecl;

implementation

uses _PCMOut,
     _const;

var
  MsgHandle:HWND;

function ASIO_GetOpenAsioLoaded:boolean;
begin
  Result:=OpenAsioLoaded;
end;

function ASIO_ChannelTypeToString(vType:TAsioSampleType):string;
begin
  Result:='';
  case vType of
    ASIOSTInt16MSB   :  Result:='Int16MSB';
    ASIOSTInt24MSB   :  Result:='Int24MSB';
    ASIOSTInt32MSB   :  Result:='Int32MSB';
    ASIOSTFloat32MSB :  Result:='Float32MSB';
    ASIOSTFloat64MSB :  Result:='Float64MSB';

    // these are used for 32 bit data buffer, with different alignment of the data inside
    // 32 bit PCI bus systems can be more easily used with these
    ASIOSTInt32MSB16 :  Result:='Int32MSB16';
    ASIOSTInt32MSB18 :  Result:='Int32MSB18';
    ASIOSTInt32MSB20 :  Result:='Int32MSB20';
    ASIOSTInt32MSB24 :  Result:='Int32MSB24';

    ASIOSTInt16LSB   :  Result:='Int16LSB';
    ASIOSTInt24LSB   :  Result:='Int24LSB';
    ASIOSTInt32LSB   :  Result:='Int32LSB';
    ASIOSTFloat32LSB :  Result:='Float32LSB';
    ASIOSTFloat64LSB :  Result:='Float64LSB';

    // these are used for 32 bit data buffer, with different alignment of the data inside
    // 32 bit PCI bus systems can more easily used with these
    ASIOSTInt32LSB16 :  Result:='Int32LSB16';
    ASIOSTInt32LSB18 :  Result:='Int32LSB18';
    ASIOSTInt32LSB20 :  Result:='Int32LSB20';
    ASIOSTInt32LSB24 :  Result:='Int32LSB24';
  end;
end;

// -----------------------------------
// Interface
// -----------------------------------

function TOutASIO.isAssignedDriver:boolean;
begin
  if OutASIO.Driver<>nil then begin
    Result:=True;
    end else begin
    Result:=False;
  end;
end;

constructor TOutASIO.Create;
begin
  inherited Create;

  MemSync:=TMultiReadExclusiveWriteSynchronizer.Create;

  MemSync.BeginWrite;

  MsgHandle:=0;

  SafeTimeMS:=0;
  PCMRate:=0;

  FreqInfoCount:=9;
  SetLength(FreqInfo,FreqInfoCount);
  FreqInfo[0].Freq:=11025;
  FreqInfo[0].Supported:=False;
  FreqInfo[1].Freq:=22050;
  FreqInfo[1].Supported:=False;
  FreqInfo[2].Freq:=32000;
  FreqInfo[2].Supported:=False;
  FreqInfo[3].Freq:=44100;
  FreqInfo[3].Supported:=False;
  FreqInfo[4].Freq:=48000;
  FreqInfo[4].Supported:=False;
  FreqInfo[5].Freq:=88200;
  FreqInfo[5].Supported:=False;
  FreqInfo[6].Freq:=96000;
  FreqInfo[6].Supported:=False;
  FreqInfo[7].Freq:=176400;
  FreqInfo[7].Supported:=False;
  FreqInfo[8].Freq:=192000;
  FreqInfo[8].Supported:=False;

  CurrentDriver:=-1;
  Driver:=nil;
  BufferInfo:=nil;
  BufferSamplesCount:=0;

  CurrentTimeInfo:='';

  // set the callbacks record fields
  callbacks.bufferSwitch:=CB_BufferSwitch;
  callbacks.sampleRateDidChange:=CB_SampleRateDidChange;
  callbacks.asioMessage:=CB_Message;
  callbacks.bufferSwitchTimeInfo:=CB_BufferSwitchTimeInfo;

  SetLength(driverlist,0);
  driverlistcount:=ListAsioDrivers(driverlist);

  CurrentChannelType:=-1;
  ChannelCount:=0;
  setlength(ChannelInfo,ChannelCount);

  MemSync.EndWrite;

  OpenAsioLoad;
  if ASIO_GetOpenAsioLoaded=False then begin
    ShowMessage('OpenAsio.dllが見つかりませんでした。');
  end;
end;

destructor TOutASIO.Destroy;
begin
  Stop;

  MemSync.BeginWrite;

  OpenAsioFree;
  if MsgHandle<>0 then begin
    Classes.DeallocateHWnd(MsgHandle);
    MsgHandle:=0;
  end;

  MemSync.EndWrite;

  MemSync.Free;

  inherited Destroy;
end;

// ---------------------------------------------------------
// asio private utilities
// ---------------------------------------------------------

function TOutASIO.RestartDriver:boolean;
var
  LastDriver:integer;
begin
  LastDriver:=CurrentDriver;
  OutASIO.CloseDriver;
  Result:=OutASIO.OpenDriver(LastDriver);
end;

procedure TOutASIO.BufferSwitch(index: integer);
begin
  MemSync.BeginWrite;

  FillChar(BufferTime, SizeOf(TAsioTime), 0);

  // get the time stamp of the buffer, not necessary if no
  // synchronization to other media is required
  if Driver.GetSamplePosition(BufferTime.timeInfo.samplePosition, BufferTime.timeInfo.systemTime) = ASE_OK then
    BufferTime.timeInfo.flags := kSystemTimeValid or kSamplePositionValid;

  BufferSwitchTimeInfo(index, BufferTime);

  MemSync.EndWrite;
end;

procedure TOutASIO.BufferSwitchTimeInfo(index: integer; const params: TAsioTime);
var
  cbuf0,cbuf1:PAsioBufferInfo;
  ch0buf,ch1buf:Pointer;
begin
  // this is where processing occurs, with the buffers provided by Driver.CreateBuffers
  // beware of the buffer output format, of course


  // tell the interface that the sample position has changed
//  PostMessage(MsgHandle, PM_UpdateSamplePos, params.timeInfo.samplePosition.hi, params.timeInfo.samplePosition.lo);

  MemSync.BeginWrite;

  if BufferSamplesCount<>0 then begin
    cbuf0:=OutASIO.BufferInfo;
    cbuf1:=OutASIO.BufferInfo;
    inc(cbuf1);
    ch0buf:=cbuf0.buffers[index];
    ch1buf:=cbuf1.buffers[index];
{$ifdef mdxwin}
    PCMOut.UpdateAsioMonitor(CurrentChannelType,BufferSamplesCount,ch0buf,ch1buf);
{$endif}
  end;

  Driver.OutputReady;    // some asio drivers require this

  MemSync.EndWrite;
end;

function TOutASIO.OpenDriver(idx:integer):boolean;
var
  inp,cnt:integer;
begin
  if Driver<>nil then Driver.Stop;
  FreeBuffer;
  CloseDriver;

  if MsgHandle<>0 then begin
    Classes.DeallocateHWnd(MsgHandle);
    MsgHandle:=0;
  end;

  CurrentDriver:=-1;
  Driver:=nil;
  if (idx<0) or (driverlistcount<=idx) then begin
    Result:=False;
    exit;
  end;

  if OpenAsioCreate(driverList[idx].id, Driver) then begin
    if Driver<>nil then begin
      MsgHandle:=Classes.AllocateHWnd(WndProc);
      if not Succeeded(Driver.Init(MsgHandle)) then Driver:=nil;
    end;
  end;

  if Driver=nil then begin
    ChannelCount:=0;
    end else begin
    CurrentDriver:=idx;
    Driver.GetChannels(inp,ChannelCount);
    SetLength(ChannelInfo,ChannelCount);
    for cnt:=0 to ChannelCount-1 do begin
      ChannelInfo[cnt].channel:=cnt;
      ChannelInfo[cnt].isInput:=ASIOFalse;   //  output
      Driver.GetChannelInfo(ChannelInfo[cnt]);
    end;
    for cnt:=0 to FreqInfoCount-1 do begin
      if Driver.CanSampleRate(FreqInfo[cnt].Freq)=ASE_OK then begin
        FreqInfo[cnt].Supported:=True;
        end else begin
        FreqInfo[cnt].Supported:=False;
      end;
    end;
  end;

  if Driver=nil then begin
    Result:=False;
    end else begin
    Result:=True;
  end;
end;

procedure TOutASIO.CloseDriver;
begin
  if OutASIO.Driver=nil then exit;
  if MsgHandle<>0 then begin
    Classes.DeallocateHWnd(MsgHandle);
    MsgHandle:=0;
  end;
  CurrentDriver:=-1;
  Driver:=nil;
end;

function TOutASIO.CreateBuffer(LeftCh,RightCh:integer;LatencyBuffer:integer):boolean;
var
   min, max, pref, gran : integer;
   currentbuffer        : PAsioBufferInfo;
begin
  if Driver=nil then begin
    Result:=False;
    exit;
  end;
  if (LeftCh<0) or (ChannelCount<=LeftCh) then begin
    Result:=False;
    exit;
  end;
  if (RightCh<0) or (ChannelCount<=RightCh) then begin
    Result:=False;
    exit;
  end;
  if LeftCh=RightCh then begin
    Result:=False;
    exit;
  end;
  if ChannelInfo[LeftCh].vType<>ChannelInfo[RightCh].vType then begin
    Result:=False;
    exit;
  end;

  GetMem(BufferInfo, SizeOf(TAsioBufferInfo)*2);
  currentbuffer := BufferInfo;

  currentbuffer^.isInput:=ASIOFalse;
  currentbuffer^.channelNum:=LeftCh;
  currentbuffer^.buffers[0]:=nil;
  currentbuffer^.buffers[1]:=nil;
  inc(currentbuffer);
  currentbuffer^.isInput:=ASIOFalse;
  currentbuffer^.channelNum:=RightCh;
  currentbuffer^.buffers[0]:=nil;
  currentbuffer^.buffers[1]:=nil;

  // actually create the buffers
  Driver.GetBufferSize(min, max, pref, gran);
  if gran=0 then gran:=1;

  LatencyBuffer:=(LatencyBuffer div gran)*gran;
  if (LatencyBuffer<min) or (max<LatencyBuffer) then LatencyBuffer:=pref;
  if Driver.CreateBuffers(BufferInfo, 2, LatencyBuffer, callbacks)=ASE_OK then begin
    CurrentChannelType:=ChannelInfo[LeftCh].vType;
    BufferSamplesCount:=LatencyBuffer;
    Result:=True;
    end else begin
    CurrentChannelType:=-1;
    BufferSamplesCount:=0;
    Result:=False;
  end;
end;

procedure TOutASIO.FreeBuffer;
begin
  if Driver=nil then exit;
  if BufferInfo=nil then exit;

  FreeMem(BufferInfo);
  BufferInfo:=nil;
  CurrentChannelType:=-1;
  BufferSamplesCount:=0;
  Driver.DisposeBuffers;
end;

function TOutASIO.CanSampleRate(freq:integer):boolean;
begin
  if OutASIO.Driver.CanSampleRate(freq)<>ASE_OK then begin
    Result:=False;
    end else begin
    Result:=True;
  end;
end;

// ---------------------------------------------------------
// asio utilities
// ---------------------------------------------------------

function TOutASIO.Play(_SafeTimeMS,_PCMRate:dword;DriverIndex:integer;lch,rch:integer;LatencyBuffer:integer):string;
var
  res:integer;
begin
  if _SafeTimeMS<>0 then SafeTimeMS:=_SafeTimeMS;
  if _PCMRate<>0 then PCMRate:=_PCMRate;

  if OpenDriver(DriverIndex)=False then begin
    Result:='ASIOドライバが開始できませんでした。';
    exit;
  end;

  if (lch<0) or (ChannelCount<=lch) then begin
    Result:='左出力'+inttostr(lch)+'は存在しないチャネルです。';
    CloseDriver;
    exit;
  end;
  if (rch<0) or (ChannelCount<=rch) then begin
    Result:='右出力'+inttostr(rch)+'は存在しないチャネルです。';
    CloseDriver;
    exit;
  end;
  if lch=rch then begin
    Result:='左出力と右出力が同じチャネルに割り当てられています。';
    CloseDriver;
    exit;
  end;
  if ChannelInfo[lch].vType<>ChannelInfo[rch].vType then begin
    Result:='左出力チャネルと右出力チャネルのフォーマットが違います。';
    CloseDriver;
    exit;
  end;
  if CanSampleRate(PCMRate)=False then begin
    Result:='対応していない周波数でASIOドライバを開始しようとしたため失敗しました。';
    CloseDriver;
    exit;
  end;
  if Driver.SetSampleRate(PCMRate*1.0)<>ASE_OK then begin
    Result:='対応していない周波数でASIOドライバを開始したため失敗しました。';
    CloseDriver;
    exit;
  end;
  if CreateBuffer(lch,rch,LatencyBuffer)=False then begin
    Result:='ASIOバッファが作成できませんでした。';
    FreeBuffer;
    CloseDriver;
    exit;
  end;

  res:=Driver.Start;
  if res<>ASE_OK then begin
    Result:='ASIOドライバからエラーを受け取りました。'+CRLF+'ASIOドライバを使用中のアプリケーションがあるかもしれません。'+CRLF+CRLF;
    case res of
      ASE_SUCCESS          : Result:=Result+'unique success return value for ASIOFuture calls.';
      ASE_NotPresent       : Result:=Result+'hardware input or output is not present or available.';
      ASE_HWMalfunction    : Result:=Result+'hardware is malfunctioning. (can be returned by any ASIO function)';
      ASE_InvalidParameter : Result:=Result+'input parameter invalid.';
      ASE_InvalidMode      : Result:=Result+'hardware is in a bad mode or used in a bad mode.';
      ASE_SPNotAdvancing   : Result:=Result+'hardware is not running when sample position is inquired.';
      ASE_NoClock          : Result:=Result+'sample clock or rate cannot be determined or is not present.';
      ASE_NoMemory         : Result:=Result+'not enough memory for completing the request.';
    end;
    FreeBuffer;
    CloseDriver;
    exit;
  end;

  Result:='';
end;

procedure TOutASIO.Stop;
begin
  if isAssignedDriver=True then begin
    if Driver<>nil then Driver.Stop;
    FreeBuffer;
    CloseDriver;
  end;
end;

procedure TOutASIO.OpenSetting(DriverIndex:integer);
begin
  if OpenDriver(DriverIndex)=False then begin
    ShowMessage('ASIOドライバーが開けませんでした。');
    exit;
  end;
  Driver.ControlPanel;
  CloseDriver;
end;

function TOutASIO.GetDriverInfo(DriverIndex:integer):string;
var
  buf:array[0..255] of char;
  min,max,pref,gran:integer;
  cnt:integer;
  inlate,outlate:integer;
begin
  MemSync.BeginWrite;

  if OpenDriver(DriverIndex)=False then begin
    Result:='ASIOドライバが開けませんでした。';
    end else begin
    Result:='';
    Driver.GetDriverName(buf);
    Result:=Result+'Name:'+buf+CRLF;
    Result:=Result+'DriverVersion:$'+inttohex(OutASIO.Driver.GetDriverVersion,8)+CRLF;
    Result:=Result+'OutputChannels:'+inttostr(OutASIO.ChannelCount)+CRLF;
    Driver.GetBufferSize(min,max,pref,gran);
    Result:=Result+'BufferSize(min):'+inttostr(min)+CRLF;
    Result:=Result+'BufferSize(max):'+inttostr(max)+CRLF;
    Result:=Result+'BufferSize(pref):'+inttostr(pref)+CRLF;
    Result:=Result+'BufferSize(gran):'+inttostr(gran)+CRLF;
    Driver.GetLatencies(inlate,outlate);
    Result:=Result+'Latency(Output):'+inttostr(outlate)+CRLF;
    Result:=Result+'Latency(Input):'+inttostr(inlate)+CRLF;
    Result:=Result+''+CRLF;
    for cnt:=0 to FreqInfoCount-1 do begin
      with FreqInfo[cnt] do begin
        if Supported=True then begin
          Result:=Result+'○ '+inttostr(Freq)+'Hz supported.'+CRLF;
          end else begin
          Result:=Result+'× '+inttostr(Freq)+'Hz not supported.'+CRLF;
        end;
      end;
    end;
    Result:=Result+''+CRLF;
    for cnt:=0 to OutASIO.ChannelCount-1 do begin
      with OutASIO.ChannelInfo[cnt] do begin
        if (vType=ASIOSTInt16LSB) or (vType=ASIOSTInt24LSB) or (vType=ASIOSTInt32LSB) then begin
          Result:=Result+'○ ';
          end else begin
          if (vType=ASIOSTFloat32LSB) or (vType=ASIOSTFloat64LSB) then begin
            Result:=Result+'△ ';
            end else begin
            Result:=Result+'× ';
          end;
        end;
        Result:=Result+'ch'+inttostr(channel)+':'+ASIO_ChannelTypeToString(vType)+' ['+name+']'+CRLF;
      end;
    end;
  end;

  CloseDriver;

  MemSync.EndWrite;
end;

procedure TOutASIO.GetBufferSize(DriverIndex:integer;var min,max,pref,gran:integer);
begin
  MemSync.BeginWrite;

  if OpenDriver(DriverIndex)=False then begin
    min:=0;
    max:=0;
    pref:=0;
    gran:=1;
    end else begin
    Driver.GetBufferSize(min,max,pref,gran);
    if gran=0 then gran:=1;
    if pref<1024 then begin
      if max<1024 then begin
        pref:=max;
        end else begin
        pref:=(1024 div gran)*gran;
      end;
    end;
  end;

  CloseDriver;

  MemSync.EndWrite;
end;

function TOutASIO.GetCurrentSampleTimeInfo:string;
begin
  MemSync.BeginWrite;
  Result:=CurrentTimeInfo;
  CurrentTimeInfo:='';
  MemSync.EndWrite;
end;

type
  PwordArray = ^TwordArray;
  TwordArray = array[0..65536*10] of word;
  TTriByte=packed record
    b:byte;
    w:word;
  end;
  Tdword2=packed record
    w0,w1:word;
  end;
  PTriByteArray = ^TTriByteArray;
  TTriByteArray = array[0..65536*10] of TTriByte;
  PdwordArray = ^TdwordArray;
  TdwordArray = array[0..65536*10] of dword;
  Pdword2 = ^Tdword2;
  PSingleArray = ^TSingleArray;
  TSingleArray = array[0..65536*10] of Single;
  PDoubleArray = ^TDoubleArray;
  TDoubleArray = array[0..65536*10] of Double;

procedure TOutASIO.SetBuffer(var buf:array of smallint;ChannelType:integer;BufferSamplesCount:integer;ch0buf,ch1buf:Pointer);
var
  bufi160,bufi161:PwordArray;
  bufi240,bufi241:PTriByteArray;
  bufi320,bufi321:PdwordArray;
  buff320,buff321:PSingleArray;
  buff640,buff641:PDoubleArray;
  cnt:integer;
  sibuf:PSmallInt;
  swbuf:PWord;
begin
  if BufferSamplesCount=0 then exit;
  if @buf=nil then exit;

  MemSync.BeginWrite;

  sibuf:=@buf[0];
  swbuf:=@buf[0];

  case ChannelType of
    ASIOSTInt16LSB: begin
      bufi160:=ch0buf;
      bufi161:=ch1buf;
      for cnt:=0 to BufferSamplesCount-1 do begin
        bufi160[cnt]:=swbuf^;
        inc(swbuf);
        bufi161[cnt]:=swbuf^;
        inc(swbuf);
      end;
    end;
    ASIOSTInt24LSB: begin
      bufi240:=ch0buf;
      bufi241:=ch1buf;
      for cnt:=0 to BufferSamplesCount-1 do begin
        bufi240[cnt].b:=$00;
        bufi240[cnt].w:=swbuf^;
        inc(swbuf);
        bufi241[cnt].b:=$00;
        bufi241[cnt].w:=swbuf^;
        inc(swbuf);
      end;
    end;
    ASIOSTInt32LSB: begin
      bufi320:=ch0buf;
      bufi321:=ch1buf;
      for cnt:=0 to BufferSamplesCount-1 do begin
        bufi320[cnt]:=dword(swbuf^) shl 16;
        inc(swbuf);
        bufi321[cnt]:=dword(swbuf^) shl 16;
        inc(swbuf);
      end;
    end;
    ASIOSTFloat32LSB: begin
      buff320:=ch0buf;
      buff321:=ch1buf;
      for cnt:=0 to BufferSamplesCount-1 do begin
        buff320[cnt]:=sibuf^;
        inc(sibuf);
        buff321[cnt]:=sibuf^;
        inc(sibuf);
      end;
    end;
    ASIOSTFloat64LSB: begin
      buff640:=ch0buf;
      buff641:=ch1buf;
      for cnt:=0 to BufferSamplesCount-1 do begin
        buff640[cnt]:=sibuf^;
        inc(sibuf);
        buff641[cnt]:=sibuf^;
        inc(sibuf);
      end;
    end;
  end;

  MemSync.EndWrite;
end;

function TOutASIO.EnableSampleRate(freq:integer):boolean;
var
  cnt:integer;
begin
  Result:=False;
  for cnt:=0 to FreqInfoCount-1 do begin
    if FreqInfo[cnt].Freq=freq then begin
      if FreqInfo[cnt].Supported=True then begin
        Result:=True;
        exit;
      end;
    end;
  end;
end;

function TOutASIO.FindSampleRate:integer;
var
  cnt:integer;
begin
  Result:=0;
  for cnt:=0 to FreqInfoCount-1 do begin
    if FreqInfo[cnt].Supported=True then begin
      Result:=FreqInfo[cnt].Freq;
      exit;
    end;
  end;
end;

// ---------------------------------------------------------
// ASIO WindowProc
// ---------------------------------------------------------

procedure TOutASIO.WndProc(var Msg:TMessage);
begin
  MemSync.BeginWrite;
  case Msg.Msg of
    PM_Asio: OutASIO.PMAsio(Msg);
    PM_UpdateSamplePos: OutASIO.PMUpdateSamplePos(Msg);
  end;
  MemSync.EndWrite;
end;

procedure TOutASIO.PMAsio(var Message: TMessage);
begin
  MemSync.BeginWrite;
  case Message.WParam of
    AM_ResetRequest         :  OutASIO.RestartDriver; // restart the driver
    AM_BufferSwitch         :  OutASIO.BufferSwitch(Message.LParam); // process a buffer
    AM_BufferSwitchTimeInfo :  OutASIO.BufferSwitchTimeInfo(Message.LParam, BufferTime); // process a buffer with time
//    AM_LatencyChanged       :  Main.ChangeEnabled(-1); // メインスレッドにアクセスするのは危険だしレイテンシ更新に意味はないから無視。
  end;
  MemSync.EndWrite;
end;

procedure TOutASIO.PMUpdateSamplePos(var Message: TMessage);
var
   Samples     : TAsioSamples;
   SampleCount : Int64;
   mseconds:int64;
   seconds     : Int64;
   minutes     : Int64;
   hours       : Int64;
begin
  if CurrentTimeInfo<>'' then exit;

  Samples.hi := Message.wParam;
  Samples.lo := Message.lParam;
  SampleCount := ASIOSamplesToInt64(Samples);

  mseconds:=SampleCount*1000 div 44100;
  seconds := mseconds div 1000;
  hours := seconds div 3600;
  minutes := (seconds mod 3600) div 60;
  seconds := seconds mod 60;
  mseconds := mseconds mod 1000;

  MemSync.BeginWrite;
  CurrentTimeInfo:=Format('time : %d:%.2d:%.2d:%.3d samples:%d', [hours, minutes, seconds,mseconds,SampleCount]);
  MemSync.EndWrite;
end;

// ---------------------------------------------------------
// ASIO callbacks
// ---------------------------------------------------------

procedure CB_BufferSwitch(doubleBufferIndex: longint; directProcess: TASIOBool); cdecl;
begin
  case directProcess of
    ASIOFalse :  PostMessage(MsgHandle, PM_ASIO, AM_BufferSwitch, doubleBufferIndex);
    ASIOTrue  :  OutASIO.BufferSwitch(doubleBufferIndex);
  end;
end;

procedure CB_SampleRateDidChange(sRate: TASIOSampleRate); cdecl;
begin
  MessageDlg('周波数が変更されました。'+FloatToStr(sRate)+'Hz.', mtInformation, [mbOK], 0);
end;

function CB_Message(selector, value: longint; message: pointer; opt: pdouble): longint; cdecl;
begin
  Result := 0;

  case selector of
    kAsioSelectorSupported    :   // return 1 if a selector is supported
      begin
        case value of
          kAsioEngineVersion        :  Result := 1;
          kAsioResetRequest         :  Result := 1;
          kAsioBufferSizeChange     :  Result := 0;
          kAsioResyncRequest        :  Result := 1;
          kAsioLatenciesChanged     :  Result := 1;
          kAsioSupportsTimeInfo     :  Result := 1;
          kAsioSupportsTimeCode     :  Result := 1;
          kAsioSupportsInputMonitor :  Result := 0;
        end;
      end;
    kAsioEngineVersion        :  Result := 2;   // ASIO 2 is supported
    kAsioResetRequest         :
      begin
        PostMessage(MsgHandle, PM_Asio, AM_ResetRequest, 0);
        Result := 1;
      end;
    kAsioBufferSizeChange     :
      begin
        PostMessage(MsgHandle, PM_Asio, AM_ResetRequest, 0);
        Result := 1;
      end;
    kAsioResyncRequest        :  ;
    kAsioLatenciesChanged     :
      begin
        PostMessage(MsgHandle, PM_Asio, AM_LatencyChanged, 0);
        Result := 1;
      end;
    kAsioSupportsTimeInfo     :  Result := 1;
    kAsioSupportsTimeCode     :  Result := 0;
    kAsioSupportsInputMonitor :  ;
  end;
end;

function CB_BufferSwitchTimeInfo(var params: TASIOTime; doubleBufferIndex: longint; directProcess: TASIOBool): PASIOTime; cdecl;
begin
  case directProcess of
    ASIOFalse :
      begin
        OutASIO.BufferTime := params;
        PostMessage(MsgHandle, PM_ASIO, AM_BufferSwitchTimeInfo, doubleBufferIndex);
      end;
    ASIOTrue  :  OutASIO.BufferSwitchTimeInfo(doubleBufferIndex, params);
  end;

  Result := nil;
end;

initialization
  OutASIO:=nil;

finalization
  if OutASIO<>nil then begin
    OutASIO.Free;
    OutASIO:=nil;
  end;

end.
