@maccesar/titools 5.2.3 → 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 +96 -87
  2. package/docs/examples.md +10 -3
  3. package/package.json +2 -2
  4. package/skills/ti-synthengine/SKILL.md +50 -18
  5. package/skills/ti-synthengine/assets/example/README.md +19 -6
  6. package/skills/ti-synthengine/assets/example/app.js +69 -12
  7. package/skills/ti-synthengine/assets/index.d.ts +106 -0
  8. package/skills/ti-synthengine/references/advanced-examples.md +34 -9
  9. package/skills/ti-synthengine/references/api.md +327 -246
  10. package/skills/ti-synthengine/references/audio-engine.md +28 -14
  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 +40 -21
  14. package/skills/ti-synthengine/references/instruments-and-effects.md +164 -0
  15. package/skills/ti-synthengine/references/native-tone-repetition-validation.md +59 -0
  16. package/skills/ti-synthengine/references/native-variation-proposal.md +182 -0
  17. package/skills/ti-synthengine/references/native-variation-validation.md +63 -0
  18. package/skills/ti-synthengine/references/native-variation.md +285 -0
  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 +5 -5
  22. package/skills/ti-synthengine/references/sound-design.md +18 -16
  23. package/skills/ti-synthengine/references/source-notes.md +54 -37
  24. package/skills/ti-synthengine/references/source-snapshot.json +188 -144
  25. package/skills/ti-synthengine/references/stats-performance.md +1 -1
  26. package/skills/ti-synthengine/references/troubleshooting.md +43 -35
@@ -1,4 +1,4 @@
1
- > Source snapshot: official TiSynthEngine 1.0.2 documentation from reviewed working-tree source based on `4a45f21230ffc5a8838fbd5fc108dd3ab470362a` (2026-10-29). Upstream `documentation/audio-engine.md` SHA-256: `17fb01f7e32a91cc44a3b5c0f30efcd7b5c3d9ec6c49369f7c789e1f22a8b442`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/audio-engine.md` SHA-256: `3e7728703a9f82631f5c4ba2641b1a9735c0d3988ef9001b8e4a4f5e47d1818d`.
2
2
 
3
3
  # Audio engine
4
4
 
@@ -17,6 +17,7 @@ The native signal path explains how mixing profiles and voice capacity affect th
17
17
  - [Voice pool](#voice-pool)
18
18
  - [Command queue](#command-queue)
19
19
  - [Shared DSP core](#shared-dsp-core)
20
+ - [Native per-attack variation](#native-per-attack-variation)
20
21
  - [Android implementation](#android-implementation)
21
22
  - [iOS implementation](#ios-implementation)
22
23
  - [Latency](#latency)
@@ -28,6 +29,7 @@ The native signal path explains how mixing profiles and voice capacity affect th
28
29
  - [Sustained voices](#sustained-voices)
29
30
  - [Native transport and processing](#native-transport-and-processing)
30
31
  - [Unified transport acceptance boundaries](#unified-transport-acceptance-boundaries)
32
+ - [Native finite-tone repetition (API 1.1.0)](#native-finite-tone-repetition-api-110)
31
33
 
32
34
  <!-- TOC-END -->
33
35
 
@@ -67,7 +69,7 @@ The engine has six sources:
67
69
  | `NOISE` | White or variable-rate sample-and-hold pseudo-random generator. |
68
70
  | `WAVETABLE` | Caller-supplied 2 to 64-sample stepped cycle. |
69
71
 
70
- Pitch can move linearly from `frequency` to `frequencyEnd`. A sine LFO adds `lfoDepth * sin(lfoPhase)` in hertz before the oscillator advances. This makes vibrato depth absolute in hertz rather than cents.
72
+ Pitch can move linearly from `frequency` to `frequencyEnd`. A sine LFO adds `lfoDepth * sin(lfoPhase)` in hertz before the oscillator advances. Vibrato depth is therefore an absolute value in hertz, rather than a value in cents.
71
73
 
72
74
  Noise rate advances an independent phase accumulator. A zero `noiseRate` uses one new pseudo-random value per output sample; a positive rate holds that value until the accumulator crosses the next update. `noiseRateEnd` changes the increment linearly over the event.
73
75
 
@@ -141,11 +143,11 @@ The profile is fixed when `start()` opens the stream.
141
143
 
142
144
  A single stereo-linked peak limiter sits after master volume and optional creative saturation. Linking preserves the stereo image when only one channel has the largest peak.
143
145
 
144
- | Parameter | Value |
145
- | --------- | -------------------------------- |
146
- | Retained samples | 0 (no lookahead) |
147
- | Release | 100 ms, exponential |
148
- | Ceiling | 0.85, or 0.70 with `speakerSafe` |
146
+ | Parameter | Value |
147
+ | ---------------- | -------------------------------- |
148
+ | Retained samples | 0 (no lookahead) |
149
+ | Release | 100 ms, exponential |
150
+ | Ceiling | 0.85, or 0.70 with `speakerSafe` |
149
151
 
150
152
  The shared `synth_output.h` core finds the larger absolute channel value for each stereo frame and immediately applies any required reduction to both channels. Recovery toward unity uses a 100 ms exponential constant, bounded by the current frame's required gain; two consecutive quiet stereo frames (each channel ≤1e-12 in magnitude) reset reduction; an isolated zero crossing does not. There is no audio delay buffer. The ceilings bound sample values, not inter-sample peaks or loudspeaker excursion.
151
153
 
@@ -171,12 +173,16 @@ Both ports use a 128-slot single-consumer ring buffer. Producers are serialized
171
173
 
172
174
  `playChord()` publishes its note data and advances the queue index once. The consumer receives the complete event or nothing. `stopAll` and `shutdown` have a control fallback so cleanup is not lost under queue pressure.
173
175
 
174
- `playSequence` runs inside the sample-clock callback; JavaScript publishes entire configurations rather than individual notes. Scheduled releases execute independently of public command-queue capacity. `sequenceend` records are delivered outside the callback and retain their sequence identifier when slots are reused.
176
+ `playSequence` runs inside the sample-clock callback; JavaScript publishes entire configurations instead of individual notes. Scheduled releases execute independently of public command-queue capacity. `sequenceend` records are delivered outside the callback and retain their sequence identifier when slots are reused.
175
177
 
176
178
  ## Shared DSP core
177
179
 
178
180
  The envelope, the state-variable filter, the sweep helpers, the FM sine and the cents conversion live once, in `shared/synth_core.h`. It is plain C, header-only and allocation-free. The Android renderer includes it from `android/jni/SynthOboeEngine.cpp` and the iOS renderer from `ios/Classes/TiSynthengineModule.m`, so both platforms run the same code. Backend voice integration and mixing remain separate per platform. Master ramps and final stereo protection share `shared/synth_output.h`. Held envelopes/ramps, transport, master effects, extra modulation and compatible retrigger support also have shared C cores.
179
181
 
182
+ ## Native per-attack variation
183
+
184
+ Version 1.1.0 supports optional `variation`; published 1.0.2 packages reject it. Check both `capabilities.variation === true` and `variationSchemaVersion === 1`, then select bounded values at admitted attacks. The chosen configuration stays stable during that voice; existing sweeps, envelopes and explicit live edits keep their normal roles. Parsing prepares fixed-size state outside audio; the callback uses bounded shared arithmetic without allocation or locks. See [native variation](native-variation.md) for supported locations, source constraints, inheritance and seeds, and [technical verification](native-variation-validation.md) for measured compatibility and limits.
185
+
180
186
  ## Android implementation
181
187
 
182
188
  Android renders in C++ through Oboe. The stream requests floating-point output, low-latency performance and exclusive sharing when the device supports it. If exclusive access is unavailable, Oboe can use shared mode.
@@ -290,13 +296,15 @@ Android ARM64 reuses coefficients across eight consecutive samples, with four-sa
290
296
 
291
297
  ## Own-platform performance verification
292
298
 
299
+ The measurements in this section predate native variation and describe the archived revisions in the cited reports. They do not measure version 1.1.0. Its separate [variation verification](native-variation-validation.md) records compatibility, storage and callback checks.
300
+
293
301
  The [performance review](performance-review.json) separates measured host improvements, exact compatibility checks and pending device acceptance. Its [raw evidence](performance-review-evidence.zip) retains rejected prototypes and the investigation of idle/stereo regressions. The Apple FIR, stable-control cache and exact idle-effects bypass were evaluated as one atomic candidate and then rejected after physical 32-stereo timing regressed. All three production changes were removed together; the full-score publication and control-memory fixes remain.
294
302
 
295
303
  Matched 48 kHz/1024-frame iPad full-load runs completed 600 audio seconds without callback deadline misses, with p95 load upper bounds 0.427 for the reference and 0.383 for the rejected initial candidate. The original Apple callback was restored because 32-stereo bypass p95 regressed reproducibly, including continuous-voice windows. Android Sim20/Sim80 frame presentation remains near 60 FPS, while occasional Sim80 output underruns persist in both versions. Compact 48 kHz iPad memory growth is about 18.2 MiB in both versions; no material RAM saving or physical 192 kHz acceptance is claimed. These results and qualified listening observations are recorded separately in the review.
296
304
 
297
305
  `tools/build_ios_render_host.py` compiles the actual Objective-C render-block body and voice/transport helpers on macOS. It replaces hardware/session/Titanium startup and fixes only the noise-seed/disabled-diagnostic clock. Output and semantic state are compared against a separately archived reference from the same platform. This is an Apple renderer test; it does not execute AVAudioEngine or establish iPad scheduling, route recovery, footprint or listening acceptance.
298
306
 
299
- The commands below reproduce the historical pre-pipeline performance review. Use its archived candidate source when requiring exact PCM/state equality with `7ab3110`; the current 1.0.2 renderer intentionally removes the old final-output delay and changes overload behavior, so it cannot satisfy that earlier exact-output comparison. For the later correction, use the [audio pipeline validation](audio-pipeline-validation.md) protocol: align only the former output delay, compare no-overload PCM within the declared tolerance, and retain overload differences separately. The module's `tools/compare_output_pcm.py` checks the corresponding matched Android/Apple capture sets; host comparisons do not establish physical output latency.
307
+ The commands below reproduce the historical pre-pipeline performance review. Use its archived candidate source when requiring exact PCM/state equality with `7ab3110`; the renderer since 1.0.2 intentionally removes the old final-output delay and changes overload behavior, so it cannot satisfy that earlier exact-output comparison. For the later correction, use the [audio pipeline validation](audio-pipeline-validation.md) protocol: align only the former output delay, compare no-overload PCM within the declared tolerance, and retain overload differences separately. The module's `tools/compare_output_pcm.py` checks the corresponding matched Android/Apple capture sets; host comparisons do not establish physical output latency.
300
308
 
301
309
  ```sh
302
310
  mkdir -p /tmp/synth-reference
@@ -354,7 +362,7 @@ python3 tests/run_stats_java.py
354
362
 
355
363
  ## Sustained voices
356
364
 
357
- `noteOn`, `noteOff` and `updateVoice` use the existing bounded native command queue. Voice storage, smoothing ramps and identity snapshots are preallocated. The callback owns envelopes, oscillator/filter state and replacement decisions; it does not allocate or acquire producer locks. `shared/synth_held.h` supplies the same held ADSR, current-level release and independent 10 ms linear parameter ramps to both renderers. Held attack/decay counters stop advancing after those stages, avoiding sample-counter overflow on long notes.
365
+ `noteOn`, `noteOff` and `updateNote` use the existing bounded native command queue. Voice storage, smoothing ramps and identity snapshots are preallocated. The callback owns envelopes, oscillator/filter state and replacement decisions; it does not allocate or acquire producer locks. `shared/synth_held.h` supplies the same held ADSR, current-level release and independent 10 ms linear parameter ramps to both renderers. Held attack/decay counters stop advancing after those stages, avoiding sample-counter overflow on long notes.
358
366
 
359
367
  IDs are app-supplied positive 32-bit integers. Atomic 32-bit identity snapshots and queued attacks let producers validate update source compatibility without reading mutable DSP state. The callback checks compatibility again. A full queue rejects note release with `false`; the caller owns its bounded retry and shutdown policy. See the [sustained contract](api.md#noteonoptions) for immutable configuration and supported updates.
360
368
 
@@ -365,13 +373,13 @@ Run `tests/held_core_regression.c` with C11 for shared envelope/ramp/identity ch
365
373
 
366
374
  `shared/synth_transport.h` owns up to four independent sequences and one global tempo/beat/millisecond clock; `shared/synth_sequence.h` advances each traversal with compensated sample increments. Beat lengths follow tempo changes; millisecond lengths remain constant. A serialized producer prepares sorted traversal and all pitches before publishing one of three immutable banks. The callback accepts tempo at the block boundary and structure at a step boundary; swing changes at a pair boundary. Transport voices share the sustained pool, retain owner identifiers and absolute note deadlines beyond internal slot reuse and release independently of the public command queue. See the [public transport contract](api.md#native-sequence-transport).
367
375
 
368
- `shared/synth_effects.h` supplies chorus, ping-pong delay, damped Schroeder reverb and optional saturation; `shared/synth_modulation.h` supplies two phase-preserving LFOs. Buffers allocate at engine initialization and free after the audio backend stops. Epoch tags on 16-sample pages invalidate histories during panic without walking delay memory; recovery clears at most one short page on a first write. Effects start disabled and default rendering follows exact bypass. Enabled effects drain on disable, bounded to 30 seconds. No new external DSP dependency is used.
376
+ `shared/synth_effects.h` supplies chorus, ping-pong delay, damped Schroeder reverb and optional saturation; `shared/synth_modulation.h` supplies two phase-preserving LFOs. Buffers allocate at engine initialization and free after the audio backend stops. Epoch tags on 16-sample pages invalidate histories during panic without walking delay memory; recovery clears at most one short page on a first write. Effects start disabled and default rendering follows exact bypass. Enabled effects drain on disable, bounded to 30 seconds. The implementation uses no additional external DSP dependency.
369
377
 
370
378
  Pre-limiter peak and maximum limiter reduction accumulate natively until reset, so polling cannot miss a brief intervention. The callback tracks linear extrema and converts to decibels once per block. Existing output peak/RMS and reduction windows remain approximately 200 ms. Diagnostics are optional; host measurements do not establish device audio deadlines or listening acceptance.
371
379
 
372
- Portable checks: compile and run `tests/sequence_core_regression.c`, `tests/effects_core_regression.c` and `tests/modulation_core_regression.c` with C11, optimization and libm. The transport test checks ten minutes at five sample rates against calculated event times, plus edits, swing, rests, chords and publication pressure. Effects tests check bypass, tails, panic, automation, memory and stability at 16, 44.1, 48, 96 and 192 kHz. On macOS, `tests/studio_contract_ios.m` validates the new iOS configuration contract without Titanium.
380
+ Portable checks: compile and run `tests/sequence_core_regression.c`, `tests/effects_core_regression.c` and `tests/modulation_core_regression.c` with C11, optimization and libm. The transport test checks ten minutes at five sample rates against calculated event times, plus edits, swing, rests, chords and publication pressure. Effects tests check bypass, tails, panic, automation, memory and stability at 16, 44.1, 48, 96 and 192 kHz. On macOS, `tests/studio_contract_ios.m` validates the iOS configuration contract without Titanium.
373
381
 
374
- `tools/calibrate_studio.py --studio-root PATH` measures the production Android callback; add `--platform ios` for the production Apple callback. The report covers each preset/scenario at 1, 2, 4, 8, 16 and 32 voices, including independent low/mid/high notes, grouped chords, coherent unisons and overlapping releases. Recommendations target −6 dBFS before protection (with 0.01 dB margin) using the maximum measured peak of the declared usual 1-8-voice configuration and the public voice-volume limit. Larger workloads are separate stress diagnostics. The tool reports fixed scalar recommendations and never edits Studio. Effects and new LFOs are disabled, so recommendations do not guarantee headroom for other configurations. See [audio pipeline validation](audio-pipeline-validation.md).
382
+ `tools/calibrate_studio.py --studio-root PATH` measures the production Android callback; add `--platform ios` for the production Apple callback. The report covers each preset/scenario at 1, 2, 4, 8, 16 and 32 voices, including independent low/mid/high notes, grouped chords, coherent unisons and overlapping releases. Recommendations target −6 dBFS before protection (with 0.01 dB margin) using the maximum measured peak of the declared usual 1-8-voice configuration and the public voice-volume limit. Larger workloads are separate stress diagnostics. The tool reports fixed scalar recommendations and never edits Studio. Effects and additional LFOs are disabled, so recommendations do not guarantee headroom for other configurations. See [audio pipeline validation](audio-pipeline-validation.md).
375
383
 
376
384
  Device feedback superseded that headroom target for Studio's initial Cristal preset: its fixed gain is restored to 1 because the calibrated setting made a fresh Android installation barely audible. This adds 10.83 dB without saturation or voice-count normalization. Three coherent factory notes remain below the limiter ceiling at maximum master; eight independent coherent notes can require limiting. The calibration recommendations remain recorded separately from this active preset exception.
377
385
 
@@ -379,4 +387,10 @@ The [Studio technical validation report](studio-validation.json) records the cal
379
387
 
380
388
  ## Unified transport acceptance boundaries
381
389
 
382
- The 1.0.0 contract uses one renderer, at most 32 voices and capacity for 1-4 sequences. `bus` categories and per-voice sends share one effects chain. These API changes do not establish complete multiplatform ten-minute maximum-load acceptance; the measured iPad result and Android limits are recorded separately in [physical validation](physical-validation.json) and [the performance summary](stats-performance.md). Host timing, bypass comparisons, memory accounting, actual Android/iOS builds and the remaining device/listening checks are separate evidence. Historical Studio benchmarks below predate these changes and remain historical observations. At 192 kHz, the revised effects buffers use approximately 13.1 MiB; the maximum sequence pool uses approximately 8.23 MiB before renderer voices/queues. Total native accounting must include those remaining structures and recovery peaks; the target is at most 32 MiB additional memory.
390
+ Public API 1.1.0 uses one renderer, at most 32 voices and capacity for 1-4 sequences. `bus` categories and per-voice sends share one effects chain. These API changes do not establish complete multiplatform ten-minute maximum-load acceptance; the measured iPad result and Android limits are recorded separately in [physical validation](physical-validation.json) and [the performance summary](stats-performance.md). Host timing, bypass comparisons, memory accounting, actual Android/iOS builds and the remaining device/listening checks are separate evidence. The cited Studio and unified-transport benchmarks are historical observations. In the pre-variation layout, effects buffers at 192 kHz used approximately 13.1 MiB and four maximum-size triple-bank transports used 8,627,712 bytes (8.23 MiB), excluding renderer voices and queues. Version 1.1.0 grows those four transports to 9,537,152 bytes (9.10 MiB) on the measured 64-bit macOS layout, an increase of 909,440 bytes; see [variation storage measurements](native-variation-validation.md#storage-and-callback-work). These structure sizes are not a new whole-process or physical-device measurement. Total accounting must also include renderer voices/tails, command queues, effects, bridge/canonical options and recovery peaks. The 32 MiB additional-memory target remains a target, not a certified bound for version 1.1.0.
391
+
392
+ ## Native finite-tone repetition (API 1.1.0)
393
+
394
+ The shared C scheduler owns 32 repetition slots independently of sequence capacity. It retains fractional sample deadlines across normal and spatial render blocks. Each `repeatTone` attack emits the same finite-tone command used by a standalone `playTone`. The first attack starts at the first render opportunity, independent of tempo. Preallocated publication banks carry complete validated configurations; the callback allocates no memory, takes no producer locks and runs no JavaScript. Stop publication has a separate atomic path, so a full ordinary command queue cannot prevent cancellation at the next block.
395
+
396
+ Each repetition owns its variation stream and admitted-attack counter. Admission failure skips that attack without advancing variation or queuing catch-up work. Updates preserve the pending deadline and affect its attack; a new interval controls the subsequent gap. `stopTone` cancels only future attacks, leaving admitted finite tones to complete. Global stop, shutdown and destruction invalidate slots and pending edits. These implementation guarantees require the updated 1.1.0 binaries; historical measurements elsewhere on this page do not measure this scheduler. See [the public contract](api.md#finite-tone-repetition).
@@ -1,4 +1,4 @@
1
- > Source snapshot: official TiSynthEngine 1.0.2 documentation from reviewed working-tree source based on `4a45f21230ffc5a8838fbd5fc108dd3ab470362a` (2026-10-29). Upstream `documentation/audio-pipeline-validation.md` SHA-256: `7359d85de63e9fe94f85477e03a22fc1531c8acaabe30333e7ff2515cc5ec3e9`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/audio-pipeline-validation.md` SHA-256: `7359d85de63e9fe94f85477e03a22fc1531c8acaabe30333e7ff2515cc5ec3e9`.
2
2
 
3
3
  # Audio pipeline correction
4
4
 
@@ -1,8 +1,8 @@
1
- > Source snapshot: official TiSynthEngine 1.0.2 documentation from reviewed working-tree based on commit `4a45f21230ffc5a8838fbd5fc108dd3ab470362a` (2026-10-29). Upstream `documentation/examples.md` SHA-256: `48104b9d413050d439ee605c92a2456a50b42c4c80e2d7c2c05706eecc7db215`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/examples.md` SHA-256: `1b3be798d8f606381696d9e00cda330c11475402397e4760dcbc3041a39ef404`.
2
2
 
3
3
  # Examples
4
4
 
5
- Every snippet on this page uses the TiSynthEngine 1.0.0 contract. Unless a snippet includes startup code, it assumes the engine is already running.
5
+ Every snippet on this page uses the TiSynthEngine 1.1.0 contract. Unless a snippet includes startup code, it assumes the engine is already running.
6
6
 
7
7
  Additional [curated recipes](recipes.md) provide application sound and interaction variants.
8
8
 
@@ -13,8 +13,8 @@ var synth = require('ti.synthengine')
13
13
  var contract = synth.getDefaults()
14
14
  var WAVE = contract.waveTypes
15
15
 
16
- if (contract.apiVersion !== '1.0.0') {
17
- throw new Error('TiSynthEngine 1.0.0 is required')
16
+ if (contract.apiVersion !== '1.1.0') {
17
+ throw new Error('TiSynthEngine 1.1.0 is required')
18
18
  }
19
19
  ```
20
20
 
@@ -570,7 +570,7 @@ synth.shutdown()
570
570
 
571
571
  `stopAll()` stops all native sequences, fades current voices and invalidates effect histories. `shutdown()` also releases the native stream. To stop only one sequence owner, use `stopSequence({ sequenceId })`.
572
572
 
573
- Follow the [interactive demo setup and test checklist](../assets/example/README.md) to run [`example/app.js`](../assets/example/app.js). It includes headphone positioning, session selection, pattern completion/replacement and all three statistics methods. For a service-level statistics recipe, see [read engine statistics](guide.md#read-engine-statistics).
573
+ Follow the [interactive demo setup and test checklist](../assets/example/README.md) to run [`example/app.js`](../assets/example/app.js). It includes headphone positioning, session selection, pattern completion/replacement and the statistics methods `getStats`, `enableStats`, `disableStats` and `resetStats`. For a service-level statistics recipe, see [read engine statistics](guide.md#read-engine-statistics).
574
574
 
575
575
  ## Positioned headphone effect
576
576
 
@@ -1,4 +1,4 @@
1
- > Source snapshot: official TiSynthEngine 1.0.2 documentation from the reviewed working-tree based on `4a45f21230ffc5a8838fbd5fc108dd3ab470362a` (2026-10-29). Upstream `documentation/guide.md` SHA-256: `6241f84ba84e20c0516bf01bc0850f9625d2b41f0bef39289c056e687b21ee37`.
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,27 +28,30 @@ 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
+ - [Variation support in version 1.1.0](#variation-support-in-version-110)
31
32
 
32
33
  <!-- TOC-END -->
33
34
 
34
35
  ## Install the module
35
36
 
36
- 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 source builds, the 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`:
37
38
 
38
- - `ti.synthengine-android-1.0.2.zip`
39
- - `ti.synthengine-iphone-1.0.2.zip`
39
+ - `ti.synthengine-android-1.1.0.zip`
40
+ - `ti.synthengine-iphone-1.1.0.zip`
40
41
 
41
42
  Put the archives in the Titanium project root or install them through the Titanium CLI. Pin the same version for both platforms:
42
43
 
43
44
  ```xml
44
45
  <modules>
45
- <module platform="android" version="1.0.2">ti.synthengine</module>
46
- <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>
47
48
  </modules>
48
49
  ```
49
50
 
50
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.
51
52
 
53
+ These declarations select version 1.1.0. Check the [variation metadata](#variation-support-in-version-110) before using native attack variation.
54
+
52
55
  ## Load and verify the contract
53
56
 
54
57
  Require the module once in the code that owns audio. Check `apiVersion` before starting. Also check the [spatial limits](#detect-the-installed-feature) and [statistics methods](#read-engine-statistics) if the app needs those features. Check [required capabilities](#detect-required-capabilities) for held voices, musical transport and master processing.
@@ -57,8 +60,8 @@ Require the module once in the code that owns audio. Check `apiVersion` before s
57
60
  var synth = require('ti.synthengine')
58
61
  var contract = synth.getDefaults()
59
62
 
60
- if (contract.apiVersion !== '1.0.0') {
61
- 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)
62
65
  }
63
66
  ```
64
67
 
@@ -112,30 +115,30 @@ Use one owner for the engine. Start it once, pass a small audio service to the s
112
115
 
113
116
  ## Detect required capabilities
114
117
 
115
- 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:
116
119
 
117
120
  ```javascript
118
121
  function hasMethods (names) {
119
122
  return names.every(function (name) { return typeof synth[name] === 'function' })
120
123
  }
121
- var supportsHeld = hasMethods(['noteOn', 'noteOff', 'updateVoice'])
124
+ var supportsHeld = hasMethods(['noteOn', 'noteOff', 'updateNote'])
122
125
  var supportsSequence = hasMethods(['playSequence', 'updateSequence', 'stopSequence', 'getSequenceState'])
123
126
  var supportsProcessing = hasMethods(['setMasterProcessing'])
124
127
  ```
125
128
 
126
- 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.
127
130
 
128
131
  ## Hold and edit a voice
129
132
 
130
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.
131
134
 
132
- `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.
133
136
 
134
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.
135
138
 
136
139
  ## Choose the musical transport
137
140
 
138
- 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.
139
142
 
140
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`.
141
144
 
@@ -143,7 +146,7 @@ Store the positive identifier returned by `playSequence`, and include it in each
143
146
  synth.setTempo({ bpm: 112 })
144
147
  var sequenceId = synth.playSequence({
145
148
  stepDuration: 0.5, swing: 0.12, bus: 'music',
146
- voice: { waveType: WAVE.SQUARE, volume: 0.55 },
149
+ voice: { waveType: contract.waveTypes.SQUARE, volume: 0.55 },
147
150
  steps: [{ notes: [60] }, { notes: [64], overrides: { pan: -0.4, panEnd: 0.4 } },
148
151
  { rest: true }, { notes: [67], duration: 1, length: 0.8 }]
149
152
  })
@@ -365,7 +368,7 @@ synth.playChord({
365
368
  synth.setTempo({ bpm: 112 })
366
369
  var sequenceId = synth.playSequence({
367
370
  stepDuration: 0.5, swing: 0.12, bus: 'music',
368
- voice: { waveType: WAVE.SQUARE, volume: 0.55 },
371
+ voice: { waveType: contract.waveTypes.SQUARE, volume: 0.55 },
369
372
  steps: [{ notes: [60] }, { notes: [64], overrides: { pan: -0.4, panEnd: 0.4 } },
370
373
  { rest: true }, { notes: [67], duration: 1, length: 0.8 }]
371
374
  })
@@ -394,7 +397,7 @@ synth.setVolume({ volume: 0.7 })
394
397
  synth.stopAll()
395
398
  ```
396
399
 
397
- 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.
398
401
 
399
402
  ## Output route changes
400
403
 
@@ -509,7 +512,7 @@ if (!playPositionedTone(explosion, 900, 100, 1000, 800)) {
509
512
 
510
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`.
511
514
 
512
- 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.
513
516
 
514
517
  ### Chords and patterns
515
518
 
@@ -554,7 +557,7 @@ Basic counters are available without enabling optional diagnostics. Reads do not
554
557
  ```javascript
555
558
  var statsTimer = null
556
559
  var statsSupported = typeof synth.getStats === 'function' &&
557
- typeof synth.setStatsEnabled === 'function' && typeof synth.resetStats === 'function'
560
+ typeof synth.enableStats === 'function' && typeof synth.disableStats === 'function' && typeof synth.resetStats === 'function'
558
561
 
559
562
  function readStats () {
560
563
  var stats = synth.getStats()
@@ -565,7 +568,7 @@ function readStats () {
565
568
 
566
569
  function startDiagnostics () {
567
570
  if (!statsSupported || statsTimer !== null) return false
568
- if (!synth.setStatsEnabled(true)) return false
571
+ if (!synth.enableStats()) return false
569
572
  statsTimer = setInterval(readStats, 200)
570
573
  return true
571
574
  }
@@ -573,7 +576,7 @@ function startDiagnostics () {
573
576
  function stopDiagnostics () {
574
577
  if (statsTimer !== null) clearInterval(statsTimer)
575
578
  statsTimer = null
576
- if (statsSupported) synth.setStatsEnabled(false)
579
+ if (statsSupported) synth.disableStats()
577
580
  }
578
581
 
579
582
  // Read basic counters without enabling timing or signal meters:
@@ -598,6 +601,22 @@ See the [complete field contract](api.md#getstats), [measurement limits](stats-p
598
601
 
599
602
  ## Diagnose mix headroom
600
603
 
601
- 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.
602
605
 
603
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
+
608
+
609
+ <a id="local-native-variation-candidate"></a>
610
+
611
+ ## Variation support in version 1.1.0
612
+
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:
614
+
615
+ ```xml
616
+ <modules>
617
+ <module platform="android" version="1.1.0">ti.synthengine</module>
618
+ <module platform="iphone" version="1.1.0">ti.synthengine</module>
619
+ </modules>
620
+ ```
621
+
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.
@@ -0,0 +1,164 @@
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
+
3
+ # Instruments, sound effects and complete sequences
4
+
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
+
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
+
9
+ ## Choose the playback contract
10
+
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 |
19
+
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.
21
+
22
+ All examples below assume successful startup. Use constants from the installed module and check native acceptance:
23
+
24
+ ```javascript
25
+ var synth = require('ti.synthengine')
26
+ var WAVE = synth.getDefaults().waveTypes
27
+ if (!synth.start({ maxVoices: 32, maxSequences: 4,
28
+ mixingProfile: 'conservative', audioSession: 'playback' })) {
29
+ throw new Error('Could not start TiSynthEngine')
30
+ }
31
+ synth.setVolume({ volume: 0.6 })
32
+ ```
33
+
34
+ See [API limits](api.md) and [startup and lifecycle](guide.md) for application integration. Do not call `start` on each key just to reset a voice pool. Shut the engine down when its application owner finishes.
35
+
36
+ ## 1. Playable instruments and sustained voices
37
+
38
+ An instrument recipe defines the response of a new note: waveform, filter, FM, envelope, level and positioning. The keyboard supplies its pitch and release gesture. Each simultaneous contact needs its own positive voice ID.
39
+
40
+ ```javascript
41
+ var nextVoiceId = 1
42
+ function pressKey(frequency) {
43
+ var voiceId = nextVoiceId++
44
+ var accepted = synth.noteOn({ voiceId: voiceId, frequency: frequency,
45
+ waveType: WAVE.TRIANGLE, volume: 0.16,
46
+ attack: 8, decay: 220, sustain: 0.35, release: 180,
47
+ bus: 'music' })
48
+ return accepted ? voiceId : 0
49
+ }
50
+ function releaseKey(voiceId) {
51
+ return synth.noteOff({ voiceId: voiceId })
52
+ }
53
+ ```
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.
56
+
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.
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.
60
+
61
+ ### Instrument demonstrations are musical performances
62
+
63
+ A demo provides notes and articulation for the current timbre. It should not silently substitute another sound or replace the user's editable pattern. A useful finite phrase has an opening, development and a resolved ending:
64
+
65
+ - Winds need an appropriate register, separated attacks and breathing spaces.
66
+ - Strings benefit from longer notes and nearly connected lengths.
67
+ - Basses benefit from a low register, rests and a deliberate groove.
68
+ - Guitars and keyboards can use arpeggios, chord changes and varied note lengths.
69
+ - Percussion needs separate strikes and enough room for its decay and release.
70
+ - Slow pads need broad entries long enough for their attack to develop.
71
+ - Unpitched textures demonstrate density, filtering and movement; changing a noise clock is not the same as transposing a harmonic note.
72
+
73
+ Schedule such phrases with the native transport. Make looping an explicit application choice. See [sequences](api.md#playsequenceoptions) and [sound-design envelopes](sound-design.md#envelope-and-total-duration).
74
+
75
+ ## 2. Independent finite sound effects
76
+
77
+ A sound effect often communicates a specific action: launch, impact, warning, confirmation. Its original pitch is part of that action. A rising missile and a descending shot can share the same square-wave timbre while conveying different movement.
78
+
79
+ ```javascript
80
+ function fireMissile() {
81
+ return synth.playTone({ frequency: 196, frequencyEnd: 523,
82
+ duration: 350, waveType: WAVE.SQUARE,
83
+ attack: 2, decay: 0, sustain: 1, release: 50,
84
+ volume: 0.15, bus: 'effects' })
85
+ }
86
+ ```
87
+
88
+ This event completes natively. Repeated independent calls can overlap naturally. Their frequencies and endpoints remain those of the recipe; they are not implicitly transposed to the key being touched.
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.
91
+
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.
93
+
94
+ ## 3. Complete sound sequences and layered effects
95
+
96
+ Some effects need multiple ordered attacks. Others combine simultaneous body, noise and high-frequency detail, each with a different envelope. A sustained key containing only the first layer cannot reproduce the complete event.
97
+
98
+ This native sequence defines a missile with 300 ms of pitch motion followed by 50 ms of release. It is a sequence interpretation, not a sample-equivalent conversion of the timed example above:
99
+
100
+ ```javascript
101
+ function missileSequence() {
102
+ return synth.playSequence({ timeUnit: 'ms', loop: false,
103
+ voice: { waveType: WAVE.SQUARE, attack: 2,
104
+ decay: 0, sustain: 1, release: 50, volume: 0.15,
105
+ bus: 'effects' },
106
+ steps: [{ frequencies: [196], duration: 350, length: 300,
107
+ overrides: { frequencyEnd: 523 } }] })
108
+ }
109
+ ```
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.
112
+
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.
114
+
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.
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.
118
+
119
+ Use complete step dictionaries and their required overrides. Do not identify variants by guessing from silence, remove an intentional gap, round source frequencies to MIDI integers, discard a sweep, or merge independent layers solely because they begin together. A source importer should carry explicit interpretation, variant boundaries and demo association alongside the recipe.
120
+
121
+ ### Repeated firing uses the native clock
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
+
125
+ ```javascript
126
+ function repeatShortShots(intervalMs) {
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 })
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 })
138
+ ```
139
+
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).
141
+
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.
143
+
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).
145
+
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.
147
+
148
+ ## 4. Polyphony, gain and output protection
149
+
150
+ Both musical notes and effects share the voice pool and output chain. Either can distort when too many contributions sum at high gain, when filters resonate strongly, when tails overlap, or when creative saturation is enabled. A successful effect in one game does not certify arbitrary held chords, denser firing, different processing, or a device's physical speaker volume.
151
+
152
+ - `maxVoices` is a capacity, not a loudness control. Reserve for simultaneous attacks and release tails, including future overlap in a repeating score.
153
+ - `maxSequences` limits independent native tracks. Do not silently drop a layer to fit the limit.
154
+ - Check native acceptance. A positive sequence ID acknowledges publication; it does not prove every future note will find enough voices.
155
+ - Native admission can replace eligible voices under pressure. If the product promises no stealing, the application must reserve capacity across all of its sound owners and reject excess work visibly.
156
+ - Do not apply chord normalization to an already authored set of independent layers unless that gain change is intended.
157
+ - Keep headroom at voice, processor input and master stages. Preserve source gains when importing a known recipe; changing the overlap pattern still needs new validation.
158
+ - Creative `saturation` intentionally changes timbre. Final output limiting protects peaks but is not proof that the earlier mix is clean.
159
+
160
+ Use `getStats` to inspect active/tail voices, pending commands, rejected publications, priority drops and voice steals. Enable diagnostics for `preLimiterPeakDb`, `maxLimiterReductionDb`, output peaks and callback load. See [gain staging](sound-design.md#chords-polyphony-and-gain), [processing](api.md#setmasterprocessingoptions), [statistics](stats-performance.md), and [troubleshooting](troubleshooting.md). Host measurements and successful validation do not replace listening on the target speaker/headphones at the intended overlap and physical volume.
161
+
162
+ ## Verify the route you actually ship
163
+
164
+ First compare an effect against its original complete recipe, including timing and levels. Separately test its playable adaptation, if offered. For instruments, test single keys, simultaneous keys, slides, Hold, release tails and finite demos. For effects, test rapid taps, native bursts, explicit variants, layered sounds, cancellation and exhausted capacity. Distinguish publication acceptance, rendered output, physical responsiveness and listening results in the report.
@@ -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.