/* 
 * Copyright (c) 2002-2010 LWJGL Project
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are 
 * met:
 * 
 * * Redistributions of source code must retain the above copyright 
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'LWJGL' nor the names of 
 *   its contributors may be used to endorse or promote products derived 
 *   from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.lwjgl.test.openal;

import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.AL11;
import org.lwjgl.openal.ALC10;
import org.lwjgl.openal.ALCcontext;
import org.lwjgl.openal.ALCdevice;
import org.lwjgl.openal.EFX10;
import org.lwjgl.openal.EFXUtil;
import org.lwjgl.util.WaveData;

/**
 * Class with a few examples testing and demonstrating the use of the OpenAL extension ALC_EXT_EFX.
 * <p>
 * This class is not compatible with the LWJGL debug build (lwjgl-debug.jar), as the debug build
 * throws exceptions instead of alGetError checks. The redundant exception handling code was not
 * added in order to keep these examples simple.
 *
 * @author Ciardhubh <ciardhubh[at]ciardhubh.de>
 * @version $Revision$
 * $Id$
 */
public final class EFX10Test {

    public static void main(final String[] args) throws Exception {
        silentTests();
        playbackTest();
        efxUtilTest();
    }

    /**
     * Loads OpenAL and makes sure ALC_EXT_EFX is supported.
     */
    private static void setupEfx() throws Exception {
        // Load and create OpenAL
        if (!AL.isCreated()) {
            AL.create();
        }
        // Query for Effect Extension
        if (!ALC10.alcIsExtensionPresent(AL.getDevice(), EFX10.ALC_EXT_EFX_NAME)) {
            throw new Exception("No ALC_EXT_EFX supported by driver.");
        }
        System.out.println("ALC_EXT_EFX found.");
    }

    /**
     * Runs a series of API calls similar to the tutorials in the Effects Extension Guide of the
     * OpenAL SDK. Nothing is played in this method.
     */
    private static void silentTests() throws Exception {
        setupEfx();

        final ALCdevice device = AL.getDevice();

        // Create context (only necessary if LWJGL context isn't sufficient, done as example)
        final IntBuffer contextAttribList = BufferUtils.createIntBuffer(8);
        contextAttribList.put(ALC10.ALC_FREQUENCY);
        contextAttribList.put(44100);
        contextAttribList.put(ALC10.ALC_REFRESH);
        contextAttribList.put(60);
        contextAttribList.put(ALC10.ALC_SYNC);
        contextAttribList.put(ALC10.ALC_FALSE);
        contextAttribList.rewind();
        // ALC_MAX_AUXILIARY_SENDS won't go above compile-time max. Set to compile-time max if
        // greater.
        contextAttribList.put(EFX10.ALC_MAX_AUXILIARY_SENDS);
        contextAttribList.put(2);
        final ALCcontext newContext = ALC10.alcCreateContext(device, contextAttribList);
        if (newContext == null) {
            throw new Exception("Failed to create context.");
        }
        final int contextCurResult = ALC10.alcMakeContextCurrent(newContext);
        if (contextCurResult == ALC10.ALC_FALSE) {
            throw new Exception("Failed to make context current.");
        }

        // Query EFX ALC values
        System.out.println("AL_VERSION: " + AL10.alGetString(AL10.AL_VERSION));
        final IntBuffer buff = BufferUtils.createIntBuffer(1);
        ALC10.alcGetInteger(device, EFX10.ALC_EFX_MAJOR_VERSION, buff);
        System.out.println("ALC_EFX_MAJOR_VERSION: " + buff.get(0));
        ALC10.alcGetInteger(device, EFX10.ALC_EFX_MINOR_VERSION, buff);
        System.out.println("ALC_EFX_MINOR_VERSION: " + buff.get(0));
        ALC10.alcGetInteger(device, EFX10.ALC_MAX_AUXILIARY_SENDS, buff);
        final int maxAuxSends = buff.get(0);
        System.out.println("ALC_MAX_AUXILIARY_SENDS: " + maxAuxSends);


        // Try to create 4 Auxiliary Effect Slots
        int numAuxSlots = 0;
        final int[] auxEffectSlots = new int[4]; // try more to test
        AL10.alGetError();
        for (numAuxSlots = 0; numAuxSlots < 4; numAuxSlots++) {
            auxEffectSlots[numAuxSlots] = EFX10.alGenAuxiliaryEffectSlots();
            if (AL10.alGetError() != AL10.AL_NO_ERROR) {
                break;
            }
        }
        System.out.println("Created " + numAuxSlots + " aux effect slots.");

        // Try to create 2 Effects
        int numEffects = 0;
        final int[] effects = new int[2];
        for (numEffects = 0; numEffects < 2; numEffects++) {
            effects[numEffects] = EFX10.alGenEffects();
            if (AL10.alGetError() != AL10.AL_NO_ERROR) {
                break;
            }
        }
        System.out.println("Created " + numEffects + " effects.");

        // Set first Effect Type to Reverb and change Decay Time
        AL10.alGetError();
        if (EFX10.alIsEffect(effects[0])) {
            EFX10.alEffecti(effects[0], EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_REVERB);
            if (AL10.alGetError() != AL10.AL_NO_ERROR) {
                System.out.println("Reverb effect not supported.");
            } else {
                EFX10.alEffectf(effects[0], EFX10.AL_REVERB_DECAY_TIME, 5.0f);
                System.out.println("Reverb effect created.");
            }
        } else {
            throw new Exception("First effect not a valid effect.");
        }

        // Set second Effect Type to Flanger and change Phase
        AL10.alGetError();
        if (EFX10.alIsEffect(effects[1])) {
            EFX10.alEffecti(effects[1], EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_FLANGER);
            if (AL10.alGetError() != AL10.AL_NO_ERROR) {
                System.out.println("Flanger effect not support.");
            } else {
                EFX10.alEffecti(effects[1], EFX10.AL_FLANGER_PHASE, 180);
                System.out.println("Flanger effect created.");
            }
        } else {
            throw new Exception("Second effect not a valid effect.");
        }

        // Try to create a Filter
        AL10.alGetError();
        final int filter = EFX10.alGenFilters();
        if (AL10.alGetError() != AL10.AL_NO_ERROR) {
            throw new Exception("Failed to create filter.");
        }
        System.out.println("Generated a filter.");
        if (EFX10.alIsFilter(filter)) {
            // Set Filter type to Low-Pass and set parameters
            EFX10.alFilteri(filter, EFX10.AL_FILTER_TYPE, EFX10.AL_FILTER_LOWPASS);
            if (AL10.alGetError() != AL10.AL_NO_ERROR) {
                System.out.println("Low pass filter not supported.");
            } else {
                EFX10.alFilterf(filter, EFX10.AL_LOWPASS_GAIN, 0.5f);
                EFX10.alFilterf(filter, EFX10.AL_LOWPASS_GAINHF, 0.5f);
                System.out.println("Low pass filter created.");
            }
        }

        // Attach Effect to Auxiliary Effect Slot
        AL10.alGetError();
        EFX10.alAuxiliaryEffectSloti(auxEffectSlots[0], EFX10.AL_EFFECTSLOT_EFFECT, effects[0]);
        if (AL10.alGetError() != AL10.AL_NO_ERROR) {
            throw new Exception("Failed to attach effect to aux effect slot.");
        }
        System.out.println("Successfully loaded effect into effect slot.");

        // Configure Source Auxiliary Effect Slot Sends
        final int source = AL10.alGenSources();
        // Set Source Send 0 to feed auxEffectSlots[0] without filtering
        AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, auxEffectSlots[0], 0,
                EFX10.AL_FILTER_NULL);
        if (AL10.alGetError() != AL10.AL_NO_ERROR) {
            throw new Exception("Failed to configure Source Send 0");
        }
        System.out.println("Linked aux effect slot to soutce slot 0");
        // Set Source Send 1 to feed uiEffectSlot[1] with filter filter
        AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, auxEffectSlots[1], 1, filter);
        if (AL10.alGetError() != AL10.AL_NO_ERROR) {
            // e.g. if only 1 send per source is available
            throw new Exception("Failed to configure Source Send 1");
        }
        System.out.println("Linked aux effect slot to soutce slot 1");
        // Disable Send 0
        AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, EFX10.AL_EFFECTSLOT_NULL, 0,
                EFX10.AL_FILTER_NULL);
        if (AL10.alGetError() != AL10.AL_NO_ERROR) {
            throw new Exception("Failed to disable Source Send 0");
        }
        System.out.println("Disabled source send 0");
        // Disable Send 1
        AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, EFX10.AL_EFFECTSLOT_NULL, 1,
                EFX10.AL_FILTER_NULL);
        if (AL10.alGetError() != AL10.AL_NO_ERROR) {
            throw new Exception("Failed to disable Source Send 1");
        }
        System.out.println("Disabled source send 1");


        // Filter 'source', a generated Source
        AL10.alSourcei(source, EFX10.AL_DIRECT_FILTER, filter);
        if (AL10.alGetError() == AL10.AL_NO_ERROR) {
            {
                System.out.println("Successfully applied a direct path filter");
                // Remove filter from 'source'
                AL10.alSourcei(source, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL);
                if (AL10.alGetError() == AL10.AL_NO_ERROR) {
                    System.out.println("Successfully removed direct filter");
                }
            }
            // Filter the Source send 0 from 'source' to Auxiliary Effect Slot auxEffectSlot[0]
            // using Filter uiFilter[0]
            AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, auxEffectSlots[0], 0, filter);
            if (AL10.alGetError() == AL10.AL_NO_ERROR) {
                {
                    System.out.println("Successfully applied aux send filter");
                    // Remove Filter from Source Auxiliary Send
                    AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, auxEffectSlots[0], 0,
                            EFX10.AL_FILTER_NULL);
                    if (AL10.alGetError() == AL10.AL_NO_ERROR) {
                        System.out.println("Successfully removed filter");
                    }
                }
            }
        }

        // Set Source Cone Outer Gain HF value
        AL10.alSourcef(source, EFX10.AL_CONE_OUTER_GAINHF, 0.5f);
        if (AL10.alGetError() == AL10.AL_NO_ERROR) {
            System.out.println("Successfully set cone outside gain filter");
        }

        // Set distance units to be in feet
        AL10.alListenerf(EFX10.AL_METERS_PER_UNIT, 0.3f);
        if (AL10.alGetError() == AL10.AL_NO_ERROR) {
            System.out.println("Successfully set distance units");
        }

        // Cleanup
        final IntBuffer auxEffectSlotsBuf = (IntBuffer) BufferUtils.createIntBuffer(
                auxEffectSlots.length).put(auxEffectSlots).rewind();
        EFX10.alDeleteAuxiliaryEffectSlots(auxEffectSlotsBuf);
        final IntBuffer effectsBuf = (IntBuffer) BufferUtils.createIntBuffer(
                effects.length).put(effects).rewind();
        EFX10.alDeleteEffects(effectsBuf);
        EFX10.alDeleteFilters(filter);
        AL.destroy();
    }

    /**
     * Plays a sound with various effects applied to it.
     */
    private static void playbackTest() throws Exception {
        setupEfx();

        // Create a source and buffer audio data
        final int source = AL10.alGenSources();
        final int buffer = AL10.alGenBuffers();
        WaveData waveFile = WaveData.create("Footsteps.wav");
        if (waveFile == null) {
            System.out.println("Failed to load Footsteps.wav! Skipping playback test.");
            AL.destroy();
            return;
        }
        AL10.alBufferData(buffer, waveFile.format, waveFile.data, waveFile.samplerate);
        waveFile.dispose();
        AL10.alSourcei(source, AL10.AL_BUFFER, buffer);
        AL10.alSourcei(source, AL10.AL_LOOPING, AL10.AL_TRUE);

        System.out.println("Playing sound unaffected by EFX ...");
        AL10.alSourcePlay(source);
        Thread.sleep(7500);

        // Add reverb effect
        final int effectSlot = EFX10.alGenAuxiliaryEffectSlots();
        final int reverbEffect = EFX10.alGenEffects();
        EFX10.alEffecti(reverbEffect, EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_REVERB);
        EFX10.alEffectf(reverbEffect, EFX10.AL_REVERB_DECAY_TIME, 5.0f);
        EFX10.alAuxiliaryEffectSloti(effectSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbEffect);
        AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, effectSlot, 0,
                EFX10.AL_FILTER_NULL);

        System.out.println("Playing sound with reverb ...");
        AL10.alSourcePlay(source);
        Thread.sleep(7500);

        // Add low-pass filter directly to source
        final int filter = EFX10.alGenFilters();
        EFX10.alFilteri(filter, EFX10.AL_FILTER_TYPE, EFX10.AL_FILTER_LOWPASS);
        EFX10.alFilterf(filter, EFX10.AL_LOWPASS_GAIN, 0.5f);
        EFX10.alFilterf(filter, EFX10.AL_LOWPASS_GAINHF, 0.5f);
        AL10.alSourcei(source, EFX10.AL_DIRECT_FILTER, filter);

        System.out.println("Playing sound with reverb and direct low pass filter ...");
        AL10.alSourcePlay(source);
        Thread.sleep(7500);
        AL10.alSourcei(source, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL);

        // Add low-pass filter to source send
        //AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, effectSlot, 0, filter);
        //
        //System.out.println("Playing sound with reverb and aux send low pass filter ...");
        //AL10.alSourcePlay(source);
        //Thread.sleep(7500);

        // Cleanup
        AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, EFX10.AL_EFFECTSLOT_NULL, 0,
                EFX10.AL_FILTER_NULL);
        EFX10.alAuxiliaryEffectSloti(effectSlot, EFX10.AL_EFFECTSLOT_EFFECT, EFX10.AL_EFFECT_NULL);
        EFX10.alDeleteEffects(reverbEffect);
        EFX10.alDeleteFilters(filter);

        // Echo effect
        final int echoEffect = EFX10.alGenEffects();
        EFX10.alEffecti(echoEffect, EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_ECHO);
        //EFX10.alEffectf(echoEffect, EFX10.AL_ECHO_DELAY, 5.0f);
        EFX10.alAuxiliaryEffectSloti(effectSlot, EFX10.AL_EFFECTSLOT_EFFECT, echoEffect);
        AL11.alSource3i(source, EFX10.AL_AUXILIARY_SEND_FILTER, effectSlot, 0,
                EFX10.AL_FILTER_NULL);

        System.out.println("Playing sound with echo effect ...");
        AL10.alSourcePlay(source);
        Thread.sleep(7500);

        AL.destroy();
    }

    /**
     * Checks OpenAL for every EFX 1.0 effect and filter and prints the result to the console.
     */
    private static void efxUtilTest() throws Exception {
        setupEfx();

        System.out.println();
        System.out.println("Checking supported effects ...");
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_NULL)) {
            System.out.println("AL_EFFECT_NULL is supported.");
        } else {
            System.out.println("AL_EFFECT_NULL is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_EAXREVERB)) {
            System.out.println("AL_EFFECT_EAXREVERB is supported.");
        } else {
            System.out.println("AL_EFFECT_EAXREVERB is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_REVERB)) {
            System.out.println("AL_EFFECT_REVERB is supported.");
        } else {
            System.out.println("AL_EFFECT_REVERB is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_CHORUS)) {
            System.out.println("AL_EFFECT_CHORUS is supported.");
        } else {
            System.out.println("AL_EFFECT_CHORUS is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_DISTORTION)) {
            System.out.println("AL_EFFECT_DISTORTION is supported.");
        } else {
            System.out.println("AL_EFFECT_DISTORTION is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_ECHO)) {
            System.out.println("AL_EFFECT_ECHO is supported.");
        } else {
            System.out.println("AL_EFFECT_ECHO is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_FLANGER)) {
            System.out.println("AL_EFFECT_FLANGER is supported.");
        } else {
            System.out.println("AL_EFFECT_FLANGER is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_FREQUENCY_SHIFTER)) {
            System.out.println("AL_EFFECT_FREQUENCY_SHIFTER is supported.");
        } else {
            System.out.println("AL_EFFECT_FREQUENCY_SHIFTER is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_VOCAL_MORPHER)) {
            System.out.println("AL_EFFECT_VOCAL_MORPHER is supported.");
        } else {
            System.out.println("AL_EFFECT_VOCAL_MORPHER is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_PITCH_SHIFTER)) {
            System.out.println("AL_EFFECT_PITCH_SHIFTER is supported.");
        } else {
            System.out.println("AL_EFFECT_PITCH_SHIFTER is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_RING_MODULATOR)) {
            System.out.println("AL_EFFECT_RING_MODULATOR is supported.");
        } else {
            System.out.println("AL_EFFECT_RING_MODULATOR is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_AUTOWAH)) {
            System.out.println("AL_EFFECT_AUTOWAH is supported.");
        } else {
            System.out.println("AL_EFFECT_AUTOWAH is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_COMPRESSOR)) {
            System.out.println("AL_EFFECT_COMPRESSOR is supported.");
        } else {
            System.out.println("AL_EFFECT_COMPRESSOR is NOT supported.");
        }
        if (EFXUtil.isEffectSupported(EFX10.AL_EFFECT_EQUALIZER)) {
            System.out.println("AL_EFFECT_EQUALIZER is supported.");
        } else {
            System.out.println("AL_EFFECT_EQUALIZER is NOT supported.");
        }

        System.out.println();
        System.out.println("Checking supported filters ...");
        if (EFXUtil.isFilterSupported(EFX10.AL_FILTER_NULL)) {
            System.out.println("AL_FILTER_NULL is supported.");
        } else {
            System.out.println("AL_FILTER_NULL is NOT supported.");
        }
        if (EFXUtil.isFilterSupported(EFX10.AL_FILTER_LOWPASS)) {
            System.out.println("AL_FILTER_LOWPASS is supported.");
        } else {
            System.out.println("AL_FILTER_LOWPASS is NOT supported.");
        }
        if (EFXUtil.isFilterSupported(EFX10.AL_FILTER_HIGHPASS)) {
            System.out.println("AL_FILTER_HIGHPASS is supported.");
        } else {
            System.out.println("AL_FILTER_HIGHPASS is NOT supported.");
        }
        if (EFXUtil.isFilterSupported(EFX10.AL_FILTER_BANDPASS)) {
            System.out.println("AL_FILTER_BANDPASS is supported.");
        } else {
            System.out.println("AL_FILTER_BANDPASS is NOT supported.");
        }
    }
}
