--- sbbs/src/xpdev/xpbeep.c 2018/04/24 16:41:24 1.1 +++ sbbs/src/xpdev/xpbeep.c 2018/04/24 16:45:34 1.1.1.2 @@ -1,7 +1,11 @@ -/* $Id: xpbeep.c,v 1.1 2018/04/24 16:41:24 root Exp $ */ +/* $Id: xpbeep.c,v 1.1.1.2 2018/04/24 16:45:34 root Exp $ */ + +/* TODO: USE PORTAUDIO! */ /* standard headers */ #include +#include +#include "xp_dl.h" #if defined(_WIN32) #include @@ -20,6 +24,7 @@ #endif #endif #ifdef USE_ALSA_SOUND + #include #include #endif /* KIOCSOUND */ @@ -43,17 +48,35 @@ #endif /* xpdev headers */ -#ifdef WITH_SDL +#ifdef WITH_PORTAUDIO +#include +#endif + +#ifdef WITH_SDL_AUDIO #include "sdlfuncs.h" #endif + #include "genwrap.h" #include "xpbeep.h" #define S_RATE 22050 +#ifdef XPDEV_THREAD_SAFE +#include "threadwrap.h" + +static BOOL sample_thread_running=FALSE; +static sem_t sample_pending_sem; +static sem_t sample_complete_sem; +static BOOL sample_initialized=FALSE; +static pthread_mutex_t sample_mutex; +static const unsigned char *sample_buffer; +static size_t sample_size; +#endif + static BOOL sound_device_open_failed=FALSE; static BOOL alsa_device_open_failed=FALSE; static BOOL sdl_device_open_failed=FALSE; +static BOOL portaudio_device_open_failed=FALSE; enum { SOUND_DEVICE_CLOSED @@ -61,26 +84,59 @@ enum { ,SOUND_DEVICE_ALSA ,SOUND_DEVICE_OSS ,SOUND_DEVICE_SDL + ,SOUND_DEVICE_PORTAUDIO }; static int handle_type=SOUND_DEVICE_CLOSED; -#ifdef WITH_SDL +#ifdef WITH_PORTAUDIO +static PaStream *portaudio_stream; +static int portaudio_buf_len=0; +static int portaudio_buf_pos=0; +static const unsigned char *pawave; +static int portaudio_initialized=FALSE; +#ifndef PaStream // Detect version... defined for 1.8 and not for 1.9 +#define PortAudioCallback void +#define PaTimestamp PaTime +#endif +struct portaudio_api_struct { + PaError (*init)( void ); + PaError (*open)( PaStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + PaError (*close)( PaStream* ); + PaError (*start)( PaStream *stream ); + PaError (*stop)( PaStream *stream ); + PaError (*active)( PaStream *stream ); + PaError (*write)( PaStream *stream, const void *buf, unsigned long frames ); + int (*version)( void ); + int ver; +}; +struct portaudio_api_struct *pa_api=NULL; +#endif + +#ifdef WITH_SDL_AUDIO static SDL_AudioSpec spec; static int sdl_audio_buf_len=0; static int sdl_audio_buf_pos=0; -static unsigned char swave[S_RATE*15/2+1]; +static unsigned char *swave; static SDL_sem *sdlToneDone; #endif #ifdef _WIN32 static HWAVEOUT waveOut; static WAVEHDR wh; -static unsigned char wave[S_RATE*15/2+1]; #endif #ifdef USE_ALSA_SOUND static snd_pcm_t *playback_handle; +static snd_pcm_hw_params_t *hw_params=NULL; #endif #ifdef AFMT_U8 @@ -116,6 +172,8 @@ struct alsa_api_struct { (snd_pcm_t *pcm); snd_pcm_sframes_t (*snd_pcm_writei) (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size); + int (*snd_pcm_drain) + (snd_pcm_t *pcm); }; struct alsa_api_struct *alsa_api=NULL; @@ -190,7 +248,34 @@ void makewave(double freq, unsigned char } } -#ifdef WITH_SDL +#ifdef WITH_PORTAUDIO +/* + * Used by v18 library, not v19! + */ +static int portaudio_callback(void *inputBuffer + , void *outputBuffer + , unsigned long framesPerBuffer + , const PaTimestamp outTime + , void *userData ) +{ + int copylen=framesPerBuffer; + int maxlen=portaudio_buf_len-portaudio_buf_pos; + + if(copylen>maxlen) { + copylen=maxlen; + memset(((char *)outputBuffer)+copylen, 128, framesPerBuffer-copylen); + } + if(copylen) { + memcpy(outputBuffer, (*((unsigned char **)userData))+portaudio_buf_pos, copylen); + portaudio_buf_pos+=copylen; + } + if(portaudio_buf_pos >= portaudio_buf_len) + return(1); + return(0); +} +#endif + +#ifdef WITH_SDL_AUDIO void sdl_fillbuf(void *userdata, Uint8 *stream, int len) { int copylen=len; @@ -232,7 +317,75 @@ BOOL xptone_open(void) if(handle_type!=SOUND_DEVICE_CLOSED) return(TRUE); -#ifdef WITH_SDL +#ifdef WITH_PORTAUDIO + if(!portaudio_device_open_failed) { + if(pa_api==NULL) { + dll_handle dl; + const char *libnames[]={"portaudio",NULL}; + if(((pa_api=(struct portaudio_api_struct *)malloc(sizeof(struct portaudio_api_struct)))==NULL) + || ((dl=xp_dlopen(libnames,RTLD_LAZY,0))==NULL) + || ((pa_api->init=xp_dlsym(dl,Pa_Initialize))==NULL) + || ((pa_api->open=xp_dlsym(dl,Pa_OpenDefaultStream))==NULL) + || ((pa_api->close=xp_dlsym(dl,Pa_CloseStream))==NULL) + || ((pa_api->start=xp_dlsym(dl,Pa_StartStream))==NULL) + || + ( + ((pa_api->active=xp_dlsym(dl,Pa_StreamActive))==NULL) + && ((pa_api->active=xp_dlsym(dl,Pa_IsStreamActive))==NULL) + ) + || ((pa_api->stop=xp_dlsym(dl,Pa_StopStream))==NULL) + ) { + if(dl) + xp_dlclose(dl); + free(pa_api); + pa_api=NULL; + } + else { + /* Get version and other optional pointers */ + pa_api->ver=1800; + if((pa_api->version=xp_dlsym(dl, Pa_GetVersion))!=NULL) { + pa_api->ver=pa_api->version(); + if(pa_api->ver >= 1899) { + if((pa_api->write=xp_dlsym(dl, Pa_WriteStream))==NULL) { + xp_dlclose(dl); + free(pa_api); + pa_api=NULL; + } + } + } + } + if(pa_api==NULL) { + portaudio_device_open_failed=TRUE; + } + } + if(pa_api != NULL) { + if(!portaudio_initialized) { + if(pa_api->init() != paNoError) + portaudio_device_open_failed=TRUE; + else + portaudio_initialized=TRUE; + } + if(portaudio_initialized) { + if(pa_api->open(&portaudio_stream + , 0 /* No input */ + , 1 /* Mono output */ + , paUInt8 + , S_RATE + , 256 + , 0 + , pa_api->ver >= 1899 ? NULL : portaudio_callback + , &pawave) != paNoError) + portaudio_device_open_failed=TRUE; + else { + handle_type=SOUND_DEVICE_PORTAUDIO; + return(TRUE); + } + } + } + } +#endif + +#ifdef WITH_SDL_AUDIO if(!sdl_device_open_failed) { if(init_sdl_audio()==-1) sdl_device_open_failed=TRUE; @@ -273,13 +426,8 @@ BOOL xptone_open(void) if(sound_device_open_failed) return(FALSE); memset(&wh, 0, sizeof(wh)); - wh.lpData=wave; + wh.lpData=NULL; wh.dwBufferLength=S_RATE*15/2+1; - if(waveOutPrepareHeader(waveOut, &wh, sizeof(wh))!=MMSYSERR_NOERROR) { - sound_device_open_failed=TRUE; - waveOutClose(waveOut); - return(FALSE); - } handle_type=SOUND_DEVICE_WIN32; if(!sound_device_open_failed) return(TRUE); @@ -289,33 +437,41 @@ BOOL xptone_open(void) #ifdef USE_ALSA_SOUND if(!alsa_device_open_failed) { if(alsa_api==NULL) { + dll_handle dl; + const char *libnames[]={"asound", NULL}; if(((alsa_api=(struct alsa_api_struct *)malloc(sizeof(struct alsa_api_struct)))==NULL) - || ((dl=dlopen("libasound.so",RTLD_LAZY))==NULL) - || ((alsa_api->snd_pcm_open=dlsym(dl,"snd_pcm_open"))==NULL) - || ((alsa_api->snd_pcm_hw_params_malloc=dlsym(dl,"snd_pcm_hw_params_malloc"))==NULL) - || ((alsa_api->snd_pcm_hw_params_any=dlsym(dl,"snd_pcm_hw_params_any"))==NULL) - || ((alsa_api->snd_pcm_hw_params_set_access=dlsym(dl,"snd_pcm_hw_params_set_access"))==NULL) - || ((alsa_api->snd_pcm_hw_params_set_format=dlsym(dl,"snd_pcm_hw_params_set_format"))==NULL) - || ((alsa_api->snd_pcm_hw_params_set_rate_near=dlsym(dl,"snd_pcm_hw_params_set_rate_near"))==NULL) - || ((alsa_api->snd_pcm_hw_params_set_channels=dlsym(dl,"snd_pcm_hw_params_set_channels"))==NULL) - || ((alsa_api->snd_pcm_hw_params=dlsym(dl,"snd_pcm_hw_params"))==NULL) - || ((alsa_api->snd_pcm_prepare=dlsym(dl,"snd_pcm_prepare"))==NULL) - || ((alsa_api->snd_pcm_hw_params_free=dlsym(dl,"snd_pcm_hw_params_free"))==NULL) - || ((alsa_api->snd_pcm_close=dlsym(dl,"snd_pcm_close"))==NULL) - || ((alsa_api->snd_pcm_writei=dlsym(dl,"snd_pcm_writei"))==NULL) - || ((alsa_api->=dlsym(dl,""))==NULL)) { + || ((dl=xp_dlopen(libnames,RTLD_LAZY,2))==NULL) + || ((alsa_api->snd_pcm_open=xp_dlsym(dl,snd_pcm_open))==NULL) + || ((alsa_api->snd_pcm_hw_params_malloc=xp_dlsym(dl,snd_pcm_hw_params_malloc))==NULL) + || ((alsa_api->snd_pcm_hw_params_any=xp_dlsym(dl,snd_pcm_hw_params_any))==NULL) + || ((alsa_api->snd_pcm_hw_params_set_access=xp_dlsym(dl,snd_pcm_hw_params_set_access))==NULL) + || ((alsa_api->snd_pcm_hw_params_set_format=xp_dlsym(dl,snd_pcm_hw_params_set_format))==NULL) + || ((alsa_api->snd_pcm_hw_params_set_rate_near=xp_dlsym(dl,snd_pcm_hw_params_set_rate_near))==NULL) + || ((alsa_api->snd_pcm_hw_params_set_channels=xp_dlsym(dl,snd_pcm_hw_params_set_channels))==NULL) + || ((alsa_api->snd_pcm_hw_params=xp_dlsym(dl,snd_pcm_hw_params))==NULL) + || ((alsa_api->snd_pcm_prepare=xp_dlsym(dl,snd_pcm_prepare))==NULL) + || ((alsa_api->snd_pcm_hw_params_free=xp_dlsym(dl,snd_pcm_hw_params_free))==NULL) + || ((alsa_api->snd_pcm_close=xp_dlsym(dl,snd_pcm_close))==NULL) + || ((alsa_api->snd_pcm_writei=xp_dlsym(dl,snd_pcm_writei))==NULL) + || ((alsa_api->snd_pcm_drain=xp_dlsym(dl,snd_pcm_drain))==NULL) + ) { + if(dl) + xp_dlclose(dl); + free(alsa_api); + alsa_api=NULL; alsa_device_open_failed=TRUE; } if(alsa_api==NULL) alsa_device_open_failed=TRUE; } if(alsa_api!=NULL) { - if((alsa_api->snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)<0) + int rate=S_RATE; + if((alsa_api->snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0)<0) || (alsa_api->snd_pcm_hw_params_malloc(&hw_params)<0) || (alsa_api->snd_pcm_hw_params_any(playback_handle, hw_params)<0) || (alsa_api->snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) || (alsa_api->snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_U8) < 0) - || (alsa_api->snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, S_RATE, 0) < 0) + || (alsa_api->snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &rate, 0) < 0) || (alsa_api->snd_pcm_hw_params_set_channels(playback_handle, hw_params, 1) < 0) || (alsa_api->snd_pcm_hw_params(playback_handle, hw_params) < 0) || (alsa_api->snd_pcm_prepare(playback_handle) < 0)) { @@ -328,6 +484,7 @@ BOOL xptone_open(void) } } else { + alsa_api->snd_pcm_hw_params_free(hw_params); handle_type=SOUND_DEVICE_ALSA; return(TRUE); } @@ -367,7 +524,13 @@ BOOL xptone_open(void) BOOL xptone_close(void) { -#ifdef WITH_SDL +#ifdef WITH_PORTAUDIO + if(handle_type==SOUND_DEVICE_PORTAUDIO) { + pa_api->close(portaudio_stream); + } +#endif + +#ifdef WITH_SDL_AUDIO if(handle_type==SOUND_DEVICE_SDL) { sdl.CloseAudio(); sdl.SDL_DestroySemaphore(sdlToneDone); @@ -377,14 +540,14 @@ BOOL xptone_close(void) #ifdef _WIN32 if(handle_type==SOUND_DEVICE_WIN32) { waveOutClose(waveOut); - while(waveOutUnprepareHeader(waveOut, &wh, sizeof(wh))==WAVERR_STILLPLAYING) - SLEEP(1); } #endif #ifdef USE_ALSA_SOUND - if(handle_type==SOUND_DEVICE_ALSA) + if(handle_type==SOUND_DEVICE_ALSA) { alsa_api->snd_pcm_close (playback_handle); + playback_handle=NULL; + } #endif #ifdef AFMT_U8 @@ -399,23 +562,175 @@ BOOL xptone_close(void) return(TRUE); } -/********************************************************************************/ -/* Play a tone through the wave/DSP output device (sound card) - Deuce */ -/********************************************************************************/ - -BOOL DLLCALL xptone(double freq, DWORD duration, enum WAVE_SHAPE shape) +#ifdef XPDEV_THREAD_SAFE +void xp_play_sample_thread(void *data) { BOOL must_close=FALSE; + BOOL posted_last=TRUE; + BOOL waited=FALSE; -#if defined(USE_ALSA_SOUND) || defined(AFMT_U8) - unsigned char wave[S_RATE*15/2+1]; - int samples; +#ifdef AFMT_U8 + int wr; + int i; #endif -#ifdef USE_ALSA_SOUND - snd_pcm_hw_params_t *hw_params=NULL; - void *dl; -#endif + sample_thread_running=TRUE; + while(1) { + if(!waited) { + if(sem_wait(&sample_pending_sem)!=0) + goto error_return; + } + else + waited=FALSE; + posted_last=FALSE; + if(pthread_mutex_lock(&sample_mutex)!=0) + goto error_return; + + if(handle_type==SOUND_DEVICE_CLOSED) { + must_close=TRUE; + if(!xptone_open()) { + sem_post(&sample_complete_sem); + pthread_mutex_unlock(&sample_mutex); + continue; + } + } + + #ifdef WITH_PORTAUDIO + if(handle_type==SOUND_DEVICE_PORTAUDIO) { + if(pa_api->ver >= 1899) { + pa_api->write(portaudio_stream, sample_buffer, sample_size); + } + else { + pawave=sample_buffer; + portaudio_buf_pos=0; + portaudio_buf_len=sample_size; + pa_api->start(portaudio_stream); + } + while(pa_api->active(portaudio_stream)) + SLEEP(1); + pa_api->stop(portaudio_stream); + } + #endif + + #ifdef WITH_SDL_AUDIO + if(handle_type==SOUND_DEVICE_SDL) { + sdl.LockAudio(); + swave=sample_buffer; + sdl_audio_buf_pos=0; + sdl_audio_buf_len=sample_size; + sdl.UnlockAudio(); + sdl.SemWait(sdlToneDone); + } + #endif + + #ifdef _WIN32 + if(handle_type==SOUND_DEVICE_WIN32) { + wh.lpData=sample_buffer; + wh.dwBufferLength=sample_size; + if(waveOutPrepareHeader(waveOut, &wh, sizeof(wh))==MMSYSERR_NOERROR) { + if(waveOutWrite(waveOut, &wh, sizeof(wh))==MMSYSERR_NOERROR) { + while(!(wh.dwFlags & WHDR_DONE)) + SLEEP(1); + while(waveOutUnprepareHeader(waveOut, &wh, sizeof(wh))==WAVERR_STILLPLAYING) + SLEEP(1); + } + } + } + #endif + + #ifdef USE_ALSA_SOUND + if(handle_type==SOUND_DEVICE_ALSA) { + int ret; + int written=0; + + while(written < sample_size) { + ret=alsa_api->snd_pcm_writei(playback_handle, sample_buffer+written, sample_size-written); + if(ret < 0) { + if(written==0) { + /* Go back and try OSS */ + xptone_close(); + alsa_device_open_failed=TRUE; + xptone_open(); + } + break; + } + written += ret; + } + if(!alsa_device_open_failed) { + while(alsa_api->snd_pcm_drain(playback_handle)) + SLEEP(1); + } + } + #endif + + #ifdef AFMT_U8 + if(handle_type==SOUND_DEVICE_OSS) { + wr=0; + while(wr=0) + wr+=i; + } + } + #endif + sem_post(&sample_complete_sem); + posted_last=TRUE; + pthread_mutex_unlock(&sample_mutex); + if(must_close) { + if(sem_trywait(&sample_pending_sem)==0) + waited=TRUE; + else + xptone_close(); + } + } + +error_return: + xptone_close(); + if(!posted_last) + sem_post(&sample_complete_sem); + sample_thread_running=FALSE; + pthread_mutex_unlock(&sample_mutex); +} + +BOOL DLLCALL xp_play_sample(const unsigned char *sample, size_t size, BOOL background) +{ + if(!sample_initialized) { + if(pthread_mutex_init(&sample_mutex, NULL)!=0) + return(FALSE); + pthread_mutex_lock(&sample_mutex); + if(sem_init(&sample_pending_sem, 0, 0)!=0) { + pthread_mutex_destroy(&sample_mutex); + return(FALSE); + } + if(sem_init(&sample_complete_sem, 0, 1)!=0) { + pthread_mutex_destroy(&sample_mutex); + sem_destroy(&sample_pending_sem); + return(FALSE); + } + sample_initialized=TRUE; + pthread_mutex_unlock(&sample_mutex); + } + + sem_wait(&sample_complete_sem); + pthread_mutex_lock(&sample_mutex); + if(!sample_thread_running) { + _beginthread(xp_play_sample_thread, 0,NULL); + } + + sample_buffer=sample; + sample_size=size; + pthread_mutex_unlock(&sample_mutex); + sem_post(&sample_pending_sem); + if(!background) { + sem_wait(&sample_complete_sem); + sem_post(&sample_complete_sem); + } + return(TRUE); +} +#else +BOOL DLLCALL xp_play_sample(const unsigned char *sample, size_t sample_size, BOOL background) +{ + BOOL must_close=FALSE; #ifdef AFMT_U8 int wr; @@ -428,14 +743,29 @@ BOOL DLLCALL xptone(double freq, DWORD d return(FALSE); } -#ifdef WITH_SDL +#ifdef WITH_PORTAUDIO + if(handle_type==SOUND_DEVICE_PORTAUDIO) { + if(pa_api->ver >= 1899) { + pa_api->write(portaudio_stream, sample, sample_size); + } + else { + pawave=sample; + portaudio_buf_pos=0; + portaudio_buf_len=sample_size; + pa_api->start(portaudio_stream); + } + while(pa_api->active(portaudio_stream)) + SLEEP(1); + pa_api->stop(portaudio_stream); + } +#endif + +#ifdef WITH_SDL_AUDIO if(handle_type==SOUND_DEVICE_SDL) { sdl.LockAudio(); + swave=sample; sdl_audio_buf_pos=0; - sdl_audio_buf_len=S_RATE*duration/1000; - if(sdl_audio_buf_len<=S_RATE/freq*2) - sdl_audio_buf_len=S_RATE/freq*2; - makewave(freq,swave,sdl_audio_buf_len,shape); + sdl_audio_buf_len=sample_size; sdl.UnlockAudio(); sdl.SemWait(sdlToneDone); } @@ -443,38 +773,40 @@ BOOL DLLCALL xptone(double freq, DWORD d #ifdef _WIN32 if(handle_type==SOUND_DEVICE_WIN32) { - wh.dwBufferLength=S_RATE*duration/1000; - if(wh.dwBufferLength<=S_RATE/freq*2) - wh.dwBufferLength=S_RATE/freq*2; - - makewave(freq,wave,wh.dwBufferLength,shape); - - if(waveOutWrite(waveOut, &wh, sizeof(wh))==MMSYSERR_NOERROR) { - while(!(wh.dwFlags & WHDR_DONE)) - SLEEP(1); + wh.lpData=sample; + wh.dwBufferLength=sample_size; + if(waveOutPrepareHeader(waveOut, &wh, sizeof(wh))==MMSYSERR_NOERROR) { + if(waveOutWrite(waveOut, &wh, sizeof(wh))==MMSYSERR_NOERROR) { + while(!(wh.dwFlags & WHDR_DONE)) + SLEEP(1); + while(waveOutUnprepareHeader(waveOut, &wh, sizeof(wh))==WAVERR_STILLPLAYING) + SLEEP(1); + } } } #endif -#if defined(USE_ALSA_SOUND) || defined(AFMT_U8) - if(freq<17) - freq=17; - samples=S_RATE*duration/1000; - if(samples<=S_RATE/freq*2) - samples=S_RATE/freq*2; - makewave(freq,wave,samples,shape); -#endif - #ifdef USE_ALSA_SOUND if(handle_type==SOUND_DEVICE_ALSA) { - alsa_api->snd_pcm_hw_params_free(hw_params); - if(alsa_api->snd_pcm_writei(handle, wave, samples)!=samples) { - /* Go back and try OSS */ - alsa_device_open_failed=TRUE; - alsa_api->snd_pcm_close (handle); - xptone_open(); + int ret; + int written=0; + + while(written < sample_size) { + ret=alsa_api->snd_pcm_writei(playback_handle, sample+written, sample_size-written); + if(ret < 0) { + if(written==0) { + /* Go back and try OSS */ + xptone_close(); + alsa_device_open_failed=TRUE; + xptone_open(); + } + break; + } + written += ret; } - else { + if(!alsa_device_open_failed) { + while(alsa_api->snd_pcm_drain(playback_handle)) + SLEEP(1); if(must_close) xptone_close(); return(TRUE); @@ -485,8 +817,8 @@ BOOL DLLCALL xptone(double freq, DWORD d #ifdef AFMT_U8 if(handle_type==SOUND_DEVICE_OSS) { wr=0; - while(wr=0) wr+=i; } @@ -499,6 +831,38 @@ BOOL DLLCALL xptone(double freq, DWORD d xptone_close(); return(FALSE); } +#endif + +/********************************************************************************/ +/* Play a tone through the wave/DSP output device (sound card) - Deuce */ +/********************************************************************************/ + +BOOL DLLCALL xptone(double freq, DWORD duration, enum WAVE_SHAPE shape) +{ + unsigned char wave[S_RATE*15/2+1]; + int samples; + + if(freq<17) + freq=17; + samples=S_RATE*duration/1000; + if(samples<=S_RATE/freq*2) + samples=S_RATE/freq*2; + if(samples > S_RATE/freq*2) { + int sample_len; + + makewave(freq,wave,S_RATE*15/2,shape); + for(sample_len=S_RATE*15/2-1; sample_len && wave[sample_len]==128; sample_len--) + ; + sample_len++; + while(samples > S_RATE*15/2) { + if(!xp_play_sample(wave, sample_len, TRUE)) + return FALSE; + samples -= sample_len; + } + } + makewave(freq,wave,samples,shape); + return(xp_play_sample(wave, samples, FALSE)); +} #ifdef __unix__ /****************************************************************************/