拙作 Newシティヒーローの冒頭ローディング画面の処理で、カセットテープのロード中にアニメをしているシーンがあります。今回はこの実装について説明します。
Title
まるでスレッドで動いているようにも見えますが、実際にはテープロードの次回バイト読み込みまでの待ち時間内で、ロゴを動かしているに過ぎません。PC-8001 ではあまりこのような処理を見かけなかったので試してみたというワケです。


  • カセットテープの読み込みをどこに配置するか
カセットテープ自体は本体の処理とは関係なしに読み込みを続けていますので、本体側の空き時間に処理をすると言っても、時間的な余裕などほぼ無いに等しい状態です。また、ゲーム本体を PC-8001のRAMに読み込む関係で
  1. 読み込み中は BASIC ROMは生かしておく
  2. ゲーム本体の読み込み範囲にローダーを置けない
という割とキツめの制約を受けます。BASIC ROMを生かすという事は、BASIC ROMが使用しているワークエリアは、ロード終了までは一切壊せないという事を意味します。BASIC のワークエリアは N-BASICと N80-BASICで微妙に違いますので、マージンを見ておく必要があります。通常であれば 0xE400 が限界です。New シティヒーローでは動作確認の結果、0xE4F1 まで使っています。おそらく、これより後ろは殆ど猶予は無いと思います。

ゲーム中のワークエリアは 0xE51A から使用していますので、実際の空きは41バイトとなっています。バックバッファを 0xE700 からとしています。0xF300 からが VRAMです。一見、どこにも空きがないように見えますが、たった一箇所、空きがあります。それが VRAMのさらに後ろ、0xFEB8 - 0xFFFF です。

実はここもゲーム中はプレイヤーのワークやパターン反転テーブルで使い切っているのですが、0xFF00 - 0xFFFF の反転テーブルはロード終了時にプログラムで作成して、そのプログラムは自動的に消えてしまうので、少なくともロード中は空いています。そこで、ゲーム本体のロードプログラムを 0xFF40 - 0xFFFF に配置する事にしました。


  • テープ読み込みの前処理
最初は OUT命令を使って自分で制御しようかとも思ったのですが、Newシティヒーローでは PC-8001 / PC-8801 / mk2 / SR / PasocomMini という幅広い環境で動かすため、特にハードの違いが大きいと予想されるカセットテープの読み込みは、自分で制御は少し危険と感じました。そのため、BASIC ROMルーチンを呼び出す事で、データの読み込みを行う事にしたのです。ROM内ルーチンの場所と画面の初期化はこちらで確認してください。画面を初期化したら、続いてカセットテープ読み込み開始の手続きを行います。

    ld      a, %00101001
    out     (0x30), a          ; カセットモーターON
    call    BIOS.CMT_OPEN_R    ; CMT 読み込み開始
以後、call BIOS.CMT_READ を呼び出すたびに、カセットテープから読み込んだ1バイトが Acc に返却されます。読み込みが終わったら、

    ld     a, %00100001
    out     (0x30), a          ; カセットモーターOFF
と制御して、カセットテープを停止します。実はテープの停止は義務ではないので、無視しても問題ないです。止めなくてもカセットテープは壊れません。


  • テープから読み込んだデータの処理
BIOS.CMT_READ から読み込んだデータは、通常は以下のように処理していきます。カセットテープにどのような順番でデータが格納されているかは、実は任意であり、やろうと思えば全く標準とは異なる形式で保存する事も出来ます(独自形式だとモニタからロードは出来ません)。ここでは比較的標準的な形式で説明します。

最初に読み込まれるのは、冒頭の「ぴーーーっ」という音です。内容的には GAP という数値が返ってきます。この GAP は 0x3A です。0x3A が続く限りは最初は空読みを続けます。これは先頭位置合わせですね。

.serial
    call    BIOS.CMT_READ
    cp      0x3A
    jr      nz, .serial
続いて読み込み先アドレスが H, L, Sum と3バイト読み込まれます。H と L を足して NEG した結果が Sum と異なっていたらエラーとなります。

    call    BIOS.CMT_READ
    ld      h, a
    call    BIOS.CMT_READ
    ld      l, a               ; HL 読み込みアドレス
    call    BIOS.CMT_READ      ; Acc チェックサム
    ld      c, a
    ld      a, h
    add     a, l
    neg
    cp      c
    jp      nz, BIOS.CMT_CLOSE ; チェックサムエラーで停止
ここからはデータブロックの読み込みとなっていきます。

    ; ブロック間 GAPを読み飛ばす
.read
call    BIOS.CMT_READ
    cp      0x3A
    jr      nz, .read

    ; ブロックサイズ読み込み
    call    BIOS.CMT_READ      ; ブロックサイズ
    ld      b, a               ; B 読み込みループ回数
    ld      c, a               ; C チェックサム初期値

    ; ブロックデータ読み込み
.store
    call    BIOS.CMT_READ      ; Acc 1バイト読み込み
    ld      (hl), a            ; メモリに配置する
    inc     hl
    add     a, c
    ld      c, a               ; チェックサム加算
    ;
    ; ** ここにアニメ処理を記述する **
    ;
    djnz    .store             ; ブロック数分ループする

    ; チェックサム確認
    ld      a, c
    neg
    ld      c, a               ; 全ての加算値を NEG
    call    BIOS.CMT_READ      ; チェックサム値読み込み
    cp      c                  ; 比較して
    jp      nz, BIOS.CMT_CLOSE ; チェックサムエラーで停止
    jr      .read              ; 読み込みを続ける
ブロックとブロックの間に1バイトの 0x3A(GAP)が挟まれていますので、それを読み込んでスキップします。

続いてブロックサイズを読み込みます。1ブロックの最大サイズは256までです。おそらく 255 が入ってきますが、最終最後には端数が入ってきます。ここで得られた値でループします。また、この読み込んだ値もチェックサム対象なので保存します。

ここからはブロックサイズ分ループしながらメモリに読み込んだデータを書き込みつつ、チェックサムを更新しつつ進行します。この部分に僅かに処理する時間があるので、ここにロゴアニメの処理を入れています。サウンド演奏ルーチンを入れたりすれば、中村光一氏のドアドアのような事も出来るかもしれません。

最後に 0x3A, 0x3A, 0x3A と3回 GAPが続いて本当の終了となります。読み込んでも意味が無いので、私は無視してしまいました(汗