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番までを順番に鳴らします。音の出力は左右、右のみ、左のみの順番で切り替わります。
音が止まる時にプツンという短い音が出てしまいます。どこかプログラムが間違っていると思います。