PCエンジンのプログラミング(その6)サウンド出力

nicotakuya.hatenablog.com
前回の続きです。HuCを使ったPCエンジンのプログラミングについて紹介します。

ここまで順調に進んできましたが、今回の課題は超苦手分野、、、サウンド出力です。
困ったことにHuCにはサウンド出力の機能が入ってません。
ドキュメントを見ても
「PSG player is started, and will be available soon(近いうちに公開)
とあります。これが書かれたのが2005年ですが、更新は2020年現在まで止まっています。

f:id:nicotakuya:20201204003855p:plain
PSG関連の関数が存在する?

ライブラリが対応していないと、どうしようもありません。ただ、不思議なことにライブラリのソースを見ると、それらしい関数の名前(PSG_~)は存在しています。もしかして、ソースを少し修正するだけで使えるようになるかもしれません。

f:id:nicotakuya:20201204003933p:plain
GitHubサウンドライブラリを発見

あと、いろいろ検索すると、HuC関連のサンプルプログラムがヒットしました。
https://github.com/uli/huc

これを見ると「st.c」「st.h」というサウンドのライブラリが存在しています。ローカルのhucフォルダ内にはこのファイルはありません。知らないところでアップデートしてるのでしょうか。
しかし、このライブラリは自前のhucではコンパイルできませんでした。なぜできないのか、原因がわかりません。
ここで、完全に手詰まりになってしまったのですが、hucのdocフォルダを漁ってみると、「PSG.txt」というテキストファイルを発見しました。

f:id:nicotakuya:20201204004038p:plain
サウンドレジスタの解説

今まで「資料がない」と愚痴ってましたが、、、反省です。
この「PSG.txt」を読むと、PCエンジンサウンド系のレジスタについて書かれています。
番地やビットの配置がわかりましたので、次のプログラムを作ってみました。

/*PSG test*/
/* based by "huc-3.21-win\doc\pce\psg.txt" */

#include "huc.h"

const char waveform[]={
 18,22,24,26,28,28,30,30,30,30,28,28,26,24,22,18,
 12, 8, 6, 4, 2, 2, 0, 0, 0, 0, 2, 2, 4, 6, 8,12
};

#define FREQ12BIT 254
/*12bit freq = (3580000/32)/Frequency */

char *psgch;    /*Channel*/
char *psgmvol;  /*Master Volume*/
char *psgfreql; /*Frequency*/
char *psgfreqh; /*Frequency*/
char *psgctrl;  /*control*/
char *psglrvol; /*L/R Volume*/
char *psgdata;  /*Waveform*/

main()
{
	int i;

	psgch   = 0x800;
	psgmvol = 0x801;
	psgfreql= 0x802;
	psgfreqh= 0x803;
	psgctrl = 0x804;
	psglrvol= 0x805;
	psgdata = 0x806;

	disp_on();
	cls();

	vsync();

	*psgch = 0;		/*Channel*/

	*psgctrl = (1<<6); 	/*reset*/
	*psgctrl = (0<<6); 	/*waveform transfer*/
	for(i=0;i<32;i++){
		*psgdata = waveform[i];	/*Waveform*/
	}

	*psgfreql = FREQ12BIT & 0xff;	/*Frequency L*/
	*psgfreqh = FREQ12BIT >> 8;	/*Frequency H*/

	*psglrvol = 0xff;	/*L/R Volume*/
	*psgmvol = 0xee;	/*Master Volume*/
	*psgctrl = (1<<7)+ 0x1f; /*Channel on*/

	put_string("PSG TEST 440Hz",3,3);

	while(1){
		vsync();
	}
}

こんな感じです。世界一シンプルなサウンドドライバです。
使用しているチャンネルは0番。周波数は440Hzです。

f:id:nicotakuya:20201204101605p:plain
波形データ

波形のデータはPSG.txtからコピペしました。波形は32バイトで表現します。振幅の範囲はPSG.txtに0~32と書かれていますが、正しくは0~31でしょうか。

f:id:nicotakuya:20201204004138p:plain
実行結果。440Hzの音を鳴らし続けます

ビルドして、ROMイメージをPCエンジンエミュレータで実行すると、「ポー」という感じの音が鳴り続けます。
これはサウンド系のレジスタを直接叩くという、過激なプログラムです。まだ安全性が確認できていません。
もっと良い方法があるかもしれないので、引き続き検討してみたいです。

(追記2020/12/05)
ノート番号を変えたり、左右の音量を変えられるようにしてみました。

f:id:nicotakuya:20201205070145p:plain
表計算ソフトでレジスタ値を計算

表計算ソフトのpower関数を使ってノート番号から周波数を計算して、
そこからさらにレジスタの値を計算してみました。
周波数設定のレジスタの値は(3580000/32)/周波数で計算できます。

/*PSG test2*/
/* based by "huc-3.21-win\doc\pce\psg.txt" */

#include "huc.h"

const char waveform[]={
	18,22,24,26,28,28,30,30,30,30,28,28,26,24,22,18,
	12, 8, 6, 4, 2, 2, 0, 0, 0, 0, 2, 2, 4, 6, 8,12
};


const int freqtbl[]={
	428,	/* note60	262Hz */
	404,	/* note61	277Hz */
	381,	/* note62	294Hz */
	360,	/* note63	311Hz */
	339,	/* note64	330Hz */
	320,	/* note65	349Hz */
	302,	/* note66	370Hz */
	285,	/* note67	392Hz */
	269,	/* note68	415Hz */
	254,	/* note69	440Hz */
	240,	/* note70	466Hz */
	227	/* note71	494Hz */
};

char *psgch;    /*Channel*/
char *psgmvol;  /*Master Volume*/
char *psgfreql; /*Frequency*/
char *psgfreqh; /*Frequency*/
char *psgctrl;  /*control*/
char *psglrvol; /*L/R Volume*/
char *psgdata;  /*Waveform*/

char wave_cnt;

/**/
wave_init()
{
	int i;

	psgch   = 0x800;
	psgmvol = 0x801;
	psgfreql= 0x802;
	psgfreqh= 0x803;
	psgctrl = 0x804;
	psglrvol= 0x805;
	psgdata = 0x806;

	vsync();

	*psgch = 0;		/*Channel*/

	*psgctrl = (1<<6); 	/*reset*/
	*psgctrl = (0<<6); 	/*waveform transfer*/
	for(i=0;i<32;i++){
		*psgdata = waveform[i];	/*Waveform*/
	}
	*psgmvol = 0xee;	/*Master Volume*/
	wave_cnt = 0;
}

/**/
wave_play(note, volume, length)
char note, volume, length;
{
	int reg;

	reg = freqtbl[note % 12];

	note -= 60;
	while(note < 0){
		note += 12;
		reg <<= 1;
	}
	while(note >= 12){
		note -= 12;
		reg >>= 1;
	}

	*psgfreql = reg & 0xff;	/*Frequency L*/
	*psgfreqh = reg >> 8;	/*Frequency H*/

	*psglrvol = volume;	/*L/R Volume*/
	*psgctrl = (1<<7) + 0x1f; /*Channel on(volume=31)*/

	wave_cnt = length;
}

/**/
main()
{
	char test,note,volume;

	wave_init();
	disp_on();
	cls();

	put_string("PSG TEST",2,2);
	put_string("NOTE NUMBER:  0x",4,4);
	put_string(" L/R VOLUME:  0x",4,6);

	test = 0;
	while(1){
		vsync();
		if(wave_cnt > 0){
			wave_cnt--;
			if(wave_cnt==0){
/*				*psglrvol = 0;*/
				*psgctrl = (0<<7) + 0x0;
			}
		}
		if(wave_cnt==0){
			note = 60+(test % 12);
			if(test < 12){
				volume = 0xff;
			}else if(test < 24){
				volume = 0x0f;
			}else{
				volume = 0xf0;
			}
			put_hex(note  , 2, 20,4);
			put_hex(volume, 2, 20,6);
			wave_play(note, volume, 100);
			test++;
			if(test >= 12*3) test=0;
		}
	}
}
f:id:nicotakuya:20201205070016p:plain
ノート番号と左右の音量を変更

実行するとノート番号60~71番までを順番に鳴らします。音の出力は左右、右のみ、左のみの順番で切り替わります。
音が止まる時にプツンという短い音が出てしまいます。どこかプログラムが間違っていると思います。

PCエンジンのプログラミング(その5)マップ表示

nicotakuya.hatenablog.com
前回に続いて、「HuC」を使ったPCエンジンのプログラミング方法について紹介します。
前回はBGの表示に成功しましたが、今度はBGをスクロールさせます。単にスクロールするだけではなく、表示画面より大きなマップ内を動いているように見せたいと思います。これを「マップ機能」と呼ぶことにします。

f:id:nicotakuya:20201202101044p:plain
画面スクロールのサンプル「SCROLL」

マップ機能についてはサンプルの「SCROLL」が参考になります。
このプログラムではBGが横方向にスクロールします。表示サイズは32×24セルですが、96×24セルのマップを移動しているように表現しています。
画面下にある32×4セルのBGはスクロールさせません。この固定した画面を「パネル」と呼んでいます。

f:id:nicotakuya:20201202101525p:plain
load_map関数

マップ機能ではBGは2×2セル(16×16ピクセル)で1つのパーツとして扱います。HuCではこれを「タイル」と呼んでいるのですが、個人的にはパターンのことをタイルと呼びたいので、混同を避けるために「マップチップ」と呼ぶことにします。マップ全体のサイズは96×24セルですが、マップを格納する配列の要素は48×12です。
ソースのSCROLL.cを見て、わかったことは、、、
・サンプルのマップチップは10種類。0~9番の番号が割り振られている。
・scroll関数でBGをスクロールさせる。scroll関数の第1引数がウインドウ番号。スクロールする画面をウインドウ0番、パネルをウインドウ1番に割り振ることで、1枚のBGを2枚に見せている。
・#inctileマクロでマップチップの画像のポインタを設定する。
・set_map_data関数を使ってマップのサイズとデータを設定する。
・set_tile_data関数でマップチップを設定する。この段階ではパターンはVRAMに転送されていない。
・load_tile関数を呼ぶと、パターンがVRAMに転送される。
・load_map関数を使ってBGを描画する。最初の一回だけは、表示画面全部を描く。
・16ピクセルぶんスクロールするたびに、load_map関数を使って、BGを部分的に描き足す。
、、、こんな感じです。マップ機能のために新しい関数がたくさん出てきます。

f:id:nicotakuya:20201202104622p:plain
0x600番地とは一体?

あと、ソースを見て気になったのが「0x600」という記述です。資料がなくて、これの意味がよくわかりませんでした。
VRAMのアドレスですよね。なぜ0x3FFより大きいのか?

f:id:nicotakuya:20201202104706p:plain
0x601番地に変更した場合

ためしに「0x601」に変更したら、BATの設定が1セルぶんズレました。
なので、ウインドウ1のBATの格納開始位置は0x600番地なのかなと思います。間違っていたらすみません。
続いて、横スクロールを行うプログラムを新しく作ります。
マップチップの素材を描きます。

f:id:nicotakuya:20201202105208p:plain
マップの素材

BMPPNGで保存します。色は16色までに限定します。

f:id:nicotakuya:20201202104527p:plain
#inctileの格納方法

初登場の#inctileマクロですが、前回の#incchrマクロとは違って、8x8ピクセルずつジグザグにデータ化します。

f:id:nicotakuya:20201202104755p:plain
マップチップ用の変換モードを追加

この仕様に対応するため、SPEDITにマップチップ用の変換機能を新しく追加しました。
https://sites.google.com/site/yugenkaisyanico/sprite-editor
変換すると、バイナリファイル「map_pattern.bin」「map_palette.bin」が生成されます。パターン用に10種類×64=640ワードのVRAMを消費します。パレットは0番にします。
パネルの素材を描きます。使える色は16色までです。

f:id:nicotakuya:20201202105233p:plain
パネルの素材

BMPPNGで保存します。

f:id:nicotakuya:20201202104848p:plain
BATをカスタマイズできるように

BGデータに変換します。
パネルのタイル番号は512番以降に割り振ることにします。256番~295番まではマップチップに使っているためです。パレットは2番です。
そこで、SPEDITにカスタマイズ機能を追加してみました。
変換すると、バイナリファイル「bg_pattern.bin」「bg_palette.bin」「bg_bat.bin」が生成されます。この3つのファイルの役割は前回と同じです。
パターン用に32×4×16=2048ワードもVRAMを使ってしまいます。

f:id:nicotakuya:20201202104937p:plain
マップデータのバイナリ化

あと、マップデータがソースにあると邪魔なので、バイナリ化してみました。これはちょっと手抜きです。本格的にゲームを作りたいという場合には、マップエディタが必要になると思います。
以上の素材がそろったので、あとはプログラムです。

/* scroll test by takuya matsubara*/
/* original program:scrolling demo(by David Michel)*/
#include "huc.h"

#define MAP_VRAM     0x1000
#define PANEL_VRAM   0x2000
#define BAT2_VRAM    0x0600

#define MAP_PAL      0
#define PANEL_PAL    2

#incbin(panel_pattern, "bg_pattern.bin")
#incbin(panel_pal, "bg_palette.bin")
#incbin(panel_bat, "bg_bat.bin")
#incbin(map_pattern, "map_pattern.bin")
#incbin(map_pal, "map_palette.bin")
#incbin(demo_map,"mapdata.bin")

#define MAP_WIDTH  48
#define MAP_HEIGHT 12
#define NB_TILE    12

const char map_pal_ref[NB_TILE] = {
	MAP_PAL<<4, MAP_PAL<<4, MAP_PAL<<4, MAP_PAL<<4,
	MAP_PAL<<4, MAP_PAL<<4, MAP_PAL<<4, MAP_PAL<<4,
	MAP_PAL<<4, MAP_PAL<<4, MAP_PAL<<4, MAP_PAL<<4
};

int sx,map_x;
int score;

main()
{
	disp_off();
	cls();

	set_map_data(demo_map, MAP_WIDTH, MAP_HEIGHT);
	set_tile_data(map_pattern, NB_TILE, map_pal_ref);
	load_tile(MAP_VRAM);
	load_palette(MAP_PAL, map_pal, 1);
	load_map(0, 0, 0, 0, 34/2, 24/2);
	set_font_pal(PANEL_PAL);

	load_vram(PANEL_VRAM, panel_pattern, 32*4*16);
	load_bat(BAT2_VRAM, panel_bat, 32, 4);
	load_palette(PANEL_PAL, panel_pal, 1);
	put_string("score: ", 1, 25);

	scroll(0, 0,    0,    0, (24*8)-1, 0xC0);
	scroll(1, 0, 24*8, 24*8, (28*8)-1, 0x80);

	disp_on();

	sx = 0;
	map_x = 0;
	score = 0;

	while(1){
		sx++;
		if((sx & 0xF) == 0) {
			map_x ++;
			if (map_x  >= MAP_WIDTH){
				map_x -= MAP_WIDTH;
			}
			load_map(
			  (sx + 256) >> 4,0,  /* sx,sy */
			   map_x + (32/2), 0, /* map x,y */
			   (2/2), (24/2));    /* w,h */

			score += 1;
		}
		put_number(score, 5, 8, 25);

		scroll(0, sx, 0, 0, ((24*8)-1), 0xC0);
		vsync();
	}
}

サンプルのSCROLLから機能を削っただけのプログラムです。BGをスクロールさせたら、16ピクセルごとにload_mapを呼んでいます。
これをビルドしてROMイメージを作ります。

f:id:nicotakuya:20201202105011p:plain
実行結果。マップがスクロールする

PCエンジンエミュレータで実行すると、こうなります。無事にスクロールさせることができました。
ひたすら、右方向にスクロールします。マップチップが48個ぶん進んだら、最初に戻ります。

PCエンジンのプログラミング(その4)BGの表示

nicotakuya.hatenablog.com
前回の続きです。今回は「BG」を表示したいと思います。
PCエンジンはとにかく資料が見当たらないです。頼りになるのはサンプルの「PONG」だけです。

f:id:nicotakuya:20201129010530p:plain
サンプルのPONG

PONGではBGを表示するために「#incchr」「#incpal」「#incbat」という3つのマクロを使っています。
このマクロの機能を調べるために次のようなプログラムを作ってみました。

/*BG dump by takuya matsubara*/
#include "huc.h"

#define BACKGROUND_FILE "special.pcx"
#incchr(bg_pattern,BACKGROUND_FILE,32,28);
#incpal(bg_palette,BACKGROUND_FILE);
#incbat(bg_bat,BACKGROUND_FILE,0x1000,32,28);

main()
{
	int i;
	char x,y;

	disp_on();
	cls();

	put_string("Pattern",1,1);
	for(i=0;i<66;i++){
		x=(i % 6)*5+1;
		y=(i/6)+2;
		put_hex(bg_pattern[i], 4, x,y);
	}

	put_string("Palette",1,14);
	for(i=0;i<24;i++){
		x=(i % 6)*5+1;
		y=(i/6)+15;
		put_hex(bg_palette[i], 4, x,y);
	}

	put_string("BAT(Background Attribute Table)",1,20);
	for(i=0;i<36;i++){
		x=(i % 6)*5+1;
		y=(i/6)+21;
		put_hex(bg_bat[i], 4, x,y);
	}

	while(1){
		vsync();
	}
}

以上のプログラムをPCエンジンエミュレータで実行すると、こうなります。

f:id:nicotakuya:20201130141434p:plain
実行結果

PONGの背景の画像をBGデータ化してダンプ表示します。画像はPCXファイル(special.pcx)です。
本来はもっと大量にデータがあるのですが、画面の都合で表示数を制限しています。
これを見て、わかったことは次の通り。
「#incchr」はパターンを生成するマクロ。1ピクセルのデータ量は4bit(16色)。タイル1枚あたり8×8ピクセル=データ量は16ワード。
「#incpal」はパレットを生成するマクロ。スプライト用でも使用。1色16bitのうち実データ9bit(緑3+赤3+青3bit)。全部で16色×16パレット=256ワード。
「#incbat」はBATを生成するマクロ。BAT(Background Attribute Table)というのは、タイル番号などを格納したバッファです。ファミコンでいうところのネームテーブルです。1セルを表現するために1ワード消費します。bit15~12がカラーパレット番号で、bit11~0がタイル番号です。タイル番号は0x100番から始まっています。おそらく、0x100番未満はフォントに使っているのではないでしょうか。これに関係して、VRAMの0x1000番地からパターンデータが格納されます。
サンプルのBGの描画サイズが32×28セル。なので、BAT全体のデータ量は896ワードです。BATはVRAMの0x0000~0x03FF番地(0x037F?)に格納されるようです。
要するにパターン+パレット+BATの3要素がBGには必要ということです。以上を参考にして、自作ツール(spedit)に機能を追加しました。
https://sites.google.com/site/yugenkaisyanico/sprite-editor

f:id:nicotakuya:20201130144111p:plain
BGのテストパターンを自動生成

メニューで「16色test(PC Engine BG)」ボタンを押すと、テストパターンのBGデータを自動生成します。
「bg_pattern.bin」「bg_palette.bin」「bg_bat.bin」という3つのバイナリファイルを作ります。スプライトと違って、BGはパターンデータの格納方法が特殊すぎるのですが、詳しくはspeditのソースを見てください。
続いて、BGを表示するプログラムを改良しました。

/*BG dump2 by takuya matsubara*/
#include "huc.h"

#incbin(bg_pattern,"bg_pattern.bin");
#incbin(bg_palette,"bg_palette.bin");
#incbin(bg_bat    ,"bg_bat.bin");

main()
{
	int i;
	char x,y;
	int work;

	disp_on();
	cls();

	put_string("Pattern",1,1);
	for(i=0;i<66;i++){
		x=(i % 6)*5+1;
		y=(i / 6)+2;
		work = bg_pattern[i*2]+0x100*bg_pattern[i*2+1];
		put_hex(work, 4, x,y);
	}

	put_string("Palette",1,14);
	for(i=0;i<24;i++){
		x=(i % 6)*5+1;
		y=(i / 6)+15;
		work = bg_palette[i*2]+0x100*bg_palette[i*2+1];
		put_hex(work, 4, x,y);
	}

	put_string("BAT(Background Attribute Table)",1,20);
	for(i=0;i<36;i++){
		x=(i % 6)*5+1;
		y=(i / 6)+21;
		work = bg_bat[i*2]+0x100*bg_bat[i*2+1];
		put_hex(work, 4, x,y);
	}

	while(joy(0)==0){
		vsync();
	}

	load_background(bg_pattern, bg_palette, bg_bat, 32, 28);
	for(i=0;i<16;i++){
		put_string("<Palette=",16,i);
		put_hex(i, 1, 16+9,i);
	}
	while(1){
		vsync();
	}
}

ここでは「#incbin」マクロでBGデータのバイナリファイルを組み込みます。「#incchr」「#incpal」「#incbat」マクロが不要になります。
エミュレータで実行すると、こうなります。

f:id:nicotakuya:20201130141523p:plain
実行結果

ここでゲームパッドのボタンを押すと、、、

f:id:nicotakuya:20201130141602p:plain
240色表示

、、、こうなります。
BGの行がパレット番号に対応しています。
これでわかりましたが、1パレット16色のうち1色は透明色に割り振られています。なので、実質15色です。画面の左端1列がまるごと黒くなっています。
15×16=240色が表現可能なBGの色。背景色が加わった場合は241色になります。

f:id:nicotakuya:20201130141747j:plain
変換前の画像


あと、自作ツールにBMP→BGデータの変換機能を追加してみました。たとえば、このような画像を用意します。これをPhotoshopで解像度を256x224ピクセルに調節。16色に変換してBMPで保存します。

f:id:nicotakuya:20201130141844p:plain
ツールでデータ化

ここで「PC Engine BG」のボタンを押す。
3つのバイナリファイルが生成されるので、先ほどのテストパターンと差し替えます。

f:id:nicotakuya:20201130141924p:plain
BGの表示に成功

PCエンジンエミュレータで実行すると、こうなります。BGにオリジナルの画像を表示することができました。これでPCXファイルが完全に不要になりました
現状の不満点としては、変換ツールが1パレットにしか対応していないので、同時発色が16色のみになっています。
あと、見た目が元画像よりものっぺりしてしまいました。これはパレットのカラーが24bit→9bitに変換されてしまうためです。色がくっ付いてしまったぶん、パレットを無駄に消費してしまいます。
このへんはこれから改善します。

(追記2020/12/3)

f:id:nicotakuya:20201203234339p:plain
同じパターンを飛ばしてVRAMを節約

SPEDITに新機能を追加して、同じ絵柄のパターンは登録をスキップできるようにしました。これでVRAMをいくらか節約できます。チェックボックスで機能のオン/オフが設定できます。

続き
nicotakuya.hatenablog.com

PCエンジンのプログラミング(その3)Spriteを動かす

nicotakuya.hatenablog.com
前回の続きです。「HuC」を使って、PCエンジンのプログラミングをやってみたいと思います。
前回は変換ツールがPCXファイルしか対応してない&使いにくいというところで終わっていましたが、ついに変換ツールを自作してみました。
こちらで公開中です。

sites.google.com
このツールを使うと、BMPファイルから画像/パレットデータを抽出することができます。

f:id:nicotakuya:20201129020851p:plain
自作のスプライトエディタで画像をバイナリ化する

ツールの実行画面です。
ここでは例として、16x16ピクセルx64個のキャラクター画像を描いてみました。キャラクター1個あたりのデータ量は64ワードなので、64個で4096ワードのVRAMを消費します。

f:id:nicotakuya:20201129022034p:plain
メニューを拡大

変換時のメニューです。
binary 16colorのボタンがPCエンジン用です。
変換後、「pattern.bin」(パターン)と「palette.bin」(パレット)という2つのバイナリファイルが出力されます。
最初は「const char pattern[]={ データ、、、」みたく、テキストで出力したかったのですが、インクルードするとhucで謎のエラーが出てしまうので、バイナリにしました。

動作確認用のプログラムを作ってみました。以下の通りです。

/* sprite move by takuya matsubara*/
#include "huc.h"
#incbin(pattern,"pattern.bin");
#incbin(palette,"palette.bin");

#define CHRADDR   0x6F00
#define SPMAX 64

main()
{
    int num;
    char newx,newy;
    char x[SPMAX],y[SPMAX],x1[SPMAX],y1[SPMAX];

    load_vram(CHRADDR,pattern,64*64);
    init_satb();
    for(num=0; num<SPMAX; num++){
        spr_set(num);
        spr_pattern(CHRADDR+(64*num));
        spr_ctrl(SIZE_MAS|FLIP_MAS, SZ_16x16|NO_FLIP);
        spr_pal(0);
        spr_pri(1);
        x[num]=16+(num % 14)*16;
        y[num]=16+(num / 14)*16;
        x1[num]=(num % 7);
        y1[num]=(num % 5);
        if((x1[num]==0)&&(y1[num]==0))y1[num]=1;
    }
    vsync(1);
    set_sprpal(0, palette);

    while(1){
         vsync(1);
         for(num=0; num<SPMAX; num++){
             spr_set(num);
             newx = x[num]+x1[num];
             newy = y[num]+y1[num];
             if((newx<=8)||(newx>=248)) x1[num] *= -1;
             if((newy<=8)||(newy>=232)) y1[num] *= -1;
             spr_x(newx);
             spr_y(newy);
             x[num]=newx;
             y[num]=newy;
         }
         satb_update();
    }
}

パターン/パレットデータは「#incbin」マクロで組み込みます。
load_vram関数でVRAMに転送します。
パターンの転送先は適切なアドレスがよくわかりませんが、0x6F00番地にしてみました。0x7EFF番地までデータが格納されます。
これで、「#incspr」や「#incpal」を使わず、PCXファイルが不要になりました。
このソースをビルドして、ROMイメージ(pceファイル)を生成します。

f:id:nicotakuya:20201129020957p:plain
PCエンジンエミュレータの実行結果

PCエンジンエミュレータで実行した結果がこちらです。
64個のスプライトが画面をバウンドします。
エミュのせいかもしれませんが、全然処理落ちしてません。実機だとどうなるのか気になります。

続き
nicotakuya.hatenablog.com

PCエンジンのプログラミング(その2)Spriteのデータ構造

nicotakuya.hatenablog.com

前回の続きです。

前回は開発環境の「HuC」を導入して、サンプルプログラムを動かすところまでやってみました。

f:id:nicotakuya:20201127121618p:plain

「PONG」に付属するボールの画像

使ってみて気が付きましたが、画像の読み込みに対応しているファイル形式が「PCX」のみで、どうにも使いにくいです。

PCXファイルというのは、こんな感じの画像ファイルです。DOSの時代には良く使われていたみたいですが、今となってはマイナーなファイル形式だと思います。PCXファイルはirfanviewで開けましたが、Photoshopでは開けませんでした。

なので、PCXファイル以外に対応する変換ツールを自作する必要が出てきました。

 

f:id:nicotakuya:20201127121329p:plain

画像/パレットデータをダンプする

まず、HuCで「#inc~」マクロでPCXファイルを取り込んだ時にどんな感じにデータ化されるのかを調べます。ドキュメントを見てもよくわからないので、上記のようなプログラムを自作しました。「BALL.PCX」はサンプルのPONGに付属する画像ファイルです。

 

f:id:nicotakuya:20201127121438p:plain

PCエンジンエミュレータでの実行結果

hucとpceasでビルドして、PCエンジンエミュレータで実行すると、こういう画面が表示されます。画面上半分が画像データ、画面下半分がパレットデータです。

これだけだと、意味不明な16進数の羅列ですが、、、

 

f:id:nicotakuya:20201127121519p:plain

画像/パレットデータの構造

 いろいろ試行錯誤しながらデータを再構築してみました。マクロの出力は次のようなフォーマットだと思われます。

「#incspr」

・スプライト用は1枚あたり16x16ピクセル。他にも32x16、16x32ピクセルも選択可。

・1枚あたり4bit=16色。透明色を除くと実質15色。

・1枚あたりのパターンはint型(16bit)×64要素=64ワード。1要素は2値16ピクセルで考える。

・16要素で1プレーン。全4プレーン。下位ビットから順番にデータを格納。

 

「#incpal」

・1パレット16色。int型(16bit)×16要素=16ワード。カラーインデックス0番から格納。

・1要素あたりは9bit(bit8~6=緑、bit5~3=赤、bit0~2=青)

・8諧調x8諧調x8諧調=512色。

・0番のカラーは透明色。

 

これで画像/パレットデータの構造がわかりましたので、次は変換ツールを自作してみたいと思います。

nicotakuya.hatenablog.com