|
| 1 | +/** |
| 2 | + * Ported from supertone-inc/supertonic (MIT License) |
| 3 | + * Source: https://github.com/supertone-inc/supertonic |
| 4 | + * |
| 5 | + * Copyright (c) 2024 Supertone Inc. |
| 6 | + * |
| 7 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 8 | + * of this software and associated documentation files (the "Software"), to deal |
| 9 | + * in the Software without restriction, including without limitation the rights |
| 10 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 11 | + * copies of the Software, and to permit persons to whom the Software is |
| 12 | + * furnished to do so, subject to the following conditions: |
| 13 | + * |
| 14 | + * The above copyright notice and this permission notice shall be included in |
| 15 | + * all copies or substantial portions of the Software. |
| 16 | + * |
| 17 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 18 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 19 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 20 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 21 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 22 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 23 | + * SOFTWARE. |
| 24 | + */ |
| 25 | + |
| 26 | +// prettier-ignore |
| 27 | +export const SUPPORTED_LANGUAGES = [ |
| 28 | + 'ar', 'bg', 'cs', 'da', 'de', 'el', 'en', 'es', 'fi', 'fr', 'hi', 'hr', |
| 29 | + 'hu', 'id', 'it', 'ja', 'ko', 'ms', 'nl', 'no', 'pl', 'pt', 'ro', 'ru', |
| 30 | + 'sk', 'sv', 'sw', 'ta', 'th', 'tl', 'tr', 'na', |
| 31 | +]; |
| 32 | + |
| 33 | +// prettier-ignore |
| 34 | +const EMOJI_PATTERN = new RegExp( |
| 35 | + '[' + |
| 36 | + '\\u{1f600}-\\u{1f64f}' + // Emoticons |
| 37 | + '\\u{1f300}-\\u{1f5ff}' + // Misc Symbols and Pictographs |
| 38 | + '\\u{1f680}-\\u{1f6ff}' + // Transport and Map Symbols |
| 39 | + '\\u{1f700}-\\u{1f77f}' + // Alchemical Symbols |
| 40 | + '\\u{1f780}-\\u{1f7ff}' + // Geometric Shapes Extended |
| 41 | + '\\u{1f800}-\\u{1f8ff}' + // Supplemental Arrows-C |
| 42 | + '\\u{1f900}-\\u{1f9ff}' + // Supplemental Symbols and Pictographs |
| 43 | + '\\u{1fa00}-\\u{1fa6f}' + // Chess Symbols / Symbols and Pictographs Extended-A |
| 44 | + '\\u{1fa70}-\\u{1faff}' + // Symbols and Pictographs Extended-A (cont.) |
| 45 | + '\\u{2600}-\\u{27ff}' + // Misc Symbols / Dingbats |
| 46 | + '\\u{1f1e6}-\\u{1f1ff}' + // Flags (Regional Indicator Symbols) |
| 47 | + ']', |
| 48 | + 'gu' |
| 49 | +); |
| 50 | + |
| 51 | +// prettier-ignore |
| 52 | +const STRING_REPLACEMENTS: Record<string, string> = { |
| 53 | + // Symbols |
| 54 | + '–': '-', '‑': '-', '—': '-', '¯': ' ', '_': ' ', |
| 55 | + '“': '"', '”': '"', '‘': "'", '’': "'", '´': "'", '`': "'", |
| 56 | + '[': ' ', ']': ' ', '|': ' ', '/': ' ', '#': ' ', |
| 57 | + '→': ' ', '←': ' ', |
| 58 | + // Special symbols (removed) |
| 59 | + '♥': '', '☆': '', '♡': '', '©': '', '\\': '', |
| 60 | + // Abbreviations |
| 61 | + '@': ' at ', |
| 62 | + 'e.g.,': 'for example, ', |
| 63 | + 'i.e.,': 'that is, ', |
| 64 | + // Punctuation spacing corrections (run after symbol normalization) |
| 65 | + ' ,': ',', |
| 66 | + ' .': '.', |
| 67 | + ' !': '!', |
| 68 | + ' ?': '?', |
| 69 | + ' ;': ';', |
| 70 | + ' :': ':', |
| 71 | + " '": "'", |
| 72 | +}; |
| 73 | + |
| 74 | +const WHITESPACE_PATTERN = /\s+/g; |
| 75 | +const DUPLICATE_QUOTES_PATTERN = /([`'""])\1+/g; |
| 76 | +const ENDING_PUNCTUATION_PATTERN = /[.!?;:,'")\]}…。」』】〉》›»]$/; |
| 77 | + |
| 78 | +/** |
| 79 | + * Normalizes and cleans raw input text using character mappings. |
| 80 | + * @category Utils |
| 81 | + * @param text The raw input text. |
| 82 | + * @param lang The language code. |
| 83 | + * @returns The preprocessed text. |
| 84 | + */ |
| 85 | +export function preprocessText(text: string, lang?: string): string { |
| 86 | + 'worklet'; |
| 87 | + |
| 88 | + let processed = text.normalize('NFKD'); |
| 89 | + |
| 90 | + for (const [key, replacement] of Object.entries(STRING_REPLACEMENTS)) { |
| 91 | + processed = processed.split(key).join(replacement); |
| 92 | + } |
| 93 | + |
| 94 | + processed = processed.replace(EMOJI_PATTERN, ''); |
| 95 | + processed = processed.replace(DUPLICATE_QUOTES_PATTERN, '$1'); |
| 96 | + processed = processed.replace(WHITESPACE_PATTERN, ' '); |
| 97 | + processed = processed.trim(); |
| 98 | + |
| 99 | + if (!ENDING_PUNCTUATION_PATTERN.test(processed)) { |
| 100 | + processed += '.'; |
| 101 | + } |
| 102 | + |
| 103 | + if (lang && lang !== 'na') { |
| 104 | + if (!SUPPORTED_LANGUAGES.includes(lang)) { |
| 105 | + throw new Error(`preprocessText: Unsupported language: ${lang}`); |
| 106 | + } |
| 107 | + processed = `<${lang}>${processed}</${lang}>`; |
| 108 | + } |
| 109 | + |
| 110 | + return processed; |
| 111 | +} |
| 112 | + |
| 113 | +/** |
| 114 | + * Encodes preprocessed text to character unicode index ids based on |
| 115 | + * unicode_indexer.json. |
| 116 | + * @category Utils |
| 117 | + * @param text The preprocessed text. |
| 118 | + * @param indexer The unicode indexer character mapping array. |
| 119 | + * @returns BigInt64Array of character IDs. |
| 120 | + */ |
| 121 | +export function encodeText(text: string, indexer: readonly number[]): BigInt64Array { |
| 122 | + 'worklet'; |
| 123 | + const ids = new BigInt64Array(text.length); |
| 124 | + for (let i = 0; i < text.length; i++) { |
| 125 | + const code = text.charCodeAt(i); |
| 126 | + const id = code < indexer.length ? indexer[code]! : -1; |
| 127 | + ids[i] = BigInt(id === -1 ? 0 : id); |
| 128 | + } |
| 129 | + return ids; |
| 130 | +} |
| 131 | + |
| 132 | +/** |
| 133 | + * Generates Gaussian (normal) random noise of the specified size on the worklet |
| 134 | + * thread using a standard Box-Muller transform. |
| 135 | + * @category Utils |
| 136 | + * @param size The number of random normal values to generate. |
| 137 | + * @returns The generated Float32Array. |
| 138 | + */ |
| 139 | +export function generateGaussianNoise(size: number): Float32Array { |
| 140 | + 'worklet'; |
| 141 | + const noise = new Float32Array(size); |
| 142 | + for (let i = 0; i < size; i += 2) { |
| 143 | + let u1 = 0; |
| 144 | + let u2 = 0; |
| 145 | + while (u1 === 0) u1 = Math.random(); |
| 146 | + while (u2 === 0) u2 = Math.random(); |
| 147 | + |
| 148 | + const r = Math.sqrt(-2.0 * Math.log(u1)); |
| 149 | + const theta = 2.0 * Math.PI * u2; |
| 150 | + |
| 151 | + noise[i] = r * Math.cos(theta); |
| 152 | + if (i + 1 < size) { |
| 153 | + noise[i + 1] = r * Math.sin(theta); |
| 154 | + } |
| 155 | + } |
| 156 | + return noise; |
| 157 | +} |
0 commit comments