前回のエントリの続きで、タイニーあぴミクさんデモのプログラム本体について書こうと思います。
無駄に長いので閲覧注意
プログラムといっても、やっていることは「よっしゅさんの P6 PSGドライバで BGMを鳴らしながらグラフィックデータをテープからロードしている」だけで、技術的に高度なことをしているわけではありません。
と前回書いていますが、そもそも最初からデモを作ろうと思ったわけではなく、最初は単に
という程度のノリでした。
Step 1: とりあえずテープからロード
まずは「VRAMデータのロード」「PSGドライバと曲データのロード」ということをしてやる必要がありますが、N60-BASIC には BLOAD といった機械語ロードの命令はありません。なので、テープからのデータロードについては自前で実装する必要があります。
このテープからのデータロードについては、安直に Tinyみずいろデモ で使われていた BASIC ROM内ルーチンを使う方法をそのまま流用させてもらいました。
Tinyみずいろデモでは「5枚のグラフィックデータをオンメモリに読み込んでおいて、BASICプログラムによるアドベンチャーパート(?)の画像表示コマンドで指定された画像をVRAMに転送する」という構成です。
テープからのロードルーチン自体は BASIC プログラムの DATA文で格納されていて、 READ文で読み出した上で POKE文で RAM上に書き込み、 EXEC文で実行、という流れです。
N60-BASICについて解説しているといくら書いても終わらないので「N6x BASIC リファレンス-コマンド一覧」等を参照してください
実際のテープからのロードとデータの構成については、まずは Z80のコードをそのまま貼ってみます。 Z80の命令そのものやアセンブラについてはいくら書いても(以下略
org 9f28h
VRAMTOP equ 0e200h
OPENTAPE equ 1a61h
READTAPE equ 1a70h
CLOSETAPE equ 1aaah
loadbin:
call OPENTAPE
ld hl,datatop
ld bc,dataend-datatop
loadbin1:
call READTAPE
cp 0c9h
jr nz,loadbin1
loadbin2:
call READTAPE
cp 0c9h
jr z,loadbin2
loadbin3:
call READTAPE
ld (hl),a
inc hl
dec bc
ld a,b
or c
jr nz,loadbin3
call CLOSETAPE
ld hl,datatop
ld de,VRAMTOP
ld bc,6144
ldir
ret
exo_mapbasebits:
defs 156 ; exomizer tables for bits, baseL, baseH
org_a000: ds 0a000h - org_a000 ;
org 0a000h
datatop:
include "apimiku-screen3.asm"
org_d000: ds 0d000h - org_d000 ;
org 0d000h
include "psgdrv.asm"
org_d740: ds 0d740h - org_d740 ;
org 0d740h
;deexo
;input: hl=compressed data start
; de=uncompressed destination start
include "deexo.asm"
org_d800: ds 0d800h - org_d800 ;
org 0d800h
OBJSAD:
include "TellYourWorld.asm"
dataend:
end
Tinyみずいろのプログラムのメモリマップは 以前のエントリ に書いていますが、
9F00h〜9F27h
までが画像表示ルーチン9F28h
からがテープロードルーチンA000h
以降にロードしたデータを格納
という構成です。
テープのアクセス自体は BASIC ROM内ルーチンの「テープ入力開始」「テープ1バイト入力」「テープ入力終了」を呼んでいるだけです。ちなみに「テープ入力開始」と「テープ入力終了」のルーチンではデータレコーダーの REMOTE端子のリレーON/OFFも行われます。
テープ上に記録されているデータの形式については、 CSAVE で記録される N-60 BASIC のプログラムについては
D3h
が 10バイト連続- 続く 6バイトがファイル名
という形式になっています。一方、カスタムの機械語データのロードにおいても先頭に同期用のヘッダデータを付加するのが作法なのか、先頭に C9h
の連続データ + 00h
のデータが付加されていました。
このヘッダの処理について、前述のプログラム上では
loadbin1
のところが ヘッダのC9h
のデータが現れるまで待つループloadbin2
のところが「C9h
以外のデータが1バイト来る」のを待つループloadbin3
のところが「BCレジスタで指定された長さ分だけテープからロードする」というループ
という構成で処理されています。
すべてのデータが読み込まれたら、 LDIR 命令を使ってロードされたグラフィックデータを VRAMに転送し、 C9h
の RET 命令で BASIC にリターンします。P6 PSGドライバの演奏開始の呼び出しは BASIC 側の EXEC文で実行しています。
それ以降にある org 0a000h
からのアドレスのデータがテープからロードされるデータで、以下の順で配置されています。
- あぴミクさん画像の VRAM データ
- P6 PSGドライバ
- Exomizer 展開ルーチン(この時点では未使用)
- 採譜した Tell Your World の曲の P6 PSGドライバ用コンパイル済み演奏データ
これらを書いて遊んでいたのがこのあたりのツイート。
P6は LDIR も速い (VRAMが小さいともいう)
— Izumi Tsutsui (@tsutsuii) 2016年7月13日
「ギャギャギャギャギャ゚(゚´ω`゚)゚。ピー pic.twitter.com/rLX8fCvrQq
— Izumi Tsutsui (@tsutsuii) 2016年7月13日
それでは聞いてください「PC-6001で歌うタイニーあぴミクさん」 pic.twitter.com/jSR9mxgGbY
— Izumi Tsutsui (@tsutsuii) 2016年7月13日
Step 2: ストリーミングロード
「ストリーミングロード」とは、 Tiny野郎さんのYS2OPデモ で使用されている、「画面スクロール描画とBGM演奏をしながらテープからデータをロードする」という技です。
この動画を見てP6いじりにハマったといっても過言ではありません
P6におけるテープストリーミングロードの実装詳細については Tiny野郎さんのブログ記事「P6イース2オープニングの作り方(6)」でくわしく解説されていますが、ポイントとしては以下の2点です。
- テープに記録するデータのストップビットをデフォルトより多く挿入し、テープロードにおけるバイトデータ間の待ち時間を増やすことで、2ms割り込みで処理されるPSGドライバの実行時間を確保する
- 画面描画のような時間のかかる処理を行う場合は、その処理時間に対応する量のダミーデータをテープ上に配置して、処理の間テープが読み飛ばされても問題ないようにする
Step 1 のテストプログラムが動いた後、
- 画像データは最初からVRAMに直接ロードすれば上から順番に表示されるはず
- そうすると画面に動きが出てロード待ち時間の退屈が緩和されるんじゃないか
- さらに、PSGドライバと曲データを先にロードするようにすれば BGM演奏を始めた状態で画像データロードができるはず?
ということで書いてみたのが以下のプログラムです。
org 9f00h
VRAMTOP equ 0e200h
OPENTAPE equ 1a61h
READTAPE equ 1a70h
CLOSETAPE equ 1aaah
loadbin:
call OPENTAPE
ld hl,datatop
ld bc,dataend-datatop
loadbin1:
call READTAPE
cp 0c3h
jr nz,loadbin1
loadbin2:
call READTAPE
cp 0c3h
jr z,loadbin2
loadbin3:
call READTAPE
ld (hl),a
inc hl
dec bc
ld a,b
or c
jr nz,loadbin3
call START
ld hl,VRAMTOP
ld bc,vdataend-vdatatop
loadbin11:
call READTAPE
cp 0c3h
jr nz,loadbin11
loadbin12:
call READTAPE
cp 0c3h
jr z,loadbin12
loadbin13:
call READTAPE
ld (hl),a
inc hl
dec bc
ld a,b
or c
jr nz,loadbin13
call CLOSETAPE
ret
exo_mapbasebits:
defs 156 ; exomizer tables for bits, baseL, baseH
org_a000: ds 0a000h - org_a000 ;
org 0a000h
datatop:
include "psgdrv.asm"
org_a740: ds 0a740h - org_a740 ;
org 0a740h
;deexo
;input: hl=compressed data start
; de=uncompressed destination start
include "deexo.asm"
org_a800: ds 0a800h - org_a800 ;
org 0a800h
OBJSAD:
include "TellYourWorld.asm"
dataend:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
vdatatop:
include "apimiku-screen3.asm"
vdataend:
end
まず、ロードされるデータの配置を以下のように入れ替えます。
A000h
(datatop
ラベル) の先頭に P6 PSGドライバを置くA740h
から exomizer の展開ルーチンを置く (この時点では未使用)A800h
から Tell Your World の P6 PSGドライバ用コンパイル済み曲データを置く- その後ろの
vdatatop
とvdataend
のラベルの間に VRAMデータを置く
その上で、曲データと VRAMデータの間に、処理時間待ちのダミーデータとロード再開時の同期用ヘッダデータを配置します。
ロードプログラムの動作は以下のようになっています。
loadbin1
,loadbin2
,loadbin3
の各ラベルで PSGドライバと曲データをA000h
からの領域にロード
ヘッダのデータをC9h
からC3h
に変えた理由が思い出せない……CALL START
で PSGドライバ演奏開始ルーチンを呼んで BGM開始loadbin11
,loadbin12
,loadbin13
の各ラベルであぴミクさん画像データを VRAMのE200h
以降に直接ロード- ロードが終わった時点で BGM演奏も画面表示も完了しているのでそのまま BASIC にリターン
ストリーミングにおいて必要な「テープに記録するデータのストップビットをデフォルトより多く挿入する」については、 Tiny野郎さん作の P6形式データ→実機テープロード用wav変換ツールである「なんでもピーガー」がストップビット設定に対応しているので、そこでパラメータとしてストップビットの数を設定するだけです。
YS2OPデモのP6実機実行 でいろいろ試行錯誤したときの経験からすると、 P6 PSGドライバを安定動作させるためには「ストップビット=7ビット」にすればほぼ問題ないようです。
なお、ストップビットが短すぎる場合は、 BGMがおかしくなるのではなく、ロードされるデータの取りこぼしが発生します。その結果として画面化けやロードデータ数のズレ等により暴走等が発生することになります。
ストリーミングにおいて必要な 2点目の「処理時間に応じたダミーデータの挿入」については、この時点ではシビアなタイミングは不要であったので、適当に多めにデータを挿入して、再ロード時は最初のロードと同様のヘッダを入れるようにしました。
よっしゅさんのPSGドライバを使えば音楽を鳴らしながらのテープロードも楽勝だということが実証できて満足です( '−`)
— Izumi Tsutsui (@tsutsuii) 2016年7月13日
ストリーミングロードにおけるエミュレータと実機動作の違い
Tiny野郎さんの「P6イース2オープニングの作り方(6)」の記事でも書かれていますが、ストリーミングロードについてはエミュレータ上の動作と実機での動作とでかなりの差があります。
デモプログラム作成においては実機での調整が必要で、この調整をするには 再アセンブル〜wav作成〜実機ロード というサイクルが必要になり、トライアンドエラー的な意味で結構難儀します。
テープデータ中のストップビット
P6 の BASIC の標準では UART のシリアル通信としてのビット構成は
スタートビット 1ビット + データ 8ビット + ストップビット 3ビット
で、合計すると「1バイトあたり 12ビット」で構成されています。この 12ビットが 1200bps で記録されるので、1バイトのデータが 10ms 毎にロードされることになります。
ここから先は想像ですが、各P6エミュレータ上では
「ロードデータの受信を完了してから10ms以内に読み出しが行われれば取りこぼさない」
という構成になっているのではないかと思います。
一方、 P6実機(正確にはデータロードを担当しているサブCPU)では、UARTとしてのストップビットは1ビットで設定されているのではないかと思います。その場合、
「ストップビットの1ビット目で受信が完了して、次のバイトのスタートビットが始まるまでにデータを取り出す必要がある」
ということになるのではないかと思います。
標準のストップビット3ビットの設定だと残りの猶予時間は 2ビット分、つまり (1/1200 × 2ビット) の 約1.67ms の間にデータを取り出す必要があるということになりますが、 P6 PSGドライバの割り込み処理時間は 2ms以上かかる場合もあるようなので、これでは全然間に合わないということになります。
ストップビットを7ビットにした場合は猶予時間は6ビット、つまり 5ms あることになります。PSGドライバ以外の VDG転送時間がどれくらいなのかは確認していませんが、PSGドライバだけでも 3ms以上消費する場合もあるようなので、設定としてはわりと妥当なのではないかと思っています。
ストップビット設定によるロード時間の差異
標準のストップビット 3ビットの設定の場合は 1バイトデータのフレームが 12ビットで 1200bps の場合 10ms/バイト という速度になります。一方、ストップビットを 7ビットにした場合は 1バイトデータのフレームが 16ビットになり、 1200bps では標準の 12ビットの場合と比べ 16/12 倍の 13.3ms/バイト かかるということになります。
PC6001V や PC6001VX の場合は 1バイトあたりのロード時間は標準のビット構成で固定されていて 10ms になっており、「ストップビットを7ビット」に変更した前提のプログラムだと「テープロード時間と BGM演奏のタイミング」や「プログラム処理実行中のテープ読み飛ばしのバイト数」がかなりズレてきます。
PC6001VW の場合は「オプション2の設定項目」の中に「テープボーレート」という項目があり、これがデフォルトでは (1200bps ではなく) 1000bps となっています。これが何を意味するかと言うと、「標準の 12ビットフレームを受信するのに 12ms 必要」つまり、2ms分 (=2.4ビット分)だけストップビットが多いのと等価です。
PC6001VW においてこの「テープボーレート」を 900bps に設定すると、 1バイトフレームの受信時間が 1200/900倍の 13.3ms となり、実質ストップビットを 7ビットにした実機と同じタイミングでロードされることになります。この設定の場合は「テープロード時間とBGM演奏のタイミング」のズレはかなり少なくなるようです。
ただし、 PSGドライバの 2ms 割り込みやサブCPUからのデータの取り出し等々で実機とまったく同じタイミングとはいかず、全体としては「実機ではエミュレータでの設定よりダミーデータを増やす必要がある」という傾向がありました。このため、最終的には実機でタイミングを合わせる必要があります。
エミュでは動くが実機では動かないモードが発動してつらい(ヽ´ω`) pic.twitter.com/DG1KrYhqiH
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
Step 3: 画像データ圧縮
BGMを鳴らしながらのストリーミングロードが動いたところで、
などという考えが頭に浮かびました。
マシンガンPによるかわいいあぴミクさん元動画はこちら
そんな思いつきで作業を始めたのがこのツイート。
タイニーあぴミクさん動画を作ろう
— Izumi Tsutsui (@tsutsuii) 2016年7月15日
動画投稿を見据えてデモの構成を考えると、1つのポイントとして
「テープロードの待ち時間(=画面が何も動いていない時間)をなるべく短くする」
ということを考えました。
データロード時間を短くするには「データを圧縮する」という案があり、圧縮アルゴリズム自体は Tinyみずいろデモ での Exomizer の実績はあったので、それを適用するだけという案はありました。
一方、画像データを圧縮すると
「圧縮したデータ全体を読み込んだ後でないと展開(=表示)できない」
という問題がある一方、VRAM直接ロードの場合は
「左上から順に走査線のように描画されていく様子が実質アニメーションになる」
というメリット(?)もあります。
……といったことをあわせて、ざっと考えて以下の構成にすることにしました。
- 1枚目は(何も表示されていないところからスタートなので)VRAM直接ロードで描画
- 2枚目以降は以下のように2分割で描画
- 上半分は(前の絵を愛でる時間として) Exomizer 圧縮データをロードした後に展開
- 下半分は(飽きてきた頃にロード中を主張する意味で) VRAMに直接ロードで描画
ランレングス圧縮
「VRAMに直接ロード」の場合でも多少なりともロード時間を短くできないか、ということで、Tiny野郎さんのブログ記事「P6イース2オープニングの作り方(3)」でも挙げられていたランレングス圧縮を入れてみることにしました。
Tiny野郎さんの記事にもあるとおり、ランレングス圧縮は「同じデータが連続する場合に、データと個数を並べる」という方式です。
より具体的には、0〜FFh までの 256通りの1バイトデータの1つを「キーデータ」として、同じデータが3つ以上連続するパターンが現れた場合(もしくはキーデータそのものが現れた場合)には
(キーデータ) (個数) (連続するデータ値)
と記録します。
展開時は、「キーデータが現れた場合はフォーマットに従って指定されたデータを個数分連続して書き込む」という動作になります。
ここで、ランレングス圧縮のもう一つのメリットとして
「圧縮後のデータ全体をバッファに格納する必要がなく、1バイトずつ読んだそばから展開ができる」
という点が生きてきます。「キーデータが来たら次のデータを個数として記憶」「さらに次のデータを個数分連続して書く」だけなので、ロード時の動作としては「データが連続している箇所は一気に描画される」という具合になります。
実際には、P6の SCREEN3 モードでは「1バイト当たり4ドット」なので、圧縮が効くのは「4ドットの特定パターンが3回以上繰り返される」という場合だけになり、意図してそういうパターンを描かない限りは実質圧縮されるのは背景のベタ塗り部分だけになります。
しかし、キーデータを「ほぼありえないデータ」にしておけば元データのサイズより増えることはほぼ無く、実際に使ったデータも背景ベタ部分がそれなりにあったので、結構圧縮の効果はありました。
特に、1枚目のこのあぴミクさんだと元の 6144バイトに対してランレングス圧縮後は 3589バイトと、約58%のサイズになりました。
ということで、前項のストリーミングロードのプログラムに対してランレングス圧縮展開を追加したのがこちらのプログラム。
org 9f00h
VRAMTOP equ 0e200h
OPENTAPE equ 1a61h
READTAPE equ 1a70h
CLOSETAPE equ 1aaah
MAGIC equ 09ch
loadbin:
call OPENTAPE
ld hl,datatop
ld bc,dataend-datatop
loadbin1:
call READTAPE
cp 0c3h
jr nz,loadbin1
loadbin2:
call READTAPE
cp 0c3h
jr z,loadbin2
loadbin3:
call READTAPE
ld (hl),a
inc hl
dec bc
ld a,b
or c
jr nz,loadbin3
call START
ld hl,VRAMTOP
ld de,vdataend-vdatatop
loadbin11:
call READTAPE
cp 0c3h
jr nz,loadbin11
loadbin12:
call READTAPE
cp 0c3h
jr z,loadbin12
loadbin13:
call READTAPE
dec de
cp MAGIC
jr z,loadbin14
ld (hl),a
inc hl
jr loadbin16
loadbin14:
call READTAPE
dec de
ld b,a
call READTAPE
dec de
loadbin15:
ld (hl),a
inc hl
djnz loadbin15
loadbin16:
ld a,d
or e
jr nz,loadbin13
call CLOSETAPE
ret
exo_mapbasebits:
defs 156 ; exomizer tables for bits, baseL, baseH
org_a000: ds 0a000h - org_a000 ;
org 0a000h
datatop:
include "psgdrv.asm"
org_a740: ds 0a740h - org_a740 ;
org 0a740h
;deexo
;input: hl=compressed data start
; de=uncompressed destination start
include "deexo.asm"
org_a800: ds 0a800h - org_a800 ;
org 0a800h
OBJSAD:
include "TellYourWorld.asm"
dataend:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
vdatatop:
include "api1-vram-rlcomp.asm"
vdataend:
end
MAGIC
で書いている 9Ch
がキーデータで、意図としては「10011100b」という「1バイト中の4ドットがすべて異なる色になっているパターン」はそんなに無いだろう、というくらいでしたが、実際出てくることはなかったようです。
実装としては、ラベル loadbin13
から loadbin15
の部分で「個数を Bレジスタに入れて DJNZ
を使って連続書き込み」としているだけです。DJNZ
のループ分だけ 1バイト受信後の処理時間が増えることになりますが、これくらいだと問題にはならないようでした。
ちなみに圧縮データを作成するためのプログラムはその場で適当に書いたのですが、これも gist に投棄しておきました。検証用展開プログラムはこちら。いずれも「圧縮して展開した結果が一致すればOK」という超テキトー実装なのでツッコミはご勘弁
蛇足ですが、ランレングス圧縮の場合「256個以上のデータが連続する場合でも、フォーマット上圧縮できるのは255個まで」というところに注意が必要です。この罠に見事にハマりました
複数画像展開
ランレングス圧縮展開が動けば、もう一つの Exomizer 展開ルーチンはすでに動作実績があるので、後は以下のように順にロード・展開の実装を書くだけです。
- 1枚目をランレングス圧縮展開で VRAM直接展開
- 2枚目の上半分の Exomizer 圧縮データをバッファに読み出し
- 読み出した圧縮データをVRAMに展開
- 2枚目の下半分をランレングス圧縮展開で VRAM直接展開
- 3枚目以降も同様
ただ、4分弱の Tell Your World の曲データに対して何枚の画像を表示できるかというところは実際に圧縮データを作ってロード時間を見ないとわかりません。ということで、最初はエイヤで以下の4枚で作成しました。
が、デバッグに悪戦苦闘しつつエミュレータ上でテストすると結構丈が余ってしまったので、元動画の「だめー?」というミクさんの絵を作って2枚目と3枚目の間に足して見ることにしました。
さらに、「せっかくだから1枚くらいはアニメーションっぽくできないかな〜」と、この絵だけは全画面を Exomizer 圧縮して一気に表示することにしました。結果として、 Exomizer の展開処理による描画速度が逆にいい感じで、さらに、たまたま書き換えのタイミングが BGMの2周目のループ部分に来たことで思ったよりいい感じになりました。自己満足炸裂モード
— Izumi Tsutsui (@tsutsuii) 2016年7月15日
よくわからんバグにハマった(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年7月15日
ブレークをかけづらい場所だ……
— Izumi Tsutsui (@tsutsuii) 2016年7月15日
久しぶりの鬼ステップ実行の結果、ロードのサイズが変という結果
— Izumi Tsutsui (@tsutsuii) 2016年7月15日
データ作成が間違っていた(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年7月15日
動きました。
— Izumi Tsutsui (@tsutsuii) 2016年7月15日
このあたりツイートがこのへんをいじって遊んでいた時分ですね……
このときにコミットした PC6001VW でデバッグ完了したソースコードはこちら。
org 9f00h
VRAMTOP equ 0e200h
OPENTAPE equ 1a61h
READTAPE equ 1a70h
CLOSETAPE equ 1aaah
MAGIC equ 09ch
loadbin:
call OPENTAPE
ld hl,datatop
ld bc,dataend-datatop
call loadtape
call START
ld hl,VRAMTOP
ld de,api1end-api1top
call loadrl
ld hl,loadbuf
ld bc,api21end-api21top
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,VRAMTOP+3072
ld de,api22end-api22top
call loadrl
ld hl,loadbuf
ld bc,api2aend-api2atop
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,loadbuf
ld bc,api31end-api31top
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,VRAMTOP+3072
ld de,api32end-api32top
call loadrl
ld hl,loadbuf
ld bc,api41end-api41top
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,VRAMTOP+3072
ld de,api42end-api42top
call loadrl
; call CLOSETAPE
; ret
jp CLOSETAPE
skipmark:
call READTAPE
cp 0c3h
jr nz,skipmark
skipmark1:
call READTAPE
cp 0c3h
jr z,skipmark1
ret
loadtape:
call skipmark
loadtape1:
call READTAPE
ld (hl),a
inc hl
dec bc
ld a,b
or c
jr nz,loadtape1
ret
loadrl:
call skipmark
loadrl1:
call READTAPE
dec de
cp MAGIC
jr z,loadrl2
ld (hl),a
inc hl
jr loadrl4
loadrl2:
call READTAPE
dec de
ld b,a
call READTAPE
dec de
loadrl3:
ld (hl),a
inc hl
djnz loadrl3
loadrl4:
ld a,d
or e
jr nz,loadrl1
ret
org_a000: ds 0a000h - org_a000 ;
org 0a000h
datatop:
include "psgdrv.asm"
org_a740: ds 0a740h - org_a740 ;
org 0a740h
;deexo
;input: hl=compressed data start
; de=uncompressed destination start
include "deexo.asm"
org_a800: ds 0a800h - org_a800 ;
org 0a800h
OBJSAD:
include "TellYourWorld2.asm"
dataend:
exo_mapbasebits:
defs 156 ; exomizer tables for bits, baseL, baseH
loadbuf:
db 0,0,0,0
db 0c3h,0c3h,0c3h,000h
api1top:
include "api1-vram-rlcomp.asm"
api1end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api21top:
include "api2-vram_1_exo.asm"
api21end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api22top:
include "api2-vram_2_rlcomp.asm"
api22end:
db 0,0,0,0,
db 0c3h,0c3h,0c3h,000h
api2atop:
include "api2a-vram_exo.asm"
api2aend:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api31top:
include "api3-vram_1_exo.asm"
api31end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api32top:
include "api3-vram_2_rlcomp.asm"
api32end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api41top:
include "api4-vram_1_exo.asm"
api41end:
db 0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api42top:
include "api4-vram_2_rlcomp.asm"
api42end:
end
自分で見返しても頭悪いという感じですが、アセンブラなので無理して構造化を考えず頭からベタで「ランレングス圧縮データのVRAM直接読み込み」「Exomizer圧縮データ読み込み」「Exomizer圧縮データ展開」をひたすら繰り返しているだけです。
これをアセンブルしてできたバイナリのうち、datatop
ラベルの A000h
以降のデータに C3h
のヘッダマークを付けたものをテープからロードする P6形式ベタデータとして保管する、ということになります。
Step 4: 実機調整
ストリーミングロードの項で書いたとおり、テープロードおよび PSGドライバの動作についてはエミュレータと実機とでは微妙にタイミングが異なるので、P6ファイル作成→wavファイル変換→P6実機CLOAD というサイクルで主に処理待ち時間のダミーデータの数を調整することになります。
実機テストつらい(速度的な意味で)
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
ウッキー!! pic.twitter.com/Y2V9gTwrhG
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
あ^〜(このあと別のところで止まりました) pic.twitter.com/knu2ZLNhyJ
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
どんだけウェイトいるんだよというか なんでこんなにエミュとタイミング違うんすか(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
ウェイト入れ過ぎると今度はBGMを画面がズレるという問題
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
やっと完走しました……
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
なんだかんだで実機での調整に1時間くらいかかってますね……。
ここまでは「Windows Media Player で wav 再生と同時に CLOAD」でテストしていたので、次はデータレコーダー実機でのテストです。
とりあえず 実機でも再生タイミングOKかな というのができたので wav ファイルをテープに録音します
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
wav からセーブ中 pic.twitter.com/HTsk3SpHS1
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
ウッキー!!! (wav だとOkだったのにテープだとコケるパターン) pic.twitter.com/UtSBXhcufz
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
P6実機 + テープ再生でもやっとこさ完走。長かった……
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
理屈の上では wav ファイル直接ロードで動いたのであればデータレコーダー実機で動かない理由はないのですが、このときはまだ複数 wav ファイルの結合(BASIC分とデータ分で2つのP6ファイルがあるので 2つの wav を結合していた)の手順が確立していなかったのと、データレコーダー自体も修理が完全ではなかったのが敗因だと思います。
Step 5 : PSGドライバロード時間短縮
「Tell Your World のオルゴール曲を鳴らしながらあぴミクさん絵をロードする」というデモ自体は当初の目論見通りに完成したわけですが、いざ動画撮影の準備として何度かデモを流してみると、最初に PSGドライバと曲データをロードしている間の「Now Loading...」の時間を短縮したくなってきました。
一通りでもが動いていたけれど、「最初のロードで圧縮ルーチンだけ先に読んで、残りのドライバは圧縮しとけばロード時間を短縮できるんじゃね?」と余計なことをやり始めている(無計画)
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
ざっとサイズを計算してみるとこんな感じです。
ドライバ+データの 3295 バイトを圧縮すると 1846 バイトで 1449バイト減。関数コール追加で数バイト使うとして1440バイト。展開時間は1秒未満。1200bps の 8ストップビットなら 20秒近く縮まるはず?
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
20秒というは動画視聴的にはかなり大きな時間なので、以下のように構成を変更してテキトーに実装してみました。
- PSGドライバを A000h から配置
- PSG曲データを A800h から配置
(ドライバと曲データとの間にすき間ができるが圧縮されるので気にしない) - その後ろから Exomizer の展開ルーチンを配置して、そこからロード用P6データ領域にする
- Exomizer展開ルーチンのあとに圧縮した PSGドライバと曲データを置く
- それ以降は従来通りの画像データを配置
実行時は以下のようになります。
- テープ先頭の Exomizer 展開ルーチンと圧縮された PSGドライバ+曲データを読む
- そこまで読み終わったら PSGドライバ+曲データを展開して即BGM再生開始
- それ以降は従来通り順番にロードもしくは展開
「圧縮された PSGドライバ+曲データ」を準備するのにアセンブラを 2パスで回す必要がありやや面倒ですが、それを除けばわりとあっさり動きました。20秒の短縮効果はかなり大きく、かつてのドアドアもこんな気持ちで作ったのかな〜、などと勝手な自己満足に浸っていたのですが、この後で再度実機でのタイミング調整をするはめになりました……。ズサンな計画ともいう
また実機だとウェイトが足りない問題にハマった(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
じゃなくてストップビット設定忘れてた(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
ストップビットだけじゃなくてウェイトも足りてなかった(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
テープ読み出しで小細工を弄するデモを作るためには、 PC-6001 のテープまわり、すなわちサブCPU周りのタイミングについても正確なエミュレータが求められる(ヽ´ω`)
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
もっとも、実行時のはオンメモリの Tinyみずいろデモでも実機とエミュレータとで目で見てわかるくらいタイミング違うから、割り込みもしくはVRAMアクセスタイミングあたりからして違うのかもしれない
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
実機タイミング調整つらい(テープの録音が)
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
最初を1400バイト削ったけど、実は156バイトの要らんバッファも読んでいたということが発覚して、その分まで速くなってしまい画面と音のタイミングがずれた件
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
ヘッダを増やして割といい感じに戻った(前が偶然いいタイミングになっていただけ)
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
gist か github に置けよという感じですが、この動画撮影時点の最終ソースコードは以下:
org 9f00h
VRAMTOP equ 0e200h
OPENTAPE equ 1a61h
READTAPE equ 1a70h
CLOSETAPE equ 1aaah
MAGIC equ 09ch
loadbin:
call OPENTAPE
ld hl,loadstart
ld bc,psgend-loadstart
call loadtape
ld hl,psgtop
ld de,datatop
call deexo
call START
ld hl,VRAMTOP
ld de,api1end-api1top
call loadrl
ld hl,loadbuf
ld bc,api21end-api21top
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,VRAMTOP+3072
ld de,api22end-api22top
call loadrl
ld hl,loadbuf
ld bc,api2aend-api2atop
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,loadbuf
ld bc,api31end-api31top
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,VRAMTOP+3072
ld de,api32end-api32top
call loadrl
ld hl,loadbuf
ld bc,api41end-api41top
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,VRAMTOP+3072
ld de,api42end-api42top
call loadrl
; call CLOSETAPE
; ret
jp CLOSETAPE
skipmark:
call READTAPE
cp 0c3h
jr nz,skipmark
skipmark1:
call READTAPE
cp 0c3h
jr z,skipmark1
ret
loadtape:
call skipmark
loadtape1:
call READTAPE
ld (hl),a
inc hl
dec bc
ld a,b
or c
jr nz,loadtape1
ret
loadrl:
call skipmark
loadrl1:
call READTAPE
dec de
cp MAGIC
jr z,loadrl2
ld (hl),a
inc hl
jr loadrl4
loadrl2:
call READTAPE
dec de
ld b,a
call READTAPE
dec de
loadrl3:
ld (hl),a
inc hl
djnz loadrl3
loadrl4:
ld a,d
or e
jr nz,loadrl1
ret
org_a000: ds 0a000h - org_a000 ;
org 0a000h
datatop:
include "psgdrv.asm"
org_a800: ds 0a800h - org_a800 ;
org 0a800h
OBJSAD:
include "TellYourWorld2.asm"
dataend:
exo_mapbasebits:
defs 156 ; exomizer tables for bits, baseL, baseH
loadstart:
;deexo
;input: hl=compressed data start
; de=uncompressed destination start
include "deexo.asm"
loadbuf:
psgtop:
include "psgdrvdata.asm"
psgend:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api1top:
include "api1-vram-rlcomp.asm"
api1end:
db 0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api21top:
include "api2-vram_1_exo.asm"
api21end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api22top:
include "api2-vram_2_rlcomp.asm"
api22end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api2atop:
include "api2a-vram_exo.asm"
api2aend:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api31top:
include "api3-vram_1_exo.asm"
api31end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api32top:
include "api3-vram_2_rlcomp.asm"
api32end:
db 0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api41top:
include "api4-vram_1_exo.asm"
api41end:
db 0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api42top:
include "api4-vram_2_rlcomp.asm"
api42end:
end
さらに動画撮影に向けて再度テープを準備:
デモのリビジョンが増えたのでカセットテープ在庫が枯渇した
— Izumi Tsutsui (@tsutsuii) 2016年7月16日
ストップビット 3ビットのテープ音と 7ビットの時のテープ音の識別ができるようになってきた
— Izumi Tsutsui (@tsutsuii) 2016年7月17日
こうしてようやくできあがったデモを動画として撮影して、ニコ動に投稿したのが前回のエントリでも書いた以下のツイート。
【パピコン編】タイニーあぴミクさんにつま先立ちで迫られたかった。 (5:15) https://t.co/169BgcvCV8 #sm29263535
— Izumi Tsutsui (@tsutsuii) 2016年7月17日
PC-6001で Z80プログラミング楽しいです^q^
PC-6001 を展示した OSC京都会場でも以下のようにいい感じでした。
BGM演奏のPSGドライバと曲データが 3295バイトあって、そのままテープからロードすると44秒かかるので、データ圧縮して1846バイトにして25秒でロードして演奏開始できるようにした話
— Izumi Tsutsui (@tsutsuii) 2016年8月2日
ロード時間が25秒だと展示ブツについてざっと説明した後に「あと10秒くらいで音楽がなると同時に画面が出始めるはずです」という流れの解説が可能
— Izumi Tsutsui (@tsutsuii) 2016年8月2日
Step 6: 16KB 対応
Tell Your World オルゴール曲の採譜から始まって、本題だったはずの OSC京都の準備をそっちのけにして作ってしまったタイニーあぴミクさん動画ですが、撮影後も1つ気になっていました。
タイニーあぴミクさんデモのプログラムはがんばればメモリ増設していない 16k の PC-6001 でも動くんじゃないかと思うけれど、さすがに起動時の 7100 bytes free の表示のためだけにそこまでがんばるガッツはなかった
— Izumi Tsutsui (@tsutsuii) 2016年7月17日
もともとあぴミクさんでもは Tinyみずいろデモの構成をベースにしていたので 32KB 前提の 9F00h からバイナリを置いていましたが、これには特に意図があったわけではありません。むしろ、5枚の画像をオンメモリに置いていた Tinyみずいろとは違い、あぴミクさんデモはPSGドライバと曲データだけがオンメモリで、あとは Exomizerの展開前の圧縮データを読むバッファ領域さえ確保できればいいということになります。
ということで、「ガッツはなかった」などと言いつつもやはり気になってしまったので調べてみると、以下のような感じです。
タイニーあぴミクさんデモが拡張RAMなしの 16KBな PC-6001 状態で動くかどうかやってみたけど 118バイトほど足りない(´・ω・`)
— Izumi Tsutsui (@tsutsuii) 2016年7月18日
従来 9F00h
から配置していた各バイナリとバッファについて、 16KB対応のため BASICプログラムのサイズも考慮して CA00h
から配置してみたのですが、「アニメっぽくしよう!」と安直な考えで全画面 Exomizer 圧縮としたこのあぴミクさん
のデータが圧縮後で 2076バイトあり、これを読み出すためのバッファがギリギリ E000h からの VRAM領域にはみ出してしまう、という状態でした。いろいろと無駄っぽいすき間を削ってみたのですが、100バイト近くを空けるのは難しい状況でした。
よっしゅさんの P6 PSG音源ドライバは 256バイト境界に置かないとダメなのだろうか……
— Izumi Tsutsui (@tsutsuii) 2016年7月18日
PSG曲データの方は256バイト境界でなくてもいいんかな
— Izumi Tsutsui (@tsutsuii) 2016年7月18日
が、よくよく考えると Exomizer の展開用バッファの 156バイトはどこにとっても問題ない、ということで次のように解決しました。
ごちゃごちゃいじってタイニーあぴミクさんデモ on PC-6001(増設なし16k) も完走。いろいろ凡ミスしてハマったけど、Exomizer の展開バッファ156バイトを非表示状態のテキストVRAMに取るという反則技でいけました pic.twitter.com/O0ZNd5GAhU
— Izumi Tsutsui (@tsutsuii) 2016年7月18日
ずっとPC-6001と戯れていて OSC京都の準備をまったくせずに終わった3連休であった(完)
— Izumi Tsutsui (@tsutsuii) 2016年7月18日
微妙にくどい感じですが、この 16KB対応のコードはこちら:
org 0CA00h
VRAMTOP equ 0e200h
OPENTAPE equ 1a61h
READTAPE equ 1a70h
CLOSETAPE equ 1aaah
MAGIC equ 09ch
loadbin:
call OPENTAPE
ld hl,loadstart
ld bc,psgend-loadstart
call loadtape
ld hl,psgtop
ld de,datatop
call deexo
call START
ld hl,VRAMTOP
ld de,api1end-api1top
call loadrl
ld hl,loadbuf
ld bc,api21end-api21top
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,VRAMTOP+3072
ld de,api22end-api22top
call loadrl
ld hl,loadbuf
ld bc,api2aend-api2atop
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,loadbuf
ld bc,api31end-api31top
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,VRAMTOP+3072
ld de,api32end-api32top
call loadrl
ld hl,loadbuf
ld bc,api41end-api41top
call loadtape
ld hl,loadbuf
ld de,VRAMTOP
call deexo
ld hl,VRAMTOP+3072
ld de,api42end-api42top
call loadrl
; call CLOSETAPE
; ret
jp CLOSETAPE
skipmark:
call READTAPE
cp 0c3h
jr nz,skipmark
skipmark1:
call READTAPE
cp 0c3h
jr z,skipmark1
ret
loadtape:
call skipmark
loadtape1:
call READTAPE
ld (hl),a
inc hl
dec bc
ld a,b
or c
jr nz,loadtape1
ret
loadrl:
call skipmark
loadrl1:
call READTAPE
dec de
cp MAGIC
jr z,loadrl2
ld (hl),a
inc hl
jr loadrl4
loadrl2:
call READTAPE
dec de
ld b,a
call READTAPE
dec de
loadrl3:
ld (hl),a
inc hl
djnz loadrl3
loadrl4:
ld a,d
or e
jr nz,loadrl1
ret
org_cb00: ds 0cb00h - org_cb00 ;
org 0cb00h
datatop:
include "psgdrv.asm"
;org_a800: ds 0a800h - org_a800 ;
; org 0a800h
OBJSAD:
include "TellYourWorld2.asm"
dataend:
exo_mapbasebits: equ 0C200H
; defs 156 ; exomizer tables for bits, baseL, baseH
loadstart:
;deexo
;input: hl=compressed data start
; de=uncompressed destination start
include "deexo.asm"
loadbuf:
psgtop:
include "psgdrvdata16.asm"
psgend:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api1top:
include "api1-vram-rlcomp.asm"
api1end:
db 0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api21top:
include "api2-vram_1_exo.asm"
api21end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api22top:
include "api2-vram_2_rlcomp.asm"
api22end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api2atop:
include "api2a-vram_exo.asm"
api2aend:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api31top:
include "api3-vram_1_exo.asm"
api31end:
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api32top:
include "api3-vram_2_rlcomp.asm"
api32end:
db 0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api41top:
include "api4-vram_1_exo.asm"
api41end:
db 0,0,0,0,0,0,0,0
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h
db 0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,0c3h,000h
api42top:
include "api4-vram_2_rlcomp.asm"
api42end:
end
変更内容としては以下だけですね。
- 各
ORG
文のアドレスを修正 - PSG曲データを PSGドライバ直後に配置
- Exomizer 展開用バッファについて
EQU
文でC200h
の VRAM アドレスを指定
実機確認は OSC京都の荷解きの関係でしばらくサボっていたのですが、以下のように問題ありませんでした。
いまさらながら PC-6001 16K 実機で動作確認 pic.twitter.com/g4Wnc1zfSy
— Izumi Tsutsui (@tsutsuii) 2016年8月31日
Step 7: PSGドライバ+曲データ ストップビット短縮
このネタ自体は、16KB対応の作業する直前に以下のようにつぶやいていたものです。
1つのテープイメージで途中からストップビット長を増やせないか(=PSGドライバをスタートする前までは最速で読んで、演奏開始後に読む部分からストップビットを増やす)というヨコシマなことも考えたけれど、さすがに挫折した
— Izumi Tsutsui (@tsutsuii) 2016年7月18日
このときは「原理上は難しくないはず」と思っていたものの、作った wav ファイルを切り貼りするツールを探して作業手順を試行錯誤する時間のほうが大変であろうという判断で見送っていました。
前述の 16KB対応の実機確認をしたさらに次の週に重い腰を上げて試行錯誤してみたところ、以下のようにあっさり動きました。
メモ:
— Izumi Tsutsui (@tsutsuii) 2016年9月5日
P6初代実機でもストップビット2ビット設定で作成した wav でテープロードは可能(わりとどうでもいい)
「途中まではストップビット2ビットで、途中からストップビット7ビットになる」というエミュレータ泣かせなテープ wav を作っても意図通りロードしてくれる(作るのが超めんどいけど)
— Izumi Tsutsui (@tsutsuii) 2016年9月5日
N60-BASIC の標準ではストップビット 3ビットということらしいのですが、少なくとも P6初代実機ではストップビット 2ビットでも CLOAD および ROM内ルーチン1A70h
のテープ1バイト読み出しとも問題はないようです。
作業手順についてはちゃんとツイートをしていませんでしたが、ここでの wav ファイル操作は Windows 用の Sound Engine Free というツールを使いました。作成手順としてはざっと以下のような感じです。
- ローダープログラムで読み込む P6データについて、 「Exomizer展開ルーチン+PSGドライバと曲の圧縮データ」までの部分とそれ以降の 2つに分割する
- PSG演奏をしない前半のデータについては、「なんでもピーガー」で ストップビット 2ビット の設定で wavファイルを作成する
- PSG演奏をしながらのストリーミングロードになる後半のデータについてはストップビット 7ビット、かつ、「空送り」「ヘッダマーク」をそれぞれ 0秒にして wavファイルを作成する
- Sound Engine Free で 2つの wavファイルの不要な無音部分を削除してから結合する
「無音部分の削除」というのがやや乱暴な操作で、矩形波のどの部分でぶった切られるのかが心配でしたが、特に問題なくロードできているようでした。なんでもピーガーとしては「矩形波を出し切った後無音部分はレベル0のセンターに戻している」ということだと思います。
で、ストップビットを削ることでどれだけロードが速くなるかというと、
P6のテープロード wav について、もともとストップビット7ビット設定のまま 2048バイト読んでいた部分をストップビット2ビット設定の音に差し替えると、ロード時間が 8.5秒短くなるという計算で、実際にそれくらい速くなるのだけれど、うーむ
— Izumi Tsutsui (@tsutsuii) 2016年9月7日
というわけで、動画撮影前にここまで頑張っておけばよかったかも? というくらいには短縮される結果になりました。
まとめ
読む側のことは二の次にして自己満足最優先で書いたので予想通り無駄にくどく長くなりましたが、前回のエントリ と合わせ、一通りまとめたいと思っていたことは書けたと思います。
むしろ、実際にデモプログラム作成にかかった時間(約1日半)よりこのエントリを書く時間のほうがかかっているという本末転倒なところもありますが、「これをやったら面白そうだ」と思うことが何よりのモチベーションである、といったところでしょうか。
一方で、1年以上経過した後でもこれだけの内容のまとめが書けたのは git のログと Twitter のつぶやきのおかげで、そういう意味では当初から狙い通りに記録を残せていた、とも言えます。
あぴミクさん以外にもまだまとめられていない作業がいろいろありますが、そのあたりも機会を見てぼちぼち書けたらいいなと思います。
ブログとかに書こうと思って書けてないネタ
— Izumi Tsutsui (@tsutsuii) 2017年8月17日
・PC-6001 内蔵32KB改造
・タイニーあぴミクさんデモ実装
・PSGドライバLUNA移植実装
・OSC京都デモ実装詳細
実はあまりないのか ネタすら忘れているのか