/*
|
* OSS compatible sequencer driver
|
*
|
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
|
*
|
* This program is free software; you can redistribute it and/or modify
|
* it under the terms of the GNU General Public License as published by
|
* the Free Software Foundation; either version 2 of the License, or
|
* (at your option) any later version.
|
*
|
* This program is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* GNU General Public License for more details.
|
*
|
* You should have received a copy of the GNU General Public License
|
* along with this program; if not, write to the Free Software
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
*/
|
|
#include "seq_oss_device.h"
|
#include "seq_oss_synth.h"
|
#include "seq_oss_midi.h"
|
#include "seq_oss_event.h"
|
#include "seq_oss_timer.h"
|
#include <sound/seq_oss_legacy.h>
|
#include "seq_oss_readq.h"
|
#include "seq_oss_writeq.h"
|
#include <linux/nospec.h>
|
|
|
/*
|
* prototypes
|
*/
|
static int extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
|
static int chn_voice_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
static int chn_common_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
static int timing_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
static int local_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
|
static int old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
|
static int note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
|
static int note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
|
static int set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev);
|
static int set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev);
|
static int set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev);
|
|
|
/*
|
* convert an OSS event to ALSA event
|
* return 0 : enqueued
|
* non-zero : invalid - ignored
|
*/
|
|
int
|
snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
{
|
switch (q->s.code) {
|
case SEQ_EXTENDED:
|
return extended_event(dp, q, ev);
|
|
case EV_CHN_VOICE:
|
return chn_voice_event(dp, q, ev);
|
|
case EV_CHN_COMMON:
|
return chn_common_event(dp, q, ev);
|
|
case EV_TIMING:
|
return timing_event(dp, q, ev);
|
|
case EV_SEQ_LOCAL:
|
return local_event(dp, q, ev);
|
|
case EV_SYSEX:
|
return snd_seq_oss_synth_sysex(dp, q->x.dev, q->x.buf, ev);
|
|
case SEQ_MIDIPUTC:
|
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
return -EINVAL;
|
/* put a midi byte */
|
if (! is_write_mode(dp->file_mode))
|
break;
|
if (snd_seq_oss_midi_open(dp, q->s.dev, SNDRV_SEQ_OSS_FILE_WRITE))
|
break;
|
if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SNDRV_SEQ_OSS_FILE_WRITE)
|
return snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, ev);
|
break;
|
|
case SEQ_ECHO:
|
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
return -EINVAL;
|
return set_echo_event(dp, q, ev);
|
|
case SEQ_PRIVATE:
|
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
return -EINVAL;
|
return snd_seq_oss_synth_raw_event(dp, q->c[1], q->c, ev);
|
|
default:
|
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
return -EINVAL;
|
return old_event(dp, q, ev);
|
}
|
return -EINVAL;
|
}
|
|
/* old type events: mode1 only */
|
static int
|
old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
{
|
switch (q->s.code) {
|
case SEQ_NOTEOFF:
|
return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
|
|
case SEQ_NOTEON:
|
return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
|
|
case SEQ_WAIT:
|
/* skip */
|
break;
|
|
case SEQ_PGMCHANGE:
|
return set_control_event(dp, 0, SNDRV_SEQ_EVENT_PGMCHANGE,
|
q->n.chn, 0, q->n.note, ev);
|
|
case SEQ_SYNCTIMER:
|
return snd_seq_oss_timer_reset(dp->timer);
|
}
|
|
return -EINVAL;
|
}
|
|
/* 8bytes extended event: mode1 only */
|
static int
|
extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
{
|
int val;
|
|
switch (q->e.cmd) {
|
case SEQ_NOTEOFF:
|
return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
|
|
case SEQ_NOTEON:
|
return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
|
|
case SEQ_PGMCHANGE:
|
return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
|
q->e.chn, 0, q->e.p1, ev);
|
|
case SEQ_AFTERTOUCH:
|
return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CHANPRESS,
|
q->e.chn, 0, q->e.p1, ev);
|
|
case SEQ_BALANCE:
|
/* convert -128:127 to 0:127 */
|
val = (char)q->e.p1;
|
val = (val + 128) / 2;
|
return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CONTROLLER,
|
q->e.chn, CTL_PAN, val, ev);
|
|
case SEQ_CONTROLLER:
|
val = ((short)q->e.p3 << 8) | (short)q->e.p2;
|
switch (q->e.p1) {
|
case CTRL_PITCH_BENDER: /* SEQ1 V2 control */
|
/* -0x2000:0x1fff */
|
return set_control_event(dp, q->e.dev,
|
SNDRV_SEQ_EVENT_PITCHBEND,
|
q->e.chn, 0, val, ev);
|
case CTRL_PITCH_BENDER_RANGE:
|
/* conversion: 100/semitone -> 128/semitone */
|
return set_control_event(dp, q->e.dev,
|
SNDRV_SEQ_EVENT_REGPARAM,
|
q->e.chn, 0, val*128/100, ev);
|
default:
|
return set_control_event(dp, q->e.dev,
|
SNDRV_SEQ_EVENT_CONTROL14,
|
q->e.chn, q->e.p1, val, ev);
|
}
|
|
case SEQ_VOLMODE:
|
return snd_seq_oss_synth_raw_event(dp, q->e.dev, q->c, ev);
|
|
}
|
return -EINVAL;
|
}
|
|
/* channel voice events: mode1 and 2 */
|
static int
|
chn_voice_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
{
|
if (q->v.chn >= 32)
|
return -EINVAL;
|
switch (q->v.cmd) {
|
case MIDI_NOTEON:
|
return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
|
|
case MIDI_NOTEOFF:
|
return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
|
|
case MIDI_KEY_PRESSURE:
|
return set_note_event(dp, q->v.dev, SNDRV_SEQ_EVENT_KEYPRESS,
|
q->v.chn, q->v.note, q->v.parm, ev);
|
|
}
|
return -EINVAL;
|
}
|
|
/* channel common events: mode1 and 2 */
|
static int
|
chn_common_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
{
|
if (q->l.chn >= 32)
|
return -EINVAL;
|
switch (q->l.cmd) {
|
case MIDI_PGM_CHANGE:
|
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
|
q->l.chn, 0, q->l.p1, ev);
|
|
case MIDI_CTL_CHANGE:
|
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CONTROLLER,
|
q->l.chn, q->l.p1, q->l.val, ev);
|
|
case MIDI_PITCH_BEND:
|
/* conversion: 0:0x3fff -> -0x2000:0x1fff */
|
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PITCHBEND,
|
q->l.chn, 0, q->l.val - 8192, ev);
|
|
case MIDI_CHN_PRESSURE:
|
return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CHANPRESS,
|
q->l.chn, 0, q->l.val, ev);
|
}
|
return -EINVAL;
|
}
|
|
/* timer events: mode1 and mode2 */
|
static int
|
timing_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
{
|
switch (q->t.cmd) {
|
case TMR_ECHO:
|
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
|
return set_echo_event(dp, q, ev);
|
else {
|
union evrec tmp;
|
memset(&tmp, 0, sizeof(tmp));
|
/* XXX: only for little-endian! */
|
tmp.echo = (q->t.time << 8) | SEQ_ECHO;
|
return set_echo_event(dp, &tmp, ev);
|
}
|
|
case TMR_STOP:
|
if (dp->seq_mode)
|
return snd_seq_oss_timer_stop(dp->timer);
|
return 0;
|
|
case TMR_CONTINUE:
|
if (dp->seq_mode)
|
return snd_seq_oss_timer_continue(dp->timer);
|
return 0;
|
|
case TMR_TEMPO:
|
if (dp->seq_mode)
|
return snd_seq_oss_timer_tempo(dp->timer, q->t.time);
|
return 0;
|
}
|
|
return -EINVAL;
|
}
|
|
/* local events: mode1 and 2 */
|
static int
|
local_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
|
{
|
return -EINVAL;
|
}
|
|
/*
|
* process note-on event for OSS synth
|
* three different modes are available:
|
* - SNDRV_SEQ_OSS_PROCESS_EVENTS (for one-voice per channel mode)
|
* Accept note 255 as volume change.
|
* - SNDRV_SEQ_OSS_PASS_EVENTS
|
* Pass all events to lowlevel driver anyway
|
* - SNDRV_SEQ_OSS_PROCESS_KEYPRESS (mostly for Emu8000)
|
* Use key-pressure if note >= 128
|
*/
|
static int
|
note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
|
{
|
struct seq_oss_synthinfo *info;
|
|
info = snd_seq_oss_synth_info(dp, dev);
|
if (!info)
|
return -ENXIO;
|
|
switch (info->arg.event_passing) {
|
case SNDRV_SEQ_OSS_PROCESS_EVENTS:
|
if (! info->ch || ch < 0 || ch >= info->nr_voices) {
|
/* pass directly */
|
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
}
|
|
ch = array_index_nospec(ch, info->nr_voices);
|
if (note == 255 && info->ch[ch].note >= 0) {
|
/* volume control */
|
int type;
|
//if (! vel)
|
/* set volume to zero -- note off */
|
// type = SNDRV_SEQ_EVENT_NOTEOFF;
|
//else
|
if (info->ch[ch].vel)
|
/* sample already started -- volume change */
|
type = SNDRV_SEQ_EVENT_KEYPRESS;
|
else
|
/* sample not started -- start now */
|
type = SNDRV_SEQ_EVENT_NOTEON;
|
info->ch[ch].vel = vel;
|
return set_note_event(dp, dev, type, ch, info->ch[ch].note, vel, ev);
|
} else if (note >= 128)
|
return -EINVAL; /* invalid */
|
|
if (note != info->ch[ch].note && info->ch[ch].note >= 0)
|
/* note changed - note off at beginning */
|
set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, ev);
|
/* set current status */
|
info->ch[ch].note = note;
|
info->ch[ch].vel = vel;
|
if (vel) /* non-zero velocity - start the note now */
|
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
return -EINVAL;
|
|
case SNDRV_SEQ_OSS_PASS_EVENTS:
|
/* pass the event anyway */
|
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
|
case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
|
if (note >= 128) /* key pressure: shifted by 128 */
|
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, ev);
|
else /* normal note-on event */
|
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
}
|
return -EINVAL;
|
}
|
|
/*
|
* process note-off event for OSS synth
|
*/
|
static int
|
note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
|
{
|
struct seq_oss_synthinfo *info;
|
|
info = snd_seq_oss_synth_info(dp, dev);
|
if (!info)
|
return -ENXIO;
|
|
switch (info->arg.event_passing) {
|
case SNDRV_SEQ_OSS_PROCESS_EVENTS:
|
if (! info->ch || ch < 0 || ch >= info->nr_voices) {
|
/* pass directly */
|
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
|
}
|
|
ch = array_index_nospec(ch, info->nr_voices);
|
if (info->ch[ch].note >= 0) {
|
note = info->ch[ch].note;
|
info->ch[ch].vel = 0;
|
info->ch[ch].note = -1;
|
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
|
}
|
return -EINVAL; /* invalid */
|
|
case SNDRV_SEQ_OSS_PASS_EVENTS:
|
case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
|
/* pass the event anyway */
|
return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
|
|
}
|
return -EINVAL;
|
}
|
|
/*
|
* create a note event
|
*/
|
static int
|
set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev)
|
{
|
if (!snd_seq_oss_synth_info(dp, dev))
|
return -ENXIO;
|
|
ev->type = type;
|
snd_seq_oss_synth_addr(dp, dev, ev);
|
ev->data.note.channel = ch;
|
ev->data.note.note = note;
|
ev->data.note.velocity = vel;
|
|
return 0;
|
}
|
|
/*
|
* create a control event
|
*/
|
static int
|
set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev)
|
{
|
if (!snd_seq_oss_synth_info(dp, dev))
|
return -ENXIO;
|
|
ev->type = type;
|
snd_seq_oss_synth_addr(dp, dev, ev);
|
ev->data.control.channel = ch;
|
ev->data.control.param = param;
|
ev->data.control.value = val;
|
|
return 0;
|
}
|
|
/*
|
* create an echo event
|
*/
|
static int
|
set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev)
|
{
|
ev->type = SNDRV_SEQ_EVENT_ECHO;
|
/* echo back to itself */
|
snd_seq_oss_fill_addr(dp, ev, dp->addr.client, dp->addr.port);
|
memcpy(&ev->data, rec, LONG_EVENT_SIZE);
|
return 0;
|
}
|
|
/*
|
* event input callback from ALSA sequencer:
|
* the echo event is processed here.
|
*/
|
int
|
snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data,
|
int atomic, int hop)
|
{
|
struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
|
union evrec *rec;
|
|
if (ev->type != SNDRV_SEQ_EVENT_ECHO)
|
return snd_seq_oss_midi_input(ev, direct, private_data);
|
|
if (ev->source.client != dp->cseq)
|
return 0; /* ignored */
|
|
rec = (union evrec*)&ev->data;
|
if (rec->s.code == SEQ_SYNCTIMER) {
|
/* sync echo back */
|
snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time);
|
|
} else {
|
/* echo back event */
|
if (dp->readq == NULL)
|
return 0;
|
snd_seq_oss_readq_put_event(dp->readq, rec);
|
}
|
return 0;
|
}
|