/* $Id: xpbeep.c,v 1.113 2020/05/15 01:12:10 deuce Exp $ */

/* TODO: USE PORTAUDIO! */

/* standard headers */
#include <math.h>
#include <stdlib.h>
#include "xp_dl.h"

#if defined(_WIN32)
	#define WIN32_LEAN_AND_MEAN
	#include <windows.h>
	#include <mmsystem.h>
#elif defined(__unix__)
	#include <fcntl.h>
	#include <sys/ioctl.h>
	#ifndef __EMSCRIPTEN__
		#ifdef SOUNDCARD_H_IN
			#if SOUNDCARD_H_IN == 1
				#include <sys/soundcard.h>
			#elif SOUNDCARD_H_IN == 2
				#include <soundcard.h>
			#elif SOUNDCARD_H_IN == 3
				#include <linux/soundcard.h>
			#endif
		#endif
		#ifdef USE_ALSA_SOUND
			#include <dlfcn.h>
			#include <alsa/asoundlib.h>
		#endif
/* KIOCSOUND */
		#if defined(__FreeBSD__)
			#include <sys/kbio.h>
		#elif defined(__linux__)
			#include <sys/kd.h>
		#elif defined(__solaris__)
			#include <sys/kbio.h>
			#include <sys/kbd.h>
		#endif
		#if (defined(__OpenBSD__) || defined(__NetBSD__)) && defined(HAS_MACHINE_SPKR_H)
			#include <machine/spkr.h>
		#elif defined(__FreeBSD__)
			#if defined(HAS_DEV_SPEAKER_SPEAKER_H)
				#include <dev/speaker/speaker.h>
			#elif defined(HAS_MACHINE_SPEAKER_H)
				#include <machine/speaker.h>
			#endif
		#endif
	#endif
#endif

/* xpdev headers */
#ifdef WITH_PORTAUDIO
#include <portaudio.h>
#endif

#ifdef WITH_PULSEAUDIO
#include <pulse/simple.h>
#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 pthread_mutex_t      sample_mutex;
static pthread_mutex_t      handle_mutex;
static const unsigned char *sample_buffer;
static int                  samples_posted;
static size_t               sample_size;

#endif

#ifdef _MSC_VER
#pragma warning(disable : 4244 4267 4018)
#endif

static bool sound_device_open_failed = false;
#ifdef USE_ALSA_SOUND
static bool alsa_device_open_failed = false;
#endif
#ifdef WITH_SDL_AUDIO
static bool sdl_device_open_failed = false;
#endif
#ifdef WITH_PORTAUDIO
static bool portaudio_device_open_failed = false;
#endif
#ifdef WITH_PULSEAUDIO
static bool pulseaudio_device_open_failed = false;
#endif

enum {
	SOUND_DEVICE_CLOSED
	, SOUND_DEVICE_WIN32
	, SOUND_DEVICE_ALSA
	, SOUND_DEVICE_OSS
	, SOUND_DEVICE_SDL
	, SOUND_DEVICE_PORTAUDIO
	, SOUND_DEVICE_PULSEAUDIO
};

static int handle_type = SOUND_DEVICE_CLOSED;
static int handle_rc;
uint32_t xpbeep_sound_devices_enabled =  XPBEEP_DEVICE_DEFAULT;

#ifdef WITH_PULSEAUDIO
struct pulseaudio_api_struct {
	dll_handle dl;
	pa_simple* (*simple_new)(const char * server, const char * name, pa_stream_direction_t dir, const char * dev, const char * stream_name, const pa_sample_spec * ss, const pa_channel_map * map, const pa_buffer_attr * attr, int * error);
	int (*simple_write)(pa_simple * s, const void * data, size_t bytes, int * error);
	int (*simple_drain)(pa_simple * s, int * error);
	void (*simple_free)(pa_simple * s);
};
struct pulseaudio_api_struct *pu_api = NULL;
static pa_simple *            pu_handle;
static bool                   pulseaudio_initialized = false;
#endif

#ifdef WITH_PORTAUDIO
static PaStream *             portaudio_stream;
static int                    portaudio_buf_len = 0;
static int                    portaudio_buf_pos = 0;
static const unsigned char *  pawave = NULL;
static bool                   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 {
	dll_handle dl;
	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 const unsigned char * swave;
static SDL_sem *             sdlToneDone;
#endif

#ifdef _WIN32
static HWAVEOUT              waveOut;
static WAVEHDR               wh[2];
static int                   curr_wh;
#endif

#ifdef USE_ALSA_SOUND
static snd_pcm_t *           playback_handle;
static snd_pcm_hw_params_t * hw_params = NULL;
#endif

#ifdef AFMT_U8
static int                   dsp;
#endif

#define WAVE_PI 3.14159265358979323846
#define WAVE_TPI 6.28318530717958647692

#ifdef USE_ALSA_SOUND
struct alsa_api_struct {
	dll_handle dl;
	int     (*snd_pcm_open)
	(snd_pcm_t * *pcm, const char *name, snd_pcm_stream_t stream, int mode);
	int     (*snd_pcm_hw_params_malloc)
	(snd_pcm_hw_params_t * *ptr);
	int     (*snd_pcm_hw_params_any)
	(snd_pcm_t * pcm, snd_pcm_hw_params_t *params);
	int     (*snd_pcm_hw_params_set_access)
	(snd_pcm_t * pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access);
	int     (*snd_pcm_hw_params_set_format)
	(snd_pcm_t * pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
	int     (*snd_pcm_hw_params_set_rate_near)
	(snd_pcm_t * pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
	int     (*snd_pcm_hw_params_set_channels)
	(snd_pcm_t * pcm, snd_pcm_hw_params_t *params, unsigned int val);
	int     (*snd_pcm_hw_params)
	(snd_pcm_t * pcm, snd_pcm_hw_params_t *params);
	int     (*snd_pcm_prepare)
	(snd_pcm_t * pcm);
	void    (*snd_pcm_hw_params_free)
	(snd_pcm_hw_params_t * obj);
	int     (*snd_pcm_close)
	(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;
#endif

#ifdef XPDEV_THREAD_SAFE
static void init_sample(void);
static bool xp_play_sample_locked(unsigned char *sample, size_t size, bool background);
#endif

/********************************************************************************/
/* Calculate and generate a sound wave pattern (thanks to Deuce!)				*/
/********************************************************************************/
void xptone_makewave(double freq, unsigned char *wave, int samples, DWORD shape)
{
	int    i;
	int    midpoint;
	double inc;
	double pos;
	bool   endhigh;
	int    crossings;
	int    numcross = 0;

	if (freq == 0) {
		memset(wave, 128, samples);
	}
	else {
		midpoint = samples / 2;
		inc = 8.0 * atan(1.0);
		inc *= ((double)freq / (double)S_RATE);

		for (i = 0; i < samples; i++) {
			pos = (inc * (double)i);
			pos -= (int)(pos / WAVE_TPI) * WAVE_TPI;
			switch (shape & WAVE_SHAPE_MASK) {
				case WAVE_SHAPE_SINE:
					wave[i] = (sin (pos)) * 127 + 128;
					break;
				case WAVE_SHAPE_SINE_HARM:
					wave[i] = (sin (pos)) * 64 + 128;
					wave[i] = (sin ((inc * 2) * (double)i)) * 24;
					wave[i] = (sin ((inc * 3) * (double)i)) * 16;
					break;
				case WAVE_SHAPE_SAWTOOTH:
					wave[i] = (WAVE_TPI - pos) * 40.5;
					break;
				case WAVE_SHAPE_SQUARE:
					wave[i] = (pos < WAVE_PI)?255:0;
					break;
				case WAVE_SHAPE_SINE_SAW:
					wave[i] = (((sin (pos)) * 127 + 128) + ((WAVE_TPI - pos) * 40.5)) / 2;
					break;
				case WAVE_SHAPE_SINE_SAW_CHORD:
					wave[i] = (((sin (pos)) * 64 + 128) + ((WAVE_TPI - pos) * 6.2)) / 2;
					wave[i] += (sin ((inc / 2) * (double)i)) * 24;
					wave[i] += (sin ((inc / 3) * (double)i)) * 16;
					break;
				case WAVE_SHAPE_SINE_SAW_HARM:
					wave[i] = (((sin (pos)) * 64 + 128) + ((WAVE_TPI - pos) * 6.2)) / 2;
					wave[i] += (sin ((inc * 2) * (double)i)) * 24;
					wave[i] += (sin ((inc * 3) * (double)i)) * 16;
					break;
			}
			/*
			 * TODO: It's silly to do this here, but the excessive wrapping above
			 * makes it the only sane way to do it and keep the noises as is.
			 */
			// Reduce by 12dB
			wave[i] = ((((double)wave[i]) - 128) * 0.251) + 128;
		}

		if ((shape & WAVE_SHAPE_NO_CLEAN) == 0) {
			/* Now we have a "perfect" wave...
			 * we must clean it up now to avoid click/pop
			 */
			if (wave[samples - 1] > 128)
				endhigh = true;
			else
				endhigh = false;
			/* Completely remove the last wave fragment */
			i = samples - 1;
			if (wave[i] != 128) {
				for (; i > midpoint; i--) {
					if (endhigh && wave[i] < 128)
						break;
					if (!endhigh && wave[i] > 128)
						break;
					wave[i] = 128;
				}
			}

			/* Number of crossings should be on the order of 5ms worth... according to the ARRL */
			/* We're ASSuming that a full wave crosses twice */
			numcross = freq / 100;
			if (numcross) {
				/* Ramp up and down by one third for three corssings of 128 */
				crossings = 0;
				for (i = 0; i < (samples - 1); i++) {
					if (((wave[i] < 128 && wave[i + 1] >= 128) || (wave[i] > 128 && wave[i + 1] <= 128)) && i > 2) {
						crossings++;
						if (crossings >= numcross)
							break;
					}
					wave[i] = 128 + ((wave[i] - 128) / ((numcross - crossings) * (numcross - crossings)));
				}
				crossings = 0;
				for (i = samples - 1; i > 0; i--) {
					if (((wave[i] < 128 && wave[i - 1] >= 128) || (wave[i] > 128 && wave[i - 1] <= 128)) && i > 2) {
						crossings++;
						if (crossings >= numcross)
							break;
					}
					wave[i] = 128 + ((wave[i] - 128) / ((numcross - crossings) * (numcross - crossings)));
				}
			}
		}
	}
}

#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;
	int maxlen = sdl_audio_buf_len - sdl_audio_buf_pos;

	/* Copy in the current buffer */
	if (copylen > maxlen)
		copylen = maxlen;
	/* Fill with silence */
	if (len > copylen)
		memset(stream + copylen, spec.silence, len - copylen);
	if (copylen) {
		memcpy(stream, swave + sdl_audio_buf_pos, copylen);
		sdl_audio_buf_pos += copylen;
		/* If we're done, post the semaphore */
		if (sdl_audio_buf_pos >= sdl_audio_buf_len) {
			xpbeep_sdl.SemPost(sdlToneDone);
			sdl_audio_buf_len = 0;
			sdl_audio_buf_pos = 0;
			free((void *)swave);
		}
	}
}
#endif

#ifdef XPDEV_THREAD_SAFE
pthread_once_t sample_initialized_pto = PTHREAD_ONCE_INIT;
#endif
static bool
xptone_open_locked(void)
{
#ifdef _WIN32
	WAVEFORMATEX w;
#endif

#ifdef AFMT_U8
	int          format = AFMT_U8;
	int          channels = 1;
	int          rate = S_RATE;
	int          fragsize = 0x7fff0004;
#endif

	/* Already open */

	if (handle_type != SOUND_DEVICE_CLOSED) {
		handle_rc++;
		return true;
	}

#ifdef WITH_PULSEAUDIO
	if (xpbeep_sound_devices_enabled & XPBEEP_DEVICE_PULSEAUDIO) {
		if (!pulseaudio_device_open_failed) {
			if (pu_api == NULL) {
				const char *libnames[] = {"pulse-simple", NULL};
				if (((pu_api = (struct pulseaudio_api_struct *)malloc(sizeof(struct pulseaudio_api_struct))) == NULL)
				    || ((pu_api->dl = xp_dlopen(libnames, RTLD_LAZY, 0)) == NULL)
				    || ((pu_api->simple_new = xp_dlsym(pu_api->dl, pa_simple_new)) == NULL)
				    || ((pu_api->simple_write = xp_dlsym(pu_api->dl, pa_simple_write)) == NULL)
				    || ((pu_api->simple_drain = xp_dlsym(pu_api->dl, pa_simple_drain)) == NULL)
				    || ((pu_api->simple_free = xp_dlsym(pu_api->dl, pa_simple_free)) == NULL)
				    ) {
					if (pu_api->dl)
						xp_dlclose(pu_api->dl);
					free(pu_api);
					pu_api = NULL;
				}
				if (pu_api == NULL) {
					pulseaudio_device_open_failed = true;
				}
			}
			if (pu_api != NULL) {
				handle_type = SOUND_DEVICE_PULSEAUDIO;
				handle_rc++;
	#ifdef XPDEV_THREAD_SAFE
				assert_pthread_mutex_unlock(&handle_mutex);
				assert_pthread_mutex_lock(&sample_mutex);
				if (samples_posted == 0)
					xp_play_sample_locked((unsigned char *)"\x80", 1, false);
				assert_pthread_mutex_unlock(&sample_mutex);
				assert_pthread_mutex_lock(&handle_mutex);
	#else
				xptone(0, 1, WAVE_SHAPE_SQUARE);
	#endif
				if (pulseaudio_device_open_failed) {
					handle_type = SOUND_DEVICE_CLOSED;
				}
				else {
					return true;
				}
			}
		}
	}
#endif

#ifdef WITH_PORTAUDIO
	if (xpbeep_sound_devices_enabled & XPBEEP_DEVICE_PORTAUDIO) {
		if (!portaudio_device_open_failed) {
			if (pa_api == NULL) {
				const char *libnames[] = {"portaudio", NULL};
				if (((pa_api = (struct portaudio_api_struct *)malloc(sizeof(struct portaudio_api_struct))) == NULL)
				    || ((pa_api->dl = xp_dlopen(libnames, RTLD_LAZY, 0)) == NULL)
				    || ((pa_api->init = xp_dlsym(pa_api->dl, Pa_Initialize)) == NULL)
				    || ((pa_api->open = xp_dlsym(pa_api->dl, Pa_OpenDefaultStream)) == NULL)
				    || ((pa_api->close = xp_dlsym(pa_api->dl, Pa_CloseStream)) == NULL)
				    || ((pa_api->start = xp_dlsym(pa_api->dl, Pa_StartStream)) == NULL)
				    ||
					(
						((pa_api->active = xp_dlsym(pa_api->dl, Pa_StreamActive)) == NULL)
						&& ((pa_api->active = xp_dlsym(pa_api->dl, Pa_IsStreamActive)) == NULL)
				    )
				    || ((pa_api->stop = xp_dlsym(pa_api->dl, Pa_StopStream)) == NULL)
				    ) {
					if (pa_api->dl)
						xp_dlclose(pa_api->dl);
					free(pa_api);
					pa_api = NULL;
				}
				else {
					/* Get version and other optional pointers */
					pa_api->ver = 1800;
					if ((pa_api->version = xp_dlsym(pa_api->dl, Pa_GetVersion)) != NULL) {
						pa_api->ver = pa_api->version();
						if (pa_api->ver >= 1899) {
							if ((pa_api->write = xp_dlsym(pa_api->dl, Pa_WriteStream)) == NULL) {
								xp_dlclose(pa_api->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;
						handle_rc++;
						return true;
					}
				}
			}
		}
	}
#endif

#ifdef WITH_SDL_AUDIO
	if (xpbeep_sound_devices_enabled & XPBEEP_DEVICE_SDL) {
		if (!sdl_device_open_failed) {
			if (init_sdl_audio() == -1) {
				sdl_device_open_failed = true;
			}
			else {
				spec.freq = 22050;
				spec.format = AUDIO_U8;
				spec.channels = 1;
				spec.samples = 256;       /* Size of audio buffer */
				spec.size = 256;
				spec.callback = sdl_fillbuf;
				spec.userdata = NULL;
				if (xpbeep_sdl.OpenAudio(&spec, NULL) == -1) {
					sdl_device_open_failed = true;
				}
				else {
					sdlToneDone = xpbeep_sdl.SDL_CreateSemaphore(0);
					sdl_audio_buf_len = 0;
					sdl_audio_buf_pos = 0;
					xpbeep_sdl.PauseAudio(false);
					handle_type = SOUND_DEVICE_SDL;
					handle_rc++;
					return true;
				}
			}
		}
	}
#endif

#ifdef _WIN32
	if (xpbeep_sound_devices_enabled & XPBEEP_DEVICE_WIN32) {
		if (!sound_device_open_failed) {
			w.wFormatTag = WAVE_FORMAT_PCM;
			w.nChannels = 1;
			w.nSamplesPerSec = S_RATE;
			w.wBitsPerSample = 8;
			w.nBlockAlign = (w.wBitsPerSample * w.nChannels) / 8;
			w.nAvgBytesPerSec = w.nSamplesPerSec * w.nBlockAlign;

			if (!sound_device_open_failed && waveOutOpen(&waveOut, WAVE_MAPPER, &w, 0, 0, 0) != MMSYSERR_NOERROR)
				sound_device_open_failed = true;
			if (sound_device_open_failed)
				return false;
			memset(&wh, 0, sizeof(wh));
			wh[0].dwBufferLength = S_RATE * 15 / 2 + 1;
			wh[1].dwBufferLength = S_RATE * 15 / 2 + 1;
			handle_type = SOUND_DEVICE_WIN32;
			if (!sound_device_open_failed) {
				handle_rc++;
				return true;
			}
		}
	}
#endif

#ifdef USE_ALSA_SOUND
	if (xpbeep_sound_devices_enabled & XPBEEP_DEVICE_ALSA) {
		if (!alsa_device_open_failed) {
			if (alsa_api == NULL) {
				const char *libnames[] = {"asound", NULL};
				if (((alsa_api = (struct alsa_api_struct *)malloc(sizeof(struct alsa_api_struct))) == NULL)
				    || ((alsa_api->dl = xp_dlopen(libnames, RTLD_LAZY, 2)) == NULL)
				    || ((alsa_api->snd_pcm_open = xp_dlsym(alsa_api->dl, snd_pcm_open)) == NULL)
				    || ((alsa_api->snd_pcm_hw_params_malloc = xp_dlsym(alsa_api->dl, snd_pcm_hw_params_malloc)) == NULL)
				    || ((alsa_api->snd_pcm_hw_params_any = xp_dlsym(alsa_api->dl, snd_pcm_hw_params_any)) == NULL)
				    || ((alsa_api->snd_pcm_hw_params_set_access = xp_dlsym(alsa_api->dl, snd_pcm_hw_params_set_access)) == NULL)
				    || ((alsa_api->snd_pcm_hw_params_set_format = xp_dlsym(alsa_api->dl, snd_pcm_hw_params_set_format)) == NULL)
				    || ((alsa_api->snd_pcm_hw_params_set_rate_near = xp_dlsym(alsa_api->dl, snd_pcm_hw_params_set_rate_near)) == NULL)
				    || ((alsa_api->snd_pcm_hw_params_set_channels = xp_dlsym(alsa_api->dl, snd_pcm_hw_params_set_channels)) == NULL)
				    || ((alsa_api->snd_pcm_hw_params = xp_dlsym(alsa_api->dl, snd_pcm_hw_params)) == NULL)
				    || ((alsa_api->snd_pcm_prepare = xp_dlsym(alsa_api->dl, snd_pcm_prepare)) == NULL)
				    || ((alsa_api->snd_pcm_hw_params_free = xp_dlsym(alsa_api->dl, snd_pcm_hw_params_free)) == NULL)
				    || ((alsa_api->snd_pcm_close = xp_dlsym(alsa_api->dl, snd_pcm_close)) == NULL)
				    || ((alsa_api->snd_pcm_writei = xp_dlsym(alsa_api->dl, snd_pcm_writei)) == NULL)
				    || ((alsa_api->snd_pcm_drain = xp_dlsym(alsa_api->dl, snd_pcm_drain)) == NULL)
				    ) {
					if (alsa_api->dl)
						xp_dlclose(alsa_api->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) {
				unsigned 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, &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)) {
					alsa_device_open_failed = true;
					if (hw_params != NULL)
						alsa_api->snd_pcm_hw_params_free(hw_params);
					if (playback_handle != NULL) {
						alsa_api->snd_pcm_close(playback_handle);
						playback_handle = NULL;
					}
				}
				else {
					alsa_api->snd_pcm_hw_params_free(hw_params);
					handle_type = SOUND_DEVICE_ALSA;
					handle_rc++;
					return true;
				}
			}
		}
	}
#endif

#ifdef AFMT_U8
	if (xpbeep_sound_devices_enabled & XPBEEP_DEVICE_OSS) {
		if (!sound_device_open_failed) {
			if ((dsp = open("/dev/dsp", O_WRONLY, 0)) < 0) {
				sound_device_open_failed = true;
			}
			else  {
				ioctl(dsp, SNDCTL_DSP_SETFRAGMENT, &fragsize);
				if ((ioctl(dsp, SNDCTL_DSP_SETFMT, &format) == -1) || format != AFMT_U8) {
					sound_device_open_failed = true;
					close(dsp);
				}
				else if ((ioctl(dsp, SNDCTL_DSP_CHANNELS, &channels) == -1) || channels != 1) {
					sound_device_open_failed = true;
					close(dsp);
				}
				else if ((ioctl(dsp, SNDCTL_DSP_SPEED, &rate) == -1) || rate != S_RATE) {
					sound_device_open_failed = true;
					close(dsp);
				}
			}
		}
		if (sound_device_open_failed) {
			return false;
		}
		handle_type = SOUND_DEVICE_OSS;
		if (!sound_device_open_failed) {
			handle_rc++;
			return true;
		}
	}
#endif

	return false;
}

bool
xptone_open(void)
{
	bool ret;
#ifdef XPDEV_THREAD_SAFE
	pthread_once(&sample_initialized_pto, init_sample);
	assert_pthread_mutex_lock(&handle_mutex);
#endif
	ret = xptone_open_locked();
#ifdef XPDEV_THREAD_SAFE
	assert_pthread_mutex_unlock(&handle_mutex);
#endif
	return ret;
}

static void
xptone_complete_locked(void)
{
	if (handle_type == SOUND_DEVICE_CLOSED) {
		return;
	}

#ifdef WITH_PULSEAUDIO
	else if (handle_type == SOUND_DEVICE_PULSEAUDIO) {
		int err;
		if (pu_handle)
			pu_api->simple_drain(pu_handle, &err);
	}
#endif

#ifdef WITH_PORTAUDIO
	else if (handle_type == SOUND_DEVICE_PORTAUDIO) {
		pa_api->stop(portaudio_stream);
		if (pawave) {
			free((void *)pawave);
			pawave = NULL;
		}
	}
#endif

#ifdef WITH_SDL_AUDIO
	else if (handle_type == SOUND_DEVICE_SDL) {
		while (xpbeep_sdl.GetAudioStatus() == SDL_AUDIO_PLAYING)
			SLEEP(1);
	}
#endif

#ifdef _WIN32
	if (handle_type == SOUND_DEVICE_WIN32) {
		if (wh[0].dwFlags & WHDR_PREPARED) {
			while (waveOutUnprepareHeader(waveOut, &wh[0], sizeof(wh)) == WAVERR_STILLPLAYING)
				SLEEP(1);
			FREE_AND_NULL(wh[0].lpData);
		}
		if (wh[1].dwFlags & WHDR_PREPARED) {
			while (waveOutUnprepareHeader(waveOut, &wh[1], sizeof(wh)) == WAVERR_STILLPLAYING)
				SLEEP(1);
			FREE_AND_NULL(wh[1].lpData);
		}
	}
#endif

#ifdef USE_ALSA_SOUND
	if (handle_type == SOUND_DEVICE_ALSA) {
		if (!alsa_device_open_failed)
			alsa_api->snd_pcm_drain(playback_handle);
	}
#endif

#ifdef AFMT_U8
	else if (handle_type == SOUND_DEVICE_OSS) {
		ioctl(dsp, SNDCTL_DSP_SYNC, NULL);
	}
#endif
}

void
xptone_complete(void)
{
#ifdef XPDEV_THREAD_SAFE
	assert_pthread_mutex_lock(&handle_mutex);
#endif
	// coverity[sleep]
	xptone_complete_locked();
#ifdef XPDEV_THREAD_SAFE
	assert_pthread_mutex_unlock(&handle_mutex);
#endif
}

bool xptone_close_locked(void)
{
	xptone_complete_locked();

	if (handle_rc <= 0)
		return false;
	if (--handle_rc)
		return true;

#ifdef WITH_PORTAUDIO
	if (handle_type == SOUND_DEVICE_PORTAUDIO) {
		pa_api->close(portaudio_stream);
	}
#endif

#ifdef WITH_PULSEAUDIO
	if (handle_type == SOUND_DEVICE_PULSEAUDIO) {
		if (pu_handle)
			pu_api->simple_free(pu_handle);
		pu_handle = NULL;
	}
#endif

#ifdef WITH_SDL_AUDIO
	if (handle_type == SOUND_DEVICE_SDL) {
		xpbeep_sdl.CloseAudio();
		xpbeep_sdl.SDL_DestroySemaphore(sdlToneDone);
	}
#endif

#ifdef _WIN32
	if (handle_type == SOUND_DEVICE_WIN32) {
		waveOutClose(waveOut);
	}
#endif

#ifdef USE_ALSA_SOUND
	if (handle_type == SOUND_DEVICE_ALSA) {
		alsa_api->snd_pcm_close (playback_handle);
		playback_handle = NULL;
	}
#endif

#ifdef AFMT_U8
	if (handle_type == SOUND_DEVICE_OSS) {
		close(dsp);
	}
#endif
	handle_type = SOUND_DEVICE_CLOSED;
	sound_device_open_failed = false;
#ifdef USE_ALSA_SOUND
	alsa_device_open_failed = false;
#endif
#ifdef WITH_SDL_AUDIO
	sdl_device_open_failed = false;
#endif
#ifdef WITH_PORTAUDIO
	portaudio_device_open_failed = false;
#endif
#ifdef WITH_PULSEAUDIO
	pulseaudio_device_open_failed = false;
#endif

	return true;
}

bool
xptone_close(void)
{
	bool ret;

#ifdef XPDEV_THREAD_SAFE
	assert_pthread_mutex_lock(&handle_mutex);
#endif
	// coverity[sleep]
	ret = xptone_close_locked();
#ifdef XPDEV_THREAD_SAFE
	assert_pthread_mutex_unlock(&handle_mutex);
#endif
	return ret;
}

// This can't be const because the Win32 API is not const.
static bool
do_xp_play_sample(unsigned char *sampo, size_t sz, int *freed)
{
	unsigned char *samp;
#if defined(WITH_PORTAUDIO) || defined(_WIN32) || defined(WITH_SDL_AUDIO)
	int            need_copy = 0;
#endif

#ifdef WITH_PORTAUDIO
	if (handle_type == SOUND_DEVICE_PORTAUDIO) {
		if (pa_api->ver < 1899)
			need_copy = 1;
	}
#endif
#ifdef _WIN32
	if (handle_type == SOUND_DEVICE_WIN32)
		need_copy = 1;
#endif
#ifdef WITH_SDL_AUDIO
	if (handle_type == SOUND_DEVICE_SDL)
		need_copy = 1;
#endif

#if defined(WITH_PORTAUDIO) || defined(_WIN32) || defined(WITH_SDL_AUDIO)
	if (freed)
		*freed = need_copy;
	if (need_copy) {
		if (freed) {
			samp = sampo;
		}
		else {
			samp = malloc(sz);
			if (!samp)
				return false;
			memcpy((void *)samp, sampo, sz);
		}
	}
	else {
		samp = sampo;
	}
#else
	if (freed)
		*freed = 0;
	samp = sampo;
#endif

#ifdef WITH_PULSEAUDIO
	if (handle_type == SOUND_DEVICE_PULSEAUDIO) {
		int            err;
		pa_sample_spec ss;
		ss.format = PA_SAMPLE_U8;
		ss.rate = 22050;
		ss.channels = 1;
		if (pu_handle == NULL) {
			if ((pu_handle = pu_api->simple_new(NULL, "XPBeep", PA_STREAM_PLAYBACK, NULL, "Beeps and Boops", &ss, NULL, NULL, NULL)) == NULL) {
				pulseaudio_device_open_failed = true;
				pulseaudio_initialized = false;
				xptone_close_locked();
				xptone_open_locked();
			}
			else
				pulseaudio_initialized = true;
		}
		if (pulseaudio_initialized)
			pu_api->simple_write(pu_handle, sampo, sz, &err);
	}
#endif

#ifdef WITH_PORTAUDIO
	if (handle_type == SOUND_DEVICE_PORTAUDIO) {
		if (pa_api->ver >= 1899) {
			pa_api->start(portaudio_stream);
			pa_api->write(portaudio_stream, samp, sz);
		}
		else {
			xptone_complete_locked();
			pawave = samp;
			portaudio_buf_pos = 0;
			portaudio_buf_len = sz;
			pa_api->start(portaudio_stream);
		}
		return true;
	}
#endif

#ifdef WITH_SDL_AUDIO
	if (handle_type == SOUND_DEVICE_SDL) {
		xpbeep_sdl.LockAudio();
		swave = samp;
		sdl_audio_buf_pos = 0;
		sdl_audio_buf_len = sz;
		xpbeep_sdl.UnlockAudio();
		xpbeep_sdl.PauseAudio(false);
		xpbeep_sdl.SemWait(sdlToneDone);
		xpbeep_sdl.PauseAudio(true);
		return true;
	}
#endif

#ifdef _WIN32
	if (handle_type == SOUND_DEVICE_WIN32) {
		if (wh[curr_wh].dwFlags & WHDR_PREPARED) {
			while (waveOutUnprepareHeader(waveOut, &wh[curr_wh], sizeof(wh[curr_wh])) == WAVERR_STILLPLAYING)
				SLEEP(1);
		}
		free(wh[curr_wh].lpData);
		wh[curr_wh].lpData = (LPSTR)samp;
		wh[curr_wh].dwBufferLength = sz;
		if (waveOutPrepareHeader(waveOut, &wh[curr_wh], sizeof(wh[curr_wh])) == MMSYSERR_NOERROR) {
			if (waveOutWrite(waveOut, &wh[curr_wh], sizeof(wh[curr_wh])) == MMSYSERR_NOERROR) {
				curr_wh ^= 1;
			}
		}
		return true;
	}
#endif

#ifdef USE_ALSA_SOUND
	if (handle_type == SOUND_DEVICE_ALSA) {
		int ret;
		int written = 0;

		while (written < sz) {
			ret = alsa_api->snd_pcm_writei(playback_handle, samp + written, sz - written);
			if (ret < 0) {
				if (alsa_api->snd_pcm_prepare(playback_handle) == 0) {
					ret = 0;
				}
				else {
					if (written == 0) {
						/* Go back and try OSS */
						xptone_close_locked();
						alsa_device_open_failed = true;
						xptone_open_locked();
					}
					break;
				}
			}
			written += ret;
		}
		if (written == sz)
			return true;
	}
#endif

#ifdef AFMT_U8
	if (handle_type == SOUND_DEVICE_OSS) {
		size_t wr = 0;
		while (wr < sz) {
			// coverity[overflow:SUPPRESS]
			ssize_t i = write(dsp, samp + wr, sz - wr);
			if (i >= 0)
				wr += i;
			else
				return false;
		}
		return true;
	}
#endif

	return false;
}

#ifdef XPDEV_THREAD_SAFE
void xp_play_sample_thread(void *data)
{
	bool           must_close;
	bool           posted_last = true;
	bool           waited = false;
	unsigned char *sample = NULL;
	size_t         this_sample_size;
	int            freed;

	(void)data; // Unused
	SetThreadName("Sample Play");
	sample_thread_running = true;
	sem_post(&sample_complete_sem);
	while (1) {
		must_close = false;
		if (!waited) {
			if (sem_wait(&sample_pending_sem) != 0)
				goto error_return;
		}
		else
			waited = false;
		posted_last = false;
		assert_pthread_mutex_lock(&handle_mutex);

		if (handle_type == SOUND_DEVICE_CLOSED) {
			must_close = true;
			if (!xptone_open_locked()) {
				sem_post(&sample_complete_sem);
				assert_pthread_mutex_unlock(&handle_mutex);
				continue;
			}
		}

		if (pthread_mutex_lock(&sample_mutex) != 0) {
			assert_pthread_mutex_unlock(&handle_mutex);
			goto error_return;
		}
		this_sample_size = sample_size;
		FREE_AND_NULL(sample);
		// coverity[def:SUPPRESS]
		sample = (unsigned char *)malloc(sample_size);
		if (sample == NULL) {
			sem_post(&sample_complete_sem);
			assert_pthread_mutex_unlock(&sample_mutex);
			assert_pthread_mutex_unlock(&handle_mutex);
			continue;
		}
		memcpy(sample, sample_buffer, this_sample_size);
		assert_pthread_mutex_unlock(&sample_mutex);

		do_xp_play_sample(sample, this_sample_size, &freed);
		if (freed)
			sample = NULL;
		sem_post(&sample_complete_sem);
		posted_last = true;
		if (must_close) {
			if (sem_trywait(&sample_pending_sem) == 0) {
				waited = true;
			}
			else {
				// coverity[sleep]
				xptone_close_locked();
			}
		}
		assert_pthread_mutex_unlock(&handle_mutex);
	}

error_return:
#ifdef _WIN32
	assert_pthread_mutex_lock(&handle_mutex);
	if (handle_type == SOUND_DEVICE_WIN32) {
		if (wh[curr_wh].dwFlags & WHDR_PREPARED) {
			while (waveOutUnprepareHeader(waveOut, &wh[curr_wh], sizeof(wh[curr_wh])) == WAVERR_STILLPLAYING)
				SLEEP(1);
		}
		FREE_AND_NULL(wh[curr_wh].lpData);
	}
	assert_pthread_mutex_unlock(&handle_mutex);
#endif

	FREE_AND_NULL(sample);
	xptone_close();
	if (!posted_last) {
		sem_post(&sample_complete_sem);
	}
	sample_thread_running = false;
}

static void
init_sample(void)
{
	assert_pthread_mutex_init(&sample_mutex, NULL);
	assert_pthread_mutex_init(&handle_mutex, NULL);
	sem_init(&sample_pending_sem, 0, 0);
	sem_init(&sample_complete_sem, 0, 0);
}

static bool xp_play_sample_locked(unsigned char *sample, size_t size, bool background)
{
	if (!sample_thread_running) {
		_beginthread(xp_play_sample_thread, 0, NULL);
		sem_wait(&sample_complete_sem);
	}

	while (samples_posted > 0) {
		assert_pthread_mutex_unlock(&sample_mutex);
		sem_wait(&sample_complete_sem);
		assert_pthread_mutex_lock(&sample_mutex);
		samples_posted--;
	}
	sample_buffer = sample;
	sample_size = size;
	samples_posted++;
	sem_post(&sample_pending_sem);
	if (!background) {
		while (samples_posted > 0) {
			assert_pthread_mutex_unlock(&sample_mutex);
			sem_wait(&sample_complete_sem);
			assert_pthread_mutex_lock(&sample_mutex);
			samples_posted--;
		}
	}
	return true;
}

/*
 * This MUST not return false after sample goes into the sample buffer in the background.
 * If it does, the caller won't be able to free() it.
 */
bool xp_play_sample(unsigned char *sample, size_t size, bool background)
{
	bool ret;
	pthread_once(&sample_initialized_pto, init_sample);

	assert_pthread_mutex_lock(&sample_mutex);
	ret = xp_play_sample_locked(sample, size, background);
	assert_pthread_mutex_unlock(&sample_mutex);
	return ret;
}
#else
bool xp_play_sample(unsigned char *sample, size_t sample_size, bool background)
{
	bool must_close = false;
	bool ret;

	(void)background; // Never used when single-threaded
	if (handle_type == SOUND_DEVICE_CLOSED) {
		must_close = true;
		if (!xptone_open())
			return false;
	}

	ret = do_xp_play_sample(sample, sample_size, NULL);
	if (must_close)
		xptone_close();
	return ret;
}
#endif

/********************************************************************************/
/* Play a tone through the wave/DSP output device (sound card) - Deuce			*/
/********************************************************************************/

bool xptone(double freq, DWORD duration, DWORD shape)
{
	unsigned char *wave;
	int            samples;
	bool           ret;

	wave = (unsigned char *)malloc(S_RATE * 15 / 2 + 1);
	if (!wave)
		return false;
	if (freq < 17 && freq != 0)
		freq = 17;
	samples = S_RATE * duration / 1000;
	if ((shape & WAVE_SHAPE_NO_CLEAN) == 0) {
		if (freq) {
			if (samples <= S_RATE / freq * 2)
				samples = S_RATE / freq * 2;
		}
	}
	if (freq == 0 || samples > S_RATE / freq * 2) {
		int sample_len;

		xptone_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)) {
				free(wave);
				return false;
			}
			samples -= sample_len;
		}
	}
	xptone_makewave(freq, wave, samples, shape);
	ret = xp_play_sample(wave, samples, false);
	free(wave);
	return ret;
}

#ifdef __unix__
/****************************************************************************/
/* Generate a tone at specified frequency for specified milliseconds		*/
/* Thanks to Casey Martin (and Deuce) for this code							*/
/****************************************************************************/
void unix_beep(int freq, int dur)
{
#if (defined(__FreeBSD__) && defined(HAS_MACHINE_SPEAKER_H)) || ((defined(__OpenBSD__) || defined(__NetBSD__)) && defined(HAS_MACHINE_SPKR_H))
	int    speaker_fd = -1;
	tone_t tone;

	speaker_fd = open("/dev/speaker", O_WRONLY | O_APPEND);
	if (speaker_fd != -1)  {
		tone.frequency = freq;
		tone.duration = dur / 10;
		ioctl(speaker_fd, SPKRTONE, &tone);
		close(speaker_fd);
		return;
	}
#endif

#if defined(__FreeBSD__) || defined(__solaris__)
	static int console_fd = -1;

	if (console_fd == -1)
		console_fd = open("/dev/console", O_NOCTTY);

	if (console_fd != -1) {
#if defined(__solaris__)
		ioctl(console_fd, KIOCCMD, KBD_CMD_BELL);
#else
		if (freq != 0)   /* Don't divide by zero */
			ioctl(console_fd, KIOCSOUND, (int) (1193180 / freq));
#endif /* solaris */
		SLEEP(dur);
#if defined(__solaris__)
		ioctl(console_fd, KIOCCMD, KBD_CMD_NOBELL); /* turn off tone */
#else
		ioctl(console_fd, KIOCSOUND, 0);    /* turn off tone */
#endif /* solaris */
	}
#endif
}

#endif

/********************************************************************************/
/* Play sound through DSP/wave device, if unsuccessful, play through PC speaker	*/
/********************************************************************************/
void xpbeep(double freq, DWORD duration)
{
	if (xptone(freq, duration, WAVE_SHAPE_SINE_SAW_HARM))
		return;

#if defined(_WIN32)
	Beep((DWORD)(freq + .5), duration);
#elif defined(__unix__)
	unix_beep((int)(freq + .5), duration);
#endif
}
