/*
 * sound effect controls for Emu8000 (AWE32/64)
 * (equalizer, chorus and reverb)
 *
 *  Copyright (c) 1999 by 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 "driver.h"
#include "emu8000.h"
#include "emu8000_reg.h"
#include "emu8000_equalizer.h"
#include "emu8000_port.h"

//#define ADD_MODULE_OPTION

#ifdef ADD_MODULE_OPTION
int use_chorus=1;
int use_reverb=1;
int use_mixer=1;
MODULE_PARM(use_chorus, "i");
MODULE_PARM_DESC(use_chorus, "enable chorus mode change");
MODULE_PARM(use_reverb, "i");
MODULE_PARM_DESC(use_reverb, "enable reverb mode change");
MODULE_PARM(use_mixer, "i");
MODULE_PARM_DESC(use_mixer, "enable bass/treble change");
#endif


/*
 * copy from/to user-space
 */
#ifndef LINUX_2_1

#undef copy_from_user
#undef copy_to_user

inline static int
copy_from_user(void *dst, const void *src, int size)
{
	if (verify_area(VERIFY_READ, src, size))
		return -EFAULT;
	memcpy_fromfs(dst, src, size);
	return 0;
}

inline static int
copy_to_user(void *dst, const void *src, int size)
{
	if (verify_area(VERIFY_WRITE, dst, size))
		return -EFAULT;
	memcpy_tofs(dst, src, size);
	return 0;
}

#endif

static unsigned short bass_parm[12][3] = {
	{0xD26A, 0xD36A, 0x0000}, /* -12 dB */
	{0xD25B, 0xD35B, 0x0000}, /*  -8 */
	{0xD24C, 0xD34C, 0x0000}, /*  -6 */
	{0xD23D, 0xD33D, 0x0000}, /*  -4 */
	{0xD21F, 0xD31F, 0x0000}, /*  -2 */
	{0xC208, 0xC308, 0x0001}, /*   0 (HW default) */
	{0xC219, 0xC319, 0x0001}, /*  +2 */
	{0xC22A, 0xC32A, 0x0001}, /*  +4 */
	{0xC24C, 0xC34C, 0x0001}, /*  +6 */
	{0xC26E, 0xC36E, 0x0001}, /*  +8 */
	{0xC248, 0xC384, 0x0002}, /* +10 */
	{0xC26A, 0xC36A, 0x0002}, /* +12 dB */
};

static unsigned short treble_parm[12][9] = {
	{0x821E, 0xC26A, 0x031E, 0xC36A, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, /* -12 dB */
	{0x821E, 0xC25B, 0x031E, 0xC35B, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
	{0x821E, 0xC24C, 0x031E, 0xC34C, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
	{0x821E, 0xC23D, 0x031E, 0xC33D, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
	{0x821E, 0xC21F, 0x031E, 0xC31F, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
	{0x821E, 0xD208, 0x031E, 0xD308, 0x021E, 0xD208, 0x831E, 0xD308, 0x0002},
	{0x821E, 0xD208, 0x031E, 0xD308, 0x021D, 0xD219, 0x831D, 0xD319, 0x0002},
	{0x821E, 0xD208, 0x031E, 0xD308, 0x021C, 0xD22A, 0x831C, 0xD32A, 0x0002},
	{0x821E, 0xD208, 0x031E, 0xD308, 0x021A, 0xD24C, 0x831A, 0xD34C, 0x0002},
	{0x821E, 0xD208, 0x031E, 0xD308, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, /* +8 (HW default) */
	{0x821D, 0xD219, 0x031D, 0xD319, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002},
	{0x821C, 0xD22A, 0x031C, 0xD32A, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}  /* +12 dB */
};


/*
 * set Emu8000 digital equalizer; from 0 to 11 [-12dB - 12dB]
 */
void
snd_emu8000_equalizer(emu8000_t *emu, int bass, int treble)
{
	unsigned short w;

#ifdef ADD_MODULE_OPTION
	if (! use_mixer)
		return;
#endif
	if (bass < 0 || bass > 11 || treble < 0 || treble > 11)
		return;
	EMU8000_INIT4_WRITE(emu, 0x01, bass_parm[bass][0]);
	EMU8000_INIT4_WRITE(emu, 0x11, bass_parm[bass][1]);
	EMU8000_INIT3_WRITE(emu, 0x11, treble_parm[treble][0]);
	EMU8000_INIT3_WRITE(emu, 0x13, treble_parm[treble][1]);
	EMU8000_INIT3_WRITE(emu, 0x1b, treble_parm[treble][2]);
	EMU8000_INIT4_WRITE(emu, 0x07, treble_parm[treble][3]);
	EMU8000_INIT4_WRITE(emu, 0x0b, treble_parm[treble][4]);
	EMU8000_INIT4_WRITE(emu, 0x0d, treble_parm[treble][5]);
	EMU8000_INIT4_WRITE(emu, 0x17, treble_parm[treble][6]);
	EMU8000_INIT4_WRITE(emu, 0x19, treble_parm[treble][7]);
	w = bass_parm[bass][2] + treble_parm[treble][8];
	EMU8000_INIT4_WRITE(emu, 0x15, (unsigned short)(w + 0x0262));
	EMU8000_INIT4_WRITE(emu, 0x1d, (unsigned short)(w + 0x8362));
}

void
snd_emu8000_update_equalizer(emu8000_t *emu)
{
	snd_emu8000_equalizer(emu, emu->bass_level, emu->treble_level);
}


/*
 * Chorus mode control
 */

/*
 * FIXME: the following parameters should be capsulated in emu8000_t record.
 */

/* 5 parameters for each chorus mode; 3 x 16bit, 2 x 32bit */
static char chorus_defined[SND_EMU8000_CHORUS_NUMBERS];
static soundfont_chorus_fx_t chorus_parm[SND_EMU8000_CHORUS_NUMBERS] = {
	{0xE600, 0x03F6, 0xBC2C ,0x00000000, 0x0000006D}, /* chorus 1 */
	{0xE608, 0x031A, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 2 */
	{0xE610, 0x031A, 0xBC84, 0x00000000, 0x00000083}, /* chorus 3 */
	{0xE620, 0x0269, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 4 */
	{0xE680, 0x04D3, 0xBCA6, 0x00000000, 0x0000005B}, /* feedback */
	{0xE6E0, 0x044E, 0xBC37, 0x00000000, 0x00000026}, /* flanger */
	{0xE600, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay */
	{0xE6C0, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay + feedback */
};

int
snd_emu8000_load_chorus_fx(void *private, int mode, const void *buf, long len)
{
	//emu8000_t *emu = (emu8000_t *)private;
	soundfont_chorus_fx_t rec;

	if (mode < SND_EMU8000_CHORUS_PREDEFINED || mode >= SND_EMU8000_CHORUS_NUMBERS) {
		snd_printk("illegal chorus mode %d for uploading\n", mode);
		return -EINVAL;
	}
	if (len < sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
		return -EFAULT;
	
	chorus_parm[mode] = rec;
	chorus_defined[mode] = 1;
	return 0;
}

void
snd_emu8000_set_chorus_mode(emu8000_t *emu, int effect)
{
#ifdef ADD_MODULE_OPTION
	if (! use_chorus)
		return;
#endif
	if (effect < 0 || effect >= SND_EMU8000_CHORUS_NUMBERS ||
	    (effect >= SND_EMU8000_CHORUS_PREDEFINED && !chorus_defined[effect]))
		return;
	EMU8000_INIT3_WRITE(emu, 0x09, chorus_parm[effect].feedback);
	EMU8000_INIT3_WRITE(emu, 0x0c, chorus_parm[effect].delay_offset);
	EMU8000_INIT4_WRITE(emu, 0x03, chorus_parm[effect].lfo_depth);
	EMU8000_HWCF4_WRITE(emu, chorus_parm[effect].delay);
	EMU8000_HWCF5_WRITE(emu, chorus_parm[effect].lfo_freq);
	EMU8000_HWCF6_WRITE(emu, 0x8000);
	EMU8000_HWCF7_WRITE(emu, 0x0000);
}

void
snd_emu8000_update_chorus_mode(emu8000_t *emu)
{
	snd_emu8000_set_chorus_mode(emu, emu->chorus_mode);
}

/*
 * Reverb mode control
 */

/*
 * FIXME: the following parameters should be capsulated in emu8000_t record.
 */

/* reverb mode settings; write the following 28 data of 16 bit length
 *   on the corresponding ports in the reverb_cmds array
 */
static char reverb_defined[SND_EMU8000_CHORUS_NUMBERS];
static soundfont_reverb_fx_t reverb_parm[SND_EMU8000_REVERB_NUMBERS] = {
{{  /* room 1 */
	0xB488, 0xA450, 0x9550, 0x84B5, 0x383A, 0x3EB5, 0x72F4,
	0x72A4, 0x7254, 0x7204, 0x7204, 0x7204, 0x4416, 0x4516,
	0xA490, 0xA590, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
}},
{{  /* room 2 */
	0xB488, 0xA458, 0x9558, 0x84B5, 0x383A, 0x3EB5, 0x7284,
	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548,
	0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
}},
{{  /* room 3 */
	0xB488, 0xA460, 0x9560, 0x84B5, 0x383A, 0x3EB5, 0x7284,
	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4416, 0x4516,
	0xA490, 0xA590, 0x842C, 0x852C, 0x842C, 0x852C, 0x842B,
	0x852B, 0x842B, 0x852B, 0x842A, 0x852A, 0x842A, 0x852A,
}},
{{  /* hall 1 */
	0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7284,
	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548,
	0xA440, 0xA540, 0x842B, 0x852B, 0x842B, 0x852B, 0x842A,
	0x852A, 0x842A, 0x852A, 0x8429, 0x8529, 0x8429, 0x8529,
}},
{{  /* hall 2 */
	0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7254,
	0x7234, 0x7224, 0x7254, 0x7264, 0x7294, 0x44C3, 0x45C3,
	0xA404, 0xA504, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
}},
{{  /* plate */
	0xB4FF, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7234,
	0x7234, 0x7234, 0x7234, 0x7234, 0x7234, 0x4448, 0x4548,
	0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
}},
{{  /* delay */
	0xB4FF, 0xA470, 0x9500, 0x84B5, 0x333A, 0x39B5, 0x7204,
	0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500,
	0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420,
	0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520,
}},
{{  /* panning delay */
	0xB4FF, 0xA490, 0x9590, 0x8474, 0x333A, 0x39B5, 0x7204,
	0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500,
	0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420,
	0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520,
}},
};

enum { DATA1, DATA2 };
#define AWE_INIT1(c)	EMU8000_CMD(2,c), DATA1
#define AWE_INIT2(c)	EMU8000_CMD(2,c), DATA2
#define AWE_INIT3(c)	EMU8000_CMD(3,c), DATA1
#define AWE_INIT4(c)	EMU8000_CMD(3,c), DATA2

static struct reverb_cmd_pair {
	unsigned short cmd, port;
} reverb_cmds[28] = {
  {AWE_INIT1(0x03)}, {AWE_INIT1(0x05)}, {AWE_INIT4(0x1F)}, {AWE_INIT1(0x07)},
  {AWE_INIT2(0x14)}, {AWE_INIT2(0x16)}, {AWE_INIT1(0x0F)}, {AWE_INIT1(0x17)},
  {AWE_INIT1(0x1F)}, {AWE_INIT2(0x07)}, {AWE_INIT2(0x0F)}, {AWE_INIT2(0x17)},
  {AWE_INIT2(0x1D)}, {AWE_INIT2(0x1F)}, {AWE_INIT3(0x01)}, {AWE_INIT3(0x03)},
  {AWE_INIT1(0x09)}, {AWE_INIT1(0x0B)}, {AWE_INIT1(0x11)}, {AWE_INIT1(0x13)},
  {AWE_INIT1(0x19)}, {AWE_INIT1(0x1B)}, {AWE_INIT2(0x01)}, {AWE_INIT2(0x03)},
  {AWE_INIT2(0x09)}, {AWE_INIT2(0x0B)}, {AWE_INIT2(0x11)}, {AWE_INIT2(0x13)},
};

int
snd_emu8000_load_reverb_fx(void *private, int mode, const void *buf, long len)
{
	//emu8000_t *emu = (emu8000_t *)private;
	soundfont_reverb_fx_t rec;

	if (mode < SND_EMU8000_REVERB_PREDEFINED || mode >= SND_EMU8000_REVERB_NUMBERS) {
		snd_printk("illegal reverb mode %d for uploading\n", mode);
		return -EINVAL;
	}
	if (len < sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
		return -EFAULT;
	
	reverb_parm[mode] = rec;
	reverb_defined[mode] = 1;
	return 0;
}

void
snd_emu8000_set_reverb_mode(emu8000_t *emu, int effect)
{
	int i;
#ifdef ADD_MODULE_OPTION
	if (! use_reverb)
		return;
#endif
	if (effect < 0 || effect >= SND_EMU8000_REVERB_NUMBERS ||
	    (effect >= SND_EMU8000_REVERB_PREDEFINED && !reverb_defined[effect]))
		return;
	for (i = 0; i < 28; i++) {
		int port;
		if (reverb_cmds[i].port == DATA1)
			port = EMU8000_DATA1(emu);
		else
			port = EMU8000_DATA2(emu);
		snd_emu8000_poke(emu, port, reverb_cmds[i].cmd, reverb_parm[effect].parms[i]);
	}
}

void
snd_emu8000_update_reverb_mode(emu8000_t *emu)
{
	snd_emu8000_set_reverb_mode(emu, emu->reverb_mode);
}
