#ifndef DEPENDENCY_AUDIO_OUT
//Stubs:
void snd_mainloop(){return;}
void snd_init(){return;}
void snd_un_init(){return;}
#else

#ifdef QB64_BACKSLASH_FILESYSTEM
#include "AL\\al.h"
#include "AL\\alc.h"
#include <stdio.h>
#include <sys\\types.h>
#include <sys\\stat.h>
#include <unistd.h>
#include <stdlib.h>
#else
#include "AL/al.h"
#include "AL/alc.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#endif

//forward refs (with no struct dependencies)
void sndsetup();
void sndclose_now(int32 handle);
void sub__sndstop(int32 handle);
void sub__sndplay(int32);
uint8 *soundwave(double frequency,double length,double volume,double fadein,double fadeout);
void qb64_generatesound(double f,double l,uint8 wait);
void qb64_internal_sndraw(uint8* data,int32 bytes,int32 block);
double func__sndrawlen(int32 handle,int32 passed);


//global variables
int32 qb64_sndraw_lock=0;
int32 qb64_internal_sndraw_handle=0;
int64 qb64_internal_sndraw_lastcall=0;
int64 qb64_internal_sndraw_prepad=0;
int64 qb64_internal_sndraw_postpad=0;
int32 soundwave_bytes=0;
int32 snd_frequency=44100;
int32 snd_buffer_size=16384;
int32 snd_allow_internal=0;//set this flag before calling snd_... commands with an internal sound

uint8 *soundwave(double frequency,double length,double volume,double fadein,double fadeout){
    //this creates 16bit signed stereo data

    sndsetup();
    static uint8 *data;
    static int32 i;
    static int16 x,lastx;
    static int16* sp;
    static double samples_per_second;
    samples_per_second=snd_frequency;

    //calculate total number of samples required
    static double samples;
    static int32 samplesi;
    samples=length*samples_per_second;
    samplesi=samples; if (!samplesi) samplesi=1;

    soundwave_bytes=samplesi*4;
    data=(uint8*)malloc(soundwave_bytes);
    sp=(int16*)data;

    static int32 direction;
    direction=1;

    static double value;
    value=0;

    static double volume_multiplier;
    volume_multiplier=volume*32767.0;

    static int32 waveend;
    waveend=0;

    static double gradient;
    //frequency*4.0*length is the total distance value will travel (+1,-2,+1[repeated])
    //samples is the number of steps to do this in
    if (samples) gradient=(frequency*4.0*length)/samples; else gradient=0;//avoid division by 0

    lastx=1;//set to 1 to avoid passing initial comparison
    for (i=0;i<samplesi;i++){
        x=value*volume_multiplier;
        *sp++=x;
        *sp++=x;
        if (x>0){
            if (lastx<=0){
                waveend=i;
            }
        }
        lastx=x;
        if (direction){
            if ((value+=gradient)>=1.0){direction=0; value=2.0-value;}
        }else{
            if ((value-=gradient)<=-1.0){direction=1; value=-2.0-value;}
        }
    }//i

    if (waveend) soundwave_bytes=waveend*4;

    return (uint8*)data;
}

int32 wavesize(double length){
    static int32 samples;
    samples=length*(double)snd_frequency; if (samples==0) samples=1;
    return samples*4;
}



void sub_sound(double frequency,double lengthinclockticks){
    sndsetup();
    if (new_error) return;
    //note: there are 18.2 clock ticks per second
    if ((frequency<37.0)&&(frequency!=0)) goto error;
    if (frequency>32767.0) goto error;
    if (lengthinclockticks<0.0) goto error;
    if (lengthinclockticks>65535.0) goto error;
    if (lengthinclockticks==0.0) return;
    qb64_generatesound(frequency,lengthinclockticks/18.2,1);
    return;
error:
    error(5);
}


struct snd_sequence_struct{
    uint16 *data;
    int32 data_size;
    uint8 channels;//note: more than 2 channels may be supported in the future
    uint16 *data_left;
    int32 data_left_size;
    uint16 *data_right;
    int32 data_right_size;

    //origins of data (only relevent before src)
    uint8 endian;//0=native, 1=little(Windows, x86), 2=big(Motorola, Xilinx Microblaze, IBM POWER)
    uint8 is_unsigned;//1=unsigned, 0=signed(most common)
    int32 sample_rate;//eg. 11025, 22100
    int32 bits_per_sample;//eg. 8, 16

    int32 references;//number of SND handles dependent on this

};
list *snd_sequences=list_new(sizeof(snd_sequence_struct));


struct snd_struct{
    uint8 internal;//1=internal
    uint8 type;//1=RAW, 2=SEQUENCE

    //sequence
    snd_sequence_struct *seq;
    //----part specific variables----
    ALuint al_seq_left_buffer;
    ALuint al_seq_right_buffer;
    ALenum al_seq_format;
    ALsizei al_seq_freq;
    ALboolean al_seq_loop;
    ALuint al_seq_left_source;
    ALuint al_seq_right_source;
    //-------------------------------

    float volume;
    uint8 volume_update;

    uint8 close;

    uint8 limit_state;//0=off, 1=awaiting start[duration has been set], 2=waiting for stop point
    double limit_duration;
    int64 limit_stop_point;

    //locks
    uint8 setpos_lock_release;

    uint8 setpos_update;
    float setpos;

    float bal_left_x,bal_left_y,bal_left_z;
    float bal_right_x,bal_right_y,bal_right_z;
    uint8 bal_update;

    //usage of buffer depends heavily on type
    uint8 *buffer;
    int32 buffer_size;

    ptrszint *stream_buffer;//pointers to buffers
    int32 stream_buffer_last;
    int32 stream_buffer_start;
    int32 stream_buffer_next;

    ALuint al_source;
    ALuint *al_buffers;//[4]
    uint8 *al_buffer_state;//[4] 0=never used, 1=processing, 2=processed
    int32 *al_buffer_index;//[4]


    int64 raw_close_time;

    //The maximum number of buffers on iOS and on OS X is 1024.
    //The maximum number of sources is 32 on iOS and 256 on OS X.
    //therefore: inactive sources should be de-initialized & buffers should be 4

    uint8 state;

};
#define SND_STATE_STOPPED 0
#define SND_STATE_PLAYING 1
#define SND_STATE_PAUSED 2


list *snd_handles=list_new(sizeof(snd_struct));

void sndsetup(){

    static int32 sndsetup_called=0;
    if (!sndsetup_called){
        sndsetup_called=1;
        //...
    }

    //scan through all sounds and close marked ones, performed here to avoid thread issues
    static int32 list_index;
    for (list_index=1;list_index<=snd_handles->indexes;list_index++){
        static snd_struct *snd; snd=(snd_struct*)list_get(snd_handles,list_index);
        if (snd){

            if (snd->close==2){
                sndclose_now(list_index);
            }

        }//snd
    }//list_index

}//sndsetup



void sub_beep(){
    sndsetup();
    qb64_generatesound(783.99,0.2,0);
    sub__delay(0.25);
}




ALCdevice *dev;
ALCcontext *ctx;
struct stat statbuf;

void snd_un_init(){
    alcCloseDevice(dev);
    return;
}

int32 snd_init_done=0;
void snd_init(){
    if (!snd_init_done){

        dev = alcOpenDevice(NULL); if(!dev) exit(111);
        ctx = alcCreateContext(dev, NULL);
        alcMakeContextCurrent(ctx); if(!ctx) exit(222);

        alListener3f(AL_POSITION, 0, 0, 0);
        alListener3f(AL_VELOCITY, 0, 0, 0);
        alListener3f(AL_ORIENTATION, 0, 0, -1);//facing 'forward' on rhs co-ordinate system
        alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);



    }
    snd_init_done=1;
}




//OPENAL

int32 snd_raw_channel=0;

int32 func__sndopenraw(){
    static int32 handle; handle=list_add(snd_handles);
    static snd_struct *snd; snd=(snd_struct*)list_get(snd_handles,handle);
    snd->internal=0;
    snd->type=1;
    snd->buffer=(uint8*)malloc(snd_buffer_size);
    snd->buffer_size=0;

    snd->stream_buffer_last=65536;
    snd->stream_buffer=(ptrszint*)malloc(sizeof(ptrszint)*(snd->stream_buffer_last+1));//range: 1-65536 (0 ignored)
    snd->stream_buffer_start=0;
    snd->stream_buffer_next=1;

    alGenSources(1,&snd->al_source);
    //if(alGetError()!=AL_NO_ERROR) return 0;

    snd->al_buffers=(ALuint*)malloc(sizeof(ALuint)*4);
    alGenBuffers(4,snd->al_buffers);
    //if(alGetError()!=AL_NO_ERROR) return 0;
    snd->al_buffer_state=(uint8*)calloc(4,1);
    snd->al_buffer_index=(int32*)calloc(4,4);
    return handle;
}





void sub__sndraw(float left,float right,int32 handle,int32 passed){
    if (passed&2){
        if (handle==0) return;//note: this would be an invalid handle
    }else{
        if (!snd_raw_channel) snd_raw_channel=func__sndopenraw();
        handle=snd_raw_channel;
    }

    static snd_struct *snd; snd=(snd_struct*)list_get(snd_handles,handle);
    if (!snd) goto error;
    if (snd->internal) goto error;
    if (snd->type!=1) goto error;

    while (qb64_sndraw_lock) Sleep(0);
    qb64_sndraw_lock=1;

    if (handle==qb64_internal_sndraw_handle) qb64_internal_sndraw_lastcall=GetTicks();

    static int16 sample_left;
    sample_left=left*32767.0;
    static int16 sample_right;
    if (passed&1) sample_right=right*32767.0; else sample_right=sample_left;

    //add sample
    if (snd->buffer_size<snd_buffer_size){
        *(int16*)(snd->buffer+snd->buffer_size)=sample_left;
        snd->buffer_size+=2;
        *(int16*)(snd->buffer+snd->buffer_size)=sample_right;
        snd->buffer_size+=2;
    }

    if (snd->buffer_size==snd_buffer_size){
        //detach buffer
        static uint8 *buffer;
        buffer=snd->buffer;

        //create new buffer
        snd->buffer=(uint8*)malloc(snd_buffer_size);
        snd->buffer_size=0;

        //attach detached buffer to stream (or discard it)
        static int32 p,p2;
        p=snd->stream_buffer_next; p2=p+1; if (p2>snd->stream_buffer_last) p2=1;
        if (p2==snd->stream_buffer_start){free(buffer); qb64_sndraw_lock=0; return;}//all buffers are full! (quietly ignore this buffer)
        snd->stream_buffer[p]=(ptrszint)buffer;
        snd->stream_buffer_next=p2;
        if (!snd->stream_buffer_start) snd->stream_buffer_start=1;

    }

    qb64_sndraw_lock=0;

    return;

error:
    error(5);
    return;

}

void snd_mainloop(){

    static int64 t;
    t=-1;

    //scan through all sounds
    int32 list_index;
    for (list_index = 1; list_index<=snd_handles->indexes; list_index++) {
        snd_struct *snd = (snd_struct*)list_get(snd_handles, list_index);
        if (!snd) continue;
        if (snd->type == 2){
            if (snd->limit_state == 2){
                if (t==-1) t = GetTicks();
                if (t >= snd->limit_stop_point) {
                    snd->limit_state=0;
                    sub__sndstop(list_index);
                }
            }//limit_state==2

            if (snd->close == 1){
                //directly poll to check the sound's state
                ALint al_state;
                alGetSourcei(snd->al_seq_left_source,AL_SOURCE_STATE,&al_state);
                if (al_state==AL_INITIAL) snd->state=SND_STATE_STOPPED;
                if (al_state==AL_STOPPED) snd->state=SND_STATE_STOPPED;
                if (al_state==AL_PLAYING) snd->state=SND_STATE_PLAYING;
                if (al_state==AL_PAUSED) snd->state=SND_STATE_PAUSED;
                if (snd->state!=SND_STATE_PLAYING) snd->close=2;
            }//snd->close==1

        }//2

        if (snd->type==1){//RAW
            if (snd->close!=2){
                if (snd->stream_buffer_start){
                    static int32 repeat;
                    do{
                        repeat=0; 

                        //internal sndraw post padding
                        //note: without post padding the final, incomplete buffer of sound data would not be played
                        if (list_index==qb64_internal_sndraw_handle){//internal sound raw
                            if (snd->stream_buffer_start==snd->stream_buffer_next){//on last source buffer
                                if (snd->buffer_size>0){//partial size
                                    if (GetTicks()>(qb64_internal_sndraw_lastcall+20)){//no input received for last 0.02 seconds
                                        if (!qb64_sndraw_lock){//lock (or skip)
                                            qb64_sndraw_lock=1;
                                            if (qb64_internal_sndraw_postpad){//post-pad allowed
                                                qb64_internal_sndraw_postpad=0;
                                                while (snd->buffer_size<snd_buffer_size){
                                                    *(int16*)(snd->buffer+snd->buffer_size)=0;
                                                    snd->buffer_size+=2;
                                                    *(int16*)(snd->buffer+snd->buffer_size)=0;
                                                    snd->buffer_size+=2;
                                                }
                                                //detach buffer
                                                static uint8 *buffer;
                                                buffer=snd->buffer;
                                                //create new buffer
                                                snd->buffer=(uint8*)calloc(snd_buffer_size,1);
                                                snd->buffer_size=0;
                                                //attach detached buffer to stream (or discard it)
                                                static int32 p,p2;
                                                p=snd->stream_buffer_next; p2=p+1; if (p2>snd->stream_buffer_last) p2=1;
                                                if (p2==snd->stream_buffer_start){
                                                    free(buffer); //all buffers are full! (quietly ignore this buffer)
                                                }else{
                                                    snd->stream_buffer[p]=(ptrszint)buffer;
                                                    snd->stream_buffer_next=p2;
                                                }
                                                //next sound command to prepad if necessary to begin sound
                                                qb64_internal_sndraw_prepad=1;
                                                //unlock
                                                qb64_sndraw_lock=0;
                                            }//post-pad allowed
                                        }//lock (or skip)
                                    }//no input received for last x seconds
                                }//partial size
                            }//on last source buffer
                        }//internal sound raw


                        if (snd->stream_buffer_start!=snd->stream_buffer_next){
                            static int32 p,p2;
                            static int32 i,i2;
                            p=snd->stream_buffer_start; p2=p+1; if (p2>snd->stream_buffer_last) p2=1;

                            //unqueue processed buffers (if any)
                            static ALint buffers_processed;
                            static ALuint buffers[4];
                            alGetSourcei(snd->al_source, AL_BUFFERS_PROCESSED, &buffers_processed);
                            if (buffers_processed){
                                alSourceUnqueueBuffers(snd->al_source, buffers_processed, &buffers[0]);
                                //free associated data
                                for (i2=0;i2<buffers_processed;i2++){
                                    for (i=0;i<=3;i++){
                                        if (buffers[i2]==snd->al_buffers[i]){
                                            free((void*)snd->stream_buffer[snd->al_buffer_index[i]]);
                                            snd->al_buffer_state[i]=2;//"processed"
                                        }
                                    }
                                }
                            }

                            //check for uninitiated buffers
                            for (i=0;i<=3;i++){
                                if (snd->al_buffer_state[i]==0){
                                    snd->al_buffer_state[i]=1;//"processing"
                                    snd->al_buffer_index[i]=p;
                                    static ALuint frequency;
                                    static ALenum format;
                                    frequency=snd_frequency;
                                    format=AL_FORMAT_STEREO16;
                                    alBufferData(snd->al_buffers[i], format, (void*)snd->stream_buffer[p], snd_buffer_size, frequency);
                                    alSourceQueueBuffers(snd->al_source, 1, &snd->al_buffers[i]);
                                    static ALint al_state;
                                    alGetSourcei(snd->al_source,AL_SOURCE_STATE,&al_state);
                                    if (al_state!=AL_PLAYING){
                                        alSourcePlay(snd->al_source);
                                    }
                                    goto gotbuffer;
                                }
                            }

                            //check for finished buffers
                            for (i=0;i<=3;i++){
                                if (snd->al_buffer_state[i]==2){//"processed"
                                    static ALuint buffer;
                                    static ALuint frequency;
                                    static ALenum format;
                                    frequency=snd_frequency;
                                    format=AL_FORMAT_STEREO16;
                                    alBufferData(snd->al_buffers[i], format, (void*)snd->stream_buffer[p], snd_buffer_size, frequency);
                                    alSourceQueueBuffers(snd->al_source, 1, &snd->al_buffers[i]);
                                    static ALint al_state;
                                    alGetSourcei(snd->al_source,AL_SOURCE_STATE,&al_state);
                                    if (al_state!=AL_PLAYING){
                                        alSourcePlay(snd->al_source);
                                    }
                                    snd->al_buffer_index[i]=p;
                                    snd->al_buffer_state[i]=1;//"processing"
                                    goto gotbuffer;
                                }
                            }

                            i=-1;

gotbuffer:
                            if (i!=-1){
                                repeat=1;
                                snd->stream_buffer_start=p2;
                            }

                        }//queued buffer exists

                    }while(repeat); 

                }//started
            }//close!=2



            //close raw?
            if (snd->close==1){
                if (t==-1) t=GetTicks();
                if (t>(snd->raw_close_time+3000)){
                    static ALint al_state;
                    alGetSourcei(snd->al_source,AL_SOURCE_STATE,&al_state);
                    if (al_state==AL_INITIAL) snd->state=SND_STATE_STOPPED;
                    if (al_state==AL_STOPPED) snd->state=SND_STATE_STOPPED;
                    if (al_state==AL_PLAYING) snd->state=SND_STATE_PLAYING;
                    if (al_state==AL_PAUSED) snd->state=SND_STATE_PAUSED;
                    if (snd->state!=SND_STATE_PLAYING){//not playing
                        //note: hardware interface parts closed here, handles closed in sndclose_now
                        if (snd->al_source){
                            alDeleteSources(1,&snd->al_source); snd->al_source=0;
                        }
                        static int32 i;
                        for (i=0;i<=3;i++){
                            if (snd->al_buffers[i]) alDeleteBuffers(1,&snd->al_buffers[i]);
                        }
                        //remove the buffers
                        //1)remove 4 AL buffers
                        free(snd->al_buffers);
                        free(snd->al_buffer_index);
                        free(snd->al_buffer_state);
                        //2)remove build buffer
                        free(snd->buffer);
                        //3)remove the 65536 pointers to potential buffers
                        free(snd->stream_buffer);
                        snd->close=2;
                    }//not playing
                }//time>3 secs
            }//sndclose==1
        }//RAW
    }//list_index loop
}

int32 sndupdate_dont_free_resources=0;
void sndupdate(snd_struct *snd){

    if (snd->type==2){//seq type
        static snd_sequence_struct *seq; seq=snd->seq;
        if (snd->al_seq_left_source){
            //update state info
            static ALint al_state;
            alGetSourcei(snd->al_seq_left_source,AL_SOURCE_STATE,&al_state);
            //ref: Each source can be in one of four possible execution states: AL_INITIAL, AL_PLAYING, AL_PAUSED, AL_STOPPED
            if (al_state==AL_INITIAL) snd->state=SND_STATE_STOPPED;
            if (al_state==AL_STOPPED) snd->state=SND_STATE_STOPPED;
            if (al_state==AL_PLAYING) snd->state=SND_STATE_PLAYING;
            if (al_state==AL_PAUSED) snd->state=SND_STATE_PAUSED;
            if (snd->state==SND_STATE_STOPPED){
                if (!sndupdate_dont_free_resources){
                    if (snd->setpos_lock_release) goto no_release;
                    //###agressively free OpenAL resources (buffers & sources are very limited on some platforms)###
                    alDeleteSources(1,&snd->al_seq_left_source); snd->al_seq_left_source=0;
                    alDeleteBuffers(1,&snd->al_seq_left_buffer); snd->al_seq_left_buffer=0;
                    if (snd->al_seq_right_source) {
                        alDeleteSources(1, &snd->al_seq_right_source); snd->al_seq_right_source = 0;
                        alDeleteBuffers(1, &snd->al_seq_right_buffer); snd->al_seq_right_buffer = 0;
                    }
                    //flag updates
                    snd->volume_update=1;
                    snd->bal_update=1;
no_release:;
                }
            }
            if (snd->limit_state==2){
                if (snd->state!=SND_STATE_PLAYING) snd->limit_state=0;//disable limit
            }
            if (snd->al_seq_left_source){//still valid?
                if (snd->bal_update){
                    snd->bal_update=0;
                     alSource3f(snd->al_seq_left_source, AL_POSITION, snd->bal_left_x, snd->bal_left_y, snd->bal_left_z);
                    if (snd->al_seq_right_source) {
                        alSource3f(snd->al_seq_right_source, AL_POSITION, snd->bal_right_x, snd->bal_right_y, snd->bal_right_z);
                    }
                /*
                   OpenAL -- like OpenGL -- uses a right-handed Cartesian coordinate system (RHS),
                   where in a frontal default view X (thumb) points right, Y (index finger) points up,
                   and Z (middle finger) points towards the viewer/camera. To switch from a left handed
                   coordinate system (LHS) to a right handed coordinate systems, flip the sign on the Z coordinate.
                 */
                /* ref: old bal system code & notes
                   x distance values go from left(negative) to right(positive).
                   y distance values go from below(negative) to above(positive).
                   z distance values go from behind(negative) to in front(positive).
                   d=sqrt(x*x+y*y+z*z);
                   if (d<1) d=0;
                   if (d>1000) d=1000;
                   d=1000-d;
                   d=d/1000.0;
                   stream_volume_mult1=d;
                   --------------------
                   snd[i].posx=x; snd[i].posy=y; snd[i].posz=z;
                   d=atan2(x,z)*57.295779513;
                   if (d<0) d=360+d;
                   i2=d+0.5; if (i2==360) i2=0;//angle
                   d2=sqrt(x*x+y*y+z*z);//distance
                   if (d2<1) d2=1;
                   if (d2>999.9) d2=999.9;
                   i3=d2/3.90625;
                 */
                }//snd->bal_update
                if (snd->volume_update){
                    snd->volume_update=0;
                    alSourcef(snd->al_seq_left_source, AL_GAIN, snd->volume);
                    if (snd->al_seq_right_source) {
                        alSourcef(snd->al_seq_right_source, AL_GAIN, snd->volume);
                    }
                }//snd->volume_update
                if (snd->setpos_lock_release){
                    if (snd->state!=SND_STATE_STOPPED){
                        snd->setpos_lock_release=0;
                    }
                }
                if (snd->setpos_update){
                    snd->setpos_update=0;
                    alSourcef(snd->al_seq_left_source,AL_SEC_OFFSET,snd->setpos);
                    if (snd->al_seq_right_source) {
                        alSourcef(snd->al_seq_right_source, AL_SEC_OFFSET, snd->setpos);
                    }
                    snd->setpos_lock_release=1;
                }
            }//snd->al_seq_source still valid?
        }//snd->al_seq_source
    }//2
/*
    if (snd->type==1){
        static ALint al_state;
        alGetSourcei(snd->al_source,AL_SOURCE_STATE,&al_state);
        //ref: Each source can be in one of four possible execution states: AL_INITIAL, AL_PLAYING, AL_PAUSED, AL_STOPPED
        if (al_state==AL_INITIAL) snd->state=SND_STATE_STOPPED;
        if (al_state==AL_STOPPED) snd->state=SND_STATE_STOPPED;
        if (al_state==AL_PLAYING) snd->state=SND_STATE_PLAYING;
        if (al_state==AL_PAUSED) snd->state=SND_STATE_PAUSED;
    }
*/
}//sndupdate

int32 func__sndcopy(int32 handle){
    if (new_error) return 0;
    sndsetup();
    if (handle == 0) return 0;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal) {
        error(5);
        return 0;
    }
    if (snd->type == 2){
        sndupdate(snd);
        //increment seq references
        snd->seq->references++;
        snd_struct *snd_source = snd;
        //create new snd handle
        int32 handle = list_add(snd_handles);
        snd_struct *snd = (snd_struct *)list_get(snd_handles, handle);
        //import all data
        memcpy (snd, snd_source, sizeof(snd_struct));
        //adjust data
        snd->al_seq_left_buffer = 0;//no buffer
        snd->al_seq_left_source = 0;//no source
        snd->al_seq_right_buffer = 0;
        snd->al_seq_right_source = 0;
        snd->volume_update = 1;
        snd->state = 0;
        return handle;
    }//2
    error(5);
    return 0;
}




int32 sub__sndvol_error=0;
void sub__sndvol(int32 handle, float volume){
    if (new_error) return;
    sndsetup();
    sub__sndvol_error = 0;
    if (handle == 0) return;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    sub__sndvol_error = 1;
    if (!snd || snd->internal) {
        error(5);
        return;
    }
    snd->volume = volume;
    snd->volume_update = 1;
    sndupdate(snd);
    sub__sndvol_error = 0;
}

void sub__sndpause(int32 handle) {
    if (new_error) return;
    sndsetup();
    if (handle == 0) return;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal) {
        error(5);
        return;
    }
    if (snd->type == 2){
        sndupdate(snd);
        if (snd->al_seq_left_source && snd->state == SND_STATE_PLAYING) {
            const ALuint sources[2] = {snd->al_seq_left_source, snd->al_seq_right_source};
            alSourcePausev(snd->al_seq_right_source ? 2 : 1, sources);
            snd->state = SND_STATE_PAUSED;
            if (snd->limit_state == 2) {
                snd->limit_state = 0;
            }
        }
        return;
    }
    error(5);
}



void sub__sndstop(int32 handle){
    if (new_error) return;
    sndsetup();
    if (handle == 0) return;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal) {
        error(5);
        return;
    }
    if (snd->type == 2){
        sndupdate(snd);
        if (snd->al_seq_left_source && snd->state != SND_STATE_STOPPED) {
            const ALuint sources[2] = {snd->al_seq_left_source, snd->al_seq_right_source};
            alSourceStopv(snd->al_seq_right_source ? 2 : 1, sources);
            if (snd->limit_state == 2) {
                snd->limit_state = 0; //disable limit
            }
        }
        return;
    }//2
    error(5);
}


int32 sndplay_loop=0;
void sub__sndplay(int32 handle){
    if (new_error) return;
    sndsetup();
    if (handle == 0) return;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd){
        error(5);
        return;
    }
    if (!snd_allow_internal){
        if (snd->internal){
            error(5);
            return;
        }
    }
    if (snd->type == 2){
        snd_sequence_struct *seq; seq=snd->seq;
        if (!snd->al_seq_left_buffer){
            alGenBuffers(1, &snd->al_seq_left_buffer);
            if (!snd->al_seq_right_buffer && seq->data_right) {
                alGenBuffers(1, &snd->al_seq_right_buffer);
            }
            snd->al_seq_freq=snd_frequency;
            snd->al_seq_format = AL_FORMAT_MONO16;
            alBufferData(snd->al_seq_left_buffer, snd->al_seq_format, seq->data_left, seq->data_left_size, snd_frequency);
            if (seq->data_right) {
                alBufferData(snd->al_seq_right_buffer, snd->al_seq_format, seq->data_right, seq->data_right_size, snd_frequency);
            }
        }
        if (!snd->al_seq_left_source){
            alGenSources(1, &snd->al_seq_left_source);
            alSourcef(snd->al_seq_left_source,AL_REFERENCE_DISTANCE,0.01);
            alSourcef(snd->al_seq_left_source,AL_MAX_DISTANCE,10);
            alSourcef(snd->al_seq_left_source,AL_ROLLOFF_FACTOR,1);
            alSourcei(snd->al_seq_left_source,AL_BUFFER,snd->al_seq_left_buffer);
        }
        if (!snd->al_seq_right_source && seq->data_right) {
            alGenSources(1, &snd->al_seq_right_source);
            alSourcef(snd->al_seq_right_source,AL_REFERENCE_DISTANCE,0.01);
            alSourcef(snd->al_seq_right_source,AL_MAX_DISTANCE,10);
            alSourcef(snd->al_seq_right_source,AL_ROLLOFF_FACTOR,1);
            alSourcei(snd->al_seq_right_source,AL_BUFFER,snd->al_seq_right_buffer);
        }

        sndupdate_dont_free_resources=1;
        sndupdate(snd);
        if (snd->state==SND_STATE_PLAYING){
            sub__sndstop(handle);
            sndupdate(snd);
        }
        sndupdate_dont_free_resources=0;


        alSourcei(snd->al_seq_left_source, AL_LOOPING, sndplay_loop ? AL_TRUE : AL_FALSE);
        alSourcei(snd->al_seq_right_source, AL_LOOPING, sndplay_loop ? AL_TRUE : AL_FALSE);

        const ALuint sources[2] = {snd->al_seq_left_source, snd->al_seq_right_source};
        alSourcePlayv(snd->al_seq_right_source ? 2 : 1, sources);

        snd->state=SND_STATE_PLAYING;

        if (snd->limit_state==2) snd->limit_state=0;//disable limit
        if (snd->limit_state==1){
            snd->limit_stop_point=GetTicks()+(snd->limit_duration*1000.0);
            snd->limit_state=2;
        }

        return;
    }//2
    error(5);
}//sndplay

void sub__sndloop(int32 handle){
    sndplay_loop=1;
    sub__sndplay(handle);//call SNDPLAY with loop option set
    sndplay_loop=0;
}//sndloop

int32 func__sndpaused(int32 handle){
    if (new_error) return 0;
    sndsetup();
    if (handle==0) return 0;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal){
        error(5);
        return 0;
    }
    sndupdate(snd);
    if (snd->state == SND_STATE_PAUSED) return -1;
    return 0;
}

int32 func__sndplaying(int32 handle){
    if (new_error) return 0;
    sndsetup();
    if (handle==0) return 0;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal){
        error(5);
        return 0;
    }
    sndupdate(snd);
    if (snd->state == SND_STATE_PLAYING) return -1;
    return 0;
}

double func__sndlen(int32 handle){
    if (new_error) return 0;
    sndsetup();
    if (handle==0) return 0;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal){error(5); return 0;}
    if (snd->type==2){
        snd_sequence_struct *seq = snd->seq;
        int32 samples = seq->data_size / seq->channels / (seq->bits_per_sample / 8);
        double seconds = samples/(double)seq->sample_rate;
        return seconds;
    }//2
    error(5); return 0;
}

void sub__sndbal(int32 handle, double x, double y, double z, int32 channel, int32 passed){
    if (new_error) return;
    sndsetup();
    if (handle == 0) return;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal || channel < 1 || channel > 2){
        error(5);
        return;
    }
    if (snd->type != 2) return;
    if (!(passed & 8)) {
        channel = 1;
    }
    if (channel == 1) {
        if (passed & 1) snd->bal_left_x = x / 100;
        if (passed & 2) snd->bal_left_y = y / 100;
        if (passed & 4) snd->bal_left_z = z / 100;   
    }
    else if (channel == 2) {
        if (passed & 1) snd->bal_right_x = x / 100;
        if (passed & 2) snd->bal_right_y = y / 100;
        if (passed & 4) snd->bal_right_z = z / 100;
    }
    snd->bal_update = 1;
    sndupdate(snd);
}

double func__sndgetpos(int32 handle){
    if (new_error) return 0;
    sndsetup();
    if (handle==0) return 0;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal) {
        error(5);
        return 0;
    }
    if (snd->type==2){
        if (snd->al_seq_left_source){
            float seconds;
            alGetSourcef(snd->al_seq_left_source, AL_SEC_OFFSET, &seconds);
            return seconds;
        }
        return 0;
    }
    error(5); return 0;
}//getpos

void sub__sndsetpos(int32 handle,double seconds){
    if (new_error) return;
    sndsetup();
    if (handle == 0) return;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
        if (!snd || snd->internal || seconds < 0) {
        error(5);
        return;
    }
    if (snd->type == 2){
        snd->setpos = seconds;
        snd->setpos_update = 1;
        sndupdate(snd);
        return;
    }
    error(5);
}//setpos

void sub__sndlimit(int32 handle,double limit){
    if (new_error) return;
    sndsetup();
    if (handle==0) return;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal || limit < 0) {
        error(5);
        return;
    }
    if (snd->type == 2){
        if (limit == 0){
            snd->limit_state = 0;
            return;
        }
        sndupdate(snd);
        if (snd->state == SND_STATE_PLAYING){
            //begin count immediately
            snd->limit_stop_point = GetTicks() + (limit * 1000.0);
            snd->limit_state = 2;
        }else{
            //begin after play is called
            snd->limit_duration = limit;
            snd->limit_state=1;
        }
        return;
    }
    error(5);
}

//note: this is an internal command
void sndclose_now(int32 handle){
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (snd->type==2){
        //remove OpenAL content
        if (snd->al_seq_left_source){
            alDeleteSources(1,&snd->al_seq_left_source); snd->al_seq_left_source=0;
            alDeleteBuffers(1,&snd->al_seq_left_buffer); snd->al_seq_left_buffer=0;
        }
        if (snd->al_seq_right_source){
            alDeleteSources(1,&snd->al_seq_right_source); snd->al_seq_right_source=0;
            alDeleteBuffers(1,&snd->al_seq_right_buffer); snd->al_seq_right_buffer=0;
        }
        //remove sound handle
        list_remove(snd_handles,handle);

        //remove seq
        if (snd->seq->references > 1){
            snd->seq->references--;
        }else{
            if (snd->seq->data) free(snd->seq->data);
            if (snd->seq->data_left != snd->seq->data) free(snd->seq->data_left);
            if (snd->seq->data_right) free(snd->seq->data_right);
            list_remove(snd_sequences, list_get_index(snd_sequences, snd->seq));
        }
    }//2

    if (snd->type==1){
        list_remove(snd_handles,handle);
    }

}//sndclose_now

void sub__sndclose(int32 handle){
    if (new_error) return;
    sndsetup();
    if (handle==0) return;//default response
    snd_struct *snd = (snd_struct*)list_get(snd_handles, handle);
    if (!snd || snd->internal) {
        error(5);
        return;
    }
    sndupdate(snd);
    snd->internal = 1;//switch to internal, no more commands related to this sound can be accepted
    if (snd->type == 2){
        if (snd->state==SND_STATE_PLAYING){
            snd->close=1;//close when finished playing
        }else{
            sndclose_now(handle);
        }
        return;
    }
    snd->close=1;//raw
    snd->raw_close_time=GetTicks();
}//sndclose

//"macros"

void sub__sndplaycopy(int32 handle,double volume,int32 passed){
    if (new_error) return;
    sndsetup();
    int32 handle2;
    handle2=func__sndcopy(handle);
    if (!handle2) return;//an error has already happened
    if (passed){
        sub__sndvol(handle2,volume);
        if (sub__sndvol_error){
            sub__sndclose(handle2);
            return;
        }
    }
    sub__sndplay(handle2);
    sub__sndclose(handle2);
}



//uint8 *soundwave(double frequency,double length,double volume,double fadein,double fadeout,uint8 *data);
//uint8 *soundwavesilence(double length,uint8 *data);

int32 func_play(int32 ignore){
    return 0;
}

/*
Formats:
A[#|+|-][0-64]
0-64 is like temp. Lnumber, 0 is whatever the current default is
 */
void sub_play(qbs *str){
    sndsetup();
    static uint8 *b,*wave,*wave2,*wave3;
    static double d;
    static int32 i,bytes_left,a,x,x2,x3,x4,x5,wave_bytes,wave_base;
    static int32 o=4;
    static double t=120;//quarter notes per minute (120/60=2 per second)
    static double l=4;
    static double pause=1.0/8.0;//ML 0.0, MN 1.0/8.0, MS 1.0/4.0
    static double length,length2;//derived from l and t
    static double frequency;
    static double mb=0;
    static double v=50;

    static int32 n;//the semitone-intervaled note to be played
    static int32 n_changed;//+,#,- applied?
    static int64 number;
    static int32 number_entered;
    static int32 followup;//1=play note
    static int32 playit;
    static uint32 handle=NULL;
    static int32 fullstops=0;
    b=str->chr;
    bytes_left=str->len;
    wave=NULL;
    wave_bytes=0;
    n_changed=0;
    n=0;
    number_entered=0;
    number=0;
    followup=0;
    length=1.0/(t/60.0)*(4.0/l);
    playit=0;
    wave_base=0;//point at which new sounds will be inserted

next_byte:
    if ((bytes_left--)||followup){

        if (bytes_left<0){i=32; goto follow_up;}

        i=*b++;
        if (i==32) goto next_byte;
        if (i>=97&&i<=122) a=i-32; else a=i;

        if (i==61){//= (+VARPTR$)
            if (fullstops){error(5); return;}
            if (number_entered){error(5); return;}
            number_entered=2;
            //VARPTR$ reference
            /*
               'BYTE=1
               'INTEGER=2
               'STRING=3 SUB-STRINGS must use "X"+VARPTR$(string$)
               'SINGLE=4
               'INT64=5
               'FLOAT=6
               'DOUBLE=8
               'LONG=20
               'BIT=64+n
             */
            if (bytes_left<3){error(5); return;}
            i=*b++; bytes_left--;//read type byte
            x=*(uint16*)b; b+=2; bytes_left-=2;//read offset within DBLOCK
            //note: allowable _BIT type variables in VARPTR$ are all at a byte offset and are all
            //      padded until the next byte
            d=0;
            switch(i){
                case 1:
                    d=*(char*)(dblock+x);
                    break;
                case (1+128):
                    d=*(uint8*)(dblock+x);
                    break;
                case 2:
                    d=*(int16*)(dblock+x);
                    break;
                case (2+128):
                    d=*(uint16*)(dblock+x);
                    break;
                case 4:
                    d=*(float*)(dblock+x);
                    break;
                case 5:
                    d=*(int64*)(dblock+x);
                    break;
                case (5+128):
                    d=*(int64*)(dblock+x); //unsigned conversion is unsupported!
                    break;
                case 6:
                    d=*(long double*)(dblock+x);
                    break;
                case 8:
                    d=*(double*)(dblock+x);
                    break;
                case 20:
                    d=*(int32*)(dblock+x);
                    break;
                case (20+128):
                    d=*(uint32*)(dblock+x);
                    break;
                default:
                    //bit type?
                    if ((i&64)==0){error(5); return;}
                    x2=i&63;
                    if (x2>56){error(5); return;}//valid number of bits?
                    //create a mask
                    static int64 i64num,mask,i64x;
                    mask=(((int64)1)<<x2)-1;
                    i64num=(*(int64*)(dblock+x))&mask;
                    //signed?
                    if (i&128){
                        mask=((int64)1)<<(x2-1);
                        if (i64num&mask){//top bit on?
                            mask=-1; mask<<=x2; i64num+=mask;
                        }
                    }//signed
                    d=i64num;
            }
            if (d>2147483647.0||d<-2147483648.0){error(5); return;}//out of range value!
            number=qbr_double_to_long(d);
            goto next_byte;
        }

        //read in a number
        if ((i>=48)&&(i<=57)){
            if (fullstops||(number_entered==2)){error(5); return;}
            if (!number_entered){number=0; number_entered=1;}
            number=number*10+i-48;
            goto next_byte;
        }

        //read fullstops
        if (i==46){
            if (followup!=7&&followup!=1&&followup!=4){error(5); return;}
            fullstops++;
            goto next_byte;
        }

follow_up:

        if (followup==8){//V...
            if (!number_entered){error(5); return;}
            number_entered=0;
            if (number>100){error(5); return;}
            v=number;
            followup=0; if (bytes_left<0) goto done;
        }//8

        if (followup==7){//P...
            if (number_entered){
                number_entered=0;
                if (number<1||number>64){error(5); return;}
                length2=1.0/(t/60.0)*(4.0/((double)number));
            }else{
                length2=length;
            }
            d=length2; for (x=1;x<=fullstops;x++){d/=2.0; length2=length2+d;} fullstops=0;

            soundwave_bytes=wavesize(length2);
            if (!wave){
                //create buffer
                wave=(uint8*)calloc(soundwave_bytes,1); wave_bytes=soundwave_bytes;
                wave_base=0;
            }else{
                //increase buffer?
                if ((wave_base+soundwave_bytes)>wave_bytes){
                    wave=(uint8*)realloc(wave,wave_base+soundwave_bytes);
                    memset(wave+wave_base,0,wave_base+soundwave_bytes-wave_bytes);
                    wave_bytes=wave_base+soundwave_bytes;
                }
            }
            if (i!=44){
                wave_base+=soundwave_bytes;
            }

            playit=1;
            followup=0;
            if (i==44) goto next_byte;
            if (bytes_left<0) goto done;
        }//7

        if (followup==6){//T...
            if (!number_entered){error(5); return;}
            number_entered=0;
            if (number<32||number>255){number=120;}
            t=number;
            length=1.0/(t/60.0)*(4.0/l);
            followup=0; if (bytes_left<0) goto done;
        }//6

        if (followup==5){//M...
            if (number_entered){error(5); return;}
            switch(a){
                case 76://L
                    pause=0;
                    break;
                case 78://N
                    pause=1.0/8.0;
                    break;
                case 83://S
                    pause=1.0/4.0;
                    break;

                case 66://MB
                    if (!mb){
                        mb=1;
                        if (playit){
                            playit=0;
                            qb64_internal_sndraw(wave,wave_bytes,1);
                        }
                        wave=NULL;
                    }
                    break;
                case 70://MF
                    if (mb){
                        mb=0;
                        //preceding MB content incorporated into MF block
                    }
                    break;
                default:
                    error(5); return;
            }
            followup=0; goto next_byte;
        }//5

        if (followup==4){//N...
            if (!number_entered){error(5); return;}
            number_entered=0;
            if (number>84){error(5); return;}
            n=-33+number;
            goto followup1;
            followup=0; if (bytes_left<0) goto done;
        }//4

        if (followup==3){//O...
            if (!number_entered){error(5); return;}
            number_entered=0;
            if (number>6){error(5); return;}
            o=number;
            followup=0; if (bytes_left<0) goto done;
        }//3

        if (followup==2){//L...
            if (!number_entered){error(5); return;}
            number_entered=0;
            if (number<1||number>64){error(5); return;}
            l=number;
            length=1.0/(t/60.0)*(4.0/l);
            followup=0; if (bytes_left<0) goto done;
        }//2

        if (followup==1){//A-G...
            if (i==45){//-
                if (n_changed||number_entered){error(5); return;}
                n_changed=1; n--;
                goto next_byte;
            }
            if (i==43||i==35){//+,#
                if (n_changed||number_entered){error(5); return;}
                n_changed=1; n++;
                goto next_byte;
            }
followup1:
            if (number_entered){
                number_entered=0;
                if (number<0||number>64){error(5); return;}
                if (!number) length2=length; else length2=1.0/(t/60.0)*(4.0/((double)number));
            }else{
                length2=length;
            }//number_entered
            d=length2; for (x=1;x<=fullstops;x++){d/=2.0; length2=length2+d;} fullstops=0;
            //frequency=(2^(note/12))*440
            frequency=pow(2.0,((double)n)/12.0)*440.0;

            //create wave
            wave2=soundwave(frequency,length2*(1.0-pause),v/100.0,NULL,NULL);
            if (pause>0){
                wave2=(uint8*)realloc(wave2,soundwave_bytes+wavesize(length2*pause));
                memset(wave2+soundwave_bytes,0,wavesize(length2*pause));
                soundwave_bytes+=wavesize(length2*pause);
            }

            if (!wave){
                //adopt buffer
                wave=wave2; wave_bytes=soundwave_bytes;
                wave_base=0;
            }else{
                //mix required?
                if (wave_base==wave_bytes) x=0; else x=1;
                //increase buffer?
                if ((wave_base+soundwave_bytes)>wave_bytes){
                    wave=(uint8*)realloc(wave,wave_base+soundwave_bytes);
                    memset(wave+wave_base,0,wave_base+soundwave_bytes-wave_bytes);
                    wave_bytes=wave_base+soundwave_bytes;
                }
                //mix or copy
                if (x){
                    //mix
                    static int16 *sp,*sp2;
                    sp=(int16*)(wave+wave_base);
                    sp2=(int16*)wave2;
                    x2=soundwave_bytes/2;
                    for (x=0;x<x2;x++){
                        x3=*sp2++;
                        x4=*sp;
                        x4+=x3;
                        if (x4>32767) x4=32767;
                        if (x4<-32767) x4=-32767;
                        *sp++=x4;
                    }//x 
                }else{
                    //copy
                    memcpy(wave+wave_base,wave2,soundwave_bytes);  
                }//x
                free(wave2);
            }
            if (i!=44){
                wave_base+=soundwave_bytes;
            }

            playit=1;
            n_changed=0;
            followup=0; 
            if (i==44) goto next_byte;
            if (bytes_left<0) goto done;
        }//1

        if (a>=65&&a<=71){
            //modify a to represent a semitonal note (n) interval
            switch(a){
                //[c][ ][d][ ][e][f][ ][g][ ][a][ ][b]
                // 0  1  2  3  4  5  6  7  8  9  0  1
                case 65: n=9; break;
                case 66: n=11; break;
                case 67: n=0; break;
                case 68: n=2; break;
                case 69: n=4; break;
                case 70: n=5; break;
                case 71: n=7; break;
            }
            n=n+(o-2)*12-9;
            followup=1;
            goto next_byte;
        }//a

        if (a==76){//L
            followup=2;
            goto next_byte;
        }

        if (a==77){//M
            followup=5;
            goto next_byte;
        }

        if (a==78){//N
            followup=4;
            goto next_byte;
        }

        if (a==79){//O
            followup=3;
            goto next_byte;
        }

        if (a==84){//T
            followup=6;
            goto next_byte;
        }

        if (a==60){//<
            o--; if (o<0) o=0;
            goto next_byte;
        }

        if (a==62){//>
            o++; if (o>6) o=6;
            goto next_byte;
        }

        if (a==80){//P
            followup=7;
            goto next_byte;
        }

        if (a==86){//V
            followup=8;
            goto next_byte;
        }

        error(5); return;
    }//bytes_left
done:
    if (number_entered||followup){error(5); return;}//unhandled data

    if (playit){
        if (mb){
            qb64_internal_sndraw(wave,wave_bytes,0);
        }else{
            qb64_internal_sndraw(wave,wave_bytes,1);
        }
    }//playit

}

int32 func__sndrate(){
    return snd_frequency;
}

void qb64_generatesound(double f,double l,uint8 block){
    sndsetup();
    static uint8* data;
    data=soundwave(f,l,1,0,0);
    qb64_internal_sndraw(data,soundwave_bytes,block);
}



void qb64_internal_sndraw(uint8* data,int32 bytes,int32 block){//data required in 16bit stereo at native frequency, data is freed
    sndsetup();
    static int32 i;
    if (qb64_internal_sndraw_handle==0){
        qb64_internal_sndraw_handle=func__sndopenraw();
        qb64_internal_sndraw_prepad=1;
    }


    int64 buffered_ms;
    if (block){
        buffered_ms=func__sndrawlen(qb64_internal_sndraw_handle,1)*1000.0;
        buffered_ms=((double)buffered_ms)*0.95;//take 95% of actual length to allow time for processing of new content
        buffered_ms-=250;//allow for latency (call frequency and pre/post pad)
        if (buffered_ms<0) buffered_ms=0;
    }

    if (qb64_internal_sndraw_prepad){
        qb64_internal_sndraw_prepad=0;
        //pad initial buffer so that first sound is played immediately
        static int32 snd_buffer_size_samples;
        snd_buffer_size_samples=snd_buffer_size/2/2;
        static int32 n;
        n=snd_buffer_size_samples-(bytes/2/2);
        if (n>0){
            for (i=0;i<n;i++){
                sub__sndraw(0,0,qb64_internal_sndraw_handle,2);
            }
        }
    }
    //move data into sndraw handle
    for (i=0;i<bytes;i+=4){
        sub__sndraw( (float)((int16*)(data+i))[0]/32768.0 ,(float)((int16*)(data+i))[1]/32768.0,qb64_internal_sndraw_handle,1+2);
    }
    qb64_internal_sndraw_postpad=1;
    free(data);//free the sound data

    if (block){
        int64 length_ms;
        length_ms=(((bytes/2/2)*1000)/snd_frequency);//length in ms
        length_ms=((double)length_ms)*0.95;//take 95% of actual length to allow time for processing of new content
        length_ms-=250;//allow for latency (call frequency and pre/post pad)
        if (length_ms>0){
            sub__delay(((double)length_ms+(double)buffered_ms)/1000.0);
        }
    }

}


double func__sndrawlen(int32 handle,int32 passed){
    if (passed){
        if (handle==0) return 0;
    }else{
        if (!snd_raw_channel) return 0;
        handle=snd_raw_channel;
    }
    static snd_struct *snd; snd=(snd_struct*)list_get(snd_handles,handle);
    if (!snd) goto error;
    if (snd->internal) goto error;
    if (snd->type!=1) goto error;
    if (!snd->stream_buffer_start) return 0;
    //count buffered source buffers
    static int32 source_buffers;
    source_buffers=0;
    static int32 i;
    i=snd->stream_buffer_start;
    while(i!=snd->stream_buffer_next){
        source_buffers++; i++; if (i>snd->stream_buffer_last) i=1;
    }
    //count dest buffers
    static int32 dest_buffers;
    dest_buffers=0;
    for (i=0;i<=3;i++){
        if (snd->al_buffer_state[i]==1) dest_buffers++;
    }

	static double result;
	result = ((double)((dest_buffers+source_buffers)*(snd_buffer_size/2/2)))/(double)snd_frequency;
	if (result < .375) result = 0; //hack to reenable _SNDRAWLEN, which gets stuck at .3715192763764172
    return result;

error:
    error(5);
    return 0;
}

void sub__sndrawdone(int32 handle,int32 passed){
    if (passed){
        if (handle==0) return;
    }else{
        if (!snd_raw_channel) return;
        handle=snd_raw_channel;
    }
    static snd_struct *snd; snd=(snd_struct*)list_get(snd_handles,handle);
    if (!snd) goto error;
    if (snd->internal) goto error;
    if (snd->type!=1) goto error;
    if (snd->buffer_size>0){//partial size
        if (!qb64_sndraw_lock){//lock (or skip)
            qb64_sndraw_lock=1;
            while (snd->buffer_size<snd_buffer_size){
                *(int16*)(snd->buffer+snd->buffer_size)=0;
                snd->buffer_size+=2;
                *(int16*)(snd->buffer+snd->buffer_size)=0;
                snd->buffer_size+=2;
            }
            //detach buffer
            static uint8 *buffer;
            buffer=snd->buffer;
            //create new buffer
            snd->buffer=(uint8*)calloc(snd_buffer_size,1);
            snd->buffer_size=0;
            //attach detached buffer to stream (or discard it)
            static int32 p,p2;
            p=snd->stream_buffer_next; p2=p+1; if (p2>snd->stream_buffer_last) p2=1;
            if (p2==snd->stream_buffer_start){
                free(buffer); //all buffers are full! (quietly ignore this buffer)
            }else{
                snd->stream_buffer[p]=(ptrszint)buffer;
                snd->stream_buffer_next=p2;
                if (!snd->stream_buffer_start) snd->stream_buffer_start=1;
            }
            //unlock
            qb64_sndraw_lock=0;
        }//lock (or skip)
    }//partial size
    return;

error:
    error(5);
    return;
}

#endif
