90da2b2859
For a slightly thorough explaination, please refer to [1] http://people.freebsd.org/~ariff/SOUND_4.TXT.html . Summary of changes includes: 1 Volume Per-Channel (vpc). Provides private / standalone volume control unique per-stream pcm channel without touching master volume / pcm. Applications can directly use SNDCTL_DSP_[GET|SET][PLAY|REC]VOL, or for backwards compatibility, SOUND_MIXER_PCM through the opened dsp device instead of /dev/mixer. Special "bypass" mode is enabled through /dev/mixer which will automatically detect if the adjustment is made through /dev/mixer and forward its request to this private volume controller. Changes to this volume object will not interfere with other channels. Requirements: - SNDCTL_DSP_[GET|SET][PLAY|REC]_VOL are newer ioctls (OSSv4) which require specific application modifications (preferred). - No modifications required for using bypass mode, so applications like mplayer or xmms should work out of the box. Kernel hints: - hint.pcm.%d.vpc (0 = disable vpc). Kernel sysctls: - hw.snd.vpc_mixer_bypass (default: 1). Enable or disable /dev/mixer bypass mode. - hw.snd.vpc_autoreset (default: 1). By default, closing/opening /dev/dsp will reset the volume back to 0 db gain/attenuation. Setting this to 0 will preserve its settings across device closing/opening. - hw.snd.vpc_reset (default: 0). Panic/reset button to reset all volume settings back to 0 db. - hw.snd.vpc_0db (default: 45). 0 db relative to linear mixer value. 2 High quality fixed-point Bandlimited SINC sampling rate converter, based on Julius O'Smith's Digital Audio Resampling - http://ccrma.stanford.edu/~jos/resample/. It includes a filter design script written in awk (the clumsiest joke I've ever written) - 100% 32bit fixed-point, 64bit accumulator. - Possibly among the fastest (if not fastest) of its kind. - Resampling quality is tunable, either runtime or during kernel compilation (FEEDER_RATE_PRESETS). - Quality can be further customized during kernel compilation by defining FEEDER_RATE_PRESETS in /etc/make.conf. Kernel sysctls: - hw.snd.feeder_rate_quality. 0 - Zero-order Hold (ZOH). Fastest, bad quality. 1 - Linear Interpolation (LINEAR). Slightly slower than ZOH, better quality but still does not eliminate aliasing. 2 - (and above) - Sinc Interpolation(SINC). Best quality. SINC quality always start from 2 and above. Rough quality comparisons: - http://people.freebsd.org/~ariff/z_comparison/ 3 Bit-perfect mode. Bypasses all feeder/dsp effects. Pure sound will be directly fed into the hardware. 4 Parametric (compile time) Software Equalizer (Bass/Treble mixer). Can be customized by defining FEEDER_EQ_PRESETS in /etc/make.conf. 5 Transparent/Adaptive Virtual Channel. Now you don't have to disable vchans in order to make digital format pass through. It also makes vchans more dynamic by choosing a better format/rate among all the concurrent streams, which means that dev.pcm.X.play.vchanformat/rate becomes sort of optional. 6 Exclusive Stream, with special open() mode O_EXCL. This will "mute" other concurrent vchan streams and only allow a single channel with O_EXCL set to keep producing sound. Other Changes: * most feeder_* stuffs are compilable in userland. Let's not speculate whether we should go all out for it (save that for FreeBSD 16.0-RELEASE). * kobj signature fixups, thanks to Andriy Gapon <avg@freebsd.org> * pull out channel mixing logic out of vchan.c and create its own feeder_mixer for world justice. * various refactoring here and there, for good or bad. * activation of few more OSSv4 ioctls() (see [1] above). * opt_snd.h for possible compile time configuration: (mostly for debugging purposes, don't try these at home) SND_DEBUG SND_DIAGNOSTIC SND_FEEDER_MULTIFORMAT SND_FEEDER_FULL_MULTIFORMAT SND_FEEDER_RATE_HP SND_PCM_64 SND_OLDSTEREO Manual page updates are on the way. Tested by: joel, Olivier SMEDTS <olivier at gid0 d org>, too many unsung / unnamed heroes.
2105 lines
47 KiB
C
2105 lines
47 KiB
C
/*-
|
|
* Copyright (c) 2003 Mathew Kanner
|
|
* Copyright (c) 1993 Hannu Savolainen
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* The sequencer personality manager.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#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/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.h>
|
|
#include <machine/clock.h> /* for DELAY */
|
|
#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 <sys/kthread.h>
|
|
#include <sys/unistd.h>
|
|
#include <sys/selinfo.h>
|
|
|
|
#ifdef HAVE_KERNEL_OPTION_HEADERS
|
|
#include "opt_snd.h"
|
|
#endif
|
|
|
|
#include <dev/sound/midi/midi.h>
|
|
#include <dev/sound/midi/midiq.h>
|
|
#include "synth_if.h"
|
|
|
|
#include <dev/sound/midi/sequencer.h>
|
|
|
|
#define TMR_TIMERBASE 13
|
|
|
|
#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM
|
|
* synthesizer and MIDI output) */
|
|
#define SND_DEV_MUSIC 8 /* /dev/music, level 2 interface */
|
|
|
|
/* 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)
|
|
|
|
#define PCMMKMINOR(u, d, c) \
|
|
((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f))
|
|
#define MIDIMKMINOR(u, d, c) PCMMKMINOR(u, d, c)
|
|
#define MIDIUNIT(y) ((dev2unit(y) >> 4) & 0x0f)
|
|
#define MIDIDEV(y) (dev2unit(y) & 0x0f)
|
|
|
|
/* 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;
|
|
|
|
static struct cdevsw seq_cdevsw = {
|
|
.d_version = D_VERSION,
|
|
.d_open = seq_open,
|
|
.d_close = seq_close,
|
|
.d_read = seq_read,
|
|
.d_write = seq_write,
|
|
.d_ioctl = seq_ioctl,
|
|
.d_poll = seq_poll,
|
|
.d_name = "sequencer",
|
|
};
|
|
|
|
struct seq_softc {
|
|
KOBJ_FIELDS;
|
|
|
|
struct mtx seq_lock, q_lock;
|
|
struct cv empty_cv, reset_cv, in_cv, out_cv, state_cv, th_cv;
|
|
|
|
MIDIQ_HEAD(, u_char) in_q, out_q;
|
|
|
|
u_long flags;
|
|
/* Flags (protected by flag_mtx of mididev_info) */
|
|
int fflags; /* Access mode */
|
|
int music;
|
|
|
|
int out_water; /* Sequence output threshould */
|
|
snd_sync_parm sync_parm; /* AIOSYNC parameter set */
|
|
struct thread *sync_thread; /* AIOSYNCing thread */
|
|
struct selinfo in_sel, out_sel;
|
|
int midi_number;
|
|
struct cdev *seqdev, *musicdev;
|
|
int unit;
|
|
int maxunits;
|
|
kobj_t *midis;
|
|
int *midi_flags;
|
|
kobj_t mapper;
|
|
void *mapper_cookie;
|
|
struct timeval timerstop, timersub;
|
|
int timerbase, tempo;
|
|
int timerrun;
|
|
int done;
|
|
int playing;
|
|
int recording;
|
|
int busy;
|
|
int pre_event_timeout;
|
|
int waiting;
|
|
};
|
|
|
|
/*
|
|
* Module specific stuff, including how many sequecers
|
|
* we currently own.
|
|
*/
|
|
|
|
SYSCTL_NODE(_hw_midi, OID_AUTO, seq, CTLFLAG_RD, 0, "Midi sequencer");
|
|
|
|
int seq_debug;
|
|
/* XXX: should this be moved into debug.midi? */
|
|
SYSCTL_INT(_hw_midi_seq, OID_AUTO, debug, CTLFLAG_RW, &seq_debug, 0, "");
|
|
|
|
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_SEQ_GETTIME, "SNDCTL_SEQ_GETTIME"},
|
|
{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},
|
|
};
|
|
|
|
midi_cmdtab cmdtab_seqcv[] = {
|
|
{MIDI_NOTEOFF, "MIDI_NOTEOFF"},
|
|
{MIDI_NOTEON, "MIDI_NOTEON"},
|
|
{MIDI_KEY_PRESSURE, "MIDI_KEY_PRESSURE"},
|
|
{-1, NULL},
|
|
};
|
|
|
|
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},
|
|
};
|
|
|
|
#ifndef KOBJMETHOD_END
|
|
#define KOBJMETHOD_END { NULL, NULL }
|
|
#endif
|
|
|
|
/*
|
|
* static const char *mpu401_mprovider(kobj_t obj, struct mpu401 *m);
|
|
*/
|
|
|
|
static kobj_method_t seq_methods[] = {
|
|
/* KOBJMETHOD(mpu_provider,mpu401_mprovider), */
|
|
KOBJMETHOD_END
|
|
};
|
|
|
|
DEFINE_CLASS(sequencer, seq_methods, 0);
|
|
|
|
/* The followings are the local function. */
|
|
static int seq_convertold(u_char *event, u_char *out);
|
|
|
|
/*
|
|
* static void seq_midiinput(struct seq_softc * scp, void *md);
|
|
*/
|
|
static void seq_reset(struct seq_softc *scp);
|
|
static int seq_sync(struct seq_softc *scp);
|
|
|
|
static int seq_processevent(struct seq_softc *scp, u_char *event);
|
|
|
|
static int seq_timing(struct seq_softc *scp, u_char *event);
|
|
static int seq_local(struct seq_softc *scp, u_char *event);
|
|
|
|
static int seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event);
|
|
static int seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event);
|
|
static int seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event);
|
|
|
|
static int seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md);
|
|
void seq_copytoinput(struct seq_softc *scp, u_char *event, int len);
|
|
int seq_modevent(module_t mod, int type, void *data);
|
|
struct seq_softc *seqs[10];
|
|
static struct mtx seqinfo_mtx;
|
|
static u_long nseq = 0;
|
|
|
|
static void timer_start(struct seq_softc *t);
|
|
static void timer_stop(struct seq_softc *t);
|
|
static void timer_setvals(struct seq_softc *t, int tempo, int timerbase);
|
|
static void timer_wait(struct seq_softc *t, int ticks, int wait_abs);
|
|
static int timer_now(struct seq_softc *t);
|
|
|
|
|
|
static void
|
|
timer_start(struct seq_softc *t)
|
|
{
|
|
t->timerrun = 1;
|
|
getmicrotime(&t->timersub);
|
|
}
|
|
|
|
static void
|
|
timer_continue(struct seq_softc *t)
|
|
{
|
|
struct timeval now;
|
|
|
|
if (t->timerrun == 1)
|
|
return;
|
|
t->timerrun = 1;
|
|
getmicrotime(&now);
|
|
timevalsub(&now, &t->timerstop);
|
|
timevaladd(&t->timersub, &now);
|
|
}
|
|
|
|
static void
|
|
timer_stop(struct seq_softc *t)
|
|
{
|
|
t->timerrun = 0;
|
|
getmicrotime(&t->timerstop);
|
|
}
|
|
|
|
static void
|
|
timer_setvals(struct seq_softc *t, int tempo, int timerbase)
|
|
{
|
|
t->tempo = tempo;
|
|
t->timerbase = timerbase;
|
|
}
|
|
|
|
static void
|
|
timer_wait(struct seq_softc *t, int ticks, int wait_abs)
|
|
{
|
|
struct timeval now, when;
|
|
int ret;
|
|
unsigned long long i;
|
|
|
|
while (t->timerrun == 0) {
|
|
SEQ_DEBUG(2, printf("Timer wait when timer isn't running\n"));
|
|
/*
|
|
* The old sequencer used timeouts that only increased
|
|
* the timer when the timer was running.
|
|
* Hence the sequencer would stick (?) if the
|
|
* timer was disabled.
|
|
*/
|
|
cv_wait(&t->reset_cv, &t->seq_lock);
|
|
if (t->playing == 0)
|
|
return;
|
|
}
|
|
|
|
i = ticks * 60ull * 1000000ull / (t->tempo * t->timerbase);
|
|
|
|
when.tv_sec = i / 1000000;
|
|
when.tv_usec = i % 1000000;
|
|
|
|
#if 0
|
|
printf("timer_wait tempo %d timerbase %d ticks %d abs %d u_sec %llu\n",
|
|
t->tempo, t->timerbase, ticks, wait_abs, i);
|
|
#endif
|
|
|
|
if (wait_abs != 0) {
|
|
getmicrotime(&now);
|
|
timevalsub(&now, &t->timersub);
|
|
timevalsub(&when, &now);
|
|
}
|
|
if (when.tv_sec < 0 || when.tv_usec < 0) {
|
|
SEQ_DEBUG(3,
|
|
printf("seq_timer error negative time %lds.%06lds\n",
|
|
(long)when.tv_sec, (long)when.tv_usec));
|
|
return;
|
|
}
|
|
i = when.tv_sec * 1000000ull;
|
|
i += when.tv_usec;
|
|
i *= hz;
|
|
i /= 1000000ull;
|
|
#if 0
|
|
printf("seq_timer usec %llu ticks %llu\n",
|
|
when.tv_sec * 1000000ull + when.tv_usec, i);
|
|
#endif
|
|
t->waiting = 1;
|
|
ret = cv_timedwait(&t->reset_cv, &t->seq_lock, i + 1);
|
|
t->waiting = 0;
|
|
|
|
if (ret != EWOULDBLOCK)
|
|
SEQ_DEBUG(3, printf("seq_timer didn't timeout\n"));
|
|
|
|
}
|
|
|
|
static int
|
|
timer_now(struct seq_softc *t)
|
|
{
|
|
struct timeval now;
|
|
unsigned long long i;
|
|
int ret;
|
|
|
|
if (t->timerrun == 0)
|
|
now = t->timerstop;
|
|
else
|
|
getmicrotime(&now);
|
|
|
|
timevalsub(&now, &t->timersub);
|
|
|
|
i = now.tv_sec * 1000000ull;
|
|
i += now.tv_usec;
|
|
i *= t->timerbase;
|
|
/* i /= t->tempo; */
|
|
i /= 1000000ull;
|
|
|
|
ret = i;
|
|
/*
|
|
* printf("timer_now: %llu %d\n", i, ret);
|
|
*/
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
seq_eventthread(void *arg)
|
|
{
|
|
struct seq_softc *scp = arg;
|
|
char event[EV_SZ];
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
SEQ_DEBUG(2, printf("seq_eventthread started\n"));
|
|
while (scp->done == 0) {
|
|
restart:
|
|
while (scp->playing == 0) {
|
|
cv_wait(&scp->state_cv, &scp->seq_lock);
|
|
if (scp->done)
|
|
goto done;
|
|
}
|
|
|
|
while (MIDIQ_EMPTY(scp->out_q)) {
|
|
cv_broadcast(&scp->empty_cv);
|
|
cv_wait(&scp->out_cv, &scp->seq_lock);
|
|
if (scp->playing == 0)
|
|
goto restart;
|
|
if (scp->done)
|
|
goto done;
|
|
}
|
|
|
|
MIDIQ_DEQ(scp->out_q, event, EV_SZ);
|
|
|
|
if (MIDIQ_AVAIL(scp->out_q) < scp->out_water) {
|
|
cv_broadcast(&scp->out_cv);
|
|
selwakeup(&scp->out_sel);
|
|
}
|
|
seq_processevent(scp, event);
|
|
}
|
|
|
|
done:
|
|
cv_broadcast(&scp->th_cv);
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(2, printf("seq_eventthread finished\n"));
|
|
#if __FreeBSD_version >= 800002
|
|
kproc_exit(0);
|
|
#else
|
|
mtx_lock(&Giant);
|
|
kthread_exit(0);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* seq_processevent: This maybe called by the event thread or the IOCTL
|
|
* handler for queued and out of band events respectively.
|
|
*/
|
|
static int
|
|
seq_processevent(struct seq_softc *scp, u_char *event)
|
|
{
|
|
int ret;
|
|
kobj_t m;
|
|
|
|
ret = 0;
|
|
|
|
if (event[0] == EV_SEQ_LOCAL)
|
|
ret = seq_local(scp, event);
|
|
else if (event[0] == EV_TIMING)
|
|
ret = seq_timing(scp, event);
|
|
else if (event[0] != EV_CHN_VOICE &&
|
|
event[0] != EV_CHN_COMMON &&
|
|
event[0] != EV_SYSEX &&
|
|
event[0] != SEQ_MIDIPUTC) {
|
|
ret = 1;
|
|
SEQ_DEBUG(2, printf("seq_processevent not known %d\n",
|
|
event[0]));
|
|
} else if (seq_fetch_mid(scp, event[1], &m) != 0) {
|
|
ret = 1;
|
|
SEQ_DEBUG(2, printf("seq_processevent midi unit not found %d\n",
|
|
event[1]));
|
|
} else
|
|
switch (event[0]) {
|
|
case EV_CHN_VOICE:
|
|
ret = seq_chnvoice(scp, m, event);
|
|
break;
|
|
case EV_CHN_COMMON:
|
|
ret = seq_chncommon(scp, m, event);
|
|
break;
|
|
case EV_SYSEX:
|
|
ret = seq_sysex(scp, m, event);
|
|
break;
|
|
case SEQ_MIDIPUTC:
|
|
mtx_unlock(&scp->seq_lock);
|
|
ret = SYNTH_WRITERAW(m, &event[2], 1);
|
|
mtx_lock(&scp->seq_lock);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
seq_addunit(void)
|
|
{
|
|
struct seq_softc *scp;
|
|
int ret;
|
|
u_char *buf;
|
|
|
|
/* Allocate the softc. */
|
|
ret = ENOMEM;
|
|
scp = malloc(sizeof(*scp), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
if (scp == NULL) {
|
|
SEQ_DEBUG(1, printf("seq_addunit: softc allocation failed.\n"));
|
|
goto err;
|
|
}
|
|
kobj_init((kobj_t)scp, &sequencer_class);
|
|
|
|
buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO);
|
|
if (buf == NULL)
|
|
goto err;
|
|
MIDIQ_INIT(scp->in_q, buf, EV_SZ * 1024);
|
|
buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO);
|
|
if (buf == NULL)
|
|
goto err;
|
|
MIDIQ_INIT(scp->out_q, buf, EV_SZ * 1024);
|
|
ret = EINVAL;
|
|
|
|
scp->midis = malloc(sizeof(kobj_t) * 32, M_TEMP, M_NOWAIT | M_ZERO);
|
|
scp->midi_flags = malloc(sizeof(*scp->midi_flags) * 32, M_TEMP,
|
|
M_NOWAIT | M_ZERO);
|
|
|
|
if (scp->midis == NULL || scp->midi_flags == NULL)
|
|
goto err;
|
|
|
|
scp->flags = 0;
|
|
|
|
mtx_init(&scp->seq_lock, "seqflq", NULL, 0);
|
|
cv_init(&scp->state_cv, "seqstate");
|
|
cv_init(&scp->empty_cv, "seqempty");
|
|
cv_init(&scp->reset_cv, "seqtimer");
|
|
cv_init(&scp->out_cv, "seqqout");
|
|
cv_init(&scp->in_cv, "seqqin");
|
|
cv_init(&scp->th_cv, "seqstart");
|
|
|
|
/*
|
|
* Init the damn timer
|
|
*/
|
|
|
|
scp->mapper = midimapper_addseq(scp, &scp->unit, &scp->mapper_cookie);
|
|
if (scp->mapper == NULL)
|
|
goto err;
|
|
|
|
scp->seqdev = make_dev(&seq_cdevsw,
|
|
MIDIMKMINOR(scp->unit, SND_DEV_SEQ, 0), UID_ROOT,
|
|
GID_WHEEL, 0666, "sequencer%d", scp->unit);
|
|
|
|
scp->musicdev = make_dev(&seq_cdevsw,
|
|
MIDIMKMINOR(scp->unit, SND_DEV_MUSIC, 0), UID_ROOT,
|
|
GID_WHEEL, 0666, "music%d", scp->unit);
|
|
|
|
if (scp->seqdev == NULL || scp->musicdev == NULL)
|
|
goto err;
|
|
/*
|
|
* TODO: Add to list of sequencers this module provides
|
|
*/
|
|
|
|
ret =
|
|
#if __FreeBSD_version >= 800002
|
|
kproc_create
|
|
#else
|
|
kthread_create
|
|
#endif
|
|
(seq_eventthread, scp, NULL, RFHIGHPID, 0,
|
|
"sequencer %02d", scp->unit);
|
|
|
|
if (ret)
|
|
goto err;
|
|
|
|
scp->seqdev->si_drv1 = scp->musicdev->si_drv1 = scp;
|
|
|
|
SEQ_DEBUG(2, printf("sequencer %d created scp %p\n", scp->unit, scp));
|
|
|
|
ret = 0;
|
|
|
|
mtx_lock(&seqinfo_mtx);
|
|
seqs[nseq++] = scp;
|
|
mtx_unlock(&seqinfo_mtx);
|
|
|
|
goto ok;
|
|
|
|
err:
|
|
if (scp != NULL) {
|
|
if (scp->seqdev != NULL)
|
|
destroy_dev(scp->seqdev);
|
|
if (scp->musicdev != NULL)
|
|
destroy_dev(scp->musicdev);
|
|
/*
|
|
* TODO: Destroy mutex and cv
|
|
*/
|
|
if (scp->midis != NULL)
|
|
free(scp->midis, M_TEMP);
|
|
if (scp->midi_flags != NULL)
|
|
free(scp->midi_flags, M_TEMP);
|
|
if (scp->out_q.b)
|
|
free(scp->out_q.b, M_TEMP);
|
|
if (scp->in_q.b)
|
|
free(scp->in_q.b, M_TEMP);
|
|
free(scp, M_DEVBUF);
|
|
}
|
|
ok:
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
seq_delunit(int unit)
|
|
{
|
|
struct seq_softc *scp = seqs[unit];
|
|
int i;
|
|
|
|
//SEQ_DEBUG(4, printf("seq_delunit: %d\n", unit));
|
|
SEQ_DEBUG(1, printf("seq_delunit: 1 \n"));
|
|
mtx_lock(&scp->seq_lock);
|
|
|
|
scp->playing = 0;
|
|
scp->done = 1;
|
|
cv_broadcast(&scp->out_cv);
|
|
cv_broadcast(&scp->state_cv);
|
|
cv_broadcast(&scp->reset_cv);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 2 \n"));
|
|
cv_wait(&scp->th_cv, &scp->seq_lock);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 3.0 \n"));
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 3.1 \n"));
|
|
|
|
cv_destroy(&scp->state_cv);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 4 \n"));
|
|
cv_destroy(&scp->empty_cv);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 5 \n"));
|
|
cv_destroy(&scp->reset_cv);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 6 \n"));
|
|
cv_destroy(&scp->out_cv);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 7 \n"));
|
|
cv_destroy(&scp->in_cv);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 8 \n"));
|
|
cv_destroy(&scp->th_cv);
|
|
|
|
SEQ_DEBUG(1, printf("seq_delunit: 10 \n"));
|
|
if (scp->seqdev)
|
|
destroy_dev(scp->seqdev);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 11 \n"));
|
|
if (scp->musicdev)
|
|
destroy_dev(scp->musicdev);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 12 \n"));
|
|
scp->seqdev = scp->musicdev = NULL;
|
|
if (scp->midis != NULL)
|
|
free(scp->midis, M_TEMP);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 13 \n"));
|
|
if (scp->midi_flags != NULL)
|
|
free(scp->midi_flags, M_TEMP);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 14 \n"));
|
|
free(scp->out_q.b, M_TEMP);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 15 \n"));
|
|
free(scp->in_q.b, M_TEMP);
|
|
|
|
SEQ_DEBUG(1, printf("seq_delunit: 16 \n"));
|
|
|
|
mtx_destroy(&scp->seq_lock);
|
|
SEQ_DEBUG(1, printf("seq_delunit: 17 \n"));
|
|
free(scp, M_DEVBUF);
|
|
|
|
mtx_lock(&seqinfo_mtx);
|
|
for (i = unit; i < (nseq - 1); i++)
|
|
seqs[i] = seqs[i + 1];
|
|
nseq--;
|
|
mtx_unlock(&seqinfo_mtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
seq_modevent(module_t mod, int type, void *data)
|
|
{
|
|
int retval, r;
|
|
|
|
retval = 0;
|
|
|
|
switch (type) {
|
|
case MOD_LOAD:
|
|
mtx_init(&seqinfo_mtx, "seqmod", NULL, 0);
|
|
retval = seq_addunit();
|
|
break;
|
|
|
|
case MOD_UNLOAD:
|
|
while (nseq) {
|
|
r = seq_delunit(nseq - 1);
|
|
if (r) {
|
|
retval = r;
|
|
break;
|
|
}
|
|
}
|
|
if (nseq == 0) {
|
|
retval = 0;
|
|
mtx_destroy(&seqinfo_mtx);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md)
|
|
{
|
|
|
|
if (unit > scp->midi_number || unit < 0)
|
|
return EINVAL;
|
|
|
|
*md = scp->midis[unit];
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
seq_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
|
|
{
|
|
struct seq_softc *scp = i_dev->si_drv1;
|
|
int i;
|
|
|
|
if (scp == NULL)
|
|
return ENXIO;
|
|
|
|
SEQ_DEBUG(3, printf("seq_open: scp %p unit %d, flags 0x%x.\n",
|
|
scp, scp->unit, flags));
|
|
|
|
/*
|
|
* Mark this device busy.
|
|
*/
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
if (scp->busy) {
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(2, printf("seq_open: unit %d is busy.\n", scp->unit));
|
|
return EBUSY;
|
|
}
|
|
scp->fflags = flags;
|
|
/*
|
|
if ((scp->fflags & O_NONBLOCK) != 0)
|
|
scp->flags |= SEQ_F_NBIO;
|
|
*/
|
|
scp->music = MIDIDEV(i_dev) == SND_DEV_MUSIC;
|
|
|
|
/*
|
|
* Enumerate the available midi devices
|
|
*/
|
|
scp->midi_number = 0;
|
|
scp->maxunits = midimapper_open(scp->mapper, &scp->mapper_cookie);
|
|
|
|
if (scp->maxunits == 0)
|
|
SEQ_DEBUG(2, printf("seq_open: no midi devices\n"));
|
|
|
|
for (i = 0; i < scp->maxunits; i++) {
|
|
scp->midis[scp->midi_number] =
|
|
midimapper_fetch_synth(scp->mapper, scp->mapper_cookie, i);
|
|
if (scp->midis[scp->midi_number]) {
|
|
if (SYNTH_OPEN(scp->midis[scp->midi_number], scp,
|
|
scp->fflags) != 0)
|
|
scp->midis[scp->midi_number] = NULL;
|
|
else {
|
|
scp->midi_flags[scp->midi_number] =
|
|
SYNTH_QUERY(scp->midis[scp->midi_number]);
|
|
scp->midi_number++;
|
|
}
|
|
}
|
|
}
|
|
|
|
timer_setvals(scp, 60, 100);
|
|
|
|
timer_start(scp);
|
|
timer_stop(scp);
|
|
/*
|
|
* actually, if we're in rdonly mode, we should start the timer
|
|
*/
|
|
/*
|
|
* TODO: Handle recording now
|
|
*/
|
|
|
|
scp->out_water = MIDIQ_SIZE(scp->out_q) / 2;
|
|
|
|
scp->busy = 1;
|
|
mtx_unlock(&scp->seq_lock);
|
|
|
|
SEQ_DEBUG(2, printf("seq_open: opened, mode %s.\n",
|
|
scp->music ? "music" : "sequencer"));
|
|
SEQ_DEBUG(2,
|
|
printf("Sequencer %d %p opened maxunits %d midi_number %d:\n",
|
|
scp->unit, scp, scp->maxunits, scp->midi_number));
|
|
for (i = 0; i < scp->midi_number; i++)
|
|
SEQ_DEBUG(3, printf(" midi %d %p\n", i, scp->midis[i]));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* seq_close
|
|
*/
|
|
int
|
|
seq_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
|
|
{
|
|
int i;
|
|
struct seq_softc *scp = i_dev->si_drv1;
|
|
int ret;
|
|
|
|
if (scp == NULL)
|
|
return ENXIO;
|
|
|
|
SEQ_DEBUG(2, printf("seq_close: unit %d.\n", scp->unit));
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
|
|
ret = ENXIO;
|
|
if (scp->busy == 0)
|
|
goto err;
|
|
|
|
seq_reset(scp);
|
|
seq_sync(scp);
|
|
|
|
for (i = 0; i < scp->midi_number; i++)
|
|
if (scp->midis[i])
|
|
SYNTH_CLOSE(scp->midis[i]);
|
|
|
|
midimapper_close(scp->mapper, scp->mapper_cookie);
|
|
|
|
timer_stop(scp);
|
|
|
|
scp->busy = 0;
|
|
ret = 0;
|
|
|
|
err:
|
|
SEQ_DEBUG(3, printf("seq_close: closed ret = %d.\n", ret));
|
|
mtx_unlock(&scp->seq_lock);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
seq_read(struct cdev *i_dev, struct uio *uio, int ioflag)
|
|
{
|
|
int retval, used;
|
|
struct seq_softc *scp = i_dev->si_drv1;
|
|
|
|
#define SEQ_RSIZE 32
|
|
u_char buf[SEQ_RSIZE];
|
|
|
|
if (scp == NULL)
|
|
return ENXIO;
|
|
|
|
SEQ_DEBUG(7, printf("seq_read: unit %d, resid %d.\n",
|
|
scp->unit, uio->uio_resid));
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
if ((scp->fflags & FREAD) == 0) {
|
|
SEQ_DEBUG(2, printf("seq_read: unit %d is not for reading.\n",
|
|
scp->unit));
|
|
retval = EIO;
|
|
goto err1;
|
|
}
|
|
/*
|
|
* Begin recording.
|
|
*/
|
|
/*
|
|
* if ((scp->flags & SEQ_F_READING) == 0)
|
|
*/
|
|
/*
|
|
* TODO, start recording if not alread
|
|
*/
|
|
|
|
/*
|
|
* I think the semantics are to return as soon
|
|
* as possible.
|
|
* Second thought, it doens't seem like midimoutain
|
|
* expects that at all.
|
|
* TODO: Look up in some sort of spec
|
|
*/
|
|
|
|
while (uio->uio_resid > 0) {
|
|
while (MIDIQ_EMPTY(scp->in_q)) {
|
|
retval = EWOULDBLOCK;
|
|
/*
|
|
* I wish I knew which one to care about
|
|
*/
|
|
|
|
if (scp->fflags & O_NONBLOCK)
|
|
goto err1;
|
|
if (ioflag & O_NONBLOCK)
|
|
goto err1;
|
|
|
|
retval = cv_wait_sig(&scp->in_cv, &scp->seq_lock);
|
|
if (retval == EINTR)
|
|
goto err1;
|
|
}
|
|
|
|
used = MIN(MIDIQ_LEN(scp->in_q), uio->uio_resid);
|
|
used = MIN(used, SEQ_RSIZE);
|
|
|
|
SEQ_DEBUG(8, printf("midiread: uiomove cc=%d\n", used));
|
|
MIDIQ_DEQ(scp->in_q, buf, used);
|
|
retval = uiomove(buf, used, uio);
|
|
if (retval)
|
|
goto err1;
|
|
}
|
|
|
|
retval = 0;
|
|
err1:
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(6, printf("seq_read: ret %d, resid %d.\n",
|
|
retval, uio->uio_resid));
|
|
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
seq_write(struct cdev *i_dev, struct uio *uio, int ioflag)
|
|
{
|
|
u_char event[EV_SZ], newevent[EV_SZ], ev_code;
|
|
struct seq_softc *scp = i_dev->si_drv1;
|
|
int retval;
|
|
int used;
|
|
|
|
SEQ_DEBUG(7, printf("seq_write: unit %d, resid %d.\n",
|
|
scp->unit, uio->uio_resid));
|
|
|
|
if (scp == NULL)
|
|
return ENXIO;
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
|
|
if ((scp->fflags & FWRITE) == 0) {
|
|
SEQ_DEBUG(2, printf("seq_write: unit %d is not for writing.\n",
|
|
scp->unit));
|
|
retval = EIO;
|
|
goto err0;
|
|
}
|
|
while (uio->uio_resid > 0) {
|
|
while (MIDIQ_AVAIL(scp->out_q) == 0) {
|
|
retval = EWOULDBLOCK;
|
|
if (scp->fflags & O_NONBLOCK)
|
|
goto err0;
|
|
if (ioflag & O_NONBLOCK)
|
|
goto err0;
|
|
SEQ_DEBUG(8, printf("seq_write cvwait\n"));
|
|
|
|
scp->playing = 1;
|
|
cv_broadcast(&scp->out_cv);
|
|
cv_broadcast(&scp->state_cv);
|
|
|
|
retval = cv_wait_sig(&scp->out_cv, &scp->seq_lock);
|
|
/*
|
|
* We slept, maybe things have changed since last
|
|
* dying check
|
|
*/
|
|
if (retval == EINTR)
|
|
goto err0;
|
|
#if 0
|
|
/*
|
|
* Useless test
|
|
*/
|
|
if (scp != i_dev->si_drv1)
|
|
retval = ENXIO;
|
|
#endif
|
|
}
|
|
|
|
used = MIN(uio->uio_resid, 4);
|
|
|
|
SEQ_DEBUG(8, printf("seqout: resid %d len %jd avail %jd\n",
|
|
uio->uio_resid, (intmax_t)MIDIQ_LEN(scp->out_q),
|
|
(intmax_t)MIDIQ_AVAIL(scp->out_q)));
|
|
|
|
if (used != 4) {
|
|
retval = ENXIO;
|
|
goto err0;
|
|
}
|
|
retval = uiomove(event, used, uio);
|
|
if (retval)
|
|
goto err0;
|
|
|
|
ev_code = event[0];
|
|
SEQ_DEBUG(8, printf("seq_write: unit %d, event %s.\n",
|
|
scp->unit, midi_cmdname(ev_code, cmdtab_seqevent)));
|
|
|
|
/* Have a look at the event code. */
|
|
if (ev_code == SEQ_FULLSIZE) {
|
|
|
|
/*
|
|
* TODO: restore code for SEQ_FULLSIZE
|
|
*/
|
|
#if 0
|
|
/*
|
|
* A long event, these are the patches/samples for a
|
|
* synthesizer.
|
|
*/
|
|
midiunit = *(u_short *)&event[2];
|
|
mtx_lock(&sd->seq_lock);
|
|
ret = lookup_mididev(scp, midiunit, LOOKUP_OPEN, &md);
|
|
mtx_unlock(&sd->seq_lock);
|
|
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);
|
|
#else
|
|
/*
|
|
* For now, just flush the darn buffer
|
|
*/
|
|
SEQ_DEBUG(2,
|
|
printf("seq_write: SEQ_FULLSIZE flusing buffer.\n"));
|
|
while (uio->uio_resid > 0) {
|
|
retval = uiomove(event, EV_SZ, uio);
|
|
if (retval)
|
|
goto err0;
|
|
|
|
}
|
|
retval = 0;
|
|
goto err0;
|
|
#endif
|
|
}
|
|
retval = EINVAL;
|
|
if (ev_code >= 128) {
|
|
|
|
/*
|
|
* Some sort of an extended event. The size is eight
|
|
* bytes. scoop extra info.
|
|
*/
|
|
if (scp->music && ev_code == SEQ_EXTENDED) {
|
|
SEQ_DEBUG(2, printf("seq_write: invalid level two event %x.\n", ev_code));
|
|
goto err0;
|
|
}
|
|
if (uiomove((caddr_t)&event[4], 4, uio)) {
|
|
SEQ_DEBUG(2,
|
|
printf("seq_write: user memory mangled?\n"));
|
|
goto err0;
|
|
}
|
|
} else {
|
|
/*
|
|
* Size four event.
|
|
*/
|
|
if (scp->music) {
|
|
SEQ_DEBUG(2, printf("seq_write: four byte event in music mode.\n"));
|
|
goto err0;
|
|
}
|
|
}
|
|
if (ev_code == SEQ_MIDIPUTC) {
|
|
/*
|
|
* TODO: event[2] is unit number to receive char.
|
|
* Range check it.
|
|
*/
|
|
}
|
|
if (scp->music) {
|
|
#ifdef not_ever_ever
|
|
if (event[0] == EV_TIMING &&
|
|
(event[1] == TMR_START || event[1] == TMR_STOP)) {
|
|
/*
|
|
* For now, try to make midimoutain work by
|
|
* forcing these events to be processed
|
|
* immediatly.
|
|
*/
|
|
seq_processevent(scp, event);
|
|
} else
|
|
MIDIQ_ENQ(scp->out_q, event, EV_SZ);
|
|
#else
|
|
MIDIQ_ENQ(scp->out_q, event, EV_SZ);
|
|
#endif
|
|
} else {
|
|
if (seq_convertold(event, newevent) > 0)
|
|
MIDIQ_ENQ(scp->out_q, newevent, EV_SZ);
|
|
#if 0
|
|
else
|
|
goto err0;
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
scp->playing = 1;
|
|
cv_broadcast(&scp->state_cv);
|
|
cv_broadcast(&scp->out_cv);
|
|
|
|
retval = 0;
|
|
|
|
err0:
|
|
SEQ_DEBUG(6,
|
|
printf("seq_write done: leftover buffer length %d retval %d\n",
|
|
uio->uio_resid, retval));
|
|
mtx_unlock(&scp->seq_lock);
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
seq_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
|
|
struct thread *td)
|
|
{
|
|
int midiunit, ret, tmp;
|
|
struct seq_softc *scp = i_dev->si_drv1;
|
|
struct synth_info *synthinfo;
|
|
struct midi_info *midiinfo;
|
|
u_char event[EV_SZ];
|
|
u_char newevent[EV_SZ];
|
|
|
|
kobj_t md;
|
|
|
|
/*
|
|
* struct snd_size *sndsize;
|
|
*/
|
|
|
|
if (scp == NULL)
|
|
return ENXIO;
|
|
|
|
SEQ_DEBUG(6, printf("seq_ioctl: unit %d, cmd %s.\n",
|
|
scp->unit, midi_cmdname(cmd, cmdtab_seqioctl)));
|
|
|
|
ret = 0;
|
|
|
|
switch (cmd) {
|
|
case SNDCTL_SEQ_GETTIME:
|
|
/*
|
|
* ioctl needed by libtse
|
|
*/
|
|
mtx_lock(&scp->seq_lock);
|
|
*(int *)arg = timer_now(scp);
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(6, printf("seq_ioctl: gettime %d.\n", *(int *)arg));
|
|
ret = 0;
|
|
break;
|
|
case SNDCTL_TMR_METRONOME:
|
|
/* fallthrough */
|
|
case SNDCTL_TMR_SOURCE:
|
|
/*
|
|
* Not implemented
|
|
*/
|
|
ret = 0;
|
|
break;
|
|
case SNDCTL_TMR_TEMPO:
|
|
event[1] = TMR_TEMPO;
|
|
event[4] = *(int *)arg & 0xFF;
|
|
event[5] = (*(int *)arg >> 8) & 0xFF;
|
|
event[6] = (*(int *)arg >> 16) & 0xFF;
|
|
event[7] = (*(int *)arg >> 24) & 0xFF;
|
|
goto timerevent;
|
|
case SNDCTL_TMR_TIMEBASE:
|
|
event[1] = TMR_TIMERBASE;
|
|
event[4] = *(int *)arg & 0xFF;
|
|
event[5] = (*(int *)arg >> 8) & 0xFF;
|
|
event[6] = (*(int *)arg >> 16) & 0xFF;
|
|
event[7] = (*(int *)arg >> 24) & 0xFF;
|
|
goto timerevent;
|
|
case SNDCTL_TMR_START:
|
|
event[1] = TMR_START;
|
|
goto timerevent;
|
|
case SNDCTL_TMR_STOP:
|
|
event[1] = TMR_STOP;
|
|
goto timerevent;
|
|
case SNDCTL_TMR_CONTINUE:
|
|
event[1] = TMR_CONTINUE;
|
|
timerevent:
|
|
event[0] = EV_TIMING;
|
|
mtx_lock(&scp->seq_lock);
|
|
if (!scp->music) {
|
|
ret = EINVAL;
|
|
mtx_unlock(&scp->seq_lock);
|
|
break;
|
|
}
|
|
seq_processevent(scp, event);
|
|
mtx_unlock(&scp->seq_lock);
|
|
break;
|
|
case SNDCTL_TMR_SELECT:
|
|
SEQ_DEBUG(2,
|
|
printf("seq_ioctl: SNDCTL_TMR_SELECT not supported\n"));
|
|
ret = EINVAL;
|
|
break;
|
|
case SNDCTL_SEQ_SYNC:
|
|
if (mode == O_RDONLY) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
mtx_lock(&scp->seq_lock);
|
|
ret = seq_sync(scp);
|
|
mtx_unlock(&scp->seq_lock);
|
|
break;
|
|
case SNDCTL_SEQ_PANIC:
|
|
/* fallthrough */
|
|
case SNDCTL_SEQ_RESET:
|
|
/*
|
|
* SNDCTL_SEQ_PANIC == SNDCTL_SEQ_RESET
|
|
*/
|
|
mtx_lock(&scp->seq_lock);
|
|
seq_reset(scp);
|
|
mtx_unlock(&scp->seq_lock);
|
|
ret = 0;
|
|
break;
|
|
case SNDCTL_SEQ_TESTMIDI:
|
|
mtx_lock(&scp->seq_lock);
|
|
/*
|
|
* TODO: SNDCTL_SEQ_TESTMIDI now means "can I write to the
|
|
* device?".
|
|
*/
|
|
mtx_unlock(&scp->seq_lock);
|
|
break;
|
|
#if 0
|
|
case SNDCTL_SEQ_GETINCOUNT:
|
|
if (mode == O_WRONLY)
|
|
*(int *)arg = 0;
|
|
else {
|
|
mtx_lock(&scp->seq_lock);
|
|
*(int *)arg = scp->in_q.rl;
|
|
mtx_unlock(&scp->seq_lock);
|
|
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(&scp->seq_lock);
|
|
*(int *)arg = scp->out_q.fl;
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(printf("seq_ioctl: outcount %d.\n",
|
|
*(int *)arg));
|
|
}
|
|
ret = 0;
|
|
break;
|
|
#endif
|
|
case SNDCTL_SEQ_CTRLRATE:
|
|
if (*(int *)arg != 0) {
|
|
ret = EINVAL;
|
|
break;
|
|
}
|
|
mtx_lock(&scp->seq_lock);
|
|
*(int *)arg = scp->timerbase;
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(3, printf("seq_ioctl: ctrlrate %d.\n", *(int *)arg));
|
|
ret = 0;
|
|
break;
|
|
/*
|
|
* TODO: ioctl SNDCTL_SEQ_RESETSAMPLES
|
|
*/
|
|
#if 0
|
|
case SNDCTL_SEQ_RESETSAMPLES:
|
|
mtx_lock(&scp->seq_lock);
|
|
ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md);
|
|
mtx_unlock(&scp->seq_lock);
|
|
if (ret != 0)
|
|
break;
|
|
ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg,
|
|
SND_DEV_MIDIN), cmd, arg, mode, td);
|
|
break;
|
|
#endif
|
|
case SNDCTL_SEQ_NRSYNTHS:
|
|
mtx_lock(&scp->seq_lock);
|
|
*(int *)arg = scp->midi_number;
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(3, printf("seq_ioctl: synths %d.\n", *(int *)arg));
|
|
ret = 0;
|
|
break;
|
|
case SNDCTL_SEQ_NRMIDIS:
|
|
mtx_lock(&scp->seq_lock);
|
|
if (scp->music)
|
|
*(int *)arg = 0;
|
|
else {
|
|
/*
|
|
* TODO: count the numbder of devices that can WRITERAW
|
|
*/
|
|
*(int *)arg = scp->midi_number;
|
|
}
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(3, printf("seq_ioctl: midis %d.\n", *(int *)arg));
|
|
ret = 0;
|
|
break;
|
|
/*
|
|
* TODO: ioctl SNDCTL_SYNTH_MEMAVL
|
|
*/
|
|
#if 0
|
|
case SNDCTL_SYNTH_MEMAVL:
|
|
mtx_lock(&scp->seq_lock);
|
|
ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md);
|
|
mtx_unlock(&scp->seq_lock);
|
|
if (ret != 0)
|
|
break;
|
|
ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg,
|
|
SND_DEV_MIDIN), cmd, arg, mode, td);
|
|
break;
|
|
#endif
|
|
case SNDCTL_SEQ_OUTOFBAND:
|
|
for (ret = 0; ret < EV_SZ; ret++)
|
|
event[ret] = (u_char)arg[0];
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
if (scp->music)
|
|
ret = seq_processevent(scp, event);
|
|
else {
|
|
if (seq_convertold(event, newevent) > 0)
|
|
ret = seq_processevent(scp, newevent);
|
|
else
|
|
ret = EINVAL;
|
|
}
|
|
mtx_unlock(&scp->seq_lock);
|
|
break;
|
|
case SNDCTL_SYNTH_INFO:
|
|
synthinfo = (struct synth_info *)arg;
|
|
midiunit = synthinfo->device;
|
|
mtx_lock(&scp->seq_lock);
|
|
if (seq_fetch_mid(scp, midiunit, &md) == 0) {
|
|
bzero(synthinfo, sizeof(*synthinfo));
|
|
synthinfo->name[0] = 'f';
|
|
synthinfo->name[1] = 'a';
|
|
synthinfo->name[2] = 'k';
|
|
synthinfo->name[3] = 'e';
|
|
synthinfo->name[4] = 's';
|
|
synthinfo->name[5] = 'y';
|
|
synthinfo->name[6] = 'n';
|
|
synthinfo->name[7] = 't';
|
|
synthinfo->name[8] = 'h';
|
|
synthinfo->device = midiunit;
|
|
synthinfo->synth_type = SYNTH_TYPE_MIDI;
|
|
synthinfo->capabilities = scp->midi_flags[midiunit];
|
|
ret = 0;
|
|
} else
|
|
ret = EINVAL;
|
|
mtx_unlock(&scp->seq_lock);
|
|
break;
|
|
case SNDCTL_MIDI_INFO:
|
|
midiinfo = (struct midi_info *)arg;
|
|
midiunit = midiinfo->device;
|
|
mtx_lock(&scp->seq_lock);
|
|
if (seq_fetch_mid(scp, midiunit, &md) == 0) {
|
|
bzero(midiinfo, sizeof(*midiinfo));
|
|
midiinfo->name[0] = 'f';
|
|
midiinfo->name[1] = 'a';
|
|
midiinfo->name[2] = 'k';
|
|
midiinfo->name[3] = 'e';
|
|
midiinfo->name[4] = 'm';
|
|
midiinfo->name[5] = 'i';
|
|
midiinfo->name[6] = 'd';
|
|
midiinfo->name[7] = 'i';
|
|
midiinfo->device = midiunit;
|
|
midiinfo->capabilities = scp->midi_flags[midiunit];
|
|
/*
|
|
* TODO: What devtype?
|
|
*/
|
|
midiinfo->dev_type = 0x01;
|
|
ret = 0;
|
|
} else
|
|
ret = EINVAL;
|
|
mtx_unlock(&scp->seq_lock);
|
|
break;
|
|
case SNDCTL_SEQ_THRESHOLD:
|
|
mtx_lock(&scp->seq_lock);
|
|
RANGE(*(int *)arg, 1, MIDIQ_SIZE(scp->out_q) - 1);
|
|
scp->out_water = *(int *)arg;
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(3, printf("seq_ioctl: water %d.\n", *(int *)arg));
|
|
ret = 0;
|
|
break;
|
|
case SNDCTL_MIDI_PRETIME:
|
|
tmp = *(int *)arg;
|
|
if (tmp < 0)
|
|
tmp = 0;
|
|
mtx_lock(&scp->seq_lock);
|
|
scp->pre_event_timeout = (hz * tmp) / 10;
|
|
*(int *)arg = scp->pre_event_timeout;
|
|
mtx_unlock(&scp->seq_lock);
|
|
SEQ_DEBUG(3, printf("seq_ioctl: pretime %d.\n", *(int *)arg));
|
|
ret = 0;
|
|
break;
|
|
case SNDCTL_FM_4OP_ENABLE:
|
|
case SNDCTL_PMGR_IFACE:
|
|
case SNDCTL_PMGR_ACCESS:
|
|
/*
|
|
* Patch manager and fm are ded, ded, ded.
|
|
*/
|
|
/* fallthrough */
|
|
default:
|
|
/*
|
|
* TODO: Consider ioctl default case.
|
|
* Old code used to
|
|
* if ((scp->fflags & O_ACCMODE) == FREAD) {
|
|
* ret = EIO;
|
|
* break;
|
|
* }
|
|
* Then pass on the ioctl to device 0
|
|
*/
|
|
SEQ_DEBUG(2,
|
|
printf("seq_ioctl: unsupported IOCTL %ld.\n", cmd));
|
|
ret = EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
seq_poll(struct cdev *i_dev, int events, struct thread *td)
|
|
{
|
|
int ret, lim;
|
|
struct seq_softc *scp = i_dev->si_drv1;
|
|
|
|
SEQ_DEBUG(3, printf("seq_poll: unit %d.\n", scp->unit));
|
|
SEQ_DEBUG(1, printf("seq_poll: unit %d.\n", scp->unit));
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
|
|
ret = 0;
|
|
|
|
/* Look up the apropriate queue and select it. */
|
|
if ((events & (POLLOUT | POLLWRNORM)) != 0) {
|
|
/* Start playing. */
|
|
scp->playing = 1;
|
|
cv_broadcast(&scp->state_cv);
|
|
cv_broadcast(&scp->out_cv);
|
|
|
|
lim = scp->out_water;
|
|
|
|
if (MIDIQ_AVAIL(scp->out_q) < lim)
|
|
/* No enough space, record select. */
|
|
selrecord(td, &scp->out_sel);
|
|
else
|
|
/* We can write now. */
|
|
ret |= events & (POLLOUT | POLLWRNORM);
|
|
}
|
|
if ((events & (POLLIN | POLLRDNORM)) != 0) {
|
|
/* TODO: Start recording. */
|
|
|
|
/* Find out the boundary. */
|
|
lim = 1;
|
|
if (MIDIQ_LEN(scp->in_q) < lim)
|
|
/* No data ready, record select. */
|
|
selrecord(td, &scp->in_sel);
|
|
else
|
|
/* We can read now. */
|
|
ret |= events & (POLLIN | POLLRDNORM);
|
|
}
|
|
mtx_unlock(&scp->seq_lock);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
sein_qtr(void *p, void /* mididev_info */ *md)
|
|
{
|
|
struct seq_softc *scp;
|
|
|
|
scp = (struct seq_softc *)p;
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
|
|
/* Restart playing if we have the data to output. */
|
|
if (scp->queueout_pending)
|
|
seq_callback(scp, SEQ_CB_START | SEQ_CB_WR);
|
|
/* Check the midi device if we are reading. */
|
|
if ((scp->flags & SEQ_F_READING) != 0)
|
|
seq_midiinput(scp, md);
|
|
|
|
mtx_unlock(&scp->seq_lock);
|
|
}
|
|
|
|
#endif
|
|
/*
|
|
* seq_convertold
|
|
* Was the old playevent. Use this to convert and old
|
|
* style /dev/sequencer event to a /dev/music event
|
|
*/
|
|
static int
|
|
seq_convertold(u_char *event, u_char *out)
|
|
{
|
|
int used;
|
|
u_char dev, chn, note, vel;
|
|
|
|
out[0] = out[1] = out[2] = out[3] = out[4] = out[5] = out[6] =
|
|
out[7] = 0;
|
|
|
|
dev = 0;
|
|
chn = event[1];
|
|
note = event[2];
|
|
vel = event[3];
|
|
|
|
used = 0;
|
|
|
|
restart:
|
|
/*
|
|
* TODO: Debug statement
|
|
*/
|
|
switch (event[0]) {
|
|
case EV_TIMING:
|
|
case EV_CHN_VOICE:
|
|
case EV_CHN_COMMON:
|
|
case EV_SYSEX:
|
|
case EV_SEQ_LOCAL:
|
|
out[0] = event[0];
|
|
out[1] = event[1];
|
|
out[2] = event[2];
|
|
out[3] = event[3];
|
|
out[4] = event[4];
|
|
out[5] = event[5];
|
|
out[6] = event[6];
|
|
out[7] = event[7];
|
|
used += 8;
|
|
break;
|
|
case SEQ_NOTEOFF:
|
|
out[0] = EV_CHN_VOICE;
|
|
out[1] = dev;
|
|
out[2] = MIDI_NOTEOFF;
|
|
out[3] = chn;
|
|
out[4] = note;
|
|
out[5] = 255;
|
|
used += 4;
|
|
break;
|
|
|
|
case SEQ_NOTEON:
|
|
out[0] = EV_CHN_VOICE;
|
|
out[1] = dev;
|
|
out[2] = MIDI_NOTEON;
|
|
out[3] = chn;
|
|
out[4] = note;
|
|
out[5] = vel;
|
|
used += 4;
|
|
break;
|
|
|
|
/*
|
|
* wait delay = (event[2] << 16) + (event[3] << 8) + event[4]
|
|
*/
|
|
|
|
case SEQ_PGMCHANGE:
|
|
out[0] = EV_CHN_COMMON;
|
|
out[1] = dev;
|
|
out[2] = MIDI_PGM_CHANGE;
|
|
out[3] = chn;
|
|
out[4] = note;
|
|
out[5] = vel;
|
|
used += 4;
|
|
break;
|
|
/*
|
|
out[0] = EV_TIMING;
|
|
out[1] = dev;
|
|
out[2] = MIDI_PGM_CHANGE;
|
|
out[3] = chn;
|
|
out[4] = note;
|
|
out[5] = vel;
|
|
SEQ_DEBUG(4,printf("seq_playevent: synctimer\n"));
|
|
break;
|
|
*/
|
|
|
|
case SEQ_MIDIPUTC:
|
|
SEQ_DEBUG(4,
|
|
printf("seq_playevent: put data 0x%02x, unit %d.\n",
|
|
event[1], event[2]));
|
|
/*
|
|
* Pass through to the midi device.
|
|
* device = event[2]
|
|
* data = event[1]
|
|
*/
|
|
out[0] = SEQ_MIDIPUTC;
|
|
out[1] = dev;
|
|
out[2] = chn;
|
|
used += 4;
|
|
break;
|
|
#ifdef notyet
|
|
case SEQ_ECHO:
|
|
/*
|
|
* This isn't handled here yet because I don't know if I can
|
|
* just use four bytes events. There might be consequences
|
|
* in the _read routing
|
|
*/
|
|
if (seq_copytoinput(scp, event, 4) == EAGAIN) {
|
|
ret = QUEUEFULL;
|
|
break;
|
|
}
|
|
ret = MORE;
|
|
break;
|
|
#endif
|
|
case SEQ_EXTENDED:
|
|
switch (event[1]) {
|
|
case SEQ_NOTEOFF:
|
|
case SEQ_NOTEON:
|
|
case SEQ_PGMCHANGE:
|
|
event++;
|
|
used = 4;
|
|
goto restart;
|
|
break;
|
|
case SEQ_AFTERTOUCH:
|
|
/*
|
|
* SYNTH_AFTERTOUCH(md, event[3], event[4])
|
|
*/
|
|
case SEQ_BALANCE:
|
|
/*
|
|
* SYNTH_PANNING(md, event[3], (char)event[4])
|
|
*/
|
|
case SEQ_CONTROLLER:
|
|
/*
|
|
* SYNTH_CONTROLLER(md, event[3], event[4], *(short *)&event[5])
|
|
*/
|
|
case SEQ_VOLMODE:
|
|
/*
|
|
* SYNTH_VOLUMEMETHOD(md, event[3])
|
|
*/
|
|
default:
|
|
SEQ_DEBUG(2,
|
|
printf("seq_convertold: SEQ_EXTENDED type %d"
|
|
"not handled\n", event[1]));
|
|
break;
|
|
}
|
|
break;
|
|
case SEQ_WAIT:
|
|
out[0] = EV_TIMING;
|
|
out[1] = TMR_WAIT_REL;
|
|
out[4] = event[2];
|
|
out[5] = event[3];
|
|
out[6] = event[4];
|
|
|
|
SEQ_DEBUG(5, printf("SEQ_WAIT %d",
|
|
event[2] + (event[3] << 8) + (event[4] << 24)));
|
|
|
|
used += 4;
|
|
break;
|
|
|
|
case SEQ_ECHO:
|
|
case SEQ_SYNCTIMER:
|
|
case SEQ_PRIVATE:
|
|
default:
|
|
SEQ_DEBUG(2,
|
|
printf("seq_convertold: event type %d not handled %d %d %d\n",
|
|
event[0], event[1], event[2], event[3]));
|
|
break;
|
|
}
|
|
return used;
|
|
}
|
|
|
|
/*
|
|
* Writting to the sequencer buffer never blocks and drops
|
|
* input which cannot be queued
|
|
*/
|
|
void
|
|
seq_copytoinput(struct seq_softc *scp, u_char *event, int len)
|
|
{
|
|
|
|
mtx_assert(&scp->seq_lock, MA_OWNED);
|
|
|
|
if (MIDIQ_AVAIL(scp->in_q) < len) {
|
|
/*
|
|
* ENOROOM? EINPUTDROPPED? ETOUGHLUCK?
|
|
*/
|
|
SEQ_DEBUG(2, printf("seq_copytoinput: queue full\n"));
|
|
} else {
|
|
MIDIQ_ENQ(scp->in_q, event, len);
|
|
selwakeup(&scp->in_sel);
|
|
cv_broadcast(&scp->in_cv);
|
|
}
|
|
|
|
}
|
|
|
|
static int
|
|
seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event)
|
|
{
|
|
int ret, voice;
|
|
u_char cmd, chn, note, parm;
|
|
|
|
ret = 0;
|
|
cmd = event[2];
|
|
chn = event[3];
|
|
note = event[4];
|
|
parm = event[5];
|
|
|
|
mtx_assert(&scp->seq_lock, MA_OWNED);
|
|
|
|
SEQ_DEBUG(5, printf("seq_chnvoice: unit %d, dev %d, cmd %s,"
|
|
" chn %d, note %d, parm %d.\n", scp->unit, event[1],
|
|
midi_cmdname(cmd, cmdtab_seqcv), chn, note, parm));
|
|
|
|
voice = SYNTH_ALLOC(md, chn, note);
|
|
|
|
mtx_unlock(&scp->seq_lock);
|
|
|
|
switch (cmd) {
|
|
case MIDI_NOTEON:
|
|
if (note < 128 || note == 255) {
|
|
#if 0
|
|
if (scp->music && chn == 9) {
|
|
/*
|
|
* This channel is a percussion. The note
|
|
* number is the patch number.
|
|
*/
|
|
/*
|
|
mtx_unlock(&scp->seq_lock);
|
|
if (SYNTH_SETINSTR(md, voice, 128 + note)
|
|
== EAGAIN) {
|
|
mtx_lock(&scp->seq_lock);
|
|
return (QUEUEFULL);
|
|
}
|
|
mtx_lock(&scp->seq_lock);
|
|
*/
|
|
note = 60; /* Middle C. */
|
|
}
|
|
#endif
|
|
if (scp->music) {
|
|
/*
|
|
mtx_unlock(&scp->seq_lock);
|
|
if (SYNTH_SETUPVOICE(md, voice, chn)
|
|
== EAGAIN) {
|
|
mtx_lock(&scp->seq_lock);
|
|
return (QUEUEFULL);
|
|
}
|
|
mtx_lock(&scp->seq_lock);
|
|
*/
|
|
}
|
|
SYNTH_STARTNOTE(md, voice, note, parm);
|
|
}
|
|
break;
|
|
case MIDI_NOTEOFF:
|
|
SYNTH_KILLNOTE(md, voice, note, parm);
|
|
break;
|
|
case MIDI_KEY_PRESSURE:
|
|
SYNTH_AFTERTOUCH(md, voice, parm);
|
|
break;
|
|
default:
|
|
ret = 1;
|
|
SEQ_DEBUG(2, printf("seq_chnvoice event type %d not handled\n",
|
|
event[1]));
|
|
break;
|
|
}
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event)
|
|
{
|
|
int ret;
|
|
u_short w14;
|
|
u_char cmd, chn, p1;
|
|
|
|
ret = 0;
|
|
cmd = event[2];
|
|
chn = event[3];
|
|
p1 = event[4];
|
|
w14 = *(u_short *)&event[6];
|
|
|
|
SEQ_DEBUG(5, printf("seq_chncommon: unit %d, dev %d, cmd %s, chn %d,"
|
|
" p1 %d, w14 %d.\n", scp->unit, event[1],
|
|
midi_cmdname(cmd, cmdtab_seqccmn), chn, p1, w14));
|
|
mtx_unlock(&scp->seq_lock);
|
|
switch (cmd) {
|
|
case MIDI_PGM_CHANGE:
|
|
SEQ_DEBUG(4, printf("seq_chncommon pgmchn chn %d pg %d\n",
|
|
chn, p1));
|
|
SYNTH_SETINSTR(md, chn, p1);
|
|
break;
|
|
case MIDI_CTL_CHANGE:
|
|
SEQ_DEBUG(4, printf("seq_chncommon ctlch chn %d pg %d %d\n",
|
|
chn, p1, w14));
|
|
SYNTH_CONTROLLER(md, chn, p1, w14);
|
|
break;
|
|
case MIDI_PITCH_BEND:
|
|
if (scp->music) {
|
|
/*
|
|
* TODO: MIDI_PITCH_BEND
|
|
*/
|
|
#if 0
|
|
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(&scp->seq_lock);
|
|
if (md->synth.bender(md, i, w14) == EAGAIN) {
|
|
mtx_lock(&scp->seq_lock);
|
|
return (QUEUEFULL);
|
|
}
|
|
mtx_lock(&scp->seq_lock);
|
|
}
|
|
} else {
|
|
mtx_unlock(&md->synth.vc_mtx);
|
|
mtx_unlock(&scp->seq_lock);
|
|
if (md->synth.bender(md, chn, w14) == EAGAIN) {
|
|
mtx_lock(&scp->seq_lock);
|
|
return (QUEUEFULL);
|
|
}
|
|
mtx_lock(&scp->seq_lock);
|
|
}
|
|
#endif
|
|
} else
|
|
SYNTH_BENDER(md, chn, w14);
|
|
break;
|
|
default:
|
|
ret = 1;
|
|
SEQ_DEBUG(2,
|
|
printf("seq_chncommon event type %d not handled.\n",
|
|
event[1]));
|
|
break;
|
|
|
|
}
|
|
mtx_lock(&scp->seq_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
seq_timing(struct seq_softc *scp, u_char *event)
|
|
{
|
|
int param;
|
|
int ret;
|
|
|
|
ret = 0;
|
|
param = event[4] + (event[5] << 8) +
|
|
(event[6] << 16) + (event[7] << 24);
|
|
|
|
SEQ_DEBUG(5, printf("seq_timing: unit %d, cmd %d, param %d.\n",
|
|
scp->unit, event[1], param));
|
|
switch (event[1]) {
|
|
case TMR_WAIT_REL:
|
|
timer_wait(scp, param, 0);
|
|
break;
|
|
case TMR_WAIT_ABS:
|
|
timer_wait(scp, param, 1);
|
|
break;
|
|
case TMR_START:
|
|
timer_start(scp);
|
|
cv_broadcast(&scp->reset_cv);
|
|
break;
|
|
case TMR_STOP:
|
|
timer_stop(scp);
|
|
/*
|
|
* The following cv_broadcast isn't needed since we only
|
|
* wait for 0->1 transitions. It probably won't hurt
|
|
*/
|
|
cv_broadcast(&scp->reset_cv);
|
|
break;
|
|
case TMR_CONTINUE:
|
|
timer_continue(scp);
|
|
cv_broadcast(&scp->reset_cv);
|
|
break;
|
|
case TMR_TEMPO:
|
|
if (param < 8)
|
|
param = 8;
|
|
if (param > 360)
|
|
param = 360;
|
|
SEQ_DEBUG(4, printf("Timer set tempo %d\n", param));
|
|
timer_setvals(scp, param, scp->timerbase);
|
|
break;
|
|
case TMR_TIMERBASE:
|
|
if (param < 1)
|
|
param = 1;
|
|
if (param > 1000)
|
|
param = 1000;
|
|
SEQ_DEBUG(4, printf("Timer set timerbase %d\n", param));
|
|
timer_setvals(scp, scp->tempo, param);
|
|
break;
|
|
case TMR_ECHO:
|
|
/*
|
|
* TODO: Consider making 4-byte events for /dev/sequencer
|
|
* PRO: Maybe needed by legacy apps
|
|
* CON: soundcard.h has been warning for a while many years
|
|
* to expect 8 byte events.
|
|
*/
|
|
#if 0
|
|
if (scp->music)
|
|
seq_copytoinput(scp, event, 8);
|
|
else {
|
|
param = (param << 8 | SEQ_ECHO);
|
|
seq_copytoinput(scp, (u_char *)¶m, 4);
|
|
}
|
|
#else
|
|
seq_copytoinput(scp, event, 8);
|
|
#endif
|
|
break;
|
|
default:
|
|
SEQ_DEBUG(2, printf("seq_timing event type %d not handled.\n",
|
|
event[1]));
|
|
ret = 1;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
seq_local(struct seq_softc *scp, u_char *event)
|
|
{
|
|
int ret;
|
|
|
|
ret = 0;
|
|
mtx_assert(&scp->seq_lock, MA_OWNED);
|
|
|
|
SEQ_DEBUG(5, printf("seq_local: unit %d, cmd %d\n", scp->unit,
|
|
event[1]));
|
|
switch (event[1]) {
|
|
default:
|
|
SEQ_DEBUG(1, printf("seq_local event type %d not handled\n",
|
|
event[1]));
|
|
ret = 1;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event)
|
|
{
|
|
int i, l;
|
|
|
|
mtx_assert(&scp->seq_lock, MA_OWNED);
|
|
SEQ_DEBUG(5, printf("seq_sysex: unit %d device %d\n", scp->unit,
|
|
event[1]));
|
|
l = 0;
|
|
for (i = 0; i < 6 && event[i + 2] != 0xff; i++)
|
|
l = i + 1;
|
|
if (l > 0) {
|
|
mtx_unlock(&scp->seq_lock);
|
|
if (SYNTH_SENDSYSEX(md, &event[2], l) == EAGAIN) {
|
|
mtx_lock(&scp->seq_lock);
|
|
return 1;
|
|
}
|
|
mtx_lock(&scp->seq_lock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Reset no longer closes the raw devices nor seq_sync's
|
|
* Callers are IOCTL and seq_close
|
|
*/
|
|
static void
|
|
seq_reset(struct seq_softc *scp)
|
|
{
|
|
int chn, i;
|
|
kobj_t m;
|
|
|
|
mtx_assert(&scp->seq_lock, MA_OWNED);
|
|
|
|
SEQ_DEBUG(5, printf("seq_reset: unit %d.\n", scp->unit));
|
|
|
|
/*
|
|
* Stop reading and writing.
|
|
*/
|
|
|
|
/* scp->recording = 0; */
|
|
scp->playing = 0;
|
|
cv_broadcast(&scp->state_cv);
|
|
cv_broadcast(&scp->out_cv);
|
|
cv_broadcast(&scp->reset_cv);
|
|
|
|
/*
|
|
* For now, don't reset the timers.
|
|
*/
|
|
MIDIQ_CLEAR(scp->in_q);
|
|
MIDIQ_CLEAR(scp->out_q);
|
|
|
|
for (i = 0; i < scp->midi_number; i++) {
|
|
m = scp->midis[i];
|
|
mtx_unlock(&scp->seq_lock);
|
|
SYNTH_RESET(m);
|
|
for (chn = 0; chn < 16; chn++) {
|
|
SYNTH_CONTROLLER(m, chn, 123, 0);
|
|
SYNTH_CONTROLLER(m, chn, 121, 0);
|
|
SYNTH_BENDER(m, chn, 1 << 13);
|
|
}
|
|
mtx_lock(&scp->seq_lock);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* seq_sync
|
|
* *really* flush the output queue
|
|
* flush the event queue, then flush the synthsisers.
|
|
* Callers are IOCTL and close
|
|
*/
|
|
|
|
#define SEQ_SYNC_TIMEOUT 8
|
|
static int
|
|
seq_sync(struct seq_softc *scp)
|
|
{
|
|
int i, rl, sync[16], done;
|
|
|
|
mtx_assert(&scp->seq_lock, MA_OWNED);
|
|
|
|
SEQ_DEBUG(4, printf("seq_sync: unit %d.\n", scp->unit));
|
|
|
|
/*
|
|
* Wait until output queue is empty. Check every so often to see if
|
|
* the queue is moving along. If it isn't just abort.
|
|
*/
|
|
while (!MIDIQ_EMPTY(scp->out_q)) {
|
|
|
|
if (!scp->playing) {
|
|
scp->playing = 1;
|
|
cv_broadcast(&scp->state_cv);
|
|
cv_broadcast(&scp->out_cv);
|
|
}
|
|
rl = MIDIQ_LEN(scp->out_q);
|
|
|
|
i = cv_timedwait_sig(&scp->out_cv,
|
|
&scp->seq_lock, SEQ_SYNC_TIMEOUT * hz);
|
|
|
|
if (i == EINTR || i == ERESTART) {
|
|
if (i == EINTR) {
|
|
/*
|
|
* XXX: I don't know why we stop playing
|
|
*/
|
|
scp->playing = 0;
|
|
cv_broadcast(&scp->out_cv);
|
|
}
|
|
return i;
|
|
}
|
|
if (i == EWOULDBLOCK && rl == MIDIQ_LEN(scp->out_q) &&
|
|
scp->waiting == 0) {
|
|
/*
|
|
* A queue seems to be stuck up. Give up and clear
|
|
* queues.
|
|
*/
|
|
MIDIQ_CLEAR(scp->out_q);
|
|
scp->playing = 0;
|
|
cv_broadcast(&scp->state_cv);
|
|
cv_broadcast(&scp->out_cv);
|
|
cv_broadcast(&scp->reset_cv);
|
|
|
|
/*
|
|
* TODO: Consider if the raw devices need to be flushed
|
|
*/
|
|
|
|
SEQ_DEBUG(1, printf("seq_sync queue stuck, aborting\n"));
|
|
|
|
return i;
|
|
}
|
|
}
|
|
|
|
scp->playing = 0;
|
|
/*
|
|
* Since syncing a midi device might block, unlock scp->seq_lock.
|
|
*/
|
|
|
|
mtx_unlock(&scp->seq_lock);
|
|
for (i = 0; i < scp->midi_number; i++)
|
|
sync[i] = 1;
|
|
|
|
do {
|
|
done = 1;
|
|
for (i = 0; i < scp->midi_number; i++)
|
|
if (sync[i]) {
|
|
if (SYNTH_INSYNC(scp->midis[i]) == 0)
|
|
sync[i] = 0;
|
|
else
|
|
done = 0;
|
|
}
|
|
if (!done)
|
|
DELAY(5000);
|
|
|
|
} while (!done);
|
|
|
|
mtx_lock(&scp->seq_lock);
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
midi_cmdname(int cmd, midi_cmdtab *tab)
|
|
{
|
|
while (tab->name != NULL) {
|
|
if (cmd == tab->cmd)
|
|
return (tab->name);
|
|
tab++;
|
|
}
|
|
|
|
return ("unknown");
|
|
}
|