mirror of
https://github.com/QB64Official/qb64.git
synced 2024-08-22 03:15:07 +00:00
1646 lines
56 KiB
C
1646 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{
|
|
void *lock_offset;
|
|
int64 lock_id;
|
|
|
|
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) goto done;
|
|
ctx = alcCreateContext(dev, NULL); if (!ctx) goto done;
|
|
alcMakeContextCurrent(ctx);
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
done:;
|
|
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();
|
|
|
|
if (snd->lock_id){
|
|
free_mem_lock((mem_lock*)snd->lock_offset);//untag
|
|
}
|
|
}//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
|