@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,42 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/onnx-runtime — THE ONE GRAPH RUNNER, LOADED FROM THE DELIVERY (ADR-0216 and its erratum; issues #185, #200).
|
|
3
|
+
//
|
|
4
|
+
// Two things of the engine run an ONNX graph: the neural voice (`platform/kokoro-runtime`) and the reading
|
|
5
|
+
// (`platform/reading-runtime`). They load the same runtime the same way, and that way has one rule nobody may forget, so it is
|
|
6
|
+
// written here once and not in each of them.
|
|
7
|
+
//
|
|
8
|
+
// ⚠️ THE THREADS LOOK FOR THEIR OWN FILES. Left alone, onnxruntime's workers import their glue and their wasm from the address
|
|
9
|
+
// the library was published at — a CDN — and in a school with no network they find nothing, never answer, and the page waits for
|
|
10
|
+
// a session that never opens. Measured in the quiz demo (#181). So the paths are pointed at `heavy/` on the page's own origin.
|
|
11
|
+
//
|
|
12
|
+
// ⚠️ NOTHING HERE IS IMPORTED FROM npm: `import(url)` of an address on our own origin needs no bundler and puts no megabyte in
|
|
13
|
+
// the chunk of a game that runs no graph at all.
|
|
14
|
+
import { deliveryPath } from './heavy.js';
|
|
15
|
+
import { HEAVY_FILES } from './heavy-catalogue.js';
|
|
16
|
+
const ORT_ESM = 'voz:runtime:onnx';
|
|
17
|
+
const ORT_GLUE = 'voz:runtime:onnx:cola';
|
|
18
|
+
const ORT_WASM = 'voz:runtime:onnx:wasm';
|
|
19
|
+
/**
|
|
20
|
+
* ⚠️ THE ADDRESSES ARE NOT WRITTEN HERE. They live once, in the catalogue, with the sha256 that is checked before anything is
|
|
21
|
+
* written into a delivery (ADR-0114, ADR-0177): a second copy in a module would be a version that drifts from the one the build
|
|
22
|
+
* fetched, and the page would ask for a file the delivery does not have.
|
|
23
|
+
*/
|
|
24
|
+
function heavyUrlOf(id) {
|
|
25
|
+
const heavy = HEAVY_FILES.find((p) => p.id === id);
|
|
26
|
+
if (!heavy?.url)
|
|
27
|
+
throw new Error(`the heavy catalogue has no address for ${id}`);
|
|
28
|
+
return heavy.url;
|
|
29
|
+
}
|
|
30
|
+
/** Where the delivery serves a catalogued file, from the page's own address. */
|
|
31
|
+
export const atDelivery = (id, base) => new URL(deliveryPath(heavyUrlOf(id)), base).href;
|
|
32
|
+
/** The runtime, from the delivery, with its threads pointed at the delivery too. Refuses by NAME when it is not there. */
|
|
33
|
+
export async function loadOnnxRuntime(d) {
|
|
34
|
+
const loadModule = d.importModule ?? ((url) => import(/* @vite-ignore */ url));
|
|
35
|
+
const module = await loadModule(atDelivery(ORT_ESM, d.base));
|
|
36
|
+
const ort = (module.default ?? module);
|
|
37
|
+
if (!ort?.InferenceSession)
|
|
38
|
+
throw new Error(`onnxruntime did not load from the delivery (${ORT_ESM})`);
|
|
39
|
+
if (ort.env?.wasm)
|
|
40
|
+
ort.env.wasm.wasmPaths = { mjs: atDelivery(ORT_GLUE, d.base), wasm: atDelivery(ORT_WASM, d.base) };
|
|
41
|
+
return ort;
|
|
42
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { ReadingAnswer, ReadingRequest } from './reading-worker.js';
|
|
3
|
+
/** The little of a `Worker` this module uses, so a gate can answer the protocol without a thread. */
|
|
4
|
+
export interface ReadingWorkerLike {
|
|
5
|
+
postMessage(message: ReadingRequest, transfer?: readonly Transferable[]): void;
|
|
6
|
+
onmessage: ((event: {
|
|
7
|
+
data: ReadingAnswer;
|
|
8
|
+
}) => void) | null;
|
|
9
|
+
onerror?: ((event: unknown) => void) | null;
|
|
10
|
+
terminate(): void;
|
|
11
|
+
}
|
|
12
|
+
export interface ReadingInWorkerDeps {
|
|
13
|
+
/** The page's address: the worker resolves `heavy/` against it, because IT is who fetches the model. */
|
|
14
|
+
readonly base: string;
|
|
15
|
+
readonly language: string;
|
|
16
|
+
/**
|
|
17
|
+
* Opens the thread: `platform/reading-worker` started as a module worker. REQUIRED (ADR-0232 D4), and the root writes it,
|
|
18
|
+
* because the one form a bundler recognises — `new Worker(new URL('…', import.meta.url), { type: 'module' })`, literally —
|
|
19
|
+
* has to sit where the `Worker` is the host's.
|
|
20
|
+
*/
|
|
21
|
+
readonly spawn: () => ReadingWorkerLike;
|
|
22
|
+
/**
|
|
23
|
+
* WHY THE THREAD COULD NOT OPEN, WHEN NOBODY IS THERE TO BE TOLD (ADR-0169).
|
|
24
|
+
*
|
|
25
|
+
* 🔴 The opening is awaited by `transcribe()`, and only by it. A game that declares `uses: { reading: true }` and has
|
|
26
|
+
* not listened yet — or one whose child never will — has nobody awaiting the answer, so a model that does not load
|
|
27
|
+
* used to reject into NOTHING: an unhandled rejection in a browser's console, and silence in `problems`.
|
|
28
|
+
* 📌 THE REASON AND NOT THE SENTENCE: what this hands over is the failure's own words, and the line a human reads —
|
|
29
|
+
* what the child loses, what to do about it — is written by whoever owns the diagnostic channel. A module that is
|
|
30
|
+
* one thread's protocol has no business holding interface prose, and the raw-prose ledger is where that shows.
|
|
31
|
+
* 📌 Optional, and the absence is answered: without it the failure is still owned, it is just not reported.
|
|
32
|
+
*/
|
|
33
|
+
readonly report?: (reason: string) => void;
|
|
34
|
+
}
|
|
35
|
+
export interface ReadingInWorker {
|
|
36
|
+
transcribe(samples: Float32Array): Promise<string>;
|
|
37
|
+
/** Lets the thread go. A transcription still running answers with the reason instead of never answering. */
|
|
38
|
+
close(): void;
|
|
39
|
+
}
|
|
40
|
+
/** The reading thread of the language being read in, kept between readings; see `keepOneThreadPerLanguage`. */
|
|
41
|
+
export interface ReadingThreads<T extends {
|
|
42
|
+
close(): void;
|
|
43
|
+
}> {
|
|
44
|
+
/**
|
|
45
|
+
* The thread for this language: the one kept when its base language is the same, else a new one from `open` — handed a
|
|
46
|
+
* `failed` to call when its opening fails. `open` is given at each call because the one form a bundler recognises for the
|
|
47
|
+
* thread's file has to sit where the host's `Worker` is (ADR-0232 D4), which is the caller's scope and not this module's.
|
|
48
|
+
*/
|
|
49
|
+
forLanguage(language: string, open: (failed: () => void) => T): T;
|
|
50
|
+
/** Lets the kept thread go (the game left); the next `forLanguage` opens one again. */
|
|
51
|
+
close(): void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* ONE READING THREAD PER LANGUAGE, KEPT (issue #185; ADR-0225 erratum). Opening a thread compiles a model of up to 378 MiB, so a
|
|
55
|
+
* reading in the language already open reuses its thread, and a reading in another language (a child who switched: `pt-BR` and
|
|
56
|
+
* `pt-PT` are one model, `pt` and `en` are two) closes the old thread and opens the new one — one compiled model in memory at a
|
|
57
|
+
* time, which is what a school machine can hold.
|
|
58
|
+
* 🔴 A THREAD WHOSE OPENING FAILED IS FORGOTTEN: the next reading opens it again — the model may have come down since, and a
|
|
59
|
+
* thread kept dead would answer every later reading with the first one's failure.
|
|
60
|
+
*/
|
|
61
|
+
export declare function keepOneThreadPerLanguage<T extends {
|
|
62
|
+
close(): void;
|
|
63
|
+
}>(): ReadingThreads<T>;
|
|
64
|
+
export declare function createReadingInWorker(d: ReadingInWorkerDeps): ReadingInWorker;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/reading-in-worker — THE ENGINE'S SIDE OF THE READING WORKER (ADR-0216 §2; issue #185).
|
|
3
|
+
//
|
|
4
|
+
// Answers the same shape `platform/reading` asks its `model` port for — something with `transcribe(samples)` — and keeps the
|
|
5
|
+
// work off the thread that draws the game and feeds the microphone (`platform/reading-worker` says what that costs when it is
|
|
6
|
+
// not kept off it).
|
|
7
|
+
//
|
|
8
|
+
// 📌 THE WORKER IS OPENED AT THE FIRST READING AND KEPT, not opened per utterance: opening it means compiling the model again,
|
|
9
|
+
// and the model is up to 378 MiB. It is closed when the engine lets the reading go, so a game that stops listening stops
|
|
10
|
+
// paying for a thread.
|
|
11
|
+
/**
|
|
12
|
+
* ONE READING THREAD PER LANGUAGE, KEPT (issue #185; ADR-0225 erratum). Opening a thread compiles a model of up to 378 MiB, so a
|
|
13
|
+
* reading in the language already open reuses its thread, and a reading in another language (a child who switched: `pt-BR` and
|
|
14
|
+
* `pt-PT` are one model, `pt` and `en` are two) closes the old thread and opens the new one — one compiled model in memory at a
|
|
15
|
+
* time, which is what a school machine can hold.
|
|
16
|
+
* 🔴 A THREAD WHOSE OPENING FAILED IS FORGOTTEN: the next reading opens it again — the model may have come down since, and a
|
|
17
|
+
* thread kept dead would answer every later reading with the first one's failure.
|
|
18
|
+
*/
|
|
19
|
+
export function keepOneThreadPerLanguage() {
|
|
20
|
+
let kept = null;
|
|
21
|
+
const baseOf = (tag) => tag.split('-')[0].toLowerCase();
|
|
22
|
+
return {
|
|
23
|
+
forLanguage(language, open) {
|
|
24
|
+
const base = baseOf(language);
|
|
25
|
+
if (kept?.base === base && kept.thread)
|
|
26
|
+
return kept.thread;
|
|
27
|
+
kept?.thread?.close();
|
|
28
|
+
const mine = { base, thread: null, dead: false };
|
|
29
|
+
kept = mine;
|
|
30
|
+
const thread = open(() => {
|
|
31
|
+
mine.dead = true;
|
|
32
|
+
if (kept === mine)
|
|
33
|
+
kept = null;
|
|
34
|
+
mine.thread?.close();
|
|
35
|
+
});
|
|
36
|
+
if (mine.dead) {
|
|
37
|
+
thread.close();
|
|
38
|
+
return thread;
|
|
39
|
+
} // it failed while opening: answered, and not kept
|
|
40
|
+
mine.thread = thread;
|
|
41
|
+
return thread;
|
|
42
|
+
},
|
|
43
|
+
close() {
|
|
44
|
+
kept?.thread?.close();
|
|
45
|
+
kept = null;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function createReadingInWorker(d) {
|
|
50
|
+
const worker = d.spawn();
|
|
51
|
+
let nextId = 1;
|
|
52
|
+
const waiting = new Map();
|
|
53
|
+
let opened = null;
|
|
54
|
+
let closed = false;
|
|
55
|
+
const openedPromise = new Promise((resolve, reject) => { opened = { resolve, reject }; });
|
|
56
|
+
/*
|
|
57
|
+
* 🔴 THE OPENING HAS AN OWNER FROM BIRTH, and giving it one is the whole job of this line. Nothing awaits
|
|
58
|
+
* `openedPromise` until the first `transcribe()`, so a thread that cannot open — a model file the delivery never
|
|
59
|
+
* wrote, a wasm that will not compile — rejected into nothing.
|
|
60
|
+
* 📏 Measured 2026-09-22: the browser project ended with every one of its 1140 cases green and an exit code of
|
|
61
|
+
* FAILURE, from this rejection alone. A suite that reports failure while passing teaches everyone to stop reading
|
|
62
|
+
* the exit code, and this house has already paid for that once — the published branch's CI was red for eight days.
|
|
63
|
+
* ⚠️ AND IT DOES NOT SWALLOW THE REASON: `transcribe()` awaits this same promise and still throws it, so whoever
|
|
64
|
+
* asked for a reading is told what went wrong. This line is for when NOBODY asks.
|
|
65
|
+
* 📌 Letting the thread go is not a failure: `close()` rejects the opening to release whoever was waiting, and a
|
|
66
|
+
* child who changed games is not a defect to report.
|
|
67
|
+
*/
|
|
68
|
+
openedPromise.catch((e) => {
|
|
69
|
+
if (closed)
|
|
70
|
+
return;
|
|
71
|
+
d.report?.(e.message);
|
|
72
|
+
});
|
|
73
|
+
/** Everything pending gives up with the SAME reason — a promise nobody ever settles is how a game freezes politely. */
|
|
74
|
+
const giveUp = (message) => {
|
|
75
|
+
opened?.reject(new Error(message));
|
|
76
|
+
for (const { reject } of waiting.values())
|
|
77
|
+
reject(new Error(message));
|
|
78
|
+
waiting.clear();
|
|
79
|
+
};
|
|
80
|
+
worker.onmessage = (event) => {
|
|
81
|
+
const msg = event.data;
|
|
82
|
+
if (msg.kind === 'ready') {
|
|
83
|
+
opened?.resolve();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (msg.kind === 'text') {
|
|
87
|
+
waiting.get(msg.id)?.resolve(msg.text);
|
|
88
|
+
waiting.delete(msg.id);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (msg.id === undefined) {
|
|
92
|
+
opened?.reject(new Error(msg.message));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
waiting.get(msg.id)?.reject(new Error(msg.message));
|
|
96
|
+
waiting.delete(msg.id);
|
|
97
|
+
};
|
|
98
|
+
// a worker that dies — a file that did not load, a wasm that ran out of memory — must not leave the child waiting in silence
|
|
99
|
+
if ('onerror' in worker)
|
|
100
|
+
worker.onerror = () => { giveUp('reading worker: the thread stopped before answering'); };
|
|
101
|
+
worker.postMessage({ kind: 'load', base: d.base, language: d.language });
|
|
102
|
+
return {
|
|
103
|
+
async transcribe(samples) {
|
|
104
|
+
if (closed)
|
|
105
|
+
throw new Error('reading worker: asked to transcribe after the thread was let go');
|
|
106
|
+
await openedPromise;
|
|
107
|
+
const id = nextId++;
|
|
108
|
+
const answer = new Promise((resolve, reject) => { waiting.set(id, { resolve, reject }); });
|
|
109
|
+
/*
|
|
110
|
+
* 📌 THE SAMPLES ARE TRANSFERRED AND NOT COPIED: 30 s at 16 kHz is 1.9 MB, and copying it is a pause on the very thread
|
|
111
|
+
* this module exists to keep free. ⚠️ The buffer is empty on this side afterwards, which is correct — the recording
|
|
112
|
+
* belongs to the transcription now, and `platform/microphone` hands over a fresh one for the next reading.
|
|
113
|
+
*/
|
|
114
|
+
worker.postMessage({ kind: 'transcribe', id, samples }, [samples.buffer]);
|
|
115
|
+
return await answer;
|
|
116
|
+
},
|
|
117
|
+
close() {
|
|
118
|
+
if (closed)
|
|
119
|
+
return;
|
|
120
|
+
closed = true;
|
|
121
|
+
giveUp('reading worker: the thread was let go while this reading was still running');
|
|
122
|
+
worker.terminate();
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** What the engine asks of a language: which catalogue files, and what the encoder eats. */
|
|
3
|
+
export interface ReadingModelPlan {
|
|
4
|
+
readonly language: string;
|
|
5
|
+
/** Whisper eats a log-mel spectrogram; Moonshine eats the samples. */
|
|
6
|
+
readonly input: 'log-mel' | 'waveform';
|
|
7
|
+
readonly encoder: string;
|
|
8
|
+
readonly decoder: string;
|
|
9
|
+
/** The second decoder graph, where the model has one: the first pass builds the cache, this one uses it. */
|
|
10
|
+
readonly decoderPast?: string;
|
|
11
|
+
readonly tokenizer: string;
|
|
12
|
+
readonly generation: string;
|
|
13
|
+
/** How many heads the caches hold and how wide each is — what a merged decoder needs to be handed empty ones. */
|
|
14
|
+
readonly config: string;
|
|
15
|
+
/** Only the mel front end needs one. */
|
|
16
|
+
readonly preprocessor?: string;
|
|
17
|
+
}
|
|
18
|
+
/** 16 kHz mono, for every one of them: it is what all three were trained on, and resampling is the caller's job. */
|
|
19
|
+
export declare const READING_RATE = 16000;
|
|
20
|
+
/** Whisper reads a 30 s window at a time, always: shorter is padded with silence, longer is cut. */
|
|
21
|
+
export declare const WHISPER_SAMPLES: number;
|
|
22
|
+
export declare const WHISPER_FRAMES = 3000;
|
|
23
|
+
export declare const WHISPER_BANDS = 80;
|
|
24
|
+
export declare const READING_MODELS: readonly ReadingModelPlan[];
|
|
25
|
+
/** The model of a language tag (`pt-BR` → pt), or `null` where the project has none — which the caller must SAY, not hide. */
|
|
26
|
+
export declare function readingModelFor(language: string): ReadingModelPlan | null;
|
|
27
|
+
/**
|
|
28
|
+
* WHAT WHISPER'S ENCODER EATS: a log-mel spectrogram of 80 bands by 3000 frames, from a 30 s window at 16 kHz.
|
|
29
|
+
*
|
|
30
|
+
* The steps are `WhisperFeatureExtractor`'s, in its order, and each one matters: the window is a PERIODIC Hann (a symmetric one
|
|
31
|
+
* moves every value); the signal is reflected at both ends by half a window, so frame 0 is centred on sample 0; the power
|
|
32
|
+
* spectrum is the magnitude SQUARED; the last frame is dropped, which is why 480 000 samples give 3000 and not 3001; the floor is
|
|
33
|
+
* eight decades below the LOUDEST point of this window, not a constant; and the scale ends in (x + 4) / 4.
|
|
34
|
+
*/
|
|
35
|
+
export declare function logMel(samples: Float32Array, melFilters: readonly (readonly number[])[]): Float32Array;
|
|
36
|
+
/** The part of a `config.json` that says how a streaming encoder cuts the wave. */
|
|
37
|
+
export interface WaveFrameConfig {
|
|
38
|
+
readonly encoder_config?: {
|
|
39
|
+
readonly frame_ms?: number;
|
|
40
|
+
readonly sample_rate?: number;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* THE FRAME A STREAMING ENCODER CUTS THE WAVE INTO, in samples: `frame_ms` at `sample_rate`, from the model's own `config.json`
|
|
45
|
+
* — 5 ms at 16 kHz, 80, for both Moonshines. 1 where the config names no frame: a model without one takes any length.
|
|
46
|
+
*/
|
|
47
|
+
export declare function waveFrameOf(config: WaveFrameConfig): number;
|
|
48
|
+
/**
|
|
49
|
+
* WHAT MOONSHINE STREAMING'S ENCODER EATS: the wave in WHOLE FRAMES. Its first node reshapes the samples to `[1, -1, 80]`, and
|
|
50
|
+
* onnxruntime refuses a length that is not a multiple of 80 — 📏 measured on both graphs (issue #185): a microphone hands over
|
|
51
|
+
* blocks of 4096, which make whole frames only five at a time, so most readings failed. The model's own processor pads with
|
|
52
|
+
* zeros on the right to a multiple of 80 (`preprocessor_config.json`: `pad_to_multiple_of`, `padding_side`, `padding_value`),
|
|
53
|
+
* and this is that step: under 5 ms of silence after the child finished, and her wave untouched. An empty recording becomes one
|
|
54
|
+
* frame of silence, because a graph handed nothing does not run; a wave already in whole frames is handed on as it came.
|
|
55
|
+
*/
|
|
56
|
+
export declare function inWholeFrames(samples: Float32Array, frame: number): Float32Array;
|
|
57
|
+
/** A `tokenizer.json`, in the parts a reading reads: the vocabulary, the added tokens, and which decoder undoes them. */
|
|
58
|
+
export interface TokenizerFile {
|
|
59
|
+
readonly model?: {
|
|
60
|
+
readonly vocab?: Readonly<Record<string, number>>;
|
|
61
|
+
};
|
|
62
|
+
readonly added_tokens?: readonly {
|
|
63
|
+
readonly id: number;
|
|
64
|
+
readonly content: string;
|
|
65
|
+
readonly special?: boolean;
|
|
66
|
+
}[];
|
|
67
|
+
readonly decoder?: {
|
|
68
|
+
readonly type?: string;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* WHAT THE CHILD ACTUALLY SAID, from the ids the decoder answered.
|
|
73
|
+
*
|
|
74
|
+
* The two families undo themselves differently and the file says which: Whisper writes every byte as a printable character
|
|
75
|
+
* (`ByteLevel`), Moonshine writes a space as `▁` and a rare byte as `<0x..>` (`Replace` + `ByteFallback` + `Fuse` + `Strip`).
|
|
76
|
+
* Both end in ONE decode of UTF-8, which is what keeps an accent that spans two tokens from becoming two broken characters.
|
|
77
|
+
*/
|
|
78
|
+
export declare function readingTextOf(tokenizer: TokenizerFile, ids: readonly number[]): string;
|
|
79
|
+
/** What a `generation_config.json` says must never be answered — Whisper's list of tokens that are not speech. */
|
|
80
|
+
export interface GenerationConfig {
|
|
81
|
+
readonly suppress_tokens?: readonly number[];
|
|
82
|
+
readonly begin_suppress_tokens?: readonly number[];
|
|
83
|
+
readonly eos_token_id?: number;
|
|
84
|
+
readonly decoder_start_token_id?: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The tokens refused at this step. `atStart` is the first answered token, where Whisper also refuses the ones that would make it
|
|
88
|
+
* open with a silence or a punctuation mark — without this it answers «.» to a child who read a whole sentence.
|
|
89
|
+
*/
|
|
90
|
+
export declare function suppressedTokens(config: GenerationConfig, atStart: boolean): ReadonlySet<number>;
|
|
91
|
+
/** The id of a named token (`<|pt|>`, `<|transcribe|>`), or `null` — a model that does not have it must be SAID, not guessed. */
|
|
92
|
+
export declare function tokenIdOf(tokenizer: TokenizerFile, content: string): number | null;
|
|
93
|
+
/** The next token: the biggest logit that is not refused. Greedy, because a child reading a known text needs no imagination. */
|
|
94
|
+
export declare function nextToken(logits: ArrayLike<number>, suppressed?: ReadonlySet<number>): number;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/reading-model — WHAT A READING MODEL NEEDS DONE TO SOUND AND TO TOKENS, in its pure half (ADR-0216 §2; issues #185, #200).
|
|
3
|
+
//
|
|
4
|
+
// The engine hears a child read aloud with one model per language (ADR-0201 erratum): Whisper small for Portuguese, Moonshine
|
|
5
|
+
// streaming small for English and Spanish. Around the graphs there is arithmetic that has nothing to do with a browser — turning
|
|
6
|
+
// sound into what the encoder eats, and turning the numbers the decoder answers back into words — and it lives here, where it can
|
|
7
|
+
// be measured. `platform/reading-runtime` is the half that opens sessions and fetches files.
|
|
8
|
+
//
|
|
9
|
+
// 🎯 MEASURED AGAINST THE ORIGINALS, not against my idea of them: `tests/fixtures/reading-ground-truth.json` is what
|
|
10
|
+
// `WhisperFeatureExtractor` and the three tokenizers answer for one deterministic signal and three phrases, written by
|
|
11
|
+
// `scratchpad/verdade-da-leitura.py`. A front end that is subtly wrong still runs and still returns text — the wrong text — so
|
|
12
|
+
// «it produced something» is not a check.
|
|
13
|
+
//
|
|
14
|
+
// · THE MEL FILTERS ARE NOT COMPUTED HERE. Whisper's own `preprocessor_config.json` carries them (80 × 201), which is most of its
|
|
15
|
+
// 185 KB, and the catalogue already fetches it: a filterbank rebuilt from a formula is a second opinion about the model's input.
|
|
16
|
+
// · Moonshine takes the WAVEFORM. No spectrogram, no padding to 30 s — it is a different front end, not a variant of this one.
|
|
17
|
+
// ⚠️ But the streaming encoder takes it in WHOLE FRAMES of 5 ms (80 samples), and refuses any other length: `inWholeFrames`.
|
|
18
|
+
/** 16 kHz mono, for every one of them: it is what all three were trained on, and resampling is the caller's job. */
|
|
19
|
+
export const READING_RATE = 16_000;
|
|
20
|
+
/** Whisper reads a 30 s window at a time, always: shorter is padded with silence, longer is cut. */
|
|
21
|
+
export const WHISPER_SAMPLES = 30 * READING_RATE;
|
|
22
|
+
export const WHISPER_FRAMES = 3000;
|
|
23
|
+
export const WHISPER_BANDS = 80;
|
|
24
|
+
const N_FFT = 400;
|
|
25
|
+
const HOP = 160;
|
|
26
|
+
const HALF = N_FFT / 2;
|
|
27
|
+
const BINS = HALF + 1;
|
|
28
|
+
/** What a band is when its power is clipped: the original clips at 1e-10 before the log. */
|
|
29
|
+
const CLIPPED = Math.log10(1e-10);
|
|
30
|
+
export const READING_MODELS = Object.freeze([
|
|
31
|
+
{
|
|
32
|
+
language: 'pt', input: 'log-mel',
|
|
33
|
+
encoder: 'reading:pt:encoder', decoder: 'reading:pt:decoder', decoderPast: 'reading:pt:decoder:past',
|
|
34
|
+
tokenizer: 'reading:pt:tokenizer', generation: 'reading:pt:generation', config: 'reading:pt:config',
|
|
35
|
+
preprocessor: 'reading:pt:preprocessor',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
language: 'en', input: 'waveform',
|
|
39
|
+
encoder: 'reading:en:encoder', decoder: 'reading:en:decoder',
|
|
40
|
+
tokenizer: 'reading:en:tokenizer', generation: 'reading:en:generation', config: 'reading:en:config',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
language: 'es', input: 'waveform',
|
|
44
|
+
encoder: 'reading:es:encoder', decoder: 'reading:es:decoder', decoderPast: 'reading:es:decoder:past',
|
|
45
|
+
tokenizer: 'reading:es:tokenizer', generation: 'reading:es:generation', config: 'reading:es:config',
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
/** The model of a language tag (`pt-BR` → pt), or `null` where the project has none — which the caller must SAY, not hide. */
|
|
49
|
+
export function readingModelFor(language) {
|
|
50
|
+
const tag = language.split('-')[0].toLowerCase();
|
|
51
|
+
return READING_MODELS.find((m) => m.language === tag) ?? null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* WHAT WHISPER'S ENCODER EATS: a log-mel spectrogram of 80 bands by 3000 frames, from a 30 s window at 16 kHz.
|
|
55
|
+
*
|
|
56
|
+
* The steps are `WhisperFeatureExtractor`'s, in its order, and each one matters: the window is a PERIODIC Hann (a symmetric one
|
|
57
|
+
* moves every value); the signal is reflected at both ends by half a window, so frame 0 is centred on sample 0; the power
|
|
58
|
+
* spectrum is the magnitude SQUARED; the last frame is dropped, which is why 480 000 samples give 3000 and not 3001; the floor is
|
|
59
|
+
* eight decades below the LOUDEST point of this window, not a constant; and the scale ends in (x + 4) / 4.
|
|
60
|
+
*/
|
|
61
|
+
export function logMel(samples, melFilters) {
|
|
62
|
+
const audio = new Float32Array(WHISPER_SAMPLES);
|
|
63
|
+
audio.set(samples.subarray(0, WHISPER_SAMPLES));
|
|
64
|
+
// the reflected padding, written once: `at(i)` is the padded signal, so no copy of 480 000 samples is made
|
|
65
|
+
const at = (i) => {
|
|
66
|
+
const j = i - HALF;
|
|
67
|
+
if (j < 0)
|
|
68
|
+
return audio[-j];
|
|
69
|
+
if (j >= WHISPER_SAMPLES)
|
|
70
|
+
return audio[2 * WHISPER_SAMPLES - j - 2];
|
|
71
|
+
return audio[j];
|
|
72
|
+
};
|
|
73
|
+
const turns = dftTurns();
|
|
74
|
+
const out = new Float32Array(WHISPER_BANDS * WHISPER_FRAMES);
|
|
75
|
+
const power = new Float64Array(BINS);
|
|
76
|
+
let loudest = -Infinity;
|
|
77
|
+
for (let frame = 0; frame < WHISPER_FRAMES; frame++) {
|
|
78
|
+
const start = frame * HOP;
|
|
79
|
+
// 🎯 A WINDOW OF PURE SILENCE IS ANSWERED, NOT COMPUTED, and it is EXACT: every bin of a window of zeros is zero, so every
|
|
80
|
+
// band is the clipped value. A child reads for a few seconds into a 30 s window, so most frames are this one. It never
|
|
81
|
+
// raises the loudest point: no computed band is ever below the clip, and an all-silent window floors at −∞.
|
|
82
|
+
let quiet = true;
|
|
83
|
+
for (let n = 0; n < N_FFT && quiet; n++)
|
|
84
|
+
quiet = at(start + n) === 0;
|
|
85
|
+
if (quiet) {
|
|
86
|
+
for (let band = 0; band < WHISPER_BANDS; band++)
|
|
87
|
+
out[band * WHISPER_FRAMES + frame] = CLIPPED;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
powerOf(at, start, turns, power);
|
|
91
|
+
loudest = Math.max(loudest, melOf(power, melFilters, out, frame));
|
|
92
|
+
}
|
|
93
|
+
// the floor is eight decades below the LOUDEST point of this window, not a constant, and the scale ends in (x + 4) / 4
|
|
94
|
+
const floor = loudest - 8;
|
|
95
|
+
for (let i = 0; i < out.length; i++)
|
|
96
|
+
out[i] = (Math.max(out[i], floor) + 4) / 4;
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* THE FRAME A STREAMING ENCODER CUTS THE WAVE INTO, in samples: `frame_ms` at `sample_rate`, from the model's own `config.json`
|
|
101
|
+
* — 5 ms at 16 kHz, 80, for both Moonshines. 1 where the config names no frame: a model without one takes any length.
|
|
102
|
+
*/
|
|
103
|
+
export function waveFrameOf(config) {
|
|
104
|
+
const { frame_ms: ms, sample_rate: rate } = config.encoder_config ?? {};
|
|
105
|
+
return ms && rate ? Math.round((ms * rate) / 1000) : 1;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* WHAT MOONSHINE STREAMING'S ENCODER EATS: the wave in WHOLE FRAMES. Its first node reshapes the samples to `[1, -1, 80]`, and
|
|
109
|
+
* onnxruntime refuses a length that is not a multiple of 80 — 📏 measured on both graphs (issue #185): a microphone hands over
|
|
110
|
+
* blocks of 4096, which make whole frames only five at a time, so most readings failed. The model's own processor pads with
|
|
111
|
+
* zeros on the right to a multiple of 80 (`preprocessor_config.json`: `pad_to_multiple_of`, `padding_side`, `padding_value`),
|
|
112
|
+
* and this is that step: under 5 ms of silence after the child finished, and her wave untouched. An empty recording becomes one
|
|
113
|
+
* frame of silence, because a graph handed nothing does not run; a wave already in whole frames is handed on as it came.
|
|
114
|
+
*/
|
|
115
|
+
export function inWholeFrames(samples, frame) {
|
|
116
|
+
const whole = Math.max(1, Math.ceil(samples.length / frame)) * frame;
|
|
117
|
+
if (whole === samples.length)
|
|
118
|
+
return samples;
|
|
119
|
+
const out = new Float32Array(whole);
|
|
120
|
+
out.set(samples);
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/** A PERIODIC Hann window: a symmetric one moves every value. 400 numbers, computed once. */
|
|
124
|
+
const HANN = Float64Array.from({ length: N_FFT }, (_, i) => 0.5 - 0.5 * Math.cos((2 * Math.PI * i) / N_FFT));
|
|
125
|
+
/**
|
|
126
|
+
* ⚠️ THE TURNS ARE COMPUTED ONCE. A 400-point real DFT for 201 bins is 80 400 sines and cosines per frame, and there are 3000
|
|
127
|
+
* frames: asking `Math.cos` for them each time is a quarter of a billion calls, which on a school machine is not «slower», it is
|
|
128
|
+
* a child waiting half a minute to be told what they read.
|
|
129
|
+
*/
|
|
130
|
+
function dftTurns() {
|
|
131
|
+
const cos = new Float64Array(BINS * N_FFT);
|
|
132
|
+
const sin = new Float64Array(BINS * N_FFT);
|
|
133
|
+
for (let k = 0; k < BINS; k++) {
|
|
134
|
+
for (let n = 0; n < N_FFT; n++) {
|
|
135
|
+
const angle = (-2 * Math.PI * k * n) / N_FFT;
|
|
136
|
+
cos[k * N_FFT + n] = Math.cos(angle);
|
|
137
|
+
sin[k * N_FFT + n] = Math.sin(angle);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { cos, sin };
|
|
141
|
+
}
|
|
142
|
+
/** One frame's power spectrum: the windowed DFT's magnitude SQUARED, one value per bin. */
|
|
143
|
+
function powerOf(at, start, turns, power) {
|
|
144
|
+
for (let k = 0; k < BINS; k++) {
|
|
145
|
+
let re = 0, im = 0;
|
|
146
|
+
const row = k * N_FFT;
|
|
147
|
+
for (let n = 0; n < N_FFT; n++) {
|
|
148
|
+
const x = at(start + n) * HANN[n];
|
|
149
|
+
re += x * turns.cos[row + n];
|
|
150
|
+
im += x * turns.sin[row + n];
|
|
151
|
+
}
|
|
152
|
+
power[k] = re * re + im * im;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** One frame's 80 bands, each the power through its mel filter, clipped at 1e-10 BEFORE the log. Answers the frame's loudest. */
|
|
156
|
+
function melOf(power, melFilters, out, frame) {
|
|
157
|
+
let loudest = -Infinity;
|
|
158
|
+
for (let band = 0; band < WHISPER_BANDS; band++) {
|
|
159
|
+
const filter = melFilters[band];
|
|
160
|
+
let sum = 0;
|
|
161
|
+
for (let k = 0; k < BINS; k++)
|
|
162
|
+
sum += power[k] * filter[k];
|
|
163
|
+
const value = Math.log10(Math.max(sum, 1e-10));
|
|
164
|
+
out[band * WHISPER_FRAMES + frame] = value;
|
|
165
|
+
if (value > loudest)
|
|
166
|
+
loudest = value;
|
|
167
|
+
}
|
|
168
|
+
return loudest;
|
|
169
|
+
}
|
|
170
|
+
/** id → the piece of text that token stands for, with the special ones (`<|pt|>`, `</s>`…) marked so they never reach a child. */
|
|
171
|
+
function piecesOf(tokenizer) {
|
|
172
|
+
const text = new Map();
|
|
173
|
+
for (const [piece, id] of Object.entries(tokenizer.model?.vocab ?? {}))
|
|
174
|
+
text.set(id, piece);
|
|
175
|
+
const special = new Set();
|
|
176
|
+
for (const added of tokenizer.added_tokens ?? []) {
|
|
177
|
+
text.set(added.id, added.content);
|
|
178
|
+
// ⚠️ `special` is absent on some files and the mark is what keeps `<|startoftranscript|>` out of a child's sentence, so the
|
|
179
|
+
// shape of the content decides when the flag does not: every one of them is bracketed.
|
|
180
|
+
if (added.special ?? /^<.*>$/.test(added.content))
|
|
181
|
+
special.add(added.id);
|
|
182
|
+
}
|
|
183
|
+
return { text, special };
|
|
184
|
+
}
|
|
185
|
+
/** The bytes GPT-2 writes as themselves: its three printable ranges, inclusive (`bytes_to_unicode`, openai/gpt-2 `encoder.py`). */
|
|
186
|
+
const PRINTABLE_RANGES = [[33, 126], [161, 172], [174, 255]];
|
|
187
|
+
// GPT-2's byte alphabet: the printable character each byte is written as, so a tokenizer file stays text. Whisper's decoder is
|
|
188
|
+
// this one (`ByteLevel`), and reversing it is the whole of it.
|
|
189
|
+
const byteOfChar = (() => {
|
|
190
|
+
const map = new Map();
|
|
191
|
+
const printable = [];
|
|
192
|
+
for (const [lo, hi] of PRINTABLE_RANGES)
|
|
193
|
+
for (let b = lo; b <= hi; b++)
|
|
194
|
+
printable.push(b);
|
|
195
|
+
for (const b of printable)
|
|
196
|
+
map.set(String.fromCharCode(b), b);
|
|
197
|
+
let next = 0;
|
|
198
|
+
for (let b = 0; b < 256; b++) {
|
|
199
|
+
if (printable.includes(b))
|
|
200
|
+
continue;
|
|
201
|
+
map.set(String.fromCharCode(256 + next), b);
|
|
202
|
+
next++;
|
|
203
|
+
}
|
|
204
|
+
return map;
|
|
205
|
+
})();
|
|
206
|
+
const BYTE_FALLBACK = /^<0x([0-9A-Fa-f]{2})>$/;
|
|
207
|
+
/**
|
|
208
|
+
* WHAT THE CHILD ACTUALLY SAID, from the ids the decoder answered.
|
|
209
|
+
*
|
|
210
|
+
* The two families undo themselves differently and the file says which: Whisper writes every byte as a printable character
|
|
211
|
+
* (`ByteLevel`), Moonshine writes a space as `▁` and a rare byte as `<0x..>` (`Replace` + `ByteFallback` + `Fuse` + `Strip`).
|
|
212
|
+
* Both end in ONE decode of UTF-8, which is what keeps an accent that spans two tokens from becoming two broken characters.
|
|
213
|
+
*/
|
|
214
|
+
export function readingTextOf(tokenizer, ids) {
|
|
215
|
+
const { text, special } = piecesOf(tokenizer);
|
|
216
|
+
const byteLevel = tokenizer.decoder?.type === 'ByteLevel';
|
|
217
|
+
const bytes = [];
|
|
218
|
+
for (const id of ids) {
|
|
219
|
+
if (special.has(id))
|
|
220
|
+
continue;
|
|
221
|
+
const piece = text.get(id);
|
|
222
|
+
if (piece === undefined)
|
|
223
|
+
continue; // an id the vocabulary does not have is not a character: it is dropped, never guessed
|
|
224
|
+
if (byteLevel) {
|
|
225
|
+
for (const ch of piece)
|
|
226
|
+
bytes.push(byteOfChar.get(ch) ?? ch.charCodeAt(0));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const fallback = BYTE_FALLBACK.exec(piece);
|
|
230
|
+
if (fallback) {
|
|
231
|
+
bytes.push(parseInt(fallback[1], 16));
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
for (const b of new TextEncoder().encode(piece.replaceAll('▁', ' ')))
|
|
235
|
+
bytes.push(b);
|
|
236
|
+
}
|
|
237
|
+
const whole = new TextDecoder().decode(Uint8Array.from(bytes));
|
|
238
|
+
return byteLevel ? whole : whole.replace(/^ /, '');
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The tokens refused at this step. `atStart` is the first answered token, where Whisper also refuses the ones that would make it
|
|
242
|
+
* open with a silence or a punctuation mark — without this it answers «.» to a child who read a whole sentence.
|
|
243
|
+
*/
|
|
244
|
+
export function suppressedTokens(config, atStart) {
|
|
245
|
+
const out = new Set(config.suppress_tokens ?? []);
|
|
246
|
+
if (atStart)
|
|
247
|
+
for (const id of config.begin_suppress_tokens ?? [])
|
|
248
|
+
out.add(id);
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
/** The id of a named token (`<|pt|>`, `<|transcribe|>`), or `null` — a model that does not have it must be SAID, not guessed. */
|
|
252
|
+
export function tokenIdOf(tokenizer, content) {
|
|
253
|
+
for (const added of tokenizer.added_tokens ?? [])
|
|
254
|
+
if (added.content === content)
|
|
255
|
+
return added.id;
|
|
256
|
+
const fromVocab = tokenizer.model?.vocab?.[content];
|
|
257
|
+
return fromVocab ?? null;
|
|
258
|
+
}
|
|
259
|
+
/** The next token: the biggest logit that is not refused. Greedy, because a child reading a known text needs no imagination. */
|
|
260
|
+
export function nextToken(logits, suppressed) {
|
|
261
|
+
let best = -1, bestValue = -Infinity;
|
|
262
|
+
for (let id = 0; id < logits.length; id++) {
|
|
263
|
+
if (suppressed?.has(id))
|
|
264
|
+
continue;
|
|
265
|
+
const value = logits[id];
|
|
266
|
+
if (value > bestValue) {
|
|
267
|
+
bestValue = value;
|
|
268
|
+
best = id;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return best;
|
|
272
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { type OnnxRuntime } from './onnx-runtime.js';
|
|
3
|
+
/** What the engine gets back: something that turns samples into words. */
|
|
4
|
+
export interface ReadingTranscriber {
|
|
5
|
+
/** 16 kHz mono samples in, the child's words out. Empty when the model answered nothing. */
|
|
6
|
+
transcribe(samples: Float32Array): Promise<string>;
|
|
7
|
+
}
|
|
8
|
+
export interface ReadingRuntimeDeps {
|
|
9
|
+
/** The page's address, which `heavy/` is resolved against. */
|
|
10
|
+
readonly base: string;
|
|
11
|
+
/** The child's language tag (`pt-BR`). A language the project has no model for is refused BY NAME. */
|
|
12
|
+
readonly language: string;
|
|
13
|
+
/**
|
|
14
|
+
* The `fetch` of the realm this runs in, lent by whoever opens the reading (ADR-0232 D4): the root's window where there is
|
|
15
|
+
* no worker, the worker's own scope inside `platform/reading-worker`. REQUIRED — this module reaches no global.
|
|
16
|
+
*/
|
|
17
|
+
readonly fetch: (url: string) => Promise<Response>;
|
|
18
|
+
/** Injected so a case can replay a real model's numbers without its 378 MiB; the default is the delivery's runtime. */
|
|
19
|
+
readonly ort?: OnnxRuntime;
|
|
20
|
+
readonly importModule?: (url: string) => Promise<unknown>;
|
|
21
|
+
/** A ceiling on the answer, so a model that loops does not hold the microphone for ever. */
|
|
22
|
+
readonly maxTokens?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function loadReadingRuntime(d: ReadingRuntimeDeps): Promise<ReadingTranscriber>;
|