/* OpenSL: The Standard for Embedded Audio Acceleration
 * http://www.khronos.org/opensles/
 * http://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.1.pdf
 */

#include "allegro5/allegro.h"
#include "allegro5/internal/aintern_audio.h"

#include <SLES/OpenSLES.h>

/* TODO:
 * If an android application goes into the suspend state then the opensl resources
 * might become 'unrealized' or 'suspended' so care needs to be taken
 * to realize them again once the application is resumed.
 */

/* Not sure if this one is needed, yet */
// #include <SLES/OpenSLES_Android.h>

/* Feel free to change MAX_FRAMES and MAX_BUFFERS if it affects
 * choppiness for your device.
 */

/* Number of samples to read in one call to al_voice_update */
static const int MAX_FRAMES = 2048;

/* Number of opensl buffers to use */
#define MAX_BUFFERS 2

ALLEGRO_DEBUG_CHANNEL("opensl")

static SLObjectItf engine;

static const char * opensl_get_error_string(SLresult result)
{
    switch (result){
        case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated";
        case SL_RESULT_PARAMETER_INVALID: return "Invalid parameter";
        case SL_RESULT_MEMORY_FAILURE: return "Memory failure";
        case SL_RESULT_RESOURCE_ERROR: return "Resource error";
        case SL_RESULT_RESOURCE_LOST: return "Resource lost";
        case SL_RESULT_IO_ERROR: return "IO error";
        case SL_RESULT_BUFFER_INSUFFICIENT: return "Insufficient buffer";
        case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted";
        case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported";
        case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found";
        case SL_RESULT_PERMISSION_DENIED: return "Permission denied";
        case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported";
        case SL_RESULT_INTERNAL_ERROR: return "Internal error";
        case SL_RESULT_UNKNOWN_ERROR: return "Unknown error";
        case SL_RESULT_OPERATION_ABORTED: return "Operation aborted";
        case SL_RESULT_CONTROL_LOST: return "Control lost";
    }
    return "Unknown OpenSL error";
}

/* Only the original 'engine' object should be passed here */
static SLEngineItf getEngine(SLObjectItf engine){
    SLresult result;
    SLEngineItf interface;
    result = (*engine)->GetInterface(engine, SL_IID_ENGINE, &interface);
    if (result == SL_RESULT_SUCCESS){
        return interface;
    } else {
        ALLEGRO_ERROR("Could not get opensl engine: %s\n", opensl_get_error_string(result));
        return NULL;
    }
}

/* Create an output mixer */
static SLObjectItf createOutputMixer(SLEngineItf engine){
    SLresult result;
    SLObjectItf output;
    SLboolean required[1];
    SLInterfaceID ids[1];

    /* Not all android devices support a mixer that you can control
     * the volume on, so just ignore it for now.
     */
    required[0] = SL_BOOLEAN_TRUE;
    ids[0] = SL_IID_VOLUME;

    result = (*engine)->CreateOutputMix(engine, &output, 0, ids, required);
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not create output mix: %s\n", opensl_get_error_string(result));
        return NULL;
    }

    result = (*output)->Realize(output, SL_BOOLEAN_FALSE);
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not realize the output mix: %s\n", opensl_get_error_string(result));
        (*output)->Destroy(output);
        return NULL;
    }

    return output;
}

static int _opensl_open(void)
{
    SLresult result;
    SLuint32 state;
    SLEngineOption options[] = {
	{ SL_ENGINEOPTION_THREADSAFE, (SLuint32) SL_BOOLEAN_TRUE },
        /*
	{ SL_ENGINEOPTION_MAJORVERSION, (SLuint32) 1 },
	{ SL_ENGINEOPTION_MINORVERSION, (SLuint32) 1 },
        */
    };

    result = slCreateEngine(&engine, 1, options, 0, NULL, NULL);
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not open audio device: %s\n",
                      opensl_get_error_string(result));
        return 1;
    }

    /* Transition the engine to the realized state in synchronous mode */
    result = (*engine)->GetState(engine, &state);
    if (result == SL_RESULT_SUCCESS){
        switch (state){
            case SL_OBJECT_STATE_UNREALIZED: {
                result = (*engine)->Realize(engine, SL_BOOLEAN_FALSE);
                break;
            }
            case SL_OBJECT_STATE_REALIZED: {
                /* This is good */
                break;
            }
            case SL_OBJECT_STATE_SUSPENDED: {
                result = (*engine)->Resume(engine, SL_BOOLEAN_FALSE);
                break;
            }
        }
    } else {
        return 1;
    }

    // output = createOutputMixer(getEngine(engine));

    return 0;
}

static void _opensl_close(void)
{
    /*
    if (output != NULL){
        (*output)->Destroy(output);
        output = NULL;
    }
    */

    if (engine != NULL){
        (*engine)->Destroy(engine);
        engine = NULL;
    }
}

typedef struct OpenSLData{
    /* Output mixer */
    SLObjectItf output;
    /* Audio player */
    SLObjectItf player;
    volatile enum { PLAYING, STOPPING, STOPPED } status;

    /* load_voice stuff that isn't used, but might be someday */
    /*
    const void * data;
    int position;
    int length;
    */

    /* Size of a single sample: depth * channels */
    int frame_size;
    ALLEGRO_THREAD * poll_thread;

    /* local buffers to keep opensl fed since it doesn't copy
     * data by default.
     */
    char * buffers[MAX_BUFFERS];
} OpenSLData;

static SLDataFormat_PCM setupFormat(ALLEGRO_VOICE * voice){
    SLDataFormat_PCM format;
    format.formatType = SL_DATAFORMAT_PCM;

    format.numChannels = al_get_channel_count(voice->chan_conf);

    /* TODO: review the channelMasks */
    switch (voice->chan_conf){
        case ALLEGRO_CHANNEL_CONF_1: {
            /* Not sure if center is right.. */
            format.channelMask = SL_SPEAKER_FRONT_CENTER;
            break;
        }
        case ALLEGRO_CHANNEL_CONF_2: {
            format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
            break;
        }
        case ALLEGRO_CHANNEL_CONF_3: {
            format.channelMask = SL_SPEAKER_FRONT_LEFT |
                                 SL_SPEAKER_FRONT_RIGHT |
                                 SL_SPEAKER_FRONT_CENTER;
            break;
        }
        case ALLEGRO_CHANNEL_CONF_4: {
            format.channelMask = SL_SPEAKER_FRONT_LEFT |
                                 SL_SPEAKER_BACK_LEFT |
                                 SL_SPEAKER_FRONT_RIGHT |
                                 SL_SPEAKER_BACK_RIGHT;
            break;
        }
        case ALLEGRO_CHANNEL_CONF_5_1: {
            format.channelMask = SL_SPEAKER_FRONT_LEFT |
                                 SL_SPEAKER_BACK_LEFT |
                                 SL_SPEAKER_FRONT_RIGHT |
                                 SL_SPEAKER_BACK_RIGHT |
                                 SL_SPEAKER_FRONT_CENTER |
                                 SL_SPEAKER_LOW_FREQUENCY;
            break;
        }
        case ALLEGRO_CHANNEL_CONF_6_1: {
            format.channelMask = SL_SPEAKER_FRONT_LEFT |
                                 SL_SPEAKER_BACK_LEFT |
                                 SL_SPEAKER_FRONT_RIGHT |
                                 SL_SPEAKER_BACK_RIGHT |
                                 SL_SPEAKER_FRONT_CENTER |
                                 SL_SPEAKER_LOW_FREQUENCY |
                                 SL_SPEAKER_SIDE_LEFT |
                                 SL_SPEAKER_SIDE_RIGHT;

            break;
        }
        case ALLEGRO_CHANNEL_CONF_7_1: {
            format.channelMask = SL_SPEAKER_FRONT_LEFT |
                                 SL_SPEAKER_BACK_LEFT |
                                 SL_SPEAKER_FRONT_RIGHT |
                                 SL_SPEAKER_BACK_RIGHT |
                                 SL_SPEAKER_FRONT_CENTER |
                                 SL_SPEAKER_LOW_FREQUENCY |
                                 SL_SPEAKER_SIDE_LEFT |
                                 SL_SPEAKER_SIDE_RIGHT |
                                 SL_SPEAKER_TOP_CENTER;
            break;
        }
        default: {
            ALLEGRO_ERROR("Cannot allocate voice with unknown channel configuration\n");
        }
    }

    switch (voice->frequency){
        case 8000: format.samplesPerSec = SL_SAMPLINGRATE_8; break;
        case 11025: format.samplesPerSec = SL_SAMPLINGRATE_11_025; break;
        case 12000: format.samplesPerSec = SL_SAMPLINGRATE_12; break;
        case 16000: format.samplesPerSec = SL_SAMPLINGRATE_16; break;
        case 22050: format.samplesPerSec = SL_SAMPLINGRATE_22_05; break;
        case 24000: format.samplesPerSec = SL_SAMPLINGRATE_24; break;
        case 32000: format.samplesPerSec = SL_SAMPLINGRATE_32; break;
        case 44100: format.samplesPerSec = SL_SAMPLINGRATE_44_1; break;
        case 48000: format.samplesPerSec = SL_SAMPLINGRATE_48; break;
        case 64000: format.samplesPerSec = SL_SAMPLINGRATE_64; break;
        case 88200: format.samplesPerSec = SL_SAMPLINGRATE_88_2; break;
        case 96000: format.samplesPerSec = SL_SAMPLINGRATE_96; break;
        case 192000: format.samplesPerSec = SL_SAMPLINGRATE_192; break;
        default: {
            ALLEGRO_ERROR("Unsupported frequency %d. Using 44100 instead.\n", voice->frequency);
            format.samplesPerSec = SL_SAMPLINGRATE_44_1;
            voice->frequency = 44100;
        }
    }

    switch (voice->depth) {
        case ALLEGRO_AUDIO_DEPTH_UINT8:
        case ALLEGRO_AUDIO_DEPTH_INT8: {
            format.bitsPerSample = 8;
            format.containerSize = 8;
            break;
        }
        case ALLEGRO_AUDIO_DEPTH_UINT16:
        case ALLEGRO_AUDIO_DEPTH_INT16: {
            format.bitsPerSample = 16;
            format.containerSize = 16;
            break;
        }
        case ALLEGRO_AUDIO_DEPTH_UINT24:
        case ALLEGRO_AUDIO_DEPTH_INT24: {
            format.bitsPerSample = 24;
            format.containerSize = 32;
            break;
        }
        case ALLEGRO_AUDIO_DEPTH_FLOAT32: {
            format.bitsPerSample = 32;
            format.containerSize = 32;
            break;
        }
        default: {
            ALLEGRO_WARN("Cannot allocate unknown voice depth\n");
        }
    }

#ifdef ALLEGRO_BIG_ENDIAN
    format.endianness = SL_BYTEORDER_BIGENDIAN;
#else
    format.endianness = SL_BYTEORDER_LITTLEENDIAN;
#endif

    /* Then OpenSL spec says these values are needed for SLDataFormat_PCM_EX
     * but not all implementations define that struct. If they do then
     * the following code can be used.
     */

    /*
    switch (voice->depth){
        case ALLEGRO_AUDIO_DEPTH_UINT8:
        case ALLEGRO_AUDIO_DEPTH_UINT16:
        case ALLEGRO_AUDIO_DEPTH_UINT24: {
            format.representation = SL_PCM_REPRESENTATION_UNSIGNED_INT;
        }
        case ALLEGRO_AUDIO_DEPTH_INT8:
        case ALLEGRO_AUDIO_DEPTH_INT16:
        case ALLEGRO_AUDIO_DEPTH_INT24: {
            format.representation = SL_PCM_REPRESENTATION_SIGNED_INT;
            break;
        }
        case ALLEGRO_AUDIO_DEPTH_FLOAT32: {
            format.representation = SL_PCM_REPRESENTATION_FLOAT;
            break;
        }
    }
    */

    return format;
}

static SLObjectItf createAudioPlayer(SLEngineItf engine, SLDataSource * source, SLDataSink * sink){
    SLresult result;
    SLObjectItf player;

    SLboolean required[1];
    SLInterfaceID ids[1];

    required[0] = SL_BOOLEAN_TRUE;
    ids[0] = SL_IID_BUFFERQUEUE;

    result = (*engine)->CreateAudioPlayer(engine, &player, source, sink, 1, ids, required);
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not create audio player: %s\n", opensl_get_error_string(result));
        return NULL;
    }

    result = (*player)->Realize(player, SL_BOOLEAN_FALSE);
    
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not realize audio player: %s\n", opensl_get_error_string(result));
        return NULL;
    }

    return player;
}

static SLObjectItf makeStreamingPlayer(ALLEGRO_VOICE * voice, SLObjectItf mixer){
    SLDataFormat_PCM format = setupFormat(voice);
    SLDataLocator_BufferQueue bufferQueue;
    SLDataSource audioSource;
    SLDataSink audioSink;
    SLDataLocator_OutputMix output;

    bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
    bufferQueue.numBuffers = MAX_BUFFERS;

    audioSource.pFormat = (void*) &format;
    audioSource.pLocator = (void*) &bufferQueue;

    output.locatorType = SL_DATALOCATOR_OUTPUTMIX;
    output.outputMix = mixer;

    audioSink.pLocator = (void*) &output;
    audioSink.pFormat = NULL;
    
    return createAudioPlayer(getEngine(engine), &audioSource, &audioSink);

    /*
    SLresult result;
    SLVolumeItf volume;
    result = (*extra->output)->GetInterface(extra->output, SL_IID_VOLUME, &volume);
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not get volume interface: %s\n", opensl_get_error_string(result));
        return 1;
    }
    */

}

/* Number of active buffers in the queue. Will not be more than MAX_BUFFERS */
static int bufferCount(SLObjectItf player){
    SLBufferQueueItf queue;
    SLBufferQueueState state;

    (*player)->GetInterface(player, SL_IID_BUFFERQUEUE, &queue);
    (*queue)->GetState(queue, &state);
    return state.count;
}

/* Attach some data to the OpenSL player. The data is not copied */
static void enqueue(SLObjectItf player, const void * data, int bytes){
    SLresult result;
    SLBufferQueueItf queue;
    SLPlayItf play;

    result = (*player)->GetInterface(player, SL_IID_BUFFERQUEUE, &queue);
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not get bufferqueue interface: %s\n", opensl_get_error_string(result));
        return;
    }

    // ALLEGRO_DEBUG("Play voice data %p\n", data);
        
    result = (*queue)->Enqueue(queue, data, bytes);
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not enqueue data: %s\n", opensl_get_error_string(result));
        return;
    }

    // result = (*volume)->SetVolumeLevel(volume, -300);

    result = (*player)->GetInterface(player, SL_IID_PLAY, &play);

    /* In case the player is not playing, make it play */
    result = (*play)->SetPlayState(play, SL_PLAYSTATE_PLAYING);

    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not set play state on OpenSL stream\n");
    }

    /*
    SLBufferQueueState state;
    result = (*queue)->GetState(queue, &state);
    if (result == SL_RESULT_SUCCESS){
        ALLEGRO_DEBUG("Buffer queue state count %d index %d\n", state.count, state.playIndex);
    }
    */
}

static void * opensl_update(ALLEGRO_THREAD * self, void * data){
    ALLEGRO_VOICE *voice = data;
    OpenSLData * opensl = voice->extra;

    int bufferIndex = 0;
    while (!al_get_thread_should_stop(self)) {
        if (opensl->status == PLAYING) {
            // unsigned int frames = 4096;
            unsigned int frames = MAX_FRAMES;
            if (voice->is_streaming) { 
                // streaming audio           
                if (bufferCount(opensl->player) < MAX_BUFFERS){
                    const void * data = _al_voice_update(voice, voice->mutex, &frames);
                    if (data){
                        /* Copy the data to a local buffer because a call to enqueue
                         * will use the memory in place and al_voice_update will
                         * re-use the same buffer for each call so we don't want
                         * to corrupt memory when the next call to al_voice_update
                         * is made.
                         */
                        char * buffer = opensl->buffers[bufferIndex];
                        memcpy(buffer, data, frames * opensl->frame_size);
                        enqueue(opensl->player, buffer, frames * opensl->frame_size);

                        bufferIndex = (bufferIndex + 1) % MAX_BUFFERS;
                    }
                } else {
                    al_rest(0.001);
                }
            } else {
                ALLEGRO_ERROR("Unimplemented direct audio\n");
                /*
                // direct buffer audio
                al_lock_mutex(pv->buffer_mutex);
                const char *data = pv->buffer;
                unsigned int len = frames * pv->frame_size;
                pv->buffer += frames * pv->frame_size;
                if (pv->buffer > pv->buffer_end) {
                    len = pv->buffer_end - data;
                    pv->buffer = voice->attached_stream->spl_data.buffer.ptr;
                    voice->attached_stream->pos = 0;
                    if (voice->attached_stream->loop == ALLEGRO_PLAYMODE_ONCE) {
                        pv->status = PV_STOPPING;
                    }
                } else {
                    voice->attached_stream->pos += frames;
                }
                al_unlock_mutex(pv->buffer_mutex);

                pa_simple_write(pv->s, data, len, NULL);
                */
            }
        } else if (opensl->status == STOPPING){
            if (bufferCount(opensl->player) == 0){
                /* When the buffer count is 0 the opensl buffer queue should
                 * transition to the SL_PLAYSTATE_STOPPED state automatically.
                 */
                opensl->status = STOPPED;
            }
        } else if (opensl->status == STOPPED){
            al_rest(0.001);
        }
    }

    return NULL;
}

static int _opensl_allocate_voice(ALLEGRO_VOICE *voice)
{
    OpenSLData * data;
    int i;

    data = al_calloc(1, sizeof(*data));
    voice->extra = data;

    data->output = createOutputMixer(getEngine(engine));
    if (data->output == NULL){
        al_free(data);
        return 1;
    }

    data->player = NULL;
    /*
    data->data = NULL;
    data->position = 0;
    data->length = voice->buffer_size;
    */
    data->frame_size = al_get_channel_count(voice->chan_conf) * al_get_audio_depth_size(voice->depth);
    data->status = STOPPED;
    data->poll_thread = NULL;
    for (i = 0; i < MAX_BUFFERS; i++){
        data->buffers[i] = al_malloc(data->frame_size * MAX_FRAMES);
    }
    
    data->player = makeStreamingPlayer(voice, data->output);
    if (data->player == NULL){
        return 1;
    }
    data->poll_thread = al_create_thread(opensl_update, (void*)voice);
    al_start_thread(data->poll_thread);

    return 0;
}

static void _opensl_deallocate_voice(ALLEGRO_VOICE *voice)
{
    OpenSLData * data = (OpenSLData*) voice->extra;
    int i;
    if (data->poll_thread != NULL){
        al_set_thread_should_stop(data->poll_thread);
        al_join_thread(data->poll_thread, NULL);
        al_destroy_thread(data->poll_thread);
        data->poll_thread = NULL;
    }

    if (data->player != NULL){
        (*data->player)->Destroy(data->player);
        data->player = NULL;
    }

    if (data->output != NULL){
        (*data->output)->Destroy(data->output);
        data->output = NULL;
    }

    for (i = 0; i < MAX_BUFFERS; i++){
        al_free(data->buffers[i]);
    }
    al_free(voice->extra);
    voice->extra = NULL;
}

/* load_voice is only called by attach_sample_instance_to_voice which
 * isn't really used, so we leave it unimplemented for now.
 */
static int _opensl_load_voice(ALLEGRO_VOICE *voice, const void *data)
{
    (void) voice;
    (void) data;
    /*
    OpenSLData * extra = (OpenSLData*) voice->extra;
    ALLEGRO_DEBUG("Load voice data %p\n", data);
    extra->data = data;
    extra->position = 0;
    */

    return 1;
}

static void _opensl_unload_voice(ALLEGRO_VOICE *voice)
{
    (void) voice;
    /*
    OpenSLData * extra = (OpenSLData*) voice->extra;
    extra->data = NULL;
    extra->position = 0;
    */
}

/*
static void updateQueue(SLBufferQueueItf queue, void * context){
    OpenSLData * data = (OpenSLData*) context;
    if (data->position < data->length){
        int bytes = data->frame_size * 1024;
        if (data->position + bytes > data->length){
            bytes = ((data->length - data->position) / data->frame_size) * data->frame_size;
        }

        SLresult result;
        ALLEGRO_DEBUG("Enqueue %d bytes\n", bytes);
        result = (*queue)->Enqueue(queue, (char*) data->data + data->position, bytes);
        data->position += bytes;
    }
}
*/

static int _opensl_start_voice(ALLEGRO_VOICE *voice)
{
    OpenSLData * extra = (OpenSLData*) voice->extra;
    
    extra->status = PLAYING;

    /*
    result = (*extra->player)->GetInterface(extra->player, SL_IID_BUFFERQUEUE, &queue);
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not get bufferqueue interface: %s\n", opensl_get_error_string(result));
        return 1;
    }

    ALLEGRO_DEBUG("Start playing voice data %p\n", extra->data);
        
    result = (*queue)->Enqueue(queue, (char*) extra->data + extra->position, extra->frame_size * 32);
    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not enqueue data: %s\n", opensl_get_error_string(result));
    }
    extra->position += extra->frame_size * 32;

    result = (*queue)->RegisterCallback(queue, updateQueue, extra);

    if (result != SL_RESULT_SUCCESS){
        ALLEGRO_ERROR("Could not register callback: %s\n", opensl_get_error_string(result));
    }

    // result = (*volume)->SetVolumeLevel(volume, -300);

    result = (*extra->player)->GetInterface(extra->player, SL_IID_PLAY, &play);
    result = (*play)->SetPlayState(play, SL_PLAYSTATE_PLAYING);

    if (result == SL_RESULT_SUCCESS){
        ALLEGRO_DEBUG("Started new OpenSL stream\n");
    }

    result = (*queue)->GetState(queue, &state);
    if (result == SL_RESULT_SUCCESS){
        ALLEGRO_DEBUG("Buffer queue state count %d index %d\n", state.count, state.playIndex);
    }
    */

    return 0;
}

static int _opensl_stop_voice(ALLEGRO_VOICE* voice)
{
    OpenSLData * data = (OpenSLData*) voice->extra;
    if (data->status == PLAYING){
        data->status = STOPPING;
    }

    while (data->status != STOPPED){
        al_rest(0.001);
    }

    return 0;
}

static bool _opensl_voice_is_playing(const ALLEGRO_VOICE *voice)
{
    OpenSLData * extra = (OpenSLData*) voice->extra;
    return extra->status == PLAYING;
}

static unsigned int _opensl_get_voice_position(const ALLEGRO_VOICE *voice)
{
    /* TODO */
    (void) voice;
    ALLEGRO_ERROR("Unimplemented: _opensl_get_voice_position\n");
    return 0;
}

static int _opensl_set_voice_position(ALLEGRO_VOICE *voice, unsigned int val)
{
    /* TODO */
    (void) voice;
    (void) val;
    ALLEGRO_ERROR("Unimplemented: _opensl_set_voice_position\n");
    return 1;
}

ALLEGRO_AUDIO_DRIVER _al_kcm_opensl_driver = {
   "OpenSL",

   _opensl_open,
   _opensl_close,

   _opensl_allocate_voice,
   _opensl_deallocate_voice,

   _opensl_load_voice,
   _opensl_unload_voice,

   _opensl_start_voice,
   _opensl_stop_voice,

   _opensl_voice_is_playing,

   _opensl_get_voice_position,
   _opensl_set_voice_position,

   NULL,
   NULL
};
