@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,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plays automation envelopes through native AudioParam scheduling.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here runs per frame. The envelope is handed to the audio thread once,
|
|
5
|
+
* as ramps and curves, so it stays sample-accurate no matter what the main
|
|
6
|
+
* thread is doing — and the offline render schedules it the same way, which is
|
|
7
|
+
* why preview and render agree by construction rather than by tolerance.
|
|
8
|
+
*/
|
|
9
|
+
import { type HfAutomation, type HfAutomationLane } from "../audioAutomation.js";
|
|
10
|
+
import type { HfAudioFxChain } from "../audioFx.js";
|
|
11
|
+
import type { FxNodeHandle, FxParamTarget } from "./audioFxGraph.js";
|
|
12
|
+
/** Where the clip sits in context time when its source is scheduled. */
|
|
13
|
+
export interface AutomationTiming {
|
|
14
|
+
/** Context time the source was scheduled at. */
|
|
15
|
+
scheduledAt: number;
|
|
16
|
+
/** Clip-local seconds already elapsed at that moment; negative before it starts. */
|
|
17
|
+
elapsed: number;
|
|
18
|
+
/** Playback rate, which compresses clip seconds into context seconds. */
|
|
19
|
+
rate: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Drop every event booked on a parameter, including a curve already under way.
|
|
23
|
+
*
|
|
24
|
+
* Anything landing inside a running curve is refused — a fresh curve, or the
|
|
25
|
+
* plain `.value` write that re-parameterising a chain performs — so the param has
|
|
26
|
+
* to be cancelled first. Measured against Chrome, any cancel frees the span; this
|
|
27
|
+
* takes the strongest form on purpose, because curve-over-curve refusals were
|
|
28
|
+
* reported from the field with a cancel at the new schedule time already in place
|
|
29
|
+
* and have never reproduced. Cancelling from zero cannot leave a stale event
|
|
30
|
+
* behind whatever that mechanism is.
|
|
31
|
+
*
|
|
32
|
+
* Safe on these parameters because this scheduler is their only writer: they
|
|
33
|
+
* belong to effect nodes it built, and the caller re-seeds the value in the same
|
|
34
|
+
* breath, so nothing clicks.
|
|
35
|
+
*/
|
|
36
|
+
export declare function clearParamLane(targets: readonly FxParamTarget[]): void;
|
|
37
|
+
/** Stop an envelope, leaving the parameter wherever it currently sits. */
|
|
38
|
+
export declare function cancelParamLane(targets: readonly FxParamTarget[], from: number): void;
|
|
39
|
+
/**
|
|
40
|
+
* Schedule one lane onto every AudioParam behind its knob.
|
|
41
|
+
*
|
|
42
|
+
* Existing automation from an earlier schedule is cancelled first, so calling
|
|
43
|
+
* this again after an edit replaces the envelope rather than layering on it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function scheduleParamLane(targets: readonly FxParamTarget[], lane: HfAutomationLane, scale: "linear" | "log", timing: AutomationTiming): void;
|
|
46
|
+
/** One built effect, paired with the chain node id lanes address it by. */
|
|
47
|
+
export interface AutomatableNode {
|
|
48
|
+
id?: string;
|
|
49
|
+
handle: FxNodeHandle;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Schedule every FX lane in an automation set against a built chain.
|
|
53
|
+
*
|
|
54
|
+
* Lanes with nowhere to write are skipped rather than reported: a one-pole
|
|
55
|
+
* filter exposes no frequency param, and a worklet effect exposes none at all.
|
|
56
|
+
* Returns the targets that were scheduled, so they can be cancelled together.
|
|
57
|
+
*/
|
|
58
|
+
export declare function scheduleChainAutomation(automation: HfAutomation, chain: HfAudioFxChain, nodes: readonly AutomatableNode[], timing: AutomationTiming,
|
|
59
|
+
/** The wet/dry blend around each preset run, from `FxChainHandle.presets`. */
|
|
60
|
+
presets?: Record<string, FxParamTarget[]>): FxParamTarget[];
|
|
61
|
+
/** The track's volume lane, if it has one. */
|
|
62
|
+
export declare function volumeLane(automation: HfAutomation): HfAutomationLane | null;
|
|
63
|
+
//# sourceMappingURL=audioFxAutomation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioFxAutomation.d.ts","sourceRoot":"","sources":["../../src/audio/audioFxAutomation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EAEtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAErE,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;CACd;AA2ID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAEtE;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAUrF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,QAAQ,GAAG,KAAK,EACvB,MAAM,EAAE,gBAAgB,GACvB,IAAI,CAeN;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,YAAY,EACxB,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,SAAS,eAAe,EAAE,EACjC,MAAM,EAAE,gBAAgB;AACxB,8EAA8E;AAC9E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,GACxC,aAAa,EAAE,CAuBjB;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,UAAU,EAAE,YAAY,GAAG,gBAAgB,GAAG,IAAI,CAE5E"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plays automation envelopes through native AudioParam scheduling.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here runs per frame. The envelope is handed to the audio thread once,
|
|
5
|
+
* as ramps and curves, so it stays sample-accurate no matter what the main
|
|
6
|
+
* thread is doing — and the offline render schedules it the same way, which is
|
|
7
|
+
* why preview and render agree by construction rather than by tolerance.
|
|
8
|
+
*/
|
|
9
|
+
import { isConstantLane, resolveAutomationRange, sampleAutomationCurve, sampleAutomationLane, } from "../audioAutomation.js";
|
|
10
|
+
import { parseAutomationTarget, VOLUME_TARGET } from "../audioAutomation.js";
|
|
11
|
+
/**
|
|
12
|
+
* Sampling density for segments that cannot be expressed as a ramp. Dense
|
|
13
|
+
* enough that a curve is inaudible from a continuous one, capped so a long
|
|
14
|
+
* segment cannot allocate without bound.
|
|
15
|
+
*/
|
|
16
|
+
const CURVE_POINTS_PER_SEC = 100;
|
|
17
|
+
const MIN_CURVE_POINTS = 8;
|
|
18
|
+
const MAX_CURVE_POINTS = 4096;
|
|
19
|
+
const identity = (v) => v;
|
|
20
|
+
/**
|
|
21
|
+
* A segment is a straight line only when nothing bends it: no curvature and no
|
|
22
|
+
* via point on the point it leaves, a linear parameter scale, and no unit
|
|
23
|
+
* mapping that could be non-linear. Everything else is sampled.
|
|
24
|
+
*
|
|
25
|
+
* Both bend forms have to be checked. `curve` is an exponent; a via point names
|
|
26
|
+
* an interior point the segment passes through, and it is what the timeline
|
|
27
|
+
* writes when a bend is dragged. Testing only the exponent read every dragged
|
|
28
|
+
* bend as straight and played it as a linear ramp — the lane drew one shape and
|
|
29
|
+
* the ear got another.
|
|
30
|
+
*/
|
|
31
|
+
function isStraight(point, scale, map) {
|
|
32
|
+
const bent = Boolean(point.curve) || (point.viaX !== undefined && point.viaY !== undefined);
|
|
33
|
+
return !bent && scale === "linear" && !map;
|
|
34
|
+
}
|
|
35
|
+
function curvePointCount(duration) {
|
|
36
|
+
const wanted = Math.ceil(duration * CURVE_POINTS_PER_SEC);
|
|
37
|
+
return Math.min(MAX_CURVE_POINTS, Math.max(MIN_CURVE_POINTS, wanted));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One segment of the envelope, as the event that plays it. Returns null when
|
|
41
|
+
* the segment is already behind the playhead.
|
|
42
|
+
*/
|
|
43
|
+
function segmentOp(lane, index, scale, timing, map) {
|
|
44
|
+
const { scheduledAt, elapsed, rate } = timing;
|
|
45
|
+
const apply = map ?? identity;
|
|
46
|
+
const toCtx = (t) => scheduledAt + (t - elapsed) / rate;
|
|
47
|
+
const a = lane.points[index];
|
|
48
|
+
const b = lane.points[index + 1];
|
|
49
|
+
const toTime = toCtx(b.t);
|
|
50
|
+
if (toTime <= scheduledAt)
|
|
51
|
+
return null;
|
|
52
|
+
const fromTime = Math.max(scheduledAt, toCtx(a.t));
|
|
53
|
+
const duration = toTime - fromTime;
|
|
54
|
+
if (duration <= 0)
|
|
55
|
+
return null;
|
|
56
|
+
if (isStraight(a, scale, map)) {
|
|
57
|
+
return { kind: "ramp", time: toTime, value: apply(b.v) };
|
|
58
|
+
}
|
|
59
|
+
// Sample from wherever the segment is entered, which is mid-segment when the
|
|
60
|
+
// playhead landed inside it.
|
|
61
|
+
const count = curvePointCount(duration);
|
|
62
|
+
const raw = sampleAutomationCurve(lane, Math.max(a.t, elapsed), b.t, count, scale);
|
|
63
|
+
const values = new Float32Array(count);
|
|
64
|
+
for (let k = 0; k < count; k += 1)
|
|
65
|
+
values[k] = apply(raw[k] ?? 0);
|
|
66
|
+
return { kind: "curve", time: fromTime, duration, values };
|
|
67
|
+
}
|
|
68
|
+
/** Turn a lane into the events one AudioParam should receive. */
|
|
69
|
+
function planOps(lane, scale, timing, map) {
|
|
70
|
+
const ops = [];
|
|
71
|
+
for (let i = 0; i + 1 < lane.points.length; i += 1) {
|
|
72
|
+
const op = segmentOp(lane, i, scale, timing, map);
|
|
73
|
+
if (op)
|
|
74
|
+
ops.push(op);
|
|
75
|
+
}
|
|
76
|
+
// Hold the envelope's value at the moment playback starts. This is what makes
|
|
77
|
+
// a lane correct before its first point and after its last, and it gives a
|
|
78
|
+
// following ramp something to ramp from.
|
|
79
|
+
//
|
|
80
|
+
// A value curve may not overlap another event, so the seed is dropped when a
|
|
81
|
+
// curve already begins at that instant — the curve sets the value itself.
|
|
82
|
+
const first = ops[0];
|
|
83
|
+
if (!(first?.kind === "curve" && first.time <= timing.scheduledAt)) {
|
|
84
|
+
ops.unshift({
|
|
85
|
+
kind: "set",
|
|
86
|
+
time: timing.scheduledAt,
|
|
87
|
+
value: (map ?? identity)(sampleAutomationLane(lane, timing.elapsed, scale)),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return ops;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Hand the plan to the AudioParam, degrading a curve the browser will not take.
|
|
94
|
+
*
|
|
95
|
+
* A value curve may not overlap another, and this has been seen refused in the
|
|
96
|
+
* field while rescheduling mid-playback — two passes landing a render quantum
|
|
97
|
+
* apart, which is what applying a carve while the transport runs does. Every
|
|
98
|
+
* caller here cancels the parameter first, and measured against Chrome that is
|
|
99
|
+
* enough to free even a curve already under way, so the mechanism behind those
|
|
100
|
+
* reports is not understood and does not reproduce. This catch is the backstop
|
|
101
|
+
* for it: unhandled, the exception reached the console and abandoned every
|
|
102
|
+
* remaining segment of the envelope.
|
|
103
|
+
*
|
|
104
|
+
* A ramp to the same destination is always accepted, since it books no span. The
|
|
105
|
+
* segment loses its bend until the next full reschedule, which is a far better
|
|
106
|
+
* failure than the rest of the envelope never being scheduled at all.
|
|
107
|
+
*/
|
|
108
|
+
function emit(param, ops) {
|
|
109
|
+
for (const op of ops) {
|
|
110
|
+
if (op.kind === "set")
|
|
111
|
+
param.setValueAtTime(op.value, op.time);
|
|
112
|
+
else if (op.kind === "ramp")
|
|
113
|
+
param.linearRampToValueAtTime(op.value, op.time);
|
|
114
|
+
else {
|
|
115
|
+
try {
|
|
116
|
+
param.setValueCurveAtTime(op.values, op.time, op.duration);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
const end = op.values[op.values.length - 1] ?? 0;
|
|
120
|
+
param.linearRampToValueAtTime(end, op.time + op.duration);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Drop every event booked on a parameter, including a curve already under way.
|
|
127
|
+
*
|
|
128
|
+
* Anything landing inside a running curve is refused — a fresh curve, or the
|
|
129
|
+
* plain `.value` write that re-parameterising a chain performs — so the param has
|
|
130
|
+
* to be cancelled first. Measured against Chrome, any cancel frees the span; this
|
|
131
|
+
* takes the strongest form on purpose, because curve-over-curve refusals were
|
|
132
|
+
* reported from the field with a cancel at the new schedule time already in place
|
|
133
|
+
* and have never reproduced. Cancelling from zero cannot leave a stale event
|
|
134
|
+
* behind whatever that mechanism is.
|
|
135
|
+
*
|
|
136
|
+
* Safe on these parameters because this scheduler is their only writer: they
|
|
137
|
+
* belong to effect nodes it built, and the caller re-seeds the value in the same
|
|
138
|
+
* breath, so nothing clicks.
|
|
139
|
+
*/
|
|
140
|
+
export function clearParamLane(targets) {
|
|
141
|
+
for (const target of targets)
|
|
142
|
+
target.param.cancelScheduledValues(0);
|
|
143
|
+
}
|
|
144
|
+
/** Stop an envelope, leaving the parameter wherever it currently sits. */
|
|
145
|
+
export function cancelParamLane(targets, from) {
|
|
146
|
+
for (const target of targets) {
|
|
147
|
+
// Keep the value the ramp had reached rather than snapping back to the
|
|
148
|
+
// last explicitly-set one, which would click.
|
|
149
|
+
if (typeof target.param.cancelAndHoldAtTime === "function") {
|
|
150
|
+
target.param.cancelAndHoldAtTime(from);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
target.param.cancelScheduledValues(from);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Schedule one lane onto every AudioParam behind its knob.
|
|
159
|
+
*
|
|
160
|
+
* Existing automation from an earlier schedule is cancelled first, so calling
|
|
161
|
+
* this again after an edit replaces the envelope rather than layering on it.
|
|
162
|
+
*/
|
|
163
|
+
export function scheduleParamLane(targets, lane, scale, timing) {
|
|
164
|
+
if (lane.points.length === 0)
|
|
165
|
+
return;
|
|
166
|
+
for (const target of targets) {
|
|
167
|
+
// Cleared from zero rather than from this instant: a reschedule mid-playback
|
|
168
|
+
// lands inside the previous pass's still-running curve, and the field reports
|
|
169
|
+
// of that being refused had a cancel at this instant already in place. The
|
|
170
|
+
// plan below re-seeds the value here, so clearing costs nothing audible.
|
|
171
|
+
clearParamLane([target]);
|
|
172
|
+
if (isConstantLane(lane)) {
|
|
173
|
+
const value = (target.map ?? identity)(lane.points[0].v);
|
|
174
|
+
target.param.setValueAtTime(value, timing.scheduledAt);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
emit(target.param, planOps(lane, scale, timing, target.map));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Schedule every FX lane in an automation set against a built chain.
|
|
182
|
+
*
|
|
183
|
+
* Lanes with nowhere to write are skipped rather than reported: a one-pole
|
|
184
|
+
* filter exposes no frequency param, and a worklet effect exposes none at all.
|
|
185
|
+
* Returns the targets that were scheduled, so they can be cancelled together.
|
|
186
|
+
*/
|
|
187
|
+
export function scheduleChainAutomation(automation, chain, nodes, timing,
|
|
188
|
+
/** The wet/dry blend around each preset run, from `FxChainHandle.presets`. */
|
|
189
|
+
presets) {
|
|
190
|
+
const byId = new Map(nodes.filter((n) => n.id).map((n) => [n.id, n.handle]));
|
|
191
|
+
const scheduled = [];
|
|
192
|
+
for (const lane of automation.lanes) {
|
|
193
|
+
const parsed = parseAutomationTarget(lane.target);
|
|
194
|
+
if (!parsed)
|
|
195
|
+
continue;
|
|
196
|
+
// A whole-preset lane drives the wet/dry blend the graph wrapped its run in,
|
|
197
|
+
// rather than any node's parameter — which is the point of it: a preset's
|
|
198
|
+
// nodes share no automatable parameter, and its worklet effects expose none
|
|
199
|
+
// at all.
|
|
200
|
+
const targets = parsed.kind === "preset"
|
|
201
|
+
? presets?.[parsed.presetId]
|
|
202
|
+
: parsed.kind === "fx"
|
|
203
|
+
? byId.get(parsed.nodeId)?.automation?.[parsed.param]
|
|
204
|
+
: undefined;
|
|
205
|
+
if (!targets || targets.length === 0)
|
|
206
|
+
continue;
|
|
207
|
+
const range = resolveAutomationRange(lane.target, chain);
|
|
208
|
+
if (!range)
|
|
209
|
+
continue;
|
|
210
|
+
scheduleParamLane(targets, lane, range.scale, timing);
|
|
211
|
+
scheduled.push(...targets);
|
|
212
|
+
}
|
|
213
|
+
return scheduled;
|
|
214
|
+
}
|
|
215
|
+
/** The track's volume lane, if it has one. */
|
|
216
|
+
export function volumeLane(automation) {
|
|
217
|
+
return automation.lanes.find((lane) => lane.target === VOLUME_TARGET) ?? null;
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=audioFxAutomation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioFxAutomation.js","sourceRoot":"","sources":["../../src/audio/audioFxAutomation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the Web Audio graph for an FX chain, one builder per `web` id in the
|
|
3
|
+
* core registry.
|
|
4
|
+
*
|
|
5
|
+
* Every node exposes `update`, so turning a dial re-parameterises the running
|
|
6
|
+
* graph instead of rebuilding it. That is the whole point of previewing in the
|
|
7
|
+
* browser: an AudioParam change lands on the next 128-sample render quantum,
|
|
8
|
+
* about 2.7 ms at 48 kHz, so the knob-to-ear loop is immediate.
|
|
9
|
+
*/
|
|
10
|
+
import { type HfAudioFxChain, type HfAudioFxParamValues } from "../audioFx.js";
|
|
11
|
+
import { audioFxWorkletsReady, ensureAudioFxWorklets } from "./audioFxWorklets.js";
|
|
12
|
+
/**
|
|
13
|
+
* Deterministic reverb impulse, shared by both engines so the browser and the
|
|
14
|
+
* render convolve the identical response. Exponentially-decaying noise with a
|
|
15
|
+
* one-pole lowpass standing in for air absorption; the PRNG is seeded from the
|
|
16
|
+
* parameters so the same room always produces the same tail.
|
|
17
|
+
*/
|
|
18
|
+
export declare function synthesizeReverbImpulse(sampleRate: number, size: number, damping: number): Float32Array;
|
|
19
|
+
/**
|
|
20
|
+
* Where an automation lane writes when it drives one knob.
|
|
21
|
+
*
|
|
22
|
+
* A knob is not always one AudioParam. A wet/dry mix is two gains moving in
|
|
23
|
+
* opposition, and a knob in milliseconds drives a delay time in seconds, so
|
|
24
|
+
* each target carries its own mapping out of the knob's declared unit.
|
|
25
|
+
*/
|
|
26
|
+
export interface FxParamTarget {
|
|
27
|
+
param: AudioParam;
|
|
28
|
+
map?: (value: number) => number;
|
|
29
|
+
}
|
|
30
|
+
export interface FxNodeHandle {
|
|
31
|
+
input: AudioNode;
|
|
32
|
+
output: AudioNode;
|
|
33
|
+
update(params: HfAudioFxParamValues): void;
|
|
34
|
+
/**
|
|
35
|
+
* AudioParams behind the knobs the registry marks `automatable`, keyed by
|
|
36
|
+
* parameter key. Absent for a node whose values cannot be scheduled.
|
|
37
|
+
*/
|
|
38
|
+
automation?: Record<string, FxParamTarget[]>;
|
|
39
|
+
dispose(): void;
|
|
40
|
+
}
|
|
41
|
+
/** Effect ids whose Web Audio node needs a worklet module registered first. */
|
|
42
|
+
export declare function chainNeedsWorklets(chain: HfAudioFxChain): boolean;
|
|
43
|
+
export declare function buildFxNode(ctx: BaseAudioContext, type: string, params: HfAudioFxParamValues, elapsed?: number): FxNodeHandle;
|
|
44
|
+
export interface FxChainHandle {
|
|
45
|
+
input: AudioNode;
|
|
46
|
+
output: AudioNode;
|
|
47
|
+
/** Built effects in chain order, carrying the node ids lanes address. */
|
|
48
|
+
nodes: {
|
|
49
|
+
id?: string;
|
|
50
|
+
type: string;
|
|
51
|
+
handle: FxNodeHandle;
|
|
52
|
+
}[];
|
|
53
|
+
/**
|
|
54
|
+
* The wet/dry blend around each preset run, by preset id — where a
|
|
55
|
+
* whole-preset lane writes. Two gains in opposition, the same shape
|
|
56
|
+
* `mixTargets` builds for an effect's own mix knob.
|
|
57
|
+
*/
|
|
58
|
+
presets: Record<string, FxParamTarget[]>;
|
|
59
|
+
/** Re-parameterise in place when the shape is unchanged; false if a rebuild is needed. */
|
|
60
|
+
update(chain: HfAudioFxChain): boolean;
|
|
61
|
+
dispose(): void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the whole chain in series. Returns a handle whose `input`/`output` can
|
|
65
|
+
* be spliced into any graph; an empty chain yields a pass-through.
|
|
66
|
+
*
|
|
67
|
+
* `elapsed` is where in the clip this is being built — see `Builder`. It only
|
|
68
|
+
* reaches the modulated effects, and only matters when the graph is built after
|
|
69
|
+
* the audio has already started.
|
|
70
|
+
*/
|
|
71
|
+
export declare function buildFxChain(ctx: BaseAudioContext, chain: HfAudioFxChain, elapsed?: number): FxChainHandle;
|
|
72
|
+
export { audioFxWorkletsReady, ensureAudioFxWorklets };
|
|
73
|
+
//# sourceMappingURL=audioFxGraph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioFxGraph.d.ts","sourceRoot":"","sources":["../../src/audio/audioFxGraph.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAIL,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAEnF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,YAAY,CA8Bd;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;IAC7C,OAAO,IAAI,IAAI,CAAC;CACjB;AA8aD,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAEjE;AAED,wBAAgB,WAAW,CACzB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,oBAAoB,EAC5B,OAAO,SAAI,GACV,YAAY,CAWd;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,SAAS,CAAC;IAClB,yEAAyE;IACzE,KAAK,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE,CAAC;IAC7D;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;IACzC,0FAA0F;IAC1F,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC;IACvC,OAAO,IAAI,IAAI,CAAC;CACjB;AAgED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,gBAAgB,EACrB,KAAK,EAAE,cAAc,EACrB,OAAO,SAAI,GACV,aAAa,CAwHf;AAED,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,CAAC"}
|