3D Sound Playback with DirectX Audio

Zamrony P. Juhara

If you ever played recent 3D games, you might hear sounds coming from many directions while you play the game. The question is, how can you do that?

This article was written, inspired by a question posted by Delphindo mailing list member about how to play sound samples on different speakers. To understand this article smoothly and to answer above question, at least you are expected to have basic knowledge about how to play sound with DirectX. You can read my article, DirectX Audio programming, as an introduction.

Ok let's get started..

3D Sound Introduction

Sound perception in real world is affected by many factors, i.e, position and velocity of sound source, environment, and position, velocity and orientation of listener.

Sound Source

Sound source is object that emits sound wave. Parameters that affect sound perception are position, velocity, orientation, environment and etc.

Listener

Main parameters of listener that affect sound perception are position, velocity and orientation of listener. Default position of listener lays at (0,0,0). There are two listener's orientations, i.e, top vector and front vector (take a look at Fig.1 below). Top vector's default value is (0,1,0) and front vector's default value is (0,0,1)

Top vector and front vector illustration

Fig.1 Top vector and front vector illustration.

Sound Cone

Sound can be categorized by orientation as follow:

  • Non-oriented sound.
  • Oriented sound.

Non-oriented sound is sound same in every directions, oriented sound can only be heard in a particular direction. Sound cone is model of intensity of oriented sound (see Fig.2). This cone is divided into two parts, i.e, inside cone and outside cone.

In inside cone area, sound is heard at loudest intensity, while at outside cone, further from the inside cone, weaker sound intensity. How much sound intensity decrease is determined by factor that defined by application.

Distance

Distance between sound source and listener affects listening perception. Closer sound source to listener, louder sound source will be heard. Minimum distance is distance where sound will no longer increase in volume while maximum distance is biggest distance where sound source no longer decrease.

The distance factor is the number of meters in a vector unit. Its default values is 1.0. For example if velocity vector is (2,2,0), then velocity is 2 meter/s to x direction, 2 meter/s to y direction and 0 meter/s to z direction.

Sound cone representation

Fig.2 Sound cone representation.

Doppler Effect

Moving sound source or listener will experience Doppler effect (review your physics class). DirectX automatically calculates this effect on moving sound source or listener base on Doppler factor which its value defined by application.

Rolloff

Rolloff is amount of attenuation that is applied to sounds, based on the listener's distance from the sound source.

DirectX 3D Sound Basic Element

3D AudioPath

Audiopath is object that controls sound sample data flow.

3D Sound Buffer

Sound buffer represents sound source. Sound buffer holds sound sample data. In DirectX, there are two sound buffer type, i.e, ordinary buffer (IDirectSoundBuffer8) and 3D buffer (IDirectSound3DBuffer8). With 3D buffer, we can set sound source paramaters such as position and velocity.

3D Listener

In DirectX, listener object is represented in IDirectSound3DListener8.

Get 3D AudioPath Instance

To play 3D sound, we must create standard audiopath for each sound we want to play. This audiopath must be created with DMUS_APATH_DYNAMIC_3D flag. For example:

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

To those of you who do not understand CreateStandardAudioPath(), please read article mentioned above for review.


Get 3D Sound Buffer from Audiopath

We need to get instance of IDirectSound3DBuffer8, because changing position and velocity of sound source involved method declared in this interface.

To do it we must use GetObjectInPath() function.

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

Parameters

dwPChannel is channel where we want to get its buffer. If we set it with DMUS_PCHANNEL_ALL, DirectX will look for it in every channels.

dwStage is type of object we want to retrieve. For sound buffer, we fill it with DMUS_PATH_BUFFER

dwBuffer is index of buffer. Because there is only one sound buffer for each audiopath, we always set dwBuffer with 0.

guidObject is GUID of object. To get sound buffer we don't need GUID, so we can set it with GUID_NULL.

dwIndex is index of object. Because there is only one sound buffer for each audiopath, dwIndex is ignored and must be set to 0.

iidInterface is interface identifier of object we need. For 3D sound buffer version 8, we set with IID_IDirectSound3DBuffer8 (or simply IDirectSoundBuffer8 because Delphi is able to get the identifier from interface).

ppObject, variable that will hold address of instance of object, in this case, pointer to IDirectSound3DBuffer8 instance.

For example:

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

Get 3D Listener from Audiopath

Listener can be retrieved from primary buffer, using GetObjectInPath() of audiopath. It is similar to example above, however dwPChannel must be set to 0, becuase primary buffer is at channel 0. dwStage is set to DMUS_PATH_PRIMARY_BUFFER because listener instance must be retrieved from primary buffer. iidInterface is set to IID_IDirectSound3DListener8.

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

Immediate Setting and Deferred Setting

Everytime any 3D sound buffer or listener parameters are changed, it will cause recalculation and remixing to occur. This needs CPU work (or DSP at sound card). To minimize performance degradation because of parameter modification that is too often, all methods of IDirectSound3DBuffer8 and IDirectSound3DListener8 that change 3D sound parameter have dwApply parameter that can be filled with DS3D_DEFERRED or DS3D_IMMEDIATE. Parameter modification marked as deferred setting, will not cause remixing to occur. Remixing is occured if we call IDirectSound3DListener8 method CommitDeferredSettings().

function CommitDeferredSetings:HResult;stdcall;

Parameter with DS3D_IMMEDIATE flag causes DirectX to do remixing.

What can we do with 3D sound buffer?

Set and Get Position of Sound Sample.

IDirectSound3DBuffer8 has SetPosition() and GetPosition() method useful for setting and retrieving sound sample position.

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

x, y, z are coordinate of sound source, dwApply determines type of setting modification, i.e, DS3D_IMMEDIATE or DS3D_DEFFERED.

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

value will be filled with position vector.

Set and Get Velocity of Sound Sample.

IDirectSound3DBuffer8 has SetVelocity() and GetVelocity() method useful for setting and retrieving velocity of sound source.

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

Their parameters are similar to previous SetPosition/GetPosition method.

Set 3D Sound Processing Mode.

3D sound processing consist of three modes, i.e, normal, head relative and disabled.

In normal mode, position and orientation of sound source are absolute to world space. This mode is suitable for non-moving sound source and it is default processing mode.

In head relative mode, 3D sound parameter are relative to position, velocity and orientation of listener. If listener move, then all sound source parameters will be recalculated. This mode is suitable for moving sound source, for example sound of mosquito flies around listener's head.

In disabled mode, 3D processing is disabled, thus sound source feels like coming from center of listener's head.

3D sound processing is set with SetMode() and GetMode() method.

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

dwMode parameter is sound proceesing mode, can be set to DS3DMODE_NORMAL, DS3DMODE_HEADRELATIVE or DS3DMODE_DISABLE. dwApply can be set with dengan DS3D_DEFERRED or DS3D_IMMEDIATE.

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

dwMode will be filled with current processing mode.

Set Minimum and Maximum Distance.

To set minimum and maximum distance, can be done with SetMinDistance() and SetMaxDistance().

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

To get minimum and maximum distance, we use GetMinDistance() dan GetMaxDistance().

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

Set Sound Cone.

Shape and orientation of sound cone is set through SetConeAngles(), SetConeOrientation() and SetConeOutsideVolume() functions.

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;

What can we do with 3D listener?

Set and Get Listener Position.

To set position of listener, IDirectSound3DListener8 has SetPosition() method.

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

pPosition will be filled with coordinates of position of listener.

Set and Get Listener Orientation.

To set orientation, we use SetOrientation().

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

xFront, yFront and zFront are orientation vector of listener. xTop, yTop and zTop are up vector head of listener.

To get listener orientation, we use GetOrientation().

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

Set Distance Factor.

Distance factor is set with SetDistanceFactor().

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

To retrieve distance factor, we use GetDistanceFactor().

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

Set Doppler factor.

Doppler factor is set through SetDopplerFactor() method.

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

To get doppler factor, we use GetDopplerFactor()

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

Set Rolloff Factor.

Rolloff factor is set through SetRolloffFactor().

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

To retrieve rolloff factor, we use GetRolloffFactor().

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

Ok, let us now create wrapper classes for 3D sound.

Class Design

All 3D playback functionalities will be encapsulated in some classes as follows:

TSoundCollection

TSoundCollection, derived from TCollection, stores items of TSoundItem instance. This class is responsible for initialization and finalization of performance and loader. It's also responsible to manage performance and loader memory allocation as well as memory of TSoundItem instance.

TSoundItem

TSoundItem is representation of sound object. It encapsulates basic steps for playing sound with DirectX. TSoundItem, derived from TCollectionItem, has basic methods for loading sound sample data from file and from stream, strating sound playback, stopping sound playback, get playback status information and also playing sound repeatedly (automatic loop) when sound has finished played.

Because main purpose of this class is for game programming, thus the most important feature of TSoundItem is capability to mix sound sample suara with sound sample currently played.

TSound3DCollection

This class is derived from TSoundCollection, to manage 3D sound objects. This class has one property, i.e, Listener of type TSound3DListener. Thisc lass ris responsible for managing listener object memory. Application accesses listener must not free its memory.

TSound3DListener

This class represents listener. For each application, ther is only one listener. To get instance of this class, application do not create it directly, but use Listener property of TSound3DCollection. This class is provided with functionalities to change listener's parameter. This functionalities is delegated to instance of TListener3DParam which is handled internally by TSound3DListener.

Aside from parameter modification, other feature is to commit deferred setting, thus make 3D sound calculation to occur, i.e, with UpdateParams() method.

TListener3DParam

This class is responsible to manage listener parameter modification. This class is handled internally by TSound3DListener. Applcaition do not create it directly.

TSound3DItem

TSound3DItem is derived from TSoundItem with added feature, i.e, capability to play 3D sound. TSound3DItem is provided with useful methods for changing 3D sound source parameter. This class handles 3D sound buffer memory management. To be able to change buffer parameter, TSound3DItem uses TBuffer3DParam class, where instance of this class is handled internally by TSound3DItem.

All modifications done on 3D buffer or listener is not automatically change actual parameter of 3D sound buffer 3D or listener (deferred).

TBuffer3DParam

This class encapsulates modification 3D sound buffer parameter includes setting sound source parameter, velocity of sound source, etc.


Class Implementation

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

http://juhara.com
=====================================}
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.

Creating 3D Sound Application

Let us create simple application to use classes that we have developed. We need some WAV files in order to do that. For this sample, I use WAV file of mosquito sound and birds sound.

Add constructor and destructor then drag drop two buttons and a TTimer, name each of it as btnStart, btnStop and Timer1. Change Enabled property of Timer1 to false, and Interval to 100. Flesh out the code to make it looks like following snippet:

{=====================================
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+'samplesmsquito.wav');
burung:=LoadSample(exePath+'samplesbirds.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.

Timer1Timer method is used to update location and velocity of mosquito and birds. btnStartClick and btnStopClick start and stop playback. Constructor and destructor is filled with code to initialize required instances, i.e, instance of 3D sound collection, listener, mosquito and birds sound object. When freeing these instances, we need tro call Free of 3D sound collection only, because listener and other objects will automatically be freed.

To download application source code, click here.