@the-inclusionist/engine 9.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -78
- package/app/css/style.css +761 -377
- package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
- package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
- package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
- package/app/public/vendor/fonts.css +36 -84
- package/dist-pkg/boot/create-game.d.ts +499 -219
- package/dist-pkg/boot/create-game.js +3944 -562
- package/dist-pkg/core/a11y-sr.d.ts +25 -3
- package/dist-pkg/core/a11y-sr.js +38 -19
- package/dist-pkg/core/accessible-label.d.ts +17 -0
- package/dist-pkg/core/accessible-label.js +37 -0
- package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
- package/dist-pkg/core/accommodation-subjects.js +32 -0
- package/dist-pkg/core/accommodations.d.ts +125 -0
- package/dist-pkg/core/accommodations.js +195 -0
- package/dist-pkg/core/actions.d.ts +108 -55
- package/dist-pkg/core/actions.js +122 -71
- package/dist-pkg/core/calm-mode.d.ts +31 -0
- package/dist-pkg/core/calm-mode.js +47 -0
- package/dist-pkg/core/camera-cycle.d.ts +15 -0
- package/dist-pkg/core/camera-cycle.js +7 -0
- package/dist-pkg/core/caption-duration.d.ts +15 -0
- package/dist-pkg/core/caption-duration.js +19 -0
- package/dist-pkg/core/cartridge-problems.d.ts +34 -0
- package/dist-pkg/core/cartridge-problems.js +53 -0
- package/dist-pkg/core/constants.d.ts +0 -54
- package/dist-pkg/core/constants.js +9 -76
- package/dist-pkg/core/contract.d.ts +142 -164
- package/dist-pkg/core/contract.js +222 -208
- package/dist-pkg/core/dom-query.d.ts +4 -4
- package/dist-pkg/core/dom-query.js +12 -20
- package/dist-pkg/core/entity.d.ts +62 -77
- package/dist-pkg/core/entity.js +18 -32
- package/dist-pkg/core/escape-html.d.ts +9 -10
- package/dist-pkg/core/escape-html.js +16 -20
- package/dist-pkg/core/flash-threshold.d.ts +17 -0
- package/dist-pkg/core/flash-threshold.js +113 -0
- package/dist-pkg/core/game-speed.d.ts +11 -0
- package/dist-pkg/core/game-speed.js +16 -0
- package/dist-pkg/core/genres.d.ts +20 -0
- package/dist-pkg/core/genres.js +44 -0
- package/dist-pkg/core/i18n.d.ts +71 -60
- package/dist-pkg/core/i18n.js +186 -200
- package/dist-pkg/core/loop.d.ts +17 -6
- package/dist-pkg/core/loop.js +26 -26
- package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
- package/dist-pkg/core/pause-icon-catalogue.js +54 -0
- package/dist-pkg/core/ring.d.ts +17 -0
- package/dist-pkg/core/ring.js +29 -0
- package/dist-pkg/core/rng.d.ts +7 -12
- package/dist-pkg/core/rng.js +23 -53
- package/dist-pkg/core/route.d.ts +26 -26
- package/dist-pkg/core/route.js +126 -121
- package/dist-pkg/core/scenes.d.ts +37 -40
- package/dist-pkg/core/scenes.js +41 -43
- package/dist-pkg/core/screens.d.ts +4 -4
- package/dist-pkg/core/screens.js +11 -16
- package/dist-pkg/core/session-clock.d.ts +23 -0
- package/dist-pkg/core/session-clock.js +37 -0
- package/dist-pkg/core/setting-defaults.d.ts +61 -0
- package/dist-pkg/core/setting-defaults.js +66 -0
- package/dist-pkg/core/speech-rate.d.ts +29 -0
- package/dist-pkg/core/speech-rate.js +47 -0
- package/dist-pkg/core/state.d.ts +118 -118
- package/dist-pkg/core/state.js +322 -297
- package/dist-pkg/core/visual-cycles.d.ts +30 -0
- package/dist-pkg/core/visual-cycles.js +52 -0
- package/dist-pkg/core/visual-state.d.ts +13 -0
- package/dist-pkg/core/visual-state.js +6 -0
- package/dist-pkg/educational/activities-registry.d.ts +1 -1
- package/dist-pkg/educational/activities-registry.js +9 -8
- package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
- package/dist-pkg/educational/adaptive-engine.js +17 -17
- package/dist-pkg/educational/segment-bar.d.ts +12 -12
- package/dist-pkg/educational/segment-bar.js +9 -9
- package/dist-pkg/i18n/en.js +324 -223
- package/dist-pkg/i18n/es.js +323 -223
- package/dist-pkg/i18n/pt.js +348 -226
- package/dist-pkg/input/default-bindings.d.ts +37 -38
- package/dist-pkg/input/default-bindings.js +127 -144
- package/dist-pkg/input/devices.d.ts +7 -7
- package/dist-pkg/input/devices.js +17 -16
- package/dist-pkg/input/edges.d.ts +23 -32
- package/dist-pkg/input/edges.js +23 -28
- package/dist-pkg/input/empathy-filter.d.ts +22 -0
- package/dist-pkg/input/empathy-filter.js +34 -0
- package/dist-pkg/input/face-map.d.ts +96 -0
- package/dist-pkg/input/face-map.js +255 -0
- package/dist-pkg/input/face-signals.d.ts +40 -0
- package/dist-pkg/input/face-signals.js +48 -0
- package/dist-pkg/input/gamepad.d.ts +195 -145
- package/dist-pkg/input/gamepad.js +383 -556
- package/dist-pkg/input/gaze-cycle.d.ts +64 -0
- package/dist-pkg/input/gaze-cycle.js +133 -0
- package/dist-pkg/input/gaze-relative.d.ts +68 -0
- package/dist-pkg/input/gaze-relative.js +142 -0
- package/dist-pkg/input/hand-map.d.ts +28 -0
- package/dist-pkg/input/hand-map.js +142 -0
- package/dist-pkg/input/input-cooldown.d.ts +17 -0
- package/dist-pkg/input/input-cooldown.js +39 -0
- package/dist-pkg/input/key-default.d.ts +30 -0
- package/dist-pkg/input/key-default.js +60 -0
- package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
- package/dist-pkg/input/keyboard-runtime.js +23 -27
- package/dist-pkg/input/keyboard.d.ts +68 -63
- package/dist-pkg/input/keyboard.js +83 -105
- package/dist-pkg/input/keydown.d.ts +123 -137
- package/dist-pkg/input/keydown.js +284 -289
- package/dist-pkg/input/latch-edge.d.ts +33 -21
- package/dist-pkg/input/latch-edge.js +34 -35
- package/dist-pkg/input/latch-scope.d.ts +51 -50
- package/dist-pkg/input/latch-scope.js +61 -82
- package/dist-pkg/input/latch-store.d.ts +26 -30
- package/dist-pkg/input/latch-store.js +42 -50
- package/dist-pkg/input/latch-sync.d.ts +29 -29
- package/dist-pkg/input/latch-sync.js +25 -25
- package/dist-pkg/input/latch.d.ts +14 -17
- package/dist-pkg/input/latch.js +29 -37
- package/dist-pkg/input/pad-defaults.d.ts +13 -16
- package/dist-pkg/input/pad-defaults.js +21 -28
- package/dist-pkg/input/pad-reading.d.ts +76 -0
- package/dist-pkg/input/pad-reading.js +169 -0
- package/dist-pkg/input/pad-wizard.d.ts +99 -0
- package/dist-pkg/input/pad-wizard.js +256 -0
- package/dist-pkg/input/pointer-space.d.ts +19 -20
- package/dist-pkg/input/pointer-space.js +27 -32
- package/dist-pkg/input/pointer.d.ts +39 -40
- package/dist-pkg/input/pointer.js +42 -45
- package/dist-pkg/input/state.d.ts +101 -89
- package/dist-pkg/input/state.js +54 -134
- package/dist-pkg/input/switch-scan.d.ts +59 -0
- package/dist-pkg/input/switch-scan.js +67 -0
- package/dist-pkg/input/synthetic-source.d.ts +42 -0
- package/dist-pkg/input/synthetic-source.js +57 -0
- package/dist-pkg/input/touch-bindings.d.ts +127 -116
- package/dist-pkg/input/touch-bindings.js +158 -173
- package/dist-pkg/input/touch.d.ts +147 -72
- package/dist-pkg/input/touch.js +362 -126
- package/dist-pkg/input/transport-in-use.d.ts +107 -0
- package/dist-pkg/input/transport-in-use.js +130 -0
- package/dist-pkg/input/transports.d.ts +90 -117
- package/dist-pkg/input/transports.js +104 -106
- package/dist-pkg/input/virtual-controller.d.ts +82 -0
- package/dist-pkg/input/virtual-controller.js +69 -0
- package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
- package/dist-pkg/input/vocabulary-migration.js +80 -79
- package/dist-pkg/input/voice-map.d.ts +59 -0
- package/dist-pkg/input/voice-map.js +123 -0
- package/dist-pkg/platform/audio-ambient.d.ts +13 -2
- package/dist-pkg/platform/audio-ambient.js +14 -14
- package/dist-pkg/platform/audio-earcons.d.ts +20 -19
- package/dist-pkg/platform/audio-earcons.js +51 -42
- package/dist-pkg/platform/audio-jingles.js +9 -10
- package/dist-pkg/platform/audio-mixer.d.ts +14 -2
- package/dist-pkg/platform/audio-mixer.js +31 -39
- package/dist-pkg/platform/audio-sonar.d.ts +49 -152
- package/dist-pkg/platform/audio-sonar.js +124 -302
- package/dist-pkg/platform/audio.d.ts +62 -19
- package/dist-pkg/platform/audio.js +193 -168
- package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
- package/dist-pkg/platform/choose-by-voice.js +157 -0
- package/dist-pkg/platform/flash-sampler.d.ts +19 -0
- package/dist-pkg/platform/flash-sampler.js +79 -0
- package/dist-pkg/platform/font-library.d.ts +77 -0
- package/dist-pkg/platform/font-library.js +98 -0
- package/dist-pkg/platform/font-library.json +721 -0
- package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
- package/dist-pkg/platform/heavy-catalogue.js +256 -0
- package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
- package/dist-pkg/platform/heavy-mirror.js +73 -0
- package/dist-pkg/platform/heavy.d.ts +136 -0
- package/dist-pkg/platform/heavy.js +324 -0
- package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
- package/dist-pkg/platform/interruptible-speech.js +77 -54
- package/dist-pkg/platform/kokoro-port.d.ts +27 -0
- package/dist-pkg/platform/kokoro-port.js +50 -0
- package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
- package/dist-pkg/platform/kokoro-runtime.js +52 -0
- package/dist-pkg/platform/kokoro.d.ts +70 -0
- package/dist-pkg/platform/kokoro.js +133 -0
- package/dist-pkg/platform/listener-scope.d.ts +12 -0
- package/dist-pkg/platform/listener-scope.js +90 -0
- package/dist-pkg/platform/locale-host.d.ts +14 -0
- package/dist-pkg/platform/locale-host.js +45 -0
- package/dist-pkg/platform/microphone.d.ts +51 -0
- package/dist-pkg/platform/microphone.js +118 -0
- package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
- package/dist-pkg/platform/onnx-runtime.js +42 -0
- package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
- package/dist-pkg/platform/reading-in-worker.js +125 -0
- package/dist-pkg/platform/reading-model.d.ts +94 -0
- package/dist-pkg/platform/reading-model.js +272 -0
- package/dist-pkg/platform/reading-runtime.d.ts +24 -0
- package/dist-pkg/platform/reading-runtime.js +164 -0
- package/dist-pkg/platform/reading-worker.d.ts +41 -0
- package/dist-pkg/platform/reading-worker.js +58 -0
- package/dist-pkg/platform/reading.d.ts +80 -0
- package/dist-pkg/platform/reading.js +136 -0
- package/dist-pkg/platform/speech-recognition.d.ts +60 -0
- package/dist-pkg/platform/speech-recognition.js +104 -0
- package/dist-pkg/platform/speech.d.ts +23 -1
- package/dist-pkg/platform/speech.js +29 -18
- package/dist-pkg/platform/storage-keys.d.ts +96 -0
- package/dist-pkg/platform/storage-keys.js +106 -0
- package/dist-pkg/platform/storage.d.ts +46 -111
- package/dist-pkg/platform/storage.js +104 -169
- package/dist-pkg/platform/tts.d.ts +85 -39
- package/dist-pkg/platform/tts.js +447 -127
- package/dist-pkg/platform/vision-loop.d.ts +23 -0
- package/dist-pkg/platform/vision-loop.js +56 -0
- package/dist-pkg/platform/vision.d.ts +132 -0
- package/dist-pkg/platform/vision.js +117 -0
- package/dist-pkg/platform/voice-listener.d.ts +40 -0
- package/dist-pkg/platform/voice-listener.js +88 -0
- package/dist-pkg/platform/voice-plan.d.ts +15 -98
- package/dist-pkg/platform/voice-plan.js +56 -143
- package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
- package/dist-pkg/platform/vosk-runtime.js +151 -0
- package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
- package/dist-pkg/platform/vosk-vocabulary.js +178 -0
- package/dist-pkg/render/canvas.d.ts +13 -7
- package/dist-pkg/render/canvas.js +13 -13
- package/dist-pkg/render/crt.d.ts +48 -12
- package/dist-pkg/render/crt.js +78 -80
- package/dist-pkg/render/cvd-matrices.d.ts +22 -18
- package/dist-pkg/render/cvd-matrices.js +37 -38
- package/dist-pkg/render/hc-role-data.d.ts +7 -7
- package/dist-pkg/render/hc-role-data.js +14 -14
- package/dist-pkg/render/high-contrast.d.ts +67 -56
- package/dist-pkg/render/high-contrast.js +226 -217
- package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
- package/dist-pkg/render/low-vision-drawing.js +46 -0
- package/dist-pkg/render/lq-filter.d.ts +32 -20
- package/dist-pkg/render/lq-filter.js +59 -65
- package/dist-pkg/render/port.d.ts +86 -88
- package/dist-pkg/render/port.js +17 -23
- package/dist-pkg/render/screen-pipeline.d.ts +42 -42
- package/dist-pkg/render/screen-pipeline.js +80 -87
- package/dist-pkg/render/sprite-fx.d.ts +2 -1
- package/dist-pkg/render/sprite-fx.js +16 -14
- package/dist-pkg/render/viewports.d.ts +20 -15
- package/dist-pkg/render/viewports.js +83 -114
- package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
- package/dist-pkg/render/viz-axes-labels.js +88 -0
- package/dist-pkg/render/viz-axes.d.ts +89 -86
- package/dist-pkg/render/viz-axes.js +128 -116
- package/dist-pkg/render/viz-modes.d.ts +28 -29
- package/dist-pkg/render/viz-modes.js +40 -45
- package/dist-pkg/render/viz-refusal.d.ts +32 -0
- package/dist-pkg/render/viz-refusal.js +57 -0
- package/dist-pkg/render/viz-setters.d.ts +104 -66
- package/dist-pkg/render/viz-setters.js +182 -191
- package/dist-pkg/ui/aac-sets.d.ts +45 -0
- package/dist-pkg/ui/aac-sets.js +41 -0
- package/dist-pkg/ui/audio-choices.d.ts +68 -0
- package/dist-pkg/ui/audio-choices.js +98 -0
- package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
- package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
- package/dist-pkg/ui/camera-control.d.ts +53 -0
- package/dist-pkg/ui/camera-control.js +48 -0
- package/dist-pkg/ui/changed-mark.d.ts +9 -8
- package/dist-pkg/ui/changed-mark.js +22 -53
- package/dist-pkg/ui/control-choices.d.ts +37 -0
- package/dist-pkg/ui/control-choices.js +84 -0
- package/dist-pkg/ui/debug-panel.d.ts +59 -43
- package/dist-pkg/ui/debug-panel.js +68 -69
- package/dist-pkg/ui/declared-words.d.ts +21 -0
- package/dist-pkg/ui/declared-words.js +66 -0
- package/dist-pkg/ui/dom.d.ts +10 -14
- package/dist-pkg/ui/dom.js +10 -47
- package/dist-pkg/ui/drawing-problems.d.ts +25 -0
- package/dist-pkg/ui/drawing-problems.js +94 -0
- package/dist-pkg/ui/eye-control.d.ts +36 -0
- package/dist-pkg/ui/eye-control.js +178 -0
- package/dist-pkg/ui/face-control.d.ts +25 -0
- package/dist-pkg/ui/face-control.js +183 -0
- package/dist-pkg/ui/focus-trap.d.ts +39 -49
- package/dist-pkg/ui/focus-trap.js +56 -65
- package/dist-pkg/ui/fonts.d.ts +157 -68
- package/dist-pkg/ui/fonts.js +305 -115
- package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
- package/dist-pkg/ui/footer-scroll-driver.js +201 -0
- package/dist-pkg/ui/footer-scroll.d.ts +38 -0
- package/dist-pkg/ui/footer-scroll.js +55 -0
- package/dist-pkg/ui/game-options.d.ts +47 -0
- package/dist-pkg/ui/game-options.js +158 -0
- package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
- package/dist-pkg/ui/gaze-overlay.js +165 -0
- package/dist-pkg/ui/hand-control.d.ts +25 -0
- package/dist-pkg/ui/hand-control.js +143 -0
- package/dist-pkg/ui/help-panel.d.ts +87 -0
- package/dist-pkg/ui/help-panel.js +242 -0
- package/dist-pkg/ui/hud-bands.d.ts +62 -0
- package/dist-pkg/ui/hud-bands.js +146 -0
- package/dist-pkg/ui/hud-row.d.ts +34 -0
- package/dist-pkg/ui/hud-row.js +80 -0
- package/dist-pkg/ui/hud.d.ts +73 -73
- package/dist-pkg/ui/hud.js +94 -98
- package/dist-pkg/ui/item-announcement.d.ts +13 -12
- package/dist-pkg/ui/item-announcement.js +25 -26
- package/dist-pkg/ui/layout.d.ts +103 -28
- package/dist-pkg/ui/layout.js +117 -99
- package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
- package/dist-pkg/ui/libras-avatar-clip.js +86 -0
- package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
- package/dist-pkg/ui/libras-avatar-load.js +134 -0
- package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
- package/dist-pkg/ui/libras-avatar-plan.js +211 -0
- package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
- package/dist-pkg/ui/libras-avatar-player.js +226 -0
- package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
- package/dist-pkg/ui/libras-avatar-stage.js +208 -0
- package/dist-pkg/ui/libras-glosses.d.ts +28 -0
- package/dist-pkg/ui/libras-glosses.js +161 -0
- package/dist-pkg/ui/locale-flags.d.ts +16 -0
- package/dist-pkg/ui/locale-flags.js +30 -0
- package/dist-pkg/ui/loop-crash.d.ts +17 -17
- package/dist-pkg/ui/loop-crash.js +33 -62
- package/dist-pkg/ui/menu-intent.d.ts +61 -0
- package/dist-pkg/ui/menu-intent.js +88 -0
- package/dist-pkg/ui/menu-items.d.ts +20 -0
- package/dist-pkg/ui/menu-items.js +51 -0
- package/dist-pkg/ui/menu-nav.d.ts +98 -92
- package/dist-pkg/ui/menu-nav.js +398 -286
- package/dist-pkg/ui/mobility-choices.d.ts +26 -0
- package/dist-pkg/ui/mobility-choices.js +41 -0
- package/dist-pkg/ui/motion-choices.d.ts +31 -0
- package/dist-pkg/ui/motion-choices.js +68 -0
- package/dist-pkg/ui/motion-scene.d.ts +24 -19
- package/dist-pkg/ui/motion-scene.js +29 -61
- package/dist-pkg/ui/mount-panel.d.ts +81 -0
- package/dist-pkg/ui/mount-panel.js +61 -0
- package/dist-pkg/ui/panel-shell.d.ts +65 -31
- package/dist-pkg/ui/panel-shell.js +92 -66
- package/dist-pkg/ui/panel-widgets.d.ts +150 -0
- package/dist-pkg/ui/panel-widgets.js +294 -0
- package/dist-pkg/ui/pause-buttons.d.ts +35 -0
- package/dist-pkg/ui/pause-buttons.js +66 -0
- package/dist-pkg/ui/pause-icons.d.ts +261 -347
- package/dist-pkg/ui/pause-icons.js +671 -601
- package/dist-pkg/ui/pause-markup.d.ts +86 -0
- package/dist-pkg/ui/pause-markup.js +125 -0
- package/dist-pkg/ui/reach-notice.d.ts +22 -23
- package/dist-pkg/ui/reach-notice.js +55 -58
- package/dist-pkg/ui/scan-overlay.d.ts +23 -0
- package/dist-pkg/ui/scan-overlay.js +50 -0
- package/dist-pkg/ui/screen-text.d.ts +25 -0
- package/dist-pkg/ui/screen-text.js +152 -0
- package/dist-pkg/ui/session-clock.d.ts +32 -0
- package/dist-pkg/ui/session-clock.js +67 -0
- package/dist-pkg/ui/settings-aac.d.ts +70 -0
- package/dist-pkg/ui/settings-aac.js +208 -0
- package/dist-pkg/ui/settings-audio.d.ts +97 -114
- package/dist-pkg/ui/settings-audio.js +493 -425
- package/dist-pkg/ui/settings-controls.d.ts +53 -96
- package/dist-pkg/ui/settings-controls.js +143 -215
- package/dist-pkg/ui/settings-empathy.d.ts +39 -30
- package/dist-pkg/ui/settings-empathy.js +53 -60
- package/dist-pkg/ui/settings-mobility.d.ts +192 -0
- package/dist-pkg/ui/settings-mobility.js +332 -0
- package/dist-pkg/ui/settings-motion.d.ts +91 -70
- package/dist-pkg/ui/settings-motion.js +287 -198
- package/dist-pkg/ui/settings-panel.d.ts +58 -45
- package/dist-pkg/ui/settings-panel.js +189 -94
- package/dist-pkg/ui/settings-typo.d.ts +29 -78
- package/dist-pkg/ui/settings-typo.js +158 -129
- package/dist-pkg/ui/settings-visual.d.ts +29 -87
- package/dist-pkg/ui/settings-visual.js +240 -193
- package/dist-pkg/ui/shell.d.ts +112 -135
- package/dist-pkg/ui/shell.js +148 -217
- package/dist-pkg/ui/simulation-list.d.ts +27 -0
- package/dist-pkg/ui/simulation-list.js +76 -0
- package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
- package/dist-pkg/ui/simulation-over-the-world.js +131 -0
- package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
- package/dist-pkg/ui/simulation-refusal.js +8 -56
- package/dist-pkg/ui/switchable-control.d.ts +11 -0
- package/dist-pkg/ui/switchable-control.js +19 -0
- package/dist-pkg/ui/title.d.ts +3 -12
- package/dist-pkg/ui/title.js +49 -18
- package/dist-pkg/ui/top-band.d.ts +26 -0
- package/dist-pkg/ui/top-band.js +96 -0
- package/dist-pkg/ui/typo-choices.d.ts +71 -0
- package/dist-pkg/ui/typo-choices.js +85 -0
- package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
- package/dist-pkg/ui/visual-axes-panel.js +12 -94
- package/dist-pkg/ui/visual-choices.d.ts +77 -0
- package/dist-pkg/ui/visual-choices.js +107 -0
- package/dist-pkg/ui/vlibras.d.ts +77 -10
- package/dist-pkg/ui/vlibras.js +130 -81
- package/dist-pkg/ui/voice-control.d.ts +68 -0
- package/dist-pkg/ui/voice-control.js +276 -0
- package/dist-pkg/ui/voice-settings.d.ts +95 -0
- package/dist-pkg/ui/voice-settings.js +352 -0
- package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
- package/dist-pkg/ui/where-the-child-is.js +70 -0
- package/docs/CREDITS.md +300 -44
- package/docs/LICENSES.md +189 -145
- package/package.json +51 -27
- package/scripts/check-cartridge.mjs +78 -0
- package/scripts/game-build.d.mts +25 -0
- package/scripts/game-build.mjs +144 -0
- package/scripts/heavy-into-the-delivery.mjs +388 -0
- package/scripts/libras-avatar.json +680 -0
- package/scripts/libras-avatar.mjs +246 -0
- package/scripts/libras-glosses/gloss.py +95 -0
- package/scripts/libras-glosses/pyproject.toml +32 -0
- package/scripts/libras-glosses/uv.lock +781 -0
- package/scripts/libras-glosses.mjs +351 -0
- package/scripts/licences/Apache-2.0.txt +176 -0
- package/scripts/licences/GPL-3.0.txt +674 -0
- package/scripts/licences/MIT.txt +17 -0
- package/scripts/licences/OFL-1.1.txt +86 -0
- package/scripts/licences/UFL-1.0.txt +96 -0
- package/scripts/licences/fonts.mjs +389 -0
- package/scripts/licences/third-party.mjs +301 -0
- package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
- package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
- package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-700.woff2 +0 -0
- package/app/public/vendor/fonts/lato-400.woff2 +0 -0
- package/app/public/vendor/fonts/lato-700.woff2 +0 -0
- package/app/public/vendor/fonts/literata-400.woff2 +0 -0
- package/app/public/vendor/fonts/literata-700.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
- package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
- package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
- package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
- package/dist-pkg/core/anel.d.ts +0 -17
- package/dist-pkg/core/anel.js +0 -31
- package/dist-pkg/core/collision.d.ts +0 -21
- package/dist-pkg/core/collision.js +0 -62
- package/dist-pkg/core/layers.d.ts +0 -63
- package/dist-pkg/core/layers.js +0 -95
- package/dist-pkg/core/letter-grid.d.ts +0 -55
- package/dist-pkg/core/letter-grid.js +0 -116
- package/dist-pkg/core/password.d.ts +0 -34
- package/dist-pkg/core/password.js +0 -205
- package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
- package/dist-pkg/core/rotulo-acessivel.js +0 -41
- package/dist-pkg/core/run-state.d.ts +0 -115
- package/dist-pkg/core/run-state.js +0 -40
- package/dist-pkg/core/tiles.d.ts +0 -12
- package/dist-pkg/core/tiles.js +0 -61
- package/dist-pkg/core/world.d.ts +0 -4
- package/dist-pkg/core/world.js +0 -58
- package/dist-pkg/input/origem-sintetica.d.ts +0 -44
- package/dist-pkg/input/origem-sintetica.js +0 -60
- package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
- package/dist-pkg/input/transporte-em-uso.js +0 -130
- package/dist-pkg/platform/audio-nav.d.ts +0 -51
- package/dist-pkg/platform/audio-nav.js +0 -99
- package/dist-pkg/platform/guide-intensity.d.ts +0 -40
- package/dist-pkg/platform/guide-intensity.js +0 -73
- package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
- package/dist-pkg/platform/pesados-catalogo.js +0 -177
- package/dist-pkg/platform/pesados.d.ts +0 -31
- package/dist-pkg/platform/pesados.js +0 -75
- package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
- package/dist-pkg/platform/vozes-prontas.js +0 -61
- package/dist-pkg/render/camera.d.ts +0 -58
- package/dist-pkg/render/camera.js +0 -105
- package/dist-pkg/render/cenario-data.d.ts +0 -127
- package/dist-pkg/render/cenario-data.js +0 -145
- package/dist-pkg/render/city-tex.d.ts +0 -63
- package/dist-pkg/render/city-tex.js +0 -228
- package/dist-pkg/render/city-tiles.d.ts +0 -11
- package/dist-pkg/render/city-tiles.js +0 -140
- package/dist-pkg/render/draw.d.ts +0 -149
- package/dist-pkg/render/draw.js +0 -231
- package/dist-pkg/render/fx.d.ts +0 -63
- package/dist-pkg/render/fx.js +0 -123
- package/dist-pkg/render/minimap.d.ts +0 -11
- package/dist-pkg/render/minimap.js +0 -94
- package/dist-pkg/render/parallax.d.ts +0 -85
- package/dist-pkg/render/parallax.js +0 -158
- package/dist-pkg/render/player-anim.d.ts +0 -71
- package/dist-pkg/render/player-anim.js +0 -152
- package/dist-pkg/render/recycling-tex.d.ts +0 -48
- package/dist-pkg/render/recycling-tex.js +0 -164
- package/dist-pkg/render/scene-city.d.ts +0 -77
- package/dist-pkg/render/scene-city.js +0 -181
- package/dist-pkg/render/scene-parallax.d.ts +0 -72
- package/dist-pkg/render/scene-parallax.js +0 -426
- package/dist-pkg/render/scene-sky.d.ts +0 -126
- package/dist-pkg/render/scene-sky.js +0 -295
- package/dist-pkg/render/set-cenario.d.ts +0 -54
- package/dist-pkg/render/set-cenario.js +0 -103
- package/dist-pkg/render/textures.d.ts +0 -75
- package/dist-pkg/render/textures.js +0 -240
- package/dist-pkg/render/title-scene.d.ts +0 -46
- package/dist-pkg/render/title-scene.js +0 -75
- package/dist-pkg/render/weather.d.ts +0 -96
- package/dist-pkg/render/weather.js +0 -171
- package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
- package/dist-pkg/render/wheelchair-sprites.js +0 -69
- package/dist-pkg/render/world-tex.d.ts +0 -18
- package/dist-pkg/render/world-tex.js +0 -87
- package/dist-pkg/ui/activities-menu.d.ts +0 -258
- package/dist-pkg/ui/activities-menu.js +0 -648
- package/dist-pkg/ui/caa-sets.d.ts +0 -39
- package/dist-pkg/ui/caa-sets.js +0 -65
- package/dist-pkg/ui/latch-refusal.d.ts +0 -32
- package/dist-pkg/ui/latch-refusal.js +0 -60
- package/dist-pkg/ui/map-hub.d.ts +0 -56
- package/dist-pkg/ui/map-hub.js +0 -138
- package/dist-pkg/ui/settings-caa.d.ts +0 -45
- package/dist-pkg/ui/settings-caa.js +0 -137
- package/dist-pkg/ui/settings-motor.d.ts +0 -175
- package/dist-pkg/ui/settings-motor.js +0 -342
- package/dist-pkg/ui/webcam.d.ts +0 -7
- package/dist-pkg/ui/webcam.js +0 -93
package/dist-pkg/platform/tts.js
CHANGED
|
@@ -1,171 +1,491 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
// platform/tts —
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// · DUAS VOZES pt-BR: o `VoiceId` da 1.0.4 instalada traz 118 vozes, e entre elas as QUATRO exactas do
|
|
21
|
-
// ADR-0110 — `pt_BR-faber-medium`, `en_US-ryan-medium`, `en_US-amy-medium`, `es_MX-claude-high`.
|
|
22
|
-
// ⚠️ O QUE CONTINUA VERDADEIRO, e é mais afiado do que o motivo velho: o bundle prende `HF_BASE` a
|
|
23
|
-
// `huggingface.co/diffusionstudio/piper-voices` COM UMA GUARDA (`if (!url.match("https://huggingface.co")) return`),
|
|
24
|
-
// logo nenhum espelho de escola é alcançável por ele; e ele busca o PRÓPRIO runtime de cdnjs/jsDelivr por
|
|
25
|
-
// omissão, que é o que o ADR-0114 retira. A montante, o Piper mudou para `OHF-Voice/piper1-gpl` e a Open Home
|
|
26
|
-
// Foundation procura mantenedores — o risco existe, mas está noutro sítio.
|
|
27
|
-
// 📌 A ESCOLHA CONTINUA A SER A #129, e este comentário não a toma: descreve o que foi medido para que o
|
|
28
|
-
// próximo leitor não herde o motivo velho como se fosse medição.
|
|
29
|
-
// ⚠️ E DESDE O ADR-0094 ESTE MÓDULO NÃO NOMEIA FORNECEDOR NENHUM — quem o nomeia é o JOGO, por `ctx.carregarVozNeural`.
|
|
30
|
-
// O nome estava aqui num `import()` e o pacote em `devDependencies`, o que publicou uma engine que não compilava
|
|
31
|
-
// (ADR-0093); pô-lo em `dependencies` consertava o build e obrigava todo consumidor a 135,4 MB de `onnxruntime-web`,
|
|
32
|
-
// que o fornecedor traz como peer não-opcional. A porta é o que faz a troca do ADR-0022 não ser um segundo abalo.
|
|
33
|
-
/**
|
|
34
|
-
* Voz NEURAL por idioma, indexada pela etiqueta BCP-47 de core/i18n. Só o pt-BR tem uma hoje, e essa ausência
|
|
35
|
-
* é agora CONSULTÁVEL em vez de presumida: `loadTTS` pergunta à tabela se existe voz para o idioma corrente,
|
|
36
|
-
* em vez de carregar a portuguesa aconteça o que acontecer.
|
|
37
|
-
*
|
|
38
|
-
* Era `TTS_SOURCES['pt-BR']` escrito à mão no ponto de uso, o que fazia o jogo em inglês baixar 25,6 MB de
|
|
39
|
-
* voz portuguesa para ler texto em inglês com fonética errada — pior que não ter voz neural nenhuma, porque
|
|
40
|
-
* gasta a banda da escola para entregar algo ininteligível.
|
|
41
|
-
*/
|
|
42
|
-
const TTS_SOURCES = {
|
|
43
|
-
'pt-BR': { engine: 'piper', voice: 'pt_BR-faber-medium' },
|
|
44
|
-
};
|
|
2
|
+
// platform/tts — spoken narration. The browser's voice speaks first (Web Speech, ADR-0200); the neural fallback is Kokoro, which
|
|
3
|
+
// THE ENGINE ITSELF loads from the delivery (ADR-0216 §1) the first time a child picks it — the game only says it wants one; the
|
|
4
|
+
// loader, the browser's speech, the clock and the `<audio>` maker are the root's, received here (ADR-0232 D4). narrate()
|
|
5
|
+
// is the entry point, gated by the mixer's narration toggle (audioCat.tts.on) — independent of captions. The PANEL's side (the
|
|
6
|
+
// engine and voice lists) lives in `ui/voice-settings` and reads and writes the choice through this module. Injection by closure.
|
|
7
|
+
//
|
|
8
|
+
// A SPOKEN TEXT CARRIES ITS LANGUAGE (ADR-0243): `narrate` takes a text, in the interface's language, or parts spoken in order —
|
|
9
|
+
// the frame in the interface's voice, the content in a voice of its own language (`voice-plan.voiceOfLanguage`).
|
|
10
|
+
import { KEYS } from './storage-keys.js';
|
|
11
|
+
import { createInterruptibleSpeech } from './interruptible-speech.js';
|
|
12
|
+
import { voicesForLocale, voiceOfLanguage, speaksLanguageOf, languageName } from './voice-plan.js';
|
|
13
|
+
import { spokenWords, speechSeconds, speechPlaybackRate } from '../core/speech-rate.js';
|
|
14
|
+
import { KOKORO_VOICES, tokenize, sentenceStyle, eFala, wavDe, } from './kokoro.js';
|
|
15
|
+
// 📌 The only neural engine is Kokoro (ADR-0207): the engine's earlier neural engine left when the licence chain of its voices came
|
|
16
|
+
// to light. 📌 And ADR-0094's fear — «a provider named here lands its runtime in every consumer's `node_modules`» — is answered by the
|
|
17
|
+
// delivery and not by a port: the runtime is catalogued, fetched by the build and read from `heavy/`, never imported from npm.
|
|
18
|
+
/** An engine the child may have stored before it left the engine (ADR-0207): read as no explicit choice, so the voice in use speaks. */
|
|
19
|
+
const ENGINES_THAT_LEFT = ['piper'];
|
|
45
20
|
export function createTts(ctx) {
|
|
46
|
-
|
|
47
|
-
let
|
|
48
|
-
let
|
|
21
|
+
const { t, bcp47 } = ctx.translator;
|
|
22
|
+
let ttsEngine = null, ttsLoading = false, ttsFailed = false, _narrateCount = 0;
|
|
23
|
+
let _ttsVoiceObj = null; // the selected Web Speech voice
|
|
24
|
+
// An engine set explicitly (stored, or by the panel) wins; otherwise the engine of the voice in use, which is the browser's when it
|
|
25
|
+
// offers one for the language (ADR-0200) — measured on the device at every call, since the browser lists its voices late.
|
|
26
|
+
const stored = ctx.store.get(KEYS.ttsEngine, null) || null; // webspeech | kokoro | kitten | espeak
|
|
27
|
+
let ttsEngineSelExplicito = stored && !ENGINES_THAT_LEFT.includes(stored) ? stored : null;
|
|
28
|
+
const engineSel = () => {
|
|
29
|
+
if (ttsEngineSelExplicito)
|
|
30
|
+
return ttsEngineSelExplicito;
|
|
31
|
+
const e = voiceInUse()?.engine;
|
|
32
|
+
// a fallback this game did not ask for is not the default: undeclared, the browser's voice (no «not bundled» alert)
|
|
33
|
+
return e === 'kokoro' && ctx.neuralVoice ? e : 'webspeech';
|
|
34
|
+
};
|
|
35
|
+
let kokoroBackend = null;
|
|
36
|
+
let loadedVoice = null; // the voice the loaded engine speaks; another choice reloads it
|
|
37
|
+
/** The loaded Kokoro model's speaker, any of its voices; null until the model loads. */
|
|
38
|
+
let neural = null;
|
|
39
|
+
/** A narration's number: a newer one ends the parts an older one has still to speak (ADR-0243 §1). */
|
|
40
|
+
let narrationTurn = 0;
|
|
41
|
+
// Each voice's words per minute, averaged over the utterances long enough to measure (`core/speech-rate`): what a short
|
|
42
|
+
// utterance («Voltar») is played against. Neural voices by id; browser voices by name.
|
|
43
|
+
const voiceAverage = new Map();
|
|
44
|
+
const media = (voiceName, ppm) => {
|
|
45
|
+
if (!ppm)
|
|
46
|
+
return;
|
|
47
|
+
const before = voiceAverage.get(voiceName);
|
|
48
|
+
voiceAverage.set(voiceName, before ? before * 0.7 + ppm * 0.3 : ppm);
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* WEB SPEECH FIRST (ADR-0200; issue #190): the browser's voices for the language lead the list, so where the device already speaks
|
|
52
|
+
* no neural voice is loaded; then Kokoro's where the game declared it speaks neurally (ADR-0216 §3) — the fallback.
|
|
53
|
+
*/
|
|
54
|
+
/** Every voice the browser lists, in every language: what a part in another language is matched against (ADR-0243 §2). */
|
|
55
|
+
const deviceVoices = () => {
|
|
56
|
+
let list = [];
|
|
57
|
+
try {
|
|
58
|
+
list = ctx.speech.synth()?.getVoices() ?? [];
|
|
59
|
+
}
|
|
60
|
+
catch { /* no speech synthesis here */ }
|
|
61
|
+
return list.map((v) => ({ locale: v.lang.replace('_', '-'), engine: 'webspeech', voice: 'webspeech:' + v.name }));
|
|
62
|
+
};
|
|
63
|
+
const availableVoices = () => [...voicesForLocale(bcp47(), deviceVoices()), ...(ctx.neuralVoice ? voicesForLocale(bcp47(), KOKORO_VOICES) : [])];
|
|
64
|
+
/** The browser voice an entry names, or null. */
|
|
65
|
+
const browserVoice = (id) => {
|
|
66
|
+
if (!id?.startsWith('webspeech:'))
|
|
67
|
+
return null;
|
|
68
|
+
try {
|
|
69
|
+
return ctx.speech.synth()?.getVoices().find((v) => 'webspeech:' + v.name === id) ?? null;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* The interface's voice (ADR-0243 §2 over the language of now): the stored choice, else `voice-plan`'s `VOICE_ORDER` — the exact
|
|
77
|
+
* tag before the region, a browser voice before a neural one.
|
|
78
|
+
*/
|
|
79
|
+
function voiceInUse() {
|
|
80
|
+
return voiceOfLanguage(bcp47(), availableVoices(), [ctx.store.get(KEYS.ttsVoz, null)]);
|
|
81
|
+
}
|
|
82
|
+
function chooseVoice(id) {
|
|
83
|
+
if (!availableVoices().some((v) => v.voice === id))
|
|
84
|
+
return false;
|
|
85
|
+
ctx.store.set(KEYS.ttsVoz, id);
|
|
86
|
+
ttsEngineSelExplicito = availableVoices().find((v) => v.voice === id).engine;
|
|
87
|
+
_ttsVoiceObj = browserVoice(id);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* NARRATION FOLLOWS THE LANGUAGE OF NOW (ADR-0243 §5): the page's voice object when it speaks the language of now; one picked in
|
|
92
|
+
* another language is dropped at the first utterance after the switch, and the voice in use for the new language speaks instead.
|
|
93
|
+
*/
|
|
94
|
+
function voiceObjectOfNow() {
|
|
95
|
+
if (_ttsVoiceObj && !speaksLanguageOf(_ttsVoiceObj.lang.replace('_', '-'), bcp47()))
|
|
96
|
+
_ttsVoiceObj = null;
|
|
97
|
+
return _ttsVoiceObj;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* One browser utterance: `lang` is the tag the text is in and `voice` a voice of it, or null for the browser to choose by the tag.
|
|
101
|
+
* ADR-0183 §1: the browser's `rate` is a multiplier. The voice's words a minute at rate 1 is measured on its own utterances (start
|
|
102
|
+
* to end, so the silent ends count — an approximation the neural path does not need); until one is measured, 1.
|
|
103
|
+
*/
|
|
104
|
+
function browserUtterance(text, lang, voice) {
|
|
105
|
+
const u = ctx.speech.utterance(text);
|
|
106
|
+
u.lang = lang;
|
|
107
|
+
if (voice)
|
|
108
|
+
u.voice = voice;
|
|
109
|
+
u.volume = Math.min(1, ctx.getVolume() * 1.4);
|
|
110
|
+
const voiceIdOf = 'webspeech:' + (voice?.name ?? lang);
|
|
111
|
+
const words = spokenWords(text);
|
|
112
|
+
const voiceBase = voiceAverage.get(voiceIdOf) ?? null;
|
|
113
|
+
u.rate = ctx.getSpeechPpm && voiceBase ? speechPlaybackRate(0, 0, ctx.getSpeechPpm(), voiceBase).rate : 1;
|
|
114
|
+
let startAt = 0;
|
|
115
|
+
u.onstart = () => { startAt = ctx.now(); };
|
|
116
|
+
u.onend = () => {
|
|
117
|
+
const seconds = startAt ? (ctx.now() - startAt) / 1000 : 0;
|
|
118
|
+
const measured = speechPlaybackRate(words, seconds, 150, null).voiceWpm;
|
|
119
|
+
if (measured)
|
|
120
|
+
media(voiceIdOf, measured / u.rate);
|
|
121
|
+
};
|
|
122
|
+
return u;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* An utterance in the interface's language. `u.lang` follows the game's language: fixed at pt-BR, a game in English or Spanish
|
|
126
|
+
* would ask the browser for a PORTUGUESE voice for a text that is not Portuguese — and the result is not an accent, it is
|
|
127
|
+
* unintelligible: the wrong phonetics applied to the wrong letters.
|
|
128
|
+
*/
|
|
129
|
+
const interfaceUtterance = (text) => browserUtterance(text, bcp47(), voiceObjectOfNow() ?? browserVoice(voiceInUse()?.voice));
|
|
49
130
|
function speakWebSpeech(text) {
|
|
50
131
|
try {
|
|
51
|
-
const ss =
|
|
132
|
+
const ss = ctx.speech.synth();
|
|
52
133
|
if (!ss)
|
|
53
134
|
return false;
|
|
54
135
|
ss.cancel();
|
|
55
|
-
|
|
56
|
-
// PORTUGUESA para um texto que não é português — e o resultado não é sotaque, é ininteligível: a
|
|
57
|
-
// fonética errada aplicada às letras erradas. Segue o idioma do jogo.
|
|
58
|
-
const u = new SpeechSynthesisUtterance(text);
|
|
59
|
-
u.lang = bcp47();
|
|
60
|
-
if (_ttsVoiceObj)
|
|
61
|
-
u.voice = _ttsVoiceObj;
|
|
62
|
-
u.rate = 1;
|
|
63
|
-
u.volume = Math.min(1, ctx.getVolume() * 1.4);
|
|
64
|
-
ss.speak(u);
|
|
136
|
+
ss.speak(interfaceUtterance(text));
|
|
65
137
|
return true;
|
|
66
138
|
}
|
|
67
139
|
catch (e) {
|
|
68
140
|
return false;
|
|
69
141
|
}
|
|
70
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* A neural voice's utterances, whatever made the WAV (ADR-0183 §1): every utterance is measured — its words over its speech time,
|
|
145
|
+
* silent ends trimmed — and played at the child's rate over the voice's, through a media element that keeps the pitch. Each
|
|
146
|
+
* request names its voice, so one loaded model reads the parts of every language it has a voice for (ADR-0243 §2).
|
|
147
|
+
*/
|
|
148
|
+
function speakByWav(produce) {
|
|
149
|
+
return createInterruptibleSpeech({
|
|
150
|
+
synthesize: async (request) => {
|
|
151
|
+
const { text } = request, voiceId = request.voice.voice;
|
|
152
|
+
const bytes = await produce(request);
|
|
153
|
+
const ac = ctx.ensureAC();
|
|
154
|
+
if (!ac)
|
|
155
|
+
throw new Error('AudioContext unavailable'); // the `catch` there handles it: this item is silent, the engine lives
|
|
156
|
+
let speedRatio = 1;
|
|
157
|
+
if (ctx.getSpeechPpm) {
|
|
158
|
+
const buf = await ac.decodeAudioData(bytes.slice(0));
|
|
159
|
+
const measure = speechPlaybackRate(spokenWords(text), speechSeconds(buf.getChannelData(0), buf.sampleRate), ctx.getSpeechPpm(), voiceAverage.get(voiceId) ?? null);
|
|
160
|
+
media(voiceId, measure.voiceWpm);
|
|
161
|
+
speedRatio = measure.rate;
|
|
162
|
+
}
|
|
163
|
+
return { url: URL.createObjectURL(new Blob([bytes], { type: 'audio/wav' })), rate: speedRatio };
|
|
164
|
+
},
|
|
165
|
+
play: (sound, onFinish) => {
|
|
166
|
+
const ac = ctx.ensureAC();
|
|
167
|
+
if (!ac) {
|
|
168
|
+
URL.revokeObjectURL(sound.url);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const el = ctx.createAudio();
|
|
172
|
+
el.preservesPitch = true;
|
|
173
|
+
el.src = sound.url;
|
|
174
|
+
el.playbackRate = sound.rate;
|
|
175
|
+
try {
|
|
176
|
+
ac.createMediaElementSource(el).connect(ctx.catNode('tts') || ctx.audioOut() || ac.destination);
|
|
177
|
+
}
|
|
178
|
+
catch { /* already routed */ }
|
|
179
|
+
el.onended = () => { URL.revokeObjectURL(sound.url); onFinish(); };
|
|
180
|
+
void el.play().catch(() => { URL.revokeObjectURL(sound.url); onFinish(); });
|
|
181
|
+
return el;
|
|
182
|
+
},
|
|
183
|
+
stop: (el) => { el.pause(); URL.revokeObjectURL(el.src); },
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* KOKORO (ADR-0186, ADR-0216; issues #181, #200): the voice's text becomes espeak-ng phonemes in its language, then the model's
|
|
188
|
+
* ids and a waveform. WebGPU only where a short test synthesis is speech — measured, WebGPU can run and return noise — and WASM
|
|
189
|
+
* with threads otherwise (ADR-0192).
|
|
190
|
+
*/
|
|
191
|
+
function loadKokoroEngine(forVoice) {
|
|
192
|
+
const load = ctx.loadKokoro;
|
|
193
|
+
const kv = KOKORO_VOICES.find((v) => v.voice === forVoice.voice);
|
|
194
|
+
if (!kv) {
|
|
195
|
+
ttsFailed = true;
|
|
196
|
+
ctx.srAlert(t('sr.tts.neuralNotBundled'));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
ttsLoading = true;
|
|
200
|
+
loadedVoice = forVoice.voice;
|
|
201
|
+
const t0 = ctx.now();
|
|
202
|
+
ctx.srSay(t('sr.tts.downloading'));
|
|
203
|
+
load().then(async (mod) => {
|
|
204
|
+
const [symbolIds, table] = await Promise.all([mod.vocabulary(), mod.voice(kv.voice)]);
|
|
205
|
+
// Each voice's style table, fetched the first time an utterance asks for that voice; a failed fetch is tried again.
|
|
206
|
+
const tables = new Map([[kv.voice, Promise.resolve(table)]]);
|
|
207
|
+
const tableOf = (voice) => {
|
|
208
|
+
let styles = tables.get(voice.voice);
|
|
209
|
+
if (!styles) {
|
|
210
|
+
styles = mod.voice(voice.voice);
|
|
211
|
+
tables.set(voice.voice, styles);
|
|
212
|
+
styles.catch(() => tables.delete(voice.voice));
|
|
213
|
+
}
|
|
214
|
+
return styles;
|
|
215
|
+
};
|
|
216
|
+
const synthesiseWith = async (kokoroSession, text, voice) => {
|
|
217
|
+
const ids = tokenize(await mod.phonemize(text, voice.espeak), symbolIds);
|
|
218
|
+
return kokoroSession.synthesize(ids, sentenceStyle(await tableOf(voice), ids.length - 2));
|
|
219
|
+
};
|
|
220
|
+
let kokoroSession = null;
|
|
221
|
+
try {
|
|
222
|
+
const gpu = await mod.session('webgpu');
|
|
223
|
+
if (eFala(await synthesiseWith(gpu, t('tts.kokoro.teste'), kv))) {
|
|
224
|
+
kokoroSession = gpu;
|
|
225
|
+
kokoroBackend = 'webgpu';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch { /* no WebGPU here: WASM below */ }
|
|
229
|
+
if (!kokoroSession) {
|
|
230
|
+
kokoroSession = await mod.session('wasm');
|
|
231
|
+
kokoroBackend = 'wasm';
|
|
232
|
+
}
|
|
233
|
+
const used = kokoroSession;
|
|
234
|
+
const speaker = speakByWav(async (request) => wavDe(await synthesiseWith(used, request.text, request.voice)));
|
|
235
|
+
neural = speaker;
|
|
236
|
+
ttsEngine = { id: 'kokoro', speak: (text, finished) => { speaker.speak({ text, voice: kv }, finished); } };
|
|
237
|
+
ttsLoading = false;
|
|
238
|
+
try {
|
|
239
|
+
ctx.speech.synth()?.cancel();
|
|
240
|
+
}
|
|
241
|
+
catch (e) { /* noop */ }
|
|
242
|
+
narrate(t('sr.tts.ready', { s: ((ctx.now() - t0) / 1000).toFixed(0) }));
|
|
243
|
+
}).catch(() => { ttsLoading = false; ttsFailed = true; ctx.srAlert(t('sr.tts.loadFailed')); });
|
|
244
|
+
}
|
|
71
245
|
function loadTTS() {
|
|
72
246
|
if (ttsEngine || ttsLoading || ttsFailed)
|
|
73
247
|
return;
|
|
74
|
-
|
|
75
|
-
|
|
248
|
+
const sel = engineSel();
|
|
249
|
+
if (sel !== 'kokoro') { // Kitten and eSpeak NG are not built
|
|
250
|
+
if (sel !== 'webspeech')
|
|
76
251
|
ctx.srAlert(t('sr.tts.engineNoLanguage'));
|
|
77
252
|
return;
|
|
78
253
|
}
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
if (!
|
|
254
|
+
// THIS GAME DID NOT ASK FOR A NEURAL VOICE (ADR-0216 §3). It comes BEFORE the language question on purpose: without
|
|
255
|
+
// the declaration there is no neural voice in any language — the delivery does not carry the model — and saying "there
|
|
256
|
+
// is no voice for your language" would make the child think switching language would fix it. `ttsFailed` so it is not
|
|
257
|
+
// repeated on every utterance.
|
|
258
|
+
if (!ctx.neuralVoice) {
|
|
84
259
|
ttsFailed = true;
|
|
85
260
|
ctx.srAlert(t('sr.tts.neuralNotBundled'));
|
|
86
261
|
return;
|
|
87
262
|
}
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
const
|
|
91
|
-
|
|
263
|
+
// The voice in use for the current language (ADR-0185); a browser voice in use with Kokoro set explicitly gives way to the
|
|
264
|
+
// language's first Kokoro voice. None: another language's voice is NOT fetched — it would read this text with the wrong phonetics.
|
|
265
|
+
const current = voiceInUse();
|
|
266
|
+
const forVoice = current?.engine === 'kokoro' ? current : voicesForLocale(bcp47(), KOKORO_VOICES)[0] ?? null;
|
|
267
|
+
if (!forVoice) {
|
|
92
268
|
ctx.srAlert(t('sr.tts.noNeuralForLanguage'));
|
|
93
269
|
return;
|
|
94
270
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const fala = criarFalaInterrompivel({
|
|
115
|
-
sintetizar: async (texto) => {
|
|
116
|
-
const wav = await session.predict(texto);
|
|
117
|
-
const ac = ctx.ensureAC();
|
|
118
|
-
if (!ac)
|
|
119
|
-
throw new Error('AudioContext unavailable'); // o `catch` de lá trata: silêncio deste item, motor vivo
|
|
120
|
-
return ac.decodeAudioData(await wav.arrayBuffer());
|
|
121
|
-
},
|
|
122
|
-
tocar: (buf, aoTerminar) => {
|
|
123
|
-
const ac = ctx.ensureAC();
|
|
124
|
-
if (!ac)
|
|
125
|
-
return null;
|
|
126
|
-
const src = ac.createBufferSource();
|
|
127
|
-
src.buffer = buf;
|
|
128
|
-
src.connect(ctx.catNode('tts') || ctx.audioOut() || ac.destination);
|
|
129
|
-
src.onended = aoTerminar;
|
|
130
|
-
src.start();
|
|
131
|
-
return src;
|
|
132
|
-
},
|
|
133
|
-
parar: (src) => { src.stop(); },
|
|
134
|
-
});
|
|
135
|
-
ttsEngine = { id: 'piper', speak: (text) => { fala.falar(text); } };
|
|
136
|
-
ttsLoading = false;
|
|
271
|
+
loadKokoroEngine(forVoice);
|
|
272
|
+
}
|
|
273
|
+
/** The loaded neural engine when the interface speaks neurally; else it starts loading, and the browser speaks meanwhile. */
|
|
274
|
+
function interfaceEngine() {
|
|
275
|
+
if (engineSel() === 'webspeech')
|
|
276
|
+
return null;
|
|
277
|
+
// another voice picked, or the language changed, since the engine loaded: load the voice in use
|
|
278
|
+
const inUse = voiceInUse();
|
|
279
|
+
if (ttsEngine && inUse?.engine !== 'webspeech' && loadedVoice !== inUse?.voice) {
|
|
280
|
+
ttsEngine = null;
|
|
281
|
+
ttsFailed = false;
|
|
282
|
+
}
|
|
283
|
+
if (!ttsEngine)
|
|
284
|
+
loadTTS(); // the neural engine is downloading or unavailable → falls back
|
|
285
|
+
return ttsEngine;
|
|
286
|
+
}
|
|
287
|
+
function ttsSpeak(text) {
|
|
288
|
+
const engine = interfaceEngine();
|
|
289
|
+
if (engine) {
|
|
137
290
|
try {
|
|
138
|
-
|
|
291
|
+
engine.speak(text);
|
|
139
292
|
}
|
|
140
293
|
catch (e) { /* noop */ }
|
|
141
|
-
|
|
142
|
-
}
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
neural?.silence(); // a neural part still speaking gives way: the latest narration is the one that counts
|
|
297
|
+
return speakWebSpeech(text); // immediate fallback: Web Speech, in the game's language
|
|
143
298
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
299
|
+
/** A FRAME part (ADR-0243 §1): the interface's voice, as a text narrated alone; none while narration is locked (ADR-0185 §4). */
|
|
300
|
+
function frameSegment(text) {
|
|
301
|
+
if (availableVoices().length === 0)
|
|
302
|
+
return null;
|
|
303
|
+
const engine = interfaceEngine();
|
|
304
|
+
if (engine)
|
|
305
|
+
return { neural: (finished) => { engine.speak(text, finished); } };
|
|
306
|
+
return { browser: () => interfaceUtterance(text) };
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* A CONTENT part (ADR-0243 §2): a voice of its language in `voice-plan`'s `VOICE_ORDER` — the exact tag before the region, a
|
|
310
|
+
* browser voice before the neural ones this game asked for, which read only once the model is loaded; while it loads, the part starts the load and a browser voice of the language reads it if
|
|
311
|
+
* the device has one. `null`: the device has no voice for the language, and no voice of another language reads it instead.
|
|
312
|
+
*/
|
|
313
|
+
function contentSegment(text, language) {
|
|
314
|
+
const chosen = chosenVoices();
|
|
315
|
+
const neuralVoices = ctx.neuralVoice && !ttsFailed ? KOKORO_VOICES : [];
|
|
316
|
+
const best = voiceOfLanguage(language, [...deviceVoices(), ...neuralVoices], chosen);
|
|
317
|
+
if (!best)
|
|
318
|
+
return noVoiceSegment(language);
|
|
319
|
+
const kokoro = KOKORO_VOICES.find((v) => v.voice === best.voice);
|
|
320
|
+
return kokoro ? neuralSegment(text, language, kokoro, chosen) : browserSegment(text, language, best);
|
|
321
|
+
}
|
|
322
|
+
/** The languages this root has already said it has no voice for, by primary language (ADR-0243 §3). */
|
|
323
|
+
const toldNoVoice = new Set();
|
|
324
|
+
/**
|
|
325
|
+
* NO VOICE FOR IT, NO WRONG VOICE (ADR-0243 §3): the part is not spoken — the text stays on the screen. Once per language, the
|
|
326
|
+
* interface's voice says so in its place, and `problems` gets one line naming the language and the fix.
|
|
327
|
+
*/
|
|
328
|
+
function noVoiceSegment(language) {
|
|
329
|
+
const english = languageName(language, 'en');
|
|
330
|
+
const code = language.split('-')[0].toLowerCase();
|
|
331
|
+
if (toldNoVoice.has(code))
|
|
332
|
+
return null;
|
|
333
|
+
toldNoVoice.add(code);
|
|
334
|
+
ctx.report?.(`narration: the device lacks a voice for ${english} (${code}), so a part a game gave in ${english} is not read aloud `
|
|
335
|
+
+ `and a child who listens does not hear it (the text stays on the screen) — install a voice for ${english} on the device, `
|
|
336
|
+
+ `in its text-to-speech settings`);
|
|
337
|
+
return frameSegment(t('sr.tts.noVoiceForLanguage', { language: languageName(language, bcp47()) }));
|
|
338
|
+
}
|
|
339
|
+
/** The voices the child chose, in order of precedence: the page's voice object, then the stored choice (ADR-0243 §2). */
|
|
340
|
+
const chosenVoices = () => [_ttsVoiceObj ? 'webspeech:' + _ttsVoiceObj.name : null, ctx.store.get(KEYS.ttsVoz, null)];
|
|
341
|
+
const browserSegment = (text, language, voice) => ({ browser: () => browserUtterance(text, language, browserVoice(voice.voice)) });
|
|
342
|
+
/** A part a Kokoro voice reads; while the model loads, a browser voice of the language reads it if the device has one. */
|
|
343
|
+
function neuralSegment(text, language, voice, chosen) {
|
|
344
|
+
const speaker = neural;
|
|
345
|
+
if (speaker)
|
|
346
|
+
return { neural: (finished) => { speaker.speak({ text, voice }, finished); } };
|
|
347
|
+
if (!ttsLoading)
|
|
348
|
+
loadKokoroEngine(voice);
|
|
349
|
+
const meanwhile = voiceOfLanguage(language, deviceVoices(), chosen);
|
|
350
|
+
return meanwhile ? browserSegment(text, language, meanwhile) : null;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* The utterances a narration waits on, held until their end is heard: an utterance nothing holds may be collected by the
|
|
354
|
+
* browser before it ends, and then its end is never heard — and a player's sonar would wait behind it forever (ADR-0234).
|
|
355
|
+
*/
|
|
356
|
+
const heldUntilOver = new Set();
|
|
357
|
+
/** Resolves when an utterance is over, however it ends — set before it is queued. */
|
|
358
|
+
const utteranceOver = (u) => new Promise((over) => {
|
|
359
|
+
heldUntilOver.add(u);
|
|
360
|
+
const done = () => { heldUntilOver.delete(u); over(); };
|
|
361
|
+
const measure = u.onend;
|
|
362
|
+
u.onend = function (e) { measure?.call(this, e); done(); };
|
|
363
|
+
u.onerror = done;
|
|
364
|
+
});
|
|
365
|
+
/**
|
|
366
|
+
* Queues the browser parts from `from` on at once; `over` resolves when the last is over, when a part follows them or the
|
|
367
|
+
* caller waits for the end (`untilOver`). A device that refuses to speak has nothing to wait for: the rest goes on.
|
|
368
|
+
*/
|
|
369
|
+
function queueBrowserParts(ss, segments, from, untilOver) {
|
|
370
|
+
const queued = [];
|
|
371
|
+
let i = from;
|
|
372
|
+
for (let s = segments[i]; s && 'browser' in s; s = segments[++i])
|
|
373
|
+
queued.push(s.browser());
|
|
374
|
+
const last = queued.at(-1);
|
|
375
|
+
const over = ss && last && (i < segments.length || untilOver) ? utteranceOver(last) : null;
|
|
376
|
+
try {
|
|
377
|
+
for (const u of queued)
|
|
378
|
+
ss?.speak(u);
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
return { next: i, over: null };
|
|
382
|
+
}
|
|
383
|
+
return { next: i, over };
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* THE PARTS, IN ORDER (ADR-0243 §1). Browser utterances go into the browser's own queue at once, each with its tag and voice; a
|
|
387
|
+
* neural part waits for what is before it to end, and what follows waits for it. A newer narration ends the rest. `untilOver`:
|
|
388
|
+
* resolves only when the last part is over, for a narration waiting its turn behind this one.
|
|
389
|
+
*/
|
|
390
|
+
async function speakInOrder(segments, turn, untilOver = false) {
|
|
391
|
+
const ss = ctx.speech.synth();
|
|
392
|
+
try {
|
|
393
|
+
ss?.cancel();
|
|
394
|
+
}
|
|
395
|
+
catch (e) { /* noop */ }
|
|
396
|
+
neural?.silence();
|
|
397
|
+
let i = 0;
|
|
398
|
+
while (i < segments.length && turn === narrationTurn) {
|
|
399
|
+
const first = segments[i];
|
|
400
|
+
if ('neural' in first) {
|
|
401
|
+
await new Promise((over) => { first.neural(over); });
|
|
402
|
+
i++;
|
|
403
|
+
continue;
|
|
152
404
|
}
|
|
153
|
-
|
|
405
|
+
const queuedParts = queueBrowserParts(ss, segments, i, untilOver);
|
|
406
|
+
i = queuedParts.next;
|
|
407
|
+
if (queuedParts.over)
|
|
408
|
+
await queuedParts.over;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/** The players' narrations waiting behind the one being spoken (ADR-0234), at most one per player, in the order they asked. */
|
|
412
|
+
const waiting = [];
|
|
413
|
+
/** The `narrationTurn` of the player's narration being spoken, and whose it is; any narration since has taken the turn. */
|
|
414
|
+
let turnSpeaking = -1, seatSpeaking = -1;
|
|
415
|
+
/**
|
|
416
|
+
* A PLAYER'S NARRATION TAKES TURNS (ADR-0234, errata 2026-09-26): behind ANOTHER player's still being spoken it waits — replacing
|
|
417
|
+
* that player's own waiting one, in its place — and is spoken when the line reaches it. With the SAME player's being spoken, or
|
|
418
|
+
* none, it starts at once and cuts what is spoken now, as every narration does; the others waiting keep waiting behind it. A
|
|
419
|
+
* narration that names no player takes the turn, so the next player's finds none being spoken and nobody waiting.
|
|
420
|
+
*/
|
|
421
|
+
function narrateInTurn(seat, segments) {
|
|
422
|
+
_narrateCount++;
|
|
423
|
+
const speakingNow = turnSpeaking === narrationTurn;
|
|
424
|
+
if (speakingNow && seat !== seatSpeaking) {
|
|
425
|
+
const own = waiting.find((w) => w.seat === seat);
|
|
426
|
+
if (own)
|
|
427
|
+
own.segments = segments;
|
|
428
|
+
else
|
|
429
|
+
waiting.push({ seat, segments });
|
|
430
|
+
return;
|
|
154
431
|
}
|
|
155
|
-
|
|
432
|
+
if (!speakingNow)
|
|
433
|
+
waiting.length = 0; // their turn was ended by a narration that named no player
|
|
434
|
+
void speakInTurn({ seat, segments }, ++narrationTurn);
|
|
156
435
|
}
|
|
157
|
-
function
|
|
436
|
+
async function speakInTurn(first, turn) {
|
|
437
|
+
turnSpeaking = turn;
|
|
438
|
+
for (let next = first; next; next = waiting.shift()) {
|
|
439
|
+
seatSpeaking = next.seat;
|
|
440
|
+
await speakInOrder(next.segments, turn, true);
|
|
441
|
+
if (turn !== narrationTurn)
|
|
442
|
+
return; // cut: the line is the newer narration's now
|
|
443
|
+
}
|
|
444
|
+
turnSpeaking = -1;
|
|
445
|
+
seatSpeaking = -1;
|
|
446
|
+
}
|
|
447
|
+
function narrate(text, how = {}) {
|
|
158
448
|
const cat = ctx.getAudioCat();
|
|
159
|
-
if (!ctx.getSoundOn() || !cat || !cat.tts || !cat.tts.on
|
|
449
|
+
if (!ctx.getSoundOn() || !cat || !cat.tts || !cat.tts.on)
|
|
450
|
+
return;
|
|
451
|
+
if (typeof text !== 'string') {
|
|
452
|
+
narrateParts(text, how);
|
|
160
453
|
return;
|
|
454
|
+
}
|
|
455
|
+
if (!text)
|
|
456
|
+
return;
|
|
457
|
+
if (availableVoices().length === 0)
|
|
458
|
+
return; // no voice speaks this language: narration is locked (ADR-0185 §4)
|
|
459
|
+
if (how.seat !== undefined) {
|
|
460
|
+
const frame = frameSegment(text);
|
|
461
|
+
if (frame)
|
|
462
|
+
narrateInTurn(how.seat, [frame]);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
narrationTurn++;
|
|
161
466
|
_narrateCount++;
|
|
162
467
|
ttsSpeak(text);
|
|
163
468
|
}
|
|
469
|
+
function narrateParts(parts, how) {
|
|
470
|
+
const segments = parts.filter((p) => p.text)
|
|
471
|
+
.map((p) => (p.language ? contentSegment(p.text, p.language) : frameSegment(p.text)))
|
|
472
|
+
.filter((s) => s !== null);
|
|
473
|
+
if (!segments.length)
|
|
474
|
+
return;
|
|
475
|
+
if (how.seat !== undefined) {
|
|
476
|
+
narrateInTurn(how.seat, segments);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
_narrateCount++;
|
|
480
|
+
void speakInOrder(segments, ++narrationTurn);
|
|
481
|
+
}
|
|
164
482
|
return {
|
|
165
483
|
narrate, ttsSpeak, loadTTS, speakWebSpeech,
|
|
166
|
-
getEngineSel:
|
|
484
|
+
getEngineSel: engineSel, setEngineSel: (v) => { ttsEngineSelExplicito = v; },
|
|
167
485
|
getEngine: () => ttsEngine, getVoiceObj: () => _ttsVoiceObj, setVoiceObj: (v) => { _ttsVoiceObj = v; },
|
|
168
486
|
get loading() { return ttsLoading; }, get failed() { return ttsFailed; }, get narrateCount() { return _narrateCount; },
|
|
169
|
-
get
|
|
487
|
+
get neuralAvailable() { return !!ctx.neuralVoice; },
|
|
488
|
+
get kokoroDevice() { return kokoroBackend; },
|
|
489
|
+
voices: availableVoices, currentVoice: voiceInUse, setVoice: chooseVoice,
|
|
170
490
|
};
|
|
171
491
|
}
|