A pure-Python IPA (International Phonetic Alphabet) phonetic toolkit: phonetic features, distances, natural classes, and conversion between IPA and CMU ARPABET, X-SAMPA, Kirshenbaum, and TIMIT notations.
- Zero runtime dependencies — all phonetic data ships as XML in the package.
- Typed (
py.typed, mypy-strict clean). - Both a library and a CLI (
ipakit).
pip install ipakitFor development (tests, linters, and the X-SAMPA table tooling):
pip install -e ".[dev]"import ipakit
# Phonetic features and descriptions
ipakit.describe("p") # 'voiceless bilabial plosive'
ipakit.features("p") # {'manner': 'plosive', 'place': 'bilabial', ...}
# Phonetic distance (0.0 identical … 1.0 maximally different)
ipakit.distance("p", "b") # 0.043 (differ only in voicing)
ipakit.nearest_phones("p", n=3) # [('ɸ', 0.005), ('f', 0.008), ('p͡f', 0.008)]
ipakit.word_similarity("kæt", "kæd") # 0.986
# Tokenize / normalize (tie-bar affricates, diphthongs)
ipakit.tokenize("t͡ʃe͡ɪnd͡ʒ") # ['t͡ʃ', 'e͡ɪ', 'n', 'd͡ʒ']
# Validate
ipakit.validate_ipa("kæt") # [] (valid)
ipakit.validate_ipa("k4t") # [{'type': 'error', 'code': 'unknown_symbol', ...}]# CMU ARPABET
ipakit.to_cmu("ˈkæt") # ['K', 'AE1', 'T']
ipakit.to_ipa(["K", "AE1", "T"]) # 'kˈæt'
# X-SAMPA (ASCII)
ipakit.ipa_to_xsampa("t͡ʃ") # 't_S'
ipakit.xsampa_to_ipa("t_S") # 't͡ʃ'
# Kirshenbaum / TIMIT
ipakit.to_kirshenbaum("kæt") # 'k&t'
ipakit.to_timit("kæt") # ['k', 'ae', 't']
# Features straight from a non-IPA symbol (list of per-segment dicts)
ipakit.features_from_xsampa("t_S") # [{'manner': 'affricate', 'place': 'postalveolar', ...}]
ipakit.features_from_cmu("K") # [{'manner': 'plosive', 'place': 'velar', ...}]By default converters skip symbols they can't map. Pass strict=True to any of
them to raise ValueError on unconvertible input instead:
ipakit.to_cmu("k4t") # ['K', 'T'] (the '4' is skipped)
ipakit.to_cmu("k4t", strict=True) # ValueError: Cannot convert to CMU ARPABET: ...distance() is the raw feature metric — an absolute, inventory-independent
mean over phonetic features (so distance("p", "b") is always 0.043). Raw
distances bunch up in a narrow band, which makes fixed thresholds hard to pick.
normalized_distance() renormalizes a raw distance to its percentile within
the bundled IPA inventory's pairwise distribution, spreading values across
[0, 1]:
ipakit.distance("p", "b") # 0.043 raw feature distance
ipakit.normalized_distance("p", "b") # 0.155 percentile within bundled IPA
ipakit.normalized_distance("p", "a") # 0.602
ipakit.confusability("p", "b") # 0.845 complement of normalized_distanceFor a model over a chosen reference inventory — percentiles are relative to
it and not comparable across inventories — use distance_model():
from ipakit import Phoneset
eng = ipakit.distance_model(
Phoneset.from_list(
["p", "b", "t", "d", "k", "ɡ", "s", "z", "m", "n", "l", "ɹ", "a", "i", "u"],
name="english",
)
)
eng.distance("p", "b") # 0.267 percentile within this 15-phone set
eng.nearest("p", n=3) # [('t', 0.048), ('s', 0.086), ('k', 0.21)]
eng.word_similarity("kæt", "kæd") # 0.956
eng.is_similar("kæt", "kæd", threshold=0.8) # Truedistance_model() also accepts gamma (power transform to push dissimilar
pairs apart), sub_mode="di" (delete+insert substitution cost for word
alignment), and threshold / max_length_ratio defaults for is_similar. The
raw pairwise matrix ships as ipakit/data/confusion.json; per-inventory models
reuse it and only re-slice the percentile distribution.
- Stress is placed on the vowel (the syllable nucleus), not the syllable
onset:
to_ipa(["K", "AE1", "T"])→kˈæt. Syllabification is preserved across round trips (W AO1 T ER0↔wˈɔtɚ). - Affricates and diphthongs use the tie bar (
t͡ʃ,e͡ɪ). - Round-trip guarantee (X-SAMPA only): IPA written in these conventions
round-trips through X-SAMPA (
ipa → xsampa → ipa). The only exceptions areb͡vandt͡θ, where the X-SAMPA tie encoding_collides with a diacritic/tone encoding (_v,_T) — an inherent X-SAMPA ambiguity that ICU shares. The CMU, TIMIT, and Kirshenbaum mappings are lossy (they collapse IPA distinctions) and carry no round-trip guarantee.
ipakit features p # Get features for 'p'
ipakit describe p # "voiceless bilabial plosive"
ipakit convert to-cmu "kˈæt" # IPA to CMU: K AE1 T (stress on the vowel)
ipakit convert to-ipa K AE1 T # CMU to IPA: kˈæt
ipakit convert to-xsampa "t͡ʃ" # IPA to X-SAMPA: t_S
ipakit query match plosive bilabial # Find phones by feature
ipakit analysis natural-class p t k # Shared features of a set
ipakit analysis minimal-pairs p # Find similar phones
ipakit distance pair p b # Raw feature distance: ~0.04
ipakit distance confusability p b # Inventory-relative: 0.8454
ipakit distance word kæt kæd # Word similarity: 0.9742
The distance confusability/word commands use the distribution-aware model;
scope them to a reference inventory with --phoneset FILE (one phone per line).
Most commands accept --format json (or -j) for machine-readable output.
Run ipakit, ipakit <group>, or append help/-h anywhere for usage.
pip install -e ".[dev]" # or ".[test]" / ".[lint]" for a lean subset
pre-commit install # black, ruff, mypy --strict, hygiene hooks
pytest # unit tests + docstring examplesCI (.github/workflows/ci.yml) mirrors these on every push/PR across Python
3.11–3.13, and validates the committed derived artifacts (the IPA ↔ X-SAMPA
table and the phone-distance matrix) against their generators in scripts/.
BSD 2-Clause — see LICENSE.