Memainkan Suara 3D dengan DirectX

June 1, 2006 at 8:21 am | Posted in Code Samples, Tutorials | Leave a comment

by: Zamrony P Juhara

Pernah memainkan game-game 3D terbaru? Tentunya pernah merasakan
seolah-olah efek suara datang dari berbagai arah. Pertanyaannya sekarang, bagaimana
melakukannya?

Artikel ini ditulis terinspirasi pertanyaan yang diajukan seorang
member milis Delphindo tentang
bagaimana memainkan beberapa sample suara pada speaker yang berbeda-beda. Untuk
mengikuti artikel ini dengan lancar dan menjawab pertanyaan tersebut di atas,
paling tidak anda telah memiliki pengetahuan tentang dasar bagaimana memainkan
suara menggunakan DirectX. Anda bisa membaca artikel Memainkan
File WAV dan MIDI dengan DirectX
yang saya tulis sebagai pendahuluan.

Ok kita mulai..

Pendahuluan 3D Sound

Persepsi suara pada dunia nyata dipengaruhi beberapa faktor
yakni posisi dan kecepatan sumber suara, lingkungan, dan posisi, kecepatan dan
orientasi pendengar.

Sumber Suara (Sound Source)

Sumber suara adalah obyek yang menghasilkan gelombang suara.
Parameter yang mempengarui bagaimana persepsi suara ditentukan oleh posisi,
kecepatan, orientasi, lingkungan dan lain-lain.

Pendengar (Listener)

Parameter utama listener yang mempengaruhi persepsi suara adalah
posisi, kecepatan dan orientasi pendengar. Posisi default listener ada di koordinat
(0,0,0). Orientasi pendengar ada dua yakni top vector dan front vector (lihat
gambar dibawah). Top vector defaultnya adalah (0,1,0) sedangkan front vector
defaultnya adalah (0,0,1)

gbr1_3d_sound.JPG
Gbr 1

Kerucut Suara

Suara dapat dikategori berdasarkan orientasi sebagai :

• Suara tidak berorientasi
• Suara berorientasi

Suara tidak berorientasi terdengar sama di segala arah, suara
berorientasi hanya terdengar pada arah tertentu saja. Kerucut suara adalah model
keras-lemahnya suara yang memiliki orientasi (lihat Gbr 2). Kerucut ini terbagi
atas dua yakni kerucut dalam (inside cone) dan kerucut luar (outside cone).

Di area kerucut dalam, suara terdengar pada intensitas bunyi
paling keras sedangkan dikerucut luar, semakain menjauhi kerucut dalam semakin
lemah suara. Besarnya penurunan intensitas suara ditentukan oleh factor yang
ditentukan aplikasi.

Jarak (Distance)

Jarak antara sumber suara dan pendengar mempengarhi persepsi
pendengaran. Semakin dekat sumber suara dengan listener semakin keras sumber
suara terdengar. Jarak minimum adalah jarak dimana suara tidak lagi mengalami
penguatan sedangkan jarak maksimum adalah jarak terbesar dimana suatu sumber
suara tidak lagi mengalami pelemahan suara.

Total meter untuk sebuah vektor satuan disebut factor jarak
(distance factor), nilai defaultnya adalah 1.0. Contoh jika vector kecepatan
adalah (2,2,0) maka kecepatan adalah 2 meter/s ke arah x, 2 meter/s ke arah
y dan 0 meter/s ke arah z.

gbr2_3d_sound.JPG

Gbr 2

Efek Doppler

Sumber suara atau listener yang bergerak akan mengalami efek
Doppler (silakan review pelajaran Fisika). DirectX akan otomatis melakukan kalkulasi
efek ini pada sumber suara atau listener yang bergerak dengan mengacu pada faktor
Doppler yang besarnya ditentukan oleh aplikasi.

Rolloff

Rolloff adalah jumlah penguatan yang diterapkan pada sumber
suara berdasarkan jarak sumber suara ke pendengar.

Elemen-Elemen Dasar Sound 3D Pada DirectX

3D AudioPath

Audiopath adalah obyek yang mengatur aliran data sample suara.

3D Sound Buffer

Sound buffer adalah representasi sumber suara. Sound buffer menyimpan data sample
suara. Pada DirectX ada dua jenis sound buffer yakni buffer biasa (IDirectSoundBuffer8)
dan buffer 3D (IDirectSound3DBuffer8). Pada buffer 3D ini kita dapat
mengatur parameter sumber suara meliputi posisi, kecepatan,

3D Listener

Listener adalah representasi pendengar. Pada DirectX, obyek
pendengar direpresentasi oleh IDirectSound3DListener8.

Mendapatkan Instance 3D AudioPath

Untuk memainkan suara 3D, kita harus menciptakan AudioPath
standar untuk masing-masing suara yang hendak kita mainkan. AudioPath ini harus
kita ciptakan menggunakan flag DMUS_APATH_DYNAMIC_3D. Contoh:

FPerformance.CreateStandardAudioPath(
                           DMUS_APATH_DYNAMIC_3D,
                           64,
                           true,
                           FAudioPath);

Bagi anda yang belum mengerti CreateStandardAudioPath, silakan
membaca artikel yang saya sebutkan di atas.

Mendapatkan Sound Buffer 3D dari Audiopath

Kita perlu mendapatkan pointer instance IDirectSound3DBuffer8
karena proses mengatur posisi sumber suara, kecepatan sumber suara dilakukan
menggunakan metode yang ada pada interface ini.

Caranya adalah dengan menggunakan fungsi GetObjectInPath

function GetObjectInPath(dwPChannel,dwStage,dwBuffer: cardinal;
                         guidObject:TGUID;
                         dwIndex:cardinal;
                         iidInterface:TGUID;
                         out ppObject:IUnknown):HResult;stdcall;

Parameter

dwPChannel adalah channel dimana kita hendak mendapatkan
buffer. Jika kita isi dengan DMUS_PCHANNEL_ALL maka, DirectX akan mencarinya
disemua channel.

dwStage adalah tipe obyek yang hendak kita ambil.
Untuk sound buffer kita isi dengan DMUS_PATH_BUFFER

dwBuffer index dari buffer.Karena sound buffer untuk
tiap audiopath hanya ada satu dwBuffer selalu kita isi 0.

guidObject adalah GUID obyek, untuk mendapatkan sound
buffer tidak diperlukan GUID oleh karena itudapat kita isi dengan GUID_NULL

dwIndex adalah index obyek dari beberapa sejumlah
obyek. Karena sound buffer untuk tiap audiopath hanya ada satu dwIndex ini diabaikan
oleh karena itu kita isi 0.

iidInterface adalah pengenal interface obyek yang
kita inginkan. Untuk sound buffer 3D versi 8 kita isi dengan IID_IDirectSound3DBuffer8

ppObject variabel yang akan menampung alamat instance
obyek dalam hal ini pointer ke IDirectSound3DBuffer8

Contoh:

FAudioPath.GetObjectInPath(DMUS_PCHANNEL_ALL,
                           DMUS_PATH_BUFFER,0
                           GUID_NULL,0,
                           IID_IDirectSound3DBuffer8,
                           F3DBuffer);

Mendapatkan 3D Listener dari Audiopath

Listener dapat kita peroleh dari primary buffer, menggunakan
GetObjectInPath audiopath. Mirip dengan contoh di atas hanya dwPChannel harus
kita isi dengan 0 karena primary buffer ada pada channel 0. dwStage kita isi
dengan DMUS_PATH_PRIMARY_BUFFER karena Listener harus diambil dari primary buffer.
iidInterface kita isi dengan IID_IDirectSound3DListener8.

FAudioPath.GetObjectInPath(0,
                           DMUS_PATH_PRIMARY_BUFFER,0
                           GUID_NULL,0,
                           IID_IDirectSound3DListener8,
                           F3DListener);

Immediate Setting dan Deferred Setting

Tiap kali parameter sound buffer dan listener 3D diubah, akan
menyebabkan terjadi kalkulasi dan mixing ulang yang membutuhkan kerja CPU (atau
DSP di sound card). Untuk meminimalkan turunnya performa karena pengubahan parameter
yang terlalu sering, semua fungsi anggota IDirectSound3DBuffer8 dan IDirectSound3DListener8
yang mengubah parameter suara 3D memiliki parameter dwApply yang dapat diisi
dengan nilai DS3D_DEFERRED atau DS3D_IMMEDIATE. Pengubahan parameter yang ditandai
sebagai deferred setting, tidak langsung menyebabkan terjadinya remixing. Remixing
baru dikerjakan bila kita memanggil fungsi anggota IDirectSound3DListener8 CommitDeferredSettings.

function CommitDeferredSetings:HResult;stdcall;

Parameter yang menggunakan DS3D_IMMEDIATE langsung menyebabkan
DirectX melakukan remixing.

Apa yang bisa kita lakukan dengan Sound
buffer 3D?

Mengubah dan Mendapatkan Posisi Sample
Suara.

IDirectSound3DBuffer8 memiliki metode SetPosition dan GetPosition
yang berguna untuk mengatur atau mendapatkan posisi sumber suara.

function SetPosition(x, y, z: TD3DValue; dwApply:DWORD) : HResult; stdcall;

x, y, z adalah koordinat sumber
suara, dwApply menentukan tipe pengubahan setting yakni DS3D_IMMEDIATE
untuk mengubah

function GetPosition(var value: TD3DVector):HResult; stdcall;

value akan diisi dengan vektor posisi.

Mengubah dan Mendapatkan Kecepatan Sample
Suara.

IDirectSound3DBuffer8 memiliki metode SetVelocity dan GetVelocity
yang berguna untuk mengatur atau mendapatkan kecepatan sumber suara.

function SetVelocity(x, y, z: TD3DValue;dwApply: DWORD) : HResult; stdcall;
function GetVelocity(var value: TD3DVector) : HResult; stdcall;

Parameternya mirip dengan fungsi SetPosition/GetPosition sebelumnya.

Mengatur Mode Pemrosesan Suara 3D

Pemrosesan suara 3D terdiri atas tiga mode yakni normal, head
relative dan disabled. Pada mode normal, sumber suara posisi dan orientasinya
absolute pada world space. Mode ini cocok untuk sumber suara yang tidak bergerak
dan merupakan mode pemrosesan default. Pada mode head relative, parameter suara
3D adalah relatif terhadap posisi, kecepatan dan orientasi listener. Jika listener
bergerak, maka semua parameter sumber suara akan dikalkulasi ulang. Mode ini
cocok untuk sumber suara yang bergerak, contoh suara nyamuk yang berkeliling
disekitar kepala listener. Mode disabled pemrosesan 3D dimatikan, sehingga sumber
suara seolah-olah dating dari pusat kepala listener.

Mode pemrosesan suara 3D diatur menggunakan fungsi SetMode
dan GetMode

function SetMode(dwMode,dwApply:cardinal):HResult;stdcall;

Parameter dwMode berisi mode pemrosesan suara, bisa
diisi dengan DS3DMODE_NORMAL, DS3DMODE_HEADRELATIVE atau DS3DMODE_DISABLE. dwApply
bisa diisi dengan DS3D_DEFERRED atau DS3D_IMMEDIATE.

function GetMode(var dwMode:cardinal):HResult;stdcall;

dwMode akan diisi mode pemrosesan saat ini.

Mengatur Jarak Minimum dan Maksimum

Mengatur jarak minimum dan maksimum, bisa dilakukan dengan
SetMinDistance dan SetMaksimumDistance.

function SetMinDistance(flMinDistance:TD3DValue; dwApply:cardinal):HResult;stdcall;
function SetMaxDistance(flMaxDistance:TD3DValue; dwApply:cardinal):HResult;stdcall;

Untuk mendapatkan jarak minimum dan jarak maksimum, kita menggunakan
GetMinDistance dan GetMaxDistance.

function GetMinDistance(var flMinDistance:TD3DValue):HResult;stdcall;
function GetMaxDistance(var flMaxDistance:TD3DValue):HResult;stdcall;

Mengatur Kerucut Suara

Bentuk dan orientasi kerucut suara diatur menggunakan fungsi-fungsi
SetConeAngles, SetConeOriantation dan SetConeOutsideVolume.

function SetConeOrientation(x, y, z: TD3DValue; dwApply: DWORD) : HResult; stdcall;
function SetConeAngles(flAngle: TD3DValue; dwApply: DWORD): HResult; stdcall;
function SetConeOutsideVolume(flOutsideVolume:TD3DValue; dwApply: DWORD): HResult; stdcall;

Apa yang bisa kita lakukan dengan Listener
3D?

Mengatur dan mendapatkan Posisi Pendengar

Untuk mengatur posisi pendengar IDirectSound3DListener8 memiliki
metode SetPosition

function SetPosition(x,y,z:TD3DValue; dwApply:cardinal):HResult;stdcall;

x,y,z adalah koordinat posisi pendengar. Defaultnya adalah
pada posisi (0,0,0). Tipe TD3DValue sendiri adalah tipe data floating point
dideklarasikan di unit DirectSound.

dwApply menentukan apakah pengubahan posisi dilakukan segera
atau tidak.

Untuk mendapatkan posisi pendengar kita menggunakan GetPosition

function GetPosition(var pPosition:TD3DVector):HResult;stdcall;

pPosition akan diisi dengan koordinat posisi pendengar.

Mengatur dan mendapatkan Orientasi Pendengar

Untuk mengubah orientasi kita menggunakan SetOrientation

function setOrientation(xFront,yFront,zFront,xTop,yTop,zTop:TD3DValue; dwApply:cardinal):HResult;stdcall;

xFront, yFront dan zFront adalah
vektor orientasi muka pendengar. xTop, yTop dan zTop
adalah vektor atas kepala pendengar.

Untuk mendapatkan orientasi pendengar menggunakan GetOrientation

function GetOrientation(var pvFront,pvTop:TD3DVector): HResult; stdcall;

Mengatur Faktor Jarak

Distance faktor diset dengan SetDistanceFactor

function SetDistanceFactor(flDistanceFactor:TD3DValue; dwApply:cardinal):HResult;stdcall;

Untuk mendapatkan distance factor menggunakan GetDistanceFactor

function GetDistanceFactor(var flDistanceFactor:TD3DValue):HResult;stdcall;

Mengatur Faktor Doppler

Doppler faktor diset dengan SetDopplerFactor

function SetDopplerFactor(flDopplerFactor:TD3DValue; 
dwApply:cardinal):HResult;stdcall;

Untuk mendapatkan doppler factor menggunakan GetDopplerFactor

function GetDopplerFactor(var flDistanceFactor:TD3DValue):HResult;stdcall;

Mengatur Faktor Rolloff

Faktor Rolloff diset dengan SetRolloffFactor

function SetRolloffFactor(flRolloffFactor:TD3DValue; dwApply:cardinal):HResult;stdcall;

Untuk mendapatkan rolloff factor menggunakan GetRolloffFactor

function GetRolloffFactor(var flRolloffFactor:TD3DValue):HResult;stdcall;

Ok sekarang mari kita buat wrapper class untuk 3D sound.

Desain Kelas

Semua fungsionalitas memainkan suara 3D akan di enkapsulasi
dalam beberapa kelas berikut:

TSoundCollection

TSoundCollection adalah turunan TCollection menyimpan item-item
TSoundItem. Kelas ini bertanggung jawab untuk proses inisialisasi/finalisasi
performance dan loader dan manajemen memori performance dan loader. Selain itu
kelas ini bertanggung jawab atas manajemen memori item-item TSoundItem.

TSoundItem

TSoundItem adalah representasi obyek sound yang mengenkapsulasi
proses dasar memainkan suara menggunakan DirectX. TSoundItem diturunkan dari
TCollectionItem, memiliki fungsi-fungsi dasar untuk loading data sample suara
dari file dan dari stream, memulai playback suara, menghentikan playback, mendapatkan
status playback dan juga memainkan suara secara berulang-ulang (looping otomatis)
ketika suara selesai dimainkan. Karena target utama kelas ini adalah untuk game
programming, maka fitur paling penting TSoundItem adalah kemampuannya untuk
melakukan mixing sample suara dengan sample suara yang sebelumnya sedang dimainkan.

TSound3DCollection

Kelas ini akan diturunkan dari TSoundCollection, guna mengatur
manajemen obyek-obyek sound 3D. Kelas ini akan memiliki satu property yakni
Listener bertipe TSound3DListener. Kelas ini bertanggung jawab atas manajemen
memori obyek listener. aplikasi yang mengakses instance listener tidak boleh
membebaskan memorinya.

TSound3DListener

Kelas ini adalah representasi listener. Untuk tiap aplikasi,
listener hanya ada satu. Untuk mendapatkan instance kelas ini, aplikasi tidak
menciptakannya langsung, melainkan menggunakan property Listener milik TSound3DCollection.
Kelas ini dilengkapi fungsionalitas untuk mengubah parameter listener. Fungsionalitas
ini didelegasikan ke instance kelas TListener3DParam yang dihandle internal
oleh TSound3DListener.

Selain fitur pengubahan parameter,fitur lainnya adalah melakukan
commit pengubahan deferred setting, sehingga kalkulasi sound 3D dilakukan, yakni
dengan prosedur UpdateParams.

TListener3DParam

Kelas ini adalah kelas yang bertanggung jawab mengubah parameter
listener. Kelas ini ditangani internal oleh TSound3DListener. Aplikasi tidak
menciptakan langsung kelas ini.

TSound3DItem

TSound3DItem diturunkan dari TSoundItem dengan tambahan fitur
kemampuan memainkan suara 3D. TSound3DItem akan dilengkapi dengan metode-metode
yang berguna untuk mengubah parameter sumber suara 3D. Kelas ini akan menghandle
manajemen memori sound buffer 3D. Untuk dapat mengubah parameter buffer, TSound3DItem
akan memanfaatkan kelas TBuffer3DParam, dimana instance kelas ini dihandle internal
oleh TSound3DItem.

Semua pengubahan yang dilakukan pada buffer 3D maupun listener
tidak otomatis mengubah parameter actual sound buffer 3D maupun listener (deferred).

TBuffer3DParam

Kelas ini adalah kelas enkapsulasi proses mengubah parameter
sound buffer 3D meliputi mengatur posisi sumber suara, kecepatan umber suara
dan lain-lain.

Implementasi Kelas

{====================================
 unit enkapsulasi 3D sound
 ====================================
 (c) 2006 zamrony p juhara

 http://members.lycos.co.uk/zamronypj
=====================================}
unit udxaudio;

interface
uses classes,windows,activex,
     DirectSound,DirectMusic,
     DirectXGraphics;

type
   TSoundValue=TD3DValue;
   TSoundVector=TD3DVector;

   TSoundCollection=class(TCollection)
   private
     FLoader:IDirectMusicLoader8;
     FPerformance:IDirectMusicPerformance8;
   public
     constructor Create(ItemClass:TCollectionItemClass);
     destructor Destroy;override;
   end;

   TSoundItem=class(TCollectionItem)
   private
     FSegment:IDirectMusicSegment8;
     FSegmentState:IDirectMusicSegmentState8;
     FAudioPath:IDirectMusicAudioPath8;
     FSoundCollection:TSoundCollection;
     FInternalStream:TMemoryStream;
     FLooped: boolean;
     function GetIsPlaying:boolean;
     procedure SetSegmentLoop(const loop:boolean);
     procedure SetLooped(const Value: boolean);
   protected
     procedure Init;virtual;
   public
     constructor Create(Collection:TCollection);override;
     destructor Destroy;override;
     function Play:HResult;
     function Stop:HResult;
     procedure LoadFromFile(const filename:string);
     procedure LoadFromStream(Stream:TStream);
   published
     property IsPlaying:boolean read GetIsPlaying;
     property Looped:boolean read FLooped write SetLooped;
   end;

   T3DMode=(md3DNormal,md3DHeadRelative,md3DDisabled);
   TConeAngles=record
     InsideAngle:cardinal;
    OutsideAngle:cardinal;
   end;

   TBuffer3DParam=class(TObject)
   private
     FBuffer:IDirectSound3DBuffer8;

     procedure SetConeOrientation(const Value: TSoundVector);
     procedure SetConeOutsideVolume(const Value: integer);
     procedure SetMaxDistance(const Value: TSoundValue);
     procedure SetMinDistance(const Value: TSoundValue);
     procedure SetMode(const Value: T3DMode);
     procedure SetPosition(const Value: TSoundVector);
     procedure SetVelocity(const Value: TSoundVector);
     function GetPosition: TSoundVector;
     function GetVelocity: TSoundVector;
     function GetMinDistance: TSoundValue;
     function GetMaxDistance: TSoundValue;
     function GetConeOrientation: TSoundVector;
     procedure SetConeAngles(const Value: TConeAngles);
     function GetConeAngles: TConeAngles;
     function GetConeOutsideVolume: integer;
     function GetMode: T3DMode;
   public
   published
     property Position:TSoundVector read GetPosition write SetPosition;
     property Velocity:TSoundVector read GetVelocity write SetVelocity;
     property MinDistance:TSoundValue read GetMinDistance write SetMinDistance;
     property MaxDistance:TSoundValue read GetMaxDistance write SetMaxDistance;
     property Mode:T3DMode read GetMode write SetMode;

     property ConeAngles:TConeAngles read GetConeAngles write SetConeAngles;
     property ConeOrientation:TSoundVector read GetConeOrientation write SetConeOrientation;
     property ConeOutsideVolume:integer read GetConeOutsideVolume write SetConeOutsideVolume;
   end;

   TOrientation=record
     Front:TSoundVector;
     Top:TSoundVector;
   end;

   TListener3DParam=class(TObject)
   private
     FListener:IDirectSound3DListener8;

     procedure SetDistanceFactor(const Value: TSoundValue);
     procedure SetDopplerFactor(const Value: TSoundValue);
     procedure SetPosition(const Value: TSoundVector);
     procedure SetRollOffFactor(const Value: TSoundValue);
     procedure SetVelocity(const Value: TSoundVector);
     procedure SetOrientation(const Value: TOrientation);
     function GetPosition:TSoundVector;
     function GetVelocity:TSoundVector;
     function GetDistanceFactor:TSoundValue;
     function GetDopplerFactor:TSoundValue;
     function GetRollOffFactor:TSoundValue;
     function GetOrientation:TOrientation;
   published
     property Position:TSoundVector read GetPosition write SetPosition;
     property Velocity:TSoundVector read GetVelocity write SetVelocity;
     property Orientation:TOrientation read GetOrientation write SetOrientation;

     property DistanceFactor:TSoundValue read GetDistanceFactor write SetDistanceFactor;
     property DopplerFactor:TSoundValue read GetDopplerFactor write SetDopplerFactor;
     property RollOffFactor:TSoundValue read GetRollOffFactor write SetRollOffFactor;
   end;

   TSound3DItem=class(TSoundItem)
   private
     FBuffer:IDirectSound3DBuffer8;
     FBufferParam: TBuffer3DParam;
   protected
     procedure Init;override;
   public
     constructor Create(Collection:TCollection);override;
     destructor Destroy;override;
   published
     property BufferParam:TBuffer3DParam read FBufferParam;
   end;

   TSound3DListener=class(TObject)
   private
     FListenerParam: TListener3DParam;
   public
     constructor Create;
     destructor Destroy;override;
     procedure UpdateParams;
   published
     property ListenerParam:TListener3DParam read FListenerParam;
   end;

   TSound3DCollection=class(TSoundCollection)
   private
     FDefault3DPath:IDirectMusicAudioPath8;
     FListener:TSound3DListener;
     function GetListener:TSound3DListener;
   public
     constructor Create(ItemClass:TCollectionItemClass);
     destructor Destroy;override;
   published
     property Listener:TSound3DListener read GetListener;
   end;

implementation

{ TSoundItem }

constructor TSoundItem.Create(Collection: TCollection);
begin
  inherited;
  FSoundCollection:=Collection as TSoundCollection;
  Init;
end;

destructor TSoundItem.Destroy;
begin
  FSegment.Unload(FAudioPath);

  FSegment:=nil;
  FSegmentState:=nil;
  FAudioPath:=nil;

  FInternalStream.Free;
  inherited;
end;

function TSoundItem.GetIsPlaying: boolean;
begin
  result:=(FSegment<>nil) and
          (FSegmentState<>nil) and
          (FSoundCollection.FPerformance.IsPlaying(FSegment,
                              FSegmentState)=S_OK);
end;

procedure TSoundItem.Init;
begin
  FSoundCollection.FPerformance.CreateStandardAudioPath(
                       DMUS_APATH_SHARED_STEREOPLUSREVERB,
                       64,true,
                       FAudioPath);
end;

procedure TSoundItem.LoadFromFile(const filename: string);
var afilename:widestring;
begin
  afilename:=filename;
  FSoundCollection.FLoader.LoadObjectFromFile(CLSID_DirectMusicSegment,
                               IDirectMusicSegment8,PWideChar(aFilename),
                               FSegment);
  FSegment.Download(FAudioPath);
  SetSegmentLoop(FLooped);
end;

procedure TSoundItem.LoadFromStream(Stream: TStream);
var objdesc:TDMus_ObjectDesc;
begin
  if FInternalStream=nil then
    FInternalStream:=TMemoryStream.Create
  else
  begin
    FInternalStream.Clear;
  end;

  FInternalStream.CopyFrom(Stream,0);

  ZeroMemory(@objDesc,sizeof(TDMus_ObjectDesc));
  objdesc.dwSize:=sizeof(TDMus_ObjectDesc);
  objdesc.dwValidData:=DMUS_OBJ_CLASS or DMUS_OBJ_MEMORY;
  objdesc.guidClass:=CLSID_DirectMusicSegment;
  objdesc.llMemLength:=FInternalStream.Size;
  objdesc.pbMemData:=FInternalStream.Memory;

  FSoundCollection.FLoader.GetObject(objdesc,
                     IDirectMusicSegment8,
                     FSegment);
  FSegment.Download(FAudioPath);
  SetSegmentLoop(FLooped);
end;

function TSoundItem.Play: HResult;
var aseg:IDirectMusicSegmentState;
begin
  result:=FSoundCollection.FPerformance.PlaySegmentEx(FSegment,
                  nil,
                  nil,
                  DMUS_SEGF_SECONDARY,
                  0,
                  aseg,
                  nil,
                  FAudioPath
                  );
  aseg.QueryInterface(IDirectMusicSegmentState8,FSegmentState);
end;

procedure TSoundItem.SetLooped(const Value: boolean);
begin
  if FLooped<>Value then
  begin
    FLooped := Value;
    SetSegmentLoop(FLooped);
  end;
end;

procedure TSoundItem.SetSegmentLoop(const loop: boolean);
var loop_count:cardinal;
begin
  if FSegment<>nil then
  begin
    if loop then
      loop_count:=DMUS_SEG_REPEAT_INFINITE
    else
      loop_count:=0;

    FSegment.SetRepeats(loop_count);
  end;
end;

function TSoundItem.Stop: HResult;
begin
  result:=FSoundCollection.FPerformance.StopEx(FSegment,0,0);
end;

{ TTSoundCollection }

constructor TSoundCollection.Create(ItemClass: TCollectionItemClass);
begin
  inherited;
  CoCreateInstance(CLSID_DirectMusicPerformance,
                   nil,
                   CLSCTX_INPROC,
                   IDirectMusicPerformance8,
                   FPerformance);
  CoCreateInstance(CLSID_DirectMusicLoader,
                   nil,
                   CLSCTX_INPROC,
                   IDirectMusicLoader8,
                   FLoader);

  FPerformance.InitAudio(nil,
                         nil,
                         0,
                         DMUS_APATH_SHARED_STEREOPLUSREVERB,
                         64,
                         DMUS_AUDIOF_ALL,
                         nil
                         );
end;

destructor TSoundCollection.Destroy;
begin
  inherited;
  FLoader:=nil;
  FPerformance.CloseDown;
  FPerformance:=nil;
end;

{ TSound3DItem }

constructor TSound3DItem.Create(Collection: TCollection);
begin
  inherited;
  FBufferParam:=TBuffer3DParam.Create;
  FBufferParam.FBuffer:=FBuffer;
end;

destructor TSound3DItem.Destroy;
begin
  FBufferParam.Free;
  FBuffer:=nil;
  inherited;
end;

procedure TSound3DItem.Init;
begin
  FSoundCollection.FPerformance.CreateStandardAudioPath(
                       DMUS_APATH_DYNAMIC_3D,
                       64,true,
                       FAudioPath);

  FAudioPath.GetObjectInPath(DMUS_PCHANNEL_ALL,
                         DMUS_PATH_BUFFER,
                         0,
                         GUID_NULL,
                         0,
                         IID_IDirectSound3DBuffer8,
                         FBuffer);
end;

{ TBuffer3DParam }

procedure TBuffer3DParam.SetConeOrientation(const Value: TSoundVector);
begin
  FBuffer.SetConeOrientation(value.x,
                             value.y,
                             value.z,
                             DS3D_DEFERRED);
end;

procedure TBuffer3DParam.SetConeOutsideVolume(const Value: integer);
begin
  FBuffer.SetConeOutsideVolume(value,DS3D_DEFERRED);
end;

procedure TBuffer3DParam.SetMaxDistance(const Value: TSoundValue);
begin
  FBuffer.SetMaxDistance(value,DS3D_DEFERRED);
end;

function TBuffer3DParam.GetMaxDistance: TSoundValue;
begin
  FBuffer.GetMaxDistance(result);
end;

procedure TBuffer3DParam.SetMinDistance(const Value: TSoundValue);
begin
  FBuffer.SetMinDistance(value,DS3D_DEFERRED);
end;

function TBuffer3DParam.GetMinDistance: TSoundValue;
begin
  FBuffer.GetMinDistance(result);
end;

procedure TBuffer3DParam.SetMode(const Value: T3DMode);
const amode:array[md3DNormal..md3DDisabled] of cardinal=
      (
       DS3DMODE_NORMAL,
       DS3DMODE_HEADRELATIVE,
       DS3DMODE_DISABLE
       );
begin
  FBuffer.SetMode(amode[value],DS3D_DEFERRED);
end;

function TBuffer3DParam.GetMode: T3DMode;
const a3Dmode:array[DS3DMODE_NORMAL..DS3DMODE_DISABLE] of T3DMode=
      (
       md3DNormal,
       md3DHeadRelative,
       md3DDisabled
       );
var amode:cardinal;
begin
  FBuffer.GetMode(amode);
  result:=a3DMode[amode];
end;


procedure TBuffer3DParam.SetPosition(const Value: TSoundVector);
begin
  FBuffer.SetPosition(value.x,
                      value.y,
                      value.z,
                      DS3D_DEFERRED);
end;

function TBuffer3DParam.GetPosition: TSoundVector;
begin
  FBuffer.GetPosition(result);
end;

procedure TBuffer3DParam.SetVelocity(const Value: TSoundVector);
begin
  FBuffer.SetVelocity(value.x,
                      value.y,
                      value.z,
                      DS3D_DEFERRED);
end;

function TBuffer3DParam.GetVelocity: TSoundVector;
begin
  FBuffer.GetVelocity(result);
end;


function TBuffer3DParam.GetConeOrientation: TSoundVector;
begin
  FBuffer.GetConeOrientation(result)
end;

procedure TBuffer3DParam.SetConeAngles(const Value: TConeAngles);
begin
  FBuffer.SetConeAngles(value.InsideAngle,
                        value.OutsideAngle,
                        DS3D_DEFERRED);
end;

function TBuffer3DParam.GetConeAngles: TConeAngles;
begin
  FBuffer.GetConeAngles(result.InsideAngle,
                        result.OutsideAngle);
end;

function TBuffer3DParam.GetConeOutsideVolume: integer;
begin
  FBuffer.GetConeOutsideVolume(result);
end;

{ TListener3DParam }

function TListener3DParam.GetDistanceFactor: TSoundValue;
begin
  FListener.GetDistanceFactor(result);
end;

function TListener3DParam.GetDopplerFactor: TSoundValue;
begin
  FListener.GetDopplerFactor(result);
end;

function TListener3DParam.GetOrientation: TOrientation;
begin
  FListener.GetOrientation(result.Front,result.Top);
end;

function TListener3DParam.GetPosition: TSoundVector;
begin
  FListener.GetPosition(result);
end;

function TListener3DParam.GetRollOffFactor: TSoundValue;
begin
  FListener.GetRolloffFactor(result);
end;

function TListener3DParam.GetVelocity: TSoundVector;
begin
  FListener.GetVelocity(result);
end;

procedure TListener3DParam.SetDistanceFactor(const Value: TSoundValue);
begin
  FListener.SetDistanceFactor(value,DS3D_DEFERRED);
end;

procedure TListener3DParam.SetDopplerFactor(const Value: TSoundValue);
begin
  FListener.SetDopplerFactor(value,DS3D_DEFERRED);
end;

procedure TListener3DParam.SetOrientation(const Value: TOrientation);
begin
  FListener.SetOrientation(value.Front.x,
                           value.Front.y,
                           value.Front.z,
                           value.Top.x,
                           value.Top.y,
                           value.Top.z,
                           DS3D_DEFERRED);
end;

procedure TListener3DParam.SetPosition(const Value: TSoundVector);
begin
  FListener.SetPosition(value.x,value.y,value.z,DS3D_DEFERRED);
end;

procedure TListener3DParam.SetRollOffFactor(const Value: TSoundValue);
begin
  FListener.SetRolloffFactor(value,DS3D_DEFERRED);
end;

procedure TListener3DParam.SetVelocity(const Value: TSoundVector);
begin
  FListener.SetVelocity(value.x,
                        value.y,
                        value.z,
                        DS3D_DEFERRED);
end;

{ TSound3DCollection }

constructor TSound3DCollection.Create(ItemClass: TCollectionItemClass);
begin
  inherited;
  FPerformance.CreateStandardAudioPath(DMUS_APATH_DYNAMIC_3D,
                                       64,
                                       true,
                                       FDefault3DPath);
  FPerformance.SetDefaultAudioPath(FDefault3DPath);
end;

destructor TSound3DCollection.Destroy;
begin
  FDefault3DPath:=nil;
  FListener.Free;
  inherited;
end;

function TSound3DCollection.GetListener: TSound3DListener;
begin
  if FListener=nil then
  begin
     FListener:=TSound3DListener.Create;

     FDefault3DPath.GetObjectInPath(0,
                         DMUS_PATH_PRIMARY_BUFFER,
                         0,
                         GUID_NULL,
                         0,
                         IID_IDirectSound3DListener8,
                         FListener.FListenerParam.FListener);
  end;
  result:=FListener;
end;

{ TSound3DListener }

constructor TSound3DListener.Create;
begin
  FListenerParam:=TListener3DParam.Create;
end;

destructor TSound3DListener.Destroy;
begin
  FListenerParam.Free;
  inherited;
end;

procedure TSound3DListener.UpdateParams;
begin
  FListenerParam.FListener.CommitDeferredSettings;
end;

initialization
  CoInitialize(nil);
finalization
  CoUninitialize;
end.

Membuat Aplikasi Sound 3D

Mari kita buat aplikasi sederhana untuk memanfaatkan kelas-kelas
yang sudah kita buat. Untuk itu kita membutuhan beberapa file WAV. Untuk contoh
ini saya menggunakan file WAV berisi suara nyamuk dan suara burung berkicau.

Tambahkan constructor dan destructor serta drag drop dua button
dan sebuah TTimer, namai masing-masing btnStart, btnStop dan Timer1. Ubah property
Enabled Timer1 menjadi false, serta Interval menjadi 100.
Lengkapi kodenya seperti berikut:

{=====================================
 aplikasi 3D sound
 =====================================
 (c) 2006 zamrony p juhara

 http://members.lycos.co.uk/zamronypj
=====================================}
unit ufrmMain3D2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs,udxaudio, StdCtrls, ComCtrls, ExtCtrls;

type
  TfrmMain = class(TForm)
    Timer1: TTimer;
    btnStart: TButton;
    Button2: TButton;
    procedure Timer1Timer(Sender: TObject);
    procedure btnStartClick(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    SoundCollection:TSound3DCollection;
    nyamuk,burung:TSound3DItem;
    SoundListener:TSound3DListener;
    t:single;
    function LoadSample(const filename:string):TSound3DItem;

    { Private declarations }
  public
    constructor Create(AOwner:TComponent);override;
    destructor Destroy;override;
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

constructor TfrmMain.Create(AOwner: TComponent);
var exepath:string;
begin
  inherited;
  SoundCollection:=TSound3DCollection.Create(TSound3DItem);
  SoundListener:=SoundCollection.Listener;
  exepath:=extractFilePath(Application.ExeName);
  nyamuk:=LoadSample(exePath+'samples\msquito.wav');
  burung:=LoadSample(exePath+'samples\birds.wav');
end;

destructor TfrmMain.Destroy;
begin
  SoundCollection.Free;
  inherited;
end;

function TfrmMain.LoadSample(const filename: string): TSound3DItem;
begin
  result:=SoundCollection.Add as TSound3DItem;
  result.Looped:=true;
  result.LoadFromFile(filename);
end;

procedure TfrmMain.Timer1Timer(Sender: TObject);
var apos:TSoundVector;
    angle,sin_angle,cos_angle:single;
begin
  angle:=t*pi;
  sin_angle:=5*sin(angle);
  cos_angle:=5*cos(angle);

  apos.x:=sin_angle;
  apos.y:=0;
  apos.z:=cos_angle;
  nyamuk.BufferParam.Position:=apos;

  apos.y:=0;
  apos.x:=sin_angle;
  apos.z:=cos_angle;
  nyamuk.BufferParam.Velocity:=apos;

  apos.z:=sin_angle;
  apos.y:=0;
  apos.x:=cos_angle;
  burung.BufferParam.Position:=apos;

  apos.y:=0;
  apos.z:=sin_angle;
  apos.x:=cos_angle;
  burung.BufferParam.Velocity:=apos;

  SoundListener.UpdateParams;

  t:=t+0.01;
  if t>2 then
    t:=0;
end;

procedure TfrmMain.btnStartClick(Sender: TObject);
begin
  t:=0;
  nyamuk.Play;
  burung.Play;

  timer1.Enabled:=true;
end;

procedure TfrmMain.Button2Click(Sender: TObject);
begin
  t:=0;
  timer1.Enabled:=false;
  nyamuk.Stop;
  burung.Stop;
end;

end.

Prosedur Timer1Timer digunakan untuk mengupdate lokasi
dan kecepatan nyamuk dan burung. btnStartClick dan btnStopClick
memulai playback dan menghentikan playback. Konstruktor dan destruktor kita
isi dengan kode untuk inisialisasi instance-instance yang kita perlukan yakni
instance sound 3D Collection, instance listener, obyek suara nyamuk dan obyek
suara burung. Saat membebaskan instance-instace ini, kita cukup memanggil Free
milik sound 3D collection karena obyek listener dan semua obyek suara akan otomatis
dibebaskan.

Untuk download source code aplikasi, klik di sini.

Ok sampai jumpa di artikel berikutnya. hepi koding.

Leave a Comment »

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.
Entries and comments feeds.

%d bloggers like this: