nicotakuya.hatenablog.com
前回の続きです。HuCを使ったPCエンジンのプログラミングについて紹介します。
ここまで順調に進んできましたが、今回の課題は超苦手分野、、、サウンド出力です。
困ったことにHuCにはサウンド出力の機能が入ってません。
ドキュメントを見ても
「PSG player is started, and will be available soon(近いうちに公開)」
とあります。これが書かれたのが2005年ですが、更新は2020年現在まで止まっています。
ライブラリが対応していないと、どうしようもありません。ただ、不思議なことにライブラリのソースを見ると、それらしい関数の名前(PSG_~)は存在しています。もしかして、ソースを少し修正するだけで使えるようになるかもしれません。
あと、いろいろ検索すると、HuC関連のサンプルプログラムがヒットしました。
https://github.com/uli/huc
これを見ると「st.c」「st.h」というサウンドのライブラリが存在しています。ローカルのhucフォルダ内にはこのファイルはありません。知らないところでアップデートしてるのでしょうか。
しかし、このライブラリは自前のhucではコンパイルできませんでした。なぜできないのか、原因がわかりません。
ここで、完全に手詰まりになってしまったのですが、hucのdocフォルダを漁ってみると、「PSG.txt」というテキストファイルを発見しました。
今まで「資料がない」と愚痴ってましたが、、、反省です。
この「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です。
波形のデータはPSG.txtからコピペしました。波形は32バイトで表現します。振幅の範囲はPSG.txtに0~32と書かれていますが、正しくは0~31でしょうか。
ビルドして、ROMイメージをPCエンジンエミュレータで実行すると、「ポー」という感じの音が鳴り続けます。
これはサウンド系のレジスタを直接叩くという、過激なプログラムです。まだ安全性が確認できていません。
もっと良い方法があるかもしれないので、引き続き検討してみたいです。
(追記2020/12/05)
ノート番号を変えたり、左右の音量を変えられるようにしてみました。
表計算ソフトの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; } } }
実行するとノート番号60~71番までを順番に鳴らします。音の出力は左右、右のみ、左のみの順番で切り替わります。
音が止まる時にプツンという短い音が出てしまいます。どこかプログラムが間違っていると思います。