freebsd-nq/sys/dev/sound/midi/sequencer.c

2098 lines
46 KiB
C
Raw Normal View History

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* 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>
2007-02-25 13:51:52 +00:00
#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>
2007-02-25 13:51:52 +00:00
#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>
Sound Mega-commit. Expect further cleanup until code freeze. 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.
2009-06-07 19:12:08 +00:00
#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
2007-02-25 13:51:52 +00:00
#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 mseq_open;
static d_close_t mseq_close;
static d_ioctl_t mseq_ioctl;
static d_read_t mseq_read;
static d_write_t mseq_write;
static d_poll_t mseq_poll;
static struct cdevsw seq_cdevsw = {
.d_version = D_VERSION,
.d_open = mseq_open,
.d_close = mseq_close,
.d_read = mseq_read,
.d_write = mseq_write,
.d_ioctl = mseq_ioctl,
.d_poll = mseq_poll,
2007-02-25 13:51:52 +00:00
.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;
2007-02-25 13:51:52 +00:00
u_long flags;
/* Flags (protected by flag_mtx of mididev_info) */
2007-02-25 13:51:52 +00:00
int fflags; /* Access mode */
int music;
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
int midi_number;
struct cdev *seqdev, *musicdev;
2007-02-25 13:51:52 +00:00
int unit;
int maxunits;
kobj_t *midis;
2007-02-25 13:51:52 +00:00
int *midi_flags;
kobj_t mapper;
void *mapper_cookie;
struct timeval timerstop, timersub;
2007-02-25 13:51:52 +00:00
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},
};
Sound Mega-commit. Expect further cleanup until code freeze. 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.
2009-06-07 19:12:08 +00:00
#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), */
Sound Mega-commit. Expect further cleanup until code freeze. 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.
2009-06-07 19:12:08 +00:00
KOBJMETHOD_END
};
2007-02-25 13:51:52 +00:00
DEFINE_CLASS(sequencer, seq_methods, 0);
/* The followings are the local function. */
static int seq_convertold(u_char *event, u_char *out);
2007-02-25 13:51:52 +00:00
/*
* static void seq_midiinput(struct seq_softc * scp, void *md);
*/
2007-02-25 13:51:52 +00:00
static void seq_reset(struct seq_softc *scp);
static int seq_sync(struct seq_softc *scp);
2007-02-25 13:51:52 +00:00
static int seq_processevent(struct seq_softc *scp, u_char *event);
2007-02-25 13:51:52 +00:00
static int seq_timing(struct seq_softc *scp, u_char *event);
static int seq_local(struct seq_softc *scp, u_char *event);
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
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];
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
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) {
2007-02-25 13:51:52 +00:00
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
2007-02-25 13:51:52 +00:00
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) {
2007-02-25 13:51:52 +00:00
getmicrotime(&now);
timevalsub(&now, &t->timersub);
timevalsub(&when, &now);
}
if (when.tv_sec < 0 || when.tv_usec < 0) {
2007-02-25 13:51:52 +00:00
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
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
now = t->timerstop;
else
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(2, printf("seq_eventthread started\n"));
while (scp->done == 0) {
restart:
2007-02-25 13:51:52 +00:00
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;
}
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(2, printf("seq_eventthread finished\n"));
kproc_exit(0);
}
/*
* 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 &&
2007-02-25 13:51:52 +00:00
event[0] != EV_CHN_COMMON &&
event[0] != EV_SYSEX &&
event[0] != SEQ_MIDIPUTC) {
ret = 1;
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(2, printf("seq_processevent not known %d\n",
event[0]));
} else if (seq_fetch_mid(scp, event[1], &m) != 0) {
ret = 1;
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
}
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) {
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(1, printf("seq_addunit: softc allocation failed.\n"));
goto err;
}
2007-02-25 13:51:52 +00:00
kobj_init((kobj_t)scp, &sequencer_class);
buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO);
if (buf == NULL)
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
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,
2007-02-25 13:51:52 +00:00
M_NOWAIT | M_ZERO);
2007-02-25 13:51:52 +00:00
if (scp->midis == NULL || scp->midi_flags == NULL)
goto err;
scp->flags = 0;
2007-03-15 14:57:54 +00:00
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)
2007-02-25 13:51:52 +00:00
goto err;
scp->seqdev = make_dev(&seq_cdevsw,
2007-02-25 13:51:52 +00:00
MIDIMKMINOR(scp->unit, SND_DEV_SEQ, 0), UID_ROOT,
GID_WHEEL, 0666, "sequencer%d", scp->unit);
scp->musicdev = make_dev(&seq_cdevsw,
2007-02-25 13:51:52 +00:00
MIDIMKMINOR(scp->unit, SND_DEV_MUSIC, 0), UID_ROOT,
GID_WHEEL, 0666, "music%d", scp->unit);
if (scp->seqdev == NULL || scp->musicdev == NULL)
2007-02-25 13:51:52 +00:00
goto err;
/*
* TODO: Add to list of sequencers this module provides
*/
Sound Mega-commit. Expect further cleanup until code freeze. 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.
2009-06-07 19:12:08 +00:00
ret =
kproc_create
(seq_eventthread, scp, NULL, RFHIGHPID, 0,
2007-02-25 13:51:52 +00:00
"sequencer %02d", scp->unit);
if (ret)
2007-02-25 13:51:52 +00:00
goto err;
scp->seqdev->si_drv1 = scp->musicdev->si_drv1 = scp;
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(2, printf("sequencer %d created scp %p\n", scp->unit, scp));
ret = 0;
mtx_lock(&seqinfo_mtx);
2007-02-25 13:51:52 +00:00
seqs[nseq++] = scp;
mtx_unlock(&seqinfo_mtx);
goto ok;
err:
if (scp != NULL) {
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
//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)
2007-02-25 13:51:52 +00:00
destroy_dev(scp->seqdev);
SEQ_DEBUG(1, printf("seq_delunit: 11 \n"));
if (scp->musicdev)
2007-02-25 13:51:52 +00:00
destroy_dev(scp->musicdev);
SEQ_DEBUG(1, printf("seq_delunit: 12 \n"));
scp->seqdev = scp->musicdev = NULL;
if (scp->midis != NULL)
2007-02-25 13:51:52 +00:00
free(scp->midis, M_TEMP);
SEQ_DEBUG(1, printf("seq_delunit: 13 \n"));
if (scp->midi_flags != NULL)
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
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) {
2007-02-25 13:51:52 +00:00
case MOD_LOAD:
2007-03-15 14:57:54 +00:00
mtx_init(&seqinfo_mtx, "seqmod", NULL, 0);
retval = seq_addunit();
break;
2007-02-25 13:51:52 +00:00
case MOD_UNLOAD:
while (nseq) {
2007-02-25 13:51:52 +00:00
r = seq_delunit(nseq - 1);
if (r) {
retval = r;
break;
}
}
2007-02-25 13:51:52 +00:00
if (nseq == 0) {
retval = 0;
mtx_destroy(&seqinfo_mtx);
}
break;
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
return EINVAL;
*md = scp->midis[unit];
return 0;
}
int
mseq_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)
2007-02-25 13:51:52 +00:00
return ENXIO;
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(3, printf("seq_open: scp %p unit %d, flags 0x%x.\n",
scp, scp->unit, flags));
/*
2007-02-25 13:51:52 +00:00
* Mark this device busy.
*/
mtx_lock(&scp->seq_lock);
if (scp->busy) {
mtx_unlock(&scp->seq_lock);
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
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;
}
/*
* mseq_close
*/
int
mseq_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)
2007-02-25 13:51:52 +00:00
return ENXIO;
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(2, printf("seq_close: unit %d.\n", scp->unit));
mtx_lock(&scp->seq_lock);
ret = ENXIO;
if (scp->busy == 0)
2007-02-25 13:51:52 +00:00
goto err;
seq_reset(scp);
seq_sync(scp);
2007-02-25 13:51:52 +00:00
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:
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(3, printf("seq_close: closed ret = %d.\n", ret));
mtx_unlock(&scp->seq_lock);
return ret;
}
int
mseq_read(struct cdev *i_dev, struct uio *uio, int ioflag)
{
int retval, used;
struct seq_softc *scp = i_dev->si_drv1;
2007-02-25 13:51:52 +00:00
#define SEQ_RSIZE 32
u_char buf[SEQ_RSIZE];
if (scp == NULL)
2007-02-25 13:51:52 +00:00
return ENXIO;
SEQ_DEBUG(7, printf("mseq_read: unit %d, resid %zd.\n",
2007-02-25 13:51:52 +00:00
scp->unit, uio->uio_resid));
mtx_lock(&scp->seq_lock);
if ((scp->fflags & FREAD) == 0) {
SEQ_DEBUG(2, printf("mseq_read: unit %d is not for reading.\n",
2007-02-25 13:51:52 +00:00
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) {
2007-02-25 13:51:52 +00:00
while (MIDIQ_EMPTY(scp->in_q)) {
retval = EWOULDBLOCK;
/*
* I wish I knew which one to care about
*/
2007-02-25 13:51:52 +00:00
if (scp->fflags & O_NONBLOCK)
goto err1;
if (ioflag & O_NONBLOCK)
goto err1;
2007-02-25 13:51:52 +00:00
retval = cv_wait_sig(&scp->in_cv, &scp->seq_lock);
if (retval == EINTR)
goto err1;
}
2007-02-25 13:51:52 +00:00
used = MIN(MIDIQ_LEN(scp->in_q), uio->uio_resid);
used = MIN(used, SEQ_RSIZE);
2007-02-25 13:51:52 +00:00
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("mseq_read: ret %d, resid %zd.\n",
2007-02-25 13:51:52 +00:00
retval, uio->uio_resid));
return retval;
}
int
mseq_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 %zd.\n",
2007-02-25 13:51:52 +00:00
scp->unit, uio->uio_resid));
if (scp == NULL)
2007-02-25 13:51:52 +00:00
return ENXIO;
mtx_lock(&scp->seq_lock);
if ((scp->fflags & FWRITE) == 0) {
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(2, printf("seq_write: unit %d is not for writing.\n",
scp->unit));
retval = EIO;
goto err0;
}
while (uio->uio_resid > 0) {
2007-02-25 13:51:52 +00:00
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
2007-02-25 13:51:52 +00:00
/*
* Useless test
*/
if (scp != i_dev->si_drv1)
retval = ENXIO;
#endif
2007-02-25 13:51:52 +00:00
}
2007-02-25 13:51:52 +00:00
used = MIN(uio->uio_resid, 4);
SEQ_DEBUG(8, printf("seqout: resid %zd len %jd avail %jd\n",
2007-02-25 13:51:52 +00:00
uio->uio_resid, (intmax_t)MIDIQ_LEN(scp->out_q),
(intmax_t)MIDIQ_AVAIL(scp->out_q)));
2007-02-25 13:51:52 +00:00
if (used != 4) {
retval = ENXIO;
goto err0;
}
retval = uiomove(event, used, uio);
if (retval)
goto err0;
2007-02-25 13:51:52 +00:00
ev_code = event[0];
SEQ_DEBUG(8, printf("seq_write: unit %d, event %s.\n",
scp->unit, midi_cmdname(ev_code, cmdtab_seqevent)));
2007-02-25 13:51:52 +00:00
/* Have a look at the event code. */
if (ev_code == SEQ_FULLSIZE) {
2007-02-25 13:51:52 +00:00
/*
* TODO: restore code for SEQ_FULLSIZE
*/
#if 0
2007-02-25 13:51:52 +00:00
/*
* 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
2007-02-25 13:51:52 +00:00
/*
* 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;
2007-02-25 13:51:52 +00:00
}
retval = 0;
goto err0;
#endif
2007-02-25 13:51:52 +00:00
}
retval = EINVAL;
if (ev_code >= 128) {
2007-02-25 13:51:52 +00:00
/*
* 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;
}
}
2007-02-25 13:51:52 +00:00
if (ev_code == SEQ_MIDIPUTC) {
/*
* TODO: event[2] is unit number to receive char.
* Range check it.
*/
}
if (scp->music) {
2006-05-29 17:37:41 +00:00
#ifdef not_ever_ever
2007-02-25 13:51:52 +00:00
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
2007-02-25 13:51:52 +00:00
MIDIQ_ENQ(scp->out_q, event, EV_SZ);
#endif
2007-02-25 13:51:52 +00:00
} else {
if (seq_convertold(event, newevent) > 0)
MIDIQ_ENQ(scp->out_q, newevent, EV_SZ);
#if 0
2007-02-25 13:51:52 +00:00
else
goto err0;
#endif
2007-02-25 13:51:52 +00:00
}
}
scp->playing = 1;
cv_broadcast(&scp->state_cv);
cv_broadcast(&scp->out_cv);
retval = 0;
err0:
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(6,
printf("seq_write done: leftover buffer length %zd retval %d\n",
2007-02-25 13:51:52 +00:00
uio->uio_resid, retval));
mtx_unlock(&scp->seq_lock);
return retval;
}
int
mseq_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
/*
* struct snd_size *sndsize;
*/
if (scp == NULL)
2007-02-25 13:51:52 +00:00
return ENXIO;
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(6, printf("seq_ioctl: unit %d, cmd %s.\n",
scp->unit, midi_cmdname(cmd, cmdtab_seqioctl)));
ret = 0;
switch (cmd) {
2007-02-25 13:51:52 +00:00
case SNDCTL_SEQ_GETTIME:
/*
* ioctl needed by libtse
*/
mtx_lock(&scp->seq_lock);
*(int *)arg = timer_now(scp);
mtx_unlock(&scp->seq_lock);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(6, printf("seq_ioctl: gettime %d.\n", *(int *)arg));
ret = 0;
break;
2007-02-25 13:51:52 +00:00
case SNDCTL_TMR_METRONOME:
/* fallthrough */
2007-02-25 13:51:52 +00:00
case SNDCTL_TMR_SOURCE:
/*
* Not implemented
*/
ret = 0;
break;
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
case SNDCTL_TMR_START:
event[1] = TMR_START;
goto timerevent;
2007-02-25 13:51:52 +00:00
case SNDCTL_TMR_STOP:
event[1] = TMR_STOP;
goto timerevent;
2007-02-25 13:51:52 +00:00
case SNDCTL_TMR_CONTINUE:
event[1] = TMR_CONTINUE;
2007-02-25 13:51:52 +00:00
timerevent:
event[0] = EV_TIMING;
mtx_lock(&scp->seq_lock);
if (!scp->music) {
2007-02-25 13:51:52 +00:00
ret = EINVAL;
mtx_unlock(&scp->seq_lock);
break;
}
seq_processevent(scp, event);
mtx_unlock(&scp->seq_lock);
break;
2007-02-25 13:51:52 +00:00
case SNDCTL_TMR_SELECT:
SEQ_DEBUG(2,
printf("seq_ioctl: SNDCTL_TMR_SELECT not supported\n"));
ret = EINVAL;
break;
2007-02-25 13:51:52 +00:00
case SNDCTL_SEQ_SYNC:
if (mode == O_RDONLY) {
2007-02-25 13:51:52 +00:00
ret = 0;
break;
}
mtx_lock(&scp->seq_lock);
ret = seq_sync(scp);
mtx_unlock(&scp->seq_lock);
break;
2007-02-25 13:51:52 +00:00
case SNDCTL_SEQ_PANIC:
/* fallthrough */
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
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
2007-02-25 13:51:52 +00:00
case SNDCTL_SEQ_GETINCOUNT:
if (mode == O_WRONLY)
2007-02-25 13:51:52 +00:00
*(int *)arg = 0;
else {
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
case SNDCTL_SEQ_GETOUTCOUNT:
if (mode == O_RDONLY)
2007-02-25 13:51:52 +00:00
*(int *)arg = 0;
else {
2007-02-25 13:51:52 +00:00
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
2007-02-25 13:51:52 +00:00
case SNDCTL_SEQ_CTRLRATE:
if (*(int *)arg != 0) {
2007-02-25 13:51:52 +00:00
ret = EINVAL;
break;
}
mtx_lock(&scp->seq_lock);
*(int *)arg = scp->timerbase;
mtx_unlock(&scp->seq_lock);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(3, printf("seq_ioctl: ctrlrate %d.\n", *(int *)arg));
ret = 0;
break;
/*
* TODO: ioctl SNDCTL_SEQ_RESETSAMPLES
*/
#if 0
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
break;
ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg,
SND_DEV_MIDIN), cmd, arg, mode, td);
break;
#endif
2007-02-25 13:51:52 +00:00
case SNDCTL_SEQ_NRSYNTHS:
mtx_lock(&scp->seq_lock);
2007-02-25 13:51:52 +00:00
*(int *)arg = scp->midi_number;
mtx_unlock(&scp->seq_lock);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(3, printf("seq_ioctl: synths %d.\n", *(int *)arg));
ret = 0;
break;
2007-02-25 13:51:52 +00:00
case SNDCTL_SEQ_NRMIDIS:
mtx_lock(&scp->seq_lock);
if (scp->music)
2007-02-25 13:51:52 +00:00
*(int *)arg = 0;
else {
2007-02-25 13:51:52 +00:00
/*
* TODO: count the numbder of devices that can WRITERAW
*/
*(int *)arg = scp->midi_number;
}
mtx_unlock(&scp->seq_lock);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(3, printf("seq_ioctl: midis %d.\n", *(int *)arg));
ret = 0;
break;
/*
* TODO: ioctl SNDCTL_SYNTH_MEMAVL
*/
#if 0
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
break;
ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg,
SND_DEV_MIDIN), cmd, arg, mode, td);
break;
#endif
2007-02-25 13:51:52 +00:00
case SNDCTL_SEQ_OUTOFBAND:
for (ret = 0; ret < EV_SZ; ret++)
2007-02-25 13:51:52 +00:00
event[ret] = (u_char)arg[0];
mtx_lock(&scp->seq_lock);
if (scp->music)
2007-02-25 13:51:52 +00:00
ret = seq_processevent(scp, event);
else {
2007-02-25 13:51:52 +00:00
if (seq_convertold(event, newevent) > 0)
ret = seq_processevent(scp, newevent);
else
ret = EINVAL;
}
mtx_unlock(&scp->seq_lock);
break;
2007-02-25 13:51:52 +00:00
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) {
2007-02-25 13:51:52 +00:00
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
2007-02-25 13:51:52 +00:00
ret = EINVAL;
mtx_unlock(&scp->seq_lock);
break;
2007-02-25 13:51:52 +00:00
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) {
2007-02-25 13:51:52 +00:00
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
2007-02-25 13:51:52 +00:00
ret = EINVAL;
mtx_unlock(&scp->seq_lock);
break;
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(3, printf("seq_ioctl: water %d.\n", *(int *)arg));
ret = 0;
break;
2007-02-25 13:51:52 +00:00
case SNDCTL_MIDI_PRETIME:
tmp = *(int *)arg;
if (tmp < 0)
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(3, printf("seq_ioctl: pretime %d.\n", *(int *)arg));
ret = 0;
break;
2007-02-25 13:51:52 +00:00
case SNDCTL_FM_4OP_ENABLE:
case SNDCTL_PMGR_IFACE:
case SNDCTL_PMGR_ACCESS:
/*
* Patch manager and fm are ded, ded, ded.
*/
/* fallthrough */
2007-02-25 13:51:52 +00:00
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
*/
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(2,
printf("seq_ioctl: unsupported IOCTL %ld.\n", cmd));
ret = EINVAL;
break;
}
return ret;
}
int
mseq_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) {
2007-02-25 13:51:52 +00:00
/* Start playing. */
scp->playing = 1;
cv_broadcast(&scp->state_cv);
cv_broadcast(&scp->out_cv);
2007-02-25 13:51:52 +00:00
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);
}
2007-02-25 13:51:52 +00:00
if ((events & (POLLIN | POLLRDNORM)) != 0) {
/* TODO: Start recording. */
2007-02-25 13:51:52 +00:00
/* 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);
}
2007-02-25 13:51:52 +00:00
#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);
}
2007-02-25 13:51:52 +00:00
#endif
/*
* seq_convertold
* Was the old playevent. Use this to convert and old
2007-02-25 13:51:52 +00:00
* 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;
2007-02-25 13:51:52 +00:00
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
*/
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
case SEQ_NOTEOFF:
out[0] = EV_CHN_VOICE;
out[1] = dev;
out[2] = MIDI_NOTEOFF;
out[3] = chn;
out[4] = note;
out[5] = 255;
2007-02-25 13:51:52 +00:00
used += 4;
break;
2007-02-25 13:51:52 +00:00
case SEQ_NOTEON:
out[0] = EV_CHN_VOICE;
out[1] = dev;
out[2] = MIDI_NOTEON;
out[3] = chn;
out[4] = note;
out[5] = vel;
2007-02-25 13:51:52 +00:00
used += 4;
break;
/*
* wait delay = (event[2] << 16) + (event[3] << 8) + event[4]
*/
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
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;
*/
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
used += 4;
break;
2006-05-29 17:37:41 +00:00
#ifdef notyet
2007-02-25 13:51:52 +00:00
case SEQ_ECHO:
/*
* This isn't handled here yet because I don't know if I can
2007-02-25 13:51:52 +00:00
* just use four bytes events. There might be consequences
* in the _read routing
*/
if (seq_copytoinput(scp, event, 4) == EAGAIN) {
2007-02-25 13:51:52 +00:00
ret = QUEUEFULL;
break;
}
ret = MORE;
break;
#endif
2007-02-25 13:51:52 +00:00
case SEQ_EXTENDED:
switch (event[1]) {
2007-02-25 13:51:52 +00:00
case SEQ_NOTEOFF:
case SEQ_NOTEON:
case SEQ_PGMCHANGE:
event++;
used = 4;
goto restart;
break;
2007-02-25 13:51:52 +00:00
case SEQ_AFTERTOUCH:
/*
* SYNTH_AFTERTOUCH(md, event[3], event[4])
*/
2007-02-25 13:51:52 +00:00
case SEQ_BALANCE:
/*
* SYNTH_PANNING(md, event[3], (char)event[4])
*/
2007-02-25 13:51:52 +00:00
case SEQ_CONTROLLER:
/*
* SYNTH_CONTROLLER(md, event[3], event[4], *(short *)&event[5])
*/
2007-02-25 13:51:52 +00:00
case SEQ_VOLMODE:
/*
2007-02-25 13:51:52 +00:00
* SYNTH_VOLUMEMETHOD(md, event[3])
*/
2007-02-25 13:51:52 +00:00
default:
SEQ_DEBUG(2,
printf("seq_convertold: SEQ_EXTENDED type %d"
"not handled\n", event[1]));
break;
}
break;
2007-02-25 13:51:52 +00:00
case SEQ_WAIT:
out[0] = EV_TIMING;
out[1] = TMR_WAIT_REL;
out[4] = event[2];
out[5] = event[3];
out[6] = event[4];
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(5, printf("SEQ_WAIT %d",
event[2] + (event[3] << 8) + (event[4] << 24)));
2007-02-25 13:51:52 +00:00
used += 4;
break;
2007-02-25 13:51:52 +00:00
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) {
2007-02-25 13:51:52 +00:00
/*
* ENOROOM? EINPUTDROPPED? ETOUGHLUCK?
*/
SEQ_DEBUG(2, printf("seq_copytoinput: queue full\n"));
} else {
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
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) {
2007-02-25 13:51:52 +00:00
case MIDI_NOTEON:
if (note < 128 || note == 255) {
#if 0
2007-02-25 13:51:52 +00:00
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
2007-02-25 13:51:52 +00:00
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);
*/
}
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
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];
2007-02-25 13:51:52 +00:00
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) {
2007-02-25 13:51:52 +00:00
case MIDI_PGM_CHANGE:
SEQ_DEBUG(4, printf("seq_chncommon pgmchn chn %d pg %d\n",
chn, p1));
SYNTH_SETINSTR(md, chn, p1);
break;
2007-02-25 13:51:52 +00:00
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;
2007-02-25 13:51:52 +00:00
case MIDI_PITCH_BEND:
if (scp->music) {
2007-02-25 13:51:52 +00:00
/*
* TODO: MIDI_PITCH_BEND
*/
#if 0
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
if (md->synth.bender(md, chn, w14) == EAGAIN) {
mtx_lock(&scp->seq_lock);
return (QUEUEFULL);
}
mtx_lock(&scp->seq_lock);
}
#endif
} else
2007-02-25 13:51:52 +00:00
SYNTH_BENDER(md, chn, w14);
break;
2007-02-25 13:51:52 +00:00
default:
ret = 1;
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
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:
2007-02-25 13:51:52 +00:00
timer_wait(scp, param, 0);
break;
case TMR_WAIT_ABS:
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
param = 8;
if (param > 360)
2007-02-25 13:51:52 +00:00
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)
2007-02-25 13:51:52 +00:00
param = 1;
if (param > 1000)
2007-02-25 13:51:52 +00:00
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 *)&param, 4);
}
#else
seq_copytoinput(scp, event, 8);
#endif
break;
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(5, printf("seq_local: unit %d, cmd %d\n", scp->unit,
event[1]));
switch (event[1]) {
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(5, printf("seq_sysex: unit %d device %d\n", scp->unit,
event[1]));
l = 0;
2007-02-25 13:51:52 +00:00
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)
{
2007-02-25 13:51:52 +00:00
int chn, i;
kobj_t m;
mtx_assert(&scp->seq_lock, MA_OWNED);
2007-02-25 13:51:52 +00:00
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++) {
2007-02-25 13:51:52 +00:00
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);
2007-02-25 13:51:52 +00:00
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)) {
2007-02-25 13:51:52 +00:00
if (!scp->playing) {
scp->playing = 1;
cv_broadcast(&scp->state_cv);
cv_broadcast(&scp->out_cv);
}
rl = MIDIQ_LEN(scp->out_q);
2007-02-25 13:51:52 +00:00
i = cv_timedwait_sig(&scp->out_cv,
&scp->seq_lock, SEQ_SYNC_TIMEOUT * hz);
2007-02-25 13:51:52 +00:00
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;
}
2007-02-25 13:51:52 +00:00
if (i == EWOULDBLOCK && rl == MIDIQ_LEN(scp->out_q) &&
scp->waiting == 0) {
2007-02-25 13:51:52 +00:00
/*
* 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);
2007-02-25 13:51:52 +00:00
/*
* TODO: Consider if the raw devices need to be flushed
*/
2007-02-25 13:51:52 +00:00
SEQ_DEBUG(1, printf("seq_sync queue stuck, aborting\n"));
2007-02-25 13:51:52 +00:00
return i;
}
}
scp->playing = 0;
/*
* Since syncing a midi device might block, unlock scp->seq_lock.
*/
mtx_unlock(&scp->seq_lock);
2007-02-25 13:51:52 +00:00
for (i = 0; i < scp->midi_number; i++)
sync[i] = 1;
do {
2007-02-25 13:51:52 +00:00
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;
}
2007-02-25 13:51:52 +00:00
char *
midi_cmdname(int cmd, midi_cmdtab *tab)
{
while (tab->name != NULL) {
if (cmd == tab->cmd)
return (tab->name);
tab++;
}
return ("unknown");
}