【Vue3・speechSynthesis】二人の人物が音声で会話するwebアプリを作る

今回はVue3とspeechSynthesisを使って二人の人物が音声で会話するwebアプリを作ります。会話の再生部分にVueはほとんど関係ないのですが,インターフェイスを作るのが楽なのでVueを使っています。

speechSynthesisはブラウザ上でテキストをもとに音声を再生する機能です。今のところChromeかEdgeで動きます。

データの設置

//会話文データ
const dialogueData = [
	{gender: "M", speech: "Hello, Tina. What are you doing these days?"},
	{gender: "W", speech: "Hi, Mr. Corby. I’m busy rehearsing for a musical."},
	{gender: "M", speech: "Really? When’s the performance?"},
	{gender: "W", speech: "It’s April 14th, at three. Please come!"},
	{gender: "M", speech: "I’d love to! Oh… no, wait. There’s a teachers’ meeting that day, and I can’t miss it. But good luck!"},
	{gender: "W", speech: "Thanks."}
];

会話文のデータを配列として用意します。genderは性別を表し,"M"なら男性,"W"なら女性の声です。また,speechにセリフを入れます。

ルートコンポーネント

次に,Vueのコンポーネントを作っていきましょう。

const RootComponent = {
	data() { return {
		dialogue: dialogueData,
	}},

ここは上で作ったデータを単に取り込んでいるだけです。

	template: `
		<div @click="speechDialogue">
			<p v-for="obj in dialogue">{{obj.gender}}: {{obj.speech}}</p>
		</div>`,

テンプレートに会話文を表示します。v-forを使うと配列の内容を順番に表示することができます。

画面上の表示は上のようになります。

@click="speechDialogue"でメソッドspeechDialogue()を呼び出します。これは会話文を再生するものです。会話文のあたりをクリックすると音声が再生されます。

いったんgetVoices()を実行する

	created() {
		const uttr = new SpeechSynthesisUtterance();
		const voices = window.speechSynthesis.getVoices();
	},

created()はコンポーネントが作成され画面上に表示される(マウントする)前に実行する部分です。

ここで,いったんspeechSynthsisをインスタンスしてgetVoices()を実行します。

speechSynthesisには奇妙なバグがあって,いったんgetVoices()を実行しておかないと,最初の再生につまづきます(最初のgetVoices()では空のオブジェクトが返ってきます。なぜか2度目以降ならちゃんと音声のリストが返ってきます)。

私はこれを「捨てgetVoices」と勝手に呼んでいますが,speechSynthesisを使うときに最初に儀式として行う必要があります。

getVoices()って何?

voices = getVoices()を実行すると,ブラウザで再生できる音声の種類を表すオブジェクトがvoicesに格納されます。

0: SpeechSynthesisVoice
		default: true
		lang: "ja-JP"
		localService: true
		name: "Microsoft Ayumi - Japanese (Japan)"
		voiceURI: "Microsoft Ayumi - Japanese (Japan)"
1: SpeechSynthesisVoice
		default: false
		lang: "ja-JP"
		localService: true
		name: "Microsoft Haruka - Japanese (Japan)"
		voiceURI: "Microsoft Haruka - Japanese (Japan)"
2: SpeechSynthesisVoice
		default: false
		lang: "ja-JP"
		localService: true
		name: "Microsoft Ichiro - Japanese (Japan)"
		voiceURI: "Microsoft Ichiro - Japanese (Japan)"
3: SpeechSynthesisVoice
		default: false
		lang: "ja-JP"
		localService: true
		name: "Microsoft Sayaka - Japanese (Japan)"
		voiceURI: "Microsoft Sayaka - Japanese (Japan)"
4: SpeechSynthesisVoice
		default: false
		lang: "de-DE"
		localService: false
		name: "Google Deutsch"
		voiceURI: "Google Deutsch"
5: SpeechSynthesisVoice
		default: false
		lang: "en-US"
		localService: false
		name: "Google US English"
		voiceURI: "Google US English"
6: SpeechSynthesisVoice
		default: false
		lang: "en-GB"
		localService: false
		name: "Google UK English Female"
		voiceURI: "Google UK English Female"
7: SpeechSynthesisVoice
		default: false
		lang: "en-GB"
		localService: false
		name: "Google UK English Male"
		voiceURI: "Google UK English Male"
8: SpeechSynthesisVoice
		default: false
		lang: "es-ES"
		localService: false
		name: "Google español"
		voiceURI: "Google español"

・・・・・・

getVoice()を実行したあとにconsole.log(voices)を書いておくとコンソール(ブラウザからF12を押すと表示されます)に中身が表示されるはずです。

こうしてみると分かりますが,英語だけでなく様々な言語の再生に対応しています。日本語も4種類あるのでこれらを使ってみても面白いでしょう。

このリストは使っているブラウザによって異なります。今回は,リストの6番と7番,イギリス人の女性と男性の声を使います。

ブラウザによってリストの内容が異なることがあるので,お使いのブラウザに合わせて数字を変えてみてください(本来はオブジェクトの中に必要な音声が含まれているかどうかを判定する必要があるのですが,今回は省略)。

getVoices()で取得した音声のリストは,あとで再生用のデータをセットするときに必要になります。

音声を再生するまでの流れ

話をまとめると,speechSynthesisで音声を再生するまでの流れは次のようになります。

  • const uttr = new SpeechSynthesisUtterance()でspeechSynthesisをインスタンス。
  • const voices = window.speechSynthesis.getVoices()を儀式として実行。
  • インスタンスとgetVoicesを再び実行(今度は音声のリストが返ってくる)。
  • 必要な設定をuttrにセットする。
  • speechSynthesis.speak(uttr)で再生。

音声を再生する

methods: {
		speechDialogue() {
			speechSynthesis.cancel();
			this.dialogue.forEach((elem) => {
				let uttr = new SpeechSynthesisUtterance();
				const voices = speechSynthesis.getVoices();
				if(elem.gender == 'W') {
					uttr.voice = voices[6]; //女性の声
				} else if(elem.gender == 'M') {
					uttr.voice = voices[7]; //男性の声
				}
				uttr.volume = 1.0; //音量
				uttr.rate = 1; //読み上げ速度
				uttr.pitch = 1.05; //音程
				uttr.lang = 'en-US'; //言語
				uttr.text = elem.speech; //読み上げテキスト
				speechSynthesis.speak(uttr); //再生
			})
		}
	}

音声を再生する部分のメソッドです。画面上の会話文をクリックするとspeechDialogue()が呼び出されます。

まず,speechSynthesis.cancel()で再生中の音声を止めます。実際に動かしてみると分かりますが,いったん会話を再生して音声が流れている途中で再びクリックすると,会話の音声が止まって初めから再生し直します。

			this.dialogue.forEach((elem) => {

次にforEachで配列の中身を一つずつ処理していきます。

				let uttr = new SpeechSynthesisUtterance();
				const voices = speechSynthesis.getVoices();

speechSynthesisのインスタンスと,音声のリストを取得するgetVoicesを実行します。今回は男女の音声が交互に入れ替わって再生されますが,入れ替わるたびにインスタンスからやり直したほうがうまくいくようです。

				if(elem.gender == 'W') {
					uttr.voice = voices[6]; //女性の声
				} else if(elem.gender == 'M') {
					uttr.voice = voices[7]; //男性の声
				}

先ほどインスタンスしたuttrに設定を送り込みます。テキストのデータからgenderWであれば女性の声,Mであれば男性の声を設定します。ここでgetVoice()で取得した音声のリストを使います。このように,音声の種類を指定するためにgetVoice()が必要になります。

				uttr.volume = 1.0; //音量
				uttr.rate = 1; //読み上げ速度
				uttr.pitch = 1.05; //音程
				uttr.lang = 'en-US'; //言語
				uttr.text = elem.speech; //読み上げテキスト

その他の設定を行います。実際に音声を聞いてみて,読み上げ速度や音程を調整してみると良いでしょう。

				speechSynthesis.speak(uttr); //再生

設定を終えたら最後にspeak()で再生します。

対話の再生のコツは,繰り返し処理を行うときにspeehSynthesisのインスタンスから行うことです。ループの外側にインスタンスを置くとうまく再生されません。

まとめ

ここでは,二人の人物の対話を音声で再生するwebアプリを作成しました。音声の種類を変えることで日本人同士の会話を行うこともできます。音声の種類を切り替えながら再生するには多少コツが必要ですが,それほど難しいものではありません。

最後に全体のコードを示します。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>2名の対話リスニングアプリ・サンプル</title>
<!--Vue-->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
<script>
//会話文データ
const dialogueData = [
	{gender: "M", speech: "Hello, Tina. What are you doing these days?"},
	{gender: "W", speech: "Hi, Mr. Corby. I’m busy rehearsing for a musical."},
	{gender: "M", speech: "Really? When’s the performance?"},
	{gender: "W", speech: "It’s April 14th, at three. Please come!"},
	{gender: "M", speech: "I’d love to! Oh… no, wait. There’s a teachers’ meeting that day, and I can’t miss it. But good luck!"},
	{gender: "W", speech: "Thanks."}
];
//ルートコンポーネント
const RootComponent = {
	created() {
		const uttr = new SpeechSynthesisUtterance();
		const voices = window.speechSynthesis.getVoices();
	},
	data() { return {
		dialogue: dialogueData,
	}},
	template: `
		<div @click="speechDialogue">
			<p v-for="obj in dialogue">{{obj.gender}}: {{obj.speech}}</p>
		</div>`,
	methods: {
		speechDialogue() {
			speechSynthesis.cancel();
			this.dialogue.forEach((elem) => {
				let uttr = new SpeechSynthesisUtterance();
				const voices = speechSynthesis.getVoices();
				if(elem.gender == 'W') {
					uttr.voice = voices[6]; //女性の声
				} else if(elem.gender == 'M') {
					uttr.voice = voices[7]; //男性の声
				}
				uttr.volume = 1.0; //音量
				uttr.rate = 1; //読み上げ速度
				uttr.pitch = 1.05; //音程
				uttr.lang = 'en-US'; //言語
				uttr.text = elem.speech; //読み上げテキスト
				speechSynthesis.speak(uttr); //再生
			})
		}
	}
}
//Vueのインスタンス
const app = Vue.createApp(RootComponent);
//アプリケーションのマウント
app.mount('#root');
</script>
</body>
</html>