Skip to content

Instantly share code, notes, and snippets.

@leegee
Last active February 27, 2026 23:18
Show Gist options
  • Select an option

  • Save leegee/c44100ce7c5b23f802c517d20d1c1042 to your computer and use it in GitHub Desktop.

Select an option

Save leegee/c44100ce7c5b23f802c517d20d1c1042 to your computer and use it in GitHub Desktop.
Speak IPA
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IPA Pronunciation</title>
<meta name="author" content="cv@lee.goddards.space">
<link href="https://cdn.jsdelivr.net/npm/beercss@4.0.16/dist/cdn/beer.min.css" rel="stylesheet" />
<script type="module" src="https://cdn.jsdelivr.net/npm/beercss@4.0.16/dist/cdn/beer.min.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/material-dynamic-colors@1.1.4/dist/cdn/material-dynamic-colors.min.js"></script>
</style>
</head>
<body>
<main class="responsive">
<article class="border large-padding large-margin">
<div class="padding primary-container">
<h1 class="small-opacity center-align">IPA Text Approximation</h1>
<nav class="no-space large-padding">
<div class="field max border left-round extra">
<input style="font-size:200%" type="text" id="ipaInput" value="/tʃiːz/">
</div>
<button class="large right-round slow-ripple extra" onclick="pronounceText()">
<i class="extra" style="font-size:200%">voice_chat</i>
</button>
</nav>
<div class="field middle-align large-padding large-margin">
<div class="slider small medium-opacity">
<input type="range" id="rateRange" min="0.5" max="2" step="0.05" value="0.9"/>
<span></span>
</div>
<output class="medium-opacity">
Speaking speed <span id="rateValue">0.9</span>
</output>
</div>
</div>
</article>
</main>
<script>
const voices = speechSynthesis.getVoices();
const ipaText = document.getElementById("ipaInput").value;
const rateRange = document.getElementById("rateRange");
const rateValue = document.getElementById("rateValue");
rateRange.addEventListener("input", () => rateValue.textContent = rateRange.value );
function pronounceText() {
const utterance = new SpeechSynthesisUtterance(ipaToPhoneticEnglish(
ipaText.replace(/\//g, "")
));
if (voices.length > 0) utterance.voice = voices[0];
utterance.rate = parseFloat(rateRange.value);
speechSynthesis.speak(utterance);
}
// Convert IPA symbols to approximate English phonetics
function ipaToPhoneticEnglish(ipaText) {
return ipaText
// Consonants
.replace(/p/g, "p")
.replace(/b/g, "b")
.replace(/t/g, "t")
.replace(/d/g, "d")
.replace(/k/g, "k")
.replace(/g/g, "g")
.replace(/f/g, "f")
.replace(/v/g, "v")
.replace(/θ/g, "th")
.replace(/ð/g, "th")
.replace(/s/g, "s")
.replace(/z/g, "z")
.replace(/ʃ/g, "sh")
.replace(/ʒ/g, "zh")
.replace(/h/g, "h")
.replace(/tʃ/g, "ch")
.replace(/dʒ/g, "j")
.replace(/m/g, "m")
.replace(/n/g, "n")
.replace(/ŋ/g, "ng")
.replace(/l/g, "l")
.replace(/r/g, "r")
.replace(/j/g, "y")
.replace(/w/g, "w")
// Vowels
.replace(/iː/g, "ee")
.replace(/ɪ/g, "i")
.replace(/eɪ/g, "ay")
.replace(/ɛ/g, "e")
.replace(/æ/g, "a")
.replace(/ɑː/g, "ah")
.replace(/ɒ/g, "o")
.replace(/ɔː/g, "or")
.replace(/oʊ/g, "oh")
.replace(/ʊ/g, "oo")
.replace(/uː/g, "oo")
.replace(/ʌ/g, "uh")
.replace(/ɜː/g, "er")
.replace(/ə/g, "uh")
.replace(/aɪ/g, "eye")
.replace(/aʊ/g, "ow")
.replace(/ɔɪ/g, "oy")
// Remove stress marks and length marks
.replace(/[ˈˌ]/g, "")
.replace(/ː/g, "");
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment