| /* Hey Emacs, this is -*-C-*-  | 
 |  ****************************************************************************** | 
 |  * linuxaudiodev.c -- Linux audio device for python. | 
 |  *  | 
 |  * Author          : Peter Bosch | 
 |  * Created On      : Thu Mar  2 21:10:33 2000 | 
 |  * Last Modified By: Peter Bosch | 
 |  * Last Modified On: Fri Mar 24 11:27:00 2000 | 
 |  * Status          : Unknown, Use with caution! | 
 |  *  | 
 |  * Unless other notices are present in any part of this file | 
 |  * explicitly claiming copyrights for other people and/or  | 
 |  * organizations, the contents of this file is fully copyright  | 
 |  * (C) 2000 Peter Bosch, all rights reserved. | 
 |  ****************************************************************************** | 
 |  */ | 
 |  | 
 | #include "Python.h" | 
 | #include "structmember.h" | 
 |  | 
 | #ifdef HAVE_UNISTD_H | 
 | #include <unistd.h> | 
 | #endif | 
 |  | 
 | #ifdef HAVE_FCNTL_H | 
 | #include <fcntl.h> | 
 | #endif | 
 |  | 
 | #include <sys/ioctl.h> | 
 | #include <linux/soundcard.h> | 
 |  | 
 | typedef unsigned long uint32_t; | 
 |  | 
 | typedef struct { | 
 |     PyObject_HEAD; | 
 |     int		x_fd;		/* The open file */ | 
 |     int		x_icount;	/* Input count */ | 
 |     int		x_ocount;	/* Output count */ | 
 |     uint32_t	x_afmts;	/* Supported audio formats */ | 
 | } lad_t; | 
 |  | 
 | static struct { | 
 |     int		a_bps; | 
 |     uint32_t	a_fmt; | 
 | } audio_types[] = { | 
 |     {  8, 	AFMT_MU_LAW }, | 
 |     {  8,	AFMT_U8 }, | 
 |     {  8, 	AFMT_S8 }, | 
 |     { 16, 	AFMT_U16_BE }, | 
 |     { 16, 	AFMT_U16_LE }, | 
 |     { 16, 	AFMT_S16_BE }, | 
 |     { 16, 	AFMT_S16_LE }, | 
 | }; | 
 |  | 
 |  | 
 | staticforward PyTypeObject Ladtype; | 
 |  | 
 | static PyObject *LinuxAudioError; | 
 |  | 
 | static lad_t * | 
 | newladobject(PyObject *arg) | 
 | { | 
 |     lad_t *xp; | 
 |     int fd, afmts, imode; | 
 |     char *mode; | 
 |     char *basedev; | 
 |  | 
 |     /* Check arg for r/w/rw */ | 
 |     if (!PyArg_ParseTuple(arg, "s:open", &mode)) return NULL; | 
 |     if (strcmp(mode, "r") == 0) | 
 |         imode = 0; | 
 |     else if (strcmp(mode, "w") == 0) | 
 |         imode = 1; | 
 |     else { | 
 |         PyErr_SetString(LinuxAudioError, "Mode should be one of 'r', or 'w'"); | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     /* Open the correct device.  The base device name comes from the | 
 |      * AUDIODEV environment variable first, then /dev/audio.  The | 
 |      * control device tacks "ctl" onto the base device name. | 
 |      */ | 
 |     basedev = getenv("AUDIODEV"); | 
 |     if (!basedev) | 
 |         basedev = "/dev/dsp"; | 
 |  | 
 |     if ((fd = open(basedev, imode)) < 0) { | 
 |         PyErr_SetFromErrnoWithFilename(LinuxAudioError, basedev); | 
 |         return NULL; | 
 |     } | 
 |     if (imode && ioctl(fd, SNDCTL_DSP_NONBLOCK, NULL) < 0) { | 
 |         PyErr_SetFromErrnoWithFilename(LinuxAudioError, basedev); | 
 |         return NULL; | 
 |     } | 
 |     if (ioctl(fd, SNDCTL_DSP_GETFMTS, &afmts) < 0) { | 
 |         PyErr_SetFromErrnoWithFilename(LinuxAudioError, basedev); | 
 |         return NULL; | 
 |     } | 
 |     /* Create and initialize the object */ | 
 |     if ((xp = PyObject_New(lad_t, &Ladtype)) == NULL) { | 
 |         close(fd); | 
 |         return NULL; | 
 |     } | 
 |     xp->x_fd     = fd; | 
 |     xp->x_icount = xp->x_ocount = 0; | 
 |     xp->x_afmts  = afmts; | 
 |     return xp; | 
 | } | 
 |  | 
 | static void | 
 | lad_dealloc(lad_t *xp) | 
 | { | 
 |     close(xp->x_fd); | 
 |     PyObject_Del(xp); | 
 | } | 
 |  | 
 | static PyObject * | 
 | lad_read(lad_t *self, PyObject *args) | 
 | { | 
 |     int size, count; | 
 |     char *cp; | 
 |     PyObject *rv; | 
 | 	 | 
 |     if (!PyArg_ParseTuple(args, "i:read", &size)) | 
 |         return NULL; | 
 |     rv = PyString_FromStringAndSize(NULL, size); | 
 |     if (rv == NULL) | 
 |         return NULL; | 
 |  | 
 |     if (!(cp = PyString_AsString(rv))) { | 
 |         Py_DECREF(rv); | 
 |         return NULL; | 
 |     } | 
 |     if ((count = read(self->x_fd, cp, size)) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         Py_DECREF(rv); | 
 |         return NULL; | 
 |     } | 
 |     self->x_icount += count; | 
 |     return rv; | 
 | } | 
 |  | 
 | static PyObject * | 
 | lad_write(lad_t *self, PyObject *args) | 
 | { | 
 |     char *cp; | 
 |     int rv, size; | 
 | 	 | 
 |     if (!PyArg_ParseTuple(args, "s#:write", &cp, &size)) return NULL; | 
 |  | 
 |     while (size > 0) { | 
 |         if ((rv = write(self->x_fd, cp, size)) < 0) { | 
 |             PyErr_SetFromErrno(LinuxAudioError); | 
 |             return NULL; | 
 |         } | 
 |         self->x_ocount += rv; | 
 |         size           -= rv; | 
 |         cp             += rv; | 
 |     } | 
 |     Py_INCREF(Py_None); | 
 |     return Py_None; | 
 | } | 
 |  | 
 | static PyObject * | 
 | lad_close(lad_t *self, PyObject *args) | 
 | { | 
 |     if (!PyArg_ParseTuple(args, ":close")) return NULL; | 
 |     if (self->x_fd >= 0) { | 
 |         close(self->x_fd); | 
 |         self->x_fd = -1; | 
 |     } | 
 |     Py_INCREF(Py_None); | 
 |     return Py_None; | 
 | } | 
 |  | 
 | static PyObject * | 
 | lad_fileno(lad_t *self, PyObject *args) | 
 | { | 
 |     if (!PyArg_ParseTuple(args, ":fileno")) return NULL; | 
 |     return PyInt_FromLong(self->x_fd); | 
 | } | 
 |  | 
 | static PyObject * | 
 | lad_setparameters(lad_t *self, PyObject *args) | 
 | { | 
 |     int rate, ssize, nchannels, stereo, n, fmt; | 
 |  | 
 |     if (!PyArg_ParseTuple(args, "iiii:setparameters", | 
 |                           &rate, &ssize, &nchannels, &fmt)) | 
 |         return NULL; | 
 |    | 
 |     if (rate < 0 || ssize < 0 || (nchannels != 1 && nchannels != 2)) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     if (ioctl(self->x_fd, SOUND_PCM_WRITE_RATE, &rate) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     if (ioctl(self->x_fd, SNDCTL_DSP_SAMPLESIZE, &ssize) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     stereo = (nchannels == 1)? 0: (nchannels == 2)? 1: -1; | 
 |     if (ioctl(self->x_fd, SNDCTL_DSP_STEREO, &stereo) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     for (n = 0; n != sizeof(audio_types) / sizeof(audio_types[0]); n++) | 
 |         if (fmt == audio_types[n].a_fmt) | 
 |             break; | 
 |  | 
 |     if (n == sizeof(audio_types) / sizeof(audio_types[0]) || | 
 |         audio_types[n].a_bps != ssize || | 
 |         (self->x_afmts & audio_types[n].a_fmt) == 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     if (ioctl(self->x_fd, SNDCTL_DSP_SETFMT, &audio_types[n].a_fmt) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     Py_INCREF(Py_None); | 
 |     return Py_None; | 
 | } | 
 |  | 
 | static int | 
 | _ssize(lad_t *self, int *nchannels, int *ssize) | 
 | { | 
 |     int fmt; | 
 |  | 
 |     fmt = 0; | 
 |     if (ioctl(self->x_fd, SNDCTL_DSP_SETFMT, &fmt) < 0)  | 
 |         return -errno; | 
 |  | 
 |     switch (fmt) { | 
 |     case AFMT_MU_LAW: | 
 |     case AFMT_A_LAW: | 
 |     case AFMT_U8: | 
 |     case AFMT_S8: | 
 |         *ssize = sizeof(char); | 
 |         break; | 
 |     case AFMT_S16_LE: | 
 |     case AFMT_S16_BE: | 
 |     case AFMT_U16_LE: | 
 |     case AFMT_U16_BE: | 
 |         *ssize = sizeof(short); | 
 |         break; | 
 |     case AFMT_MPEG: | 
 |     case AFMT_IMA_ADPCM: | 
 |     default: | 
 |         return -EOPNOTSUPP; | 
 |     } | 
 |     *nchannels = 0; | 
 |     if (ioctl(self->x_fd, SNDCTL_DSP_CHANNELS, nchannels) < 0) | 
 |         return -errno; | 
 |     return 0; | 
 | } | 
 |  | 
 |  | 
 | /* bufsize returns the size of the hardware audio buffer in number  | 
 |    of samples */ | 
 | static PyObject * | 
 | lad_bufsize(lad_t *self, PyObject *args) | 
 | { | 
 |     audio_buf_info ai; | 
 |     int nchannels, ssize; | 
 |  | 
 |     if (!PyArg_ParseTuple(args, ":bufsize")) return NULL; | 
 |  | 
 |     if (_ssize(self, &nchannels, &ssize) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     if (ioctl(self->x_fd, SNDCTL_DSP_GETOSPACE, &ai) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     return PyInt_FromLong((ai.fragstotal * ai.fragsize) / (nchannels * ssize)); | 
 | } | 
 |  | 
 | /* obufcount returns the number of samples that are available in the  | 
 |    hardware for playing */ | 
 | static PyObject * | 
 | lad_obufcount(lad_t *self, PyObject *args) | 
 | { | 
 |     audio_buf_info ai; | 
 |     int nchannels, ssize; | 
 |  | 
 |     if (!PyArg_ParseTuple(args, ":obufcount")) | 
 |         return NULL; | 
 |  | 
 |     if (_ssize(self, &nchannels, &ssize) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     if (ioctl(self->x_fd, SNDCTL_DSP_GETOSPACE, &ai) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     return PyInt_FromLong((ai.fragstotal * ai.fragsize - ai.bytes) /  | 
 |                           (ssize * nchannels)); | 
 | } | 
 |  | 
 | /* obufcount returns the number of samples that can be played without | 
 |    blocking */ | 
 | static PyObject * | 
 | lad_obuffree(lad_t *self, PyObject *args) | 
 | { | 
 |     audio_buf_info ai; | 
 |     int nchannels, ssize; | 
 |  | 
 |     if (!PyArg_ParseTuple(args, ":obuffree")) | 
 |         return NULL; | 
 |  | 
 |     if (_ssize(self, &nchannels, &ssize) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     if (ioctl(self->x_fd, SNDCTL_DSP_GETOSPACE, &ai) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     return PyInt_FromLong(ai.bytes / (ssize * nchannels)); | 
 | } | 
 |  | 
 | /* Flush the device */ | 
 | static PyObject * | 
 | lad_flush(lad_t *self, PyObject *args) | 
 | { | 
 |     if (!PyArg_ParseTuple(args, ":flush")) return NULL; | 
 |  | 
 |     if (ioctl(self->x_fd, SNDCTL_DSP_SYNC, NULL) < 0) { | 
 |         PyErr_SetFromErrno(LinuxAudioError); | 
 |         return NULL; | 
 |     } | 
 |     Py_INCREF(Py_None); | 
 |     return Py_None; | 
 | } | 
 |  | 
 | static PyMethodDef lad_methods[] = { | 
 |     { "read",		(PyCFunction)lad_read, METH_VARARGS }, | 
 |     { "write",		(PyCFunction)lad_write, METH_VARARGS }, | 
 |     { "setparameters",	(PyCFunction)lad_setparameters, METH_VARARGS }, | 
 |     { "bufsize",	(PyCFunction)lad_bufsize, METH_VARARGS }, | 
 |     { "obufcount",	(PyCFunction)lad_obufcount, METH_VARARGS }, | 
 |     { "obuffree",	(PyCFunction)lad_obuffree, METH_VARARGS }, | 
 |     { "flush",		(PyCFunction)lad_flush, METH_VARARGS }, | 
 |     { "close",		(PyCFunction)lad_close, METH_VARARGS }, | 
 |     { "fileno",     	(PyCFunction)lad_fileno, METH_VARARGS }, | 
 |     { NULL,		NULL}		/* sentinel */ | 
 | }; | 
 |  | 
 | static PyObject * | 
 | lad_getattr(lad_t *xp, char *name) | 
 | { | 
 |     return Py_FindMethod(lad_methods, (PyObject *)xp, name); | 
 | } | 
 |  | 
 | static PyTypeObject Ladtype = { | 
 |     PyObject_HEAD_INIT(&PyType_Type) | 
 |     0,				/*ob_size*/ | 
 |     "linux_audio_device",	/*tp_name*/ | 
 |     sizeof(lad_t),		/*tp_size*/ | 
 |     0,				/*tp_itemsize*/ | 
 |     /* methods */ | 
 |     (destructor)lad_dealloc,	/*tp_dealloc*/ | 
 |     0,				/*tp_print*/ | 
 |     (getattrfunc)lad_getattr,	/*tp_getattr*/ | 
 |     0,				/*tp_setattr*/ | 
 |     0,				/*tp_compare*/ | 
 |     0,				/*tp_repr*/ | 
 | }; | 
 |  | 
 | static PyObject * | 
 | ladopen(PyObject *self, PyObject *args) | 
 | { | 
 |     return (PyObject *)newladobject(args); | 
 | } | 
 |  | 
 | static PyMethodDef linuxaudiodev_methods[] = { | 
 |     { "open", ladopen, METH_VARARGS }, | 
 |     { 0, 0 }, | 
 | }; | 
 |  | 
 | void | 
 | initlinuxaudiodev(void) | 
 | { | 
 |     PyObject *m, *d, *x; | 
 |    | 
 |     m = Py_InitModule("linuxaudiodev", linuxaudiodev_methods); | 
 |     d = PyModule_GetDict(m); | 
 |  | 
 |     LinuxAudioError = PyErr_NewException("linuxaudiodev.error", NULL, NULL); | 
 |     if (LinuxAudioError) | 
 |         PyDict_SetItemString(d, "error", LinuxAudioError); | 
 |  | 
 |     x = PyInt_FromLong((long) AFMT_MU_LAW); | 
 |     if (x == NULL || PyDict_SetItemString(d, "AFMT_MU_LAW", x) < 0) | 
 |         goto error; | 
 |     Py_DECREF(x); | 
 |  | 
 |     x = PyInt_FromLong((long) AFMT_U8); | 
 |     if (x == NULL || PyDict_SetItemString(d, "AFMT_U8", x) < 0) | 
 |         goto error; | 
 |     Py_DECREF(x); | 
 |  | 
 |     x = PyInt_FromLong((long) AFMT_S8); | 
 |     if (x == NULL || PyDict_SetItemString(d, "AFMT_S8", x) < 0) | 
 |         goto error; | 
 |     Py_DECREF(x); | 
 |  | 
 |     x = PyInt_FromLong((long) AFMT_U16_BE); | 
 |     if (x == NULL || PyDict_SetItemString(d, "AFMT_U16_BE", x) < 0) | 
 |         goto error; | 
 |     Py_DECREF(x); | 
 |  | 
 |     x = PyInt_FromLong((long) AFMT_U16_LE); | 
 |     if (x == NULL || PyDict_SetItemString(d, "AFMT_U16_LE", x) < 0) | 
 |         goto error; | 
 |     Py_DECREF(x); | 
 |  | 
 |     x = PyInt_FromLong((long) AFMT_S16_BE); | 
 |     if (x == NULL || PyDict_SetItemString(d, "AFMT_S16_BE", x) < 0) | 
 |         goto error; | 
 |     Py_DECREF(x); | 
 |  | 
 |     x = PyInt_FromLong((long) AFMT_S16_LE); | 
 |     if (x == NULL || PyDict_SetItemString(d, "AFMT_S16_LE", x) < 0) | 
 |         goto error; | 
 |     Py_DECREF(x); | 
 |  | 
 |     /* Check for errors */ | 
 |     if (PyErr_Occurred()) { | 
 |     error: | 
 |         Py_FatalError("can't initialize module linuxaudiodev"); | 
 |     } | 
 | } |