Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 推導《東國正韻》式漢字音 #49

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ https://nk2028-1305783649.file.myqcloud.com/qieyun-examples/<filename>
- 推導上海話 (Extrapolated Shanghainese): `zaonhe.js`
- 推導南京話 (Extrapolated Nankinese): `langjin.js`
- 推導大埔話 (Extrapolated Taibu Hakka): `taibu.js`
- 推導《東國正韻》式漢字音 (Extrapolated Sino-Korean Pronunciation from _Dongguk Jeongun_): `dongguk.js`

**人造音系 artificial phonological system**

Expand Down
278 changes: 278 additions & 0 deletions dongguk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
/* 推導《東國正韻》式漢字音
*
* https://zhuanlan.zhihu.com/p/616644846
*
* 申叔舟, 崔恆, 成三問, et al., 1448(朝鮮正統十三年). 東國正韻[M/OL]. 漢城: [s.n.]. https://commons.wikimedia.org/wiki/Category:Dictionary_of_Korean_Pronunciation_of_Chinese_Letters.
* 申祐先, 2014. 韓國語漢字音歷史層次研究[D/OL]. 臺北: 國立臺灣大學. https://archive.org/details/woosun_shin_2014.
*
* @author Mishiro
*/

const is = (...x) => 音韻地位.屬於(...x);
const when = (...x) => 音韻地位.判斷(...x);

if (!音韻地位) return [
['推導原書部分例外讀音', true],
['推導現代讀音', false],
].concat(選項.推導現代讀音 ? [
['使用激音表示次清聲母', true],
['應用頭音法則', false],
['標記長音', 選項.注音方案 !== '文觀部轉寫' ? false : null],
['注音方案', [1, '諺文', '耶魯轉寫', '文觀部轉寫']],
] : [
['注音方案', [1, '諺文', '耶魯轉寫']],
]);

const is推導例外 = 選項.推導原書部分例外讀音;
const is推導現代 = 選項.推導現代讀音;

const 轉寫 = {
'耶魯轉寫': {
ᄀ: 'k', ᄏ: 'kh', ᄁ: 'kk', ᅌ: 'ng',
ᄃ: 't', ᄐ: 'th', ᄄ: 'tt', ᄂ: 'n', ᄅ: 'l',
ᄇ: 'p', ᄑ: 'ph', ᄈ: 'pp', ᄆ: 'm',
ᄌ: 'c', ᄎ: 'ch', ᄍ: 'cc',
ᄉ: 's',   ᄊ: 'ss',   ᅀ: 'z',
ᅙ: 'q', ᄒ: 'h', ᅘ: 'hh',   ᄋ: '',

ᆞ: 'o', ᆡ: 'oy',     ᅩ: 'wo', ᅬ: 'woy', ᅭ: 'ywo',
ᅳ: 'u', ᅴ: 'uy',     ᅮ: 'wu', ᅱ: 'wuy', ᅲ: 'ywu', ᆔ: 'ywuy',
ᅵ: 'i',       // ᅱ: 'wi',
ᅡ: 'a', ᅢ: 'ay', ᅣ: 'ya', ᅤ: 'yay', ᅪ: 'wa', ᅫ: 'way',
ᅥ: 'e', ᅦ: 'ey', ᅧ: 'ye', ᅨ: 'yey', ᅯ: 'we', ᅰ: 'wey', ᆑ: 'ywe', ᆒ: 'ywey',

ᇰ: 'ng', ᆨ: 'k', ᆼ: is推導現代 ? 'ng' : '',
ᆫ: 'n', ᇙ: 'lq', ᆯ: 'l',
ᆷ: 'm', ᆸ: 'p', ᇢ: 'w',
},

'文觀部轉寫': {
ᄀ: 'g', ᄂ: 'n', ᄃ: 'd', ᄅ: 'r', ᄆ: 'm', ᄇ: 'b', ᄉ: 's', ᄋ: '', ᄌ: 'j', ᄎ: 'ch', ᄏ: 'k', ᄐ: 't', ᄑ: 'p', ᄒ: 'h',

  ᅡ: 'a', ᅢ: 'ae',   ᅣ: 'ya', ᅤ: 'yae',
  ᅥ: 'eo', ᅦ: 'e',   ᅧ: 'yeo', ᅨ: 'ye',
ᅩ: 'o', ᅪ: 'wa', ᅫ: 'wae', ᅬ: 'oe', ᅭ: 'yo',
ᅮ: 'u', ᅯ: 'wo', ᅰ: 'we', ᅱ: 'wi', ᅲ: 'yu',
ᅳ: 'eu',     ᅴ: 'ui',
ᅵ: 'i',

ᆨ: 'k', ᆫ: 'n', ᆯ: 'l', ᆷ: 'm', ᆸ: 'p', ᆼ: 'ng',
},
};

function 音變(音節) {
const hasᅵ = 'ᅵᅭᅣᅲᅧᅨ'.includes(音節.中聲);
function 替換(key, rule, condition = true) {
if (condition) Object.entries(rule).forEach(([k, v]) => {
音節[key] = 音節[key].replace(k, v);
});
}
替換('初聲', { ᄏ: 'ᄀ', ᄐ: 'ᄃ', ᄑ: 'ᄇ', ᄎ: 'ᄌ' }, !選項.使用激音表示次清聲母);
替換('初聲', {
ᄁ: 'ᄀ', ᄄ: 'ᄃ', ᄈ: 'ᄇ', ᄍ: 'ᄌ', ᄊ: 'ᄉ', ᅘ: 'ᄒ',
ᅌ: 'ᄋ', ᅙ: 'ᄋ', ᅀ: 'ᄋ',
});
替換('中聲', { ᆞ: 'ᅡ', ᆡ: 'ᅢ', ᆔ: 'ᅲ', ᆑ: 'ᅧ', ᆒ: 'ᅨ' });
替換('終聲', { ᇢ: '', ᆼ: '', ᇰ: 'ᆼ', ᇙ: 'ᆯ' });
替換('初聲', { ᄅ: 'ᄂ' }, 選項.應用頭音法則);
替換('初聲', { ᄂ: 'ᄋ' }, hasᅵ && 選項.應用頭音法則);
替換('初聲', { ᄃ: 'ᄌ', ᄐ: 'ᄎ' }, hasᅵ);
替換('中聲', { ᅭ: 'ᅩ', ᅣ: 'ᅡ', ᅲ: 'ᅮ', ᅧ: 'ᅥ', ᅨ: 'ᅦ' }, 'ᄌᄎᄉ'.includes(音節.初聲));
替換('中聲', { ᅳ: 'ᅮ' }, 'ᄇᄆ'.includes(音節.初聲));
替換('中聲', { ᅴ: 'ᅵ' }, !'ᄒᄋ'.includes(音節.初聲));
替換('中聲', { ᅪ: 'ᅡ', ᅫ: 'ᅢ' }, !'ᄀᄏᄒᄋ'.includes(音節.初聲));
替換('中聲', { ᅱ: 'ᅮ' }, 音節.終聲);
}

function 聲母() {
return when([
[is推導例外, [
['疑母', [
['齊韻 上聲 或 先韻 平上聲 或 蕭韻 去聲 或 青韻 入聲', 'ᅌ'],
['耕韻 去聲', 'ᄋ'],
]],
['滂母 凡韻', 'ᄇ'],
['崇母 (通效假宕攝 或 尤韻 平聲)', 'ᄊ'],
['云母', [
['蒸韻', 'ᅙ'],
['支脂之韻 仄聲 或 仙韻 開口 或 侵韻 入聲', 'ᄋ'],
]],
]],

['崇母 止攝', 'ᄊ'],
['云母 通攝 舒聲', 'ᅘ'],
['疑母 (四等 或 重紐A類 或 幽韻)', 'ᄋ'],
['', {
// 牙音
見: 'ᄀ', 溪: 'ᄏ', 羣: 'ᄁ', 疑: 'ᅌ',
// 舌音
端: 'ᄃ', 透: 'ᄐ', 定: 'ᄄ', 泥: 'ᄂ',
知: 'ᄃ', 徹: 'ᄐ', 澄: 'ᄄ', 孃: 'ᄂ',
// 脣音
幫: 'ᄇ', 滂: 'ᄑ', 並: 'ᄈ', 明: 'ᄆ',
// 齒音
精: 'ᄌ', 清: 'ᄎ', 從: 'ᄍ', 心: 'ᄉ', 邪: 'ᄊ',
莊: 'ᄌ', 初: 'ᄎ', 崇: 'ᄍ', 生: 'ᄉ', 俟: 'ᄊ',
章: 'ᄌ', 昌: 'ᄎ', 常: 'ᄊ', 書: 'ᄉ', 船: 'ᄊ',
// 喉音
影: 'ᅙ', 曉: 'ᄒ', 匣: 'ᅘ', 云: 'ᅌ', 以: 'ᄋ',
// 半舌音
來: 'ᄅ',
// 半齒音
日: 'ᅀ',
}[音韻地位.母]],
], '無初聲規則', true);
}

function 韻母(輸出) {
const 韻母字典 = {
曾開: { 中聲: 'ᅵ ᅳ ᅳ ᅳ', 終聲: 'ᇰ' },
曾合: { 中聲: '〇 ᆑ 〇 ᅱ', 終聲: 'ᇰ' },
梗開: { 中聲: 'ᅧ ᅧ ᆡ 〇', 終聲: 'ᇰ' },
梗合: { 中聲: 'ᆑ ᆑ ᅬ 〇', 終聲: 'ᇰ' },
通開: { 中聲: 'ᅲ ᅮ ᅲ ᅩ', 終聲: 'ᇰ' }, // 東冬韻
通合: { 中聲: 'ᅭ ᅩ 〇 ᅩ', 終聲: 'ᇰ' }, // 鍾韻
宕開: { 中聲: 'ᅣ ᅣ ᅡ ᅡ', 終聲: 'ᇰ' }, // 江陽唐韻
宕合: { 中聲: '〇 ᅪ ᅪ ᅪ', 終聲: 'ᇰ' },

臻開: { 中聲: 'ᅵ ᅳ ᅳ ᆞ', 終聲: 'ᆫ' },
臻合: { 中聲: 'ᅲ ᅮ ᅩ ᅩ', 終聲: 'ᆫ' },
山開: { 中聲: 'ᅧ ᅥ ᅡ ᅡ', 終聲: 'ᆫ' },
山合: { 中聲: 'ᆑ ᅯ ᅪ ᅪ', 終聲: 'ᆫ' },

深開: { 中聲: 'ᅵ ᅳ ᆞ 〇', 終聲: 'ᆷ' },
咸開: { 中聲: 'ᅧ ᅥ ᅡ ᅡ', 終聲: 'ᆷ' },

效開: { 中聲: 'ᅭ ᅭ ᅭ ᅩ', 終聲: 'ᇢ' },
流開: { 中聲: 'ᅲ ᅮ ᅮ ᅮ', 終聲: 'ᇢ' },

止開: { 中聲: 'ᅵ ᅴ ᆞ ᆞ', 終聲: 'ᆼ' },
止合: { 中聲: 'ᆔ ᅱ ᆔ 〇', 終聲: 'ᆼ' },
蟹開: { 中聲: 'ᅨ ᅨ ᅢ ᆡ', 終聲: 'ᆼ' },
蟹合: { 中聲: 'ᆒ ᆒ ᅫ ᅬ', 終聲: 'ᆼ' },
遇開: { 中聲: 'ᅧ ᅥ ᅩ 〇', 終聲: 'ᆼ' }, // 魚韻
遇合: { 中聲: 'ᅲ ᅮ ᅮ ᅩ', 終聲: 'ᆼ' }, // 虞模韻
果開: { 中聲: 'ᅣ ᅣ ᅡ ᅡ', 終聲: 'ᆼ' }, // 歌麻韻
果合: { 中聲: 'ᅪ ᅪ ᅪ ᅪ', 終聲: 'ᆼ' },
};

let 等 = when([
['精組 止攝 開口', '一'],
['莊組', '二'],
['非 三等', 音韻地位.等],

['幫組', [
['東鍾歌陽韻 非 重紐A類', '一'],
['微廢韻', '四'],
['重紐B類 非 侵韻 或 蒸幽韻', '四'],
]],
['銳音 或 重紐A類 或 麻幽韻 非 重紐B類', '四'],
['', '三'],
], '無等規則', true);
let 開合 = when([
['鍾虞模韻', '合'],
['江韻 銳音', '合'],
['文魂韻 幫組', '合'],
['', 音韻地位.呼 ?? '開'],
], '無開合規則', true);

let 韻母 = 韻母字典[(is`江韻` ? '宕' : is`元韻` ? '山' : is`麻韻` ? '果' : 音韻地位.攝) + 開合];
let 中聲 = when([
[is推導例外, [
['東韻 云曉母 三等 入聲', 'ᅲ'],
['鍾韻', [
['孃母 平聲 或 書來母 入聲', 'ᅩ'],
['羣母 入聲', 'ᅮ'],
['澄曉母 舒聲', 'ᅲ'],
]],
['江韻 徹初母 入聲', 'ᅡ'],
['脂韻 合口', [
['知母 去聲', 'ᅱ'],
['云母 上聲', 'ᆔ'],
]],
['之韻 崇母 平聲', 'ᅵ'],
['虞韻', [
['來母', 'ᅮ'],
['生母 上聲', 'ᅲ'],
]],
['佳韻 匣母 上聲', 'ᅢ'],
['咍韻 (見組 或 泥母)', 'ᅢ'],
['廢韻 昌母', 'ᆡ'],
['眞韻', [
['開口 (影母 入聲 或 曉母 去聲)', 'ᅵ'],
['合口 (重紐B類 或 云母) 非 見母', 'ᅲ'], // 無重紐對立一律按四等讀
]],
['元韻 影母 開口 平聲', 'ᅧ'],
['痕韻 匣母', is`舒聲` ? 'ᅳ' : 'ᅩ'],
['仙韻', [
['開口 (見母 或 溪母 去聲)', 'ᅥ'],
['開口 (疑母 上聲 或 影曉母 平聲)', 'ᅧ'],
['合口 來母 非 入聲', 'ᅯ'],
]],
['肴韻 崇母', 'ᅩ'],
['歌韻 來母 去聲', 'ᅪ'],
['陽韻 莊母 入聲', 'ᅣ'],
['庚韻 開口 二等 (澄母 上聲 或 曉母)', 'ᅧ'],
['耕韻 (見母 開口 或 疑母 去聲 或 明母 上聲)', 'ᅧ'],
['青韻 曉母 入聲', 'ᆑ'],
['蒸韻', [
['精從來母 或 以母 舒聲', 'ᅳ'],
['曉母 開口 入聲', 'ᅵ'],
['生母', is`舒聲` ? 'ᅵ' : 'ᆡ'],
['幫組 入聲', 'ᅧ'],
]],
['登韻 匣母 合口', 'ᅬ'],
['流攝 三等 曉母', 'ᅲ'],
['尤韻 滂母 平聲', 'ᅲ'],
['侯韻 泥母', 'ᅲ'],
['幽韻 羣母 上聲', 'ᅮ'],
['侵韻', [
['重紐A類 或 以母', 'ᅳ'],
['初母 上去聲', 'ᅵ'],
]],
['鹽韻 云母', 'ᅧ'],
]],

['止攝 莊初母 開口', 'ᅴ'],
['祭韻 合口 (重紐B類 或 云母)', 'ᅱ'],
['泰韻 非 合口', 'ᅢ'],
['肴韻 幫組', 'ᅩ'],
['陽韻 見組 開口', 'ᅡ'],
['', 韻母.中聲.split(' ')['四三二一'.indexOf(等)]],
], '無中聲規則', true);
let 終聲 = is`舒聲` ? 韻母.終聲 : { ᆷ: 'ᆸ', ᆫ: 'ᇙ', ᇰ: 'ᆨ' }[韻母.終聲];

return { 中聲, 終聲 };
}

let 音節 = {
初聲: 聲母(),
...韻母()
};
if (is推導現代) 音變(音節);

function 聲調() {
if (is推導現代) {
if (!選項.標記長音 || 選項.注音方案 === '文觀部轉寫') return '';
return {
諺文: { 平: '', 上: 'ː', 去: 'ː', 入: '' },
耶魯轉寫: { 平: '', 上: '̄', 去: '̄', 入: '' },
}[選項.注音方案][音韻地位.聲];
}
return {
諺文: { 平: '', 上: '〯', 去: '〮', 入: '〮' },
耶魯轉寫: { 平: '̀', 上: '̌', 去: '́', 入: '́' },
}[選項.注音方案][音韻地位.聲];
}

音節 = 音節.初聲 + 音節.中聲 + 音節.終聲;
if (選項.注音方案 !== '諺文') Object.entries(轉寫[選項.注音方案]).forEach(([k, v]) => { 音節 = 音節.replace(k, v); });
if (選項.注音方案 === '耶魯轉寫') {
if (is推導現代) {
音節 = 音節.replace('wo', 'o');
if (is`脣音` || 音節.includes('ywu')) 音節 = 音節.replace('wu', 'u');
音節 = 音節.replace('wuy', 'wi');
}
return 音節.replace(/.*[aeiou]/, "$&" + 聲調());
}
return 音節 + 聲調();
2 changes: 2 additions & 0 deletions test/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
zaonhe,
langjin,
taibu,
dongguk,
ayaka_v8,
} from "../index.js";

Expand Down Expand Up @@ -53,6 +54,7 @@ assert_equal(gwongzau(音韻地位), "siu2");
assert_equal(zaonhe(音韻地位), "sɔ̄");
assert_equal(langjin(音韻地位), "shao³");
assert_equal(taibu(音韻地位), "shau3");
assert_equal(dongguk(音韻地位), "쇼ᇢ〯");
assert_equal(ayaka_v8(音韻地位), "seu");

assert_equal(
Expand Down