使用語音合成 API 建立一台語音翻譯機。
範例連結
在 Day 21 有提到 Web Speech API 分成聲音轉字串的語音辨識 (Speech Recognition) 以及字串轉聲音的 語音合成 (Speech Synthesis) ,今天練習的是後者。
一台簡單的語音翻譯機框架如下:
<div class="voiceinator">
<h1>The Voiceinator 5000</h1>
<!-- 可以選擇各種語音的清單 -->
<select name="voice" id="voices">
<option value="">Select A Voice</option>
</select>
<!-- 調節語音速度 -->
<label for="rate">Rate:</label>
<input name="rate" type="range" min="0" max="3" value="1" step="0.1">
<!-- 調節音調高低 -->
<label for="pitch">Pitch:</label>
<input name="pitch" type="range" min="0" max="2" step="0.1">
<!-- 要唸出來的文字 -->
<textarea name="text">Hello! I love JavaScript ?</textarea>
<button id="stop">Stop!</button>
<button id="speak">Speak</button>
</div>
要使用語音合成,得先設定好 SpeechSynthesisUtterance
,然後用 speechSynthesis.speak()
方法將其讀出。
SpeechSynthesisUtterance
是種請求,裡面包含要讀什麼、怎麼讀等資訊。整個過程可以想像成先準備好草稿 (Utterance),再發表演講(speak)這樣。
草稿最基本的需求為「要讀什麼」,可以透過設置 SpeechSynthesisUtterance.text
來達到。 先看以下程式碼:
// 創建一個語音需求的物件
const msg = new SpeechSynthesisUtterance();
const speakButton = document.querySelector('#speak');
// 播放語音的函式
function toggle() {
// 讀出語音需求
speechSynthesis.speak(msg);
}
// 設置要讀什麼
msg.text = 'Dudi is here.';
speakButton.addEventListener('click', toggle);
先創建一個語音需求物件,指定要讀的文字給 text
屬性,用 speeckSynthesis.speak()
讀出,其他的設定都會按照預設屬性來跑。
現在來看看要怎麼手動修改其他屬性,首先來設定要用哪種內建語音來讀。
先解釋一個機制,SpeechSynthesisVoiceList
是瀏覽器支援的語音所組成的清單,該清單會在瀏覽器頁面開啟時被安裝。
speechSynthesis.getVoices()
方法會回傳 SpeechSynthesisVoiceList
。
而 voicechanged
事件則是在 SpeechSynthesisVoiceList
內容改變時會被觸發。也就是說, voicechanged
在瀏覽器開啟後, SpeechSynthesisVoiceList
安裝完畢時就會被觸發一次。
為什麼要說這些?要擷取支援的語音清單需要用到它們,加入下面程式碼:
const voicesDropdown = document.querySelector('[name="voice"]');
let voices = [];
function populateVoices() {
// 擷取語音模組清單
voices = this.getVoices();
voicesDropdown.innerHTML = voices
.map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
.join('');
}
// 當 `voicechanged` 觸發時,呼叫 `populateVoices` 函式載入支援語音
speechSynthesis.addEventListener('voiceschanged', populateVoices);
利用 voicechanged
事件的特性,當所有支援的語音模組載入完畢時,呼叫函式擷取所有的模組。取得每個語音模組的 name
屬性與 lang
屬性後,組合成 HTML 選項標籤貼到下拉選單內,我們就可以選擇想要的語音模組使用。
SpeechSynthesisUtterance
的 voice
屬性可以設定要使用哪個語音模組,我們可以監聽下拉式選單的變動來改變 voice
屬性,然後讓新的語音模組被設定完就立即播放。加入以下程式碼:
function setVoice() {
msg.voice = voices.find(voice => voice.name === this.value);
// 設定完就播放
toggle();
}
voicesDropdown.addEventListener('change', setVoice);
SpeechSynthesisUtterance
的 pitch
屬性可以設定語音音調、rate
屬性可以設定語音速度。將相同的字串放在 <input type="range">
標籤的 name
屬性,用程式碼連結便能直接操控兩者。加入以下程式碼:
const options = document.querySelectorAll('[type="range"], [name="text"]');
function setOption() {
msg[this.name] = this.value;
toggle();
}
options.forEach(option => option.addEventListener('change', setOption));
和前幾篇慣用的手法相同,監聽滑動軸的變化,只要值改變了就更新 SpeechSynthesisUtterance
內相對應的屬性到新的值。
順便把要講的字也接到可以輸入的欄位中,加入下列程式碼:
msg.text = document.querySelector('[name=text]').value;
最後要對 toggle()
函式做點小小的改變。我們希望切換語音的時候,不用等到原句子講完,可以直接中斷並播放新的。另外我們也希望按了停止鍵後,會停止所有語音。改造 toggle
順便加點東西:
function toggle(startOver = true) {
// 先停止
speechSynthesis.cancel();
// 如果 startOver 開關是打開的才會播放
if(startOver) {
speechSynthesis.speak(msg);
}
}
// Stop 按鈕會把 startOver 開關關閉
stopButton.addEventListener('click', toggle.bind(null, false));
這裡用函式預設參數的方式讓 startOver
參數預設是打開的。只有 stop
按鈕按下時可以把切換該參數。因為在 addEventListener
內的函式加了括號會自動執行,因此無法在裡面直接塞參數。這裡用了 bind
方法, bind
方法的第一個參數可以強制指定 toggle
的呼叫對象,第二個參數可以塞參數進去 toggle
內。我們只使用第二個參數,第一個參數設 null
即可。
以上就是 JS30 第二十三篇!
SpeechSythesisUtterance
SpeechSynthesis.getVoices() 方法及 voicechanged 事件的關係
完整程式碼