/*	
 *	clipp62.c
 *
 *	P/ECE HuC6280-PSG Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Mon Feb 23 04:28:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

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

/* チャネルボリュームを更新します。 */
void
huc6280psg_update_volume(HUC6280PSG* psg, HUC6280PSGCHANNEL* channel)
{
	channel->volume = psg->main_volume					/*   5bit メインボリューム   */
			* (channel->reg[2] & 0x1f)				/* + 5bit チャネルボリューム */
			* ((channel->reg[3] & 0xf) + (channel->reg[3] >> 4));	/* + 5bit 左右バランスを加算 */
}										/* =15bit */

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

void
huc6280psg_reset(HUC6280PSG* psg, int clock)
{
	int i_channel;
	HUC6280PSGCHANNEL* channel;

	/* まずクリア。 */
	memset(psg, 0, sizeof(HUC6280PSG));

	/* クロック進行DDAセットアップ。 */
	psg->clock_progress.u = clock / SPEAKER_FREQUENCY; /* 毎回の変化量 */
	psg->clock_progress.n = clock % SPEAKER_FREQUENCY; /* 端数の増加量 */
	psg->clock_progress.d =        -SPEAKER_FREQUENCY; /* 端数 */

	/* 0不可のフィールドを確実に初期化。 */
	for(i_channel = 0, channel = psg->channel; i_channel < 6; i_channel++, channel++) {
		channel->tone_period	= 1 << 12;
		channel->noise_period	= 0x20 << 6;	/* Ch4,5のみ必須 */
		channel->noise_lfsr	= 1;		/* Ch4,5のみ必須 */
	}
}

void
huc6280psg_write(HUC6280PSG* psg, int addr, unsigned char data)
{
	int i_channel, volume;
	HUC6280PSGCHANNEL* channel;

	switch(addr) {
	/***** 設定チャネル選択 *****/
	case 0:	/* ?????ccc (c:チャネル番号(0〜5)) */
		data &= 7;	/* 未使用ビットをマスク */
		if(data <= 5) psg->channel_select = data;
		return;

	/***** メインボリューム *****/
	case 1:	/* llllrrrr (l:左メインボリューム, r:右メインボリューム) */
		volume = (data & 0xf) + (data >> 4);	/* 左右メインボリュームを加算 (4bit×2 = 5bit) */
		if(psg->main_volume == volume) return;	/* 変化無し */
		psg->main_volume = volume;		/* 変化後の値を格納 */
		for(i_channel = 0, channel = psg->channel; i_channel < 6; i_channel++, channel++) {
			huc6280psg_update_volume(psg, channel);	/* チャネルボリューム更新 */
		}
		return;

	/***** チャネル設定 *****/
#define REG(i)	(channel->reg[(i) - 2])	/* HUC6280PSGCHANNEL.reg[6]は$0802〜$0807を保持しています */

	case 2:	/* ffffffff (f:トーン周期[ 7-0]) */
	case 3:	/* ????ffff (f:トーン周期[11-8]) */
		channel = &psg->channel[psg->channel_select];
		if(REG(addr) == data) return;	/* 変化無し */
		REG(addr) = data;	/* 変化後の値を格納 */
		//
		channel->tone_period = REG(2) | (REG(3) & 0xf) << 8;
		if(!channel->tone_period) channel->tone_period = 1 << 12; /* ※要確認 */
		channel->tone_progress = channel->tone_period; /* ※必要? */
		return;

	case 4:	/* cd?vvvvv (c:チャネルON/OFF, d:DirectD/A ON/OFF, v:チャネルボリューム) */
		data &= 0xdf;	/* 比較・格納前に未使用ビットをマスク */
		//
		channel = &psg->channel[psg->channel_select];
		if(REG(addr) == data) return;	/* 変化無し */
		REG(addr) = data;	/* 変化後の値を格納 */
		//
		channel->enable = (REG(4) >> 7) & 1;
		channel->dda = (REG(4) >> 6) & 1;
		if(channel->dda) channel->wave_index = 0;
		huc6280psg_update_volume(psg, channel);
		return;

	case 5:	/* llllrrrr (l:左チャネルボリューム, r:右チャネルボリューム) */
		channel = &psg->channel[psg->channel_select];
		if(REG(addr) == data) return;	/* 変化無し */
		REG(addr) = data;	/* 変化後の値を格納 */
		//
		huc6280psg_update_volume(psg, channel);
		return;

	case 6:	/* 000wwwww (w:波形データ) */
		data &= 0xdf;	/* 未使用ビットをマスク */
		//
		channel = &psg->channel[psg->channel_select];
		//
		channel->wave[channel->wave_index & 0x1f] = data - 16;	/* 0〜31 => -16〜+15 変換 */
		if(!channel->dda) channel->wave_index++;
		return;

	case 7:	/* n??fffff (n:ノイズON/OFF, f:ノイズ周期の1の補数) */
		if(psg->channel_select < 4) return;	/* Ch0,1,2,3にはノイズモードなし */
		data &= 0x9f;	/* 比較・格納前に未使用ビットをマスク */
		//
		channel = &psg->channel[psg->channel_select];
		if(REG(addr) == data) return;	/* 変化無し */
		REG(addr) = data;	/* 変化後の値を格納 */
		//
		channel->noise = (REG(7) >> 7) & 1;
		if(channel->noise) {
			channel->noise_period = (~REG(7) & 0x1f) << 6;
			if(!channel->noise_period) channel->noise_period = 0x20 << 6; /* ※要確認 */
			channel->noise_progress = channel->noise_period; /* ※必要? */
		}
		return;

#undef REG

	///***** LFO (※未対応) *****/
	//case 8:
	//case 9:
	//	return;
	}
}

