/*
 * This modue provides sound access for QUISK using the ALSA
 * library for Linux.
*/
#include <Python.h>
#include <complex.h>
#include <math.h>
#include <alsa/asoundlib.h>
#include "quisk.h"

/*
 The sample rate is in frames per second.  Each frame has a number of channels,
 and each channel has a sample of size sample_bytes.  The channels are interleaved:
 (channel0, channel1), (channel0, channel1), ...
*/

#define CORRECT_PLAY_RATE	1

extern struct sound_conf quisk_sound_state;	// Current sound status

static int is_little_endian;		// Test byte order; is it little-endian?
static short buffer2[SAMP_BUFFER_SIZE];				// Buffer for 2-byte samples from sound
static unsigned char buffer3[3 * SAMP_BUFFER_SIZE];	// Buffer for 3-byte samples from sound
static int buffer4[SAMP_BUFFER_SIZE];				// Buffer for 4-byte samples from sound
static int bufferz[SAMP_BUFFER_SIZE];				// Buffer for zero samples
static double mic_playbuf_util = 0.70;		// Current mic play buffer utilization 0.0 to 1.0

int quisk_read_alsa(struct sound_dev * dev, complex double * cSamples)
{	// cSamples can be NULL to discard samples.
	// Read sound samples from the ALSA soundcard.
	// Samples are converted to 32 bits with a range of +/- CLIP32 and placed into cSamples.
	int i;
	snd_pcm_sframes_t frames, delay, avail;
	short si, sq;
	int ii, qq;
	int nSamples;

	if (!dev->handle)
		return -1;

	switch(snd_pcm_state(dev->handle)) {
	case SND_PCM_STATE_RUNNING:
		break;
	case SND_PCM_STATE_PREPARED:
		break;
	case SND_PCM_STATE_XRUN:
#if DEBUG_IO
		QuiskPrintTime("read_alsa: Capture overrun", 0);
#endif
		snd_pcm_prepare(dev->handle);
		break;
	default:
#if DEBUG_IO
		QuiskPrintTime("read_alsa: State UNKNOWN", 0);
#endif
		break;
	}

	if (snd_pcm_avail_delay(dev->handle, &avail, &delay) >= 0) {
		dev->dev_latency = avail + delay;	// avail frames can be read plus delay frames digitized but can't be read yet
	}
	else {
		avail = 32;
		dev->dev_latency = 0;
		dev->dev_error++;
#if DEBUG_IO
		QuiskPrintTime("read_alsa: snd_pcm_avail_delay failed", 0);
#endif
	}
	if (dev->read_frames == 0) {		// non-blocking: read available frames
		if (avail < 32)
			avail = 32;	// read frames to restart from error
	}
	else {
		avail = dev->read_frames;	// size of read request
	}
	i = SAMP_BUFFER_SIZE * 8 / 10 / dev->num_channels;	// limit read request to buffer size
	if (avail > i)
		avail = i;
	nSamples = 0;
	switch (dev->sample_bytes) {
	case 2:
		frames = snd_pcm_readi (dev->handle, buffer2, avail);	// read samples
		if ( ! cSamples)
			return 0;
		if (frames == -EAGAIN) {	// no samples available
			break;
		}
		else if (frames <= 0) {		// error
			dev->dev_error++;
#if DEBUG_IO
			QuiskPrintTime("read_alsa: frames < 0", 0);
#endif
			snd_pcm_prepare (dev->handle);
			snd_pcm_start (dev->handle);
			break;
		}
		for (i = 0; frames; i += dev->num_channels, frames--) {
			si = buffer2[i + dev->channel_I];
			sq = buffer2[i + dev->channel_Q];
			if (si >=  CLIP16 || si <= -CLIP16)
				dev->overrange++;	// assume overrange returns max int
			if (sq >=  CLIP16 || sq <= -CLIP16)
				dev->overrange++;
			ii = si << 16;
			qq = sq << 16;
			cSamples[nSamples] = ii + I * qq;
			nSamples++;
		}
		break;
	case 3:
		frames = snd_pcm_readi (dev->handle, buffer3, avail);	// read samples
		if ( ! cSamples)
			return 0;
		if (frames == -EAGAIN) {	// no samples available
			break;
		}
		else if (frames <= 0) {		// error
			dev->dev_error++;
#if DEBUG_IO
			QuiskPrintTime("read_alsa: frames < 0", 0);
#endif
			snd_pcm_prepare (dev->handle);
			snd_pcm_start (dev->handle);
			break;
		}
		for (i = 0; frames; i += dev->num_channels, frames--) {
			ii = qq = 0;
			if (!is_little_endian) {	// convert to big-endian
				*((unsigned char *)&ii    ) = buffer3[(i + dev->channel_I) * 3 + 2];
				*((unsigned char *)&ii + 1) = buffer3[(i + dev->channel_I) * 3 + 1];
				*((unsigned char *)&ii + 2) = buffer3[(i + dev->channel_I) * 3    ];
				*((unsigned char *)&qq    ) = buffer3[(i + dev->channel_Q) * 3 + 2];
				*((unsigned char *)&qq + 1) = buffer3[(i + dev->channel_Q) * 3 + 1];
				*((unsigned char *)&qq + 2) = buffer3[(i + dev->channel_Q) * 3    ];
			}
			else {		// convert to little-endian
				memcpy((unsigned char *)&ii + 1, buffer3 + (i + dev->channel_I) * 3, 3);
				memcpy((unsigned char *)&qq + 1, buffer3 + (i + dev->channel_Q) * 3, 3);
			}
			if (ii >=  CLIP32 || ii <= -CLIP32)
				dev->overrange++;	// assume overrange returns max int
			if (qq >=  CLIP32 || qq <= -CLIP32)
				dev->overrange++;
			cSamples[nSamples] = ii + I * qq;
			nSamples++;
		}
		break;
	case 4:
		frames = snd_pcm_readi (dev->handle, buffer4, avail);	// read samples
		if ( ! cSamples)
			return 0;
		if (frames == -EAGAIN) {	// no samples available
			break;
		}
		else if (frames <= 0) {		// error
			dev->dev_error++;
#if DEBUG_IO
			QuiskPrintTime("read_alsa: frames < 0", 0);
#endif
			snd_pcm_prepare (dev->handle);
			snd_pcm_start (dev->handle);
			break;
		}
		for (i = 0; frames; i += dev->num_channels, frames--) {
			ii = buffer4[i + dev->channel_I];
			qq = buffer4[i + dev->channel_Q];
			if (ii >=  CLIP32 || ii <= -CLIP32)
				dev->overrange++;	// assume overrange returns max int
			if (qq >=  CLIP32 || qq <= -CLIP32)
				dev->overrange++;
			cSamples[nSamples] = ii + I * qq;
			nSamples++;
		}
		break;
	default:
		return 0;
	}
#if CORRECT_PLAY_RATE
	if ( ! strcmp(dev->stream_description, "Microphone Input")) {
		if (mic_playbuf_util > 0.85) {		// Remove a sample
			nSamples--;
#if DEBUG_IO
			printf("read_alsa %s: Remove a mic sample, util %.2lf\n", dev->stream_description, mic_playbuf_util);
#endif
		}
		else if(cSamples && mic_playbuf_util < 0.55 && nSamples >= 2) {	// Add a sample
			cSamples[nSamples] = cSamples[nSamples - 1];
			cSamples[nSamples - 1] = (cSamples[nSamples - 2] + cSamples[nSamples]) / 2.0;
			nSamples++;
#if DEBUG_IO
			printf("read_alsa %s: Add a mic sample, util %.2lf\n", dev->stream_description, mic_playbuf_util);
#endif
		}
	}
#endif
	return nSamples;
}

void quisk_play_alsa(struct sound_dev * playdev, int nSamples,
		complex double * cSamples, int report_latency, double volume)
{	// Play the samples; write them to the ALSA soundcard.
	int i, n, index;
	snd_pcm_sframes_t frames, delay;
	int ii, qq;
#if DEBUG_IO
	static int timer=0;
#endif

	if (!playdev->handle || nSamples <= 0)
		return;
// Note: snd_pcm_delay() is not reliable when using the "default" ALSA device and
// arbitrary sample rates.  It seems to be confused by the rate conversion.  So we
// are changing rates (decimate) ourselves.
//
	delay = 0;
	switch(snd_pcm_state(playdev->handle)) {
	case SND_PCM_STATE_RUNNING:
		//printf("State RUNNING\n");
		snd_pcm_delay(playdev->handle, &delay);	// samples left in play buffer
		break;
	case SND_PCM_STATE_PREPARED:
#if DEBUG_IO
		//QuiskPrintTime("play_alsa: State PREPARED", 0);
#endif
		snd_pcm_delay(playdev->handle, &delay);
		delay = 0;
		break;
	case SND_PCM_STATE_XRUN:
#if DEBUG_IO
		QuiskPrintTime("", 0);
		printf("play_alsa: Play underrun; nSamples %d\n", nSamples);
#endif
		quisk_sound_state.underrun_error++;
		playdev->dev_underrun++;
		snd_pcm_prepare(playdev->handle);
		break;
	default:
#if DEBUG_IO
		QuiskPrintTime("play_alsa: State UNKNOWN", 0);
#endif
		break;
	}
	playdev->dev_latency = delay;
	if (report_latency) {		// Report for main playback device
		quisk_sound_state.latencyPlay = delay;		// samples left in play buffer
	}
	if ( ! strcmp(playdev->stream_description, "I/Q Tx Sample Output")) {
		mic_playbuf_util = (double)(nSamples + delay) / playdev->latency_frames;
	}
	// There will be additional samples available to read in the capture buffer.
	index = 0;
#if CORRECT_PLAY_RATE
#if DEBUG_IO
	timer += nSamples;
	if (timer > playdev->sample_rate) {
		timer = 0;
		printf("play_alsa %s: Samples new %d old %ld total %ld latency_frames %d\n", playdev->stream_description, nSamples, delay, nSamples + delay, playdev->latency_frames);
	}
#endif
	if (volume == 0.0) {
		n = playdev->latency_frames * 7 / 10 - delay;	// samples needed to get back to the starting value of 70% full
#if DEBUG_IO
		if (abs(n - nSamples) > 100)
			printf("play_alsa %s: Zero volume correction nSamples %5d to %5d\n", playdev->stream_description, nSamples, n);
#endif
		if (n <= 0)
			nSamples = 0;
		else if (n <= nSamples)
			nSamples = n;
		else {
			n -= nSamples;	// number of samples to add
			if (n > 100)	// arbitrary increase - should be tested
				n = 100;
			for (i = 0; i < n; i++)
				cSamples[nSamples++] = 0.0;
		}
	}
	else if (nSamples + delay > playdev->latency_frames * 95 / 100) {
		nSamples--;
#if DEBUG_IO
		printf("play_alsa %s: Remove a sample nSamples %d  delay %d  total %d\n", playdev->stream_description, nSamples, (int)delay, nSamples + (int)delay);
#endif
	}
	else if(nSamples + delay < playdev->latency_frames * 4 / 10 && nSamples >= 2) {
		cSamples[nSamples] = cSamples[nSamples - 1];
		cSamples[nSamples - 1] = (cSamples[nSamples - 2] + cSamples[nSamples]) / 2.0;
		nSamples++;
#if DEBUG_IO
		printf ("play_alsa %s: Add a sample nSamples %d  delay %d  total %d\n", playdev->stream_description, nSamples, (int)delay, nSamples + (int)delay);
#endif
	}
#endif
	if (nSamples + delay > playdev->latency_frames) {
		index = nSamples + delay - playdev->latency_frames;	// write only the most recent samples
		quisk_sound_state.write_error++;
		playdev->dev_error++;
#if DEBUG_IO
		//QuiskPrintTime("", 0);
		printf("play_alsa %s: Discard %d of %d samples at %ld delay\n", playdev->stream_description, index, nSamples, delay);
#endif
	}

	if (playdev->sample_bytes == 2) {
		while (index < nSamples) {
			for (i = 0, n = index; n < nSamples; i += playdev->num_channels, n++) {
				ii = (int)(volume * creal(cSamples[n]) / 65536);
				qq = (int)(volume * cimag(cSamples[n]) / 65536);
				buffer2[i + playdev->channel_I] = (short)ii;
				buffer2[i + playdev->channel_Q] = (short)qq;
			}
			n = n - index;
			frames = snd_pcm_writei (playdev->handle, buffer2, n);
			if (frames <= 0) {
#if DEBUG_IO
				QuiskPrintTime("play_alsa: frames < 0", 0);
#endif
				if (frames == -EPIPE) {	// underrun
					quisk_sound_state.underrun_error++;
					playdev->dev_underrun++;
				}
				else {
					quisk_sound_state.write_error++;
					playdev->dev_error++;
				}
				snd_pcm_prepare(playdev->handle);
				frames = snd_pcm_writei (playdev->handle, buffer2, n);
				if (frames <= 0) {
					index = nSamples;	// give up
				}
				else {
					index += frames;
				}
			}
			else {
				index += frames;
			}
		}
	}
	else if (playdev->sample_bytes == 3) {
		while (index < nSamples) {
			for (i = 0, n = index; n < nSamples; i += playdev->num_channels, n++) {
				ii = (int)(volume * creal(cSamples[n]) / 256);
				qq = (int)(volume * cimag(cSamples[n]) / 256);
				if (!is_little_endian) {	// convert to big-endian
					buffer3[(i + playdev->channel_I) * 3    ] = *((unsigned char *)&ii + 2);
					buffer3[(i + playdev->channel_Q) * 3    ] = *((unsigned char *)&qq + 2);
					buffer3[(i + playdev->channel_I) * 3 + 1] = *((unsigned char *)&ii + 1);
					buffer3[(i + playdev->channel_Q) * 3 + 1] = *((unsigned char *)&qq + 1);
					buffer3[(i + playdev->channel_I) * 3 + 2] = *((unsigned char *)&ii    );
					buffer3[(i + playdev->channel_Q) * 3 + 2] = *((unsigned char *)&qq    );
				}
				else {	// convert to little-endian
					memcpy(buffer3 + (i + playdev->channel_I) * 3, (unsigned char *)&ii, 3);
					memcpy(buffer3 + (i + playdev->channel_Q) * 3, (unsigned char *)&qq, 3);
				}
			}
			n = n - index;
			frames = snd_pcm_writei (playdev->handle, buffer3, n);
			if (frames <= 0) {
#if DEBUG_IO
				QuiskPrintTime("play_alsa: frames < 0", 0);
#endif
				if (frames == -EPIPE) {	// underrun
					quisk_sound_state.underrun_error++;
					playdev->dev_underrun++;
				}
				else {
					quisk_sound_state.write_error++;
					playdev->dev_error++;
				}
				snd_pcm_prepare(playdev->handle);
				frames = snd_pcm_writei (playdev->handle, buffer3, n);
				if (frames <= 0) {
					index = nSamples;	// give up
				}
				else {
					index += frames;
				}
			}
			else {
				index += frames;
			}
		}
	}
	else if (playdev->sample_bytes == 4) {
		while (index < nSamples) {
			for (i = 0, n = index; n < nSamples; i += playdev->num_channels, n++) {
				ii = (int)(volume * creal(cSamples[n]));
				qq = (int)(volume * cimag(cSamples[n]));
				buffer4[i + playdev->channel_I] = ii;
				buffer4[i + playdev->channel_Q] = qq;
			}
			n = n - index;
			frames = snd_pcm_writei (playdev->handle, buffer4, n);
			if (frames <= 0) {
#if DEBUG_IO
				QuiskPrintTime("play_alsa: frames < 0", 0);
#endif
				if (frames == -EPIPE) {	// underrun
					quisk_sound_state.underrun_error++;
					playdev->dev_underrun++;
				}
				else {
					quisk_sound_state.write_error++;
					playdev->dev_error++;
				}
				snd_pcm_prepare(playdev->handle);
				frames = snd_pcm_writei (playdev->handle, buffer4, n);
				if (frames <= 0) {
					index = nSamples;	// give up
				}
				else {
					index += frames;
				}
			}
			else {
				index += frames;
			}
		}
	}
}

static int device_list(PyObject * py, snd_pcm_stream_t stream, char * name)
{	// return 1 if the card name was substituted
	snd_ctl_t *handle;
	int card, err, dev;
	char buf100[100];
	const char * card_text, * pcm_text;
	snd_ctl_card_info_t *info;
	snd_pcm_info_t *pcminfo;

	snd_ctl_card_info_alloca(&info);
	snd_pcm_info_alloca(&pcminfo);

	card = -1;
	if (snd_card_next(&card) < 0 || card < 0) {
		printf("no soundcards found...\n");
		return 0;
	}
	while (card >= 0) {
		sprintf(buf100, "hw:%d", card);
		if ((err = snd_ctl_open(&handle, buf100, 0)) < 0) {
			printf("device_list: control open (%i): %s", card, snd_strerror(err));
			goto next_card;
		}
		if ((err = snd_ctl_card_info(handle, info)) < 0) {
			printf("device_list: control hardware info (%i): %s", card, snd_strerror(err));
			snd_ctl_close(handle);
			goto next_card;
		}
		dev = -1;
		while (1) {
			if (snd_ctl_pcm_next_device(handle, &dev)<0)
				printf("device_list: snd_ctl_pcm_next_device\n");
			if (dev < 0)
				break;
			snd_pcm_info_set_device(pcminfo, dev);
			snd_pcm_info_set_subdevice(pcminfo, 0);
			snd_pcm_info_set_stream(pcminfo, stream);
			card_text = snd_ctl_card_info_get_name(info);
			if ( ! card_text || ! card_text[0])
				card_text = snd_ctl_card_info_get_id(info);
			if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
				if (err != -ENOENT)
					printf ("device_list: control digital audio info (%i): %s", card, snd_strerror(err));
				continue;
			}
			else {
				pcm_text = snd_pcm_info_get_name(pcminfo);
				if ( ! pcm_text || ! pcm_text[0])
					pcm_text = snd_pcm_info_get_id(pcminfo);
			}
			snprintf(buf100, 100, "%s %s (hw:%d,%d)", card_text, pcm_text, card, dev);
			if (py) {		// add to list of devices
				PyList_Append(py, PyString_FromString(buf100));
			}
			if (name) {		// return the "hw:" name
				if (strstr(buf100, name)) {
					snprintf(name, QUISK_SC_SIZE, "hw:%d,%d", card, dev);
					snd_ctl_close(handle);
					return 1;
				}
			}
		}
		snd_ctl_close(handle);
	next_card:
		if (snd_card_next(&card) < 0) {
			printf("snd_card_next\n");
			break;
		}
	}
	return 0;
}

PyObject * quisk_sound_devices(PyObject * self, PyObject * args)
{	// Return a list of ALSA device names [pycapt, pyplay]
	PyObject * pylist, * pycapt, * pyplay;

	if (!PyArg_ParseTuple (args, ""))
		return NULL;
	// Each pycapt and pyplay is [pydev, pyname]
	pylist = PyList_New(0);		// list [pycapt, pyplay]
	pycapt = PyList_New(0);		// list of capture devices
	pyplay = PyList_New(0);		// list of play devices
	PyList_Append(pylist, pycapt);
	PyList_Append(pylist, pyplay);
	device_list(pycapt, SND_PCM_STREAM_CAPTURE, NULL);
	device_list(pyplay, SND_PCM_STREAM_PLAYBACK, NULL);
	return pylist;
}

static snd_pcm_format_t check_formats(struct sound_dev * dev, snd_pcm_hw_params_t *hware)
{
	snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
	dev->sample_bytes = 0;

	strncpy (dev->msg1, "Available formats: ", QUISK_SC_SIZE);
	if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S32) == 0) {
		if (!dev->sample_bytes) {
			strncat(dev->msg1, "*", QUISK_SC_SIZE);
			dev->sample_bytes = 4;
			format = SND_PCM_FORMAT_S32;
		}
		strncat(dev->msg1, "S32 ", QUISK_SC_SIZE);
	}
	if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_U32) == 0) {
		strncat(dev->msg1, "U32 ", QUISK_SC_SIZE);
	}
	if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S24) == 0) {
		strncat(dev->msg1, "S24 ", QUISK_SC_SIZE);
	}
	if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_U24) == 0) {
		strncat(dev->msg1, "U24 ", QUISK_SC_SIZE);
	}
	if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S24_3LE) == 0) {
		if (!dev->sample_bytes) {
			strncat(dev->msg1, "*", QUISK_SC_SIZE);
			dev->sample_bytes = 3;
			format = SND_PCM_FORMAT_S24_3LE;
		}
		strncat(dev->msg1, "S24_3LE ", QUISK_SC_SIZE);
	}
	if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S16) == 0) {
		if (!dev->sample_bytes) {
			strncat(dev->msg1, "*", QUISK_SC_SIZE);
			dev->sample_bytes = 2;
			format = SND_PCM_FORMAT_S16;
		}
		strncat(dev->msg1, "S16 ", QUISK_SC_SIZE);
	}
	if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_U16) == 0) {
		strncat(dev->msg1, "U16 ", QUISK_SC_SIZE);
	}
	if (format == SND_PCM_FORMAT_UNKNOWN)
		strncat(dev->msg1, "*UNSUPPORTED", QUISK_SC_SIZE);
	else
		snd_pcm_hw_params_set_format (dev->handle, hware, format);
	return format;
}

static int quisk_open_alsa_capture(struct sound_dev * dev)
{	// Open the ALSA soundcard for capture.  Return non-zero for error.
	int i, err, dir, sample_rate, mode;
	int poll_size;
	unsigned int ui;
	char buf[QUISK_SC_SIZE];
	snd_pcm_hw_params_t *hware;
	snd_pcm_sw_params_t *sware;
	snd_pcm_uframes_t frames;
	snd_pcm_t * handle;

	if ( ! dev->name[0])	// Check for null capture name
		return 0;

#if DEBUG_IO
	printf("*** Capture on alsa device %s\n", dev->name);
#endif
	if (dev->read_frames == 0)
		mode = SND_PCM_NONBLOCK;
	else
		mode = 0;
	if ( ! strncmp (dev->name, "alsa:", 5)) {	// search for the name in info strings, put device name into buf
		strncpy(buf, dev->name + 5, QUISK_SC_SIZE);
		device_list(NULL, SND_PCM_STREAM_CAPTURE, buf);
	}
	else {		// just try to open the name
		strncpy(buf, dev->name, QUISK_SC_SIZE);
	}
	for (i = 0; i < 6; i++) {	// try a few times in case the device is busy
		err = snd_pcm_open (&handle, buf, SND_PCM_STREAM_CAPTURE, mode);
		if (err >= 0)
			break;
		QuiskSleepMicrosec(500000);
	}
	if (err < 0) {
		snprintf(quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot open capture device %.40s (%.40s)",
				dev->name, snd_strerror (err));
		return 1;
	}
	dev->handle = handle;
	dev->driver = DEV_DRIVER_ALSA;
	if ((err = snd_pcm_sw_params_malloc (&sware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot allocate software parameter structure (%s)\n",
				snd_strerror (err));
		return 1;
	}
	if ((err = snd_pcm_hw_params_malloc (&hware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot allocate hardware parameter structure (%s)\n",
				snd_strerror (err));
		snd_pcm_sw_params_free (sware);
		return 1;
	}
	if ((err = snd_pcm_hw_params_any (handle, hware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot initialize capture parameters (%s)\n",
				snd_strerror (err));
		goto errend;
	}
	/* UNAVAILABLE
	if ((err = snd_pcm_hw_params_set_rate_resample (handle, hware, 0)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot disable resampling (%s)\n",
			snd_strerror (err));
		goto errend;
	}
	*/
	// Get some parameters to send back
	if (snd_pcm_hw_params_get_rate_min(hware, &dev->rate_min, &dir) != 0)
		dev->rate_min = 0;	// Error
	if (snd_pcm_hw_params_get_rate_max(hware, &dev->rate_max, &dir) != 0)
		dev->rate_max = 0;	// Error
	if (snd_pcm_hw_params_get_channels_min(hware, &dev->chan_min) != 0)
		dev->chan_min= 0;	// Error
	if (snd_pcm_hw_params_get_channels_max(hware, &dev->chan_max) != 0)
		dev->chan_max= 0;	// Error
#if DEBUG_IO
	printf("Sample rate min %d  max %d\n",  dev->rate_min, dev->rate_max);
	printf("Number of channels min %d  max %d\n",  dev->chan_min, dev->chan_max);
#endif
	// Set the capture parameters
	if (check_formats(dev, hware) == SND_PCM_FORMAT_UNKNOWN) {
		strncpy(quisk_sound_state.msg1, dev->msg1, QUISK_SC_SIZE);
		strncpy (quisk_sound_state.err_msg, "Quisk does not support your capture format.", QUISK_SC_SIZE);
		goto errend;
	}
	strncpy(quisk_sound_state.msg1, dev->msg1, QUISK_SC_SIZE);
	sample_rate = dev->sample_rate;
	if (snd_pcm_hw_params_set_rate (handle, hware, sample_rate, 0) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Can not set sample rate %d",
			sample_rate);
		goto errend;
	}
	if (snd_pcm_hw_params_set_access (handle, hware, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
		strncpy(quisk_sound_state.err_msg, "Interleaved access is not available", QUISK_SC_SIZE);
		goto errend;
	}
	if (snd_pcm_hw_params_get_channels_min(hware, &ui) != 0)
		ui = 0;	// Error
	if (dev->num_channels < ui)		// increase number of channels to minimum available
		dev->num_channels = ui;
	if (snd_pcm_hw_params_set_channels (handle, hware, dev->num_channels) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Can not set channels to %d", dev->num_channels);
		goto errend;
	}
	// Try to set a capture buffer larger than needed
	frames = sample_rate * 200 / 1000;	// buffer size in milliseconds
	if (snd_pcm_hw_params_set_buffer_size_near (handle, hware, &frames) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Can not set capture buffer size");
		goto errend;
	}
	poll_size = (int)(quisk_sound_state.data_poll_usec * 1e-6 * sample_rate + 0.5);
	if (frames < poll_size * 3) {		// buffer size is too small, reduce poll time
		quisk_sound_state.data_poll_usec = (int)(frames * 1.e6 / sample_rate / 3 + 0.5);
#if DEBUG_IO
		printf("Reduced data_poll_usec %d for small sound capture buffer\n",
			quisk_sound_state.data_poll_usec);
#endif
	}
#if DEBUG_IO
	printf("sample rate %d\n", sample_rate);
	printf("num_channels %d, %s\n", dev->num_channels, dev->msg1);
	printf("Capture buffer size %d\n", (int)frames);
	if (frames > SAMP_BUFFER_SIZE / dev->num_channels)
		printf("Capture buffer exceeds size of sample buffers\n");
#endif
	if ((err = snd_pcm_hw_params (handle, hware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot set hw capture parameters (%s)\n",
				snd_strerror (err));
		goto errend;
	}
	if ((err = snd_pcm_sw_params_current (handle, sware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot get software capture parameters (%s)\n",
				snd_strerror (err));
		goto errend;
	}

	if ((err = snd_pcm_prepare (handle)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot prepare capture interface for use (%s)\n",
			snd_strerror (err));
		goto errend;
	}
	// Success
	snd_pcm_hw_params_free (hware);
	snd_pcm_sw_params_free (sware);
#if DEBUG_IO
    printf("*** End capture on alsa device %s %s\n", dev->name, quisk_sound_state.err_msg);
#endif
	return 0;
errend:
	snd_pcm_hw_params_free (hware);
	snd_pcm_sw_params_free (sware);
#if DEBUG_IO
    printf("*** Error end for capture on alsa device %s %s\n", dev->name, quisk_sound_state.err_msg);
#endif
	return 1;
}

static int quisk_open_alsa_playback(struct sound_dev * dev)
{	// Open the ALSA soundcard for playback.  Return non-zero on error.
	int i, err, dir, sample_rate, mode;
	unsigned int ui;
	char buf[QUISK_SC_SIZE];
	snd_pcm_hw_params_t *hware;
	snd_pcm_sw_params_t *sware;
	snd_pcm_uframes_t frames;
	snd_pcm_t * handle;

	if ( ! dev->name[0])	// Check for null play name
		return 0;

#if DEBUG_IO
	printf("*** Playback on alsa device %s\n", dev->name);
	printf("quisk_open_alsa_playback(): %s\n", dev->stream_description);
#endif
	if (dev->read_frames == 0)
		mode = SND_PCM_NONBLOCK;
	else
		mode = 0;
	if ( ! strncmp (dev->name, "alsa:", 5)) {	// search for the name in info strings, put device name into buf
		strncpy(buf, dev->name + 5, QUISK_SC_SIZE);
		device_list(NULL, SND_PCM_STREAM_PLAYBACK, buf);
	}
	else {		// just try to open the name
		strncpy(buf, dev->name, QUISK_SC_SIZE);
	}
	for (i = 0; i < 6; i++) {	// try a few times in case the device is busy
		err = snd_pcm_open (&handle, buf, SND_PCM_STREAM_PLAYBACK, mode);
		if (err >= 0)
			break;
		QuiskSleepMicrosec(500000);
	}
	if (err < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot open playback device %.40s (%.40s)\n",
				dev->name, snd_strerror (err));
		return 1;
	}
	dev->handle = handle;
	if ((err = snd_pcm_sw_params_malloc (&sware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot allocate software parameter structure (%s)\n",
				snd_strerror (err));
		return 1;
	}
	if ((err = snd_pcm_hw_params_malloc (&hware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot allocate hardware parameter structure (%s)\n",
				snd_strerror (err));
		snd_pcm_sw_params_free (sware);
		return 1;
	}
	if ((err = snd_pcm_hw_params_any (handle, hware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot initialize playback parameter structure (%s)\n",
				snd_strerror (err));
		goto errend;
	}
	// Get some parameters to send back
	if (snd_pcm_hw_params_get_rate_min(hware, &dev->rate_min, &dir) != 0)
		dev->rate_min = 0;	// Error
	if (snd_pcm_hw_params_get_rate_max(hware, &dev->rate_max, &dir) != 0)
		dev->rate_max = 0;	// Error
	if (snd_pcm_hw_params_get_channels_min(hware, &dev->chan_min) != 0)
		dev->chan_min= 0;	// Error
	if (snd_pcm_hw_params_get_channels_max(hware, &dev->chan_max) != 0)
		dev->chan_max= 0;	// Error
#if DEBUG_IO
	printf("Sample rate min %d  max %d\n",  dev->rate_min, dev->rate_max);
	printf("Number of channels min %d  max %d\n",  dev->chan_min, dev->chan_max);
#endif
	// Set the playback parameters
	sample_rate = dev->sample_rate;
	if (snd_pcm_hw_params_set_rate (handle, hware, sample_rate, 0) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot set playback rate %d",
			sample_rate);
		goto errend;
	}
	if (snd_pcm_hw_params_set_access (handle, hware, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot set playback access to interleaved.");
		goto errend;
	}
	if (snd_pcm_hw_params_get_channels_min(hware, &ui) != 0)
		ui = 0;	// Error
	if (dev->num_channels < ui)		// increase number of channels to minimum available
		dev->num_channels = ui;
	if (snd_pcm_hw_params_set_channels (handle, hware, dev->num_channels) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot set playback channels to %d",
			dev->num_channels);
		goto errend;
	}
	if (check_formats(dev, hware) == SND_PCM_FORMAT_UNKNOWN) {
		strncpy(quisk_sound_state.msg1, dev->msg1, QUISK_SC_SIZE);
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot set playback format.");
		goto errend;
	}
	// Try to set a play buffer larger than needed
	frames = sample_rate * 200 / 1000;	// buffer size in milliseconds
	if (snd_pcm_hw_params_set_buffer_size_near (handle, hware, &frames) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Can not set playback buffer size");
		goto errend;
	}
	dev->play_buf_size = frames;
#if DEBUG_IO
	printf("num_channels %d, %s\n", dev->num_channels, dev->msg1);
	printf("Playback buffer size %d\n", dev->play_buf_size);
#endif
	if ((err = snd_pcm_hw_params (handle, hware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot set playback hw_params (%s)\n",
			snd_strerror (err));
		goto errend;
	}
	if ((err = snd_pcm_sw_params_current (handle, sware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot get software playback parameters (%s)\n",
				snd_strerror (err));
		goto errend;
	}
	if (dev->latency_frames > dev->play_buf_size) {
		dev->latency_frames = dev->play_buf_size;
#if DEBUG_IO
		printf("Latency frames limited to buffer size\n");
#endif
	}
#if DEBUG_IO
	printf("Audio rate %d latency_frames %d\n", sample_rate, dev->latency_frames);
#endif
	if (snd_pcm_sw_params_set_start_threshold (handle, sware, dev->latency_frames * 7 / 10) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot set start threshold\n");
		goto errend;
	}
	if ((err = snd_pcm_sw_params (handle, sware)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot set playback sw_params (%s)\n",
			snd_strerror (err));
		goto errend;
	}
#if DEBUG_IO
		snd_pcm_sw_params_get_silence_threshold(sware, &frames);
		printf("play silence threshold %d\n", (int)frames);
		snd_pcm_sw_params_get_silence_size(sware, &frames);
		printf("play silence size %d\n", (int)frames);
		snd_pcm_sw_params_get_start_threshold(sware, &frames);
		printf("play start threshold %d\n", (int)frames);
		printf ("play channels are %d %d\n", dev->channel_I, dev->channel_Q);
#endif
	if ((err = snd_pcm_prepare (handle)) < 0) {
		snprintf (quisk_sound_state.err_msg, QUISK_SC_SIZE, "Cannot prepare playback interface for use (%s)\n",
			snd_strerror (err));
		goto errend;
	}
	// Success
	snd_pcm_hw_params_free (hware);
	snd_pcm_sw_params_free (sware);
#if DEBUG_IO
    printf("*** End playback on alsa device %s %s\n", dev->name, quisk_sound_state.err_msg);
#endif
	return 0;
errend:
	snd_pcm_hw_params_free (hware);
	snd_pcm_sw_params_free (sware);
#if DEBUG_IO
    printf("*** Error end for playback on alsa device %s %s\n", dev->name, quisk_sound_state.err_msg);
#endif
	return 1;
}

void quisk_start_sound_alsa (struct sound_dev ** pCapture, struct sound_dev ** pPlayback)
{
	struct sound_dev * pDev;

	memset(bufferz, 0, sizeof(int) * SAMP_BUFFER_SIZE);
	is_little_endian = 1;	// Test machine byte order
	if (*(char *)&is_little_endian == 1)
		is_little_endian = 1;
	else
		is_little_endian = 0;
	if (quisk_sound_state.err_msg[0])
		return;		// prior error
	// Open the alsa playback devices
	while (1) {
		pDev = *pPlayback++;
		if ( ! pDev)
			break;
		if ( ! pDev->handle && pDev->driver == DEV_DRIVER_ALSA)
			if (quisk_open_alsa_playback(pDev))
				return;		// error
	}
	// Open the alsa capture devices and start them
	while (1) {
		pDev = *pCapture++;
		if ( ! pDev)
			break;
		if ( ! pDev->handle && pDev->driver == DEV_DRIVER_ALSA) {
			if (quisk_open_alsa_capture(pDev))
				return;		// error
			if (pDev->handle)
				snd_pcm_start((snd_pcm_t *)pDev->handle);
		}
	}
}

void quisk_close_sound_alsa(struct sound_dev ** pCapture, struct sound_dev ** pPlayback)
{
	struct sound_dev * pDev;

	while (*pCapture) {
		pDev = *pCapture;
		if (pDev->handle && pDev->driver == DEV_DRIVER_ALSA) {
			snd_pcm_drop((snd_pcm_t *)pDev->handle);
			snd_pcm_close((snd_pcm_t *)pDev->handle);
		}
		pDev->handle = NULL;
		pDev->driver = DEV_DRIVER_NONE;
		pCapture++;
	}
	while (*pPlayback) {
		pDev = *pPlayback;
		if (pDev->handle && pDev->driver == DEV_DRIVER_ALSA) {
			snd_pcm_drop((snd_pcm_t *)pDev->handle);
			snd_pcm_close((snd_pcm_t *)pDev->handle);
		}
		pDev->handle = NULL;
		pDev->driver = DEV_DRIVER_NONE;
		pPlayback++;
	}
}

void quisk_mixer_set(char * card_name, int numid, PyObject * value, char * err_msg, int err_size)
// Set card card_name mixer control numid to value for integer, boolean, enum controls.
// If value is a float, interpret value as a decimal fraction of min/max.
{
	int err;
	static snd_ctl_t * handle = NULL;
	snd_ctl_elem_info_t *info;
	snd_ctl_elem_id_t * id;
	snd_ctl_elem_value_t * control;
	unsigned int idx;
	long imin, imax, tmp;
	snd_ctl_elem_type_t type;
	unsigned int count;

	snd_ctl_elem_info_alloca(&info);
	snd_ctl_elem_id_alloca(&id);
	snd_ctl_elem_value_alloca(&control);

	err_msg[0] = 0;

	snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
	snd_ctl_elem_id_set_numid(id, numid);
	//snd_ctl_elem_id_set_index(id, index);
	//snd_ctl_elem_id_set_device(id, device);
	//snd_ctl_elem_id_set_subdevice(id, subdevice);
	if ( ! strncmp (card_name, "alsa:", 5)) {	// search for the name in info strings
		char buf[QUISK_SC_SIZE];
		strncpy(buf, card_name + 5, QUISK_SC_SIZE);
		if ( ! device_list(NULL, SND_PCM_STREAM_CAPTURE, buf))	// check capture and play names
			device_list(NULL, SND_PCM_STREAM_PLAYBACK, buf);
		buf[4] = 0;		// Remove device nuumber
		err = snd_ctl_open(&handle, buf, 0);
	}
	else {		// just try to open the name
		err = snd_ctl_open(&handle, card_name, 0);
	}
	if (err < 0) {
		snprintf (err_msg, err_size, "Control %s open error: %s\n", card_name, snd_strerror(err));
		return;
	}
	snd_ctl_elem_info_set_id(info, id);
	if ((err = snd_ctl_elem_info(handle, info)) < 0) {
		snprintf (err_msg, err_size, "Cannot find the given element from control %s\n", card_name);
		return;
	}
	snd_ctl_elem_info_get_id(info, id);
	type = snd_ctl_elem_info_get_type(info);
	snd_ctl_elem_value_set_id(control, id);
	count = snd_ctl_elem_info_get_count(info);
	
	for (idx = 0; idx < count; idx++) {
		switch (type) {
		case SND_CTL_ELEM_TYPE_BOOLEAN:
			if (PyObject_IsTrue(value))
				snd_ctl_elem_value_set_boolean(control, idx, 1);
			else
				snd_ctl_elem_value_set_boolean(control, idx, 0);
			break;
		case SND_CTL_ELEM_TYPE_INTEGER:
			imin = snd_ctl_elem_info_get_min(info);
			imax = snd_ctl_elem_info_get_max(info);
			if (PyFloat_CheckExact(value)) {
				tmp = (long)(imin + (imax - imin) * PyFloat_AsDouble(value) + 0.4);
				snd_ctl_elem_value_set_integer(control, idx, tmp);
			}
			else if(PyInt_Check(value)) {
				tmp = PyInt_AsLong(value);
				snd_ctl_elem_value_set_integer(control, idx, tmp);
			}
			else {
				snprintf (err_msg, err_size, "Control %s id %d has bad value\n", card_name, numid);
			}
			break;
		case SND_CTL_ELEM_TYPE_INTEGER64:
			imin = snd_ctl_elem_info_get_min64(info);
			imax = snd_ctl_elem_info_get_max64(info);
			if (PyFloat_CheckExact(value)) {
				tmp = (long)(imin + (imax - imin) * PyFloat_AsDouble(value) + 0.4);
				snd_ctl_elem_value_set_integer64(control, idx, tmp);
			}
			else if(PyInt_Check(value)) {
				tmp = PyInt_AsLong(value);
				snd_ctl_elem_value_set_integer64(control, idx, tmp);
			}
			else {
				snprintf (err_msg, err_size, "Control %s id %d has bad value\n", card_name, numid);
			}
			break;
		case SND_CTL_ELEM_TYPE_ENUMERATED:
			if(PyInt_Check(value)) {
				tmp = PyInt_AsLong(value);
				snd_ctl_elem_value_set_enumerated(control, idx, (unsigned int)tmp);
			}
			else {
				snprintf (err_msg, err_size, "Control %s id %d has bad value\n", card_name, numid);
			}
			break;
		default:
			snprintf (err_msg, err_size, "Control %s element has unknown type\n", card_name);
			break;
		}
		if ((err = snd_ctl_elem_write(handle, control)) < 0) {
			snprintf (err_msg, err_size, "Control %s element write error: %s\n", card_name, snd_strerror(err));
			return;
		}
	}
	snd_ctl_close(handle);
	return;
}

