/*
 *  Midi synth routines for the emu8000 (AWE32/64)
 *
 *  Copyright (C) 1999 Steve Ratcliffe
 *  Copyright (c) 1999 by Takashi Iwai <iwai@ww.uni-erlangen.de>
 *
 *  Contains code based on awe_wave.c by Takashi Iwai
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   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 "../../include/driver.h"
#include "../../include/emu8000.h"
#include "../../include/emu8000_reg.h"
#include "../../include/soundfont.h"
#include "../../include/midi.h"
#include "../../include/seq_midi_emul.h"
#include "emu8000_voice.h"
#include "emu8000_port.h"

/*
 * Routines to actually synthesize sounds.
 *
 * 
 */

/* Prototypes for static functions */
static void update_note(emu8000_t *emu, emu8000_voice_t *vp, int update);
static void exclusive_note_off(emu8000_t *emu, emu8000_port_t *port, int exclass);
static void terminate_note(emu8000_t *emu, int ch, emu8000_voice_t *vp);
static void setup_voice(emu8000_t *emu, emu8000_voice_t *voice);
static void calc_volume(emu8000_voice_t *vp);
static void calc_pitch(emu8000_voice_t *vp);
static emu8000_voice_t * get_voice(emu8000_t *emu);
static int get_bank(emu8000_port_t *port, snd_midi_channel_t *chan);
static int get_zone(emu8000_t *emu, emu8000_port_t *port, int *notep, int vel, snd_midi_channel_t *chan, snd_sf_zone_t **table);
static void start_note(emu8000_t *emu, emu8000_voice_t *vp);
static void trigger_note(emu8000_t *emu, emu8000_voice_t *vp);
static void set_pitch(emu8000_t *emu, emu8000_voice_t *vp);
static void set_volume(emu8000_t *emu, emu8000_voice_t *vp);
static void set_pan(emu8000_t *emu, emu8000_voice_t *vp);
static void set_fmmod(emu8000_t *emu, emu8000_voice_t *vp);
static void set_tremfreq(emu8000_t *emu, emu8000_voice_t *vp);
static void set_fm2frq2(emu8000_t *emu, emu8000_voice_t *vp);
static void set_filterQ(emu8000_t *emu, emu8000_voice_t *vp);
static void setup_register(emu8000_voice_t *vp);

/*
 * Ensure a value is between two points
 * macro evaluates its args more than once, so changed to upper-case.
 */
#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)

#define STATE_IS_PLAYING(s) ((s) & EMU8000_ST_ON)

/*
 * Start a note.
 */
void
snd_emu8000_note_on(void *p, int note, int vel, snd_midi_channel_t *chan)
{
	emu8000_t *emu;
	emu8000_port_t *port;
	int i, key, nvoices;
	emu8000_voice_t *vp;
	snd_sf_zone_t *table[EMU8000_CHANNELS];

	if (!(port = p) || !chan)
		return;
	emu = port->emu;
	if (EMU8000_CHECK(emu) < 0)
		return;

	key = note; /* remember the original note */
	nvoices = get_zone(emu, port, &note, vel, chan, table);
	if (! nvoices)
		return;

	for (i = 0; i < nvoices; i++) {
		snd_sf_zone_t *zp = table[i];
		if (zp && zp->v.exclusiveClass)
			exclusive_note_off(emu, port, zp->v.exclusiveClass);
	}

	/* Turn off the same note on the same channel. */
	snd_emu8000_note_terminate(port, note, chan);

	for (i = 0; i < nvoices; i++) {
		if (! table[i])
			continue;

		vp = get_voice(emu);
		if (! vp)
			continue;

		vp->chan = chan;
		vp->port = port;
		vp->key = key;
		vp->note = note;
		vp->velocity = vel;
		vp->zone = table[i];

		setup_voice(emu, vp);

		start_note(emu, vp);
	}

	/* start envelope now */
	for (i = 0; i < emu->max_voices; i++) {
		vp = &emu->voices[i];
		if (vp->state == EMU8000_ST_STANDBY &&
		    vp->chan == chan)
			trigger_note(emu, vp);
	}

	if (port->port_mode == PORT_MODE_OSS_SYNTH) {
		/* clear voice position for the next note on this channel */
		emu8000_effect_table_t *fx = chan->private;
		if (fx) {
			fx->flag[EMU8000_FX_SAMPLE_START] = 0;
			fx->flag[EMU8000_FX_COARSE_SAMPLE_START] = 0;
		}
	}
}

/*
 * Release a note in response to a midi note off.
 */
void
snd_emu8000_note_off(void *p, int note, int vel, snd_midi_channel_t *chan)
{
	int ch;
	emu8000_t *emu;
	emu8000_port_t *port;
	emu8000_voice_t *vp;

	if (!(port = p) || !chan)
		return;
	emu = port->emu;
	if (EMU8000_CHECK(emu) < 0)
		return;

	for (ch = 0; ch < emu->max_voices; ch++) {
		vp = &emu->voices[ch];
		if (STATE_IS_PLAYING(vp->state) &&
		    vp->chan == chan && vp->key == note)
		{
			int dcysusv;

			vp->time = emu->use_time++;
			vp->state = EMU8000_ST_RELEASED;

			dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
			EMU8000_DCYSUS_WRITE(emu, ch, dcysusv);
			dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease;
			EMU8000_DCYSUSV_WRITE(emu, ch, dcysusv);
		}
	}
}


/*
 * key pressure change
 */
void
snd_emu8000_key_press(void *p, int note, int vel, snd_midi_channel_t *chan)
{
	int ch;
	emu8000_t *emu;
	emu8000_port_t *port;
	emu8000_voice_t *vp;

	if (!(port = p) || !chan)
		return;
	emu = port->emu;
	if (EMU8000_CHECK(emu) < 0)
		return;

	for (ch = 0; ch < emu->max_voices; ch++) {
		vp = &emu->voices[ch];
		if (vp->state == EMU8000_ST_ON &&
		    vp->chan == chan && vp->key == note) {
			vp->velocity = vel;
			calc_volume(vp);
			set_volume(emu, vp);
		}
	}
}


/*
 * Modulate the voices which belong to the channel
 */
void
snd_emu8000_update_channel(emu8000_t *emu, snd_midi_channel_t *chan, int update)
{
	emu8000_voice_t *vp;
	int i;

	if (! chan || ! update)
		return;

	for (i = 0; i < emu->max_voices; i++) {
		vp = &emu->voices[i];
		if (vp->chan == chan)
			update_note(emu, vp, update);
	}
}

/*
 * Modulate all the voices which belong to the port.
 */
void
snd_emu8000_update_port(emu8000_t *emu, emu8000_port_t *port, int update)
{
	emu8000_voice_t *vp;
	int i;

	if (! port || ! update)
		return;

	for (i = 0; i < emu->max_voices; i++) {
		vp = &emu->voices[i];
		if (vp->port == port)
			update_note(emu, vp, update);
	}
}


/*
 * Modulate the voice
 */
static void
update_note(emu8000_t *emu, emu8000_voice_t *vp, int update)
{
	snd_midi_channel_t *chan;

	if (!STATE_IS_PLAYING(vp->state))
		return;

	chan = vp->chan;
	if (chan == NULL || vp->port == NULL)
		return;

	if (update & EMU8000_UPDATE_VOLUME) {
		calc_volume(vp);
		set_volume(emu, vp);
	}
	if (update & EMU8000_UPDATE_PITCH) {
		calc_pitch(vp);
		set_pitch(emu, vp);
	}
	if ((update & EMU8000_UPDATE_PAN) &&
	    vp->port->ctrls[EMU8000_MD_REALTIME_PAN])
		set_pan(emu, vp);
	if (update & EMU8000_UPDATE_FMMOD)
		set_fmmod(emu, vp);
	if (update & EMU8000_UPDATE_TREMFREQ)
		set_tremfreq(emu, vp);
	if (update & EMU8000_UPDATE_FM2FRQ2)
		set_fm2frq2(emu, vp);
	if (update & EMU8000_UPDATE_Q)
		set_filterQ(emu, vp);
}


/*
 * Deal with a controler type event.  This includes all types of
 * control events, not just the midi controllers
 */
void
snd_emu8000_control(void *p, int type, snd_midi_channel_t *chan)
{
	emu8000_port_t *port;
	emu8000_t *emu;

	if (!(port = p) || !chan)
		return;
	emu = port->emu;
	if (EMU8000_CHECK(emu) < 0)
		return;

	switch (type) {
	case SND_MCTL_MSB_MAIN_VOLUME:
	case SND_MCTL_MSB_EXPRESSION:
		snd_emu8000_update_channel(emu, chan, EMU8000_UPDATE_VOLUME);
		break;
		
	case SND_MCTL_MSB_PAN:
		snd_emu8000_update_channel(emu, chan, EMU8000_UPDATE_PAN);
		break;

	case SND_MCTL_SOFT_PEDAL:
		/* FIXME: this is an emulation */
		snd_emu8000_send_effect(emu, chan, EMU8000_FX_CUTOFF, -160,
					EMU8000_FX_FLAG_ADD);
		break;

	case SND_MCTL_PITCHBEND:
		snd_emu8000_update_channel(emu, chan, EMU8000_UPDATE_PITCH);
		break;

	case SND_MCTL_MSB_MODWHEEL:
	case SND_MCTL_CHAN_PRESSURE:
		snd_emu8000_update_channel(emu, chan,
					   EMU8000_UPDATE_FMMOD|EMU8000_UPDATE_FM2FRQ2);
		break;

	}

	if (port->chset.midi_mode == SND_MIDI_MODE_XG) {
		snd_emu8000_xg_control(emu, port, chan, type);
	}
}

/*
 * Terminate all voices that have the same exclusive class.  This
 * is mainly for drums.
 */
static void
exclusive_note_off(emu8000_t *emu, emu8000_port_t *port, int exclass)
{
	emu8000_voice_t *vp;
	int  i;

	for (i = 0; i < emu->max_voices; i++) {
		vp = &emu->voices[i];
		if (STATE_IS_PLAYING(vp->state) && vp->port == port &&
		    vp->reg.exclusiveClass == exclass)
			terminate_note(emu, i, vp);
	}
}

/*
 * Terminate a voice
 */
static void
terminate_note(emu8000_t *emu, int ch, emu8000_voice_t *vp)
{
	vp->time = emu->use_time++;
	EMU8000_DCYSUSV_WRITE(emu, ch, 0x807F);
	/*snd_emu8000_tweak_voice(emu, ch);*/
	vp->chan = NULL;
	vp->port = NULL;
	vp->state = EMU8000_ST_OFF;
}


/*
 * terminate note - exported for midi emulation
 */
void
snd_emu8000_note_terminate(void *p, int note, snd_midi_channel_t *chan)
{
	emu8000_port_t *port;
	emu8000_t *emu;
	emu8000_voice_t *vp;
	int  i;

	if (!(port = p) || !chan)
		return;
	emu = port->emu;
	if (EMU8000_CHECK(emu) < 0)
		return;

	for (i = 0; i < emu->max_voices; i++) {
		vp = &emu->voices[i];
		if (STATE_IS_PLAYING(vp->state) && vp->chan == chan &&
		    vp->key == note) {
			terminate_note(emu, i, vp);
			/*snd_emu8000_tweak_voice(emu, i);*/
		}
	}
}


/*
 * Terminate all the notes
 */
void
snd_emu8000_terminate_all(emu8000_t *emu)
{
	int i;

	for (i = 0; i < emu->max_voices; i++) {
		terminate_note(emu, i, &emu->voices[i]);
		snd_emu8000_tweak_voice(emu, i);
		emu->voices[i].time = 0;
	}
	/* initialize allocation time */
	emu->use_time = 0;
}


/*
 * Terminate all voices associated with the given port
 */
void
snd_emu8000_sounds_off_all(emu8000_t *emu, emu8000_port_t *port)
{
	int i;
	emu8000_voice_t *vp;

	if (! emu || ! port)
		return;

	for (i = 0; i < emu->max_voices; i++) {
		vp = &emu->voices[i];
		if (STATE_IS_PLAYING(vp->state) &&
		    vp->port == port)
			terminate_note(emu, i, vp);
	}
}


/*
 * Sets up the voice structure by calculating some values that
 * will be needed later.
 */
static void
setup_voice(emu8000_t *emu, emu8000_voice_t *voice)
{
	setup_register(voice);

	calc_volume(voice);
	calc_pitch(voice);

	voice->apan = -1; /* to reset panning status */
}

/*
 * calculate volume attenuation
 *
 * Voice volume is controlled by volume attenuation parameter.
 * So volume becomes maximum when avol is 0 (no attenuation), and
 * minimum when 255 (-96dB or silence).
 */

/* tables for volume->attenuation calculation */
static unsigned char voltab1[128] = {
   0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
   0x63, 0x2b, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22,
   0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a,
   0x19, 0x19, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14,
   0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10,
   0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d,
   0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b,
   0x0b, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09,
   0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06,
   0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04,
   0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02,
   0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01,
   0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

static unsigned char voltab2[128] = {
   0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x2a,
   0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x24, 0x23, 0x22, 0x21,
   0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1c, 0x1b, 0x1a,
   0x1a, 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15,
   0x14, 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x10,
   0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d,
   0x0d, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a,
   0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08,
   0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06,
   0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
   0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03,
   0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01,
   0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
};

static unsigned char expressiontab[128] = {
   0x7f, 0x6c, 0x62, 0x5a, 0x54, 0x50, 0x4b, 0x48, 0x45, 0x42,
   0x40, 0x3d, 0x3b, 0x39, 0x38, 0x36, 0x34, 0x33, 0x31, 0x30,
   0x2f, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25,
   0x24, 0x24, 0x23, 0x22, 0x21, 0x21, 0x20, 0x1f, 0x1e, 0x1e,
   0x1d, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a, 0x1a, 0x19, 0x18, 0x18,
   0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x15, 0x14, 0x14, 0x13,
   0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, 0x10, 0x0f, 0x0f,
   0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
   0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09,
   0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06,
   0x06, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03,
   0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01,
   0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/*
 * Magic to calculate the volume (actually attenuation) from all the
 * voice and channels parameters.
 */
static void
calc_volume(emu8000_voice_t *vp)
{
	int vol;
	int main_vol, expression_vol, master_vol;
	snd_midi_channel_t *chan = vp->chan;
	emu8000_port_t *port = vp->port;

	expression_vol = chan->control[SND_MCTL_MSB_EXPRESSION];
	LIMITMAX(vp->velocity, 127);
	LIMITVALUE(expression_vol, 0, 127);
	if (port->port_mode == PORT_MODE_OSS_SYNTH) {
		/* 0 - 127 */
		main_vol = chan->control[SND_MCTL_MSB_MAIN_VOLUME];
		vol = (vp->velocity * main_vol * expression_vol) / (127*127);
		vol = vol * vp->reg.amplitude / 127;

		LIMITVALUE(vol, 0, 127);

		/* calc to attenuation */
		vol = snd_sf_vol_table[vol];

	} else {
		main_vol = chan->control[SND_MCTL_MSB_MAIN_VOLUME] * vp->reg.amplitude / 127;
		LIMITVALUE(main_vol, 0, 127);

		vol = voltab1[main_vol] + voltab2[vp->velocity];
		vol = (vol * 8) / 3;
		vol += vp->reg.attenuation;
		vol += ((0x100 - vol) * expressiontab[expression_vol])/128;
	}

	master_vol = port->chset.gs_master_volume;
	LIMITVALUE(master_vol, 0, 127);
	vol += snd_sf_vol_table[master_vol];
	vol += port->volume_atten;
	if (chan->private) {
		emu8000_effect_table_t *fx = chan->private;
		vol += fx->val[EMU8000_FX_ATTEN];
	}

	LIMITVALUE(vol, 0, 255);
	vp->avol = vol;
		
	if (!SF_IS_DRUM_BANK(get_bank(port, chan))
	    && ((soundfont_voice_parm_block_t*)&vp->reg.parm)->volatk < 0x7d) {
		int atten;
		if (vp->velocity < 70)
			atten = 70;
		else
			atten = vp->velocity;
		vp->acutoff = (atten * vp->reg.parm.cutoff + 0xa0) >> 7;
	} else {
		vp->acutoff = vp->reg.parm.cutoff;
	}
}

/*
 * calculate pitch offset
 *
 * 0xE000 is no pitch offset at 44100Hz sample.
 * Every 4096 is one octave.
 */

static void
calc_pitch(emu8000_voice_t *vp)
{
	snd_midi_channel_t *chan = vp->chan;
	int offset;

	/* calculate offset */
	if (vp->reg.fixkey >= 0) {
		offset = (vp->reg.fixkey - vp->reg.root) * 4096 / 12;
	} else {
		offset = (vp->note - vp->reg.root) * 4096 / 12;
	}
	offset = (offset * vp->reg.scaleTuning) / 100;
	offset += vp->reg.tune * 4096 / 1200;
	if (chan->midi_pitchbend != 0) {
		/* (128 * 8192: 1 semitone) ==> (4096: 12 semitones) */
		offset += chan->midi_pitchbend * chan->gm_rpn_pitch_bend_range / 3072;
	}

	/* tuning via RPN:
	 *   coarse = -8192 to 8192 (100 cent per 128)
	 *   fine = -8192 to 8192 (max=100cent)
	 */
	/* 4096 = 1200 cents in emu8000 parameter */
	offset += chan->gm_rpn_coarse_tuning * 4096 / (12 * 128);
	offset += chan->gm_rpn_fine_tuning / 24;

	/* add initial pitch correction */
	if (chan->private) {
		emu8000_effect_table_t *fx = chan->private;
		if (fx->flag[EMU8000_FX_INIT_PITCH])
			offset += fx->val[EMU8000_FX_INIT_PITCH];
	}

	/* 0xe000: root pitch */
	vp->apitch = 0xe000 + vp->reg.rate_offset + offset;
	if (vp->apitch > 0xffff)
		vp->apitch = 0xffff;
	if (vp->apitch < 0)
		vp->apitch = 0;
}

/*
 * Find a channel (voice) within the EMU that is not in use or at least
 * less in use than other channels.  Always returns a valid pointer
 * no matter what.  If there is a real shortage of voices then one
 * will be cut. Such is life.
 */
static emu8000_voice_t *
get_voice(emu8000_t *emu)
{
	int  i;
	emu8000_voice_t *vp;

	/* what we are looking for, in order of preference */
	enum {
		OFF=0, RELEASED, SUSTAINED, PLAYING, END
	};

	/* Keeps track of what we are finding */
	struct best {
		unsigned int  time;
		emu8000_voice_t *voice;
	} best[END];
	struct best *bp;

	for (i = 0; i < END; i++) {
		best[i].time = 0x7fffffff /* XXX MAX_?INT really */;
		best[i].voice = NULL;
	}

	/*
	 * Go through them all and get a best one to use.
	 * NOTE: could also look at volume and pick the quietest one.
	 */
	for (i = 0; i < emu->max_voices; i++) {
		int state, val;

		vp = &emu->voices[i];
		state = vp->state;

		if (state == EMU8000_ST_OFF)
			bp = best + OFF;
		else if (state == EMU8000_ST_RELEASED) {
			bp = best + RELEASED;
			val = (EMU8000_VTFT_READ(emu, i) >> 16) & 0xffff;
			if (! val)
				bp = best + OFF;
		}
#if 0
		else if (state == EMU8000_ST_SUSTAINED)
			bp = best + SUSTAINED;
#endif
		else if (state & EMU8000_ST_ON)
			bp = best + PLAYING;
		else
			continue;

		/* check if sample is finished playing (non-looping only) */
		if (state != EMU8000_ST_OFF &&
		    (vp->reg.sample_mode & SND_SFNT_SAMPLE_SINGLESHOT)) {
			val = EMU8000_CCCA_READ(emu, i) & 0xffffff;
			if (val >= vp->reg.loopstart)
				bp = best + OFF;
		}

		if (vp->time < bp->time) {
			bp->time = vp->time;
			bp->voice = vp;
		}
	}

	for (i = 0; i < END; i++) {
		vp = best[i].voice;
		if (vp) {
			if (vp->state != EMU8000_ST_OFF)
				terminate_note(emu, vp->ch, vp);
			else
				vp->time = emu->use_time++;
			return vp;
		}
	}

	/* not found */
	return NULL;
}


/*
 * Get the bank number assigned to the channel
 */
static int
get_bank(emu8000_port_t *port, snd_midi_channel_t *chan)
{
	int val;

	switch (port->chset.midi_mode) {
	case SND_MIDI_MODE_XG:
		val = chan->control[SND_MCTL_MSB_BANK];
		if (val == 127)
			return 128; /* return drum bank */
		return chan->control[SND_MCTL_LSB_BANK];

	case SND_MIDI_MODE_GS:
		if (chan->drum_channel)
			return 128;
		/* ignore LSB (bank map) */
		return chan->control[SND_MCTL_MSB_BANK];
		
	default:
		if (chan->drum_channel)
			return 128;
		return chan->control[SND_MCTL_MSB_BANK];
	}
}


/* Look for the zones matching with the given note and velocity.
 * The resultant zones are stored on table.
 */
static int
get_zone(emu8000_t *emu, emu8000_port_t *port,
	 int *notep, int vel, snd_midi_channel_t *chan, snd_sf_zone_t **table)
{
	int preset, bank, def_preset, def_bank;

	bank = get_bank(port, chan);
	preset = chan->midi_program;

	if (SF_IS_DRUM_BANK(bank)) {
		def_preset = port->ctrls[EMU8000_MD_DEF_DRUM];
		def_bank = bank;
	} else {
		def_preset = preset;
		def_bank = port->ctrls[EMU8000_MD_DEF_BANK];
	}

	return snd_soundfont_search_zone(emu->sflist, notep, vel, preset, bank,
					 def_preset, def_bank,
					 table, EMU8000_CHANNELS);
}


/* table for volume target calculation */
static unsigned short voltarget[16] = { 
   0xEAC0, 0xE0C8, 0xD740, 0xCE20, 0xC560, 0xBD08, 0xB500, 0xAD58,
   0xA5F8, 0x9EF0, 0x9830, 0x91C0, 0x8B90, 0x85A8, 0x8000, 0x7A90
};

/* Here goes... */

/*
 * This is it, write all the registers to get the note going.  There
 * is so much to do here that it is a wonder that it can all be done
 * before the note is finished..
 * All copied directly from Takashi's driver apart from name changes etc.
 */
static void
start_note(emu8000_t *emu, emu8000_voice_t *vp)
{
	unsigned int temp;
	int ch;
	int addr;
	int vtarget, ftarget, pitch;
	soundfont_voice_parm_block_t *parm;
	snd_midi_channel_t *chan = vp->chan;

	ch = vp->ch;
	if (ch < 0) {
		snd_printd("ch -ve %d!\n", ch);
		ch = 0;
	}


	parm = (soundfont_voice_parm_block_t*)&vp->reg.parm;

	/* channel to be silent and idle */
	EMU8000_DCYSUSV_WRITE(emu, ch, 0x0080);
	EMU8000_VTFT_WRITE(emu, ch, 0x0000FFFF);
	EMU8000_CVCF_WRITE(emu, ch, 0x0000FFFF);
	EMU8000_PTRX_WRITE(emu, ch, 0);
	EMU8000_CPF_WRITE(emu, ch, 0);

	/* set pitch offset */
	set_pitch(emu, vp);

	/* modulation & volume envelope */
	if (parm->modatk >= 0x80 && parm->moddelay >= 0x8000) {
		EMU8000_ENVVAL_WRITE(emu, ch, 0xBFFF);
		pitch = (parm->env1pit<<4) + vp->apitch;
		if (pitch > 0xffff) pitch = 0xffff;
		/* calculate filter target */
		ftarget = parm->cutoff + parm->env1fc;
		LIMITVALUE(ftarget, 0, 255);
		ftarget <<= 8;
	} else {
		EMU8000_ENVVAL_WRITE(emu, ch, parm->moddelay);
		ftarget = parm->cutoff;
		ftarget <<= 8;
		pitch = vp->apitch;
	}
	
	/* calcualte pitch target */
	if (pitch != 0xffff) {
		vp->ptarget = 1 << (pitch >> 12);
		if (pitch & 0x800) vp->ptarget += (vp->ptarget*0x102e)/0x2710;
		if (pitch & 0x400) vp->ptarget += (vp->ptarget*0x764)/0x2710;
		if (pitch & 0x200) vp->ptarget += (vp->ptarget*0x389)/0x2710;
		vp->ptarget += (vp->ptarget>>1);
		if (vp->ptarget > 0xffff) vp->ptarget = 0xffff;

	} else
		vp->ptarget = 0xffff;

	if (parm->modatk >= 0x80)
		EMU8000_ATKHLD_WRITE(emu, ch, (parm->modhld << 8) | 0x7f);
	else
		EMU8000_ATKHLD_WRITE(emu, ch, vp->reg.parm.modatkhld);

	EMU8000_DCYSUS_WRITE(emu, ch, vp->reg.parm.moddcysus);

	if (parm->volatk >= 0x80 && parm->voldelay >= 0x8000) {
		EMU8000_ENVVOL_WRITE(emu, ch, 0xBFFF);
		vtarget = voltarget[vp->avol%0x10]>>(vp->avol>>4);
	} else {
		EMU8000_ENVVOL_WRITE(emu, ch, vp->reg.parm.voldelay);
		vtarget = 0;
	}

	if (parm->volatk >= 0x80)
		EMU8000_ATKHLDV_WRITE(emu, ch, (parm->volhld << 8) | 0x7f);
	else
		EMU8000_ATKHLDV_WRITE(emu, ch, vp->reg.parm.volatkhld);
	/* decay/sustain parameter for volume envelope must be set at last */

	/* cutoff and volume */
	set_volume(emu, vp);

	/* modulation envelope heights */
	EMU8000_PEFE_WRITE(emu, ch, vp->reg.parm.pefe);

	/* lfo1/2 delay */
	EMU8000_LFO1VAL_WRITE(emu, ch, vp->reg.parm.lfo1delay);
	EMU8000_LFO2VAL_WRITE(emu, ch, vp->reg.parm.lfo2delay);

	/* lfo1 pitch & cutoff shift */
	set_fmmod(emu, vp);
	/* lfo1 volume & freq */
	set_tremfreq(emu, vp);
	/* lfo2 pitch & freq */
	set_fm2frq2(emu, vp);
	/* pan & loop start */
	set_pan(emu, vp);

	/* chorus & loop end (chorus 8bit, MSB) */
	addr = vp->reg.loopend - 1;
	temp = vp->reg.parm.chorus;
	temp += (int)chan->control[SND_MCTL_E3_CHORUS_DEPTH] * 9 / 10;
	LIMITVALUE(temp, 0, 255);
	temp = (temp <<24) | (unsigned int)addr;
	EMU8000_CSL_WRITE(emu, ch, temp);

	/* Q & current address (Q 4bit value, MSB) */
	addr = vp->reg.start - 1;
	temp = vp->reg.parm.filterQ;
	temp = (temp<<28) | (unsigned int)addr;
	EMU8000_CCCA_WRITE(emu, ch, temp);

	/* clear unknown registers */
	EMU8000_00A0_WRITE(emu, ch, 0);
	EMU8000_0080_WRITE(emu, ch, 0);

	/* reset volume */
	EMU8000_VTFT_WRITE(emu, ch, (vtarget<<16)|ftarget);
	EMU8000_CVCF_WRITE(emu, ch, (vtarget<<16)|ftarget);

	vp->state = EMU8000_ST_STANDBY;
}

/*
 * Start envelope
 */
static void
trigger_note(emu8000_t *emu, emu8000_voice_t *vp)
{
	unsigned int temp;
	int ch = vp->ch;

	/* turn on envelope */
	EMU8000_DCYSUSV_WRITE(emu, ch, vp->reg.parm.voldcysus);

	/* set reverb */
	temp = vp->reg.parm.reverb;
	temp += (int)vp->chan->control[SND_MCTL_E1_REVERB_DEPTH] * 9 / 10;
	LIMITVALUE(temp, 0, 255);
	temp = (temp << 8) | (vp->ptarget << 16) | vp->aaux;
	EMU8000_PTRX_WRITE(emu, ch, temp);
	EMU8000_CPF_WRITE(emu, ch, vp->ptarget << 16);

	vp->state = EMU8000_ST_ON;
}

/*
 * Set the pitch of a possibly playing note.
 */
static void
set_pitch(emu8000_t *emu, emu8000_voice_t *vp)
{
	EMU8000_IP_WRITE(emu, vp->ch, vp->apitch);
}

/*
 * Set the volume of a possibly already playing note
 */
static void
set_volume(emu8000_t *emu, emu8000_voice_t *vp)
{
	int  ifatn;

	ifatn = (unsigned char)vp->acutoff;
	ifatn = (ifatn << 8);
	ifatn |= (unsigned char)vp->avol;
	EMU8000_IFATN_WRITE(emu, vp->ch, ifatn);
}

/*
 * Set pan and loop start address.
 */
static void
set_pan(emu8000_t *emu, emu8000_voice_t *vp)
{
	unsigned int temp;
	int pan;
	int addr;
	snd_midi_channel_t *chan = vp->chan;

	/* pan & loop start (pan 8bit, MSB, 0:right, 0xff:left) */
	if (vp->reg.fixpan > 0)	/* 0-127 */
		pan = 255 - (int)vp->reg.fixpan * 2;
	else {
		pan = chan->control[SND_MCTL_MSB_PAN] - 64;
		if (vp->reg.pan >= 0) /* 0-127 */
			pan += vp->reg.pan - 64;
		pan = 127 - (int)pan * 2;
	}
	LIMITVALUE(pan, 0, 255);

	if (pan != vp->apan) {
		vp->apan = pan;
		addr = vp->reg.loopstart - 1;
		temp = ((unsigned int)pan<<24) | (unsigned int)addr;

		EMU8000_PSST_WRITE(emu, vp->ch, temp);

		if (temp == 0)
			vp->aaux = 0xff;
		else
			vp->aaux = (-temp)&0xff;
	}
}

#define MOD_SENSE 18

static void
set_fmmod(emu8000_t *emu, emu8000_voice_t *vp)
{
	unsigned short fmmod;
	short pitch;
	unsigned char cutoff;
	int modulation;

	pitch = (char)(vp->reg.parm.fmmod>>8);
	cutoff = (vp->reg.parm.fmmod & 0xff);
	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
	pitch += (MOD_SENSE * modulation) / 1200;
	LIMITVALUE(pitch, -128, 127);
	fmmod = ((unsigned char)pitch<<8) | cutoff;
	EMU8000_FMMOD_WRITE(emu, vp->ch, fmmod);
}

/* set tremolo (lfo1) volume & frequency */
static void
set_tremfreq(emu8000_t *emu, emu8000_voice_t *vp)
{
	EMU8000_TREMFRQ_WRITE(emu, vp->ch, vp->reg.parm.tremfrq);
}

/* set lfo2 pitch & frequency */
static void
set_fm2frq2(emu8000_t *emu, emu8000_voice_t *vp)
{
	unsigned short fm2frq2;
	short pitch;
	unsigned char freq;
	int modulation;

	pitch = (char)(vp->reg.parm.fm2frq2>>8);
	freq = vp->reg.parm.fm2frq2 & 0xff;
	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
	pitch += (MOD_SENSE * modulation) / 1200;
	LIMITVALUE(pitch, -128, 127);
	fm2frq2 = ((unsigned char)pitch<<8) | freq;
	EMU8000_FM2FRQ2_WRITE(emu, vp->ch, fm2frq2);
}

/* set filterQ */
static void
set_filterQ(emu8000_t *emu, emu8000_voice_t *vp)
{
	unsigned int addr;
	addr = EMU8000_CCCA_READ(emu, vp->ch) & 0xffffff;
	addr |= (vp->reg.parm.filterQ << 28);
	EMU8000_CCCA_WRITE(emu, vp->ch, addr);
}


/*
 * effects table
 */

#define xoffsetof(type,tag)	((long)(&((type)NULL)->tag) - (long)(NULL))

#define parm_offset(tag)	xoffsetof(soundfont_voice_parm_block_t*, tag)

#define PARM_BYTE	0
#define PARM_WORD	1
#define PARM_SIGN	2

static struct emu8000_parm_defs {
	int type;	/* byte or word */
	int low, high;	/* value range */
	long offset;	/* offset in parm_block (-1 = not written) */
	int update;	/* flgas for real-time update */
} parm_defs[EMU8000_NUM_EFFECTS] = {
	{PARM_WORD, 0, 0x8000, parm_offset(moddelay), 0},	/* env1 delay */
	{PARM_BYTE, 1, 0x80, parm_offset(modatk), 0},	/* env1 attack */
	{PARM_BYTE, 0, 0x7e, parm_offset(modhld), 0},	/* env1 hold */
	{PARM_BYTE, 1, 0x7f, parm_offset(moddcy), 0},	/* env1 decay */
	{PARM_BYTE, 1, 0x7f, parm_offset(modrel), 0},	/* env1 release */
	{PARM_BYTE, 0, 0x7f, parm_offset(modsus), 0},	/* env1 sustain */
	{PARM_BYTE, 0, 0xff, parm_offset(env1pit), 0},	/* env1 pitch */
	{PARM_BYTE, 0, 0xff, parm_offset(env1fc), 0},	/* env1 fc */

	{PARM_WORD, 0, 0x8000, parm_offset(voldelay), 0},	/* env2 delay */
	{PARM_BYTE, 1, 0x80, parm_offset(volatk), 0},	/* env2 attack */
	{PARM_BYTE, 0, 0x7e, parm_offset(volhld), 0},	/* env2 hold */
	{PARM_BYTE, 1, 0x7f, parm_offset(voldcy), 0},	/* env2 decay */
	{PARM_BYTE, 1, 0x7f, parm_offset(volrel), 0},	/* env2 release */
	{PARM_BYTE, 0, 0x7f, parm_offset(volsus), 0},	/* env2 sustain */

	{PARM_WORD, 0, 0x8000, parm_offset(lfo1delay), 0},	/* lfo1 delay */
	{PARM_BYTE, 0, 0xff, parm_offset(lfo1freq), EMU8000_UPDATE_TREMFREQ},	/* lfo1 freq */
	{PARM_SIGN, -128, 127, parm_offset(lfo1vol), EMU8000_UPDATE_TREMFREQ},	/* lfo1 vol */
	{PARM_SIGN, -128, 127, parm_offset(lfo1pit), EMU8000_UPDATE_FMMOD},	/* lfo1 pitch */
	{PARM_BYTE, 0, 0xff, parm_offset(lfo1fc), EMU8000_UPDATE_FMMOD},	/* lfo1 cutoff */

	{PARM_WORD, 0, 0x8000, parm_offset(lfo2delay), 0},	/* lfo2 delay */
	{PARM_BYTE, 0, 0xff, parm_offset(lfo2freq), EMU8000_UPDATE_FM2FRQ2},	/* lfo2 freq */
	{PARM_SIGN, -128, 127, parm_offset(lfo2pit), EMU8000_UPDATE_FM2FRQ2},	/* lfo2 pitch */

	{PARM_WORD, 0, 0xffff, -1, EMU8000_UPDATE_PITCH},	/* initial pitch */
	{PARM_BYTE, 0, 0xff, parm_offset(chorus), 0},	/* chorus */
	{PARM_BYTE, 0, 0xff, parm_offset(reverb), 0},	/* reverb */
	{PARM_BYTE, 0, 0xff, parm_offset(cutoff), EMU8000_UPDATE_VOLUME},	/* cutoff */
	{PARM_BYTE, 0, 15, parm_offset(filterQ), EMU8000_UPDATE_Q},	/* resonance */

	{PARM_WORD, 0, 0xffff, -1, 0},	/* sample start */
	{PARM_WORD, 0, 0xffff, -1, 0},	/* loop start */
	{PARM_WORD, 0, 0xffff, -1, 0},	/* loop end */
	{PARM_WORD, 0, 0xffff, -1, 0},	/* coarse sample start */
	{PARM_WORD, 0, 0xffff, -1, 0},	/* coarse loop start */
	{PARM_WORD, 0, 0xffff, -1, 0},	/* coarse loop end */
	{PARM_BYTE, 0, 0xff, -1, EMU8000_UPDATE_VOLUME},	/* initial attenuation */
};

/* set byte effect value */
static void
effect_set_byte(unsigned char *valp, snd_midi_channel_t *chan, int type)
{
	short effect;
	emu8000_effect_table_t *fx = chan->private;

	effect = fx->val[type];
	if (fx->flag[type] == EMU8000_FX_FLAG_ADD) {
		if (parm_defs[type].type == PARM_SIGN)
			effect += *(char*)valp;
		else
			effect += *valp;
	}
	if (effect < parm_defs[type].low)
		effect = parm_defs[type].low;
	else if (effect > parm_defs[type].high)
		effect = parm_defs[type].high;
	*valp = (unsigned char)effect;
}

/* set word effect value */
static void
effect_set_word(unsigned short *valp, snd_midi_channel_t *chan, int type)
{
	int effect;
	emu8000_effect_table_t *fx = chan->private;

	effect = *(unsigned short*)&fx->val[type];
	if (fx->flag[type] == EMU8000_FX_FLAG_ADD)
		effect += *valp;
	if (effect < parm_defs[type].low)
		effect = parm_defs[type].low;
	else if (effect > parm_defs[type].high)
		effect = parm_defs[type].high;
	*valp = (unsigned short)effect;
}

/* address offset */
static int
effect_get_offset(snd_midi_channel_t *chan, int lo, int hi, int mode)
{
	int addr = 0;
	emu8000_effect_table_t *fx = chan->private;

	if (fx->flag[hi])
		addr = (short)fx->val[hi];
	addr = addr << 15;
	if (fx->flag[lo])
		addr += (short)fx->val[lo];
	if (!(mode & SND_SFNT_SAMPLE_8BITS))
		addr /= 2;
	return addr;
}

#ifdef CONFIG_SND_OSSEMUL
/* change effects - for OSS sequencer compatibility */
void
snd_emu8000_send_effect_oss(emu8000_t *emu, snd_midi_channel_t *chan, int type, int val)
{
	int mode;

	if (type & 0x40)
		mode = EMU8000_FX_FLAG_OFF;
	else if (type & 0x80)
		mode = EMU8000_FX_FLAG_ADD;
	else
		mode = EMU8000_FX_FLAG_SET;
	type &= 0x3f;

	snd_emu8000_send_effect(emu, chan, type, val, mode);
}
#endif

/* Modify the effect value.
 * if update is necessary, call emu8000_control
 */
void
snd_emu8000_send_effect(emu8000_t *emu, snd_midi_channel_t *chan, int type, int val, int mode)
{
	int i;
	int offset;
	unsigned char *srcp, *origp;
	emu8000_effect_table_t *fx = chan->private;

	if (! fx)
		return;
	if (type < 0 || type >= EMU8000_NUM_EFFECTS)
		return;

	fx->val[type] = val;
	fx->flag[type] = mode;

	/* do we need to modify the register in realtime ? */
	if (! parm_defs[type].update || (offset = parm_defs[type].offset) < 0)
		return;

	/* modify the register values */
	for (i = 0; i < emu->max_voices; i++) {
		emu8000_voice_t *vp = &emu->voices[i];
		if (!STATE_IS_PLAYING(vp->state) || vp->chan != chan)
			continue;
		srcp = (unsigned char*)&vp->reg.parm + offset;
		origp = (unsigned char*)&vp->zone->v.parm + offset;
		switch (parm_defs[type].type) {
		case PARM_BYTE:
		case PARM_SIGN:
			*srcp = *origp;
			effect_set_byte(srcp, chan, type);
			break;
		case PARM_WORD:
			*(unsigned short*)srcp = *(unsigned short*)origp;
			effect_set_word((unsigned short*)srcp, chan, type);
			break;
		}
	}

	/* activate them */
	snd_emu8000_update_channel(emu, chan, parm_defs[type].update);
}


/* copy emu8000 registers to voice table */
static void
setup_register(emu8000_voice_t *vp)
{
	snd_midi_channel_t *chan = vp->chan;
	emu8000_effect_table_t *fx;
	unsigned char *srcp;
	int i;

	/* copy the original register values */
	memcpy(&vp->reg, &vp->zone->v, sizeof(emu8000_register_t));

	if (! (fx = chan->private))
		return;

	/* modify the register values via effect table */
	for (i = 0; i < EMU8000_FX_END; i++) {
		int offset;
		if (! fx->flag[i] || (offset = parm_defs[i].offset) < 0)
			continue;
		srcp = (unsigned char*)&vp->reg.parm + offset;
		switch (parm_defs[i].type) {
		case PARM_BYTE:
		case PARM_SIGN:
			effect_set_byte(srcp, chan, i);
			break;
		case PARM_WORD:
			effect_set_word((unsigned short*)srcp, chan, i);
			break;
		}
	}

	/* correct sample and loop points */
	vp->reg.start += effect_get_offset(chan, EMU8000_FX_SAMPLE_START,
					   EMU8000_FX_COARSE_SAMPLE_START,
					   vp->reg.sample_mode);

	vp->reg.loopstart += effect_get_offset(chan, EMU8000_FX_LOOP_START,
					       EMU8000_FX_COARSE_LOOP_START,
					       vp->reg.sample_mode);

	vp->reg.loopend += effect_get_offset(chan, EMU8000_FX_LOOP_END,
					     EMU8000_FX_COARSE_LOOP_END,
					     vp->reg.sample_mode);
}
