@hyperframes/core 0.7.107 → 0.7.109
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/dist/audio/audioFxAutomation.d.ts +63 -0
- package/dist/audio/audioFxAutomation.d.ts.map +1 -0
- package/dist/audio/audioFxAutomation.js +219 -0
- package/dist/audio/audioFxAutomation.js.map +1 -0
- package/dist/audio/audioFxGraph.d.ts +73 -0
- package/dist/audio/audioFxGraph.d.ts.map +1 -0
- package/dist/audio/audioFxGraph.js +649 -0
- package/dist/audio/audioFxGraph.js.map +1 -0
- package/dist/audio/audioFxTail.d.ts +28 -0
- package/dist/audio/audioFxTail.d.ts.map +1 -0
- package/dist/audio/audioFxTail.js +95 -0
- package/dist/audio/audioFxTail.js.map +1 -0
- package/dist/audio/audioFxWorklets.d.ts +8 -0
- package/dist/audio/audioFxWorklets.d.ts.map +1 -0
- package/dist/audio/audioFxWorklets.js +263 -0
- package/dist/audio/audioFxWorklets.js.map +1 -0
- package/dist/audioAutomation.d.ts +191 -0
- package/dist/audioAutomation.d.ts.map +1 -0
- package/dist/audioAutomation.js +488 -0
- package/dist/audioAutomation.js.map +1 -0
- package/dist/audioCarve.d.ts +216 -0
- package/dist/audioCarve.d.ts.map +1 -0
- package/dist/audioCarve.js +608 -0
- package/dist/audioCarve.js.map +1 -0
- package/dist/audioFx.d.ts +182 -0
- package/dist/audioFx.d.ts.map +1 -0
- package/dist/audioFx.js +813 -0
- package/dist/audioFx.js.map +1 -0
- package/dist/audioFxCopy.d.ts +89 -0
- package/dist/audioFxCopy.d.ts.map +1 -0
- package/dist/audioFxCopy.js +340 -0
- package/dist/audioFxCopy.js.map +1 -0
- package/dist/audioFxEq.d.ts +75 -0
- package/dist/audioFxEq.d.ts.map +1 -0
- package/dist/audioFxEq.js +153 -0
- package/dist/audioFxEq.js.map +1 -0
- package/dist/audioFxJobs.d.ts +52 -0
- package/dist/audioFxJobs.d.ts.map +1 -0
- package/dist/audioFxJobs.js +92 -0
- package/dist/audioFxJobs.js.map +1 -0
- package/dist/audioFxPresets.d.ts +91 -0
- package/dist/audioFxPresets.d.ts.map +1 -0
- package/dist/audioFxPresets.js +320 -0
- package/dist/audioFxPresets.js.map +1 -0
- package/dist/audioFxProfiles.d.ts +65 -0
- package/dist/audioFxProfiles.d.ts.map +1 -0
- package/dist/audioFxProfiles.js +209 -0
- package/dist/audioFxProfiles.js.map +1 -0
- package/dist/audioLeveller.d.ts +62 -0
- package/dist/audioLeveller.d.ts.map +1 -0
- package/dist/audioLeveller.js +208 -0
- package/dist/audioLeveller.js.map +1 -0
- package/dist/canaryRegistry.d.ts.map +1 -1
- package/dist/canaryRegistry.js +8 -0
- package/dist/canaryRegistry.js.map +1 -1
- package/dist/compiler/compositionScoping.d.ts +3 -0
- package/dist/compiler/compositionScoping.d.ts.map +1 -1
- package/dist/compiler/compositionScoping.js +34 -11
- package/dist/compiler/compositionScoping.js.map +1 -1
- package/dist/compiler/htmlBundler.d.ts.map +1 -1
- package/dist/compiler/htmlBundler.js +42 -2
- package/dist/compiler/htmlBundler.js.map +1 -1
- package/dist/editing/affordances.d.ts +3 -0
- package/dist/editing/affordances.d.ts.map +1 -1
- package/dist/editing/affordances.js +1 -0
- package/dist/editing/affordances.js.map +1 -1
- package/dist/generated/audio-fx-runtime-inline.d.ts +3 -0
- package/dist/generated/audio-fx-runtime-inline.d.ts.map +1 -0
- package/dist/generated/audio-fx-runtime-inline.js +7 -0
- package/dist/generated/audio-fx-runtime-inline.js.map +1 -0
- package/dist/generated/runtime-inline.js +1 -1
- package/dist/generated/runtime-inline.js.map +1 -1
- package/dist/hyperframe.runtime.iife.js +238 -28
- package/dist/runtime/applyVariableBindings.d.ts +45 -0
- package/dist/runtime/applyVariableBindings.d.ts.map +1 -0
- package/dist/runtime/applyVariableBindings.js +194 -0
- package/dist/runtime/applyVariableBindings.js.map +1 -0
- package/dist/runtime/audioAutomationVolume.d.ts +17 -0
- package/dist/runtime/audioAutomationVolume.d.ts.map +1 -0
- package/dist/runtime/audioAutomationVolume.js +49 -0
- package/dist/runtime/audioAutomationVolume.js.map +1 -0
- package/dist/runtime/media.d.ts.map +1 -1
- package/dist/runtime/media.js +17 -1
- package/dist/runtime/media.js.map +1 -1
- package/dist/runtime/variableScope.d.ts +9 -0
- package/dist/runtime/variableScope.d.ts.map +1 -0
- package/dist/runtime/variableScope.js +21 -0
- package/dist/runtime/variableScope.js.map +1 -0
- package/package.json +51 -6
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voiceover carve: find the bands a voice occupies and dip a music bed there,
|
|
3
|
+
* so the voice sits in front without ducking the whole track.
|
|
4
|
+
*
|
|
5
|
+
* This is a relationship between two tracks, not an effect on one. The controls
|
|
6
|
+
* live on the bed being processed and name the voice to listen to, the same way
|
|
7
|
+
* a sidechain compressor works: you select the track that gets quieter and pick
|
|
8
|
+
* what makes it quieter.
|
|
9
|
+
*
|
|
10
|
+
* The output is an ordinary FX chain of peaking filters, so a carve is just a
|
|
11
|
+
* chain the studio generated rather than a separate rendering path.
|
|
12
|
+
*/
|
|
13
|
+
import { type HfAudioFxChain } from "./audioFx.js";
|
|
14
|
+
export declare const HF_AUDIO_CARVE_ATTR = "data-fx-carve";
|
|
15
|
+
export interface HfCarveBand {
|
|
16
|
+
freq: number;
|
|
17
|
+
gainDb: number;
|
|
18
|
+
q: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* What the author sets: which voice to listen to, how hard to work, and whether
|
|
22
|
+
* the work follows the voice moment to moment.
|
|
23
|
+
*
|
|
24
|
+
* One number for the strength of the effect, not six for its mechanism. The
|
|
25
|
+
* mechanism has six numbers — how deep to cut, how many bands, how wide, how far
|
|
26
|
+
* to favour intelligibility over raw energy, how far the level may drop, how far
|
|
27
|
+
* under the voice to aim — and every one of them was a control nobody could set
|
|
28
|
+
* without knowing what the analysis does with it. They move together anyway: a
|
|
29
|
+
* gentle carve is a shallow cut in few bands with little ducking, a hard one is
|
|
30
|
+
* deeper in more bands with more. `carveProfile` is that relationship, written
|
|
31
|
+
* once.
|
|
32
|
+
*/
|
|
33
|
+
export interface HfCarveSettings {
|
|
34
|
+
/**
|
|
35
|
+
* Element ids of every voice track this bed makes room for.
|
|
36
|
+
*
|
|
37
|
+
* More than one because a bed usually runs under a whole sequence: a narrator, an
|
|
38
|
+
* interview answer, a second presenter. Each occupies its own stretch of the bed,
|
|
39
|
+
* and carving against only one of them leaves the others fighting it. They are
|
|
40
|
+
* analysed together — see `mixCarveSources` — so the cuts follow whoever is
|
|
41
|
+
* speaking rather than averaging strangers.
|
|
42
|
+
*/
|
|
43
|
+
sources: string[];
|
|
44
|
+
/** How hard to carve, 0..1. */
|
|
45
|
+
strength: number;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the carve is applied at all.
|
|
48
|
+
*
|
|
49
|
+
* A bed under a voice wants carving, so a track that has never been configured
|
|
50
|
+
* is treated as on and carved without being asked. That default needs an off
|
|
51
|
+
* switch that survives: with "off" represented by having no settings at all,
|
|
52
|
+
* selecting the clip again would read it as never-configured and re-apply. So
|
|
53
|
+
* switching it off writes `enabled: false` and the default stops applying.
|
|
54
|
+
*/
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
}
|
|
57
|
+
/** The numbers the analysis actually works in, all derived from `strength`. */
|
|
58
|
+
export interface HfCarveProfile {
|
|
59
|
+
/** Deepest cut applied to the strongest band. */
|
|
60
|
+
maxCutDb: number;
|
|
61
|
+
/** How many bands to dip. */
|
|
62
|
+
bands: number;
|
|
63
|
+
q: number;
|
|
64
|
+
/**
|
|
65
|
+
* Weight band selection toward intelligibility rather than raw voice energy.
|
|
66
|
+
*
|
|
67
|
+
* Ranking purely by voice power lands on the fundamental almost every time,
|
|
68
|
+
* because that is where a voice is loudest — but masking that actually hurts a
|
|
69
|
+
* voiceover happens higher up, and dipping 160 Hz mostly just thins the bed.
|
|
70
|
+
*/
|
|
71
|
+
intelligibilityBias: number;
|
|
72
|
+
/** How far the bed's whole level may come down to make room, in dB. */
|
|
73
|
+
duckDb: number;
|
|
74
|
+
/** How far under the voice the bed should sit while the voice speaks, in dB. */
|
|
75
|
+
headroomDb: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* What a track's name suggests it holds.
|
|
79
|
+
*
|
|
80
|
+
* Only ever a hint — a name is what the author called something, not what is in the
|
|
81
|
+
* file — so this is used to order and to filter a list of candidates, never to
|
|
82
|
+
* decide alone. `unknown` is deliberately common: a track called `a1` could be
|
|
83
|
+
* anything, and treating an unrecognised name as "not a voice" would hide the one
|
|
84
|
+
* track somebody needs to pick.
|
|
85
|
+
*/
|
|
86
|
+
export type HfAudioNameKind = "voice" | "music" | "sfx" | "unknown";
|
|
87
|
+
/**
|
|
88
|
+
* Classify a track from its id and filename together.
|
|
89
|
+
*
|
|
90
|
+
* Both, because either can be the informative one: an author naming elements `a1`
|
|
91
|
+
* and `a2` may still have `narration.mp3` and `bgm.mp3` as their sources, and one
|
|
92
|
+
* naming them `voice` and `music` may have opaque hashes for filenames.
|
|
93
|
+
*
|
|
94
|
+
* Voice is tested first: a file called `voiceover-music-bed.wav` is more likely the
|
|
95
|
+
* voiceover than the bed, and a track matching both hints is better offered than
|
|
96
|
+
* hidden.
|
|
97
|
+
*/
|
|
98
|
+
export declare function classifyAudioName(...parts: readonly (string | null | undefined)[]): HfAudioNameKind;
|
|
99
|
+
/** A clip's place on the timeline. A duration that is not a number is unbounded. */
|
|
100
|
+
export interface HfClipSpan {
|
|
101
|
+
start: number;
|
|
102
|
+
duration?: number | null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Do these two clips share any time at all?
|
|
106
|
+
*
|
|
107
|
+
* A voice that never plays while the bed does cannot mask it, so it has no business
|
|
108
|
+
* in the carve: it would contribute silence to the analysis and, worse, invite the
|
|
109
|
+
* author to wonder why including it changed nothing.
|
|
110
|
+
*
|
|
111
|
+
* An unknown duration counts as unbounded rather than as zero. Refusing a track
|
|
112
|
+
* because its length is not written down would drop the commonest case there is — a
|
|
113
|
+
* clip whose duration the composition leaves to the media itself.
|
|
114
|
+
*/
|
|
115
|
+
export declare function clipsOverlap(a: HfClipSpan, b: HfClipSpan): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Could this track be the voice a carve listens to?
|
|
118
|
+
*
|
|
119
|
+
* Music and SFX are out: a bed is the thing being carved, and a 200 ms whoosh has
|
|
120
|
+
* no speech to make room for. Everything else stays in, including names that say
|
|
121
|
+
* nothing — see `HfAudioNameKind`.
|
|
122
|
+
*/
|
|
123
|
+
export declare function couldBeCarveSource(...parts: readonly (string | null | undefined)[]): boolean;
|
|
124
|
+
export declare const DEFAULT_CARVE: HfCarveSettings;
|
|
125
|
+
/**
|
|
126
|
+
* Strength as the six numbers the analysis needs.
|
|
127
|
+
*
|
|
128
|
+
* Each is a straight line from "barely there" to as far as the effect goes. The
|
|
129
|
+
* slopes are twice what they first were: the top of the knob was not strong
|
|
130
|
+
* enough to sit a bed under a loud voice, so what used to be full strength is now
|
|
131
|
+
* the halfway point and everything above it is new range. Quarter strength is
|
|
132
|
+
* therefore where the separate controls' own defaults land, which is what the
|
|
133
|
+
* panel defaults to.
|
|
134
|
+
*/
|
|
135
|
+
export declare function carveProfile(strength: number): HfCarveProfile;
|
|
136
|
+
/**
|
|
137
|
+
* Read carve settings from an attribute.
|
|
138
|
+
*
|
|
139
|
+
* Projects written before the collapse to one knob carry the six mechanism
|
|
140
|
+
* numbers instead; their depth is the one that says most about intent, so it maps
|
|
141
|
+
* back onto strength rather than being dropped. Everything else about such a
|
|
142
|
+
* carve is re-derived, which is the point of having one control.
|
|
143
|
+
*/
|
|
144
|
+
export declare function normalizeCarveSettings(raw: Partial<HfCarveSettings & HfCarveProfile> | undefined): HfCarveSettings;
|
|
145
|
+
/**
|
|
146
|
+
* Every voice as one signal on the BED's clock.
|
|
147
|
+
*
|
|
148
|
+
* The analysis asks one question — where and when is speech masking this bed — and
|
|
149
|
+
* that question has one answer even when three people are talking at different
|
|
150
|
+
* times. Summing them onto the bed's timeline first means the existing analysis
|
|
151
|
+
* needs no notion of "which voice": bands come out of all the speech there is, and
|
|
152
|
+
* the envelopes rise wherever any of it is happening.
|
|
153
|
+
*
|
|
154
|
+
* `offsetSeconds` is where each voice starts relative to the bed. Audio before the
|
|
155
|
+
* bed begins is dropped rather than folded in at zero: it plays over nothing and
|
|
156
|
+
* cannot mask anything, and shifting it would put a cut where there is no voice.
|
|
157
|
+
*
|
|
158
|
+
* Summed, not averaged. Two people speaking at once mask more than either alone,
|
|
159
|
+
* which is exactly what the carve should answer to.
|
|
160
|
+
*/
|
|
161
|
+
export declare function mixCarveSources(parts: readonly {
|
|
162
|
+
samples: Float32Array;
|
|
163
|
+
offsetSeconds: number;
|
|
164
|
+
}[], sampleRate: number): Float32Array;
|
|
165
|
+
/**
|
|
166
|
+
* Analyse a voice and return the bands to dip in the bed. Bands come back in
|
|
167
|
+
* ascending frequency; the deepest cut lands on the strongest band and the
|
|
168
|
+
* others scale with their relative weight, floored at half depth so a selected
|
|
169
|
+
* band still does something audible.
|
|
170
|
+
*/
|
|
171
|
+
export declare function analyseCarveBands(voice: Float32Array, sampleRate: number, profile: HfCarveProfile): HfCarveBand[];
|
|
172
|
+
export interface HfCarveDynamics {
|
|
173
|
+
/** The band this envelope drives, matching a band from `analyseCarveBands`. */
|
|
174
|
+
freq: number;
|
|
175
|
+
/** Gain in dB over time, in seconds from the start of the *voice* clip. */
|
|
176
|
+
points: {
|
|
177
|
+
t: number;
|
|
178
|
+
v: number;
|
|
179
|
+
}[];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Turn each carved band into an envelope that follows the voice's level in that
|
|
183
|
+
* same band.
|
|
184
|
+
*
|
|
185
|
+
* The depth from `analyseCarveBands` becomes the envelope's ceiling rather than a
|
|
186
|
+
* constant: full depth where that band is at its loudest in the voice, flat where
|
|
187
|
+
* the voice is silent, scaled in dB between the two. Written as automation on the
|
|
188
|
+
* filters' gain, so playback and render both already know how to follow it.
|
|
189
|
+
*
|
|
190
|
+
* Times are relative to the voice clip, since that is what was measured. A caller
|
|
191
|
+
* placing these on another element shifts them by the gap between the two clips'
|
|
192
|
+
* starts. This assumes the voice plays from its own beginning at its `data-start`
|
|
193
|
+
* — a clip with a media offset would need that added.
|
|
194
|
+
*/
|
|
195
|
+
export declare function analyseCarveDynamics(voice: Float32Array, sampleRate: number, bands: HfCarveBand[]): HfCarveDynamics[];
|
|
196
|
+
/**
|
|
197
|
+
* The level envelope that keeps a bed under a voice.
|
|
198
|
+
*
|
|
199
|
+
* The spectral carve makes room in the frequency domain, which does nothing
|
|
200
|
+
* about a bed that is simply louder than the voice: the notches sit in the right
|
|
201
|
+
* places while the level goes on winning. This measures both tracks over the
|
|
202
|
+
* same windows and returns the gain the bed needs to sit `headroomDb` under the
|
|
203
|
+
* voice — no more than `duckDb`, and nothing at all where the voice is not
|
|
204
|
+
* speaking, so pauses stay open rather than being held down.
|
|
205
|
+
*
|
|
206
|
+
* Times are in seconds from the start of the *voice* clip; `offsetSeconds` is
|
|
207
|
+
* how far into the bed the voice's start falls, so the two are read at the same
|
|
208
|
+
* moment of the composition even when the clips begin at different times.
|
|
209
|
+
*/
|
|
210
|
+
export declare function analyseCarveDuck(voice: Float32Array, bed: Float32Array, sampleRate: number, profile: HfCarveProfile, offsetSeconds: number): {
|
|
211
|
+
t: number;
|
|
212
|
+
v: number;
|
|
213
|
+
}[];
|
|
214
|
+
/** Carve bands as an ordinary FX chain of peaking filters. */
|
|
215
|
+
export declare function carveBandsToChain(bands: HfCarveBand[]): HfAudioFxChain;
|
|
216
|
+
//# sourceMappingURL=audioCarve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioCarve.d.ts","sourceRoot":"","sources":["../src/audioCarve.ts"],"names":[],"mappings":"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"}
|