(******************************************************************************

  Do the DirectSound with Delphi
     MCMXCVII,MCMXCVIII,MCMXCIX,MM (c) SANDMAN

******************************************************************************)
unit ddsd;


interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  DSound, OLE2, MMSystem, d3dtypes,QDArc;


//C++Builder3.0, 4.0では、シンボル FOR_CPPBUILDER が定義される
{$IFDEF VER110}
  {$DEFINE FOR_CPPBUILDER}
{$ENDIF}

{$IFDEF VER125}
  {$DEFINE FOR_CPPBUILDER}
{$ENDIF}


{ま、コンポーネントだし}
procedure Register;

(****************************************************************************
 *
 * デバッグ用変数…TDDSD.DebugOptionで制御されます。
 *
 ****************************************************************************)
var
  DDSD_HaltOnError:Boolean;        {エラーが起きたらすぐにHaltする}
  DDSD_DebugMessage:Boolean;       {デバッグメッセージをファイルに書き出す}
  DDSD_ExceptOnError:Boolean;      {エラーが起きたら例外を発生する}

//デバッグオプション
type TDDSDDebugOption = set of (dsoHaltOnError, dsoDebugMessage, dsoExceptOnError);


(******************************************************************************
  ちょっとしたレコード
******************************************************************************)
//3D音源の減衰開始・減衰完了距離
type TDDSDDistance = record
  Min,Max:D3DValue;
end;

//コーン角度
type TDDSDConeAngle = record
  Inside,Outside:DWord;
end;

//リスナーの向き
type TDDSDOrientation = record
  Front,Top:D3DVector;
end;

(******************************************************************************
  クラス
******************************************************************************)
type

TDDSDGenWave = class;
TDDSDChannel = class;
TDDSDCapture = class;

//ストリーム更新イベント
TDDSDUpdateEvent = procedure (Sender:TDDSDGenWave; Player:TDDSDChannel; availableOffset,bufferSize:DWord) of object;

//キリのいいとこまでキャプチャしたざんすイベント
TDDSDRecordingEvent = procedure (Sender:TDDSDCapture; StartOffset,bufferSize:DWord) of object;


//オーディオストリーム更新用イベント監視スレッド
TDDSDNotifyThread = class(TThread)
  private
    FOwner:TDDSDGenWave; //このスレッドを作り出したTDDSDGenWaveオブジェクト
  public
    IsTop:Boolean;                          //現在、バッファの先頭に再生位置があるか
    constructor Create(owner:TDDSDGenWave);
    procedure Execute; override;
end;

//オーディオストリーム更新用イベント監視スレッド、キャプチャ用
TDDSDCaptureNotifyThread = class(TThread)
  private
    FOwner:TDDSDCapture; //このスレッドを作り出したTDDSDCaptureオブジェクト
  public
    IsTop:Boolean;                          //現在、バッファの先頭に録音位置があるか
    constructor Create(owner:TDDSDCapture);
    procedure Execute; override;
end;


//WAVファイルをカプセル化した物
TDDSDGenWave = class
  private
    FSoundBuffer:IDirectSoundBuffer;
    FWaveFormat:TWaveFormatEx;
    FLength:DWord;                  //バッファの長さ
    FOwner:TObject;                   //オーナーとなるDDSDオブジェクト
    FIsStream:Boolean;                //ストリームかそうでないか
    FHalfPos:DWord;                   //真中のオフセット
    FDSNotify:IDirectSoundNotify;     //Notify用オブジェクト
    FEvent:Array[0..2] of THandle;    //イベントオブジェクトへのハンドル
    FNotifyThread:TDDSDNotifyThread;  //ストリームの場合、イベントの監視スレッド
    FOnUpdate:TDDSDUpdateEvent;       //「バッファを更新しなさい」イベント
    FPlayer:TDDSDChannel;             //このWaveDataを再生しているTDDSDChannelオブジェクト…ストリームの場合のみ有効
    procedure Sync;                   //NotifyThreadから呼ばれて、ストリーム更新イベントを呼ぶルーチン…ほんとはSynchronizeずべきなんだけど…
  public
    constructor Create(OWner:TObject; fname:String; is3D:Boolean); //WAVEファイルを読み込む
    constructor CreateFromQDA(OWner:TObject; QDAname:String; id:String; is3D:Boolean); //QDAからWAVEファイルを読み込む
    constructor CreateEmptyBuffer(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord; is3D:Boolean; isStream:Boolean);

    destructor Destroy; override;
    procedure BlockCopy(offset:DWord; src:Pointer; length:DWord);
    property Buffer:IDirectSoundBuffer read FSoundBuffer;
    property IsStream:Boolean read FIsStream;
    property WaveFormat:TWaveFormatEx read FWaveFormat;
    property Length:DWord read FLength;
    property OnUpdate:TDDSDUpdateEvent read FOnUpdate write FOnUpdate;
end;

//3D音源パラメータ
TDDSDParams3D = class
  private
    FOwner:TObject;  //TDDSDChannelオブジェクト

    FDsb:DS3DBUFFER;  //情報格納用

    function getPosition:D3DVector;
    function getVelocity:D3DVector;
    function getConeOrientation:D3DVector;
    function getConeAngle:TDDSDConeAngle;
    function getConeOutsideVolume:Integer;
    function getMinDistance:D3DValue;
    function getMaxDistance:D3DValue;
    function getRaw:DS3DBuffer;

    procedure setPosition(const v:D3DVector);
    procedure setVelocity(const v:D3DVector);
    procedure setConeOrientation(const v:D3DVector);
    procedure setConeAngle(const v:TDDSDConeAngle);
    procedure setConeOutsideVolume(const v:Integer);
    procedure setMinDistance(const v:D3DValue);
    procedure setMaxDistance(const v:D3DValue);
    procedure setRaw(const v:DS3DBuffer);
  public
    constructor Create(Owner:TObject);
    destructor Destroy; override;
    property Position:D3DVector         read getPosition          write setPosition;
    property Velocity:D3DVector         read getVelocity          write setVelocity;
    property ConeOrientation:D3DVector  read getConeOrientation   write setConeOrientation;
    property ConeAngle:TDDSDConeAngle   read getConeAngle         write setConeAngle;
    property ConeOutsideVolume:Integer  read getConeOutsideVolume write setConeOutsideVolume;
    property MinDistance:D3DValue       read getMinDistance       write setMinDistance;
    property MaxDistance:D3DValue       read getMaxDistance       write setMaxDistance;
    property RawParams:DS3DBuffer       read getRaw               write setRaw;
    procedure Assign(v:TDDSDParams3D);
end;

//リスナーパラメータ
TDDSDListener = class
  private
    FOwner:TObject;     //オーナーとなるDDSDコンポーネント
    FDsl:DS3DListener;  //情報格納用

    function getPosition:D3DVector;
    function getVelocity:D3DVector;
    function getOrientation:TDDSDOrientation;
    function getDistanceFactor:D3DValue;
    function getRollOffFactor:D3DValue;
    function getDopplerFactor:D3DValue;
    function getRaw:DS3DListener;

    procedure setPosition(const v:D3DVector);
    procedure setVelocity(const v:D3DVector);
    procedure setOrientation(const v:TDDSDOrientation);
    procedure setDistanceFactor(const v:D3DValue);
    procedure setRollOffFactor(const v:D3DValue);
    procedure setDopplerFactor(const v:D3DValue);
    procedure setRaw(const v:DS3DListener);

  public
    constructor Create(Owner:TObject);
    destructor Destroy; override;
    property Position:D3DVector       read getPosition          write setPosition;
    property Velocity:D3DVector       read getVelocity          write setVelocity;
    property Orientation:TDDSDOrientation read getOrientation   write setOrientation;
    property DistanceFactor:D3DValue  read getDistanceFactor    write setDistanceFactor;
    property RolloffFactor:D3DValue   read getRollOffFactor     write setRollOffFactor;
    property DopplerFactor:D3DValue   read getDopplerFactor     write setDopplerFactor;
    property RawParams:DS3DListener   read getRaw               write setRaw;
    procedure Assign(v:TDDSDListener);
end;


//WAVファイル(普通の)
TDDSDWaveData = class(TDDSDGenWave)
  public
    //共通コンストラクタ
    constructor Create(OWner:TObject; fname:String);
    constructor CreateFromQDA(OWner:TObject; QDAname:String; id:String); //QDAからWAVEファイルを読み込む

    {$IFNDEF FOR_CPPBUILDER}
    //Delphi用コンストラクタ
    constructor CreateBuffer(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord); //ファイルを読まずにバッファだけ作る
    constructor CreateStream(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord); //ストリームを作成
    {$ELSE}
    //C++Builder用コンストラクタ
    constructor CreateEmptyBuffer(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord; isStream:Boolean);
    {$ENDIF}
    destructor Destroy; override;
end;

//WAVファイル(3D版)
TDDSDWave3D = class(TDDSDGenWave)
  private
    FParams3D:TDDSDParams3D;
  public
    //共通コンストラクタ
    constructor Create(OWner:TObject; fname:String);
    constructor CreateFromQDA(OWner:TObject; QDAname:String; id:String); //QDAからWAVEファイルを読み込む

    {$IFNDEF FOR_CPPBUILDER}
    //Delphi用コンストラクタ
    constructor CreateBuffer(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord); //ファイルを読まずにバッファだけ作る
    constructor CreateStream(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord); //ストリームを作成
    {$ELSE}
    //C++Builder用コンストラクタ
    constructor CreateEmptyBuffer(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord; isStream:Boolean);
    {$ENDIF}

    destructor Destroy; override;
    property Params3D:TDDSDParams3D read FParams3D write FParams3D;
end;



//二次バッファをカプセル化した物
TDDSDChannel = class
  private
    FSoundBuffer:IDirectSoundBuffer;
    F3DBuffer:IDirectSound3DBuffer;

    FIs3D:Boolean;  //3DサウンドデータがWaveDataに指定されている
    FOwner:TObject; //オーナーとなるTDDSDコンポーネント
    FPan:Integer;     //パンポット
    FFreq:DWORD;    //再生周波数
    FSrcFreq:DWORD; //元のWAVの周波数
    FFreqRate:Double; //元の周波数に対する再生周波数の割合
    FVolume:Integer;  //ボリューム(1/100dB単位)
    FWaveData:TDDSDGenWave;
    FParams3D:TDDSDParams3D;
    FIsStream:Boolean; //ストリーミング対応WaveDataを載せているか
    //3D・非3D兼用
    procedure setFreqRate(v:Double);
    procedure setVolume(v:Integer);
    procedure setPan(v:Integer);
    procedure setWaveData(wav:TDDSDGenWave);
    function getStatus:DWORD;
    function getPlayPosition:DWord;   //バッファの何バイト目を現在演奏中か
    procedure setPlayPosition(pos:DWord);  //再生位置の指定
    //3D専用
    procedure setParams3D(v:TDDSDParams3D);
  public
    constructor Create(Owner:TObject);
    destructor Destroy; override;
    procedure Play;
    procedure LoopPlay;
    procedure PlayAt(ofs:DWord);    //バッファの任意のオフセットから再生
    procedure LoopPlayAt(ofs:DWord);
    procedure Stop;
    property Buffer:IDirectSoundBuffer read FSoundBuffer;
    //非3D用
    property Pan:Integer read FPan write setPan;  //パンニングは、3D化している場合無効
    //3D用
    property Params3D:TDDSDParams3D read FParams3D write setParams3D;  //3Dパラメータ
    //両用
    property FreqRate:Double read FFreqRate write setFreqRate;
    property Volume:Integer read FVolume write setVolume;
    property SrcFreq:DWORD read FSrcFreq;
    property Status:DWORD read getStatus;
    property WaveData:TDDSDGenWave read FWaveData write setWavedata;
    property Is3D:Boolean read FIs3D;
    property IsStream:Boolean read FIsStream;
    property PlayPosition:DWord read getPlayPosition write setPlayPosition;
end;

TDDSDChannelArray = Array[0..$FFFFFF] of TDDSDChannel;
PDDSDChannelArray = ^TDDSDChannelArray;
EDDSD = class(Exception);

//DDSD本体
TDDSD = class(TComponent)
  private
    FDSound:IDirectSound;
    FPrimaryBuffer:IDirectSoundBuffer;
    F3DListener:IDirectSound3DListener;
    FChannels:PDDSDChannelArray;          //各チャンネル
    FChannelCount:DWORD;                  //チャンネル数
    FInitialized:Boolean;                 //「サウンドカードが無い」フラグ
    FUse3D:Boolean;                       //「3Dサウンドを使う」フラグ
    FListener:TDDSDListener;
    FStickyFocus:Boolean;
    FDebugOption: TDDSDDebugOption;                 //ほかのアプリに制御が移っても、再生できるように
    function GetChannels(iCh:Integer):TDDSDChannel;
    //3D専用
    procedure setListener(const v:TDDSDListener);
    procedure SetDebugOption(const Value: TDDSDDebugOption);
  protected
    procedure Loaded; override;
  public
    constructor Create(AOWner:TComponent); override;
    destructor Destroy; override;
    function forceInitialize:Boolean;     //DirectSoundの再初期化
    property DSound:IDirectSound read FDSound;
    property PrimaryBuffer:IDirectSoundBuffer read FPrimaryBuffer;
    property Channels[iCh:Integer]:TDDSDChannel read GetChannels; default;
    property Initialized:Boolean read FInitialized;
    property Listener:TDDSDListener read FListener write setListener;
    procedure SetPrimaryBufferFotmat(freq, bps:DWord; isStereo:Boolean);

  published
    property DebugOption:TDDSDDebugOption read FDebugOption write SetDebugOption;
    property Use3D:Boolean read FUse3D write FUse3D default False;
    property ChannelCount:DWORD read FChannelCount write FChannelCount default 8;
    property StickyFocus:Boolean read FStickyFocus write FStickyFocus default True;
end;


//キャプチャデバイス
TDDSDCapture = class
  private
    FWaveFormat:TWaveFormatEx;

    FDSCapture:IDirectSoundCapture;
    FCaptureBuffer:IDirectSoundCaptureBuffer;

    FLength:DWord;                    //キャプチャバッファの長さ
    FOwner:TObject;                   //オーナーとなるDDSDオブジェクト
    FHalfPos:DWord;                   //真中のオフセット
    FDSNotify:IDirectSoundNotify;     //Notify用オブジェクト
    FEvent:Array[0..2] of THandle;    //イベントオブジェクトへのハンドル
    FNotifyThread:TDDSDCaptureNotifyThread;  //イベントの監視スレッド
    FOnRecording:TDDSDRecordingEvent;    //「キリのいいとこまで録音したよ」イベント

    FInitialized:Boolean;             //ちゃんと初期化できた？

    function GetPosition:DWord;       //今サウンドバッファのどこにキャプチャしてるか
    function GetStatus:DWord;         //録音についての状態
  public
    constructor Create(OWner:TObject); //ストリームを作成
    procedure SetupCaptureBuffer(freq,bps:DWord; isStereo:Boolean; length:DWord); //キャプチャバッファを生成

    destructor Destroy; override;
    procedure Sync;                   //NotifyThreadから呼ばれて、ストリーム更新イベントを呼ぶルーチン…ほんとはSynchronizeずべきなんだけど…

    procedure Start(wait:Boolean);    //バッファの最初から最後を埋める分だけキャプチャ
    procedure StartLoop;              //ストリーミングキャプチャ
    procedure Stop;                   //キャプチャ停止

    procedure BlockCopy(offset:DWord; dest:Pointer; length:DWord); //ブロック転送
    function IsSupportedFormat(freq,bps:DWord; isStereo:Boolean):Boolean; //このフォーマットは通用するか

    property DSCapture:IDirectSoundCapture read FDSCapture;
    property Buffer:IDirectSoundCaptureBuffer read FCaptureBuffer;
    property Status:DWord read GetStatus;

    property Position:DWord read GetPosition; //キャプチャに使っているキャプチャバッファ上のオフセット

    property OnRecording:TDDSDRecordingEvent read FOnRecording write FOnRecording;
    property Length:DWord read FLength;
end;

implementation

(****************************************************************************
  登録
 ****************************************************************************)
procedure Register;
begin
  RegisterComponents('Quadruple D',[TDDSD]);
end;

(******************************************************************************
  デバッグ補助関数
******************************************************************************)
var
  DebugCount:Integer = 0;
  ErrDDSD:EDDSD;       //例外オブジェクト

procedure PutDebugMessage(mes:String);
var
  F:TextFile;
  fname:String;
begin
  //デバッグメッセージの出力が禁止になっているなら、何もしない
  if DDSD_DebugMessage then begin

    //EXEファイルのあるディレクトリのdddd.debugというファイルに一行書き込む
    fname:=ExtractFilePath(Application.Exename) + 'ddsd.debug';

    //ファイルを開く
    AssignFile(F,fname);
    //もし、プログラムの実行後、はじめての書き込みなら、ファイルをクリア
    if debugCount = 0 then
      ReWrite(F)
    else
      Append(F);

    Inc(debugCount);

    //一行書き込む
    Writeln(F,Format('debug#%.5d ... %s',[debugCount,mes]));

    //ファイル閉じる
    CloseFile(F);
  end;

  //例外の発生がOnなら、例外を発生させる
  if DDSD_ExceptOnError then begin
    ErrDDSD:=EDDSD.Create(mes);
    raise ErrDDSD;
  end;

end;

function TRYDS(err:HRESULT; failMes:String):HRESULT;
begin
  result:=err;

  if err = DS_OK then
    exit;

	PutDebugMessage(failMes+Format('(Error Code(%x))',[err]));

  if DDSD_HaltOnError then
    Halt; //安全のため、Haltしちゃう

end;



(******************************************************************************
  ヘルパー関数
******************************************************************************)

function BufferFormatToDWord(freq,bps:DWord; IsStereo:Boolean):DWord;
var
  fmt:DWord;
begin
  //Format指定用のDWORD値の生成
  fmt:=1;

  Case freq of
    11025: ;
    22050: fmt := fmt Shl 4;
    44100: fmt := fmt Shl 8;
    else
      fmt:=WAVE_INVALIDFORMAT;
  end;

  Case bps of
    8:  ;
    16: fmt:=fmt Shl 2;
    else
      fmt:=WAVE_INVALIDFORMAT;
  end;

  if IsStereo then
    fmt:=fmt Shl 1;

  result:=fmt;

end;



(******************************************************************************
  GenWaveオブジェクト
******************************************************************************)
constructor TDDSDGenWave.Create(Owner:TObject; fname:String; is3D:Boolean); //WAVEファイルを読み込む
var
  hm:HMMIO;
  mckP:TMMCKINFO;    //親チャンクの情報
  mckC:TMMCKINFO;    //子チャンクの情報

  fmtSize:DWord;    //fmtチャンクのサイズ
  dataSize:DWord;  //dataチャンクのサイズ

  dsbd:DSBUFFERDESC;
  lpBuf1,lpBuf2:Pointer;
  dwBuf1,dwBuf2:DWord;

  function MakeFourCC(ch0,ch1,ch2,ch3:Char):DWord;
  begin
    result:= Integer(Ord(ch0)) + Integer(Ord(ch1)) Shl 8 +
             Integer(Ord(ch2)) Shl 16 + Integer(Ord(ch3)) Shl 24;
  end;
begin
  inherited Create;

  FIsStream:=False;
  FOwner:=Owner;

  //サウンドカードが無いなら、何もしない
  if not TDDSD(FOwner).Initialized then
    Exit;

  hm := mmioOpen(PChar(fname), Nil, MMIO_READ Or MMIO_ALLOCBUF);

  if hm = 0 then begin
    PutDebugMessage(fname + 'が、見つかりません');
    Exit;
  end;

  //WAVEに入る
  mckP.fccType:=MakeFourCC('W','A','V','E');
  if (mmioDescend(hm, @mckP, Nil, MMIO_FINDRIFF)) <> 0 then begin
    mmioClose(hm, 0);
    PutDebugMessage(fname + 'は、WAVEファイルではありません');
    Exit;
  end;

  //fmtチャンクに入る
  mckC.ckID:=MakeFourCC('f','m','t',' ');
  if (mmioDescend(hm, @mckC, @mckP, MMIO_FINDCHUNK)) <> 0 then begin
    mmioClose(hm, 0);
    PutDebugMessage(fname + 'には、fmtチャンクが有りません');
    Exit;
  end;

  //fmtチャンクを読み取る
  fmtSize := mckC.cksize;
  if mmioRead(hm, Pointer(@FWaveFormat), fmtSize) <> integer(fmtSize) then begin
    mmioClose(hm, 0);
    PutDebugMessage(fname + 'のfmtチャンクが不正です');
    Exit;
  end;

  //fmtチャンクを抜け、dataチャンクに入る
  mmioAscend(hm, @mckC, 0);
  mckC.ckID:=MakeFourCC('d','a','t','a');
  if (mmioDescend(hm, @mckC, @mckP, MMIO_FINDCHUNK)) <> 0 then begin
    mmioClose(hm, 0);
    PutDebugMessage(fname + 'には、dataチャンクが有りません');
    Exit;
  end;

  dataSize:=mckC.cksize;

  //二次バッファの作成
  ZeroMemory(@dsbd,sizeof(dsbd));
  with dsbd do begin
    dwSize:=sizeof(dsbd);
    if Is3D then
      dwFlags:=DSBCAPS_STATIC + DSBCAPS_CTRLDEFAULT + DSBCAPS_CTRL3D - DSBCAPS_CTRLPAN
    else
      dwFlags:=DSBCAPS_STATIC Or DSBCAPS_CTRLDEFAULT;

    if TDDSD(FOwner).StickyFocus then
      dwFlags:=dwFlags Or DSBCAPS_STICKYFOCUS;

    dwBufferBytes:=dataSize;
    lpwfxFormat:=@FWaveFormat;
  end;

  FLength:=dataSize;

  TRYDS(TDDSD(FOwner).FDSound.CreateSoundBuffer(@dsbd,FSoundBuffer,Nil),'サウンドバッファの作成に失敗しました');

  //二次バッファのロック
  TRYDS(FSoundBuffer.Lock(0,dataSize, lpBuf1,@dwBuf1,lpBuf2,@dwBuf2,0),'LoadWAV中に、サウンドバッファのロックが失敗しました');

  //音データのロード(dataチャンクを読み取る)
  mmioRead(hm, Pointer(lpBuf1), dwBuf1);
  if dwBuf2 <> 0 then begin
    mmioRead(hm, Pointer(lpBuf2), dwBuf2);
  end;

  TRYDS(FSoundBuffer.UnLock(lpBuf1,dwBuf1,lpBuf2,dwBuf2),'LoadWAV中に、サウンドバッファのアンロックが失敗しました');
  FSoundBuffer.SetFrequency(FWaveFormat.nSamplesPerSec);
  mmioClose(hm, 0);

end;

//QDAからWAVEファイルを読み込む
constructor TDDSDGenWave.CreateFromQDA(OWner:TObject; QDAname:String; id:String; is3D:Boolean);
var
  TempPath:Array[0..MAX_PATH] of Char;
  TempName:Array[0..MAX_PATH] of Char;
  ms:TMemoryStream; //解凍結果を入れたストリーム
  fs:TFileStream;
  err:EDDSD;
begin
  try
    ms:=ExtractFromQDAFile(QDAName,id);
  except
    err:=EDDSD.Create(Format('アーカイブファイル %s からの %s のオープンに失敗しました',[QDAName,id]));
    raise err;
  end;

  //テンポラリファイルの作成
  try
    GetTempPath(MAX_PATH,@TempPath);
    GetTempFileName(@TempPath,PChar('tmp'),0,@TempName);

    fs:=TFileStream.Create(StrPas(TempName), fmCreate);
    fs.Write(ms.Memory^,ms.Size);
    fs.Free;
  except
    err:=EDDSD.Create(Format('WaveDataの展開に失敗しました',[QDAName,id]));
    raise err;
  end;

  ms.Free;
  //テンポラリから読みこむ
  Create(Owner,StrPas(TempName),is3D);

  //テンポラリの削除
  DeleteFile(StrPas(TempName));
end;


//ファイルを読まずにバッファだけ作る
constructor TDDSDGenWave.CreateEmptyBuffer(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord; is3D:Boolean; isStream:Boolean);
var
  dsbd:DSBUFFERDESC;
  pn:Array[0..2] of DSBPOSITIONNOTIFY;
  flagBase:DWord;
begin
  inherited Create;

  FIsStream:=isStream;
  FOwner:=Owner;
  FLength:=length;
  FOnUpdate:=Nil;

  //サウンドカードが無いなら、何もしない
  if not TDDSD(FOwner).Initialized then
    Exit;

  //WaveFormatを記述
  with FWaveFormat do begin
    wFormatTag:=WAVE_FORMAT_PCM;
    if isStereo then
      nChannels:=2
    else
      nChannels:=1;
    nSamplesPerSec:=freq;
    nBlockAlign:=nChannels * bps div 8;
    nAvgBytesPerSec:=freq * nBlockAlign;
    wBitsPerSample:=bps;
    cbSize:=0;
  end;

  //二次バッファの作成
  if isStream then
    flagBase:=DSBCAPS_CTRLDEFAULT + DSBCAPS_CTRLPOSITIONNOTIFY
  else
    flagBase:=DSBCAPS_CTRLDEFAULT + DSBCAPS_STATIC;

  ZeroMemory(@dsbd,sizeof(dsbd));
  with dsbd do begin
    dwSize:=sizeof(dsbd);
    if Is3D then
      dwFlags:=flagBase + DSBCAPS_CTRL3D - DSBCAPS_CTRLPAN
    else
      dwFlags:=flagBase;

    if TDDSD(FOwner).StickyFocus then
      dwFlags:=dwFlags Or DSBCAPS_STICKYFOCUS;

    dwBufferBytes:=length;
    lpwfxFormat:=@FWaveFormat;
  end;

  TRYDS(TDDSD(FOwner).FDSound.CreateSoundBuffer(@dsbd,FSoundBuffer,Nil),'サウンドバッファの作成に失敗しました');


  if isStream then begin

    FHalfPos:=length shr 1;

    //イベントオブジェクトの生成
    FEvent[0]:=CreateEvent(Nil,False,False,Nil);
    FEvent[1]:=CreateEvent(Nil,False,False,Nil);
    FEvent[2]:=CreateEvent(Nil,False,False,Nil);

    //イベントの設定
    pn[0].dwOffset:=0;  //先頭
    pn[0].hEventNotify:=FEvent[0];

    pn[1].dwOffset:=FHalfPos;  //真中
    pn[1].hEventNotify:=FEvent[1];

    pn[2].dwOffset:=DSBPN_OFFSETSTOP;  //再生停止
    pn[2].hEventNotify:=FEvent[2];

    //ノーティファイオブジェクトの作成
    TRYDS(FSoundBuffer.QueryInterface(IID_IDirectSoundNotify,FDSNotify),'DirectSoundNotifyオブジェクトの作成に失敗しました');
    TRYDS(FDSNotify.SetNotificationPositions(3,@pn), 'NotifyPositionの設定に失敗しました');

    //イベント通知用スレッドの作成
    FNotifyThread:=TDDSDNotifyThread.Create(Self);
    //FnotifyThread.Priority:=tpHigher;
  end;
end;




{
constructor TDDSDGenWave.Create(Owner:TObject; fname:String; is3D:Boolean); //WAVEファイルを読み込む
var
  fs:TFileStream;
  dsbd:DSBUFFERDESC;
  dwWavSize:DWORD;
  lpBuf1,lpBuf2:Pointer;
  dwBuf1,dwBuf2:DWord;
  fmt:DWORD;
begin

  FOwner:=Owner;

  //サウンドカードが無いなら、何もしない
  if not TDDSD(FOwner).Initialized then
    Exit;

  try
    fs:=TFileStream.Create(fname,fmOpenRead);
  except
    PutDebugMessage(fname+'が読み込めません');
    if DDSD_HaltOnError then
      Halt;
    Exit;
  end;

  //ファイルを読みつつ、SoundBufferを作る
  try
    //ヘッダのチェック
    fs.Position:=0;
    fs.Read(fmt,4);
    if fmt <> (Ord('R') + Ord('I') Shl 8 + Ord('F') Shl 16 + Ord('F') Shl 24) then begin
      PutDebugMessage('対応できないフォーマットです');
      Abort;
    end;
    fs.Position:=8;
    fs.Read(fmt,4);
    if fmt <> (Ord('W') + Ord('A') Shl 8 + Ord('V') Shl 16 + Ord('E') Shl 24) then begin
      PutDebugMessage('対応できないフォーマットです');
      Abort;
    end;
    fs.Position:=12;
    fmt:=0;
    fs.Read(fmt,3);
    if fmt <> (Ord('f') + Ord('m') Shl 8 + Ord('t') Shl 16) then begin
      PutDebugMessage('対応できないフォーマットです');
      Abort;
    end;
    fs.Position:=36;
    fs.Read(fmt,4);
    if fmt <> (Ord('d') + Ord('a') Shl 8 + Ord('t') Shl 16 + Ord('a') Shl 24) then begin
      PutDebugMessage('対応できないフォーマットです');
      Abort;
    end;


    fs.Position:=40; //ヘッダ飛ばす
    fs.Read(dwWavSize,sizeof(DWORD));
    fs.Position:=20; //WAVEフォーマットを読む
    fs.Read(FWaveFormat,sizeOf(FWaveFormat));

    //二次バッファの作成
    ZeroMemory(@dsbd,sizeof(dsbd));
    with dsbd do begin
      dwSize:=sizeof(dsbd);
      if Is3D then
        dwFlags:=DSBCAPS_STATIC + DSBCAPS_CTRLDEFAULT + DSBCAPS_CTRL3D - DSBCAPS_CTRLPAN
      else
        dwFlags:=DSBCAPS_STATIC Or DSBCAPS_CTRLDEFAULT;
      dwBufferBytes:=dwWavSize;
      lpwfxFormat:=@FWaveFormat;
    end;

    TRYDS(TDDSD(FOwner).FDSound.CreateSoundBuffer(@dsbd,FSoundBuffer,Nil),'サウンドバッファの作成に失敗しました');

    //二次バッファのロック
    TRYDS(FSoundBuffer.Lock(0,dwWavSize, lpBuf1,@dwBuf1,lpBuf2,@dwBuf2,0),'LoadWAV中に、サウンドバッファのロックが失敗しました');

    //音データのロード
    fs.Position:=44;
    fs.Read(lpBuf1^,dwBuf1);
    if dwBuf2<>0 then begin
      fs.Read(lpBuf2^,dwBuf2);
    end;

    TRYDS(FSoundBuffer.UnLock(lpBuf1,dwBuf1,lpBuf2,dwBuf2),'LoadWAV中に、サウンドバッファのアンロックが失敗しました');
    FSoundBuffer.SetFrequency(FWaveFormat.nSamplesPerSec);

  finally
    fs.Free;
  end;

end;
}

destructor TDDSDGenWave.Destroy;
begin
  if TDDSD(FOwner).Initialized then begin

    if Assigned(FSoundBuffer) then begin
      TRYDS(FSoundBuffer.Release,'二次バッファの解放に失敗しました');
      if FIsStream And Assigned(FnotifyThread) then begin
        FNotifyThread.Free;
      end;
    end;

  end;

  inherited destroy;
end;

//サウンドバッファへブロックコピー
procedure TDDSDGenWave.BlockCopy(offset:DWord; src:Pointer; length:DWord);
var
  len1,len2:DWord;
  pBuf1,pBuf2:Pointer;
begin
  //サウンドカードが無いなら、何もしない
  if not TDDSD(FOwner).Initialized then
    Exit;

  //ロック
  TRYDS(FSoundBuffer.Lock(offset,length,pBuf1,@len1,pBuf2,@len2,0),'DirectSoundBufferのロックに失敗しました');

  //Buf1へコピー
  Move(src^,pBuf1^,len1);

  //Buf2へコピー
  if len1 < length then begin
    Inc(pByte(src),len1);
    Move(src^,pBuf2^,len2);
  end;

  //アンロック
  FSoundBuffer.Unlock(pBuf1,len1,pBuf2,len2);
end;


//バッファの更新イベントを呼ぶルーチン。NotifyThreadから呼ばれる。
procedure TDDSDGenWave.Sync;
begin
  if Assigned(FOnUpdate) then begin
    if FNotifyThread.IsTop then
      FOnUpdate(Self,FPlayer,FHalfPos,FLength - FHalfPos)
    else
      FOnUpdate(Self,FPlayer,0,FHalfPos);
  end;
end;


(******************************************************************************
  TDDSDGenWaveのストリーミング再生用スレッド
******************************************************************************)
constructor TDDSDNotifyThread.Create(owner:TDDSDGenWave);
begin
  inherited Create(False);
  FOwner:=Owner;
end;

procedure TDDSDNotifyThread.Execute;
var
  e,flg:Integer;
  Rpos,Wpos : DWORD;
begin
  flg := 1;

  while not Terminated do begin
    e:=WaitForMultipleObjects(3,@FOwner.FEvent,False,100);  //100ミリ秒間隔でTerminatedフラグの監視

    case e of
      //先頭に再生位置が戻ってきた
      WAIT_OBJECT_0: begin
        FOwner.FSoundBuffer.GetCurrentPosition(@Rpos,@Wpos);
        if ((RPos < (FOwner.FLength div 2))and(flg = 1)) then
        if (flg = 1)then
        begin
          IsTop:=True;
          FOwner.Sync;
          flg := 0;
          //Synchronize(FOwner.Sync);
        end;
      end;

      //バッファ中央に再生位置がきた
      WAIT_OBJECT_0 + 1: begin
        FOwner.FSoundBuffer.GetCurrentPosition(@Rpos,@Wpos);
        if ((RPos > (FOwner.FLength div 2))and(flg = 0)) then
        if (flg = 0)then
        begin
          IsTop:=False;
          FOwner.Sync;
          flg := 1;
          //Synchronize(FOwner.Sync);
        end;
      end;

      //再生停止要求が来た
      WAIT_OBJECT_0 + 2: begin
        //Suspend;
      end;
    end;
  end;

end;

(******************************************************************************
  録音用スレッド
******************************************************************************)
constructor TDDSDCaptureNotifyThread.Create(owner:TDDSDCapture);
begin
  inherited Create(False);
  FOwner:=Owner;
end;

procedure TDDSDCaptureNotifyThread.Execute;
var
  e:Integer;
begin

  while not Terminated do begin
    e:=WaitForMultipleObjects(3,@FOwner.FEvent,False,100);  //100ミリ秒間隔でTerminatedフラグの監視

    case e of
      //先頭に録音位置が戻ってきた
      WAIT_OBJECT_0: begin
        IsTop:=True;
        FOwner.Sync;
        //Synchronize(FOwner.Sync);
      end;

      //バッファ中央に再生位置がきた
      WAIT_OBJECT_0 + 1: begin
        IsTop:=False;
        FOwner.Sync;
        //Synchronize(FOwner.Sync);
      end;

      //再生停止要求が来た
      WAIT_OBJECT_0 + 2: begin
        //Suspend;
      end;
    end;
  end;

end;

(******************************************************************************
  Params3Dオブジェクト
******************************************************************************)
constructor TDDSDParams3D.Create(Owner:TObject);
begin
  inherited Create;

  FOwner:=Owner;

  //音源パラメータの設定
  //デフォルトでは、無指向性で有効レンジ0〜100m
  with FDsb do begin
    dwSize:=sizeof(DS3DBuffer);
    with vPosition do begin
      x:=0; y:=0; z:=0;
    end;
    with vVelocity do begin
      x:=0; y:=0; z:=0;
    end;
    dwInsideConeAngle:=DS3D_DEFAULTCONEANGLE;
    dwOutsideConeAngle:=DS3D_DEFAULTCONEANGLE;
    with vConeOrientation do begin
      x:=0; y:=0; z:=1;
    end;
    lConeoutsideVolume:= DS3D_DEFAULTCONEOUTSIDEVOLUME; //コーンの外側でも減衰しない
    flMinDistance:=5;     //5m以内なら、原音とおなじボリュームで聞こえる
    flMaxDistance:=100.0; //100mのところで、音が完全に聞こえなくなる
    dwMode:=DS3DMODE_NORMAL;
  end;

end;

destructor TDDSDParams3D.destroy;
begin
  inherited destroy;
end;



function TDDSDParams3D.getPosition:D3DVector;
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.GetPosition(@FDsb.vPosition);
  end;
  result:=FDsb.vPosition;
end;

function TDDSDParams3D.getVelocity:D3DVector;
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.GetVelocity(@FDsb.vVelocity);
  end;
  result:=FDsb.vVelocity;
end;

function TDDSDParams3D.getConeOrientation:D3DVector;
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.GetConeOrientation(@FDsb.vConeOrientation);
  end;
  result:=FDsb.vConeOrientation;
end;

function TDDSDParams3D.getConeAngle:TDDSDConeAngle;
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.GetConeAngles(@FDsb.dwInsideConeAngle,@FDsb.dwOutsideConeAngle);
  end;
  with result do begin
    Inside :=FDsb.dwInsideConeAngle;
    OutSide:=FDsb.dwOutsideConeAngle;
  end;
end;

function TDDSDParams3D.getConeOutsideVolume:Integer;
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.GetConeOutsideVolume(@FDsb.lConeOutsideVolume);
  end;
  result:=FDsb.lConeOutsideVolume;
end;

function TDDSDParams3D.getMinDistance:D3DValue;
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.GetMinDistance(@FDsb.flMinDistance);
  end;
  result:=FDsb.flMinDistance;
end;

function TDDSDParams3D.getMaxDistance:D3DValue;
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.GetMaxDistance(@FDsb.flMaxDistance);
  end;
  result:=FDsb.flMaxDistance;
end;

function TDDSDParams3D.getRaw:DS3DBuffer;
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.GetAllParameters(@FDsb);
  end;
  result:=FDsb;
end;


procedure TDDSDParams3D.setPosition(const v:D3DVector);
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.SetPosition(v.x, v.y, v.z, DS3D_IMMEDIATE);
  end;
  FDsb.vPosition:=v;
end;

procedure TDDSDParams3D.setVelocity(const v:D3DVector);
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.SetVelocity(v.x, v.y, v.z, DS3D_IMMEDIATE);
  end;
  FDsb.vVelocity:=v;
end;

procedure TDDSDParams3D.setConeOrientation(const v:D3DVector);
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.SetConeOrientation(v.x, v.y, v.z, DS3D_IMMEDIATE);
  end;
  FDsb.vConeOrientation:=v;
end;

procedure TDDSDParams3D.setConeAngle(const v:TDDSDConeAngle);
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.SetConeAngles(v.Inside,v.Outside, DS3D_IMMEDIATE);
  end;
   FDsb.dwInsideConeAngle:=v.Inside;
   FDsb.dwInsideConeAngle:=v.Outside;
end;

procedure TDDSDParams3D.setConeOutsideVolume(const v:Integer);
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.SetConeOutsideVolume(v, DS3D_IMMEDIATE);
  end;
  FDsb.lConeOutsideVolume:=v;
end;

procedure TDDSDParams3D.setMinDistance(const v:D3DValue);
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.SetMinDistance(v, DS3D_IMMEDIATE);
  end;
  FDsb.flMinDistance:=v;
end;

procedure TDDSDParams3D.setMaxDistance(const v:D3DValue);
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TDDSDChannel(FOwner).F3DBuffer.SetMaxDistance(v, DS3D_IMMEDIATE);
  end;
  FDsb.flMaxDistance:=v;
end;

procedure TDDSDParams3D.setRaw(const v:DS3DBuffer);
begin
  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TRYDS(TDDSDChannel(FOwner).F3DBuffer.SetAllParameters(@v, DS3D_IMMEDIATE),'Params3Dに指定されたパラメータが不適切です');
  end;
  FDsb:=v;
end;

procedure TDDSDParams3D.Assign(v:TDDSDParams3D);
begin
  //相手の情報を取り出す
   FDsb:=v.RawParams;

  if (FOwner <> Nil) And (TDDSDChannel(FOwner).F3DBuffer <> Nil) then begin
    TRYDS(TDDSDChannel(FOwner).F3DBuffer.SetAllParameters(@FDsb, DS3D_IMMEDIATE),'Params3Dに指定されたパラメータが不適切です');
  end;
end;


(******************************************************************************
  Listnerオブジェクト
******************************************************************************)

constructor TDDSDListener.Create(Owner:TObject);
begin
  inherited Create;

  FOwner:=Owner;

  ZeroMemory(@FDsl,sizeof(FDsl));
  with FDsl do begin
    dwSize:=sizeOf(FDsl);
    with vPosition do begin
      x:=0; y:=0; z:=0;
    end;
    with vVelocity do begin
      x:=0; y:=0; z:=0;
    end;
    with vOrientFront do begin
      x:=0; y:=0; z:=1;
    end;
    with vOrientTop do begin
      x:=0; y:=1; z:=0;
    end;
    flDistanceFactor:=1.0; //DS3D_DEFAULTDISTANCEFACTOR;
    flRolloffFactor:=1.0; //DS3D_DEFAULTROLLOFFFACTOR;
    flDopplerFactor:=1.0; //DS3D_DEFAULTDOPPLERFACTOR;
  end;

  if TDDSD(FOwner).F3DListener <> Nil then begin
    TRYDS(TDDSD(FOwner).F3DListener.SetAllParameters(@FDsl, DS3D_IMMEDIATE),'Listenerに指定されたパラメータが不適切です');
  end;
end;

destructor TDDSDListener.Destroy;
begin
  inherited Destroy;
end;


function TDDSDListener.getPosition:D3DVector;
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.GetPosition(@FDsl.vPosition);
  end;
  result:=FDsl.vPosition;
end;

function TDDSDListener.getVelocity:D3DVector;
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.GetVelocity(@FDsl.vVelocity);
  end;
  result:=FDsl.vVelocity;
end;

function TDDSDListener.getOrientation:TDDSDOrientation;
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.GetOrientation(@FDsl.vOrientFront,@FDsl.vOrientTop);
  end;
  with result do begin
    Front:=FDsl.vOrientFront;
    Top:=FDsl.vOrientTop;
  end;
end;

function TDDSDListener.getDistanceFactor:D3DValue;
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.GetDistanceFactor(@FDsl.flDistanceFactor);
  end;
  result:=FDsl.flDistanceFactor;
end;

function TDDSDListener.getRollOffFactor:D3DValue;
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.GetRollOffFactor(@FDsl.flRollOffFactor);
  end;
  result:=FDsl.flRollOffFactor;
end;

function TDDSDListener.getDopplerFactor:D3DValue;
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.GetDopplerFactor(@FDsl.flDopplerFactor);
  end;
  result:=FDsl.flDopplerFactor;
end;

function TDDSDListener.getRaw:DS3DListener;
begin
  if (FOwner <> Nil) And (TDDSD(FOwner).F3DListener <> Nil) then begin
    TDDSD(FOwner).F3DListener.GetAllParameters(@FDsl);
  end;
  result:=FDsl;
end;


procedure TDDSDListener.setPosition(const v:D3DVector);
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.SetPosition(v.x,v.y,v.z, DS3D_IMMEDIATE);
  end;
  FDsl.vPosition:=v;
end;

procedure TDDSDListener.setVelocity(const v:D3DVector);
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.SetVelocity(v.x,v.y,v.z, DS3D_IMMEDIATE);
  end;
  FDsl.vVelocity:=v;
end;

procedure TDDSDListener.setOrientation(const v:TDDSDOrientation);
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.SetOrientation(v.Front.x,v.Front.y,v.Front.z, v.Top.x,v.Top.y,v.Top.z, DS3D_IMMEDIATE);
  end;
  FDsl.vOrientFront:=v.Front;
  FDsl.vOrientTop:=v.Top;
end;

procedure TDDSDListener.setDistanceFactor(const v:D3DValue);
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.SetDistanceFactor(v, DS3D_IMMEDIATE);
  end;
  FDsl.flDistanceFactor:=v;
end;

procedure TDDSDListener.setRollOffFactor(const v:D3DValue);
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.SetRolloffFactor(v, DS3D_IMMEDIATE);
  end;
  FDsl.flRolloffFactor:=v;
end;

procedure TDDSDListener.setDopplerFactor(const v:D3DValue);
begin
  if TDDSD(FOwner).F3DListener <> Nil then begin
    TDDSD(FOwner).F3DListener.SetDopplerFactor(v, DS3D_IMMEDIATE);
  end;
  FDsl.flDopplerFactor:=v;
end;

procedure TDDSDListener.setRaw(const v:DS3DListener);
begin
  if (FOwner <> Nil) And (TDDSD(FOwner).F3DListener <> Nil) then begin
    TRYDS(TDDSD(FOwner).F3DListener.SetAllParameters(@v, DS3D_IMMEDIATE),'Listenerに指定されたパラメータが不適切です');
  end;
  FDsl:=v;
end;

procedure TDDSDListener.Assign(v:TDDSDListener);
begin
  //相手の情報を取り出す
   FDsl:=v.RawParams;

  if (FOwner <> Nil) And (TDDSD(FOwner).F3DListener <> Nil) then begin
    TRYDS(TDDSD(FOwner).F3DListener.SetAllParameters(@FDsl, DS3D_IMMEDIATE),'Listenerに指定されたパラメータが不適切です');
  end;
end;

(******************************************************************************
  WaveDataオブジェクト
******************************************************************************)
constructor TDDSDWaveData.Create(Owner:TObject; fname:String); //WAVEファイルを読み込む
begin
  inherited Create(Owner,fname,False);
end;

constructor TDDSDWaveData.CreateFromQDA(OWner:TObject; QDAname:String; id:String); //QDAからWAVEファイルを読み込む
begin
  inherited CreateFromQDA(Owner,QDAname,id, False);
end;

{$IFNDEF FOR_CPPBUILDER}
constructor TDDSDWaveData.CreateBuffer(owner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord);
begin
  inherited CreateEmptyBuffer(Owner,freq,bps,isStereo,length,False, False);
end;

constructor TDDSDWaveData.CreateStream(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord);
begin
  inherited CreateEmptyBuffer(Owner,freq,bps,isStereo,length,False, True);
end;

{$ELSE}
constructor TDDSDWaveData.CreateEmptyBuffer(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord; isStream:Boolean);
begin
  inherited CreateEmptyBuffer(Owner,freq,bps,isStereo,length,False, isStream);
end;

{$ENDIF}  //FOR_CPPBUILDER



destructor TDDSDWaveData.Destroy;
begin
  inherited Destroy;
end;

(******************************************************************************
  Wave3Dオブジェクト
******************************************************************************)
constructor TDDSDWave3D.Create(Owner:TObject; fname:String); //WAVEファイルを読み込む
begin
  inherited Create(Owner,fname,True);

  FParams3D:=TDDSDParams3D.Create(Nil); //パラメータ初期化
end;

constructor TDDSDWave3D.CreateFromQDA(OWner:TObject; QDAname:String; id:String); //QDAからWAVEファイルを読み込む
begin
  inherited CreateFromQDA(Owner,QDAname,id,True);

  FParams3D:=TDDSDParams3D.Create(Nil); //パラメータ初期化
end;

{$IFNDEF FOR_CPPBUILDER}

constructor TDDSDWave3D.CreateBuffer(owner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord);
begin
  inherited CreateEmptyBuffer(Owner,freq,bps,isStereo,length,True, False);

  FParams3D:=TDDSDParams3D.Create(Nil); //パラメータ初期化
end;

constructor TDDSDWave3D.CreateStream(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:Dword); //ストリームを作成
begin
  inherited CreateEmptyBuffer(Owner,freq,bps,isStereo,length,True, True);

  FParams3D:=TDDSDParams3D.Create(Nil); //パラメータ初期化
end;

{$ELSE}
constructor TDDSDWave3D.CreateEmptyBuffer(OWner:TObject; freq,bps:DWord; isStereo:Boolean; length:DWord; isStream:Boolean);
begin
  inherited CreateEmptyBuffer(Owner,freq,bps,isStereo,length,True, isStream);

  FParams3D:=TDDSDParams3D.Create(Nil); //パラメータ初期化
end;

{$ENDIF}  //FOR_CPPBUILDER



destructor TDDSDWave3D.Destroy;
begin
  FParams3D.Free;

  inherited Destroy;
end;






(******************************************************************************
  Channelオブジェクト
******************************************************************************)
constructor TDDSDChannel.Create(Owner:TObject);
begin
  inherited Create;
  FOwner:=Owner;
  FSoundBuffer:=Nil;
  F3DBuffer:=Nil;
  FWaveData:=Nil;
  FIs3D:=False;
  FIsStream:=False;

  FPan:=0;
  FFreqRate:=1.0;
  FVolume:=0;

  FParams3D:=TDDSDParams3D.Create(Self);
end;


destructor TDDSDChannel.Destroy;
begin
  if Assigned(F3DBuffer) then
    F3DBuffer.Release;
  if Assigned(FSoundBuffer) then
    FSoundBuffer.Release;

  if Assigned(FParams3D) then
    FParams3D.Free;

  inherited destroy;
end;

procedure TDDSDChannel.setWaveData(wav:TDDSDGenWave);
begin
  //サウンドカード無しでは、何も出来ん
  if not TDDSD(FOwner).Initialized then
    Exit;

  //既存のサウンドバッファの解放
  if not IsStream then begin
    if Assigned(F3DBuffer) then begin
      TRYDS(F3DBuffer.Release,'setWaveData 3Dサウンドバッファの解放に失敗しました');
      F3DBuffer:=Nil;
    if Assigned(FSoundBuffer) then begin
      TRYDS(FSoundBuffer.Release,'setWaveData サウンドバッファの解放に失敗しました');
      FSoundBuffer:=Nil;
    end;
  end else begin
    //いま乗せているのと同じオブジェクトなら、何もしない
    if wav = FWaveData then
      Exit;

    //いままでの再生を止めさせる
    Stop;
  end;

  if wav = Nil then begin
    FWaveData:=Nil;
    Exit;
  end;

  //ストリームならば、単純に代入。そうでないならDuplicateSoundBufferして多重再生できるようにする
  if wav.IsStream then begin
    FSoundBuffer:=wav.Buffer;
    wav.FPlayer:=Self;
  end else
    TRYDS(TDDSD(FOwner).DSound.DuplicateSoundBuffer(wav.Buffer,FSoundBuffer),'setWaveData サウンドバッファの複製に失敗しました');

  FIsStream:=wav.IsStream;


  //3D再生できるようにする
  if wav is TDDSDWave3D then begin
    FIs3D:=True;
    TRYDS(FSoundBuffer.QueryInterface(IID_IDirectSound3DBuffer,F3DBuffer),'setWaveData 3Dサウンドバッファの作成に失敗しました');
    Params3D:=TDDSDWave3D(wav).Params3D;
  end else begin
    FIs3D:=False;
  end;

  FSrcFreq:=wav.FWaveFormat.nSamplesPerSec;
  FWaveData:=wav;
end;

procedure TDDSDChannel.Play;
begin
  if not Assigned(FSoundBuffer) then
    Exit;

  {
  if not Is3D then
    FSoundBuffer.SetPan(FPan);
  FSoundBuffer.Setfrequency(FFreq);
  FSoundBuffer.SetVolume(FVolume);

  FSoundBuffer.Stop;
  }

  //ストリームの場合、通知用のスレッドをアクティベートする
  {
  if IsStream then begin
    if FWaveData.FNotifyThread.Suspended then
      FWaveData.FNotifyThread.Resume;
  end;
  }

  FSoundBuffer.SetCurrentPosition(0);
  FSoundBuffer.Play(0,0,0);
end;

procedure TDDSDChannel.Stop;
begin

  if not Assigned(FSoundBuffer) then begin
    Exit;
  end;

  FSoundBuffer.Stop;

  //ストリームの場合、スレッドがサスペンドするまで待つ
  {
  if IsStream then begin
    //while not FWaveData.FNotifyThread.Suspended do
    //  ;
  end;
  }
end;

procedure TDDSDChannel.LoopPlay;
begin
  if not Assigned(FSoundBuffer) then
    Exit;

  {
  FSoundBuffer.SetPan(FPan);
  FSoundBuffer.setfrequency(FFreq);
  FSoundBuffer.SetVolume(FVolume);
  FSoundBuffer.Stop;
  }
  //ストリームの場合、通知用のスレッドをアクティベートする
  {
  if IsStream then begin
    if FWaveData.FNotifyThread.Suspended then
      FWaveData.FNotifyThread.Resume;
  end;
  }
  
  FSoundBuffer.SetCurrentPosition(0);
  FSoundbuffer.Play(0,0,DSBPLAY_LOOPING);
end;


//バッファの任意のオフセットから再生
procedure TDDSDChannel.PlayAt(ofs:DWord);
begin
  if not Assigned(FSoundBuffer) then
    Exit;

  //ストリームの場合、通知用のスレッドをアクティベートする
  {
  if IsStream then begin
    if FWaveData.FNotifyThread.Suspended then
      FWaveData.FNotifyThread.Resume;
  end;
  }
  
  FSoundBuffer.SetCurrentPosition(ofs);
  FSoundbuffer.Play(0,0,0);
end;

procedure TDDSDChannel.LoopPlayAt(ofs:DWord);
begin
  if not Assigned(FSoundBuffer) then
    Exit;

  //ストリームの場合、通知用のスレッドをアクティベートする
  {
  if IsStream then begin
    if FWaveData.FNotifyThread.Suspended then
      FWaveData.FNotifyThread.Resume;
  end;
  }
  
  FSoundBuffer.SetCurrentPosition(ofs);
  FSoundbuffer.Play(0,0,DSBPLAY_LOOPING);
end;

procedure TDDSDChannel.setFreqRate(v:Double);
begin
  if not Assigned(FSoundBuffer) then
    Exit;

  FFreq:=Trunc(v*FSrcFreq);
  FSoundBuffer.setfrequency(FFreq);
end;

procedure TDDSDChannel.setVolume(v:Integer);
begin
  if not Assigned(FSoundBuffer) then
    Exit;
  FVolume:=v;
  FSoundBuffer.SetVolume(FVolume);
end;

procedure TDDSDChannel.setPan(v:Integer);
begin
  if not Assigned(FSoundBuffer) then
    Exit;

  if not Is3D then begin
    FPan:=v;
    FSoundBuffer.SetPan(FPan);
  end;
end;

function TDDSDChannel.getStatus:DWORD;
var
  ret:DWORD;
begin
  if not Assigned(FSoundBuffer) then begin
    result:=0;
    Exit;
  end;
  FSoundBuffer.GetStatus(@ret);
  result:=ret;
end;

procedure TDDSDChannel.setParams3D(v:TDDSDParams3D);
begin
  FParams3D.Assign(v);
  //TRYDS(F3DBuffer.SetAllParameters(@v,DS3D_IMMEDIATE),'Params3Dに指定されたパラメータが不適切です');
end;

//バッファの何バイト目を現在演奏中か
function TDDSDChannel.getPlayPosition:DWord;
var
  pos:DWord;
begin
  if not Assigned(FSoundBuffer) then begin
    result:=0;
    Exit;
  end;

  FSoundBuffer.GetCurrentPosition(@pos,Nil);
  result:=pos;
end;

//再生位置の指定
procedure TDDSDChannel.setPlayPosition(pos:DWord);
begin
  if not Assigned(FSoundBuffer) then begin
    Exit;
  end;

  FSoundBuffer.SetCurrentPosition(pos);
end;

(******************************************************************************
  DDSDコンポーネント本体
******************************************************************************)

constructor TDDSD.Create(AOWner:TComponent);
var
  i,count:Integer;
begin
  inherited Create(AOWner);

  if (csDesigning in ComponentState) then begin
    //DDSDコンポが同一フォームに張りつけられてないか、チェック
    count:=0;
    for i:=0 to Owner.ComponentCount-1 do begin
      if Owner.Components[i] is TDDSD then begin
        Inc(count);
      end;
    end;

    if count >=2 then begin
      raise Exception.Create('DDSDコンポーネントを、同一フォームに2つ以上貼る事はできません');
      exit;
    end;
  end;
  
  //デフォルト値の設定
  ChannelCount:=8;
  FStickyFocus:=True;
  FUse3D:=False;
  DebugOption:=[dsoHaltOnError];
end;

destructor TDDSD.Destroy;
var
  i:DWORD;
begin
  if (csDesigning in ComponentState) then begin
    inherited destroy;
    Exit;
  end;

  For i:=0 to FChannelCount-1 do
    Channels[i].Free;

  if Assigned(FListener) then
    FListener.Free;

  if Assigned(F3DListener) then
    TRYDS(F3DListener.Release,'DirectSound3D Listnerの解放に失敗しました');
  if Assigned(FPrimaryBuffer) then
    TRYDS(FPrimaryBuffer.Release,'一次バッファの解放に失敗しました');

  if Assigned(FDSound) then
    TRYDS(FDSound.Release,'DirectSoundの解放に失敗しました');

  FreeMem(FChannels);

  inherited Destroy;
end;


//DirectSound初期化
function TDDSD.forceInitialize:Boolean;     //DirectSoundの再初期化
var
  i:DWORD;
  dsbd:DSBUFFERDESC;
begin

  result:=True;

  if Assigned(FChannels) then begin
    For i:=0 to FChannelCount-1 do begin
      FChannels^[i].Free;
    end;
    FreeMem(FChannels);
  end;

  GetMem(FChannels,sizeof(TDDSDChannel)*FChannelCount);

  For i:=0 to FChannelCount-1 do begin
    FChannels^[i]:=TDDSDChannel.Create(Self);
  end;

  //DirectSoundの初期化
  if DirectSoundCreate(Nil,FDSound,Nil)<>DS_OK then begin
    FInitialized:=False;
    result:=False;
    Exit;
  end;
  FInitialized:=True;

  TRYDS(FDSound.SetCooperativeLevel(TForm(Owner).Handle,DSSCL_PRIORITY),'DirectSoundの協調レベルの設定に失敗しました');

  //一次バッファの初期化
  ZeroMemory(@dsbd,sizeof(dsbd));
  with dsbd do begin
    dwSize:=sizeof(dsbd);
    if Use3D then
      dwFlags:=DSBCAPS_PRIMARYBUFFER Or DSBCAPS_CTRL3D
    else
      dwFlags:=DSBCAPS_PRIMARYBUFFER;

    TRYDS(FDSound.CreateSoundBuffer(@dsbd,FPrimaryBuffer,Nil),'一次バッファの作成に失敗しました');
  end;

  //3D対応にする
  if Use3D then begin
    TRYDS(FPrimaryBuffer.QueryInterface(IID_IDirectSound3DListener,F3DListener),'DirectSound3D Listenerの作成に失敗しました');
    if FListener <> Nil then
      FListener.Free;
    FListener:=TDDSDListener.Create(Self);
  end;

end;


procedure TDDSD.SetDebugOption(const Value: TDDSDDebugOption);
begin
  FDebugOption := Value;

  DDSD_DebugMessage := dsoDebugMessage in Value;
  DDSD_ExceptOnError := dsoExceptOnError in Value;
  DDSD_HaltOnError := dsoHaltOnError in Value;

end;


procedure TDDSD.Loaded;
begin
  inherited Loaded;

  if (csDesigning in ComponentState) then
    Exit;

  DebugOption:=DebugOption;
  forceInitialize;
end;


function TDDSD.GetChannels(iCh:Integer):TDDSDChannel;
begin
  if DWORD(iCh) > FChannelCount then begin
    PutDebugMessage(Format('チャンネル番号%dは無効です、%d〜%dの範囲で指定してください',[iCh,0,FChannelCount-1]));
    if DDSD_HaltOnError then
      Halt;
    result:=Nil;
    Exit;
  end;
  result:=FChannels^[iCh];
end;

procedure TDDSD.setListener(const v:TDDSDListener);
begin
  FListener.Assign(v);
end;

//プライマリ・バッファのフォーマットを設定
procedure TDDSD.SetPrimaryBufferFotmat(freq, bps: DWord;
  isStereo: Boolean);
var
  format:TWaveFormatEx;
begin
  //サウンドカードの無い環境なら、何もしない
  if not FInitialized then
    exit;

  //プライマリ･バッファのフォーマットを設定する
  with format do begin
    wFormatTag := WAVE_FORMAT_PCM;

    if isStereo then
      nChannels := 2
    else
      nChannels := 1;

    nSamplesPerSec := freq;
    wBitsPerSample := bps;
    nBlockAlign := wBitsPerSample div 8 * nChannels;
    nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
    cbSize:=0;
  end;

  TRYDS(PrimaryBuffer.SetFormat(@format),'プライマリ・バッファのフォーマットの設定に失敗しました');
end;




{ TDDSDCapture }

//コンストラクタ
constructor TDDSDCapture.Create(OWner: TObject);
begin
  FOwner:=Owner;
  FLength:=0;
  FOnRecording:=Nil;

  //DirectSoundCaptureオブジェクトの生成
  if DirectSoundCaptureCreate(Nil, FDSCapture, Nil) <> DS_OK then begin
    FInitialized:=False;
    exit;
  end;

  //初期化はOK
  FInitialized:=True;

end;

//デストラクタ
destructor TDDSDCapture.Destroy;
begin
  //初期化がちゃんとされてないなら、帰る
  if not FInitialized then
    exit;

  if Assigned(FCaptureBuffer) then
    FCaptureBuffer.Release;

  FDSCapture.Release;
end;


//読み込みOKな部分の先頭オフセットを返す
function TDDSDCapture.GetPosition: DWord;
var
  pos:DWord;
begin
  //初期化がちゃんとされてないなら、帰る
  if not FInitialized then begin
    result:=0;
    exit;
  end;

  Buffer.GetCurrentPosition(Nil, @pos);
  result:=pos;
end;

//録音状態の取得
function TDDSDCapture.GetStatus: DWord;
var
  stat:DWord;
begin
  //初期化がちゃんとされてないなら、帰る
  if not FInitialized then begin
    result:=0;
    exit;
  end;

  Buffer.GetStatus(@stat);
  result:=stat;
end;


//初めから終わりまでを埋めるだけキャプチャ
procedure TDDSDCapture.Start(wait: Boolean);
begin
  //初期化がちゃんとされてないなら、帰る
  if not FInitialized then
    exit;

  //キャプチャ中ならなにもしない
  if Status <> 0 then
    exit;

  //キャプチャ
  Buffer.Start(0);

  //waitがTrueならキャプチャ終了まで待つ
  if wait then
    while Status <> 0 do
      ;
end;

//ずっとキャプチャ
procedure TDDSDCapture.StartLoop;
begin
  //初期化がちゃんとされてないなら、帰る
  if not FInitialized then
    exit;

  //キャプチャ中ならなにもしない
  if Status <> 0 then
    exit;

  //キャプチャ開始
  Buffer.Start(DSCBSTART_LOOPING);
end;

//キャプチャ終了
procedure TDDSDCapture.Stop;
begin
  //初期化がちゃんとされてないなら、帰る
  if not FInitialized then
    exit;

  Buffer.Stop;
end;

//キリのいいところまで録音できた
procedure TDDSDCapture.Sync;
begin
  if Assigned(FOnRecording) then begin
    if FNotifyThread.IsTop then
      FOnRecording(Self, FLength - FHalfPos,FHalfPos)
    else
      FOnRecording(Self,0, FHalfPos);
  end;
end;

//ブロック転送
procedure TDDSDCapture.BlockCopy(offset: DWord; dest: Pointer;
  length: DWord);
var
  len1,len2:DWord;
  pBuf1,pBuf2:Pointer;
begin
  //初期化がちゃんとされてないなら、帰る
  if not FInitialized then
    exit;

  //ロック
  TRYDS(FCaptureBuffer.Lock(offset,length,pBuf1,@len1,pBuf2,@len2,0), 'DirectSoundCaptureBufferのロックに失敗しました');

  //Buf1からコピー
  Move(pBuf1^,dest^,len1);

  //Buf2からコピー
  if len1 < length then begin
    Inc(pByte(dest),len1);
    Move(pBuf2^,dest^,len2);
  end;

  //アンロック
  FCaptureBuffer.Unlock(pBuf1,len1,pBuf2,len2);
end;



//サポートしてるフォーマットのチェック
function TDDSDCapture.IsSupportedFormat(freq, bps: DWord;
  isStereo: Boolean): Boolean;
var
  caps:DSCCAPS;
begin
  //初期化に失敗してるなら、終わる
  if not FInitialized then begin
    result:=False;
    exit;
  end;

  DSCapture.GetCaps(@caps);

  if (caps.dwFormats And BufferFormatToDWord(freq,bps,IsStereo)) <> 0 then
    result:=True
  else
    result:=False;
end;


//キャプチャバッファのフォーマットを設定
procedure TDDSDCapture.SetupCaptureBuffer(freq, bps: DWord;
  isStereo: Boolean; length: DWord);
var
  desc:DSCBUFFERDESC;
  pn:Array[0..2] of DSBPOSITIONNOTIFY;
begin
  //初期化に失敗してるなら、終わる
  if not FInitialized then begin
    exit;
  end;

  //既に確保されてるなら、それを解放
  if Assigned(FCaptureBuffer) then
    FCaptureBuffer.Release;


  //プロパティに反映
  FLength:=length;
  FHalfPos:=length shr 1;

  //キャプチャフォーマットの設定
  with FWaveFormat do begin
    wFormatTag:=WAVE_FORMAT_PCM;
    if isStereo then
      nChannels:=2
    else
      nChannels:=1;
    nSamplesPerSec:=freq;
    nBlockAlign:=nChannels * bps div 8;
    nAvgBytesPerSec:=freq * nBlockAlign;
    wBitsPerSample:=bps;
    cbSize:=0;
  end;

  //キャプチャバッファのフォーマットを設定
  ZeroMemory(@desc,sizeof(desc));
  with desc do begin
    dwSize:=Sizeof(desc);
    dwBufferBytes:=length;
    lpwfxFormat:=@FWaveFormat;
  end;

  //キャプチャバッファの生成
  TRYDS(DSCapture.CreateCaptureBuffer(@desc, FCaptureBuffer, Nil), 'DirectSoundCaptureBufferオブジェクトの生成に失敗しました。');


  (* 通知用スレッドの設定 *)

  //イベントオブジェクトの生成
  FEvent[0]:=CreateEvent(Nil,False,False,Nil);
  FEvent[1]:=CreateEvent(Nil,False,False,Nil);
  FEvent[2]:=CreateEvent(Nil,False,False,Nil);

  //イベントの設定
  pn[0].dwOffset:=0;  //先頭
  pn[0].hEventNotify:=FEvent[0];

  pn[1].dwOffset:=FHalfPos;  //真中
  pn[1].hEventNotify:=FEvent[1];

  pn[2].dwOffset:=DSBPN_OFFSETSTOP;  //再生停止
  pn[2].hEventNotify:=FEvent[2];

  //ノーティファイオブジェクトの作成
  TRYDS(FCaptureBuffer.QueryInterface(IID_IDirectSoundNotify,FDSNotify),'DirectSoundNotifyオブジェクトの作成に失敗しました');
  TRYDS(FDSNotify.SetNotificationPositions(3,@pn), 'NotifyPositionの設定に失敗しました');

  //イベント通知用スレッドの作成
  FNotifyThread:=TDDSDCaptureNotifyThread.Create(Self);
  //FnotifyThread.Priority:=tpHigher;
end;



end.
