DDSDチュートリアル(3)

ストリーミング再生に挑戦

 ここでは、再生しながら中身を書きかえられるサウンドバッファを作り、そこに波形データを逐次書きこむ事で、メモリに入らないような長大なWaveファイルを再生したり、MOD等のように、リアルタイムで波形を作成しながら再生する方法について、ご説明します。

 ここでは、DirectSoundを使って矩形波で何か曲…そうですね、なるべく簡単なのが良いので「さいたさいた」を、波形データを合成しつつ、演奏させる事にします。

 

ストリーム再生の基礎

 まず、サウンドバッファを再生しながら中身を書き換えるのは良いのですが、「今、DirectSoundはサウンドバッファの中のどこを再生しているのか」という事をアプリケーションは知らなければ、サウンドバッファの中のどこからどこまでを書き換えて良いのか分からないので、まともな演奏が出来ないという事は自明です。

 TDDSDWaveDataオブジェクトでは、CreateStreamコンストラクタを使ってオブジェクトを作ると、それをTDDSDChannel.Playメソッドを使って再生した時に、

 のそれぞれのタイミングで、TDDSDWaveData.OnUpdateイベントを呼ぶようになっています。このイベントを利用することで、ユーザは再生状態に同期してサウンドバッファの書き換えができる、というわけです。

 

 Playメソッドで再生した場合は、上に書いたように2回しかイベントは発生しないのですが、LoopPlayメソッドで再生した場合、このイベントは何度も呼ばれることになります(サウンドバッファの最後まで演奏すると、また先頭から再生するから)

 つまり、LoopPlayメソッドで繰り返しサウンドバッファを演奏させつつ、バッファの中身を書き換えていくのがストリーム再生の基礎となります。

 それではアプリケーションを新規作成してください。

 そして、DDSDコンポーネントをフォームに貼りつけます。

 

DDSDWaveDataオブジェクトの作成

 まず、TDDSDWaveData型の変数を、wavとして宣言します。

 そして、wav用のイベントハンドラをUpdateWav手続きとして宣言します。

type
TForm1 = class(TForm)
  DDSD1: TDDSD;
  Button1: TButton;
  procedure FormCreate(Sender: TObject);
  procedure Button1Click(Sender: TObject);
  private
  { Private 宣言 }
  public
  { Public 宣言 }
    wav:TDDSDWaveData;
    procedure UpdateWav(Sender:TDDSDGenWave; Player:TDDSDChannel; ofs,len:Cardinal);
end;

 こんな感じ。

 宣言したwavオブジェクトを設定していきましょう。ここではForm.OnCreateイベントで設定する事にします。

procedure TForm1.FormCreate(Sender: TObject);
begin
  wav:=TDDSDWaveData.CreateStream(DDSD1, 22050, 8, False, 22050);
  wav.OnUpdate:=UpdateWav;
  DDSD1[0].WaveData:=wav;
end;

 先ほど説明した、CreateStreamコンストラクタでwavオブジェクトを生成します。

 そして、OnUpdateイベントハンドラにUpdateWav手続きを設定します。

 これで、DDSD1[0].PlayLoopとやれば、繰り返しUpdateWavイベントが呼ばれるようになります。

 

さいたさいた

 ではOnUpdateイベントで「さいたさいた」の音程データを読みながら、波形データを作っていくようにします。

 まず、音程データはsamplesという配列で、矩形波のサンプル数で与えることにします。サンプル数が長いほど、周波数の低い音が出ます。

 次に、countという変数で、どこまで演奏が終わったかを管理することにします。

 TForm1の宣言に、次のように書き加えます

type
TForm1 = class(TForm)
DDSD1: TDDSD;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
  procedure UpdateWav(Sender:TDDSDGenWave; Player:TDDSDChannel; ofs,len:Cardinal);
public
{ Public 宣言 }
  wav:TDDSDWaveData;
  count:Integer;
end;


const
  samples:Array[0..47] of Integer =
  ( 84,75,67,67, //さいた
  84,75,67,67, //さいた
  56,67,75,84, //チューリップの
  75,67,75,75, //はなが
  84,75,67,67, //ならんだ
  84,75,67,67, //ならんだ
  56,67,75,84, //あかしろ
  75,67,84,84, //きいろ
  56,56,67,56, //どのはな
  50,50,56,56, //みても
  67,67,75,75, //きれいだ
  84,84,84,84 //な
  );

 ではいよいよOnUpdateイベントの作成です。

procedure TForm1.UpdateWav(Sender:TDDSDGenWave; Player:TDDSDChannel; ofs,len:Cardinal);
var
  i:Integer;
  buffer:Array[0..11024] of Byte;
begin
  for i:=0 to 11024 do begin
    buffer[i]:= ((i div samples[count]) And 1) * 64; //サンプル数がsamples[idx] * 2の矩形波を作る
  end;
  //転送
  wav.BlockCopy(ofs,@buffer,len);
  //カウンタを増やす
  Inc(count);
  //ループさせる
  if count > High(samples) then
  count:=0;
end;

 まず、OnUpdateイベントの持っている引数について説明します。

 尚、

 となります。

 このイベントハンドラでは、Samplesで与えられるサンプル長を持った矩形波を作って、wavオブジェクトのサウンドバッファに転送しています。

 

ちゃんとさいたかな?

 では、再生してみましょう。

 ボタンを一個配置して、それをクリックすると再生される事にしましょう。

procedure TForm1.Button1Click(Sender: TObject);
var
  i:Integer;
buffer:Array[0..11024] of Byte;
begin
  DDSD1[0].Stop;

  //バッファの前半分を予め埋める
  for i:=0 to 11024 do begin
    buffer[i]:= ((i div samples[0]) And 1) * 64; //サンプル数がsamples[idx] * 2の矩形波を作る
  end;

  count:=1;
  wav.BlockCopy(0, @buffer, 11025);
  DDSD1[0].LoopPlay;
end;


 ちょっと長いのでがっかりされたかもしれませんが(^^;)

 イベントが呼ばれるのは、実際に再生がはじまってからなので、まずバッファの前半分を埋めておかないと、最初に無音が再生されてしまうからです。

 なんにせよ、これで「さいたさいた」が演奏されたはずです。

 MODプレーヤやMIDIエミュレータなどを作成している方々の苦労が少しはわかりました?

 

最後に注意

 CreateStreamによって作成されたTDDSDWaveDataオブジェクトは、複数のチャンネルから同時に再生させることが出来ません。複数のTDDSDWaveDataオブジェクトを作って、一つ一つを1チャンネルに割り当ててください。 通常の用途には、ストリーム再生が必要なチャンネルなんて、一個もあれば十分でしょう。

 TDDSDWaveData.OnUpdateイベントは、実はVCL本体のスレッドとは別のスレッドから呼ばれています。重たい処理をやっている途中でも再生が途切れないようにするためです。このため、スレッド競合を回避するためにOnUpdateイベントハンドラについて、以下を守ってください。