xref: /openbsd/usr.bin/sndiod/opt.c (revision 7b639200)
1 /*	$OpenBSD: opt.c,v 1.13 2024/12/20 07:35:56 ratchov Exp $	*/
2 /*
3  * Copyright (c) 2008-2011 Alexandre Ratchov <alex@caoua.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <string.h>
18 
19 #include "dev.h"
20 #include "midi.h"
21 #include "opt.h"
22 #include "sysex.h"
23 #include "utils.h"
24 
25 struct opt *opt_list;
26 
27 void opt_midi_imsg(void *, unsigned char *, int);
28 void opt_midi_omsg(void *, unsigned char *, int);
29 void opt_midi_fill(void *, int);
30 void opt_midi_exit(void *);
31 
32 struct midiops opt_midiops = {
33 	opt_midi_imsg,
34 	opt_midi_omsg,
35 	opt_midi_fill,
36 	opt_midi_exit
37 };
38 
39 void
opt_midi_imsg(void * arg,unsigned char * msg,int len)40 opt_midi_imsg(void *arg, unsigned char *msg, int len)
41 {
42 #ifdef DEBUG
43 	struct opt *o = arg;
44 
45 	logx(0, "%s: can't receive midi messages", o->name);
46 	panic();
47 #endif
48 }
49 
50 void
opt_midi_omsg(void * arg,unsigned char * msg,int len)51 opt_midi_omsg(void *arg, unsigned char *msg, int len)
52 {
53 	struct opt *o = arg;
54 	struct sysex *x;
55 	unsigned int fps, chan;
56 
57 	if ((msg[0] & MIDI_CMDMASK) == MIDI_CTL && msg[1] == MIDI_CTL_VOL) {
58 		chan = msg[0] & MIDI_CHANMASK;
59 		if (chan >= DEV_NSLOT)
60 			return;
61 		if (slot_array[chan].opt != o)
62 			return;
63 		slot_setvol(slot_array + chan, msg[2]);
64 		ctl_onval(CTL_SLOT_LEVEL, slot_array + chan, NULL, msg[2]);
65 		return;
66 	}
67 	x = (struct sysex *)msg;
68 	if (x->start != SYSEX_START)
69 		return;
70 	if (len < SYSEX_SIZE(empty))
71 		return;
72 	switch (x->type) {
73 	case SYSEX_TYPE_RT:
74 		if (x->id0 == SYSEX_CONTROL && x->id1 == SYSEX_MASTER) {
75 			if (len == SYSEX_SIZE(master)) {
76 				dev_master(o->dev, x->u.master.coarse);
77 				if (o->dev->master_enabled) {
78 					ctl_onval(CTL_DEV_MASTER, o->dev, NULL,
79 					   x->u.master.coarse);
80 				}
81 			}
82 			return;
83 		}
84 		if (x->id0 != SYSEX_MMC)
85 			return;
86 		switch (x->id1) {
87 		case SYSEX_MMC_STOP:
88 			if (len != SYSEX_SIZE(stop))
89 				return;
90 			if (o->mtc == NULL)
91 				return;
92 			mtc_setdev(o->mtc, o->dev);
93 			logx(2, "%s: mmc stop", o->name);
94 			mtc_stop(o->mtc);
95 			break;
96 		case SYSEX_MMC_START:
97 			if (len != SYSEX_SIZE(start))
98 				return;
99 			if (o->mtc == NULL)
100 				return;
101 			mtc_setdev(o->mtc, o->dev);
102 			logx(2, "%s: mmc start", o->name);
103 			mtc_start(o->mtc);
104 			break;
105 		case SYSEX_MMC_LOC:
106 			if (len != SYSEX_SIZE(loc) ||
107 			    x->u.loc.len != SYSEX_MMC_LOC_LEN ||
108 			    x->u.loc.cmd != SYSEX_MMC_LOC_CMD)
109 				return;
110 			if (o->mtc == NULL)
111 				return;
112 			mtc_setdev(o->mtc, o->dev);
113 			switch (x->u.loc.hr >> 5) {
114 			case MTC_FPS_24:
115 				fps = 24;
116 				break;
117 			case MTC_FPS_25:
118 				fps = 25;
119 				break;
120 			case MTC_FPS_30:
121 				fps = 30;
122 				break;
123 			default:
124 				mtc_stop(o->mtc);
125 				return;
126 			}
127 			mtc_loc(o->mtc,
128 			    (x->u.loc.hr & 0x1f) * 3600 * MTC_SEC +
129 			     x->u.loc.min * 60 * MTC_SEC +
130 			     x->u.loc.sec * MTC_SEC +
131 			     x->u.loc.fr * (MTC_SEC / fps));
132 			break;
133 		}
134 		break;
135 	case SYSEX_TYPE_EDU:
136 		if (x->id0 != SYSEX_AUCAT || x->id1 != SYSEX_AUCAT_DUMPREQ)
137 			return;
138 		if (len != SYSEX_SIZE(dumpreq))
139 			return;
140 		dev_midi_dump(o->dev);
141 		break;
142 	}
143 }
144 
145 void
opt_midi_fill(void * arg,int count)146 opt_midi_fill(void *arg, int count)
147 {
148 	/* nothing to do */
149 }
150 
151 void
opt_midi_exit(void * arg)152 opt_midi_exit(void *arg)
153 {
154 	struct opt *o = arg;
155 
156 	logx(1, "%s: midi end point died", o->name);
157 }
158 
159 /*
160  * create a new audio sub-device "configuration"
161  */
162 struct opt *
opt_new(struct dev * d,char * name,int pmin,int pmax,int rmin,int rmax,int maxweight,int mmc,int dup,unsigned int mode)163 opt_new(struct dev *d, char *name,
164     int pmin, int pmax, int rmin, int rmax,
165     int maxweight, int mmc, int dup, unsigned int mode)
166 {
167 	struct dev *a;
168 	struct opt *o, **po;
169 	char str[64];
170 	unsigned int len, num;
171 	char c;
172 
173 	if (name == NULL) {
174 		name = d->name;
175 		len = strlen(name);
176 	} else {
177 		for (len = 0; name[len] != '\0'; len++) {
178 			if (len == OPT_NAMEMAX) {
179 				logx(0, "%s: too long", name);
180 				return NULL;
181 			}
182 			c = name[len];
183 			if ((c < 'a' || c > 'z') &&
184 			    (c < 'A' || c > 'Z')) {
185 				logx(0, "%s: only alphabetic chars allowed", name);
186 				return NULL;
187 			}
188 		}
189 	}
190 	num = 0;
191 	for (po = &opt_list; *po != NULL; po = &(*po)->next)
192 		num++;
193 	if (num >= OPT_NMAX) {
194 		logx(0, "%s: too many opts", name);
195 		return NULL;
196 	}
197 
198 	if (opt_byname(name)) {
199 		logx(1, "%s: already defined", name);
200 		return NULL;
201 	}
202 
203 	if (mmc) {
204 		if (mtc_array[0].dev != NULL && mtc_array[0].dev != d) {
205 			logx(0, "%s: MTC already setup for another device", name);
206 			return NULL;
207 		}
208 		mtc_array[0].dev = d;
209 		logx(2, "%s: initial MTC source, controlled by MMC", d->path);
210 	}
211 
212 	if (strcmp(d->name, name) == 0)
213 		a = d;
214 	else {
215 		/* circulate to the first "alternate" device (greatest num) */
216 		for (a = d; a->alt_next->num > a->num; a = a->alt_next)
217 			;
218 	}
219 
220 	o = xmalloc(sizeof(struct opt));
221 	o->num = num;
222 	o->alt_first = o->dev = a;
223 	o->refcnt = 0;
224 
225 	/*
226 	 * XXX: below, we allocate a midi input buffer, since we don't
227 	 *	receive raw midi data, so no need to allocate a input
228 	 *	ibuf.  Possibly set imsg & fill callbacks to NULL and
229 	 *	use this to in midi_new() to check if buffers need to be
230 	 *	allocated
231 	 */
232 	o->midi = midi_new(&opt_midiops, o, MODE_MIDIIN | MODE_MIDIOUT);
233 	midi_tag(o->midi, o->num);
234 
235 	if (mode & MODE_PLAY) {
236 		o->pmin = pmin;
237 		o->pmax = pmax;
238 	}
239 	if (mode & MODE_RECMASK) {
240 		o->rmin = rmin;
241 		o->rmax = rmax;
242 	}
243 	o->maxweight = maxweight;
244 	o->mtc = mmc ? &mtc_array[0] : NULL;
245 	o->dup = dup;
246 	o->mode = mode;
247 	memcpy(o->name, name, len + 1);
248 	o->next = *po;
249 	*po = o;
250 
251 	logx(2, "%s: %s%s, vol = %d", o->name, (chans_fmt(str, sizeof(str),
252 	    o->mode, o->pmin, o->pmax, o->rmin, o->rmax), str),
253 	    (o->dup) ? ", dup" : "", o->maxweight);
254 
255 	return o;
256 }
257 
258 struct opt *
opt_byname(char * name)259 opt_byname(char *name)
260 {
261 	struct opt *o;
262 
263 	for (o = opt_list; o != NULL; o = o->next) {
264 		if (strcmp(name, o->name) == 0)
265 			return o;
266 	}
267 	return NULL;
268 }
269 
270 struct opt *
opt_bynum(int num)271 opt_bynum(int num)
272 {
273 	struct opt *o;
274 
275 	for (o = opt_list; o != NULL; o = o->next) {
276 		if (o->num == num)
277 			return o;
278 	}
279 	return NULL;
280 }
281 
282 void
opt_del(struct opt * o)283 opt_del(struct opt *o)
284 {
285 	struct opt **po;
286 
287 	for (po = &opt_list; *po != o; po = &(*po)->next) {
288 #ifdef DEBUG
289 		if (*po == NULL) {
290 			logx(0, "%s: not on list", __func__);
291 			panic();
292 		}
293 #endif
294 	}
295 	midi_del(o->midi);
296 	*po = o->next;
297 	xfree(o);
298 }
299 
300 void
opt_init(struct opt * o)301 opt_init(struct opt *o)
302 {
303 }
304 
305 void
opt_done(struct opt * o)306 opt_done(struct opt *o)
307 {
308 	struct dev *d;
309 
310 	if (o->refcnt != 0) {
311 		// XXX: all clients are already kicked, so this never happens
312 		logx(0, "%s: still has refs", o->name);
313 	}
314 	for (d = dev_list; d != NULL; d = d->next)
315 		ctl_del(CTL_OPT_DEV, o, d);
316 }
317 
318 /*
319  * Set opt's device, and (if necessary) move clients to
320  * to the new device
321  */
322 int
opt_setdev(struct opt * o,struct dev * ndev)323 opt_setdev(struct opt *o, struct dev *ndev)
324 {
325 	struct dev *odev;
326 	struct ctl *c;
327 	struct ctlslot *p;
328 	struct slot *s;
329 	int i;
330 
331 	if (!dev_ref(ndev))
332 		return 0;
333 
334 	odev = o->dev;
335 	if (odev == ndev) {
336 		dev_unref(ndev);
337 		return 1;
338 	}
339 
340 	/* check if clients can use new device */
341 	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
342 		if (s->opt != o)
343 			continue;
344 		if (s->ops != NULL && !dev_iscompat(odev, ndev)) {
345 			dev_unref(ndev);
346 			return 0;
347 		}
348 	}
349 
350 	/*
351 	 * if we're using MMC, move all opts to the new device, mtc_setdev()
352 	 * will call us back
353 	 *
354 	 * XXX: move this to the end to avoid the recursion
355 	 */
356 	if (o->mtc != NULL && o->mtc->dev != ndev) {
357 		mtc_setdev(o->mtc, ndev);
358 		dev_unref(ndev);
359 		return 1;
360 	}
361 
362 	c = ctl_find(CTL_OPT_DEV, o, o->dev);
363 	if (c != NULL)
364 		c->curval = 0;
365 
366 	/* detach clients from old device */
367 	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
368 		if (s->opt != o)
369 			continue;
370 
371 		if (s->pstate == SLOT_RUN || s->pstate == SLOT_STOP)
372 			slot_detach(s);
373 	}
374 
375 	o->dev = ndev;
376 
377 	if (o->refcnt > 0) {
378 		dev_unref(odev);
379 		dev_ref(o->dev);
380 	}
381 
382 	c = ctl_find(CTL_OPT_DEV, o, o->dev);
383 	if (c != NULL) {
384 		c->curval = 1;
385 		c->val_mask = ~0;
386 	}
387 
388 	/* attach clients to new device */
389 	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
390 		if (s->opt != o)
391 			continue;
392 
393 		if (ndev != odev) {
394 			dev_midi_slotdesc(odev, s);
395 			dev_midi_slotdesc(ndev, s);
396 			dev_midi_vol(ndev, s);
397 		}
398 
399 		c = ctl_find(CTL_SLOT_LEVEL, s, NULL);
400 		ctl_update(c);
401 
402 		if (s->pstate == SLOT_RUN || s->pstate == SLOT_STOP) {
403 			slot_initconv(s);
404 			slot_attach(s);
405 		}
406 	}
407 
408 	/* move controlling clients to new device */
409 	for (p = ctlslot_array, i = 0; i < DEV_NCTLSLOT; i++, p++) {
410 		if (p->ops == NULL)
411 			continue;
412 		if (p->opt == o)
413 			ctlslot_update(p);
414 	}
415 
416 	dev_unref(ndev);
417 	return 1;
418 }
419 
420 /*
421  * Get a reference to opt's device
422  */
423 struct dev *
opt_ref(struct opt * o)424 opt_ref(struct opt *o)
425 {
426 	struct dev *d;
427 
428 	if (o->refcnt == 0) {
429 		if (strcmp(o->name, o->dev->name) == 0) {
430 			if (!dev_ref(o->dev))
431 				return NULL;
432 		} else {
433 			/* find first working one */
434 			d = o->alt_first;
435 			while (1) {
436 				if (dev_ref(d))
437 					break;
438 				d = d->alt_next;
439 				if (d == o->alt_first)
440 					return NULL;
441 			}
442 
443 			/* if device changed, move everything to the new one */
444 			if (d != o->dev)
445 				opt_setdev(o, d);
446 
447 			/* create server.device control */
448 			for (d = dev_list; d != NULL; d = d->next) {
449 				d->refcnt++;
450 				if (d->pstate == DEV_CFG)
451 					dev_open(d);
452 				ctl_new(CTL_OPT_DEV, o, d,
453 				    CTL_SEL, dev_getdisplay(d),
454 				    o->name, "server", -1, "device",
455 				    d->name, -1, 1, o->dev == d);
456 			}
457 		}
458 	}
459 
460 	o->refcnt++;
461 	return o->dev;
462 }
463 
464 /*
465  * Release opt's device
466  */
467 void
opt_unref(struct opt * o)468 opt_unref(struct opt *o)
469 {
470 	struct dev *d;
471 
472 	o->refcnt--;
473 	if (o->refcnt == 0) {
474 		/* delete server.device control */
475 		for (d = dev_list; d != NULL; d = d->next) {
476 			if (ctl_del(CTL_OPT_DEV, o, d))
477 				dev_unref(d);
478 		}
479 		dev_unref(o->dev);
480 	}
481 }
482