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