Wesbos - 텍스트 음성변환


완성본 예시

Web Speech API인 SpeechSynthesisUtterance() 를 활용한 텍스트 음성 합성기를 활용해보는 챕터였다. 배우면 배울수록 JavaScript가 다재다능한 언어임을 깨닫는다.


로직

  1. 선언과 SpeechSynthesisUtterance() 불러오기
  2. 음성종류(voice) option에 불러오기
  3. 텍스트 읽기와 정지 기능 구현하기
  4. Rate와 Pitch값 조절기능 구현

const & Speech API

SpeechSynthesisUtterance()

💡 Web Speech API로, 스피치(말하기)서비스와 읽기관련 다양한 기능들을 제공한다.

이름 설명
.lang 언어를 불러오고 세팅함
.pitch 피치를 조절할 수 있음
.rate 말하기 속도를 조절 가능
.text 말하기 할 텍스트를 설정 및 입력
.getVoices 목소리 종류를 설정
.volume 볼륨 설정

버튼, 옵션인풋, 드롭다운 등을 선언해준다.

1
2
3
4
5
6
const msg = new SpeechSynthesisUtterance();
let voices = [];
const voicesDropdown = document.querySelector('[name="voice"]');
const options = document.querySelectorAll('[type="range"], [name="text"]');
const speakButton = document.querySelector('#speak');
const stopButton = document.querySelector('#stop');

Voices 드롭다운에 불러오기

💡 speechSynthesis.OOO을 활용해야 한다.

voiceschanged

페이지 실행 시(로드) 실행되며, list를 불러오기 위해 주로 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function populateVoices() {
voices = this.getVoices();
console.log(voices);
voicesDropdown.innerHTML = voices
// 영어와 한국어만 사용하기 위해 이하와 같이 filter를 걸었다.
.filter((voice) => voice.lang.includes('en') || voice.lang.includes('ko'))
.map(
(voice) =>
`<option value="${voice.name}">${voice.name} (${voice.lang})</option>`
)
.join('');
}

speechSynthesis.addEventListener('voiceschanged', populateVoices);

언어설정


보이스 세팅

💡 읽기(speak)를 기능하게 하도록 하기 위해선 보이스(voice)를 설정해줘야 한다.

1
2
3
4
5
6
function setVoices() {
console.log(this);
console.log(this.value);
}

voicesDropdown.addEventListener('change', setVoices);

찍어보기

잘 찍힌다...

자… 이제 위의 값들을 msg.voice에 넣어주면 된다.

1
2
3
function setVoices() {
msg.voice = voices.find((voice) => voice.name == this.value);
}

읽기와 정지 버튼 활성화

💡 speechSynthesis.speak || .cancel 메서드를 활용해주면 된다 !

우선! textarea에 적혀있는 텍스트가 msg.text에 할당되어야 한다.

1
msg.text = document.querySelector('[name="text"]').value;

texarea에 입력된 텍스트 읽기

1
2
3
4
5
6
7
8
function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}

speakButton.addEventListener('click', toggle);

하지만…. 여기서 끝낼 경우 계속 HTML에 미리 입력해둔 “Hello! I love JavaScript 👍”만 읽는다…

⇒ 옵션을 세팅해줘야 함!


msg.textRate, 그리고 Pitch값 할당

textareaRate, Pitch값이 변경되면 그 값을 msg에 할당하도록 해보자

1
2
3
4
5
6
7
8
9
const typedText = document.querySelector('[name="text"]').value;
msg.text = typedText;

function setOption() {
console.log(this.name, this.value);
msg[this.name] = this.value;
}

options.forEach((option) => option.addEventListener('change', setOption));

잘 찍힘!


최종 완성 코드

참고로 필자는 옵션이 변경될 때마다 텍스트를 읽는게 불편해서

toggle() 이 말하기를 눌렀을 때만 실행되도록 코딩했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const msg = new SpeechSynthesisUtterance();
let voices = [];
const voicesDropdown = document.querySelector('[name="voice"]');
const options = document.querySelectorAll('[type="range"], [name="text"]');
const textarea = document.querySelector('textarea');
const speakButton = document.querySelector('#speak');
const stopButton = document.querySelector('#stop');
const typedText = document.querySelector('[name="text"]').value;
msg.text = typedText;

function populateVoices() {
voices = this.getVoices();
voicesDropdown.innerHTML = voices
.filter((voice) => voice.lang.includes('en') || voice.lang.includes('ko'))
.map(
(voice) =>
`<option value="${voice.name}">${voice.name} (${voice.lang})</option>`
)
.join('');
}

function setVoices() {
msg.voice = voices.find((voice) => voice.name == this.value);
}

function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}

function setOption() {
console.log(this.name, this.value);
msg[this.name] = this.value;
}

speechSynthesis.addEventListener('voiceschanged', populateVoices);
voicesDropdown.addEventListener('change', setVoices);
options.forEach((option) => option.addEventListener('change', setOption));
speakButton.addEventListener('click', toggle);
stopButton.addEventListener('click', toggle(false));

Author

Hoonjoo

Posted on

2022-01-05

Updated on

2022-02-07

Licensed under

Comments