@glissade/sfx 0.5.0-pre.2 → 0.5.0-pre.4

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/dist/index.d.ts CHANGED
@@ -118,5 +118,39 @@ interface SfxClipOptions {
118
118
  * re-evaluation out of order never drifts. Mirrors buildNarrationClips.
119
119
  */
120
120
  declare function buildSfxClips(hits: readonly SfxHit[], source: SfxSource, opts?: SfxClipOptions): AudioClip[];
121
+ /** One keystroke to sonify — the structural shape both the typewriter's
122
+ * `EditMark` and a monotonic `RevealMark` satisfy. `kind` lets a backspace
123
+ * take a different sample; absent, it's treated as an insert. */
124
+ interface KeystrokeMark {
125
+ time: number;
126
+ grapheme: string;
127
+ kind?: 'insert' | 'delete';
128
+ }
129
+ interface KeystrokeOptions extends SfxClipOptions {
130
+ /** voice for inserts (a typed char); default 'type' */
131
+ insertVoice?: string;
132
+ /** voice for deletes (a backspace); default = insertVoice */
133
+ deleteVoice?: string;
134
+ /**
135
+ * Round-robin pool for inserts (overrides insertVoice) — a real keyboard
136
+ * foley pack rotates several keypress recordings so the typing doesn't sound
137
+ * looped. The per-keystroke pick is index-seeded (deterministic).
138
+ */
139
+ insertVoices?: readonly string[];
140
+ /** round-robin pool for deletes (overrides deleteVoice; default = the insert pool) */
141
+ deleteVoices?: readonly string[];
142
+ /** graphemes to NOT click; default whitespace (space, tab, newline) */
143
+ skip?: (grapheme: string) => boolean;
144
+ }
145
+ /**
146
+ * One AudioClip per keystroke, placed at its time — the SFX side of the
147
+ * typewriter, the analogue of buildNarrationClips. Consumes the typewriter's
148
+ * `marks` (insert + delete) or a monotonic `revealSchedule` (inserts only).
149
+ * Char-class policy lives HERE: whitespace is skipped by default, a backspace
150
+ * can take a distinct voice, and a multi-sample pool round-robins (index-seeded)
151
+ * for non-looping foley. The marks stay neutral data; everything is a pure
152
+ * function of position.
153
+ */
154
+ declare function keystrokeClips(marks: readonly KeystrokeMark[], source: SfxSource, opts?: KeystrokeOptions): AudioClip[];
121
155
  //#endregion
122
- export { PRESETS, SFX_PRESETS, SfxClipOptions, SfxError, SfxHit, SfxPreset, SfxSamplePack, SfxSource, SfxVoiceRef, SfxWaveform, SfxrParams, buildSfxClips, encodeWavMono, hashStr, renderSfxAssets, renderSfxr, samplePackSource, sfxFileName, sfxrSource };
156
+ export { KeystrokeMark, KeystrokeOptions, PRESETS, SFX_PRESETS, SfxClipOptions, SfxError, SfxHit, SfxPreset, SfxSamplePack, SfxSource, SfxVoiceRef, SfxWaveform, SfxrParams, buildSfxClips, encodeWavMono, hashStr, keystrokeClips, renderSfxAssets, renderSfxr, samplePackSource, sfxFileName, sfxrSource };
package/dist/index.js CHANGED
@@ -289,5 +289,33 @@ function buildSfxClips(hits, source, opts = {}) {
289
289
  return clip;
290
290
  });
291
291
  }
292
+ const isWhitespace = (g) => /^\s+$/.test(g);
293
+ /**
294
+ * One AudioClip per keystroke, placed at its time — the SFX side of the
295
+ * typewriter, the analogue of buildNarrationClips. Consumes the typewriter's
296
+ * `marks` (insert + delete) or a monotonic `revealSchedule` (inserts only).
297
+ * Char-class policy lives HERE: whitespace is skipped by default, a backspace
298
+ * can take a distinct voice, and a multi-sample pool round-robins (index-seeded)
299
+ * for non-looping foley. The marks stay neutral data; everything is a pure
300
+ * function of position.
301
+ */
302
+ function keystrokeClips(marks, source, opts = {}) {
303
+ const insertPool = opts.insertVoices ?? [opts.insertVoice ?? "type"];
304
+ const deletePool = opts.deleteVoices ?? (opts.deleteVoice ? [opts.deleteVoice] : insertPool);
305
+ const skip = opts.skip ?? isWhitespace;
306
+ const seed = opts.seed ?? 0;
307
+ return buildSfxClips(marks.filter((m) => !skip(m.grapheme)).map((m, index) => {
308
+ const pool = m.kind === "delete" ? deletePool : insertPool;
309
+ let voice = pool[0];
310
+ if (pool.length > 1) {
311
+ const r = random((seed ^ hashStr("keystroke") ^ Math.imul(index + 1, 2246822507)) >>> 0)();
312
+ voice = pool[Math.min(pool.length - 1, Math.floor(r * pool.length))];
313
+ }
314
+ return {
315
+ voice,
316
+ at: m.time
317
+ };
318
+ }), source, opts);
319
+ }
292
320
  //#endregion
293
- export { PRESETS, SFX_PRESETS, SfxError, buildSfxClips, encodeWavMono, hashStr, renderSfxAssets, renderSfxr, samplePackSource, sfxFileName, sfxrSource };
321
+ export { PRESETS, SFX_PRESETS, SfxError, buildSfxClips, encodeWavMono, hashStr, keystrokeClips, renderSfxAssets, renderSfxr, samplePackSource, sfxFileName, sfxrSource };
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@glissade/sfx",
3
- "version": "0.5.0-pre.2",
3
+ "version": "0.5.0-pre.4",
4
4
  "description": "glissade sound effects: a clean-room procedural (sfxr-style) synth, license-checked sample packs, and deterministic index-seeded variation — committed WAVs feed the same offline FFmpeg mix as narration and music.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
8
11
  "exports": {
9
12
  ".": {
10
13
  "types": "./dist/index.d.ts",
@@ -15,7 +18,7 @@
15
18
  "dist"
16
19
  ],
17
20
  "dependencies": {
18
- "@glissade/core": "0.5.0-pre.2"
21
+ "@glissade/core": "0.5.0-pre.4"
19
22
  },
20
23
  "repository": {
21
24
  "type": "git",