diff --git a/CHANGES b/CHANGES index bd3f954..d528334 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +Version 0.4: +- added mixer.getenum() +- small documentation improvements + Version 0.3: - wrapped blocking calls with Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS - added pause diff --git a/README b/README index 179cb79..cb8dceb 100644 --- a/README +++ b/README @@ -1,7 +1,7 @@ PyAlsaAudio =========== -Author: Casper Wilstrup (cwi@unispeed.dk) +Author: Casper Wilstrup (cwi@aves.dk) This package contains wrappers for accessing the ALSA api from Python. It is currently fairly complete for PCM devices. My next goal is to have @@ -52,4 +52,4 @@ stdin 'recordtest.py' which captures sound from the microphone at writes it raw to stdout. -'mixertest.py' which can be used to manipulate the mixers +'mixertest.py' which can be used to manipulate the mixers. diff --git a/alsaaudio.c b/alsaaudio.c index 0313332..7eddf47 100644 --- a/alsaaudio.c +++ b/alsaaudio.c @@ -191,7 +191,7 @@ alsapcm_dumpinfo(alsapcm_t *self, PyObject *args) { snd_pcm_hw_params_current(self->handle,hwparams); - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":dumpinfo")) return NULL; printf("PCM handle name = '%s'\n", snd_pcm_name(self->handle)); printf("PCM state = %s\n", snd_pcm_state_name(snd_pcm_state(self->handle))); @@ -276,7 +276,7 @@ alsapcm_dumpinfo(alsapcm_t *self, PyObject *args) { static PyObject * alsapcm_pcmtype(alsapcm_t *self, PyObject *args) { - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":pcmtype")) return NULL; return PyInt_FromLong(self->pcmtype); } @@ -288,7 +288,7 @@ Returns either PCM_CAPTURE or PCM_PLAYBACK."); static PyObject * alsapcm_pcmmode(alsapcm_t *self, PyObject *args) { - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,"pcmmode")) return NULL; return PyInt_FromLong(self->pcmmode); } @@ -303,7 +303,7 @@ Returns the mode of the PCM object. One of:\n\ static PyObject * alsapcm_cardname(alsapcm_t *self, PyObject *args) { - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":cardname")) return NULL; return PyString_FromString(self->cardname); } @@ -317,7 +317,7 @@ static PyObject * alsapcm_setchannels(alsapcm_t *self, PyObject *args) { int channels; int res; - if (!PyArg_ParseTuple(args,"i",&channels)) return NULL; + if (!PyArg_ParseTuple(args,"i:setchannels",&channels)) return NULL; self->channels = channels; res = alsapcm_setup(self); if (res < 0) { @@ -340,7 +340,7 @@ static PyObject * alsapcm_setrate(alsapcm_t *self, PyObject *args) { int rate; int res; - if (!PyArg_ParseTuple(args,"i",&rate)) return NULL; + if (!PyArg_ParseTuple(args,"i:setrate",&rate)) return NULL; self->rate = rate; res = alsapcm_setup(self); if (res < 0) { @@ -361,7 +361,7 @@ static PyObject * alsapcm_setformat(alsapcm_t *self, PyObject *args) { int format; int res; - if (!PyArg_ParseTuple(args,"i",&format)) return NULL; + if (!PyArg_ParseTuple(args,"i:setformat",&format)) return NULL; self->format = format; res = alsapcm_setup(self); if (res < 0) { @@ -379,7 +379,7 @@ static PyObject * alsapcm_setperiodsize(alsapcm_t *self, PyObject *args) { int periodsize; int res; - if (!PyArg_ParseTuple(args,"i",&periodsize)) return NULL; + if (!PyArg_ParseTuple(args,"i:setperiodsize",&periodsize)) return NULL; self->periodsize = periodsize; res = alsapcm_setup(self); if (res < 0) { @@ -408,7 +408,7 @@ alsapcm_read(alsapcm_t *self, PyObject *args) { return NULL; } - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":read")) return NULL; if (self->pcmtype != SND_PCM_STREAM_CAPTURE) { PyErr_SetString(ALSAAudioError,"Cannot read from playback PCM"); return NULL; @@ -453,7 +453,7 @@ static PyObject *alsapcm_write(alsapcm_t *self, PyObject *args) { char *data; int datalen; int res; - if (!PyArg_ParseTuple(args,"s#",&data,&datalen)) return NULL; + if (!PyArg_ParseTuple(args,"s#:write",&data,&datalen)) return NULL; if (datalen%self->framesize) { PyErr_SetString(ALSAAudioError, "Data size must be a multiple of framesize"); @@ -500,7 +500,7 @@ written at a later time."); static PyObject *alsapcm_pause(alsapcm_t *self, PyObject *args) { int enabled=1, res; - if (!PyArg_ParseTuple(args,"|i",&enabled)) return NULL; + if (!PyArg_ParseTuple(args,"|i:pause",&enabled)) return NULL; Py_BEGIN_ALLOW_THREADS res = snd_pcm_pause(self->handle, enabled); @@ -632,7 +632,7 @@ alsamixer_list(PyObject *self, PyObject *args) { char *cardname = "default"; PyObject *result = PyList_New(0); - if (!PyArg_ParseTuple(args,"|s",&cardname)) return NULL; + if (!PyArg_ParseTuple(args,"|s:mixers",&cardname)) return NULL; snd_mixer_selem_id_alloca(&sid); err = alsamixer_gethandle(cardname,&handle); @@ -751,8 +751,9 @@ alsamixer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { if (snd_mixer_selem_is_playback_mono(elem)) self->pchannels = 1; else { for (channel=0; channel <= SND_MIXER_SCHN_LAST; channel++) { - if (snd_mixer_selem_has_playback_channel(elem, channel)) self->pchannels++; - else break; + if (snd_mixer_selem_has_playback_channel(elem, channel)) + self->pchannels++; + else break; } } } @@ -762,8 +763,9 @@ alsamixer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { if (snd_mixer_selem_is_capture_mono(elem)) self->cchannels = 1; else { for (channel=0; channel <= SND_MIXER_SCHN_LAST; channel++) { - if (snd_mixer_selem_has_capture_channel(elem, channel)) self->cchannels++; - else break; + if (snd_mixer_selem_has_capture_channel(elem, channel)) + self->cchannels++; + else break; } } } @@ -784,7 +786,7 @@ static void alsamixer_dealloc(alsamixer_t *self) { static PyObject * alsamixer_cardname(alsamixer_t *self, PyObject *args) { - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":cardname")) return NULL; return PyString_FromString(self->cardname); } @@ -796,7 +798,7 @@ Returns the name of the sound card used by this Mixer object."); static PyObject * alsamixer_mixer(alsamixer_t *self, PyObject *args) { - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":mixer")) return NULL; return PyString_FromString(self->controlname); } @@ -809,7 +811,7 @@ for example 'Master' or 'PCM'"); static PyObject * alsamixer_mixerid(alsamixer_t *self, PyObject *args) { - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":mixerid")) return NULL; return PyInt_FromLong(self->controlid); } @@ -822,7 +824,7 @@ Returns the ID of the ALSA mixer controlled by this object."); static PyObject * alsamixer_volumecap(alsamixer_t *self, PyObject *args) { PyObject *result; - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":volumecap")) return NULL; result = PyList_New(0); if (self->volume_cap&MIXER_CAP_VOLUME) PyList_Append(result,PyString_FromString("Volume")); @@ -856,7 +858,7 @@ Possible values in this list are:\n\ static PyObject * alsamixer_switchcap(alsamixer_t *self, PyObject *args) { PyObject *result; - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":switchcap")) return NULL; result = PyList_New(0); if (self->volume_cap&MIXER_CAP_SWITCH) PyList_Append(result,PyString_FromString("Mute")); @@ -920,7 +922,7 @@ alsamixer_getvolume(alsamixer_t *self, PyObject *args) { char *dirstr = 0; PyObject *result; - if (!PyArg_ParseTuple(args,"|s",&dirstr)) return NULL; + if (!PyArg_ParseTuple(args,"|s:getvolume",&dirstr)) return NULL; elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid); @@ -948,8 +950,8 @@ alsamixer_getvolume(alsamixer_t *self, PyObject *args) { && snd_mixer_selem_has_capture_volume(elem)) { snd_mixer_selem_get_capture_volume(elem, channel, &ival); PyList_Append( - result,PyInt_FromLong(alsamixer_getpercentage(self->cmin, - self->cmax, ival))); + result, PyInt_FromLong(alsamixer_getpercentage(self->cmin, + self->cmax, ival))); } } return result; @@ -972,9 +974,8 @@ alsamixer_getrange(alsamixer_t *self, PyObject *args) { snd_mixer_elem_t *elem; int direction; char *dirstr = 0; - PyObject *result; - if (!PyArg_ParseTuple(args,"|s",&dirstr)) return NULL; + if (!PyArg_ParseTuple(args,"|s:getrange",&dirstr)) return NULL; elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid); @@ -985,20 +986,31 @@ alsamixer_getrange(alsamixer_t *self, PyObject *args) { else if (strcasecmp(dirstr,"playback")==0) direction = 0; else if (strcasecmp(dirstr,"capture")==0) direction = 1; else { - PyErr_SetString(ALSAAudioError,"Invalid direction argument for direction"); + PyErr_SetString(ALSAAudioError,"Invalid argument for direction"); return NULL; } - result = PyList_New(0); - if (direction == 0 && snd_mixer_selem_has_playback_channel(elem, 0)) { - PyList_Append(result,PyInt_FromLong(self->pmin)); - PyList_Append(result,PyInt_FromLong(self->pmax)); + if (direction == 0) { + if (snd_mixer_selem_has_playback_channel(elem, 0)) { + return Py_BuildValue("[ii]", self->pmin, self->pmax); } - else if (direction == 1 && snd_mixer_selem_has_capture_channel(elem, 0) - && snd_mixer_selem_has_capture_volume(elem)) { - PyList_Append(result,PyInt_FromLong(self->cmin)); - PyList_Append(result,PyInt_FromLong(self->cmax)); + + PyErr_SetString(ALSAAudioError, "Mixer has no playback channel"); + return NULL; + } + else if (direction == 1) { + if (snd_mixer_selem_has_capture_channel(elem, 0) + && snd_mixer_selem_has_capture_volume(elem)) { + return Py_BuildValue("[ii]", self->cmin, self->cmax); } - return result; + + PyErr_SetString(ALSAAudioError, "Mixer has no capture channel " + "or capture volume"); + return NULL; + } + + // Unreached statement + PyErr_SetString(ALSAAudioError,"Huh?"); + return NULL; } PyDoc_STRVAR(getrange_doc, @@ -1015,40 +1027,73 @@ if the mixer has this capability, otherwise 'capture'"); static PyObject * alsamixer_getenum(alsamixer_t *self, PyObject *args) { snd_mixer_elem_t *elem; + PyObject *elems; + int i, count, rc; unsigned int index; char name[32]; - int res; PyObject *result; - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args, ":getenum")) return NULL; elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid); if (!snd_mixer_selem_is_enumerated(elem)) { - PyErr_SetString(ALSAAudioError,"Mixer is no enumerated control"); - return NULL; - } + // Not an enumerated control, return an empty tuple + return PyTuple_New(0); + } - res=snd_mixer_selem_get_enum_item(elem, 0, &index); - if(res) { - PyErr_SetString(ALSAAudioError, snd_strerror(res)); + count = snd_mixer_selem_get_enum_items(elem); + if (count < 0) { + PyErr_SetString(ALSAAudioError, snd_strerror(count)); return NULL; } - res=snd_mixer_selem_get_enum_item_name(elem, index, sizeof(name)-1, name); - if(res) { - PyErr_SetString(ALSAAudioError, snd_strerror(res)); + + result = PyTuple_New(2); + if (!result) + return NULL; + + rc = snd_mixer_selem_get_enum_item(elem, 0, &index); + if(rc) { + PyErr_SetString(ALSAAudioError, snd_strerror(rc)); return NULL; - } else { - result = PyList_New(0); - PyList_Append(result,PyString_FromString(name)); } + rc = snd_mixer_selem_get_enum_item_name(elem, index, sizeof(name)-1, name); + if (rc) { + Py_DECREF(result); + PyErr_SetString(ALSAAudioError, snd_strerror(rc)); + return NULL; + } + + PyTuple_SetItem(result, 0, PyString_FromString(name)); + + elems = PyList_New(count); + if (!elems) + { + Py_DECREF(result); + return NULL; + } + + for (i = 0; i < count; ++i) { + rc = snd_mixer_selem_get_enum_item_name(elem, i, sizeof(name)-1, name); + if (rc) { + Py_DECREF(elems); + Py_DECREF(result); + PyErr_SetString(ALSAAudioError, snd_strerror(rc)); + return NULL; + } + + PyList_SetItem(elems, i, PyString_FromString(name)); + } + + PyTuple_SetItem(result, 1, elems); + return result; } PyDoc_STRVAR(getenum_doc, -"getenum([direction]) -> List of enumerated controls (string)\n\ +"getenum() -> Tuple of (string, list of strings)\n\ \n\ -Returns a list of strings with the enumerated controls."); - +Returns a a tuple. The first element is name of the active enumerated item, \n\ +the second a list available enumerated items."); static PyObject * alsamixer_getmute(alsamixer_t *self, PyObject *args) { @@ -1056,7 +1101,7 @@ alsamixer_getmute(alsamixer_t *self, PyObject *args) { int i; int ival; PyObject *result; - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":getmute")) return NULL; elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid); if (!snd_mixer_selem_has_playback_switch(elem)) { @@ -1088,7 +1133,7 @@ alsamixer_getrec(alsamixer_t *self, PyObject *args) { int i; int ival; PyObject *result; - if (!PyArg_ParseTuple(args,"")) return NULL; + if (!PyArg_ParseTuple(args,":getrec")) return NULL; elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid); if (!snd_mixer_selem_has_capture_switch(elem)) { @@ -1124,7 +1169,9 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args) { int channel = MIXER_CHANNEL_ALL; int done = 0; - if (!PyArg_ParseTuple(args,"l|is",&volume,&channel,&dirstr)) return NULL; + if (!PyArg_ParseTuple(args,"l|is:setvolume",&volume,&channel,&dirstr)) + return NULL; + if (volume < 0 || volume > 100) { PyErr_SetString(ALSAAudioError,"Volume must be between 0 and 100"); return NULL; @@ -1146,16 +1193,16 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args) { for (i = 0; i <= SND_MIXER_SCHN_LAST; i++) { if (channel == -1 || channel == i) { if (direction == 0 && snd_mixer_selem_has_playback_channel(elem, i)) { - physvolume = alsamixer_getphysvolume(self->pmin,self->pmax,volume); - snd_mixer_selem_set_playback_volume(elem, i, physvolume); - done++; + physvolume = alsamixer_getphysvolume(self->pmin,self->pmax,volume); + snd_mixer_selem_set_playback_volume(elem, i, physvolume); + done++; } else if (direction == 1 && snd_mixer_selem_has_capture_channel(elem, channel) && snd_mixer_selem_has_capture_volume(elem)) { - physvolume = alsamixer_getphysvolume(self->cmin,self->cmax,volume); - snd_mixer_selem_set_capture_volume(elem, i, physvolume); - done++; + physvolume = alsamixer_getphysvolume(self->cmin,self->cmax,volume); + snd_mixer_selem_set_capture_volume(elem, i, physvolume); + done++; } } } @@ -1190,7 +1237,7 @@ alsamixer_setmute(alsamixer_t *self, PyObject *args) { int mute = 0; int done = 0; int channel = MIXER_CHANNEL_ALL; - if (!PyArg_ParseTuple(args,"i|i",&mute,&channel)) return NULL; + if (!PyArg_ParseTuple(args,"i|i:setmute",&mute,&channel)) return NULL; elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid); if (!snd_mixer_selem_has_playback_switch(elem)) { @@ -1200,8 +1247,8 @@ alsamixer_setmute(alsamixer_t *self, PyObject *args) { for (i = 0; i <= SND_MIXER_SCHN_LAST; i++) { if (channel == MIXER_CHANNEL_ALL || channel == i) { if (snd_mixer_selem_has_playback_channel(elem, i)) { - snd_mixer_selem_set_playback_switch(elem, i, !mute); - done++; + snd_mixer_selem_set_playback_switch(elem, i, !mute); + done++; } } } @@ -1231,7 +1278,7 @@ alsamixer_setrec(alsamixer_t *self, PyObject *args) { int rec = 0; int done = 0; int channel = MIXER_CHANNEL_ALL; - if (!PyArg_ParseTuple(args,"i|i",&rec,&channel)) return NULL; + if (!PyArg_ParseTuple(args,"i|i:setrec",&rec,&channel)) return NULL; elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid); if (!snd_mixer_selem_has_capture_switch(elem)) { @@ -1369,39 +1416,52 @@ void initalsaaudio(void) { } - _EXPORT_INT(m,"PCM_PLAYBACK",SND_PCM_STREAM_PLAYBACK); - _EXPORT_INT(m,"PCM_CAPTURE",SND_PCM_STREAM_CAPTURE); + _EXPORT_INT(m, "PCM_PLAYBACK",SND_PCM_STREAM_PLAYBACK); + _EXPORT_INT(m, "PCM_CAPTURE",SND_PCM_STREAM_CAPTURE); - _EXPORT_INT(m,"PCM_NORMAL",0); - _EXPORT_INT(m,"PCM_NONBLOCK",SND_PCM_NONBLOCK); - _EXPORT_INT(m,"PCM_ASYNC",SND_PCM_ASYNC); + _EXPORT_INT(m, "PCM_NORMAL",0); + _EXPORT_INT(m, "PCM_NONBLOCK",SND_PCM_NONBLOCK); + _EXPORT_INT(m, "PCM_ASYNC",SND_PCM_ASYNC); /* PCM Formats */ - _EXPORT_INT(m,"PCM_FORMAT_S8",SND_PCM_FORMAT_S8); - _EXPORT_INT(m,"PCM_FORMAT_U8",SND_PCM_FORMAT_U8); - _EXPORT_INT(m,"PCM_FORMAT_S16_LE",SND_PCM_FORMAT_S16_LE); - _EXPORT_INT(m,"PCM_FORMAT_S16_BE",SND_PCM_FORMAT_S16_BE); - _EXPORT_INT(m,"PCM_FORMAT_U16_LE",SND_PCM_FORMAT_U16_LE); - _EXPORT_INT(m,"PCM_FORMAT_U16_BE",SND_PCM_FORMAT_U16_BE); - _EXPORT_INT(m,"PCM_FORMAT_S24_LE",SND_PCM_FORMAT_S24_LE); - _EXPORT_INT(m,"PCM_FORMAT_S24_BE",SND_PCM_FORMAT_S24_BE); - _EXPORT_INT(m,"PCM_FORMAT_U24_LE",SND_PCM_FORMAT_U24_LE); - _EXPORT_INT(m,"PCM_FORMAT_U24_BE",SND_PCM_FORMAT_U24_BE); - _EXPORT_INT(m,"PCM_FORMAT_S32_LE",SND_PCM_FORMAT_S32_LE); - _EXPORT_INT(m,"PCM_FORMAT_S32_BE",SND_PCM_FORMAT_S32_BE); - _EXPORT_INT(m,"PCM_FORMAT_U32_LE",SND_PCM_FORMAT_U32_LE); - _EXPORT_INT(m,"PCM_FORMAT_U32_BE",SND_PCM_FORMAT_U32_BE); - _EXPORT_INT(m,"PCM_FORMAT_FLOAT_LE",SND_PCM_FORMAT_FLOAT_LE); - _EXPORT_INT(m,"PCM_FORMAT_FLOAT_BE",SND_PCM_FORMAT_FLOAT_BE); - _EXPORT_INT(m,"PCM_FORMAT_FLOAT64_LE",SND_PCM_FORMAT_FLOAT64_LE); - _EXPORT_INT(m,"PCM_FORMAT_FLOAT64_BE",SND_PCM_FORMAT_FLOAT64_BE); - _EXPORT_INT(m,"PCM_FORMAT_MU_LAW",SND_PCM_FORMAT_MU_LAW); - _EXPORT_INT(m,"PCM_FORMAT_A_LAW",SND_PCM_FORMAT_A_LAW); - _EXPORT_INT(m,"PCM_FORMAT_IMA_ADPCM",SND_PCM_FORMAT_IMA_ADPCM); - _EXPORT_INT(m,"PCM_FORMAT_MPEG",SND_PCM_FORMAT_MPEG); - _EXPORT_INT(m,"PCM_FORMAT_GSM",SND_PCM_FORMAT_GSM); + _EXPORT_INT(m, "PCM_FORMAT_S8",SND_PCM_FORMAT_S8); + _EXPORT_INT(m, "PCM_FORMAT_U8",SND_PCM_FORMAT_U8); + _EXPORT_INT(m, "PCM_FORMAT_S16_LE",SND_PCM_FORMAT_S16_LE); + _EXPORT_INT(m, "PCM_FORMAT_S16_BE",SND_PCM_FORMAT_S16_BE); + _EXPORT_INT(m, "PCM_FORMAT_U16_LE",SND_PCM_FORMAT_U16_LE); + _EXPORT_INT(m, "PCM_FORMAT_U16_BE",SND_PCM_FORMAT_U16_BE); + _EXPORT_INT(m, "PCM_FORMAT_S24_LE",SND_PCM_FORMAT_S24_LE); + _EXPORT_INT(m, "PCM_FORMAT_S24_BE",SND_PCM_FORMAT_S24_BE); + _EXPORT_INT(m, "PCM_FORMAT_U24_LE",SND_PCM_FORMAT_U24_LE); + _EXPORT_INT(m, "PCM_FORMAT_U24_BE",SND_PCM_FORMAT_U24_BE); + _EXPORT_INT(m, "PCM_FORMAT_S32_LE",SND_PCM_FORMAT_S32_LE); + _EXPORT_INT(m, "PCM_FORMAT_S32_BE",SND_PCM_FORMAT_S32_BE); + _EXPORT_INT(m, "PCM_FORMAT_U32_LE",SND_PCM_FORMAT_U32_LE); + _EXPORT_INT(m, "PCM_FORMAT_U32_BE",SND_PCM_FORMAT_U32_BE); + _EXPORT_INT(m, "PCM_FORMAT_FLOAT_LE",SND_PCM_FORMAT_FLOAT_LE); + _EXPORT_INT(m, "PCM_FORMAT_FLOAT_BE",SND_PCM_FORMAT_FLOAT_BE); + _EXPORT_INT(m, "PCM_FORMAT_FLOAT64_LE",SND_PCM_FORMAT_FLOAT64_LE); + _EXPORT_INT(m, "PCM_FORMAT_FLOAT64_BE",SND_PCM_FORMAT_FLOAT64_BE); + _EXPORT_INT(m, "PCM_FORMAT_MU_LAW",SND_PCM_FORMAT_MU_LAW); + _EXPORT_INT(m, "PCM_FORMAT_A_LAW",SND_PCM_FORMAT_A_LAW); + _EXPORT_INT(m, "PCM_FORMAT_IMA_ADPCM",SND_PCM_FORMAT_IMA_ADPCM); + _EXPORT_INT(m, "PCM_FORMAT_MPEG",SND_PCM_FORMAT_MPEG); + _EXPORT_INT(m, "PCM_FORMAT_GSM",SND_PCM_FORMAT_GSM); /* Mixer stuff */ - _EXPORT_INT(m,"MIXER_CHANNEL_ALL",MIXER_CHANNEL_ALL); + _EXPORT_INT(m, "MIXER_CHANNEL_ALL", MIXER_CHANNEL_ALL); +#if 0 // Omit for now - use case unknown + _EXPORT_INT(m, "MIXER_SCHN_UNKNOWN", SND_MIXER_SCHN_UNKNOWN); + _EXPORT_INT(m, "MIXER_SCHN_FRONT_LEFT", SND_MIXER_SCHN_FRONT_LEFT); + _EXPORT_INT(m, "MIXER_SCHN_FRONT_RIGHT", SND_MIXER_SCHN_FRONT_RIGHT); + _EXPORT_INT(m, "MIXER_SCHN_REAR_LEFT", SND_MIXER_SCHN_REAR_LEFT); + _EXPORT_INT(m, "MIXER_SCHN_REAR_RIGHT", SND_MIXER_SCHN_REAR_RIGHT); + _EXPORT_INT(m, "MIXER_SCHN_FRONT_CENTER", SND_MIXER_SCHN_FRONT_CENTER); + _EXPORT_INT(m, "MIXER_SCHN_WOOFER", SND_MIXER_SCHN_WOOFER); + _EXPORT_INT(m, "MIXER_SCHN_SIDE_LEFT", SND_MIXER_SCHN_SIDE_LEFT); + _EXPORT_INT(m, "MIXER_SCHN_SIDE_RIGHT", SND_MIXER_SCHN_SIDE_RIGHT); + _EXPORT_INT(m, "MIXER_SCHN_REAR_CENTER", SND_MIXER_SCHN_REAR_CENTER); + _EXPORT_INT(m, "MIXER_SCHN_MONO", SND_MIXER_SCHN_MONO); +#endif } diff --git a/doc/about.html b/doc/about.html index 494951a..cd7bf76 100644 --- a/doc/about.html +++ b/doc/about.html @@ -103,7 +103,7 @@ About this document ...
-If you find bugs in the wrappers please use the SourceForge bug tracker. Please -don't send bug reports regarding ALSA specifically. There are several -bugs in this API, and those should be reported to the ALSA team - not -me. +If you find bugs in the wrappers please use the SourceForge bug +tracker. Please don't send bug reports regarding ALSA specifically. +There are several bugs in this API, and those should be reported to +the ALSA team - not me.
@@ -112,7 +113,7 @@ me.
Casper Wilstrup
+cwi@aves.dk
| [control], [id], [cardname]) |
id - the id of the mixer control. Default is 0
-cardname - specifies which card should be used (this is only relevant -if you have more than one sound card). Omit to use the default sound card +cardname - specifies which card should be used (this is only + relevant if you have more than one sound card). Omit to use the + default sound card
@@ -80,36 +84,36 @@ Mixer objects have the following methods:
| ) |
| ) |
| ) |
| ) |
-To manipulate these swithes use the setrec or setmute methods +To manipulate these swithes use the setrec or +setmute methods
| ) |
| This mixer can control volume | |
| This mixer can control volume for all channels at the same time | This mixer can control volume for all channels at + the same time |
| This mixer can manipulate the playback volume | |
| Manipulate playback volumne for all channels at the same time | Manipulate playback volumne for all + channels at the same time |
| Manipulate sound capture volume | |
| Manipulate sound capture volume for all channels at a time | Manipulate sound capture volume for all + channels at a time |
@@ -176,34 +184,40 @@ the list are:
| [direction]) |
-The optional direction argument can be either 'playback' or 'capture', -which is relevant if the mixer can control both playback and capture volume. -The default value is 'playback' if the mixer has this capability, otherwise -'capture' +Returns a tuple (string, list of strings).
-
-
| [direction]) |
+$ amixer get "Mono Output Select" +Simple mixer control 'Mono Output Select',0 + Capabilities: enum + Items: 'Mix' 'Mic' + Item0: 'Mix' +
-The optional direction argument can be either 'playback' or 'capture', which is relevant -if the mixer can control both playback and capture volume. The default value is 'playback' -if the mixer has this capability, otherwise 'capture' +Using alsaaudio, one could do: +
+>>> import alsaaudio
+>>> m = alsaaudio.Mixer('Mono Output Select')
+>>> m.getenum()
+('Mix', ['Mix', 'Mic'])
++This method will return an empty tuple if the mixer is not an + enumerated control.
@@ -211,52 +225,95 @@ if the mixer has this capability, otherwise 'capture'
-This method will fail if the mixer has no playback switch capabilities. +This method will fail if the mixer has no playback switch + capabilities.
| [direction]) |
+The optional direction argument can be either 'playback' or + 'capture', which is relevant if the mixer can control both playback + and capture volume. The default value is 'playback' if the mixer + has this capability, otherwise 'capture' + +
+
+
| ) |
-This method will fail if the mixer has no capture switch capabilities. +This method will fail if the mixer has no capture switch + capabilities.
| volume,[channel],[direction]) |
-If the optional argument channel is present, the volume is set only for this channel. This -assumes that the mixer can control the volume for the channels independently. +The optional direction argument can be either 'playback' or + 'capture', which is relevant if the mixer can control both playback + and capture volume. The default value is 'playback' if the mixer has + this capability, otherwise 'capture'
-The optional direction argument can be either 'playback' or 'capture' is relevant if the mixer -has independent playback and capture volume capabilities, and controls which of the volumes -if changed. The default is 'playback' if the mixer has this capability, otherwise 'capture'.
| volume,[channel], + [direction]) |
+Change the current volume settings for this mixer. The volume + argument controls the new volume setting as an integer percentage. + +
+If the optional argument channel is present, the volume is set + only for this channel. This assumes that the mixer can control the + volume for the channels independently. + +
+The optional direction argument can be either 'playback' or + 'capture' is relevant if the mixer has independent playback and + capture volume capabilities, and controls which of the volumes if + changed. The default is 'playback' if the mixer has this capability, + otherwise 'capture'. +
+
| mute, [channel]) |
-The optional channel argument controls which channel is muted. The default is to set the mute flag -for all channels. +The optional channel argument controls which channel is muted. + The default is to set the mute flag for all channels.
This method will fail if the mixer has no playback mute capabilities @@ -264,42 +321,48 @@ This method will fail if the mixer has no playback mute capabilities
| capture,[channel]) |
-The optional channel argument controls which channel is changed. The default is to set the capture flag -for all channels. +The optional channel argument controls which channel is + changed. The default is to set the capture flag for all channels.
-This method will fail if the mixer has no capture switch capabilities +This method will fail if the mixer has no capture switch + capabilities.
A Note on the ALSA Mixer API
-The ALSA mixer API is extremely complicated - and hardly documented at all. alsaaudio implements -a much simplified way to access this API. In designing the API I've had to make some choices which -may limit what can and cannot be controlled through the API. However, If I had chosen to implement the -full API, I would have reexposed the horrible complexity/documentation ratio of the underlying API. -At least the alsaaudio API is easy to understand and use. +The ALSA mixer API is extremely complicated - and hardly documented at +all. alsaaudio implements a much simplified way to access +this API. In designing the API I've had to make some choices which may +limit what can and cannot be controlled through the API. However, If I +had chosen to implement the full API, I would have reexposed the +horrible complexity/documentation ratio of the underlying API. At +least the alsaaudio API is easy to understand and use.
-If my design choises prevents you from doing something that the underlying API would have allowed, -please let me know, so I can incorporate these need into future versions. +If my design choises prevents you from doing something that the +underlying API would have allowed, please let me know, so I can +incorporate these need into future versions.
-If the current state of affairs annoy you, the best you can do is to write a HOWTO on the API and -make this available on the net. Until somebody does this, the availability of ALSA mixer capable -devices will stay quite limited. +If the current state of affairs annoy you, the best you can do is to +write a HOWTO on the API and make this available on the net. Until +somebody does this, the availability of ALSA mixer capable devices +will stay quite limited.
-Unfortunately, I'm not able to create such a HOWTO myself, since I only understand half of the API, -and that which I do understand has come from a painful trial and error process. +Unfortunately, I'm not able to create such a HOWTO myself, since I +only understand half of the API, and that which I do understand has +come from a painful trial and error process.
@@ -336,7 +399,7 @@ and that which I do understand has come from a painful trial and error process.
-tex2html_comment_mark>15 - +
@@ -83,12 +82,14 @@ sound card). Omit to use the default sound card.
@@ -99,7 +100,7 @@ if you have more than one sound card). Omit to use the default sound card
This class is used to access a specific ALSA mixer.
The arguments are:
control - Name of the chosen mixed (default is Master).
-
id - id of mixer (default is 0) - More explaniation needed here
+
id - id of mixer (default is 0) - More explanation needed here
cardname specifies which card should be used (this is only relevant
if you have more than one sound card). Omit to use the default sound card
@@ -107,9 +108,9 @@ if you have more than one sound card). Omit to use the default sound card
@@ -161,7 +162,7 @@ failure.
The Advanced Linux Sound Architecture (ALSA) provides audio and MIDI -functionality to the Linux operating system. +functionality to the Linux operating system.
Logically ALSA consists of these components: @@ -113,7 +113,7 @@ More information about ALSA may be found on the project homepage
-The older Linux sound API (OSS) which is now deprecated is well supported -from the standard Python library, through the ossaudiodev module. No native -ALSA support exists in the standard library (yet). +The older Linux sound API (OSS) which is now deprecated is well +supported from the standard Python library, through the ossaudiodev +module. No native ALSA support exists in the standard library (yet).
-There are a few other ``ALSA for Python'' projects available, including at -least two different projects called pyAlsa. Neither of these seem to be under -active development at the time - and neither are very feature complete. +There are a few other ``ALSA for Python'' projects available, +including at least two different projects called pyAlsa. Neither of +these seem to be under active development at the time - and neither +are very feature complete.
-I wrote PyAlsaAudio to fill this gap. My long term goal is to have the module -included in the standard Python library, but that is probably a while of yet. +I wrote PyAlsaAudio to fill this gap. My long term goal is to have the +module included in the standard Python library, but that is probably a +while off yet.
-PyAlsaAudio hass full support for sound capture, playback of sound, as well as -the ALSA Mixer API. +PyAlsaAudio hass full support for sound capture, playback of sound, as +well as the ALSA Mixer API.
-MIDI support is not available, and since I don't own any MIDI hardware, it's -difficult for me to implement it. Volunteers to work on this would be greatly -appreciated +MIDI support is not available, and since I don't own any MIDI +hardware, it's difficult for me to implement it. Volunteers to work on +this would be greatly appreciated
Note: the wrappers link with the alsasound library (from the alsa-lib -package). Verify that this is installed by looking for /usr/lib/libasound.so -before building. Naturally you also need to use a kernel with proper ALSA -support. This is the default in Linux kernel 2.6 and later. If you are using -kernel version 2.4 you may need to install the ALSA patches yourself - although -most distributions ship with ALSA kernels. +package) and need the ALSA headers for compilation. Verify that you +have /usr/lib/libasound.so and /usr/include/alsa (or +similar paths) before building. + +
+On Debian (and probably Ubuntu), make sure you have libasound2-dev installed. + +
+Naturally you also need to use a kernel with proper ALSA support. This +is the default in Linux kernel 2.6 and later. If you are using kernel +version 2.4 you may need to install the ALSA patches yourself - +although most distributions ship with ALSA kernels.
To install, execute the following: @@ -108,7 +115,7 @@ And then as root:
-8000 Hz mono sound with 8 bit (1 byte) samples has a data rate of 8000 * 1 * 1 = 8 kb/s +8000 Hz mono sound with 8 bit (1 byte) samples has a data rate of + 8000 * 1 * 1 = 8 kb/s
-At the other end of the scale, 96000 Hz, 6 channel sound with 64 bit (8 bytes) samples -has a data rate of 96000 * 6 * 8 = 4608 kb/s (almost 5 Mb sound data per second) +At the other end of the scale, 96000 Hz, 6 channel sound with 64 bit + (8 bytes) samples has a data rate of 96000 * 6 * 8 = 4608 kb/s + (almost 5 Mb sound data per second)
-Once you understand these concepts, you will be ready to actually utilize PCM API. Read on. +Once you understand these concepts, you will be ready to use the PCM +API. Read on.
@@ -162,7 +176,7 @@ Once you understand these concepts, you will be ready to actually utilize PCM AP
-For now, the only examples available are the 'playbacktest.py' and the 'recordtest.py' programs included. -This will change in a future version. +For now, the only examples available are the 'playbacktest.py' and the +'recordtest.py' programs included. This will change in a future +version.
@@ -92,7 +93,7 @@ This will change in a future version.
-The acronym PCM is short for Pulse Code Modulation and is the method used in ALSA -and many other places to handle playback and capture of sampled sound data. +The acronym PCM is short for Pulse Code Modulation and is the method +used in ALSA and many other places to handle playback and capture of +sampled sound data.
-PCM objects in alsaaudio are used to do exactly that, either play sample based -sound or capture sound from some input source (perhaps a microphone). The PCM object -constructor takes the following arguments: +PCM objects in alsaaudio are used to do exactly that, either +play sample based sound or capture sound from some input source +(probably a microphone). The PCM object constructor takes the following +arguments:
| ) |
@@ -116,7 +121,8 @@ Returns the type of PCM object. Either PCM_CAPTURE or PCM_PLAYBACK.
@@ -124,7 +130,7 @@ Return the mode of the PCM object. One of PCM_NONBLOCK, PCM_ASYNC, or PCM_NORMAL
@@ -132,8 +138,9 @@ Return the name of the sound card used by this PCM object.
@@ -141,17 +148,18 @@ and 6 = full 6 channel audio. Few sound cards support more than 2 channels
| ) |
The following formats are provided by ALSA: @@ -168,47 +176,66 @@ The following formats are provided by ALSA:
@@ -219,9 +246,10 @@ The following formats are provided by ALSA:
@@ -229,14 +257,16 @@ it may return nothing at all)
-In PCM_NONBLOCK mode, the call will not block, but will return (0,'') if no new period
-has become available since the last call to read.
+In PCM_NONBLOCK mode, the call will not block, but will return
+ (0,'') if no new period has become available since the last
+ call to read.
@@ -244,50 +274,67 @@ has become available since the last call to read.
-If the device is not in PCM_NONBLOCK mode, this call will block if the kernel buffer is full, and -until enough sound has been played to allow the sound data to be buffered. The call always returns -the size of the data provided +If the device is not in PCM_NONBLOCK mode, this call will block if + the kernel buffer is full, and until enough sound has been played to + allow the sound data to be buffered. The call always returns the + size of the data provided
-In PCM_NONBLOCK mode, the call will return immediately, with a return value of zero, if the buffer is -full. In this case, the data should be written at a later time. +In PCM_NONBLOCK mode, the call will return immediately, with a + return value of zero, if the buffer is full. In this case, the data + should be written at a later time. +
+
| [enable=1]) |
A few hints on using PCM devices for playback
-The most common reason for problems with playback of PCM audio, is that the people don't properly understand -that writes to PCM devices must match exactly the data rate of the device. +The most common reason for problems with playback of PCM audio, is +that the people don't properly understand that writes to PCM devices +must match exactly the data rate of the device.
-If too little data is written to the device, it will underrun, and ugly clicking sounds will occur. Conversely, -of too much data is written to the device, the write function will either block (PCM_NORMAL mode) or return zero -(PCM_NONBLOCK mode). +If too little data is written to the device, it will underrun, and +ugly clicking sounds will occur. Conversely, of too much data is +written to the device, the write function will either block +(PCM_NORMAL mode) or return zero (PCM_NONBLOCK mode).
-If your program does nothing, but play sound, the easiest way is to put the device in PCM_NORMAL mode, and just -write as much data to the device as possible. This strategy can also be achieved by using a separate thread -with the sole task of playing out sound. +If your program does nothing, but play sound, the easiest way is to +put the device in PCM_NORMAL mode, and just write as much data to the +device as possible. This strategy can also be achieved by using a +separate thread with the sole task of playing out sound.
-In GUI programs, however, it may be a better strategy to setup the device, preload the buffer with a few -periods by calling write a couple of times, and then use some timer method to write one period size of data to -the device every period. The purpose of the preloading is to avoid underrun clicks if the used timer -doesn't expire exactly on time. +In GUI programs, however, it may be a better strategy to setup the +device, preload the buffer with a few periods by calling write a +couple of times, and then use some timer method to write one period +size of data to the device every period. The purpose of the preloading +is to avoid underrun clicks if the used timer doesn't expire exactly +on time.
-Also note, that most timer APIs that you can find for Python will cummulate time delays: If you set the timer -to expire after 1/10'th of a second, the actual timeout will happen slightly later, which will accumulate to -quite a lot after a few seconds. Hint: use time.time() to check how much time has really passed, and add -extra writes as nessecary. +Also note, that most timer APIs that you can find for Python will +cummulate time delays: If you set the timer to expire after 1/10'th of +a second, the actual timeout will happen slightly later, which will +accumulate to quite a lot after a few seconds. Hint: use time.time() +to check how much time has really passed, and add extra writes as +nessecary.
@@ -324,7 +371,7 @@ extra writes as nessecary.
Casper Wilstrup
+cwi@aves.dk