Unit ALUtils

DescriptionusesClasses, Interfaces, Objects and RecordsFunctions and ProceduresTypesConstantsVariables

Description

A set of general utilities for working with OpenAL.

This unit provides many comfortable utilities to work with OpenAL. Everything is based on my OpenAL binding in unit KambiOpenAL. Most important things are BeginAL and EndAL procedures for initializing / releasing OpenAL context. Also TALSoundFile class is useful to load files into OpenAL buffers.

This unit does not use alut* functions from KambiOpenAL, so your programs will not depend on existence of alut* functions in some library. You can define symbol USE_ALUT and recompile this unit to use alut* functions, but this is really useful only for debugging. One can say that this unit provides a superset of alut functionality.

When you're using this unit, you shouldn't use any alc* functions and alutInit/alutExit functions from KambiOpenAL. The rule is that this unit takes care about such things.

uses

Overview

Classes, Interfaces, Objects and Records

Name Description
Class EALError This is for errors reported by alGetError (using constants AL_xxx)
Class TALSoundFile  

Functions and Procedures

function EnumerationExtPresent(out pDeviceList: PChar): boolean; overload;
function EnumerationExtPresent: boolean; overload;
procedure GetOpenALDevices(DevicesList: TStringList);
function ALCDeviceToNiceStr(const ALCDevice: string): string;
procedure OpenALOptionsParse;
function OpenALOptionsHelp(PrintALCDeviceAsDefault: boolean): string;
procedure BeginAL(CheckForAlut: boolean);
function TryBeginAL(CheckForAlut: boolean): boolean;
procedure EndAL;
function GetALCString(enum: TALCenum): string;
function GetALCStringTrapped(enum: TALCenum): string;
procedure CheckAL(const situation: string);
function alGetSource1i(SourceName: TALuint; Attribute: TALenum): TALint;
function alGetSource1f(SourceName: TALuint; Attribute: TALenum): TALfloat;
function alGetSource1bool(SourceName: TALuint; Attribute: TALenum): TALboolean;
function alGetSource1ui(SourceName: TALuint; Attribute: TALenum): TALuint;
function alGetSource3f(SourceName: TALuint; Attribute: TALenum): TALVector3f;
function alGetBuffer1sizei(BufferName: TALuint; Attribute: TALenum): TALsizei;
function alGetBuffer1i(BufferName: TALuint; Attribute: TALenum): TALint;
function alGetBuffer1f(BufferName: TALuint; Attribute: TALenum): TALfloat;
function alGetListener1f(Attribute: TALenum): TALfloat;
function alGetListener3f(Attribute: TALenum): TALVector3f;
function alGetListenerOrientation: TALTwoVectors3f;
procedure alSourceVector3f(SourceName: TALuint; Param: TALenum; const Value: TALVector3f);
procedure alListenerVector3f(Param: TALenum; const Value: TALVector3f);
procedure alListenerOrientation(const Dir, Up: TALVector3f); overload;
procedure alListenerOrientation(const Orient: TALTwoVectors3f); overload;
procedure alCreateSources(n: TALsizei; sources: PALuint);
procedure alCreateBuffers(n: TALsizei; buffers: PALuint);
function alSourcePlayingOrPaused(ALSource: TALuint): boolean;

Constants

BestALCDevice = '';
BoolToAL: array[boolean] of TALint = (AL_FALSE, AL_TRUE);

Variables

ALCDevice: string = BestALCDevice;
ALActive: boolean = false;
ALActivationErrorMessage: string = '';

Description

Functions and Procedures

function EnumerationExtPresent(out pDeviceList: PChar): boolean; overload;

Checks if ALC_ENUMERATION_EXT is present. If it is, pDeviceList is initialized.

Below is some description of how it works:

This used to be trivial:

  1. check alcIsExtensionPresent(nil, 'ALC_ENUMERATION_EXT')

  2. if true, then extension is supported, and get

      pDeviceList := alcGetString(nil, ALC_DEVICE_SPECIFIER);
      Assert(pDeviceList <> nil);
    

But then buggy Apple openal implementation came. With this implementation

  1. alcIsExtensionPresent(nil, 'ALC_ENUMERATION_EXT') returns true (confirmed by looking at trunk source code: oalImp.cpp, line 75, alcExtensionsList contains ALC_ENUMERATION_EXT, so alcIsExtensionPresent always returns true for it.)

  2. But alcGetString(nil, ALC_DEVICE_SPECIFIER) returns nil, so this extension is not actually implemented... Moreover, the second thing is that alGetError reports AL_INVALID_VALUE error after this (which should be in alcError instead).

function EnumerationExtPresent: boolean; overload;
 
procedure GetOpenALDevices(DevicesList: TStringList);

This appends to DevicesList object list of available OpenAL devices.

It uses ALC_ENUMERATION_EXT extension, so ALInited must be true and ALC_ENUMERATION_EXT. If ALC_ENUMERATION_EXT extension is not supported but ALInited is True and we're compiled under Unix, then it assumes that devices supported by default [http://www.openal.org/] Linux OpenAL implementation are supported and returns them. (so in the 2 most common situations — current Creative's Windows OpenAL and current Linux OpenAL from [http://www.openal.org/] — it is able to return list of devices).

Otherwise it doesn't append anything to DevicesList.

Remember that for every OpenAL implementation, there is also an implicit OpenAL device named '' (empty string) supported.

function ALCDeviceToNiceStr(const ALCDevice: string): string;

This returns nice, user-readable description of OpenAL device named ALCDevice. Currently this returns nice string for

  • Empty string (means "Default OpenAL device")

  • Various Unix devices looking like '(( devices '(DEVICE-NAME) ))

procedure OpenALOptionsParse;

This parses parameters in Parameters and interprets and removes recognized options. Internally it uses ParseParameters with ParseOnlyKnownLongOptions = True. Recognized options :

--audio-device DEVICE-NAME

Set ALCDevice variable to given argument.

--print-audio-devices

Use ALC_ENUMERATION_EXT to print all available OpenAL audio devices to stdout (uses InfoWrite, so on Windows when program is GUI, it will make a dialog box). If this extension is not present, write something like "Enumerating audio devices not supported by your OpenAL".

Then do ProgramBreak.

More user-oriented documentation for the above options is here: [http://vrmlengine.sourceforge.net/openal_notes.php#section_options]

function OpenALOptionsHelp(PrintALCDeviceAsDefault: boolean): string;

This is help string for options parsed by OpenALOptionsParse.

Formatting is consistent with Kambi standards (see file ../base/README.kambi_command_line_params).

If PrintALCDeviceAsDefault then it will also say (near the help for option --audio-device) that "defauls device is ..." and will give here current value of ALCDevice. This is usually useful, e.g. if you don't intend to modify directly ALCDevice (only indirectly via OpenALOptionsParse) then you should give here true.

procedure BeginAL(CheckForAlut: boolean);

BeginAL creates and activates OpenAL context for the ALCDevice device.

You should deactivate and free the context by calling EndAL. You should usually use try .. finally .. end construction for this, e.g.

 BeginAL; try ... finally EndAL; end; 

Detailed description of what BeginAL does:

  1. Checks is ALInited = True, if CheckForAlut then it also checks is ALUTInited = True. If the test will fail, raises EOpenALInitError.

  2. Init's OpenAL context using appropriate alc* functions and using the device specified in ALCDevice variable.

    If this initialization will fail, ALActive will be set to False, ALActivationErrorStr will be set to error message and EOpenALError (but not EOpenALInitError) will be raised (with Message equal to ALActivationErrorStr value).

    If this initialization will succeed, ALActive will be set to True.

Exceptions raised
EOpenALError
if something will go wrong
function TryBeginAL(CheckForAlut: boolean): boolean;

This is like BeginAL but it doesn't raise EOpenALError.

So you should check ALActive (if False, error message will be in ALActivationErrorMessage) to know whether initialization was successfull.

procedure EndAL;

This deactivates and frees the context initialized by last TryBeginAL or BeginAL call. ALActive is set to False.

If not ALActive, then this does nothing.

function GetALCString(enum: TALCenum): string;

Simple wrapper — calls alcGetString with the device created by last TryBeginAL or BeginAL call (or nil if no TryBeginAL or BeginAL was called or the device was freed by EndAL). And returns normal Pascal AnsiString (so you don't have to worry anymore that alcGetString returns a pointer to some internal data in OpenAL library and you can't modify it).

function GetALCStringTrapped(enum: TALCenum): string;

Calls GetALCString and additionally checks alcGetError. If alcGetError returns error, it returns string looking like '(detailed error description)' (normal incorrect call to alcGetString would return just nil).

Actually, this also checks normal al error (alGetError instead of alcGetError). That's because on Darwin (Mac OS X) Apple's OpenAL implementation fails to return some alcGetString (e.g. ALC_DEFAULT_DEVICE_SPECIFIER, ALC_DEVICE_SPECIFIER, ALC_EXTENSIONS) and reports this by setting AL error (instead of ALC one) to "invalid value". Yes, that's Apple's bug (TODO: report ? check newer al version ?).

Just like all other functions that somehow check al[c]GetError, this function assumes that error state was "clear" before calling this function, i.e. al[c]GetError would return AL[C]_NO_ERROR.

procedure CheckAL(const situation: string);
 
Exceptions raised
EALError
if alGetError returned something <> AL_NO_ERROR
function alGetSource1i(SourceName: TALuint; Attribute: TALenum): TALint;

Simple wrappers for alGetSource*, alGetBuffer*, alGetListener* and alGetFloat/Integer/Boolean*. In many cases these should be more comfortable (because they are functions) and safer (no need to pass some pointer) than directly using related OpenAL functions.

OpenAL errors are not checked by these functions (i.e. CheckAL or alGetError is not called).

No checking does Attribute really return value of given type is done. This means that if you will request value of the wrong type for given Attribute, OpenAL may do some convertion, or may set the error state. In some cases you may even get nasty access violation errors or accidental writes over some random place in memory — this may happen if for given Attribute OpenAL likes to return an array of some values, and you will use the wrong version (e.g. using AL_GAIN with a version that returns TALVector3f, or using AL_POSITION with a version that returns single TALfloat). So always check carefully that given Attribute supports the requested output value.

function alGetSource1f(SourceName: TALuint; Attribute: TALenum): TALfloat;
 
function alGetSource1bool(SourceName: TALuint; Attribute: TALenum): TALboolean;
 
function alGetSource1ui(SourceName: TALuint; Attribute: TALenum): TALuint;
 
function alGetSource3f(SourceName: TALuint; Attribute: TALenum): TALVector3f;
 
function alGetBuffer1sizei(BufferName: TALuint; Attribute: TALenum): TALsizei;
 
function alGetBuffer1i(BufferName: TALuint; Attribute: TALenum): TALint;
 
function alGetBuffer1f(BufferName: TALuint; Attribute: TALenum): TALfloat;
 
function alGetListener1f(Attribute: TALenum): TALfloat;
 
function alGetListener3f(Attribute: TALenum): TALVector3f;
 
function alGetListenerOrientation: TALTwoVectors3f;
 
procedure alSourceVector3f(SourceName: TALuint; Param: TALenum; const Value: TALVector3f);

These functions are simple wrappers over OpenAL functions to allow you to pass TALVector* / TALTwoVectors* types.

Just like with alGet* wrappers (above in this unit), no error checking is done (no CheckAL etc.) and no checking does Param accept the given type of value is done.

procedure alListenerVector3f(Param: TALenum; const Value: TALVector3f);
 
procedure alListenerOrientation(const Dir, Up: TALVector3f); overload;
 
procedure alListenerOrientation(const Orient: TALTwoVectors3f); overload;
 
procedure alCreateSources(n: TALsizei; sources: PALuint);

Unfortunately current Creative OpenAL Windows implementation violates OpenAL specifitation : default source state (i.e. newly generated source state) is not as it is specified by OpenAL implementation : attributes MAX_DISTANCE, DIRECTION and CONE_OUTER_GAIN have different values.

So alCreateSources calls alGenSources and then makes sure that all sources have state consistent with OpenAL specification (under Windows it means that it sets MAX_DISTANCE, DIRECTION and CONE_OUTER_GAIN attributes to their proper values). alCreateBuffers does the same for alGenBuffers (which means, for now, that it simply calls alGenBuffers.)

(alCreateSources and alCreateBuffers may be extended at some time if I discover some other incompatibilities in alGenSources/Buffers, maybe in Windows and maybe in Linux implementation (and maybe in others?)).

So explaining it simply : always use alCreateSources and alCreateBuffers instead alGenSources and alGenBuffers. (warning : no additional CheckAL is called in alCreateSources and alCreateBuffers; their intent is to be a complete analogy to calling alGenSources and alGenBuffers).

procedure alCreateBuffers(n: TALsizei; buffers: PALuint);
 
function alSourcePlayingOrPaused(ALSource: TALuint): boolean;
 

Constants

BestALCDevice = '';

BestALCDevice says what OpenAL device is generally the best.

Theoretically always the default OpenAL device should be considered "the best", so BestALCDevice should be equal to ''. And that's indeed the current situation — BestALCDevice is a constant equal to ''. But, in the past there were some reasons to set this to some other specific values for some OSes... And I want to be able in the future to do such tweaking again.

BoolToAL: array[boolean] of TALint = (AL_FALSE, AL_TRUE);
 

Variables

ALCDevice: string = BestALCDevice;

This OpenAL device will be used when you will call TryBeginAL or BeginAL.

I could make it instead a parameter for TryBeginAL / BeginAL procedures, but it's more comfortable to have a global variable for this — it's useful e.g. to easily implement handling of --audio-device option in OpenALOptionsParse. TryBeginAL / BeginAL procedures assume anyway that your program will require only one OpenAL context at any time.

ALActive: boolean = false;

True means that you successfully called [Try]BeginAL and you didn't call EndAL after it. In other words, this means that you have active OpenAL context (and KambiOpenAL functions are available, i.e. ALInited is true and maybe even ALUTInited = true, if you passed CheckForAlut = true to BeginAL).

ALActivationErrorMessage: string = '';

When BeginAL fails with EOpenALError or when TryBeginAL returns with false, they put error message in this variable. When you're using BeginAL this variable is actually useless for you, as this is equal to Message property of raised exception. But this is of course useful when you're using TryBeginAL, as this is the only way to get detailed info why TryBeginAL failed.


Generated by PasDoc 0.10.0 on 2008-02-25 00:00:29