├── .github ├── actions │ └── publish-to-cos │ │ └── action.yml └── workflows │ └── build.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── ayaka_v8.js ├── baxter.js ├── build └── main.js ├── eslint.config.js ├── gwongzau.js ├── high_tang.js ├── karlgren.js ├── mid_tang.js ├── mongol.js ├── msoeg_v8.js ├── n_song.js ├── package-lock.json ├── package.json ├── panwuyun.js ├── position.js ├── putonghua.js ├── rules └── tagged-is.js ├── test └── main.js ├── tupa.js ├── unt.js ├── wangli.js ├── yec_en_hua.js ├── zaonhe.js └── zhongyuan.js /.github/actions/publish-to-cos/action.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Tencent Cloud COS 2 | description: Publish static files to Tencent Cloud Object Storage 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup Python 8 | uses: actions/setup-python@v5 9 | with: 10 | python-version: '3.11' 11 | 12 | - name: Install coscmd 13 | run: pip install coscmd 14 | shell: bash 15 | 16 | - name: Configure coscmd 17 | run: coscmd config -a $SECRET_ID -s $SECRET_KEY -b nk2028-1305783649 -r ap-guangzhou 18 | shell: bash 19 | 20 | - name: Publish static files to COS 21 | run: coscmd upload -rs --delete -f . /tshet-uinh-examples --ignore '*/.*,*/node_modules/*,./test/*,./build/*,./dist/*,./package.json,./package-lock.json,./eslint.config.js' 22 | shell: bash 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main, dev, dev-*] 7 | pull_request: 8 | branches: [main] 9 | release: 10 | types: [created] 11 | 12 | jobs: 13 | build: 14 | name: ${{ github.event_name == 'release' && 'Publish to NPM' || (github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.repository == 'nk2028/tshet-uinh-examples' && github.ref == 'refs/heads/main')) && 'Publish to Tencent Cloud COS' || 'Test' }} 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout latest code 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: '22' 24 | registry-url: https://registry.npmjs.org/ 25 | 26 | - name: Install Node.js dependencies 27 | run: npm ci 28 | 29 | - name: Lint schemata 30 | run: npm run lint 31 | 32 | - name: Build project 33 | run: npm run build 34 | 35 | - name: Run tests 36 | run: npm test 37 | 38 | - if: github.event_name == 'release' 39 | name: Publish to NPM 40 | run: npm publish 41 | env: 42 | NODE_AUTH_TOKEN: ${{ secrets.npm_token }} 43 | 44 | - if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.repository == 'nk2028/tshet-uinh-examples' && github.ref == 'refs/heads/main') 45 | name: Publish to Tencent Cloud COS 46 | uses: ./.github/actions/publish-to-cos 47 | env: 48 | SECRET_ID: ${{ secrets.SecretId }} 49 | SECRET_KEY: ${{ secrets.SecretKey }} 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["ms-python.python"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[javascript]": { 3 | "editor.formatOnSave": false 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tshet-uinh-examples 2 | 3 | JavaScript code examples to generate the derivatives of the Qieyun system using TshetUinh.js 4 | 5 | ## Usage 6 | 7 | ``` 8 | https://nk2028-1305783649.file.myqcloud.com/tshet-uinh-examples/ 9 | ``` 10 | 11 | ## List of included examples 12 | 13 | **音韻地位 phonological position:** `position.js` 14 | 15 | **切韻音系拼音或轉寫 romanization/transcription of the Qieyun system** 16 | 17 | - 切韻拼音 (Tshet-uinh Phonetic Alphabet): `tupa.js` 18 | - 白一平轉寫 (Baxter’s Transcription): `baxter.js` 19 | 20 | **切韻音系擬音 reconstruction of the Qieyun system** 21 | 22 | - 高本漢擬音 (Bernhard Karlgren’s Reconstruction): `karlgren.js` 23 | - 王力擬音 (Wang Li’s Reconstruction): `wangli.js` 24 | - 潘悟雲擬音 (Pan Wuyun’s Reconstruction): `panwuyun.js` 25 | - unt 擬音 (unt’s Qieyun Reconstruction): `unt.js` 26 | - msoeg 擬音 V8 (msoeg’s Reconstruction V8): `msoeg_v8.js` 27 | 28 | **推導後世音系 extrapolated phonological system of later periods** 29 | 30 | - 推導盛唐(平水韻)擬音 (Extrapolated Reconstruction of High Tang Chinese (*Pingshui Yun*)): `high_tang.js` 31 | - 推導中唐(韻圖)擬音 (Extrapolated Reconstruction of Middle Tang Chinese (*Yuntu*)): `mid_tang.js` 32 | - 推導北宋(聲音唱和圖)擬音 (Extrapolated Reconstruction of Northern Song Chinese (*Shengyin Changhe Tu*)): `n_song.js` 33 | - 推導《蒙古字韻》 (Extrapolated _Menggu Ziyun_): `mongol.js` 34 | - 推導《中原音韻》擬音 (Extrapolated Reconstruction of _Zhongyuan Yinyun_): `zhongyuan.js` 35 | 36 | **現代方言推導音 extrapolated pronunciations of modern dialects** 37 | 38 | - 推導普通話 (Extrapolated Putonghua): `putonghua.js` 39 | - 推導廣州話 (Extrapolated Cantonese): `gwongzau.js` 40 | - 推導上海話 (Extrapolated Shanghainese): `zaonhe.js` 41 | 42 | **人造音系 artificial phonological system** 43 | 44 | - 綾香思考音系 (Ayaka’s Phonological System for Thinking): `ayaka_v8.js` 45 | - 不通話 (Yec-en-ʻua): `yec_en_hua.js` 46 | -------------------------------------------------------------------------------- /ayaka_v8.js: -------------------------------------------------------------------------------- 1 | /* 綾香思考音系 2 | * 3 | * https://ayaka.shn.hk/v8/ 4 | * 5 | * @author Ayaka 6 | */ 7 | 8 | /** @type { 音韻地位['屬於'] } */ 9 | const is = (...x) => 音韻地位.屬於(...x); 10 | /** @type { 音韻地位['判斷'] } */ 11 | const when = (...x) => 音韻地位.判斷(...x); 12 | 13 | // 1. 選項 14 | 15 | if (!音韻地位) return [ 16 | ['書寫系統', [3, '平假名', '片假名', '日本式羅馬字', '平文式羅馬字'], { 17 | description: [ 18 | "'平假名': 地 ち", 19 | "'片假名': 地 チ", 20 | "'日本式羅馬字': 地 ti", 21 | "'平文式羅馬字': 地 chi" 22 | ].join('\n'), 23 | }], 24 | 25 | ['ヰヱヲ小假名', true, { 26 | description: ['true: 迥 ク𛅥ィ', 'false: 迥 クヱィ', '僅當開啓假名時生效'].join('\n'), 27 | }], 28 | 29 | ['音變', [1, { value: null, text: '無' }, '現代日語'], { 30 | description: [ 31 | "'無': 宙 チウ tiu;南 ダム dam;愁 スウ suu", 32 | "'現代日語': 宙 チュウ tyuu;南 ダン dan;愁 スウ suu", 33 | ].join('\n'), 34 | }], 35 | 36 | // 參考:尉遲治平. 日本悉曇家所傳古漢語調值. 37 | ['聲調', [1, { value: null, text: '無' }, '四聲', '四聲(數字)', '四聲(調值)', '六聲(調值)', '六聲(符號)', '八聲', '八聲(數字)', '八聲(調值)']], 38 | ]; 39 | 40 | // 2. 輔助函數 41 | 42 | const 假名表 = { 43 | a: 'ア', i: 'イ', u: 'ウ', e: 'エ', o: 'オ', 44 | ka: 'カ', ki: 'キ', ku: 'ク', ke: 'ケ', ko: 'コ', 45 | ga: 'ガ', gi: 'ギ', gu: 'グ', ge: 'ゲ', go: 'ゴ', 46 | sa: 'サ', si: 'シ', su: 'ス', se: 'セ', so: 'ソ', 47 | za: 'ザ', zi: 'ジ', zu: 'ズ', ze: 'ゼ', zo: 'ゾ', 48 | ta: 'タ', ti: 'チ', tu: 'ツ', te: 'テ', to: 'ト', 49 | da: 'ダ', di: 'ヂ', du: 'ヅ', de: 'デ', do: 'ド', 50 | na: 'ナ', ni: 'ニ', nu: 'ヌ', ne: 'ネ', no: 'ノ', 51 | pa: 'ハ', pi: 'ヒ', pu: 'フ', pe: 'ヘ', po: 'ホ', 52 | ba: 'バ', bi: 'ビ', bu: 'ブ', be: 'ベ', bo: 'ボ', 53 | ma: 'マ', mi: 'ミ', mu: 'ム', me: 'メ', mo: 'モ', 54 | ya: 'ヤ', yu: 'ユ', yo: 'ヨ', 55 | ra: 'ラ', ri: 'リ', ru: 'ル', re: 'レ', ro: 'ロ', 56 | wa: 'ワ', wi: 'ヰ', we: 'ヱ', wo: 'ヲ', 57 | }; 58 | 59 | const 拗音表 = { 60 | wya: 'ヰャ', wyo: 'ヰョ', 61 | ya: 'ャ', yu: 'ュ', yo: 'ョ', 62 | wa: 'ヮ', wi: '𛅤', we: '𛅥', wo: '𛅦', 63 | }; 64 | 65 | const 韻尾表 = { 66 | '': '', 'i': 'イ', 'u': 'ウ', 67 | 'm': 'ム', 'n': 'ン', 'ng': 'ゥ', // ng: 'ィ', 68 | 'p': 'フ', 't': 'ツ', 'k': 'ク', // k: 'キ', 69 | }; 70 | 71 | function roma2kata(s) { 72 | const r = /^([kgsztdnpbmyrw]?w??[yw]?)([aiueo])([ptkmngiu]*)$/g; // 將音節分為韻頭、主要元音及韻尾 73 | const match = r.exec(s); 74 | if (match === null) { 75 | throw new Error('無法轉換為假名:' + s); 76 | } 77 | const { 1: 韻頭, 2: 主要元音, 3: 韻尾 } = match; 78 | let 假名韻尾 = 韻尾表[韻尾]; 79 | if (主要元音 === 'e') { 80 | if (韻尾 === 'k') 假名韻尾 = 'キ'; 81 | if (韻尾 === 'ng') 假名韻尾 = 'ィ'; 82 | } 83 | if (韻頭.length <= 1) { 84 | return 假名表[韻頭 + 主要元音] + 假名韻尾; 85 | } 86 | const 填充元音 = 韻頭[1] === 'w' ? 'u' : 'i'; // 韻頭[1] 只能為 w 或 y 87 | return 假名表[韻頭[0] + 填充元音] + 拗音表[韻頭.slice(1) + 主要元音] + 假名韻尾; 88 | } 89 | 90 | function kata2hira(s) { 91 | const diff = 'ぁ'.charCodeAt(0) - 'ァ'.charCodeAt(0); 92 | return [...s].map(c => ({ 93 | '𛅤': '𛅐', 94 | '𛅥': '𛅑', 95 | '𛅦': '𛅒', 96 | }[c] ?? String.fromCharCode(c.charCodeAt(0) + diff))).join(''); 97 | } 98 | 99 | function small2large(s) { 100 | return [...s].map(c => ({ 101 | '𛅤': 'ヰ', 102 | '𛅥': 'ヱ', 103 | '𛅦': 'ヲ', 104 | }[c] ?? c)).join(''); 105 | } 106 | 107 | // 3. 推導規則 108 | 109 | function 聲母規則() { 110 | return when([ 111 | // 脣音 112 | ['幫滂並母', 'p'], 113 | ['明母', [ 114 | ['梗攝 非 (庚耕青韻 入聲)', 'm'], 115 | ['', 'b'], 116 | ]], 117 | 118 | // 舌音、半舌音 119 | ['端透定知徹澄母', 't'], 120 | ['泥孃母', [ 121 | ['梗攝', 'n'], 122 | ['', 'd'], 123 | ]], 124 | ['來母', 'r'], 125 | 126 | // 齒音、半齒音 127 | ['精莊章組', 's'], 128 | ['日母', 'z'], 129 | 130 | // 牙音、喉音 131 | ['見溪羣曉匣母', 'k'], 132 | ['疑母', 'g'], 133 | ['影云以母', ''] 134 | 135 | ], '無聲母規則'); 136 | } 137 | 138 | function 韻母規則() { 139 | return when([ 140 | ['通攝', [ 141 | ['一等', 'ong'], 142 | ['東韻', [ 143 | ['脣音', [['幫滂並母 入聲', 'uk'], ['', 'ong']]], 144 | ['精莊章組', 'yung'], 145 | ['', [ 146 | ['舒聲', 'yung'], 147 | ['入聲 影母', 'wik'], 148 | ['入聲', 'ik'] 149 | ]], 150 | ]], 151 | ['鍾韻', [ 152 | ['脣音', 'ong'], 153 | ['', 'yong'], 154 | ]], 155 | ]], 156 | 157 | ['止攝', [ 158 | ['合口', [ 159 | ['舌齒音', 'ui'], 160 | ['牙喉音', 'wi'], 161 | ]], 162 | ['', 'i'], 163 | ]], 164 | 165 | ['遇攝', [ 166 | ['魚韻', [ 167 | ['莊組', 'o'], 168 | ['', 'yo'], 169 | ]], 170 | ['虞韻', [ 171 | ['鈍音 或 莊組 或 來母', 'u'], 172 | ['知組', 'yuu'], 173 | ['', 'yu'], 174 | ]], 175 | ['一等', [ 176 | ['影母', 'wo'], 177 | ['', 'o'], 178 | ]], 179 | ]], 180 | 181 | ['蟹攝', [ 182 | ['三四等', [ 183 | ['廢韻 平上聲 章組', 'ai'], 184 | ['廢韻 合口', 'wai'], 185 | ['合口', 'wei'], 186 | ['', 'ei'], 187 | ]], 188 | ['一二等', [ 189 | ['合口', 'wai'], 190 | ['', 'ai'], 191 | ]], 192 | ]], 193 | 194 | ['臻攝', [ 195 | ['三四等', [ 196 | ['文韻', 'un'], 197 | ['合口', [ 198 | ['莊組', [['舒聲', 'on'], ['', 'it']]], 199 | ['舌齒音 非 來母', [['舒聲', 'yun'], ['', 'ot']]], 200 | ['云母', 'win'], 201 | ['', 'in'], 202 | ]], 203 | ['', 'in'], 204 | ]], 205 | ['一等', 'on'], 206 | ]], 207 | 208 | ['山攝', [ 209 | ['一二等 或 脣音 C類', [ 210 | ['合口', 'wan'], 211 | ['', 'an'], 212 | ]], 213 | ['三四等', [ 214 | ['合口', 'wen'], 215 | ['', 'en'], 216 | ]], 217 | ]], 218 | 219 | ['效攝', [ 220 | ['三四等', 'eu'], 221 | ['二等', 'au'], 222 | ['一等', [ 223 | ['脣音', 'ou'], 224 | ['', 'au'], 225 | ]], 226 | ]], 227 | 228 | ['果假攝', [ 229 | ['一二等 或 脣音 C類', [ 230 | ['合口', 'wa'], 231 | ['', 'a'], 232 | ]], 233 | ['三四等', [ 234 | ['合口', 'wa'], 235 | ['', 'ya'], 236 | ]], 237 | ]], 238 | 239 | ['宕江攝', [ 240 | ['三四等', [ 241 | ['合口', [ 242 | ['舌齒音', 'ang'], 243 | ['影云母', 'wang'], 244 | ['牙喉音', 'wyang'], 245 | ]], 246 | ['幫莊組', 'ang'], 247 | ['', 'yang'], 248 | ]], 249 | ['一二等', [ 250 | ['合口', 'wang'], 251 | ['', 'ang'], 252 | ]], 253 | ]], 254 | 255 | ['梗攝', [ 256 | ['三四等', [ 257 | ['合口', 'weng'], 258 | ['', 'eng'], 259 | ]], 260 | ['二等', [ 261 | ['合口', 'wang'], 262 | ['', 'ang'], 263 | ]], 264 | ]], 265 | 266 | ['曾攝', [ 267 | ['三四等', [ 268 | ['合口', [ 269 | ['牙音 或 曉匣母', 'wyong'], 270 | ['', 'yong'], 271 | ]], 272 | ['莊組', 'ong'], 273 | ['', 'yong'], 274 | ]], 275 | ['一等', 'ong'], 276 | ]], 277 | 278 | ['流攝', [ 279 | ['三四等', [ 280 | ['脣音 AB類', 'iu'], 281 | ['明母', 'ou'], 282 | ['幫莊組', 'uu'], 283 | ['', 'iu'], 284 | ]], 285 | ['一等', 'ou'], 286 | ]], 287 | 288 | ['深攝', 'im'], 289 | 290 | ['咸攝', [ 291 | ['一二等 或 脣音 C類', 'am'], 292 | ['三四等', 'em'], 293 | ]], 294 | ], '無韻母規則'); 295 | } 296 | 297 | let 聲母 = 聲母規則(); 298 | let 韻母 = 韻母規則(); 299 | 300 | if (is`入聲`) { 301 | if (韻母.endsWith('m')) 韻母 = 韻母.slice(0, -1) + 'p'; 302 | else if (韻母.endsWith('n')) 韻母 = 韻母.slice(0, -1) + 't'; 303 | else if (韻母.endsWith('ng')) 韻母 = 韻母.slice(0, -2) + 'k'; 304 | } 305 | 306 | function 聲調規則() { 307 | if (['四聲', '四聲(數字)', '四聲(調值)'].includes(選項.聲調)) { 308 | return { 309 | '平': ['꜀', '1', '11'], 310 | '上': ['꜂', '2', '55'], 311 | '去': ['꜄', '3', '15'], 312 | '入': ['꜆', '4', '1'], 313 | }[音韻地位.聲][['四聲', '四聲(數字)', '四聲(調值)'].indexOf(選項.聲調)]; 314 | } 315 | 316 | if (選項.聲調 === '六聲(調值)') { 317 | return when([ 318 | ['平聲 全濁', '11'], 319 | ['平聲', '51'], 320 | ['上去聲 全濁', '15'], 321 | ['上去聲', '55'], 322 | ['入聲 全濁', '1'], 323 | ['入聲', '5'], 324 | ], '無聲調規則'); 325 | } 326 | 327 | if (選項.聲調 === '六聲(符號)') { 328 | if (is`入聲 全濁`) { 329 | if (韻母.endsWith('p')) 韻母 = 韻母.slice(0, -1) + 'b'; 330 | else if (韻母.endsWith('t')) 韻母 = 韻母.slice(0, -1) + 'd'; 331 | else if (韻母.endsWith('k')) 韻母 = 韻母.slice(0, -1) + 'g'; 332 | return ''; 333 | } 334 | return when([ 335 | ['平聲 全濁', 'z'], 336 | ['平聲', ''], 337 | ['上去聲 全濁', 'h'], 338 | ['上去聲', 'x'], 339 | ['入聲', ''], 340 | ], '無聲調規則'); 341 | } 342 | 343 | if (['八聲', '八聲(數字)', '八聲(調值)'].includes(選項.聲調)) { 344 | return [ 345 | { // 陰 346 | '平': ['꜀', '1', '51'], 347 | '上': ['꜂', '2', '55'], 348 | '去': ['꜄', '3', '535'], 349 | '入': ['꜆', '4', '5'], 350 | }, 351 | { // 陽 352 | '平': ['꜁', '5', '11'], 353 | '上': ['꜃', '6', '15'], 354 | '去': ['꜅', '7', '315'], 355 | '入': ['꜇', '8', '1'], 356 | } 357 | ][+is`全濁`][音韻地位.聲][['八聲', '八聲(數字)', '八聲(調值)'].indexOf(選項.聲調)]; 358 | } 359 | 360 | return ''; 361 | } 362 | 363 | let 聲調 = 聲調規則(); 364 | 365 | if (韻母.startsWith('w') && is`非 牙喉音 或 A類 或 以母`) 韻母 = 韻母.slice(1); 366 | 367 | // 4. 音變規則 368 | 369 | if (選項.音變 === '現代日語') { 370 | if (韻母.startsWith('w')) 韻母 = 韻母.slice(1); // 園 wen -> en 371 | 372 | if (韻母.endsWith('p')) 韻母 = 韻母.slice(0, -1) + 'u'; // 鄴 gep -> geu 373 | else if (韻母.endsWith('m')) 韻母 = 韻母.slice(0, -1) + 'n'; // 南 dam -> dan 374 | else if (韻母.endsWith('eng')) 韻母 = 韻母.slice(0, -2) + 'i'; // 生 seng -> sei 375 | else if (韻母.endsWith('ng')) 韻母 = 韻母.slice(0, -2) + 'u'; // 相 syang -> syau 376 | 377 | if (韻母.endsWith('au')) 韻母 = 韻母.slice(0, -2) + 'ou'; // 高 kau -> kou 378 | else if (韻母.endsWith('iu')) 韻母 = 韻母.slice(0, -2) + 'yuu'; // 宙 tiu -> tyuu 379 | else if (韻母.endsWith('eu')) 韻母 = 韻母.slice(0, -2) + 'you'; // 遙 eu -> you 380 | 381 | if (聲母 === 'd' && /^[iy]/.test(韻母)) 聲母 = 'z'; // 膩 di -> zi, 紐 dyuu -> zyuu 382 | } 383 | 384 | let 聲韻; 385 | 386 | if (['平假名', '片假名'].includes(選項.書寫系統)) { 387 | 聲韻 = roma2kata(聲母 + 韻母); 388 | if (!選項.ヰヱヲ小假名) 聲韻 = small2large(聲韻); 389 | if (選項.書寫系統 === '平假名') 聲韻 = kata2hira(聲韻); 390 | } else { 391 | if (選項.音變 === '現代日語') { 392 | if (聲母 === 'p') 聲母 = 'h'; // 甫 pu -> hu 393 | 394 | if (韻母.endsWith('t')) 韻母 += 'u'; // 遏 at -> atu 395 | else if (韻母.endsWith('ek')) 韻母 += 'i'; // 席 sek -> seki 396 | else if (韻母.endsWith('k')) 韻母 += 'u'; // 澤 tak -> taku 397 | } 398 | 399 | if (選項.書寫系統 === '平文式羅馬字') { 400 | if (選項.音變 === '現代日語') { 401 | if (聲母 === 's' && 韻母.startsWith('i')) 聲母 = 'sh'; // 四 si -> shi 402 | else if (聲母 === 'z' && 韻母.startsWith('i')) 聲母 = 'j'; // 人 zin -> jin 403 | else if (聲母 === 't' && 韻母.startsWith('i')) 聲母 = 'ch'; // 地 ti -> chi 404 | else if (聲母 === 't' && 韻母.startsWith('u')) 聲母 = 'ts'; // 追 tui -> tsui 405 | else if (聲母 === 'h' && 韻母.startsWith('u')) 聲母 = 'f'; // 甫 hu -> fu 406 | else if (聲母 === 's' && 韻母.startsWith('y')) { 聲母 = 'sh'; 韻母 = 韻母.slice(1); } // 小 syou -> shou 407 | else if (聲母 === 'z' && 韻母.startsWith('y')) { 聲母 = 'j'; 韻母 = 韻母.slice(1); } // 繞 zyou -> jou 408 | else if (聲母 === 't' && 韻母.startsWith('y')) { 聲母 = 'ch'; 韻母 = 韻母.slice(1); } // 兆 tyou -> chou 409 | 410 | if (韻母.endsWith('tu')) 韻母 = 韻母.slice(0, -1) + 'su'; // 遏 atu -> atsu 411 | } 412 | } 413 | 414 | 聲韻 = 聲母 + 韻母; 415 | } 416 | 417 | return [...'꜀꜁꜂꜃'].includes(聲調) ? 聲調 + 聲韻 : 聲韻 + 聲調; 418 | -------------------------------------------------------------------------------- /baxter.js: -------------------------------------------------------------------------------- 1 | /* 白一平轉寫 2 | * 3 | * - Baxter, W. H. (1992). A Handbook of Old Chinese Phonology. De Gruyter Mouton. 4 | * - Baxter, W. H., & Sagart, L. (2014). Old Chinese: A New Reconstruction. Oxford University Press. 5 | * 6 | * @author Ayaka 7 | */ 8 | 9 | /** @type { 音韻地位['屬於'] } */ 10 | const is = (...x) => 音韻地位.屬於(...x); 11 | /** @type { 音韻地位['判斷'] } */ 12 | const when = (...x) => 音韻地位.判斷(...x); 13 | 14 | if (!音韻地位) return [ 15 | // 版本可選 '1992' 或 '2014',預設值為 '2014' 16 | ['版本', [2, '1992', '2014']], 17 | ]; 18 | 19 | let 聲母 = { 20 | 幫: 'p', 滂: 'ph', 並: 'b', 明: 'm', 21 | 端: 't', 透: 'th', 定: 'd', 泥: 'n', 來: 'l', 22 | 知: 'tr', 徹: 'trh', 澄: 'dr', 孃: 'nr', 23 | 精: 'ts', 清: 'tsh', 從: 'dz', 心: 's', 邪: 'z', 24 | 莊: 'tsr', 初: 'tsrh', 崇: 'dzr', 生: 'sr', 俟: 'zr', 25 | 章: 'tsy', 昌: 'tsyh', 常: 'dzy', 日: 'ny', 書: 'sy', 船: 'zy', 以: 'y', 26 | 見: 'k', 溪: 'kh', 羣: 'g', 疑: 'ng', 27 | 影: "'", 曉: 'x', 匣: 'h', 云: 'h', 28 | }[音韻地位.母]; 29 | 30 | if (選項.版本 === '1992' && 聲母 === "'") { 31 | 聲母 = 'ʔ'; 32 | } 33 | 34 | let 韻母 = { 35 | // 一等韻 36 | 東: 'uwng', 37 | 冬: 'owng', 38 | 模: 'u', 39 | 泰: 'aj', 40 | 灰: 'oj', 41 | 咍: 'oj', 42 | 魂: 'on', 43 | 痕: 'on', 44 | 寒: 'an', 45 | 豪: 'aw', 46 | 歌: 'a', 47 | 唐: 'ang', 48 | 登: 'ong', 49 | 侯: 'uw', 50 | 覃: 'om', 51 | 談: 'am', 52 | 53 | // 二等韻 54 | 江: 'aewng', 55 | 佳: 'ea', 56 | 皆: 'eaj', 57 | 夬: 'aej', 58 | 刪: 'aen', 59 | 山: 'ean', 60 | 肴: 'aew', 61 | 麻: 'ae', 62 | 庚: 'aeng', 63 | 耕: 'eang', 64 | 咸: 'eam', 65 | 銜: 'aem', 66 | 67 | // 四等韻 68 | 齊: 'ej', 69 | 先: 'en', 70 | 蕭: 'ew', 71 | 青: 'eng', 72 | 添: 'em', 73 | 74 | // 三等陰聲韻 75 | 支: 'je', 76 | 脂: 'ij', 77 | 之: 'i', 78 | 微: 'j+j', 79 | 魚: 'jo', 80 | 虞: 'ju', 81 | 祭: 'jej', 82 | 廢: 'joj', 83 | 宵: 'jew', 84 | // 歌: 'ja', 85 | // 麻: 'jae', 86 | 尤: 'juw', 87 | 幽: 'jiw', 88 | 89 | // 三等陽聲韻 90 | // 東: 'juwng', 91 | 鍾: 'jowng', 92 | 真: 'in', 93 | 臻: 'in', 94 | 文: 'jun', 95 | 殷: 'j+n', 96 | 元: 'jon', 97 | 仙: 'jen', 98 | 陽: 'jang', 99 | // 庚: 'jaeng', 100 | 清: 'jeng', 101 | 蒸: 'ing', 102 | 侵: 'im', 103 | 鹽: 'jem', 104 | 嚴: 'jaem', 105 | 凡: 'jom', 106 | }[音韻地位.韻]; 107 | 108 | // 東歌麻庚韻同時含三等與非三等,上文僅處理非三等,此處處理三等 109 | // 「四等」是考慮端組在內 110 | if (is`東歌麻庚韻 三四等`) { 111 | 韻母 = 'j' + 韻母; 112 | } 113 | 114 | if (選項.版本 === '1992') { 115 | if (韻母 === 'ea') 韻母 = 'ɛɨ'; 116 | 韻母 = 韻母.replace('+', 'ɨ').replace('ae', 'æ').replace('ea', 'ɛ'); 117 | } 118 | 119 | // 章組或日以母只與三等韻相拼,省去韻母起始的 j 120 | if (is`章組 或 日以母` && 韻母.startsWith('j')) { 121 | 韻母 = 韻母.slice(1); 122 | } 123 | 124 | // 重紐 A 類添加 j 或 i 125 | if (is`A類 非 麻幽陽韻`) { 126 | if (韻母.startsWith('j')) 韻母 = 'ji' + 韻母.slice(1); 127 | else 韻母 = 'j' + 韻母; 128 | } 129 | 130 | // 合口字添加 w 131 | if (is`(合口 或 灰魂韻) 非 虞文凡韻`) { 132 | if (韻母.startsWith('j')) 韻母 = 'jw' + 韻母.slice(1); 133 | else 韻母 = 'w' + 韻母; 134 | } 135 | 136 | if (is`入聲`) { 137 | if (韻母.endsWith('m')) 韻母 = 韻母.slice(0, -1) + 'p'; 138 | else if (韻母.endsWith('n')) 韻母 = 韻母.slice(0, -1) + 't'; 139 | else if (韻母.endsWith('ng')) 韻母 = 韻母.slice(0, -2) + 'k'; 140 | } 141 | 142 | const 聲調 = { 143 | 上: 'X', 144 | 去: 'H', 145 | }[音韻地位.聲] || ''; 146 | 147 | return 聲母 + 韻母 + 聲調; 148 | -------------------------------------------------------------------------------- /build/main.js: -------------------------------------------------------------------------------- 1 | import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; 2 | 3 | mkdirSync('./dist', { recursive: true }); 4 | 5 | writeFileSync('./dist/index.js', /* js */ `\ 6 | import TshetUinh from 'tshet-uinh'; 7 | import { 推導方案 } from 'tshet-uinh-deriver-tools'; 8 | 9 | export function from字頭(schema, 字頭, 選項) { 10 | return TshetUinh.資料.query字頭(字頭).map(條目 => { 11 | if (Array.isArray(schema)) (條目.推導結果 = schema.map((schema, i) => _schemata[schema](Array.isArray(選項) && 選項[i] || 選項?.[schema])(條目.音韻地位, 條目.字頭))).forEach((推導結果, i) => 條目.推導結果[schema[i]] = 推導結果); 12 | else 條目.推導結果 = _schemata[schema](選項)(條目.音韻地位, 條目.字頭); 13 | return 條目; 14 | }); 15 | } 16 | `); 17 | 18 | writeFileSync('./dist/index.d.ts', /* ts */`\ 19 | import TshetUinh from 'tshet-uinh'; 20 | import { 推導方案 } from 'tshet-uinh-deriver-tools'; 21 | 22 | type 推導選項 = Readonly> | undefined; 23 | interface 字頭檢索及推導結果 extends TshetUinh.資料.檢索結果 { 24 | 推導結果: T extends readonly Schema[] ? { -readonly [K in keyof T]: string } & { -readonly [K in T[number]]: string } : string; 25 | } 26 | 27 | /** 28 | * 查詢字頭的擬音。 29 | * @param schema - 推導方案或推導方案陣列 30 | * @param 字頭 - 要查詢的字頭 31 | * @param 選項 - 選項(可選) 32 | * - 若 \`schema\` 為字串(單個方案),則該引數為其選項 33 | * - 若 \`schema\` 為字串列表(多個方案),則該引數亦為列表,元素為相應方案的選項 34 | * @return 由字頭檢索到的各條目及相應推導結果 35 | * - 若 \`schema\` 為字串,傳回結果中的 \`推導結果\` 屬性為字串 36 | * - 若 \`schema\` 為字串列表,傳回結果中的 \`推導結果\` 屬性亦為字串列表 37 | */ 38 | export function from字頭( 39 | schema: T, 40 | 字頭: string, 41 | 選項?: T extends readonly Schema[] ? { readonly [K in keyof T]?: 推導選項 } | { readonly [K in T[number]]?: 推導選項 } : 推導選項, 42 | ): 字頭檢索及推導結果[]; 43 | `); 44 | 45 | const directoryFiles = new Set(readdirSync('.').filter(file => file.endsWith('.js') && !file.endsWith('.config.js'))); 46 | const nonExistentFiles = new Set(); 47 | 48 | const readmeContent = readFileSync('README.md', 'utf-8'); 49 | const files = readmeContent.matchAll(/`(([a-z0-9_]+)\.js)`/g); 50 | const schemata = []; 51 | 52 | for (const [, file, schema] of files) { 53 | if (!existsSync(file)) { 54 | nonExistentFiles.add(file); 55 | continue; 56 | } 57 | 58 | const content = readFileSync(file, 'utf-8'); 59 | const [, description, code] = content.match(/^\/\*(.+?)\*\/(.+)$/s); 60 | 61 | appendFileSync('./dist/index.js', /* js */ ` 62 | export const ${schema} = new 推導方案(function (選項, 音韻地位, 字頭) { 63 | ${code.trim()} 64 | }); 65 | `); 66 | 67 | appendFileSync('./dist/index.d.ts', /* ts */ ` 68 | /** 69 | * ${description.trim()} 70 | */ 71 | export const ${schema}: 推導方案; 72 | `); 73 | 74 | schemata.push(schema); 75 | directoryFiles.delete(file); 76 | } 77 | 78 | appendFileSync('./dist/index.js', /* js */ ` 79 | const _schemata = { ${schemata.join(', ')} }; 80 | `); 81 | 82 | appendFileSync('./dist/index.d.ts', /* ts */ ` 83 | type Schema = '${schemata.join("' | '")}'; 84 | `); 85 | 86 | if (directoryFiles.size || nonExistentFiles.size) { 87 | if (directoryFiles.size) console.error('The following files are missing from README.md:', directoryFiles); 88 | if (nonExistentFiles.size) console.error('The following files are listed in README.md but do not exist:', nonExistentFiles); 89 | process.exit(1); 90 | } 91 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import stylisticJs from '@stylistic/eslint-plugin-js'; 2 | import taggedIs from './rules/tagged-is.js'; 3 | 4 | export default [ 5 | { 6 | languageOptions: { 7 | sourceType: 'script', 8 | globals: { 9 | TshetUinh: 'readonly', 10 | 音韻地位: 'writable', 11 | 字頭: 'writable', 12 | 選項: 'readonly', 13 | require: 'readonly', 14 | }, 15 | parserOptions: { 16 | ecmaFeatures: { 17 | globalReturn: true, 18 | impliedStrict: true, 19 | }, 20 | }, 21 | }, 22 | plugins: { 23 | '@stylistic/js': stylisticJs, 24 | 'tagged-is': { 25 | rules: { 26 | 'tagged-is': taggedIs, 27 | }, 28 | }, 29 | }, 30 | rules: { 31 | 'no-constant-binary-expression': 'error', 32 | 'no-debugger': 'error', 33 | 'no-dupe-class-members': 'error', 34 | 'no-dupe-else-if': 'error', 35 | 'no-dupe-keys': 'error', 36 | 'no-duplicate-case': 'error', 37 | 'no-self-assign': 'error', 38 | 'no-self-compare': 'error', 39 | 'no-setter-return': 'error', 40 | 'no-undef': 'error', 41 | 'no-unexpected-multiline': 'error', 42 | 'no-unreachable': 'error', 43 | 'no-unreachable-loop': 'error', 44 | 'no-unused-private-class-members': 'error', 45 | 'no-unused-vars': ['error', { varsIgnorePattern: '^(is|when)$' }], 46 | 'no-useless-assignment': 'error', 47 | 'no-useless-backreference': 'error', 48 | 'default-case-last': 'error', 49 | 'eqeqeq': 'error', 50 | 'grouped-accessor-pairs': 'error', 51 | 'logical-assignment-operators': 'error', 52 | 'no-empty': ['error', { allowEmptyCatch: true }], 53 | 'no-empty-function': 'error', 54 | 'no-empty-static-block': 'error', 55 | 'no-eval': 'error', 56 | 'no-extend-native': 'error', 57 | 'no-extra-bind': 'error', 58 | 'no-extra-boolean-cast': 'error', 59 | 'no-extra-label': 'error', 60 | 'no-global-assign': 'error', 61 | 'no-label-var': 'error', 62 | 'no-new-func': 'error', 63 | 'no-new-wrappers': 'error', 64 | 'no-object-constructor': 'error', 65 | 'no-sequences': 'error', 66 | 'no-shadow': 'error', 67 | 'no-shadow-restricted-names': 'error', 68 | 'no-throw-literal': 'error', 69 | 'no-undefined': 'error', 70 | 'no-unneeded-ternary': 'error', 71 | 'no-unused-expressions': 'error', 72 | 'no-unused-labels': 'error', 73 | 'no-useless-call': 'error', 74 | 'no-useless-catch': 'error', 75 | 'no-useless-computed-key': 'error', 76 | 'no-useless-concat': 'error', 77 | 'no-useless-constructor': 'error', 78 | 'no-useless-escape': 'error', 79 | 'no-useless-return': 'error', 80 | 'no-var': 'error', 81 | 'no-void': 'error', 82 | 'object-shorthand': 'error', 83 | 'operator-assignment': 'error', 84 | 'prefer-arrow-callback': 'error', 85 | 'prefer-exponentiation-operator': 'error', 86 | 'prefer-numeric-literals': 'error', 87 | 'prefer-object-has-own': 'error', 88 | 'prefer-object-spread': 'error', 89 | 'prefer-regex-literals': 'error', 90 | 'prefer-rest-params': 'error', 91 | 'prefer-spread': 'error', 92 | 'strict': ['error', 'never'], 93 | 'symbol-description': 'error', 94 | 95 | '@stylistic/js/array-bracket-spacing': 'error', 96 | '@stylistic/js/arrow-parens': ['error', 'as-needed'], 97 | '@stylistic/js/arrow-spacing': 'error', 98 | '@stylistic/js/block-spacing': 'error', 99 | '@stylistic/js/comma-spacing': 'error', 100 | '@stylistic/js/comma-style': 'error', 101 | '@stylistic/js/computed-property-spacing': 'error', 102 | '@stylistic/js/dot-location': ['error', 'property'], 103 | '@stylistic/js/eol-last': 'error', 104 | '@stylistic/js/function-call-spacing': 'error', 105 | '@stylistic/js/generator-star-spacing': ['error', 'after'], 106 | '@stylistic/js/indent': ['error', 2, { SwitchCase: 1, ArrayExpression: 'off', ObjectExpression: 'off' }], 107 | '@stylistic/js/key-spacing': 'error', 108 | '@stylistic/js/keyword-spacing': 'error', 109 | '@stylistic/js/new-parens': 'error', 110 | '@stylistic/js/no-extra-semi': 'error', 111 | '@stylistic/js/no-floating-decimal': 'error', 112 | '@stylistic/js/no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }], 113 | '@stylistic/js/no-tabs': 'error', 114 | '@stylistic/js/no-trailing-spaces': 'error', 115 | '@stylistic/js/no-whitespace-before-property': 'error', 116 | '@stylistic/js/object-curly-spacing': ['error', 'always'], 117 | '@stylistic/js/operator-linebreak': ['error', 'after'], 118 | '@stylistic/js/padded-blocks': ['error', 'never'], 119 | '@stylistic/js/quote-props': ['error', 'consistent'], 120 | '@stylistic/js/quotes': ['error', 'single', { avoidEscape: true }], 121 | '@stylistic/js/rest-spread-spacing': 'error', 122 | '@stylistic/js/semi': 'error', 123 | '@stylistic/js/semi-spacing': 'error', 124 | '@stylistic/js/semi-style': 'error', 125 | '@stylistic/js/space-before-blocks': 'error', 126 | '@stylistic/js/space-before-function-paren': ['error', { anonymous: 'always', named: 'never', asyncArrow: 'always' }], 127 | '@stylistic/js/space-in-parens': 'error', 128 | '@stylistic/js/space-infix-ops': 'error', 129 | '@stylistic/js/space-unary-ops': 'error', 130 | '@stylistic/js/spaced-comment': 'error', 131 | '@stylistic/js/switch-colon-spacing': 'error', 132 | '@stylistic/js/template-curly-spacing': 'error', 133 | '@stylistic/js/template-tag-spacing': 'error', 134 | '@stylistic/js/yield-star-spacing': 'error', 135 | 136 | 'tagged-is/tagged-is': 'error', 137 | }, 138 | }, 139 | { 140 | files: ['eslint.config.js', '*/**/*.js'], 141 | languageOptions: { 142 | sourceType: 'module', 143 | globals: { 144 | TshetUinh: 'off', 145 | 音韻地位: 'off', 146 | 字頭: 'off', 147 | 選項: 'off', 148 | require: 'off', 149 | console: 'readonly', 150 | process: 'readonly', 151 | }, 152 | parserOptions: { 153 | ecmaFeatures: {}, 154 | }, 155 | }, 156 | }, 157 | ]; 158 | -------------------------------------------------------------------------------- /gwongzau.js: -------------------------------------------------------------------------------- 1 | /* 推導廣州話 2 | * 3 | * https://ayaka.shn.hk/teoi/ 4 | * 5 | * @author Ayaka 6 | */ 7 | 8 | /** @type { 音韻地位['屬於'] } */ 9 | const is = (...x) => 音韻地位.屬於(...x); 10 | /** @type { 音韻地位['判斷'] } */ 11 | const when = (...x) => 音韻地位.判斷(...x); 12 | 13 | if (!音韻地位) return []; 14 | 15 | if (is`云母 通攝 舒聲`) 音韻地位 = 音韻地位.調整('匣母', ['匣母三等']); 16 | 17 | function 聲母規則() { 18 | return when([ 19 | ['幫滂並母 C類', 'f'], 20 | ['幫母 或 並母 仄聲', 'b'], 21 | ['滂母 或 並母 平聲', 'p'], 22 | ['明母', 'm'], 23 | 24 | ['端母 或 定母 仄聲', 'd'], 25 | ['透母 或 定母 平聲', 't'], 26 | ['泥孃母', 'n'], 27 | ['來母', 'l'], 28 | 29 | ['精莊知章母 或 從崇俟邪澄母 仄聲', 'z'], // 精莊組濁音塞擦音多於擦音(下同) 30 | ['清初徹昌母 或 從崇俟邪澄母 平聲', 'c'], 31 | ['心生常書船母', 's'], // 章組濁音擦音多於塞擦音 32 | 33 | ['見母 或 羣母 仄聲', 'g'], 34 | ['羣母 平聲', 'k'], 35 | ['疑母', 'ng'], // 細音為 j,詳後 36 | 37 | ['溪曉母', 'h'], // 溪母多數擦化;三四等拼 a 元音部分韻母時為 j/w,詳後 38 | ['匣母', [ 39 | ['合口 或 (遇攝 一等)', 'j'], // 拼展脣或後元音時為 w,詳後 40 | ['', 'h'], 41 | ]], 42 | ['影云以日母', [ 43 | ['三四等', 'j'], // 拼展脣或後元音時為 w,詳後 44 | ['', ''], 45 | ]], 46 | ], '無聲母規則'); 47 | } 48 | 49 | function 韻母規則() { 50 | return when([ 51 | ['通攝', 'ung'], 52 | 53 | ['止攝', [ 54 | ['脣音', 'ei'], 55 | ['開口 (端組 或 孃來母 或 見溪羣曉母)', 'ei'], 56 | ['開口', 'i'], 57 | ['合口 舌齒音', 'eoi'], 58 | ['合口 牙喉音', 'ai'], 59 | ]], 60 | 61 | ['遇攝', [ 62 | ['三四等', [ 63 | ['幫滂並母', 'u'], 64 | ['明母', 'ou'], 65 | ['莊組', 'o'], 66 | ['端精組 或 孃來母 或 見溪羣曉母', 'eoi'], 67 | ['', 'yu'], 68 | ]], 69 | ['一等', [ 70 | ['脣音 或 舌齒音', 'ou'], 71 | ['疑母', ''], 72 | ['牙喉音', 'u'], 73 | ]], 74 | ]], 75 | 76 | ['蟹攝', [ 77 | ['廢韻 平上聲 章組', 'oi'], // 參照「茝」coi2 78 | ['合口 銳音', 'eoi'], // 含以母 79 | ['三四等', 'ai'], 80 | ['二等 或 泰韻 開口 (端組 或 來母)', 'aai'], 81 | ['一等', [ 82 | ['開口 或 疑母', 'oi'], 83 | ['', 'ui'], 84 | ]], 85 | ]], 86 | 87 | ['臻攝', [ 88 | ['三四等', [ 89 | ['合口 舌齒音', 'eon'], 90 | ['', 'an'], 91 | ]], 92 | ['一等', [ 93 | ['脣音', 'un'], 94 | ['合口 (端組 或 來母)', 'eon'], 95 | ['合口 精組', 'yun'], 96 | ['', 'an'], 97 | ]], 98 | ]], 99 | 100 | ['山攝', [ 101 | ['二等 或 脣音 C類', 'aan'], 102 | ['三四等', [ 103 | ['開口 或 脣音', 'in'], 104 | ['合口', 'yun'], 105 | ]], 106 | ['一等', [ 107 | ['開口 舌齒音', 'aan'], 108 | ['開口 牙喉音', 'on'], 109 | ['合口 舌齒音', 'yun'], 110 | ['合口 牙喉音 或 脣音', 'un'], 111 | ]], 112 | ]], 113 | 114 | ['效攝', [ 115 | ['三四等', 'iu'], 116 | ['二等', 'aau'], 117 | ['一等', 'ou'], 118 | ]], 119 | 120 | ['果假攝', [ 121 | ['三四等', [ 122 | ['脣音 C類', 'o'], 123 | ['開口 或 脣音', 'e'], 124 | ['合口', 'oe'], 125 | ]], 126 | ['二等', 'aa'], 127 | ['一等', 'o'], 128 | ]], 129 | 130 | ['宕江攝', [ 131 | ['三四等', [ 132 | ['脣音 C類 或 開口 莊組 或 合口', 'ong'], 133 | ['', 'oeng'], 134 | ]], 135 | ['二等 舌齒音', 'oeng'], 136 | ['一二等', 'ong'], 137 | ]], 138 | 139 | ['梗曾攝', [ 140 | ['一二等 或 梗攝 莊組', [ // 文 ang、白 aang,兩者勢均,推導音依文讀 141 | ['庚韻 二等 (來母 或 端組)', 'aang'], // 唯來母「冷」向無 lang 音例,故例外,端組亦從之 142 | ['', 'ang'], 143 | ]], 144 | ['三四等', 'ing'], 145 | ]], 146 | 147 | ['流攝', [ 148 | ['四等 端組 或 幽韻 幫滂並母', 'iu'], // 「丟」「彪」「淲」 149 | ['', 'au'], 150 | ]], 151 | 152 | ['深攝', 'am'], // -m 拼脣音時為 -n,詳後,下同 153 | 154 | ['咸攝', [ 155 | ['二等 或 脣音 C類', 'aam'], 156 | ['三四等', 'im'], 157 | ['一等 脣舌齒音', 'aam'], // 脣音僅僻字,如「姏」maan4 158 | ['一等 牙喉音', 'om'], // -om 併入 -am,但影響陰入分化,詳後 159 | ]], 160 | ], '無韻母規則'); 161 | } 162 | 163 | function 聲調規則() { 164 | return when([ 165 | ['清音', [ 166 | ['平聲', '1'], 167 | ['上聲', '2'], 168 | ['去入聲', '3'], // 中入於短元音為 1(陰入),詳後 169 | ]], 170 | ['濁音', [ 171 | ['平聲', '4'], 172 | ['上聲 次濁', '5'], 173 | ['上聲 全濁 或 去入聲', '6'], 174 | ]] 175 | ], '無聲調規則'); 176 | } 177 | 178 | function is短元音(韻母) { 179 | if (['am', 'an', 'ang', 'eon', 'ing', 'ung'].includes(韻母)) return true; 180 | if (['aam', 'aan', 'im', 'in', 'om', 'on', 'ong', 'oeng', 'un', 'yun'].includes(韻母)) return false; 181 | throw new Error('無長短元音規則:' + 韻母); 182 | } 183 | 184 | let 聲母 = 聲母規則(); 185 | let 韻母 = 韻母規則(); 186 | let 聲調 = 聲調規則(); 187 | 188 | // ng 拼細音時為 j 189 | const is細音 = ['eo', 'i', 'oe', 'u', 'yu'].some(x => 韻母.startsWith(x)); 190 | if (聲母 === 'ng' && is細音) 聲母 = 'j'; 191 | 192 | // 三四等清調 h 拼 a 元音部分韻母時為 j/w 193 | if (聲母 === 'h' && ['au', 'an', 'am'].includes(韻母) && is`清音 三四等 非 (臻攝 開口 入聲) 非 (臻攝 合口 舒聲)`) { 194 | 聲母 = 'j'; 195 | } 196 | 197 | // 陰入分化 198 | if (is`入聲` && 聲調 === '3' && is短元音(韻母)) 聲調 = '1'; 199 | 200 | // 合口 201 | if (is`合口 或 模韻` && !['eo', 'oe', 'yu'].some(x => 韻母.startsWith(x))) { 202 | if ((聲母 === 'g' || 聲母 === 'k') && !韻母.startsWith('u')) 聲母 += 'w'; 203 | else if (聲母 === 'h' && !韻母.startsWith('i')) 聲母 = 'f'; 204 | else if (聲母 === 'j' || 聲母 === '') 聲母 = 'w'; 205 | } 206 | 207 | // -om 併入 -am 208 | if (韻母 === 'om') 韻母 = 'am'; 209 | 210 | // m 韻尾在聲母為脣音時為 n 211 | if (is`脣音` && 韻母.endsWith('m')) 韻母 = 韻母.slice(0, -1) + 'n'; 212 | 213 | if (is`入聲`) { 214 | if (韻母.endsWith('m')) 韻母 = 韻母.slice(0, -1) + 'p'; 215 | else if (韻母.endsWith('n')) 韻母 = 韻母.slice(0, -1) + 't'; 216 | else if (韻母.endsWith('ng')) 韻母 = 韻母.slice(0, -2) + 'k'; 217 | } 218 | 219 | return 聲母 + 韻母 + 聲調; 220 | -------------------------------------------------------------------------------- /high_tang.js: -------------------------------------------------------------------------------- 1 | /* 推導盛唐(平水韻)擬音 2 | * 3 | * 盛唐通用語沒有直接描述音系的材料,但有詩韻(「平水韻」)和日語漢音作爲緊密相關的材料 4 | * 5 | * 來源:「平水韻」擬音 6 | * https://phesoca.com/aws/351/ 或 7 | * https://www.bilibili.com/read/cv37390491/ 或 8 | * https://zhuanlan.zhihu.com/p/681190661 9 | * 10 | * @author unt 11 | */ 12 | 13 | /** @type { 音韻地位['屬於'] } */ 14 | const is = (...x) => 音韻地位.屬於(...x); 15 | /** @type { 音韻地位['判斷'] } */ 16 | const when = (...x) => 音韻地位.判斷(...x); 17 | 18 | const 非組字典 = { 19 | 'f': { 幫: 'f', 滂: 'fʰ', 並: 'v', 明: 'ɱ' }, 20 | 'pf': { 幫: 'pf', 滂: 'pfʰ', 並: 'bv', 明: 'ɱ' }, 21 | }; 22 | 23 | function get選單(音類列表, 音標列表s, 音類音標連接符 = ' ', 音類連接符 = ' | ') { 24 | return 音標列表s.map(音標列表 => ({ 25 | value: 音標列表.join(','), 26 | text: 音標列表.map((音標, i) => 音類列表[i] + 音類音標連接符 + 音標).join(音類連接符), 27 | })); 28 | } 29 | 30 | if (!音韻地位) return [ 31 | '聲', 32 | ['非組', [1, ...Object.keys(非組字典)]], 33 | [`等類記法 34 | j 代表聲母腭化、ɣ 代表聲母軟腭化,不是介音; 35 | 非組和莊章組後總是不寫等類記號`, [1, ...get選單( 36 | ['三A', '三BC', '一二四'], 37 | [['j', 'ɣ', ''], ['ʲ', 'ˠ', '']], 38 | ' C', 39 | )]], 40 | ['見組非三等簡寫作軟腭音', false], 41 | 42 | '韻', 43 | ['低元音', [2, ...get選單(['前', '後'], [['æ', 'a'], ['æ', 'ɑ'], ['a', 'ɑ']])]], 44 | ['後高元音\n微韻開口總爲 ɨj,不受本選項影響', [1, ...get選單(['閉音節', '魚韻'], [['ɨ', 'ɨ'], ['ə', 'ɨ'], ['ə', 'ɯ']])]], 45 | ['支脂合併', false], 46 | ['咍泰合併', false], 47 | ['覃談合併', true], 48 | ['東一冬合併', false], 49 | ['輕脣東鍾合併', false], 50 | ['部分蟹攝二等入假攝', false], 51 | ['部分流攝脣音入遇攝', false], 52 | 53 | '調', 54 | ['聲調', [2, '五度符號', '附加符號', '調類數字']], 55 | ['全濁上歸去', false], 56 | ]; 57 | 58 | function 調整音韻地位() { 59 | function 調整(表達式, 調整屬性, 字頭串 = null) { 60 | if (typeof (字頭串) === 'string' && !字頭串.includes(字頭)) return; 61 | if (is(表達式)) 音韻地位 = 音韻地位.調整(調整屬性); 62 | } 63 | 64 | // 輕唇化例外 65 | 調整('明母 尤韻', { 等: '一', 類: null, 韻: '侯' }); 66 | 調整('明母 東韻', { 等: '一', 類: null }); 67 | 68 | // [慧琳反切體現的, 唐代用韻體現的, 據今音推測的] 69 | const 蟹攝二等入假攝字 = ['崖咼(呙)扠涯搋派差絓畫(画)罣罷(罢)', '佳鼃娃解釵(钗)卦柴', '哇洼蛙灑蝸話(话)掛挂查叉杈衩'].join(''); 70 | const 流攝脣音入遇攝字 = ['浮戊母罦罘蜉矛茂覆懋拇某負(负)阜', '謀(谋)部畝(亩)畮婦(妇)不否桴富牟缶', '復複(复)副牡'].join(''); 71 | if (選項.部分蟹攝二等入假攝) 調整('蟹攝 二等', { 韻: '麻' }, 蟹攝二等入假攝字); 72 | if (選項.部分流攝脣音入遇攝) 調整('幫組 尤侯韻', { 韻: is`尤韻` ? '虞' : '模' }, 流攝脣音入遇攝字); 73 | } 74 | 75 | 調整音韻地位(); 76 | 77 | function get聲母() { 78 | let 等類記法 = 選項.等類記法.split(','); 79 | let 聲母 = when([ 80 | ['幫組 C類', 非組字典[選項.非組][音韻地位.母]], 81 | [!選項.見組非三等簡寫作軟腭音 && '見溪疑曉匣母 非 三等', { 82 | 見: 'q', 溪: 'qʰ', 疑: 'ɴ', 曉: 'χ', 匣: 'ʁ', 83 | }[音韻地位.母]], 84 | ['', { 85 | 幫: 'p', 滂: 'pʰ', 並: 'b', 明: 'm', 86 | 端: 't', 透: 'tʰ', 定: 'd', 泥: 'n', 來: 'l', 87 | 知: 'ʈ', 徹: 'ʈʰ', 澄: 'ɖ', 孃: 'ɳ', 88 | 精: 'ts', 清: 'tsʰ', 從: 'dz', 心: 's', 邪: 'z', 89 | 莊: 'tʂ', 初: 'tʂʰ', 崇: 'dʐ', 生: 'ʂ', 俟: 'ʐ', 90 | 章: 'tɕ', 昌: 'tɕʰ', 常: 'dʑ', 書: 'ɕ', 船: 'ʑ', 日: 'ɲ', 以: 'j', 91 | 見: 'k', 溪: 'kʰ', 羣: 'ɡ', 疑: 'ŋ', 92 | 影: 'ʔ', 曉: 'x', 匣: 'ɣ', 云: '', 93 | }[音韻地位.母]], 94 | ]) + when([ 95 | ['幫組 C類', ''], 96 | ['莊章組 或 日以母', ''], 97 | ['三等 (A類 或 精組)', 等類記法[0]], 98 | ['三等', 等類記法[1]], 99 | ['', 等類記法[2]], 100 | ]); 101 | 聲母 = 聲母.replace(/ʰ([ʲˠ])/, '$1ʰ'); 102 | 聲母 = 聲母.replace(/^ˠ/, 'ɰ'); 103 | return 聲母; 104 | } 105 | 106 | function get介音() { 107 | return is`合口` ? 'w' : ''; 108 | } 109 | 110 | function get韻基() { 111 | let 韻基 = when([ 112 | [選項.支脂合併 === true && '脂之韻', 'i'], 113 | [選項.咍泰合併 === true && '灰咍韻', 'ɑj'], // 不含廢韻 114 | ['脂之韻', 'ij'], ['微韻', [['開口', 'ɨj'], ['', 'uj']]], 115 | ['齊祭韻', 'ej'], ['灰咍廢韻', 'əj'], 116 | ['佳皆夬韻', 'æj'], ['泰韻', 'ɑj'], 117 | 118 | ['支佳韻', 'i'], ['魚韻', 'ɨ'], ['虞模韻', 'u'], 119 | ['麻韻', 'æ'], ['歌韻', 'ɑ'], 120 | 121 | ['尤侯幽韻', 'ɨw'], 122 | ['蕭宵韻', 'ew'], 123 | ['肴韻', 'æw'], ['豪韻', 'ɑw'], 124 | 125 | ['真臻韻', 'in'], ['殷韻', 'ɨn'], ['文韻', 'un'], 126 | ['先仙韻', 'en'], ['元魂痕韻', 'ən'], 127 | ['刪山韻', 'æn'], ['寒韻', 'ɑn'], 128 | 129 | ['侵韻', 'im'], 130 | ['鹽添韻', 'em'], ['嚴凡韻', 'əm'], [選項.覃談合併 === false && '覃韻', 'əm'], 131 | ['咸銜韻', 'æm'], ['覃談韻', 'ɑm'], 132 | 133 | [選項.東一冬合併 === true && '冬韻', 'ɨwŋ'], 134 | [選項.輕脣東鍾合併 === true && '通攝 幫組 C類', 'uŋ'], 135 | ['蒸登韻', 'ɨŋ'], ['東韻', 'ɨwŋ'], ['冬鍾韻', 'uŋ'], 136 | ['青韻', 'eŋ'], 137 | ['庚耕清韻', 'æŋ'], ['陽唐韻', 'ɑŋ'], ['江韻', 'æwŋ'], 138 | ]); 139 | 韻基 = 韻基.replace(/ɨ(?=$|[^j])/, 選項.後高元音.split(',')[+is`魚韻`]); 140 | 韻基 = 韻基.replace('æ', 選項.低元音.split(',')[0]); 141 | 韻基 = 韻基.replace('ɑ', 選項.低元音.split(',')[1]); 142 | if (is`入聲`) [...'mnŋɴ'].forEach((v, i) => { 韻基 = 韻基.replace(v, 'ptkq'[i]); }); 143 | return 韻基; 144 | } 145 | 146 | function get聲調() { 147 | const is陰 = is`全清 或 次清 或 次濁 上入聲`; 148 | return { 149 | 五度符號: is陰 ? 150 | { 平: '˦˨', 上: '˦˥', 去: '˧˨˦', 入: '˦' } : 151 | { 平: '˨˩', 上: '˨˦', 去: '˨˨˦', 入: '˨' }, 152 | 附加符號: { 平: '̀', 上: '́', 去: '̌', 入: '' }, 153 | 調類數字: { 平: '¹', 上: '²', 去: '³', 入: '⁴' }, 154 | }[選項.聲調][選項.全濁上歸去 && is`全濁 上聲` ? '去' : 音韻地位.聲]; 155 | } 156 | 157 | function get音節() { 158 | const 音節 = { 159 | 聲母: get聲母(), 160 | 介音: get介音(), 161 | 韻基: get韻基(), 162 | 聲調: get聲調(), 163 | }; 164 | if (音節.韻基[0] === 'i') 音節.聲母 = 音節.聲母.replace(/(? 音韻地位.屬於(...x); 40 | /** @type { 音韻地位['判斷'] } */ 41 | const when = (...x) => 音韻地位.判斷(...x); 42 | 43 | const 音標字典 = { 44 | '原書音標': { 45 | ʰ: 'ʼ', ʱ: 'ʼ', 46 | ʔ: 'ꞏ', ɡ: 'g', ŋ: 'ng', 47 | ȶ: 't̑', ȡ: 'd̑', ȵ: 'ń', // 上加弧線是瑞典方言字母表腭化的一種方式,不是揚抑符 48 | ɕ: 'ś', ʑ: 'ź', 49 | ʂ: 'ṣ', ʐ: 'ẓ', 50 | x: 'χ', ɣ: 'γ', 51 | 52 | ă: 'ă', ɑ̆: 'ậ', ĕ: 'ĕ', 53 | ɛ: 'ä', ɔ: 'å', 54 | // 央次低元音原書作“ɒ”形,實際上是 ɐ 的斜體,不是很多人引用成的 ɒ。這個符號來自瑞典方言字母 55 | æ: 'ɛ', ɐ: 選項.央次低元音?.slice(0, 1) || 'ɐ', 56 | ɑ: 'â', 57 | }, 58 | '國際音標(原貌)': { 59 | ʰ: 'ʻ', ʱ: 'ʻ', ʔ: 'ˀ', // ɡ: 'g', 60 | tʂ: 'ʈʂ', dʐ: 'ɖʐ', 61 | tɕ: 'ȶɕ', dʑ: 'ȡʑ', 62 | }, 63 | '國際音標(通用)': { 64 | ʱ: 選項.濁送氣 || 'ʰ', 65 | }, 66 | }; 67 | 68 | if (!音韻地位) return [ 69 | ['音標體系', [3].concat(Object.keys(音標字典))], 70 | ['聲調記號', [3, '不標', '平ˉ 上ˊ 去ˋ', '上꞉ 去˗']], 71 | ['央次低元音', 選項.音標體系?.includes('原書') ? [1, 'ɐ(準確)', 'ɒ(流行但不準確)'] : null], 72 | ['濁送氣', !選項.音標體系 || 選項.音標體系.includes('通用') ? [1, 'ʰ', 'ʱ'] : null], 73 | ]; 74 | 75 | function get聲母() { 76 | let 聲母 = { 77 | 幫: 'p', 滂: 'pʰ', 並: 'bʱ', 明: 'm', 78 | 端: 't', 透: 'tʰ', 定: 'dʱ', 泥: 'n', 來: 'l', 79 | 知: 'ȶ', 徹: 'ȶʰ', 澄: 'ȡʱ', 孃: 'ȵ', 80 | 見: 'k', 溪: 'kʰ', 羣: 'ɡʱ', 疑: 'ŋ', 81 | 影: 'ʔ', 曉: 'x', 匣: 'ɣ', 以: '', 82 | 精: 'ts', 清: 'tsʰ', 從: 'dzʱ', 心: 's', 邪: 'z', 83 | 莊: 'tʂ', 初: 'tʂʰ', 崇: 'dʐʱ', 生: 'ʂ', 俟: 'dʐʱ', 84 | 章: 'tɕ', 昌: 'tɕʰ', 船: 'dʑʱ', 書: 'ɕ', 常: 'ʑ', 日: 'ȵʑ', 云: 'j', 85 | // 注意云以、常船是顛倒的,俟同崇 86 | }[音韻地位.母]; 87 | return 聲母; 88 | } 89 | 90 | function get韻母() { 91 | const 韻 = { 92 | 文: '殷', 魂: '痕', 灰: '咍', 凡: '嚴', 93 | 之: '脂', 夬: '佳', // 這兩對高本漢無法找到區分方法 94 | }[音韻地位.韻] ?? 音韻地位.韻; 95 | const 元音表 = { 96 | // 三等的 ə、o 暫加短音符以便與一等區分,之後移除 97 | i: '脂     ', ï: '      ', u: '虞東', 98 | ĕ: '   真幽 ', ə̆: ' 蒸 殷 侵', ŏ: '魚鍾', e̯i: '微', ə̯̆u: '尤', 99 | e: ' 青齊先蕭添', ə: ' 登 痕  ', o: '模冬', ie̯: '支', ə̯u: '侯', 100 | ɛ: ' 清祭仙宵鹽', ɐ: ' 庚廢元 嚴', ɔ: ' 江', 101 | æ: ' 耕 臻  ', 102 | ă: '  皆山 咸', ɑ̆: '  咍  覃', 103 | a: '麻陽佳刪肴銜', ɑ: '歌唐泰寒豪談', 104 | }; 105 | const 韻尾列表 = is`舒聲` ? ['', ...'ŋinum'] : [...' k t p']; 106 | 107 | let 韻核 = Object.keys(元音表).find(e => 元音表[e].includes(韻)); 108 | let 韻尾 = 韻尾列表[元音表[韻核].indexOf(韻)]; 109 | 韻核 = 韻核.replace('ə̆', 'ə').replace('ŏ', 'o'); 110 | let 介音 = ''; 111 | if (is`止攝 (鈍音 非 云母 或 來母)`) 介音 += 'j'; // 云母已經是 j,無需加 112 | if (is`三等 非 止攝`) 介音 += 'i̯'; 113 | if (is`四等`) 介音 += 'i'; 114 | 介音 += when([ 115 | ['模冬灰文魂韻', 'u'], 116 | ['歌寒韻 非 開口', 'u'], // 戈桓 117 | ['真韻 合口 (A類 或 銳音 非 莊組)', 'u'], // 諄 118 | ['合口 非 虞韻 或 魚鍾凡韻', 'w'], // 虞韻元音已是 u 119 | ['幫組', [ 120 | ['微韻', 'w'], 121 | ['廢元韻 或 庚韻 三等', 'w'], // ɐ 122 | ['耕韻 明母', 'w'], // æ 123 | ['陽夬刪韻', 'w'], // a, 但麻佳韻原書無 w(儘管擬音不分佳夬) 124 | ['皆韻 或 山韻 入聲', 'w'], // ă,但山韻舒聲原書無 w 125 | ['泰韻 或 唐韻 舒聲', 'w'], // ɑ,同歌寒,但唐韻入聲原書無 w 126 | // 個別字合口的情況不計入,如《漢文典》中: 127 | // “邊”歸合口,但同小韻的“編”歸開口 128 | // “憫”歸合口,但同小韻的“緡”歸開口 129 | ]], 130 | ['', ''], 131 | ], '', true); 132 | 133 | return 介音 + 韻核 + 韻尾; 134 | } 135 | 136 | function get聲調() { 137 | if (選項.聲調記號 === '四角標圈') return { 138 | 139 | }; 140 | const 聲調記號字典 = Object.fromEntries(選項.聲調記號.split(' ').map(e => [...e])); 141 | return 聲調記號字典[音韻地位.聲] ?? ''; 142 | } 143 | 144 | let 音節 = get聲母() + get韻母() + get聲調(); 145 | Object.entries(音標字典[選項.音標體系]).forEach(([k, v]) => { 音節 = 音節.replace(k, v); }); 146 | return 音節; 147 | -------------------------------------------------------------------------------- /mid_tang.js: -------------------------------------------------------------------------------- 1 | /* 推導中唐(韻圖)擬音 2 | * 3 | * 中唐通用語以慧琳《一切經音義》反切和韻圖《韻鏡》《七音略》爲代表 4 | * 5 | * 來源:中唐音系韻基和聲類 6 | * https://phesoca.com/linguistics/mid-tang.png 或 7 | * https://t.bilibili.com/1005592182086696967 或 8 | * https://www.zhihu.com/pin/1725177872707268608 9 | * 10 | * @author unt 11 | */ 12 | 13 | /** @type { 音韻地位['屬於'] } */ 14 | const is = (...x) => 音韻地位.屬於(...x); 15 | /** @type { 音韻地位['判斷'] } */ 16 | const when = (...x) => 音韻地位.判斷(...x); 17 | 18 | const 非組字典 = { 19 | 'f': { 幫: 'f', 滂: 'fʰ', 並: 'v', 明: 'ɱ' }, 20 | 'pf': { 幫: 'pf', 滂: 'pfʰ', 並: 'bv', 明: 'ɱ' }, 21 | }; 22 | 23 | function get選單(音類列表, 音標列表s, 音類音標連接符 = ' ', 音類連接符 = ' | ') { 24 | return 音標列表s.map(音標列表 => ({ 25 | value: 音標列表.join(','), 26 | text: 音標列表.map((音標, i) => 音類列表[i] + 音類音標連接符 + 音標).join(音類連接符), 27 | })); 28 | } 29 | 30 | if (!音韻地位) return [ 31 | '聲', 32 | ['非組', [1, ...Object.keys(非組字典)]], 33 | ['常船合併崇俟合併|常船合併、崇俟合併\n常 dʑ 船 ʑ 合併作擦音 ʑ\n崇 dʐ 俟 ʐ 合併作塞擦音 dʐ', true], 34 | [`等類記法 35 | j 代表聲母腭化、ɣ 代表聲母軟腭化,不是介音; 36 | 選擇「三 Cɣ」時,匣母三四等(軟腭音)也寫作 ʁ,以免與 ɣ 衝突; 37 | 非組和莊章組後總是不寫等類記號`, [1, ...get選單( 38 | ['四', '三', '一二'], 39 | [['j', 'ɣ', ''], ['ʲ', 'ˠ', '']], 40 | ' C', 41 | )]], 42 | ['見組一二等簡寫作軟腭音', false, { description: 選項.等類記法?.includes('ˠ') ? null : '匣母總是寫作 ʁ,實際上匣四 ʁj 是腭化軟腭音、匣三 ʁɣ 是軟腭音' }], 43 | 44 | '韻', 45 | ['低元音\n此外,半低元音總爲 ɛ,其前後未定,不一定是前元音', [2, ...get選單(['前', '後'], [['æ', 'a'], ['æ', 'ɑ'], ['a', 'ɑ']])]], 46 | ['後高元音', [1, ...get選單(['閉音節', '魚韻'], [['ɨ', 'ɨ'], ['ə', 'ɨ'], ['ə', 'ɯ']])]], 47 | ['部分蟹攝二等入假攝', true], 48 | ['部分流攝脣音入遇攝', true], 49 | ['幽韻一律歸四等\n韻圖如此,但不符合實際', false], 50 | ['完全莊三化二\n韻圖如此,但不符合實際', false], 51 | 52 | '調', 53 | ['聲調', [2, '五度符號', '附加符號', '調類數字']], 54 | ['全濁上歸去', false], 55 | ]; 56 | 57 | function 調整音韻地位() { 58 | function 調整(表達式, 調整屬性, 字頭串 = null) { 59 | if (typeof (字頭串) === 'string' && !字頭串.includes(字頭)) return; 60 | if (is(表達式)) 音韻地位 = 音韻地位.調整(調整屬性); 61 | } 62 | 63 | // 輕唇化例外 64 | 調整('明母 尤韻', { 等: '一', 類: null, 韻: '侯' }); 65 | 調整('明母 東韻', { 等: '一', 類: null }); 66 | 67 | if (is`云母 通攝 舒聲`) 音韻地位 = 音韻地位.調整('匣母', ['匣母三等']); // 雄熊 68 | 69 | // [慧琳反切體現的, 唐代用韻體現的, 據今音推測的] 70 | const 蟹攝二等入假攝字 = ['崖咼(呙)扠涯搋派差絓畫(画)罣罷(罢)', '佳鼃娃解釵(钗)卦柴', '哇洼蛙灑蝸話(话)掛挂查叉杈衩'].join(''); 71 | const 流攝脣音入遇攝字 = ['浮戊母罦罘蜉矛茂覆懋拇某負(负)阜', '謀(谋)部畝(亩)畮婦(妇)不否桴富牟缶', '復複(复)副牡'].join(''); 72 | if (選項.部分蟹攝二等入假攝 !== false) 調整('蟹攝 二等', { 韻: '麻' }, 蟹攝二等入假攝字); 73 | if (選項.部分流攝脣音入遇攝 !== false) 調整('幫組 尤侯韻', { 韻: is`尤韻` ? '虞' : '模' }, 流攝脣音入遇攝字); 74 | } 75 | 76 | 調整音韻地位(); 77 | 78 | const 韻圖等 = when([ 79 | [!選項.幽韻一律歸四等 && '幽韻 B類', '三'], 80 | [!選項.完全莊三化二 && '莊組 三等', '三'], 81 | ['精組 止攝 開口', '一'], // 實際上無用,因爲 i 前的 j 本身就會被省略 82 | ['', 音韻地位.韻圖等], 83 | ]); 84 | 85 | function get聲母() { 86 | let 等類記法 = 選項.等類記法.split(','); 87 | let 聲母 = when([ 88 | ['幫組 C類', 非組字典[選項.非組][音韻地位.母]], 89 | [選項.常船合併崇俟合併 !== false && '俟母', 'dʐ'], 90 | [選項.常船合併崇俟合併 !== false && '常母', 'ʑ'], 91 | [!選項.見組一二等簡寫作軟腭音 && '見溪疑曉匣母 一二等', { 92 | 見: 'q', 溪: 'qʰ', 疑: 'ɴ', 曉: 'χ', 匣: 'ʁ', 93 | }[音韻地位.母]], 94 | ['', { 95 | 幫: 'p', 滂: 'pʰ', 並: 'b', 明: 'm', 96 | 端: 't', 透: 'tʰ', 定: 'd', 泥: 'n', 來: 'l', 97 | 知: 'ʈ', 徹: 'ʈʰ', 澄: 'ɖ', 孃: 'ɳ', 98 | 精: 'ts', 清: 'tsʰ', 從: 'dz', 心: 's', 邪: 'z', 99 | 莊: 'tʂ', 初: 'tʂʰ', 崇: 'dʐ', 生: 'ʂ', 俟: 'ʐ', 100 | 章: 'tɕ', 昌: 'tɕʰ', 常: 'dʑ', 書: 'ɕ', 船: 'ʑ', 日: 'ɲ', 以: 'j', 101 | 見: 'k', 溪: 'kʰ', 羣: 'ɡ', 疑: 'ŋ', 102 | 影: 'ʔ', 曉: 'x', 匣: 'ʁ', 云: '', 103 | }[音韻地位.母]], 104 | ]) + when([ 105 | ['幫組 C類', ''], 106 | ['莊章組 或 日以母', ''], 107 | [韻圖等 === '四', 等類記法[0]], 108 | [韻圖等 === '三', 等類記法[1]], 109 | ['', 等類記法[2]], 110 | ]); 111 | 聲母 = 聲母.replace(/ʰ([ʲˠ])/, '$1ʰ'); 112 | 聲母 = 聲母.replace(/ʁʲ/, 'ɣʲ'); 113 | 聲母 = 聲母.replace(/ʁˠ/, 'ɣ'); 114 | 聲母 = 聲母.replace(/^ˠ/, 'ɰ'); 115 | return 聲母; 116 | } 117 | 118 | function get介音() { 119 | return is`合口` ? 'w' : ''; 120 | } 121 | 122 | function get韻基() { 123 | let 韻基 = when([ 124 | ['遇攝 非 開口', 'u'], 125 | ['遇攝', 'ɨ'], 126 | ['假攝', 'æ'], ['果攝', 'ɑ'], 127 | 128 | ['止攝', 'i'], ['流攝', 'ɨw'], 129 | ['蟹攝', 'ɛj'], ['效攝', 'ɛw'], 130 | 131 | ['臻攝 (B類 合口 或 C類 非 開口)', 'un'], 132 | ['臻攝', 'in'], ['深攝', 'im'], 133 | ['山攝', 'ɛn'], ['咸攝', 'ɛm'], 134 | 135 | ['通攝 幫組 C類 或 鍾韻', 'uŋ'], // 鍾韻底層形式爲 /wɨwŋ/ 136 | ['曾攝', 'ɨŋ'], ['通攝', 'ɨwŋ'], 137 | ['梗攝', 'ɛŋ'], ['宕攝', 'ɑŋ'], ['江攝', 'æwŋ'], 138 | ]); 139 | if (韻圖等 === '一') 韻基 = 韻基.replace(/i(?=[^ŋ])/, 'ɨ'); 140 | 韻基 = 韻基.replace('ɨ', 選項.後高元音.split(',')[+is`魚韻`]); 141 | if (韻圖等 === '二') 韻基 = 韻基.replace(/ɛ(?=[^ŋ])/, 'æ'); 142 | if (韻圖等 === '一') 韻基 = 韻基.replace(/ɛ(?=[^ŋ])/, 'ɑ'); 143 | 韻基 = 韻基.replace('æ', 選項.低元音.split(',')[0]); 144 | 韻基 = 韻基.replace('ɑ', 選項.低元音.split(',')[1]); 145 | if (is`入聲`) [...'mnŋɴ'].forEach((v, i) => { 韻基 = 韻基.replace(v, 'ptkq'[i]); }); 146 | return 韻基; 147 | } 148 | 149 | function get聲調() { 150 | const is陰 = is`全清 或 次清 或 次濁 上入聲`; 151 | return { 152 | 五度符號: is陰 ? 153 | { 平: '˦˨', 上: '˦˥', 去: '˧˨˦', 入: '˦' } : 154 | { 平: '˨˩', 上: '˨˦', 去: '˨˨˦', 入: '˨' }, 155 | 附加符號: { 平: '̀', 上: '́', 去: '̌', 入: '' }, 156 | 調類數字: { 平: '¹', 上: '²', 去: '³', 入: '⁴' }, 157 | }[選項.聲調][選項.全濁上歸去 && is`全濁 上聲` ? '去' : 音韻地位.聲]; 158 | } 159 | 160 | function get音節() { 161 | const 音節 = { 162 | 聲母: get聲母(), 163 | 介音: get介音(), 164 | 韻基: get韻基(), 165 | 聲調: get聲調(), 166 | }; 167 | if (音節.韻基[0] === 'i') 音節.聲母 = 音節.聲母.replace(/(? 音韻地位.屬於(...x); 30 | /** @type { 音韻地位['判斷'] } */ 31 | const when = (...x) => 音韻地位.判斷(...x); 32 | 33 | const 字母韻後綴 = 選項?.後綴 ?? '₂'; 34 | 35 | if (!音韻地位) return [...[ 36 | ['顯示', [1, 37 | '八思巴字', 38 | '照那斯圖 1987 轉寫 ⭐', 39 | '吉池孝一 2005 轉寫', 40 | 'Coblin 2007 轉寫', 41 | 'Coblin 2007 擬音', 42 | '沈鐘偉 2008/2015 轉寫兼擬音', 43 | // TODO: '濱田武志 2019 擬音', 44 | 'unt 2023 轉寫 ⭐', 45 | 'unt 2023 擬音 ⭐', 46 | ]], 47 | ['ꡠ、ꡦ 的轉寫', [1, 48 | 'ꡠe ꡦė(八思巴字漢語風格)', 49 | 'ꡠė ꡦe(八思巴字蒙古語風格)', 50 | ], { hidden: 選項.顯示 !== '照那斯圖 1987 轉寫 ⭐' }], 51 | [['聲母附加數字|', 52 | '非敷 ꡰ f2 ≠ 奉 ꡤ f1', 53 | '審 ꡮ š2 ≠ 禪 ꡚ š1', 54 | '曉 ꡜ h2 ≠ 匣 ꡯ h1', 55 | '幺 ꡗ y2 ≠ 喻 ꡭ y1', 56 | ].join('\n') 57 | .replace(/1/g, 選項?.聲母附加數字?.split(' ')[0] ?? '1') 58 | .replace(/2/g, 選項?.聲母附加數字?.split(' ')[1] ?? '2'), 59 | [1, 60 | '₁ ₂', 61 | { value: '1 2', text: '1 2(原文)' }, 62 | ], { hidden: 選項.顯示 !== '吉池孝一 2005 轉寫' }], 63 | ['零聲母陽調下加|\n原文零聲母陽調整個音節加下劃線,此處用首個字母下加橫線代替', [2, 64 | { value: '\u0331', text: '◌\u0331 長音符(U+0331)' }, 65 | { value: '\u0332', text: '◌\u0332 橫線(U+0332)' }, 66 | { value: '\u035F', text: '◌\u035F 雙長音符(U+035F)' }, 67 | ], { hidden: 選項.顯示 !== '沈鐘偉 2008/2015 轉寫兼擬音' }], 68 | ], ...(選項?.顯示?.includes(' 擬音') ? [] : [ 69 | '', 70 | ['兩字母韻拼寫相同時加後綴區分', true, { 71 | description: [ 72 | '經韻 ꡦꡞꡃ ≠ 行韻 ꡦꡞꡃ', 73 | '弓韻 ꡦꡟꡃ ≠ 雄韻 ꡦꡟꡃ', 74 | '規韻 ꡦꡟꡠ ≠ 麾韻 ꡦꡟꡠ', 75 | '杴韻 ꡦꡠꡏ ≠ 嫌韻 ꡦꡠꡏ', 76 | ].map(e => 選項.兩字母韻拼寫相同時加後綴區分 === false ? e : e + 字母韻後綴).join('\n'), 77 | }], 78 | ['後綴', [1, '₂', '²', '2', '′'], { hidden: 選項.兩字母韻拼寫相同時加後綴區分 === false }], 79 | ['寶字採用特殊拼寫|寶字採用 ꡎꡖꡡ 拼寫\n「ꡎꡖꡡ 御寳上用此寳字」', false], 80 | ]), ...[ 81 | '音韻地位的選取', 82 | '', ['推導器 ≠ 傳統等韻學 = 原書 時', [2, '依推導器', '依原書']], 83 | '', ['推導器 = 傳統等韻學 ≠ 原書 時', [2, '依推導器', '依原書']], 84 | '', ['保留《蒙古字韻》小韻歸併錯誤', false], 85 | // 不保留《蒙古字韻》小韻拼寫錯誤 86 | ['不字讀重脣音', true], 87 | ]]; 88 | 89 | const use傳統 = 選項['推導器 ≠ 傳統等韻學 = 原書 時'] === '依原書'; 90 | const use創新 = 選項['推導器 = 傳統等韻學 ≠ 原書 時'] === '依原書'; 91 | 92 | function 調整音韻地位() { 93 | function 調整(表達式, 調整屬性, 字頭串 = null, 邊緣地位種類 = []) { 94 | if (typeof (字頭串) === 'string' && !字頭串.includes(字頭)) return; 95 | if (is(表達式)) 音韻地位 = 音韻地位.調整(調整屬性, 邊緣地位種類); 96 | } 97 | 98 | [[true, [ // 《廣韻》特殊小韻的正常調整 99 | ['云母 通曾攝 舒聲 非 開口', { 母: '匣' }, null, ['匣母三等']], // 雄熊 100 | ['云匣母 真韻 開口', { 母: '匣', 類: 'A' }, null, ['匣母三等']], // 礥 101 | ]], 102 | [use傳統, [ 103 | ['從母 真韻 去聲', { 母: '邪' }], // 賮燼藎贐 104 | ['影母 青韻 去聲', { 呼: '開' }], // 鎣瑩瀅 105 | ['清母 歌韻 去聲', { 呼: '合' }, '磋'], // 磋 106 | ['見母 仙韻 A類 開口 入聲', { 類: 'B' }], // 孑 107 | ['並母 灰韻 上聲', { 韻: '咍' }, '倍菩蓓萯䔒培痱傰', ['咍韻脣音']], // 倍 108 | ['滂母 灰韻 上聲', { 韻: '咍' }, null, ['咍韻脣音']], // (仿照倍) 109 | ['昌母 廢韻 平上聲 開口', { 母: '初', 韻: '皆', 等: '二' }], // 茝 110 | ['以母 廢韻 平上聲 開口', { 母: '疑', 韻: '皆', 等: '二' }], // (仿照茝) 111 | ['匣母 先韻 開口 上聲', { 韻: '真', 等: '三', 類: 'A' }, '礥𧥺㘋', ['匣母三等']], // 礥 112 | ['以母 脂韻 上聲 合口', { 韻: '宵', 呼: '開' }, '鷕'], // 鷕。此爲 TshetUinh.js v0.15 新修正地位 113 | ]], 114 | [use創新, [ // 《通考》也如此 115 | ['云母 咸攝 舒聲', { 母: '以', 類: null }], // 炎焱 116 | ['云母 蒸韻 合口 入聲', { 母: '影' }], // 域罭棫緎淢 117 | ['端母 庚韻 二等', { 韻: '麻' }, null, ['端組類隔']], // 打 118 | ['來母 歌韻 去聲', { 呼: '合' }], // 邏 119 | ['曉母 脂韻 A類 合口 去聲', { 韻: '灰', 等: '一', 類: null }, '䁤睢𥍋婎'], // 《通考》睢(《蒙古字韻》無字) 120 | // ['曉母 青韻 合口 入聲', { 韻: '庚', 等: '三', 類: 'B' }], // 《通考》殈(《蒙古字韻》無字) 121 | ['曉母 梗攝 三四等 合口 去聲', { 韻: '庚', 等: '三', 類: 'B' }], // 夐 122 | ['曉母 青韻 開口 入聲', { 呼: '合' }], // 赥䦧𥍠 123 | ['溪母 仙韻 A類 開口 去聲', { 類: 'B' }], // 譴遣 124 | ['曉母 咸攝 三等 入聲', { 韻: '添', 等: '四', 類: null }], // 脅愶㢵嗋熁 125 | ]], 126 | [選項['保留《蒙古字韻》小韻歸併錯誤'], [ 127 | ['曉母 (止攝 或 臻攝 入聲) A類', { 母: '匣' }, null, ['匣母三等']], // 屎欯、隳墮獝 128 | ['溪母 文韻 上聲', { 母: '云' }], // 𦄐 129 | ['曉母 陽韻 合口 入聲', { 母: '並' }], // 戄(ħwjaw 混入 vaw) 130 | ['澄母 仙韻 開口 入聲', { 母: '知' }], // 轍徹撤澈 131 | ['溪母 青韻 開口 上聲', { 呼: '合' }, '綮'], // 《廣韻》未收,《集韻》溪開四青上 132 | ['羣母 之韻 平聲', { 韻: '脂', 類: 'A' }, '蘄'], 133 | ['精母 咍韻 去聲', { 母: '明', 韻: '庚', 呼: null, 等: '二', 聲: '入' }, '載'], 134 | ['匣母 寒韻 合口 去聲', { 母: '端', 呼: '開' }, '漶'], 135 | ['明母 仙韻 B類 上聲', { 母: '滂' }, '葂莬'], // 《廣韻》未收 136 | ['影母 陽韻 合口 入聲', { 韻: '唐', 等: '一', 類: null }, '矱'], // 《廣韻》未收 137 | ['見母 幽韻 A類 上聲', { 韻: '尤', 類: 'C' }, '糺'], // 《廣韻》未收 138 | ['溪母 添韻 去聲', { 韻: '咸', 等: '二' }, '䈴'], // 《廣韻》未收 139 | ]], 140 | [選項.不字讀重脣音, [ // 《古今韻會舉要》也有此音 141 | ['幫母 文韻 入聲', { 韻: '魂', 等: '一', 類: null }, '不'], 142 | ]]].forEach(e => { 143 | if (e[0]) e[1].forEach(args => 調整(...args)); 144 | }); 145 | } 146 | 147 | 調整音韻地位(); 148 | 149 | const 韻圖等 = when([ 150 | ['幫組 C類', '輕'], 151 | 152 | // 切韻一二四等到韻圖不變 153 | ['非 三等', 音韻地位.等], 154 | 155 | // 按韻圖約定,幽韻一律歸四等 156 | ['幽韻', '四'], 157 | 158 | // 切韻三等,聲母是銳音的情況 159 | ['莊組', '二'], 160 | ['知章組 或 來日母', '三'], 161 | ['端精組 或 以母', '四'], 162 | 163 | // 切韻三等,聲母是鈍音的情況 164 | ['A類', '四'], 165 | ['', '三'], 166 | ]); 167 | 168 | // 接下來先推導 unt 擬音,再反推八思巴字 169 | function get聲母() { 170 | const is創新的二四等併入三等 = is`見溪羣曉母 臻攝 舒聲 A類 合口` || 171 | is`曉母 ((曾梗攝 開口 二三四等) 或 (山咸攝 (四等 或 A類)) 或 幽韻) 舒聲` || 172 | is`羣母 山攝 三四等 合口 舒聲`; 173 | let 聲母字典 = {}; 174 | if ((韻圖等 === '四' || 韻圖等 === '二' && !is`合口 或 江韻 舒聲`) && !(use創新 && is創新的二四等併入三等)) 聲母字典 = { 175 | 見: 'c', 溪: 'cʰ', 羣: 'ɟ', 曉: 'ç', 匣: 'ʝ', 176 | 影: 'ʔ', 疑: '', 云: '', 以: '', 177 | }; 178 | else if (韻圖等 === '輕') 聲母字典 = { 179 | 幫: 'f', 滂: 'f', 並: 'v', 明: 'ʋ', 180 | }; 181 | else if (韻圖等 === '三' || use創新 && is創新的二四等併入三等 || is`江韻 舒聲`) 聲母字典 = { 182 | 曉: 'x', 匣: use創新 ? 'ʝ' : 'ɣ', // 匣三(雄小韻)併入四等 183 | }; 184 | if (!(音韻地位.母 in 聲母字典)) 聲母字典 = { 185 | 幫: 'p', 滂: 'pʰ', 並: 'b', 明: 'm', 186 | 端: 't', 透: 'tʰ', 定: 'd', 泥: 'n', 來: 'l', 187 | 188 | 精: 'ts', 清: 'tsʰ', 從: 'dz', 心: 's', 邪: 'z', 189 | 知: 'tʂ', 徹: 'tʂʰ', 澄: 'dʐ', 孃: 'n', // 泥孃合併 190 | 莊: 'tʂ', 初: 'tʂʰ', 崇: 'dʐ', 生: 'ʂ', 俟: 'dʐ', // 俟母併入崇母 191 | 章: 'tʂ', 昌: 'tʂʰ', 船: 'dʐ', 書: 'ʂ', 常: 'ʐ', 日: 'ɻ', // 常船顛倒 192 | 見: 'k', 溪: 'kʰ', 羣: 'ɡ', 曉: 'χ', 匣: 'ʁ', 193 | 影: 'ʡ', 疑: 'ŋ', 云: 'ŋ', 以: 'ŋ', // ŋ 稍後處理 194 | }; 195 | return 聲母字典[音韻地位.母]; 196 | } 197 | 198 | function get韻母() { 199 | const is合口 = when([ 200 | ['效深咸攝 舒聲', '開'], // 流攝唇音歸合口 201 | ['遇通攝 或 痕韻 入聲', '合'], 202 | ['江韻 銳音', '合'], 203 | [use創新 && '明母 曾梗攝 一二等 舒聲', '合'], 204 | ['幫組', [ 205 | [韻圖等 === '三' && '止蟹攝 或 (臻深曾梗攝 入聲)', '合'], 206 | [韻圖等 === '輕' && '臻流果攝', '合'], 207 | ['二三四等', '開'], 208 | ['曾攝 舒聲 或 宕攝', '開'], 209 | [use創新 && '咍泰韻', '開'], 210 | ['', '合'], 211 | ]], 212 | ['', 音韻地位.呼 ?? '開'], 213 | ]) === '合'; 214 | const is三四等 = '三四'.includes(韻圖等) && !is`止攝 精組 開口` || 215 | is`見影組 江梗攝 二等 舒聲 非 合口` || 216 | 韻圖等 === '輕' && is`止蟹攝`; 217 | 218 | // 推導底層形式,僅 3 個元音:ɨ、ʌ、a 219 | let 韻基 = when([ 220 | ['臻深攝 入聲', [ 221 | [韻圖等 === '四' && '見影組 或 以母', 'ɨj'], 222 | [韻圖等 === '三' && '幫組', 'ɨj'], 223 | ['', 'ɨ'], 224 | ]], 225 | 226 | ['止攝 精莊組 開口 或 遇攝 或 通攝 入聲', 'ɨ'], 227 | ['果假攝 或 山咸攝 入聲', [ 228 | [韻圖等 === '輕' && '果攝', 'ʌ'], // 《通考》縛(《蒙古字韻》無字) 229 | [is三四等 || 韻圖等 === '一' && (is合口 || '非 (入聲 非 見影組)'), 'ʌ'], 230 | ['', 'a'], 231 | ]], 232 | 233 | ['通曾梗攝 舒聲', 'ɨŋ'], 234 | ['宕江攝 舒聲', 'aŋ'], 235 | 236 | ['止蟹攝 或 曾梗攝 入聲', [ 237 | [use創新 && 韻圖等 === '二' && '曾攝', 'ɨj'], 238 | [use創新 && 韻圖等 === '二' && '蟹攝 三四等', 'ɨj'], // 㯔毳。另有《廣韻》𠻜小韻 239 | [is三四等 || 韻圖等 === '一' && (is合口 || '入聲'), 'ɨj'], 240 | ['', 'aj'], 241 | ]], 242 | 243 | ['臻攝 舒聲', 'ɨn'], 244 | ['山攝 舒聲', [ 245 | [is三四等 || 韻圖等 === '一' && is合口, 'ʌn'], 246 | ['', 'an'], 247 | ]], 248 | 249 | ['流攝', 'ɨw'], 250 | ['效攝 或 宕江攝 入聲', [ 251 | [use創新 && '宕攝 莊組 三等', 'ʌw'], // 斮 252 | [is三四等, 'ʌw'], 253 | ['', 'aw'], 254 | ]], 255 | 256 | ['深攝 舒聲', 'ɨm'], 257 | ['咸攝 舒聲', [ 258 | [is三四等, 'ʌm'], 259 | ['', 'am'], 260 | ]], 261 | ], '無韻母規則', true); 262 | 263 | // 生成表層形式 264 | const is細音 = is三四等 || 265 | 韻圖等 === '二' && !is合口 && is`見影組 或 以母` || 266 | use創新 && is`莊組 蟹攝 三四等 開口` || // 《通考》殺(《集韻》生開三祭去,對應《廣韻》㡜小韻)(《蒙古字韻》無字) 267 | use創新 && is`宕攝 莊組 三等 入聲`; 268 | 韻基 = { 269 | ɨ: ['i', 'y', 'ɨ', 'u'], 270 | ʌ: ['jɛ', 'ɥɛ', 'ʌ', 'wɔ'], 271 | a: ['ja', 'wa', 'a', 'wa'], // 撮口呼併入合口呼 272 | }[韻基[0]][!is細音 * 2 + (is合口)] + 韻基.slice(1); 273 | if (韻基 === 'ij') 韻基 = 'i'; 274 | if (韻基 === 'y') 韻基 = 'ɥu'; 275 | if (韻基 === 'yj' && !(韻圖等 === '四' && is`見影組 或 以母`)) 韻基 = 'uj'; // 三等併入合口呼 276 | if (韻基 === 'ɥɛn' && 韻圖等 === '三' && is`見影組 或 來母`) 韻基 = 'ɥɔn'; // 條件變體 277 | if (韻基 === 'ɥɛn' && use創新 && is`羣曉母`) return 'ɥɔn'; // 四等併入三等 278 | return 韻基; 279 | } 280 | 281 | function get擬音() { 282 | let 擬音 = get聲母() + get韻母(); 283 | [ 284 | [/ŋ(?=ɥ|y|w|u)/, 'w'], 285 | ['jj', 'j'], ['ww', 'w'], 286 | ].forEach(e => 擬音 = 擬音.replace(...e)); 287 | return 擬音; 288 | } 289 | 290 | function 擬音to八思巴字(擬音) { 291 | if (選項.寶字採用特殊拼寫 && 擬音 === 'paw' && '寶寳宝珤'.includes(字頭)) { // 只考慮推導器的廣韻資料,不增加更多字頭 292 | return 'ꡎꡖꡡ'; 293 | } 294 | const 聲母字典 = { 295 | p: 'ꡎ', pʰ: 'ꡍ', b: 'ꡌ', m: 'ꡏ', f: 'ꡰ', v: 'ꡤ', ʋ: 'ꡓ', 296 | t: 'ꡊ', tʰ: 'ꡉ', d: 'ꡈ', n: 'ꡋ', l: 'ꡙ', // ꡇ 稍後處理 297 | ts: 'ꡒ', tsʰ: 'ꡑ', dz: 'ꡐ', s: 'ꡛ', z: 'ꡕ', 298 | tʂ: 'ꡆ', tʂʰ: 'ꡅ', dʐ: 'ꡄ', ʂ: 'ꡮ', ʐ: 'ꡚ', ɻ: 'ꡔ', 299 | c: 'ꡂꡦ', cʰ: 'ꡁꡦ', ɟ: 'ꡀꡦ', ç: 'ꡜꡦ', ʝ: 'ꡯꡦ', ɣ: 'ꡯꡦ', ʝjaŋ: 'ꡯjaŋ', ɣjaŋ: 'ꡯjaŋ', 300 | k: 'ꡂ', kʰ: 'ꡁ', ɡ: 'ꡀ', x: 'ꡜ', χ: 'ꡜ', ʁ: 'ꡣ', 301 | xwaŋ: 'ꡜɥaŋ', xuj: 'ꡜuj₂', // 怳、麾 302 | ʔ: 'ꡗ', ʔja: 'ꡗa', 303 | j: 'ꡭ', ɥ: 'ꡭɥ', i: 'ꡭi', y: 'ꡭy', jɛ: 'ꡭjɛ', 304 | ʡ: 'ꡖ', 305 | ŋ: 'ꡃ', wɥ: 'ꡝɥ', w: 'ꡝw', wy: 'ꡝy', wu: 'ꡝu', 306 | }; 307 | const 後處理替換列表 = [ 308 | // 【韻母到八思巴字】 309 | // 介音 + 韻核 310 | ['i', 'ꡞ'], ['ɨ', 'ꡜꡞ'], 311 | [/y|ꡦy(?=j)|ɥu/, 'ꡦꡟ'], ['u', 'ꡟ'], 312 | [/ꡦjɛ|(? e 327 | [/(?<=[ꡤ])ꡟꡓ/, 'ꡡꡓ'], // (v)uw -> ow 328 | [/(?<=[ꡣ])ꡧꡃ/, 'ꡡꡃ'], // (ʁ)waŋ -> oŋ(可能代表 o̯aŋ) 329 | 330 | // 照組(含孃母)後調整洪細 331 | [/(?<=[ꡆꡅ ꡮꡚꡔꡇ])ꡦꡟꡃ/, 'ꡟꡃ'], // juŋ -> uŋ(崇母除外) 332 | [/(?<=[ꡆꡅꡄꡮꡚꡔꡇ])ꡃ/, 'ꡜꡃ'], // aŋ -> ħaŋ 333 | [/(?<=[ꡆꡅꡄꡮꡚꡔꡇ])ꡦꡃ/, 'ꡃ'], // jaŋ -> aŋ 334 | [/(?<=[ꡔ])ꡟꡃ/, use創新 ? 'ꡦꡟꡃ' : 'ꡟꡃ'], // (ɻ)uŋ -> juŋ 335 | 336 | // ħ、ɣ、ʁ 後調整洪細 337 | [/(?<=^[ꡜꡯ])ꡞ(?!$)/, 'ꡦꡞ'], // (ħ|ɣ)i(C) -> ji 338 | [/(?<=^[ꡜꡣ])ꡜ/, ''], // (ħ|ʁ)ħ -> 刪除 339 | [/(?<=^[ꡜ])ꡦ(?=ꡋ)/, use創新 ? 'ꡠ' : 'ꡦ'], // (ħ)je(n) -> e 340 | [/(?<=^[ꡜꡯ])ꡦ(?=ꡋ|ꡏ)/, 'ꡦꡠ'], // (ħ|ɣ)je(n|m) -> jee 341 | [/(?<=^[ꡜꡯ])ꡠ(?=ꡋ|ꡏ)/, 'ꡦ'], // (ħ|ɣ)e(n|m) -> je 342 | [/(?<=^[ꡜ])ꡦ(?=ꡏ)/, use創新 ? 'ꡦꡠ' : 'ꡦ'], // (ħ)je(m) -> jee 343 | 344 | // 三四等的不規則分佈 345 | [/(? e 346 | [/(? e 347 | [/(?<=[ꡗ])ꡠ(?!ꡟ|ꡃ|ꡡ)/, 'ꡦ'], // (ʔ͡j)e -> je 348 | [/(?<=[ꡖꡭꡃ])ꡦ(?!ꡟ|ꡃ|ꡡ)/, 'ꡠ'], // (ʔ|j|ŋ)je -> e 349 | [/(?<=[ꡖ])ꡠ(?=$|ꡋ)/, use創新 ? 'ꡦ' : 'ꡠ'], // (ʔ)e(0|n) -> je 350 | [/(?<=[ꡍꡐ])ꡠ(?=ꡓ|ꡏ)/, use創新 ? 'ꡦ' : 'ꡠ'], // (pʰ|dz)e(w|m) -> je 351 | [/(?<=[ꡏꡔ])ꡦ(?=ꡋ)/, use創新 ? 'ꡠ' : 'ꡦ'], // (m|ɻ)je(n) -> e 352 | [/(?<=[ꡙ])ꡦ(?=ꡓ)/, use創新 ? 'ꡠ' : 'ꡦ'], // (l)je(w) -> e 353 | 354 | // 韻母特殊拼寫 355 | [/(?<=[ꡗꡭ])ꡦꡟꡠ/, 'ꡧꡞ'], // (ʔ͡j|j)jue -> wi 356 | [/(?<=[ꡖꡝ])ꡦꡟꡋ/, 'ꡧꡞꡋ'], // (ʔ|ɦ)jun -> win 357 | [/(?<=[ꡖ])ꡧꡦ$/, use創新 ? 'ꡧꡠ' : 'ꡧꡦ'], // (ʔ)jwe -> we。《通考》噦(《蒙古字韻》無字) 358 | [/(?<=[ꡀꡜꡣꡖꡝ])ꡦꡡꡋ/, 'ꡧꡦꡋ'], // (ɡ|ħ|ʁ|ʔ|ɦ)jon -> wjen 359 | [/ꡦꡦꡟꡃ/, 'ꡦꡟꡃ' + (選項.兩字母韻拼寫相同時加後綴區分 ? 字母韻後綴 : '')], 360 | [/(?<=ꡯ)ꡦꡞꡃ/, 'ꡦꡞꡃ' + (選項.兩字母韻拼寫相同時加後綴區分 ? 字母韻後綴 : '')], 361 | [/ꡟꡠ₂/, 'ꡦꡟꡠ' + (選項.兩字母韻拼寫相同時加後綴區分 ? 字母韻後綴 : '')], 362 | [/(?<=ꡯ)ꡦꡠꡏ/, 'ꡦꡠꡏ' + (選項.兩字母韻拼寫相同時加後綴區分 ? 字母韻後綴 : '')], 363 | 364 | // 聲母特殊拼寫 365 | [/ꡗ(?=ꡧꡞ|ꡧꡦ$)/, use創新 ? 'ꡖ' : 'ꡗ'], // 恚、抉 ʔ͡j > ʔ。《通考》也如此 366 | [/ꡝꡧ?(?=ꡟ(?!ꡠ)|ꡡ)/, ''], // 主要元音是 u、o 時,省略 w 母(ue 中的 u 不被視爲主要元音) 367 | 368 | [/ꡦ+/, 'ꡦ'], // 不use創新時的潛在情況 369 | ]; 370 | const 古代對立未合併列表 = [ 371 | [/ꡋ(?=ꡞ$|ꡟꡠ|ꡠꡏ)/, 'ꡇ', '孃母'], // 泥捼鮎 n > 尼諉黏 ɲ 372 | [/(?<=[ꡭ])ꡠ$/, 'ꡦ', '疑母'], // 謁 je > 齧 jje 373 | [/(?<=[ꡜ])ꡦꡟꡃ/, 'ꡧꡞꡃ', '梗攝 B類 平聲'], // 兄 ħwiŋ > 胷 ħjuŋ 374 | [/(?<=[ꡖ])ꡟꡃ/, 'ꡧꡟꡃ', '梗攝'], // 翁 ʔuŋ > 泓 ʔwuŋ 375 | [/(?<=[ꡆꡅ])ꡦꡋ/, 'ꡠꡋ', '知組 平去聲'], // 饘 tʂjen > 邅 tʂen 376 | [/(?<=[ꡅ])ꡠꡓ/, 'ꡦꡓ', '徹母 開口'], // 弨 tʂʰew > 超 tʂʰjew 377 | ]; 378 | 379 | let 聲母 = ''; 380 | Object.keys(聲母字典).forEach(e => { if (擬音.startsWith(e) && e.length > 聲母.length) 聲母 = e; }); 381 | 擬音 = 擬音.replace(聲母, 聲母字典[聲母]); 382 | 後處理替換列表.forEach(e => 擬音 = 擬音.replace(...e)); 383 | if (use創新) 古代對立未合併列表.forEach(e => { if (is(e[2])) 擬音 = 擬音.replace(e[0], e[1]); }); 384 | return 擬音; 385 | } 386 | 387 | function 八思巴字to轉寫(str) { 388 | const 八思巴字轉寫列表 = { 389 | '八思巴字': [ 390 | 'ꡂ', 'ꡁ', 'ꡀ', 'ꡃ', 391 | 'ꡊ', 'ꡉ', 'ꡈ', 'ꡋ', 392 | 'ꡆ', 'ꡅ', 'ꡄ', 'ꡇ', 393 | 'ꡎ', 'ꡍ', 'ꡌ', 'ꡏ', 'ꡰ', 'ꡤ', 'ꡓ', 394 | 'ꡒ', 'ꡑ', 'ꡐ', 'ꡛ', 'ꡕ', 'ꡮ', 'ꡚ', 395 | 'ꡜ', 'ꡣ', 'ꡯ', 'ꡖ', 'ꡗ', 'ꡝ', 'ꡭ', 'ꡙ', 'ꡔ', 396 | 'ꡞ', 'ꡟ', 'ꡠ', 'ꡡ', 'ꡦ', 'ꡧ', 'ꡨ', 397 | ], 398 | '照那斯圖 1987 轉寫 ⭐': [ 399 | 'g', 'kʻ', 'k', 'ŋ', 400 | 'd', 'tʻ', 't', 'n', 401 | 'dž', 'tšʻ', 'tš', 'ň', 402 | 'b', 'pʻ', 'p', 'm', 'hu̯', 'ħu̯', 'w', 403 | 'dz', 'tsʻ', 'ts', 's', 'z', 'š₂', 'š₁', 404 | 'h', 'ɣ', 'ħ', 'ꞏ', 'j̊', 'ʼ', 'j', 'l', 'ž', 405 | 'i', 'u', 'e', 'o', 'ė', 'u̯', 'i̯', 406 | ], 407 | '吉池孝一 2005 轉寫': [ 408 | 'g', 'kʻ', 'k', 'ŋ', 409 | 'd', 'tʻ', 't', 'n', 410 | 'ǰ', 'čʻ', 'č', 'ň', 411 | 'b', 'pʻ', 'p', 'm', 'f2', 'f1', 'v', 412 | 'j', 'cʻ', 'c', 's', 'z', 'š2', 'š1', 413 | 'h2', 'γ', 'h1', 'ꞏ', 'y2', 'ʼ', 'y1', 'l', 'ž', 414 | 'i', 'u', 'ė', 'o', 'e', 'ŭ', 'ĭ', 415 | ], 416 | 'Coblin 2007 轉寫': [ 417 | 'g', 'kh', 'k', 'ng', 418 | 'd', 'th', 't', 'n', 419 | 'j', 'ch', 'c', 'ñ', 420 | 'b', 'ph', 'p', 'm', 'Hw', 'hw', 'w', 421 | 'dz', 'tsh', 'ts', 's', 'z', 'sh', 'zh', 422 | 'ʰ', 'X', 'H', '\'', 'Y', 'x', 'y', 'l', 'Zh', 423 | 'i', 'u', 'e', 'o', 'ÿ', 'w', 'y', 424 | ], 425 | 'Coblin 2007 擬音': [ 426 | 'k', 'kʼ', 'ɡ', 'ŋ', 427 | 't', 'tʼ', 'd', 'n', 428 | 'tʂ', 'tʂʼ', 'dʐ', 'ȵ', 429 | 'p', 'pʼ', 'b', 'm', 'f', 'v', 'ʋ', 430 | 'ts', 'tsʼ', 'dz', 's', 'z', 'ʂ', 'ʐ', 431 | 'ʰ', 'ɣ', 'ɣj', 'ʔ', 'ʔj', 'ɦ', 'j', 'l', 'r', 432 | 'i', 'u', 'ɛ', 'ɔ', 'ÿ', 'w', 'j', 433 | ], 434 | '沈鐘偉 2008/2015 轉寫兼擬音': [ 435 | 'k', 'kʰ', 'ɡ', 'ŋ', 436 | 't', 'tʰ', 'd', 'n', 437 | 'tʃ', 'tʃʰ', 'dʒ', 'ɲ', 438 | 'p', 'pʰ', 'b', 'm', 'f', 'v', 'ʋ', 439 | 'ts', 'tsʰ', 'dz', 's', 'z', 'ʃ', 'ʒ', 440 | 'h', 'ɦ', 'ɦj', '0', 'j', '0̲', 'j̲', 'l', 'r', 441 | 'i', 'u', 'e', 'o', 'ɛ', 'w', 'j', 442 | ], 443 | 'unt 2023 轉寫 ⭐': [ 444 | 'k', 'kʰ', 'ɡ', 'ŋ', 445 | 't', 'tʰ', 'd', 'n', 446 | 'tʂ', 'tʂʰ', 'dʐ', 'ɲ', 447 | 'p', 'pʰ', 'b', 'm', 'f', 'v', 'w', 448 | 'ts', 'tsʰ', 'dz', 's', 'z', 'ʂ', 'ʐ', 449 | 'ħ', 'ʁ', 'ɣ', 'ʔ', 'ʔ͡j', 'ɦ', 'j', 'l', 'ɻ', 450 | 'i', 'u', 'e', 'o', 'jE', 'w', 'j', 451 | ], 452 | }; 453 | 454 | const 後處理替換列表字典 = { 455 | '照那斯圖 1987 轉寫 ⭐': [['ėa', 'ė'], ['bꞏo', 'boꞏo']].concat( 456 | 選項['ꡠ、ꡦ 的轉寫'] === 'ꡠė ꡦe(八思巴字蒙古語風格)' ? [['ė', 'E'], ['e', 'ė'], ['E', 'e'],] : [] 457 | ), 458 | '吉池孝一 2005 轉寫': [ 459 | [/(?<=[fšhy])1/g, 選項.聲母附加數字?.split(' ')[0]], 460 | [/(?<=[fšhy])2/g, 選項.聲母附加數字?.split(' ')[1]], 461 | ], 462 | 'Coblin 2007 轉寫': [[/^ʰ/, 'h'], ["b'o", "ba'o"]], 463 | 'Coblin 2007 擬音': [ 464 | [/ʋ$/, 'w'], 465 | [/^ʰ/, 'x'], ['ʰa', 'A'], ['ʰi', 'ə'], [/(?<=s|z)ə$/, 'ɿ'], [/ə$/, 'ʅ'], 466 | ['ÿaŋ', 'jaŋ'], ['ÿa', 'jɛ'], ['ÿ', 'j'], ['jj', 'j'], 467 | ['juŋ', 'yuŋ'], ['ju', 'y'], ['jɔ', 'yɔ'], [/jwj|jw|wj/, 'y'], 468 | [/^y/, 'jy'], 469 | ['uɛ', 'uɛ̌'], 470 | ], 471 | '沈鐘偉 2008/2015 轉寫兼擬音': [ 472 | [/ɦj(?=w?ɛ)/, 'ɦ'], 473 | ['ɛi', 'ji'], ['ɛe', 'je'], 474 | ['ɛu', 'y'], ['ɛo', 'ø'], ['ɛa', 'ɛ'], 475 | [/(? (str.includes(元音)))) { 488 | if (str.length > 1 && 'ꡏꡋꡃꡭꡓ'.includes(str.slice(-1))) { 489 | // 有韻尾則 a 補在韻尾前 490 | str = str.slice(0, -1) + 'a' + str.slice(-1); 491 | } else { 492 | // 否則 a 補在最後 493 | str += 'a'; 494 | } 495 | } 496 | const 八思巴字轉寫字典 = Object.fromEntries( 497 | 八思巴字轉寫列表.八思巴字.map((e, i) => [e, 八思巴字轉寫列表[選項.顯示][i]]) 498 | ); 499 | str = [...str].map(e => 八思巴字轉寫字典[e] ?? e).join(''); 500 | 後處理替換列表字典[選項.顯示].forEach(pair => str = str.replace(...pair)); 501 | return str; 502 | } 503 | 504 | const 擬音 = get擬音(); 505 | if (選項.顯示 === 'unt 2023 擬音 ⭐') return 擬音; 506 | const 八思巴字 = 擬音to八思巴字(擬音); 507 | return 選項.顯示 === '八思巴字' ? 八思巴字 : 八思巴字to轉寫(八思巴字); 508 | -------------------------------------------------------------------------------- /msoeg_v8.js: -------------------------------------------------------------------------------- 1 | /* msoeg 中古擬音 V8 2 | * 3 | * https://zhuanlan.zhihu.com/p/145409852 4 | * 5 | * @author unt 6 | */ 7 | 8 | /** @type { 音韻地位['屬於'] } */ 9 | const is = (...x) => 音韻地位.屬於(...x); 10 | /** @type { 音韻地位['判斷'] } */ 11 | const when = (...x) => 音韻地位.判斷(...x); 12 | 13 | let isIPA = 選項.音標體系 !== 'MPA'; 14 | let 音標體系changed = 選項._last音標體系 && 選項._last音標體系 !== 選項.音標體系; 15 | let mpaRevoked = false; // 用於在用戶選擇放棄使用 MPA 時刷新選項列表的內容 16 | if (音標體系changed && 選項.音標體系 === 'MPA') { 17 | const message = '請注意 MPA 不是國際音標,其中諸多符號不可按國際音標理解,容易引起誤會。在公共場合使用 MPA 後果自負!\n\n確認使用 MPA?'; 18 | if (!confirm(message)) { // eslint-disable-line no-undef 19 | isIPA = true; 20 | 音標體系changed = false; 21 | mpaRevoked = true; 22 | } 23 | } 24 | 25 | if (!音韻地位) return [ 26 | ['_last音標體系', [1, !mpaRevoked && 選項.音標體系 || '國際音標'], { hidden: true }], 27 | ['音標體系', [1, '國際音標', 'MPA'], { 28 | reset: mpaRevoked, 29 | description: [ 30 | '擬音中用到的 MPA(即 msoeg 音標)與國際音標對照', 31 | // 詳見 https://zhuanlan.zhihu.com/p/710982203 32 | '(1)\u2002腭噝音(章組)[tɕ] MPA 作 ⟨tç⟩', 33 | '(2)\u2002r 化元音記號 [◌˞\u2006] MPA 作 ⟨◌̣⟩', 34 | '(3)\u2002[i ɯ̯ ɯ u ɛ ɔ a] 對應的 r 化元音 MPA 作 ⟨ị ɨ̣ ɯ̣ ụ ɜ̣ ɞ̣ ạ⟩', 35 | '(4)\u2002[o̯](實即 [ȗ̙])MPA 作 ⟨ᵒ⟩', 36 | '此外,擬音中的 MPA ⟨ɯ/u、ɤ/o、ʌ/ɔ、ɑ⟩ 是非前元音,⟨ɛ、ɜ̣/ɞ̣、ʌ/ɔ⟩ 是中元音,這裏保留 MPA 原貌', 37 | ].join('\n'), 38 | }], 39 | ['r化元音記號|r 化元音記號\n知乎文章用下加點\n韻鑒用 r 音鉤\n「r 化」原文稱「捲舌」', [ 40 | isIPA ? 1 : 3, 41 | { text: 'r 音鉤(帶空隙)◌˞', value: '\u02DE\u2006' }, 42 | { text: 'r 音鉤(無空隙)◌˞', value: '\u02DE' }, 43 | { text: '下加點 ◌̣', value: '\u0323' }, 44 | ].slice(0, isIPA ? 3 : 4), { reset: 音標體系changed }], 45 | ['通江宕攝韻尾|\n知乎文章用 ŋʷ/kʷ\n韻鑒用 ɴ/q', [3, 'ŋ/k', 'ŋʷ/kʷ', 'ɴ/q']], 46 | ['聲調記號|\n上標的 ʔ Unicode 未收,這裏以 ˀ 代替', 47 | [1, '上ʔ 去h', '上ˀ 去ʰ'].filter((_, i) => isIPA || i !== 1), 48 | { reset: 音標體系changed }, 49 | ], 50 | ['顯示高級選項', false], 51 | 52 | 選項.顯示高級選項 ? '高級選項' : '', 53 | ['保留非三等ʶ記號|保留非三等 ʶ 記號\nʶ 不是標準國際音標,代表舌根偏後', 54 | false, { hidden: !選項.顯示高級選項 }], 55 | ['章組|\n知乎文章和韻鑒用腭噝音', 56 | [2, '齦後噝音 tʃ', isIPA ? '腭噝音 tɕ' : '腭噝音 tç'], { hidden: !選項.顯示高級選項 }], 57 | ['莊三韻母起始|\n知乎文章用 r 化元音\n韻鑒用 ɻ\n這裏默認用普通的三等起始(B 類或 C 類)', 58 | [1, '普通', 'r 化元音', 'ɻ'], { hidden: !選項.顯示高級選項 }], 59 | ['覺韻|\n知乎文章和韻鑒用低元音\n這裏默認用中元音,與江韻一致', 60 | [1, '中元音', '低元音'], { hidden: !選項.顯示高級選項 }], 61 | ['庚三清|\n知乎文章和韻鑒用低元音', 62 | [2, '中元音', '低元音'], { hidden: !選項.顯示高級選項 }], 63 | ['宕攝入聲附加|\n知乎文章和韻鑒用 ⁽ʷ⁾\n這裏默認省略', 64 | [1, '無', '⁽ʷ⁾', 'ʷ'], { hidden: !選項.顯示高級選項 || 選項.通江宕攝韻尾?.includes('ʷ') }], 65 | ]; 66 | 67 | function get聲母_默認拼寫() { 68 | // 五十一聲類 + 俟母 69 | const 普通聲母字典 = { 70 | 幫: 'p', 滂: 'pʰ', 並: 'b', 明: 'm', 71 | 知: 'ʈ', 徹: 'ʈʰ', 澄: 'ɖ', 孃: 'ɳ', 來: 'ɭ', 72 | 見: 'k', 溪: 'kʰ', 羣: 'ɡ', 疑: 'ŋ', 云: 'w', 73 | 影: 'ʔ', 曉: 'x', 74 | 精: 'ts', 清: 'tsʰ', 從: 'dz', 心: 's', 邪: 'z', 75 | 莊: 'tʂ', 初: 'tʂʰ', 崇: 'dʐ', 生: 'ʂ', 俟: 'ʐ', 76 | 章: 'tɕ', 昌: 'tɕʰ', 常: 'dʑ', 書: 'ɕ', 船: 'ʑ', 日: 'ɲ', 以: 'j', 77 | }; 78 | const 非三等聲母字典 = { 79 | 幫: 'pʶ', 滂: 'pʶʰ', 並: 'bʶ', 明: 'mʶ', 80 | 端: 'tʶ', 透: 'tʶʰ', 定: 'dʶ', 泥: 'nʶ', 來: 'lʶ', 81 | 見: 'q', 溪: 'qʰ', 疑: 'ɴ', 82 | 影: 'ʔʶ', 曉: 'χ', 匣: 'ʁ', 83 | 精: 'tsʶ', 清: 'tsʶʰ', 從: 'dzʶ', 心: 'sʶ', 84 | }; 85 | if (is`云母 開口 非 侵鹽韻`) return 'ɰ'; 86 | if (is`以母 合口 或 以母 東鍾虞韻`) return 'ɥ'; 87 | if (is`三等 或 來母 二等 非 庚韻`) return 普通聲母字典[音韻地位.母] || 非三等聲母字典[音韻地位.母]; 88 | return 非三等聲母字典[音韻地位.母] || 普通聲母字典[音韻地位.母]; 89 | } 90 | 91 | function get聲母() { 92 | let 聲母 = get聲母_默認拼寫(); 93 | if (選項.章組.includes('ʃ')) { 94 | 聲母 = 聲母.replace('ɕ', 'ʃ').replace('ʑ', 'ʒ'); 95 | } else if (選項.章組.includes('ç')) { 96 | 聲母 = 聲母.replace('ɕ', 'ç').replace('ʑ', 'ʝ'); 97 | } 98 | if (!選項.保留非三等ʶ記號) { 99 | 聲母 = 聲母.replace('ʶ', ''); 100 | } 101 | return 聲母; 102 | } 103 | 104 | function get韻母() { 105 | let 韻母 = when([ 106 | ['之韻', 'ɯ'], ['支韻', 'e'], ['魚韻', 'ɤ'], ['虞模韻', 'o'], 107 | ['佳韻', 'ɜ̣'], 108 | ['麻韻 三四等', 'a'], ['麻韻', 'ạ'], ['歌韻', 'ɑ'], 109 | 110 | [選項.庚三清 === '中元音' && '庚清韻 三等', 'ɛŋ'], 111 | [選項.覺韻 === '低元音' && '江韻 入聲', 'ɑ̣ɴ'], 112 | ['蒸韻', 'ɯŋ'], ['東韻', 'uɴ'], ['鍾韻', 'oɴ'], 113 | ['青韻', 'ɛŋ'], ['耕韻', 'ɜ̣ŋ'], ['登韻', 'ʌŋ'], ['冬韻', 'ɔɴ'], ['江韻', 'ɞ̣ɴ'], 114 | ['庚清韻 (三四等 或 端組 或 來母)', 'aŋ'], ['庚清韻', 'ạŋ'], ['陽唐韻', 'ɑɴ'], 115 | 116 | ['脂韻', 'ii'], ['微韻', 'ɯi'], 117 | ['齊祭韻', 'ɛi'], ['皆韻', 'ɜ̣i'], ['灰咍廢韻', 'ʌi'], 118 | ['夬韻', 'ại'], ['泰韻', 'ɑi'], 119 | 120 | ['真臻韻', 'in'], ['殷韻', 'ɯn'], ['文韻', 'un'], 121 | ['先仙韻', 'ɛn'], ['山韻', 'ɜ̣n'], ['元魂痕韻', 'ʌn'], 122 | ['刪韻', 'ạn'], ['寒韻', 'ɑn'], 123 | 124 | ['幽韻', 'iu'], ['尤侯韻', 'uu'], 125 | ['蕭宵韻', 'ɛu'], 126 | ['肴韻', 'ạu'], ['豪韻', 'ɑu'], 127 | 128 | ['侵韻', 'im'], 129 | ['鹽添韻', 'ɛm'], ['咸韻', 'ɜ̣m'], ['嚴凡韻', 'ʌm'], ['覃韻', 'ɔm'], 130 | ['銜韻', 'ạm'], ['談韻', 'ɑm'], 131 | ]); 132 | 133 | // 等類記號 134 | if (is`三等` || is`四等` && 韻母.startsWith('a')) { 135 | if (韻母.startsWith('ɯ') && is`銳音 或 蒸韻 AB類`) 韻母 = 'i' + 韻母; 136 | else if (/^[eɛa]/.test(韻母)) 韻母 = 'i' + 韻母; 137 | else if (/^[ɤʌɑ]/.test(韻母)) 韻母 = 'ɯ' + 韻母; 138 | else if (韻母.startsWith('o')) 韻母 = 'u' + 韻母; 139 | 140 | if (韻母.startsWith('i') && is`B類 非 幽韻`) { 141 | // 幽韻知乎原文和韻鑒僅 A 類 142 | // 其中,支宵侵的重紐三等開口歸 C 類 143 | 韻母 = 韻母.replace('i', is`支宵韻 或 侵韻 見影組 非 云母` ? 'ɯ' : 'ị'); 144 | } 145 | 146 | if (is`莊組`) { 147 | 韻母 = 韻母.replace(/^i/, 'ị'); // 莊組 AB 類歸 B 類 148 | if (選項.莊三韻母起始 === 'r 化元音') { 149 | 韻母 = 韻母.replace(/^(.)̣?(.*)/, '$1̣$2'); 150 | 韻母 = 韻母.replace(/ị(?!e)|ɯ̣(?!ɤ)/, 'ɨ̣'); // ABC 類的起始都等同於二等的 ɨ̣,支魚韻除外 151 | } else if (選項.莊三韻母起始 === 'ɻ') { 152 | 韻母 = 韻母.replace(/(ị|^ɯ(?!ɤ))?/, 'ɻ'); // 以其他元音起始的則直接前加 ɻ,魚韻除外 153 | } 154 | } 155 | } else { 156 | if (韻母.startsWith('u')) 韻母 = 'ᵒ' + 韻母; 157 | } 158 | 159 | // 開合記號 160 | if (韻母.startsWith('ɯ')) { 161 | if (is`合口 或 幫組 非 支宵侵韻`) 韻母 = 韻母.replace('ɯ', 'u'); 162 | } else { 163 | if (is`合口 非 云以母`) 韻母 = 韻母.replace(/(ɻ?)(.*)/, '$1ʷ$2').replace('ʷu', 'u'); 164 | } 165 | 166 | if (isIPA) 韻母 = 韻母 167 | .replace('ɨ̣', 'ɯ̣') 168 | .replace('ɜ̣', 'ɛ̣').replace('ɞ̣', 'ɔ̣') 169 | .replace('ᵒ', 'o̯'); 170 | 韻母 = 韻母 171 | .replace('ii', 'i').replace('uu', 'u') 172 | .replace('̣', 選項.r化元音記號) 173 | .replace('ɴ', 選項.通江宕攝韻尾.split('/')[0]); 174 | if (is`入聲`) 韻母 = 韻母 175 | .replace('m', 'p') 176 | .replace('n', 't') 177 | .replace('ŋ', 'k') 178 | .replace('ɴ', 'q'); 179 | if (is`江攝 入聲` && !韻母.endsWith('ʷ') && 選項.覺韻 === '低元音') 韻母 += 'ʷ'; 180 | if (is`宕攝 入聲` && !韻母.endsWith('ʷ')) 韻母 += 選項.宕攝入聲附加.replace('無', ''); 181 | return 韻母; 182 | } 183 | 184 | function get聲調() { 185 | if (is`平入聲`) return ''; 186 | return 選項.聲調記號.split(' ')[+is`去聲`].slice(1); 187 | } 188 | 189 | return get聲母() + get韻母() + get聲調(); 190 | -------------------------------------------------------------------------------- /n_song.js: -------------------------------------------------------------------------------- 1 | /* 推導北宋擬音(聲音唱和圖) 2 | * 3 | * 以《聲音唱和圖》和北宋中原押韻爲基礎。《聲音唱和圖》分析並記錄了北宋共通語的音系,對該圖的解析與擬音詳見:https://zhuanlan.zhihu.com/p/498778513 4 | * 5 | * 代碼注釋中的等均指韻圖等(聲音唱和圖等),而不是切韻等 6 | * 7 | * @author unt 8 | */ 9 | 10 | /** @type { 音韻地位['屬於'] } */ 11 | const is = (...x) => 音韻地位.屬於(...x); 12 | /** @type { 音韻地位['判斷'] } */ 13 | const when = (...x) => 音韻地位.判斷(...x); 14 | 15 | const is表層 = 選項.顯示形式 !== '底層'; 16 | 17 | if (!音韻地位) return [ 18 | ['_last顯示形式', [1, 選項.顯示形式 ?? '底層'], { hidden: true }], 19 | ['顯示形式|顯示\n音位分析據《聲音唱和圖》', [2, 20 | { value: '底層', text: '底層形式(音位形式)' }, 21 | { value: '表層', text: '表層形式(語音實現)' }, 22 | ]], 23 | '聲', 24 | ['全濁平送氣|全濁平送氣 ʰ\n《聲音唱和圖》全濁平塞音送氣,但按通常習慣可不標', true], 25 | ['次濁上喉化|次濁上喉化 ˀ\n《聲音唱和圖》次濁上歸陰調,但按通常習慣可不標\n喉化此處寫作上標的 ʔ,此上標字母 Unicode 未收,以 ˀ 代替', true], 26 | ['知照組\ntʂ、tɕ《聲音唱和圖》已合爲同一音位。按通用的國際音標習慣,無對立的 tʂ、tɕ 可一律標爲 tʃ,但爲了照顧漢語習慣,表層形式默認選擇 tʂ、tɕ 分立', 27 | [is表層 ? 3 : 1, 28 | { value: 'ʃʒɹ', text: '知tɹ 莊章tʃ' }, 29 | { value: 'ʂʐɻ', text: '知tɻ 莊章tʂ' }, 30 | { value: 'ʂʐɹ|ɕʑɹ', text: '知tɹ 莊tʂ 章tɕ' }, 31 | { value: 'ʂʐɻ|ɕʑɻ', text: '知tɻ 莊tʂ 章tɕ' }, 32 | ].slice(0, is表層 ? 5 : 3), 33 | { reset: 選項._last顯示形式 === '底層' && 選項.顯示形式 !== '底層' }, 34 | ], 35 | '韻', 36 | is表層 ? ['入聲尾\n深咸 | 臻山 | 曾梗通江宕', [1, 37 | { value: 'p t k', text: 'p | t | k(符合通常習慣)' }, 38 | { value: 'β ɾ ɣ', text: 'β | ɾ | ɣ(接近實際音值)' }, 39 | ]] : ['入聲尾\n陰入相配依《聲音唱和圖》\n深咸 | 臻山 | 曾梗 | 通江宕', [1, 40 | { value: 'p t k wk', text: 'p | t | k | wk(陽入相配)' }, 41 | { value: 'ʋˀ ˀ jˀ wˀ', text: 'ʋˀ | ˀ | jˀ | wˀ(陰入相配)' }, 42 | ]], 43 | ['咸山蟹攝銳音一開歸\n保守:一等\n時興:二等\n(泥母蟹攝除外。《聲音唱和圖》“乃”仍在一等)', [2, '一等', '二等']], 44 | ['咸山攝輕脣歸\n保守:一等\n時興:二等', [2, '一等', '二等']], 45 | { 46 | key: '止蟹三四合韻基', 47 | text: '止蟹三四合', 48 | description: is表層 ? '保守:ɥi\n時興:yj' : '保守:jwɨ\n時興:jwɨj', 49 | value: 'ɨj', 50 | options: [ 51 | { value: 'ɨ', text: is表層 ? 'ɥi' : 'jwɨ' }, 52 | { value: 'ɨj', text: is表層 ? 'yj' : 'jwɨj' }, 53 | ], 54 | }, 55 | ['蟹一合\n保守:wɔj\n時興:uj', [2, 'wɔj', 'uj'], { hidden: !is表層 }], 56 | ['章止同蟹|章止開 = 章蟹開\n《聲音唱和圖》和《蒙古字韻》同音,但北方官話實際不同音。《中原音韻》中,章止開歸支思韻,章蟹開歸齊微韻', false], 57 | ['部分蟹攝二等歸假攝', true], 58 | ['部分流攝脣音歸遇攝', true], 59 | '調', 60 | ['聲調', [1, '附加符號', '調類數字', '省略']], 61 | ['全濁上歸去\n《聲音唱和圖》未體現全濁上字的聲調,按口語則已歸去聲,按《蒙古字韻》風格則仍算上聲', true], 62 | ['影喻上聲合併\n《聲音唱和圖》無影喻上聲對立的空間,可能口語已合併,但同期反切未見相混的情況', false], 63 | ]; 64 | 65 | function 調整音韻地位() { 66 | function 調整(表達式, 調整屬性) { if (is(表達式)) 音韻地位 = 音韻地位.調整(調整屬性); } 67 | // 輕唇化例外 68 | 調整('明母 尤韻', { 等: '一', 類: null, 韻: '侯' }); 69 | 調整('明母 東韻', { 等: '一', 類: null }); 70 | 71 | if (is`云母 通攝 舒聲`) 音韻地位 = 音韻地位.調整('匣母', ['匣母三等']); // 雄熊 72 | 73 | // [慧琳反切體現的, 唐代用韻體現的, 據今音推測的] 74 | const 蟹攝二等歸假攝字 = ['崖咼(呙)扠涯派差絓畫(画)罣罷(罢)', '佳鼃娃解卦', '灑蝸話(话)掛挂查叉杈衩'].join(''); 75 | const 流攝脣音歸遇攝字 = ['浮戊母罦罘蜉覆拇負(负)阜', '部畝(亩)畮婦(妇)不否桴富', '復複(复)副牡'].join(''); 76 | if (選項.部分蟹攝二等歸假攝 !== false && 蟹攝二等歸假攝字.includes(字頭)) 調整('蟹攝 二等', { 韻: '麻' }); 77 | if (選項.部分流攝脣音歸遇攝 !== false && 流攝脣音歸遇攝字.includes(字頭)) 調整('幫組 尤侯韻', { 韻: is`尤韻` ? '虞' : '模' }); 78 | } 79 | 80 | function get聲母() { 81 | return when([ 82 | ['匣母 肴韻 平聲', 'ɰ'], // 匣母“爻”在三音,但一等無字,說明“完”不是零聲母 83 | ['崇母 止攝 仄聲', 'ʒ'], // 崇母“士”在十音。另船母船小韻和繩小韻中“乘”聲符字今讀塞擦音,但圖中未體現,這裏不考慮 84 | 85 | ['幫組 C類', [ 86 | ['幫滂母', 'f'], ['並母', 'v'], ['明母 上聲', 'ˀʋ'], ['明母', 'ʋ'], // 四音 87 | ]], 88 | ['曉母', 'x'], ['匣母', 'ɣ'], ['疑母 上聲', 'ˀŋ'], ['疑母', 'ŋ'], // 二音 89 | ['心母', 's'], ['邪母', 'z'], // 九音。這一組的後兩個聲母(同部位的近音)有音無字 90 | ['生書母', 'ʃ'], ['常母 仄聲 或 俟船母', 'ʒ'], ['日母 上聲', 'ˀɹ'], ['日母', 'ɹ'], // 十音 91 | 92 | ['幫母', 'p'], ['並母 仄聲', 'b'], ['滂母', 'pʰ'], ['並母', 'bʰ'], // 五音 93 | ['端母', 't'], ['定母 仄聲', 'd'], ['透母', 'tʰ'], ['定母', 'dʰ'], // 六音 94 | ['見母', 'k'], ['羣母 仄聲', 'ɡ'], ['溪母', 'kʰ'], ['羣母', 'ɡʰ'], // 一音 95 | ['知母', 'tɹ'], ['澄母 仄聲', 'dɹ'], ['徹母', 'tɹʰ'], ['澄母', 'dɹʰ'], // 十二音 96 | ['精母', 'ts'], ['從母 仄聲', 'dz'], ['清母', 'tsʰ'], ['從母', 'dzʰ'], // 八音 97 | ['莊章母', 'tʃ'], ['崇母 仄聲', 'dʒ'], ['初昌母', 'tʃʰ'], ['崇常母', 'dʒʰ'], // 十一音 98 | 99 | ['泥孃母 上聲', 'ˀn'], ['泥孃母', 'n'], ['來母 上聲', 'ˀl'], ['來母', 'l'], // 七音 100 | ['影母', 'ʔ'], ['云以母 上聲', 選項.影喻上聲合併 ? 'ʔ' : 'ˀɰ'], ['云以母', 'ɰ'], ['明母 上聲', 'ˀm'], ['明母', 'm'], // 三音 101 | ]); 102 | } 103 | 104 | function get等() { 105 | return when([ 106 | ['幫組 C類', [ 107 | ['止蟹攝', '四'], 108 | ['咸山攝', 選項.咸山攝輕脣歸?.[0] ?? '二'], 109 | ['', '一'], 110 | ]], 111 | ['精組 止攝 開口', '一'], // 中唐 /sjɨ/ > /sɨ/ 變一等 112 | ['銳音 蟹山咸攝 一等 開口 非 (泥母 蟹攝)', 選項.咸山蟹攝銳音一開歸?.[0] ?? '二'], 113 | [音韻地位.韻圖等 === '四' && '銳音 非 以母', '三'], // 圖中銳音一律無四等 114 | ['', 音韻地位.韻圖等], 115 | ]); 116 | } 117 | 118 | function get開合() { 119 | return when([ 120 | [選項.咸山攝輕脣歸 === '一等' && '咸山攝 幫組 C類', '合'], 121 | ['流深咸攝', '開'], // 深咸攝舒入聲都視爲開口,不依圖中定義 122 | ['幫組', [ 123 | ['通宕攝', '開'], 124 | ['一等 或 虞文歌韻', '合'], // 其餘一等(包括輕脣變一等的)歸合口 125 | ['', '開'], 126 | // 圖中“八”在合口,但二等不應該在合口。這可能是繼承了《韻鏡》《七音略》脣音刪舒、山入在合口,刪入、山舒在開口。本方案不考慮 127 | ]], 128 | ['鍾虞模韻', '合'], 129 | ['江韻 銳音', '合'], 130 | ['', 音韻地位.呼 ?? '開'], 131 | ]); 132 | } 133 | 134 | function get介音(等, 開合) { 135 | return { 136 | 一: { 開: '', 合: 'w' }, 137 | 二: { 開: 'ʕ', 合: 'ʕw' }, 138 | 三: { 開: 'j', 合: 'jw' }, 139 | 四: { 開: 'ʲj', 合: 'ʲjw' }, 140 | }[等][開合]; 141 | } 142 | 143 | function get韻基() { 144 | return when([ 145 | ['遇攝', 'ɯ'], // 六聲下(僅舒聲) 146 | 147 | ['止攝 (莊組 或 (精章組 或 日母) 開口)', 'ɨ'], // 五聲上(舒) 148 | ['臻攝', is`入聲` ? 'ɨt' : 'ɨn'], // 五聲上(入) & 三聲下 149 | 150 | ['果假攝', 'a'], // 一聲上(舒) 151 | ['山攝', is`入聲` ? 'at' : 'an'], // 一聲上(入) & 三聲上 152 | 153 | ['蟹攝 (一二等 或 莊組)', 'aj'], // 一聲下(僅舒聲) 154 | 155 | ['止蟹攝 合口', 選項.止蟹三四合韻基 ?? 'ɨj'], // 五聲下(舒) 156 | ['止蟹攝', 選項.章止同蟹 ? 'ɨ' : 'ɨj'], 157 | ['曾梗攝', is`入聲` ? 'ɨk' : 'ɨŋ'], // 五聲下(入) & 二聲下 158 | 159 | ['流攝', 'ɨw'], // 四聲下(舒) 160 | ['通攝', is`入聲` ? 'ɨwk' : 'ɨwŋ'], // 四聲下(入) & 六聲上 161 | 162 | ['效攝', 'aw'], // 四聲上(舒) 163 | ['宕江攝', is`入聲` ? 'awk' : 'awŋ'], // 四聲上(入) & 二聲上 164 | 165 | ['深攝', is`入聲` ? 'ɨp' : 'ɨm'], // 七聲上 166 | ['咸攝', is`入聲` ? 'ap' : 'am'], // 七聲下 167 | ]); 168 | } 169 | 170 | function get聲調() { 171 | if (選項.聲調 === '省略') return ''; 172 | let 聲調 = 音韻地位.聲; 173 | if (選項.全濁上歸去 && is`全濁 上聲`) 聲調 = '去'; 174 | return { 175 | 附加符號: ['̀', '́', '̌', ''], // 入聲已由韻尾表明,無需附加符號 176 | 調類數字: ['¹', '²', '³', '⁴'], 177 | }[選項.聲調 ?? '附加符號']['平上去入'.indexOf(聲調)]; 178 | } 179 | 180 | function 底層to表層(音節) { 181 | function 替換韻核(from, tos, condition = true) { 182 | if (!condition) return; 183 | 音節.韻核 = 音節.韻核.replace(from, 音節.介音.includes('w') ? tos.pop() : tos[0]); 184 | } 185 | 186 | const is曾梗攝 = ['ŋ', 'k'].includes(音節.韻尾); 187 | 替換韻核('a', ['ɑ']); // 先把 /a/ 重置成一等的表層形式 [ɑ] 188 | if (音節.介音.includes('ʕ')) { 189 | // 二等 190 | 替換韻核('ɯ', ['ɯ', 'u']); 191 | 替換韻核('ɨ', ['iˤ'], is曾梗攝); 192 | 替換韻核('ɨ', ['ɨ', 'u'], 音節.韻尾); 193 | 替換韻核('ɑ', ['a']); 194 | } else if (音節.介音.includes('j')) { 195 | // 三四等 196 | 替換韻核('ɯ', ['ɯ', 'u']); 197 | 替換韻核('ɨ', ['i', 'y'], 音節.韻尾); 198 | 替換韻核('ɨ', ['i'], 選項.章止同蟹); 199 | 替換韻核('ɨ', ['ɨ', 'i']); 200 | 替換韻核('ɑ', ['æ', 'ɐ'], !音節.韻尾); // 北宋時麻二麻三未有明確分開的跡象,暫擬作 [jæ] 201 | 替換韻核('ɑ', ['ɛ', 'ɔ'], is`鈍音` && !音節.介音.includes('ʲ')); 202 | 替換韻核('ɑ', ['ɛ']); 203 | } else { 204 | // 一等 205 | 替換韻核('ɯ', ['ɯ', 'u']); 206 | 替換韻核('ɨ', ['ɨ', 'u']); 207 | 替換韻核('ɑ', ['ʌ', 'ɔ'], !音節.韻尾); 208 | 替換韻核('ɑ', ['ɑ', 'ɔ']); 209 | 替換韻核('ɔ', [選項.蟹一合?.slice(-2, -1) ?? 'u'], 音節.韻尾 === 'j'); 210 | } 211 | if (is曾梗攝) { 212 | 替換韻核('u', ['ɨ']); // /wɨŋ/ 不寫作 [uŋ],以免與通攝混淆 213 | } else if (['wŋ', 'wk'].includes(音節.韻尾)) { 214 | 替換韻核(/[iɨy]/, ['u']); 215 | 替換韻核(/[ɛɔ]/, ['ɑ']); 216 | 音節.韻尾 = 音節.韻尾.replace('w', ''); 217 | } 218 | 音節.介音 = 音節.介音.replace('jw', 'ɥ'); 219 | if (['ji', 'ɥy', 'wu'].includes(音節.介音.slice(-1) + 音節.韻核)) 音節.介音 = 音節.介音.slice(0, -1); 220 | if (音節.韻核 === 'i' && 音節.韻尾 === 'j') 音節.韻尾 = ''; 221 | } 222 | 223 | function 後處理(音節) { 224 | let 知照組符號 = 選項.知照組 ?? 'ʂʐɹ|ɕʑɹ'; 225 | if (!音節.介音.includes('ʕ')) 知照組符號 = 知照組符號.slice(-3); // 二等取開頭 3 個符號,三等取最後 3 個符號 226 | [...'ʃʒɹ'].forEach((e, i) => { 音節.聲母 = 音節.聲母.replace(e, 知照組符號[i]); }); 227 | if (is表層) 音節.介音 = 音節.介音.replace('ʕ', ''); 228 | 229 | if (音節.介音.includes('ʲ')) { 230 | 音節.聲母 += 'ʲ'; 231 | 音節.聲母 = 音節.聲母.replace('ʰʲ', 'ʲʰ'); 232 | 音節.介音 = 音節.介音.replace('ʲ', ''); 233 | } 234 | if (is表層 && 音節.聲母.includes('ɰ')) { 235 | 音節.聲母 = 音節.聲母.replace('ɰʲ', ''); // 韻圖四等由細音韻母指示 236 | if (音節.介音 === 'ɥ' || !音節.介音 && 音節.韻核 === 'y') 音節.聲母 = 音節.聲母.replace('ɰ', 'w'); 237 | if (音節.介音 === 'w' || !音節.介音 && 音節.韻核 === 'u') 音節.聲母 = 音節.聲母.replace('ɰ', ''); 238 | } 239 | if (選項.次濁上喉化 === false) 音節.聲母 = 音節.聲母.replace('ˀ', ''); 240 | if (選項.全濁平送氣 === false && is`全濁`) 音節.聲母 = 音節.聲母.replace('ʰ', ''); 241 | if (is`入聲`) { 242 | let 韻尾from = ['p', 't', 'k', 'wk']; 243 | let 韻尾to = 選項.入聲尾?.split(' ') ?? 韻尾from; 244 | 音節.韻尾 = 韻尾to[韻尾from.indexOf(音節.韻尾)]; 245 | } 246 | } 247 | 248 | function get音節() { 249 | const 韻基 = get韻基(); 250 | const 音節 = { 251 | 聲母: get聲母(), 252 | 介音: get介音(get等(), get開合()), 253 | 韻核: 韻基[0], 254 | 韻尾: 韻基.substring(1), 255 | 聲調: get聲調(), 256 | }; 257 | if (is表層) 底層to表層(音節); 258 | 後處理(音節); 259 | 音節.韻母 = 音節.介音 + 音節.韻核 + 音節.韻尾; 260 | if (選項.聲調 === '調類數字') 音節.帶調韻母 = 音節.韻母 + 音節.聲調; 261 | else if (音節.韻核.length === 1) 音節.帶調韻母 = 音節.介音 + 音節.韻核 + 音節.聲調 + 音節.韻尾; 262 | else 音節.帶調韻母 = 音節.介音 + 音節.韻核[0] + 音節.聲調 + 音節.韻核.slice(1) + 音節.韻尾; 263 | return 音節; 264 | } 265 | 266 | 調整音韻地位(); 267 | const 音節 = get音節(); 268 | return 音節.聲母 + 音節.帶調韻母; 269 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tshet-uinh-examples", 3 | "version": "20241008.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "tshet-uinh-examples", 9 | "version": "20241008.0.0", 10 | "license": "CC0-1.0", 11 | "dependencies": { 12 | "tshet-uinh": "^0.15.1", 13 | "tshet-uinh-deriver-tools": "^0.2.0" 14 | }, 15 | "devDependencies": { 16 | "@stylistic/eslint-plugin-js": "^2.11.0", 17 | "eslint": "^9.15.0" 18 | } 19 | }, 20 | "node_modules/@eslint-community/eslint-utils": { 21 | "version": "4.4.1", 22 | "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", 23 | "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", 24 | "dev": true, 25 | "dependencies": { 26 | "eslint-visitor-keys": "^3.4.3" 27 | }, 28 | "engines": { 29 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 30 | }, 31 | "funding": { 32 | "url": "https://opencollective.com/eslint" 33 | }, 34 | "peerDependencies": { 35 | "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 36 | } 37 | }, 38 | "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 39 | "version": "3.4.3", 40 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 41 | "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 42 | "dev": true, 43 | "engines": { 44 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 45 | }, 46 | "funding": { 47 | "url": "https://opencollective.com/eslint" 48 | } 49 | }, 50 | "node_modules/@eslint-community/regexpp": { 51 | "version": "4.12.1", 52 | "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", 53 | "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", 54 | "dev": true, 55 | "engines": { 56 | "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 57 | } 58 | }, 59 | "node_modules/@eslint/config-array": { 60 | "version": "0.19.0", 61 | "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", 62 | "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", 63 | "dev": true, 64 | "dependencies": { 65 | "@eslint/object-schema": "^2.1.4", 66 | "debug": "^4.3.1", 67 | "minimatch": "^3.1.2" 68 | }, 69 | "engines": { 70 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 71 | } 72 | }, 73 | "node_modules/@eslint/core": { 74 | "version": "0.9.0", 75 | "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", 76 | "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", 77 | "dev": true, 78 | "engines": { 79 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 80 | } 81 | }, 82 | "node_modules/@eslint/eslintrc": { 83 | "version": "3.2.0", 84 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", 85 | "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", 86 | "dev": true, 87 | "dependencies": { 88 | "ajv": "^6.12.4", 89 | "debug": "^4.3.2", 90 | "espree": "^10.0.1", 91 | "globals": "^14.0.0", 92 | "ignore": "^5.2.0", 93 | "import-fresh": "^3.2.1", 94 | "js-yaml": "^4.1.0", 95 | "minimatch": "^3.1.2", 96 | "strip-json-comments": "^3.1.1" 97 | }, 98 | "engines": { 99 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 100 | }, 101 | "funding": { 102 | "url": "https://opencollective.com/eslint" 103 | } 104 | }, 105 | "node_modules/@eslint/js": { 106 | "version": "9.15.0", 107 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", 108 | "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", 109 | "dev": true, 110 | "engines": { 111 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 112 | } 113 | }, 114 | "node_modules/@eslint/object-schema": { 115 | "version": "2.1.4", 116 | "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", 117 | "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", 118 | "dev": true, 119 | "engines": { 120 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 121 | } 122 | }, 123 | "node_modules/@eslint/plugin-kit": { 124 | "version": "0.2.3", 125 | "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", 126 | "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", 127 | "dev": true, 128 | "dependencies": { 129 | "levn": "^0.4.1" 130 | }, 131 | "engines": { 132 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 133 | } 134 | }, 135 | "node_modules/@humanfs/core": { 136 | "version": "0.19.1", 137 | "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 138 | "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 139 | "dev": true, 140 | "engines": { 141 | "node": ">=18.18.0" 142 | } 143 | }, 144 | "node_modules/@humanfs/node": { 145 | "version": "0.16.6", 146 | "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", 147 | "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", 148 | "dev": true, 149 | "dependencies": { 150 | "@humanfs/core": "^0.19.1", 151 | "@humanwhocodes/retry": "^0.3.0" 152 | }, 153 | "engines": { 154 | "node": ">=18.18.0" 155 | } 156 | }, 157 | "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { 158 | "version": "0.3.1", 159 | "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", 160 | "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", 161 | "dev": true, 162 | "engines": { 163 | "node": ">=18.18" 164 | }, 165 | "funding": { 166 | "type": "github", 167 | "url": "https://github.com/sponsors/nzakas" 168 | } 169 | }, 170 | "node_modules/@humanwhocodes/module-importer": { 171 | "version": "1.0.1", 172 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 173 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 174 | "dev": true, 175 | "engines": { 176 | "node": ">=12.22" 177 | }, 178 | "funding": { 179 | "type": "github", 180 | "url": "https://github.com/sponsors/nzakas" 181 | } 182 | }, 183 | "node_modules/@humanwhocodes/retry": { 184 | "version": "0.4.1", 185 | "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", 186 | "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", 187 | "dev": true, 188 | "engines": { 189 | "node": ">=18.18" 190 | }, 191 | "funding": { 192 | "type": "github", 193 | "url": "https://github.com/sponsors/nzakas" 194 | } 195 | }, 196 | "node_modules/@stylistic/eslint-plugin-js": { 197 | "version": "2.11.0", 198 | "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.11.0.tgz", 199 | "integrity": "sha512-btchD0P3iij6cIk5RR5QMdEhtCCV0+L6cNheGhGCd//jaHILZMTi/EOqgEDAf1s4ZoViyExoToM+S2Iwa3U9DA==", 200 | "dev": true, 201 | "dependencies": { 202 | "eslint-visitor-keys": "^4.2.0", 203 | "espree": "^10.3.0" 204 | }, 205 | "engines": { 206 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 207 | }, 208 | "peerDependencies": { 209 | "eslint": ">=8.40.0" 210 | } 211 | }, 212 | "node_modules/@types/estree": { 213 | "version": "1.0.6", 214 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 215 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 216 | "dev": true 217 | }, 218 | "node_modules/@types/json-schema": { 219 | "version": "7.0.15", 220 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 221 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 222 | "dev": true 223 | }, 224 | "node_modules/acorn": { 225 | "version": "8.14.0", 226 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", 227 | "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", 228 | "dev": true, 229 | "bin": { 230 | "acorn": "bin/acorn" 231 | }, 232 | "engines": { 233 | "node": ">=0.4.0" 234 | } 235 | }, 236 | "node_modules/acorn-jsx": { 237 | "version": "5.3.2", 238 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 239 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 240 | "dev": true, 241 | "peerDependencies": { 242 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 243 | } 244 | }, 245 | "node_modules/ajv": { 246 | "version": "6.12.6", 247 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 248 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 249 | "dev": true, 250 | "dependencies": { 251 | "fast-deep-equal": "^3.1.1", 252 | "fast-json-stable-stringify": "^2.0.0", 253 | "json-schema-traverse": "^0.4.1", 254 | "uri-js": "^4.2.2" 255 | }, 256 | "funding": { 257 | "type": "github", 258 | "url": "https://github.com/sponsors/epoberezkin" 259 | } 260 | }, 261 | "node_modules/ansi-styles": { 262 | "version": "4.3.0", 263 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 264 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 265 | "dev": true, 266 | "dependencies": { 267 | "color-convert": "^2.0.1" 268 | }, 269 | "engines": { 270 | "node": ">=8" 271 | }, 272 | "funding": { 273 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 274 | } 275 | }, 276 | "node_modules/argparse": { 277 | "version": "2.0.1", 278 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 279 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 280 | "dev": true 281 | }, 282 | "node_modules/balanced-match": { 283 | "version": "1.0.2", 284 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 285 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 286 | "dev": true 287 | }, 288 | "node_modules/brace-expansion": { 289 | "version": "1.1.11", 290 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 291 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 292 | "dev": true, 293 | "dependencies": { 294 | "balanced-match": "^1.0.0", 295 | "concat-map": "0.0.1" 296 | } 297 | }, 298 | "node_modules/callsites": { 299 | "version": "3.1.0", 300 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 301 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 302 | "dev": true, 303 | "engines": { 304 | "node": ">=6" 305 | } 306 | }, 307 | "node_modules/chalk": { 308 | "version": "4.1.2", 309 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 310 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 311 | "dev": true, 312 | "dependencies": { 313 | "ansi-styles": "^4.1.0", 314 | "supports-color": "^7.1.0" 315 | }, 316 | "engines": { 317 | "node": ">=10" 318 | }, 319 | "funding": { 320 | "url": "https://github.com/chalk/chalk?sponsor=1" 321 | } 322 | }, 323 | "node_modules/color-convert": { 324 | "version": "2.0.1", 325 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 326 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 327 | "dev": true, 328 | "dependencies": { 329 | "color-name": "~1.1.4" 330 | }, 331 | "engines": { 332 | "node": ">=7.0.0" 333 | } 334 | }, 335 | "node_modules/color-name": { 336 | "version": "1.1.4", 337 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 338 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 339 | "dev": true 340 | }, 341 | "node_modules/concat-map": { 342 | "version": "0.0.1", 343 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 344 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 345 | "dev": true 346 | }, 347 | "node_modules/cross-spawn": { 348 | "version": "7.0.6", 349 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 350 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 351 | "dev": true, 352 | "dependencies": { 353 | "path-key": "^3.1.0", 354 | "shebang-command": "^2.0.0", 355 | "which": "^2.0.1" 356 | }, 357 | "engines": { 358 | "node": ">= 8" 359 | } 360 | }, 361 | "node_modules/debug": { 362 | "version": "4.3.7", 363 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", 364 | "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", 365 | "dev": true, 366 | "dependencies": { 367 | "ms": "^2.1.3" 368 | }, 369 | "engines": { 370 | "node": ">=6.0" 371 | }, 372 | "peerDependenciesMeta": { 373 | "supports-color": { 374 | "optional": true 375 | } 376 | } 377 | }, 378 | "node_modules/deep-is": { 379 | "version": "0.1.4", 380 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 381 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 382 | "dev": true 383 | }, 384 | "node_modules/escape-string-regexp": { 385 | "version": "4.0.0", 386 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 387 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 388 | "dev": true, 389 | "engines": { 390 | "node": ">=10" 391 | }, 392 | "funding": { 393 | "url": "https://github.com/sponsors/sindresorhus" 394 | } 395 | }, 396 | "node_modules/eslint": { 397 | "version": "9.15.0", 398 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", 399 | "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", 400 | "dev": true, 401 | "dependencies": { 402 | "@eslint-community/eslint-utils": "^4.2.0", 403 | "@eslint-community/regexpp": "^4.12.1", 404 | "@eslint/config-array": "^0.19.0", 405 | "@eslint/core": "^0.9.0", 406 | "@eslint/eslintrc": "^3.2.0", 407 | "@eslint/js": "9.15.0", 408 | "@eslint/plugin-kit": "^0.2.3", 409 | "@humanfs/node": "^0.16.6", 410 | "@humanwhocodes/module-importer": "^1.0.1", 411 | "@humanwhocodes/retry": "^0.4.1", 412 | "@types/estree": "^1.0.6", 413 | "@types/json-schema": "^7.0.15", 414 | "ajv": "^6.12.4", 415 | "chalk": "^4.0.0", 416 | "cross-spawn": "^7.0.5", 417 | "debug": "^4.3.2", 418 | "escape-string-regexp": "^4.0.0", 419 | "eslint-scope": "^8.2.0", 420 | "eslint-visitor-keys": "^4.2.0", 421 | "espree": "^10.3.0", 422 | "esquery": "^1.5.0", 423 | "esutils": "^2.0.2", 424 | "fast-deep-equal": "^3.1.3", 425 | "file-entry-cache": "^8.0.0", 426 | "find-up": "^5.0.0", 427 | "glob-parent": "^6.0.2", 428 | "ignore": "^5.2.0", 429 | "imurmurhash": "^0.1.4", 430 | "is-glob": "^4.0.0", 431 | "json-stable-stringify-without-jsonify": "^1.0.1", 432 | "lodash.merge": "^4.6.2", 433 | "minimatch": "^3.1.2", 434 | "natural-compare": "^1.4.0", 435 | "optionator": "^0.9.3" 436 | }, 437 | "bin": { 438 | "eslint": "bin/eslint.js" 439 | }, 440 | "engines": { 441 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 442 | }, 443 | "funding": { 444 | "url": "https://eslint.org/donate" 445 | }, 446 | "peerDependencies": { 447 | "jiti": "*" 448 | }, 449 | "peerDependenciesMeta": { 450 | "jiti": { 451 | "optional": true 452 | } 453 | } 454 | }, 455 | "node_modules/eslint-scope": { 456 | "version": "8.2.0", 457 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", 458 | "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", 459 | "dev": true, 460 | "dependencies": { 461 | "esrecurse": "^4.3.0", 462 | "estraverse": "^5.2.0" 463 | }, 464 | "engines": { 465 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 466 | }, 467 | "funding": { 468 | "url": "https://opencollective.com/eslint" 469 | } 470 | }, 471 | "node_modules/eslint-visitor-keys": { 472 | "version": "4.2.0", 473 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 474 | "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", 475 | "dev": true, 476 | "engines": { 477 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 478 | }, 479 | "funding": { 480 | "url": "https://opencollective.com/eslint" 481 | } 482 | }, 483 | "node_modules/espree": { 484 | "version": "10.3.0", 485 | "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", 486 | "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", 487 | "dev": true, 488 | "dependencies": { 489 | "acorn": "^8.14.0", 490 | "acorn-jsx": "^5.3.2", 491 | "eslint-visitor-keys": "^4.2.0" 492 | }, 493 | "engines": { 494 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 495 | }, 496 | "funding": { 497 | "url": "https://opencollective.com/eslint" 498 | } 499 | }, 500 | "node_modules/esquery": { 501 | "version": "1.6.0", 502 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 503 | "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 504 | "dev": true, 505 | "dependencies": { 506 | "estraverse": "^5.1.0" 507 | }, 508 | "engines": { 509 | "node": ">=0.10" 510 | } 511 | }, 512 | "node_modules/esrecurse": { 513 | "version": "4.3.0", 514 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 515 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 516 | "dev": true, 517 | "dependencies": { 518 | "estraverse": "^5.2.0" 519 | }, 520 | "engines": { 521 | "node": ">=4.0" 522 | } 523 | }, 524 | "node_modules/estraverse": { 525 | "version": "5.3.0", 526 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 527 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 528 | "dev": true, 529 | "engines": { 530 | "node": ">=4.0" 531 | } 532 | }, 533 | "node_modules/esutils": { 534 | "version": "2.0.3", 535 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 536 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 537 | "dev": true, 538 | "engines": { 539 | "node": ">=0.10.0" 540 | } 541 | }, 542 | "node_modules/fast-deep-equal": { 543 | "version": "3.1.3", 544 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 545 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 546 | "dev": true 547 | }, 548 | "node_modules/fast-json-stable-stringify": { 549 | "version": "2.1.0", 550 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 551 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 552 | "dev": true 553 | }, 554 | "node_modules/fast-levenshtein": { 555 | "version": "2.0.6", 556 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 557 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 558 | "dev": true 559 | }, 560 | "node_modules/file-entry-cache": { 561 | "version": "8.0.0", 562 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 563 | "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 564 | "dev": true, 565 | "dependencies": { 566 | "flat-cache": "^4.0.0" 567 | }, 568 | "engines": { 569 | "node": ">=16.0.0" 570 | } 571 | }, 572 | "node_modules/find-up": { 573 | "version": "5.0.0", 574 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 575 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 576 | "dev": true, 577 | "dependencies": { 578 | "locate-path": "^6.0.0", 579 | "path-exists": "^4.0.0" 580 | }, 581 | "engines": { 582 | "node": ">=10" 583 | }, 584 | "funding": { 585 | "url": "https://github.com/sponsors/sindresorhus" 586 | } 587 | }, 588 | "node_modules/flat-cache": { 589 | "version": "4.0.1", 590 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 591 | "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 592 | "dev": true, 593 | "dependencies": { 594 | "flatted": "^3.2.9", 595 | "keyv": "^4.5.4" 596 | }, 597 | "engines": { 598 | "node": ">=16" 599 | } 600 | }, 601 | "node_modules/flatted": { 602 | "version": "3.3.2", 603 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", 604 | "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", 605 | "dev": true 606 | }, 607 | "node_modules/glob-parent": { 608 | "version": "6.0.2", 609 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 610 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 611 | "dev": true, 612 | "dependencies": { 613 | "is-glob": "^4.0.3" 614 | }, 615 | "engines": { 616 | "node": ">=10.13.0" 617 | } 618 | }, 619 | "node_modules/globals": { 620 | "version": "14.0.0", 621 | "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 622 | "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 623 | "dev": true, 624 | "engines": { 625 | "node": ">=18" 626 | }, 627 | "funding": { 628 | "url": "https://github.com/sponsors/sindresorhus" 629 | } 630 | }, 631 | "node_modules/has-flag": { 632 | "version": "4.0.0", 633 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 634 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 635 | "dev": true, 636 | "engines": { 637 | "node": ">=8" 638 | } 639 | }, 640 | "node_modules/ignore": { 641 | "version": "5.3.2", 642 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 643 | "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 644 | "dev": true, 645 | "engines": { 646 | "node": ">= 4" 647 | } 648 | }, 649 | "node_modules/import-fresh": { 650 | "version": "3.3.0", 651 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 652 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 653 | "dev": true, 654 | "dependencies": { 655 | "parent-module": "^1.0.0", 656 | "resolve-from": "^4.0.0" 657 | }, 658 | "engines": { 659 | "node": ">=6" 660 | }, 661 | "funding": { 662 | "url": "https://github.com/sponsors/sindresorhus" 663 | } 664 | }, 665 | "node_modules/imurmurhash": { 666 | "version": "0.1.4", 667 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 668 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 669 | "dev": true, 670 | "engines": { 671 | "node": ">=0.8.19" 672 | } 673 | }, 674 | "node_modules/is-extglob": { 675 | "version": "2.1.1", 676 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 677 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 678 | "dev": true, 679 | "engines": { 680 | "node": ">=0.10.0" 681 | } 682 | }, 683 | "node_modules/is-glob": { 684 | "version": "4.0.3", 685 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 686 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 687 | "dev": true, 688 | "dependencies": { 689 | "is-extglob": "^2.1.1" 690 | }, 691 | "engines": { 692 | "node": ">=0.10.0" 693 | } 694 | }, 695 | "node_modules/isexe": { 696 | "version": "2.0.0", 697 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 698 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 699 | "dev": true 700 | }, 701 | "node_modules/js-yaml": { 702 | "version": "4.1.0", 703 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 704 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 705 | "dev": true, 706 | "dependencies": { 707 | "argparse": "^2.0.1" 708 | }, 709 | "bin": { 710 | "js-yaml": "bin/js-yaml.js" 711 | } 712 | }, 713 | "node_modules/json-buffer": { 714 | "version": "3.0.1", 715 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 716 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 717 | "dev": true 718 | }, 719 | "node_modules/json-schema-traverse": { 720 | "version": "0.4.1", 721 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 722 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 723 | "dev": true 724 | }, 725 | "node_modules/json-stable-stringify-without-jsonify": { 726 | "version": "1.0.1", 727 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 728 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 729 | "dev": true 730 | }, 731 | "node_modules/keyv": { 732 | "version": "4.5.4", 733 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 734 | "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 735 | "dev": true, 736 | "dependencies": { 737 | "json-buffer": "3.0.1" 738 | } 739 | }, 740 | "node_modules/levn": { 741 | "version": "0.4.1", 742 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 743 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 744 | "dev": true, 745 | "dependencies": { 746 | "prelude-ls": "^1.2.1", 747 | "type-check": "~0.4.0" 748 | }, 749 | "engines": { 750 | "node": ">= 0.8.0" 751 | } 752 | }, 753 | "node_modules/locate-path": { 754 | "version": "6.0.0", 755 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 756 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 757 | "dev": true, 758 | "dependencies": { 759 | "p-locate": "^5.0.0" 760 | }, 761 | "engines": { 762 | "node": ">=10" 763 | }, 764 | "funding": { 765 | "url": "https://github.com/sponsors/sindresorhus" 766 | } 767 | }, 768 | "node_modules/lodash.merge": { 769 | "version": "4.6.2", 770 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 771 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 772 | "dev": true 773 | }, 774 | "node_modules/minimatch": { 775 | "version": "3.1.2", 776 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 777 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 778 | "dev": true, 779 | "dependencies": { 780 | "brace-expansion": "^1.1.7" 781 | }, 782 | "engines": { 783 | "node": "*" 784 | } 785 | }, 786 | "node_modules/ms": { 787 | "version": "2.1.3", 788 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 789 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 790 | "dev": true 791 | }, 792 | "node_modules/natural-compare": { 793 | "version": "1.4.0", 794 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 795 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 796 | "dev": true 797 | }, 798 | "node_modules/optionator": { 799 | "version": "0.9.4", 800 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 801 | "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 802 | "dev": true, 803 | "dependencies": { 804 | "deep-is": "^0.1.3", 805 | "fast-levenshtein": "^2.0.6", 806 | "levn": "^0.4.1", 807 | "prelude-ls": "^1.2.1", 808 | "type-check": "^0.4.0", 809 | "word-wrap": "^1.2.5" 810 | }, 811 | "engines": { 812 | "node": ">= 0.8.0" 813 | } 814 | }, 815 | "node_modules/p-limit": { 816 | "version": "3.1.0", 817 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 818 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 819 | "dev": true, 820 | "dependencies": { 821 | "yocto-queue": "^0.1.0" 822 | }, 823 | "engines": { 824 | "node": ">=10" 825 | }, 826 | "funding": { 827 | "url": "https://github.com/sponsors/sindresorhus" 828 | } 829 | }, 830 | "node_modules/p-locate": { 831 | "version": "5.0.0", 832 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 833 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 834 | "dev": true, 835 | "dependencies": { 836 | "p-limit": "^3.0.2" 837 | }, 838 | "engines": { 839 | "node": ">=10" 840 | }, 841 | "funding": { 842 | "url": "https://github.com/sponsors/sindresorhus" 843 | } 844 | }, 845 | "node_modules/parent-module": { 846 | "version": "1.0.1", 847 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 848 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 849 | "dev": true, 850 | "dependencies": { 851 | "callsites": "^3.0.0" 852 | }, 853 | "engines": { 854 | "node": ">=6" 855 | } 856 | }, 857 | "node_modules/path-exists": { 858 | "version": "4.0.0", 859 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 860 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 861 | "dev": true, 862 | "engines": { 863 | "node": ">=8" 864 | } 865 | }, 866 | "node_modules/path-key": { 867 | "version": "3.1.1", 868 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 869 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 870 | "dev": true, 871 | "engines": { 872 | "node": ">=8" 873 | } 874 | }, 875 | "node_modules/prelude-ls": { 876 | "version": "1.2.1", 877 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 878 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 879 | "dev": true, 880 | "engines": { 881 | "node": ">= 0.8.0" 882 | } 883 | }, 884 | "node_modules/punycode": { 885 | "version": "2.3.1", 886 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 887 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 888 | "dev": true, 889 | "engines": { 890 | "node": ">=6" 891 | } 892 | }, 893 | "node_modules/resolve-from": { 894 | "version": "4.0.0", 895 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 896 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 897 | "dev": true, 898 | "engines": { 899 | "node": ">=4" 900 | } 901 | }, 902 | "node_modules/shebang-command": { 903 | "version": "2.0.0", 904 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 905 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 906 | "dev": true, 907 | "dependencies": { 908 | "shebang-regex": "^3.0.0" 909 | }, 910 | "engines": { 911 | "node": ">=8" 912 | } 913 | }, 914 | "node_modules/shebang-regex": { 915 | "version": "3.0.0", 916 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 917 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 918 | "dev": true, 919 | "engines": { 920 | "node": ">=8" 921 | } 922 | }, 923 | "node_modules/strip-json-comments": { 924 | "version": "3.1.1", 925 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 926 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 927 | "dev": true, 928 | "engines": { 929 | "node": ">=8" 930 | }, 931 | "funding": { 932 | "url": "https://github.com/sponsors/sindresorhus" 933 | } 934 | }, 935 | "node_modules/supports-color": { 936 | "version": "7.2.0", 937 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 938 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 939 | "dev": true, 940 | "dependencies": { 941 | "has-flag": "^4.0.0" 942 | }, 943 | "engines": { 944 | "node": ">=8" 945 | } 946 | }, 947 | "node_modules/tshet-uinh": { 948 | "version": "0.15.1", 949 | "resolved": "https://registry.npmjs.org/tshet-uinh/-/tshet-uinh-0.15.1.tgz", 950 | "integrity": "sha512-5RN8wfwkxLfyv+PVDmPYacvBJ9tCGbeG6AH7UPqiculLOJ7FnQLqSIVmtFehP6o1275+8s3Rgia5Hu5kNuXsMQ==", 951 | "license": "MIT", 952 | "engines": { 953 | "node": "^18.18 || ^20.9 || ^21 || >=22" 954 | } 955 | }, 956 | "node_modules/tshet-uinh-deriver-tools": { 957 | "version": "0.2.0", 958 | "resolved": "https://registry.npmjs.org/tshet-uinh-deriver-tools/-/tshet-uinh-deriver-tools-0.2.0.tgz", 959 | "integrity": "sha512-r4MKw5fmezav00yGDzJkvfwvGnoKPPMmgjJtICFzKSULqUgKLaQQOGieor2Ts48NKcR+0sqOmW3ne2gTjVJa5w==", 960 | "license": "MIT", 961 | "peerDependencies": { 962 | "tshet-uinh": "^0.15.0" 963 | } 964 | }, 965 | "node_modules/type-check": { 966 | "version": "0.4.0", 967 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 968 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 969 | "dev": true, 970 | "dependencies": { 971 | "prelude-ls": "^1.2.1" 972 | }, 973 | "engines": { 974 | "node": ">= 0.8.0" 975 | } 976 | }, 977 | "node_modules/uri-js": { 978 | "version": "4.4.1", 979 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 980 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 981 | "dev": true, 982 | "dependencies": { 983 | "punycode": "^2.1.0" 984 | } 985 | }, 986 | "node_modules/which": { 987 | "version": "2.0.2", 988 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 989 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 990 | "dev": true, 991 | "dependencies": { 992 | "isexe": "^2.0.0" 993 | }, 994 | "bin": { 995 | "node-which": "bin/node-which" 996 | }, 997 | "engines": { 998 | "node": ">= 8" 999 | } 1000 | }, 1001 | "node_modules/word-wrap": { 1002 | "version": "1.2.5", 1003 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 1004 | "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 1005 | "dev": true, 1006 | "engines": { 1007 | "node": ">=0.10.0" 1008 | } 1009 | }, 1010 | "node_modules/yocto-queue": { 1011 | "version": "0.1.0", 1012 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1013 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1014 | "dev": true, 1015 | "engines": { 1016 | "node": ">=10" 1017 | }, 1018 | "funding": { 1019 | "url": "https://github.com/sponsors/sindresorhus" 1020 | } 1021 | } 1022 | } 1023 | } 1024 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tshet-uinh-examples", 3 | "version": "20241008.0.0", 4 | "description": "JavaScript code examples to generate the derivatives of the Qieyun phonological system using qieyun-js", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "default": "./dist/index.js", 11 | "types": "./dist/index.d.ts" 12 | } 13 | }, 14 | "files": ["dist"], 15 | "scripts": { 16 | "lint": "eslint . --ignore-pattern dist/**/*", 17 | "lint:fix": "eslint --fix . --ignore-pattern dist/**/*", 18 | "build": "node build/main.js", 19 | "test": "node test/main.js" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/nk2028/tshet-uinh-examples.git" 24 | }, 25 | "keywords": [ 26 | "historical-linguistics", 27 | "middle-chinese", 28 | "qieyun" 29 | ], 30 | "author": "nk2028", 31 | "license": "CC0-1.0", 32 | "bugs": { 33 | "url": "https://github.com/nk2028/tshet-uinh-examples/issues" 34 | }, 35 | "homepage": "https://github.com/nk2028/tshet-uinh-examples#readme", 36 | "dependencies": { 37 | "tshet-uinh": "^0.15.1", 38 | "tshet-uinh-deriver-tools": "^0.2.0" 39 | }, 40 | "devDependencies": { 41 | "@stylistic/eslint-plugin-js": "^2.11.0", 42 | "eslint": "^9.15.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /panwuyun.js: -------------------------------------------------------------------------------- 1 | /* 潘悟雲擬音 2 | * 3 | * 3 個版本: 4 | * 5 | * - 潘悟雲. 2000. 漢語歷史音韻學. 上海: 上海教育出版社. 6 | * - 潘悟雲 & 張洪明. 2013. 漢語中古音. 語言研究 33(2), 1–7. 7 | * - 潘悟雲. 2023. 漢語古音手冊. 上海: 中西書局. 8 | * 9 | * @author unt 10 | */ 11 | 12 | /** @type { 音韻地位['屬於'] } */ 13 | const is = (...x) => 音韻地位.屬於(...x); 14 | /** @type { 音韻地位['判斷'] } */ 15 | const when = (...x) => 音韻地位.判斷(...x); 16 | 17 | const is2000 = Boolean(選項.版本?.includes('2000')); 18 | const is2013 = Boolean(選項.版本?.includes('2013')); 19 | const is2023 = 選項.版本?.includes('2023') ?? true; 20 | const 三C介音 = 選項.非前三等介音 ? 選項.非前三等介音.split('(')[0] : 'i'; 21 | 22 | if (!音韻地位) return [ 23 | ['版本', [3, 24 | '2000:漢語歷史音韻學', 25 | '2013:漢語中古音', 26 | '2023:漢語古音手冊', 27 | ], { 28 | description: 29 | is2000 && '《漢語歷史音韻學》勘誤\n生母作 ʃ,係誤植,應爲 ʂ。在推導方案中已更正' || 30 | is2013 && '《漢語中古音》勘誤\n(1)\u2002從母聲母表作 ʣ,係排版錯誤,此處依正文作 dz\n(2)\u2002俟母聲母表未列,此處補上,爲 ʐ' || 31 | is2023 && '《漢語古音手冊》(第一版)勘誤\n(1)\u2002莊組拼重紐三等韻未加 ɨ 介音,再版將加上\n(2)\u2002燭韻作 i̯ʊk,附加符號多餘,再版將去除\n(3)\u2002微韻作 ɤi,遺漏 i 介音,再版將加上\n以上在推導方案中已更正', 32 | }], 33 | ['非前三等介音', [1, 'i(原書簡寫)', 'ɨ(實際音值)']], 34 | ['聲調記號', [2, '隱藏', '五度符號', '調值數字'], { 35 | description: 36 | is2000 && '《漢語歷史音韻學》未給具體調值,此處依《漢語中古音》(2013)調值' || 37 | is2023 && '《漢語古音手冊》未給出調值,此處依《漢語中古音》(2013)調值' || '', 38 | }], 39 | ['送氣記號', [1, 'ʰ(通用)', 'h(原書)'], { hidden: !is2000 }], 40 | ['支韻', [1, 'iɛ(簡寫)', 'iᵉ(實際音值)'], { hidden: !is2000 }], 41 | ['虞韻', [2, 'io(簡寫)', 'iʊ(實際音值)'], { hidden: !is2000 }], 42 | ]; 43 | /* 韻典網與本方案 2000 擬音不同之處: 44 | * 45 | * - 歌一合誤作 uɑ,應爲 ʷɑ 46 | * - 部分祭合、薛合誤作 iei、iet,應爲 iɛi、iɛt 47 | * - 幫組陽韻歸合口。原書雖未指明,但暗示爲開口;本方案歸開口 48 | * - 送氣記號改作 ʰ,原書作 h;本方案可自選 49 | * - 支韻、虞韻作 iɛ、io,原書韻母擬音比較表作 iᵉ、iʊ;本方案可自選 50 | */ 51 | 52 | function get聲母() { 53 | let 聲母 = { 54 | 幫: 'p', 滂: 'pʰ', 並: 'b', 明: 'm', 55 | 端: 't', 透: 'tʰ', 定: 'd', 泥: 'n', 來: 'l', 56 | 知: 'ʈ', 徹: 'ʈʰ', 澄: 'ɖ', 孃: 'ɳ', 57 | 見: 'k', 溪: 'kʰ', 羣: 'ɡ', 疑: 'ŋ', 58 | 影: 'ʔ', 曉: 'h', 匣: 'ɦ', 云: is2023 ? 'ɦᶤ' : 'ɦ', 59 | 精: 'ts', 清: 'tsʰ', 從: 'dz', 心: 's', 邪: 'z', 60 | 莊: 'tʂ', 初: 'tʂʰ', 崇: 'dʐ', 生: 'ʂ', 俟: 'ʐ', 61 | 章: 'tɕ', 昌: 'tɕʰ', 常: 'dʑ', 書: 'ɕ', 船: 'ʑ', 日: 'ȵ', 以: 'j', 62 | }[音韻地位.母]; 63 | if (is2000) 聲母 = 聲母.replace('ʰ', 選項.送氣記號[0]); 64 | return 聲母; 65 | } 66 | 67 | function get韻母() { 68 | const 韻 = 音韻地位.韻.replace('凡', '嚴'); 69 | const 元音表 = { 70 | ɪ: '   臻  ', 71 | i: '脂 侵真 幽', ɨ: '之蒸 殷微尤', u: '侯東 文  ', 72 | e: ' 青添先齊蕭', ə: ' 登覃痕咍 ', o: '模冬 魂灰 ', 73 | ᴇ: '支清鹽仙祭宵', ɤ: '魚  元  ', ʊ: '虞鍾    ', 74 | ɛ: '佳耕咸山皆 ', a: ' 陽嚴 廢 ', ɔ: ' 江    ', 75 | æ: '麻庚銜刪夬肴', ɑ: '歌唐談寒泰豪', 76 | }; 77 | const 韻尾列表 = [''].concat(is`舒聲` ? [...'ŋmniu'] : [...'kpt']); 78 | let 韻核 = Object.keys(元音表).find(e => 元音表[e].includes(韻)); 79 | let 韻尾 = 韻尾列表[元音表[韻核].indexOf(韻)]; 80 | 81 | let 介音 = ''; 82 | if (is`合口` && ![...'mpu'].includes(韻尾) && ![...'uoʊɔ'].includes(韻核)) 83 | 介音 += 'ʷ'; 84 | if (is`幫組` && ![...'ŋkmpu'].includes(韻尾) && [...'ɨəɤaɑ'].includes(韻核) && !is`泰韻` || !is2023 && is`凡韻`) 85 | 介音 += is2013 ? 'u̯' : 'ʷ'; 86 | // 云母 B 類不寫介音 87 | // 《漢語歷史音韻學》韻母擬音比較表庚三直接寫作 B 類,但此處拼銳音和云母時不加介音 88 | // 《漢語中古音》《漢語古音手冊》庚三作無重紐三等,此處暫歸 C 類 89 | if (!is2000 && is`庚韻 三等`) { 90 | 介音 += 三C介音; 91 | } else { 92 | if (is`二等 或 B類 非 蒸幽韻 非 云母` || is2023 && [...'ɪiᴇæ'].includes(韻核) && is`莊組 三等 非 庚韻`) 93 | 介音 += is2000 ? 'ɯ' : is2013 ? 'ɣ' : is`二等` ? 'ᵚ' : 'ɨ'; 94 | if (is`三等` && ![...'ɪiɨ'].includes(韻核) && !介音.includes('ɨ') || is`端組 麻庚清韻 四等`) 95 | 介音 += [...'ᴇæ'].includes(韻核) ? 'i' : 三C介音; 96 | } 97 | if ([...'oʊ'].includes(韻核)) 98 | 介音 += is2013 ? 'u̯' : 99 | 介音 ? '' : is2000 ? 'u' : 'u̯'; 100 | if (is2023 && is`凡韻`) 101 | 介音 += 'u̯'; 102 | 103 | // 韻核相關調整 104 | if (is2000) { 105 | const 韻核鏈移列表 = [...'ᴇɛæaɐ']; // “鏈移”只是比喻 106 | if (韻核鏈移列表.includes(韻核)) 韻核 = 韻核鏈移列表[韻核鏈移列表.indexOf(韻核) + 1]; 107 | 108 | 韻核 = { 109 | 尤: 'i', 幽: 'ɨ', 侯: 'əu', 110 | 支: 選項.支韻[1], 魚: 'ɔ', 虞: 選項.虞韻[1], 111 | 元: 'ɐ', 鍾: 'o', 112 | }[音韻地位.韻] ?? 韻核; 113 | } else if (is2013) { 114 | 韻核 = 韻核.replace('ʊ', 'o̝'); 115 | } else { // is2023 116 | 韻核 = { 117 | 尤: 'i', 幽: 'ɨ', 微: 'ɤ', 118 | 支: 'e', 119 | }[音韻地位.韻] ?? 韻核; 120 | if (is`微韻`) 介音 += 三C介音; 121 | } 122 | return 介音 + 韻核 + 韻尾; 123 | } 124 | 125 | function get聲調() { 126 | return 選項.聲調記號 === '隱藏' ? '' : { 127 | '五度符號': ['˧', '˧˥', '˥˩', '꜊'], 128 | '調值數字': ['³³', '³⁵', '⁵¹', '³'], 129 | }[選項.聲調記號]['平上去入'.indexOf(音韻地位.聲)]; 130 | } 131 | 132 | return get聲母() + get韻母() + get聲調(); 133 | -------------------------------------------------------------------------------- /position.js: -------------------------------------------------------------------------------- 1 | /* 音韻地位 2 | * 3 | * @author unt 4 | */ 5 | 6 | const is = (...x) => 音韻地位.屬於(...x); 7 | const when = (...x) => 音韻地位.判斷(...x); 8 | 9 | const sextupleKeys = ['母', '呼', '等', '類', '韻系', '聲']; 10 | if (選項._六元組全選) sextupleKeys.forEach(k => 選項[k] = true); 11 | if (選項._六元組全不選) sextupleKeys.forEach(k => 選項[k] = false); 12 | const keys = Object.keys(選項).filter(k => !k.startsWith('_')); 13 | const checkedKeys = keys.filter(k => 選項[k]); 14 | const checkedSextupleKeys = checkedKeys.filter(k => sextupleKeys.includes(k)); 15 | 16 | if (!音韻地位) return [ 17 | '整體', 18 | [`描述|音韻地位描述 19 | 聲母、呼、等、類、韻系、聲調 20 | 韻系用字依《切韻》,舉平以賅上去入`, true], 21 | ['簡略描述|音韻地位簡略描述\n不推薦', false], 22 | '單項', 23 | ['母|聲母', false], 24 | ['呼', false], 25 | ['等\n切韻等\n端組拼三等韻(爹小韻、地小韻)歸四等', false], 26 | ['類', false], 27 | ['韻系', false], 28 | ['聲|聲調', false], 29 | ['_六元組全選|六項全選', false, { hidden: checkedSextupleKeys.length === 6, reset: true }], 30 | ['_六元組全不選|六項全不選', false, { hidden: checkedSextupleKeys.length === 0, reset: true }], 31 | '更多', 32 | ['聲類|聲類\n五十一聲類 + 俟母\n來母二等歸盧類;也有人將其歸力類', false], 33 | ['組|聲母組', false], 34 | ['音|五音', false], 35 | ['清濁\n曉母歸全清;也有人將其歸次清', false], 36 | ['韻目|韻目\n爲理論韻目,非韻書中的韻目原貌', false], 37 | ['韻別|韻母陰陽入', false], 38 | ['表達式|音韻地位表達式', false], 39 | '對應韻圖音系相關屬性', 40 | ['字母\n三十六字母', false], 41 | ['韻圖等', false], 42 | ['攝', false], 43 | [`字彙描述|音韻地位描述(《漢語方音字彙》格式) 44 | 攝、呼、等、聲調、韻目、聲母 45 | 重紐第二類(即三等 A 類)在左上角加黑點表示 46 | 參照《字彙》1962 第一版、1989 第二版、2003 第二版重排本。呼依《字彙》。韻目用字依《廣韻》(個別字形與《字彙》不同;另《字彙》欣韻作殷韻,不取)。幫組 C 類、孃母、常母依《字彙》作非組、泥母、禅母,俟母依《方言調查字表》(第 18 頁)併入崇母`, false], 47 | '', 48 | [`_韻風格|韻系風格 49 | 舉平以賅上去入適合切韻音系、韻圖音系 50 | 舉平以賅上去適合宋代及之後音系`, [1, '舉平以賅上去入', '舉平以賅上去'], { hidden: !選項.韻系 }], 51 | [`_韻用字| 52 | 《切韻》韻目 → 《廣韻》韻目 53 | • 真軫震質 → 真軫震質(開口、脣音、B 類及莊組)、諄準稕術(A 類合口及莊組以外舌齒音合口) 54 | • 殷 → 欣 55 | • 佷 → 很[痕韻上聲] 56 | • 寒旱翰末 → 寒旱翰曷(開口)、桓緩換末(其他) 57 | • 敬 → 映[庚韻去聲] 58 | • 歌哿箇 → 歌哿箇(一等開口)、戈果過(其他) 59 | • [嚴韻上去聲] → 儼釅 60 | (存在少許例外)`, [1, '依《切韻》', '依《廣韻》'], { text: ['韻系', '韻目'].filter(e => 選項[e]).join('、') + '用字', hidden: !選項.韻系 && !選項.韻目 }], 61 | ['_分隔符|多個屬性間的連接符', ',', { hidden: checkedKeys.length < 2 }], 62 | ]; 63 | 64 | const 聲母to聲類列表 = { 65 | 幫: '方博', 滂: '芳普', 並: '符蒲', 明: '武莫', 66 | 端: ' 都', 透: ' 他', 定: ' 徒', 泥: ' 奴', 67 | 來: '力盧', 68 | 見: '居古', 溪: '去苦', 羣: '渠 ', 疑: '魚五', 69 | 影: '於烏', 曉: '許呼', 匣: ' 胡', 云: '于 ', 70 | 71 | 精: '子作', 清: '七倉', 從: '疾昨', 心: '息蘇', 邪: '徐 ', 72 | 73 | 知: '陟', 徹: '丑', 澄: '直', 孃: '女', 74 | 莊: '側', 初: '初', 崇: '士', 生: '所', 俟: '俟', 75 | 章: '之', 昌: '昌', 常: '食', 書: '式', 船: '時', 日: '而', 以: '以', 76 | }; 77 | 78 | const 韻to切韻韻目列表 = Object.fromEntries([ 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 | ].map(切韻韻目列表 => [切韻韻目列表.trim(' ')[0], 切韻韻目列表])); 138 | 139 | function get聲類() { 140 | let 聲類列表 = 聲母to聲類列表[音韻地位.母].trim(' '); 141 | return is`三等` ? 聲類列表[0] : 聲類列表.slice(-1); 142 | } 143 | 144 | function get韻系(is廣韻 = 選項._韻用字 === '依《廣韻》', 入聲獨立 = !選項._韻風格.includes('入')) { 145 | return when([ 146 | [入聲獨立 && '入聲', [ 147 | [is廣韻, [ 148 | ['術', '真韻 合口 非 B類 非 莊組'], 149 | ['曷', '寒韻 開口 入聲'], 150 | ].map(([a, b]) => [b, a])], 151 | ['', 韻to切韻韻目列表[音韻地位.韻][3]], 152 | ]], 153 | ['', [ 154 | [is廣韻, [ 155 | ['諄', '真韻 合口 非 B類 非 莊組'], 156 | ['欣', '殷韻'], 157 | ['桓', '寒韻 非 開口'], 158 | ['戈', '歌韻 非 (一等 開口)'], 159 | ].map(([a, b]) => [b, a])], 160 | ['', 音韻地位.韻], 161 | ]] 162 | ], '', true); 163 | } 164 | 165 | function get韻目(is廣韻 = 選項._韻用字 === '《廣韻》') { 166 | return when([ 167 | [is廣韻, [ 168 | ['諄準稕術', '真韻 合口 非 B類 非 莊組'], 169 | ['欣   ', '殷韻 平聲'], 170 | [' 很  ', '痕韻 上聲'], 171 | ['   曷', '寒韻 開口 入聲'], 172 | ['桓緩換 ', '寒韻 非 開口 舒聲'], 173 | ['  映 ', '庚韻 去聲'], 174 | ['戈果過 ', '歌韻 非 (一等 開口)'], 175 | [' 儼釅 ', '嚴韻 上去聲'], 176 | ].map(([a, b]) => [b, a])], 177 | ['', 韻to切韻韻目列表[when([ 178 | ['祭韻 非 去聲', '齊'], 179 | ['廢韻 非 去聲', [ 180 | ['開口', '咍'], 181 | ['', '灰'], 182 | ]], 183 | ['', 音韻地位.韻], 184 | ])]], 185 | ], '', true)['平上去入'.indexOf(音韻地位.聲)]; 186 | } 187 | 188 | function get字彙描述() { 189 | let { 攝, 呼, 等, 聲, 母 } = 音韻地位; 190 | let 韻 = get韻目(true); 191 | let 重紐第二類標記 = is`A類` ? '˙' : ''; 192 | 呼 = when([ 193 | ['通遇攝', '合'], 194 | ['江流攝', '開'], 195 | ['幫組', [ 196 | // 脣音咍韻在《字彙》《方言調查字表》中實已改作灰韻,與 nk2028 資料一致 197 | ['C類 或 一等 非 登唐咍泰豪覃談韻', '合'], 198 | ['', '開'], 199 | ]], 200 | ['', 呼], 201 | ]); 202 | 母 = when([ 203 | ['幫組 C類', 音韻地位.字母], 204 | ['孃母', '泥'], 205 | ['俟母', '崇'], 206 | ['常母', '禅'], 207 | ['', 母], 208 | ]); 209 | return [重紐第二類標記, 攝, 呼, 等, 聲, 韻, 母].join(''); 210 | } 211 | 212 | const 補充屬性 = { 213 | 聲類: get聲類(), 214 | 韻系: get韻系(), 215 | 韻目: get韻目(), 216 | 字彙描述: get字彙描述(), 217 | }; 218 | 219 | return checkedKeys.map(k => 音韻地位[k] ?? 補充屬性[k]).join(選項._分隔符); 220 | -------------------------------------------------------------------------------- /putonghua.js: -------------------------------------------------------------------------------- 1 | /* 推導普通話 2 | * 3 | * @author graphemecluster 4 | * @author JwietPuj-Drin 5 | * @author SyiMyuZya 6 | * 7 | * 選項「清聲母入聲調分派層次」詳見平山久雄《中古汉语的清入声在北京话里的对应规律》 8 | * http://ccj.pku.edu.cn/Article/DownLoad?id=271015083&&type=ArticleFile 9 | * 默認選擇為「皆派入陰平」,参考刘海阳在對「北京话的入声为什么会派入三个不同的声调?」的回答中披露的材料—— 10 | * 「古代清音入声字在北京話的声調,凡是沒有异讀的,就采用北京已經通行的讀法。凡是有异讀的,假若其中有一个是陰平調,原則上就采用陰平,例如:“息”ㄒㄧ(xī)“击”ㄐㄧ(jī)。否則逐字考慮,采用比較通行……」 11 | * https://www.zhihu.com/question/30370012/answer/533234460 12 | * 13 | * 選項「常母平聲陰聲韻聲母和船母平聲聲母」詳見 unt 對「为何中古的dʑ ʑ和普通话读音的对应似乎是反的(即dʑ > ʂ、ʑ > ʈʂ)?」的回答 14 | * https://www.zhihu.com/question/526195183/answer/2425807330 15 | */ 16 | 17 | /** @type { 音韻地位['屬於'] } */ 18 | const is = (...x) => 音韻地位.屬於(...x); 19 | /** @type { 音韻地位['判斷'] } */ 20 | const when = (...x) => 音韻地位.判斷(...x); 21 | 22 | const is更多選項 = 選項.更多選項 ?? false; 23 | 24 | if (!音韻地位) return [ 25 | ['標調方式', [2, '數字', '附標']], 26 | 27 | ['更多選項', is更多選項], 28 | ...(is更多選項 ? [ 29 | '更多選項', 30 | ['清聲母入聲調分派層次', 31 | [2, 32 | '皆派入上聲', 33 | '皆派入陰平', 34 | '次清、擦音和零聲母字派入去聲,其餘派入陽平', 35 | '次清和零聲母字派入去聲,其餘派入陽平', 36 | '皆不標調', 37 | '連同濁聲母,所有入聲字皆派入去聲', 38 | ] 39 | ], 40 | ['常母平聲陰聲韻聲母和船母平聲聲母', [2, 'ch', 'sh']], 41 | ] : []), 42 | ]; 43 | 44 | // 預調整(中古中期通語已產生的不同) 45 | if (is`明母 尤東韻`) 音韻地位 = 音韻地位.調整(`${音韻地位.韻 === '尤' ? '侯' : 音韻地位.韻}韻 一等 不分類`); 46 | if (is`云母 通攝 舒聲`) 音韻地位 = 音韻地位.調整('匣母', ['匣母三等']); 47 | // TODO 蟹攝入假攝、流攝入遇攝等 48 | 49 | // j、q、x 不列於聲母規則,之後會由「拼細音」條件得出 50 | const 聲母規則 = () => when([ 51 | ['幫滂並母 C類', 'f'], 52 | ['幫母', 'b'], 53 | ['滂母', 'p'], 54 | ['並母', [['平聲', 'p'], ['', 'b']]], 55 | ['明母', [['C類', 'w'], ['', 'm']]], 56 | 57 | ['端母', 'd'], 58 | ['透母', 't'], 59 | ['定母', [['平聲', 't'], ['', 'd']]], 60 | ['泥孃母', 'n'], 61 | ['來母', 'l'], 62 | 63 | ['精母', 'z'], 64 | ['清母', 'c'], 65 | ['從母', [['平聲', 'c'], ['', 'z']]], 66 | ['心邪母', 's'], 67 | 68 | ['知莊章母', 'zh'], 69 | ['徹初昌母', 'ch'], 70 | ['澄崇母', [['平聲', 'ch'], ['', 'zh']]], 71 | ['常母', [['平聲 陽聲韻', 'ch'], ['', 'sh']]], 72 | ['生書母', 'sh'], 73 | ['俟船母', 'sh'], 74 | ['日母', 'r'], 75 | 76 | ['見母', 'g'], 77 | ['溪母', 'k'], 78 | ['羣母', [['平聲', 'k'], ['', 'g']]], 79 | ['曉匣母', 'h'], 80 | ['以母 蟹攝 三四等 合口', 'r'], // 「銳」 81 | ['疑影云以母', ''], 82 | ], '無聲母規則'); 83 | 84 | // 韻母均按零聲母寫法,唯 y、w、yu 作 i、u、ü(例如用 uen、ueng 不用 un、ong),這是為了方便後面的拼寫處理。 85 | // 韻母規則不額外列出 zh、ch、sh、r、f、w 及部分 n、l 系統性地拼為洪音的情形,後面會處理。 86 | const 舒聲韻母規則 = () => when([ 87 | ['通攝', [['三四等 牙喉音', 'iong'], ['', 'ueng']]], 88 | 89 | ['止攝', [ 90 | ['合口', [['莊組', 'uai'], ['', 'uei']]], 91 | ['', 'er'], // 'er' 指示其拼日母時為 er,以及拼 z、c、s 時聲母不作 j、q、x,其餘情形均同 i 92 | ]], 93 | 94 | ['遇攝', [['三四等', 'ü'], ['', 'u']]], 95 | 96 | ['蟹攝', [ 97 | ['廢韻 平上聲 章組', [['合口', 'uai'], ['', 'ai']]], // 「茝」 98 | ['三四等', [['合口 莊組', 'uai'], ['合口', 'uei'], ['', 'i']]], 99 | // FIXME 蟹攝二等有古已轉入假攝者,依更早韻書推導時需判斷具體的字。目前暫僅列出個別可能存在系統性者 100 | ['二等 開口 溪影母', 'ai'], // 「矮隘楷揩」等 101 | ['佳韻 合口 牙喉音', 'ua'], // 「畫掛」等(FIXME 未覆蓋如「佳」「話」等,且過度覆蓋「拐」等,當依具體字而非音) 102 | ['佳韻 開口 疑母', 'ia'], // 「崖睚」等(FIXME 當依具體字而非音) 103 | ['二等', [ 104 | ['開口 牙喉音', 'ie'], 105 | ['合口', 'uai'], 106 | ['', 'ai']], 107 | ], 108 | ['一等', [['開口', 'ai'], ['', 'uei']]], 109 | ]], 110 | 111 | ['臻深攝', [ 112 | ['三四等', [['合口', 'ün'], ['', 'in']]], 113 | ['', [ 114 | ['合口 或 舌齒音', 'uen'], // 覆蓋「吞」 115 | ['', 'en'], 116 | ]], 117 | ]], 118 | 119 | ['山咸攝', [ 120 | ['三四等', [['合口', 'üan'], ['', 'ian']]], 121 | ['二等 牙喉音 開口', 'ian'], 122 | ['', [['合口', 'uan'], ['', 'an']]], 123 | ]], 124 | 125 | ['效攝', [ 126 | ['三四等 或 二等 牙喉音', 'iao'], 127 | ['', 'ao'], 128 | ]], 129 | 130 | ['果假攝', [ 131 | ['三四等', [['合口', 'üe'], ['', 'ie']]], 132 | ['二等', [['合口', 'ua'], ['牙喉音', 'ia'], ['', 'a']]], 133 | ['一等', [['開口 牙喉音', 'e'], ['', 'uo']]], 134 | ]], 135 | 136 | ['宕江攝', [ 137 | ['合口 或 莊組 或 二等 知組', 'uang'], 138 | ['三四等 或 二等 牙喉音', 'iang'], 139 | ['', 'ang'], 140 | ]], 141 | 142 | ['梗曾攝', [ 143 | ['三四等', [['合口', 'iong'], ['', 'ing']]], 144 | ['', [['合口', 'ueng'], ['', 'eng']]], 145 | ]], 146 | 147 | ['流攝', [ 148 | // FIXME 流攝脣音有古已轉入遇攝者,依更早韻書推導時需判斷具體的字。目前暫僅列出個別可能存在系統性者 149 | ['幽韻 幫滂並母', 'iao'], // 「彪髟淲」等 150 | ['尤韻 脣音 非 (幫母 上聲)', 'u'], // 排除「否缶」(FIXME 當依具體字而非音) 151 | ['三四等', 'iou'], 152 | ['', 'ou'], // FIXME 未覆蓋如「牡」「部」「矛」「茂」等,當依具體字而非音 153 | ]], 154 | ], '無韻母規則'); 155 | 156 | const 入聲韻母規則 = () => when([ 157 | ['通攝', [['三四等 牙喉音', 'ü'], ['', 'u']]], 158 | 159 | ['臻深攝', [ 160 | ['莊組', [['合口', 'uai'], ['', 'e']]], // TODO 「櫛」? 161 | ['三四等', [['合口 或 脣音 C類', 'ü'], ['', 'i']]], 162 | ['', [['脣音', 'o'], ['合口', 'u'], ['', 'e']]], 163 | ]], 164 | 165 | ['山咸攝', [ 166 | // TODO 「茁」 167 | ['二等 或 莊組 或 脣音 C類', [['合口', 'ua'], ['牙喉音', 'ia'], ['', 'a']]], 168 | ['三四等', [['合口', 'üe'], ['', 'ie']]], 169 | ['', [ 170 | ['開口', [['牙喉音', 'e'], ['', 'a']]], 171 | ['', 'uo'], 172 | ]], 173 | ]], 174 | 175 | ['宕江攝', [ 176 | // TODO 白讀 177 | ['三四等 或 二等 牙喉音', 'üe'], 178 | ['一等 開口 牙喉音', 'e'], 179 | ['', 'uo'], 180 | ]], 181 | 182 | ['梗曾攝', [ 183 | // TODO 白讀 184 | ['三四等 非 莊組', [['合口', 'ü'], ['', 'i']]], 185 | ['', [['開口', 'e'], ['', 'uo']]], 186 | ]], 187 | ], '無韻母規則'); 188 | 189 | const 聲調規則 = () => when([ 190 | ['清音', [ 191 | ['平聲', '1'], 192 | ['上聲', '3'], 193 | ['去聲', '4'], 194 | ['入聲', ''], 195 | ]], 196 | ['濁音', [ 197 | ['平聲', '2'], 198 | ['上聲', [['全濁', '4'], ['次濁', '3']]], 199 | ['去聲', '4'], 200 | ['入聲', [['全濁', '2'], ['次濁', '4']]], 201 | ]], 202 | ], '無聲調規則'); 203 | 204 | let 聲母 = 聲母規則(); 205 | let 韻母 = is`舒聲` ? 舒聲韻母規則() : 入聲韻母規則(); 206 | let 聲調 = 聲調規則(); 207 | 208 | if (選項.更多選項) { 209 | // 參考 https://www.zhihu.com/question/526195183/answer/2425807330 210 | if (is`(常母 陰聲韻 或 船母) 平聲`) 聲母 = 選項.常母平聲陰聲韻聲母和船母平聲聲母; 211 | 212 | /* 參考 213 | * http://ccj.pku.edu.cn/Article/DownLoad?id=271015083&&type=ArticleFile (https://web.archive.org/web/20240223084634/http://ccj.pku.edu.cn/Article/DownLoad?id=271015083&&type=ArticleFile) 214 | * https://www.zhihu.com/question/30370012/answer/533234460 215 | * https://www.zhihu.com/question/30370012/answer/535713330 216 | */ 217 | if (is`入聲`) { 218 | switch (選項.清聲母入聲調分派層次) { 219 | case '皆派入上聲': 220 | if (is`清音`) 聲調 = '3'; 221 | break; 222 | case '皆派入陰平': 223 | if (is`清音`) 聲調 = '1'; 224 | break; 225 | case '次清、擦音和零聲母字派入去聲,其餘派入陽平': 226 | if (is`心生書影曉母 或 次清`) 聲調 = '4'; 227 | else if (is`全清`) 聲調 = '2'; 228 | break; 229 | case '次清和零聲母字派入去聲,其餘派入陽平': 230 | if (is`影母 或 次清`) 聲調 = '4'; 231 | else if (is`全清`) 聲調 = '2'; 232 | break; 233 | case '連同濁聲母,所有入聲字皆派入去聲': 234 | 聲調 = '4'; 235 | break; 236 | } 237 | } 238 | } 239 | 240 | // j、q、x 聲母 241 | if (韻母 === 'er' || ['i', 'ü'].includes(韻母[0])) { 242 | 聲母 = { g: 'j', k: 'q', h: 'x' }[聲母] ?? 聲母; 243 | if (韻母 !== 'er') 聲母 = { z: 'j', c: 'q', s: 'x' }[聲母] ?? 聲母; 244 | } 245 | 246 | // er 247 | if (韻母 === 'er') { 248 | if (聲母 === 'r') 聲母 = ''; 249 | else 韻母 = 'i'; 250 | } 251 | 252 | // 以下音節系統性地作開口而非合口 253 | if (['n', 'l'].includes(聲母) && ['ua', 'uai', 'uang', 'uei'].includes(韻母)) 韻母 = 韻母.slice(1); 254 | // 以下音節系統性地作合口而非撮口 255 | if (韻母[0] === 'ü' && ['n', 'l'].includes(聲母) && !['ü', 'üe'].includes(韻母)) { 256 | 韻母 = 'u' + (韻母[1] === 'n' ? 'e' : '') + 韻母.slice(1); 257 | } 258 | 259 | // 以下音節系統性地作洪音而非細音 260 | if (['zh', 'ch', 'sh', 'r', 'f', 'w'].includes(聲母)) { 261 | if (韻母 === 'i') { 262 | if (聲母 === 'f' || 聲母 === 'w') 韻母 = 'ei'; 263 | } else if (韻母[0] === 'i' || 韻母[0] === 'ü') { 264 | 韻母 = (韻母[0] === 'ü' ? 'u' : '') + (韻母[1] === 'n' ? 'e' : '') + 韻母.slice(1); 265 | if (韻母 === 'ue') 韻母 = 'uo'; 266 | else if (韻母 === 'e' && ['f', 'w'].includes(聲母)) 韻母 = 'o'; 267 | } 268 | } 269 | 270 | // 以下音節系統性地作開口而非合口 271 | if (['b', 'p', 'm', 'f', 'w'].includes(聲母) && 韻母[0] === 'u' && 韻母[1]) 韻母 = 韻母.slice(1); 272 | 273 | // 拼音拼寫規則 274 | if (!聲母) { 275 | if (韻母[0] === 'i' || 韻母[0] === 'ü') 聲母 = 'y'; 276 | if (韻母[0] === 'u') 聲母 = 'w'; 277 | if (聲母 && 韻母[0] !== 'ü' && 韻母[1] && 韻母[1] !== 'n') 韻母 = 韻母.slice(1); 278 | } 279 | 韻母 = { iou: 'iu', uei: 'ui', uen: 'un', ueng: 'ong' }[韻母] || 韻母; 280 | if (韻母[0] === 'ü' && !['n', 'l'].includes(聲母)) 韻母 = 'u' + 韻母.slice(1); 281 | 282 | if (選項.標調方式 === '數字') return 聲母 + 韻母 + 聲調; 283 | return 聲母 + (聲調 ? 韻母.replace(/.*a|.*[eo]|.*[iuü]/, '$&' + ' ̄́̌̀'[聲調]) : 韻母); 284 | -------------------------------------------------------------------------------- /rules/tagged-is.js: -------------------------------------------------------------------------------- 1 | export default { 2 | meta: { 3 | type: 'suggestion', 4 | docs: { 5 | description: 'Enforce using tagged template with the `is` function', 6 | }, 7 | fixable: 'code', 8 | schema: [], 9 | }, 10 | create(context) { 11 | return { 12 | CallExpression(node) { 13 | if (node.callee.name === 'is') { 14 | const arg = node.arguments[0]; 15 | if (arg && (arg.type === 'TemplateLiteral' || (arg.type === 'Literal' && typeof arg.value === 'string'))) { 16 | context.report({ 17 | node, 18 | message: 'Use tagged template with the `is` function.', 19 | fix(fixer) { 20 | if (arg.type === 'TemplateLiteral') { 21 | return fixer.replaceText(node, `is${context.getSourceCode().getText(arg)}`); 22 | } 23 | if (arg.type === 'Literal' && typeof arg.value === 'string') { 24 | return fixer.replaceText(node, `is\`${ 25 | arg.raw.slice(1, -1).replace(/\\(('|")|.)|`|\$\{/g, (match, escaped, quote) => quote || `\\${escaped || match}`) 26 | }\``); 27 | } 28 | return null; 29 | }, 30 | }); 31 | } 32 | } 33 | }, 34 | }; 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'node:fs'; 2 | import util from 'node:util'; 3 | 4 | import { 資料, 音韻地位 } from 'tshet-uinh'; 5 | import * as TshetUinhExamples from '../dist/index.js'; 6 | 7 | const 地位 = 音韻地位.from描述('書開三宵上'); 8 | 9 | const testCases = [ 10 | ['position', '書開三宵上'], 11 | ['tupa', 'sjiewq'], 12 | ['baxter', 'syewX'], 13 | ['karlgren', 'ɕi̯ɛu꞉'], 14 | ['wangli', '꜂ɕĭɛu'], 15 | ['panwuyun', 'ɕiᴇu˧˥'], 16 | ['unt', 'ɕéw'], 17 | ['msoeg_v8', 'ɕiɛuʔ'], 18 | ['high_tang', 'ɕéw'], 19 | ['mid_tang', 'ɕɛ́w'], 20 | ['n_song', 'ɕjɛ́w'], 21 | ['mongol', 'ꡮꡠꡓ'], 22 | ['zhongyuan', 'ʂjɛw³'], 23 | // ['fanwan', 'shiu2'], 24 | ['putonghua', 'shǎo'], 25 | ['gwongzau', 'siu2'], 26 | ['zaonhe', 'sɔ̄'], 27 | ['ayaka_v8', 'seu'], 28 | ['yec_en_hua', 'A'], 29 | ]; 30 | 31 | const directorySchemata = new Set(readdirSync('.').flatMap(file => file !== (file = file.replace(/\.js$/, '')) && !file.endsWith('.config') ? [file] : [])); 32 | const nonExistentSchemata = new Set(); 33 | 34 | let passed = 0; 35 | let total = 0; 36 | for (const [schema, expected] of testCases) { 37 | if (!(schema in TshetUinhExamples)) { 38 | nonExistentSchemata.add(schema); 39 | continue; 40 | } 41 | 42 | console.log(`Testing: ${schema}`); 43 | total++; 44 | try { 45 | const deriver = TshetUinhExamples[schema](); 46 | Array.from(資料.iter音韻地位(), deriver); // Ensure no error is thrown from all 音韻地位 47 | const result = deriver(地位); 48 | if (result === expected) { 49 | passed++; 50 | } else { 51 | console.log( 52 | ` Expected ${util.inspect(expected)}, got ${util.inspect(result)}` 53 | ); 54 | } 55 | } catch (e) { 56 | console.log(` Error thrown: ${util.inspect(e)}`); 57 | } 58 | 59 | directorySchemata.delete(schema); 60 | } 61 | 62 | console.log(`${passed}/${total} tests passed.`); 63 | if (directorySchemata.size || nonExistentSchemata.size || passed < total) { 64 | if (directorySchemata.size) console.log('The following schemata are untested:', directorySchemata); 65 | if (nonExistentSchemata.size) console.log('There are test cases for the following schemata but they are missing:', nonExistentSchemata); 66 | process.exit(1); 67 | } 68 | -------------------------------------------------------------------------------- /tupa.js: -------------------------------------------------------------------------------- 1 | /* 切韻拼音 2 | * 3 | * https://phesoca.com/tupa/ 或 4 | * https://www.bilibili.com/read/cv19972367 或 5 | * https://zhuanlan.zhihu.com/p/478751152 6 | * 7 | * @author unt 8 | */ 9 | 10 | /** @type { 音韻地位['屬於'] } */ 11 | const is = (...x) => 音韻地位.屬於(...x); 12 | /** @type { 音韻地位['判斷'] } */ 13 | const when = (...x) => 音韻地位.判斷(...x); 14 | 15 | if (!音韻地位) return []; 16 | 17 | function get聲母() { 18 | return { 19 | 幫: 'p', 滂: 'ph', 並: 'b', 明: 'm', 20 | 端: 't', 透: 'th', 定: 'd', 泥: 'n', 來: 'l', 21 | 知: 'tr', 徹: 'trh', 澄: 'dr', 孃: 'nr', 22 | 見: 'k', 溪: 'kh', 羣: 'g', 疑: 'ng', 云: '', 23 | 影: 'q', 曉: 'h', 匣: 'gh', 24 | 精: 'ts', 清: 'tsh', 從: 'dz', 心: 's', 邪: 'z', 25 | 莊: 'tsr', 初: 'tsrh', 崇: 'dzr', 生: 'sr', 俟: 'zr', 26 | 章: 'tj', 昌: 'tjh', 常: 'dj', 書: 'sj', 船: 'zj', 日: 'nj', 以: 'j', 27 | }[音韻地位.母]; 28 | } 29 | 30 | function get韻母() { 31 | let 韻母 = when([ 32 | ['脂韻', 'i'], ['之韻', 'y'], ['尤侯韻', 'u'], 33 | ['支韻', 'e'], ['佳韻', 'ee'], ['魚韻', 'eo'], ['虞模韻', 'o'], 34 | ['麻韻', 'ae'], ['歌韻', 'a'], 35 | 36 | ['蒸韻 AB類', 'ing'], ['蒸韻', 'yng'], ['東韻', 'ung'], 37 | ['青韻', 'eng'], ['耕韻', 'eeng'], ['登韻', 'eong'], ['冬鍾韻', 'ong'], ['江韻', 'oeung'], 38 | ['庚清韻', 'aeng'], ['陽唐韻', 'ang'], 39 | 40 | ['微韻', 'uj'], 41 | ['齊祭韻', 'ej'], ['皆韻', 'eej'], ['灰咍廢韻', 'oj'], 42 | ['夬韻', 'aej'], ['泰韻', 'aj'], 43 | 44 | ['真臻韻', 'in'], ['殷文韻', 'un'], 45 | ['先仙韻', 'en'], ['山韻', 'een'], ['元魂痕韻', 'on'], 46 | ['刪韻', 'aen'], ['寒韻', 'an'], 47 | 48 | ['幽韻', 'iw'], 49 | ['蕭宵韻', 'ew'], 50 | ['肴韻', 'aew'], ['豪韻', 'aw'], 51 | 52 | ['侵韻', 'im'], 53 | ['鹽添韻', 'em'], ['咸韻', 'eem'], ['覃嚴凡韻', 'om'], 54 | ['銜韻', 'aem'], ['談韻', 'am'], 55 | ]); 56 | // 不圓脣元音 57 | if (is`開口` && !韻母.endsWith('m')) 韻母 = 韻母.replace(/^u/, 'y').replace(/^o/, 'eo'); 58 | // 等類標記 59 | if (is`三等` || is`四等` && 韻母.startsWith('ae')) { 60 | if (is`A類` || is`銳音 非 莊組` && /^i|^e(?!o)|^ae/.test(韻母)) { 61 | // A 類以 i- 標記 62 | if (!韻母.startsWith('i')) 韻母 = 'i' + 韻母; 63 | } else { 64 | // B、C 類以 y-/u- 標記 65 | if (/^[uo]|^a(?!e)/.test(韻母) ? is`開口` : is`非 合口`) { 66 | if (!韻母.startsWith('y')) 韻母 = 'y' + 韻母; 67 | 韻母 = 韻母.replace('yeo', 'yo'); 68 | } else { 69 | if (!韻母.startsWith('u')) 韻母 = 'u' + 韻母; 70 | } 71 | } 72 | } else { 73 | // 高元音非三等以 o- 標記 74 | if (/^[yu]/.test(韻母)) 韻母 = 'o' + 韻母; 75 | } 76 | if (is`合口` && !/^[uo]/.test(韻母)) 韻母 = 'w' + 韻母; 77 | if (is`入聲`) 韻母 = 韻母 78 | .replace('ng', 'k') 79 | .replace('n', 't') 80 | .replace('m', 'p'); 81 | return 韻母; 82 | } 83 | 84 | function get聲調() { 85 | return { 上: 'q', 去: 'h' }[音韻地位.聲] || ''; 86 | } 87 | 88 | return get聲母() + get韻母() + get聲調(); 89 | -------------------------------------------------------------------------------- /unt.js: -------------------------------------------------------------------------------- 1 | /* unt 切韻擬音 2 | * 3 | * 包含 6 個版本: 4 | * 5 | * - 2019:切韻朗讀音 6 | * https://zhuanlan.zhihu.com/p/58227457 7 | * 8 | * - 2020:切韻擬音 J(原版) 9 | * https://zhuanlan.zhihu.com/p/305516512 & https://zhuanlan.zhihu.com/p/313005024 10 | * 11 | * - 2020:切韻擬音 J(2022 新版) 12 | * - 2020:切韻擬音 J(2024 新版) 13 | * 都由切韻擬音 L 改寫,與 L 的對比見 https://zhuanlan.zhihu.com/p/545490174 文末 14 | * 15 | * - 2022:切韻擬音 L 16 | * https://zhuanlan.zhihu.com/p/545490174 17 | * 18 | * - 2023:切韻通俗擬音 19 | * https://zhuanlan.zhihu.com/p/545490174 20 | * 21 | * 前 3 個版本已過時,僅作爲歷史存檔,不建議使用 22 | * 23 | * @author unt 24 | */ 25 | 26 | /** @type { 音韻地位['屬於'] } */ 27 | const is = (...x) => 音韻地位.屬於(...x); 28 | /** @type { 音韻地位['判斷'] } */ 29 | const when = (...x) => 音韻地位.判斷(...x); 30 | 31 | const is專業模式 = 選項.專業模式 ?? false; 32 | const isL = 選項.版本?.includes('L') ?? true; 33 | const isJ原版 = Boolean(選項.版本?.includes('J') && 選項.版本?.includes('原版')); // 2020 34 | const isJ新版 = Boolean(選項.版本?.includes('J') && !選項.版本?.includes('原版')); // 2022 或 2024 35 | const isJ2024 = Boolean(選項.版本?.includes('J') && !選項.版本?.includes('版')); // 2024 36 | const is朗讀音 = Boolean(選項.版本?.includes('朗讀音')); 37 | const is通俗 = Boolean(選項.版本?.includes('通俗')); 38 | 39 | function get選項列表() { 40 | const 後低元音選單 = !is朗讀音 ? (!is通俗 ? [1, 'a', 'ɑ'] : [2, 'ɑ', '歌陽唐ɑ 其他a']) : [1, 'ɑ']; 41 | 42 | const { _last版本, _last後低元音選單 } = 選項; 43 | const 版本changed = _last版本 && _last版本 !== 選項.版本; 44 | const 後低元音選單changed = _last後低元音選單 !== 後低元音選單.join('\n'); 45 | 46 | return [ 47 | ['_last版本', [1, 選項.版本 ?? '2022:切韻擬音 L'], { hidden: true }], 48 | ['_last後低元音選單', [1, 後低元音選單.join('\n')], { hidden: true }], 49 | ['版本|\n切韻擬音 J 和 L 均爲 2024 新版。如需調取過時版本、查看高級選項,請勾選「專業模式」', [is專業模式 ? 5 : 2, 50 | '2019:切韻朗讀音', 51 | '2020:切韻擬音 J(原版)', 52 | '2020:切韻擬音 J(2022 版)', 53 | '2020:切韻擬音 J', 54 | '2022:切韻擬音 L', 55 | '2023:切韻通俗擬音', 56 | ].filter((_, i) => is專業模式 || ![1, 2, 3].includes(i))], 57 | ['專業模式', is專業模式], 58 | 59 | '基本選項', 60 | ['後低元音', 後低元音選單, { reset: 後低元音選單changed }], 61 | ['聲調記號', [1, 62 | { text: '上ˊ 去ˋ', value: '\u0301\u0300' }, 63 | { text: '上ʔ 去h', value: 'ʔh' }, 64 | { text: '上ˀ 去ʰ', value: 'ˀʰ' }, 65 | '五度符號', 66 | '五度符號(帶拖腔)', 67 | '無', 68 | ].filter((_, i) => (is專業模式 || [0, 1, 2, 4, 6].includes(i)) && !(is朗讀音 && i === 4)), 69 | { description: is專業模式 && !is朗讀音 && 'ʔ 對應的上標字母 Unicode 未收,以 ˀ 代替' }, 70 | ], 71 | ['鈍C介音|鈍 C 介音', 72 | isL && [1, '開∅ 合w'] || 73 | // ɣ 代表軟腭近音(即 ɣ̞) 74 | isJ2024 && [1, '開ɣ 唇ʋ 合ɣw'] || 75 | // β、ʋ 都代表雙唇近音(即 β̞ = ʋ̟) 76 | isJ新版 && [1, '開j̈ 唇ʋ 合w', '開j̈ 唇ʋ 合ɥ̈', '開j̈ 唇β 合ɥ̈'] || 77 | isJ原版 && [1, '開j̈ 唇β 合ɥ̈'] || 78 | is朗讀音 && [1, '開j̈ 唇ɥ̈ 合ẅ'] || 79 | is通俗 && [1, '開ɨ 唇低ɨ 唇非低ʉ 合ʉ'], 80 | { reset: 版本changed }, 81 | ], 82 | ['見組非三等簡寫作軟腭音', isJ2024 ? false : is通俗 ? true : null, { reset: 版本changed }], 83 | ].concat(is專業模式 && !is通俗 ? [ 84 | '高級選項', 85 | ['前部聲母非三等', isL ? [1, 'ᵱ ɫ', 'pˤ lˤ', 'p̙ l̙'] : null], 86 | ['知組', isJ原版 ? [2, 'ʈ', 'tɹ'] : null], 87 | ['二等元音記號|\n雙下橫線僅在下等號顯示不正常時使用', !is朗讀音 ? [1, 88 | { text: '咽化 ◌ˤ', value: 'ˤ' }, 89 | { text: 'r 音鉤(帶空隙)◌˞', value: '˞\u2006' }, 90 | { text: 'r 音鉤(無空隙)◌˞', value: '˞' }, 91 | { text: '下等號 ◌͇', value: '͇' }, 92 | { text: '雙下橫線 ◌̳', value: '̳' }, 93 | ] : null], 94 | ['脂支魚虞', isJ2024 || isL ? [2, 95 | 'i ie ɨə uo', 96 | 'i ie ə o', 97 | 'i e ə o', 98 | ] : null], 99 | ['灰魂', isJ2024 || isL ? [1, 'wəj wən', 'oj on'] : null], 100 | ['豪覃', isJ2024 || isL ? [1, 'əw əm', 'ʌw ʌm'] : null], 101 | ['泰祭夬廢韻尾', isJ原版 ? [1, 'j', 'ɹ'] : is朗讀音 ? [1, 'jɕ'] : null], 102 | ['j 韻尾去聲作 ɹ', isJ新版 || isL ? false : null], // 脂韻去聲則後加 ɹ 103 | ['顯示純音位形式', isL ? false : null], 104 | ] : []); 105 | } 106 | 107 | const { 四等韻 } = TshetUinh.表達式; 108 | 109 | function 調整音韻地位() { 110 | function 調整(表達式, 調整屬性) { if (is(表達式)) 音韻地位 = 音韻地位.調整(調整屬性); } 111 | if (!is通俗) 調整('明母 尤韻', '侯韻 一等 不分類'); // 實爲一等 112 | if (!is通俗) 調整('銳音 幽韻', `尤韻 開合中立 ${is`端組` ? '四等' : ''}`); 113 | } 114 | 115 | function get聲母() { 116 | return when([ 117 | ['云母 開口 非 (侵鹽韻 入聲)', { 云: '' }], // 煜曄兩小韻爲“合口” 118 | [選項.顯示純音位形式 === true && '精組 非 三等', { 119 | 精: 'tᵴ', 清: 'tᵴʰ', 從: 'dᵶ', 心: 'ᵴ', 邪: 'ᵶ', // 精組僅在音位形式中區分三等非三等 120 | }], 121 | ['(鈍音 非 羣母 非 三等) 或 (來母 非 三等) 或 (匣母)', { 122 | // 羣母一律按三等寫,匣母一律按非三等寫 123 | 幫: 'ᵱ', 滂: 'ᵱʰ', 並: 'ᵬ', 明: 'ᵯ', 124 | 見: 'q', 溪: 'qʰ', 疑: 'ɴ', 125 | 影: 'ʡ', 曉: 'χ', 匣: 'ʁ', 126 | 來: 'ɫ', 127 | }], 128 | ['', { 129 | 幫: 'p', 滂: 'pʰ', 並: 'b', 明: 'm', 130 | 端: 't', 透: 'tʰ', 定: 'd', 泥: 'n', 來: 'l', 131 | 知: 'ʈ', 徹: 'ʈʰ', 澄: 'ɖ', 孃: 'ɳ', 132 | 見: 'k', 溪: 'kʰ', 羣: 'ɡ', 疑: 'ŋ', 云: 'w', 133 | 影: 'ʔ', 曉: 'x', 134 | 精: 'ts', 清: 'tsʰ', 從: 'dz', 心: 's', 邪: 'z', 135 | 莊: 'tʂ', 初: 'tʂʰ', 崇: 'dʐ', 生: 'ʂ', 俟: 'ʐ', 136 | 章: 'tɕ', 昌: 'tɕʰ', 常: 'dʑ', 書: 'ɕ', 船: 'ʑ', 日: 'ɲ', 以: 'j', 137 | }] 138 | ])[音韻地位.母]; 139 | } 140 | 141 | function get韻基() { 142 | const 韻 = { 143 | 臻: '真', 144 | 侯: '尤', 145 | 唐: '陽', 146 | }[音韻地位.韻] ?? 音韻地位.韻; 147 | const 所有韻 = [ 148 | // 介音(AB 韻只列在第一列,開合兼有的韻不列在最後一列) 149 | // 三 等:j|ɹ|∅|w 150 | // 非三等:e̯|ʕ|∅|o̯ 151 | ['ɨ', '   脂真幽侵|       |之蒸 微殷  |尤 東 文  '], 152 | ['ə', ' 青 齊先蕭添|佳耕江皆山 咸| 登 咍痕豪覃|模 冬灰魂  '], // 非三等 153 | ['ə', '   祭仙宵鹽|       |魚  廢元 嚴|虞 鍾   凡'], // 三等 154 | ['a', ' 清     |麻庚 夬刪肴銜|歌 陽泰寒 談'], 155 | ['ɨ', '       支'], // 前響二合元音 [ie] 156 | ]; 157 | const 韻尾列表 = is`舒聲` ? ['', ...'ŋɴjnwme'] : [...' kq t p']; 158 | 159 | let 匹配行 = 所有韻.find(e => e[1].includes(韻)); 160 | let 韻核 = 匹配行[0]; 161 | let 韻尾 = 韻尾列表[匹配行[1].split('|').find(v => v.includes(韻)).indexOf(韻)]; 162 | if (is`泰祭夬廢韻 去聲`) 韻尾 = 選項.泰祭夬廢韻尾 || 韻尾; 163 | if (選項['j 韻尾去聲作 ɹ'] && 韻尾 === 'j' && is`去聲`) 韻尾 = 'ɹ'; 164 | return [韻核, 韻尾]; 165 | } 166 | 167 | function get介音() { 168 | const A = 'j'; 169 | const B = 'ɹ'; 170 | const C = ''; 171 | let 等類介音 = when([ 172 | ['A類 或 四等', A], // 四等包含「地」「爹」等 173 | ['B類 或 二等', B], 174 | ['C類 或 一等', C], 175 | // 餘下為三等銳音(含以母) 176 | ['精組', A], 177 | ['', ''], 178 | ]); 179 | let 合口介音 = is`(合口 或 尤韻) 非 (幫組 或 云母)` ? 'w' : ''; 180 | return 等類介音 + 合口介音; 181 | } 182 | 183 | function get聲調() { 184 | if (選項.聲調記號 === '無') return ''; 185 | 186 | if (!選項.聲調記號.includes('五度符號')) { 187 | if (is`平入聲`) return ''; 188 | let 聲調記號 = 選項.聲調記號[+is`去聲`]; 189 | if (is朗讀音 && is`泰祭夬廢韻 去聲` && ['ʰ', 'h'].includes(聲調記號)) return ''; 190 | return 聲調記號; 191 | } 192 | 193 | const 五度符號列表 = is朗讀音 ? [ 194 | '˦', '˦˦˥', '˥˩', '˥', 195 | '˨˩', '˨˨˧', '˧˩˨', '˨˩', 196 | ] : 選項.聲調記號.includes('帶拖腔') ? [ 197 | '˦˦˨', '˦˥', '˦˩˨', '˦', 198 | '˨˨˩', '˨˦', '˨˩˨', '˨', 199 | ] : [ 200 | '˦', '˦˥', '˦˩', '˦', 201 | '˨', '˨˦', '˨˩', '˨', 202 | ]; 203 | const is陽調 = is`全濁` || is朗讀音 && is`次濁 平去聲`; 204 | return 五度符號列表['平上去入'.indexOf(音韻地位.聲) + is陽調 * 4]; 205 | } 206 | 207 | function 音位to音值(音節) { 208 | const is雙唇韻尾 = [...'wmp'].includes(音節.韻尾); 209 | const is展唇鈍韻尾 = [...'ŋk'].includes(音節.韻尾); 210 | const is圓唇鈍韻尾 = [...'ɴq'].includes(音節.韻尾); 211 | const is後部韻尾 = is展唇鈍韻尾 || is圓唇鈍韻尾 || !音節.韻尾; 212 | const is音節首含銳 = !['', 'w'].includes(音節.介音) || is`銳音 三等`; 213 | const is音節首含唇 = 音節.介音.includes('w') || is`幫組` || 音節.聲母 === 'w'; 214 | 215 | // (1) 韻核前化 216 | // 韻核前的非小舌化銳音使韻核前化,但後部韻尾使韻核不被前化(-K 只使 /ɨ/ 不被前化)。 217 | // 通俗擬音不強制前化。茝佁䑂 3 小韻也除外 218 | if (is音節首含銳 && !(is通俗 && is`東鍾之微魚虞廢殷元文歌陽尤嚴凡韻`) && !is`廢韻 非 去聲`) { 219 | if (!is後部韻尾) 音節.替換('韻核', 'ɨ', 'i'); 220 | if (!is圓唇鈍韻尾 && (音節.韻尾 || is`二等`)) 音節.替換('韻核', 'ə', 'e'); 221 | } 222 | 223 | // (2) 韻核圓唇化 224 | // a1. 圓唇鈍韻尾使韻核圓唇化 225 | // a2. 非三等音节相當於 a1 226 | // b1. 含唇音的三 等音節首使 ɨ 圓唇化(三 等 w 實現爲 u̯,同化 ɨ) 227 | // b2. 含唇音的非三等音節首使 ə 圓唇化(非三等 w 實現爲 o̯,同化 ə) 228 | // b3. 但展唇鈍韻尾和雙唇韻尾排斥圓唇,使韻核保持展唇 229 | if (is圓唇鈍韻尾 || !is`三等` && !音節.韻尾) { 230 | 音節.替換('韻核', 'ɨ', 'u'); 231 | 音節.替換('韻核', 'ə', 'o'); 232 | } 233 | if (is音節首含唇 && !is展唇鈍韻尾 && !is雙唇韻尾) { 234 | 音節.替換('韻核', 'ɨ', 'u'); 235 | if ((選項.灰魂?.includes('o') || !(isJ2024 || isL)) && !is`三等` || !音節.韻尾) 音節.替換('韻核', 'ə', 'o'); 236 | } // 豪韻唇音可能也是獨立的,但爲了簡便不處理它 237 | 238 | // (3) 韻核咽化 239 | // 非三等 ɹ 實現爲 ʕ,使韻核咽化 240 | if (!is`三等` && 音節.介音.includes('ɹ')) { 241 | 音節.韻核 += 'ˤ'; 242 | } 243 | 244 | // (4) 省略與韻核同質的介音、韻尾 245 | if (音節.韻核 === 'i') 音節.替換('韻尾', 'j', ''); 246 | if (音節.韻核 === 'i') 音節.替換('介音', 'j', ''); 247 | if (音節.韻核 === 'u') 音節.替換('介音', 'w', ''); 248 | if (音節.韻核 === 'e' && !is`三等`) 音節.替換('介音', 'j', ''); 249 | if (音節.韻核 === 'o') 音節.替換('介音', 'w', ''); 250 | if (音節.韻核.endsWith('ˤ')) 音節.替換('介音', 'ɹ', ''); 251 | 252 | // (5) 豪覃韻韻核寫作 ʌ 253 | if ((選項.豪覃?.includes('ʌ') || !(isJ2024 || isL)) && !is`三等` && is雙唇韻尾) 音節.替換('韻核', 'ə', 'ʌ'); 254 | 255 | // 後處理:按需顯示 ɑ,包括𦣛小韻(銳音歌三合) 256 | if (選項.後低元音.length === 1) 257 | if (!is音節首含銳 || is圓唇鈍韻尾 || is`歌韻 三等 合口`) 音節.替換('韻核', 'a', 選項.後低元音); 258 | // 後處理:脂支魚虞 259 | const i = ['i', 'ie', 'ə', 'o'].indexOf(音節.韻核 + 音節.韻尾); 260 | if (i !== -1 && is`三等`) { 261 | // 舊版本魚虞按前響二合元音處理 262 | const 韻基 = (選項.脂支魚虞 ?? (isJ2024 || isL ? 'i ie ə o' : 'i ie ɨə uo')).split(' ')[i]; 263 | 音節.韻核 = 韻基[0]; 264 | 音節.韻尾 = 韻基.slice(1); 265 | } 266 | } 267 | 268 | function 調整聲母(音節) { 269 | const 舊版聲母字典 = [ 270 | [選項.前部聲母非三等?.includes('ˤ'), { 271 | 'ᵱ': 'pˤ', 'ᵬ': 'bˤ', 'ᵯ': 'mˤ', 272 | 'ᵴ': 'sˤ', 'ᵶ': 'zˤ', 'ɫ': 'lˤ', 'ʡ': 'ʔˤ', 273 | }], 274 | [選項.前部聲母非三等?.includes('̙'), { 275 | 'ᵱ': 'p̙', 'ᵬ': 'b̙', 'ᵯ': 'm̙', 276 | 'ᵴ': 's̙', 'ᵶ': 'z̙', 'ɫ': 'l̙', 'ʡ': 'ʔ̙', 277 | }], 278 | [!isL, { 'ᵱ': 'p', 'ᵬ': 'b', 'ᵯ': 'm' }], 279 | [isJ原版 || is朗讀音 || is通俗 || isJ2024, { 'ʡ': 'ʔ' }], 280 | [isJ原版 || is朗讀音 || is通俗, { 'ɫ': 'l' }], 281 | [isJ原版 || is朗讀音, { 'x': 'h' }], 282 | [選項.見組非三等簡寫作軟腭音, { 'q': 'k', 'ɴ': 'ŋ', 'χ': 'x' }], 283 | [選項.見組非三等簡寫作軟腭音 && is通俗, { 'ʁ': 'ɣ' }], 284 | [選項.知組 === 'tɹ', { 'ʈ': 'tɹ', 'ɖ': 'dɹ', 'ɳ': 'nɹ' }], 285 | ].reduce((prev, cur) => Object.assign(prev, cur[0] ? cur[1] : {}), {}); 286 | 287 | for (const [k, v] of Object.entries(舊版聲母字典)) { 288 | if (音節.聲母.includes(k)) { 289 | 音節.替換('聲母', k, v); 290 | break; 291 | } 292 | } 293 | } 294 | 295 | function 調整鈍C介音(音節) { 296 | if (選項.鈍C介音.includes('∅')) return; 297 | const 鈍C介音列表 = 選項.鈍C介音.split(' ').map(e => e.replace(/[開合唇非低]/g, '')); 298 | const [鈍C開, 鈍C唇低, 鈍C唇非低, 鈍C合] = [...鈍C介音列表.slice(0, 2), ...鈍C介音列表.slice(-2)]; 299 | 音節.介音 = when([ 300 | [音節.韻核 !== 'i' && '三等 鈍音 非 云母', [ // J 的云母一律拼作 w 且後無多餘介音。通俗的云母爲零聲母,後面再處理 301 | [音節.介音 === 'w', 鈍C合], 302 | [音節.介音 === '', [ 303 | ['幫組', [ 304 | ['歌陽韻', 鈍C唇低], 305 | ['', 鈍C唇非低], 306 | ]], 307 | [isJ2024 && 音節.韻核 !== 'ɨ', 鈍C開], 308 | [!['ɨ', 'u'].includes(音節.韻核) || '東尤韻', 鈍C開], 309 | ]], 310 | ]], 311 | ['', 音節.介音], 312 | ], '', true); 313 | } 314 | 315 | function 調整二等元音(音節) { 316 | const 默認記號 = 'ˤ'; 317 | let 新記號 = 選項.二等元音記號; 318 | if (!新記號 || 新記號 === 默認記號 || !音節.韻核.includes(默認記號)) return; 319 | 320 | if (新記號[0] === '˞' && is`端組 或 來母 庚韻`) 新記號 = ''; 321 | 音節.替換('韻核', 默認記號, 新記號); 322 | if (音節.韻核 === 'e' && !音節.韻尾) { // 箉小韻,無附加符號時改作 -aj 323 | 音節.韻核 = 'a'; 324 | 音節.韻尾 = 'j'; 325 | } 326 | } 327 | 328 | function 音值toJ(音節) { 329 | if (選項.鈍C介音.includes('ɥ̈') && is`三等 非 東尤韻`) 音節.替換('韻核', 'u', 'ʉ'); 330 | if (isJ新版) return; 331 | 332 | if ( 333 | 音節.介音.includes('w') || 334 | 音節.介音 === 'ɥ̈' || 335 | [...'ʉuo'].includes(音節.韻核) && is`(合口 或 鍾韻) 非 (幫組 或 云母)` 336 | ) { 337 | 音節.聲母 += 'ʷ'; 338 | 音節.替換('聲母', 'ʰʷ', 'ʷʰ'); 339 | 音節.替換('聲母', 'jʷ', 'ɥ'); 340 | 音節.替換('介音', 'w', ''); 341 | 音節.替換('介音', 'j', 'ɥ'); 342 | } 343 | 344 | 音節.韻核 = when([ 345 | ['蒸韻', 'i'], 346 | ['臻韻 開口', 'ɹ̩'], 347 | ['侯韻 非 明母', 'ɘu'], 348 | 349 | ['清韻', 'iæ'], 350 | ['陽韻', (is`開口 或 A類` ? 'ɨ' : 'ʉ') + 'ɐ'], 351 | ['鍾韻', 'ʉɔ'], 352 | 353 | ['江韻', 'œˤ'], 354 | ['凡韻', 'œ'], 355 | ['', 音節.韻核], 356 | ]); 357 | 358 | 音節.介音 = when([ 359 | [音節.聲母.includes('ʷ') && 音節.韻核[0] !== 'ʉ' && '精組 三等', 'ɹ'], 360 | ['精組 三等 東尤韻', 'ɹ'], 361 | [音節.韻核[0] === 'i' && (['j', 'ɥ'].includes(音節.介音) || '知組 或 來母'), ''], 362 | [[...'iɨʉ'].includes(音節.韻核[0]) && (['j̈', 'ɥ̈'].includes(音節.介音) || '銳音'), ''], 363 | [選項.知組 !== 'tɹ' && '知組 三等', 'ɹ'], 364 | ['來母 三等', 'ɹ'], 365 | ['', 音節.介音], 366 | ]); 367 | 368 | 音節.韻尾 = { 369 | 微: 'i', 幽: 'u', 370 | 支: 'ɛ', 魚: 'ʌ', 虞: 'ɔ', 371 | }[音韻地位.韻] ?? 音節.韻尾; 372 | } 373 | 374 | function 音值to朗讀音(音節) { 375 | if (音節.聲母.replace('ʰ', '').length > 1) { 376 | 音節.聲母 = 音節.聲母[0] + '͡' + 音節.聲母.slice(1); 377 | } 378 | 379 | const hasW = 音節.聲母 === 'w' || 音節.介音.includes('w') || ['u', 'o'].includes(音節.韻核); 380 | 音節.替換('聲母', 'w', ''); 381 | 音節.介音 = when([ 382 | [`三四等 非 ${四等韻}`, [ // 包含「地」「爹」「丟」等 383 | ['幽韻', is`幫組` ? 'j' : 'ɥ'], 384 | [[...'iea'].includes(音節.韻核[0]) || '蒸韻 或 AB類', [ 385 | ['B類 或 知莊組 或 蒸韻 鈍音', hasW ? 'ɻɥ' : 'ɻj'], // B 386 | ['', hasW ? 'ɥ' : 'j'], // A 387 | ]], 388 | ['', is`幫組 或 尤韻` ? 'ɥ̈' : hasW ? 'ẅ' : 'j̈'], // C 389 | ]], 390 | ['二等 非 (端組 或 來母 庚韻)', is`知莊組` ? 'ɻ' + 音節.介音 : 音節.介音 + 'ɻ'], 391 | [音節.韻核 === 'o' && '一等 非 (冬模韻 或 幫組)', 'w'], 392 | ['', 音節.介音], 393 | ]); 394 | if (音節.聲母 === 'j' && ['j', 'ɥ'].includes(音節.介音)) 音節.聲母 = ''; 395 | 396 | if (音節.韻核.includes('ˤ')) { 397 | 音節.替換('韻核', 'eˤ', 'æ'); 398 | 音節.替換('韻核', 'oˤ', 'æ'); 399 | 音節.替換('韻核', 'aˤ', 'a'); 400 | } else { 401 | 音節.韻核 = when([ 402 | [`三四等 非 ${四等韻}`, [ 403 | ['蒸韻', 'i'], 404 | ['臻韻 開口', 'i˞ '], 405 | ['微韻', 'ɨ'], 406 | ['幽韻', 'ÿ'], 407 | [音節.韻核 === 'e' || '支清韻', 'ɛ'], 408 | ['魚韻', 'ə'], 409 | [音節.韻核 === 'o' || '虞韻', 'ɔ'], 410 | ['陽韻', 'ɐ'], 411 | ['嚴凡韻 幫組', 'ɞ'], 412 | ['', 音節.韻核], 413 | ]], 414 | ['侯韻 非 幫組', 'ɘu'], 415 | ['豪韻', 'ɑ'], 416 | ['覃韻 或 咍灰韻 開口', 'ɐ'], 417 | ['咍灰韻', 'ɔ̞'], 418 | [音節.韻核 === 'ə', 'ɘ'], 419 | ['', 音節.韻核], 420 | ]); 421 | } 422 | 423 | 音節.韻尾 = when([ 424 | ['通江攝', is`舒聲` ? 'ŋʷ' : 'kʷ'], 425 | ['宕攝', is`舒聲` ? 'ŋ' : 'k'], 426 | ['梗攝', is`舒聲` ? 'ɲ' : 'c'], 427 | ['支魚虞幽韻', ''], 428 | ['', 音節.韻尾], 429 | ]); 430 | 431 | [['j', 'i'], ['j̈', 'ɨ']].forEach(e => { 432 | if (音節.韻核[0] === e[1] && (音節.聲母 || 音節.介音[0] === 'ɻ')) 音節.替換('介音', e[0], ''); 433 | }); 434 | } 435 | 436 | function 音值to通俗(音節) { 437 | const is小舌尾 = is`尤侯虞模歌東冬鍾江陽唐韻`; 438 | 439 | if (音節.聲母 === 'w') { 440 | 音節.聲母 = ''; 441 | if (!音節.介音) 音節.介音 = 'ɨ'; 442 | 音節.介音 += 'u'; 443 | } 444 | if (is`蒸韻 B類`) 音節.韻核 = 'i'; 445 | 音節.替換('介音', 'j', 'i'); 446 | 音節.替換('介音', 'ɹ', 'ɨ'); 447 | 音節.替換('介音', 'w', 'u'); 448 | 音節.替換('介音', 'ʉ', 'ɨu'); 449 | if (is`銳音 三四等 非 ${四等韻}`) { 450 | if (!is`端精組`) 音節.介音 = (is`莊組` ? 'ɨ' : 'i') + 音節.介音; 451 | if (![...'iea'].includes(音節.韻核) || is小舌尾) 音節.替換('介音', 'i', 'ɨ'); 452 | } 453 | if (is`支魚虞韻`) { 454 | if (!['i', 'ɨ'].includes(音節.介音[0])) 音節.介音 = 音節.韻核.replace('u', 'ɨu') + 音節.介音; 455 | 音節.韻核 = 音節.韻尾; 456 | 音節.韻尾 = ''; 457 | } 458 | if (音節.韻核 === 'u' && is小舌尾) 音節.介音 = 音節.介音.replace('u', ''); 459 | if (音節.韻核 === 'i' && 音節.介音 === 'u') 音節.介音 = 'iu'; 460 | if (音節.韻核 === 'o' && 音節.介音 && !音節.介音.includes('u')) 音節.介音 += 'u'; 461 | 音節.替換('介音', 'iu', 'y'); 462 | 音節.替換('介音', 'ɨu', 'ʉ'); 463 | 464 | if (音節.韻核.includes('ˤ')) { 465 | 音節.替換('韻核', 'eˤ', 'ɛ'); 466 | 音節.替換('韻核', 'oˤ', 'ɔ'); 467 | 音節.替換('韻核', 'aˤ', 'æ'); 468 | } 469 | if (!is小舌尾) { 470 | if (音節.韻核 === 'o') 音節.介音 += 'u'; 471 | 音節.替換('韻核', 'u', 'ʉ'); 472 | 音節.替換('韻核', 'ʌ', is`豪韻` ? 'a' : 'ə'); 473 | 音節.替換('韻核', 'o', 'ə'); 474 | if (is`三四等`) 音節.替換('韻核', 'a', 'æ'); 475 | } 476 | if (選項.後低元音.includes('歌陽唐') && is小舌尾) 音節.替換('韻核', 'a', 'ɑ'); 477 | if (音節.介音 === 音節.韻核) 音節.介音 = ''; 478 | 479 | 音節.替換('韻尾', 'ɴ', 'ŋ'); 480 | 音節.替換('韻尾', 'q', 'k'); 481 | } 482 | 483 | function get音節() { 484 | const 音節 = { 485 | 聲母: get聲母(), 486 | 介音: get介音(), 487 | 聲調: get聲調(), 488 | 替換(propertyName, from, to) { this[propertyName] = this[propertyName].replace(from, to); } 489 | }; 490 | [音節.韻核, 音節.韻尾] = get韻基(); 491 | 492 | if (!選項.顯示純音位形式) 音位to音值(音節); 493 | 調整聲母(音節); 494 | 調整鈍C介音(音節); 495 | if (isJ新版 || isJ原版) 音值toJ(音節); 496 | else if (is朗讀音) 音值to朗讀音(音節); 497 | else if (is通俗) 音值to通俗(音節); 498 | 調整二等元音(音節); 499 | 500 | let 聲調記號插入位置 = ['̩', '͇', '̳', 'ɘu'].some(e => 音節.韻核.includes(e)) ? 2 : 1; 501 | 音節.韻基 = 音節.韻核 + 音節.韻尾; 502 | 音節.帶調韻基 = 選項.聲調記號.includes('\u0301') ? 503 | 音節.韻核.slice(0, 聲調記號插入位置) + 音節.聲調 + 音節.韻核.slice(聲調記號插入位置) + 音節.韻尾 : 504 | 音節.韻核 + 音節.韻尾 + 音節.聲調; 505 | 音節.韻母 = 音節.介音 + 音節.韻基; 506 | 音節.帶調韻母 = 音節.介音 + 音節.帶調韻基; 507 | return 音節; 508 | } 509 | 510 | if (!音韻地位) return get選項列表(); 511 | 調整音韻地位(); 512 | const 音節 = get音節(); 513 | return 音節.聲母 + 音節.帶調韻母; 514 | -------------------------------------------------------------------------------- /wangli.js: -------------------------------------------------------------------------------- 1 | /* 王力擬音 2 | * 3 | * 擬音來自王力著作: 4 | * 5 | * - 漢語史稿 [M]. 北京: 中華書局, 1980: 50-54. 6 | * - 漢語語音史 [M]. 北京: 中國社會科學出版社, 1985: 110-227. 7 | * 8 | * 提供 3 種擬音版本: 9 | * 10 | * - 《漢語史稿》 第二章 第十節 中古的語音系統(默認) 11 | * - 《漢語語音史》 卷上 第三章 魏晉南北朝音系 12 | * - 《漢語語音史》 卷上 第四章 隋——中唐音系 13 | * 14 | * 提供 2 種音標風格: 15 | * 16 | * - 國際音標(原貌):王力著作採用的音標符號 17 | * - 國際音標(通用):現在的中國通用音標符號 18 | * 19 | * 參考:高本漢擬音 (https://github.com/nk2028/tshet-uinh-examples/blob/qieyun-0.13/karlgren.js) 20 | * 21 | * @author Mishiro 22 | */ 23 | 24 | /** @type { 音韻地位['屬於'] } */ 25 | const is = (...x) => 音韻地位.屬於(...x); 26 | /** @type { 音韻地位['判斷'] } */ 27 | const when = (...x) => 音韻地位.判斷(...x); 28 | 29 | const is史稿 = 選項.擬音版本?.includes('稿') ?? true; 30 | const is隋唐 = 選項.擬音版本?.includes('隋') ?? false; 31 | 32 | const 音標字典 = { 33 | '國際音標(原貌)': { 34 | ʰ: 'ʻ', ʔ: 'ˀ', 35 | ʱ: is史稿 ? 'ʻ' : '', 36 | ɡ: is史稿 ? 'g' : 'ɡ', 37 | ʒ: is史稿 ? 'ʒ' : 'ᶎ', 38 | }, 39 | '國際音標(通用)': { 40 | ʱ: is史稿 ? 'ʱ' : '', 41 | }, 42 | }; 43 | 44 | if (!音韻地位) return [ 45 | ['擬音版本', [1, '漢語史稿', '漢語語音史(魏晉南北朝音系)', '漢語語音史(隋——中唐音系)']], 46 | ['音標體系', [2].concat(Object.keys(音標字典))], 47 | ['知組記法', is隋唐 ? [1, 'ȶ', 't'] : null], 48 | ['聲調記法', [2, '不標', '四角標圈', '數字上標']], 49 | ]; 50 | 51 | function get聲母() { 52 | let 聲母 = { 53 | 幫: 'p', 滂: 'pʰ', 並: 'bʱ', 明: 'm', 54 | 端: 't', 透: 'tʰ', 定: 'dʱ', 泥: 'n',     來: 'l', 55 | 知: 't', 徹: 'tʰ', 澄: 'dʱ', 孃: 'n', 56 | 精: 'ts', 清: 'tsʰ', 從: 'dzʱ',   心: 's', 邪: 'z', 57 | 莊: 'tʃ', 初: 'tʃʰ', 崇: 'dʒʱ',   生: 'ʃ', 俟: 'ʒ', 58 | 章: 'tɕ', 昌: 'tɕʰ', 船: 'dʑʱ', 日: 'ȵ', 書: 'ɕ', 常: 'ʑ', 以: 'j', // 常船顛倒 59 | 見: 'k', 溪: 'kʰ', 羣: 'ɡʱ', 疑: 'ŋ', 曉: 'x', 匣: 'ɣ', 云: 'ɣ', 60 | 影: is史稿 ? '' : 'ʔ', 61 | }[音韻地位.母]; 62 | if (選項.知組記法 === 'ȶ' || is史稿) 聲母 = { 63 | 知: 'ȶ', 徹: 'ȶʰ', 澄: 'ȡʱ', 64 | }[音韻地位.母] || 聲母; 65 | if (is史稿) 聲母 = { 66 | 俟: 'dʒʱ', 日: 'nʑ', // 俟同崇 67 | }[音韻地位.母] || 聲母; 68 | return 聲母; 69 | } 70 | 71 | function get韻母() { 72 | // 漢語語音史 73 | let 韻 = { 74 | 鍾: '冬', 虞: '模', 齊: '祭', 臻: '真', 魂: '元', 痕: '元', 75 | 先: '仙', 蕭: '宵', 陽: '唐', 庚: '耕', 清: '耕', 尤: '侯', 幽: '侯', 添: '鹽', 凡: '嚴', 76 | }[音韻地位.韻] ?? 音韻地位.韻; 77 | if (is隋唐) 韻 = { 78 | 支: '脂', 之: '脂', 灰: '泰', 咍: '泰', 佳: '夬', 皆: '夬', 殷: '真', 山: '刪', 79 | 登: '蒸', 覃: '談', 咸: '銜', 80 | }[音韻地位.韻] || 韻; 81 | else 韻 = { 82 | 江: '冬', 佳: '泰', 皆: '廢', 夬: '祭', 灰: '廢', 咍: '廢', 殷: '文', 83 | 刪: is`入聲` ? '仙' : '寒', 山: is`入聲` ? '寒' : '仙', // 黠鎋顛倒 84 | 肴: '宵', 豪: '宵', 麻: '歌', 青: '耕', 談: '鹽', 咸: '覃', 銜: '鹽', 85 | }[音韻地位.韻] || 韻; 86 | const 元音表1 = { // 魏晉南北朝音系 87 |               u: '侯冬', 88 | e: '支耕脂真  ', ə: '之蒸微文 侵', o: '模東', ou: '宵', 89 | æ: '  祭仙 鹽', ɐ: ' 登廢元 嚴', ɔ: '魚 ', 90 |        ɑ: '歌唐泰寒 覃', 91 | }; 92 | const 元音表2 = { // 隋——中唐音系 93 | i: '脂青 真 侵',        u: '模冬    ', 94 |        ə: ' 蒸微文  ', o: '魚東  侯 ', 95 | æ: '  祭仙宵鹽', ɐ: ' 耕廢元 嚴', ɔ: ' 江    ', 96 | a: '麻 夬刪肴銜',        ɑ: '歌唐泰寒豪談', 97 | }; 98 | 99 | // 漢語史稿 100 | if (is史稿) 韻 = { 101 | 鍾: '冬', 虞: '模', 皆: '廢', 灰: '咍', 臻: '先', 殷: '文', 魂: '文', 痕: '文', 102 | 登: '蒸', 侯: '尤', 幽: '尤', 咸: '嚴', 凡: '嚴', 103 | }[音韻地位.韻] ?? 音韻地位.韻; 104 | const 元音表0 = { 105 | i: '脂     ',        u: '模東    ', 106 | ĕ: '   真 侵', 107 | e: '支青齊先蕭添', ə: '之蒸微文尤 ', o: '魚冬    ', 108 | ɛ: ' 清祭仙宵鹽',        ɔ: ' 江    ', 109 | æ: ' 耕夬山  ', ɐ: ' 庚廢元 嚴', ɒ: '  咍  覃', 110 | a: '麻陽佳刪肴銜',        ɑ: '歌唐泰寒豪談', 111 | }; 112 | 113 | const 韻尾列表 = is`舒聲` ? ['', ...'ŋinum'] : [...' k t p']; 114 | 115 | let 元音表 = is史稿 ? 元音表0 : is隋唐 ? 元音表2 : 元音表1; 116 | let 韻核 = Object.keys(元音表).find(e => 元音表[e].includes(韻)); 117 | let 韻尾 = 韻尾列表[元音表[韻核].indexOf(韻)]; 118 | let 介音 = ''; 119 | if (is史稿) { 120 | if (is`${TshetUinh.表達式.四等韻} 或 幽韻`) 介音 += 'i'; 121 | else if (is`三四等 非 脂幽韻`) 介音 += 'ĭ'; 122 | if (is`冬灰文魂韻 或 泰寒歌韻 非 開口 或 唐登韻 合口 或 真韻 合口 (A類 或 銳音 非 莊組)`) 介音 += 'u'; 123 | else if (is`合口 非 虞韻 或 鍾凡韻 或 幫組 微廢元陽韻`) 介音 += 'w'; 124 | } else { 125 | if (is`${TshetUinh.表達式.四等韻} 或 幽韻`) 介音 += !is`合口` && 韻核 === 'i' ? '' : 'i'; 126 | else if (is`三四等 非 臻幽韻`) 介音 += !is`合口` && 韻核 === 'i' ? '' : 'i̯'; 127 | if (is`二等 或 臻韻` && !is隋唐) 介音 += is`合口` ? 'o' : 韻核 === 'e' ? '' : 'e'; 128 | else if (is`合口 非 虞韻 或 幫組 微泰灰廢文元魂寒歌陽凡韻`) 介音 += 'u'; 129 | } 130 | 131 | return 介音 + 韻核 + 韻尾; 132 | } 133 | 134 | function get聲調() { 135 | return 選項.聲調記法 === '不標' ? '' : { 136 | 四角標圈: { 平: '꜀', 上: '꜂', 去: '꜄', 入: '꜆' }, // 四聲未分陰陽 137 | 數字上標: { 平: '¹', 上: '²', 去: '³', 入: '⁴' }, 138 | }[選項.聲調記法][音韻地位.聲]; 139 | } 140 | 141 | let 音節 = get聲母() + get韻母(); 142 | Object.entries(音標字典[選項.音標體系]).forEach(([k, v]) => { 音節 = 音節.replace(k, v); }); 143 | return (選項.聲調記法 === '四角標圈' && is`平上聲`) ? get聲調() + 音節 : 音節 + get聲調(); 144 | -------------------------------------------------------------------------------- /yec_en_hua.js: -------------------------------------------------------------------------------- 1 | /* 不通話 2 | * 3 | * https://www.zhihu.com/question/381250756/answer/2709792926 4 | * https://zhuanlan.zhihu.com/p/572453269 5 | * 6 | * @author 笑也 (JavaScript: unt) 7 | */ 8 | 9 | /** @type { 音韻地位['屬於'] } */ 10 | const is = (...x) => 音韻地位.屬於(...x); 11 | /** @type { 音韻地位['判斷'] } */ 12 | const when = (...x) => 音韻地位.判斷(...x); 13 | 14 | if (!音韻地位) return [ 15 | ['顯示', [1, '正字法', '國際音標']], 16 | ]; 17 | 18 | function get聲母() { 19 | return when([ 20 | // 單豎線左側爲正字法,右側爲國際音標,下同;雙豎線左清右濁 21 | ['云船母', '‖V|ʋ'], 22 | ['精組 三四等 或 知組', 'T|t‖D|n'], // 不含來母 23 | ['莊組', 'S|k‖R|ŋ'], 24 | ['', '|h‖ʻ|'], // 幫端章見影組、精組一等 25 | ]).split('‖')[+is`全濁 或 次濁`]; 26 | } 27 | 28 | function get介音() { 29 | return when([ 30 | ['一四等', '|'], 31 | ['二等', 'U|u'], 32 | // 以下爲三等 33 | ['精組 或 來以母', 'I|i'], 34 | ['銳音 或 云母', '|'], 35 | ['A類 非 麻幽韻', 'I|i'], 36 | ['', 'Y|y'], 37 | ]); 38 | // FIXME 麻幽韻此前是按C類推導,亦不分幽A/B, 39 | // 由於尚未檢驗修改它會造成的影響,故暫未修正。 40 | // (此外亦有:蒸C=B、蒸合=東三) 41 | } 42 | 43 | function get韻核() { 44 | const 韻核列表 = { 45 | 脂〇〇真幽臻: 'O|ʉ', 之蒸微殷〇侵: 'E|ə', 臻: 'O|ʉ', 尤侯東文: 'E|ə', 46 | 〇青齊先蕭添: 'A|a', 〇登咍痕〇覃: 'E|ə', 灰魂: 'E|ə', 模冬江: 'O|ʉ', 47 | 支〇祭仙宵鹽: 'A|a', 魚〇廢元〇嚴: 'E|ə', 凡: 'E|ə', 虞鍾: 'O|ʉ', 48 | 佳耕皆山〇咸: 'E|ə', 49 | 麻庚夬刪肴銜: 'A|a', 歌唐泰寒豪談: 'A|a', 清陽: 'A|a', 50 | }; 51 | return Object.entries(韻核列表).find(e => e[0].includes(音韻地位.韻))[1]; 52 | } 53 | 54 | function get韻尾() { 55 | return when([ 56 | ['齊微皆祭韻', 'J|j'], 57 | ['通江宕曾梗攝', 'N|̃'], 58 | ['深咸攝', 'V|ː'], 59 | ['', '|'], 60 | ]); 61 | } 62 | 63 | function get聲調() { 64 | return when([ 65 | ['全濁 上聲', 'L|˨˩'], 66 | ['入聲', 'C|˧˥'], 67 | ['', '|˥'], 68 | ]); 69 | } 70 | 71 | return [ 72 | get聲母(), get介音(), get韻核(), get韻尾(), get聲調(), 73 | ].map(e => e.split('|')[+(選項.顯示 === '國際音標')]).join(''); 74 | -------------------------------------------------------------------------------- /zaonhe.js: -------------------------------------------------------------------------------- 1 | /* 推導上海話 2 | * 3 | * https://zhuanlan.zhihu.com/p/386456940 4 | * 5 | * ——適改「上海市区方言志」音系 6 | * 7 | * 在墶「上海市区方言志」個基礎上向增加着一些理論白讀層搭着理論文讀層(參考「上海土白集字」),但是確保至少一個層次是「上海市区方言志」音系。 8 | * 提供了一些選項,其中「上海市区方言志」弗分尖團、弗分衣煙、弗分來蘭、弗分襪麥、弗分打黨、弗分肉月。 9 | * 10 | * @author Nyoeghau 11 | */ 12 | 13 | /** @type { 音韻地位['屬於'] } */ 14 | const is = (...x) => 音韻地位.屬於(...x); 15 | /** @type { 音韻地位['判斷'] } */ 16 | const when = (...x) => 音韻地位.判斷(...x); 17 | 18 | if (!音韻地位) 19 | return [ 20 | ['文白讀', [4, '文上白下', '僅白讀', '僅文讀', '主流層']], 21 | ['標調方式', [3, '數字調值', '折線', '附標', '八調序號', '弗標']], 22 | ['分尖團', [1, '分尖團', '區分⟨情、琴⟩', '區分⟨徐、齊⟩']], 23 | ['分衣煙|區分⟨衣、煙⟩', true], 24 | ['分來蘭|區分⟨來、蘭⟩', true], 25 | ['分袜麦|區分⟨袜、麦⟩', true], 26 | ['分打黨|區分⟨打、黨⟩', true], 27 | ['分肉月|區分⟨肉、月⟩', true], 28 | ['分國骨|區分⟨國、骨⟩', true], 29 | ['分于園|區分⟨于、園⟩', true], 30 | ['分干官|區分⟨干、官⟩', true], 31 | ['分困孔|區分⟨困、孔⟩', true], 32 | ['分羣窮|區分⟨羣、窮⟩', true], 33 | ]; 34 | 35 | const 元音 = 'iyɨʉɯuɪʏʊeøɘɵɤoəɛœɜɞʌɔæɐaɶäɑɒ'; 36 | const 元音Re = new RegExp('[' + 元音 + ']'); 37 | const 閉前元音 = 'iyɪʏ'; 38 | const 元音附標 = '̃̈'; 39 | const 顎化分尖團 = { 40 | n: 'ɲ', 41 | k: 'tɕ', 42 | kʰ: 'tɕʰ', 43 | h: 'ɕ', 44 | ŋ: 'ɲ', 45 | g: 'dʑ', 46 | }; 47 | const 顎化分情琴 = { 48 | ts: 'tɕ', 49 | tsʰ: 'tɕʰ', 50 | s: 'ɕ', 51 | z: 'ʑ', 52 | }; 53 | const 顎化弗分尖團 = { 54 | ʑ: 'dʑ', 55 | }; 56 | const 數字標調 = { 57 | 陰平: '⁵³', 58 | 陰去: '³³⁴', 59 | 陽去: '²³', 60 | 陰入: '⁵⁵', 61 | 陽入: '¹²', 62 | }; 63 | const 折線標調 = { 64 | 陰平: '˥˧', 65 | 陰去: '˧˧˦', 66 | 陽去: '˨˧', 67 | 陰入: '˥', 68 | 陽入: '˩˨', 69 | }; 70 | const 附標標調 = { 71 | 陰平: '᷇', 72 | 陰去: '̄', 73 | 陽去: '̌', 74 | 陰入: '̋', 75 | 陽入: '᷅', 76 | }; 77 | const 序號標調 = { 78 | 陰平: '¹', 79 | 陰去: '⁵', 80 | 陽去: '⁶', 81 | 陰入: '⁷', 82 | 陽入: '⁸', 83 | }; 84 | 85 | function 聲母規則(文讀) { 86 | let 聲母 = when([ 87 | // 顎化規則由獨立個 function 來做 88 | // '▽' 標識弗是主流層 89 | // 表一(of「上海市区方言志」) 90 | ['幫滂母 C類', 'f'], // 理論白讀 /p/ 91 | ['並母 C類', 文讀 ? 'v' : '▽▽b'], // 「防」等 92 | ['明母 C類 非 通流攝', 文讀 ? 'v' : '▽▽m'], // 「蚊」等 93 | ['幫母', 'p'], ['滂母', 'pʰ'], ['並母', 'b'], ['明母', 'm'], 94 | // 表二 95 | ['端母', 't'], ['透母', 'tʰ'], ['定母', 'd'], ['泥孃母', 'n'], ['來母', 'l'], 96 | // 表四 97 | ['見母', 'k'], ['溪母', 'kʰ'], ['曉母', 'h'], ['羣母', 'g'], [!文讀 && '日母', 'n'], 98 | // 表五 99 | ['日母 止攝 開口', '▽▽ɦ'], 100 | ['疑母', [ 101 | ['遇攝 一等 上聲', 文讀 ? '▽▽ʔ' : 'ŋ'], // 「五」。表中無 102 | ['合口 麻泰歌韻 或 開合中立 非 侯韻', 文讀 ? '▽▽ɦ' : 'ŋ'], // 「外」「瓦」「臥」。「吾」等 103 | ['合口', 文讀 ? 'ɦ' : '▽▽ŋ'], // 「魏」等 104 | [文讀 && '二三四等', '▽▽ɦ'], // 「言」等 105 | ['', 'ŋ'], 106 | ]], 107 | ['以母 脂韻 合口', 'v'], // 表中無 108 | // ['以母 上聲 通攝', 'ʔ'], // 似無規律。弗收 109 | // ['以苡已勇蛹涌恿甬俑踊慂𧻹悀埇𧗴'.includes(字頭) && '以母 上聲', 'ʔ'], 110 | ['云母 上去聲 梗攝', 'ʔ'], // 表中無 111 | ['匣云以母', 'ɦ'], 112 | ['影母', 'ʔ'], 113 | // 表三 114 | ['精知莊章母', 'ts'], ['清徹初昌母', 'tsʰ'], ['心生書母', 's'], ['從邪澄崇俟常船日母', 'z'], 115 | ], '無聲母規則'); 116 | return 聲母; 117 | } 118 | 119 | function 韻母規則(文讀) { 120 | let 韻母 = when([ 121 | // 特殊韻母 122 | [!文讀 && '無嘸无呒'.includes(字頭), '̩'], 123 | [!文讀 && '畝𠭇畮畞亩'.includes(字頭), '̩'], 124 | [!文讀 && '魚鱼'.includes(字頭), '̩'], 125 | [!文讀 && '吳吴'.includes(字頭), '̩'], 126 | [!文讀 && '疑母 模韻 上聲', '̩'], // 「五」「午」 127 | 128 | ['通攝 舒聲', [ 129 | ['三等 (孃疑母 或 喉音)', 'ioŋ'], 130 | ['三等 牙音', 文讀 ? 'oŋ' : 'ioŋ▽'], // 「龔」等 131 | ['三等 日母', 文讀 ? 'oŋ▽' : 'ioŋ'], // 「茸」等 132 | ['', 'oŋ'], // 脣音白讀僅「夢」。待考 133 | ]], 134 | ['通攝 入聲', [ 135 | ['三等 (孃母 或 牙喉音)', 'ioʔ'], 136 | ['三等 日母', 文讀 ? 'oʔ▽' : 'ioʔ'], // 「肉」等 137 | ['', 'oʔ'], 138 | ]], 139 | ['江攝 舒聲', [ 140 | [文讀 && '牙音', 'iã▽'], // 「江」等 141 | ['', 'ɑ̃'], 142 | ]], 143 | ['江攝 入聲', [ 144 | // [文讀 && '牙音 非 疑母', 'ioʔ▽'], // 「確」等。箇個同「上海市区方言志」音系 145 | [文讀 && '牙喉音', 'iɑʔ▽'], // 「確」等。箇個同「上海土白集字」音系。「樂」等。疑母文讀同匣母。「學」等 146 | ['', 'oʔ'], 147 | ]], 148 | ['止攝', [ 149 | [文讀 && '開口 日母', 'əɭ'], 150 | ['開口 (端組 或 來孃日母)', 'i'], 151 | ['開口 舌齒音', 'z̩'], 152 | ['開口 或 脣音', 'i'], 153 | ['合口 以母 脂韻', 'i'], 154 | ['合口 來孃母', 'e'], 155 | ['合口 日母', 文讀 ? 'ø' : 'y▽'], // 「蕊」 156 | ['合口 舌齒音', 文讀 ? 'ø' : 'z̩▽'], // 「吹」等 157 | [!文讀 && '合口 牙音 非 疑母', 'y▽'], // 「跪」等 158 | ['合口', 'ue'], 159 | ]], 160 | ['遇攝', [ 161 | [!文讀 && '魚韻 (來孃日母 或 精組)', 'i▽'], // 「呂」等。「女」(老派)。日母白讀類推無實例。「絮」等 162 | [!文讀 && '魚韻 莊組', 'z̩▽'], // 「鋤」等 163 | [!文讀 && '三等 日母', 'y▽'], // 白讀類推無實例 164 | ['三等 莊組', 'u'], 165 | ['C類 脣音', 'u'], 166 | ['三等 舌齒音 非 精組 非 來孃母', 'z̩'], 167 | ['三等', 'y'], // 牙喉音白讀 /e/「許」「鋸」。白讀 /i/「去」「渠」。屬於特殊存古,且弗唯一,故弗用 168 | ['一等', 'u'], 169 | ]], 170 | ['蟹攝', [ 171 | [!文讀 && '三等 合口 日母', 'i▽'], // 白讀類推無實例 172 | ['三四等 合口 舌齒音', 'ø'], 173 | ['三四等 合口', 'ue'], 174 | ['莊組 開口', 'a'], 175 | ['三等 舌齒音 非 精組 非 來母', 'z̩'], 176 | ['三四等', 'i'], 177 | ['二等 開口 匣母', 文讀 ? 'ie' : 'a▽'], // 「械」等。理論上還有 /ia/ 個白讀 178 | ['二等 開口 牙喉音', 文讀 ? 'ia' : 'a▽'], // 「解」等 179 | ['二等 合口 疑母', 文讀 ? 'ue' : 'a▽'], // 字典弗曾收「聵」 180 | ['二等 合口 牙喉音', 文讀 ? 'ua' : 'o▽'], // 「卦」等 181 | ['二等 合口 舌齒音', 'ø'], 182 | ['二等', 'a'], 183 | ['泰韻 開口 舌齒音', 'a'], 184 | ['泰韻 合口 疑母', 文讀 ? 'ue▽' : 'a'], // 「外」 185 | ['一等 合口 精組', 'ø'], 186 | ['一等 合口 牙喉音', 'ue'], 187 | ['一等', 'e'], 188 | ]], 189 | ['臻攝 舒聲', [ 190 | [文讀 && '三等 日母', 'əŋ▽'], // 「人」「閏」等 191 | ['三等 開口 知章莊組', 'əŋ'], 192 | ['C類 脣音', 'əŋ'], 193 | ['三等 (開口 或 脣音)', 'ɪɲ'], 194 | ['三等 合口 精組 或 日母', 'ɪɲ'], 195 | ['三等 合口 舌齒音', 'əŋ'], 196 | ['三等 合口', 'yɪɲ'], 197 | [!文讀 && '一等 合口 疑母', 'əŋ▽'], // 「諢」。白讀類推無實例 198 | ['一等 合口 牙喉音', 'uəŋ'], 199 | ['一等', 'əŋ'], 200 | ]], 201 | ['臻攝 入聲', [ 202 | [文讀 && '三等 日母', 'əʔ▽'], // 「日」等 203 | ['三等 開口 知章莊組', 'əʔ'], 204 | ['C類 脣音', 'əʔ'], 205 | ['三等 (開口 或 脣音)', 'iɪʔ'], 206 | ['三等 合口 (精組 或 來母)', 'iɪʔ'], 207 | ['三等 合口 舌齒音', 'əʔ'], 208 | ['三等 合口', 'yɪʔ'], 209 | ['一等 合口 疑母', 文讀 ? 'uəʔ▽' : 'əʔ'], // 「兀」 210 | ['一等 合口 牙喉音', 'uəʔ'], 211 | ['一等', 'əʔ'], 212 | ]], 213 | ['山攝 舒聲', [ 214 | ['三等 開口 知章組', 'ø'], 215 | ['三等 開口 日母', 文讀 ? 'ø' : 'iɪ▽'], // 「燃」等 216 | ['C類 脣音', 'æ'], 217 | ['三四等 (開口 或 脣音)', 'iɪ'], 218 | ['三等 合口 日母', 文讀 ? 'ø▽' : 'yø'], // 「軟」等 219 | ['三四等 合口 (精組 或 來母)', 'iɪ'], // 白讀 /æ/ 或 /ø/ 只有「全」特殊存古。故弗列 220 | ['三四等 合口 舌齒音', 'ø'], 221 | ['三四等 合口', 'yø'], 222 | [文讀 && '二等 開口 牙喉音', 'iɪ▽'], // 「間」等 223 | ['二等 合口 牙喉音', 'uæ'], 224 | ['二等 合口 舌齒音', 'ø'], 225 | ['二等', 'æ'], 226 | ['一等 開口 舌齒音', 'æ'], 227 | ['一等 合口 牙喉音', 'uø'], 228 | ['一等', 'ø'], 229 | ]], 230 | ['山攝 入聲', [ 231 | ['三等 知莊章組', 'əʔ'], 232 | [文讀 && '三等 日母', 'əʔ▽'], // 「熱」「爇」等 233 | ['C類 脣音', 'ɐʔ'], 234 | ['三四等 (開口 或 脣音)', 'iɪʔ'], 235 | ['三四等 合口 舌齒音', 'iɪʔ'], 236 | ['三四等 合口', 'yɪʔ'], 237 | [文讀 && '二等 開口 疑匣母', 'iɐʔ▽'], // 「齾」。「轄」 238 | ['二等 開口 舌齒音 日母', 文讀 ? 'əʔ▽' : 'iɪʔ'], // 字典弗曾收「𩭿」 239 | ['二等 合口 牙喉音 疑母', 文讀 ? 'uɐʔ▽' : 'ɐʔ'], // 字典弗曾收「刖」 240 | ['二等 合口 牙喉音', 'uɐʔ'], 241 | ['二等 合口 舌齒音', 'əʔ'], 242 | ['二等', 'ɐʔ'], 243 | ['一等 開口 舌齒音', 'ɐʔ'], 244 | ['一等 合口 牙喉音 見曉影母', 'uɐʔ'], 245 | ['一等 合口 牙喉音 疑母', 文讀 ? 'uəʔ▽' : 'əʔ'], // 字典弗曾收「枂」 246 | ['一等 合口 牙喉音', 'uəʔ'], 247 | ['一等', 'əʔ'], 248 | ]], 249 | ['效攝', [ 250 | [文讀 && '二等 牙喉音', 'iɔ▽'], // 「交」等 251 | ['三四等 非 知章組', 'iɔ'], 252 | ['', 'ɔ'], 253 | ]], 254 | ['果攝', [ 255 | [!文讀 && '一等 開口 (端組 或 喉音)', 'a▽'], // 「大」「何」等 256 | ['一等 或 三等 脣音', 'u'], // 「縛」 257 | ['開口', 文讀 ? 'ia▽' : 'a'], // 「茄」等 258 | ['合口', 文讀 ? 'ia▽' : 'io'], // 「瘸」等 259 | ]], 260 | ['假攝', [ 261 | [文讀 && '二等 牙喉音 合口', 'ua▽'], // 「花」等。脆麻花~ 262 | [文讀 && '二等 牙喉音', 'ia▽'], // 「下」等 263 | [文讀 && '二等', 'a▽'], // 「馬」「差」等 264 | ['二等 開口 牙音', 'a'], // 「家」等 265 | ['二等', 'o'], 266 | [文讀 && '牙喉音', 'ie▽'], // 「也」等 267 | [文讀 && '脣音 或 端精組', 'i▽'], // 「乜」。「姐」等 268 | ['舌齒音 日母', 文讀 ? 'a' : 'ia▽'], // 「惹」等 269 | ['舌齒音 章組', 文讀 ? 'o' : 'a▽'], // 「射」等 270 | ['', 'ia'], 271 | ]], 272 | ['宕攝 舒聲', [ 273 | ['三等 日母', 文讀 ? 'ɑ̃▽' : 'iã'], // 「壤」等 274 | ['三等 (精組 或 來孃母)', 'iã'], 275 | ['三等 知組', 'ã'], 276 | ['三等 (舌齒音 或 C類 脣音)', 'ɑ̃'], 277 | ['三等 (開口 或 脣音)', 'iã'], 278 | [!文讀 && '三等 合口 云母', 'iã▽'], // 「旺」等 279 | ['三等 合口', 'uɑ̃'], 280 | ['一等 合口 牙喉音', 'uɑ̃'], 281 | ['一等', 'ɑ̃'], 282 | ]], 283 | ['宕攝 入聲', [ 284 | ['三等 日母', 文讀 ? 'ɑʔ' : 'iɑʔ▽'], // 「箬」等 285 | ['三等 (精組 或 來孃母)', 'iɑʔ'], 286 | ['三等 (莊組 或 C類 脣音)', 'oʔ'], 287 | ['三等 舌齒音', 'ɑʔ'], 288 | ['三等 (開口 或 脣音)', 'iɑʔ'], 289 | ['三等 合口 牙喉音', 'ioʔ'], 290 | ['一等', 'oʔ'], 291 | ]], 292 | ['梗攝 舒聲', [ 293 | ['三四等 知章組', 文讀 ? 'əŋ' : 'ã▽'], // 「聲」「省」等 294 | [!文讀 && '三四等 開口 牙喉音', 'iã▽'], // 「映」等 295 | ['三四等 合口 牙喉音', 'ioŋ'], // 有異讀 /ɪɲ/ 296 | ['三四等 非 莊組', 'ɪɲ'], 297 | ['耕韻 開口 牙喉音', 文讀 ? 'ɪɲ' : 'ã▽'], // 「櫻」等 298 | ['耕韻 合口 喉音', 'oŋ'], 299 | ['二等 合口 牙喉音', 'uɑ̃'], 300 | ['二等 或 莊組', 文讀 ? 'əŋ▽' : 'ã'], // 「萌」「猛」「爭」「澄」「更」等 301 | ]], 302 | ['梗攝 入聲', [ 303 | ['三四等 知章組', 文讀 ? 'əʔ▽' : 'ɑʔ'], // 「適」等 304 | ['三四等 合口 牙喉音', 'ioʔ'], 305 | ['三四等', 'iɪʔ'], 306 | ['二等 合口 牙喉音', 'oʔ'], 307 | ['二等', 文讀 ? 'əʔ▽' : 'ɑʔ'], // 「脈」「白」「革」「客」「責」「澤」等。「脈」「白」「客」收於「上海土白集字」 308 | ]], 309 | ['曾攝 舒聲', [ 310 | [文讀 && '三等 日母', 'əŋ▽'], // 「仍」等 311 | ['三等 舌齒音 非 來日母', 文讀 ? 'əŋ' : 'ã▽'], // 「剩」等 312 | ['三等', 'ɪɲ'], 313 | ['一等 明母', 文讀 ? 'oŋ' : 'ɑ̃▽'], // 「懵」 314 | ['一等 脣音', 文讀 ? 'əŋ▽' : 'ã'], // 「崩」等 315 | ['一等 開口', 'əŋ'], // 無白讀例 316 | ['一等 合口', 'oŋ'], 317 | ]], 318 | ['曾攝 入聲', [ 319 | [文讀 && '三等 日母', 'əʔ▽'], // 「仍」等 320 | ['三等 舌齒音 非 精組 非 來孃日母', 'əʔ'], 321 | ['三等 合口', 'ioʔ'], 322 | ['三等', 'iɪʔ'], 323 | ['一等 明母', 'əʔ'], 324 | ['一等 脣音', 'oʔ'], 325 | ['一等 開口', 'əʔ'], 326 | ['一等 合口', 'oʔ'], 327 | ]], 328 | ['流攝', [ 329 | ['幽韻 明母', 文讀 ? 'iɤ▽' : 'iɔ'], // 「繆」等。白讀地位實不對應 330 | ['幽韻 脣音', 'iɔ'], // 地位實不對應 331 | ['三等 日母', 文讀 ? 'ɤ' : 'iɤ▽'], // 「柔」等 332 | ['三等 舌齒音 非 精組 非 來孃母', 'ɤ'], 333 | ['三等 C類 脣音 去聲', 'u'], 334 | ['三等 C類 脣音', 'ɤ'], 335 | ['三等', 'iɤ'], 336 | ['一等', 'ɤ'], 337 | ]], 338 | ['深攝 舒聲', [ 339 | ['日母', 文讀 ? 'əŋ' : 'ɪɲ▽'], // 「任」等 340 | ['舌齒音 非 精組 非 來孃母', 'əŋ'], 341 | ['', 'ɪɲ'], 342 | ]], 343 | ['深攝 入聲', [ 344 | ['日母', 文讀 ? 'əʔ' : 'iɪʔ▽'], // 「入」等 345 | ['舌齒音 非 精組 非 來孃母', 'əʔ'], 346 | ['', 'iɪʔ'], 347 | ]], 348 | ['咸攝 舒聲', [ 349 | ['三等 日母', 文讀 ? 'ø' : 'iɪ▽'], // 「染」等 350 | ['三等 舌齒音 非 來孃母 非 精組', 'ø'], 351 | ['C類 脣音', 'æ'], 352 | ['三四等', 'iɪ'], 353 | ['二等 孃母', 'ø'], // 地位實不對應。「喃」 354 | ['二等 來母', 'iɪ'], // 地位實不對應。「臉」 355 | [文讀 && '二等 牙喉音', 'iɪ▽'], // 「減」等 356 | ['二等', 'æ'], 357 | ['覃韻 端定來溪母', 'æ'], // 也有讀 /ø/ 個口音,但是弗在「上海市區方言志」 358 | ['談韻 舌齒音', 'æ'], 359 | ['一等', 'ø'], 360 | ]], 361 | ['咸攝 入聲', [ 362 | ['三等 日母', 文讀 ? 'əʔ▽' : 'iɪʔ'], // 無文讀例 363 | ['三等 舌齒音 非 來孃母 非 精組', 'əʔ'], 364 | ['C類 脣音', 'ɐʔ'], 365 | ['三四等', 'iɪʔ'], 366 | [文讀 && '二等 牙喉音', 'iɐʔ▽'], // 「甲」等 367 | ['二等', 'ɐʔ'], 368 | ['覃韻 舌齒音', 文讀 ? 'əʔ▽' : 'ɐʔ'], // 「答」等 369 | ['談韻 舌齒音', 'ɐʔ'], 370 | ['一等', 'əʔ'], 371 | ]], 372 | ], '無韻母規則'); 373 | 374 | let is主流層 = !韻母.includes('▽'); 375 | 韻母 = 韻母.replace('▽', ''); 376 | 韻母 = { 377 | 'iɪ': !選項.分衣煙 && 'i', 378 | 'æ': !選項.分來蘭 && 'e', 379 | 'uæ': !選項.分來蘭 && 'ue', 380 | 'ɑʔ': !選項.分袜麦 && 'ɐʔ', 381 | 'iɑʔ': !選項.分袜麦 && 'iɐʔ', 382 | 'ã': !選項.分打黨 && 'ɑ̃', 383 | 'iã': !選項.分打黨 && 'iɑ̃', 384 | 'ioʔ': !選項.分肉月 && 'yɪʔ', 385 | 'uəʔ': !選項.分國骨 && 'oʔ', 386 | 'yø': !選項.分于園 && 'y', 387 | 'io': !選項.分于園 && 'y', 388 | 'uø': !選項.分干官 && 'ø', 389 | 'uəŋ': !選項.分困孔 && 'oŋ', 390 | 'yɪɲ': !選項.分羣窮 && 'ioŋ', 391 | }[韻母] || 韻母; 392 | if (!is主流層) 韻母 += '▽'; 393 | return 韻母; 394 | } 395 | 396 | function 顎化規則(音節) { 397 | const match = 元音Re.exec(音節); 398 | if (match !== null) { 399 | if (閉前元音.includes(match[0])) { 400 | for (let 聲母 in 顎化分尖團) 音節 = 音節.replace(聲母, 顎化分尖團[聲母]); 401 | if (選項.分尖團 !== '分尖團') { 402 | for (let 聲母 in 顎化分情琴) 403 | 音節 = 音節.replace(聲母, 顎化分情琴[聲母]); 404 | } 405 | if (選項.分尖團 === '區分⟨徐、齊⟩') { 406 | if (is`從崇常母`) { 407 | for (let 聲母 in 顎化弗分尖團) 408 | 音節 = 音節.replace(聲母, 顎化弗分尖團[聲母]); 409 | } 410 | } 411 | } 412 | } 413 | return 音節; 414 | } 415 | 416 | function 主流層選擇規則(音們) { 417 | let 音們_非主流度們 = []; 418 | 音們.forEach(音 => { 419 | 音們_非主流度們.push({ 音, 非主流度: (音.match(/▽/g) || []).length }); 420 | }); 421 | return 音們_非主流度們.reduce((prev, curr) => { 422 | return prev.非主流度 < curr.非主流度 ? prev : curr; 423 | }).音; 424 | } 425 | 426 | function 聲調規則(音節) { 427 | const 聲調 = when([ 428 | ['云母 上去聲 梗攝', '陰去'], // 由於聲母例外 429 | // ['以母 上聲 通攝', '陰去'], // 由於聲母例外 430 | ['疑母 上聲 模韻', '陰去'], 431 | ['全清 或 次清', [['平聲', '陰平'], ['上去聲', '陰去'], ['入聲', '陰入']]], 432 | ['全濁 或 次濁', [['舒聲', '陽去'], ['入聲', '陽入']]], 433 | ], '無聲調規則'); 434 | 435 | if (選項.標調方式 === '附標') { 436 | let 標調位置; 437 | const match = 元音Re.exec(音節); 438 | if (match !== null) { 439 | 標調位置 = match.index; 440 | if (元音.includes(音節[標調位置 + 1])) 標調位置 += 1; // 弗要標在介音上 441 | if (元音附標.includes(音節[標調位置 + 1])) 標調位置 += 1; // 弗要標在附標下頭 442 | } else { 443 | 標調位置 = 音節.indexOf('̩'); 444 | } 445 | 標調位置 += 1; 446 | return 音節.slice(0, 標調位置) + 附標標調[聲調] + 音節.slice(標調位置); 447 | } else if (選項.標調方式 === '數字調值') { 448 | return 音節 + 數字標調[聲調]; 449 | } else if (選項.標調方式 === '折線') { 450 | return 音節 + 折線標調[聲調]; 451 | } else if (選項.標調方式 === '八調序號') { 452 | return 音節 + 序號標調[聲調]; 453 | } else { 454 | return 音節; 455 | } 456 | } 457 | 458 | function finalise(音節) { 459 | 音節 = 音節.replace(/▽/g, ''); 460 | 音節 = 顎化規則(音節); 461 | 音節 = 聲調規則(音節); 462 | return 音節; 463 | } 464 | 465 | let 文讀聲母 = 聲母規則(true); 466 | let 白讀聲母 = 聲母規則(false); 467 | 468 | let 文讀韻母 = 韻母規則(true); 469 | let 白讀韻母 = 韻母規則(false); 470 | 471 | let 文讀音 = 文讀聲母 + 文讀韻母; 472 | let 白讀音 = 白讀聲母 + 白讀韻母; 473 | 474 | let 結果; 475 | if (選項.文白讀 === '主流層') { 476 | 結果 = 主流層選擇規則([文讀音, 白讀音]); 477 | 結果 = finalise(結果); 478 | } else { 479 | if (選項.文白讀 === '僅白讀') 結果 = finalise(白讀音); 480 | else if (選項.文白讀 === '僅文讀') 結果 = finalise(文讀音); 481 | else if (文讀音 === 白讀音) 結果 = finalise(文讀音); 482 | else 結果 = finalise(文讀音) + '\n' + finalise(白讀音); 483 | } 484 | 485 | return 結果; 486 | -------------------------------------------------------------------------------- /zhongyuan.js: -------------------------------------------------------------------------------- 1 | /* 推導《中原音韻》 2 | * 3 | * 可選四家擬音方案: 4 | * 5 | * - 楊耐思. 中原音韻音系. 北京: 中國社會科學出版社, 1981. 6 | * - 寧繼福. 中原音韻表稿. 長春: 吉林文史出版社, 1985. 7 | * - 薛鳳生. 中原音韻音位系統. 魯國堯, 侍建國, 譯. 北京: 北京語言學院出版社, 1990. 8 | * - unt. 《中原音韻》音系簡述, 2021. https://zhuanlan.zhihu.com/p/353713058 9 | * 10 | * @author unt 11 | */ 12 | 13 | /** @type { 音韻地位['屬於'] } */ 14 | const is = (...x) => 音韻地位.屬於(...x); 15 | /** @type { 音韻地位['判斷'] } */ 16 | const when = (...x) => 音韻地位.判斷(...x); 17 | 18 | if (!音韻地位) return [ 19 | ['顯示', [5, 20 | '音位(薛鳳生, 1990)', 21 | '音位(unt, 2021)', 22 | '音值(楊耐思, 1981)', 23 | '音值(寧繼福, 1985)', 24 | '音值(unt, 2021)', 25 | ]], 26 | ['標記古入聲字|\n入聲在四聲標記(陰 ¹、陽 ²、上 ³、去 ⁴)後加 ʼ', true], 27 | ['包含部分例外音變', true], 28 | ['異讀分隔符|異讀分隔符(留空則爲換行)', ''], 29 | ].concat(選項.顯示?.includes('unt') === false ? [] : [ 30 | ['高元音開口呼|\n如選 ə 則支思韻寫 ɹ̩、ɻ̍', [1, 'ɨ', 'ə']], 31 | ['皆來韻古入聲字韻基', [1, 'ɛj', 'aj']], 32 | ['皆來韻梗二開見入|其中的見二開韻母\n韻基擬 aj 的情況下,是否有 j 介音無法確定', [2, 'aj', '(j)aj', 'jaj'], { hidden: 選項.皆來韻古入聲字韻基 !== 'aj' }], 33 | ]); 34 | 35 | const 例外 = 選項.包含部分例外音變; 36 | let 層次 = 0; // 本方案只涉及 0 和 1 兩個層次。對入聲來說,0 代表白讀,1 代表文讀 37 | 38 | function 調整音韻地位() { 39 | function 調整(表達式, 調整屬性, 邊緣地位種類 = []) { if (is(表達式)) 音韻地位 = 音韻地位.調整(調整屬性, 邊緣地位種類); } 40 | // 輕唇化例外 41 | 調整('明母 尤韻', { 等: '一', 類: null, 韻: '侯' }); 42 | 調整('明母 東韻', { 等: '一', 類: null }); 43 | 44 | 調整('云母 通攝 平聲', { 母: '匣' }, ['匣母三等']); // 熊 45 | if (!例外) return; 46 | 47 | // 流攝脣音入遇攝字 48 | 調整('明母 侯韻 上聲', { 韻: '模' }); // 母某牡畝(謀戊在《廣韻》中涉及的字太多,不加入) 49 | if (層次 === 0) 調整('尤韻 並母 平上聲', { 韻: '虞' }); // 浮、婦阜負(《中原音韻》僅魚模,元曲押魚模、尤侯) 50 | 調整('尤韻 幫滂母 去聲', { 韻: '虞' }); // 富、副 51 | 52 | // 蟹攝二等入假攝字 53 | if (when([ 54 | ['佳韻', [ 55 | ['並母 上聲', true], // 罷 56 | ['見母 開口 平聲', true], // 佳 57 | ['溪母 合口 平聲', true], // 咼 58 | ['見匣母 合口 去聲', true], // 卦掛、畫 59 | [層次 === 1 && '疑母 開口 平聲', true], // 涯\崖 60 | [層次 === 1 && '生母 開口 去聲', true], // 洒\曬 61 | ]], 62 | ['夬韻 匣母 合口 去聲', true], // 話 63 | ])) 調整('蟹攝', { 韻: '麻' }); 64 | 65 | 調整('端母 蕭韻 上聲', { 母: '泥' }); // 鳥 66 | 調整('生母 山韻 上聲', { 母: '初' }); // 産 67 | 調整('書母 通攝 舒聲', { 母: '昌' }); // 舂 68 | 調整('書母 鍾韻 入聲', { 母: '昌' }); // 束 69 | if (層次 === 0) 調整('書母 支韻 開口 去聲', { 母: '昌' }); // 翅\施 70 | 調整('見母 蕭韻 平聲', { 母: '曉' }); // 梟鴞驍 71 | 72 | if (層次 === 0) { 73 | 調整('果攝 開口 (定泥母 去聲 或 透母 平聲)', { 韻: '麻', 等: '二' }, ['端組類隔']); // 大那+他 74 | 調整('端母 庚韻 上聲', { 韻: '麻' }, ['端組類隔']); // 打 75 | } 76 | } 77 | 78 | function get聲母() { 79 | return when([ 80 | [例外, [ 81 | ['崇母 止攝 仄聲', 'ʂ'], // 士 82 | ['常母 平聲 (支韻 合口 或 魚尤宵韻)', 'tʂʰ'], // 垂蜍讎韶 83 | ['常母 深攝 平聲', 'ʂ'], // 忱煁 84 | ['船母 平聲 合口', 'tʂʰ'], // 船唇 85 | [層次 === 1 && '船母 曾攝 平聲', 'tʂʰ'], // 乗\繩 86 | 87 | [層次 === 1 && '匣母 寒韻 合口 平聲', ''], // 丸\桓 88 | ['以母 蟹攝 合口', 'ɻ'], // 鋭 89 | [層次 === 0 && '脂韻 以母 合口 平聲', 'ʋ'], // 惟\遺 90 | ['疑母', [ 91 | ['宕攝 三等 開口', 'ŋ'], // 仰、虐瘧 92 | ['山攝 三四等 開口 入聲', 'n'], // 囓臬糵 93 | ['咸攝 三四等 入聲', 'ŋ'], // 業鄴 94 | [層次 === 1 && '梗攝 二等 入聲', 'ŋ'], // 額 95 | ['效攝 一等 仄聲', 'ŋ'], // 傲奡鏊 96 | ]], // 俺《廣韻》未收,不考慮 97 | // x 爻。《中州樂府音韻類編》與哮小韻陰陽配對,《中州音韻》與遙小韻合併 98 | ]], 99 | 100 | ['幫組 C類', [ 101 | ['明母', 'ʋ'], ['', 'f'], 102 | ]], 103 | ['幫母 或 並母 仄聲', 'p'], ['滂並母', 'pʰ'], ['明母', 'm'], 104 | ['端母 或 定母 仄聲', 't'], ['透定母', 'tʰ'], ['泥孃母', 'n'], ['來母', 'l'], 105 | ['見母 或 羣母 仄聲', 'k'], ['溪羣母', 'kʰ'], ['影疑云以母', ''], ['曉匣母', 'x'], 106 | ['精母 或 從母 仄聲', 'ts'], ['清從母', 'tsʰ'], ['心邪俟母', 's'], 107 | 108 | ['常母 平聲 陽聲韻', 'tʂʰ'], 109 | ['知莊章母 或 澄崇母 仄聲', 'tʂ'], ['徹澄初崇昌母', 'tʂʰ'], ['生俟常書船母', 'ʂ'], ['日母', 'ɻ'], 110 | ], '無聲母規則', true); 111 | } 112 | 113 | function get韻母() { 114 | let 洪細 = when([ 115 | [例外, [ 116 | ['見影組 二等 開口 梗攝 平聲', [ 117 | ['曉母', ''], // 亨 118 | ['匣母 耕韻', ''], // 莖 119 | [層次 === 1 && '影母', ''], // 甖 120 | ]], 121 | ['通攝 三等', [ 122 | ['日母 入聲', 'j'], // 辱褥 123 | ['溪羣母 平聲', 'j'], // 穹芎、窮藭蛩卭笻 124 | [層次 === 1 && '影母 平聲', ''], // 癰廱壅\邕嗈雍 125 | ]] 126 | ]], 127 | 128 | ['幫組 C類 止蟹攝', 'j'], 129 | ['幫組 C類', ''], 130 | ['莊組', ''], 131 | 132 | ['(精章組 或 日母) 止攝 開口', ''], 133 | ['宕攝 合口 三等', [ 134 | [層次 === 1 && '入聲', 'j'], 135 | ['', ''], 136 | ]], 137 | ['通攝 三等', [ 138 | ['知章組 非 孃母 或 日母', [ 139 | ['舒聲', ''], 140 | [層次 === 1 && '入聲', ''], 141 | ]], 142 | ['見溪羣母 舒聲', ''], // 弓拱恐共 143 | ]], 144 | 145 | ['三四等 或 見影組 二等 非 合口', 'j'], 146 | ['', ''], 147 | ], '無洪細規則', true); 148 | 149 | let 開合 = when([ 150 | [例外, [ 151 | ['止蟹攝 四等 合口 匣母 平聲', ''], // 畦携 152 | ['止蟹攝 A類 合口 見母 去聲', ''], // 季 153 | ['脂韻 以母 合口 平聲', ''], // 遺 154 | [層次 === 1 && '以母 合口 山攝', ''], // 緣沿掾\捐鉛鳶 155 | [層次 === 1 && '曉匣母 先韻 合口', ''], // 懸縣血 156 | ['梗攝 三四等 合口 非 B類', [ 157 | ['入聲', ''], 158 | [層次 === 1 && '舒聲', ''], 159 | ]], 160 | 161 | ['見組 祭韻 合口', ''], // 鱖 162 | 163 | [層次 === 0 && '疑母 歌韻 開口 上聲', 'w'], // 我 164 | [層次 === 1 && '定母 宕江攝 入聲', ''], // 鐸 165 | 166 | ['止蟹攝 B類', [ 167 | ['幫母 去聲', ''], // 秘祕賁\詖 168 | ['滂母 上聲', ''], // 嚭 169 | ]], 170 | ['止蟹攝 A類', [ 171 | ['幫母 平聲', 'w'], // 卑(避諱) 172 | ['幫母 支韻 去聲', 'w'], // 臂 173 | ['並母 仄聲', 'w'], // 婢避幣\斃 174 | ['明母 去聲', 'w'], // 袂寐 175 | ]], 176 | ]], 177 | 178 | ['幫組', [ 179 | ['一等 非 通宕曾流效攝', 'w'], 180 | [層次 === 1 && '宕攝 入聲', 'w'], 181 | ['曾攝 一等 入聲', 'w'], 182 | ['文歌韻', 'w'], 183 | ['(止蟹攝 或 臻攝 入聲) B類', 'w'], // 蟹攝幫三實際上無 B 類 184 | ]], 185 | 186 | ['果江攝 銳音 或 宕攝 莊組', 'w'], 187 | [層次 === 1 && '宕攝 入聲 銳音', 'w'], 188 | ['合口', 'w'], 189 | ['', ''], 190 | ], '無開合規則', true); 191 | 192 | let 韻基 = when([ 193 | [例外, [ 194 | [層次 === 1 && '心母 止攝 開口 上聲 非 脂韻', 'jɨj'], // 璽枲徙\死(避諱。元曲“徙”押支思) 195 | [層次 === 1 && '昌母 止攝 開口 非 (之韻 上聲 或 支韻 去聲)', 'jɨj'], // 蚩媸鴟幟熾\齒(元曲押支思)\\翅施 196 | ['知母 開口 (脂韻 平聲 或 之韻 上聲)', 'ɨ'], // 胝(元曲無)、徵(元曲押支思) 197 | 198 | [層次 === 0 && '幫滂並母 尤韻 仄聲', 'ʌw'], // 缶覆 199 | [層次 === 0 && '滂母 侯韻 上聲', 'ʌw'], // 剖 200 | [層次 === 0 && '明母 侯韻 去聲', 'aw'], // 茂 201 | ['效攝 一等 明母', 'aw'], 202 | ['泰韻 疑母 合口', 'aj'], // 外 203 | ]], 204 | 205 | ['遇攝', 'u'], // 魚模韻 206 | ['止攝 開口 (精莊章組 或 日母)', 'ɨ'], // 支思韻 207 | ['果攝 (一等 或 幫組 三等)', 'ʌ'], // 歌戈韻 208 | ['假攝 (二等 或 莊組)', 'a'], // 家麻韻 209 | ['果假攝 三四等', 'ɛ'], // 車遮韻 210 | 211 | ['蟹攝 (一等 開口 或 二等 或 莊組) 或 止攝 莊組', 'aj'], // 皆來韻 212 | ['止蟹攝', 'ɨj'], // 齊微韻 213 | 214 | ['流攝', 'ɨw'], // 尤侯韻 215 | ['效攝 一等', 'ʌw'], // 蕭豪韻·一等。作爲共通音位,先將 ʌw 獨立,以便推導各家擬音 216 | ['效攝 (二等 或 莊組)', 'aw'], // 蕭豪韻·二等 217 | ['效攝 三四等', 'ɛw'], // 蕭豪韻·三四等 218 | 219 | ['舒聲', [ 220 | [例外, [ 221 | [層次 === 1 && '曾梗攝 一二等 非 開口 或 庚韻 三等 合口', 'uŋ'], 222 | ['溪母 登韻 上聲 或 以母 蒸韻 去聲', 'ɨn'], // 肯孕 223 | [層次 === 1 && '知母 清韻 或 昌母 蒸韻 去聲', 'ɨn'], // +貞稱(元曲押真文、庚青) 224 | ]], 225 | ['通攝', 'uŋ'], // 東鍾韻 226 | ['宕江攝', 'aŋ'], // 江陽韻 227 | ['曾梗攝', 'ɨŋ'], // 庚青韻 228 | 229 | ['臻攝', 'ɨn'], // 真文韻 230 | ['山攝 一等 非 開口', 'ʌn'], // 桓歡韻 231 | ['山攝 (一二等 或 莊組 或 幫組 C類)', 'an'], // 寒山韻 232 | ['山攝 三四等', 'ɛn'], // 先天韻 233 | 234 | ['深攝', is`幫組` ? 'ɨn' : 'ɨm'], // 侵尋韻 235 | ['咸攝 (一二等 或 莊組 或 幫組 C類)', is`幫組` ? 'an' : 'am'], // 監咸韻 236 | ['咸攝 三四等', is`幫組` ? 'ɛn' : 'ɛm'], // 廉纖韻 237 | ]], 238 | ['入聲', [ 239 | [例外, [ 240 | [層次 === 0 && '宕江攝 一等 明母', 'aw'], 241 | [層次 === 1 && '登韻 心母', 'ɨ'], // 塞(元曲押齊微) 242 | [層次 === 1 && '登韻 精母', 'aj'], // 則(元曲押齊微) 243 | [層次 === 1 && '曾梗攝 一等 溪母 開口', 'jaj'], // 刻(元曲押齊微、皆來) 244 | [層次 === 1 && '曾梗攝 二等 溪疑母 開口', 'ɛ'], // 客(元曲押皆來、車遮)、額(元曲只押皆來) 245 | [層次 === 0 && '文韻 並母', 'ʌ'], // 佛(元曲押魚模、歌戈) 246 | ['臻攝 一等 幫組 非 明母', 'ʌ'], // 勃 247 | [層次 === 0 && '日母 深攝', 'u'], // 入 248 | ]], 249 | 250 | ['通攝', [ 251 | [層次 === 0 && '(精知章莊組 或 來日母) 東韻 三等', 'ɨw'], 252 | [層次 === 0 && '(知章莊組 或 日母) 鍾韻', 'ɨw'], // 燭褥+贖屬(元曲押魚模、尤侯)\辱(元曲只押魚模) 253 | ['', 'u'], 254 | ]], 255 | ['宕江攝', [ 256 | [層次 === 1, 'ʌ'], 257 | ['一等 或 鈍音 三等 非 開口', 'ʌw'], 258 | ['', 'aw'], 259 | ]], 260 | 261 | ['曾梗攝 (二等 或 莊組)', 'aj'], 262 | ['曾梗攝 B類 合口', 'u'], 263 | ['臻攝 莊組 合口', 'aj'], 264 | ['臻攝 (一等 或 幫組 C類 或 合口)', 'u'], // +麧(《中原音韻》未收) 265 | ['臻深攝 莊組', 'ɨ'], 266 | ['曾梗臻深攝', 'ɨj'], 267 | ['山咸攝 一等 非 (銳音 開口)', 'ʌ'], 268 | ['山咸攝 (一二等 或 莊組 或 幫組 C類)', 'a'], 269 | ['山咸攝 三四等', 'ɛ'], 270 | ]], 271 | ], '無韻基規則', true); 272 | 273 | let 韻母 = 洪細 + 開合 + 韻基; 274 | 韻母 = 韻母.replace('wu', 'u'); 275 | 韻母 = 韻母.replace('jwɨj', 'wɨj'); 276 | 韻母 = 韻母.replace('jʌ', 'jwʌ'); 277 | return 韻母; 278 | } 279 | 280 | function get聲調() { 281 | return when([ 282 | [例外, [ 283 | ['匣母 蟹攝 上聲 開口', '³'], // 駭蟹\解獬 284 | ['羣母 臻攝 上聲 合口', '³'], // 窘 285 | 286 | ['羣母 梗攝 三等 開口 入聲', '⁴ʼ'], // 劇 287 | ['生母 山攝 合口 入聲', '⁴ʼ'], // 刷 288 | ['影疑母 通臻攝 一等 入聲 非 開口', '³ʼ'], // 屋沃兀 289 | [層次 === 0 && '影母 臻攝 A類 開口 入聲', '³ʼ'], // 一 290 | ]], 291 | ['平聲 (全清 或 次清)', '¹'], 292 | ['平聲 (全濁 或 次濁)', '²'], 293 | ['上聲 非 全濁', '³'], 294 | ['上去聲', '⁴'], 295 | ['入聲', [ 296 | ['全濁', '²ʼ'], 297 | ['次濁 或 影母', '⁴ʼ'], // 影母入聲《中原音韻》按次濁歸派 298 | ['', '³ʼ'], 299 | ]], 300 | ], '無聲調規則', true); 301 | } 302 | 303 | function get音節() { 304 | let 聲母 = get聲母(); 305 | let 韻母 = get韻母(); 306 | let 聲調 = get聲調(); 307 | const is脣音 = 'pmfʋ'.includes(聲母[0]); 308 | const is銳音 = 'tnlsʂɻ'.includes(聲母[0]); 309 | if (is脣音 && !'jwu'.includes(韻母[0])) 韻母 = 'β' + 韻母; // 以便推導薛鳳生和寧繼福擬音 310 | 311 | const 轉換規則字典 = { 312 | // 規則: (from, to, [condition, [else to]]) 313 | '音位(unt, 2021)': [ 314 | ['β', ''], 315 | ['ʌw', 'waw', is脣音 && !聲調.includes('ʼ'), 'aw'], 316 | ['ɨ', 選項.高元音開口呼], 317 | ['aj', 選項.皆來韻古入聲字韻基, 聲調.includes('ʼ')], 318 | ['jaj', 選項.皆來韻梗二開見入, 聲調.includes('ʼ')], 319 | ], 320 | '音值(unt, 2021)': [ 321 | ['β', ''], 322 | ['jɨ', 'i'], ['ij', 'i'], 323 | ['jw', 'ɥ'], 324 | ['wɨ', 'u', !韻母.includes('ŋ')], 325 | ['ɥɨ', 'y', !韻母.includes('ŋ'), 'ɥi'], 326 | ['ʌw', 'waw', is脣音 && !聲調.includes('ʼ'), 'aw'], 327 | ['wʌ', 'ɔ', is脣音 || is銳音 && 韻母 === 'wʌ', 'wɔ'], 328 | ['ɥʌ', 'jɔ'], 329 | ['ɨ', 選項.高元音開口呼], ['ə', 'ɹ̩', 韻母 === 'ə'], ['ɹ̩', 'ɻ̍', !聲母.includes('s')], 330 | ['aj', 選項.皆來韻古入聲字韻基, 聲調.includes('ʼ')], 331 | ['jaj', 選項.皆來韻梗二開見入, 聲調.includes('ʼ')], 332 | ], 333 | '音位(薛鳳生, 1990)': [ 334 | ['ʰ', 'h'], ['ʋ', 'v'], ['ʂ', 'sr'], ['ɻ', 'r'], 335 | ['ts', 'c'], ['x', 'h'], 336 | ['waw', 'ow'], ['β', 'w'], 337 | ['j', 'y'], 338 | ['ɨ', 'e', 韻母.includes('ŋ')], ['ɛ', 'e'], 339 | ['u', 'wɨ', !韻母.includes('ŋ'), 'wo'], ['ʌ', 'o'], 340 | ], 341 | '音值(楊耐思, 1981)': [ 342 | ['ʰ', 'ʻ'], ['ʋ', 'v'], ['ʂ', 'ʃ'], ['ɻ', 'ʒ'], 343 | ['β', ''], 344 | ['ɨ', 'ï', 韻母 === 'ɨ', 'ə'], 345 | ['j', 'i'], ['w', 'u'], 346 | ['əi', 'ei'], ['iei', 'i'], 347 | ['uau', 'au'], ['ʌu', 'au'], ['iau', 'iɛu', is銳音], 348 | ['ʌ', 'o'], ['iuo', 'io'], ['uon', 'on'], 349 | ['ia', 'i̯a', !韻母.includes('ŋ')], 350 | ], 351 | '音值(寧繼福, 1985)': [ 352 | ['β', '', 'jw'.includes(韻母.slice(-1)), 'w'], 353 | ['ʰ', 'ʻ'], ['ɻ', 'ɽ'], 354 | ['ɨ', 'ï', 韻母 === 'ɨ', 'ə'], 355 | ['j', 'i'], ['w', 'u'], 356 | ['uəi', 'ui'], ['əi', 'ei'], ['iei', 'i'], 357 | ['uʌu', 'au'], ['ʌu', 'ɑu'], 358 | ['uau', 'au'], ['iau', 'au', !聲調.includes('ʼ')], 359 | ['iɛu', 'iau'], 360 | ['ʌ', 'ɔ'], ['iuɔ', 'iɔ'], 361 | ], 362 | }; 363 | let 音节 = 聲母 + 韻母 + 聲調; 364 | 轉換規則字典[選項.顯示]?.forEach(規則 => { 365 | if (規則.length === 2 || 規則[2]) 音节 = 音节.replace(new RegExp(規則[0], 'g'), 規則[1]); 366 | else if (規則.length === 4) 音节 = 音节.replace(new RegExp(規則[0], 'g'), 規則[3]); 367 | }); 368 | 369 | if (!選項.標記古入聲字) 音节 = 音节.replace('ʼ', ''); 370 | return 音节; 371 | } 372 | 373 | const 音韻地位備份 = 音韻地位; 374 | const 結果 = [0, 1].map(i => { 375 | 層次 = i; 376 | 音韻地位 = 音韻地位備份; 377 | 調整音韻地位(); 378 | return get音節(); 379 | }); 380 | return [...new Set(結果)].join(選項.異讀分隔符 || '\n'); 381 | --------------------------------------------------------------------------------