@clipkit/runtime 1.0.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/LICENSE +54 -0
- package/README.md +98 -0
- package/dist/animation/easings.d.ts +9 -0
- package/dist/animation/easings.d.ts.map +1 -0
- package/dist/animation/easings.js +230 -0
- package/dist/animation/easings.js.map +1 -0
- package/dist/animation/expr.d.ts +44 -0
- package/dist/animation/expr.d.ts.map +1 -0
- package/dist/animation/expr.js +236 -0
- package/dist/animation/expr.js.map +1 -0
- package/dist/animation/keyframes.d.ts +23 -0
- package/dist/animation/keyframes.d.ts.map +1 -0
- package/dist/animation/keyframes.js +117 -0
- package/dist/animation/keyframes.js.map +1 -0
- package/dist/animation/motion-path.d.ts +18 -0
- package/dist/animation/motion-path.d.ts.map +1 -0
- package/dist/animation/motion-path.js +269 -0
- package/dist/animation/motion-path.js.map +1 -0
- package/dist/animation/noise1d.d.ts +5 -0
- package/dist/animation/noise1d.d.ts.map +1 -0
- package/dist/animation/noise1d.js +27 -0
- package/dist/animation/noise1d.js.map +1 -0
- package/dist/animation/presets.d.ts +60 -0
- package/dist/animation/presets.d.ts.map +1 -0
- package/dist/animation/presets.js +221 -0
- package/dist/animation/presets.js.map +1 -0
- package/dist/assets/cache.d.ts +18 -0
- package/dist/assets/cache.d.ts.map +1 -0
- package/dist/assets/cache.js +56 -0
- package/dist/assets/cache.js.map +1 -0
- package/dist/assets/fonts.d.ts +20 -0
- package/dist/assets/fonts.d.ts.map +1 -0
- package/dist/assets/fonts.js +127 -0
- package/dist/assets/fonts.js.map +1 -0
- package/dist/assets/loader.d.ts +18 -0
- package/dist/assets/loader.d.ts.map +1 -0
- package/dist/assets/loader.js +87 -0
- package/dist/assets/loader.js.map +1 -0
- package/dist/assets/lut.d.ts +5 -0
- package/dist/assets/lut.d.ts.map +1 -0
- package/dist/assets/lut.js +77 -0
- package/dist/assets/lut.js.map +1 -0
- package/dist/assets/media-time.d.ts +31 -0
- package/dist/assets/media-time.d.ts.map +1 -0
- package/dist/assets/media-time.js +65 -0
- package/dist/assets/media-time.js.map +1 -0
- package/dist/assets/mp4-frame-source.d.ts +44 -0
- package/dist/assets/mp4-frame-source.d.ts.map +1 -0
- package/dist/assets/mp4-frame-source.js +387 -0
- package/dist/assets/mp4-frame-source.js.map +1 -0
- package/dist/audio/encoder.d.ts +31 -0
- package/dist/audio/encoder.d.ts.map +1 -0
- package/dist/audio/encoder.js +96 -0
- package/dist/audio/encoder.js.map +1 -0
- package/dist/audio/fades.d.ts +16 -0
- package/dist/audio/fades.d.ts.map +1 -0
- package/dist/audio/fades.js +43 -0
- package/dist/audio/fades.js.map +1 -0
- package/dist/audio/limiter.d.ts +8 -0
- package/dist/audio/limiter.d.ts.map +1 -0
- package/dist/audio/limiter.js +39 -0
- package/dist/audio/limiter.js.map +1 -0
- package/dist/audio/loader.d.ts +6 -0
- package/dist/audio/loader.d.ts.map +1 -0
- package/dist/audio/loader.js +42 -0
- package/dist/audio/loader.js.map +1 -0
- package/dist/audio/mixer.d.ts +17 -0
- package/dist/audio/mixer.d.ts.map +1 -0
- package/dist/audio/mixer.js +204 -0
- package/dist/audio/mixer.js.map +1 -0
- package/dist/audio/varispeed.d.ts +24 -0
- package/dist/audio/varispeed.d.ts.map +1 -0
- package/dist/audio/varispeed.js +114 -0
- package/dist/audio/varispeed.js.map +1 -0
- package/dist/audio/wav.d.ts +6 -0
- package/dist/audio/wav.d.ts.map +1 -0
- package/dist/audio/wav.js +62 -0
- package/dist/audio/wav.js.map +1 -0
- package/dist/backend/backend.d.ts +579 -0
- package/dist/backend/backend.d.ts.map +1 -0
- package/dist/backend/backend.js +17 -0
- package/dist/backend/backend.js.map +1 -0
- package/dist/backend/webgl-backend.d.ts +97 -0
- package/dist/backend/webgl-backend.d.ts.map +1 -0
- package/dist/backend/webgl-backend.js +2142 -0
- package/dist/backend/webgl-backend.js.map +1 -0
- package/dist/backend/webgpu-backend.d.ts +121 -0
- package/dist/backend/webgpu-backend.d.ts.map +1 -0
- package/dist/backend/webgpu-backend.js +2481 -0
- package/dist/backend/webgpu-backend.js.map +1 -0
- package/dist/compositor/bitfont.d.ts +8 -0
- package/dist/compositor/bitfont.d.ts.map +1 -0
- package/dist/compositor/bitfont.js +52 -0
- package/dist/compositor/bitfont.js.map +1 -0
- package/dist/compositor/camera.d.ts +5 -0
- package/dist/compositor/camera.d.ts.map +1 -0
- package/dist/compositor/camera.js +114 -0
- package/dist/compositor/camera.js.map +1 -0
- package/dist/compositor/color.d.ts +26 -0
- package/dist/compositor/color.d.ts.map +1 -0
- package/dist/compositor/color.js +189 -0
- package/dist/compositor/color.js.map +1 -0
- package/dist/compositor/element-renderers/caption.d.ts +4 -0
- package/dist/compositor/element-renderers/caption.d.ts.map +1 -0
- package/dist/compositor/element-renderers/caption.js +376 -0
- package/dist/compositor/element-renderers/caption.js.map +1 -0
- package/dist/compositor/element-renderers/group.d.ts +12 -0
- package/dist/compositor/element-renderers/group.d.ts.map +1 -0
- package/dist/compositor/element-renderers/group.js +259 -0
- package/dist/compositor/element-renderers/group.js.map +1 -0
- package/dist/compositor/element-renderers/image.d.ts +4 -0
- package/dist/compositor/element-renderers/image.d.ts.map +1 -0
- package/dist/compositor/element-renderers/image.js +97 -0
- package/dist/compositor/element-renderers/image.js.map +1 -0
- package/dist/compositor/element-renderers/lit.d.ts +6 -0
- package/dist/compositor/element-renderers/lit.d.ts.map +1 -0
- package/dist/compositor/element-renderers/lit.js +82 -0
- package/dist/compositor/element-renderers/lit.js.map +1 -0
- package/dist/compositor/element-renderers/particles.d.ts +4 -0
- package/dist/compositor/element-renderers/particles.d.ts.map +1 -0
- package/dist/compositor/element-renderers/particles.js +212 -0
- package/dist/compositor/element-renderers/particles.js.map +1 -0
- package/dist/compositor/element-renderers/shape.d.ts +4 -0
- package/dist/compositor/element-renderers/shape.d.ts.map +1 -0
- package/dist/compositor/element-renderers/shape.js +171 -0
- package/dist/compositor/element-renderers/shape.js.map +1 -0
- package/dist/compositor/element-renderers/svg.d.ts +4 -0
- package/dist/compositor/element-renderers/svg.d.ts.map +1 -0
- package/dist/compositor/element-renderers/svg.js +210 -0
- package/dist/compositor/element-renderers/svg.js.map +1 -0
- package/dist/compositor/element-renderers/text.d.ts +25 -0
- package/dist/compositor/element-renderers/text.d.ts.map +1 -0
- package/dist/compositor/element-renderers/text.js +1358 -0
- package/dist/compositor/element-renderers/text.js.map +1 -0
- package/dist/compositor/element-renderers/video.d.ts +12 -0
- package/dist/compositor/element-renderers/video.d.ts.map +1 -0
- package/dist/compositor/element-renderers/video.js +109 -0
- package/dist/compositor/element-renderers/video.js.map +1 -0
- package/dist/compositor/fit.d.ts +18 -0
- package/dist/compositor/fit.d.ts.map +1 -0
- package/dist/compositor/fit.js +106 -0
- package/dist/compositor/fit.js.map +1 -0
- package/dist/compositor/lighting.d.ts +63 -0
- package/dist/compositor/lighting.d.ts.map +1 -0
- package/dist/compositor/lighting.js +141 -0
- package/dist/compositor/lighting.js.map +1 -0
- package/dist/compositor/mat4.d.ts +88 -0
- package/dist/compositor/mat4.d.ts.map +1 -0
- package/dist/compositor/mat4.js +245 -0
- package/dist/compositor/mat4.js.map +1 -0
- package/dist/compositor/project.d.ts +24 -0
- package/dist/compositor/project.d.ts.map +1 -0
- package/dist/compositor/project.js +105 -0
- package/dist/compositor/project.js.map +1 -0
- package/dist/compositor/render-context.d.ts +194 -0
- package/dist/compositor/render-context.d.ts.map +1 -0
- package/dist/compositor/render-context.js +10 -0
- package/dist/compositor/render-context.js.map +1 -0
- package/dist/compositor/resolve.d.ts +80 -0
- package/dist/compositor/resolve.d.ts.map +1 -0
- package/dist/compositor/resolve.js +276 -0
- package/dist/compositor/resolve.js.map +1 -0
- package/dist/compositor/scene.d.ts +10 -0
- package/dist/compositor/scene.d.ts.map +1 -0
- package/dist/compositor/scene.js +658 -0
- package/dist/compositor/scene.js.map +1 -0
- package/dist/compositor/transform.d.ts +73 -0
- package/dist/compositor/transform.d.ts.map +1 -0
- package/dist/compositor/transform.js +229 -0
- package/dist/compositor/transform.js.map +1 -0
- package/dist/compositor/unit.d.ts +27 -0
- package/dist/compositor/unit.d.ts.map +1 -0
- package/dist/compositor/unit.js +74 -0
- package/dist/compositor/unit.js.map +1 -0
- package/dist/encoder/exporter.d.ts +95 -0
- package/dist/encoder/exporter.d.ts.map +1 -0
- package/dist/encoder/exporter.js +341 -0
- package/dist/encoder/exporter.js.map +1 -0
- package/dist/encoder/index.d.ts +3 -0
- package/dist/encoder/index.d.ts.map +1 -0
- package/dist/encoder/index.js +2 -0
- package/dist/encoder/index.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +32 -0
- package/dist/logger.js.map +1 -0
- package/dist/runtime.d.ts +216 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1012 -0
- package/dist/runtime.js.map +1 -0
- package/dist/svg/morph.d.ts +6 -0
- package/dist/svg/morph.d.ts.map +1 -0
- package/dist/svg/morph.js +62 -0
- package/dist/svg/morph.js.map +1 -0
- package/dist/svg/svg-renderer.d.ts +18 -0
- package/dist/svg/svg-renderer.d.ts.map +1 -0
- package/dist/svg/svg-renderer.js +142 -0
- package/dist/svg/svg-renderer.js.map +1 -0
- package/dist/text/caption-chunk.d.ts +17 -0
- package/dist/text/caption-chunk.d.ts.map +1 -0
- package/dist/text/caption-chunk.js +76 -0
- package/dist/text/caption-chunk.js.map +1 -0
- package/dist/text/font-atlas.d.ts +63 -0
- package/dist/text/font-atlas.d.ts.map +1 -0
- package/dist/text/font-atlas.js +225 -0
- package/dist/text/font-atlas.js.map +1 -0
- package/dist/text/measure.d.ts +38 -0
- package/dist/text/measure.d.ts.map +1 -0
- package/dist/text/measure.js +164 -0
- package/dist/text/measure.js.map +1 -0
- package/dist/text/text-animation.d.ts +52 -0
- package/dist/text/text-animation.d.ts.map +1 -0
- package/dist/text/text-animation.js +133 -0
- package/dist/text/text-animation.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Master output limiter — a fixed, knob-less safety stage on the master bus,
|
|
2
|
+
// identical in preview (playback) and export (runtime mixer) so what you hear
|
|
3
|
+
// is what you render.
|
|
4
|
+
//
|
|
5
|
+
// It is TRANSPARENT below the ceiling: signal under the threshold passes at
|
|
6
|
+
// unity (Web Audio's DynamicsCompressorNode applies no makeup gain), so it
|
|
7
|
+
// only acts when the summed mix would otherwise clip past 0 dBFS — replacing
|
|
8
|
+
// ugly digital clipping with controlled limiting. This is an output-stage
|
|
9
|
+
// guarantee, NOT a document field: nothing here is serialized into the Source,
|
|
10
|
+
// and there are no per-element side effects (it sees only the final sum).
|
|
11
|
+
//
|
|
12
|
+
// Tradeoff: a DynamicsCompressorNode at ratio 20 is not a perfectly infinite
|
|
13
|
+
// brick-wall, but with -1 dB of headroom it keeps typical material comfortably
|
|
14
|
+
// under 0 dBFS without the lookahead cost of a custom limiter. Lean by design.
|
|
15
|
+
/** Threshold just below 0 dBFS — a hair of headroom before the ceiling. */
|
|
16
|
+
const THRESHOLD_DB = -1;
|
|
17
|
+
/** Hard knee — no soft transition, so quiet content stays untouched. */
|
|
18
|
+
const KNEE_DB = 0;
|
|
19
|
+
/** Steepest ratio Web Audio allows; effectively a limiter. */
|
|
20
|
+
const RATIO = 20;
|
|
21
|
+
/** Fast enough to catch transients without audible pumping. */
|
|
22
|
+
const ATTACK_S = 0.003;
|
|
23
|
+
const RELEASE_S = 0.1;
|
|
24
|
+
/**
|
|
25
|
+
* Create the master limiter node. Works on both AudioContext (preview) and
|
|
26
|
+
* OfflineAudioContext (export). Route the summed master gain through this,
|
|
27
|
+
* then to the destination — and tap meters POST-limiter so they show what
|
|
28
|
+
* you actually hear.
|
|
29
|
+
*/
|
|
30
|
+
export function createMasterLimiter(ctx) {
|
|
31
|
+
const limiter = ctx.createDynamicsCompressor();
|
|
32
|
+
limiter.threshold.value = THRESHOLD_DB;
|
|
33
|
+
limiter.knee.value = KNEE_DB;
|
|
34
|
+
limiter.ratio.value = RATIO;
|
|
35
|
+
limiter.attack.value = ATTACK_S;
|
|
36
|
+
limiter.release.value = RELEASE_S;
|
|
37
|
+
return limiter;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limiter.js","sourceRoot":"","sources":["../../src/audio/limiter.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,sBAAsB;AACtB,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,6EAA6E;AAC7E,0EAA0E;AAC1E,+EAA+E;AAC/E,0EAA0E;AAC1E,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAE/E,2EAA2E;AAC3E,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC;AACxB,wEAAwE;AACxE,MAAM,OAAO,GAAG,CAAC,CAAC;AAClB,8DAA8D;AAC9D,MAAM,KAAK,GAAG,EAAE,CAAC;AACjB,+DAA+D;AAC/D,MAAM,QAAQ,GAAG,KAAK,CAAC;AACvB,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAqB;IACvD,MAAM,OAAO,GAAG,GAAG,CAAC,wBAAwB,EAAE,CAAC;IAC/C,OAAO,CAAC,SAAS,CAAC,KAAK,GAAG,YAAY,CAAC;IACvC,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;IAC7B,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,QAAQ,CAAC;IAChC,OAAO,CAAC,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;IAClC,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/audio/loader.ts"],"names":[],"mappings":"AAmBA;;;GAGG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAmBjE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Audio loader. Fetches a URL and decodes it into an AudioBuffer that we
|
|
2
|
+
// can later play (preview) or mix offline (export).
|
|
3
|
+
//
|
|
4
|
+
// Uses the regular (non-offline) AudioContext to decode because
|
|
5
|
+
// decodeAudioData isn't exposed on OfflineAudioContext. The resulting
|
|
6
|
+
// AudioBuffer is detached from any context and can be used in either.
|
|
7
|
+
let sharedDecodeContext = null;
|
|
8
|
+
function getDecodeContext() {
|
|
9
|
+
if (!sharedDecodeContext) {
|
|
10
|
+
sharedDecodeContext = new AudioContext();
|
|
11
|
+
// Most browsers auto-suspend until a user gesture; suspend explicitly
|
|
12
|
+
// to avoid burning cycles during decode (we don't play through this one).
|
|
13
|
+
void sharedDecodeContext.suspend();
|
|
14
|
+
}
|
|
15
|
+
return sharedDecodeContext;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Fetch + decode a URL into an AudioBuffer. Throws on network or decode
|
|
19
|
+
* errors; callers should let the error propagate through preload().
|
|
20
|
+
*/
|
|
21
|
+
export async function loadAudio(url) {
|
|
22
|
+
// 10s fetch timeout (mirrors loadImage): a dead/stalled URL (e.g. a retired
|
|
23
|
+
// host) otherwise dangles a connection and leans entirely on preload()'s
|
|
24
|
+
// per-asset guard. AbortController cancels the fetch itself.
|
|
25
|
+
const ac = new AbortController();
|
|
26
|
+
const timer = setTimeout(() => ac.abort(), 10000);
|
|
27
|
+
let response;
|
|
28
|
+
try {
|
|
29
|
+
response = await fetch(url, { mode: 'cors', signal: ac.signal });
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
}
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`Failed to fetch audio ${url}: HTTP ${response.status}`);
|
|
36
|
+
}
|
|
37
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
38
|
+
const ctx = getDecodeContext();
|
|
39
|
+
// decodeAudioData returns a Promise in the modern API.
|
|
40
|
+
return ctx.decodeAudioData(arrayBuffer);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/audio/loader.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,oDAAoD;AACpD,EAAE;AACF,gEAAgE;AAChE,sEAAsE;AACtE,sEAAsE;AAEtE,IAAI,mBAAmB,GAAwB,IAAI,CAAC;AAEpD,SAAS,gBAAgB;IACvB,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,mBAAmB,GAAG,IAAI,YAAY,EAAE,CAAC;QACzC,sEAAsE;QACtE,0EAA0E;QAC1E,KAAK,mBAAmB,CAAC,OAAO,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,4EAA4E;IAC5E,yEAAyE;IACzE,6DAA6D;IAC7D,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,CAAC;IAClD,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IACnE,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,UAAU,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;IACjD,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,uDAAuD;IACvD,OAAO,GAAG,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Source } from '@clipkit/protocol';
|
|
2
|
+
export interface MixOptions {
|
|
3
|
+
/** Sample rate of the output buffer. Common: 48000 (matches AAC defaults). */
|
|
4
|
+
sampleRate?: number;
|
|
5
|
+
/** Number of channels. 2 = stereo. */
|
|
6
|
+
numberOfChannels?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Render all `audio` elements in a Source to a single mixed AudioBuffer.
|
|
10
|
+
*
|
|
11
|
+
* @param source The Source being exported.
|
|
12
|
+
* @param audioBuffers Map of element.source URL → decoded AudioBuffer (preloaded).
|
|
13
|
+
* @param totalDuration Duration of the export in seconds.
|
|
14
|
+
* @param options Sample rate / channels override.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mixSourceAudio(source: Source, audioBuffers: Map<string, AudioBuffer>, totalDuration: number, options?: MixOptions): Promise<AudioBuffer | null>;
|
|
17
|
+
//# sourceMappingURL=mixer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixer.d.ts","sourceRoot":"","sources":["../../src/audio/mixer.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAmC,MAAM,EAAgB,MAAM,mBAAmB,CAAC;AAyC/F,MAAM,WAAW,UAAU;IACzB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAKD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EACtC,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAkJ7B"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Offline audio mixing for export.
|
|
2
|
+
//
|
|
3
|
+
// Schedules each audio element — AND each video element's embedded
|
|
4
|
+
// audio track — on an OfflineAudioContext at its `time`, trimmed by
|
|
5
|
+
// `trim_start`/`trim_duration`, rate-adjusted by `playback_rate`
|
|
6
|
+
// (videos), capped at `duration`, with gain set from `volume`. The
|
|
7
|
+
// rendered AudioBuffer goes to the encoder.
|
|
8
|
+
//
|
|
9
|
+
// Volume keyframe animations are NOT applied in v1 — the static `volume`
|
|
10
|
+
// is used. Animating volume requires running a separate Web Audio graph
|
|
11
|
+
// per element with a GainNode driven by automation; small refactor when
|
|
12
|
+
// we want it.
|
|
13
|
+
import { interpolateKeyframes } from '../animation/keyframes.js';
|
|
14
|
+
import { mapToMediaTime, rateOf, timeRemapOf, trimDurationOf, trimWindow } from '../assets/media-time.js';
|
|
15
|
+
import { fadeBreakpoints } from './fades.js';
|
|
16
|
+
import { createMasterLimiter } from './limiter.js';
|
|
17
|
+
import { scheduleVarispeedRun, varispeedRuns } from './varispeed.js';
|
|
18
|
+
import { getLogger } from '../logger.js';
|
|
19
|
+
/** Recursively collect sound elements with their ancestor time scopes. */
|
|
20
|
+
function collectSound(elements, chain, parentDur, out) {
|
|
21
|
+
for (const el of elements) {
|
|
22
|
+
const start = numberOr(el.time, 0);
|
|
23
|
+
const dur = parseDuration(el.duration, parentDur - start);
|
|
24
|
+
if (el.type === 'audio' || el.type === 'video') {
|
|
25
|
+
out.push({ el: el, chain });
|
|
26
|
+
}
|
|
27
|
+
else if (el.type === 'group') {
|
|
28
|
+
const kids = el.elements;
|
|
29
|
+
if (Array.isArray(kids)) {
|
|
30
|
+
collectSound(kids, [...chain, { start, dur, remap: timeRemapOf(el.time_remap) }], dur, out);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const DEFAULT_SAMPLE_RATE = 48000;
|
|
36
|
+
const DEFAULT_CHANNELS = 2;
|
|
37
|
+
/**
|
|
38
|
+
* Render all `audio` elements in a Source to a single mixed AudioBuffer.
|
|
39
|
+
*
|
|
40
|
+
* @param source The Source being exported.
|
|
41
|
+
* @param audioBuffers Map of element.source URL → decoded AudioBuffer (preloaded).
|
|
42
|
+
* @param totalDuration Duration of the export in seconds.
|
|
43
|
+
* @param options Sample rate / channels override.
|
|
44
|
+
*/
|
|
45
|
+
export async function mixSourceAudio(source, audioBuffers, totalDuration, options = {}) {
|
|
46
|
+
const sampleRate = options.sampleRate ?? DEFAULT_SAMPLE_RATE;
|
|
47
|
+
const channels = options.numberOfChannels ?? DEFAULT_CHANNELS;
|
|
48
|
+
const totalFrames = Math.ceil(totalDuration * sampleRate);
|
|
49
|
+
const soundEntries = [];
|
|
50
|
+
collectSound(source.elements, [], totalDuration, soundEntries);
|
|
51
|
+
if (soundEntries.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
// OfflineAudioContext renders deterministically with no realtime constraint.
|
|
54
|
+
const ctx = new OfflineAudioContext({
|
|
55
|
+
numberOfChannels: channels,
|
|
56
|
+
length: totalFrames,
|
|
57
|
+
sampleRate,
|
|
58
|
+
});
|
|
59
|
+
// Master bus: every source sums into masterGain, then passes through a fixed
|
|
60
|
+
// safety limiter to the destination. The limiter is transparent below 0 dBFS
|
|
61
|
+
// and only engages when the sum would otherwise clip — identical to the
|
|
62
|
+
// preview scheduler's master bus, so export matches what you hear.
|
|
63
|
+
const masterGain = ctx.createGain();
|
|
64
|
+
const limiter = createMasterLimiter(ctx);
|
|
65
|
+
masterGain.connect(limiter).connect(ctx.destination);
|
|
66
|
+
let scheduledCount = 0;
|
|
67
|
+
for (const { el: element, chain } of soundEntries) {
|
|
68
|
+
const url = String(element.source ?? '');
|
|
69
|
+
const buffer = audioBuffers.get(url);
|
|
70
|
+
if (!buffer) {
|
|
71
|
+
// Expected for silent videos; a real miss for audio elements.
|
|
72
|
+
if (element.type === 'audio') {
|
|
73
|
+
getLogger().warn(`Audio element ${element.id ?? '(no id)'} skipped — buffer not preloaded for ${url}`);
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const ownRemap = element.type === 'video' ? timeRemapOf(element.time_remap) : null;
|
|
78
|
+
const warped = ownRemap !== null || chain.some((c) => c.remap !== null);
|
|
79
|
+
if (warped) {
|
|
80
|
+
// Varispeed (§5.3.2/§5.8.3): sample the effective media-time
|
|
81
|
+
// function through the warp chain, split into monotonic runs,
|
|
82
|
+
// schedule each (reversed buffer for backward runs). Fades are
|
|
83
|
+
// not applied under warps in v1; volume is.
|
|
84
|
+
const elStart = numberOr(element.time, 0);
|
|
85
|
+
const elDur = parseDuration(element.duration, totalDuration);
|
|
86
|
+
const rate0 = element.type === 'video' ? rateOf(element.playback_rate) : 1;
|
|
87
|
+
const mediaAt = (t) => {
|
|
88
|
+
let c = t;
|
|
89
|
+
for (const scope of chain) {
|
|
90
|
+
if (c < scope.start || c > scope.start + scope.dur)
|
|
91
|
+
return null;
|
|
92
|
+
c -= scope.start;
|
|
93
|
+
if (scope.remap)
|
|
94
|
+
c = Math.max(0, interpolateKeyframes(scope.remap, c));
|
|
95
|
+
}
|
|
96
|
+
if (c < elStart || c > elStart + elDur)
|
|
97
|
+
return null;
|
|
98
|
+
const m = mapToMediaTime(c, {
|
|
99
|
+
elementStart: elStart,
|
|
100
|
+
trimStart: clampNonNeg(numberOr(element.trim_start, 0)),
|
|
101
|
+
trimDuration: trimDurationOf(element.trim_duration),
|
|
102
|
+
rate: rate0,
|
|
103
|
+
loop: element.loop === true,
|
|
104
|
+
timeRemap: ownRemap,
|
|
105
|
+
}, buffer.duration);
|
|
106
|
+
return Math.max(0, Math.min(m, buffer.duration));
|
|
107
|
+
};
|
|
108
|
+
const volumePct0 = numberOr(element.volume, 100);
|
|
109
|
+
const gain0 = Math.max(0, volumePct0 / 100);
|
|
110
|
+
for (const run of varispeedRuns(mediaAt, 0, totalDuration)) {
|
|
111
|
+
scheduleVarispeedRun(ctx, buffer, run, masterGain, run.t0, gain0);
|
|
112
|
+
scheduledCount++;
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// Static path — translate-only chains: the audible window is the
|
|
117
|
+
// element's own, offset by its ancestors' starts.
|
|
118
|
+
const chainOffset = chain.reduce((a, c) => a + c.start, 0);
|
|
119
|
+
const startTime = clampNonNeg(chainOffset + numberOr(element.time, 0));
|
|
120
|
+
// playback_rate only exists on video; audio elements run at 1.
|
|
121
|
+
const rate = element.type === 'video' ? rateOf(element.playback_rate) : 1;
|
|
122
|
+
const window = trimWindow({
|
|
123
|
+
trimStart: clampNonNeg(numberOr(element.trim_start, 0)),
|
|
124
|
+
trimDuration: trimDurationOf(element.trim_duration),
|
|
125
|
+
}, buffer.duration);
|
|
126
|
+
// Bail if scheduling is past the end of the export.
|
|
127
|
+
if (startTime >= totalDuration)
|
|
128
|
+
continue;
|
|
129
|
+
if (window.length <= 0)
|
|
130
|
+
continue;
|
|
131
|
+
// Timeline seconds the element occupies; media plays at `rate`.
|
|
132
|
+
const elementDuration = parseDuration(element.duration, window.length / rate);
|
|
133
|
+
const timelineCap = Math.min(elementDuration, totalDuration - startTime);
|
|
134
|
+
// start()'s duration argument is in BUFFER seconds.
|
|
135
|
+
const bufferPlayDuration = Math.min(window.length, timelineCap * rate);
|
|
136
|
+
if (bufferPlayDuration <= 0)
|
|
137
|
+
continue;
|
|
138
|
+
const sourceNode = ctx.createBufferSource();
|
|
139
|
+
sourceNode.buffer = buffer;
|
|
140
|
+
sourceNode.playbackRate.value = rate;
|
|
141
|
+
const gainNode = ctx.createGain();
|
|
142
|
+
const volumePct = numberOr(element.volume, 100);
|
|
143
|
+
const baseGain = Math.max(0, volumePct / 100);
|
|
144
|
+
gainNode.gain.value = baseGain;
|
|
145
|
+
// audio_fade_in / audio_fade_out — piecewise-linear gain envelope
|
|
146
|
+
// over the element's timeline window (shared with the preview
|
|
147
|
+
// scheduler via fades.ts).
|
|
148
|
+
const fadeIn = Math.max(0, numberOr(element.audio_fade_in, 0));
|
|
149
|
+
const fadeOut = Math.max(0, numberOr(element.audio_fade_out, 0));
|
|
150
|
+
if (fadeIn > 0 || fadeOut > 0) {
|
|
151
|
+
const points = fadeBreakpoints(0, timelineCap, fadeIn, fadeOut);
|
|
152
|
+
gainNode.gain.setValueAtTime(baseGain * points[0].gain, startTime);
|
|
153
|
+
for (const p of points.slice(1)) {
|
|
154
|
+
gainNode.gain.linearRampToValueAtTime(baseGain * p.gain, startTime + p.tau);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
sourceNode.connect(gainNode).connect(masterGain);
|
|
158
|
+
if (element.loop) {
|
|
159
|
+
sourceNode.loop = true;
|
|
160
|
+
sourceNode.loopStart = window.start;
|
|
161
|
+
sourceNode.loopEnd = window.start + window.length;
|
|
162
|
+
// Looping nodes ignore start()'s duration in some implementations;
|
|
163
|
+
// stop explicitly at the element's timeline end.
|
|
164
|
+
sourceNode.start(startTime, window.start);
|
|
165
|
+
sourceNode.stop(startTime + timelineCap);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
sourceNode.start(startTime, window.start, bufferPlayDuration);
|
|
169
|
+
}
|
|
170
|
+
scheduledCount++;
|
|
171
|
+
}
|
|
172
|
+
if (scheduledCount === 0)
|
|
173
|
+
return null;
|
|
174
|
+
return ctx.startRendering();
|
|
175
|
+
}
|
|
176
|
+
function numberOr(v, fallback) {
|
|
177
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
178
|
+
return v;
|
|
179
|
+
if (typeof v === 'string') {
|
|
180
|
+
const n = parseFloat(v);
|
|
181
|
+
if (Number.isFinite(n))
|
|
182
|
+
return n;
|
|
183
|
+
}
|
|
184
|
+
return fallback;
|
|
185
|
+
}
|
|
186
|
+
function numberOrNull(v) {
|
|
187
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
188
|
+
return v;
|
|
189
|
+
if (typeof v === 'string') {
|
|
190
|
+
const n = parseFloat(v);
|
|
191
|
+
if (Number.isFinite(n))
|
|
192
|
+
return n;
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
function parseDuration(v, fallback) {
|
|
197
|
+
if (v === 'auto' || v === 'end' || v == null)
|
|
198
|
+
return fallback;
|
|
199
|
+
return numberOr(v, fallback);
|
|
200
|
+
}
|
|
201
|
+
function clampNonNeg(n) {
|
|
202
|
+
return n < 0 ? 0 : n;
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=mixer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixer.js","sourceRoot":"","sources":["../../src/audio/mixer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Sampling interval for the media-time function, seconds. */
|
|
2
|
+
export declare const VARISPEED_STEP = 0.01;
|
|
3
|
+
export interface VarispeedRun {
|
|
4
|
+
/** Composition-time window of the run. */
|
|
5
|
+
t0: number;
|
|
6
|
+
t1: number;
|
|
7
|
+
/** Media position at each 10 ms sample inside [t0, t1]. */
|
|
8
|
+
media: number[];
|
|
9
|
+
direction: 'forward' | 'backward';
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Split a sampled media-time function into monotonic runs. `mediaAt`
|
|
13
|
+
* returns the media position at a real composition time, or null when
|
|
14
|
+
* the element is inactive (outside its own or an ancestor's window).
|
|
15
|
+
*/
|
|
16
|
+
export declare function varispeedRuns(mediaAt: (t: number) => number | null, start: number, end: number): VarispeedRun[];
|
|
17
|
+
/**
|
|
18
|
+
* Schedule one varispeed run. Returns the created source node (caller
|
|
19
|
+
* may track it for cancellation in live contexts).
|
|
20
|
+
*/
|
|
21
|
+
export declare function scheduleVarispeedRun(ctx: BaseAudioContext, buffer: AudioBuffer, run: VarispeedRun, destination: AudioNode,
|
|
22
|
+
/** AudioContext time corresponding to composition time run.t0. */
|
|
23
|
+
when: number, gain: number): AudioBufferSourceNode;
|
|
24
|
+
//# sourceMappingURL=varispeed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"varispeed.d.ts","sourceRoot":"","sources":["../../src/audio/varispeed.ts"],"names":[],"mappings":"AAmBA,8DAA8D;AAC9D,eAAO,MAAM,cAAc,OAAO,CAAC;AAInC,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,2DAA2D;IAC3D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EACrC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,YAAY,EAAE,CA2BhB;AAkBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,gBAAgB,EACrB,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,YAAY,EACjB,WAAW,EAAE,SAAS;AACtB,kEAAkE;AAClE,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,qBAAqB,CAkCvB"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Varispeed audio under warped clocks (§5.3.2 / §5.8.3).
|
|
2
|
+
//
|
|
3
|
+
// When a sound element sits under group time_remap (or carries video
|
|
4
|
+
// time_remap itself), its media position is a piecewise function of
|
|
5
|
+
// REAL composition time. Rather than algebra over nested keyframe
|
|
6
|
+
// chains, the reference implementation SAMPLES the effective
|
|
7
|
+
// media-time function at 10 ms, splits it into monotonic runs, and
|
|
8
|
+
// schedules each run on an AudioBufferSourceNode:
|
|
9
|
+
//
|
|
10
|
+
// forward — start(t0, offset m(t0)); playbackRate follows the
|
|
11
|
+
// sampled derivative via setValueCurveAtTime (tape-style
|
|
12
|
+
// pitch: 2× plays high, slow-mo plays low)
|
|
13
|
+
// freeze — silence (the tape isn't moving)
|
|
14
|
+
// backward — a cached REVERSED copy of the buffer plays forward at
|
|
15
|
+
// |dm/dt|, which is reversed audio at the right speed
|
|
16
|
+
//
|
|
17
|
+
// Pitch-preserving time-stretch is deliberately out of scope (no
|
|
18
|
+
// exactly-specifiable algorithm).
|
|
19
|
+
/** Sampling interval for the media-time function, seconds. */
|
|
20
|
+
export const VARISPEED_STEP = 0.01;
|
|
21
|
+
/** Below this |Δmedia/Δt| a sample step counts as frozen. */
|
|
22
|
+
const FREEZE_EPS = 0.02;
|
|
23
|
+
/**
|
|
24
|
+
* Split a sampled media-time function into monotonic runs. `mediaAt`
|
|
25
|
+
* returns the media position at a real composition time, or null when
|
|
26
|
+
* the element is inactive (outside its own or an ancestor's window).
|
|
27
|
+
*/
|
|
28
|
+
export function varispeedRuns(mediaAt, start, end) {
|
|
29
|
+
const runs = [];
|
|
30
|
+
let cur = null;
|
|
31
|
+
let prev = null;
|
|
32
|
+
for (let t = start; t <= end + 1e-9; t += VARISPEED_STEP) {
|
|
33
|
+
const m = mediaAt(t);
|
|
34
|
+
const step = prev !== null && m !== null ? (m - prev) / VARISPEED_STEP : null;
|
|
35
|
+
const dir = step === null ? null : step > FREEZE_EPS ? 'forward' : step < -FREEZE_EPS ? 'backward' : 'freeze';
|
|
36
|
+
if (dir === 'forward' || dir === 'backward') {
|
|
37
|
+
if (cur && cur.direction === dir) {
|
|
38
|
+
cur.media.push(m);
|
|
39
|
+
cur.t1 = t;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
if (cur && cur.media.length > 1)
|
|
43
|
+
runs.push(cur);
|
|
44
|
+
cur = { t0: t - VARISPEED_STEP, t1: t, media: [prev, m], direction: dir };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if (cur && cur.media.length > 1)
|
|
49
|
+
runs.push(cur);
|
|
50
|
+
cur = null;
|
|
51
|
+
}
|
|
52
|
+
prev = m;
|
|
53
|
+
}
|
|
54
|
+
if (cur && cur.media.length > 1)
|
|
55
|
+
runs.push(cur);
|
|
56
|
+
return runs;
|
|
57
|
+
}
|
|
58
|
+
/** Reversed copies, keyed by the original buffer (per-mix lifetime). */
|
|
59
|
+
const reversedCache = new WeakMap();
|
|
60
|
+
function reversedBuffer(ctx, buffer) {
|
|
61
|
+
const hit = reversedCache.get(buffer);
|
|
62
|
+
if (hit)
|
|
63
|
+
return hit;
|
|
64
|
+
const rev = ctx.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate);
|
|
65
|
+
for (let ch = 0; ch < buffer.numberOfChannels; ch++) {
|
|
66
|
+
const src = buffer.getChannelData(ch);
|
|
67
|
+
const dst = rev.getChannelData(ch);
|
|
68
|
+
for (let i = 0, n = src.length; i < n; i++)
|
|
69
|
+
dst[i] = src[n - 1 - i];
|
|
70
|
+
}
|
|
71
|
+
reversedCache.set(buffer, rev);
|
|
72
|
+
return rev;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Schedule one varispeed run. Returns the created source node (caller
|
|
76
|
+
* may track it for cancellation in live contexts).
|
|
77
|
+
*/
|
|
78
|
+
export function scheduleVarispeedRun(ctx, buffer, run, destination,
|
|
79
|
+
/** AudioContext time corresponding to composition time run.t0. */
|
|
80
|
+
when, gain) {
|
|
81
|
+
const node = ctx.createBufferSource();
|
|
82
|
+
const dur = run.t1 - run.t0;
|
|
83
|
+
const n = run.media.length;
|
|
84
|
+
// Rate curve: |Δmedia| per step, one value per sample interval.
|
|
85
|
+
const rates = new Float32Array(Math.max(2, n - 1));
|
|
86
|
+
for (let i = 0; i < n - 1; i++) {
|
|
87
|
+
rates[i] = Math.max(0.001, Math.abs(run.media[i + 1] - run.media[i]) / VARISPEED_STEP);
|
|
88
|
+
}
|
|
89
|
+
if (n - 1 === 1)
|
|
90
|
+
rates[1] = rates[0];
|
|
91
|
+
const m0 = run.media[0];
|
|
92
|
+
const mEnd = run.media[n - 1];
|
|
93
|
+
const mediaSpan = Math.abs(mEnd - m0);
|
|
94
|
+
if (run.direction === 'forward') {
|
|
95
|
+
node.buffer = buffer;
|
|
96
|
+
node.playbackRate.setValueCurveAtTime(rates, when, dur);
|
|
97
|
+
const g = ctx.createGain();
|
|
98
|
+
g.gain.value = gain;
|
|
99
|
+
node.connect(g).connect(destination);
|
|
100
|
+
node.start(when, Math.max(0, Math.min(m0, buffer.duration)), mediaSpan);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const rev = reversedBuffer(ctx, buffer);
|
|
104
|
+
node.buffer = rev;
|
|
105
|
+
node.playbackRate.setValueCurveAtTime(rates, when, dur);
|
|
106
|
+
const g = ctx.createGain();
|
|
107
|
+
g.gain.value = gain;
|
|
108
|
+
node.connect(g).connect(destination);
|
|
109
|
+
// Media position p lives at reversed offset (duration − p).
|
|
110
|
+
node.start(when, Math.max(0, Math.min(buffer.duration - m0, buffer.duration)), mediaSpan);
|
|
111
|
+
}
|
|
112
|
+
return node;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=varispeed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"varispeed.js","sourceRoot":"","sources":["../../src/audio/varispeed.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize an AudioBuffer to a 16-bit PCM (s16le) WAV file as raw bytes.
|
|
3
|
+
* Channels are interleaved; sample rate / channel count come from the buffer.
|
|
4
|
+
*/
|
|
5
|
+
export declare function audioBufferToWav(buffer: AudioBuffer): ArrayBuffer;
|
|
6
|
+
//# sourceMappingURL=wav.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wav.d.ts","sourceRoot":"","sources":["../../src/audio/wav.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAmDjE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Dependency-free WAV (RIFF/PCM) serializer.
|
|
2
|
+
//
|
|
3
|
+
// The transparent export path (ProRes 4444 / VP9-alpha) can't use the WebCodecs
|
|
4
|
+
// muxer, so its mixed AudioBuffer can't ride along in an MP4. Instead we
|
|
5
|
+
// serialize it to a plain 16-bit PCM WAV the server-side ffmpeg reads as a
|
|
6
|
+
// second input. WAV is the lowest-common-denominator container — no codec, no
|
|
7
|
+
// muxer dependency — and ffmpeg re-encodes it to the format's audio codec
|
|
8
|
+
// (PCM for MOV/ProRes, Opus for WebM).
|
|
9
|
+
//
|
|
10
|
+
// Output is interleaved signed 16-bit little-endian PCM (s16le), the most
|
|
11
|
+
// universally readable WAV flavor.
|
|
12
|
+
/**
|
|
13
|
+
* Serialize an AudioBuffer to a 16-bit PCM (s16le) WAV file as raw bytes.
|
|
14
|
+
* Channels are interleaved; sample rate / channel count come from the buffer.
|
|
15
|
+
*/
|
|
16
|
+
export function audioBufferToWav(buffer) {
|
|
17
|
+
const numChannels = buffer.numberOfChannels;
|
|
18
|
+
const sampleRate = buffer.sampleRate;
|
|
19
|
+
const numFrames = buffer.length;
|
|
20
|
+
const bytesPerSample = 2; // s16le
|
|
21
|
+
const blockAlign = numChannels * bytesPerSample;
|
|
22
|
+
const dataSize = numFrames * blockAlign;
|
|
23
|
+
const bufferSize = 44 + dataSize; // 44-byte canonical WAV header + PCM data
|
|
24
|
+
const out = new ArrayBuffer(bufferSize);
|
|
25
|
+
const view = new DataView(out);
|
|
26
|
+
const writeAscii = (offset, text) => {
|
|
27
|
+
for (let i = 0; i < text.length; i++)
|
|
28
|
+
view.setUint8(offset + i, text.charCodeAt(i));
|
|
29
|
+
};
|
|
30
|
+
// RIFF chunk descriptor
|
|
31
|
+
writeAscii(0, 'RIFF');
|
|
32
|
+
view.setUint32(4, 36 + dataSize, true); // file size minus the first 8 bytes
|
|
33
|
+
writeAscii(8, 'WAVE');
|
|
34
|
+
// fmt subchunk (PCM)
|
|
35
|
+
writeAscii(12, 'fmt ');
|
|
36
|
+
view.setUint32(16, 16, true); // subchunk1 size (16 for PCM)
|
|
37
|
+
view.setUint16(20, 1, true); // audio format = 1 (PCM)
|
|
38
|
+
view.setUint16(22, numChannels, true);
|
|
39
|
+
view.setUint32(24, sampleRate, true);
|
|
40
|
+
view.setUint32(28, sampleRate * blockAlign, true); // byte rate
|
|
41
|
+
view.setUint16(32, blockAlign, true);
|
|
42
|
+
view.setUint16(34, bytesPerSample * 8, true); // bits per sample
|
|
43
|
+
// data subchunk
|
|
44
|
+
writeAscii(36, 'data');
|
|
45
|
+
view.setUint32(40, dataSize, true);
|
|
46
|
+
// Interleave channels and write clamped 16-bit samples.
|
|
47
|
+
const channelData = [];
|
|
48
|
+
for (let c = 0; c < numChannels; c++)
|
|
49
|
+
channelData.push(buffer.getChannelData(c));
|
|
50
|
+
let offset = 44;
|
|
51
|
+
for (let frame = 0; frame < numFrames; frame++) {
|
|
52
|
+
for (let c = 0; c < numChannels; c++) {
|
|
53
|
+
let s = channelData[c][frame];
|
|
54
|
+
// Clamp to [-1, 1] then scale to the signed 16-bit range.
|
|
55
|
+
s = s < -1 ? -1 : s > 1 ? 1 : s;
|
|
56
|
+
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
|
57
|
+
offset += 2;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=wav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wav.js","sourceRoot":"","sources":["../../src/audio/wav.ts"],"names":[],"mappings":"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"}
|