From 1beaf8b5d13febe3f72803e7370fd7d4be75b32b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 8 Sep 2026 14:46:50 +0200 Subject: [PATCH] alsa: Add OSS sequencer API emulation Implement OSS /dev/sequencer and /dev/music emulation via the ALSA sequencer API. Enumerate available MIDI ports at open time, subscribe to them, and translate OSS 4/8-byte event records to/from ALSA sequencer events. A background pthread handles incoming ALSA events and converts them to OSS format in a ring buffer; a pipe wakes poll/select waiters. SNDCTL_SEQ_SYNC uses an ECHO event echo-back to synchronize with the ALSA queue. Signed-off-by: Takashi Iwai --- alsa/Makefile.am | 2 +- alsa/alsa-oss-emul.h | 12 + alsa/alsa-oss.c | 128 +++- alsa/sequencer.c | 1460 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1581 insertions(+), 21 deletions(-) create mode 100644 alsa/sequencer.c diff --git a/alsa/Makefile.am b/alsa/Makefile.am index 8ce94f7..f88e35e 100644 --- a/alsa/Makefile.am +++ b/alsa/Makefile.am @@ -13,6 +13,6 @@ libaoss_la_LIBADD = libalsatoss.la libaoss_la_LDFLAGS = -version-info $(COMPATNUM) libalsatoss_la_CFLAGS = @ALSA_CFLAGS@ -libalsatoss_la_SOURCES = pcm.c mixer.c +libalsatoss_la_SOURCES = pcm.c mixer.c sequencer.c libalsatoss_la_LIBADD = @ALSA_LIBS@ libalsatoss_la_LDFLAGS = -version-info $(COMPATNUM) diff --git a/alsa/alsa-oss-emul.h b/alsa/alsa-oss-emul.h index 44396a4..a202732 100644 --- a/alsa/alsa-oss-emul.h +++ b/alsa/alsa-oss-emul.h @@ -62,4 +62,16 @@ extern int lib_oss_mixer_open(const char *pathname, int flags, ...); extern int lib_oss_mixer_close(int fd); extern int lib_oss_mixer_ioctl(int fd, unsigned long int request, ...); +extern int lib_oss_seq_open(const char *pathname, int flags); +extern int lib_oss_seq_close(int fd); +extern ssize_t lib_oss_seq_read(int fd, void *buf, size_t count); +extern ssize_t lib_oss_seq_write(int fd, const void *buf, size_t count); +extern int lib_oss_seq_ioctl(int fd, unsigned long int request, ...); +extern int lib_oss_seq_poll_fds(int fd); +extern int lib_oss_seq_poll_prepare(int fd, int fmode, struct pollfd *ufds); +extern int lib_oss_seq_poll_result(int fd, struct pollfd *ufds); +extern int lib_oss_seq_select_prepare(int fd, int fmode, fd_set *rfds, fd_set *wfds, fd_set *efds); +extern int lib_oss_seq_select_result(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds); +/* Note: wfds/efds are unused in the select helpers above */ + #endif /* __ALSA_OSS_EMUL_H */ diff --git a/alsa/alsa-oss.c b/alsa/alsa-oss.c index 1922755..c4a3760 100644 --- a/alsa/alsa-oss.c +++ b/alsa/alsa-oss.c @@ -94,6 +94,7 @@ typedef struct ops { typedef enum { FD_OSS_DSP, FD_OSS_MIXER, + FD_OSS_SEQ, FD_CLASSES, } fd_class_t; @@ -119,6 +120,10 @@ static inline int is_oss_device(int fd) return fd >= 0 && fd < open_max && fds[fd]; } +#define is_oss_pcm_class(fd) (fds[fd]->class == FD_OSS_DSP) +#define is_oss_seq_class(fd) (fds[fd]->class == FD_OSS_SEQ) +#define is_oss_pcm_or_seq_class(fd) (is_oss_pcm_class(fd) || is_oss_seq_class(fd)) + static int is_dsp_device(const char *pathname) { if(!pathname) return 0; @@ -139,6 +144,14 @@ static int is_mixer_device(const char *pathname) return 0; } +static int is_seq_device(const char *pathname) +{ + if(!pathname) return 0; + if(strncmp(pathname,"/dev/sequencer",14) == 0) return 1; + if(strncmp(pathname,"/dev/music",10) == 0) return 1; + return 0; +} + static int oss_pcm_fcntl(int fd, int cmd, ...) { int result; @@ -173,6 +186,27 @@ static int oss_pcm_fcntl(int fd, int cmd, ...) return -1; } +static int oss_seq_fcntl(int fd, int cmd, ...) +{ + va_list args; + long arg; + + va_start(args, cmd); + arg = va_arg(args, long); + va_end(args); + + switch (cmd) { + case F_GETFL: + return fds[fd]->oflags; + case F_SETFL: + fds[fd]->oflags = (fds[fd]->oflags & ~O_NONBLOCK) | + (arg & O_NONBLOCK); + return 0; + default: + return _fcntl(fd, cmd, arg); + } +} + static int oss_mixer_fcntl(int fd, int cmd, ...) { int result; @@ -242,6 +276,15 @@ static ops_t ops[FD_CLASSES] = { .mmap = bad_mmap, .munmap = bad_munmap, }, + [FD_OSS_SEQ] = { + .close = lib_oss_seq_close, + .write = lib_oss_seq_write, + .read = lib_oss_seq_read, + .ioctl = lib_oss_seq_ioctl, + .fcntl = oss_seq_fcntl, + .mmap = bad_mmap, + .munmap = bad_munmap, + }, }; static int dsp_open_helper(const char *file, int oflag) @@ -282,6 +325,29 @@ static int mixer_open_helper(const char *file, int oflag) fds[fd]->oflags = oflag; } return fd; +} + +static int seq_open_helper(const char *file, int oflag) +{ + int fd; + fd = lib_oss_seq_open(file, oflag); + if (fd >= 0) { + int nfds; + fds[fd] = calloc(sizeof(fd_t), 1); + if (fds[fd] == NULL) { + ops[FD_OSS_SEQ].close(fd); + errno = ENOMEM; + return -1; + } + fds[fd]->class = FD_OSS_SEQ; + fds[fd]->oflags = oflag; + nfds = lib_oss_seq_poll_fds(fd); + if (nfds > 0) { + fds[fd]->poll_fds = nfds; + poll_fds_add += nfds; + } + } + return fd; } #define DECL_OPEN(name, callback) \ @@ -301,6 +367,8 @@ int name(const char *file, int oflag, ...) \ fd = dsp_open_helper(file, oflag); \ else if (is_mixer_device(file)) \ fd = mixer_open_helper(file, oflag); \ + else if (is_seq_device(file)) \ + fd = seq_open_helper(file, oflag); \ else { \ fd = callback(file, oflag, mode); \ if (fd >= 0) \ @@ -478,7 +546,7 @@ void dump_select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, } #endif -static int poll_with_pcm(struct pollfd *pfds, unsigned long nfds, int timeout); +static int poll_with_pcm_or_seq(struct pollfd *pfds, unsigned long nfds, int timeout); int poll(struct pollfd *pfds, unsigned long nfds, int timeout) { @@ -491,14 +559,14 @@ int poll(struct pollfd *pfds, unsigned long nfds, int timeout) int fd = pfds[k].fd; if (! is_oss_device(fd)) continue; - if (fds[fd]->class == FD_OSS_DSP) - return poll_with_pcm(pfds, nfds, timeout); + if (is_oss_pcm_or_seq_class(fd)) + return poll_with_pcm_or_seq(pfds, nfds, timeout); } return _poll(pfds, nfds, timeout); } -static int poll_with_pcm(struct pollfd *pfds, unsigned long nfds, int timeout) +static int poll_with_pcm_or_seq(struct pollfd *pfds, unsigned long nfds, int timeout) { unsigned int k; unsigned int nfds1; @@ -508,7 +576,7 @@ static int poll_with_pcm(struct pollfd *pfds, unsigned long nfds, int timeout) nfds1 = 0; for (k = 0; k < nfds; ++k) { int fd = pfds[k].fd; - if (is_oss_device(fd) && fds[fd]->class == FD_OSS_DSP) { + if (is_oss_device(fd) && is_oss_pcm_or_seq_class(fd)) { unsigned short events = pfds[k].events; int fmode = 0; if ((events & (POLLIN|POLLOUT)) == (POLLIN|POLLOUT)) @@ -517,7 +585,10 @@ static int poll_with_pcm(struct pollfd *pfds, unsigned long nfds, int timeout) fmode = O_RDONLY; else fmode = O_WRONLY; - count = lib_oss_pcm_poll_prepare(fd, fmode, &pfds1[nfds1]); + if (is_oss_pcm_class(fd)) + count = lib_oss_pcm_poll_prepare(fd, fmode, &pfds1[nfds1]); + else + count = lib_oss_seq_poll_prepare(fd, fmode, &pfds1[nfds1]); if (count < 0) return -1; nfds1 += count; @@ -547,8 +618,13 @@ static int poll_with_pcm(struct pollfd *pfds, unsigned long nfds, int timeout) for (k = 0; k < nfds; ++k) { int fd = pfds[k].fd; unsigned int revents; - if (is_oss_device(fd) && fds[fd]->class == FD_OSS_DSP) { - int result = lib_oss_pcm_poll_result(fd, &pfds1[nfds1]); + if (is_oss_device(fd) && is_oss_pcm_or_seq_class(fd)) { + int result; + + if (is_oss_pcm_class(fd)) + result = lib_oss_pcm_poll_result(fd, &pfds1[nfds1]); + else + result = lib_oss_seq_poll_result(fd, &pfds1[nfds1]); revents = 0; if (result < 0) { revents |= POLLNVAL; @@ -557,7 +633,10 @@ static int poll_with_pcm(struct pollfd *pfds, unsigned long nfds, int timeout) ((result & OSS_WAIT_EVENT_READ) ? POLLIN : 0) | ((result & OSS_WAIT_EVENT_WRITE) ? POLLOUT : 0); } - nfds1 += lib_oss_pcm_poll_fds(fd); + if (is_oss_pcm_class(fd)) + nfds1 += lib_oss_pcm_poll_fds(fd); + else + nfds1 += lib_oss_seq_poll_fds(fd); } else { revents = pfds1[nfds1].revents; nfds1++; @@ -577,8 +656,8 @@ static int poll_with_pcm(struct pollfd *pfds, unsigned long nfds, int timeout) return count; } -static int select_with_pcm(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, - struct timeval *timeout); +static int select_with_pcm_or_seq(int nfds, fd_set *rfds, fd_set *wfds, + fd_set *efds, struct timeval *timeout); int select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *timeout) @@ -594,15 +673,15 @@ int select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, int e = (efds && FD_ISSET(fd, efds)); if (!(r || w || e)) continue; - if (is_oss_device(fd) && fds[fd]->class == FD_OSS_DSP) - return select_with_pcm(nfds, rfds, wfds, efds, timeout); + if (is_oss_device(fd) && is_oss_pcm_or_seq_class(fd)) + return select_with_pcm_or_seq(nfds, rfds, wfds, efds, timeout); } return _select(nfds, rfds, wfds, efds, timeout); } -static int select_with_pcm(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, - struct timeval *timeout) +static int select_with_pcm_or_seq(int nfds, fd_set *rfds, fd_set *wfds, + fd_set *efds, struct timeval *timeout) { fd_set _rfds1, _wfds1, _efds1; fd_set *rfds1, *wfds1, *efds1; @@ -632,7 +711,7 @@ static int select_with_pcm(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, int e = (efds && FD_ISSET(fd, efds)); if (!(r || w || e)) continue; - if (is_oss_device(fd) && fds[fd]->class == FD_OSS_DSP) { + if (is_oss_device(fd) && is_oss_pcm_or_seq_class(fd)) { int res, fmode = 0; if (r & w) @@ -641,8 +720,12 @@ static int select_with_pcm(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, fmode = O_RDONLY; else fmode = O_WRONLY; - res = lib_oss_pcm_select_prepare(fd, fmode, rfds1, wfds1, - e ? efds1 : NULL); + if (is_oss_pcm_class(fd)) + res = lib_oss_pcm_select_prepare(fd, fmode, rfds1, wfds1, + e ? efds1 : NULL); + else + res = lib_oss_seq_select_prepare(fd, fmode, rfds1, wfds1, + e ? efds1 : NULL); if (res < 0) return -1; if (nfds1 < res + 1) @@ -683,8 +766,13 @@ static int select_with_pcm(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, int r1, w1, e1; if (!(r || w || e)) continue; - if (is_oss_device(fd) && fds[fd]->class == FD_OSS_DSP) { - int result = lib_oss_pcm_select_result(fd, rfds1, wfds1, efds1); + if (is_oss_device(fd) && is_oss_pcm_or_seq_class(fd)) { + int result; + + if (is_oss_pcm_class(fd)) + result = lib_oss_pcm_select_result(fd, rfds1, wfds1, efds1); + else + result = lib_oss_seq_select_result(fd, rfds1, wfds1, efds1); r1 = w1 = e1 = 0; if (result < 0 && e) { if (efds) diff --git a/alsa/sequencer.c b/alsa/sequencer.c new file mode 100644 index 0000000..2ac45fb --- /dev/null +++ b/alsa/sequencer.c @@ -0,0 +1,1460 @@ +/* + * OSS -> ALSA sequencer compatibility layer + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "alsa-local.h" + +/* OSS sequencer modes */ +#define OSS_SEQ_MODE_SYNTH 0 /* /dev/sequencer */ +#define OSS_SEQ_MODE_MUSIC 1 /* /dev/music */ + +/* OSS event sizes */ +#define SHORT_EVENT_SIZE 4 +#define LONG_EVENT_SIZE 8 + +/* Read queue size (events) */ +#define READQ_SIZE 1024 + +/* SEQ_EXTENDED sub-command indices (0-7, not MIDI status bytes) */ +#define SEQ_EXT_NOTEOFF 0 +#define SEQ_EXT_NOTEON 1 +#define SEQ_EXT_PGMCHANGE 3 + +/* OSS event record union (matches kernel layout) */ +union evrec { + struct { unsigned char code, parm1, dev, parm2; } s; + struct { unsigned char code, chn, note, vel; } n; + struct { unsigned char code, dev, cmd, chn, note, parm; unsigned short dummy; } v; + struct { unsigned char code, dev, cmd, chn, p1, p2, p3, p4; } e; + struct { unsigned char code, dev, cmd, chn, p1, p2; short val; } l; + struct { unsigned char code, cmd, dummy1, dummy2; unsigned int time; } t; + struct { unsigned char code, dev; unsigned char buf[6]; } x; + unsigned int echo; + unsigned char c[8]; +}; + +#define ev_is_long(ev) ((ev)->s.code >= 128) + +/* MIDI device entry */ +typedef struct { + int client; + int port; + snd_seq_addr_t addr; + unsigned int caps; + char name[64]; + int opened; /* PERM_READ | PERM_WRITE */ + snd_midi_event_t *coder; +} oss_midi_dev_t; + +/* Timer state */ +typedef struct { + int oss_tempo; /* BPM */ + int oss_timebase; /* PPQ */ + unsigned int cur_tick; + int realtime; + int running; +} oss_seq_timer_t; + +/* Read queue (ring buffer of OSS events) */ +typedef struct { + union evrec buf[READQ_SIZE]; + int head, tail, qlen; + int pipe_rd, pipe_wr; + unsigned int input_tick; +} oss_readq_t; + +/* Main sequencer state */ +typedef struct oss_seq_s { + int fileno; /* /dev/null fd used as OSS fd */ + snd_seq_t *seq; + int client, port, queue; + snd_seq_addr_t addr; + int seq_mode; /* OSS_SEQ_MODE_SYNTH or OSS_SEQ_MODE_MUSIC */ + int file_mode; /* O_RDONLY / O_WRONLY / O_RDWR */ + int nonblock; + int max_mididev; + oss_midi_dev_t *midi_devs; + oss_seq_timer_t timer; + oss_readq_t *readq; + struct oss_seq_s *next; +} oss_seq_t; + +static oss_seq_t *seq_list = NULL; + +/* Internal helpers */ + +static oss_seq_t *find_seq(int fd) +{ + oss_seq_t *dp; + + for (dp = seq_list; dp; dp = dp->next) { + if (dp->fileno == fd) + break; + } + return dp; +} + +static void readq_put(oss_readq_t *rq, union evrec *ev, int long_ev) +{ + int len = long_ev ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE; + char c = 0; + + if (rq->qlen < READQ_SIZE) { + memcpy(&rq->buf[rq->tail], ev, len); + rq->tail = (rq->tail + 1) % READQ_SIZE; + rq->qlen++; + write(rq->pipe_wr, &c, 1); + } +} + +/* Put SEQ_WAIT timestamp if tick changed (SYNTH mode) */ +static void readq_put_timestamp_synth(oss_readq_t *rq, unsigned int tick) +{ + union evrec ev; + + if (tick == rq->input_tick) + return; + rq->input_tick = tick; + memset(&ev, 0, sizeof(ev)); + ev.echo = (tick << 8) | SEQ_WAIT; + readq_put(rq, &ev, 0); +} + +/* Put EV_TIMING/TMR_WAIT_ABS timestamp if tick changed (MUSIC mode) */ +static void readq_put_timestamp_music(oss_readq_t *rq, unsigned int tick) +{ + union evrec ev; + + if (tick == rq->input_tick) + return; + rq->input_tick = tick; + memset(&ev, 0, sizeof(ev)); + ev.t.code = EV_TIMING; + ev.t.cmd = TMR_WAIT_ABS; + ev.t.time = tick; + readq_put(rq, &ev, 1); +} + +/* ALSA queue / timer control */ + +static void apply_queue_tempo(oss_seq_t *dp) +{ + snd_seq_queue_tempo_t *tempo; + + snd_seq_queue_tempo_alloca(&tempo); + snd_seq_queue_tempo_set_tempo(tempo, 60000000 / dp->timer.oss_tempo); + snd_seq_queue_tempo_set_ppq(tempo, dp->timer.oss_timebase); + snd_seq_set_queue_tempo(dp->seq, dp->queue, tempo); +} + +static void send_queue_control(oss_seq_t *dp, int type) +{ + snd_seq_event_t ev; + + snd_seq_ev_clear(&ev); + ev.type = type; + snd_seq_ev_set_fixed(&ev); + ev.data.queue.queue = dp->queue; + snd_seq_ev_set_dest(&ev, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_TIMER); + snd_seq_ev_set_direct(&ev); + snd_seq_event_output_direct(dp->seq, &ev); +} + +static void timer_start(oss_seq_t *dp) +{ + apply_queue_tempo(dp); + send_queue_control(dp, SND_SEQ_EVENT_START); + dp->timer.running = 1; + dp->timer.cur_tick = 0; + dp->timer.realtime = 0; +} + +static void timer_stop(oss_seq_t *dp) +{ + send_queue_control(dp, SND_SEQ_EVENT_STOP); + dp->timer.running = 0; +} + +static void timer_continue(oss_seq_t *dp) +{ + send_queue_control(dp, SND_SEQ_EVENT_CONTINUE); + dp->timer.running = 1; +} + +/* Event processing: OSS to ALSA */ + +/* + * Returns 1 if the event is a pure timing event (consumed), 0 otherwise. + */ +static int process_timer_event(oss_seq_t *dp, union evrec *ev) +{ + unsigned int parm, tick; + + if (ev->t.code == EV_TIMING) { + parm = ev->t.time; + switch (ev->t.cmd) { + case TMR_WAIT_REL: + dp->timer.cur_tick += parm; + dp->timer.realtime = 0; + return 1; + case TMR_WAIT_ABS: + if (parm == 0) + dp->timer.realtime = 1; + else if (parm >= dp->timer.cur_tick) { + dp->timer.realtime = 0; + dp->timer.cur_tick = parm; + } + return 1; + case TMR_START: + timer_start(dp); + return 1; + case TMR_STOP: + timer_stop(dp); + return 1; + case TMR_CONTINUE: + timer_continue(dp); + return 1; + case TMR_TEMPO: + if (parm > 0) + dp->timer.oss_tempo = parm; + if (dp->timer.running) + apply_queue_tempo(dp); + return 1; + default: + return 0; + } + } else if (ev->s.code == SEQ_WAIT) { + /* 4-byte wait: tick in bytes 1-3 */ + tick = (ev->echo >> 8) & 0xffffff; + if (tick > dp->timer.cur_tick) { + dp->timer.cur_tick = tick; + dp->timer.realtime = 0; + } + return 1; + } + return 0; +} + +static void set_dest_midi(oss_seq_t *dp, snd_seq_event_t *ev, int dev) +{ + if (dev >= 0 && dev < dp->max_mididev) + snd_seq_ev_set_dest(ev, dp->midi_devs[dev].client, dp->midi_devs[dev].port); + else + snd_seq_ev_set_dest(ev, SND_SEQ_ADDRESS_SUBSCRIBERS, 0); +} + +/* + * Translate a single OSS event record to an ALSA event. + * Returns 0 on success, negative on error/skip, 1 if already sent (sysex/midiputc). + */ +static int process_event(oss_seq_t *dp, union evrec *ev, snd_seq_event_t *out) +{ + int dev, chn, note, vel, sysex_len, i; + union evrec tmp; + oss_midi_dev_t *md; + snd_seq_event_t mev; + long encode_rc; + unsigned char sysex_buf[6]; + + snd_seq_ev_clear(out); + out->source = dp->addr; + + switch (ev->s.code) { + case EV_CHN_VOICE: + dev = ev->v.dev; + chn = ev->v.chn; + note = ev->v.note; + vel = ev->v.parm; + set_dest_midi(dp, out, dev); + switch (ev->v.cmd) { + case MIDI_NOTEON: + if (vel == 0) + snd_seq_ev_set_noteoff(out, chn, note, 0); + else + snd_seq_ev_set_noteon(out, chn, note, vel); + break; + case MIDI_NOTEOFF: + snd_seq_ev_set_noteoff(out, chn, note, vel); + break; + case MIDI_KEY_PRESSURE: + out->type = SND_SEQ_EVENT_KEYPRESS; + out->data.note.channel = chn; + out->data.note.note = note; + out->data.note.velocity = vel; + break; + default: + return -EINVAL; + } + break; + + case EV_CHN_COMMON: + dev = ev->l.dev; + chn = ev->l.chn; + set_dest_midi(dp, out, dev); + switch (ev->l.cmd) { + case MIDI_PGM_CHANGE: + snd_seq_ev_set_pgmchange(out, chn, ev->l.p1); + break; + case MIDI_CTL_CHANGE: + snd_seq_ev_set_controller(out, chn, ev->l.p1, ev->l.val); + break; + case MIDI_PITCH_BEND: + snd_seq_ev_set_pitchbend(out, chn, ev->l.val - 8192); + break; + case MIDI_CHN_PRESSURE: + snd_seq_ev_set_chanpress(out, chn, ev->l.val); + break; + default: + return -EINVAL; + } + break; + + case EV_TIMING: + switch (ev->t.cmd) { + case TMR_ECHO: + /* schedule an ECHO event that loops back */ + if (dp->seq_mode == OSS_SEQ_MODE_SYNTH) { + memset(&tmp, 0, sizeof(tmp)); + tmp.echo = (ev->t.time << 8) | SEQ_ECHO; + } else { + memcpy(&tmp, ev, LONG_EVENT_SIZE); + } + out->type = SND_SEQ_EVENT_ECHO; + snd_seq_ev_set_dest(out, dp->addr.client, dp->addr.port); + memcpy(&out->data, &tmp, LONG_EVENT_SIZE); + break; + /* TMR_START/STOP/CONTINUE/TEMPO already handled in process_timer_event */ + default: + return -EINVAL; + } + break; + + case SEQ_EXTENDED: + if (dp->seq_mode != OSS_SEQ_MODE_SYNTH) + return -EINVAL; + dev = ev->e.dev; + chn = ev->e.chn; + set_dest_midi(dp, out, dev); + switch (ev->e.cmd) { + case SEQ_EXT_NOTEOFF: + snd_seq_ev_set_noteoff(out, chn, ev->e.p1, ev->e.p2); + break; + case SEQ_EXT_NOTEON: + snd_seq_ev_set_noteon(out, chn, ev->e.p1, ev->e.p2); + break; + case SEQ_EXT_PGMCHANGE: + snd_seq_ev_set_pgmchange(out, chn, ev->e.p1); + break; + case SEQ_AFTERTOUCH: /* 4 */ + snd_seq_ev_set_chanpress(out, chn, ev->e.p1); + break; + case SEQ_BALANCE: /* 5 */ + snd_seq_ev_set_controller(out, chn, 10 /* CTL_PAN */, ev->e.p1); + break; + case SEQ_CONTROLLER: /* 6 */ + snd_seq_ev_set_controller(out, chn, ev->e.p1, ev->e.p2); + break; + default: + return -EINVAL; + } + break; + + case SEQ_MIDIPUTC: + dev = ev->s.dev; + if (dev < 0 || dev >= dp->max_mididev) + return -ENXIO; + md = &dp->midi_devs[dev]; + if (!md->coder) + return -ENXIO; + snd_seq_ev_clear(&mev); + mev.source = dp->addr; + set_dest_midi(dp, &mev, dev); + encode_rc = snd_midi_event_encode_byte(md->coder, ev->s.parm1, &mev); + if (encode_rc > 0) { + if (dp->timer.realtime || !dp->timer.running) + snd_seq_ev_set_direct(&mev); + else + snd_seq_ev_schedule_tick(&mev, dp->queue, 0, dp->timer.cur_tick); + snd_seq_event_output(dp->seq, &mev); + } + return 1; /* already handled */ + + case EV_SYSEX: + dev = ev->x.dev; + sysex_len = 0; + for (i = 0; i < 6; i++) { + sysex_buf[i] = ev->x.buf[i]; + if (sysex_buf[i] == 0xff) + break; + sysex_len++; + } + if (sysex_len == 0) + return -EINVAL; + set_dest_midi(dp, out, dev); + snd_seq_ev_set_variable(out, sysex_len, sysex_buf); + out->type = SND_SEQ_EVENT_SYSEX; + /* must output immediately since sysex_buf is stack-allocated */ + if (dp->timer.realtime || !dp->timer.running) + snd_seq_ev_set_direct(out); + else + snd_seq_ev_schedule_tick(out, dp->queue, 0, dp->timer.cur_tick); + snd_seq_event_output(dp->seq, out); + return 1; /* already handled */ + + case SEQ_ECHO: + if (dp->seq_mode == OSS_SEQ_MODE_MUSIC) + return -EINVAL; + out->type = SND_SEQ_EVENT_ECHO; + snd_seq_ev_set_dest(out, dp->addr.client, dp->addr.port); + memcpy(&out->data, ev, SHORT_EVENT_SIZE); + break; + + /* Old-style 4-byte events (SYNTH mode only) */ + default: + if (dp->seq_mode == OSS_SEQ_MODE_MUSIC) + return -EINVAL; + switch (ev->s.code) { + case SEQ_NOTEON: + set_dest_midi(dp, out, 0); + snd_seq_ev_set_noteon(out, ev->n.chn, ev->n.note, ev->n.vel); + break; + case SEQ_NOTEOFF: + set_dest_midi(dp, out, 0); + snd_seq_ev_set_noteoff(out, ev->n.chn, ev->n.note, 0); + break; + case SEQ_PGMCHANGE: + set_dest_midi(dp, out, 0); + snd_seq_ev_set_pgmchange(out, ev->n.chn, ev->n.note); + break; + case SEQ_SYNCTIMER: + timer_start(dp); + return -EINVAL; + default: + return -EINVAL; + } + break; + } + return 0; +} + +/* Event processing: ALSA to OSS */ + +/* Decode ALSA event to OSS SEQ_MIDIPUTC records */ +static void send_midi_bytes(oss_seq_t *dp, int dev, snd_seq_event_t *ev) +{ + oss_midi_dev_t *md; + unsigned char buf[16]; + unsigned int tick; + union evrec rec; + long len; + int i; + + if (dev < 0 || dev >= dp->max_mididev) + return; + md = &dp->midi_devs[dev]; + if (!md->coder) + return; + len = snd_midi_event_decode(md->coder, buf, sizeof(buf), ev); + if (len <= 0) + return; + tick = ev->time.tick; + readq_put_timestamp_synth(dp->readq, tick); + for (i = 0; i < len; i++) { + memset(&rec, 0, sizeof(rec)); + rec.c[0] = SEQ_MIDIPUTC; + rec.c[1] = buf[i]; + rec.c[2] = dev; + readq_put(dp->readq, &rec, 0); + } +} + +/* Find which midi_dev this ALSA address belongs to */ +static int find_midi_dev_by_addr(oss_seq_t *dp, snd_seq_addr_t *addr) +{ + int i; + + for (i = 0; i < dp->max_mididev; i++) { + if (dp->midi_devs[i].addr.client == addr->client && + dp->midi_devs[i].addr.port == addr->port) + return i; + } + return -1; +} + +/* Translate ALSA event to OSS EV_CHN_VOICE/EV_CHN_COMMON (MUSIC mode) */ +static void send_synth_event(oss_seq_t *dp, int dev, snd_seq_event_t *ev) +{ + unsigned int tick = ev->time.tick; + union evrec rec; + + readq_put_timestamp_music(dp->readq, tick); + memset(&rec, 0, sizeof(rec)); + switch (ev->type) { + case SND_SEQ_EVENT_NOTEON: + rec.v.code = EV_CHN_VOICE; + rec.v.dev = dev; + rec.v.cmd = MIDI_NOTEON; + rec.v.chn = ev->data.note.channel; + rec.v.note = ev->data.note.note; + rec.v.parm = ev->data.note.velocity; + readq_put(dp->readq, &rec, 1); + break; + case SND_SEQ_EVENT_NOTEOFF: + rec.v.code = EV_CHN_VOICE; + rec.v.dev = dev; + rec.v.cmd = MIDI_NOTEOFF; + rec.v.chn = ev->data.note.channel; + rec.v.note = ev->data.note.note; + rec.v.parm = ev->data.note.velocity; + readq_put(dp->readq, &rec, 1); + break; + case SND_SEQ_EVENT_KEYPRESS: + rec.v.code = EV_CHN_VOICE; + rec.v.dev = dev; + rec.v.cmd = MIDI_KEY_PRESSURE; + rec.v.chn = ev->data.note.channel; + rec.v.note = ev->data.note.note; + rec.v.parm = ev->data.note.velocity; + readq_put(dp->readq, &rec, 1); + break; + case SND_SEQ_EVENT_CONTROLLER: + rec.l.code = EV_CHN_COMMON; + rec.l.dev = dev; + rec.l.cmd = MIDI_CTL_CHANGE; + rec.l.chn = ev->data.control.channel; + rec.l.p1 = ev->data.control.param; + rec.l.val = ev->data.control.value; + readq_put(dp->readq, &rec, 1); + break; + case SND_SEQ_EVENT_PGMCHANGE: + rec.l.code = EV_CHN_COMMON; + rec.l.dev = dev; + rec.l.cmd = MIDI_PGM_CHANGE; + rec.l.chn = ev->data.control.channel; + rec.l.p1 = ev->data.control.value; + readq_put(dp->readq, &rec, 1); + break; + case SND_SEQ_EVENT_PITCHBEND: + rec.l.code = EV_CHN_COMMON; + rec.l.dev = dev; + rec.l.cmd = MIDI_PITCH_BEND; + rec.l.chn = ev->data.control.channel; + rec.l.val = ev->data.control.value + 8192; + readq_put(dp->readq, &rec, 1); + break; + case SND_SEQ_EVENT_CHANPRESS: + rec.l.code = EV_CHN_COMMON; + rec.l.dev = dev; + rec.l.cmd = MIDI_CHN_PRESSURE; + rec.l.chn = ev->data.control.channel; + rec.l.val = ev->data.control.value; + readq_put(dp->readq, &rec, 1); + break; + default: + break; + } +} + +/* + * Fetch available ALSA events and dispatch them. + * Non-echo events go into readq; echo events go into readq unless want_sync + * is set and the echo is a SEQ_SYNCTIMER, in which case returns 1 (sync done). + */ +static int handle_alsa_input(oss_seq_t *dp, int want_sync) +{ + snd_seq_event_t *ev; + int dev; + + snd_seq_event_input_pending(dp->seq, 1); + while (snd_seq_event_input_pending(dp->seq, 0) > 0) { + if (snd_seq_event_input(dp->seq, &ev) < 0) + break; + if (ev->type == SND_SEQ_EVENT_ECHO) { + union evrec *rec = (union evrec *)&ev->data; + if (want_sync && rec->s.code == SEQ_SYNCTIMER) + return 1; + if (dp->readq) + readq_put(dp->readq, rec, ev_is_long(rec)); + } else if (dp->readq) { + dev = find_midi_dev_by_addr(dp, &ev->source); + if (dp->seq_mode == OSS_SEQ_MODE_MUSIC) + send_synth_event(dp, dev, ev); + else + send_midi_bytes(dp, dev, ev); + } + } + return 0; +} + +/* MIDI device enumeration */ + +#define PERM_READ (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ) +#define PERM_WRITE (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE) + +static int enumerate_midi_devs(oss_seq_t *dp) +{ + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + oss_midi_dev_t *md, *tmp; + unsigned int type, caps; + int count = 0; + int alloc = 16; + int cli; + + snd_seq_client_info_alloca(&cinfo); + snd_seq_port_info_alloca(&pinfo); + + dp->midi_devs = malloc(alloc * sizeof(oss_midi_dev_t)); + if (!dp->midi_devs) + return -ENOMEM; + + snd_seq_client_info_set_client(cinfo, -1); + while (snd_seq_query_next_client(dp->seq, cinfo) >= 0) { + cli = snd_seq_client_info_get_client(cinfo); + if (cli == dp->client) + continue; + snd_seq_port_info_set_client(pinfo, cli); + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(dp->seq, pinfo) >= 0) { + type = snd_seq_port_info_get_type(pinfo); + caps = snd_seq_port_info_get_capability(pinfo); + if (!(type & SND_SEQ_PORT_TYPE_MIDI_GENERIC)) + continue; + if (!(caps & (PERM_READ | PERM_WRITE))) + continue; + if (count >= alloc) { + alloc *= 2; + tmp = realloc(dp->midi_devs, + alloc * sizeof(oss_midi_dev_t)); + if (!tmp) + return -ENOMEM; + dp->midi_devs = tmp; + } + md = &dp->midi_devs[count]; + memset(md, 0, sizeof(*md)); + md->client = snd_seq_port_info_get_client(pinfo); + md->port = snd_seq_port_info_get_port(pinfo); + md->addr.client = md->client; + md->addr.port = md->port; + md->caps = caps; + strncpy(md->name, snd_seq_port_info_get_name(pinfo), + sizeof(md->name) - 1); + count++; + } + } + dp->max_mididev = count; + return 0; +} + +/* Subscribe / unsubscribe MIDI ports */ + +static int subscribe_midi(oss_seq_t *dp, int dev, int for_read) +{ + snd_seq_port_subscribe_t *sub; + oss_midi_dev_t *md; + int rc; + + snd_seq_port_subscribe_alloca(&sub); + md = &dp->midi_devs[dev]; + + if (for_read) { + snd_seq_port_subscribe_set_sender(sub, &md->addr); + snd_seq_port_subscribe_set_dest(sub, &dp->addr); + snd_seq_port_subscribe_set_time_update(sub, 1); + snd_seq_port_subscribe_set_queue(sub, dp->queue); + snd_seq_port_subscribe_set_time_real(sub, 0); + rc = snd_seq_subscribe_port(dp->seq, sub); + if (rc < 0) + return rc; + md->opened |= PERM_READ; + } else { + snd_seq_port_subscribe_set_sender(sub, &dp->addr); + snd_seq_port_subscribe_set_dest(sub, &md->addr); + rc = snd_seq_subscribe_port(dp->seq, sub); + if (rc < 0) + return rc; + md->opened |= PERM_WRITE; + } + return 0; +} + +static void unsubscribe_midi(oss_seq_t *dp, int dev) +{ + snd_seq_port_subscribe_t *sub; + oss_midi_dev_t *md; + + snd_seq_port_subscribe_alloca(&sub); + md = &dp->midi_devs[dev]; + + if (md->opened & PERM_READ) { + snd_seq_port_subscribe_set_sender(sub, &md->addr); + snd_seq_port_subscribe_set_dest(sub, &dp->addr); + snd_seq_unsubscribe_port(dp->seq, sub); + } + if (md->opened & PERM_WRITE) { + snd_seq_port_subscribe_set_sender(sub, &dp->addr); + snd_seq_port_subscribe_set_dest(sub, &md->addr); + snd_seq_unsubscribe_port(dp->seq, sub); + } + md->opened = 0; +} + +/* Wait for the ALSA queue to finish playing before close */ +static void drain_at_close(oss_seq_t *dp) +{ + snd_seq_event_t ev, *evp; + union evrec rec; + struct pollfd pfds[4]; + int nfds, rc; + + if (!dp->timer.running || dp->timer.realtime) + return; + + /* + * Schedule a sentinel ECHO at cur_tick; when it arrives back, the + * queue has delivered all events up to that tick + */ + snd_seq_ev_clear(&ev); + ev.type = SND_SEQ_EVENT_ECHO; + memset(&rec, 0, sizeof(rec)); + rec.t.code = SEQ_SYNCTIMER; + rec.t.time = dp->timer.cur_tick; + memcpy(&ev.data, &rec, sizeof(rec)); + snd_seq_ev_set_dest(&ev, dp->addr.client, dp->addr.port); + snd_seq_ev_schedule_tick(&ev, dp->queue, 0, dp->timer.cur_tick); + snd_seq_event_output(dp->seq, &ev); + snd_seq_drain_output(dp->seq); + + nfds = snd_seq_poll_descriptors_count(dp->seq, POLLIN); + if (nfds <= 0 || nfds > (int)(sizeof(pfds) / sizeof(pfds[0]))) + return; + snd_seq_poll_descriptors(dp->seq, pfds, nfds, POLLIN); + + for (;;) { + rc = poll(pfds, nfds, 5000); + if (rc < 0) { + if (errno == EINTR) + continue; + break; + } + if (rc == 0) + break; /* timeout: give up waiting */ + /* fetch from kernel into userspace buffer */ + snd_seq_event_input_pending(dp->seq, 1); + while (snd_seq_event_input_pending(dp->seq, 0) > 0) { + if (snd_seq_event_input(dp->seq, &evp) < 0) + return; + if (evp->type == SND_SEQ_EVENT_ECHO) { + union evrec *r = (union evrec *)&evp->data; + if (r->s.code == SEQ_SYNCTIMER) + return; + } + } + } +} + +/* Reset helpers */ + +static void seq_all_notes_off(oss_seq_t *dp) +{ + snd_seq_event_t ev; + int i, ch; + + for (i = 0; i < dp->max_mididev; i++) { + if (!(dp->midi_devs[i].opened & PERM_WRITE)) + continue; + snd_seq_ev_clear(&ev); + ev.source = dp->addr; + snd_seq_ev_set_dest(&ev, dp->midi_devs[i].client, dp->midi_devs[i].port); + snd_seq_ev_set_direct(&ev); + for (ch = 0; ch < 16; ch++) { + snd_seq_ev_set_controller(&ev, ch, 123 /* all notes off */, 0); + snd_seq_event_output_direct(dp->seq, &ev); + } + } +} + +static void readq_clear(oss_readq_t *rq) +{ + char buf[64]; + + rq->head = rq->tail = rq->qlen = 0; + while (read(rq->pipe_rd, buf, sizeof(buf)) > 0) + ; +} + +/* Public API: open / close */ + +int lib_oss_seq_open(const char *pathname, int flags) +{ + int seq_mode, oflags, rc, readable, writable, i; + int pipefds[2]; + unsigned int port_caps, port_type; + oss_seq_t *dp; + + if (strcmp(pathname, "/dev/music") == 0) + seq_mode = OSS_SEQ_MODE_MUSIC; + else + seq_mode = OSS_SEQ_MODE_SYNTH; + + dp = calloc(1, sizeof(*dp)); + if (!dp) + return -ENOMEM; + + dp->seq_mode = seq_mode; + dp->file_mode = flags & O_ACCMODE; + dp->timer.oss_tempo = 60; + dp->timer.oss_timebase = 100; + dp->timer.realtime = 1; + + oflags = flags & O_ACCMODE; + dp->fileno = open("/dev/null", oflags); + if (dp->fileno < 0) { + free(dp); + return -errno; + } + + rc = snd_seq_open(&dp->seq, "default", SND_SEQ_OPEN_DUPLEX, 0); + if (rc < 0) { + close(dp->fileno); + free(dp); + errno = -rc; + return -1; + } + + snd_seq_set_client_name(dp->seq, "OSS sequencer"); + dp->client = snd_seq_client_id(dp->seq); + + port_caps = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE; + port_type = SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION; + dp->port = snd_seq_create_simple_port(dp->seq, "OSS port", port_caps, port_type); + if (dp->port < 0) { + rc = dp->port; + goto err_close_seq; + } + dp->addr.client = dp->client; + dp->addr.port = dp->port; + + dp->queue = snd_seq_alloc_queue(dp->seq); + if (dp->queue < 0) { + rc = dp->queue; + goto err_delete_port; + } + apply_queue_tempo(dp); + + rc = enumerate_midi_devs(dp); + if (rc < 0) + goto err_free_queue; + + readable = (dp->file_mode == O_RDONLY || dp->file_mode == O_RDWR); + if (readable) { + dp->readq = calloc(1, sizeof(*dp->readq)); + if (!dp->readq) { + rc = -ENOMEM; + goto err_free_devs; + } + if (pipe2(pipefds, O_NONBLOCK) < 0) { + rc = -errno; + goto err_free_readq; + } + dp->readq->pipe_rd = pipefds[0]; + dp->readq->pipe_wr = pipefds[1]; + + for (i = 0; i < dp->max_mididev; i++) { + if (dp->midi_devs[i].caps & PERM_READ) + subscribe_midi(dp, i, 1); + } + } + + writable = (dp->file_mode == O_WRONLY || dp->file_mode == O_RDWR); + if (writable) { + for (i = 0; i < dp->max_mididev; i++) { + if (dp->midi_devs[i].caps & PERM_WRITE) + subscribe_midi(dp, i, 0); + } + for (i = 0; i < dp->max_mididev; i++) { + snd_midi_event_new(256, &dp->midi_devs[i].coder); + if (dp->midi_devs[i].coder) + snd_midi_event_no_status(dp->midi_devs[i].coder, 1); + } + } + + dp->next = seq_list; + seq_list = dp; + + return dp->fileno; + +err_free_readq: + if (dp->readq) { + free(dp->readq); + } +err_free_devs: + for (i = 0; i < dp->max_mididev; i++) { + if (dp->midi_devs[i].coder) + snd_midi_event_free(dp->midi_devs[i].coder); + } + free(dp->midi_devs); +err_free_queue: + snd_seq_free_queue(dp->seq, dp->queue); +err_delete_port: + snd_seq_delete_simple_port(dp->seq, dp->port); +err_close_seq: + snd_seq_close(dp->seq); + close(dp->fileno); + free(dp); + errno = -rc; + return -1; +} + +int lib_oss_seq_close(int fd) +{ + oss_seq_t *dp, *prev = NULL; + int i; + + for (dp = seq_list; dp; prev = dp, dp = dp->next) { + if (dp->fileno == fd) + break; + } + if (!dp) { + errno = EBADF; + return -1; + } + if (prev) + prev->next = dp->next; + else + seq_list = dp->next; + + drain_at_close(dp); + + for (i = 0; i < dp->max_mididev; i++) { + unsubscribe_midi(dp, i); + if (dp->midi_devs[i].coder) + snd_midi_event_free(dp->midi_devs[i].coder); + } + free(dp->midi_devs); + + snd_seq_drop_output(dp->seq); + snd_seq_free_queue(dp->seq, dp->queue); + snd_seq_delete_simple_port(dp->seq, dp->port); + snd_seq_close(dp->seq); + + if (dp->readq) { + close(dp->readq->pipe_rd); + close(dp->readq->pipe_wr); + free(dp->readq); + } + + close(fd); + + free(dp); + return 0; +} + +/* Public API: read / write */ + +ssize_t lib_oss_seq_read(int fd, void *buf, size_t count) +{ + oss_seq_t *dp = find_seq(fd); + oss_readq_t *rq; + unsigned char *p = buf; + union evrec *rec; + char tmp[64]; + size_t copied = 0; + int sz, nfds; + struct pollfd pfds[8]; + + if (!dp) { + errno = EBADF; + return -1; + } + if (!dp->readq) { + errno = EINVAL; + return -1; + } + rq = dp->readq; + + while (copied == 0) { + if (rq->qlen == 0) { + if (dp->nonblock) { + handle_alsa_input(dp, 0); + if (rq->qlen == 0) { + errno = EAGAIN; + return -1; + } + } else { + nfds = snd_seq_poll_descriptors_count(dp->seq, POLLIN); + if (nfds > (int)(sizeof(pfds) / sizeof(pfds[0]))) + nfds = sizeof(pfds) / sizeof(pfds[0]); + do { + snd_seq_poll_descriptors(dp->seq, pfds, nfds, POLLIN); + while (poll(pfds, nfds, -1) < 0 && errno == EINTR) + ; + handle_alsa_input(dp, 0); + } while (rq->qlen == 0); + } + continue; + } + while (rq->qlen > 0 && copied + LONG_EVENT_SIZE <= count) { + rec = &rq->buf[rq->head]; + sz = ev_is_long(rec) ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE; + if (copied + sz > count) + break; + memcpy(p + copied, rec, sz); + copied += sz; + rq->head = (rq->head + 1) % READQ_SIZE; + rq->qlen--; + } + if (copied == 0 && count < SHORT_EVENT_SIZE) { + errno = EINVAL; + return -1; + } + if (copied == 0) + break; /* event too large for buffer */ + } + if (rq->qlen == 0) { + while (read(rq->pipe_rd, tmp, sizeof(tmp)) > 0) + ; + } + return copied; +} + +ssize_t lib_oss_seq_write(int fd, const void *buf, size_t count) +{ + oss_seq_t *dp = find_seq(fd); + const unsigned char *p = buf; + union evrec ev; + snd_seq_event_t sev; + size_t pos = 0; + int is_long, evlen, rc; + + if (!dp) { + errno = EBADF; + return -1; + } + + while (pos + SHORT_EVENT_SIZE <= count) { + memset(&ev, 0, sizeof(ev)); + memcpy(&ev, p + pos, SHORT_EVENT_SIZE); + + if (ev.s.code == SEQ_FULLSIZE) { + pos += SHORT_EVENT_SIZE; + continue; + } + + is_long = ev_is_long(&ev); + evlen = is_long ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE; + + if (pos + evlen > count) + break; + + if (is_long) + memcpy(&ev, p + pos, LONG_EVENT_SIZE); + + pos += evlen; + + if (process_timer_event(dp, &ev)) + continue; + + rc = process_event(dp, &ev, &sev); + if (rc < 0) + continue; + if (rc == 1) + continue; /* already sent (sysex/midiputc) */ + + if (dp->timer.realtime || !dp->timer.running) + snd_seq_ev_set_direct(&sev); + else + snd_seq_ev_schedule_tick(&sev, dp->queue, 0, dp->timer.cur_tick); + + snd_seq_event_output(dp->seq, &sev); + } + + snd_seq_drain_output(dp->seq); + return pos; +} + +/* Public API: ioctl */ + +int lib_oss_seq_ioctl(int fd, unsigned long request, ...) +{ + va_list args; + void *arg; + oss_seq_t *dp; + snd_seq_event_t sev; + union evrec ev, rec; + snd_seq_client_pool_t *pool; + struct synth_info *sinfo; + struct midi_info *minfo; + struct pollfd pfds[4]; + int dev, val, rc, nfds; + + va_start(args, request); + arg = va_arg(args, void *); + va_end(args); + + dp = find_seq(fd); + if (!dp) { + errno = EBADF; + return -1; + } + + switch (request) { + case SNDCTL_SEQ_RESET: + seq_all_notes_off(dp); + snd_seq_drop_output(dp->seq); + if (dp->readq) + readq_clear(dp->readq); + dp->timer.cur_tick = 0; + dp->timer.realtime = 1; + return 0; + + case SNDCTL_SEQ_PANIC: + seq_all_notes_off(dp); + snd_seq_drop_output(dp->seq); + if (dp->readq) + readq_clear(dp->readq); + errno = EINVAL; + return -1; + + case SNDCTL_SEQ_SYNC: + snd_seq_ev_clear(&sev); + sev.type = SND_SEQ_EVENT_ECHO; + memset(&rec, 0, sizeof(rec)); + rec.t.code = SEQ_SYNCTIMER; + rec.t.time = dp->timer.cur_tick; + memcpy(&sev.data, &rec, sizeof(rec)); + snd_seq_ev_set_dest(&sev, dp->addr.client, dp->addr.port); + if (!dp->timer.running || dp->timer.realtime) + snd_seq_ev_set_direct(&sev); + else + snd_seq_ev_schedule_tick(&sev, dp->queue, 0, dp->timer.cur_tick); + snd_seq_event_output(dp->seq, &sev); + snd_seq_drain_output(dp->seq); + + nfds = snd_seq_poll_descriptors_count(dp->seq, POLLIN); + if (nfds > (int)(sizeof(pfds) / sizeof(pfds[0]))) + nfds = sizeof(pfds) / sizeof(pfds[0]); + for (;;) { + snd_seq_poll_descriptors(dp->seq, pfds, nfds, POLLIN); + rc = poll(pfds, nfds, 5000); + if (rc < 0) { + if (errno == EINTR) + continue; + break; + } + if (rc == 0) + break; /* timeout */ + if (handle_alsa_input(dp, 1)) + break; /* sync echo received */ + } + return 0; + + case SNDCTL_SEQ_NRSYNTHS: + if (dp->seq_mode == OSS_SEQ_MODE_MUSIC) + *(int *)arg = dp->max_mididev; + else + *(int *)arg = 0; + return 0; + + case SNDCTL_SEQ_NRMIDIS: + *(int *)arg = dp->max_mididev; + return 0; + + case SNDCTL_SEQ_GETTIME: + *(int *)arg = dp->timer.cur_tick; + return 0; + + case SNDCTL_SEQ_CTRLRATE: + *(int *)arg = (dp->timer.oss_tempo * dp->timer.oss_timebase + 30) / 60; + return 0; + + case SNDCTL_SEQ_GETINCOUNT: + *(int *)arg = dp->readq ? dp->readq->qlen : 0; + return 0; + + case SNDCTL_SEQ_GETOUTCOUNT: + snd_seq_client_pool_alloca(&pool); + if (snd_seq_get_client_pool(dp->seq, pool) >= 0) + *(int *)arg = snd_seq_client_pool_get_output_room(pool); + else + *(int *)arg = 0; + return 0; + + case SNDCTL_SEQ_THRESHOLD: + return 0; + + case SNDCTL_SEQ_RESETSAMPLES: + case SNDCTL_SYNTH_MEMAVL: + case SNDCTL_FM_4OP_ENABLE: + errno = EINVAL; + return -1; + + case SNDCTL_SEQ_TESTMIDI: + dev = *(int *)arg; + if (dev < 0 || dev >= dp->max_mididev) { + errno = ENXIO; + return -1; + } + return 0; + + case SNDCTL_SEQ_OUTOFBAND: + if (!arg) { + errno = EINVAL; + return -1; + } + memcpy(&ev, arg, LONG_EVENT_SIZE); + snd_seq_ev_clear(&sev); + rc = process_event(dp, &ev, &sev); + if (rc < 0) { + errno = -rc; + return -1; + } + if (rc == 0) { + snd_seq_ev_set_direct(&sev); + snd_seq_event_output_direct(dp->seq, &sev); + } + return 0; + + case SNDCTL_SYNTH_INFO: + case SNDCTL_SYNTH_ID: + sinfo = arg; + dev = sinfo->device; + if (dp->seq_mode != OSS_SEQ_MODE_MUSIC || dev < 0 || dev >= dp->max_mididev) { + errno = ENXIO; + return -1; + } + memset(sinfo, 0, sizeof(*sinfo)); + sinfo->device = dev; + sinfo->synth_type = SYNTH_TYPE_MIDI; + sinfo->synth_subtype = 0; + sinfo->nr_voices = 16; + snprintf(sinfo->name, sizeof(sinfo->name), "%s", dp->midi_devs[dev].name); + return 0; + + case SNDCTL_MIDI_INFO: + minfo = arg; + dev = minfo->device; + if (dev < 0 || dev >= dp->max_mididev) { + errno = ENXIO; + return -1; + } + memset(minfo, 0, sizeof(*minfo)); + minfo->device = dev; + snprintf(minfo->name, sizeof(minfo->name), "%s", dp->midi_devs[dev].name); + return 0; + + case SNDCTL_MIDI_PRETIME: + return 0; + + case SNDCTL_TMR_TIMEBASE: + val = *(int *)arg; + if (val > 0) { + dp->timer.oss_timebase = val; + if (dp->timer.running) + apply_queue_tempo(dp); + } + *(int *)arg = dp->timer.oss_timebase; + return 0; + + case SNDCTL_TMR_TEMPO: + val = *(int *)arg; + if (val > 0) { + dp->timer.oss_tempo = val; + if (dp->timer.running) + apply_queue_tempo(dp); + } + *(int *)arg = dp->timer.oss_tempo; + return 0; + + case SNDCTL_TMR_START: + timer_start(dp); + return 0; + + case SNDCTL_TMR_STOP: + timer_stop(dp); + return 0; + + case SNDCTL_TMR_CONTINUE: + timer_continue(dp); + return 0; + + case SNDCTL_TMR_METRONOME: + return 0; + + case SNDCTL_TMR_SOURCE: + *(int *)arg = TMR_INTERNAL; + return 0; + + case SNDCTL_TMR_SELECT: + return 0; + + case OSS_GETVERSION: + *(int *)arg = 0x030901; + return 0; + + default: + errno = EINVAL; + return -1; + } +} + +/* Public API: poll support */ + +int lib_oss_seq_poll_fds(int fd) +{ + oss_seq_t *dp = find_seq(fd); + int n = 0; + + if (!dp) + return 0; + if (dp->readq) { + n++; /* pipe_rd */ + n += snd_seq_poll_descriptors_count(dp->seq, POLLIN); + } + if (dp->file_mode == O_WRONLY || dp->file_mode == O_RDWR) + n += snd_seq_poll_descriptors_count(dp->seq, POLLOUT); + /* subtract 1 because the caller already accounts for the base fd */ + return n > 0 ? n - 1 : 0; +} + +int lib_oss_seq_poll_prepare(int fd, int fmode, struct pollfd *ufds) +{ + oss_seq_t *dp = find_seq(fd); + int n = 0, cnt; + + if (!dp) + return 0; + if ((fmode == O_RDONLY || fmode == O_RDWR) && dp->readq) { + ufds[n].fd = dp->readq->pipe_rd; + ufds[n].events = POLLIN; + ufds[n].revents = 0; + n++; + cnt = snd_seq_poll_descriptors(dp->seq, &ufds[n], + snd_seq_poll_descriptors_count(dp->seq, POLLIN), + POLLIN); + n += cnt; + } + if (fmode == O_WRONLY || fmode == O_RDWR) { + cnt = snd_seq_poll_descriptors(dp->seq, &ufds[n], + snd_seq_poll_descriptors_count(dp->seq, POLLOUT), + POLLOUT); + n += cnt; + } + return n; +} + +int lib_oss_seq_poll_result(int fd, struct pollfd *ufds) +{ + oss_seq_t *dp = find_seq(fd); + unsigned short revents = 0; + int result = 0, n = 0, cnt; + + if (!dp) + return OSS_WAIT_EVENT_ERROR; + if (dp->readq) { + if (ufds[n].revents & (POLLIN | POLLHUP)) + result |= OSS_WAIT_EVENT_READ; + if (ufds[n].revents & POLLERR) + result |= OSS_WAIT_EVENT_ERROR; + n++; + cnt = snd_seq_poll_descriptors_count(dp->seq, POLLIN); + snd_seq_poll_descriptors_revents(dp->seq, &ufds[n], cnt, &revents); + if (revents & POLLIN) + result |= OSS_WAIT_EVENT_READ; + if (revents & POLLERR) + result |= OSS_WAIT_EVENT_ERROR; + n += cnt; + } + if (dp->file_mode == O_WRONLY || dp->file_mode == O_RDWR) { + cnt = snd_seq_poll_descriptors_count(dp->seq, POLLOUT); + snd_seq_poll_descriptors_revents(dp->seq, &ufds[n], cnt, &revents); + if (revents & POLLOUT) + result |= OSS_WAIT_EVENT_WRITE; + if (revents & POLLERR) + result |= OSS_WAIT_EVENT_ERROR; + } + return result; +} + +/* select wrappers (thin; poll is the primary interface) */ + +int lib_oss_seq_select_prepare(int fd, int fmode, fd_set *rfds, + fd_set *wfds __attribute__((unused)), + fd_set *efds __attribute__((unused))) +{ + oss_seq_t *dp = find_seq(fd); + struct pollfd tmp[8]; + int maxfd = 0, n, i; + + if (!dp) + return 0; + if ((fmode == O_RDONLY || fmode == O_RDWR) && dp->readq) { + FD_SET(dp->readq->pipe_rd, rfds); + if (dp->readq->pipe_rd > maxfd) + maxfd = dp->readq->pipe_rd; + n = snd_seq_poll_descriptors_count(dp->seq, POLLIN); + if (n > (int)(sizeof(tmp) / sizeof(tmp[0]))) + n = sizeof(tmp) / sizeof(tmp[0]); + n = snd_seq_poll_descriptors(dp->seq, tmp, n, POLLIN); + for (i = 0; i < n; i++) { + if (tmp[i].events & POLLIN) { + FD_SET(tmp[i].fd, rfds); + if (tmp[i].fd > maxfd) + maxfd = tmp[i].fd; + } + } + } + return maxfd; +} + +int lib_oss_seq_select_result(int fd, fd_set *rfds, + fd_set *wfds __attribute__((unused)), + fd_set *efds __attribute__((unused))) +{ + oss_seq_t *dp = find_seq(fd); + struct pollfd tmp[8]; + int result = 0, n, i; + + if (!dp) + return OSS_WAIT_EVENT_ERROR; + if (dp->readq && rfds) { + if (FD_ISSET(dp->readq->pipe_rd, rfds)) + result |= OSS_WAIT_EVENT_READ; + n = snd_seq_poll_descriptors_count(dp->seq, POLLIN); + if (n > (int)(sizeof(tmp) / sizeof(tmp[0]))) + n = sizeof(tmp) / sizeof(tmp[0]); + n = snd_seq_poll_descriptors(dp->seq, tmp, n, POLLIN); + for (i = 0; i < n; i++) { + if ((tmp[i].events & POLLIN) && FD_ISSET(tmp[i].fd, rfds)) { + result |= OSS_WAIT_EVENT_READ; + break; + } + } + } + return result; +} -- 2.52.0