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,228 @@
|
|
|
1
|
+
import { AudioIndex, ResourceType, SongPlayer, SoundDevice, parseAdLibBank, parseSound, type MidiOutput, type ResourceManager, type Song } from "../../../packages/sci/src/index.ts";
|
|
2
|
+
import { AdLibOutput } from "./adlib.ts";
|
|
3
|
+
import { WebAudioOutput } from "./audio.ts";
|
|
4
|
+
import { BASE, httpFiles } from "./files.ts";
|
|
5
|
+
import { SoundFontMidi } from "./music.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A mini player for sound resources in the resource viewer: the General MIDI track through
|
|
9
|
+
* the SoundFont, the AdLib track through the OPL2 emulator, or the digital version from
|
|
10
|
+
* RESOURCE.SFX when there is one. Music plays on a 60 Hz tick clock, as in the game.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface Audio {
|
|
14
|
+
out: WebAudioOutput;
|
|
15
|
+
gm: SoundFontMidi;
|
|
16
|
+
gmReady: Promise<void>;
|
|
17
|
+
/** Only for games with an AdLib instrument bank (patch 3). */
|
|
18
|
+
adlib: AdLibOutput | undefined;
|
|
19
|
+
index: AudioIndex;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let audio: Promise<Audio> | undefined;
|
|
23
|
+
const openAudio = (rm: ResourceManager) =>
|
|
24
|
+
(audio ??= (async () => {
|
|
25
|
+
await rm.preload(); // the audio maps and the AdLib bank are read synchronously
|
|
26
|
+
const out = new WebAudioOutput(httpFiles, (name) => rm.file(name));
|
|
27
|
+
const gm = new SoundFontMidi(out.ctx);
|
|
28
|
+
const gmReady = gm.load(`${BASE}soundfonts/GeneralUser-GS.sf2`);
|
|
29
|
+
gmReady.catch((e) => console.warn("SoundFont unavailable:", e));
|
|
30
|
+
const bank = { type: ResourceType.Patch, number: 3 };
|
|
31
|
+
const adlib = rm.has(bank) ? await AdLibOutput.create(out.ctx, parseAdLibBank(rm.loadSync(bank).data)) : undefined;
|
|
32
|
+
return { out, gm, gmReady, adlib, index: AudioIndex.build(rm) };
|
|
33
|
+
})());
|
|
34
|
+
|
|
35
|
+
type Source = "gm" | "adlib" | "digital";
|
|
36
|
+
const SOURCE_NAMES: Record<Source, string> = { gm: "General MIDI", adlib: "AdLib", digital: "Digital (SFX)" };
|
|
37
|
+
|
|
38
|
+
const nowTicks = () => (performance.now() * 60) / 1000;
|
|
39
|
+
const clock = (ticks: number) => {
|
|
40
|
+
const s = Math.max(0, Math.round(ticks / 60));
|
|
41
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** What's playing, so switching resources (or sources) can stop it. */
|
|
45
|
+
let stopCurrent: (() => void) | undefined;
|
|
46
|
+
let toggleCurrent: (() => void) | undefined;
|
|
47
|
+
|
|
48
|
+
export function stopSoundPreview(): void {
|
|
49
|
+
stopCurrent?.();
|
|
50
|
+
stopCurrent = undefined;
|
|
51
|
+
toggleCurrent = undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Space in the viewer: play or pause the sound on show, if any. */
|
|
55
|
+
export function toggleSoundPreview(): boolean {
|
|
56
|
+
if (!toggleCurrent) return false;
|
|
57
|
+
toggleCurrent();
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The sound's length and tracks, for its row in the list. */
|
|
62
|
+
export function soundSummary(data: Uint8Array): string {
|
|
63
|
+
const gm = parseSound(data, SoundDevice.GeneralMidi);
|
|
64
|
+
const adlib = parseSound(data, SoundDevice.AdLib);
|
|
65
|
+
const tracks = [gm && "MIDI", adlib && "AdLib"].filter(Boolean).join(" · ");
|
|
66
|
+
const length = gm?.length ?? adlib?.length;
|
|
67
|
+
return [length !== undefined ? clock(length) : undefined, tracks].filter(Boolean).join(" · ");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const make = <K extends keyof HTMLElementTagNameMap>(tag: K, props: Record<string, unknown> = {}): HTMLElementTagNameMap[K] =>
|
|
71
|
+
Object.assign(document.createElement(tag), props);
|
|
72
|
+
|
|
73
|
+
export async function renderSound(rm: ResourceManager, number: number, data: Uint8Array, stillWanted: () => boolean = () => true): Promise<HTMLElement> {
|
|
74
|
+
stopSoundPreview();
|
|
75
|
+
const songs: Partial<Record<Source, Song>> = {};
|
|
76
|
+
const gmSong = parseSound(data, SoundDevice.GeneralMidi);
|
|
77
|
+
const adlibSong = parseSound(data, SoundDevice.AdLib);
|
|
78
|
+
if (gmSong) songs.gm = gmSong;
|
|
79
|
+
// The AdLib track needs the game's instrument bank.
|
|
80
|
+
if (adlibSong && rm.has({ type: ResourceType.Patch, number: 3 })) songs.adlib = adlibSong;
|
|
81
|
+
|
|
82
|
+
const wrap = make("div", { className: "sound" });
|
|
83
|
+
const info = make("p", { className: "meta" });
|
|
84
|
+
const controls = make("div", { className: "player" });
|
|
85
|
+
const play = make("button", { type: "button", className: "play", textContent: "▶", title: "Play / pause (Space)" });
|
|
86
|
+
const stop = make("button", { type: "button", textContent: "■", title: "Stop" });
|
|
87
|
+
const source = make("select", { title: "Which version to play" });
|
|
88
|
+
const loopBox = make("input", { type: "checkbox" });
|
|
89
|
+
const loop = make("label", { className: "loop-opt" });
|
|
90
|
+
loop.append(loopBox, " loop");
|
|
91
|
+
const seek = make("input", { type: "range", min: "0", max: "1000", value: "0", className: "seek" });
|
|
92
|
+
const time = make("span", { className: "time", textContent: "0:00 / 0:00" });
|
|
93
|
+
controls.append(play, stop, source, loop, seek, time);
|
|
94
|
+
wrap.append(info, controls);
|
|
95
|
+
|
|
96
|
+
const a = await openAudio(rm);
|
|
97
|
+
const clip = a.index.effects.get(number);
|
|
98
|
+
const sources = ([...(clip ? ["digital"] : []), ...(songs.gm ? ["gm"] : []), ...(songs.adlib ? ["adlib"] : [])] as Source[]);
|
|
99
|
+
for (const s of sources) source.append(make("option", { value: s, textContent: SOURCE_NAMES[s] }));
|
|
100
|
+
if (!sources.length) {
|
|
101
|
+
info.textContent = "No playable track for these devices.";
|
|
102
|
+
play.disabled = stop.disabled = true;
|
|
103
|
+
return wrap;
|
|
104
|
+
}
|
|
105
|
+
const describe = (s: Song) => `${s.channels.length} channels · ${s.events.length} events · ${clock(s.length)}`;
|
|
106
|
+
info.textContent = [
|
|
107
|
+
songs.gm && `General MIDI: ${describe(songs.gm)}`,
|
|
108
|
+
songs.adlib && `AdLib: ${describe(songs.adlib)}`,
|
|
109
|
+
clip && `digital effect: ${clock(clip.ticks ?? 0)}`,
|
|
110
|
+
].filter(Boolean).join(" · ");
|
|
111
|
+
|
|
112
|
+
// --- Playback ---
|
|
113
|
+
let player: SongPlayer | undefined;
|
|
114
|
+
let midi: MidiOutput | undefined;
|
|
115
|
+
let started = 0; // tick at position 0
|
|
116
|
+
let pausedAt: number | undefined;
|
|
117
|
+
let digitalPlaying = false;
|
|
118
|
+
// A timer, not animation frames: music keeps playing in a background tab.
|
|
119
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
120
|
+
const length = () => (source.value === "digital" ? clip?.ticks ?? 0 : songs[source.value as Source]?.length ?? 0);
|
|
121
|
+
const position = () => {
|
|
122
|
+
const at = (pausedAt ?? nowTicks()) - started;
|
|
123
|
+
const len = Math.max(length(), 1);
|
|
124
|
+
return loopBox.checked ? at % len : Math.min(at, len);
|
|
125
|
+
};
|
|
126
|
+
const playing = () => !!player || digitalPlaying;
|
|
127
|
+
|
|
128
|
+
const frame = () => {
|
|
129
|
+
if (player && pausedAt === undefined) {
|
|
130
|
+
player.update(nowTicks());
|
|
131
|
+
if (player.finished) return halt();
|
|
132
|
+
}
|
|
133
|
+
if (digitalPlaying && pausedAt === undefined && !loopBox.checked && nowTicks() - started >= length()) return halt();
|
|
134
|
+
seek.value = String(Math.round((position() / Math.max(length(), 1)) * 1000));
|
|
135
|
+
time.textContent = `${clock(position())} / ${clock(length())}`;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const silence = () => {
|
|
139
|
+
if (!midi) return;
|
|
140
|
+
for (let c = 0; c < 16; c++) midi.send([0xb0 | c, 123, 0]); // all notes off
|
|
141
|
+
};
|
|
142
|
+
function halt(): void {
|
|
143
|
+
clearInterval(timer);
|
|
144
|
+
timer = undefined;
|
|
145
|
+
player?.stop();
|
|
146
|
+
silence();
|
|
147
|
+
player = undefined;
|
|
148
|
+
if (digitalPlaying) a.out.stop("preview");
|
|
149
|
+
digitalPlaying = false;
|
|
150
|
+
pausedAt = undefined;
|
|
151
|
+
play.textContent = "▶";
|
|
152
|
+
seek.value = "0";
|
|
153
|
+
time.textContent = `0:00 / ${clock(length())}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const start = async (from = 0) => {
|
|
157
|
+
halt();
|
|
158
|
+
a.out.unlock();
|
|
159
|
+
const s = source.value as Source;
|
|
160
|
+
if (s === "digital") {
|
|
161
|
+
a.out.play("preview", clip!, { loop: loopBox.checked, volume: 1 });
|
|
162
|
+
digitalPlaying = true;
|
|
163
|
+
started = nowTicks();
|
|
164
|
+
} else {
|
|
165
|
+
if (s === "gm") {
|
|
166
|
+
time.textContent = "loading SoundFont…";
|
|
167
|
+
await a.gmReady.catch(() => undefined); // the SoundFont is 32 MB: the first time takes a moment
|
|
168
|
+
midi = a.gm;
|
|
169
|
+
} else {
|
|
170
|
+
a.adlib!.reset();
|
|
171
|
+
midi = a.adlib!;
|
|
172
|
+
}
|
|
173
|
+
player = new SongPlayer(songs[s]!, midi, loopBox.checked, 127, nowTicks());
|
|
174
|
+
if (from) player.seek(from, nowTicks());
|
|
175
|
+
started = nowTicks() - from;
|
|
176
|
+
}
|
|
177
|
+
play.textContent = "❚❚";
|
|
178
|
+
timer = setInterval(frame, 15);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const toggle = () => {
|
|
182
|
+
if (!playing()) return void start();
|
|
183
|
+
const now = nowTicks();
|
|
184
|
+
if (pausedAt === undefined) {
|
|
185
|
+
pausedAt = now;
|
|
186
|
+
player?.pause(now);
|
|
187
|
+
if (digitalPlaying) a.out.pause("preview");
|
|
188
|
+
play.textContent = "▶";
|
|
189
|
+
} else {
|
|
190
|
+
started += now - pausedAt;
|
|
191
|
+
player?.resume(now);
|
|
192
|
+
if (digitalPlaying) a.out.resume("preview");
|
|
193
|
+
pausedAt = undefined;
|
|
194
|
+
play.textContent = "❚❚";
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
play.onclick = toggle;
|
|
199
|
+
stop.onclick = halt;
|
|
200
|
+
source.onchange = () => {
|
|
201
|
+
const was = playing();
|
|
202
|
+
halt();
|
|
203
|
+
seek.disabled = source.value === "digital";
|
|
204
|
+
if (was) void start();
|
|
205
|
+
};
|
|
206
|
+
loopBox.onchange = () => {
|
|
207
|
+
if (player) player.loop = loopBox.checked;
|
|
208
|
+
};
|
|
209
|
+
// Seeking: music only (digital clips play from the start).
|
|
210
|
+
seek.oninput = () => {
|
|
211
|
+
if (source.value === "digital") return;
|
|
212
|
+
const to = (Number(seek.value) / 1000) * length();
|
|
213
|
+
if (player) {
|
|
214
|
+
player.seek(to, nowTicks());
|
|
215
|
+
started = nowTicks() - to;
|
|
216
|
+
if (pausedAt !== undefined) pausedAt = nowTicks();
|
|
217
|
+
} else void start(to);
|
|
218
|
+
};
|
|
219
|
+
seek.disabled = source.value === "digital";
|
|
220
|
+
time.textContent = `0:00 / ${clock(length())}`;
|
|
221
|
+
|
|
222
|
+
// Only the sound on show owns the controls (a slow load may finish after moving on).
|
|
223
|
+
if (stillWanted()) {
|
|
224
|
+
stopCurrent = halt;
|
|
225
|
+
toggleCurrent = toggle;
|
|
226
|
+
}
|
|
227
|
+
return wrap;
|
|
228
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
:root { --bg: #111; --fg: #ddd; --muted: #888; --accent: #e8b04a; --line: #2a2a2a; color-scheme: dark; }
|
|
2
|
+
* { box-sizing: border-box; }
|
|
3
|
+
body { margin: 0; display: grid; grid-template-columns: 260px 1fr; height: 100vh; background: var(--bg); color: var(--fg); font: 13px/1.4 ui-monospace, Menlo, monospace; }
|
|
4
|
+
aside { border-right: 1px solid var(--line); display: flex; flex-direction: column; min-height: 0; }
|
|
5
|
+
select { margin: 8px; background: #1b1b1b; color: var(--fg); border: 1px solid var(--line); padding: 4px; }
|
|
6
|
+
ul { list-style: none; margin: 0; padding: 0; overflow: auto; }
|
|
7
|
+
li { padding: 2px 12px; cursor: pointer; }
|
|
8
|
+
li:hover, li.active { background: #1f1f1f; color: var(--accent); }
|
|
9
|
+
li small { color: var(--muted); }
|
|
10
|
+
main { overflow: auto; padding: 12px 20px; }
|
|
11
|
+
h1 { font-size: 14px; color: var(--accent); }
|
|
12
|
+
pre { color: #bbb; }
|
|
13
|
+
canvas { image-rendering: pixelated; display: block; }
|
|
14
|
+
.opt { margin: 0 8px 8px; color: var(--muted); }
|
|
15
|
+
.meta { color: var(--muted); margin: 6px 0; }
|
|
16
|
+
.error { color: #f66; }
|
|
17
|
+
details { margin-top: 16px; }
|
|
18
|
+
summary { cursor: pointer; color: var(--muted); }
|
|
19
|
+
.swatches { display: grid; grid-template-columns: repeat(16, 24px); gap: 2px; }
|
|
20
|
+
.sw { width: 24px; height: 24px; border-radius: 2px; }
|
|
21
|
+
.sw.unused { opacity: 0.15; outline: 1px dashed #555; }
|
|
22
|
+
.loop { display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap; margin: 12px 0; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
|
|
23
|
+
.loop .meta { width: 100%; }
|
|
24
|
+
.stage { padding: 12px; background: #1a1a1a; display: inline-block; margin: 8px 0; }
|
|
25
|
+
main select { background: #1b1b1b; color: var(--fg); border: 1px solid var(--line); padding: 4px; margin: 4px 0; }
|
|
26
|
+
.dialog { display: inline-block; margin: 8px 0 16px; padding: 12px 16px; background: #1d2a44; border: 3px double #8aa0c8; min-height: 40px; }
|
|
27
|
+
table.messages { border-collapse: collapse; width: 100%; }
|
|
28
|
+
.messages th { text-align: left; color: var(--muted); font-weight: normal; border-bottom: 1px solid var(--line); padding: 4px 8px; position: sticky; top: 0; background: var(--bg); }
|
|
29
|
+
.messages td { padding: 3px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
|
|
30
|
+
.messages td:nth-child(-n+5) { color: var(--muted); white-space: nowrap; }
|
|
31
|
+
.messages tr { cursor: pointer; }
|
|
32
|
+
.messages tr:hover td, .messages tr.active td { color: var(--accent); }
|
|
33
|
+
pre.asm { color: #cfcfcf; line-height: 1.5; }
|
|
34
|
+
pre.asm .c { color: #7f9f7f; }
|
|
35
|
+
pre.asm .fn { color: var(--accent); font-weight: bold; scroll-margin-top: 12px; }
|
|
36
|
+
ul.thumbs li { display: flex; align-items: center; gap: 10px; padding: 4px 10px; min-height: 30px; }
|
|
37
|
+
ul.thumbs .thumb { flex: none; width: 96px; display: flex; align-items: center; justify-content: center; }
|
|
38
|
+
ul.thumbs .thumb canvas { max-width: 96px; max-height: 64px; width: auto; height: auto; image-rendering: pixelated; }
|
|
39
|
+
ul.thumbs.type-pic .thumb { height: 60px; background: #000; }
|
|
40
|
+
ul.thumbs.type-pic .thumb canvas { width: 96px; height: 60px; }
|
|
41
|
+
ul.thumbs.type-view .thumb { height: 48px; background: #1a1a1a; border-radius: 3px; }
|
|
42
|
+
ul.thumbs.type-view .thumb canvas { max-height: 44px; max-width: 92px; }
|
|
43
|
+
ul.thumbs.type-message .thumb { width: 150px; justify-content: flex-start; }
|
|
44
|
+
ul.thumbs .snippet { color: var(--muted); font-size: 11px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
45
|
+
ul.thumbs li.active { box-shadow: inset 3px 0 var(--accent); }
|
|
46
|
+
.keys { margin: 0 8px 8px; color: var(--muted); font-size: 11px; }
|
|
47
|
+
#search { margin: 0 8px 6px; padding: 5px 8px; background: #1b1b1b; color: var(--fg); border: 1px solid var(--line); border-radius: 4px; font: inherit; }
|
|
48
|
+
#search:focus { outline: none; border-color: var(--accent); }
|
|
49
|
+
li .num { display: flex; flex-direction: column; min-width: 0; }
|
|
50
|
+
li .tags { color: var(--muted); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 120px; }
|
|
51
|
+
#uses { margin: -4px 0 12px; }
|
|
52
|
+
#uses .use { color: var(--muted); margin: 2px 0; }
|
|
53
|
+
#uses .use b { color: var(--fg); font-weight: normal; }
|
|
54
|
+
#uses .use q { color: #c9c0a8; font-style: italic; }
|
|
55
|
+
.empty { padding: 8px 12px; color: var(--muted); }
|
|
56
|
+
ul.thumbs.type-sound .thumb { width: 110px; justify-content: flex-start; }
|
|
57
|
+
.player { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 10px 0; padding: 10px 12px; background: #1a1a1a; border: 1px solid var(--line); border-radius: 6px; max-width: 720px; }
|
|
58
|
+
.player button { background: #222; color: var(--fg); border: 1px solid var(--line); border-radius: 4px; padding: 4px 10px; font: inherit; cursor: pointer; min-width: 36px; }
|
|
59
|
+
.player button.play { color: var(--accent); border-color: var(--accent); }
|
|
60
|
+
.player button:hover { border-color: var(--accent); }
|
|
61
|
+
.player select { margin: 0; }
|
|
62
|
+
.player .loop-opt { color: var(--muted); }
|
|
63
|
+
.player .seek { flex: 1; min-width: 160px; accent-color: var(--accent); }
|
|
64
|
+
.player .time { color: var(--muted); min-width: 92px; text-align: right; font-variant-numeric: tabular-nums; }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
import { sciGame } from "./plugin.ts";
|
|
4
|
+
|
|
5
|
+
/** The game to serve: original/ unless SCI_GAME names another (e.g. out/games/hello). */
|
|
6
|
+
const GAME_DIR = resolve(import.meta.dirname, "../..", process.env.SCI_GAME ?? "original");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The resource viewer, player, explorer and room editor, for the game in SCI_GAME: its
|
|
10
|
+
* files, built mods (`pnpm mod build mods/<name>` → out/mods/<name>), SoundFonts
|
|
11
|
+
* (assets/soundfonts) and the editor's routes come from the sciGame plugin.
|
|
12
|
+
*/
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
publicDir: false,
|
|
15
|
+
// Allow access through ngrok tunnels (Vite blocks unknown hostnames by default).
|
|
16
|
+
server: { allowedHosts: [".ngrok-free.app", ".ngrok-free.dev", ".ngrok.app", ".ngrok.io"] },
|
|
17
|
+
plugins: [
|
|
18
|
+
sciGame({
|
|
19
|
+
game: GAME_DIR,
|
|
20
|
+
mods: resolve(import.meta.dirname, "../../out/mods"),
|
|
21
|
+
soundfonts: resolve(import.meta.dirname, "../../assets/soundfonts"),
|
|
22
|
+
editor: true,
|
|
23
|
+
}),
|
|
24
|
+
],
|
|
25
|
+
});
|
package/bin/sci-ts.mjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The sci-ts command, run with tsx so that a game's own TypeScript (resources.ts) loads.
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const cli = fileURLToPath(new URL("../tools/cli.js", import.meta.url));
|
|
7
|
+
const child = spawn(process.execPath, ["--import", import.meta.resolve("tsx"), cli, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
8
|
+
child.on("exit", (code, signal) => (signal ? process.kill(process.pid, signal) : process.exit(code ?? 1)));
|
|
9
|
+
for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => child.kill(signal));
|
package/lib/994.sc
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
;;; Sound: music and effects. A sound resource number plays as a digital effect if there
|
|
2
|
+
;;; is one, else as music.
|
|
3
|
+
(script 994)
|
|
4
|
+
(include "system.sh")
|
|
5
|
+
|
|
6
|
+
(class Sound of Obj
|
|
7
|
+
(properties
|
|
8
|
+
number 0
|
|
9
|
+
loop 1 ; -1: forever
|
|
10
|
+
vol 127
|
|
11
|
+
priority 0
|
|
12
|
+
handle 0 ; set while playing (the interpreter's)
|
|
13
|
+
signal 0 ; -1 when it has ended
|
|
14
|
+
dataInc 0
|
|
15
|
+
nodePtr 0
|
|
16
|
+
client 0) ; cued when it ends
|
|
17
|
+
|
|
18
|
+
(method (init)
|
|
19
|
+
(DoSound SND_INIT self)
|
|
20
|
+
(sounds add: self))
|
|
21
|
+
|
|
22
|
+
(method (play who)
|
|
23
|
+
(if (not nodePtr) (self init:))
|
|
24
|
+
(= client (if argc who else 0))
|
|
25
|
+
(DoSound SND_PLAY self))
|
|
26
|
+
|
|
27
|
+
(method (stop)
|
|
28
|
+
(DoSound SND_STOP self))
|
|
29
|
+
|
|
30
|
+
;; How many times to play: -1 forever.
|
|
31
|
+
(method (setLoop n)
|
|
32
|
+
(= loop n)
|
|
33
|
+
(return self))
|
|
34
|
+
|
|
35
|
+
;; Each cycle (from the game): has it ended?
|
|
36
|
+
(method (check &tmp c)
|
|
37
|
+
(if handle
|
|
38
|
+
(DoSound SND_UPDATE_CUES self)
|
|
39
|
+
(if (== signal -1)
|
|
40
|
+
(self stop:)
|
|
41
|
+
(= c client)
|
|
42
|
+
(= client 0)
|
|
43
|
+
(if c (c cue:)))))
|
|
44
|
+
|
|
45
|
+
(method (dispose)
|
|
46
|
+
(DoSound SND_STOP self)
|
|
47
|
+
(DoSound SND_DISPOSE self)
|
|
48
|
+
(sounds delete: self)
|
|
49
|
+
(super dispose:)))
|
package/lib/995.sc
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
;;; Input: events, and what clicks do.
|
|
2
|
+
(script 995)
|
|
3
|
+
(include "system.sh")
|
|
4
|
+
|
|
5
|
+
;; What GetEvent fills in.
|
|
6
|
+
(class Event of Obj
|
|
7
|
+
(properties
|
|
8
|
+
type 0
|
|
9
|
+
message 0
|
|
10
|
+
modifiers 0
|
|
11
|
+
x 0 y 0
|
|
12
|
+
claimed 0))
|
|
13
|
+
|
|
14
|
+
;; Turns clicks into walking and verbs. Right-click goes to the next verb (and cursor);
|
|
15
|
+
;; a click walks the hero there, or does the verb to what's under the cursor: an actor or
|
|
16
|
+
;; prop, a feature, or else the room. A click while a line is shown dismisses it; while a
|
|
17
|
+
;; menu is open, it goes to the menu.
|
|
18
|
+
(class User of Obj
|
|
19
|
+
(properties
|
|
20
|
+
verb V_WALK
|
|
21
|
+
canInput TRUE)
|
|
22
|
+
|
|
23
|
+
(method (init)
|
|
24
|
+
(self setVerb: verb))
|
|
25
|
+
|
|
26
|
+
(method (setVerb v)
|
|
27
|
+
(= verb v)
|
|
28
|
+
(SetCursor (+ CURSOR_BASE v) 0 0))
|
|
29
|
+
|
|
30
|
+
(method (doit)
|
|
31
|
+
(while (GetEvent EV_ALL theEvent)
|
|
32
|
+
(self handleEvent: theEvent)))
|
|
33
|
+
|
|
34
|
+
(method (handleEvent event &tmp obj ex ey)
|
|
35
|
+
(event claimed: FALSE)
|
|
36
|
+
;; A line being shown, then an open menu, get clicks first.
|
|
37
|
+
(if (and talking (talking handleEvent: event)) (return))
|
|
38
|
+
(if dialog
|
|
39
|
+
(dialog handleEvent: event)
|
|
40
|
+
(return))
|
|
41
|
+
(if (or (not canInput) (!= (event type?) EV_MOUSE_DOWN)) (return))
|
|
42
|
+
(if (& (event modifiers?) MOD_RIGHT)
|
|
43
|
+
(self setVerb: (+ (mod verb VERB_COUNT) 1))
|
|
44
|
+
(return))
|
|
45
|
+
(= ex (event x?))
|
|
46
|
+
(= ey (event y?))
|
|
47
|
+
(if (== verb V_WALK)
|
|
48
|
+
(if ego (ego setMotion: PolyPath ex ey))
|
|
49
|
+
(return))
|
|
50
|
+
(= obj (cast firstTrue: #onMe ex ey))
|
|
51
|
+
(if (not obj) (= obj (features firstTrue: #onMe ex ey)))
|
|
52
|
+
(if (not obj) (= obj curRoom))
|
|
53
|
+
(if obj (obj doVerb: verb))))
|