[alsa-devel] [RFC] virtual master control (1/3)

Takashi Iwai tiwai at suse.de
Fri Nov 23 18:26:28 CET 2007


At Mon, 19 Nov 2007 12:13:28 +0100,
I wrote:
> 
> > > > If not, then it is better  to remove it/remame to VolumeKnob
> > >
> > > Agreed.  I'd like to take a safer way if you don't insist...
> > Great, it is probably the best to have a virtual master volume.
> > Just one question, it will be probably enabled for devices that don't have a 
> > master volume (or have it broken like the STAC), right?,
> > And when you expect it to be merged?
> 
> Hopefully will be posted in this week after a small brush up.

OK, here is a series of the patch I promised.

The first one is the patch to add virtual master controls.

===

[PATCH] Add virtual master control

This patch adds the routines to create virtual master controls.
A virtual master control can have multiple slave controls that
are supposed to be identical type.  The master volume will add the
master attenuation and the master switch will add the master mute
switch.

---

diff -r 56fbe685f99d core/Kconfig
--- a/core/Kconfig	Fri Nov 23 15:41:44 2007 +0100
+++ b/core/Kconfig	Fri Nov 23 18:13:37 2007 +0100
@@ -15,6 +15,9 @@ config SND_RAWMIDI
 config SND_RAWMIDI
 	tristate
 	depends on SND
+
+config SND_VMASTER
+	bool
 
 config SND_SEQUENCER
 	tristate "Sequencer support"
diff -r 56fbe685f99d core/Makefile
--- a/core/Makefile	Fri Nov 23 15:41:44 2007 +0100
+++ b/core/Makefile	Fri Nov 23 18:13:37 2007 +0100
@@ -6,6 +6,7 @@ snd-y     := sound.o init.o memory.o inf
 snd-y     := sound.o init.o memory.o info.o control.o misc.o device.o
 snd-$(CONFIG_ISA_DMA_API) += isadma.o
 snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o info_oss.o
+snd-$(CONFIG_SND_VMASTER) += vmaster.o
 
 snd-pcm-objs := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
 		pcm_memory.o
diff -r 56fbe685f99d core/vmaster.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/vmaster.c	Fri Nov 23 18:13:37 2007 +0100
@@ -0,0 +1,323 @@
+/*
+ * Virtual master and slave controls
+ *
+ *  Copyright (c) by Takashi Iwai <tiwai at suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+
+struct link_master {
+	struct list_head slaves;
+	int val;
+};
+
+struct link_slave {
+	struct list_head list;
+	struct link_master *master;
+	int type;
+	int count;
+	int min_val, max_val;
+	int vals[2];
+	struct snd_kcontrol slave;
+};
+
+/* get the slave ctl info and save the initial values */
+static int slave_init(struct link_slave *slave)
+{
+	struct snd_ctl_elem_info *uinfo;
+	struct snd_ctl_elem_value *uctl;
+	int err, ch;
+
+	uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL);
+	if (!uinfo)
+		return -ENOMEM;
+	uinfo->id = slave->slave.id;
+	err = slave->slave.info(&slave->slave, uinfo);
+	if (err < 0) {
+		kfree(uinfo);
+		return err;
+	}
+	slave->type = uinfo->type;
+	slave->count = uinfo->count;
+	if (slave->count > 2  ||
+	    (slave->type != SNDRV_CTL_ELEM_TYPE_INTEGER &&
+	     slave->type != SNDRV_CTL_ELEM_TYPE_BOOLEAN)) {
+		kfree(uinfo);
+		return -EINVAL;
+	}
+	slave->min_val = uinfo->value.integer.min;
+	slave->max_val = uinfo->value.integer.max;
+	kfree(uinfo);
+
+	uctl = kmalloc(sizeof(*uctl), GFP_KERNEL);
+	if (!uctl)
+		return -ENOMEM;
+	uctl->id = slave->slave.id;
+	err = slave->slave.get(&slave->slave, uctl);
+	for (ch = 0; ch < slave->count; ch++)
+		slave->vals[ch] = uctl->value.integer.value[ch];
+	kfree(uctl);
+	return 0;
+}
+
+static int slave_get_val(struct link_slave *slave,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	int ch;
+
+	if (!slave->count) {
+		int err = slave_init(slave);
+		if (err < 0)
+			return err;
+	}
+	for (ch = 0; ch < slave->count; ch++)
+		ucontrol->value.integer.value[ch] = slave->vals[ch];
+	return 0;
+}
+
+static int slave_put_val(struct link_slave *slave,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	int ch, vol;
+
+	switch (slave->type) {
+	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+		for (ch = 0; ch < slave->count; ch++)
+			ucontrol->value.integer.value[ch] &=
+				!!slave->master->val;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_INTEGER:
+		for (ch = 0; ch < slave->count; ch++) {
+			/* max master volume is supposed to be 0 dB */
+			vol = ucontrol->value.integer.value[ch];
+			vol += slave->master->val - slave->max_val;
+			if (vol < slave->min_val)
+				vol = slave->min_val;
+			else if (vol > slave->max_val)
+				vol = slave->max_val;
+			ucontrol->value.integer.value[ch] = vol;
+		}
+		break;
+	}
+	return slave->slave.put(&slave->slave, ucontrol);
+}
+
+/*
+ * ctl callbacks for slaves
+ */
+static int slave_info(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_info *uinfo)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	return slave->slave.info(&slave->slave, uinfo);
+}
+
+static int slave_get(struct snd_kcontrol *kcontrol,
+		     struct snd_ctl_elem_value *ucontrol)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	return slave_get_val(slave, ucontrol);
+}
+
+static int slave_put(struct snd_kcontrol *kcontrol,
+		     struct snd_ctl_elem_value *ucontrol)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	int ch, changed = 0;
+
+	if (!slave->count) {
+		int err = slave_init(slave);
+		if (err < 0)
+			return err;
+	}
+	for (ch = 0; ch < slave->count; ch++) {
+		if (slave->vals[ch] != ucontrol->value.integer.value[ch]) {
+			changed = 1;
+			slave->vals[ch] = ucontrol->value.integer.value[ch];
+		}
+	}
+	if (!changed)
+		return 0;
+	return slave_put_val(slave, ucontrol);
+}
+
+static int slave_tlv_cmd(struct snd_kcontrol *kcontrol,
+			 int op_flag, unsigned int size,
+			 unsigned int __user *tlv)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	return slave->slave.tlv.c(&slave->slave, op_flag, size, tlv);
+}
+
+static void slave_free(struct snd_kcontrol *kcontrol)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	if (slave->slave.private_free)
+		slave->slave.private_free(&slave->slave);
+	if (slave->master)
+		list_del(&slave->list);
+	kfree(slave);
+}
+
+/*
+ * add a slave control to the group with the given master control
+ *
+ * All slaves must be the same type (returning the same information
+ * via info callback).  The fucntion doesn't check it, so it's your
+ * responsibility.
+ *
+ * Also, some additional limitations:
+ * - at most two channels
+ * - logarithmic volume control (dB level), no linear volume
+ * - master can only attenuate the volume, no gain
+ */
+int snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave)
+{
+	struct link_master *master_link = snd_kcontrol_chip(master);
+	struct link_slave *srec;
+
+	srec = kzalloc(sizeof(*srec) +
+		       slave->count * sizeof(*slave->vd), GFP_KERNEL);
+	if (!srec)
+		return -ENOMEM;
+	srec->slave = *slave;
+	memcpy(srec->slave.vd, slave->vd, slave->count * sizeof(*slave->vd));
+	srec->master = master_link;
+
+	/* override callbacks */
+	slave->info = slave_info;
+	slave->get = slave_get;
+	slave->put = slave_put;
+	if (slave->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)
+		slave->tlv.c = slave_tlv_cmd;
+	slave->private_data = srec;
+	slave->private_free = slave_free;
+
+	list_add_tail(&srec->list, &master_link->slaves);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ctl_add_slave);
+
+/*
+ * ctl callbacks for master controls
+ */
+static int master_info(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_info *uinfo)
+{
+	struct link_master *master = snd_kcontrol_chip(kcontrol);
+	struct link_slave *slave;
+	int ret;
+
+	if (list_empty(&master->slaves))
+		return -EINVAL;
+	slave = list_first_entry(&master->slaves, struct link_slave, list);
+	ret = slave->slave.info(&slave->slave, uinfo);
+	uinfo->count = 1; /* override, always mono */
+	return ret;
+}
+
+static int master_get(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol)
+{
+	struct link_master *master = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = master->val;
+	return 0;
+}
+
+static int master_put(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol)
+{
+	struct link_master *master = snd_kcontrol_chip(kcontrol);
+	struct link_slave *slave;
+	struct snd_ctl_elem_value *uval;
+	int old_val = master->val;
+
+	if (ucontrol->value.integer.value[0] == old_val)
+		return 0;
+
+	uval = kmalloc(sizeof(*uval), GFP_KERNEL);
+	if (!uval)
+		return -ENOMEM;
+	list_for_each_entry(slave, &master->slaves, list) {
+		master->val = old_val;
+		uval->id = slave->slave.id;
+		slave_get_val(slave, uval);
+		master->val = ucontrol->value.integer.value[0];
+		slave_put_val(slave, uval);
+	}
+	kfree(uval);
+	return 1;
+}
+
+static void master_free(struct snd_kcontrol *kcontrol)
+{
+	struct link_master *master = snd_kcontrol_chip(kcontrol);
+	struct link_slave *slave;
+
+	list_for_each_entry(slave, &master->slaves, list)
+		slave->master = NULL;
+	kfree(master);
+}
+
+
+/*
+ * create a virtual master control with the given name
+ *
+ * So far, only a control with mono channel is supported
+ * (just for simplicity reason)
+ */
+struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
+						 const unsigned int *tlv)
+{
+	struct link_master *master;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_new knew;
+
+	memset(&knew, 0, sizeof(knew));
+	knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	knew.name = name;
+	knew.info = master_info;
+
+	master = kzalloc(sizeof(*master), GFP_KERNEL);
+	if (!master)
+		return NULL;
+	INIT_LIST_HEAD(&master->slaves);
+
+	kctl = snd_ctl_new1(&knew, master);
+	if (!kctl) {
+		kfree(master);
+		return NULL;
+	}
+	/* override some callbacks */
+	kctl->info = master_info;
+	kctl->get = master_get;
+	kctl->put = master_put;
+	kctl->private_free = master_free;
+
+	if (tlv) {
+		kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+		kctl->tlv.p = tlv;
+	}
+	return kctl;
+}
+
+EXPORT_SYMBOL(snd_ctl_make_virtual_master);
diff -r 56fbe685f99d include/control.h
--- a/include/control.h	Fri Nov 23 15:41:44 2007 +0100
+++ b/include/control.h	Fri Nov 23 18:13:37 2007 +0100
@@ -169,4 +169,12 @@ int snd_ctl_boolean_stereo_info(struct s
 int snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol,
 				struct snd_ctl_elem_info *uinfo);
 
+/*
+ * virtual master control
+ */
+struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
+						 const unsigned int *tlv);
+int snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave);
+		      
+
 #endif	/* __SOUND_CONTROL_H */


More information about the Alsa-devel mailing list