mirror of https://github.com/ericonr/sndio.git
move mmc/mtc and volume control bits from struct aproc to struct dev
This commit is contained in:
parent
a34a843352
commit
37baea58d8
|
@ -1002,8 +1002,6 @@ mix_opos(struct aproc *p, struct abuf *obuf, int delta)
|
|||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
if (APROC_OK(p->u.mix.ctl))
|
||||
ctl_ontick(p->u.mix.ctl, delta);
|
||||
aproc_opos(p, obuf, delta);
|
||||
if (APROC_OK(p->u.mix.mon))
|
||||
p->u.mix.mon->ops->ipos(p->u.mix.mon, NULL, delta);
|
||||
|
@ -1032,7 +1030,6 @@ mix_new(char *name, int maxlat, unsigned round, unsigned autovol)
|
|||
p->u.mix.lat = 0;
|
||||
p->u.mix.round = round;
|
||||
p->u.mix.maxlat = maxlat;
|
||||
p->u.mix.ctl = NULL;
|
||||
p->u.mix.mon = NULL;
|
||||
p->u.mix.autovol = autovol;
|
||||
return p;
|
||||
|
@ -1425,8 +1422,6 @@ sub_ipos(struct aproc *p, struct abuf *ibuf, int delta)
|
|||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
if (APROC_OK(p->u.sub.ctl))
|
||||
ctl_ontick(p->u.sub.ctl, delta);
|
||||
aproc_ipos(p, ibuf, delta);
|
||||
}
|
||||
|
||||
|
@ -1453,7 +1448,6 @@ sub_new(char *name, int maxlat, unsigned round)
|
|||
p->u.sub.lat = 0;
|
||||
p->u.sub.round = round;
|
||||
p->u.sub.maxlat = maxlat;
|
||||
p->u.sub.ctl = NULL;
|
||||
return p;
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,6 @@ struct aproc {
|
|||
int lat; /* current latency */
|
||||
int maxlat; /* max latency allowed */
|
||||
unsigned abspos; /* frames produced */
|
||||
struct aproc *ctl; /* MIDI control/sync */
|
||||
struct aproc *mon; /* snoop output */
|
||||
unsigned autovol; /* adjust volume dynamically */
|
||||
} mix;
|
||||
|
@ -148,7 +147,6 @@ struct aproc {
|
|||
int lat; /* current latency */
|
||||
int maxlat; /* max latency allowed */
|
||||
unsigned abspos; /* frames consumed */
|
||||
struct aproc *ctl;
|
||||
} sub;
|
||||
struct {
|
||||
int delta; /* time position */
|
||||
|
@ -176,15 +174,6 @@ struct aproc {
|
|||
} thru;
|
||||
struct {
|
||||
struct dev *dev; /* controlled device */
|
||||
#define CTL_NSLOT 8
|
||||
#define CTL_NAMEMAX 8
|
||||
unsigned serial;
|
||||
#define CTL_OFF 0 /* ignore MMC messages */
|
||||
#define CTL_STOP 1 /* stopped, can't start */
|
||||
#define CTL_START 2 /* attempting to start */
|
||||
#define CTL_RUN 3 /* started */
|
||||
unsigned tstate;
|
||||
unsigned origin; /* MTC start time */
|
||||
unsigned fps; /* MTC frames per second */
|
||||
#define MTC_FPS_24 0
|
||||
#define MTC_FPS_25 1
|
||||
|
@ -196,21 +185,6 @@ struct aproc {
|
|||
unsigned fr; /* MTC frames */
|
||||
unsigned qfr; /* MTC quarter frames */
|
||||
int delta; /* rel. to the last MTC tick */
|
||||
struct ctl_slot {
|
||||
struct ctl_ops {
|
||||
void (*vol)(void *, unsigned);
|
||||
void (*start)(void *);
|
||||
void (*stop)(void *);
|
||||
void (*loc)(void *, unsigned);
|
||||
void (*quit)(void *);
|
||||
} *ops;
|
||||
void *arg;
|
||||
unsigned unit;
|
||||
char name[CTL_NAMEMAX];
|
||||
unsigned serial;
|
||||
unsigned vol;
|
||||
unsigned tstate;
|
||||
} slot[CTL_NSLOT];
|
||||
} ctl;
|
||||
} u;
|
||||
};
|
||||
|
|
|
@ -583,7 +583,7 @@ main(int argc, char **argv)
|
|||
if (!dev_init(d))
|
||||
exit(1);
|
||||
if (d->autostart && (d->mode & MODE_AUDIOMASK))
|
||||
ctl_start(d->midi);
|
||||
dev_mmcstart(d);
|
||||
}
|
||||
for (l = listen_list; l != NULL; l = l->next) {
|
||||
if (!listen_init(l))
|
||||
|
@ -609,8 +609,7 @@ main(int argc, char **argv)
|
|||
dnext = d->next;
|
||||
if (!dev_run(d))
|
||||
goto fatal;
|
||||
if ((d->mode & MODE_THRU) ||
|
||||
(d->pstate != DEV_CLOSED && !ctl_idle(d->midi)))
|
||||
if (!dev_idle(d))
|
||||
active = 1;
|
||||
}
|
||||
if (dev_list == NULL)
|
||||
|
|
486
aucat/dev.c
486
aucat/dev.c
|
@ -64,6 +64,7 @@
|
|||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "abuf.h"
|
||||
|
@ -84,6 +85,7 @@ void dev_close(struct dev *);
|
|||
void dev_start(struct dev *);
|
||||
void dev_stop(struct dev *);
|
||||
void dev_clear(struct dev *);
|
||||
void dev_onmove(void *, int);
|
||||
int devctl_open(struct dev *, struct devctl *);
|
||||
|
||||
struct dev *dev_list = NULL;
|
||||
|
@ -111,7 +113,7 @@ dev_new(char *path, unsigned mode,
|
|||
unsigned bufsz, unsigned round, unsigned hold, unsigned autovol)
|
||||
{
|
||||
struct dev *d;
|
||||
unsigned *pnum;
|
||||
unsigned *pnum, i;
|
||||
|
||||
d = malloc(sizeof(struct dev));
|
||||
if (d == NULL) {
|
||||
|
@ -140,6 +142,17 @@ dev_new(char *path, unsigned mode,
|
|||
d->autovol = autovol;
|
||||
d->autostart = 0;
|
||||
d->pstate = DEV_CLOSED;
|
||||
d->serial = 0;
|
||||
for (i = 0; i < CTL_NSLOT; i++) {
|
||||
d->slot[i].unit = i;
|
||||
d->slot[i].ops = NULL;
|
||||
d->slot[i].vol = MIDI_MAXCTL;
|
||||
d->slot[i].tstate = CTL_OFF;
|
||||
d->slot[i].serial = d->serial++;
|
||||
d->slot[i].name[0] = '\0';
|
||||
}
|
||||
d->origin = 0;
|
||||
d->tstate = CTL_STOP;
|
||||
d->next = dev_list;
|
||||
dev_list = d;
|
||||
return d;
|
||||
|
@ -393,16 +406,10 @@ dev_open(struct dev *d)
|
|||
if (d->mode & MODE_PLAY) {
|
||||
d->mix = mix_new("play", d->bufsz, d->round, d->autovol);
|
||||
d->mix->refs++;
|
||||
d->mix->u.mix.ctl = d->midi;
|
||||
}
|
||||
if (d->mode & MODE_REC) {
|
||||
d->sub = sub_new("rec", d->bufsz, d->round);
|
||||
d->sub->refs++;
|
||||
/*
|
||||
* If not playing, use the record end as clock source
|
||||
*/
|
||||
if (!(d->mode & MODE_PLAY))
|
||||
d->sub->u.sub.ctl = d->midi;
|
||||
}
|
||||
if (d->mode & MODE_LOOP) {
|
||||
/*
|
||||
|
@ -657,6 +664,13 @@ dev_close(struct dev *d)
|
|||
void
|
||||
dev_drain(struct dev *d)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
for (i = 0, s = d->slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (s->ops)
|
||||
s->ops->quit(s->arg);
|
||||
}
|
||||
if (d->pstate != DEV_CLOSED)
|
||||
dev_close(d);
|
||||
}
|
||||
|
@ -728,10 +742,10 @@ dev_start(struct dev *d)
|
|||
d->submon->flags |= APROC_DROP;
|
||||
if (APROC_OK(d->play) && d->play->u.io.file) {
|
||||
f = d->play->u.io.file;
|
||||
f->ops->start(f);
|
||||
f->ops->start(f, dev_onmove, d);
|
||||
} else if (APROC_OK(d->rec) && d->rec->u.io.file) {
|
||||
f = d->rec->u.io.file;
|
||||
f->ops->start(f);
|
||||
f->ops->start(f, dev_onmove, d);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -852,7 +866,7 @@ dev_run(struct dev *d)
|
|||
(!APROC_OK(d->submon) ||
|
||||
d->submon->u.sub.idle > 2 * d->bufsz) &&
|
||||
(!APROC_OK(d->midi) ||
|
||||
d->midi->u.ctl.tstate != CTL_RUN)) {
|
||||
d->tstate != CTL_RUN)) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dev_dbg(d);
|
||||
|
@ -1009,6 +1023,8 @@ dev_sync(struct dev *d, unsigned mode, struct abuf *ibuf, struct abuf *obuf)
|
|||
/*
|
||||
* return the current latency (in frames), ie the latency that
|
||||
* a stream would have if dev_attach() is called on it.
|
||||
*
|
||||
* XXX: return a "unsigned", since result is always positive, isn't it?
|
||||
*/
|
||||
int
|
||||
dev_getpos(struct dev *d)
|
||||
|
@ -1263,3 +1279,453 @@ dev_clear(struct dev *d)
|
|||
mon_clear(d->mon);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void
|
||||
dev_slotdbg(struct dev *d, int slot)
|
||||
{
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (slot < 0) {
|
||||
dbg_puts("none");
|
||||
} else {
|
||||
s = d->slot + slot;
|
||||
dbg_puts(s->name);
|
||||
dbg_putu(s->unit);
|
||||
dbg_puts("(");
|
||||
dbg_putu(s->vol);
|
||||
dbg_puts(")/");
|
||||
switch (s->tstate) {
|
||||
case CTL_OFF:
|
||||
dbg_puts("off");
|
||||
break;
|
||||
case CTL_RUN:
|
||||
dbg_puts("run");
|
||||
break;
|
||||
case CTL_START:
|
||||
dbg_puts("sta");
|
||||
break;
|
||||
case CTL_STOP:
|
||||
dbg_puts("stp");
|
||||
break;
|
||||
default:
|
||||
dbg_puts("unk");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* find the best matching free slot index (ie midi channel).
|
||||
* return -1, if there are no free slots anymore
|
||||
*/
|
||||
int
|
||||
dev_mkslot(struct dev *d, char *who)
|
||||
{
|
||||
char *s;
|
||||
struct ctl_slot *slot;
|
||||
char name[CTL_NAMEMAX];
|
||||
unsigned i, unit, umap = 0;
|
||||
unsigned ser, bestser, bestidx;
|
||||
|
||||
/*
|
||||
* create a ``valid'' control name (lowcase, remove [^a-z], trucate)
|
||||
*/
|
||||
for (i = 0, s = who; ; s++) {
|
||||
if (i == CTL_NAMEMAX - 1 || *s == '\0') {
|
||||
name[i] = '\0';
|
||||
break;
|
||||
} else if (*s >= 'A' && *s <= 'Z') {
|
||||
name[i++] = *s + 'a' - 'A';
|
||||
} else if (*s >= 'a' && *s <= 'z')
|
||||
name[i++] = *s;
|
||||
}
|
||||
if (i == 0)
|
||||
strlcpy(name, "noname", CTL_NAMEMAX);
|
||||
|
||||
/*
|
||||
* find the instance number of the control name
|
||||
*/
|
||||
for (i = 0, slot = d->slot; i < CTL_NSLOT; i++, slot++) {
|
||||
if (slot->ops == NULL)
|
||||
continue;
|
||||
if (strcmp(slot->name, name) == 0)
|
||||
umap |= (1 << i);
|
||||
}
|
||||
for (unit = 0; ; unit++) {
|
||||
if (unit == CTL_NSLOT) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 1) {
|
||||
dbg_puts(name);
|
||||
dbg_puts(": too many instances\n");
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
if ((umap & (1 << unit)) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* find a free controller slot with the same name/unit
|
||||
*/
|
||||
for (i = 0, slot = d->slot; i < CTL_NSLOT; i++, slot++) {
|
||||
if (slot->ops == NULL &&
|
||||
strcmp(slot->name, name) == 0 &&
|
||||
slot->unit == unit) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dbg_puts(name);
|
||||
dbg_putu(unit);
|
||||
dbg_puts(": found slot ");
|
||||
dbg_putu(i);
|
||||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* couldn't find a matching slot, pick oldest free slot
|
||||
* and set its name/unit
|
||||
*/
|
||||
bestser = 0;
|
||||
bestidx = CTL_NSLOT;
|
||||
for (i = 0, slot = d->slot; i < CTL_NSLOT; i++, slot++) {
|
||||
if (slot->ops != NULL)
|
||||
continue;
|
||||
ser = d->serial - slot->serial;
|
||||
if (ser > bestser) {
|
||||
bestser = ser;
|
||||
bestidx = i;
|
||||
}
|
||||
}
|
||||
if (bestidx == CTL_NSLOT) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 1) {
|
||||
dbg_puts(name);
|
||||
dbg_putu(unit);
|
||||
dbg_puts(": out of mixer slots\n");
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
slot = d->slot + bestidx;
|
||||
if (slot->name[0] != '\0')
|
||||
slot->vol = MIDI_MAXCTL;
|
||||
strlcpy(slot->name, name, CTL_NAMEMAX);
|
||||
slot->serial = d->serial++;
|
||||
slot->unit = unit;
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dbg_puts(name);
|
||||
dbg_putu(unit);
|
||||
dbg_puts(": overwritten slot ");
|
||||
dbg_putu(bestidx);
|
||||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
return bestidx;
|
||||
}
|
||||
|
||||
/*
|
||||
* allocate a new slot and register the given call-backs
|
||||
*/
|
||||
int
|
||||
dev_slotnew(struct dev *d, char *who, struct ctl_ops *ops, void *arg, int mmc)
|
||||
{
|
||||
int slot;
|
||||
struct ctl_slot *s;
|
||||
|
||||
slot = dev_mkslot(d, who);
|
||||
if (slot < 0)
|
||||
return -1;
|
||||
|
||||
s = d->slot + slot;
|
||||
s->ops = ops;
|
||||
s->arg = arg;
|
||||
s->tstate = mmc ? CTL_STOP : CTL_OFF;
|
||||
s->ops->vol(s->arg, s->vol);
|
||||
|
||||
if (APROC_OK(d->midi)) {
|
||||
ctl_slot(d->midi, slot);
|
||||
ctl_vol(d->midi, slot, s->vol);
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 2) {
|
||||
dev_slotdbg(d, slot);
|
||||
dbg_puts(": MIDI control not available\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
/*
|
||||
* release the given slot
|
||||
*/
|
||||
void
|
||||
dev_slotdel(struct dev *d, int slot)
|
||||
{
|
||||
struct ctl_slot *s;
|
||||
|
||||
s = d->slot + slot;
|
||||
s->ops = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* notifty the mixer that volume changed, called by whom allocad the slot using
|
||||
* ctl_slotnew(). Note: it doesn't make sens to call this from within the
|
||||
* call-back.
|
||||
*
|
||||
* XXX: set actual volume here and use only this interface. Now, this
|
||||
* can work because all streams have a slot
|
||||
*/
|
||||
void
|
||||
dev_slotvol(struct dev *d, int slot, unsigned vol)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dev_slotdbg(d, slot);
|
||||
dbg_puts(": changing volume to ");
|
||||
dbg_putu(vol);
|
||||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
d->slot[slot].vol = vol;
|
||||
if (APROC_OK(d->midi))
|
||||
ctl_vol(d->midi, slot, vol);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* check if there are controlled streams
|
||||
*/
|
||||
int
|
||||
dev_idle(struct dev *d)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
/*
|
||||
* XXX: this conditions breaks -aoff for thru boxes
|
||||
*/
|
||||
if (d->mode & MODE_THRU)
|
||||
return 0;
|
||||
|
||||
if (d->pstate != DEV_CLOSED)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* XXX: if the device is closed, we're sure there are no
|
||||
* slots in use, so the following test is useless
|
||||
*/
|
||||
for (i = 0, s = d->slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (s->ops)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* check that all clients controlled by MMC are ready to start,
|
||||
* if so, start them all but the caller
|
||||
*/
|
||||
int
|
||||
dev_try(struct dev *d, int slot)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (d->tstate != CTL_START) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dev_slotdbg(d, slot);
|
||||
dbg_puts(": server not started, delayd\n");
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
for (i = 0, s = d->slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (!s->ops || i == slot)
|
||||
continue;
|
||||
if (s->tstate != CTL_OFF && s->tstate != CTL_START) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dev_slotdbg(d, i);
|
||||
dbg_puts(": not ready, server delayed\n");
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
for (i = 0, s = d->slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (!s->ops || i == slot)
|
||||
continue;
|
||||
if (s->tstate == CTL_START) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dev_slotdbg(d, i);
|
||||
dbg_puts(": started\n");
|
||||
}
|
||||
#endif
|
||||
s->tstate = CTL_RUN;
|
||||
s->ops->start(s->arg);
|
||||
}
|
||||
}
|
||||
if (slot >= 0)
|
||||
d->slot[slot].tstate = CTL_RUN;
|
||||
d->tstate = CTL_RUN;
|
||||
if (APROC_OK(d->midi))
|
||||
ctl_full(d->midi, d->origin, d->rate, d->round, dev_getpos(d));
|
||||
dev_wakeup(d);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* notify the MMC layer that the stream is attempting
|
||||
* to start. If other streams are not ready, 0 is returned meaning
|
||||
* that the stream should wait. If other streams are ready, they
|
||||
* are started, and the caller should start immediately.
|
||||
*/
|
||||
int
|
||||
dev_slotstart(struct dev *d, int slot)
|
||||
{
|
||||
struct ctl_slot *s = d->slot + slot;
|
||||
|
||||
if (s->tstate == CTL_OFF || d->tstate == CTL_OFF)
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* if the server already started (the client missed the
|
||||
* start rendez-vous) or the server is stopped, then
|
||||
* tag the client as ``wanting to start''
|
||||
*/
|
||||
s->tstate = CTL_START;
|
||||
return dev_try(d, slot);
|
||||
}
|
||||
|
||||
/*
|
||||
* notify the MMC layer that the stream no longer is trying to
|
||||
* start (or that it just stopped), meaning that its ``start'' call-back
|
||||
* shouldn't be called anymore
|
||||
*/
|
||||
void
|
||||
dev_slotstop(struct dev *d, int slot)
|
||||
{
|
||||
struct ctl_slot *s = d->slot + slot;
|
||||
|
||||
/*
|
||||
* tag the stream as not trying to start,
|
||||
* unless MMC is turned off
|
||||
*/
|
||||
if (s->tstate != CTL_OFF)
|
||||
s->tstate = CTL_STOP;
|
||||
}
|
||||
|
||||
/*
|
||||
* start all slots simultaneously
|
||||
*/
|
||||
void
|
||||
dev_mmcstart(struct dev *d)
|
||||
{
|
||||
if (d->tstate == CTL_STOP) {
|
||||
d->tstate = CTL_START;
|
||||
(void)dev_try(d, -1);
|
||||
#ifdef DEBUG
|
||||
} else {
|
||||
if (debug_level >= 3) {
|
||||
dev_dbg(d);
|
||||
dbg_puts(": ignoring mmc start\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* stop all slots simultaneously
|
||||
*/
|
||||
void
|
||||
dev_mmcstop(struct dev *d)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
switch (d->tstate) {
|
||||
case CTL_START:
|
||||
d->tstate = CTL_STOP;
|
||||
return;
|
||||
case CTL_RUN:
|
||||
d->tstate = CTL_STOP;
|
||||
break;
|
||||
default:
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dev_dbg(d);
|
||||
dbg_puts(": ignored mmc stop\n");
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
for (i = 0, s = d->slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (!s->ops)
|
||||
continue;
|
||||
if (s->tstate == CTL_RUN) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dev_slotdbg(d, i);
|
||||
dbg_puts(": requested to stop\n");
|
||||
}
|
||||
#endif
|
||||
s->ops->stop(s->arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* relocate all slots simultaneously
|
||||
*/
|
||||
void
|
||||
dev_loc(struct dev *d, unsigned origin)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 2) {
|
||||
dbg_puts("server relocated to ");
|
||||
dbg_putu(origin);
|
||||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
if (d->tstate == CTL_RUN)
|
||||
dev_mmcstop(d);
|
||||
d->origin = origin;
|
||||
for (i = 0, s = d->slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (!s->ops)
|
||||
continue;
|
||||
s->ops->loc(s->arg, d->origin);
|
||||
}
|
||||
if (d->tstate == CTL_RUN)
|
||||
dev_mmcstart(d);
|
||||
}
|
||||
|
||||
/*
|
||||
* called at every clock tick by the mixer, delta is positive, unless
|
||||
* there's an overrun/underrun
|
||||
*/
|
||||
void
|
||||
dev_onmove(void *arg, int delta)
|
||||
{
|
||||
struct dev *d = (struct dev *)arg;
|
||||
|
||||
/*
|
||||
* don't send ticks before the start signal
|
||||
*/
|
||||
if (d->tstate != CTL_RUN)
|
||||
return;
|
||||
if (APROC_OK(d->midi))
|
||||
ctl_qfr(d->midi, d->rate, delta);
|
||||
}
|
||||
|
|
38
aucat/dev.h
38
aucat/dev.h
|
@ -60,6 +60,32 @@ struct dev {
|
|||
unsigned mode;
|
||||
char *path;
|
||||
} *ctl_list;
|
||||
|
||||
/* volume control and MMC/MTC */
|
||||
#define CTL_NSLOT 8
|
||||
#define CTL_NAMEMAX 8
|
||||
unsigned serial;
|
||||
struct ctl_slot {
|
||||
struct ctl_ops {
|
||||
void (*vol)(void *, unsigned);
|
||||
void (*start)(void *);
|
||||
void (*stop)(void *);
|
||||
void (*loc)(void *, unsigned);
|
||||
void (*quit)(void *);
|
||||
} *ops;
|
||||
void *arg;
|
||||
unsigned unit;
|
||||
char name[CTL_NAMEMAX];
|
||||
unsigned serial;
|
||||
unsigned vol;
|
||||
unsigned tstate;
|
||||
} slot[CTL_NSLOT];
|
||||
#define CTL_OFF 0 /* ignore MMC messages */
|
||||
#define CTL_STOP 1 /* stopped, can't start */
|
||||
#define CTL_START 2 /* attempting to start */
|
||||
#define CTL_RUN 3 /* started */
|
||||
unsigned tstate; /* one of above */
|
||||
unsigned origin; /* MTC start time */
|
||||
};
|
||||
|
||||
extern struct dev *dev_list;
|
||||
|
@ -84,4 +110,16 @@ void dev_attach(struct dev *, char *, unsigned,
|
|||
unsigned, int);
|
||||
void dev_setvol(struct dev *, struct abuf *, int);
|
||||
|
||||
void dev_slotdbg(struct dev *, int);
|
||||
int dev_slotnew(struct dev *, char *, struct ctl_ops *, void *, int);
|
||||
void dev_slotdel(struct dev *, int);
|
||||
void dev_slotvol(struct dev *, int, unsigned);
|
||||
|
||||
int dev_slotstart(struct dev *, int);
|
||||
void dev_slotstop(struct dev *, int);
|
||||
void dev_mmcstart(struct dev *);
|
||||
void dev_mmcstop(struct dev *);
|
||||
void dev_loc(struct dev *, unsigned);
|
||||
int dev_idle(struct dev *);
|
||||
|
||||
#endif /* !define(DEV_H) */
|
||||
|
|
|
@ -38,7 +38,7 @@ struct fileops {
|
|||
void (*close)(struct file *);
|
||||
unsigned (*read)(struct file *, unsigned char *, unsigned);
|
||||
unsigned (*write)(struct file *, unsigned char *, unsigned);
|
||||
void (*start)(struct file *);
|
||||
void (*start)(struct file *, void (*)(void *, int), void *);
|
||||
void (*stop)(struct file *);
|
||||
int (*nfds)(struct file *);
|
||||
int (*pollfd)(struct file *, struct pollfd *, int);
|
||||
|
|
588
aucat/midi.c
588
aucat/midi.c
|
@ -355,42 +355,6 @@ thru_new(char *name)
|
|||
return p;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void
|
||||
ctl_slotdbg(struct aproc *p, int slot)
|
||||
{
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (slot < 0) {
|
||||
dbg_puts("none");
|
||||
} else {
|
||||
s = p->u.ctl.slot + slot;
|
||||
dbg_puts(s->name);
|
||||
dbg_putu(s->unit);
|
||||
dbg_puts("(");
|
||||
dbg_putu(s->vol);
|
||||
dbg_puts(")/");
|
||||
switch (s->tstate) {
|
||||
case CTL_OFF:
|
||||
dbg_puts("off");
|
||||
break;
|
||||
case CTL_RUN:
|
||||
dbg_puts("run");
|
||||
break;
|
||||
case CTL_START:
|
||||
dbg_puts("sta");
|
||||
break;
|
||||
case CTL_STOP:
|
||||
dbg_puts("stp");
|
||||
break;
|
||||
default:
|
||||
dbg_puts("unk");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* send a message to the given output
|
||||
*/
|
||||
|
@ -454,11 +418,23 @@ ctl_sendmsg(struct aproc *p, struct abuf *ibuf, unsigned char *msg, unsigned len
|
|||
* send a quarter frame MTC message
|
||||
*/
|
||||
void
|
||||
ctl_qfr(struct aproc *p)
|
||||
ctl_qfr(struct aproc *p, unsigned rate, int delta)
|
||||
{
|
||||
unsigned char buf[2];
|
||||
unsigned data;
|
||||
int qfrlen;
|
||||
|
||||
p->u.ctl.delta += delta * MTC_SEC;
|
||||
|
||||
/*
|
||||
* don't send ticks during the count-down
|
||||
* XXX: test not useful, given while() condition
|
||||
*/
|
||||
if (p->u.ctl.delta < 0)
|
||||
return;
|
||||
|
||||
qfrlen = rate * (MTC_SEC / (4 * p->u.ctl.fps));
|
||||
while (p->u.ctl.delta >= qfrlen) {
|
||||
switch (p->u.ctl.qfr) {
|
||||
case 0:
|
||||
data = p->u.ctl.fr & 0xf;
|
||||
|
@ -512,18 +488,41 @@ ctl_qfr(struct aproc *p)
|
|||
p->u.ctl.qfr++;
|
||||
p->u.ctl.qfr &= 7;
|
||||
ctl_sendmsg(p, NULL, buf, 2);
|
||||
p->u.ctl.delta -= qfrlen;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* send a full frame MTC message
|
||||
*/
|
||||
void
|
||||
ctl_full(struct aproc *p)
|
||||
ctl_full(struct aproc *p, unsigned origin, unsigned rate, unsigned round, unsigned pos)
|
||||
{
|
||||
unsigned char buf[10];
|
||||
unsigned origin = p->u.ctl.origin;
|
||||
unsigned fps = p->u.ctl.fps;
|
||||
unsigned fps;
|
||||
|
||||
p->u.ctl.delta = MTC_SEC * pos;
|
||||
if (rate % (30 * 4 * round) == 0) {
|
||||
p->u.ctl.fps_id = MTC_FPS_30;
|
||||
p->u.ctl.fps = 30;
|
||||
} else if (rate % (25 * 4 * round) == 0) {
|
||||
p->u.ctl.fps_id = MTC_FPS_25;
|
||||
p->u.ctl.fps = 25;
|
||||
} else {
|
||||
p->u.ctl.fps_id = MTC_FPS_24;
|
||||
p->u.ctl.fps = 24;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
aproc_dbg(p);
|
||||
dbg_puts(": mtc full frame at ");
|
||||
dbg_puti(p->u.ctl.delta);
|
||||
dbg_puts(", ");
|
||||
dbg_puti(p->u.ctl.fps);
|
||||
dbg_puts(" fps\n");
|
||||
}
|
||||
#endif
|
||||
fps = p->u.ctl.fps;
|
||||
p->u.ctl.hr = (origin / (3600 * MTC_SEC)) % 24;
|
||||
p->u.ctl.min = (origin / (60 * MTC_SEC)) % 60;
|
||||
p->u.ctl.sec = (origin / MTC_SEC) % 60;
|
||||
|
@ -549,7 +548,7 @@ ctl_msg_info(struct aproc *p, int slot, char *msg)
|
|||
struct ctl_slot *s;
|
||||
struct sysex *x = (struct sysex *)msg;
|
||||
|
||||
s = p->u.ctl.slot + slot;
|
||||
s = p->u.ctl.dev->slot + slot;
|
||||
memset(x, 0, sizeof(struct sysex));
|
||||
x->start = SYSEX_START;
|
||||
x->type = SYSEX_TYPE_EDU;
|
||||
|
@ -568,7 +567,7 @@ ctl_msg_vol(struct aproc *p, int slot, char *msg)
|
|||
{
|
||||
struct ctl_slot *s;
|
||||
|
||||
s = p->u.ctl.slot + slot;
|
||||
s = p->u.ctl.dev->slot + slot;
|
||||
msg[0] = MIDI_CTL | slot;
|
||||
msg[1] = MIDI_CTLVOL;
|
||||
msg[2] = s->vol;
|
||||
|
@ -581,7 +580,7 @@ ctl_dump(struct aproc *p, struct abuf *obuf)
|
|||
unsigned char msg[sizeof(struct sysex)];
|
||||
struct ctl_slot *s;
|
||||
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
for (i = 0, s = p->u.ctl.dev->slot; i < CTL_NSLOT; i++, s++) {
|
||||
ctl_msg_info(p, i, msg);
|
||||
ctl_copymsg(obuf, msg, SYSEX_SIZE(mixinfo));
|
||||
ctl_msg_vol(p, i, msg);
|
||||
|
@ -597,462 +596,27 @@ ctl_dump(struct aproc *p, struct abuf *obuf)
|
|||
abuf_flush(obuf);
|
||||
}
|
||||
|
||||
/*
|
||||
* find the best matching free slot index (ie midi channel).
|
||||
* return -1, if there are no free slots anymore
|
||||
*/
|
||||
int
|
||||
ctl_getidx(struct aproc *p, char *who)
|
||||
{
|
||||
char *s;
|
||||
struct ctl_slot *slot;
|
||||
char name[CTL_NAMEMAX];
|
||||
unsigned i, unit, umap = 0;
|
||||
unsigned ser, bestser, bestidx;
|
||||
|
||||
/*
|
||||
* create a ``valid'' control name (lowcase, remove [^a-z], trucate)
|
||||
*/
|
||||
for (i = 0, s = who; ; s++) {
|
||||
if (i == CTL_NAMEMAX - 1 || *s == '\0') {
|
||||
name[i] = '\0';
|
||||
break;
|
||||
} else if (*s >= 'A' && *s <= 'Z') {
|
||||
name[i++] = *s + 'a' - 'A';
|
||||
} else if (*s >= 'a' && *s <= 'z')
|
||||
name[i++] = *s;
|
||||
}
|
||||
if (i == 0)
|
||||
strlcpy(name, "noname", CTL_NAMEMAX);
|
||||
|
||||
/*
|
||||
* find the instance number of the control name
|
||||
*/
|
||||
for (i = 0, slot = p->u.ctl.slot; i < CTL_NSLOT; i++, slot++) {
|
||||
if (slot->ops == NULL)
|
||||
continue;
|
||||
if (strcmp(slot->name, name) == 0)
|
||||
umap |= (1 << i);
|
||||
}
|
||||
for (unit = 0; ; unit++) {
|
||||
if (unit == CTL_NSLOT) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 1) {
|
||||
dbg_puts(name);
|
||||
dbg_puts(": too many instances\n");
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
if ((umap & (1 << unit)) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* find a free controller slot with the same name/unit
|
||||
*/
|
||||
for (i = 0, slot = p->u.ctl.slot; i < CTL_NSLOT; i++, slot++) {
|
||||
if (slot->ops == NULL &&
|
||||
strcmp(slot->name, name) == 0 &&
|
||||
slot->unit == unit) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dbg_puts(name);
|
||||
dbg_putu(unit);
|
||||
dbg_puts(": found slot ");
|
||||
dbg_putu(i);
|
||||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* couldn't find a matching slot, pick oldest free slot
|
||||
* and set its name/unit
|
||||
*/
|
||||
bestser = 0;
|
||||
bestidx = CTL_NSLOT;
|
||||
for (i = 0, slot = p->u.ctl.slot; i < CTL_NSLOT; i++, slot++) {
|
||||
if (slot->ops != NULL)
|
||||
continue;
|
||||
ser = p->u.ctl.serial - slot->serial;
|
||||
if (ser > bestser) {
|
||||
bestser = ser;
|
||||
bestidx = i;
|
||||
}
|
||||
}
|
||||
if (bestidx == CTL_NSLOT) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 1) {
|
||||
dbg_puts(name);
|
||||
dbg_putu(unit);
|
||||
dbg_puts(": out of mixer slots\n");
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
slot = p->u.ctl.slot + bestidx;
|
||||
if (slot->name[0] != '\0')
|
||||
slot->vol = MIDI_MAXCTL;
|
||||
strlcpy(slot->name, name, CTL_NAMEMAX);
|
||||
slot->serial = p->u.ctl.serial++;
|
||||
slot->unit = unit;
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
dbg_puts(name);
|
||||
dbg_putu(unit);
|
||||
dbg_puts(": overwritten slot ");
|
||||
dbg_putu(bestidx);
|
||||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
return bestidx;
|
||||
}
|
||||
|
||||
/*
|
||||
* check that all clients controlled by MMC are ready to start,
|
||||
* if so, start them all but the caller
|
||||
*/
|
||||
int
|
||||
ctl_trystart(struct aproc *p, int caller)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (p->u.ctl.tstate != CTL_START) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
ctl_slotdbg(p, caller);
|
||||
dbg_puts(": server not started, delayd\n");
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (!s->ops || i == caller)
|
||||
continue;
|
||||
if (s->tstate != CTL_OFF && s->tstate != CTL_START) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
ctl_slotdbg(p, i);
|
||||
dbg_puts(": not ready, server delayed\n");
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (!s->ops || i == caller)
|
||||
continue;
|
||||
if (s->tstate == CTL_START) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
ctl_slotdbg(p, i);
|
||||
dbg_puts(": started\n");
|
||||
}
|
||||
#endif
|
||||
s->tstate = CTL_RUN;
|
||||
s->ops->start(s->arg);
|
||||
}
|
||||
}
|
||||
if (caller >= 0)
|
||||
p->u.ctl.slot[caller].tstate = CTL_RUN;
|
||||
p->u.ctl.tstate = CTL_RUN;
|
||||
p->u.ctl.delta = MTC_SEC * dev_getpos(p->u.ctl.dev);
|
||||
if (p->u.ctl.dev->rate % (30 * 4 * p->u.ctl.dev->round) == 0) {
|
||||
p->u.ctl.fps_id = MTC_FPS_30;
|
||||
p->u.ctl.fps = 30;
|
||||
} else if (p->u.ctl.dev->rate % (25 * 4 * p->u.ctl.dev->round) == 0) {
|
||||
p->u.ctl.fps_id = MTC_FPS_25;
|
||||
p->u.ctl.fps = 25;
|
||||
} else {
|
||||
p->u.ctl.fps_id = MTC_FPS_24;
|
||||
p->u.ctl.fps = 24;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
ctl_slotdbg(p, caller);
|
||||
dbg_puts(": started server at ");
|
||||
dbg_puti(p->u.ctl.delta);
|
||||
dbg_puts(", ");
|
||||
dbg_puti(p->u.ctl.fps);
|
||||
dbg_puts(" mtc fps\n");
|
||||
}
|
||||
#endif
|
||||
dev_wakeup(p->u.ctl.dev);
|
||||
ctl_full(p);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* allocate a new slot and register the given call-backs
|
||||
*/
|
||||
int
|
||||
ctl_slotnew(struct aproc *p, char *who, struct ctl_ops *ops, void *arg, int mmc)
|
||||
{
|
||||
int idx;
|
||||
struct ctl_slot *s;
|
||||
unsigned char msg[sizeof(struct sysex)];
|
||||
|
||||
if (!APROC_OK(p)) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 2) {
|
||||
dbg_puts(who);
|
||||
dbg_puts(": MIDI control not available\n");
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
idx = ctl_getidx(p, who);
|
||||
if (idx < 0)
|
||||
return -1;
|
||||
|
||||
s = p->u.ctl.slot + idx;
|
||||
s->ops = ops;
|
||||
s->arg = arg;
|
||||
s->tstate = mmc ? CTL_STOP : CTL_OFF;
|
||||
s->ops->vol(s->arg, s->vol);
|
||||
ctl_msg_info(p, idx, msg);
|
||||
ctl_sendmsg(p, NULL, msg, SYSEX_SIZE(mixinfo));
|
||||
ctl_msg_vol(p, idx, msg);
|
||||
ctl_sendmsg(p, NULL, msg, 3);
|
||||
return idx;
|
||||
}
|
||||
|
||||
/*
|
||||
* release the given slot
|
||||
*/
|
||||
void
|
||||
ctl_slotdel(struct aproc *p, int index)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (!APROC_OK(p))
|
||||
return;
|
||||
p->u.ctl.slot[index].ops = NULL;
|
||||
if (!(p->flags & APROC_QUIT))
|
||||
return;
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (s->ops)
|
||||
return;
|
||||
}
|
||||
if (LIST_EMPTY(&p->ins))
|
||||
aproc_del(p);
|
||||
}
|
||||
|
||||
/*
|
||||
* called at every clock tick by the mixer, delta is positive, unless
|
||||
* there's an overrun/underrun
|
||||
*/
|
||||
void
|
||||
ctl_ontick(struct aproc *p, int delta)
|
||||
{
|
||||
int qfrlen;
|
||||
|
||||
/*
|
||||
* don't send ticks before the start signal
|
||||
*/
|
||||
if (p->u.ctl.tstate != CTL_RUN)
|
||||
return;
|
||||
|
||||
p->u.ctl.delta += delta * MTC_SEC;
|
||||
|
||||
/*
|
||||
* don't send ticks during the count-down
|
||||
*/
|
||||
if (p->u.ctl.delta < 0)
|
||||
return;
|
||||
|
||||
qfrlen = p->u.ctl.dev->rate * (MTC_SEC / (4 * p->u.ctl.fps));
|
||||
while (p->u.ctl.delta >= qfrlen) {
|
||||
ctl_qfr(p);
|
||||
p->u.ctl.delta -= qfrlen;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* notifty the mixer that volume changed, called by whom allocad the slot using
|
||||
* ctl_slotnew(). Note: it doesn't make sens to call this from within the
|
||||
* call-back.
|
||||
*/
|
||||
void
|
||||
ctl_slotvol(struct aproc *p, int slot, unsigned vol)
|
||||
ctl_vol(struct aproc *p, int slot, unsigned vol)
|
||||
{
|
||||
unsigned char msg[3];
|
||||
|
||||
if (!APROC_OK(p))
|
||||
return;
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
ctl_slotdbg(p, slot);
|
||||
dbg_puts(": changing volume to ");
|
||||
dbg_putu(vol);
|
||||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
p->u.ctl.slot[slot].vol = vol;
|
||||
ctl_msg_vol(p, slot, msg);
|
||||
ctl_sendmsg(p, NULL, msg, 3);
|
||||
}
|
||||
|
||||
/*
|
||||
* notify the MMC layer that the stream is attempting
|
||||
* to start. If other streams are not ready, 0 is returned meaning
|
||||
* that the stream should wait. If other streams are ready, they
|
||||
* are started, and the caller should start immediately.
|
||||
*/
|
||||
int
|
||||
ctl_slotstart(struct aproc *p, int slot)
|
||||
{
|
||||
struct ctl_slot *s = p->u.ctl.slot + slot;
|
||||
|
||||
if (!APROC_OK(p))
|
||||
return 1;
|
||||
if (s->tstate == CTL_OFF || p->u.ctl.tstate == CTL_OFF)
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* if the server already started (the client missed the
|
||||
* start rendez-vous) or the server is stopped, then
|
||||
* tag the client as ``wanting to start''
|
||||
*/
|
||||
s->tstate = CTL_START;
|
||||
return ctl_trystart(p, slot);
|
||||
}
|
||||
|
||||
/*
|
||||
* notify the MMC layer that the stream no longer is trying to
|
||||
* start (or that it just stopped), meaning that its ``start'' call-back
|
||||
* shouldn't be called anymore
|
||||
*/
|
||||
void
|
||||
ctl_slotstop(struct aproc *p, int slot)
|
||||
ctl_slot(struct aproc *p, int slot)
|
||||
{
|
||||
struct ctl_slot *s = p->u.ctl.slot + slot;
|
||||
unsigned char msg[sizeof(struct sysex)];
|
||||
|
||||
if (!APROC_OK(p))
|
||||
return;
|
||||
/*
|
||||
* tag the stream as not trying to start,
|
||||
* unless MMC is turned off
|
||||
*/
|
||||
if (s->tstate != CTL_OFF)
|
||||
s->tstate = CTL_STOP;
|
||||
}
|
||||
|
||||
/*
|
||||
* start all slots simultaneously
|
||||
*/
|
||||
void
|
||||
ctl_start(struct aproc *p)
|
||||
{
|
||||
if (!APROC_OK(p))
|
||||
return;
|
||||
if (p->u.ctl.tstate == CTL_STOP) {
|
||||
p->u.ctl.tstate = CTL_START;
|
||||
(void)ctl_trystart(p, -1);
|
||||
#ifdef DEBUG
|
||||
} else {
|
||||
if (debug_level >= 3) {
|
||||
aproc_dbg(p);
|
||||
dbg_puts(": ignoring mmc start\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* stop all slots simultaneously
|
||||
*/
|
||||
void
|
||||
ctl_stop(struct aproc *p)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (!APROC_OK(p))
|
||||
return;
|
||||
switch (p->u.ctl.tstate) {
|
||||
case CTL_START:
|
||||
p->u.ctl.tstate = CTL_STOP;
|
||||
return;
|
||||
case CTL_RUN:
|
||||
p->u.ctl.tstate = CTL_STOP;
|
||||
break;
|
||||
default:
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
aproc_dbg(p);
|
||||
dbg_puts(": ignored mmc stop\n");
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (!s->ops)
|
||||
continue;
|
||||
if (s->tstate == CTL_RUN) {
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 3) {
|
||||
ctl_slotdbg(p, i);
|
||||
dbg_puts(": requested to stop\n");
|
||||
}
|
||||
#endif
|
||||
s->ops->stop(s->arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* relocate all slots simultaneously
|
||||
*/
|
||||
void
|
||||
ctl_loc(struct aproc *p, unsigned origin)
|
||||
{
|
||||
unsigned i, tstate;
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (!APROC_OK(p))
|
||||
return;
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 2) {
|
||||
dbg_puts("server relocated to ");
|
||||
dbg_putu(origin);
|
||||
dbg_puts("\n");
|
||||
}
|
||||
#endif
|
||||
tstate = p->u.ctl.tstate;
|
||||
if (tstate == CTL_RUN)
|
||||
ctl_stop(p);
|
||||
p->u.ctl.origin = origin;
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (!s->ops)
|
||||
continue;
|
||||
s->ops->loc(s->arg, p->u.ctl.origin);
|
||||
}
|
||||
if (tstate == CTL_RUN)
|
||||
ctl_start(p);
|
||||
}
|
||||
|
||||
/*
|
||||
* check if there are controlled streams
|
||||
*/
|
||||
int
|
||||
ctl_idle(struct aproc *p)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (!APROC_OK(p))
|
||||
return 1;
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (s->ops)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
ctl_msg_info(p, slot, msg);
|
||||
ctl_sendmsg(p, NULL, msg, SYSEX_SIZE(mixinfo));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1083,7 +647,7 @@ ctl_ev(struct aproc *p, struct abuf *ibuf)
|
|||
chan = ibuf->r.midi.msg[0] & MIDI_CHANMASK;
|
||||
if (chan >= CTL_NSLOT)
|
||||
return;
|
||||
slot = p->u.ctl.slot + chan;
|
||||
slot = p->u.ctl.dev->slot + chan;
|
||||
slot->vol = ibuf->r.midi.msg[2];
|
||||
if (slot->ops == NULL)
|
||||
return;
|
||||
|
@ -1110,7 +674,7 @@ ctl_ev(struct aproc *p, struct abuf *ibuf)
|
|||
dbg_puts(": mmc stop\n");
|
||||
}
|
||||
#endif
|
||||
ctl_stop(p);
|
||||
dev_mmcstop(p->u.ctl.dev);
|
||||
break;
|
||||
case SYSEX_MMC_START:
|
||||
if (len != SYSEX_SIZE(start))
|
||||
|
@ -1121,7 +685,7 @@ ctl_ev(struct aproc *p, struct abuf *ibuf)
|
|||
dbg_puts(": mmc start\n");
|
||||
}
|
||||
#endif
|
||||
ctl_start(p);
|
||||
dev_mmcstart(p->u.ctl.dev);
|
||||
break;
|
||||
case SYSEX_MMC_LOC:
|
||||
if (len != SYSEX_SIZE(loc) ||
|
||||
|
@ -1139,10 +703,10 @@ ctl_ev(struct aproc *p, struct abuf *ibuf)
|
|||
fps = 30;
|
||||
break;
|
||||
default:
|
||||
p->u.ctl.origin = 0;
|
||||
/* XXX: should dev_mmcstop() here */
|
||||
return;
|
||||
}
|
||||
ctl_loc(p,
|
||||
dev_loc(p->u.ctl.dev,
|
||||
(x->u.loc.hr & 0x1f) * 3600 * MTC_SEC +
|
||||
x->u.loc.min * 60 * MTC_SEC +
|
||||
x->u.loc.sec * MTC_SEC +
|
||||
|
@ -1216,32 +780,14 @@ ctl_out(struct aproc *p, struct abuf *obuf)
|
|||
void
|
||||
ctl_eof(struct aproc *p, struct abuf *ibuf)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (!(p->flags & APROC_QUIT))
|
||||
return;
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (s->ops != NULL)
|
||||
s->ops->quit(s->arg);
|
||||
}
|
||||
if (LIST_EMPTY(&p->ins))
|
||||
if ((p->flags & APROC_QUIT) && LIST_EMPTY(&p->ins))
|
||||
aproc_del(p);
|
||||
}
|
||||
|
||||
void
|
||||
ctl_hup(struct aproc *p, struct abuf *obuf)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
if (!(p->flags & APROC_QUIT))
|
||||
return;
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (s->ops)
|
||||
return;
|
||||
}
|
||||
if (LIST_EMPTY(&p->ins))
|
||||
if ((p->flags & APROC_QUIT) && LIST_EMPTY(&p->ins))
|
||||
aproc_del(p);
|
||||
}
|
||||
|
||||
|
@ -1254,18 +800,6 @@ ctl_newin(struct aproc *p, struct abuf *ibuf)
|
|||
ibuf->r.midi.st = 0;
|
||||
}
|
||||
|
||||
void
|
||||
ctl_done(struct aproc *p)
|
||||
{
|
||||
unsigned i;
|
||||
struct ctl_slot *s;
|
||||
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
if (s->ops != NULL)
|
||||
s->ops->quit(s->arg);
|
||||
}
|
||||
}
|
||||
|
||||
struct aproc_ops ctl_ops = {
|
||||
"ctl",
|
||||
ctl_in,
|
||||
|
@ -1276,27 +810,15 @@ struct aproc_ops ctl_ops = {
|
|||
NULL, /* newout */
|
||||
NULL, /* ipos */
|
||||
NULL, /* opos */
|
||||
ctl_done
|
||||
NULL,
|
||||
};
|
||||
|
||||
struct aproc *
|
||||
ctl_new(char *name, struct dev *dev)
|
||||
{
|
||||
struct aproc *p;
|
||||
struct ctl_slot *s;
|
||||
unsigned i;
|
||||
|
||||
p = aproc_new(&ctl_ops, name);
|
||||
p->u.ctl.dev = dev;
|
||||
p->u.ctl.serial = 0;
|
||||
p->u.ctl.tstate = CTL_STOP;
|
||||
for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
|
||||
p->u.ctl.slot[i].unit = i;
|
||||
p->u.ctl.slot[i].ops = NULL;
|
||||
p->u.ctl.slot[i].vol = MIDI_MAXCTL;
|
||||
p->u.ctl.slot[i].tstate = CTL_OFF;
|
||||
p->u.ctl.slot[i].serial = p->u.ctl.serial++;
|
||||
p->u.ctl.slot[i].name[0] = '\0';
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
|
15
aucat/midi.h
15
aucat/midi.h
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: midi.h,v 1.9 2010/06/04 06:15:28 ratchov Exp $ */
|
||||
/* $OpenBSD$ */
|
||||
/*
|
||||
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
|
||||
*
|
||||
|
@ -22,15 +22,10 @@ struct dev;
|
|||
struct aproc *thru_new(char *);
|
||||
struct aproc *ctl_new(char *, struct dev *);
|
||||
|
||||
int ctl_slotnew(struct aproc *, char *, struct ctl_ops *, void *, int);
|
||||
void ctl_slotdel(struct aproc *, int);
|
||||
void ctl_slotvol(struct aproc *, int, unsigned);
|
||||
int ctl_slotstart(struct aproc *, int);
|
||||
void ctl_slotstop(struct aproc *, int);
|
||||
void ctl_ontick(struct aproc *, int);
|
||||
|
||||
void ctl_stop(struct aproc *);
|
||||
void ctl_start(struct aproc *);
|
||||
int ctl_idle(struct aproc *);
|
||||
void ctl_slot(struct aproc *, int);
|
||||
void ctl_vol(struct aproc *, int, unsigned);
|
||||
void ctl_full(struct aproc *, unsigned, unsigned, unsigned, unsigned);
|
||||
void ctl_qfr(struct aproc *, unsigned, int);
|
||||
|
||||
#endif /* !defined(MIDI_H) */
|
||||
|
|
|
@ -42,6 +42,8 @@ struct siofile {
|
|||
unsigned rtickets, rbpf;
|
||||
unsigned bufsz;
|
||||
int started;
|
||||
void (*onmove)(void *, int);
|
||||
void *arg;
|
||||
#ifdef DEBUG
|
||||
long long wtime, utime;
|
||||
#endif
|
||||
|
@ -50,7 +52,7 @@ struct siofile {
|
|||
void siofile_close(struct file *);
|
||||
unsigned siofile_read(struct file *, unsigned char *, unsigned);
|
||||
unsigned siofile_write(struct file *, unsigned char *, unsigned);
|
||||
void siofile_start(struct file *);
|
||||
void siofile_start(struct file *, void (*)(void *, int), void *);
|
||||
void siofile_stop(struct file *);
|
||||
int siofile_nfds(struct file *);
|
||||
int siofile_pollfd(struct file *, struct pollfd *, int);
|
||||
|
@ -189,12 +191,12 @@ siofile_cb(void *addr, int delta)
|
|||
p = f->file.wproc;
|
||||
if (p && p->ops->opos)
|
||||
p->ops->opos(p, NULL, delta);
|
||||
}
|
||||
if (delta != 0) {
|
||||
p = f->file.rproc;
|
||||
if (p && p->ops->ipos)
|
||||
p->ops->ipos(p, NULL, delta);
|
||||
}
|
||||
if (f->onmove)
|
||||
f->onmove(f->arg, delta);
|
||||
f->wtickets += delta * f->wbpf;
|
||||
f->rtickets += delta * f->rbpf;
|
||||
}
|
||||
|
@ -305,7 +307,7 @@ siofile_new(struct fileops *ops, char *path, unsigned *rmode,
|
|||
}
|
||||
|
||||
void
|
||||
siofile_start(struct file *file)
|
||||
siofile_start(struct file *file, void (*cb)(void *, int), void *arg)
|
||||
{
|
||||
struct siofile *f = (struct siofile *)file;
|
||||
|
||||
|
@ -328,6 +330,8 @@ siofile_start(struct file *file)
|
|||
dbg_puts(": started\n");
|
||||
}
|
||||
#endif
|
||||
f->onmove = cb;
|
||||
f->arg = arg;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -336,6 +340,7 @@ siofile_stop(struct file *file)
|
|||
struct siofile *f = (struct siofile *)file;
|
||||
|
||||
f->started = 0;
|
||||
f->onmove = NULL;
|
||||
if (!sio_eof(f->hdl) && !sio_stop(f->hdl)) {
|
||||
#ifdef DEBUG
|
||||
dbg_puts(f->file.name);
|
||||
|
|
37
aucat/sock.c
37
aucat/sock.c
|
@ -62,12 +62,10 @@ sock_dbg(struct sock *f)
|
|||
};
|
||||
static char *rstates[] = { "rdat", "rmsg", "rret" };
|
||||
static char *wstates[] = { "widl", "wmsg", "wdat" };
|
||||
struct aproc *midi;
|
||||
|
||||
midi = f->dev ? f->dev->midi : NULL;
|
||||
if (f->slot >= 0 && APROC_OK(midi)) {
|
||||
dbg_puts(midi->u.ctl.slot[f->slot].name);
|
||||
dbg_putu(midi->u.ctl.slot[f->slot].unit);
|
||||
if (f->slot >= 0) {
|
||||
dbg_puts(f->dev->slot[f->slot].name);
|
||||
dbg_putu(f->dev->slot[f->slot].unit);
|
||||
} else
|
||||
dbg_puts(f->pipe.file.name);
|
||||
dbg_puts("/");
|
||||
|
@ -121,7 +119,7 @@ rsock_done(struct aproc *p)
|
|||
f->pipe.file.rproc = NULL;
|
||||
if (f->pipe.file.wproc) {
|
||||
if (f->slot >= 0)
|
||||
ctl_slotdel(f->dev->midi, f->slot);
|
||||
dev_slotdel(f->dev, f->slot);
|
||||
aproc_del(f->pipe.file.wproc);
|
||||
file_del(&f->pipe.file);
|
||||
}
|
||||
|
@ -226,7 +224,7 @@ wsock_done(struct aproc *p)
|
|||
f->pipe.file.wproc = NULL;
|
||||
if (f->pipe.file.rproc) {
|
||||
if (f->slot >= 0)
|
||||
ctl_slotdel(f->dev->midi, f->slot);
|
||||
dev_slotdel(f->dev, f->slot);
|
||||
aproc_del(f->pipe.file.rproc);
|
||||
file_del(&f->pipe.file);
|
||||
}
|
||||
|
@ -375,7 +373,7 @@ sock_freebuf(struct sock *f)
|
|||
wbuf = LIST_FIRST(&f->pipe.file.wproc->ins);
|
||||
rbuf = LIST_FIRST(&f->pipe.file.rproc->outs);
|
||||
if (rbuf || wbuf)
|
||||
ctl_slotstop(f->dev->midi, f->slot);
|
||||
dev_slotstop(f->dev, f->slot);
|
||||
if (rbuf)
|
||||
abuf_eof(rbuf);
|
||||
if (wbuf)
|
||||
|
@ -428,7 +426,7 @@ sock_allocbuf(struct sock *f)
|
|||
f->pstate = SOCK_START;
|
||||
} else {
|
||||
f->pstate = SOCK_READY;
|
||||
if (ctl_slotstart(f->dev->midi, f->slot))
|
||||
if (dev_slotstart(f->dev, f->slot))
|
||||
(void)sock_attach(f, 0);
|
||||
}
|
||||
}
|
||||
|
@ -586,7 +584,7 @@ sock_reset(struct sock *f)
|
|||
switch (f->pstate) {
|
||||
case SOCK_START:
|
||||
case SOCK_READY:
|
||||
if (ctl_slotstart(f->dev->midi, f->slot)) {
|
||||
if (dev_slotstart(f->dev, f->slot)) {
|
||||
(void)sock_attach(f, 1);
|
||||
f->pstate = SOCK_RUN;
|
||||
}
|
||||
|
@ -926,11 +924,8 @@ sock_setpar(struct sock *f)
|
|||
}
|
||||
#ifdef DEBUG
|
||||
if (debug_level >= 2) {
|
||||
if (APROC_OK(f->dev->midi)) {
|
||||
dbg_puts(f->dev->midi->u.ctl.slot[f->slot].name);
|
||||
dbg_putu(f->dev->midi->u.ctl.slot[f->slot].unit);
|
||||
} else
|
||||
dbg_puts(f->pipe.file.name);
|
||||
dbg_puts(f->dev->slot[f->slot].name);
|
||||
dbg_putu(f->dev->slot[f->slot].unit);
|
||||
dbg_puts(": buffer size = ");
|
||||
dbg_putu(f->bufsz);
|
||||
if (f->mode & MODE_PLAY) {
|
||||
|
@ -1082,9 +1077,7 @@ sock_hello(struct sock *f)
|
|||
f->xrun = (f->opt->mmc) ? XRUN_SYNC : XRUN_IGNORE;
|
||||
f->bufsz = f->dev->bufsz;
|
||||
f->round = f->dev->round;
|
||||
f->slot = ctl_slotnew(f->dev->midi, p->who,
|
||||
&ctl_sockops, f,
|
||||
f->opt->mmc);
|
||||
f->slot = dev_slotnew(f->dev, p->who, &ctl_sockops, f, f->opt->mmc);
|
||||
if (f->slot < 0)
|
||||
return 0;
|
||||
f->pstate = SOCK_INIT;
|
||||
|
@ -1225,7 +1218,7 @@ sock_execmsg(struct sock *f)
|
|||
* see how this is fixed in wav.c
|
||||
*/
|
||||
if ((f->pstate == SOCK_START || f->pstate == SOCK_READY) &&
|
||||
ctl_slotstart(f->dev->midi, f->slot))
|
||||
dev_slotstart(f->dev, f->slot))
|
||||
(void)sock_attach(f, 1);
|
||||
if (f->wstate != SOCK_WDATA || f->wtodo == 0)
|
||||
sock_freebuf(f);
|
||||
|
@ -1336,7 +1329,7 @@ sock_execmsg(struct sock *f)
|
|||
}
|
||||
sock_setvol(f, ctl);
|
||||
if (f->slot >= 0)
|
||||
ctl_slotvol(f->dev->midi, f->slot, ctl);
|
||||
dev_slotvol(f->dev, f->slot, ctl);
|
||||
f->rtodo = sizeof(struct amsg);
|
||||
f->rstate = SOCK_RMSG;
|
||||
break;
|
||||
|
@ -1585,10 +1578,10 @@ sock_read(struct sock *f)
|
|||
}
|
||||
/*
|
||||
* XXX: sock_attach() may not start if there's not enough
|
||||
* samples queued, if so ctl_slotstart() will trigger
|
||||
* samples queued, if so dev_slotstart() will trigger
|
||||
* other streams, but this one won't start.
|
||||
*/
|
||||
if (f->pstate == SOCK_READY && ctl_slotstart(f->dev->midi, f->slot))
|
||||
if (f->pstate == SOCK_READY && dev_slotstart(f->dev, f->slot))
|
||||
(void)sock_attach(f, 0);
|
||||
break;
|
||||
case SOCK_RRET:
|
||||
|
|
25
aucat/wav.c
25
aucat/wav.c
|
@ -185,12 +185,11 @@ void
|
|||
wav_dbg(struct wav *f)
|
||||
{
|
||||
static char *pstates[] = { "cfg", "ini", "sta", "rdy", "run", "mid" };
|
||||
struct aproc *midi = f->dev ? f->dev->midi : NULL;
|
||||
|
||||
dbg_puts("wav(");
|
||||
if (f->slot >= 0 && APROC_OK(midi)) {
|
||||
dbg_puts(midi->u.ctl.slot[f->slot].name);
|
||||
dbg_putu(midi->u.ctl.slot[f->slot].unit);
|
||||
if (f->slot >= 0) {
|
||||
dbg_puts(f->dev->slot[f->slot].name);
|
||||
dbg_putu(f->dev->slot[f->slot].unit);
|
||||
} else
|
||||
dbg_puts(f->pipe.file.name);
|
||||
dbg_puts(")/");
|
||||
|
@ -407,7 +406,7 @@ wav_allocbuf(struct wav *f)
|
|||
dbg_puts(": allocating buffers\n");
|
||||
}
|
||||
#endif
|
||||
if (f->pstate == WAV_READY && ctl_slotstart(d->midi, f->slot))
|
||||
if (f->pstate == WAV_READY && dev_slotstart(d, f->slot))
|
||||
(void)wav_attach(f, 0);
|
||||
}
|
||||
|
||||
|
@ -431,7 +430,7 @@ wav_freebuf(struct wav *f)
|
|||
}
|
||||
#endif
|
||||
if (rbuf || wbuf)
|
||||
ctl_slotstop(f->dev->midi, f->slot);
|
||||
dev_slotstop(f->dev, f->slot);
|
||||
if (rbuf)
|
||||
abuf_eof(rbuf);
|
||||
if (wbuf)
|
||||
|
@ -448,7 +447,7 @@ wav_reset(struct wav *f)
|
|||
switch (f->pstate) {
|
||||
case WAV_START:
|
||||
case WAV_READY:
|
||||
if (ctl_slotstart(f->dev->midi, f->slot))
|
||||
if (dev_slotstart(f->dev, f->slot))
|
||||
(void)wav_attach(f, 1);
|
||||
/* PASSTHROUGH */
|
||||
case WAV_RUN:
|
||||
|
@ -493,7 +492,7 @@ wav_init(struct wav *f)
|
|||
wav_midiattach(f);
|
||||
return 1;
|
||||
}
|
||||
f->slot = ctl_slotnew(f->dev->midi, "wav", &ctl_wavops, f, 1);
|
||||
f->slot = dev_slotnew(f->dev, "wav", &ctl_wavops, f, 1);
|
||||
f->pstate = WAV_INIT;
|
||||
if ((f->mode & f->dev->mode) != f->mode) {
|
||||
#ifdef DEBUG
|
||||
|
@ -527,14 +526,14 @@ wav_seekmmc(struct wav *f)
|
|||
* don't make other stream wait for us
|
||||
*/
|
||||
if (f->slot >= 0)
|
||||
ctl_slotstart(f->dev->midi, f->slot);
|
||||
dev_slotstart(f->dev, f->slot);
|
||||
return 0;
|
||||
}
|
||||
if (!pipe_seek(&f->pipe.file, f->mmcpos)) {
|
||||
wav_exit(f);
|
||||
return 0;
|
||||
}
|
||||
if (f->mode & MODE_RECMASK)
|
||||
if ((f->mode & MODE_RECMASK) && f->mmcpos > f->endpos)
|
||||
f->endpos = f->mmcpos;
|
||||
if (f->hdr == HDR_WAV)
|
||||
f->wbytes = WAV_DATAMAX - f->mmcpos;
|
||||
|
@ -567,7 +566,7 @@ wav_rdata(struct wav *f)
|
|||
f->pstate = WAV_READY;
|
||||
/* PASSTHROUGH */
|
||||
case WAV_READY:
|
||||
if (ctl_slotstart(f->dev->midi, f->slot))
|
||||
if (dev_slotstart(f->dev, f->slot))
|
||||
(void)wav_attach(f, 0);
|
||||
break;
|
||||
case WAV_RUN:
|
||||
|
@ -928,7 +927,7 @@ rwav_done(struct aproc *p)
|
|||
struct wav *f = (struct wav *)p->u.io.file;
|
||||
|
||||
if (f->slot >= 0)
|
||||
ctl_slotdel(f->dev->midi, f->slot);
|
||||
dev_slotdel(f->dev, f->slot);
|
||||
f->slot = -1;
|
||||
rfile_done(p);
|
||||
}
|
||||
|
@ -981,7 +980,7 @@ wwav_done(struct aproc *p)
|
|||
struct wav *f = (struct wav *)p->u.io.file;
|
||||
|
||||
if (f->slot >= 0)
|
||||
ctl_slotdel(f->dev->midi, f->slot);
|
||||
dev_slotdel(f->dev, f->slot);
|
||||
f->slot = -1;
|
||||
wfile_done(p);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue