diff options
Diffstat (limited to 'sys/dev/sound/midi')
-rw-r--r-- | sys/dev/sound/midi/midi.c | 1057 | ||||
-rw-r--r-- | sys/dev/sound/midi/midi.h | 334 | ||||
-rw-r--r-- | sys/dev/sound/midi/midibuf.c | 403 | ||||
-rw-r--r-- | sys/dev/sound/midi/midibuf.h | 65 | ||||
-rw-r--r-- | sys/dev/sound/midi/midisynth.c | 532 | ||||
-rw-r--r-- | sys/dev/sound/midi/midisynth.h | 110 | ||||
-rw-r--r-- | sys/dev/sound/midi/miditypes.h | 34 | ||||
-rw-r--r-- | sys/dev/sound/midi/sequencer.c | 2606 | ||||
-rw-r--r-- | sys/dev/sound/midi/sequencer.h | 272 | ||||
-rw-r--r-- | sys/dev/sound/midi/timer.c | 564 | ||||
-rw-r--r-- | sys/dev/sound/midi/timer.h | 80 |
11 files changed, 6057 insertions, 0 deletions
diff --git a/sys/dev/sound/midi/midi.c b/sys/dev/sound/midi/midi.c new file mode 100644 index 0000000..07d2660 --- /dev/null +++ b/sys/dev/sound/midi/midi.c @@ -0,0 +1,1057 @@ +/* + * Main midi driver for FreeBSD. This file provides the main + * entry points for probe/attach and all i/o demultiplexing, including + * default routines for generic devices. + * + * (C) 1999 Seigo Tanimura + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * For each card type a template "mididev_info" structure contains + * all the relevant parameters, both for configuration and runtime. + * + * In this file we build tables of pointers to the descriptors for + * the various supported cards. The generic probe routine scans + * the table(s) looking for a matching entry, then invokes the + * board-specific probe routine. If successful, a pointer to the + * correct mididev_info is stored in mididev_last_probed, for subsequent + * use in the attach routine. The generic attach routine copies + * the template to a permanent descriptor (midi_info and + * friends), initializes all generic parameters, and calls the + * board-specific attach routine. + * + * On device calls, the generic routines do the checks on unit and + * device parameters, then call the board-specific routines if + * available, or try to perform the task using the default code. + * + * $FreeBSD$ + * + */ + +#include <dev/sound/midi/midi.h> + +static devclass_t midi_devclass; + +static d_open_t midiopen; +static d_close_t midiclose; +static d_ioctl_t midiioctl; +static d_read_t midiread; +static d_write_t midiwrite; +static d_poll_t midipoll; + +/* These functions are local. */ +static d_open_t midistat_open; +static d_close_t midistat_close; +static d_read_t midistat_read; +static int midi_initstatus(char *buf, int size); +static int midi_readstatus(char *buf, int *ptr, struct uio *uio); + +#define CDEV_MAJOR MIDI_CDEV_MAJOR +static struct cdevsw midi_cdevsw = { + /* open */ midiopen, + /* close */ midiclose, + /* read */ midiread, + /* write */ midiwrite, + /* ioctl */ midiioctl, + /* poll */ midipoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "midi", + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +/* + * descriptors for active devices. also used as the public softc + * of a device. + */ +static TAILQ_HEAD(,_mididev_info) midi_info; +static int nmidi, nsynth; +/* Mutex to protect midi_info, nmidi and nsynth. */ +static struct mtx midiinfo_mtx; +static int midiinfo_mtx_init; + +/* These make the buffer for /dev/midistat */ +static int midistatbusy; +static char midistatbuf[4096]; +static int midistatptr; + +SYSCTL_NODE(_hw, OID_AUTO, midi, CTLFLAG_RD, 0, "Midi driver"); + +int midi_debug; +SYSCTL_INT(_hw_midi, OID_AUTO, debug, CTLFLAG_RW, &midi_debug, 0, ""); + +midi_cmdtab cmdtab_midiioctl[] = { + {SNDCTL_MIDI_PRETIME, "SNDCTL_MIDI_PRETIME"}, + {SNDCTL_MIDI_MPUMODE, "SNDCTL_MIDI_MPUMODE"}, + {SNDCTL_MIDI_MPUCMD, "SNDCTL_MIDI_MPUCMD"}, + {SNDCTL_SYNTH_INFO, "SNDCTL_SYNTH_INFO"}, + {SNDCTL_MIDI_INFO, "SNDCTL_MIDI_INFO"}, + {SNDCTL_SYNTH_MEMAVL, "SNDCTL_SYNTH_MEMAVL"}, + {SNDCTL_FM_LOAD_INSTR, "SNDCTL_FM_LOAD_INSTR"}, + {SNDCTL_FM_4OP_ENABLE, "SNDCTL_FM_4OP_ENABLE"}, + {MIOSPASSTHRU, "MIOSPASSTHRU"}, + {MIOGPASSTHRU, "MIOGPASSTHRU"}, + {AIONWRITE, "AIONWRITE"}, + {AIOGSIZE, "AIOGSIZE"}, + {AIOSSIZE, "AIOSSIZE"}, + {AIOGFMT, "AIOGFMT"}, + {AIOSFMT, "AIOSFMT"}, + {AIOGMIX, "AIOGMIX"}, + {AIOSMIX, "AIOSMIX"}, + {AIOSTOP, "AIOSTOP"}, + {AIOSYNC, "AIOSYNC"}, + {AIOGCAP, "AIOGCAP"}, + {-1, NULL}, +}; + +/* + * This is the generic init routine. + * Must be called after device-specific init. + */ +int +midiinit(mididev_info *d, device_t dev) +{ + int unit; + + /* + * initialize standard parameters for the device. This can be + * overridden by device-specific configurations but better do + * here the generic things. + */ + + MIDI_DEBUG(printf("midiinit: unit %d.\n", d->unit)); + + unit = d->unit; + d->softc = device_get_softc(dev); + d->dev = dev; + d->magic = MAGIC(d->unit); /* debugging... */ + d->flags = 0; + d->fflags = 0; + d->midi_dbuf_in.unit_size = 1; + d->midi_dbuf_out.unit_size = 1; + d->midi_dbuf_passthru.unit_size = 1; + + mtx_unlock(&d->flagqueue_mtx); + + if (midi_devclass == NULL) { + midi_devclass = device_get_devclass(dev); + make_dev(&midi_cdevsw, MIDIMKMINOR(0, MIDI_DEV_STATUS), + UID_ROOT, GID_WHEEL, 0444, "midistat"); + } + make_dev(&midi_cdevsw, MIDIMKMINOR(unit, MIDI_DEV_MIDIN), + UID_ROOT, GID_WHEEL, 0666, "midi%d", unit); + + return 0 ; +} + +/* + * a small utility function which, given a device number, returns + * a pointer to the associated mididev_info struct, and sets the unit + * number. + */ +mididev_info * +get_mididev_info(dev_t i_dev, int *unit) +{ + int u; + + if (MIDIDEV(i_dev) != MIDI_DEV_MIDIN) + return NULL; + u = MIDIUNIT(i_dev); + if (unit) + *unit = u; + + return get_mididev_info_unit(u); +} + +/* + * a small utility function which, given a unit number, returns + * a pointer to the associated mididev_info struct. + */ +mididev_info * +get_mididev_info_unit(int unit) +{ + mididev_info *md; + + /* XXX */ + if (!midiinfo_mtx_init) { + midiinfo_mtx_init = 1; + mtx_init(&midiinfo_mtx, "midinf", NULL, MTX_DEF); + TAILQ_INIT(&midi_info); + } + + mtx_lock(&midiinfo_mtx); + TAILQ_FOREACH(md, &midi_info, md_link) { + if (md->unit == unit) + break; + } + mtx_unlock(&midiinfo_mtx); + + return md; +} + +/* + * a small utility function which, given a unit number, returns + * a pointer to the associated mididev_info struct with MDT_MIDI. + */ +mididev_info * +get_mididev_midi_unit(int unit) +{ + mididev_info *md; + + /* XXX */ + if (!midiinfo_mtx_init) { + midiinfo_mtx_init = 1; + mtx_init(&midiinfo_mtx, "midinf", NULL, MTX_DEF); + TAILQ_INIT(&midi_info); + } + + mtx_lock(&midiinfo_mtx); + TAILQ_FOREACH(md, &midi_info, md_link) { + if (md->midiunit == unit) + break; + } + mtx_unlock(&midiinfo_mtx); + + return md; +} + +/* + * a small utility function which, given a unit number, returns + * a pointer to the associated mididev_info struct with MDT_SYNTH. + */ +mididev_info * +get_mididev_synth_unit(int unit) +{ + mididev_info *md; + + /* XXX */ + if (!midiinfo_mtx_init) { + midiinfo_mtx_init = 1; + mtx_init(&midiinfo_mtx, "midinf", NULL, MTX_DEF); + TAILQ_INIT(&midi_info); + } + + mtx_lock(&midiinfo_mtx); + TAILQ_FOREACH(md, &midi_info, md_link) { + if (md->synthunit == unit) + break; + } + mtx_unlock(&midiinfo_mtx); + + return md; +} + +/* Create a new midi device info structure. */ +/* TODO: lock md, then exit. */ +mididev_info * +create_mididev_info_unit(int type, mididev_info *mdinf, synthdev_info *syninf) +{ + int unit; + mididev_info *md, *mdnew; + + /* XXX */ + if (!midiinfo_mtx_init) { + midiinfo_mtx_init = 1; + mtx_init(&midiinfo_mtx, "midinf", NULL, MTX_DEF); + TAILQ_INIT(&midi_info); + } + + /* As malloc(9) might block, allocate mididev_info now. */ + mdnew = malloc(sizeof(mididev_info), M_DEVBUF, M_WAITOK | M_ZERO); + if (mdnew == NULL) + return NULL; + bcopy(mdinf, mdnew, sizeof(mididev_info)); + bcopy(syninf, &mdnew->synth, sizeof(synthdev_info)); + midibuf_init(&mdnew->midi_dbuf_in); + midibuf_init(&mdnew->midi_dbuf_out); + midibuf_init(&mdnew->midi_dbuf_passthru); + mtx_init(&mdnew->flagqueue_mtx, "midflq", NULL, MTX_DEF); + mtx_init(&mdnew->synth.vc_mtx, "synsvc", NULL, MTX_DEF); + mtx_init(&mdnew->synth.status_mtx, "synsst", NULL, MTX_DEF); + + mtx_lock(&midiinfo_mtx); + + switch (type) { + case MDT_MIDI: + mdnew->midiunit = nmidi; + mdnew->synthunit = nmidi; + nmidi++; + break; + case MDT_SYNTH: + mdnew->midiunit = -1; + mdnew->synthunit = nsynth; + nsynth++; + break; + default: + mtx_unlock(&midiinfo_mtx); + midibuf_destroy(&mdnew->midi_dbuf_in); + midibuf_destroy(&mdnew->midi_dbuf_out); + midibuf_destroy(&mdnew->midi_dbuf_passthru); + mtx_destroy(&mdnew->flagqueue_mtx); + mtx_destroy(&mdnew->synth.vc_mtx); + mtx_destroy(&mdnew->synth.status_mtx); + free(mdnew, M_DEVBUF); + panic("unsupported device type"); + return NULL; + } + mdnew->mdtype = type; + + for (unit = 0 ; ; unit++) { + TAILQ_FOREACH(md, &midi_info, md_link) { + if (md->unit == unit) + break; + } + if (md == NULL) + break; + } + + mdnew->unit = unit; + mtx_lock(&mdnew->flagqueue_mtx); + TAILQ_INSERT_TAIL(&midi_info, mdnew, md_link); + + mtx_unlock(&midiinfo_mtx); + + return mdnew; +} + +/* Return the number of configured devices. */ +int +mididev_info_number(void) +{ + return nmidi + nsynth; +} + +/* Return the number of configured midi devices. */ +int +mididev_midi_number(void) +{ + return nmidi; +} + +/* Return the number of configured synth devices. */ +int +mididev_synth_number(void) +{ + return nsynth; +} + +/* + * here are the switches for the main functions. The switches do + * all necessary checks on the device number to make sure + * that the device is configured. They also provide some default + * functionalities so that device-specific drivers have to deal + * only with special cases. + */ + +static int +midiopen(dev_t i_dev, int flags, int mode, struct thread *td) +{ + int ret; + + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + ret = midi_open(i_dev, flags, mode, td); + break; + case MIDI_DEV_STATUS: + ret = midistat_open(i_dev, flags, mode, td); + break; + default: + ret = ENXIO; + break; + } + + return (ret); +} + +static int +midiclose(dev_t i_dev, int flags, int mode, struct thread *td) +{ + int ret; + + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + ret = midi_close(i_dev, flags, mode, td); + break; + case MIDI_DEV_STATUS: + ret = midistat_close(i_dev, flags, mode, td); + break; + default: + ret = ENXIO; + break; + } + + return (ret); +} + +static int +midiread(dev_t i_dev, struct uio * buf, int flag) +{ + int ret; + + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + ret = midi_read(i_dev, buf, flag); + break; + case MIDI_DEV_STATUS: + ret = midistat_read(i_dev, buf, flag); + break; + default: + ret = ENXIO; + break; + } + + return (ret); +} + +static int +midiwrite(dev_t i_dev, struct uio * buf, int flag) +{ + int ret; + + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + ret = midi_write(i_dev, buf, flag); + break; + default: + ret = ENXIO; + break; + } + + return (ret); +} + +static int +midiioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + int ret; + + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + ret = midi_ioctl(i_dev, cmd, arg, mode, td); + break; + default: + ret = ENXIO; + break; + } + + return (ret); +} + +static int +midipoll(dev_t i_dev, int events, struct thread *td) +{ + int ret; + + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + ret = midi_poll(i_dev, events, td); + break; + default: + ret = ENXIO; + break; + } + + return (ret); +} + +/* + * Followings are the generic methods in midi drivers. + */ + +int +midi_open(dev_t i_dev, int flags, int mode, struct thread *td) +{ + int dev, unit, ret; + mididev_info *d; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + MIDI_DEBUG(printf("midi_open: unit %d, flags 0x%x.\n", unit, flags)); + + if (d == NULL) + return (ENXIO); + + /* Mark this device busy. */ + mtx_lock(&d->flagqueue_mtx); + device_busy(d->dev); + if ((d->flags & MIDI_F_BUSY) != 0) { + mtx_unlock(&d->flagqueue_mtx); + printf("midi_open: unit %d is busy.\n", unit); + return (EBUSY); + } + d->fflags = flags; + d->flags |= MIDI_F_BUSY; + d->flags &= ~(MIDI_F_READING | MIDI_F_WRITING); + if ((d->fflags & O_NONBLOCK) != 0) + d->flags |= MIDI_F_NBIO; + + /* Init the queue. */ + if ((flags & FREAD) != 0) + midibuf_clear(&d->midi_dbuf_in); + if ((flags & FWRITE) != 0) { + midibuf_clear(&d->midi_dbuf_out); + midibuf_clear(&d->midi_dbuf_passthru); + } + + mtx_unlock(&d->flagqueue_mtx); + + if (d->open == NULL) + ret = 0; + else + ret = d->open(i_dev, flags, mode, td); + + mtx_lock(&d->flagqueue_mtx); + + /* Begin recording if nonblocking. */ + if ((d->flags & (MIDI_F_READING | MIDI_F_NBIO)) == MIDI_F_NBIO && (d->fflags & FREAD) != 0) + d->callback(d, MIDI_CB_START | MIDI_CB_RD); + + mtx_unlock(&d->flagqueue_mtx); + + MIDI_DEBUG(printf("midi_open: opened.\n")); + + return (ret); +} + +int +midi_close(dev_t i_dev, int flags, int mode, struct thread *td) +{ + int dev, unit, ret; + mididev_info *d; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + MIDI_DEBUG(printf("midi_close: unit %d.\n", unit)); + + if (d == NULL) + return (ENXIO); + + mtx_lock(&d->flagqueue_mtx); + + /* Stop recording and playing. */ + if ((d->flags & MIDI_F_READING) != 0) + d->callback(d, MIDI_CB_ABORT | MIDI_CB_RD); + if ((d->flags & MIDI_F_WRITING) != 0) + d->callback(d, MIDI_CB_ABORT | MIDI_CB_WR); + + /* Clear the queues. */ + if ((d->fflags & FREAD) != 0) + midibuf_clear(&d->midi_dbuf_in); + if ((d->fflags & FWRITE) != 0) { + midibuf_clear(&d->midi_dbuf_out); + midibuf_clear(&d->midi_dbuf_passthru); + } + + /* Stop playing and unmark this device busy. */ + d->flags &= ~MIDI_F_BUSY; + d->fflags = 0; + + device_unbusy(d->dev); + + mtx_unlock(&d->flagqueue_mtx); + + if (d->close == NULL) + ret = 0; + else + ret = d->close(i_dev, flags, mode, td); + + MIDI_DEBUG(printf("midi_close: closed.\n")); + + return (ret); +} + +int +midi_read(dev_t i_dev, struct uio * buf, int flag) +{ + int dev, unit, len, lenr, ret; + mididev_info *d ; + u_char *uiobuf; + + dev = minor(i_dev); + + d = get_mididev_info(i_dev, &unit); + MIDI_DEBUG(printf("midi_read: unit %d, resid %d.\n", unit, buf->uio_resid)); + + if (d == NULL) + return (ENXIO); + + ret = 0; + + len = buf->uio_resid; + lenr = 0; + + uiobuf = (u_char *)malloc(len, M_DEVBUF, M_WAITOK | M_ZERO); + if (uiobuf == NULL) + return (ENOMEM); + + mtx_lock(&d->flagqueue_mtx); + + /* Begin recording. */ + d->callback(d, MIDI_CB_START | MIDI_CB_RD); + + /* Have we got the data to read? */ + if ((d->flags & MIDI_F_NBIO) != 0 && d->midi_dbuf_in.rl == 0) + ret = EAGAIN; + else { + if ((d->flags & MIDI_F_NBIO) != 0 && len > d->midi_dbuf_in.rl) + len = d->midi_dbuf_in.rl; + ret = midibuf_seqread(&d->midi_dbuf_in, uiobuf, len, &lenr, + d->callback, d, MIDI_CB_START | MIDI_CB_RD, + &d->flagqueue_mtx); + } + + mtx_unlock(&d->flagqueue_mtx); + + if (lenr > 0) + ret = uiomove(uiobuf, lenr, buf); + + free(uiobuf, M_DEVBUF); + + MIDI_DEBUG(printf("midi_read: ret %d, resid %d.\n", ret, buf->uio_resid)); + + return (ret); +} + +int +midi_write(dev_t i_dev, struct uio * buf, int flag) +{ + int dev, unit, len, len2, lenw, ret; + mididev_info *d; + u_char *uiobuf; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + MIDI_DEBUG(printf("midi_write: unit %d.\n", unit)); + + if (d == NULL) + return (ENXIO); + + ret = 0; + + len = buf->uio_resid; + lenw = 0; + + uiobuf = (u_char *)malloc(len, M_DEVBUF, M_WAITOK | M_ZERO); + if (uiobuf == NULL) + return (ENOMEM); + + ret = uiomove(uiobuf, len, buf); + if (ret != 0) { + free(uiobuf, M_DEVBUF); + return (ret); + } + + mtx_lock(&d->flagqueue_mtx); + + /* Have we got the data to write? */ + if ((d->flags & MIDI_F_NBIO) != 0 && d->midi_dbuf_out.fl == 0) { + /* Begin playing. */ + d->callback(d, MIDI_CB_START | MIDI_CB_WR); + ret = EAGAIN; + } else { + len2 = len; + if ((d->flags & MIDI_F_NBIO) != 0 && len2 > d->midi_dbuf_out.fl) + len2 = d->midi_dbuf_out.fl; + ret = midibuf_seqwrite(&d->midi_dbuf_out, uiobuf, len2, &lenw, + d->callback, d, MIDI_CB_START | MIDI_CB_WR, + &d->flagqueue_mtx); + } + + mtx_unlock(&d->flagqueue_mtx); + + free(uiobuf, M_DEVBUF); + buf->uio_resid = len - lenw; + + return (ret); +} + +/* + * generic midi ioctl. Functions of the default driver can be + * overridden by the device-specific ioctl call. + * If a device-specific call returns ENOSYS (Function not implemented), + * the default driver is called. Otherwise, the returned value + * is passed up. + * + * The default handler, for many parameters, sets the value in the + * descriptor, sets MIDI_F_INIT, and calls the callback function with + * reason INIT. If successful, the callback returns 1 and the caller + * can update the parameter. + */ + +int +midi_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + int ret = ENOSYS, dev, unit; + mididev_info *d; + struct snd_size *sndsize; + snd_sync_parm *sp; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + if (d == NULL) + return (ENXIO); + + if (d->ioctl) + ret = d->ioctl(i_dev, cmd, arg, mode, td); + if (ret != ENOSYS) + return ret; + + /* + * pass control to the default ioctl handler. Set ret to 0 now. + */ + ret = 0; + + MIDI_DEBUG(printf("midi_ioctl: unit %d, cmd %s.\n", unit, midi_cmdname(cmd, cmdtab_midiioctl))); + + /* + * all routines are called with int. blocked. Make sure that + * ints are re-enabled when calling slow or blocking functions! + */ + switch(cmd) { + + /* + * we start with the new ioctl interface. + */ + case AIONWRITE: /* how many bytes can write ? */ + mtx_lock(&d->flagqueue_mtx); + *(int *)arg = d->midi_dbuf_out.fl; + mtx_unlock(&d->flagqueue_mtx); + MIDI_DEBUG(printf("midi_ioctl: fl %d.\n", *(int *)arg)); + break; + + case AIOSSIZE: /* set the current blocksize */ + sndsize = (struct snd_size *)arg; + MIDI_DEBUG(printf("midi_ioctl: play %d, rec %d.\n", sndsize->play_size, sndsize->rec_size)); + mtx_lock(&d->flagqueue_mtx); + if (sndsize->play_size <= d->midi_dbuf_out.unit_size && sndsize->rec_size <= d->midi_dbuf_in.unit_size) { + d->midi_dbuf_out.blocksize = d->midi_dbuf_out.unit_size; + d->midi_dbuf_in.blocksize = d->midi_dbuf_in.unit_size; + sndsize->play_size = d->midi_dbuf_out.blocksize; + sndsize->rec_size = d->midi_dbuf_in.blocksize; + d->flags &= ~MIDI_F_HAS_SIZE; + mtx_unlock(&d->flagqueue_mtx); + } + else { + if (sndsize->play_size > d->midi_dbuf_out.bufsize / 4) + sndsize->play_size = d->midi_dbuf_out.bufsize / 4; + if (sndsize->rec_size > d->midi_dbuf_in.bufsize / 4) + sndsize->rec_size = d->midi_dbuf_in.bufsize / 4; + /* Round up the size to the multiple of EV_SZ. */ + d->midi_dbuf_out.blocksize = + ((sndsize->play_size + d->midi_dbuf_out.unit_size - 1) + / d->midi_dbuf_out.unit_size) * d->midi_dbuf_out.unit_size; + d->midi_dbuf_in.blocksize = + ((sndsize->rec_size + d->midi_dbuf_in.unit_size - 1) + / d->midi_dbuf_in.unit_size) * d->midi_dbuf_in.unit_size; + sndsize->play_size = d->midi_dbuf_out.blocksize; + sndsize->rec_size = d->midi_dbuf_in.blocksize; + d->flags |= MIDI_F_HAS_SIZE; + mtx_unlock(&d->flagqueue_mtx); + } + + ret = 0; + break; + + case AIOGSIZE: /* get the current blocksize */ + sndsize = (struct snd_size *)arg; + mtx_lock(&d->flagqueue_mtx); + sndsize->play_size = d->midi_dbuf_out.blocksize; + sndsize->rec_size = d->midi_dbuf_in.blocksize; + mtx_unlock(&d->flagqueue_mtx); + MIDI_DEBUG(printf("midi_ioctl: play %d, rec %d.\n", sndsize->play_size, sndsize->rec_size)); + + ret = 0; + break; + + case AIOSTOP: + mtx_lock(&d->flagqueue_mtx); + if (*(int *)arg == AIOSYNC_PLAY) /* play */ + *(int *)arg = d->callback(d, MIDI_CB_STOP | MIDI_CB_WR); + else if (*(int *)arg == AIOSYNC_CAPTURE) + *(int *)arg = d->callback(d, MIDI_CB_STOP | MIDI_CB_RD); + else { + MIDI_DEBUG(printf("midi_ioctl: bad channel 0x%x.\n", *(int *)arg)); + *(int *)arg = 0 ; + } + mtx_unlock(&d->flagqueue_mtx); + break ; + + case AIOSYNC: + sp = (snd_sync_parm *)arg; + MIDI_DEBUG(printf("midi_ioctl: unimplemented, chan 0x%03lx pos %lu.\n", + sp->chan, + sp->pos)); + break; + /* + * here follow the standard ioctls (filio.h etc.) + */ + case FIONREAD: /* get # bytes to read */ + mtx_lock(&d->flagqueue_mtx); + *(int *)arg = d->midi_dbuf_in.rl; + mtx_unlock(&d->flagqueue_mtx); + MIDI_DEBUG(printf("midi_ioctl: rl %d.\n", *(int *)arg)); + break; + + case FIOASYNC: /*set/clear async i/o */ + MIDI_DEBUG(printf("FIOASYNC\n")); + break; + + case FIONBIO: /* set/clear non-blocking i/o */ + mtx_lock(&d->flagqueue_mtx); + if (*(int *)arg == 0) + d->flags &= ~MIDI_F_NBIO ; + else + d->flags |= MIDI_F_NBIO ; + mtx_unlock(&d->flagqueue_mtx); + MIDI_DEBUG(printf("midi_ioctl: arg %d.\n", *(int *)arg)); + break ; + + case MIOSPASSTHRU: /* set/clear passthru */ + mtx_lock(&d->flagqueue_mtx); + if (*(int *)arg == 0) + d->flags &= ~MIDI_F_PASSTHRU ; + else + d->flags |= MIDI_F_PASSTHRU ; + + /* Init the queue. */ + midibuf_clear(&d->midi_dbuf_passthru); + + mtx_unlock(&d->flagqueue_mtx); + MIDI_DEBUG(printf("midi_ioctl: passthru %d.\n", *(int *)arg)); + + /* FALLTHROUGH */ + case MIOGPASSTHRU: /* get passthru */ + mtx_lock(&d->flagqueue_mtx); + if ((d->flags & MIDI_F_PASSTHRU) != 0) + *(int *)arg = 1; + else + *(int *)arg = 0; + mtx_unlock(&d->flagqueue_mtx); + MIDI_DEBUG(printf("midi_ioctl: passthru %d.\n", *(int *)arg)); + break; + + default: + MIDI_DEBUG(printf("midi_ioctl: default ioctl midi%d subdev %d fn 0x%08lx fail\n", + unit, dev & 0xf, cmd)); + ret = EINVAL; + break ; + } + return ret ; +} + +int +midi_poll(dev_t i_dev, int events, struct thread *td) +{ + int unit, dev, ret, lim; + mididev_info *d; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + MIDI_DEBUG(printf("midi_poll: unit %d.\n", unit)); + + if (d == NULL) + return (ENXIO); + + ret = 0; + + mtx_lock(&d->flagqueue_mtx); + + /* Look up the apropriate queue and select it. */ + if ((events & (POLLOUT | POLLWRNORM)) != 0) { + /* Start playing. */ + d->callback(d, MIDI_CB_START | MIDI_CB_WR); + + /* Find out the boundary. */ + if ((d->flags & MIDI_F_HAS_SIZE) != 0) + lim = d->midi_dbuf_out.blocksize; + else + lim = d->midi_dbuf_out.unit_size; + if (d->midi_dbuf_out.fl < lim) + /* No enough space, record select. */ + selrecord(td, &d->midi_dbuf_out.sel); + else + /* We can write now. */ + ret |= events & (POLLOUT | POLLWRNORM); + } + if ((events & (POLLIN | POLLRDNORM)) != 0) { + /* Start recording. */ + d->callback(d, MIDI_CB_START | MIDI_CB_RD); + + /* Find out the boundary. */ + if ((d->flags & MIDI_F_HAS_SIZE) != 0) + lim = d->midi_dbuf_in.blocksize; + else + lim = d->midi_dbuf_in.unit_size; + if (d->midi_dbuf_in.rl < lim) + /* No data ready, record select. */ + selrecord(td, &d->midi_dbuf_in.sel); + else + /* We can write now. */ + ret |= events & (POLLIN | POLLRDNORM); + } + + mtx_unlock(&d->flagqueue_mtx); + + return (ret); +} + +void +midi_intr(mididev_info *d) +{ + if (d->intr != NULL) + d->intr(d->intrarg, d); +} + +/* Flush the output queue. */ +#define MIDI_SYNC_TIMEOUT 1 +int +midi_sync(mididev_info *d) +{ + int i, rl; + + mtx_assert(&d->flagqueue_mtx, MA_OWNED); + + MIDI_DEBUG(printf("midi_sync: unit %d.\n", d->unit)); + + while (d->midi_dbuf_out.rl > 0) { + if ((d->flags & MIDI_F_WRITING) == 0) + d->callback(d, MIDI_CB_START | MIDI_CB_WR); + rl = d->midi_dbuf_out.rl; + i = cv_timedwait_sig(&d->midi_dbuf_out.cv_out, + &d->flagqueue_mtx, + (d->midi_dbuf_out.bufsize * 10 * hz / 38400) + + MIDI_SYNC_TIMEOUT * hz); + if (i == EINTR || i == ERESTART) { + if (i == EINTR) + d->callback(d, MIDI_CB_STOP | MIDI_CB_WR); + return (i); + } + if (i == EWOULDBLOCK && rl == d->midi_dbuf_out.rl) { + /* A queue seems to be stuck up. Give up and clear the queue. */ + d->callback(d, MIDI_CB_STOP | MIDI_CB_WR); + midibuf_clear(&d->midi_dbuf_out); + return (0); + } + } + + return 0; +} + +/* + * These handle the status message of the midi drivers. + */ + +int +midistat_open(dev_t i_dev, int flags, int mode, struct thread *td) +{ + if (midistatbusy) + return (EBUSY); + + bzero(midistatbuf, sizeof(midistatbuf)); + midistatptr = 0; + if (midi_initstatus(midistatbuf, sizeof(midistatbuf) - 1)) + return (ENOMEM); + + midistatbusy = 1; + + return (0); +} + +int +midistat_close(dev_t i_dev, int flags, int mode, struct thread *td) +{ + midistatbusy = 0; + + return (0); +} + +int +midistat_read(dev_t i_dev, struct uio * buf, int flag) +{ + return midi_readstatus(midistatbuf, &midistatptr, buf); +} + +/* + * finally, some "libraries" + */ + +/* Inits the buffer for /dev/midistat. */ +static int +midi_initstatus(char *buf, int size) +{ + int i, p; + device_t dev; + mididev_info *md; + + p = 0; + p += snprintf(buf, size, "FreeBSD Midi Driver (newmidi) %s %s\nInstalled devices:\n", __DATE__, __TIME__); + for (i = 0 ; i < mididev_info_number() ; i++) { + md = get_mididev_info_unit(i); + if (!MIDICONFED(md)) + continue; + dev = devclass_get_device(midi_devclass, i); + if (p < size) + p += snprintf(&buf[p], size - p, "midi%d: <%s> %s\n", i, device_get_desc(dev), md->midistat); + else + return (1); + } + + return (0); +} + +/* Reads the status message. */ +static int +midi_readstatus(char *buf, int *ptr, struct uio *uio) +{ + int len; + + len = min(uio->uio_resid, strlen(&buf[*ptr])); + if (len > 0) { + uiomove(&buf[*ptr], len, uio); + *ptr += len; + } + + return (0); +} + +char +*midi_cmdname(int cmd, midi_cmdtab *tab) +{ + while (tab->name != NULL) { + if (cmd == tab->cmd) + return (tab->name); + tab++; + } + + return ("unknown"); +} diff --git a/sys/dev/sound/midi/midi.h b/sys/dev/sound/midi/midi.h new file mode 100644 index 0000000..0867654 --- /dev/null +++ b/sys/dev/sound/midi/midi.h @@ -0,0 +1,334 @@ +/* + * Include file for midi driver. + * + * Copyright by Seigo Tanimura 1999. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +/* + * first, include kernel header files. + */ + +#ifndef _MIDI_H_ +#define _MIDI_H_ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/ioccom.h> + +#include <sys/filio.h> +#include <sys/lock.h> +#include <sys/sockio.h> +#include <sys/fcntl.h> +#include <sys/tty.h> +#include <sys/proc.h> +#include <sys/sysctl.h> + +#include <sys/kernel.h> /* for DATA_SET */ + +#include <sys/module.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/uio.h> +#include <sys/syslog.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <sys/bus.h> +#include <machine/resource.h> +#include <machine/bus_memio.h> +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <machine/clock.h> /* for DELAY */ +#include <machine/limits.h> +#include <sys/soundcard.h> +#include <sys/rman.h> +#include <sys/mman.h> +#include <sys/poll.h> +#include <sys/mutex.h> +#include <sys/condvar.h> + +#include <dev/sound/midi/miditypes.h> +#include <dev/sound/midi/midibuf.h> +#include <dev/sound/midi/midisynth.h> + +#define MIDI_CDEV_MAJOR 30 + +/*#define MIDI_OUTOFGIANT*/ + +/* + * The order of mutex lock (from the first to the last) + * + * 1. sequencer flags, queues, timer and device list + * 2. midi synth voice and channel + * 3. midi synth status + * 4. generic midi flags and queues + * 5. midi device + */ + +/* + * descriptor of midi operations ... + * + */ + +struct _mididev_info { + + /* + * the first part of the descriptor is filled up from a + * template. + */ + char name[64]; + + int type; + + d_open_t *open; + d_close_t *close; + d_ioctl_t *ioctl; + midi_callback_t *callback; + + /* + * combinations of the following flags are used as second argument in + * the callback from the dma module to the device-specific routines. + */ + +#define MIDI_CB_RD 0x100 /* read callback */ +#define MIDI_CB_WR 0x200 /* write callback */ +#define MIDI_CB_REASON_MASK 0xff +#define MIDI_CB_START 0x01 /* start dma op */ +#define MIDI_CB_STOP 0x03 /* stop dma op */ +#define MIDI_CB_ABORT 0x04 /* abort dma op */ +#define MIDI_CB_INIT 0x05 /* init board parameters */ + + /* + * callback extensions + */ +#define MIDI_CB_DMADONE 0x10 +#define MIDI_CB_DMAUPDATE 0x11 +#define MIDI_CB_DMASTOP 0x12 + + /* init can only be called with int enabled and + * no pending DMA activity. + */ + + /* + * whereas from here, parameters are set at runtime. + * resources are stored in the softc of the device, + * not in the common structure. + */ + + int unit; /* unit number of the device */ + int midiunit; /* unit number for midi devices */ + int synthunit; /* unit number for synth devices */ + int mdtype; /* MDT_MIDI or MDT_SYNTH */ + void *softc; /* softc for the device */ + device_t dev; /* device_t for the device */ + + int bd_id; /* used to hold board-id info, eg. sb version, + * mss codec type, etc. etc. + */ + + struct mtx flagqueue_mtx; /* Mutex to protect flags and queues */ + + /* Queues */ + midi_dbuf midi_dbuf_in; /* midi input event/message queue */ + midi_dbuf midi_dbuf_out; /* midi output event/message queue */ + midi_dbuf midi_dbuf_passthru; /* midi passthru event/message queue */ + + /* + * these parameters describe the operation of the board. + * Generic things like busy flag, speed, etc are here. + */ + + /* Flags */ + volatile u_long flags; /* 32 bits, used for various purposes. */ + int fflags; /* file flag */ + + /* + * we have separate flags for read and write, although in some + * cases this is probably not necessary (e.g. because we cannot + * know how many processes are using the device, we cannot + * distinguish if open, close, abort are for a write or for a + * read). + */ + + /* + * the following flag is used by open-close routines + * to mark the status of the device. + */ +#define MIDI_F_BUSY 0x0001 /* has been opened */ + /* + * the next two are used to allow only one pending operation of + * each type. + */ +#define MIDI_F_READING 0x0004 /* have a pending read */ +#define MIDI_F_WRITING 0x0008 /* have a pending write */ + + /* + * flag used to mark a pending close. + */ +#define MIDI_F_CLOSING 0x0040 /* a pending close */ + + /* + * if user has not set block size, then make it adaptive + * (0.25s, or the perhaps last read/write ?) + */ +#define MIDI_F_HAS_SIZE 0x0080 /* user set block size */ + /* + * assorted flags related to operating mode. + */ +#define MIDI_F_STEREO 0x0100 /* doing stereo */ +#define MIDI_F_NBIO 0x0200 /* do non-blocking i/o */ +#define MIDI_F_PASSTHRU 0x0400 /* pass received data to output port */ + + /* + * these flags mark a pending abort on a r/w operation. + */ +#define MIDI_F_ABORTING 0x1000 /* a pending abort */ + + /* + * this is used to mark that board initialization is needed, e.g. + * because of a change in sampling rate, format, etc. -- It will + * be done at the next convenient time. + */ +#define MIDI_F_INIT 0x4000 /* changed parameters. need init */ + + int play_blocksize, rec_blocksize; /* blocksize for io and dma ops */ + +#define mwsel midi_dbuf_out.sel +#define mrsel midi_dbuf_in.sel + u_long nterrupts; /* counter of interrupts */ + u_long magic; +#define MAGIC(unit) ( 0xa4d10de0 + unit ) + void *device_data ; /* just in case it is needed...*/ + + midi_intr_t *intr; /* interrupt handler of the upper layer (ie sequencer) */ + void *intrarg; /* argument to interrupt handler */ + + /* The following is the interface from a midi sequencer to a midi device. */ + synthdev_info synth; + + /* This is the status message to display via /dev/midistat */ + char midistat[128]; + + /* The tailq entry of the next midi device. */ + TAILQ_ENTRY(_mididev_info) md_link; + + /* The tailq entry of the next midi device opened by a sequencer. */ + TAILQ_ENTRY(_mididev_info) md_linkseq; +} ; + +/* + * then ioctls and other stuff + */ + +#define NMIDI_MAX 16 /* Number of supported devices */ + +/* + * many variables should be reduced to a range. Here define a macro + */ + +#define RANGE(var, low, high) (var) = \ +((var)<(low)?(low) : (var)>(high)?(high) : (var)) + +/* + * convert dev_t to unit and dev + */ +#define MIDIMINOR(x) (minor(x)) +#define MIDIUNIT(x) ((MIDIMINOR(x) & 0x000000f0) >> 4) +#define MIDIDEV(x) (MIDIMINOR(x) & 0x0000000f) +#define MIDIMKMINOR(u, d) (((u) & 0x0f) << 4 | ((d) & 0x0f)) +#define MIDIMKDEV(m, u, d) (makedev((m), MIDIMKMINOR((u), (d)))) + +/* + * see if the device is configured + */ +#define MIDICONFED(x) ((x)->ioctl != NULL) + +/* + * finally, all default parameters + */ +#define MIDI_BUFFSIZE (1024) /* XXX */ + +#ifdef _KERNEL + +/* This is the generic midi drvier initializer. */ + int midiinit(mididev_info *d, device_t dev); + +/* This provides an access to the mididev_info. */ + mididev_info *get_mididev_info(dev_t i_dev, int *unit); + mididev_info *get_mididev_info_unit(int unit); + mididev_info *get_mididev_midi_unit(int unit); + mididev_info *get_mididev_synth_unit(int unit); + mididev_info *create_mididev_info_unit(int type, mididev_info *mdinf, synthdev_info *syninf); + int mididev_info_number(void); + int mididev_midi_number(void); + int mididev_synth_number(void); +#define MDT_MIDI (0) +#define MDT_SYNTH (1) + +/* These are the generic methods for a midi driver. */ + d_open_t midi_open; + d_close_t midi_close; + d_ioctl_t midi_ioctl; + d_read_t midi_read; + d_write_t midi_write; + d_poll_t midi_poll; + +/* Common interrupt handler */ +void midi_intr(mididev_info *); + +/* Sync output */ +int midi_sync(mididev_info *); + +struct _midi_cmdtab { + int cmd; + char * name; +}; +typedef struct _midi_cmdtab midi_cmdtab; + +char *midi_cmdname(int cmd, midi_cmdtab *tab); + +SYSCTL_DECL(_hw_midi); + +extern int midi_debug; +#define MIDI_DEBUG(x) \ + do { \ + if (midi_debug) { \ + (x); \ + } \ + } while(0) + +extern midi_cmdtab cmdtab_midiioctl[]; + +#endif /* _KERNEL */ + +/* + * Minor numbers for the midi driver. + */ + +#define MIDI_DEV_MIDIN 2 /* Raw midi access */ +#define MIDI_DEV_STATUS 15 /* /dev/midistat */ + +#endif /* _MIDI_H_ */ diff --git a/sys/dev/sound/midi/midibuf.c b/sys/dev/sound/midi/midibuf.c new file mode 100644 index 0000000..8b4eda7 --- /dev/null +++ b/sys/dev/sound/midi/midibuf.c @@ -0,0 +1,403 @@ +/* + * Copyright (C) 1999 Seigo Tanimura + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +/* + * This file implements a midi event/message queue. A midi + * event/message queue holds midi events and messages to + * transmit to or received from a midi interface. + */ + +#include <dev/sound/midi/midi.h> + +/* Some macros to handle the queue. */ +#define DATA_AVAIL(dbuf) ((dbuf)->rl) +#define SPACE_AVAIL(dbuf) ((dbuf)->fl) + +static void queuerawdata(midi_dbuf *dbuf, char *data, int len); +static void dequeuerawdata(midi_dbuf *dbuf, char *data, int len); +static void copyrawdata(midi_dbuf *dbuf, int offset, char *data, int len); +static void deleterawdata(midi_dbuf *dbuf, int len); + +/* + * Here are the functions to interact to the midi device drivers. + * These are called from midi device driver functions under sys/i386/isa/snd. + */ + +int +midibuf_init(midi_dbuf *dbuf) +{ + if (dbuf->buf == NULL) { + dbuf->buf = malloc(MIDI_BUFFSIZE, M_DEVBUF, M_WAITOK | M_ZERO); + cv_init(&dbuf->cv_in, "midi queue in"); + cv_init(&dbuf->cv_out, "midi queue out"); + } + + return (midibuf_clear(dbuf)); +} + +int +midibuf_destroy(midi_dbuf *dbuf) +{ + if (dbuf->buf != NULL) { + free(dbuf->buf, M_DEVBUF); + cv_destroy(&dbuf->cv_in); + cv_destroy(&dbuf->cv_out); + } + + return (0); +} + +int +midibuf_clear(midi_dbuf *dbuf) +{ + bzero(dbuf->buf, MIDI_BUFFSIZE); + dbuf->bufsize = MIDI_BUFFSIZE; + dbuf->rp = dbuf->fp = 0; + dbuf->dl = 0; + dbuf->rl = 0; + dbuf->fl = dbuf->bufsize; + dbuf->int_count = 0; + dbuf->chan = 0; + /*dbuf->unit_size = 1;*/ /* The drivers are responsible. */ + bzero(&dbuf->sel, sizeof(dbuf->sel)); + dbuf->total = 0; + dbuf->prev_total = 0; + dbuf->blocksize = dbuf->bufsize / 4; + + return (0); +} + +/* The sequencer calls this function to queue data. */ +int +midibuf_seqwrite(midi_dbuf *dbuf, u_char* data, int len, int *lenw, midi_callback_t *cb, void *d, int reason, struct mtx *m) +{ + int i, lwrt; + + if (m != NULL) + mtx_assert(m, MA_OWNED); + + if (lenw == NULL) + return (EINVAL); + *lenw = 0; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (EINVAL); + + /* Write down every single byte. */ + while (len > 0) { + /* Find out the number of bytes to write. */ + lwrt = SPACE_AVAIL(dbuf); + if (lwrt > len) + lwrt = len; + if (lwrt > 0) { + /* We can write some now. Queue the data. */ + queuerawdata(dbuf, data, lwrt); + + *lenw += lwrt; + len -= lwrt; + data += lwrt; + } + + if (cb != NULL) + (*cb)(d, reason); + + /* Have we got still more data to write? */ + if (len > 0) { + /* Sleep until we have enough space. */ + i = cv_wait_sig(&dbuf->cv_out, m); + if (i == EINTR || i == ERESTART) + return (i); + } + } + + return (0); +} + +int +midibuf_output_intr(midi_dbuf *dbuf, u_char *data, int len, int *leno) +{ + if (leno == NULL) + return (EINVAL); + *leno = 0; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (EINVAL); + + /* Have we got any data in the queue? */ + *leno = DATA_AVAIL(dbuf); + if (*leno == 0) + return (EAGAIN); + + /* Dequeue the data. */ + if (*leno > len) + *leno = len; + dequeuerawdata(dbuf, data, *leno); + + return (0); +} + +int +midibuf_input_intr(midi_dbuf *dbuf, u_char *data, int len, int *leni) +{ + if (leni == NULL) + return (EINVAL); + *leni = 0; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (EINVAL); + + /* Have we got any data to write? */ + if (len == 0) + return (0); + /* Can we write now? */ + if (SPACE_AVAIL(dbuf) < len) + return (EAGAIN); + + /* We can write some now. Queue the data. */ + queuerawdata(dbuf, data, len); + *leni = len; + + return (0); +} + +/* The sequencer calls this function to dequeue data. */ +int +midibuf_seqread(midi_dbuf *dbuf, u_char* data, int len, int *lenr, midi_callback_t *cb, void *d, int reason, struct mtx *m) +{ + int i, lrd; + + if (m != NULL) + mtx_assert(m, MA_OWNED); + + if (lenr == NULL) + return (EINVAL); + *lenr = 0; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (EINVAL); + + /* Write down every single byte. */ + while (len > 0) { + if (cb != NULL) + (*cb)(d, reason); + + /* Have we got data to read? */ + if ((lrd = DATA_AVAIL(dbuf)) == 0) { + /* Sleep until we have data ready to read. */ + i = cv_wait_sig(&dbuf->cv_in, m); + if (i == EINTR || i == ERESTART) + return (i); + /* Find out the number of bytes to read. */ + lrd = DATA_AVAIL(dbuf); + } + + if (lrd > len) + lrd = len; + if (lrd > 0) { + /* We can read some data now. Dequeue the data. */ + dequeuerawdata(dbuf, data, lrd); + + *lenr += lrd; + len -= lrd; + data += lrd; + } + } + + return (0); +} + +/* The sequencer calls this function to copy data without dequeueing. */ +int +midibuf_seqcopy(midi_dbuf *dbuf, u_char* data, int len, int *lenc, midi_callback_t *cb, void *d, int reason, struct mtx *m) +{ + int i, lrd; + + if (m != NULL) + mtx_assert(m, MA_OWNED); + + if (lenc == NULL) + return (EINVAL); + *lenc = 0; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (EINVAL); + + /* Write down every single byte. */ + while (len > 0) { + if (cb != NULL) + (*cb)(d, reason); + + /* Have we got data to read? */ + if ((lrd = DATA_AVAIL(dbuf)) == 0) { + /* Sleep until we have data ready to read. */ + i = cv_wait_sig(&dbuf->cv_in, m); + if (i == EINTR || i == ERESTART) + return (i); + /* Find out the number of bytes to read. */ + lrd = DATA_AVAIL(dbuf); + } + + if (lrd > len) + lrd = len; + if (lrd > 0) { + /* We can read some data now. Copy the data. */ + copyrawdata(dbuf, *lenc, data, lrd); + + *lenc += lrd; + len -= lrd; + data += lrd; + } + } + + return (0); +} + +/* + * The sequencer calls this function to delete the data + * that the sequencer has already read. + */ +int +midibuf_seqdelete(midi_dbuf *dbuf, int len, int *lenr, midi_callback_t *cb, void *d, int reason, struct mtx *m) +{ + int i, lrd; + + if (m != NULL) + mtx_assert(m, MA_OWNED); + + if (lenr == NULL) + return (EINVAL); + *lenr = 0; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (EINVAL); + + /* Write down every single byte. */ + while (len > 0) { + if (cb != NULL) + (*cb)(d, reason); + + /* Have we got data to read? */ + if ((lrd = DATA_AVAIL(dbuf)) == 0) { + /* Sleep until we have data ready to read. */ + i = cv_wait_sig(&dbuf->cv_in, m); + if (i == EINTR || i == ERESTART) + return (i); + /* Find out the number of bytes to read. */ + lrd = DATA_AVAIL(dbuf); + } + + if (lrd > len) + lrd = len; + if (lrd > 0) { + /* We can read some data now. Delete the data. */ + deleterawdata(dbuf, lrd); + + *lenr += lrd; + len -= lrd; + } + } + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +static void +queuerawdata(midi_dbuf *dbuf, char *data, int len) +{ + /* dbuf->fp might wrap around dbuf->bufsize. */ + if (dbuf->bufsize - dbuf->fp < len) { + /* The new data wraps, copy them twice. */ + bcopy(data, dbuf->buf + dbuf->fp, dbuf->bufsize - dbuf->fp); + bcopy(data + dbuf->bufsize - dbuf->fp, dbuf->buf, len - (dbuf->bufsize - dbuf->fp)); + } else + /* The new data do not wrap, once is enough. */ + bcopy(data, dbuf->buf + dbuf->fp, len); + + /* Adjust the pointer and the length counters. */ + dbuf->fp = (dbuf->fp + len) % dbuf->bufsize; + dbuf->fl -= len; + dbuf->rl += len; + + /* Wake up the processes sleeping on input data. */ + cv_broadcast(&dbuf->cv_in); + if (SEL_WAITING(&dbuf->sel) && dbuf->rl >= dbuf->blocksize) + selwakeup(&dbuf->sel); +} + +static void +dequeuerawdata(midi_dbuf *dbuf, char *data, int len) +{ + /* Copy the data. */ + copyrawdata(dbuf, 0, data, len); + + /* Delete the data. */ + deleterawdata(dbuf, len); +} + +static void +copyrawdata(midi_dbuf *dbuf, int offset, char *data, int len) +{ + int rp; + + rp = (dbuf->rp + offset) % dbuf->bufsize; + + /* dbuf->rp might wrap around dbuf->bufsize. */ + if (dbuf->bufsize - rp < len) { + /* The data to be read wraps, copy them twice. */ + bcopy(dbuf->buf + rp, data, dbuf->bufsize - rp); + bcopy(dbuf->buf, data + dbuf->bufsize - rp, len - (dbuf->bufsize - rp)); + } else + /* The new data do not wrap, once is enough. */ + bcopy(dbuf->buf + rp, data, len); +} + +static void +deleterawdata(midi_dbuf *dbuf, int len) +{ + /* Adjust the pointer and the length counters. */ + dbuf->rp = (dbuf->rp + len) % dbuf->bufsize; + dbuf->rl -= len; + dbuf->fl += len; + + /* Wake up the processes sleeping on queueing. */ + cv_broadcast(&dbuf->cv_out); + if (SEL_WAITING(&dbuf->sel) && dbuf->fl >= dbuf->blocksize) + selwakeup(&dbuf->sel); +} diff --git a/sys/dev/sound/midi/midibuf.h b/sys/dev/sound/midi/midibuf.h new file mode 100644 index 0000000..a25c1cc --- /dev/null +++ b/sys/dev/sound/midi/midibuf.h @@ -0,0 +1,65 @@ +/* + * Include file for midi buffer. + * + * Copyright by Seigo Tanimura 1999. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +/* + * descriptor of a midi buffer. See midibuf.c for documentation. + * (rp,rl) and (fp,fl) identify the READY and FREE regions of the + * buffer. dl contains the length used for dma transfer, dl>0 also + * means that the channel is busy and there is a DMA transfer in progress. + */ + +typedef struct _midi_dbuf { + char *buf; + int bufsize ; + volatile int rp, fp; /* pointers to the ready and free area */ + volatile int dl; /* transfer size */ + volatile int rl, fl; /* length of ready and free areas. */ + int int_count; + int chan; /* dma channel */ + int unit_size ; /* unit size */ + struct selinfo sel; + u_long total; /* total bytes processed */ + u_long prev_total; /* copy of the above when GETxPTR called */ + struct cv cv_in, cv_out; /* condvars */ + int blocksize; /* block size */ +} midi_dbuf ; + +/* + * These are the midi buffer methods, used in midi interface devices. + */ +int midibuf_init(midi_dbuf *dbuf); +int midibuf_destroy(midi_dbuf *dbuf); +int midibuf_clear(midi_dbuf *dbuf); +int midibuf_seqwrite(midi_dbuf *dbuf, u_char* data, int len, int *lenw, midi_callback_t *cb, void *d, int reason, struct mtx *m); +int midibuf_output_intr(midi_dbuf *dbuf, u_char *data, int len, int *leno); +int midibuf_input_intr(midi_dbuf *dbuf, u_char *data, int len, int *leni); +int midibuf_seqread(midi_dbuf *dbuf, u_char* data, int len, int *lenr, midi_callback_t *cb, void *d, int reason, struct mtx *m); +int midibuf_seqcopy(midi_dbuf *dbuf, u_char* data, int len, int *lenc, midi_callback_t *cb, void *d, int reason, struct mtx *m); +int midibuf_seqdelete(midi_dbuf *dbuf, int len, int *lend, midi_callback_t *cb, void *d, int reason, struct mtx *m); diff --git a/sys/dev/sound/midi/midisynth.c b/sys/dev/sound/midi/midisynth.c new file mode 100644 index 0000000..d370833 --- /dev/null +++ b/sys/dev/sound/midi/midisynth.c @@ -0,0 +1,532 @@ +/* + * Copyright by Hannu Savolainen 1993 + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +/* + * This is the interface for a sequencer to interact a midi driver. + * This interface translates the sequencer operations to the corresponding + * midi messages, and vice versa. + */ + +#include <dev/sound/midi/midi.h> + +#define TYPEDRANGE(type, x, lower, upper) \ +{ \ + type tl, tu; \ + tl = (lower); \ + tu = (upper); \ + if (x < tl) { \ + x = tl; \ + } else if(x > tu) { \ + x = tu; \ + } \ +} + +/* + * These functions goes into midisynthdev_op_desc. + */ +static mdsy_killnote_t synth_killnote; +static mdsy_setinstr_t synth_setinstr; +static mdsy_startnote_t synth_startnote; +static mdsy_reset_t synth_reset; +static mdsy_hwcontrol_t synth_hwcontrol; +static mdsy_loadpatch_t synth_loadpatch; +static mdsy_panning_t synth_panning; +static mdsy_aftertouch_t synth_aftertouch; +static mdsy_controller_t synth_controller; +static mdsy_patchmgr_t synth_patchmgr; +static mdsy_bender_t synth_bender; +static mdsy_allocvoice_t synth_allocvoice; +static mdsy_setupvoice_t synth_setupvoice; +static mdsy_sendsysex_t synth_sendsysex; +static mdsy_prefixcmd_t synth_prefixcmd; +static mdsy_volumemethod_t synth_volumemethod; +static mdsy_readraw_t synth_readraw; +static mdsy_writeraw_t synth_writeraw; + +/* + * This is the synthdev_info for a midi interface device. + * You may have to replace a few of functions for an internal + * synthesizer. + */ +synthdev_info midisynth_op_desc = { + synth_killnote, + synth_setinstr, + synth_startnote, + synth_reset, + synth_hwcontrol, + synth_loadpatch, + synth_panning, + synth_aftertouch, + synth_controller, + synth_patchmgr, + synth_bender, + synth_allocvoice, + synth_setupvoice, + synth_sendsysex, + synth_prefixcmd, + synth_volumemethod, + synth_readraw, + synth_writeraw, +}; + +/* The following functions are local. */ +static int synth_leavesysex(mididev_info *md); + +/* + * Here are the main functions to interact to the midi sequencer. + * These are called from the sequencer functions in sequencer.c. + */ + +static int +synth_killnote(mididev_info *md, int chn, int note, int vel) +{ + int unit, lenw; + synthdev_info *sd; + u_char c[3]; + + unit = md->unit; + sd = &md->synth; + + if (note < 0 || note > 127 || chn < 0 || chn > 15) + return (EINVAL); + TYPEDRANGE(int, vel, 0, 127); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + if (vel == 64) { + c[0] = 0x90 | (chn & 0x0f); /* Note on. */ + c[1] = (u_char)note; + c[2] = 0; + } else { + c[0] = 0x80 | (chn & 0x0f); /* Note off. */ + c[1] = (u_char)note; + c[2] = (u_char)vel; + } + + if (synth_prefixcmd(md, c[0])) + return (0); + + return (md->synth.writeraw(md, c, 3, &lenw, 1)); +} + +static int +synth_setinstr(mididev_info *md, int chn, int instr) +{ + int unit, lenw; + synthdev_info *sd; + u_char c[2]; + + unit = md->unit; + sd = &md->synth; + + if (instr < 0 || instr > 127 || chn < 0 || chn > 15) + return (EINVAL); + + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + c[0] = 0xc0 | (chn & 0x0f); /* Progamme change. */ + c[1] = (u_char)instr; + + return (md->synth.writeraw(md, c, 3, &lenw, 1)); +} + +static int +synth_startnote(mididev_info *md, int chn, int note, int vel) +{ + int unit, lenw; + synthdev_info *sd; + u_char c[3]; + + unit = md->unit; + sd = &md->synth; + + if (note < 0 || note > 127 || chn < 0 || chn > 15) + return (EINVAL); + TYPEDRANGE(int, vel, 0, 127); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + c[0] = 0x90 | (chn & 0x0f); /* Note on. */ + c[1] = (u_char)note; + c[2] = (u_char)vel; + if (synth_prefixcmd(md, c[0])) + return (0); + + return (md->synth.writeraw(md, c, 3, &lenw, 1)); +} + +static int +synth_reset(mididev_info *md) +{ + synth_leavesysex(md); + return (0); +} + +static int +synth_hwcontrol(mididev_info *md, u_char *event) +{ + /* NOP. */ + return (0); +} + +static int +synth_loadpatch(mididev_info *md, int format, struct uio *buf, int offs, int count, int pmgr_flag) +{ + struct sysex_info sysex; + synthdev_info *sd; + int unit, i, eox_seen, first_byte, left, src_offs, hdr_size, lenw; + u_char c[count]; + + unit = md->unit; + sd = &md->synth; + + eox_seen = 0; + first_byte = 1; + hdr_size = offsetof(struct sysex_info, data); + + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + if (synth_prefixcmd(md, 0xf0)) + return (0); + if (format != SYSEX_PATCH) { + printf("synth_loadpatch: patch format 0x%x is invalid.\n", format); + return (EINVAL); + } + if (count < hdr_size) { + printf("synth_loadpatch: patch header is too short.\n"); + return (EINVAL); + } + count -= hdr_size; + + /* Copy the patch data. */ + if (uiomove((caddr_t)&((char *)&sysex)[offs], hdr_size - offs, buf)) + printf("synth_loadpatch: memory mangled?\n"); + + if (count < sysex.len) { + sysex.len = (long)count; + printf("synth_loadpatch: sysex record of %d bytes is too long, adjusted to %d bytes.\n", (int)sysex.len, count); + } + left = sysex.len; + src_offs = 0; + + for (i = 0 ; i < left ; i++) { + uiomove((caddr_t)&c[i], 1, buf); + eox_seen = i > 0 && (c[i] & 0x80) != 0; + if (eox_seen && c[i] != 0xf7) + c[i] = 0xf7; + if (i == 0 && c[i] != 0x80) { + printf("synth_loadpatch: sysex does not begin with the status.\n"); + return (EINVAL); + } + if (!first_byte && (c[i] & 0x80) != 0) { + md->synth.writeraw(md, c, i + 1, &lenw, 0); + return (0); + } + first_byte = 0; + } + + if (!eox_seen) { + c[0] = 0xf7; + md->synth.writeraw(md, c, 1, &lenw, 0); + } + + return (0); +} + +static int +synth_panning(mididev_info *md, int chn, int pan) +{ + /* NOP. */ + return (0); +} + +static int +synth_aftertouch(mididev_info *md, int chn, int press) +{ + int unit, lenw; + synthdev_info *sd; + u_char c[2]; + + unit = md->unit; + sd = &md->synth; + + if (press < 0 || press > 127 || chn < 0 || chn > 15) + return (EINVAL); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + c[0] = 0xd0 | (chn & 0x0f); /* Channel Pressure. */ + c[1] = (u_char)press; + if (synth_prefixcmd(md, c[0])) + return (0); + + return (md->synth.writeraw(md, c, 2, &lenw, 1)); +} + +static int +synth_controller(mididev_info *md, int chn, int ctrlnum, int val) +{ + int unit, lenw; + synthdev_info *sd; + u_char c[3]; + + unit = md->unit; + sd = &md->synth; + + if (ctrlnum < 1 || ctrlnum > 127 || chn < 0 || chn > 15) + return (EINVAL); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + c[0] = 0xb0 | (chn & 0x0f); /* Control Message. */ + c[1] = (u_char)ctrlnum; + if (synth_prefixcmd(md, c[0])) + return (0); + + return (md->synth.writeraw(md, c, 3, &lenw, 1)); +} + +static int +synth_patchmgr(mididev_info *md, struct patmgr_info *rec) +{ + return (EINVAL); +} + +static int +synth_bender(mididev_info *md, int chn, int val) +{ + int unit, lenw; + synthdev_info *sd; + u_char c[3]; + + unit = md->unit; + sd = &md->synth; + + if (val < 0 || val > 16383 || chn < 0 || chn > 15) + return (EINVAL); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + c[0] = 0xe0 | (chn & 0x0f); /* Pitch bend. */ + c[1] = (u_char)val & 0x7f; + c[2] = (u_char)(val >> 7) & 0x7f; + if (synth_prefixcmd(md, c[0])) + return (0); + + return (md->synth.writeraw(md, c, 3, &lenw, 1)); +} + +static int +synth_allocvoice(mididev_info *md, int chn, int note, struct voice_alloc_info *alloc) +{ + /* NOP. */ + return (0); +} + +static int +synth_setupvoice(mididev_info *md, int voice, int chn) +{ + /* NOP. */ + return (0); +} + +static int +synth_sendsysex(mididev_info *md, u_char *sysex, int len) +{ + int unit, i, lenw; + synthdev_info *sd; + u_char c[len]; + + unit = md->unit; + sd = &md->synth; + + mtx_lock(&sd->status_mtx); + for (i = 0 ; i < len ; i++) { + switch (sysex[i]) { + case 0xf0: + /* Sysex begins. */ + if (synth_prefixcmd(md, 0xf0)) { + mtx_unlock(&sd->status_mtx); + return (0); + } + sd->sysex_state = 1; + break; + case 0xf7: + /* Sysex ends. */ + if (!sd->sysex_state) { + mtx_unlock(&sd->status_mtx); + return (0); + } + sd->sysex_state = 0; + break; + default: + if (!sd->sysex_state) { + mtx_unlock(&sd->status_mtx); + return (0); + } + if ((sysex[i] & 0x80) != 0) { + /* A status in a sysex? */ + sysex[i] = 0xf7; + sd->sysex_state = 0; + } + break; + } + c[i] = sysex[i]; + if (!sd->sysex_state) + break; + } + mtx_unlock(&sd->status_mtx); + + return (md->synth.writeraw(md, c, i, &lenw, 1)); +} + +static int +synth_prefixcmd(mididev_info *md, int status) +{ + /* NOP. */ + return (0); +} + +static int +synth_volumemethod(mididev_info *md, int mode) +{ + /* NOP. */ + return (0); +} + +static int +synth_readraw(mididev_info *md, u_char *buf, int len, int *lenr, int nonblock) +{ + int unit, ret; + + if (md == NULL) + return (ENXIO); + if (lenr == NULL) + return (EINVAL); + + *lenr = 0; + unit = md->unit; + + if ((md->fflags & FREAD) == 0) { + MIDI_DEBUG(printf("synth_readraw: unit %d is not for reading.\n", unit)); + return (EIO); + } + + mtx_lock(&md->flagqueue_mtx); + + /* Begin recording. */ + if ((md->flags & MIDI_F_READING) == 0) + md->callback(md, MIDI_CB_START | MIDI_CB_RD); + + if (nonblock) { + /* Have we got enough data to read? */ + if (md->midi_dbuf_in.rl < len) { + mtx_unlock(&md->flagqueue_mtx); + return (EAGAIN); + } + } + + ret = midibuf_seqread(&md->midi_dbuf_in, buf, len, lenr, + md->callback, md, MIDI_CB_START | MIDI_CB_RD, + &md->flagqueue_mtx); + + mtx_unlock(&md->flagqueue_mtx); + + return (ret); +} + +static int +synth_writeraw(mididev_info *md, u_char *buf, int len, int *lenw, int nonblock) +{ + int unit, ret; + + if (md == NULL) + return (ENXIO); + if (lenw == NULL) + return (EINVAL); + + *lenw = 0; + unit = md->unit; + + if ((md->fflags & FWRITE) == 0) { + MIDI_DEBUG(printf("synth_writeraw: unit %d is not for writing.\n", unit)); + return (EIO); + } + + /* For nonblocking, have we got enough space to write? */ + mtx_lock(&md->flagqueue_mtx); + if (nonblock && md->midi_dbuf_out.fl < len) { + /* Begin playing. */ + md->callback(md, MIDI_CB_START | MIDI_CB_WR); + mtx_unlock(&md->flagqueue_mtx); + return (EAGAIN); + } + + ret = midibuf_seqwrite(&md->midi_dbuf_out, buf, len, lenw, + md->callback, md, MIDI_CB_START | MIDI_CB_WR, + &md->flagqueue_mtx); + + if (ret == 0) + /* Begin playing. */ + md->callback(md, MIDI_CB_START | MIDI_CB_WR); + + mtx_unlock(&md->flagqueue_mtx); + + return (ret); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +static int +synth_leavesysex(mididev_info *md) +{ + int unit, lenw; + synthdev_info *sd; + u_char c; + + unit = md->unit; + sd = &md->synth; + + mtx_lock(&sd->status_mtx); + if (!sd->sysex_state) { + mtx_unlock(&sd->status_mtx); + return (0); + } + + sd->sysex_state = 0; + mtx_unlock(&sd->status_mtx); + c = 0xf7; + + return (md->synth.writeraw(md, &c, sizeof(c), &lenw, 1)); +} diff --git a/sys/dev/sound/midi/midisynth.h b/sys/dev/sound/midi/midisynth.h new file mode 100644 index 0000000..356be89 --- /dev/null +++ b/sys/dev/sound/midi/midisynth.h @@ -0,0 +1,110 @@ +/* + * include file for midi synthesizer interface. + * + * Copyright by Seigo Tanimura 1999. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#define SYNTH_MAX_VOICES 32 + +/* This is the voice allocation state for a synthesizer. */ +struct voice_alloc_info { + int max_voice; + int used_voices; + int ptr; /* For device specific use */ + u_short map[SYNTH_MAX_VOICES]; /* (ch << 8) | (note+1) */ + int timestamp; + int alloc_times[SYNTH_MAX_VOICES]; +}; + +/* This is the channel information for a synthesizer. */ +struct channel_info { + int pgm_num; + int bender_value; + u_char controllers[128]; +}; + +/* These are the function types for a midi synthesizer interface. */ +typedef int (mdsy_killnote_t)(mididev_info *md, int chn, int note, int vel); +typedef int (mdsy_setinstr_t)(mididev_info *md, int chn, int instr); +typedef int (mdsy_startnote_t)(mididev_info *md, int chn, int note, int vel); +typedef int (mdsy_reset_t)(mididev_info *md); +typedef int (mdsy_hwcontrol_t)(mididev_info *md, u_char *event); +typedef int (mdsy_loadpatch_t)(mididev_info *md, int format, struct uio *buf, int offs, int count, int pmgr_flag); +typedef int (mdsy_panning_t)(mididev_info *md, int chn, int pan); +typedef int (mdsy_aftertouch_t)(mididev_info *md, int chn, int press); +typedef int (mdsy_controller_t)(mididev_info *md, int chn, int ctrlnum, int val); +typedef int (mdsy_patchmgr_t)(mididev_info *md, struct patmgr_info *rec); +typedef int (mdsy_bender_t)(mididev_info *md, int chn, int val); +typedef int (mdsy_allocvoice_t)(mididev_info *md, int chn, int note, struct voice_alloc_info *alloc); +typedef int (mdsy_setupvoice_t)(mididev_info *md, int voice, int chn); +typedef int (mdsy_sendsysex_t)(mididev_info *md, u_char *sysex, int len); +typedef int (mdsy_prefixcmd_t)(mididev_info *md, int status); +typedef int (mdsy_volumemethod_t)(mididev_info *md, int mode); +typedef int (mdsy_readraw_t)(mididev_info *md, u_char *buf, int len, int *lenr, int nonblock); +typedef int (mdsy_writeraw_t)(mididev_info *md, u_char *buf, int len, int *lenw, int nonblock); + +/* + * The order of mutex lock (from the first to the last) + * + * 1. sequencer flags, queues, timer and devlice list + * 2. midi synth voice and channel + * 3. midi synth status + * 4. generic midi flags and queues + * 5. midi device + */ + +/* This is a midi synthesizer interface and state. */ +struct _synthdev_info { + mdsy_killnote_t *killnote; + mdsy_setinstr_t *setinstr; + mdsy_startnote_t *startnote; + mdsy_reset_t *reset; + mdsy_hwcontrol_t *hwcontrol; + mdsy_loadpatch_t *loadpatch; + mdsy_panning_t *panning; + mdsy_aftertouch_t *aftertouch; + mdsy_controller_t *controller; + mdsy_patchmgr_t *patchmgr; + mdsy_bender_t *bender; + mdsy_allocvoice_t *allocvoice; + mdsy_setupvoice_t *setupvoice; + mdsy_sendsysex_t *sendsysex; + mdsy_prefixcmd_t *prefixcmd; + mdsy_volumemethod_t *volumemethod; + mdsy_readraw_t *readraw; + mdsy_writeraw_t *writeraw; + + /* Voice and channel */ + struct mtx vc_mtx; /* Mutex to protect voice and channel. */ + struct voice_alloc_info alloc; /* Voice allocation. */ + struct channel_info chn_info[16]; /* Channel information. */ + + /* Status */ + struct mtx status_mtx; /* Mutex to protect status. */ + int sysex_state; /* State of sysex transmission. */ +}; +typedef struct _synthdev_info synthdev_info; diff --git a/sys/dev/sound/midi/miditypes.h b/sys/dev/sound/midi/miditypes.h new file mode 100644 index 0000000..0657342 --- /dev/null +++ b/sys/dev/sound/midi/miditypes.h @@ -0,0 +1,34 @@ +/* + * Include file for type definitions in midi driver. + * + * Copyright by Seigo Tanimura 1999. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +typedef struct _mididev_info mididev_info; + +typedef int (midi_callback_t)(void *d, int reason); +typedef void (midi_intr_t)(void *p, mididev_info *md); diff --git a/sys/dev/sound/midi/sequencer.c b/sys/dev/sound/midi/sequencer.c new file mode 100644 index 0000000..5115cc9 --- /dev/null +++ b/sys/dev/sound/midi/sequencer.c @@ -0,0 +1,2606 @@ +/* + * The sequencer personality manager. + * + * Copyright by Hannu Savolainen 1993 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. 2. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +/* + * This is the newmidi sequencer driver. This driver handles io against + * /dev/sequencer, midi input and output event queues and event transmittion + * to and from a midi device or synthesizer. + */ + +#include <dev/sound/midi/midi.h> +#include <dev/sound/midi/sequencer.h> + +#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + synthesizer and MIDI output) */ +#define SND_DEV_MIDIN 2 /* Raw midi access */ +#define SND_DEV_MUSIC 8 /* /dev/music, level 2 interface */ + +#define MIDIDEV_MODE 0x2000 + +/* Length of a sequencer event. */ +#define EV_SZ 8 +#define IEV_SZ 8 + +/* Lookup modes */ +#define LOOKUP_EXIST (0) +#define LOOKUP_OPEN (1) +#define LOOKUP_CLOSE (2) + +/* + * These functions goes into seq_op_desc to get called + * from sound.c. + */ + +static midi_intr_t seq_intr; +static midi_callback_t seq_callback; + +/* These are the entries to the sequencer driver. */ +static d_open_t seq_open; +static d_close_t seq_close; +static d_ioctl_t seq_ioctl; +static d_read_t seq_read; +static d_write_t seq_write; +static d_poll_t seq_poll; + +/* + * This is the device descriptor for the midi sequencer. + */ +seqdev_info seq_op_desc = { + "midi sequencer", + + 0, + + seq_open, + seq_close, + seq_read, + seq_write, + seq_ioctl, + seq_poll, + + seq_callback, + + SEQ_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + + +/* Here is the parameter structure per a device. */ +struct seq_softc { + seqdev_info *devinfo; /* sequencer device information */ + + /* Flags (protected by flag_mtx of mididev_info) */ + int fflags; /* Access mode */ + int queueout_pending; /* Pending for the output queue */ + int seq_mode; /* Sequencer mode */ + + /* Timer counters */ + u_long seq_time; /* The beggining time of this sequence */ + u_long prev_event_time; /* The time of the previous event output */ + u_long prev_input_time; /* The time of the previous event input */ + u_long prev_wakeup_time; /* The time of the previous wakeup */ + struct callout timeout_ch; /* Timer callout handler */ + long timer_current; /* Current timer value */ + int timer_running; /* State of timer */ + int pending_timer; /* Timer change operation */ + int pre_event_timeout; /* Time to wait event input */ + + /* Devices */ + TAILQ_HEAD(,_mididev_info) midi_open; /* Midi devices opened by this sequencer. */ + timerdev_info *timer; /* A timer device for /dev/music */ + + /* + * XXX not sure to which category these belong. + * (and some might be no-op) + */ + int output_threshould; /* Sequence output threshould */ + snd_sync_parm sync_parm; /* AIOSYNC parameter set */ + struct thread *sync_thread; /* AIOSYNCing thread */ +}; + +typedef struct seq_softc *sc_p; + +static d_open_t seqopen; +static d_close_t seqclose; +static d_ioctl_t seqioctl; +static d_read_t seqread; +static d_write_t seqwrite; +static d_poll_t seqpoll; + +#define CDEV_MAJOR SEQ_CDEV_MAJOR +static struct cdevsw seq_cdevsw = { + /* open */ seqopen, + /* close */ seqclose, + /* read */ seqread, + /* write */ seqwrite, + /* ioctl */ seqioctl, + /* poll */ seqpoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "midi", /* XXX */ + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + + +static TAILQ_HEAD(,_seqdev_info) seq_info; +/* Mutex to protect seq_info and nseq. */ +static struct mtx seqinfo_mtx; +/* total number of sequencers */ +static u_long nseq; +static dev_t seq_alias = NODEV; +static dev_t music_alias = NODEV; + +SYSCTL_NODE(_hw_midi, OID_AUTO, seq, CTLFLAG_RD, 0, "Midi sequencer"); + +int seq_debug; +SYSCTL_INT(_hw_midi_seq, OID_AUTO, debug, CTLFLAG_RW, &seq_debug, 0, ""); + +static midi_cmdtab cmdtab_seqevent[] = { + {SEQ_NOTEOFF, "SEQ_NOTEOFF"}, + {SEQ_NOTEON, "SEQ_NOTEON"}, + {SEQ_WAIT, "SEQ_WAIT"}, + {SEQ_PGMCHANGE, "SEQ_PGMCHANGE"}, + {SEQ_SYNCTIMER, "SEQ_SYNCTIMER"}, + {SEQ_MIDIPUTC, "SEQ_MIDIPUTC"}, + {SEQ_DRUMON, "SEQ_DRUMON"}, + {SEQ_DRUMOFF, "SEQ_DRUMOFF"}, + {SEQ_ECHO, "SEQ_ECHO"}, + {SEQ_AFTERTOUCH, "SEQ_AFTERTOUCH"}, + {SEQ_CONTROLLER, "SEQ_CONTROLLER"}, + {SEQ_BALANCE, "SEQ_BALANCE"}, + {SEQ_VOLMODE, "SEQ_VOLMODE"}, + {SEQ_FULLSIZE, "SEQ_FULLSIZE"}, + {SEQ_PRIVATE, "SEQ_PRIVATE"}, + {SEQ_EXTENDED, "SEQ_EXTENDED"}, + {EV_SEQ_LOCAL, "EV_SEQ_LOCAL"}, + {EV_TIMING, "EV_TIMING"}, + {EV_CHN_COMMON, "EV_CHN_COMMON"}, + {EV_CHN_VOICE, "EV_CHN_VOICE"}, + {EV_SYSEX, "EV_SYSEX"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_seqioctl[] = { + {SNDCTL_SEQ_RESET, "SNDCTL_SEQ_RESET"}, + {SNDCTL_SEQ_SYNC, "SNDCTL_SEQ_SYNC"}, + {SNDCTL_SYNTH_INFO, "SNDCTL_SYNTH_INFO"}, + {SNDCTL_SEQ_CTRLRATE, "SNDCTL_SEQ_CTRLRATE"}, + {SNDCTL_SEQ_GETOUTCOUNT, "SNDCTL_SEQ_GETOUTCOUNT"}, + {SNDCTL_SEQ_GETINCOUNT, "SNDCTL_SEQ_GETINCOUNT"}, + {SNDCTL_SEQ_PERCMODE, "SNDCTL_SEQ_PERCMODE"}, + {SNDCTL_FM_LOAD_INSTR, "SNDCTL_FM_LOAD_INSTR"}, + {SNDCTL_SEQ_TESTMIDI, "SNDCTL_SEQ_TESTMIDI"}, + {SNDCTL_SEQ_RESETSAMPLES, "SNDCTL_SEQ_RESETSAMPLES"}, + {SNDCTL_SEQ_NRSYNTHS, "SNDCTL_SEQ_NRSYNTHS"}, + {SNDCTL_SEQ_NRMIDIS, "SNDCTL_SEQ_NRMIDIS"}, + {SNDCTL_MIDI_INFO, "SNDCTL_MIDI_INFO"}, + {SNDCTL_SEQ_THRESHOLD, "SNDCTL_SEQ_THRESHOLD"}, + {SNDCTL_SYNTH_MEMAVL, "SNDCTL_SYNTH_MEMAVL"}, + {SNDCTL_FM_4OP_ENABLE, "SNDCTL_FM_4OP_ENABLE"}, + {SNDCTL_PMGR_ACCESS, "SNDCTL_PMGR_ACCESS"}, + {SNDCTL_SEQ_PANIC, "SNDCTL_SEQ_PANIC"}, + {SNDCTL_SEQ_OUTOFBAND, "SNDCTL_SEQ_OUTOFBAND"}, + {SNDCTL_TMR_TIMEBASE, "SNDCTL_TMR_TIMEBASE"}, + {SNDCTL_TMR_START, "SNDCTL_TMR_START"}, + {SNDCTL_TMR_STOP, "SNDCTL_TMR_STOP"}, + {SNDCTL_TMR_CONTINUE, "SNDCTL_TMR_CONTINUE"}, + {SNDCTL_TMR_TEMPO, "SNDCTL_TMR_TEMPO"}, + {SNDCTL_TMR_SOURCE, "SNDCTL_TMR_SOURCE"}, + {SNDCTL_TMR_METRONOME, "SNDCTL_TMR_METRONOME"}, + {SNDCTL_TMR_SELECT, "SNDCTL_TMR_SELECT"}, + {SNDCTL_MIDI_PRETIME, "SNDCTL_MIDI_PRETIME"}, + {AIONWRITE, "AIONWRITE"}, + {AIOGSIZE, "AIOGSIZE"}, + {AIOSSIZE, "AIOSSIZE"}, + {AIOGFMT, "AIOGFMT"}, + {AIOSFMT, "AIOSFMT"}, + {AIOGMIX, "AIOGMIX"}, + {AIOSMIX, "AIOSMIX"}, + {AIOSTOP, "AIOSTOP"}, + {AIOSYNC, "AIOSYNC"}, + {AIOGCAP, "AIOGCAP"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_timer[] = { + {TMR_WAIT_REL, "TMR_WAIT_REL"}, + {TMR_WAIT_ABS, "TMR_WAIT_ABS"}, + {TMR_STOP, "TMR_STOP"}, + {TMR_START, "TMR_START"}, + {TMR_CONTINUE, "TMR_CONTINUE"}, + {TMR_TEMPO, "TMR_TEMPO"}, + {TMR_ECHO, "TMR_ECHO"}, + {TMR_CLOCK, "TMR_CLOCK"}, + {TMR_SPP, "TMR_SPP"}, + {TMR_TIMESIG, "TMR_TIMESIG"}, + {-1, NULL}, +}; + +static midi_cmdtab cmdtab_seqcv[] = { + {MIDI_NOTEOFF, "MIDI_NOTEOFF"}, + {MIDI_NOTEON, "MIDI_NOTEON"}, + {MIDI_KEY_PRESSURE, "MIDI_KEY_PRESSURE"}, + {-1, NULL}, +}; + +static midi_cmdtab cmdtab_seqccmn[] = { + {MIDI_CTL_CHANGE, "MIDI_CTL_CHANGE"}, + {MIDI_PGM_CHANGE, "MIDI_PGM_CHANGE"}, + {MIDI_CHN_PRESSURE, "MIDI_CHN_PRESSURE"}, + {MIDI_PITCH_BEND, "MIDI_PITCH_BEND"}, + {MIDI_SYSTEM_PREFIX, "MIDI_SYSTEM_PREFIX"}, + {-1, NULL}, +}; + + +/* The followings are the local function. */ +static int seq_init(void); +static int seq_initunit(int unit); +static int seq_queue(sc_p scp, u_char *note); +static void seq_startplay(sc_p scp); +static int seq_playevent(sc_p scp, u_char *event); +static u_long seq_gettime(void); +static int seq_requesttimer(sc_p scp, int delay); +static void seq_stoptimer(sc_p scp); +static void seq_midiinput(sc_p scp, mididev_info *md); +static int seq_extended(sc_p scp, u_char *event); +static int seq_chnvoice(sc_p scp, u_char *event); +static int seq_findvoice(mididev_info *md, int chn, int note) __unused; +static int seq_allocvoice(sc_p scp, mididev_info *md, int chn, int note) __unused; +static int seq_chncommon(sc_p scp, u_char *event); +static int seq_timing(sc_p scp, u_char *event); +static int seq_local(sc_p scp, u_char *event); +static int seq_sysex(sc_p scp, u_char *event); +static int seq_reset(sc_p scp); +static int seq_openmidi(sc_p scp, mididev_info *md, int flags, int mode, struct thread *p); +static int seq_closemidi(sc_p scp, mididev_info *md, int flags, int mode, struct thread *p); +static void seq_panic(sc_p scp); +static int seq_sync(sc_p scp); + +static seqdev_info *get_seqdev_info(dev_t i_dev, int *unit); +static seqdev_info *get_seqdev_info_unit(int unit); +static seqdev_info *create_seqdev_info_unit(int unit, seqdev_info *seq); +static int lookup_mididev(sc_p scp, int unit, int mode, mididev_info **mdp); +static int lookup_mididev_midi(sc_p scp, int unit, int mode, mididev_info **mdp); +static void seq_clone(void *arg, char *name, int namelen, dev_t *dev); + +/* + * Here are the main functions to interact to the user process. + * These are called from snd* functions in sys/i386/isa/snd/sound.c. + */ + +static int +seq_init(void) +{ + SEQ_DEBUG(printf("seq: initing.\n")); + + mtx_init(&seqinfo_mtx, "seqinf", NULL, MTX_DEF); + TAILQ_INIT(&seq_info); + + seq_initunit(0); + EVENTHANDLER_REGISTER(dev_clone, seq_clone, 0, 1000); + + SEQ_DEBUG(printf("seq: inited.\n")); + + return (0); +} + +static int +seq_initunit(int unit) +{ + sc_p scp; + seqdev_info *devinfo; + dev_t seqdev, musicdev; + + /* Allocate the softc. */ + scp = malloc(sizeof(*scp), M_DEVBUF, M_WAITOK | M_ZERO); + if (scp == (sc_p)NULL) { + printf("seq_initunit: unit %d, softc allocation failed.\n", unit); + return (1); + } + + /* Fill the softc and the seq_info for this unit. */ + scp->seq_time = seq_gettime(); + scp->prev_event_time = 0; + scp->prev_input_time = 0; + scp->prev_wakeup_time = scp->seq_time; +#if defined(MIDI_OUTOFGIANT) + callout_init(&scp->timeout_ch, 1); +#else + callout_init(&scp->timeout_ch, 0); +#endif /* MIDI_OUTOFGIANT */ + scp->timer_current = 0; + scp->timer_running = 0; + scp->queueout_pending = 0; + TAILQ_INIT(&scp->midi_open); + scp->pending_timer = -1; + + scp->devinfo = devinfo = create_seqdev_info_unit(unit, &seq_op_desc); + devinfo->midi_dbuf_in.unit_size = devinfo->midi_dbuf_out.unit_size = EV_SZ; + devinfo->softc = scp; + devinfo->flags = 0; + mtx_unlock(&devinfo->flagqueue_mtx); + + seqdev = make_dev(&seq_cdevsw, MIDIMKMINOR(unit, SND_DEV_SEQ), + UID_ROOT, GID_WHEEL, 0666, "sequencer%d", unit); + musicdev = make_dev(&seq_cdevsw, MIDIMKMINOR(unit, SND_DEV_MUSIC), + UID_ROOT, GID_WHEEL, 0666, "music%d", unit); + + mtx_lock(&seqinfo_mtx); + if (seq_alias != NODEV) { + destroy_dev(seq_alias); + seq_alias = NODEV; + } + seq_alias = make_dev_alias(seqdev, "sequencer"); + if (music_alias != NODEV) { + destroy_dev(music_alias); + music_alias = NODEV; + } + music_alias = make_dev_alias(musicdev, "music"); + mtx_unlock(&seqinfo_mtx); + + if (timerdev_install() != 0) + printf("seq_initunit: timerdev_install failed.\n"); + + return (0); +} + +int +seq_open(dev_t i_dev, int flags, int mode, struct thread *td) +{ + int unit; + sc_p scp; + seqdev_info *sd; + + unit = MIDIUNIT(i_dev); + + SEQ_DEBUG(printf("seq_open: unit %d, flags 0x%x.\n", unit, flags)); + + if (unit >= NSEQ_MAX) { + SEQ_DEBUG(printf("seq_open: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + SEQ_DEBUG(printf("seq_open: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + + /* Mark this device busy. */ + mtx_lock(&sd->flagqueue_mtx); + if ((sd->flags & SEQ_F_BUSY) != 0) { + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_open: unit %d is busy.\n", unit)); + return (EBUSY); + } + scp->fflags = flags; + sd->flags |= SEQ_F_BUSY; + sd->flags &= ~(SEQ_F_READING | SEQ_F_WRITING); + if ((scp->fflags & O_NONBLOCK) != 0) + sd->flags |= SEQ_F_NBIO; + scp->seq_mode = MIDIDEV(i_dev); + + /* Init the queue. */ + midibuf_clear(&sd->midi_dbuf_in); + midibuf_clear(&sd->midi_dbuf_out); + + /* Init timestamp. */ + scp->seq_time = seq_gettime(); + scp->prev_event_time = 0; + scp->prev_input_time = 0; + scp->prev_wakeup_time = scp->seq_time; + + if (scp->pending_timer != -1) { + scp->timer = get_timerdev_info_unit(scp->pending_timer); + scp->pending_timer = -1; + } + if (scp->timer == NULL) + scp->timer = get_timerdev_info(); + if (scp->timer != NULL) { + scp->timer->seq = scp; + mtx_unlock(&scp->timer->mtx); + } else if (scp->seq_mode == SND_DEV_MUSIC) { + mtx_unlock(&sd->flagqueue_mtx); + printf("seq_open: no timer available.\n"); + sd->flags &= ~SEQ_F_BUSY; + return (ENXIO); + } + + if (scp->seq_mode == SND_DEV_MUSIC) + scp->timer->open(scp->timer, flags, mode, td); + + /* Begin recording if nonblocking. */ + if ((sd->flags & (SEQ_F_READING | SEQ_F_NBIO)) == SEQ_F_NBIO && (scp->fflags & FREAD) != 0) + sd->callback(sd, SEQ_CB_START | SEQ_CB_RD); + + mtx_unlock(&sd->flagqueue_mtx); + + SEQ_DEBUG(printf("seq_open: opened, mode %d.\n", scp->seq_mode == SND_DEV_MUSIC ? 2 : 1)); + + return (0); +} + +int +seq_close(dev_t i_dev, int flags, int mode, struct thread *td) +{ + int unit; + sc_p scp; + seqdev_info *sd; + mididev_info *md; + timerdev_info *tmd; + + unit = MIDIUNIT(i_dev); + + SEQ_DEBUG(printf("seq_close: unit %d.\n", unit)); + + if (unit >= NSEQ_MAX) { + SEQ_DEBUG(printf("seq_close: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + SEQ_DEBUG(printf("seq_close: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + + mtx_lock(&sd->flagqueue_mtx); + + if (!(sd->flags & MIDI_F_NBIO)) + seq_sync(scp); + + /* Stop the timer. */ + seq_stoptimer(scp); + + /* Reset the sequencer. */ + seq_reset(scp); + seq_sync(scp); + + /* Clean up the midi device. */ + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) + lookup_mididev(scp, md->unit, LOOKUP_CLOSE, NULL); + + /* Stop playing and unmark this device busy. */ + sd->flags &= ~(SEQ_F_BUSY | SEQ_F_READING | SEQ_F_WRITING | SEQ_F_INSYNC); + + if (scp->seq_mode == SND_DEV_MUSIC) + scp->timer->close(scp->timer, flags, mode, td); + + if (scp->timer != NULL) { + tmd = scp->timer; + mtx_lock(&tmd->mtx); + scp->timer = NULL; + tmd->seq = NULL; + mtx_unlock(&tmd->mtx); + } + + mtx_unlock(&sd->flagqueue_mtx); + + SEQ_DEBUG(printf("seq_close: closed.\n")); + + return (0); +} + +int +seq_read(dev_t i_dev, struct uio *buf, int flag) +{ + int unit, ret, len, lenr; + sc_p scp; + seqdev_info *sd; + u_char *uiobuf; + + unit = MIDIUNIT(i_dev); + + SEQ_DEBUG(printf("seq_read: unit %d, resid %d.\n", unit, buf->uio_resid)); + + if (unit >= NSEQ_MAX) { + SEQ_DEBUG(printf("seq_read: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + SEQ_DEBUG(printf("seq_read: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + if ((scp->fflags & FREAD) == 0) { + SEQ_DEBUG(printf("seq_read: unit %d is not for reading.\n", unit)); + return (EIO); + } + + len = buf->uio_resid; + lenr = 0; + + uiobuf = (u_char *)malloc(len, M_DEVBUF, M_WAITOK | M_ZERO); + if (uiobuf == NULL) + return (ENOMEM); + + mtx_lock(&sd->flagqueue_mtx); + + /* Begin recording. */ + if ((sd->flags & SEQ_F_READING) == 0) + sd->callback(sd, SEQ_CB_START | SEQ_CB_RD); + + /* Have we got the data to read? */ + if ((sd->flags & SEQ_F_NBIO) != 0 && sd->midi_dbuf_in.rl == 0) + ret = EAGAIN; + else { + if ((sd->flags & SEQ_F_NBIO) != 0 && len > sd->midi_dbuf_in.rl) + len = sd->midi_dbuf_in.rl; + ret = midibuf_seqread(&sd->midi_dbuf_in, uiobuf, len, &lenr, + sd->callback, sd, SEQ_CB_START | SEQ_CB_RD, + &sd->flagqueue_mtx); + } + + mtx_unlock(&sd->flagqueue_mtx); + + if (ret == 0 && lenr > 0) + ret = uiomove(uiobuf, lenr, buf); + + free(uiobuf, M_DEVBUF); + + SEQ_DEBUG(printf("seq_read: ret %d, resid %d.\n", ret, buf->uio_resid)); + + return (ret); +} + +int +seq_write(dev_t i_dev, struct uio *buf, int flag) +{ + u_char event[EV_SZ], ev_code; + int unit, count, countorg, midiunit, ev_size, p, ret; + sc_p scp; + seqdev_info *sd; + mididev_info *md; + + unit = MIDIUNIT(i_dev); + + SEQ_DEBUG(printf("seq_write: unit %d, resid %d.\n", unit, buf->uio_resid)); + + if (unit >= NSEQ_MAX) { + SEQ_DEBUG(printf("seq_write: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + SEQ_DEBUG(printf("seq_write: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + if ((scp->fflags & FWRITE) == 0) { + SEQ_DEBUG(printf("seq_write: unit %d is not for writing.\n", unit)); + return (EIO); + } + + p = 0; + countorg = buf->uio_resid; + count = countorg; + + /* Pick up an event. */ + while (count >= 4) { + if (uiomove((caddr_t)event, 4, buf)) + printf("seq_write: user memory mangled?\n"); + ev_code = event[0]; + SEQ_DEBUG(printf("seq_write: unit %d, event %s.\n", unit, midi_cmdname(ev_code, cmdtab_seqevent))); + + /* Have a look at the event code. */ + if (ev_code == SEQ_FULLSIZE) { + + /* A long event, these are the patches/samples for a synthesizer. */ + midiunit = *(u_short *)&event[2]; + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev(scp, midiunit, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + return (ret); + + SEQ_DEBUG(printf("seq_write: loading a patch to the unit %d.\n", midiunit)); + + ret = md->synth.loadpatch(md, *(short *)&event[0], buf, p + 4, count, 0); + return (ret); + } + + if (ev_code >= 128) { + + /* Some sort of an extended event. The size is eight bytes. */ + if (scp->seq_mode == SND_DEV_MUSIC && ev_code == SEQ_EXTENDED) { + printf("seq_write: invalid level two event %x.\n", ev_code); + return (EINVAL); + } + ev_size = 8; + + if (count < ev_size) { + /* No more data. Start playing now. */ + mtx_lock(&sd->flagqueue_mtx); + if ((sd->flags & SEQ_F_WRITING) == 0) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + mtx_unlock(&sd->flagqueue_mtx); + buf->uio_resid += 4; + + return (0); + } + if (uiomove((caddr_t)&event[4], 4, buf)) + printf("seq_write: user memory mangled?\n"); + } else { + + /* Not an extended event. The size is four bytes. */ + if (scp->seq_mode == SND_DEV_MUSIC) { + printf("seq_write: four byte event in level two mode.\n"); + return (EINVAL); + } + ev_size = 4; + } + if (ev_code == SEQ_MIDIPUTC) { + /* An event passed to the midi device itself. */ + midiunit = event[2]; + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev_midi(scp, midiunit, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + return (ret); + } + + SEQ_DEBUG(printf("seq_write: queueing event %s.\n", midi_cmdname(event[0], cmdtab_seqevent))); + /* Now we queue the event. */ + mtx_lock(&sd->flagqueue_mtx); + switch (seq_queue(scp, event)) { + case EAGAIN: + /* The queue is full. Start playing now. */ + if ((sd->flags & SEQ_F_WRITING) == 0) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + mtx_unlock(&sd->flagqueue_mtx); + buf->uio_resid = count; + SEQ_DEBUG(printf("seq_write: resid %d.\n", buf->uio_resid)); + if (count < countorg) + return (0); + return (EAGAIN); + case EINTR: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_write: resid %d.\n", buf->uio_resid)); + return (EINTR); + case ERESTART: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_write: resid %d.\n", buf->uio_resid)); + return (ERESTART); + } + mtx_unlock(&sd->flagqueue_mtx); + p += ev_size; + count -= ev_size; + } + + /* We have written every single data. Start playing now. */ + mtx_lock(&sd->flagqueue_mtx); + if ((sd->flags & SEQ_F_WRITING) == 0) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + mtx_unlock(&sd->flagqueue_mtx); + + SEQ_DEBUG(printf("seq_write: resid %d.\n", buf->uio_resid)); + + return (0); +} + +int +seq_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + int unit, midiunit, ret, tmp; + sc_p scp; + seqdev_info *sd; + mididev_info *md; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + struct patmgr_info *patinfo; + struct seq_event_rec *event; + struct snd_size *sndsize; + + unit = MIDIUNIT(i_dev); + + SEQ_DEBUG(printf("seq_ioctl: unit %d, cmd %s.\n", unit, midi_cmdname(cmd, cmdtab_seqioctl))); + + if (unit >= NSEQ_MAX) { + SEQ_DEBUG(printf("seq_ioctl: unit %d does not exist.\n", unit)); + return (ENXIO); + } + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + SEQ_DEBUG(printf("seq_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + + ret = 0; + + switch (cmd) { + + /* + * we start with the new ioctl interface. + */ + case AIONWRITE: /* how many bytes can be written ? */ + mtx_lock(&sd->flagqueue_mtx); + *(int *)arg = sd->midi_dbuf_out.fl; + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_ioctl: fl %d.\n", *(int *)arg)); + break; + + case AIOSSIZE: /* set the current blocksize */ + sndsize = (struct snd_size *)arg; + SEQ_DEBUG(printf("seq_ioctl: play %d, rec %d.\n", sndsize->play_size, sndsize->rec_size)); + mtx_lock(&sd->flagqueue_mtx); + if (sndsize->play_size <= sd->midi_dbuf_out.unit_size && sndsize->rec_size <= sd->midi_dbuf_in.unit_size) { + sd->midi_dbuf_out.blocksize = sd->midi_dbuf_out.unit_size; + sd->midi_dbuf_in.blocksize = sd->midi_dbuf_in.unit_size; + sndsize->play_size = sd->midi_dbuf_out.blocksize; + sndsize->rec_size = sd->midi_dbuf_in.blocksize; + sd->flags &= ~MIDI_F_HAS_SIZE; + mtx_unlock(&sd->flagqueue_mtx); + } + else { + if (sndsize->play_size > sd->midi_dbuf_out.bufsize / 4) + sndsize->play_size = sd->midi_dbuf_out.bufsize / 4; + if (sndsize->rec_size > sd->midi_dbuf_in.bufsize / 4) + sndsize->rec_size = sd->midi_dbuf_in.bufsize / 4; + /* Round up the size to the multiple of EV_SZ. */ + sd->midi_dbuf_out.blocksize = + ((sndsize->play_size + sd->midi_dbuf_out.unit_size - 1) + / sd->midi_dbuf_out.unit_size) * sd->midi_dbuf_out.unit_size; + sd->midi_dbuf_in.blocksize = + ((sndsize->rec_size + sd->midi_dbuf_in.unit_size - 1) + / sd->midi_dbuf_in.unit_size) * sd->midi_dbuf_in.unit_size; + sndsize->play_size = sd->midi_dbuf_out.blocksize; + sndsize->rec_size = sd->midi_dbuf_in.blocksize; + sd->flags |= MIDI_F_HAS_SIZE; + mtx_unlock(&sd->flagqueue_mtx); + } + + ret = 0; + break; + + case AIOGSIZE: /* get the current blocksize */ + sndsize = (struct snd_size *)arg; + mtx_lock(&sd->flagqueue_mtx); + sndsize->play_size = sd->midi_dbuf_out.blocksize; + sndsize->rec_size = sd->midi_dbuf_in.blocksize; + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_ioctl: play %d, rec %d.\n", sndsize->play_size, sndsize->rec_size)); + + ret = 0; + break; + + case AIOSTOP: + if (*(int *)arg == AIOSYNC_PLAY) { + + /* Stop writing. */ + mtx_lock(&sd->flagqueue_mtx); + sd->callback(sd, SEQ_CB_ABORT | SEQ_CB_WR); + mtx_unlock(&sd->flagqueue_mtx); + + /* Pass the ioctl to the midi devices. */ + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) { + if ((md->flags & MIDI_F_WRITING) != 0) + midi_ioctl(MIDIMKDEV(major(i_dev), md->unit, SND_DEV_MIDIN), cmd, (caddr_t)arg, mode, td); + } + + mtx_lock(&sd->flagqueue_mtx); + *(int *)arg = sd->midi_dbuf_out.rl; + mtx_unlock(&sd->flagqueue_mtx); + } + else if (*(int *)arg == AIOSYNC_CAPTURE) { + + /* Stop reading. */ + mtx_lock(&sd->flagqueue_mtx); + sd->callback(sd, SEQ_CB_ABORT | SEQ_CB_RD); + mtx_unlock(&sd->flagqueue_mtx); + + /* Pass the ioctl to the midi devices. */ + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) { + if ((md->flags & MIDI_F_WRITING) != 0) + midi_ioctl(MIDIMKDEV(major(i_dev), md->unit, SND_DEV_MIDIN), cmd, (caddr_t)arg, mode, td); + } + + mtx_lock(&sd->flagqueue_mtx); + *(int *)arg = sd->midi_dbuf_in.rl; + mtx_unlock(&sd->flagqueue_mtx); + } + + ret = 0; + break; + + case AIOSYNC: + mtx_lock(&sd->flagqueue_mtx); + scp->sync_parm = *(snd_sync_parm *)arg; + mtx_unlock(&sd->flagqueue_mtx); + + /* XXX Should select(2) against us watch the blocksize, or sync_parm? */ + + ret = 0; + break; + + case FIONBIO: /* set/clear non-blocking i/o */ + mtx_lock(&sd->flagqueue_mtx); + if (*(int *)arg == 0) + sd->flags &= ~SEQ_F_NBIO ; + else + sd->flags |= SEQ_F_NBIO ; + mtx_unlock(&sd->flagqueue_mtx); + MIDI_DEBUG(printf("seq_ioctl: arg %d.\n", *(int *)arg)); + break ; + + case SNDCTL_TMR_TIMEBASE: + case SNDCTL_TMR_TEMPO: + case SNDCTL_TMR_START: + case SNDCTL_TMR_STOP: + case SNDCTL_TMR_CONTINUE: + case SNDCTL_TMR_METRONOME: + case SNDCTL_TMR_SOURCE: + mtx_lock(&sd->flagqueue_mtx); + if (scp->seq_mode != SND_DEV_MUSIC) { + ret = EINVAL; + mtx_unlock(&sd->flagqueue_mtx); + break; + } + mtx_unlock(&sd->flagqueue_mtx); + /* XXX We should adopt am sx to protect scp->timer */ + ret = scp->timer->ioctl(scp->timer, cmd, arg, mode, td); + break; + case SNDCTL_TMR_SELECT: + mtx_lock(&sd->flagqueue_mtx); + if (scp->seq_mode != SND_DEV_MUSIC) { + ret = EINVAL; + mtx_unlock(&sd->flagqueue_mtx); + break; + } + mtx_unlock(&sd->flagqueue_mtx); + scp->pending_timer = *(int *)arg; + mtx_lock(&sd->flagqueue_mtx); + if (scp->pending_timer < 0) { + scp->pending_timer = -1; + ret = EINVAL; + mtx_unlock(&sd->flagqueue_mtx); + break; + } + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_ioctl: new timer %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_SEQ_PANIC: + mtx_lock(&sd->flagqueue_mtx); + seq_panic(scp); + mtx_unlock(&sd->flagqueue_mtx); + ret = 0; + break; + case SNDCTL_SEQ_SYNC: + if (mode == O_RDONLY) { + ret = 0; + break; + } + mtx_lock(&scp->devinfo->flagqueue_mtx); + ret = seq_sync(scp); + mtx_unlock(&scp->devinfo->flagqueue_mtx); + break; + case SNDCTL_SEQ_RESET: + mtx_lock(&scp->devinfo->flagqueue_mtx); + seq_reset(scp); + mtx_unlock(&scp->devinfo->flagqueue_mtx); + ret = 0; + break; + case SNDCTL_SEQ_TESTMIDI: + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev_midi(scp, *(int *)arg, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + break; + case SNDCTL_SEQ_GETINCOUNT: + if (mode == O_WRONLY) + *(int *)arg = 0; + else { + mtx_lock(&sd->flagqueue_mtx); + *(int *)arg = sd->midi_dbuf_in.rl; + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_ioctl: incount %d.\n", *(int *)arg)); + } + ret = 0; + break; + case SNDCTL_SEQ_GETOUTCOUNT: + if (mode == O_RDONLY) + *(int *)arg = 0; + else { + mtx_lock(&sd->flagqueue_mtx); + *(int *)arg = sd->midi_dbuf_out.fl; + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_ioctl: outcount %d.\n", *(int *)arg)); + } + ret = 0; + break; + case SNDCTL_SEQ_CTRLRATE: + mtx_lock(&sd->flagqueue_mtx); + if (scp->seq_mode == SND_DEV_MUSIC) { + mtx_unlock(&sd->flagqueue_mtx); + ret = scp->timer->ioctl(scp->timer, cmd, arg, mode, td); + break; + } + mtx_unlock(&sd->flagqueue_mtx); + if (*(int *)arg != 0) { + ret = EINVAL; + break; + } + *(int *)arg = hz; + SEQ_DEBUG(printf("seq_ioctl: ctrlrate %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_SEQ_RESETSAMPLES: + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, SND_DEV_MIDIN), cmd, arg, mode, td); + break; + case SNDCTL_SEQ_NRSYNTHS: + mtx_lock(&sd->flagqueue_mtx); + if (scp->seq_mode == SND_DEV_MUSIC) + *(int *)arg = mididev_synth_number() + mididev_midi_number(); + else + *(int *)arg = mididev_synth_number(); + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_ioctl: synths %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_SEQ_NRMIDIS: + mtx_lock(&sd->flagqueue_mtx); + if (scp->seq_mode == SND_DEV_MUSIC) + *(int *)arg = 0; + else + *(int *)arg = mididev_midi_number(); + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_ioctl: midis %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_SYNTH_MEMAVL: + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, SND_DEV_MIDIN), cmd, arg, mode, td); + break; + case SNDCTL_FM_4OP_ENABLE: + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, SND_DEV_MIDIN), cmd, arg, mode, td); + break; + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + midiunit = synthinfo->device; + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev(scp, midiunit, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, td); + break; + case SNDCTL_SEQ_OUTOFBAND: + event = (struct seq_event_rec *)arg; + mtx_lock(&sd->flagqueue_mtx); + ret = seq_playevent(scp, event->arr); + mtx_unlock(&sd->flagqueue_mtx); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + midiunit = midiinfo->device; + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev_midi(scp, midiunit, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, td); + break; + case SNDCTL_PMGR_IFACE: + patinfo = (struct patmgr_info *)arg; + midiunit = patinfo->device; + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev(scp, midiunit, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, td); + break; + case SNDCTL_PMGR_ACCESS: + patinfo = (struct patmgr_info *)arg; + midiunit = patinfo->device; + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev(scp, midiunit, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, td); + break; + case SNDCTL_SEQ_THRESHOLD: + mtx_lock(&sd->flagqueue_mtx); + RANGE(*(int *)arg, 1, sd->midi_dbuf_out.bufsize - 1); + scp->output_threshould = *(int *)arg; + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_ioctl: threshold %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_MIDI_PRETIME: + tmp = *(int *)arg; + if (tmp < 0) + tmp = 0; + mtx_lock(&sd->flagqueue_mtx); + scp->pre_event_timeout = (hz * tmp) / 10; + *(int *)arg = scp->pre_event_timeout; + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_ioctl: pretime %d.\n", *(int *)arg)); + ret = 0; + break; + default: + if ((scp->fflags & O_ACCMODE) == FREAD) { + ret = EIO; + break; + } + mtx_lock(&sd->flagqueue_mtx); + ret = lookup_mididev(scp, 0, LOOKUP_OPEN, &md); + mtx_unlock(&sd->flagqueue_mtx); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), 0, SND_DEV_MIDIN), cmd, arg, mode, td); + break; + } + + return (ret); +} + +int +seq_poll(dev_t i_dev, int events, struct thread *td) +{ + int unit, ret, lim; + sc_p scp; + seqdev_info *sd; + + unit = MIDIUNIT(i_dev); + + SEQ_DEBUG(printf("seq_poll: unit %d.\n", unit)); + + if (unit >= NSEQ_MAX) { + SEQ_DEBUG(printf("seq_poll: unit %d does not exist.\n", unit)); + return (ENXIO); + } + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + SEQ_DEBUG(printf("seq_poll: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + + mtx_lock(&sd->flagqueue_mtx); + + ret = 0; + + /* Look up the apropriate queue and select it. */ + if ((events & (POLLOUT | POLLWRNORM)) != 0) { + /* Start playing. */ + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + + /* Find out the boundary. */ + if ((sd->flags & SEQ_F_HAS_SIZE) != 0) + lim = sd->midi_dbuf_out.blocksize; + else + lim = sd->midi_dbuf_out.unit_size; + if (sd->midi_dbuf_out.fl < lim) + /* No enough space, record select. */ + selrecord(td, &sd->midi_dbuf_out.sel); + else + /* We can write now. */ + ret |= events & (POLLOUT | POLLWRNORM); + } + if ((events & (POLLIN | POLLRDNORM)) != 0) { + /* Start recording. */ + sd->callback(sd, SEQ_CB_START | SEQ_CB_RD); + + /* Find out the boundary. */ + if ((sd->flags & SEQ_F_HAS_SIZE) != 0) + lim = sd->midi_dbuf_in.blocksize; + else + lim = sd->midi_dbuf_in.unit_size; + if (sd->midi_dbuf_in.rl < lim) + /* No data ready, record select. */ + selrecord(td, &sd->midi_dbuf_in.sel); + else + /* We can write now. */ + ret |= events & (POLLIN | POLLRDNORM); + } + + mtx_unlock(&sd->flagqueue_mtx); + + return (ret); +} + +static void +seq_intr(void *p, mididev_info *md) +{ + sc_p scp; + seqdev_info *sd; + + sd = (seqdev_info *)p; + scp = sd->softc; + + mtx_lock(&sd->flagqueue_mtx); + + /* Restart playing if we have the data to output. */ + if (scp->queueout_pending) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + /* Check the midi device if we are reading. */ + if ((sd->flags & SEQ_F_READING) != 0) + seq_midiinput(scp, md); + + mtx_unlock(&sd->flagqueue_mtx); +} + +static int +seq_callback(void *d, int reason) +{ + int unit; + sc_p scp; + seqdev_info *sd; + + sd = (seqdev_info *)d; + + SEQ_DEBUG(printf("seq_callback: reason 0x%x.\n", reason)); + + if (sd == NULL) { + SEQ_DEBUG(printf("seq_callback: device not configured.\n")); + return (ENXIO); + } + scp = sd->softc; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + switch (reason & SEQ_CB_REASON_MASK) { + case SEQ_CB_START: + if ((reason & SEQ_CB_RD) != 0 && (sd->flags & SEQ_F_READING) == 0) + /* Begin recording. */ + sd->flags |= SEQ_F_READING; + if ((reason & SEQ_CB_WR) != 0 && (sd->flags & SEQ_F_WRITING) == 0) + /* Start playing. */ + seq_startplay(scp); + break; + case SEQ_CB_STOP: + case SEQ_CB_ABORT: + if ((reason & SEQ_CB_RD) != 0 && (sd->flags & SEQ_F_READING) != 0) { + /* Stop recording. */ + sd->flags &= ~SEQ_F_READING; + scp->seq_time = seq_gettime(); + scp->prev_input_time = 0; + } + if ((reason & SEQ_CB_WR) != 0 && (sd->flags & SEQ_F_WRITING) != 0) { + /* Stop Playing. */ + sd->flags &= ~SEQ_F_WRITING; + scp->queueout_pending = 0; + scp->seq_time = seq_gettime(); + scp->prev_input_time = 0; + + /* Stop the timer. */ + seq_stoptimer(scp); + } + } + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +static int +seq_queue(sc_p scp, u_char *note) +{ + int unit, err, lenw; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + SEQ_DEBUG(printf("seq_queue: unit %d.\n", unit)); + + if ((sd->flags & SEQ_F_INSYNC) != 0) + cv_wait(&sd->insync_cv, &sd->flagqueue_mtx); + + if (sd->midi_dbuf_out.fl < EV_SZ) { + /* We have no space. Start playing if not yet. */ + if ((sd->flags & SEQ_F_WRITING) == 0) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + if ((sd->flags & SEQ_F_NBIO) != 0 && sd->midi_dbuf_out.fl < EV_SZ) + /* We would block. */ + return (EAGAIN); + } + + /* Write to the queue. */ + err = midibuf_seqwrite(&sd->midi_dbuf_out, note, EV_SZ, &lenw, + sd->callback, sd, SEQ_CB_START | SEQ_CB_WR, + &sd->flagqueue_mtx); + + if (err == 0) { + /* Start playing if we have some data in the queue. */ + if (sd->midi_dbuf_out.rl >= EV_SZ && ((sd->flags & SEQ_F_WRITING) == 0)) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + } + + return (err); +} + +static void +seq_startplay(sc_p scp) +{ + int unit, lenr; + u_char event[EV_SZ]; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + sd->flags |= SEQ_F_WRITING; + + /* Dequeue the events to play. */ + while (sd->midi_dbuf_out.rl >= EV_SZ) { + + midibuf_seqcopy(&sd->midi_dbuf_out, event, EV_SZ, &lenr, + NULL, NULL, 0, + &sd->flagqueue_mtx); + + switch (seq_playevent(scp, event)) { + case TIMERARMED: + midibuf_seqdelete(&sd->midi_dbuf_out, EV_SZ, &lenr, + NULL, NULL, 0, + &sd->flagqueue_mtx); + return; + case QUEUEFULL: + /* We cannot play any further. */ + return; + case MORE: + midibuf_seqdelete(&sd->midi_dbuf_out, EV_SZ, &lenr, + NULL, NULL, 0, + &sd->flagqueue_mtx); + break; + } + } + + /* Played every event in the queue. */ + sd->flags &= ~SEQ_F_WRITING; +} + +static int +seq_playevent(sc_p scp, u_char *event) +{ + int unit, ret, lenw; + long *delay; + seqdev_info *sd; + mididev_info *md; + + sd = scp->devinfo; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + ret = lookup_mididev(scp, 0, LOOKUP_OPEN, &md); + if (ret != 0) + return (MORE); + + SEQ_DEBUG(printf("seq_playevent: unit %d, event %s.\n", sd->unit, midi_cmdname(event[0], cmdtab_seqevent))); + + switch(event[0]) { + case SEQ_NOTEOFF: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_playevent: chn %d, note %d, vel %d.\n", event[1], event[2], event[3])); + if (md->synth.killnote(md, event[1], 255, event[3]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + ret = QUEUEFULL; + break; + } + mtx_lock(&sd->flagqueue_mtx); + ret = MORE; + break; + case SEQ_NOTEON: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_playevent: chn %d, note %d, vel %d, aux %d.\n", event[1], event[2], event[3], event[4])); + if ((event[4] < 128 || event[4] == 255) && md->synth.startnote(md, event[1], event[2], event[3]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + ret = QUEUEFULL; + break; + } + mtx_lock(&sd->flagqueue_mtx); + ret = MORE; + break; + case SEQ_WAIT: + + /* Extract the delay. */ + delay = (long *)event; + *delay = (*delay >> 8) & 0xffffff; + SEQ_DEBUG(printf("seq_playevent: delay %ld.\n", *delay)); + if (*delay > 0) { + /* Arm the timer. */ + sd->flags |= SEQ_F_WRITING; + if (seq_requesttimer(scp, *delay)) { + ret = TIMERARMED; + break; + } + } + ret = MORE; + break; + case SEQ_PGMCHANGE: + SEQ_DEBUG(printf("seq_playevent: chn %d, instr %d.\n", event[1], event[2])); + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.setinstr(md, event[1], event[2]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + ret = QUEUEFULL; + break; + } + mtx_lock(&sd->flagqueue_mtx); + ret = MORE; + break; + case SEQ_SYNCTIMER: + /* Reset the timer. */ + scp->seq_time = seq_gettime(); + scp->prev_input_time = 0; + scp->prev_event_time = 0; + scp->prev_wakeup_time = scp->seq_time; + ret = MORE; + break; + case SEQ_MIDIPUTC: + SEQ_DEBUG(printf("seq_playevent: data 0x%02x, unit %d.\n", event[1], event[2])); + /* Pass through to the midi device. */ + ret = lookup_mididev_midi(scp, event[2], LOOKUP_OPEN, &md); + if (ret != 0) { + ret = MORE; + break; + } + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.writeraw(md, &event[1], sizeof(event[1]), &lenw, 1) == EAGAIN) + /* The queue was full. Try again later. */ + ret = QUEUEFULL; + else + ret = MORE; + mtx_lock(&sd->flagqueue_mtx); + break; + case SEQ_ECHO: + /* Echo this event back. */ + if (seq_copytoinput(scp, event, 4) == EAGAIN) { + ret = QUEUEFULL; + break; + } + ret = MORE; + break; + case SEQ_PRIVATE: + ret = lookup_mididev(scp, event[1], LOOKUP_OPEN, &md); + if (ret != 0) { + ret = MORE; + break; + } + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.hwcontrol(md, event) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + ret = QUEUEFULL; + break; + } + mtx_lock(&sd->flagqueue_mtx); + ret = MORE; + break; + case SEQ_EXTENDED: + ret = seq_extended(scp, event); + break; + case EV_CHN_VOICE: + ret = seq_chnvoice(scp, event); + break; + case EV_CHN_COMMON: + ret = seq_chncommon(scp, event); + break; + case EV_TIMING: + ret = seq_timing(scp, event); + break; + case EV_SEQ_LOCAL: + ret = seq_local(scp, event); + break; + case EV_SYSEX: + ret = seq_sysex(scp, event); + break; + default: + ret = MORE; + break; + } + + switch (ret) { + case QUEUEFULL: + SEQ_DEBUG(printf("seq_playevent: the queue is full.\n")); + /* The queue was full. Try again on the interrupt by the midi device. */ + sd->flags |= SEQ_F_WRITING; + scp->queueout_pending = 1; + break; + case TIMERARMED: + SEQ_DEBUG(printf("seq_playevent: armed timer.\n")); + sd->flags |= SEQ_F_WRITING; + /* FALLTHRU */ + case MORE: + scp->queueout_pending = 0; + break; + } + + return (ret); +} + +static u_long +seq_gettime(void) +{ + struct timeval timecopy; + + getmicrotime(&timecopy); + return timecopy.tv_usec / (1000000 / hz) + (u_long) timecopy.tv_sec * hz; +} + +static int +seq_requesttimer(sc_p scp, int delay) +{ + u_long cur_time, rel_base; + + SEQ_DEBUG(printf("seq_requesttimer: unit %d, delay %d.\n", scp->devinfo->unit, delay)); + + mtx_assert(&scp->devinfo->flagqueue_mtx, MA_OWNED); + + cur_time = seq_gettime(); + + scp->prev_event_time = delay; + if (delay < 0) + /* Request a new timer. */ + delay = -delay; + else { + rel_base = cur_time - scp->seq_time; + if (delay <= rel_base) { + seq_stoptimer(scp); + return 0; + } + delay -= rel_base; + } + +#if notdef + /* + * Compensate the delay of midi message transmission. + * XXX Do we have to consider the accumulation of errors + * less than 1/hz second? + */ + delay -= (cur_time - scp->prev_wakeup_time); + if (delay < 1) { + printf("sequencer: prev = %lu, cur = %lu, delay = %d, skip sleeping.\n", + scp->prev_wakeup_time, cur_time, delay); + seq_stoptimer(scp); + return 0; + } +#endif /* notdef */ + + callout_reset(&scp->timeout_ch, delay, seq_timer, (void *)scp); + scp->timer_running = 1; + + return 1; +} + +static void +seq_stoptimer(sc_p scp) +{ + SEQ_DEBUG(printf("seq_stoptimer: unit %d.\n", scp->devinfo->unit)); + + mtx_assert(&scp->devinfo->flagqueue_mtx, MA_OWNED); + + if (scp->timer_running) { + callout_stop(&scp->timeout_ch); + scp->timer_running = 0; + } +} + +static void +seq_midiinput(sc_p scp, mididev_info *md) +{ + int unit, midiunit, lenr; + u_long tstamp; + u_char event[4]; + seqdev_info *sd; + + mtx_assert(&scp->devinfo->flagqueue_mtx, MA_OWNED); + + sd = scp->devinfo; + unit = sd->unit; + + /* Can this midi device interrupt for input? */ + midiunit = md->midiunit; + if (lookup_mididev_midi(scp, midiunit, LOOKUP_EXIST, NULL) != 0) + return; + + if ((md->flags & MIDI_F_READING) != 0 && md->intrarg == sd) { + /* Read the input data. */ + mtx_unlock(&scp->devinfo->flagqueue_mtx); + while (md->synth.readraw(md, &event[1], sizeof(event[1]), &lenr, 1) == 0) { + mtx_lock(&scp->devinfo->flagqueue_mtx); + tstamp = seq_gettime() - scp->seq_time; + if (tstamp != scp->prev_input_time) { + /* Insert a wait between events. */ + tstamp = (tstamp << 8) | SEQ_WAIT; + seq_copytoinput(scp, (u_char *)&tstamp, 4); + scp->prev_input_time = tstamp; + } + bzero(event, sizeof(event)); + event[0] = SEQ_MIDIPUTC; + event[2] = midiunit; + event[3] = 0; + seq_copytoinput(scp, event, sizeof(event)); + mtx_unlock(&scp->devinfo->flagqueue_mtx); + } + mtx_lock(&scp->devinfo->flagqueue_mtx); + } +} + +int +seq_copytoinput(void *arg, u_char *event, int len) +{ + int ret, leni; + sc_p scp; + seqdev_info *sd; + + scp = arg; + sd = scp->devinfo; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + if (len != 4 && len != 8) + return (EINVAL); + if (scp->seq_mode == SND_DEV_MUSIC && len != 8) + return (EINVAL); + + ret = midibuf_input_intr(&sd->midi_dbuf_in, event, len, &leni); + if (ret == EAGAIN) + ret = 0; + + return (ret); +} + +static int +seq_extended(sc_p scp, u_char *event) +{ + int unit; + seqdev_info *sd; + mididev_info *md; + + sd = scp->devinfo; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + if (lookup_mididev(scp, event[2], LOOKUP_OPEN, &md) != 0) + return (MORE); + + SEQ_DEBUG(printf("seq_extended: unit %d, event %s, midiunit %d.\n", unit, midi_cmdname(event[1], cmdtab_seqevent), event[2])); + + switch (event[1]) { + case SEQ_NOTEOFF: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_extended: chn %d, note %d, vel %d.\n", event[3], event[4], event[5])); + if (md->synth.killnote(md, event[3], event[4], event[5]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + break; + case SEQ_NOTEON: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_extended: chn %d, note %d, vel %d.\n", event[3], event[4], event[5])); + if ((event[4] < 128 || event[4] == 255) && md->synth.startnote(md, event[3], event[4], event[5]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + break; + case SEQ_PGMCHANGE: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_extended: chn %d, instr %d.\n", event[3], event[4])); + if (md->synth.setinstr(md, event[3], event[4]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + break; + case SEQ_AFTERTOUCH: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_extended: chn %d, press %d.\n", event[3], event[4])); + if (md->synth.aftertouch(md, event[3], event[4]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + break; + case SEQ_BALANCE: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_extended: chn %d, pan %d.\n", event[3], event[4])); + if (md->synth.panning(md, event[3], (char)event[4]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + break; + case SEQ_CONTROLLER: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_extended: chn %d, ctrlnum %d, val %d.\n", event[3], event[4], *(short *)&event[5])); + if (md->synth.controller(md, event[3], event[4], *(short *)&event[5]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + break; + case SEQ_VOLMODE: + mtx_unlock(&sd->flagqueue_mtx); + SEQ_DEBUG(printf("seq_extended: mode %d.\n", event[3])); + if (md->synth.volumemethod != NULL && md->synth.volumemethod(md, event[3]) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + break; + } + + return (MORE); +} + +static int +seq_chnvoice(sc_p scp, u_char *event) +{ + int voice; + seqdev_info *sd; + mididev_info *md; + u_char dev, cmd, chn, note, parm; + + voice = -1; + dev = event[1]; + cmd = event[2]; + chn = event[3]; + note = event[4]; + parm = event[5]; + + sd = scp->devinfo; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + if (lookup_mididev(scp, dev, LOOKUP_OPEN, &md) != 0) + return (MORE); + + SEQ_DEBUG(printf("seq_chnvoice: unit %d, dev %d, cmd %s, chn %d, note %d, parm %d.\n", + sd->unit, + dev, + midi_cmdname(cmd, cmdtab_seqcv), + chn, + note, + parm)); + + if (scp->seq_mode == SND_DEV_MUSIC && md->synth.allocvoice != NULL) + voice = seq_allocvoice(scp, md, chn, note); + switch (cmd) { + case MIDI_NOTEON: + if (note < 128 || note == 255) { + if (voice == -1 && scp->seq_mode == SND_DEV_MUSIC && md->synth.allocvoice) + /* This is an internal synthesizer. (FM, GUS, etc) */ + if ((voice = seq_allocvoice(scp, md, chn, note)) == EAGAIN) + return (QUEUEFULL); + if (voice == -1) + voice = chn; + + if (scp->seq_mode == SND_DEV_MUSIC && chn == 9) { + /* This channel is a percussion. The note number is the patch number. */ + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.setinstr(md, voice, 128 + note) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + + note = 60; /* Middle C. */ + } + if (scp->seq_mode == SND_DEV_MUSIC) { + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.setupvoice(md, voice, chn) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + } + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.startnote(md, voice, note, parm) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + } + break; + case MIDI_NOTEOFF: + if (voice == -1) + voice = chn; + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.killnote(md, voice, note, parm) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + break; + case MIDI_KEY_PRESSURE: + if (voice == -1) + voice = chn; + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.aftertouch(md, voice, parm) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + break; + } + + return (MORE); +} + +static int +seq_findvoice(mididev_info *md, int chn, int note) +{ + int i; + u_short key; + + key = (chn << 8) | (note + 1); + + mtx_lock(&md->synth.vc_mtx); + for (i = 0 ; i < md->synth.alloc.max_voice ; i++) + if (md->synth.alloc.map[i] == key) { + mtx_unlock(&md->synth.vc_mtx); + return (i); + } + mtx_unlock(&md->synth.vc_mtx); + + return (-1); +} + +static int +seq_allocvoice(sc_p scp, mididev_info *md, int chn, int note) +{ + int voice; + u_short key; + + mtx_assert(&scp->devinfo->flagqueue_mtx, MA_OWNED); + + key = (chn << 8) | (note + 1); + + mtx_unlock(&scp->devinfo->flagqueue_mtx); + if ((voice = md->synth.allocvoice(md, chn, note, &md->synth.alloc)) == EAGAIN) { + mtx_lock(&scp->devinfo->flagqueue_mtx); + return (EAGAIN); + } + mtx_lock(&scp->devinfo->flagqueue_mtx); + + mtx_lock(&md->synth.vc_mtx); + md->synth.alloc.map[voice] = key; + md->synth.alloc.alloc_times[voice] = md->synth.alloc.timestamp++; + mtx_unlock(&md->synth.vc_mtx); + + return (voice); +} + +static int +seq_chncommon(sc_p scp, u_char *event) +{ + int unit, i, val, key; + u_short w14; + u_char dev, cmd, chn, p1; + seqdev_info *sd; + mididev_info *md; + + dev = event[1]; + cmd = event[2]; + chn = event[3]; + p1 = event[4]; + w14 = *(u_short *)&event[6]; + + sd = scp->devinfo; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + if (lookup_mididev(scp, dev, LOOKUP_OPEN, &md) != 0) + return (MORE); + + SEQ_DEBUG(printf("seq_chnvoice: unit %d, dev %d, cmd %s, chn %d, p1 %d, w14 %d.\n", + sd->unit, + dev, + midi_cmdname(cmd, cmdtab_seqccmn), + chn, + p1, + w14)); + + switch (cmd) { + case MIDI_PGM_CHANGE: + if (scp->seq_mode == SND_DEV_MUSIC) { + mtx_lock(&md->synth.vc_mtx); + md->synth.chn_info[chn].pgm_num = p1; + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&sd->flagqueue_mtx); + if (md->midiunit >= 0) { + if (md->synth.setinstr(md, chn, p1) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + } + mtx_lock(&sd->flagqueue_mtx); + } else { + /* For Mode 1. */ + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.setinstr(md, chn, p1) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + } + break; + case MIDI_CTL_CHANGE: + /* mtx_lock(&md->giant); */ + if (scp->seq_mode == SND_DEV_MUSIC) { + if (chn < 16 && p1 < 128) { + mtx_lock(&md->synth.vc_mtx); + md->synth.chn_info[chn].controllers[p1] = w14 & 0x7f; + if (p1 < 32) + /* We have set the MSB, clear the LSB. */ + md->synth.chn_info[chn].controllers[p1 + 32] = 0; + if (md->midiunit >= 0) { + val = w14 & 0x7f; + if (p1 < 64) { + /* Combine the MSB and the LSB. */ + val = ((md->synth.chn_info[chn].controllers[p1 & ~32] & 0x7f) << 7) + | (md->synth.chn_info[chn].controllers[p1 | 32] & 0x7f); + p1 &= ~32; + } + /* Handle all of the notes playing on this channel. */ + key = ((int)chn << 8); + for (i = 0 ; i < md->synth.alloc.max_voice ; i++) + if ((md->synth.alloc.map[i] & 0xff00) == key) { + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.controller(md, i, p1, val) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + mtx_lock(&md->synth.vc_mtx); + } + mtx_unlock(&md->synth.vc_mtx); + } else { + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.controller(md, chn, p1, w14) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + } + } + } else { + /* For Mode 1. */ + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.controller(md, chn, p1, w14) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + } + break; + case MIDI_PITCH_BEND: + if (scp->seq_mode == SND_DEV_MUSIC) { + mtx_lock(&md->synth.vc_mtx); + md->synth.chn_info[chn].bender_value = w14; + if (md->midiunit >= 0) { + /* Handle all of the notes playing on this channel. */ + key = ((int)chn << 8); + for (i = 0 ; i < md->synth.alloc.max_voice ; i++) + if ((md->synth.alloc.map[i] & 0xff00) == key) { + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.bender(md, i, w14) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + } + } else { + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.bender(md, chn, w14) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + } + } else { + /* For Mode 1. */ + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.bender(md, chn, w14) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + } + break; + } + + return (MORE); +} + +static int +seq_timing(sc_p scp, u_char *event) +{ + int unit, ret; + long parm; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + parm = *(long *)&event[4]; + + if (scp->seq_mode == SND_DEV_MUSIC) { + ret = scp->timer->event(scp->timer, event); + if (ret == TIMERARMED) + sd->flags |= SEQ_F_WRITING; + return (ret); + } + + SEQ_DEBUG(printf("seq_timing: unit %d, cmd %s, parm %lu.\n", + unit, midi_cmdname(event[1], cmdtab_timer), parm)); + + ret = MORE; + switch (event[1]) { + case TMR_WAIT_REL: + parm += scp->prev_event_time; + /* FALLTHRU */ + case TMR_WAIT_ABS: + if (parm > 0) { + sd->flags |= SEQ_F_WRITING; + if (seq_requesttimer(scp, parm)) + ret = TIMERARMED; + } + break; + case TMR_START: + scp->seq_time = seq_gettime(); + scp->prev_input_time = 0; + scp->prev_event_time = 0; + scp->prev_wakeup_time = scp->seq_time; + break; + case TMR_STOP: + break; + case TMR_CONTINUE: + break; + case TMR_TEMPO: + break; + case TMR_ECHO: + if (scp->seq_mode == SND_DEV_MUSIC) + seq_copytoinput(scp, event, 8); + else { + parm = (parm << 8 | SEQ_ECHO); + seq_copytoinput(scp, (u_char *)&parm, 4); + } + break; + } + + SEQ_DEBUG(printf("seq_timing: timer %s.\n", + ret == TIMERARMED ? "armed" : "not armed")); + + return (ret); +} + +static int +seq_local(sc_p scp, u_char *event) +{ + int unit; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + switch (event[1]) { + case LOCL_STARTAUDIO: +#if notyet + DMAbuf_start_devices(*(u_int *)&event[4]); +#endif /* notyet */ + break; + } + + return (MORE); +} + +static int +seq_sysex(sc_p scp, u_char *event) +{ + int unit, i, l; + seqdev_info *sd; + mididev_info *md; + + sd = scp->devinfo; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + if (lookup_mididev(scp, event[1], LOOKUP_OPEN, &md) != 0) + return (MORE); + + l = 0; + for (i = 0 ; i < 6 && event[i + 2] != 0xff ; i++) + l = i + 1; + if (l > 0) { + mtx_unlock(&sd->flagqueue_mtx); + if (md->synth.sendsysex(md, &event[2], l) == EAGAIN) { + mtx_lock(&sd->flagqueue_mtx); + return (QUEUEFULL); + } + mtx_lock(&sd->flagqueue_mtx); + } + + return (MORE); +} + +void +seq_timer(void *arg) +{ + sc_p scp; + seqdev_info *sd; + + scp = arg; + sd = scp->devinfo; + + SEQ_DEBUG(printf("seq_timer: unit %d, timer fired.\n", sd->unit)); + + /* Record the current timestamp. */ + mtx_lock(&sd->flagqueue_mtx); + + scp->timer_running = 0; + scp->prev_wakeup_time = seq_gettime(); + seq_startplay(scp); + + mtx_unlock(&sd->flagqueue_mtx); +} + +static int +seq_openmidi(sc_p scp, mididev_info *md, int flags, int mode, struct thread *td) +{ + int midiunit, err, insync, chn; + + mtx_assert(&scp->devinfo->flagqueue_mtx, MA_OWNED); + + midiunit = md->unit; + + SEQ_DEBUG(printf("seq_openmidi: opening midi unit %d.\n", midiunit)); + + err = midi_open(MIDIMKDEV(MIDI_CDEV_MAJOR, midiunit, SND_DEV_MIDIN), flags, mode, td); + if (err != 0) { + printf("seq_openmidi: failed to open midi device %d.\n", midiunit); + return (err); + } + mtx_lock(&md->synth.status_mtx); + mtx_lock(&md->flagqueue_mtx); + md->intr = seq_intr; + md->intrarg = scp->devinfo; + mtx_unlock(&md->flagqueue_mtx); + md->synth.sysex_state = 0; + if (scp->seq_mode == SND_DEV_MUSIC) { + for (chn = 0 ; chn < 16 ; chn++) { + md->synth.chn_info[chn].pgm_num = 0; + md->synth.reset(md); + md->synth.chn_info[chn].bender_value = (1 << 7); + } + } + mtx_unlock(&md->synth.status_mtx); + + insync = 0; + if ((scp->devinfo->flags & SEQ_F_INSYNC) != 0) { + insync = 1; + cv_wait(&scp->devinfo->insync_cv, &scp->devinfo->flagqueue_mtx); + } + + TAILQ_INSERT_TAIL(&scp->midi_open, md, md_linkseq); + + if (insync) + cv_broadcast(&scp->devinfo->insync_cv); + + return (0); +} + +static int +seq_closemidi(sc_p scp, mididev_info *md, int flags, int mode, struct thread *td) +{ + int midiunit, insync; + + mtx_assert(&scp->devinfo->flagqueue_mtx, MA_OWNED); + + if (md == NULL || !MIDICONFED(md)) { + SEQ_DEBUG(printf("seq_closemidi: midi device does not exist.\n")); + return (ENXIO); + } + midiunit = md->unit; + + SEQ_DEBUG(printf("seq_closemidi: closing midi unit %d.\n", midiunit)); + + midi_close(MIDIMKDEV(MIDI_CDEV_MAJOR, midiunit, SND_DEV_MIDIN), flags, mode, td); + mtx_lock(&md->flagqueue_mtx); + md->intr = NULL; + md->intrarg = NULL; + mtx_unlock(&md->flagqueue_mtx); + + insync = 0; + if ((scp->devinfo->flags & SEQ_F_INSYNC) != 0) { + insync = 1; + cv_wait(&scp->devinfo->insync_cv, &scp->devinfo->flagqueue_mtx); + } + + TAILQ_REMOVE(&scp->midi_open, md, md_linkseq); + + if (insync) + cv_broadcast(&scp->devinfo->insync_cv); + + return (0); +} + +static void +seq_panic(sc_p scp) +{ + mtx_assert(&scp->devinfo->flagqueue_mtx, MA_OWNED); + + seq_reset(scp); +} + +static int +seq_reset(sc_p scp) +{ + int unit, chn, lenw, ret; + seqdev_info *sd; + mididev_info *md; + u_char c[3]; + + sd = scp->devinfo; + unit = sd->unit; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + SEQ_DEBUG(printf("seq_reset: unit %d.\n", unit)); + + if ((sd->flags & SEQ_F_INSYNC) != 0) + cv_wait(&sd->insync_cv, &sd->flagqueue_mtx); + + /* Stop reading and writing. */ + sd->callback(sd, SEQ_CB_ABORT | SEQ_CB_RD | SEQ_CB_WR); + + /* Clear the queues. */ + midibuf_clear(&sd->midi_dbuf_in); + midibuf_clear(&sd->midi_dbuf_out); + + /* Reset the synthesizers. */ + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) + md->synth.reset(md); + + if (scp->seq_mode == SND_DEV_MUSIC) { + for (chn = 0 ; chn < 16 ; chn++) { + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) { + mtx_unlock(&sd->flagqueue_mtx); + ret = 0; + if (md->synth.controller(md, chn, 123, 0) == EAGAIN /* All notes off. */ + || md->synth.controller(md, chn, 121, 0) == EAGAIN /* Reset all controllers. */ + || md->synth.bender(md, chn, 1 << 13) == EAGAIN) /* Reset pitch bend. */ + ret = EAGAIN; + mtx_lock(&sd->flagqueue_mtx); + return (ret); + } + } + } else { + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) { + for (chn = 0 ; chn < 16 ; chn++) { + mtx_unlock(&sd->flagqueue_mtx); + c[0] = 0xb0 | (chn & 0x0f); + c[1] = (u_char)0x78; /* All sound off */ + c[2] = (u_char)0; + md->synth.writeraw(md, c, 3, &lenw, 0); + c[1] = (u_char)0x7b; /* All note off */ + md->synth.writeraw(md, c, 3, &lenw, 0); + c[1] = (u_char)0x79; /* Reset all controller */ + md->synth.writeraw(md, c, 3, &lenw, 0); + mtx_lock(&sd->flagqueue_mtx); + } + } + seq_sync(scp); + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) + lookup_mididev(scp, md->unit, LOOKUP_CLOSE, NULL); + } + + return (0); +} + +#define SEQ_SYNC_TIMEOUT 8 +static int +seq_sync(sc_p scp) +{ + int i, rl; + seqdev_info *sd; + mididev_info *md; + + sd = scp->devinfo; + + mtx_assert(&sd->flagqueue_mtx, MA_OWNED); + + SEQ_DEBUG(printf("seq_sync: unit %d.\n", sd->unit)); + sd->flags |= SEQ_F_INSYNC; + + while (sd->midi_dbuf_out.rl >= EV_SZ) { + if ((sd->flags & SEQ_F_WRITING) == 0) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + rl = sd->midi_dbuf_out.rl; + i = cv_timedwait_sig(&sd->midi_dbuf_out.cv_out, &sd->flagqueue_mtx, SEQ_SYNC_TIMEOUT * hz); + if (i == EINTR || i == ERESTART) { + if (i == EINTR) + sd->callback(sd, SEQ_CB_STOP | SEQ_CB_WR); + sd->flags &= ~SEQ_F_INSYNC; + return (i); + } + if (i == EWOULDBLOCK && rl == sd->midi_dbuf_out.rl && !scp->timer_running) { + /* A queue seems to be stuck up. Give up and clear queues. */ + sd->callback(sd, SEQ_CB_STOP | SEQ_CB_WR); + midibuf_clear(&sd->midi_dbuf_out); + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) { + mtx_lock(&md->flagqueue_mtx); + md->callback(md, MIDI_CB_ABORT | MIDI_CB_WR); + midibuf_clear(&md->midi_dbuf_out); + mtx_unlock(&md->flagqueue_mtx); + } + break; + } + } + + /* + * Since syncing a midi device might block, unlock sd->flagqueue_mtx. + * Keep sd->midi_dbuf_out from writing by setting SEQ_F_INSYNC. + * sd->insync_cv is signalled when sync is finished. + */ + mtx_unlock(&sd->flagqueue_mtx); + + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) { + mtx_lock(&md->flagqueue_mtx); + midi_sync(md); + mtx_unlock(&md->flagqueue_mtx); + } + + mtx_lock(&sd->flagqueue_mtx); + sd->flags &= ~SEQ_F_INSYNC; + cv_broadcast(&sd->insync_cv); + + return (0); +} + +/* + * a small utility function which, given a device number, returns + * a pointer to the associated seqdev_info struct, and sets the unit + * number. + */ +static seqdev_info * +get_seqdev_info(dev_t i_dev, int *unit) +{ + int u; + + if (MIDIDEV(i_dev) != SND_DEV_SEQ && MIDIDEV(i_dev) != SND_DEV_MUSIC) + return NULL; + u = MIDIUNIT(i_dev); + if (unit) + *unit = u ; + + return get_seqdev_info_unit(u); +} + +/* + * a small utility function which, given a unit number, returns + * a pointer to the associated mididev_info struct. + */ +seqdev_info * +get_seqdev_info_unit(int unit) +{ + seqdev_info *sd; + + mtx_lock(&seqinfo_mtx); + TAILQ_FOREACH(sd, &seq_info, sd_link) { + if (sd->unit == unit) + break; + } + mtx_unlock(&seqinfo_mtx); + + return sd; +} + +/* Create a new sequencer device info structure. */ +seqdev_info * +create_seqdev_info_unit(int unit, seqdev_info *seq) +{ + seqdev_info *sd, *sdnew; + + /* As malloc(9) might block, allocate seqdev_info now. */ + sdnew = malloc(sizeof(seqdev_info), M_DEVBUF, M_WAITOK | M_ZERO); + if (sdnew == NULL) + return NULL; + bcopy(seq, sdnew, sizeof(seqdev_info)); + sdnew->unit = unit; + midibuf_init(&sdnew->midi_dbuf_in); + midibuf_init(&sdnew->midi_dbuf_out); + mtx_init(&sdnew->flagqueue_mtx, "seqflq", NULL, MTX_DEF); + cv_init(&sdnew->insync_cv, "seqins"); + + mtx_lock(&seqinfo_mtx); + + TAILQ_FOREACH(sd, &seq_info, sd_link) { + if (sd->unit == unit) { + mtx_unlock(&seqinfo_mtx); + midibuf_destroy(&sdnew->midi_dbuf_in); + midibuf_destroy(&sdnew->midi_dbuf_out); + mtx_destroy(&sdnew->flagqueue_mtx); + cv_destroy(&sdnew->insync_cv); + free(sdnew, M_DEVBUF); + return sd; + } + } + + mtx_lock(&sdnew->flagqueue_mtx); + TAILQ_INSERT_TAIL(&seq_info, sdnew, sd_link); + nseq++; + + mtx_unlock(&seqinfo_mtx); + + return sdnew; +} + +/* + * Look up a midi device by its unit number opened by this sequencer. + * If the device is not opened and mode is LOOKUP_OPEN, open the device. + */ +static int +lookup_mididev(sc_p scp, int unit, int mode, mididev_info **mdp) +{ + int ret; + mididev_info *md; + + if (mdp == NULL) + mdp = &md; + + *mdp = NULL; + + mtx_assert(&scp->devinfo->flagqueue_mtx, MA_OWNED); + + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) { + if (scp->seq_mode == SND_DEV_MUSIC ? md->unit == unit : md->synthunit == unit) { + *mdp = md; + if (mode == LOOKUP_CLOSE) + return seq_closemidi(scp, md, scp->fflags, MIDIDEV_MODE, curthread); + + return (md != NULL && MIDICONFED(md)) ? 0 : ENXIO; + } + } + + if (mode == LOOKUP_OPEN) { + if (scp->seq_mode == SND_DEV_MUSIC) + md = get_mididev_info_unit(unit); + else + md = get_mididev_synth_unit(unit); + if (md != NULL) { + *mdp = md; + ret = seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curthread); + return ret; + } + } + + return ENXIO; +} + +/* + * Look up a midi device by its midi unit number opened by this sequencer. + * If the device is not opened and mode is LOOKUP_OPEN, open the device. + */ +static int +lookup_mididev_midi(sc_p scp, int unit, int mode, mididev_info **mdp) +{ + int ret; + mididev_info *md; + + if (mdp == NULL) + mdp = &md; + + *mdp = NULL; + + if (scp->seq_mode == SND_DEV_MUSIC) + return (ENXIO); + + mtx_assert(&scp->devinfo->flagqueue_mtx, MA_OWNED); + + TAILQ_FOREACH(md, &scp->midi_open, md_linkseq) { + if (md->midiunit == unit) { + *mdp = md; + if (mode == LOOKUP_CLOSE) + return seq_closemidi(scp, md, scp->fflags, MIDIDEV_MODE, curthread); + + return (md != NULL && MIDICONFED(md)) ? 0 : ENXIO; + } + } + + if (mode == LOOKUP_OPEN) { + md = get_mididev_midi_unit(unit); + if (md != NULL) { + *mdp = md; + ret = seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curthread); + return ret; + } + } + + return ENXIO; +} + +/* XXX These functions are actually redundant. */ +static int +seqopen(dev_t i_dev, int flags, int mode, struct thread *td) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + case MIDI_DEV_MUSIC: + return seq_open(i_dev, flags, mode, td); + } + + return (ENXIO); +} + +static int +seqclose(dev_t i_dev, int flags, int mode, struct thread *td) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + case MIDI_DEV_MUSIC: + return seq_close(i_dev, flags, mode, td); + } + + return (ENXIO); +} + +static int +seqread(dev_t i_dev, struct uio * buf, int flag) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + case MIDI_DEV_MUSIC: + return seq_read(i_dev, buf, flag); + } + + return (ENXIO); +} + +static int +seqwrite(dev_t i_dev, struct uio * buf, int flag) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + case MIDI_DEV_MUSIC: + return seq_write(i_dev, buf, flag); + } + + return (ENXIO); +} + +static int +seqioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + case MIDI_DEV_MUSIC: + return seq_ioctl(i_dev, cmd, arg, mode, td); + } + + return (ENXIO); +} + +static int +seqpoll(dev_t i_dev, int events, struct thread *td) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + case MIDI_DEV_MUSIC: + return seq_poll(i_dev, events, td); + } + + return (ENXIO); +} + +static int +seq_modevent(module_t mod, int type, void *data) +{ + int retval; + + retval = 0; + + switch (type) { + case MOD_LOAD: + seq_init(); + break; + + case MOD_UNLOAD: + printf("sequencer: unload not supported yet.\n"); + retval = EOPNOTSUPP; + break; + + default: + break; + } + + return retval; +} + +DEV_MODULE(seq, seq_modevent, NULL); + +static void +seq_clone(arg, name, namelen, dev) + void *arg; + char *name; + int namelen; + dev_t *dev; +{ + int u; + + if (*dev != NODEV) + return; + if (bcmp(name, "sequencer", 9) != 0) + return; + if (name[10] != '\0' && name[11] != '\0') + return; + u = name[9] - '0'; + if (name[10] != '\0') { + u *= 10; + u += name[10] - '0'; + } + seq_initunit(u); + *dev = MIDIMKDEV(SEQ_CDEV_MAJOR, u, MIDI_DEV_SEQ); + return; +} diff --git a/sys/dev/sound/midi/sequencer.h b/sys/dev/sound/midi/sequencer.h new file mode 100644 index 0000000..c5db264 --- /dev/null +++ b/sys/dev/sound/midi/sequencer.h @@ -0,0 +1,272 @@ +/* + * Include file for midi sequencer driver. + * + * Copyright by Seigo Tanimura 1999. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +/* + * first, include kernel header files. + */ + +#ifndef _SEQUENCER_H_ +#define _SEQUENCER_H_ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/ioccom.h> + +#include <sys/filio.h> +#include <sys/sockio.h> +#include <sys/fcntl.h> +#include <sys/tty.h> +#include <sys/proc.h> + +#include <sys/kernel.h> /* for DATA_SET */ + +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/uio.h> +#include <sys/syslog.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <sys/condvar.h> +#include <machine/clock.h> /* for DELAY */ +#include <sys/soundcard.h> + +#include <dev/sound/midi/timer.h> + +#define SEQ_CDEV_MAJOR MIDI_CDEV_MAJOR + +/* + * the following assumes that FreeBSD 3.X uses poll(2) instead of select(2). + * This change dates to late 1997. + */ +#include <sys/poll.h> +#define d_select_t d_poll_t + +/* Return value from seq_playevent and timer event handers. */ +enum { + MORE, + TIMERARMED, + QUEUEFULL +}; + +typedef struct _seqdev_info seqdev_info; + +/* + * The order of mutex lock (from the first to the last) + * + * 1. sequencer flags, queues, timer and device list + * 2. midi synth voice and channel + * 3. midi synth status + * 4. generic midi flags and queues + * 5. midi device + */ + +/* + * descriptor of sequencer operations ... + * + */ + +struct _seqdev_info { + + /* + * the first part of the descriptor is filled up from a + * template. + */ + char name[64]; + + int type ; + + d_open_t *open; + d_close_t *close; + d_read_t *read; + d_write_t *write; + d_ioctl_t *ioctl; + d_poll_t *poll; + midi_callback_t *callback; + + /* + * combinations of the following flags are used as second argument in + * the callback from the dma module to the device-specific routines. + */ + +#define SEQ_CB_RD 0x100 /* read callback */ +#define SEQ_CB_WR 0x200 /* write callback */ +#define SEQ_CB_REASON_MASK 0xff +#define SEQ_CB_START 0x01 /* start dma op */ +#define SEQ_CB_STOP 0x03 /* stop dma op */ +#define SEQ_CB_ABORT 0x04 /* abort dma op */ +#define SEQ_CB_INIT 0x05 /* init board parameters */ + + /* + * callback extensions + */ +#define SEQ_CB_DMADONE 0x10 +#define SEQ_CB_DMAUPDATE 0x11 +#define SEQ_CB_DMASTOP 0x12 + + /* init can only be called with int enabled and + * no pending DMA activity. + */ + + /* + * whereas from here, parameters are set at runtime. + * io_base == 0 means that the board is not configured. + */ + + int unit; /* unit number of the device */ + void *softc; /* softc for a device */ + + int bd_id ; /* used to hold board-id info, eg. sb version, + * mss codec type, etc. etc. + */ + + struct mtx flagqueue_mtx; /* Mutex to protect flags and queues */ + struct cv insync_cv; /* Conditional variable for sync */ + + /* Queues */ + midi_dbuf midi_dbuf_in; /* midi input event/message queue */ + midi_dbuf midi_dbuf_out; /* midi output event/message queue */ + + + /* + * these parameters describe the operation of the board. + * Generic things like busy flag, speed, etc are here. + */ + + /* Flags */ + volatile u_long flags ; /* 32 bits, used for various purposes. */ + + /* + * we have separate flags for read and write, although in some + * cases this is probably not necessary (e.g. because we cannot + * know how many processes are using the device, we cannot + * distinguish if open, close, abort are for a write or for a + * read). + */ + + /* + * the following flag is used by open-close routines + * to mark the status of the device. + */ +#define SEQ_F_BUSY 0x0001 /* has been opened */ + /* + * the next two are used to allow only one pending operation of + * each type. + */ +#define SEQ_F_READING 0x0004 /* have a pending read */ +#define SEQ_F_WRITING 0x0008 /* have a pending write */ + + /* + * flag used to mark a pending close. + */ +#define SEQ_F_CLOSING 0x0040 /* a pending close */ + + /* + * if user has not set block size, then make it adaptive + * (0.25s, or the perhaps last read/write ?) + */ +#define SEQ_F_HAS_SIZE 0x0080 /* user set block size */ + /* + * assorted flags related to operating mode. + */ +#define SEQ_F_STEREO 0x0100 /* doing stereo */ +#define SEQ_F_NBIO 0x0200 /* do non-blocking i/o */ + + /* + * these flags mark a pending abort on a r/w operation. + */ +#define SEQ_F_ABORTING 0x1000 /* a pending abort */ + + /* + * this is used to mark that board initialization is needed, e.g. + * because of a change in sampling rate, format, etc. -- It will + * be done at the next convenient time. + */ +#define SEQ_F_INIT 0x4000 /* changed parameters. need init */ + +#define SEQ_F_INSYNC 0x8000 /* a pending sync */ + + int play_blocksize, rec_blocksize; /* blocksize for io and dma ops */ + +#define swsel midi_dbuf_out.sel +#define srsel midi_dbuf_in.sel + u_long interrupts; /* counter of interrupts */ + u_long magic; +#define MAGIC(unit) ( 0xa4d10de0 + unit ) + void *device_data ; /* just in case it is needed...*/ + + /* The tailq entry of the next sequencer device. */ + TAILQ_ENTRY(_seqdev_info) sd_link; +}; + + +/* + * then ioctls and other stuff + */ +#define NSEQ_MAX 16 /* Number of supported devices */ + +/* + * many variables should be reduced to a range. Here define a macro + */ + +#define RANGE(var, low, high) (var) = \ +((var)<(low)?(low) : (var)>(high)?(high) : (var)) + +/* + * finally, all default parameters + */ +#define SEQ_BUFFSIZE (1024) /* XXX */ + +#define MIDI_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + synthesizer and MIDI output) */ +#define MIDI_DEV_MUSIC 8 /* Sequencer output /dev/music (FM + synthesizer and MIDI output) */ + +#ifdef _KERNEL + +extern midi_cmdtab cmdtab_seqioctl[]; +extern midi_cmdtab cmdtab_timer[]; + +void seq_timer(void *arg); +int seq_copytoinput(void *arg, u_char *event, int len); + +SYSCTL_DECL(_hw_midi_seq); + +extern int seq_debug; +#define SEQ_DEBUG(x) \ + do { \ + if (seq_debug) { \ + (x); \ + } \ + } while(0) + +#endif /* _KERNEL */ + + +#endif /* _SEQUENCER_H_ */ diff --git a/sys/dev/sound/midi/timer.c b/sys/dev/sound/midi/timer.c new file mode 100644 index 0000000..ba4bce0 --- /dev/null +++ b/sys/dev/sound/midi/timer.c @@ -0,0 +1,564 @@ +/* + * This is the timer engine of /dev/music for FreeBSD. + * + * (C) 2002 Seigo Tanimura + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#include <dev/sound/midi/midi.h> +#include <dev/sound/midi/sequencer.h> + +#define TMR2TICKS(scp, tmr_val) \ + ((((tmr_val) * (scp)->tempo * (scp)->timebase) + (30 * hz)) / (60 * hz)) +#define CURTICKS(scp) \ + ((scp)->ticks_offset + (scp)->ticks_cur - (scp)->ticks_base) + +struct systmr_timer_softc { + int running; + + u_long ticks_offset; + u_long ticks_base; + u_long ticks_cur; + + int tempo; + int timebase; + + u_long nexteventtime; + u_long preveventtime; + + struct callout timer; +}; + +static timeout_t systmr_timer; +static void systmr_reset(timerdev_info *tmd); +static u_long systmr_time(void); + +static tmr_open_t systmr_open; +static tmr_close_t systmr_close; +static tmr_event_t systmr_event; +static tmr_gettime_t systmr_gettime; +static tmr_ioctl_t systmr_ioctl; +static tmr_armtimer_t systmr_armtimer; + +static timerdev_info systmr_timerdev = { + "System clock", + 0, + 0, + systmr_open, + systmr_close, + systmr_event, + systmr_gettime, + systmr_ioctl, + systmr_armtimer, +}; + +static TAILQ_HEAD(,_timerdev_info) timer_info; +static struct mtx timerinfo_mtx; +static int timerinfo_mtx_init; +static int ntimer; + + +/* Install a system timer. */ +int +timerdev_install(void) +{ + int ret; + timerdev_info *tmd; + struct systmr_timer_softc *scp; + + SEQ_DEBUG(printf("timerdev_install: install a new timer.\n")); + + ret = 0; + tmd = NULL; + scp = NULL; + + scp = malloc(sizeof(*scp), M_DEVBUF, M_WAITOK | M_ZERO); + if (scp == NULL) { + ret = ENOMEM; + goto fail; + } + + tmd = create_timerdev_info_unit(&systmr_timerdev); + if (tmd == NULL) { + ret = ENOMEM; + goto fail; + } + + tmd->softc = scp; + callout_init(&scp->timer, 0); + + mtx_unlock(&tmd->mtx); + + SEQ_DEBUG(printf("timerdev_install: installed a new timer, unit %d.\n", tmd->unit)); + + return (0); + +fail: + if (scp != NULL) + free(scp, M_DEVBUF); + if (tmd != NULL) { + TAILQ_REMOVE(&timer_info, tmd, tmd_link); + free(tmd, M_DEVBUF); + } + + SEQ_DEBUG(printf("timerdev_install: installation failed.\n")); + + return (ret); +} + +/* Create a new timer device info structure. */ +timerdev_info * +create_timerdev_info_unit(timerdev_info *tmdinf) +{ + int unit; + timerdev_info *tmd, *tmdnew; + + /* XXX */ + if (!timerinfo_mtx_init) { + timerinfo_mtx_init = 1; + mtx_init(&timerinfo_mtx, "tmrinf", NULL, MTX_DEF); + TAILQ_INIT(&timer_info); + } + + /* As malloc(9) might block, allocate timerdev_info now. */ + tmdnew = malloc(sizeof(timerdev_info), M_DEVBUF, M_WAITOK | M_ZERO); + if (tmdnew == NULL) + return NULL; + bcopy(tmdinf, tmdnew, sizeof(timerdev_info)); + mtx_init(&tmdnew->mtx, "tmrmtx", NULL, MTX_DEF); + + mtx_lock(&timerinfo_mtx); + + ntimer++; + + for (unit = 0 ; ; unit++) { + TAILQ_FOREACH(tmd, &timer_info, tmd_link) { + if (tmd->unit == unit) + break; + } + if (tmd == NULL) + break; + } + + tmdnew->unit = unit; + mtx_lock(&tmdnew->mtx); + tmd = TAILQ_FIRST(&timer_info); + while (tmd != NULL) { + if (tmd->prio < tmdnew->prio) + break; + tmd = TAILQ_NEXT(tmd, tmd_link); + } + if (tmd != NULL) + TAILQ_INSERT_BEFORE(tmd, tmdnew, tmd_link); + else + TAILQ_INSERT_TAIL(&timer_info, tmdnew, tmd_link); + + mtx_unlock(&timerinfo_mtx); + + return (tmdnew); +} + +/* + * a small utility function which, given a unit number, returns + * a pointer to the associated timerdev_info struct. + */ +timerdev_info * +get_timerdev_info_unit(int unit) +{ + timerdev_info *tmd; + + /* XXX */ + if (!timerinfo_mtx_init) { + timerinfo_mtx_init = 1; + mtx_init(&timerinfo_mtx, "tmrinf", NULL, MTX_DEF); + TAILQ_INIT(&timer_info); + } + + mtx_lock(&timerinfo_mtx); + TAILQ_FOREACH(tmd, &timer_info, tmd_link) { + mtx_lock(&tmd->mtx); + if (tmd->unit == unit && tmd->seq == NULL) + break; + mtx_unlock(&tmd->mtx); + } + mtx_unlock(&timerinfo_mtx); + + return tmd; +} + +/* + * a small utility function which returns a pointer + * to the best preferred timerdev_info struct with + * no sequencer. + */ +timerdev_info * +get_timerdev_info(void) +{ + timerdev_info *tmd; + + /* XXX */ + if (!timerinfo_mtx_init) { + timerinfo_mtx_init = 1; + mtx_init(&timerinfo_mtx, "tmrinf", NULL, MTX_DEF); + TAILQ_INIT(&timer_info); + } + + mtx_lock(&timerinfo_mtx); + TAILQ_FOREACH(tmd, &timer_info, tmd_link) { + mtx_lock(&tmd->mtx); + if (tmd->seq == NULL) + break; + mtx_unlock(&tmd->mtx); + } + mtx_unlock(&timerinfo_mtx); + + return tmd; +} + + +/* ARGSUSED */ +static void +systmr_timer(void *d) +{ + timerdev_info *tmd; + struct systmr_timer_softc *scp; + void *seq; + + tmd = (timerdev_info *)d; + scp = (struct systmr_timer_softc *)tmd->softc; + seq = NULL; + + mtx_lock(&tmd->mtx); + + if (tmd->opened) { + callout_reset(&scp->timer, 1, systmr_timer, tmd); + + if (scp->running) { + scp->ticks_cur = TMR2TICKS(scp, systmr_time()); + + if (CURTICKS(scp) >= scp->nexteventtime) { + SEQ_DEBUG(printf("systmr_timer: CURTICKS %lu, call the sequencer.\n", CURTICKS(scp))); + scp->nexteventtime = ULONG_MAX; + seq = tmd->seq; + } + } + } + + mtx_unlock(&tmd->mtx); + + if (seq != NULL) + seq_timer(seq); +} + +static void +systmr_reset(timerdev_info *tmd) +{ + struct systmr_timer_softc *scp; + + scp = (struct systmr_timer_softc *)tmd->softc; + + mtx_assert(&tmd->mtx, MA_OWNED); + + SEQ_DEBUG(printf("systmr_reset: unit %d.\n", tmd->unit)); + + scp->ticks_offset = 0; + scp->ticks_base = scp->ticks_cur = TMR2TICKS(scp, systmr_time()); + + scp->nexteventtime = ULONG_MAX; + scp->preveventtime = 0; +} + +static u_long +systmr_time(void) +{ + struct timeval timecopy; + + getmicrotime(&timecopy); + return timecopy.tv_usec / (1000000 / hz) + (u_long) timecopy.tv_sec * hz; +} + + +/* ARGSUSED */ +static int +systmr_open(timerdev_info *tmd, int oflags, int devtype, struct thread *td) +{ + struct systmr_timer_softc *scp; + + scp = (struct systmr_timer_softc *)tmd->softc; + + SEQ_DEBUG(printf("systmr_open: unit %d.\n", tmd->unit)); + + mtx_lock(&tmd->mtx); + + if (tmd->opened) { + mtx_unlock(&tmd->mtx); + return (EBUSY); + } + + systmr_reset(tmd); + scp->tempo = 60; + scp->timebase = hz; + tmd->opened = 1; + + callout_reset(&scp->timer, 1, systmr_timer, tmd); + + mtx_unlock(&tmd->mtx); + + return (0); +} + +static int +systmr_close(timerdev_info *tmd, int fflag, int devtype, struct thread *td) +{ + struct systmr_timer_softc *scp; + + scp = (struct systmr_timer_softc *)tmd->softc; + + SEQ_DEBUG(printf("systmr_close: unit %d.\n", tmd->unit)); + + mtx_lock(&tmd->mtx); + + tmd->opened = 0; + scp->running = 0; + + callout_stop(&scp->timer); + + mtx_unlock(&tmd->mtx); + + return (0); +} + +static int +systmr_event(timerdev_info *tmd, u_char *ev) +{ + struct systmr_timer_softc *scp; + u_char cmd; + u_long parm, t; + int ret; + void * seq; + + scp = (struct systmr_timer_softc *)tmd->softc; + cmd = ev[1]; + parm = *(int *)&ev[4]; + ret = MORE; + + SEQ_DEBUG(printf("systmr_event: unit %d, cmd %s, parm %lu.\n", + tmd->unit, midi_cmdname(cmd, cmdtab_timer), parm)); + + mtx_lock(&tmd->mtx); + + switch (cmd) { + case TMR_WAIT_REL: + parm += scp->preveventtime; + /* FALLTHRU */ + case TMR_WAIT_ABS: + if (parm > 0) { + if (parm <= CURTICKS(scp)) + break; + t = parm; + scp->nexteventtime = scp->preveventtime = t; + ret = TIMERARMED; + break; + } + break; + + case TMR_START: + systmr_reset(tmd); + scp->running = 1; + break; + + case TMR_STOP: + scp->running = 0; + break; + + case TMR_CONTINUE: + scp->running = 1; + break; + + case TMR_TEMPO: + if (parm > 0) { + RANGE(parm, 8, 360); + scp->ticks_offset += scp->ticks_cur + - scp->ticks_base; + scp->ticks_base = scp->ticks_cur; + scp->tempo = parm; + } + break; + + case TMR_ECHO: + seq = tmd->seq; + mtx_unlock(&tmd->mtx); + seq_copytoinput(seq, ev, 8); + mtx_lock(&tmd->mtx); + break; + } + + mtx_unlock(&tmd->mtx); + + SEQ_DEBUG(printf("systmr_event: timer %s.\n", + ret == TIMERARMED ? "armed" : "not armed")); + + return (ret); +} + +static int +systmr_gettime(timerdev_info *tmd, u_long *t) +{ + struct systmr_timer_softc *scp; + int ret; + + scp = (struct systmr_timer_softc *)tmd->softc; + + SEQ_DEBUG(printf("systmr_gettime: unit %d.\n", tmd->unit)); + + mtx_lock(&tmd->mtx); + + if (!tmd->opened || t == NULL) { + ret = EINVAL; + goto fail; + } + + *t = CURTICKS(scp); + SEQ_DEBUG(printf("systmr_gettime: ticks %lu.\n", *t)); + +fail: + mtx_unlock(&tmd->mtx); + + return (0); +} + +static int +systmr_ioctl(timerdev_info *tmd, u_long cmd, caddr_t data, int fflag, struct thread *td) +{ + struct systmr_timer_softc *scp; + int ret, val; + + scp = (struct systmr_timer_softc *)tmd->softc; + ret = 0; + + SEQ_DEBUG(printf("systmr_ioctl: unit %d, cmd %s.\n", + tmd->unit, midi_cmdname(cmd, cmdtab_seqioctl))); + + switch (cmd) { + case SNDCTL_TMR_SOURCE: + *(int *)data = TMR_INTERNAL; + break; + + case SNDCTL_TMR_START: + mtx_lock(&tmd->mtx); + systmr_reset(tmd); + scp->running = 1; + mtx_unlock(&tmd->mtx); + break; + + case SNDCTL_TMR_STOP: + mtx_lock(&tmd->mtx); + scp->running = 0; + mtx_unlock(&tmd->mtx); + break; + + case SNDCTL_TMR_CONTINUE: + mtx_lock(&tmd->mtx); + scp->running = 1; + mtx_unlock(&tmd->mtx); + break; + + case SNDCTL_TMR_TIMEBASE: + val = *(int *)data; + mtx_lock(&tmd->mtx); + if (val > 0) { + RANGE(val, 1, 1000); + scp->timebase = val; + } + *(int *)data = scp->timebase; + mtx_unlock(&tmd->mtx); + SEQ_DEBUG(printf("systmr_ioctl: timebase %d.\n", *(int *)data)); + break; + + case SNDCTL_TMR_TEMPO: + val = *(int *)data; + mtx_lock(&tmd->mtx); + if (val > 0) { + RANGE(val, 8, 360); + scp->ticks_offset += scp->ticks_cur + - scp->ticks_base; + scp->ticks_base = scp->ticks_cur; + scp->tempo = val; + } + *(int *)data = scp->tempo; + SEQ_DEBUG(printf("systmr_ioctl: tempo %d.\n", *(int *)data)); + mtx_unlock(&tmd->mtx); + break; + + case SNDCTL_SEQ_CTRLRATE: + val = *(int *)data; + if (val > 0) + ret = EINVAL; + else { + mtx_lock(&tmd->mtx); + *(int *)data = ((scp->tempo * scp->timebase) + 30) / 60; + mtx_unlock(&tmd->mtx); + SEQ_DEBUG(printf("systmr_ioctl: ctrlrate %d.\n", *(int *)data)); + } + break; + + case SNDCTL_TMR_METRONOME: + /* NOP. */ + break; + + case SNDCTL_TMR_SELECT: + /* NOP. */ + break; + + default: + ret = EINVAL; + } + + return (ret); +} + +static int +systmr_armtimer(timerdev_info *tmd, u_long t) +{ + struct systmr_timer_softc *scp; + + scp = (struct systmr_timer_softc *)tmd->softc; + + SEQ_DEBUG(printf("systmr_armtimer: unit %d, t %lu.\n", tmd->unit, t)); + + mtx_lock(&tmd->mtx); + + if (t < 0) + t = CURTICKS(scp) + 1; + else if (t > CURTICKS(scp)) + scp->nexteventtime = scp->preveventtime = t; + + mtx_unlock(&tmd->mtx); + + return (0); +} diff --git a/sys/dev/sound/midi/timer.h b/sys/dev/sound/midi/timer.h new file mode 100644 index 0000000..a615ab6 --- /dev/null +++ b/sys/dev/sound/midi/timer.h @@ -0,0 +1,80 @@ +/* + * Include file for a midi timer. + * + * Copyright by Seigo Tanimura 2002. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +typedef struct _timerdev_info timerdev_info; + +typedef int (tmr_open_t)(timerdev_info *tmd, int oflags, int devtype, struct thread *td); +typedef int (tmr_close_t)(timerdev_info *tmd, int fflag, int devtype, struct thread *td); +typedef int (tmr_event_t)(timerdev_info *tmd, u_char *ev); +typedef int (tmr_gettime_t)(timerdev_info *tmd, u_long *t); +typedef int (tmr_ioctl_t)(timerdev_info *tmd, u_long cmd, caddr_t data, int fflag, struct thread *td); +typedef int (tmr_armtimer_t)(timerdev_info *tmd, u_long t); + +struct _timerdev_info { + /* + * the first part of the descriptor is filled up from a + * template. + */ + char name[32]; + + int caps; + int prio; + + tmr_open_t *open; + tmr_close_t *close; + tmr_event_t *event; + tmr_gettime_t *gettime; + tmr_ioctl_t *ioctl; + tmr_armtimer_t *armtimer; + + + int unit; + void *softc; + void *seq; + + /* The tailq entry of the next timer device. */ + TAILQ_ENTRY(_timerdev_info) tmd_link; + + int opened; + + struct mtx mtx; +}; + +#ifdef _KERNEL +int timerdev_install(void); +timerdev_info *create_timerdev_info_unit(timerdev_info *tmdinf); +timerdev_info *get_timerdev_info_unit(int unit); +timerdev_info *get_timerdev_info(void); +#endif /* _KERNEL */ + +#endif /* _TIMER_H_ */ |