diff options
author | sanine <sanine.not@pm.me> | 2024-05-06 18:03:34 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2024-05-06 18:03:34 -0500 |
commit | 83606cd57dbfdad8a1fa0eaef2ee127f7fc2ba2c (patch) | |
tree | ede1cc779e0c0faab0511ab802dd1b14afff4114 | |
parent | 345b7daf4509ac89f70790c71cb2cba9b7a4f300 (diff) |
add basic consonant features
-rw-r--r-- | src/language.test.ts | 38 | ||||
-rw-r--r-- | src/language.ts | 379 |
2 files changed, 417 insertions, 0 deletions
diff --git a/src/language.test.ts b/src/language.test.ts index e9845a1..d3c6f23 100644 --- a/src/language.test.ts +++ b/src/language.test.ts @@ -31,3 +31,41 @@ test('correctly output basic vowels', () => { })).toBe('eː'); }); + + +test('correctly output basic consonants', () => { + expect(lang.consonantFeaturesToIpa({ + place: lang.ConsonantPlace.Bilabial, + manner: lang.ConsonantManner.Nasal, + voice: true, + })).toBe('m'); + expect(lang.consonantFeaturesToIpa({ + place: lang.ConsonantPlace.Uvular, + manner: lang.ConsonantManner.NonSibilantFricative, + voice: true, + })).toBe('ʁ'); + expect(lang.consonantFeaturesToIpa({ + place: lang.ConsonantPlace.Velar, + manner: lang.ConsonantManner.Plosive, + voice: false, + })).toBe('k'); +}); + + +test('fail to output impossible consonants', () => { + expect(lang.consonantFeaturesToIpa({ + place: lang.ConsonantPlace.Retroflex, + manner: lang.ConsonantManner.NonSibilantAffricate, + voice: false, + })).toBe(''); + expect(lang.consonantFeaturesToIpa({ + place: lang.ConsonantPlace.Labiodental, + manner: lang.ConsonantManner.Trill, + voice: true, + })).toBe(''); + expect(lang.consonantFeaturesToIpa({ + place: lang.ConsonantPlace.Palatal, + manner: lang.ConsonantManner.Approximant, + voice: false, + })).toBe(''); +}); diff --git a/src/language.ts b/src/language.ts index 2d0f547..0721e09 100644 --- a/src/language.ts +++ b/src/language.ts @@ -19,6 +19,8 @@ export interface VowelFeatures { long: boolean; nasal: boolean; } + +// convert a set of vowel features to an IPA representation export function vowelFeaturesToIpa(features: VowelFeatures): string { const ipa = () => { switch (features.height) { @@ -101,3 +103,380 @@ export function vowelFeaturesToIpa(features: VowelFeatures): string { const vlong = features.long ? 'ː' : ''; return ipa() + nasal + vlong; } + + +export enum ConsonantPlace { + Bilabial, + Labiodental, + Linguolabial, + Dental, + Alveolar, + Postalveolar, + Retroflex, + Palatal, + Velar, + Uvular, + Pharyngeal, + Glottal, +} +export enum ConsonantManner { + Nasal, + Plosive, + SibilantAffricate, + NonSibilantAffricate, + SibilantFricative, + NonSibilantFricative, + Approximant, + Tap, + Trill, + LateralAffricate, + LateralFricative, + LateralApproximant, + LateralTap, +} +export interface ConsonantFeatures { + place: ConsonantPlace; + manner: ConsonantManner; + voice: boolean; +} + + +export function consonantFeaturesToIpa(features: ConsonantFeatures): string { + switch (features.place) { + case ConsonantPlace.Bilabial: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'm' : 'm̥'; + case ConsonantManner.Plosive: + return features.voice ? 'b' : 'p'; + case ConsonantManner.SibilantAffricate: + return ''; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? 'bβ' : 'pɸ'; + case ConsonantManner.SibilantFricative: + return ''; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'β' : 'ɸ'; + case ConsonantManner.Approximant: + return features.voice ? 'β̞' : ''; + case ConsonantManner.Tap: + return features.voice ? 'ⱱ̟' : ''; + case ConsonantManner.Trill: + return features.voice ? 'ʙ' : 'ʙ̥'; + case ConsonantManner.LateralAffricate: + case ConsonantManner.LateralFricative: + case ConsonantManner.LateralApproximant: + case ConsonantManner.LateralTap: + return ''; + } + + case ConsonantPlace.Labiodental: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'ɱ' : 'ɱ̊'; + case ConsonantManner.Plosive: + return features.voice ? 'b̪' : 'p̪'; + case ConsonantManner.SibilantAffricate: + return ''; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? 'b̪v' : 'p̪f'; + case ConsonantManner.SibilantFricative: + return ''; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'v' : 'f'; + case ConsonantManner.Approximant: + return features.voice ? 'ʋ' : ''; + case ConsonantManner.Tap: + return features.voice ? 'ⱱ' : ''; + case ConsonantManner.Trill: + case ConsonantManner.LateralAffricate: + case ConsonantManner.LateralFricative: + case ConsonantManner.LateralApproximant: + case ConsonantManner.LateralTap: + return ''; + } + + case ConsonantPlace.Linguolabial: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'n̼' : ''; + case ConsonantManner.Plosive: + return features.voice ? 'd̼' : 't̼'; + case ConsonantManner.SibilantAffricate: + case ConsonantManner.NonSibilantAffricate: + case ConsonantManner.SibilantFricative: + return ''; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ð̼' : 'θ̼'; + case ConsonantManner.Approximant: + return ''; + case ConsonantManner.Tap: + return features.voice ? 'ɾ̼' : ''; + case ConsonantManner.Trill: + case ConsonantManner.LateralAffricate: + case ConsonantManner.LateralFricative: + case ConsonantManner.LateralApproximant: + case ConsonantManner.LateralTap: + return ''; + } + + case ConsonantPlace.Dental: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'n' : 'n̥'; + case ConsonantManner.Plosive: + return features.voice ? 'd̪' : 't̪'; + case ConsonantManner.SibilantAffricate: + return features.voice ? 'd̪z̪' : 't̪s̪'; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? 'd̪ð' : 't̪θ'; + case ConsonantManner.SibilantFricative: + return features.voice ? 'z' : 's'; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ð' : 'θ'; + case ConsonantManner.Approximant: + return features.voice ? 'ð̞' : ''; + case ConsonantManner.Tap: + return features.voice ? 'ɾ' : 'ɾ̥'; + case ConsonantManner.Trill: + return features.voice ? 'r' : 'r̥'; + case ConsonantManner.LateralAffricate: + return features.voice ? 'dɮ' : 'tɬ'; + case ConsonantManner.LateralFricative: + return features.voice ? 'ɮ' : 'ɬ'; + case ConsonantManner.LateralApproximant: + return features.voice ? 'l̪' : ''; + case ConsonantManner.LateralTap: + return features.voice ? 'ɺ' : 'ɺ̥'; + } + + case ConsonantPlace.Alveolar: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'n' : 'n̥'; + case ConsonantManner.Plosive: + return features.voice ? 'd' : 't'; + case ConsonantManner.SibilantAffricate: + return features.voice ? 'dz' : 'ts'; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? 'dɹ̝' : 'tɹ̝̊'; + case ConsonantManner.SibilantFricative: + return features.voice ? 'z' : 's'; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ð̠' : 'θ̠'; + case ConsonantManner.Approximant: + return features.voice ? 'ɹ' : ''; + case ConsonantManner.Tap: + return features.voice ? 'ɾ' : 'ɾ̥'; + case ConsonantManner.Trill: + return features.voice ? 'r' : 'r̥'; + case ConsonantManner.LateralAffricate: + return features.voice ? 'dɮ' : 'tɬ'; + case ConsonantManner.LateralFricative: + return features.voice ? 'ɮ' : 'ɬ'; + case ConsonantManner.LateralApproximant: + return features.voice ? 'l' : ''; + case ConsonantManner.LateralTap: + return features.voice ? 'ɺ' : 'ɺ̥'; + } + + case ConsonantPlace.Postalveolar: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'n' : 'n̥'; + case ConsonantManner.Plosive: + return features.voice ? 'd' : 't'; + case ConsonantManner.SibilantAffricate: + return features.voice ? 'd̠ʒ' : 't̠ʃ'; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? 'd̠ɹ̠˔' : 't̠ɹ̠̊˔'; + case ConsonantManner.SibilantFricative: + return features.voice ? 'ʒ' : 'ʃ'; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ɹ̠˔' : 'ɹ̠̊˔'; + case ConsonantManner.Approximant: + return features.voice ? 'ɹ̠' : ''; + case ConsonantManner.Tap: + return features.voice ? 'r̠' : 'ɾ̥'; + case ConsonantManner.Trill: + return features.voice ? 'r' : 'r̥'; + case ConsonantManner.LateralAffricate: + return features.voice ? 'dɮ' : 'tɬ'; + case ConsonantManner.LateralFricative: + return features.voice ? 'ɮ' : 'ɬ'; + case ConsonantManner.LateralApproximant: + return features.voice ? 'l̠' : ''; + case ConsonantManner.LateralTap: + return features.voice ? 'ɺ' : 'ɺ̥'; + } + + case ConsonantPlace.Retroflex: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'ɳ' : 'ɳ̊'; + case ConsonantManner.Plosive: + return features.voice ? 'ɖ' : 'ʈ'; + case ConsonantManner.SibilantAffricate: + return features.voice ? 'dʐ' : 'tʂ'; + case ConsonantManner.NonSibilantAffricate: + return ''; + case ConsonantManner.SibilantFricative: + return features.voice ? 'ʐ' : 'ʂ'; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ɻ˔' : 'ɻ̊˔'; + case ConsonantManner.Approximant: + return features.voice ? 'ɻ' : ''; + case ConsonantManner.Tap: + return features.voice ? 'ɽ' : 'ɽ̊'; + case ConsonantManner.Trill: + return features.voice ? 'ɽr' : 'ɽ̊r̥'; + case ConsonantManner.LateralAffricate: + return features.voice ? 'd𝼅' : 'tꞎ'; + case ConsonantManner.LateralFricative: + return features.voice ? '𝼅' : 'ꞎ'; + case ConsonantManner.LateralApproximant: + return features.voice ? 'ɭ' : ''; + case ConsonantManner.LateralTap: + return features.voice ? '𝼈' : '𝼈̥'; + } + + case ConsonantPlace.Palatal: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'ɲ' : 'ɲ̊'; + case ConsonantManner.Plosive: + return features.voice ? 'ɟ' : 'c'; + case ConsonantManner.SibilantAffricate: + return features.voice ? 'dʑ' : 'tɕ'; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? 'ɟʝ' : 'cç'; + case ConsonantManner.SibilantFricative: + return features.voice ? 'ʑ' : 'ɕ'; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ʝ' : 'ç'; + case ConsonantManner.Approximant: + return features.voice ? 'j' : ''; + case ConsonantManner.Tap: + case ConsonantManner.Trill: + return ''; + case ConsonantManner.LateralAffricate: + return features.voice ? 'ɟʎ̝' : 'c𝼆'; + case ConsonantManner.LateralFricative: + return features.voice ? 'ʎ̝' : '𝼆'; + case ConsonantManner.LateralApproximant: + return features.voice ? 'ʎ' : ''; + case ConsonantManner.LateralTap: + return features.voice ? 'ʎ̆' : ''; + } + + case ConsonantPlace.Velar: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'ŋ' : 'ŋ̊'; + case ConsonantManner.Plosive: + return features.voice ? 'g' : 'k'; + case ConsonantManner.SibilantAffricate: + return ''; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? 'ɡɣ' : 'kx'; + case ConsonantManner.SibilantFricative: + return ''; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ɣ' : 'x'; + case ConsonantManner.Approximant: + return features.voice ? 'ɰ' : ''; + case ConsonantManner.Tap: + case ConsonantManner.Trill: + return ''; + case ConsonantManner.LateralAffricate: + return features.voice ? 'ɡʟ̝' : 'k𝼄'; + case ConsonantManner.LateralFricative: + return features.voice ? 'ʟ̝' : '𝼄'; + case ConsonantManner.LateralApproximant: + return features.voice ? 'ʟ' : ''; + case ConsonantManner.LateralTap: + return features.voice ? 'ʟ̆' : ''; + } + + case ConsonantPlace.Uvular: + switch (features.manner) { + case ConsonantManner.Nasal: + return features.voice ? 'ɴ' : 'ɴ̥'; + case ConsonantManner.Plosive: + return features.voice ? 'ɢ' : 'q'; + case ConsonantManner.SibilantAffricate: + return ''; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? 'ɢʁ' : 'qχ'; + case ConsonantManner.SibilantFricative: + return ''; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ʁ' : 'χ'; + case ConsonantManner.Approximant: + return features.voice ? 'ʁ̞' : ''; + case ConsonantManner.Tap: + return features.voice ? 'ɢ̆' : ''; + case ConsonantManner.Trill: + return features.voice ? 'ʀ' : 'ʀ̥'; + case ConsonantManner.LateralAffricate: + case ConsonantManner.LateralFricative: + return ''; + case ConsonantManner.LateralApproximant: + return features.voice ? 'ʟ̠' : ''; + case ConsonantManner.LateralTap: + return ''; + } + + case ConsonantPlace.Pharyngeal: + switch (features.manner) { + case ConsonantManner.Nasal: + return ''; + case ConsonantManner.Plosive: + return features.voice ? '' : 'ʡ'; + case ConsonantManner.SibilantAffricate: + return ''; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? 'ʡʢ' : 'ʡʜ'; + case ConsonantManner.SibilantFricative: + return ''; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ʕ' : 'ħ'; + case ConsonantManner.Approximant: + return ''; + case ConsonantManner.Tap: + return features.voice ? 'ʡ̆' : ''; + case ConsonantManner.Trill: + return features.voice ? 'ʢ' : 'ʜ'; + case ConsonantManner.LateralAffricate: + case ConsonantManner.LateralFricative: + case ConsonantManner.LateralApproximant: + case ConsonantManner.LateralTap: + return ''; + } + + case ConsonantPlace.Glottal: + switch (features.manner) { + case ConsonantManner.Nasal: + return ''; + case ConsonantManner.Plosive: + return features.voice ? '' : 'ʔ'; + case ConsonantManner.SibilantAffricate: + return ''; + case ConsonantManner.NonSibilantAffricate: + return features.voice ? '' : 'ʔh'; + case ConsonantManner.SibilantFricative: + return ''; + case ConsonantManner.NonSibilantFricative: + return features.voice ? 'ɦ' : 'h'; + case ConsonantManner.Approximant: + return features.voice ? 'ʔ̞' : ''; + case ConsonantManner.Tap: + case ConsonantManner.Trill: + case ConsonantManner.LateralAffricate: + case ConsonantManner.LateralFricative: + case ConsonantManner.LateralApproximant: + case ConsonantManner.LateralTap: + return ''; + } + } +} |