summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2024-05-06 18:03:34 -0500
committersanine <sanine.not@pm.me>2024-05-06 18:03:34 -0500
commit83606cd57dbfdad8a1fa0eaef2ee127f7fc2ba2c (patch)
treeede1cc779e0c0faab0511ab802dd1b14afff4114
parent345b7daf4509ac89f70790c71cb2cba9b7a4f300 (diff)
add basic consonant features
-rw-r--r--src/language.test.ts38
-rw-r--r--src/language.ts379
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 '';
+ }
+ }
+}