@maccesar/titools 5.2.5 → 5.4.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 (138) hide show
  1. package/CONTRIBUTING.md +1 -1
  2. package/README.md +58 -97
  3. package/agents/ti-pro.md +4 -2
  4. package/commands/ti-audit.md +7 -5
  5. package/docs/examples.md +24 -13
  6. package/docs/knowledge-index.md +2 -1
  7. package/lib/config.js +1 -0
  8. package/package.json +1 -1
  9. package/skills/alloy-guides/SKILL.md +18 -179
  10. package/skills/alloy-guides/references/CLI_TASKS.md +17 -216
  11. package/skills/alloy-guides/references/CONCEPTS.md +27 -162
  12. package/skills/alloy-guides/references/CONTROLLERS.md +38 -236
  13. package/skills/alloy-guides/references/MODELS.md +47 -723
  14. package/skills/alloy-guides/references/MODELS_ADVANCED.md +21 -482
  15. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +12 -222
  16. package/skills/alloy-guides/references/VIEWS_STYLES.md +39 -358
  17. package/skills/alloy-guides/references/VIEWS_XML.md +36 -552
  18. package/skills/alloy-guides/references/WIDGETS.md +49 -127
  19. package/skills/alloy-howtos/SKILL.md +14 -169
  20. package/skills/alloy-howtos/references/best_practices.md +9 -113
  21. package/skills/alloy-howtos/references/cli_reference.md +18 -223
  22. package/skills/alloy-howtos/references/config_files.md +50 -133
  23. package/skills/alloy-howtos/references/debugging_troubleshooting.md +19 -63
  24. package/skills/alloy-howtos/references/samples.md +21 -138
  25. package/skills/ti-api/SKILL.md +10 -2
  26. package/skills/ti-api/references/api-android.md +36 -2
  27. package/skills/ti-api/references/api-app-platform.md +39 -3
  28. package/skills/ti-api/references/api-core.md +50 -18
  29. package/skills/ti-api/references/api-data-network.md +49 -35
  30. package/skills/ti-api/references/api-media.md +45 -27
  31. package/skills/ti-api/references/api-services.md +39 -9
  32. package/skills/ti-api/references/api-ui-android.md +33 -13
  33. package/skills/ti-api/references/api-ui-extras.md +41 -15
  34. package/skills/ti-api/references/api-ui-ios-animator.md +42 -26
  35. package/skills/ti-api/references/api-ui-ios.md +74 -2
  36. package/skills/ti-api/references/api-ui-lists.md +32 -16
  37. package/skills/ti-api/references/api-ui-text-input.md +34 -17
  38. package/skills/ti-api/references/api-ui-views.md +27 -1
  39. package/skills/ti-api/references/api-ui-windows-navigation.md +32 -18
  40. package/skills/ti-api/references/api-xml-global.md +66 -16
  41. package/skills/ti-create-release/SKILL.md +98 -0
  42. package/skills/ti-create-release/references/module-discovery.md +69 -0
  43. package/skills/ti-expert/references/adaptive-layouts.md +1 -1
  44. package/skills/ti-guides/SKILL.md +37 -76
  45. package/skills/ti-guides/references/advanced-data-and-images.md +122 -126
  46. package/skills/ti-guides/references/android-manifest.md +24 -83
  47. package/skills/ti-guides/references/app-distribution.md +31 -377
  48. package/skills/ti-guides/references/application-frameworks.md +127 -335
  49. package/skills/ti-guides/references/cli-reference.md +184 -633
  50. package/skills/ti-guides/references/commonjs-advanced.md +81 -231
  51. package/skills/ti-guides/references/compatibility-matrix.md +36 -75
  52. package/skills/ti-guides/references/hello-world.md +95 -70
  53. package/skills/ti-guides/references/hyperloop-native-access.md +52 -448
  54. package/skills/ti-guides/references/resources.md +174 -142
  55. package/skills/ti-guides/references/sdk-release-notes.md +28 -71
  56. package/skills/ti-guides/references/tiapp-config.md +222 -598
  57. package/skills/ti-howtos/SKILL.md +23 -123
  58. package/skills/ti-howtos/references/cross-platform-development.md +119 -324
  59. package/skills/ti-howtos/references/debugging-profiling.md +77 -445
  60. package/skills/ti-howtos/references/extending-titanium.md +199 -673
  61. package/skills/ti-howtos/references/local-data-sources.md +87 -300
  62. package/skills/ti-howtos/references/location-and-maps.md +86 -213
  63. package/skills/ti-howtos/references/media-apis.md +103 -223
  64. package/skills/ti-howtos/references/notification-services.md +86 -506
  65. package/skills/ti-howtos/references/remote-data-sources.md +97 -316
  66. package/skills/ti-howtos/references/using-modules.md +77 -133
  67. package/skills/ti-synthengine/SKILL.md +62 -111
  68. package/skills/ti-synthengine/assets/example/README.md +50 -59
  69. package/skills/ti-synthengine/assets/example/app.js +67 -65
  70. package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +39 -40
  71. package/skills/ti-synthengine/assets/example/lib/soundPreset.js +34 -0
  72. package/skills/ti-synthengine/assets/index.d.ts +122 -2
  73. package/skills/ti-synthengine/references/advanced-examples.md +4 -4
  74. package/skills/ti-synthengine/references/api.md +40 -18
  75. package/skills/ti-synthengine/references/audio-engine.md +15 -57
  76. package/skills/ti-synthengine/references/examples.md +63 -6
  77. package/skills/ti-synthengine/references/guide.md +61 -57
  78. package/skills/ti-synthengine/references/instruments-and-effects.md +15 -15
  79. package/skills/ti-synthengine/references/native-variation.md +53 -14
  80. package/skills/ti-synthengine/references/recipes-interaction.md +25 -20
  81. package/skills/ti-synthengine/references/recipes.md +19 -16
  82. package/skills/ti-synthengine/references/recommendations.md +12 -12
  83. package/skills/ti-synthengine/references/sound-api.md +197 -0
  84. package/skills/ti-synthengine/references/sound-design.md +7 -7
  85. package/skills/ti-synthengine/references/source-notes.md +62 -172
  86. package/skills/ti-synthengine/references/troubleshooting.md +17 -19
  87. package/skills/ti-synthengine/references/upgrade-2.0.0.md +40 -0
  88. package/skills/ti-ui/SKILL.md +28 -143
  89. package/skills/ti-ui/references/icons-and-splash-screens.md +74 -403
  90. package/skills/ti-ui/references/layouts-and-positioning.md +175 -402
  91. package/skills/ti-ui/references/listviews-and-performance.md +100 -703
  92. package/skills/ti-ui/references/platform-conventions.md +180 -0
  93. package/skills/ti-ui/references/user-interface.md +43 -0
  94. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +0 -102
  95. package/skills/alloy-howtos/references/custom_tags.md +0 -148
  96. package/skills/ti-guides/references/coding-best-practices.md +0 -149
  97. package/skills/ti-guides/references/javascript-primer.md +0 -414
  98. package/skills/ti-guides/references/reserved-words.md +0 -36
  99. package/skills/ti-guides/references/style-and-conventions.md +0 -103
  100. package/skills/ti-howtos/references/android-platform-deep-dives.md +0 -624
  101. package/skills/ti-howtos/references/automation-fastlane-appium.md +0 -96
  102. package/skills/ti-howtos/references/buffer-codec-streams.md +0 -162
  103. package/skills/ti-howtos/references/google-maps-v2.md +0 -172
  104. package/skills/ti-howtos/references/ios-map-kit.md +0 -149
  105. package/skills/ti-howtos/references/ios-platform-deep-dives.md +0 -611
  106. package/skills/ti-howtos/references/tutorials.md +0 -565
  107. package/skills/ti-howtos/references/web-content-integration.md +0 -287
  108. package/skills/ti-howtos/references/webpack-build-pipeline.md +0 -124
  109. package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
  110. package/skills/ti-synthengine/references/android-sim80-correction.json +0 -162
  111. package/skills/ti-synthengine/references/audio-pipeline-validation.md +0 -19
  112. package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
  113. package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
  114. package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
  115. package/skills/ti-synthengine/references/frontier-physical-validation.json +0 -1918
  116. package/skills/ti-synthengine/references/native-tone-repetition-validation.md +0 -59
  117. package/skills/ti-synthengine/references/native-variation-proposal.md +0 -182
  118. package/skills/ti-synthengine/references/native-variation-validation.md +0 -63
  119. package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
  120. package/skills/ti-synthengine/references/performance-review.json +0 -1146
  121. package/skills/ti-synthengine/references/physical-validation.json +0 -1457
  122. package/skills/ti-synthengine/references/source-snapshot.json +0 -542
  123. package/skills/ti-synthengine/references/stats-performance.json +0 -2992
  124. package/skills/ti-synthengine/references/stats-performance.md +0 -82
  125. package/skills/ti-synthengine/references/studio-validation.json +0 -6202
  126. package/skills/ti-synthengine/references/unified-bypass-performance.json +0 -22425
  127. package/skills/ti-synthengine/references/unified-validation.json +0 -314
  128. package/skills/ti-ui/references/accessibility-deep-dive.md +0 -288
  129. package/skills/ti-ui/references/animation-and-matrices.md +0 -569
  130. package/skills/ti-ui/references/application-structures.md +0 -626
  131. package/skills/ti-ui/references/custom-fonts-styling.md +0 -549
  132. package/skills/ti-ui/references/event-handling.md +0 -401
  133. package/skills/ti-ui/references/gestures.md +0 -448
  134. package/skills/ti-ui/references/orientation.md +0 -374
  135. package/skills/ti-ui/references/platform-ui-android.md +0 -727
  136. package/skills/ti-ui/references/platform-ui-ios.md +0 -576
  137. package/skills/ti-ui/references/scrolling-views.md +0 -288
  138. package/skills/ti-ui/references/tableviews.md +0 -542
@@ -1,27 +1,66 @@
1
- > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/native-variation.md` SHA-256: `650d2c675dc4b4e09a85521812b7fedd3b02cb993a451378a8082e2b7782303a`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/native-variation.md` SHA-256: `6cfd65b0741dea79d37bb5eee14d1bcb98445ee6b5db14a8c00e3ccfd00a5190`.
2
2
 
3
3
  # Native per-attack variation
4
4
 
5
5
  `variation` lets the engine choose bounded parameter values when it admits an attack. Repeated shots can have different pitch, noise color or filter settings without JavaScript randomization. Each voice keeps the chosen values for its lifetime; existing envelopes, sweeps and LFOs continue from that starting configuration.
6
6
 
7
- Android and iOS support this dictionary in the 1.1.0 packages, with public API version `1.1.0` and variation schema version `1`. The published 1.0.2 packages do not accept it.
7
+ Android and iOS support this dictionary in TiSynth 2.0.0. The variation data schema is `1`. Store it in `sound.variation`; a pattern step can supply `steps[i].sound.variation`.
8
8
 
9
- ## Select version 1.1.0
9
+ <!-- TOC-START -->
10
+ ## Contents
10
11
 
11
- Install the matching Android and iOS 1.1.0 archives in the app and select them in `tiapp.xml`:
12
+ - [Reuse a sound with variation](#reuse-a-sound-with-variation)
13
+ - [Select the module version](#select-the-module-version)
14
+ - [Detect support and start playback](#detect-support-and-start-playback)
15
+ - [Supported locations](#supported-locations)
16
+ - [Dictionary reference](#dictionary-reference)
17
+ - [What amount means](#what-amount-means)
18
+ - [Repeated independent effects](#repeated-independent-effects)
19
+ - [Chords and application gain](#chords-and-application-gain)
20
+ - [Sustained voices and explicit live edits](#sustained-voices-and-explicit-live-edits)
21
+ - [Native loops and dictionary inheritance](#native-loops-and-dictionary-inheritance)
22
+ - [Seeds and reproducibility](#seeds-and-reproducibility)
23
+ - [Rejected configurations](#rejected-configurations)
24
+ - [Studio and game integration](#studio-and-game-integration)
25
+ - [Implementation and verification](#implementation-and-verification)
26
+
27
+ <!-- TOC-END -->
28
+
29
+ ## Reuse a sound with variation
30
+
31
+ ```javascript
32
+ var synth = require('ti.synthengine')
33
+ var bell = synth.createSound({
34
+ sound: {
35
+ waveType: synth.getContract().waveTypes.SINE,
36
+ volume: 0.25, attack: 5, release: 120,
37
+ variation: { amount: 0.6, ranges: { volume: [0.18, 0.25], pitchCents: [-8, 8] } }
38
+ },
39
+ playback: { type: 'tone', notes: ['A4'], duration: 400 }
40
+ })
41
+
42
+ bell.play()
43
+ bell.play({ notes: ['C5'] })
44
+ ```
45
+
46
+ Call playback after engine startup. The detailed native dictionaries below explain source restrictions and inheritance. For Sound patterns, root variation belongs in `sound.variation` and a step replacement belongs in `steps[i].sound.variation`.
47
+
48
+ ## Select the module version
49
+
50
+ Install the matching Android and iOS 2.0.0 archives in the app and select them in `tiapp.xml`:
12
51
 
13
52
  ```xml
14
53
  <modules>
15
- <module platform="android" version="1.1.0">ti.synthengine</module>
16
- <module platform="iphone" version="1.1.0">ti.synthengine</module>
54
+ <module platform="android" version="2.0.0">ti.synthengine</module>
55
+ <module platform="iphone" version="2.0.0">ti.synthengine</module>
17
56
  </modules>
18
57
  ```
19
58
 
20
- Rebuild and reinstall the application after replacing native modules. Copying module files alone does not update an installed app. Keep the published 1.0.2 archives separate; version 1.1.0 reports API `1.1.0`.
59
+ Rebuild and reinstall the application after replacing native modules. Copying module files alone does not update an installed app. Keep historical released archives separate. The installed module version is available as `getDefaults().version`.
21
60
 
22
61
  ## Detect support and start playback
23
62
 
24
- Check both metadata fields. API version alone does not identify variation support. Omit the property completely when either announcement is missing, including when the requested amount is zero.
63
+ Check both capability/schema fields, even when the module version matches the one you expect. Omit the property completely if either field is missing, including when the requested amount is zero.
25
64
 
26
65
  ```javascript
27
66
  var synth = require('ti.synthengine')
@@ -133,11 +172,11 @@ function fireLaser() {
133
172
  fireLaser()
134
173
  ```
135
174
 
136
- The engine samples after admission and before initializing filters, smoothing controls and trajectories. Rejected attacks do not consume a selection. Existing release tails, capacity policy and voice replacement still apply.
175
+ The engine samples after admitting an attack, before initializing its filters, smoothing controls and trajectories. Rejected attacks consume no selection. Release tails, capacity policy and voice replacement remain unchanged.
137
176
 
138
177
  ## Chords and application gain
139
178
 
140
- One chord is one atomic publication. Each voice uses its position within that attack to obtain independent field samples; it does not reuse the first voice's result. If the chord cannot be admitted, none of its voices starts and no event is consumed.
179
+ A chord is published atomically. Each voice's position within the attack determines its independent field samples, so later voices do not reuse the first voice's result. If the chord cannot be admitted, none of its voices starts and no event is consumed.
141
180
 
142
181
  Scale a volume range with the same application gain as the authored volume. Let the engine apply its existing chord compensation once.
143
182
 
@@ -234,7 +273,7 @@ if (supportsVariation && sequenceId > 0) {
234
273
  }
235
274
  ```
236
275
 
237
- Stop the owned sequence through the normal lifecycle when it is no longer needed. Stop/new play starts a new stream.
276
+ Stop the owned sequence during normal lifecycle cleanup when it is no longer needed. Stopping and starting playback again creates a new stream.
238
277
 
239
278
  ## Seeds and reproducibility
240
279
 
@@ -276,10 +315,10 @@ Also check pitch corners near 1 Hz or the reported ceiling. A valid base pitch c
276
315
 
277
316
  Studio calls this control Variantes. Its displayed percentage maps linearly: 25% sends native amount `0.25`, 50% sends `0.5`, and 100% sends `1`. Studio prepares broader ranges for its editor. The games retain their authored range profiles.
278
317
 
279
- Frontier automatically applies authored ranges only to its eligible destruction sounds; Guardian applies them only to its two lasers. Both require the capability/schema pair and keep their existing recipes on older modules. Studio allows any source to opt in through compatible parameter ranges. See [Studio's control and preset guide](https://github.com/macCesar/TiSynth-Studio/blob/main/documentation/variation.md); the native contract in this document does not depend on a consumer app.
318
+ Frontier automatically applies authored ranges only to its eligible destruction sounds; Guardian applies them only to its two lasers. Both require the capability/schema pair. Studio allows any source to opt in through compatible parameter ranges. See [Studio's control and preset guide](https://github.com/macCesar/TiSynth-Studio/blob/main/documentation/variation.md); the native contract in this document does not depend on a consumer app.
280
319
 
281
320
  ## Implementation and verification
282
321
 
283
- Both renderers use [`shared/synth_variation.h`](https://github.com/macCesar/TiSynthEngine/blob/7cd035409453b01df2736aef56f2316d94ca019c/shared/synth_variation.h): stable schema slots, integer-defined SplitMix64 finalization and exact uint32-to-uniform conversion. Parsing prepares a fixed 144-byte configuration outside the audio callback. The callback performs bounded arithmetic without allocation, locks, JavaScript calls or entropy services.
322
+ Both renderers use [`shared/synth_variation.h`](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/shared/synth_variation.h): stable schema slots, integer-defined SplitMix64 finalization and exact uint32-to-uniform conversion. Parsing prepares a fixed 144-byte configuration outside the audio callback. The callback performs bounded arithmetic without allocation, locks, JavaScript calls or entropy services.
284
323
 
285
- Focused checks cover known vectors, both native validators, attack admission, chord ordinals, sustained stability, loops, revisions, rejection and old-renderer compatibility with controlled noise initialization. [Technical verification](native-variation-validation.md) records the measured memory/callback cost and its limits. Host results do not certify physical latency or listening quality on every device.
324
+ Focused checks cover known vectors, both native validators, attack admission, chord ordinals, sustained stability, loops, revisions, rejection and old-renderer compatibility with controlled noise initialization. [Technical verification](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/native-variation-validation.md) records the measured memory/callback cost and its limits. Host results do not certify physical latency or listening quality on every device.
@@ -1,10 +1,16 @@
1
1
  # Instrument, polyphony and expressive recipes
2
2
 
3
- These curated TiTools recipes use the same running engine and runtime `WAVE`
4
- constants as [the arcade recipes](recipes.md). Keep one owner, check native
5
- acceptance and use their [verification checklist](recipes.md#recipe-verification-checklist).
6
- The expressive envelope/filter/FM ranges are starting points verified against
7
- the public contract, not device-auditioned presets. Listen before shipping.
3
+ These TiTools recipes use the supported direct interface in 2.0.0 and share
4
+ the running engine and runtime `WAVE` constants from
5
+ [the arcade recipes](recipes.md). Keep one engine owner, check native acceptance
6
+ and follow the [verification checklist](recipes.md#recipe-verification-checklist).
7
+
8
+ The envelope, filter and FM ranges are starting points checked against the
9
+ public contract. They have not been auditioned on a device, so listen before
10
+ shipping.
11
+
12
+ For new reusable instruments, read [Sound definitions and controls](sound-api.md).
13
+ Keep these recipes' gains, pitch arrays, envelopes and ownership when adapting them.
8
14
 
9
15
  ```javascript
10
16
  const synth = require('ti.synthengine')
@@ -114,9 +120,8 @@ In a Titanium view:
114
120
  - call `gesture.finish()` on both `touchend` and `touchcancel`;
115
121
  - keep visual pressed-state counts separate if multiple touches are supported.
116
122
 
117
- The audio release may continue after the finger leaves a bar. That is desired
118
- for a xylophone. `stopAll()` is a global panic: it also cancels every native
119
- sequence and invalidates shared effect histories. Use an owner's
123
+ For a xylophone, let the audio release continue after the finger leaves a bar.
124
+ `stopAll()` is a global panic: it also cancels every native sequence and invalidates shared effect histories. Use an owner's
120
125
  `stopSequence({ sequenceId })` when only that sequence should stop.
121
126
 
122
127
  ## Four-pad memory cues
@@ -169,8 +174,8 @@ function playMemoryBank (bankName) {
169
174
 
170
175
  ### Controlled timbre comparison
171
176
 
172
- For an honest A/B test, hold pitches, duration, envelope and pan constant.
173
- Change only waveform and its comparison level. Never pass a profile object
177
+ For an A/B test, hold pitches, duration, envelope and pan constant. Change
178
+ only the waveform and its comparison level. Never pass a profile object
174
179
  containing labels or other app metadata directly to `playChord()` because its
175
180
  options dictionary is closed.
176
181
 
@@ -202,8 +207,8 @@ function auditionTimbre (profileName) {
202
207
 
203
208
  ### Convert MIDI pitch to hertz
204
209
 
205
- The standard A4 = 440 Hz formula makes it easy to build interval recipes
206
- without embedding a long frequency table.
210
+ Use the standard A4 = 440 Hz formula to build interval recipes without
211
+ embedding a long frequency table.
207
212
 
208
213
  ```javascript
209
214
  function midiToHz (midi) {
@@ -337,9 +342,9 @@ This repeated chord uses one centered native sequence. The one-off layered chord
337
342
 
338
343
  ### Cancellable repetition
339
344
 
340
- The chord pulse above has an identified sequence owner. Repeated start requests coalesce until its matching `sequenceend` retirement frees the slot. Stop its sequence ID and cancel pending restarts when its window closes; its native sequence schedules subsequent chord attacks.
345
+ The chord pulse above belongs to one identified sequence owner. Repeated start requests coalesce until the matching `sequenceend` event retires it and frees the slot. When its window closes, stop that sequence ID and cancel pending restarts. The native sequence schedules subsequent chord attacks.
341
346
 
342
- For repetition of an original finite tone in API 1.1.0, use `repeatTone({ intervalMs, ...recipe })`, retain its positive tone ID, edit future attacks with `updateTone`, and cancel with `stopTone`. Each independent layer retains its own tone ID and overlap budget. `stopTone` preserves admitted finite attacks; `stopSequence` releases its sequence's voices. Tone IDs have no completion event, so the sequence retirement/restart procedure above does not apply to them. Independent event calls and intentionally timed finite JavaScript bursts remain supported; cancel any app-owned timers at teardown. See [the repetition contract](api.md#finite-tone-repetition) and [complete effect fidelity](instruments-and-effects.md#repeated-firing-uses-the-native-clock).
347
+ For direct repetition of an original finite tone, use `repeatTone({ intervalMs, ...recipe })`, retain its positive tone ID, edit future attacks with `updateTone`, and cancel with `stopTone`. Each independent layer retains its own tone ID and overlap budget. `stopTone` preserves admitted finite attacks; `stopSequence` releases its sequence's voices. Tone IDs have no completion event, so the sequence retirement/restart procedure above does not apply to them. Independent event calls and intentionally timed finite JavaScript bursts remain supported; cancel any app-owned timers at teardown. See [the repetition contract](api.md#finite-tone-repetition) and [complete effect fidelity](instruments-and-effects.md#repeated-firing-uses-the-native-clock).
343
348
 
344
349
  ```javascript
345
350
  function cleanupAudio () {
@@ -356,9 +361,9 @@ Call `shutdown()` only from the screen or service that owns the shared engine.
356
361
 
357
362
  ### Plucked marimba note
358
363
 
359
- A short attack, a decay that falls to a low sustain and a release that fits
360
- give the struck-and-ring shape of a mallet or plucked string. A sustain below 1
361
- needs a decay, and `attack + decay` must fit inside `duration`.
364
+ A short attack followed by decay to a low sustain gives the strike and ring
365
+ of a mallet or plucked string. Keep the release within the available duration.
366
+ A sustain below 1 needs a decay, and `attack + decay` must fit inside `duration`.
362
367
 
363
368
  ```javascript
364
369
  synth.playTone({
@@ -412,9 +417,9 @@ synth.playTone({
412
417
 
413
418
  ### Electric piano
414
419
 
415
- Frequency modulation with a 1:1 ratio gives a tine-like tone. Letting the
416
- modulation index fall through `fmDepthEnd` makes the note mellow as it rings,
417
- which is what the ear expects from a struck tine. FM needs a sine carrier.
420
+ Frequency modulation with a 1:1 ratio gives a tine-like tone. Reduce the
421
+ modulation index through `fmDepthEnd` so the note mellows as it rings, as a
422
+ struck tine does. FM needs a sine carrier.
418
423
 
419
424
  ```javascript
420
425
  synth.playTone({
@@ -1,11 +1,13 @@
1
1
  # Curated sound and interaction recipes
2
2
 
3
- These recipes use the core public contract shared by `ti.synthengine`
4
- 1.0.2 / API 1.0.0 and 1.1.0 / API 1.1.0. They use only public option keys,
5
- keep every explicit envelope valid, and obtain
6
- waveforms from the runtime contract instead of hardcoding integers. Install the
7
- matching official Android/iOS packages and follow the owning-engine
8
- [installation and lifecycle guide](guide.md) before using them.
3
+ These recipes use the supported direct native interface in `ti.synthengine` 2.0.0
4
+ and earlier 1.x modules. For reusable definitions in new code, use
5
+ [Sound objects](sound-api.md) while preserving how each recipe renders.
6
+
7
+ The examples use public option keys and valid explicit envelopes. They read
8
+ waveforms from the runtime contract instead of hardcoding integers. Install
9
+ matching official Android/iOS packages and follow the
10
+ [installation and lifecycle guide](guide.md) to set up the engine owner.
9
11
 
10
12
  Unless a section says otherwise, the snippets assume one owner has already
11
13
  started the engine:
@@ -16,13 +18,13 @@ const contract = synth.getDefaults()
16
18
  const WAVE = contract.waveTypes
17
19
  ```
18
20
 
19
- Treat levels as starting points, not mastering targets. Test on the actual
20
- phone speaker and leave headroom when release tails or independent events can
21
- overlap.
21
+ Use the levels as starting points for listening tests, rather than final
22
+ mastering targets. Test on the actual phone speaker and leave headroom for
23
+ overlapping release tails or independent events.
22
24
 
23
- Instrument interactions, layered chords and expressive synthesis are in
24
- [the interaction recipes](recipes-interaction.md). Their standard synthesis
25
- starting ranges require listening on the target device before shipping.
25
+ For instrument interactions, layered chords and expressive synthesis, see
26
+ [the interaction recipes](recipes-interaction.md). Listen to those starting
27
+ ranges on the target device before shipping.
26
28
 
27
29
  <!-- TOC-START -->
28
30
  ## Contents
@@ -166,8 +168,8 @@ if (!sequenceId) throw new Error('Sequence rejected')
166
168
 
167
169
  ### Fast four-note arpeggio
168
170
 
169
- Very short square notes with a small attack retain the hard cartridge-era
170
- edge while reducing uncontrolled clicks. The 10 ms rests keep the first three
171
+ Very short square notes with a small attack keep the hard cartridge-era
172
+ edge and reduce uncontrolled clicks. The 10 ms rests keep the first three
171
173
  40 ms notes on a 50 ms grid; the final note rings for 80 ms.
172
174
 
173
175
  ```javascript
@@ -404,8 +406,9 @@ if (!sequenceId) throw new Error('Sequence rejected')
404
406
 
405
407
  ### Hovering craft
406
408
 
407
- A triangle voice with strong sine vibrato creates a cyclical engine wobble. Slow stereo travel adds movement on headphones while pitch
408
- remains the primary cue on mono speakers.
409
+ A triangle voice with strong sine vibrato creates a repeating engine wobble.
410
+ Slow stereo travel adds movement on headphones; pitch remains the primary cue
411
+ on mono speakers.
409
412
 
410
413
  ```javascript
411
414
  synth.playTone({
@@ -1,4 +1,4 @@
1
- > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/recommendations.md` SHA-256: `f72c3df9c36df0cbdb843bbe2c6799ae537324ece499fe6d1a2de80e43df5261`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/recommendations.md` SHA-256: `0e7c55830b05ef21bdd1fd125e7d158ea5b598e02618202e65ddd8b8d2044cab`.
2
2
 
3
3
  # Recommendations
4
4
 
@@ -6,9 +6,9 @@ The defaults work for ordinary tones and chords. Use the guidance below when an
6
6
 
7
7
  ## Group simultaneous notes
8
8
 
9
- Use `playChord()` when pitches form one known simultaneous event. The chord is atomic. Protected profiles normalize per-voice source gain by the square root of the event voice count; `raw` bypasses that normalization. Separate `playTone()` calls are appropriate for independent touches and effects, but their start times and gain are independent too.
9
+ Declare `playback.type: 'chord'` when pitches form one known simultaneous event. The chord is atomic. Protected profiles normalize per-voice source gain by the square root of the event voice count; `raw` bypasses that normalization. Separate tone executions suit independent effects, but their start times and gain are independent too.
10
10
 
11
- For keys or pads a user can trigger freely, keep each event around 0.4 to 0.7 until you have listened to the worst overlap. If every event needs gain above 1, consider `balanced` for the session before raising individual notes further.
11
+ For freely triggered keys or pads, keep each event around 0.4 to 0.7 until you have listened to the worst overlap. If every event needs gain above 1, consider `balanced` for the session before raising individual notes further.
12
12
 
13
13
  ## Choose the mixing profile once
14
14
 
@@ -25,7 +25,7 @@ If a phone buzzes under `speakerSafe`, lower the event volume or raise the pitch
25
25
 
26
26
  ## Size `maxVoices` for real overlap
27
27
 
28
- `maxVoices` controls capacity. It does not make the engine louder or quieter.
28
+ `maxVoices` sets voice capacity without changing event gain.
29
29
 
30
30
  | App behavior | Starting value |
31
31
  | ---------------------------------- | -------------: |
@@ -34,7 +34,7 @@ If a phone buzzes under `speakerSafe`, lower the event volume or raise the pitch
34
34
  | Chords, keys or long release tails | 16 to 24 |
35
35
  | Explicit stress testing | 32 |
36
36
 
37
- Count release tails along with buttons pressed at the same instant. Replacement first selects the lowest eligible priority; higher-priority voices are protected. Among that priority, timed-only events steal the slot with the least time remaining; sustained/transport voices prefer a releasing slot, then the oldest attack. The short fade avoids a hard click; the stolen note still ends early.
37
+ Count release tails as well as buttons pressed at the same instant. Replacement first selects the lowest eligible priority; higher-priority voices are protected. Among that priority, timed-only events steal the slot with the least time remaining; sustained/transport voices prefer a releasing slot, then the oldest attack. The short fade avoids a hard click; the stolen note still ends early.
38
38
 
39
39
  ## Keep one lifecycle owner
40
40
 
@@ -67,7 +67,7 @@ The resonant filter, FM and stacked detuned voices change level and spectrum mor
67
67
  - A resonance that keeps reaching the limiter makes the whole mix pump. Lower the volume or the Q before reaching for a different profile.
68
68
  - FM is not band-limited. A high carrier with a large `fmDepth` and `fmRatio` aliases. Keep carriers of bell-like sounds around 500 Hz, or lower the index.
69
69
  - A detuned stack is one voice per copy. Count them when choosing `maxVoices`. Protected profiles divide per-voice source gain by the square root of the event voice count; `raw` does not. Correlated or phase-aligned voices can still reinforce each other, so this is not a guarantee of constant loudness or peaks.
70
- - `sequenceend` reports completion or stopping by sequence identifier. Put contiguous notes into one sequence when sample timing matters; the event crosses JavaScript delivery latency.
70
+ - A pattern control emits `end` on completion or stopping. Put contiguous notes into one pattern when sample timing matters; the event crosses JavaScript delivery latency.
71
71
 
72
72
  ## Validate values at the app boundary
73
73
 
@@ -87,7 +87,7 @@ Do not copy the frequency maximum into application code. It depends on the activ
87
87
 
88
88
  The same rule applies to `noiseRate`, `lowPassHz` and `filterHz`. Validate a custom `waveTable` as 2 to 64 finite values from -1 to 1, and remember that `bitDepth` accepts zero or an integer from 2 to 16. For `detune`, check the resulting pitch too: a shifted pitch that leaves the frequency range is rejected even when the cents value is in range.
89
89
 
90
- Check each method's documented return value during development: Boolean publication acceptance or a positive sequence ID, with 0 rejecting a sequence. Methods that emit structured validation warnings include the method, key and reason in the native log, but some sequence/control rejection paths do not log. Logging the return at the caller helps identify a stale preset or misspelled field without treating silence in the native log as acceptance.
90
+ Check each method's documented return value during development. Sound playback returns finite acceptance, a playback control, or `false` on operational rejection; invalid Sound configurations throw. Direct native methods keep their documented Boolean or identifier returns. Methods that emit structured validation warnings include the method, key and reason in the native log, but some sequence/control rejection paths do not log. Logging the return at the caller helps identify a stale preset or misspelled field. A silent native log does not establish acceptance.
91
91
 
92
92
  ## Test the intended sound
93
93
 
@@ -111,13 +111,13 @@ Use the [integration guide](guide.md#integrate-binaural-in-a-2d-app) to convert
111
111
 
112
112
  ## Known limits
113
113
 
114
- - Use `noteOn` with app-supplied IDs for individual release and compatible live updates. Check the required methods before enabling controls; timed events do not return voice handles.
114
+ - Keep the control returned by `sound.noteOn()` or held `play()` for release and compatible live updates. Finite tones and chords return no individual control.
115
115
  - Timed tones and chords last at most 60 seconds including release. Sequence intervals and explicit note lengths use the sequence unit, at most 300 beats or 60,000 ms; sequence ADSR remains in milliseconds and release may extend beyond the interval. Sustained `noteOn` voices last until release, replacement or shutdown.
116
116
  - The module synthesizes waveforms and does not play audio files.
117
117
  - One native transport supports up to four independent sequences, preallocated by `maxSequences`. All share at most 32 voices.
118
- - A single tone or chord has no completion event. Sequences emit `sequenceend` with their identifier and completed/stopped reason.
118
+ - A single finite tone or chord has no completion event. A pattern control emits `end` with its completed/stopped reason.
119
119
  - Voice capacity and mixing profile remain fixed until shutdown.
120
- - Sequences run on the sample clock in beats or milliseconds. `duration` controls intervals; `length` independently controls note duration.
120
+ - Patterns run on the sample clock in beats or milliseconds. Root and step `interval` control onset spacing; `length` independently controls note duration.
121
121
  - `lowPassHz` is a plain one-pole filter. The resonant filter has fixed 12 dB per octave and one exponential sweep; there is no filter envelope.
122
122
  - FM has one modulator and works only on `SINE`.
123
123
  - Binaural positions occupy a front plane. Spatial held voices support smooth live movement; there is no rear positioning, distance attenuation or head tracking. Output is two channels, without a multichannel 2.1/5.1 routing API.
@@ -128,10 +128,10 @@ Use a sample player or a dedicated music engine when a product needs streaming,
128
128
 
129
129
  ## Use effects and held voices deliberately
130
130
 
131
- Use `noteOn` for a sound whose duration follows interaction, such as a key, motor or beam; release its ID on touch end, cancellation, pause or scene teardown. Use `playSequence` for a finite musical cue or repeating grid. Neither needs JavaScript to keep an already accepted note alive. Both share the same 32-slot maximum with collision and interface tones; held notes can be stolen, and an accepted `updateNote` on a stolen ID is a no-op. Optional priority 0-3 affects stealing; it reserves no voices. There is no voice-finished event.
131
+ Use `sound.noteOn()` for a sound whose duration follows interaction, such as a key, motor or beam; release its note control on touch end, cancellation, pause or scene teardown. Use a pattern definition for a finite musical cue or repeating grid. The native engine sustains notes and schedules attacks. Both share the same 32-voice maximum with collision and interface tones; held notes can be stolen, and updates to a stolen note are accepted as no-ops. Optional priority 0-3 affects stealing without reserving voices. There is no voice-finished event.
132
132
 
133
133
  Keep chorus/delay/reverb mixes modest when dialogue or short game feedback must remain distinct. By default they process all four categories, so enabling reverb for a victory chord also affects simultaneous shots and collisions. Select categories with `applyTo` and assign each voice a `bus`, or use explicit `effectSend` values to choose the processed share. All selected voices share one effects chain. Disabling effects lets tails drain for at most 30 seconds; use panic when immediate history invalidation is required. Start with saturation disabled and compare creative drive at matched levels. Do not use saturation as an automatic substitute for the final limiter.
134
134
 
135
135
  Fixed input/event gain does not decrease when unrelated voices start. If level dips, inspect `maxLimiterReductionDb` and `preLimiterPeakDb`, then consider same-pitch phase cancellation or the device's speaker protection. Eight coherent independent voices can still exceed the limiter threshold; module capacity is not a clean-mix loudness guarantee. See [measurement example](advanced-examples.md#measure-peaks-and-limiter-reduction).
136
136
 
137
- Measure the complete workload on each target device, including held voices, binaural, LFOs, all enabled effects and tails. The [historical statistics benchmark](stats-performance.md) predates these features; the [current technical report](studio-validation.json) includes an unmet full-chain host deadline criterion. Neither is a guarantee of game-frame rate, physical audio deadlines, heat or battery life.
137
+ Measure the complete workload on each target device, including held voices, binaural, LFOs, all enabled effects and tails. The [historical statistics benchmark](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/stats-performance.md) predates these features; the [current technical report](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/docs/validation/studio-validation.json) includes an unmet full-chain host deadline criterion. Neither is a guarantee of game-frame rate, physical audio deadlines, heat or battery life.
@@ -0,0 +1,197 @@
1
+ > Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/sound-api.md` SHA-256: `bb7bfe10a63aba700bdd3584e803436852e07200b82798f5f16c83383687a546`.
2
+
3
+ # Sound objects: TiSynth 2.0.0
4
+
5
+ TiSynth 2.0.0 provides reusable sound definitions and independent playback controls on Android and iOS. `createSound()` allocates no voice. Its definition survives `shutdown()` and can play again after `start()`. Rendering, gain, admission limits and scheduling use the existing native tone, chord, held, repetition and sequence paths.
6
+
7
+ TiSynth uses one version: the installed module version. Both `getContract().version` and `getDefaults().version` report `2.0.0`. `getContract()` also describes `capabilities.soundObjects: true`, supported interpretations, property names and units.
8
+
9
+ ## First sound
10
+
11
+ ```javascript
12
+ const synth = require('ti.synthengine')
13
+ const contract = synth.getContract()
14
+ if (contract.version !== '2.0.0') throw new Error('Sound objects require TiSynth 2.0.0')
15
+ if (!synth.start({ maxVoices: 16, maxSequences: 4, mixingProfile: 'conservative', audioSession: 'ambient' })) {
16
+ throw new Error('Audio startup rejected')
17
+ }
18
+ const bell = synth.createSound({
19
+ name: 'Bell',
20
+ sound: { waveType: contract.waveTypes.SINE, volume: 0.25, attack: 5, release: 120 },
21
+ playback: { type: 'tone', notes: ['A4'], duration: 400 }
22
+ })
23
+ if (!bell.play()) throw new Error('Bell rejected')
24
+ bell.play({ notes: ['C5'], volume: 0.15 })
25
+ synth.setVolume(0.8)
26
+ synth.setTempo(110)
27
+ ```
28
+
29
+ Start the engine after the owning screen has settled, and call `shutdown()` when its audio owner closes. Follow the [lifecycle guidance](guide.md#lifecycle) and plan the [voice budget](recommendations.md). A later higher-priority event can replace a finite event that was accepted earlier.
30
+
31
+ ## Stored definitions and temporary overrides
32
+
33
+ A preset has optional `name`, `sound` and `playback` dictionaries. `sound` holds synthesis and routing; `playback` holds pitch and interpretation. `getPreset()` returns a detached copy, preserving whether `type` was authored or inferred. Inputs are copied too.
34
+
35
+ `Sound.set({ sound, playback })` recursively merges objects, replaces whole arrays and validates the complete result before replacing the stored definition. A failed change throws without partially applying it. `null` removes an optional leaf when the remaining preset is valid; it does not remove a whole `sound` or `playback` dictionary. Arrays, including `steps`, `notes`, `frequencies`, `waveTable`, `modulations` and variation ranges, replace the complete array; they do not concatenate. `set()` affects future executions; it does not edit controls already playing.
36
+
37
+ `Sound.play(changes)` and `Sound.noteOn(changes)` accept **flat** overrides, such as `{ notes: ['D4'], volume: 0.2 }`. They do not accept nested `{ sound, playback }` arguments. An execution never mutates the stored preset. Repetition and pattern updates also accept `null` to remove an optional leaf when the complete resulting revision is valid. Such resets replace the native recipe without restarting its transport or repetition deadline. Held-note updates require concrete live values; use supported disabling values such as zero or an empty modulation array. Setting `notes` replaces a previously stored `frequencies` source and vice versa; supplying both together throws.
38
+
39
+ ```javascript
40
+ const sound = synth.createSound({
41
+ sound: { volume: 0.3, pan: 0.2, variation: { amount: 1, ranges: { volume: [0.2, 0.3] } } },
42
+ playback: { notes: ['C4'], duration: 300 }
43
+ })
44
+ sound.set({ sound: { pan: null, variation: { amount: 0.5 } } })
45
+ sound.set({ playback: { notes: ['D4'] } })
46
+ const preset = sound.getPreset()
47
+ // The volume range is retained; pan is absent; notes is now exactly ['D4'].
48
+ if (preset.sound.pan !== undefined || preset.playback.notes.length !== 1) throw new Error('Unexpected preset')
49
+ sound.play({ notes: ['E4'] })
50
+ ```
51
+
52
+ Unknown keys, wrong types, incompatible fields and out-of-range values throw descriptive `TypeError`s before publication. Fields are never silently stripped to make a definition valid. Numeric ranges and waveform-dependent rules remain those in the [native reference](api.md#strict-validation). Operational failures, including an unavailable engine or a rejected publication, return `false`.
53
+
54
+ ## Interpretations and returns
55
+
56
+ An explicit type on the call wins over the preset's declared type. If neither declares a type, `steps` infers a pattern, more than one pitch infers a chord, and otherwise a tone is inferred. Inference never writes a type into the stored preset. Repetition and held interpretation require an explicit type, or `noteOn()` for held execution.
57
+
58
+ | Type or method | Accepted return | Playback fields | Native path |
59
+ | --- | --- | --- | --- |
60
+ | `tone` | `true` | `notes` or `frequencies`, `duration`, `frequencyEnd` | One finite tone |
61
+ | `chord` | `true` | `notes` or `frequencies`, `duration` | Atomic finite chord, including an explicitly declared one-note chord |
62
+ | `repeat` | Repetition control | Single pitch, `duration`, `frequencyEnd`, required `interval` | Native finite-tone repetition |
63
+ | `pattern` | Pattern control | `steps`, `interval`, `timeUnit`, `gate`, `loop`, `swing`, `mode`, `tuning`, optional root `frequencyEnd` | Native sequence |
64
+ | `held` or `Sound.noteOn()` | Note control | Single pitch only | Existing sustained voice |
65
+
66
+ All executions can return `false` on operational rejection. Finite tones and chords expose no individual control or identifier. A declared type remains in force when pitches change: `type: 'tone'` with two notes throws; an explicit `type: 'chord'` with one note retains chord gain and admission behavior. Choosing another type does not discard incompatible preset fields: clear or replace them in the definition first.
67
+
68
+ Use either `notes` as note names or integer MIDI notes 12 through 119, or `frequencies` in hertz. Tone, held and repetition use one-element arrays. Chords use 1 through 32 pitches. Numeric pitches in finite/held calls convert to native note names; pattern pitches remain MIDI integers. Keep duplicate pitches when unison is intentional.
69
+
70
+ Each interpretation has restrictions beyond the shared sound-property allowlist below. Finite-tone endpoints cannot be used as held live controls, and the native chord validator rejects sweeps and modulations. Source rules require sine for FM, square for PWM, an active filter for filter targets, and compatible stereo or spatial controls. Use explicit `type: 'held'` when a definition contains held-only modulation or envelopes so construction validates the intended native path.
71
+
72
+ ## Held notes
73
+
74
+ ```javascript
75
+ const heldSound = synth.createSound({
76
+ sound: { waveType: 0, volume: 0.2, fmRatio: 2, fmDepth: 1, attack: 10, release: 160,
77
+ modulations: [{ target: 'fmDepth', wave: 'sine', rate: 2, depth: 0.1 }] },
78
+ playback: { type: 'held', notes: ['C4'] }
79
+ })
80
+ const note = heldSound.play()
81
+ if (note) {
82
+ note.update({ notes: ['D4'], volume: 0.15, fmDepth: 0 })
83
+ if (!note.noteOff()) throw new Error('Note release rejected; owner must retry or close audio')
84
+ }
85
+ const anotherNote = heldSound.noteOn({ notes: ['E4'] })
86
+ if (anotherNote) anotherNote.noteOff()
87
+ ```
88
+
89
+ Each start returns an independent `{ update, noteOff }` control with private identity. No `voiceId` is accepted or exposed. `noteOn()` always uses the single-note held path, even when called on a finite preset; finite `playback.duration` and `playback.frequencyEnd` are not applied to that held execution. Incompatible synthesis leaves, such as `sound.panEnd`, still throw rather than being discarded. It accepts flat synthesis and live pitch overrides, not `type`, `duration` or `frequencyEnd`.
90
+
91
+ `Note.update()` accepts `notes` or `frequencies`, `volume`, `pan`, `lfoFreq`, `lfoDepth`, `noiseRate`, `lowPassHz`, `pulseWidth`, `filterHz`, `filterQ`, `fmRatio`, `fmDepth`, `spatialX`, `spatialY`, `effectSend` and `modulations`. Only compatible source controls can change; variation, waveform, attack/release, bus and priority are not live fields. Live FM depth can reach zero. Updates retain phase and envelope, using the existing native 10 ms transition. They validate the live patch without requiring attack-only settings again.
92
+
93
+ Release on touch cancellation, pause and owner teardown. A rejected release remains retryable; the [held ownership guidance](guide.md#hold-and-edit-a-voice) describes bounded recovery. After an accepted release, that control no longer updates or releases a second time. Ended or stolen voices retain the native no-op edit behavior while their control generation remains valid.
94
+
95
+ ## Repetition
96
+
97
+ ```javascript
98
+ const pulse = synth.createSound({
99
+ sound: { waveType: 1, volume: 0.15, attack: 2, release: 40 },
100
+ playback: { type: 'repeat', frequencies: [440], duration: 120, interval: 200 }
101
+ })
102
+ const repetition = pulse.play()
103
+ if (repetition) {
104
+ repetition.update({ frequencies: [660], interval: 180 })
105
+ repetition.stop()
106
+ }
107
+ ```
108
+
109
+ A repetition exposes `{ update, stop }`. `interval` is integer milliseconds from 1 through 60000. Each attack retains finite-tone duration, envelope and sweep semantics. An update affects the next scheduled attack without moving its existing deadline; the edited interval controls the following gap. Already admitted attacks retain their original recipe. `stop()` cancels future attacks while admitted finite attacks finish. Repetition has no end event and uses its existing capacity independently of pattern slots. Shared voice admission still applies to each attack.
110
+
111
+ ## Patterns
112
+
113
+ ```javascript
114
+ const melody = synth.createSound({
115
+ sound: { waveType: 3, volume: 0.2, attack: 5, release: 80, bus: 'music', priority: 2 },
116
+ playback: { type: 'pattern', timeUnit: 'beats', interval: 0.25, loop: false,
117
+ steps: [{ notes: ['C4'] }, { notes: [64], interval: 0.5, sound: { volume: 0.15 } },
118
+ { rest: true }, { notes: [60, 64, 67], length: 0.4 }] }
119
+ })
120
+ const pattern = melody.play()
121
+ if (pattern) {
122
+ pattern.addEventListener('end', event => {
123
+ // event.reason is 'completed' or 'stopped'. Release and effects may remain audible.
124
+ Ti.API.info(event.reason)
125
+ })
126
+ pattern.update({ swing: 0.1 })
127
+ const state = pattern.getState()
128
+ if (state) Ti.API.info(state.step)
129
+ pattern.stop()
130
+ }
131
+ ```
132
+
133
+ A pattern exposes `{ update, stop, getState, addEventListener, removeEventListener }`. The event name is `end`; its payload is `{ reason: 'completed' | 'stopped' }`, with no native identifier. A control receives completion at most once. `getState()` returns the existing applied transport state with `sequenceId` removed, or `null` when unavailable. Removing a listener requires the same function reference.
134
+
135
+ Root and per-step `interval` specify time until the next attack. `length` still controls sounding length; omitting it uses `gate`. A step contains exactly one of `notes`, `frequencies` or `rest: true`, with optional `interval`, `length`, `gate` and `sound`. Per-step `sound` supplies synthesis settings for that attack. Root `sound.detune` applies the pattern pitch rule. Root `playback.frequencyEnd` maps to the native voice endpoint, while a per-step endpoint belongs in `steps[i].sound.frequencyEnd`.
136
+
137
+ `timeUnit` is `beats` by default or `ms`, and stays fixed during playback. Root interval defaults to 0.25 beats or 100 ms. Patterns contain up to 512 steps and 32 pitches per sounding step, with the existing swing, mode, revision and capacity rules. Updates replace arrays and preserve native revision scheduling; they do not restart transport. Pattern `stop()` remains accepted before its native slot is retired. Coalesce replacements and wait for the owned control's `end` event before publishing the next score when capacity is occupied. An `end` event marks transport retirement, not audible silence: release and processing tails may continue.
138
+
139
+ ## Units and processing
140
+
141
+ | Quantity | Unit and meaning |
142
+ | --- | --- |
143
+ | `sound.attack`, `decay`, `release` | Milliseconds in every interpretation |
144
+ | Tone, chord or repetition `playback.duration` | Total milliseconds including release, including the complete finite envelope |
145
+ | Tone or repetition `playback.frequencyEnd` | Hertz; native finite sweep endpoint |
146
+ | Repetition `playback.interval` | Integer milliseconds, independent of global tempo |
147
+ | Pattern root/step `interval` and step `length` | Beats or milliseconds according to the pattern's `timeUnit` |
148
+ | Pattern `frequencyEnd` | Hertz; native note-length sweep timing with release afterward |
149
+ | `setTempo(number)` | Beats per minute, 20 through 300 |
150
+ | `setProcessing().delay.time` | Milliseconds, 20 through 2000 |
151
+ | `setProcessing().reverb.decay` | Milliseconds, 200 through 8000 |
152
+
153
+ Finite and pattern envelopes are different native paths. Converting a finite total duration of 150 ms with 25 ms release to a millisecond pattern requires `length: 125` and `release: 25` to preserve that sounding lifetime; attack spacing is a separate `interval`. Do not replace grouped chords with independent tone starts or convert repetition into a sequence when preserving the original gain and envelope interpretation.
154
+
155
+ ```javascript
156
+ synth.setProcessing({ inputGain: 1, applyTo: ['music'],
157
+ delay: { enabled: true, time: 250, feedback: 0.25, mix: 0.1 },
158
+ reverb: { enabled: true, decay: 1500, damping: 0.6, mix: 0.12 } })
159
+ synth.enableStats()
160
+ const statistics = synth.getStats()
161
+ synth.disableStats()
162
+ ```
163
+
164
+ `setProcessing()` uses the shared effects chain and strict native validation. Supply delay `time` and reverb `decay` in milliseconds. The adapter converts them to native `timeMs` and seconds-based decay. Other chorus/delay/reverb/saturation, `inputGain` and `applyTo` fields retain the [processing reference](api.md#setmasterprocessingoptions) ranges and semantics.
165
+
166
+ `bus`, `effectSend` and `priority` belong to `sound`, including a step's `sound` where supported. Routes, sends, fixed per-event gain and chord grouping are unchanged. Sound creation does not enable diagnostics; use `enableStats()` and `disableStats()`, and retain unavailable readings as `null`.
167
+
168
+ ## Lifetime and errors
169
+
170
+ A Sound is reusable across engine shutdown/start; a playback control is bound to the generation that created it. Shutdown invalidates old controls, which subsequently return `false` for updates/releases/stops and `null` for pattern state. They cannot affect a new execution or a reused app identifier. Stale completion/invalidation events cannot retire a new generation's pattern. Listener installation on an invalid pattern has no effect. `stopAll()` retains its global panic behavior; prefer an owned control for ordinary release or cancellation.
171
+
172
+ The [upgrade guide](upgrade-2.0.0.md) records method mappings and metadata changes. Global `setVolume()` and `setTempo()` return Boolean acceptance. Sound and `setProcessing()` reject invalid configurations with descriptive errors.
173
+
174
+ The [TypeScript declarations](../assets/index.d.ts) describe Sound definitions, controls and the supported direct methods. `Sound.play()` has a mixed return when the stored interpretation is not known at the call: narrow the result before calling a control method, or supply an explicit call `type`. Runtime validation remains authoritative for numeric ranges and combinations.
175
+
176
+ ## Property allowlists
177
+
178
+ `getContract()` reports these shared structural allowlists. Native validators then apply each interpretation's compatibility rules.
179
+
180
+ ### Sound properties
181
+
182
+ | Keys | Purpose |
183
+ | --- | --- |
184
+ | `attack`, `decay`, `sustain`, `release`, `volume`, `waveType` | Envelope, gain and source |
185
+ | `pan`, `panEnd`, `pans`, `panSpread`, `spatialX`, `spatialY` | Stereo or spatial placement |
186
+ | `lfoFreq`, `lfoDepth`, `noiseRate`, `noiseRateEnd`, `lowPassHz` | Vibrato and noise shaping |
187
+ | `pulseWidth`, `pulseWidthEnd`, `waveTable`, `bitDepth` | Wave shaping |
188
+ | `filterType`, `filterHz`, `filterHzEnd`, `filterQ` | Resonant filter |
189
+ | `fmRatio`, `fmDepth`, `fmDepthEnd`, `detune`, `detunes`, `modulations` | Pitch, FM and compatible LFOs |
190
+ | `bus`, `effectSend`, `priority`, `variation` | Routing, admission and native variation |
191
+
192
+ ### Playback properties
193
+
194
+ | Keys | Purpose |
195
+ | --- | --- |
196
+ | `type`, `notes`, `frequencies`, `frequencyEnd`, `duration` | Interpretation, pitches and finite timing |
197
+ | `interval`, `timeUnit`, `steps`, `gate`, `loop`, `swing`, `mode`, `tuning` | Repetition or pattern scheduling |