@tongue-core/engine 0.1.0-rc.4 → 0.1.0-rc.6

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.
@@ -1,3 +1,6 @@
1
1
  import type { PcmAudio } from "./types.js";
2
2
  export declare function readWav(bytes: Uint8Array): PcmAudio;
3
+ /** 32-bit float WAV: every sample kept exactly as it is, with no dither and no clipping. For audio
4
+ * between steps (a stem on its way to a processor), where 16 bits would round it each time. */
5
+ export declare function writeFloatWav(audio: PcmAudio): Uint8Array;
3
6
  export declare function writeWav(audio: PcmAudio, seed?: number): Uint8Array;
@@ -96,7 +96,9 @@ function random(seed) {
96
96
  return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
97
97
  };
98
98
  }
99
- export function writeWav(audio, seed = 0x5eed) {
99
+ /** A WAV header for `frames` of `channels` samples of `bits` each, in the given format; the
100
+ * samples go after it, at byte 44. */
101
+ function header(audio, format, bits) {
100
102
  const channels = audio.channels.length;
101
103
  const frames = audio.channels[0]?.length ?? 0;
102
104
  if (channels === 0)
@@ -105,21 +107,39 @@ export function writeWav(audio, seed = 0x5eed) {
105
107
  fail("channels have different lengths");
106
108
  if (!Number.isInteger(audio.sampleRate) || audio.sampleRate <= 0)
107
109
  fail(`bad sample rate ${audio.sampleRate}`);
108
- const dataLength = frames * channels * 2;
110
+ const bytes = bits / 8;
111
+ const dataLength = frames * channels * bytes;
109
112
  const out = new Uint8Array(44 + dataLength);
110
113
  const view = new DataView(out.buffer);
111
114
  out.set([0x52, 0x49, 0x46, 0x46]); // RIFF
112
115
  view.setUint32(4, 36 + dataLength, true);
113
116
  out.set([0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20], 8); // WAVEfmt␠
114
117
  view.setUint32(16, 16, true);
115
- view.setUint16(20, FORMAT_PCM, true);
118
+ view.setUint16(20, format, true);
116
119
  view.setUint16(22, channels, true);
117
120
  view.setUint32(24, audio.sampleRate, true);
118
- view.setUint32(28, audio.sampleRate * channels * 2, true);
119
- view.setUint16(32, channels * 2, true);
120
- view.setUint16(34, 16, true);
121
+ view.setUint32(28, audio.sampleRate * channels * bytes, true);
122
+ view.setUint16(32, channels * bytes, true);
123
+ view.setUint16(34, bits, true);
121
124
  out.set([0x64, 0x61, 0x74, 0x61], 36); // data
122
125
  view.setUint32(40, dataLength, true);
126
+ return { out, view, frames };
127
+ }
128
+ /** 32-bit float WAV: every sample kept exactly as it is, with no dither and no clipping. For audio
129
+ * between steps (a stem on its way to a processor), where 16 bits would round it each time. */
130
+ export function writeFloatWav(audio) {
131
+ const { out, view, frames } = header(audio, FORMAT_FLOAT, 32);
132
+ let at = 44;
133
+ for (let f = 0; f < frames; f++) {
134
+ for (const channel of audio.channels) {
135
+ view.setFloat32(at, channel[f] ?? 0, true);
136
+ at += 4;
137
+ }
138
+ }
139
+ return out;
140
+ }
141
+ export function writeWav(audio, seed = 0x5eed) {
142
+ const { view, out, frames } = header(audio, FORMAT_PCM, 16);
123
143
  // TPDF dither: the difference of two uniform variables spans ±1 LSB with a triangular
124
144
  // distribution, which decorrelates the rounding error from the signal.
125
145
  const next = random(seed);
@@ -43,4 +43,10 @@ export interface WindowInput {
43
43
  /** The part of the soundtrack between two frames: what a player mixes a few seconds at a time, so
44
44
  * an hour-long film never needs its whole soundtrack in memory. Exactly that part of `mix`, except
45
45
  * where a clip changes speed (time-stretching is padded, not continued from the window before). */
46
- export declare function mixWindow({ fps, clips, sounds, from, to }: WindowInput): PcmAudio;
46
+ export declare function mixWindow(input: WindowInput): PcmAudio;
47
+ /** The peak limiter the soundtrack ends with, on audio summed some other way (stems mixed
48
+ * separately, then added together). Per sample, so `limitAudio(busWindow(x))` is `mixWindow(x)`. */
49
+ export declare function limitAudio(audio: PcmAudio): PcmAudio;
50
+ /** What `mixWindow` sums before it limits: the clips and sounds between two frames, stereo at
51
+ * 48 kHz, unlimited. A stem (some of a film's clips, mixed on their own) is this over those clips. */
52
+ export declare function busWindow({ fps, clips, sounds, from, to }: WindowInput): PcmAudio;
package/dist/audio/mix.js CHANGED
@@ -216,7 +216,17 @@ const STRETCH_PAD = 4 * WINDOW;
216
216
  /** The part of the soundtrack between two frames: what a player mixes a few seconds at a time, so
217
217
  * an hour-long film never needs its whole soundtrack in memory. Exactly that part of `mix`, except
218
218
  * where a clip changes speed (time-stretching is padded, not continued from the window before). */
219
- export function mixWindow({ fps, clips, sounds, from, to }) {
219
+ export function mixWindow(input) {
220
+ return limitAudio(busWindow(input));
221
+ }
222
+ /** The peak limiter the soundtrack ends with, on audio summed some other way (stems mixed
223
+ * separately, then added together). Per sample, so `limitAudio(busWindow(x))` is `mixWindow(x)`. */
224
+ export function limitAudio(audio) {
225
+ return { sampleRate: audio.sampleRate, channels: audio.channels.map(limit) };
226
+ }
227
+ /** What `mixWindow` sums before it limits: the clips and sounds between two frames, stereo at
228
+ * 48 kHz, unlimited. A stem (some of a film's clips, mixed on their own) is this over those clips. */
229
+ export function busWindow({ fps, clips, sounds, from, to }) {
220
230
  const start = Math.round((from / fps) * SAMPLE_RATE);
221
231
  const length = Math.max(0, Math.ceil((to / fps) * SAMPLE_RATE) - start);
222
232
  const left = new Float32Array(length), right = new Float32Array(length);
@@ -242,5 +252,5 @@ export function mixWindow({ fps, clips, sounds, from, to }) {
242
252
  add(left, sound, at, gain);
243
253
  add(right, sound, at, gain);
244
254
  }
245
- return { sampleRate: SAMPLE_RATE, channels: [limit(left), limit(right)] };
255
+ return { sampleRate: SAMPLE_RATE, channels: [left, right] };
246
256
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { FilmRenderer, sizeAtScale, type FrameOptions, type FrameSize, type DrawnFrame, type ImageUpload } from "./render/renderer.js";
2
2
  export { collectSounds, audioClips } from "./render/sounds.js";
3
- export { mix, mixWindow, SAMPLE_RATE, SOUND_SECONDS, type MixInput, type WindowInput, type ClipAudio } from "./audio/mix.js";
3
+ export { mix, mixWindow, busWindow, limitAudio, SAMPLE_RATE, SOUND_SECONDS, type MixInput, type WindowInput, type ClipAudio } from "./audio/mix.js";
4
4
  export { loadAssets, AssetShelf, ImageNotLoaded, imageOf, videoOf, audioOf, parseVideoInfo, parseProxyInfo, qoiSize, imageSize, AssetError, type LoadedAssets, type LoadedImage, type LoadedVideo, type VideoInfo, type ProxyInfo, type Fetcher, type LoadOptions } from "./assets/store.js";
5
5
  export { importedPath, importedFiles, videoInfoPath, videoPosterPath, videoProxyDir, videoProxyPath, PROXY_SEGMENT_SECONDS, PROXY_KEYFRAME_SECONDS, type AssetKey } from "./assets/paths.js";
6
6
  export { demuxMp4, sampleData, Mp4Error, type Mp4Video, type Mp4Sample } from "./media/mp4.js";
@@ -15,7 +15,7 @@ export { decodePng } from "./assets/codecs/png.js";
15
15
  export { decodeJpeg } from "./assets/codecs/jpeg.js";
16
16
  export { encodeQoi, decodeQoi } from "./assets/codecs/qoi.js";
17
17
  export { resizeImage } from "./assets/codecs/resize.js";
18
- export { readWav, writeWav } from "./assets/codecs/wav.js";
18
+ export { readWav, writeWav, writeFloatWav } from "./assets/codecs/wav.js";
19
19
  export { woff2ToSfnt } from "./text/woff2.js";
20
20
  export { parseFont } from "./text/ttf.js";
21
21
  export type { RgbaImage, PcmAudio } from "./assets/codecs/types.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // raster), so the same frame is byte-identical in Bun, browsers and Workers.
4
4
  export { FilmRenderer, sizeAtScale } from "./render/renderer.js";
5
5
  export { collectSounds, audioClips } from "./render/sounds.js";
6
- export { mix, mixWindow, SAMPLE_RATE, SOUND_SECONDS } from "./audio/mix.js";
6
+ export { mix, mixWindow, busWindow, limitAudio, SAMPLE_RATE, SOUND_SECONDS } from "./audio/mix.js";
7
7
  export { loadAssets, AssetShelf, ImageNotLoaded, imageOf, videoOf, audioOf, parseVideoInfo, parseProxyInfo, qoiSize, imageSize, AssetError } from "./assets/store.js";
8
8
  export { importedPath, importedFiles, videoInfoPath, videoPosterPath, videoProxyDir, videoProxyPath, PROXY_SEGMENT_SECONDS, PROXY_KEYFRAME_SECONDS } from "./assets/paths.js";
9
9
  export { demuxMp4, sampleData, Mp4Error } from "./media/mp4.js";
@@ -16,6 +16,6 @@ export { decodePng } from "./assets/codecs/png.js";
16
16
  export { decodeJpeg } from "./assets/codecs/jpeg.js";
17
17
  export { encodeQoi, decodeQoi } from "./assets/codecs/qoi.js";
18
18
  export { resizeImage } from "./assets/codecs/resize.js";
19
- export { readWav, writeWav } from "./assets/codecs/wav.js";
19
+ export { readWav, writeWav, writeFloatWav } from "./assets/codecs/wav.js";
20
20
  export { woff2ToSfnt } from "./text/woff2.js";
21
21
  export { parseFont } from "./text/ttf.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tongue-core/engine",
3
- "version": "0.1.0-rc.4",
3
+ "version": "0.1.0-rc.6",
4
4
  "description": "Frame to pixels for Tongue films: evaluate, layout, paint and rasterize, deterministic everywhere.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,7 @@
26
26
  "README.md"
27
27
  ],
28
28
  "dependencies": {
29
- "@tongue-core/film": "0.1.0-rc.4"
29
+ "@tongue-core/film": "0.1.0-rc.6"
30
30
  },
31
31
  "publishConfig": {
32
32
  "access": "public"