#include "driver.h"
#include "nmk004.h"
#include "sound/2203intf.h"
#include "sound/okim6295.h"



#define FM_CHANNELS			6
#define PSG_CHANNELS		3
#define EFFECTS_CHANNELS	8

#define FM_FLAG_NEED_INITIALIZATION			(1<<0)
#define FM_FLAG_UNKNOWN2					(1<<1)
#define FM_FLAG_NOTE_IS_PAUSE				(1<<2)
#define FM_FLAG_UNKNOWN3					(1<<3)
#define FM_FLAG_MODULATE_NOTE				(1<<4)
#define FM_FLAG_MUST_SEND_KEYON				(1<<5)
#define FM_FLAG_MUST_SEND_CONFIGURATION		(1<<6)
#define FM_FLAG_ACTIVE						(1<<7)

#define PSG_FLAG_ACTIVE						(1<<0)
#define PSG_FLAG_NOTE_IS_PAUSE				(1<<1)
#define PSG_FLAG_NEED_INITIALIZATION		(1<<2)
#define PSG_FLAG_INITIALIZE_VOLUME			(1<<3)
#define PSG_FLAG_NOTE_IS_NOISE				(1<<5)
#define PSG_FLAG_NOISE_NOT_ENABLED			(1<<6)

#define EFFECTS_FLAG_NEED_INITIALIZATION	(1<<0)
#define EFFECTS_FLAG_ACTIVE					(1<<7)

#define NOTE_PAUSE	0x0c

struct psg_control
{
/* C220      */	UINT8  flags;
/* C221-C222 */	UINT16 note_timer;
/* C223-C224 */	UINT16 note_length;
/* C225      */	UINT8  volume_timer;
/* C227-C228 */	UINT16 current;		// current position in control table
/* C229-C22A */	UINT16 return_address[16];	// return address when control table calls a subtable
				int return_address_depth;
/* C22B-C22C */	UINT16 loop_start;	// first instruction of loop
/* C22D      */	UINT8  loop_times;	// number of times to loop
/* C22E      */	UINT8  volume_shape;
/* C22F      */	UINT8  volume_position;
/* C230      */	UINT8  octave;	// base octave
/* C231      */	UINT8  note;	// note to play
/* C233      */	UINT8  note_period_hi_bits;
};

struct fm_control
{
UINT8 note;
/* C020      */	UINT8  flags;
/* C021      */	UINT8  slot;	// for ym2203 keyon command
/* C022-C039 */	UINT8  voice_params[0x18];	// parameters for the YM2203 to configure sound shape
/* C03A-C03B */	UINT16 f_number;
/* C03C      */	UINT8  self_feedback;
/* C03D      */	UINT8  note_duration_table_select;
/* C03E-C03F */	UINT16 current;	// current position in control table
/* C040-C041 */	UINT16 loop_start;	// first instruction of loop
/* C042      */	UINT8  loop_times;	// number of times to loop
/* C043-C044 */	UINT16 return_address[16];	// return address when control table calls a subtable
				int    return_address_depth;
/* C045      */	UINT8  octave;
/* C046-C047 */	UINT16 timer1;
/* C048-C049 */	UINT16 timer2;
/* C04A-C04B */	UINT16 timer1_duration;
/* C04C-C04D */	UINT16 timer2_duration;
/* C04E      */	UINT8  modulation_table_number;
/* C04F-C050 */	UINT16 modulation_timer;
/* C051-C052 */	UINT16 modulation_table;
/* C053-C054 */	UINT16 modulation_table_position;
/* C055-C056 */	UINT16 note_period;
/* C057-C05A */	UINT8  voice_volume[4];	// parameters for the YM2203 to configure sound shape
/* C05C      */ UINT8  must_update_voice_params;
};

struct effects_control
{
/* C1A0      */	UINT8  flags;
/* C1BE-C1BF */	UINT16 current;	// current position in control table
/* C1C0-C1C1 */	UINT16 loop_start;	// first instruction of loop
/* C1C2      */	UINT8  loop_times;	// number of times to loop
/* C1C3-C1C4 */	UINT16 return_address[16];	// return address when control table calls a subtable
				int    return_address_depth;
/* C1C6-C1C7 */	UINT16 timer;
/* C1CA-C1CB */	UINT16 timer_duration;
};

struct
{
	const UINT8 *rom;	// NMK004 data ROM
	UINT8 from_main;	// command from main CPU
	UINT8 to_main;		// answer to main CPU
	int protection_check;

	/* C001      */	UINT8 last_command;		// last command received
	/* C016      */	UINT8 oki_playing;		// bitmap of active Oki channels
	/* C020-C19F */	struct fm_control fm_control[FM_CHANNELS];
	/* C220-C2DF */	struct psg_control psg_control[PSG_CHANNELS];
	/* C1A0-C21F */	struct effects_control effects_control[EFFECTS_CHANNELS];
} NMK004_state;


#define SAMPLE_TABLE_0		0xefe0
#define SAMPLE_TABLE_1		0xefe2
#define FM_MODULATION_TABLE	0xefe4
#define FM_NOTE_TABLE		0xefe6
#define NOTE_LENGTH_TABLE_1	0xefe8
#define NOTE_LENGTH_TABLE_2	0xefea
#define NOTE_LENGTH_TABLE_3	0xefec
#define PSG_VOLUME_TABLE	0xefee
#define COMMAND_TABLE		0xeff0
#define PSG_NOTE_TABLE		0xeff2


static UINT8 read8(int address)
{
	return NMK004_state.rom[address];
}

static UINT16 read16(int address)
{
	return NMK004_state.rom[address] + 256 * NMK004_state.rom[address+1];
}



/*****************************

          OKI6295

*****************************/

static void oki_play_sample(int sample_no)
{
	UINT16 table_start = (sample_no & 0x80) ? read16(SAMPLE_TABLE_1) : read16(SAMPLE_TABLE_0);
	UINT8 byte1 = read8(table_start + 2 * (sample_no & 0x7f) + 0);
	UINT8 byte2 = read8(table_start + 2 * (sample_no & 0x7f) + 1);
	int chip = (byte1 & 0x80) >> 7;

	if ((byte1 & 0x7f) == 0)
	{
		// stop all channels
		if (chip == 0)
			OKIM6295_data_0_w(0, 0x78 );
		else
			OKIM6295_data_1_w(0, 0x78 );
	}
	else
	{
		int sample = byte1 & 0x7f;
		int ch = byte2 & 0x03;
		int force = (byte2 & 0x80) >> 7;

		if (!force && (NMK004_state.oki_playing & (1 << (ch + 4*chip))))
			return;

		NMK004_state.oki_playing |= 1 << (ch + 4*chip);

		// stop channel
		if (chip == 0)
			OKIM6295_data_0_w(0, (0x08 << ch) );
		else
			OKIM6295_data_1_w(0, (0x08 << ch) );

		if (sample != 0)
		{
			UINT8 *rom = memory_region(REGION_SOUND1 + chip);
			int bank = (byte2 & 0x0c) >> 2;
			int vol = (byte2 & 0x70) >> 4;

			if (bank != 3)
				memcpy(rom + 0x20000,rom + 0x40000 + bank * 0x20000,0x20000);

			if (chip == 0)
			{
				OKIM6295_data_0_w(0, 0x80 | sample );
				OKIM6295_data_0_w(0, (0x10 << ch) | vol );
			}
			else
			{
				OKIM6295_data_1_w(0, 0x80 | sample );
				OKIM6295_data_1_w(0, (0x10 << ch) | vol );
			}
		}
	}
}

static void oki_update_state(void)
{
	NMK004_state.oki_playing = ((OKIM6295_status_1_r(0) & 0x0f) << 4) | (OKIM6295_status_0_r(0) & 0x0f);
}



/*****************************

     EFFECTS (OKI6295)

*****************************/

static void effects_update(int channel)
{
	struct effects_control *effects = &NMK004_state.effects_control[channel];

	// advance the timers
	if (effects->timer)
		effects->timer--;


	if (effects->flags & EFFECTS_FLAG_NEED_INITIALIZATION)
	{
		effects->flags = EFFECTS_FLAG_ACTIVE;
		effects->timer = 0;
	}


	if (effects->flags & EFFECTS_FLAG_ACTIVE)
	{
		if (effects->timer == 0)
		{
			UINT8 token;

			do
			{
//logerror("channel %d address %04x token %02x\n",channel,effects->current,read8(effects->current));
				token = read8(effects->current++);

				if (token == 0x0ef || (token & 0xf0) == 0xf0)
				{
					switch (token)
					{
						case 0xef:	// play sample
							oki_play_sample(read8(effects->current++));
							break;

						case 0xf6:	// jump
							effects->current = read16(effects->current);
							break;

						case 0xf7:	// begin repeat loop
							effects->loop_times = read8(effects->current++);
							effects->loop_start = effects->current;
							break;

						case 0xf8:	// end repeat loop
							if (--effects->loop_times > 0)
								effects->current = effects->loop_start;
							break;

						case 0xf9:	// call subtable
							effects->return_address[effects->return_address_depth++] = effects->current + 2;
							effects->current = read16(effects->current);
							break;

						case 0xfa:	// return from subtable
							effects->current = effects->return_address[--effects->return_address_depth];
							break;

						case 0xfc:	// ??? (hachamf command 04)
							break;

						case 0xfd:	// ??? (hachamf command 04)
							break;

						case 0xff:	// end
							effects->flags = 0;	// disable channel
							return;

						default:
							fatalerror("effects channel %d unsupported token %02x",channel,token);
					}
				}
			} while (token == 0xef || (token & 0xf0) == 0xf0);

			effects->current--;

			if ((read8(effects->current) & 0x80) == 0)
			{
				if (read8(effects->current++) != 0x0c)
				{
					// this shouldn't happen on the effects channels (but it happens e.g. hachamf command 04)

					logerror("effects channel %d invalid token %02x\n",channel,read8(effects->current));
				}
			}

			// optional note length (otherwise use the same length as the previous one)
			if (read8(effects->current) & 0x80)
			{
				UINT16 table_start = read16(NOTE_LENGTH_TABLE_1);

				effects->timer_duration = read16(table_start + 2 * (read8(effects->current++) & 0x7f));
			}

			effects->timer = effects->timer_duration;
		}
	}
}



/*****************************

        YM2203 - FM

*****************************/

static void fm_update(int channel)
{
	struct fm_control *fm = &NMK004_state.fm_control[channel];

	// advance the timers
	if (fm->timer1)
		fm->timer1--;

	if (fm->timer2)
		fm->timer2--;

	if (fm->modulation_timer)
		fm->modulation_timer--;


	if (fm->flags & FM_FLAG_NEED_INITIALIZATION)
	{
		fm->flags = FM_FLAG_ACTIVE;
		fm->timer1 = 0;
	}


	if (fm->flags & FM_FLAG_ACTIVE)
	{
		if (fm->timer1 == 0)
		{
			UINT8 token;

			do
			{
//logerror("channel %d address %04x token %02x\n",channel,fm->current,read8(fm->current));
				token = read8(fm->current++);

				if (token == 0x0ef || (token & 0xf0) == 0xf0)
				{
					int i;

					switch (token)
					{
//                      case 0xef:  // play sample
//                          oki_play_sample(read8(fm->current++));
//                          break;

						case 0xf0:	// slot (for keyon ym2203 command)
							fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION;
							fm->slot = read8(fm->current++);
							if (channel < 3 || !(NMK004_state.fm_control[channel-3].flags & FM_FLAG_ACTIVE))
							{
								YM2203_control_port_0_w(0, 0x28);	// keyon/off
								YM2203_write_port_0_w(0, channel % 3);
							}
							break;

						case 0xf1:	// sound shape
							fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION;
							for (i = 0x00; i < 0x04; i++)
								fm->voice_params[i] = read8(fm->current++);
							break;

						case 0xf2:	// sound shape
							fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION;
							for (i = 0; i < 4; i++)
								fm->voice_volume[i] = read8(fm->current++);
							break;

						case 0xf3:	// sound shape
							fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION;
							for (i = 0x08; i < 0x18; i++)
								fm->voice_params[i] = read8(fm->current++);
							break;

						case 0xf4:	// set self-feedback
							fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION;
							fm->self_feedback = read8(fm->current++);
							break;

						case 0xf5:	// select note duration table
							fm->note_duration_table_select = read8(fm->current++);
							break;

						case 0xf6:	// jump
							fm->current = read16(fm->current);
							break;

						case 0xf7:	// begin repeat loop
							fm->loop_times = read8(fm->current++);
							fm->loop_start = fm->current;
							break;

						case 0xf8:	// end repeat loop
							if (--fm->loop_times > 0)
								fm->current = fm->loop_start;
							break;

						case 0xf9:	// call subtable
							fm->return_address[fm->return_address_depth++] = fm->current + 2;
							fm->current = read16(fm->current);
							break;

						case 0xfa:	// return from subtable
							fm->current = fm->return_address[--fm->return_address_depth];
							break;

						case 0xfb:	// set octave
							fm->octave = read8(fm->current++);
							break;

						case 0xfc:	// ???
							fm->flags |=  FM_FLAG_UNKNOWN2;
							fm->flags |=  FM_FLAG_UNKNOWN3;
							break;

						case 0xfd:	// ???
							fm->flags &= ~FM_FLAG_UNKNOWN2;
							break;

						case 0xfe:	// set note modulation
							fm->modulation_table_number = read8(fm->current++);
							if (fm->modulation_table_number == 0)
							{
								fm->flags &= ~FM_FLAG_MODULATE_NOTE;
							}
							else
							{
								UINT16 table_start = read16(FM_MODULATION_TABLE);

								fm->modulation_table = read16(table_start + 2 * (fm->modulation_table_number - 1));
								fm->modulation_timer = read16(fm->modulation_table);
								fm->modulation_table_position = fm->modulation_table + 2;
								fm->flags |= FM_FLAG_MODULATE_NOTE;
							}
							break;

						case 0xff:	// end
							fm->flags = FM_FLAG_MUST_SEND_CONFIGURATION;	// disable channel
							for (i = 0x04; i < 0x08; i++)
								fm->voice_params[i] = 0x7f;
							for (i = 0x14; i < 0x18; i++)
								fm->voice_params[i] = 0x0f;
							return;

						default:
							fatalerror("fm channel %d unsupported token %02x",channel,token);
					}
				}
			} while (token == 0xef || (token & 0xf0) == 0xf0);

			fm->current--;

			if ((read8(fm->current) & 0x80) == 0)
			{
				int note = read8(fm->current++);

fm->note = note;
				if ((note & 0x0f) == NOTE_PAUSE)
					fm->flags |=  FM_FLAG_NOTE_IS_PAUSE;
				else
				{
					UINT16 table_start = read16(FM_NOTE_TABLE);
					UINT16 period = read16(table_start + 2 * (note & 0x0f));
					UINT8 octave = ((fm->octave << 4) + note) & 0xf0;

					fm->flags &= ~FM_FLAG_NOTE_IS_PAUSE;
					fm->note_period = period | (octave << 7);

					fm->must_update_voice_params = 1;
				}
			}

			// optional note length (otherwise use the same length as the previous one)
			if (read8(fm->current) & 0x80)
			{
				UINT16 table_start;
				UINT8 duration = read8(fm->current++) & 0x7f;

				table_start = read16(NOTE_LENGTH_TABLE_1);
				fm->timer1_duration = read16(table_start + 2 * duration);

				table_start = fm->note_duration_table_select ? read16(NOTE_LENGTH_TABLE_3) : read16(NOTE_LENGTH_TABLE_2);
				fm->timer2_duration = read16(table_start + 2 * duration);
			}

			fm->timer1 = fm->timer1_duration;
			fm->timer2 = fm->timer2_duration;


			if (!(fm->flags & FM_FLAG_NOTE_IS_PAUSE) &&
				((fm->flags & FM_FLAG_UNKNOWN3) || !(fm->flags & FM_FLAG_UNKNOWN2)))
			{
				fm->flags &= ~FM_FLAG_UNKNOWN3;
				fm->flags |=  FM_FLAG_MUST_SEND_KEYON;
				fm->flags |=  FM_FLAG_MUST_SEND_CONFIGURATION;
			}
		}
	}



	if ((fm->flags & FM_FLAG_MODULATE_NOTE) && (fm->flags & FM_FLAG_MUST_SEND_KEYON))
	{
		fm->modulation_timer = read16(fm->modulation_table);
		fm->modulation_table_position = fm->modulation_table + 2;
	}

	if (!(fm->flags & FM_FLAG_MODULATE_NOTE) || (fm->flags & FM_FLAG_MUST_SEND_KEYON) || fm->must_update_voice_params)
	{
		int i;

		fm->must_update_voice_params = 0;

		for (i = 0; i < 4; i++)
			fm->voice_params[0x04 + i] = fm->voice_volume[i];

		fm->f_number = fm->note_period;
	}
	else
	{
		if (fm->modulation_timer == 0)
		{
			int i;
			UINT16 a;

			for (i = 0; i < 4; i++)
				fm->voice_params[0x04 + i] = fm->voice_volume[i];

			fm->modulation_table_position++;
			a = read8(fm->modulation_table_position++);
			if (a & 0x80)	// sign extend
				a |= 0xff00;
			a *= 4;

			fm->f_number = fm->note_period + a;

			fm->modulation_timer = read8(fm->modulation_table_position++);	// modulation_timer is UINT16 but this is just 8-bit

			if (read8(fm->modulation_table_position) == 0x80)	// end of table - repeat
			{
				fm->modulation_table_position = fm->modulation_table + 2;
			}
			else if (read8(fm->modulation_table_position) == 0x88)	// end of table - stop
			{
				fm->flags &= ~FM_FLAG_MODULATE_NOTE;
			}
		}
	}


#if 0
popmessage("%02x %02x %02x %02x %02x %02x",
		NMK004_state.fm_control[0].note,
		NMK004_state.fm_control[1].note,
		NMK004_state.fm_control[2].note,
		NMK004_state.fm_control[3].note,
		NMK004_state.fm_control[4].note,
		NMK004_state.fm_control[5].note);
#endif
#if 0
popmessage("%02x %02x%02x%02x%02x %02x %02x%02x%02x%02x %02x %02x%02x%02x%02x",
		NMK004_state.fm_control[3].note,
		NMK004_state.fm_control[3].voice_volume[0],
		NMK004_state.fm_control[3].voice_volume[1],
		NMK004_state.fm_control[3].voice_volume[2],
		NMK004_state.fm_control[3].voice_volume[3],
		NMK004_state.fm_control[4].note,
		NMK004_state.fm_control[4].voice_volume[0],
		NMK004_state.fm_control[4].voice_volume[1],
		NMK004_state.fm_control[4].voice_volume[2],
		NMK004_state.fm_control[4].voice_volume[3],
		NMK004_state.fm_control[5].note,
		NMK004_state.fm_control[5].voice_volume[0],
		NMK004_state.fm_control[5].voice_volume[1],
		NMK004_state.fm_control[5].voice_volume[2],
		NMK004_state.fm_control[5].voice_volume[3]);
#endif
}


static void fm_voices_update(void)
{
	static int ym2203_registers[0x18] =
	{
		0x30,0x38,0x34,0x3C,0x40,0x48,0x44,0x4C,0x50,0x58,0x54,0x5C,0x60,0x68,0x64,0x6C,
		0x70,0x78,0x74,0x7C,0x80,0x88,0x84,0x8C
	};
	int channel,i;

	for (channel = 0; channel < 3;channel++)
	{
		struct fm_control *fm1 = &NMK004_state.fm_control[channel];
		struct fm_control *fm2 = &NMK004_state.fm_control[channel + 3];

		if (fm1->flags &  FM_FLAG_MUST_SEND_CONFIGURATION)
		{
			fm1->flags &= ~FM_FLAG_MUST_SEND_CONFIGURATION;

			for (i = 0; i < 0x18; i++)
			{
				YM2203_control_port_0_w(0, ym2203_registers[i] + channel);
				YM2203_write_port_0_w(0, fm1->voice_params[i]);
			}
		}

		if (fm2->flags &  FM_FLAG_MUST_SEND_CONFIGURATION)
		{
			fm2->flags &= ~FM_FLAG_MUST_SEND_CONFIGURATION;

			if (!(fm1->flags & FM_FLAG_ACTIVE))
			{
				for (i = 0; i < 0x18; i++)
				{
					YM2203_control_port_0_w(0, ym2203_registers[i] + channel);
					YM2203_write_port_0_w(0, fm2->voice_params[i]);
				}
			}
		}


		if (fm1->flags & FM_FLAG_ACTIVE)
		{
			YM2203_control_port_0_w(0, 0xb0 + channel);	// self-feedback
			YM2203_write_port_0_w(0, fm1->self_feedback);

			YM2203_control_port_0_w(0, 0xa4 + channel);	// F-number
			YM2203_write_port_0_w(0, fm1->f_number >> 8);

			YM2203_control_port_0_w(0, 0xa0 + channel);	// F-number
			YM2203_write_port_0_w(0, fm1->f_number & 0xff);
		}
		else
		{
			YM2203_control_port_0_w(0, 0xb0 + channel);	// self-feedback
			YM2203_write_port_0_w(0, fm2->self_feedback);

			YM2203_control_port_0_w(0, 0xa4 + channel);	// F-number
			YM2203_write_port_0_w(0, fm2->f_number >> 8);

			YM2203_control_port_0_w(0, 0xa0 + channel);	// F-number
			YM2203_write_port_0_w(0, fm2->f_number & 0xff);
		}



		if (fm1->flags & FM_FLAG_MUST_SEND_KEYON)
		{
			fm1->flags &= ~FM_FLAG_MUST_SEND_KEYON;

			YM2203_control_port_0_w(0, 0x28);	// keyon/off
			YM2203_write_port_0_w(0, fm1->slot | channel);
		}

		if (fm2->flags & FM_FLAG_MUST_SEND_KEYON)
		{
			fm2->flags &= ~FM_FLAG_MUST_SEND_KEYON;

			if (!(fm1->flags & FM_FLAG_ACTIVE))
			{
				YM2203_control_port_0_w(0, 0x28);	// keyon/off
				YM2203_write_port_0_w(0, fm2->slot | channel);
			}
		}
	}
}



/*****************************

        YM2203 - PSG

*****************************/

static void psg_update(int channel)
{
	struct psg_control *psg = &NMK004_state.psg_control[channel];

	// advance the timers
	if (psg->note_timer)
		psg->note_timer--;

	if (psg->volume_timer)
		psg->volume_timer--;

	if (psg->flags & PSG_FLAG_NEED_INITIALIZATION)
	{
		psg->flags &= ~PSG_FLAG_NEED_INITIALIZATION;
		psg->flags |=  PSG_FLAG_ACTIVE;

		if (psg->flags & PSG_FLAG_NOTE_IS_NOISE)
		{
			int enable;

			psg->flags &= ~PSG_FLAG_NOTE_IS_NOISE;
			psg->flags &= ~PSG_FLAG_NOISE_NOT_ENABLED;

			// enable noise, disable tone on this channel
			YM2203_control_port_0_w(0, 0x07);
			enable = YM2203_read_port_0_r(0);
			enable |=  (0x01 << channel);	// disable tone
			enable &= ~(0x08 << channel);	// enable noise
			YM2203_write_port_0_w(0, enable);
		}


		psg->note_timer = 0;
		psg->volume_timer = 0;
		psg->octave = 0;
	}

	if (psg->flags & PSG_FLAG_ACTIVE)
	{
		if (psg->note_timer == 0)
		{
			UINT8 token;

			do
			{
				token = read8(psg->current++);

				if ((token & 0xf0) == 0xf0)
				{
					int enable;

					switch (token)
					{
						case 0xf0:	// noise
							psg->flags |= PSG_FLAG_NOTE_IS_NOISE;
							break;

						case 0xf1:	// note
							psg->flags &= ~PSG_FLAG_NOTE_IS_NOISE;
							psg->flags &= ~PSG_FLAG_NOISE_NOT_ENABLED;

							// enable noise, disable tone on this channel
							YM2203_control_port_0_w(0, 0x07);
							enable = YM2203_read_port_0_r(0);
							enable |=  (0x01 << channel);	// disable tone
							enable &= ~(0x08 << channel);	// enable noise
							YM2203_write_port_0_w(0, enable);
							break;

						case 0xf2:	// set volume shape
						case 0xf3:
						case 0xf4:
						case 0xf5:
							psg->volume_shape = read8(psg->current++);
							break;

						case 0xf6:	// jump
							psg->current = read16(psg->current);
							break;

						case 0xf7:	// begin repeat loop
							psg->loop_times = read8(psg->current++);
							psg->loop_start = psg->current;
							break;

						case 0xf8:	// end repeat loop
							if (--psg->loop_times > 0)
								psg->current = psg->loop_start;
							break;

						case 0xf9:	// call subtable
							psg->return_address[psg->return_address_depth++] = psg->current + 2;
							psg->current = read16(psg->current);
							break;

						case 0xfa:	// return from subtable
							psg->current = psg->return_address[--psg->return_address_depth];
							break;

						case 0xfb:	// set octave
						case 0xfc:
						case 0xfd:
						case 0xfe:
							psg->octave = read8(psg->current++);
							break;

						case 0xff:	// end
							psg->flags = 0;	// disable channel
							psg->volume_shape = 0;

							// mute channel
							YM2203_control_port_0_w(0, 8 + channel);
							YM2203_write_port_0_w(0, 0);
							return;
					}
				}
			} while ((token & 0xf0) == 0xf0);

			// token is the note to play
			psg->note = token;
			if ((psg->note & 0x0f) > NOTE_PAUSE)
			{
				fatalerror("PSG channel %d invalid note %02x",channel,psg->note);
			}

			// optional note length (otherwise use the same length as the previous one)
			if (read8(psg->current) & 0x80)
			{
				UINT16 table_start = read16(NOTE_LENGTH_TABLE_1);

				psg->note_length = read16(table_start + 2 * (read8(psg->current++) & 0x7f));
			}

			psg->note_timer = psg->note_length;
			psg->volume_timer = 0;

			psg->flags |= PSG_FLAG_INITIALIZE_VOLUME;

			if (psg->note == NOTE_PAUSE)
				psg->flags |=  PSG_FLAG_NOTE_IS_PAUSE;
			else
			{
				psg->flags &= ~PSG_FLAG_NOTE_IS_PAUSE;

				if (!(psg->flags & PSG_FLAG_NOTE_IS_NOISE))
				{
					UINT16 table_start = read16(PSG_NOTE_TABLE);
					UINT16 period = read16(table_start + 2 * (psg->note & 0x0f));
					UINT8 octave = psg->octave + ((psg->note & 0xf0) >> 4);

					period >>= octave;

					YM2203_control_port_0_w(0, 2 * channel + 1);
					YM2203_write_port_0_w(0, (period & 0x0f00) >> 8);
					YM2203_control_port_0_w(0, 2 * channel + 0);
					YM2203_write_port_0_w(0, (period & 0x00ff));

					psg->note_period_hi_bits = (period & 0x0f00) >> 8;
				}
				else
				{
					if (!(psg->flags & PSG_FLAG_NOISE_NOT_ENABLED))
					{
						int enable;

						psg->flags |= PSG_FLAG_NOISE_NOT_ENABLED;

						// disable noise, enable tone on this channel
						YM2203_control_port_0_w(0, 0x07);
						enable = YM2203_read_port_0_r(0);
						enable &= ~(0x01 << channel);	// enable tone
						enable |=  (0x08 << channel);	// disable noise
						YM2203_write_port_0_w(0, enable);
					}

					YM2203_control_port_0_w(0, 0x06);	// noise period
					YM2203_write_port_0_w(0, psg->note);
					psg->note_period_hi_bits = psg->note;
				}
			}
		}

		if (psg->volume_timer == 0)
		{
			UINT16 table_start = read16(PSG_VOLUME_TABLE);
			UINT16 vol_table_start = read16(table_start + 2 * psg->volume_shape);
			int volume;

			if (psg->flags & PSG_FLAG_INITIALIZE_VOLUME)
			{
				psg->flags &= ~PSG_FLAG_INITIALIZE_VOLUME;
				psg->volume_position = 0;
			}

			volume = read8(vol_table_start + psg->volume_position++);
			psg->volume_timer = read8(vol_table_start + psg->volume_position++);

			if (psg->flags & PSG_FLAG_NOTE_IS_PAUSE)
				volume = 0;

			// set volume
			YM2203_control_port_0_w(0, 8 + channel);
			YM2203_write_port_0_w(0, volume & 0x0f);
		}
	}
}



/*****************************

     Command processing

*****************************/

static void get_command(void)
{
	static UINT8 from_main[] =
	{
		0x00,0x22,0x62,0x8c,0xc7,0x00,0x00,0x3f,0x7f,0x89,0xc7,0x00,0x00,0x2b,0x6b
	};
	static UINT8 to_main[] =
	{
		0x82,0xc7,0x00,0x2c,0x6c,0x00,0x9f,0xc7,0x00,0x29,0x69,0x00,0x8b,0xc7,0x00
	};

	UINT8 cmd = NMK004_state.from_main;

	if (NMK004_state.protection_check < sizeof(to_main))
	{
		// startup handshake
		if (cmd == from_main[NMK004_state.protection_check])
		{
			logerror("advance handshake to %02x\n",to_main[NMK004_state.protection_check]);
			NMK004_state.to_main = to_main[NMK004_state.protection_check++];
		}
	}
	else
	{
		// send command back to main CPU to acknowledge reception
		NMK004_state.to_main = cmd;
	}

	if (NMK004_state.last_command != cmd)
	{
		UINT16 table_start = read16(COMMAND_TABLE);
		UINT16 cmd_table = read16(table_start + 2 * cmd);

		NMK004_state.last_command = cmd;

		if ((cmd_table & 0xff00) == 0)
		{
			oki_play_sample(cmd_table);
		}
		else
		{
			int channel;

			while ((channel = read8(cmd_table++)) != 0xff)
			{
				table_start = read16(cmd_table);
				cmd_table += 2;

				if (channel == 0xef) break;	// bioship bug?

//logerror("%04x: channel %d table %04x\n",cmd_table-3,channel,table_start);
				if (channel < FM_CHANNELS)
				{
					NMK004_state.fm_control[channel].current = table_start;
					NMK004_state.fm_control[channel].return_address_depth = 0;
					NMK004_state.fm_control[channel].flags |= FM_FLAG_NEED_INITIALIZATION;
				}
				else
				{
					channel -= FM_CHANNELS;
					if (channel < PSG_CHANNELS)
					{
						NMK004_state.psg_control[channel].current = table_start;
						NMK004_state.psg_control[channel].return_address_depth = 0;
						NMK004_state.psg_control[channel].flags |= PSG_FLAG_NEED_INITIALIZATION;
					}
					else
					{
						channel -= PSG_CHANNELS;
						if (channel >= EFFECTS_CHANNELS)
						{
							fatalerror("too many effects channels");
						}
						NMK004_state.effects_control[channel].current = table_start;
						NMK004_state.effects_control[channel].return_address_depth = 0;
						NMK004_state.effects_control[channel].flags |= EFFECTS_FLAG_NEED_INITIALIZATION;
					}
				}
			}
		}
	}
}



static void update_music(void)
{
	int channel;

	for (channel = 0; channel < FM_CHANNELS; channel++)
		fm_update(channel);
	fm_voices_update();

	for (channel = 0; channel < PSG_CHANNELS; channel++)
		psg_update(channel);

	for (channel = 0; channel < EFFECTS_CHANNELS; channel++)
		effects_update(channel);
}



void NMK004_irq(int irq)
{
	int status;

	if (!irq) return;

	status = YM2203_status_port_0_r(0);

	if (status & 1)	// timer A expired
	{
		oki_update_state();
		get_command();
		update_music();

		// restart timer
		YM2203_control_port_0_w(0, 0x27);
		YM2203_write_port_0_w(0, 0x15);
	}
}


static void real_nmk004_init(int param)
{
	static UINT8 ym2203_init[] =
	{
		0x07,0x38,0x08,0x00,0x09,0x00,0x0A,0x00,0x24,0xB3,0x25,0x00,0x26,0xF9,0x27,0x15,
		0x28,0x00,0x28,0x01,0x28,0x02,0x40,0x00,0x41,0x00,0x42,0x00,0x44,0x00,0x45,0x00,
		0x46,0x00,0x48,0x00,0x49,0x00,0x4A,0x00,0x4C,0x00,0x4D,0x00,0x4E,0x00,0xFF,
	};
	int i;

	memset(&NMK004_state, 0, sizeof(NMK004_state));

	NMK004_state.rom = memory_region(REGION_CPU2);

	YM2203_control_port_0_w(0, 0x2f);

	i = 0;
	while (ym2203_init[i] != 0xff)
	{
		YM2203_control_port_0_w(0, ym2203_init[i++]);
		YM2203_write_port_0_w(0, ym2203_init[i++]);
	}

	NMK004_state.oki_playing = 0;

	oki_play_sample(0);

	NMK004_state.protection_check = 0;
}

void NMK004_init(void)
{
	/* we have to do this via a timer because we get called before the sound reset */
	mame_timer_set(time_zero, 0, real_nmk004_init);
}


WRITE16_HANDLER( NMK004_w )
{
	if (ACCESSING_LSB)
	{
logerror("%06x: NMK004_w %02x\n",activecpu_get_pc(),data);
		NMK004_state.from_main = data & 0xff;
	}
}

READ16_HANDLER( NMK004_r )
{
static int last;
	int res = NMK004_state.to_main;

if (res != last) logerror("%06x: NMK004_r %02x\n",activecpu_get_pc(),res);
last = res;

	return res;
}

