sci2-ts 0.0.0-stage → 0.2.1
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/CHANGELOG.md +72 -0
- package/LICENSE +21 -0
- package/README.md +113 -2
- package/apps/viewer/editor.html +43 -0
- package/apps/viewer/explore.html +27 -0
- package/apps/viewer/index.html +26 -0
- package/apps/viewer/package.json +27 -0
- package/apps/viewer/play.html +26 -0
- package/apps/viewer/plugin.d.ts +18 -0
- package/apps/viewer/plugin.js +75 -0
- package/apps/viewer/plugin.ts +84 -0
- package/apps/viewer/src/adlib.d.ts +20 -0
- package/apps/viewer/src/adlib.js +55 -0
- package/apps/viewer/src/adlib.ts +62 -0
- package/apps/viewer/src/app.d.ts +5 -0
- package/apps/viewer/src/app.js +5 -0
- package/apps/viewer/src/app.ts +5 -0
- package/apps/viewer/src/asset-index.d.ts +22 -0
- package/apps/viewer/src/asset-index.js +74 -0
- package/apps/viewer/src/asset-index.ts +90 -0
- package/apps/viewer/src/audio.d.ts +28 -0
- package/apps/viewer/src/audio.js +102 -0
- package/apps/viewer/src/audio.ts +110 -0
- package/apps/viewer/src/editor-handles.d.ts +63 -0
- package/apps/viewer/src/editor-handles.js +332 -0
- package/apps/viewer/src/editor-handles.ts +368 -0
- package/apps/viewer/src/editor-languages.d.ts +23 -0
- package/apps/viewer/src/editor-languages.js +157 -0
- package/apps/viewer/src/editor-languages.ts +135 -0
- package/apps/viewer/src/editor.css +213 -0
- package/apps/viewer/src/editor.d.ts +1 -0
- package/apps/viewer/src/editor.js +393 -0
- package/apps/viewer/src/editor.ts +412 -0
- package/apps/viewer/src/explore/classes.d.ts +7 -0
- package/apps/viewer/src/explore/classes.js +63 -0
- package/apps/viewer/src/explore/classes.ts +61 -0
- package/apps/viewer/src/explore/context.d.ts +33 -0
- package/apps/viewer/src/explore/context.js +96 -0
- package/apps/viewer/src/explore/context.ts +124 -0
- package/apps/viewer/src/explore/explore.css +93 -0
- package/apps/viewer/src/explore/explore.d.ts +1 -0
- package/apps/viewer/src/explore/explore.js +102 -0
- package/apps/viewer/src/explore/explore.ts +104 -0
- package/apps/viewer/src/explore/inspect.d.ts +5 -0
- package/apps/viewer/src/explore/inspect.js +208 -0
- package/apps/viewer/src/explore/inspect.ts +279 -0
- package/apps/viewer/src/explore/live.d.ts +22 -0
- package/apps/viewer/src/explore/live.js +318 -0
- package/apps/viewer/src/explore/live.ts +365 -0
- package/apps/viewer/src/explore/map.d.ts +11 -0
- package/apps/viewer/src/explore/map.js +182 -0
- package/apps/viewer/src/explore/map.ts +193 -0
- package/apps/viewer/src/files.d.ts +21 -0
- package/apps/viewer/src/files.js +69 -0
- package/apps/viewer/src/files.ts +72 -0
- package/apps/viewer/src/game-profile.d.ts +19 -0
- package/apps/viewer/src/game-profile.js +4 -0
- package/apps/viewer/src/game-profile.ts +26 -0
- package/apps/viewer/src/main.d.ts +1 -0
- package/apps/viewer/src/main.js +473 -0
- package/apps/viewer/src/main.ts +527 -0
- package/apps/viewer/src/music.d.ts +14 -0
- package/apps/viewer/src/music.js +32 -0
- package/apps/viewer/src/music.ts +34 -0
- package/apps/viewer/src/opl-worklet.d.ts +1 -0
- package/apps/viewer/src/opl-worklet.js +47 -0
- package/apps/viewer/src/opl-worklet.ts +55 -0
- package/apps/viewer/src/overlay.d.ts +31 -0
- package/apps/viewer/src/overlay.js +103 -0
- package/apps/viewer/src/overlay.ts +123 -0
- package/apps/viewer/src/play.css +14 -0
- package/apps/viewer/src/play.d.ts +1 -0
- package/apps/viewer/src/play.js +179 -0
- package/apps/viewer/src/play.ts +177 -0
- package/apps/viewer/src/saves.d.ts +10 -0
- package/apps/viewer/src/saves.js +50 -0
- package/apps/viewer/src/saves.ts +51 -0
- package/apps/viewer/src/session.d.ts +91 -0
- package/apps/viewer/src/session.js +301 -0
- package/apps/viewer/src/session.ts +321 -0
- package/apps/viewer/src/sound-preview.d.ts +7 -0
- package/apps/viewer/src/sound-preview.js +214 -0
- package/apps/viewer/src/sound-preview.ts +228 -0
- package/apps/viewer/src/style.css +64 -0
- package/apps/viewer/vite.config.ts +25 -0
- package/bin/sci-ts.mjs +9 -0
- package/lib/994.sc +49 -0
- package/lib/995.sc +53 -0
- package/lib/996.sc +299 -0
- package/lib/997.sc +198 -0
- package/lib/998.sc +338 -0
- package/lib/999.sc +189 -0
- package/lib/resources.d.ts +2 -0
- package/lib/resources.js +84 -0
- package/lib/resources.ts +88 -0
- package/lib/system.sh +62 -0
- package/package.json +75 -4
- package/packages/content/room.schema.json +267 -0
- package/packages/content/src/index.d.ts +3 -0
- package/packages/content/src/index.js +3 -0
- package/packages/content/src/index.ts +3 -0
- package/packages/content/src/room.d.ts +111 -0
- package/packages/content/src/room.js +886 -0
- package/packages/content/src/room.ts +1011 -0
- package/packages/content/src/target.d.ts +80 -0
- package/packages/content/src/target.js +1 -0
- package/packages/content/src/target.ts +60 -0
- package/packages/content/src/yarn.d.ts +90 -0
- package/packages/content/src/yarn.js +214 -0
- package/packages/content/src/yarn.ts +261 -0
- package/packages/sci/src/analysis/game.d.ts +90 -0
- package/packages/sci/src/analysis/game.js +221 -0
- package/packages/sci/src/analysis/game.ts +305 -0
- package/packages/sci/src/analysis/runtime.d.ts +58 -0
- package/packages/sci/src/analysis/runtime.js +133 -0
- package/packages/sci/src/analysis/runtime.ts +163 -0
- package/packages/sci/src/audio/adlib.d.ts +73 -0
- package/packages/sci/src/audio/adlib.js +280 -0
- package/packages/sci/src/audio/adlib.ts +329 -0
- package/packages/sci/src/audio/index.d.ts +43 -0
- package/packages/sci/src/audio/index.js +99 -0
- package/packages/sci/src/audio/index.ts +122 -0
- package/packages/sci/src/audio/midi.d.ts +92 -0
- package/packages/sci/src/audio/midi.js +242 -0
- package/packages/sci/src/audio/midi.ts +262 -0
- package/packages/sci/src/audio/opl2.d.ts +74 -0
- package/packages/sci/src/audio/opl2.js +283 -0
- package/packages/sci/src/audio/opl2.ts +283 -0
- package/packages/sci/src/audio/sol.d.ts +32 -0
- package/packages/sci/src/audio/sol.js +81 -0
- package/packages/sci/src/audio/sol.ts +100 -0
- package/packages/sci/src/audio/write.d.ts +35 -0
- package/packages/sci/src/audio/write.js +91 -0
- package/packages/sci/src/audio/write.ts +108 -0
- package/packages/sci/src/gfx/cel.d.ts +36 -0
- package/packages/sci/src/gfx/cel.js +82 -0
- package/packages/sci/src/gfx/cel.ts +110 -0
- package/packages/sci/src/gfx/palette.d.ts +24 -0
- package/packages/sci/src/gfx/palette.js +49 -0
- package/packages/sci/src/gfx/palette.ts +56 -0
- package/packages/sci/src/gfx/pic-file.d.ts +46 -0
- package/packages/sci/src/gfx/pic-file.js +114 -0
- package/packages/sci/src/gfx/pic-file.ts +155 -0
- package/packages/sci/src/gfx/pic.d.ts +27 -0
- package/packages/sci/src/gfx/pic.js +34 -0
- package/packages/sci/src/gfx/pic.ts +53 -0
- package/packages/sci/src/gfx/quantize.d.ts +42 -0
- package/packages/sci/src/gfx/quantize.js +199 -0
- package/packages/sci/src/gfx/quantize.ts +218 -0
- package/packages/sci/src/gfx/view-file.d.ts +62 -0
- package/packages/sci/src/gfx/view-file.js +182 -0
- package/packages/sci/src/gfx/view-file.ts +233 -0
- package/packages/sci/src/gfx/view.d.ts +28 -0
- package/packages/sci/src/gfx/view.js +45 -0
- package/packages/sci/src/gfx/view.ts +62 -0
- package/packages/sci/src/index.d.ts +38 -0
- package/packages/sci/src/index.js +38 -0
- package/packages/sci/src/index.ts +38 -0
- package/packages/sci/src/motion/pathfinding.d.ts +40 -0
- package/packages/sci/src/motion/pathfinding.js +140 -0
- package/packages/sci/src/motion/pathfinding.ts +147 -0
- package/packages/sci/src/resource/archive.d.ts +16 -0
- package/packages/sci/src/resource/archive.js +51 -0
- package/packages/sci/src/resource/archive.ts +56 -0
- package/packages/sci/src/resource/lzs.d.ts +12 -0
- package/packages/sci/src/resource/lzs.js +64 -0
- package/packages/sci/src/resource/lzs.ts +66 -0
- package/packages/sci/src/resource/manager.d.ts +45 -0
- package/packages/sci/src/resource/manager.js +158 -0
- package/packages/sci/src/resource/manager.ts +184 -0
- package/packages/sci/src/resource/map.d.ts +18 -0
- package/packages/sci/src/resource/map.js +34 -0
- package/packages/sci/src/resource/map.ts +44 -0
- package/packages/sci/src/resource/types.d.ts +37 -0
- package/packages/sci/src/resource/types.js +45 -0
- package/packages/sci/src/resource/types.ts +57 -0
- package/packages/sci/src/script/assembly.d.ts +67 -0
- package/packages/sci/src/script/assembly.js +748 -0
- package/packages/sci/src/script/assembly.ts +714 -0
- package/packages/sci/src/script/compiler.d.ts +75 -0
- package/packages/sci/src/script/compiler.js +993 -0
- package/packages/sci/src/script/compiler.ts +1040 -0
- package/packages/sci/src/script/disasm.d.ts +81 -0
- package/packages/sci/src/script/disasm.js +318 -0
- package/packages/sci/src/script/disasm.ts +323 -0
- package/packages/sci/src/script/kernel-names.d.ts +6 -0
- package/packages/sci/src/script/kernel-names.js +165 -0
- package/packages/sci/src/script/kernel-names.ts +165 -0
- package/packages/sci/src/script/opcodes.d.ts +22 -0
- package/packages/sci/src/script/opcodes.js +45 -0
- package/packages/sci/src/script/opcodes.ts +72 -0
- package/packages/sci/src/script/script.d.ts +56 -0
- package/packages/sci/src/script/script.js +63 -0
- package/packages/sci/src/script/script.ts +111 -0
- package/packages/sci/src/script/sexpr.d.ts +45 -0
- package/packages/sci/src/script/sexpr.js +151 -0
- package/packages/sci/src/script/sexpr.ts +139 -0
- package/packages/sci/src/script/vocab.d.ts +15 -0
- package/packages/sci/src/script/vocab.js +44 -0
- package/packages/sci/src/script/vocab.ts +48 -0
- package/packages/sci/src/script/world.d.ts +25 -0
- package/packages/sci/src/script/world.js +65 -0
- package/packages/sci/src/script/world.ts +70 -0
- package/packages/sci/src/text/font.d.ts +37 -0
- package/packages/sci/src/text/font.js +103 -0
- package/packages/sci/src/text/font.ts +125 -0
- package/packages/sci/src/text/message.d.ts +69 -0
- package/packages/sci/src/text/message.js +171 -0
- package/packages/sci/src/text/message.ts +205 -0
- package/packages/sci/src/vm/kernels/arrays.d.ts +19 -0
- package/packages/sci/src/vm/kernels/arrays.js +182 -0
- package/packages/sci/src/vm/kernels/arrays.ts +164 -0
- package/packages/sci/src/vm/kernels/audio.d.ts +70 -0
- package/packages/sci/src/vm/kernels/audio.js +347 -0
- package/packages/sci/src/vm/kernels/audio.ts +349 -0
- package/packages/sci/src/vm/kernels/core.d.ts +5 -0
- package/packages/sci/src/vm/kernels/core.js +87 -0
- package/packages/sci/src/vm/kernels/core.ts +91 -0
- package/packages/sci/src/vm/kernels/graphics.d.ts +141 -0
- package/packages/sci/src/vm/kernels/graphics.js +467 -0
- package/packages/sci/src/vm/kernels/graphics.ts +502 -0
- package/packages/sci/src/vm/kernels/index.d.ts +8 -0
- package/packages/sci/src/vm/kernels/index.js +29 -0
- package/packages/sci/src/vm/kernels/index.ts +32 -0
- package/packages/sci/src/vm/kernels/lists.d.ts +6 -0
- package/packages/sci/src/vm/kernels/lists.js +127 -0
- package/packages/sci/src/vm/kernels/lists.ts +111 -0
- package/packages/sci/src/vm/kernels/motion.d.ts +7 -0
- package/packages/sci/src/vm/kernels/motion.js +204 -0
- package/packages/sci/src/vm/kernels/motion.ts +194 -0
- package/packages/sci/src/vm/kernels/palette.d.ts +82 -0
- package/packages/sci/src/vm/kernels/palette.js +292 -0
- package/packages/sci/src/vm/kernels/palette.ts +303 -0
- package/packages/sci/src/vm/kernels/saves.d.ts +45 -0
- package/packages/sci/src/vm/kernels/saves.js +123 -0
- package/packages/sci/src/vm/kernels/saves.ts +149 -0
- package/packages/sci/src/vm/kernels/system.d.ts +62 -0
- package/packages/sci/src/vm/kernels/system.js +110 -0
- package/packages/sci/src/vm/kernels/system.ts +128 -0
- package/packages/sci/src/vm/kernels/text.d.ts +13 -0
- package/packages/sci/src/vm/kernels/text.js +338 -0
- package/packages/sci/src/vm/kernels/text.ts +328 -0
- package/packages/sci/src/vm/memory.d.ts +138 -0
- package/packages/sci/src/vm/memory.js +168 -0
- package/packages/sci/src/vm/memory.ts +266 -0
- package/packages/sci/src/vm/replace.d.ts +11 -0
- package/packages/sci/src/vm/replace.js +14 -0
- package/packages/sci/src/vm/replace.ts +16 -0
- package/packages/sci/src/vm/savegame.d.ts +89 -0
- package/packages/sci/src/vm/savegame.js +161 -0
- package/packages/sci/src/vm/savegame.ts +188 -0
- package/packages/sci/src/vm/value.d.ts +17 -0
- package/packages/sci/src/vm/value.js +12 -0
- package/packages/sci/src/vm/value.ts +25 -0
- package/packages/sci/src/vm/vm.d.ts +155 -0
- package/packages/sci/src/vm/vm.js +1053 -0
- package/packages/sci/src/vm/vm.ts +914 -0
- package/tools/adlib-render.d.ts +1 -0
- package/tools/adlib-render.js +49 -0
- package/tools/adlib-render.ts +42 -0
- package/tools/analyze.d.ts +1 -0
- package/tools/analyze.js +25 -0
- package/tools/analyze.ts +25 -0
- package/tools/art/build.d.ts +34 -0
- package/tools/art/build.js +175 -0
- package/tools/art/build.ts +153 -0
- package/tools/art.d.ts +1 -0
- package/tools/art.js +31 -0
- package/tools/art.ts +29 -0
- package/tools/asm.d.ts +1 -0
- package/tools/asm.js +61 -0
- package/tools/asm.ts +55 -0
- package/tools/cli.d.ts +1 -0
- package/tools/cli.js +99 -0
- package/tools/cli.ts +100 -0
- package/tools/editor-server.d.ts +29 -0
- package/tools/editor-server.js +162 -0
- package/tools/editor-server.ts +176 -0
- package/tools/explore.d.ts +1 -0
- package/tools/explore.js +305 -0
- package/tools/explore.ts +250 -0
- package/tools/game/audio.d.ts +19 -0
- package/tools/game/audio.js +143 -0
- package/tools/game/audio.ts +130 -0
- package/tools/game/build.d.ts +46 -0
- package/tools/game/build.js +259 -0
- package/tools/game/build.ts +283 -0
- package/tools/game/defaults.d.ts +26 -0
- package/tools/game/defaults.js +82 -0
- package/tools/game/defaults.ts +83 -0
- package/tools/game/font.d.ts +3 -0
- package/tools/game/font.js +124 -0
- package/tools/game/font.ts +126 -0
- package/tools/game/kit.d.ts +7 -0
- package/tools/game/kit.js +7 -0
- package/tools/game/kit.ts +10 -0
- package/tools/game/new.d.ts +8 -0
- package/tools/game/new.js +39 -0
- package/tools/game/new.ts +38 -0
- package/tools/game/target.d.ts +7 -0
- package/tools/game/target.js +41 -0
- package/tools/game/target.ts +42 -0
- package/tools/game/template/resources.ts.tmpl +43 -0
- package/tools/game/template/rooms/1.room.yaml +9 -0
- package/tools/game/template/rooms/1.yarn +17 -0
- package/tools/game/template/scripts/0.sc +24 -0
- package/tools/game.d.ts +1 -0
- package/tools/game.js +77 -0
- package/tools/game.ts +70 -0
- package/tools/mod-build.d.ts +28 -0
- package/tools/mod-build.js +173 -0
- package/tools/mod-build.ts +179 -0
- package/tools/mod-target.d.ts +16 -0
- package/tools/mod-target.js +26 -0
- package/tools/mod-target.ts +32 -0
- package/tools/mod.d.ts +1 -0
- package/tools/mod.js +47 -0
- package/tools/mod.ts +45 -0
- package/tools/msg.d.ts +1 -0
- package/tools/msg.js +40 -0
- package/tools/msg.ts +36 -0
- package/tools/node-files.d.ts +3 -0
- package/tools/node-files.js +34 -0
- package/tools/node-files.ts +33 -0
- package/tools/patches.d.ts +3 -0
- package/tools/patches.js +13 -0
- package/tools/patches.ts +13 -0
- package/tools/pic-build.d.ts +9 -0
- package/tools/pic-build.js +26 -0
- package/tools/pic-build.ts +27 -0
- package/tools/pic.d.ts +1 -0
- package/tools/pic.js +64 -0
- package/tools/pic.ts +61 -0
- package/tools/play.d.ts +1 -0
- package/tools/play.js +54 -0
- package/tools/play.ts +54 -0
- package/tools/png.d.ts +16 -0
- package/tools/png.js +124 -0
- package/tools/png.ts +123 -0
- package/tools/res.d.ts +1 -0
- package/tools/res.js +48 -0
- package/tools/res.ts +50 -0
- package/tools/scenario.d.ts +1 -0
- package/tools/scenario.js +74 -0
- package/tools/scenario.ts +80 -0
- package/tools/site/build.d.ts +8 -0
- package/tools/site/build.js +110 -0
- package/tools/site/build.ts +116 -0
- package/tools/site/markdown.d.ts +11 -0
- package/tools/site/markdown.js +88 -0
- package/tools/site/markdown.ts +84 -0
- package/tools/view-build.d.ts +34 -0
- package/tools/view-build.js +47 -0
- package/tools/view-build.ts +71 -0
- package/tools/view.d.ts +1 -0
- package/tools/view.js +54 -0
- package/tools/view.ts +52 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { ArrayType, SciArray } from "../memory.ts";
|
|
2
|
+
import type { KernelFn, Vm } from "../vm.ts";
|
|
3
|
+
import { NULL, fromInt, isNumber, segmentOf, toSigned, type Value } from "../value.ts";
|
|
4
|
+
|
|
5
|
+
const arrayTypes: ArrayType[] = ["int16", "id", "byte", "string"];
|
|
6
|
+
|
|
7
|
+
/** Resolves an array argument: an array ref, or a String/Array object holding one in `data`. */
|
|
8
|
+
function resolve(vm: Vm, v: Value): SciArray | undefined {
|
|
9
|
+
const direct = vm.memory.array(v);
|
|
10
|
+
if (direct) return direct;
|
|
11
|
+
const obj = vm.memory.object(v);
|
|
12
|
+
if (obj) {
|
|
13
|
+
const data = vm.getProp(obj, "data");
|
|
14
|
+
if (data !== undefined) return vm.memory.array(data);
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const store = (a: SciArray, value: Value) => (a.type === "byte" || a.type === "string" ? value & 0xff : value);
|
|
20
|
+
|
|
21
|
+
function setElements(vm: Vm, [ref = 0, index = 0, ...values]: Value[]): Value {
|
|
22
|
+
const a = resolve(vm, ref);
|
|
23
|
+
if (!a) return ref;
|
|
24
|
+
const start = toSigned(index);
|
|
25
|
+
values.forEach((v, i) => {
|
|
26
|
+
a.data[start + i] = store(a, v);
|
|
27
|
+
});
|
|
28
|
+
for (let i = 0; i < a.data.length; i++) a.data[i] ??= 0;
|
|
29
|
+
return ref;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Byte values of a string argument: literal in a script heap or an array. */
|
|
33
|
+
function chars(vm: Vm, v: Value): number[] {
|
|
34
|
+
const a = resolve(vm, v);
|
|
35
|
+
if (a) {
|
|
36
|
+
const out: number[] = [];
|
|
37
|
+
for (const c of a.data) {
|
|
38
|
+
if ((c & 0xff) === 0) break;
|
|
39
|
+
out.push(c & 0xff);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
if (!v) return [];
|
|
44
|
+
return [...vm.memory.string(v)].map((c) => c.charCodeAt(0));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const str = (vm: Vm, v: Value) => String.fromCharCode(...chars(vm, v));
|
|
48
|
+
|
|
49
|
+
function newString(vm: Vm, s: string): Value {
|
|
50
|
+
const ref = vm.memory.newArray("string", s.length + 1);
|
|
51
|
+
const a = vm.memory.array(ref)!;
|
|
52
|
+
for (let i = 0; i < s.length; i++) a.data[i] = s.charCodeAt(i) & 0xff;
|
|
53
|
+
return ref;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writeString(vm: Vm, ref: Value, s: string): Value {
|
|
57
|
+
const a = resolve(vm, ref);
|
|
58
|
+
if (!a) return ref;
|
|
59
|
+
a.data.length = 0;
|
|
60
|
+
for (let i = 0; i < s.length; i++) a.data.push(s.charCodeAt(i) & 0xff);
|
|
61
|
+
a.data.push(0);
|
|
62
|
+
return ref;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function copy(vm: Vm, [dst = 0, dstIndex = 0, src = 0, srcIndex = 0, count = 0xffff]: Value[]): Value {
|
|
66
|
+
const d = resolve(vm, dst);
|
|
67
|
+
if (!d) return dst;
|
|
68
|
+
const s = resolve(vm, src);
|
|
69
|
+
const from = s ? s.data : chars(vm, src).concat(0);
|
|
70
|
+
const start = toSigned(srcIndex);
|
|
71
|
+
let n = toSigned(count);
|
|
72
|
+
if (n < 0) n = from.length - start;
|
|
73
|
+
for (let i = 0; i < n && start + i < from.length; i++) d.data[toSigned(dstIndex) + i] = store(d, from[start + i]!);
|
|
74
|
+
for (let i = 0; i < d.data.length; i++) d.data[i] ??= 0;
|
|
75
|
+
return dst;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** printf-style formatting, as SCI's StringFormat understands it. */
|
|
79
|
+
export function sciFormat(vm: Vm, format: string, args: Value[]): string {
|
|
80
|
+
let argi = 0;
|
|
81
|
+
return format.replace(/%(-?)(0?)(\d*)([dsuxXc%])/g, (_m, left: string, zero: string, width: string, conv: string) => {
|
|
82
|
+
if (conv === "%") return "%";
|
|
83
|
+
const arg = args[argi++] ?? 0;
|
|
84
|
+
let out: string;
|
|
85
|
+
switch (conv) {
|
|
86
|
+
case "d": out = String(toSigned(arg)); break;
|
|
87
|
+
case "u": out = String(arg & 0xffff); break;
|
|
88
|
+
case "x": out = (arg & 0xffff).toString(16); break;
|
|
89
|
+
case "X": out = (arg & 0xffff).toString(16).toUpperCase(); break;
|
|
90
|
+
case "c": out = String.fromCharCode(arg & 0xff); break;
|
|
91
|
+
default: out = isNumber(arg) && arg !== 0 ? String(toSigned(arg)) : str(vm, arg);
|
|
92
|
+
}
|
|
93
|
+
const w = Number(width || 0);
|
|
94
|
+
return left ? out.padEnd(w) : out.padStart(w, zero ? "0" : " ");
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const arraySubops: Record<number, KernelFn> = {
|
|
99
|
+
0: (vm, [size = 0, type = 0]) => vm.memory.newArray(arrayTypes[type] ?? "int16", size),
|
|
100
|
+
1: (vm, [ref = 0]) => resolve(vm, ref)?.data.length ?? 0,
|
|
101
|
+
2: (vm, [ref = 0, index = 0]) => resolve(vm, ref)?.data[toSigned(index)] ?? 0,
|
|
102
|
+
3: setElements,
|
|
103
|
+
4: (vm, [ref = 0]) => {
|
|
104
|
+
if (vm.memory.array(ref)) vm.memory.free(segmentOf(ref));
|
|
105
|
+
return 0;
|
|
106
|
+
},
|
|
107
|
+
5: (vm, [ref = 0, index = 0, count = 0, value = 0]) => {
|
|
108
|
+
const a = resolve(vm, ref);
|
|
109
|
+
if (a) for (let i = 0; i < count; i++) a.data[toSigned(index) + i] = store(a, value);
|
|
110
|
+
if (a) for (let i = 0; i < a.data.length; i++) a.data[i] ??= 0;
|
|
111
|
+
return ref;
|
|
112
|
+
},
|
|
113
|
+
6: copy,
|
|
114
|
+
8: (vm, [ref = 0]) => {
|
|
115
|
+
const a = resolve(vm, ref);
|
|
116
|
+
if (!a) return NULL;
|
|
117
|
+
const dup = vm.memory.newArray(a.type, 0);
|
|
118
|
+
vm.memory.array(dup)!.data = a.data.slice();
|
|
119
|
+
return dup;
|
|
120
|
+
},
|
|
121
|
+
9: (vm, [ref = 0]) => {
|
|
122
|
+
const obj = vm.memory.object(ref);
|
|
123
|
+
return obj ? (vm.getProp(obj, "data") ?? ref) : ref;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const stringSubops: Record<number, KernelFn> = {
|
|
128
|
+
...arraySubops,
|
|
129
|
+
0: (vm, [size = 0]) => vm.memory.newArray("string", size),
|
|
130
|
+
// Duplicate: also takes a literal string from a script's heap (e.g. `Prints "text"`).
|
|
131
|
+
8: (vm, args) => (resolve(vm, args[0] ?? 0) ? arraySubops[8]!(vm, args) : newString(vm, str(vm, args[0] ?? 0))),
|
|
132
|
+
7: (vm, [a = 0, b = 0, n]) => {
|
|
133
|
+
let x = str(vm, a), y = str(vm, b);
|
|
134
|
+
if (n !== undefined) (x = x.slice(0, n), (y = y.slice(0, n)));
|
|
135
|
+
return fromInt(x < y ? -1 : x > y ? 1 : 0);
|
|
136
|
+
},
|
|
137
|
+
10: (vm, [s = 0]) => chars(vm, s).length,
|
|
138
|
+
11: (vm, [format = 0, ...args]) => newString(vm, sciFormat(vm, str(vm, format), args)),
|
|
139
|
+
12: (vm, [dst = 0, format = 0, ...args]) => writeString(vm, dst, sciFormat(vm, str(vm, format), args)),
|
|
140
|
+
13: (vm, [s = 0]) => fromInt(parseInt(str(vm, s), 10) || 0),
|
|
141
|
+
14: (vm, [s = 0, flags = 0]) => {
|
|
142
|
+
let t = str(vm, s);
|
|
143
|
+
if (flags & 1) t = t.trimStart();
|
|
144
|
+
if (flags & 2) t = t.trimEnd();
|
|
145
|
+
if (flags & 4) t = t.replace(/ /g, "");
|
|
146
|
+
return writeString(vm, s, t);
|
|
147
|
+
},
|
|
148
|
+
15: (vm, [s = 0]) => writeString(vm, s, str(vm, s).toUpperCase()),
|
|
149
|
+
16: (vm, [s = 0]) => writeString(vm, s, str(vm, s).toLowerCase()),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const dispatch = (name: string, table: Record<number, KernelFn>): KernelFn => (vm, [op = 0, ...args]) => {
|
|
153
|
+
const fn = table[op];
|
|
154
|
+
if (!fn) throw new Error(`${name} subop ${op} not implemented`);
|
|
155
|
+
return fn(vm, args);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** SCI32 dynamic arrays and strings. */
|
|
159
|
+
export const arrayKernels: Record<string, KernelFn> = {
|
|
160
|
+
Array: dispatch("Array", arraySubops),
|
|
161
|
+
String: dispatch("String", stringSubops),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const stringHelpers = { str, newString, writeString, resolve };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AudioIndex, type AudioClip, type AudioOutput } from "../../audio/index.ts";
|
|
2
|
+
import { SongPlayer, type MidiOutput, type Song } from "../../audio/midi.ts";
|
|
3
|
+
import type { KernelFn, Vm } from "../vm.ts";
|
|
4
|
+
import { type Value } from "../value.ts";
|
|
5
|
+
interface Channel {
|
|
6
|
+
clip: AudioClip;
|
|
7
|
+
/** VM clock (ms) when playback started; undefined while waiting to start. */
|
|
8
|
+
start: number | undefined;
|
|
9
|
+
pausedAt: number | undefined;
|
|
10
|
+
ticks: number;
|
|
11
|
+
loop: boolean;
|
|
12
|
+
volume: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Digital audio state. Durations are known synchronously from the index, so the scripts'
|
|
16
|
+
* position polling works off the VM clock; the host's AudioOutput does the actual playback
|
|
17
|
+
* (fetching and decoding clips asynchronously).
|
|
18
|
+
*/
|
|
19
|
+
export declare class AudioState {
|
|
20
|
+
private readonly vm;
|
|
21
|
+
readonly index: AudioIndex;
|
|
22
|
+
readonly channels: Map<string, Channel>;
|
|
23
|
+
output: AudioOutput | undefined;
|
|
24
|
+
/** MIDI music goes here (a SoundFont synth or the AdLib driver). */
|
|
25
|
+
midi: MidiOutput | undefined;
|
|
26
|
+
/** Which track of each sound resource to play: General MIDI or AdLib. */
|
|
27
|
+
device: number;
|
|
28
|
+
readonly songs: Map<number, SongPlayer>;
|
|
29
|
+
private readonly songNumbers;
|
|
30
|
+
private readonly songCache;
|
|
31
|
+
masterVolume: number;
|
|
32
|
+
/**
|
|
33
|
+
* Ticks to report when a speech clip doesn't exist (new dialogue in a mod). The game times
|
|
34
|
+
* a spoken line by its clip, so a missing clip would flash the text for a second; hosts
|
|
35
|
+
* that load mods set this (see `readingTime`). Unset, missing clips report 0 like SSCI.
|
|
36
|
+
*/
|
|
37
|
+
missingSpeechTicks?: (module: number, noun: number, verb: number, cond: number, seq: number) => number;
|
|
38
|
+
constructor(vm: Vm);
|
|
39
|
+
ticks(): number;
|
|
40
|
+
song(number: number, device?: number): Song | undefined;
|
|
41
|
+
startSong(obj: Value, number: number, loop: boolean, volume: number, position?: number): void;
|
|
42
|
+
playingSongs(): {
|
|
43
|
+
object: Value;
|
|
44
|
+
number: number;
|
|
45
|
+
player: SongPlayer;
|
|
46
|
+
}[];
|
|
47
|
+
/** Silences all music, effects and speech (restart / restore). */
|
|
48
|
+
stopEverything(): void;
|
|
49
|
+
stopSong(obj: Value): void;
|
|
50
|
+
/**
|
|
51
|
+
* Switches music to another device (track) and output, carrying every playing song over
|
|
52
|
+
* at its current position.
|
|
53
|
+
*/
|
|
54
|
+
setMusicDevice(device: number, output: MidiOutput | undefined): void;
|
|
55
|
+
play(key: string, clip: AudioClip, loop: boolean, volume: number, autoStart?: boolean): number;
|
|
56
|
+
start(key: string): void;
|
|
57
|
+
stop(key: string): void;
|
|
58
|
+
stopAll(prefix?: string): void;
|
|
59
|
+
/** Elapsed ticks, or undefined once the clip has finished (non-looping). */
|
|
60
|
+
position(key: string): number | undefined;
|
|
61
|
+
pause(key: string, paused: boolean): void;
|
|
62
|
+
}
|
|
63
|
+
export declare const audio: (vm: Vm) => AudioState;
|
|
64
|
+
export declare const audioKernels: Record<string, KernelFn>;
|
|
65
|
+
/**
|
|
66
|
+
* A `missingSpeechTicks` that gives a line without a recording as long as it takes to read:
|
|
67
|
+
* ~15 characters a second, at least two seconds. Reads the text from the message resources.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readingTime(vm: Vm): (module: number, noun: number, verb: number, cond: number, seq: number) => number;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { AudioIndex, speechKey } from "../../audio/index.js";
|
|
2
|
+
import { SongPlayer, SoundDevice, parseSound } from "../../audio/midi.js";
|
|
3
|
+
import { ResourceType } from "../../resource/types.js";
|
|
4
|
+
import { findMessage, parseMessages } from "../../text/message.js";
|
|
5
|
+
import { toSigned } from "../value.js";
|
|
6
|
+
const NOT_PLAYING = 0xffff;
|
|
7
|
+
const MAX_VOLUME = 127;
|
|
8
|
+
/**
|
|
9
|
+
* Digital audio state. Durations are known synchronously from the index, so the scripts'
|
|
10
|
+
* position polling works off the VM clock; the host's AudioOutput does the actual playback
|
|
11
|
+
* (fetching and decoding clips asynchronously).
|
|
12
|
+
*/
|
|
13
|
+
export class AudioState {
|
|
14
|
+
vm;
|
|
15
|
+
index;
|
|
16
|
+
channels = new Map();
|
|
17
|
+
output;
|
|
18
|
+
/** MIDI music goes here (a SoundFont synth or the AdLib driver). */
|
|
19
|
+
midi;
|
|
20
|
+
/** Which track of each sound resource to play: General MIDI or AdLib. */
|
|
21
|
+
device = SoundDevice.GeneralMidi;
|
|
22
|
+
songs = new Map();
|
|
23
|
+
songNumbers = new Map();
|
|
24
|
+
songCache = new Map();
|
|
25
|
+
masterVolume = 15;
|
|
26
|
+
/**
|
|
27
|
+
* Ticks to report when a speech clip doesn't exist (new dialogue in a mod). The game times
|
|
28
|
+
* a spoken line by its clip, so a missing clip would flash the text for a second; hosts
|
|
29
|
+
* that load mods set this (see `readingTime`). Unset, missing clips report 0 like SSCI.
|
|
30
|
+
*/
|
|
31
|
+
missingSpeechTicks;
|
|
32
|
+
constructor(vm) {
|
|
33
|
+
this.vm = vm;
|
|
34
|
+
this.index = AudioIndex.build(vm.resources);
|
|
35
|
+
vm.frameHooks.push(() => {
|
|
36
|
+
const now = this.ticks();
|
|
37
|
+
for (const player of this.songs.values())
|
|
38
|
+
player.update(now);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
ticks() {
|
|
42
|
+
return Math.floor((this.vm.clock() * 60) / 1000);
|
|
43
|
+
}
|
|
44
|
+
song(number, device = this.device) {
|
|
45
|
+
const key = `${number}:${device}`;
|
|
46
|
+
if (!this.songCache.has(key)) {
|
|
47
|
+
const id = { type: ResourceType.Sound, number };
|
|
48
|
+
this.songCache.set(key, this.vm.resources.has(id) ? (parseSound(this.vm.resources.loadSync(id).data, device) ?? null) : null);
|
|
49
|
+
}
|
|
50
|
+
return this.songCache.get(key) ?? undefined;
|
|
51
|
+
}
|
|
52
|
+
startSong(obj, number, loop, volume, position = 0) {
|
|
53
|
+
const song = this.song(number);
|
|
54
|
+
if (!song)
|
|
55
|
+
return;
|
|
56
|
+
const player = new SongPlayer(song, this.midi, loop, volume, this.ticks());
|
|
57
|
+
if (position)
|
|
58
|
+
player.seek(position % Math.max(song.length, 1), this.ticks());
|
|
59
|
+
this.songs.set(obj, player);
|
|
60
|
+
this.songNumbers.set(obj, number);
|
|
61
|
+
}
|
|
62
|
+
playingSongs() {
|
|
63
|
+
return [...this.songs].map(([object, player]) => ({ object, number: this.songNumbers.get(object), player }));
|
|
64
|
+
}
|
|
65
|
+
/** Silences all music, effects and speech (restart / restore). */
|
|
66
|
+
stopEverything() {
|
|
67
|
+
for (const obj of [...this.songs.keys()])
|
|
68
|
+
this.stopSong(obj);
|
|
69
|
+
for (const key of [...this.channels.keys()])
|
|
70
|
+
this.stop(key);
|
|
71
|
+
}
|
|
72
|
+
stopSong(obj) {
|
|
73
|
+
this.songs.get(obj)?.stop();
|
|
74
|
+
this.songs.delete(obj);
|
|
75
|
+
this.songNumbers.delete(obj);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Switches music to another device (track) and output, carrying every playing song over
|
|
79
|
+
* at its current position.
|
|
80
|
+
*/
|
|
81
|
+
setMusicDevice(device, output) {
|
|
82
|
+
const now = this.ticks();
|
|
83
|
+
const playing = [...this.songs].map(([obj, player]) => ({ obj, player, pos: player.position(now) }));
|
|
84
|
+
for (const { player } of playing)
|
|
85
|
+
player.stop();
|
|
86
|
+
this.device = device;
|
|
87
|
+
this.midi = output;
|
|
88
|
+
output?.setMasterVolume?.(this.masterVolume);
|
|
89
|
+
for (const { obj, player, pos } of playing) {
|
|
90
|
+
const song = this.song(this.songNumbers.get(obj));
|
|
91
|
+
if (!song) {
|
|
92
|
+
this.songs.delete(obj);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const next = new SongPlayer(song, output, player.loop, player.volume, now);
|
|
96
|
+
next.seek(song.length ? pos % Math.max(song.length, 1) : 0, now);
|
|
97
|
+
this.songs.set(obj, next);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
play(key, clip, loop, volume, autoStart = true) {
|
|
101
|
+
this.stop(key);
|
|
102
|
+
const ticks = AudioIndex.ticks(clip);
|
|
103
|
+
this.channels.set(key, { clip, start: undefined, pausedAt: undefined, ticks, loop, volume });
|
|
104
|
+
if (autoStart)
|
|
105
|
+
this.start(key);
|
|
106
|
+
return ticks;
|
|
107
|
+
}
|
|
108
|
+
start(key) {
|
|
109
|
+
const ch = this.channels.get(key);
|
|
110
|
+
if (!ch || ch.start !== undefined)
|
|
111
|
+
return;
|
|
112
|
+
ch.start = this.vm.clock();
|
|
113
|
+
this.output?.play(key, ch.clip, { loop: ch.loop, volume: ch.volume / MAX_VOLUME });
|
|
114
|
+
}
|
|
115
|
+
stop(key) {
|
|
116
|
+
if (!this.channels.delete(key))
|
|
117
|
+
return;
|
|
118
|
+
this.output?.stop(key);
|
|
119
|
+
}
|
|
120
|
+
stopAll(prefix = "") {
|
|
121
|
+
for (const key of [...this.channels.keys()])
|
|
122
|
+
if (key.startsWith(prefix))
|
|
123
|
+
this.stop(key);
|
|
124
|
+
}
|
|
125
|
+
/** Elapsed ticks, or undefined once the clip has finished (non-looping). */
|
|
126
|
+
position(key) {
|
|
127
|
+
const ch = this.channels.get(key);
|
|
128
|
+
if (!ch)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (ch.start === undefined)
|
|
131
|
+
return 0;
|
|
132
|
+
const now = ch.pausedAt ?? this.vm.clock();
|
|
133
|
+
const elapsed = Math.floor(((now - ch.start) * 60) / 1000);
|
|
134
|
+
if (!ch.loop && elapsed >= ch.ticks) {
|
|
135
|
+
this.stop(key);
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
return ch.loop ? elapsed % ch.ticks : elapsed;
|
|
139
|
+
}
|
|
140
|
+
pause(key, paused) {
|
|
141
|
+
const ch = this.channels.get(key);
|
|
142
|
+
if (!ch || ch.start === undefined)
|
|
143
|
+
return;
|
|
144
|
+
if (paused && ch.pausedAt === undefined) {
|
|
145
|
+
ch.pausedAt = this.vm.clock();
|
|
146
|
+
this.output?.pause(key);
|
|
147
|
+
}
|
|
148
|
+
else if (!paused && ch.pausedAt !== undefined) {
|
|
149
|
+
ch.start += this.vm.clock() - ch.pausedAt;
|
|
150
|
+
ch.pausedAt = undefined;
|
|
151
|
+
this.output?.resume(key);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const stateOf = new WeakMap();
|
|
156
|
+
export const audio = (vm) => {
|
|
157
|
+
let a = stateOf.get(vm);
|
|
158
|
+
if (!a) {
|
|
159
|
+
const created = (a = new AudioState(vm));
|
|
160
|
+
stateOf.set(vm, created);
|
|
161
|
+
vm.hostRoots.push(() => created.songs.keys());
|
|
162
|
+
}
|
|
163
|
+
return a;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* DoAudio arguments name a clip either as an audio36 (speech) tuple
|
|
167
|
+
* `module, noun, verb, cond, seq, [loop], [volume]` or as a plain audio number
|
|
168
|
+
* `number, [loop], [volume]`.
|
|
169
|
+
*/
|
|
170
|
+
function resolveArgs(a, args) {
|
|
171
|
+
if (args.length >= 5) {
|
|
172
|
+
const [module = 0, noun = 0, verb = 0, cond = 0, seq = 0, loop, volume] = args;
|
|
173
|
+
return {
|
|
174
|
+
key: `aud:${speechKey(module, noun, verb, cond, seq)}`,
|
|
175
|
+
clip: a.index.speech.get(speechKey(module, noun, verb, cond, seq)),
|
|
176
|
+
loop: loop !== undefined && toSigned(loop) === -1,
|
|
177
|
+
volume: volume !== undefined && toSigned(volume) >= 0 ? Math.min(toSigned(volume), MAX_VOLUME) : MAX_VOLUME,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (args.length >= 1) {
|
|
181
|
+
const [number = 0, loop, volume] = args;
|
|
182
|
+
return {
|
|
183
|
+
key: `sfx:${number}`,
|
|
184
|
+
clip: a.index.effects.get(number),
|
|
185
|
+
loop: loop !== undefined && toSigned(loop) === -1,
|
|
186
|
+
volume: volume !== undefined && toSigned(volume) >= 0 ? Math.min(toSigned(volume), MAX_VOLUME) : MAX_VOLUME,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
const soundKey = (obj) => `snd:${obj.toString(16)}`;
|
|
192
|
+
const prop = (vm, obj, name) => toSigned(vm.getProp(obj, name) ?? 0);
|
|
193
|
+
export const audioKernels = {
|
|
194
|
+
DoAudio: (vm, [subop = 0, ...args]) => {
|
|
195
|
+
const a = audio(vm);
|
|
196
|
+
const target = resolveArgs(a, args);
|
|
197
|
+
switch (subop) {
|
|
198
|
+
case 0: return 0; // init
|
|
199
|
+
case 1: // wait-for-play: prepare without starting, return length
|
|
200
|
+
case 2: { // play
|
|
201
|
+
if (!target)
|
|
202
|
+
return a.channels.size;
|
|
203
|
+
if (!target.clip) {
|
|
204
|
+
const [module = 0, noun = 0, verb = 0, cond = 0, seq = 0] = args;
|
|
205
|
+
return args.length >= 5 && a.missingSpeechTicks ? a.missingSpeechTicks(module, noun, verb, cond, seq) & 0xffff : 0;
|
|
206
|
+
}
|
|
207
|
+
return a.play(target.key, target.clip, target.loop, target.volume, subop === 2);
|
|
208
|
+
}
|
|
209
|
+
case 3: // stop
|
|
210
|
+
if (target)
|
|
211
|
+
a.stop(target.key);
|
|
212
|
+
else
|
|
213
|
+
a.stopAll("aud:"), a.stopAll("sfx:");
|
|
214
|
+
return 0;
|
|
215
|
+
case 4:
|
|
216
|
+
case 5: // pause / resume
|
|
217
|
+
for (const key of target ? [target.key] : [...a.channels.keys()])
|
|
218
|
+
a.pause(key, subop === 4);
|
|
219
|
+
return 0;
|
|
220
|
+
case 6: { // position in ticks, -1 when not playing
|
|
221
|
+
const key = target?.key ?? [...a.channels.keys()].find((k) => k.startsWith("aud:"));
|
|
222
|
+
const pos = key === undefined ? undefined : a.position(key);
|
|
223
|
+
return pos === undefined ? NOT_PLAYING : pos & 0xffff;
|
|
224
|
+
}
|
|
225
|
+
case 8: return 0; // volume
|
|
226
|
+
case 9: return 1; // capability: audio present
|
|
227
|
+
case 10: return 16; // bit depth
|
|
228
|
+
case 12: return 1; // mixing
|
|
229
|
+
case 13: return 2; // channels
|
|
230
|
+
default: return 0;
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
// Sound objects. Numbers with a digital effect in RESOURCE.SFX play as samples;
|
|
234
|
+
// everything else is MIDI, sequenced here and sent to `midi` (a SoundFont synth).
|
|
235
|
+
DoSound: (vm, [subop = 0, obj = 0, arg = 0, , , stopAfter = 0]) => {
|
|
236
|
+
const a = audio(vm);
|
|
237
|
+
switch (subop) {
|
|
238
|
+
case 0: // master volume (get, or set to arg)
|
|
239
|
+
if (obj !== 0 || arg) {
|
|
240
|
+
a.masterVolume = obj & 0xf;
|
|
241
|
+
a.midi?.setMasterVolume?.(a.masterVolume);
|
|
242
|
+
}
|
|
243
|
+
return a.masterVolume;
|
|
244
|
+
case 3: return 32; // polyphony
|
|
245
|
+
case 4: return 1; // digital audio available
|
|
246
|
+
case 6: // init
|
|
247
|
+
vm.setProp(obj, "nodePtr", obj);
|
|
248
|
+
return 0;
|
|
249
|
+
case 7: // dispose
|
|
250
|
+
a.stop(soundKey(obj));
|
|
251
|
+
a.stopSong(obj);
|
|
252
|
+
vm.setProp(obj, "nodePtr", 0);
|
|
253
|
+
return 0;
|
|
254
|
+
case 8: { // play: a digital effect if RESOURCE.SFX has this number, else MIDI
|
|
255
|
+
const number = prop(vm, obj, "number");
|
|
256
|
+
const loop = prop(vm, obj, "loop") === -1;
|
|
257
|
+
const volume = vm.getProp(obj, "vol") === undefined ? MAX_VOLUME : prop(vm, obj, "vol");
|
|
258
|
+
a.stop(soundKey(obj));
|
|
259
|
+
a.stopSong(obj);
|
|
260
|
+
vm.setProp(obj, "signal", 0);
|
|
261
|
+
// SCI2 Sounds ask for updates (Sound::check -> updateCues, which reports the end and
|
|
262
|
+
// cues the client) only while `handle` is set; playing sets it, stopping clears it.
|
|
263
|
+
vm.setProp(obj, "handle", obj);
|
|
264
|
+
const clip = a.index.effects.get(number);
|
|
265
|
+
if (clip)
|
|
266
|
+
a.play(soundKey(obj), clip, loop, volume);
|
|
267
|
+
else
|
|
268
|
+
a.startSong(obj, number, loop, volume);
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
case 9: // stop
|
|
272
|
+
a.stop(soundKey(obj));
|
|
273
|
+
a.stopSong(obj);
|
|
274
|
+
vm.setProp(obj, "signal", NOT_PLAYING);
|
|
275
|
+
vm.setProp(obj, "handle", 0);
|
|
276
|
+
return 0;
|
|
277
|
+
case 10: { // pause (obj or 0 = all, arg = pause?)
|
|
278
|
+
for (const key of obj ? [soundKey(obj)] : [...a.channels.keys()].filter((k) => k.startsWith("snd:")))
|
|
279
|
+
a.pause(key, arg !== 0);
|
|
280
|
+
for (const player of obj ? [a.songs.get(obj)] : [...a.songs.values()]) {
|
|
281
|
+
if (arg !== 0)
|
|
282
|
+
player?.pause(a.ticks());
|
|
283
|
+
else
|
|
284
|
+
player?.resume(a.ticks());
|
|
285
|
+
}
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
case 11: { // fade(obj, targetVolume, ticks, step, stopAfter): fading out → stop
|
|
289
|
+
if (arg === 0 || stopAfter) {
|
|
290
|
+
a.stop(soundKey(obj));
|
|
291
|
+
a.stopSong(obj);
|
|
292
|
+
vm.setProp(obj, "signal", NOT_PLAYING);
|
|
293
|
+
}
|
|
294
|
+
else
|
|
295
|
+
a.songs.get(obj)?.setVolume(arg);
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
case 14: // set volume
|
|
299
|
+
a.output?.setVolume(soundKey(obj), arg / MAX_VOLUME);
|
|
300
|
+
a.songs.get(obj)?.setVolume(arg);
|
|
301
|
+
vm.setProp(obj, "vol", arg);
|
|
302
|
+
return 0;
|
|
303
|
+
case 16: { // set loop
|
|
304
|
+
vm.setProp(obj, "loop", arg);
|
|
305
|
+
const player = a.songs.get(obj);
|
|
306
|
+
if (player)
|
|
307
|
+
player.loop = toSigned(arg) === -1;
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
case 17: { // update cues: report song signals/cues and finished sounds to the script
|
|
311
|
+
if (a.channels.has(soundKey(obj)) && a.position(soundKey(obj)) === undefined)
|
|
312
|
+
vm.setProp(obj, "signal", NOT_PLAYING);
|
|
313
|
+
const player = a.songs.get(obj);
|
|
314
|
+
if (player) {
|
|
315
|
+
player.update(a.ticks());
|
|
316
|
+
if (player.signal) {
|
|
317
|
+
vm.setProp(obj, "signal", player.signal);
|
|
318
|
+
vm.setProp(obj, "dataInc", player.dataInc);
|
|
319
|
+
player.signal = 0;
|
|
320
|
+
}
|
|
321
|
+
if (player.finished)
|
|
322
|
+
a.stopSong(obj);
|
|
323
|
+
}
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
default:
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
DoSync: () => 0,
|
|
331
|
+
};
|
|
332
|
+
/**
|
|
333
|
+
* A `missingSpeechTicks` that gives a line without a recording as long as it takes to read:
|
|
334
|
+
* ~15 characters a second, at least two seconds. Reads the text from the message resources.
|
|
335
|
+
*/
|
|
336
|
+
export function readingTime(vm) {
|
|
337
|
+
const files = new Map();
|
|
338
|
+
return (module, noun, verb, cond, seq) => {
|
|
339
|
+
if (!files.has(module)) {
|
|
340
|
+
const id = { type: ResourceType.Message, number: module };
|
|
341
|
+
files.set(module, vm.resources.has(id) ? parseMessages(vm.resources.loadSync(id).data) : undefined);
|
|
342
|
+
}
|
|
343
|
+
const file = files.get(module);
|
|
344
|
+
const text = file ? findMessage(file, { noun, verb, cond, seq })?.text ?? "" : "";
|
|
345
|
+
return Math.max(120, text.length * 4);
|
|
346
|
+
};
|
|
347
|
+
}
|