1
1
Fork 0
mirror of https://github.com/QB64Official/qb64.git synced 2024-07-19 06:25:15 +00:00
qb64/internal/c/parts/audio/out/src.c
FellippeHeitor 7d3f1f9833 Temporary hack to reenable _SNDRAWLEN.
_SNDRAWLEN currently gets stuck at .3715192763764172; this reenables it by returning zero when < .375.
2017-09-09 13:17:03 -03:00

1639 lines
56 KiB
C

#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