PC-8x01 シリーズの音源についての覚え書き


img
PC80/88 は拡張音源が多数ありますが、その数の多さに反してコントロールする情報が
いまひとつ少ないように思えたので、分かる範囲で情報を書き記しておきたいと思います。

音の場合は正誤の確認が容易なのと、実物が手元にあって途方に暮れるよりは
少しでも手掛かりがあった方が良いだろうと思いこのページを作成することにしました。

マイナー音源に対応したソフトの解析の参考にもなるかもしれません。



■ 注 意

エミュレータ上の実装を元に書いている情報があります。実際と違う場合があることに注意が必要です。
クロックは厳密なチップへの供給クロックという意味ではなく、音程周波数を得るための基準程度の意味です。

紙幅の都合で、掲載したテストプログラムは簡易的・部分的なものです。
4MHz 前提であること、DMA やウェイトを無視した状態であることなど注意が必要です。

掲載したテストプログラムと併せて、音源ユニットの動作確認用にテストプログラムを入れたディスクイメージを置いておきます。
ほとんど V2-4MHz 用ですが、一部 PC-8001 専用音源のプログラム(エミュレータ上での動作確認用)も入っています。
これらは実機ではそのままでは動きません。

・テスト/デモプログラム (soundinfo.zip)


■ BEEP

I/O ポート $40 【入力】
bit7未使用
bit6未使用
bit5CRTCからの垂直帰線信号
0=ディスプレイサイクル
1=垂直帰線サイクル
bit4カレンダクロックからのデータ
bit3DISKブートモード
0=DISKから起動した
1=DISKからの起動ではない
bit2CMTデータキャリアディテクト信号
0=キャリアオフ
1=キャリアオン
bit1高解像度CRTモード信号
0=高解像度CRTモード
1=標準CRTモード
bit0プリンタからのBUSY信号
0=プリンタREADY
1=プリンタBUSY
I/O ポート $40 【出力】
bit7ポート波形を出力(88mk2以降)
bit6汎用出力ポート
bit5BEEP(2400Hz)コントロール
0=BEEP Off
1=BEEP On
bit4グラフィックハイスピードモード(V1Sのみ有効)
0=スタンダード
1=ハイスピード
bit3CRT I/F 同期コントロール
0=同期ビットオフ
1=同期ビットオン
bit2カレンダクロック用シフトクロック
0=クロックオフ
1=クロックオン
bit1カレンダクロックへのストローブ信号
0=ストローブオフ
1=ストローブオン
bit0プリンタへのストローブ信号
0=ストローブオン
1=ストローブオフ
関係ない所も一応載せています。

BEEP に関連するところは出力の bit7 と bit5 です。
bit5 は PC-8001 から使える伝統的な BEEP 音で、BASIC のエラーでなじみ深い音です。
単純に bit5=1 で鳴り、bit5=0 で止まります。一定時間で勝手に鳴り止んだりはしません。

音程、音量が固定(音色も)ですが、ソフト的に工夫することで音程っぽいものは出せます。

10 OUT &H40,&H20:::::::OUT &H40,0 
20 GOTO 10
	

これだけでも、通常の BEEP 音と違う音程になります。間のコロン(:)の数を増減させると音程が上下。

より正確に音程を実現しようとすると機械語での制御が必要になります。
公称は 2400Hz の矩形波(?)ですが、これをざっくり、鳴っている間を 1、鳴っていない間を 0 とみなして音階を作ってみます。
CPU が邪魔をされないために割り込み・CRTC なども切って 4MHz 全力でつかえるものとします。

Main:
    di
    ld          e,7
    ld          ix,NoteTable
.loop:
    call        Play
    inc         ix
    inc         ix
    dec         e
    jr          nz,.loop
    jr          Main

Play:
    ld          b,100                   ;おとのながさ(てきとう)
.loop:
    ld          l,(ix+0)                ;19
    ld          h,(ix+1)                ;19

    ld          a,$20                   ;7   $80=88mk2BEEP
    out         ($40),a                 ;11
.wait1:
    dec         hl                      ;6 -+
    ld          a,l                     ;4  |
    or          h                       ;4  |
    jp          nz,.wait1               ;10-+ * n

    inc         hl                      ;6
    ld          a,0                     ;7
    ld          l,(ix+0)                ;19
    ld          h,(ix+1)                ;19

    ld          a,0                     ;7
    out         ($40),a                 ;11
.wait2:
    dec         hl                      ;6 -+
    ld          a,l                     ;4  |
    or          h                       ;4  |
    jp          nz,.wait2               ;10-+ * n
    djnz        .loop                   ;13
    ret                                 ;=138+(24*n*2)

NoteTable:
    dw      ((3993600 / 261) - 138) / 48        ;o4c
    dw      ((3993600 / 293) - 138) / 48        ;o4d
    dw      ((3993600 / 329) - 138) / 48        ;o4e
    dw      ((3993600 / 349) - 138) / 48        ;o4f
    dw      ((3993600 / 391) - 138) / 48        ;o4g
    dw      ((3993600 / 440) - 138) / 48        ;o4a
    dw      ((3993600 / 493) - 138) / 48        ;o4b
	

手抜きなのでだんだん音の長さが短くなります(soundinfo -> beep)。

bit5 の BEEP は PC-8001 時代から存在するので、和音を出せるものや音声を再生するものまで、
上のソースコードなどより断然高いレベルで散々弄り尽くされてきました。

初代 PC-8001 は本体のみでは 1/600 秒タイマ割り込みは使えません。PC-8011/12/13 などの拡張ユニットが必要。
ただし mk2/88 などの N モードでは、上位互換的にタイマ割り込みが使えるようになっています。

拡張ユニットに挿すタイプの音源ボードであれば、前提としてタイマ割り込みが使えるのでいいのですが、
本体の拡張バスに直結するものや、本体のみでの BEEP では音楽演奏はかなり苦しいです。

こちらに MML ベースの BEEP 多重演奏プログラムがあります。過去記事に詳しめの解説も。
http://wwwb.pikara.ne.jp/minosoft/pc-8001/index.htm


bit7 は 88mk2 で増設された、新しい beep ですが、こちらも ON と OFF のみ。
何が違うかというと、出力が完全なソフトまかせで 2400Hz ですらないということです。
発振回路などがなく単にスピーカーに直結されているだけ。

Techknow8800 に初代 8801 の CMT 端子に出ている bit6 にスピーカーを直結して音階を鳴らすプログラムが載っていましたが、
空いていた bit7 を使って公式でそれをやってしまったのが CMD SING という形です。
画面を消して、音の出力に全力を出せるようにするコマンドもありました。過渡期ならではの窮余の策ですね。

bit5 の beep では 2400Hz の矩形波を 1 と見なしていましたが、bit7 の方は正真正銘の 1 なので、
上のソースコードの流用(ビット位置を変えるだけ)で問題なく動きます。
矩形波のデューティ比も自分で決めないといけないので、50%:50% 以外の波形を作り出すことも可能です。

CMD SING は 8MHz 機種が出るまでは残っていたのですが、4/8MHz で音程の互換性を保つのが大変だったのか、無くなってしまいました。
その頃になると BEEP で音楽をやる機会も無くなったという事情もあるのかもしれません。
8MHz 機種でもハードウェアは変わらず載っているので、4MHz 機種のシステムディスクを持ってくれば使えるはずです。
タートルグラフィックと一緒に CMD 拡張命令に入っていると思います。

新旧ふたつの BEEP は同時に使用できます。
ただし、I/O ポートが同じでかつ読み出し不可なので、どこかに書き込んだ値を保持しておく必要があります。


サンプルとして bit7 を使って二重和音で曲を演奏するプログラムをおいておきます(soundinfo -> beepmus)。
デューティ比可変はちょっと大変なので 50% 矩形波で、音符・音長と最初に戻るループ程度の制御構造しかありません。

bit7 は単音ならそこそこ聴ける音にはなります。和音再生するよりは時分割方式のほうがいいかもしれません。


■ FM 音源と割り込みについて

割り込みについて簡単に書いておきます。
音楽演奏には次の 3 つの割り込みがよく使われます。
  1. VRTC 割り込み
  2. CLOCK 割り込み
  3. FM 音源割り込み
割り込みは初代 PC-8001 とそれ以降とで大きく違うのですが、音源を扱う環境では割り込みは主に INT3 か INT4 で受け付けることになっているようです。
80mk2 と 88 両対応の拡張音源ボードにはディップスイッチやジャンパスイッチがあったりしますが、拡張スロット端子の INT 周りの端子の位置が違うことによるものです。

N88-BASIC での割り込みテーブル
Lv.0$F300-$F301RXRDYRS-232C 受信割り込み




Lv.1$F302-$F303VRTC画面終了割り込み 1/60 秒
Lv.2$F304-$F305CLOCKリアルタイムクロック 1/600 秒
Lv.3$F306-$F307INT4ユーザー割り込み (音源)
Lv.4$F308-$F309INT3ユーザー割り込み (音源)
Lv.5$F30A-$F30BINT2ユーザー割り込み
Lv.6$F30C-$F30DFDINT1FDD 用リザーブ
Lv.7$F30E-$F30FFDINT0FDD 用リザーブ

I/O ポート $E4 【入力・出力】
bit30=割り込みレベルを参照して割り込みを発生させる
1=優先順位による割り込み
bit2-0割り込みレベル 0-7

割り込み処理の最中に別の割り込みが入ることがあるので、優先順位のやりかたを決めておけます。uPB8214?
bit3=0 のときは、bit2-0 に書き込んだ割り込みレベルより優先度が高いものしか受け付けなくなります。0 を書き込むと全て不許可。
bit3=1 のときは、bit2-0 に関わらず、優先度が高いものの割り込みを発生させます。

割り込みの中で割り込みを受け付けたいような場合に、ここを設定します。
音源ドライバは複雑さに比例して処理時間が結構長くなるので、処理の始まりから終わりまでを割り込み禁止にしておくと、
VRTC 割り込みでキー入力を拾う処理などがスキップされる可能性があります。
なので、音源処理の冒頭ですぐに割り込み許可をして、他の割り込みが来ても大丈夫なようにしておくのが得策です。

注意点として、INT3 で呼び出された音源ドライバが、その処理の最中に VRTC 割り込みを受け付けるつもりで EI してしまうと、
ヘタをすると自分自身を再度呼び出してしまうことになりかねません。
そのため一旦レベルに 2 を書き込んで INT3 を禁止したあと EI をすればレベル 1 の VRTC 割り込みが安全に呼び出せるという仕組みです。
もちろん音源ドライバ処理が終わった後は、レベルを 5 以上に元に戻さなければなりません。

I/O ポート $E6 【出力】
bit2USART 割り込みマスク 0=禁止 1=許可
bit1VRTC 割り込みマスク 0=禁止 1=許可
bit0リアルタイムクロック割り込みマスク 0=禁止 1=許可

上位 3 つの割り込みに関しては、個別に禁止・許可が設定できます。

I/O ポート $32 【入力・出力】
bit7音源割り込みマスク
0=割り込み許可
1=割り込み不可
bit6グラフィックVRAMアクセスモード
0=独立アクセスモード
1=ALUアクセスモード
bit5カラーモード
0=デジタルモード
1=アナログモード
bit4高速RAM/メインRAMバンク選択
0=高速テキストRAM選択
1=メインRAM選択
bit3画面出力モード
00=テレビ/ビデオモード
01=なし
10=アナログRGBモード
11=オプションモード
bit2
bit1内部EROM選択
00=内部EROMバンク0選択
01=内部EROMバンク1選択
02=内部EROMバンク2選択
03=内部EROMバンク3選択
bit0

割り込みに関係するのは bit 7 です。bit7=1 になっていると FM 音源割り込みは発生しません。
起動直後は 1 になっているので、FM 音源割り込みを使う際は bit7=0 にしなければなりません。

I/O ポート $AA 【出力】
bit7音源割り込みマスク
0=割り込み許可
1=割り込み不可
bit6-0

拡張音源ボードを挿すと $AA に音源割り込みマスクが現れることがあります(別項拡張一覧参照)。
ポート $32 の bit7 と役割は同じですが、出力のみで入力不可な模様。単純なプログラムの書き換えはできないということになります。
また、8001mk2SR の場合は、$33 の bit1 に同機能があるそうです。


実際に割り込みが発生するまでに様々なフラグや条件があるので、うまくいかない場合はどこかが詰まっていることが多いです。
OPNA の場合、とりあえず大ざっぱに以下のような感じです。順序は適当ですが。


・FM 音源タイマー B が満期
->FM 音源レジスタ $27 のタイマー B オーバーフロー受け付けフラグが ON 
->FM 音源 OPNA レジスタ $29 のタイマー B 使用可能フラグが有効
->FM 音源 OPNA レジスタ $10 のタイマー B マスクフラグが降りている
-> I/O ポート $32 の音源割り込みマスク「許可」
-> I/O ポート $E4 の割り込みレベルの条件を満たしている
-> Z80 の状態が割り込み許可 (EI)
-> 割り込み発生!

・1/600秒カウンタ満期
-> I/O ポート$E6 の割り込みマスク「許可」
-> I/O ポート $E4 の割り込みレベルの条件を満たしている
-> Z80 の状態が割り込み許可 (EI)
-> 割り込み発生!


割り込みは一旦オーバーフローすると、次の割り込みに備えて再セットが必要になります。
一例を示しておきます。割り込みの中で別の割り込みを受け付けたい場合は、割り込みレベルの適切な設定やフラグの管理が重要です。

Sount_Interrupt:
    di
    push    af              ;FM 音源割り込みでここに来たので FM 音源ドライバの処理を行う
    push    bc
    push    de
    push    hl
    push    ix

    ld      d,$27           ;FM 音源コントロールポート
    ld      e,%00101010     ;FM 音源タイマー B を再セット
    call    WriteOPN

    ld      a,7             ;割り込みレベル再設定
    out     ($E4),a

    call    Sound_Driver    ;音源ドライバ処理

    pop     ix
    pop     hl
    pop     de
    pop     bc
    pop     af
    ei
    ret
	

Timer-A/B を同時に使用する場合は以下のようにします。
CPU に入る割り込みエントリは同じなので、割り込み処理の中で Timer-A,B どちらからの割り込みなのかを調べます。

IntSound:
    in          a,($44)
    rrca
    jr          c,.timera
    rrca
    jr          nc,.exit
.timerb:
    ld          d,$27            ;mode. timer-ctrl
    ld          e,%00101111      ;[ResetB]
    call        WriteOPN

    ここに Timer-B の処理を書く
    jr          IntSound         ;必ず両方のフラグの処理を消化する

.timera:
    ld          d,$27            ;mode. timer-ctrl
    ld          e,%00011111      ;[ResetA]
    call        WriteOPN

    ここに Timer-A の処理を書く
    jr          IntSound         ;必ず両方のフラグの処理を消化する 

.exit:
    ld          a,5
    out         ($E4),a
    ei
    ret
	

Timer-A/B を同時に使う場合、どちらかのオーバーフローフラグが立った状態では次の割り込みが発生しない点には注意が必要です。
両方のフラグが立った状態で割り込みに入ったときは、一度の割り込み処理の中で必ず両方の処理を消化しフラグを両方とも下ろす必要があります。
フラグは一度の書き込みで同時にリセットすることができます。

必ずしも使いたい音源が、便利なタイマ割り込みを持っているわけではありません。
そのため、テンポを取るためだけに本体標準装備の 1/600 秒割り込みや OPN 音源の助けを借りたりするケースも生じます。

後に OPNA がサウンドボードII に載るようになると、音源からの割り込みの種類も増え、ADPCM 機能絡みの割り込みが増設されました。
これについては OPNA の項目で触れます。

Timer-A ($24,$25)、TImer-B($26) に書き込んだ値は現行カウント中は即時反映はされず、
オーバーフローフラグが一旦立ってからコントロールレジスタでリセットをしたタイミングで反映されます。

IO ポート $32 のサウンド割り込みマスクやポート $E4 の割り込みレベル(再)設定および DI-EI は単に割り込みをマスクするだけで、
FM 音源タイマーのカウント動作とは直接関係はありません。
これらの ON/OFF やレベルの操作にかかわらず、FM 音源タイマーは勝手に回っています。

DI-EI と $E4 の場合は割り込みが保留されており、EI や OUT ($E4),5 を実行した瞬間に(可能であれば)保留されていた割り込みがかかります。
一方 $32 の方は割り込みの発生そのものがキャンセルされています。

Timer-A/B の Enable ビットはオーバーフロー時にフラグを 1 にするかどうかのスイッチなので、これも FM 音源タイマーのカウント動作とは関係ありません。
カウント開始時に Enable=0 にしていても、オーバーフロー直前に Enable=1 にすれば、すぐに割り込みが発生します。

Timer-A/B の Reset ビットはオーバーフローフラグが立っていたら下ろすだけの動作で、やはりカウント動作とは関係ありません。

Timer-A/B の Load ビットを 1<->0 とすると FM 音源タイマーカウントはリセットされ新しいカウント値が反映されます。


■ 機種判別

PC-8x01 は機種が多いこともあり、ソフト側から見てどんな機能が使えるか知ることは重要です。
ソフト側から変更できる設定を除いて、おおよそ知っておきたい機種情報は以下あたりでしょうか。
全項目を全機種について調べるのは難しいので、主要なところを掲載しておきます。ただし動作未確認。
まずは機種チェックから。

Check_N88ROM:
    ld          a,%00111001                 ;N88-BASIC, ROM&RAM
    out         ($31),a

    ld          a,($79D7)
    cp          $32
    jr          c,.PC8801                   ;1.0-1.1 初代
    jr          z,.PC8801A                  ;1.2 A
    cp          $33
    jr          z,.PC8801mk2                ;1.3 mk2
    cp          $34
    jr          z,.PC8801mk2SRTR            ;1.4 SR/TR
    cp          $38
    jr          c,.PC8801FRMR               ;1.5-1.7 FR/MR
    jr          z,.PC8801FHMH               ;1.8 FH/MH

    ld          a,($79D8)
    cp          $0D
    jr          z,.PC8801FAMAVAx            ;1.9 VA/FA/MA/VA2/VA3
    cp          $31
    jr          z,.PC8801FEMA2              ;1.91 FE/MA2
    cp          $32
    jr          z,.PC98DO                   ;1.92 DO
    cp          $33
    jr          z,.PC8801FE2MC              ;1.93 FE2/MC
    cp          $34
    jr          z,.PC98DOP                  ;1.94 DO+

    jr          .PC8801unknown              ;不明
以下個別の処理(略)
	

BASIC ROM の文字列「NEC N-88 BASIC Version 1.91」の「91」の部分を見ているだけなのですが、広く使われている方法のようです。
参考: https://retrocomputerpeople.web.fc2.com/machines/nec/cmn_vers.html
AR は分かりません。
VA 系は V2モードが 2.4 で FA/MA は 2.3 なので、そこでさらに区別してもいいと思います。

つぎに V1,V2,4MHz,8MHz を判別します。
これは I/O ポートにそのまま情報が出るのでわかりやすいです。
何度も参照するようなことはありませんが、定型で用意しておくと使い回しが出来てラクです。

Check_Mode:
    in          a,($31)                 ;bit76:10=V1S 11=V1H 01=V2
    rlca
    ld          hl," 2"
    jr          nc,.mode
    rlca
    ld          hl,"H1"
    jr          c,.mode
    ld          hl,"S1"
.mode:
    in          a,($6E)                 ;bit7:0=8MHz 1=4MHz (FH以降)
    rlca
    ld          a,"4"
    jr          c,.spd
    ld          a,"8"
.spd:
    ld          (SPEED),a
    ld          (MODE+1),hl
    ret
SPEED:
    db          "4MHz",0
MODE:
    db          "V2S Mode",0

	

次に 8MHz-S と 8MHz-H の区別を掲載しておきます。
上で 8MHz であることが確定してから呼び出します。

Check_MC:
    in          a,($40)
    and         %00000010
    ld          hl,$01C0                ;15KHz
    jr          nz,.skip1
    ld          hl,$0100                ;24KHz
.skip1:
    ld          bc,0
    call        WaitVblank
.loop:
    ld          ix,($0000)
    inc         bc
    in          a,($40)
    and         $20
    jr          nz,.loop                ;vblank の間ループを続ける

    sbc         hl,bc                   ; bc>hl なら 8MHz-H モードと見なす
    ld          hl,"H-"
    jr          c,.skip2
    ld          hl,"S-"
.skip2:
    ret

WaitVblank:
    in          a,($40)
    and         $20
    jr          nz,WaitVblank           ;bit5 一旦ディスプレイサイクルになるまで待つ
.wait:
    in          a,($40)
    and         $20                     ;bit5 ディスプレイサイクル → Vblank になったら ret
    jr          z,.wait
    ret
	

ついでに Mx 系機種で標準装備され、それ以外の機種でも拡張ボードによって増設できる拡張 RAM の利用可能バンク数を取得する
処理を掲載しておきます。

Check_ExtRAM:
    ld          a,%00111011
    out         ($31),a                 ;64KRAM 選択
    ld          a,%00010001
    out         ($E2),a

    ld          b,0
    ld          hl,$AA00
.loop1:
    ld          a,b
    out         ($E3),a
    ld          ($0000),hl              ;適当に埋める
    dec         l
    djnz        .loop1                  ;降順

    xor         a
    out         ($E2),a
    ld          hl,$8801
    ld          ($0000),hl              ;メインRAMに書き込み

    ld          a,%00010001
    out         ($E2),a
    ld          bc,0
.loop2:
    ld          a,b
    out         ($E3),a
    ld          hl,($0000)
    ld          a,h
    cp          $88
    jr          z,.skip
    cp          $AA
    jr          nz,.skip
    ld          a,l
    cp          b
    jr          nz,.skip
    inc         c
.skip:
    inc         b
    jp          nz,.loop2               ;昇順

    xor         a
    out         ($E2),a
    out         ($E3),a
    ld          a,c                     ;バンク数で返る。32K*バンク数byteのメモリがあるはず。
    ld          (Machine_ExtRAM),a
    ret
	

一応実績はありますが、動作保証はしません。
ほかに、88 系と 80 系で共用する場合なども機種判別はあったほうがいいと思います。


■ SR 以降内蔵 / PC-8801-11 サウンドボード (YM-2203)

上位の OPNA がほぼ互換なので、これでしか出来ないということは無いと思います。

OPNA も含めて I/O ポートと割り込みの整理をします。

内蔵拡張INT3INT4割込制御FM1 FM2 FM3 FM4 JOYSTICK
PC-8001mk2
+PC-8801-11
なしOPN 80側88側$AA $A8,$A9 ?
PC-8001mk2
+PC-8801-23
なしOPNA 拡張$AA $A8,$A9$AC,$AD ?
PC-8001mk2SR OPN 内蔵 $33.bit1 $44,$45 ?
PC-8001mk2SR
+PC-8801-11
OPN OPN 内蔵88側$33,$AA $44,$45$A8,$A9 ?
PC-8001mk2SR
+PC-8801-23
OPN OPNA内蔵拡張$33,$AA $44,$45$A8,$A9$AC,$AD ?
PC-8801
+PC-8801-11
なしOPN 88側80側$AA $A8,$A9 ?
PC-8801
+PC-8801-23
なしOPNA拡張 $AA $A8,$A9$AC,$AD ?
PC-8801mk2SR OPN 内蔵 $32 $44,$45 内蔵
PC-8801mk2SR
+PC-8801-11
OPN OPN 内蔵80側$32,$AA $44,$45$A8,$A9 ?
PC-8801mk2SR
+PC-8801-23
OPN OPNA両方 $32,$AA $44,$45$A8,$A9$AC,$AD ?
PC-8801FA OPNA 内蔵 $32 $44,$45$46,$47 内蔵
PC-8801FA
+PC-8801-11
OPNAOPN 内蔵80側$32,$AA $44,$45$46,$47$A8, $A9 ?
PC-8801FA
+PC-8801-23
OPNAOPNA両方 $32,$AA $44,$45$46,$47$A8,$A9$AC,$AD?
PC-8801xH
+PC-8801-24
OPN OPNA拡張 $32 × $44,$45$46,$47 ?
PC-8801FE
+PC-8801-25
OPN OPNA拡張 $32 × $44,$45$46,$47 内蔵
PC-8801xH
+Re:BitrhFM
OPN OPNA拡張 $32 × $44,$45$46,$47 ?

※ この表は憶測含みでかなりいいかげんです。

INT3 = N88BASIC の場合 $FA08,$FA09
INT4 = N88BASIC の場合 $FA06,$FA07
80側 /88 側は拡張ボードのジャンパピン設定を 80/88 どちら側にするかという意味です。

「×」は、拡張ボードにより本体内蔵機能が切り離されることを意味します。
80mk2 系と 88 系は拡張スロットの INT3<->INT4 が入れ替わっている関係で 80mk2 系に 88 系の拡張ボードを挿すと INT4 に接続されます。

内蔵音源が無い機種にジャンパスイッチで割り込みを切り替え可能な音源ボードを挿す場合は、80mk2 側でも 88 側でもかまいません。
2 枚刺し以上の場合は制御用 I/O ポートが被るので難しいです。

JOYSTICK は、複数音源のときにどちらのポートにジョイスティック/マウス入力が見えるかです。
FE/FE2 + PC-8801-25 では OPNA を拡張してもジョイスティックは内蔵 OPN 側に見えるそうです(つまり完全には切り離されない)。

複数の音源を判別するためにはポートをスキャンする必要があります。
適当に PSG のレジスタに書き込んでみて、正常に読み出せるようなら存在すると判断するのがいいのではないかと思います。
また、FM 音源レジスタに $FF を指定して読み出すと、サウンドボードII の場合は 1 が返ってきます。併せてチェックするとよいでしょう。

OPM の載った響は I/O ポートが $88-$89 なので、全く被らずに増設できます。ただし OPM タイマーによる割り込みはありません。

PC-8801-10 はミュージックインターフェースボードで、ボード上に PSG * 2 が載っています(別掲)。
このボードは I/O ポートが被らないので上記のものと同時使用できそうです。

PC-8801-24 は FH/MH 専用のサウンドボードII で、専用のスロットに挿します。小型のカートリッジのようなタイプ。
PC-8801-25 は FE/FE2 専用の同上。専用 SB2 を挿す機種に、8801-23 を挿すとどうなるのかは分かりません。

他に書くべきところが無いのでここに書いておきますが、88SR 以降の N-BASIC モードからでも標準搭載の FM 音源 I/O ポートはアクセスできます。
おそらく他の拡張音源 I/O もアクセス可能と思われます。

BASIC では、SR 以降の本体 OPN + PC-8801-11 での FM6 重和音を公式にサポートしています。

10 new cmd
20 cmd play"@13v15c"
	

BASIC から cmd play コマンドで OPN を扱うには new cmd で拡張コマンドを有効にします。
PC-8801-11 や、PC-8801-23 など音源ボードを扱う場合は、別途テープやディスクから拡張コマンドのプログラムをロードする必要があります。

直接 I/O を叩くなら new cmd も別途のプログラムロードも必要ありません。
とりあえず以下のような感じで鳴ります(soundinfo -> opntest)。音源を標準搭載している機種は V1 モードであっても $44,$45 の方で扱えるようです。
PC-8801-11 など、$A8,$A9 側に OPN がある場合は音源レジスタに書き込んでいる I/O ポート番号を書き換える必要があります。

10 'fm(opn) sound test
20 DEFINT A-Z:CH=0:OCT=3:FRQ=&H26A 'o4c
30 READ AL,FB
40 REG=&HB0+CH:DAT=FB*8+AL:GOSUB 460
50 OP=0:GOSUB 360:OP=2:GOSUB 360:OP=1:GOSUB 360:OP=3:GOSUB 360
60 '
70 REG=&HA4+CH:DAT=((OCT*2048+FRQ) AND &HFF00)/256:GOSUB 460
80 REG=&HA0+CH:DAT=((OCT*2048+FRQ) AND &HFF):GOSUB 460
90 REG=&H28:DAT=&HF0+CH:GOSUB 460
100 PRINT"PUSH SPACE TO KEYOFF"
110 IF INP(9)<>191 THEN 110
120 REG=&H28:DAT=CH:GOSUB 460
130 END
360 'write fm parameter to (op)
370 READ AR,DR,SR,RR,SL,TL,KS,ML,DT,SSG
380 REG=&H30+CH+OP*4:DAT=DT*16+ML:GOSUB 460
390 REG=&H40+CH+OP*4:DAT=TL:GOSUB 460
400 REG=&H50+CH+OP*4:DAT=KS*64+AR:GOSUB 460
410 REG=&H60+CH+OP*4:DAT=DR:GOSUB 460
420 REG=&H70+CH+OP*4:DAT=SR:GOSUB 460
430 REG=&H80+CH+OP*4:DAT=SL*16+RR:GOSUB 460
440 REG=&H90+CH+OP*4:DAT=SSG:GOSUB 460
450 RETURN
460 'write fm register
470 OUT &H44,REG
480 OUT &H45,DAT
490 RETURN
500 '    AL FB
510 DATA 2, 7
520 '    AR DR SR RR SL TL KS ML DT SSG-EG
530 DATA 31,12, 4,10, 1,32, 0,12, 0, 0
540 DATA 31, 2, 4, 6,15,57, 3,15, 1, 0
550 DATA 31,12, 4, 6, 0,30, 0, 1, 0, 0
560 DATA 31, 5, 7, 7, 2, 0, 2, 3, 5, 0
	

以下、($32,$44-$47)系を基準に書きますが($AA,$A8-$AD)系も同じなので適宜読み替えてください。
I/O ポート $32 は音源割り込みの禁止、許可なので上の項目を参照(PC-8001mk2SR は $33.bit1)。
$AA は出力のみで入力不可なことにも注意。

I/O ポート $44 【入力】
bit71=データをレジスタにセット中
bit11=OPN タイマー B がオーバーフローした
bit01=OPN タイマー A がオーバーフローした

I/O ポート $44 【出力】
bit7-6OPN レジスタ指定

I/O ポート $45 【入出力】
bit7-6OPN データ入出力

$44 で音源のレジスタを指定し、$45 にデータの書き込みあるいは読み込みをします。
読み込めるのは PSG からのデータのみで FM 音源部は書き込みのみです。

Block/F-Number の書き込み $A0-$A6 は、上位つまり Block を含んだ方を先に書き込まなければなりません。
PSG は上位下位どちらから書き込んでも Ok.

WriteOPN:
    in      a,($44)
    rlca
    jr      c,WriteOPN
    ld      a,d
    out     ($44),a
    ld      a,(ix+0)        ;dummy
    ld      a,e
    out     ($45),a
    ret
	

レジスタ指定の後、データが書き込めるようになるまでの時間は CPU が 4MHz なら 17 クロック分、8MHz なら 34 クロック分です。
大事を取って 34 待つことで 4MHz/8MHz 両モードで確実に書き込めるようにできます。
音色を書き込むだけで 28 パラメータ分これが回るので結構な時間がかかります。なるべく速く回したいところです。

SR 時代の 4MHz を前提に作られたゲームで、8MHz で動かすと音楽がおかしくなるものがあったようです。
クロックウェイトを決め打ちしているとそうなります。

データ書き込み後、再びレジスタ指定できるようになるまでの BUSY の期間は 83 クロック(8MHz時)らしいですが、
処理内容にバラつきがある場合が多いので素直に BUSY クリアを待った方がいいと思います。

BUSY を気にしなければならないのは、FM 部のみで PSG はノーウェイトで書き込めます。
BUSY になるのはデータを書き込んだ後のみで、レジスタ指定の後は BUSY にはなりません(BUSY が一瞬で解除されているだけかもしれませんが)。

タイマーフラグ A/B は OPN の Timer-A/B のそれぞれのオーバーフロー時に 1 になります。
オーバーフローありに設定されており、かつ音源割り込みが有効であれば本体 CPU に割り込みが発生します。
一旦オーバーフローしたら、リセットしないと次の割り込みが発生しません。

両方のタイマーを同時に使うことも出来ますが、「どちらのタイマーがオーバーフローしたか」を知るには前掲 I/O ポート $44 のフラグの状態を見ます。

OPN レジスタ $27 【出力】
bit7-600=通常モード
01=効果音モード
10=音声合成モード
bit51=フラグB リセット
bit41=フラグA リセット
bit31=タイマーB のオーバーフロー時にフラグB を 1 にする。0=オーバーフローを無視
bit21=タイマーA のオーバーフロー時にフラグA を 1 にする。0=オーバーフローを無視
bit11=タイマーB 始動 0=停止
bit01=タイマーA 始動 0=停止

OPN のプリスケーラ機能は、デフォルトでは FM=1/6, SSG=1/4 になっています。
以下はプリスケーラの設定変更です。これらのレジスタ群にはデータは指定せず、レジスタラッチだけで反映されます。FM1/3,PSG1/2 の設定は $2D,$2E へ連続アクセスします。
この設定はマスタークロックの係数なので、割り込み周期や音程周波数に影響があります。
例えばデフォルト(1/6,1/4)での F-number 計算式で /72 になっている部分を 12*(6) であると解釈すると良いと思います。

OPN レジスタ $2F 【出力】
bit7-0FM 音源の分周比 1/2 SSG 音源の分周比 1/1 OPN への最大入力クロック 1.4MHz

OPN レジスタ $2D 【出力】
bit7-0FM 音源の分周比 1/6 SSG 音源の分周比 1/4 OPN への最大入力クロック 4.2MHz

OPN レジスタ $2D,$2E 【出力】
bit7-0FM 音源の分周比 1/3 SSG 音源の分周比 1/2 OPN への最大入力クロック 2.1MHz

内蔵系 OPN(OPNA) の他の使い方として、ジョイスティックまたはマウスの入力を読むことができます。
いずれも PSG のレジスタ 14,15 のデータを読むことで入力を拾えます。

音楽演奏とジョイスティック入力を別々の割り込みで処理していると、一方でラッチした音源レジスタを他方で上書きしてしまう可能性があります。
音源操作中は割り込みを禁止するなど、双方が邪魔しないような対策が必要です。

PadInit:                                ;初回に一度呼ぶ
    ld          a,7
    out         ($44),a
    in          a,($45)
    and         %00111111               ;PSG I/O Port A/B 共に入力にする
    out         ($45),a
    ret

PadRead:
    ld          a,$0E
    out         ($44),a
    in          a,($45)
    cpl                                 ;....RLDU
    and         $0F                     ;押したところが 1 になる
    ld          (Pad),a

    ld          a,$0F
    out         ($44),a
    in          a,($45)
    cpl
    and         %00000011               ;ボタン0=bit0 ボタン1=bit1
    ld          (Button),a
    ret
	

本体前面or背面にある「マウス」や「汎用入出力インターフェース」にジョイスティック/マウスをつなぎます。
上の例はジョイスティック入力の取得で、マウスは少し異なります。脱線するので省略。

88 にはあまり関係ないですが、PSG の互換品の中には入出力ポートが省略されているものもあります。

mk2 世代と SR 世代は、形状は同じ D-SUB 9 ピンですが中身が全く別物です。誤って接続すると破損の恐れがあります。

80mk2/88mk2 の汎用 I/O ポート【入出力】88SR の汎用 I/O ポート【入出力】
pin1UIP2IN $30.bit7JIP1IN OPN($0E).bit0
pin2UIP1IN $30.bit6JIP2IN OPN($0E).bit1
pin3UOP1OUT $30.bit6JIP3IN OPN($0E).bit2
pin4UOP2OUT $30.bit7JIP4IN OPN($0E).bit3
pin5GND-+5V-
pin6UOP3OUT $10.bit7JIOP1IN/OUT OPN($0F).bit0
pin7GND-JIOP2IN/OUT OPN($0F).bit1
pin8UINT2負論理JOP1OUT $40.bit6
pin9+5V-GND-

音源そのものについては散々弄り尽くされていると思うので、改めて述べることはしません。
CSM/効果音モードについては、BASIC 付属のサンプルかゲームアーツ作品などを見るとよいでしょう。

OPN では、マスタークロックを 72 分周した値を PG やタイマーに用いているようです。
マスタークロック 4MHz のとき、およそ 55466Hz が OPN の出力単位であり、サンプリングレートもその整数倍に近い値にすると良いと思います。
OPNA ではマスタークロック 8MHz なので 144 分周。

音源ボードが標準装備か拡張スロット装備か、または複数の OPN/OPNA を搭載しているかをチェックする場合は、
I/O ポートをスキャンする必要があります。レジスタ $FF から 1 が返ってくれば OPNA が存在します。
存在しない場合は「不定」となっていますが、ほぼ $FF で問題ないと思います。

    ld      c,$44
    call    .sub
    ret     nc              ;c=$44 側に OPNA がある
    ld      c,$A8
    call    .sub
    ret     nc              ;c=$A8 側に OPNA がある
    jp      notexist        ;OPNA が存在しない
.sub:
    ld      a,$FF
    out     (c),a
    inc     c
    in      a,(c)
    dec     c
    cp      1
    ret     z               ;存在したら cf=0
    scf
    ret                     ;存在しなければ cf=1
	

OPN を判別する場合は PSG に書き込んだ値を読んでみるのが簡単だと思います。
何度か読み書きしてみて全て正常に読み取れれば存在と見なせます。


■ FA 以降内蔵 / PC-8801-23/24/25 サウンドボードII (YM-2608)

OPN の上位互換なので、共通部分(FM1,2,3 チャンネルと PSG)については前掲の OPN と同じです。
FM4,5,6 チャンネルとリズム音源、ADPCM 音源を使う場合に以下の I/O ポート $46-$47 を使います。$A8-$A9,$AA 側は適宜読み替えのこと。

I/O ポート $46 【入力】
bit7[BUSY]1=データをレジスタにセット中
bit6未使用
bit5[PCMBSY] 1=ADPCM録音・再生中
bit4[ZERO] 1=ADPCM録音中に無音状態になった
bit3[BRDY] 1=ADPCM録音・再生またはメモリへの格納が 1byte 完了した
bit2[EOS] 1=ADPCM録音・再生が終端アドレスに達した、またはPCM再生周期になった
bit1[FLAGB]1=OPNA タイマー B がオーバーフローした
bit0[FLAGA]1=OPNA タイマー A がオーバーフローした

I/O ポート $46 【出力】
bit7-6OPNA レジスタ指定

I/O ポート $47 【入出力】
bit7-6OPNA データ入出力

$47 の入力で読めるのは ADPCM 部のデータだけです。録音やデータ読み出しなどで ADPCM レジスタ $8,$F からの入力が必要になります。

OPN の $44,$45 と役割はほぼ同じですが、ステータス部分に ADPCM 関係のものが増えています。
BUSY は $44 にもありますが、$46 は FM4,5,6 ・リズム音源用の BUSY です。
FM1,2,3 の BUSYは変わらず $44 側です。

FLAG A/B については $44 側にも同じものがあります。両方で共通の値を読むことが出来ます。

新設されたフラグも含めて、OPNA レジスタ $29 が割り込み関連のコントロールポートになっています。
タイマ A/B の設定は OPN の $27 にもあるので、FM4-6, ADPCM を有効にするついでに、こちらも有効にしておきましょう。

OPNA レジスタ $29 【出力】
bit71=FM4-6, ADPCM を使用可能にする
bit41=ADPCM録音中に無音状態になった時に[ZERO]フラグを立て割り込みをかける
bit31=ADPCM録音・再生またはメモリへの格納が 1byte 完了したときに[BRDY]フラグを立て割り込みをかける
bit21=ADPCM録音・再生が終端アドレスに達した、またはPCM再生周期になった時に[EOS]フラグを立て割り込みをかける
bit11=OPNA タイマー B がオーバーフローしたときに[FLAGB]を立て割り込みをかける
bit01=OPNA タイマー A がオーバーフローしたときに[FLAGA]を立て割り込みをかける

[ZERO][BRDY][EOS] については新しく割り込みトリガになりました。
後述する PCM 再生処理などに対して役に立つのですが、エミュレータでは省略されていることがあります。

OPN においてタイマ A/B がオーバーフローしたときに、これをクリアする必要がありましたが、OPNA における新設フラグ群も同様です。

OPNA レジスタ $10 【出力】
bit71=全フラグを 0 にする。bit4-0 の設定は無視
bit41=[ZERO]を常に 0 にマスクする
bit31=[BRDY]を常に 0 にマスクする
bit21=[EOS]を常に 0 にマスクする
bit11=[FLAGB]を常に 0 にマスクする
bit01=[FLAGA]を常に 0 にマスクする

ADPCM 関連の割り込みを使用した後は、レジスタ $10 に $80 を出力してフラグをクリアします。

レジスタ指定後にデータが書き込めるようになるまでのウェイトは OPN よりも減っており、
CPU が 4MHz なら 9 クロック、8MHz なら 17 クロックです。安全のため 8MHz-H に合わせておきましょう。
MA2 までなら実質 6MHz 程度なので割とルーズでも大丈夫なのですが。
このウェイトは FM 音源部とリズム音源部で必要です。PSG と ADPCM は必要無し。

WriteOPNA:
    in      a,($44)
    rlca
    jr      c,WriteOPNA
    ld      a,d
    out     ($44),a
    nop                     ;dummy
    ld      a,e
    out     ($45),a
    ret
	

FM 音源部のデータ書き込み後のウェイトはレジスタ $21-$9E(音色パラメータ)で 83 クロック、$A0-$B6(BLK/FNUM など)で 47 クロックとか。
ADPCM 部への書き込みは PSG と同様、データ書き込み後も含めノーウェイトです。
注意すべきはリズム音源で、$10 のキーオン・ダンプに 576 クロックのウェイト、それ以外に 83 クロックのウェイトが掛かります。
いずれにせよ、ここは busy フラグを見てウェイトを取るべきでしょう。

ウェイトを嫌ってか(あるいは貧弱なリズム音を嫌ってか)リズム音源を使わないメーカーもありました。


ADPCM 部についての豆知識。
というわけで、メモリの読み書きに関しては結構簡略化できます。
一例としてメモリから読み出しを掲載します。書き込みもほぼ同様です。
※ 以下、8MHz-S までは大丈夫ですが、8MHz-H だと化けるようなので、素直にウェイトを取った方がいいかも。

ADPCM_Init:                             ;初回に一度だけ呼び出せばよい
    ld          c,$46

    ld          de,$1000                ;フラグコントロール どのフラグもマスクしない
    call        WriteFM2
    ld          de,$1080                ;フラグコントロール 全てのステータスフラグを0に
    call        WriteFM2
    ld          de,$0102                ;x8 DRAM
    call        WriteFM2

    ld          de,$04FF                ;ストップアドレス下位 $04
    call        WriteFM2
    ld          de,$051F                ;ストップアドレス上位 $05
    call        WriteFM2

    ld          de,$0CFF                ;リミットアドレス下位 $FF
    call        WriteFM2
    ld          de,$0D1F                ;リミットアドレス上位 $1F
    jp          WriteFM2

ADPCM_READ2:
    ld          c,$46

    ld          de,$0020                ;$00 メモリアクセス許可
    call        WriteFM2

    ld          hl,(ADPCM_START)
    ld          d,$02
    ld          e,l                     ;$02 スタートアドレス下位
    call        WriteFM2
    ld          d,$03
    ld          e,h                     ;$03 スタートアドレス上位
    call        WriteFM2

    ex          de,hl
    ld          hl,(ADPCM_END)
    or          a
    sbc         hl,de                   ;Length=End-Start+1
    inc         l                       ;+1はdec hlと相殺
    inc         h
    ex          de,hl

    ld          a,$08
    out         (c),a
    inc         c
    in          a,(c)
    in          a,(c)                   ;2回分空読みする

    ld          hl,(ADPCM_ADR)          ;書き込み先
.loop:
    ld          b,32
    inir                                ;BRDY を見る必要なし. フラグのリセットも必要なし.
    dec         e
    jp          nz,.loop
    dec         d
    jp          nz,.loop
    dec         c

    ld          de,$0001                ;$00 コントロール1 リセット
    jp          WriteFM2
	

ADPCM の操作がノーウェイトといっても毎回手間をかけるのはやはり無駄なので、できるだけ速いほうが良いでしょう。


PC88 における YM-2608 の利用は ADPCM として使用されることがほとんどでしたが、PC98 における 86PCM と同様に PCM 録音・再生も可能です。
PCM を録音・再生する場合はボード上に載った 256KB メモリではなく本体メモリと Z80 CPU からの逐次書き込みが必要です。
ADPCM の場合と比べて、かなり CPU パワーとメモリを使うので、確かに PC88 向きではありません。

データは符号付き 8bit を ADPCM のレジスタ $0E に書き込む(録音は $0F から読み込む)だけです。音量は指定できません。
録音・再生周波数は EOS をポーリングするか、EOS を元に割り込みをかけるかの二通りあります。

EOS は ADPCM のプリスケール設定に周波数値を与えると、その周期でフラグが立ちます(あるいは割り込みがかかる)。

前掲の OPNA レジスタ $29 に割り込みソースとして EOS を選択できるようになっているのでこれを使うのがスマートだと思います。
もちろん、フラグを一切使わずに、CPU でタイミングをとるやり方でも録音・再生はできます。

EOS 割り込みは、8MHz ÷ 2 ÷ PreScale(11bit) という式なので、1950Hz 〜 4MHz の割り込みができます。
他のタイマーリソースと比べても、結構こまかく使えて便利だと思います。ただしエミュレータではサポートされていないことも。

Timer A/B も併用しているのであれば、I/O ポート $44/$46 を割り込み時に読んでみて、どの割り込みが発生したかの場合分けが必要です。
また、割り込みが発生する度にフラグクリアも必要ですが、ZERO,BRDY,EOS の 3 つは Timer-A/B と違い、
個別にリセットがかけられないので注意が必要です。逆にオールリセット(Reg.$10=$80)を使うと Timer-A/B もクリアされてしまいます。


こうしてみると、ADPCM と比べて PCM を使うメリットは皆無に思えてきますが、PCM でしか出来ないこともあります。
例えば PCM の多重再生や PCM のリアルタイム合成です。



PCM 再生テストを兼ねて、多重再生のデモを置いておきます(-> rhythm_box)。
EOS 割り込みが使えないので、エミュレータの方は Timer-A で代用してあります。
ただし PCM 機能そのものが省略されているエミュレータでは動きません。
8MHz(H) であっても音が時々飛びます。
音を 2 つに限定するなり音量調節を諦めるなりすれば、その分音質や CPU パワーが削減できます。
15600Hz で ADPCM 最高音質と同じになります。




PCM のリアルタイム合成のデモとして波形メモリ音源を PCM でつくってみました(-> wtsnd)。
8bit 32step の波形に音程と音量の変更可、チャンネル数は 2 で 10400Hz のサンプリングレートという仮想スペックです。
コンセプトの実証が主目的なので実装がいいかげんなのは大目に見てください。
このデモは CPU 8MHz モードでのみ動作します。



■ HMB-20 響 (YM2151)

HAL 研究所製、OPM の載った PC-8800 シリーズ用ボードです。対応ソフトは少ないと思います。
入出力は I/O ポートなので、OPN/OPNA と同じ感覚で使うことができます。
CPU 8MHz モードでも使用可能なはずですが、付属ソフトの「響子」は 8MHz では正常動作しない、との記述がマニュアルにあります。
M88 のモジュールや j80 や ePC-8801MA で実装されています。

音源ボード上に 4.000MHz のクロックが載っています。

I/O ポート $88 【出力】
bit7-6OPM レジスタ指定

I/O ポート $89 【出力】
bit7-6OPM データ出力

I/O ポート $89 【入力】
bit71=データをレジスタにセット中
bit11=OPM タイマー B がオーバーフローした
bit01=OPM タイマー A がオーバーフローした

BASIC から扱うのであれば、以下の通り(soundinfo -> opmtest)。

10 'fm(opm) sound test
20 DEFINT A-Z:CH=0:OCT=3:LR=3:KC=0:KF=0 'o4c
30 READ AL,FB,WF,SY,SPD,PMD,AMD,PMS,AMS
40 REG=&H18:DAT=SPD:GOSUB 460
50 REG=&H19:DAT=&H80+PMD:GOSUB 460
60 REG=&H19:DAT=AMD:GOSUB 460
70 REG=&H1B:DAT=WF:GOSUB 460
80 REG=&H20+CH:DAT=LR*64+FB*8+AL:GOSUB 460
90 REG=&H38+CH:DAT=PMS*16+AMS:GOSUB 460
100 OP=0:GOSUB 370:OP=2:GOSUB 370:OP=1:GOSUB 370:OP=3:GOSUB 370
110 '
120 REG=&H28+CH:DAT=OCT*16+KC:GOSUB 460
130 REG=&H30+CH:DAT=KF*4:GOSUB 460
140 REG=&H8:DAT=&H78+CH:GOSUB 460
150 PRINT"PUSH SPACE TO KEYOFF"
160 IF INP(9)<>191 THEN 160
170 REG=&H8:DAT=CH:GOSUB 460
180 END
370 'write fm parameter to (op)
380 READ AR,DR,SR,RR,SL,TL,KS,ML,DT1,DT2,AME
390 REG=&H40+CH+OP*8:DAT=DT1*16+ML:GOSUB 460
400 REG=&H60+CH+OP*8:DAT=TL:GOSUB 460
410 REG=&H80+CH+OP*8:DAT=KS*64+AR:GOSUB 460
420 REG=&HA0+CH+OP*8:DAT=AME*128+DR:GOSUB 460
430 REG=&HC0+CH+OP*8:DAT=DT2*64+SR:GOSUB 460
440 REG=&HE0+CH+OP*8:DAT=SL*16+RR:GOSUB 460
450 RETURN
460 'write fm register
470 OUT &H88,REG
480 OUT &H89,DAT
490 RETURN
500 '    AL FB WF SY SPD PMD AMD PMS AMS
510 DATA 2, 7,  2, 1,220,  0, 4, 1,  1
520 '    AR DR SR RR SL TL KS ML DT1 DT2 AME
530 DATA 31, 5, 7, 4, 9,37, 1, 1, 5, 0,  0
540 DATA 22, 0, 4, 5, 4,62, 1, 5, 2, 0,  0
550 DATA 29, 0, 4, 5, 4,77, 1, 1, 7, 0,  0
560 DATA 31, 7, 6, 5, 4, 0, 2, 1, 1, 0,  1
	

機械語での書き込みは以下。

WriteOPM:
    in      a,($89)
    rlca
    jr      c,WriteOPM
    ld      a,d
    out     ($88),a
    ld      a,(ix+0)        ;dummy
    ld      a,e
    out     ($89),a
    ret
	

OPN/OPNA と同様に、$88 で OPM レジスタアドレスを指定した後、$89 にデータを書き込みます。書き込んだデータの読み出しは不可。
書き込む際は busy フラグがクリアされていることを確認してから書き込みます。
busy フラグがクリアされるのはデータを書き込んでから 68 クロック経過後の模様。アドレス書き込み後は不明ですが 34 あれば大丈夫だと思います。

OPM と紐付けられた割り込みは無いので、SR 以降であれば本体内蔵の OPN/OPNA のタイマーを流用するか、別の割り込みソースを用意するのが良いでしょう。
OPM タイマー A/B をポーリングすることもできますが、あまり意味は無いと思います。

OPN/OPNA と同様に CSM モードもあります。Timer-A がオーバーフローすると全スロットが自動的に Key On されます。効果音モードは無し。

HMB-20 は MIDI 制御も可能です。YM3802 が載っている他、MIDI-IN/OUT 端子各 1 個と MUSIC KBD 端子がボードから出ています。
MUSIC KBD 端子は、ヤマハ製ミュージックキーボード YK-01,YK20 などに接続するものだそうです。
MIDI 制御のためのタイマー割り込みもあるようです。INT2(+A,+B)に入るとか。
ボード上のジャンパピンは割り込み信号を入れ替えるもののようです。

無改造での複数枚挿しは残念ながら無理そうです。
j80 では複数枚刺し($C8,$C9/$CA,$CB)と割込み(Lv4)がサポートされています。YM2149($CC,$CD)も。

ボード写真・マニュアル等の資料を UME-3 さんにいただきました。

(?)MIDI 機能詳細。
(?)ボード上のクロックと供給先。
(?)割り込み詳細。

■ PC-8801-10 / GSX-8800 (AY-3-8910 * 2)

PC-8801-10 はミュージックインターフェースボードで、MIDI が主の拡張ボードですが、PSG が 2 つ載っています。
HAL 研究所製の GSX-8800 とは、PSG 部が互換です。
GSX は 83.1 の発売で、8801-10 は 84.2 なので、実は公式が後追いした形になります。
CPU が 8MHz モードでも動作可能。
エミュレータは M88 の拡張モジュールや j80 などで実装されています。

基準クロックは 4MHz。

I/O ポート $A0 【出力】
bit7-6PSG1 レジスタ指定
I/O ポート $A1 【入出力】
bit7-6PSG1 データ入力・出力
I/O ポート $A2 【出力】
bit7-6PSG2 レジスタ指定
I/O ポート $A3 【入出力】
bit7-6PSG2 データ入力・出力
PSG1 は $A0 がレジスタ指定で $A1 でデータ読み込み・書き込み。
PSG2 は $A2 がレジスタ指定で $A3 でデータ読み込み・書き込み。

GSX-8800 では 2 枚刺しが可能で、ジャンパー設定で $A0-$A3 と $A4-$A7 に分けて合計 PSGx4 の 12 音構成が可能なようです。

I/O ポート $A4 【出力】
bit7-6PSG3 レジスタ指定
I/O ポート $A5 【入出力】
bit7-6PSG3 データ入力・出力
I/O ポート $A6 【出力】
bit7-6PSG4 レジスタ指定
I/O ポート $A7 【入出力】
bit7-6PSG4 データ入力・出力
PSG3 は $A4 がレジスタ指定で $A5 でデータ読み込み・書き込み。
PSG4 は $A6 がレジスタ指定で $A7 でデータ読み込み・書き込み。

I/O ポート $C2 【入出力】
bit7-6MIDI 通信用 8251 データ入出力

I/O ポート $C3 【入出力】
bit7-6MIDI コントロール用 8251 コマンド出力・ステータス入力

BASIC からは以下の通り(soundinfo -> gsx8800)。

10 'PC-8801-10 and GSX-8800 PSG Sound Test
20 CLK=3993600
30 CS=0:R=7:D=&H38:GOSUB 200:CS=1:R=7:D=&H38:GOSUB 200
40 CS=2:R=7:D=&H38:GOSUB 200:CS=3:R=7:D=&H38:GOSUB 200
50 CS=0:R=8:D=15:GOSUB 200:CS=1:R=8:D=15:GOSUB 200
60 CS=2:R=8:D=15:GOSUB 200:CS=3:R=8:D=15:GOSUB 200
100 CS=0:D=CLK/32/261:GOSUB 230 'o4c
110 CS=1:D=CLK/32/329:GOSUB 230 'o4e
120 CS=2:D=CLK/32/392:GOSUB 230 'o4g
130 CS=3:D=CLK/32/523:GOSUB 230 'o5c
140 PRINT"PUSH SPACE TO STOP"
150 IF INP(9)<>191 THEN 150
160 CS=0:R=8:D=0:GOSUB 200:CS=1:GOSUB 200
170 CS=2:GOSUB 200:CS=3:GOSUB 200
180 END
190 'write PSG
200 OUT &HA0+CS*2,R
210 OUT &HA1+CS*2,D
220 RETURN
230 'write frq
240 OUT &HA0+CS*2,0
250 OUT &HA1+CS*2,(D AND 255)
260 OUT &HA0+CS*2,1
270 OUT &HA1+CS*2,(D AND &HFF00) / 256
280 RETURN
	

INT3 にタイマー割り込み、INT4 に MIDI 用の割り込みが入るようです。
N88-BASIC であれば $F308,$F309(INT3) と $F306,$F307(INT4) が割り込みベクタとなります。
本体上にジャンパピンがあり、80mk2 にも対応しています。88 と 80mk2 ではピンが逆なので、INT3/4 を逆転させるためのものなのかも。

SR 以降では本体標準の FM 音源 が INT3 なので競合するような気がします。
uPD8253 と uPD8251 が載っているようなので、これで MIDI クロックと信号を作っているのかも。

ボード上には MIDI-IN/OUT 各 1 個と RHYTHM-BOX のコネクタがあります。
PSG 2 つはミックスされず、L,R それぞれに出力されるようになっています。

PSG はマスタークロック 32 分周が出力単位のようなので、3993600/32=124800Hz およそ 125KHz が目安のサンプリングレートになります。

(?)I/O $C2-$CF, $D0-D7 の詳細。
(?)タイマーおよび割り込みの詳細。
(?)MIDI 部の詳細。
(?)GSX-8800 は 2 枚刺しが可能。PC-8801-10 も?

■ 空き


■ CMU-800

工事中

■ OPNx4(YK-20)

工事中

■ OPL(トラ技:1986.8) OPL2(トラ技SP:18)

工事中

■ PCG-8100/8800/8200 (8253)

PC-8001 に PCG (Programable Character Generater)機能を追加する外部拡張ユニットですが、
その機能の一つとして、PIT(=Programable Interval Timer : intel(uPD)8253) で音が鳴らせます。
コンパックから PSA という互換基板も出ていたようです。他にもあるかも。

PCG-8100 初期バージョンでは 1 音だったものが、後期バージョンおよび 8200/8800 では 3 音鳴らせるようになりました。
8200 は PC-8001mk2、8800 は PC-8801 用です。

8253 の制御は MZ-700 関連の資料が充実していると思います。他に PC98 や TOWNS にも載っていました。
16bit のカウンタを 3 つ持っていて、そのカウントの仕方によってモードが分かれているのが特徴です。

エミュレータでは M88 の拡張モジュール、j80、QUASI88 などで実装されています。

I/O ポート $02 【PIT コントロール出力】
bit7チャンネル 2 出力 1=ON 0=OFF
bit6チャンネル 1 出力 1=ON 0=OFF
bit5PCG ROM コピー
bit4PCG RAM ライト
bit3チャンネル 0 出力 1=ON 0=OFF
bit2RAM A10
bit1RAM A9
bit0RAM A8

PCG 関連の他の I/O ポート情報は省略しています。全 3ch を有効にするには最初に $C8 を出力すると良いでしょう。

I/O ポート $0F 【PIT コントロール出力】
bit7-6カウンタチャンネル指定
00=チャンネル 0
01=チャンネル 1
10=チャンネル 2
bit5-4カウンタアクセスモード
11=16bit ロード(下位・上位の順)
10=上位 8bit のロード
01=下位 8bit のロード
00=カウント値ラッチ
bit3-1カウントモード
000=mode0:ターミナルカウント
001=mode1:プログラマブルワンショット
010=110=mode2:レートジェネレータ
011=111=mode3:方形波レートジェネレータ
100=mode4:ソフトウェアトリガストローブ
101=mode5:ハードウェアトリガストローブ
bit0PIT カウントモード 1=BCD 0=2進数

bit7-6 で操作するカウンタチャンネルを選択します。
bit5-4 で操作内容を指定します。下の BASIC リストでは 16bit ロードを使っています。カウンタ値ラッチは読み出しができます。
bit3-1 はモードを選択します。モードは全部で 6 つ。以下概略。

モード 0: カウンタセットで開始。L が初期状態で、カウント値が 0 になると H になる。以後、再設定まで変化なし。
モード 1: トリガー入力でカウント開始。H が初期状態で、カウント期間中だけ L になり、再び H に戻る。以後、再設定まで変化なし。
モード 2: カウンタセットかトリガ入力で開始。H が初期状態で、カウント値でが 0 になると 1 クロックだけ L になり H に戻る。以下カウントの繰り返し。
モード 3: カウンタセットかトリガ入力で開始。初期状態は H で、カウント値を H と L でカウント値の半分ずつカウントする動作を繰り返す。
モード 4: カウンタセットで開始。H が初期状態で、カウンタが 0 になると 1 クロックだけ L になって再び H に戻る。繰り返さない。
モード 5: トリガー入力で開始。動作はモード 4 と同じ。

モード別の動作は、mame のソース pit8253.cpp が図付きで一番わかりやすいかと。

bit0 は設定されたカウント値を BCD 数値として(0-9999)扱うか、そのまま 2進数として(0-65535)扱うかの設定。

I/O ポート $0C 【PIT チャンネル 0 出力】
bit7-0チャンネル 0 カウント値

I/O ポート $0D 【PIT チャンネル 1 出力】
bit7-0チャンネル 1 カウント値

I/O ポート $0E 【PIT チャンネル 2 出力】
bit7-0チャンネル 2 カウント値

音(矩形波)を出すのであれば、モード 3 を使います。
カウント値はカウントアクセスモードを 16bit ロードにして、下位上位の順にチャンネル 0 であれば I/O ポート $0C に書き込みます。
4MHz ÷ 音程周波数 がカウント値になります。

BASIC からは以下の通り(soundinfo -> pcg8800)。

10 'PCG-8800 sound output test
20 OUT &H2,&HC8 'pcg sound on
30 CH=0:F=15301:GOSUB 100  '3993600 / 261Hz o4c
40 CH=1:F=12138:GOSUB 100  '3993600 / 329Hz o4e
50 CH=2:F=10187:GOSUB 100  '3993600 / 392Hz o4g
60 PRINT "hit space to stop"
70 IF INP(9)<>191 THEN 70
80 OUT &H2,0 'pcg sound off
90 END
100 '
110 FL=F AND &HFF
120 FH=(F AND &HFF00) / 256
200 '
210 OUT &HF,&H36+CH*64
220 OUT &HC+CH,FL
230 OUT &HC+CH,FH
240 RETURN
	

8253 には音量制御がありません。なので、音を消すには可聴域外に飛ばすか、コントロール出力でカウンタ値を設定せずに止める必要があります。
または、I/O ポート $02 に 0 を出力しても消音できます。

モードを変えれば PCM 再生や PWM 再生も出来ます。
PCM は、モード 0 の初期状態が L で、それ以外のモードが H であることを利用して、モード設定だけで 0,1 を出力することが可能とのこと。
PWM で出力するのであれば、以下のようにします。

    ld          a,$C8
    out         ($02),a                 ;PCG sound ch.0-2 on

    ld          a,%00110000             ;ch.0/16bit load/mode0/binary
    out         ($0F),a

    ld          hl,PCMDATA              ;7800Hz 8bit PCM
    ld          de,PCMEND - PCMDATA
.loop:
    ld          a,(hl)                  ;7
    inc         hl                      ;6
    add         a,a                     ;4
    out         ($0C),a                 ;11
    ld          a,0                     ;7
    rla                                 ;4
    out         ($0C),a                 ;11

    ld          b,33                    ;7
    ld          b,33                    ;7
    djnz        $                       ;424 (13*32+8)

    dec         de                      ;6
    ld          a,d                     ;4
    or          e                       ;4
    jp          nz,.loop                ;10 =512clk
    xor         a
    out         ($02),a
	

で、うまく鳴るはずなのですが何だかヘンです(->8253pwm)。
エミュレータによっては PCG サウンドは矩形波再生のみ(?)で、mode0 をサポートしていないようです。

8253 文化といえば 98 ノートなどは音源非搭載な時代も長かったので、FM 音源より耳に残っている人も多いかもしれません。

(参考)
http://oswiki.osask.jp/?%28PIT%298254)
http://www43.tok2.com/home/cmpslv/Pc80/Pc80pcg.htm
http://www.maroon.dti.ne.jp/youkan/mz700/pg/sound.html

(?)PCG-8800 の CPU 8MHz 時の動作。
(?)テンポをどうやって取るか。要解析。
(?)エミュレータではモードが何でも鳴ってしまう。実機はどうか。

■ ADDCOM サウンドユニット (SN76489 * 2)

ADDCOM 製サウンドユニットで、いくつかバリエーションがあったようです。
雑誌掲載のものがあり、PC-8001 用が I/O 誌 82.6 掲載、80mk2/88 用がマイコン誌 84.2 掲載だそうです。
M88 の拡張モジュールや j80 で実装されています。

SN76489 を 2 つ搭載しています。
基準クロックは 4MHz です。

I/O ポート $AC 【出力】
bit7-6DCSG1 データ出力

I/O ポート $AD,$AE 【出力】
bit7-6クロックマスク / 解除

I/O ポート $AF 【出力】
bit7-6DCSG2 データ出力

SN76489 はレジスタ指定・データ書き込みを 1byte で行うようになっています。読み出しは一切なし。
セガマーク3 / マスターシステム / ゲームギア / MZ-1500 等々の資料が参考になるでしょう。

(SN76489,SN76496),(SN76489AN),(SN76494,SN76496) で「ノイズのフィードバックパターン」「ノイズのシフトレジスタ幅」が違うという区別があるようです。
PSG と違いハードウェアエンベロープはありません。ノイズチャンネルが独立していることと、ノイズ+トーンの連動機能があることなどが特徴です。
BASIC からは以下の通り(soundinfo -> addcom)。

10 ' ADDCOM Sound Unit SN76489 TEST
15 OUT &HAE,0
20 PT=&HAC:F=478:GOSUB 80 '3993600 / 32 / 261 o4c
30 PT=&HAF:F=318:GOSUB 80 '3993600 / 32 / 392 o4g
40 IF INP(9)<>191 THEN 40
50 PT=&HAC:D=&H80+CH*32+16+15:GOSUB 120
60 PT=&HAF:D=&H80+CH*32+16+15:GOSUB 120
70 END
80 CH=0:V=0:D=&H80+CH*32+16+V:GOSUB 120
90 CH=0:D=&H80+CH*32+(F AND 15):GOSUB 120
100 CH=0:D=(F AND &H3F0)/16:GOSUB 120
110 RETURN
120 DT=(D AND 1)*128  'invert bit7<->bit0
130 DT=DT+(D AND 2)*32
140 DT=DT+(D AND 4)*8
150 DT=DT+(D AND 8)*2
160 DT=DT+(D AND 16)/2
170 DT=DT+(D AND 32)/8
180 DT=DT+(D AND 64)/32
190 DT=DT+(D AND 128)/128
200 OUT PT,DT
210 RETURN
	

低音が弱い(音痴)なのはチップの特性です。
書き込みの際にビット順の反転が必要なことに注意しましょう。音源に書き込むデータは bit7 <-> bit0 にしなければなりません。
メモリに余裕があればテーブル引きにしてもいいかもしれませんが、機械語で書いても大した手間ではありません。

SN76489 は明示的に音を止めてやらないと起動直後などは音が出っぱなしになります。
このアドコムのユニットではリセットまたはポート $AD に何かを書き込むとクロックがマスクされて音が止まるそうです(HAL8999 氏)。
逆にポート $AE への書き込みでクロック供給開始。

(?)CPU 8MHz での動作。
(?)割り込みの有無。
(?)ビット反転は単に配線の問題?
(?)I/O $AD,$AE は無関係?

■ PCS-8081 テクノシンセ (AY-3-8910)

AY-3-8910 が 1 つ載っています。PC-8001 専用?
複数挿しが可能なようで、j80 では 4 枚挿しの状態で実装されています。合計で PSG 4 つの 12 音構成。

PCS-8081 -> PCS-8007 の順に発売されたようです。

PPI (intel8255) 経由で PSG をコントロールします。
基本的に、ポート C にレジスタ/データどちらの書き込みであるかを指示し、
ポート A にレジスタまたはデータを書き込み、最後にポート C に 0 を書き込みます。

コントロール方法は HAL9999 さんに教えていただきました。

I/O ポート $90 【PPI ポート A 出力】
bit7-0PSG レジスタ・データ

I/O ポート $92 【PPI ポート C 入出力】
bit5-4レジスタ指定=11 データ書き込み=10
bit7-0全ビット 0 を書き込むと書き込み確定
bit00,1 が定期的に読み出せる。

I/O ポート $93 【PPI コントロール出力】
bit71
bit6-5PortA, PortC上位4bitのモード選択 00=mode0 01=mode1 10=11=mode2
bit4PortA 0=出力 1=入力
bit3PortC上位4bit 0=出力 1=入力
bit2PortB, PortC下位4bitのモード選択 0=mode0 1=mode1
bit1PortB 0=出力 1=入力
bit0PortC下位4bit 0=出力 1=入力

BASIC からは以下の通り(soundinfo -> pcs8081)。

10 'PCS-8081
20 CLK=3579545
30 OUT &H93,&H81:OUT &H97,&H81:OUT &H9B,&H81:OUT &H9F,&H81
40 FRQ=CLK/261 'o4c
100 CS=0:REG=7:DAT=&H38:GOSUB 500
110 CS=0:REG=0:DAT=FRQ AND 255:GOSUB 500
120 CS=0:REG=1:DAT=(FRQ AND &HFF00)/256:GOSUB 500
130 CS=0:REG=8:DAT=15:GOSUB 500
180 PRINT"PUSH SPACE TO STOP"
190 IF INP(9)<>&HBF THEN 190
200 REG=8:DAT=0:CS=0:GOSUB 500:CS=1:GOSUB 500
210 REG=8:DAT=0:CS=2:GOSUB 500:CS=3:GOSUB 500
400 END
500 'write PSG ChipSelect=0/1/2/3
510 OUT &H92+CS*4,&H30
520 OUT &H90+CS*4,REG
530 OUT &H92+CS*4,0
540 OUT &H92+CS*4,&H20
550 OUT &H90+CS*4,DAT
560 OUT &H92+CS*4,0
570 RETURN
	

複数挿しをすると、2 つ目以降 の PCS-8081 はそれぞれ I/O ポート $94-$97, $98-$9B, $9C-$9F でコントロールできるようです。

ボード上にクロックが載っており、3.579545MHz を基準とするようです。

チャンネル毎に出力端子があるとか。複数挿しをするとミックスが大変なような。

タイマ機能があり、テンポ維持に使っているようです。
PPI の PortC から読み出せる、ということで実際 $92 の読み出しでは 0,1 が繰り返していて、
ドライバがこれで見てループを回しているのは分かるのですが、間隔が良く分かりません。(もしあれば)素直に 1/600 秒タイマなどを使うべきかと。

(?)対応機種詳細。
(?)ディップ設定、パターンカットなどの情報。
(?)書き込みの手順の検証。ドライバでは PSG データは「データ書き込み」->「データ指定」->「0 書き込み」となっている。
(?)何枚挿しまで可能なのか。
(?)タイマ詳細。
(?)ポート B は未使用?

■ PCS-8007 ルンルンシンセ (AY-3-8910 * 2)

AY-3-8910 が 2 つ載っています。PC-8001/mk2//88 対応。
複数挿しが可能なようで、j80 では 2 枚挿しの状態で実装されています。合計で PSG 4 つ。
ボード上に 3.579545MHz のクロックが載っています。

PCS-8081 -> PCS-8007 の順に発売されたようです。

PPI (Intel8255) 経由で PSG をコントロールします。
基本的に、ポート C にレジスタ/データどちらの書き込みであるかを指示し、
ポート A(PSG1)、ポートB(PSG2) にレジスタまたはデータを書き込み、最後にポート C に 0 を書き込みます。

コントロール方法は HAL9999 さんに教えていただきました。

I/O ポート $90 【PPI ポート A 出力】
bit7-0PSG1 レジスタ・データ

I/O ポート $91 【PPI ポート B 出力】
bit7-0PSG2 レジスタ・データ

I/O ポート $92 【PPI ポート C 入出力】
bit7-6PSG2: レジスタ指定=11 データ書き込み=10
bit5-4PSG1: レジスタ指定=11 データ書き込み=10
bit7-0全ビット 0 を書き込むと書き込み確定
bit00,1 が定期的に読み出せる。

I/O ポート $93 【PPI コントロール出力】
bit71
bit6-5PortA, PortC上位4bitのモード選択 00=mode0 01=mode1 10=11=mode2
bit4PortA 0=出力 1=入力
bit3PortC上位4bit 0=出力 1=入力
bit2PortB, PortC下位4bitのモード選択 0=mode0 1=mode1
bit1PortB 0=出力 1=入力
bit0PortC下位4bit 0=出力 1=入力

BASIC からは以下の通り(soundinfo -> pcs8007)。

10 'PCS-8007
20 CLK=3579545
30 OUT &H93,&H81:OUT &H97,&H81
40 FRQ=CLK/261 'o4c
100 CS=0:REG=7:DAT=&H38:GOSUB 500
110 CS=0:REG=0:DAT=FRQ AND 255:GOSUB 500
120 CS=0:REG=1:DAT=(FRQ AND &HFF00)/256:GOSUB 500
130 CS=0:REG=8:DAT=15:GOSUB 500
180 PRINT"PUSH SPACE TO STOP"
190 IF INP(9)<>&HBF THEN 190
200 CS=0:REG=8:DAT=0:GOSUB 500:CS=1:GOSUB 500
210 CS=2:REG=8:DAT=0:GOSUB 500:CS=3:GOSUB 500
400 END
500 'write PSG ChipSelect=0/1/2/3
510 OUT &H92+(CS AND 2)*2,&H30*4^(CS AND 1)
520 OUT &H90+(CS AND 2)*2+(CS AND 1),REG
530 OUT &H92+(CS AND 2)*2,0
540 OUT &H92+(CS AND 2)*2,&H20*4^(CS AND 1)
550 OUT &H90+(CS AND 2)*2+(CS AND 1),DAT
560 OUT &H92+(CS AND 2)*2,0
570 RETURN
	

複数枚挿しをすると、2 枚目 の PCS-8007 は I/O ポート $94-$97 でコントロールできるようです。

CPU 8MHz モードでも動作すると思います。
出力は LR の 2 つあり、PSG1 が R、PSG2 が L ですがディップスイッチの 1 を ON にすることで、チャンネル毎に LR 同時出力(モノラル)が可能です。
PPI コントロール $93 の内訳は推測。ドライバがそうなっているので。

タイマ機能があり、テンポ維持に使っているようです。
PPI の PortC から読み出せる、ということで実際 $92 の読み出しでは 0,1 が繰り返していて、
ドライバがこれで見てループを回しているのは分かるのですが、間隔が良く分かりません。(もしあれば)素直に 1/600 秒タイマなどを使うべきかと。

(?)対応機種詳細。
(?)1番以外のディップ設定、パターンカットなど。
(?)書き込みの手順の検証。ドライバでは PSG データは「データ書き込み」->「データ指定」->「0 書き込み」となっている。
(?)何枚挿しまで可能なのか。
(?)タイマ詳細。

■ PSG + CTC Card [I/O] (AY-3-8910)

I/O 誌 81.12 掲載の『PSGによる音楽自動演奏』という記事によるもののようです。j80 で実装。

初代 PC-8001 用であり上位機種では動かないようです。
AY-3-8910 1 つの構成ですが Z80 CTC を備えており、タイマ割り込みによりテンポをとるのが容易で、BASIC 実行中に割り込みで鳴らせるのが特徴。

PSG の扱い方は至って素直です。

I/O ポート $82 【出力】
bit7-0PSG レジスタ指定

I/O ポート $83 【出力】
bit7-6PSG データ書き込み

とりあえず、以下のようにすれば PSG は鳴ります(soundinfo -> psgctc)。

10 'PSG+CTC (I/O:1981.12)
20 CLK=3993600
30 FRQ=CLK/261 'o4c
100 CS=0:REG=7:DAT=&H38:GOSUB 500
110 CS=0:REG=0:DAT=FRQ AND 255:GOSUB 500
120 CS=0:REG=1:DAT=(FRQ AND &HFF00)/256:GOSUB 500
130 CS=0:REG=8:DAT=15:GOSUB 500
140 PRINT"PUSH SPACE TO STOP"
150 IF INP(9)<>&HBF THEN 190
160 CS=0:REG=8:DAT=0:GOSUB 500
400 END
500 'write PSG
510 OUT &H82,REG
520 OUT &H83,DAT
530 RETURN
	

CTC は ch.2 がタイマモードで動き、ch.2 をクロック源として ch.3 がカウンタモードで動作と割り込みを行う仕組みのようです。
X1 でいうところの ch.0 と ch.3 の関係。

I/O ポート $84 【CTC ch.0 出力】
bit7割り込みイネーブル 1:許可 0:禁止
bit6モードの設定。 1:カウンタモード 0:タイマーモード
bit5プリスケールの選択。タイマモードにおいてのみ意味がある。
1:1/256 0:1/16 供給クロック 4MHz に対する分周比を指定する。
bit4エッジの選択。無視して良い。
タイマモードの時は bit3 で使うトリガの方向(立ち上がり=1, 立ち下がり=0 を決める)
カウンタモードの時は数えるパルスの方向を決める(立ち上がり=1, 立ち下がり=0)
bit3トリガの有無。タイマモードにおいてのみ意味がある。無視して良い。
bit2タイムコンスタントの設定。bit2=1 で、次の 1byte がタイムコンスタントであることを示す。
bit1リセット。bit1=1 で停止。もし bit2=1 ならタイムコンスタントが書き込まれた後、動作開始。
bit0常に 1 にする。ch.0 の場合は bit0=0 ならば割り込みベクタ設定

I/O ポート $85 【CTC ch.1 出力】
bit7-0ch.0 と同じ

I/O ポート $86 【CTC ch.2 出力】
bit7-0ch.0 と同じ

I/O ポート $87 【CTC ch.3 出力】
bit7-0ch.0 と同じ

割り込みベクタ設定ができるのは ch.0 だけです。ch.0 で設定すると、ch.0-3 までベクタが連番で設定されます。
ベクタは下 3 bit が 000 でなければダメで、つまりアドレス xxx0 か xxx8 から +0,+2,+4,+6 に ch.0-3 のベクタが設定されます。

bit7 は「割り込みの」有無であって、タイマ・カウンタの動作や停止ではありません。

初代 PC-8001 は素の状態では割り込みが無いので、CTC で割り込みを設定した際には、
IM 2 と I レジスタへの割り込みテーブルアドレス設定が必要なようです。

CTC のタイマモードでは、供給クロック 4MHz ÷ プリスケール設定 ÷ タイムコンスタントのタイミングで出力が発生します。
カウンタモードでは、クロック源 ÷ タイムコンスタントのタイミングで出力が発生します。

例えば ch.2 にプリスケール 1/256 とタイムコンスタント 65 を設定すると 240Hz のタイマーになります。
その出力を ch.3 でカウントするので、240 ÷ ch.3 のタイムコンスタント値の間隔で割り込みが発生することになります。

動作モードはプログラマブルなので、上記構成以外も一応可能なはず。

CTC 単体でのデモプログラムを付けておきます(->psgctc)。残念ながら N88 モードでは動かず、N モードに切り替えてからでないと動作しません。

Table:
    jr      Main
    dw      Dummy
    dw      Dummy
    dw      IntCTC
    dw      Dummy
    dw      Dummy
    dw      Dummy
    dw      Dummy

Dummy:
    ei
    ret

Main:
    di
    im          2
    ld          a,Table >> 8
    ld          i,a
    ld          a,$FF
    out         ($E4),a

    ld          a,CTC_SETVECTOR + $00
    out         ($84),a

    ld          a,CTC_DISABLE | CTC_TIMER | CTC_PRESCALE256 | CTC_EDGE_UP | CTC_CONSTANT | CTC_RESET | CTC_SETCMD
    out         ($86),a
    ld          a,$41                       ;3993600/256/65=240(Hz)
    out         ($86),a

    ld          a,CTC_ENABLE | CTC_COUNTER | CTC_PRESCALE16 | CTC_EDGE_UP | CTC_CONSTANT | CTC_RESET | CTC_SETCMD
    out         ($87),a
    ld          a,$F0                       ;240Hz/240=1Hz
    out         ($87),a
    ei
    jr          $

IntCTC:
    push        af
    ld          a,$FF
    out         ($E4),a
〜長いので略〜
    pop         af
    ei
    ret
	

ch.2 で 240Hz を作って、それを元に ch.3 で 1Hz の割り込みを作り IntCTC を呼び出します。
I/O ポート $E4 を毎回設定するのが重要。

(?)PC-8001 初代機拡張バス接続?
(?)PSG 読み込みはできない?
(?)CTC 詳細

■ FAD-06 12ch シンセサイザユニット [Oh!PC:1982.8] (AY-3-8910 * 4)

PPI 接続の PSG ユニットで AY-3-8910 が 4 つも載る豪勢な仕様です。j80 で実装されています。
Oh!PC 誌 82.8 に製作記事がのっているそうですが、製品としてあった(?)という話も。

PPI というのは Intel 8255 (Programmable Peripheral Interface) のことで、80/88 であれば FDD のインターフェースとしてよく知られています。
Port A,B,C の 8bit 入出力ポートを 3 本備えています。モードは 0,1,2 がありますが、0 以外はハンドシェイクなどで煩わしいので覚えなくていいとか。

PPI のポート A,B,C は全てモード 0 で出力にします。
PSG のコントロールに必要なのはポート A,B の二つだけで C は使いません。
手順は PCS-8081/8007 によく似ています。

I/O ポート $87 【PPI コントロール出力】
bit71
bit6-5 PortA, PortC上位4bitのモード選択 00=mode0 01=mode1 10=11=mode2
bit4PortA 0=出力 1=入力
bit3PortC上位4bit 0=出力 1=入力
bit2PortB, PortC下位4bitのモード選択 0=mode0 1=mode1
bit1PortB 0=出力 1=入力
bit0PortC下位4bit 0=出力 1=入力

I/O ポート $84 【PPI ポート A 出力】
bit7-0PSG レジスタ・データ

I/O ポート $85 【PPI ポート B 出力】
bit7-6PSG4: レジスタ指定=11 データ書き込み=10 解除=00
bit5-4PSG3: レジスタ指定=11 データ書き込み=10 解除=00
bit3-2PSG2: レジスタ指定=11 データ書き込み=10 解除=00
bit1-0PSG1: レジスタ指定=11 データ書き込み=10 解除=00

基準となるクロックは 4MHz。
PSG へは、レジスタ指定 -> どの PSG へのレジスタ指定かを指示 -> 0 で解除、
データ指定 -> どの PSG へのデータ書き込みかを指示 -> 0 で解除、の順です。

BASIC からは以下の通り(soundinfo -> psg12ch)。

10 '12ch Synth Unit (Oh!PC:1982.8)
20 OUT &H87,&H80
30 CLK=3993600/32
40 FRQ=CLK/261 'o4c
100 CS=0:REG=7:DAT=&H38:GOSUB 500
110 CS=0:REG=0:DAT=FRQ AND 255:GOSUB 500
120 CS=0:REG=1:DAT=(FRQ AND &HFF00)/256:GOSUB 500 
130 CS=0:REG=8:DAT=15:GOSUB 500
180 PRINT"PUSH SPACE TO STOP"
190 IF INP(9)<>&HBF THEN 190
200 CS=0:REG=8:DAT=0:GOSUB 500
400 END
500 'write PSG ChipSelect=0/1/2/3
510 OUT &H84,REG
520 OUT &H85,&H3*(4^CS)
530 OUT &H85,0
540 OUT &H84,DAT
550 OUT &H85,&H2*(4^CS)
560 OUT &H85,0
570 RETURN
	

I/O ポート $85 の設定を見ても分かるとおり、PSG チップ毎にビット位置が分かれているので
複数の PSG に一括してラッチと書き込みができるようです。ステレオ演奏などの時は便利かも。

(?)対応機種詳細。
(?)書き込みの手順の検証。ドライバでは PSG データは「データ書き込み」->「データ指定」->「0 書き込み」となっている。
(?)PPI の内訳についてかなり推測。
(?)読み込みできるかどうか不明。

■ PPI + 1bit PCM [Oh!PC]

Oh!PC 83.5 の PPI 基板をつかった 1bit PCM 再生ユニットだそうです。同 83.7 に掲載(未見)。
j80 でサポートされています。

PPI というのは Intel 8255 (Programmable Peripheral Interface) のことで、80/88 であれば FDD のインターフェースとしてよく知られています。
この 1bit PCM につかう PPI は I/O ポート $D0-$D3 につながっているようで、そのうち PCM 再生には Port.B を使用する模様。

I/O ポート $D3 【PPI コントロール出力】
bit71
bit6-5 PortA, PortC上位4bitのモード選択 00=mode0 01=mode1 10=11=mode2
bit4PortA 0=出力 1=入力
bit3PortC上位4bit 0=出力 1=入力
bit2PortB, PortC下位4bitのモード選択 0=mode0 1=mode1
bit1PortB 0=出力 1=入力
bit0PortC下位4bit 0=出力 1=入力

I/O ポート $D1 【PPI ポート B 出力】
bit7-01bit PCM データ

Port.B 8bit 中、どの bit が PCM 出力として該当するのか不明。

テストプログラム(-> ppi1bit)では PCM データを PWM に変換して再生します。
大体こんな感じ。8bit -> 5bit 相当に落としています。

IO_PPI_PORTA    equ $D0
IO_PPI_PORTB    equ $D1
IO_PPI_PORTC    equ $D2
IO_PPI_CTRL     equ $D3

    ld          a,PPI_MODE0_ACH | PPI_OUT_A | PPI_OUT_CH | PPI_MODE0_BCL | PPI_OUT_B | PPI_OUT_CL 
    out         (IO_PPI_CTRL),a

    ld          hl,PCMDATA
    ld          de,PCMEND - PCMDATA
    ld          c,IO_PPI_PORTB
.loop:
    ld          b,(hl)                  ;7
    inc         hl                      ;6

    srl         b
    srl         b
    srl         b                       ;b=0-31

    ld          a,$FF                   ;7
    out         (c),a                   ;12

    ld          a,32                    ;7
    sub         b                       ;4
    inc         b                       ;4
    djnz        $

    out         (c),b                   ;12
    ld          b,a                     ;4
    djnz        $

    dec         de                      ;6
    ld          a,d                     ;4
    or          e                       ;4
    jp          nz,.loop                ;10
	

元データが 8bit, 8192Hz のものしか用意できなかったのですが、最初から再生レートやビット解像度を最適化して作れば多少はマシになるはず。

PPI に他に何が繋がっていて、ポートの入出力設定が本来どんなものなのか分からないので Port.A,B,C を全てモード 0 の出力にしています。
とりあえず 1bit PCM を再生することは出来ているようなので、他に問題がありそうなら設定を変えるとよいでしょう。
というか元記事を漁った方が早いのでしょうけれど…。

(?)対応機種詳細。

■ ミュージックアダプタ [ASCII 82.12] (SN76489 * 1)

プリンタポートにつなぐ SN76489x1 のアダプタです。ASCII 誌 82.12 に掲載。
j80 で実装されています。

音源チップは ADDCOM サウンドと同じですが、こちらはビット順序の反転は必要ありません。
普通にプリンタにデータを出力するのと同じ手順で書き込みます。
ただし、データを書き込む度に、やはりプリンタと同じようにストローブの OFF/ON が必要です。

I/O ポート $10 と $40 は PC88 内蔵カレンダクロック設定にも使われるのでバグには注意しましょう(戒め)。

基準クロックは 3.58MHz です。
(SN76489,SN76496),(SN76489AN),(SN76494,SN76496) で「ノイズのフィードバックパターン」「ノイズのシフトレジスタ幅」が違うという区別があるようです。

I/O ポート $10 【出力】
bit7-6DCSG データ出力

プリンタポートのストローブ信号出力は、I/O ポート $40 です。こちらも一応載せておきます。bit0 しか関係ないですが。

I/O ポート $40 【入力】
bit7未使用
bit6未使用
bit5CRTCからの垂直帰線信号
0=ディスプレイサイクル
1=垂直帰線サイクル
bit4カレンダクロックからのデータ
bit3DISKブートモード
0=DISKから起動した
1=DISKからの起動ではない
bit2CMTデータキャリアディテクト信号
0=キャリアオフ
1=キャリアオン
bit1高解像度CRTモード信号
0=高解像度CRTモード
1=標準CRTモード
bit0プリンタからのBUSY信号
0=プリンタREADY
1=プリンタBUSY
I/O ポート $40 【出力】
bit7ポート波形を出力(88mk2以降)
bit6汎用出力ポート
bit5BEEP(2400Hz)コントロール
0=BEEP Off
1=BEEP On
bit4グラフィックハイスピードモード(V1Sのみ有効)
0=スタンダード
1=ハイスピード
bit3CRT I/F 同期コントロール
0=同期ビットオフ
1=同期ビットオン
bit2カレンダクロック用シフトクロック
0=クロックオフ
1=クロックオン
bit1カレンダクロックへのストローブ信号
0=ストローブオフ
1=ストローブオン
bit0プリンタへのストローブ信号
0=ストローブオン
1=ストローブオフ
BASIC からは以下の通り(soundinfo -> ascmus)。

10 ' ASCII Music Addapter SN76489 TEST
20 PT=&H10
30 F=3579545/32/261 'o4c
40 CH=0:V=0 :D=&H80+CH*32+16+V:GOSUB 110
50 CH=0:D=&H80+CH*32+(F AND 15):GOSUB 110
60 CH=0:D=(F AND &H3F0)/16:GOSUB 110
70 PRINT"PUSH SPACE TO STOP"
80 IF INP(9)<>191 THEN 80
90 CH=0:D=&H80+CH*32+16+15:GOSUB 110
100 END
110 OUT &H40,0:OUT PT,D:OUT &H40,1:RETURN
	

プリンタポートなので、当然そこにつなぐ他の機器とは排他になります。

(?)全機種全モード対応?

■ JAST SOUND

プリンタポートに接続する PCM 音源です。
「天使たちの午後 リニューアル版」など、対応ゲーム上で音声が再生されます。
再生は CPU によるごり押しで、8bit モノラル 4KHz(可変)程度です。音質はあまりよくありませんが、当時ならこんなものでしょう。

PC88 の他、PC98、FM7、X1 シリーズに対応していたようです。
セントロニクス仕様のプリンタポートのある機種なら大抵繋がりそうな感じ。

http://web.archive.org/web/20050223115959/http://www.george24.com/~toppe/kikaku/just/just1.htm(Inernet Archive)
http://kitahei88.blog.fc2.com/blog-entry-87.html(レトロゲーム漫遊記 : episode2)
手作り互換品の製作記事があります。

このほかに といったラインナップがあった模様。本当に発売されていたのかは分かりません。
Wikipedia の JAST のページにも載っていて、Plus の CPU は Z80 だとか。

エミュレータは M88 の拡張モジュールの他、j80 でも対応しています。

コントロールは簡単で、JAST SOUND が接続されているプリンタポートに 8bit PCM データを流し込むだけです。
ストローブ(I/O ポート $40)の ON/OFF は必要ないようです。

I/O ポート $10 【出力】
bit7-6JAST SOUND データ出力

割り込みリソースが無い場合は再生レートを保つために CPU で空ループを回して再生に専念するなどの工夫が必要になります。
当時のゲームなどでは、音声が再生されている間は他のことが一切出来ない(場合によっては画面表示すら消える)ということがよくありました。

    di
    ld          hl,PCMDATA
    ld          de,PCMEND - 1
    ld          c,$10
.loop:
    outi                                ;16

    ld          b,33                    ;7
    djnz        $                       ;13/8 [13*32+8=424]

    or          a                       ;4
    sbc         hl,de                   ;15
    add         hl,de                   ;11
    jp          nz,.loop                ;10
    ei
	

PCM 再生テストプログラムを入れておきます(-> jastsnd)。
M88 の拡張モジュールである jastsnd.m88 はデフォルトでは再生レート 4000Hz で決め打ちなので、
設定ファイル jastsnd.ini に rate=8192 と記述しないと正常に再生されません。

レジスタを指定しなくて良い点を考えると Sound Board II の PCM 部より扱いやすいかもしれません。音質は悪いですが。

(?)他のバリエーションの詳細
(?)全機種全モード対応?

■ VOICE BOX

プリンタポートに接続します。j80 で対応。
「一石にかける青春」など、対応したソフトも少数ながらあるようです。MSX 版にも対応とか。

LOG は「DRAGON」などのテーブル/パズルっぽいゲームが主だったような気がします。
98 なら「極」シリーズなどでしょうか。

(ひろせ堂めいど商会)
VOICE BOX 紹介記事。パッケージや本体外観など。

4bit なので、プリンタポートに書き込む 1byte 中、データは下位 4bit に収めて書き込みます。
上位 4bit は %1101 固定で PCM データに OR $D0 してから書き込むということのようです。
再生レートは 7000Hz〜7500Hz 近辺と思われます。

I/O ポート $10 【出力】
bit71
bit61
bit50
bit41
bit3PCM Data bit2
bit2PCM Data bit1
bit1PCM Data bit0
bit0PCM Data 符号
という仕様らしいです。HAL9999 さんに教えていただきました。

    ld          hl,PCMDATA
    ld          de,PCMEND - PCMDATA
.loop:
    ld          a,(hl)
    rrca
    rrca
    rrca
    and         %00001110
    bit         7,(hl)
    jr          z,.skip
    inc         a
.skip:
    or          %11010000               ;$D0 と or を取る
    ex          af,af'

.wait1:
    in          a,($40)
    rrca                                ;port ready であれば待つ
    jr          nc,.wait1
.wait2:
    in          a,($40)
    rrca                                ;port busy であれば待つ
    jr          c,.wait2

    ex          af,af'
    out         ($10),a
    inc         hl
    dec         de
    ld          a,d
    or          e
    jp          nz,.loop

    xor         a
    out         ($10),a
	

プリンタポートの busy フラグを見ることで再生レートを保つ仕組みのようです。

4bit データを 1byte の上位・下位にパックするのが省メモリでいいと思うのですが、そういうツールはなかなか無いので
再生テストプログラム(-> voicebox)では 8bit PCM の上位 4bit を使っています。


(?)全機種全モード対応?
(?)詳細

■ 1bit PCM(PD0/PD7)

プリンタポートに接続するタイプの 1bit PCM で、雑誌掲載のもののようです。
PD0 の方は [ASCII 80.8 PC-8001 による音声合成][81.3]、PD7 の方は [I/O 82.2 スピーチ・シンセサイザ] に掲載の模様(未見)。
j80 でサポートされています。RAM 誌 84.4 にも載っていたとか。

PD0, PD7 はプリンタ(=パラレル)ポートのピン番号で、JAST SOUND 等と違い 1bit のみを使用します。

I/O ポート $10 に書き込むわけですが、単に使用するビット位置が違うだけであり、ON/OFF の情報を
わざわざ bit7 に合わせて $80/$00 または bit0 に合わせて $01/$00 などとしない限り、
$FF/$00 を書き込めば良いだけなのでプログラムは共通です。

I/O ポート $10 【出力】
bit71bit PCM(PD7) データ出力
bit01bit PCM(PD0) データ出力

再生テストプログラム(-> pd07)では PCM データを PWM に変換して再生します。
大体こんな感じ。8bit -> 5bit 相当に落としています。

    ld          hl,PCMDATA              ;8bit 8192Hz 
    ld          de,PCMEND - PCMDATA
    ld          c,$10
.loop:
    ld          b,(hl)                  ;7
    inc         hl                      ;6

    srl         b
    srl         b
    srl         b                       ;b=0-31

    ld          a,$FF                   ;7
    out         (c),a                   ;12

    ld          a,32                    ;7
    sub         b                       ;4
    inc         b                       ;4
    djnz        $

    out         (c),b                   ;12
    ld          b,a                     ;4
    djnz        $

    dec         de                      ;6
    ld          a,d                     ;4
    or          e                       ;4
    jp          nz,.loop                ;10
    jr          Main
	

元データが 8bit, 8192Hz のものしか用意できなかったのですが、最初から再生レートやビット深度を最適化して作れば多少はマシになるはず。

余談ですが、MSX にはキーボードのクリック音を出力する 1bit サウンドポートというものがあり、やはり音声再生に使われていたようです。


■ Appendix

随時追記・修正予定。
このページに書いてあることは多くの推測を含みます。実機での実験結果以外をソースにするのはお薦めできません。

Thanks.
・Out of STANDATRD http://upd780c1.g1.xrea.com/index.html
・UME-3 さん
・ALPHA-MINI DOS 1.2http://onitama.tv/split/
・ファルコム音楽フリー宣言 https://www.falcom.co.jp/music_use/


▲ TOP