@vessel-dsp/chain 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +230 -0
- package/dist/chain.d.ts +31 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +208 -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 +13 -0
- package/dist/index.js.map +1 -0
- package/dist/ir-resample.d.ts +2 -0
- package/dist/ir-resample.d.ts.map +1 -0
- package/dist/ir-resample.js +41 -0
- package/dist/ir-resample.js.map +1 -0
- package/dist/nam/engine.d.ts +73 -0
- package/dist/nam/engine.d.ts.map +1 -0
- package/dist/nam/engine.js +89 -0
- package/dist/nam/engine.js.map +1 -0
- package/dist/nodes/amp-shaper-node.d.ts +37 -0
- package/dist/nodes/amp-shaper-node.d.ts.map +1 -0
- package/dist/nodes/amp-shaper-node.js +134 -0
- package/dist/nodes/amp-shaper-node.js.map +1 -0
- package/dist/nodes/gain-node.d.ts +19 -0
- package/dist/nodes/gain-node.d.ts.map +1 -0
- package/dist/nodes/gain-node.js +49 -0
- package/dist/nodes/gain-node.js.map +1 -0
- package/dist/nodes/input-profile-node.d.ts +38 -0
- package/dist/nodes/input-profile-node.d.ts.map +1 -0
- package/dist/nodes/input-profile-node.js +245 -0
- package/dist/nodes/input-profile-node.js.map +1 -0
- package/dist/nodes/ir-node.d.ts +56 -0
- package/dist/nodes/ir-node.d.ts.map +1 -0
- package/dist/nodes/ir-node.js +438 -0
- package/dist/nodes/ir-node.js.map +1 -0
- package/dist/nodes/master-node.d.ts +23 -0
- package/dist/nodes/master-node.d.ts.map +1 -0
- package/dist/nodes/master-node.js +93 -0
- package/dist/nodes/master-node.js.map +1 -0
- package/dist/nodes/nam-node.d.ts +81 -0
- package/dist/nodes/nam-node.d.ts.map +1 -0
- package/dist/nodes/nam-node.js +234 -0
- package/dist/nodes/nam-node.js.map +1 -0
- package/dist/nodes/runtime-node.d.ts +66 -0
- package/dist/nodes/runtime-node.d.ts.map +1 -0
- package/dist/nodes/runtime-node.js +247 -0
- package/dist/nodes/runtime-node.js.map +1 -0
- package/dist/supply-profile.d.ts +56 -0
- package/dist/supply-profile.d.ts.map +1 -0
- package/dist/supply-profile.js +139 -0
- package/dist/supply-profile.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/nam-engine/NOTICE.md +32 -0
- package/nam-engine/nam-engine.js +2 -0
- package/nam-engine/nam-engine.wasm +0 -0
- package/package.json +67 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joseph Cheng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# @vessel-dsp/chain
|
|
2
|
+
|
|
3
|
+
Headless audio signal chain graph engine for VesselDSP circuits: guitar input
|
|
4
|
+
profiling, compiled circuit runtimes, amp shaping, NAM (`.nam`) playback,
|
|
5
|
+
cabinet IR convolution, and master output. No UI, no audio runtime, no file
|
|
6
|
+
access.
|
|
7
|
+
|
|
8
|
+
## Nodes
|
|
9
|
+
|
|
10
|
+
- `InputProfileNode` (`input-profile`): pickup type, impedance loading,
|
|
11
|
+
input trim gain, and a resonant low-pass from pickup inductance plus cable
|
|
12
|
+
capacitance. It models the guitar, cable, and input load only. It is not a
|
|
13
|
+
pickup capture and it does not model pedals or amps.
|
|
14
|
+
- `RuntimeNode` (`circuit-runtime`): wraps a `@vessel-dsp/compiler` Program
|
|
15
|
+
running on `@vessel-dsp/runtime`. It is the only node that solves a circuit.
|
|
16
|
+
It needs a compiled program; it cannot compile `.vdsp` by itself.
|
|
17
|
+
- `AmpShaperNode` (`amp-shaper`): a tanh waveshaper plus a tone stack with
|
|
18
|
+
dry/wet `mix`. It is not a model loader and must not be called NAM.
|
|
19
|
+
- `NamNode` (`nam`): plays Neural Amp Modeler `.nam` captures through the
|
|
20
|
+
vendored WASM engine. See "NAM playback" below.
|
|
21
|
+
- `CabinetIrNode` (`cabinet-ir`): zero-latency uniform-partitioned FFT
|
|
22
|
+
convolution. Direct time-domain convolution covers the first 128 taps and
|
|
23
|
+
FFT partitions (256-point frame, in-house radix-2 FFT, no extra dependency)
|
|
24
|
+
cover the tail through a frequency-domain delay line. `process()` accepts
|
|
25
|
+
any block length and output is sample-aligned with the input (latency 0).
|
|
26
|
+
`lowCutHz`/`highCutHz` are 2nd-order Butterworth biquads on the wet signal
|
|
27
|
+
before the mix (defaults 20 Hz / 20000 Hz, skipped at the extremes so a
|
|
28
|
+
default node is transparent; clamps 20..500 and 1000..20000). Optional
|
|
29
|
+
`irSampleRate` resamples the IR at `prepare()` with a windowed sinc (Hann,
|
|
30
|
+
16 zero crossings) when it differs from the prepared rate. The bundled
|
|
31
|
+
128-tap synthetic IR is a placeholder, not a 4x12 capture.
|
|
32
|
+
- `GainNode` (`gain`): flat trim gain.
|
|
33
|
+
- `MasterNode` (`master`): volume, mute, and a soft-knee safety limiter. It
|
|
34
|
+
is not a loudness maximizer.
|
|
35
|
+
- `SignalChain`: ordered graph (`input-profile -> effects -> master`) with
|
|
36
|
+
`addNode`, `insertNode`, `moveNode`, `reorderNodes`, `clearNodes`,
|
|
37
|
+
`toJson`/`fromJson`, and preset `getPreset`/`loadPreset`.
|
|
38
|
+
|
|
39
|
+
## NAM playback
|
|
40
|
+
|
|
41
|
+
`NamNode` runs `.nam` models on the NeuralAmpModelerCore engine compiled to
|
|
42
|
+
plain WebAssembly, which ships in this package as `nam-engine/nam-engine.wasm`
|
|
43
|
+
with its glue `nam-engine/nam-engine.js` (MIT; see `nam-engine/NOTICE.md`).
|
|
44
|
+
|
|
45
|
+
- **Architectures:** whatever the pinned engine supports -- `Linear`,
|
|
46
|
+
`ConvNet`, `WaveNet`, `LSTM`, and the A2/slimmable variants. The engine parses the model; the node
|
|
47
|
+
does not inspect it beyond the sample rate and loudness the engine reports.
|
|
48
|
+
- **The host passes the wasm bytes.** Instantiate the glue with
|
|
49
|
+
`instantiateNamEngine(wasmBytes, factory)` and hand the resulting module to
|
|
50
|
+
the node. An `AudioWorkletGlobalScope` has no `fetch`, so the node never
|
|
51
|
+
fetches the wasm itself.
|
|
52
|
+
- **Sample-rate mismatch refuses.** If the model states a sample rate and it
|
|
53
|
+
differs from the chain rate by more than 0.5 Hz, `prepare()` throws naming
|
|
54
|
+
both rates and the model. A model that states no rate is accepted. Nothing
|
|
55
|
+
resamples a model to fit the chain.
|
|
56
|
+
- **Loudness is normalised by the engine.** `nam_process` already applies NAM's
|
|
57
|
+
`Normalized` output mode (target -18 dB) with smoothing against clicks; the
|
|
58
|
+
node applies no loudness gain of its own. `getInfo().loudness` reports what
|
|
59
|
+
the engine did.
|
|
60
|
+
- **No A/B calibration mode.** NAM's `Calibrated` mode needs an input
|
|
61
|
+
calibration level that models do not carry; it is not implemented.
|
|
62
|
+
- **Model files are not bundled.** `.nam` captures are third-party artefacts
|
|
63
|
+
whose licences are their authors'. The package ships the engine only; the
|
|
64
|
+
caller supplies the model's JSON text.
|
|
65
|
+
|
|
66
|
+
Usage (Bun/Node):
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { readFileSync } from "node:fs";
|
|
70
|
+
import { instantiateNamEngine, NamNode, SignalChain } from "@vessel-dsp/chain";
|
|
71
|
+
|
|
72
|
+
const factory = (await import("@vessel-dsp/chain/nam-engine.js")).default;
|
|
73
|
+
const engine = await instantiateNamEngine(
|
|
74
|
+
readFileSync(new URL("./nam-engine/nam-engine.wasm", import.meta.url)),
|
|
75
|
+
factory,
|
|
76
|
+
);
|
|
77
|
+
const chain = new SignalChain({ sampleRate: 48000 });
|
|
78
|
+
chain.addNode(new NamNode("nam-amp", "NAM Amp", {
|
|
79
|
+
engine,
|
|
80
|
+
model: readFileSync("my-amp.nam", "utf8"),
|
|
81
|
+
}));
|
|
82
|
+
const out = chain.process(new Float64Array(128));
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Latency and limits
|
|
86
|
+
|
|
87
|
+
- Cabinet IR and NAM both have 0 samples of latency by construction.
|
|
88
|
+
- Cost scales with IR length: a 48000-tap IR in 128-sample blocks runs at
|
|
89
|
+
about 5-10x realtime on a desktop CPU in Bun (measured during tests, see
|
|
90
|
+
below); per-sample direct cost is always 128 taps.
|
|
91
|
+
- IRs are mono `Float64Array`. Very long IRs use more memory for partitions
|
|
92
|
+
(about 4 KB per 128-tap partition plus the delay line).
|
|
93
|
+
- Filters are fixed 2nd-order Butterworth; there is no multi-band EQ.
|
|
94
|
+
- `AmpShaperNode` is a single static waveshaper; there is no oversampling.
|
|
95
|
+
- NAM cost is the model's own: a WaveNet capture in 128-sample blocks runs at
|
|
96
|
+
roughly 20-40x realtime under Bun on a desktop CPU (wasm, not browser).
|
|
97
|
+
|
|
98
|
+
## Cable model
|
|
99
|
+
|
|
100
|
+
`guitarCableLengthMeters` (default 3 m) with `cableCapacitancePfPerM`
|
|
101
|
+
(default 100 pF/m) sets the pickup resonance
|
|
102
|
+
`f0 = 1 / (2 * pi * sqrt(L * C))`, where `C` is the pickup internal
|
|
103
|
+
capacitance plus cable capacitance. Single-coil at 3 m lands around 5.0 kHz;
|
|
104
|
+
longer cables lower the resonance. Active pickups bypass the resonant filter
|
|
105
|
+
and piezos use a fixed resonance.
|
|
106
|
+
|
|
107
|
+
## Power supply
|
|
108
|
+
|
|
109
|
+
`RuntimeNode` can apply a `SupplyProfile` to the circuit external supply.
|
|
110
|
+
Pass the `.vdsp` text as the fourth constructor argument, then call
|
|
111
|
+
`setSupplyProfile`. The node resolves once with `resolveSupplyStamps` from
|
|
112
|
+
`@vessel-dsp/compiler` and retargets each resolved `dc-source` stamp through
|
|
113
|
+
`ReferenceRuntime.setSupply`. There is no audio-domain supply processing: the
|
|
114
|
+
old audio-domain waveshaper node was rejected and is not present. Sag is the
|
|
115
|
+
circuit own current draw through the profile series resistance.
|
|
116
|
+
|
|
117
|
+
A profile is an open-circuit EMF magnitude plus a series resistance. A null
|
|
118
|
+
`openCircuitVolts` keeps the pedal own declared supply voltage. The sign
|
|
119
|
+
always follows the stamp own compiled volts sign read from `getSupplies`,
|
|
120
|
+
so a positive-ground germanium rail stays negative.
|
|
121
|
+
|
|
122
|
+
Built-in profiles in `SUPPLY_PROFILES` (values from Jack Orman, 9v Battery
|
|
123
|
+
Impedance, AMZ-FX Lab Notebook, 2015, http://www.muzique.com/lab/batteryz.htm,
|
|
124
|
+
method: unloaded reading, then loaded with a 560 ohm resistor, Rint by Ohm
|
|
125
|
+
law):
|
|
126
|
+
|
|
127
|
+
- `ideal`: null volts, 0 ohm, source `definition`.
|
|
128
|
+
- `alkaline-fresh`: null volts, 5.405 ohm. Source: Orman 2015 fresh table,
|
|
129
|
+
AC-Delco alkaline 5.99 and 4.82 ohm; the value is their mean.
|
|
130
|
+
- `zinc-carbon-fresh`: null volts, 25.925 ohm. Source: Orman 2015 fresh
|
|
131
|
+
table, Sunbeam Heavy Duty 25.28 and 26.57 ohm; the value is their mean.
|
|
132
|
+
- `alkaline-depleted-specimen`: 7.73 V, 195.00 ohm. Source: Orman 2015 used
|
|
133
|
+
table, Duracell alkaline 6, unloaded 7.73 V, loaded 6.43 V, 195.00 ohm.
|
|
134
|
+
A single specimen, not a type rating.
|
|
135
|
+
- `zinc-carbon-used-specimen`: 9.02 V, 78.47 ohm. Source: Orman 2015 used
|
|
136
|
+
table, Golden Power Heavy Duty 7, unloaded 9.02 V, loaded 7.91 V,
|
|
137
|
+
78.47 ohm. A single specimen.
|
|
138
|
+
|
|
139
|
+
Adapter profiles are deliberately absent. No source was found for regulated
|
|
140
|
+
9 V, regulated 18 V, or unregulated AC/DC resistance or voltage.
|
|
141
|
+
|
|
142
|
+
A measured profile follows the same method:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { profileFromMeasurement } from "@vessel-dsp/chain";
|
|
146
|
+
|
|
147
|
+
const cell = profileFromMeasurement({
|
|
148
|
+
openCircuitVolts: 9.0,
|
|
149
|
+
loadedVolts: 8.0,
|
|
150
|
+
loadOhms: 560,
|
|
151
|
+
});
|
|
152
|
+
console.log(cell.internalResistanceOhms);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
70
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`Rint = (Vopen - Vloaded) / (Vloaded / Rload)`. Here `(9 - 8) / (8 / 560)`
|
|
160
|
+
is `560 / 8`, which is 70 ohm.
|
|
161
|
+
|
|
162
|
+
`customSupplyProfile` builds a profile from caller-supplied numbers with
|
|
163
|
+
source `caller-supplied`. `getParams` adds `supplyOpenCircuitVolts` and
|
|
164
|
+
`supplyInternalResistanceOhms` once a profile has been applied, and
|
|
165
|
+
`setParam` on either re-applies the pair as a custom profile, so a chain
|
|
166
|
+
preset round-trips the supply setting. `reset` and `prepare` keep the
|
|
167
|
+
applied profile in force. `getSupplyResolution` returns the cached
|
|
168
|
+
resolution or null until resolved. A program with refused derived rails but
|
|
169
|
+
one resolved external rail applies to the resolved one and keeps the
|
|
170
|
+
refusals visible. Missing source text or zero resolved supplies throws with
|
|
171
|
+
the refusal reason codes and rail ids.
|
|
172
|
+
|
|
173
|
+
Example with `big-muff-pi.vdsp` from the corpus, compiled with
|
|
174
|
+
`pedalPartCatalog` (read-only, not a committed test):
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { readFileSync } from "node:fs";
|
|
178
|
+
import { compile, pedalPartCatalog } from "@vessel-dsp/compiler";
|
|
179
|
+
import { RuntimeNode, SUPPLY_PROFILES } from "@vessel-dsp/chain";
|
|
180
|
+
|
|
181
|
+
const source = readFileSync("big-muff-pi.vdsp", "utf8");
|
|
182
|
+
const program = (() => {
|
|
183
|
+
const result = compile(source, { registry: pedalPartCatalog });
|
|
184
|
+
if (result.status !== "ok") throw new Error("compile failed");
|
|
185
|
+
return result.program;
|
|
186
|
+
})();
|
|
187
|
+
|
|
188
|
+
function rms(values: Float64Array): number {
|
|
189
|
+
let sum = 0;
|
|
190
|
+
for (const v of values) sum += v * v;
|
|
191
|
+
return Math.sqrt(sum / values.length);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const input = new Float64Array(4800);
|
|
195
|
+
for (let i = 0; i < input.length; i++) {
|
|
196
|
+
input[i] = 0.3 * Math.sin((2 * Math.PI * 440 * i) / 48000);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const id of ["ideal", "alkaline-depleted-specimen"] as const) {
|
|
200
|
+
const profile = SUPPLY_PROFILES.find((p) => p.id === id)!;
|
|
201
|
+
const node = new RuntimeNode("muff", "Big Muff", program, { source });
|
|
202
|
+
node.prepare(48000);
|
|
203
|
+
node.setSupplyProfile(profile);
|
|
204
|
+
node.process(input);
|
|
205
|
+
console.log(`${id} rms: ${rms(node.process(input)).toFixed(5)}`);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
ideal rms: 0.20087
|
|
211
|
+
alkaline-depleted-specimen rms: 0.19471
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Limits, stated plainly:
|
|
215
|
+
|
|
216
|
+
- It uses the TypeScript reference runtime. The WebAssembly console the
|
|
217
|
+
workbench product runs needs the same setter in the workbench repo and is
|
|
218
|
+
not done.
|
|
219
|
+
- Op-amp stages draw almost no supply current in the model, so sag on
|
|
220
|
+
op-amp-heavy pedals is under-reported.
|
|
221
|
+
- The model source can absorb current a real cell cannot, so a hard clipper
|
|
222
|
+
can push the rail up.
|
|
223
|
+
- A single series resistance is a DC approximation (Orman's own AC table shows
|
|
224
|
+
impedance falls with frequency).
|
|
225
|
+
- Mains-fed amps are refused by design.
|
|
226
|
+
|
|
227
|
+
## Out of scope for 0.1.0
|
|
228
|
+
|
|
229
|
+
- The old audio-domain waveshaper supply node was rejected and is not
|
|
230
|
+
present. There is no audio-domain supply processing.
|
package/dist/chain.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { InputProfileNode } from "./nodes/input-profile-node.js";
|
|
2
|
+
import { MasterNode } from "./nodes/master-node.js";
|
|
3
|
+
import type { ChainNode, ChainPreset, NodeSnapshot, SignalChainOptions } from "./types.js";
|
|
4
|
+
export declare class SignalChain {
|
|
5
|
+
readonly inputProfile: InputProfileNode;
|
|
6
|
+
readonly master: MasterNode;
|
|
7
|
+
private nodes;
|
|
8
|
+
private sampleRate;
|
|
9
|
+
constructor(options?: SignalChainOptions);
|
|
10
|
+
addNode(node: ChainNode): this;
|
|
11
|
+
insertNode(node: ChainNode, index: number): this;
|
|
12
|
+
moveNode(id: string, targetIndex: number): boolean;
|
|
13
|
+
reorderNodes(orderedIds: readonly string[]): boolean;
|
|
14
|
+
removeNode(id: string): boolean;
|
|
15
|
+
clearNodes(): this;
|
|
16
|
+
getNode(id: string): ChainNode | undefined;
|
|
17
|
+
getNodes(): readonly ChainNode[];
|
|
18
|
+
getEffectNodes(): readonly ChainNode[];
|
|
19
|
+
get length(): number;
|
|
20
|
+
prepare(sampleRate: number): void;
|
|
21
|
+
reset(): void;
|
|
22
|
+
process(input: Float64Array | Float32Array): Float64Array;
|
|
23
|
+
getPreset(name?: string): ChainPreset;
|
|
24
|
+
loadPreset(preset: ChainPreset, nodeFactory?: (snap: NodeSnapshot) => ChainNode | undefined): void;
|
|
25
|
+
toJson(name?: string): string;
|
|
26
|
+
static fromJson(jsonStr: string, options?: {
|
|
27
|
+
sampleRate?: number;
|
|
28
|
+
nodeFactory?: (snap: NodeSnapshot) => ChainNode | undefined;
|
|
29
|
+
}): SignalChain;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=chain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,EACX,SAAS,EACT,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,MAAM,YAAY,CAAC;AAEpB,qBAAa,WAAW;IACvB,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAE5B,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,CAAC,EAAE,kBAAkB;IASxC,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAc9B,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAQhD,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO;IAYlD,YAAY,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO;IAqBpD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAU/B,UAAU,IAAI,IAAI;IAQlB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAM1C,QAAQ,IAAI,SAAS,SAAS,EAAE;IAIhC,cAAc,IAAI,SAAS,SAAS,EAAE;IAItC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IASjC,KAAK,IAAI,IAAI;IAQb,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY;IAUzD,SAAS,CAAC,IAAI,SAAmB,GAAG,WAAW;IAmB/C,UAAU,CACT,MAAM,EAAE,WAAW,EACnB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,SAAS,GAAG,SAAS,GACzD,IAAI;IAqDP,MAAM,CAAC,IAAI,SAAiB,GAAG,MAAM;IAIrC,MAAM,CAAC,QAAQ,CACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACT,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,SAAS,GAAG,SAAS,CAAC;KAC5D,GACC,WAAW;CAyBd"}
|
package/dist/chain.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { AmpShaperNode } from "./nodes/amp-shaper-node.js";
|
|
2
|
+
import { GainNode } from "./nodes/gain-node.js";
|
|
3
|
+
import { InputProfileNode } from "./nodes/input-profile-node.js";
|
|
4
|
+
import { CabinetIrNode } from "./nodes/ir-node.js";
|
|
5
|
+
import { MasterNode } from "./nodes/master-node.js";
|
|
6
|
+
export class SignalChain {
|
|
7
|
+
inputProfile;
|
|
8
|
+
master;
|
|
9
|
+
nodes = [];
|
|
10
|
+
sampleRate = 48000;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.inputProfile = new InputProfileNode(options?.inputProfile);
|
|
13
|
+
this.master = new MasterNode(options?.master);
|
|
14
|
+
if (options?.sampleRate) {
|
|
15
|
+
this.sampleRate = options.sampleRate;
|
|
16
|
+
this.prepare(options.sampleRate);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
addNode(node) {
|
|
20
|
+
const existingIndex = this.nodes.findIndex((n) => n.id === node.id);
|
|
21
|
+
if (existingIndex >= 0) {
|
|
22
|
+
// The node being replaced is dropped here, so it is disposed here: a NAM node holds
|
|
23
|
+
// a WASM instance, and leaving it alive would leak one per replace.
|
|
24
|
+
this.nodes[existingIndex]?.dispose?.();
|
|
25
|
+
this.nodes[existingIndex] = node;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.nodes.push(node);
|
|
29
|
+
}
|
|
30
|
+
node.prepare(this.sampleRate);
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
insertNode(node, index) {
|
|
34
|
+
this.removeNode(node.id);
|
|
35
|
+
const targetIndex = Math.max(0, Math.min(this.nodes.length, index));
|
|
36
|
+
this.nodes.splice(targetIndex, 0, node);
|
|
37
|
+
node.prepare(this.sampleRate);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
moveNode(id, targetIndex) {
|
|
41
|
+
const currentIndex = this.nodes.findIndex((n) => n.id === id);
|
|
42
|
+
if (currentIndex < 0)
|
|
43
|
+
return false;
|
|
44
|
+
const [node] = this.nodes.splice(currentIndex, 1);
|
|
45
|
+
if (!node)
|
|
46
|
+
return false;
|
|
47
|
+
const clampedIndex = Math.max(0, Math.min(this.nodes.length, targetIndex));
|
|
48
|
+
this.nodes.splice(clampedIndex, 0, node);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
reorderNodes(orderedIds) {
|
|
52
|
+
const newNodes = [];
|
|
53
|
+
const remaining = new Map(this.nodes.map((n) => [n.id, n]));
|
|
54
|
+
for (const id of orderedIds) {
|
|
55
|
+
const node = remaining.get(id);
|
|
56
|
+
if (node) {
|
|
57
|
+
newNodes.push(node);
|
|
58
|
+
remaining.delete(id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Append any nodes that were not explicitly listed
|
|
62
|
+
for (const node of remaining.values()) {
|
|
63
|
+
newNodes.push(node);
|
|
64
|
+
}
|
|
65
|
+
this.nodes = newNodes;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
removeNode(id) {
|
|
69
|
+
const index = this.nodes.findIndex((n) => n.id === id);
|
|
70
|
+
if (index >= 0) {
|
|
71
|
+
const [node] = this.nodes.splice(index, 1);
|
|
72
|
+
node?.dispose?.();
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
clearNodes() {
|
|
78
|
+
for (const node of this.nodes) {
|
|
79
|
+
node.dispose?.();
|
|
80
|
+
}
|
|
81
|
+
this.nodes = [];
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
getNode(id) {
|
|
85
|
+
if (id === this.inputProfile.id)
|
|
86
|
+
return this.inputProfile;
|
|
87
|
+
if (id === this.master.id)
|
|
88
|
+
return this.master;
|
|
89
|
+
return this.nodes.find((n) => n.id === id);
|
|
90
|
+
}
|
|
91
|
+
getNodes() {
|
|
92
|
+
return [this.inputProfile, ...this.nodes, this.master];
|
|
93
|
+
}
|
|
94
|
+
getEffectNodes() {
|
|
95
|
+
return [...this.nodes];
|
|
96
|
+
}
|
|
97
|
+
get length() {
|
|
98
|
+
return this.nodes.length;
|
|
99
|
+
}
|
|
100
|
+
prepare(sampleRate) {
|
|
101
|
+
this.sampleRate = sampleRate;
|
|
102
|
+
this.inputProfile.prepare(sampleRate);
|
|
103
|
+
for (const node of this.nodes) {
|
|
104
|
+
node.prepare(sampleRate);
|
|
105
|
+
}
|
|
106
|
+
this.master.prepare(sampleRate);
|
|
107
|
+
}
|
|
108
|
+
reset() {
|
|
109
|
+
this.inputProfile.reset();
|
|
110
|
+
for (const node of this.nodes) {
|
|
111
|
+
node.reset();
|
|
112
|
+
}
|
|
113
|
+
this.master.reset();
|
|
114
|
+
}
|
|
115
|
+
process(input) {
|
|
116
|
+
let current = this.inputProfile.process(input);
|
|
117
|
+
for (const node of this.nodes) {
|
|
118
|
+
current = node.process(current);
|
|
119
|
+
}
|
|
120
|
+
return this.master.process(current);
|
|
121
|
+
}
|
|
122
|
+
getPreset(name = "Default Preset") {
|
|
123
|
+
const snapshots = this.nodes.map((n) => ({
|
|
124
|
+
id: n.id,
|
|
125
|
+
name: n.name,
|
|
126
|
+
kind: n.kind,
|
|
127
|
+
bypassed: n.bypassed,
|
|
128
|
+
mix: n.mix,
|
|
129
|
+
params: n.getParams(),
|
|
130
|
+
}));
|
|
131
|
+
return {
|
|
132
|
+
name,
|
|
133
|
+
version: "1.0",
|
|
134
|
+
inputProfile: this.inputProfile.getConfig(),
|
|
135
|
+
nodes: snapshots,
|
|
136
|
+
master: this.master.getConfig(),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
loadPreset(preset, nodeFactory) {
|
|
140
|
+
if (preset.inputProfile) {
|
|
141
|
+
this.inputProfile.setPickupType(preset.inputProfile.pickupType);
|
|
142
|
+
this.inputProfile.setImpedance(preset.inputProfile.impedanceOhms);
|
|
143
|
+
this.inputProfile.setInputGainDb(preset.inputProfile.inputGainDb);
|
|
144
|
+
if (preset.inputProfile.guitarCableLengthMeters !== undefined) {
|
|
145
|
+
this.inputProfile.setGuitarCableLength(preset.inputProfile.guitarCableLengthMeters);
|
|
146
|
+
}
|
|
147
|
+
if (preset.inputProfile.cableCapacitancePfPerM !== undefined) {
|
|
148
|
+
this.inputProfile.setCableCapacitancePfPerM(preset.inputProfile.cableCapacitancePfPerM);
|
|
149
|
+
}
|
|
150
|
+
if (preset.inputProfile.resonantFreqHz !== undefined) {
|
|
151
|
+
this.inputProfile.setParam("resonantFreqHz", preset.inputProfile.resonantFreqHz);
|
|
152
|
+
}
|
|
153
|
+
if (preset.inputProfile.resonantQ !== undefined) {
|
|
154
|
+
this.inputProfile.setParam("resonantQ", preset.inputProfile.resonantQ);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (preset.master) {
|
|
158
|
+
this.master.setVolumeDb(preset.master.volumeDb);
|
|
159
|
+
this.master.setMuted(preset.master.muted);
|
|
160
|
+
this.master.setLimiter(preset.master.limiter);
|
|
161
|
+
}
|
|
162
|
+
if (preset.nodes) {
|
|
163
|
+
for (const snap of preset.nodes) {
|
|
164
|
+
let node = this.getNode(snap.id);
|
|
165
|
+
if (!node && nodeFactory) {
|
|
166
|
+
node = nodeFactory(snap);
|
|
167
|
+
if (node) {
|
|
168
|
+
this.addNode(node);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (node) {
|
|
172
|
+
node.bypassed = snap.bypassed;
|
|
173
|
+
node.mix = snap.mix;
|
|
174
|
+
for (const [key, val] of Object.entries(snap.params)) {
|
|
175
|
+
node.setParam(key, val);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
toJson(name = "Signal Chain") {
|
|
182
|
+
return JSON.stringify(this.getPreset(name), null, 2);
|
|
183
|
+
}
|
|
184
|
+
static fromJson(jsonStr, options) {
|
|
185
|
+
const preset = JSON.parse(jsonStr);
|
|
186
|
+
const chain = new SignalChain({ sampleRate: options?.sampleRate ?? 48000 });
|
|
187
|
+
const defaultFactory = (snap) => {
|
|
188
|
+
if (snap.kind === "amp-shaper") {
|
|
189
|
+
return new AmpShaperNode(snap.id, snap.name);
|
|
190
|
+
}
|
|
191
|
+
if (snap.kind === "cabinet-ir") {
|
|
192
|
+
return new CabinetIrNode(snap.id, snap.name);
|
|
193
|
+
}
|
|
194
|
+
if (snap.kind === "gain") {
|
|
195
|
+
return new GainNode(snap.id, snap.name);
|
|
196
|
+
}
|
|
197
|
+
// A `nam` snapshot is deliberately NOT recreated here. A NamNode needs an instantiated
|
|
198
|
+
// engine and the model's JSON text, and a preset carries neither -- the model file is
|
|
199
|
+
// the user's, not the preset's. A host that can supply both passes its own
|
|
200
|
+
// `nodeFactory`; the snapshot's params are still applied to any NamNode the host
|
|
201
|
+
// already has under that id.
|
|
202
|
+
return undefined;
|
|
203
|
+
};
|
|
204
|
+
chain.loadPreset(preset, options?.nodeFactory ?? defaultFactory);
|
|
205
|
+
return chain;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=chain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain.js","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./chain.js";
|
|
2
|
+
export { resampleImpulseResponse } from "./ir-resample.js";
|
|
3
|
+
export * from "./nam/engine.js";
|
|
4
|
+
export * from "./nodes/amp-shaper-node.js";
|
|
5
|
+
export * from "./nodes/gain-node.js";
|
|
6
|
+
export * from "./nodes/input-profile-node.js";
|
|
7
|
+
export * from "./nodes/ir-node.js";
|
|
8
|
+
export * from "./nodes/master-node.js";
|
|
9
|
+
export * from "./nodes/nam-node.js";
|
|
10
|
+
export * from "./nodes/runtime-node.js";
|
|
11
|
+
export type { SupplyProfile } from "./supply-profile.js";
|
|
12
|
+
export { customSupplyProfile, profileFromMeasurement, SUPPLY_PROFILES, } from "./supply-profile.js";
|
|
13
|
+
export * from "./types.js";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACN,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,GACf,MAAM,qBAAqB,CAAC;AAC7B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./chain.js";
|
|
2
|
+
export { resampleImpulseResponse } from "./ir-resample.js";
|
|
3
|
+
export * from "./nam/engine.js";
|
|
4
|
+
export * from "./nodes/amp-shaper-node.js";
|
|
5
|
+
export * from "./nodes/gain-node.js";
|
|
6
|
+
export * from "./nodes/input-profile-node.js";
|
|
7
|
+
export * from "./nodes/ir-node.js";
|
|
8
|
+
export * from "./nodes/master-node.js";
|
|
9
|
+
export * from "./nodes/nam-node.js";
|
|
10
|
+
export * from "./nodes/runtime-node.js";
|
|
11
|
+
export { customSupplyProfile, profileFromMeasurement, SUPPLY_PROFILES, } from "./supply-profile.js";
|
|
12
|
+
export * from "./types.js";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AAExC,OAAO,EACN,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,GACf,MAAM,qBAAqB,CAAC;AAC7B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ir-resample.d.ts","sourceRoot":"","sources":["../src/ir-resample.ts"],"names":[],"mappings":"AAOA,wBAAgB,uBAAuB,CACtC,EAAE,EAAE,YAAY,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACZ,YAAY,CAiCd"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Windowed-sinc IR resampler shared by chain and player.
|
|
2
|
+
//
|
|
3
|
+
// Moved here from packages/chain/src/nodes/ir-node.ts without changing the
|
|
4
|
+
// numerics. The player needs the same rate conversion without constructing a
|
|
5
|
+
// CabinetIrNode, so the function lives in this module and the node imports it.
|
|
6
|
+
// The loop below is intentionally unchanged from the original.
|
|
7
|
+
export function resampleImpulseResponse(ir, fromRate, toRate) {
|
|
8
|
+
if (!(fromRate > 0) || !(toRate > 0)) {
|
|
9
|
+
throw new RangeError(`resampleImpulseResponse requires positive rates, got fromRate=${String(fromRate)} toRate=${String(toRate)}`);
|
|
10
|
+
}
|
|
11
|
+
if (ir.length === 0) {
|
|
12
|
+
throw new RangeError("resampleImpulseResponse requires a non-empty IR");
|
|
13
|
+
}
|
|
14
|
+
if (fromRate === toRate) {
|
|
15
|
+
return new Float64Array(ir);
|
|
16
|
+
}
|
|
17
|
+
const ratio = toRate / fromRate;
|
|
18
|
+
const outLen = Math.max(1, Math.round(ir.length * ratio));
|
|
19
|
+
const cutoff = Math.min(1, ratio);
|
|
20
|
+
const radius = Math.max(1, Math.ceil(16 / cutoff));
|
|
21
|
+
const output = new Float64Array(outLen);
|
|
22
|
+
for (let n = 0; n < outLen; n++) {
|
|
23
|
+
const srcPos = n / ratio;
|
|
24
|
+
const iCenter = Math.floor(srcPos);
|
|
25
|
+
let sum = 0;
|
|
26
|
+
for (let i = iCenter - radius; i <= iCenter + radius; i++) {
|
|
27
|
+
if (i < 0 || i >= ir.length)
|
|
28
|
+
continue;
|
|
29
|
+
const d = srcPos - i;
|
|
30
|
+
if (Math.abs(d) > radius)
|
|
31
|
+
continue;
|
|
32
|
+
const xd = d * cutoff;
|
|
33
|
+
const sinc = xd === 0 ? 1 : Math.sin(Math.PI * xd) / (Math.PI * xd);
|
|
34
|
+
const hann = 0.5 * (1 + Math.cos((Math.PI * d) / radius));
|
|
35
|
+
sum += (ir[i] ?? 0) * cutoff * sinc * hann;
|
|
36
|
+
}
|
|
37
|
+
output[n] = sum;
|
|
38
|
+
}
|
|
39
|
+
return output;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=ir-resample.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ir-resample.js","sourceRoot":"","sources":["../src/ir-resample.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,2EAA2E;AAC3E,6EAA6E;AAC7E,+EAA+E;AAC/E,+DAA+D;AAE/D,MAAM,UAAU,uBAAuB,CACtC,EAAgB,EAChB,QAAgB,EAChB,MAAc;IAEd,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,UAAU,CACnB,iEAAiE,MAAM,CAAC,QAAQ,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,EAAE,CAC5G,CAAC;IACH,CAAC;IACD,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,UAAU,CAAC,iDAAiD,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO,IAAI,YAAY,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,IAAI,OAAO,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3D,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM;gBAAE,SAAS;YACtC,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;YACrB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM;gBAAE,SAAS;YACnC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC;YACtB,MAAM,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YACpE,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;YAC1D,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;QAC5C,CAAC;QACD,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;IACjB,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC"}
|