@maccesar/titools 4.16.3 → 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,150 @@
1
+ > Source snapshot: official TiSynthEngine 1.0.0 documentation at commit `6685b81` (2026-08-29).
2
+
3
+ # Recommendations
4
+
5
+ The defaults work for ordinary tones and chords. The choices below matter when
6
+ an app has dense overlap, user-controlled values, demanding speakers or several
7
+ audio systems sharing one lifecycle.
8
+
9
+ ## Group simultaneous notes
10
+
11
+ Use `playChord()` when pitches form one known simultaneous event. The chord is
12
+ atomic and its source gain accounts for its voice count. Separate `playTone()`
13
+ calls are appropriate for independent touches and effects, but their start
14
+ times and gain are independent too.
15
+
16
+ For keys or pads a user can trigger freely, keep each event around 0.4 to 0.7
17
+ until the worst overlap has been heard. Raising every event above 1 is usually
18
+ a sign that the session needs `balanced`, not that each note needs more gain.
19
+
20
+ ## Choose the mixing profile once
21
+
22
+ | Situation | Profile |
23
+ | --- | --- |
24
+ | General app audio and chords | `conservative` |
25
+ | Mostly single notes that need more level | `balanced` |
26
+ | Low pitches or distortion from a small phone speaker | `speakerSafe` |
27
+ | The app performs its own strict gain staging | `raw` |
28
+
29
+ The profile is fixed for the stream. Changing it requires `shutdown()` and a
30
+ new `startEngine()` call, so it should not be a live sound-design control.
31
+
32
+ If a phone buzzes under `speakerSafe`, lower the event volume or raise the
33
+ pitch. Small drivers can distort acoustically even when the digital output is
34
+ well below its ceiling.
35
+
36
+ ## Size `maxVoices` for real overlap
37
+
38
+ `maxVoices` controls capacity. It does not make the engine louder or quieter.
39
+
40
+ | App behavior | Starting value |
41
+ | --- | ---: |
42
+ | Occasional interface feedback | 4 to 8 |
43
+ | Game effects with overlap | 8 to 16 |
44
+ | Chords, keys or long release tails | 16 to 24 |
45
+ | Explicit stress testing | 32 |
46
+
47
+ Count release tails, not just buttons pressed at the same instant. A new voice
48
+ steals the slot with the least time remaining when the pool is full. The short
49
+ fade prevents a hard click but cannot preserve the stolen note.
50
+
51
+ ## Keep one lifecycle owner
52
+
53
+ Require the module in one audio service or controller, start it after the first
54
+ layout settles, and shut it down from the same owner. This avoids an engine that
55
+ outlives its window or a timer that starts after cleanup.
56
+
57
+ A 250 ms debounce after `postlayout` is a practical baseline for a plain
58
+ screen. Heavy startup work, advertisements or entry animations may need more.
59
+ Measure the real app instead of increasing the delay indefinitely.
60
+
61
+ Route recovery is automatic. Do not call `shutdown()` just because headphones
62
+ were inserted or removed. The sound crossing the transition may end early;
63
+ play later sounds normally once the system has moved to the new route.
64
+
65
+ ## Coexisting with other audio
66
+
67
+ On iOS, the audio session is app-wide and the last component to configure it
68
+ wins. TiSynthEngine selects `Playback`, which ignores the ring/silent switch and
69
+ normally interrupts other apps. That behavior fits an instrument or an app
70
+ whose sound must be heard.
71
+
72
+ A game that should respect the silent switch may set Titanium's ambient
73
+ category after the last engine startup:
74
+
75
+ ```javascript
76
+ if (Ti.Platform.osname !== 'android') {
77
+ Ti.Media.audioSessionCategory = Ti.Media.AUDIO_SESSION_CATEGORY_AMBIENT
78
+ }
79
+ ```
80
+
81
+ The order is important because `startEngine()` configures the session. Other
82
+ native audio modules may configure it again. Test the final startup sequence,
83
+ interruptions, background behavior and route changes on iOS before relying on
84
+ an override.
85
+
86
+ Android mixes independent output streams at the system level, but another app
87
+ or stale test process can still consume low-latency resources. Force-stop old
88
+ test builds when diagnosing a glitch that does not reproduce after reboot.
89
+
90
+ ## Validate values at the app boundary
91
+
92
+ The native API rejects invalid values instead of clamping them. Use
93
+ `getDefaults().limits` for a user-facing slider, remote configuration or saved
94
+ sound preset.
95
+
96
+ ```javascript
97
+ function acceptedFrequency (raw) {
98
+ var value = Number(raw)
99
+ var range = synth.getDefaults().limits.frequency
100
+ if (!isFinite(value)) return null
101
+ if (value < range.min || value > range.max) return null
102
+ return value
103
+ }
104
+ ```
105
+
106
+ Do not copy the frequency maximum into application code. It depends on the
107
+ active sample rate and should be read again after startup.
108
+
109
+ Check Boolean return values during development. A rejected call includes the
110
+ method, key and reason in the native log. Logging that result at the caller is
111
+ often enough to identify a stale preset or misspelled field.
112
+
113
+ ## Test the intended sound
114
+
115
+ Listen on the output people will use. Headphones reveal noise and stereo detail;
116
+ the phone speaker reveals acoustic buzz, weak bass and mono collapse. Test at
117
+ low, normal and high system volume.
118
+
119
+ Use one sustained chord when judging polyphonic timbre. A loop that starts the
120
+ same chord before its predecessor finishes deliberately changes the number of
121
+ overlapping events. That can drive the limiter in a repeating cycle and sound
122
+ like pumping. Keep the loop as an extreme scheduling and recovery test, not as
123
+ the reference for how the chord should sound.
124
+
125
+ Exercise the real worst case:
126
+
127
+ - every pad the UI allows at once;
128
+ - the fastest pattern the app can request;
129
+ - a long release followed by unrelated feedback;
130
+ - a route change while a note is playing;
131
+ - screen off, interruption and background transitions if the product depends
132
+ on them.
133
+
134
+ An underrun counter only describes transport. A clean counter cannot tell you
135
+ whether a waveform, pitch or mix is pleasant.
136
+
137
+ ## Known limits
138
+
139
+ - There are no per-voice handles. A single active note cannot be edited or
140
+ stopped; `stopAll()` affects the whole pool.
141
+ - Every event has a finite duration of at most 60 seconds. There is no looping.
142
+ - The module synthesizes waveforms and does not play audio files.
143
+ - Only one pattern scheduler is pending at a time.
144
+ - There is no completion event for a note, chord or pattern.
145
+ - The module emits no JavaScript events.
146
+ - Voice capacity and mixing profile remain fixed until shutdown.
147
+ - Pattern scheduling is timer-based, not sample-accurate.
148
+
149
+ Use a sample player or a dedicated music engine when a product needs streaming,
150
+ loop points, per-voice control or a synchronized composition timeline.
@@ -0,0 +1,428 @@
1
+ > Source snapshot: official TiSynthEngine 1.0.0 documentation at commit `6685b81` (2026-08-29).
2
+
3
+ # Sound design with TiSynthEngine
4
+
5
+ TiSynthEngine builds sounds from oscillators. There is no recorded sample
6
+ underneath a tone: pitch, waveform, envelope, movement and level describe the
7
+ whole sound. That small set of controls is enough for interface feedback,
8
+ musical cues, alarms, retro effects and simple percussion.
9
+
10
+ The examples assume the engine is running and use these constants:
11
+
12
+ ```javascript
13
+ var synth = require('ti.synthengine')
14
+ var WAVE = synth.getDefaults().waveTypes
15
+ ```
16
+
17
+ <!-- TOC-START -->
18
+ ## Contents
19
+
20
+ - [Notes, frequencies and octaves](#notes-frequencies-and-octaves)
21
+ - [Waveform and timbre](#waveform-and-timbre)
22
+ - [Envelope and total duration](#envelope-and-total-duration)
23
+ - [Pan and movement](#pan-and-movement)
24
+ - [Vibrato and the LFO](#vibrato-and-the-lfo)
25
+ - [Chords, polyphony and gain](#chords-polyphony-and-gain)
26
+ - [Mixing profiles and limiter](#mixing-profiles-and-limiter)
27
+ - [Speakers, headphones and devices](#speakers-headphones-and-devices)
28
+ - [Recipes](#recipes)
29
+
30
+ <!-- TOC-END -->
31
+
32
+ ## Notes, frequencies and octaves
33
+
34
+ Frequency is the number of waveform cycles per second, measured in hertz. A
35
+ 440 Hz oscillator repeats 440 times each second. TiSynthEngine accepts a
36
+ frequency directly or converts a note name for you:
37
+
38
+ ```javascript
39
+ synth.playTone({ frequency: 440 })
40
+ synth.playTone({ note: 'A4' })
41
+ ```
42
+
43
+ Both calls have the same pitch. Note names use an octave number: `C4` is middle
44
+ C, `C5` is one octave higher, and `C3` is one octave lower. Moving up one
45
+ octave doubles frequency; moving down halves it.
46
+
47
+ The note parser accepts English letters and solfege. Sharps use `#`; flats use
48
+ a lowercase `b`.
49
+
50
+ ```text
51
+ C4 C#4 Db4 D4
52
+ DO4 DO#4 REb4 RE4
53
+ ```
54
+
55
+ When no octave is present, octave 4 is used. The supported octave digit is 0
56
+ through 9, subject to the current frequency ceiling.
57
+
58
+ The tuning system is twelve-tone equal temperament with A4 at 440 Hz. Each
59
+ semitone multiplies frequency by the twelfth root of two:
60
+
61
+ ```text
62
+ frequency = 440 * 2 ^ ((midiNote - 69) / 12)
63
+ ```
64
+
65
+ Equal temperament keeps intervals consistent in every key. It also means a
66
+ major third is the practical keyboard interval, not a pure 5:4 frequency
67
+ ratio. That distinction rarely matters for game or interface sounds.
68
+
69
+ ## Waveform and timbre
70
+
71
+ Pitch says how fast the oscillator repeats. Waveform says how it moves within
72
+ each cycle, which determines the harmonic content and perceived timbre.
73
+
74
+ ### Sine
75
+
76
+ `SINE` contains the fundamental and no intentional overtones. It works for
77
+ clean beeps, soft bass notes and sounds that should stay out of the way. On a
78
+ small speaker, very low sines may be quiet or may excite the speaker's own
79
+ distortion because there are no upper harmonics to carry the pitch.
80
+
81
+ ### Square
82
+
83
+ `SQUARE` contains strong odd harmonics. It sounds hollow, bright and electronic.
84
+ It is a good starting point for retro game cues and clear melodic prompts. The
85
+ extra high-frequency energy makes it more assertive than a sine at the same
86
+ pitch.
87
+
88
+ ### Sawtooth
89
+
90
+ `SAWTOOTH` contains both even and odd harmonics. It is the densest pitched
91
+ waveform in the module and reads as buzzy or brassy. Use it for alarms, arcade
92
+ leads and short sweeps. Square and sawtooth are band-limited with PolyBLEP to
93
+ reduce aliasing at discontinuities.
94
+
95
+ ### Triangle
96
+
97
+ `TRIANGLE` contains odd harmonics that fall off faster than a square wave. It
98
+ has more body than a sine without the hard edge of a square. It suits gentle
99
+ melodies, pads and xylophone-like sketches.
100
+
101
+ ### Noise
102
+
103
+ `NOISE` produces white noise rather than a repeating pitched cycle. Its
104
+ `frequency` is accepted because every sound event uses the same contract, but
105
+ changing that value does not tune the noise. A short envelope turns it into a
106
+ click, hat or impact layer; a long envelope creates wind or static.
107
+
108
+ Try every waveform at a moderate level before choosing one:
109
+
110
+ ```javascript
111
+ ;[WAVE.SINE, WAVE.SQUARE, WAVE.SAWTOOTH, WAVE.TRIANGLE, WAVE.NOISE]
112
+ .forEach(function (waveType, index) {
113
+ setTimeout(function () {
114
+ synth.playTone({
115
+ note: 'A4',
116
+ waveType: waveType,
117
+ duration: 450,
118
+ attack: 8,
119
+ release: 120,
120
+ volume: 0.65
121
+ })
122
+ }, index * 550)
123
+ })
124
+ ```
125
+
126
+ ## Envelope and total duration
127
+
128
+ Every event uses an attack, an implicit sustain section and a release:
129
+
130
+ ```text
131
+ silence -> attack -> full level -> release -> silence
132
+ |<---------- duration ---------->|
133
+ ```
134
+
135
+ `duration` is the total time, not the sustain time. If duration is 500 ms,
136
+ attack is 50 ms and release is 100 ms, the full-level middle lasts 350 ms.
137
+
138
+ Short attacks feel immediate. A 0 or 1 ms attack can be useful for percussion,
139
+ but an abrupt start at an unlucky phase can sound like a click. Values between
140
+ 5 and 15 ms keep most game feedback crisp without a hard edge.
141
+
142
+ Long attacks soften the onset. Use 100 to 500 ms for pads and slow warnings.
143
+ Release controls how the sound leaves: 20 to 80 ms feels tight, while several
144
+ hundred milliseconds leaves a tail.
145
+
146
+ Explicit attack and release values must fit. These relationships are required:
147
+
148
+ ```text
149
+ attack < duration
150
+ release <= duration - attack
151
+ ```
152
+
153
+ Defaults are adjusted internally if a very short duration leaves no room, but
154
+ values supplied by the caller are never reshaped silently.
155
+
156
+ ## Pan and movement
157
+
158
+ `pan` ranges from -1 on the left to 1 on the right. Zero is centered. Stereo
159
+ movement is a straight interpolation from `pan` to `panEnd` over the event.
160
+
161
+ ```javascript
162
+ synth.playTone({
163
+ note: 'E5',
164
+ pan: -0.9,
165
+ panEnd: 0.9,
166
+ duration: 900,
167
+ attack: 10,
168
+ release: 120,
169
+ waveType: WAVE.TRIANGLE
170
+ })
171
+ ```
172
+
173
+ Phone speakers are often mono or close together, so pan may be subtle. Treat it
174
+ as extra detail rather than the only cue that distinguishes two sounds.
175
+
176
+ `frequencyEnd` makes a linear pitch sweep. Use an upward sweep for activation
177
+ or reward and a downward sweep for dismissal, power-down or impact.
178
+
179
+ ```javascript
180
+ synth.playTone({
181
+ frequency: 180,
182
+ frequencyEnd: 920,
183
+ duration: 420,
184
+ attack: 4,
185
+ release: 90,
186
+ waveType: WAVE.SAWTOOTH,
187
+ volume: 0.55
188
+ })
189
+ ```
190
+
191
+ ## Vibrato and the LFO
192
+
193
+ The low-frequency oscillator modulates pitch. `lfoFreq` is the modulation rate
194
+ in cycles per second, and `lfoDepth` is the maximum pitch movement in hertz.
195
+
196
+ ```javascript
197
+ synth.playTone({
198
+ note: 'A4',
199
+ duration: 1400,
200
+ attack: 80,
201
+ release: 240,
202
+ lfoFreq: 5.5,
203
+ lfoDepth: 7,
204
+ waveType: WAVE.SINE
205
+ })
206
+ ```
207
+
208
+ A rate around 4 to 7 Hz with a small depth sounds like musical vibrato. A fast
209
+ rate or large depth becomes an effect. Because depth is measured in hertz, 8 Hz
210
+ is a larger musical interval on a low note than on a high note.
211
+
212
+ ## Chords, polyphony and gain
213
+
214
+ `playChord` is for notes that should start together. The call is placed in the
215
+ native queue as one event, so the audio callback sees the whole chord or none
216
+ of it. Its source gain is divided by the square root of the chord's voice count
217
+ to keep power stable.
218
+
219
+ ```javascript
220
+ synth.playChord({
221
+ notes: ['C4', 'E4', 'G4', 'B4'],
222
+ duration: 1500,
223
+ attack: 180,
224
+ release: 450,
225
+ volume: 0.65,
226
+ waveType: WAVE.TRIANGLE,
227
+ pans: [-0.75, -0.25, 0.25, 0.75]
228
+ })
229
+ ```
230
+
231
+ Four separate `playTone` calls are four independent full-gain events. They are
232
+ not atomic and may start in different audio callbacks. Their sum is also hotter
233
+ and can push the limiter. Separate calls are correct for unrelated taps or
234
+ effects; use `playChord` when the app already knows the pitches are one chord.
235
+
236
+ `maxVoices` is capacity, not a loudness control. When the pool is full, the
237
+ voice with the least time remaining is faded and reused. Leave a small margin for release tails, but do
238
+ not set 32 by habit when the app can only make four sounds at once.
239
+
240
+ ## Mixing profiles and limiter
241
+
242
+ Choose the profile once in `startEngine()`:
243
+
244
+ | Profile | Practical use |
245
+ | --- | --- |
246
+ | `conservative` | Default for mixed effects, chords and unpredictable overlap. |
247
+ | `balanced` | More level for mostly monophonic sounds. |
248
+ | `speakerSafe` | Lower source gain and a 0.70 ceiling for small speakers. |
249
+ | `raw` | Full source gain; useful only when the app owns all gain staging. |
250
+
251
+ Protected profiles calibrate source gain by waveform and event voice count.
252
+ The final stereo-linked limiter catches exceptional peaks with 10 ms of
253
+ lookahead and a 100 ms release. Its ceiling is 0.85, or 0.70 for `speakerSafe`.
254
+
255
+ The limiter is protection, not a loudness effect. If it works on every note,
256
+ lower event volume or choose a more conservative profile. Repeatedly starting
257
+ the same long sound before its previous release ends raises and lowers the sum,
258
+ which can produce audible pumping. That overlap is useful as a stress test, but
259
+ it is a poor reference for the tone's normal quality.
260
+
261
+ ## Speakers, headphones and devices
262
+
263
+ Headphones usually reproduce bass and stereo movement more cleanly than a phone
264
+ speaker. A sound that is smooth in headphones can buzz on a small driver even
265
+ when the digital signal never clips. Low pitches and square or sawtooth waves
266
+ are the most demanding.
267
+
268
+ For speaker-facing sounds:
269
+
270
+ - begin with `conservative` or `speakerSafe`;
271
+ - keep important pitches above roughly 200 Hz;
272
+ - use short attacks instead of an instantaneous full-scale edge;
273
+ - test at several system volume settings on the target device;
274
+ - keep critical meaning in rhythm and pitch, not pan alone.
275
+
276
+ Different phones have different sample rates, speaker protection and acoustic
277
+ resonances. Test the real device family. A desktop browser or simulator cannot
278
+ answer whether a small speaker will rattle.
279
+
280
+ ## Recipes
281
+
282
+ These recipes use only the 1.0.0 contract and keep explicit envelopes valid.
283
+ Tune pitch and volume for the app's other audio rather than treating the values
284
+ as mastering targets.
285
+
286
+ ### Interface click
287
+
288
+ ```javascript
289
+ synth.playTone({
290
+ frequency: 1200,
291
+ duration: 32,
292
+ attack: 1,
293
+ release: 24,
294
+ volume: 0.35,
295
+ waveType: WAVE.SINE
296
+ })
297
+ ```
298
+
299
+ The short sine is cleaner than a full-band noise click. Raise the frequency for
300
+ a lighter control or lower it for a heavier one.
301
+
302
+ ### Confirmation hit
303
+
304
+ ```javascript
305
+ synth.playTone({
306
+ frequency: 760,
307
+ frequencyEnd: 1040,
308
+ duration: 150,
309
+ attack: 3,
310
+ release: 90,
311
+ volume: 0.6,
312
+ waveType: WAVE.TRIANGLE
313
+ })
314
+ ```
315
+
316
+ ### Soft pad
317
+
318
+ ```javascript
319
+ synth.playChord({
320
+ notes: ['C4', 'G4', 'C5', 'E5'],
321
+ duration: 2600,
322
+ attack: 480,
323
+ release: 900,
324
+ volume: 0.55,
325
+ waveType: WAVE.TRIANGLE,
326
+ panSpread: 0.8
327
+ })
328
+ ```
329
+
330
+ ### Two-tone alarm
331
+
332
+ ```javascript
333
+ synth.playPattern({
334
+ bpm: 150,
335
+ noteValue: '8n',
336
+ attack: 8,
337
+ release: 70,
338
+ volume: 0.55,
339
+ waveType: WAVE.SAWTOOTH,
340
+ steps: [
341
+ { note: 'A4' },
342
+ { note: 'E5' },
343
+ { note: 'A4' },
344
+ { note: 'E5' }
345
+ ]
346
+ })
347
+ ```
348
+
349
+ ### Retro pickup
350
+
351
+ ```javascript
352
+ synth.playPattern({
353
+ bpm: 180,
354
+ swing: 0.06,
355
+ noteValue: '16n',
356
+ attack: 2,
357
+ release: 45,
358
+ volume: 0.5,
359
+ waveType: WAVE.SQUARE,
360
+ steps: [
361
+ { note: 'C5', duration: 80 },
362
+ { note: 'E5', duration: 80 },
363
+ { note: 'G5', duration: 80 },
364
+ { note: 'C6', duration: 160, noteValue: '8n' }
365
+ ]
366
+ })
367
+ ```
368
+
369
+ The pattern root does not accept `duration`. Put it on each sounding step when
370
+ you want a gate shorter than its rhythmic step.
371
+
372
+ ### Noise percussion
373
+
374
+ ```javascript
375
+ synth.playPattern({
376
+ bpm: 112,
377
+ noteValue: '8n',
378
+ waveType: WAVE.NOISE,
379
+ volume: 0.34,
380
+ steps: [
381
+ { frequency: 220, duration: 65, attack: 1, release: 55 },
382
+ { rest: true },
383
+ { frequency: 220, duration: 35, attack: 1, release: 28 },
384
+ { frequency: 220, duration: 35, attack: 1, release: 28 }
385
+ ]
386
+ })
387
+ ```
388
+
389
+ Noise steps still need `frequency` or `note` because every sounding pattern
390
+ step follows the same shape. The pitch value is not used by the noise source.
391
+
392
+ ### Simon-style cues
393
+
394
+ Use short, distinct square-wave pitches. Play one tone for the sequence and
395
+ one atomic chord only when demonstrating all pads together.
396
+
397
+ ```javascript
398
+ var SIMON = [329.63, 440, 554.37, 659.25]
399
+
400
+ function playSimonPad (index) {
401
+ return synth.playTone({
402
+ frequency: SIMON[index],
403
+ duration: 340,
404
+ attack: 6,
405
+ release: 90,
406
+ volume: 0.62,
407
+ waveType: WAVE.SQUARE
408
+ })
409
+ }
410
+
411
+ function playAllSimonPads () {
412
+ return synth.playChord({
413
+ frequencies: SIMON,
414
+ duration: 520,
415
+ attack: 8,
416
+ release: 130,
417
+ volume: 0.58,
418
+ waveType: WAVE.SQUARE,
419
+ pans: [-0.75, -0.25, 0.25, 0.75]
420
+ })
421
+ }
422
+ ```
423
+
424
+ For more complete code, continue with [Examples](examples.md) and
425
+ [Curated sound and interaction recipes](recipes.md). The latter includes retro
426
+ effects, a playable xylophone, memory-pad timbres, layered chords and timer
427
+ ownership. For gain and device-testing advice, see
428
+ [Recommendations](recommendations.md).