Making「音声出力」その2

Home PC-6001mkII Program etc

 このページでは、音声出力のながら処理研究の過程を書いています。

Making「音声出力」 Top


2010/05/xx:解析その3
 ここまで来たら、音声を鳴らしながら他の処理もしたいところだ。

 SRでは、この芸当が出来るらしい。話によると、音声出力専用(?)の割り込みがあるとの事。残念ながら、mkIIではそんな割り込みはな...まてよ通常の割り込みならあるな。
 これをうまい具合に利用すれば音声出力の「ながら処理」が出来ないだろうか?

 そもそも、なぜmkIIでは音声出力時にほぼ割り込みを止めているのだろうか?憶測ではあるが、音声合成ROMからのステータスを受け取るタイミングがそれだけシビアなのだろう。

 という事で、以下のような簡単なプログラムをmkII実機で実行し、各段階のステータスの変化(ポートE0Hからの入力の変化)を見る。
	LD HL, 4400H
	LD A, 00H

	DI
	OUT 0E2H, A	;音の速さに対応した値

;ポートE3H,ポートE0Hへの出力処理はここで行う

	LD B, 00H
L01:
		IN A, 0E0H
		LD (HL), A
		INC L
	DJNZ L01:
	EI
	RET

 おさらいをすると、ポートへの出力は、E2H → E3H → E0H → E0H ...(以下7バイト or 1バイトのグループでポートE0Hへの出力が続く) の順となる。

 まずはポートE2Hまで出力して試してみる。...ステータスは[00H]のまま変化なし。あれ、考え方を外したかな?まあいいや次!

 次は、ポートE3Hへの出力後の状態を見る。結果は、最初の30回ほどが[A0H]、そこから先が[E0H]となった。ただ、マシン語処理の終了後にBASICから「?INP(&hE0)」で確かめると48、すなわち[30H]が帰ってきた。どうやら、少なくとも[A0H][E0H][30H]の3段階に変化しているようだ。

 前の結果と照らし合わせると、値の意味合いはこんな感じだろうか?
  • [00H]:初期状態
  • [30H]:エラー(bit4=1)
  • [A0H]:処理中(bit6=0)
  • [E0H]:次のデータを受付可能?
先ほどのマシン語に少しウェイトを置くか。
		LD (HL), A
		LD C, xxH	;ウェイトループ回数
L02:
		DEC C
		JR NZ, L02:
		INC L

 ウェイトのループ回数を5回にして何度か試すと、こんな感じになった。
  • [A0H] x 2, [E0H] x 14, [30H] x 残り
  • [A0H] x 2, [E0H] x 45, [30H] x 残り
  • [A0H] x 2, [E0H] x 56, [30H] x 残り
  • [A0H] x 8, [E0H] x 72, [30H] x 残り
  • [A0H] x 10, [E0H] x 32, [30H] x 残り
 さらに、ステータスが[E0H]になるのを待ってポートE0Hに対し連続でデータを送った後のステータスの変化を見る。30回ほど試した結果は以下の通り。
ポートE0Hへの
出力バイト数
[A0H]
回数
[E0H]
回数
[30H]
回数
備考
10〜117〜72残り 
20〜128〜71残り 
30〜112〜70残り 
40〜112〜69残り 
50〜149〜68残り 
60〜127〜69残り 
725〜12045〜138残り[A0H]+[E0H]:71〜166
80〜145〜103残り 
1456〜15145〜140残り[A0H]+[E0H]:196〜219
2167〜15145〜129残り[A0H]+[E0H]:196〜219
2856〜15145〜140残り[A0H]+[E0H]:196〜218
3556〜15146〜141残り[A0H]+[E0H]:196〜203
4257〜15145〜140残り[A0H]+[E0H]:196〜197
430〜145〜140残り 

 1〜6バイトでは、[E0H]は概ね50〜70程度なのだが、たまに12〜28となる。
 7バイトデータの区切りでは、[A0H]の時間が長くなる。この時、[A0H][E0H]の値にかなりのムラがあるが、[A0H]と[E0H]を足した数は割と安定している。

 測定の方法や測定に使用する音声データで大きく変わる可能性もあるが、とりあえずこの結果を参考に処理を考えることにしよう。

2010/06/xx:割り込み処理その1
 さて、割込み処理について考える。

 前の結果から、7バイト単位の区切りでは受付可能状態(ステータスがE0H)が最短で45カウント分とわかる。つまり、これ以上時間が空くと音声出力に失敗する可能性があるという事だ。

 さて、この45カウント分とは、いったいどれくらいの時間なのか?計算してみよう。
Z80ステート数ステート数小計
 LD B, 00H 8 8
L01: - 29
  IN A, 0E0H 13
  LD (HL), A 8
  LD C, 05H 8
L02: - 85
  DEC C 5
  JR NZ, L02:13 or 8
  INC L 5 19
 DJNZ L01: 14( or 9)

 えーと、つまり...
 8 + (29 + 85 + 19) * 45 = 5993ステップ。
 CPUがおおよそ2MHzとすると、1ステートは0.0005ms。
 という事で、5993 * 0.0005 ≒ 3.0ms

 一方、mkIIの通常の割り込み処理間隔はマニュアルより2/975秒という事で、約2.05ms。一応3.0msには収まっているが、何らかの原因で割り込みが飛ばされるとアウトだ。

 という事で、割り込み回数を倍に増やす事にする。これなら、割込み処理が1回飛ばされてもなんとかやってくれるだろう。

2010/06/xx:割り込み処理その2
 さて、マシン語の処理を作っていくか。

 マシン語のプログラム構成は、以下の3種類で行くことにする。
項番処理名処理内容
1初期化処理 BASICよりEXECで呼び出される。
・割込み回数を倍(ポートF6Hに01Hを出力)にする
・BIOSの割込み処理に音声出力処理をねじ込む
・USR()関数の飛び先を設定
・ステータスの初期化
2USR()処理 BASICからのUSR()実行で呼び出される。
・音声データの開始アドレスをワークに設定
3音声出力処理 割込み処理から呼び出される。
・ステータスに従い、ポートへの出力を行う

 ステータスは、USR()関数から別の音声出力要求があったら現在の音声出力を中断できるようにしたいので、こんな所かな?
ステータス状態音声出力処理の処理内容
0初期状態何もしない
1〜6ポートE0H出力1バイト分のデータを取り出し、ポートE0Hに出力
ステータスはカウントアップ
7ポートE0H出力ステータス1〜6に加え以下を実行
次の音声が控えていたら、音声データの番地を途中終了用の番地に変更
音声データが終了の場合、ステータスを以下に変更
・次の音声が控えていた場合:255
 (次の音声データの番地をセットする)
・それ以外:0
255音声開始ポートE2H, E3Hへの出力
処理後ステータスを1にする。

 初期化処理ではステータスを0にする。
 USR()処理では、ステータス0なら255に変更、それ以外はステータス変更なしで次の音声データの番地を控えておく。

 とまあ、こんな所だろうか。

2010/06/30:ながら音声 V1.0
 ここまで出来れば、あともう少し。

 サンプルの音声データを用意し、マシン語を呼び出すBASICプログラムを作成する。カーソルキーの上下左右で4種類の音声を出力するようにしようかな?

 お手軽に、マシン語プログラムと音声データを読み込むようにしてっと、こんな所かな。いざ実行!
 PC-6001VW上では、概ねうまく動いていた。ただ、なぜか音声が少し遅く再生される。また、画面をスクロールするとたまに音声出力がされなくなる問題が残る。

 実機でも試してみるか。簡易的にテープ用のイメージを作成して、mkII実機にロードする。いざ実行!
 ...あれ?何も鳴らない。暴走しているわけでもなさそうだ。うーん、なんだろう。

 デバック用に途中経過を出力してみると、はじめから3〜4バイトの出力まではうまく動いているようだ。やっぱりエミュよりもタイミングがシビアなのかな。

 結局、実機ではうまく音声出力がされないまま、ながら音声を公開した。
まあ、最初にやろうと思っていた事は達成したので、ここまでにするか。

 と、この時は思っていた。

2011/01/09:改善
 前回の公開から5ヶ月ちょっと経った頃、突如改善を思い立つ。

 前回は割込み毎に1バイトずつ出力していたのだが、1割込みで7バイトを一気に出力するように変更する。
 という文章を書いて思ったが、せっかく、前に 7バイトデータの区切りで時間的余裕が大きい事がわかったのになぜ前回は1割り込み1バイト出力にしたんだろう...?

 はじめは、PC-6001VWで確認...特に問題なさそうだ。前回よりもスムーズにしゃべるようになった気がする。

 さて、次はmkII実機だ...おぉ、しゃべった。
 これで成功したと思ったのもつかの間、なぜか実機では続けて2回目の音声出力がされない。調べてみると、初回の音声出力後の内部ステータスがエミュと実機で異なっていた。

 違いの元を辿ると、音声出力後のポートE0Hの出力結果がエミュと実機で以下のように異なっていた。
 任意語の音声出力(TALK "F2 A." 等)を実行した後のポートE0Hの出力
  エミュ:00H、実機:30H
 固定語の音声出力(TALK "F21." 等)を実行した後のポートE0Hの出力
  エミュ:00H、実機:00H

 エラーになると音声出力処理を飛ばしているので実機ではうまく動かないことがわかったが、そもそもなぜ実機ではエラーになるんだろう?
 固定語出力と任意語出力の処理の違いがわかれば音声出力の真の終了条件もわかるのだろうが、固定語出力の処理を追うスキルはない。ならば別の手段で回避すべきだろう。

 まず、ポートE0Hの状態にかかわらず音声データの終わりまで来たら内部ステータスを00Hに戻すように変更した。あともう一つ、7バイトデータ出力前のポートE0Hのエラーチェックを外した。エラーチェックは7バイトデータ出力のループ内でも行っているので、大きな問題は出ないだろう、多分。

 エミュで動作を確認後、実機での動作を試みる。
 ...いけてる、かな?うまくいってそうだ。

 こうして、V1.1が完成した。


その1 Making「音声出力」 その3