Skip to content

Commit

Permalink
Add Pan Wuyun and Pulleyblank MC
Browse files Browse the repository at this point in the history
  • Loading branch information
justinsilvestre committed Mar 13, 2024
1 parent 8b9f9f0 commit ae9342e
Show file tree
Hide file tree
Showing 11 changed files with 1,064 additions and 55 deletions.
6 changes: 3 additions & 3 deletions src/app/qieyun/QysInitial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ export const initialGroups = {
};
const initialToGroup = Object.fromEntries(
Object.entries(initialGroups).flatMap(([group, initials]) =>
Array.from(initials, (initial) => [initial, group]),
),
Array.from(initials, (initial) => [initial, group])
)
);
export const getInitialGroup = (initial: string) =>
initialToGroup[initial] || null;
(initialToGroup[initial] as keyof typeof initialGroups) || null;
11 changes: 0 additions & 11 deletions src/app/qieyun/QysSyllableProfile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import type { QieyunRhymeCycleHead } from "./QieyunRhymeCycleHead";
import type { QysInitial } from "./QysInitial";

export type DengOrChongniu = "一" | "二" | "三" | "四" | "A" | "B";
export enum Kaihe {
Open = "開",
Expand All @@ -13,11 +10,3 @@ export enum Tone {
= "去",
= "入",
}

export interface QysSyllableProfile {
initial: QysInitial;
dengOrChongniu: DengOrChongniu | null;
kaihe: Kaihe | null;
tone: Tone;
cycleHead: QieyunRhymeCycleHead;
}
6 changes: 4 additions & 2 deletions src/app/qieyun/QysTranscriptionProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ export interface QysTranscriptionProfile {
canonical母: QysInitial;
tone聲: "平" | "上" | "去" | "入";
is重紐A類: boolean;
is重紐B類: boolean;
qieyunCycleHead韻: QieyunRhymeCycleHead;
contrastiveRow等: DengOrChongniu | null;
row等: DengOrChongniu | null;
}

// below adapted from https://github.com/nk2028/qieyun-js/blob/main/src/lib/%E9%9F%B3%E9%9F%BB%E5%9C%B0%E4%BD%8D.ts
Expand Down Expand Up @@ -74,7 +75,8 @@ export function getQysTranscriptionProfile(
canonical母: as QysInitial,
tone聲: as "平" | "上" | "去" | "入",
is重紐A類: 重紐 === "A",
is重紐B類: 重紐 === "B",
qieyunCycleHead韻: as QieyunRhymeCycleHead,
contrastiveRow等: as DengOrChongniu | null,
row等: as DengOrChongniu | null,
};
}
28 changes: 6 additions & 22 deletions src/app/qieyun/transcribeDecoratedKanOn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const rhymes: Record<
| ((syllable: QysTranscriptionProfile) => keyof typeof asciiFinals)
> = {
: (s) => {
if (s.tone聲 === "入" && s.contrastiveRow等 === "三") {
if (s.tone聲 === "入" && s.row等 === "三") {
if (initialGroups["幫"].has(s.canonical母)) return "ūk";
if (s.canonical母 === "以") return "ẁīk";
return initialGroups["莊"].has(s.canonical母) ||
Expand All @@ -153,7 +153,7 @@ const rhymes: Record<
? "yūk"
: "wīk";
}
if (s.contrastiveRow等 === "三") {
if (s.row等 === "三") {
return initialGroups["幫"].has(s.canonical母) ? "ūng" : "iūng";
}
return s.canonical母 === "影" ? "wōng" : "ōng";
Expand All @@ -168,8 +168,8 @@ const rhymes: Record<
: (s) => (s.is合口 ? "wan" : "an"),
: "au",
: (s) => {
if (s.is合口) return s.contrastiveRow等 === "三" ? "wȧ" : "wa";
return s.contrastiveRow等 === "三" ? "ya" : "a";
if (s.is合口) return s.row等 === "三" ? "wȧ" : "wa";
return s.row等 === "三" ? "ya" : "a";
},
: (s) => (s.is合口 ? "wang" : "ang"),
: (s) => (s.is合口 ? "wŏng" : "ŏng"),
Expand All @@ -186,11 +186,11 @@ const rhymes: Record<
: (s) => (s.is合口 ? "wạ̈n" : "ạ̈n"),
: "ạu",
: (s) => {
if (s.contrastiveRow等 === "三") return "yạ";
if (s.row等 === "三") return "yạ";
return s.is合口 ? "wạ" : "ạ";
},
: (s) => {
if (s.contrastiveRow等 === "三") return s.is合口 ? "wẹng" : "ẹng";
if (s.row等 === "三") return s.is合口 ? "wẹng" : "ẹng";
return s.is合口 ? "wạng" : "ạng";
},
: (s) => (s.is合口 ? "wạ̈ng" : "ạ̈ng"),
Expand Down Expand Up @@ -478,22 +478,6 @@ export function transcribe(
聲調
);
}
export function transcribeSyllableProfile(
syllableProfile: QysSyllableProfile,
options?: TranscriptionOptions
) {
return transcribe(
{
is合口: syllableProfile.kaihe === Kaihe.Closed,
canonical母: syllableProfile.initial,
tone聲: syllableProfile.tone,
is重紐A類: syllableProfile.dengOrChongniu === "A",
qieyunCycleHead韻: syllableProfile.cycleHead,
contrastiveRow等: syllableProfile.dengOrChongniu,
},
options
);
}

const retroflexToDental = {
: "精",
Expand Down
13 changes: 5 additions & 8 deletions src/app/qieyun/transcribeKarlgren.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function transcribe(
央次低元音?: "ɐ" | "ɒ";
濁送氣?: "ʰ" | "ʱ";
} = {
音標體系: "原書音標",
音標體系: "國際音標(通用)",
}
) {
const { canonical母, tone聲: , qieyunCycleHead韻: } = syllable;
Expand Down Expand Up @@ -197,13 +197,11 @@ export function transcribe(
)
介音 += "j"; // 云母已經是 j,無需加
if (
(syllable.contrastiveRow等 === "三" ||
syllable.contrastiveRow等 === "B") &&
(syllable.row等 === "三" || syllable.row等 === "B") &&
!止攝.has(syllable.qieyunCycleHead韻)
)
介音 += "i̯";
if (syllable.contrastiveRow等 === "四" || syllable.contrastiveRow等 === "A")
介音 += "i";
if (syllable.row等 === "四" || syllable.row等 === "A") 介音 += "i";

const alwaysUMedial = new Set<QieyunRhymeCycleHead>([
"模",
Expand All @@ -220,7 +218,7 @@ export function transcribe(
if (
syllable.qieyunCycleHead韻 === "眞" &&
syllable.is合口 &&
(syllable.contrastiveRow等 === "A" ||
(syllable.row等 === "A" ||
(!鈍音.has(syllable.canonical母) && initialGroup !== "莊"))
)
return "u";
Expand All @@ -237,8 +235,7 @@ export function transcribe(
if (
syllable.qieyunCycleHead韻 === "廢" ||
syllable.qieyunCycleHead韻 === "元" ||
(syllable.qieyunCycleHead韻 === "庚" &&
syllable.contrastiveRow等 === "三")
(syllable.qieyunCycleHead韻 === "庚" && syllable.row等 === "三")
)
return "w";
if (
Expand Down
201 changes: 201 additions & 0 deletions src/app/qieyun/transcribePanWuyun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// adapted from https://github.com/nk2028/qieyun-examples/blob/main/karlgren.js
// license: CC0 1.0 Universal https://github.com/nk2028/qieyun-examples/blob/main/LICENSE

import { QysInitial, getInitialGroup } from "./QysInitial";
import { QysTranscriptionProfile } from "./QysTranscriptionProfile";
/* 潘悟雲擬音
*
* 兩個版本:
*
* - 潘悟雲. 漢語歷史音韻學. 上海: 上海教育出版社, 2000.
* - 潘悟雲, 張洪明. 漢語中古音. 語言研究, 2013, 33 (2): 1–7.
*
* @author unt
*/

type Options = {
版本?: "2000" | "2013";
非前三等介音: "i" | "ɨ";
聲調記號: string;
送氣記號?: string[];
支韻?: string[];
虞韻?: string[];
};

export function transcribe(
syllable: QysTranscriptionProfile,
選項: Options = {
版本: "2013",
非前三等介音: "ɨ",
聲調記號: "五度符號",
}
) {
const { canonical母, tone聲: , qieyunCycleHead韻: } = syllable;
const = canonical母;

const is2000 = 選項.版本 === "2000";

const effectiveOptions: Options & Required<Pick<Options, "支韻" | "虞韻">> = {
版本: "2013",
送氣記號: ["ʰ"],
支韻: is2000 ? ["iɛ", "iᵉ"] : ["iɛ"],
虞韻: is2000 ? ["io", "iʊ"] : ["io"],
...選項,
};

const 聲母 = get聲母(, is2000, effectiveOptions);
const 韻母 = get韻母(syllable, , is2000, effectiveOptions);
const 聲調 = get聲調(, effectiveOptions);
return 聲母 + 韻母 + 聲調;
}

function get聲母(: QysInitial, is2000: boolean, 選項: Options) {
let 聲母 = {
: "p",
: "pʰ",
: "b",
: "m",
: "t",
: "tʰ",
: "d",
: "n",
: "l",
: "ʈ",
: "ʈʰ",
: "ɖ",
: "ɳ",
: "k",
: "kʰ",
: "ɡ",
: "ŋ",
: "ʔ",
: "h",
: "ɦ",
: "ɦ",
: "ts",
: "tsʰ",
: "dz",
: "s",
: "z",
: "tʂ",
: "tʂʰ",
: "dʐ",
: "ʂ",
: "ʐ",
: "tɕ",
: "tɕʰ",
: "dʑ",
: "ɕ",
: "ʑ",
: "ȵ",
: "j",
// 生母《漢語歷史音韻學》作“ʃ”,係誤植
// 從母《漢語中古音》聲母表作“ʣ”,係排版錯誤,正文作“dz”
// 俟母《漢語中古音》聲母表未列,此處補上
}[];
if (is2000 && 選項.送氣記號) 聲母 = 聲母.replace("ʰ", 選項.送氣記號[0]);
return 聲母;
}

function get韻母(
profile: QysTranscriptionProfile,
韻母: string,
is2000: boolean,
選項: Options & Required<Pick<Options, "支韻" | "虞韻">>
): string {
const = profile.qieyunCycleHead韻;
if ( === "凡")
return get韻母(
{
...profile,
qieyunCycleHead韻: "嚴",
},
韻母,
is2000,
選項
);
const 元音表 = {
ɪ: "   臻  ",
i: "脂 侵眞 幽",
ɨ: "之蒸 欣微尤",
u: "侯東 文  ",
e: " 青添先齊蕭",
ə: " 登覃痕咍 ",
o: "模冬 魂灰 ",
: "支清鹽仙祭宵",
ɤ: "魚  元  ",
ʊ: "虞鍾    ",
ɛ: "佳耕咸山皆 ",
a: " 陽嚴 廢 ",
ɔ: " 江    ",
æ: "麻庚銜刪夬肴",
ɑ: "歌唐談寒泰豪",
};
const 韻尾列表 = [""].concat(
profile.tone聲 !== "入" ? [..."ŋmniu"] : [..."kpt"]
);
let 韻核 = Object.entries(元音表).find(([k, v]) => v.includes(韻母))?.[0]!;
let 韻尾 = 韻尾列表[元音表[韻核 as keyof typeof 元音表].indexOf(韻母)] || "";
let 介音 = "";
if (
profile.is合口 &&
![..."mpu"].includes(韻尾) &&
![..."uoʊɔ"].includes(韻核)
)
介音 += "ʷ";
const initialGroup = getInitialGroup(profile.canonical母);
if (
initialGroup === "幫" &&
![..."ŋkmpu"].includes(韻尾) &&
[..."ɨəɤaɑ"].includes(韻核) &&
profile.qieyunCycleHead韻 !== "泰"
)
介音 += is2000 ? "ʷ" : "u̯";
if (
profile.row等 === "二" ||
profile.qieyunCycleHead韻 === "庚" ||
(profile.is重紐B類 &&
!(
profile.qieyunCycleHead韻 === "蒸" || profile.qieyunCycleHead韻 === "幽"
))
)
介音 += is2000 ? "ɯ" : "ɣ";
if (profile.row等 === "三" && ![..."ɪiɨ"].includes(韻核))
介音 += [..."ᴇæ"].includes(韻核) ? "i" : 選項.非前三等介音;
if ([..."oʊ"].includes(韻核)) 介音 += is2000 ? (介音 ? "" : "u") : "u̯";
// 調整韻核
if (is2000) {
const 韻核鏈移列表 = [..."ᴇɛæaɐ"]; // “鏈移”只是比喻
if (韻核鏈移列表.includes(韻核))
韻核 = 韻核鏈移列表[韻核鏈移列表.indexOf(韻核) + 1];
韻核 =
(
{
: "i",
: "ɨ",
: "əu",
: 選項.支韻[1],
: "ɔ",
: 選項.虞韻[1],
: "ɐ",
: "o",
} as { [key: string]: string }
)[] ?? 韻核;
} else {
韻核 = 韻核.replace("ʊ", "o̝");
}
return 介音 + 韻核 + 韻尾;
}

function get聲調(: "平" | "上" | "去" | "入", 選項: Options) {
return 選項.聲調記號 === "隱藏"
? ""
: (
{
五度符號: ["˧", "˧˥", "˥˩", "꜊"],
調值數字: ["³³", "³⁵", "⁵¹", "³"],
} as {
[key: string]: string[];
}
)[選項.聲調記號.slice(0, 4)]["平上去入".indexOf()];
}
Loading

0 comments on commit ae9342e

Please sign in to comment.