#include "sound.hpp"

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>

#include <portaudio.h>

#include "util.hpp"

// for dumping sound
#include    <sndfile.h>

#include <pthread.h>

namespace sound {

    //struct Slot {
    //    bool used;
    //    bool usable;
    //    bool deleteOnCleanup;
    //    SoundHandle *handle;
    //};

    struct SoundContext {
        PaStream *stream;

        // this locks access to the context
        pthread_mutex_t mutex;

        unsigned int framesPerBuffer;
        unsigned int sampleRate;

        size_t sound_uid_counter;

        // constant
        size_t maxNumSounds;

        // index of highest sound or -1, if there are no sounds.
        // so 0 means no sounds, > 0 could mean different things depending on
        // empty sounds.
        volatile ssize_t highestSoundIndex;

        // pointer to array of sound handles that has this number of active sounds
        //Slot *soundSlots;
        SoundHandle **sounds;

        bool dumping;
        const char *dump_filename;
        SNDFILE *outfile;
        SF_INFO  sfinfo;

        bool init;
    };

    // singleton so no arguments must be supplied for play() functions etc.
    static SoundContext context;

    static double startup_time = -1.0;
    static double global_time = -1.0;;

    static double timestamp_double(void)
    {
        struct timeval now;
        gettimeofday(&now, NULL);

        //printf("%d %d\n", (int) now.tv_sec, (int) now.tv_usec);

        double t = now.tv_sec;
        t += now.tv_usec / 1000000.0f;
        //printf("%f\n", t);
        return t;
    }

    const char *stringFromEnvelope(enum EnvelopeState envelope)
    {
        switch(envelope) {
            case New:   return "New";
            case Wait:  return "Wait";
            case Rise:  return "Rise";
            case BeginOvershoot: return "BeginOvershoot";
            case EndOvershoot: return   "EndOvershoot";
            case Decay: return "Decay";
            case Hold:  return "Hold";
            case Done:  return "Done";
            default: assert(false); return "<error>";
        }
    }

    static void checkEnvelopeState(SoundHandle *handle, enum EnvelopeState next)
    {
        if (handle == NULL) {
            return;
        }

        if (handle->envelope != next) {
#if 1
            printf("[sound] global time %4.3f #%zd (%s) t=%f %5s -> %5s\n",
                global_time,
                handle->uid,
                handle->name,
                handle->time,
                stringFromEnvelope(handle->envelope),
                stringFromEnvelope(next));
#endif

            handle->envelope = next;
        }
    }

    void stopDumpingWav(void)
    {
        if (!context.init || !context.dumping) {
            return;
        }

        int err = sf_close(context.outfile);
        context.outfile = NULL;
        context.dumping = false;

       if (err < 0) {
           fprintf(stderr, "[sound] warning: stop dumping wav returned error: %d\n", err);
       } else {
           printf("[sound] sucessfully stopped dumping to file %s\n", context.dump_filename);
       }
    }

    static bool dumpSamples(float *samples, size_t numSamples)
    {
        // TODO: dumping in mono?

        if (!context.init || !context.dumping) {
            fprintf(stderr, "[sound] warning: can't dump samples: context not initialized or not currently dumping!\n");
            return false;
        }

        sf_writef_float(context.outfile, samples, numSamples);
        sf_write_sync(context.outfile); // force writing

        return true;
    }

    bool startDumpingWav(const char *filename)
    {
        if (!context.init) {
            return false;
        }

        if (context.dumping) {
            printf("[sound] warning: already dumping to %s!\n", context.dump_filename);
            return false;
        }

        context.sfinfo.channels = 2;
        context.sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
        context.sfinfo.samplerate = context.sampleRate;

        context.outfile = sf_open(filename, SFM_WRITE, &context.sfinfo);

        if (context.outfile == NULL) {
            fprintf(stderr, "[sound] Unable to dump to output file %s.\n", filename);
            sf_perror(NULL);
            return false;
        };

        printf("[sound] start dumping wav to file %s\n", filename);
        context.dump_filename = filename;
        context.dumping = true;
        return true;
    }

    static float amplitudeForTime(SoundHandle *handle)
    {
        //float amplitude = handle->amplitude;
        float amplitude = 1.0;

        float t = handle->time;
        if (t < handle->_riseTime) {
            // linear increase
            float a = t / handle->_riseTime;

            if (handle->_relativeOvershootTime == 0.0) {
                checkEnvelopeState(handle, Rise);

            } else {
                //
                //           |--overshoot-up-| |-overshoot-down-|
                //           |-------------overshoot------------|
                // |-----------------rise-time -----------------|

                // make it raise to full level earlier
                const float overshoot = 2.0*handle->_relativeOvershootTime;

                a *= 1.0+overshoot;

                // compensate the overshoot during last r/2 time to normal level
                // (1.0) by inversing slope
                if (a < 1.0) {
                    checkEnvelopeState(handle, Rise);

                } else if (a < 1.0 + (overshoot/2.0)) {
                    checkEnvelopeState(handle, BeginOvershoot);

                } else {
                    a = 2.0 + overshoot - a;
                    checkEnvelopeState(handle, EndOvershoot);

                }
            }

            amplitude *= a;
            //printf("amplitude rising: %f\n", a);

        } else {
            t -= handle->_riseTime;
            if (t < handle->_holdTime) {
                // stays constant during this time
                checkEnvelopeState(handle, Hold);

            } else {
                t -= handle->_holdTime;
                if (t < handle->_decayTime) {
                    // linear decrease
                    float a = 1.0 - (t / handle->_decayTime);
                    //printf("amplitude decaying : %f\n", a);
                    amplitude *= a;
                    checkEnvelopeState(handle, Decay);

                } else {
                    bool switchToDone = false;
                    t -= handle->_decayTime;
                    if (t < handle->_waitTime) {
                        // still waiting
                        checkEnvelopeState(handle, Wait);

                    } else if (handle->looping && (handle->_loopCount == -1 || handle->_loopCount > 0)) {
                        // waited long enough to restart
                        // TODO: dont waste time! does stuff stay synchronized
                        // over a long period of time?
                        //handle->time = 0.0;
                        handle->time -= handle->_riseTime + handle->_waitTime + handle->_holdTime + handle->_decayTime;

                        if (handle->_loopCount != -1) {
                            handle->_loopCount--;
                            //printf("decrement loopCount to %d\n", handle->_loopCount);
                            if (handle->_loopCount == 0) {
                                switchToDone = true;
                            }
                        }
                    } else {
                        switchToDone = true;
                    }

                    if (switchToDone) {
                        // zero
                        checkEnvelopeState(handle, Done);
                        handle->_done = true;
                        return 0.0;
                    }
                    amplitude = 0.0;
                }
            }
        }

        //int phase = handle->envelope;
        //printf("%f, %d, %f\n", handle->time, phase, amplitude);

        if (handle->minAmplitude != 0.0) {
            amplitude = handle->minAmplitude + amplitude/(1.0-handle->minAmplitude);
        }

        amplitude *= handle->amplitude;

        return amplitude;
    }

    // TODO: support stereo fully

    // args:
    // n: n-th last history value
    static float historyValue(SoundHandle *handle, size_t n)
    {
        if (handle == NULL) {
            return 0.0;
        }
        if (handle->history == NULL) {
            return 0.0;
        }
        if (n >= handle->numHistorySamples) {
            // TODO clear history when looping?
            // or add zero samples?
            printf("[sound] warning: try to access more history values than saved.\n");
            return 0.0;
        }

        return handle->history[2 * ((handle->lastHistorySample+n) % handle->numHistorySamples)];
    }

    void saveHistoryValue(SoundHandle *handle, float v)
    {
        if (handle == NULL || handle->history == NULL) {
            return;
        }

        handle->history[2*handle->lastHistorySample] = v;
        handle->lastHistorySample = (handle->lastHistorySample+1) % handle->numHistorySamples;
    }

    float sumHistory(SoundHandle *handle, float initialWeight, float v)
    {
        float result = initialWeight * v;
        float totalWeight = initialWeight;

        size_t i;
        for (i=0; i<handle->numHistorySamples; i++) {
            float weight = 1.0f / (float) (1+i);
            //float weight = 1.0f;
            result += weight * historyValue(handle, i);
            totalWeight += weight;
        }

        result /= totalWeight;
        return result;
    }

    // return false on no success
    bool tryLock()
    {
        int ret = pthread_mutex_trylock(&context.mutex);
        return ret == 0;
    }

    // wait if does not lock
    void lock()
    {
        pthread_mutex_lock(&context.mutex);
    }

    void unlock()
    {
        pthread_mutex_unlock(&context.mutex);
    }


    // return false if playing done.
    static bool advanceSound(SoundHandle *handle, float *out, unsigned int framesPerBuffer)
    {
        assert(handle != NULL);
        assert(framesPerBuffer > 0);
        assert(out != NULL);

        if (handle == NULL || framesPerBuffer == 0 || out == NULL) {
            return false;
        }

        // TODO: add length to sounds

        // TODO: find out the period, write just one period and then copy the
        // values.
        //float dt = handle->freq / context.sampleRate;

        // TODO: divide into three parts for envelops
        // because they are linear in the phases so interpolating between that
        // should be made easier

        // advance parameters for testing
        switch(handle->_type) {
            //float div = 10.0;
            //float t = (((int)(100*handle->time/div)) % 101) / 100.0;
            //handle->leakage = 0.5+0.5*sin(2.0*M_PI*t);
            default:
                break;
        }

        const float dt = 1.0 / context.sampleRate;
        const float ds = 2.0 * M_PI * handle->freq * dt;

        // XXX too long range when becomes done
        unsigned int i;
        for (i=0; i<framesPerBuffer; i++) {
            handle->time += dt;

            // TODO: save this state too?
            if (handle->time < 0.0) {
                checkEnvelopeState(handle, Wait);
                // this sound can not yet be heard
                continue;
            }

            // TODO: add phase as argument.

            float v;
            switch (handle->_type) {
            case SoundFrequency:
                handle->state += ds;
                v = sin(handle->state);
                break;

            case SoundWhiteNoise:
                v = util::randf_m1_1();
                break;

            case SoundPinkNoise:
                //v = (historyValue(handle, 0) + Randf_m1_1()) / 2.0f;
                //saveHistoryValue(handle, v);
                v = (handle->state + util::randf_m1_1()) / 2.0f;
                handle->state = v;
                break;

            case SoundBrownianNoise:
                // this is created by integrating white noise.

                {
                    float white = util::randf_m1_1();
                    float p = handle->leakage * handle->state;
                    float s = handle->scaling * white;
                    float z = p + s;
                    if (handle->skipDirectionIfClamping && fabsf(z) >= 1.0f) {
                        z = p - s;
                    }
                    handle->state = z;
                    v = z;
                }
                //v = sumHistory(handle, handle->freq, randf_m1_1());
                //v = sumHistory(handle, 0.1, randf_m1_1());
                //v = 0.2*historyValue(handle, 1) + 0.3*historyValue(handle, 0) + 0.5*randf_m1_1();
                //v = (historyValue(handle, 1) + historyValue(handle, 0) + randf_m1_1()) / 3.0;
                //saveHistoryValue(handle, v);

#if 0
                {
                    float a = 0.8;
                    v = a*handle->state + (1.0-a)*randf_m1_1();
                    handle->state = v;
                }
#endif
                break;

            default:
                printf("[sound] warning: unimplemented sound type for #%zd: %d\n", handle->uid, handle->_type);
                continue;
            }

            if (handle->_useEnvelope) {
                v *= amplitudeForTime(handle);
                if (handle->_done) {
                    return false;
                }
            } else {
                v *= handle->amplitude;
            }

            // add values from this sound te to current sound level
            *out++ += v;
            *out++ += v;
        }

        return true;
    }

    /* This routine will be called by the PortAudio engine when audio is needed.
       It may called at interrupt level on some machines so don't do anything
       that could mess up the system like calling malloc() or free().
    */
    static int sound_callback(const void *inputBuffer,
                                    void *outputBuffer,
                           unsigned long  framesPerBuffer,
          const PaStreamCallbackTimeInfo *timeInfo,
                   PaStreamCallbackFlags  statusFlags,
                                    void *userData)
    {
        (void) inputBuffer;
        (void) timeInfo;
        (void) statusFlags;
        (void) userData;

        // update time
        double now = timestamp_double();

        // should not happen. just in case.
        global_time = now - startup_time;

        //printf("global time raw: %f\n", global_time);
        if (global_time < 0.0) {
            global_time = 0.0;
        }

        // we have 32 bit float format so cast here
        float *out = (float *) outputBuffer;

        {
            // set all to zero
            unsigned int i;
            for (i=0; i<framesPerBuffer; i++) {
                *out++ = 0.0f;
                *out++ = 0.0f;
            }
        }

        // reset pointer
        out = (float *) outputBuffer;

        // advance all sounds
        ssize_t i;
        for (i=0; i<context.highestSoundIndex; i++) {
            SoundHandle *handle = context.sounds[i];
            if (handle == NULL) {
                continue;
            }
            if (handle->deleteFlag) {
                continue;
            }
            if (handle->isDeleted) {
                continue;
            }

            if (!handle->_done) {
#if 0
                    printf("play sound 0x%x with loop=%d, sample=%d, freq=%f amplitude=%f\n",
                            (void*) handle,
                            handle->looping,
                            (unsigned int) handle->_currentSample, handle->freq, handle->amplitude);
#endif

                if (!advanceSound(handle, out, framesPerBuffer)) {
                    handle->_done = true;
                }
            }

            // TODO: when we should not free in the loop, where else?
            if (handle->_done && !handle->deleteFlag) {
                //printf("[sound] %f freeing sound #%d handle 0x%p\n", global_time, handle->uid, handle);
                printf("[sound] %f mark sound #%zd handle 0x%p for deletion\n", global_time, handle->uid, handle);
                //context.sounds[i] = NULL;
                handle->deleteFlag = true;
                //if (!handle->keep_when_done) {
                //    deleteSound(handle);
                //}
            }
        }

        // make sure all -1.0 >= x <= 1
#if 0
        out = outputBuffer;
        for (i=0; i<2*framesPerBuffer; i++) {
            if (*out < -1.0) { *out = -1.0; }
            else if (*out > 1.0) { *out = 1.0; }
        }
#endif

        if (context.dumping) {
            dumpSamples((float *) outputBuffer, framesPerBuffer);
        }

        // TODO: check!
#if 0
        // apply post-processing
        out = outputBuffer;
        // advance all sounds
        for (i=0; i<context.highestSoundIndex; i++) {
            float v = *out;
            v = pow(v, 2.0);
            *out++ = v;

            v = *out;
            v = pow(v, 2.0);
            *out++ = v;
        }
#endif

        return 0;
    }

    bool initSound(void)
    {
        startup_time = timestamp_double();
        global_time  = 0.0;

        // TODO: make all customizable too
        context.init = false; // for now, set on succes to true
        context.dumping = false;
        context.dump_filename = NULL;
        context.sampleRate      = 44800;
        context.framesPerBuffer = 256;
        //context.framesPerBuffer = 1024;
        //context.framesPerBuffer = 100;
        //context.framesPerBuffer = 44100;

        // TODO: add max number of simultanous playing sounds, that's more important.

        context.sound_uid_counter = 0;
        context.maxNumSounds      = 10;
        context.highestSoundIndex = -1;
        //context.soundSlots = (Slot*) calloc(context.maxNumSounds, 1000); // XXX HACK
        context.sounds = (SoundHandle **) calloc(context.maxNumSounds, 1000); // XXX HACK
        //assert(context.soundSlots != NULL);

        PaError err = Pa_Initialize();
        if (err != paNoError) {
            printf("error: sound failed to initialize. error=%s\n", Pa_GetErrorText(err));
            return false;

        } else {
            //printf("sound initialized :)");
        }

        int numDevices;
        numDevices = Pa_GetDeviceCount();
        if (numDevices < 0 ) {
            printf("ERROR: Pa_CountDevices returned %i\n", numDevices);
            return false;

        } else {
#if 0
            printf("sound: num devices=%d\n", numDevices);
            const PaDeviceInfo *deviceInfo;
            int i;
            for( i=0; i<numDevices; i++ ) {
                deviceInfo = Pa_GetDeviceInfo( i );
                printf("dev [%i]: %s\n", i, deviceInfo->name);
            }
#endif
        }

        /* Open an audio I/O stream. */
        err = Pa_OpenDefaultStream(
            &context.stream,
            0,              /* no input channels */
            2,              /* stereo output */
            paFloat32,      /* 32 bit floating point output */
            context.sampleRate,          /* sample rate */
            context.framesPerBuffer,            /* frames per buffer */
            sound_callback,            /* this is your callback function */
            NULL);          /* callback function pointer */

        if (err != paNoError) {
            printf("error: opening default stream: %s\n", Pa_GetErrorText(err));
            return false;
        }

        err = Pa_StartStream(context.stream);
        if (err != paNoError) {
            printf("error: starting default stream failed: %s\n", Pa_GetErrorText(err));
            return false;
        }

        // clear that
        //sound_context.num_active_handles = 0;
        //sound_context.num_cached = 0;

        context.init = true;
        return true;
    }

    static void allocateHistory(SoundHandle *handle, size_t numHistorySamples)
    {
        handle->lastHistorySample = 0;

        if (numHistorySamples == 0) {
            handle->numHistorySamples = 0;
            handle->history = NULL;
            return;
        }

        handle->numHistorySamples = numHistorySamples;
        handle->history = (float *) calloc(sizeof(float)*2, handle->numHistorySamples);

        // clear all
        size_t i;
        for (i=0; i<2*handle->numHistorySamples; i++) {
            handle->history[i] = 0.0f;
        }
    }

    SoundHandle *playFrequency(float freq, float amplitude)
    {
        SoundHandle *handle = playSound(SoundFrequency, amplitude);
        if (handle != NULL) {
            handle->freq = freq;
            //handle->looping = true;
        }
        return handle;
    }

    SoundHandle *playSound(enum Sound type, float amplitude)
    {
        if (!context.init) {
            //printf("warning: called playSound() with no initialized sound system!\n");
            return NULL;
        }

        ssize_t nextFreeIndex = 0;
        while(nextFreeIndex <= context.highestSoundIndex) {
            if (context.sounds[nextFreeIndex] == NULL || context.sounds[nextFreeIndex]->isDeleted) {
                // got an empty sound to use
                break;
            }
            nextFreeIndex++;
        }

        if (context.sounds[nextFreeIndex] != NULL) {
            printf("[sound] playSound() finally free'ing old sound: %zd\n",
                    context.sounds[nextFreeIndex]->uid);

            free(context.sounds[nextFreeIndex]);
            context.sounds[nextFreeIndex] = NULL;
        }

        if (context.sounds[nextFreeIndex] != NULL) {
            fprintf(stderr, "[sound] warning: playSound() nextFreeIndex is "
                    "%zd but that sound is not NULL!\n", nextFreeIndex);

            return NULL;
        }

        if (nextFreeIndex+1 >= (ssize_t) context.maxNumSounds) {
            printf("[sound] warning: can't create more sounds, got maximum of: %zd\n", context.maxNumSounds);
            return NULL;
        }

        SoundHandle *handle = (SoundHandle *) calloc(1, sizeof(SoundHandle));
        handle->deleteFlag = false;
        handle->isDeleted  = false;
        handle->_done = false;
        handle->envelope = New;
        handle->amplitude = amplitude;
        handle->minAmplitude = 0.0;
        handle->freq = 1000;
        handle->looping = false;
        handle->_loopCount = 0;
        handle->_type = type;
        handle->time = 0.0;
        handle->keep_when_done = false;
        handle->state = 0.0;
        handle->leakage = 0.0;
        handle->scaling = 0.0;
        handle->skipDirectionIfClamping = false;

        handle->_useEnvelope = false;
        handle->_riseTime    = 0.0;
        handle->_relativeOvershootTime = 0.0;
        handle->_decayTime   = 0.0;
        handle->_holdTime    = 0.0;
        handle->_waitTime    = 0.0;
        handle->decayType    = SoundDecayLinear;
        handle->riseType     = SoundDecayLinear;
        handle->name         = "<unnamed>";

        // filters may to access values from their history
        int numHistoryValues = 0;

        switch(type) {
            // pink/white/brown noise need to save only one sample, state is enough
            // for that.
            case SoundBrownianNoise:
                // something like lowpass filter constant.
                // lower value == lower freq. noise
                // values that audacity uses
                //handle->leakage = 0.997;
                //handle->scaling = 0.05;

                handle->leakage = (context.sampleRate - 144.0) / context.sampleRate;
                handle->scaling = 9.0 / sqrt(context.sampleRate);
                //printf("computed leakage and scaling: %f %F\n", handle->leakage, handle->scaling);

                //handle->leakage = 0.7;
                //handle->scaling = 0.2;
                handle->skipDirectionIfClamping = true;
                break;

            case SoundPinkNoise:
                break;

            case SoundWhiteNoise:
                break;

            default:
                break;
        }

        allocateHistory(handle, numHistoryValues);

        handle->uid = context.sound_uid_counter++;

        context.sounds[nextFreeIndex] = handle;

        if (nextFreeIndex == context.highestSoundIndex+1) {
            context.highestSoundIndex++;
        }
        //if (nextFreeIndex < context.highestSoundIndex) {
        //    fprintf(stderr, "[sound] warning: nextFreeIndex > context.highestSoundIndex, "
        //            "this should not happen: %zd %zd\n", nextFreeIndex, context.highestSoundIndex);
        //}

        printf("[sound] playSound() created sound #%zd name %s\n", handle->uid, handle->name);
        printf("[sound] highestSoundIndex now %zd\n", context.highestSoundIndex);

        return handle;
    }

    bool stopSound(SoundHandle *handle)
    {
        if (!context.init) {
            printf("warning: called stopSound() with no initialized sound system!\n");
            return false;
        }

        if (handle == NULL) {
            printf("warning: stopSound() handle is NULL\n");
            return false;
        }

        if (handle->_done) {
            printf("warning: stopSound() sound is already stopped\n");
            return false;
        }

        // TODO: kill with variable
        handle->_done = true;

        return true;
    }

    void stopAllSounds(void)
    {
        if (!context.init) {
            printf("warning: called stopAllSounds() with no initialized sound system!\n");
        }

        unsigned int i;
        for (i=0; i<context.highestSoundIndex; i++) {
            if (context.sounds[i] != NULL && !context.sounds[i]->_done) {
                stopSound(context.sounds[i]);
            }
        }
    }

    void computeEnvelopeUse(SoundHandle *handle)
    {
        if (handle == NULL) {
            return;
        }

        // wait time has no influence (yet)
        handle->_useEnvelope = (handle->_riseTime != 0.0 || handle->_holdTime != 0.0 || handle->_decayTime != 0.0);
    }

    bool configureEnvelope(SoundHandle *handle, float rise, float hold, float decay)
    {
        if (handle == NULL) {
            return false;
        }

        // TODO: check inf/nan too
        if (rise < 0.0) {
            return false;
        }
        if (hold < 0.0) {
            return false;
        }
        if (decay < 0.0) {
            return false;
        }

        handle->_riseTime   = rise;
        handle->_holdTime   = hold;
        handle->_decayTime  = decay;

        // set depending on flags
        computeEnvelopeUse(handle);

        return true;
    }

    bool configureEnvelopeLooping(SoundHandle *handle, float rise, float hold, float decay, float wait)
    {
        if (wait < 0.0) {
            return false;
        }

        if (!configureEnvelope(handle, rise, hold, decay)) {
            return false;
        }

        handle->looping = true;
        handle->_loopCount = -1;
        handle->_waitTime = wait;

        return true;
    }

    float getRiseDuration(SoundHandle *handle)
    {
        if (handle == NULL) {
            return -1.0;
        }

        return handle->_riseTime;
    }

    float getHoldDuration(SoundHandle *handle)
    {
        if (handle == NULL) {
            return -1.0;
        }

        return handle->_holdTime;
    }

    float getDecayDuration(SoundHandle *handle)
    {
        if (handle == NULL) {
            return -1.0;
        }

        return handle->_decayTime;
    }

    float getwaitTime(SoundHandle *handle)
    {
        if (handle == NULL) {
            return -1.0;
        }

        return handle->_waitTime;
    }

    void teardownSound(void)
    {
        if (!context.init) {
            printf("[sonud] can't teardown sound when not initialized!\n");
            return;
        }

        PaError err = Pa_CloseStream(context.stream);
        if (err != paNoError) {
            printf("[sound] portaudio failed to close stream: %d\n", err);
        }

        err = Pa_Terminate();
        if (err != paNoError) {
            printf("[sound] portaudio failed to terminate: %d\n", err);
        }

        if (context.dumping) {
            stopDumpingWav();
        }

        context.init = false;
    }

    bool configureOvershoot(SoundHandle *handle, float relativeOvershootTime)
    {
        if (handle == NULL) {
            return false;
        }
        if (relativeOvershootTime < 0.0) {
            return false;
        }

        handle->_relativeOvershootTime      = relativeOvershootTime;

        return true;
    }

    void setLoopCount(SoundHandle *handle, int numRepetitions)
    {
        if (numRepetitions < -1) {
            numRepetitions = -1;
        }

        handle->_loopCount = numRepetitions;

        if (numRepetitions == 0) {
            handle->looping = false;
        } else if (numRepetitions == -1) {
            handle->looping    = true;
        } else {
            handle->looping = true;
        }
    }

    int numActiveSounds(void)
    {
        int num = 0;
        ssize_t i;
        for (i=0; i<=context.highestSoundIndex; i++) {
            // TODO: active is when not done or not deleted!
            if (context.sounds[i] != NULL) {
                num++;
            }
        }
        return num;
    }

    void deleteSound(SoundHandle *handle)
    {
        if (handle == NULL) {
            return;
        }

        // just to be sure
        handle->_done = true;

        // clear from that
        ssize_t i;
        for (i=0; i<=context.highestSoundIndex; i++) {
            if (context.sounds[i] == handle) {
                context.sounds[i] = NULL;
                if (i+1 == context.highestSoundIndex) {
                    context.highestSoundIndex--;
                }
                break;
            }
        }

        if (handle->history != NULL) {
            free(handle->history);
        }

        free(handle);
    }

    bool configureEnvelopeWait(SoundHandle *handle, float rise, float hold, float decay, float wait)
    {
        if (configureEnvelopeLooping(handle, rise, hold, decay, wait)) {
            handle->looping = false;
            handle->_loopCount = -1;
            return true;

        } else {
            return false;
        }
    }

    void deleteOldSounds(void)
    {
        ssize_t i;
        for (i=0; i<=context.highestSoundIndex; i++) {
            SoundHandle *handle = context.sounds[i];
            if (handle != NULL && handle->deleteFlag) {
                //context.sounds[i] = NULL;
                printf("[sound] %f finally deleting sound #%zd handle 0x%p\n", global_time, handle->uid, handle);
                if (handle->history != NULL) {
                    free(handle->history);
                }
                handle->isDeleted = true;
                handle->deleteFlag = false;
                //free(handle);
            }
        }

        ssize_t nextHighestSoundIndex = -1;
        for (i=0; i<=context.highestSoundIndex; i++) {
            SoundHandle *handle = context.sounds[i];
            if (handle != NULL) {
                nextHighestSoundIndex = i;
            }
        }

        if (context.highestSoundIndex != nextHighestSoundIndex) {
            printf("[sound] deleteOldSounds() setting hightest sound index from %zd to %zd\n",
                    context.highestSoundIndex, nextHighestSoundIndex);

            context.highestSoundIndex = nextHighestSoundIndex;
        }
    }
}
