forked from nk2028/tshet-uinh-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathn_song.js
261 lines (234 loc) · 12.4 KB
/
n_song.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
/* 推導北宋擬音(聲音唱和圖)
*
* 以《聲音唱和圖》和北宋中原押韻爲基礎。《聲音唱和圖》分析並記錄了北宋共通語的音系,對該圖的解析與擬音詳見:https://zhuanlan.zhihu.com/p/498778513
*
* 代碼注釋中的等均指韻圖等(聲音唱和圖等),而不是切韻等
*
* @author unt
*/
const is = (...x) => 音韻地位.屬於(...x);
const when = (...x) => 音韻地位.判斷(...x);
const is表層 = 選項.顯示形式 !== '底層';
if (!音韻地位) return [
['_last顯示形式', [1, 選項.顯示形式 ?? '底層'], { hidden: true }],
['顯示形式|顯示\n音位分析據《聲音唱和圖》', [2,
{ value: '底層', text: '底層形式(音位形式)' },
{ value: '表層', text: '表層形式(語音實現)' },
]],
'聲',
['全濁平送氣|全濁平送氣 ʰ\n《聲音唱和圖》全濁平塞音送氣,但按通常習慣可不標', true],
['次濁上喉化|次濁上喉化 ˀ\n《聲音唱和圖》次濁上歸陰調,但按通常習慣可不標\n喉化此處寫作上標的 ʔ,此上標字母 Unicode 未收,以 ˀ 代替', true],
['知照組\ntʂ、tɕ《聲音唱和圖》已合爲同一音位。按通用的國際音標習慣,無對立的 tʂ、tɕ 可一律標爲 tʃ,但爲了照顧漢語習慣,表層形式默認選擇 tʂ、tɕ 分立',
[is表層 ? 3 : 1,
{ value: 'ʃʒɹ', text: '知tɹ 莊章tʃ' },
{ value: 'ʂʐɻ', text: '知tɻ 莊章tʂ' },
{ value: 'ʂʐɹ|ɕʑɹ', text: '知tɹ 莊tʂ 章tɕ' },
{ value: 'ʂʐɻ|ɕʑɻ', text: '知tɻ 莊tʂ 章tɕ' },
].slice(0, is表層 ? 5 : 3),
{ reset: 選項._last顯示形式 === '底層' && 選項.顯示形式 !== '底層' },
],
'韻',
is表層 ? ['入聲尾\n深咸 | 臻山 | 曾梗通江宕', [1,
{ value: 'p t k', text: 'p | t | k(符合通常習慣)' },
{ value: 'β ɾ ɣ', text: 'β | ɾ | ɣ(接近實際音值)' },
]] : ['入聲尾\n陰入相配依《聲音唱和圖》\n深咸 | 臻山 | 曾梗 | 通江宕', [1,
{ value: 'p t k', text: 'p | t | k | wk(陽入相配)' },
{ value: 'ʋˀ ˀ jˀ wˀ', text: 'ʋˀ | ˀ | jˀ | wˀ(陰入相配)' },
]],
['咸山蟹攝銳音一開歸\n保守:一等\n時興:二等\n(泥母蟹攝除外。《聲音唱和圖》“乃”仍在一等)', [2, '一等', '二等']],
['咸山攝輕脣歸\n保守:一等\n時興:二等', [2, '一等', '二等']],
{
key: '止蟹三四合韻基',
text: '止蟹三四合',
description: is表層 ? '保守:ɥi\n時興:yj' : '保守:jwɨ\n時興:jwɨj',
value: 'ɨj',
options: [
{ value: 'ɨ', text: is表層 ? 'ɥi' : 'jwɨ' },
{ value: 'ɨj', text: is表層 ? 'yj' : 'jwɨj' },
],
},
['蟹一合\n保守:wɔj\n時興:uj', [2, 'wɔj', 'uj'], { hidden: !is表層 }],
['部分蟹攝二等歸假攝', true],
['部分流攝脣音歸遇攝', true],
'調',
['聲調', [1, '附加符號', '調類數字', '省略']],
['全濁上歸去\n《聲音唱和圖》未體現全濁上字的聲調,按口語則已歸去聲,按《蒙古字韻》風格則仍算上聲', true],
['影喻上聲合併\n《聲音唱和圖》無影喻上聲對立的空間,可能口語已合併,但同期反切未見相混的情況', false],
];
function 調整音韻地位() {
function 調整(表達式, 調整屬性) { if (is(表達式)) 音韻地位 = 音韻地位.調整(調整屬性); }
// 輕唇化例外
調整('明母 尤韻', { 等: '一', 類: null, 韻: '侯' });
調整('明母 東韻', { 等: '一', 類: null });
if (is`云母 通攝 舒聲`) 音韻地位 = 音韻地位.調整('匣母', ['匣母三等']); // 雄熊
// [慧琳反切體現的, 唐代用韻體現的, 據今音推測的]
const 蟹攝二等歸假攝字 = ['崖咼(呙)扠涯派差絓畫(画)罣罷(罢)', '佳鼃娃解卦', '灑蝸話(话)掛挂查叉杈衩'].join('');
const 流攝脣音歸遇攝字 = ['浮戊母罦罘蜉覆拇負(负)阜', '部畝(亩)畮婦(妇)不否桴富', '復複(复)副牡'].join('');
if (選項.部分蟹攝二等歸假攝 !== false && 蟹攝二等歸假攝字.includes(字頭)) 調整('蟹攝 二等', { 韻: '麻' });
if (選項.部分流攝脣音歸遇攝 !== false && 流攝脣音歸遇攝字.includes(字頭)) 調整('幫組 尤侯韻', { 韻: is`尤韻` ? '虞' : '模' });
}
function get聲母() {
return when([
['匣母 肴韻 平聲', 'ɰ'], // 匣母“爻”在三音,但一等無字,說明“完”不是零聲母
['崇母 止攝 仄聲', 'ʒ'], // 崇母“士”在十音。另船母船小韻和繩小韻中“乘”聲符字今讀塞擦音,但圖中未體現,這裏不考慮
['幫組 C類', [
['幫滂母', 'f'], ['並母', 'v'], ['明母 上聲', 'ˀʋ'], ['明母', 'ʋ'], // 四音
]],
['曉母', 'x'], ['匣母', 'ɣ'], ['疑母 上聲', 'ˀŋ'], ['疑母', 'ŋ'], // 二音
['心母', 's'], ['邪母', 'z'], // 九音。這一組的後兩個聲母(同部位的近音)有音無字
['生書母', 'ʃ'], ['常母 仄聲 或 俟船母', 'ʒ'], ['日母 上聲', 'ˀɹ'], ['日母', 'ɹ'], // 十音
['幫母', 'p'], ['並母 仄聲', 'b'], ['滂母', 'pʰ'], ['並母', 'bʰ'], // 五音
['端母', 't'], ['定母 仄聲', 'd'], ['透母', 'tʰ'], ['定母', 'dʰ'], // 六音
['見母', 'k'], ['羣母 仄聲', 'ɡ'], ['溪母', 'kʰ'], ['羣母', 'ɡʰ'], // 一音
['知母', 'tɹ'], ['澄母 仄聲', 'dɹ'], ['徹母', 'tɹʰ'], ['澄母', 'dɹʰ'], // 十二音
['精母', 'ts'], ['從母 仄聲', 'dz'], ['清母', 'tsʰ'], ['從母', 'dzʰ'], // 八音
['莊章母', 'tʃ'], ['崇母 仄聲', 'dʒ'], ['初昌母', 'tʃʰ'], ['崇常母', 'dʒʰ'], // 十一音
['泥孃母 上聲', 'ˀn'], ['泥孃母', 'n'], ['來母 上聲', 'ˀl'], ['來母', 'l'], // 七音
['影母', 'ʔ'], ['云以母 上聲', 選項.影喻上聲合併 ? 'ʔ' : 'ˀɰ'], ['云以母', 'ɰ'], ['明母 上聲', 'ˀm'], ['明母', 'm'], // 三音
]);
}
function get等() {
return when([
['幫組 C類', [
['止蟹攝', '四'],
['咸山攝', 選項.咸山攝輕脣歸?.[0] ?? '二'],
['', '一'],
]],
['精組 止攝 開口', '一'], // 中唐 /sjɨ/ > /sɨ/ 變一等
['銳音 蟹山咸攝 一等 開口 非 (泥母 蟹攝)', 選項.咸山蟹攝銳音一開歸?.[0] ?? '二'],
[音韻地位.韻圖等 === '四' && '銳音 非 以母', '三'], // 圖中銳音一律無四等
['', 音韻地位.韻圖等],
]);
}
function get開合() {
return when([
[選項.咸山攝輕脣歸 === '一等' && '咸山攝 幫組 C類', '合'],
['流深咸攝', '開'], // 深咸攝舒入聲都視爲開口,不依圖中定義
['幫組', [
['通宕攝', '開'],
['一等 或 虞文歌韻', '合'], // 其餘一等(包括輕脣變一等的)歸合口
['', '開'],
// 圖中“八”在合口,但二等不應該在合口。這可能是繼承了《韻鏡》《七音略》脣音刪舒、山入在合口,刪入、山舒在開口。本方案不考慮
]],
['鍾虞模韻', '合'],
['江韻 銳音', '合'],
['', 音韻地位.呼 ?? '開'],
]);
}
function get介音(等, 開合) {
return {
一: { 開: '', 合: 'w' },
二: { 開: 'ʕ', 合: 'ʕw' },
三: { 開: 'j', 合: 'jw' },
四: { 開: 'ʲj', 合: 'ʲjw' },
}[等][開合];
}
function get韻基() {
return when([
['蟹攝 (一二等 或 莊組)', 'aj'], // 一聲下(僅舒聲)
['止蟹攝 合口 非 莊組', 選項.止蟹三四合韻基 ?? 'ɨj'], // 五聲下(舒)
['曾梗攝', is`入聲` ? 'ɨk' : 'ɨŋ'], // 五聲下(入) & 二聲下
['止蟹攝', 'ɨ'], // 五聲上(舒)
['臻攝', is`入聲` ? 'ɨt' : 'ɨn'], // 五聲上(入) & 三聲下
['果假攝', 'a'], // 一聲上(舒)
['山攝', is`入聲` ? 'at' : 'an'],// 一聲上(入) & 三聲上
['遇攝', 'ɯ'], // 六聲下(僅舒聲)
['流攝', 'ɨw'], // 四聲下(舒)
['通攝', is`入聲` ? 'ɨwk' : 'ɨwŋ'], // 四聲下(入) & 六聲上
['效攝', 'aw'], // 四聲上(舒)
['宕江攝', is`入聲` ? 'awk' : 'awŋ'], // 四聲上(入) & 二聲上
['深攝', is`入聲` ? 'ɨp' : 'ɨm'], // 七聲上
['咸攝', is`入聲` ? 'ap' : 'am'], // 七聲下
]);
}
function get聲調() {
if (選項.聲調 === '省略') return '';
let 聲調 = 音韻地位.聲;
if (選項.全濁上歸去 && is`全濁 上聲`) 聲調 = '去';
return {
附加符號: ['̀', '́', '̌', ''], // 入聲已由韻尾表明,無需附加符號
調類數字: ['¹', '²', '³', '⁴'],
}[選項.聲調 ?? '附加符號']['平上去入'.indexOf(聲調)];
}
function 底層to表層(音節) {
function 替換韻核(from, tos, condition = true) {
if (!condition) return;
音節.韻核 = 音節.韻核.replace(from, 音節.介音.includes('w') ? tos.pop() : tos[0]);
}
const is曾梗攝 = ['ŋ', 'k'].includes(音節.韻尾);
替換韻核('a', ['ɑ']); // 先把 /a/ 重置成一等的表層形式 [ɑ]
if (音節.介音.includes('ʕ')) {
// 二等
替換韻核('ɯ', ['ɯ', 'u']);
替換韻核('ɨ', ['iˤ'], is曾梗攝);
替換韻核('ɨ', ['ɨ', 'u'], 音節.韻尾);
替換韻核('ɑ', ['a']);
} else if (音節.介音.includes('j')) {
// 三四等
替換韻核('ɯ', ['ɯ', 'u']);
替換韻核('ɨ', ['ɨ', 'y'], 音節.韻尾);
替換韻核('ɨ', ['i']);
替換韻核('ɑ', ['æ', 'ɐ'], !音節.韻尾); // 北宋時麻二麻三未有明確分開的跡象,暫擬作 [jæ]
替換韻核('ɑ', ['ɛ', 'ɔ'], is`鈍音` && !音節.介音.includes('ʲ'));
替換韻核('ɑ', ['ɛ']);
} else {
// 一等
替換韻核('ɯ', ['ɯ', 'u']);
替換韻核('ɨ', ['ɨ', 'u']);
替換韻核('ɑ', ['ʌ', 'ɔ'], !音節.韻尾);
替換韻核('ɑ', ['ɑ', 'ɔ']);
替換韻核('ɔ', [選項.蟹一合?.slice(-2, -1) ?? 'u'], 音節.韻尾 === 'j');
}
if (is曾梗攝) {
替換韻核('u', ['ɨ']); // /wɨŋ/ 不寫作 [uŋ],以免與通攝混淆
} else if (['wŋ', 'wk'].includes(音節.韻尾)) {
替換韻核(/[iɨy]/, ['u']);
替換韻核(/[ɛɔ]/, ['ɑ']);
音節.韻尾 = 音節.韻尾.replace('w', '');
}
音節.介音 = 音節.介音.replace('jw', 'ɥ');
if (['ji', 'ɥy', 'wu'].includes(音節.介音.slice(-1) + 音節.韻核)) 音節.介音 = 音節.介音.slice(0, -1);
}
function 後處理(音節) {
let 知照組符號 = 選項.知照組 ?? 'ʂʐɹ|ɕʑɹ';
if (!音節.介音.includes('ʕ')) 知照組符號 = 知照組符號.slice(-3); // 二等取開頭 3 個符號,三等取最後 3 個符號
[...'ʃʒɹ'].forEach((e, i) => { 音節.聲母 = 音節.聲母.replace(e, 知照組符號[i]); });
if (is表層) 音節.介音 = 音節.介音.replace('ʕ', '');
if (音節.介音.includes('ʲ')) {
音節.聲母 += 'ʲ';
音節.聲母 = 音節.聲母.replace('ʰʲ', 'ʲʰ');
音節.介音 = 音節.介音.replace('ʲ', '');
}
if (is表層 && 音節.聲母.includes('ɰ')) {
音節.聲母 = 音節.聲母.replace('ɰʲ', ''); // 韻圖四等由細音韻母指示
if (音節.介音 === 'ɥ' || !音節.介音 && 音節.韻核 === 'y') 音節.聲母 = 音節.聲母.replace('ɰ', 'w');
if (音節.介音 === 'w' || !音節.介音 && 音節.韻核 === 'u') 音節.聲母 = 音節.聲母.replace('ɰ', '');
}
if (選項.次濁上喉化 === false) 音節.聲母 = 音節.聲母.replace('ˀ', '');
if (選項.全濁平送氣 === false && is`全濁`) 音節.聲母 = 音節.聲母.replace('ʰ', '');
if (is`入聲`) {
let 韻尾from = ['p', 't', 'k', 'wk'];
let 韻尾to = 選項.入聲尾?.split(' ') ?? 韻尾from;
音節.韻尾 = 韻尾to[韻尾from.indexOf(音節.韻尾)];
}
}
function get音節() {
const 韻基 = get韻基();
const 音節 = {
聲母: get聲母(),
介音: get介音(get等(), get開合()),
韻核: 韻基[0],
韻尾: 韻基.substring(1),
聲調: get聲調(),
};
if (is表層) 底層to表層(音節);
後處理(音節);
音節.韻母 = 音節.介音 + 音節.韻核 + 音節.韻尾;
if (選項.聲調 === '調類數字') 音節.帶調韻母 = 音節.韻母 + 音節.聲調;
else if (音節.韻核.length === 1) 音節.帶調韻母 = 音節.介音 + 音節.韻核 + 音節.聲調 + 音節.韻尾;
else 音節.帶調韻母 = 音節.介音 + 音節.韻核[0] + 音節.聲調 + 音節.韻核.slice(1) + 音節.韻尾;
return 音節;
}
調整音韻地位();
const 音節 = get音節();
return 音節.聲母 + 音節.帶調韻母;