]> git.alsa-project.org Git - alsa-lib.git/commitdiff
control: remap - add sync feature
authorJaroslav Kysela <perex@perex.cz>
Thu, 20 Mar 2025 10:24:32 +0000 (11:24 +0100)
committerJaroslav Kysela <perex@perex.cz>
Fri, 21 Mar 2025 09:39:09 +0000 (10:39 +0100)
For UCM, it may be required to sync multiple controls. The logic
is really simple - last write to any control in the group wins.

Link: https://github.com/alsa-project/alsa-ucm-conf/pull/410
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
src/control/control_remap.c

index eaef390b39d687dc6fe75b4aa24855e1fd55d637..05c0c88a70613e619c2bfc91aa4aaf74a234b5cb 100644 (file)
@@ -87,6 +87,11 @@ typedef struct {
        unsigned int event_mask;
 } snd_ctl_map_event_t;
 
+typedef struct {
+       size_t control_items;
+       snd_ctl_elem_id_t *control_ids;
+} snd_ctl_sync_t;
+
 typedef struct {
        snd_ctl_t *child;
        int numid_remap_active;
@@ -105,6 +110,10 @@ typedef struct {
        size_t map_alloc;
        snd_ctl_map_t *map;
 
+       size_t sync_items;
+       size_t sync_alloc;
+       snd_ctl_sync_t *sync;
+
        size_t event_items;
        size_t event_queue_head;
        size_t event_queue_tail;
@@ -183,6 +192,23 @@ static snd_ctl_numid_t *remap_find_numid_child(snd_ctl_remap_t *priv, unsigned i
        return remap_numid_child_new(priv, numid_child);
 }
 
+static void remap_forget_numid_child(snd_ctl_remap_t *priv, unsigned int numid_child)
+{
+       snd_ctl_numid_t *numid;
+       size_t index;
+
+       if (!priv->numid_remap_active)
+               return;
+       numid = priv->numid;
+       for (index = 0; index < priv->numid_items; index++) {
+               if (numid[index].numid_child != numid_child)
+                       continue;
+               memcpy(&priv->numid[index], &priv->numid[index + 1],
+                               (priv->numid_items - 1 - index) * sizeof(*numid));
+               priv->numid_items++;
+       }
+}
+
 static snd_ctl_remap_id_t *remap_find_id_child(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id)
 {
        size_t count;
@@ -302,11 +328,63 @@ static int remap_id_to_app(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id, snd_ctl
        return err;
 }
 
+static snd_ctl_sync_t *remap_find_sync_numid(snd_ctl_remap_t *priv, unsigned int numid)
+{
+       size_t count, index2;
+       snd_ctl_sync_t *sync;
+
+       if (numid == 0)
+               return NULL;
+       sync = priv->sync;
+       for (count = priv->sync_items; count > 0; count--, sync++)
+               for (index2 = 0; index2 < sync->control_items; index2++)
+                       if (numid == sync->control_ids[index2].numid)
+                               return sync;
+       return NULL;
+}
+
+static snd_ctl_sync_t *remap_find_sync_id(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id)
+{
+       size_t count, index2;
+       snd_ctl_sync_t *sync;
+
+       if (id->numid > 0)
+               return remap_find_sync_numid(priv, id->numid);
+       sync = priv->sync;
+       for (count = priv->sync_items; count > 0; count--, sync++)
+               for (index2 = 0; index2 < sync->control_items; index2++)
+                       if (snd_ctl_elem_id_compare_set(id, &sync->control_ids[index2]) == 0)
+                               return sync;
+       return NULL;
+}
+
+static void remap_update_sync_id(snd_ctl_remap_t *priv, snd_ctl_elem_id_t *id)
+{
+       size_t count, index2;
+       snd_ctl_sync_t *sync;
+
+       if (id->numid == 0)
+               return;
+       sync = priv->sync;
+       for (count = priv->sync_items; count > 0; count--, sync++)
+               for (index2 = 0; index2 < sync->control_items; index2++) {
+                       if (snd_ctl_elem_id_compare_set(id, &sync->control_ids[index2]) == 0) {
+                               sync->control_ids[index2].numid = id->numid;
+                               break;
+                       }
+               }
+}
+
 static void remap_free(snd_ctl_remap_t *priv)
 {
        size_t idx1, idx2;
+       snd_ctl_sync_t *sync;
        snd_ctl_map_t *map;
 
+       for (idx1 = 0; idx1 < priv->sync_items; idx1++) {
+               sync = &priv->sync[idx1];
+               free(sync->control_ids);
+       }
        for (idx1 = 0; idx1 < priv->map_items; idx1++) {
                map = &priv->map[idx1];
                for (idx2 = 0; idx2 < map->controls_items; idx2++)
@@ -314,6 +392,7 @@ static void remap_free(snd_ctl_remap_t *priv)
                free(map->controls);
        }
        free(priv->event_queue);
+       free(priv->sync);
        free(priv->map);
        free(priv->remap);
        free(priv->numid);
@@ -484,6 +563,8 @@ static int snd_ctl_remap_elem_info(snd_ctl_t *ctl, snd_ctl_elem_info_t *info)
        if (err < 0)
                return err;
        err = snd_ctl_elem_info(priv->child, info);
+       if (err >= 0 && priv->sync_items > 0)
+               remap_update_sync_id(priv, &info->id);
        return remap_id_to_app(priv, &info->id, rid, err);
 }
 
@@ -644,6 +725,29 @@ static int remap_map_elem_write(snd_ctl_remap_t *priv, snd_ctl_elem_value_t *con
        return 0;
 }
 
+static int remap_sync_elem_write(snd_ctl_remap_t *priv, snd_ctl_elem_value_t *control)
+{
+       snd_ctl_sync_t *sync;
+       snd_ctl_elem_value_t control2;
+       size_t item;
+       int err;
+
+       sync = remap_find_sync_id(priv, &control->id);
+       if (sync == NULL)
+               return -EREMAPNOTFOUND;
+       debug_id(&control->id, "%s\n", __func__);
+       control2 = *control;
+       for (item = 0; item < sync->control_items; item++) {
+               control2.id = sync->control_ids[item];
+               debug_id(&control2.id, "%s sync[%zd]\n", __func__, item);
+               /* TODO: it's a blind write - no checks if the values are in range for all controls */
+               err = snd_ctl_elem_write(priv->child, &control2);
+               if (err < 0)
+                       return err;
+       }
+       return remap_id_to_app(priv, &control->id, NULL, 0);
+}
+
 static int snd_ctl_remap_elem_write(snd_ctl_t *ctl, snd_ctl_elem_value_t *control)
 {
        snd_ctl_remap_t *priv = ctl->private_data;
@@ -652,6 +756,9 @@ static int snd_ctl_remap_elem_write(snd_ctl_t *ctl, snd_ctl_elem_value_t *contro
 
        debug_id(&control->id, "%s\n", __func__);
        err = remap_map_elem_write(priv, control);
+       if (err != -EREMAPNOTFOUND)
+               return err;
+       err = remap_sync_elem_write(priv, control);
        if (err != -EREMAPNOTFOUND)
                return err;
        err = remap_id_to_child(priv, &control->id, &rid);
@@ -897,12 +1004,55 @@ static void remap_event_for_all_map_controls(snd_ctl_remap_t *priv,
        }
 }
 
+static void remap_event_for_all_sync_controls(snd_ctl_remap_t *priv,
+                                             snd_ctl_elem_id_t *id,
+                                             unsigned int event_mask)
+{
+       size_t count, index;
+       snd_ctl_sync_t *sync;
+       snd_ctl_numid_t *numid;
+       snd_ctl_elem_id_t *sid;
+       int changed = 0;
+
+       if (event_mask == SNDRV_CTL_EVENT_MASK_REMOVE)
+               return;
+       sync = priv->sync;
+       for (count = priv->sync_items; count > 0; count--, sync++) {
+               changed = 0;
+               for (index = 0; index < sync->control_items; index++) {
+                       sid = &sync->control_ids[index];
+                       if (sid->numid == 0) {
+                               if (snd_ctl_elem_id_compare_set(id, sid))
+                                       continue;
+                               sid->numid = id->numid;
+                       }
+                       if (id->numid != sid->numid)
+                               continue;
+                       debug_id(sid, "%s found (all)\n", __func__);
+                       changed = 1;
+                       break;
+               }
+               if (!changed)
+                       continue;
+               for (index = 0; index < sync->control_items; index++) {
+                       sid = &sync->control_ids[index];
+                       /* skip double updates */
+                       if (sid->numid == id->numid)
+                               continue;
+                       numid = remap_find_numid_child(priv, sid->numid);
+                       if (numid)
+                               event_add(priv, sid, numid->numid_app, event_mask);
+               }
+       }
+}
+
 static int snd_ctl_remap_read(snd_ctl_t *ctl, snd_ctl_event_t *event)
 {
        snd_ctl_remap_t *priv = ctl->private_data;
        snd_ctl_remap_id_t *rid;
        snd_ctl_numid_t *numid;
        snd_ctl_map_event_t *map_event;
+       unsigned int numid_child;
        int err;
 
        if (priv->event_queue_head != priv->event_queue_tail) {
@@ -923,11 +1073,13 @@ static int snd_ctl_remap_read(snd_ctl_t *ctl, snd_ctl_event_t *event)
            (event->data.elem.mask & (SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO |
                                      SNDRV_CTL_EVENT_MASK_ADD | SNDRV_CTL_EVENT_MASK_TLV)) != 0) {
                debug_id(&event->data.elem.id, "%s event mask 0x%x\n", __func__, event->data.elem.mask);
+               numid_child = event->data.elem.id.numid;
                remap_event_for_all_map_controls(priv, &event->data.elem.id, event->data.elem.mask);
+               remap_event_for_all_sync_controls(priv, &event->data.elem.id, event->data.elem.mask);
                rid = remap_find_id_child(priv, &event->data.elem.id);
                if (rid) {
                        if (rid->id_child.numid == 0) {
-                               numid = remap_find_numid_child(priv, event->data.elem.id.numid);
+                               numid = remap_find_numid_child(priv, numid_child);
                                if (numid == NULL)
                                        return -EIO;
                                rid->id_child.numid = numid->numid_child;
@@ -935,11 +1087,13 @@ static int snd_ctl_remap_read(snd_ctl_t *ctl, snd_ctl_event_t *event)
                        }
                        event->data.elem.id = rid->id_app;
                } else {
-                       numid = remap_find_numid_child(priv, event->data.elem.id.numid);
+                       numid = remap_find_numid_child(priv, numid_child);
                        if (numid == NULL)
                                return -EIO;
                        event->data.elem.id.numid = numid->numid_app;
                }
+               if (event->data.elem.mask == SNDRV_CTL_EVENT_MASK_REMOVE)
+                       remap_forget_numid_child(priv, numid_child);
        }
        return err;
 }
@@ -1240,12 +1394,84 @@ static int parse_map(snd_ctl_remap_t *priv, snd_config_t *conf)
        return 0;
 }
 
+static snd_ctl_sync_t *alloc_sync(snd_ctl_remap_t *priv)
+{
+       snd_ctl_sync_t *sync;
+
+       if (priv->sync_alloc == priv->sync_items) {
+               sync = realloc(priv->sync, (priv->sync_alloc + 16) * sizeof(*sync));
+               if (sync == NULL)
+                       return NULL;
+               memset(sync + priv->sync_alloc, 0, sizeof(*sync) * 16);
+               priv->sync_alloc += 16;
+               priv->sync = sync;
+       }
+       return &priv->sync[priv->sync_items++];
+}
+
+static int parse_sync1(snd_ctl_remap_t *priv, unsigned int count, snd_config_t *conf)
+{
+       snd_config_iterator_t i, next;
+       snd_ctl_elem_id_t *eid;
+       snd_ctl_sync_t *sync;
+       const char *str;
+       int err, index = 0;
+
+       sync = alloc_sync(priv);
+       if (sync == NULL)
+               return -ENOMEM;
+       sync->control_ids = calloc(count, sizeof(sync->control_ids[0]));
+       if (sync->control_ids == NULL)
+               return -ENOMEM;
+       snd_config_for_each(i, next, conf) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               if (snd_config_get_string(n, &str) < 0) {
+                       SNDERR("strings are expected in sync array");
+                       return -EINVAL;
+               }
+               eid = &sync->control_ids[index];
+               snd_ctl_elem_id_clear(eid);
+               err = snd_ctl_ascii_elem_id_parse(eid, str);
+               if (err < 0) {
+                       SNDERR("unable to parse control id '%s'!", str);
+                       return -EINVAL;
+               }
+               sync->control_items++;
+               index++;
+       }
+
+       return 0;
+}
+
+static int parse_sync(snd_ctl_remap_t *priv, snd_config_t *conf)
+{
+       snd_config_iterator_t i, next;
+       int count, err;
+
+       if (conf == NULL)
+               return 0;
+       snd_config_for_each(i, next, conf) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               count = snd_config_is_array(n);
+               if (count <= 0) {
+                       SNDERR("Array is expected for sync!");
+                       return -EINVAL;
+               }
+               err = parse_sync1(priv, count, n);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
 /**
- * \brief Creates a new remap & map control handle
+ * \brief Creates a new remap/map/sync control handle
  * \param handlep Returns created control handle
  * \param name Name of control device
  * \param remap Remap configuration
  * \param map Map configuration
+ * \param sync Sync configuration
  * \param child child configuration root
  * \param mode Control handle mode
  * \retval zero on success otherwise a negative error code
@@ -1254,14 +1480,15 @@ static int parse_map(snd_ctl_remap_t *priv, snd_config_t *conf)
  *          changed in future.
  */
 int snd_ctl_remap_open(snd_ctl_t **handlep, const char *name, snd_config_t *remap,
-                      snd_config_t *map, snd_ctl_t *child, int mode)
+                      snd_config_t *map, snd_config_t *sync, snd_ctl_t *child, int mode)
 {
        snd_ctl_remap_t *priv;
        snd_ctl_t *ctl;
+       size_t index;
        int result, err;
 
        /* no-op, remove the plugin */
-       if (!remap && !map)
+       if (!remap && !map && !sync)
                goto _noop;
 
        priv = calloc(1, sizeof(*priv));
@@ -1280,8 +1507,14 @@ int snd_ctl_remap_open(snd_ctl_t **handlep, const char *name, snd_config_t *rema
                goto _err;
        }
 
+       err = parse_sync(priv, sync);
+       if (err < 0) {
+               result = err;
+               goto _err;
+       }
+
        /* no-op check, remove the plugin */
-       if (priv->map_items == 0 && priv->remap_items == 0) {
+       if (priv->map_items == 0 && priv->remap_items == 0 && priv->sync_items == 0) {
                remap_free(priv);
  _noop:
                free(child->name);
@@ -1293,13 +1526,15 @@ int snd_ctl_remap_open(snd_ctl_t **handlep, const char *name, snd_config_t *rema
        }
 
        priv->event_items = priv->map_items;
+       for (index = 0; index < priv->sync_items; index++)
+               priv->event_items += priv->sync[index].control_items;
        priv->event_queue = calloc(priv->event_items, sizeof(priv->event_queue[0]));
        if (priv->event_queue == NULL) {
                result = -ENOMEM;
                goto _err;
        }
 
-       priv->numid_remap_active = priv->map_items > 0;
+       priv->numid_remap_active = priv->map_items > 0 || priv->sync_items;
 
        priv->child = child;
        err = snd_ctl_new(&ctl, SND_CTL_TYPE_REMAP, name, mode);
@@ -1371,6 +1606,13 @@ ctl.name {
                        SRC_ID6_STR.vindex.1 [ 0 1 ] # source channels 0+1 to merged channel 1
                }
        }
+       sync {
+               # synchronize multiple controls without any translations
+               sample_group_1 [
+                       SYNC_ID1_STR
+                       SYNC_ID2_STR
+               ]
+       }
 }
 \endcode
 
@@ -1401,6 +1643,7 @@ int _snd_ctl_remap_open(snd_ctl_t **handlep, char *name, snd_config_t *root, snd
        snd_config_t *child = NULL;
        snd_config_t *remap = NULL;
        snd_config_t *map = NULL;
+       snd_config_t *sync = NULL;
        snd_ctl_t *cctl;
        int err;
 
@@ -1419,6 +1662,10 @@ int _snd_ctl_remap_open(snd_ctl_t **handlep, char *name, snd_config_t *root, snd
                        map = n;
                        continue;
                }
+               if (strcmp(id, "sync") == 0) {
+                       sync = n;
+                       continue;
+               }
                if (strcmp(id, "child") == 0) {
                        child = n;
                        continue;
@@ -1433,7 +1680,7 @@ int _snd_ctl_remap_open(snd_ctl_t **handlep, char *name, snd_config_t *root, snd
        err = _snd_ctl_open_child(&cctl, root, child, mode, conf);
        if (err < 0)
                return err;
-       err = snd_ctl_remap_open(handlep, name, remap, map, cctl, mode);
+       err = snd_ctl_remap_open(handlep, name, remap, map, sync, cctl, mode);
        if (err < 0)
                snd_ctl_close(cctl);
        return err;