ipa-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
Binary file
package/core.js ADDED
@@ -0,0 +1,131 @@
1
+ // IPA elements in json notation
2
+ const core = {
3
+
4
+ //consonants
5
+ //pulmonic
6
+
7
+ //plosive
8
+ 'p': { type: "consonant", features: { manner: "plosive", place: "bilabial", voicing: "voiceless" }, airstream: "pulmonic" },
9
+ 'b': { type: "consonant", features: { manner: "plosive", place: "bilabial", voicing: "voiced" }, airstream: "pulmonic" },
10
+ 't': { type: "consonant", features: { manner: "plosive", place: "alveolar", voicing: "voiceless" }, airstream: "pulmonic" },
11
+ 'd': { type: "consonant", features: { manner: "plosive", place: "alveolar", voicing: "voiced" }, airstream: "pulmonic" },
12
+ 'ʈ': { type: "consonant", features: { manner: "plosive", place: "retroflex", voicing: "voiceless" }, airstream: "pulmonic" },
13
+ 'ɖ': { type: "consonant", features: { manner: "plosive", place: "retroflex", voicing: "voiced" }, airstream: "pulmonic" },
14
+ 'c': { type: "consonant", features: { manner: "plosive", place: "palatal", voicing: "voiceless" }, airstream: "pulmonic" },
15
+ 'ɟ': { type: "consonant", features: { manner: "plosive", place: "palatal", voicing: "voiced" }, airstream: "pulmonic" },
16
+ 'k': { type: "consonant", features: { manner: "plosive", place: "velar", voicing: "voiceless" }, airstream: "pulmonic" },
17
+ 'ɡ': { type: "consonant", features: { manner: "plosive", place: "velar", voicing: "voiced" }, airstream: "pulmonic" },
18
+ 'q': { type: "consonant", features: { manner: "plosive", place: "uvular", voicing: "voiceless" }, airstream: "pulmonic" },
19
+ 'ɢ': { type: "consonant", features: { manner: "plosive", place: "uvular", voicing: "voiced" }, airstream: "pulmonic" },
20
+ 'ʔ': { type: "consonant", features: { manner: "plosive", place: "glottal", voicing: "voiceless" }, airstream: "pulmonic" },
21
+ //nasal
22
+ 'm': { type: "consonant", features: { manner: "nasal", place: "bilabial", voicing: "voiced" }, airstream: "pulmonic" },
23
+ 'ɱ': { type: "consonant", features: { manner: "nasal", place: "labiodental", voicing: "voiced" }, airstream: "pulmonic" },
24
+ 'n': { type: "consonant", features: { manner: "nasal", place: "alveolar", voicing: "voiced" }, airstream: "pulmonic" },
25
+ 'ɳ': { type: "consonant", features: { manner: "nasal", place: "retroflex", voicing: "voiced" }, airstream: "pulmonic" },
26
+ 'ɲ': { type: "consonant", features: { manner: "nasal", place: "palatal", voicing: "voiced" }, airstream: "pulmonic" },
27
+ 'ŋ': { type: "consonant", features: { manner: "nasal", place: "velar", voicing: "voiced" }, airstream: "pulmonic" },
28
+ 'ɴ': { type: "consonant", features: { manner: "nasal", place: "uvular", voicing: "voiced" }, airstream: "pulmonic" },
29
+ //trill
30
+ 'ʙ': { type: "consonant", features: { manner: "trill", place: "bilabial", voicing: "voiced" }, airstream: "pulmonic" },
31
+ 'r': { type: "consonant", features: { manner: "trill", place: "alveolar", voicing: "voiced" }, airstream: "pulmonic" },
32
+ 'ʀ': { type: "consonant", features: { manner: "trill", place: "uvular", voicing: "voiced" }, airstream: "pulmonic" },
33
+ //tap or flap
34
+ 'ⱱ': { type: "consonant", features: { manner: "tap/flap", place: "labiodental", voicing: "voiced" }, airstream: "pulmonic" },
35
+ 'ɾ': { type: "consonant", features: { manner: "tap/flap", place: "alveolar", voicing: "voiced" }, airstream: "pulmonic" },
36
+ 'ɽ': { type: "consonant", features: { manner: "tap/flap", place: "retroflex", voicing: "voiced" }, airstream: "pulmonic" },
37
+ //fricative
38
+ 'ɸ': { type: "consonant", features: { manner: "fricative", place: "bilabial", voicing: "voiceless" }, airstream: "pulmonic" },
39
+ 'f': { type: "consonant", features: { manner: "fricative", place: "labiodental", voicing: "voiceless" }, airstream: "pulmonic" },
40
+ 'θ': { type: "consonant", features: { manner: "fricative", place: "dental", voicing: "voiceless" }, airstream: "pulmonic" },
41
+ 's': { type: "consonant", features: { manner: "fricative", place: "alveolar", voicing: "voiceless" }, airstream: "pulmonic" },
42
+ 'ʃ': { type: "consonant", features: { manner: "fricative", place: "postalveolar", voicing: "voiceless" }, airstream: "pulmonic" },
43
+ 'ʂ': { type: "consonant", features: { manner: "fricative", place: "retroflex", voicing: "voiceless" }, airstream: "pulmonic" },
44
+ 'ç': { type: "consonant", features: { manner: "fricative", place: "palatal", voicing: "voiceless" }, airstream: "pulmonic" },
45
+ 'x': { type: "consonant", features: { manner: "fricative", place: "velar", voicing: "voiceless" }, airstream: "pulmonic" },
46
+ 'χ': { type: "consonant", features: { manner: "fricative", place: "uvular", voicing: "voiceless" }, airstream: "pulmonic" },
47
+ 'ħ': { type: "consonant", features: { manner: "fricative", place: "pharyngeal", voicing: "voiceless" }, airstream: "pulmonic" },
48
+ 'h': { type: "consonant", features: { manner: "fricative", place: "glottal", voicing: "voiceless" }, airstream: "pulmonic" },
49
+ 'β': { type: "consonant", features: { manner: "fricative", place: "bilabial", voicing: "voiced" }, airstream: "pulmonic" },
50
+ 'v': { type: "consonant", features: { manner: "fricative", place: "labiodental", voicing: "voiced" }, airstream: "pulmonic" },
51
+ 'ð': { type: "consonant", features: { manner: "fricative", place: "dental", voicing: "voiced" }, airstream: "pulmonic" },
52
+ 'z': { type: "consonant", features: { manner: "fricative", place: "alveolar", voicing: "voiced" }, airstream: "pulmonic" },
53
+ 'ʒ': { type: "consonant", features: { manner: "fricative", place: "postalveolar", voicing: "voiced" }, airstream: "pulmonic" },
54
+ 'ʐ': { type: "consonant", features: { manner: "fricative", place: "retroflex", voicing: "voiced" }, airstream: "pulmonic" },
55
+ 'ʝ': { type: "consonant", features: { manner: "fricative", place: "palatal", voicing: "voiced" }, airstream: "pulmonic" },
56
+ 'ɣ': { type: "consonant", features: { manner: "fricative", place: "velar", voicing: "voiced" }, airstream: "pulmonic" },
57
+ 'ʁ': { type: "consonant", features: { manner: "fricative", place: "uvular", voicing: "voiced" }, airstream: "pulmonic" },
58
+ 'ʕ': { type: "consonant", features: { manner: "fricative", place: "pharyngeal", voicing: "voiced" }, airstream: "pulmonic" },
59
+ 'ɦ': { type: "consonant", features: { manner: "fricative", place: "glottal", voicing: "voiced" }, airstream: "pulmonic" },
60
+ // LATERAL FRICATIVES
61
+ 'ɬ': { type: "consonant", features: { manner: "lateral fricative", place: "alveolar", voicing: "voiceless" }, airstream: "pulmonic" },
62
+ 'ɮ': { type: "consonant", features: { manner: "lateral fricative", place: "alveolar", voicing: "voiced" }, airstream: "pulmonic" },
63
+ //aproximant
64
+ 'ʋ': { type: "consonant", features: { manner: "approximant", place: "labiodental", voicing: "voiced" }, airstream: "pulmonic" },
65
+ 'ɹ': { type: "consonant", features: { manner: "approximant", place: "alveolar", voicing: "voiced" }, airstream: "pulmonic" },
66
+ 'ɻ': { type: "consonant", features: { manner: "approximant", place: "retroflex", voicing: "voiced" }, airstream: "pulmonic" },
67
+ 'j': { type: "consonant", features: { manner: "approximant", place: "palatal", voicing: "voiced" }, airstream: "pulmonic" },
68
+ 'ɰ': { type: "consonant", features: { manner: "approximant", place: "velar", voicing: "voiced" }, airstream: "pulmonic" },
69
+ //lateral approximant
70
+ 'l': { type: "consonant", features: { manner: "lateral approximant", place: "alveolar", voicing: "voiced" }, airstream: "pulmonic" },
71
+ 'ɭ': { type: "consonant", features: { manner: "lateral approximant", place: "retroflex", voicing: "voiced" }, airstream: "pulmonic" },
72
+ 'ʎ': { type: "consonant", features: { manner: "lateral approximant", place: "palatal", voicing: "voiced" }, airstream: "pulmonic" },
73
+ 'ʟ': { type: "consonant", features: { manner: "lateral approximant", place: "velar", voicing: "voiced" }, airstream: "pulmonic" },
74
+ //non-pulmonic
75
+ //implosive
76
+ 'ɓ': { type: "consonant", features: { manner: "plosive", place: "bilabial", voicing: "voiced" }, airstream: "glottalic ingressive" },
77
+ 'ɗ': { type: "consonant", features: { manner: "plosive", place: "alveolar", voicing: "voiced" }, airstream: "glottalic ingressive" },
78
+ 'ʄ': { type: "consonant", features: { manner: "plosive", place: "palatal", voicing: "voiced" }, airstream: "glottalic ingressive" },
79
+ 'ɠ': { type: "consonant", features: { manner: "plosive", place: "velar", voicing: "voiced" }, airstream: "glottalic ingressive" },
80
+ 'ʛ': { type: "consonant", features: { manner: "plosive", place: "uvular", voicing: "voiced" }, airstream: "glottalic ingressive" },
81
+ //clicks
82
+ 'ʘ': { type: "consonant", features: { manner: "click", place: "bilabial" }, airstream: "lingual ingressive" },
83
+ 'ǀ': { type: "consonant", features: { manner: "click", place: "dental" }, airstream: "lingual ingressive" },
84
+ 'ǃ': { type: "consonant", features: { manner: "click", place: "postalveolar" }, airstream: "lingual ingressive" },
85
+ 'ǂ': { type: "consonant", features: { manner: "click", place: "palatoalveolar" }, airstream: "lingual ingressive" },
86
+ 'ǁ': { type: "consonant", features: { manner: "click", place: "alveolar lateral" }, airstream: "lingual ingressive" },
87
+ //others
88
+ 'ʍ': { type: "consonant", features: { manner: "fricative", place: "labial-velar", voicing: "voiceless" }, airstream: "pulmonic" },
89
+ 'ɕ': { type: "consonant", features: { manner: "fricative", place: "alveolo-palatal", voicing: "voiceless" }, airstream: "pulmonic" },
90
+ 'ʑ': { type: "consonant", features: { manner: "fricative", place: "alveolo-palatal", voicing: "voiced" }, airstream: "pulmonic" },
91
+ 'w': { type: "consonant", features: { manner: "approximant", place: "labial-velar", voicing: "voiced" }, airstream: "pulmonic" },
92
+ 'ɺ': { type: "consonant", features: { manner: "lateral flap", place: "alveolar", voicing: "voiced" }, airstream: "pulmonic" },
93
+ 'ɥ': { type: "consonant", features: { manner: "approximant", place: "labial-palatal", voicing: "voiced" }, airstream: "pulmonic" },
94
+ 'ʜ': { type: "consonant", features: { manner: "fricative", place: "epiglottal", voicing: "voiceless" }, airstream: "pulmonic" },
95
+ 'ʢ': { type: "consonant", features: { manner: "fricative/approximant", place: "epiglottal", voicing: "voiced" }, airstream: "pulmonic" },
96
+ 'ʡ': { type: "consonant", features: { manner: "plosive", place: "epiglottal", voicing: "voiceless" }, airstream: "pulmonic" },
97
+ //coarticulated
98
+ 'ɧ': { type: "consonant", coarticulated: true, components: ["ʃ", "x"] },
99
+ //vowels
100
+ //front
101
+ 'i': { type: "vowel", features: { height: "close", backness: "front", rounding: "unrounded" }, airstream: "pulmonic" },
102
+ 'y': { type: "vowel", features: { height: "close", backness: "front", rounding: "rounded" }, airstream: "pulmonic" },
103
+ 'ɪ': { type: "vowel", features: { height: "near-close", backness: "front", rounding: "unrounded" }, airstream: "pulmonic" },
104
+ 'ʏ': { type: "vowel", features: { height: "near-close", backness: "front", rounding: "rounded" }, airstream: "pulmonic" },
105
+ 'e': { type: "vowel", features: { height: "close-mid", backness: "front", rounding: "unrounded" }, airstream: "pulmonic" },
106
+ 'ø': { type: "vowel", features: { height: "close-mid", backness: "front", rounding: "rounded" }, airstream: "pulmonic" },
107
+ 'ɛ': { type: "vowel", features: { height: "open-mid", backness: "front", rounding: "unrounded" }, airstream: "pulmonic" },
108
+ 'œ': { type: "vowel", features: { height: "open-mid", backness: "front", rounding: "rounded" }, airstream: "pulmonic" },
109
+ 'æ': { type: "vowel", features: { height: "near-open", backness: "front", rounding: "unrounded" }, airstream: "pulmonic" },
110
+ //central
111
+ 'ə': { type: "vowel", features: { height: "mid", backness: "central", rounding: "unrounded" }, airstream: "pulmonic" },
112
+ 'ɨ': { type: "vowel", features: { height: "close", backness: "central", rounding: "unrounded" }, airstream: "pulmonic" },
113
+ 'ʉ': { type: "vowel", features: { height: "close", backness: "central", rounding: "rounded" }, airstream: "pulmonic" },
114
+ 'ɘ': { type: "vowel", features: { height: "close-mid", backness: "central", rounding: "unrounded" }, airstream: "pulmonic" },
115
+ 'ɵ': { type: "vowel", features: { height: "close-mid", backness: "central", rounding: "rounded" }, airstream: "pulmonic" },
116
+ 'ɜ': { type: "vowel", features: { height: "open-mid", backness: "central", rounding: "unrounded" }, airstream: "pulmonic" },
117
+ 'ɞ': { type: "vowel", features: { height: "open-mid", backness: "central", rounding: "rounded" }, airstream: "pulmonic" },
118
+ 'ɐ': { type: "vowel", features: { height: "near-open", backness: "central", rounding: "unrounded" }, airstream: "pulmonic" },
119
+ //back
120
+ 'ɯ': { type: "vowel", features: { height: "close", backness: "back", rounding: "unrounded" }, airstream: "pulmonic" },
121
+ 'u': { type: "vowel", features: { height: "close", backness: "back", rounding: "rounded" }, airstream: "pulmonic" },
122
+ 'ʊ': { type: "vowel", features: { height: "near-close", backness: "back", rounding: "rounded" }, airstream: "pulmonic" },
123
+ 'ɤ': { type: "vowel", features: { height: "close-mid", backness: "back", rounding: "unrounded" }, airstream: "pulmonic" },
124
+ 'o': { type: "vowel", features: { height: "close-mid", backness: "back", rounding: "rounded" }, airstream: "pulmonic" },
125
+ 'ɔ': { type: "vowel", features: { height: "open-mid", backness: "back", rounding: "rounded" }, airstream: "pulmonic" },
126
+ 'ɑ': { type: "vowel", features: { height: "open", backness: "back", rounding: "unrounded" }, airstream: "pulmonic" },
127
+ 'ɒ': { type: "vowel", features: { height: "open", backness: "back", rounding: "rounded" }, airstream: "pulmonic" }
128
+
129
+ }
130
+
131
+ export default core
@@ -0,0 +1,170 @@
1
+ // generate-config.js
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ import core from './core.js';
7
+ import modifiers from './modifiers.js';
8
+
9
+
10
+ // -----------------------------
11
+ // GROUP IPA SYMBOLS BY TYPE
12
+ // -----------------------------
13
+ function groupByType(core) {
14
+ const groups = {};
15
+
16
+ for (const [symbol, data] of Object.entries(core)) {
17
+ const type = data.type || 'other';
18
+ if (!groups[type]) groups[type] = [];
19
+ groups[type].push(symbol);
20
+ }
21
+
22
+ return groups;
23
+ }
24
+
25
+
26
+ // -----------------------------
27
+ // WRAP SYMBOL LINES
28
+ // -----------------------------
29
+ function wrapSymbols(symbols, perLine = 12) {
30
+ const lines = [];
31
+ for (let i = 0; i < symbols.length; i += perLine) {
32
+ lines.push('// ' + symbols.slice(i, i + perLine).join(' '));
33
+ }
34
+ return lines.join('\n');
35
+ }
36
+
37
+
38
+ // -----------------------------
39
+ // IPA SECTION
40
+ // -----------------------------
41
+ function generateIPASection(core) {
42
+ const grouped = groupByType(core);
43
+
44
+ const sections = Object.entries(grouped).map(([type, symbols]) => {
45
+ symbols.sort((a, b) => a.localeCompare(b));
46
+
47
+ return [
48
+ `// ${type.toUpperCase()}`,
49
+ wrapSymbols(symbols),
50
+ ''
51
+ ].join('\n');
52
+ });
53
+
54
+ return [
55
+ '/**',
56
+ ' * IPA SYMBOL REFERENCE',
57
+ ' * Copy/paste symbols as needed',
58
+ ' */',
59
+ '',
60
+ ...sections
61
+ ].join('\n');
62
+ }
63
+
64
+
65
+ // -----------------------------
66
+ // WRAP MODIFIERS HORIZONTALLY
67
+ // -----------------------------
68
+ function wrapModifiers(modList, maxLineLength = 90) {
69
+ const lines = [];
70
+ let currentLine = '// ';
71
+
72
+ modList.forEach((mod, index) => {
73
+ const separator = index === 0 ? '' : ' | ';
74
+ const nextChunk = separator + mod;
75
+
76
+ if ((currentLine + nextChunk).length > maxLineLength) {
77
+ lines.push(currentLine);
78
+ currentLine = '// ' + mod;
79
+ } else {
80
+ currentLine += nextChunk;
81
+ }
82
+ });
83
+
84
+ if (currentLine.trim()) lines.push(currentLine);
85
+
86
+ return lines.join('\n');
87
+ }
88
+
89
+
90
+ // -----------------------------
91
+ // MODIFIER SECTION
92
+ // -----------------------------
93
+ function generateModifierSection(modifiers) {
94
+ const entries = Object.entries(modifiers);
95
+ entries.sort(([a], [b]) => a.localeCompare(b));
96
+
97
+ const formatted = entries.map(([key, data]) => {
98
+ const appliesTo = Array.isArray(data.appliesTo)
99
+ ? data.appliesTo.join(', ')
100
+ : data.appliesTo || '';
101
+ return `${key}(${appliesTo})`;
102
+ });
103
+
104
+ return [
105
+ '/**',
106
+ ' * MODIFIER REFERENCE',
107
+ ' * modifier(appliesTo)',
108
+ ' */',
109
+ '',
110
+ wrapModifiers(formatted),
111
+ ''
112
+ ].join('\n');
113
+ }
114
+
115
+
116
+ // -----------------------------
117
+ // ORTHOGRAPHY EXPORT SECTION
118
+ // -----------------------------
119
+ function generateOrthographyExport() {
120
+ const commentedOrthography = [
121
+ '// k: ["k", ["aspirated"]],',
122
+ '// a: ["ɑ", ["more_rounded"]],',
123
+ '// x̱: [',
124
+ '// ["k", ["ejective", "aspirated"]],',
125
+ '// ["x", []]',
126
+ '// ],',
127
+ '// l: ["ɬ"]'
128
+ ].join('\n');
129
+
130
+ return [
131
+ '/**',
132
+ ' * ORTHOGRAPHY CONFIG',
133
+ ' * Use parseConfig() at runtime',
134
+ ' *',
135
+ ' * Fill in your orthography below; examples are commented out.',
136
+ ' */',
137
+ '',
138
+ "import { parseConfig } from './parser.js';",
139
+ '',
140
+ 'const orthography = {',
141
+ commentedOrthography,
142
+ '};',
143
+ '',
144
+ 'export default parseConfig(orthography);',
145
+ ''
146
+ ].join('\n');
147
+ }
148
+
149
+
150
+ // -----------------------------
151
+ // MAIN GENERATOR
152
+ // -----------------------------
153
+ function generateConfig() {
154
+ const content = [
155
+ generateIPASection(core),
156
+ '',
157
+ generateModifierSection(modifiers),
158
+ '',
159
+ generateOrthographyExport()
160
+ ].join('\n');
161
+
162
+ const outputPath = path.join(process.cwd(), 'ipa.config.js');
163
+ fs.writeFileSync(outputPath, content, 'utf-8');
164
+
165
+ console.log('✅ ipa.config.js generated with parseConfig() export');
166
+ }
167
+
168
+
169
+ // RUN
170
+ generateConfig();
@@ -0,0 +1,120 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import readline from "readline";
4
+ import core from "./core.js";
5
+ import modifiers from "./modifiers.js";
6
+
7
+ /** Pick random element */
8
+ const pickRandom = (arr) => arr[Math.floor(Math.random() * arr.length)];
9
+
10
+ /** Shuffle array */
11
+ const shuffle = (arr) => {
12
+ for (let i = arr.length - 1; i > 0; i--) {
13
+ const j = Math.floor(Math.random() * (i + 1));
14
+ [arr[i], arr[j]] = [arr[j], arr[i]];
15
+ }
16
+ return arr;
17
+ };
18
+
19
+ /** Prompt helper */
20
+ function prompt(query) {
21
+ const rl = readline.createInterface({
22
+ input: process.stdin,
23
+ output: process.stdout,
24
+ });
25
+ return new Promise((resolve) =>
26
+ rl.question(query, (ans) => {
27
+ rl.close();
28
+ resolve(ans);
29
+ })
30
+ );
31
+ }
32
+
33
+ /** Generate conlang orthography */
34
+ function generateConlang({ alphabet, mod1Chance, mod2Chance, affricateChance }) {
35
+ const letters = alphabet.split("");
36
+ const usedIPA = new Set();
37
+ const orthography = {};
38
+ const coreKeys = shuffle(Object.keys(core));
39
+
40
+ letters.forEach((letter) => {
41
+ // Pick unused IPA
42
+ let ipaKey;
43
+ while (true) {
44
+ const candidate = pickRandom(coreKeys);
45
+ if (!usedIPA.has(candidate)) {
46
+ ipaKey = candidate;
47
+ usedIPA.add(candidate);
48
+ break;
49
+ }
50
+ }
51
+
52
+ const baseObj = core[ipaKey];
53
+ const modifiersList = [];
54
+
55
+ // Modifier 1
56
+ if (Math.random() < mod1Chance) {
57
+ const compatible = Object.entries(modifiers).filter(
58
+ ([, mod]) => !mod.appliesTo || mod.appliesTo.includes(baseObj?.type)
59
+ );
60
+ if (compatible.length) modifiersList.push(pickRandom(compatible)[0]);
61
+ }
62
+
63
+ // Modifier 2
64
+ if (Math.random() < mod2Chance && modifiersList.length) {
65
+ const compatible = Object.entries(modifiers).filter(
66
+ ([key, mod]) =>
67
+ !modifiersList.includes(key) &&
68
+ (!mod.appliesTo || mod.appliesTo.includes(baseObj?.type))
69
+ );
70
+ if (compatible.length) modifiersList.push(pickRandom(compatible)[0]);
71
+ }
72
+
73
+ // Plosive → affricate
74
+ if (baseObj?.features?.manner === "plosive" && Math.random() < affricateChance) {
75
+ const fricatives = Object.entries(core).filter(
76
+ ([, obj]) =>
77
+ obj?.features?.manner === "fricative" &&
78
+ obj.features.place === baseObj.features.place
79
+ );
80
+ if (fricatives.length) {
81
+ const fricKey = pickRandom(fricatives)[0];
82
+ orthography[letter] = [
83
+ [ipaKey, modifiersList],
84
+ [fricKey, []],
85
+ ];
86
+ return;
87
+ }
88
+ }
89
+
90
+ orthography[letter] = [ipaKey, modifiersList];
91
+ });
92
+
93
+ return orthography;
94
+ }
95
+
96
+ /** Main async function */
97
+ async function main() {
98
+ const alphabet = (await prompt("Enter alphabet (default a-z): ")) || "abcdefghijklmnopqrstuvwxyz";
99
+ const mod1 = parseFloat((await prompt("Modifier 1 chance (default 0.33): ")) || "0.33");
100
+ const mod2 = parseFloat((await prompt("Modifier 2 chance (default 0.06): ")) || "0.06");
101
+ const aff = parseFloat((await prompt("Plosive → affricate chance (default 0.1): ")) || "0.1");
102
+
103
+ const orthography = generateConlang({
104
+ alphabet,
105
+ mod1Chance: mod1,
106
+ mod2Chance: mod2,
107
+ affricateChance: aff,
108
+ });
109
+
110
+ const outPath = path.join(process.cwd(), "generatedConlang.js");
111
+ const lines = Object.entries(orthography).map(
112
+ ([l, v]) => ` ${JSON.stringify(l)}:${JSON.stringify(v)}`
113
+ );
114
+ const content = `/** Generated conlang orthography */\nconst generatedOrthography={\n${lines.join(",\n")}\n};\nexport default generatedOrthography;`;
115
+
116
+ fs.writeFileSync(outPath, content, "utf-8");
117
+ console.log("✅ generatedConlang.js written");
118
+ }
119
+
120
+ main();
package/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // Import your data layers
2
+ import core from "./core.js"; // base consonants & vowels
3
+ import modifiers from "./modifiers.js"; // diacritics, tones, suprasegmentals
4
+ import rules from "./rules.js"; // modifier compatibility rules
5
+ import parser from "./parser.js"; // parser (parsePhoneme, parseUnit, parseConfig)
6
+
7
+ // Export the layers so users can access them if needed
8
+ export const { parsePhoneme, parseUnit, parseConfig } = parser;
9
+ export { core, modifiers, rules, parser };
package/modifiers.js ADDED
@@ -0,0 +1,250 @@
1
+ const modifiers = {
2
+ // DIACRITICS
3
+
4
+ voiceless: {
5
+ symbol: "̥",
6
+ appliesTo: "consonant",
7
+ effects: { voicing: "voiceless" }
8
+ },
9
+ voiced: {
10
+ symbol: "̬",
11
+ appliesTo: "consonant",
12
+ effects: { voicing: "voiced" }
13
+ },
14
+ aspirated: {
15
+ symbol: "ʰ",
16
+ appliesTo: "consonant",
17
+ effects: { aspiration: true }
18
+ },
19
+
20
+ more_rounded: {
21
+ symbol: "̹",
22
+ appliesTo: "vowel",
23
+ effects: { rounding: "more" }
24
+ },
25
+ less_rounded: {
26
+ symbol: "̜",
27
+ appliesTo: "vowel",
28
+ effects: { rounding: "less" }
29
+ },
30
+
31
+ advanced: {
32
+ symbol: "̟",
33
+ appliesTo: "consonant",
34
+ effects: { placeModifier: "advanced" }
35
+ },
36
+ retracted: {
37
+ symbol: "̠",
38
+ appliesTo: "consonant",
39
+ effects: { placeModifier: "retracted" }
40
+ },
41
+
42
+ centralized: {
43
+ symbol: "̈",
44
+ appliesTo: "vowel",
45
+ effects: { backness: "centralized" }
46
+ },
47
+ mid_centralized: {
48
+ symbol: "̽",
49
+ appliesTo: "vowel",
50
+ effects: { backness: "mid-centralized" }
51
+ },
52
+
53
+ syllabic: {
54
+ symbol: "̩",
55
+ appliesTo: "consonant",
56
+ effects: { syllabic: true }
57
+ },
58
+ non_syllabic: {
59
+ symbol: "̯",
60
+ appliesTo: "vowel",
61
+ effects: { syllabic: false }
62
+ },
63
+
64
+ dental: {
65
+ symbol: "̪",
66
+ appliesTo: "consonant",
67
+ effects: { placeModifier: "dental" }
68
+ },
69
+ apical: {
70
+ symbol: "̺",
71
+ appliesTo: "consonant",
72
+ effects: { articulation: "apical" }
73
+ },
74
+ laminal: {
75
+ symbol: "̻",
76
+ appliesTo: "consonant",
77
+ effects: { articulation: "laminal" }
78
+ },
79
+
80
+ velarized_or_pharyngealized: {
81
+ symbol: "ˠ",
82
+ appliesTo: "consonant",
83
+ effects: { secondaryArticulation: "velarized_or_pharyngealized" }
84
+ },
85
+ velarized: {
86
+ symbol: "̴",
87
+ appliesTo: "consonant",
88
+ effects: { secondaryArticulation: "velarized" }
89
+ },
90
+ palatalized: {
91
+ symbol: "ʲ",
92
+ appliesTo: "consonant",
93
+ effects: { secondaryArticulation: "palatalized" }
94
+ },
95
+ labialized: {
96
+ symbol: "ʷ",
97
+ appliesTo: "consonant",
98
+ effects: { secondaryArticulation: "labialized" }
99
+ },
100
+ pharyngealized: {
101
+ symbol: "ˤ",
102
+ appliesTo: "consonant",
103
+ effects: { secondaryArticulation: "pharyngealized" }
104
+ },
105
+
106
+ nasalized: {
107
+ symbol: "̃",
108
+ appliesTo: ["vowel", "consonant"],
109
+ effects: { nasalization: true }
110
+ },
111
+
112
+ rhoticity: {
113
+ symbol: "˞",
114
+ appliesTo: "vowel",
115
+ effects: { rhotic: true }
116
+ },
117
+
118
+ length_half_long: {
119
+ symbol: "ˑ",
120
+ appliesTo: ["vowel", "consonant"],
121
+ effects: { length: "half-long" }
122
+ },
123
+ length_long: {
124
+ symbol: "ː",
125
+ appliesTo: ["vowel", "consonant"],
126
+ effects: { length: "long" }
127
+ },
128
+ extra_short: {
129
+ symbol: "̆",
130
+ appliesTo: ["vowel", "consonant"],
131
+ effects: { length: "extra-short" }
132
+ },
133
+
134
+ voiceless_flap: {
135
+ symbol: "̥̆",
136
+ appliesTo: "consonant",
137
+ effects: {
138
+ voicing: "voiceless",
139
+ mannerModifier: "flap"
140
+ }
141
+ },
142
+
143
+ creaky_voice: {
144
+ symbol: "̰",
145
+ appliesTo: ["vowel", "consonant"],
146
+ effects: { phonation: "creaky" }
147
+ },
148
+ breathy_voice: {
149
+ symbol: "̤",
150
+ appliesTo: ["vowel", "consonant"],
151
+ effects: { phonation: "breathy" }
152
+ },
153
+
154
+ linguolabial: {
155
+ symbol: "̼",
156
+ appliesTo: "consonant",
157
+ effects: { placeModifier: "linguolabial" }
158
+ },
159
+
160
+ raised: {
161
+ symbol: "̝",
162
+ appliesTo: ["vowel", "consonant"],
163
+ effects: { heightModifier: "raised" }
164
+ },
165
+ lowered: {
166
+ symbol: "̞",
167
+ appliesTo: ["vowel", "consonant"],
168
+ effects: { heightModifier: "lowered" }
169
+ },
170
+
171
+ advanced_tongue_root: {
172
+ symbol: "̘",
173
+ appliesTo: "vowel",
174
+ effects: { tongueRoot: "advanced" }
175
+ },
176
+ retracted_tongue_root: {
177
+ symbol: "̙",
178
+ appliesTo: "vowel",
179
+ effects: { tongueRoot: "retracted" }
180
+ },
181
+
182
+ // TONES / ACCENTS
183
+
184
+ high_tone: {
185
+ symbol: "́",
186
+ appliesTo: ["vowel", "consonant"],
187
+ effects: { tone: "high" }
188
+ },
189
+ low_tone: {
190
+ symbol: "̀",
191
+ appliesTo: ["vowel", "consonant"],
192
+ effects: { tone: "low" }
193
+ },
194
+ rising_tone: {
195
+ symbol: "̌",
196
+ appliesTo: ["vowel", "consonant"],
197
+ effects: { tone: "rising" }
198
+ },
199
+ falling_tone: {
200
+ symbol: "̂",
201
+ appliesTo: ["vowel", "consonant"],
202
+ effects: { tone: "falling" }
203
+ },
204
+ extra_high_tone: {
205
+ symbol: "᷄",
206
+ appliesTo: ["vowel", "consonant"],
207
+ effects: { tone: "extra-high" }
208
+ },
209
+ extra_low_tone: {
210
+ symbol: "᷅",
211
+ appliesTo: ["vowel", "consonant"],
212
+ effects: { tone: "extra-low" }
213
+ },
214
+
215
+ extra_high_pitch: {
216
+ symbol: "᷄",
217
+ appliesTo: ["vowel", "consonant"],
218
+ effects: { pitch: "extra-high" }
219
+ },
220
+ extra_low_pitch: {
221
+ symbol: "᷅",
222
+ appliesTo: ["vowel", "consonant"],
223
+ effects: { pitch: "extra-low" }
224
+ },
225
+
226
+ downstep: {
227
+ symbol: "ꜜ",
228
+ appliesTo: ["vowel", "consonant"],
229
+ effects: { toneModifier: "downstep" }
230
+ },
231
+ upstep: {
232
+ symbol: "ꜛ",
233
+ appliesTo: ["vowel", "consonant"],
234
+ effects: { toneModifier: "upstep" }
235
+ },
236
+
237
+ no_audible_release: {
238
+ symbol: "̚",
239
+ appliesTo: "consonant",
240
+ effects: { release: "none" }
241
+ },
242
+ //ejective
243
+ ejective: {
244
+ symbol: "ʼ",
245
+ appliesTo: "consonant",
246
+ effects: { airstream: "egressive_glottalic" } // or just mark it as ejective
247
+ }
248
+ };
249
+
250
+ export default modifiers;
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "ipa-core",
3
+ "version": "1.0.0",
4
+ "description": "Language-agnostic IPA core with features",
5
+ "license": "ISC",
6
+ "author": "Cody Bruno",
7
+ "type": "module",
8
+ "main": "index.js",
9
+ "scripts": {
10
+ "test": "node test.js",
11
+ "init:ipa": "node generate-config.js",
12
+ "make-conlang": "node generate-conlang.js"
13
+ },
14
+ "keywords": ["ipa", "phoneme", "language", "linguistics"]
15
+ }
package/parser.js ADDED
@@ -0,0 +1,137 @@
1
+ // parser.js
2
+
3
+ import core from "./core";
4
+ import modifiers from "./modifiers";
5
+ import rules from "./rules";
6
+
7
+ /**
8
+ * Parses a single phoneme with optional modifiers.
9
+ * @param {string} base - Base IPA symbol
10
+ * @param {string[]} appliedModifiers - List of modifier keys
11
+ * @returns {object} - Full feature object
12
+ */
13
+ function parsePhoneme(base, appliedModifiers = []) {
14
+ const baseObj = core[base];
15
+ if (!baseObj) {
16
+ throw new Error(`Unknown base phoneme: ${base}`);
17
+ }
18
+
19
+ let features = { ...baseObj.features };
20
+ const modifiersApplied = [];
21
+
22
+ for (const modKey of appliedModifiers) {
23
+ const mod = modifiers[modKey];
24
+ if (!mod) {
25
+ throw new Error(`Unknown modifier: ${modKey}`);
26
+ }
27
+
28
+ const incompatible = rules[modKey]?.incompatibleWith || [];
29
+ const conflict = modifiersApplied.find(applied => incompatible.includes(applied));
30
+ if (conflict) {
31
+ console.warn(`Modifier "${modKey}" is incompatible with already applied "${conflict}". Skipping.`);
32
+ continue;
33
+ }
34
+
35
+ if (mod.features) {
36
+ features = { ...features, ...mod.features };
37
+ } else {
38
+ features[modKey] = true;
39
+ }
40
+
41
+ modifiersApplied.push(modKey);
42
+ }
43
+
44
+ return {
45
+ base,
46
+ symbol: baseObj.symbol || base,
47
+ features,
48
+ modifiersApplied
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Helper to create a phoneme unit for orthography config.
54
+ * Can be a single phoneme or a sequence object.
55
+ * @param {string|object} unit - Single base or a sequence object
56
+ * @param {string[]} appliedModifiers - Modifiers for single phoneme (ignored if sequence)
57
+ * @returns {object}
58
+ */
59
+ function parseUnit(unit, appliedModifiers = []) {
60
+ // if unit is a string, treat as single phoneme
61
+ if (typeof unit === "string") {
62
+ return { type: "single", base: unit, modifiers: appliedModifiers };
63
+ }
64
+
65
+ // if unit is an object with multiple phonemes, treat as sequence
66
+ if (typeof unit === "object") {
67
+ const sequence = [];
68
+ for (const base in unit) {
69
+ const mods = unit[base] || [];
70
+ sequence.push({ base, modifiers: mods });
71
+ }
72
+ return { type: "sequence", sequence };
73
+ }
74
+
75
+ throw new Error(`Invalid unit: ${unit}`);
76
+ }
77
+
78
+ /**
79
+ * Parses a unit (single or sequence) into full feature objects
80
+ * @param {object} unitObj
81
+ * @returns {object|array} - Parsed phoneme object or array of parsed phonemes
82
+ */
83
+ function parseUnitObj(unitObj) {
84
+ if (unitObj.type === "single") {
85
+ return parsePhoneme(unitObj.base, unitObj.modifiers);
86
+ }
87
+
88
+ if (unitObj.type === "sequence") {
89
+ return unitObj.sequence.map(u => parsePhoneme(u.base, u.modifiers));
90
+ }
91
+
92
+ throw new Error(`Unknown unit type: ${unitObj.type}`);
93
+ }
94
+
95
+ /**
96
+ * Parses an entire orthography config.
97
+ * Supports:
98
+ * 1) Full parseUnit() objects
99
+ * 2) Single-phoneme shorthand: ["base", ["modifiers"]]
100
+ * 3) Sequence shorthand: [["base1", ["mods1"]], ["base2", ["mods2"]], ...]
101
+ * @param {object} config - { letter: parseUnit(...) OR shorthand }
102
+ * @returns {object} parsed - { letter: parsedPhoneme OR array if sequence }
103
+ */
104
+ function parseConfig(config) {
105
+ const parsed = {};
106
+
107
+ for (const letter in config) {
108
+ const val = config[letter];
109
+
110
+ let unitObj;
111
+
112
+ // Already a parseUnit object
113
+ if (val && typeof val === "object" && val.type && (val.type === "single" || val.type === "sequence")) {
114
+ unitObj = val;
115
+ }
116
+ // Shorthand single phoneme: ["base", ["modifiers"]]
117
+ else if (Array.isArray(val) && typeof val[0] === "string") {
118
+ const base = val[0];
119
+ const mods = Array.isArray(val[1]) ? val[1] : [];
120
+ unitObj = { type: "single", base, modifiers: mods };
121
+ }
122
+ // Shorthand sequence: [["base1", ["mods1"]], ["base2", ["mods2"]], ...]
123
+ else if (Array.isArray(val) && val.every(el => Array.isArray(el) && typeof el[0] === "string")) {
124
+ const sequence = val.map(([base, mods]) => ({ base, modifiers: Array.isArray(mods) ? mods : [] }));
125
+ unitObj = { type: "sequence", sequence };
126
+ }
127
+ else {
128
+ throw new Error(`Invalid orthography entry for "${letter}": ${JSON.stringify(val)}`);
129
+ }
130
+
131
+ parsed[letter] = parseUnitObj(unitObj);
132
+ }
133
+
134
+ return parsed;
135
+ }
136
+
137
+ export { parsePhoneme, parseUnit, parseConfig };
package/rules.js ADDED
@@ -0,0 +1,52 @@
1
+ //modifier compatability
2
+ const rules = {
3
+ // Voicing / phonation
4
+ voiceless: { incompatibleWith: ["voiced", "creaky_voice", "breathy_voice"] },
5
+ voiced: { incompatibleWith: ["voiceless", "voiceless_flap"] },
6
+ voiceless_flap: { incompatibleWith: ["voiced", "aspirated"] },
7
+ creaky_voice: { incompatibleWith: ["voiceless"] },
8
+ breathy_voice: { incompatibleWith: ["voiceless"] },
9
+
10
+ // Place / articulation
11
+ advanced: { incompatibleWith: ["retracted", "velarized_or_pharyngealized"] },
12
+ retracted: { incompatibleWith: ["advanced", "velarized_or_pharyngealized"] },
13
+ palatalized: { incompatibleWith: ["velarized_or_pharyngealized"] },
14
+ velarized_or_pharyngealized: { incompatibleWith: ["palatalized", "advanced", "retracted"] },
15
+ pharyngealized: { incompatibleWith: ["palatalized"] },
16
+ linguolabial: { incompatibleWith: [] },
17
+ dental: { incompatibleWith: [] },
18
+ apical: { incompatibleWith: [] },
19
+ laminal: { incompatibleWith: [] },
20
+ velarized: { incompatibleWith: ["palatalized", "advanced", "retracted"] },
21
+
22
+ // Syllabicity
23
+ syllabic: { incompatibleWith: ["non_syllabic"] },
24
+ non_syllabic: { incompatibleWith: ["syllabic"] },
25
+
26
+ // Nasalization / rhoticity
27
+ nasalized: { incompatibleWith: [] },
28
+ rhoticity: { incompatibleWith: [] },
29
+
30
+ // Length / duration
31
+ extra_short: { incompatibleWith: ["length_half_long", "length_long"] },
32
+ length_half_long: { incompatibleWith: ["extra_short", "length_long"] },
33
+ length_long: { incompatibleWith: ["extra_short", "length_half_long"] },
34
+
35
+ // Segment-level tones / accents
36
+ high_tone: { incompatibleWith: ["low_tone", "falling_tone", "extra_low_tone"] },
37
+ low_tone: { incompatibleWith: ["high_tone", "rising_tone", "extra_high_tone"] },
38
+ rising_tone: { incompatibleWith: ["falling_tone", "low_tone"] },
39
+ falling_tone: { incompatibleWith: ["rising_tone", "high_tone"] },
40
+ extra_high_tone: { incompatibleWith: ["extra_low_tone", "low_tone"] },
41
+ extra_low_tone: { incompatibleWith: ["extra_high_tone", "high_tone"] },
42
+ downstep: { incompatibleWith: [] },
43
+ upstep: { incompatibleWith: [] },
44
+ extra_high_pitch: { incompatibleWith: ["extra_low_pitch"] },
45
+ extra_low_pitch: { incompatibleWith: ["extra_high_pitch"] },
46
+
47
+ // Special consonant markers
48
+ aspirated: { incompatibleWith: ["voiceless_flap", "no_audible_release"] },
49
+ no_audible_release: { incompatibleWith: ["aspirated"] }
50
+ };
51
+
52
+ module.exports = rules
package/test.js ADDED
@@ -0,0 +1,60 @@
1
+ // test.js
2
+
3
+ const { parseUnit, parseConfig } = require("./parser");
4
+
5
+ // Sample orthography config
6
+ const orthography = {
7
+ // A single consonant with a modifier
8
+ k: parseUnit("k", ["aspirated"]),
9
+
10
+ // A single vowel with a modifier
11
+ a: parseUnit("ɑ", ["more_rounded"]),
12
+
13
+ // A consonant sequence (affricate)
14
+ x̱: parseUnit({
15
+ k: ["ejective", "aspirated"],
16
+ x: []
17
+ }),
18
+
19
+ // Another single consonant
20
+ l: parseUnit("ɬ")
21
+ };
22
+
23
+ // Another sample orthography config using shorthand
24
+ const shorthandOrthography = {
25
+ // A single consonant with a modifier
26
+ k: ["k", ["aspirated"]],
27
+
28
+ // A single vowel with a modifier
29
+ a: ["ɑ", ["more_rounded"]],
30
+
31
+ // A consonant sequence (affricate)
32
+ x̱: [[
33
+ 'k', ["ejective", "aspirated"],
34
+ 'x', []
35
+ ]],
36
+
37
+ // Another single consonant
38
+ l: ["ɬ"]
39
+ };
40
+
41
+ console.log("=== Orthography Config ===");
42
+ console.log(JSON.stringify(orthography, null, 2));
43
+
44
+ const parsed = parseConfig(orthography);
45
+ console.log("\n=== Parsed Output ===");
46
+ console.log(JSON.stringify(parsed, null, 2));
47
+
48
+ // Optional: inspect a single phoneme
49
+ const singlePhoneme = parsed.k;
50
+ console.log("\n=== Single Phoneme 'k' Features ===");
51
+ console.log(singlePhoneme);
52
+
53
+ // Optional: inspect a sequence
54
+ const affricateSequence = parsed.x̱;
55
+ console.log("\n=== Affricate Sequence 'x̱' Features ===");
56
+ console.log(affricateSequence);
57
+
58
+ const parsedShorthand = parseConfig(shorthandOrthography);
59
+ console.log("\n=== Parsed Output2 ===");
60
+ console.log(JSON.stringify(parsedShorthand, null, 2));