/** Decodes an opened OggVorbis file into an OpenAL buffer
 *  \param psTrack pointer to object which will contain the final buffer
 *  \param PHYSFS_fileHandle file handle given by PhysicsFS to the opened file
 *  \return on success the psTrack pointer, otherwise it will be free'd and a NULL pointer is returned instead
 */
static inline TRACK* sound_DecodeOggVorbisTrack(TRACK *psTrack, PHYSFS_file* PHYSFS_fileHandle)
{
#ifndef WZ_NOSOUND
	ALenum		format;
	ALuint		buffer;
	struct OggVorbisDecoderState *decoder;
	soundDataBuffer	*soundBuffer;

	if ( !openal_initialized )
	{
		return NULL;
	}

	decoder = sound_CreateOggVorbisDecoder(PHYSFS_fileHandle, true);
	if (decoder == NULL)
	{
		debug(LOG_WARNING, "Failed to open audio file for decoding");
		free(psTrack);
		return NULL;
	}

	soundBuffer = sound_DecodeOggVorbis(decoder, 0);
	sound_DestroyOggVorbisDecoder(decoder);

	if (soundBuffer == NULL)
	{
		free(psTrack);
		return NULL;
	}

	if (soundBuffer->size == 0)
	{
		debug(LOG_WARNING, "sound_DecodeOggVorbisTrack: OggVorbis track is entirely empty after decoding");
// NOTE: I'm not entirely sure if a track that's empty after decoding should be
//       considered an error condition. Therefore I'll only error out on DEBUG
//       builds. (Returning NULL here __will__ result in a program termination.)
#ifdef DEBUG
		free(soundBuffer);
		free(psTrack);
		return NULL;
#endif
	}

	// Determine PCM data format
	format = (soundBuffer->channelCount == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;

	// Create an OpenAL buffer and fill it with the decoded data
	alGenBuffers(1, &buffer);
	sound_GetError();
	alBufferData(buffer, format, soundBuffer->data, soundBuffer->size, soundBuffer->frequency);
	sound_GetError();

	free(soundBuffer);

	// save buffer name in track
	psTrack->iBufferName = buffer;
#endif

	return psTrack;
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
BOOL sound_Play3DSample( TRACK *psTrack, AUDIO_SAMPLE *psSample )
{
#ifndef WZ_NOSOUND
	ALfloat zero[3] = { 0.0, 0.0, 0.0 };
	ALfloat volume;
	ALint error;

	if (sfx3d_volume == 0.0)
	{
		return false;
	}

	volume = ((float)psTrack->iVol / 100.f);		// max range is 0-100
	psSample->fVol = volume;						// store results for later

	// If we can't hear it, then don't bother playing it.
	if (volume == 0.0f)
	{
		return false;
	}
	// Clear error codes
	alGetError();

	alGenSources( 1, &(psSample->iSample) );

	error = sound_GetError();
	if (error != AL_NO_ERROR)
	{
		/* FIXME: We run out of OpenAL sources very quickly, so we
		 * should handle the case where we've ran out of them.
		 * Currently we don't do this, causing some unpleasant side
		 * effects, e.g. crashing...
		 */
	}

	// HACK: this is a workaround for a bug in the 64bit implementation of OpenAL on GNU/Linux
	// The AL_PITCH value really should be 1.0.
	alSourcef(psSample->iSample, AL_PITCH, 1.001f);

	sound_SetObjectPosition( psSample );
	alSourcefv( psSample->iSample, AL_VELOCITY, zero );
	alSourcei( psSample->iSample, AL_BUFFER, psTrack->iBufferName );
	alSourcei( psSample->iSample, AL_LOOPING, (sound_SetupChannel(psSample)) ? AL_TRUE : AL_FALSE );

	// NOTE: this is only useful for debugging.
#ifdef DEBUG
	psSample->is3d = true;
	psSample->isLooping = sound_TrackLooped(psSample->iTrack)? AL_TRUE : AL_FALSE;
	memcpy(psSample->filename,psTrack->fileName, strlen(psTrack->fileName));
	psSample->filename[strlen(psTrack->fileName)]='\0';
#endif

	// Clear error codes
	alGetError();

	alSourcePlay( psSample->iSample );
	sound_GetError();
#endif
	return true;
}
/** Destroy the given stream and release its associated resources. This function
 *  also handles calling of the \c onFinished callback function and closing of
 *  the PhysicsFS file handle.
 *  \param stream the stream to destroy
 */
static void sound_DestroyStream(AUDIO_STREAM *stream)
{
    ALint buffer_count;
    ALuint *buffers;
    ALint error;

    // Stop the OpenAL source from playing
    alSourceStop(stream->source);
    error = sound_GetError();

    if (error != AL_NO_ERROR)
    {
        // FIXME: We should really handle these errors.
    }

    // Retrieve the amount of buffers which were processed
    alGetSourcei(stream->source, AL_BUFFERS_PROCESSED, &buffer_count);
    error = sound_GetError();
    if (error != AL_NO_ERROR)
    {
        /* FIXME: We're leaking memory and resources here when bailing
         * out. But not doing so could cause stack overflows as a
         * result of the below alloca() call (due to buffer_count not
         * being properly initialised.
         */
        debug(LOG_SOUND, "alGetSourcei(AL_BUFFERS_PROCESSED) failed; bailing out...");
        return;
    }

    // Detach all buffers and retrieve their ID numbers
    buffers = (ALuint *)alloca(buffer_count * sizeof(ALuint));
    alSourceUnqueueBuffers(stream->source, buffer_count, buffers);
    sound_GetError();

    // Destroy all of these buffers
    alDeleteBuffers(buffer_count, buffers);
    sound_GetError();

    // Destroy the OpenAL source
    alDeleteSources(1, &stream->source);
    sound_GetError();

    // Destroy the sound decoder
    sound_DestroyOggVorbisDecoder(stream->decoder);

    // Now close the file
    PHYSFS_close(stream->fileHandle);

    // Now call the finished callback
    if (stream->onFinished)
    {
        stream->onFinished(stream->user_data);
    }

    // Free the memory used by this stream
    free(stream);
}
void sound_FreeTrack( TRACK *psTrack )
{
#ifndef WZ_NOSOUND
	alDeleteBuffers(1, &psTrack->iBufferName);
	sound_GetError();
#endif
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_ResumeSample( AUDIO_SAMPLE *psSample )
{
#ifndef WZ_NOSOUND
	alSourcePlay( psSample->iSample );
	sound_GetError();
#endif
}
void sound_SetPlayerPos(Vector3f pos)
{
#ifndef WZ_NOSOUND
	alListener3f(AL_POSITION, pos.x, pos.y, pos.z);
	sound_GetError();
#endif
}
/** Retrieve the playing volume of the given stream.
 *
 *  @param stream the stream to retrieve the volume for.
 *
 *  @return a floating point value between 0.f and 1.f, representing this
 *          stream's volume.
 */
float sound_GetStreamVolume(const AUDIO_STREAM *stream)
{
    ALfloat volume;
    alGetSourcef(stream->source, AL_GAIN, &volume);
    sound_GetError();

    return volume;
}
/** Set the playing volume of the given stream.
 *
 *  @param stream the stream to change the volume for.
 *  @param volume a floating point value between 0.f and 1.f, to use as this
 *                @c stream's volume.
 */
void sound_SetStreamVolume(AUDIO_STREAM* stream, float volume)
{
	stream->volume = volume;
#if !defined(WZ_NOSOUND)
	alSourcef(stream->source, AL_GAIN, stream->volume);
	sound_GetError();
#endif
}
//*
// =======================================================================================================================
// Compute the sample's volume relative to AL_POSITION.
// =======================================================================================================================
//
void sound_SetObjectPosition(AUDIO_SAMPLE *psSample)
{
#ifndef WZ_NOSOUND
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// coordinates
	float	listenerX, listenerY, listenerZ, dX, dY, dZ;

	// calculation results
	float	distance, gain;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	// only set it when we have a valid sample
	if (!psSample)
	{
		return;
	}

	// compute distance
	alGetListener3f( AL_POSITION, &listenerX, &listenerY, &listenerZ );
	sound_GetError();
	dX = psSample->x  - listenerX; // distances on all axis
	dY = psSample->y - listenerY;
	dZ = psSample->z - listenerZ;
	distance = sqrtf(dX * dX + dY * dY + dZ * dZ); // Pythagorean theorem

	// compute gain
	gain = (1.0f - (distance * ATTENUATION_FACTOR)) * psSample->fVol * sfx3d_volume;
	// max volume
	if (gain > 1.0f)
	{
		gain = 1.0f;
	}
	if (gain < 0.0f)
	{
		// this sample can't be heard right now
		gain = 0.0f;
	}
	alSourcef( psSample->iSample, AL_GAIN, gain );

	// the alSource3i variant would be better, if it wouldn't provide linker errors however
	alSource3f( psSample->iSample, AL_POSITION, (float)psSample->x,(float)psSample->y,(float)psSample->z );
	sound_GetError();
#endif
	return;
}
/** Resumes playing of a stream that's paused by means of sound_PauseStream().
 *  \param stream the stream to resume playing for
 */
void sound_ResumeStream(AUDIO_STREAM *stream)
{
    ALint state;

    // To be sure we won't go mutilating this OpenAL source, check wether
    // it's paused first.
    alGetSourcei(stream->source, AL_SOURCE_STATE, &state);
    sound_GetError();

    if (state != AL_PAUSED)
    {
        return;
    }

    // Resume playing of this OpenAL source
    alSourcePlay(stream->source);
    sound_GetError();
}
/** Pauses playing of this stream until playing is resumed with
 *  sound_ResumeStream() or completely stopped with sound_StopStream().
 *  \param stream the stream to pause playing for
 */
void sound_PauseStream(AUDIO_STREAM *stream)
{
	ALint state;

	// To be sure we won't go mutilating this OpenAL source, check whether
	// it's playing first.
	alGetSourcei(stream->source, AL_SOURCE_STATE, &state);
	sound_GetError();

	if (state != AL_PLAYING)
	{
		return;
	}

	// Pause playing of this OpenAL source
	alSourcePause(stream->source);
	sound_GetError();
}
/** Stops the current stream from playing.
 *  \param stream the stream to stop
 *  \post The stopped stream will be destroyed on the next invocation of
 *        sound_UpdateStreams(). So calling this function will result in the
 *        \c onFinished callback being called and the \c stream pointer becoming
 *        invalid.
 */
void sound_StopStream(AUDIO_STREAM *stream)
{
    assert(stream != NULL);

    alGetError();	// clear error codes
    // Tell OpenAL to stop playing on the given source
    alSourceStop(stream->source);
    sound_GetError();
}
/**
 * Sets the player's orientation to use for sound
 * \param angle the angle in radians
 @NOTE the up vector is swapped because of qsound idiosyncrasies
 @FIXME we don't use qsound, but it still is in qsound 'format'...
*/
void sound_SetPlayerOrientation(float angle)
{
    const ALfloat ori[6] =
    {
        -sinf(angle), cosf(angle), 0.0f,	// forward (at) vector
        0.0f, 0.0f, 1.0f,					// up vector
    };
    alListenerfv(AL_ORIENTATION, ori);
    sound_GetError();
}
/**
 * Sets the player's orientation to use for sound
 * \param forward forward pointing vector
 * \param up      upward pointing vector
 */
void sound_SetPlayerOrientationVector(Vector3f forward, Vector3f up)
{
    const ALfloat ori[6] =
    {
        forward.x, forward.y, forward.z,
        up.x,      up.y,      up.z,
    };

    alListenerfv(AL_ORIENTATION, ori);
    sound_GetError();
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
bool sound_SampleIsFinished(AUDIO_SAMPLE *psSample)
{
    ALenum	state;

    alGetSourcei(psSample->iSample, AL_SOURCE_STATE, &state);
    sound_GetError(); // check for an error and clear the error state for later on in this function
    if (state == AL_PLAYING || state == AL_PAUSED)
    {
        return false;
    }

    if (psSample->iSample != (ALuint)AL_INVALID)
    {
        alDeleteSources(1, &(psSample->iSample));
        sound_GetError();
        psSample->iSample = AL_INVALID;
    }

    return true;
}
/** Retrieve the playing volume of the given stream.
 *
 *  @param stream the stream to retrieve the volume for.
 *
 *  @return a floating point value between 0.f and 1.f, representing this
 *          stream's volume.
 */
float sound_GetStreamVolume(const AUDIO_STREAM* stream)
{
#if !defined(WZ_NOSOUND)
	ALfloat volume;
	alGetSourcef(stream->source, AL_GAIN, &volume);
	sound_GetError();

	return volume;
#else
	return 1.f;
#endif
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_StopSample(AUDIO_SAMPLE *psSample)
{
    if (psSample->iSample == (ALuint)SAMPLE_NOT_ALLOCATED)
    {
        debug(LOG_SOUND, "sound_StopSample: sample number (%u) out of range, we probably have run out of available OpenAL sources", psSample->iSample);
        return;
    }
    alGetError();	// clear error codes
    // Tell OpenAL to stop playing the given sample
    alSourceStop(psSample->iSample);
    sound_GetError();
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
bool sound_QueueSamplePlaying(void)
{
    ALenum	state;

    if (!openal_initialized)
    {
        return false;
    }
    if (current_queue_sample == (ALuint)AL_INVALID)
    {
        return false;
    }

    alGetSourcei(current_queue_sample, AL_SOURCE_STATE, &state);

    // Check whether an error occurred while retrieving the state.
    // If one did, the state returned is useless. So instead of
    // using it return false.
    if (sound_GetError() != AL_NO_ERROR)
    {
        return false;
    }

    if (state == AL_PLAYING)
    {
        return true;
    }

    if (current_queue_sample != (ALuint)AL_INVALID)
    {
        SAMPLE_LIST *node = active_samples;
        SAMPLE_LIST *previous = NULL;

        // We need to remove it from the queue of actively played samples
        while (node != NULL)
        {
            if (node->curr->iSample == current_queue_sample)
            {
                sound_DestroyIteratedSample(&previous, &node);
                current_queue_sample = AL_INVALID;
                return false;
            }
            previous = node;
            if (node)
            {
                node = node->next;
            }
        }
        debug(LOG_ERROR, "Sample %u not deleted because it wasn't in the active queue!", current_queue_sample);
        current_queue_sample = AL_INVALID;
    }
    return false;
}
/** Update the given stream by making sure its buffers remain full
 *  \param stream the stream to update
 *  \return true when the stream is still playing, false when it has stopped
 */
static bool sound_UpdateStream(AUDIO_STREAM *stream)
{
    ALint state, buffer_count;

    alGetSourcei(stream->source, AL_SOURCE_STATE, &state);
    sound_GetError();

    if (state != AL_PLAYING && state != AL_PAUSED)
    {
        return false;
    }

    // Retrieve the amount of buffers which were processed and need refilling
    alGetSourcei(stream->source, AL_BUFFERS_PROCESSED, &buffer_count);
    sound_GetError();

    // Refill and reattach all buffers
    for (; buffer_count != 0; --buffer_count)
    {
        soundDataBuffer *soundBuffer;
        ALuint buffer;

        // Retrieve the buffer to work on
        alSourceUnqueueBuffers(stream->source, 1, &buffer);
        sound_GetError();

        // Decode some data to stuff in our buffer
        soundBuffer = sound_DecodeOggVorbis(stream->decoder, stream->bufferSize);

        // If we actually decoded some data
        if (soundBuffer && soundBuffer->size > 0)
        {
            // Determine PCM data format
            ALenum format = (soundBuffer->channelCount == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;

            // Insert the data into the buffer
            alBufferData(buffer, format, soundBuffer->data, soundBuffer->size, soundBuffer->frequency);
            sound_GetError();

            // Reattach the buffer to the source
            alSourceQueueBuffers(stream->source, 1, &buffer);
            sound_GetError();
        }
        else
        {
            // If no data has been decoded we're probably at the end of our
            // stream. So cleanup this buffer.

            // Then remove OpenAL's buffer
            alDeleteBuffers(1, &buffer);
            sound_GetError();
        }

        // Now remove the data buffer itself
        free(soundBuffer);
    }

    return true;
}
/** Checks if the stream is playing.
 *  \param stream the stream to check
 *  \post true if playing, false otherwise.
 *
 */
bool sound_isStreamPlaying(AUDIO_STREAM *stream)
{
    ALint state;

    if (stream)
    {
        alGetSourcei(stream->source, AL_SOURCE_STATE, &state);
        sound_GetError();
        if (state == AL_PLAYING)
        {
            return true;
        }
    }
    return false;
}
/** Deletes the given sample and updates the \c previous and \c current iterators
 *  \param previous iterator to the previous sample in the list
 *  \param sample iterator to the current sample in the list which you want to delete
 */
static void sound_DestroyIteratedSample(SAMPLE_LIST **previous, SAMPLE_LIST **sample)
{
    // If an OpenAL source is associated with this sample, release it
    if ((*sample)->curr->iSample != (ALuint)AL_INVALID)
    {
        alDeleteSources(1, &(*sample)->curr->iSample);
        sound_GetError();
    }

    // Do the cleanup of this sample
    sound_FinishedCallback((*sample)->curr);

    // Remove the sample from the list
    sound_RemoveSample(*previous, *sample);
    // Free it
    free(*sample);

    // Get a pointer to the next node, the previous pointer doesn't change
    *sample = (*previous != NULL) ? (*previous)->next : active_samples;
}
void sound_SetPlayerPos(Vector3f pos)
{
    alListener3f(AL_POSITION, pos.x, pos.y, pos.z);
    sound_GetError();
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
bool sound_InitLibrary(void)
{
    int err;
    const ALfloat listenerVel[3] = { 0.0, 0.0, 0.0 };
    const ALfloat listenerOri[6] = { 0.0, 0.0, 1.0, 0.0, 1.0, 0.0 };
    char buf[512];
    const ALCchar *deviceName;

#if 0
    // This code is disabled because enumerating devices apparently crashes PulseAudio on Fedora12

    /* Get the available devices and print them.
     * Devices are separated by NUL chars ('\0') and the list of devices is
     * terminated by two NUL chars.
     */
    deviceName = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
    while (deviceName != NULL && *deviceName != '\0')
    {
        debug(LOG_SOUND, "available OpenAL device(s) are: %s", deviceName);
        deviceName += strlen(deviceName) + 1;
    }
#endif

#ifdef WZ_OS_WIN
    /* HACK: Select the "software" OpenAL device on Windows because it
     *       provides 256 sound sources (unlike most Creative's default
     *       which provides only 16), causing our lack of source-management
     *       to be significantly less noticeable.
     */
    device = alcOpenDevice("Generic Software");

    // If the software device isn't available, fall back to default
    if (!device)
#endif
    {
        // Open default device
        device = alcOpenDevice(NULL);
    }

    if (!device)
    {
        debug(LOG_ERROR, "Couldn't open audio device.");
        return false;
    }

    // Print current device name and add it to dump info
    deviceName = alcGetString(device, ALC_DEVICE_SPECIFIER);
    debug(LOG_SOUND, "Current audio device: %s", deviceName);
    ssprintf(buf, "OpenAL Device Name: %s", deviceName);
    addDumpInfo(buf);

    context = alcCreateContext(device, NULL);		//NULL was contextAttributes
    if (!context)
    {
        debug(LOG_ERROR, "Couldn't open audio context.");
        return false;
    }

    alcMakeContextCurrent(context);

    err = sound_GetContextError(device);
    if (err != ALC_NO_ERROR)
    {
        debug(LOG_ERROR, "Couldn't initialize audio context: %s", alcGetString(device, err));
        return false;
    }

    // Dump Open AL device info (depends on context)
    // to the crash handler for the dump file and debug log
    ssprintf(buf, "OpenAL Vendor: %s", alGetString(AL_VENDOR));
    addDumpInfo(buf);
    debug(LOG_SOUND, "%s", buf);

    ssprintf(buf, "OpenAL Version: %s", alGetString(AL_VERSION));
    addDumpInfo(buf);
    debug(LOG_SOUND, "%s", buf);

    ssprintf(buf, "OpenAL Renderer: %s", alGetString(AL_RENDERER));
    addDumpInfo(buf);
    debug(LOG_SOUND, "%s", buf);

    ssprintf(buf, "OpenAL Extensions: %s", alGetString(AL_EXTENSIONS));
    addDumpInfo(buf);
    debug(LOG_SOUND, "%s", buf);

    openal_initialized = true;

    // Clear Error Codes
    alGetError();
    alcGetError(device);

    alListener3f(AL_POSITION, 0.f, 0.f, 0.f);
    alListenerfv(AL_VELOCITY, listenerVel);
    alListenerfv(AL_ORIENTATION, listenerOri);
    alDistanceModel(AL_NONE);
    sound_GetError();

    return true;
}
/** Plays the audio data from the given file
 *  \param fileHandle,volume,onFinished,user_data see sound_PlayStream()
 *  \param streamBufferSize the size to use for the decoded audio buffers
 *  \param buffer_count the amount of audio buffers to use
 *  \see sound_PlayStream() for details about the rest of the function
 *       parameters and other details.
 */
AUDIO_STREAM *sound_PlayStreamWithBuf(PHYSFS_file *fileHandle, float volume, void (*onFinished)(void *), void *user_data, size_t streamBufferSize, unsigned int buffer_count)
{
    AUDIO_STREAM *stream;
    ALuint       *buffers = (ALuint *)alloca(sizeof(ALuint) * buffer_count);
    ALint error;
    unsigned int i;

    if (!openal_initialized)
    {
        debug(LOG_WARNING, "OpenAL isn't initialized, not creating an audio stream");
        return NULL;
    }

    stream = (AUDIO_STREAM *)malloc(sizeof(AUDIO_STREAM));
    if (stream == NULL)
    {
        debug(LOG_FATAL, "sound_PlayStream: Out of memory");
        abort();
        return NULL;
    }

    // Clear error codes
    alGetError();

    // Retrieve an OpenAL sound source
    alGenSources(1, &(stream->source));

    error = sound_GetError();
    if (error != AL_NO_ERROR)
    {
        // Failed to create OpenAL sound source, so bail out...
        debug(LOG_SOUND, "alGenSources failed, most likely out of sound sources");
        free(stream);
        return NULL;
    }

    stream->fileHandle = fileHandle;

    stream->decoder = sound_CreateOggVorbisDecoder(stream->fileHandle, false);
    if (stream->decoder == NULL)
    {
        debug(LOG_ERROR, "sound_PlayStream: Failed to open audio file for decoding");
        free(stream);
        return NULL;
    }

    stream->volume = volume;
    stream->bufferSize = streamBufferSize;

    alSourcef(stream->source, AL_GAIN, stream->volume);

    // HACK: this is a workaround for a bug in the 64bit implementation of OpenAL on GNU/Linux
    // The AL_PITCH value really should be 1.0.
    alSourcef(stream->source, AL_PITCH, 1.001f);

    // Create some OpenAL buffers to store the decoded data in
    alGenBuffers(buffer_count, buffers);
    sound_GetError();

    // Fill some buffers with audio data
    for (i = 0; i < buffer_count; ++i)
    {
        // Decode some audio data
        soundDataBuffer *soundBuffer = sound_DecodeOggVorbis(stream->decoder, stream->bufferSize);

        // If we actually decoded some data
        if (soundBuffer && soundBuffer->size > 0)
        {
            // Determine PCM data format
            ALenum format = (soundBuffer->channelCount == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;

            // Copy the audio data into one of OpenAL's own buffers
            alBufferData(buffers[i], format, soundBuffer->data, soundBuffer->size, soundBuffer->frequency);
            sound_GetError();

            // Clean up our memory
            free(soundBuffer);
        }
        else
        {
            // If no data has been decoded we're probably at the end of our
            // stream. So cleanup the excess stuff here.

            // First remove the data buffer itself
            free(soundBuffer);

            // Then remove OpenAL's buffers
            alDeleteBuffers(buffer_count - i, &buffers[i]);
            sound_GetError();

            break;
        }
    }

    // Bail out if we didn't fill any buffers
    if (i == 0)
    {
        debug(LOG_ERROR, "Failed to fill buffers with decoded audio data!");

        // Destroy the decoder
        sound_DestroyOggVorbisDecoder(stream->decoder);

        // Destroy the OpenAL source
        alDeleteSources(1, &stream->source);

        // Free allocated memory
        free(stream);

        return NULL;
    }

    // Attach the OpenAL buffers to our OpenAL source
    // (i = the amount of buffers we worked on in the above for-loop)
    alSourceQueueBuffers(stream->source, i, buffers);
    sound_GetError();

    // Start playing the source
    alSourcePlay(stream->source);

    sound_GetError();

    // Set callback info
    stream->onFinished = onFinished;
    stream->user_data = user_data;

    // Prepend this stream to the linked list
    stream->next = active_streams;
    active_streams = stream;

    return stream;
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
bool sound_Play2DSample(TRACK *psTrack, AUDIO_SAMPLE *psSample, bool bQueued)
{
    ALfloat zero[3] = { 0.0, 0.0, 0.0 };
    ALfloat volume;
    ALint error;

    if (sfx_volume == 0.0)
    {
        return false;
    }
    volume = ((float)psTrack->iVol / 100.0f);		// each object can have OWN volume!
    psSample->fVol = volume;						// save computed volume
    volume *= sfx_volume;							// and now take into account the Users sound Prefs.

    // We can't hear it, so don't bother creating it.
    if (volume == 0.0f)
    {
        return false;
    }

    // Clear error codes
    alGetError();

    alGenSources(1, &(psSample->iSample));

    error = sound_GetError();
    if (error != AL_NO_ERROR)
    {
        /* FIXME: We run out of OpenAL sources very quickly, so we
         * should handle the case where we've ran out of them.
         * Currently we don't do this, causing some unpleasant side
         * effects, e.g. crashing...
         */
    }

    alSourcef(psSample->iSample, AL_PITCH, 1.0f);
    alSourcef(psSample->iSample, AL_GAIN, volume);
    alSourcefv(psSample->iSample, AL_POSITION, zero);
    alSourcefv(psSample->iSample, AL_VELOCITY, zero);
    alSourcei(psSample->iSample, AL_BUFFER, psTrack->iBufferName);
    alSourcei(psSample->iSample, AL_SOURCE_RELATIVE, AL_TRUE);
    alSourcei(psSample->iSample, AL_LOOPING, (sound_SetupChannel(psSample)) ? AL_TRUE : AL_FALSE);

    // NOTE: this is only useful for debugging.
#ifdef DEBUG
    psSample->is3d = false;
    psSample->isLooping = sound_TrackLooped(psSample->iTrack) ? AL_TRUE : AL_FALSE;
    memcpy(psSample->filename, psTrack->fileName, strlen(psTrack->fileName));
    psSample->filename[strlen(psTrack->fileName)] = '\0';
#endif
    // Clear error codes
    alGetError();

    alSourcePlay(psSample->iSample);
    sound_GetError();

    if (bQueued)
    {
        current_queue_sample = psSample->iSample;
    }
    else if (current_queue_sample == psSample->iSample)
    {
        current_queue_sample = -1;
    }

    return true;
}
void sound_FreeTrack(TRACK *psTrack)
{
    alDeleteBuffers(1, &psTrack->iBufferName);
    sound_GetError();
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_PauseSample(AUDIO_SAMPLE *psSample)
{
    alSourcePause(psSample->iSample);
    sound_GetError();
}
void sound_Update()
{
    SAMPLE_LIST *node = active_samples;
    SAMPLE_LIST *previous = NULL;
    ALCenum err;
    ALfloat gain;

    if (!openal_initialized)
    {
        return;
    }

    // Update all streaming audio
    sound_UpdateStreams();

    while (node != NULL)
    {
        ALenum state, err;

        // query what the gain is for this sample
        alGetSourcef(node->curr->iSample, AL_GAIN, &gain);
        err = sound_GetError();

        // if gain is 0, then we can't hear it, so we kill it.
        if (gain == 0.0f)
        {
            sound_DestroyIteratedSample(&previous, &node);
            continue;
        }

        //ASSERT(alIsSource(node->curr->iSample), "Not a valid source!");
        alGetSourcei(node->curr->iSample, AL_SOURCE_STATE, &state);

        // Check whether an error occurred while retrieving the state.
        // If one did, the state returned is useless. So instead of
        // using it continue with the next sample.
        err = sound_GetError();
        if (err != AL_NO_ERROR)
        {
            // Make sure to invoke the "finished" callback
            sound_FinishedCallback(node->curr);

            // Destroy this object and move to the next object
            sound_DestroyIteratedSample(&previous, &node);
            continue;
        }

        switch (state)
        {
        case AL_PLAYING:
        case AL_PAUSED:
            // If we haven't finished playing yet, just
            // continue with the next item in the list.

            // sound_SetObjectPosition(i->curr->iSample, i->curr->x, i->curr->y, i->curr->z);

            // Move to the next object
            previous = node;
            node = node->next;
            break;

        // NOTE: if it isn't playing | paused, then it is most likely either done
        // or a error.  In either case, we want to kill the sample in question.

        default:
            sound_DestroyIteratedSample(&previous, &node);
            break;
        }
    }

    // Reset the current error state
    alcGetError(device);

    alcProcessContext(context);

    err = sound_GetContextError(device);
    if (err != ALC_NO_ERROR)
    {
        debug(LOG_ERROR, "Error while processing audio context: %s", alGetString(err));
    }
}
/** Set the playing volume of the given stream.
 *
 *  @param stream the stream to change the volume for.
 *  @param volume a floating point value between 0.f and 1.f, to use as this
 *                @c stream's volume.
 */
void sound_SetStreamVolume(AUDIO_STREAM *stream, float volume)
{
    stream->volume = volume;
    alSourcef(stream->source, AL_GAIN, stream->volume);
    sound_GetError();
}
//*
// =======================================================================================================================
// =======================================================================================================================
//
void sound_ResumeSample(AUDIO_SAMPLE *psSample)
{
    alSourcePlay(psSample->iSample);
    sound_GetError();
}