@datamoc/mw_games 0.39.1 → 0.41.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/CHANGELOG.md +89 -0
- package/REFERENCE.md +2115 -0
- package/api-surface.json +48 -0
- package/data/microgm-opt.PROVENANCE.md +23 -0
- package/data/microgm-opt.sf2 +0 -0
- package/data/soundfont-catalog.json +183 -0
- package/dist/ai/index.d.ts +10 -0
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/lua.d.ts +6 -0
- package/dist/ai/lua.d.ts.map +1 -1
- package/dist/ai/lua.js.map +1 -1
- package/dist/audio/SampleDecoder.d.ts +41 -0
- package/dist/audio/SampleDecoder.d.ts.map +1 -0
- package/dist/audio/SampleDecoder.js +70 -0
- package/dist/audio/SampleDecoder.js.map +1 -0
- package/dist/audio/SoundFont.d.ts +59 -0
- package/dist/audio/SoundFont.d.ts.map +1 -1
- package/dist/audio/SoundFont.js +248 -2
- package/dist/audio/SoundFont.js.map +1 -1
- package/dist/audio/index.d.ts +3 -1
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +2 -1
- package/dist/audio/index.js.map +1 -1
- package/dist/mw_games.global.js +11 -11
- package/dist/mwl/presentation.d.ts +16 -0
- package/dist/mwl/presentation.d.ts.map +1 -1
- package/dist/mwl/presentation.js.map +1 -1
- package/dist/mwl/pyroeis-runtime.d.ts +7 -0
- package/dist/mwl/pyroeis-runtime.d.ts.map +1 -1
- package/dist/mwl/pyroeis-runtime.js.map +1 -1
- package/dist/mwl/pyroeis.d.ts +3 -0
- package/dist/mwl/pyroeis.d.ts.map +1 -1
- package/dist/mwl/pyroeis.js.map +1 -1
- package/dist/physics/PhysicsWorld.d.ts +3 -0
- package/dist/physics/PhysicsWorld.d.ts.map +1 -1
- package/dist/physics/PhysicsWorld.js.map +1 -1
- package/dist/physics/XpbdWorld.d.ts +11 -0
- package/dist/physics/XpbdWorld.d.ts.map +1 -1
- package/dist/physics/XpbdWorld.js.map +1 -1
- package/dist/physics/cloth/index.d.ts +20 -19
- package/dist/physics/cloth/index.d.ts.map +1 -1
- package/dist/physics/cloth/index.js +19 -0
- package/dist/physics/cloth/index.js.map +1 -1
- package/dist/physics/spring/index.d.ts +19 -18
- package/dist/physics/spring/index.d.ts.map +1 -1
- package/dist/physics/spring/index.js +23 -0
- package/dist/physics/spring/index.js.map +1 -1
- package/dist/three-d/Character3D.d.ts +8 -7
- package/dist/three-d/Character3D.d.ts.map +1 -1
- package/dist/three-d/Character3D.js +7 -0
- package/dist/three-d/Character3D.js.map +1 -1
- package/dist/three-d/Collision3D.d.ts +4 -0
- package/dist/three-d/Collision3D.d.ts.map +1 -1
- package/dist/three-d/Collision3D.js.map +1 -1
- package/dist/three-d/Engine3D.d.ts +2 -0
- package/dist/three-d/Engine3D.d.ts.map +1 -1
- package/dist/three-d/Engine3D.js.map +1 -1
- package/dist/three-d/Grid.d.ts +5 -0
- package/dist/three-d/Grid.d.ts.map +1 -1
- package/dist/three-d/Grid.js.map +1 -1
- package/dist/three-d/Heightmap.d.ts +3 -0
- package/dist/three-d/Heightmap.d.ts.map +1 -1
- package/dist/three-d/Heightmap.js.map +1 -1
- package/dist/three-d/TileGrid3D.d.ts +6 -0
- package/dist/three-d/TileGrid3D.d.ts.map +1 -1
- package/dist/three-d/TileGrid3D.js.map +1 -1
- package/dist/three-d/Vox.d.ts +7 -0
- package/dist/three-d/Vox.d.ts.map +1 -1
- package/dist/three-d/Vox.js.map +1 -1
- package/dist/two-d/presentation/MwlPresentation.d.ts +7 -0
- package/dist/two-d/presentation/MwlPresentation.d.ts.map +1 -1
- package/dist/two-d/presentation/MwlPresentation.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +18 -1
- package/sbom.cdx.json +139 -139
- package/tools/benchmark-browser.mjs +6 -0
- package/tools/bundle-size.mjs +4 -0
- package/tools/compile-resources.d.mts +22 -0
- package/tools/compile-resources.mjs +116 -3
- package/tools/emit-page.d.mts +7 -1
- package/tools/emit-page.mjs +20 -2
- package/tools/init.mjs +3 -1
- package/tools/make-example-assets.mjs +4 -0
- package/tools/multiplayer-server.mjs +4 -0
- package/tools/mwl.mjs +7 -0
- package/tools/play-uci.mjs +4 -0
- package/tools/sbom.mjs +4 -0
- package/tools/soundfonts.d.mts +79 -0
- package/tools/soundfonts.mjs +301 -0
- package/tools/verify-scenarios.mjs +6 -2
- package/tools/vite.mjs +2 -0
- package/tools.md +216 -0
|
@@ -136,6 +136,12 @@ export async function measurePage({
|
|
|
136
136
|
* `benchmark-results/<page>.json`, named by the page so two pages keep separate histories.
|
|
137
137
|
*/
|
|
138
138
|
async function main(argv) {
|
|
139
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
140
|
+
console.log(
|
|
141
|
+
'usage: mwg-bench <page.html | dist folder> [--frames=180] [--min-fps=45] [--max-p95=40] [--history=<file>]',
|
|
142
|
+
);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
139
145
|
const value = (name, env, fallback) =>
|
|
140
146
|
argv.find((a) => a.startsWith(`--${name}=`))?.split('=')[1] ?? process.env[env] ?? fallback;
|
|
141
147
|
const target = argv.find((arg) => !arg.startsWith('--')) ?? 'examples/dungeon/dist/index.html';
|
package/tools/bundle-size.mjs
CHANGED
|
@@ -99,6 +99,10 @@ function measureFramework(root) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function main(argv) {
|
|
102
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
103
|
+
console.log('usage: mwg-size <dist folder> [--baseline=<file>] [--tolerance=0.02] [--update]');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
102
106
|
const tolerance = Number(
|
|
103
107
|
argv.find((a) => a.startsWith('--tolerance='))?.slice(12) ?? process.env.MWG_SIZE_TOLERANCE ?? 0.02,
|
|
104
108
|
);
|
|
@@ -12,6 +12,10 @@ export interface CompileResourcesOptions {
|
|
|
12
12
|
toWebp?: boolean;
|
|
13
13
|
webpLossless?: boolean;
|
|
14
14
|
webpQuality?: number;
|
|
15
|
+
/** Subset an embedded `.sf2` to the programs the embedded `.mid`/`.midi` files use. Default true; `false` embeds every SoundFont whole. */
|
|
16
|
+
soundFont?: boolean;
|
|
17
|
+
/** Explicit `{ bank, program }` list to subset a SoundFont to, overriding the MIDI-derived list. */
|
|
18
|
+
soundFontUsage?: readonly SoundFontUsage[];
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
export interface CompileResourcesGroup {
|
|
@@ -28,9 +32,27 @@ export interface CompileResourcesResult {
|
|
|
28
32
|
skipped: number;
|
|
29
33
|
/** the `CREDITS.txt` text when the asset folder holds a `credits.json`, else null */
|
|
30
34
|
credits: string | null;
|
|
35
|
+
/** one entry per embedded `.sf2`: the programs kept (null when embedded whole) and the byte saving */
|
|
36
|
+
soundFonts: SoundFontEmbed[];
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
/** the plain-text credits for a manifest (an array, or `{ credits: [...] }`, of `{ name, license, author?, url? }`) */
|
|
34
40
|
export function creditsText(manifest: unknown): string;
|
|
35
41
|
|
|
42
|
+
export interface SoundFontUsage {
|
|
43
|
+
bank: number;
|
|
44
|
+
program: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SoundFontEmbed {
|
|
48
|
+
key: string;
|
|
49
|
+
/** programs kept, or null when the font was embedded whole for lack of a usage list */
|
|
50
|
+
programs: number | null;
|
|
51
|
+
rawBytes: number;
|
|
52
|
+
embeddedBytes: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** parses a `--soundfont-usage=bank:program,...` CLI value; undefined when the flag is absent */
|
|
56
|
+
export function soundFontUsageFromFlag(value: string | undefined): SoundFontUsage[] | undefined;
|
|
57
|
+
|
|
36
58
|
export function compileResources(options: CompileResourcesOptions): Promise<CompileResourcesResult>;
|
|
@@ -38,8 +38,57 @@ export const DEFAULT_MIME = {
|
|
|
38
38
|
'.woff2': 'font/woff2',
|
|
39
39
|
'.json': 'application/json',
|
|
40
40
|
'.txt': 'text/plain',
|
|
41
|
+
'.mid': 'audio/midi',
|
|
42
|
+
'.midi': 'audio/midi',
|
|
43
|
+
'.sf2': 'application/octet-stream',
|
|
44
|
+
'.sf3': 'application/octet-stream',
|
|
41
45
|
};
|
|
42
46
|
|
|
47
|
+
const isSoundFontKey = (key) => /\.sf[23]$/i.test(key);
|
|
48
|
+
const isMidiKey = (key) => /\.(mid|midi)$/i.test(key);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The SoundFont/MIDI implementation lives in the library, not in this tool: in this
|
|
52
|
+
* repository it is the TypeScript source, in an installed package it is the built
|
|
53
|
+
* `dist/` the source compiles to. Only called when a build actually embeds a
|
|
54
|
+
* SoundFont it can subset, so a game without one never pays for the import.
|
|
55
|
+
*/
|
|
56
|
+
async function loadSoundFontTools() {
|
|
57
|
+
try {
|
|
58
|
+
const [soundFont, midi] = await Promise.all([
|
|
59
|
+
import('../src/audio/SoundFont.ts'),
|
|
60
|
+
import('../src/audio/Midi.ts'),
|
|
61
|
+
]);
|
|
62
|
+
return { ...soundFont, ...midi };
|
|
63
|
+
} catch {
|
|
64
|
+
return import('../dist/audio/index.js');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function checkedSoundFontUsage(soundFontUsage) {
|
|
69
|
+
if (soundFontUsage === undefined) return undefined;
|
|
70
|
+
if (!Array.isArray(soundFontUsage)) throw new Error('soundFontUsage must be an array of { bank, program }');
|
|
71
|
+
return soundFontUsage.map((entry, index) => {
|
|
72
|
+
const { bank, program } = entry ?? {};
|
|
73
|
+
if (!Number.isInteger(bank) || bank < 0 || !Number.isInteger(program) || program < 0 || program > 127) {
|
|
74
|
+
throw new Error(`soundFontUsage[${index}] needs an integer bank >= 0 and a program between 0 and 127`);
|
|
75
|
+
}
|
|
76
|
+
return { bank, program };
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** parses a `--soundfont-usage=bank:program,...` CLI value; undefined when the flag is absent */
|
|
81
|
+
export function soundFontUsageFromFlag(value) {
|
|
82
|
+
if (value === undefined) return undefined;
|
|
83
|
+
return value
|
|
84
|
+
.split(',')
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.map((pair) => {
|
|
87
|
+
const [bank, program] = pair.split(':').map(Number);
|
|
88
|
+
return { bank, program };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
43
92
|
export async function* walk(dir) {
|
|
44
93
|
let entries;
|
|
45
94
|
try {
|
|
@@ -72,6 +121,10 @@ export async function* walk(dir) {
|
|
|
72
121
|
* pixel-art tileset's exact colours survive; set false only with `webpQuality` to opt into
|
|
73
122
|
* lossy re-encoding for photographic source art)
|
|
74
123
|
* @param options.webpQuality 0-100, used only when `webpLossless` is false (default 90)
|
|
124
|
+
* @param options.soundFont subset an embedded `.sf2` or `.sf3` to the programs the embedded
|
|
125
|
+
* `.mid`/`.midi` files use (default true); `false` embeds every SoundFont whole
|
|
126
|
+
* @param options.soundFontUsage explicit `{ bank, program }` list to subset to, for a
|
|
127
|
+
* game that voices notes no shipped MIDI file requests; overrides the MIDI-derived list
|
|
75
128
|
* @returns the group names, in the order the page should load them
|
|
76
129
|
*
|
|
77
130
|
* An `assets/credits.json` (an array, or `{ "credits": [...] }`, of `{ name, license, author?, url? }`)
|
|
@@ -88,6 +141,8 @@ export async function compileResources({
|
|
|
88
141
|
toWebp: convertToWebp = false,
|
|
89
142
|
webpLossless = true,
|
|
90
143
|
webpQuality = 90,
|
|
144
|
+
soundFont = true,
|
|
145
|
+
soundFontUsage,
|
|
91
146
|
} = {}) {
|
|
92
147
|
if (!from || !to) throw new Error('compileResources needs both `from` and `to`');
|
|
93
148
|
|
|
@@ -110,6 +165,38 @@ export async function compileResources({
|
|
|
110
165
|
}
|
|
111
166
|
}
|
|
112
167
|
|
|
168
|
+
// A shipped SoundFont carries only the programs the game's shipped MIDI files
|
|
169
|
+
// request: the full bank - even a small one - is mostly
|
|
170
|
+
// instruments no track plays. Usage is resolved before any output is replaced,
|
|
171
|
+
// so an unreadable MIDI or SoundFont fails the build with the old output intact.
|
|
172
|
+
// With no shipped MIDI and no explicit `soundFontUsage` the usage is unknowable
|
|
173
|
+
// (a game may voice notes it generates), so the font embeds whole.
|
|
174
|
+
const isSelected = (file) => !selected || selected.has(keyOf(file));
|
|
175
|
+
const explicitUsage = checkedSoundFontUsage(soundFontUsage);
|
|
176
|
+
const embedsSoundFont =
|
|
177
|
+
soundFont !== false && files.some((file) => isSelected(file) && isSoundFontKey(keyOf(file)));
|
|
178
|
+
let soundFontTools = null;
|
|
179
|
+
let usage = explicitUsage;
|
|
180
|
+
if (embedsSoundFont && usage === undefined) {
|
|
181
|
+
const midiFiles = files.filter((file) => isSelected(file) && isMidiKey(keyOf(file)));
|
|
182
|
+
if (midiFiles.length > 0) {
|
|
183
|
+
soundFontTools = await loadSoundFontTools();
|
|
184
|
+
const parsed = [];
|
|
185
|
+
for (const file of midiFiles) {
|
|
186
|
+
try {
|
|
187
|
+
parsed.push(soundFontTools.parseMidi(await readFile(file)));
|
|
188
|
+
} catch (error) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`cannot read MIDI asset ${keyOf(file)} to subset a SoundFont: ${error.message ?? error}`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
usage = soundFontTools.collectSoundFontUsage(parsed);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (embedsSoundFont && usage !== undefined && !soundFontTools) soundFontTools = await loadSoundFontTools();
|
|
198
|
+
const soundFonts = [];
|
|
199
|
+
|
|
113
200
|
await rm(to, { recursive: true, force: true });
|
|
114
201
|
await mkdir(to, { recursive: true });
|
|
115
202
|
|
|
@@ -153,6 +240,19 @@ export async function compileResources({
|
|
|
153
240
|
}
|
|
154
241
|
}
|
|
155
242
|
|
|
243
|
+
if (isSoundFontKey(key)) {
|
|
244
|
+
if (usage !== undefined && soundFontTools) {
|
|
245
|
+
try {
|
|
246
|
+
bytes = Buffer.from(soundFontTools.subsetSoundFont(data, usage));
|
|
247
|
+
} catch (error) {
|
|
248
|
+
throw new Error(`cannot subset SoundFont asset ${key}: ${error.message ?? error}`);
|
|
249
|
+
}
|
|
250
|
+
soundFonts.push({ key, programs: usage.length, rawBytes: data.length, embeddedBytes: bytes.length });
|
|
251
|
+
} else {
|
|
252
|
+
soundFonts.push({ key, programs: null, rawBytes: data.length, embeddedBytes: data.length });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
156
256
|
const name = group(key);
|
|
157
257
|
if (!groups.has(name)) groups.set(name, {});
|
|
158
258
|
groups.get(name)[key] = `data:${mimeType};base64,${bytes.toString('base64')}`;
|
|
@@ -173,7 +273,7 @@ export async function compileResources({
|
|
|
173
273
|
|
|
174
274
|
if (credits) await writeFile(join(to, 'CREDITS.txt'), credits, 'utf8');
|
|
175
275
|
|
|
176
|
-
return { groups: written, rawBytes, embeddedBytes, webpConverted, skipped, credits };
|
|
276
|
+
return { groups: written, rawBytes, embeddedBytes, webpConverted, skipped, credits, soundFonts };
|
|
177
277
|
}
|
|
178
278
|
|
|
179
279
|
/** the plain-text credits for a manifest, one block per entry; throws on an entry that cannot be attributed */
|
|
@@ -202,7 +302,7 @@ async function runCli() {
|
|
|
202
302
|
const [from, to] = args.filter((a) => !a.startsWith('--'));
|
|
203
303
|
if (!from || !to) {
|
|
204
304
|
console.error(
|
|
205
|
-
'usage: node compile-resources.mjs <asset folder> <output folder> [--to-webp] [--webp-lossy[=quality]]',
|
|
305
|
+
'usage: node compile-resources.mjs <asset folder> <output folder> [--to-webp] [--webp-lossy[=quality]] [--no-soundfont-subset] [--soundfont-usage=bank:program,...]',
|
|
206
306
|
);
|
|
207
307
|
process.exit(1);
|
|
208
308
|
}
|
|
@@ -211,7 +311,11 @@ async function runCli() {
|
|
|
211
311
|
const webpLossless = !lossyFlag;
|
|
212
312
|
const webpQuality = lossyFlag && lossyFlag.includes('=') ? Number(lossyFlag.split('=')[1]) : 90;
|
|
213
313
|
|
|
214
|
-
const
|
|
314
|
+
const soundFontUsage = soundFontUsageFromFlag(
|
|
315
|
+
args.find((a) => a.startsWith('--soundfont-usage='))?.slice('--soundfont-usage='.length),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const { groups, rawBytes, embeddedBytes, webpConverted, skipped, soundFonts } = await compileResources({
|
|
215
319
|
from,
|
|
216
320
|
to,
|
|
217
321
|
//a flat folder has no subdirectories to group by, so everything lands in one file
|
|
@@ -219,6 +323,8 @@ async function runCli() {
|
|
|
219
323
|
toWebp,
|
|
220
324
|
webpLossless,
|
|
221
325
|
webpQuality,
|
|
326
|
+
soundFont: !args.includes('--no-soundfont-subset'),
|
|
327
|
+
soundFontUsage,
|
|
222
328
|
});
|
|
223
329
|
|
|
224
330
|
const kb = (n) => (n / 1024).toFixed(1).padStart(8) + ' KB';
|
|
@@ -226,6 +332,13 @@ async function runCli() {
|
|
|
226
332
|
console.log(` ${join(relative(process.cwd(), to), name + '.js').padEnd(40)}${kb(size)} (${count} files)`);
|
|
227
333
|
}
|
|
228
334
|
console.log(` ${groups.length} script(s), ${kb(rawBytes)} of assets` + (skipped ? `, ${skipped} skipped` : ''));
|
|
335
|
+
for (const font of soundFonts) {
|
|
336
|
+
console.log(
|
|
337
|
+
font.programs === null
|
|
338
|
+
? ` ${font.key}: SoundFont embedded whole (${kb(font.rawBytes)}), no shipped MIDI to subset against`
|
|
339
|
+
: ` ${font.key}: SoundFont subset to ${font.programs} program(s), ${kb(font.rawBytes)} -> ${kb(font.embeddedBytes)}`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
229
342
|
if (toWebp) {
|
|
230
343
|
console.log(
|
|
231
344
|
` ${webpConverted} image(s) converted to WebP (${webpLossless ? 'lossless' : `lossy, quality ${webpQuality}`}), ` +
|
package/tools/emit-page.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompileResourcesGroup } from './compile-resources.mjs';
|
|
1
|
+
import type { CompileResourcesGroup, SoundFontEmbed, SoundFontUsage } from './compile-resources.mjs';
|
|
2
2
|
import type { CompressRow } from './compress-dist.mjs';
|
|
3
3
|
import type { SingleFileOptions, SingleFileResult } from './single-file.mjs';
|
|
4
4
|
|
|
@@ -20,6 +20,10 @@ export interface EmitPageOptions {
|
|
|
20
20
|
toWebp?: boolean;
|
|
21
21
|
webpLossless?: boolean;
|
|
22
22
|
webpQuality?: number;
|
|
23
|
+
/** Subset an embedded `.sf2` to the programs the embedded `.mid`/`.midi` files use. Default true; `false` embeds every SoundFont whole. */
|
|
24
|
+
soundFont?: boolean;
|
|
25
|
+
/** Explicit `{ bank, program }` list to subset a SoundFont to, overriding the MIDI-derived list. */
|
|
26
|
+
soundFontUsage?: readonly SoundFontUsage[];
|
|
23
27
|
/** the page's Content-Security-Policy: on by default, `false` to leave it out */
|
|
24
28
|
csp?: boolean | CspOptions;
|
|
25
29
|
}
|
|
@@ -38,6 +42,8 @@ export interface EmitPageResult {
|
|
|
38
42
|
rawBytes: number;
|
|
39
43
|
embeddedBytes: number;
|
|
40
44
|
webpConverted: number;
|
|
45
|
+
/** one entry per embedded `.sf2`: the programs kept (null when embedded whole) and the byte saving */
|
|
46
|
+
soundFonts: SoundFontEmbed[];
|
|
41
47
|
compressed: CompressRow[] | null;
|
|
42
48
|
single: SingleFileResult | null;
|
|
43
49
|
}
|
package/tools/emit-page.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile, writeFile, rm, stat } from 'node:fs/promises';
|
|
|
3
3
|
import { realpathSync } from 'node:fs';
|
|
4
4
|
import { join, resolve as resolvePath } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { compileResources } from './compile-resources.mjs';
|
|
6
|
+
import { compileResources, soundFontUsageFromFlag } from './compile-resources.mjs';
|
|
7
7
|
import { compressDist } from './compress-dist.mjs';
|
|
8
8
|
import { toClassicScript } from './classic-html.mjs';
|
|
9
9
|
import { buildSingleFile } from './single-file.mjs';
|
|
@@ -30,6 +30,7 @@ import { withContentSecurityPolicy } from './csp.mjs';
|
|
|
30
30
|
* [--no-compress] [--xz]
|
|
31
31
|
* [--single-file] [--single-file-compress[=level]]
|
|
32
32
|
* [--single-file-brotli[=quality]] [--to-webp] [--webp-lossy[=quality]]
|
|
33
|
+
* [--no-soundfont-subset] [--soundfont-usage=bank:program,...]
|
|
33
34
|
*
|
|
34
35
|
* `--dist` defaults to `<project folder>/dist` and `--assets` to `<project folder>/assets`; with
|
|
35
36
|
* no assets folder at all, the page is rewritten and nothing is compiled.
|
|
@@ -73,6 +74,8 @@ export async function emitPage({
|
|
|
73
74
|
toWebp = false,
|
|
74
75
|
webpLossless = true,
|
|
75
76
|
webpQuality = 90,
|
|
77
|
+
soundFont = true,
|
|
78
|
+
soundFontUsage,
|
|
76
79
|
csp = true,
|
|
77
80
|
}) {
|
|
78
81
|
if (!dist) throw new Error('emitPage needs the dist folder of a vite build');
|
|
@@ -86,8 +89,10 @@ export async function emitPage({
|
|
|
86
89
|
toWebp,
|
|
87
90
|
webpLossless,
|
|
88
91
|
webpQuality,
|
|
92
|
+
soundFont,
|
|
93
|
+
soundFontUsage,
|
|
89
94
|
})
|
|
90
|
-
: { groups: [], rawBytes: 0, embeddedBytes: 0, webpConverted: 0 };
|
|
95
|
+
: { groups: [], rawBytes: 0, embeddedBytes: 0, webpConverted: 0, soundFonts: [] };
|
|
91
96
|
|
|
92
97
|
const html = await readFile(join(dist, 'index.html'), 'utf8');
|
|
93
98
|
const classic = toClassicScript(html);
|
|
@@ -142,6 +147,10 @@ async function main(argv) {
|
|
|
142
147
|
level: levelFlag?.includes('=') ? Number(levelFlag.split('=')[1]) : undefined,
|
|
143
148
|
};
|
|
144
149
|
|
|
150
|
+
const soundFontUsage = soundFontUsageFromFlag(
|
|
151
|
+
argv.find((a) => a.startsWith('--soundfont-usage='))?.slice('--soundfont-usage='.length),
|
|
152
|
+
);
|
|
153
|
+
|
|
145
154
|
const result = await emitPage({
|
|
146
155
|
dist,
|
|
147
156
|
assets: hasAssets ? assetsDir : undefined,
|
|
@@ -153,6 +162,8 @@ async function main(argv) {
|
|
|
153
162
|
toWebp: argv.includes('--to-webp'),
|
|
154
163
|
webpLossless: !webpLossyFlag,
|
|
155
164
|
webpQuality: webpLossyFlag?.includes('=') ? Number(webpLossyFlag.split('=')[1]) : 90,
|
|
165
|
+
soundFont: !argv.includes('--no-soundfont-subset'),
|
|
166
|
+
soundFontUsage,
|
|
156
167
|
csp: argv.includes('--no-csp')
|
|
157
168
|
? false
|
|
158
169
|
: { connect: argv.filter((a) => a.startsWith('--connect=')).map((a) => a.slice('--connect='.length)) },
|
|
@@ -168,6 +179,13 @@ async function main(argv) {
|
|
|
168
179
|
`${kb(result.rawBytes)} -> ${kb(result.embeddedBytes)} embedded`,
|
|
169
180
|
);
|
|
170
181
|
}
|
|
182
|
+
for (const font of result.soundFonts ?? []) {
|
|
183
|
+
console.log(
|
|
184
|
+
font.programs === null
|
|
185
|
+
? ` ${font.key}: SoundFont embedded whole (${kb(font.rawBytes)}), no shipped MIDI to subset against`
|
|
186
|
+
: ` ${font.key}: SoundFont subset to ${font.programs} program(s), ${kb(font.rawBytes)} -> ${kb(font.embeddedBytes)}`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
171
189
|
console.log(' open index.html directly - no server needed');
|
|
172
190
|
if (result.compressed) {
|
|
173
191
|
for (const row of result.compressed) {
|
package/tools/init.mjs
CHANGED
|
@@ -130,7 +130,9 @@ export function initProject(folder) {
|
|
|
130
130
|
//no top-level await: this module is also imported, and the real path covers an npm bin symlink
|
|
131
131
|
if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
132
132
|
const folder = process.argv[2];
|
|
133
|
-
if (
|
|
133
|
+
if (folder === '--help' || folder === '-h') {
|
|
134
|
+
console.log('usage: mwg-init <new folder>');
|
|
135
|
+
} else if (!folder) {
|
|
134
136
|
console.error('usage: mwg-init <new folder>');
|
|
135
137
|
process.exitCode = 2;
|
|
136
138
|
} else {
|
|
@@ -717,6 +717,10 @@ if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.me
|
|
|
717
717
|
const args = process.argv.slice(2);
|
|
718
718
|
const folder = args.find((arg) => !arg.startsWith('--'));
|
|
719
719
|
const inRepository = existsSync(join(here, '..', 'examples'));
|
|
720
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
721
|
+
console.log('usage: mwg-placeholder-assets <folder> [--force]');
|
|
722
|
+
process.exit(0);
|
|
723
|
+
}
|
|
720
724
|
if (!folder && !inRepository) {
|
|
721
725
|
console.error('usage: mwg-placeholder-assets <folder> [--force]');
|
|
722
726
|
process.exit(1);
|
|
@@ -187,6 +187,10 @@ export function createLockstepServer(options = {}) {
|
|
|
187
187
|
//realpath, since an npm `bin` shim reaches this file through a symlink
|
|
188
188
|
if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
189
189
|
const args = process.argv.slice(2);
|
|
190
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
191
|
+
console.log('usage: mwg-lockstep-server [port] [--host=<address>]');
|
|
192
|
+
process.exit(0);
|
|
193
|
+
}
|
|
190
194
|
const port = Number(args.find((arg) => !arg.startsWith('--')) ?? 8787);
|
|
191
195
|
const host = args.find((arg) => arg.startsWith('--host='))?.slice(7) ?? '127.0.0.1';
|
|
192
196
|
const server = createLockstepServer({ port, host });
|
package/tools/mwl.mjs
CHANGED
|
@@ -65,6 +65,13 @@ const assetRoot = assetRootFlag === -1 ? undefined : args[assetRootFlag + 1];
|
|
|
65
65
|
const targetFlag = args.indexOf('--to');
|
|
66
66
|
const target = targetFlag === -1 ? undefined : args[targetFlag + 1];
|
|
67
67
|
|
|
68
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
69
|
+
console.log(
|
|
70
|
+
'usage: mwg-mwl <validate|compile|format|migrate|extract-i18n|assets|report|diagnostics|ci|hooks|build> input.mwl [-o output] [--to 1.0] [--manifest hooks.json] [--asset-root directory]',
|
|
71
|
+
);
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
68
75
|
if (!command || !input || !commands.includes(command)) {
|
|
69
76
|
console.error(
|
|
70
77
|
'Usage: mwl <validate|compile|format|migrate|extract-i18n|assets|report|diagnostics|ci|hooks|build> input.mwl [-o output] [--to 1.0] [--manifest hooks.json] [--asset-root directory]',
|
package/tools/play-uci.mjs
CHANGED
|
@@ -171,6 +171,10 @@ const isMain = process.argv[1] && realpathSync(process.argv[1]) === fileURLToPat
|
|
|
171
171
|
|
|
172
172
|
if (isMain) {
|
|
173
173
|
const args = process.argv.slice(2);
|
|
174
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
175
|
+
console.log('usage: mwg-uci [--book <book.json>] [--tables <dir>] [--depth N]');
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
174
178
|
const flag = (name) => {
|
|
175
179
|
const at = args.indexOf(name);
|
|
176
180
|
return at === -1 ? undefined : args[at + 1];
|
package/tools/sbom.mjs
CHANGED
|
@@ -301,6 +301,10 @@ async function readJson(path) {
|
|
|
301
301
|
* `application`, anything else as a `library`. `--check` compares instead of writing, for CI.
|
|
302
302
|
*/
|
|
303
303
|
async function main(argv) {
|
|
304
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
305
|
+
console.log('usage: mwg-sbom [project folder] [--out=<file>] [--check]');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
304
308
|
const root = resolve(argv.find((arg) => !arg.startsWith('--')) ?? '.');
|
|
305
309
|
const out = resolve(root, argv.find((arg) => arg.startsWith('--out='))?.slice(6) ?? OUTPUT);
|
|
306
310
|
const packageJson = await readJson(join(root, 'package.json'));
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** how freely a catalogue font may be shipped inside a game */
|
|
2
|
+
export type SoundFontLicenceClass =
|
|
3
|
+
'public-domain' | 'permissive' | 'attribution' | 'share-alike' | 'copyleft' | 'check';
|
|
4
|
+
|
|
5
|
+
export interface SoundFontCatalogEntry {
|
|
6
|
+
name: string;
|
|
7
|
+
licence: string;
|
|
8
|
+
licenceClass: SoundFontLicenceClass;
|
|
9
|
+
web?: string;
|
|
10
|
+
/** instrument files the collection carries for this font (programs for a bundled bank) */
|
|
11
|
+
programs: number;
|
|
12
|
+
/** megabytes of a full General MIDI bank, SoundFont 3 compressed; absent for the bundled bank */
|
|
13
|
+
gmMB?: number;
|
|
14
|
+
/** megabytes of every instrument file, SoundFont 3 compressed (the bundled bank's own size) */
|
|
15
|
+
fullMB: number;
|
|
16
|
+
/** the file of a bundled bank, relative to the package root */
|
|
17
|
+
file?: string;
|
|
18
|
+
note?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SoundFontCatalog {
|
|
22
|
+
description: string;
|
|
23
|
+
/** where the per-instrument files are fetched from by default */
|
|
24
|
+
source: string;
|
|
25
|
+
/** the name of the bank the package ships and games use unless they choose another */
|
|
26
|
+
default: string;
|
|
27
|
+
bundled: SoundFontCatalogEntry[];
|
|
28
|
+
fonts: SoundFontCatalogEntry[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SoundFontProgramRef {
|
|
32
|
+
bank: number;
|
|
33
|
+
program: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface FetchSoundFontOptions {
|
|
37
|
+
/** a catalogue font name, from `mwg-soundfonts list` */
|
|
38
|
+
name: string;
|
|
39
|
+
programs: SoundFontProgramRef[];
|
|
40
|
+
catalog?: SoundFontCatalog;
|
|
41
|
+
/** a folder or `http(s)` URL laid out as `<font>/<bank>/<program>.sf3`; the catalogue's source by default */
|
|
42
|
+
source?: string;
|
|
43
|
+
/** required for a licence class that is not public-domain, permissive or attribution */
|
|
44
|
+
acceptLicence?: boolean;
|
|
45
|
+
/** the decoded font is a SoundFont 2 (`ffmpeg` needed) instead of a SoundFont 3; `sf3` by default */
|
|
46
|
+
format?: 'sf2' | 'sf3';
|
|
47
|
+
/** Ogg Vorbis bytes to mono 16-bit frames, for `format: 'sf2'`; `ffmpeg` by default */
|
|
48
|
+
decode?: (ogg: Uint8Array, rate: number) => Int16Array | Promise<Int16Array>;
|
|
49
|
+
/** replaces the file reader, mainly for tests */
|
|
50
|
+
read?: (path: string) => Promise<Uint8Array | null>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface FetchedSoundFont {
|
|
54
|
+
/** one font holding the found programs, in `format` */
|
|
55
|
+
bytes: Uint8Array;
|
|
56
|
+
format: 'sf2' | 'sf3';
|
|
57
|
+
found: SoundFontProgramRef[];
|
|
58
|
+
/** requested programs the collection does not carry */
|
|
59
|
+
missing: SoundFontProgramRef[];
|
|
60
|
+
font: SoundFontCatalogEntry;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** the licence-classified catalogue of fetchable fonts */
|
|
64
|
+
export declare const CATALOG_PATH: string;
|
|
65
|
+
export declare function readCatalog(path?: string): SoundFontCatalog;
|
|
66
|
+
export declare function findFont(catalog: SoundFontCatalog, name: string): SoundFontCatalogEntry | undefined;
|
|
67
|
+
/** `"0,24,128:0"`: a bare number is a program of bank 0, `bank:program` names both */
|
|
68
|
+
export declare function parseProgramList(text: string): SoundFontProgramRef[];
|
|
69
|
+
/** mono 16-bit frames from Ogg Vorbis bytes, through `ffmpeg` */
|
|
70
|
+
export declare function ffmpegDecode(ogg: Uint8Array): Int16Array;
|
|
71
|
+
/** a SoundFont 3 decoded into SoundFont 2 bytes (`ffmpeg` unless `decode` is given) */
|
|
72
|
+
export declare function sf3ToSf2(
|
|
73
|
+
bytes: Uint8Array,
|
|
74
|
+
decode?: (ogg: Uint8Array, rate: number) => Int16Array | Promise<Int16Array>,
|
|
75
|
+
): Promise<Uint8Array>;
|
|
76
|
+
export declare function readCollectionFile(source: string, path: string): Promise<Uint8Array | null>;
|
|
77
|
+
export declare function fetchSoundFont(options: FetchSoundFontOptions): Promise<FetchedSoundFont>;
|
|
78
|
+
/** the credits note that travels beside a fetched font */
|
|
79
|
+
export declare function creditsFor(font: SoundFontCatalogEntry, programs: SoundFontProgramRef[]): string;
|