Friday, May 1, 2020

http://members.at.infoseek.co.jp/DrHell/ps1/index.html

  
JUNAUGMAR
Previous capture19Next capture
200920102011
63 captures
11 Dec 2005 - 25 Oct 2011
¥n 

NYOットやろうぜ

~独田地獄斎が贈るPlayStation研究序説~
                     NNNNNN                           
                     NNNNNNNNNNNN                     
                     NNNNNNNNNNNNNNNN                 
                     NNNNNNNNNNNNNNNNNNN              
                     NNNNNNNNN   NNNNNNNNN            
                     NNNNNNNNN    NNNNNNNNN           
                     NNNNNNNNN    NNNNNNNNN           
                     NNNNNNNNN    NNNNNNNNNN          
                     NNNNNNNNN    NNNNNNNNNN          
                     NNNNNNNNN    NNNNNNNNNN          
                     NNNNNNNNN    NNNNNNNNNN          
                     NNNNNNNNN    NNNNNNNNN           
                     NNNNNNNNN    NNNNNNNN            
                   OONNNNNNNNN    NNNNNNN             
              YYYOOOONNNNNNNNN                        
          YYYYYYYYYYYNNNNNNNNN     OOOOOOOOOOOO       
      YYYYYYYYYYYYYYYNNNNNNNNNYYOOOOOOOOOOOOOOOOOOO   
 OOOOOOYYYYYYYYY     NNNNNNNNNYYYYYYYOOO     OOOOOOOOO
OOOOOOOOOOO        YYNNNNNNNNNYYYYY        OOOOOOOOOOO
OOOOOOOOO     OOOYYYYNNNNNNNNN        YYYYYYYYYOOOOOO 
   OOOOOOOOOOOOOOOOOONNNNNNNNN   YYYYYYYYYYYYYYY      
       OOOOOOOOOOOO  NNNNNNNNNOYYYYYYYYYYYY           
                     NNNNNNNNNOOOOOOYYY               
                          NNNNOOOO                    

はじめに

本ページは未来のPlayStationエミュレータ開発者への情報提供を目的とする。 ただし、ここでコーディングの講義をするつもりはない。 なぜならコーディングの観点からエミュレータ特有と呼べるものは何もないからだ。 動的コンパイルは珍しいかもしれないが、これはエミュレータの本質ではない。 換言すると、この程度のコーディングに躓くようではプログラマとして終っている。 基礎からやり直した方がいいんじゃないか。

解析環境

PlayStation本体
SCPH-7000 (SONY)
制御マシン
いわゆる自作DOS/V
通信デバイス
PS X-TERMINATOR (Future Console Design)
通信デバイスファームウェア
caetla 0.34 (K-Communications)
通信プログラム
caetools (K-Communications)
directio (K-Communications)
解析プログラム作成
GNU Binary Utilities 2.8.1 mipsel-generic-elf/ecoff (こぺる)
GNU C/C++ Compiler 2.7.2.3 mipsel-generic-elf/ecoff (こぺる)
Borland C++Compiler 5.5 (Borland)

参考文献

「ネットやろうぜ」ユーザガイドホームページ
http://www.scei.co.jp/Net/guide/user/
PADUA PLAYSTATION RESOURCE
http://psx.rules.org/psxrul2.shtml
ただし、これらには多くの誤記があるので鵜呑みにしないこと。 正規開発マニュアルから抜粋したと思われる部分にすら誤記がある (正規マニュアルにも誤記はある)。 開発者ならば最終的には自分でウラをとること。 幸い、PlayStationにはその現実的な手段があるのだから。
東芝のTX39のドキュメント
http://www.semicon.toshiba.co.jp/product/micro/index.html

アーキテクチャ

ブロック図

メモリマップ

表. メモリマップ
アドレス内容
0x00000000~0x001FFFFFメインメモリ(命令キャッシュ有効)
0x00200000~0x003FFFFFメインメモリ ミラー(命令キャッシュ有効)
0x00400000~0x005FFFFFメインメモリ ミラー(命令キャッシュ有効)
0x00600000~0x007FFFFFメインメモリ ミラー(命令キャッシュ有効)
0x00800000~
0x1F000000~PIO
0x1F0?????~
0x1F800000~0x1F8003FFスクラッチパッド
0x1F000400~
0x1F801000~I/Oポート(いわゆるメモリマップドI/O)
0x1F80????~
0x1FC00000~0x1FC7FFFFOS ROM
0x1F800000~
0x80000000~0x801FFFFFメインメモリ ミラー(命令キャッシュ有効)
0x80200000~0x803FFFFFメインメモリ ミラー(命令キャッシュ有効)
0x80400000~0x805FFFFFメインメモリ ミラー(命令キャッシュ有効)
0x80600000~0x807FFFFFメインメモリ ミラー(命令キャッシュ有効)
0x80800000~
0x9FC00000~0x9FC7FFFFOS ROM ミラー
0x9FC80000~
0xA0000000~0xA01FFFFFメインメモリ ミラー(命令キャッシュ無効)
0xA0200000~0xA03FFFFFメインメモリ ミラー(命令キャッシュ無効)
0xA0400000~0xA05FFFFFメインメモリ ミラー(命令キャッシュ無効)
0xA0600000~0xA07FFFFFメインメモリ ミラー(命令キャッシュ無効)
0xA0800000~
0xBFC00000~0xBFC7FFFFOS ROM ミラー
0xBFC80000~
0xFFFE0130~0xFFFE0133SwapCache?
0xFFFE0134~

レジスタマップ

表. レジスタマップ
アドレスレジスタ名(仮)内容
0x1F801000?拡張メモリ領域指定?
0x1F801004?I/Oポート終端指定?
0x1F801008??
0x1F80100C??
0x1F801010??
0x1F801014?SPU DMAのウェイト?
0x1F801018?OTC DMAのウェイト?
0x1F80101C??
0x1F801020?CD DMAのウェイト?



0x1F801040COMA_DATACOMAデータ
0x1F801044COMA_STATCOMAステータス
0x1F801048COMA_MODECOMAモード
0x1F80104ACOMA_CTRLCOMAコントロール
0x1F80104ECOMA_BAUDCOMAボー
0x1F801050SIO_DATASIOデータ
0x1F801054SIO_STATSIOステータス
0x1F801058SIO_MODESIOモード
0x1F80105ASIO_CTRLSIOコントロール
0x1F80105ESIO_BAUDSIOボー
0x1F801060RAM_SIZE実効メモリサイズ



0x1F801070I_STAT割り込みステータス
0x1F801074I_MASK割り込みマスク



0x1F801080D0_MADRDMACチャネル0(MDECin)メモリアドレス
0x1F801084D0_BCRDMACチャネル0(MDECin)ブロックカウント
0x1F801088D0_CHCRDMACチャネル0(MDECin)チャネルコントロール



0x1F801090D1_MADRDMACチャネル1(MDECout)メモリアドレス
0x1F801094D1_BCRDMACチャネル1(MDECout)ブロックカウント
0x1F801098D1_CHCRDMACチャネル1(MDECout)チャネルコントロール



0x1F8010A0D2_MADRDMACチャネル2(GPU)メモリアドレス
0x1F8010A4D2_BCRDMACチャネル2(GPU)ブロックカウント
0x1F8010A8D2_CHCRDMACチャネル2(GPU)チャネルコントロール



0x1F8010B0D3_MADRDMACチャネル3(CD)メモリアドレス
0x1F8010B4D3_BCRDMACチャネル3(CD)ブロックカウント
0x1F8010B8D3_CHCRDMACチャネル3(CD)チャネルコントロール



0x1F8010C0D4_MADRDMACチャネル4(SPU)メモリアドレス
0x1F8010C4D4_BCRDMACチャネル4(SPU)ブロックカウント
0x1F8010C8D4_CHCRDMACチャネル4(SPU)チャネルコントロール



0x1F8010D0D5_MADRDMACチャネル5(PIO)メモリアドレス
0x1F8010D4D5_BCRDMACチャネル5(PIO)ブロックカウント
0x1F8010D8D5_CHCRDMACチャネル5(PIO)チャネルコントロール



0x1F8010E0D6_MADRDMACチャネル6(OTC)メモリアドレス
0x1F8010E4D6_BCRDMACチャネル6(OTC)ブロックカウント
0x1F8010E8D6_CHCRDMACチャネル6(OTC)チャネルコントロール



0x1F8010F0D_PCRDMAC優先度コントロール
0x1F8010F4D_ICRDMAC割り込みコントロール



0x1F801100T0_COUNTタイマ0(システム/ピクセル)カウント
0x1F801104T0_MODEタイマ0(システム/ピクセル)モード
0x1F801108T0_COMPタイマ0(システム/ピクセル)コンペア



0x1F801110T1_COUNTタイマ1(システム/水平同期)カウント
0x1F801114T1_MODEタイマ1(システム/水平同期)モード
0x1F801118T1_COMPタイマ1(システム/水平同期)コンペア



0x1F801120T2_COUNTタイマ2(システム/8分周)カウント
0x1F801124T2_MODEタイマ2(システム/8分周)モード
0x1F801128T2_COMPタイマ2(システム/8分周)コンペア



0x1F801800CD_REG0CDレジスタ0
0x1F801801CD_REG1CDレジスタ1
0x1F801802CD_REG2CDレジスタ2
0x1F801803CD_REG3CDレジスタ3



0x1F801810GPU_DATAGPUデータ/コマンド
0x1F801814GPU_CTRLGPUステータス/コントロール



0x1F801820MDEC_DATAMDECデータ/コマンド
0x1F801824MDEC_CTRLMDECステータス/コントロール



0x1F801C00SPU_REG00SPUレジスタ0
0x1F801C02SPU_REG01SPUレジスタ1
中略中略中略
0x1F801DFCSPU_REGFESPUレジスタ254
0x1F801DFESPU_REGFFSPUレジスタ255



0x1F802041?ディップスイッチ?




CPU(中央処理装置)

PlayStationのCPUはMIPSのR3000をベースとしたカスタムCPUである。 動作周波数は33868800Hzと言われ、エンディアンはリトルである。 若干の違いはあるが、それは青本も同じなので、 基本的な理解は東芝のTX39のドキュメントで十分である。

コード表

COP1,COP3,TLB系は存在しない。 サービスコール内のatofでCOP1命令が使われているがatof自体が呼ばれた試しがない。 おそらく試作機ではデバグ処理のために存在するが、結局オミットされたのだろう。
表. フィールドの名称
3130292827262524232221201918171615141312111009080706050403020100
oprsrtrdsafunct

表. op
op0x000x010x020x030x040x050x060x07
0x00SPECIALBCONDJJALBEQBNEBLEZBGTZ
0x08ADDIADDIUSLTISLTIUANDIORIXORILUI
0x10COP0
COP2




0x18







0x20LBLHLWLLWLBULHULWR
0x28SBSHSWLSW

SWR
0x30

LWC2




0x38

SWC2





表. funct(op=SPECIAL)
funct0x000x010x020x030x040x050x060x07
0x00SLL
SRLSRASLLV
SRLVSRAV
0x08JRJALR

SYSCALLBREAK

0x10MFHIMTHIMFLOMTLO



0x18MULTMULTUDIVDIVU



0x20ADDADDUSUBSUBUANDORXORNOR
0x28

SLTSLTU



0x30







0x38








表. rs(op=BCOND)
rs0x000x010x020x030x040x050x060x07
0x00BLTZBGEZ





0x08







0x10BLTZALBGEZAL





0x18








表. rs(op=COP0)
rs0x000x010x020x030x040x050x060x07
0x00MFC0


MTC0


0x08







0x10CP0
0x18

表. funct(op=COP0,rs=CP0)
funct0x000x010x020x030x040x050x060x07
0x00







0x08







0x10RFE






0x18







0x20







0x28







0x30







0x38








表. rs(op=COP2)
rs0x000x010x020x030x040x050x060x07
0x00MFC2
CFC2
MTC2
CTC2
0x08







0x10CP2
0x18

表. funct(op=COP2,rs=CP2)
funct0x000x010x020x030x040x050x060x07
0x00
RTPS



NCLIP
0x08



OP


0x10DPCSINTPLMVMVANCDSCDP
NCDT
0x18


NCCSCC
NCS
0x20NCT






0x28SQRDCPLDPCT

AVSZ3AVSZ4
0x30RTPT






0x38




GPFGPLNCCT
rs+b20をコードと一対一に対応させるのは、ただの慣習である。

I(命令)キャッシュ

ネットやろうぜの記述には重要部であるほど誤りがあるので、注意が必要である。 まずメモリ空間とキャッシュ(バースト転送)の有効/無効の関係を示した表において メモリ空間を取り違えている。また
Iキャッシュが有効な論理メモリ空間上の命令コードは通常の約5倍の速度でCPUに読み込まれます。 また、一旦読み込まれた命令コードはCPU内のIキャッシュメモリに保存されますので、 再実行時にはメインメモリへのアクセスなしに実行されます。
という文章もおかしい。 後半の文章があるから、前半はキャッシュミスした状態での実行速度と考えるしかない。 ならば理論的に24/9倍速以上にはならない。

スクラッチパッド Dキャッシュ

アドレス0x1F800000にマッピングされた、ストールの発生しないRAMを PlayStationの「方言」としてDキャッシュと呼んでいるが、 これは一般的な意味のDキャッシュではない。 一般的な意味のDキャッシュはPlayStationには存在しない。

GTE(Geometry Transformation Engine グラフィックデータ生成プロセッサ)

逆数テーブル

RTPS,RTPT命令において、投影変換係数
(int) (h * 65536 / Sz + 0.5)
が求められるが、実際は除算ではなく、逆数テーブルを用いた積算
(h * iSz + (1 << (N - 1))) >> N
が行われる。iSzは0x10000~0x1FFFFの範囲であり、小数部16ビットの浮動小数に相当する。 iSzは必ずしも
(65536 << N) / Sz
にはなっていないので、ブルートフォースアタックによる探索が必要になる。
Sz:32768~65535(N=16)について結果を示す。 Sz:0~32767については、Nを減らすだけなので割愛する。

エミュレート法

動的コンパイル法の一例

コード表から分かるようにR3000は単純な命令だけで構成されており、既にマイクロコードであるとも言える。 従って、一コードに対して一個のコンパイル済みデータを予め用意し、実行時に変数部分を書き換えるだけでも 実用に耐える動的コンパイルが可能である。例えばSRAV,SRLVでは、
/*
 *
 */
uchar __SRAV[] = {
    0x8B, 0x0D, '¥¥', '¥¥', '¥¥', '¥¥', /* 00 mov ecx,[GPR[rs]] 02 &GPR[rs] */
    0xA1, '¥¥', '¥¥', '¥¥', '¥¥',       /* 06 mov eax,[GPR[rt]] 07 &GPR[rt] */
    0xD3, 0xF8,                         /* 0B sar eax,cl */
    0xA3, '¥¥', '¥¥', '¥¥', '¥¥'        /* 0D mov [GPR[rd]],eax 0E &GPR[rd] */
};


/*
 *
 */
uchar __SRLV[] = {
    0x8B, 0x0D, '¥¥', '¥¥', '¥¥', '¥¥', /* 00 mov ecx,[GPR[rs]] 02 &GPR[rs] */
    0xA1, '¥¥', '¥¥', '¥¥', '¥¥',       /* 06 mov eax,[GPR[rt]] 07 &GPR[rt] */
    0xD3, 0xE8,                         /* 0B shr eax,cl */
    0xA3, '¥¥', '¥¥', '¥¥', '¥¥'        /* 0D mov [GPR[rd]],eax 0E &GPR[rd] */
};
の様なデータを予め用意し、実行時に
/*
 *
 */
#define _rs_    ((opcode >> 21) & 31)
#define _rt_    ((opcode >> 16) & 31)
#define _rd_    ((opcode >> 11) & 31)


/*
 *
 */
#define CPU_SET_SRAV()                                    ¥
    {                                                     ¥
        *(ulong *) (&__SRAV[0x02]) = (ulong) & GPR[_rs_]; ¥
        *(ulong *) (&__SRAV[0x07]) = (ulong) & GPR[_rt_]; ¥
        *(ulong *) (&__SRAV[0x0E]) = (ulong) & GPR[_rd_]; ¥
        CPU_OPCODE                 = __SRAV;              ¥
        CPU_OPSIZE                 = sizeof(__SRAV);      ¥
    }


/*
 *
 */
#define CPU_SET_SRLV()                                    ¥
    {                                                     ¥
        *(ulong *) (&__SRLV[0x02]) = (ulong) & GPR[_rs_]; ¥
        *(ulong *) (&__SRLV[0x07]) = (ulong) & GPR[_rt_]; ¥
        *(ulong *) (&__SRLV[0x0E]) = (ulong) & GPR[_rd_]; ¥
        CPU_OPCODE                 = __SRLV;              ¥
        CPU_OPSIZE                 = sizeof(__SRLV);      ¥
    }
の様に変数部分を書き換える。後はこれを連結コピーして関数を作成し、出来上がった関数を実行する。

注意事項

巷で誤解があるようなので、注意を喚起すると、 実行毎に新たな関数を作成していたのでは、インタプリタよりも遅くなる。 出来上がった関数を管理・再利用することこそが、動的コンパイルの肝である。 動的コンパイルとは可変長データを対象としたキャッシュ機構を構築することに等しい。

コンパイル済データの作成法の一例

アセンブラで直接記述する以外に、以下の様にCで記述し、 ファイル出力、逆アセンブルするのも一つの手である。
/*
 *
 */
#define _rs_    1               /* 変数部分なので適当 */
#define _rt_    2               /* 変数部分なので適当 */
#define _rd_    3               /* 変数部分なので適当 */


/*
 *
 */
long GPR[32];


/*
 *
 */
void __SRAV(void)
{
    GPR[_rd_] = GPR[_rt_] >> GPR[_rs_];
}


/*
 *
 */
void ____SRAV(void)
{
}


/*
 *
 */
void __SRLV(void)
{
    GPR[_rd_] = ((ulong) GPR[_rt_]) >> GPR[_rs_];
}


/*
 *
 */
void ____SRLV(void)
{
}


/*
 *
 */
void SaveToFile(char *lpFileName, void *lpStart, void *lpEnd)
{
    FILE *fp;

    if (NULL == (fp = fopen(lpFileName, "wb"))) {
        return;
    }
    fwrite(lpStart, (ulong) lpEnd - (ulong) lpStart, 1, fp);
    fclose(fp);
}


/*
 *
 */
int main(void)
{
    SaveToFile("SRAV.X86", __SRAV, ____SRAV);
    SaveToFile("SRLV.X86", __SRLV, ____SRLV);
    return(0);
}

INTC(割り込みコントローラ)

I_STATレジスタ

ロードした場合
15141312111009080706050403020100

S
S
P
U
S
S
I
O
S
C
O
M
A
S
T
M
R
2
S
T
M
R
1
S
T
M
R
0
S
D
M
A
C
S
C
D
S
G
P
U
S
V
S
Y
N
C

フィールド内容
SVSYNCVSYNCの割り込みステータス
0:VSYNCから割り込み要求がない
1:VSYNCから割り込み要求があった
SGPU~SSPUSVSYNCと同様
ストアした場合
15141312111009080706050403020100

C
S
P
U
C
S
I
O
C
C
O
M
A
C
T
M
R
2
C
T
M
R
1
C
T
M
R
0
C
D
M
A
C
C
C
D
C
G
P
U
C
V
S
Y
N
C

フィールド内容
CVSYNCVSYNCの割り込みクリア
0→SVSYNCを0にする
CGPU~CSPUCVSYNCと同様

I_MASKレジスタ

ロードした場合
ストアした値が返る
ストアした場合
15141312111009080706050403020100

M
S
P
U
M
S
I
O
M
C
O
M
A
M
T
M
R
2
M
T
M
R
1
M
T
M
R
0
M
D
M
A
C
M
C
D
M
G
P
U
M
V
S
Y
N
C

フィールド内容
MVSYNCVSYNCの割り込みマスク
MVSYNCとSVSYNCがともに1である限り、INTCはCPUに割り込みを要求し続ける。
MGPU~MSPUMVSYNCと同様

DMAC(DMAコントローラ)

準備中

タイマ

タイマは設定によりシステムクロックではなくピクセル表示や水平同期によりカウントアップさせることができる。
表. カウントタイミング
タイプ画面幅[A][B]システムクロック/水平同期システムクロック/ピクセル表示システムクロック/垂直同期
非インターレースインターレース
偶数フィールド奇数フィールド
NTSC256566107.5005[A]/263[A]/89683=[B]/341[B]*263[B]*263[B]*262
320[A]/112038=[B]/426
368[A]/128081=[B]/487
512[A]/179366=[B]/682
640[A]/224339=[B]/853
PAL256674399.5367[A]/314[A]/106760=[B]/340[B]*314[B]*313[B]*312
320[A]/133764=[B]/426
368[A]/152604=[B]/486
512[A]/213834=[B]/681
640[A]/267214=[B]/851

メインメモリ(Main Memory or RAM 主記憶装置)

準備中

OS ROM(or BIOS 基本操作システム記憶装置)

準備中

MDEC(Motion Decoder データ伸長エンジン)

準備中

PIO(拡張パラレルポート)

準備中

SIO(or COMB 拡張シリアルポート)

準備中

GPU(グラフィック描画処理プロセッサ)

GPU_DATAレジスタ

ロードした場合
基本的には内部のリザルトレジスタの値が返るが、 StoreImageコマンド実行中の場合は、その結果が返る。
リザルトレジスタには、 GPU制御系コマンドの結果や StoreImageコマンドの最初の結果が格納される。 途中の結果は格納されない。
ストアした場合
データは内部の多目的FIFOに格納され、 必要な数のデータが貯まると、 GPU描画系コマンドが実行される。

GPU_CTRLレジスタ

ロードした場合
3130292827262524232221201918171615141312111009080706050403020100
o
d
e
t
m
o
d
e
i
s
e
m
p
t
y
d
a
t
a
r
d
y
i
s
i
d
l
e
d
m
a
r
d
y
r
e
s
e
r
v
e
d
i
s
s
t
o
p
i
s
i
n
t
e
r
i
s
r
g
b
2
4
i
s
p
a
l
v
r
e
s
h
r
e
s
1
h
r
e
s
2
r
e
s
e
r
v
e
d
u
n
k
n
o
w
n
r
e
s
e
r
v
e
d
p
b
c
p
b
w
d
f
e
d
t
d
t
p
a
b
r
t
y
t
x

フィールド内容
odeビデオ出力の走査フィールドがフレームバッファ上の奇数ラインかどうか。 よってインターレースでは数百水平同期毎、非インターレースでは1水平同期毎(指定ブランク区間は0のまま)に変化する。
tmode転送モード
isemptyFIFO(パケットバッファ)が空かどうか
datardyデータレディ
isidleアイドル状態である
dmardyDMAレディ
isstopビデオ出力を停止している
isinterインターレースで出力している(垂直解像度を倍密度にする場合は必須)
isrgb24rgb24ビットモードである
ispalPALモードである
vres垂直解像度
hres1水平解像度1
hres2水平解像度2(水平解像度1は無効になる)
unknown不明(オン/オフ操作は可能)
pbc優先度ビットコンペアを行う
pbw優先度ビットを書き込む
dfeインターレースであっても、 非インターレースと同様、 走査フィールドに関係なく描画する
dtdディザ処理を行う
tpテクスチャのビットモード
abr半透明率
tyテクスチャページのY座標
txテクスチャページのX座標
ストアした場合
GPU制御系コマンドが実行される。

SPU(サウンド処理プロセッサ)

ブロック図

ボイス

全24チャネルの内蔵音源のこと。各チャネルは独立に 「波形データ(ADPCMまたは共通ノイズ)×エンベロープ×ボイス音量(左右独立)」 の三者の掛け合わせを出力する。

エンベロープ

波形データにかけるバイアスのこと。
表. ADSRレート
レート[*]実効レート
分母分子(増加)分子(減少)
0~471(7 - (RATE & 3)) << (11 - (RATE >> 2))(-8 + (RATE & 3)) << (11 - (RATE >> 2))
48~1 << ((RATE >> 2) - 11)7 - (RATE & 3)-8 + (RATE & 3)
[*]ディケイレート、リリースレートは1/4で指定されるので4倍して考える

表. ADSRモード
モード内容
線形増加分母サンプリング時間毎にエンベロープレベルに分子(増加)を加算
線形減少分母サンプリング時間毎にエンベロープレベルに分子(減少)を加算
指数増加基本的には線形増加と同じだがエンベロープレベルが0x6000以上になるとレートが8増加
指数減少分母サンプリング時間毎にエンベロープレベルに ((分子(減少) * レベル) >> 15)を加算

表. ADSRステータス
ステータス開始条件終了条件飽和下限飽和上限
初期化キーオンするアタックレートに依存した初期化時間[*]が経過する00
アタック初期化が終了するエンベロープレベルが32767以上になる
32767
ディケイアタックが終了するエンベロープレベルがサステインレベル未満になる0
サステインディケイが終了するキーオフする032767
リリースキーオフするエンベロープレベルが0以下になる-1
停止・リリースが終了する
・ADPCMデコーダが強制停止する
キーオンする00
[*]アタックレート0~47では5サンプリング時間で、 それ以上は徐々に短くなる。 これはADPCMデコードのフィルタ処理に起因する、 残響が消えるための時間である。 レートが大きくなると、アタックの方で0になる時間が増えるから、 その分短くて良いのである。

ADPCM波形

ADPCMデコーダが圧縮データをデコードして得たサンプリング波形のこと。 重要なのはADPCMデコーダが(リバーブプロセスも)常時稼動していることだろう。 停止したように見えて、それはエンベロープを強制停止させた結果にすぎない。 つまり、常に割り込みが発生する可能性がある。 しかしながら、常時稼動のエミュレーションは、高負荷のワリに見返りが小さい。 モラトリアム期間を設けるのが現実解であろう。
表. ループフラグ
フィールド内容
0x04カレントブロックの先頭をループアドレスに設定する。
0x02エンベロープを強制停止しない。
0x01カレントブロックを演奏後、ループアドレスにジャンプする。 0x02が設定されていないと、エンベロープを強制停止する。
組み合わせとしては以下の5通りとなる(ループアドレスが強制的に設定された場合は、この限りではない)。
表. ループフラグの組み合わせ
ループフラグ内容
0x06,0x04カレントブロックの先頭をループアドレスに設定する。
0x03カレントブロック演奏後、ループアドレスにジャンプする。
0x07上2つの組み合わせにより、カレントブロックを延々と演奏する。
0x01ループアドレスにジャンプするが、エンベロープを停止するので無音になる。
0x05カレントブロックを延々と演奏するが、エンベロープを停止するので無音になる。

共通ノイズ波形

ADPCM波形の替わりに用いるランダム波形のこと。 チャネル毎にどちらかを選択できるが、 チャネル毎に異なるノイズ波形を得るといったことは出来ない。
アルゴリズム
/*
 * レベル変化時の計算式
 */
char Addition[64] = {
    1, 0, 0, 1, 0, 1, 1, 0,
    1, 0, 0, 1, 0, 1, 1, 0,
    1, 0, 0, 1, 0, 1, 1, 0,
    1, 0, 0, 1, 0, 1, 1, 0,
    0, 1, 1, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 1, 0, 0, 1
};

NoiseLevel = (short) (NoiseLevel + NoiseLevel + Addition[(NoiseLevel >> 10) & 63]);


/*
 * レベル変化の周期
 */
Freq = 0x8000 >> (NoiseClock >> 2);


/*
 * 周期が半減する頻度
 */
Half = ((NoiseClock & 3) * 2) / (4 + (NoiseClock & 3));

リバーブ

アルゴリズム
/*
 * PlayStation Reverberation Algorithm (C)Dr.Hell, 2005
 * 厳密には、左右の処理のタイミングは1サンプリング時間ずれており、
 * 各々は2サンプリング時間毎に実行される
 */
mAPF1    = *((ushort *) 0x1F801DC0) * 4;
mAPF2    = *((ushort *) 0x1F801DC2) * 4;
gIIR     = *((short *) 0x1F801DC4) / 32768.0;
gCOMB1   = *((short *) 0x1F801DC6) / 32768.0;
gCOMB2   = *((short *) 0x1F801DC8) / 32768.0;
gCOMB3   = *((short *) 0x1F801DCA) / 32768.0;
gCOMB4   = *((short *) 0x1F801DCC) / 32768.0;
gWALL    = *((short *) 0x1F801DCE) / 32768.0;
gAPF1    = *((short *) 0x1F801DD0) / 32768.0;
gAPF2    = *((short *) 0x1F801DD2) / 32768.0;
z0_Lsame = *((ushort *) 0x1F801DD4) * 4;
z0_Rsame = *((ushort *) 0x1F801DD6) * 4;
m1_Lcomb = *((ushort *) 0x1F801DD8) * 4;
m1_Rcomb = *((ushort *) 0x1F801DDA) * 4;
m2_Lcomb = *((ushort *) 0x1F801DDC) * 4;
m2_Rcomb = *((ushort *) 0x1F801DDE) * 4;
zm_Lsame = *((ushort *) 0x1F801DE0) * 4;
zm_Rsame = *((ushort *) 0x1F801DE2) * 4;
z0_Ldiff = *((ushort *) 0x1F801DE4) * 4;
z0_Rdiff = *((ushort *) 0x1F801DE6) * 4;
m3_Lcomb = *((ushort *) 0x1F801DE8) * 4;
m3_Rcomb = *((ushort *) 0x1F801DEA) * 4;
m4_Lcomb = *((ushort *) 0x1F801DEC) * 4;
m4_Rcomb = *((ushort *) 0x1F801DEE) * 4;
zm_Ldiff = *((ushort *) 0x1F801DF0) * 4;
zm_Rdiff = *((ushort *) 0x1F801DF2) * 4;
z0_Lapf1 = *((ushort *) 0x1F801DF4) * 4;
z0_Rapf1 = *((ushort *) 0x1F801DF6) * 4;
z0_Lapf2 = *((ushort *) 0x1F801DF8) * 4;
z0_Rapf2 = *((ushort *) 0x1F801DFA) * 4;
gLIN     = *((short *) 0x1F801DFC) / 32768.0;
gRIN     = *((short *) 0x1F801DFE) / 32768.0;
z1_Lsame = z0_Lsame - 1;
z1_Rsame = z0_Rsame - 1;
z1_Ldiff = z0_Ldiff - 1;
z1_Rdiff = z0_Rdiff - 1;
zm_Lapf1 = z0_Lapf1 - mAPF1;
zm_Rapf1 = z0_Rapf1 - mAPF1;
zm_Lapf2 = z0_Lapf2 - mAPF2;
zm_Rapf2 = z0_Rapf2 - mAPF2;
for (;;) {
    /*
     * LoadFromLowPassFilterは35もしくは39タップのFIRフィルタ
     * 最外周の係数が0でも結果は同じなので、35か39かは特定できず
     */
    L_in = gLIN * LoadFromLowPassFilterL();
    R_in = gRIN * LoadFromLowPassFilterR();


    /*
     * Left -> Wall -> Left Reflection
     */
    L_temp = ReadReverbWork(zm_Lsame);
    R_temp = ReadReverbWork(zm_Rsame);
    L_same = L_in + gWALL * L_temp;
    R_same = R_in + gWALL * R_temp;
    L_temp = ReadReverbWork(z1_Lsame);
    R_temp = ReadReverbWork(z1_Rsame);
    L_same = L_temp + gIIR * (L_same - L_temp);
    R_same = R_temp + gIIR * (R_same - R_temp);


    /*
     * Left -> Wall -> Right Reflection
     */
    L_temp = ReadReverbWork(zm_Rdiff);
    R_temp = ReadReverbWork(zm_Ldiff);
    L_diff = L_in + gWALL * L_temp;
    R_diff = R_in + gWALL * R_temp;
    L_temp = ReadReverbWork(z1_Ldiff);
    R_temp = ReadReverbWork(z1_Rdiff);
    L_diff = L_temp + gIIR * (L_diff - L_temp);
    R_diff = R_temp + gIIR * (R_diff - R_temp);


    /*
     * Early Echo(Comb Filter)
     */
    L_in = gCOMB1 * ReadReverbWork(m1_Lcomb) + gCOMB2 *ReadReverbWork(m2_Lcomb) + gCOMB3 *ReadReverbWork(m3_Lcomb) + gCOMB4 *ReadReverbWork(m4_Lcomb);
    R_in = gCOMB1 * ReadReverbWork(m1_Rcomb) + gCOMB2 *ReadReverbWork(m2_Rcomb) + gCOMB3 *ReadReverbWork(m3_Rcomb) + gCOMB4 *ReadReverbWork(m4_Rcomb);


    /*
     * Late Reverb(Two All Pass Filters)
     */
    L_temp = ReadReverbWork(zm_Lapf1);
    R_temp = ReadReverbWork(zm_Rapf1);
    L_apf1 = L_in - gAPF1 * L_temp;
    R_apf1 = R_in - gAPF1 * R_temp;
    L_in   = L_temp + gAPF1 * L_apf1;
    R_in   = R_temp + gAPF1 * R_apf1;
    L_temp = ReadReverbWork(zm_Lapf2);
    R_temp = ReadReverbWork(zm_Rapf2);
    L_apf2 = L_in - gAPF2 * L_temp;
    R_apf2 = R_in - gAPF2 * R_temp;
    L_in   = L_temp + gAPF2 * L_apf2;
    R_in   = R_temp + gAPF2 * R_apf2;


    /*
     * Output
     */
    SetOutputL(L_in);
    SetOutputR(R_in);


    /*
     * Write Buffer
     */
    WriteReverbWork(z0_Lsame, L_same);
    WriteReverbWork(z0_Rsame, R_same);
    WriteReverbWork(z0_Ldiff, L_diff);
    WriteReverbWork(z0_Rdiff, R_diff);
    WriteReverbWork(z0_Lapf1, L_apf1);
    WriteReverbWork(z0_Rapf1, R_apf1);
    WriteReverbWork(z0_Lapf2, L_apf2);
    WriteReverbWork(z0_Rapf2, R_apf2);


    /*
     * Update Circular Buffer
     */
    UpdateReverbWork();
}

CD(コンパクトディスク読込み装置)

CDコマンド

表. CDコマンドの引数
番号コマンド名123
0x00Sync0


0x01Nop0


0x02Setloc3AMinASecAFrac
0x03Play0/1Track

0x04Forward0


0x05Backward0


0x06ReadN0


0x07Standby0


0x08Stop0


0x09Pause0


0x0AReset0


0x0BMute0


0x0CDemute0


0x0DSetfilter2FileChan
0x0ESetmode1Mode

0x0FGetparam0


0x10GetlocL0


0x11GetlocP0


0x12ReadT10x01

0x13GetTN0


0x14GetTD1Track

0x15SeekL0


0x16SeekP0


0x17Setclock



0x18Getclock



0x19Test1Num

0x1AId0


0x1BReadS0


0x1CInit0


0x1D




0x1EReadTOC0


0x1F





表. CDコマンドの結果(一次)
番号コマンド名所要
クロック
σ%割り
込み
12345678
0x00Sync

0x031Stat






0x01Nop30547230x031Stat






0x02Setloc3155770x031Stat






0x03Play51444
0x031Stat






0x04Forward193250120x031Stat






0x05Backward12511900x031Stat






0x06ReadN7570170x031Stat






0x07Standby7475350x031Stat






0x08Stop43503130x031Stat






0x09Pause27648240x031Stat






0x0AReset80295120x031Stat






0x0BMute36114410x031Stat






0x0CDemute32768100x031Stat






0x0DSetfilter39105480x031Stat






0x0ESetmode41082520x031Stat






0x0FGetparam40163250x035StatMode0x00FileChan


0x10GetlocL43335200x038セクタの13番目~20番目のデータ(つまりRead,SeekLが必要)
0x11GetlocP37937170x038TrackIndexMinSecFracAMinASecAFrac
0x12ReadT98762130x031Stat






0x13GetTN40833
0x033StatBeginEnd




0x14GetTD39091
0x033StatAMinASec




0x15SeekL41771250x031Stat






0x16SeekP62041400x031Stat






0x17Setclock











0x18Getclock











0x19Test(0x20)40864140x034YearMonDayVer



0x1AId30980220x031Stat






0x1BReadS8192520x031Stat






0x1CInit25847290x031Stat






0x1D












0x1EReadTOC73292100x031Stat






0x1F













表. CDコマンドの結果(二次以降)
番号コマンド名所要
クロック
σ%割り
込み
12345678
0x00Sync











0x01Nop











0x02Setloc











0x03Play

0x018StatTrackIndexA/MinA/SecA/FracLvHiLvLo
0x04Forward











0x05Backward











0x06ReadN

0x011Stat






0x07Standby51429486550x021Stat






0x08Stop1276719420x021Stat






0x09Pause366579400x021Stat






0x0AReset365647960x021Stat






0x0BMute











0x0CDemute











0x0DSetfilter











0x0ESetmode











0x0FGetparam











0x10GetlocL











0x11GetlocP











0x12ReadT5770166500x021Stat






0x13GetTN











0x14GetTD











0x15SeekL

0x021Stat






0x16SeekP

0x021Stat






0x17Setclock











0x18Getclock











0x19Test(0x20)











0x1AId1965810x0280x020x000x200x00'S''C''E''I'
0x1BReadS

0x011Stat






0x1CInit











0x1D












0x1EReadTOC31330364140x021Stat






0x1F













表. コマンドの補足事項
コマンド名補足事項
PlaySetmodeで0x04を立てると、AFrac(BCD)の下位4ビットが0の時にレポートを返す。上位が奇数の時は位置情報がトラック相対になるが、その際フラグとしてSec(BCD)の0x80が立つ。
Reset位置もホームポジションに戻る。
ReadT引数は0x01以外上手くいっていない。
GetTD引数を0x00にすると全トラックの合計になる。
Init承認後も暫く時間をあける必要がある。

通信デバイス(Comms Device or COMA)

準備中

独自解析プログラム群

準備中

エミゅってしまうま

実践結果としてのエミュレータ「エミゅってしまうま(略称XEBRA/ARBEX)」を公開する。

No comments:

Post a Comment