@maccesar/titools 5.2.4 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +27 -32
  2. package/agents/ti-pro.md +1 -1
  3. package/commands/ti-audit.md +1 -1
  4. package/docs/examples.md +15 -8
  5. package/package.json +1 -1
  6. package/skills/ti-synthengine/SKILL.md +62 -92
  7. package/skills/ti-synthengine/assets/example/README.md +51 -54
  8. package/skills/ti-synthengine/assets/example/app.js +96 -57
  9. package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +39 -40
  10. package/skills/ti-synthengine/assets/example/lib/soundPreset.js +34 -0
  11. package/skills/ti-synthengine/assets/index.d.ts +226 -0
  12. package/skills/ti-synthengine/references/advanced-examples.md +12 -12
  13. package/skills/ti-synthengine/references/api.md +125 -33
  14. package/skills/ti-synthengine/references/audio-engine.md +24 -59
  15. package/skills/ti-synthengine/references/examples.md +64 -7
  16. package/skills/ti-synthengine/references/guide.md +69 -63
  17. package/skills/ti-synthengine/references/instruments-and-effects.md +34 -27
  18. package/skills/ti-synthengine/references/native-variation.md +92 -51
  19. package/skills/ti-synthengine/references/recipes-interaction.md +26 -19
  20. package/skills/ti-synthengine/references/recipes.md +19 -15
  21. package/skills/ti-synthengine/references/recommendations.md +14 -14
  22. package/skills/ti-synthengine/references/sound-api.md +197 -0
  23. package/skills/ti-synthengine/references/sound-design.md +11 -11
  24. package/skills/ti-synthengine/references/source-notes.md +62 -147
  25. package/skills/ti-synthengine/references/troubleshooting.md +22 -24
  26. package/skills/ti-synthengine/references/upgrade-2.0.0.md +40 -0
  27. package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
  28. package/skills/ti-synthengine/references/android-sim80-correction.json +0 -162
  29. package/skills/ti-synthengine/references/audio-pipeline-validation.md +0 -19
  30. package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
  31. package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
  32. package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
  33. package/skills/ti-synthengine/references/frontier-physical-validation.json +0 -1918
  34. package/skills/ti-synthengine/references/native-variation-proposal.md +0 -182
  35. package/skills/ti-synthengine/references/native-variation-validation.md +0 -63
  36. package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
  37. package/skills/ti-synthengine/references/performance-review.json +0 -1146
  38. package/skills/ti-synthengine/references/physical-validation.json +0 -1457
  39. package/skills/ti-synthengine/references/source-snapshot.json +0 -463
  40. package/skills/ti-synthengine/references/stats-performance.json +0 -2992
  41. package/skills/ti-synthengine/references/stats-performance.md +0 -82
  42. package/skills/ti-synthengine/references/studio-validation.json +0 -6202
  43. package/skills/ti-synthengine/references/unified-bypass-performance.json +0 -22425
  44. package/skills/ti-synthengine/references/unified-validation.json +0 -314
@@ -1,11 +1,11 @@
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/examples.md` SHA-256: `637806516d2804c43427d187a37786cfcf2f0d28758762f01a570ba15194bae3`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/examples.md` SHA-256: `ee8aed566a63062ae63ae78f0b96fa8d73c70ab2f50aed81081c26acc24718af`.
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.
6
-
7
5
  Additional [curated recipes](recipes.md) provide application sound and interaction variants.
8
6
 
7
+ Every snippet on this page uses TiSynthEngine 2.0.0 (including its retained direct playback methods). Unless a snippet includes startup code, it assumes the engine is already running.
8
+
9
9
  For interactive instruments and native processing, begin with [held motors](advanced-examples.md#sustained-game-motor), [native sequences](advanced-examples.md#native-sequence-with-chords-and-edits), [master processing](advanced-examples.md#chorus-delay-reverb-and-creative-saturation) or [two LFOs](advanced-examples.md#two-voice-lfos). Use matching official packages and check method availability before enabling a feature.
10
10
 
11
11
  ```javascript
@@ -13,14 +13,18 @@ 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.version !== '2.0.0') {
17
+ throw new Error('TiSynthEngine 2.0.0 is required')
18
18
  }
19
19
  ```
20
20
 
21
21
  <!-- TOC-START -->
22
22
  ## Contents
23
23
 
24
+ - [Reuse a sound with different notes](#reuse-a-sound-with-different-notes)
25
+ - [Hold a note and release its control](#hold-a-note-and-release-its-control)
26
+ - [Repeat finite attacks](#repeat-finite-attacks)
27
+ - [Direct native parameter examples](#direct-native-parameter-examples)
24
28
  - [Start once after layout](#start-once-after-layout)
25
29
  - [Built-in waveforms](#built-in-waveforms)
26
30
  - [Variable-rate filtered noise](#variable-rate-filtered-noise)
@@ -52,6 +56,59 @@ if (contract.apiVersion !== '1.0.0') {
52
56
 
53
57
  <!-- TOC-END -->
54
58
 
59
+ ## Reuse a sound with different notes
60
+
61
+ ```javascript
62
+ var bell = synth.createSound({
63
+ sound: { waveType: WAVE.SINE, volume: 0.25, attack: 5, release: 120 },
64
+ playback: { type: 'tone', notes: ['A4'], duration: 400 }
65
+ })
66
+
67
+ bell.play()
68
+ bell.play({ notes: ['C5'], volume: 0.15 })
69
+ bell.set({ sound: { volume: 0.2 } })
70
+ ```
71
+
72
+ Execution overrides leave the stored definition unchanged. `set()` validates an atomic revision for future executions. For an atomic chord, declare `playback.type: 'chord'` and supply a pitch array.
73
+
74
+ ## Hold a note and release its control
75
+
76
+ ```javascript
77
+ var instrument = synth.createSound({
78
+ sound: { waveType: WAVE.TRIANGLE, volume: 0.2, attack: 8, release: 180 },
79
+ playback: { type: 'held', notes: ['C4'] }
80
+ })
81
+
82
+ var note = instrument.noteOn({ notes: ['E4'] })
83
+ if (note) {
84
+ note.update({ notes: ['G4'], volume: 0.15 })
85
+ note.noteOff()
86
+ }
87
+ ```
88
+
89
+ Keep each note control with its interaction owner. On a rejected release, retain it for bounded retry or owner shutdown. A control cannot act on a restarted engine.
90
+
91
+ ## Repeat finite attacks
92
+
93
+ ```javascript
94
+ var shot = synth.createSound({
95
+ sound: { waveType: WAVE.SQUARE, volume: 0.1, attack: 2, release: 40 },
96
+ playback: { type: 'repeat', frequencies: [880], duration: 105, interval: 150 }
97
+ })
98
+
99
+ var firing = shot.play()
100
+ if (firing) {
101
+ firing.update({ interval: 120 })
102
+ firing.stop()
103
+ }
104
+ ```
105
+
106
+ The native clock schedules attacks. `stop()` cancels future attacks; admitted 105 ms attacks finish naturally. See [patterns](sound-api.md#patterns) for ordered scores, chords and rests, and [processing](sound-api.md#units-and-processing) for shared effects in milliseconds.
107
+
108
+ ## Direct native parameter examples
109
+
110
+ The remaining recipes use the supported direct methods and their native parameter dictionaries. For reusable application sounds, store synthesis fields under `sound`, pitches/timing under `playback`, and use the controls above. The [Sound reference](sound-api.md) lists interpretation-specific restrictions; the [upgrade guide](upgrade-2.0.0.md) maps direct names and units.
111
+
55
112
  ## Start once after layout
56
113
 
57
114
  ```javascript
@@ -430,7 +487,7 @@ synth.playTone({
430
487
  })
431
488
  ```
432
489
 
433
- A fractional `fmRatio` puts the overtones between harmonics, which reads as metal. `fmDepthEnd: 0` lets the brightness die away completely.
490
+ A fractional `fmRatio` puts the overtones between harmonics, giving the sound a metallic timbre. `fmDepthEnd: 0` lets the brightness die away completely.
434
491
 
435
492
  ## Wah and filtered laser
436
493
 
@@ -570,7 +627,7 @@ synth.shutdown()
570
627
 
571
628
  `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
629
 
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).
630
+ 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
631
 
575
632
  ## Positioned headphone effect
576
633
 
@@ -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 `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/guide.md` SHA-256: `873379c35afe13d947eaf3262e942884e7d1e6b572a7a7867e646f89c7fd1e0f`.
2
2
 
3
3
  # Guide
4
4
 
@@ -28,40 +28,40 @@ 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](#variation-support)
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
+ Use matching Android/iOS 2.0.0 packages. Source builds produce these archives 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-2.0.0.zip`
40
+ - `ti.synthengine-iphone-2.0.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="2.0.0">ti.synthengine</module>
47
+ <module platform="iphone" version="2.0.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 2.0.0. Check the [variation metadata](#variation-support) before using native attack variation.
54
54
 
55
55
  ## Load and verify the contract
56
56
 
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
+ Require the module once in the code that owns audio, and check the installed module `version` before starting. Check the [spatial limits](#detect-the-installed-feature) if the app needs spatial audio, and the [statistics methods](#read-engine-statistics) if it needs statistics. For held voices, musical transport and master processing, check [required capabilities](#detect-required-capabilities).
58
58
 
59
59
  ```javascript
60
60
  var synth = require('ti.synthengine')
61
- var contract = synth.getDefaults()
61
+ var contract = synth.getContract()
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.version !== '2.0.0') {
64
+ throw new Error('TiSynthEngine 2.0.0 is required; found ' + contract.version)
65
65
  }
66
66
  ```
67
67
 
@@ -115,47 +115,52 @@ 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
+ TiSynth 2.0.0 supplies reusable sounds, held notes, patterns, repetitions and shared processing. Inspect `getContract().capabilities.soundObjects` and the documented property limits. The variation dictionary also uses `capabilities.variation` and `variationSchemaVersion`.
119
119
 
120
- ```javascript
121
- function hasMethods (names) {
122
- return names.every(function (name) { return typeof synth[name] === 'function' })
123
- }
124
- var supportsHeld = hasMethods(['noteOn', 'noteOff', 'updateVoice'])
125
- var supportsSequence = hasMethods(['playSequence', 'updateSequence', 'stopSequence', 'getSequenceState'])
126
- var supportsProcessing = hasMethods(['setMasterProcessing'])
127
- ```
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.
120
+ Start the engine successfully before publishing audio. Reading metadata and creating definitions do not open the stream. Rebuild the app after replacing a native module.
130
121
 
131
122
  ## Hold and edit a voice
132
123
 
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.
124
+ Create a held definition when duration follows interaction. Each call returns its own control; keep it with the contact that started it.
134
125
 
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.
126
+ ```javascript
127
+ var instrument = synth.createSound({
128
+ sound: { waveType: contract.waveTypes.TRIANGLE, volume: 0.2, attack: 8, release: 180 },
129
+ playback: { type: 'held', notes: ['C4'] }
130
+ })
136
131
 
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.
132
+ var note = instrument.noteOn({ notes: ['E4'] })
133
+ if (note) {
134
+ note.update({ volume: 0.15 })
135
+ note.noteOff()
136
+ }
137
+ ```
138
138
 
139
- ## Choose the musical transport
139
+ Release on key up, touch cancellation, pause and owner close. Keep a rejected release retryable: retry at most twice at 16 ms, then shut down/report if it still fails. These timers recover lifecycle operations; they do not schedule musical attacks. Shutdown invalidates the control.
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
+ Compatible live updates use 10 ms transitions and retain phase and envelope. Waveform, table, filter type, ADSR, variation, bus and priority are fixed at attack. See [held fields](sound-api.md#held-notes). Chords played as independent held notes have independent gain and admission; use the chord interpretation for an atomic finite group.
142
142
 
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`.
143
+ ## Choose the musical transport
144
+
145
+ Use `playback.type: 'tone'` or `'chord'` for finite events, `'held'` for interaction, `'repeat'` for repeated finite attacks and `'pattern'` for ordered scores. All share the configured pool of up to 32 voices. Startup `maxSequences` reserves one through four pattern slots.
144
146
 
145
147
  ```javascript
146
- synth.setTempo({ bpm: 112 })
147
- var sequenceId = synth.playSequence({
148
- stepDuration: 0.5, swing: 0.12, bus: 'music',
149
- voice: { waveType: contract.waveTypes.SQUARE, volume: 0.55 },
150
- steps: [{ notes: [60] }, { notes: [64], overrides: { pan: -0.4, panEnd: 0.4 } },
151
- { rest: true }, { notes: [67], duration: 1, length: 0.8 }]
148
+ synth.setTempo(112)
149
+
150
+ var melody = synth.createSound({
151
+ sound: { waveType: contract.waveTypes.SQUARE, volume: 0.2, bus: 'music' },
152
+ playback: {
153
+ type: 'pattern', interval: 0.5, swing: 0.12,
154
+ steps: [{ notes: [60] }, { notes: [64], sound: { pan: -0.4, panEnd: 0.4 } },
155
+ { rest: true }, { notes: [67], interval: 1, length: 0.8 }]
156
+ }
152
157
  })
153
- if (sequenceId === 0) throw new Error('Sequence rejected')
154
- // Stop this owner without affecting unrelated voices:
155
- synth.stopSequence({ sequenceId: sequenceId })
158
+
159
+ var pattern = melody.play()
160
+ if (pattern) pattern.stop()
156
161
  ```
157
162
 
158
- See [the full sequence contract](api.md#native-sequence-transport) for length, gate, variable durations, arpeggios, revisions and capacity rejection.
163
+ Pattern controls provide `update()`, `stop()`, `getState()` and an `end` event. Updates preserve native revision scheduling. Beat patterns follow global tempo; millisecond patterns keep fixed times. Stopping releases that pattern's voices and leaves processing tails. A repetition's `stop()` cancels future attacks while admitted finite attacks finish. See [controls and timing](sound-api.md#patterns).
159
164
 
160
165
  ## Add master effects
161
166
 
@@ -163,26 +168,25 @@ The native chain is voices, fixed input gain, chorus, ping-pong delay, stereo Sc
163
168
 
164
169
  Chorus adds stereo movement, delay adds free or synchronized echoes, and reverb adds a damped tail. Saturation is optional creative drive. Select processed categories with `applyTo: ['music', 'ambience']`; a voice's explicit `effectSend` overrides that selection. One chain processes the selected share and one final limiter protects the recombined output.
165
170
 
166
- `setMasterProcessing` merges partial updates, validates the entire result and returns publication acceptance. Continuous controls ramp over 10 ms. Disabling an effect closes input gradually and keeps its tail, bounded to 30 seconds with a final fade, then restores exact bypass. It does not undo a non-unity input gain. `stopAll` invalidates histories and stops every native sequence; `shutdown` stops the stream and frees its buffers. See the [processing example](advanced-examples.md#chorus-delay-reverb-and-creative-saturation) and [field ranges](api.md#setmasterprocessingoptions).
171
+ `setProcessing()` merges partial updates, validates the entire result and returns publication acceptance. Delay `time` and reverb `decay` use milliseconds. Continuous controls ramp over 10 ms. Disabling an effect closes input gradually and keeps its tail, bounded to 30 seconds with a final fade, then restores exact bypass. It does not undo a non-unity input gain. `stopAll` invalidates histories and stops every native sequence; `shutdown` stops the stream and frees its buffers. See the [processing example](advanced-examples.md#chorus-delay-reverb-and-creative-saturation) and [field ranges](api.md#setmasterprocessingoptions).
167
172
 
168
173
  Delay and LFO sync follow global `setTempo` (initially 120); free settings retain milliseconds or hertz. The two extra voice LFOs target filter cutoff, square PWM or sine FM, with sine/triangle waves. They are independent of `lfoFreq`/`lfoDepth` pitch vibrato. See [two LFOs](advanced-examples.md#two-voice-lfos).
169
174
 
170
175
  ## Play the first sound
171
176
 
172
177
  ```javascript
173
- var accepted = synth.playTone({
174
- note: 'A4',
175
- duration: 300,
176
- attack: 8,
177
- release: 80,
178
- volume: 0.8,
179
- waveType: contract.waveTypes.SINE
178
+ var bell = synth.createSound({
179
+ sound: { waveType: contract.waveTypes.SINE, volume: 0.25, attack: 8, release: 80 },
180
+ playback: { type: 'tone', notes: ['A4'], duration: 300 }
180
181
  })
181
182
 
182
- if (!accepted) Ti.API.warn('The tone was rejected')
183
+ bell.play()
184
+ bell.play({ notes: ['C5'] })
183
185
  ```
184
186
 
185
- Timed and held sound calls and accepted edits return `true` when accepted and `false` when rejected. `playSequence` instead returns a positive sequence ID or `0`; state queries return their documented state or `null`, and lifecycle/cleanup methods follow their own return contracts. During development, handle the relevant result and inspect logcat or the Xcode console for the structured validation warning.
187
+ A finite tone returns acceptance and finishes naturally. The same reusable definition can receive temporary pitch or synthesis overrides. Use `bell.set({ sound: { volume: 0.2 } })` to change future executions; `bell.getPreset()` returns an independent copy.
188
+
189
+ The sections below explain the direct native parameter dictionaries for shaping and spatial conversion. Those methods remain supported in 2.0.0. New application code can use the same synthesis fields under `sound` and pitches/timing under `playback`; see [Sound properties](sound-api.md#property-allowlists).
186
190
 
187
191
  ## Choose a pitch
188
192
 
@@ -323,7 +327,7 @@ synth.playTone({
323
327
  })
324
328
  ```
325
329
 
326
- For a caller-defined repeating shape, select `WAVETABLE` and provide 2 to 64 samples from -1 to 1. The table is stepped rather than interpolated.
330
+ For a caller-defined repeating shape, select `WAVETABLE` and provide 2 to 64 samples from -1 to 1. The table uses stepped lookup without interpolation.
327
331
 
328
332
  ```javascript
329
333
  synth.playTone({
@@ -397,7 +401,7 @@ synth.setVolume({ volume: 0.7 })
397
401
  synth.stopAll()
398
402
  ```
399
403
 
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.
404
+ 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
405
 
402
406
  ## Output route changes
403
407
 
@@ -512,7 +516,7 @@ if (!playPositionedTone(explosion, 900, 100, 1000, 800)) {
512
516
 
513
517
  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
518
 
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.
519
+ 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
520
 
517
521
  ### Chords and patterns
518
522
 
@@ -552,12 +556,12 @@ Measure CPU cost on the intended device with realistic event overlap. The cost d
552
556
 
553
557
  ## Read engine statistics
554
558
 
555
- Basic counters are available without enabling optional diagnostics. Reads do not start audio or reset counters. Detect the methods if supporting older development packages that also report 1.0.0:
559
+ Basic counters are available without enabling optional diagnostics. Reads do not start audio or reset counters. The diagnostic controls are:
556
560
 
557
561
  ```javascript
558
562
  var statsTimer = null
559
563
  var statsSupported = typeof synth.getStats === 'function' &&
560
- typeof synth.setStatsEnabled === 'function' && typeof synth.resetStats === 'function'
564
+ typeof synth.enableStats === 'function' && typeof synth.disableStats === 'function' && typeof synth.resetStats === 'function'
561
565
 
562
566
  function readStats () {
563
567
  var stats = synth.getStats()
@@ -568,7 +572,7 @@ function readStats () {
568
572
 
569
573
  function startDiagnostics () {
570
574
  if (!statsSupported || statsTimer !== null) return false
571
- if (!synth.setStatsEnabled(true)) return false
575
+ if (!synth.enableStats()) return false
572
576
  statsTimer = setInterval(readStats, 200)
573
577
  return true
574
578
  }
@@ -576,7 +580,7 @@ function startDiagnostics () {
576
580
  function stopDiagnostics () {
577
581
  if (statsTimer !== null) clearInterval(statsTimer)
578
582
  statsTimer = null
579
- if (statsSupported) synth.setStatsEnabled(false)
583
+ if (statsSupported) synth.disableStats()
580
584
  }
581
585
 
582
586
  // Read basic counters without enabling timing or signal meters:
@@ -590,7 +594,7 @@ if (statsSupported) readStats()
590
594
 
591
595
  Integrate `stopDiagnostics()` into the existing owner's hide/background/close handlers, before shutdown. Resume polling only while the diagnostic view is active; do not add a second engine owner. Timing and output meters stay `null` until enabled and measured. Basic counters remain available when diagnostics are off. Android backend underrun/buffer/latency fields are best-effort and independent of that switch; iOS returns `null` for those backend fields. A zero underrun count does not prove clean sound.
592
596
 
593
- See the [complete field contract](api.md#getstats), [measurement limits](stats-performance.md) and [interactive statistics controls](../assets/example/README.md#statistics).
597
+ See the [complete field contract](api.md#getstats), [measurement limits](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/stats-performance.md) and [interactive statistics controls](../assets/example/README.md#statistics).
594
598
 
595
599
  ## Next steps
596
600
 
@@ -601,20 +605,22 @@ See the [complete field contract](api.md#getstats), [measurement limits](stats-p
601
605
 
602
606
  ## Diagnose mix headroom
603
607
 
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.
608
+ 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
609
 
606
610
  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
611
 
608
612
 
609
- ## Local native-variation candidate
613
+ <a id="local-native-variation-candidate"></a>
614
+
615
+ ## Variation support
610
616
 
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:
617
+ Source builds produce archives at `android/dist/ti.synthengine-android-2.0.0.zip` and `ios/dist/ti.synthengine-iphone-2.0.0.zip`. Install both matching 2.0.0 packages in the consumer app and select them explicitly:
612
618
 
613
619
  ```xml
614
620
  <modules>
615
- <module platform="android" version="1.1.0">ti.synthengine</module>
616
- <module platform="iphone" version="1.1.0">ti.synthengine</module>
621
+ <module platform="android" version="2.0.0">ti.synthengine</module>
622
+ <module platform="iphone" version="2.0.0">ti.synthengine</module>
617
623
  </modules>
618
624
  ```
619
625
 
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.
626
+ Rebuild and reinstall the application after replacing the native modules. TiSynth 2.0.0 retains 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,24 +1,25 @@
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 `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/instruments-and-effects.md` SHA-256: `3796589dd6b3ac23ad1b9808a17bc1302187b5de980b5b234521246bba745b3c`.
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 the sound. 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 | Sound interface | Who ends the sound? |
12
+ | --- | --- | --- |
13
+ | Play a key until the finger lifts | Held definition's `play()` or `sound.noteOn()` | The owner calls its note control's `noteOff()` |
14
+ | Fire one finite sound | Tone definition's `play()` | Finite duration includes release |
15
+ | Repeat one finite tone or independent layer | Repeat definition's `play()` | Control `stop()` cancels future attacks; admitted attacks finish |
16
+ | Fire a simultaneous chord | Chord definition's `play()` | Native duration and release |
17
+ | Play an ordered score, alarm or pattern | Pattern definition's `play()` | Native clock; control `stop()` releases its voices |
18
+ | Add chorus, delay, reverb or saturation | `synth.setProcessing()` | Shared processor retains bounded tails |
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
- All examples below assume successful startup. Use constants from the installed module and check native acceptance:
22
+ The detailed recipes below use supported direct native calls to explain the rendering parameters. Reusable application code uses the Sound controls above; see the [Sound reference](sound-api.md). All examples assume successful startup:
22
23
 
23
24
  ```javascript
24
25
  var synth = require('ti.synthengine')
@@ -53,9 +54,9 @@ 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
- 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
+ A short envelope does not automatically end a held voice. 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
 
60
61
  ### Instrument demonstrations are musical performances
61
62
 
@@ -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 the retained `repeatTone({ intervalMs, ... })` method 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.
140
147
 
141
148
  ## 4. Polyphony, gain and output protection
142
149
 
@@ -150,7 +157,7 @@ Both musical notes and effects share the voice pool and output chain. Either can
150
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.
151
158
  - Creative `saturation` intentionally changes timbre. Final output limiting protects peaks but is not proof that the earlier mix is clean.
152
159
 
153
- 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.
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](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/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.
154
161
 
155
162
  ## Verify the route you actually ship
156
163