@maccesar/titools 4.16.2 → 4.17.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.
@@ -0,0 +1,348 @@
1
+ > Source snapshot: official TiSynthEngine 1.0.0 documentation at commit `6685b81` (2026-08-29).
2
+
3
+ # Examples
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.
7
+
8
+ ```javascript
9
+ var synth = require('ti.synthengine')
10
+ var contract = synth.getDefaults()
11
+ var WAVE = contract.waveTypes
12
+
13
+ if (contract.apiVersion !== '1.0.0') {
14
+ throw new Error('TiSynthEngine 1.0.0 is required')
15
+ }
16
+ ```
17
+
18
+ <!-- TOC-START -->
19
+ ## Contents
20
+
21
+ - [Start once after layout](#start-once-after-layout)
22
+ - [Five waveforms](#five-waveforms)
23
+ - [Pitch sweep and pan movement](#pitch-sweep-and-pan-movement)
24
+ - [Vibrato](#vibrato)
25
+ - [Chord with an even spread](#chord-with-an-even-spread)
26
+ - [Chord with explicit pans](#chord-with-explicit-pans)
27
+ - [Tempo, note values and swing](#tempo-note-values-and-swing)
28
+ - [Millisecond pattern](#millisecond-pattern)
29
+ - [Step overrides](#step-overrides)
30
+ - [Noise percussion](#noise-percussion)
31
+ - [Xylophone sketch](#xylophone-sketch)
32
+ - [Simon-style pads](#simon-style-pads)
33
+ - [Speaker-safe startup](#speaker-safe-startup)
34
+ - [Validation at an input boundary](#validation-at-an-input-boundary)
35
+ - [Stop and clean up](#stop-and-clean-up)
36
+
37
+ <!-- TOC-END -->
38
+
39
+ ## Start once after layout
40
+
41
+ ```javascript
42
+ var started = false
43
+ var timer = null
44
+
45
+ function onPostLayout () {
46
+ if (timer !== null) clearTimeout(timer)
47
+ timer = setTimeout(function () {
48
+ timer = null
49
+ win.removeEventListener('postlayout', onPostLayout)
50
+ started = synth.startEngine({
51
+ maxVoices: 16,
52
+ mixingProfile: 'conservative'
53
+ })
54
+ }, 250)
55
+ }
56
+
57
+ function cleanup () {
58
+ win.removeEventListener('postlayout', onPostLayout)
59
+ if (timer !== null) clearTimeout(timer)
60
+ if (started) synth.shutdown()
61
+ }
62
+
63
+ win.addEventListener('postlayout', onPostLayout)
64
+ win.addEventListener('close', cleanup)
65
+ win.open()
66
+ ```
67
+
68
+ ## Five waveforms
69
+
70
+ ```javascript
71
+ var names = ['SINE', 'SQUARE', 'SAWTOOTH', 'TRIANGLE', 'NOISE']
72
+
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)
84
+ })
85
+ ```
86
+
87
+ Noise ignores pitch, but `note` keeps the event shape valid.
88
+
89
+ ## Pitch sweep and pan movement
90
+
91
+ ```javascript
92
+ synth.playTone({
93
+ frequency: 180,
94
+ frequencyEnd: 1200,
95
+ pan: -0.9,
96
+ panEnd: 0.9,
97
+ duration: 950,
98
+ attack: 5,
99
+ release: 160,
100
+ volume: 0.55,
101
+ waveType: WAVE.SAWTOOTH
102
+ })
103
+ ```
104
+
105
+ Set `frequencyEnd: 0` to keep the starting pitch. Set `panEnd` equal to `pan`
106
+ when no stereo movement is wanted.
107
+
108
+ ## Vibrato
109
+
110
+ ```javascript
111
+ synth.playTone({
112
+ note: 'E4',
113
+ duration: 1600,
114
+ attack: 100,
115
+ release: 300,
116
+ volume: 0.7,
117
+ waveType: WAVE.SINE,
118
+ lfoFreq: 5.2,
119
+ lfoDepth: 6
120
+ })
121
+ ```
122
+
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.
125
+
126
+ ## Chord with an even spread
127
+
128
+ ```javascript
129
+ synth.playChord({
130
+ notes: ['C4', 'E4', 'G4', 'C5'],
131
+ duration: 1200,
132
+ attack: 120,
133
+ release: 360,
134
+ volume: 0.65,
135
+ waveType: WAVE.TRIANGLE,
136
+ panSpread: 0.85
137
+ })
138
+ ```
139
+
140
+ ## Chord with explicit pans
141
+
142
+ ```javascript
143
+ synth.playChord({
144
+ frequencies: [261.63, 329.63, 392, 523.25],
145
+ duration: 900,
146
+ attack: 20,
147
+ release: 240,
148
+ volume: 0.62,
149
+ waveType: WAVE.SINE,
150
+ pans: [-0.8, -0.25, 0.25, 0.8]
151
+ })
152
+ ```
153
+
154
+ Use `playChord` for a known simultaneous group. Launching four separate tones
155
+ is not atomic and gives each one full event gain.
156
+
157
+ ## Tempo, note values and swing
158
+
159
+ ```javascript
160
+ synth.playPattern({
161
+ bpm: 132,
162
+ swing: 0.12,
163
+ noteValue: '8n',
164
+ attack: 4,
165
+ release: 55,
166
+ volume: 0.55,
167
+ waveType: WAVE.SQUARE,
168
+ steps: [
169
+ { note: 'C4', duration: 150 },
170
+ { note: 'E4', duration: 150 },
171
+ { rest: true },
172
+ { note: 'G4', duration: 150 },
173
+ { note: 'C5', duration: 280, noteValue: '4n' }
174
+ ]
175
+ })
176
+ ```
177
+
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.
180
+
181
+ ## Millisecond pattern
182
+
183
+ ```javascript
184
+ synth.playPattern({
185
+ stepDuration: 110,
186
+ pan: -0.4,
187
+ panEnd: 0.4,
188
+ waveType: WAVE.TRIANGLE,
189
+ steps: [
190
+ { note: 'C5', duration: 90 },
191
+ { note: 'D5', duration: 90 },
192
+ { note: 'E5', duration: 90 },
193
+ { note: 'G5', duration: 180 }
194
+ ]
195
+ })
196
+ ```
197
+
198
+ `stepDuration` controls spacing. Per-step `duration` controls how long the
199
+ sound itself lasts. The pattern root does not accept `duration`.
200
+
201
+ ## Step overrides
202
+
203
+ ```javascript
204
+ synth.playPattern({
205
+ bpm: 100,
206
+ noteValue: '8n',
207
+ attack: 10,
208
+ release: 90,
209
+ volume: 0.5,
210
+ waveType: WAVE.SINE,
211
+ 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
+ },
221
+ { rest: true },
222
+ { note: 'G4', volume: 0.75, noteValue: '4n' }
223
+ ]
224
+ })
225
+ ```
226
+
227
+ ## Noise percussion
228
+
229
+ ```javascript
230
+ synth.playPattern({
231
+ bpm: 116,
232
+ noteValue: '16n',
233
+ volume: 0.32,
234
+ waveType: WAVE.NOISE,
235
+ steps: [
236
+ { frequency: 200, duration: 70, attack: 1, release: 60 },
237
+ { rest: true },
238
+ { frequency: 200, duration: 32, attack: 1, release: 25 },
239
+ { frequency: 200, duration: 32, attack: 1, release: 25 },
240
+ { rest: true },
241
+ { frequency: 200, duration: 52, attack: 1, release: 44 }
242
+ ]
243
+ })
244
+ ```
245
+
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`.
249
+
250
+ ## Xylophone sketch
251
+
252
+ ```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)
269
+ ```
270
+
271
+ These are deliberately independent strikes. Their releases overlap, so leave
272
+ headroom in `volume` and choose enough `maxVoices` for the tails.
273
+
274
+ ## Simon-style pads
275
+
276
+ ```javascript
277
+ var simonPitches = [329.63, 440, 554.37, 659.25]
278
+
279
+ function playPad (index) {
280
+ return synth.playTone({
281
+ frequency: simonPitches[index],
282
+ duration: 340,
283
+ attack: 6,
284
+ release: 90,
285
+ volume: 0.62,
286
+ waveType: WAVE.SQUARE
287
+ })
288
+ }
289
+
290
+ function playAllPads () {
291
+ return synth.playChord({
292
+ frequencies: simonPitches,
293
+ duration: 480,
294
+ attack: 8,
295
+ release: 120,
296
+ volume: 0.58,
297
+ waveType: WAVE.SQUARE,
298
+ pans: [-0.75, -0.25, 0.25, 0.75]
299
+ })
300
+ }
301
+ ```
302
+
303
+ ## Speaker-safe startup
304
+
305
+ ```javascript
306
+ var accepted = synth.startEngine({
307
+ maxVoices: 12,
308
+ mixingProfile: 'speakerSafe'
309
+ })
310
+
311
+ if (accepted) synth.setVolume({ volume: 0.85 })
312
+ ```
313
+
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.
317
+
318
+ ## Validation at an input boundary
319
+
320
+ ```javascript
321
+ function playUserFrequency (input) {
322
+ var limits = synth.getDefaults().limits.frequency
323
+ var frequency = Number(input)
324
+
325
+ if (!isFinite(frequency)) return false
326
+ if (frequency < limits.min || frequency > limits.max) return false
327
+
328
+ return synth.playTone({ frequency: frequency })
329
+ }
330
+ ```
331
+
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.
335
+
336
+ ## Stop and clean up
337
+
338
+ ```javascript
339
+ synth.stopAll()
340
+
341
+ // Later, when the audio owner closes:
342
+ synth.shutdown()
343
+ ```
344
+
345
+ `stopAll()` fades current voices but leaves pending pattern scheduling alone.
346
+ `shutdown()` cancels the scheduler and releases the native stream.
347
+
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).
@@ -0,0 +1,252 @@
1
+ > Source snapshot: official TiSynthEngine 1.0.0 documentation at commit `6685b81` (2026-08-29).
2
+
3
+ # Guide
4
+
5
+ This guide covers the normal path from installing TiSynthEngine to releasing
6
+ the native audio resources when the owning screen closes.
7
+
8
+ ## Install the module
9
+
10
+ Use the archive for each target platform:
11
+
12
+ - `ti.synthengine-android-1.0.0.zip`
13
+ - `ti.synthengine-iphone-1.0.0.zip`
14
+
15
+ Put the archives in the Titanium project root or install them through the
16
+ Titanium CLI. Pin the same version for both platforms:
17
+
18
+ ```xml
19
+ <modules>
20
+ <module platform="android" version="1.0.0">ti.synthengine</module>
21
+ <module platform="iphone" version="1.0.0">ti.synthengine</module>
22
+ </modules>
23
+ ```
24
+
25
+ TiSynthEngine requires Titanium SDK 13.3.1.GA or newer. The iOS renderer
26
+ requires iOS 13 or newer.
27
+
28
+ ## Load and verify the contract
29
+
30
+ Require the module once in the code that owns audio. Check `apiVersion` before
31
+ starting the engine so a stale native archive fails with a useful message.
32
+
33
+ ```javascript
34
+ var synth = require('ti.synthengine')
35
+ var contract = synth.getDefaults()
36
+
37
+ if (contract.apiVersion !== '1.0.0') {
38
+ throw new Error('TiSynthEngine 1.0.0 is required; found ' + contract.apiVersion)
39
+ }
40
+ ```
41
+
42
+ `getDefaults()` works before the stream starts. Call it again after startup if
43
+ you need the frequency ceiling for input validation, because that ceiling
44
+ depends on the output sample rate.
45
+
46
+ ## Lifecycle
47
+
48
+ Opening a low-latency stream while Titanium lays out the first window can cause
49
+ an underrun on some Android devices. Register `postlayout` before `open()`, wait
50
+ for a short quiet period, and cancel the timer if the window closes first.
51
+
52
+ ```javascript
53
+ var running = false
54
+ var startTimer = null
55
+
56
+ function startAudio () {
57
+ startTimer = null
58
+ win.removeEventListener('postlayout', onPostLayout)
59
+ running = synth.startEngine({
60
+ maxVoices: 16,
61
+ mixingProfile: 'conservative'
62
+ })
63
+ }
64
+
65
+ function onPostLayout () {
66
+ if (startTimer !== null) clearTimeout(startTimer)
67
+ startTimer = setTimeout(startAudio, 250)
68
+ }
69
+
70
+ function closeAudio () {
71
+ win.removeEventListener('postlayout', onPostLayout)
72
+ win.removeEventListener('close', closeAudio)
73
+
74
+ if (startTimer !== null) {
75
+ clearTimeout(startTimer)
76
+ startTimer = null
77
+ }
78
+
79
+ if (running) {
80
+ synth.shutdown()
81
+ running = false
82
+ }
83
+ }
84
+
85
+ win.addEventListener('postlayout', onPostLayout)
86
+ win.addEventListener('close', closeAudio)
87
+ win.open()
88
+ ```
89
+
90
+ Use one owner for the engine. Start it once, pass a small audio service to the
91
+ screens that need it, and call `shutdown()` from the owner. A second
92
+ `startEngine()` call is harmless, but it makes ownership and cleanup unclear.
93
+
94
+ ## Play the first sound
95
+
96
+ ```javascript
97
+ var accepted = synth.playTone({
98
+ note: 'A4',
99
+ duration: 300,
100
+ attack: 8,
101
+ release: 80,
102
+ volume: 0.8,
103
+ waveType: contract.waveTypes.SINE
104
+ })
105
+
106
+ if (!accepted) Ti.API.warn('The tone was rejected')
107
+ ```
108
+
109
+ Calls that create or change sound return `true` when accepted and `false` when
110
+ rejected. During development, handle that result and inspect logcat or the
111
+ Xcode console for the structured validation warning.
112
+
113
+ ## Choose a pitch
114
+
115
+ `playTone` accepts one of these fields:
116
+
117
+ ```javascript
118
+ synth.playTone({ frequency: 440 })
119
+ synth.playTone({ note: 'A4' })
120
+ ```
121
+
122
+ Do not send both. Notes accept English names (`C#4`, `Bb3`) and solfege names
123
+ (`DO4`, `FA#5`, `Sib3`). When the octave is omitted, octave 4 is used.
124
+
125
+ ## Shape a tone
126
+
127
+ The oscillator runs for `duration` milliseconds. `attack` controls the fade in
128
+ and `release` controls the fade out; the time between them stays at full level.
129
+
130
+ ```javascript
131
+ synth.playTone({
132
+ note: 'C4',
133
+ duration: 1100,
134
+ attack: 180,
135
+ release: 420,
136
+ waveType: contract.waveTypes.TRIANGLE
137
+ })
138
+ ```
139
+
140
+ Explicit envelope values must fit inside `duration`. An attack equal to or
141
+ longer than the duration is rejected, as is a release longer than the time left
142
+ after attack.
143
+
144
+ Tone options can also move during the event:
145
+
146
+ ```javascript
147
+ synth.playTone({
148
+ frequency: 220,
149
+ frequencyEnd: 880,
150
+ pan: -0.8,
151
+ panEnd: 0.8,
152
+ lfoFreq: 5.5,
153
+ lfoDepth: 8,
154
+ duration: 1200,
155
+ release: 180,
156
+ waveType: contract.waveTypes.SAWTOOTH
157
+ })
158
+ ```
159
+
160
+ `frequencyEnd` creates a linear pitch sweep. `panEnd` moves the sound between
161
+ channels. `lfoFreq` is the vibrato rate in hertz and `lfoDepth` is its depth in
162
+ hertz.
163
+
164
+ ## Play simultaneous notes
165
+
166
+ Use `playChord` when notes belong to one musical event:
167
+
168
+ ```javascript
169
+ synth.playChord({
170
+ notes: ['C4', 'E4', 'G4'],
171
+ duration: 900,
172
+ attack: 25,
173
+ release: 220,
174
+ panSpread: 0.7,
175
+ waveType: contract.waveTypes.SINE
176
+ })
177
+ ```
178
+
179
+ The chord is placed on the audio queue atomically. Its voices also share a
180
+ constant-power event gain. Several separate `playTone` calls are independent,
181
+ can begin in different callbacks, and are hotter when summed.
182
+
183
+ Use `pans` when every note needs a deliberate position. Missing entries remain
184
+ centered, and an array longer than the note array is rejected.
185
+
186
+ ```javascript
187
+ synth.playChord({
188
+ frequencies: [261.63, 329.63, 392],
189
+ pans: [-0.75, 0, 0.75],
190
+ duration: 700
191
+ })
192
+ ```
193
+
194
+ ## Play a pattern
195
+
196
+ A pattern contains 1 to 512 object steps. Each step must have exactly one of
197
+ `note`, `frequency` or `rest: true`.
198
+
199
+ ```javascript
200
+ synth.playPattern({
201
+ bpm: 126,
202
+ swing: 0.1,
203
+ noteValue: '8n',
204
+ waveType: contract.waveTypes.SQUARE,
205
+ steps: [
206
+ { note: 'C4' },
207
+ { note: 'E4' },
208
+ { rest: true },
209
+ { note: 'G4', noteValue: '4n', pan: 0.5 }
210
+ ]
211
+ })
212
+ ```
213
+
214
+ Step timing is chosen in this order: step `duration`, step `noteValue`, pattern
215
+ `noteValue`, pattern `stepDuration`, then the 100 ms default. A step also
216
+ inherits the pattern's envelope, volume, waveform, pan, sweep and vibrato
217
+ options. Values set on the step override the inherited value.
218
+
219
+ Only one pattern scheduler is active. Starting another pattern cancels the
220
+ pending steps of the previous pattern. It does not stop voices that have
221
+ already started.
222
+
223
+ ## Master level and stopping
224
+
225
+ ```javascript
226
+ synth.setVolume({ volume: 0.7 })
227
+ synth.stopAll()
228
+ ```
229
+
230
+ Master volume is ramped to avoid a step in the output. `stopAll()` fades active
231
+ voices, but it does not cancel future steps in the current pattern. Start a new
232
+ pattern or call `shutdown()` when future scheduling must stop as well.
233
+
234
+ ## Output route changes
235
+
236
+ The native ports rebuild or resume their stream after headphones, Bluetooth or
237
+ other output routes change. The note crossing the transition may end early;
238
+ new notes can be played normally after the system settles. Do not restart the
239
+ whole app for a route change.
240
+
241
+ On iOS, `startEngine()` activates a `Playback` audio session. That ignores the
242
+ silent switch and normally interrupts other audio. If the app needs different
243
+ session behavior, read [coexisting with other audio](recommendations.md#coexisting-with-other-audio)
244
+ and test the final app on a device.
245
+
246
+ ## Next steps
247
+
248
+ - [API reference](api.md) for every accepted key and limit.
249
+ - [Sound design](sound-design.md) for waveform and envelope recipes.
250
+ - [Examples](examples.md) for copy-ready sounds and patterns.
251
+ - [Troubleshooting](troubleshooting.md) when a call returns `false` or audio is
252
+ silent.