/*	
 *	clipfram.c
 *
 *	高速RAMに転送するモジュール
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2002 Naoyuki Sawa
 *
 *	* Tue Nov 19 12:30:00 JST 2002 Naoyuki Sawa
 *	- 旧clipdobj.cとclipmod.cから、コピー・修正して新規作成しました。
 *	* Sun Dec 8 05:43:00 JST 2002 Naoyuki Sawa
 *	- mod_mix()のループ音バグを修正。
 *	- テンポエフェクト対応。
 *	* Fri Jan 31 21:57:00 JST 2003 Naoyuki Sawa
 *	- new_LCDDrawObject()のPIXEL(p,m)マクロのmask判定部分を改良。
 *	 「(mask >> (m) & 1)」→「(mask & (1 << (m)))」に変更しました。
 *	  約3%高速化(全画面転送時)、コードサイズは20バイト少なくなりました。
 *	  この改良は「ryukの薫製工房」さん(http://www.interq.or.jp/earth/ryuk/)によるものです。
 *	- new_LCDDrawObject()のクリッピング時画面外判定処理を改良。
 *	  これまで、反転なし/ありそれぞれの処理の中で行っていたものを、共通化してまとめました。
 *	  コードサイズが4バイト少なくなりました。
 *	- 以上の変更を行った結果、現時点のコードサイズは0xfe0(4064)バイトです。
 *	* Sat Feb 1 14:08:00 JST 2003 Naoyuki Sawa
 *	- new_LCDDrawObject()にて、残りライン数の取得「y = obj.dh」を共通化、容量節約しました。
 *	- MODドライバを可変チャネル数に対応しました。
 */
#include "clip.h"

/****************************************************************************
 *	高速版pceLCDDrawObject
 ****************************************************************************/

/* * 高速版pceLCDDrawObjectには、次のような制限があります。
 * - 転送方法がDRW_NOMAL、かつ、転送元ビットマップが2bitデータmask付きである場合以外は、
 *   本物のpceLCDDrawObjectに処理を任せます。呼び出しが増える分、少し遅くなります。
 * - 転送元矩形に、転送元ビットマップをはみ出す領域を指定すると、正しく動作しません。
 * - 転送元ビットマップは、横8ピクセル単位のサイズで作成されていなければいけません。
 *   この制限は、P/ECEビットマップ形式の制限でもあるので、特に注意する必要はありません。
 * * アプリケーション起動時に一度だけhook_LCDDrawObject()を呼び、
 *   アプリケーション終了時には必ずunhook_LCDDrawObject()を読んでください。
 */

extern int (*old_LCDDrawObject)(DRAW_OBJECT);	/* 本物のpceLCDDrawObjectを保存 -> clippce.c */

int
new_LCDDrawObject(DRAW_OBJECT obj)
{
	PIECE_VRAM dest;
	PIECE_BMP src;
	int n, x, y, step, dest_stride, src_stride;
	unsigned char *dest_buf, *src_mask, mask;
	unsigned short *src_buf, pixel;

	/* 転送元ビットマップ情報を取り出しておきます。
	 * 2bitデータまたは2bitデータmask付きでなければ、本物に任せます。
	 */
	src = *obj.src;
	if(src.header.bpp == 2) {
		/* 転送先VRAM情報を取得。 */
		if(obj.dest != NULL) {
			dest = *obj.dest;
		} else {
			dest.w = DISP_X;
			dest.h = DISP_Y;
			dest.buf = pceLCDSetBuffer(INVALIDPTR);
		}

		/* 転送先クリッピング。 */
		if(!(obj.param & DRW_REVX)) {
			if(obj.dx < obj.clip.left) {
				n = obj.clip.left - obj.dx; /* 左にはみ出たピクセル数 */
				obj.dx = obj.clip.left;
				obj.dw -= n;
				obj.sx += n;
			}
			if(obj.dx + obj.dw > obj.clip.right) {
				obj.dw = obj.clip.right - obj.dx;
			}
		} else {
			if(obj.dx < obj.clip.left) {
				n = obj.clip.left - obj.dx; /* 左にはみ出たピクセル数 */
				obj.dx = obj.clip.left;
				obj.dw -= n;
			}
			if(obj.dx + obj.dw > obj.clip.right) {
				n = obj.dx + obj.dw - obj.clip.right; /* 右にはみ出たピクセル数 */
				obj.dw -= n;
				obj.sx += n;
			}
		}
		if(obj.dw <= 0) return 0; /* 画面外 */
		if(!(obj.param & DRW_REVY)) {
			if(obj.dy < obj.clip.top) {
				n = obj.clip.top - obj.dy; /* 上にはみ出たピクセル数 */
				obj.dy = obj.clip.top;
				obj.dh -= n;
				obj.sy += n;
			}
			if(obj.dy + obj.dh > obj.clip.bottom) {
				obj.dh = obj.clip.bottom - obj.dy;
			}
		} else {
			if(obj.dy < obj.clip.top) {
				n = obj.clip.top - obj.dy; /* 上にはみ出たピクセル数 */
				obj.dy = obj.clip.top;
				obj.dh -= n;
			}
			if(obj.dy + obj.dh > obj.clip.bottom) {
				n = obj.dy + obj.dh - obj.clip.bottom; /* 下にはみ出たピクセル数 */
				obj.dh -= n;
				obj.sy += n;
			}
		}
		if(obj.dh <= 0) return 0; /* 画面外 */

		/* 転送元クリッピングは行いません。
		 * 元々指定された転送元矩形が転送元ビットマップ内に収まっていたならば、
		 * 転送先クリッピングによって転送元がはみ出すことはありえないからです。
		 * しかし本物は転送元クリッピングも行っているので、この点は非互換です。
		 */

		/* 転送元ピクセルアドレスを求めます。 */
		n = (src.header.w * obj.sy + obj.sx) / 8;
		src_buf = (unsigned short*)src.buf + n;
		src_mask = src.mask + n;
		src_stride = src.header.w / 8 - ((obj.sx & 7) + obj.dw - 1) / 8;
		/*                                                     ~~~      */
		/* 8ステップ完了時に進むのではなく、9ステップ目の先頭で進むから */

		/* 残り転送ライン数を用意しておきます。 */
		y = obj.dh;

		/* 反転方向によって、処理を分けます。(※DRW_NOMAL=0と仮定)
		 * 反転方向以外のフラグが指定されていたら、本物に任せます。
		 */
		switch(obj.param) {
		case 0/*反転なし*/:
			/* 転送先ピクセルアドレスを求めます。 */
			dest_buf = dest.buf + dest.w * obj.dy + obj.dx;
			dest_stride = dest.w - obj.dw;

			switch(src.header.mask) {
			case 0/*2bitデータ*/:
				do {
					/* 8ピクセル単位のうち、何ピクセル目から開始するか？ */
					step = obj.sx & 7;

					/* 開始点から始まるか途中から始まるかわからないので、
					 * 初回はたとえstep=0でも、ここで最初のデータを取得することにします。
					 * ※P/ECEビットマップのピクセル並びは、MSB側が左のピクセルなんですね。
					 *   S1C33はリトルエンディアンなんだから、LSB側を左のピクセルにした方が扱いやすいと思うのですが。
					 *   WindowsBMPはMSB側が左のピクセルなのかな？もしそうならば、BMPの仕様に合わせたのかも知れません。
					 */
					pixel = *src_buf;	/* ピクセル並びは MSB:45670123:LSB の順です */

					/* 何ピクセル目から開始するかによって、適切な位置へ飛び込みます。 */
					x = obj.dw; /* 残りピクセル数 */
					switch(step) {
LOOP_01: /* 初回はここは飛び越します */
						/* 8ピクセル単位の開始点で、次のデータを取得します。 */
						pixel = *++src_buf;
#define PIXEL(p) \
						/* ピクセルを、描きます。 */ \
						*dest_buf++ = pixel >> (p) * 2 & 3; \
						/* 1ライン転送完了したら、抜けます。 */ \
						if(--x == 0) break;
					case 0: PIXEL(3);
					case 1: PIXEL(2);
					case 2: PIXEL(1);
					case 3: PIXEL(0);
					case 4: PIXEL(7);
					case 5: PIXEL(6);
					case 6: PIXEL(5);
					default/*7*/: PIXEL(4); goto LOOP_01;
#undef PIXEL
					}

					/* 転送先・転送元ピクセルアドレスを次のラインへ進めます。 */
					dest_buf += dest_stride;
					src_buf += src_stride;
				} while(--y != 0);
				return 1; /* 描画完了 */

			case 1/*2bitデータmask付き*/:
				do {
					/* 8ピクセル単位のうち、何ピクセル目から開始するか？ */
					step = obj.sx & 7;

					/* 開始点から始まるか途中から始まるかわからないので、
					 * 初回はたとえstep=0でも、ここで最初のデータを取得することにします。
					 * ※P/ECEビットマップのピクセル並びは、MSB側が左のピクセルなんですね。
					 *   S1C33はリトルエンディアンなんだから、LSB側を左のピクセルにした方が扱いやすいと思うのですが。
					 *   WindowsBMPはMSB側が左のピクセルなのかな？もしそうならば、BMPの仕様に合わせたのかも知れません。
					 */
					pixel = *src_buf;	/* ピクセル並びは MSB:45670123:LSB の順です */
					mask = *src_mask;	/* ピクセル並びは MSB:01234567:LSB の順です */

					/* 何ピクセル目から開始するかによって、適切な位置へ飛び込みます。 */
					x = obj.dw; /* 残りピクセル数 */
					switch(step) {
LOOP_02: /* 初回はここは飛び越します */
						/* 8ピクセル単位の開始点で、次のデータを取得します。 */
						pixel = *++src_buf;
						mask = *++src_mask;
#define PIXEL(p, m) \
						/* 不透明ピクセルなら、描きます。 */ \
						if(mask & (1 << (m))) *dest_buf = pixel >> (p) * 2 & 3; \
						dest_buf++; \
						/* 1ライン転送完了したら、抜けます。 */ \
						if(--x == 0) break;
					case 0: PIXEL(3, 7);
					case 1: PIXEL(2, 6);
					case 2: PIXEL(1, 5);
					case 3: PIXEL(0, 4);
					case 4: PIXEL(7, 3);
					case 5: PIXEL(6, 2);
					case 6: PIXEL(5, 1);
					default/*7*/: PIXEL(4, 0); goto LOOP_02;
#undef PIXEL
					}

					/* 転送先・転送元ピクセルアドレスを次のラインへ進めます。 */
					dest_buf += dest_stride;
					src_buf += src_stride;
					src_mask += src_stride;
				} while(--y != 0);
				return 1; /* 描画完了 */
			}
			break; /* 本物に任せます */

		case DRW_REVX:
			/* 転送先ピクセルアドレスを求めます。 */
			dest_buf = dest.buf + dest.w * obj.dy + obj.dx + obj.dw - 1;
			dest_stride = dest.w + obj.dw;

			switch(src.header.mask) {
			case 0/*2bitデータ*/:
				do {
					/* 8ピクセル単位のうち、何ピクセル目から開始するか？ */
					step = obj.sx & 7;

					/* 開始点から始まるか途中から始まるかわからないので、
					 * 初回はたとえstep=0でも、ここで最初のデータを取得することにします。
					 * ※P/ECEビットマップのピクセル並びは、MSB側が左のピクセルなんですね。
					 *   S1C33はリトルエンディアンなんだから、LSB側を左のピクセルにした方が扱いやすいと思うのですが。
					 *   WindowsBMPはMSB側が左のピクセルなのかな？もしそうならば、BMPの仕様に合わせたのかも知れません。
					 */
					pixel = *src_buf;	/* ピクセル並びは MSB:45670123:LSB の順です */

					/* 何ピクセル目から開始するかによって、適切な位置へ飛び込みます。 */
					x = obj.dw; /* 残りピクセル数 */
					switch(step) {
LOOP_11: /* 初回はここは飛び越します */
						/* 8ピクセル単位の開始点で、次のデータを取得します。 */
						pixel = *++src_buf;
#define PIXEL(p) \
						/* ピクセルを、描きます。 */ \
						*dest_buf-- = pixel >> (p) * 2 & 3; \
						/* 1ライン転送完了したら、抜けます。 */ \
						if(--x == 0) break;
					case 0: PIXEL(3);
					case 1: PIXEL(2);
					case 2: PIXEL(1);
					case 3: PIXEL(0);
					case 4: PIXEL(7);
					case 5: PIXEL(6);
					case 6: PIXEL(5);
					default/*7*/: PIXEL(4); goto LOOP_11;
#undef PIXEL
					}

					/* 転送先・転送元ピクセルアドレスを次のラインへ進めます。 */
					dest_buf += dest_stride;
					src_buf += src_stride;
				} while(--y != 0);
				return 1; /* 描画完了 */

			case 1/*2bitデータmask付き*/:
				do {
					/* 8ピクセル単位のうち、何ピクセル目から開始するか？ */
					step = obj.sx & 7;

					/* 開始点から始まるか途中から始まるかわからないので、
					 * 初回はたとえstep=0でも、ここで最初のデータを取得することにします。
					 * ※P/ECEビットマップのピクセル並びは、MSB側が左のピクセルなんですね。
					 *   S1C33はリトルエンディアンなんだから、LSB側を左のピクセルにした方が扱いやすいと思うのですが。
					 *   WindowsBMPはMSB側が左のピクセルなのかな？もしそうならば、BMPの仕様に合わせたのかも知れません。
					 */
					pixel = *src_buf;	/* ピクセル並びは MSB:45670123:LSB の順です */
					mask = *src_mask;	/* ピクセル並びは MSB:01234567:LSB の順です */

					/* 何ピクセル目から開始するかによって、適切な位置へ飛び込みます。 */
					x = obj.dw; /* 残りピクセル数 */
					switch(step) {
LOOP_12: /* 初回はここは飛び越します */
						/* 8ピクセル単位の開始点で、次のデータを取得します。 */
						pixel = *++src_buf;
						mask = *++src_mask;
#define PIXEL(p, m) \
						/* 不透明ピクセルなら、描きます。 */ \
						if(mask & (1 << (m))) *dest_buf = pixel >> (p) * 2 & 3; \
						dest_buf--; \
						/* 1ライン転送完了したら、抜けます。 */ \
						if(--x == 0) break;
					case 0: PIXEL(3, 7);
					case 1: PIXEL(2, 6);
					case 2: PIXEL(1, 5);
					case 3: PIXEL(0, 4);
					case 4: PIXEL(7, 3);
					case 5: PIXEL(6, 2);
					case 6: PIXEL(5, 1);
					default/*7*/: PIXEL(4, 0); goto LOOP_12;
#undef PIXEL
					}

					/* 転送先・転送元ピクセルアドレスを次のラインへ進めます。 */
					dest_buf += dest_stride;
					src_buf += src_stride;
					src_mask += src_stride;
				} while(--y != 0);
				return 1; /* 描画完了 */
			}
			break; /* 本物に任せます */

		case DRW_REVY:
			/* 転送先ピクセルアドレスを求めます。 */
			dest_buf = dest.buf + dest.w * (obj.dy + obj.dh - 1) + obj.dx;
			dest_stride = -dest.w - obj.dw;

			switch(src.header.mask) {
			case 0/*2bitデータ*/:
				do {
					/* 8ピクセル単位のうち、何ピクセル目から開始するか？ */
					step = obj.sx & 7;

					/* 開始点から始まるか途中から始まるかわからないので、
					 * 初回はたとえstep=0でも、ここで最初のデータを取得することにします。
					 * ※P/ECEビットマップのピクセル並びは、MSB側が左のピクセルなんですね。
					 *   S1C33はリトルエンディアンなんだから、LSB側を左のピクセルにした方が扱いやすいと思うのですが。
					 *   WindowsBMPはMSB側が左のピクセルなのかな？もしそうならば、BMPの仕様に合わせたのかも知れません。
					 */
					pixel = *src_buf;	/* ピクセル並びは MSB:45670123:LSB の順です */

					/* 何ピクセル目から開始するかによって、適切な位置へ飛び込みます。 */
					x = obj.dw; /* 残りピクセル数 */
					switch(step) {
LOOP_21: /* 初回はここは飛び越します */
						/* 8ピクセル単位の開始点で、次のデータを取得します。 */
						pixel = *++src_buf;
#define PIXEL(p) \
						/* ピクセルを、描きます。 */ \
						*dest_buf++ = pixel >> (p) * 2 & 3; \
						/* 1ライン転送完了したら、抜けます。 */ \
						if(--x == 0) break;
					case 0: PIXEL(3);
					case 1: PIXEL(2);
					case 2: PIXEL(1);
					case 3: PIXEL(0);
					case 4: PIXEL(7);
					case 5: PIXEL(6);
					case 6: PIXEL(5);
					default/*7*/: PIXEL(4); goto LOOP_21;
#undef PIXEL
					}

					/* 転送先・転送元ピクセルアドレスを次のラインへ進めます。 */
					dest_buf += dest_stride;
					src_buf += src_stride;
				} while(--y != 0);
				return 1; /* 描画完了 */

			case 1/*2bitデータmask付き*/:
				do {
					/* 8ピクセル単位のうち、何ピクセル目から開始するか？ */
					step = obj.sx & 7;

					/* 開始点から始まるか途中から始まるかわからないので、
					 * 初回はたとえstep=0でも、ここで最初のデータを取得することにします。
					 * ※P/ECEビットマップのピクセル並びは、MSB側が左のピクセルなんですね。
					 *   S1C33はリトルエンディアンなんだから、LSB側を左のピクセルにした方が扱いやすいと思うのですが。
					 *   WindowsBMPはMSB側が左のピクセルなのかな？もしそうならば、BMPの仕様に合わせたのかも知れません。
					 */
					pixel = *src_buf;	/* ピクセル並びは MSB:45670123:LSB の順です */
					mask = *src_mask;	/* ピクセル並びは MSB:01234567:LSB の順です */

					/* 何ピクセル目から開始するかによって、適切な位置へ飛び込みます。 */
					x = obj.dw; /* 残りピクセル数 */
					switch(step) {
LOOP_22: /* 初回はここは飛び越します */
						/* 8ピクセル単位の開始点で、次のデータを取得します。 */
						pixel = *++src_buf;
						mask = *++src_mask;
#define PIXEL(p, m) \
						/* 不透明ピクセルなら、描きます。 */ \
						if(mask & (1 << (m))) *dest_buf = pixel >> (p) * 2 & 3; \
						dest_buf++; \
						/* 1ライン転送完了したら、抜けます。 */ \
						if(--x == 0) break;
					case 0: PIXEL(3, 7);
					case 1: PIXEL(2, 6);
					case 2: PIXEL(1, 5);
					case 3: PIXEL(0, 4);
					case 4: PIXEL(7, 3);
					case 5: PIXEL(6, 2);
					case 6: PIXEL(5, 1);
					default/*7*/: PIXEL(4, 0); goto LOOP_22;
#undef PIXEL
					}

					/* 転送先・転送元ピクセルアドレスを次のラインへ進めます。 */
					dest_buf += dest_stride;
					src_buf += src_stride;
					src_mask += src_stride;
				} while(--y != 0);
				return 1; /* 描画完了 */
			}
			break; /* 本物に任せます */

		case DRW_REVX | DRW_REVY:
			/* 転送先ピクセルアドレスを求めます。 */
			dest_buf = dest.buf + dest.w * (obj.dy + obj.dh - 1) + obj.dx + obj.dw - 1;
			dest_stride = -dest.w + obj.dw;

			switch(src.header.mask) {
			case 0/*2bitデータ*/:
				do {
					/* 8ピクセル単位のうち、何ピクセル目から開始するか？ */
					step = obj.sx & 7;

					/* 開始点から始まるか途中から始まるかわからないので、
					 * 初回はたとえstep=0でも、ここで最初のデータを取得することにします。
					 * ※P/ECEビットマップのピクセル並びは、MSB側が左のピクセルなんですね。
					 *   S1C33はリトルエンディアンなんだから、LSB側を左のピクセルにした方が扱いやすいと思うのですが。
					 *   WindowsBMPはMSB側が左のピクセルなのかな？もしそうならば、BMPの仕様に合わせたのかも知れません。
					 */
					pixel = *src_buf;	/* ピクセル並びは MSB:45670123:LSB の順です */

					/* 何ピクセル目から開始するかによって、適切な位置へ飛び込みます。 */
					x = obj.dw; /* 残りピクセル数 */
					switch(step) {
LOOP_31: /* 初回はここは飛び越します */
						/* 8ピクセル単位の開始点で、次のデータを取得します。 */
						pixel = *++src_buf;
#define PIXEL(p) \
						/* ピクセルを、描きます。 */ \
						*dest_buf-- = pixel >> (p) * 2 & 3; \
						/* 1ライン転送完了したら、抜けます。 */ \
						if(--x == 0) break;
					case 0: PIXEL(3);
					case 1: PIXEL(2);
					case 2: PIXEL(1);
					case 3: PIXEL(0);
					case 4: PIXEL(7);
					case 5: PIXEL(6);
					case 6: PIXEL(5);
					default/*7*/: PIXEL(4); goto LOOP_31;
#undef PIXEL
					}

					/* 転送先・転送元ピクセルアドレスを次のラインへ進めます。 */
					dest_buf += dest_stride;
					src_buf += src_stride;
				} while(--y != 0);
				return 1; /* 描画完了 */

			case 1/*2bitデータmask付き*/:
				do {
					/* 8ピクセル単位のうち、何ピクセル目から開始するか？ */
					step = obj.sx & 7;

					/* 開始点から始まるか途中から始まるかわからないので、
					 * 初回はたとえstep=0でも、ここで最初のデータを取得することにします。
					 * ※P/ECEビットマップのピクセル並びは、MSB側が左のピクセルなんですね。
					 *   S1C33はリトルエンディアンなんだから、LSB側を左のピクセルにした方が扱いやすいと思うのですが。
					 *   WindowsBMPはMSB側が左のピクセルなのかな？もしそうならば、BMPの仕様に合わせたのかも知れません。
					 */
					pixel = *src_buf;	/* ピクセル並びは MSB:45670123:LSB の順です */
					mask = *src_mask;	/* ピクセル並びは MSB:01234567:LSB の順です */

					/* 何ピクセル目から開始するかによって、適切な位置へ飛び込みます。 */
					x = obj.dw; /* 残りピクセル数 */
					switch(step) {
LOOP_32: /* 初回はここは飛び越します */
						/* 8ピクセル単位の開始点で、次のデータを取得します。 */
						pixel = *++src_buf;
						mask = *++src_mask;
#define PIXEL(p, m) \
						/* 不透明ピクセルなら、描きます。 */ \
						if(mask & (1 << (m))) *dest_buf = pixel >> (p) * 2 & 3; \
						dest_buf--; \
						/* 1ライン転送完了したら、抜けます。 */ \
						if(--x == 0) break;
					case 0: PIXEL(3, 7);
					case 1: PIXEL(2, 6);
					case 2: PIXEL(1, 5);
					case 3: PIXEL(0, 4);
					case 4: PIXEL(7, 3);
					case 5: PIXEL(6, 2);
					case 6: PIXEL(5, 1);
					default/*7*/: PIXEL(4, 0); goto LOOP_32;
#undef PIXEL
					}

					/* 転送先・転送元ピクセルアドレスを次のラインへ進めます。 */
					dest_buf += dest_stride;
					src_buf += src_stride;
					src_mask += src_stride;
				} while(--y != 0);
				return 1; /* 描画完了 */
			}
			break; /* 本物に任せます */
		}
	}

	/* 高速ルーチンが対応していない描画方式なら、本物に任せます。 */
	return old_LCDDrawObject(obj);
}

/****************************************************************************
 *	ユーティリティ：フォント
 ****************************************************************************/

/* シンプルライブラリのput_1char()内部関数(\usr\PIECE\lib\src\simple\symbol.c)を参考にしました。
 * また、pceFontPut()関数(\usr\PIECE\sysdev\pcekn\font.c)に倣って、ビューポート内/外による場合分けを行いました。
 * フォント描画領域が完全にビューポート内なら、クリッピングを省略し、二倍程度高速に描画できます。
 */
unsigned short
render_font(RENDER* render, int x, int y, unsigned short code, int type, int color)
{
	SURFACE* surface;
	unsigned char* vptr;
	const unsigned char* p;
	int ofs, w, h, x_min, y_min, x_max, y_max, c;

	/* フォントサイズを求めます。 */
	type &= 0x7f; /* bit7(改行指示)は無視 */
	if(type < 0 || 2 < type) /*DIE()*/return 0; /* 無効なフォントタイプ、容量削減のためDIE()しません */
	w = render_font_size[type][0];
	h = render_font_size[type][1];

	 /* 半角空白は頻繁に指定されると思うので、描画を省略して高速化します。
	  * w,hは戻り値に使うので、w,hを取得する前にスキップしないよう注意！
	  */
	if(code == ' ') goto SKIP;

	/* フォントアドレスを求めます。 */
	pceFontSetType(type);
	p = pceFontGetAdrs(code);

	/* 半角フォント描画… */
	if((int)p & (1 << 31)) {
		p = (unsigned char*)((int)p & ~(1 << 31)); /* 半角ビットを下ろす */
		/* 描画範囲を求めます。 */
		x_min = x;
		y_min = y;
		x_max = x + w;
		y_max = y + h;
		/* 完全にビューポート内なら、クリッピング不要です。 */
		if(render->left <= x_min && x_max <= render->right &&
		   render->top  <= y_min && y_max <= render->bottom) {
			surface = render->surface;
			ofs = surface->w * y + x;
			vptr = surface->vbuff + ofs;
			for(y = y_min; y < y_max; y++) {
				c = *p++;
				for(x = x_min; x < x_max; x++) {
					if(c & 0x80) *vptr = color;
					vptr++;
					c <<= 1;
				}
				vptr += surface->w - w; /* 次の行へ */
			}
		/* ビューポートをはみ出していたら、要クリッピング。 */
		} else {
			for(y = y_min; y < y_max; y++) {
				c = *p++;
				for(x = x_min; x < x_max; x++) {
					if(c & 0x80) render_point(render, x, y, color);
					c <<= 1;
				}
			}
		}
	/* 全角フォント描画… */
	} else {
		w *= 2; /* 全角なので二文字分の幅。戻り値に使うのでwを変更する必要あり */
		/* 描画範囲を求めます。 */
		x_min = x;
		y_min = y;
		x_max = x + w;
		y_max = y + h;
		/* 完全にビューポート内なら、クリッピング不要です。 */
		if(render->left <= x_min && x_max <= render->right &&
		   render->top  <= y_min && y_max <= render->bottom) {
			surface = render->surface;
			ofs = surface->w * y + x;
			vptr = surface->vbuff + ofs;
			for(y = y_min; y < y_max; y += 2) { /* 2ラインづつ描く */
				c = p[0] << 16 | p[1] << 8 | p[2]; /* 3バイトで2ライン分 */
				p += 3;
				for(x = x_min; x < x_max; x++) {
					if(c & 0x800000) *(vptr + 0         ) = color;
					if(c &    0x800) *(vptr + surface->w) = color;
					vptr++;
					c <<= 1;
				}
				vptr += surface->w * 2 - w; /* 二行下へ */
			}
		/* ビューポートをはみ出していたら、要クリッピング。 */
		} else {
			for(y = y_min; y < y_max; y += 2) { /* 2ラインづつ描く */
				c = p[0] << 16 | p[1] << 8 | p[2]; /* 3バイトで2ライン分 */
				p += 3;
				for(x = x_min; x < x_max; x++) {
					if(c & 0x800000) render_point(render, x, y + 0, color);
					if(c &    0x800) render_point(render, x, y + 1, color);
					c <<= 1;
				}
			}
		}
	}
SKIP:
	return h << 8 | w;
}

/****************************************************************************
 *	P/ECE MOD Driver
 ****************************************************************************/

int
mod_seq(MODDRIVER* driver)
{
	MODHEADER* header = driver->header;
	MODSAMPLE* sample;
	MODCHANNEL* channel;
	int chno, smpno, period, effect, eff_ff, eff_0f, eff_f0;
	unsigned int *row, note;

	/* チックを進めます。 */
	driver->tick += driver->tempo;
	if(driver->tick >= driver->speed * 125) {
		driver->tick -= driver->speed * 125;

		/* ローを取得します。
		 * * ローやシーケンス番号は、エフェクトによって変更される可能性があるので、
		 *   ローからシーケンス番号への繰上げや終了判定は、ロー取得直前に行います。
		 */
		if(driver->rowno >= 64) {
			driver->rowno = 0;
			driver->seqno++;
		}
		if(driver->seqno >= header->seqlen) {
			if(header->seqrep < 127) { /* イコール127の場合も演奏終了です！ */
				driver->seqno = header->seqrep;
			} else {
				return 1; /* シーケンス終了 */
			}
		}
		row = driver->pattern[header->sequence[driver->seqno]] + (driver->chans * driver->rowno);
		driver->rowno++; /* 次回のためにローを進めておきます */

		/* 各チャネルのノートを処理します。 */
		for(chno = 0, channel = driver->channel; chno < driver->chans; chno++, channel++, row++) {
			note = SWAPW(*row);

			/* サンプル番号を変更します。 */
			smpno = (note >> 24 & 0xf0) | (note >> 12 & 0xf);
			if(smpno) {
				smpno--; /* ノート上のサンプル番号は#1ベースなので、#0ベースに補正 */
				sample = &header->sample[smpno];
				channel->smpno = smpno;
				channel->volume = sample->volume;
			}

			/* ピリオド値を変更します。 */
			period = note >> 16 & 0xfff;
			if(period) {
				channel->period = period; /* 発声開始 */
				channel->offset = 0;
			}

			/* エフェクトを格納します。 */
			effect = note & 0xfff;
			channel->effect = effect;
		}
	}

	/* 各チャネルのエフェクトを処理します。 */
	for(chno = 0, channel = driver->channel; chno < driver->chans; chno++, channel++) {
		effect = channel->effect;
		if(effect) {
			eff_ff = effect      & 0xff;	/* bit 7〜0 */
			eff_0f = effect      & 0x0f;	/* bit 3〜0 */
			eff_f0 = effect >> 4 & 0x0f;	/* bit 7〜4 */
			switch(  effect >> 8 & 0x0f) {	/* bit11〜8 */
			/*---------- 音程に関するエフェクト ----------*/
			case 0x1: /* Slide Up */
				channel->period -= eff_ff;
				if(channel->period < 0) channel->period = 0;
				/* エフェクト継続 */
				break;
			case 0x2: /* Slide Down */
				channel->period += eff_ff;
				if(channel->period > 0xfff) channel->period = 0xfff;
				/* エフェクト継続 */
				break;
			/*---------- 音量に関するエフェクト ----------*/
			case 0x5: /* Tone Portamento + Volume Slide */
			case 0x6: /* Vibrato + Volume Slide */
			case 0xa: /* Volume Slide */
				/* トーンポルタメント・ビブラートは未対応です。 */
				if(eff_0f) {
					channel->volume -= eff_0f;
					if(channel->volume < 0) channel->volume = 0;
				} else {
					channel->volume += eff_f0;
					if(channel->volume > 64) channel->volume = 64;
				}
				/* エフェクト継続 */
				break;
			case 0xc: /* Set Volume */
				channel->volume = eff_ff;
				channel->effect = 0; /* エフェクト解除 */
				break;
			/*---------- 演奏位置に関するエフェクト ----------*/
			case 0x9: /* Set Sample Offset */
				channel->offset = eff_ff << (8/*MOD仕様*/ + 8/*固定小数*/);
				channel->effect = 0; /* エフェクト解除 */
				break;
			case 0xb: /* Position Jump */
				driver->seqno = eff_ff;
				driver->rowno = 0;
				channel->effect = 0; /* エフェクト解除 */
				break;
			case 0xd: /* Pattern Break */
				/* 注意！同一ROWに二つ以上のPattern Breakを置かないで下さい！
				 * もしも同一ROWに二つのPattern Breakがあると、一度に二つ先のパターンへ進んでしまいます。
				 * Mod Plug Trackerでは、同一ROWに二つ以上のPattern Breakがあっても、一つしか先へ進まないのですが、
				 * P/ECE MOD Driverでは、容量制限が厳しいため、このチェックを行うことができませんでした。
				 * データ作成時に注意して、同一ROWに二つ以上のPattern Breakを置かないようにしてください。
				 */
				driver->seqno++;
				driver->rowno = eff_ff;
				channel->effect = 0; /* エフェクト解除 */
				break;
			case 0xf: /* Set Speed */
				if(eff_ff <= 32) {
					driver->speed = eff_ff; /*  0〜32:スピード設定 */
				} else {
					driver->tempo = eff_ff; /* 33〜255:テンポ設定  */
				}
				channel->effect = 0; /* エフェクト解除 */
				break;
			/*{{未対応エフェクト*/
			//case 0x0: /* Arpeggio */
			//case 0x3: /* Tone Portamento */
			//case 0x4: /* Vibrato */
			//case 0x7: /* Tremolo */
			//case 0x8: /* Set Panning Position */
			/**/#ifdef WIN32
			/**/default:
			/**/	fprintf(stderr, "IGN-EFF: %03x\n", effect);
			/**/	break;
			/**/#endif /*WIN32*/
			/*}}未対応エフェクト*/
			case 0xe: /* Extended Effects */
				switch(eff_f0) {
				/*---------- 音程に関する拡張エフェクト ----------*/
				case 0x1: /* Fine Slide Up */
					channel->period -= eff_0f;
					if(channel->period < 0) channel->period = 0;
					channel->effect = 0; /* エフェクト解除 */
					break;
				case 0x2: /* Fine Slide Down */
					channel->period += eff_0f;
					if(channel->period > 0xfff) channel->period = 0xfff;
					channel->effect = 0; /* エフェクト解除 */
					break;
				/*---------- 音量に関する拡張エフェクト ----------*/
				case 0xa: /* Fine Volume Slide Up */
					channel->volume += eff_0f;
					if(channel->volume > 64) channel->volume = 64;
					channel->effect = 0; /* エフェクト解除 */
					break;
				case 0xb: /* Fine Volume Slide Down */
					channel->volume -= eff_0f;
					if(channel->volume < 0) channel->volume = 0;
					channel->effect = 0; /* エフェクト解除 */
					break;
				/*{{未対応拡張エフェクト*/
				//case 0x0: /* Set Filter */
				//case 0x3: /* Glissando Control */
				//case 0x4: /* Set Vibrato Waveform */
				//case 0x5: /* Set Fine Tune */
				//case 0x6: /* Set/Jump to Loop */
				//case 0x7: /* Set Tremolo Waveform */
				//case 0x8: /* NOT USED */
				//case 0x9: /* Retrig Note */
				//case 0xc: /* Note Cut */
				//case 0xd: /* Note Delay */
				//case 0xe: /* Pattern Delay */
				//case 0xf: /* Invert Loop */
				/**/#ifdef WIN32
				/**/default:
				/**/	fprintf(stderr, "IGN-EFF: %03x\n", effect);
				/**/	break;
				/**/#endif /*WIN32*/
				/*}}未対応拡張エフェクト*/
				}
				break;
			}
		}
	}

	return 0;
}

int
mod_mix(MODDRIVER* driver, short wbuff[/*MODBUFLEN*/])
{
	static int mixbuf[MODBUFLEN]; /* ミキシングバッファ、ＳＴＡＴＩＣです！ */

	MODHEADER* header = driver->header;
	int i, chno, period, length, repofs, replen, volume, offset, step;
	int* pmix;
	short* pout;
	char* smpdat;
	MODSAMPLE* sample;
	MODCHANNEL* channel;

	/* まず、ミキシングバッファをクリアします。 */
	memset(mixbuf, 0, sizeof mixbuf);

	/* 発生中の各チャネルについて... */
	for(chno = 0, channel = driver->channel; chno < driver->chans; chno++, channel++) {
		if(!(period = channel->period)) continue;

		/* サンプル情報などを取り出しておきます。 */
		sample = &header->sample[channel->smpno];
		smpdat = driver->smpdat[channel->smpno];
		length = SWAPH(sample->length) * 2 << 8; /* 24.8bit */
		repofs = SWAPH(sample->repofs) * 2 << 8; /* 24.8bit */
		replen = SWAPH(sample->replen) * 2 << 8; /* 24.8bit */
		if(replen <= 1 * 2 << 8) replen = 0; /* ※歴史上の理由(？)でreplen=1もループなしとする */
		volume = channel->volume;
		offset = channel->offset;

		/* ピリオド値からサンプルオフセット増分を求めます。
		 * * 定数の由来：
		 *	AmigaのPAL基本クロック(?) = 7093789.2[Hz] ⇒ 二分周してサウンド回路に供給？
		 *	P/ECEのサウンド出力周波数 =   16000  [Hz]
		 * * サンプルオフセット増分：
		 *	再生レート    = 7093789.2 / (period * 2)
		 *	増分[24.8bit] = (再生レート / 16000) * 256
		 *	              = ((7093789.2 / (period * 2)) / 16000) * 256
		 *	              = (((7093789.2 / 2) / period) / 16000) * 256
		 *	              = ((7093789.2 / (2 * 16000)) / period) * 256
		 *	              = ((7093789.2 * 256) / (2 * 16000)) / period
		 *	              = 56750.3136 / period
		 */
		step = 56750 / period;

		for(i = 0, pmix = mixbuf; i < MODBUFLEN; i++, pmix++) {
LOOP:
			/* サンプルオフセットをサンプルデータ内にクリピングします。 */
			if(!replen) {
				/* ループなし：
				 * 0→lengthまで再生して、発声停止します。
				 */
				if(offset >= length) {
					channel->period = 0; /* 発声停止 */
					goto NO_LOOP;
				}
			} else {
				/* ループあり：
				 * 0→(repofs+replen),repofs→(repofs+replen),...
				 * ※(repofs+replen)〜lengthの区間は再生しません。
				 */
				if(offset >= repofs + replen) {
					offset -= replen;
					goto LOOP;
				}
			}

			/* ミキシングバッファにサンプル値×ボリュームを加算します。 */
			*pmix += smpdat[offset >> 8] * volume;

			/* サンプルオフセットを進めます。 */
			offset += step;
		}
NO_LOOP:
		/* サンプルオフセットを書き戻します。 */
		channel->offset = offset;
	}

	/* ミキシングバッファ(32ビット)から出力バッファ(16ビット)へコピーします。 */
	for(i = 0, pmix = mixbuf, pout = wbuff; i < MODBUFLEN; i++, pmix++, pout++) {
		volume = *pmix << 1/*音量増幅、調整可*/;
		     if(volume < -32767) volume = -32767;
		else if(volume >  32767) volume =  32767;
		*pout = volume;
	}

	return 0;
}

