/*
 * Copyright (c) 2009 Jacob Meuser <jakemsr@sdf.lonestar.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <config.h>

#ifndef _REENTRANT
#define _REENTRANT
#endif
#ifndef _THREAD_SAFE
#define _THREAD_SAFE
#endif

#include <sys/types.h>

#include <errno.h>
#include <getopt.h>
#include <poll.h>
#include <sndio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <jack/types.h>
#include <jack/internal.h>
#include <jack/engine.h>
#include <jack/thread.h>
#include <sysdeps/time.h>

#include "sndio_driver.h"


#define SNDIO_DRIVER_N_PARAMS	10
const static jack_driver_param_desc_t sndio_params[SNDIO_DRIVER_N_PARAMS] = {
	{ "rate",
	  'r',
	  JackDriverParamUInt,
	  { .ui = SNDIO_DRIVER_DEF_FS },
	  NULL,
	  "sample rate",
	  "sample rate"
	},
	{ "period",
	  'p',
	  JackDriverParamUInt,
	  { .ui = SNDIO_DRIVER_DEF_BLKSIZE },
	  NULL,
	  "period size",
	  "period size"
	},
	{ "nperiods",
	  'n',
	  JackDriverParamUInt,
	  { .ui = SNDIO_DRIVER_DEF_NPERIODS },
	  NULL,
	  "number of periods in buffer",
	  "number of periods in buffer"
	},
	{ "wordlength",
	  'w',
	  JackDriverParamInt,
	  { .i = SNDIO_DRIVER_DEF_BITS },
	  NULL,
	  "word length",
	  "word length"
	},
	{ "inchannels",
	  'i',
	  JackDriverParamUInt,
	  { .ui = SNDIO_DRIVER_DEF_INS },
	  NULL,
	  "capture channels",
	  "capture channels"
	},
	{ "outchannels",
	  'o',
	  JackDriverParamUInt,
	  { .ui = SNDIO_DRIVER_DEF_OUTS },
	  NULL,
	  "playback channels",
	  "playback channels"
	},
	{ "device",
	  'd',
	  JackDriverParamString,
	  { },
	  NULL,
	  "device",
	  "device"
	},
	{ "ignorehwbuf",
	  'b',
	  JackDriverParamBool,
	  { },
	  NULL,
	  "ignore hardware period size",
	  "ignore hardware period size"
	},
	{ "input latency",
	  'I',
	  JackDriverParamUInt,
	  { .ui = 0 },
	  NULL,
	  "system capture latency",
	  "system capture latency"
	},
	{ "output latency",
	  'O',
	  JackDriverParamUInt,
	  { .ui = 0 },
	  NULL,
	  "system playback latency",
	  "system playback latency"
	}
};


/* internal functions */


static void
set_period_size (sndio_driver_t *driver, jack_nframes_t new_period_size)
{
	driver->period_size = new_period_size;

	driver->period_usecs = 
		((double)driver->period_size /
		(double)driver->sample_rate) * 1e6;
	driver->last_wait_ust = 0;
	driver->poll_timeout = (int)(driver->period_usecs / 666);
}


static void
sndio_driver_write_silence (sndio_driver_t *driver, jack_nframes_t nframes)
{
	size_t localsize, io_res, nbytes, offset;
	void *localbuf;

	localsize = nframes * driver->sample_bytes * driver->playback_channels;
	localbuf = malloc(localsize);
	if (localbuf == NULL)
	{
		jack_error("sndio_driver: malloc() failed: %s@%i",
			__FILE__, __LINE__);
		return;
	}
	bzero(localbuf, localsize);

	offset = 0;
	nbytes = localsize;
	while (nbytes > 0)
	{
		io_res = sio_write(driver->hdl, localbuf + offset, nbytes);
		if (io_res == 0)
		{
			jack_error("sndio_driver: sio_write() failed: "
				"count=%d/%d: %s@%i", io_res, localsize,
				__FILE__, __LINE__);
		}
		offset += io_res;
		nbytes -= io_res;
	}
	free(localbuf);
}


static void
sndio_driver_read_silence (sndio_driver_t *driver, jack_nframes_t nframes)
{
	size_t localsize, io_res, nbytes, offset;
	void *localbuf;

	localsize = nframes * driver->sample_bytes * driver->capture_channels;
	localbuf = malloc(localsize);
	if (localbuf == NULL)
	{
		jack_error("sndio_driver: malloc() failed: %s@%i",
			__FILE__, __LINE__);
		return;
	}

	offset = 0;
	nbytes = localsize;
	while (nbytes > 0) {
		io_res = sio_read(driver->hdl, localbuf + offset, nbytes);
		if (io_res == 0) {
			jack_error("sndio_driver: sio_read() failed: "
				"count=%d/%d: %s@%i", io_res, nbytes,
				__FILE__, __LINE__);
			break;
		}
		offset +=- io_res;
		nbytes -= io_res;
	}
	free(localbuf);
}


static int
sndio_driver_start (sndio_driver_t *driver)
{
	if (!sio_start(driver->hdl))
		jack_error("sio_start failed: %s@%i",
			__FILE__, __LINE__);

	/* prime playback buffers */
	if (driver->playback_channels > 0)
		sndio_driver_write_silence(driver, driver->pprime);

	return 0;
}


static int
sndio_driver_set_parameters (sndio_driver_t *driver)
{
	struct sio_par par;
	unsigned int period_size = 0;
	unsigned int nperiods;
	int mode = 0;

	if (driver->capture_channels > 0)
		mode |= SIO_REC;

	if (driver->playback_channels > 0)
		mode |= SIO_PLAY;

	driver->hdl = sio_open(driver->dev, mode, 0);
	if (driver->hdl == NULL)
	{
		jack_error("sndio_driver: failed to open device "
			"%s: %s@%i", (driver->dev == NULL) ?
			"default" : driver->dev, __FILE__, __LINE__);
		return -1;
	}

	if (driver->bits != 16 && driver->bits != 24 && driver->bits != 32)
	{
		jack_error("sndio_driver: invalid sample bits");
		return -1;
	}

	sio_initpar(&par);
	par.sig = 1;
	par.bits = driver->bits;
	par.pchan = driver->playback_channels;
	par.rchan = driver->capture_channels;
	par.rate = driver->sample_rate;
	par.appbufsz = driver->period_size * driver->nperiods;
	par.round = driver->period_size;
	par.xrun = SIO_SYNC;

	if (!sio_setpar(driver->hdl, &par))
	{
		jack_error("sndio_driver: failed to set parameters: %s@%i",
			__FILE__, __LINE__);
		return -1;
	}

	if (!sio_getpar(driver->hdl, &par))
	{
		jack_error("sndio_driver: sio_getpar() failed: %s@%i",
			__FILE__, __LINE__);
		return -1;
	}

	if (par.sig != 1 || par.bits != driver->bits ||
		par.pchan != driver->playback_channels ||
		par.rchan != driver->capture_channels ||
		par.rate != driver->sample_rate)
	{
		jack_error("sndio_driver: setting parameters failed: %s@%i",
			__FILE__, __LINE__);
		return -1;
	}

	period_size = par.round;
	nperiods = par.appbufsz / par.round;
	driver->sample_bytes = par.bps;
	driver->pprime = par.bufsz;

	if (period_size != 0 && !driver->ignorehwbuf &&
		(period_size != driver->period_size || 
		nperiods != driver->nperiods))
	{
		printf("sndio_driver: buffer update: "
			"period_size=%u, nperiods=%u\n", period_size, nperiods);

		driver->nperiods = nperiods;
		set_period_size(driver, period_size);

		if (driver->engine)
			driver->engine->set_buffer_size(driver->engine, 
				driver->period_size);
	}

	driver->capbufsize = 0;
	driver->capbuf = NULL;
	if (driver->capture_channels != 0)
	{
		driver->capbufsize = driver->period_size * 
			driver->capture_channels * driver->sample_bytes;
		driver->capbuf = malloc(driver->capbufsize);
		if (driver->capbuf == NULL)
		{
			jack_error("sndio_driver: malloc() failed: %s@%i", 
				__FILE__, __LINE__);
			return -1;
		}
		bzero(driver->capbuf, driver->capbufsize);
	}

	driver->playbufsize = 0;
	driver->playbuf = NULL;
	if (driver->playback_channels > 0)
	{
		driver->playbufsize = driver->period_size * 
			driver->playback_channels * driver->sample_bytes;
		driver->playbuf = malloc(driver->playbufsize);
		if (driver->playbuf == NULL)
		{
			jack_error("sndio_driver: malloc() failed: %s@%i", 
				__FILE__, __LINE__);
			return -1;
		}
		bzero(driver->playbuf, driver->playbufsize);
	}

	printf("sndio_driver: capbuf %zd B, playbuf %zd B\n",
		driver->capbufsize, driver->playbufsize);

	return 0;
}


static int
sndio_driver_stop (sndio_driver_t *driver)
{
	if (driver->hdl != NULL)
		sio_stop(driver->hdl);

	return 0;
}


static jack_nframes_t
sndio_driver_wait (sndio_driver_t *driver, int *status, float *iodelay)
{
	struct pollfd pfd;
	nfds_t snfds, nfds;
	jack_time_t poll_ret;
	int need_capture, need_playback;
	int events, revents;

	*status = 0;
	*iodelay = 0;

	need_capture = need_playback = 0;

	if (driver->capture_channels > 0)
		need_capture = 1;

	if (driver->playback_channels > 0)
		need_playback = 1;

	if (jack_get_microseconds() > driver->poll_next)
	{
		/* late. don't count as wakeup delay. */
		driver->poll_next = 0;
	}

	snfds = sio_nfds(driver->hdl);

	while (need_capture || need_playback)
	{
		events = 0;
		if (need_capture)
			events |= POLLIN;

		if (need_playback)
			events |= POLLOUT;

		if (snfds != sio_pollfd(driver->hdl, &pfd, events)) {
			jack_error("sndio_driver: sio_pollfd failed: %s@%i",
				__FILE__, __LINE__);
			*status = -1;
			return 0;
		}
		nfds = poll(&pfd, snfds, 1000);
		if (nfds == -1)
		{
			jack_error("sndio_driver: poll() error: %s: %s@%i",  
				strerror(errno), __FILE__, __LINE__);
			*status = -1;
			return 0;
		}
		else if (nfds == 0)
		{
			jack_error("sndio_driver: poll() time out: %s@%i",  
				__FILE__, __LINE__);
			*status = -1;
			return 0;
		}
		revents = sio_revents(driver->hdl, &pfd);
		if (revents & (POLLERR | POLLHUP | POLLNVAL))
		{
			jack_error("sndio_driver: poll() error: %s@%i",  
				__FILE__, __LINE__);
			*status = -1;
			return 0;
		}

		if (revents & POLLIN)
			need_capture = 0;

		if (revents & POLLOUT)
			need_playback = 0;

		if (sio_eof(driver->hdl))
		{
			jack_error("sndio_driver: sio_eof(): %s@%i",
				__FILE__, __LINE__);
			*status = -1;
			return 0;
		}
	}
	poll_ret = jack_get_microseconds();

	if (driver->poll_next && poll_ret > driver->poll_next)
		*iodelay = poll_ret - driver->poll_next;

	driver->poll_next = poll_ret + driver->period_usecs;
	driver->engine->transport_cycle_start(driver->engine, poll_ret);

	driver->last_wait_ust = poll_ret;

	return driver->period_size;
}


static inline int
sndio_driver_run_cycle (sndio_driver_t *driver)
{
	jack_nframes_t nframes;
	int wait_status;
	float iodelay;

	nframes = sndio_driver_wait(driver, &wait_status, &iodelay);

	if (wait_status < 0)
		return -1;

	return driver->engine->run_cycle(driver->engine, nframes, iodelay);
}


static void
copy_and_convert_in (jack_sample_t *dst, void *src, 
	size_t nframes,	int channel, int chcount, int bits)
{
	int srcidx, dstidx;
	signed short *s16src = (signed short *)src;
	signed int *s32src = (signed int *)src;
	jack_sample_t scale;

	srcidx = channel;
	switch (bits)
	{
		case 16:
			scale = 1.0f / 0x7fff;
			for (dstidx = 0; dstidx < nframes; dstidx++)
			{
				dst[dstidx] = (jack_sample_t) 
					s16src[srcidx] * scale;
				srcidx += chcount;
			}
			break;
		case 24:
		case 32:
			scale = 1.0f / 0x7fffffff;
			for (dstidx = 0; dstidx < nframes; dstidx++)
			{
				dst[dstidx] = (jack_sample_t)
					s32src[srcidx] * scale;
				srcidx += chcount;
			}
			break;
	}
}


static void
copy_and_convert_out (void *dst, jack_sample_t *src, 
	size_t nframes,	int channel, int chcount, int bits)
{
	int srcidx;
	int dstidx;
	signed short *s16dst = (signed short *)dst;
	signed int *s32dst = (signed int *)dst;
	jack_sample_t scale;

	dstidx = channel;
	switch (bits)
	{
		case 16:
			scale = 0x7fff;
			for (srcidx = 0; srcidx < nframes; srcidx++)
			{
				s16dst[dstidx] = (signed short)
					(src[srcidx] >= 0.0f) ?
					(src[srcidx] * scale + 0.5f) :
					(src[srcidx] * scale - 0.5f);
				dstidx += chcount;
			}
			break;
		case 24:
		case 32:
			scale = 0x7fffffff;
			for (srcidx = 0; srcidx < nframes; srcidx++)
			{
				s32dst[dstidx] = (signed int)
					(src[srcidx] >= 0.0f) ?
					(src[srcidx] * scale + 0.5f) :
					(src[srcidx] * scale - 0.5f);
				dstidx += chcount;
			}
			break;
	}
}


/* jack driver interface */

static int
sndio_driver_attach (sndio_driver_t *driver)
{
	int port_flags;
	int channel;
	char channel_name[64];
	jack_port_t *port;
	jack_latency_range_t range;

	driver->engine->set_buffer_size(driver->engine, driver->period_size);
	driver->engine->set_sample_rate(driver->engine, driver->sample_rate);

	port_flags = JackPortIsOutput|JackPortIsPhysical|JackPortIsTerminal;

	for (channel = 0; channel < driver->capture_channels; channel++)
	{
		snprintf(channel_name, sizeof(channel_name),
			"capture_%u", channel + 1);
		port = jack_port_register(driver->client, channel_name,
			JACK_DEFAULT_AUDIO_TYPE, port_flags, 0);
		if (port == NULL)
		{
			jack_error("sndio_driver: cannot register port for %s: "
				"%s@%i", channel_name, __FILE__, __LINE__);
			break;
		}
		range.min = range.max = driver->period_size +
		    driver->sys_cap_latency;
		jack_port_set_latency_range(port, JackCaptureLatency, &range);
		driver->capture_ports =
			jack_slist_append(driver->capture_ports, port);
	}

	port_flags = JackPortIsInput|JackPortIsPhysical|JackPortIsTerminal;
	for (channel = 0; channel < driver->playback_channels; channel++)
	{
		snprintf(channel_name, sizeof(channel_name),
			"playback_%u", channel + 1);
		port = jack_port_register(driver->client, channel_name,
			JACK_DEFAULT_AUDIO_TYPE, port_flags, 0);
		if (port == NULL)
		{
			jack_error("sndio_driver: cannot register port for "
				"%s: %s@%i", channel_name, __FILE__, __LINE__);
			break;
		}
		range.min = range.max = driver->period_size +
		    driver->sys_play_latency;
		jack_port_set_latency_range(port, JackPlaybackLatency, &range);
		driver->playback_ports =
			jack_slist_append(driver->playback_ports, port);
	}

	return jack_activate(driver->client);
}


static int
sndio_driver_detach (sndio_driver_t *driver)
{
	JSList *node;

	if (driver->engine == NULL)
		return 0;

	node = driver->capture_ports;
	while (node != NULL)
	{
		jack_port_unregister(driver->client, 
			((jack_port_t *) node->data));
		node = jack_slist_next(node);
	}
	if (driver->capture_ports != NULL)
	{
		jack_slist_free(driver->capture_ports);
		driver->capture_ports = NULL;
	}

	node = driver->playback_ports;
	while (node != NULL)
	{
		jack_port_unregister(driver->client,
			((jack_port_t *) node->data));
		node = jack_slist_next(node);
	}
	if (driver->playback_ports != NULL)
	{
		jack_slist_free(driver->playback_ports);
		driver->playback_ports = NULL;
	}

	return 0;
}


static int
sndio_driver_read (sndio_driver_t *driver, jack_nframes_t nframes)
{
	jack_nframes_t nbytes, offset;
	int channel;
	size_t io_res;
	jack_sample_t *portbuf;
	JSList *node;
	jack_port_t *port;

	if (driver->engine->freewheeling || driver->capture_channels == 0)
		return 0;

	if (nframes > driver->period_size)
	{
		jack_error("sndio_driver: read failed: nframes > period_size: "
			"(%u/%u): %s@%i", nframes, driver->period_size,
			__FILE__, __LINE__);
		return -1;
	}

	node = driver->capture_ports;
	channel = 0;
	while (node != NULL)
	{
		port = (jack_port_t *)node->data;

		if (jack_port_connected(port))
		{
			portbuf = jack_port_get_buffer(port, nframes);
			copy_and_convert_in(portbuf, driver->capbuf, 
				nframes, channel, 
				driver->capture_channels,
				driver->bits);
		}

		node = jack_slist_next(node);
		channel++;
	}

	io_res = offset = 0;
	nbytes = nframes * driver->capture_channels * driver->sample_bytes;
	while (nbytes > 0)
	{
		io_res = sio_read(driver->hdl, driver->capbuf + offset, nbytes);
		if (io_res == 0)
		{
			jack_error("sndio_driver: sio_read() failed: %s@%i",
				__FILE__, __LINE__);
			break;
		}
		offset += io_res;
		nbytes -= io_res;
	}
	return 0;
}


static int
sndio_driver_write (sndio_driver_t *driver, jack_nframes_t nframes)
{
	jack_nframes_t nbytes;
	int channel;
	size_t io_res, offset;
	jack_sample_t *portbuf;
	JSList *node;
	jack_port_t *port;

	if (driver->engine->freewheeling || driver->playback_channels == 0)
		return 0;

	if (nframes > driver->period_size)
	{
		jack_error("sndio_driver: write failed: nframes > period_size "
			"(%u/%u): %s@%i", nframes, driver->period_size,
			__FILE__, __LINE__);
		return -1;
	}

	node = driver->playback_ports;
	channel = 0;
	while (node != NULL)
	{
		port = (jack_port_t *)node->data;

		if (jack_port_connected(port))
		{
			portbuf = jack_port_get_buffer(port, nframes);
			copy_and_convert_out(driver->playbuf, portbuf, 
				nframes, channel,
				driver->playback_channels,
				driver->bits);
		}

		node = jack_slist_next(node);
		channel++;
	}

	io_res = offset = 0;
	nbytes = nframes * driver->playback_channels * driver->sample_bytes;
	while (nbytes > 0)
	{
		io_res = sio_write(driver->hdl, driver->playbuf + offset, nbytes);
		if (io_res == 0)
		{
			jack_error("sndio_driver: sio_write() failed: %s@%i",
				__FILE__, __LINE__);
			break;
		}
		offset += io_res;
		nbytes -= io_res;
	}
	bzero(driver->playbuf, driver->playbufsize);
	return 0;
}


static int
sndio_driver_null_cycle (sndio_driver_t *driver, jack_nframes_t nframes)
{
	if (nframes > driver->period_size)
	{
		jack_error("sndio_driver: null cycle failed: "
			"nframes > period_size (%u/%u): %s@%i", nframes,
			driver->period_size, __FILE__, __LINE__);
		return -1;
	}

	printf("sndio_driver: running null cycle\n");

	if (driver->playback_channels > 0)
		sndio_driver_write_silence (driver, nframes);

	if (driver->capture_channels > 0)
		sndio_driver_read_silence (driver, nframes);

	return 0;
}


static int
sndio_driver_bufsize (sndio_driver_t *driver, jack_nframes_t nframes)
{
	return sndio_driver_set_parameters(driver);
}


static void
sndio_driver_delete (sndio_driver_t *driver)
{
	if (driver->hdl != NULL)
	{
		sio_close(driver->hdl);
		driver->hdl = NULL;
	}

	if (driver->capbuf != NULL)
	{
		free(driver->capbuf);
		driver->capbuf = NULL;
	}
	if (driver->playbuf != NULL)
	{
		free(driver->playbuf);
		driver->playbuf = NULL;
	}

	if (driver->dev != NULL)
	{
		free(driver->dev);
		driver->dev = NULL;
	}

	jack_driver_nt_finish((jack_driver_nt_t *) driver);

	if (driver != NULL)
	{
		free(driver);
		driver = NULL;
	}
}


void
driver_finish (jack_driver_t *driver)
{
	sndio_driver_delete((sndio_driver_t *)driver);
}


static jack_driver_t *
sndio_driver_new (char *dev, jack_client_t *client,
	jack_nframes_t sample_rate, jack_nframes_t period_size,
	jack_nframes_t nperiods, int bits,
	int capture_channels, int playback_channels,
	jack_nframes_t cap_latency, jack_nframes_t play_latency,
	int ignorehwbuf)
{
	sndio_driver_t *driver;

	driver = (sndio_driver_t *)malloc(sizeof(sndio_driver_t));
	if (driver == NULL)
	{
		jack_error("sndio_driver: malloc() failed: %s: %s@%i",
			strerror(errno), __FILE__, __LINE__);
		return NULL;
	}
	driver->engine = NULL;
	jack_driver_nt_init((jack_driver_nt_t *)driver);

	driver->nt_attach = (JackDriverNTAttachFunction)sndio_driver_attach;
	driver->nt_detach = (JackDriverNTDetachFunction)sndio_driver_detach;
	driver->read = (JackDriverReadFunction)sndio_driver_read;
	driver->write = (JackDriverWriteFunction)sndio_driver_write;
	driver->null_cycle = (JackDriverNullCycleFunction)sndio_driver_null_cycle;
	driver->nt_bufsize = (JackDriverNTBufSizeFunction)sndio_driver_bufsize;
	driver->nt_start = (JackDriverNTStartFunction)sndio_driver_start;
	driver->nt_stop = (JackDriverNTStopFunction)sndio_driver_stop;
	driver->nt_run_cycle = (JackDriverNTRunCycleFunction)sndio_driver_run_cycle;

	if (dev != NULL)
		driver->dev = strdup(dev);
	else
		driver->dev = NULL;

	driver->ignorehwbuf = ignorehwbuf;

	driver->sample_rate = sample_rate;
	driver->period_size = period_size;
	driver->orig_period_size = period_size;
	driver->nperiods = nperiods;
	driver->bits = bits;
	driver->capture_channels = capture_channels;
	driver->playback_channels = playback_channels;
	driver->sys_cap_latency = cap_latency;
	driver->sys_play_latency = play_latency;

	set_period_size(driver, period_size);

	driver->hdl = NULL;
	driver->capbuf = driver->playbuf = NULL;
	driver->capture_ports = driver->playback_ports = NULL;

	driver->poll_next = 0;

	if (sndio_driver_set_parameters(driver) < 0)
	{
		free(driver);
		return NULL;
	}

	driver->client = client;

	return (jack_driver_t *)driver;
}


/* jack driver published interface */


const char driver_client_name[] = "sndio";


jack_driver_desc_t *
driver_get_descriptor ()
{
	jack_driver_desc_t *desc;
	jack_driver_param_desc_t *params;

	desc = (jack_driver_desc_t *)calloc(1, sizeof(jack_driver_desc_t));
	if (desc == NULL)
	{
		jack_error("sndio_driver: calloc() failed: %s: %s@%i",
			strerror(errno), __FILE__, __LINE__);
		return NULL;
	}
	strlcpy(desc->name, driver_client_name, sizeof(desc->name));
	desc->nparams = SNDIO_DRIVER_N_PARAMS;

	params = calloc(desc->nparams, sizeof(jack_driver_param_desc_t));
	if (params == NULL)
	{
		jack_error("sndio_driver: calloc() failed: %s: %s@%i",
			strerror(errno), __FILE__, __LINE__);
		return NULL;
	}
	memcpy(params, sndio_params, 
		desc->nparams * sizeof(jack_driver_param_desc_t));
	desc->params = params;

	return desc;
}


jack_driver_t *
driver_initialize (jack_client_t *client, JSList * params)
{
	int bits = SNDIO_DRIVER_DEF_BITS;
	jack_nframes_t sample_rate = SNDIO_DRIVER_DEF_FS;
	jack_nframes_t period_size = SNDIO_DRIVER_DEF_BLKSIZE;
	jack_nframes_t cap_latency = 0;
	jack_nframes_t play_latency = 0;
	unsigned int nperiods = SNDIO_DRIVER_DEF_NPERIODS;
	unsigned int capture_channels = SNDIO_DRIVER_DEF_INS;
	unsigned int playback_channels = SNDIO_DRIVER_DEF_OUTS;
	const JSList *pnode;
	const jack_driver_param_t *param;
	char *dev = NULL;
	int ignorehwbuf = 0;

	pnode = params;
	while (pnode != NULL)
	{
		param = (const jack_driver_param_t *)pnode->data;

		switch (param->character)
		{
			case 'r':
				sample_rate = param->value.ui;
				break;
			case 'p':
				period_size = param->value.ui;
				break;
			case 'n':
				nperiods = param->value.ui;
				break;
			case 'w':
				bits = param->value.i;
				break;
			case 'i':
				capture_channels = param->value.ui;
				break;
			case 'o':
				playback_channels = param->value.ui;
				break;
			case 'd':
				dev = strdup(param->value.str);
				break;
			case 'b':
				ignorehwbuf = 1;
				break;
			case 'I':
				cap_latency = param->value.ui;
				break;
			case 'O':
				play_latency = param->value.ui;
				break;
		}
		pnode = jack_slist_next(pnode);
	}

	return sndio_driver_new(dev, client, sample_rate, period_size,
		nperiods, bits, capture_channels, playback_channels,
		cap_latency, play_latency, ignorehwbuf);
}
