@maccesar/titools 5.2.4 → 5.3.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 (44) hide show
  1. package/README.md +27 -32
  2. package/agents/ti-pro.md +1 -1
  3. package/commands/ti-audit.md +1 -1
  4. package/docs/examples.md +15 -8
  5. package/package.json +1 -1
  6. package/skills/ti-synthengine/SKILL.md +62 -92
  7. package/skills/ti-synthengine/assets/example/README.md +51 -54
  8. package/skills/ti-synthengine/assets/example/app.js +96 -57
  9. package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +39 -40
  10. package/skills/ti-synthengine/assets/example/lib/soundPreset.js +34 -0
  11. package/skills/ti-synthengine/assets/index.d.ts +226 -0
  12. package/skills/ti-synthengine/references/advanced-examples.md +12 -12
  13. package/skills/ti-synthengine/references/api.md +125 -33
  14. package/skills/ti-synthengine/references/audio-engine.md +24 -59
  15. package/skills/ti-synthengine/references/examples.md +64 -7
  16. package/skills/ti-synthengine/references/guide.md +69 -63
  17. package/skills/ti-synthengine/references/instruments-and-effects.md +34 -27
  18. package/skills/ti-synthengine/references/native-variation.md +92 -51
  19. package/skills/ti-synthengine/references/recipes-interaction.md +26 -19
  20. package/skills/ti-synthengine/references/recipes.md +19 -15
  21. package/skills/ti-synthengine/references/recommendations.md +14 -14
  22. package/skills/ti-synthengine/references/sound-api.md +197 -0
  23. package/skills/ti-synthengine/references/sound-design.md +11 -11
  24. package/skills/ti-synthengine/references/source-notes.md +62 -147
  25. package/skills/ti-synthengine/references/troubleshooting.md +22 -24
  26. package/skills/ti-synthengine/references/upgrade-2.0.0.md +40 -0
  27. package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
  28. package/skills/ti-synthengine/references/android-sim80-correction.json +0 -162
  29. package/skills/ti-synthengine/references/audio-pipeline-validation.md +0 -19
  30. package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
  31. package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
  32. package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
  33. package/skills/ti-synthengine/references/frontier-physical-validation.json +0 -1918
  34. package/skills/ti-synthengine/references/native-variation-proposal.md +0 -182
  35. package/skills/ti-synthengine/references/native-variation-validation.md +0 -63
  36. package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
  37. package/skills/ti-synthengine/references/performance-review.json +0 -1146
  38. package/skills/ti-synthengine/references/physical-validation.json +0 -1457
  39. package/skills/ti-synthengine/references/source-snapshot.json +0 -463
  40. package/skills/ti-synthengine/references/stats-performance.json +0 -2992
  41. package/skills/ti-synthengine/references/stats-performance.md +0 -82
  42. package/skills/ti-synthengine/references/studio-validation.json +0 -6202
  43. package/skills/ti-synthengine/references/unified-bypass-performance.json +0 -22425
  44. package/skills/ti-synthengine/references/unified-validation.json +0 -314
@@ -1,27 +1,66 @@
1
- > Source snapshot: official TiSynthEngine documentation at commit `b07c97c762be5905f132cecf33e9d60ea0c21316` (2026-11-02). Published 1.0.2; local 1.1.0 candidates; public API 1.0.0. Upstream `documentation/native-variation.md` SHA-256: `30bbf837a163372aa0d369c6c8e267dc8fc2d3349e8ae30c51d13d20c88cb7eb`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/native-variation.md` SHA-256: `6cfd65b0741dea79d37bb5eee14d1bcb98445ee6b5db14a8c00e3ccfd00a5190`.
2
2
 
3
3
  # Native per-attack variation
4
4
 
5
5
  `variation` lets the engine choose bounded parameter values when it admits an attack. Repeated shots can have different pitch, noise color or filter settings without JavaScript randomization. Each voice keeps the chosen values for its lifetime; existing envelopes, sweeps and LFOs continue from that starting configuration.
6
6
 
7
- Android and iOS support this dictionary in the local 1.1.0 packages, with public API version `1.0.0` and variation schema version `1`. The published 1.0.2 packages do not accept it. The local packages have not been published as a release.
7
+ Android and iOS support this dictionary in TiSynth 2.0.0. The variation data schema is `1`. Store it in `sound.variation`; a pattern step can supply `steps[i].sound.variation`.
8
8
 
9
- ## Select the local package
9
+ <!-- TOC-START -->
10
+ ## Contents
10
11
 
11
- Install the matching locally built Android and iOS 1.1.0 archives in the app and select them in `tiapp.xml`:
12
+ - [Reuse a sound with variation](#reuse-a-sound-with-variation)
13
+ - [Select the module version](#select-the-module-version)
14
+ - [Detect support and start playback](#detect-support-and-start-playback)
15
+ - [Supported locations](#supported-locations)
16
+ - [Dictionary reference](#dictionary-reference)
17
+ - [What amount means](#what-amount-means)
18
+ - [Repeated independent effects](#repeated-independent-effects)
19
+ - [Chords and application gain](#chords-and-application-gain)
20
+ - [Sustained voices and explicit live edits](#sustained-voices-and-explicit-live-edits)
21
+ - [Native loops and dictionary inheritance](#native-loops-and-dictionary-inheritance)
22
+ - [Seeds and reproducibility](#seeds-and-reproducibility)
23
+ - [Rejected configurations](#rejected-configurations)
24
+ - [Studio and game integration](#studio-and-game-integration)
25
+ - [Implementation and verification](#implementation-and-verification)
26
+
27
+ <!-- TOC-END -->
28
+
29
+ ## Reuse a sound with variation
30
+
31
+ ```javascript
32
+ var synth = require('ti.synthengine')
33
+ var bell = synth.createSound({
34
+ sound: {
35
+ waveType: synth.getContract().waveTypes.SINE,
36
+ volume: 0.25, attack: 5, release: 120,
37
+ variation: { amount: 0.6, ranges: { volume: [0.18, 0.25], pitchCents: [-8, 8] } }
38
+ },
39
+ playback: { type: 'tone', notes: ['A4'], duration: 400 }
40
+ })
41
+
42
+ bell.play()
43
+ bell.play({ notes: ['C5'] })
44
+ ```
45
+
46
+ Call playback after engine startup. The detailed native dictionaries below explain source restrictions and inheritance. For Sound patterns, root variation belongs in `sound.variation` and a step replacement belongs in `steps[i].sound.variation`.
47
+
48
+ ## Select the module version
49
+
50
+ Install the matching Android and iOS 2.0.0 archives in the app and select them in `tiapp.xml`:
12
51
 
13
52
  ```xml
14
53
  <modules>
15
- <module platform="android" version="1.1.0">ti.synthengine</module>
16
- <module platform="iphone" version="1.1.0">ti.synthengine</module>
54
+ <module platform="android" version="2.0.0">ti.synthengine</module>
55
+ <module platform="iphone" version="2.0.0">ti.synthengine</module>
17
56
  </modules>
18
57
  ```
19
58
 
20
- Rebuild and reinstall the application after replacing native modules. Copying module files alone does not update an installed app. Keep the published 1.0.2 archives separate; the new property does not change the reported public API version.
59
+ Rebuild and reinstall the application after replacing native modules. Copying module files alone does not update an installed app. Keep historical released archives separate. The installed module version is available as `getDefaults().version`.
21
60
 
22
61
  ## Detect support and start playback
23
62
 
24
- Check both metadata fields. API version alone does not identify variation support. Omit the property completely when either announcement is missing, including when the requested amount is zero.
63
+ Check both capability/schema fields, even when the module version matches the one you expect. Omit the property completely if either field is missing, including when the requested amount is zero.
25
64
 
26
65
  ```javascript
27
66
  var synth = require('ti.synthengine')
@@ -35,19 +74,21 @@ synth.start({ maxVoices: 8, maxSequences: 1 })
35
74
  synth.setVolume({ volume: 0.6 })
36
75
  ```
37
76
 
38
- The following examples use those variables. Handle rejected publications through the normal [return values and diagnostics](api.md#return-values). For rapid effects, size the shared voice pool for overlapping durations and releases.
77
+ The examples below use those variables. Handle rejected publications through the normal [return values and diagnostics](api.md#return-values). For rapid effects, size the shared voice pool for overlapping durations and releases.
39
78
 
40
79
  ## Supported locations
41
80
 
42
- | Call or score field | Behavior |
43
- | --- | --- |
44
- | `playTone({ variation, ... })` | Chooses once for an admitted timed tone |
45
- | `playChord({ variation, ... })` | Admits the whole chord atomically and chooses independent values for each voice |
46
- | `noteOn({ variation, ... })` | Chooses at the held voice's attack; sustain and release keep that configuration |
47
- | `playSequence({ voice: { variation, ... }, ... })` | Supplies the default for sounding steps on every native loop |
48
- | `steps[i].overrides.variation` | Replaces the entire dictionary inherited from the sequence voice |
49
- | `updateSequence({ voice, steps, ... })` | Changes future attacks when the revision applies |
50
- | `updateVoice({ variation, ... })` | Rejected; live edits must name the parameters they change |
81
+ | Call or score field | Behavior |
82
+ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
83
+ | `playTone({ variation, ... })` | Chooses once for an admitted timed tone |
84
+ | `repeatTone({ variation, intervalMs, ... })` | Retains a stream/counter and chooses once per admitted repeated finite attack |
85
+ | `updateTone({ toneId, variation, ... })` | Replaces the whole future-attack dictionary; amount zero disables; seed change resets the stream |
86
+ | `playChord({ variation, ... })` | Admits the whole chord atomically and chooses independent values for each voice |
87
+ | `noteOn({ variation, ... })` | Chooses at the held voice's attack; sustain and release keep that configuration |
88
+ | `playSequence({ voice: { variation, ... }, ... })` | Supplies the default for sounding steps on every native loop |
89
+ | `steps[i].overrides.variation` | Replaces the entire dictionary inherited from the sequence voice |
90
+ | `updateSequence({ voice, steps, ... })` | Changes future attacks when the revision applies |
91
+ | `updateNote({ variation, ... })` | Rejected; live edits must name the parameters they change |
51
92
 
52
93
  Schema 1 varies `volume`, `filterHz`, `filterQ`, `fmDepth`, `noiseRate`, `lowPassHz`, `pulseWidth` and `pitchCents`. Waveform, table contents, ADSR, duration, cadence, FM ratio, routing, priority and voice count retain their authored values.
53
94
 
@@ -64,26 +105,26 @@ var variation = {
64
105
  }
65
106
  ```
66
107
 
67
- | Field | Default | Accepted values |
68
- | --- | --- | --- |
69
- | `amount` | `1` | Finite number from `0` to `1` |
70
- | `seed` | Omitted; engine selects a native stream | Integer from `0` to `4294967295`; `0` is an explicit deterministic seed |
71
- | `ranges` | Empty dictionary | Any subset of the eight range names below |
108
+ | Field | Default | Accepted values |
109
+ | -------- | --------------------------------------- | ----------------------------------------------------------------------- |
110
+ | `amount` | `1` | Finite number from `0` to `1` |
111
+ | `seed` | Omitted; engine selects a native stream | Integer from `0` to `4294967295`; `0` is an explicit deterministic seed |
112
+ | `ranges` | Empty dictionary | Any subset of the eight range names below |
72
113
 
73
114
  Only these keys are accepted. A range must contain exactly two finite numbers in ascending order. Equal endpoints are valid. Strings, booleans, `null`, `NaN`, infinity, reversed bounds and unknown names are rejected. An empty range dictionary makes no parameter changes and consumes no variation event.
74
115
 
75
116
  The frequency ceiling is the active engine's `limits.frequency.max` value from `getDefaults()`. The maximum is `sampleRate * 0.45`, with a 19,845 Hz fallback before startup. Read `getDefaults()` again after `start()` when preparing device-specific ranges.
76
117
 
77
- | Range | Inclusive validation bounds | Required source or setting |
78
- | --- | --- | --- |
79
- | `volume` | `0` to `2` | Any source; existing chord compensation follows selection |
80
- | `filterHz` | `20` Hz to the active frequency ceiling | Enabled resonant filter: `lowpass`, `highpass` or `bandpass` |
81
- | `filterQ` | `0.5` to `10` | Enabled resonant filter |
82
- | `fmDepth` | `0` to `10` | `SINE`, with the normal FM ratio/depth rules |
83
- | `noiseRate` | `1` Hz to the active frequency ceiling | `NOISE` with authored `noiseRate >= 1` |
84
- | `lowPassHz` | `20` Hz to the active frequency ceiling | One-pole low-pass already enabled with authored `lowPassHz >= 20` |
85
- | `pulseWidth` | `0.05` to `0.95` | `SQUARE` |
86
- | `pitchCents` | `-1200` to `1200` cents | Resulting tuned frequencies and active pitch endpoints must remain within engine limits |
118
+ | Range | Inclusive validation bounds | Required source or setting |
119
+ | ------------ | --------------------------------------- | --------------------------------------------------------------------------------------- |
120
+ | `volume` | `0` to `2` | Any source; existing chord compensation follows selection |
121
+ | `filterHz` | `20` Hz to the active frequency ceiling | Enabled resonant filter: `lowpass`, `highpass` or `bandpass` |
122
+ | `filterQ` | `0.5` to `10` | Enabled resonant filter |
123
+ | `fmDepth` | `0` to `10` | `SINE`, with the normal FM ratio/depth rules |
124
+ | `noiseRate` | `1` Hz to the active frequency ceiling | `NOISE` with authored `noiseRate >= 1` |
125
+ | `lowPassHz` | `20` Hz to the active frequency ceiling | One-pole low-pass already enabled with authored `lowPassHz >= 20` |
126
+ | `pulseWidth` | `0.05` to `0.95` | `SQUARE` |
127
+ | `pitchCents` | `-1200` to `1200` cents | Resulting tuned frequencies and active pitch endpoints must remain within engine limits |
87
128
 
88
129
  `filterHz` controls the resonant filter; `lowPassHz` controls the separate one-pole low-pass. A range cannot enable a bypassed filter or turn white noise into colored noise. Pitch cents change oscillator pitch; use `noiseRate` to change a noise source's clock. A zero-depth sine can start with an active FM-depth range when the remaining FM configuration is valid.
89
130
 
@@ -112,7 +153,7 @@ Validation checks the complete reachable configuration before admission, includi
112
153
 
113
154
  ## Repeated independent effects
114
155
 
115
- Leave out `seed` to obtain successive native selections. Call `playTone` for each actual shot; keep the application's existing firing clock.
156
+ Leave out `seed` to obtain successive native selections. Call `playTone` for each actual gameplay event. For periodic repetition of that finite recipe, call `repeatTone` with `intervalMs` once and retain its `toneId`; the renderer then owns the repeated deadlines. Standalone calls, including intentionally timed finite bursts, remain supported and return Boolean acceptance. `updateTone` changes future attacks, preserving the current deadline and variation counter. A changed seed restarts the stream, and dropped attacks consume no selection.
116
157
 
117
158
  ```javascript
118
159
  function fireLaser() {
@@ -131,11 +172,11 @@ function fireLaser() {
131
172
  fireLaser()
132
173
  ```
133
174
 
134
- The engine samples after admission and before initializing filters, smoothing controls and trajectories. Rejected attacks do not consume a selection. Existing release tails, capacity policy and voice replacement still apply.
175
+ The engine samples after admitting an attack, before initializing its filters, smoothing controls and trajectories. Rejected attacks consume no selection. Release tails, capacity policy and voice replacement remain unchanged.
135
176
 
136
177
  ## Chords and application gain
137
178
 
138
- One chord is one atomic publication. Each voice uses its position within that attack to obtain independent field samples; it does not reuse the first voice's result. If the chord cannot be admitted, none of its voices starts and no event is consumed.
179
+ A chord is published atomically. Each voice's position within the attack determines its independent field samples, so later voices do not reuse the first voice's result. If the chord cannot be admitted, none of its voices starts and no event is consumed.
139
180
 
140
181
  Scale a volume range with the same application gain as the authored volume. Let the engine apply its existing chord compensation once.
141
182
 
@@ -171,11 +212,11 @@ if (supportsVariation) held.variation = {
171
212
  ranges: { fmDepth: [1.1, 1.7], pitchCents: [-10, 10] }
172
213
  }
173
214
  synth.noteOn(held)
174
- synth.updateVoice({ voiceId: 71, volume: 0.12 })
215
+ synth.updateNote({ voiceId: 71, volume: 0.12 })
175
216
  synth.noteOff({ voiceId: 71 })
176
217
  ```
177
218
 
178
- `updateVoice` rejects `variation`. To obtain another varied attack, release the owned voice and start another through the normal ownership path. A sustained motor stays steady under a single held attack. Release owned IDs on cancellation, pause and teardown; keep the usual bounded handling of rejected releases.
219
+ `updateNote` rejects `variation`. To obtain another varied attack, release the owned voice and start another through the normal ownership path. A sustained motor stays steady under a single held attack. Release owned IDs on cancellation, pause and teardown; keep the usual bounded handling of rejected releases.
179
220
 
180
221
  ## Native loops and dictionary inheritance
181
222
 
@@ -232,22 +273,22 @@ if (supportsVariation && sequenceId > 0) {
232
273
  }
233
274
  ```
234
275
 
235
- Stop the owned sequence through the normal lifecycle when it is no longer needed. Stop/new play starts a new stream.
276
+ Stop the owned sequence during normal lifecycle cleanup when it is no longer needed. Stopping and starting playback again creates a new stream.
236
277
 
237
278
  ## Seeds and reproducibility
238
279
 
239
- | Usage | Reproducibility |
240
- | --- | --- |
280
+ | Usage | Reproducibility |
281
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------------- |
241
282
  | Repeated standalone calls with the same recipe and explicit seed | Reproduce the selected parameter values, starting at event zero each time |
242
- | Standalone calls without a seed | Use successive native selections |
243
- | Seeded native loop | Advances its attack counter across loop iterations |
244
- | Revision with unchanged seed identity | Retains that counter |
245
- | Revision that changes the root seed | Resets the counter when the revision applies |
246
- | Stop followed by a new sequence | Starts a new counter |
283
+ | Standalone calls without a seed | Use successive native selections |
284
+ | Seeded native loop | Advances its attack counter across loop iterations |
285
+ | Revision with unchanged seed identity | Retains that counter |
286
+ | Revision that changes the root seed | Resets the counter when the revision applies |
287
+ | Stop followed by a new sequence | Starts a new counter |
247
288
 
248
289
  Seed identity includes whether the root seed was declared and its value, plus the sorted multiset of explicit step seed declarations. Moving steps, swapping existing declarations or inserting inherited rests preserves that identity. Adding, removing or changing an explicit step seed declaration changes it and resets the counter. Prefer a root seed for comparisons of a complete score.
249
290
 
250
- The counter is an unsigned 64-bit value and wraps modulo `2^64`. Admitted attack, voice ordinal and field slot determine each sample through the shared integer helper. Field slots are stable, so changing dictionary property order or adding an unrelated range does not shift existing field samples. Variation state is separate from the noise generator. A reproducible seed promises selected parameters, not identical noise PCM or identical DSP output across platforms.
291
+ The counter is an unsigned 64-bit value and wraps modulo `2^64`. Admitted attack, voice ordinal and field slot determine each sample through the shared integer helper. Field slots are stable, so changing dictionary property order or adding an unrelated range does not shift existing field samples. Variation state is separate from the noise generator. A reproducible seed reproduces selected parameters. It does not promise identical noise PCM or identical DSP output across platforms.
251
292
 
252
293
  ## Rejected configurations
253
294
 
@@ -264,7 +305,7 @@ synth.playTone({ variation: {
264
305
  } })
265
306
 
266
307
  // Live updates do not accept an attack-only dictionary.
267
- synth.updateVoice({ voiceId: 71,
308
+ synth.updateNote({ voiceId: 71,
268
309
  variation: { ranges: { pitchCents: [-10, 10] } } })
269
310
  ```
270
311
 
@@ -274,10 +315,10 @@ Also check pitch corners near 1 Hz or the reported ceiling. A valid base pitch c
274
315
 
275
316
  Studio calls this control Variantes. Its displayed percentage maps linearly: 25% sends native amount `0.25`, 50% sends `0.5`, and 100% sends `1`. Studio prepares broader ranges for its editor. The games retain their authored range profiles.
276
317
 
277
- Frontier automatically applies authored ranges only to its eligible destruction sounds; Guardian applies them only to its two lasers. Both require the capability/schema pair and keep their existing recipes on older modules. Studio allows any source to opt in through compatible parameter ranges. See [Studio's control and preset guide](https://github.com/macCesar/TiSynth-Studio/blob/main/documentation/variation.md); the native contract in this document does not depend on a consumer app.
318
+ Frontier automatically applies authored ranges only to its eligible destruction sounds; Guardian applies them only to its two lasers. Both require the capability/schema pair. Studio allows any source to opt in through compatible parameter ranges. See [Studio's control and preset guide](https://github.com/macCesar/TiSynth-Studio/blob/main/documentation/variation.md); the native contract in this document does not depend on a consumer app.
278
319
 
279
320
  ## Implementation and verification
280
321
 
281
- Both renderers use [`shared/synth_variation.h`](https://github.com/macCesar/TiSynthEngine/blob/433d7884800f70fb4803dbfe2e0e869cd5411889/shared/synth_variation.h): stable schema slots, integer-defined SplitMix64 finalization and exact uint32-to-uniform conversion. Parsing prepares a fixed 144-byte configuration outside the audio callback. The callback performs bounded arithmetic without allocation, locks, JavaScript calls or entropy services.
322
+ Both renderers use [`shared/synth_variation.h`](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/shared/synth_variation.h): stable schema slots, integer-defined SplitMix64 finalization and exact uint32-to-uniform conversion. Parsing prepares a fixed 144-byte configuration outside the audio callback. The callback performs bounded arithmetic without allocation, locks, JavaScript calls or entropy services.
282
323
 
283
- Focused checks cover known vectors, both native validators, attack admission, chord ordinals, sustained stability, loops, revisions, rejection and old-renderer compatibility with controlled noise initialization. [Technical verification](native-variation-validation.md) records the measured memory/callback cost and its limits. Host results do not certify physical latency or listening quality on every device.
324
+ Focused checks cover known vectors, both native validators, attack admission, chord ordinals, sustained stability, loops, revisions, rejection and old-renderer compatibility with controlled noise initialization. [Technical verification](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/native-variation-validation.md) records the measured memory/callback cost and its limits. Host results do not certify physical latency or listening quality on every device.
@@ -1,10 +1,16 @@
1
1
  # Instrument, polyphony and expressive recipes
2
2
 
3
- These curated TiTools recipes use the same running engine and runtime `WAVE`
4
- constants as [the arcade recipes](recipes.md). Keep one owner, check native
5
- acceptance and use their [verification checklist](recipes.md#recipe-verification-checklist).
6
- The expressive envelope/filter/FM ranges are starting points verified against
7
- the public contract, not device-auditioned presets. Listen before shipping.
3
+ These TiTools recipes use the supported direct interface in 2.0.0 and share
4
+ the running engine and runtime `WAVE` constants from
5
+ [the arcade recipes](recipes.md). Keep one engine owner, check native acceptance
6
+ and follow the [verification checklist](recipes.md#recipe-verification-checklist).
7
+
8
+ The envelope, filter and FM ranges are starting points checked against the
9
+ public contract. They have not been auditioned on a device, so listen before
10
+ shipping.
11
+
12
+ For new reusable instruments, read [Sound definitions and controls](sound-api.md).
13
+ Keep these recipes' gains, pitch arrays, envelopes and ownership when adapting them.
8
14
 
9
15
  ```javascript
10
16
  const synth = require('ti.synthengine')
@@ -114,9 +120,8 @@ In a Titanium view:
114
120
  - call `gesture.finish()` on both `touchend` and `touchcancel`;
115
121
  - keep visual pressed-state counts separate if multiple touches are supported.
116
122
 
117
- The audio release may continue after the finger leaves a bar. That is desired
118
- for a xylophone. `stopAll()` is a global panic: it also cancels every native
119
- sequence and invalidates shared effect histories. Use an owner's
123
+ For a xylophone, let the audio release continue after the finger leaves a bar.
124
+ `stopAll()` is a global panic: it also cancels every native sequence and invalidates shared effect histories. Use an owner's
120
125
  `stopSequence({ sequenceId })` when only that sequence should stop.
121
126
 
122
127
  ## Four-pad memory cues
@@ -169,8 +174,8 @@ function playMemoryBank (bankName) {
169
174
 
170
175
  ### Controlled timbre comparison
171
176
 
172
- For an honest A/B test, hold pitches, duration, envelope and pan constant.
173
- Change only waveform and its comparison level. Never pass a profile object
177
+ For an A/B test, hold pitches, duration, envelope and pan constant. Change
178
+ only the waveform and its comparison level. Never pass a profile object
174
179
  containing labels or other app metadata directly to `playChord()` because its
175
180
  options dictionary is closed.
176
181
 
@@ -202,8 +207,8 @@ function auditionTimbre (profileName) {
202
207
 
203
208
  ### Convert MIDI pitch to hertz
204
209
 
205
- The standard A4 = 440 Hz formula makes it easy to build interval recipes
206
- without embedding a long frequency table.
210
+ Use the standard A4 = 440 Hz formula to build interval recipes without
211
+ embedding a long frequency table.
207
212
 
208
213
  ```javascript
209
214
  function midiToHz (midi) {
@@ -337,7 +342,9 @@ This repeated chord uses one centered native sequence. The one-off layered chord
337
342
 
338
343
  ### Cancellable repetition
339
344
 
340
- Every repeating sound has an identified native owner. Repeated start requests coalesce until the matching retirement event frees its slot. Stop its sequence ID and cancel pending restarts when its window closes; no JavaScript timer schedules subsequent notes.
345
+ The chord pulse above belongs to one identified sequence owner. Repeated start requests coalesce until the matching `sequenceend` event retires it and frees the slot. When its window closes, stop that sequence ID and cancel pending restarts. The native sequence schedules subsequent chord attacks.
346
+
347
+ For direct repetition of an original finite tone, use `repeatTone({ intervalMs, ...recipe })`, retain its positive tone ID, edit future attacks with `updateTone`, and cancel with `stopTone`. Each independent layer retains its own tone ID and overlap budget. `stopTone` preserves admitted finite attacks; `stopSequence` releases its sequence's voices. Tone IDs have no completion event, so the sequence retirement/restart procedure above does not apply to them. Independent event calls and intentionally timed finite JavaScript bursts remain supported; cancel any app-owned timers at teardown. See [the repetition contract](api.md#finite-tone-repetition) and [complete effect fidelity](instruments-and-effects.md#repeated-firing-uses-the-native-clock).
341
348
 
342
349
  ```javascript
343
350
  function cleanupAudio () {
@@ -354,9 +361,9 @@ Call `shutdown()` only from the screen or service that owns the shared engine.
354
361
 
355
362
  ### Plucked marimba note
356
363
 
357
- A short attack, a decay that falls to a low sustain and a release that fits
358
- give the struck-and-ring shape of a mallet or plucked string. A sustain below 1
359
- needs a decay, and `attack + decay` must fit inside `duration`.
364
+ A short attack followed by decay to a low sustain gives the strike and ring
365
+ of a mallet or plucked string. Keep the release within the available duration.
366
+ A sustain below 1 needs a decay, and `attack + decay` must fit inside `duration`.
360
367
 
361
368
  ```javascript
362
369
  synth.playTone({
@@ -410,9 +417,9 @@ synth.playTone({
410
417
 
411
418
  ### Electric piano
412
419
 
413
- Frequency modulation with a 1:1 ratio gives a tine-like tone. Letting the
414
- modulation index fall through `fmDepthEnd` makes the note mellow as it rings,
415
- which is what the ear expects from a struck tine. FM needs a sine carrier.
420
+ Frequency modulation with a 1:1 ratio gives a tine-like tone. Reduce the
421
+ modulation index through `fmDepthEnd` so the note mellows as it rings, as a
422
+ struck tine does. FM needs a sine carrier.
416
423
 
417
424
  ```javascript
418
425
  synth.playTone({
@@ -1,10 +1,13 @@
1
1
  # Curated sound and interaction recipes
2
2
 
3
- These recipes are tuned starting points for `ti.synthengine` 1.0.0. They use
4
- only public option keys, keep every explicit envelope valid, and obtain
5
- waveforms from the runtime contract instead of hardcoding integers. Install the
6
- matching official Android/iOS packages and follow the owning-engine
7
- [installation and lifecycle guide](guide.md) before using them.
3
+ These recipes use the supported direct native interface in `ti.synthengine` 2.0.0
4
+ and earlier 1.x modules. For reusable definitions in new code, use
5
+ [Sound objects](sound-api.md) while preserving how each recipe renders.
6
+
7
+ The examples use public option keys and valid explicit envelopes. They read
8
+ waveforms from the runtime contract instead of hardcoding integers. Install
9
+ matching official Android/iOS packages and follow the
10
+ [installation and lifecycle guide](guide.md) to set up the engine owner.
8
11
 
9
12
  Unless a section says otherwise, the snippets assume one owner has already
10
13
  started the engine:
@@ -15,13 +18,13 @@ const contract = synth.getDefaults()
15
18
  const WAVE = contract.waveTypes
16
19
  ```
17
20
 
18
- Treat levels as starting points, not mastering targets. Test on the actual
19
- phone speaker and leave headroom when release tails or independent events can
20
- overlap.
21
+ Use the levels as starting points for listening tests, rather than final
22
+ mastering targets. Test on the actual phone speaker and leave headroom for
23
+ overlapping release tails or independent events.
21
24
 
22
- Instrument interactions, layered chords and expressive synthesis are in
23
- [the interaction recipes](recipes-interaction.md). Their standard synthesis
24
- starting ranges require listening on the target device before shipping.
25
+ For instrument interactions, layered chords and expressive synthesis, see
26
+ [the interaction recipes](recipes-interaction.md). Listen to those starting
27
+ ranges on the target device before shipping.
25
28
 
26
29
  <!-- TOC-START -->
27
30
  ## Contents
@@ -165,8 +168,8 @@ if (!sequenceId) throw new Error('Sequence rejected')
165
168
 
166
169
  ### Fast four-note arpeggio
167
170
 
168
- Very short square notes with a small attack retain the hard cartridge-era
169
- edge while reducing uncontrolled clicks. The 10 ms rests keep the first three
171
+ Very short square notes with a small attack keep the hard cartridge-era
172
+ edge and reduce uncontrolled clicks. The 10 ms rests keep the first three
170
173
  40 ms notes on a 50 ms grid; the final note rings for 80 ms.
171
174
 
172
175
  ```javascript
@@ -403,8 +406,9 @@ if (!sequenceId) throw new Error('Sequence rejected')
403
406
 
404
407
  ### Hovering craft
405
408
 
406
- A triangle voice with strong sine vibrato creates a cyclical engine wobble. Slow stereo travel adds movement on headphones while pitch
407
- remains the primary cue on mono speakers.
409
+ A triangle voice with strong sine vibrato creates a repeating engine wobble.
410
+ Slow stereo travel adds movement on headphones; pitch remains the primary cue
411
+ on mono speakers.
408
412
 
409
413
  ```javascript
410
414
  synth.playTone({
@@ -1,14 +1,14 @@
1
- > Source snapshot: official TiSynthEngine documentation at commit `b07c97c762be5905f132cecf33e9d60ea0c21316` (2026-11-02). Published 1.0.2; local 1.1.0 candidates; public API 1.0.0. Upstream `documentation/recommendations.md` SHA-256: `d3d508efe07fce012baceb44285aa9ab4b850cc5c53a5d14c9b8d193e38ca8fb`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/recommendations.md` SHA-256: `0e7c55830b05ef21bdd1fd125e7d158ea5b598e02618202e65ddd8b8d2044cab`.
2
2
 
3
3
  # Recommendations
4
4
 
5
- The defaults work for ordinary tones and chords. The choices below matter when an app has dense overlap, user-controlled values, demanding speakers or several audio systems sharing one lifecycle.
5
+ The defaults work for ordinary tones and chords. Use the guidance below when an app has dense overlap, user-controlled values, demanding speakers or several audio systems sharing one lifecycle.
6
6
 
7
7
  ## Group simultaneous notes
8
8
 
9
- Use `playChord()` when pitches form one known simultaneous event. The chord is atomic. Protected profiles normalize per-voice source gain by the square root of the event voice count; `raw` bypasses that normalization. Separate `playTone()` calls are appropriate for independent touches and effects, but their start times and gain are independent too.
9
+ Declare `playback.type: 'chord'` when pitches form one known simultaneous event. The chord is atomic. Protected profiles normalize per-voice source gain by the square root of the event voice count; `raw` bypasses that normalization. Separate tone executions suit independent effects, but their start times and gain are independent too.
10
10
 
11
- For keys or pads a user can trigger freely, keep each event around 0.4 to 0.7 until you have listened to the worst overlap. If every event needs gain above 1, consider `balanced` for the session before raising individual notes further.
11
+ For freely triggered keys or pads, keep each event around 0.4 to 0.7 until you have listened to the worst overlap. If every event needs gain above 1, consider `balanced` for the session before raising individual notes further.
12
12
 
13
13
  ## Choose the mixing profile once
14
14
 
@@ -25,7 +25,7 @@ If a phone buzzes under `speakerSafe`, lower the event volume or raise the pitch
25
25
 
26
26
  ## Size `maxVoices` for real overlap
27
27
 
28
- `maxVoices` controls capacity. It does not make the engine louder or quieter.
28
+ `maxVoices` sets voice capacity without changing event gain.
29
29
 
30
30
  | App behavior | Starting value |
31
31
  | ---------------------------------- | -------------: |
@@ -34,7 +34,7 @@ If a phone buzzes under `speakerSafe`, lower the event volume or raise the pitch
34
34
  | Chords, keys or long release tails | 16 to 24 |
35
35
  | Explicit stress testing | 32 |
36
36
 
37
- Count release tails along with buttons pressed at the same instant. Replacement first selects the lowest eligible priority; higher-priority voices are protected. Among that priority, timed-only events steal the slot with the least time remaining; sustained/transport voices prefer a releasing slot, then the oldest attack. The short fade avoids a hard click; the stolen note still ends early.
37
+ Count release tails as well as buttons pressed at the same instant. Replacement first selects the lowest eligible priority; higher-priority voices are protected. Among that priority, timed-only events steal the slot with the least time remaining; sustained/transport voices prefer a releasing slot, then the oldest attack. The short fade avoids a hard click; the stolen note still ends early.
38
38
 
39
39
  ## Keep one lifecycle owner
40
40
 
@@ -67,7 +67,7 @@ The resonant filter, FM and stacked detuned voices change level and spectrum mor
67
67
  - A resonance that keeps reaching the limiter makes the whole mix pump. Lower the volume or the Q before reaching for a different profile.
68
68
  - FM is not band-limited. A high carrier with a large `fmDepth` and `fmRatio` aliases. Keep carriers of bell-like sounds around 500 Hz, or lower the index.
69
69
  - A detuned stack is one voice per copy. Count them when choosing `maxVoices`. Protected profiles divide per-voice source gain by the square root of the event voice count; `raw` does not. Correlated or phase-aligned voices can still reinforce each other, so this is not a guarantee of constant loudness or peaks.
70
- - `sequenceend` reports completion or stopping by sequence identifier. Put contiguous notes into one sequence when sample timing matters; the event crosses JavaScript delivery latency.
70
+ - A pattern control emits `end` on completion or stopping. Put contiguous notes into one pattern when sample timing matters; the event crosses JavaScript delivery latency.
71
71
 
72
72
  ## Validate values at the app boundary
73
73
 
@@ -87,7 +87,7 @@ Do not copy the frequency maximum into application code. It depends on the activ
87
87
 
88
88
  The same rule applies to `noiseRate`, `lowPassHz` and `filterHz`. Validate a custom `waveTable` as 2 to 64 finite values from -1 to 1, and remember that `bitDepth` accepts zero or an integer from 2 to 16. For `detune`, check the resulting pitch too: a shifted pitch that leaves the frequency range is rejected even when the cents value is in range.
89
89
 
90
- Check each method's documented return value during development: Boolean publication acceptance or a positive sequence ID, with 0 rejecting a sequence. Methods that emit structured validation warnings include the method, key and reason in the native log, but some sequence/control rejection paths do not log. Logging the return at the caller helps identify a stale preset or misspelled field without treating silence in the native log as acceptance.
90
+ Check each method's documented return value during development. Sound playback returns finite acceptance, a playback control, or `false` on operational rejection; invalid Sound configurations throw. Direct native methods keep their documented Boolean or identifier returns. Methods that emit structured validation warnings include the method, key and reason in the native log, but some sequence/control rejection paths do not log. Logging the return at the caller helps identify a stale preset or misspelled field. A silent native log does not establish acceptance.
91
91
 
92
92
  ## Test the intended sound
93
93
 
@@ -103,7 +103,7 @@ Exercise the real worst case:
103
103
  - a route change while a note is playing;
104
104
  - screen off, interruption and background transitions if the product depends on them.
105
105
 
106
- An underrun counter only describes transport. A clean counter cannot tell you whether a waveform, pitch or mix is pleasant.
106
+ An underrun counter describes transport; it cannot tell you whether a waveform, pitch or mix is pleasant.
107
107
 
108
108
  ## Binaural placement
109
109
 
@@ -111,13 +111,13 @@ Use the [integration guide](guide.md#integrate-binaural-in-a-2d-app) to convert
111
111
 
112
112
  ## Known limits
113
113
 
114
- - Use `noteOn` with app-supplied IDs for individual release and compatible live updates. Check the required methods before enabling controls; timed events do not return voice handles.
114
+ - Keep the control returned by `sound.noteOn()` or held `play()` for release and compatible live updates. Finite tones and chords return no individual control.
115
115
  - Timed tones and chords last at most 60 seconds including release. Sequence intervals and explicit note lengths use the sequence unit, at most 300 beats or 60,000 ms; sequence ADSR remains in milliseconds and release may extend beyond the interval. Sustained `noteOn` voices last until release, replacement or shutdown.
116
116
  - The module synthesizes waveforms and does not play audio files.
117
117
  - One native transport supports up to four independent sequences, preallocated by `maxSequences`. All share at most 32 voices.
118
- - A single tone or chord has no completion event. Sequences emit `sequenceend` with their identifier and completed/stopped reason.
118
+ - A single finite tone or chord has no completion event. A pattern control emits `end` with its completed/stopped reason.
119
119
  - Voice capacity and mixing profile remain fixed until shutdown.
120
- - Sequences run on the sample clock in beats or milliseconds. `duration` controls intervals; `length` independently controls note duration.
120
+ - Patterns run on the sample clock in beats or milliseconds. Root and step `interval` control onset spacing; `length` independently controls note duration.
121
121
  - `lowPassHz` is a plain one-pole filter. The resonant filter has fixed 12 dB per octave and one exponential sweep; there is no filter envelope.
122
122
  - FM has one modulator and works only on `SINE`.
123
123
  - Binaural positions occupy a front plane. Spatial held voices support smooth live movement; there is no rear positioning, distance attenuation or head tracking. Output is two channels, without a multichannel 2.1/5.1 routing API.
@@ -128,10 +128,10 @@ Use a sample player or a dedicated music engine when a product needs streaming,
128
128
 
129
129
  ## Use effects and held voices deliberately
130
130
 
131
- Use `noteOn` for a sound whose duration follows interaction, such as a key, motor or beam; release its ID on touch end, cancellation, pause or scene teardown. Use `playSequence` for a finite musical cue or repeating grid. Neither needs JavaScript to keep an already accepted note alive. Both share the same 32-slot maximum with collision and interface tones; held notes can be stolen, and an accepted `updateVoice` on a stolen ID is a no-op. Optional priority 0-3 affects stealing; it reserves no voices. There is no voice-finished event.
131
+ Use `sound.noteOn()` for a sound whose duration follows interaction, such as a key, motor or beam; release its note control on touch end, cancellation, pause or scene teardown. Use a pattern definition for a finite musical cue or repeating grid. The native engine sustains notes and schedules attacks. Both share the same 32-voice maximum with collision and interface tones; held notes can be stolen, and updates to a stolen note are accepted as no-ops. Optional priority 0-3 affects stealing without reserving voices. There is no voice-finished event.
132
132
 
133
133
  Keep chorus/delay/reverb mixes modest when dialogue or short game feedback must remain distinct. By default they process all four categories, so enabling reverb for a victory chord also affects simultaneous shots and collisions. Select categories with `applyTo` and assign each voice a `bus`, or use explicit `effectSend` values to choose the processed share. All selected voices share one effects chain. Disabling effects lets tails drain for at most 30 seconds; use panic when immediate history invalidation is required. Start with saturation disabled and compare creative drive at matched levels. Do not use saturation as an automatic substitute for the final limiter.
134
134
 
135
135
  Fixed input/event gain does not decrease when unrelated voices start. If level dips, inspect `maxLimiterReductionDb` and `preLimiterPeakDb`, then consider same-pitch phase cancellation or the device's speaker protection. Eight coherent independent voices can still exceed the limiter threshold; module capacity is not a clean-mix loudness guarantee. See [measurement example](advanced-examples.md#measure-peaks-and-limiter-reduction).
136
136
 
137
- Measure the complete workload on each target device, including held voices, binaural, LFOs, all enabled effects and tails. The [historical statistics benchmark](stats-performance.md) predates these features; the [current technical report](studio-validation.json) includes an unmet full-chain host deadline criterion. Neither is a guarantee of game-frame rate, physical audio deadlines, heat or battery life.
137
+ Measure the complete workload on each target device, including held voices, binaural, LFOs, all enabled effects and tails. The [historical statistics benchmark](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/stats-performance.md) predates these features; the [current technical report](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/docs/validation/studio-validation.json) includes an unmet full-chain host deadline criterion. Neither is a guarantee of game-frame rate, physical audio deadlines, heat or battery life.