reframe-video 0.6.26 → 0.6.28

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.
@@ -4,6 +4,44 @@ import { readFile } from "node:fs/promises";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
+ // ../core/src/ir.ts
8
+ var SFX_NAMES = [
9
+ "whoosh",
10
+ "swish",
11
+ "swoosh",
12
+ "rise",
13
+ "riser",
14
+ "warp",
15
+ "tick",
16
+ "click",
17
+ "blip",
18
+ "pop",
19
+ "select",
20
+ "thud",
21
+ "boom",
22
+ "knock",
23
+ "sub",
24
+ "chime",
25
+ "ding",
26
+ "coin",
27
+ "sparkle",
28
+ "shimmer",
29
+ "success",
30
+ "zap",
31
+ "error",
32
+ "glitch",
33
+ "static",
34
+ "scan",
35
+ "powerup",
36
+ "powerdown",
37
+ "snare",
38
+ "hat",
39
+ "bubble",
40
+ "notify",
41
+ "camera"
42
+ ];
43
+ var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
44
+
7
45
  // ../core/src/interpolate.ts
8
46
  var BACK_C1 = 1.70158;
9
47
  var BACK_C2 = BACK_C1 * 1.525;
@@ -329,7 +367,6 @@ function validateScene(ir) {
329
367
  }
330
368
  }
331
369
  }
332
- const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
333
370
  for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
334
371
  if (typeof cue.at === "string" && !labels.has(cue.at)) {
335
372
  problems.push(
@@ -365,6 +402,10 @@ function validateScene(ir) {
365
402
  if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
366
403
  problems.push('audio.bgm: use either "file" or "synth", not both');
367
404
  }
405
+ const bgmSynth = ir.audio?.bgm?.synth;
406
+ if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
407
+ problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
408
+ }
368
409
  if (problems.length > 0) throw new SceneValidationError(problems);
369
410
  }
370
411
  var TRANSITIONS = ["cut", "crossfade"];
package/dist/compile.js CHANGED
@@ -9,6 +9,44 @@ import { readFile } from "node:fs/promises";
9
9
  import { dirname, resolve } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
 
12
+ // ../core/src/ir.ts
13
+ var SFX_NAMES = [
14
+ "whoosh",
15
+ "swish",
16
+ "swoosh",
17
+ "rise",
18
+ "riser",
19
+ "warp",
20
+ "tick",
21
+ "click",
22
+ "blip",
23
+ "pop",
24
+ "select",
25
+ "thud",
26
+ "boom",
27
+ "knock",
28
+ "sub",
29
+ "chime",
30
+ "ding",
31
+ "coin",
32
+ "sparkle",
33
+ "shimmer",
34
+ "success",
35
+ "zap",
36
+ "error",
37
+ "glitch",
38
+ "static",
39
+ "scan",
40
+ "powerup",
41
+ "powerdown",
42
+ "snare",
43
+ "hat",
44
+ "bubble",
45
+ "notify",
46
+ "camera"
47
+ ];
48
+ var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
49
+
12
50
  // ../core/src/interpolate.ts
13
51
  var BACK_C1 = 1.70158;
14
52
  var BACK_C2 = BACK_C1 * 1.525;
@@ -334,7 +372,6 @@ function validateScene(ir) {
334
372
  }
335
373
  }
336
374
  }
337
- const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
338
375
  for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
339
376
  if (typeof cue.at === "string" && !labels.has(cue.at)) {
340
377
  problems.push(
@@ -370,6 +407,10 @@ function validateScene(ir) {
370
407
  if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
371
408
  problems.push('audio.bgm: use either "file" or "synth", not both');
372
409
  }
410
+ const bgmSynth = ir.audio?.bgm?.synth;
411
+ if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
412
+ problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
413
+ }
373
414
  if (problems.length > 0) throw new SceneValidationError(problems);
374
415
  }
375
416
 
package/dist/diff.js CHANGED
@@ -13,6 +13,42 @@ import { dirname, resolve } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
14
 
15
15
  // ../core/src/ir.ts
16
+ var SFX_NAMES = [
17
+ "whoosh",
18
+ "swish",
19
+ "swoosh",
20
+ "rise",
21
+ "riser",
22
+ "warp",
23
+ "tick",
24
+ "click",
25
+ "blip",
26
+ "pop",
27
+ "select",
28
+ "thud",
29
+ "boom",
30
+ "knock",
31
+ "sub",
32
+ "chime",
33
+ "ding",
34
+ "coin",
35
+ "sparkle",
36
+ "shimmer",
37
+ "success",
38
+ "zap",
39
+ "error",
40
+ "glitch",
41
+ "static",
42
+ "scan",
43
+ "powerup",
44
+ "powerdown",
45
+ "snare",
46
+ "hat",
47
+ "bubble",
48
+ "notify",
49
+ "camera"
50
+ ];
51
+ var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
16
52
  var DEFAULT_TO_DURATION = 0.5;
17
53
  var DEFAULT_TWEEN_DURATION = 0.5;
18
54
  var DEFAULT_MOTIONPATH_DURATION = 1;
@@ -662,7 +698,6 @@ function validateScene(ir) {
662
698
  }
663
699
  }
664
700
  }
665
- const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
666
701
  for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
667
702
  if (typeof cue.at === "string" && !labels.has(cue.at)) {
668
703
  problems.push(
@@ -698,6 +733,10 @@ function validateScene(ir) {
698
733
  if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
699
734
  problems.push('audio.bgm: use either "file" or "synth", not both');
700
735
  }
736
+ const bgmSynth = ir.audio?.bgm?.synth;
737
+ if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
738
+ problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
739
+ }
701
740
  if (problems.length > 0) throw new SceneValidationError(problems);
702
741
  }
703
742
  var TRANSITIONS = ["cut", "crossfade"];
package/dist/frame.js CHANGED
@@ -40,6 +40,42 @@ import { existsSync } from "node:fs";
40
40
  import { extname, isAbsolute, resolve } from "node:path";
41
41
 
42
42
  // ../core/src/ir.ts
43
+ var SFX_NAMES = [
44
+ "whoosh",
45
+ "swish",
46
+ "swoosh",
47
+ "rise",
48
+ "riser",
49
+ "warp",
50
+ "tick",
51
+ "click",
52
+ "blip",
53
+ "pop",
54
+ "select",
55
+ "thud",
56
+ "boom",
57
+ "knock",
58
+ "sub",
59
+ "chime",
60
+ "ding",
61
+ "coin",
62
+ "sparkle",
63
+ "shimmer",
64
+ "success",
65
+ "zap",
66
+ "error",
67
+ "glitch",
68
+ "static",
69
+ "scan",
70
+ "powerup",
71
+ "powerdown",
72
+ "snare",
73
+ "hat",
74
+ "bubble",
75
+ "notify",
76
+ "camera"
77
+ ];
78
+ var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
43
79
  var DEFAULT_TO_DURATION = 0.5;
44
80
  var DEFAULT_TWEEN_DURATION = 0.5;
45
81
  var DEFAULT_MOTIONPATH_DURATION = 1;
@@ -689,7 +725,6 @@ function validateScene(ir) {
689
725
  }
690
726
  }
691
727
  }
692
- const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
693
728
  for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
694
729
  if (typeof cue.at === "string" && !labels.has(cue.at)) {
695
730
  problems.push(
@@ -725,6 +760,10 @@ function validateScene(ir) {
725
760
  if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
726
761
  problems.push('audio.bgm: use either "file" or "synth", not both');
727
762
  }
763
+ const bgmSynth = ir.audio?.bgm?.synth;
764
+ if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
765
+ problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
766
+ }
728
767
  if (problems.length > 0) throw new SceneValidationError(problems);
729
768
  }
730
769
  var TRANSITIONS = ["cut", "crossfade"];
package/dist/index.js CHANGED
@@ -1,4 +1,40 @@
1
1
  // ../core/src/ir.ts
2
+ var SFX_NAMES = [
3
+ "whoosh",
4
+ "swish",
5
+ "swoosh",
6
+ "rise",
7
+ "riser",
8
+ "warp",
9
+ "tick",
10
+ "click",
11
+ "blip",
12
+ "pop",
13
+ "select",
14
+ "thud",
15
+ "boom",
16
+ "knock",
17
+ "sub",
18
+ "chime",
19
+ "ding",
20
+ "coin",
21
+ "sparkle",
22
+ "shimmer",
23
+ "success",
24
+ "zap",
25
+ "error",
26
+ "glitch",
27
+ "static",
28
+ "scan",
29
+ "powerup",
30
+ "powerdown",
31
+ "snare",
32
+ "hat",
33
+ "bubble",
34
+ "notify",
35
+ "camera"
36
+ ];
37
+ var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
2
38
  var DEFAULT_CROSSFADE = 0.5;
3
39
  var DEFAULT_TO_DURATION = 0.5;
4
40
  var DEFAULT_TWEEN_DURATION = 0.5;
@@ -785,7 +821,6 @@ function validateScene(ir) {
785
821
  }
786
822
  }
787
823
  }
788
- const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
789
824
  for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
790
825
  if (typeof cue.at === "string" && !labels.has(cue.at)) {
791
826
  problems.push(
@@ -821,6 +856,10 @@ function validateScene(ir) {
821
856
  if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
822
857
  problems.push('audio.bgm: use either "file" or "synth", not both');
823
858
  }
859
+ const bgmSynth = ir.audio?.bgm?.synth;
860
+ if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
861
+ problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
862
+ }
824
863
  if (problems.length > 0) throw new SceneValidationError(problems);
825
864
  }
826
865
  var TRANSITIONS = ["cut", "crossfade"];
@@ -2889,12 +2928,46 @@ function motionOp(name, target, opts = {}) {
2889
2928
 
2890
2929
  // ../core/src/audio.ts
2891
2930
  var SFX_DURATION = {
2931
+ // transition
2892
2932
  whoosh: 0.35,
2893
- pop: 0.12,
2894
- tick: 0.03,
2933
+ swish: 0.32,
2934
+ swoosh: 0.35,
2895
2935
  rise: 0.5,
2936
+ riser: 0.85,
2937
+ warp: 0.5,
2938
+ // ui
2939
+ tick: 0.03,
2940
+ click: 0.05,
2941
+ blip: 0.1,
2942
+ pop: 0.12,
2943
+ select: 0.18,
2944
+ // impact
2945
+ thud: 0.25,
2946
+ boom: 0.6,
2947
+ knock: 0.14,
2948
+ sub: 0.7,
2949
+ // positive
2950
+ chime: 0.7,
2951
+ ding: 0.5,
2952
+ coin: 0.3,
2953
+ sparkle: 0.6,
2896
2954
  shimmer: 0.9,
2897
- thud: 0.25
2955
+ success: 0.6,
2956
+ // alert
2957
+ zap: 0.22,
2958
+ error: 0.4,
2959
+ // tech
2960
+ glitch: 0.3,
2961
+ static: 0.18,
2962
+ scan: 0.45,
2963
+ powerup: 0.4,
2964
+ powerdown: 0.5,
2965
+ // rhythm / foley
2966
+ snare: 0.18,
2967
+ hat: 0.05,
2968
+ bubble: 0.16,
2969
+ notify: 0.45,
2970
+ camera: 0.18
2898
2971
  };
2899
2972
  var FILE_CUE_DURATION = 0.4;
2900
2973
  function collectClipAudio(ir, duration, warnings) {
@@ -2954,7 +3027,11 @@ function resolveAudioPlan(compiled) {
2954
3027
  fadeIn: cue.fadeIn ?? 0,
2955
3028
  fadeOut: cue.fadeOut ?? 0,
2956
3029
  pan: cue.pan ?? 0,
2957
- source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
3030
+ source: cue.sfx ? (
3031
+ // auto-vary: default the seed to the cue's order so repeated sfx differ
3032
+ // (pitch/texture); an explicit params.seed always wins.
3033
+ { kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
3034
+ ) : { kind: "file", path: cue.file }
2958
3035
  });
2959
3036
  }
2960
3037
  cues.sort((a, b) => a.t - b.t);
@@ -3034,7 +3111,11 @@ function resolveCompositionAudioPlan(comp) {
3034
3111
  fadeIn: cue.fadeIn ?? 0,
3035
3112
  fadeOut: cue.fadeOut ?? 0,
3036
3113
  pan: cue.pan ?? 0,
3037
- source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
3114
+ source: cue.sfx ? (
3115
+ // auto-vary: default the seed to the cue's order so repeated sfx differ
3116
+ // (pitch/texture); an explicit params.seed always wins.
3117
+ { kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
3118
+ ) : { kind: "file", path: cue.file }
3038
3119
  });
3039
3120
  }
3040
3121
  if (!audio?.bgm && cues.length === 0 && clipAudio.length === 0) return null;
@@ -3593,6 +3674,7 @@ function sketchToTimeline(sketch, nodeIds) {
3593
3674
  return par(...steps);
3594
3675
  }
3595
3676
  export {
3677
+ BGM_SYNTHS,
3596
3678
  CAMERA_ID,
3597
3679
  CAMERA_PROPS2 as CAMERA_PROPS,
3598
3680
  CHARACTER_PRESET_NAMES,
@@ -3608,6 +3690,7 @@ export {
3608
3690
  PRESET_NAMES,
3609
3691
  PROPS_BY_TYPE,
3610
3692
  SFX_DURATION,
3693
+ SFX_NAMES,
3611
3694
  SceneValidationError,
3612
3695
  beat,
3613
3696
  cameraFit,
package/dist/labels.js CHANGED
@@ -1,6 +1,42 @@
1
1
  #!/usr/bin/env tsx
2
2
 
3
3
  // ../core/src/ir.ts
4
+ var SFX_NAMES = [
5
+ "whoosh",
6
+ "swish",
7
+ "swoosh",
8
+ "rise",
9
+ "riser",
10
+ "warp",
11
+ "tick",
12
+ "click",
13
+ "blip",
14
+ "pop",
15
+ "select",
16
+ "thud",
17
+ "boom",
18
+ "knock",
19
+ "sub",
20
+ "chime",
21
+ "ding",
22
+ "coin",
23
+ "sparkle",
24
+ "shimmer",
25
+ "success",
26
+ "zap",
27
+ "error",
28
+ "glitch",
29
+ "static",
30
+ "scan",
31
+ "powerup",
32
+ "powerdown",
33
+ "snare",
34
+ "hat",
35
+ "bubble",
36
+ "notify",
37
+ "camera"
38
+ ];
39
+ var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
4
40
  var DEFAULT_TO_DURATION = 0.5;
5
41
  var DEFAULT_TWEEN_DURATION = 0.5;
6
42
  var DEFAULT_MOTIONPATH_DURATION = 1;
@@ -650,7 +686,6 @@ function validateScene(ir) {
650
686
  }
651
687
  }
652
688
  }
653
- const SFX_NAMES = ["whoosh", "pop", "tick", "rise", "shimmer", "thud"];
654
689
  for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
655
690
  if (typeof cue.at === "string" && !labels.has(cue.at)) {
656
691
  problems.push(
@@ -686,6 +721,10 @@ function validateScene(ir) {
686
721
  if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
687
722
  problems.push('audio.bgm: use either "file" or "synth", not both');
688
723
  }
724
+ const bgmSynth = ir.audio?.bgm?.synth;
725
+ if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
726
+ problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
727
+ }
689
728
  if (problems.length > 0) throw new SceneValidationError(problems);
690
729
  }
691
730
 
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import type { CompiledScene } from "./compile.js";
10
10
  import type { CompiledComposition } from "./composeComposition.js";
11
- import type { SfxName } from "./ir.js";
11
+ import type { BgmSynth, SfxName } from "./ir.js";
12
12
  /** Nominal cue lengths (s) for duck-window math; file cues use a default. */
13
13
  export declare const SFX_DURATION: Record<SfxName, number>;
14
14
  export interface ResolvedCue {
@@ -55,7 +55,7 @@ export interface AudioPlan {
55
55
  path: string;
56
56
  } | {
57
57
  kind: "synth";
58
- name: "ambient-pad";
58
+ name: BgmSynth;
59
59
  };
60
60
  gain: number;
61
61
  fadeIn: number;
@@ -412,7 +412,21 @@ export interface BehaviorIR {
412
412
  };
413
413
  };
414
414
  }
415
- export type SfxName = "whoosh" | "pop" | "tick" | "rise" | "shimmer" | "thud";
415
+ /**
416
+ * The procedural sfx palette — the single source of truth (the type, validation,
417
+ * and the render-cli synth recipes all key off this). Grouped by use:
418
+ * transition: whoosh swish swoosh rise riser warp · ui: tick click blip pop select
419
+ * impact: thud boom knock sub · positive: chime ding coin sparkle shimmer success
420
+ * alert: zap error · tech: glitch static scan powerup powerdown
421
+ * rhythm: snare hat · foley: bubble notify camera
422
+ * Every cue's pitch/texture varies with its `seed` (auto-seeded by cue order), so
423
+ * repeated cues sound different; `params.pitch` is an explicit multiplier.
424
+ */
425
+ export declare const SFX_NAMES: readonly ["whoosh", "swish", "swoosh", "rise", "riser", "warp", "tick", "click", "blip", "pop", "select", "thud", "boom", "knock", "sub", "chime", "ding", "coin", "sparkle", "shimmer", "success", "zap", "error", "glitch", "static", "scan", "powerup", "powerdown", "snare", "hat", "bubble", "notify", "camera"];
426
+ export type SfxName = (typeof SFX_NAMES)[number];
427
+ /** Synthesized background-music beds (license-free). */
428
+ export declare const BGM_SYNTHS: readonly ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
429
+ export type BgmSynth = (typeof BGM_SYNTHS)[number];
416
430
  export interface AudioCueIR {
417
431
  /** Anchor: a timeline label (the step's start) or absolute seconds. */
418
432
  at: string | number;
@@ -430,14 +444,18 @@ export interface AudioCueIR {
430
444
  fadeOut?: number;
431
445
  /** Stereo balance: -1 full left, 0 centre (default), +1 full right. */
432
446
  pan?: number;
433
- /** Synth parameter overrides (seed, duration, …) — numbers only. */
447
+ /**
448
+ * Synth parameter overrides — numbers only. `seed` varies pitch/texture
449
+ * (defaults to the cue's order so repeats differ); `pitch` is an explicit
450
+ * frequency multiplier (1 = unchanged, 2 = an octave up); `gainDb` trims level.
451
+ */
434
452
  params?: Record<string, number>;
435
453
  }
436
454
  export interface AudioIR {
437
455
  bgm?: {
438
456
  file?: string;
439
- /** License-free synthesized bed. */
440
- synth?: "ambient-pad";
457
+ /** License-free synthesized bed (see {@link BGM_SYNTHS}). */
458
+ synth?: BgmSynth;
441
459
  gain?: number;
442
460
  fadeIn?: number;
443
461
  fadeOut?: number;
@@ -558,12 +558,31 @@ audio: {
558
558
  }
559
559
  ```
560
560
 
561
- Procedural sfx names: `whoosh` `pop` `tick` `rise` `shimmer` `thud` (deterministic,
562
- seedable via `params: { seed }`). Exactly one of `sfx`/`file` per cue.
563
- **Mixing**: any cue takes `fadeIn`/`fadeOut` (seconds) and `pan` (-1 left … 0 centre …
564
- +1 right). A `video` clip's audio takes `fadeIn` and `pan` too (clip fade-out isn't
565
- supported yet a clip has no fixed length in the plan). The bed auto-ducks under cues
566
- (`bgm.duck`).
561
+ **Procedural sfx palette** (deterministic; exactly one of `sfx`/`file` per cue):
562
+
563
+ | group | names |
564
+ | --- | --- |
565
+ | transition | `whoosh` `swish` `swoosh` `rise` `riser` `warp` |
566
+ | ui | `tick` `click` `blip` `pop` `select` |
567
+ | impact | `thud` `boom` `knock` `sub` |
568
+ | positive | `chime` `ding` `coin` `sparkle` `shimmer` `success` |
569
+ | alert | `zap` `error` |
570
+ | tech | `glitch` `static` `scan` `powerup` `powerdown` |
571
+ | rhythm | `snare` `hat` |
572
+ | foley | `bubble` `notify` `camera` |
573
+
574
+ **Variation — repeats don't sound the same.** Each cue's `seed` shifts the sound's
575
+ PITCH (a musical step) and texture, and it **defaults to the cue's order**, so a run of
576
+ the same sfx becomes a little phrase instead of a stuck note — no setup needed. Override
577
+ explicitly with `params`: `{ sfx: "blip", params: { seed: 4 } }` (pick the variant) or
578
+ `{ sfx: "tick", params: { pitch: 1.5 } }` (an explicit frequency multiplier; `2` = octave
579
+ up). `params.gainDb` trims a single hit.
580
+
581
+ **bgm beds** (`bgm.synth`): `ambient-pad` `lofi` `pulse` `tension` `uplift` — or
582
+ `bgm.file` for your own. **Mixing**: any cue takes `fadeIn`/`fadeOut` (seconds) and `pan`
583
+ (-1 left … 0 centre … +1 right). A `video` clip's audio takes `fadeIn` and `pan` too
584
+ (clip fade-out isn't supported yet). The bed auto-ducks under cues (`bgm.duck`). See
585
+ `examples/scenes/sfx-showcase.ts` to audition the whole palette.
567
586
 
568
587
  ## Rules
569
588
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reframe-video",
3
- "version": "0.6.26",
3
+ "version": "0.6.28",
4
4
  "description": "Declarative motion graphics that AI can write and humans can tweak — human edits survive AI regeneration. Deterministic mp4 renders from a plain-data scene format.",
5
5
  "keywords": [
6
6
  "motion-graphics",