@maccesar/titools 5.1.1 → 5.2.1

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 (98) hide show
  1. package/CONTRIBUTING.md +69 -0
  2. package/README.md +112 -72
  3. package/agents/ti-pro.md +5 -1
  4. package/commands/ti-audit.md +5 -1
  5. package/docs/examples.md +91 -0
  6. package/docs/knowledge-index.md +57 -0
  7. package/lib/config.js +2 -0
  8. package/lib/downloader.js +1 -1
  9. package/package.json +4 -2
  10. package/skills/purgetss/references/multi-density-images.md +1 -1
  11. package/skills/ti-admob-unified/SKILL.md +58 -0
  12. package/skills/ti-admob-unified/agents/openai.yaml +4 -0
  13. package/skills/ti-admob-unified/assets/example/app.js +1207 -0
  14. package/skills/ti-admob-unified/assets/example/lib/ads.js +646 -0
  15. package/skills/ti-admob-unified/assets/example/library-usage.js +136 -0
  16. package/skills/ti-admob-unified/references/api-reference.md +615 -0
  17. package/skills/ti-admob-unified/references/banners.md +435 -0
  18. package/skills/ti-admob-unified/references/contributing.md +233 -0
  19. package/skills/ti-admob-unified/references/dependencies.md +91 -0
  20. package/skills/ti-admob-unified/references/fullscreen.md +238 -0
  21. package/skills/ti-admob-unified/references/game-monetization.md +148 -0
  22. package/skills/ti-admob-unified/references/getting-started.md +319 -0
  23. package/skills/ti-admob-unified/references/index.md +33 -0
  24. package/skills/ti-admob-unified/references/integration.md +87 -0
  25. package/skills/ti-admob-unified/references/library.md +220 -0
  26. package/skills/ti-admob-unified/references/mediation-parity.md +17 -0
  27. package/skills/ti-admob-unified/references/mediation.md +145 -0
  28. package/skills/ti-admob-unified/references/migration.md +259 -0
  29. package/skills/ti-admob-unified/references/module-changelog.md +84 -0
  30. package/skills/ti-admob-unified/references/module-license.txt +21 -0
  31. package/skills/ti-admob-unified/references/privacy.md +281 -0
  32. package/skills/ti-admob-unified/references/production.md +67 -0
  33. package/skills/ti-admob-unified/references/rewarded-interstitial.md +101 -0
  34. package/skills/ti-admob-unified/references/roadmap.md +143 -0
  35. package/skills/ti-admob-unified/references/source-notes.md +80 -0
  36. package/skills/ti-admob-unified/references/source-snapshot.json +644 -0
  37. package/skills/ti-admob-unified/references/third-party-notices.md +22 -0
  38. package/skills/ti-game/SKILL.md +31 -5
  39. package/skills/ti-game/references/api.md +36 -24
  40. package/skills/ti-game/references/debugging-performance.md +22 -7
  41. package/skills/ti-game/references/examples.md +113 -20
  42. package/skills/ti-game/references/input.md +101 -0
  43. package/skills/ti-game/references/patterns.md +56 -10
  44. package/skills/ti-game/references/physics.md +530 -0
  45. package/skills/ti-game/references/project-setup.md +131 -47
  46. package/skills/ti-game/references/recipes-action.md +149 -18
  47. package/skills/ti-game/references/recipes-adventure-effects.md +47 -4
  48. package/skills/ti-game/references/roadmap.md +11 -4
  49. package/skills/ti-game/references/source-notes.md +81 -0
  50. package/skills/ti-game/references/tilemaps.md +13 -4
  51. package/skills/ti-game-assets/SKILL.md +53 -0
  52. package/skills/ti-game-assets/agents/openai.yaml +4 -0
  53. package/skills/ti-game-assets/references/alpha.md +26 -0
  54. package/skills/ti-game-assets/references/art-direction.md +37 -0
  55. package/skills/ti-game-assets/references/integration.md +49 -0
  56. package/skills/ti-game-assets/references/manifest.example.json +429 -0
  57. package/skills/ti-game-assets/references/manifest.md +39 -0
  58. package/skills/ti-game-assets/references/offline-audio.md +33 -0
  59. package/skills/ti-game-assets/references/prompts.md +33 -0
  60. package/skills/ti-game-assets/references/repeating-textures.md +17 -0
  61. package/skills/ti-game-assets/references/responsible-ads.md +31 -0
  62. package/skills/ti-game-assets/references/sound-assets.md +42 -0
  63. package/skills/ti-game-assets/references/vector-sources.md +21 -0
  64. package/skills/ti-game-assets/scripts/alpha_tools.py +160 -0
  65. package/skills/ti-game-assets/scripts/requirements.txt +1 -0
  66. package/skills/ti-game-assets/scripts/validate_manifest.py +267 -0
  67. package/skills/ti-synthengine/SKILL.md +77 -67
  68. package/skills/ti-synthengine/assets/example/README.md +84 -0
  69. package/skills/ti-synthengine/assets/example/app.js +1178 -0
  70. package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +83 -0
  71. package/skills/ti-synthengine/assets/example/lib/sequenceScore.js +25 -0
  72. package/skills/ti-synthengine/references/advanced-examples.md +174 -0
  73. package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
  74. package/skills/ti-synthengine/references/android-sim80-correction.json +162 -0
  75. package/skills/ti-synthengine/references/api.md +573 -153
  76. package/skills/ti-synthengine/references/audio-engine.md +272 -124
  77. package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
  78. package/skills/ti-synthengine/references/examples.md +355 -106
  79. package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
  80. package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
  81. package/skills/ti-synthengine/references/frontier-physical-validation.json +1918 -0
  82. package/skills/ti-synthengine/references/guide.md +426 -75
  83. package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
  84. package/skills/ti-synthengine/references/performance-review.json +1146 -0
  85. package/skills/ti-synthengine/references/physical-validation.json +1457 -0
  86. package/skills/ti-synthengine/references/recipes-interaction.md +549 -0
  87. package/skills/ti-synthengine/references/recipes.md +434 -420
  88. package/skills/ti-synthengine/references/recommendations.md +72 -85
  89. package/skills/ti-synthengine/references/sound-design.md +375 -155
  90. package/skills/ti-synthengine/references/source-notes.md +109 -0
  91. package/skills/ti-synthengine/references/source-snapshot.json +452 -0
  92. package/skills/ti-synthengine/references/stats-performance.json +2992 -0
  93. package/skills/ti-synthengine/references/stats-performance.md +82 -0
  94. package/skills/ti-synthengine/references/studio-validation.json +6202 -0
  95. package/skills/ti-synthengine/references/troubleshooting.md +123 -78
  96. package/skills/ti-synthengine/references/unified-bypass-performance.json +22425 -0
  97. package/skills/ti-synthengine/references/unified-validation.json +314 -0
  98. package/AGENTS-VERCEL-RESEARCH.md +0 -175
@@ -1,9 +1,12 @@
1
- > Source snapshot: official TiSynthEngine 1.0.0 documentation at commit `6685b81` (2026-08-29).
1
+ > Source snapshot: official TiSynthEngine 1.0.0 documentation at release source commit `e7cbbe8a6f9ab38088fd8029c7b43cef6501101f` (2026-10-24). Upstream `documentation/examples.md` SHA-256: `48104b9d413050d439ee605c92a2456a50b42c4c80e2d7c2c05706eecc7db215`.
2
2
 
3
3
  # Examples
4
4
 
5
- Every snippet on this page uses the TiSynthEngine 1.0.0 contract. Unless a
6
- snippet includes startup code, it assumes the engine is already running.
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
+ Additional [curated recipes](recipes.md) provide application sound and interaction variants.
8
+
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.
7
10
 
8
11
  ```javascript
9
12
  var synth = require('ti.synthengine')
@@ -19,7 +22,12 @@ if (contract.apiVersion !== '1.0.0') {
19
22
  ## Contents
20
23
 
21
24
  - [Start once after layout](#start-once-after-layout)
22
- - [Five waveforms](#five-waveforms)
25
+ - [Built-in waveforms](#built-in-waveforms)
26
+ - [Variable-rate filtered noise](#variable-rate-filtered-noise)
27
+ - [Pulse-width sweep](#pulse-width-sweep)
28
+ - [Stepped wavetable](#stepped-wavetable)
29
+ - [Fixed controls on a chord](#fixed-controls-on-a-chord)
30
+ - [Wave-shaping pattern overrides](#wave-shaping-pattern-overrides)
23
31
  - [Pitch sweep and pan movement](#pitch-sweep-and-pan-movement)
24
32
  - [Vibrato](#vibrato)
25
33
  - [Chord with an even spread](#chord-with-an-even-spread)
@@ -30,9 +38,17 @@ if (contract.apiVersion !== '1.0.0') {
30
38
  - [Noise percussion](#noise-percussion)
31
39
  - [Xylophone sketch](#xylophone-sketch)
32
40
  - [Simon-style pads](#simon-style-pads)
41
+ - [Pluck and electric piano](#pluck-and-electric-piano)
42
+ - [Bell](#bell)
43
+ - [Wah and filtered laser](#wah-and-filtered-laser)
44
+ - [Kick and hi-hat in one pattern](#kick-and-hi-hat-in-one-pattern)
45
+ - [Detuned chords](#detuned-chords)
46
+ - [Chain sequences with `sequenceend`](#chain-sequences-with-sequenceend)
47
+ - [Rules across keys](#rules-across-keys)
33
48
  - [Speaker-safe startup](#speaker-safe-startup)
34
49
  - [Validation at an input boundary](#validation-at-an-input-boundary)
35
50
  - [Stop and clean up](#stop-and-clean-up)
51
+ - [Positioned headphone effect](#positioned-headphone-effect)
36
52
 
37
53
  <!-- TOC-END -->
38
54
 
@@ -47,10 +63,12 @@ function onPostLayout () {
47
63
  timer = setTimeout(function () {
48
64
  timer = null
49
65
  win.removeEventListener('postlayout', onPostLayout)
50
- started = synth.startEngine({
66
+ started = synth.start({
51
67
  maxVoices: 16,
52
- mixingProfile: 'conservative'
68
+ mixingProfile: 'conservative',
69
+ audioSession: 'ambient'
53
70
  })
71
+ if (!started) Ti.API.warn('Audio startup failed; keep playback controls disabled')
54
72
  }, 250)
55
73
  }
56
74
 
@@ -65,26 +83,111 @@ win.addEventListener('close', cleanup)
65
83
  win.open()
66
84
  ```
67
85
 
68
- ## Five waveforms
86
+ The startup example uses `ambient` for game/UI feedback. On iOS it respects silent mode and mixes with other apps. The module default is `playback`; Android validates either value without changing its output. Shut down before changing the policy. Use the [interactive session controls](../assets/example/README.md#audio-session) to compare them.
87
+
88
+ ## Built-in waveforms
69
89
 
70
90
  ```javascript
71
91
  var names = ['SINE', 'SQUARE', 'SAWTOOTH', 'TRIANGLE', 'NOISE']
92
+ synth.playSequence({ timeUnit: 'ms', stepDuration: 500,
93
+ voice: { attack: 8, release: 100, volume: 0.6 },
94
+ steps: names.map(function (name) {
95
+ return { notes: [69], length: 300, overrides: { waveType: WAVE[name] } }
96
+ })
97
+ })
98
+ ```
72
99
 
73
- names.forEach(function (name, index) {
74
- setTimeout(function () {
75
- synth.playTone({
76
- note: 'A4',
77
- duration: 400,
78
- attack: 8,
79
- release: 100,
80
- volume: 0.6,
81
- waveType: WAVE[name]
82
- })
83
- }, index * 500)
100
+ Noise ignores pitch; each sounding step still supplies one pitch array.
101
+
102
+ ## Variable-rate filtered noise
103
+
104
+ ```javascript
105
+ synth.playTone({
106
+ frequency: 100,
107
+ duration: 760,
108
+ attack: 1,
109
+ release: 580,
110
+ volume: 0.65,
111
+ waveType: WAVE.NOISE,
112
+ noiseRate: 12000,
113
+ noiseRateEnd: 600,
114
+ lowPassHz: 2400,
115
+ bitDepth: 8
84
116
  })
85
117
  ```
86
118
 
87
- Noise ignores pitch, but `note` keeps the event shape valid.
119
+ `noiseRate` starts at 12 kHz and falls to 600 Hz. `lowPassHz` removes high frequency energy after the 8-bit source quantizer.
120
+
121
+ ## Pulse-width sweep
122
+
123
+ ```javascript
124
+ synth.playTone({
125
+ note: 'A3',
126
+ duration: 700,
127
+ attack: 3,
128
+ release: 180,
129
+ volume: 0.55,
130
+ waveType: WAVE.SQUARE,
131
+ pulseWidth: 0.1,
132
+ pulseWidthEnd: 0.86
133
+ })
134
+ ```
135
+
136
+ The square duty cycle moves from 10 percent to 86 percent over the event.
137
+
138
+ ## Stepped wavetable
139
+
140
+ ```javascript
141
+ synth.playTone({
142
+ note: 'D4',
143
+ duration: 650,
144
+ attack: 4,
145
+ release: 220,
146
+ volume: 0.58,
147
+ waveType: WAVE.WAVETABLE,
148
+ waveTable: [-1, -0.7, -0.1, 0.8, 1, 0.35, -0.25, -0.55],
149
+ bitDepth: 8
150
+ })
151
+ ```
152
+
153
+ The table repeats as a stepped cycle. `bitDepth: 8` quantizes each table value before filtering and the amplitude envelope.
154
+
155
+ ## Fixed controls on a chord
156
+
157
+ ```javascript
158
+ synth.playChord({
159
+ notes: ['A3', 'E4', 'A4'],
160
+ duration: 900,
161
+ attack: 8,
162
+ release: 220,
163
+ volume: 0.5,
164
+ waveType: WAVE.SQUARE,
165
+ pulseWidth: 0.22,
166
+ lowPassHz: 3600,
167
+ bitDepth: 8,
168
+ pans: [-0.65, 0, 0.65]
169
+ })
170
+ ```
171
+
172
+ Chord voices share fixed shaping controls. Sweep fields such as `pulseWidthEnd` and `noiseRateEnd` are not accepted by `playChord()`.
173
+
174
+ ## Wave-shaping pattern overrides
175
+
176
+ ```javascript
177
+ synth.playSequence({
178
+ timeUnit: 'ms',
179
+ stepDuration: 210,
180
+ voice: { attack: 2, release: 80, volume: 0.48, waveType: WAVE.NOISE, noiseRate: 12000, lowPassHz: 2600, bitDepth: 8 },
181
+ steps: [
182
+ { frequencies: [120], duration: 180, overrides: { release: 150, noiseRateEnd: 800 } },
183
+ { rest: true, duration: 100 },
184
+ { notes: [57], duration: 300, overrides: { release: 120, waveType: WAVE.SQUARE, pulseWidth: 0.1, pulseWidthEnd: 0.82, lowPassHz: 0, bitDepth: 0 } },
185
+ { notes: [62], duration: 360, overrides: { release: 160, waveType: WAVE.WAVETABLE, waveTable: [-1, -0.7, -0.1, 0.8, 1, 0.35, -0.25, -0.55] } }
186
+ ]
187
+ })
188
+ ```
189
+
190
+ Pattern roots provide inherited values. Each sounding step can override the source and any shaping control; a `WAVETABLE` step needs a local or inherited `waveTable`.
88
191
 
89
192
  ## Pitch sweep and pan movement
90
193
 
@@ -102,8 +205,7 @@ synth.playTone({
102
205
  })
103
206
  ```
104
207
 
105
- Set `frequencyEnd: 0` to keep the starting pitch. Set `panEnd` equal to `pan`
106
- when no stereo movement is wanted.
208
+ Set `frequencyEnd: 0` to keep the starting pitch. Set `panEnd` equal to `pan` when no stereo movement is wanted.
107
209
 
108
210
  ## Vibrato
109
211
 
@@ -120,8 +222,7 @@ synth.playTone({
120
222
  })
121
223
  ```
122
224
 
123
- `lfoFreq` and `lfoDepth` are both in hertz. A depth of 6 Hz is gentle around
124
- E4; use your ears when moving the same setting to a much lower note.
225
+ `lfoFreq` and `lfoDepth` are both in hertz. A depth of 6 Hz is gentle around E4; use your ears when moving the same setting to a much lower note.
125
226
 
126
227
  ## Chord with an even spread
127
228
 
@@ -151,75 +252,63 @@ synth.playChord({
151
252
  })
152
253
  ```
153
254
 
154
- Use `playChord` for a known simultaneous group. Launching four separate tones
155
- is not atomic and gives each one full event gain.
255
+ Use `playChord` for a known simultaneous group. Launching four separate tones is not atomic and gives each one full event gain.
156
256
 
157
257
  ## Tempo, note values and swing
158
258
 
159
259
  ```javascript
160
- synth.playPattern({
161
- bpm: 132,
260
+ synth.setTempo({ bpm: 132 })
261
+ synth.playSequence({
262
+ timeUnit: 'beats',
263
+ stepDuration: 0.5,
162
264
  swing: 0.12,
163
- noteValue: '8n',
164
- attack: 4,
165
- release: 55,
166
- volume: 0.55,
167
- waveType: WAVE.SQUARE,
265
+ voice: { attack: 4, release: 55, volume: 0.55, waveType: WAVE.SQUARE },
168
266
  steps: [
169
- { note: 'C4', duration: 150 },
170
- { note: 'E4', duration: 150 },
267
+ { notes: [60] },
268
+ { notes: [64] },
171
269
  { rest: true },
172
- { note: 'G4', duration: 150 },
173
- { note: 'C5', duration: 280, noteValue: '4n' }
270
+ { notes: [67] },
271
+ { notes: [72], duration: 1 }
174
272
  ]
175
273
  })
176
274
  ```
177
275
 
178
- Positive swing lengthens one step and shortens the next. Negative swing flips
179
- that relationship. Keep it subtle unless the lurch is the intended effect.
276
+ Swing adjusts pairs of intervals by `swing * min(A, B)`: the first grows and the second shrinks by that amount, preserving the pair total. Negative values reverse the adjustment. The default is 0 and the range is −0.75…0.75.
180
277
 
181
278
  ## Millisecond pattern
182
279
 
183
280
  ```javascript
184
- synth.playPattern({
185
- stepDuration: 110,
186
- pan: -0.4,
187
- panEnd: 0.4,
188
- waveType: WAVE.TRIANGLE,
281
+ synth.playSequence({
282
+ timeUnit: 'ms',
283
+ stepDuration: 100,
284
+ voice: { pan: -0.4, panEnd: 0.4, waveType: WAVE.TRIANGLE },
189
285
  steps: [
190
- { note: 'C5', duration: 90 },
191
- { note: 'D5', duration: 90 },
192
- { note: 'E5', duration: 90 },
193
- { note: 'G5', duration: 180 }
286
+ { notes: [72], duration: 90 },
287
+ { rest: true, duration: 20 },
288
+ { notes: [74], duration: 90 },
289
+ { rest: true, duration: 20 },
290
+ { notes: [76], duration: 90 },
291
+ { rest: true, duration: 20 },
292
+ { notes: [79], duration: 180 }
194
293
  ]
195
294
  })
196
295
  ```
197
296
 
198
- `stepDuration` controls spacing. Per-step `duration` controls how long the
199
- sound itself lasts. The pattern root does not accept `duration`.
297
+ Each `duration` sets the interval until the next step. The 90 ms intervals followed by 20 ms rests place attacks 110 ms apart; a release can overlap a rest. Gate defaults to 0.8. Use `length` to choose note duration independently, while ADSR remains milliseconds.
200
298
 
201
299
  ## Step overrides
202
300
 
203
301
  ```javascript
204
- synth.playPattern({
205
- bpm: 100,
206
- noteValue: '8n',
207
- attack: 10,
208
- release: 90,
209
- volume: 0.5,
210
- waveType: WAVE.SINE,
302
+ synth.setTempo({ bpm: 100 })
303
+ synth.playSequence({
304
+ timeUnit: 'beats',
305
+ stepDuration: 0.5,
306
+ voice: { attack: 10, release: 90, volume: 0.5, waveType: WAVE.SINE },
211
307
  steps: [
212
- { note: 'C4' },
213
- {
214
- note: 'E4',
215
- waveType: WAVE.SAWTOOTH,
216
- pan: -0.7,
217
- panEnd: 0.7,
218
- lfoFreq: 7,
219
- lfoDepth: 10
220
- },
308
+ { notes: [60] },
309
+ { notes: [64], overrides: { waveType: WAVE.SAWTOOTH, pan: -0.7, panEnd: 0.7, lfoFreq: 7, lfoDepth: 10 } },
221
310
  { rest: true },
222
- { note: 'G4', volume: 0.75, noteValue: '4n' }
311
+ { notes: [67], duration: 1, overrides: { volume: 0.75 } }
223
312
  ]
224
313
  })
225
314
  ```
@@ -227,49 +316,37 @@ synth.playPattern({
227
316
  ## Noise percussion
228
317
 
229
318
  ```javascript
230
- synth.playPattern({
231
- bpm: 116,
232
- noteValue: '16n',
233
- volume: 0.32,
234
- waveType: WAVE.NOISE,
319
+ synth.setTempo({ bpm: 116 })
320
+ synth.playSequence({
321
+ timeUnit: 'beats',
322
+ stepDuration: 0.25,
323
+ voice: { volume: 0.32, waveType: WAVE.NOISE },
235
324
  steps: [
236
- { frequency: 200, duration: 70, attack: 1, release: 60 },
325
+ { frequencies: [200], overrides: { attack: 1, decay: 70, sustain: 0, release: 60 } },
237
326
  { rest: true },
238
- { frequency: 200, duration: 32, attack: 1, release: 25 },
239
- { frequency: 200, duration: 32, attack: 1, release: 25 },
327
+ { frequencies: [200], overrides: { attack: 1, decay: 32, sustain: 0, release: 25 } },
328
+ { frequencies: [200], overrides: { attack: 1, decay: 32, sustain: 0, release: 25 } },
240
329
  { rest: true },
241
- { frequency: 200, duration: 52, attack: 1, release: 44 }
330
+ { frequencies: [200], overrides: { attack: 1, decay: 52, sustain: 0, release: 44 } }
242
331
  ]
243
332
  })
244
333
  ```
245
334
 
246
- Every sounding step needs `frequency` or `note`, including a noise step. Do not
247
- write a primitive step such as `200`; primitive steps return
248
- `invalid_step_type`.
335
+ The steps retain the sixteenth-note grid. Short decay with `sustain: 0` makes each hit settle to silence before its step ends, without changing that grid. Every sounding step needs `frequencies` or `notes`, including a noise step. Do not write a primitive step such as `200`; primitive steps reject the complete publication.
249
336
 
250
337
  ## Xylophone sketch
251
338
 
252
339
  ```javascript
253
- function playBar (note, pan) {
254
- return synth.playTone({
255
- note: note,
256
- duration: 520,
257
- attack: 2,
258
- release: 430,
259
- volume: 0.62,
260
- waveType: WAVE.TRIANGLE,
261
- pan: pan
262
- })
263
- }
264
-
265
- playBar('C5', -0.7)
266
- setTimeout(function () { playBar('E5', -0.2) }, 130)
267
- setTimeout(function () { playBar('G5', 0.3) }, 260)
268
- setTimeout(function () { playBar('C6', 0.75) }, 390)
340
+ synth.playSequence({ timeUnit: 'ms', stepDuration: 130,
341
+ voice: { attack: 2, release: 430, volume: 0.62, waveType: WAVE.TRIANGLE },
342
+ steps: [{ notes: [72], length: 90, overrides: { pan: -0.7 } },
343
+ { notes: [76], length: 90, overrides: { pan: -0.2 } },
344
+ { notes: [79], length: 90, overrides: { pan: 0.3 } },
345
+ { notes: [84], length: 90, overrides: { pan: 0.75 } }]
346
+ })
269
347
  ```
270
348
 
271
- These are deliberately independent strikes. Their releases overlap, so leave
272
- headroom in `volume` and choose enough `maxVoices` for the tails.
349
+ Native attacks are 130 ms apart. Each note releases after 90 ms and its 430 ms tail overlaps later strikes. Leave headroom and voice capacity for those tails.
273
350
 
274
351
  ## Simon-style pads
275
352
 
@@ -300,10 +377,163 @@ function playAllPads () {
300
377
  }
301
378
  ```
302
379
 
380
+ ## Pluck and electric piano
381
+
382
+ `decay` and `sustain` shape the tone into a plucked or struck sound. The electric piano also lowers the FM index as the level falls, giving it a bright attack and a mellow tail.
383
+
384
+ ```javascript
385
+ function pluck (note) {
386
+ return synth.playTone({
387
+ note: note,
388
+ duration: 900,
389
+ attack: 2,
390
+ decay: 260,
391
+ sustain: 0.18,
392
+ release: 380,
393
+ volume: 0.6,
394
+ waveType: WAVE.TRIANGLE
395
+ })
396
+ }
397
+
398
+ function electricPiano (note) {
399
+ return synth.playTone({
400
+ note: note,
401
+ duration: 1800,
402
+ attack: 3,
403
+ decay: 700,
404
+ sustain: 0.3,
405
+ release: 600,
406
+ volume: 0.55,
407
+ waveType: WAVE.SINE,
408
+ fmRatio: 1,
409
+ fmDepth: 2.2,
410
+ fmDepthEnd: 0.25
411
+ })
412
+ }
413
+ ```
414
+
415
+ ## Bell
416
+
417
+ ```javascript
418
+ synth.playTone({
419
+ note: 'C5',
420
+ duration: 2600,
421
+ attack: 1,
422
+ decay: 2000,
423
+ sustain: 0.02,
424
+ release: 500,
425
+ volume: 0.5,
426
+ waveType: WAVE.SINE,
427
+ fmRatio: 3.5,
428
+ fmDepth: 4,
429
+ fmDepthEnd: 0
430
+ })
431
+ ```
432
+
433
+ A fractional `fmRatio` puts the overtones between harmonics, which reads as metal. `fmDepthEnd: 0` lets the brightness die away completely.
434
+
435
+ ## Wah and filtered laser
436
+
437
+ ```javascript
438
+ synth.playSequence({ timeUnit: 'ms',
439
+ voice: { waveType: WAVE.SAWTOOTH },
440
+ steps: [{ notes: [45], duration: 1300, length: 800,
441
+ overrides: { attack: 10, release: 200, volume: 0.5,
442
+ filterType: 'bandpass', filterHz: 350, filterHzEnd: 2400, filterQ: 5 } },
443
+ { frequencies: [1600], duration: 340, length: 250,
444
+ overrides: { frequencyEnd: 160, attack: 2, release: 90, volume: 0.4,
445
+ filterType: 'lowpass', filterHz: 7000, filterHzEnd: 500, filterQ: 4 } }]
446
+ })
447
+ ```
448
+
449
+ Each sweep reaches its destination at `length`, then holds there through release. Intervals and note lengths remain fixed in milliseconds when the global tempo changes.
450
+
451
+ ## Kick and hi-hat in one pattern
452
+
453
+ Each step can carry its own envelope and filter.
454
+
455
+ ```javascript
456
+ synth.setTempo({ bpm: 110 })
457
+ synth.playSequence({
458
+ timeUnit: 'beats',
459
+ stepDuration: 0.5,
460
+ steps: [
461
+ { frequencies: [150], duration: 1, overrides: { frequencyEnd: 42, attack: 1, decay: 140, sustain: 0.3, release: 120, volume: 0.85 } },
462
+ { frequencies: [440], overrides: { waveType: WAVE.NOISE, attack: 1, decay: 30, sustain: 0.25, release: 45, volume: 0.45, filterType: 'highpass', filterHz: 7000 } },
463
+ { rest: true },
464
+ { frequencies: [440], overrides: { waveType: WAVE.NOISE, attack: 1, decay: 30, sustain: 0.25, release: 45, volume: 0.45, filterType: 'highpass', filterHz: 7000 } }
465
+ ]
466
+ })
467
+ ```
468
+
469
+ ## Detuned chords
470
+
471
+ Repeat a pitch with small offsets in `detunes` to make it beat and widen. Two copies 12 cents apart at 440 Hz beat about three times a second.
472
+
473
+ ```javascript
474
+ synth.playChord({
475
+ frequencies: [440, 440],
476
+ detunes: [-6, 6],
477
+ duration: 2000,
478
+ attack: 200,
479
+ release: 600,
480
+ volume: 0.55,
481
+ waveType: WAVE.SINE
482
+ })
483
+
484
+ // Alternative voicing:
485
+ synth.playChord({
486
+ notes: ['A3', 'E4', 'A3', 'E4', 'A3', 'E4'],
487
+ detunes: [-9, -9, 0, 0, 9, 9],
488
+ duration: 3200,
489
+ attack: 700,
490
+ release: 1300,
491
+ volume: 0.5,
492
+ waveType: WAVE.SAWTOOTH,
493
+ filterType: 'lowpass',
494
+ filterHz: 1500,
495
+ panSpread: 0.8
496
+ })
497
+ ```
498
+
499
+ Each repeated pitch is a voice. The second chord needs `maxVoices` of at least 6.
500
+
501
+ ## Chain sequences with `sequenceend`
502
+
503
+ Use one sequence containing all steps for uninterrupted, sample-clock phrasing. The asynchronous completion event is useful for presentation or launching an unrelated cue:
504
+
505
+ ```javascript
506
+ var jingleId = synth.playSequence({
507
+ stepDuration: 0.5,
508
+ voice: { attack: 5, release: 60, volume: 0.5, waveType: WAVE.SQUARE },
509
+ steps: [{ notes: [72] }, { notes: [76] }, { notes: [79] }]
510
+ })
511
+ function onSequenceEnd (event) {
512
+ if (event.sequenceId === jingleId && event.reason === 'completed') Ti.API.info('Jingle scheduled to completion')
513
+ }
514
+ synth.addEventListener('sequenceend', onSequenceEnd)
515
+ // When the owner closes:
516
+ synth.removeEventListener('sequenceend', onSequenceEnd)
517
+ ```
518
+
519
+ A positive identifier acknowledges publication; 0 rejects the complete operation. Sequence completion does not guarantee audible silence: release envelopes and effect tails can continue.
520
+
521
+ ## Rules across keys
522
+
523
+ Some combinations are rejected even when every value is in range. This call returns `false` with `sustain_without_decay`, because a sustain below 1 with no decay would jump in level and click:
524
+
525
+ ```javascript
526
+ // Rejected on purpose: sustain needs a decay.
527
+ var accepted = synth.playTone({ note: 'A4', sustain: 0.5 })
528
+
529
+ // Accepted: the same note with a decay.
530
+ synth.playTone({ note: 'A4', decay: 200, sustain: 0.5, duration: 800 })
531
+ ```
532
+
303
533
  ## Speaker-safe startup
304
534
 
305
535
  ```javascript
306
- var accepted = synth.startEngine({
536
+ var accepted = synth.start({
307
537
  maxVoices: 12,
308
538
  mixingProfile: 'speakerSafe'
309
539
  })
@@ -311,9 +541,7 @@ var accepted = synth.startEngine({
311
541
  if (accepted) synth.setVolume({ volume: 0.85 })
312
542
  ```
313
543
 
314
- This profile lowers source gain and the limiter ceiling. It cannot repair a
315
- speaker that physically struggles with a very low pitch, so test the real
316
- device and raise the pitch when needed.
544
+ This profile lowers source gain and the limiter ceiling. It cannot repair a speaker that physically struggles with a very low pitch, so test the real device and raise the pitch when needed.
317
545
 
318
546
  ## Validation at an input boundary
319
547
 
@@ -329,9 +557,7 @@ function playUserFrequency (input) {
329
557
  }
330
558
  ```
331
559
 
332
- The native contract rejects bad values rather than clamping them. Validation in
333
- the app lets you give the user a specific message before the call reaches the
334
- module.
560
+ The native contract rejects invalid values without clamping them. Validation in the app lets you give the user a specific message before the call reaches the module.
335
561
 
336
562
  ## Stop and clean up
337
563
 
@@ -342,7 +568,30 @@ synth.stopAll()
342
568
  synth.shutdown()
343
569
  ```
344
570
 
345
- `stopAll()` fades current voices but leaves pending pattern scheduling alone.
346
- `shutdown()` cancels the scheduler and releases the native stream.
571
+ `stopAll()` stops all native sequences, fades current voices and invalidates effect histories. `shutdown()` also releases the native stream. To stop only one sequence owner, use `stopSequence({ sequenceId })`.
572
+
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).
574
+
575
+ ## Positioned headphone effect
576
+
577
+ With the engine running, check the installed coordinate limits. This example places one generated noise effect above and to the right. Other sound calls keep their existing placement:
578
+
579
+ ```javascript
580
+ if (!contract.limits.spatialX || !contract.limits.spatialY) {
581
+ throw new Error('This installed module does not report binaural support')
582
+ }
583
+ if (!synth.playTone({
584
+ frequency: 90,
585
+ waveType: WAVE.NOISE,
586
+ duration: 500,
587
+ attack: 2,
588
+ release: 200,
589
+ volume: 0.3,
590
+ spatialX: 0.8,
591
+ spatialY: 0.75
592
+ })) Ti.API.warn('Positioned effect rejected')
593
+ ```
594
+
595
+ For app switches, amplitude sliders, all nine screen positions and routing multiple effects through one service, use the [binaural integration guide](guide.md#integrate-binaural-in-a-2d-app). Omitting both coordinates disables binaural for that event; setting both to zero keeps it enabled at the front center.
347
596
 
348
- The complete interactive demo is `example/app.js` in the upstream TiSynthEngine repository. For additional retro effects, playable xylophone interaction, memory pads, layered polyphony and cancellable repetition, continue with [Curated sound and interaction recipes](recipes.md).
597
+ Continue with [advanced examples](advanced-examples.md).