AI の進化により音声合成の性能も向上し、さまざまなシーンで使われるようになってきました。多くは有料ですが、音声合成のクラウドサービスも多岐にわたります。しかし、近年のすべてのメジャーブラウザーで JavaScript で音声合成できることをご存じでしょうか。しかも無料です。今回は音声合成を担う Web Speech API について、ハマリどころも含めて詳しく解説します。
Web Speech API とは
Web Speech API は音声認識 (Speech Recognition) と音声合成 (Speech Synthesis) を実現する JavaScript API です。
音声認識はマイクロフォンを通して得られたユーザーの音声をテキストに変換します。英語では Speech-to-Text とも呼ばれます。Web Speech API では、言語選択、連続音声認識、中間結果の取得などの機能が用意されています。ウェブアプリでは主に音声コマンドや音声入力などに使われます。2023 年 12 月現在、音声認識は Chrome と Safari で部分的にサポートされています。ベンダープレフィックスが必要なこともあり、現時点ではサービスに組み込むのは時期尚早かもしれません。
音声合成はテキストを音声に変換してスピーカーで再生します。英語では Text-to-Speech とも呼ばれます。Web Speech API では、音声のピッチや速度を調整することができます。主にウェブサイトの読み上げや音声ガイドなどに使われます。音声合成はすでにすべてのメジャーブラウザーでサポートされています。そしてベンダープレフィックスも必要ありません。この記事では音声合成にフォーカスして解説します。
音声合成を簡単に試す
まずは、簡単に音声合成を試してみましょう。たったこれだけのコードでテキストを読み上げてくれます。
const utter = new SpeechSynthesisUtterance('こんにちは');
window.speechSynthesis.speak(utter);
SpeechSynthesisUtterance
コンストラクタから SpeechSynthesisUtterance
オブジェクト (上記コードでは変数 utter
) を生成します。これは発話内容を表します。コンストラクタには発話させたいテキストを指定します。
SpeechSynthesis
オブジェクト (window.speechSynthesis
) の speak()
メソッドに SpeechSynthesisUtterance
オブジェクトを引き渡すことで、SpeechSynthesisUtterance
オブジェクトにセットしたテキストをスピーカーを通して読み上げます。次のボタンを押して実際に聞いてみましょう。
音声の再生制御
SpeechSynthesis
オブジェクト (window.speechSynthesis
) には、speak()
メソッドのほかにも音声の再生を制御するメソッドがいくつか用意されています。
メソッド | 説明 |
---|---|
speak() | 引数に指定した発話 (SpeechSynthesisUtterance オブジェクト) をキューに追加します。 |
pause() | 再生中の発話を一時停止します。 |
resume() | 一時停止されていた発話を続きから再生します。 |
cancel() | キューに残っているすべての発話を削除します。 |
ここで気に留めてほしいのがキューの存在です。speak()
メソッドはブラウザーのキューに SpeechSynthesisUtterance
オブジェクトを追加しているにすぎません。ブラウザーはキューに追加された発話を順番に処理します。そのため、すでに他の発話がキューに残っていると、speak()
メソッドに指定の発話がすぐに開始されるとは限りませんので注意してください。
音声の再生制御は以上の 4 つのメソッドしかありませんので、video
要素や audio
要素のメディア再生ほどきめ細やかに制御することはできません。早送り、巻き戻し、再生位置の移動といった手段は用意されていません。たとえば、もし再生中の発話を最初に戻したいなら、pause()
メソッドを呼び出した後、cansel()
メソッドを使ってキューを空にしてから speak()
を呼び出すしかありません。
pause()
メソッドで一時停止した場合、再生を再開したいなら resume()
メソッドを使ってください。一時停止中に speak()
メソッドを呼び出しても何も起こりませんので注意してください。
音声の再生状態
SpeechSynthesis
オブジェクト (window.speechSynthesis
) には音声再生の状態を表すプロパティが用意されています。
プロパティ | 説明 |
---|---|
speaking | 発話処理中なら true を、そうでなければ false を返します。 |
paused | 一時停止状態なら true を、そうでなければ false を返します。 |
pending | キューにまだ再生が開始されていないものがあれば true を、そうでなければ false を返します。 |
speaking
プロパティの意味については注意が必要です。一時停止状態 (paused
プロパティが true
の状態) であっても、speaking
プロパティの値は true
です。speaking
プロパティは、スピーカーから音声が出ているかどうかは関係がありません。speaking
プロパティは、ひとたび発話が開始されたら、一時停止という状態は関係がなく、キューが無くなるまで true
であり続けます。
言語の指定
前述のサンプルでは日本語の「こんにちは」を発話させましたが、今度は英語の「Hello world」を発話させてみましょう。次のボタンを押して実際に聞いてみてください。
const utter = new SpeechSynthesisUtterance('Hello world');
window.speechSynthesis.speak(utter);
恐らく、コテコテのジャパニーズイングリッシュな発音だったのではないでしょうか。実は、Web Speech API の音声合成では、デフォルトでは、この HTML の html
要素の lang
属性に指定された言語が適用されます。なければ、ブラウザーのデフォルト言語が適用されます。そのため、"ja-JP"
が適用されていたわけです。つまり、”Hello World” を日本語読みさせていたわけです。そういう意味では、上手な?ジャパニーズイングリッシュだったのではないでしょうか。
もし言語を指定したいなら、SpeechSynthesisUtterance
オブジェクトの lang
プロパティを使います。米国英語で発話させたいなら、次のようなコードになります。
const utter = new SpeechSynthesisUtterance('Hello world');
utter.lang = 'en-US';
window.speechSynthesis.speak(utter);
声の微調整
これまでのサンプルを聞いて、声が機械的に感じなかったでしょうか。Web Speech API では、声の高さと発話の速度を調整することができます。これらの調整は、SpeechSynthesisUtterance
オブジェクトの pitch
プロパティと rate
プロパティを使います。そのほか、volume
プロパティを使うと音量も調整できます。
プロパティ | 説明 |
---|---|
pitch | 声の高さを 0.0 (最も低い声) から 2.0 (最も高い声) の範囲の数値で指定します。1.0 が規定値です。 |
rate | 発話の速度を 0.1 (最も遅い) から 10.0 (最も速い) の範囲の数値で指定します。1.0 が規定値です。 |
volume | 発話の音量を 0.0 (最低) から 1.0 (最高) の範囲の数値で指定します。1.0 が規定値です。 |
次のサンプルは声を最高に高く、そして、発話の速度を最高に速くしています。
const utter = new SpeechSynthesisUtterance('こんにちは');
utter.pitch = 2.0;
utter.rate = 10.0;
window.speechSynthesis.speak(utter);
利用可能な声を調べる
前述の声の高さや発話の速度は、あくまでも同じ声に対して調整するものでしたが、声そのものを変更することもできます。男性の声、女性の声、また、同じ女性でも別人の声、といった感じで、いくつか声を選択することができます。選択できる声はブラウザーや OS によって異なります。
利用可能な声は、SpeechSynthesis
オブジェクト (window.speechSynthesis
) の getVoices()
メソッドを使って配列 (Array
オブジェクト) として得られます。
const voices = window.speechSynthesis.getVoices();
for(const voice of voices) {
console.log(`${voice.name} (${voice.lang})`);
}
このコードを実行すると次のようなメッセージがコンソールに出力されるはずです。
Microsoft Ayumi - Japanese (Japan) (ja-JP)
test1.html:19 Microsoft Mark - English (United States) (en-US)
...
次のボタンを押すと、実際にあなたのブラウザーがどんな声をサポートしているのかを表示します。
この配列の中には声の情報を格納した SpeechSynthesisVoice
オブジェクトが含まれています。このオブジェクトの主なプロパティをいくつか下表に挙げます。これらのプロパティから必要な声を判定することになります。
プロパティ | 説明 |
---|---|
name | 声を表す名前を返します。 |
lang | 言語を BCP 47 形式 ("en-US" や "ja-JP" ) で返します。 |
default | デフォルトの声なら true を、そうでなければ false を返します。 |
localService | 音声合成が PC 内部で行われるなら true を、外部のサービスを利用しているなら false を返します。 |
リストアップされる多くの SpeechSynthesisVoice
オブジェクトの localService
プロパティの値は true
になります。これらの声は基本的に OS が持っている声です。そのため、同じ OS ならどのブラウザーでも同じ声を選択することができます。
一方で、ブラウザーベンダーが独自に追加した声もあります。もしそれがクラウドのサービスを利用している場合、localService
プロパティの値は false
になります。Google Chrome と Microsoft Edge はそれぞれクオリティが高い独自の声をオンラインで提供しています。一方、そういった声を使う場合、ネットに接続していることが前提で、かつ、ネット品質によっては再生開始が遅れるなどのリスクがあります。
声をセットする
どんな声が使えるのかが分かったところで、次はその声で発話させてみましょう。そのためには、SpeechSynthesisUtterance
オブジェクトの voice プロパティに、SpeechSynthesisVoice
オブジェクトをセットします。
次の例は、利用可能な声から言語が “ja-JP” で、かつ、名前に “Ichiro” が含まれる声があれば、それをセットして発話させています。
const utter = new SpeechSynthesisUtterance('こんにちは');
const voices = window.speechSynthesis.getVoices();
for (const voice of voices) {
if (voice.lang === 'ja-JP' && /Ichiro/.test(voice.name)) {
utter.voice = voice;
}
}
window.speechSynthesis.speak(utter);
次のボタンを押して試してください。Windows であれば男性の声が聞こえると思います。もし男性の声ではなく、今までの通り女性の声が聞こえたなら、もう一度ボタンを押してみてください。Mac の場合は “Ichiro” という名前の声が存在しませんので、以前と違いはないはずです。
Windows の Chrome と Edge の場合、声を変えて発話させようとすると、ボタンを 2 度押さないと再生しない場合があります。これは事前に空文字列を発話させておくと解決します。再生の直前ではうまくいきませんので、ページがロードされた直後などが良いでしょう。
window.addEventListener('load', () => {
const utter_dummy = new SpeechSynthesisUtterance('');
window.speechSynthesis.speak(utter_dummy);
});
また、ページ読込直後に getVoices()
を使う場合には注意が必要です。ブラウザーによってはページ読込直後では準備が整っていないせいか getVoices()
が空の配列を返してしまいます。
筆者が試したところ、Chrome と Edge では OS を問わず発生します。document
オブジェクトの DOMContentLoaded
イベント発生時はおろか、window
オブジェクトの load
イベント発生時ですら空文字列が返ります。もう少し待たなければいけないようです。
ページ読込後に可能な限り早く利用可能な声のリストを取り出したい場合は、次のようなコードが良いでしょう。
let voices = window.speechSynthesis.getVoices();
if (voices.length > 0) {
doSomething();
} else {
window.speechSynthesis.addEventListener('voiceschanged', () => {
voices = window.speechSynthesis.getVoices();
doSomething();
}, { once: true });
}
上記コードでは、関数 doSomething()
が声のリストを扱う処理だとします。getVoices()
で得られたリストの要素数が 0 より大きければ、すぐに doSomething()
を実行します。しかし、要素数が 0 個であれば、SpeechSynthesis
オブジェクト (window.speechSynthesis
) で発生する voiceschanged
イベントのリスナーをセットしておきます。
Chrome と Edge の場合、getVoices()
の準備が整うと、SpeechSynthesis
オブジェクト (window.speechSynthesis
) で voiceschanged
イベントが発生しますので、それを利用します。しかし、他のブラウザーでは voiceschanged
は発生しませんので注意してください。
次のセレクトメニューでは、いまお使いのブラウザーがサポートしている日本語の声をリストアップしています。声を選択して発話させてみてください。声の雰囲気の違いが良く分かるように、やや長い文章をセットしてみました。「Web Speech APIは、Webアプリケーションに音声認識と音声合成の機能を追加するためのインターフェースを提供する技術です。」(ChatGPT で生成した文章)と発話します。
Chrome でしたら「Google 日本語」(女性の声)、Edge でしたら「Microsoft Keita Online (Natural) – Japanese (Japan)」(男性の声)、または、「Microsoft Nanami Online (Natural) – Japanese (Japan)」(女性の声)が筆者の好みです。無料で使えるにしては非常にクオリティが高いです。
残念ながら、Firefox は独自の声は用意されていません。また、Safari は声が 1 つしか提供しておらず、非常に機械的なトーンになってしまいます。
Chrome の声「Google 日本語」の問題
Chrome に用意されている声「Google 日本語」は声のクオリティが高いとお伝えしましたが、実はこの「Google 日本語」の利用には問題があります。実際に試すと分かりますが、発話が 15 秒程度で途切れてしまいます。実際に試してみましょう。Chrome を使ってボタンを押すと次の文章を読み上げますが、途中で途切れてしまいます。
「Web Speech APIは、Webアプリケーションに音声認識と音声合成の機能を追加するためのインターフェースを提供する技術です。このAPIは、ブラウザーがユーザーの話し声をテキストに変換したり、テキストを音声で読み上げたりすることを可能にします。」
15 秒以内に発話が終わるような短い文章なら問題ありませんが、長い文章では困ります。さらに発話が途切れたことを検知する手段がありません。そのため、せっかくクオリティが高い声なのですが、15 秒を超えそうな文言を発話させたい場合は「Google 日本語」の利用を止めておきましょう。
まとめ
Web Speech API がブラウザーに初めて実装され始めて 10 年ほど経ちましたが、今なお実験的な機能として扱われています。将来的に細かい点で変更が発生するかもしれません。また、有料のクラウドサービスと比べると機械的な声になってしまいますが、無料という点は見逃せません。
もしウェブアプリやウェブサイトでテキストを発話させたい場合、最初から有料のクラウドサービスを考えるのではなく、まずは無料の Web Speech API の音声合成で十分なのかを確かめると良いでしょう。
今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。