@m4l-jweb/bridge 0.6.0 → 0.7.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +48 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m4l-jweb/bridge",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "m4l-jweb: the browser-side bridge connecting a device's web UI to Max for Live.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -188,6 +188,8 @@ export const CHAIN_OUT = {
188
188
  buffer_play: "buffer_play",
189
189
  /** UI -> samples: `buffer_stop` - stop the preview. */
190
190
  buffer_stop: "buffer_stop",
191
+ /** UI -> instrument: `voice_play <pitch> <vel> <durMs> <channels>` - play one poly~ voice. */
192
+ voice_play: "voice_play",
191
193
  } as const;
192
194
 
193
195
  /**
@@ -406,6 +408,52 @@ export function stopSample(): void {
406
408
  outlet(CHAIN_OUT.buffer_stop);
407
409
  }
408
410
 
411
+ /* ------------------------------------------------------------------ *
412
+ * Instrument - the `instrument` chain ([poly~] voices)
413
+ * ------------------------------------------------------------------ */
414
+
415
+ /** One played note, handed to the `instrument` chain's [poly~]. The app times it; Max allocates a voice. */
416
+ export interface Voice {
417
+ /**
418
+ * Which slot's buffer to play, as a 0-based INDEX into the device's `slots` (the
419
+ * order declared in the manifest). The instrument is a keymap of named buffers, so a
420
+ * note names its sample; the app owns the slot order and passes the index.
421
+ */
422
+ slot: number;
423
+ /**
424
+ * Playback RATE. 1 plays the buffer at its recorded pitch; 2 is an octave up, 0.5 an
425
+ * octave down. EXPLICIT, so the app decides whether a note plays a dedicated sample
426
+ * (rate 1) or a repitched one - the chain does no pitch arithmetic.
427
+ */
428
+ rate: number;
429
+ /** 1-127. Scaled to amplitude in the voice. */
430
+ velocity: number;
431
+ /** How long to HOLD the voice, in ms - after which [poly~] frees it for re-use. */
432
+ durationMs: number;
433
+ /**
434
+ * That slot's channel count, as reported by `loadSample`'s `buffer_ready`. A mono
435
+ * buffer (1) is folded to both ears in the voice; pass what you measured, not what
436
+ * you hoped for. Defaults to 2 (no fold).
437
+ */
438
+ channels?: number;
439
+ }
440
+
441
+ /**
442
+ * Play one note through the `instrument` chain's [poly~].
443
+ *
444
+ * Requires the note's slot already loaded (via `loadSample`, the same call the sampler
445
+ * uses). Polyphony and voice-stealing are Max's job: send as many overlapping notes as
446
+ * you like - across any slots - and [poly~] hands each to a free voice, or steals the
447
+ * oldest. A chord is several `playVoice()` calls in the same tick.
448
+ *
449
+ * The note is a control-plane message, not audio: it says which sample, at what rate,
450
+ * how loud and how long to hold the voice, and Max makes the sound sample-accurately.
451
+ * Pass the `channels` you learned from `loadSample` so a mono sample folds to both ears.
452
+ */
453
+ export function playVoice(v: Voice): void {
454
+ outlet(CHAIN_OUT.voice_play, v.slot, v.rate, v.velocity, v.durationMs, v.channels ?? 2);
455
+ }
456
+
409
457
  /**
410
458
  * Max splits messages on commas and semicolons, so any structured payload -
411
459
  * JSON, code, a filesystem path - must be encoded before it crosses the bridge.