]> git.alsa-project.org Git - alsa-utils.git/commitdiff
alsactl - restore dB level
authorTakashi Iwai <tiwai@suse.de>
Wed, 24 Oct 2007 11:53:14 +0000 (13:53 +0200)
committerTakashi Iwai <tiwai@suse.de>
Wed, 24 Oct 2007 11:53:14 +0000 (13:53 +0200)
When alsactl saved state has dB level information and an attribute
of a control element is changed after save (e.g. volume range is
changed), try to restore the values to keep the same dB level.

This change requires the new alsa-lib functions for TLV dB
conversion, so we check it in configure (until AM_PATH_ALSA(1.0.16)
works).

alsactl/state.c
configure.in

index 887589f3ee3b53a5d5f20aa8d0471c92df426df1..be0a5126390b89b04ae1bf668837050d53cbf314 100644 (file)
@@ -188,6 +188,46 @@ static unsigned int *str_to_tlv(const char *s)
        return tlv;
 }
 
+/*
+ * add the TLV string and dB ranges to comment fields
+ */
+static int add_tlv_comments(snd_ctl_t *handle, snd_ctl_elem_id_t *id,
+                           snd_ctl_elem_info_t *info, snd_config_t *comment)
+{
+       unsigned int tlv[MAX_USER_TLV_SIZE];
+       unsigned int *db;
+       long dbmin, dbmax;
+       int err;
+
+       if (snd_ctl_elem_tlv_read(handle, id, tlv, sizeof(tlv)) < 0)
+               return 0; /* ignore error */
+
+       if (snd_ctl_elem_info_is_tlv_writable(info)) {
+               char *s = tlv_to_str(tlv);
+               if (s) {
+                       err = snd_config_string_add(comment, "tlv", s);
+                       if (err < 0) {
+                               error("snd_config_string_add: %s", snd_strerror(err));
+                               return err;
+                       }
+                       free(s);
+               }
+       }
+
+       err = snd_tlv_parse_dB_info(tlv, sizeof(tlv), &db);
+       if (err <= 0)
+               return 0;
+
+       snd_tlv_get_dB_range(db, snd_ctl_elem_info_get_min(info),
+                            snd_ctl_elem_info_get_max(info),
+                            &dbmin, &dbmax);
+       if (err < 0)
+               return err;
+       snd_config_integer_add(comment, "dbmin", dbmin);
+       snd_config_integer_add(comment, "dbmax", dbmax);
+       return 0;
+}
+
 static int get_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, snd_config_t *top)
 {
        snd_ctl_elem_value_t *ctl;
@@ -285,8 +325,12 @@ static int get_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, snd_config_t *t
                        error("snd_config_string_add: %s", snd_strerror(err));
                        return err;
                }
-               if (snd_ctl_elem_info_is_tlv_readable(info) &&
-                   snd_ctl_elem_info_is_tlv_writable(info)) {
+               if (snd_ctl_elem_info_is_tlv_readable(info)) {
+                       err = add_tlv_comments(handle, id, info, comment);
+                       if (err < 0)
+                               return err;
+               }
+               if (snd_ctl_elem_info_is_tlv_readable(info)) {
                        unsigned int tlv[MAX_USER_TLV_SIZE];
                        err = snd_ctl_elem_tlv_read(handle, id, tlv, sizeof(tlv));
                        if (err >= 0) {
@@ -765,6 +809,56 @@ static int is_user_control(snd_config_t *conf)
        return 0;
 }
 
+/*
+ * get the item type from the given comment config
+ */
+static int get_comment_type(snd_config_t *n)
+{
+       const char *type;
+
+       if (snd_config_get_string(n, &type) < 0)
+               return -EINVAL;
+       if (strcmp(type, "BOOLEAN") == 0)
+               return SND_CTL_ELEM_TYPE_BOOLEAN;
+       else if (strcmp(type, "INTEGER") == 0)
+               return SND_CTL_ELEM_TYPE_INTEGER;
+       else if (strcmp(type, "ENUMERATED") == 0)
+               return SND_CTL_ELEM_TYPE_ENUMERATED;
+       else if (strcmp(type, "INTEGER64") == 0)
+               return SND_CTL_ELEM_TYPE_INTEGER;
+       else if (strcmp(type, "IEC958") == 0)
+               return SND_CTL_ELEM_TYPE_IEC958;
+       else
+               return -EINVAL;
+}
+
+/*
+ * get the value range from the given comment config
+ */
+static int get_comment_range(snd_config_t *n, int ctype,
+                            long *imin, long *imax, long *istep)
+{
+       const char *s;
+       int err;
+
+       if (snd_config_get_string(n, &s) < 0)
+               return -EINVAL;
+       switch (ctype) {
+       case SND_CTL_ELEM_TYPE_INTEGER:
+               err = sscanf(s, "%li - %li (step %li)", imin, imax, istep);
+               if (err != 3) {
+                       istep = 0;
+                       err = sscanf(s, "%li - %li", imin, imax);
+                       if (err != 2)
+                               return -EINVAL;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
 static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_config_t *conf)
 {
        snd_ctl_elem_id_t *id;
@@ -781,39 +875,20 @@ static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_co
        tlv = NULL;
        snd_config_for_each(i, next, conf) {
                snd_config_t *n = snd_config_iterator_entry(i);
-               const char *id, *type;
+               const char *id;
                if (snd_config_get_id(n, &id) < 0)
                        continue;
                if (strcmp(id, "type") == 0) {
-                       if ((err = snd_config_get_string(n, &type)) < 0)
-                               return -EINVAL;
-                       if (strcmp(type, "BOOLEAN") == 0)
-                               ctype = SND_CTL_ELEM_TYPE_BOOLEAN;
-                       else if (strcmp(type, "INTEGER") == 0)
-                               ctype = SND_CTL_ELEM_TYPE_INTEGER;
-                       else if (strcmp(type, "IEC958") == 0)
-                               ctype = SND_CTL_ELEM_TYPE_IEC958;
-                       else
-                               return -EINVAL;
+                       err = get_comment_type(n);
+                       if (err < 0)
+                               return err;
+                       ctype = err;
                        continue;
                }
                if (strcmp(id, "range") == 0) {
-                       const char *s;
-                       if ((err = snd_config_get_string(n, &s)) < 0)
-                               return -EINVAL;
-                       switch (ctype) {
-                       case SND_CTL_ELEM_TYPE_INTEGER:
-                               err = sscanf(s, "%li - %li (step %li)", &imin, &imax, &istep);
-                               if (err != 3) {
-                                       istep = 0;
-                                       err = sscanf(s, "%li - %li", &imin, &imax);
-                                       if (err != 2)
-                                               return -EINVAL;
-                               }
-                               break;
-                       default:
-                               return -EINVAL;
-                       }
+                       err = get_comment_range(n, ctype, &imin, &imax, &istep);
+                       if (err < 0)
+                               return err;
                        continue;
                }
                if (strcmp(id, "count") == 0) {
@@ -867,6 +942,137 @@ static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_co
        return snd_ctl_elem_info(handle, info);
 }
 
+/*
+ * look for a config node with the given item name
+ */
+static snd_config_t *search_comment_item(snd_config_t *conf, const char *name)
+{
+       snd_config_iterator_t i, next;
+       snd_config_for_each(i, next, conf) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               const char *id;
+               if (snd_config_get_id(n, &id) < 0)
+                       continue;
+               if (strcmp(id, name) == 0)
+                       return n;
+       }
+       return NULL;
+}
+
+/*
+ * check whether the config item has the same of compatible type
+ */
+static int check_comment_type(snd_config_t *conf, int type)
+{
+       snd_config_t *n = search_comment_item(conf, "type");
+       int ctype;
+
+       if (!n)
+               return 0; /* not defined */
+       ctype = get_comment_type(n);
+       if (ctype == type)
+               return 0;
+       if ((ctype == SND_CTL_ELEM_TYPE_BOOLEAN ||
+            ctype == SND_CTL_ELEM_TYPE_INTEGER ||
+            ctype == SND_CTL_ELEM_TYPE_INTEGER64 ||
+            ctype == SND_CTL_ELEM_TYPE_ENUMERATED) &&
+           (type == SND_CTL_ELEM_TYPE_BOOLEAN ||
+            type == SND_CTL_ELEM_TYPE_INTEGER ||
+            type == SND_CTL_ELEM_TYPE_INTEGER64 ||
+            type == SND_CTL_ELEM_TYPE_ENUMERATED))
+               return 0; /* OK, compatible */
+       return -EINVAL;
+}
+
+/*
+ * convert from an old value to a new value with the same dB level
+ */
+static int convert_to_new_db(snd_config_t *value, long omin, long omax,
+                            long nmin, long nmax,
+                            long odbmin, long odbmax,
+                            long ndbmin, long ndbmax)
+{
+       long val;
+       if (config_integer(value, &val) < 0)
+               return -EINVAL;
+       if (val < omin || val > omax)
+               return -EINVAL;
+       val = ((val - omin) * (odbmax - odbmin)) / (omax - omin) + odbmin;
+       if (val < ndbmin)
+               val = ndbmin;
+       else if (val > ndbmax)
+               val = ndbmax;
+       val = ((val - ndbmin) * (nmax - nmin)) / (ndbmax - ndbmin) + nmin;
+       return snd_config_set_integer(value, val);
+}
+
+/*
+ * compare the current value range with the old range in comments.
+ * also, if dB information is available, try to compare them.
+ * if any change occurs, try to keep the same dB level.
+ */
+static int check_comment_range(snd_ctl_t *handle, snd_config_t *conf,
+                              snd_ctl_elem_info_t *info, snd_config_t *value)
+{
+       snd_config_t *n;
+       long omin, omax, ostep;
+       long nmin, nmax;
+       long odbmin, odbmax;
+       long ndbmin, ndbmax;
+       snd_ctl_elem_id_t *id;
+
+       n = search_comment_item(conf, "range");
+       if (!n)
+               return 0;
+       if (get_comment_range(n, SND_CTL_ELEM_TYPE_INTEGER,
+                             &omin, &omax, &ostep) < 0)
+               return 0;
+       nmin = snd_ctl_elem_info_get_min(info);
+       nmax = snd_ctl_elem_info_get_max(info);
+       if (omin != nmin && omax != nmax) {
+               /* Hey, the range mismatches */
+               if (!force_restore)
+                       return -EINVAL;
+       }
+       if (omin >= omax || nmin >= nmax)
+               return 0; /* invalid values */
+
+       n = search_comment_item(conf, "dbmin");
+       if (!n)
+               return 0;
+       if (config_integer(n, &odbmin) < 0)
+               return 0;
+       n = search_comment_item(conf, "dbmax");
+       if (!n)
+               return 0;
+       if (config_integer(n, &odbmax) < 0)
+               return 0;
+       if (odbmin >= odbmax)
+               return 0; /* invalid values */
+       snd_ctl_elem_id_alloca(&id);
+       snd_ctl_elem_info_get_id(info, id);
+       if (snd_ctl_get_dB_range(handle, id, &ndbmin, &ndbmax) < 0)
+               return 0;
+       if (ndbmin >= ndbmax)
+               return 0; /* invalid values */
+       if (omin == nmin && omax == nmax &&
+           odbmin == ndbmin && odbmax == ndbmax)
+               return 0; /* OK, identical one */
+
+       /* Let's guess the current value from dB range */
+       if (snd_config_get_type(value) == SND_CONFIG_TYPE_COMPOUND) {
+               snd_config_iterator_t i, next;
+               snd_config_for_each(i, next, value) {
+                       snd_config_t *n = snd_config_iterator_entry(i);
+                       convert_to_new_db(n, omin, omax, nmin, nmax,
+                                         odbmin, odbmax, ndbmin, ndbmax);
+               }
+       } else
+               convert_to_new_db(value, omin, omax, nmin, nmax,
+                                 odbmin, odbmax, ndbmin, ndbmax);
+       return 0;
+}
+
 static int restore_config_value(snd_ctl_t *handle, snd_ctl_elem_info_t *info,
                                snd_ctl_elem_iface_t type,
                                snd_config_t *value,
@@ -1104,14 +1310,17 @@ static int set_control(snd_ctl_t *handle, snd_config_t *control)
                return -ENOENT;
        }
 
-#if 0
        if (comment) {
-               check_comment_type(comment, type);
-               if (type == SND_CTL_ELEM_TYPE_INTEGER ||
-                   type == SND_CTL_ELEM_TYPE_INTEGER64)
-                       check_comment_range(comment, info);
+               if (check_comment_type(comment, type) < 0)
+                       error("incompatible field type for control #%d", numid);
+               if (type == SND_CTL_ELEM_TYPE_INTEGER) {
+                       if (check_comment_range(handle, comment, info, value) < 0) {
+                               error("value range mismatch for control #%d",
+                                     numid);
+                               return -EINVAL;
+                       }
+               }
        }
-#endif
 
        if (!snd_ctl_elem_info_is_writable(info))
                return 0;
index 39c2bbf8f374962a48e91a0ae900a00a75cd835a..a160f20bdb6693dc5e3f57823cba0155eb4c3108 100644 (file)
@@ -27,7 +27,9 @@ AC_PROG_CC
 dnl AC_PROG_CXX
 AC_PROG_INSTALL
 AC_PROG_LN_S
-AM_PATH_ALSA(1.0.12)
+AM_PATH_ALSA(1.0.15)
+AC_CHECK_FUNC(snd_tlv_get_dB_range,,
+       AC_ERROR([No TLV support code in alsa-lib]))
 
 AC_ARG_ENABLE(alsamixer,
      [  --disable-alsamixer     Disable alsamixer compilation],