@maccesar/titools 5.2.0 → 5.2.2
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.
- package/CONTRIBUTING.md +82 -0
- package/README.md +83 -72
- package/agents/ti-pro.md +1 -1
- package/commands/ti-audit.md +2 -2
- package/docs/examples.md +91 -0
- package/docs/knowledge-index.md +57 -0
- package/lib/commands/uninstall.js +19 -16
- package/lib/commands/update.js +1 -1
- package/lib/downloader.js +1 -1
- package/lib/utils.js +2 -0
- package/package.json +4 -2
- package/skills/purgetss/references/multi-density-images.md +1 -1
- package/skills/ti-admob-unified/SKILL.md +21 -7
- package/skills/ti-admob-unified/assets/example/app.js +229 -25
- package/skills/ti-admob-unified/assets/example/lib/ads.js +30 -2
- package/skills/ti-admob-unified/assets/example/library-usage.js +33 -1
- package/skills/ti-admob-unified/references/api-reference.md +39 -16
- package/skills/ti-admob-unified/references/banners.md +9 -9
- package/skills/ti-admob-unified/references/contributing.md +14 -9
- package/skills/ti-admob-unified/references/dependencies.md +31 -25
- package/skills/ti-admob-unified/references/fullscreen.md +43 -28
- package/skills/ti-admob-unified/references/game-monetization.md +138 -7
- package/skills/ti-admob-unified/references/getting-started.md +75 -31
- package/skills/ti-admob-unified/references/index.md +5 -2
- package/skills/ti-admob-unified/references/integration.md +8 -6
- package/skills/ti-admob-unified/references/library.md +41 -10
- package/skills/ti-admob-unified/references/mediation-parity.md +17 -0
- package/skills/ti-admob-unified/references/mediation.md +22 -9
- package/skills/ti-admob-unified/references/migration.md +11 -7
- package/skills/ti-admob-unified/references/module-changelog.md +41 -11
- package/skills/ti-admob-unified/references/privacy.md +35 -8
- package/skills/ti-admob-unified/references/production.md +67 -0
- package/skills/ti-admob-unified/references/rewarded-interstitial.md +101 -0
- package/skills/ti-admob-unified/references/roadmap.md +28 -31
- package/skills/ti-admob-unified/references/source-notes.md +51 -4
- package/skills/ti-admob-unified/references/source-snapshot.json +249 -45
- package/skills/ti-admob-unified/references/third-party-notices.md +4 -4
- package/skills/ti-game/SKILL.md +23 -6
- package/skills/ti-game/references/api.md +34 -30
- package/skills/ti-game/references/debugging-performance.md +18 -9
- package/skills/ti-game/references/examples.md +103 -20
- package/skills/ti-game/references/input.md +101 -0
- package/skills/ti-game/references/patterns.md +39 -15
- package/skills/ti-game/references/physics.md +80 -52
- package/skills/ti-game/references/project-setup.md +118 -56
- package/skills/ti-game/references/recipes-action.md +107 -8
- package/skills/ti-game/references/recipes-adventure-effects.md +41 -9
- package/skills/ti-game/references/roadmap.md +10 -3
- package/skills/ti-game/references/source-notes.md +81 -0
- package/skills/ti-game/references/tilemaps.md +12 -4
- package/skills/ti-synthengine/SKILL.md +81 -95
- package/skills/ti-synthengine/assets/example/README.md +84 -0
- package/skills/ti-synthengine/assets/example/app.js +1178 -0
- package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +83 -0
- package/skills/ti-synthengine/assets/example/lib/sequenceScore.js +25 -0
- package/skills/ti-synthengine/references/advanced-examples.md +174 -0
- package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
- package/skills/ti-synthengine/references/android-sim80-correction.json +162 -0
- package/skills/ti-synthengine/references/api.md +434 -380
- package/skills/ti-synthengine/references/audio-engine.md +157 -230
- package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
- package/skills/ti-synthengine/references/examples.md +123 -253
- package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-physical-validation.json +1918 -0
- package/skills/ti-synthengine/references/guide.md +188 -156
- package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
- package/skills/ti-synthengine/references/performance-review.json +1146 -0
- package/skills/ti-synthengine/references/physical-validation.json +1457 -0
- package/skills/ti-synthengine/references/recipes-interaction.md +549 -0
- package/skills/ti-synthengine/references/recipes.md +423 -599
- package/skills/ti-synthengine/references/recommendations.md +63 -113
- package/skills/ti-synthengine/references/sound-design.md +134 -258
- package/skills/ti-synthengine/references/source-notes.md +93 -105
- package/skills/ti-synthengine/references/source-snapshot.json +379 -32
- package/skills/ti-synthengine/references/stats-performance.md +64 -20
- package/skills/ti-synthengine/references/studio-validation.json +6202 -0
- package/skills/ti-synthengine/references/troubleshooting.md +109 -121
- package/skills/ti-synthengine/references/unified-bypass-performance.json +22425 -0
- package/skills/ti-synthengine/references/unified-validation.json +314 -0
- package/AGENTS-VERCEL-RESEARCH.md +0 -175
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
> Source snapshot: official TiSynthEngine 1.0.0
|
|
1
|
+
> Source snapshot: official TiSynthEngine 1.0.0 documentation at release source commit `e7cbbe8a6f9ab38088fd8029c7b43cef6501101f` (2026-10-24). Upstream `documentation/guide.md` SHA-256: `dd03392e1d5f89e520f4be70dab2fbad8e86776bf3a60d6af93f0dc0695c8c69`.
|
|
2
2
|
|
|
3
3
|
# Guide
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
the native audio resources when the owning screen closes.
|
|
5
|
+
Install TiSynthEngine, start it after layout settles, and release its native audio resources when the owning screen closes.
|
|
7
6
|
|
|
8
7
|
<!-- TOC-START -->
|
|
9
8
|
## Contents
|
|
@@ -11,30 +10,35 @@ the native audio resources when the owning screen closes.
|
|
|
11
10
|
- [Install the module](#install-the-module)
|
|
12
11
|
- [Load and verify the contract](#load-and-verify-the-contract)
|
|
13
12
|
- [Lifecycle](#lifecycle)
|
|
13
|
+
- [Detect required capabilities](#detect-required-capabilities)
|
|
14
|
+
- [Hold and edit a voice](#hold-and-edit-a-voice)
|
|
15
|
+
- [Choose the musical transport](#choose-the-musical-transport)
|
|
16
|
+
- [Add master effects](#add-master-effects)
|
|
14
17
|
- [Play the first sound](#play-the-first-sound)
|
|
15
18
|
- [Choose a pitch](#choose-a-pitch)
|
|
16
19
|
- [Shape a tone](#shape-a-tone)
|
|
17
20
|
- [Settle, filter and modulate](#settle-filter-and-modulate)
|
|
18
21
|
- [Shape noise and waveforms](#shape-noise-and-waveforms)
|
|
19
22
|
- [Play simultaneous notes](#play-simultaneous-notes)
|
|
20
|
-
- [Play a
|
|
21
|
-
- [Know when a
|
|
23
|
+
- [Play a sequence](#play-a-sequence)
|
|
24
|
+
- [Know when a sequence ends](#know-when-a-sequence-ends)
|
|
22
25
|
- [Master level and stopping](#master-level-and-stopping)
|
|
23
26
|
- [Output route changes](#output-route-changes)
|
|
24
27
|
- [Integrate binaural in a 2D app](#integrate-binaural-in-a-2d-app)
|
|
28
|
+
- [Read engine statistics](#read-engine-statistics)
|
|
25
29
|
- [Next steps](#next-steps)
|
|
30
|
+
- [Diagnose mix headroom](#diagnose-mix-headroom)
|
|
26
31
|
|
|
27
32
|
<!-- TOC-END -->
|
|
28
33
|
|
|
29
34
|
## Install the module
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
Download both official packages from [Android 1.0.0](https://github.com/macCesar/TiSynthEngine/releases/tag/android-1.0.0) and [iOS 1.0.0](https://github.com/macCesar/TiSynthEngine/releases/tag/ios-1.0.0). For source builds, the matching archives are produced under `android/dist` and `ios/dist`:
|
|
32
37
|
|
|
33
38
|
- `ti.synthengine-android-1.0.0.zip`
|
|
34
39
|
- `ti.synthengine-iphone-1.0.0.zip`
|
|
35
40
|
|
|
36
|
-
Put the archives in the Titanium project root or install them through the
|
|
37
|
-
Titanium CLI. Pin the same version for both platforms:
|
|
41
|
+
Put the archives in the Titanium project root or install them through the Titanium CLI. Pin the same version for both platforms:
|
|
38
42
|
|
|
39
43
|
```xml
|
|
40
44
|
<modules>
|
|
@@ -43,13 +47,11 @@ Titanium CLI. Pin the same version for both platforms:
|
|
|
43
47
|
</modules>
|
|
44
48
|
```
|
|
45
49
|
|
|
46
|
-
TiSynthEngine requires Titanium SDK 13.2.0.GA or newer. Android projects must
|
|
47
|
-
compile against API 35 or newer. The iOS module requires iOS 15 or newer.
|
|
50
|
+
TiSynthEngine requires Titanium SDK 13.2.0.GA or newer. Android projects must compile against API 35 or newer. The iOS module requires iOS 15 or newer.
|
|
48
51
|
|
|
49
52
|
## Load and verify the contract
|
|
50
53
|
|
|
51
|
-
Require the module once in the code that owns audio. Check `apiVersion` before
|
|
52
|
-
starting the engine so a stale native archive fails with a useful message.
|
|
54
|
+
Require the module once in the code that owns audio. Check `apiVersion` before starting. Also check the [spatial limits](#detect-the-installed-feature) and [statistics methods](#read-engine-statistics) if the app needs those features. Check [required capabilities](#detect-required-capabilities) for held voices, musical transport and master processing.
|
|
53
55
|
|
|
54
56
|
```javascript
|
|
55
57
|
var synth = require('ti.synthengine')
|
|
@@ -60,15 +62,11 @@ if (contract.apiVersion !== '1.0.0') {
|
|
|
60
62
|
}
|
|
61
63
|
```
|
|
62
64
|
|
|
63
|
-
`getDefaults()` works before the stream starts. Call it again after startup if
|
|
64
|
-
you need the frequency ceiling for input validation, because that ceiling
|
|
65
|
-
depends on the output sample rate.
|
|
65
|
+
`getDefaults()` works before the stream starts. Call it again after startup if you need the frequency ceiling for input validation, because that ceiling depends on the output sample rate.
|
|
66
66
|
|
|
67
67
|
## Lifecycle
|
|
68
68
|
|
|
69
|
-
Opening a low-latency stream while Titanium lays out the first window can cause
|
|
70
|
-
an underrun on some Android devices. Register `postlayout` before `open()`, wait
|
|
71
|
-
for a short quiet period, and cancel the timer if the window closes first.
|
|
69
|
+
Opening a low-latency stream while Titanium lays out the first window can cause an underrun on some Android devices. Register `postlayout` before `open()`, wait for a short quiet period, and cancel the timer if the window closes first.
|
|
72
70
|
|
|
73
71
|
```javascript
|
|
74
72
|
var running = false
|
|
@@ -77,10 +75,12 @@ var startTimer = null
|
|
|
77
75
|
function startAudio () {
|
|
78
76
|
startTimer = null
|
|
79
77
|
win.removeEventListener('postlayout', onPostLayout)
|
|
80
|
-
running = synth.
|
|
78
|
+
running = synth.start({
|
|
81
79
|
maxVoices: 16,
|
|
82
|
-
mixingProfile: 'conservative'
|
|
80
|
+
mixingProfile: 'conservative',
|
|
81
|
+
audioSession: 'ambient'
|
|
83
82
|
})
|
|
83
|
+
if (!running) Ti.API.warn('TiSynthEngine could not start; keep playback controls disabled')
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function onPostLayout () {
|
|
@@ -108,9 +108,61 @@ win.addEventListener('close', closeAudio)
|
|
|
108
108
|
win.open()
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
Use one owner for the engine. Start it once, pass a small audio service to the
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
Use one owner for the engine. Start it once, pass a small audio service to the screens that need it, and call `shutdown()` from the owner. While the engine is running, another valid `start()` call returns `true` and leaves the existing options in effect. Changing voice capacity, profile or session policy requires `shutdown()` and a new start. This example selects `ambient` for game/UI audio; the default remains `playback`. Android validates the session policy and leaves output unchanged.
|
|
112
|
+
|
|
113
|
+
## Detect required capabilities
|
|
114
|
+
|
|
115
|
+
Release 1.0.0 includes held voices, sample-clock sequences, stereo effects and two optional LFOs. Require the methods used by each feature before enabling its controls:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
function hasMethods (names) {
|
|
119
|
+
return names.every(function (name) { return typeof synth[name] === 'function' })
|
|
120
|
+
}
|
|
121
|
+
var supportsHeld = hasMethods(['noteOn', 'noteOff', 'updateVoice'])
|
|
122
|
+
var supportsSequence = hasMethods(['playSequence', 'updateSequence', 'stopSequence', 'getSequenceState'])
|
|
123
|
+
var supportsProcessing = hasMethods(['setMasterProcessing'])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Start the engine successfully before publishing held notes, sequence edits or processing. `getDefaults` and feature checks do not open the stream. Install matching packages for Android/iOS and rebuild the application when replacing a native module. Additional LFOs require the documented modulation contract; check acceptance of the complete configuration. Treat rejected configurations as unavailable rather than silently dropping their keys.
|
|
127
|
+
|
|
128
|
+
## Hold and edit a voice
|
|
129
|
+
|
|
130
|
+
Use `noteOn({ voiceId, ... })` when duration follows interaction: a piano key, motor, beam, pad or the last chord of a ceremony. Allocate a unique positive integer ID per attack, and release only that ID with `noteOff`. No duration or sweep-end fields are accepted. A chord held this way consists of separate IDs; unlike one `playChord` or sequence chord, separate `noteOn` calls have independent gain and publication.
|
|
131
|
+
|
|
132
|
+
`updateVoice` changes compatible pitch, volume, cutoff/Q, vibrato, PWM/FM, LFO targets, noise clock, one-pole cutoff, effect send and stereo/spatial position with 10 ms ramps, without restarting oscillator/envelope phase or clearing spatial history. Source, table, filter type and ADSR are fixed at attack. Spatial edits require a voice started spatially; stereo pan edits require a stereo voice. See [complete fields](api.md#updatevoiceoptions). A note that has ended or been stolen accepts updates as a no-op; there is no note-finished event or reserved slot. A `true` return means accepted publication, not proof that the sound is still active.
|
|
133
|
+
|
|
134
|
+
Release on touch end and cancellation as well as ordinary key up. Retry a rejected release twice at 16 ms outside the audio callback, then shut down and report failure if publication remains blocked. Cancel retries during teardown. The [held motor example](advanced-examples.md#sustained-game-motor) implements this ownership policy. For pause/owner close, invalidate app scheduling and shut down; require a new start before future notes.
|
|
135
|
+
|
|
136
|
+
## Choose the musical transport
|
|
137
|
+
|
|
138
|
+
Use `playSequence` for all successive native sounds. `playTone` and `playChord` handle independent timed events; `noteOn`/`noteOff` hold a sound for interaction. Start with `maxSequences: 1…4` to preallocate independent sequence capacity. All use the same pool of up to 32 voices.
|
|
139
|
+
|
|
140
|
+
Store the positive identifier returned by `playSequence`, and include it in each update, stop or state query. 0 rejects publication. Three banks per sequence publish complete edits atomically. Structural edits enter at the next step; swing at the next pair. `setTempo({ bpm })` applies at the next block, preserving beat position. Beat sequences follow it; ms sequences keep fixed times. `stopSequence({ sequenceId })` releases only that owner's voices and leaves effect tails. A retired identifier returns null from `getSequenceState`.
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
synth.setTempo({ bpm: 112 })
|
|
144
|
+
var sequenceId = synth.playSequence({
|
|
145
|
+
stepDuration: 0.5, swing: 0.12, bus: 'music',
|
|
146
|
+
voice: { waveType: WAVE.SQUARE, volume: 0.55 },
|
|
147
|
+
steps: [{ notes: [60] }, { notes: [64], overrides: { pan: -0.4, panEnd: 0.4 } },
|
|
148
|
+
{ rest: true }, { notes: [67], duration: 1, length: 0.8 }]
|
|
149
|
+
})
|
|
150
|
+
if (sequenceId === 0) throw new Error('Sequence rejected')
|
|
151
|
+
// Stop this owner without affecting unrelated voices:
|
|
152
|
+
synth.stopSequence({ sequenceId: sequenceId })
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
See [the full sequence contract](api.md#native-sequence-transport) for length, gate, variable durations, arpeggios, revisions and capacity rejection.
|
|
156
|
+
|
|
157
|
+
## Add master effects
|
|
158
|
+
|
|
159
|
+
The native chain is voices, fixed input gain, chorus, ping-pong delay, stereo Schroeder reverb, master volume, optional saturation, then the final limiter. The initial `inputGain` is 1 and chorus/delay/reverb/saturation are disabled. New notes have no extra LFOs unless requested. This preserves default render behavior without making effects exclusive to instruments or Studio.
|
|
160
|
+
|
|
161
|
+
Chorus adds stereo movement, delay adds free or synchronized echoes, and reverb adds a damped tail. Saturation is optional creative drive. Select processed categories with `applyTo: ['music', 'ambience']`; a voice's explicit `effectSend` overrides that selection. One chain processes the selected share and one final limiter protects the recombined output.
|
|
162
|
+
|
|
163
|
+
`setMasterProcessing` merges partial updates, validates the entire result and returns publication acceptance. Continuous controls ramp over 10 ms. Disabling an effect closes input gradually and keeps its tail, bounded to 30 seconds with a final fade, then restores exact bypass. It does not undo a non-unity input gain. `stopAll` invalidates histories and stops the native sequence; `shutdown` stops the stream and frees its buffers. See the [processing example](advanced-examples.md#chorus-delay-reverb-and-creative-saturation) and [field ranges](api.md#setmasterprocessingoptions).
|
|
164
|
+
|
|
165
|
+
Delay and LFO sync follow global `setTempo` (initially 120); free settings retain milliseconds or hertz. The two extra voice LFOs target filter cutoff, square PWM or sine FM, with sine/triangle waves. They are independent of `lfoFreq`/`lfoDepth` pitch vibrato. See [two LFOs](advanced-examples.md#two-voice-lfos).
|
|
114
166
|
|
|
115
167
|
## Play the first sound
|
|
116
168
|
|
|
@@ -127,9 +179,7 @@ var accepted = synth.playTone({
|
|
|
127
179
|
if (!accepted) Ti.API.warn('The tone was rejected')
|
|
128
180
|
```
|
|
129
181
|
|
|
130
|
-
|
|
131
|
-
rejected. During development, handle that result and inspect logcat or the
|
|
132
|
-
Xcode console for the structured validation warning.
|
|
182
|
+
Timed and held sound calls and accepted edits return `true` when accepted and `false` when rejected. `playSequence` instead returns a positive sequence ID or `0`; state queries return their documented state or `null`, and lifecycle/cleanup methods follow their own return contracts. During development, handle the relevant result and inspect logcat or the Xcode console for the structured validation warning.
|
|
133
183
|
|
|
134
184
|
## Choose a pitch
|
|
135
185
|
|
|
@@ -140,13 +190,11 @@ synth.playTone({ frequency: 440 })
|
|
|
140
190
|
synth.playTone({ note: 'A4' })
|
|
141
191
|
```
|
|
142
192
|
|
|
143
|
-
Do not send both. Notes accept English names (`C#4`, `Bb3`) and solfege names
|
|
144
|
-
(`DO4`, `FA#5`, `Sib3`). When the octave is omitted, octave 4 is used.
|
|
193
|
+
Do not send both. Notes accept English names (`C#4`, `Bb3`) and solfege names (`DO4`, `FA#5`, `Sib3`). When the octave is omitted, octave 4 is used.
|
|
145
194
|
|
|
146
195
|
## Shape a tone
|
|
147
196
|
|
|
148
|
-
The oscillator runs for `duration` milliseconds. `attack` controls the fade in
|
|
149
|
-
and `release` controls the fade out; the time between them stays at full level.
|
|
197
|
+
The oscillator runs for `duration` milliseconds. `attack` controls the fade in and `release` controls the fade out; the time between them stays at full level.
|
|
150
198
|
|
|
151
199
|
```javascript
|
|
152
200
|
synth.playTone({
|
|
@@ -158,9 +206,7 @@ synth.playTone({
|
|
|
158
206
|
})
|
|
159
207
|
```
|
|
160
208
|
|
|
161
|
-
Explicit envelope values must fit inside `duration`. An attack equal to or
|
|
162
|
-
longer than the duration is rejected, as is a release longer than the time left
|
|
163
|
-
after attack.
|
|
209
|
+
Explicit envelope values must fit inside `duration`. An attack equal to or longer than the duration is rejected, as is a release longer than the time left after attack.
|
|
164
210
|
|
|
165
211
|
Tone options can also move during the event:
|
|
166
212
|
|
|
@@ -178,15 +224,11 @@ synth.playTone({
|
|
|
178
224
|
})
|
|
179
225
|
```
|
|
180
226
|
|
|
181
|
-
`frequencyEnd` creates a linear pitch sweep. `panEnd` moves the sound between
|
|
182
|
-
channels. `lfoFreq` is the vibrato rate in hertz and `lfoDepth` is its depth in
|
|
183
|
-
hertz.
|
|
227
|
+
`frequencyEnd` creates a linear pitch sweep. `panEnd` moves the sound between channels. `lfoFreq` is the vibrato rate in hertz and `lfoDepth` is its depth in hertz.
|
|
184
228
|
|
|
185
229
|
## Settle, filter and modulate
|
|
186
230
|
|
|
187
|
-
A flat envelope suits beeps. For plucks, keys and bells, add `decay` and
|
|
188
|
-
`sustain`: the level falls from full scale to `sustain` over `decay`
|
|
189
|
-
milliseconds and holds there until the release.
|
|
231
|
+
A flat envelope suits beeps. For plucks, keys and bells, add `decay` and `sustain`: the level falls from full scale to `sustain` over `decay` milliseconds and holds there until the release.
|
|
190
232
|
|
|
191
233
|
```javascript
|
|
192
234
|
synth.playTone({
|
|
@@ -200,13 +242,9 @@ synth.playTone({
|
|
|
200
242
|
})
|
|
201
243
|
```
|
|
202
244
|
|
|
203
|
-
`sustain` below 1 needs a nonzero `decay`, and `attack` plus `decay` must fit in
|
|
204
|
-
`duration`. The 50 ms default attack counts, so set a short `attack` when the
|
|
205
|
-
decay is long.
|
|
245
|
+
`sustain` below 1 needs a nonzero `decay`, and `attack` plus `decay` must fit in `duration`. The 50 ms default attack counts, so set a short `attack` when the decay is long.
|
|
206
246
|
|
|
207
|
-
`filterType` adds a resonant filter: `lowpass`, `highpass` or `bandpass`.
|
|
208
|
-
`filterHz` is the cutoff, `filterQ` the resonance, and `filterHzEnd` sweeps the
|
|
209
|
-
cutoff over the note.
|
|
247
|
+
`filterType` adds a resonant filter: `lowpass`, `highpass` or `bandpass`. `filterHz` is the cutoff, `filterQ` the resonance, and `filterHzEnd` sweeps the cutoff over the note.
|
|
210
248
|
|
|
211
249
|
```javascript
|
|
212
250
|
synth.playTone({
|
|
@@ -221,12 +259,9 @@ synth.playTone({
|
|
|
221
259
|
})
|
|
222
260
|
```
|
|
223
261
|
|
|
224
|
-
Any `filterHz`, `filterHzEnd` or `filterQ` without a `filterType` other than
|
|
225
|
-
`none` is rejected with `filter_type_required`.
|
|
262
|
+
Any `filterHz`, `filterHzEnd` or `filterQ` without a `filterType` other than `none` is rejected with `filter_type_required`.
|
|
226
263
|
|
|
227
|
-
`fmDepth` makes a `SINE` voice an FM voice. `fmRatio` is the modulator
|
|
228
|
-
frequency as a multiple of the carrier, and `fmDepthEnd` moves the modulation
|
|
229
|
-
index over the note. Omit `fmDepthEnd` for no sweep; 0 is a real target.
|
|
264
|
+
`fmDepth` makes a `SINE` voice an FM voice. `fmRatio` is the modulator frequency as a multiple of the carrier, and `fmDepthEnd` moves the modulation index over the note. Omit `fmDepthEnd` for no sweep; 0 is a real target.
|
|
230
265
|
|
|
231
266
|
```javascript
|
|
232
267
|
synth.playTone({
|
|
@@ -241,8 +276,7 @@ synth.playTone({
|
|
|
241
276
|
})
|
|
242
277
|
```
|
|
243
278
|
|
|
244
|
-
`detune` shifts a pitch in cents. In a chord, `detunes` gives one offset per
|
|
245
|
-
pitch, so repeating a note with small offsets thickens it:
|
|
279
|
+
`detune` shifts a pitch in cents. In a chord, `detunes` gives one offset per pitch, so repeating a note with small offsets thickens it:
|
|
246
280
|
|
|
247
281
|
```javascript
|
|
248
282
|
synth.playChord({
|
|
@@ -255,9 +289,7 @@ synth.playChord({
|
|
|
255
289
|
|
|
256
290
|
## Shape noise and waveforms
|
|
257
291
|
|
|
258
|
-
Use `noiseRate` to hold each pseudo-random noise value instead of generating a
|
|
259
|
-
new one every sample. A low rate sounds sparse and grainy; a high rate moves
|
|
260
|
-
toward white noise. `noiseRateEnd` sweeps the update rate.
|
|
292
|
+
Use `noiseRate` to hold each pseudo-random noise value instead of generating a new one every sample. A low rate sounds sparse and grainy; a high rate moves toward white noise. `noiseRateEnd` sweeps the update rate.
|
|
261
293
|
|
|
262
294
|
```javascript
|
|
263
295
|
synth.playTone({
|
|
@@ -273,8 +305,7 @@ synth.playTone({
|
|
|
273
305
|
})
|
|
274
306
|
```
|
|
275
307
|
|
|
276
|
-
`lowPassHz` is a per-voice one-pole filter. `bitDepth` applies amplitude
|
|
277
|
-
quantization from 2 to 16 bits. Zero disables either control.
|
|
308
|
+
`lowPassHz` is a per-voice one-pole filter. `bitDepth` applies amplitude quantization from 2 to 16 bits. Zero disables either control.
|
|
278
309
|
|
|
279
310
|
Square waves accept a duty cycle and a duty-cycle sweep:
|
|
280
311
|
|
|
@@ -289,8 +320,7 @@ synth.playTone({
|
|
|
289
320
|
})
|
|
290
321
|
```
|
|
291
322
|
|
|
292
|
-
For a caller-defined repeating shape, select `WAVETABLE` and provide 2 to 64
|
|
293
|
-
samples from -1 to 1. The table is stepped rather than interpolated.
|
|
323
|
+
For a caller-defined repeating shape, select `WAVETABLE` and provide 2 to 64 samples from -1 to 1. The table is stepped rather than interpolated.
|
|
294
324
|
|
|
295
325
|
```javascript
|
|
296
326
|
synth.playTone({
|
|
@@ -317,11 +347,9 @@ synth.playChord({
|
|
|
317
347
|
})
|
|
318
348
|
```
|
|
319
349
|
|
|
320
|
-
The chord is placed on the audio queue atomically. Its voices also share a
|
|
321
|
-
constant-power event gain. Several separate `playTone` calls are independent,
|
|
322
|
-
can begin in different callbacks, and are hotter when summed.
|
|
350
|
+
The chord is placed on the audio queue atomically. Its voices also share a constant-power event gain. Several separate `playTone` calls are independent, can begin in different callbacks, and are hotter when summed.
|
|
323
351
|
|
|
324
|
-
Use `pans`
|
|
352
|
+
Use `pans` to choose a position for each note. Supplied entries override `panSpread`; missing entries use the spread position, or remain centered when spread is 0. An array longer than the note array is rejected.
|
|
325
353
|
|
|
326
354
|
```javascript
|
|
327
355
|
synth.playChord({
|
|
@@ -331,65 +359,34 @@ synth.playChord({
|
|
|
331
359
|
})
|
|
332
360
|
```
|
|
333
361
|
|
|
334
|
-
## Play a
|
|
335
|
-
|
|
336
|
-
A pattern contains 1 to 512 object steps. Each step must have exactly one of
|
|
337
|
-
`note`, `frequency` or `rest: true`.
|
|
362
|
+
## Play a sequence
|
|
338
363
|
|
|
339
364
|
```javascript
|
|
340
|
-
synth.
|
|
341
|
-
|
|
342
|
-
swing: 0.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
{ note: 'C4' },
|
|
347
|
-
{ note: 'E4' },
|
|
348
|
-
{ rest: true },
|
|
349
|
-
{ note: 'G4', noteValue: '4n', pan: 0.5 }
|
|
350
|
-
]
|
|
365
|
+
synth.setTempo({ bpm: 112 })
|
|
366
|
+
var sequenceId = synth.playSequence({
|
|
367
|
+
stepDuration: 0.5, swing: 0.12, bus: 'music',
|
|
368
|
+
voice: { waveType: WAVE.SQUARE, volume: 0.55 },
|
|
369
|
+
steps: [{ notes: [60] }, { notes: [64], overrides: { pan: -0.4, panEnd: 0.4 } },
|
|
370
|
+
{ rest: true }, { notes: [67], duration: 1, length: 0.8 }]
|
|
351
371
|
})
|
|
372
|
+
if (sequenceId === 0) throw new Error('Sequence rejected')
|
|
373
|
+
// Stop this owner without affecting unrelated voices:
|
|
374
|
+
synth.stopSequence({ sequenceId: sequenceId })
|
|
352
375
|
```
|
|
353
376
|
|
|
354
|
-
|
|
355
|
-
`noteValue`, pattern `stepDuration`, then the 100 ms default. A step also
|
|
356
|
-
inherits the pattern's envelope, decay, sustain, volume, waveform, pan, sweep,
|
|
357
|
-
vibrato, noise, filter, resonant filter, FM, detune, pulse-width, wavetable and
|
|
358
|
-
quantization options. Values set on the step override the inherited value.
|
|
377
|
+
## Know when a sequence ends
|
|
359
378
|
|
|
360
|
-
|
|
361
|
-
pending steps of the previous pattern. It does not stop voices that have
|
|
362
|
-
already started.
|
|
363
|
-
|
|
364
|
-
## Know when a pattern ends
|
|
365
|
-
|
|
366
|
-
Each accepted pattern produces one `patternend` event. `completed` is `true`
|
|
367
|
-
when the pattern reaches its natural end and `false` when a newer pattern
|
|
368
|
-
replaces it. Use it to chain jingles or to release state that follows a
|
|
369
|
-
pattern.
|
|
370
|
-
|
|
371
|
-
```javascript
|
|
372
|
-
function onPatternEnd (event) {
|
|
373
|
-
if (event.completed) playNextJingle()
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
synth.addEventListener('patternend', onPatternEnd)
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
The listener belongs to the module, not to the pattern, so it outlives the
|
|
380
|
-
screen unless you remove it. Add it to the same cleanup that calls
|
|
381
|
-
`shutdown()`:
|
|
379
|
+
Listen for `sequenceend`, whose payload is `{ sequenceId, reason: 'completed' | 'stopped' }`. Filter by the owner ID before changing UI. The event is asynchronous and may arrive while a release or effect tail remains audible. For a precise next attack, place it in the same native sequence; event chaining crosses JavaScript delivery latency. Remove the listener during owner cleanup. Identifiers survive internal slot reuse without referring to the new sequence.
|
|
382
380
|
|
|
383
381
|
```javascript
|
|
384
|
-
function
|
|
385
|
-
|
|
386
|
-
if (running) synth.shutdown()
|
|
382
|
+
function onSequenceEnd (event) {
|
|
383
|
+
if (event.sequenceId === sequenceId) Ti.API.info(event.reason)
|
|
387
384
|
}
|
|
385
|
+
synth.addEventListener('sequenceend', onSequenceEnd)
|
|
386
|
+
// During owner cleanup:
|
|
387
|
+
synth.removeEventListener('sequenceend', onSequenceEnd)
|
|
388
388
|
```
|
|
389
389
|
|
|
390
|
-
`shutdown()` discards a pending pattern without an event, so a closing owner
|
|
391
|
-
never receives a callback after cleanup.
|
|
392
|
-
|
|
393
390
|
## Master level and stopping
|
|
394
391
|
|
|
395
392
|
```javascript
|
|
@@ -397,32 +394,23 @@ synth.setVolume({ volume: 0.7 })
|
|
|
397
394
|
synth.stopAll()
|
|
398
395
|
```
|
|
399
396
|
|
|
400
|
-
Master volume is ramped to avoid a step in the output. `stopAll()` fades active
|
|
401
|
-
voices, but it does not cancel future steps in the current pattern. Start a new
|
|
402
|
-
pattern or call `shutdown()` when future scheduling must stop as well.
|
|
397
|
+
Master volume is ramped to avoid a step in the output. `stopAll()` stops all native sequences, fades active voices and invalidates effect histories while leaving the engine available. Use `stopSequence({ sequenceId })` to stop only one owner and preserve unrelated sounds. `shutdown()` also stops the stream and releases its resources.
|
|
403
398
|
|
|
404
399
|
## Output route changes
|
|
405
400
|
|
|
406
|
-
The native ports rebuild or resume their stream after headphones, Bluetooth or
|
|
407
|
-
other output routes change. The note crossing the transition may end early;
|
|
408
|
-
new notes can be played normally after the system settles. Do not restart the
|
|
409
|
-
whole app for a route change.
|
|
401
|
+
The native ports rebuild or resume their stream after headphones, Bluetooth or other output routes change. The note crossing the transition may end early; new notes can be played normally after the system settles. Do not restart the whole app for a route change.
|
|
410
402
|
|
|
411
|
-
On iOS, `
|
|
412
|
-
ignores the silent switch and normally interrupts other audio. Pass
|
|
413
|
-
`audioSession: 'ambient'` to respect the silent switch and mix with other apps;
|
|
414
|
-
read [coexisting with other audio](recommendations.md#coexisting-with-other-audio)
|
|
415
|
-
and test the final app on a device.
|
|
403
|
+
On iOS, `start()` activates a `Playback` audio session by default. That ignores the silent switch and normally interrupts other audio. Pass `audioSession: 'ambient'` to respect the silent switch and mix with other apps; read [coexisting with other audio](recommendations.md#coexisting-with-other-audio) and test the final app on a device.
|
|
416
404
|
|
|
417
405
|
## Integrate binaural in a 2D app
|
|
418
406
|
|
|
419
407
|
The JavaScript app chooses a position for each sound; the native module generates and filters that voice. Installing the module or enabling an app switch does not convert existing `pan` calls into binaural calls. Update every playback path that should use positioning, including explosions, impacts, shots, lasers and repeated motor/thrust events. A central audio service can do this once for all effects that supply their current source position. Calls bypassing that service keep their existing behavior.
|
|
420
408
|
|
|
421
|
-
Keep the synthesis recipe (pitch, waveform, duration, envelope, filters, FM and volume)
|
|
409
|
+
Keep the synthesis recipe (pitch, waveform, duration, envelope, filters, FM and volume), and replace its placement fields when submitting the event. The native ports process each event with the shared KEMAR FIR; there is no global platform spatial-audio switch.
|
|
422
410
|
|
|
423
411
|
### Detect the installed feature
|
|
424
412
|
|
|
425
|
-
|
|
413
|
+
Check both coordinate limits before enabling binaural controls to verify that the app loaded the expected native module:
|
|
426
414
|
|
|
427
415
|
```javascript
|
|
428
416
|
var synth = require('ti.synthengine')
|
|
@@ -437,28 +425,28 @@ If the limits are absent, disable the binaural switch and keep stereo playback.
|
|
|
437
425
|
|
|
438
426
|
`spatialX`/`spatialY` are signed directions from -1 to 1. A 0 to 100% amplitude slider scales the position derived from the object; it does not select a direction. At height 100%, an upper object remains above and a lower object remains below. At height 0%, both are at the front-center height, while binaural remains enabled. Direct direction sliders would instead need -100 to +100%, divided by 100.
|
|
439
427
|
|
|
440
|
-
| App control
|
|
441
|
-
|
|
|
442
|
-
| Binaural switch
|
|
428
|
+
| App control | Effect |
|
|
429
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
430
|
+
| Binaural switch | Send spatial coordinates when on; omit both when off. |
|
|
443
431
|
| Horizontal amplitude, 0 to 100% | Scale each source's left/right coordinate; also controls stereo `pan` when binaural is off. |
|
|
444
|
-
| Height amplitude, 0 to 100%
|
|
445
|
-
| Master volume
|
|
432
|
+
| Height amplitude, 0 to 100% | Scale each source's below/above coordinate; inactive in stereo mode. |
|
|
433
|
+
| Master volume | Use `setVolume`; independent of direction and amplitude. |
|
|
446
434
|
|
|
447
|
-
|
|
435
|
+
Store these settings as app preferences; `start` does not accept them, and they have no separate native methods. Keep the app's chosen defaults and saved preferences; the module does not impose a 100% slider default. Persist them in the app if they should survive restart. A switch change applies to newly submitted events, without restarting the engine or repositioning voices already playing.
|
|
448
436
|
|
|
449
437
|
With both amplitude controls at 100%, screen positions map as follows:
|
|
450
438
|
|
|
451
|
-
| Position
|
|
452
|
-
|
|
|
453
|
-
| Top left
|
|
454
|
-
| Top center
|
|
455
|
-
| Top right
|
|
456
|
-
| Middle left
|
|
457
|
-
| Center
|
|
458
|
-
| Middle right
|
|
459
|
-
| Bottom left
|
|
460
|
-
| Bottom center |
|
|
461
|
-
| Bottom right
|
|
439
|
+
| Position | `spatialX` | `spatialY` |
|
|
440
|
+
| ------------- | ---------: | ---------: |
|
|
441
|
+
| Top left | -1 | 1 |
|
|
442
|
+
| Top center | 0 | 1 |
|
|
443
|
+
| Top right | 1 | 1 |
|
|
444
|
+
| Middle left | -1 | 0 |
|
|
445
|
+
| Center | 0 | 0 |
|
|
446
|
+
| Middle right | 1 | 0 |
|
|
447
|
+
| Bottom left | -1 | -1 |
|
|
448
|
+
| Bottom center | 0 | -1 |
|
|
449
|
+
| Bottom right | 1 | -1 |
|
|
462
450
|
|
|
463
451
|
### Convert screen positions when playing a tone
|
|
464
452
|
|
|
@@ -521,7 +509,7 @@ if (!playPositionedTone(explosion, 900, 100, 1000, 800)) {
|
|
|
521
509
|
|
|
522
510
|
Connect a Titanium switch's `change` event to `audioSettings.binaural = event.value`; connect the two sliders' `change` events to `horizontalPercent` and `heightPercent`. Configure slider minimum 0 and maximum 100, and disable the height slider while the switch is off. Validate restored preference values as finite numbers within those ranges before using them. Do not pass these preference names to `playTone`: that would return `false` with `unsupported_option`.
|
|
523
511
|
|
|
524
|
-
Supply the current position whenever an effect fires. A projectile launch cue can use the launch position; an impact uses the collision position.
|
|
512
|
+
Supply the current position whenever an effect fires. A projectile launch cue can use the launch position; an impact uses the collision position. Timed event positions remain fixed after submission. An identified sustained voice started spatially can instead change `spatialX`/`spatialY` through `updateVoice`, with 10 ms ramps and retained spatial history. A stereo voice cannot become spatial through an update. Stereo `panEnd` movement cannot be combined with binaural; do not continually retrigger long sounds every frame to imitate movement.
|
|
525
513
|
|
|
526
514
|
### Chords and patterns
|
|
527
515
|
|
|
@@ -537,21 +525,18 @@ if (!synth.playChord({
|
|
|
537
525
|
})) Ti.API.warn('Spatial chord rejected')
|
|
538
526
|
```
|
|
539
527
|
|
|
540
|
-
|
|
528
|
+
Sequence steps inherit coordinates from root `voice` and can override either one. Remove conflicting stereo fields from both root `voice` and every step override; even `pan: 0` conflicts with a spatial coordinate. A step cannot opt out of inherited binaural by passing zero coordinates; use a pattern without spatial coordinates in its root voice for a mix of spatial and stereo steps.
|
|
541
529
|
|
|
542
530
|
```javascript
|
|
543
|
-
if (!synth.
|
|
531
|
+
if (!synth.playSequence({
|
|
532
|
+
timeUnit: 'ms',
|
|
544
533
|
stepDuration: 300,
|
|
545
|
-
attack: 5,
|
|
546
|
-
release: 80,
|
|
547
|
-
volume: 0.3,
|
|
548
|
-
spatialX: 0,
|
|
549
|
-
spatialY: 1,
|
|
534
|
+
voice: { attack: 5, release: 80, volume: 0.3, spatialX: 0, spatialY: 1 },
|
|
550
535
|
steps: [
|
|
551
|
-
{
|
|
552
|
-
{
|
|
536
|
+
{ notes: [60] },
|
|
537
|
+
{ notes: [64], overrides: { spatialY: -1 } },
|
|
553
538
|
{ rest: true },
|
|
554
|
-
{
|
|
539
|
+
{ notes: [67], overrides: { spatialX: 1, spatialY: 0 } }
|
|
555
540
|
]
|
|
556
541
|
})) Ti.API.warn('Spatial pattern rejected')
|
|
557
542
|
```
|
|
@@ -560,12 +545,59 @@ if (!synth.playPattern({
|
|
|
560
545
|
|
|
561
546
|
Use stereo headphones and compare isolated broadband noise above/below before testing many collisions. Perceived elevation varies with the listener, headphones and spectrum; low sine tones are weak tests. The output is still stereo, with a front plane and no rear sources, distance attenuation, room reflections or head tracking. A 100% slider uses the modeled directional range; it does not guarantee strong localization for every person.
|
|
562
547
|
|
|
563
|
-
CPU cost depends on concurrent spatial voices and their short tails
|
|
548
|
+
Measure CPU cost on the intended device with realistic event overlap. The cost depends on concurrent spatial voices and their short tails; the total number of effects over a game does not determine it. Host replay supports comparisons but does not measure device CPU, battery, heat or underruns. See [the renderer](audio-engine.md#binaural-front-plane-renderer) and [diagnostic checks](troubleshooting.md#binaural-does-not-change-the-position).
|
|
549
|
+
|
|
550
|
+
## Read engine statistics
|
|
551
|
+
|
|
552
|
+
Basic counters are available without enabling optional diagnostics. Reads do not start audio or reset counters. Detect the methods if supporting older development packages that also report 1.0.0:
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
var statsTimer = null
|
|
556
|
+
var statsSupported = typeof synth.getStats === 'function' &&
|
|
557
|
+
typeof synth.setStatsEnabled === 'function' && typeof synth.resetStats === 'function'
|
|
558
|
+
|
|
559
|
+
function readStats () {
|
|
560
|
+
var stats = synth.getStats()
|
|
561
|
+
Ti.API.info('Voices: ' + stats.activeVoices + '/' + stats.maxVoices)
|
|
562
|
+
Ti.API.info('Render load: ' + (stats.renderLoad === null
|
|
563
|
+
? 'unmeasured' : (stats.renderLoad * 100).toFixed(1) + '%'))
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function startDiagnostics () {
|
|
567
|
+
if (!statsSupported || statsTimer !== null) return false
|
|
568
|
+
if (!synth.setStatsEnabled(true)) return false
|
|
569
|
+
statsTimer = setInterval(readStats, 200)
|
|
570
|
+
return true
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function stopDiagnostics () {
|
|
574
|
+
if (statsTimer !== null) clearInterval(statsTimer)
|
|
575
|
+
statsTimer = null
|
|
576
|
+
if (statsSupported) synth.setStatsEnabled(false)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Read basic counters without enabling timing or signal meters:
|
|
580
|
+
if (statsSupported) readStats()
|
|
581
|
+
// When the user requests diagnostics: startDiagnostics()
|
|
582
|
+
// When the diagnostic view hides, the app backgrounds or the owner closes:
|
|
583
|
+
// stopDiagnostics()
|
|
584
|
+
// To reset accumulated counters deliberately, without stopping playback:
|
|
585
|
+
// if (statsSupported) synth.resetStats()
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Integrate `stopDiagnostics()` into the existing owner's hide/background/close handlers, before shutdown. Resume polling only while the diagnostic view is active; do not add a second engine owner. Timing and output meters stay `null` until enabled and measured. Basic counters remain available when diagnostics are off. Android backend underrun/buffer/latency fields are best-effort and independent of that switch; iOS returns `null` for those backend fields. A zero underrun count does not prove clean sound.
|
|
589
|
+
|
|
590
|
+
See the [complete field contract](api.md#getstats), [measurement limits](stats-performance.md) and [interactive statistics controls](../assets/example/README.md#statistics).
|
|
564
591
|
|
|
565
592
|
## Next steps
|
|
566
593
|
|
|
567
594
|
- [API reference](api.md) for every accepted key and limit.
|
|
568
595
|
- [Sound design](sound-design.md) for waveform, envelope, filter and FM recipes.
|
|
569
596
|
- [Examples](examples.md) for copy-ready sounds and patterns.
|
|
570
|
-
- [Troubleshooting](troubleshooting.md) when a call returns `false` or audio is
|
|
571
|
-
|
|
597
|
+
- [Troubleshooting](troubleshooting.md) when a call returns `false` or audio is silent.
|
|
598
|
+
|
|
599
|
+
## Diagnose mix headroom
|
|
600
|
+
|
|
601
|
+
The current diagnostics add `preLimiterPeakDb` (maximum signal before final limiting, in dBFS) and `maxLimiterReductionDb` (greatest limiter attenuation in dB) accumulated since reset. Unlike the approximately 200 ms window reduction field, brief peaks remain visible between UI polls. Enable measurements, reset before the scenario and keep `null`/absent fields distinct from measured zero. The [measurement example](advanced-examples.md#measure-peaks-and-limiter-reduction) shows opt-in polling and cleanup.
|
|
602
|
+
|
|
603
|
+
Fixed event/input gain does not normalize continuously by active voice count. Coherent overlapping notes, effects feedback or resonance can still exceed the ceiling; the limiter then acts on the whole stereo sum. Saturation is a separate creative processor, disabled initially. Physical loudness and small-speaker bass response are not measured by these digital meters.
|
|
Binary file
|