animader-cli 0.0.0-stage → 0.1.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.
Files changed (71) hide show
  1. package/LICENSE +121 -0
  2. package/adapters/build-node.mjs +265 -0
  3. package/adapters/devserver.mjs +481 -0
  4. package/adapters/devsession.mjs +619 -0
  5. package/adapters/export-node.mjs +603 -0
  6. package/adapters/export-run.mjs +126 -0
  7. package/adapters/generations.mjs +183 -0
  8. package/adapters/http.mjs +294 -0
  9. package/adapters/launch.mjs +83 -0
  10. package/adapters/node-fs.mjs +80 -0
  11. package/adapters/pngine.mjs +87 -0
  12. package/adapters/snapshot.mjs +143 -0
  13. package/adapters/target.mjs +104 -0
  14. package/adapters/watch.mjs +355 -0
  15. package/adapters/webaudio.mjs +102 -0
  16. package/bin/animader.mjs +31 -0
  17. package/hello/join.js +46 -0
  18. package/hello/run.js +259 -0
  19. package/lib/build.mjs +126 -0
  20. package/lib/buildloop.mjs +254 -0
  21. package/lib/contract.mjs +371 -0
  22. package/lib/errors.mjs +51 -0
  23. package/lib/export.mjs +445 -0
  24. package/lib/new.mjs +304 -0
  25. package/lib/pipeline.mjs +247 -0
  26. package/lib/receipt.mjs +331 -0
  27. package/lib/rmtree.mjs +49 -0
  28. package/lib/routes.mjs +170 -0
  29. package/lib/score.mjs +434 -0
  30. package/lib/show.mjs +458 -0
  31. package/lib/soundtrack.mjs +67 -0
  32. package/lib/vendor.mjs +99 -0
  33. package/lib/wav.mjs +197 -0
  34. package/package.json +47 -4
  35. package/player/boot.mjs +792 -0
  36. package/player/controller.mjs +366 -0
  37. package/player/devchannel.mjs +306 -0
  38. package/player/index.html +35 -0
  39. package/player/page.mjs +134 -0
  40. package/player/player.css +393 -0
  41. package/player/release.mjs +76 -0
  42. package/src/commands/build.mjs +201 -0
  43. package/src/commands/dev.mjs +214 -0
  44. package/src/commands/doctor.mjs +348 -0
  45. package/src/commands/export.mjs +158 -0
  46. package/src/commands/new.mjs +164 -0
  47. package/src/commands/project.mjs +46 -0
  48. package/src/config.mjs +110 -0
  49. package/src/router.mjs +149 -0
  50. package/src/toolchain.mjs +691 -0
  51. package/starters/circle/README.md +50 -0
  52. package/starters/circle/animader.json +4 -0
  53. package/starters/circle/gitignore +9 -0
  54. package/starters/circle/kernels/circle.wgsl +32 -0
  55. package/starters/circle/program.sjon +34 -0
  56. package/starters/circle/timeline.piece.sjon +26 -0
  57. package/starters/ember/bake/spans.bin +0 -0
  58. package/starters/ember/kernels/curl.wgsl +10 -0
  59. package/starters/ember/kernels/ember.wgsl +10 -0
  60. package/starters/ember/kernels/hash.wgsl +12 -0
  61. package/starters/ember/kernels/simplex.wgsl +27 -0
  62. package/starters/ember/program.sjon +328 -0
  63. package/starters/ember/timeline.piece.sjon +34 -0
  64. package/toolchain/licenses/animader.txt +121 -0
  65. package/toolchain/licenses/esbuild.txt +21 -0
  66. package/toolchain/licenses/pacer.txt +121 -0
  67. package/toolchain/licenses/pngine.txt +121 -0
  68. package/toolchain/manifest.json +212 -0
  69. package/toolchain/prepare.mjs +446 -0
  70. package/toolchain/uniform-type-name.js +184 -0
  71. package/README.md +0 -3
package/lib/wav.mjs ADDED
@@ -0,0 +1,197 @@
1
+ // The fixture's soundtrack helper: a deterministic, clearly labeled 16-bit
2
+ // PCM WAV smoke tone. It is not party art or music, and it is never fetched
3
+ // from the network. The RIFF header, an INFO/INAM label, and the sample
4
+ // count are the contract the tests and the page can audit.
5
+ //
6
+ // Ownership: media bytes for the generated fixture. Writing the file and
7
+ // hashing it are host adapter work.
8
+
9
+ import { DemoError } from './errors.mjs';
10
+
11
+ export const TONE_LABEL = 'Animader demo test tone (not music)';
12
+
13
+ export const TONE = Object.freeze({
14
+ seconds: 8,
15
+ sampleRate: 48000,
16
+ channels: 2,
17
+ bitsPerSample: 16,
18
+ frequency: 440,
19
+ amplitude: 0.12,
20
+ });
21
+
22
+ const PCM = 1;
23
+ const ENCODER = new TextEncoder();
24
+ const DECODER = new TextDecoder();
25
+
26
+ function toneError(message) {
27
+ return new DemoError('invalid_tone_duration', message, {
28
+ remedy: 'Use a positive finite duration the sample rate can represent exactly.',
29
+ });
30
+ }
31
+
32
+ function writeAscii(bytes, offset, text) {
33
+ for (let index = 0; index < text.length; index++) bytes[offset + index] = text.charCodeAt(index) & 0xff;
34
+ }
35
+
36
+ function writeChunkHeader(bytes, offset, id, size) {
37
+ writeAscii(bytes, offset, id);
38
+ new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).setUint32(offset + 4, size, true);
39
+ }
40
+
41
+ /**
42
+ * Create the labeled smoke tone as bytes. No file, network or browser is
43
+ * involved, so the same call is used by the prepare adapter and by tests.
44
+ *
45
+ * @param {Object} [options]
46
+ * @param {number} [options.seconds]
47
+ * @param {number} [options.sampleRate]
48
+ * @param {number} [options.channels]
49
+ * @param {string} [options.label]
50
+ * @returns {Uint8Array}
51
+ */
52
+ export function createTestTone(options = {}) {
53
+ const seconds = options.seconds ?? TONE.seconds;
54
+ const sampleRate = options.sampleRate ?? TONE.sampleRate;
55
+ const channels = options.channels ?? TONE.channels;
56
+ const label = options.label ?? TONE_LABEL;
57
+
58
+ if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds <= 0) {
59
+ throw toneError(`seconds must be a positive finite number; got ${String(seconds)}`);
60
+ }
61
+ if (!Number.isInteger(sampleRate) || sampleRate <= 0 || sampleRate > 384000) {
62
+ throw toneError(`sampleRate must be a positive integer; got ${String(sampleRate)}`);
63
+ }
64
+ if (!Number.isInteger(channels) || channels < 1 || channels > 2) {
65
+ throw toneError(`channels must be 1 or 2; got ${String(channels)}`);
66
+ }
67
+ const frames = Math.round(seconds * sampleRate);
68
+ if (frames < 2) throw toneError('the duration is too short to carry a frame');
69
+ if (Math.abs(frames / sampleRate - seconds) > 0.5 / sampleRate) {
70
+ throw toneError(`seconds ${seconds} cannot be represented exactly at ${sampleRate} Hz`);
71
+ }
72
+
73
+ const labelBytes = ENCODER.encode(`${String(label).replace(/\0/g, '')}\0`);
74
+ const labelPadded = labelBytes.length + (labelBytes.length % 2);
75
+ const listSize = 4 + 8 + labelPadded;
76
+ const dataBytes = frames * channels * 2;
77
+ const fileBytes = 12 + 8 + 16 + 8 + listSize + 8 + dataBytes;
78
+ const bytes = new Uint8Array(fileBytes);
79
+ const view = new DataView(bytes.buffer);
80
+
81
+ writeAscii(bytes, 0, 'RIFF');
82
+ view.setUint32(4, fileBytes - 8, true);
83
+ writeAscii(bytes, 8, 'WAVE');
84
+
85
+ let offset = 12;
86
+ writeChunkHeader(bytes, offset, 'fmt ', 16);
87
+ offset += 8;
88
+ view.setUint16(offset, PCM, true);
89
+ view.setUint16(offset + 2, channels, true);
90
+ view.setUint32(offset + 4, sampleRate, true);
91
+ view.setUint32(offset + 8, sampleRate * channels * 2, true);
92
+ view.setUint16(offset + 12, channels * 2, true);
93
+ view.setUint16(offset + 14, 16, true);
94
+ offset += 16;
95
+
96
+ writeChunkHeader(bytes, offset, 'LIST', listSize);
97
+ offset += 8;
98
+ writeAscii(bytes, offset, 'INFO');
99
+ offset += 4;
100
+ writeChunkHeader(bytes, offset, 'INAM', labelBytes.length);
101
+ offset += 8;
102
+ bytes.set(labelBytes, offset);
103
+ offset += labelPadded;
104
+
105
+ writeChunkHeader(bytes, offset, 'data', dataBytes);
106
+ offset += 8;
107
+ const dataOffset = offset;
108
+
109
+ const fade = Math.min(Math.round(sampleRate * 0.01), Math.floor(frames / 2));
110
+ for (let frame = 0; frame < frames; frame++) {
111
+ const envelope = Math.min(1, frame / fade, (frames - 1 - frame) / fade);
112
+ const sample = Math.round(Math.sin((2 * Math.PI * TONE.frequency * frame) / sampleRate) * TONE.amplitude * 32767 * envelope);
113
+ for (let channel = 0; channel < channels; channel++) {
114
+ view.setInt16(dataOffset + (frame * channels + channel) * 2, sample, true);
115
+ }
116
+ }
117
+
118
+ return bytes;
119
+ }
120
+
121
+ /**
122
+ * Read the RIFF metadata a fixture proof can audit without decoding audio.
123
+ *
124
+ * @param {Uint8Array|ArrayBuffer} input
125
+ * @returns {{format: string, audioFormat: number, channels: number, sampleRate: number,
126
+ * byteRate: number, blockAlign: number, bitsPerSample: number, frames: number,
127
+ * seconds: number, dataOffset: number, dataBytes: number, label: string|null}}
128
+ */
129
+ export function readWavInfo(input) {
130
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
131
+ if (bytes.length < 44 || String.fromCharCode(...bytes.subarray(0, 4)) !== 'RIFF' || String.fromCharCode(...bytes.subarray(8, 12)) !== 'WAVE') {
132
+ throw new Error('not a RIFF/WAVE file');
133
+ }
134
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
135
+ let audioFormat = null;
136
+ let channels = null;
137
+ let sampleRate = null;
138
+ let byteRate = null;
139
+ let blockAlign = null;
140
+ let bitsPerSample = null;
141
+ let dataOffset = null;
142
+ let dataBytes = null;
143
+ let label = null;
144
+
145
+ let offset = 12;
146
+ while (offset + 8 <= bytes.length) {
147
+ const id = String.fromCharCode(...bytes.subarray(offset, offset + 4));
148
+ const size = view.getUint32(offset + 4, true);
149
+ const body = offset + 8;
150
+ if (body + size > bytes.length) throw new Error(`WAV chunk ${id} overruns the file`);
151
+ if (id === 'fmt ') {
152
+ if (size < 16) throw new Error('WAV fmt chunk is short');
153
+ audioFormat = view.getUint16(body, true);
154
+ channels = view.getUint16(body + 2, true);
155
+ sampleRate = view.getUint32(body + 4, true);
156
+ byteRate = view.getUint32(body + 8, true);
157
+ blockAlign = view.getUint16(body + 12, true);
158
+ bitsPerSample = view.getUint16(body + 14, true);
159
+ } else if (id === 'data') {
160
+ dataOffset = body;
161
+ dataBytes = size;
162
+ } else if (id === 'LIST' && size >= 4 && String.fromCharCode(...bytes.subarray(body, body + 4)) === 'INFO') {
163
+ let at = body + 4;
164
+ const end = body + size;
165
+ while (at + 8 <= end) {
166
+ const subId = String.fromCharCode(...bytes.subarray(at, at + 4));
167
+ const subSize = view.getUint32(at + 4, true);
168
+ const subBody = at + 8;
169
+ if (subBody + subSize > end) break;
170
+ if (subId === 'INAM') {
171
+ label = DECODER.decode(bytes.subarray(subBody, subBody + subSize)).replace(/\0+$/, '');
172
+ }
173
+ at = subBody + subSize + (subSize % 2);
174
+ }
175
+ }
176
+ offset = body + size + (size % 2);
177
+ }
178
+
179
+ if (audioFormat === null || dataOffset === null || byteRate === null || blockAlign === null) {
180
+ throw new Error('WAV is missing its fmt or data chunk');
181
+ }
182
+ const frames = dataBytes / blockAlign;
183
+ return {
184
+ format: 'WAVE',
185
+ audioFormat,
186
+ channels,
187
+ sampleRate,
188
+ byteRate,
189
+ blockAlign,
190
+ bitsPerSample,
191
+ frames,
192
+ seconds: frames / sampleRate,
193
+ dataOffset,
194
+ dataBytes,
195
+ label,
196
+ };
197
+ }
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "animader-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "The animader command line: project creation, dev server, build, standalone export and host/project doctor checks",
5
+ "type": "module",
6
+ "bin": {
7
+ "animader": "./bin/animader.mjs"
8
+ },
9
+ "engines": {
10
+ "node": ">=22"
11
+ },
12
+ "license": "CC0-1.0",
13
+ "dependencies": {
14
+ "animader": "0.1.1",
15
+ "esbuild": "0.24.2",
16
+ "@mr_hugo/pacer": "0.2.0",
17
+ "pngine": "4.1.0"
18
+ },
19
+ "optionalDependencies": {
20
+ "@mr_hugo/pacer-darwin-arm64": "0.2.0",
21
+ "@pngine/darwin-arm64": "4.1.0"
22
+ },
23
+ "files": [
24
+ "bin",
25
+ "src",
26
+ "lib",
27
+ "adapters",
28
+ "player",
29
+ "hello",
30
+ "starters",
31
+ "toolchain",
32
+ "LICENSE"
33
+ ],
34
+ "exports": {
35
+ ".": "./src/router.mjs",
36
+ "./config": "./src/config.mjs",
37
+ "./new": "./src/commands/new.mjs",
38
+ "./dev": "./src/commands/dev.mjs",
39
+ "./build": "./src/commands/build.mjs",
40
+ "./export": "./src/commands/export.mjs",
41
+ "./doctor": "./src/commands/doctor.mjs",
42
+ "./package.json": "./package.json"
43
+ },
44
+ "scripts": {
45
+ "test": "node --test",
46
+ "release:accept": "node scripts/release-accept.mjs",
47
+ "release:accept:self-test": "node scripts/release-accept.mjs --self-test-missing-artifact"
48
+ }
49
+ }