{"id":30570,"date":"2025-10-14T12:13:02","date_gmt":"2025-10-14T04:13:02","guid":{"rendered":"https:\/\/www.hkmu.edu.hk\/oetools\/?page_id=30570"},"modified":"2026-03-04T09:23:22","modified_gmt":"2026-03-04T01:23:22","slug":"agent-10-chinese-polyphones","status":"publish","type":"page","link":"https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/","title":{"rendered":"Agent 10 Chinese Polyphones"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"30570\" class=\"elementor elementor-30570\" data-elementor-settings=\"[]\">\n\t\t\t\t\t\t\t<div class=\"elementor-section-wrap\">\n\t\t\t\t\t\t\t<section class=\"has_eae_slider wavo-column-gap-default elementor-section elementor-top-section elementor-element elementor-element-5e38f9e elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"5e38f9e\" data-element_type=\"section\" data-settings=\"{&quot;jet_parallax_layout_list&quot;:[]}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"has_eae_slider elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-6078bb5\" data-id=\"6078bb5\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-bd56e54 elementor-widget elementor-widget-html\" data-id=\"bd56e54\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>\u97f3\u97fb\u5927\u5e2b Master of Mandarin Phonology<\/title>\r\n    <style>\r\n        \/* ================ GLOBAL STYLES ================ *\/\r\n        :root {\r\n            \/* color palette *\/\r\n            --primary-color: #fa975e;\r\n            --primary-light: #e8864a;\r\n            --primary-dark: #d87a40;\r\n            --accent-teal: #fa975e;\r\n            --accent-green: #7AC143;\r\n            --light-gray: #f9f5f3;\r\n            --medium-gray: #e0e0e0;\r\n            --dark-gray: #707070;\r\n            --success-color: #7AC143;\r\n            --danger-color: #f44336;\r\n            --warning-color: #ff9800;\r\n            --info-color: #fa975e;\r\n            --dark-text: #333;\r\n            --light-text: #666;\r\n            --card-bg: #ffffff;\r\n            --bg-light: linear-gradient(135deg, #fa975e, #e8864a);\r\n            --shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\r\n            \r\n            \/* UI elements *\/\r\n            --border-radius: 12px;\r\n            --small-radius: 8px;\r\n            --box-shadow: 0 2px 8px rgba(0,0,0,0.1);\r\n            --box-shadow-hover: 0 8px 25px rgba(0,0,0,0.15);\r\n            --transition: all 0.3s ease;\r\n        }\r\n\r\n        \/* Reset and base styles *\/\r\n        * {\r\n            margin: 0;\r\n            padding: 0;\r\n            box-sizing: border-box;\r\n            font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif;\r\n        }\r\n\r\n        body {\r\n            color: var(--dark-text);\r\n            font-size: 1rem;\r\n            line-height: 1.6;\r\n            min-height: 100vh;\r\n        }\r\n\r\n        .container {\r\n            max-width: 1800px;\r\n            margin: 0 auto;\r\n            margin-bottom: 48px;\r\n            padding: 0px;\r\n            background: rgba(255, 255, 255, 0.95);\r\n            border-radius: var(--border-radius);\r\n            box-shadow: var(--shadow);\r\n            overflow: visible;\r\n        }\r\n\r\n        .header {\r\n            background: linear-gradient(45deg, #fa975e, #e8864a);\r\n            color: #ffffff;\r\n            padding: 20px;\r\n            text-align: center;\r\n        }\r\n\r\n        .header h1 {\r\n            font-size: 2.5rem;\r\n            font-weight: 700;\r\n            margin-bottom: 10px;\r\n            color: #ffffff !important;\r\n            position: relative;\r\n            text-shadow: 0 2px 4px rgba(0,0,0,0.2);\r\n        }\r\n\r\n        .header p {\r\n            font-size: 1.1rem;\r\n            color: #ffffff;\r\n            opacity: 0.9;\r\n            max-width: 1000px;\r\n            margin: 0 auto;\r\n            position: relative;\r\n        }\r\n\r\n        h2 {\r\n            font-size: 1.5rem;\r\n            color: var(--primary-color);\r\n            margin-bottom: 16px;\r\n            padding-bottom: 8px;\r\n            border-bottom: 2px solid var(--primary-color);\r\n            font-weight: 600;\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 10px;\r\n        }\r\n\r\n        h3 {\r\n            font-size: 1.5rem;\r\n            color: var(--primary-color);\r\n            margin-bottom: 16px;\r\n            font-weight: 700;\r\n\t\t\tpadding-bottom: 2px;\r\n            border-bottom: 2px solid #fa975e;\r\n        }\r\n\r\n        h4 {\r\n            font-size: 1.2rem;\r\n            color: var(--primary-color);\r\n            margin-bottom: 8px;\r\n            font-weight: 600;\r\n        }\r\n\r\n        \/* Document Statistics Styles *\/\r\n        .document-stats {\r\n            background-color: #f8f9fa;\r\n            padding: 15px;\r\n            border-radius: 8px;\r\n            border: 1px solid #e0e6ed;\r\n        }\r\n\r\n        .stat-item {\r\n            text-align: center;\r\n        }\r\n\r\n        .stat-number {\r\n            font-size: 1.5rem;\r\n            font-weight: 600;\r\n            color: var(--primary-color);\r\n        }\r\n\r\n        .stat-label {\r\n            font-size: 1rem;\r\n            color: var(--light-text);\r\n            margin-top: 5px;\r\n        }\r\n\r\n        \/* ================ SECTION STYLES ================ *\/\r\n        .section {\r\n            background-color: var(--card-bg);\r\n            padding: 32px;\r\n            margin-bottom: 24px;\r\n            border-radius: var(--border-radius);\r\n            box-shadow: var(--box-shadow);\r\n            transition: var(--transition);\r\n        }\r\n\r\n        .section:hover {\r\n            box-shadow: var(--box-shadow-hover);\r\n            transform: translateY(-2px);\r\n        }\r\n\r\n        .section.no-transform:hover {\r\n            transform: none !important;\r\n        }\r\n\r\n        \/* Also disable transform when any child is focused (robust in WP widgets) *\/\r\n        .section:focus-within {\r\n            transform: none !important;\r\n        }\r\n\r\n        .split-row {\r\n            display: flex;\r\n            gap: 16px;\r\n            align-items: flex-start;\r\n            justify-content: space-between;\r\n        }\r\n\r\n        .split-col {\r\n            flex: 1;\r\n            min-width: 260px;\r\n            padding: 8px 0;\r\n        }\r\n\r\n        .scroll-guard {\r\n            overscroll-behavior: contain;\r\n        }\r\n\r\n        textarea, input, select {\r\n            width: 100%;\r\n            padding: 16px;\r\n            border: 1px solid var(--medium-gray);\r\n            border-radius: var(--small-radius);\r\n            font-size: 1rem;\r\n            transition: var(--transition);\r\n            margin-bottom: 8px;\r\n        }\r\n\r\n        \/* Force native dropdown behavior across themes that override appearance *\/\r\n        select {\r\n            -webkit-appearance: menulist;\r\n            -moz-appearance: menulist;\r\n            appearance: auto;\r\n            touch-action: auto;\r\n        }\r\n\r\n        select[size] {\r\n            max-height: 50vh;\r\n            overflow-y: auto;\r\n            -webkit-overflow-scrolling: touch;\r\n        }\r\n\r\n        textarea {\r\n            min-height: 60px;\r\n            resize: vertical;\r\n        }\r\n\r\n        textarea:focus, input:focus, select:focus {\r\n            outline: none;\r\n            border-color: var(--primary-color);\r\n            box-shadow: 0 0 0 3px rgba(250, 151, 94, 0.2);\r\n        }\r\n\r\n        \/* ================ BUTTONS ================ *\/\r\n        button {\r\n            background: var(--accent-green);\r\n            color: white;\r\n            border: none;\r\n            padding: 8px 28px;\r\n            border-radius: var(--small-radius);\r\n            cursor: pointer;\r\n            font-weight: 600;\r\n            font-size: 24px;\r\n            transition: var(--transition);\r\n            box-shadow: 0 4px 8px rgba(250, 151, 94, 0.3);\r\n            width: 100%;\r\n            margin-bottom: 10px;\r\n        }\r\n\r\n        button:hover {\r\n            background: #5a8f32;\r\n            transform: translateY(-2px);\r\n            box-shadow: 0 6px 12px rgba(250, 151, 94, 0.4);\r\n        }\r\n\r\n        button:disabled {\r\n            background: var(--medium-gray);\r\n            cursor: not-allowed;\r\n            transform: none;\r\n            box-shadow: none;\r\n        }\r\n\r\n        .btn-secondary {\r\n            background: var(--primary-color);\r\n            font-size: 16px;\r\n            font-family: 'Noto Sans SC', sans-serif;\r\n            font-weight: 400;\r\n            padding: 8px 20px;\r\n            width: auto;\r\n            display: inline-block;\r\n            margin-right: 10px;\r\n            margin-bottom: 15px;\r\n        }\r\n\r\n        .btn-secondary:hover {\r\n            background: var(--primary-dark);\r\n        }\r\n\r\n        \/* Custom styles for the app *\/\r\n        .input-section {\r\n            margin-bottom: 25px;\r\n        }\r\n\r\n        label {\r\n            display: block;\r\n            margin-bottom: 8px;\r\n            font-weight: 500;\r\n\t\t\tfont-size:1.1rem;\r\n            color: var(--dark-text);\r\n        }\r\n\r\n        .result-section {\r\n            margin-top: 30px;\r\n            padding: 20px;\r\n            background: var(--light-gray);\r\n            border-radius: var(--small-radius);\r\n            display: none;\r\n        }\r\n\r\n        .audio-controls {\r\n            display: flex;\r\n            flex-direction: column;\r\n            gap: 15px;\r\n        }\r\n\r\n        .audio-player {\r\n            width: 100%;\r\n        }\r\n\r\n        .download-btn {\r\n            background: var(--accent-green);\r\n            padding: 10px;\r\n            text-align: center;\r\n            text-decoration: none;\r\n            color: white;\r\n            border-radius: var(--small-radius);\r\n            display: inline-block;\r\n            font-weight: 600;\r\n            transition: var(--transition);\r\n        }\r\n\r\n        .download-btn:hover {\r\n            background: #5a8f32;\r\n            transform: translateY(-2px);\r\n        }\r\n\r\n        \r\n\r\n        .status {\r\n            text-align: center;\r\n            padding: 10px;\r\n            margin: 10px 0;\r\n            border-radius: var(--small-radius);\r\n        }\r\n\r\n        .status.processing {\r\n            background: #fff3cd;\r\n            color: #856404;\r\n        }\r\n\r\n        .status.error {\r\n            background: #f8d7da;\r\n            color: #721c24;\r\n        }\r\n\r\n        .status.success {\r\n            background: #d4edda;\r\n            color: #155724;\r\n        }\r\n\r\n        .example {\r\n            background: var(--light-gray);\r\n            padding: 10px;\r\n            border-radius: var(--small-radius);\r\n            font-size: 14px;\r\n            margin-bottom: 10px;\r\n        }\r\n\r\n        .instructions {\r\n            background: var(--light-gray);\r\n            padding: 10px;\r\n            border-radius: var(--small-radius);\r\n            margin-bottom: 15px;\r\n            font-size: 0.9rem;\r\n        }\r\n\r\n        .instructions h4 {\r\n            margin-top: 0;\r\n        }\r\n\r\n        .instructions ul {\r\n            padding-left: 20px;\r\n            margin: 10px 0;\r\n        }\r\n\r\n        .instructions li {\r\n            margin-bottom: 8px;\r\n        }\r\n    <\/style>\r\n<\/head>\r\n<body>\r\n    <div class=\"container\">\r\n        <div class=\"header\">\r\n            <h1>\u97f3\u97fb\u5927\u5e2b Master of Mandarin Phonology<\/h1>\r\n            <p>\u97f3\u97fb\u5927\u5e2b is an intelligent TTS tool that masters the nuances of Mandarin. With its customizable \u767c\u97f3\u8a5e\u5178 (Pronunciation Dictionary), you can instruct the system on how to pronounce specific polyphonic words (\u591a\u97f3\u5b57).<\/p>\r\n        <\/div>\r\n        \r\n        <div class=\"section\">\r\n            <h3>Step 1: Input Text<\/h3>\r\n                   \r\n            <div class=\"input-section\">\r\n                <label for=\"textInput\">Text Input:<\/label>\r\n                <textarea id=\"textInput\">\u8981\u77e5\u9053\uff0c\u4e00\u500b\u79d1\u5b78\u5bb6\u5728\u653b\u514b\u79d1\u5b78\u5821\u58d8\u7684\u9577\u5f81\u4e2d\uff0c\u5931\u6557\u7684\u6b21\u6578\u548c\u7d93\u9a57\uff0c\u9060\u6bd4\u6210\u529f\u7684\u7d93\u9a57\u8981\u8c50\u5bcc\u3001\u6df1\u523b\u5f97\u591a\u3002\u7d93\u904e\u9577\u671f\u7a4d\u7d2f\uff0c\u5c31\u591a\u5c11\u53ef\u4ee5\u770b\u51fa\u6210\u7e3e\u4f86\u3002\u9032\u5c71\u4e00\u770b\uff0c\u8349\u53e2\u77f3\u7e2b\uff0c\u5230\u8655\u90fd\u6e67\u6d41\u7740\u6e05\u4eae\u7684\u6cc9\u6c34\u3002<\/textarea>\r\n                <button id=\"addPauseBtn\" class=\"btn-secondary\">Add 0.5s Pause<\/button>\r\n\t\t\t\t<div class=\"instructions\">\r\n                    <p>Adding pauses in speech by markers in the form <code>&lt;#x#&gt;<\/code>, where x is the pause duration in seconds (valid range: 0.01 - 99.99).<\/p>\r\n                <\/div>\r\n                <h3>Step 2: Input Pronunciation<\/h3>\r\n                <textarea id=\"pronunciationDict\">[\"\u9577\u5f81\/(chang2)(zheng1)\", \"\u9577\u671f\/(chang2)(qi1)\", \"\u6210\u7e3e\/(cheng2)(ji4)\",\"\u6e67\u6d41\/(yong3)(liu2)\", \"\u90fd\u6e67\u6d41\/(dou1)(yong3)(liu2)\"]<\/textarea>\r\n                <div class=\"example\">\r\n                    Format: [\"text\/(pronunciation)\", ...]<br>\r\n                    Chinese tones: 1=high, 2=rising, 3=low\/dipping, 4=falling, 5=neutral\r\n                <\/div>\r\n                <h3>Step 3: Config Voice<\/h3>\r\n                <div class=\"split-row\" id=\"voiceEmotionRow\">\r\n                    <div class=\"split-col\">\r\n                        <label for=\"voiceSelect\">\u8072\u97f3\uff08Voice\uff09<\/label>\r\n                        <select id=\"voiceSelect\">\r\n                            <option value=\"Chinese (Mandarin)_News_Anchor\" selected>\u65b0\u805e\u4e3b\u64ad\uff08\u9810\u8a2d\uff09<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Unrestrained_Young_Man\">\u4e0d\u7f88\u5c11\u5e74<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Reliable_Executive\">\u9738\u9053\u7e3d\u88c1<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Mature_Woman\">\u6210\u719f\u5973\u6027<\/option>\r\n                            <option value=\"Arrogant_Miss\">\u9ad8\u50b2\u5c0f\u59d0<\/option>\r\n                            <option value=\"Robot_Armor\">\u6a5f\u7532\u6a5f\u5668\u4eba<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Kind-hearted_Antie\">\u5584\u826f\u963f\u59e8<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Humorous_Elder\">\u5e7d\u9ed8\u9577\u8005<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Gentleman\">\u7d33\u58eb<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Warm_Bestie\">\u6eab\u6696\u95a8\u871c<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Stubborn_Friend\">\u5014\u5f37\u670b\u53cb<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Sweet_Lady\">\u751c\u7f8e\u5c0f\u59d0<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Southern_Young_Man\">\u5357\u65b9\u5c11\u5e74<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Wise_Women\">\u667a\u6167\u5973\u6027<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Gentle_Youth\">\u6eab\u67d4\u9752\u5e74<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Warm_Girl\">\u6eab\u6696\u5973\u5b69<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Male_Announcer\">\u7537\u64ad\u97f3\u54e1<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Kind-hearted_Elder\">\u5584\u826f\u9577\u8005<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Cute_Spirit\">\u53ef\u611b\u7cbe\u9748<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Radio_Host\">\u96fb\u53f0\u4e3b\u6301<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Lyrical_Voice\">\u6292\u60c5\u55d3\u97f3<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Straightforward_Boy\">\u76f4\u7387\u7537\u5b69<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Sincere_Adult\">\u771f\u8aa0\u6210\u4eba<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Gentle_Senior\">\u6eab\u548c\u524d\u8f29<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Crisp_Girl\">\u6e05\u8106\u5973\u5b69<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Pure-hearted_Boy\">\u7d14\u771f\u7537\u5b69<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Soft_Girl\">\u67d4\u548c\u5973\u5b69<\/option>\r\n                            <option value=\"Chinese (Mandarin)_IntellectualGirl\">\u77e5\u6027\u5973\u5b69<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Warm_HeartedGirl\">\u6eab\u6696\u5973\u5b69<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Laid_BackGirl\">\u6175\u61f6\u5973\u5b69<\/option>\r\n                            <option value=\"Chinese (Mandarin)_ExplorativeGirl\">\u63a2\u7d22\u578b\u5973\u5b69<\/option>\r\n                            <option value=\"Chinese (Mandarin)_Warm-HeartedAunt\">\u71b1\u5fc3\u963f\u59e8<\/option>\r\n                            <option value=\"Chinese (Mandarin)_BashfulGirl\">\u5bb3\u7f9e\u5973\u5b69<\/option>\r\n                        <\/select>\r\n                    <\/div>\r\n                    <div class=\"split-col\">\r\n                        <label for=\"emotionSelect\">\u60c5\u7dd2\uff08Emotion\uff09<\/label>\r\n                        <select id=\"emotionSelect\">\r\n                            <option value=\"\" selected>\u4e0d\u6307\u5b9a\uff08\u9810\u8a2d\uff09<\/option>\r\n                            <option value=\"happy\">\u5feb\u6a02<\/option>\r\n                            <option value=\"sad\">\u60b2\u50b7<\/option>\r\n                            <option value=\"angry\">\u61a4\u6012<\/option>\r\n                            <option value=\"fearful\">\u6050\u61fc<\/option>\r\n                            <option value=\"disgusted\">\u53ad\u60e1<\/option>\r\n                            <option value=\"surprised\">\u9a5a\u8a1d<\/option>\r\n                            <option value=\"neutral\">\u4e2d\u6027<\/option>\r\n                        <\/select>\r\n                    <\/div>\r\n                <\/div>\r\n\r\n                <button id=\"advancedSettingsBtn\" class=\"btn-secondary\" style=\"margin-top: 12px;\">\u9032\u968e\u97f3\u8a0a\u8a2d\u5b9a<\/button>\r\n                <div id=\"advancedSettingsPanel\" style=\"display: none; margin-top: 12px;\">\r\n                    <div id=\"advancedRow\" style=\"display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap;\">\r\n                        <div class=\"section\" style=\"flex: 1; min-width: 220px; padding: 12px;\">\r\n                            <label for=\"speedInput\">\u8a9e\u901f\uff080.5-2.0\uff0c\u9810\u8a2d 1.0\uff09<\/label>\r\n                            <input id=\"speedInput\" type=\"range\" min=\"0.5\" max=\"2.0\" step=\"0.01\" value=\"1.0\" \/>\r\n                            <div>\u76ee\u524d\uff1a<span id=\"speedValue\">1.00<\/span><\/div>\r\n                        <\/div>\r\n                        <div class=\"section\" style=\"flex: 1; min-width: 220px; padding: 12px;\">\r\n                            <label for=\"volInput\">\u97f3\u91cf\uff080-10\uff0c\u9810\u8a2d 1.0\uff09<\/label>\r\n                            <input id=\"volInput\" type=\"range\" min=\"0\" max=\"10\" step=\"0.1\" value=\"1.0\" \/>\r\n                            <div>\u76ee\u524d\uff1a<span id=\"volValue\">1.0<\/span><\/div>\r\n                        <\/div>\r\n                        <div class=\"section\" style=\"flex: 1; min-width: 220px; padding: 12px;\">\r\n                            <label for=\"pitchInput\">\u97f3\u9ad8\uff08-12 \u81f3 12\uff0c\u9810\u8a2d 0\uff09<\/label>\r\n                            <input id=\"pitchInput\" type=\"range\" min=\"-12\" max=\"12\" step=\"1\" value=\"0\" \/>\r\n                            <div>\u76ee\u524d\uff1a<span id=\"pitchValue\">0<\/span><\/div>\r\n                        <\/div>\r\n                    <\/div>\r\n                    <div style=\"font-size: 12px; color: #666; margin-top: 8px;\">\u63d0\u793a\uff1a\u9032\u968e\u8a2d\u5b9a\u8b8a\u66f4\u5f8c\uff0c\u60c5\u7dd2\u56fa\u5b9a\u70ba\u300c\u4e2d\u6027\u300d\u4e26\u4e0d\u53ef\u66f4\u6539\u3002<\/div>\r\n                <\/div>\r\n                \r\n                <button id=\"generateBtn\">Generate Audio with Chinese Tones<\/button>\r\n            <\/div>\r\n        <\/div>\r\n        \r\n        <div id=\"status\" class=\"status\" style=\"display: none;\"><\/div>\r\n        \r\n        <div id=\"resultSection\" class=\"section result-section\">\r\n            <h3>Step 4: Play and Download Audio<\/h3>\r\n            <div class=\"audio-controls\">\r\n                <audio id=\"audioPlayer\" class=\"audio-player\" controls><\/audio>\r\n                <a id=\"downloadLink\" class=\"download-btn\" download=\"minimax_audio.mp3\">Download MP3<\/a>\r\n            <\/div>\r\n        <\/div>\r\n        \r\n        \r\n    <\/div>\r\n\r\n    <script>\r\n        \/\/ =====================================\r\n        \/\/ BACKEND AUTHORISATION SYSTEM\r\n        \/\/ =====================================\r\n        function modInverse(k1, mod = 256) {\r\n            let t = 0, newT = 1, r = mod, newR = k1;\r\n            while (newR !== 0) {\r\n                const quotient = Math.floor(r \/ newR);\r\n                [t, newT] = [newT, t - quotient * newT];\r\n                [r, newR] = [newR, r - quotient * newR];\r\n            }\r\n            if (r > 1) throw new Error('k1 is not invertible');\r\n            if (t < 0) t += mod;\r\n            return t;\r\n        }\r\n\r\n        function decryptServerPayload(payload) {\r\n            if (payload.length < 12) throw new Error('Invalid payload: too short');\r\n            const suffix = payload.slice(-12), b64 = payload.slice(0, -12);\r\n            const numericKey = BigInt(suffix.slice(0, 10));\r\n            const k1 = Number((numericKey % 127n) * 2n + 1n), k2 = Number(numericKey % 256n);\r\n            const invK1 = modInverse(k1, 256);\r\n            const binaryString = atob(b64);\r\n            const bytes = new Uint8Array(binaryString.length);\r\n            for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);\r\n            const decryptedBytes = new Uint8Array(bytes.length);\r\n            for (let i = 0; i < bytes.length; i++) {\r\n                const temp = (bytes[i] - k2 + 256) % 256;\r\n                decryptedBytes[i] = (invK1 * temp) % 256;\r\n            }\r\n            return new TextDecoder('utf-8').decode(decryptedBytes);\r\n        }\r\n\r\n        async function fetchDecryptedKey(service) {\r\n            const sanitizedBase = BACKEND_API_URL.endsWith('\/') ? BACKEND_API_URL.slice(0, -1) : BACKEND_API_URL;\r\n            const url = `${sanitizedBase}\/get_encrypted_key?service=${service}`;\r\n            const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'application\/json' } });\r\n            if (!response.ok) throw new Error(`Failed to fetch ${service} key: ${response.status}`);\r\n            const data = await response.json();\r\n            if (!data.encrypted) throw new Error(`No encrypted key returned for ${service}`);\r\n            return decryptServerPayload(data.encrypted);\r\n        }\r\n\r\n        async function initializeApiKeys() {\r\n            return { minimaxKey: await fetchDecryptedKey('minimax') };\r\n        }\r\n\r\n        function clearApiKeys() {\r\n            if (typeof MINIMAX_API_KEY_RUNTIME !== 'undefined') MINIMAX_API_KEY_RUNTIME = null;\r\n        }\r\n\r\n        async function retryWithKeyRefresh(apiCallFn, maxRetries = 3) {\r\n            let lastError;\r\n            for (let attempt = 1; attempt <= maxRetries; attempt++) {\r\n                try {\r\n                    return await apiCallFn();\r\n                } catch (error) {\r\n                    lastError = error;\r\n                    const is401or403 = error.message.includes('401') || error.message.includes('403');\r\n                    if (is401or403 && attempt < maxRetries) {\r\n                        MINIMAX_API_KEY_RUNTIME = await fetchDecryptedKey('minimax');\r\n                    } else {\r\n                        throw lastError;\r\n                    }\r\n                }\r\n            }\r\n            throw lastError;\r\n        }\r\n\r\n        \/\/ =====================================\r\n        \/\/ API Configuration\r\n        \/\/ =====================================\r\n        let MINIMAX_API_KEY_RUNTIME = null;\r\n        Object.defineProperty(window, 'MINIMAX_API_KEY', {\r\n            get() { return MINIMAX_API_KEY_RUNTIME; },\r\n            configurable: false\r\n        });\r\n\r\n        const BACKEND_API_URL = (() => {\r\n            try {\r\n                const { protocol, hostname } = window.location;\r\n                if (protocol === 'https:' && (hostname === 'oetools.net' || hostname.endsWith('.oetools.net'))) {\r\n                    return '\/api';\r\n                }\r\n            } catch (error) {}\r\n            return 'https:\/\/oetools.net\/api';\r\n        })();\r\n\r\n        const API_BASE_URL = 'https:\/\/oetools.net\/minimax\/tts';\r\n        const GROUP_ID = '1892726332706529707';\r\n        \r\n        \/\/ DOM Elements\r\n        const textInput = document.getElementById('textInput');\r\n        const pronunciationDict = document.getElementById('pronunciationDict');\r\n        const generateBtn = document.getElementById('generateBtn');\r\n        const addPauseBtn = document.getElementById('addPauseBtn');\r\n        const statusDiv = document.getElementById('status');\r\n        const resultSection = document.getElementById('resultSection');\r\n        const audioPlayer = document.getElementById('audioPlayer');\r\n        const downloadLink = document.getElementById('downloadLink');\r\n        const voiceSelect = document.getElementById('voiceSelect');\r\n        const emotionSelect = document.getElementById('emotionSelect');\r\n        const advancedSettingsBtn = document.getElementById('advancedSettingsBtn');\r\n        const advancedSettingsPanel = document.getElementById('advancedSettingsPanel');\r\n        const speedInput = document.getElementById('speedInput');\r\n        const volInput = document.getElementById('volInput');\r\n        const pitchInput = document.getElementById('pitchInput');\r\n        const speedValue = document.getElementById('speedValue');\r\n        const volValue = document.getElementById('volValue');\r\n        const pitchValue = document.getElementById('pitchValue');\r\n        let advancedSettingsModified = false;\r\n        \r\n        [voiceSelect, emotionSelect].forEach(sel => {\r\n            if (!sel) return;\r\n            const section = sel.closest('.section');\r\n            const enableNoTransform = () => section && section.classList.add('no-transform');\r\n            const disableNoTransform = () => section && section.classList.remove('no-transform');\r\n            sel.addEventListener('focus', enableNoTransform);\r\n            sel.addEventListener('mousedown', enableNoTransform);\r\n            sel.addEventListener('touchstart', enableNoTransform, { passive: true });\r\n            sel.addEventListener('blur', disableNoTransform);\r\n        });\r\n        \r\n        (function(){\r\n            const inWordPress = !!document.querySelector('.wp-block-html, .widget_text');\r\n            const controlsRow = document.getElementById('voiceEmotionRow');\r\n            const controlsSection = controlsRow ? controlsRow.closest('.section') : null;\r\n            if (inWordPress && controlsSection) controlsSection.classList.add('no-transform');\r\n            const addGuard = () => document.body.classList.add('scroll-guard');\r\n            const removeGuard = () => document.body.classList.remove('scroll-guard');\r\n            [voiceSelect, emotionSelect].forEach(sel => {\r\n                if (!sel) return;\r\n                sel.addEventListener('focus', addGuard);\r\n                sel.addEventListener('blur', removeGuard);\r\n            });\r\n\r\n            if (inWordPress && voiceSelect) {\r\n                const expandListbox = () => {\r\n                    voiceSelect.size = Math.min(10, voiceSelect.options.length);\r\n                    voiceSelect.style.maxHeight = '50vh';\r\n                    voiceSelect.style.overflowY = 'auto';\r\n                    voiceSelect.style.zIndex = '2';\r\n                };\r\n                const collapseListbox = () => {\r\n                    voiceSelect.size = 1;\r\n                    voiceSelect.style.maxHeight = '';\r\n                    voiceSelect.style.overflowY = '';\r\n                    voiceSelect.style.zIndex = '';\r\n                };\r\n                voiceSelect.addEventListener('focus', expandListbox);\r\n                voiceSelect.addEventListener('blur', collapseListbox);\r\n                voiceSelect.addEventListener('keydown', (e) => { if (e.key === 'Escape') collapseListbox(); });\r\n            }\r\n        })();\r\n        \r\n        \r\n        \/\/ Add pause marker at cursor position\r\n        function addPauseMarker() {\r\n            const cursorPos = textInput.selectionStart;\r\n            const textBefore = textInput.value.substring(0, cursorPos);\r\n            const textAfter = textInput.value.substring(cursorPos);\r\n            textInput.value = textBefore + '<#0.5#>' + textAfter;\r\n            \r\n            \/\/ Set cursor position after the inserted pause marker\r\n            const newCursorPos = cursorPos + 7; \/\/ Length of '<#0.5#>'\r\n            textInput.setSelectionRange(newCursorPos, newCursorPos);\r\n            textInput.focus();\r\n        }\r\n\r\n        function toggleAdvancedPanel() {\r\n            if (!advancedSettingsPanel) return;\r\n            const visible = advancedSettingsPanel.style.display !== 'none';\r\n            advancedSettingsPanel.style.display = visible ? 'none' : 'block';\r\n        }\r\n\r\n        function markAdvancedChanged() {\r\n            advancedSettingsModified = true;\r\n            if (emotionSelect) {\r\n                emotionSelect.value = 'neutral';\r\n                emotionSelect.disabled = true;\r\n            }\r\n        }\r\n\r\n        function onSpeedChange() {\r\n            if (!speedInput || !speedValue) return;\r\n            speedValue.textContent = parseFloat(speedInput.value).toFixed(2);\r\n            markAdvancedChanged();\r\n        }\r\n\r\n        function onVolChange() {\r\n            if (!volInput || !volValue) return;\r\n            volValue.textContent = parseFloat(volInput.value).toFixed(1);\r\n            markAdvancedChanged();\r\n        }\r\n\r\n        function onPitchChange() {\r\n            if (!pitchInput || !pitchValue) return;\r\n            pitchValue.textContent = parseInt(pitchInput.value, 10);\r\n            markAdvancedChanged();\r\n        }\r\n        \r\n        \/\/ Generate audio with Chinese tones\r\n        async function generateAudio() {\r\n            const text = textInput.value;\r\n            let toneList = [];\r\n            \r\n            try {\r\n                \/\/ Get the pronunciation dictionary value and replace Chinese commas with English commas\r\n                let pronunciationValue = pronunciationDict.value.trim();\r\n                if (pronunciationValue) {\r\n                    \/\/ Replace Chinese commas with English commas\r\n                    pronunciationValue = pronunciationValue.replace(\/\uff0c\/g, ',');\r\n                    \/\/ Update the input field to show the corrected value\r\n                    pronunciationDict.value = pronunciationValue;\r\n                    \r\n                    toneList = JSON.parse(pronunciationValue);\r\n                    if (!Array.isArray(toneList)) {\r\n                        throw new Error(\"Pronunciation dictionary must be a JSON array\");\r\n                    }\r\n                } else {\r\n                    \/\/ Use empty array if input is empty\r\n                    toneList = [];\r\n                }\r\n            } catch (error) {\r\n                showStatus(`Error parsing pronunciation dictionary: ${error.message}`, 'error');\r\n                return;\r\n            }\r\n            \r\n            \/\/ Show processing status\r\n            showStatus('Processing audio generation...', 'processing');\r\n            generateBtn.disabled = true;\r\n            \r\n            try {\r\n                const performMinimaxCall = async () => {\r\n                    \/\/ Prepare API request\r\n                    const url = API_BASE_URL;\r\n                    const headers = {\r\n                        'Authorization': `Bearer ${MINIMAX_API_KEY}`,\r\n                        'Content-Type': 'application\/json',\r\n                        'X-Origin-PW': 'origin',\r\n                        'X-Minimax-Group': GROUP_ID\r\n                    };\r\n                    \r\n                    \/\/ Read selected voice and emotion\r\n                    const selectedVoiceId = (voiceSelect && voiceSelect.value) ? voiceSelect.value : \"Chinese (Mandarin)_News_Anchor\";\r\n                    const selectedEmotionRaw = (emotionSelect && emotionSelect.value) ? emotionSelect.value : \"\";\r\n                    const selectedEmotion = advancedSettingsModified ? 'neutral' : (selectedEmotionRaw === \"\" ? null : selectedEmotionRaw);\r\n\r\n                    \/\/ Read advanced settings values or fall back to defaults\r\n                    const speedVal = speedInput ? parseFloat(speedInput.value) : 1.0;\r\n                    const volVal = volInput ? parseFloat(volInput.value) : 1.0;\r\n                    const pitchVal = pitchInput ? parseInt(pitchInput.value, 10) : 0;\r\n\r\n                    const requestBody = {\r\n                        model: \"speech-2.6-turbo\",\r\n                        text: text,\r\n                        stream: false,\r\n                        language_boost: \"Chinese\",\r\n                        voice_setting: {\r\n                            voice_id: selectedVoiceId,\r\n                            speed: speedVal,\r\n                            vol: volVal,\r\n                            pitch: pitchVal,\r\n                            emotion: selectedEmotion\r\n                        },\r\n                        audio_setting: {\r\n                            sample_rate: 32000,\r\n                            bitrate: 128000,\r\n                            format: \"mp3\",\r\n                            channel: 1\r\n                        },\r\n                        output_format: \"hex\"\r\n                    };\r\n                    \r\n                    \/\/ Add pronunciation_dict only if toneList is not empty\r\n                    if (toneList.length > 0) {\r\n                        requestBody.pronunciation_dict = {\r\n                            tone: toneList\r\n                        };\r\n                    }\r\n                    \r\n                    \/\/ Make API call\r\n                    const response = await fetch(url, {\r\n                        method: 'POST',\r\n                        headers: headers,\r\n                        body: JSON.stringify(requestBody)\r\n                    });\r\n                    \r\n                    if (!response.ok) {\r\n                        const errorText = await response.text();\r\n                        throw new Error(`HTTP ${response.status}: ${errorText}`);\r\n                    }\r\n                    \r\n                    return await response.json();\r\n                };\r\n                \r\n                const data = await retryWithKeyRefresh(performMinimaxCall);\r\n                \r\n                \/\/ Always check API-level status\r\n                if (data.base_resp && data.base_resp.status_code !== 0) {\r\n                    throw new Error(`Minimax error ${data.base_resp.status_code}: ${data.base_resp.status_msg}`);\r\n                }\r\n                \r\n                if (data.data && data.data.audio) {\r\n                    \/\/ Decode hex to blob URL\r\n                    const hexStr = data.data.audio;\r\n                    const bytes = new Uint8Array(hexStr.length \/ 2);\r\n                    for (let i = 0; i < bytes.length; i++) {\r\n                        bytes[i] = parseInt(hexStr.substr(i * 2, 2), 16);\r\n                    }\r\n                    const blob = new Blob([bytes], { type: 'audio\/mp3' });\r\n                    const blobUrl = URL.createObjectURL(blob);\r\n                    \r\n                    \/\/ Display results\r\n                    displayAudio(blobUrl);\r\n                    showStatus('Audio generated successfully!', 'success');\r\n                } else {\r\n                    throw new Error('Audio data not found in response');\r\n                }\r\n            } catch (error) {\r\n                console.error('Error generating audio:', error);\r\n                showStatus(`Error: ${error.message}`, 'error');\r\n                \r\n            } finally {\r\n                generateBtn.disabled = false;\r\n            }\r\n        }\r\n        \r\n        \/\/ Display audio in player\r\n        function displayAudio(audioUrl) {\r\n            audioPlayer.src = audioUrl;\r\n            downloadLink.href = audioUrl;\r\n            resultSection.style.display = 'block';\r\n        }\r\n        \r\n        \/\/ Show status message\r\n        function showStatus(message, type) {\r\n            statusDiv.textContent = message;\r\n            statusDiv.className = 'status ' + type;\r\n            statusDiv.style.display = 'block';\r\n            \r\n            \/\/ Auto-hide success messages after 5 seconds\r\n            if (type === 'success') {\r\n                setTimeout(() => {\r\n                    statusDiv.style.display = 'none';\r\n                }, 5000);\r\n            }\r\n        }\r\n        \r\n        \/\/ Event Listeners\r\n        generateBtn.addEventListener('click', generateAudio);\r\n        addPauseBtn.addEventListener('click', addPauseMarker);\r\n        if (advancedSettingsBtn) advancedSettingsBtn.addEventListener('click', toggleAdvancedPanel);\r\n        if (speedInput) speedInput.addEventListener('input', onSpeedChange);\r\n        if (volInput) volInput.addEventListener('input', onVolChange);\r\n        if (pitchInput) pitchInput.addEventListener('input', onPitchChange);\r\n        \r\n        \/\/ Initialize API keys on page load\r\n        window.addEventListener('DOMContentLoaded', async () => {\r\n            try {\r\n                const { minimaxKey } = await initializeApiKeys();\r\n                MINIMAX_API_KEY_RUNTIME = minimaxKey;\r\n                console.log('\u2713 Minimax API key initialized successfully');\r\n            } catch (error) {\r\n                console.error('Failed to initialize API keys:', error);\r\n                alert('Authorisation was not granted by the server. This may be due to a network issue. Please check your connection and try again.');\r\n            }\r\n        });\r\n\r\n        window.addEventListener('beforeunload', clearApiKeys);\r\n    <\/script>\r\n<\/body>\r\n<\/html>\r\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>&nbsp; &nbsp; \u97f3\u97fb\u5927\u5e2b Master of Mandarin Phonology \u97f3\u97fb\u5927\u5e2b is an intelligent TTS tool that masters the nuances of Mandarin. With its customizable \u767c\u97f3\u8a5e\u5178 (Pronunciation Dictionary), you can instruct the system on how to pronounce specific polyphonic words (\u591a\u97f3\u5b57). Step 1: Input Text Text Input: \u8981\u77e5\u9053\uff0c\u4e00\u500b\u79d1\u5b78\u5bb6\u5728\u653b\u514b\u79d1\u5b78\u5821\u58d8\u7684\u9577\u5f81\u4e2d\uff0c\u5931\u6557\u7684\u6b21\u6578\u548c\u7d93\u9a57\uff0c\u9060\u6bd4\u6210\u529f\u7684\u7d93\u9a57\u8981\u8c50\u5bcc\u3001\u6df1\u523b\u5f97\u591a\u3002\u7d93\u904e\u9577\u671f\u7a4d\u7d2f\uff0c\u5c31\u591a\u5c11\u53ef\u4ee5\u770b\u51fa\u6210\u7e3e\u4f86\u3002\u9032\u5c71\u4e00\u770b\uff0c\u8349\u53e2\u77f3\u7e2b\uff0c\u5230\u8655\u90fd\u6e67\u6d41\u7740\u6e05\u4eae\u7684\u6cc9\u6c34\u3002 Add 0.5s Pause Adding pauses in speech by markers in...<\/p>\n","protected":false},"author":748,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"redux-templates_canvas","meta":{"_expiration-date-status":"","_expiration-date":0,"_expiration-date-type":"","_expiration-date-categories":[],"_expiration-date-options":[]},"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v17.3 (Yoast SEO v21.2) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Agent 10 Chinese Polyphones - Hong Kong Metropolitan University<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Agent 10 Chinese Polyphones\" \/>\n<meta property=\"og:description\" content=\"&nbsp; &nbsp; \u97f3\u97fb\u5927\u5e2b Master of Mandarin Phonology \u97f3\u97fb\u5927\u5e2b is an intelligent TTS tool that masters the nuances of Mandarin. With its customizable \u767c\u97f3\u8a5e\u5178\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/\" \/>\n<meta property=\"og:site_name\" content=\"Hong Kong Metropolitan University\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-04T01:23:22+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Agent 10 Chinese Polyphones - Hong Kong Metropolitan University","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/","og_locale":"en_US","og_type":"article","og_title":"Agent 10 Chinese Polyphones","og_description":"&nbsp; &nbsp; \u97f3\u97fb\u5927\u5e2b Master of Mandarin Phonology \u97f3\u97fb\u5927\u5e2b is an intelligent TTS tool that masters the nuances of Mandarin. With its customizable \u767c\u97f3\u8a5e\u5178","og_url":"https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/","og_site_name":"Hong Kong Metropolitan University","article_modified_time":"2026-03-04T01:23:22+00:00","twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/","url":"https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/","name":"Agent 10 Chinese Polyphones - Hong Kong Metropolitan University","isPartOf":{"@id":"https:\/\/www.hkmu.edu.hk\/oetools\/#website"},"datePublished":"2025-10-14T04:13:02+00:00","dateModified":"2026-03-04T01:23:22+00:00","breadcrumb":{"@id":"https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.hkmu.edu.hk\/oetools\/agent-10-chinese-polyphones\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Open Educational Tools","item":"\/oetools\/"},{"@type":"ListItem","position":2,"name":"Agent 10 Chinese Polyphones"}]},{"@type":"WebSite","@id":"https:\/\/www.hkmu.edu.hk\/oetools\/#website","url":"https:\/\/www.hkmu.edu.hk\/oetools\/","name":"Hong Kong Metropolitan University","description":"Open Educational Tools - Hong Kong Metropolitan University","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.hkmu.edu.hk\/oetools\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"}]}},"_links":{"self":[{"href":"https:\/\/www.hkmu.edu.hk\/oetools\/wp-json\/wp\/v2\/pages\/30570"}],"collection":[{"href":"https:\/\/www.hkmu.edu.hk\/oetools\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.hkmu.edu.hk\/oetools\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.hkmu.edu.hk\/oetools\/wp-json\/wp\/v2\/users\/748"}],"replies":[{"embeddable":true,"href":"https:\/\/www.hkmu.edu.hk\/oetools\/wp-json\/wp\/v2\/comments?post=30570"}],"version-history":[{"count":53,"href":"https:\/\/www.hkmu.edu.hk\/oetools\/wp-json\/wp\/v2\/pages\/30570\/revisions"}],"predecessor-version":[{"id":32312,"href":"https:\/\/www.hkmu.edu.hk\/oetools\/wp-json\/wp\/v2\/pages\/30570\/revisions\/32312"}],"wp:attachment":[{"href":"https:\/\/www.hkmu.edu.hk\/oetools\/wp-json\/wp\/v2\/media?parent=30570"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}