pronouncing
v0.0.2
Published
a simple interface to the CMU pronouncing dictionary
Readme
Pronouncing TS
Intro, Tutorial, and Reference
This is a TypeScript port and optimization of Pronouncing, facilitated by Aleksey Calvin Tsukanov on the behalf of SilverAgePoets.com.
The Pronouncing library, originally for Python and JavaScript, is a work of Allison Parrish, the living legend of auto-frankensteining poetics.
Parrish describes Pronouncing as a "simple interface to the CMU Pronouncing Dictionary", but is clearly being too modest.
Pronouncing "interfaces" the CMU dictionary from top to bottom, bottom to top, inside and out, from inside any output, or vice versa, or whilst revising, or versing, while somersaulting on a tightrope with an arsenal of magic wands, selectively, tactically, like a piece of software, and in most other ways... probably... or at least probabilistically.
This document serves as a reference specifically for the TypeScript / Node.js version of Pronouncing. It is adapted from Parrish's O.G. tutorial for the Python version, translated to fit the JavaScript API; primarily via converting function names into the camelCase, as per Parrish's note on the pronouncingjs repository door.
Installation and Setup
Node.js / npm
To use the library in Node.js, install it from npm or directly from the GitHub repository:
# From npm
npm install pronouncingts
# Or directly from GitHub (installs as 'pronouncing' in node_modules)
npm install github:alekseycalvin/pronouncingtsTo use directly in your terminal, launch node.
Then you ought to import the library in your script (or the CLI/Terminal for iterative or exploratory usage). The dictionary data is loaded automatically when the module is required or imported.
TypeScript / ESM:
import * as pronouncing from 'pronouncing';CommonJS:
const pronouncing = require('pronouncing');Or:
var pronouncing = require('pronouncing');Browser (Web Pages)
If you are building a website, you do not need Node.js or npm.
You use the pre-compiled "browserified" build.
- Download the file
pronouncing-browser.jsfrom the repository'sdist/directory. - Place it in your web project folder (e.g.,
/js/pronouncing-browser.js). - Include it in your HTML file using a
<script>tag.
Example HTML Usage:
<!DOCTYPE html>
<html>
<head>
<!-- Include the library -->
<script src="js/pronouncing-browser.js"></script>
</head>
<body>
<script>
// The library is now available as a global variable 'pronouncing'
var phones = pronouncing.phonesForWord("hello");
console.log(phones);
</script>
</body>
</html>Note: In the browser environment, you use var or window.pronouncing to access the library, as require is not available unless you are using a bundler like Webpack or Browserify yourself.
Core Functions
Word Pronunciations
To get the pronunciation for a given word, use the phonesForWord function.
It returns an array of all pronunciations found in the CMU pronouncing dictionary.
Function: pronouncing.phonesForWord(word)
- Input:
string(word) - Returns:
Array<string>(phones)
Returns an array of pronunciation strings for the given word. Pronunciations are given using a special phonetic alphabet known as ARPAbet.
> pronouncing.phonesForWord("permit");
[ 'P ER0 M IH1 T', 'P ER1 M IH2 T' ]pronouncing.phonesForWord("adverse")
// [ 'AE0 D V ER1 S', 'AE1 D V ER2 S', 'AE2 D V ER1 S' ]Each token in a pronunciation string is called a "phone." The numbers after the vowels indicate the degree of its stress in a normative pronounciation.
1indicates primary stress2indicates secondary stress0indicates unstressed This vowel-stress typically corresponds to the syllabic stress within a word. And syllabic stress is the primary measure of phonetic/prosodic meter (which may or may not exactly align with poetic meters, but that's its own distinct and specialized problematic which this library is not equipped to handle in and of itself).
Sometimes, the CMU pronouncing dictionary offers more than one pronunciation for the same word.
"Permit" is a good example: it can be pronounced either with the stress on the first syllable ("do you have a permit to program here?") or on the second syllable ("will you permit me to program here?").
For this reason, the phonesForWord function returns a list of possible pronunciations.
(You'll need to come up with your own criteria for deciding which pronunciation is best for your purposes.)
Pronunciations use the ARPAbet phonetic alphabet. Numbers after vowels indicate stress:
1: Primary stress2: Secondary stress0: Unstressed
Counting Syllables
Function: pronouncing.syllableCount(phones)
Counts the number of syllables in a phone string.
- Input:
string(phones) - Returns:
number
To count syllables in a word (or text), pass a string of phones to the syllableCount function.
In practice (unless you've gots some ready-made phones), we would be chaining-up 'phonesForWord' and 'syllableCount'.
This could be done in one of several ways.
The most obvious way to do this would be via a compact one-liner like:
> pronouncing.syllableCount(pronouncing.phonesForWord("programming")[0])
3Or:
pronouncing.syllableCount(pronouncing.phonesForWord("adverse")[0])
// 2Another way is to explicitly define the output of 'phonesForWord' as 'phones' and pass it to 'syllableCount'. This could be the way to go if you're trying to do multiple things with these phones. Like this:
> const phones = pronouncing.phonesForWord("purple")[0];
> pronouncing.syllableCount(phones);
// 2
> pronouncing.stresses(phones);
// 10
> pronouncing.rhymingPart(phones)
// 'ER1 P AH0 L'But note that a 'const' binding is immutable within a certain range. So, don't expect to immediately reattach 'phones' to some other word or sequence! As such, in many cases it may be better to use 'let' instead of 'const'. After all:
> let phonetic = pronouncing.phonesForWord("programming")[0];
> pronouncing.stresses(phonetic)
// '120'
> phonetic = pronouncing.phonesForWord("whirling")[0];
// 'W ER1 L IH0 NG'Beyond this, one can pass a function call directly inside syllableCount (compact style) or store the result in a variable (expanded style).
Using let allows you to check the phones before counting.
Compact Style:
> pronouncing.syllableCount(pronouncing.phonesForWord("programming")[0])
3Expanded Style (Exploratory: real-time CLI/Terminal-use, etc...):
> let phones = pronouncing.phonesForWord("programming")[0];
> phones
'P R OW1 G R AE2 M IH0 NG'
> pronouncing.syllableCount(phones)
3On side note, many words in the CMU dictionary are polyphonetic:
> let phones = pronouncing.phonesForWord("permit");
> phones
[ 'P ER0 M IH1 T', 'P ER1 M IH2 T' ]
> phones = pronouncing.phonesForWord("record");
[ 'R EH1 K ER0 D', 'R IH0 K AO1 R D' ]Tutorial: Common Tasks
1. Calculating Most Common Sounds
You can iterate through a text to count the frequency of specific phones.
Since JavaScript does not have a built-in Counter like Python, we can use a Map or an object to aggregate counts.
const pronouncing = require('pronouncing');
let text = "april is the cruelest month breeding lilacs out of the dead";
let counts = {};
let words = text.split(" ");
for (let word of words) {
let pronunciationList = pronouncing.phonesForWord(word);
if (pronunciationList.length > 0) {
let phones = pronunciationList[0].split(" ");
for (let phone of phones) {
counts[phone] = (counts[phone] || 0) + 1;
}
}
}
// Sorting and printing the top 5 phones
// (This snippet converts the object to an array, sorts it, and slices it)
let sortedCounts = Object.entries(counts).sort((a, b) => b[1] - a[1]);
console.log(sortedCounts.slice(0, 5));Output of the 5 most common sounds/phones in the above verse-line:
[ [ 'AH0', 4 ], [ 'L', 4 ], [ 'D', 3 ], [ 'R', 3 ], [ 'DH', 2 ] ]2. Pronunciation Search
Function: pronouncing.search(pattern)
- Input:
string | RegExp - Returns:
Array<string>(matching words)
Behavior:
- If
patternis a String: The library automatically wraps it with word boundaries (\bat start and end). - If
patternis a RegExp: The library uses the regex as-is. You must add your own boundaries if needed.
The search function finds words whose pronunciation matches a regular expression.
Word boundaries (\b) are added automatically.
// String input (automatic boundaries)
pronouncing.search("^S K R AE1")
// ['scrabble', 'scragg', 'scraggle', ...]
// RegExp input (manual control)
pronouncing.search(/\bIH. \w* IH. \w* IH. \w* IH.\b/)
// ['definitive', 'definitively', ...]Finding words containing the sounds for "sighs":
> const phones = pronouncing.phonesForWord("sighs")[0];
> pronouncing.search(phones).slice(0, 5);
[ 'incise', 'incised', 'incisor', 'incisors', 'malloseismic' ]A CLI/terminal-oriented example with 'let'-style variable binding:
> let matches = pronouncing.search("^S K R AE1");
> matches.slice(0, 3);
[ 'scrabble', 'scragg', 'scraggle' ]
// Reuse 'matches' for a different search
> matches = pronouncing.search("IH1 D AH0 L$");
> matches.slice(0, 3);
[ 'biddle', 'criddle', 'fiddle' ]Finding words ending in "-iddle" (using regex $):
> pronouncing.search("IH1 D AH0 L$").slice(0, 5);
[ 'biddle', 'criddle', 'fiddle', 'friddle', 'kiddle' ]Or without the .slice(0, 5) (which just abridges the output to the first 5 matches):
pronouncing.search("IH1 D AH0 L$")
[
'biddle', 'criddle', 'fiddle',
'friddle', 'kiddle', 'liddell',
'liddle', 'middle', 'piddle',
'riddell', 'riddle', 'rydell',
'schmidl', 'siddall', 'siddell',
'siddle', 'spidel', 'spidell',
'twiddle', 'widdle', 'widell'
]** Comples Example: Rewriting text based on first two phones**
This rewrites an input text using words that share the first two phones with the input words.
Code:
let text = 'april is the cruelest month breeding lilacs out of the dead';
let out = [];
text.split(" ").forEach(word => {
let phones = pronouncing.phonesForWord(word);
if (phones.length > 0) {
let first2 = phones[0].split(" ").slice(0, 2).join(" ");
// Search for words starting with these two phones
let matches = pronouncing.search("^" + first2);
if (matches.length > 0) {
// Pick a random match
let randomMatch = matches[Math.floor(Math.random() * matches.length)];
out.push(randomMatch);
} else {
out.push(word);
}
} else {
out.push(word);
}
});
console.log(out.join(" "));Output (Example):
apec's isn't them kraatz muffy bronte leichliter outpacing of than delfsAdvanced Search with Regex
Finding words that match specific phonetic constraints.
The search function allows you to pass a compiled RegExp for complex logic.
// Finding words matching a complex pattern:
// Below: The phone "IH" + any char, repeated a few times...
const pattern = /\bIH. \w* IH. \w* IH. \w* IH.\b/;
const matches = pronouncing.search(pattern);
// ['definitive', 'definitively', 'diminishes', ...]3. Meter and Stress Patterns
Function: pronouncing.stresses(phones)
- Input:
string(phones) - Returns:
stringUsingstressesextracts the stress pattern of a phone string (returns a string of digits0,1,2).
> const phones = pronouncing.phonesForWord("snappiest")[0];
> pronouncing.stresses(phones);
'102'Use searchStresses to find words matching a specific stress pattern regex.
Function: pronouncing.searchStresses(pattern)
- Input:
string - Returns:
Array<string> - Note: Word boundaries (
\b) are automatically added.
Finding words that fit a specific meter (e.g., anapestic words).
// Finding words with stress pattern: unstressed, unstressed, stressed (anapest)
// at the end of the word.
const results = pronouncing.searchStresses("001$");
// ['understand', 'overcome', ...]Or simply:
> pronouncing.searchStresses("001$")
[
'abidjan', 'adoree', 'adorees', 'adrienne', 'aguillon',
'aladeen', 'aleron', 'alleyoop', 'almaguer', 'almanzar',
'almazan', 'almelund', 'amadon', 'amaral', 'andujar',
'appointee', 'appointees', 'aquilar', 'armentor', 'arreguin',
'augustin', 'avelar', 'avenall', 'avenel', 'avenell',
'averill', 'balaban', 'baldemar', 'baltazar', 'balthazor',
'banderas', 'becerril', 'bensenyore', 'bernadette', 'bernadine',
'bordenave', 'cadogan', 'carbajal', 'carreon', 'carvajal',
'casebeer', 'chandelier', 'chelyabinsk', 'christiane', 'christianne',
'comandeer', 'comandeered', 'comandeers', 'couvillion', 'couvillon',
'dameron', 'dauphinee', 'davignon', 'deguzman', 'delamar',
'delosreyes', 'demaree', 'desecrate', 'desecrate', 'desecrates',
'desecrates', 'deverell', 'disabuse', 'disabuse', 'disabused',
'disagree', 'disagreed', 'disagrees', 'disapproves', 'disbelieve',
'disconnect', 'disconnects', 'discontent', 'disembark', 'disengage',
'disengaged', 'disharoon', 'disinfect', 'disinform', 'disinvite',
'dolezal', 'dominique', 'doralin', 'dorion', 'dunlavey',
'erbakan', "erbakan's", 'erion', 'esquibel', 'esquivel',
'fatheree', 'gabaldon', 'geraldine', 'groseclose', 'hammontree',
'hardegree', 'hardigree', 'hargadon', 'hocevar', 'hohensee',
... 162 more items
]Reminder: append .slice(#, #) to the function to specify how many matches you want outputted, or which segment/slice of the output range.
Finding words with two dactyls (100100):
> pronouncing.searchStresses("100100");
[ 'afroamerican', 'afroamericans', 'interrelationship', 'overcapacity' ]Finding words consisting of two anapests (^00[12]00[12]$):
> pronouncing.searchStresses("^00[12]00[12]$");
[ 'neopositivist', 'undercapitalize', 'undercapitalized' ]Complex Example: Meter (Stress Pattern Rewrite)
This rewrites text by matching stress patterns.
Code:
let text = 'april is the cruelest month breeding lilacs out of the dead';
let out = [];
text.split(" ").forEach(word => {
let pronunciations = pronouncing.phonesForWord(word);
if (pronunciations.length > 0) {
let pat = pronouncing.stresses(pronunciations[0]);
// Find words with the exact same stress pattern
let matches = pronouncing.searchStresses("^" + pat + "$");
if (matches.length > 0) {
let randomMatch = matches[Math.floor(Math.random() * matches.length)];
out.push(randomMatch);
} else {
out.push(word);
}
} else {
out.push(word);
}
});
console.log(out.join(" "));Output (Example):
joneses kopf whats rathbun p's gavan midpoint nill goh the pont's4. Rhyme
Function: pronouncing.rhymes(word)
- Input:
string(word) - Returns:
Array<string>(rhyming words) Userhymesto get a list of words that rhyme with the input word. It checks all pronunciations of the input.
> pronouncing.rhymes("failings");
[ 'mailings', 'railings', 'tailings' ]pronouncing.rhymes("sinking")
// [ 'blinking', 'drinking', 'linking', ... ]Checking whether a word rhymes with another:
> pronouncing.rhymes("cheese").includes("wheeze");
true
> pronouncing.rhymes("cheese").includes("geese");
falseExtracting the end-rhyme portion of a word's phones:
Function: pronouncing.rhymingPart(phones)
- Input:
string(phones) It checks all pronunciations of the input. - Returns:
stringExtracts the "rhyming part" of a pronunciation (the vowel and following consonants of the last stressed syllable).
const phones = pronouncing.phonesForWord("sleekly")[0]; // "S L IY1 K L IY0"
pronouncing.rhymingPart(phones);
// "IY1 K L IY0"> const phones = pronouncing.phonesForWord("purple")[0];
> pronouncing.rhymingPart(phones);
'ER1 P AH0 L'Finding words that rhyme with 'purple':
> let phones = pronouncing.phonesForWord("purple")[0];
> let rhymePart = pronouncing.rhymingPart(phones);
> rhymePart
'ER1 P AH0 L'
// Now find words that match that specific part
> pronouncing.search(rhymePart + "$")
[ 'circle', 'hurtle', 'kerfuffle', ... ]Advanced Rhyming with rhymingPart:
If you need to find rhymes for a specific pronunciation, extract the "rhyming part" using rhymingPart and pass it to search.
> const pronunciations = pronouncing.phonesForWord("uses");
> const sss = pronouncing.rhymingPart(pronunciations[0]); // 'UW1 S IH0 Z'
> const zzz = pronouncing.rhymingPart(pronunciations[1]); // 'Y UW1 S IH0 Z'
// Find rhymes for the first pronunciation
> pronouncing.search(sss + "$").slice(0, 5);
[ "bruce's", 'juices', 'medusas', 'produces', "tuscaloosa's" ]
// Find rhymes for the second pronunciation
> pronouncing.search(zzz + "$").slice(0, 5);
[ 'abuses', 'cabooses', 'disabuses', 'excuses', 'induces' ]Complex Example: Rhyme
The following example rewrites a text, replacing each word with a rhyming word (when a rhyming word is available in the CMU dictionary).
Code:
let text = 'april is the cruelest month breeding lilacs out of the dead';
let out = [];
text.split(" ").forEach(word => {
let rhymes = pronouncing.rhymes(word);
if (rhymes.length > 0) {
let randomRhyme = rhymes[Math.floor(Math.random() * rhymes.length)];
out.push(randomRhyme);
} else {
// If no rhymes found, keep the original word
out.push(word);
}
});
console.log(out.join(" "));Output (Example):
april wiles's duh coolest month ceding pontiac's krout what've worthey wehde5. Counting Syllables in Longer Texts
This calculates the total syllable count for a string of text.
Code:
let text = "april is the cruelest month breeding lilacs out of the dead";
// Map words to phones, then map phones to counts, then sum
let totalSyllables = text.split(" ").reduce((sum, word) => {
let phones = pronouncing.phonesForWord(word);
if (phones.length > 0) {
return sum + pronouncing.syllableCount(phones[0]);
}
return sum;
}, 0);
console.log(totalSyllables);Output:
156.Additional API Reference
search(pattern) vs searchStresses(pattern)
search: Matches against the full phone string (e.g.,"P ER1 M IH2 T"). Automatically adds word boundaries.searchStresses: Matches against the stress pattern string (e.g.,"12"). Does not add word boundaries automatically, allowing you to use anchors (^,$) as needed.
parseCMU(str)
- Input:
string(CMU file content) - Returns:
Array<Array<string>>(list of[word, phones]tuples) Parses a string containing CMU dictionary data. Useful for loading custom dictionaries, though the library loads the default dictionary automatically on startup.
const data = "ADOLESCENT AE2 D AH0 L EH1 S AH0 N T";
const parsed = pronouncing.parseCMU(data);
// [['adolescent', 'AE2 D AH0 L EH1 S AH0 N T']]7. Next Steps
Hopefully this is only the beginning of your rhyme&meter-filled journey!
Pronouncing is just one possible interface for the CMU pronouncing dictionary, and you may find that for your particular purposes, a more specialized approach is necessary.
In that case, feel free to peruse the library's source code for helpful hints and tidbits.
