diff --git a/README.md b/README.md index e7548f2..4c00d3e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ https://nk2028-1305783649.file.myqcloud.com/qieyun-examples/ - 推導盛中唐擬音 (Extrapolated Reconstruction of High and Middle Tang Chinese): `mid_tang.js` - 推導《聲音唱和圖》擬音 (Extrapolated Reconstruction of _Shing-im Chiang-xhua Dhu_): `chiangxhua.js` +- 推導《中原音韻》擬音 (Extrapolated Reconstruction of _Zhongyuan Yinyun_): `zhongyuan.js` - 推導《分韻撮要》擬音 (Extrapolated Reconstruction of _Fan-Wan Tsʽüt-Iú_): `fanwan.js` - 推導普通話 (Extrapolated Putonghua): `putonghua.js` - 推導廣州音 (Extrapolated Cantonese): `gwongzau.js` diff --git a/test/main.js b/test/main.js index ac516e9..3bc0ed1 100644 --- a/test/main.js +++ b/test/main.js @@ -13,6 +13,7 @@ import { msoeg_v8, mid_tang, chiangxhua, + zhongyuan, fanwan, putonghua, gwongzau, @@ -43,6 +44,7 @@ assert_equal(unt(音韻地位), "ɕéw"); assert_equal(msoeg_v8(音韻地位), "çiɛuˀ"); assert_equal(mid_tang(音韻地位), "ɕiɛw˦˥"); assert_equal(chiangxhua(音韻地位), "ɕiɛ́w"); +assert_equal(zhongyuan(音韻地位), "ʂjɛw³"); assert_equal(fanwan(音韻地位), "shiu2"); assert_equal(putonghua(音韻地位), "shǎo"); assert_equal(gwongzau(音韻地位), "siu2"); diff --git a/zhongyuan.js b/zhongyuan.js new file mode 100644 index 0000000..44e935e --- /dev/null +++ b/zhongyuan.js @@ -0,0 +1,342 @@ +/* 推導《中原音韻》 + * + * 可選四家擬音方案: + * + * - 楊耐思. 中原音韻音系. 北京: 中國社會科學出版社, 1981. + * - 寧繼福. 中原音韻表稿. 長春: 吉林文史出版社, 1985. + * - 薛鳳生. 中原音韻音位系統. 魯國堯, 侍建國, 譯. 北京: 北京語言學院出版社, 1990. + * - unt. 《中原音韻》音系簡述, 2021. https://zhuanlan.zhihu.com/p/353713058 + * + * @author unt + */ + +const is = (...x) => 音韻地位.屬於(...x); +const when = (...x) => 音韻地位.判斷(...x); + +if (!音韻地位) return [ + ['顯示', [5, + '音位(薛鳳生, 1990)', + '音位(unt, 2021)', + '音值(楊耐思, 1981)', + '音值(寧繼福, 1985)', + '音值(unt, 2021)', + ]], + ['標記古入聲字', true], + ['包含部分例外音變', true], + ['異讀分隔符(留空則爲換行)', ''], + ['高元音開口呼', 選項.顯示?.includes('unt') ?? true ? [2, 'ɨ', 'ə'] : null], +]; + +const 例外 = 選項.包含部分例外音變; +let 層次 = 0; // 本方案只涉及 0 和 1 兩個層次。對入聲來說,0 代表白讀,1 代表文讀 + +function 調整音韻地位() { + function 調整(表達式, 調整屬性) { if (is(表達式)) 音韻地位 = 音韻地位.調整(調整屬性); } + // 輕唇化例外 + 調整('明母 尤韻', { 等: '一', 韻: '侯' }); + 調整('明母 東韻', { 等: '一' }); + + 調整('云母 通攝 平聲', { 母: '匣' }); // 熊 + if (!例外) return; + + // 流攝脣音入遇攝字 + 調整('明母 侯韻 上聲', { 韻: '模' }); // 母某牡畝(忽略:謀戊) + 調整('尤韻 (並母 平上聲 或 幫滂母 去聲)', { 韻: '虞' }); // 浮、婦阜負、富、副 + + // 蟹攝二等入假攝字 + if (when([ + ['佳韻', [ + ['並母 上聲', true], // 罷 + ['見母 開口 平聲', true], // 佳 + ['溪母 合口 平聲', true], // 咼 + ['見匣母 合口 去聲', true], // 卦掛、畫 + [層次 === 1 && '疑母 開口 平聲', true], // 涯\崖 + [層次 === 1 && '生母 開口 去聲', true], // 洒\曬 + ]], + ['夬韻 匣母 合口 去聲', true], // 話 + ])) 調整('蟹攝', { 韻: '麻' }); + + 調整('端母 蕭韻 上聲', { 母: '泥' }); // 鳥 + 調整('生母 山韻 上聲', { 母: '初' }); // 産 + 調整('書母 通攝 舒聲', { 母: '昌' }); // 舂 + 調整('書母 鍾韻 入聲', { 母: '昌' }); // 束 + if (層次 === 0) 調整('書母 支韻 開口 去聲', { 母: '昌' }); // 翅\施 + 調整('見母 蕭韻 平聲', { 母: '曉' }); // 梟鴞驍 + + if (層次 === 0) { + 調整('果攝 開口 (定泥母 去聲 或 透母 平聲)', { 韻: '麻', 等: '二' }); // 大那+他 + 調整('端母 庚韻 上聲', { 韻: '麻' }); // 打 + } +} + +function get聲母() { + return when([ + [例外, [ + ['崇母 止攝 仄聲', 'ʂ'], // 士 + ['常母 平聲 (支韻 合口 或 魚尤宵韻)', 'tʂʰ'], // 垂蜍讎韶 + ['常母 深攝 平聲', 'ʂ'], // 忱煁 + ['船母 平聲 合口', 'tʂʰ'], // 船唇 + [層次 === 1 && '船母 曾攝 舒聲', 'tʂʰ'], // 乗\繩 + + [層次 === 1 && '匣母 寒韻 合口 平聲', ''], // 丸\桓 + ['匣母 肴韻 平聲', 'x'], // 爻。《中州樂府音韻類編》與哮小韻陰陽配對,《中州音韻》與遙小韻合併 + ['以母 蟹攝 合口', 'ɻ'], // 鋭 + [層次 === 0 && '脂韻 以母 合口 平聲', 'ʋ'], // 惟\遺 + ['疑母', [ + ['宕攝 三等 開口', 'ŋ'], // 仰、虐瘧 + ['山攝 三四等 開口 入聲', 'n'], // 囓臬糵 + ['咸攝 三四等 入聲', 'ŋ'], // 業鄴 + [層次 === 1 && '梗攝 二等 入聲', 'ŋ'], // 額 + ['效攝 一等 仄聲', 'ŋ'], // 傲奡鏊 + ]], // 俺《廣韻》未收,不考慮 + ]], + + ['東鍾微虞廢文元歌陽尤凡韻 三等 非 重紐A類', [ // “非 重紐A類”用於過濾𩦠小韻,“歌韻”用於包含縛小韻 + ['幫滂並母', 'f'], ['明母', 'ʋ'], + ]], + ['幫母 或 並母 仄聲', 'p'], ['滂並母', 'pʰ'], ['明母', 'm'], + ['端母 或 定母 仄聲', 't'], ['透定母', 'tʰ'], ['泥孃母', 'n'], ['來母', 'l'], + ['見母 或 羣母 仄聲', 'k'], ['溪羣母', 'kʰ'], ['影疑云以母', ''], ['曉匣母', 'x'], + ['精母 或 從母 仄聲', 'ts'], ['清從母', 'tsʰ'], ['心邪俟母', 's'], + + ['常母 平聲 陽聲韻', 'tʂʰ'], + ['知莊章母 或 澄崇母 仄聲', 'tʂ'], ['徹澄初崇昌母', 'tʂʰ'], ['生俟常書船母', 'ʂ'], ['日母', 'ɻ'], + ], '無聲母規則', true); +} + +function get介音() { + let 洪細 = when([ + [例外 && '見影組 二等 開口 梗攝 平聲', [ + ['曉母', ''], // 亨 + ['匣母 耕韻', ''], // 莖 + [層次 === 1 && '影母', ''], // 甖 + ]], + + ['幫組 微廢韻', 'j'], + ['幫組 東鍾微虞廢文元歌陽尤凡韻 三等 非 重紐A類', ''], + ['莊組', ''], + + ['宕攝 合口 三等', ''], + ['(精章組 或 日母) 止攝 開口', ''], + ['通攝 三等 舒聲', [ + ['知章組 非 孃母 或 日母', ''], + ['見母 或 溪羣母 仄聲', ''], // 弓拱恐共 + [層次 === 1 && '影母 平聲', ''], // 癰廱壅\邕嗈雍 + ]], + [層次 === 1 && '(知章組 或 日母) 通攝 入聲 非 孃母', ''], + + ['三四等 或 見影組 二等 非 合口', 'j'], + ['', ''], + ], '無洪細規則', true); + + let 開合 = when([ + [例外, [ + ['止蟹攝 四等 合口 匣母 平聲', ''], // 畦携 + ['止蟹攝 重紐A類 合口 見母 去聲', ''], // 季 + ['脂韻 以母 合口 平聲', ''], // 遺 + [層次 === 1 && '以母 合口 山攝', ''], // 緣沿掾\捐鉛鳶 + [層次 === 1 && '曉匣母 先韻 合口', ''], // 懸縣血 + [層次 === 1 && '清青韻 合口', ''], + + ['見組 祭韻 合口', ''], // 鱖 + + [層次 === 0 && '疑母 歌韻 開口 上聲', 'w'], // 我 + [層次 === 1 && '定母 宕江攝 入聲', ''], // 鐸 + [層次 === 0 && '明母 宕江攝 入聲', ''], + ['明母 豪韻', ''], + + ['止蟹攝 重紐B類 幫母 去聲', 'w'], // 秘祕賁\詖 + ['止蟹攝 重紐A類', [ + ['幫母 平聲', 'w'], // 卑(避諱) + ['幫母 支韻 去聲', 'w'], // 臂 + ['並母 仄聲', 'w'], // 婢避幣\斃 + ['明母 去聲', 'w'], // 袂寐 + ]], + ]], + + ['幫組', [ + ['(宕攝 或 曾攝 一等) 入聲', 'w'], + ['一等 非 通宕曾流攝 或 文歌韻', 'w'], + ['(止蟹攝 或 臻攝 入聲) 重紐B類', 'w'], // 蟹攝幫三實際上無 B 類 + ]], + + ['果江攝 銳音 或 宕攝 莊組', 'w'], + [層次 === 1 && '宕攝 入聲 銳音', 'w'], + ['合口', 'w'], + ['', ''], + ], '無開合規則', true); + + return 洪細 + 開合; +} + +function get韻基() { + return when([ + [例外, [ + ['心母 止攝 開口 上聲 非 脂韻', 'jəj'], // 璽枲徙\死(避諱) + [層次 === 1 && '昌母 止攝 開口 非 (之韻 上聲 或 支韻 去聲)', 'jəj'], // 蚩媸鴟幟熾\齒(元曲押支思)\\翅施 + ['知母 開口 (脂韻 平聲 或 之韻 上聲)', 'ə'], // 胝(元曲無)、徵(元曲押支思) + + [層次 === 0 && '幫滂並母 尤韻 仄聲', 'waw'], // 缶覆 + [層次 === 0 && '滂母 侯韻 上聲', 'waw'], // 剖 + [層次 === 0 && '明母 侯韻 去聲', 'aw'], // 茂 + ['泰韻 疑母 合口', 'aj'], // 外 + ]], + + ['遇攝', 'u'], // 魚模韻 + ['止攝 開口 (精莊章組 或 日母)', 'ə'], // 支思韻 + ['果攝 (一等 或 幫組 三等)', 'ʌ'], // 歌戈韻 + ['假攝 二等', 'a'], // 家麻韻 + ['果假攝 三等', 'ɛ'], // 車遮韻 + + ['蟹攝 (一等 開口 或 二等 或 莊組) 或 止攝 莊組', 'aj'], // 皆來韻 + ['止蟹攝', 'əj'], // 齊微韻 + + ['流攝', 'əw'], // 尤侯韻 + ['效攝 (一二等 或 莊組)', 'aw'], // 蕭豪韻·一二等 + ['效攝 三四等', 'ɛw'], // 蕭豪韻·三四等 + + ['舒聲', [ + [例外 && 層次 === 1 && '曾梗攝 一二等 非 開口 或 庚韻 三等 合口', 'uŋ'], + ['通攝', 'uŋ'], // 東鍾韻 + ['宕江攝', 'aŋ'], // 江陽韻 + ['曾梗攝', 'əŋ'], // 庚青韻 + + ['臻攝 非 元韻', 'ən'], // 真文韻 + ['山攝 一等 非 開口', 'ʌn'], // 桓歡韻 + ['山攝 (一二等 或 莊組) 或 元韻 幫組', 'an'], // 寒山韻 + ['山攝 三四等 或 元韻', 'ɛn'], // 先天韻 + + ['深攝', is`幫組` ? 'ən' : 'əm'], // 侵尋韻 + ['咸攝 (一二等 或 莊組) 或 嚴凡韻 幫組', is`幫組` ? 'an' : 'am'], // 監咸韻 + ['咸攝 三四等', is`幫組` ? 'ɛn' : 'ɛm'], // 廉纖韻 + ]], + ['入聲', [ + ['通攝', [ + [層次 === 0 && '(精知章莊組 或 來日母) 東韻 三等', 'əw'], + [層次 === 0 && '(知章莊組 或 日母) 鍾韻', 'əw'], // 燭褥+贖屬(元曲押魚模、尤侯)\辱(元曲只押魚模) + ['', 'u'], + ]], + ['宕江攝', [ + [層次 === 1, 'ʌ'], + ['', 'aw'], + ]], + + [例外, [ + [層次 === 1 && '登韻 心母', 'ə'], // 塞(元曲押齊微) + [層次 === 1 && '登韻 精母', 'aj'], // 則(元曲押齊微) + [層次 === 1 && '曾梗攝 一等 溪母 開口', 'jaj'], // 刻(元曲押齊微、皆來) + [層次 === 1 && '曾梗攝 二等 溪疑母 開口', 'ɛ'], // 客(元曲押皆來、車遮)、額(元曲只押皆來) + [層次 === 0 && '文韻 並母', 'ʌ'], // 佛(元曲押魚模、歌戈) + ['臻攝 一等 幫組 非 明母', 'ʌ'], // 勃 + [層次 === 0 && '日母 深攝', 'u'], // 入 + ]], + ['臻攝 (一等 或 文韻 幫組 或 合口 非 元韻)', 'u'], // +麧(《中原音韻》未收) + ['臻深攝 莊組 開口', 'ə'], + ['曾梗臻深攝 (二等 或 莊組)', 'aj'], + ['曾梗臻深攝 非 元韻', 'əj'], + ['山咸攝 一等 非 (銳音 開口)', 'ʌ'], + ['山咸攝 (一二等 或 莊組) 或 元嚴凡韻 幫組', 'a'], + ['山咸攝 三四等 或 元韻', 'ɛ'], + ]], + ], '無韻基規則', true); +} + +function get聲調() { + return when([ + [例外, [ + ['匣母 蟹攝 上聲 開口', '³'], // 駭蟹\解獬 + ['羣母 臻攝 上聲 合口 非 元韻', '³'], // 窘 + + ['羣母 梗攝 三等 開口 入聲', '⁴ʼ'], // 劇 + ['生母 山攝 合口 入聲', '⁴ʼ'], // 刷 + ['影疑母 通臻攝 一等 入聲 非 開口', '³ʼ'], // 屋沃兀 + [層次 === 0 && '影母 眞韻 重紐A類 開口 入聲', '³ʼ'], // 一 + ]], + ['平聲 (全清 或 次清)', '¹'], + ['平聲 (全濁 或 次濁)', '²'], + ['上聲 非 全濁', '³'], + ['上去聲', '⁴'], + ['入聲', [ + ['全濁', '²ʼ'], + ['次濁 或 影母', '⁴ʼ'], // 影母入聲《中原音韻》按次濁歸派 + ['', '³ʼ'], + ]], + ], '無聲調規則', true); +} + +function get音節() { + function 批量替換(str, pairs) { + pairs.forEach(pair => { str = str.replace(new RegExp(pair[0], 'g'), pair[1]); }); + return str; + } + let 聲母 = get聲母(); + let 韻母 = get介音() + get韻基(); + let 聲調 = get聲調(); + 韻母 = 韻母.replace('wu', 'u'); + 韻母 = 韻母.replace('jwəj', 'wəj'); + 韻母 = 韻母.replace('jʌ', 'jwʌ'); + + if (選項.顯示.includes('unt')) { + if (選項.顯示.includes('音值')) { + 韻母 = 批量替換(韻母, [ + ['jw', 'ɥ'], ['əj', 'əi'], + ['jə', 'i'], ['ii', 'i'], + ['wə', 韻母.includes('ŋ') ? 'wə' : 'u'], + ['ɥə', 韻母.includes('ŋ') ? 'ɥi' : 'y'], + ['wʌ', 'wɔ'], ['ɥʌ', 'jɔ'], + ]); + if (韻母 === 'ə' && 選項.高元音開口呼 === 'ə') 韻母 = 聲母.includes('s') ? 'ɹ̩' : 'ɻ̍'; + if ('pmfʋ'.includes(聲母[0])) 韻母 = 韻母.replace('wɔ', 'ɔ'); + if ('tnlsʂɻ'.includes(聲母[0]) && 韻母 === 'wɔ') 韻母 = 'ɔ'; + } + 韻母 = 韻母.replace('ə', 選項.高元音開口呼); + } else if (選項.顯示.includes('楊耐思')) { + 聲母 = 批量替換(聲母, [['ʰ', 'ʻ'], ['ʋ', 'v'], ['ʂ', 'ʃ'], ['ɻ', 'ʒ']]); + 韻母 = 批量替換(韻母, [ + ['j', 'i'], ['w', 'u'], + ['əi', 'ei'], ['iei', 'i'], + ['uau', ['k', 'kʻ', '', 'ŋ', 'x'].includes(聲母) ? 'uau' : 'au'], + ['iau', ['k', 'kʻ', '', 'ŋ', 'x'].includes(聲母) ? 'iau' : 'iɛu'], + ['ʌ', 'o'], ['iuo', 'io'], ['uon', 'on'], + ['ia', 'i̯a'], ['i̯aŋ', 'iaŋ'], + ]); + if (韻母 === 'ə') 韻母 = 'ï'; + } else if (選項.顯示.includes('寧繼福')) { + 聲母 = 批量替換(聲母, [['ʰ', 'ʻ'], ['ɻ', 'ɽ']]); + 韻母 = 批量替換(韻母, [ + ['j', 'i'], ['w', 'u'], + ['iəi', 'i'], ['uəi', 'ui'], ['əi', 'ei'], + ['uau', 'pmfʋ'.includes(聲母[0]) ? 'ɑ-u' : 'a-u'], // - 用於佔位 + ['iau', 聲調.includes('ʼ') ? 'ia-u' : 'a-u'], + ['au', 聲母.includes('ʂ') || 'pmfʋ'.includes(聲母[0]) ? 'au' : 'ɑu'], + ['iɛu', 'iau'], ['-u', 'u'], + ['ʌ', 'ɔ'], ['iuɔ', 'iɔ'], + ]); + if (韻母 === 'ə') 韻母 = 'ï'; + if ('pmfʋ'.includes(聲母[0]) && !'iu'.includes(韻母[0]) && !'iu'.includes(韻母.slice(-1))) 韻母 = 'u' + 韻母; + if (is`孃母 效攝`) 韻母 = 'au'; + } else if (選項.顯示.includes('薛鳳生')) { + 聲母 = 批量替換(聲母, [['ʰ', 'h'], ['ʋ', 'v'], ['ʂ', 'sr'], ['ɻ', 'r'], ['ts', 'c'], ['x', 'h']]); + 韻母 = 批量替換(韻母, [ + ['j', 'y'], + ['əŋ', 'eŋ'], ['ə', 'ɨ'], ['ɛ', 'e'], + ['uŋ', 'woŋ'], ['u', 'wɨ'], ['ʌ', 'o'], + ['waw', 聲母.includes('r') ? 'ow' : 'wow'], + ['aw', 聲母.includes('r') || 'pmfv'.includes(聲母[0]) ? 'aw' : 'ow'], ['yow', 'yaw'], + ]); + if ('pmfv'.includes(聲母[0]) && !'yw'.includes(韻母[0])) 韻母 = 'w' + 韻母; + if (is`孃母 效攝`) 韻母 = 'aw'; + } + + if (!選項.標記古入聲字) 聲調 = 聲調[0]; + return 聲母 + 韻母 + 聲調; +} + +const 音韻地位備份 = 音韻地位; +const 結果 = [0, 1].map(i => { + 層次 = i; + 音韻地位 = 音韻地位備份; + 調整音韻地位(); + return get音節(); +}); +return [...new Set(結果)].join(選項['異讀分隔符(留空則爲換行)'] || '\n');