@vessel-dsp/runtime 0.4.1 → 0.5.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 +1 -1
- package/dist/reference-runtime.d.ts +13 -0
- package/dist/reference-runtime.d.ts.map +1 -1
- package/dist/reference-runtime.js +186 -18
- package/dist/reference-runtime.js.map +1 -1
- package/dist/wasm/v2_dsp.wasm +0 -0
- package/dist/worklet/v2-audio-worklet.js +82 -12
- package/package.json +2 -2
package/dist/wasm/v2_dsp.wasm
CHANGED
|
Binary file
|
|
@@ -1339,7 +1339,10 @@ var OPAMP_FOLD_STREAK = 6;
|
|
|
1339
1339
|
function opAmpHalfSwing(railHigh, railLow) {
|
|
1340
1340
|
return Math.max((railHigh - railLow) / 2, 1e-9);
|
|
1341
1341
|
}
|
|
1342
|
-
var
|
|
1342
|
+
var OPAMP_DEFAULT_GAIN_BANDWIDTH_HZ = 1e6;
|
|
1343
|
+
function opAmpGainBandwidthHz(stamp) {
|
|
1344
|
+
return stamp.gainBandwidthHz ?? OPAMP_DEFAULT_GAIN_BANDWIDTH_HZ;
|
|
1345
|
+
}
|
|
1343
1346
|
var OPAMP_POLE_BAND = 8;
|
|
1344
1347
|
function boundOpAmpRaw(raw, halfSwing) {
|
|
1345
1348
|
const limit = OPAMP_POLE_BAND * halfSwing;
|
|
@@ -1850,6 +1853,8 @@ var ReferenceRuntime = class {
|
|
|
1850
1853
|
limiterTrace = typeof process !== "undefined" && process.env?.VESSEL_LIMITER_TRACE === "1";
|
|
1851
1854
|
limitedIterationCount = 0;
|
|
1852
1855
|
limitedIterationPerSample = [];
|
|
1856
|
+
/** Fold reseeds taken so far (diagnostic, read past `private` by the stall study's tracer). */
|
|
1857
|
+
foldReseedCount = 0;
|
|
1853
1858
|
/** Series ohms of whatever drives the input jack -- see `prepare()`'s option. */
|
|
1854
1859
|
inputSourceOhms = 0;
|
|
1855
1860
|
/**
|
|
@@ -4025,7 +4030,9 @@ var ReferenceRuntime = class {
|
|
|
4025
4030
|
let bestDelta = Number.POSITIVE_INFINITY;
|
|
4026
4031
|
let noImprovement = 0;
|
|
4027
4032
|
let foldStreak = 0;
|
|
4028
|
-
let
|
|
4033
|
+
let foldReseeds = 0;
|
|
4034
|
+
let lastWorstNode = -1;
|
|
4035
|
+
let lastWorstSign = 0;
|
|
4029
4036
|
for (let iteration = 0; iteration < iterations; iteration += 1) {
|
|
4030
4037
|
used = iteration + 1;
|
|
4031
4038
|
this.limitedIterate = false;
|
|
@@ -4153,6 +4160,12 @@ var ReferenceRuntime = class {
|
|
|
4153
4160
|
}
|
|
4154
4161
|
}
|
|
4155
4162
|
}
|
|
4163
|
+
const damping = dc ? 1 : this.opAmpStepDamping(block, blockIndex, current, next, dt, dc);
|
|
4164
|
+
if (damping < 1) {
|
|
4165
|
+
for (let i = 0; i < size; i++) {
|
|
4166
|
+
next[i] = current[i] + damping * (next[i] - current[i]);
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4156
4169
|
if (relaxing) {
|
|
4157
4170
|
for (let i = 0; i < size; i++) {
|
|
4158
4171
|
next[i] = current[i] + alpha * (next[i] - current[i]);
|
|
@@ -4161,6 +4174,10 @@ var ReferenceRuntime = class {
|
|
|
4161
4174
|
const delta = maxAbsDifference(next, current);
|
|
4162
4175
|
worstNode = worstDifferenceIndex(next, current);
|
|
4163
4176
|
worstDelta = delta;
|
|
4177
|
+
const worstSign = Math.sign((next[worstNode] ?? 0) - (current[worstNode] ?? 0));
|
|
4178
|
+
const reversed = dc || worstNode === lastWorstNode && worstSign !== 0 && lastWorstSign !== 0 && worstSign !== lastWorstSign;
|
|
4179
|
+
lastWorstNode = worstNode;
|
|
4180
|
+
lastWorstSign = worstSign;
|
|
4164
4181
|
if (delta < bestDelta * 0.999) {
|
|
4165
4182
|
bestDelta = delta;
|
|
4166
4183
|
noImprovement = 0;
|
|
@@ -4173,7 +4190,7 @@ var ReferenceRuntime = class {
|
|
|
4173
4190
|
}
|
|
4174
4191
|
}
|
|
4175
4192
|
} else {
|
|
4176
|
-
noImprovement += 1;
|
|
4193
|
+
noImprovement += reversed ? 1 : 0;
|
|
4177
4194
|
if (!relaxing) {
|
|
4178
4195
|
if (noImprovement >= NEWTON_NON_CONTRACTING_LIMIT && iteration >= NEWTON_RELAXATION_EARLIEST_ITERATION) {
|
|
4179
4196
|
relaxing = true;
|
|
@@ -4204,13 +4221,15 @@ var ReferenceRuntime = class {
|
|
|
4204
4221
|
}
|
|
4205
4222
|
const limitedOpamp = this.limitedOpamp;
|
|
4206
4223
|
foldStreak = limitedOpamp?.folded === true ? foldStreak + 1 : 0;
|
|
4207
|
-
if (!dc &&
|
|
4208
|
-
|
|
4224
|
+
if (!dc && limitedOpamp !== null && foldStreak >= OPAMP_FOLD_STREAK) {
|
|
4225
|
+
foldReseeds += 1;
|
|
4226
|
+
this.foldReseedCount += 1;
|
|
4209
4227
|
foldStreak = 0;
|
|
4210
4228
|
const wasHigh = (start[limitedOpamp.output] ?? limitedOpamp.centre) >= limitedOpamp.centre;
|
|
4211
|
-
|
|
4229
|
+
const toHigh = foldReseeds % 2 === 1 ? wasHigh : !wasHigh;
|
|
4230
|
+
current[limitedOpamp.output] = toHigh ? limitedOpamp.railHigh : limitedOpamp.railLow;
|
|
4212
4231
|
this.opampHistory.set(limitedOpamp.key, {
|
|
4213
|
-
differential:
|
|
4232
|
+
differential: toHigh ? limitedOpamp.band : -limitedOpamp.band,
|
|
4214
4233
|
step: 0,
|
|
4215
4234
|
cap: limitedOpamp.maxStep
|
|
4216
4235
|
});
|
|
@@ -4250,6 +4269,53 @@ var ReferenceRuntime = class {
|
|
|
4250
4269
|
}
|
|
4251
4270
|
return { solution: current, converged, used, worstNode, worstDelta };
|
|
4252
4271
|
}
|
|
4272
|
+
/**
|
|
4273
|
+
* The factor the Newton update is scaled by so that no railed op-amp's differential moves
|
|
4274
|
+
* further than its step cap and no op-amp output node moves further than one full swing:
|
|
4275
|
+
* the smallest `cap / |requested differential step|` and `2 * halfSwing / |requested output
|
|
4276
|
+
* step|` over the block's railed op-amps, or 1. `current` is the iterate the rows were
|
|
4277
|
+
* stamped at and `next` the undamped solve. The history read here is the one the stamps
|
|
4278
|
+
* wrote this iteration, so `differential` is the point each row's tangent was taken at and
|
|
4279
|
+
* `cap` the step the next evaluation will allow; a request inside both bounds scales nothing.
|
|
4280
|
+
* Mirrored in `Engine.cpp` (`opAmpStepDamping`); the two must agree to the operation.
|
|
4281
|
+
*/
|
|
4282
|
+
opAmpStepDamping(block, blockIndex, current, next, dt, dc) {
|
|
4283
|
+
let damping = 1;
|
|
4284
|
+
for (const stamp of block.stamps) {
|
|
4285
|
+
if (stamp.kind !== "ideal-opamp" || stamp.railHigh === null || stamp.railLow === null) {
|
|
4286
|
+
continue;
|
|
4287
|
+
}
|
|
4288
|
+
const history = this.opampHistory.get(
|
|
4289
|
+
packHistoryKey3(blockIndex, stamp.plus, stamp.minus, stamp.output)
|
|
4290
|
+
);
|
|
4291
|
+
if (history === void 0) continue;
|
|
4292
|
+
const halfSwing = opAmpHalfSwing(stamp.railHigh, stamp.railLow);
|
|
4293
|
+
const alpha = dc ? 1 : opAmpPoleAlpha(
|
|
4294
|
+
stamp.openLoopGain,
|
|
4295
|
+
opAmpGainBandwidthHz(stamp),
|
|
4296
|
+
dt,
|
|
4297
|
+
this.sampleRate
|
|
4298
|
+
);
|
|
4299
|
+
const effectiveGain = alpha * stamp.openLoopGain;
|
|
4300
|
+
const linearWidth = halfSwing / effectiveGain;
|
|
4301
|
+
const band = OPAMP_DIFFERENTIAL_BAND * linearWidth;
|
|
4302
|
+
const nextDifferential = clamp(
|
|
4303
|
+
(next[stamp.plus] ?? 0) - (next[stamp.minus] ?? 0),
|
|
4304
|
+
-band,
|
|
4305
|
+
band
|
|
4306
|
+
);
|
|
4307
|
+
const magnitude = Math.abs(nextDifferential - history.differential);
|
|
4308
|
+
if (magnitude > history.cap) {
|
|
4309
|
+
damping = Math.min(damping, history.cap / magnitude);
|
|
4310
|
+
}
|
|
4311
|
+
const swing = 2 * halfSwing;
|
|
4312
|
+
const outputStep = Math.abs((next[stamp.output] ?? 0) - (current[stamp.output] ?? 0));
|
|
4313
|
+
if (outputStep > swing) {
|
|
4314
|
+
damping = Math.min(damping, swing / outputStep);
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
return damping;
|
|
4318
|
+
}
|
|
4253
4319
|
/**
|
|
4254
4320
|
* Build-order step 6, vertical slice: the part of a block's matrix that is the same for
|
|
4255
4321
|
* every Newton iteration of this sample -- every stamp `newtonPortRowsOf` calls linear,
|
|
@@ -5073,7 +5139,7 @@ var ReferenceRuntime = class {
|
|
|
5073
5139
|
const key = packHistoryKey3(blockIndex, stamp.plus, stamp.minus, stamp.output);
|
|
5074
5140
|
const alpha = opAmpPoleAlpha(
|
|
5075
5141
|
stamp.openLoopGain,
|
|
5076
|
-
|
|
5142
|
+
opAmpGainBandwidthHz(stamp),
|
|
5077
5143
|
dt,
|
|
5078
5144
|
this.sampleRate
|
|
5079
5145
|
);
|
|
@@ -5718,26 +5784,29 @@ var ReferenceRuntime = class {
|
|
|
5718
5784
|
);
|
|
5719
5785
|
const alpha = dc ? 1 : opAmpPoleAlpha(
|
|
5720
5786
|
stamp.openLoopGain,
|
|
5721
|
-
|
|
5787
|
+
opAmpGainBandwidthHz(stamp),
|
|
5722
5788
|
dt,
|
|
5723
5789
|
this.sampleRate
|
|
5724
5790
|
);
|
|
5725
5791
|
const effectiveGain = alpha * stamp.openLoopGain;
|
|
5726
5792
|
const linearWidth = halfSwing / effectiveGain;
|
|
5727
5793
|
const previous = this.opampHistory.get(opampKey);
|
|
5728
|
-
const previousDifferential = previous?.differential ?? 0;
|
|
5729
5794
|
const rawDifferential = (solution[stamp.plus] ?? 0) - (solution[stamp.minus] ?? 0);
|
|
5730
5795
|
const band = OPAMP_DIFFERENTIAL_BAND * linearWidth;
|
|
5731
5796
|
const bounded = clamp(rawDifferential, -band, band);
|
|
5797
|
+
const previousDifferential = previous?.differential ?? bounded;
|
|
5732
5798
|
const step = bounded - previousDifferential;
|
|
5733
5799
|
const maxStep = OPAMP_MAX_DIFFERENTIAL_STEP * linearWidth;
|
|
5734
5800
|
const reversing = previous !== void 0 && previous.step !== 0 && step !== 0 && Math.sign(step) !== Math.sign(previous.step);
|
|
5735
5801
|
const previousCap = previous?.cap ?? maxStep;
|
|
5736
5802
|
const cap = reversing ? Math.max(previousCap / 2, linearWidth / 64) : Math.min(maxStep, previousCap * 1.2);
|
|
5737
5803
|
const limited = Math.abs(step) > cap;
|
|
5804
|
+
const folded = cap <= linearWidth / 8 && Math.abs(step) >= linearWidth / 256;
|
|
5738
5805
|
if (limited) {
|
|
5739
5806
|
this.limitedIterate = true;
|
|
5740
5807
|
this.limitedBy = `opamp +=${stamp.plus} -=${stamp.minus} out=${stamp.output}`;
|
|
5808
|
+
}
|
|
5809
|
+
if (limited || folded) {
|
|
5741
5810
|
this.limitedOpamp = {
|
|
5742
5811
|
key: opampKey,
|
|
5743
5812
|
output: stamp.output,
|
|
@@ -5746,7 +5815,7 @@ var ReferenceRuntime = class {
|
|
|
5746
5815
|
railLow: stamp.railLow,
|
|
5747
5816
|
band,
|
|
5748
5817
|
maxStep,
|
|
5749
|
-
folded
|
|
5818
|
+
folded
|
|
5750
5819
|
};
|
|
5751
5820
|
}
|
|
5752
5821
|
const differential = limited ? previousDifferential + Math.sign(step) * cap : bounded;
|
|
@@ -5763,10 +5832,11 @@ var ReferenceRuntime = class {
|
|
|
5763
5832
|
clamp(scaled, -OPAMP_SATURATION_BAND, OPAMP_SATURATION_BAND)
|
|
5764
5833
|
);
|
|
5765
5834
|
const slope = effectiveGain * (1 - derivativeShape * derivativeShape);
|
|
5835
|
+
const linearisedAt = limited ? differential : rawDifferential;
|
|
5766
5836
|
matrix[row][stamp.output] += 1;
|
|
5767
5837
|
matrix[row][stamp.plus] -= slope;
|
|
5768
5838
|
matrix[row][stamp.minus] += slope;
|
|
5769
|
-
rhs[row] = output - slope *
|
|
5839
|
+
rhs[row] = output - slope * linearisedAt;
|
|
5770
5840
|
break;
|
|
5771
5841
|
}
|
|
5772
5842
|
case "vccs": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vessel-dsp/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Real-time MNA simulation runtime and solver console for VesselDSP Program ROMs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"guitar-pedal",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"generate-kernels": "bun scripts/generate-kernels.ts"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@vessel-dsp/compiler": "0.
|
|
61
|
+
"@vessel-dsp/compiler": "0.5.0"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|