/* $OpenBSD: dev.c,v 1.78 2012/04/04 11:16:24 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Device abstraction module * * This module exposes a ``enhanced device'' that uses aproc * structures framework; it does conversions on the fly and can * handle multiple streams. The enhanced device starts and stops * automatically, when streams are attached, and provides * primitives for MIDI control * * From the main loop, the device is used as follows: * * 1. create the device using dev_new_xxx() * 2. call dev_run() in the event loop * 3. destroy the device using dev_del() * 4. continue running the event loop to drain * * The device is used as follows from aproc context: * * 1. open the device with dev_ref() * 2. negociate parameters (mode, rate, ...) * 3. create your stream (ie allocate and fill abufs) * 4. attach your stream atomically: * - first call dev_wakeup() to ensure device is not suspended * - possibly fetch dynamic parameters (eg. dev_getpos()) * - attach your buffers with dev_attach() * 5. close your stream, ie abuf_eof() or abuf_hup() * 6. close the device with dev_unref() * * The device has the following states: * * CLOSED sio_open() is not called, it's not ready and * no streams can be attached; dev_ref() must * be called to open the device * * INIT device is opened, processing chain is ready, but * DMA is not started yet. Streams can attach, * in which case device will automatically switch * to the START state * * START at least one stream is attached, play buffers * are primed (if necessary) DMA is ready and * will start immeadiately (next cycle) * * RUN DMA is started. New streams can attach. If the * device is idle (all streams are closed and * finished draining), then the device * automatically switches to INIT or CLOSED */ #include #include #include #include #include "abuf.h" #include "aproc.h" #include "conf.h" #include "dev.h" #include "pipe.h" #include "miofile.h" #include "siofile.h" #include "midi.h" #include "opt.h" #ifdef DEBUG #include "dbg.h" #endif int dev_open(struct dev *); 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; unsigned dev_sndnum = 0, dev_thrnum = 0; #ifdef DEBUG void dev_dbg(struct dev *d) { if (d->num >= DEV_NMAX) { dbg_puts("thr"); dbg_putu(d->num - DEV_NMAX); } else { dbg_puts("snd"); dbg_putu(d->num); } } #endif /* * Create a sndio device */ struct dev * dev_new(char *path, unsigned mode, unsigned bufsz, unsigned round, unsigned hold, unsigned autovol) { struct dev *d; unsigned *pnum, i; pnum = (mode & MODE_THRU) ? &dev_thrnum : &dev_sndnum; if (*pnum == DEV_NMAX) { #ifdef DEBUG if (debug_level >= 1) dbg_puts("too many devices\n"); #endif return NULL; } d = malloc(sizeof(struct dev)); if (d == NULL) { perror("malloc"); exit(1); } d->num = (*pnum)++; if (mode & MODE_THRU) d->num += DEV_NMAX; d->ctl_list = NULL; d->path = path; d->reqmode = mode; aparams_init(&d->reqopar, NCHAN_MAX, 0, 0); aparams_init(&d->reqipar, NCHAN_MAX, 0, 0); d->reqbufsz = bufsz; d->reqround = round; d->hold = hold; d->autovol = autovol; d->autostart = 0; d->refcnt = 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->master = MIDI_MAXCTL; d->origin = 0; d->tstate = CTL_STOP; d->next = dev_list; dev_list = d; return d; } /* * adjust device parameters and mode */ void dev_adjpar(struct dev *d, unsigned mode, struct aparams *ipar, struct aparams *opar) { d->reqmode |= (mode | MODE_MIDIMASK); if (mode & MODE_REC) aparams_grow(&d->reqipar, ipar); if (mode & MODE_PLAY) aparams_grow(&d->reqopar, opar); } /* * Initialize the device with the current parameters */ int dev_init(struct dev *d) { if ((d->reqmode & (MODE_AUDIOMASK | MODE_MIDIMASK)) == 0) { #ifdef DEBUG dev_dbg(d); dbg_puts(": has no streams, skipped\n"); #endif return 1; } if (d->hold && d->pstate == DEV_CLOSED && !dev_open(d)) { dev_del(d); return 0; } return 1; } /* * Add a MIDI port to the device */ int devctl_add(struct dev *d, char *path, unsigned mode) { struct devctl *c; c = malloc(sizeof(struct devctl)); if (c == NULL) { perror("malloc"); exit(1); } c->path = path; c->mode = mode; c->next = d->ctl_list; d->ctl_list = c; if (d->pstate != DEV_CLOSED && !devctl_open(d, c)) return 0; return 1; } /* * Open a MIDI device and connect it to the thru box */ int devctl_open(struct dev *d, struct devctl *c) { struct file *f; struct abuf *rbuf = NULL, *wbuf = NULL; struct aproc *rproc, *wproc; f = (struct file *)miofile_new(&miofile_ops, c->path, c->mode); if (f == NULL) return 0; if (c->mode & MODE_MIDIIN) { rproc = rfile_new(f); rbuf = abuf_new(MIDI_BUFSZ, &aparams_none); aproc_setout(rproc, rbuf); } if (c->mode & MODE_MIDIOUT) { wproc = wfile_new(f); wbuf = abuf_new(MIDI_BUFSZ, &aparams_none); aproc_setin(wproc, wbuf); } dev_midiattach(d, rbuf, wbuf); return 1; } /* * Open the device with the dev_reqxxx capabilities. Setup a mixer, demuxer, * monitor, midi control, and any necessary conversions. */ int dev_open(struct dev *d) { struct file *f; struct devctl *c; struct aparams par; struct aproc *conv; struct abuf *buf; unsigned siomode, cmin, cmax, rate; d->mode = d->reqmode; d->round = d->reqround; d->bufsz = d->reqbufsz; d->ipar = d->reqipar; d->opar = d->reqopar; d->rec = NULL; d->play = NULL; d->mon = NULL; d->mix = NULL; d->sub = NULL; d->submon = NULL; d->midi = NULL; d->rate = 0; if (d->opar.cmin > d->opar.cmax) { d->opar.cmin = 0; d->opar.cmax = 1; } if (d->ipar.cmin > d->ipar.cmax) { d->ipar.cmin = 0; d->ipar.cmax = 1; } if (d->opar.rate > d->ipar.rate) d->ipar.rate = d->opar.rate; else d->opar.rate = d->ipar.rate; if (d->opar.rate == 0) d->opar.rate = d->ipar.rate = 48000; /* XXX */ if (d->mode & MODE_THRU) d->mode &= ~MODE_AUDIOMASK; /* * If needed, open the device (ie create dev_rec and dev_play) */ if ((d->mode & (MODE_PLAY | MODE_REC)) && !(d->mode & MODE_LOOP)) { siomode = d->mode & (MODE_PLAY | MODE_REC); f = (struct file *)siofile_new(&siofile_ops, d->path, &siomode, &d->ipar, &d->opar, &d->bufsz, &d->round); if (f == NULL) { #ifdef DEBUG if (debug_level >= 1) { dev_dbg(d); dbg_puts(": "); dbg_puts(d->path); dbg_puts(": failed to open audio device\n"); } #endif return 0; } if (!(siomode & MODE_PLAY)) d->mode &= ~(MODE_PLAY | MODE_MON); if (!(siomode & MODE_REC)) d->mode &= ~MODE_REC; if ((d->mode & (MODE_PLAY | MODE_REC)) == 0) { #ifdef DEBUG if (debug_level >= 1) { dev_dbg(d); dbg_puts(": mode not supported by device\n"); } #endif return 0; } d->rate = d->mode & MODE_REC ? d->ipar.rate : d->opar.rate; if (d->mode & MODE_REC) { d->rec = rsio_new(f); d->rec->refs++; } if (d->mode & MODE_PLAY) { d->play = wsio_new(f); d->play->refs++; } } if (d->mode & MODE_LOOP) { if (d->mode & MODE_MON) { #ifdef DEBUG if (debug_level >= 1) { dbg_puts("monitoring not allowed " "in loopback mode\n"); } #endif return 0; } if ((d->mode & MODE_PLAYREC) != MODE_PLAYREC) { #ifdef DEBUG if (debug_level >= 1) { dbg_puts("both play and record streams " "required in loopback mode\n"); } #endif return 0; } if (d->ctl_list) { #ifdef DEBUG if (debug_level >= 1) { dbg_puts("MIDI control not allowed " "in loopback mode\n"); } #endif return 0; } cmin = (d->ipar.cmin < d->opar.cmin) ? d->ipar.cmin : d->opar.cmin; cmax = (d->ipar.cmax > d->opar.cmax) ? d->ipar.cmax : d->opar.cmax; rate = (d->ipar.rate > d->opar.rate) ? d->ipar.rate : d->opar.rate; aparams_init(&par, cmin, cmax, rate); d->ipar = par; d->opar = par; d->rate = rate; d->round = rate; d->bufsz = 2 * d->round; } #ifdef DEBUG if (debug_level >= 2) { if (d->mode & MODE_REC) { dev_dbg(d); dbg_puts(": recording "); aparams_dbg(&d->ipar); dbg_puts("\n"); } if (d->mode & MODE_PLAY) { dev_dbg(d); dbg_puts(": playing "); aparams_dbg(&d->opar); dbg_puts("\n"); } } #endif /* * Create the midi control end, or a simple thru box * if there's no device */ if (d->mode & MODE_MIDIMASK) { d->midi = midi_new("midi", (d->mode & MODE_THRU) ? NULL : d); d->midi->refs++; } /* * Create mixer, demuxer and monitor */ if (d->mode & MODE_PLAY) { d->mix = mix_new("play", d->bufsz, d->round, d->autovol, MIDI_TO_ADATA(d->master)); d->mix->refs++; } if (d->mode & MODE_REC) { d->sub = sub_new("rec", d->bufsz, d->round); d->sub->refs++; } if (d->mode & MODE_LOOP) { /* * connect mixer out to demuxer in */ buf = abuf_new(d->bufsz, &d->opar); aproc_setout(d->mix, buf); aproc_setin(d->sub, buf); d->mix->flags |= APROC_QUIT; d->sub->flags |= APROC_QUIT; } if (d->rec) { aparams_init(&par, d->ipar.cmin, d->ipar.cmax, d->rate); /* * Create device <-> demuxer buffer */ buf = abuf_new(d->bufsz, &d->ipar); aproc_setout(d->rec, buf); /* * Insert a converter, if needed. */ if (!aparams_eqenc(&d->ipar, &par)) { conv = dec_new("rec", &d->ipar); aproc_setin(conv, buf); buf = abuf_new(d->round, &par); aproc_setout(conv, buf); } d->ipar = par; aproc_setin(d->sub, buf); } if (d->play) { aparams_init(&par, d->opar.cmin, d->opar.cmax, d->rate); /* * Create device <-> mixer buffer */ buf = abuf_new(d->bufsz, &d->opar); aproc_setin(d->play, buf); /* * Append a converter, if needed. */ if (!aparams_eqenc(&par, &d->opar)) { conv = enc_new("play", &d->opar); aproc_setout(conv, buf); buf = abuf_new(d->round, &par); aproc_setin(conv, buf); } d->opar = par; aproc_setout(d->mix, buf); } if (d->mode & MODE_MON) { d->mon = mon_new("mon", d->bufsz); d->mon->refs++; buf = abuf_new(d->bufsz, &d->opar); aproc_setout(d->mon, buf); /* * Append a "sub" to which clients will connect. */ d->submon = sub_new("mon", d->bufsz, d->round); d->submon->refs++; aproc_setin(d->submon, buf); /* * Attach to the mixer */ d->mix->u.mix.mon = d->mon; d->mon->refs++; } #ifdef DEBUG if (debug_level >= 2) { if (d->mode & (MODE_PLAY | MODE_RECMASK)) { dev_dbg(d); dbg_puts(": block size is "); dbg_putu(d->round); dbg_puts(" frames, using "); dbg_putu(d->bufsz / d->round); dbg_puts(" blocks\n"); } } #endif d->pstate = DEV_INIT; for (c = d->ctl_list; c != NULL; c = c->next) { if (!devctl_open(d, c)) { #ifdef DEBUG if (debug_level >= 1) { dbg_puts(c->path); dbg_puts(": couldn't open MIDI port\n"); } #endif dev_close(d); return 0; } } return 1; } /* * Cleanly stop and drain everything and close the device * once both play chain and record chain are gone. */ void dev_close(struct dev *d) { struct file *f; /* * if the device is starting, ensure it actually starts * so buffers are drained, else clear any buffers */ switch (d->pstate) { case DEV_START: #ifdef DEBUG if (debug_level >= 3) { dev_dbg(d); dbg_puts(": draining device\n"); } #endif dev_start(d); break; case DEV_INIT: #ifdef DEBUG if (debug_level >= 3) { dev_dbg(d); dbg_puts(": flushing device\n"); } #endif dev_clear(d); break; } #ifdef DEBUG if (debug_level >= 2) { dev_dbg(d); dbg_puts(": closing device\n"); } #endif d->pstate = DEV_CLOSED; if (d->mix) { /* * Put the mixer in ``autoquit'' state and generate * EOF on all inputs connected it. Once buffers are * drained the mixer will terminate and shutdown the * device. * * NOTE: since file_eof() can destroy the file and * reorder the file_list, we have to restart the loop * after each call to file_eof(). */ if (APROC_OK(d->mix)) mix_quit(d->mix); /* * XXX: handle this in mix_done() */ if (APROC_OK(d->mix->u.mix.mon)) { d->mix->u.mix.mon->refs--; aproc_del(d->mix->u.mix.mon); d->mix->u.mix.mon = NULL; } restart_mix: LIST_FOREACH(f, &file_list, entry) { if (f->rproc != NULL && aproc_depend(d->mix, f->rproc)) { file_eof(f); goto restart_mix; } } } else if (d->sub) { /* * Same as above, but since there's no mixer, * we generate EOF on the record-end of the * device. */ restart_sub: LIST_FOREACH(f, &file_list, entry) { if (f->rproc != NULL && aproc_depend(d->sub, f->rproc)) { file_eof(f); goto restart_sub; } } } else if (d->submon) { /* * Same as above */ restart_submon: LIST_FOREACH(f, &file_list, entry) { if (f->rproc != NULL && aproc_depend(d->submon, f->rproc)) { file_eof(f); goto restart_submon; } } } if (d->midi) { d->midi->flags |= APROC_QUIT; if (LIST_EMPTY(&d->midi->ins)) aproc_del(d->midi); restart_midi: LIST_FOREACH(f, &file_list, entry) { if (f->rproc && aproc_depend(d->midi, f->rproc)) { file_eof(f); goto restart_midi; } } } if (d->mix) { if (--d->mix->refs == 0 && (d->mix->flags & APROC_ZOMB)) aproc_del(d->mix); d->mix = NULL; } if (d->play) { if (--d->play->refs == 0 && (d->play->flags & APROC_ZOMB)) aproc_del(d->play); d->play = NULL; } if (d->sub) { if (--d->sub->refs == 0 && (d->sub->flags & APROC_ZOMB)) aproc_del(d->sub); d->sub = NULL; } if (d->rec) { if (--d->rec->refs == 0 && (d->rec->flags & APROC_ZOMB)) aproc_del(d->rec); d->rec = NULL; } if (d->submon) { if (--d->submon->refs == 0 && (d->submon->flags & APROC_ZOMB)) aproc_del(d->submon); d->submon = NULL; } if (d->mon) { if (--d->mon->refs == 0 && (d->mon->flags & APROC_ZOMB)) aproc_del(d->mon); d->mon = NULL; } if (d->midi) { if (--d->midi->refs == 0 && (d->midi->flags & APROC_ZOMB)) aproc_del(d->midi); d->midi = NULL; } } /* * Unless the device is already in process of closing, request it to close */ 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); } /* * Free the device */ void dev_del(struct dev *d) { struct dev **p; dev_drain(d); for (p = &dev_list; *p != d; p = &(*p)->next) { #ifdef DEBUG if (*p == NULL) { dbg_puts("device to delete not on the list\n"); dbg_panic(); } #endif } *p = d->next; free(d); } /* * Attach a bi-directional MIDI stream to the MIDI device */ void dev_midiattach(struct dev *d, struct abuf *ibuf, struct abuf *obuf) { if (ibuf) aproc_setin(d->midi, ibuf); if (obuf) { aproc_setout(d->midi, obuf); if (ibuf) { ibuf->duplex = obuf; obuf->duplex = ibuf; } } } unsigned dev_roundof(struct dev *d, unsigned newrate) { return (d->round * newrate + d->rate / 2) / d->rate; } /* * Start the (paused) device. By default it's paused. */ void dev_start(struct dev *d) { struct file *f; #ifdef DEBUG if (debug_level >= 2) dbg_puts("starting device\n"); #endif d->pstate = DEV_RUN; if (d->mode & MODE_LOOP) return; if (APROC_OK(d->mix)) d->mix->flags |= APROC_DROP; if (APROC_OK(d->sub)) d->sub->flags |= APROC_DROP; if (APROC_OK(d->submon)) 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, dev_onmove, d); } else if (APROC_OK(d->rec) && d->rec->u.io.file) { f = d->rec->u.io.file; f->ops->start(f, dev_onmove, d); } } /* * Pause the device. This may trigger context switches, * so it shouldn't be called from aproc methods */ void dev_stop(struct dev *d) { struct file *f; #ifdef DEBUG if (debug_level >= 2) { dev_dbg(d); dbg_puts(": device stopped\n"); } #endif d->pstate = DEV_INIT; if (d->mode & MODE_LOOP) return; if (APROC_OK(d->play) && d->play->u.io.file) { f = d->play->u.io.file; f->ops->stop(f); } else if (APROC_OK(d->rec) && d->rec->u.io.file) { f = d->rec->u.io.file; f->ops->stop(f); } if (APROC_OK(d->mix)) d->mix->flags &= ~APROC_DROP; if (APROC_OK(d->sub)) d->sub->flags &= ~APROC_DROP; if (APROC_OK(d->submon)) d->submon->flags &= ~APROC_DROP; } int dev_ref(struct dev *d) { #ifdef DEBUG if (debug_level >= 3) { dev_dbg(d); dbg_puts(": device requested\n"); } #endif if (d->pstate == DEV_CLOSED && !dev_open(d)) { if (d->hold) dev_del(d); return 0; } d->refcnt++; return 1; } void dev_unref(struct dev *d) { #ifdef DEBUG if (debug_level >= 3) { dev_dbg(d); dbg_puts(": device released\n"); } #endif d->refcnt--; if (d->refcnt == 0 && d->pstate == DEV_INIT && !d->hold) dev_close(d); } /* * There are actions (like start/stop/close ... ) that may trigger aproc * operations, a thus cannot be started from aproc context. * To avoid problems, aprocs only change the s!tate of the device, * and actual operations are triggered from the main loop, * outside the aproc code path. * * The following routine invokes pending actions, returns 0 * on fatal error */ int dev_run(struct dev *d) { if (d->pstate == DEV_CLOSED) return 1; /* * check if device isn't gone */ if (((d->mode & MODE_PLAY) && !APROC_OK(d->mix)) || ((d->mode & MODE_REC) && !APROC_OK(d->sub)) || ((d->mode & MODE_MON) && !APROC_OK(d->submon))) { #ifdef DEBUG if (debug_level >= 2) { dev_dbg(d); dbg_puts(": device disappeared\n"); } #endif if (d->hold) { dev_del(d); return 0; } dev_close(d); return 1; } switch (d->pstate) { case DEV_INIT: /* nothing */ break; case DEV_START: dev_start(d); /* PASSTHROUGH */ case DEV_RUN: /* * if the device is not used, then stop it */ if ((!APROC_OK(d->mix) || d->mix->u.mix.idle > 2 * d->bufsz) && (!APROC_OK(d->sub) || d->sub->u.sub.idle > 2 * d->bufsz) && (!APROC_OK(d->submon) || d->submon->u.sub.idle > 2 * d->bufsz) && (!APROC_OK(d->midi) || d->tstate != CTL_RUN)) { #ifdef DEBUG if (debug_level >= 3) { dev_dbg(d); dbg_puts(": device idle, suspending\n"); } #endif dev_stop(d); if (d->refcnt == 0 && !d->hold) dev_close(d); else dev_clear(d); } break; } return 1; } /* * If the device is paused, then resume it. * This routine can be called from aproc context. */ void dev_wakeup(struct dev *d) { if (d->pstate == DEV_INIT) d->pstate = DEV_START; } /* * Find the end points connected to the mix/sub. */ int dev_getep(struct dev *d, unsigned mode, struct abuf **sibuf, struct abuf **sobuf) { struct abuf *ibuf, *obuf; if (mode & MODE_PLAY) { if (!APROC_OK(d->mix)) return 0; ibuf = *sibuf; for (;;) { if (!ibuf || !ibuf->rproc) { #ifdef DEBUG if (debug_level >= 3) { abuf_dbg(*sibuf); dbg_puts(": not connected to device\n"); } #endif return 0; } if (ibuf->rproc == d->mix) break; ibuf = LIST_FIRST(&ibuf->rproc->outs); } *sibuf = ibuf; } if (mode & MODE_REC) { if (!APROC_OK(d->sub)) return 0; obuf = *sobuf; for (;;) { if (!obuf || !obuf->wproc) { #ifdef DEBUG if (debug_level >= 3) { abuf_dbg(*sobuf); dbg_puts(": not connected to device\n"); } #endif return 0; } if (obuf->wproc == d->sub) break; obuf = LIST_FIRST(&obuf->wproc->ins); } *sobuf = obuf; } if (mode & MODE_MON) { if (!APROC_OK(d->submon)) return 0; obuf = *sobuf; for (;;) { if (!obuf || !obuf->wproc) { #ifdef DEBUG if (debug_level >= 3) { abuf_dbg(*sobuf); dbg_puts(": not connected to device\n"); } #endif return 0; } if (obuf->wproc == d->submon) break; obuf = LIST_FIRST(&obuf->wproc->ins); } *sobuf = obuf; } return 1; } /* * Sync play buffer to rec buffer (for instance when one of * them underruns/overruns). */ void dev_sync(struct dev *d, unsigned mode, struct abuf *ibuf, struct abuf *obuf) { int delta, offs; struct abuf *mbuf = NULL; if (!dev_getep(d, mode, &ibuf, &obuf)) return; /* * Calculate delta, the number of frames the play chain is ahead * of the record chain. It's necessary to schedule silences (or * drops) in order to start playback and record in sync. */ offs = 0; delta = 0; if (APROC_OK(d->mix)) { mbuf = LIST_FIRST(&d->mix->outs); offs += mbuf->w.mix.todo; delta += d->mix->u.mix.lat; } if (APROC_OK(d->sub)) delta += d->sub->u.sub.lat; #ifdef DEBUG if (debug_level >= 3) { dev_dbg(d); dbg_puts(": syncing device"); if (APROC_OK(d->mix)) { dbg_puts(", "); aproc_dbg(d->mix); dbg_puts(": todo = "); dbg_putu(mbuf->w.mix.todo); dbg_puts(": lat = "); dbg_putu(d->mix->u.mix.lat); } if (APROC_OK(d->sub)) { dbg_puts(", "); aproc_dbg(d->sub); dbg_puts(": lat = "); dbg_putu(d->sub->u.sub.lat); } dbg_puts("\n"); } #endif if (mode & MODE_PLAY) mix_drop(ibuf, -offs); if (mode & MODE_RECMASK) sub_silence(obuf, -(offs + delta)); } /* * 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) { struct abuf *mbuf = NULL; if (APROC_OK(d->mix)) { mbuf = LIST_FIRST(&d->mix->outs); return -(mbuf->w.mix.todo + d->mix->u.mix.lat); } else return 0; } /* * Attach the given input and output buffers to the mixer and the * multiplexer respectively. The operation is done synchronously, so * both buffers enter in sync. If buffers do not match play * and rec. */ void dev_attach(struct dev *d, char *name, unsigned mode, struct abuf *ibuf, struct aparams *sipar, unsigned inch, struct abuf *obuf, struct aparams *sopar, unsigned onch, unsigned xrun, int vol) { struct aparams ipar, opar; struct aproc *conv; unsigned round, nblk, nch; #ifdef DEBUG if ((!APROC_OK(d->mix) && (mode & MODE_PLAY)) || (!APROC_OK(d->sub) && (mode & MODE_REC)) || (!APROC_OK(d->submon) && (mode & MODE_MON))) { dev_dbg(d); dbg_puts(": mode beyond device mode, not attaching\n"); return; } #endif if (mode & MODE_PLAY) { ipar = *sipar; nblk = (d->bufsz / d->round + 3) / 4; round = dev_roundof(d, ipar.rate); nch = ipar.cmax - ipar.cmin + 1; if (!aparams_eqenc(&ipar, &d->opar)) { conv = dec_new(name, &ipar); ipar.bps = d->opar.bps; ipar.bits = d->opar.bits; ipar.sig = d->opar.sig; ipar.le = d->opar.le; ipar.msb = d->opar.msb; aproc_setin(conv, ibuf); ibuf = abuf_new(nblk * round, &ipar); aproc_setout(conv, ibuf); } if (inch > 0 && nch >= inch * 2) { conv = join_new(name); aproc_setin(conv, ibuf); ipar.cmax = ipar.cmin + inch - 1; ibuf = abuf_new(nblk * round, &ipar); aproc_setout(conv, ibuf); } if (!aparams_eqrate(&ipar, &d->opar)) { conv = resamp_new(name, round, d->round); ipar.rate = d->opar.rate; round = d->round; aproc_setin(conv, ibuf); ibuf = abuf_new(nblk * round, &ipar); aproc_setout(conv, ibuf); } if (inch > 0 && nch * 2 <= inch) { conv = join_new(name); aproc_setin(conv, ibuf); ipar.cmax = ipar.cmin + inch - 1; ibuf = abuf_new(nblk * round, &ipar); aproc_setout(conv, ibuf); } aproc_setin(d->mix, ibuf); ibuf->r.mix.xrun = xrun; ibuf->r.mix.maxweight = vol; mix_setmaster(d->mix); } if (mode & MODE_REC) { opar = *sopar; round = dev_roundof(d, opar.rate); nblk = (d->bufsz / d->round + 3) / 4; nch = opar.cmax - opar.cmin + 1; if (!aparams_eqenc(&opar, &d->ipar)) { conv = enc_new(name, &opar); opar.bps = d->ipar.bps; opar.bits = d->ipar.bits; opar.sig = d->ipar.sig; opar.le = d->ipar.le; opar.msb = d->ipar.msb; aproc_setout(conv, obuf); obuf = abuf_new(nblk * round, &opar); aproc_setin(conv, obuf); } if (onch > 0 && nch >= onch * 2) { conv = join_new(name); aproc_setout(conv, obuf); opar.cmax = opar.cmin + onch - 1; obuf = abuf_new(nblk * round, &opar); aproc_setin(conv, obuf); } if (!aparams_eqrate(&opar, &d->ipar)) { conv = resamp_new(name, d->round, round); opar.rate = d->ipar.rate; round = d->round; aproc_setout(conv, obuf); obuf = abuf_new(nblk * round, &opar); aproc_setin(conv, obuf); } if (onch > 0 && nch * 2 <= onch) { conv = join_new(name); aproc_setout(conv, obuf); opar.cmax = opar.cmin + onch - 1; obuf = abuf_new(nblk * round, &opar); aproc_setin(conv, obuf); } aproc_setout(d->sub, obuf); obuf->w.sub.xrun = xrun; } if (mode & MODE_MON) { opar = *sopar; round = dev_roundof(d, opar.rate); nblk = (d->bufsz / d->round + 3) / 4; nch = opar.cmax - opar.cmin + 1; if (!aparams_eqenc(&opar, &d->opar)) { conv = enc_new(name, &opar); opar.bps = d->opar.bps; opar.bits = d->opar.bits; opar.sig = d->opar.sig; opar.le = d->opar.le; opar.msb = d->opar.msb; aproc_setout(conv, obuf); obuf = abuf_new(nblk * round, &opar); aproc_setin(conv, obuf); } if (onch > 0 && nch >= onch * 2) { conv = join_new(name); aproc_setout(conv, obuf); opar.cmax = opar.cmin + onch - 1; obuf = abuf_new(nblk * round, &opar); aproc_setin(conv, obuf); } if (!aparams_eqrate(&opar, &d->opar)) { conv = resamp_new(name, d->round, round); opar.rate = d->opar.rate; round = d->round; aproc_setout(conv, obuf); obuf = abuf_new(nblk * round, &opar); aproc_setin(conv, obuf); } if (onch > 0 && nch * 2 <= onch) { conv = join_new(name); aproc_setout(conv, obuf); opar.cmax = opar.cmin + onch - 1; obuf = abuf_new(nblk * round, &opar); aproc_setin(conv, obuf); } aproc_setout(d->submon, obuf); obuf->w.sub.xrun = xrun; } /* * Sync play to record. */ if ((mode & MODE_PLAY) && (mode & MODE_RECMASK)) { ibuf->duplex = obuf; obuf->duplex = ibuf; } dev_sync(d, mode, ibuf, obuf); } /* * Change the playback volume of the given stream. */ void dev_setvol(struct dev *d, struct abuf *ibuf, int vol) { #ifdef DEBUG if (debug_level >= 3) { abuf_dbg(ibuf); dbg_puts(": setting volume to "); dbg_putu(vol); dbg_puts("\n"); } #endif if (!dev_getep(d, MODE_PLAY, &ibuf, NULL)) { return; } ibuf->r.mix.vol = vol; } /* * Clear buffers of the play and record chains so that when the device * is started, playback and record start in sync. */ void dev_clear(struct dev *d) { struct abuf *buf; if (APROC_OK(d->mix)) { #ifdef DEBUG if (!LIST_EMPTY(&d->mix->ins)) { dev_dbg(d); dbg_puts(": play end not idle, can't clear device\n"); dbg_panic(); } #endif buf = LIST_FIRST(&d->mix->outs); while (buf) { abuf_clear(buf); buf = LIST_FIRST(&buf->rproc->outs); } mix_clear(d->mix); } if (APROC_OK(d->sub)) { #ifdef DEBUG if (!LIST_EMPTY(&d->sub->outs)) { dev_dbg(d); dbg_puts(": record end not idle, can't clear device\n"); dbg_panic(); } #endif buf = LIST_FIRST(&d->sub->ins); while (buf) { abuf_clear(buf); buf = LIST_FIRST(&buf->wproc->ins); } sub_clear(d->sub); } if (APROC_OK(d->submon)) { #ifdef DEBUG if (!LIST_EMPTY(&d->submon->outs)) { dev_dbg(d); dbg_puts(": monitoring end not idle, can't clear device\n"); dbg_panic(); } #endif buf = LIST_FIRST(&d->submon->ins); while (buf) { abuf_clear(buf); buf = LIST_FIRST(&buf->wproc->ins); } sub_clear(d->submon); 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)) { midi_send_slot(d->midi, slot); midi_send_vol(d->midi, slot, s->vol); midi_flush(d->midi); } 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)) { midi_send_vol(d->midi, slot, vol); midi_flush(d->midi); } } /* * 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)) { midi_send_full(d->midi, d->origin, d->rate, d->round, dev_getpos(d)); midi_flush(d->midi); } 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)) { midi_send_qfr(d->midi, d->rate, delta); midi_flush(d->midi); } } void dev_master(struct dev *d, unsigned master) { #ifdef DEBUG if (debug_level >= 3) { dev_dbg(d); dbg_puts(": changing master volume to "); dbg_putu(master); dbg_puts("\n"); } #endif d->master = master; if (APROC_OK(d->mix)) { d->mix->u.mix.master = MIDI_TO_ADATA(master); mix_setmaster(d->mix); } if (APROC_OK(d->midi)) { midi_send_master(d->midi); midi_flush(d->midi); } }