/*	
 *	clipdms2.c
 *
 *	P/ECE DMG-Sound Emulator (高速版)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Mon Feb 28 20:00:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

static void dmgsound2_speaker_volume();

/****************************************************************************
 *	グローバル変数
 ****************************************************************************/

DMGSOUND2 dmgsound2;
char dmgsound2_wave_pattern[32];

/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/

void
dmgsound2_reset()
{
	DMGSOUND2* sound = &dmgsound2;

	memset(sound, 0, sizeof(DMGSOUND2));
}

int
dmgsound2_read(int addr)
{
	DMGSOUND2* sound = &dmgsound2;
	//
	int data = 0;

	switch(addr) {
	case 0xff10: /* $FF10 (NR10) */
		data = sound->nr10;
		break;
	case 0xff11: /* $FF11 (NR11) */
		data = sound->nr11;
		break;
	case 0xff12: /* $FF12 (NR12) */
		data = sound->nr12;
		break;
	case 0xff13: /* $FF13 (NR13) */
		data = sound->nr13;
		break;
	case 0xff14: /* $FF14 (NR14) */
		data = sound->nr14;
		break;
	case 0xff16: /* $FF16 (NR21) */
		data = sound->nr21;
		break;
	case 0xff17: /* $FF17 (NR22) */
		data = sound->nr22;
		break;
	case 0xff18: /* $FF18 (NR23) */
		data = sound->nr23;
		break;
	case 0xff19: /* $FF19 (NR24) */
		data = sound->nr24;
		break;
	case 0xff1a: /* $FF1A (NR30) */
		data = sound->nr30;
		break;
	case 0xff1b: /* $FF1B (NR31) */
		data = sound->nr31;
		break;
	case 0xff1c: /* $FF1C (NR32) */
		data = sound->nr32;
		break;
	case 0xff1d: /* $FF1D (NR33) */
		data = sound->nr33;
		break;
	case 0xff1e: /* $FF1E (NR34) */
		data = sound->nr34;
		break;
	case 0xff20: /* $FF20 (NR41) */
		data = sound->nr41;
		break;
	case 0xff21: /* $FF21 (NR42) */
		data = sound->nr42;
		break;
	case 0xff22: /* $FF22 (NR43) */
		data = sound->nr43;
		break;
	case 0xff23: /* $FF23 (NR44) */
		data = sound->nr44;
		break;
	case 0xff24: /* $FF24 (NR50) */
		data = sound->nr50;
		break;
	case 0xff25: /* $FF25 (NR51) */
		data = sound->nr51;
		break;
	case 0xff26: /* $FF26 (NR52) */
		data = sound->nr52 & (1<<7); /* All sound on/off */
		if(data) {
			if(sound->ch1.sound_length) {
				data |= (1<<0);
			}
			if(sound->ch2.sound_length) {
				data |= (1<<1);
			}
			if(sound->ch3.sound_length) {
				if(sound->nr30 & (1<<7)) { /* Sound on/off */
					data |= (1<<2);
				}
			}
			if(sound->ch4.sound_length) {
				data |= (1<<3);
			}
		}
		break;
	case 0xff30: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff31: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff32: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff33: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff34: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff35: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff36: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff37: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff38: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff39: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3a: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3b: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3c: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3d: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3e: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3f: /* $FF30-$FF3F (Wave Pattern RAM) */
		addr -= 0xff30;
		data = ((dmgsound2_wave_pattern[addr * 2 + 0] + 15) / 2) << 4 /* D7:4: -15..+15 -> 0..15 */
		     | ((dmgsound2_wave_pattern[addr * 2 + 1] + 15) / 2);     /* D3:0: -15..+15 -> 0..15 */
		break;
	}

	return data;
}

void
dmgsound2_write(int addr, int data)
{
	DMGSOUND2* sound = &dmgsound2;

	switch(addr) {
	/*** Ch1: 矩形波 (Sweep有り) ***/
	case 0xff10: /* $FF10 (NR10) */
		sound->nr10 = data;
		/* Sweep */
		sound->ch1.sweep_time = ((data >> 4) & 7) << 1;
		sound->ch1.sweep_type = (data >> 3) & 1;
		sound->ch1.sweep_shift = data & 7;
		sound->ch1.sweep_count = sound->ch1.sweep_time;
		return;
	case 0xff11: /* $FF11 (NR11) */
		sound->nr11 = data;
		/* Wave */
		sound->ch1.wave_duty = ((data >> 6) & 3) << 1;
		if(!sound->ch1.wave_duty) {
			sound->ch1.wave_duty = 1;
		}
		sound->ch1.wave_index = 0;
		return;
	case 0xff12: /* $FF12 (NR12) */
		sound->nr12 = data;
		/* Envelope */
		sound->ch1.envelope_volume = (data >> 4) & 15;
		sound->ch1.envelope_type = (data >> 3) & 1;
		sound->ch1.envelope_time = (data & 7) << 2;
		sound->ch1.envelope_count = sound->ch1.envelope_time;
		return;
	case 0xff13: /* $FF13 (NR13) */
		sound->nr13 = data;
		/* Wave */
		sound->ch1.wave_frequency = data | (sound->nr14 & 7) << 8;
		return;
	case 0xff14: /* $FF14 (NR14) */
		sound->nr14 = data;
		/* Wave */
		sound->ch1.wave_frequency = sound->nr13 | (data & 7) << 8;
		if(data & (1<<7)) {
			/* Length */
			if(data & (1<<6)) {
				sound->ch1.sound_length = 64 - (sound->nr11 & 63);
			} else {
				sound->ch1.sound_length = -1;
			}
			sound->ch1.wave_count = 0;
		}
		return;

	/*** Ch2: 矩形波 (Sweep無し) ***/
	case 0xff16: /* $FF16 (NR21) */
		sound->nr21 = data;
		/* Wave */
		sound->ch2.wave_duty = ((data >> 6) & 3) << 1;
		if(!sound->ch2.wave_duty) {
			sound->ch2.wave_duty = 1;
		}
		sound->ch2.wave_index = 0;
		return;
	case 0xff17: /* $FF17 (NR22) */
		sound->nr22 = data;
		/* Envelope */
		sound->ch2.envelope_volume = (data >> 4) & 15;
		sound->ch2.envelope_type = (data >> 3) & 1;
		sound->ch2.envelope_time = (data & 7) << 2;
		sound->ch2.envelope_count = sound->ch2.envelope_time;
		return;
	case 0xff18: /* $FF18 (NR23) */
		sound->nr23 = data;
		/* Wave */
		sound->ch2.wave_frequency = data | (sound->nr24 & 7) << 8;
		return;
	case 0xff19: /* $FF19 (NR24) */
		sound->nr24 = data;
		/* Wave */
		sound->ch2.wave_frequency = sound->nr23 | (data & 7) << 8;
		if(data & (1<<7)) {
			/* Length */
			if(data & (1<<6)) {
				sound->ch2.sound_length = 64 - (sound->nr21 & 63);
			} else {
				sound->ch2.sound_length = -1;
			}
			sound->ch2.wave_count = 0;
		}
		return;

	/*** Ch3: 波形メモリ ***/
	case 0xff1a: /* $FF1A (NR30) */
		sound->nr30 = data;
		return;
	case 0xff1b: /* $FF1B (NR31) */
		sound->nr31 = data;
		return;
	case 0xff1c: /* $FF1C (NR32) */
		sound->nr32 = data;
		/* Wave */
		sound->ch3.wave_shift = (data >> 5) & 3;
		return;
	case 0xff1d: /* $FF1D (NR33) */
		sound->nr33 = data;
		/* Wave */
		sound->ch3.wave_frequency = data | (sound->nr34 & 7) << 8;
		return;
	case 0xff1e: /* $FF1E (NR34) */
		sound->nr34 = data;
		/* Wave */
		sound->ch3.wave_frequency = sound->nr33 | (data & 7) << 8;
		if(data & (1<<7)) {
			/* Length */
			if(data & (1<<6)) {
				sound->ch3.sound_length = (256 - sound->nr31) << 7;
			} else {
				sound->ch3.sound_length = -1;
			}
			sound->ch3.wave_count = 0;
		}
		return;

	/*** Ch4: ノイズ ***/
	case 0xff20: /* $FF20 (NR41) */
		sound->nr41 = data;
		return;
	case 0xff21: /* $FF21 (NR42) */
		sound->nr42 = data;
		/* Envelope */
		sound->ch4.envelope_volume = (data >> 4) & 15;
		sound->ch4.envelope_type = (data >> 3) & 1;
		sound->ch4.envelope_time = (data & 7) << 2;
		sound->ch4.envelope_count = sound->ch4.envelope_time;
		return;
	case 0xff22: /* $FF22 (NR43) */
		sound->nr43 = data;
		/* LFSR */
		sound->ch4.lfsr_type = !(data & (1<<3)) ? 1/*LongMode*/ : 6/*ShortMode*/;
		sound->ch4.lfsr = 1;
		sound->ch4.lfsr_frequency = (data & 7) << 1;
		if(!sound->ch4.lfsr_frequency) {
			sound->ch4.lfsr_frequency = 1;
		}
		sound->ch4.lfsr_frequency <<= ((data >> 4) & 15) + 3;
		return;
	case 0xff23: /* $FF23 (NR44) */
		sound->nr44 = data;
		if(data & (1<<7)) {
			/* Length */
			if(data & (1<<6)) {
				sound->ch4.sound_length = 64 - (sound->nr41 & 63);
			} else {
				sound->ch4.sound_length = -1;
			}
			sound->ch4.lfsr_count = 0;
		}
		return;

	/*** コントロール ***/
	case 0xff24: /* $FF24 (NR50) */
		sound->nr50 = data;
		/* Speaker */
		dmgsound2_speaker_volume();
		return;
	case 0xff25: /* $FF25 (NR51) */
		sound->nr51 = data;
		/* Speaker */
		dmgsound2_speaker_volume();
		return;
	case 0xff26: /* $FF26 (NR52) */
		sound->nr52 = data;
		return;

	/*** Wave Pattern RAM ***/
	case 0xff30: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff31: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff32: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff33: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff34: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff35: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff36: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff37: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff38: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff39: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3a: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3b: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3c: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3d: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3e: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3f: /* $FF30-$FF3F (Wave Pattern RAM) */
		addr -= 0xff30;
		dmgsound2_wave_pattern[addr * 2 + 0] = (data >> 4) * 2 - 15; /* D7:4: 0..15 -> -15..+15 */
		dmgsound2_wave_pattern[addr * 2 + 1] = (data & 15) * 2 - 15; /* D3:0: 0..15 -> -15..+15 */
		return;
	}
}

void
dmgsound2_mix(short wbuff[/*DMGSOUND2BUFLEN*/])
{
	DMGSOUND2* sound = &dmgsound2;
	//
	int i;

	memset(wbuff, 0, sizeof(short) * DMGSOUND2BUFLEN);

	if(!(sound->nr52 & (1<<7))) { /* All sound on/off */
		return;
	}

	i = DMGSOUND2BUFLEN / 64; /* 16000[Hz]/64[Sample]=250[Hz] */
	do {
		/* 1/256[sec]フレーム処理。(Length,Envelope,Sweep) */
		sound->conv_250_256 -= 256; /* 250->256[Hz]変換 */
		do {
			dmgsound2_square_frame(&sound->ch1);
			dmgsound2_square_frame(&sound->ch2);
			if(sound->nr30 & (1<<7)) { /* Sound on/off */
				dmgsound2_wave_frame(&sound->ch3);
			}
			dmgsound2_noise_frame(&sound->ch4);
		} while((sound->conv_250_256 += 250) < 0);

		/* ミキシング処理。 */
		dmgsound2_square_mix(&sound->ch1, wbuff);
		dmgsound2_square_mix(&sound->ch2, wbuff);
		if(sound->nr30 & (1<<7)) { /* Sound on/off */
			dmgsound2_wave_mix(&sound->ch3, wbuff);
		}
		dmgsound2_noise_mix(&sound->ch4, wbuff);

		wbuff += 64;
	} while(--i);

	wbuff -= DMGSOUND2BUFLEN;
	dmgsound2_volume_shift(wbuff);
}

/****************************************************************************
 *	内部関数
 ****************************************************************************/

static void
dmgsound2_speaker_volume()
{
	DMGSOUND2* sound = &dmgsound2;
	//
	int nr50;
	int nr51;
	int s01;
	int s02;
	int vol;

	nr50 = sound->nr50;
	nr51 = sound->nr51;

	s01 =  nr50       & 7;
	s02 = (nr50 >> 4) & 7;

	vol = 0;
	if(nr51 & (1<<0)) {
		vol += s01;
	}
	if(nr51 & (1<<4)) {
		vol += s02;
	}
	sound->ch1.speaker_volume = vol;

	vol = 0;
	if(nr51 & (1<<1)) {
		vol += s01;
	}
	if(nr51 & (1<<5)) {
		vol += s02;
	}
	sound->ch2.speaker_volume = vol;

	vol = 0;
	if(nr51 & (1<<2)) {
		vol += s01;
	}
	if(nr51 & (1<<6)) {
		vol += s02;
	}
	sound->ch3.speaker_volume = vol;

	vol = 0;
	if(nr51 & (1<<3)) {
		vol += s01;
	}
	if(nr51 & (1<<7)) {
		vol += s02;
	}
	sound->ch4.speaker_volume = vol;
}

void
dmgsound2_square_frame(DMGSOUND2SQUARE* ch)
{
	/* Length */
	if(ch->sound_length > 0) {
		ch->sound_length--;
	}
	if(!ch->sound_length) {
		return;
	}

	/* Envelope */
	if(ch->envelope_time) {
		if(!--ch->envelope_count) {
			ch->envelope_count = ch->envelope_time;
			if(!ch->envelope_type) {
				if(ch->envelope_volume > 0) {
					ch->envelope_volume--;
				}
			} else {
				if(ch->envelope_volume < 15) {
					ch->envelope_volume++;
				}
			}
		}
	}

	/* Sweep */
	if(ch->sweep_time) {
		if(!--ch->sweep_count) {
			ch->sweep_count = ch->sweep_time;
			if(!ch->sweep_type) {
				ch->wave_frequency += ch->wave_frequency >> ch->sweep_shift;
				if(ch->wave_frequency > 0x7ff) {
					ch->sound_length = 0;
					return;
				}
			} else {
				ch->wave_frequency -= ch->wave_frequency >> ch->sweep_shift;
				if(ch->wave_frequency < 0) {
					ch->sound_length = 0;
					return;
				}
			}
		}
	}
}

void
dmgsound2_wave_frame(DMGSOUND2WAVE* ch)
{
	/* Length */
	if(ch->sound_length > 0) {
		ch->sound_length--;
	}
	if(!ch->sound_length) {
		return;
	}
}

void
dmgsound2_noise_frame(DMGSOUND2NOISE* ch)
{
	/* Length */
	if(ch->sound_length > 0) {
		ch->sound_length--;
	}
	if(!ch->sound_length) {
		return;
	}

	/* Envelope */
	if(ch->envelope_time) {
		if(!--ch->envelope_count) {
			ch->envelope_count = ch->envelope_time;
			if(!ch->envelope_type) {
				if(ch->envelope_volume > 0) {
					ch->envelope_volume--;
				}
			} else {
				if(ch->envelope_volume < 15) {
					ch->envelope_volume++;
				}
			}
		}
	}
}

/* 波長が短すぎるとミキシングが間に合わないので、サンプル更新周期に制限を設けます。
 * 2005/03/01現在、サンプル更新周期を (DMGSOUND2_CLOCK/16) と定義しています。
 * これは、P/ECEスピーカー出力(16KHz)1サンプル当り、最大16回の更新処理を意味します。
 */
#define DMGSOUND2_PERIOD_MIN (DMGSOUND2_CLOCK/16) /* = (1<<22)/16 = (1<<18) */

#ifndef PIECE

void
dmgsound2_square_mix(DMGSOUND2SQUARE* ch, short wbuff[/*64*/])
{
	int i;
	int volume;
	int period;
	int output;
	int wave_duty;
	int wave_index;
	int wave_count;

	/* 残り発声時間が0ならば、停止中です。 */
	if(!ch->sound_length) {
		return;
	}

	/* 最終ボリュームを求め、0ならば帰ります。 */
	volume = ch->envelope_volume * ch->speaker_volume;
	if(!volume) {
		return;
	}

	/* 波長を求めます。 */
	period = (2048 - ch->wave_frequency) * 4 * SPEAKER_FREQUENCY;
	if(period < DMGSOUND2_PERIOD_MIN) {
		period = DMGSOUND2_PERIOD_MIN;
	}

	wave_duty = ch->wave_duty;

	wave_index = ch->wave_index; /* 取り出し */
	wave_count = ch->wave_count; /* 取り出し */

	output = (wave_index < wave_duty) ? -volume : +volume;

	i = 64;
	do {
		wave_count -= DMGSOUND2_CLOCK;
		if(wave_count < 0) {
			do {
				wave_index++;
			} while((wave_count += period) < 0);
			wave_index &= 7;
			output = (wave_index < wave_duty) ? -volume : +volume;
		}
		*wbuff++ += output;
	} while(--i);

	ch->wave_index = wave_index; /* 書き戻し */
	ch->wave_count = wave_count; /* 書き戻し */
}

void
dmgsound2_wave_mix(DMGSOUND2WAVE* ch, short wbuff[/*64*/])
{
	int i;
	int volume;
	int period;
	int output;
	int wave_shift;
	int wave_index;
	int wave_count;

	/* 残り発声時間が0ならば、停止中です。 */
	if(!ch->sound_length) {
		return;
	}

	/* 最終ボリュームを求め、0ならば帰ります。 */
	volume = ch->speaker_volume;
	if(!volume) {
		return;
	}

	/* 波長を求めます。 */
	period = (2048 - ch->wave_frequency) * 2 * SPEAKER_FREQUENCY;
	if(period < DMGSOUND2_PERIOD_MIN) {
		period = DMGSOUND2_PERIOD_MIN;
	}

	/* 出力レベルを求め、消音ならば帰ります。 */
	wave_shift = ch->wave_shift;
	if((--wave_shift) < 0) {
		return;
	}

	wave_index = ch->wave_index; /* 取り出し */
	wave_count = ch->wave_count; /* 取り出し */

	output = (dmgsound2_wave_pattern[wave_index] >> wave_shift) * volume;

	i = 64;
	do {
		wave_count -= DMGSOUND2_CLOCK;
		if(wave_count < 0) {
			do {
				wave_index++;
			} while((wave_count += period) < 0);
			wave_index &= 31;
			output = (dmgsound2_wave_pattern[wave_index] >> wave_shift) * volume;
		}
		*wbuff++ += output;
	} while(--i);

	ch->wave_index = wave_index; /* 書き戻し */
	ch->wave_count = wave_count; /* 書き戻し */
}

void
dmgsound2_noise_mix(DMGSOUND2NOISE* ch, short wbuff[/*64*/])
{
	int i;
	int v;
	int volume;
	int period;
	int output;
	int lfsr_type;
	int lfsr;
	int lfsr_count;

	/* 残り発声時間が0ならば、停止中です。 */
	if(!ch->sound_length) {
		return;
	}

	/* 最終ボリュームを求め、0ならば帰ります。 */
	volume = ch->envelope_volume * ch->speaker_volume;
	if(!volume) {
		return;
	}

	/* 波長を求めます。 */
	//period = ch->lfsr_frequency * SPEAKER_FREQUENCY;
	//if(period < DMGSOUND2_PERIOD_MIN) {
	//	period = DMGSOUND2_PERIOD_MIN;
	//}
	//↓OverFlow回避
	period = ch->lfsr_frequency * (SPEAKER_FREQUENCY>>7);
	if(period < (DMGSOUND2_PERIOD_MIN>>7)) {
		period = (DMGSOUND2_PERIOD_MIN>>7);
	}

	lfsr_type = ch->lfsr_type;

	lfsr       = ch->lfsr;       /* 取り出し */
	lfsr_count = ch->lfsr_count; /* 取り出し */

	output = !(lfsr & 1) ? -volume : +volume;

	i = 64;
	do {
		//lfsr_count -= DMGSOUND2_CLOCK;
		//↓OverFlow回避
		lfsr_count -= (DMGSOUND2_CLOCK>>7);
		if(lfsr_count < 0) {
			do {
				v = ((lfsr>>0) ^ (lfsr>>lfsr_type)) & 1;
				lfsr = (lfsr >> 1) | (v << 14);
			} while((lfsr_count += period) < 0);
			output = !(lfsr & 1) ? -volume : +volume;
		}
		*wbuff++ += output;
	} while(--i);

	ch->lfsr       = lfsr;       /* 書き戻し */
	ch->lfsr_count = lfsr_count; /* 書き戻し */
}

/*       ++++++++----------- envelope_volume or wave_pattern
 *       ||||||||   +++++--- speaker_volume
 * Ch1: (-15..+15)*(0..14) = 符号付き5bit*符号無し4bit = 符号付き 9bit
 * Ch2: (-15..+15)*(0..14) = 符号付き5bit*符号無し4bit = 符号付き 9bit
 * Ch3: (-15..+15)*(0..14) = 符号付き5bit*符号無し4bit = 符号付き 9bit
 * Ch4: (-15..+15)*(0..14) = 符号付き5bit*符号無し4bit = 符号付き 9bit
 * 合計 ================================================ 符号付き11bit
 */
#define DMGSOUND2_VOLUME_SHIFT ((16-11)+1/*調整可*/) /* = 6 */

void
dmgsound2_volume_shift(short wbuff[/*DMGSOUND2BUFLEN*/])
{
	int i;
	int v;

	i = DMGSOUND2BUFLEN;
	do {
		v = *wbuff;
		v <<= DMGSOUND2_VOLUME_SHIFT;
		if(v >  32767) v =  32767;
		if(v < -32768) v = -32768;
		*wbuff++ = v;
	} while(--i);
}

#endif /*PIECE*/
