mirror of
https://github.com/larsimmisch/pyalsaaudio.git
synced 2026-04-16 16:15:31 +00:00
Compare commits
3 Commits
main-pre-r
...
volume-db
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
917a11b398 | ||
|
|
ce84e69cc1 | ||
|
|
c2cfe0211b |
199
alsaaudio.c
199
alsaaudio.c
@@ -77,6 +77,12 @@ typedef struct {
|
||||
snd_mixer_t *handle;
|
||||
} alsamixer_t;
|
||||
|
||||
typedef enum {
|
||||
unit_percent,
|
||||
unit_dB,
|
||||
unit_last
|
||||
} volume_unit_t;
|
||||
|
||||
/******************************************/
|
||||
/* PCM object wrapper */
|
||||
/******************************************/
|
||||
@@ -1567,21 +1573,31 @@ Possible values in this list are:\n\
|
||||
- 'Capture Exclusive'\n");
|
||||
|
||||
|
||||
static int alsamixer_getpercentage(long min, long max, long value)
|
||||
static double alsamixer_getpercentage(long min, long max, long value)
|
||||
{
|
||||
/* Convert from number in range to percentage */
|
||||
int range = max - min;
|
||||
int tmp;
|
||||
|
||||
if (range == 0)
|
||||
return 0;
|
||||
|
||||
value -= min;
|
||||
tmp = rint((double)value/(double)range * 100);
|
||||
return tmp;
|
||||
return (double)value/(double)range * 100.0;
|
||||
}
|
||||
|
||||
static long alsamixer_getphysvolume(long min, long max, int percentage)
|
||||
static double alsamixer_getdB(long min, long max, long value)
|
||||
{
|
||||
/* Convert from number in range to dB */
|
||||
int range = max - min;
|
||||
|
||||
if (range == 0)
|
||||
return 0;
|
||||
|
||||
value -= min;
|
||||
return log10((double)value/range) * 60.0;
|
||||
}
|
||||
|
||||
static long alsamixer_getphysvolume(long min, long max, double percentage)
|
||||
{
|
||||
/* Convert from percentage to number in range */
|
||||
int range = max - min;
|
||||
@@ -1590,57 +1606,76 @@ static long alsamixer_getphysvolume(long min, long max, int percentage)
|
||||
if (range == 0)
|
||||
return 0;
|
||||
|
||||
tmp = rint((double)range * ((double)percentage*.01)) + min;
|
||||
tmp = rint((double)range * (percentage * .01)) + min;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
alsamixer_getvolume(alsamixer_t *self, PyObject *args)
|
||||
alsamixer_getvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
|
||||
{
|
||||
snd_mixer_elem_t *elem;
|
||||
int channel;
|
||||
long ival;
|
||||
PyObject *pcmtypeobj = NULL;
|
||||
long pcmtype;
|
||||
PyObject *dirobj = NULL;
|
||||
long dir;
|
||||
int unit = unit_percent;
|
||||
PyObject *result;
|
||||
PyObject *item;
|
||||
|
||||
if (!PyArg_ParseTuple(args,"|O:getvolume", &pcmtypeobj))
|
||||
static char *kwlist[] = { "direction", "unit", NULL };
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "|Oi:getvolume", kwlist, &dirobj, &unit))
|
||||
return NULL;
|
||||
|
||||
if (unit >= unit_last) {
|
||||
PyErr_SetString(PyExc_ValueError, "unit must be 'percent' or 'dB'");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dir = get_pcmtype(dirobj);
|
||||
if (dir < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->handle)
|
||||
{
|
||||
PyErr_SetString(ALSAAudioError, "Mixer is closed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pcmtype = get_pcmtype(pcmtypeobj);
|
||||
if (pcmtype < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
|
||||
|
||||
result = PyList_New(0);
|
||||
|
||||
for (channel = 0; channel <= SND_MIXER_SCHN_LAST; channel++) {
|
||||
if (pcmtype == SND_PCM_STREAM_PLAYBACK &&
|
||||
if (dir == SND_PCM_STREAM_PLAYBACK &&
|
||||
snd_mixer_selem_has_playback_channel(elem, channel))
|
||||
{
|
||||
snd_mixer_selem_get_playback_volume(elem, channel, &ival);
|
||||
item = PyLong_FromLong(alsamixer_getpercentage(self->pmin,
|
||||
self->pmax,
|
||||
ival));
|
||||
if (unit == unit_percent) {
|
||||
item = PyFloat_FromDouble(
|
||||
alsamixer_getpercentage(self->pmin, self->pmax, ival));
|
||||
}
|
||||
else {
|
||||
item = PyFloat_FromDouble(
|
||||
alsamixer_getdB(self->pmin, self->pmax, ival));
|
||||
}
|
||||
PyList_Append(result, item);
|
||||
Py_DECREF(item);
|
||||
}
|
||||
else if (pcmtype == SND_PCM_STREAM_CAPTURE
|
||||
else if (dir == SND_PCM_STREAM_CAPTURE
|
||||
&& snd_mixer_selem_has_capture_channel(elem, channel)
|
||||
&& snd_mixer_selem_has_capture_volume(elem)) {
|
||||
&& snd_mixer_selem_has_capture_volume(elem))
|
||||
{
|
||||
snd_mixer_selem_get_capture_volume(elem, channel, &ival);
|
||||
item = PyLong_FromLong(alsamixer_getpercentage(self->cmin,
|
||||
self->cmax,
|
||||
ival));
|
||||
if (unit == unit_percent) {
|
||||
item = PyFloat_FromDouble(
|
||||
alsamixer_getpercentage(self->cmin, self->cmax, ival));
|
||||
}
|
||||
else {
|
||||
item = PyFloat_FromDouble(
|
||||
alsamixer_getdB(self->cmin, self->cmax, ival));
|
||||
}
|
||||
PyList_Append(result, item);
|
||||
Py_DECREF(item);
|
||||
}
|
||||
@@ -1650,15 +1685,17 @@ alsamixer_getvolume(alsamixer_t *self, PyObject *args)
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(getvolume_doc,
|
||||
"getvolume([pcmtype]) -> List of volume settings (int)\n\
|
||||
"getvolume(direction=PCM_PLAYBACK, unit=Percent) -> List of volume settings (float)\n\
|
||||
\n\
|
||||
Returns a list with the current volume settings for each channel.\n\
|
||||
The list elements are integer percentages.\n\
|
||||
The list elements are float percentages.\n\
|
||||
\n\
|
||||
The optional 'pcmtype' argument can be either PCM_PLAYBACK or\n\
|
||||
The 'direction' argument can be either PCM_PLAYBACK or\n\
|
||||
PCM_CAPTURE, which is relevant if the mixer can control both\n\
|
||||
playback and capture volume. The default value is PCM_PLAYBACK\n\
|
||||
if the mixer has this capability, otherwise PCM_CAPTURE");
|
||||
if the mixer has this capability, otherwise PCM_CAPTURE\
|
||||
\n\
|
||||
The optional 'unit' argument can be either 'percent' or 'dB'.");
|
||||
|
||||
|
||||
static PyObject *
|
||||
@@ -1984,29 +2021,53 @@ This method will fail if the mixer has no capture switch capabilities.");
|
||||
|
||||
|
||||
static PyObject *
|
||||
alsamixer_setvolume(alsamixer_t *self, PyObject *args)
|
||||
alsamixer_setvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
|
||||
{
|
||||
snd_mixer_elem_t *elem;
|
||||
int i;
|
||||
long volume;
|
||||
double volume = 0.0;
|
||||
PyObject *volumeobj = NULL;
|
||||
int physvolume;
|
||||
PyObject *pcmtypeobj = NULL;
|
||||
long pcmtype;
|
||||
PyObject *dirobj = NULL;
|
||||
long dir;
|
||||
int unit = unit_percent;
|
||||
int channel = MIXER_CHANNEL_ALL;
|
||||
int done = 0;
|
||||
|
||||
if (!PyArg_ParseTuple(args,"l|iO:setvolume", &volume, &channel,
|
||||
&pcmtypeobj))
|
||||
return NULL;
|
||||
|
||||
if (volume < 0 || volume > 100)
|
||||
{
|
||||
PyErr_SetString(ALSAAudioError, "Volume must be between 0 and 100");
|
||||
static char *kwlist[] = { "channel", "direction", "unit", NULL };
|
||||
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "O|iOi:setvolume", kwlist, &volumeobj, &channel,
|
||||
&dirobj, &unit)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pcmtype = get_pcmtype(pcmtypeobj);
|
||||
if (pcmtype < 0) {
|
||||
// unit
|
||||
if (unit >= unit_last) {
|
||||
PyErr_SetString(PyExc_ValueError, "unit must be 'percent' or 'dB'");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyLong_Check(volumeobj)) {
|
||||
volume = (double)PyLong_AsLong(volumeobj);
|
||||
}
|
||||
else if (PyFloat_Check(volumeobj)) {
|
||||
volume = PyFloat_AsDouble(volumeobj);
|
||||
}
|
||||
else {
|
||||
PyErr_SetString(PyExc_ValueError, "Volume must be integer or float");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (unit == unit_percent && (volume < 0.0 || volume > 100.0))
|
||||
{
|
||||
PyErr_SetString(PyExc_ValueError, "Volume in percent must be between 0 and 100");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// pcmtype
|
||||
dir = get_pcmtype(dirobj);
|
||||
if (dir < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -2015,41 +2076,51 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args)
|
||||
PyErr_SetString(ALSAAudioError, "Mixer is closed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
|
||||
|
||||
if (!pcmtypeobj || (pcmtypeobj == Py_None))
|
||||
if (!dirobj || (dirobj == Py_None))
|
||||
{
|
||||
if (self->pchannels)
|
||||
pcmtype = SND_PCM_STREAM_PLAYBACK;
|
||||
dir = SND_PCM_STREAM_PLAYBACK;
|
||||
else
|
||||
pcmtype = SND_PCM_STREAM_CAPTURE;
|
||||
dir = SND_PCM_STREAM_CAPTURE;
|
||||
}
|
||||
|
||||
for (i = 0; i <= SND_MIXER_SCHN_LAST; i++)
|
||||
{
|
||||
if (channel == -1 || channel == i)
|
||||
{
|
||||
if (pcmtype == SND_PCM_STREAM_PLAYBACK &&
|
||||
if (dir == SND_PCM_STREAM_PLAYBACK &&
|
||||
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);
|
||||
if (unit == unit_percent) {
|
||||
physvolume = alsamixer_getphysvolume(self->pmin,
|
||||
self->pmax, volume);
|
||||
snd_mixer_selem_set_playback_volume(elem, i, physvolume);
|
||||
}
|
||||
else {
|
||||
snd_mixer_selem_set_playback_dB(elem, i, (long)(volume * 100.0), 0);
|
||||
}
|
||||
done++;
|
||||
}
|
||||
else if (pcmtype == SND_PCM_STREAM_CAPTURE
|
||||
else if (dir == SND_PCM_STREAM_CAPTURE
|
||||
&& snd_mixer_selem_has_capture_channel(elem, i)
|
||||
&& snd_mixer_selem_has_capture_volume(elem))
|
||||
{
|
||||
physvolume = alsamixer_getphysvolume(self->cmin, self->cmax,
|
||||
volume);
|
||||
snd_mixer_selem_set_capture_volume(elem, i, physvolume);
|
||||
if (unit == unit_percent) {
|
||||
physvolume = alsamixer_getphysvolume(self->cmin, self->cmax,
|
||||
volume);
|
||||
snd_mixer_selem_set_capture_volume(elem, i, physvolume);
|
||||
}
|
||||
else {
|
||||
snd_mixer_selem_set_capture_dB(elem, i, (long)(volume * 100), 0);
|
||||
}
|
||||
done++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!done)
|
||||
if (!done)
|
||||
{
|
||||
PyErr_Format(ALSAAudioError, "No such channel [%s]",
|
||||
self->cardname);
|
||||
@@ -2061,19 +2132,21 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args)
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(setvolume_doc,
|
||||
"setvolume(volume[[, channel] [, pcmtype]])\n\
|
||||
"setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit='percent')\n\
|
||||
\n\
|
||||
Change the current volume settings for this mixer. The volume argument\n\
|
||||
controls the new volume setting as an integer percentage.\n\
|
||||
controls the new volume setting as a percentage.\n\
|
||||
If the optional argument channel is present, the volume is set only for\n\
|
||||
this channel. This assumes that the mixer can control the volume for the\n\
|
||||
channels independently.\n\
|
||||
\n\
|
||||
The optional direction argument can be either PCM_PLAYBACK or PCM_CAPTURE.\n\
|
||||
The optional 'direction' argument can be either PCM_PLAYBACK or PCM_CAPTURE.\n\
|
||||
It is relevant if the mixer has independent playback and capture volume\n\
|
||||
capabilities, and controls which of the volumes will be changed.\n\
|
||||
The default is 'playback' if the mixer has this capability, otherwise\n\
|
||||
'capture'.");
|
||||
The default is PCM_PLAYBACK if the mixer has this capability, otherwise\n\
|
||||
PCM_CAPTURE.\n\
|
||||
\n\
|
||||
The optional 'unit' argument can be either 'percent' (the default) or 'dB'.");
|
||||
|
||||
|
||||
static PyObject *
|
||||
@@ -2084,6 +2157,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:setmute", &mute, &channel))
|
||||
return NULL;
|
||||
|
||||
@@ -2291,13 +2365,13 @@ static PyMethodDef alsamixer_methods[] = {
|
||||
switchcap_doc},
|
||||
{"volumecap", (PyCFunction)alsamixer_volumecap, METH_VARARGS,
|
||||
volumecap_doc},
|
||||
{"getvolume", (PyCFunction)alsamixer_getvolume, METH_VARARGS,
|
||||
{"getvolume", (PyCFunction)alsamixer_getvolume, METH_VARARGS | METH_KEYWORDS,
|
||||
getvolume_doc},
|
||||
{"getrange", (PyCFunction)alsamixer_getrange, METH_VARARGS, getrange_doc},
|
||||
{"getenum", (PyCFunction)alsamixer_getenum, METH_VARARGS, getenum_doc},
|
||||
{"getmute", (PyCFunction)alsamixer_getmute, METH_VARARGS, getmute_doc},
|
||||
{"getrec", (PyCFunction)alsamixer_getrec, METH_VARARGS, getrec_doc},
|
||||
{"setvolume", (PyCFunction)alsamixer_setvolume, METH_VARARGS,
|
||||
{"setvolume", (PyCFunction)alsamixer_setvolume, METH_VARARGS | METH_KEYWORDS,
|
||||
setvolume_doc},
|
||||
{"setenum", (PyCFunction)alsamixer_setenum, METH_VARARGS, setenum_doc},
|
||||
{"setmute", (PyCFunction)alsamixer_setmute, METH_VARARGS, setmute_doc},
|
||||
@@ -2447,6 +2521,9 @@ PyObject *PyInit_alsaaudio(void)
|
||||
Py_INCREF(ALSAAudioError);
|
||||
PyModule_AddObject(m, "ALSAAudioError", ALSAAudioError);
|
||||
|
||||
_EXPORT_INT(m, "Percent", unit_percent);
|
||||
_EXPORT_INT(m, "dB", unit_dB);
|
||||
|
||||
_EXPORT_INT(m, "PCM_PLAYBACK",SND_PCM_STREAM_PLAYBACK);
|
||||
_EXPORT_INT(m, "PCM_CAPTURE",SND_PCM_STREAM_CAPTURE);
|
||||
|
||||
|
||||
@@ -443,31 +443,35 @@ Mixer objects have the following methods:
|
||||
This method will fail if the mixer has no capture switch capabilities.
|
||||
|
||||
|
||||
.. method:: Mixer.getvolume([direction])
|
||||
.. method:: Mixer.getvolume(direction=PCM_PLAYBACK, unit=Percent)
|
||||
|
||||
Returns a list with the current volume settings for each channel. The list
|
||||
elements are integer percentages.
|
||||
elements are percentages or dB values, depending on *unit*.
|
||||
|
||||
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
|
||||
The *direction* argument can be either :const:`PCM_PLAYBACK` or
|
||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
||||
|
||||
|
||||
.. method:: Mixer.setvolume(volume, [channel], [direction])
|
||||
.. method:: Mixer.setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit=Percent)
|
||||
|
||||
Change the current volume settings for this mixer. The *volume* argument
|
||||
controls the new volume setting as an integer percentage.
|
||||
controls the new volume setting as either a percentage or a dB value. Both
|
||||
integer and floating point values can be given.
|
||||
|
||||
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 *channel* argument can be used to restrict the channels for which the volume is
|
||||
set. By default, the volume of all channels is adjusted. This assumes that the mixer
|
||||
can control the volume for the channels independently.
|
||||
|
||||
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
|
||||
The *direction* argument can be either :const:`PCM_PLAYBACK` or
|
||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
||||
|
||||
The *unit* argument determines how the volume value is interpreted, as a prcentage
|
||||
or as a dB value.
|
||||
|
||||
.. method:: Mixer.setmute(mute, [channel])
|
||||
|
||||
Sets the mute flag to a new value. The *mute* argument is either 0 for not
|
||||
|
||||
18
mixertest.py
18
mixertest.py
@@ -46,13 +46,17 @@ def show_mixer(name, kwargs):
|
||||
print("Capabilities: %s %s" % (' '.join(mixer.volumecap()),
|
||||
' '.join(mixer.switchcap())))
|
||||
volumes = mixer.getvolume()
|
||||
for i in range(len(volumes)):
|
||||
print("Channel %i volume: %i%%" % (i,volumes[i]))
|
||||
|
||||
for i, v in enumerate(volumes):
|
||||
print("Channel %i volume: %.02f%%" % (i, v))
|
||||
|
||||
volumes = mixer.getvolume(unit=alsaaudio.dB)
|
||||
for i, v in enumerate(volumes):
|
||||
print("Channel %i volume: %.02fdB" % (i, v))
|
||||
|
||||
try:
|
||||
mutes = mixer.getmute()
|
||||
for i in range(len(mutes)):
|
||||
if mutes[i]:
|
||||
for i, m in enumerate(mutes):
|
||||
if m:
|
||||
print("Channel %i is muted" % i)
|
||||
except alsaaudio.ALSAAudioError:
|
||||
# May not support muting
|
||||
@@ -60,8 +64,8 @@ def show_mixer(name, kwargs):
|
||||
|
||||
try:
|
||||
recs = mixer.getrec()
|
||||
for i in range(len(recs)):
|
||||
if recs[i]:
|
||||
for i, r in enumerate(recs):
|
||||
if r:
|
||||
print("Channel %i is recording" % i)
|
||||
except alsaaudio.ALSAAudioError:
|
||||
# May not support recording
|
||||
|
||||
Reference in New Issue
Block a user