@vessel-dsp/runtime 0.2.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/README.md +178 -0
- package/dist/admission.d.ts +94 -0
- package/dist/admission.d.ts.map +1 -0
- package/dist/admission.js +209 -0
- package/dist/admission.js.map +1 -0
- package/dist/calibrate.d.ts +104 -0
- package/dist/calibrate.d.ts.map +1 -0
- package/dist/calibrate.js +420 -0
- package/dist/calibrate.js.map +1 -0
- package/dist/chain-advisories.d.ts +71 -0
- package/dist/chain-advisories.d.ts.map +1 -0
- package/dist/chain-advisories.js +268 -0
- package/dist/chain-advisories.js.map +1 -0
- package/dist/chain-scale.d.ts +82 -0
- package/dist/chain-scale.d.ts.map +1 -0
- package/dist/chain-scale.js +121 -0
- package/dist/chain-scale.js.map +1 -0
- package/dist/chain-slot.d.ts +130 -0
- package/dist/chain-slot.d.ts.map +1 -0
- package/dist/chain-slot.js +105 -0
- package/dist/chain-slot.js.map +1 -0
- package/dist/chain.d.ts +113 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +355 -0
- package/dist/chain.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/numeric-pivot.d.ts +4 -0
- package/dist/numeric-pivot.d.ts.map +1 -0
- package/dist/numeric-pivot.js +192 -0
- package/dist/numeric-pivot.js.map +1 -0
- package/dist/reference-runtime.d.ts +1080 -0
- package/dist/reference-runtime.d.ts.map +1 -0
- package/dist/reference-runtime.js +6743 -0
- package/dist/reference-runtime.js.map +1 -0
- package/dist/settle.d.ts +359 -0
- package/dist/settle.d.ts.map +1 -0
- package/dist/settle.js +900 -0
- package/dist/settle.js.map +1 -0
- package/dist/supply-ground.d.ts +22 -0
- package/dist/supply-ground.d.ts.map +1 -0
- package/dist/supply-ground.js +62 -0
- package/dist/supply-ground.js.map +1 -0
- package/dist/supply.d.ts +30 -0
- package/dist/supply.d.ts.map +1 -0
- package/dist/supply.js +10 -0
- package/dist/supply.js.map +1 -0
- package/dist/taper.d.ts +10 -0
- package/dist/taper.d.ts.map +1 -0
- package/dist/taper.js +34 -0
- package/dist/taper.js.map +1 -0
- package/dist/v2-wasm-engine.d.ts +91 -0
- package/dist/v2-wasm-engine.d.ts.map +1 -0
- package/dist/v2-wasm-engine.js +296 -0
- package/dist/v2-wasm-engine.js.map +1 -0
- package/dist/wasm/v2_dsp.cjs +22 -0
- package/dist/wasm/v2_dsp.wasm +0 -0
- package/package.json +62 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// What a chain can say about itself that no single program can.
|
|
2
|
+
//
|
|
3
|
+
// `ChainRuntime` scales between slots by the ratio of one slot's output full scale to the next
|
|
4
|
+
// slot's input full scale (`Program.portFullScaleVolts`, derived in
|
|
5
|
+
// `../compiler/port-full-scale.ts`). When either side is `null` the program did not state a
|
|
6
|
+
// supply to derive the bound from, so there is no ratio to apply and the signal crosses at 1.0 --
|
|
7
|
+
// which is the old behaviour, and is a guess about level rather than a statement about it.
|
|
8
|
+
//
|
|
9
|
+
// It then applies the **impedance divider** at the same seam (`seamDivider`), which is a different
|
|
10
|
+
// question from the same connection: full scale says what units a number is in, loading says how
|
|
11
|
+
// much of the signal survives the connection at all. Both default to 1 when unstated, and neither
|
|
12
|
+
// invents a figure.
|
|
13
|
+
//
|
|
14
|
+
// Advisory rather than fail-closed, the same shape as `./supply-ground.ts`: the chain still runs,
|
|
15
|
+
// and what is missing is a source fact, not a runtime capability.
|
|
16
|
+
/**
|
|
17
|
+
* How far along the amplification path each coverage sits, so a rule can say "at or beyond" instead
|
|
18
|
+
* of naming one value and silently missing the others.
|
|
19
|
+
*
|
|
20
|
+
* Introduced because `speaker-signal-into-input` tested `produces === "speaker-electrical"` and
|
|
21
|
+
* therefore let **`miked` into an instrument input pass in silence** -- a full-rig capture feeding a
|
|
22
|
+
* pedal, which is the same kind error one rung further along. Ordering the vocabulary is what makes
|
|
23
|
+
* that class of miss impossible rather than a thing to remember.
|
|
24
|
+
*/
|
|
25
|
+
const COVERAGE_RANK = {
|
|
26
|
+
instrument: 0,
|
|
27
|
+
preamp: 1,
|
|
28
|
+
"speaker-electrical": 2,
|
|
29
|
+
miked: 3,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Seams where one side states no full scale. Pure, so a host can ask before playing.
|
|
33
|
+
*
|
|
34
|
+
* A one-slot chain has no seam and never reports.
|
|
35
|
+
*/
|
|
36
|
+
export function chainAdvisories(chain) {
|
|
37
|
+
const advisories = [];
|
|
38
|
+
for (let slot = 1; slot < chain.length; slot += 1) {
|
|
39
|
+
const upstream = chain[slot - 1]?.portFullScaleVolts.output ?? null;
|
|
40
|
+
const downstream = chain[slot]?.portFullScaleVolts.input ?? null;
|
|
41
|
+
if (upstream !== null && downstream !== null) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const missing = upstream === null && downstream === null
|
|
45
|
+
? "neither side"
|
|
46
|
+
: upstream === null
|
|
47
|
+
? `slot ${slot - 1}'s output`
|
|
48
|
+
: `slot ${slot}'s input`;
|
|
49
|
+
advisories.push({
|
|
50
|
+
code: "unscalable-seam",
|
|
51
|
+
slots: [slot - 1, slot],
|
|
52
|
+
message: `between slot ${slot - 1} and slot ${slot}, ${missing} states a full-scale ` +
|
|
53
|
+
"voltage, so the signal crosses unscaled: a level mismatch here is not corrected " +
|
|
54
|
+
"and the receiving slot may be driven far above or below what it expects. The " +
|
|
55
|
+
"bound is derived from the program's own supplies, so a program declaring none " +
|
|
56
|
+
"cannot supply one",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// **A speaker terminal is not an instrument signal.** `speaker-electrical` means the output node
|
|
60
|
+
// *is* the transformer secondary -- tens of volts across an 8 or 16 ohm load, not the fraction of
|
|
61
|
+
// a volt a jack expects. Feeding that to a slot that wants instrument level is wrong in kind as
|
|
62
|
+
// well as level, and it is the shape of mistake that put an amp's preamp tap and another amp's
|
|
63
|
+
// speaker terminal in the same "plausible" column for a day.
|
|
64
|
+
//
|
|
65
|
+
// **Narrowed to what the receiver expects.** This used to fire whenever a `speaker-electrical`
|
|
66
|
+
// output fed *anything*, which would report the single most ordinary amp chain there is: an amp
|
|
67
|
+
// into a cabinet. A cab stage is exactly what a speaker terminal is for.
|
|
68
|
+
//
|
|
69
|
+
// **"At or beyond a speaker terminal", not "is a speaker terminal".** The first version of this
|
|
70
|
+
// tested one value and let `miked` through: a full-rig capture into a pedal reported nothing,
|
|
71
|
+
// though it is the same error one rung further along. `COVERAGE_RANK` is what closes that.
|
|
72
|
+
for (let slot = 1; slot < chain.length; slot += 1) {
|
|
73
|
+
const produces = chain[slot - 1]?.produces;
|
|
74
|
+
if (produces === undefined ||
|
|
75
|
+
COVERAGE_RANK[produces] < COVERAGE_RANK["speaker-electrical"]) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (chain[slot]?.expects !== "instrument") {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
advisories.push({
|
|
82
|
+
code: "speaker-signal-into-input",
|
|
83
|
+
slots: [slot - 1, slot],
|
|
84
|
+
message: `slot ${slot - 1} produces a ${produces} signal -- at or past the speaker terminal -- ` +
|
|
85
|
+
`and it feeds slot ${slot}, whose input expects an instrument-level signal: this is a ` +
|
|
86
|
+
"difference of kind, not only of level, and no scaling makes it a valid connection",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// **The other direction is milder, and deliberately narrower.** A cabinet stage fed a signal that
|
|
90
|
+
// never passed a power amp is thin rather than wrong -- nothing is over-driven and nothing is
|
|
91
|
+
// damaged -- so it is reported, not treated as a kind error.
|
|
92
|
+
//
|
|
93
|
+
// **`preamp` is excluded on purpose, and that exclusion is load-bearing.** A preamp into an
|
|
94
|
+
// impulse response is an established, ordinary rig, and `mesa-boogie-mark-v` renders `preamp`
|
|
95
|
+
// coverage precisely because the signal-chain plan's decision 2 says to honour its declared
|
|
96
|
+
// handoff. Firing here on rank 1 would report that decision as a mistake on every chain.
|
|
97
|
+
//
|
|
98
|
+
// So only rank 0 into a speaker-terminal stage reports: an instrument-level signal with no
|
|
99
|
+
// amplification of any kind between it and a cabinet.
|
|
100
|
+
for (let slot = 1; slot < chain.length; slot += 1) {
|
|
101
|
+
const produces = chain[slot - 1]?.produces;
|
|
102
|
+
const expects = chain[slot]?.expects;
|
|
103
|
+
if (produces === undefined || expects === undefined) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (COVERAGE_RANK[produces] !== COVERAGE_RANK.instrument ||
|
|
107
|
+
COVERAGE_RANK[expects] < COVERAGE_RANK["speaker-electrical"]) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
advisories.push({
|
|
111
|
+
code: "instrument-into-speaker-stage",
|
|
112
|
+
slots: [slot - 1, slot],
|
|
113
|
+
message: `slot ${slot} expects a ${expects} signal -- one that has passed a power amp -- and ` +
|
|
114
|
+
`slot ${slot - 1} produces instrument level with no amplification stage between ` +
|
|
115
|
+
"them: the chain runs, but the cabinet colours a signal that never saw a power " +
|
|
116
|
+
"stage, which is audibly thin rather than incorrect",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// **A miked signal must not be miked again.** Now expressible, because the cabinet is a slot:
|
|
120
|
+
// two consecutive slots both *producing* `miked` means a cab stage was applied to a signal that
|
|
121
|
+
// already carries one.
|
|
122
|
+
//
|
|
123
|
+
// This is the only warnable cab arrangement, and deliberately so. A NAM amp capture produces
|
|
124
|
+
// `speaker-electrical`, so NAM into an IR is silent -- it is what most people do. A whitebox
|
|
125
|
+
// amp into an IR is silent for the same reason. **No cab stage at all is silent too**: a full-rig
|
|
126
|
+
// NAM, or a player who wants none, is a complete chain and nothing here should nag for one.
|
|
127
|
+
for (let slot = 1; slot < chain.length; slot += 1) {
|
|
128
|
+
if (chain[slot - 1]?.produces !== "miked" ||
|
|
129
|
+
chain[slot]?.produces !== "miked") {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
advisories.push({
|
|
133
|
+
code: "cab-after-miked",
|
|
134
|
+
slots: [slot - 1, slot],
|
|
135
|
+
message: `slot ${slot - 1} already produces a miked signal -- a full-rig capture, or a ` +
|
|
136
|
+
`cabinet stage -- and slot ${slot} applies another one: the cabinet and microphone ` +
|
|
137
|
+
"colour the signal twice, which is audible as a hollow, over-filtered tone rather " +
|
|
138
|
+
"than a level error",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// **Only the asymmetric case is worth a word.** A divider needs an impedance on both sides, and
|
|
142
|
+
// the two ways it can be missing are not equally interesting:
|
|
143
|
+
//
|
|
144
|
+
// - *Neither side declares* — the ordinary state of every corpus document today, and reporting
|
|
145
|
+
// it would be a line of noise on every seam of every chain. Silence.
|
|
146
|
+
// - *Either side is a processor* — a NAM or an IR has no impedance to declare, so there is
|
|
147
|
+
// nothing missing. Silence, and this is why `portImpedanceOhms` is nullable as a whole
|
|
148
|
+
// object rather than only per port.
|
|
149
|
+
// - *One electrical side declares and the other does not* — someone measured half of a real
|
|
150
|
+
// interaction and the other half silently defaulted to no loading at all. That is worth
|
|
151
|
+
// naming, because the declared number looks like it is doing something and is not.
|
|
152
|
+
for (let slot = 1; slot < chain.length; slot += 1) {
|
|
153
|
+
const source = chain[slot - 1]?.portImpedanceOhms;
|
|
154
|
+
const load = chain[slot]?.portImpedanceOhms;
|
|
155
|
+
if (source === null ||
|
|
156
|
+
source === undefined ||
|
|
157
|
+
load === null ||
|
|
158
|
+
load === undefined) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const out = source.output;
|
|
162
|
+
const inn = load.input;
|
|
163
|
+
if ((out === null) === (inn === null)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
advisories.push({
|
|
167
|
+
code: "undividable-seam",
|
|
168
|
+
slots: [slot - 1, slot],
|
|
169
|
+
message: out === null
|
|
170
|
+
? `slot ${slot} declares a ${inn} Ω input impedance but slot ${slot - 1} declares ` +
|
|
171
|
+
"no output impedance, so no loading is applied and the declared figure has no " +
|
|
172
|
+
"effect: a low input impedance is meant to load what drives it"
|
|
173
|
+
: `slot ${slot - 1} declares a ${out} Ω output impedance but slot ${slot} declares ` +
|
|
174
|
+
"no input impedance, so the signal crosses without loading and the declared " +
|
|
175
|
+
"figure has no effect",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return advisories;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Bypass requested on a packet that declares no bypass switch.
|
|
182
|
+
*
|
|
183
|
+
* `audio.bypass: "none"` is a declared absence (37 core-only lanes + 1 passive volume, 38 total
|
|
184
|
+
* after backfill) — not a missing field. A chain "bypass" on such a slot has nothing in the
|
|
185
|
+
* circuit to mean; wire is the only honest choice, and the slot must say so via advisory, not
|
|
186
|
+
* log. Hard-coded to the Program's carried declaration when present; missing field (pre-backfill
|
|
187
|
+
* programs) is treated as "switch" (has bypass) until Y lands `program.bypass`.
|
|
188
|
+
*/
|
|
189
|
+
export function bypassNotModelledAdvisories(slots) {
|
|
190
|
+
const advisories = [];
|
|
191
|
+
for (let idx = 0; idx < slots.length; idx++) {
|
|
192
|
+
const slot = slots[idx];
|
|
193
|
+
if (slot === undefined || slot.kind !== "program")
|
|
194
|
+
continue;
|
|
195
|
+
const bypassMode = slot.bypassMode ?? "effect";
|
|
196
|
+
if (bypassMode !== "wire" && bypassMode !== "buffer")
|
|
197
|
+
continue;
|
|
198
|
+
const bypass = slot.program.bypass;
|
|
199
|
+
if (bypass.declared === "none") {
|
|
200
|
+
advisories.push({
|
|
201
|
+
code: "bypass-not-modelled",
|
|
202
|
+
slots: [idx, idx],
|
|
203
|
+
message: `slot ${idx} bypass not modelled by this packet; slot is wired through (audio.bypass: "none" — declared absence, not a guess)`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
else if (bypass.kind === "not-in-audio-path") {
|
|
207
|
+
advisories.push({
|
|
208
|
+
code: "bypass-not-modelled",
|
|
209
|
+
slots: [idx, idx],
|
|
210
|
+
message: `slot ${idx} bypass switch is not in the audio path; slot is wired through`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return advisories;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The multiplier applied to the signal crossing into `slot`.
|
|
218
|
+
*
|
|
219
|
+
* Two independent factors, multiplied:
|
|
220
|
+
*
|
|
221
|
+
* - the **full-scale ratio**, converting between the two slots' own voltage normalisations, or 1
|
|
222
|
+
* where either side states no supply to derive a bound from;
|
|
223
|
+
* - the **impedance divider** `Zin / (Zout + Zin)`, the loading one slot's input puts on the
|
|
224
|
+
* previous slot's output, or 1 where either electrical side declares none.
|
|
225
|
+
*
|
|
226
|
+
* They are separate questions and neither substitutes for the other: full scale is about the units
|
|
227
|
+
* a number is in, loading is about how much of the signal survives the connection. A seam can scale
|
|
228
|
+
* and not divide, or divide and not scale.
|
|
229
|
+
*/
|
|
230
|
+
export function seamScale(chain, slot) {
|
|
231
|
+
const upstream = chain[slot - 1]?.portFullScaleVolts.output ?? null;
|
|
232
|
+
const downstream = chain[slot]?.portFullScaleVolts.input ?? null;
|
|
233
|
+
const ratio = upstream === null || downstream === null || downstream === 0
|
|
234
|
+
? 1
|
|
235
|
+
: upstream / downstream;
|
|
236
|
+
return ratio * seamDivider(chain, slot);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The resistive divider at a seam, or `1` when it cannot be computed.
|
|
240
|
+
*
|
|
241
|
+
* **Resistive and one-directional, which is a stated approximation rather than an oversight.** Real
|
|
242
|
+
* interaction between two stages is bidirectional and frequency-dependent, and only co-solving the
|
|
243
|
+
* adjacent blocks captures it -- the fidelity ceiling the unified plan §5.3 names and this
|
|
244
|
+
* deliberately does not attempt. What this does capture is the audible first-order half: a fuzz with
|
|
245
|
+
* a low input impedance loads the pickup or the pedal ahead of it, which is a large part of what that
|
|
246
|
+
* pedal *is*.
|
|
247
|
+
*
|
|
248
|
+
* **No default is invented anywhere.** A missing impedance yields 1, never an assumed "standard
|
|
249
|
+
* 1 MΩ input" -- a fabricated convention would be indistinguishable from a measurement in every
|
|
250
|
+
* downstream number. Measured on the corpus as of 2026-08-21 that means this returns 1 for every
|
|
251
|
+
* seam: **zero input jacks declare a typed impedance**, and the only four declarations anywhere are
|
|
252
|
+
* output impedances (`marshall-blues-breaker` 1 MΩ, `mxr-m117r-flanger` 1 kΩ,
|
|
253
|
+
* `mxr-noise-gate-line-driver` 10 kΩ, `pigtronix-philosophers-tone` 150 Ω). The mechanism is
|
|
254
|
+
* therefore inert until sources carry the input side, and `undividable-seam` is what says so out
|
|
255
|
+
* loud on a chain built from one of those four.
|
|
256
|
+
*/
|
|
257
|
+
export function seamDivider(chain, slot) {
|
|
258
|
+
const source = chain[slot - 1]?.portImpedanceOhms;
|
|
259
|
+
const load = chain[slot]?.portImpedanceOhms;
|
|
260
|
+
const out = source?.output ?? null;
|
|
261
|
+
const inn = load?.input ?? null;
|
|
262
|
+
if (out === null || inn === null) {
|
|
263
|
+
return 1;
|
|
264
|
+
}
|
|
265
|
+
const total = out + inn;
|
|
266
|
+
return total <= 0 ? 1 : inn / total;
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=chain-advisories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain-advisories.js","sourceRoot":"","sources":["../src/chain-advisories.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Program } from "@vessel-dsp/compiler";
|
|
2
|
+
/**
|
|
3
|
+
* Pure arithmetic: the multiplier `ChainRuntime` applies to one boundary's samples, from the
|
|
4
|
+
* two adjacent ports' full-scale voltages. `null` on either side is unity — see this
|
|
5
|
+
* module's header for why that is not a refusal.
|
|
6
|
+
*/
|
|
7
|
+
export declare function chainScaleFactor(outFullScaleVolts: number | null, inFullScaleVolts: number | null): number;
|
|
8
|
+
/** One boundary where the chain fell back to unity because a full scale was `null`. */
|
|
9
|
+
export type ChainScaleAdvisory = {
|
|
10
|
+
/** The boundary sits between this slot and the next. */
|
|
11
|
+
readonly slot: number;
|
|
12
|
+
readonly message: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Every boundary in an ordered chain where at least one side's full-scale voltage is `null`,
|
|
16
|
+
* so `ChainRuntime` applied unity there instead of the declared ratio.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors `supplyGroundConflicts`'s shape deliberately: a chain-level fact, computed from
|
|
19
|
+
* the programs alone, carried alongside a successful load rather than blocking one.
|
|
20
|
+
*/
|
|
21
|
+
export declare function chainScaleAdvisories(programs: readonly Program[]): readonly ChainScaleAdvisory[];
|
|
22
|
+
/**
|
|
23
|
+
* The multiplier the **worklet** applies to the chain's last slot before the samples reach the
|
|
24
|
+
* DAC, so a program that swings to its supply rail does not peg the output against it.
|
|
25
|
+
*
|
|
26
|
+
* **This lives here because it was living in exactly one place and that place was not reachable
|
|
27
|
+
* from a report.** It was inline in `src/web/v2-audio-worklet.ts`, which no instrument imports, so
|
|
28
|
+
* every corpus measurement was taken on the program's output in volts while the app played that
|
|
29
|
+
* output divided by up to 558.61 (`marshall-1959-super-lead-plexi`). A packet could read a healthy
|
|
30
|
+
* 5e-3 V and reach the speaker at -101 dBFS. Both halves have to read the same rule or the
|
|
31
|
+
* measurement is not of the product.
|
|
32
|
+
*
|
|
33
|
+
* Unity below 1.0 V rather than a gain: this normalises a rail down, it never boosts a quiet
|
|
34
|
+
* program up to meet the ceiling.
|
|
35
|
+
*/
|
|
36
|
+
export declare function dacScaleFactor(fullScaleOutVolts: number | null): number;
|
|
37
|
+
/**
|
|
38
|
+
* A program's output level in the domain a listener is in: dBFS at the DAC, after
|
|
39
|
+
* `dacScaleFactor`.
|
|
40
|
+
*
|
|
41
|
+
* **Volts and dBFS are not interchangeable and the corpus has been treating them as if they
|
|
42
|
+
* were.** `report-input-dependence.ts`'s `AUDIBLE_FLOOR` comment reconciles its absolute-volts bar
|
|
43
|
+
* against a tier's `-50 dBFS` one by taking "full scale 1.0" -- true of no packet in the corpus,
|
|
44
|
+
* where the declared full scale runs 4.5 V to 558.61 V. Converted through the rule the app
|
|
45
|
+
* actually applies, a single volts threshold is a different dBFS bar for every packet: 1e-3 V is
|
|
46
|
+
* -60 dBFS at full scale 1.0, -79 dBFS on a 9 V pedal and -115 dBFS on the plexi.
|
|
47
|
+
*/
|
|
48
|
+
export declare function outputDbfs(rmsVolts: number, fullScaleOutVolts: number | null): number;
|
|
49
|
+
/**
|
|
50
|
+
* The number a level conversion divides an output by: the port's declared 0 dBFS
|
|
51
|
+
* reference where it states one, the derived ceiling for speaker-electrical outputs,
|
|
52
|
+
* or the instrument operating reference convention.
|
|
53
|
+
*
|
|
54
|
+
* These are different quantities -- `link.ts` says so and keeps them apart -- and a folding
|
|
55
|
+
* consumer has to pick the one its question needs. A conversion into a level (a +/-1 file,
|
|
56
|
+
* a DAC scale, a dBFS audibility judgement) wants the **reference** where a source states it:
|
|
57
|
+
* it is what the source says full scale *means* rather than what the node can reach.
|
|
58
|
+
*
|
|
59
|
+
* For `speaker-electrical` stage coverage (power amp secondary into speaker tap), the
|
|
60
|
+
* transformer-stepped ceiling (30 V to 500 V) is physically right to prevent DAC wrapping.
|
|
61
|
+
*
|
|
62
|
+
* For `instrument` stage coverage (stompboxes/pedals), the DC supply rail (e.g. 9 V) is an
|
|
63
|
+
* electrical clipping ceiling, never an operating reference level. Falling back to the rail
|
|
64
|
+
* caused pedals without an explicit output reference to play 19 dB quieter than input
|
|
65
|
+
* (0.3 V / 9 V = -29.5 dBFS). When no output reference is declared:
|
|
66
|
+
* 1. Read `portReferenceVolts.input` if declared (preserving the pedal's declared operating scale).
|
|
67
|
+
* 2. Fall back to 1.0 V, the convention matching digital full scale (1.0 digital = 1.0 V),
|
|
68
|
+
* which is the median and mode of the declared corpus population (59 of 65 declared inputs).
|
|
69
|
+
*/
|
|
70
|
+
export declare function outputConversionFullScale(contract: {
|
|
71
|
+
readonly portFullScaleVolts: {
|
|
72
|
+
readonly input: number | null;
|
|
73
|
+
readonly output: number | null;
|
|
74
|
+
};
|
|
75
|
+
readonly portReferenceVolts?: {
|
|
76
|
+
readonly input: number | null;
|
|
77
|
+
readonly output: number | null;
|
|
78
|
+
};
|
|
79
|
+
readonly stageCoverage?: Program["stageCoverage"];
|
|
80
|
+
readonly produces?: Program["stageCoverage"];
|
|
81
|
+
}): number | null;
|
|
82
|
+
//# sourceMappingURL=chain-scale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain-scale.d.ts","sourceRoot":"","sources":["../src/chain-scale.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC/B,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAC7B,MAAM,CAKR;AAED,uFAAuF;AACvF,MAAM,MAAM,kBAAkB,GAAG;IAChC,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAC1B,SAAS,kBAAkB,EAAE,CAgB/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAEvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAErF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,yBAAyB,CACxC,QAAQ,EAAE;IACT,QAAQ,CAAC,kBAAkB,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC/F,QAAQ,CAAC,kBAAkB,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAChG,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;CAC7C,GACC,MAAM,GAAG,IAAI,CAqBf"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// The chain's level convention: multiply between slots by the ratio of declared full-scale
|
|
2
|
+
// voltages, per the signal-chain plan
|
|
3
|
+
// (thoughts/shared/plans/2026-08-21-signal-chain-and-amp-output-stage.md) §3.1 —
|
|
4
|
+
//
|
|
5
|
+
// x_normalized(slot n+1) = y_normalized(slot n) * (fullScale(out n) / fullScale(in n+1))
|
|
6
|
+
//
|
|
7
|
+
// **Unity, not a refusal, when either side is `null`.** A program that declares no supply
|
|
8
|
+
// gives `Program.ports` nothing to derive a ceiling from — see `../compiler/port-full-
|
|
9
|
+
// scale.ts` — and the chain still has to produce audio: it is not wrong to play it, only
|
|
10
|
+
// unable to say the boundary's level is correct. `chainScaleAdvisories` is the visible half
|
|
11
|
+
// of that (structured, matching `../runtime/supply-ground.ts`'s pattern rather than a
|
|
12
|
+
// console.warn), so a host can surface it without the render itself refusing.
|
|
13
|
+
/**
|
|
14
|
+
* Pure arithmetic: the multiplier `ChainRuntime` applies to one boundary's samples, from the
|
|
15
|
+
* two adjacent ports' full-scale voltages. `null` on either side is unity — see this
|
|
16
|
+
* module's header for why that is not a refusal.
|
|
17
|
+
*/
|
|
18
|
+
export function chainScaleFactor(outFullScaleVolts, inFullScaleVolts) {
|
|
19
|
+
if (outFullScaleVolts === null || inFullScaleVolts === null) {
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
return outFullScaleVolts / inFullScaleVolts;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Every boundary in an ordered chain where at least one side's full-scale voltage is `null`,
|
|
26
|
+
* so `ChainRuntime` applied unity there instead of the declared ratio.
|
|
27
|
+
*
|
|
28
|
+
* Mirrors `supplyGroundConflicts`'s shape deliberately: a chain-level fact, computed from
|
|
29
|
+
* the programs alone, carried alongside a successful load rather than blocking one.
|
|
30
|
+
*/
|
|
31
|
+
export function chainScaleAdvisories(programs) {
|
|
32
|
+
const advisories = [];
|
|
33
|
+
for (let slot = 0; slot < programs.length - 1; slot++) {
|
|
34
|
+
const out = programs[slot]?.portFullScaleVolts.output ?? null;
|
|
35
|
+
const nextIn = programs[slot + 1]?.portFullScaleVolts.input ?? null;
|
|
36
|
+
if (out === null || nextIn === null) {
|
|
37
|
+
advisories.push({
|
|
38
|
+
slot,
|
|
39
|
+
message: `slot ${slot}'s output or slot ${slot + 1}'s input declares no full-scale ` +
|
|
40
|
+
"voltage (its program declares no supply), so the chain applies unity " +
|
|
41
|
+
"scaling at this boundary instead of the declared ratio",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return advisories;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The multiplier the **worklet** applies to the chain's last slot before the samples reach the
|
|
49
|
+
* DAC, so a program that swings to its supply rail does not peg the output against it.
|
|
50
|
+
*
|
|
51
|
+
* **This lives here because it was living in exactly one place and that place was not reachable
|
|
52
|
+
* from a report.** It was inline in `src/web/v2-audio-worklet.ts`, which no instrument imports, so
|
|
53
|
+
* every corpus measurement was taken on the program's output in volts while the app played that
|
|
54
|
+
* output divided by up to 558.61 (`marshall-1959-super-lead-plexi`). A packet could read a healthy
|
|
55
|
+
* 5e-3 V and reach the speaker at -101 dBFS. Both halves have to read the same rule or the
|
|
56
|
+
* measurement is not of the product.
|
|
57
|
+
*
|
|
58
|
+
* Unity below 1.0 V rather than a gain: this normalises a rail down, it never boosts a quiet
|
|
59
|
+
* program up to meet the ceiling.
|
|
60
|
+
*/
|
|
61
|
+
export function dacScaleFactor(fullScaleOutVolts) {
|
|
62
|
+
return fullScaleOutVolts !== null && fullScaleOutVolts > 1.0 ? 1.0 / fullScaleOutVolts : 1.0;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A program's output level in the domain a listener is in: dBFS at the DAC, after
|
|
66
|
+
* `dacScaleFactor`.
|
|
67
|
+
*
|
|
68
|
+
* **Volts and dBFS are not interchangeable and the corpus has been treating them as if they
|
|
69
|
+
* were.** `report-input-dependence.ts`'s `AUDIBLE_FLOOR` comment reconciles its absolute-volts bar
|
|
70
|
+
* against a tier's `-50 dBFS` one by taking "full scale 1.0" -- true of no packet in the corpus,
|
|
71
|
+
* where the declared full scale runs 4.5 V to 558.61 V. Converted through the rule the app
|
|
72
|
+
* actually applies, a single volts threshold is a different dBFS bar for every packet: 1e-3 V is
|
|
73
|
+
* -60 dBFS at full scale 1.0, -79 dBFS on a 9 V pedal and -115 dBFS on the plexi.
|
|
74
|
+
*/
|
|
75
|
+
export function outputDbfs(rmsVolts, fullScaleOutVolts) {
|
|
76
|
+
return 20 * Math.log10(Math.max(rmsVolts * dacScaleFactor(fullScaleOutVolts), 1e-12));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The number a level conversion divides an output by: the port's declared 0 dBFS
|
|
80
|
+
* reference where it states one, the derived ceiling for speaker-electrical outputs,
|
|
81
|
+
* or the instrument operating reference convention.
|
|
82
|
+
*
|
|
83
|
+
* These are different quantities -- `link.ts` says so and keeps them apart -- and a folding
|
|
84
|
+
* consumer has to pick the one its question needs. A conversion into a level (a +/-1 file,
|
|
85
|
+
* a DAC scale, a dBFS audibility judgement) wants the **reference** where a source states it:
|
|
86
|
+
* it is what the source says full scale *means* rather than what the node can reach.
|
|
87
|
+
*
|
|
88
|
+
* For `speaker-electrical` stage coverage (power amp secondary into speaker tap), the
|
|
89
|
+
* transformer-stepped ceiling (30 V to 500 V) is physically right to prevent DAC wrapping.
|
|
90
|
+
*
|
|
91
|
+
* For `instrument` stage coverage (stompboxes/pedals), the DC supply rail (e.g. 9 V) is an
|
|
92
|
+
* electrical clipping ceiling, never an operating reference level. Falling back to the rail
|
|
93
|
+
* caused pedals without an explicit output reference to play 19 dB quieter than input
|
|
94
|
+
* (0.3 V / 9 V = -29.5 dBFS). When no output reference is declared:
|
|
95
|
+
* 1. Read `portReferenceVolts.input` if declared (preserving the pedal's declared operating scale).
|
|
96
|
+
* 2. Fall back to 1.0 V, the convention matching digital full scale (1.0 digital = 1.0 V),
|
|
97
|
+
* which is the median and mode of the declared corpus population (59 of 65 declared inputs).
|
|
98
|
+
*/
|
|
99
|
+
export function outputConversionFullScale(contract) {
|
|
100
|
+
const declared = contract.portReferenceVolts?.output ?? null;
|
|
101
|
+
if (declared !== null && declared > 0) {
|
|
102
|
+
return declared;
|
|
103
|
+
}
|
|
104
|
+
const coverage = contract.stageCoverage ?? contract.produces;
|
|
105
|
+
if (coverage === "speaker-electrical") {
|
|
106
|
+
return contract.portFullScaleVolts.output;
|
|
107
|
+
}
|
|
108
|
+
if (coverage === "instrument") {
|
|
109
|
+
const inputRef = contract.portReferenceVolts?.input ?? null;
|
|
110
|
+
if (inputRef !== null && inputRef > 0) {
|
|
111
|
+
return inputRef;
|
|
112
|
+
}
|
|
113
|
+
return 1.0;
|
|
114
|
+
}
|
|
115
|
+
const inputRef = contract.portReferenceVolts?.input ?? null;
|
|
116
|
+
if (inputRef !== null && inputRef > 0) {
|
|
117
|
+
return inputRef;
|
|
118
|
+
}
|
|
119
|
+
return contract.portFullScaleVolts.output;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=chain-scale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain-scale.js","sourceRoot":"","sources":["../src/chain-scale.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Program } from "@vessel-dsp/compiler";
|
|
2
|
+
/** How far down the amplification chain a signal already is. The compiler's own vocabulary. */
|
|
3
|
+
export type StageCoverage = Program["stageCoverage"];
|
|
4
|
+
/**
|
|
5
|
+
* What the chain needs to know about a slot, whatever runs inside it.
|
|
6
|
+
*
|
|
7
|
+
* `Program` satisfies the produced/full-scale half structurally, which is deliberate: the seam
|
|
8
|
+
* arithmetic is identical for a compiled circuit and an injected processor, and writing it twice
|
|
9
|
+
* would be how the two drift apart.
|
|
10
|
+
*/
|
|
11
|
+
export type SlotContract = {
|
|
12
|
+
/** The coverage of this slot's **output**. */
|
|
13
|
+
readonly produces: StageCoverage;
|
|
14
|
+
/** The coverage this slot's **input** is meant to receive. */
|
|
15
|
+
readonly expects: StageCoverage;
|
|
16
|
+
readonly portFullScaleVolts: {
|
|
17
|
+
readonly input: number | null;
|
|
18
|
+
readonly output: number | null;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The port's declared 0 dBFS reference where the source states one, separate from the derived ceiling.
|
|
22
|
+
*/
|
|
23
|
+
readonly portReferenceVolts?: {
|
|
24
|
+
readonly input: number | null;
|
|
25
|
+
readonly output: number | null;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Port impedances for the seam divider, or **`null` when this slot's ports are not electrical**.
|
|
29
|
+
*
|
|
30
|
+
* The two cases are different and must not collapse into one. A *circuit* seam with no declared
|
|
31
|
+
* impedance is **unknown** -- undeclared data, worth reporting once sources carry it. A
|
|
32
|
+
* *processor* seam has **nothing to declare**: a NAM capture already contains whatever loading
|
|
33
|
+
* existed when it was taken, and an impulse response or cab simulation is a filter rather than a
|
|
34
|
+
* source or a load. §3.2's own table says as much -- "IR / cabMic: unity, a filter, not a source".
|
|
35
|
+
*
|
|
36
|
+
* So a whole-object `null` means *not applicable*, and an inner `null` means *not stated*. Without
|
|
37
|
+
* that distinction the divider's advisory would fire on every NAM + cabinet chain, complaining
|
|
38
|
+
* about data that cannot exist.
|
|
39
|
+
*/
|
|
40
|
+
readonly portImpedanceOhms: {
|
|
41
|
+
readonly input: number | null;
|
|
42
|
+
readonly output: number | null;
|
|
43
|
+
} | null;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* A processor the host supplies: a NAM profile, an impulse response, the cab simulation.
|
|
47
|
+
*
|
|
48
|
+
* `process` takes one buffer and returns one buffer of the same length, sample-for-sample causal in
|
|
49
|
+
* the same sense `ReferenceRuntime.process` is -- the chain hands whole buffers between slots and
|
|
50
|
+
* relies on that.
|
|
51
|
+
*
|
|
52
|
+
* **A NAM amp capture declares `produces: "speaker-electrical"`, not `"miked"`.** A load-box
|
|
53
|
+
* capture is taken at the speaker terminal, which is what that value means -- the same coverage a
|
|
54
|
+
* whitebox amp's own output carries, so NAM + IR and amp + IR are one case rather than two. The great
|
|
55
|
+
* majority of captures on TONE3000 are taken through a reactive load box with no speaker and no
|
|
56
|
+
* microphone, precisely so the player adds their own IR, and NAM + IR is the ordinary chain. Only a
|
|
57
|
+
* profile that states it is a full-rig capture declares `miked`, and nothing here may infer that from
|
|
58
|
+
* a file name or a title.
|
|
59
|
+
*/
|
|
60
|
+
export type ExternalProcessor = SlotContract & {
|
|
61
|
+
/** Named in advisories and telemetry, since a processor has no program to point at. */
|
|
62
|
+
readonly id: string;
|
|
63
|
+
prepare(sampleRate: number): void;
|
|
64
|
+
process(buffer: Float64Array): Float64Array;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* **Nothing here distinguishes a NAM from an IR, on purpose.** Both are buffer-in, buffer-out black
|
|
68
|
+
* boxes, so the seam arithmetic, the ordering and the advisories are identical; `id` is a label for
|
|
69
|
+
* messages, never a discriminator. What the chain asks is not *what kind of processor is this* but
|
|
70
|
+
* *what coverage does it produce* -- which is why a NAM amp capture and a whitebox amp's own output
|
|
71
|
+
* collapse to one case, and why a cab simulator and an impulse response do too.
|
|
72
|
+
*
|
|
73
|
+
* They are not, however, the same *kind* of process, and one consequence survives the abstraction.
|
|
74
|
+
* An impulse response is linear and time-invariant: an unscaled seam into it is purely a level error,
|
|
75
|
+
* and a gain after it undoes the damage exactly. A NAM is a neural capture of a **nonlinear** amp, so
|
|
76
|
+
* the same signal at half the level does not merely arrive quieter -- it arrives with different
|
|
77
|
+
* harmonic content, and no later gain recovers it. So an `unscalable-seam` reported into a nonlinear
|
|
78
|
+
* slot is a **tone** error, where into a linear one it is only a level error.
|
|
79
|
+
*
|
|
80
|
+
* That distinction is recorded rather than encoded. Adding a `linear` flag to grade the advisory's
|
|
81
|
+
* severity would be a field with one speculative consumer, and the advisory already names the seam;
|
|
82
|
+
* a host that knows its processor is nonlinear knows to take that seam more seriously.
|
|
83
|
+
*/
|
|
84
|
+
export type BypassMode = "wire" | "buffer" | "effect";
|
|
85
|
+
/**
|
|
86
|
+
* Row 8 tail step 1 — host mapping: bypass state → chain mode, per `program.bypass.kind`.
|
|
87
|
+
*
|
|
88
|
+
* The Studio's slot toggle already reads ENGAGED/BYPASSED, but the worklet chain omitted
|
|
89
|
+
* bypassed slots, so a bypassed buffered pedal ran no buffer and the mode plumbing
|
|
90
|
+
* (`setBypassMode`, `effectiveBypassMode`) never engaged in product. The host now keeps
|
|
91
|
+
* the slot and states the mode instead of dropping it.
|
|
92
|
+
*
|
|
93
|
+
* Returns `null` when no honest mode exists yet: `buffered` bypass needs the compiler's
|
|
94
|
+
* `bufferProgram` derivation (row 8 step 2), and requesting `buffer` without one falls back
|
|
95
|
+
* to the full program — which would play the effect under bypass. The caller omits those
|
|
96
|
+
* slots exactly as before, until step 2 lands. Everything else maps:
|
|
97
|
+
* engaged → `effect`; bypassed `true-bypass` → `wire`; `hardwire` → `wire`, with the input
|
|
98
|
+
* loading a hardwired output switch leaves behind still unmodelled (23 pedals by census —
|
|
99
|
+
* named here, not silent); `none` / `not-in-audio-path` → `wire`, agreeing with the
|
|
100
|
+
* runtime's own demotion guard.
|
|
101
|
+
*/
|
|
102
|
+
export declare function resolveBypassMode(bypass: Program["bypass"], engaged: boolean): BypassMode | null;
|
|
103
|
+
export type ChainSlot = {
|
|
104
|
+
readonly kind: "program";
|
|
105
|
+
readonly program: Program;
|
|
106
|
+
readonly bufferProgram?: Program;
|
|
107
|
+
readonly bufferProcessor?: ExternalProcessor;
|
|
108
|
+
readonly bypassMode?: BypassMode;
|
|
109
|
+
} | {
|
|
110
|
+
readonly kind: "processor";
|
|
111
|
+
readonly processor: ExternalProcessor;
|
|
112
|
+
readonly bufferProcessor?: ExternalProcessor;
|
|
113
|
+
readonly bypassMode?: BypassMode;
|
|
114
|
+
};
|
|
115
|
+
/** A compiled circuit as a slot. `bufferProgram` is the buffer-only stages for buffered bypass. */
|
|
116
|
+
export declare function programSlot(program: Program, bufferProgram?: Program): ChainSlot;
|
|
117
|
+
/** A compiled circuit as a slot with explicit buffer processor (WASM). */
|
|
118
|
+
export declare function programSlotWithBufferProcessor(program: Program, bufferProcessor: ExternalProcessor, bufferProgram?: Program): ChainSlot;
|
|
119
|
+
/** An injected processor as a slot. */
|
|
120
|
+
export declare function processorSlot(processor: ExternalProcessor): ChainSlot;
|
|
121
|
+
/**
|
|
122
|
+
* The contract a slot presents to the seam arithmetic.
|
|
123
|
+
*
|
|
124
|
+
* A program's `expects` is `instrument` by construction -- its input is a jack. Nothing infers it,
|
|
125
|
+
* and no compiler field is needed for it.
|
|
126
|
+
*/
|
|
127
|
+
export declare function slotContract(slot: ChainSlot): SlotContract;
|
|
128
|
+
/** How a slot names itself in a message. */
|
|
129
|
+
export declare function slotLabel(slot: ChainSlot, index: number): string;
|
|
130
|
+
//# sourceMappingURL=chain-slot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain-slot.d.ts","sourceRoot":"","sources":["../src/chain-slot.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,+FAA+F;AAC/F,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,kBAAkB,EAAE;QAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,CAAC;IACF;;OAEG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE;QAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,CAAC;IACF;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE;QAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,GAAG,IAAI,CAAC;CACT,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAC9C,uFAAuF;IACvF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAAC;CAC5C,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEtD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,EACzB,OAAO,EAAE,OAAO,GACd,UAAU,GAAG,IAAI,CAsBnB;AAED,MAAM,MAAM,SAAS,GAClB;IACA,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,eAAe,CAAC,EAAE,iBAAiB,CAAC;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;CAChC,GACD;IACA,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,eAAe,CAAC,EAAE,iBAAiB,CAAC;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;CAChC,CAAC;AAEL,mGAAmG;AACnG,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAEhF;AAED,0EAA0E;AAC1E,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,iBAAiB,EAClC,aAAa,CAAC,EAAE,OAAO,GACrB,SAAS,CAEX;AAED,uCAAuC;AACvC,wBAAgB,aAAa,CAAC,SAAS,EAAE,iBAAiB,GAAG,SAAS,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,YAAY,CAa1D;AAED,4CAA4C;AAC5C,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhE"}
|