/*	
 *	clipsp2.h
 *
 *	P/ECE SPC Driver V2
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Thu Oct 07 06:00:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#ifndef __CLIP_SP2_H__
#define __CLIP_SP2_H__

/* * 関連ファイル一覧
 * - clipsp2.h	メインヘッダファイルです。(このファイル)
 * - clipsp2a.h	Ｃ言語・アセンブラ両方にインクルードするヘッダファイルです。
 * - clipsp2b.h	アセンブラソースだけからインクルードするヘッダファイルです。
 * - clipsp2.c	SPC Driver V2 のC言語実装部分です。
 * - clipsp2c.c	S-CPU(サウンドCPU・SPC700カスタム)のC言語実装部分です。
 * - clipsp2d.c	S-DSP(サウンドDSP)のC言語実装部分です。
 * - clipsp2a.s	高速RAMに転送しない、アセンブラ実装部分です。
 * - framsp2a.s	高速RAMに転送する、アセンブラ実装部分です。
 * - (※以下、必要に応じて追加する予定です)
 */

// SPC2_DEBUGシンボルを定義すると、デバッグルーチンを生成します。
// SPC2_TRACEシンボルを定義すると、トレースルーチンを生成します。
// 注意!! アセンブラ用プリプロセッサ(pp33.exe)ではネストした#ifdefが使えないため、
//        アセンブラソース中のトレースルーチンは手動で有効化/無効化してください。
//        アセンブラソース中のトレースルーチンは;;{{SPC2_TRACE〜;;}}SPC2_TRACEで囲ってあります。
//        なお、2004/10/18現在、アセンブラソース中にデバッグルーチンはありません。
///#define SPC2_DEBUG
///#define SPC2_TRACE

/* SPC2_ASMシンボル定義を読み込みます。
 * SPC2_ASMシンボルの未定義/定義によるC/アセンブラコードの切り替えは、
 * Cソースファイル(*.c)とアセンブラソースファイル(*.s)から参照するので、
 * 別のインクルードファイルに分けています。
 * (C言語特有の定義を含むインクルードファイルをそのままアセンブラソースに
 *  インクルードすると、アセンブルエラーになってしまうため。)
 */
#include "clipsp2a.h"

/* P/ECE以外ではアセンブラコードは使用できません。
 */
#ifndef PIECE
#undef SPC2_ASM
#endif /*PIECE*/

/****************************************************************************
 *	SPC2CPU
 ****************************************************************************/

/* * いちばん使用頻度が高いと思われるPSW_Pに1命令でアクセスできるよう、PSW_Pを先頭にしました。
 *   PCの方が使用頻度が高いですが、spc2cpu_run()の間レジスタに保持するので、後ろで構いません。
 */
typedef struct _SPC2CPU {
	unsigned short psw_p;	/* + 0,2: 0x000 / 0x100 */
	unsigned char psw_nz;	/* + 2,1: [7]=N,[7:0]=Z */	//┬ +10,2: [8]=C,[7]=N,[7:0]=Z
	unsigned char psw_c;	/* + 3,1: [0]=C */		//┘
	unsigned char psw_v;	/* + 4,1: [8]=V */
	unsigned char x;	/* + 5,1: X */
	unsigned char a;	/* + 6,1: A */			//┬ +14,2: YA
	unsigned char y;	/* + 7,1: Y */			//┘
	unsigned char* sp;	/* + 8,4: &spc2ram[0x100|SP] */
	unsigned char* pc;	/* +12,4: &spc2ram[      PC] */
} SPC2CPU;			/* =16 */

/* SPC2CPU構造体を初期化します。
 * [in]
 *	pc		PCレジスタの値。
 *	a		Aレジスタの値。
 *	x		Xレジスタの値。
 *	y		Yレジスタの値。
 *	psw		PSWレジスタの値。
 *	sp		SPレジスタの値。
 * [note]
 *	* 引数の順序は、SPCファイルのレジスタフィールドの並びに合わせました。
 */
void spc2cpu_init(int pc, int a, int x, int y, int psw, int sp);

/* CPUを実行します。
 * [in]
 *	count		実行する命令数。
 */
void spc2cpu_run(int count);

/****************************************************************************
 *	SPC2DSP
 ****************************************************************************/

/* エンベロープテーブル */
extern const int spc2dsp_attack_rate_table[16/*AR*/];
extern const int spc2dsp_decay_rate_table[8/*SL*/][8/*DR*/];
extern const int spc2dsp_sustain_rate_table[32/*SR*/];
extern const int spc2dsp_sustain_level_table[8/*SL*/];
extern const int spc2dsp_increase_linear_table[32];
extern const int spc2dsp_increase_bentline_table[32];
#define spc2dsp_decrease_linear_table spc2dsp_increase_linear_table
#define spc2dsp_decrease_exponential_table spc2dsp_sustain_rate_table

/* BRR復号用フィルタ係数テーブル */
extern const short spc2dsp_filter_table[4][2];

typedef struct _SPC2VCE {
	int state;		/* + 0, 4: {SPC2VCE_*} */
	int vol;		/* + 4, 4: Volume : max(|VOL(L)|,|VOL(R)|)=>0〜127 */
	int p;			/* + 8, 4: Pitch  : 0〜0x3fff */
	int ar;			/* +12, 4: Attack Rate   (64サンプル毎のenvx変化量) */
	int dr;			/* +16, 4: Decay Rate    (64サンプル毎のenvx変化量) */
	int sr;			/* +20, 4: Sustain Rate  (64サンプル毎のenvx変化量) */
	int sl;			/* +24, 4: Sustain Level (0〜127×固定小数16bit) */
	int gain;		/* +28, 4: Gain (0〜127×固定小数16bit または 64サンプル毎のenvx変化量) */
	int envx;		/* +32, 4: Envelope (0〜127×固定小数16bit) */
	int lsa;		/* +36, 4: Source Loop Start Address */
	int brr_addr;		/* +40, 4: 次のBRRブロックアドレス */
	int last_loop;		/* +44, 4: bit0=0:途中のブロック or 1:最終ブロック */
				/*         bit1=0:ループしない音色 or 1:ループ音色 */
	int progress;		/* +48, 4: 波形進行カウンタ */
	short sample[16];	/* +52,32: BRRブロック展開バッファ */
} SPC2VCE;			/* =84 */

/* SPC2DSP.kon */
#define SPC2VCE_ATTACK		1	/* ADSR      */
#define SPC2VCE_DECAY		2	/* ADSR      */
#define SPC2VCE_SUSTAIN		3	/* ADSR      */
#define SPC2VCE_RELEASE		4	/* ADSR/GAIN */
#define SPC2VCE_DIRECT		5	/*      GAIN */
#define SPC2VCE_INCREASE	6	/*      GAIN */
#define SPC2VCE_DECREASE	7	/*      GAIN */

typedef struct _SPC2DSP {
	int mvol;			/* +  0,  4: Main Volume : max(|MVOL(L)|,|MVOL(R)|)=>0〜127 */
	SPC2VCE vce[8];			/* +  4,672: Voice×8チャネル */
	unsigned char reg[0x80];	/* +676,128: レジスタデータ */
} SPC2DSP;				/* =804 */

/* SPC2DSP構造体を初期化します。
 */
void spc2dsp_init();

/* DSPレジスタを読み出します。
 * [in]
 *	addr		読み出すレジスタアドレス。
 * [out]
 *	戻り値		読み出したレジスタデータ。
 */
int spc2dsp_read(int addr);

/* DSPレジスタへ書き込みます。
 * [in]
 *	addr		書き込むレジスタアドレス。
 *	data		書き込むレジスタデータ。([32:8]は必ず0としてください)
 */
void spc2dsp_write(int addr, int data);

/* 64サンプルのミキシングを行います。
 * [in]
 *	wbuff		出力バッファ。
 */
void spc2dsp_mix(short wbuff[/*64*/]);

/* 指定されたVoiceの64サンプル毎のエンベロープ更新を行います。
 * [in]
 *	vce		SPC2VCE構造体。
 * [out]
 *	戻り値		vce->stateを返します。
 */
int spc2vce_env(SPC2VCE* vce);

/* 指定されたVoiceの64サンプルを生成し、加算します。
 * [in]
 *	vce		SPC2VCE構造体。
 *	mixbuf		ミキシングバッファ。
 */
void spc2vce_mix(SPC2VCE* vce, int mixbuf[/*64*/]);

/* 圧縮サンプルデータ(BRR:Bit Rate Reduction)を復号します。
 * [in]
 *	vce		SPC2VCE構造体。
 */
void spc2vce_decode_brr(SPC2VCE* vce);

/****************************************************************************
 *	SPC2HDR
 ****************************************************************************/

/* * SPC2HDR・SPC2ID666構造体どちらも、リトルエンディアンです。
 *
 * * SPC2HDR・SPC2ID666構造体どちらも、フィールドアライメントなしでパックされています。
 *   そのためshortやintが使えないので、2バイト以上のフィールドはchar[]として定義しました。
 *
 * * SPCファイルに含まれているID666 Tagが text format / binary format どちらかを見分ける方法が不明です。
 *   とりあえず両方に共通の song_title,game_title,dumper,comments フィールドだけを使うのが安全そうです。
 *
 * * 2004/10/22追記: ID666 Tagが text format / binary format どちらかを見分ける方法について
 *   ヘッダ先頭からのオフセット[0xd2]が0ならばバイナリ形式、0以外ならばテキスト形式です。
 *   (注意:“ヘッダ先頭からの”オフセットです。“ID666先頭からの”オフセットじゃないよ!!)
 *   テキスト形式ならばオフセット[0xd2]はEmulator used to dump SPCであり、キャラクタコード'0'以上の値が入っています。
 *   バイナリ形式ならばオフセット[0xd2]は未使用領域であり、ゼロクリアされています。
 *   この方法は、SSDLaboさん(http://www.ssdlabo.jp/)のテキスト資料「SPC ファイルの ID666 仕様書」
 *   (http://www.ssdlabo.jp/win/spcplay/ID666.txt)を参考にさせていただきました。ありがとうございます。
 */

/* ID666 Tag (text format) */
typedef struct _SPC2ID666TXT {
	         char song_title[32];		/* +0x0002e, 32: Song title */
	         char game_title[32];		/* +0x0004e, 32: Game title */
	         char dumper[16];		/* +0x0006e, 16: Name of dumper */
	         char comments[32];		/* +0x0007e, 32: Comments */
	         char date[11];			/* +0x0009e, 11: Date SPC was dumped (MM/DD/YYYY) */
	         char play_time[3];		/* +0x000a9,  3: Number of seconds to play song before fading out */
	         char fade_time[5];		/* +0x000ac,  5: Length of face in milliseconds */
	         char artist[32];		/* +0x000b1, 32: Artist of song */
	unsigned char channel_disable;		/* +0x000d1,  1: Default channel disables (0:enable/1:disable) */
	unsigned char emulator;			/* +0x000d2,  1: Emulator used to dump SPC ('0':unknown/'1':ZSNES/'2':Snes9x) */
	unsigned char resv000d3[45];		/* +0x000d3, 45: (予約) */
} SPC2ID666TXT;					/* =0x00100,210 */

/* ID666 Tag (binary format) */
typedef struct _SPC2ID666BIN {
	         char song_title[32];		/* +0x0002e, 32: Song title */
	         char game_title[32];		/* +0x0004e, 32: Game title */
	         char dumper[16];		/* +0x0006e, 16: Name of dumper */
	         char comments[32];		/* +0x0007e, 32: Comments */
	unsigned char date[4];			/* +0x0009e,  4: Date SPC was dumped (YYYYMMDD) */
	unsigned char resv000a2[7];		/* +0x000a2,  7: (予約) */
	unsigned char play_time[3];		/* +0x000a9,  3: Number of seconds to play song before fading out */
	unsigned char fade_time[4];		/* +0x000ac,  4: Length of face in milliseconds */
	         char artist[32];		/* +0x000b0, 32: Artist of song */
	unsigned char channel_disable;		/* +0x000d0,  1: Default channel disables (0:enable/1:disable) */
	unsigned char emulator;			/* +0x000d1,  1: Emulator used to dump SPC (0:unknown/1:ZSNES/2:Snes9x) */
	unsigned char resv000d2[46];		/* +0x000d2, 46: (予約) */
} SPC2ID666BIN;					/* =0x00100,210 */

/* ID666 Tag */
typedef union _SPC2ID666 {
	SPC2ID666TXT txt;			/* text format */
	SPC2ID666BIN bin;			/* binary format */
} SPC2ID666;

typedef struct _SPC2HDR {
	         char signature[33];		/* +0x00000, 33: "SNES-SPC700 Sound File Data v0.30" 固定 */
	unsigned char resv00021[2];		/* +0x00021,  2: {26,26} 固定 */
	unsigned char has_id666;		/* +0x00023,  1: ID666 Tag有効/無効 (26:有効/27:無効) */
	unsigned char minor_ver;		/* +0x00024,  1: マイナーバージョン (30固定?) */
	/*===== SPC700 Registers =====*/
	unsigned char pc[2];			/* +0x00025,  1: PC */
	unsigned char a;			/* +0x00027,  1: A */
	unsigned char x;			/* +0x00028,  1: X */
	unsigned char y;			/* +0x00029,  1: Y */
	unsigned char psw;			/* +0x0002a,  1: PSW */
	unsigned char sp;			/* +0x0002b,  1: SP */
	unsigned char resv0002c[2];		/* +0x0002c,  2: (予約) ※「SPC File Format v0.30」(spc_file_format.txt)には3バイトと書いてあります。誤植? */
	/*===== ID666 Tag =====*/
	        SPC2ID666 id666;		/* +0x0002e,210: ID666 Tag (text or binary format) */
	/*===== Memory Image =====*/
	unsigned char main_ram[0x10000];	/* +0x00100,64K: メインRAM */
	unsigned char dsp_regs[128];		/* +0x10100,128: DSPレジスタ */
	unsigned char resv10180[64];		/* +0x10180, 64: (予約) */
	unsigned char extra_ram[64];		/* +0x101c0, 64: Extra RAM (IPL ROMの裏RAM?) */
} SPC2HDR;					/* =0x10200 */
						/* この後にExtended ID666 Itemsが続く場合があります。(未対応) */

/* SPCヘッダのシグネチャ検査を行います。
 * [in]
 *	romp		SPCファイルイメージの先頭アドレス。(==SPCヘッダの先頭アドレス)
 * [out]
 *	戻り値		正しいSPCヘッダならば0を返します。
 *			正しくないSPCヘッダならば負値を返します。
 */
int spc2hdr_test(const void* romp);

/****************************************************************************
 *	SPC2DRV
 ****************************************************************************/

#define SPC2BUFLEN	320	/* = 16000/50 … 1[tick](=1/50[sec])当たりのサンプル数 */

typedef struct _SPC2DRV {
	int timer[3];	/* タイマカウンタ (0-1:8KHz,2:64KHz) */
} SPC2DRV;

/* SPC2DRV構造体を初期化します。
 * [in]
 *	romp		SPCファイルイメージの先頭アドレス。
 * [out]
 *	戻り値		初期化成功ならば0を返します。
 *			初期化失敗ならば負値を返します。
 */
int spc2drv_init(const void* romp);

/* I/O空間(0x00f0〜0x00ff)の読み出し処理です。
 * [in]
 *	addr		読み出すアドレス。
 * [out]
 *	戻り値		読み出したデータ。
 * [note]
 *	* spc2cpu_run()の実行中からのみ使用します。
 *	  それ以外の場所から呼び出さないでください。
 *	* P/ECE環境でSPC2_ASMが定義されている場合、高速化のため、特殊なアセンブラ版に置き換えます。
 *	  特殊なアセンブラ版はspc2cpu_runのレジスタ割り当てに依存しているため、
 *	  誤ってspc2cpu_run以外から呼ばれると誤動作の危険があります。
 *	  誤ってC言語から呼ばれないよう、アセンブラ版は関数名はspc2drv_read_asmに変更してあります。
 *	  P/ECE環境でSPC2_ASMが定義されている場合、C言語版のspc2drv_readはリンクされません。
 *	  詳しくは、clipsp2.cのspc2drv_read_asmを参照してください。
 */
int spc2drv_read(int addr);

/* I/O空間(0x00f0〜0x00ff)への書き込み処理です。
 * [in]
 *	addr		書き込むアドレス。
 *	戻り値		書き込むデータ。([32:8]は必ず0としてください)
 */
void spc2drv_write(int addr, int data);

/* 1[tick](=1/50[sec])分のミキシングを行います。
 * [in]
 *	wbuff		出力バッファ。
 */
void spc2drv_mix(short wbuff[/*SPC2BUFLEN*/]);

/* stream_play()のためのコールバック関数です。
 * [in]
 *	wbuff		出力バッファ。
 *	param		(未使用)
 * [out]
 *	戻り値		成功ならば0を返します。
 *			失敗ならば負値を返します。
 */
int spc2drv_stream_callback(short* wbuff, int param);

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

extern SPC2CPU spc2cpu;
extern SPC2DSP spc2dsp;
extern SPC2DRV spc2drv;
extern unsigned char spc2ram[0x10000];	/* 0x0000〜0xffff */
extern unsigned char spc2ext[64];	/* 0xffc0〜0xffffの裏RAM? */

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

/* ID666タグの文字列フィールドは、32文字(dumperのみ16文字)いっぱいまで使用されることがあります。
 * いっぱいまで使用されていた場合、nul終端されていません。
 * 確実にnul終端するために、SPC2INF構造体の文字列フィールドは長さを+1することにしました。
 * なお、ID666 text format のdateフィールドは、もともと確実にnul終端されているので、+1不要です。
 */
typedef struct _SPC2INF {
	char song_title[32 + 1];	/* Song title     (確実にnul終端) */
	char game_title[32 + 1];	/* Game title     (確実にnul終端) */
	char dumper[16 + 1];		/* Name of dumper (確実にnul終端) */
	char comments[32 + 1];		/* Comments       (確実にnul終端) */
	char date[11/*+1不要*/];	/* Date SPC was dumped (MM/DD/YYYY) (確実にnul終端) */
	int  play_time;			/* Number of seconds to play song before fading out (0:unknown) */
	int  fade_time;			/* Length of face in milliseconds */
	char artist[32 + 1];		/* Artist of song (確実にnul終端) */
	int  channel_disable;		/* Default channel disables (0:enable/1:disable) */
	int  emulator;			/* Emulator used to dump SPC (0:unknown/1:ZSNES/2:Snes9x) */
} SPC2INF;

/* SPCファイルの再生を開始します。
 * [in]
 *	data		SPCファイルイメージの先頭アドレス。
 * [out]
 *	戻り値		成功ならば0を返します。
 *			失敗ならば負値を返します。
 * [note]
 *	* この関数は内部でstream_play()を呼び出しています。
 *	* stream_play()を呼ぶと、サウンド割り込みベクタがフックされます。
 *	  プログラム終了前に必ずspc2_stop()を呼んで、ベクタを復元してください。
 */
int spc2_play(const void* romp);

/* SPCファイルの再生を停止します。
 * [note]
 *	* 再生中でなければ、この関数は何もしません。
 */
void spc2_stop();

/* SPCファイル情報を取得します。
 * [in]
 *	data		SPCファイルイメージの先頭アドレス。
 *	inf		SPCファイル情報を格納するSPC2INF構造体。(NULL可能)
 * [out]
 *	戻り値		成功ならば0を返します。
 *			失敗ならば負値を返します。
 * [note]
 *	* 正しいSPCファイルイメージだがID666タグを持っていない(hdr->has_id666!=26)場合、
 *	  SPC2INF構造体の全フィールドをクリアし、成功(0)を返します。
 *	* 引数infがNULLならば、正しいSPCファイルイメージか否かだけを判断し、
 *	  正しいSPCファイルイメージであってもSPCファイル情報を格納しません。
 */
int spc2_inf(const void* romp, SPC2INF* inf);

#endif /*__CLIP_SP2_H__*/
