TALK文のできるまで

PC-6001mkII/6601用に互換BASICを書く上で苦労した/していることの一つが、音声合成命令であるTALK文[1]の実装です。P6シリーズにはμPD7752という音声合成LSIが搭載されているのですが、あまり詳しい資料がないようで、わからない部分も結構あります。とは言え6001mkII/6601の最大の売りの一つである音声合成機能が使えないのは寂しいので、できる範囲で動作について調べて推測し、何とかTALK文を書いてみました。そう喋っていると思って聞けばそういう風に聞こえなくもない、という程度の品質なのですが、せっかくなのでこれまでの作業内容をまとめておきます。


目次


音声合成方式

まず、μPD7752の音声合成方式についてざっくりと。人間の発声の仕組みは、大まかに言うと

という風になっています。この共鳴の周波数ピークをフォルマント (あるいはホルマント) と呼び、この仕組みを模擬した音声合成方式をフォルマント合成と呼びます。パルス周期を決めるパラメータで音程を決め、共鳴周波数を決めるパラメータで「あ」とか「い」とかの音種を決めるといった具合になります。

μPD7752はこのフォルマント合成方式をとっています。フォルマントパラメータとして5個の周波数値を選ぶことができ、またそれぞれの強度も設定できます。実装としては、10ビットの係数を使った2次のIIRフィルタが直列に5段用意されているようです[2]


送出データ構造

次に、6001mkII/6601で音声合成LSIを操作する時に、どういった手順でどういったデータを送るのかについて簡単にまとめます。詳しくは参考文献[2],[3]を参照してください。解説しているサイトとしては、KAWさんの『古くて新しいモノ Making「音声出力」』や、えすびさんの『P6つくろうブログ P6解析:音声合成』が大変詳しくわかりやすいです。

手順は以下の通りです。

  1. ポートE2hに発声速度を表す値を出力する (表1)
  2. ポートE3hにコマンドを表す値を出力する (表2)
  3. ポートE0hを読んでステータスを確認しながら、ポートE0hに音声データを出力する (表3、表4)

表1が発声速度を設定するのに用いる出力値です。参考文献[3]の表はどうも不正確なようです。

表1: 発声速度の設定
TALK文での値 出力する値
空白 0
1 2
2 0
3 1
4 6
5 4
6 5

表2がコマンド出力値です。発声停止コマンドについては、必ずしも送らなくても良いようです。

表2: コマンド
内容 出力する値
固定語の出力 0〜4 (μPD7752自体の機能としては0〜3Fhに対応)
外部からのデータを使っての出力 FEh
発声停止 FFh

TALK文で自由な文を喋らせる場合は、μPD7752の外部からデータを送る形になるので、コマンドFEhを使います。この場合、ステータスのREQビットが1になるのを待って、データを1バイトずつ出力します。表3がステータス、表4が送るデータの形式です。どちらもポートE0hを使います。出力データは基本的には7バイトで1セットです。

表3: ステータスレジスタ
bit 信号 内容
bit7 BSY 発声中
bit6 REQ 外部データ要求
bit5 INT/EXT 固定語使用なら0、外部データ使用なら1
bit4 ERR エラー
表4: 出力データ
順番 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
1 TIME QMAG S.I V/UV
2 f1 b1
3 f2 b2
4 f3 b3
5 f4 b4
6 f5 b5
7〜 AMP FV p

表4のパラメータの意味は以下の通りです。


音源振幅情報

文献で調べられた情報はここまでです。それぞれのパラメータの具体的な値、例えば以下の情報については見つけることができませんでした。

これらについて、PC-6601実機のオーディオ出力波形を元に調べてみました。μPD7752はデジタル出力ピンがあるのでそこを見られればより正確なのですが、機材が無いためやっていません。なので、アナログノイズの他、μPD7752のアナログ出力ピンからオーディオ端子までの回路や、波形を取り込んだPC側の回路の影響も受けた結果であることをあらかじめお断りしておきます。

まずはわかりやすそうな振幅情報 (AMP) についてです。以下の7バイトデータ列を繰り返し与え、音声波形を記録しました。

09h 00h 00h 00h 00h 00h **h

**の部分を00h〜f0hの16段階に変化させて波形を比較したのが図1です。本体ボリュームつまみによって音量は変わるので、縦軸 (絶対値) は任意単位ということで。

振幅の変化
図1: 振幅の変化

AMP=0〜15に対応して、段階的に振幅が変わっているのがわかると思います。この波形の最大値-最小値の振幅が、AMP値に対してどのように変化しているのかをプロットしたのが図2です。

振幅の変化
図2: 振幅の変化

AMPが小さい部分についてはちょっと怪しいですが、全体的にはおおむね指数関数的に変化しているようです。AMP=0はほぼ無音で、見えているのはアナログノイズのみのようです。

ちなみに、参考文献[2]には「音源振幅情報(AMP)」とは別に、「音源振幅差分(amp)」についての記述があります。これは恐らく内部的なパラメータのことで、外部から指定できるように見える図もありますがたぶん誤記ではないかと思います。


ピッチ情報

続いてピッチ情報です。振幅情報と違って差分で与えるため、例えばp=+3を10回繰り返すと累積ピッチ=+30となり、この累積値に対応した音程が鳴ります。ポートE2hで速度設定をするとリセットされます。ピッチを与えた後で音を出し、パルス間の時間の平均を取って周期を求めました。横軸を累積ピッチとしてプロットしたのが図3です。

ピッチの変化
図3: ピッチの変化

参考文献[2]には、ピッチに関して、「サンプリング周期 (100μs) のP倍の時間ごとにインパルスをディジタルフィルタに入力します」という記述があります。デフォルトのピッチ+累積ピッチの合計である内部パラメータを (大文字の) Pと呼んでいるのですね。実機による測定ではデフォルトの周期は約3.0msだったので (図1参照)、Pの初期値は30ということになります。

少し細かい話をすると、このPは1〜128の範囲で循環するようで、累積ピッチ=+128とすると元の高さの音が鳴り、累積ピッチ=+98 (P=128) で周期は最長となりました。累積ピッチ=-29 (P=1) とすると最短周期になりそうなのですが、実際には音の鳴らし始めと鳴らし終わりにポツッと音が鳴るだけで、その間は無音でした。パルス発生周期とサンプリング周期が同じになってしまうと無変化のため無音になってしまうのかもしれません。

更に細かい話をすると、図3に示したように、比例係数は0.1msぴったりではなく、約0.1006msでした。少しの差とは言え、先の参考文献の記述と有意に異なる結果となったのですが、これは恐らく入力クロックが原因ではないかと思います。μPD7752は3.6MHzのクロックを想定して作られているらしいのですが、P6シリーズでは約3.58MHzのクロックを入力しています[4]。その比率の分だけサンプリング周期が間延びしていると考えると計算が合うようです。音程にして約10セントの差になります。


フォルマント情報

次に、フォルマントパラメータの影響を見ます。ピッチと同じでこれも差分で与えますが、参考文献[3]では差分パラメータは小文字、累積された内部パラメータは大文字という表記になっているので、それに準じることにしましょう。例えばf2=+1を10回繰り返した場合はF2=+10と表記します。F2を変化させた時の波形の変化を1パルス分ずつ抜き出したものが図4です。波形の記録にあたっては、ピッチは最大値であるP=128としました。

F2による波形の変化
図4: F2による波形の変化

上下に振動しながらだんだん減衰する波形ですが、F2を増やすにつれその周期が短くなるのがわかるでしょうか。この振動の周波数が、F2によって決まるフォルマント周波数となります。

この周波数を求めるために、波形をフーリエ変換してピークの周波数を求めます。人の声の波形をフーリエ変換してフォルマントを得る場合は、ピッチ周波数の影響を除去するため、ケプストラム分析やらLPC分析やらということをするらしいのですが、面倒そうなのでここでは省略します。合成音声では、パラメータを変えない限りはピッチ周期ごとに同じ波形が繰り返されると思われるので、1パルス分の波形だけフーリエ変換することでフォルマント成分のみを取り出せるはずです。ただしピッチ周期は最大で約12.9msなので、フーリエ変換の結果は約78Hzの精度しか持たないことになります。まあ大まかな傾向がわかればTALK文の実装には十分でしょう。こうして得られた、F2とフォルマント周波数の関係が図5です。

フォルマントの変化
図5: フォルマントの変化

F2パラメータは7ビット幅となっており、周期128で循環することが確認できます。F2=+64やF2=+96で波形が急激に変わるのですが、この理由はよくわかりません。μPD7752の内部データがそうなっているのかもしれませんし、あるいは周波数が高すぎるなどしてアナログ回路で成分が落ちているのかもしれません。とりあえず、互換BASICの実装にあたっては、連続しているF2=0〜+63の範囲のみを使うようにしました。この部分を抜き出して対数プロットしたのが図6です。

フォルマントの変化
図6: フォルマントの変化

低い周波数ではフーリエ変換の都合により相対的に測定誤差が大きくなっていますが、フォルマント周波数は概ね指数関数的に変化すると言えそうです。


フォルマント情報・続き

さて、ここまでF1でなくF2を扱ってきたのは理由がありまして、デフォルト強度では、F2に比べると他のフォルマントはほとんど波形に影響しないのです。F1/F3/F4/F5について調べるには、帯域幅を表すBパラメータでF2の強度を下げ、他のフォルマントの強度を逆に上げてやる必要があります。

詳細は省略しますが、F1/F3/F4/F5についてもF2と同様に波形を記録して調べたところ、これらのフォルマント周波数の変化は、F2の変化カーブを横にずらした形になることがわかりました。例えばF1=0,+1,+2…は、図5のF2=+62,+63,+64…に相当する周波数になります。またB値については、値としては6ビット幅あるようなのですが、実際に使える範囲はどうも16段階しかないようです。その範囲を超えると、波形が急激に変化してしまいます。このF値のずれと、使用できるB値の範囲をまとめたのが表5です。

表5: F2を基準にした場合のF値のずれと、B値の範囲
F1 F2 F3 F4 F5
+62 0 +57 +47 +32
B1 B2 B3 B4 B5
-9〜+6 -4〜+11 -9〜+6 -9〜+6 -11〜+4

B値による強度変化を調べたのが図7です。本当なら帯域幅の名前通り、周波数ピークの幅を調べたかったのですが、測定精度的に難しそうだったので、とりあえずピークの高さの方を測ってみました。参考文献[3]には「帯域幅 (共振の強さ)」とあるので、B値を変えることで幅だけでなく高さも変わるようなフィルタになっているものと思われます。恐らく帯域幅が狭いほどピークは高く、広いほどピークは低くなるはずです。調べる際のフォルマント周波数は約2kHzにセットしました。横軸は0〜15の範囲になるように揃えてあります。

フォルマント強度の変化
図7: フォルマント強度の変化

B値を増やすと強度が下がる (=帯域幅が広くなる) ことが確認できました。変化の傾向については、各フォルマントとも概ね一致しているようで、つまりデフォルトの強度が違うだけと考えて良さそうです。だいたい指数関数的に変化する感じでしょうか?この図で言うと、B1のデフォルト値は横軸=9の位置、B2のデフォルト値は横軸=4の位置となるので、かなり強度に差があるのがわかります。

最後におまけのデータとして、すべてのF値を最低周波数に設定し、すべてのB値を最低強度に設定した時の波形を記録してみたので図8に示します。つまりフォルマントフィルタによる影響が最小で、パルス発生部の出力にわりと近いと思われる波形です。何となく矩形波っぽいような気がします。幅は0.1〜0.16ms程度でしょうか。ピッチ周期がこれよりある程度長いならば、ピッチ周期に関わらず幅はいつも同じのようです。

矩形波?
図8: 矩形波?


音声データ作り

ここまで調べて、ようやくTALK文で使う音声データ作りに取り掛かることができました。付け焼き刃的に音声学などの本を読んでみたのですが、音声の認識にはある程度法則性のようなものがあるそうです。例えば、何Hzぐらいと何Hzぐらいにフォルマントのピークが立っていれば「あ」と言っているように聞こえるとか、そのピークがこう移動するとこの子音がくっ付いているように聞こえるとか。音声データ作りでは、各音素を特徴付ける周波数からフォルマントパラメータを計算した後、それらしく聞こえるよう試行錯誤で調整しました。

母音については、参考文献[5]第2章に記載されているフォルマント周波数値を用いました。狙いとするフォルマント周波数に設定した後、そのまま変化させず引き延ばせば何となくその母音が鳴っているように聞こえるわけです。実機のTALK文では微妙な抑揚が付けてあるのですが、そのレベルの実装にはまだまだ届いていません。

例えば男声の「あ」は、648Hz, 1028Hz, 2560Hz付近にフォルマントピークがある、とあります。この周波数を図6の式に入れて逆算すると、F2=28, 37, 53になります。互換BASICではF2, F5, F4のパラメータを使用して音を出すようにしたので、表5のオフセットをそれぞれ引いてやると、F2=28, F5=5, F4=6となります。これを表4に当てはめて、出力データを作ってやるわけです。talk"m2 a."とした場合、以下のような出力をポートE0hに対して行います。

09 00 00 00 00 00 03 (ピッチ設定=+3、必要回数繰り返す)
...
0D 03 70 03 1E 15 A0 (フォルマント設定)
09 00 00 00 07 0F A0 (フォルマント設定続き)
09 00 00 00 00 00 A0 (音量維持、必要な時間繰り返す)
...

子音については、参考文献[6]第8章に記載されている周波数値を用いました。大抵の子音についてはスタート時のフォルマント周波数だけをデータとして持っており、そこから接続する母音のフォルマント周波数まで最速で機械的に変化させています。が、子音スタートから母音までの遷移時間が認識の決め手になる音もあるので、ゆっくりとした変化が必要な子音については、各母音ごとに変化の過程もデータとして持つようにしました。子音母音共に、ほとんどのデータでは、F2/F5/F4の三つのフォルマントのみ使用しています。

ほとんどの子音については、男声と女声とで同じデータを使い回しています。本来は別のデータを用意するべきなのですが、女声に関する網羅的な測定データを見つけられませんでした。音声学の本を見ても、男女のフォルマントの違いは声道長からは説明できないとか、男女の声の認識で一番大きいのは結局ピッチだとか、何となく歯切れの悪い記述しか見つけられず。何かいい資料がありましたら教えてください。

実機のTALK文では、文全体に「最初少し上がってだんだんと下がる」という抑揚が自動的に付きますが、互換BASICでは今のところ行っていません。全体が平板なのでいかにも機械が喋っている感じです。今後の課題としたいと思います。

歌唱機能については、実機ベースで音程を合わせたので、エミュレータでは音程が異なる可能性があります。また、音が高くなると、音程決めの精度が相対的に粗くなっていくので、音痴になります。実機のTALK文でもそうですが、まともに歌えるのはO3の範囲で、あとはせいぜいO4の下の方とO2の上の方ぐらいだけ使うようにした方が良さそうです。

歌唱機能使用時、短い音符で急激に音程を変えると、ピッチ変化が追いつかず指定よりも音が長くなることがあります。実機のTALK文だと、急な変化では音程の方が指定値まで変化しきれず音痴になってしまうのですが、どちらの実装も一長一短ですね。


「μPD7752風味音声合成エンジン」へのパッチ

cisc.さんによる「μPD7752風味音声合成エンジン」には、実機による調査結果と異なる点がいくつかあるようです。d7752.cへのパッチを書いたので載せておきます。ピッチと有声摩擦音に関する修正です。また、サンプリングレートは10000Hzでなく9943Hz程度 (=10000×3.579545/3.6) に設定するのが実機に近いようです。


--- d7752.c.org	2004-02-25 21:25:58.000000000 +0900
+++ d7752.c	2017-05-07 11:27:28.816106400 +0900
@@ -265,3 +265,3 @@
 	}
-	next.pitch = curr->pitch + I2F(p);
+	next.pitch = I2F(((F2I(curr->pitch) + p - 1) & 0x7f) + 1);
 
@@ -278,3 +278,3 @@
 	vu  = param[0] & 1 ? 1 : 2;
-	vu |= param[6] & 4 ? 3 : 0;
+	vu |= param[6] & 8 ? 3 : 0;
 
@@ -291,3 +291,3 @@
 		c = F2I(curr->pitch);
-		if (inst->pitch_count > (c > 0 ? c : 128))
+		if (inst->pitch_count >= c)
		{

参考文献

  1. PC-6001シリーズBASICマニュアル, NEC, 1981-1985.
  2. 鈴木宗一/川上雄一/小山斉/五十嵐初日出/町田俊明/高畠謙二/伏木田勝信, 「ホルマントをパラメータとする音声合成LSI」, NEC技報, Vol.34, No.10 (1981/10), pp.92-97.
  3. 笠本正典, 「新・マシン語初級講座」, Oh!PC, Vol.3, No.8 (1985/1), pp.193-197.
  4. えすび/Hashi, 「PC-Techknow6000 Vol.2」, TinyProject, 2015.
  5. 城生佰太郎, 「コンピュータ音声学」, おうふう, 2001.
  6. レイ・D・ケント/チャールズ・リード著, 荒井隆行/菅原勉 監訳, 「音声の音響分析」, 海文堂出版, 1996.

トップ > 雑多なもの > PC-6001mkII/6601用互換BASIC > TALK文のできるまで