@opendaw/studio-core-wasm 0.0.12 → 0.0.14

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 (37) hide show
  1. package/dist/engine-modules.d.ts.map +1 -1
  2. package/dist/engine-modules.js +1 -0
  3. package/dist/script-bridge.d.ts.map +1 -1
  4. package/dist/script-bridge.js +12 -4
  5. package/dist/script-spielwerk.d.ts +2 -1
  6. package/dist/script-spielwerk.d.ts.map +1 -1
  7. package/dist/script-spielwerk.js +31 -6
  8. package/dist/wasm/engine.wasm +0 -0
  9. package/dist/wasm/plugins/device_apparat.wasm +0 -0
  10. package/dist/wasm/plugins/device_autotune.wasm +0 -0
  11. package/dist/wasm/plugins/device_compressor.wasm +0 -0
  12. package/dist/wasm/plugins/device_crusher.wasm +0 -0
  13. package/dist/wasm/plugins/device_cubed.wasm +0 -0
  14. package/dist/wasm/plugins/device_dattorro_reverb.wasm +0 -0
  15. package/dist/wasm/plugins/device_delay.wasm +0 -0
  16. package/dist/wasm/plugins/device_fold.wasm +0 -0
  17. package/dist/wasm/plugins/device_gate.wasm +0 -0
  18. package/dist/wasm/plugins/device_maximizer.wasm +0 -0
  19. package/dist/wasm/plugins/device_nano.wasm +0 -0
  20. package/dist/wasm/plugins/device_neon.wasm +0 -0
  21. package/dist/wasm/plugins/device_neural_amp.wasm +0 -0
  22. package/dist/wasm/plugins/device_playfield_sample.wasm +0 -0
  23. package/dist/wasm/plugins/device_revamp.wasm +0 -0
  24. package/dist/wasm/plugins/device_reverb.wasm +0 -0
  25. package/dist/wasm/plugins/device_soundfont.wasm +0 -0
  26. package/dist/wasm/plugins/device_stereo_tool.wasm +0 -0
  27. package/dist/wasm/plugins/device_tidal.wasm +0 -0
  28. package/dist/wasm/plugins/device_vaporisateur.wasm +0 -0
  29. package/dist/wasm/plugins/device_vocoder.wasm +0 -0
  30. package/dist/wasm/plugins/device_waveshaper.wasm +0 -0
  31. package/dist/wasm/plugins/device_werkstatt.wasm +0 -0
  32. package/dist/wasm/plugins/device_zeitgeist.wasm +0 -0
  33. package/dist/wasm-offline-worker.js +43 -40
  34. package/dist/wasm-offline-worker.js.map +4 -4
  35. package/dist/wasm-processor.js +17 -16
  36. package/dist/wasm-processor.js.map +4 -4
  37. package/package.json +11 -11
@@ -1 +1 @@
1
- {"version":3,"file":"engine-modules.d.ts","sourceRoot":"","sources":["../src/engine-modules.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,aAAa,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAG7E,mBAAmB,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAE3E,eAAe,EAAE,MAAM,CAAC;IAGxB,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAG3C,cAAc,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAC9C,CAAA;AASD,MAAM,MAAM,mBAAmB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,mBAAmB,CAAA;IACzB,WAAW,EAAE,0BAA0B,CAAA;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAC1C,CAAA;AAGD,oBAAY,mBAAmB;IAAE,WAAW,IAAI;IAAE,UAAU,IAAI;CAAC;AAGjE,oBAAY,0BAA0B;IAAE,SAAS,IAAI;IAAE,MAAM,IAAI;IAAE,SAAS,IAAI;CAAC;AAEjF,MAAM,MAAM,aAAa,GAAG;IACxB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAA;IAChC,aAAa,EAAE,aAAa,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IAChD,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,UAAU,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;IACxC,gBAAgB,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAA;CACvD,CAAA;AAQD,eAAO,MAAM,kBAAkB,QAAO,WAAW,CAAC,MAAgD,CAAA;AAMlG,eAAO,MAAM,OAAO,EAAE,aAAa,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA4BnE,CAAA;AAKD,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,aAAa,CAWnD,CAAA;AAMD,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,mBAAmB,CA0BhE,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAU,OAAM,MAAW,KAAG,OAAO,CAAC,aAAa,CAUhF,CAAA"}
1
+ {"version":3,"file":"engine-modules.d.ts","sourceRoot":"","sources":["../src/engine-modules.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,aAAa,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAG7E,mBAAmB,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAE3E,eAAe,EAAE,MAAM,CAAC;IAGxB,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAG3C,cAAc,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAC9C,CAAA;AASD,MAAM,MAAM,mBAAmB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,mBAAmB,CAAA;IACzB,WAAW,EAAE,0BAA0B,CAAA;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAC1C,CAAA;AAGD,oBAAY,mBAAmB;IAAE,WAAW,IAAI;IAAE,UAAU,IAAI;CAAC;AAGjE,oBAAY,0BAA0B;IAAE,SAAS,IAAI;IAAE,MAAM,IAAI;IAAE,SAAS,IAAI;CAAC;AAEjF,MAAM,MAAM,aAAa,GAAG;IACxB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAA;IAChC,aAAa,EAAE,aAAa,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IAChD,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,UAAU,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;IACxC,gBAAgB,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAA;CACvD,CAAA;AAQD,eAAO,MAAM,kBAAkB,QAAO,WAAW,CAAC,MAAgD,CAAA;AAMlG,eAAO,MAAM,OAAO,EAAE,aAAa,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA6BnE,CAAA;AAKD,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,aAAa,CAWnD,CAAA;AAMD,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,mBAAmB,CA0BhE,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAU,OAAM,MAAW,KAAG,OAAO,CAAC,aAAa,CAUhF,CAAA"}
@@ -49,6 +49,7 @@ export const DEVICES = [
49
49
  { url: "/wasm/plugins/device_reverb.wasm", boxType: "ReverbDeviceBox" }, // audio effect
50
50
  { url: "/wasm/plugins/device_dattorro_reverb.wasm", boxType: "DattorroReverbDeviceBox" }, // audio effect
51
51
  { url: "/wasm/plugins/device_soundfont.wasm", boxType: "SoundfontDeviceBox" }, // instrument (preset sampler)
52
+ { url: "/wasm/plugins/device_cubed.wasm", boxType: "CubedDeviceBox" }, // instrument (303-style bassline)
52
53
  { url: "/wasm/plugins/device_vocoder.wasm", boxType: "VocoderDeviceBox" }, // audio effect (channel vocoder + sidechain)
53
54
  { url: "/wasm/plugins/device_neural_amp.wasm", boxType: "NeuralAmpDeviceBox" }, // audio effect (NAM, via the nam bridge)
54
55
  { url: "/wasm/plugins/device_autotune.wasm", boxType: "AutotuneDeviceBox" }, // audio effect (pitch correction, PSOLA)
@@ -1 +1 @@
1
- {"version":3,"file":"script-bridge.d.ts","sourceRoot":"","sources":["../src/script-bridge.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,YAAY;IACzB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3D,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;CACxC;AAsDD,qBAAa,aAAa;;gBASV,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EACpE,SAAS,GAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAe;IAQzE,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC;IA+M/D,eAAe,IAAI,MAAM;CAG5B"}
1
+ {"version":3,"file":"script-bridge.d.ts","sourceRoot":"","sources":["../src/script-bridge.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,YAAY;IACzB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3D,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;CACxC;AAyDD,qBAAa,aAAa;;gBASV,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EACpE,SAAS,GAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAe;IAQzE,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC;IAoN/D,eAAe,IAAI,MAAM;CAG5B"}
@@ -12,7 +12,7 @@
12
12
  // views EVERY call, since the SharedArrayBuffer can grow / detach (talc), never caching a typed-array view.
13
13
  import { isDefined, UUID, ValueMapping } from "@opendaw/lib-std";
14
14
  import { SimpleLimiter } from "@opendaw/lib-dsp";
15
- import { runSpielwerk, SpielwerkRuntime } from "./script-spielwerk";
15
+ import { copyEvents, runSpielwerk, SpielwerkRuntime } from "./script-spielwerk";
16
16
  const RENDER_QUANTUM = 128;
17
17
  // ~1 second of render calls (128-frame quanta at 48k) before a scriptless device is reported — long enough to
18
18
  // cover a script still compiling / addModule-ing async, short enough to surface a genuine misconfiguration.
@@ -49,6 +49,9 @@ class Bridge {
49
49
  sampleHandles = new Map(); // index -> resolved engine sample handle (-1 = no sample; 0 is valid)
50
50
  limiter = null;
51
51
  spielwerk = null;
52
+ // Spielwerk's `@no-pass` verdict for the CURRENTLY loaded script. `true` by default so a device whose script
53
+ // never loaded (or died) forwards its input instead of muting the chain.
54
+ pass = true;
52
55
  // A scriptable device with no registered Processor renders silence. That is legitimate for a block or two
53
56
  // while the script loads async, but a device that stays scriptless is an anomaly the host must surface (not
54
57
  // swallow). Count the render calls that found no registry entry; warn ONCE past the grace window.
@@ -143,6 +146,8 @@ export class ScriptBridges {
143
146
  return null;
144
147
  }
145
148
  if (registry !== undefined && registry.update !== bridge.currentUpdate) {
149
+ // Outside the try: a script that throws in its constructor still gets ITS declared note routing.
150
+ bridge.pass = registry.pass !== false;
146
151
  try {
147
152
  const proc = new registry.create();
148
153
  bridge.paramMappings = new Map(registry.params.map(declaration => [declaration.index, { label: declaration.label, mapping: resolveMapping(declaration) }]));
@@ -277,16 +282,19 @@ export class ScriptBridges {
277
282
  if (bridge === undefined) {
278
283
  return 0;
279
284
  }
285
+ // No live Processor (never loaded, or silenced): forward the input, so a scriptless / broken Spielwerk
286
+ // is transparent rather than a mute button. A `@no-pass` script keeps its silence, as its author asked.
287
+ const forward = () => bridge.pass ? copyEvents(this.#memory.buffer, inPtr, inCount, outPtr, outMax) : 0;
280
288
  const proc = this.#ensureProc(bridge);
281
289
  if (proc === null || bridge.spielwerk === null) {
282
- return 0;
290
+ return forward();
283
291
  }
284
292
  try {
285
- return runSpielwerk(bridge.spielwerk, proc, this.#memory.buffer, inPtr, inCount, outPtr, outMax, from, to, bpm, flags, s0, s1);
293
+ return runSpielwerk(bridge.spielwerk, proc, this.#memory.buffer, inPtr, inCount, outPtr, outMax, from, to, bpm, flags, s0, s1, bridge.pass);
286
294
  }
287
295
  catch (error) {
288
296
  this.#silence(bridge, `Runtime error: ${error}`);
289
- return 0;
297
+ return forward();
290
298
  }
291
299
  }
292
300
  // Resolve a sample handle's resident planar frames and hand the user proc an AudioData view (no copy).
@@ -19,6 +19,7 @@ export declare class SpielwerkRuntime {
19
19
  readonly sourceToOutput: Map<number, Set<number>>;
20
20
  reset(): void;
21
21
  }
22
- export declare const runSpielwerk: (runtime: SpielwerkRuntime, proc: any, memory: ArrayBufferLike, inPtr: number, inCount: number, outPtr: number, outMax: number, from: number, to: number, bpm: number, flags: number, s0: number, s1: number) => number;
22
+ export declare const copyEvents: (memory: ArrayBufferLike, inPtr: number, inCount: number, outPtr: number, outMax: number) => number;
23
+ export declare const runSpielwerk: (runtime: SpielwerkRuntime, proc: any, memory: ArrayBufferLike, inPtr: number, inCount: number, outPtr: number, outMax: number, from: number, to: number, bpm: number, flags: number, s0: number, s1: number, pass: boolean) => number;
23
24
  export {};
24
25
  //# sourceMappingURL=script-spielwerk.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"script-spielwerk.d.ts","sourceRoot":"","sources":["../src/script-spielwerk.ts"],"names":[],"mappings":"AAgBA,KAAK,aAAa,GAAG;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAA;AACxG,KAAK,YAAY,GAAG;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAA;AAKnH,qBAAa,gBAAgB;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAK;IACtC,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,CAAK;IACxC,QAAQ,CAAC,cAAc,2BAAiC;IAExD,KAAK,IAAI,IAAI;CAKhB;AA6BD,eAAO,MAAM,YAAY,GAAI,SAAS,gBAAgB,EAAE,MAAM,GAAG,EAAE,QAAQ,eAAe,EAC7D,OAAO,MAAM,EAAE,SAAS,MAAM,EAAE,QAAQ,MAAM,EAAE,QAAQ,MAAM,EAC9D,MAAM,MAAM,EAAE,IAAI,MAAM,EAAE,KAAK,MAAM,EAAE,OAAO,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM,KAAG,MA0F3G,CAAA"}
1
+ {"version":3,"file":"script-spielwerk.d.ts","sourceRoot":"","sources":["../src/script-spielwerk.ts"],"names":[],"mappings":"AAuBA,KAAK,aAAa,GAAG;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAA;AACxG,KAAK,YAAY,GAAG;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAA;AAKnH,qBAAa,gBAAgB;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAK;IACtC,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,CAAK;IACxC,QAAQ,CAAC,cAAc,2BAAiC;IAExD,KAAK,IAAI,IAAI;CAKhB;AAgBD,eAAO,MAAM,UAAU,GAAI,QAAQ,eAAe,EAAE,OAAO,MAAM,EAAE,SAAS,MAAM,EACvD,QAAQ,MAAM,EAAE,QAAQ,MAAM,KAAG,MAI3D,CAAA;AAiBD,eAAO,MAAM,YAAY,GAAI,SAAS,gBAAgB,EAAE,MAAM,GAAG,EAAE,QAAQ,eAAe,EAC7D,OAAO,MAAM,EAAE,SAAS,MAAM,EAAE,QAAQ,MAAM,EAAE,QAAQ,MAAM,EAC9D,MAAM,MAAM,EAAE,IAAI,MAAM,EAAE,KAAK,MAAM,EAAE,OAAO,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM,EAC5E,MAAM,OAAO,KAAG,MAoG5C,CAAA"}
@@ -1,8 +1,10 @@
1
1
  // Spielwerk's stateful note-transform orchestration (mirrors the TS `SpielwerkDeviceProcessor.processNotes`),
2
2
  // run JS-side by the script bridge. It reads the upstream input `EventRecord`s the device pulled (note-on /
3
- // note-off), builds the `UserEvent` stream, runs the user generator `*process(block, events)`, validates each
4
- // yielded note, schedules future-block notes, and correlates note-on -> note-off via a retainer so it can emit
5
- // the stops. The persistent state (retainer + scheduler) lives in `SpielwerkRuntime` across blocks.
3
+ // note-off), FORWARDS them verbatim unless the script declared `// @no-pass`, builds the `UserEvent` stream,
4
+ // runs the user generator `*process(block, events)`, validates each yielded note, schedules future-block notes,
5
+ // and correlates note-on -> note-off via a retainer so it can emit the stops. Forwarded notes are NOT retained:
6
+ // their note-off arrives from upstream. The persistent state (retainer + scheduler) lives in `SpielwerkRuntime`
7
+ // across blocks.
6
8
  //
7
9
  // EventRecord (abi, 40 bytes, little-endian): position f64@0, offset u32@8, kind u32@12, id u32@16,
8
10
  // pitch u32@20, velocity f32@24, cent f32@28, duration f64@32. kind: 0 = note-on, 1 = note-off. `duration` is
@@ -12,6 +14,11 @@ const KIND_NOTE_ON = 0;
12
14
  const KIND_NOTE_OFF = 1;
13
15
  const MAX_NOTES_PER_BLOCK = 128;
14
16
  const MAX_SCHEDULED_NOTES = 128;
17
+ // Forwarded input records keep their UPSTREAM ids, so the ids this device MINTS must live in a disjoint range:
18
+ // the consumer matches a note-off to its voice by id, and a collision would stop the wrong note.
19
+ const GENERATED_ID_BASE = 0x4000_0000;
20
+ const GENERATED_ID_MASK = 0x3fff_ffff;
21
+ const FLAG_DISCONTINUOUS = 1 << 1; // mirrors abi `BlockFlags::DISCONTINUOUS`: a loop wrap or a seek
15
22
  let nextOutputId = 1;
16
23
  // One Spielwerk instance's cross-block state.
17
24
  export class SpielwerkRuntime {
@@ -35,6 +42,13 @@ const writeRecord = (view, slot, kind, position, id, pitch, velocity, cent, dura
35
42
  view.setFloat32(base + 28, cent, true);
36
43
  view.setFloat64(base + 32, duration, true);
37
44
  };
45
+ // Forward the pulled input records UNCHANGED (id, position, duration, pitch, velocity, cent). Used both as the
46
+ // pass-through prologue of a run and as the fallback when there is no live Processor to run at all.
47
+ export const copyEvents = (memory, inPtr, inCount, outPtr, outMax) => {
48
+ const count = Math.min(inCount, outMax);
49
+ new Uint8Array(memory, outPtr, count * RECORD_SIZE).set(new Uint8Array(memory, inPtr, count * RECORD_SIZE));
50
+ return count;
51
+ };
38
52
  const validateNote = (note, from) => {
39
53
  if (note === undefined || note === null) {
40
54
  return "process yielded undefined";
@@ -67,16 +81,27 @@ const validateNote = (note, from) => {
67
81
  };
68
82
  // Run one pulled range through the user generator + tracking, writing output records, returning the count.
69
83
  // Throws on a script error / validation failure / flood (the bridge catches it and silences).
70
- export const runSpielwerk = (runtime, proc, memory, inPtr, inCount, outPtr, outMax, from, to, bpm, flags, s0, s1) => {
84
+ export const runSpielwerk = (runtime, proc, memory, inPtr, inCount, outPtr, outMax, from, to, bpm, flags, s0, s1, pass) => {
71
85
  const input = new DataView(memory, inPtr, inCount * RECORD_SIZE);
72
86
  const out = new DataView(memory, outPtr, outMax * RECORD_SIZE);
73
- let outCount = 0;
87
+ // Pass-through prologue: the input goes out verbatim (the output is still empty, so one bulk copy), and the
88
+ // generator's yields are ADDED on top. `@no-pass` scripts skip this and own the output alone.
89
+ let outCount = pass ? copyEvents(memory, inPtr, inCount, outPtr, outMax) : 0;
74
90
  const emit = (kind, position, id, pitch, velocity, cent, duration) => {
75
91
  if (outCount >= outMax) {
76
92
  throw new Error(`Note flood: exceeded ${outMax} output notes per range`);
77
93
  }
78
94
  writeRecord(out, outCount++, kind, position, id, pitch, velocity, cent, duration);
79
95
  };
96
+ // A transport JUMP (loop wrap / seek) releases EVERYTHING held, at `from` — the contract the engine's own
97
+ // note source honours (`NoteSequencer::process_notes`). Without it a note whose span reaches past the loop
98
+ // end never sees its end position again and the downstream instrument holds that voice forever.
99
+ if ((flags & FLAG_DISCONTINUOUS) !== 0) {
100
+ for (const note of runtime.retained) {
101
+ emit(KIND_NOTE_OFF, from, note.id, note.pitch, 0, 0, 0);
102
+ }
103
+ runtime.reset();
104
+ }
80
105
  // Release retained notes whose span completed within this range, emitting their note-off.
81
106
  for (let i = runtime.retained.length - 1; i >= 0; i--) {
82
107
  const note = runtime.retained[i];
@@ -173,7 +198,7 @@ export const runSpielwerk = (runtime, proc, memory, inPtr, inCount, outPtr, outM
173
198
  return outCount;
174
199
  };
175
200
  const retain = (runtime, note) => {
176
- const id = nextOutputId++;
201
+ const id = GENERATED_ID_BASE + (nextOutputId++ & GENERATED_ID_MASK);
177
202
  runtime.retained.push({ id, position: note.position, duration: note.duration, pitch: note.pitch, velocity: note.velocity, cent: note.cent });
178
203
  return id;
179
204
  };
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file