JavaScript30 - Day 23 (Speech Synthesis)

This is day 23 in my #javascript30 journey. This is the free course from Wes Bos that lets you brush up on your JavaScript skills via 30 projects.

Yesterday we created a follow along link highlighter. You can keep track of all the projects we're building here.

Today we're exploring speech synthesis.


Day 23 - Speech Synthesis

The speech synthesis API is available in most modern browsers. This allows us to create some pretty nifty text to voice functionality.

Todays project will be made up of:

  • A select component that allows us to select the voice we want.
  • 2 Sliders to control the rate and pitch of the voice.
  • A text area input that contains the text to be read
  • 2 buttons to start and stop the speech synthesis.

Like most of our projects in this course Wes has already provided the basic HTML and CSS we're just going to be inserting the JS.

Starting Blocks

Quick note: Our rate, pitch and text must be named these exact terms as they will match up with those properties on the utterance.


Getting Our Voices List

In order to start out speech synthesis working we will need to determine the voice that the text is going to be synthesised in.

To do this we will listen for when the voiceschanged event fires on the speechSynthesis object (as this needs to load before we can access the available voices).

Once we have these loaded we need to insert them into the voices dropdown selector.

We will filter and map the available voices so that we only display those with English as their language:

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

speechSynthesis.addEventListener('voiceschanged', populateVoices)  

Getting our Voices

Change our Voice

We also need to listen for when a voice has been selected from the dropdown menu.

If you guessed that we're going to need another event listener and function then you have been paying attention. Well done.

We're going to search through the voices array and find the corresponding SpeechSynthesisVoice object to set as the voice on our utterance:

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

voicesDropdown.addEventListener('change', setVoice)  
Restart the Speech on Changes

Every time we alter the voice, or rate or pitch we want to restart the speech with the new properties.

First up we need to cancel any utterances that are currently speaking. Then we can start the new utterance:

function setVoice() {  
  msg.voice = voices.find(voice => voice.name === this.value)
  toggle()
}

function toggle() {  
  // Cancel the current utterance
  speechSynthesis.cancel()
  // Start a new utterance
  speechSynthesis.speak(msg)
}

We can also add a parameter that allows us to call the toggle function to stop the speech:

function toggle(startOver = true) {  
  speechSynthesis.cancel()
  if (startOver) {
    speechSynthesis.speak(msg)
  }
}
Working with our Options

Now that we have the speech synthesis working on a basic level let's add the ability for the users to edit the options (rate, pitch & message).

These are all selected and saved to options:

const options = document.querySelectorAll('[type="range"], [name="text"]')  

We can then add an event listener to each of these options. We can use the selector to gather the name and value of each of these options. Once we have these we can update the msg with these:

function setOption() {  
  msg[this.name] = this.value
  toggle()
}

options.forEach(option => option.addEventListener('change', setOption))  
Hook the Buttons Up

The final task is to hook the buttons up.

First, eventListener. Then call toggle. Done.

speakButton.addEventListener('click', toggle)  
stopButton.addEventListener('click', () => toggle(false))

Now we have working Speech Synthesis!

You can play around with the Speech Synthesis project here.

You can keep track of all the projects in this JavaScript30 challenge here.