@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
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/heavy.ts — THE HEAVY THINGS, DOWNLOADED ON FIRST LOAD (ADR-0110, ADR-0116, ADR-0119).
|
|
3
|
+
//
|
|
4
|
+
// ========================= WHAT THIS IS =========================
|
|
5
|
+
// Pillar 8 says "PWA ONLINE on the first day, then OFFLINE-FIRST", and ADR-0116 removed the contradiction that blocked
|
|
6
|
+
// this: installing is already a network act, so fetching at install violates nothing. What was missing was someone
|
|
7
|
+
// fetching.
|
|
8
|
+
//
|
|
9
|
+
// ⚠️ AND IT DOES NOT BLOCK THE GAME. The child plays while the heavy files come down; what must not happen is them
|
|
10
|
+
// reaching the second day, offline, and finding the voice was never fetched.
|
|
11
|
+
//
|
|
12
|
+
// ========================= THE THREE RULES THE SHAPE IMPOSES =========================
|
|
13
|
+
// 1. **ONE AT A TIME.** Four 60 MB downloads in parallel on a school link fight for the same bandwidth and none
|
|
14
|
+
// finishes first — and the game, which needs the network for its own images, waits behind them.
|
|
15
|
+
// 2. **NEVER THROWS.** A network failure is REPORTED and the list goes on. A `throw` here would bring down a game's
|
|
16
|
+
// start over a resource it may not even use today.
|
|
17
|
+
// 3. **IDEMPOTENT.** What is already in Cache Storage is not fetched again — which is what makes this safe to call on
|
|
18
|
+
// every start instead of only "on the first", which nobody can detect honestly.
|
|
19
|
+
import { CACHE_HEAVY, HEAVY_FILES, DELIVERY_LISTS, readingLanguageOf, commandsLanguageOf, } from './heavy-catalogue.js';
|
|
20
|
+
export { CACHE_HEAVY, HEAVY_FILES };
|
|
21
|
+
/**
|
|
22
|
+
* Who waits behind whom inside one language's position (`heavyAtBoot`): 0 the ways in (the command models and runtime, the
|
|
23
|
+
* camera's reader), 1 the reading and the graph runtime it opens its models with, 2 the rest — the neural voice, which the
|
|
24
|
+
* browser's voice covers until it lands.
|
|
25
|
+
*/
|
|
26
|
+
function waitsBehind(id) {
|
|
27
|
+
if (id.startsWith('commands:') || id.startsWith('visao:'))
|
|
28
|
+
return 0;
|
|
29
|
+
if (id.startsWith('reading:') || id.startsWith('voz:runtime:onnx'))
|
|
30
|
+
return 1;
|
|
31
|
+
return 2;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* WHAT A GAME'S START FETCHES (ADR-0216 §3): the catalogue, less what this game did not ask for.
|
|
35
|
+
*
|
|
36
|
+
* · The neural voice — its model, its voices AND the runtime that speaks them: without that answer they are 372 MB taken from a
|
|
37
|
+
* school's link and a child's device for nothing.
|
|
38
|
+
* · The reading models, one per language (850 MiB for the three): `reading` is a language or a LIST of them, like `commands`, and
|
|
39
|
+
* the root asks for every language the page can switch to, the child's first (ADR-0225 erratum, the Dev: «Negativo, baixar os
|
|
40
|
+
* três. Toda criança vai experimentar as três línguas imediatamente.»). A language the delivery's `--reading` list left out is a
|
|
41
|
+
* quiet 404 here and, when she reads in it, a line of `problems` and a sentence to her. A game that never listens gets none.
|
|
42
|
+
* · 🔴 THE GRAPH RUNTIME IS NOT THE VOICE'S, and its `voz:` name said otherwise. `platform/onnx-runtime` runs Kokoro AND the
|
|
43
|
+
* reading models, so a game that only LISTENS needs it: without this line its delivery carried a 378 MiB model and nothing
|
|
44
|
+
* able to open it, and the first `listen()` asked for a file the build never wrote. Measured on 2026-09-21, building the
|
|
45
|
+
* very delivery this exists to serve. The PHONEMIZER (`voz:runtime:fonemas`, 18.7 MiB) stays the voice's — nothing else
|
|
46
|
+
* turns letters into sounds.
|
|
47
|
+
* · The command models (issue #184): one per language too, 108 MiB for the three, and the runtime that loads them. Nothing in
|
|
48
|
+
* the game decides this — speaking instead of pressing is a way INTO the controller, and a cartridge does not get to close
|
|
49
|
+
* one (ADR-0111). `commands` is a language or a LIST of them, and the models come in the list's order: the root asks for
|
|
50
|
+
* every language the page can switch to, the child's first, because a language changed mid-game must find its model already
|
|
51
|
+
* kept (ADR-0225 erratum, the Dev: «A entrega leva as três línguas.»). The delivery carries the three unless its
|
|
52
|
+
* `--commands` list narrows it; a language it did not carry is a quiet 404 here and a line of `problems` when she speaks.
|
|
53
|
+
* · The Libras player (ADR-0234): the delivery's list of its avatar, clips, glosses and three.js chunk (34.9 MB in the engine's
|
|
54
|
+
* delivery), only with `libras`: the root asks for it while deaf mode is on, so a device whose child never asks for signing
|
|
55
|
+
* never downloads it. No game declares it either — deaf mode is the person's (ADR-0111) — and a delivery built without
|
|
56
|
+
* `--libras` simply has none, the same quiet 404 as a missing command model. No catalogue file is the player's: it is all the
|
|
57
|
+
* delivery's own, kept by what the delivery carries.
|
|
58
|
+
* The ids are the catalogue's files, then the delivery lists (`DELIVERY_LISTS`), by one rule.
|
|
59
|
+
*
|
|
60
|
+
* 🔴 THE ORDER IS WHO WAITS, because the download is one file at a time (rule 1): a file of a language takes that language's
|
|
61
|
+
* position in the list it was asked by, a file of no language (the runtimes, vision) takes the first, and the sort is stable. So
|
|
62
|
+
* everything of the child's language comes first, then each other language whole — her command model never waits behind 470 MiB
|
|
63
|
+
* of another language's reading.
|
|
64
|
+
* 🔴 AND WITHIN A POSITION, THE WAYS IN COME FIRST (`waitsBehind`): the command models and the camera's reader are how a child
|
|
65
|
+
* who does not press reaches the game at all (ADR-0111), and they have nothing to fall back on; the reading is the game's own
|
|
66
|
+
* listening, with nothing either; the neural voice comes last, because the browser's voice speaks meanwhile (ADR-0200). 📏 In
|
|
67
|
+
* the catalogue's order a 👄 turned on in a fresh install waited behind 336 MiB of neural voice and 378 MiB of reading for a
|
|
68
|
+
* 34 MiB model — seven seconds from a local disk, minutes on a school's link (measured on 2026-09-27).
|
|
69
|
+
*/
|
|
70
|
+
export function heavyAtBoot(declared) {
|
|
71
|
+
const reading = languagesAsked(declared.reading);
|
|
72
|
+
const commands = languagesAsked(declared.commands);
|
|
73
|
+
const asked = (id) => {
|
|
74
|
+
const language = readingLanguageOf(id);
|
|
75
|
+
if (language)
|
|
76
|
+
return reading.includes(language);
|
|
77
|
+
// 📌 THE COMMAND MODELS ARE A TRANSPORT'S, not a game's: no cartridge declares them, because a child who speaks instead of
|
|
78
|
+
// pressing is reaching the controller, and a cartridge does not get to deny her a way in (ADR-0111). The LANGUAGES are
|
|
79
|
+
// still asked, and the runtime comes with whichever one is.
|
|
80
|
+
const commanded = commandsLanguageOf(id);
|
|
81
|
+
if (commanded)
|
|
82
|
+
return commands.includes(commanded);
|
|
83
|
+
if (id.startsWith('commands:runtime'))
|
|
84
|
+
return commands.length > 0;
|
|
85
|
+
if (id.startsWith('libras:'))
|
|
86
|
+
return !!declared.libras;
|
|
87
|
+
if (id.startsWith('voz:runtime:onnx'))
|
|
88
|
+
return declared.kokoro || reading.length > 0;
|
|
89
|
+
return declared.kokoro || !(id.startsWith('voz:kokoro:') || id.startsWith('voz:runtime:'));
|
|
90
|
+
};
|
|
91
|
+
const rank = (id) => {
|
|
92
|
+
const read = readingLanguageOf(id);
|
|
93
|
+
if (read)
|
|
94
|
+
return reading.indexOf(read);
|
|
95
|
+
const spoken = commandsLanguageOf(id);
|
|
96
|
+
return spoken ? commands.indexOf(spoken) : 0;
|
|
97
|
+
};
|
|
98
|
+
return [
|
|
99
|
+
// `Array.prototype.sort` is stable (ES2019): within a position and a kind, the catalogue's order — a runtime before its model
|
|
100
|
+
...HEAVY_FILES.map((p) => p.id).filter(asked).sort((a, b) => rank(a) - rank(b) || waitsBehind(a) - waitsBehind(b)),
|
|
101
|
+
...DELIVERY_LISTS.map((l) => l.id).filter(asked),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
/** The base languages asked — one tag or a list of them — without repeats, in the order asked (`es-MX` asks for `es`). */
|
|
105
|
+
function languagesAsked(asked) {
|
|
106
|
+
const tags = asked == null ? [] : typeof asked === 'string' ? [asked] : asked;
|
|
107
|
+
return [...new Set(tags.filter(Boolean).map((tag) => tag.split('-')[0].toLowerCase()))];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* WHERE THE DELIVERY SERVES A HEAVY FILE (ADR-0177, issue #173): `heavy/<host><path>` beside the page. The child's device
|
|
111
|
+
* reads it from the game's own origin; the upstream address is only where the build fetched it from.
|
|
112
|
+
* 📌 The upstream address stays the CACHE KEY: it is what the voice and vision libraries ask for, and the service worker answers
|
|
113
|
+
* them from the checked cache without a network request.
|
|
114
|
+
*
|
|
115
|
+
* 🔴 THE FOLDER WAS CALLED «pesados» UNTIL 2026-09-21 (ADR-0219). 📏 What that costs a school was measured before it changed:
|
|
116
|
+
* the entries are kept under the UPSTREAM address, so nothing is re-downloaded — what breaks is a delivery built by the old
|
|
117
|
+
* `bin` under a new engine, which asks for `heavy/…` and gets a 404 until the delivery is built again.
|
|
118
|
+
*/
|
|
119
|
+
export function deliveryPath(url) {
|
|
120
|
+
const u = new URL(url);
|
|
121
|
+
return `heavy/${u.host}${u.pathname}`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The inverse, for the service worker: a request for `…/heavy/<host><path>` is answered from the entry kept under
|
|
125
|
+
* `https://<host><path>`; any other address has no key (`null`). It is the route's `cacheKeyWillBeUsed` itself, so it also
|
|
126
|
+
* takes Workbox's `{ request }`. Self-contained on purpose — the PWA plugin copies this function's SOURCE into `sw.js`, where
|
|
127
|
+
* nothing else from this module exists.
|
|
128
|
+
*
|
|
129
|
+
* ⚠️ THE OFFSET COMES FROM THE FOLDER'S OWN LENGTH, and it is written this way because of what a blind rename would have done
|
|
130
|
+
* here: the old code advanced by 9, the length of the old folder's name with its slashes, and `/heavy/` is 7 — a text
|
|
131
|
+
* replacement of the name alone would
|
|
132
|
+
* have left every cache key with two characters of the host eaten, and the only symptom would be a cache that never hits.
|
|
133
|
+
*/
|
|
134
|
+
export function deliveryCacheKey(urlOrRequest) {
|
|
135
|
+
const folder = '/heavy/';
|
|
136
|
+
const urlPath = new URL(typeof urlOrRequest === 'string' ? urlOrRequest : urlOrRequest.request.url).pathname;
|
|
137
|
+
const i = urlPath.indexOf(folder);
|
|
138
|
+
return i < 0 ? null : 'https://' + urlPath.slice(i + folder.length);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* DOWNLOADS WHAT IS MISSING, ONE AT A TIME, AND RETURNS WHAT HAPPENED TO EACH.
|
|
142
|
+
*
|
|
143
|
+
* ⚠️ ENTRIES WITHOUT A `url` ARE NOT SKIPPED IN SILENCE — they return `sem-fonte`. It is the difference between "this
|
|
144
|
+
* subsystem has nowhere to come from yet" and "this subsystem is handled", exactly the distinction ADR-0119 measured as
|
|
145
|
+
* missing: the engine PROMISED four things and delivered one, with nothing saying so.
|
|
146
|
+
*
|
|
147
|
+
* The files handed in `also` come first (the declared library fonts), then the catalogue's, then the delivery's lists
|
|
148
|
+
* (`DELIVERY_LISTS`), each list ONE report for all its files.
|
|
149
|
+
*/
|
|
150
|
+
export async function downloadHeavy(options) {
|
|
151
|
+
const targets = [...(options.also ?? []), ...(options.only
|
|
152
|
+
? [...new Set(options.only)].flatMap((id) => HEAVY_FILES.filter((p) => p.id === id))
|
|
153
|
+
: HEAVY_FILES)];
|
|
154
|
+
const lists = DELIVERY_LISTS.filter((l) => !options.only || options.only.includes(l.id));
|
|
155
|
+
const out = [];
|
|
156
|
+
const record = (r) => { out.push(r); options.onProgress?.(r); };
|
|
157
|
+
const { cacheStorage, fetch: fetchFile, digest, base } = options;
|
|
158
|
+
if (!cacheStorage || !fetchFile) {
|
|
159
|
+
for (const p of [...targets, ...lists])
|
|
160
|
+
record({ id: p.id, outcome: 'falhou', error: 'sem Cache Storage ou sem fetch' });
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
const tools = { cache: await cacheStorage.open(CACHE_HEAVY), fetchFile, digest, base };
|
|
164
|
+
for (const p of targets)
|
|
165
|
+
record(await fetchOne(p, tools));
|
|
166
|
+
for (const l of lists)
|
|
167
|
+
record(await keepListed(l, tools));
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
/** One file's fate: already kept, fetched from the delivery and checked, or refused — always with the reason. */
|
|
171
|
+
async function fetchOne(p, t) {
|
|
172
|
+
if (!p.url)
|
|
173
|
+
return { id: p.id, outcome: 'sem-fonte', error: p.whyNoSource };
|
|
174
|
+
try {
|
|
175
|
+
if (await t.cache.match(p.url))
|
|
176
|
+
return { id: p.id, outcome: 'ja-tinha' };
|
|
177
|
+
// from the delivery's own origin, never from the upstream host (ADR-0177)
|
|
178
|
+
const resp = await t.fetchFile(new URL(deliveryPath(p.url), t.base).href);
|
|
179
|
+
if (!resp.ok)
|
|
180
|
+
return { id: p.id, outcome: 'falhou', error: `HTTP ${resp.status}` };
|
|
181
|
+
return await keepIfChecked(p, p.url, resp, t);
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
return { id: p.id, outcome: 'falhou', error: e instanceof Error ? e.message : String(e) };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* CHECKED BEFORE KEPT (issue #168; STRIDE client pass). What is kept runs in the child's page and is served offline from then
|
|
189
|
+
* on, so a body whose SHA-256 is not the measured one never enters the cache. It is kept under the UPSTREAM address, which is
|
|
190
|
+
* what the voice and vision libraries ask for.
|
|
191
|
+
*/
|
|
192
|
+
async function keepIfChecked(p, url, resp, t) {
|
|
193
|
+
const refused = (reason) => ({ id: p.id, outcome: 'falhou', error: reason });
|
|
194
|
+
if (!p.sha256)
|
|
195
|
+
return refused('this entry pins its sha256 nowhere: there is nothing to check it against');
|
|
196
|
+
if (!t.digest)
|
|
197
|
+
return refused('this host cannot compute a sha256 (crypto.subtle needs a secure context)');
|
|
198
|
+
const body = await resp.arrayBuffer();
|
|
199
|
+
const got = await t.digest(body);
|
|
200
|
+
if (got !== p.sha256)
|
|
201
|
+
return refused(`sha256 mismatch: expected ${p.sha256}, got ${got} — not kept`);
|
|
202
|
+
await t.cache.put(url, new Response(body, { status: resp.status, statusText: resp.statusText, headers: resp.headers }));
|
|
203
|
+
return { id: p.id, outcome: 'baixado', bytes: p.bytes };
|
|
204
|
+
}
|
|
205
|
+
/** The header a file kept from a delivery's list carries its listed sha256 in: how a later start knows it is kept, and current. */
|
|
206
|
+
const LISTED_SHA256 = 'x-inclusionist-sha256';
|
|
207
|
+
/**
|
|
208
|
+
* The files a delivery's list names, or a THROW saying why the list is refused whole: a list with no `files`, an entry without a
|
|
209
|
+
* path or a sha256, or one whose address falls outside the list's folders (a `..`, another origin, a query) — the list may only
|
|
210
|
+
* put the player's own files in the checked cache, never the game's page nor anything a route outside those folders serves.
|
|
211
|
+
*/
|
|
212
|
+
function listedFiles(data, l, base) {
|
|
213
|
+
const files = data?.files;
|
|
214
|
+
if (!Array.isArray(files))
|
|
215
|
+
throw new Error('its `files` is missing');
|
|
216
|
+
const folders = l.folders.map((folder) => new URL(folder, base).href);
|
|
217
|
+
return files.map((f) => {
|
|
218
|
+
const { path, sha256 } = (f ?? {});
|
|
219
|
+
if (typeof path !== 'string' || typeof sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(sha256)) {
|
|
220
|
+
throw new Error(`a listed file is malformed: ${JSON.stringify(f)}`);
|
|
221
|
+
}
|
|
222
|
+
const at = new URL(path, base);
|
|
223
|
+
const url = at.href;
|
|
224
|
+
if (at.search || at.hash || !folders.some((folder) => url.startsWith(folder) && url.length > folder.length)) {
|
|
225
|
+
throw new Error(`it names ${path}, outside ${l.folders.join(' and ')}`);
|
|
226
|
+
}
|
|
227
|
+
return { path, url, sha256 };
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* A DELIVERY'S LIST, KEPT FILE BY FILE (ADR-0234, pillar 8): the list is read from the page's origin, and each file it names is
|
|
232
|
+
* fetched from there, CHECKED against its listed sha256, and kept under its own address, where the service worker answers it
|
|
233
|
+
* offline. A file already kept with that sha256 is not fetched again; a body that is not the listed one is never kept, and the
|
|
234
|
+
* report names it. One report for the list: `baixado` with the bytes fetched, `ja-tinha`, or `falhou` with the files refused.
|
|
235
|
+
*/
|
|
236
|
+
async function keepListed(l, t) {
|
|
237
|
+
const failed = (error) => ({ id: l.id, outcome: 'falhou', error });
|
|
238
|
+
const digest = t.digest;
|
|
239
|
+
if (!digest)
|
|
240
|
+
return failed('this host cannot compute a sha256 (crypto.subtle needs a secure context)');
|
|
241
|
+
const files = await readList(l, t);
|
|
242
|
+
if (typeof files === 'string')
|
|
243
|
+
return failed(files);
|
|
244
|
+
const refused = [];
|
|
245
|
+
let bytes = 0;
|
|
246
|
+
let fetched = 0;
|
|
247
|
+
for (const f of files) {
|
|
248
|
+
const kept = await keepListedFile(f, t, digest);
|
|
249
|
+
if (typeof kept === 'string')
|
|
250
|
+
refused.push(`${f.path}: ${kept}`);
|
|
251
|
+
else if (kept !== null) {
|
|
252
|
+
bytes += kept;
|
|
253
|
+
fetched += 1;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (!refused.length)
|
|
257
|
+
return fetched ? { id: l.id, outcome: 'baixado', bytes } : { id: l.id, outcome: 'ja-tinha' };
|
|
258
|
+
const more = refused.length > 5 ? `; and ${refused.length - 5} more` : '';
|
|
259
|
+
return failed(`${refused.length} of ${files.length} files of ${l.path} not kept: ${refused.slice(0, 5).join('; ')}${more}`);
|
|
260
|
+
}
|
|
261
|
+
/** The list's files, or why it is refused whole (not there, not JSON, or naming what it may not). */
|
|
262
|
+
async function readList(l, t) {
|
|
263
|
+
try {
|
|
264
|
+
// never an old copy of the list: it is what says which files are current
|
|
265
|
+
const resp = await t.fetchFile(new URL(l.path, t.base).href, { cache: 'no-store' });
|
|
266
|
+
if (!resp.ok)
|
|
267
|
+
return `HTTP ${resp.status} — ${l.path}`;
|
|
268
|
+
return listedFiles(await resp.json(), l, t.base);
|
|
269
|
+
}
|
|
270
|
+
catch (e) {
|
|
271
|
+
return `the list ${l.path} is refused: ${e instanceof Error ? e.message : String(e)}`;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** One listed file: `null` when it is already kept with its listed hash, the bytes kept, or why it was not kept. */
|
|
275
|
+
async function keepListedFile(f, t, digest) {
|
|
276
|
+
try {
|
|
277
|
+
if ((await t.cache.match(f.url))?.headers.get(LISTED_SHA256) === f.sha256)
|
|
278
|
+
return null;
|
|
279
|
+
/*
|
|
280
|
+
* 📌 THE LISTED HASH GOES IN THE QUERY: the service worker answers these folders from this cache by their address, so a file
|
|
281
|
+
* the list CHANGED would be answered with the copy it replaces. Another address reaches the delivery, and the old copy stays
|
|
282
|
+
* kept until the new one is checked — offline a child keeps the player she had.
|
|
283
|
+
*/
|
|
284
|
+
const resp = await t.fetchFile(`${f.url}?sha256=${f.sha256}`);
|
|
285
|
+
if (!resp.ok)
|
|
286
|
+
return `HTTP ${resp.status}`;
|
|
287
|
+
const body = await resp.arrayBuffer();
|
|
288
|
+
const got = await digest(body);
|
|
289
|
+
if (got !== f.sha256)
|
|
290
|
+
return `sha256 mismatch: expected ${f.sha256}, got ${got} — not kept`;
|
|
291
|
+
const headers = new Headers(resp.headers);
|
|
292
|
+
headers.set(LISTED_SHA256, f.sha256);
|
|
293
|
+
await t.cache.put(f.url, new Response(body, { status: resp.status, statusText: resp.statusText, headers }));
|
|
294
|
+
return body.byteLength;
|
|
295
|
+
}
|
|
296
|
+
catch (e) {
|
|
297
|
+
return e instanceof Error ? e.message : String(e);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* THE SHA-256 OF A BODY AS LOWERCASE HEX, by the `crypto.subtle` it is handed (issue #168) — or `null` when there is none,
|
|
302
|
+
* which is a host outside a secure context: the answer `HeavyOptions.digest` reads as «cannot hash, so keep nothing».
|
|
303
|
+
*/
|
|
304
|
+
export function sha256With(subtle) {
|
|
305
|
+
if (!subtle)
|
|
306
|
+
return null;
|
|
307
|
+
return async (payload) => {
|
|
308
|
+
const bytes = new Uint8Array(await subtle.digest('SHA-256', payload));
|
|
309
|
+
return [...bytes].map((x) => x.toString(16).padStart(2, '0')).join('');
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* WHETHER A CATALOGUE FILE IS IN THE CHECKED CACHE, by its upstream address — what `platform/vision` and
|
|
314
|
+
* `platform/vosk-runtime` ask before loading anything. The host's `caches` is handed in; `undefined` (an insecure context)
|
|
315
|
+
* holds nothing, so every file is missing and the loader names them.
|
|
316
|
+
*/
|
|
317
|
+
export function checkedCacheHas(cacheStorage) {
|
|
318
|
+
return async (url) => !!cacheStorage && !!(await (await cacheStorage.open(CACHE_HEAVY)).match(url));
|
|
319
|
+
}
|
|
320
|
+
/** The size of what is still missing, in bytes — so a notice can say how much is left before it starts. */
|
|
321
|
+
export function bytesLeftToDownload(soFar) {
|
|
322
|
+
const feitos = new Set(soFar.filter((r) => r.outcome === 'ja-tinha' || r.outcome === 'baixado').map((r) => r.id));
|
|
323
|
+
return HEAVY_FILES.filter((p) => p.url && !feitos.has(p.id)).reduce((s, p) => s + (p.bytes ?? 0), 0);
|
|
324
|
+
}
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
/**
|
|
3
|
+
* What the caller provides. Nothing here knows the neural engine, Web Audio or the browser. `Request` is what one utterance
|
|
4
|
+
* asks for: a text by default, or a text with the voice that reads it (ADR-0243 §2).
|
|
5
|
+
*/
|
|
6
|
+
export interface SpeechEngine<Audio, Fonte, Request = string> {
|
|
7
|
+
/** Request → audio. ASYNCHRONOUS on purpose: it is where neural synthesis spends its time. */
|
|
8
|
+
synthesize(request: Request): Promise<Audio>;
|
|
9
|
+
/** Starts playing and returns the source, so it can be stopped. `null` = it could not play right now. */
|
|
10
|
+
play(audio: Audio, onEnded: () => void): Fonte | null;
|
|
11
|
+
/** Silences the source. Called with what `play` returned, and never with `null`. */
|
|
12
|
+
stop(playing: Fonte): void;
|
|
10
13
|
}
|
|
11
|
-
export interface
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
export interface InterruptibleSpeech<Request = string> {
|
|
15
|
+
/**
|
|
16
|
+
* Speaks `request`, SILENCING at once whatever is speaking. It does not queue: the latest request is the one that counts.
|
|
17
|
+
* `finished` is called ONCE, when this utterance is over for any reason — it ended, it was silenced or overtaken, or it could
|
|
18
|
+
* not be synthesised or played — so a caller speaking parts in order (ADR-0243 §1) knows when to go on, and never waits forever.
|
|
19
|
+
*/
|
|
20
|
+
speak(request: Request, finished?: () => void): void;
|
|
21
|
+
/** Silences and forgets. Used when a menu closes or narration is switched off. */
|
|
22
|
+
silence(): void;
|
|
23
|
+
/** Is anything playing? For tests and debugging only — the policy does not depend on it. */
|
|
24
|
+
speaking(): boolean;
|
|
18
25
|
}
|
|
19
|
-
export declare function
|
|
26
|
+
export declare function createInterruptibleSpeech<Audio, Fonte, Request = string>(engine: SpeechEngine<Audio, Fonte, Request>): InterruptibleSpeech<Request>;
|
|
@@ -1,79 +1,102 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
// platform/interruptible-speech —
|
|
2
|
+
// platform/interruptible-speech — SPEAK THE LATEST REQUEST, AND SILENCE THE PREVIOUS ONE AT ONCE (ADR-0044, item 2).
|
|
3
3
|
//
|
|
4
|
-
// =========================
|
|
5
|
-
//
|
|
4
|
+
// ========================= THE DEFECT THIS REPLACES =========================
|
|
5
|
+
// The neural engine used to speak like this:
|
|
6
6
|
//
|
|
7
|
-
// speak: (text) => { if (busy) next = text; else speakNow(text); } //
|
|
7
|
+
// speak: (text) => { if (busy) next = text; else speakNow(text); } // a queue of 1
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// escuta estava vários itens atrás do foco.
|
|
9
|
+
// A QUEUE, of size one. Sweeping five menu items, the child heard the FIRST one whole and then the LAST — the three in
|
|
10
|
+
// between vanished in silence, because each new request overwrote `next`. It is slow AND it loses information, and both
|
|
11
|
+
// hurt in the same place: a person who cannot see navigates BY EAR, and the ear was several items behind the focus.
|
|
13
12
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// XAG 106 says what to do, and why: if the focused item's narration is still being read when the focus moves, the
|
|
14
|
+
// original element's narration stops IMMEDIATELY and the new element's begins.
|
|
16
15
|
//
|
|
17
|
-
// =========================
|
|
16
|
+
// ========================= THE TWO THINGS THIS GUARANTEES =========================
|
|
18
17
|
//
|
|
19
|
-
// 1.
|
|
20
|
-
//
|
|
21
|
-
//
|
|
18
|
+
// 1. SILENCE BEFORE SYNTHESISING, not after. Neural synthesis takes time; stopping only once the new audio is ready would
|
|
19
|
+
// leave the old voice speaking during the wait — the wrong item, with conviction. Silence is the right answer: the
|
|
20
|
+
// child knows they moved, and the silence confirms it.
|
|
22
21
|
//
|
|
23
|
-
// 2.
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
22
|
+
// 2. A GENERATION, which is what prevents the race. `synthesize` is asynchronous, and two requests can finish out of
|
|
23
|
+
// order: the second is ready first and starts playing, then the first arrives and runs it over — the child would
|
|
24
|
+
// hear the item they ALREADY passed, on top of the current one. Each request carries its number; whatever comes back
|
|
25
|
+
// with an old number is dropped, at each of the two `await`s.
|
|
27
26
|
//
|
|
28
|
-
// =========================
|
|
29
|
-
//
|
|
30
|
-
// runtime
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
export function
|
|
34
|
-
let
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
// ========================= WHY A MODULE, AND NOT A LINE IN `tts` =========================
|
|
28
|
+
// Because this way it can be PROVEN. The neural block lives inside a dynamic `import()` that only resolves with the
|
|
29
|
+
// runtime present; testing it there would need the real engine. Here the policy is pure — it receives "how to
|
|
30
|
+
// synthesise", "how to play" and "how to stop" — and the node test exercises the race and the interruption with fakes,
|
|
31
|
+
// in milliseconds.
|
|
32
|
+
export function createInterruptibleSpeech(engine) {
|
|
33
|
+
let nowPlaying = null;
|
|
34
|
+
let nowFinished = null;
|
|
35
|
+
let currentTurn = 0;
|
|
36
|
+
/** Silences whatever is playing. The `try` exists because stopping an already finished source throws in some engines. */
|
|
37
|
+
function stopPlayback() {
|
|
38
|
+
if (nowPlaying !== null) {
|
|
39
39
|
try {
|
|
40
|
-
|
|
40
|
+
engine.stop(nowPlaying);
|
|
41
41
|
}
|
|
42
|
-
catch (e) { /*
|
|
43
|
-
|
|
42
|
+
catch (e) { /* the source already ended — stopping again is no error */ }
|
|
43
|
+
nowPlaying = null;
|
|
44
44
|
}
|
|
45
|
+
const finished = nowFinished;
|
|
46
|
+
nowFinished = null;
|
|
47
|
+
finished?.();
|
|
45
48
|
}
|
|
46
|
-
async function
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
if (!
|
|
49
|
+
async function speakNow(request, finished) {
|
|
50
|
+
const myTurn = ++currentTurn; // claims the turn BEFORE any wait
|
|
51
|
+
stopPlayback(); // guarantee 1: immediate silence, not at the end of synthesis
|
|
52
|
+
if (!request) {
|
|
53
|
+
finished();
|
|
50
54
|
return;
|
|
55
|
+
}
|
|
51
56
|
try {
|
|
52
|
-
const audio = await
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
const audio = await engine.synthesize(request);
|
|
58
|
+
if (myTurn !== currentTurn) {
|
|
59
|
+
finished();
|
|
60
|
+
return;
|
|
61
|
+
} // guarantee 2: it arrived late — another request already took over
|
|
62
|
+
stopPlayback(); // again: something may have started playing during the wait
|
|
63
|
+
const playing = engine.play(audio, () => {
|
|
64
|
+
if (nowPlaying === playing) {
|
|
65
|
+
nowPlaying = null;
|
|
66
|
+
nowFinished = null;
|
|
67
|
+
}
|
|
68
|
+
finished();
|
|
69
|
+
});
|
|
70
|
+
if (playing === null) {
|
|
71
|
+
finished();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (myTurn !== currentTurn) {
|
|
75
|
+
try {
|
|
76
|
+
engine.stop(playing);
|
|
64
77
|
}
|
|
78
|
+
catch (e) { /* noop */ }
|
|
79
|
+
finished();
|
|
65
80
|
return;
|
|
66
81
|
}
|
|
67
|
-
|
|
82
|
+
nowPlaying = playing;
|
|
83
|
+
nowFinished = finished;
|
|
68
84
|
}
|
|
69
85
|
catch (e) {
|
|
70
|
-
//
|
|
71
|
-
//
|
|
86
|
+
// Synthesis failed for THIS text. No reason to bring the whole narration down: the next request tries again, and one
|
|
87
|
+
// silent item is better than a dead engine.
|
|
88
|
+
finished();
|
|
72
89
|
}
|
|
73
90
|
}
|
|
91
|
+
/** Calls `finished` at most once, whichever of the ends above comes first. */
|
|
92
|
+
const once = (finished) => {
|
|
93
|
+
let called = false;
|
|
94
|
+
return () => { if (called)
|
|
95
|
+
return; called = true; finished?.(); };
|
|
96
|
+
};
|
|
74
97
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
98
|
+
speak: (request, finished) => { void speakNow(request, once(finished)); },
|
|
99
|
+
silence: () => { currentTurn++; stopPlayback(); }, // the `++` invalidates whatever is being synthesised now
|
|
100
|
+
speaking: () => nowPlaying !== null,
|
|
78
101
|
};
|
|
79
102
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { KokoroModule } from './kokoro.js';
|
|
3
|
+
import type { OnnxRuntime } from './onnx-runtime.js';
|
|
4
|
+
/** The file system espeak-ng's Emscripten module exposes — only what this uses. */
|
|
5
|
+
export interface EspeakFileSystem {
|
|
6
|
+
readonly writeFile: (path: string, text: string) => void;
|
|
7
|
+
readonly readFile: (path: string, options: {
|
|
8
|
+
encoding: 'utf8';
|
|
9
|
+
}) => string;
|
|
10
|
+
}
|
|
11
|
+
/** espeak-ng's module factory: one program run per call, with its arguments and the hooks run before `main`. */
|
|
12
|
+
export type EspeakFactory = (options: {
|
|
13
|
+
readonly arguments: readonly string[];
|
|
14
|
+
readonly preRun: readonly ((m: {
|
|
15
|
+
FS: EspeakFileSystem;
|
|
16
|
+
}) => void)[];
|
|
17
|
+
}) => Promise<{
|
|
18
|
+
FS: EspeakFileSystem;
|
|
19
|
+
}>;
|
|
20
|
+
export interface KokoroPortDeps {
|
|
21
|
+
readonly espeak: EspeakFactory;
|
|
22
|
+
readonly ort: OnnxRuntime;
|
|
23
|
+
readonly fetch: (url: string) => Promise<Response>;
|
|
24
|
+
/** The page's address `heavy/` is resolved against. */
|
|
25
|
+
readonly base: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function createKokoroPort(d: KokoroPortDeps): KokoroModule;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { KOKORO_MODEL_URL, URL_DO_TOKENIZADOR_KOKORO, kokoroVoiceUrl } from './kokoro.js';
|
|
3
|
+
import { deliveryPath } from './heavy.js';
|
|
4
|
+
const INPUT = 'entrada.txt';
|
|
5
|
+
const OUTPUT = 'fonemas.txt';
|
|
6
|
+
export function createKokoroPort(d) {
|
|
7
|
+
const fromDelivery = async (url) => {
|
|
8
|
+
const path = deliveryPath(url);
|
|
9
|
+
const r = await d.fetch(new URL(path, d.base).href);
|
|
10
|
+
if (!r.ok)
|
|
11
|
+
throw new Error(`Kokoro: HTTP ${r.status} for ${path} — the delivery does not carry it (inclusionist-heavy --kokoro)`);
|
|
12
|
+
return r;
|
|
13
|
+
};
|
|
14
|
+
let vocabulary = null;
|
|
15
|
+
// ⚠️ ONE COPY of the 325 MB model for every session: the engine tries WebGPU and may fall back to WASM
|
|
16
|
+
let model = null;
|
|
17
|
+
return {
|
|
18
|
+
async phonemize(text, espeak) {
|
|
19
|
+
// the sentence goes in as a FILE and never as an argument: a line starting with «-» would be read as an option
|
|
20
|
+
const e = await d.espeak({
|
|
21
|
+
arguments: ['-q', '--ipa', '--phonout', OUTPUT, '-v', espeak, '-f', INPUT],
|
|
22
|
+
preRun: [(m) => m.FS.writeFile(INPUT, text.replace(/\s+/g, ' '))],
|
|
23
|
+
});
|
|
24
|
+
return e.FS.readFile(OUTPUT, { encoding: 'utf8' }).trim().replace(/\s*\n\s*/g, ' ');
|
|
25
|
+
},
|
|
26
|
+
vocabulary() {
|
|
27
|
+
vocabulary ??= fromDelivery(URL_DO_TOKENIZADOR_KOKORO)
|
|
28
|
+
.then(async (r) => (await r.json()).model.vocab);
|
|
29
|
+
return vocabulary;
|
|
30
|
+
},
|
|
31
|
+
async voice(id) {
|
|
32
|
+
return new Float32Array(await (await fromDelivery(kokoroVoiceUrl(id))).arrayBuffer());
|
|
33
|
+
},
|
|
34
|
+
async session(device) {
|
|
35
|
+
model ??= fromDelivery(KOKORO_MODEL_URL).then(async (r) => new Uint8Array(await r.arrayBuffer()));
|
|
36
|
+
const s = await d.ort.InferenceSession.create(await model, { executionProviders: [device] });
|
|
37
|
+
const session = {
|
|
38
|
+
async synthesize(ids, style) {
|
|
39
|
+
const out = await s.run({
|
|
40
|
+
input_ids: new d.ort.Tensor('int64', BigInt64Array.from(ids, (id) => BigInt(id)), [1, ids.length]),
|
|
41
|
+
style: new d.ort.Tensor('float32', style, [1, style.length]),
|
|
42
|
+
speed: new d.ort.Tensor('float32', Float32Array.of(1), [1]), // the pace is the engine's, on playback (ADR-0196)
|
|
43
|
+
});
|
|
44
|
+
return out[s.outputNames[0]].data;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
return session;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { KokoroModule } from './kokoro.js';
|
|
3
|
+
export interface KokoroRuntimeDeps {
|
|
4
|
+
/** The page's address, which `heavy/` is resolved against. */
|
|
5
|
+
readonly base: string;
|
|
6
|
+
/**
|
|
7
|
+
* The host's `fetch`, and the host's `WebAssembly.compile` and `instantiate` below: REQUIRED (ADR-0232 D4). The root lends
|
|
8
|
+
* them from `host.win`; a default would be the page's globals reached from `platform/`, and a case answers without a network.
|
|
9
|
+
*/
|
|
10
|
+
readonly fetch: (url: string) => Promise<Response>;
|
|
11
|
+
/** `import()`, injected so a case can hand in the two modules without a network. Optional: `import()` is not a global. */
|
|
12
|
+
readonly importModule?: (url: string) => Promise<unknown>;
|
|
13
|
+
readonly compileWasm: (bytes: ArrayBuffer) => Promise<WebAssembly.Module>;
|
|
14
|
+
readonly instantiateWasm: (module: WebAssembly.Module, imports: WebAssembly.Imports) => Promise<WebAssembly.Instance>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Builds the neural voice. Everything it needs is fetched from the delivery, so a device that never had a network day has it all
|
|
18
|
+
* or has none of it — and «none of it» is a refusal the caller reports, not a silence.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadKokoroRuntime(d: KokoroRuntimeDeps): Promise<KokoroModule>;
|