@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.
Files changed (58) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +230 -0
  3. package/dist/chain.d.ts +31 -0
  4. package/dist/chain.d.ts.map +1 -0
  5. package/dist/chain.js +208 -0
  6. package/dist/chain.js.map +1 -0
  7. package/dist/index.d.ts +14 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +13 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/ir-resample.d.ts +2 -0
  12. package/dist/ir-resample.d.ts.map +1 -0
  13. package/dist/ir-resample.js +41 -0
  14. package/dist/ir-resample.js.map +1 -0
  15. package/dist/nam/engine.d.ts +73 -0
  16. package/dist/nam/engine.d.ts.map +1 -0
  17. package/dist/nam/engine.js +89 -0
  18. package/dist/nam/engine.js.map +1 -0
  19. package/dist/nodes/amp-shaper-node.d.ts +37 -0
  20. package/dist/nodes/amp-shaper-node.d.ts.map +1 -0
  21. package/dist/nodes/amp-shaper-node.js +134 -0
  22. package/dist/nodes/amp-shaper-node.js.map +1 -0
  23. package/dist/nodes/gain-node.d.ts +19 -0
  24. package/dist/nodes/gain-node.d.ts.map +1 -0
  25. package/dist/nodes/gain-node.js +49 -0
  26. package/dist/nodes/gain-node.js.map +1 -0
  27. package/dist/nodes/input-profile-node.d.ts +38 -0
  28. package/dist/nodes/input-profile-node.d.ts.map +1 -0
  29. package/dist/nodes/input-profile-node.js +245 -0
  30. package/dist/nodes/input-profile-node.js.map +1 -0
  31. package/dist/nodes/ir-node.d.ts +56 -0
  32. package/dist/nodes/ir-node.d.ts.map +1 -0
  33. package/dist/nodes/ir-node.js +438 -0
  34. package/dist/nodes/ir-node.js.map +1 -0
  35. package/dist/nodes/master-node.d.ts +23 -0
  36. package/dist/nodes/master-node.d.ts.map +1 -0
  37. package/dist/nodes/master-node.js +93 -0
  38. package/dist/nodes/master-node.js.map +1 -0
  39. package/dist/nodes/nam-node.d.ts +81 -0
  40. package/dist/nodes/nam-node.d.ts.map +1 -0
  41. package/dist/nodes/nam-node.js +234 -0
  42. package/dist/nodes/nam-node.js.map +1 -0
  43. package/dist/nodes/runtime-node.d.ts +66 -0
  44. package/dist/nodes/runtime-node.d.ts.map +1 -0
  45. package/dist/nodes/runtime-node.js +247 -0
  46. package/dist/nodes/runtime-node.js.map +1 -0
  47. package/dist/supply-profile.d.ts +56 -0
  48. package/dist/supply-profile.d.ts.map +1 -0
  49. package/dist/supply-profile.js +139 -0
  50. package/dist/supply-profile.js.map +1 -0
  51. package/dist/types.d.ts +66 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +22 -0
  54. package/dist/types.js.map +1 -0
  55. package/nam-engine/NOTICE.md +32 -0
  56. package/nam-engine/nam-engine.js +2 -0
  57. package/nam-engine/nam-engine.wasm +0 -0
  58. 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.
@@ -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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"}
@@ -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,2 @@
1
+ export declare function resampleImpulseResponse(ir: Float64Array, fromRate: number, toRate: number): Float64Array;
2
+ //# sourceMappingURL=ir-resample.d.ts.map
@@ -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"}