#include <AL/al.h>
#include <AL/alc.h>
#include <AL/alu.h>
#include <AL/alure.h>
#include <AL/efx.h>
#include <al/EFX-Util.h>
#include <al/efx-creative.h>
//#include <al/xram.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

// TODO EFX/EAX troubleshooting, stream, capture, network transceive, examine timing (add firing time, etc)

#include	<sndfile.h>

#define RANGE(a, b, c)            ((b) < (a) ? (a) : ((b) > (c) ? (c) : (b)))

// EFX help (where we got the .h): Dmytry Lavrov (Public Domain code samples)

//EAX
//const GUID DSPROPSETID_EAX20_ListenerProperties = { 0x306a6a8, 0xb224, 0x11d2, { 0x99, 0xe5, 0x0, 0x0, 0xe8, 0xd8, 0xc7, 0x22 } };
//const GUID DSPROPSETID_EAX20_BufferProperties   = { 0x306a6a7, 0xb224, 0x11d2, { 0x99, 0xe5, 0x0, 0x0, 0xe8, 0xd8, 0xc7, 0x22 } };

//#ifndef(AL_FORMAT_VORBIS_EXT)
//#define AL_FORMAT_VORBIS_EXT 0x10003
//#endif

#ifndef ALC_EXT_EFX
#define AL_FILTER_TYPE                                     0x8001
#define AL_EFFECT_TYPE                                     0x8001
#define AL_FILTER_NULL                                     0x0000
#define AL_FILTER_LOWPASS                                  0x0001
#define AL_FILTER_HIGHPASS                                 0x0002
#define AL_FILTER_BANDPASS                                 0x0003
#define AL_EFFECT_NULL                                     0x0000
#define AL_EFFECT_EAXREVERB                                0x8000
#define AL_EFFECT_REVERB                                   0x0001
#define AL_EFFECT_CHORUS                                   0x0002
#define AL_EFFECT_DISTORTION                               0x0003
#define AL_EFFECT_ECHO                                     0x0004
#define AL_EFFECT_FLANGER                                  0x0005
#define AL_EFFECT_FREQUENCY_SHIFTER                        0x0006
#define AL_EFFECT_VOCAL_MORPHER                            0x0007
#define AL_EFFECT_PITCH_SHIFTER                            0x0008
#define AL_EFFECT_RING_MODULATOR                           0x0009
#define AL_EFFECT_AUTOWAH                                  0x000A
#define AL_EFFECT_COMPRESSOR                               0x000B
#define AL_EFFECT_EQUALIZER                                0x000C
#define ALC_EFX_MAJOR_VERSION                              0x20001
#define ALC_EFX_MINOR_VERSION                              0x20002
#define ALC_MAX_AUXILIARY_SENDS                            0x20003
#endif

#ifndef ALC_MONO_SOURCES
#define ALC_MONO_SOURCES                         0x1010
#define ALC_STEREO_SOURCES                       0x1011
#endif

#ifndef AL_EFFECT_EAXREVERB
#define AL_EFFECT_EAXREVERB                                0x8000
#endif

#ifndef ALC_CAPTURE_DEVICE_SPECIFIER
/**
 * The Specifier string for default device
 */
#define ALC_DEFAULT_DEVICE_SPECIFIER             0x1004
#define ALC_DEVICE_SPECIFIER                     0x1005
#define ALC_EXTENSIONS                           0x1006

#define ALC_MAJOR_VERSION                        0x1000
#define ALC_MINOR_VERSION                        0x1001

#define ALC_ATTRIBUTES_SIZE                      0x1002
#define ALC_ALL_ATTRIBUTES                       0x1003
/**
 * Capture extension
 */
#define ALC_CAPTURE_DEVICE_SPECIFIER             0x310
#define ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER     0x311
#define ALC_CAPTURE_SAMPLES                      0x312
#endif

import "ecere"

// Be very careful with these two parameters
// It is very dependant on the audio hardware your
// user is using. It you get too large, it may work
// on one persons system but not on another.
// TODO Write a hardware test !
#define MAX_AUDIO_BUFFERS   64
#define MAX_AUDIO_SOURCES   16

enum ALState { empty=0, stopped=1, paused=2, playing=3 };
enum ALSoundFormat { mono8=AL_FORMAT_MONO8, mono16=AL_FORMAT_MONO16, stereo8=AL_FORMAT_STEREO8, stereo16=AL_FORMAT_STEREO16 };

/*
 * Compare strings, case insensitive.
 */
#define LOWER(c) ((c) >= 'A' && (c) <= 'Z' ? (c)+'a'-'A' : (c))
bool contains( const char *astr, const char *bstr )
{
 if ( astr == null ) { printf( "Str_cmp: null astr.\n" ); return false; }
 if ( bstr == null ) { printf( "Str_cmp: null bstr.\n" ); return false; }
 for ( ; *astr || *bstr; astr++, bstr++ ) if ( LOWER(*astr) != LOWER(*bstr) ) return false;
 return true;
}

// Used during Init(), maybe it shouldn't die() here...
static void checkForErrors(void)
{
 ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
 ALCenum ALCerror = alcGetError(device);
 ALenum ALerror;
 if(ALCerror != ALC_NO_ERROR) printf( "ALC %s\n",  alcGetString(device, ALCerror));
 ALerror = alGetError();
 if(ALerror != AL_NO_ERROR) printf("AL %s\n", alGetString(ALerror));
}

static const int indentation = 4;
static const int maxmimumWidth = 79;
static void printChar(int c, int *width) { putchar(c); *width = ((c == '\n') ? 0 : ((*width) + 1)); }
static void indent(int *width) { int i; for(i = 0; i < indentation; i++) printChar(' ', width); }

static void printList(const char *header, char separator, const char *list)
{
    int width = 0, start = 0, end = 0;
    printf("%s:\n", header);
    if(list == NULL || list[0] == '\0') return;
    indent(&width);
    while(1)    {
        if(list[end] == separator || list[end] == '\0')        {
            if(width + end - start + 2 > maxmimumWidth)            {
                printChar('\n', &width);
                indent(&width);
            }
            while(start < end)            {
                printChar(list[start], &width);
                start++;
            }
            if(list[end] == '\0')                break;
            start++;
            end++;
            if(list[end] == '\0')                break;
            printChar(',', &width);
            printChar(' ', &width);
        }
        end++;
    }
    printChar('\n', &width);
}

struct SoundVector
{
 float x,y,z,a,b,c;
 //SoundVector() { y=1.0f; }
 float *asFloat3( ) { return &x; }
 float *asFloat6( ) { return &x; }
};

//int unusedBuffers, unusedSources;

void GetALCErrorString(ALenum err){
 switch(err) {
  case ALC_NO_ERROR: printf("AL_NO_ERROR"); break;
  case ALC_INVALID_DEVICE: printf("ALC_INVALID_DEVICE"); break;
  case ALC_INVALID_CONTEXT: printf("ALC_INVALID_CONTEXT"); break;
  case ALC_INVALID_ENUM: printf("ALC_INVALID_ENUM"); break;
  case ALC_INVALID_VALUE: printf("ALC_INVALID_VALUE"); break;
  case ALC_OUT_OF_MEMORY: printf("ALC_OUT_OF_MEMORY"); break;
 }
}

bool ALError( char *tb ) {
 ALenum errCode;
 if ( ( errCode = alGetError() ) != AL_NO_ERROR )  {
  printf( "Audio %s Error #%d %s\n", tb, errCode, (char *) alGetString( errCode ) );
  return true;
 }
 return false;
}

class ALBuffer : ListItem {
 ALuint id;
 inline bool valid() { if ( alIsBuffer( id ) ) return true; }
 float duration() {
  ALint bits, channels, freq, size;
  alGetBufferi(id, AL_BITS, &bits);
  alGetBufferi(id, AL_CHANNELS, &channels);
  alGetBufferi(id, AL_FREQUENCY, &freq);
  alGetBufferi(id, AL_SIZE, &size);
  if(ALError("ALBuffer:duration()")) { return 0.0f; }
  return (float) ((size / channels * 8 / bits ) / freq);
 }
 bool Close() {
  if ( valid() ) alDeleteBuffers(1, &(id)); if ( ALError( "ALBuffer:Close:alDeleteBuffers") ) return false; //unusedBuffers++;
 }
 ~ALBuffer() { Close(); }
}

class ALBuffers : LinkList<ALBuffer> {
 void ClearInvalid() {
  for ( b:this ) { if ( !b.valid() ) this.Delete(b); }
 }
 ALBuffer New() {
  ALBuffer b { };
  alGetError();
  //if ( unusedBuffers <= 0 ) { printf( "No unused buffers available when requesting New() buffer\n" ); delete b; return null; }
  b= ALBuffer { };
  alGenBuffers(1,&(b.id)); if(ALError("ALBuffer:alGenBuffers")) { delete b; return null; } //unusedBuffers--;
  //printf( "#%d buffer created with ID of %d\n", unusedBuffers, b.id );
  this.Add(b);
  return b;
 }
};

class ALSource : ListItem {
 ALuint id;
 SoundVector position, velocity;
 float pitch,gain;
 float rolloff;
 ALboolean loop,relative;
 ALState state;
 ALSource() { position = SoundVector { }; velocity = SoundVector { }; pitch=1.0f; gain=1.0f; loop=AL_FALSE; relative=AL_FALSE; }
 inline bool valid() { if ( alIsSource( id ) ) return true; }
 void bindBuffer( ALBuffer buffer ) {
  alGetError();
  alSourcei (id, AL_BUFFER,   buffer.id );
  alSourcef (id, AL_PITCH,    pitch  );
  alSourcef (id, AL_GAIN,     gain   );
  alSourcefv(id, AL_POSITION, position.asFloat3() );
  alSourcefv(id, AL_VELOCITY, velocity.asFloat3() );
  alSourcef (id, AL_ROLLOFF_FACTOR, rolloff );
  alSourcei (id, AL_LOOPING,  loop   );
  alSourcei (id, AL_SOURCE_RELATIVE, relative );
  ALError("ALSource:bindBuffer() had an error");
  state=stopped;
 }
 void bindSound( ALSound sound ) {
  alGetError();
  alSourcei (id, AL_BUFFER,   sound.waveform.buffer.id );
  alSourcef (id, AL_PITCH,    sound.pitch  );
  alSourcef (id, AL_GAIN,     sound.gain   );
  alSourcefv(id, AL_POSITION, sound.position.asFloat3() );
  alSourcefv(id, AL_VELOCITY, sound.velocity.asFloat3() );
  alSourcef (id, AL_ROLLOFF_FACTOR, rolloff );
  alSourcei (id, AL_LOOPING,  sound.loop   );
  alSourcei (id, AL_SOURCE_RELATIVE, sound.relative );
  ALError("ALSource:bindSound() had an error");
  state=stopped;
 }
 void bindStream( ALUREStream stream ) {
  alGetError();
  alSourcef (id, AL_PITCH,    pitch  );
  alSourcef (id, AL_GAIN,     gain   );
  alSourcefv(id, AL_POSITION, position.asFloat3() );
  alSourcefv(id, AL_VELOCITY, velocity.asFloat3() );
  alSourcef (id, AL_ROLLOFF_FACTOR, rolloff );
  alSourcei (id, AL_LOOPING,  AL_FALSE   ); // handled elsewhere
  alSourcei (id, AL_SOURCE_RELATIVE, relative );
  stream.source=this;
  ALError("ALSource:bindStream() had an error");
  state=stopped;
 }
 void Detach() { if ( state == empty ) { printf( "ALSource:Detach:Empty source attempted. (Cannot dump a buffer from an empty source)\n" ); return; }
            else if ( state != stopped ) { Stop(); }
                 if ( valid() ) { alSourcei(id,AL_BUFFER,0); state=empty; alGetError(); }
               }
 inline void Stop()      { if ( valid() && state == playing ) { alGetError(); alSourceStop(id);  alGetError(); state=stopped; } }
 inline void Pause()     { if ( valid() && state == playing ) { alGetError(); alSourcePause(id); alGetError(); state=paused;  } }
 inline void Play()      { if ( valid() && state != playing ) { ALError("ALSound:Pre-Play()"); alSourcePlay(id); ALError("ALSound:Play() had an error."); alGetError(); state=playing; } }
 inline void Resume() { Play(); }
//        void PauseAll()  { if ( valid() && state == playing ) Pause();  if ( next ) next.PauseAll();  }
//        void ResumeAll() { if ( valid() && state == paused  ) Resume(); if ( next ) next.ResumeAll(); }

 bool Close( ) { 
  if ( valid() ) { 
   if ( state != empty ) this.Detach();
   alDeleteSources(1, &(id)); if ( ALError( "ALSource:Close:alDeleteSources") ) return false; 
   //unusedSources++; 
   return true;
  }
  return false;
 }

// void rehead( ALSource a ) { ALSource b=this; for ( ; b; b=b.next ) b.head=a; }
 ~ALSource() { Close(); }
}

class ALSources : LinkList<ALSource>
{
 // List functions for managing a multi-source list.
 ALSource AddNew()
 {
  ALSource s = null;
 // if( unusedSources > 0 ) 
  {
   uint id;
   alGenSources(1,&id);
   if(!ALError("ALSource:WAV:alGenSources")) {
    //unusedSources--;
    Insert(null, s = { id = id });
   }
  }
  return s;
 }
}


/*
ALURE stream
*/
class ALUREStream {
 public union { LinkElement<thisclass> link; struct { thisclass prev, next; }; }; 
 ALSoundManager manager;
 alureStream *stream;
 char *name;
 int bufferSize;
 int usesBuffers;
 uint samples;
 float duration;
 float refreshRate;
// ALuint *buf;
 ALSource source;
 bool created;
 bool loop;
 bool badError;
 SoundVector position, velocity;
 float pitch,gain;
 float threadDelay;
 bool terminated;

 bufferSize=128*1024;
 usesBuffers=3;//3;
 threadDelay=0.01f;

 ~ALUREStream()
 {
  alureDestroyStream(stream, 0, null);// usesBuffers, buf);
  delete source;
  //unusedBuffers+=usesBuffers;
  delete name;
 }

 inline bool Playing() { return ( source && source.state == playing ); }

// uint Main() { Sleep(threadDelay); DelayExpired(); return 0; }

 void Stop() { if(created) { alureStopSource(source.id,AL_FALSE); } } //terminated = true; ((GuiApplication)__thisModule).Unlock(); Wait(); ((GuiApplication)__thisModule).Lock(); } }

 bool LoadIntoSource(char *fn, ALSource s, bool CalcSize ) {
  name=CopyString(fn);
  if ( !source ) { printf("ALUREStream:Create complains the required source was not provided.\n" ); return false; }
  //if ( usesBuffers > unusedBuffers ) { printf("ALUREStream:Create Not enough buffers available. Requested: %d Available: %d\n", usesBuffers, unusedBuffers ); return false; }
  alGetError();
  source=s;
//  if ( buf ) { printf( "ALUREStream:Create warns, the buffers were already created, was .Create() mistakenly called twice?\n" ); delete buf; }
  //buf=new ALuint[usesBuffers];
  //unusedBuffers-=usesBuffers;
  stream=alureCreateStreamFromFile((ALchar *) fn,bufferSize,0/*usesBuffers*/,null);//buf);
  if ( !stream ) { printf( "ALUREStream:Create could not load the file: %s\n", fn, alureGetErrorString()); return false; }
  if ( CalcSize ) CalculateSize(fn);
  return (created=true);
 }

 virtual void OnComplete() { }

 bool Play() {
  if ( !this ) { printf( "ALUREStream:Play:Called on a null object.\n" ); return false; }
  if ( !source ) { printf( "ALUREStream:Play:No source defined for the stream '%s'.  Did it load properly?\n", name ); return false; }
  if ( source.state == stopped ) {
   printf ( "Play() was called on a stream.\n" );
   alGetError();
//   alSourceQueueBuffers(source.id,usesBuffers,buf);
//   if ( ALError("ALUREStream:Play:alSourceQueueBuffers") ) { printf( "ALUREStream:Play failed to queue buffers.\n" ); return false; }
//   alSourcePlay(source.id);
   alurePlaySourceStream(source.id,stream,usesBuffers,0/*# of loops*/,null,null); // add eos_callback tie-in here
   source.state=playing;
 //  if ( ALError("ALUREStream:Play:alSourcePlay") ) { printf( "ALUREStream:Play failed to start source.\n" ); return false; }
 //  this.timer.delay = (1.0f/refreshRate);
 //  this.timer.Start();
//   Create();
  } else if ( source.state == paused || source.state==stopped ) { source.Resume(); return true; } 
    else if ( source.state == empty   ) { printf( "ALUREStream:Play called on an empty source (no buffer selected).\n" ); return false; }
    else if ( source.state == playing ) { printf( "ALUREStream:Play() called on a playing stream.\n" ); return true; }
  return true;
 }

 virtual void BetweenFrames() { }
 virtual void OnError() { }

 // Slightly inefficient, but precise.
 int CalculateSize( char * fn ) {
  ALuint total = 0;
  ALuint b;
  ALint freq = 0;
  alureStream *s;

  s = alureCreateStreamFromFile( (ALchar *) fn, 19200, 1, &b);
  if(s) {
   do {
    ALint size, bits, channels;
    alGetBufferi(b, AL_SIZE, &size);
    alGetBufferi(b, AL_BITS, &bits);
    alGetBufferi(b, AL_CHANNELS, &channels);
    if(!freq) alGetBufferi(b, AL_FREQUENCY, &freq);
    total += bits / channels * 8 / bits;
   } while(alureBufferDataFromStream(s, 1, &b) > 0);
   alureDestroyStream(s,1,&b);
  }
  duration=(float)total/(float)freq;
  samples=(uint) total;
 }
}

class ALUREStreams : LinkList<ALUREStream, link=ALUREStream::link> {};

class ALCaptureStream {
 ALSoundFormat format;
 int sampleFactor;
public:
 property ALSoundFormat format { get { return format; } 
                                 set { format=value; 
                                     switch ( (ALSoundFormat) value ) { 
                                      case mono8: sampleFactor=1; break;  
                                      case mono16: case stereo8: sampleFactor=2; break;
                                      case stereo16: sampleFactor=4; break;
                                     }
                                   }
                               }
 uint frequency;
 uint bufferSize;
 int samples;
 ALuint available;
 ALCdevice *captureDevice;
 char *deviceName;
 bool capturing;
 byte *target;
 int ofs;

 ALCaptureStream() { format=stereo16; sampleFactor=4; }

 void Start() { if ( captureDevice ) { alcCaptureStart(captureDevice); capturing=true; } }

 int *Acquire() {
  if ( capturing ) {
   int *buffer;
   alcGetIntegerv(captureDevice, ALC_CAPTURE_SAMPLES, 1, &available);
   buffer = new byte[available*sampleFactor];
   memset(buffer,0,available);
   alcCaptureSamples(captureDevice,buffer,(ALuint) available);
   return buffer;
  } 
  return null;
 }

 void AcquireTo( byte **buf ) {
  if ( capturing ) {
   if ( *buf ) delete *buf;
   alcGetIntegerv(captureDevice, ALC_CAPTURE_SAMPLES, 1, &available);
   *buf = new byte[available*sampleFactor];
   target = *buf;
   alcCaptureSamples(captureDevice,*buf,(ALuint) available);
  } else {
   delete *buf; *buf=null;
  }
 }

 void AcquireAppend( byte *pre ) { 
  if ( capturing ) {
   int *post=&(pre[ofs]);
   alcGetIntegerv(captureDevice, ALC_CAPTURE_SAMPLES, 1, &available);
   ofs+=available;
   alcCaptureSamples(captureDevice,post,(ALuint) available);
  }
 }

 void Stop() {
  if ( captureDevice ) { alcCaptureStop(captureDevice); capturing=false; OnCaptureComplete(); }
 }

 virtual void OnCaptureComplete() { }

 bool Init( char *name ) {
  deviceName=CopyString(name);
  captureDevice = alcCaptureOpenDevice( (ALCchar *) name, frequency, format, (ALCsizei) bufferSize );
  if ( !captureDevice ) {
   printf( "ALCaptureStream:Init:alcCaptureOpenDevice -> unable to open a device\n" );
   return false;
  }
  return true;
 }

 ~ALCaptureStream() {
 }
}

class ALSound {
 public union { LinkElement<thisclass> link; struct { thisclass prev, next; }; }; 
 ALWaveForm waveform;
 char *name;
 uint id;
 float duration;
 bool deleteMe;
 ALenum format;
 ALvoid *data;
 ALsizei size,freq;
 ALboolean loop, relative;
 ALSource source; // sources are points of emitting sound.
 SoundVector position, velocity, direction;

 float pitch,gain,min_gain,max_gain,distance,rolloff;
 bool boundToSource;
 bool lastFramePlayState;
 bool Restart;

 bool Release() { alSourceStop( id ); }

 int NextAvailableID( int max ) {
  ALSound s;
  int i;
  bool *notavailable= new bool[max];
  for ( s=this; s; s=s.next ) notavailable[s.id-1]=true;
  for ( i=0; i<max; i++ ) if ( notavailable[i] == false ) { delete notavailable; return i+1; }
  delete notavailable;
  return -1;
 }

 bool Play( )
 {
  int sourceAudioState = 0;
  WhenPlayed();
  if ( !source ) { printf( "Audio %s had no source.\n", waveform.filename ); return false; }
  alGetError();
  alGetSourcei( source.id, AL_SOURCE_STATE, &sourceAudioState );
  if ( sourceAudioState == AL_PLAYING ) { 
   if ( Restart ) Stop( id ); else return false;
  }
  alSourcePlay( source.id );
  if ( ALError( "ALSound:Play:alSourcePlay") ) return false;
  lastFramePlayState=true;
  source.state=playing;
  alGetError();
  return true;
 }

 bool Pause( ) {
  WhenPaused();
  alGetError();
  alSourcePause( source.id );
  if ( ALError( "ALSound:Pause:alSourcePause") ) return false;
  lastFramePlayState=false;
  source.state=paused;
  alGetError();
  return true;
 }

 bool Playing() {
  int sourceAudioState = 0;
  if ( !source ) return false;
  alGetSourcei( source.id, AL_SOURCE_STATE, &sourceAudioState );
  if ( sourceAudioState == AL_PLAYING ) source.state=playing;
  if ( sourceAudioState == AL_PAUSED ) source.state=paused;
  return ( source.state == playing );
 }

 // If the sound was paused the sound will resume, else it will play from the beginning.
 bool Resume( )
 {
  int sourceAudioState = 0;
  WhenResumed();
  alGetError();
  alGetSourcei( source.id, AL_SOURCE_STATE, &sourceAudioState );
  if ( sourceAudioState == AL_PLAYING ) return true;
  if ( sourceAudioState == AL_PAUSED ) { alSourcePlay( source.id ); if ( ALError( "Resume:alSourcePlay") ) return false; }
  if ( ALError( "ALSound:Resume:alSourcePlay") ) return false;
  lastFramePlayState=true;
  source.state=playing;
  alGetError();
  return true;
 }

 // Make sure the audio source ident is valid and usable
 bool Stop( )
 {
  alGetError();
  alSourceStop( source.id );
  if ( ALError( "ALSound:Stop:alSourceStop") ) return false;
  lastFramePlayState=false;
  source.state=stopped;
  alGetError();
  return true;
 }

 bool position( )
 {
  alGetError();
  alSourcefv(source.id, AL_POSITION, position.asFloat3() ); if ( ALError( "ALSound:position:alSourcefv:AL_POSITION" ) ) return false;
  return true;
 }

 bool SetPVD( )
 {
  if ( !Playing() ) return true;
  alGetError();
  alSourcefv(source.id, AL_POSITION,  position.asFloat3()  ); if ( ALError( "ALSound:SetPVD:alSourcefv:AL_POSITION" ) ) return false;
  alSourcefv(source.id, AL_VELOCITY,  velocity.asFloat3()  ); if ( ALError( "ALSound:SetPVD:alSourcefv:AL_VELOCITY" ) ) return false;
  alSourcefv(source.id, AL_DIRECTION, direction.asFloat3() ); if ( ALError( "ALSound:SetPVD:alSourcefv:AL_DIRECTION" ) ) return false;
  alGetError();
  return true;
 }

 bool SetSound( float maxDistance, float minGain, float maxGain, float rollOff )
 { 
  alGetError();
  alSourcef(source.id, AL_MAX_DISTANCE,   distance=maxDistance ); if ( ALError( "ALSound:SetSound:alSourcef:AL_MAX_DISTANCE"   ) ) return false;
  alSourcef(source.id, AL_MIN_GAIN,       min_gain=minGain     ); if ( ALError( "ALSound:SetSound:alSourcef:AL_MIN_GAIN"       ) ) return false;
  alSourcef(source.id, AL_MAX_GAIN,       max_gain=maxGain     ); if ( ALError( "ALSound:SetSound:alSourcef:AL_MAX_GAIN"       ) ) return false;
  alSourcef(source.id, AL_ROLLOFF_FACTOR, rolloff =rollOff     ); if ( ALError( "ALSound:SetSound:alSourcef:AL_ROLLOFF_FACTOR" ) ) return false;
  return true;
 }

 ALSource UpdateSource()
 {
  alGetError();
//  alSourcei (source.id, AL_BUFFER,   buffer.id );
  alSourcef (source.id, AL_PITCH,    pitch  );
  alSourcef (source.id, AL_GAIN,     gain   );
  alSourcef (source.id, AL_MAX_DISTANCE,   distance );        if ( ALError( "ALSound:AddSource:AL_MAX_DISTANCE"   ) ) { delete source; return null; }
  alSourcef (source.id, AL_MIN_GAIN,       min_gain );        if ( ALError( "ALSound:AddSource:AL_MIN_GAIN"       ) ) { delete source; return null; }
  alSourcef (source.id, AL_MAX_GAIN,       max_gain );        if ( ALError( "ALSound:AddSource:AL_MAX_GAIN"       ) ) { delete source; return null; }
  alSourcef (source.id, AL_ROLLOFF_FACTOR, rolloff  );        if ( ALError( "ALSound:AddSource:AL_ROLLOFF_FACTOR" ) ) { delete source; return null; }
  alSourcefv(source.id, AL_POSITION,  position.asFloat3()  ); if ( ALError( "ALSound:AddSource:AL_POSITION"       ) ) { delete source; return null; }
  alSourcefv(source.id, AL_VELOCITY,  velocity.asFloat3()  ); if ( ALError( "ALSound:AddSource:AL_VELOCITY"       ) ) { delete source; return null; }
  alSourcefv(source.id, AL_DIRECTION, direction.asFloat3() ); if ( ALError( "ALSound:AddSource:AL_DIRECTION"      ) ) { delete source; return null; }
  alSourcei (source.id, AL_LOOPING,  (ALboolean) loop );
  alSourcePlay(source.id);
  return source;
 }

 void AssignSource( ALSource s ) {
  alGetError();
  if ( source ) { printf( "ALSound:AssignSource:Error! Attempted to assign a source when there already was one.\n" ); return; }
  source=s;
  alSourcei (source.id, AL_BUFFER,   waveform.buffer.id );
  UpdateSource();
 }

 ALSource AddSource()
 {
  if ( !source ) {
   ALSource s { };
   alGetError();
   alGenSources(1, &(s.id));
   if (alGetError() != AL_NO_ERROR) { printf("ALSound:AddSource(): Error generating audio source."); return; }
   alSourcei (s.id, AL_BUFFER,         waveform.buffer.id );
   alSourcef (s.id, AL_PITCH,          pitch  );
   alSourcef (s.id, AL_GAIN,           gain   );
   alSourcef (s.id, AL_MAX_DISTANCE,   distance );        if ( ALError( "ALSound:AddSource:AL_MAX_DISTANCE"   ) ) { delete source; return null; }
   alSourcef (s.id, AL_MIN_GAIN,       min_gain );        if ( ALError( "ALSound:AddSource:AL_MIN_GAIN"       ) ) { delete source; return null; }
   alSourcef (s.id, AL_MAX_GAIN,       max_gain );        if ( ALError( "ALSound:AddSource:AL_MAX_GAIN"       ) ) { delete source; return null; }
   alSourcef (s.id, AL_ROLLOFF_FACTOR, rolloff  );        if ( ALError( "ALSound:AddSource:AL_ROLLOFF_FACTOR" ) ) { delete source; return null; }
   alSourcefv(s.id, AL_POSITION,  position.asFloat3()  ); if ( ALError( "ALSound:AddSource:AL_POSITION"       ) ) { delete source; return null; }
   alSourcefv(s.id, AL_VELOCITY,  velocity.asFloat3()  ); if ( ALError( "ALSound:AddSource:AL_VELOCITY"       ) ) { delete source; return null; }
   alSourcefv(s.id, AL_DIRECTION, direction.asFloat3() ); if ( ALError( "ALSound:AddSource:AL_DIRECTION"      ) ) { delete source; return null; }
   alSourcei (s.id, AL_LOOPING,  (ALboolean) loop );
   alSourcePlay(s.id); 
   this.source=s;
   return s;
  } 
  printf( "ALSound:AddSource() - source Already Created; File: %s\n", waveform.filename );
  return null;
 }

 void PrintInfo( ) {
 }

 bool *ThrowASwitch;

 virtual void WhenPlayed   ( ){ }
 virtual void WhenPaused   ( ){ } 
 virtual void WhenResumed  ( ){ }
 virtual void WhenComplete ( ){ if ( ThrowASwitch ) *ThrowASwitch=true; } 
 virtual void BetweenFrames( ){ }

 void _BetweenFrames() { 
  int state;
  alGetSourcei(source.id, AL_SOURCE_STATE, &state);
  if ( AL_PLAYING == state ) BetweenFrames();
  if ( !(AL_PLAYING == state) && lastFramePlayState ) {
   WhenComplete();
   this.Release();
   this.deleteMe=true; //printf( "%s sound cleared for deletion.\n", name );
  }
  lastFramePlayState=(AL_PLAYING == state);
 }

 ~ALSound() {
//  printf( "Deleting sound %s\n", name );
  delete source;
  delete name;
 }
}

class ALSounds : LinkList<ALSound, link=ALSound::link>
{
 bool inList( char *fn ) { for ( s : this; contains(fn,s.name)) return true; return false; }
 ALSound findInList( char *fn ) { for ( s : this; contains(fn,s.name) ) return s; return null; }
 void DeleteDone() { ALSound s,n; for ( s=this.first; s; s=n ) { n=s.next; if ( s.deleteMe ) Delete((void *)s); } }
}

class ALCDeviceContext : ListItem {
 ALCcontext* context;
 ALCdevice* device;
 char *deviceName;
 bool closed;

 void PickDefault() {
  device=alcOpenDevice(null);
  context=alcCreateContext(device,null);
  alcMakeContextCurrent(context);
  closed=false;
 }
 
 void Init( char *context, void *variables ) {
  device = alcOpenDevice(context);
  this.context = alcCreateContext(device, variables);
  alcMakeContextCurrent(this.context);
  closed=false;
 }

 void Close() {
  context = alcGetCurrentContext();
  device = alcGetContextsDevice(context);
  alcMakeContextCurrent(null);
  alcDestroyContext(context);
  alcCloseDevice(device);
  closed=true;
 }

 void DeviceInfo( ) {
  printf( "Sound devices available:\n" ); 
  if ( alcIsExtensionPresent( null, "ALC_ENUMERATION_EXT" ) == AL_TRUE ) {
   printf( "ALC_DEVICE_SPECIFIER: %s\n", (char*) alcGetString( device, ALC_DEVICE_SPECIFIER ) );
   printf( "ALC_DEFAULT_DEVICE_SPECIFIER: %s\n", (char*) alcGetString( null, ALC_DEFAULT_DEVICE_SPECIFIER ) );
   printf( "ALC_EXTENSIONS: %s\n", (char*) alcGetString( null, ALC_EXTENSIONS ) );
  }
  else printf( " ... enumeration error.\n" );
 }

 ~ALCDeviceContext() { if (!closed) Close(); }
}
class ALCDeviceContexts : LinkList<ALCDeviceContext>;

enum EFXType {
 null=0,
 reverb=1,
 chorus=2,
 distortion=3,
 echo=4,
 flanger=5,
 frequencyShifter=6,
 vocalMorpher=7,
 pitchShifter=8,
 ringModulator=9,
 autoWah=10,
 compressor=11,
 equalizer=12 
};

enum EFXWaveForm { sinusoid=0, triangle=1 };
enum EFXDirection { down=0, up=1, off=2 };
enum EFXPhoneme { A=0, E=1, I=2, O=3, U=4, AA=5, AE=6, AH=7, AO=8, EH=9, ER=10, IH=11, IY=12, UH=13, AW=14, 
                  B=15, D=16, F=17, G=18, J=19, K=20, L=21, M=22, N=23, P=24, R=25, S=26, T=27, V=28, Z=29 };
enum EFXWaveForm3a { sinusoid=0, triangle=1, sawtooth=2 };
enum EFXWaveForm3b { sinusoid=0, sawtooth=1, square=2   };

class EFXAuxSlot : ListItem {
 EFX efx;
 ALuint id;
 bool Create() {
  alGetError();
  efx.alGenAuxiliaryEffectSlots(1,&id);
  if ( ALError("EFXAuxSlot:Create:alGenAuxiliaryEffectSlots") ) return false;
  return true;
 }
 bool Destroy() {
  alGetError();
  efx.alDeleteAuxiliaryEffectSlots(1,&id);
  if ( ALError("EFXAuxSlot:Destroy:alDeleteAuxiliaryEffectSlots") ) return false;
  return true;
 }
}
class EFXAuxSlots : LinkList<EFXAuxSlot>;

class EFXEffect {
 EFX efx;
 EFXAuxSlot aux;
 uint id;
 uint type;
 uint firstParameter,lastParameter;
 float directFilter, 
       auxSendFilter, 
       airAbsorption, 
       roomRolloff, 
       coneOuterGainHF, 
       directFilterGainHF, 
       auxSendFilterGainAuto, 
       auxSendFilterGainHFAuto;
 bool Create() {
  alGetError();
  if ( aux ) if ( !aux.Destroy() ) { return false; }
  delete aux; aux = EFXAuxSlot { }; if ( !aux.Create() ) { return false; }
  efx.alGenEffects(1,&id);
  if ( ALError("EFXEffect:Create:alGenEffects") )  return false;
  efx.alEffecti(id,AL_EFFECT_TYPE,type);
  if ( ALError("EFXEffect:Create:alEffecti") ) { efx.alDeleteEffects(1,&id); return false; }
  efx.alAuxiliaryEffectSloti(aux.id, AL_EFFECTSLOT_EFFECT, id);
 }
 bool Destroy() {
  alGetError();
  efx.alAuxiliaryEffectSloti(aux.id, AL_EFFECTSLOT_EFFECT, AL_EFFECT_NULL);
  efx.alDeleteEffects(1,&id);
  if ( ALError("EFXEffect:Destroy:alDeleteEffects") )  return false;
 }
 void ApplySharedEffectParameters() {
 }
}

enum EAXReverbPresets { hanger=2,bathroom=1 };

class EAXReverb : EFXEffect {
 float environmentSize, 
       environmentDiffusion,
       density,
       diffusion,
       gain,
       gainHF,
       gainLF,
       decayTime,
       decayHFRatio,
       decayLFRatio,
       reflectionsGain,
       reflectionsDelay,
       lateGain,
       lateDelay,
       reverbDelay,
       echoTime,
       echoDepth,
       modulationTime,
       modulationDepth,
       airAbsorptionGainHF,
       referenceHF,
       referenceLF,
       roomRolloffFactor;
 SoundVector reflectionsPan, latePan;
 int room, roomHF, roomLF, reverb, flags, reflections, environment;
 bool decayHFLimit;
 EAXReverb() { // Defaulted to bathroom effect
  reflectionsPan = SoundVector { };
  latePan = SoundVector { };
 }
 bool Apply() {
  alGetError();
  efx.alEffectf(id, AL_EAXREVERB_DENSITY, density);
  efx.alEffectf(id, AL_EAXREVERB_DIFFUSION, diffusion);
  efx.alEffectf(id, AL_EAXREVERB_GAIN, gain);
  efx.alEffectf(id, AL_EAXREVERB_GAINHF, gainHF);
  efx.alEffectf(id, AL_EAXREVERB_GAINLF, gainLF);
  efx.alEffectf(id, AL_EAXREVERB_DECAY_TIME, decayTime);
  efx.alEffectf(id, AL_EAXREVERB_DECAY_HFRATIO, decayHFRatio);
  efx.alEffectf(id, AL_EAXREVERB_DECAY_LFRATIO, decayLFRatio);
  efx.alEffectf(id, AL_EAXREVERB_REFLECTIONS_GAIN, reflectionsGain);
  efx.alEffectf(id, AL_EAXREVERB_REFLECTIONS_DELAY, reflectionsDelay);
  efx.alEffectfv(id, AL_EAXREVERB_REFLECTIONS_PAN, reflectionsPan.asFloat3());
  efx.alEffectf(id, AL_EAXREVERB_LATE_REVERB_GAIN, lateGain);
  efx.alEffectf(id, AL_EAXREVERB_LATE_REVERB_DELAY, lateDelay);
  efx.alEffectfv(id, AL_EAXREVERB_LATE_REVERB_PAN, latePan.asFloat3());
  efx.alEffectf(id, AL_EAXREVERB_ECHO_TIME, echoTime);
  efx.alEffectf(id, AL_EAXREVERB_ECHO_DEPTH, echoDepth);
  efx.alEffectf(id, AL_EAXREVERB_MODULATION_TIME, modulationTime);
  efx.alEffectf(id, AL_EAXREVERB_MODULATION_DEPTH, modulationDepth);
  efx.alEffectf(id, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, airAbsorptionGainHF);
  efx.alEffectf(id, AL_EAXREVERB_HFREFERENCE, referenceHF);
  efx.alEffectf(id, AL_EAXREVERB_LFREFERENCE, referenceLF);
  efx.alEffectf(id, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, roomRolloffFactor);
  efx.alEffecti(id, AL_EAXREVERB_DECAY_HFLIMIT, (ALboolean) decayHFLimit);
  if ( ALError("EAXReverb:Apply") ) return false; 
  return true;
 }
 void Preset( EAXReverbPresets preset ) {
  switch ( preset ) {
         default:
   case bathroom:
    environment=3; environmentSize=1.4f; environmentDiffusion=1.000f;
    room=-1000; roomHF=-1200; roomLF=0;
    decayTime=1.49f; decayHFRatio=0.54f; decayLFRatio=1.00f;
    reflections=-370; reflectionsDelay=0.007f; reflectionsPan.x=0.0f; reflectionsPan.y=0.0f; reflectionsPan.z=0.0f;
    reverb=1030; reverbDelay=0.011f; latePan.x=0.00f; latePan.y=0.00f; latePan.z=0.00f;
    echoTime=0.250f; echoDepth=0.000f;
    modulationTime=0.250f; modulationDepth=0.000f;
    airAbsorptionGainHF=-5.0f;
    referenceHF=5000.0f; referenceLF=250.0f;
    roomRolloffFactor=0.00f;
    flags=0x3f;
   break;
  }
 }
}

class EFXReverb : EFXEffect {
 float density, 
       diffusion, 
       gain, 
       gainHF, 
       decayTime, 
       decayHFRatio, 
       reflectionsGain, 
       reflectionsDelay, 
       lateGain, 
       lateDelay, 
       airAbsorptionGainHF, 
       roomRolloffFactor;
  bool decayHFLimit;
 EFXReverb() { 
  density             = AL_REVERB_DEFAULT_DENSITY; 
  diffusion           = AL_REVERB_DEFAULT_DIFFUSION;
  gain                = AL_REVERB_DEFAULT_GAIN;
  gainHF              = AL_REVERB_DEFAULT_GAINHF;
  decayTime           = AL_REVERB_DEFAULT_DECAY_TIME;
  decayHFRatio        = AL_REVERB_DEFAULT_DECAY_HFRATIO;
  reflectionsGain     = AL_REVERB_DEFAULT_REFLECTIONS_GAIN;
  reflectionsDelay    = AL_REVERB_DEFAULT_REFLECTIONS_DELAY;
  lateGain            = AL_REVERB_DEFAULT_LATE_REVERB_GAIN;
  lateDelay           = AL_REVERB_DEFAULT_LATE_REVERB_DELAY;
  airAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
  roomRolloffFactor   = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
  decayHFLimit        = AL_REVERB_DEFAULT_DECAY_HFLIMIT;
 }
public:
 property float density             { get { return density;             } set { density             =RANGE(AL_REVERB_MIN_DENSITY,               value, AL_REVERB_MAX_DENSITY);               } }
 property float diffusion           { get { return diffusion;           } set { diffusion           =RANGE(AL_REVERB_MIN_DIFFUSION,             value, AL_REVERB_MAX_DIFFUSION);             } }
 property float gain                { get { return gain;                } set { gain                =RANGE(AL_REVERB_MIN_GAIN,                  value, AL_REVERB_MAX_GAIN);                  } }
 property float gainHF              { get { return gainHF;              } set { gainHF              =RANGE(AL_REVERB_MIN_GAINHF,                value, AL_REVERB_MAX_GAINHF);                } }
 property float decayTime           { get { return decayTime;           } set { decayTime           =RANGE(AL_REVERB_MIN_DECAY_TIME,            value, AL_REVERB_MAX_DECAY_TIME);            } }
 property float decayHFRatio        { get { return decayHFRatio;        } set { decayHFRatio        =RANGE(AL_REVERB_MIN_DECAY_HFRATIO,         value, AL_REVERB_MAX_DECAY_HFRATIO);         } }
 property float reflectionsGain     { get { return reflectionsGain;     } set { reflectionsGain     =RANGE(AL_REVERB_MIN_REFLECTIONS_GAIN,      value, AL_REVERB_MAX_REFLECTIONS_GAIN);      } }
 property float reflectionsDelay    { get { return reflectionsDelay;    } set { reflectionsDelay    =RANGE(AL_REVERB_MIN_LATE_REVERB_DELAY,     value, AL_REVERB_MAX_REFLECTIONS_DELAY);     } }
 property float lateGain            { get { return lateGain;            } set { lateGain            =RANGE(AL_REVERB_MIN_LATE_REVERB_GAIN,      value, AL_REVERB_MAX_LATE_REVERB_GAIN);      } }
 property float lateDelay           { get { return lateDelay;           } set { lateDelay           =RANGE(AL_REVERB_MIN_LATE_REVERB_DELAY,     value, AL_REVERB_MAX_LATE_REVERB_DELAY);     } }
 property float airAbsorptionGainHF { get { return airAbsorptionGainHF; } set { airAbsorptionGainHF =RANGE(AL_REVERB_MIN_AIR_ABSORPTION_GAINHF, value, AL_REVERB_MAX_AIR_ABSORPTION_GAINHF); } }
 property float roomRolloffFactor   { get { return roomRolloffFactor;   } set { roomRolloffFactor   =RANGE(AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR,   value, AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR);   } }
 property bool  decayHFLimit        { get { return decayHFLimit;        } set { decayHFLimit        =RANGE(AL_REVERB_MIN_DECAY_HFLIMIT,         value, AL_REVERB_MAX_DECAY_HFLIMIT);         } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXChorus : EFXEffect {
 EFXWaveForm waveform;
 int phase;
 float rate, depth, feedback, delay;
 EFXChorus() { 
  waveform = (EFXWaveForm) AL_CHORUS_DEFAULT_WAVEFORM;
  phase               = AL_CHORUS_DEFAULT_PHASE; 
  rate                = AL_CHORUS_DEFAULT_RATE;
  depth               = AL_CHORUS_DEFAULT_DEPTH;
  feedback            = AL_CHORUS_DEFAULT_FEEDBACK;
  delay               = AL_CHORUS_DEFAULT_DELAY;
 }
public:
 property float phase    { get { return phase;    } set { phase     =RANGE(AL_CHORUS_MIN_PHASE,    value, AL_CHORUS_MAX_PHASE);     } }
 property float rate     { get { return rate;     } set { rate      =RANGE(AL_CHORUS_MIN_RATE,     value, AL_CHORUS_MAX_RATE);      } }
 property float depth    { get { return depth;    } set { depth     =RANGE(AL_CHORUS_MIN_DEPTH,    value, AL_CHORUS_MAX_DEPTH);     } }
 property float feedback { get { return feedback; } set { feedback  =RANGE(AL_CHORUS_MIN_FEEDBACK, value, AL_CHORUS_MAX_FEEDBACK);  } }
 property float delay    { get { return delay;    } set { delay     =RANGE(AL_CHORUS_MIN_DELAY,    value, AL_CHORUS_MAX_DELAY);     } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXDistortion : EFXEffect {
 float edge, gain, cutoff, eqCenter, eqBandwidth;
 EFXDistortion() {
  edge         = AL_DISTORTION_DEFAULT_EDGE; 
  gain         = AL_DISTORTION_DEFAULT_GAIN;
  cutoff       = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
  eqCenter     = AL_DISTORTION_DEFAULT_EQCENTER;
  eqBandwidth  = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
 }
public:
 property float edge        { get { return edge;        } set { edge        =RANGE(AL_DISTORTION_MIN_EDGE,           value, AL_DISTORTION_MAX_EDGE);           } }
 property float gain        { get { return gain;        } set { gain        =RANGE(AL_DISTORTION_MIN_GAIN,           value, AL_DISTORTION_MAX_GAIN);           } }
 property float cutoff      { get { return cutoff;      } set { cutoff      =RANGE(AL_DISTORTION_MIN_LOWPASS_CUTOFF, value, AL_DISTORTION_MAX_LOWPASS_CUTOFF); } }
 property float eqCenter    { get { return eqCenter;    } set { eqCenter    =RANGE(AL_DISTORTION_MIN_EQCENTER,       value, AL_DISTORTION_MAX_EQCENTER);       } }
 property float eqBandwidth { get { return eqBandwidth; } set { eqBandwidth =RANGE(AL_DISTORTION_MIN_EQBANDWIDTH,    value, AL_DISTORTION_MAX_EQBANDWIDTH);    } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXEcho : EFXEffect {
 float delay, delayLR, damping, feedback, spread;
 EFXEcho() {
  delay     = AL_ECHO_DEFAULT_DELAY; 
  delayLR   = AL_ECHO_DEFAULT_LRDELAY;
  damping   = AL_ECHO_DEFAULT_DAMPING;
  feedback  = AL_ECHO_DEFAULT_FEEDBACK;
  spread    = AL_ECHO_DEFAULT_SPREAD;
 }
public:
 property float delay    { get { return delay;    } set { delay    =RANGE(AL_ECHO_MIN_DELAY,    value, AL_ECHO_MAX_DELAY);    } }
 property float delayLR  { get { return delayLR;  } set { delayLR  =RANGE(AL_ECHO_MIN_LRDELAY,  value, AL_ECHO_MAX_LRDELAY);  } }
 property float damping  { get { return damping;  } set { damping  =RANGE(AL_ECHO_MIN_DAMPING,  value, AL_ECHO_MAX_DAMPING);  } }
 property float feedback { get { return feedback; } set { feedback =RANGE(AL_ECHO_MIN_FEEDBACK, value, AL_ECHO_MAX_FEEDBACK); } }
 property float spread   { get { return spread;   } set { spread   =RANGE(AL_ECHO_MIN_SPREAD,   value, AL_ECHO_MAX_SPREAD);   } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXFlanger : EFXEffect {
 EFXWaveForm waveform;
 int phase;
 float rate, depth, feedback, delay;
 EFXFlanger() {
  waveform = (EFXWaveForm) AL_FLANGER_DEFAULT_WAVEFORM;
  phase    = AL_FLANGER_DEFAULT_PHASE; 
  rate     = AL_FLANGER_DEFAULT_RATE;
  depth    = AL_FLANGER_DEFAULT_DEPTH;
  feedback = AL_FLANGER_DEFAULT_FEEDBACK;
  delay    = AL_FLANGER_DEFAULT_DELAY;
 }
public:
 property float phase    { get { return phase;    } set { phase    =RANGE(AL_FLANGER_MIN_PHASE,    value, AL_FLANGER_MAX_PHASE);    } }
 property float rate     { get { return rate;     } set { rate     =RANGE(AL_FLANGER_MIN_RATE,     value, AL_FLANGER_MAX_RATE);     } }
 property float depth    { get { return depth;    } set { depth    =RANGE(AL_FLANGER_MIN_DEPTH,    value, AL_FLANGER_MAX_DEPTH);    } }
 property float feedback { get { return feedback; } set { feedback =RANGE(AL_FLANGER_MIN_FEEDBACK, value, AL_FLANGER_MAX_FEEDBACK); } }
 property float delay    { get { return delay;    } set { delay    =RANGE(AL_FLANGER_MIN_DELAY,    value, AL_FLANGER_MAX_DELAY);    } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXFrequencyShift : EFXEffect {
 float freq;
 EFXDirection L,R;
 EFXFrequencyShift() {
  freq = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; 
  L    = (EFXDirection) AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION;
  R    = (EFXDirection) AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION;
 }
public:
 property float freq      { get { return freq;      } set { freq      =RANGE(AL_FREQUENCY_SHIFTER_MIN_FREQUENCY,    value, AL_FREQUENCY_SHIFTER_MAX_FREQUENCY);    } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXVocalMorph : EFXEffect {
 EFXPhoneme phonemeA, phonemeB;
 int phonemeACoarseTuning, phonemeBCoarseTuning;
 EFXWaveForm3a waveform;
 float rate;
 EFXReverb() { 
  phonemeA             = (EFXPhoneme) AL_VOCAL_MORPHER_DEFAULT_PHONEMEA; 
  phonemeB             = (EFXPhoneme) AL_VOCAL_MORPHER_DEFAULT_PHONEMEB;
  phonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
  phonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
  waveform             = (EFXWaveForm3a) AL_VOCAL_MORPHER_DEFAULT_WAVEFORM;
  rate                 = AL_VOCAL_MORPHER_DEFAULT_RATE;
 }
public:
 property float phonemeACoarseTuning { get { return phonemeACoarseTuning; } set { phonemeACoarseTuning =RANGE(AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING, value, AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING); } }
 property float phonemeBCoarseTuning { get { return phonemeBCoarseTuning; } set { phonemeBCoarseTuning =RANGE(AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING, value, AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING); } }
 property float rate                 { get { return rate;                 } set { rate                 =RANGE(AL_VOCAL_MORPHER_MIN_RATE,                   value, AL_VOCAL_MORPHER_MAX_RATE);                   } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXPitchShift : EFXEffect {
 int coarse, fine;
 EFXReverb() { 
  coarse = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; 
  fine   = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
 }
public:
 property float coarse { get { return coarse; } set { coarse =RANGE(AL_PITCH_SHIFTER_MIN_COARSE_TUNE, value, AL_PITCH_SHIFTER_MAX_COARSE_TUNE); } }
 property float fine   { get { return fine;   } set { fine   =RANGE(AL_PITCH_SHIFTER_MIN_FINE_TUNE,   value, AL_PITCH_SHIFTER_MAX_FINE_TUNE);   } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXRingMod : EFXEffect {
 float frequency, cutoff;
 EFXWaveForm3b waveform;
 EFXReverb() { 
  frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; 
  cutoff    = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
  waveform  = (EFXWaveForm3b) AL_RING_MODULATOR_DEFAULT_WAVEFORM;
 }
public:
 property float frequency { get { return frequency; } set { frequency =RANGE(AL_RING_MODULATOR_MIN_FREQUENCY,       value, AL_RING_MODULATOR_MAX_FREQUENCY);       } }
 property float cutoff    { get { return cutoff;    } set { cutoff    =RANGE(AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF, value, AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF); } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXAutoWah : EFXEffect {
 float attack, release, resonance, gain;
 EFXReverb() { 
  attack    = AL_AUTOWAH_DEFAULT_ATTACK_TIME; 
  release   = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
  resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
  gain      = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
 }
public:
 property float attack    { get { return attack;    } set { attack    =RANGE(AL_AUTOWAH_MIN_ATTACK_TIME,  value, AL_AUTOWAH_MAX_ATTACK_TIME);  } }
 property float release   { get { return release;   } set { release   =RANGE(AL_AUTOWAH_MIN_RELEASE_TIME, value, AL_AUTOWAH_MAX_RELEASE_TIME); } }
 property float resonance { get { return resonance; } set { resonance =RANGE(AL_AUTOWAH_MIN_RESONANCE,    value, AL_AUTOWAH_MAX_RESONANCE);    } }
 property float gain      { get { return gain;      } set { gain      =RANGE(AL_AUTOWAH_MIN_PEAK_GAIN,    value, AL_AUTOWAH_MAX_PEAK_GAIN);    } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXCompressor {
 bool on;
 EFXReverb() { 
  on             = (bool) AL_COMPRESSOR_DEFAULT_ONOFF; 
 }
public:
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXEqualizer {
 float lowGain, lowCutoff, mid1Gain, mid1Center, mid1Width, mid2Gain, mid2Center, mid2Width, highGain, highCutoff; 
 EFXReverb() { 
  lowGain    = AL_EQUALIZER_DEFAULT_LOW_GAIN; 
  lowCutoff  = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
  mid1Gain   = AL_EQUALIZER_DEFAULT_MID1_GAIN;
  mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
  mid1Width  = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
  mid2Gain   = AL_EQUALIZER_DEFAULT_MID2_GAIN;
  mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
  mid2Width  = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
  highGain   = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
  highCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
 }
public:
 property float lowGain    { get { return lowGain;    } set { lowGain    =RANGE(AL_EQUALIZER_MIN_LOW_GAIN,    value, AL_EQUALIZER_MAX_LOW_GAIN);    } }
 property float lowCutoff  { get { return lowCutoff;  } set { lowCutoff  =RANGE(AL_EQUALIZER_MIN_LOW_CUTOFF,  value, AL_EQUALIZER_MAX_LOW_CUTOFF);  } }
 property float mid1Gain   { get { return mid1Gain;   } set { mid1Gain   =RANGE(AL_EQUALIZER_MIN_MID1_GAIN,   value, AL_EQUALIZER_MAX_MID1_GAIN);   } }
 property float mid1Center { get { return mid1Center; } set { mid1Center =RANGE(AL_EQUALIZER_MIN_MID1_CENTER, value, AL_EQUALIZER_MAX_MID1_CENTER); } }
 property float mid1Width  { get { return mid1Width;  } set { mid1Width  =RANGE(AL_EQUALIZER_MIN_MID1_WIDTH,  value, AL_EQUALIZER_MAX_MID1_WIDTH);  } }
 property float mid2Gain   { get { return mid2Gain;   } set { mid2Gain   =RANGE(AL_EQUALIZER_MIN_MID2_GAIN,   value, AL_EQUALIZER_MAX_MID2_GAIN);   } }
 property float mid2Center { get { return mid2Center; } set { mid2Center =RANGE(AL_EQUALIZER_MIN_MID2_CENTER, value, AL_EQUALIZER_MAX_MID2_CENTER); } }
 property float mid2Width  { get { return mid2Width;  } set { mid2Width  =RANGE(AL_EQUALIZER_MIN_MID2_WIDTH,  value, AL_EQUALIZER_MAX_MID2_WIDTH);  } }
 property float highGain   { get { return highGain;   } set { highGain   =RANGE(AL_EQUALIZER_MIN_HIGH_GAIN,   value, AL_EQUALIZER_MAX_HIGH_GAIN);   } }
 property float highCutoff { get { return highCutoff; } set { highCutoff =RANGE(AL_EQUALIZER_MIN_HIGH_CUTOFF, value, AL_EQUALIZER_MAX_HIGH_CUTOFF); } }
 void Apply() {
 }
 void Update() {
 }
 void Print() {
 }
}

class EFXFilter {
}

class EFX { 
 EFXEffect effects;
 int version_major,version_minor,max_aux_sends,meters_per_unit;
 
 LPALGENEFFECTS alGenEffects;
 LPALDELETEEFFECTS alDeleteEffects;
 LPALISEFFECT alIsEffect;
 LPALEFFECTI alEffecti;
 LPALEFFECTIV alEffectiv;
 LPALEFFECTF alEffectf;
 LPALEFFECTFV alEffectfv;
 LPALGETEFFECTI alGetEffecti;
 LPALGETEFFECTIV alGetEffectiv;
 LPALGETEFFECTF alGetEffectf;
 LPALGETEFFECTFV alGetEffectfv;
 LPALGENFILTERS alGenFilters;
 LPALDELETEFILTERS alDeleteFilters;
 LPALISFILTER alIsFilter;
 LPALFILTERI alFilteri;
 LPALFILTERIV alFilteriv;
 LPALFILTERF alFilterf;
 LPALFILTERFV alFilterfv;
 LPALGETFILTERI alGetFilteri;
 LPALGETFILTERIV alGetFilteriv;
 LPALGETFILTERF alGetFilterf;
 LPALGETFILTERFV alGetFilterfv;
 LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
 LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
 LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
 LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
 LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
 LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
 LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
 LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
 LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
 LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
 LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;

 bool Init()
 {
  bool hasEFX=false;
  ALCdevice *device;
  ALuint obj;
  int i;
  const ALenum effects[] = {
   AL_EFFECT_EAXREVERB, AL_EFFECT_REVERB, AL_EFFECT_CHORUS,
   AL_EFFECT_DISTORTION, AL_EFFECT_ECHO, AL_EFFECT_FLANGER,
   AL_EFFECT_FREQUENCY_SHIFTER, AL_EFFECT_VOCAL_MORPHER,
   AL_EFFECT_PITCH_SHIFTER, AL_EFFECT_RING_MODULATOR, AL_EFFECT_AUTOWAH,
   AL_EFFECT_COMPRESSOR, AL_EFFECT_EQUALIZER, AL_EFFECT_NULL
  };
  char effectNames[] = "EAX Reverb,Reverb,Chorus,Distortion,Echo,Flanger,Frequency Shifter,Vocal Morpher,pitch Shifter,Ring Modulator,Autowah,Compressor,Equalizer,";
  const ALenum filters[] = {
   AL_FILTER_LOWPASS, AL_FILTER_HIGHPASS, AL_FILTER_BANDPASS,
   AL_FILTER_NULL
  };
  char filterNames[] = "Low-pass,High-pass,Band-pass,";
  char *current;

  device = alcGetContextsDevice(alcGetCurrentContext());
  if(alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_FALSE) { printf("EFX not available.\n"); return; }

  alcGetIntegerv(device, ALC_EFX_MAJOR_VERSION, 1, &version_major);
  alcGetIntegerv(device, ALC_EFX_MINOR_VERSION, 1, &version_minor);
  checkForErrors();
  printf("EFX version: %d.%d\n", (int)version_major, (int)version_minor);

  alcGetIntegerv(device, ALC_MAX_AUXILIARY_SENDS, 1, &max_aux_sends);
  checkForErrors();
  printf("Max auxiliary sends: %d\n", (int)max_aux_sends);

  hasEFX = alcIsExtensionPresent(null, ALC_EXT_EFX_NAME );
  if ( hasEFX ) {
   printf( "EFX extension available: \n" );
   alGenEffects                 = (LPALGENEFFECTS) alGetProcAddress( "alGenEffects" ); if ( alGenEffects == null ) hasEFX = false;
   alDeleteEffects              = (LPALDELETEEFFECTS) alGetProcAddress( "alDeleteEffects" ); if ( alDeleteEffects == null ) hasEFX = false; 
   alIsEffect                   = (LPALISEFFECT) alGetProcAddress( "alIsEffect" ); if ( alIsEffect == null ) hasEFX = false;
   alEffecti                    = (LPALEFFECTI) alGetProcAddress( "alEffecti" ); if ( alEffecti == null ) hasEFX = false;
   alEffectiv                   = (LPALEFFECTIV) alGetProcAddress( "alEffectiv" ); if ( alEffectiv == null ) hasEFX = false;
   alEffectf                    = (LPALEFFECTF) alGetProcAddress( "alEffectf" ); if ( alEffectf == null ) hasEFX = false;
   alEffectfv                   = (LPALEFFECTFV) alGetProcAddress( "alEffectfv" ); if ( alEffectfv == null ) hasEFX = false;
   alGetEffecti                 = (LPALGETEFFECTI) alGetProcAddress( "alGetEffecti" ); if ( alGetEffecti == null ) hasEFX = false;
   alGetEffectiv                = (LPALGETEFFECTIV) alGetProcAddress( "alGetEffectiv" ); if ( alGetEffectiv == null ) hasEFX = false;
   alGetEffectf                 = (LPALGETEFFECTF) alGetProcAddress( "alGetEffectf" ); if ( alGetEffectf == null ) hasEFX = false;
   alGetEffectfv                = (LPALGETEFFECTFV) alGetProcAddress( "alGetEffectfv" ); if ( alGetEffectfv == null ) hasEFX = false;
   alGenFilters                 = (LPALGENFILTERS) alGetProcAddress( "alGenFilters" ); if ( alGenFilters == null ) hasEFX = false;
   alDeleteFilters              = (LPALDELETEFILTERS) alGetProcAddress( "alDeleteFilters" ); if ( alDeleteFilters == null ) hasEFX = false;
   alIsFilter                   = (LPALISFILTER) alGetProcAddress( "alIsFilter" ); if ( alIsFilter == null ) hasEFX = false;
   alFilteri                    = (LPALFILTERI) alGetProcAddress( "alFilteri" ); if ( alFilteri == null ) hasEFX = false;
   alFilteriv                   = (LPALFILTERIV) alGetProcAddress( "alFilteriv" ); if ( alFilteriv == null ) hasEFX = false;
   alFilterf                    = (LPALFILTERF) alGetProcAddress( "alFilterf" ); if ( alFilterf == null ) hasEFX = false;
   alFilterfv                   = (LPALFILTERFV) alGetProcAddress( "alFilterfv" ); if ( alFilterfv == null ) hasEFX = false;
   alGetFilteri                 = (LPALGETFILTERI) alGetProcAddress( "alGetFilteri" ); if ( alGetFilteri == null ) hasEFX = false;
   alGetFilteriv                = (LPALGETFILTERIV) alGetProcAddress( "alGetFilteriv" ); if ( alGetFilteriv == null ) hasEFX = false;
   alGetFilterf                 = (LPALGETFILTERF) alGetProcAddress( "alGetFilterf" ); if ( alGetFilterf == null ) hasEFX = false;
   alGetFilterfv                = (LPALGETFILTERFV) alGetProcAddress( "alGetFilterfv" ); if ( alGetFilterfv == null ) hasEFX = false;
   alGenAuxiliaryEffectSlots    = (LPALGENAUXILIARYEFFECTSLOTS) alGetProcAddress( "alGenAuxiliaryEffectSlots" ); if ( alGenAuxiliaryEffectSlots == null ) hasEFX = false;
   alDeleteAuxiliaryEffectSlots = (LPALDELETEAUXILIARYEFFECTSLOTS) alGetProcAddress( "alDeleteAuxiliaryEffectSlots" ); if ( alDeleteAuxiliaryEffectSlots == null ) hasEFX = false;
   alIsAuxiliaryEffectSlot      = (LPALISAUXILIARYEFFECTSLOT) alGetProcAddress( "alIsAuxiliaryEffectSlot" ); if ( alIsAuxiliaryEffectSlot == null ) hasEFX = false;
   alAuxiliaryEffectSloti       = (LPALAUXILIARYEFFECTSLOTI) alGetProcAddress( "alAuxiliaryEffectSloti" ); if ( alAuxiliaryEffectSloti == null ) hasEFX = false;
   alAuxiliaryEffectSlotiv      = (LPALAUXILIARYEFFECTSLOTIV) alGetProcAddress( "alAuxiliaryEffectSlotiv" ); if ( alAuxiliaryEffectSlotiv == null ) hasEFX = false;
   alAuxiliaryEffectSlotf       = (LPALAUXILIARYEFFECTSLOTF) alGetProcAddress( "alAuxiliaryEffectSlotf" ); if ( alAuxiliaryEffectSlotf == null ) hasEFX = false;
   alAuxiliaryEffectSlotfv      = (LPALAUXILIARYEFFECTSLOTFV) alGetProcAddress( "alAuxiliaryEffectSlotfv" ); if ( alAuxiliaryEffectSlotfv == null ) hasEFX = false;
   alGetAuxiliaryEffectSloti    = (LPALGETAUXILIARYEFFECTSLOTI) alGetProcAddress( "alGetAuxiliaryEffectSloti" ); if ( alGetAuxiliaryEffectSloti == null ) hasEFX = false;
   alGetAuxiliaryEffectSlotiv   = (LPALGETAUXILIARYEFFECTSLOTIV) alGetProcAddress( "alGetAuxiliaryEffectSlotiv" ); if ( alGetAuxiliaryEffectSlotiv == null ) hasEFX = false;
   alGetAuxiliaryEffectSlotf    = (LPALGETAUXILIARYEFFECTSLOTF) alGetProcAddress( "alGetAuxiliaryEffectSlotf" ); if ( alGetAuxiliaryEffectSlotf == null ) hasEFX = false;
   alGetAuxiliaryEffectSlotfv   = (LPALGETAUXILIARYEFFECTSLOTFV) alGetProcAddress( "alGetAuxiliaryEffectSlotfv" ); if ( alGetAuxiliaryEffectSlotfv == null ) hasEFX = false;
  }
  alGetError();
  if ( !hasEFX ) { ALError( "Failed to retreive EFX extension functions.\n" ); return; }

  alGenFilters(1, &obj);
  checkForErrors();
  current = filterNames;
  for(i = 0;filters[i] != AL_FILTER_NULL;i++)
  {
   char *next = strchr(current, ',');

   alFilteri(obj, AL_FILTER_TYPE, filters[i]);
   if(alGetError() == AL_NO_ERROR) current = next+1;
   else memmove(current, next+1, strlen(next));
  }
  alDeleteFilters(1, &obj);
  checkForErrors();
  printList("Supported filters", ',', filterNames);

  alGenEffects(1, &obj);
  checkForErrors();
  current = effectNames;
  for(i = 0;effects[i] != AL_EFFECT_NULL;i++) {
   char *next = strchr(current, ',');
   alEffecti(obj, AL_EFFECT_TYPE, effects[i]);
   if(alGetError() == AL_NO_ERROR)
   current = next+1;
   else
   memmove(current, next+1, strlen(next));
  }
  alDeleteEffects(1, &obj);
  checkForErrors();
  printList("Supported effects", ',', effectNames);
  return hasEFX=true;
 }
}

class ALDeviceDescription : ListItem {
 String name;
 property String name { set { delete name; name = CopyString(value); } };
 ~ALDeviceDescription() { delete name; }
}

class ALDeviceDescriptions : LinkList<ALDeviceDescription>
{
 bool inList(char *fn) { for (a : this; contains(a.name, fn)) return true; return false; }
 ALDeviceDescription find(char *fn) { for (a : this; contains(a.name, fn)) return a; return null; }
}

// Simple Sound Manager
class ALSoundManager {
 ALWaveForms waveforms { };
 ALUREStreams streams { };
 ALSounds sounds { };
 ALCDeviceContexts deviceContexts { };
 bool error,initialized,muted,hasOggExtension,hasEAX;
 float distance, gain, max_gain, min_gain, dopplerFactor, dopplerVelocity;
 int version_major, version_minor, refreshRate;
// uint bufferClamp, sourceClamp;
 uint monoSources, stereoSources;
 ALDeviceDescriptions outputs { };
 ALDeviceDescriptions inputs { };

 Timer timer { this, delay=(1.0f/refreshRate); started=false;
  bool DelayExpired()
  {
   alureUpdate(); //printf( "alureUpdate()\n" ); ALError("Update: ");
   for ( s : sounds) s._BetweenFrames();
   sounds.DeleteDone();
   timer.delay = (1.0f/refreshRate);
   timer.Start();
   return true;
  }
 };

 // EAX 2.0 GUIDs
 EAXSet eaxSet; // EAXSet function, retrieved if EAX Extension is supported
 EAXGet eaxGet; // EAXGet function, retrieved if EAX Extension is supported
 EFX efx {};    // EFX function bindings

 SoundVector position, velocity, orientation;
                               // ^orientation of the Listener. (first 3 elements are "at", second 3 are "up")
                               //  Also note that these should be units of '1'.

 //bufferClamp=MAX_AUDIO_BUFFERS;
 //sourceClamp=MAX_AUDIO_SOURCES;
 refreshRate=60;
 distance = 10000.0f; gain=1.0f; max_gain=1.0f; min_gain=0.0f;
 position    = SoundVector { x=0.0f, y=0.0f, z=0.0f }; 
 velocity    = SoundVector { x=0.0f, y=0.0f, z=0.0f };  
 orientation = SoundVector { x=0.0f, y=0.0f, z=-1.0f,  a=0.0f, b=1.0f, c=0.0f };

 ALSoundManager() {
  this.timer.delay = (1.0f/refreshRate);
  this.timer.Start();
 }
 ~ALSoundManager()
 {
  for (s : sounds) { s.Release(); }
  delete sounds;
  delete efx;
  deviceContexts.Free();
  for(s : streams) s.Stop();
  streams.Free();
  delete waveforms;
 }

 inline void ClearError() { alGetError(); }
 ALCdevice *currentDevice() { return alcGetContextsDevice(alcGetCurrentContext()); }
 ALCcontext *currentContext() { return alcGetCurrentContext(); }

 // Forces initialization defaulting to software when available.
 // You would use this to support EFX on most contemporary sound devices.
 // The device context is returned, or null if none was initialized.
 ALCDeviceContext InitSoftware() {
  ALCDeviceContext alc { };
  alc.Init("Generic Software",0);
  if ( !Init(alc) ) { delete alc; return null; }
  return alc;
 }
 ALCDeviceContext InitDirectSound() {
  ALCDeviceContext alc { };
  alc.Init("DirectSound",0);
  if ( !Init(alc) ) { delete alc; return null; }
  return alc;
 }
 ALCDeviceContext InitContext( char * context ) {
  ALCDeviceContext alc { };
  alc.Init(context,0);
  if ( !Init(alc) ) { delete alc; return null; }
  return alc;
 }

 // Initialize OpenAL and clear the error bit.
 bool Init( ALCDeviceContext alc ) { 
  const char *str;
  if ( initialized ) return;
  if ( alc ) { deviceContexts.Insert(null, alc); }
  else { alc = ALCDeviceContext { }; alc.PickDefault(); deviceContexts.Insert(null, alc); }
  str = alcGetString(alc.device, ALC_DEVICE_SPECIFIER);
  printf( "OpenAL initialized: %s\n", str );
  //unusedBuffers=bufferClamp;
  //unusedSources=sourceClamp;
  alGetError();
  if ( alGetError() != AL_NO_ERROR ) {
   printf( "Init: Error initializing audio!\n" );
   initialized=false;
   return false;
  }
  printf( "ALSound: Initializing.\n" );
  { ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
    alcGetIntegerv(alc ? alc.device : device, ALC_MONO_SOURCES, 1, &monoSources);
    alcGetIntegerv(alc ? alc.device : device, ALC_STEREO_SOURCES, 1, &stereoSources);
  }
  printf( "Detected %d hinted mono source%s, and %d hinted stereo source%s.\n", 
          monoSources, monoSources != 1 ? "s" : "", stereoSources, stereoSources != 1 ? "s" : "" );
  if ( alIsExtensionPresent( "AL_EXT_vorbis" ) != AL_TRUE ) {
   printf( "Ogg Vorbis extension not available!  This is probably not Linux.\n" );
   hasOggExtension = false;
  } else hasOggExtension = true;

  // Check for EAX 2.0 support and
  // Retrieves function entry addresses to API ARB extensions, in this case,
  // for the EAX extension. See Appendix 1 (Extensions) of
  // http://www.openal.org/openal_webstf/specs/OpenAL1-1Spec_html/al11spec7.html
  hasEAX = alIsExtensionPresent( "EAX2.0" );
  if ( hasEAX ) {
   printf( "EAX 2.0 extension is available.\n" );
   eaxSet = (EAXSet) alGetProcAddress( "EAXSet" ); if ( eaxSet == null ) hasEAX = false;
   eaxGet = (EAXGet) alGetProcAddress( "EAXGet" ); if ( eaxGet == null ) hasEAX = false;
   if ( !hasEAX ) ALError( "Failed to retreive EAX extension functions.\n" );
  }

  if ( !efx.Init() ) { delete efx; efx=null; printf( "EFX did not initialize properly, and therefore is unavailable.\n" ); }
  DeviceInfo( );
  alGetError();
  return true;
 }

 ALSound Cue( char *filename, float pitch, float gain, bool looped, bool createSource )
 {
  ALSound sound;
  int newid;
  if ( !filename ) return null;
  alGetError();
//  if ( unusedBuffers <= 0 ) { printf( "ALSound:Cue() No more buffers available (%d used)\n", bufferClamp-unusedBuffers ); return null; }
  sound = ALSound { pitch=pitch, gain=gain, loop=(ALboolean) looped, name=CopyString(filename) };
  sound.waveform=waveforms.findOrLoad(filename);
  sound.format=sound.waveform.sfinfo.format;
  sound.size=sound.waveform.sampleCount * sound.waveform.sfinfo.channels *2;
  sound.freq=(uint) sound.waveform.sfinfo.samplerate;
  //sound.loop = looped ? AL_LOOP : 0;
  sound.duration=(float) sound.waveform.sampleCount / (float) sound.freq;
  //sound.printInfo();
  sound.source = ALSource { };
//  sources.Insert(null, sound.source); 
  //unusedSources--;
  alGenSources(1, &(sound.source.id)); if(ALError("ALSoundManager:Cue:alGenSources")) { delete sound; return null; }
  sound.source.bindSound(sound);
  sounds.Insert(null, sound);
  return sound;
 }

 ALSound CueAndPlay( char *filename, float pitch, float gain, bool looped, bool createSource ) {
  ALSound s = Cue( filename, pitch, gain, looped, createSource );
//  printf( "Attempting to play %s\n", filename );
//  if ( !s ) printf( "Sound was null, so it couldn't have been played.\n" );
//  else { 
//   printf ( "Playing sound '%s' buffer %d source %d\n", s.name, (int) s.buffer.id, (int) s.source.id );
  if ( s ) s.Play();
  return s;
 }

 ALUREStream AddStream( char *fn, ALSource source, bool loop, bool calcLength ) {
  ALUREStream stream { };
  if ( !source ) {	 // Bind the buffer with the source.
   stream.source = ALSource { };
   //unusedSources--;
   alGenSources(1, &(stream.source.id)); if(ALError("ALSoundManager:WAV:alGenSources")) { return stream; }
   stream.source.bindStream(stream);
	// Do another error check and return.
	if(ALError("ALSoundManager:AddStream:alSource")) { return stream; }
  }
  else stream.source=source;
  streams.Insert(null, stream);
  incref stream;
  stream.manager=this;
  stream.loop=loop;
  if ( !stream.LoadIntoSource(fn,stream.source,calcLength) ) { printf("ALSoundManager:AddStream:stream.Create() had an error.\n" ); return stream; }
  return stream;
 }

 void SetListenerValues()
 {
  alListenerfv(AL_POSITION,    position.asFloat3()    );
  alListenerfv(AL_VELOCITY,    velocity.asFloat3()    );
  alListenerfv(AL_ORIENTATION, orientation.asFloat6() );
 }

 void SetGain( float f ) { gain=f; alListenerf( AL_GAIN, gain ); }

 // Detaches all sources from buffers.  This is done before all sounds are discarded.
 void Clear() { }

 bool ResumeAll( void )
 {
  int sourceAudioState = 0;
  alGetError();
  for ( s : sounds) { // Are we currently playing the audio source?
   alGetSourcei( s.source.id, AL_SOURCE_STATE, &sourceAudioState );
   if ( sourceAudioState == AL_PAUSED )
   {
    alSourcePlay( s.source.id );
    if ( ALError( "ALSoundManager:ResumeAll:alSourcePlay") ) return false;
    s.source.state=playing;
   }
  }
  muted=false;
  return true;
 }

 bool PauseAll() {
  int sourceAudioState = 0;
  alGetError();
  for ( s : sounds ) {
   if ( s.Playing() ) {
    alGetSourcei( s.source.id, AL_SOURCE_STATE, &sourceAudioState );
    if ( sourceAudioState != AL_PAUSED ) {
     alSourcePause( s.source.id );
     if ( ALError( "ALSoundManager:PauseAll:alSourcePa
