Making「音声出力」その1

Home PC-6001mkII Program etc

 このページでは、音声データの解析から音声出力までの過程を書いています。

Making「音声出力」 Top


2008/xx/xx:解析その1
 はじめの目標としては、やはり音声を出すところだろう。とはいえ、さてどこから手をつければいいのやら...。

 この時点で、音声出力関連で手元にある資料といえば、こんなところだった。
  • 「新・マシン語初級講座」の記事(Oh!PC 1985年1月号らしい)
  • CISCさん作成の「μPD7752風味 音声合成エンジン」のプログラム
  • PC-6001VW(「μPD7752風味 音声合成エンジン」搭載)
 「新・マシン語初級講座」より、音声出力にはE0H, E2H, E3Hのポートが絡んでいるようだ。という事で、TALK処理のBIOSルーチンでこのポートに出力している部分をPC-6001VWのモニタモードで探る...4D4AHあたりからか。

 4D4AHからは、概ね以下のような処理になっている。
  1. ポートE2Hに値を出力
  2. ポートE3Hに値を出力
  3. 割り込み禁止
  4. 以下7回ループ
  5.  ポートE0Hの入力bit4=1だと終了(多分エラー)
  6.  ポートE0Hの入力bit6=0だと待つ
     → 待ちくたびれる(待ちカウンタ65535回)と終了(多分エラー)
  7.  音声データサイズカウントダウン、0だと終了
  8.  ポートE3Hに値を出力
  9. 割り込み禁止の解除
  10. 3.に移動
 なるほど、mkIIでは音声出力終了までほぼ割り込みなしで処理をするので、音声出力中は他の処理が止まるのか。

 さて「新・マシン語初級講座」の情報とあわせると、はじめにスピード(ポートE2Hへの出力)とモード(ポートE3Hへの出力)を行った後、音声データを7バイト単位で送り続けると良いのかな?

 音声出力の中核部分はなんとなくわかったとして、お膳立てと後始末の処理がさっぱりわからない。「新・マシン語初級講座」にあるPC-6601用のサンプルプログラムとうまい具合に合わせればいいのだろうか...。

2012/04/08:今更ながら...項番6は、bit6=1 → bit6=0に修正

2008/xx/xx:解析その2
 手始めに、「新・マシン語初級講座」で直前に出力した音声を出力するサンプルプログラムを変えてみよう。

 まず記事に載っているアドレスをmkII用に置き換えてみる。こんな感じなのかな?
項目mkII66
スピード0126H02D3H
モード0127H02D4H
男声(FFH)/女声(00H)0128H02D5H
SP(保存値)0129H-012AH02D6H-02D7H
HL(保存値)012BH-012CH?
IX(保存値)012DH-012EH02D8H-02D9H
IY(保存値)012FH-0130H02DAH-02DBH
(FE64H)の値?02DCH
割り込みベクトルI値?02DDH
データ長01B3H-01B4H03E8-03E9H?
7バイトごとのデータ01B5-03EAH?-
BIOSルーチン音声出力部分?421AH-

 この情報を元に、マシン語部分を変更してみる。音声合成ROMの選択とIレジスタへの代入はサンプルの処理を流用し、音声出力部分はBIOSの処理から抜き出してつなげてみる。

 おぉ、適当につなげた割には音声出力には成功しているようだ。まずは第一歩のクリアだ。

2008/05/xx〜2009/09/xx:お蔵入りと蔵出し
 さて、次の段階は、任意のアドレスにある音声データの再生だ。

 BASICのUSR()関数で指定したアドレスを音声の開始番地として、音声出力を行うようプログラムを変更する。

 ついでに、IX, IY, SPレジスタへの代入部分と、音声合成ROMを選択する部分を省く。
 今回の処理では一度も使っていないので問題ないだろう、と軽い気持ちで変更したのだが、この事を境に音声出力に失敗するようになった。さっぱり原因がわからない。

 こうして、なんとなくお蔵入りの状態となった。

 ...そして1年以上経った日、きっかけはよく覚えていないが、ともかくまた再開する事となった。

 憶測ではあるが、音声出力の処理が途中で止まったのは割り込みが悪さをしたのだろう。という事で、音声出力処理全体を割り込み禁止にする。
 少し強引な方法ではあるが、ようやく音声が鳴り出した。とりあえず今回はこれで行くことにしよう。

 割り込みに関して、Iレジスタへの値代入も不要とわかったのは、ここからさらに1年と少し後の話となる。

2009/10/0x:7バイトデータ
 「μPD7752風味 音声合成エンジン」のソースプログラムを、繁々と眺める。

 実はC言語は細かい文法がよくわからないのだが、それなりには読む事ができる。このソースによると、7バイトデータの内訳はこんな感じのようだ。
バイト位置ビット内容
1バイト目 bit7-3不明
bit2"F","B"パラメータ増分値倍増フラグ
bit1不明
bit0インパルス[0], ノイズ[1]
2〜6バイト目 bit7-3"F"パラメータ増分値[-16〜15]
bit2-0"B"パラメータ増分値[-4〜3]
7バイト目 bit7-4Amp[0-15]
bit3不明
bit2インパルス+ノイズフラグ(bit3かも?)
bit2-0Pitch増分値[-4〜3]

 "F", "B"パラメータは5つずつあり、Pitchと共に前回値からの差分データとなっているように見える。

 という事は...、連続する7バイトデータ2つの差分データを足し合わせて7バイトデータ1つにうまくまとめれば、倍速で音声出力できるのでは?よし試してみよう。

2009/09/xx〜21:寿限無
 今回やりたかった事は、長時間の音声出力だ。音声の題材として、最も長い名前「寿限無」を選んでみる。

 当初は長い音声を全て一つなぎにしたかったのだが、肝心の音声データのつなぎ方がわからない。音声データのほとんどが相対指定なので、ここをうまく調整すれば行けるかも知れないけど...。

 という事で、今回は妥協して細切れで音声データを取得しておいて、BASICから連続で出力することにする。こうして、「寿限無」が完成した。

 まだ自在に音声を使うには程遠い状態だが、とりあえずの第一歩を踏み出したと思う。

2009/10/0x:生麦生米生卵
 さて、倍速音声出力を目指すか。

 差分データ(F1〜5, B1〜5, Pitch)は値の足し合わせ、絶対値データ(Amp)は平均値をとる事にする。

 ただ差分データの方だが、当然ながら2つ分を足すと最大で2倍の値になり、値の範囲に収まらないケースが出てくる。この時は、"F","B"パラメータ増分値倍増フラグで対応し、さらにあふれる場合はあふれた分を次に持ち越しにする。

 まあ、大まかな変換ルールはこんな所か。この変換を手作業ではとてもやってられないので、HSPで変換プログラムを作成する。
 さあ、うまく変換できるかな?「東京特許許可局」の音声をTALK文で実行したときの音声データで試してみる...失敗。

 おかしい、よくよく見ると音声データ部分が7で割り切れないぞ?どうやら、「っ」を鳴らす部分で7バイトのフォーマットになっていないようだ。
 しかたない、別の音声に変えるか。mkIIのTALK文で一度に鳴らせる15音節でなにか良い早口言葉があったかな?...お、これはどうだ「生麦生米生卵」。

 HSPで変換した倍速(であろう)音声データを使い、「寿限無」の音声出力ルーチンで鳴らしてみる。おぉ!速い速い!。という事で、マシン語を呼び出すBASIC部分を作成し、後ろにマシン語ルーチンをつなげたテープイメージを作成する。

 さて実機ではちゃんと再生できるんだろうか?と心配はしたが、無事早口で再生され一安心。こうして、「生麦生米生卵」が完成した。

 7バイトデータでない音声データ形式は、後日明らかになる。

2009/10/1x:非7バイトの謎
 促音が入ったときに音声データが7バイトの倍数にならない事について、Bernieさんの掲示板に書き込んだ。

 Bernieさんの回答によると、7バイトデータの1バイト目bit7〜3が、同じデータの繰り返し回数との事。一種の圧縮フォーマットなのか。
 29 01 02 03 04 05 D0 D7 D0 D0 D0
 だと、1バイト目のbit7〜3が00101B、つまり10進数で5なので5回繰り返しで以下のように展開されるらしい。
 09 01 02 03 04 05 D0
 09 00 00 00 00 00 D7
 09 00 00 00 00 00 D0
 09 00 00 00 00 00 D0
 09 00 00 00 00 00 D0

 という事で、またいつかこれを考慮して作っていこうと思う。

 7バイトデータの内訳も少し更新しておくとするか。
バイト位置ビット内容
1バイト目 bit7-3(1〜6バイト目の)繰り返し回数
bit2"F","B"パラメータ増分値倍増フラグ
bit1不明
bit0インパルス[0], ノイズ[1]
2〜6バイト目 bit7-3"F"パラメータ増分値[-16〜15]
bit2-0"B"パラメータ増分値[-4〜3]
7バイト目 bit7-4Amp[0-15]
bit3不明
bit2インパルス+ノイズフラグ(bit3かも?)
bit2-0Pitch増分値[-4〜3]



2010/01/xx:音階
 さて、今度は...音程をいじってみるか。

 「新・マシン語初級講座」の記事によると、7バイトデータの7バイト目下位3ビット(Pitch)が音程と絡んでいるようだ。つまり、音声の音量を上げる前にPitch値を調整すれば音階を変化させる事ができるのだろう。

 実際、mkIIのTALK文で出力される音声データには、Pitchの調整と思われる領域がある。たとえば、TALK "F2 a."ならばこんな感じになる。
 01B5: 0D 07 4B FE FD 0D 02 Amp=0(無声)
 01BC: 09 07 03 00 F8 06 00
 01C3: 09 00 00 00 00 00 00
 01CA: 09 00 00 00 00 00 00 おそらく
 01D1: 09 00 00 00 00 00 00 この辺りで
 01D8: 09 00 00 00 00 00 00 Pitchを
 01DF: 09 00 00 00 00 00 00 調整
 01E6: 09 00 00 00 00 00 00 している
 01ED: 09 00 00 00 00 00 00
 01F4: 09 00 00 00 00 00 00
 01FB: 09 00 00 00 00 00 80 Amp=8(ここから音声出力)
 0202: 09 00 F8 00 00 00 B2
  :
 ようするに、このPitch調整部分でPitchを変更するプログラムを作れば、音声データを使い回しして音程だけを変えられる、はず。というわけでやってみよう!

 BASICのUSR(x)関数の引数に入力した値を基にPitchを変更し、その後安静出力を行う処理をつなげる。とりあえず±3が8回という事で、引数xの値は-24〜24に制限しておこう。

 いくつか単体でUSR(x)を実行すると、音程がついた音声が出力された。あとは、この音階を使ったデモだ。なににしようかな?少し考えた結果、「びっくり交響曲」にした。

 若干音階が合っていない部分もあるし、入力値が人間に優しくない部分もあるが、とりあえず出来た。よし、HPにあげちゃえ!
 こうして、「音階」が完成した。


Making「音声出力」 その2