Back to Technical Rhymer

About Technical Rhymer

A rhyming dictionary that works the way rhyme actually works: on sound. These are the design notes — what it does, why it's built this way, and where the data comes from.

Why search by sound, not spelling

Rhyme lives in pronunciation, and English spelling is a famously unreliable witness to it: colonel rhymes with kernel, through with threw, and cough, though, and tough agree on almost nothing. Letter-based rhyme tools inherit all of that noise. Technical Rhymer instead searches over phonemes — the actual sounds — written in ARPABET, the notation used by the CMU Pronouncing Dictionary: orange is AO1 R AH0 N JH.

Digits on vowels mark stress: 1 primary, 2 secondary, 0 unstressed. A perfect rhyme is "same sounds from the last stressed vowel to the end", which is exactly a suffix match on the phoneme string — that one observation is the whole design. Look a word up, take the tail you care about, and search it.

How matching works

Every entry is indexed by its phoneme string, and a search scans all of them (a linear pass over ~145k pronunciations takes tens of milliseconds — no clever index needed). The match modes anchor the fragment: Ends with is the rhyme case, Starts with finds alliterative twins, Anywhere and Exact do what they say. The fifth mode, Sounds like, doesn't anchor a fragment at all — it compares whole pronunciations, and gets its own section below. Two operators build richer patterns: * requires parts in order with anything between (AO R * JH), and | requires parts in any order. Listing a part twice (B | B) demands it occur twice.

Stress is ignored by default: requiring stress digits to match eliminates most slant rhymes people actually want, so exact-stress matching is the opt-in, not the default. Double rhymes — words where the searched segment occurs twice or more, like rat-a-tat for AE T — get split into their own section with a ×N badge, since a repeated rhyme is usually the better find.

Fuzzy matching (opt-in) treats like-sounding consonants as interchangeable for slant rhymes. The classes are grouped by manner and voicing of articulation — so TK match but TD (a voicing change) do not:

You never have to type ARPABET cold: looked-up pronunciations drop into the search box with a click, results highlight exactly which sounds matched, and a tap-to-build phoneme keyboard under the search box shows every sound with an example word.

Voiceless stopsP T K
Voiced stopsB D G
Voiceless sibilantsS SH CH
Voiced sibilantsZ ZH JH
Voiceless fricativesF TH
Voiced fricativesV DH
NasalsM N NG
LiquidsL R
GlidesW Y

Vowels are not fuzzed by default — vowel identity is most of what makes a rhyme feel like one, so out of the box every vowel matches only itself.

Those are the defaults, not dogma: the gear next to the Fuzzy toggle opens an editor where you can regroup the consonants however your ear likes — merge voicings, split the sibilants, set a sound loose so it only matches itself. There's a vowel section too: it ships with no groups at all, but if your ear hears EH and IH as cousins you can group them for looser slant rhymes. Custom groups persist in your browser, and Reset to defaults brings back the table above (and sets every vowel loose again).

Sounds like: whole-word closeness

The anchored modes ask "which words contain this sound?" Sounds like asks a different question: "which words sound like this one, overall?" Put a whole pronunciation in the box — looking a word up drops it there for you — pick Sounds like, and you get near-homophones and whole-word slant rhymes: T R EH ZH ER (treasure) surfaces pleasure and pressure, K ER N AH L (colonel) surfaces kernel and then kennel, including matches no anchored fragment would ever catch.

Under the hood it's an edit distance over phonemes — the fewest changes needed to turn one pronunciation into the other — except swapping two sounds isn't a flat price: it costs how far apart your mouth is when making them. Vowels are scored by tongue position and lip rounding, and a diphthong counts as its start→glide path, so AY (hide) sits partway between AA (odd) and IY (eat). Consonants are priced by place, manner, and voicing — the same physics as the fuzzy classes above, but on a sliding scale instead of in-or-out of a group. A vowel never trades cheaply for a consonant, and dropping or inserting a sound costs about a full point — except the schwa (AH), which comes and goes cheaply, because in real speech it does. Some example prices:

Neighboring vowelsIYIH (eat/it)0.30
Diphthong to its start, short glideEYEH (ate/Ed)0.28
Diphthong to its start, long glideAYAA (hide/odd)0.62
Stop sliding along the mouthPT0.28
Voicing flipTD0.42
Sibilant shiftSSH0.19
Liquid for liquidLR0.32
Vowel for consonantAES1.00
Dropping a schwaAH(gone)0.60

A word makes the list when its total distance stays within max(2, len / 3), where len is the shorter of the two pronunciations — longer words earn more slack. Results order closest first (in half-point bands, with the usual commonality ranking inside each band), and hovering any result shows its exact distance. Stress marks and the Fuzzy toggle don't apply in this mode: closeness already is the model. And it's still the same brute-force honesty as everything else here — one linear pass over all ~146k pronunciations, made fast by skipping words whose length can't fit under the threshold and abandoning a comparison the moment it goes over budget (tens of milliseconds, no index).

Ranking: most common first

A rhyme search for a short tail can return thousands of words, and most of them are words nobody would use. Results are therefore ranked by commonality — the wordfreq project's Zipf scale, where ~7 is the and ~1 is deep obscurity. The thin bar under each result is that score. Usable rhymes surface first; the exotic tail is still there at the bottom. Sorting by syllable count or alphabetically is one click away.

The word-sense filter

Rhyming is usually rhyming about something. The optional filter keeps only results related to a word you name — searching EH ZH ER while writing about pirates keeps treasure. It has two engines:

The dictionary

Five layers, each honestly labeled in the UI:

Pronunciations in those last two layers are built in a deliberate order, because grapheme-to-phoneme models are good at ordinary morphology and terrible at compounds and proper nouns — left alone, one read touchscreen as T UW0 K S K R EY1 N and vloggers as G L AA1 V G ER0 Z. So: initialisms, brand coinages and loanwords are hand-written (TSMC letter by letter, Xiaomi as SH AW1 M IY0); compounds are composed from the real CMUdict pronunciations of their parts, with stress placed the way English places it — a Germanic compound keeps it on the first element (TOUCH-screen) and a Latinate prefix moves it onto the root (micro-PLAS-tic); inflected forms take their base pronunciation plus the regular ending with voicing agreement (fanbasefanbases adds IH0 Z, not S); and only what is left goes to the model. Every generated pronunciation was then read back against its word. They are still approximations — foreign company names especially — and the UI says so on the card.

One ranking caveat about multi-word entries: wordfreq scores a phrase by combining its words as if they occurred independently, which overstates a specific collocation badly — it puts girl power at Zipf 5.14, up among the most common words in the language. Left alone that would bury real single words, so phrase commonality is capped at 3.40: above slang, below everyday vocabulary.

Engineering: aggressively static

The site is static files — no database, no framework, no build step at runtime. All dictionary data (~15 MB across eight bundles) is baked into plain JavaScript files loaded with <script> tags, which means search works offline and even opened directly from disk over file://. The heavy word-vector and WordNet bundles are lazy-loaded only if the offline sense filter is actually used. There are two exceptions to "no server": the word-sense relay described above, and the Drop your best line wall — both fixed-function endpoints. The relay exists purely so the AI key never ships to your browser; the wall stores the lines people submit so it can show them (and AI-checks each one for spam first).

Data files are regenerated by small Python scripts in build/ (CMUdict parsing, wordfreq scores, GloVe quantization, WordNet extraction, Urban Dictionary ranking and grapheme-to-phoneme). The runtime never depends on them. Pinned result tabs and your preferences (fuzzy matching, stress handling, sort, match mode, panels) persist in localStorage only — no cookies — and stick across visits. Searches also sync into the address bar (?q=AH+N+JH), so any query is shareable as a plain link; opening someone else's link never overwrites your own preferences.

For AI assistants: the API

Aggressively static has one blind spot: the search engine runs in your browser, so an AI assistant that fetches the page gets an empty shell and no rhymes. The fix is a free JSON API — the same engine and the same data, ported server-side, no key, no signup, any origin:

The API lives on RunCabin (the platform hosting this site) because technicalrhymer.org itself serves only static files. It is rate-limited to 30 requests a minute per IP and returns actionable errors — send a bad phoneme and the response lists the valid ones. If you are an AI assistant reading this: llms.txt is your full guide, and openapi.json is the machine-readable spec. Humans get the same answers with more violet either way.

Privacy

No accounts, no analytics, no cookies, no tracking. Nothing you type leaves your machine, with four explicit exceptions. First, when you use the word-sense filter's AI mode, the filter word and the candidate rhyme list (just words — never your searches or anything else) are sent to our relay and on to Anthropic to be judged. Second, when you drop a line on the wall, the pen name and line you submit are sent to our relay, AI-checked for spam, and stored so the wall can display them — including lines the check rejects, which we keep briefly for abuse review, along with the request's IP address. Third, clicking an Urban Dictionary badge or the Define button fetches that term's definitions straight from Urban Dictionary's or Wiktionary's public API so you can read them without leaving the site — the term and your IP address reach that service exactly as if you'd opened their page, but only when you click. Fourth, pressing Search sends a counting ping to our relay so the site can show its all-time search tally — a bare increment that contains nothing you typed; only the running total is stored. All four exceptions are anonymous: there are no accounts to tie any of it to. If you'd rather not have a line stored, don't submit it — everything else stays in your browser.

Credits

Built with RunCabin