[PATCH 3/5] ALSA: usb-audio: Add Audient iD14 MkI mixer matrix
From: Will Porter
Date: Mon Oct 19 2026 - 19:04:11 EST
Mixer unit 60 mixes 14 inputs (Mic 1-2, Digi 1-8, DAW 1-4) into four
outputs (Main L/R, Cue L/R). GET RANGE reports -127 to 0 dB in 1 dB
steps, so the generic driver creates four "Analogue 4 Volume" controls
with values from 0 to 127. The crosspoints take -128 dB (mute) to +6 dB
in 1/256 dB steps. The reported range cannot show the gains above 0 dB
or the fine steps that Audient's iD application uses.
Add one control per crosspoint, "Mix <output> <input> Playback Volume".
The value is the device's s16 in 1/256 dB, from -128 dB (mute) to
+6 dB. The fine steps carry the pan law of Audient's iD application.
GET CUR works, so get reads the device. Hide the generic controls in
the MkI map. The driver writes nothing to the mixer at probe.
Test setup:
- iD14 MkI, firmware 1.1.1
- Bazzite 44, kernel 7.2.9-ogc3.1.fc44
- snd-usb-audio built out of tree from the v7.2.9 sound/usb sources
and the complete driver (this commit and the later ones)
Results:
- crosspoints read back after writes
- DAW 1/2 reach Main L/R and Cue L/R through them (listening test)
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Will Porter <mrwillporter@xxxxxxxxx>
---
sound/usb/mixer_audient_id.c | 124 +++++++++++++++++++++++++++++++++++
sound/usb/mixer_maps.c | 1 +
2 files changed, 125 insertions(+)
diff --git a/sound/usb/mixer_audient_id.c b/sound/usb/mixer_audient_id.c
index b4d1e1697..5b38590ce 100644
--- a/sound/usb/mixer_audient_id.c
+++ b/sound/usb/mixer_audient_id.c
@@ -43,6 +43,7 @@
#define ID14_FU_OUT 0x0a /* feature unit before the outputs */
#define ID14_ROUTE 0x33 /* routing XU: CN = destination */
#define ID14_MON 0x36 /* monitor controller XU */
+#define ID14_MIXER 0x3c /* mixer unit: 14 inputs x 4 outputs */
/* Control selectors */
#define ID14_ROUTE_SOURCE 0x06 /* u8: source of the destination */
@@ -60,6 +61,7 @@
* use 1 dB steps. The controls show the steps: 0 (-128 dB, mute) ... 128 (0 dB).
*/
#define ID_DB(db) ((db) * 256)
+#define ID_DB_MIN ID_DB(-128) /* 0x8000: mute */
#define ID_VOL_MAX 128 /* control value for 0 dB */
enum id_kind {
@@ -392,10 +394,125 @@ static int id_add_ctl(struct usb_mixer_interface *mixer, const struct id_ctl *c)
return snd_usb_mixer_add_list(&e->list, kctl, false, true);
}
+/*
+ * iD14 MkI mixer crosspoints: CS 1, CN = input * 4 + output, s16 in
+ * 1/256 dB. The control value is the raw s16, -128 dB (mute) ... +6 dB;
+ * the iD application's pan law needs the fine steps. GET CUR works; no
+ * probe writes.
+ */
+#define ID14_MIX_OUTPUTS 4
+#define ID14_MIX_MAX ID_DB(6)
+
+static const char *const id14_mix_inputs[] = {
+ "Mic 1", "Mic 2",
+ "Digi 1", "Digi 2", "Digi 3", "Digi 4", "Digi 5", "Digi 6", "Digi 7", "Digi 8",
+ "DAW 1", "DAW 2", "DAW 3", "DAW 4",
+};
+
+static const char *const id14_mix_outputs[ID14_MIX_OUTPUTS] = {
+ "Main L", "Main R", "Cue L", "Cue R",
+};
+
+static const DECLARE_TLV_DB_MINMAX_MUTE(id14_mix_tlv, -12800, 600);
+
+static int id14_mix_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = ID_DB_MIN;
+ uinfo->value.integer.max = ID14_MIX_MAX;
+ return 0;
+}
+
+struct id14_mix {
+ struct usb_mixer_elem_list list; /* first: snd_usb_mixer_elem_free */
+ u8 cn;
+};
+
+static int id14_mix_read(struct id14_mix *m, long *val)
+{
+ u8 buf[2];
+ int err;
+
+ err = id_xfer(m->list.mixer, false, ID14_MIXER, UAC2_MU_MIXER, m->cn, buf, sizeof(buf));
+ if (err < 0)
+ return err;
+ *val = clamp_t(long, (s16)get_unaligned_le16(buf), ID_DB_MIN, ID14_MIX_MAX);
+ return 0;
+}
+
+static int id14_mix_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+ struct id14_mix *m = snd_kcontrol_chip(kctl);
+ long val;
+ int err;
+
+ err = id14_mix_read(m, &val);
+ if (err < 0)
+ return err;
+ v->value.integer.value[0] = val;
+ return 0;
+}
+
+static int id14_mix_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+ struct id14_mix *m = snd_kcontrol_chip(kctl);
+ long cur, val = v->value.integer.value[0];
+ u8 buf[2];
+ int err;
+
+ if (val < ID_DB_MIN || val > ID14_MIX_MAX)
+ return -EINVAL;
+ CLASS(snd_usb_lock, pm)(m->list.mixer->chip);
+ if (pm.err < 0)
+ return pm.err;
+ err = id14_mix_read(m, &cur);
+ if (err < 0 || cur == val)
+ return err;
+ put_unaligned_le16(val, buf);
+ err = id_xfer(m->list.mixer, true, ID14_MIXER, UAC2_MU_MIXER, m->cn, buf, sizeof(buf));
+ return err < 0 ? err : 1;
+}
+
+static int id14_mix_add_ctl(struct usb_mixer_interface *mixer, unsigned int in,
+ unsigned int out)
+{
+ struct snd_kcontrol_new knew = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = id14_mix_info,
+ .get = id14_mix_get,
+ .put = id14_mix_put,
+ .tlv.p = id14_mix_tlv,
+ };
+ struct snd_kcontrol *kctl;
+ struct id14_mix *m;
+
+ m = kzalloc_obj(*m);
+ if (!m)
+ return -ENOMEM;
+ m->list.mixer = mixer;
+ m->list.id = ID14_MIXER;
+ m->cn = in * ID14_MIX_OUTPUTS + out;
+
+ kctl = snd_ctl_new1(&knew, m);
+ if (!kctl) {
+ kfree(m);
+ return -ENOMEM;
+ }
+ snprintf(kctl->id.name, sizeof(kctl->id.name), "Mix %s %s Playback Volume",
+ id14_mix_outputs[out], id14_mix_inputs[in]);
+ kctl->private_free = snd_usb_mixer_elem_free;
+ return snd_usb_mixer_add_list(&m->list, kctl, false, true);
+}
+
/* called from mixer_quirks.c */
int snd_audient_id14_mk1_controls_create(struct usb_mixer_interface *mixer)
{
const struct id_ctl *c;
+ unsigned int in, out;
int err;
for (c = id14_mk1_ctls; c->name; c++) {
@@ -403,5 +520,12 @@ int snd_audient_id14_mk1_controls_create(struct usb_mixer_interface *mixer)
if (err < 0)
return err;
}
+ for (in = 0; in < ARRAY_SIZE(id14_mix_inputs); in++) {
+ for (out = 0; out < ID14_MIX_OUTPUTS; out++) {
+ err = id14_mix_add_ctl(mixer, in, out);
+ if (err < 0)
+ return err;
+ }
+ }
return 0;
}
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
index 7795a5fbe..e147c1a48 100644
--- a/sound/usb/mixer_maps.c
+++ b/sound/usb/mixer_maps.c
@@ -529,6 +529,7 @@ static const struct usbmix_name_map audient_id14_map[] = {
/* Audient iD14 MkI: model-specific control map. */
static const struct usbmix_name_map audient_id14_mk1_map[] = {
{ 12, "Monitor Mix Playback" }, /* FU, partial coverage */
+ { 60, NULL }, /* MU, see mixer_audient_id.c */
{}
};
--
2.47.3