Index | Thread | Search

From:
Alexandre Ratchov <alex@caoua.org>
Subject:
sndiod: per-program (instead of per-client) level control
To:
tech@openbsd.org
Date:
Mon, 16 Jun 2025 20:20:15 +0200

Download raw body.

Thread
This diff is to adjust the volume of all instances of the same program
with a single control. Example, instead of:

firefox0.level
firefox1.level
firefox2.level
...

there will be single "firefox.level" control. There will be no way to
control individual tracks anymore, but that will not be a big loss
beacause there's currently no way to know in advance which sound
corresponds to which firefox instance.

There's a nice side effect:

Currently if a web browser has too many tabs/windows, it may consume
all the controls, preventing other programs from using sndiod. Now,
the number of connections that a single program can make can be
increased (the diff cranks it to 32) which fixes the problem in most
cases.

OK?

Index: dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
diff -u -p -r1.122 dev.c
--- dev.c	16 Jun 2025 06:19:29 -0000	1.122
+++ dev.c	16 Jun 2025 18:17:57 -0000
@@ -78,7 +78,6 @@ unsigned int dev_sndnum = 0;
 
 struct ctlslot ctlslot_array[DEV_NCTLSLOT];
 struct slot slot_array[DEV_NSLOT];
-unsigned int slot_serial;		/* for slot allocation */
 
 /*
  * we support/need a single MTC clock source only
@@ -88,27 +87,6 @@ struct mtc mtc_array[1] = {
 };
 
 void
-slot_array_init(void)
-{
-	unsigned int i;
-
-	for (i = 0; i < DEV_NSLOT; i++) {
-		slot_array[i].unit = i;
-		slot_array[i].ops = NULL;
-		slot_array[i].vol = MIDI_MAXCTL;
-		slot_array[i].opt = NULL;
-		slot_array[i].serial = slot_serial++;
-		memset(slot_array[i].name, 0, SLOT_NAMEMAX);
-	}
-}
-
-void
-slot_ctlname(struct slot *s, char *name, size_t size)
-{
-	snprintf(name, size, "slot%zu", s - slot_array);
-}
-
-void
 zomb_onmove(void *arg)
 {
 }
@@ -298,16 +276,18 @@ mtc_midi_full(struct mtc *mtc)
 
 /*
  * send a volume change MIDI message
+ *
+ * XXX: rename to opt_midi_vol() and move to opt.c
  */
 void
-dev_midi_vol(struct dev *d, struct slot *s)
+dev_midi_vol(struct opt *o, struct app *a)
 {
 	unsigned char msg[3];
 
-	msg[0] = MIDI_CTL | (s - slot_array);
+	msg[0] = MIDI_CTL | (a - o->app_array);
 	msg[1] = MIDI_CTL_VOL;
-	msg[2] = s->vol;
-	dev_midi_send(d, msg, 3);
+	msg[2] = a->vol;
+	midi_send(o->midi, msg, sizeof(msg));
 }
 
 /*
@@ -352,9 +332,11 @@ dev_midi_master(struct dev *d)
 
 /*
  * send a sndiod-specific slot description MIDI message
+ *
+ * XXX: rename to opt_midi_appdesc() and move to opt.c
  */
 void
-dev_midi_slotdesc(struct dev *d, struct slot *s)
+dev_midi_slotdesc(struct opt *o, struct app *a)
 {
 	struct sysex x;
 
@@ -364,26 +346,26 @@ dev_midi_slotdesc(struct dev *d, struct 
 	x.dev = SYSEX_DEV_ANY;
 	x.id0 = SYSEX_AUCAT;
 	x.id1 = SYSEX_AUCAT_SLOTDESC;
-	if (s->opt != NULL && s->opt->dev == d)
-		slot_ctlname(s, (char *)x.u.slotdesc.name, SYSEX_NAMELEN);
-	x.u.slotdesc.chan = (s - slot_array);
+	strlcpy(x.u.slotdesc.name, a->name, SYSEX_NAMELEN);
+	x.u.slotdesc.chan = (a - o->app_array);
 	x.u.slotdesc.end = SYSEX_END;
-	dev_midi_send(d, (unsigned char *)&x, SYSEX_SIZE(slotdesc));
+	midi_send(o->midi, (unsigned char *)&x, SYSEX_SIZE(slotdesc));
 }
 
+/*
+ * XXX: rename to opt_midi_dump() and move to opt.c
+ */
 void
-dev_midi_dump(struct dev *d)
+dev_midi_dump(struct opt *o)
 {
 	struct sysex x;
-	struct slot *s;
+	struct app *a;
 	int i;
 
-	dev_midi_master(d);
-	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
-		if (s->opt != NULL && s->opt->dev != d)
-			continue;
-		dev_midi_slotdesc(d, s);
-		dev_midi_vol(d, s);
+	dev_midi_master(o->dev);
+	for (i = 0, a = o->app_array; i < OPT_NAPP; i++, a++) {
+		dev_midi_slotdesc(o, a);
+		dev_midi_vol(o, a);
 	}
 	x.start = SYSEX_START;
 	x.type = SYSEX_TYPE_EDU;
@@ -391,7 +373,7 @@ dev_midi_dump(struct dev *d)
 	x.id0 = SYSEX_AUCAT;
 	x.id1 = SYSEX_AUCAT_DUMPEND;
 	x.u.dumpend.end = SYSEX_END;
-	dev_midi_send(d, (unsigned char *)&x, SYSEX_SIZE(dumpend));
+	midi_send(o->midi, (unsigned char *)&x, SYSEX_SIZE(dumpend));
 }
 
 int
@@ -1523,98 +1505,34 @@ struct slot *
 slot_new(struct opt *opt, unsigned int id, char *who,
     struct slotops *ops, void *arg, int mode)
 {
-	char *p;
-	char name[SLOT_NAMEMAX];
-	char ctl_name[CTL_NAMEMAX];
-	unsigned int i, ser, bestser, bestidx;
-	struct slot *unit[DEV_NSLOT];
+	struct app *a;
 	struct slot *s;
+	int i;
 
-	/*
-	 * create a ``valid'' control name (lowcase, remove [^a-z], truncate)
-	 */
-	for (i = 0, p = who; ; p++) {
-		if (i == SLOT_NAMEMAX - 1 || *p == '\0') {
-			name[i] = '\0';
-			break;
-		} else if (*p >= 'A' && *p <= 'Z') {
-			name[i++] = *p + 'a' - 'A';
-		} else if (*p >= 'a' && *p <= 'z')
-			name[i++] = *p;
-	}
-	if (i == 0)
-		strlcpy(name, "noname", SLOT_NAMEMAX);
-
-	/*
-	 * build a unit-to-slot map for this name
-	 */
-	for (i = 0; i < DEV_NSLOT; i++)
-		unit[i] = NULL;
-	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
-		if (strcmp(s->name, name) == 0)
-			unit[s->unit] = s;
-	}
-
-	/*
-	 * find the free slot with the least unit number and same id
-	 */
-	for (i = 0; i < DEV_NSLOT; i++) {
-		s = unit[i];
-		if (s != NULL && s->ops == NULL && s->id == id)
-			goto found;
-	}
-
-	/*
-	 * find the free slot with the least unit number
-	 */
-	for (i = 0; i < DEV_NSLOT; i++) {
-		s = unit[i];
-		if (s != NULL && s->ops == NULL) {
-			s->id = id;
-			goto found;
-		}
-	}
+	a = opt_mkapp(opt, who);
+	if (a == NULL)
+		return NULL;
 
 	/*
-	 * couldn't find a matching slot, pick oldest free slot
-	 * and set its name/unit
+	 * find a free slot and assign it the smallest possible unit number
 	 */
-	bestser = 0;
-	bestidx = DEV_NSLOT;
-	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
-		if (s->ops != NULL)
-			continue;
-		ser = slot_serial - s->serial;
-		if (ser > bestser) {
-			bestser = ser;
-			bestidx = i;
+	i = 0;
+	s = slot_array;
+	while (1) {
+		if (i == DEV_NSLOT) {
+			logx(1, "%s: too many connections", a->name);
+			return NULL;
 		}
+		if (s->ops == NULL)
+			break;
+		i++;
+		s++;
 	}
 
-	if (bestidx == DEV_NSLOT) {
-		logx(1, "%s: out of sub-device slots", name);
-		return NULL;
-	}
-
-	s = slot_array + bestidx;
-	ctl_del(CTL_SLOT_LEVEL, s, NULL);
-	s->vol = MIDI_MAXCTL;
-	strlcpy(s->name, name, SLOT_NAMEMAX);
-	s->serial = slot_serial++;
-	for (i = 0; unit[i] != NULL; i++)
-		; /* nothing */
-	s->unit = i;
-	s->id = id;
-	s->opt = opt;
-	slot_ctlname(s, ctl_name, CTL_NAMEMAX);
-	ctl_new(CTL_SLOT_LEVEL, s, NULL,
-	    CTL_NUM, "", "app", ctl_name, -1, "level",
-	    NULL, -1, 127, s->vol);
-
-found:
-	/* open device, this may change opt's device */
 	if (!opt_ref(opt))
 		return NULL;
+
+	s->app = a;
 	s->opt = opt;
 	s->ops = ops;
 	s->arg = arg;
@@ -1629,10 +1547,8 @@ found:
 	s->appbufsz = s->opt->dev->bufsz;
 	s->round = s->opt->dev->round;
 	s->rate = s->opt->dev->rate;
-	dev_midi_slotdesc(s->opt->dev, s);
-	dev_midi_vol(s->opt->dev, s);
 #ifdef DEBUG
-	logx(3, "slot%zu: %s/%s%u", s - slot_array, s->opt->name, s->name, s->unit);
+	logx(3, "slot%zu: %s/%s", s - slot_array, s->opt->name, s->app->name);
 #endif
 	return s;
 }
@@ -1660,66 +1576,21 @@ slot_del(struct slot *s)
 }
 
 /*
- * change the slot play volume; called either by the slot or by MIDI
+ * change the slot play volume; called by the client
  */
 void
 slot_setvol(struct slot *s, unsigned int vol)
 {
+	struct opt *o = s->opt;
+	struct app *a = s->app;
+
 #ifdef DEBUG
 	logx(3, "slot%zu: setting volume %u", s - slot_array, vol);
 #endif
-	s->vol = vol;
-	s->mix.vol = MIDI_TO_ADATA(s->vol);
-}
-
-/*
- * set device for this slot
- */
-void
-slot_setopt(struct slot *s, struct opt *o)
-{
-	struct opt *t;
-	struct dev *odev, *ndev;
-	struct ctl *c;
-
-	if (s->opt == NULL || s->opt == o)
-		return;
-
-	logx(2, "slot%zu: moving to opt %s", s - slot_array, o->name);
-
-	odev = s->opt->dev;
-	if (s->ops != NULL) {
-		ndev = opt_ref(o);
-		if (ndev == NULL)
-			return;
-
-		if (!dev_iscompat(odev, ndev)) {
-			opt_unref(o);
-			return;
-		}
-	}
-
-	if (s->pstate == SLOT_RUN || s->pstate == SLOT_STOP)
-		slot_detach(s);
-
-	t = s->opt;
-	s->opt = o;
-
-	c = ctl_find(CTL_SLOT_LEVEL, s, NULL);
-	ctl_update(c);
-
-	if (o->dev != t->dev) {
-		dev_midi_slotdesc(odev, s);
-		dev_midi_slotdesc(ndev, s);
-		dev_midi_vol(ndev, s);
-	}
-
-	if (s->pstate == SLOT_RUN || s->pstate == SLOT_STOP)
-		slot_attach(s);
-
-	if (s->ops != NULL) {
-		opt_unref(t);
-		return;
+	if (a->vol != vol) {
+		opt_appvol(o, a, vol);
+		dev_midi_vol(o, a);
+		ctl_onval(CTL_APP_LEVEL, o, a, vol);
 	}
 }
 
@@ -1775,7 +1646,7 @@ slot_attach(struct slot *s)
 	s->next = d->slot_list;
 	d->slot_list = s;
 	if (s->mode & MODE_PLAY) {
-		s->mix.vol = MIDI_TO_ADATA(s->vol);
+		s->mix.vol = MIDI_TO_ADATA(s->app->vol);
 		dev_mix_adjvol(d);
 	}
 }
@@ -2073,8 +1944,8 @@ ctlslot_visible(struct ctlslot *s, struc
 		return (s->opt->dev == c->u.any.arg0);
 	case CTL_OPT_DEV:
 		return (s->opt == c->u.any.arg0);
-	case CTL_SLOT_LEVEL:
-		return (s->opt->dev == c->u.slot_level.slot->opt->dev);
+	case CTL_APP_LEVEL:
+		return (s->opt == c->u.app_level.opt);
 	default:
 		return 0;
 	}
@@ -2146,9 +2017,9 @@ ctl_scope_fmt(char *buf, size_t size, st
 	case CTL_DEV_MASTER:
 		return snprintf(buf, size, "dev_master:%s",
 		    c->u.dev_master.dev->name);
-	case CTL_SLOT_LEVEL:
-		return snprintf(buf, size, "slot_level:%s%u",
-		    c->u.slot_level.slot->name, c->u.slot_level.slot->unit);
+	case CTL_APP_LEVEL:
+		return snprintf(buf, size, "app_level:%s/%s",
+		    c->u.app_level.opt->name, c->u.app_level.app->name);
 	case CTL_OPT_DEV:
 		return snprintf(buf, size, "opt_dev:%s/%s",
 		    c->u.opt_dev.opt->name, c->u.opt_dev.dev->name);
@@ -2208,10 +2079,9 @@ ctl_setval(struct ctl *c, int val)
 		c->val_mask = ~0U;
 		c->curval = val;
 		return 1;
-	case CTL_SLOT_LEVEL:
-		slot_setvol(c->u.slot_level.slot, val);
-		// XXX change dev_midi_vol() into slot_midi_vol()
-		dev_midi_vol(c->u.slot_level.slot->opt->dev, c->u.slot_level.slot);
+	case CTL_APP_LEVEL:
+		opt_appvol(c->u.app_level.opt, c->u.app_level.app, val);
+		dev_midi_vol(c->u.app_level.opt, c->u.app_level.app);
 		c->val_mask = ~0U;
 		c->curval = val;
 		return 1;
@@ -2272,6 +2142,7 @@ ctl_new(int scope, void *arg0, void *arg
 		c->u.hw.addr = *(unsigned int *)arg1;
 		break;
 	case CTL_OPT_DEV:
+	case CTL_APP_LEVEL:
 		c->u.any.arg1 = arg1;
 		break;
 	default:
@@ -2337,6 +2208,7 @@ ctl_match(struct ctl *c, int scope, void
 			return 0;
 		break;
 	case CTL_OPT_DEV:
+	case CTL_APP_LEVEL:
 		if (arg1 != NULL && c->u.any.arg1 != arg1)
 			return 0;
 		break;
Index: dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
diff -u -p -r1.47 dev.h
--- dev.h	20 Dec 2024 07:35:56 -0000	1.47
+++ dev.h	16 Jun 2025 18:17:58 -0000
@@ -26,7 +26,7 @@
 /*
  * preallocated audio clients
  */
-#define DEV_NSLOT	8
+#define DEV_NSLOT	32
 
 /*
  * preallocated control clients
@@ -101,12 +101,7 @@ struct slot {
 #define SLOT_STOP	4			/* draining */
 	int pstate;
 
-#define SLOT_NAMEMAX	8
-	char name[SLOT_NAMEMAX];		/* name matching [a-z]+ */
-	unsigned int unit;			/* instance of name */
-	unsigned int serial;			/* global unique number */
-	unsigned int vol;			/* current (midi) volume */
-	unsigned int id;			/* process id */
+	struct app *app;
 };
 
 /*
@@ -127,7 +122,7 @@ struct ctl {
 #define CTL_HW		0
 #define CTL_DEV_MASTER	1
 #define CTL_OPT_DEV	2
-#define CTL_SLOT_LEVEL	3
+#define CTL_APP_LEVEL	3
 	unsigned int scope;
 	union {
 		struct {
@@ -142,12 +137,9 @@ struct ctl {
 			struct dev *dev;
 		} dev_master;
 		struct {
-			struct slot *slot;
-		} slot_level;
-		struct {
-			struct slot *slot;
 			struct opt *opt;
-		} slot_opt;
+			struct app *app;
+		} app_level;
 		struct {
 			struct opt *opt;
 			struct dev *dev;
@@ -287,7 +279,6 @@ extern struct slot slot_array[DEV_NSLOT]
 extern struct ctlslot ctlslot_array[DEV_NCTLSLOT];
 extern struct mtc mtc_array[1];
 
-void slot_array_init(void);
 size_t chans_fmt(char *, size_t, int, int, int, int, int);
 int dev_open(struct dev *);
 void dev_close(struct dev *);
@@ -316,10 +307,10 @@ void dev_cycle(struct dev *);
  */
 void dev_master(struct dev *, unsigned int);
 void dev_midi_send(struct dev *, void *, int);
-void dev_midi_vol(struct dev *, struct slot *);
+void dev_midi_vol(struct opt *, struct app *);
 void dev_midi_master(struct dev *);
-void dev_midi_slotdesc(struct dev *, struct slot *);
-void dev_midi_dump(struct dev *);
+void dev_midi_slotdesc(struct opt *o, struct app *a);
+void dev_midi_dump(struct opt *o);
 
 void mtc_midi_qfr(struct mtc *, int);
 void mtc_midi_full(struct mtc *);
@@ -336,7 +327,6 @@ struct slot *slot_new(struct opt *, unsi
     struct slotops *, void *, int);
 void slot_del(struct slot *);
 void slot_setvol(struct slot *, unsigned int);
-void slot_setopt(struct slot *, struct opt *);
 void slot_start(struct slot *);
 void slot_stop(struct slot *, int);
 void slot_read(struct slot *);
Index: opt.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/opt.c,v
diff -u -p -r1.13 opt.c
--- opt.c	20 Dec 2024 07:35:56 -0000	1.13
+++ opt.c	16 Jun 2025 18:17:58 -0000
@@ -36,6 +36,103 @@ struct midiops opt_midiops = {
 	opt_midi_exit
 };
 
+struct app *
+opt_mkapp(struct opt *o, char *who)
+{
+	char *p;
+	char name[APP_NAMEMAX];
+	unsigned int i, ser, bestser, bestidx, inuse;
+	struct app *a;
+	struct slot *s;
+
+	/*
+	 * create a valid control name (lowcase, remove [^a-z], truncate)
+	 */
+	for (i = 0, p = who; ; p++) {
+		if (i == APP_NAMEMAX - 1 || *p == '\0') {
+			name[i] = '\0';
+			break;
+		} else if (*p >= 'A' && *p <= 'Z') {
+			name[i++] = *p + 'a' - 'A';
+		} else if (*p >= 'a' && *p <= 'z')
+			name[i++] = *p;
+	}
+	if (i == 0)
+		strlcpy(name, "noname", APP_NAMEMAX);
+
+	/*
+	 * return the app with this name (if any)
+	 */
+	for (i = 0, a = o->app_array; i < OPT_NAPP; i++, a++) {
+		if (strcmp(a->name, name) == 0)
+			return a;
+	}
+
+	/*
+	 * build a bitmap of app structures currently in use
+	 */
+	inuse = 0;
+	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
+		if (s->app != NULL && s->ops != NULL)
+			inuse |= 1 << (s->app - o->app_array);
+	}
+
+	if (inuse == (1 << OPT_NAPP) - 1) {
+		logx(1, "%s: too many programs", name);
+		return NULL;
+	}
+
+	/*
+	 * recycle the oldest free structure
+	 */
+
+	o->app_serial++;
+	bestser = 0;
+	bestidx = OPT_NAPP;
+	for (i = 0, a = o->app_array; i < OPT_NAPP; i++, a++) {
+		if (inuse & (1 << i))
+			continue;
+		ser = o->app_serial - a->serial;
+		if (ser > bestser) {
+			bestser = ser;
+			bestidx = i;
+		}
+	}
+
+	a = o->app_array + bestidx;
+
+	ctl_del(CTL_APP_LEVEL, o, a);
+
+	strlcpy(a->name, name, sizeof(a->name));
+	a->serial = o->app_serial;
+	a->vol = MIDI_MAXCTL;
+	ctl_new(CTL_APP_LEVEL, o, a,
+	    CTL_NUM, "", "app", a->name, -1, "level",
+	    NULL, -1, 127, a->vol);
+	dev_midi_slotdesc(o, a);
+	dev_midi_vol(o, a);
+
+	return a;
+}
+
+void
+opt_appvol(struct opt *o, struct app *a, int vol)
+{
+	struct slot *s;
+	int i;
+
+	a->vol = vol;
+
+	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
+		if (s->app != a || s->opt != o)
+			continue;
+		s->mix.vol = MIDI_TO_ADATA(vol);
+#ifdef DEBUG
+		logx(3, "%s/%s: setting volume %u", o->name, a->name, vol);
+#endif
+	}
+}
+
 void
 opt_midi_imsg(void *arg, unsigned char *msg, int len)
 {
@@ -56,12 +153,10 @@ opt_midi_omsg(void *arg, unsigned char *
 
 	if ((msg[0] & MIDI_CMDMASK) == MIDI_CTL && msg[1] == MIDI_CTL_VOL) {
 		chan = msg[0] & MIDI_CHANMASK;
-		if (chan >= DEV_NSLOT)
-			return;
-		if (slot_array[chan].opt != o)
+		if (chan >= OPT_NAPP)
 			return;
-		slot_setvol(slot_array + chan, msg[2]);
-		ctl_onval(CTL_SLOT_LEVEL, slot_array + chan, NULL, msg[2]);
+		opt_appvol(o, o->app_array + chan, msg[2]);
+		ctl_onval(CTL_APP_LEVEL, o, o->app_array + chan, msg[2]);
 		return;
 	}
 	x = (struct sysex *)msg;
@@ -137,7 +232,7 @@ opt_midi_omsg(void *arg, unsigned char *
 			return;
 		if (len != SYSEX_SIZE(dumpreq))
 			return;
-		dev_midi_dump(o->dev);
+		dev_midi_dump(o);
 		break;
 	}
 }
@@ -389,15 +484,6 @@ opt_setdev(struct opt *o, struct dev *nd
 	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
 		if (s->opt != o)
 			continue;
-
-		if (ndev != odev) {
-			dev_midi_slotdesc(odev, s);
-			dev_midi_slotdesc(ndev, s);
-			dev_midi_vol(ndev, s);
-		}
-
-		c = ctl_find(CTL_SLOT_LEVEL, s, NULL);
-		ctl_update(c);
 
 		if (s->pstate == SLOT_RUN || s->pstate == SLOT_STOP) {
 			slot_initconv(s);
Index: opt.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/opt.h,v
diff -u -p -r1.8 opt.h
--- opt.h	22 Apr 2024 10:42:04 -0000	1.8
+++ opt.h	16 Jun 2025 18:17:58 -0000
@@ -18,15 +18,26 @@
 #define OPT_H
 
 #define OPT_NMAX		16
+#define OPT_NAPP		8
 
 struct dev;
 
+struct app {
+#define APP_NAMEMAX	12
+	char name[APP_NAMEMAX];		/* name matching [a-z]+ */
+	unsigned int serial;		/* global unique number */
+	int vol;
+};
+
 struct opt {
 	struct opt *next;
 	struct dev *dev, *alt_first;
 	struct midi *midi;
 	struct mtc *mtc;	/* if set, MMC-controlled MTC source */
 
+	struct app app_array[OPT_NAPP];
+	unsigned int app_serial;
+
 	int num;
 #define OPT_NAMEMAX 11
 	char name[OPT_NAMEMAX + 1];
@@ -40,6 +51,8 @@ struct opt {
 
 extern struct opt *opt_list;
 
+struct app *opt_mkapp(struct opt *o, char *who);
+void opt_appvol(struct opt *o, struct app *a, int vol);
 struct opt *opt_new(struct dev *, char *, int, int, int, int,
     int, int, int, unsigned int);
 void opt_del(struct opt *);
Index: sndiod.8
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.8,v
diff -u -p -r1.17 sndiod.8
--- sndiod.8	3 May 2024 16:47:15 -0000	1.17
+++ sndiod.8	16 Jun 2025 18:17:58 -0000
@@ -440,12 +440,13 @@ exposes the audio device clock
 and allows audio device properties to be controlled
 through MIDI.
 .Pp
-A MIDI channel is assigned to each stream, and the volume
+A MIDI channel is assigned to each program, and the volume
 is changed using the standard volume controller (number 7).
 Similarly, when the audio client changes its volume,
 the same MIDI controller message is sent out; it can be used
 for instance for monitoring or as feedback for motorized
 faders.
+If there are multiple instances of the same program they will share the same setting.
 .Pp
 The master volume can be changed using the standard master volume
 system exclusive message.
Index: sndiod.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.c,v
diff -u -p -r1.50 sndiod.c
--- sndiod.c	20 Dec 2024 07:35:56 -0000	1.50
+++ sndiod.c	16 Jun 2025 18:17:58 -0000
@@ -587,8 +587,6 @@ main(int argc, char **argv)
 	d = NULL;
 	p = NULL;
 
-	slot_array_init();
-
 	while ((c = getopt(argc, argv,
 	    "a:b:c:C:de:F:f:j:L:m:Q:q:r:s:t:U:v:w:x:z:")) != -1) {
 		switch (c) {
Index: sock.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.c,v
diff -u -p -r1.54 sock.c
--- sock.c	16 Jun 2025 06:18:18 -0000	1.54
+++ sock.c	16 Jun 2025 18:17:58 -0000
@@ -250,7 +250,7 @@ sock_slot_onvol(void *arg)
 	struct slot *s = f->slot;
 
 #ifdef DEBUG
-	logx(4, "slot%zu: onvol: vol -> %d", s - slot_array, s->vol);
+	logx(4, "slot%zu: onvol: vol -> %u", s - slot_array, s->app->vol);
 #endif
 	if (s->pstate != SOCK_START)
 		return;
@@ -1005,8 +1005,6 @@ sock_execmsg(struct sock *f)
 		f->rstate = SOCK_RMSG;
 		f->lastvol = ctl; /* dont trigger feedback message */
 		slot_setvol(s, ctl);
-		dev_midi_vol(s->opt->dev, s);
-		ctl_onval(CTL_SLOT_LEVEL, s, NULL, ctl);
 		break;
 	case AMSG_CTLSUB_OLD:
 	case AMSG_CTLSUB:
@@ -1187,17 +1185,17 @@ sock_buildmsg(struct sock *f)
 	/*
 	 * if volume changed build a SETVOL message
 	 */
-	if (f->pstate >= SOCK_START && f->slot->vol != f->lastvol) {
+	if (f->pstate >= SOCK_START && f->slot->app->vol != f->lastvol) {
 #ifdef DEBUG
 		logx(3, "sock %d: building SETVOL message, vol = %d", f->fd,
-		    f->slot->vol);
+		    f->slot->app->vol);
 #endif
 		AMSG_INIT(&f->wmsg);
 		f->wmsg.cmd = htonl(AMSG_SETVOL);
-		f->wmsg.u.vol.ctl = htonl(f->slot->vol);
+		f->wmsg.u.vol.ctl = htonl(f->slot->app->vol);
 		f->wtodo = sizeof(struct amsg);
 		f->wstate = SOCK_WMSG;
-		f->lastvol = f->slot->vol;
+		f->lastvol = f->slot->app->vol;
 		return 1;
 	}