@vessel-dsp/runtime 0.4.1 → 0.6.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.
@@ -585,37 +585,38 @@ var require_v2_dsp = __commonJS({
585
585
  var _v2_testonly_resample_reset = Module["_v2_testonly_resample_reset"] = (a0) => (_v2_testonly_resample_reset = Module["_v2_testonly_resample_reset"] = wasmExports["q"])(a0);
586
586
  var _v2_testonly_resample_process = Module["_v2_testonly_resample_process"] = (a0, a1) => (_v2_testonly_resample_process = Module["_v2_testonly_resample_process"] = wasmExports["r"])(a0, a1);
587
587
  var _v2_testonly_resample_destroy = Module["_v2_testonly_resample_destroy"] = (a0) => (_v2_testonly_resample_destroy = Module["_v2_testonly_resample_destroy"] = wasmExports["s"])(a0);
588
- var _v2_engine_reset = Module["_v2_engine_reset"] = (a0) => (_v2_engine_reset = Module["_v2_engine_reset"] = wasmExports["t"])(a0);
589
- var _v2_engine_set_control = Module["_v2_engine_set_control"] = (a0, a1, a2) => (_v2_engine_set_control = Module["_v2_engine_set_control"] = wasmExports["u"])(a0, a1, a2);
590
- var _v2_engine_get_control = Module["_v2_engine_get_control"] = (a0, a1) => (_v2_engine_get_control = Module["_v2_engine_get_control"] = wasmExports["v"])(a0, a1);
591
- var _v2_engine_set_supply = Module["_v2_engine_set_supply"] = (a0, a1, a2, a3, a4) => (_v2_engine_set_supply = Module["_v2_engine_set_supply"] = wasmExports["w"])(a0, a1, a2, a3, a4);
592
- var _v2_engine_process_sample = Module["_v2_engine_process_sample"] = (a0, a1) => (_v2_engine_process_sample = Module["_v2_engine_process_sample"] = wasmExports["x"])(a0, a1);
593
- var _v2_engine_process_block = Module["_v2_engine_process_block"] = (a0, a1, a2, a3) => (_v2_engine_process_block = Module["_v2_engine_process_block"] = wasmExports["y"])(a0, a1, a2, a3);
594
- var _v2_engine_get_input_buffer = Module["_v2_engine_get_input_buffer"] = (a0, a1) => (_v2_engine_get_input_buffer = Module["_v2_engine_get_input_buffer"] = wasmExports["z"])(a0, a1);
595
- var _v2_engine_get_output_buffer = Module["_v2_engine_get_output_buffer"] = (a0, a1) => (_v2_engine_get_output_buffer = Module["_v2_engine_get_output_buffer"] = wasmExports["A"])(a0, a1);
596
- var _v2_engine_process_internal = Module["_v2_engine_process_internal"] = (a0, a1) => (_v2_engine_process_internal = Module["_v2_engine_process_internal"] = wasmExports["B"])(a0, a1);
597
- var _v2_engine_get_last_iteration_count = Module["_v2_engine_get_last_iteration_count"] = (a0) => (_v2_engine_get_last_iteration_count = Module["_v2_engine_get_last_iteration_count"] = wasmExports["C"])(a0);
598
- var _v2_engine_get_last_converged = Module["_v2_engine_get_last_converged"] = (a0) => (_v2_engine_get_last_converged = Module["_v2_engine_get_last_converged"] = wasmExports["D"])(a0);
599
- var _v2_engine_get_max_iterations = Module["_v2_engine_get_max_iterations"] = (a0) => (_v2_engine_get_max_iterations = Module["_v2_engine_get_max_iterations"] = wasmExports["E"])(a0);
600
- var _v2_engine_get_operating_point = Module["_v2_engine_get_operating_point"] = (a0, a1, a2) => (_v2_engine_get_operating_point = Module["_v2_engine_get_operating_point"] = wasmExports["F"])(a0, a1, a2);
601
- var _v2_engine_get_state = Module["_v2_engine_get_state"] = (a0, a1, a2) => (_v2_engine_get_state = Module["_v2_engine_get_state"] = wasmExports["G"])(a0, a1, a2);
602
- var _v2_engine_get_last_error = Module["_v2_engine_get_last_error"] = (a0) => (_v2_engine_get_last_error = Module["_v2_engine_get_last_error"] = wasmExports["H"])(a0);
603
- var _v2_engine_get_schedule_solves = Module["_v2_engine_get_schedule_solves"] = (a0) => (_v2_engine_get_schedule_solves = Module["_v2_engine_get_schedule_solves"] = wasmExports["I"])(a0);
604
- var _v2_engine_get_schedule_fallbacks = Module["_v2_engine_get_schedule_fallbacks"] = (a0) => (_v2_engine_get_schedule_fallbacks = Module["_v2_engine_get_schedule_fallbacks"] = wasmExports["J"])(a0);
605
- var _v2_engine_get_predictor_seeds = Module["_v2_engine_get_predictor_seeds"] = (a0) => (_v2_engine_get_predictor_seeds = Module["_v2_engine_get_predictor_seeds"] = wasmExports["K"])(a0);
606
- var _v2_engine_get_one_iteration_solves = Module["_v2_engine_get_one_iteration_solves"] = (a0) => (_v2_engine_get_one_iteration_solves = Module["_v2_engine_get_one_iteration_solves"] = wasmExports["L"])(a0);
607
- var _v2_engine_get_total_iterations = Module["_v2_engine_get_total_iterations"] = (a0) => (_v2_engine_get_total_iterations = Module["_v2_engine_get_total_iterations"] = wasmExports["M"])(a0);
608
- var _v2_engine_get_predictor_order = Module["_v2_engine_get_predictor_order"] = (a0, a1) => (_v2_engine_get_predictor_order = Module["_v2_engine_get_predictor_order"] = wasmExports["N"])(a0, a1);
609
- var _v2_engine_get_kernel_solves = Module["_v2_engine_get_kernel_solves"] = (a0) => (_v2_engine_get_kernel_solves = Module["_v2_engine_get_kernel_solves"] = wasmExports["O"])(a0);
610
- var _v2_engine_get_repivoted_schedule_blocks = Module["_v2_engine_get_repivoted_schedule_blocks"] = (a0) => (_v2_engine_get_repivoted_schedule_blocks = Module["_v2_engine_get_repivoted_schedule_blocks"] = wasmExports["P"])(a0);
611
- var _v2_engine_get_abandoned_schedule_blocks = Module["_v2_engine_get_abandoned_schedule_blocks"] = (a0) => (_v2_engine_get_abandoned_schedule_blocks = Module["_v2_engine_get_abandoned_schedule_blocks"] = wasmExports["Q"])(a0);
612
- var _v2_engine_get_dropped_schedule_blocks = Module["_v2_engine_get_dropped_schedule_blocks"] = (a0) => (_v2_engine_get_dropped_schedule_blocks = Module["_v2_engine_get_dropped_schedule_blocks"] = wasmExports["R"])(a0);
613
- var _malloc = Module["_malloc"] = (a0) => (_malloc = Module["_malloc"] = wasmExports["S"])(a0);
614
- var _free = Module["_free"] = (a0) => (_free = Module["_free"] = wasmExports["T"])(a0);
615
- var ___trap = () => (___trap = wasmExports["U"])();
616
- var __emscripten_stack_restore = (a0) => (__emscripten_stack_restore = wasmExports["V"])(a0);
617
- var __emscripten_stack_alloc = (a0) => (__emscripten_stack_alloc = wasmExports["W"])(a0);
618
- var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports["X"])();
588
+ var _v2_testonly_taper_fraction = Module["_v2_testonly_taper_fraction"] = (a0, a1) => (_v2_testonly_taper_fraction = Module["_v2_testonly_taper_fraction"] = wasmExports["t"])(a0, a1);
589
+ var _v2_engine_reset = Module["_v2_engine_reset"] = (a0) => (_v2_engine_reset = Module["_v2_engine_reset"] = wasmExports["u"])(a0);
590
+ var _v2_engine_set_control = Module["_v2_engine_set_control"] = (a0, a1, a2) => (_v2_engine_set_control = Module["_v2_engine_set_control"] = wasmExports["v"])(a0, a1, a2);
591
+ var _v2_engine_get_control = Module["_v2_engine_get_control"] = (a0, a1) => (_v2_engine_get_control = Module["_v2_engine_get_control"] = wasmExports["w"])(a0, a1);
592
+ var _v2_engine_set_supply = Module["_v2_engine_set_supply"] = (a0, a1, a2, a3, a4) => (_v2_engine_set_supply = Module["_v2_engine_set_supply"] = wasmExports["x"])(a0, a1, a2, a3, a4);
593
+ var _v2_engine_process_sample = Module["_v2_engine_process_sample"] = (a0, a1) => (_v2_engine_process_sample = Module["_v2_engine_process_sample"] = wasmExports["y"])(a0, a1);
594
+ var _v2_engine_process_block = Module["_v2_engine_process_block"] = (a0, a1, a2, a3) => (_v2_engine_process_block = Module["_v2_engine_process_block"] = wasmExports["z"])(a0, a1, a2, a3);
595
+ var _v2_engine_get_input_buffer = Module["_v2_engine_get_input_buffer"] = (a0, a1) => (_v2_engine_get_input_buffer = Module["_v2_engine_get_input_buffer"] = wasmExports["A"])(a0, a1);
596
+ var _v2_engine_get_output_buffer = Module["_v2_engine_get_output_buffer"] = (a0, a1) => (_v2_engine_get_output_buffer = Module["_v2_engine_get_output_buffer"] = wasmExports["B"])(a0, a1);
597
+ var _v2_engine_process_internal = Module["_v2_engine_process_internal"] = (a0, a1) => (_v2_engine_process_internal = Module["_v2_engine_process_internal"] = wasmExports["C"])(a0, a1);
598
+ var _v2_engine_get_last_iteration_count = Module["_v2_engine_get_last_iteration_count"] = (a0) => (_v2_engine_get_last_iteration_count = Module["_v2_engine_get_last_iteration_count"] = wasmExports["D"])(a0);
599
+ var _v2_engine_get_last_converged = Module["_v2_engine_get_last_converged"] = (a0) => (_v2_engine_get_last_converged = Module["_v2_engine_get_last_converged"] = wasmExports["E"])(a0);
600
+ var _v2_engine_get_max_iterations = Module["_v2_engine_get_max_iterations"] = (a0) => (_v2_engine_get_max_iterations = Module["_v2_engine_get_max_iterations"] = wasmExports["F"])(a0);
601
+ var _v2_engine_get_operating_point = Module["_v2_engine_get_operating_point"] = (a0, a1, a2) => (_v2_engine_get_operating_point = Module["_v2_engine_get_operating_point"] = wasmExports["G"])(a0, a1, a2);
602
+ var _v2_engine_get_state = Module["_v2_engine_get_state"] = (a0, a1, a2) => (_v2_engine_get_state = Module["_v2_engine_get_state"] = wasmExports["H"])(a0, a1, a2);
603
+ var _v2_engine_get_last_error = Module["_v2_engine_get_last_error"] = (a0) => (_v2_engine_get_last_error = Module["_v2_engine_get_last_error"] = wasmExports["I"])(a0);
604
+ var _v2_engine_get_schedule_solves = Module["_v2_engine_get_schedule_solves"] = (a0) => (_v2_engine_get_schedule_solves = Module["_v2_engine_get_schedule_solves"] = wasmExports["J"])(a0);
605
+ var _v2_engine_get_schedule_fallbacks = Module["_v2_engine_get_schedule_fallbacks"] = (a0) => (_v2_engine_get_schedule_fallbacks = Module["_v2_engine_get_schedule_fallbacks"] = wasmExports["K"])(a0);
606
+ var _v2_engine_get_predictor_seeds = Module["_v2_engine_get_predictor_seeds"] = (a0) => (_v2_engine_get_predictor_seeds = Module["_v2_engine_get_predictor_seeds"] = wasmExports["L"])(a0);
607
+ var _v2_engine_get_one_iteration_solves = Module["_v2_engine_get_one_iteration_solves"] = (a0) => (_v2_engine_get_one_iteration_solves = Module["_v2_engine_get_one_iteration_solves"] = wasmExports["M"])(a0);
608
+ var _v2_engine_get_total_iterations = Module["_v2_engine_get_total_iterations"] = (a0) => (_v2_engine_get_total_iterations = Module["_v2_engine_get_total_iterations"] = wasmExports["N"])(a0);
609
+ var _v2_engine_get_predictor_order = Module["_v2_engine_get_predictor_order"] = (a0, a1) => (_v2_engine_get_predictor_order = Module["_v2_engine_get_predictor_order"] = wasmExports["O"])(a0, a1);
610
+ var _v2_engine_get_kernel_solves = Module["_v2_engine_get_kernel_solves"] = (a0) => (_v2_engine_get_kernel_solves = Module["_v2_engine_get_kernel_solves"] = wasmExports["P"])(a0);
611
+ var _v2_engine_get_repivoted_schedule_blocks = Module["_v2_engine_get_repivoted_schedule_blocks"] = (a0) => (_v2_engine_get_repivoted_schedule_blocks = Module["_v2_engine_get_repivoted_schedule_blocks"] = wasmExports["Q"])(a0);
612
+ var _v2_engine_get_abandoned_schedule_blocks = Module["_v2_engine_get_abandoned_schedule_blocks"] = (a0) => (_v2_engine_get_abandoned_schedule_blocks = Module["_v2_engine_get_abandoned_schedule_blocks"] = wasmExports["R"])(a0);
613
+ var _v2_engine_get_dropped_schedule_blocks = Module["_v2_engine_get_dropped_schedule_blocks"] = (a0) => (_v2_engine_get_dropped_schedule_blocks = Module["_v2_engine_get_dropped_schedule_blocks"] = wasmExports["S"])(a0);
614
+ var _malloc = Module["_malloc"] = (a0) => (_malloc = Module["_malloc"] = wasmExports["T"])(a0);
615
+ var _free = Module["_free"] = (a0) => (_free = Module["_free"] = wasmExports["U"])(a0);
616
+ var ___trap = () => (___trap = wasmExports["V"])();
617
+ var __emscripten_stack_restore = (a0) => (__emscripten_stack_restore = wasmExports["W"])(a0);
618
+ var __emscripten_stack_alloc = (a0) => (__emscripten_stack_alloc = wasmExports["X"])(a0);
619
+ var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports["Y"])();
619
620
  Module["ccall"] = ccall;
620
621
  Module["cwrap"] = cwrap;
621
622
  Module["setValue"] = setValue;
@@ -1167,6 +1168,58 @@ function cascadeLatencyHostSamples(stages) {
1167
1168
  }
1168
1169
 
1169
1170
  // src/taper.ts
1171
+ var IMPLEMENTED_TAPERS = {
1172
+ linear: true,
1173
+ logarithmic: true,
1174
+ "reverse-logarithmic": true,
1175
+ "reverse-linear": true,
1176
+ "s-curve": true
1177
+ };
1178
+ var IMPLEMENTED_TAPER_NAMES = Object.keys(IMPLEMENTED_TAPERS);
1179
+ function unrecognisedTapers(program) {
1180
+ const found = [];
1181
+ const check = (where, taper) => {
1182
+ if (taper === void 0) return;
1183
+ if (typeof taper !== "string" || !Object.hasOwn(IMPLEMENTED_TAPERS, taper)) {
1184
+ found.push({ where, taper: String(taper) });
1185
+ }
1186
+ };
1187
+ for (const control of program.controls) {
1188
+ check(`control "${control.id}"`, control.taper);
1189
+ }
1190
+ for (const block of program.blocks) {
1191
+ if (block.kind === "mna") {
1192
+ for (const stamp of block.stamps) {
1193
+ if ("taper" in stamp) {
1194
+ check(`${stamp.kind} stamp in block "${block.id}"`, stamp.taper);
1195
+ }
1196
+ }
1197
+ } else if (block.clockControl != null) {
1198
+ check(`clockControl of block "${block.id}"`, block.clockControl.taper);
1199
+ }
1200
+ }
1201
+ return found;
1202
+ }
1203
+ var S_CURVE_SEGMENTS = [
1204
+ { x0: 0, h: 0.3, y0: 0, y1: 0.1, m0: 0, m1: 0.6 },
1205
+ { x0: 0.3, h: 0.2, y0: 0.1, y1: 0.5, m0: 0.6, m1: 2 },
1206
+ { x0: 0.5, h: 0.2, y0: 0.5, y1: 0.9, m0: 2, m1: 0.6 },
1207
+ { x0: 0.7, h: 0.3, y0: 0.9, y1: 1, m0: 0.6, m1: 0 }
1208
+ ];
1209
+ function sCurveFraction(x) {
1210
+ if (x <= 0) return 0;
1211
+ if (x >= 1) return 1;
1212
+ const segment = x < 0.3 ? 0 : x < 0.5 ? 1 : x < 0.7 ? 2 : 3;
1213
+ const { x0, h, y0, y1, m0, m1 } = S_CURVE_SEGMENTS[segment];
1214
+ const t = (x - x0) / h;
1215
+ const t2 = t * t;
1216
+ const t3 = t2 * t;
1217
+ const h00 = 2 * t3 - 3 * t2 + 1;
1218
+ const h10 = t3 - 2 * t2 + t;
1219
+ const h01 = -2 * t3 + 3 * t2;
1220
+ const h11 = t3 - t2;
1221
+ return h00 * y0 + h10 * h * m0 + h01 * y1 + h11 * h * m1;
1222
+ }
1170
1223
  function taperFraction(taper, position) {
1171
1224
  const x = Math.min(1, Math.max(0, position));
1172
1225
  const audioBase = 1089 / 49;
@@ -1179,6 +1232,9 @@ function taperFraction(taper, position) {
1179
1232
  if (taper === "reverse-linear") {
1180
1233
  return 1 - x;
1181
1234
  }
1235
+ if (taper === "s-curve") {
1236
+ return sCurveFraction(x);
1237
+ }
1182
1238
  return x;
1183
1239
  }
1184
1240
 
@@ -1339,7 +1395,10 @@ var OPAMP_FOLD_STREAK = 6;
1339
1395
  function opAmpHalfSwing(railHigh, railLow) {
1340
1396
  return Math.max((railHigh - railLow) / 2, 1e-9);
1341
1397
  }
1342
- var OPAMP_GAIN_BANDWIDTH_HZ = 1e6;
1398
+ var OPAMP_DEFAULT_GAIN_BANDWIDTH_HZ = 1e6;
1399
+ function opAmpGainBandwidthHz(stamp) {
1400
+ return stamp.gainBandwidthHz ?? OPAMP_DEFAULT_GAIN_BANDWIDTH_HZ;
1401
+ }
1343
1402
  var OPAMP_POLE_BAND = 8;
1344
1403
  function boundOpAmpRaw(raw, halfSwing) {
1345
1404
  const limit = OPAMP_POLE_BAND * halfSwing;
@@ -1850,6 +1909,8 @@ var ReferenceRuntime = class {
1850
1909
  limiterTrace = typeof process !== "undefined" && process.env?.VESSEL_LIMITER_TRACE === "1";
1851
1910
  limitedIterationCount = 0;
1852
1911
  limitedIterationPerSample = [];
1912
+ /** Fold reseeds taken so far (diagnostic, read past `private` by the stall study's tracer). */
1913
+ foldReseedCount = 0;
1853
1914
  /** Series ohms of whatever drives the input jack -- see `prepare()`'s option. */
1854
1915
  inputSourceOhms = 0;
1855
1916
  /**
@@ -2162,6 +2223,12 @@ var ReferenceRuntime = class {
2162
2223
  `program requires ${missingModels.length === 1 ? "a DSP model" : "DSP models"} this runtime does not implement: ${missingModels.join(", ")}`
2163
2224
  );
2164
2225
  }
2226
+ const unrecognised = unrecognisedTapers(this.program);
2227
+ if (unrecognised.length > 0) {
2228
+ throw new RuntimeError(
2229
+ `program names ${unrecognised.length === 1 ? "a taper" : "tapers"} this runtime does not implement: ${unrecognised.map((entry) => `"${entry.taper}" (${entry.where})`).join(", ")}; it implements ${IMPLEMENTED_TAPER_NAMES.join(", ")}`
2230
+ );
2231
+ }
2165
2232
  const dispatched = this.program.blocks.filter(
2166
2233
  (block) => block.kind === "macro"
2167
2234
  );
@@ -4025,7 +4092,9 @@ var ReferenceRuntime = class {
4025
4092
  let bestDelta = Number.POSITIVE_INFINITY;
4026
4093
  let noImprovement = 0;
4027
4094
  let foldStreak = 0;
4028
- let foldReseeded = false;
4095
+ let foldReseeds = 0;
4096
+ let lastWorstNode = -1;
4097
+ let lastWorstSign = 0;
4029
4098
  for (let iteration = 0; iteration < iterations; iteration += 1) {
4030
4099
  used = iteration + 1;
4031
4100
  this.limitedIterate = false;
@@ -4153,6 +4222,12 @@ var ReferenceRuntime = class {
4153
4222
  }
4154
4223
  }
4155
4224
  }
4225
+ const damping = dc ? 1 : this.opAmpStepDamping(block, blockIndex, current, next, dt, dc);
4226
+ if (damping < 1) {
4227
+ for (let i = 0; i < size; i++) {
4228
+ next[i] = current[i] + damping * (next[i] - current[i]);
4229
+ }
4230
+ }
4156
4231
  if (relaxing) {
4157
4232
  for (let i = 0; i < size; i++) {
4158
4233
  next[i] = current[i] + alpha * (next[i] - current[i]);
@@ -4161,6 +4236,10 @@ var ReferenceRuntime = class {
4161
4236
  const delta = maxAbsDifference(next, current);
4162
4237
  worstNode = worstDifferenceIndex(next, current);
4163
4238
  worstDelta = delta;
4239
+ const worstSign = Math.sign((next[worstNode] ?? 0) - (current[worstNode] ?? 0));
4240
+ const reversed = dc || worstNode === lastWorstNode && worstSign !== 0 && lastWorstSign !== 0 && worstSign !== lastWorstSign;
4241
+ lastWorstNode = worstNode;
4242
+ lastWorstSign = worstSign;
4164
4243
  if (delta < bestDelta * 0.999) {
4165
4244
  bestDelta = delta;
4166
4245
  noImprovement = 0;
@@ -4173,7 +4252,7 @@ var ReferenceRuntime = class {
4173
4252
  }
4174
4253
  }
4175
4254
  } else {
4176
- noImprovement += 1;
4255
+ noImprovement += reversed ? 1 : 0;
4177
4256
  if (!relaxing) {
4178
4257
  if (noImprovement >= NEWTON_NON_CONTRACTING_LIMIT && iteration >= NEWTON_RELAXATION_EARLIEST_ITERATION) {
4179
4258
  relaxing = true;
@@ -4204,13 +4283,15 @@ var ReferenceRuntime = class {
4204
4283
  }
4205
4284
  const limitedOpamp = this.limitedOpamp;
4206
4285
  foldStreak = limitedOpamp?.folded === true ? foldStreak + 1 : 0;
4207
- if (!dc && !foldReseeded && limitedOpamp !== null && foldStreak >= OPAMP_FOLD_STREAK) {
4208
- foldReseeded = true;
4286
+ if (!dc && limitedOpamp !== null && foldStreak >= OPAMP_FOLD_STREAK) {
4287
+ foldReseeds += 1;
4288
+ this.foldReseedCount += 1;
4209
4289
  foldStreak = 0;
4210
4290
  const wasHigh = (start[limitedOpamp.output] ?? limitedOpamp.centre) >= limitedOpamp.centre;
4211
- current[limitedOpamp.output] = wasHigh ? limitedOpamp.railLow : limitedOpamp.railHigh;
4291
+ const toHigh = foldReseeds % 2 === 1 ? wasHigh : !wasHigh;
4292
+ current[limitedOpamp.output] = toHigh ? limitedOpamp.railHigh : limitedOpamp.railLow;
4212
4293
  this.opampHistory.set(limitedOpamp.key, {
4213
- differential: wasHigh ? -limitedOpamp.band : limitedOpamp.band,
4294
+ differential: toHigh ? limitedOpamp.band : -limitedOpamp.band,
4214
4295
  step: 0,
4215
4296
  cap: limitedOpamp.maxStep
4216
4297
  });
@@ -4250,6 +4331,53 @@ var ReferenceRuntime = class {
4250
4331
  }
4251
4332
  return { solution: current, converged, used, worstNode, worstDelta };
4252
4333
  }
4334
+ /**
4335
+ * The factor the Newton update is scaled by so that no railed op-amp's differential moves
4336
+ * further than its step cap and no op-amp output node moves further than one full swing:
4337
+ * the smallest `cap / |requested differential step|` and `2 * halfSwing / |requested output
4338
+ * step|` over the block's railed op-amps, or 1. `current` is the iterate the rows were
4339
+ * stamped at and `next` the undamped solve. The history read here is the one the stamps
4340
+ * wrote this iteration, so `differential` is the point each row's tangent was taken at and
4341
+ * `cap` the step the next evaluation will allow; a request inside both bounds scales nothing.
4342
+ * Mirrored in `Engine.cpp` (`opAmpStepDamping`); the two must agree to the operation.
4343
+ */
4344
+ opAmpStepDamping(block, blockIndex, current, next, dt, dc) {
4345
+ let damping = 1;
4346
+ for (const stamp of block.stamps) {
4347
+ if (stamp.kind !== "ideal-opamp" || stamp.railHigh === null || stamp.railLow === null) {
4348
+ continue;
4349
+ }
4350
+ const history = this.opampHistory.get(
4351
+ packHistoryKey3(blockIndex, stamp.plus, stamp.minus, stamp.output)
4352
+ );
4353
+ if (history === void 0) continue;
4354
+ const halfSwing = opAmpHalfSwing(stamp.railHigh, stamp.railLow);
4355
+ const alpha = dc ? 1 : opAmpPoleAlpha(
4356
+ stamp.openLoopGain,
4357
+ opAmpGainBandwidthHz(stamp),
4358
+ dt,
4359
+ this.sampleRate
4360
+ );
4361
+ const effectiveGain = alpha * stamp.openLoopGain;
4362
+ const linearWidth = halfSwing / effectiveGain;
4363
+ const band = OPAMP_DIFFERENTIAL_BAND * linearWidth;
4364
+ const nextDifferential = clamp(
4365
+ (next[stamp.plus] ?? 0) - (next[stamp.minus] ?? 0),
4366
+ -band,
4367
+ band
4368
+ );
4369
+ const magnitude = Math.abs(nextDifferential - history.differential);
4370
+ if (magnitude > history.cap) {
4371
+ damping = Math.min(damping, history.cap / magnitude);
4372
+ }
4373
+ const swing = 2 * halfSwing;
4374
+ const outputStep = Math.abs((next[stamp.output] ?? 0) - (current[stamp.output] ?? 0));
4375
+ if (outputStep > swing) {
4376
+ damping = Math.min(damping, swing / outputStep);
4377
+ }
4378
+ }
4379
+ return damping;
4380
+ }
4253
4381
  /**
4254
4382
  * Build-order step 6, vertical slice: the part of a block's matrix that is the same for
4255
4383
  * every Newton iteration of this sample -- every stamp `newtonPortRowsOf` calls linear,
@@ -5073,7 +5201,7 @@ var ReferenceRuntime = class {
5073
5201
  const key = packHistoryKey3(blockIndex, stamp.plus, stamp.minus, stamp.output);
5074
5202
  const alpha = opAmpPoleAlpha(
5075
5203
  stamp.openLoopGain,
5076
- OPAMP_GAIN_BANDWIDTH_HZ,
5204
+ opAmpGainBandwidthHz(stamp),
5077
5205
  dt,
5078
5206
  this.sampleRate
5079
5207
  );
@@ -5718,26 +5846,29 @@ var ReferenceRuntime = class {
5718
5846
  );
5719
5847
  const alpha = dc ? 1 : opAmpPoleAlpha(
5720
5848
  stamp.openLoopGain,
5721
- OPAMP_GAIN_BANDWIDTH_HZ,
5849
+ opAmpGainBandwidthHz(stamp),
5722
5850
  dt,
5723
5851
  this.sampleRate
5724
5852
  );
5725
5853
  const effectiveGain = alpha * stamp.openLoopGain;
5726
5854
  const linearWidth = halfSwing / effectiveGain;
5727
5855
  const previous = this.opampHistory.get(opampKey);
5728
- const previousDifferential = previous?.differential ?? 0;
5729
5856
  const rawDifferential = (solution[stamp.plus] ?? 0) - (solution[stamp.minus] ?? 0);
5730
5857
  const band = OPAMP_DIFFERENTIAL_BAND * linearWidth;
5731
5858
  const bounded = clamp(rawDifferential, -band, band);
5859
+ const previousDifferential = previous?.differential ?? bounded;
5732
5860
  const step = bounded - previousDifferential;
5733
5861
  const maxStep = OPAMP_MAX_DIFFERENTIAL_STEP * linearWidth;
5734
5862
  const reversing = previous !== void 0 && previous.step !== 0 && step !== 0 && Math.sign(step) !== Math.sign(previous.step);
5735
5863
  const previousCap = previous?.cap ?? maxStep;
5736
5864
  const cap = reversing ? Math.max(previousCap / 2, linearWidth / 64) : Math.min(maxStep, previousCap * 1.2);
5737
5865
  const limited = Math.abs(step) > cap;
5866
+ const folded = cap <= linearWidth / 8 && Math.abs(step) >= linearWidth / 256;
5738
5867
  if (limited) {
5739
5868
  this.limitedIterate = true;
5740
5869
  this.limitedBy = `opamp +=${stamp.plus} -=${stamp.minus} out=${stamp.output}`;
5870
+ }
5871
+ if (limited || folded) {
5741
5872
  this.limitedOpamp = {
5742
5873
  key: opampKey,
5743
5874
  output: stamp.output,
@@ -5746,7 +5877,7 @@ var ReferenceRuntime = class {
5746
5877
  railLow: stamp.railLow,
5747
5878
  band,
5748
5879
  maxStep,
5749
- folded: cap <= linearWidth / 8
5880
+ folded
5750
5881
  };
5751
5882
  }
5752
5883
  const differential = limited ? previousDifferential + Math.sign(step) * cap : bounded;
@@ -5763,10 +5894,11 @@ var ReferenceRuntime = class {
5763
5894
  clamp(scaled, -OPAMP_SATURATION_BAND, OPAMP_SATURATION_BAND)
5764
5895
  );
5765
5896
  const slope = effectiveGain * (1 - derivativeShape * derivativeShape);
5897
+ const linearisedAt = limited ? differential : rawDifferential;
5766
5898
  matrix[row][stamp.output] += 1;
5767
5899
  matrix[row][stamp.plus] -= slope;
5768
5900
  matrix[row][stamp.minus] += slope;
5769
- rhs[row] = output - slope * differential;
5901
+ rhs[row] = output - slope * linearisedAt;
5770
5902
  break;
5771
5903
  }
5772
5904
  case "vccs": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vessel-dsp/runtime",
3
- "version": "0.4.1",
3
+ "version": "0.6.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.4.0"
61
+ "@vessel-dsp/compiler": "0.6.0"
62
62
  },
63
63
  "publishConfig": {
64
64
  "access": "public"