/*
 * OSS compatible sequencer driver
 *
 * Copyright (C) 1998,99 Takashi Iwai <iwai@ww.uni-erlangen.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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "seq_oss_device.h"
#include "seq_oss_synth.h"
#include "seq_oss_midi.h"
#include "seq_oss_event.h"
#include "seq_oss_legacy.h"
#include "seq_oss_readq.h"


/*
 * prototypes
 */
static int extended_event(seq_oss_devinfo_t *dp, evrec_t *q, int atomic);
static int chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, int atomic);
static int chn_common_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, int atomic);
static int timing_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, int atomic);
static int local_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, int atomic);
static int old_event(seq_oss_devinfo_t *dp, evrec_t *q, int atomic);
static int note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, int atomic);
static int note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, int atomic);
static int send_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, int atomic);
static int send_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, int atomic);


/*
 * process the event record
 * return value:
 *   0 = normal event played.
 *   1 = MIDI output buffer full.  do pending.
 */

int
snd_seq_oss_play_event(seq_oss_devinfo_t *dp, evrec_t *q, int atomic)
{
	switch (q->s.code) {
	case SND_OSS_SEQ_EXTENDED:
		extended_event(dp, q, atomic);
		break;

	case SND_OSS_EV_CHN_VOICE:
		chn_voice_event(dp, q, atomic);
		break;

	case SND_OSS_EV_CHN_COMMON:
		chn_common_event(dp, q, atomic);
		break;

	case SND_OSS_EV_TIMING:
		timing_event(dp, q, atomic);
		break;

	case SND_OSS_EV_SEQ_LOCAL:
		local_event(dp, q, atomic);
		break;

	case SND_OSS_EV_SYSEX:
		snd_seq_oss_synth_send_sysex(dp, q->x.dev, q->x.buf, atomic);
		break;

	case SND_OSS_SEQ_MIDIPUTC:
		/* put a midi byte */
		if (! is_write_mode(dp->file_mode))
			break;
		if (snd_seq_oss_midi_open(dp, q->s.dev, SND_SEQ_OSS_FILE_WRITE))
			break;
		if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SND_SEQ_OSS_FILE_WRITE) {
			if (snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, atomic) > 0)
				/* output FIFO is full */
				return 1;
		}
		break;

	case SND_OSS_SEQ_ECHO:
		/* echo back to the process */
		if (dp->readq)
			snd_seq_oss_readq_put_event(dp->readq, q);
		break;

	case SND_OSS_SEQ_PRIVATE:
		snd_seq_oss_synth_send_raw_event(dp, q->c[1], q->c, atomic);
		break;

	default:
		old_event(dp, q, atomic);
		break;
	}
	return 0;
}

static int
old_event(seq_oss_devinfo_t *dp, evrec_t *q, int atomic)
{
	switch (q->s.code) {
	/* old type events */
	case SND_OSS_SEQ_NOTEOFF:
		return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, atomic);

	case SND_OSS_SEQ_NOTEON:
		return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, atomic);

	case SND_OSS_SEQ_WAIT:
		/* skip */
		break;

	case SND_OSS_SEQ_PGMCHANGE:
		return send_control_event(dp, 0, SND_SEQ_EVENT_PGMCHANGE,
					  q->n.chn, 0, q->n.note, atomic);

	case SND_OSS_SEQ_SYNCTIMER:
		/* start timer -- already parsed */
		break;

	}

	return 0;
}

/* 8bytes extended event */
static int
extended_event(seq_oss_devinfo_t *dp, evrec_t *q, int atomic)
{
	int val;

	switch (q->e.cmd) {
	case SND_OSS_SEQ_NOTEOFF:
		return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, atomic);

	case SND_OSS_SEQ_NOTEON:
		return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, atomic);
		break;

	case SND_OSS_SEQ_PGMCHANGE:
		return send_control_event(dp, q->e.dev, SND_SEQ_EVENT_PGMCHANGE,
					  q->e.chn, 0, q->e.p1, atomic);

	case SND_OSS_SEQ_AFTERTOUCH:
		return send_control_event(dp, q->e.dev, SND_SEQ_EVENT_CHANPRESS,
					  q->e.chn, 0, q->e.p1, atomic);

	case SND_OSS_SEQ_BALANCE:
		/* convert -128:127 to 0:127 */
		val = (char)q->e.p1;
		val = (val + 128) / 2;
		return send_control_event(dp, q->e.dev, SND_SEQ_EVENT_CONTROLLER,
					  q->e.chn, SND_OSS_CTL_PAN, val, atomic);

	case SND_OSS_SEQ_CONTROLLER:
		val = ((short)q->e.p3 << 8) | (short)q->e.p2;
		switch (q->e.p1) {
		case SND_OSS_CTRL_PITCH_BENDER: /* SEQ1 V2 control */
			/* -0x2000:0x1fff */
			return send_control_event(dp, q->e.dev,
						  SND_SEQ_EVENT_PITCHBEND,
						  q->e.chn, 0, val, atomic);
		case SND_OSS_CTRL_PITCH_BENDER_RANGE:
			/* conversion: 100/semitone -> 128/semitone */
			return send_control_event(dp, q->e.dev,
						  SND_SEQ_EVENT_REGPARAM,
						  q->e.chn, 0, val*128/100, atomic);
		default:
			return send_control_event(dp, q->e.dev,
						  SND_SEQ_EVENT_CONTROL14,
						  q->e.chn, q->e.p1, val, atomic);
		}

	case SND_OSS_SEQ_VOLMODE:
		return snd_seq_oss_synth_send_raw_event(dp, q->e.dev, q->c, atomic);

	default:
		return -EINVAL;
	}
	return 0;
}

/* channel voice events */
static int
chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *q, int atomic)
{
	switch (q->v.cmd) {
	case SND_OSS_MIDI_NOTEON:
		return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, atomic);

	case SND_OSS_MIDI_NOTEOFF:
		return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, atomic);

	case SND_OSS_MIDI_KEY_PRESSURE:
		return send_note_event(dp, q->v.dev, SND_SEQ_EVENT_KEYPRESS,
				       q->v.chn, q->v.note, q->v.parm, atomic);

	}
	return 0;
}

/* channel common events */
static int
chn_common_event(seq_oss_devinfo_t *dp, evrec_t *q, int atomic)
{
	switch (q->l.cmd) {
	case SND_OSS_MIDI_PGM_CHANGE:
		return send_control_event(dp, q->l.dev, SND_SEQ_EVENT_PGMCHANGE,
					  q->l.chn, 0, q->l.p1, atomic);

	case SND_OSS_MIDI_CTL_CHANGE:
		return send_control_event(dp, q->l.dev, SND_SEQ_EVENT_CONTROLLER,
					  q->l.chn, q->l.p1, q->l.val, atomic);

	case SND_OSS_MIDI_PITCH_BEND:
		/* conversion: 0:0x3fff -> -0x2000:0x1fff */
		return send_control_event(dp, q->l.dev, SND_SEQ_EVENT_PITCHBEND,
					  q->l.chn, 0, q->l.val - 8192, atomic);
		
	case SND_OSS_MIDI_CHN_PRESSURE:
		return send_control_event(dp, q->l.dev, SND_SEQ_EVENT_CHANPRESS,
					  q->l.chn, 0, q->l.val, atomic);
	}
	return 0;
}

/* timer events */
static int
timing_event(seq_oss_devinfo_t *dp, evrec_t *q, int atomic)
{
	if (q->t.cmd == SND_OSS_TMR_ECHO) {
		evrec_t rec;
		rec.echo = (q->t.time << 8 | SND_OSS_SEQ_ECHO);
		if (dp->readq)
			snd_seq_oss_readq_put_event(dp->readq, &rec);
	}

	return 0;
}

/* local events */
static int
local_event(seq_oss_devinfo_t *dp, evrec_t *q, int atomic)
{
	return 0;
}

/*
 * process note-on event for OSS synth
 * three different modes are available:
 * - SND_SEQ_OSS_PROCESS_EVENTS  (for one-voice per channel mode)
 *	Accept note 255 as volume change.
 * - SND_SEQ_OSS_PASS_EVENTS
 *	Pass all events to lowlevel driver anyway
 * - SND_SEQ_OSS_PROCESS_KEYPRESS  (mostly for Emu8000)
 *	Use key-pressure if note >= 128
 */
static int
note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, int atomic)
{
	seq_oss_synthinfo_t *info = &dp->synths[dev];
	switch (info->arg.event_passing) {
	case SND_SEQ_OSS_PROCESS_EVENTS:
		if (! info->ch || ch < 0 || ch >= info->nr_voices) {
			/* pass directly */
			return send_note_event(dp, dev, SND_SEQ_EVENT_NOTEON, ch, note, vel, atomic);
		}

		if (note == 255 && info->ch[ch].note >= 0) {
			/* volume control */
			int type;
			//if (! vel)
				/* set volume to zero -- note off */
			//	type = SND_SEQ_EVENT_NOTEOFF;
			//else
				if (info->ch[ch].vel)
				/* sample already started -- volume change */
				type = SND_SEQ_EVENT_KEYPRESS;
			else
				/* sample not started -- start now */
				type = SND_SEQ_EVENT_NOTEON;
			info->ch[ch].vel = vel;
			return send_note_event(dp, dev, type, ch, info->ch[ch].note, vel, atomic);
		} else if (note >= 128)
			return 0; /* invalid */

		if (note != info->ch[ch].note && info->ch[ch].note >= 0)
			/* note changed - note off at beginning */
			send_note_event(dp, dev, SND_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, atomic);
		/* set current status */
		info->ch[ch].note = note;
		info->ch[ch].vel = vel;
		if (vel) /* non-zero velocity - start the note now */
			return send_note_event(dp, dev, SND_SEQ_EVENT_NOTEON, ch, note, vel, atomic);
		return 0;
		
	case SND_SEQ_OSS_PASS_EVENTS:
		/* pass the event anyway */
		return send_note_event(dp, dev, SND_SEQ_EVENT_NOTEON, ch, note, vel, atomic);

	case SND_SEQ_OSS_PROCESS_KEYPRESS:
		if (note >= 128) /* key pressure: shifted by 128 */
			return send_note_event(dp, dev, SND_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, atomic);
		else /* normal note-on event */
			return send_note_event(dp, dev, SND_SEQ_EVENT_NOTEON, ch, note, vel, atomic);
	}
	return 0;
}

/*
 * process note-off event for OSS synth
 */
static int
note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, int atomic)
{
	seq_oss_synthinfo_t *info = &dp->synths[dev];
	switch (info->arg.event_passing) {
	case SND_SEQ_OSS_PROCESS_EVENTS:
		if (! info->ch || ch < 0 || ch >= info->nr_voices) {
			/* pass directly */
			return send_note_event(dp, dev, SND_SEQ_EVENT_NOTEON, ch, note, vel, atomic);
		}

		if (info->ch[ch].note >= 0) {
			note = info->ch[ch].note;
			info->ch[ch].vel = 0;
			info->ch[ch].note = -1;
			return send_note_event(dp, dev, SND_SEQ_EVENT_NOTEOFF, ch, note, vel, atomic);
		}
		return 0; /* invalid */

	case SND_SEQ_OSS_PASS_EVENTS:
	case SND_SEQ_OSS_PROCESS_KEYPRESS:
		/* pass the event anyway */
		return send_note_event(dp, dev, SND_SEQ_EVENT_NOTEOFF, ch, note, vel, atomic);

	}
	return 0;
}

/*
 * dispatch a note event
 */
static int
send_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, int atomic)
{
	snd_seq_event_t ev;

	if (! snd_seq_oss_synth_is_valid(dp, dev))
		return -ENXIO;
	
	memset(&ev, 0, sizeof(ev));
	ev.flags = 0;
	ev.type = type;
	snd_seq_oss_synth_addr(dp, dev, &ev);
	ev.dest.channel = ch;
	ev.data.note.note = note;
	ev.data.note.velocity = vel;

	return snd_seq_oss_send_event(dp, &ev, atomic);
}

/*
 * dispatch a control event
 */
static int
send_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, int atomic)
{
	snd_seq_event_t ev;

	if (! snd_seq_oss_synth_is_valid(dp, dev))
		return -ENXIO;
	
	memset(&ev, 0, sizeof(ev));
	ev.flags = 0;
	ev.type = type;
	snd_seq_oss_synth_addr(dp, dev, &ev);
	ev.dest.channel = ch;
	ev.data.control.param = param;
	ev.data.control.value = val;

	return snd_seq_oss_send_event(dp, &ev, atomic);
}
