@maccesar/titools 5.2.4 → 5.2.5

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 (26) hide show
  1. package/README.md +16 -11
  2. package/docs/examples.md +9 -4
  3. package/package.json +1 -1
  4. package/skills/ti-synthengine/SKILL.md +42 -23
  5. package/skills/ti-synthengine/assets/example/README.md +15 -9
  6. package/skills/ti-synthengine/assets/example/app.js +49 -12
  7. package/skills/ti-synthengine/assets/index.d.ts +106 -0
  8. package/skills/ti-synthengine/references/advanced-examples.md +10 -10
  9. package/skills/ti-synthengine/references/api.md +96 -26
  10. package/skills/ti-synthengine/references/audio-engine.md +17 -10
  11. package/skills/ti-synthengine/references/audio-pipeline-validation.md +1 -1
  12. package/skills/ti-synthengine/references/examples.md +5 -5
  13. package/skills/ti-synthengine/references/guide.md +26 -24
  14. package/skills/ti-synthengine/references/instruments-and-effects.md +31 -24
  15. package/skills/ti-synthengine/references/native-tone-repetition-validation.md +59 -0
  16. package/skills/ti-synthengine/references/native-variation-proposal.md +1 -1
  17. package/skills/ti-synthengine/references/native-variation-validation.md +1 -1
  18. package/skills/ti-synthengine/references/native-variation.md +45 -43
  19. package/skills/ti-synthengine/references/recipes-interaction.md +3 -1
  20. package/skills/ti-synthengine/references/recipes.md +3 -2
  21. package/skills/ti-synthengine/references/recommendations.md +4 -4
  22. package/skills/ti-synthengine/references/sound-design.md +5 -5
  23. package/skills/ti-synthengine/references/source-notes.md +35 -10
  24. package/skills/ti-synthengine/references/source-snapshot.json +168 -89
  25. package/skills/ti-synthengine/references/stats-performance.md +1 -1
  26. package/skills/ti-synthengine/references/troubleshooting.md +15 -15
@@ -1,4 +1,4 @@
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/guide.md` SHA-256: `4da2d85f329a49a8b81a9c671c75051b41788371b2c00990fbd499176623894d`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/guide.md` SHA-256: `0edc2fb8bccb4412d80e00d63a5a631b455d0ede6178c16642e63e37ce3dd77e`.
2
2
 
3
3
  # Guide
4
4
 
@@ -28,29 +28,29 @@ Install TiSynthEngine, start it after layout settles, and release its native aud
28
28
  - [Read engine statistics](#read-engine-statistics)
29
29
  - [Next steps](#next-steps)
30
30
  - [Diagnose mix headroom](#diagnose-mix-headroom)
31
- - [Local native-variation candidate](#local-native-variation-candidate)
31
+ - [Variation support in version 1.1.0](#variation-support-in-version-110)
32
32
 
33
33
  <!-- TOC-END -->
34
34
 
35
35
  ## Install the module
36
36
 
37
- Download both official packages from [Android 1.0.2](https://github.com/macCesar/TiSynthEngine/releases/tag/android-1.0.2) and [iOS 1.0.2](https://github.com/macCesar/TiSynthEngine/releases/tag/ios-1.0.2). For the published 1.0.2 revision, matching archives are produced under `android/dist` and `ios/dist`:
37
+ Download both official packages from [Android 1.1.0](https://github.com/macCesar/TiSynthEngine/releases/tag/android-1.1.0) and [iOS 1.1.0](https://github.com/macCesar/TiSynthEngine/releases/tag/ios-1.1.0). Matching archives are produced under `android/dist` and `ios/dist`:
38
38
 
39
- - `ti.synthengine-android-1.0.2.zip`
40
- - `ti.synthengine-iphone-1.0.2.zip`
39
+ - `ti.synthengine-android-1.1.0.zip`
40
+ - `ti.synthengine-iphone-1.1.0.zip`
41
41
 
42
42
  Put the archives in the Titanium project root or install them through the Titanium CLI. Pin the same version for both platforms:
43
43
 
44
44
  ```xml
45
45
  <modules>
46
- <module platform="android" version="1.0.2">ti.synthengine</module>
47
- <module platform="iphone" version="1.0.2">ti.synthengine</module>
46
+ <module platform="android" version="1.1.0">ti.synthengine</module>
47
+ <module platform="iphone" version="1.1.0">ti.synthengine</module>
48
48
  </modules>
49
49
  ```
50
50
 
51
51
  TiSynthEngine requires Titanium SDK 13.2.0.GA or newer. Android projects must compile against API 35 or newer. The iOS module requires iOS 15 or newer.
52
52
 
53
- These declarations select the published 1.0.2 packages. Current source builds produce unpublished local 1.1.0 candidates; use the separate [candidate setup](#local-native-variation-candidate) below when testing native attack variation.
53
+ These declarations select version 1.1.0. Check the [variation metadata](#variation-support-in-version-110) before using native attack variation.
54
54
 
55
55
  ## Load and verify the contract
56
56
 
@@ -60,8 +60,8 @@ Require the module once in the code that owns audio. Check `apiVersion` before s
60
60
  var synth = require('ti.synthengine')
61
61
  var contract = synth.getDefaults()
62
62
 
63
- if (contract.apiVersion !== '1.0.0') {
64
- throw new Error('TiSynthEngine 1.0.0 is required; found ' + contract.apiVersion)
63
+ if (contract.apiVersion !== '1.1.0') {
64
+ throw new Error('TiSynthEngine 1.1.0 is required; found ' + contract.apiVersion)
65
65
  }
66
66
  ```
67
67
 
@@ -115,30 +115,30 @@ Use one owner for the engine. Start it once, pass a small audio service to the s
115
115
 
116
116
  ## Detect required capabilities
117
117
 
118
- Public API version 1.0.0 includes held voices, sample-clock sequences, stereo effects and two optional LFOs. Require the methods used by each feature before enabling its controls:
118
+ Public API version 1.1.0 includes held voices, sample-clock sequences, stereo effects and two optional LFOs. Require the methods used by each feature before enabling its controls:
119
119
 
120
120
  ```javascript
121
121
  function hasMethods (names) {
122
122
  return names.every(function (name) { return typeof synth[name] === 'function' })
123
123
  }
124
- var supportsHeld = hasMethods(['noteOn', 'noteOff', 'updateVoice'])
124
+ var supportsHeld = hasMethods(['noteOn', 'noteOff', 'updateNote'])
125
125
  var supportsSequence = hasMethods(['playSequence', 'updateSequence', 'stopSequence', 'getSequenceState'])
126
126
  var supportsProcessing = hasMethods(['setMasterProcessing'])
127
127
  ```
128
128
 
129
- Start the engine successfully before publishing held notes, sequence edits or processing. `getDefaults` and feature checks do not open the stream. Install matching packages for Android/iOS and rebuild the application when replacing a native module. Additional LFOs require the documented modulation contract; check acceptance of the complete configuration. Treat rejected configurations as unavailable rather than silently dropping their keys.
129
+ Start the engine successfully before publishing held notes, sequence edits or processing. `getDefaults` and feature checks do not open the stream. Install matching packages for Android/iOS and rebuild the application when replacing a native module. Additional LFOs require the documented modulation contract; check acceptance of the complete configuration. Treat rejected configurations as unavailable. Do not silently drop their keys.
130
130
 
131
131
  ## Hold and edit a voice
132
132
 
133
133
  Use `noteOn({ voiceId, ... })` when duration follows interaction: a piano key, motor, beam, pad or the last chord of a ceremony. Allocate a unique positive integer ID per attack, and release only that ID with `noteOff`. No duration or sweep-end fields are accepted. A chord held this way consists of separate IDs; unlike one `playChord` or sequence chord, separate `noteOn` calls have independent gain and publication.
134
134
 
135
- `updateVoice` changes compatible pitch, volume, cutoff/Q, vibrato, PWM/FM, LFO targets, noise clock, one-pole cutoff, effect send and stereo/spatial position with 10 ms ramps, without restarting oscillator/envelope phase. Source, table, filter type, quantization, ADSR and stereo-versus-spatial mode are fixed at attack. See [complete fields](api.md#updatevoiceoptions). A note that has ended or been stolen accepts updates as a no-op; there is no note-finished event or reserved slot. A `true` return means accepted publication, not proof that the sound is still active.
135
+ `updateNote` changes compatible pitch, volume, cutoff/Q, vibrato, PWM/FM, LFO targets, noise clock, one-pole cutoff, effect send and stereo/spatial position with 10 ms ramps, without restarting oscillator/envelope phase. Source, table, filter type, quantization, ADSR and stereo-versus-spatial mode are fixed at attack. See [complete fields](api.md#updatenoteoptions). A note that has ended or been stolen accepts updates as a no-op; there is no note-finished event or reserved slot. A `true` return means accepted publication, not proof that the sound is still active.
136
136
 
137
137
  Release on touch end and cancellation as well as ordinary key up. Retry a rejected release twice at 16 ms outside the audio callback, then shut down and report failure if publication remains blocked. Cancel retries during teardown. The [held motor example](advanced-examples.md#sustained-game-motor) implements this ownership policy. For pause/owner close, invalidate app scheduling and shut down; require a new start before future notes.
138
138
 
139
139
  ## Choose the musical transport
140
140
 
141
- Use `playSequence` for all successive native sounds. `playTone` and `playChord` handle independent timed events; `noteOn`/`noteOff` hold a sound for interaction. Start with `maxSequences: 1…4` to preallocate independent sequence capacity. All use the same pool of up to 32 voices.
141
+ Use `repeatTone({ intervalMs, ...recipe })` for native repetition of an original finite tone, and `playSequence` for ordered native scores. Retain each positive tone ID, edit with `updateTone` and cancel future attacks with `stopTone`; see [finite-tone repetition](api.md#finite-tone-repetition). `playTone` and `playChord` handle independent timed events; `noteOn`/`noteOff` hold a sound for interaction. Start with `maxSequences: 1…4` to preallocate independent sequence capacity. All use the same pool of up to 32 voices.
142
142
 
143
143
  Store the positive identifier returned by `playSequence`, and include it in each update, stop or state query. 0 rejects publication. Three banks per sequence publish complete edits atomically. Structural edits enter at the next step; swing at the next pair. `setTempo({ bpm })` applies at the next block, preserving beat position. Beat sequences follow it; ms sequences keep fixed times. `stopSequence({ sequenceId })` releases only that owner's voices and leaves effect tails. A retired identifier returns null from `getSequenceState`.
144
144
 
@@ -397,7 +397,7 @@ synth.setVolume({ volume: 0.7 })
397
397
  synth.stopAll()
398
398
  ```
399
399
 
400
- Master volume changes use a linear 10 ms ramp. `stopAll()` stops every native sequence, cancels future attacks, fades active voices and invalidates effect histories. Call `stopSequence({ sequenceId })` to stop only one sequence, or `shutdown()` to close the stream and free resources.
400
+ Master volume changes use a linear 10 ms ramp. `stopAll()` stops every native sequence and finite-tone repetition, cancels future attacks, fades active voices and invalidates effect histories. Call `stopSequence({ sequenceId })` to stop only one sequence, or `shutdown()` to close the stream and free resources.
401
401
 
402
402
  ## Output route changes
403
403
 
@@ -512,7 +512,7 @@ if (!playPositionedTone(explosion, 900, 100, 1000, 800)) {
512
512
 
513
513
  Connect a Titanium switch's `change` event to `audioSettings.binaural = event.value`; connect the two sliders' `change` events to `horizontalPercent` and `heightPercent`. Configure slider minimum 0 and maximum 100, and disable the height slider while the switch is off. Validate restored preference values as finite numbers within those ranges before using them. Do not pass these preference names to `playTone`: that would return `false` with `unsupported_option`.
514
514
 
515
- Supply the current position whenever an effect fires. A projectile launch cue can use the launch position; an impact uses the collision position. Timed event positions remain fixed after submission. An identified sustained voice started spatially can instead change `spatialX`/`spatialY` through `updateVoice`, with 10 ms ramps and retained spatial history. A stereo voice cannot become spatial through an update. Stereo `panEnd` movement cannot be combined with binaural; do not continually retrigger long sounds every frame to imitate movement.
515
+ Supply the current position whenever an effect fires. A projectile launch cue can use the launch position; an impact uses the collision position. Timed event positions remain fixed after submission. An identified sustained voice started spatially can instead change `spatialX`/`spatialY` through `updateNote`, with 10 ms ramps and retained spatial history. A stereo voice cannot become spatial through an update. Stereo `panEnd` movement cannot be combined with binaural; do not continually retrigger long sounds every frame to imitate movement.
516
516
 
517
517
  ### Chords and patterns
518
518
 
@@ -557,7 +557,7 @@ Basic counters are available without enabling optional diagnostics. Reads do not
557
557
  ```javascript
558
558
  var statsTimer = null
559
559
  var statsSupported = typeof synth.getStats === 'function' &&
560
- typeof synth.setStatsEnabled === 'function' && typeof synth.resetStats === 'function'
560
+ typeof synth.enableStats === 'function' && typeof synth.disableStats === 'function' && typeof synth.resetStats === 'function'
561
561
 
562
562
  function readStats () {
563
563
  var stats = synth.getStats()
@@ -568,7 +568,7 @@ function readStats () {
568
568
 
569
569
  function startDiagnostics () {
570
570
  if (!statsSupported || statsTimer !== null) return false
571
- if (!synth.setStatsEnabled(true)) return false
571
+ if (!synth.enableStats()) return false
572
572
  statsTimer = setInterval(readStats, 200)
573
573
  return true
574
574
  }
@@ -576,7 +576,7 @@ function startDiagnostics () {
576
576
  function stopDiagnostics () {
577
577
  if (statsTimer !== null) clearInterval(statsTimer)
578
578
  statsTimer = null
579
- if (statsSupported) synth.setStatsEnabled(false)
579
+ if (statsSupported) synth.disableStats()
580
580
  }
581
581
 
582
582
  // Read basic counters without enabling timing or signal meters:
@@ -601,14 +601,16 @@ See the [complete field contract](api.md#getstats), [measurement limits](stats-p
601
601
 
602
602
  ## Diagnose mix headroom
603
603
 
604
- The current diagnostics add `preLimiterPeakDb` (maximum signal before final limiting, in dBFS) and `maxLimiterReductionDb` (greatest limiter attenuation in dB) accumulated since reset. Unlike the approximately 200 ms window reduction field, brief peaks remain visible between UI polls. Enable measurements, reset before the scenario and keep `null`/absent fields distinct from measured zero. The [measurement example](advanced-examples.md#measure-peaks-and-limiter-reduction) shows opt-in polling and cleanup.
604
+ The diagnostics include `preLimiterPeakDb` (maximum signal before final limiting, in dBFS) and `maxLimiterReductionDb` (greatest limiter attenuation in dB) accumulated since reset. Unlike the approximately 200 ms window reduction field, brief peaks remain visible between UI polls. Enable measurements, reset before the scenario and keep `null`/absent fields distinct from measured zero. The [measurement example](advanced-examples.md#measure-peaks-and-limiter-reduction) shows opt-in polling and cleanup.
605
605
 
606
606
  Fixed event/input gain does not normalize continuously by active voice count. Coherent overlapping notes, effects feedback or resonance can still exceed the ceiling; the limiter then acts on the whole stereo sum. Saturation is a separate creative processor, disabled initially. Physical loudness and small-speaker bass response are not measured by these digital meters.
607
607
 
608
608
 
609
- ## Local native-variation candidate
609
+ <a id="local-native-variation-candidate"></a>
610
+
611
+ ## Variation support in version 1.1.0
610
612
 
611
- Current source builds unpublished local candidates at `android/dist/ti.synthengine-android-1.1.0.zip` and `ios/dist/ti.synthengine-iphone-1.1.0.zip`. Keep them separate from the published 1.0.2 packages. Install both matching candidates in the consumer app and select them explicitly:
613
+ Source builds produce archives at `android/dist/ti.synthengine-android-1.1.0.zip` and `ios/dist/ti.synthengine-iphone-1.1.0.zip`. Keep them separate from the published 1.0.2 packages. Install both matching 1.1.0 packages in the consumer app and select them explicitly:
612
614
 
613
615
  ```xml
614
616
  <modules>
@@ -617,4 +619,4 @@ Current source builds unpublished local candidates at `android/dist/ti.synthengi
617
619
  </modules>
618
620
  ```
619
621
 
620
- Rebuild and reinstall the application after replacing the native modules. Both package lines retain public API `1.0.0`; it does not identify variation support. Send a `variation` dictionary only when `getDefaults().capabilities.variation === true` and `getDefaults().variationSchemaVersion === 1`. If either announcement is absent, omit the property completely, including at amount zero. See [native attack variation](native-variation.md) for accepted locations, bounded ranges and seed behavior. The 1.1.0 candidates have not been published as a release.
622
+ Rebuild and reinstall the application after replacing the native modules. Published 1.0.2 retains API `1.0.0`; version 1.1.0 reports API `1.1.0` and supports native finite-tone repetition. Check the variation announcements directly as well. Send a `variation` dictionary only when `getDefaults().capabilities.variation === true` and `getDefaults().variationSchemaVersion === 1`. If either announcement is absent, omit the property completely, including at amount zero. See [native attack variation](native-variation.md) for accepted locations, bounded ranges and seed behavior.
@@ -1,22 +1,23 @@
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/instruments-and-effects.md` SHA-256: `bcd7c2191bfae2b07688c9dbe19d813fe4eb8fcc45ab38d49b29dff7bdc2981c`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/instruments-and-effects.md` SHA-256: `6c9b96c406c54426a1d37a85b42909a50621cc647d08d37fa89c19283b3acfa0`.
2
2
 
3
3
  # Instruments, sound effects and complete sequences
4
4
 
5
- A playable instrument and a missile sound can use the same oscillator. What makes them behave differently is the complete recipe: pitch, envelope, pitch/filter movement, layers, timing, and how the app starts and ends it. This guide explains which public API to use and how to preserve that recipe.
5
+ A playable instrument and a missile sound can use the same oscillator. Their behavior depends on the complete recipe: pitch, envelope, pitch/filter movement, layers, timing, and how the app starts and ends it. This guide explains which public API to use and how to preserve that recipe.
6
6
 
7
7
  These are application roles built on the same native synthesizer and output chain. They are not different audio engines, quality modes, or guarantees against distortion. An instrument can be noisy or inharmonic; an effect can be a single pitched tone, a chord, or a musical phrase.
8
8
 
9
9
  ## Choose the playback contract
10
10
 
11
- | Intention | Native methods | Who ends the sound? | What to preserve |
12
- | ---------------------------------------------------------------- | ------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- |
13
- | Play a key until the finger lifts | `noteOn`, `updateVoice`, `noteOff` | The app releases its unique voice ID | Timbre, tuning, envelope, contact ownership |
14
- | Fire one finite sound | `playTone` | Native duration and release | Original frequency, endpoints, duration, envelope, gain |
15
- | Fire a simultaneous chord with shared shaping | `playChord` | Native duration and release | All pitches, per-note pan/detune, shared envelope, native chord compensation |
16
- | Play a melody, alarm, layered effect, or repeated firing pattern | `playSequence`, optionally `updateSequence` | Native step clock; `stopSequence` cancels future attacks | Complete ordered steps, rests, lengths, overrides and independent layers |
17
- | Add chorus, delay, reverb or creative saturation | `setMasterProcessing` | The shared processor owns its tails | Routing, sends, input gain, effect settings and output headroom |
11
+ | Intention | Native methods | Who ends the sound? | What to preserve |
12
+ | ----------------------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- |
13
+ | Play a key until the finger lifts | `noteOn`, `updateNote`, `noteOff` | The app releases its unique voice ID | Timbre, tuning, envelope, contact ownership |
14
+ | Fire one finite sound | `playTone` | Native duration and release | Original frequency, endpoints, duration, envelope, gain |
15
+ | Repeat one finite tone, including simultaneous independent layers | `repeatTone({ intervalMs, ... })`, `updateTone`, `stopTone` | Native audio clock; stop cancels future attacks only | Original finite-tone recipe, interval, overlap budget and each tone ID |
16
+ | Fire a simultaneous chord with shared shaping | `playChord` | Native duration and release | All pitches, per-note pan/detune, shared envelope, native chord compensation |
17
+ | Play a melody, alarm, layered effect, or repeated firing pattern | `playSequence`, optionally `updateSequence` | Native step clock; `stopSequence` cancels future attacks | Complete ordered steps, rests, lengths, overrides and independent layers |
18
+ | Add chorus, delay, reverb or creative saturation | `setMasterProcessing` | The shared processor owns its tails | Routing, sends, input gain, effect settings and output headroom |
18
19
 
19
- The last row describes **audio processing effects**. Those processors are different from an authored **sound effect**, such as a laser or an explosion. A laser may use no reverb or delay at all.
20
+ The last row describes audio processing effects. Those processors are different from an authored sound effect, such as a laser or an explosion. A laser may use no reverb or delay at all.
20
21
 
21
22
  All examples below assume successful startup. Use constants from the installed module and check native acceptance:
22
23
 
@@ -53,7 +54,7 @@ function releaseKey(voiceId) {
53
54
 
54
55
  Store the returned ID with that contact. Sliding to another key must release the old owner and admit the new attack; do not release another finger's voice. An app-level Hold control retains selected IDs until explicitly released. If `noteOff` is rejected, keep ownership and follow a bounded retry/error policy; silently forgetting the ID can leave a stuck voice. The [held voice guide](guide.md#hold-and-edit-a-voice) covers lifecycle and compatible live edits.
55
56
 
56
- `noteOn` does not accept timed-event endpoint sweeps such as `frequencyEnd`. A held voice can use its supported vibrato/LFOs and compatible `updateVoice` edits. Removing a timed sweep, replacing the original frequency with the key's pitch, and holding indefinitely changes the sound's meaning: that is a new playable timbre, not an exact audition of the original effect.
57
+ `noteOn` does not accept timed-event endpoint sweeps such as `frequencyEnd`. A held voice can use its supported vibrato/LFOs and compatible `updateNote` edits. Removing a timed sweep, replacing the original frequency with the key's pitch, and holding indefinitely changes the sound's meaning: that is a new playable timbre, not an exact audition of the original effect.
57
58
 
58
59
  A short envelope also does not automatically make a held voice finite. With `sustain: 0`, it can become silent while still requiring release of its ID. Choose a timed event for a self-contained strike when contact ownership is unnecessary.
59
60
 
@@ -88,7 +89,7 @@ This event completes natively. Repeated independent calls can overlap naturally.
88
89
 
89
90
  A keyboard adapter that keeps only the square wave and envelope, starts at C7, and omits `frequencyEnd` will sound very different. Neither result establishes an engine fault: the adapter has selected a different pitch, duration model and motion. An effect audition should execute the whole recipe. Label a separately offered playable adaptation clearly.
90
91
 
91
- Use `playChord` when its contract exactly represents the intended chord. It applies its own chord compensation. Independent layers authored with distinct levels, envelopes or waves are not automatically equivalent to one chord call. Preserve those layers rather than normalizing them again or flattening them into one source.
92
+ Use `playChord` when its contract exactly represents the intended chord. It applies its own chord compensation. Independent layers authored with distinct levels, envelopes or waves are not automatically equivalent to one chord call. Preserve those layers without normalizing them again or flattening them into one source.
92
93
 
93
94
  ## 3. Complete sound sequences and layered effects
94
95
 
@@ -109,9 +110,9 @@ function missileSequence() {
109
110
 
110
111
  `duration` on a sequence step is the interval until the next attack. `length` is how long that note runs before release starts. Both use the sequence's time unit; attack, decay and release remain milliseconds. A note can extend beyond its step and overlap later attacks. In a timed `playTone` call, `duration` includes release. These values cannot be copied interchangeably without accounting for release.
111
112
 
112
- **Timed and sequence routes are not automatically interchangeable.** In the current native implementation, a `playTone` endpoint sweep progresses across the total timed duration, including release. A sequence endpoint sweep progresses across its explicit `length`, then holds the endpoint during release. Thus the timed 350 ms example and the sequence with `length: 300` do not have the same pitch trajectory, even though both finish after 350 ms and use the same endpoints. Sequence voices also use the held envelope's minimum 3 ms attack/release edges; very short or zero timed edges can differ. This matters most for short weapon/noise effects and is not corrected by merely copying the option names.
113
+ Timed and sequence routes are not automatically interchangeable. In the current native implementation, a `playTone` endpoint sweep progresses across the total timed duration, including release. A sequence endpoint sweep progresses across its explicit `length`, then holds the endpoint during release. Thus the timed 350 ms example and the sequence with `length: 300` do not have the same pitch trajectory, even though both finish after 350 ms and use the same endpoints. Sequence voices also use the held envelope's minimum 3 ms attack/release edges; very short or zero timed edges can differ. This matters most for short weapon/noise effects and is not corrected by merely copying the option names.
113
114
 
114
- For consistent manual and repeated auditions, use the same native interpretation for both. Preserve the original attack length when changing only the repetition interval; each new attack may overlap the prior voice. Do not claim exact timed-to-sequence fidelity from field equality or validator acceptance. Preserving the exact timed renderer inside a native repetition would require native support for that rendering contract; it cannot be obtained by a JavaScript adapter that simply renames `duration` to `length`.
115
+ For consistent manual and repeated auditions, use the same native interpretation for both. Preserve the original attack length when changing only the repetition interval; each new attack may overlap the prior voice. Do not claim exact timed-to-sequence fidelity from field equality or validator acceptance. Use API 1.1.0 `repeatTone({ intervalMs, ... })` to repeat the original finite-tone renderer directly. A JavaScript adapter that simply renames `duration` to `length` does not preserve that rendering contract.
115
116
 
116
117
  For a sequence-owned burst, `stopSequence` also releases emitted voices. To stop repetitions while preserving those attacks, publish a finite traversal with `loop: false`, retain its current effect steps and suppress later variant attacks as appropriate. Natural completion leaves emitted voices to their own lengths/releases. Retain the first attack in an update that can arrive before the initial callback. Handle publication rejection explicitly, with bounded control retries if necessary; those retries must not trigger notes or queue new sound requests.
117
118
 
@@ -119,24 +120,30 @@ Use complete step dictionaries and their required overrides. Do not identify var
119
120
 
120
121
  ### Repeated firing uses the native clock
121
122
 
123
+ For effects whose complete recipe consists of finite tone calls, pass each original recipe to `repeatTone` with `intervalMs`. Retain every returned tone ID for independent layers. Compound ordered patterns and chords keep their existing sequence/chord interpretation.
124
+
122
125
  ```javascript
123
126
  function repeatShortShots(intervalMs) {
124
- return synth.playSequence({ timeUnit: 'ms', loop: true,
125
- voice: { waveType: WAVE.SQUARE, attack: 2,
126
- decay: 0, sustain: 1, release: 40, volume: 0.1,
127
- bus: 'effects' },
128
- steps: [{ frequencies: [1380], duration: intervalMs, length: 65,
129
- overrides: { frequencyEnd: 340 } }] })
127
+ return synth.repeatTone({ frequency: 1380, frequencyEnd: 340,
128
+ duration: 105, waveType: WAVE.SQUARE, attack: 2,
129
+ decay: 0, sustain: 1, release: 40, volume: 0.1,
130
+ bus: 'effects', intervalMs: intervalMs })
130
131
  }
132
+ var shotId = repeatShortShots(150)
133
+ if (!shotId) throw new Error('Repeating shot rejected')
134
+ // Keep the next attack deadline; use the new interval after it.
135
+ synth.updateTone({ toneId: shotId, intervalMs: 120 })
136
+ // Current 105 ms attacks finish; future attacks are cancelled.
137
+ synth.stopTone({ toneId: shotId })
131
138
  ```
132
139
 
133
- Validate `intervalMs` against native limits and the application's voice budget. This example has a 105 ms event (65 ms plus 40 ms release). At a shorter interval, tails overlap; at 150 ms they have room to finish. A compound multi-attack effect also needs enough interval for its complete pattern if the app promises complete repetitions.
140
+ Validate the integer interval against the native 1-60,000 ms limits and the application's overlap budget. This recipe retains the 105 ms timed attack, including its 40 ms release; shortening only the interval can overlap voices. The first attack starts at the first render opportunity. The native sample clock preserves fractional samples and is independent of tempo. This native repetition needs no JavaScript attack timer. Independent calls remain supported for gameplay events or an explicitly scheduled finite burst; `playTone` always returns Boolean acceptance. See [the standalone burst example](api.md#finite-tone-repetition).
134
141
 
135
- The app can begin an independent first attack immediately and publish delayed native repetitions, or publish a complete native repeating score with its first attack at time zero. Avoid emitting the same initial attack through both `touchstart` and `click`. JavaScript polling may display transport position, but `setInterval`/`setTimeout` should not schedule individual musical attacks.
142
+ Each layer consumes one of 32 repetition slots, independently of the four sequence slots. Each admitted attack still consumes shared voice capacity. Preserve headroom for old attacks after edits and for other owners. Rejected attacks are skipped without catch-up or variation consumption. Releasing a momentary pad, disabling fixed repetition, switching presets, backgrounding and owner close should call `stopTone` for all owned IDs. Stop remains publishable with a full ordinary queue; it cancels at the next block, including before the first attack. Global `stopAll` and shutdown also invalidate those IDs and pending edits.
136
143
 
137
- Keep every positive sequence ID. Releasing a momentary pad, disabling a fixed burst, switching presets, backgrounding the app, and pressing Stop should cancel the sequences owned by that panel. `stopSequence` releases that sequence's voices and cancels future attacks; independent timed calls already emitted are separate and finish naturally. Stop is different from global `stopAll`/shutdown panic.
144
+ `updateTone` merges changes with the last accepted recipe and validates it as a whole. It changes the next scheduled attack without advancing it. `note` and `frequency` replace each other. A supplied variation dictionary replaces the previous one, and `{ amount: 0 }` disables future variation. Each repetition advances its reproducible native stream only for admitted attacks; parameter edits retain its counter, while changing seed restarts the succession. Sounding attacks keep their original recipe. See [the complete repetition contract](api.md#finite-tone-repetition).
138
145
 
139
- A successful stop request does not mean the native slot is already reusable. Wait for the matching `sequenceend` retirement when replacing a sequence; do not poll and enqueue a backlog of missed firing requests. Keep authored variant order explicit and advance it only for accepted work. Application recipe functions are ordinary JavaScript; there is no built-in public named-recipe registry. Local 1.1.0 candidates support optional [native per-attack variation](native-variation.md), which chooses once at each admitted attack, including native loop attacks. It does not continuously resample an already sounding voice, and `updateVoice` rejects the variation dictionary. Published 1.0.2 packages reject it entirely; require both the variation capability and schema announcement before adding that property.
146
+ Native sequences remain appropriate for complete ordered scores. Keep every positive sequence ID and wait for matching `sequenceend` retirement when replacing a sequence. `stopSequence` releases that sequence's emitted voices, whereas `stopTone` lets admitted finite attacks finish. Application recipe functions remain ordinary JavaScript; there is no built-in named-recipe registry. Published 1.0.2 packages do not provide finite-tone repetition or variation.
140
147
 
141
148
  ## 4. Polyphony, gain and output protection
142
149
 
@@ -0,0 +1,59 @@
1
+ > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/native-tone-repetition-validation.md` SHA-256: `b29758b5ec3b50650c4e30e8c408cb9bc680cad3fc158e543c3f6eec00c268a1`.
2
+
3
+ # Native tone repetition validation: November 2, 2026
4
+
5
+ The local Android/iOS 1.1.0 candidates announce API 1.1.0. `playTone(recipe)` retains Boolean acceptance for one finite attack, including independent successive JavaScript calls. `repeatTone(recipe)` requires `intervalMs` inside that single dictionary and returns a positive tone ID or numeric zero. `updateTone` and `stopTone` manage future attacks; admitted voices finish their original lifetimes. The [API reference](api.md#repeattoneoptions) describes the contract.
6
+
7
+ ## Native checks
8
+
9
+ The shared fixed-capacity scheduler was tested for fractional sample remainders, variable render blocks, update deadlines, independent stops and slot reuse. A serialized control producer and an audio consumer exercised 2,000 start/stop cycles and 40,000 revisions under ThreadSanitizer without reported races.
10
+
11
+ Android production-render hosts cover finite attack PCM parity for all six waveforms and four mixing profiles, controlled noise, spatial onset boundaries, tempo independence, 32 simultaneous repetitions beside four sequences, overlap, partial revisions and variation streams. iOS production-render tests cover all six shapes in stereo/spatial routes, fractional 44.1-frame intervals, deadlines, overlap, capacity, queued stops, revision snapshots and aliases. Existing Apple render fixtures cover 120 rate/scenario combinations. The variation contract corpus passes 156 cases per platform.
12
+
13
+ Focused regressions reproduce and verify corrections for global-stop ordering (a later accepted repetition survives an earlier stop), immutable accepted nested dictionaries/arrays on iOS, and standalone JavaScript attacks remaining unrestricted. Unknown/stopped IDs, invalid intervals, missing required intervals, source validation, queue saturation and the separate note/stat method aliases are covered.
14
+
15
+ Relevant commands:
16
+
17
+ ```sh
18
+ clang -std=c11 -O2 tests/tone_core_regression.c -lm -o /tmp/synth-tone-core
19
+ /tmp/synth-tone-core
20
+ clang++ -std=c++17 -O1 -g -fsanitize=thread -pthread tests/tone_concurrency_regression.cpp -o /tmp/synth-tone-tsan
21
+ /tmp/synth-tone-tsan
22
+ python3 tests/run_native_hosts.py tone_engine variation_engine spatial_engine engine_render
23
+ python3 tests/run_stats_java.py
24
+ python3 tools/build_ios_render_host.py --output /tmp/ios-tones --driver tests/ios_tone_repetition.m
25
+ /tmp/ios-tones
26
+ python3 tests/run_sequence_events_ios.py
27
+ python3 tests/run_variation_contracts.py
28
+ node tests/api_contract_regression.js
29
+ node tests/documentation_regression.js
30
+ node --test tests/example_app_regression.js
31
+ ```
32
+
33
+ The runnable example passes ten regressions, including native repetition ownership, edits and teardown. Public linkage checks cover 25 methods. TypeScript declarations pass strict checking and positive/negative contract examples.
34
+
35
+ ## Preallocated memory and host cost
36
+
37
+ | Platform | Repetition transport storage | Benchmark |
38
+ | -------- | ---------------------------: | ------------------------------------------------ |
39
+ | Android | 617,744 bytes | 500 callbacks × 256 frames at 48 kHz; thread CPU |
40
+ | iOS | 620,048 bytes | 1,000 callbacks × 256 frames at 48 kHz; host CPU |
41
+
42
+ The storage includes 32 slots with three plain-recipe banks each; it is allocated before audio callbacks. The scheduler does not allocate, lock or execute JavaScript in the callback.
43
+
44
+ | Synthetic workload | Android callback/deadline | iOS callback/deadline |
45
+ | ---------------------- | ------------------------: | --------------------: |
46
+ | One stereo repetition | 0.571% | 1.01% |
47
+ | 32 stereo repetitions | 6.683% | 3.19% |
48
+ | One spatial repetition | 2.065% | 1.40% |
49
+ | 32 spatial repetitions | 36.840% | 14.95% |
50
+
51
+ The host workloads use separate configurations. Their results do not rank platform performance, measure physical-device CPU or guarantee real-time behavior. See `tests/tone_benchmark_host.cpp` and `tests/ios_tone_benchmark.m` for their recipes and measurement methods. Existing sequence/voice/effect storage is additional to the transport figures.
52
+
53
+ ## Packaging and consumer integration
54
+
55
+ Both native module builds pass. Candidates remain package version 1.1.0. Documentation packaging checks ZIP CRC, examples, declaration source equality, links/anchors and unchanged native bytes during the documentation refresh. Studio receives the rebuilt AAR and all three Apple XCFramework variants; binary hashes are checked against their source ZIPs. Official 1.0.2 packages are retained.
56
+
57
+ Studio uses native repetition for complete finite-tone plans, including parallel layers, while existing chords, compound scores and sustained motors keep their playback. It reserves overlaps and prior recipe tails after edits. Focused regressions cover cancellation, partial publication failure, variation disable and changed-layer capacity accounting. Native attack timing is covered by render hosts; UI gestures and perceived sound remain separate checks.
58
+
59
+ Raw logs, binary hashes, installation preference comparisons and host measurements are retained outside distribution packages in `Developer/validation-evidence/TiSynthEngine/native-tone-repetition-2026-11-02`. The records above describe checks completed before publication. Release verification records the source commit and final artifact hashes separately.
@@ -1,4 +1,4 @@
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-proposal.md` SHA-256: `17b74434b0e03e5353b71c693d4ec722dede92fee8aed142003e67f60e158e27`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/native-variation-proposal.md` SHA-256: `17b74434b0e03e5353b71c693d4ec722dede92fee8aed142003e67f60e158e27`.
2
2
 
3
3
  <a id="native-per-attack-variation--proposal-and-implementation-plan"></a>
4
4
 
@@ -1,4 +1,4 @@
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-validation.md` SHA-256: `df18826824984cb440d7412b547663ba6b8a7152bc347508114d7af982c90e68`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/native-variation-validation.md` SHA-256: `df18826824984cb440d7412b547663ba6b8a7152bc347508114d7af982c90e68`.
2
2
 
3
3
  # Native variation: technical verification
4
4
 
@@ -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/native-variation.md` SHA-256: `30bbf837a163372aa0d369c6c8e267dc8fc2d3349e8ae30c51d13d20c88cb7eb`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/native-variation.md` SHA-256: `650d2c675dc4b4e09a85521812b7fedd3b02cb993a451378a8082e2b7782303a`.
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 the 1.1.0 packages, with public API version `1.1.0` and variation schema version `1`. The published 1.0.2 packages do not accept it.
8
8
 
9
- ## Select the local package
9
+ ## Select version 1.1.0
10
10
 
11
- Install the matching locally built Android and iOS 1.1.0 archives in the app and select them in `tiapp.xml`:
11
+ Install the matching Android and iOS 1.1.0 archives in the app and select them in `tiapp.xml`:
12
12
 
13
13
  ```xml
14
14
  <modules>
@@ -17,7 +17,7 @@ Install the matching locally built Android and iOS 1.1.0 archives in the app and
17
17
  </modules>
18
18
  ```
19
19
 
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.
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; version 1.1.0 reports API `1.1.0`.
21
21
 
22
22
  ## Detect support and start playback
23
23
 
@@ -35,19 +35,21 @@ synth.start({ maxVoices: 8, maxSequences: 1 })
35
35
  synth.setVolume({ volume: 0.6 })
36
36
  ```
37
37
 
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.
38
+ 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
39
 
40
40
  ## Supported locations
41
41
 
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 |
42
+ | Call or score field | Behavior |
43
+ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
44
+ | `playTone({ variation, ... })` | Chooses once for an admitted timed tone |
45
+ | `repeatTone({ variation, intervalMs, ... })` | Retains a stream/counter and chooses once per admitted repeated finite attack |
46
+ | `updateTone({ toneId, variation, ... })` | Replaces the whole future-attack dictionary; amount zero disables; seed change resets the stream |
47
+ | `playChord({ variation, ... })` | Admits the whole chord atomically and chooses independent values for each voice |
48
+ | `noteOn({ variation, ... })` | Chooses at the held voice's attack; sustain and release keep that configuration |
49
+ | `playSequence({ voice: { variation, ... }, ... })` | Supplies the default for sounding steps on every native loop |
50
+ | `steps[i].overrides.variation` | Replaces the entire dictionary inherited from the sequence voice |
51
+ | `updateSequence({ voice, steps, ... })` | Changes future attacks when the revision applies |
52
+ | `updateNote({ variation, ... })` | Rejected; live edits must name the parameters they change |
51
53
 
52
54
  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
55
 
@@ -64,26 +66,26 @@ var variation = {
64
66
  }
65
67
  ```
66
68
 
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 |
69
+ | Field | Default | Accepted values |
70
+ | -------- | --------------------------------------- | ----------------------------------------------------------------------- |
71
+ | `amount` | `1` | Finite number from `0` to `1` |
72
+ | `seed` | Omitted; engine selects a native stream | Integer from `0` to `4294967295`; `0` is an explicit deterministic seed |
73
+ | `ranges` | Empty dictionary | Any subset of the eight range names below |
72
74
 
73
75
  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
76
 
75
77
  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
78
 
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 |
79
+ | Range | Inclusive validation bounds | Required source or setting |
80
+ | ------------ | --------------------------------------- | --------------------------------------------------------------------------------------- |
81
+ | `volume` | `0` to `2` | Any source; existing chord compensation follows selection |
82
+ | `filterHz` | `20` Hz to the active frequency ceiling | Enabled resonant filter: `lowpass`, `highpass` or `bandpass` |
83
+ | `filterQ` | `0.5` to `10` | Enabled resonant filter |
84
+ | `fmDepth` | `0` to `10` | `SINE`, with the normal FM ratio/depth rules |
85
+ | `noiseRate` | `1` Hz to the active frequency ceiling | `NOISE` with authored `noiseRate >= 1` |
86
+ | `lowPassHz` | `20` Hz to the active frequency ceiling | One-pole low-pass already enabled with authored `lowPassHz >= 20` |
87
+ | `pulseWidth` | `0.05` to `0.95` | `SQUARE` |
88
+ | `pitchCents` | `-1200` to `1200` cents | Resulting tuned frequencies and active pitch endpoints must remain within engine limits |
87
89
 
88
90
  `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
91
 
@@ -112,7 +114,7 @@ Validation checks the complete reachable configuration before admission, includi
112
114
 
113
115
  ## Repeated independent effects
114
116
 
115
- Leave out `seed` to obtain successive native selections. Call `playTone` for each actual shot; keep the application's existing firing clock.
117
+ 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
118
 
117
119
  ```javascript
118
120
  function fireLaser() {
@@ -171,11 +173,11 @@ if (supportsVariation) held.variation = {
171
173
  ranges: { fmDepth: [1.1, 1.7], pitchCents: [-10, 10] }
172
174
  }
173
175
  synth.noteOn(held)
174
- synth.updateVoice({ voiceId: 71, volume: 0.12 })
176
+ synth.updateNote({ voiceId: 71, volume: 0.12 })
175
177
  synth.noteOff({ voiceId: 71 })
176
178
  ```
177
179
 
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.
180
+ `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
181
 
180
182
  ## Native loops and dictionary inheritance
181
183
 
@@ -236,18 +238,18 @@ Stop the owned sequence through the normal lifecycle when it is no longer needed
236
238
 
237
239
  ## Seeds and reproducibility
238
240
 
239
- | Usage | Reproducibility |
240
- | --- | --- |
241
+ | Usage | Reproducibility |
242
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------------- |
241
243
  | 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 |
244
+ | Standalone calls without a seed | Use successive native selections |
245
+ | Seeded native loop | Advances its attack counter across loop iterations |
246
+ | Revision with unchanged seed identity | Retains that counter |
247
+ | Revision that changes the root seed | Resets the counter when the revision applies |
248
+ | Stop followed by a new sequence | Starts a new counter |
247
249
 
248
250
  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
251
 
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.
252
+ 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
253
 
252
254
  ## Rejected configurations
253
255
 
@@ -264,7 +266,7 @@ synth.playTone({ variation: {
264
266
  } })
265
267
 
266
268
  // Live updates do not accept an attack-only dictionary.
267
- synth.updateVoice({ voiceId: 71,
269
+ synth.updateNote({ voiceId: 71,
268
270
  variation: { ranges: { pitchCents: [-10, 10] } } })
269
271
  ```
270
272
 
@@ -278,6 +280,6 @@ Frontier automatically applies authored ranges only to its eligible destruction
278
280
 
279
281
  ## Implementation and verification
280
282
 
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.
283
+ Both renderers use [`shared/synth_variation.h`](https://github.com/macCesar/TiSynthEngine/blob/7cd035409453b01df2736aef56f2316d94ca019c/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
284
 
283
285
  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.
@@ -337,7 +337,9 @@ This repeated chord uses one centered native sequence. The one-off layered chord
337
337
 
338
338
  ### Cancellable repetition
339
339
 
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.
340
+ The chord pulse above has an identified sequence owner. Repeated start requests coalesce until its matching `sequenceend` retirement frees the slot. Stop its sequence ID and cancel pending restarts when its window closes; its native sequence schedules subsequent chord attacks.
341
+
342
+ For repetition of an original finite tone in API 1.1.0, 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
343
 
342
344
  ```javascript
343
345
  function cleanupAudio () {
@@ -1,7 +1,8 @@
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
3
+ These recipes use the core public contract shared by `ti.synthengine`
4
+ 1.0.2 / API 1.0.0 and 1.1.0 / API 1.1.0. They use only public option keys,
5
+ keep every explicit envelope valid, and obtain
5
6
  waveforms from the runtime contract instead of hardcoding integers. Install the
6
7
  matching official Android/iOS packages and follow the owning-engine
7
8
  [installation and lifecycle guide](guide.md) before using them.