@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.
- package/AGENTS-VERCEL-RESEARCH.md +10 -6
- package/README.md +58 -31
- package/agents/ti-pro.md +16 -3
- package/commands/ti-audit.md +21 -6
- package/commands/ti-check.md +2 -1
- package/lib/config.js +1 -0
- package/lib/downloader.js +1 -0
- package/package.json +6 -3
- package/skills/ti-synthengine/SKILL.md +92 -0
- package/skills/ti-synthengine/references/api.md +236 -0
- package/skills/ti-synthengine/references/audio-engine.md +217 -0
- package/skills/ti-synthengine/references/examples.md +348 -0
- package/skills/ti-synthengine/references/guide.md +252 -0
- package/skills/ti-synthengine/references/recipes.md +610 -0
- package/skills/ti-synthengine/references/recommendations.md +150 -0
- package/skills/ti-synthengine/references/sound-design.md +428 -0
- package/skills/ti-synthengine/references/troubleshooting.md +162 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
> Source snapshot: official TiSynthEngine 1.0.0 documentation at commit `6685b81` (2026-08-29).
|
|
2
|
+
|
|
3
|
+
# API reference
|
|
4
|
+
|
|
5
|
+
Module id: `ti.synthengine`
|
|
6
|
+
|
|
7
|
+
Public API version: `1.0.0`
|
|
8
|
+
|
|
9
|
+
Android and iOS expose the same eight methods, defaults, limits, waveform
|
|
10
|
+
values and validation behavior. Option objects are strict: an unknown key is
|
|
11
|
+
rejected with `unsupported_option`.
|
|
12
|
+
|
|
13
|
+
## Return values
|
|
14
|
+
|
|
15
|
+
`startEngine`, `playTone`, `playChord`, `playPattern` and `setVolume` return a
|
|
16
|
+
Boolean. `true` means the request was accepted. `false` means validation
|
|
17
|
+
failed, the engine was unavailable, or the native command queue was full.
|
|
18
|
+
|
|
19
|
+
`stopAll` and `shutdown` do not return a value. `getDefaults` returns the
|
|
20
|
+
contract object described below.
|
|
21
|
+
|
|
22
|
+
## `getDefaults()`
|
|
23
|
+
|
|
24
|
+
Returns the installed contract without starting the audio stream.
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
var contract = synth.getDefaults()
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The result has this shape:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
{
|
|
34
|
+
apiVersion: '1.0.0',
|
|
35
|
+
defaults: {
|
|
36
|
+
maxVoices: 8,
|
|
37
|
+
mixingProfile: 'conservative',
|
|
38
|
+
frequency: 440,
|
|
39
|
+
duration: 500,
|
|
40
|
+
attack: 50,
|
|
41
|
+
release: 50,
|
|
42
|
+
volume: 1,
|
|
43
|
+
waveType: 0,
|
|
44
|
+
pan: 0,
|
|
45
|
+
panEnd: 0,
|
|
46
|
+
frequencyEnd: 0,
|
|
47
|
+
lfoFreq: 0,
|
|
48
|
+
lfoDepth: 0,
|
|
49
|
+
panSpread: 0,
|
|
50
|
+
bpm: 120,
|
|
51
|
+
swing: 0,
|
|
52
|
+
stepDuration: 100
|
|
53
|
+
},
|
|
54
|
+
limits: { /* ranges listed below */ },
|
|
55
|
+
mixingProfiles: ['conservative', 'raw', 'balanced', 'speakerSafe'],
|
|
56
|
+
waveTypes: {
|
|
57
|
+
SINE: 0,
|
|
58
|
+
SQUARE: 1,
|
|
59
|
+
SAWTOOTH: 2,
|
|
60
|
+
TRIANGLE: 3,
|
|
61
|
+
NOISE: 4
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The maximum frequency is `sampleRate * 0.45`. Before the stream opens, the
|
|
67
|
+
reported fallback is 19,845 Hz. Read the object again after `startEngine()` if
|
|
68
|
+
the precise device limit matters.
|
|
69
|
+
|
|
70
|
+
## Limits
|
|
71
|
+
|
|
72
|
+
All ranges include their endpoints.
|
|
73
|
+
|
|
74
|
+
| Field | Minimum | Maximum | Unit or rule |
|
|
75
|
+
| --- | ---: | ---: | --- |
|
|
76
|
+
| `maxVoices` | 1 | 32 | integer voices |
|
|
77
|
+
| `frequency` | 1 | `sampleRate * 0.45` | Hz |
|
|
78
|
+
| `duration` | 1 | 60,000 | integer ms |
|
|
79
|
+
| `attack` | 0 | 2,000 | integer ms; must be less than duration |
|
|
80
|
+
| `release` | 0 | 4,000 | integer ms; must fit after attack |
|
|
81
|
+
| `volume` | 0 | 2 | event or master multiplier |
|
|
82
|
+
| `waveType` | 0 | 4 | integer waveform value |
|
|
83
|
+
| `patternSteps` | 1 | 512 | object steps |
|
|
84
|
+
| `pan`, `panEnd`, `pans[]` | -1 | 1 | left to right |
|
|
85
|
+
| `panSpread` | 0 | 1 | chord stereo width |
|
|
86
|
+
| `frequencyEnd` | 0 | `sampleRate * 0.45` | Hz; 0 disables the sweep |
|
|
87
|
+
| `lfoFreq` | 0 | 20,000 | Hz; 0 disables vibrato |
|
|
88
|
+
| `lfoDepth` | 0 | 20,000 | Hz |
|
|
89
|
+
| `bpm` | 20 | 300 | beats per minute |
|
|
90
|
+
| `swing` | -0.49 | 0.49 | alternating timing offset |
|
|
91
|
+
| `stepDuration` | 1 | 60,000 | ms |
|
|
92
|
+
|
|
93
|
+
The allowed `noteValue` strings are `1n`, `2n`, `4n`, `8n`, `16n` and `32n`.
|
|
94
|
+
|
|
95
|
+
## `startEngine(options)`
|
|
96
|
+
|
|
97
|
+
Starts the native stream. Calling it again while the engine is running returns
|
|
98
|
+
`true` without rebuilding the stream.
|
|
99
|
+
|
|
100
|
+
| Key | Type | Default | Description |
|
|
101
|
+
| --- | --- | --- | --- |
|
|
102
|
+
| `maxVoices` | integer | `8` | Size of the voice pool, from 1 to 32. |
|
|
103
|
+
| `mixingProfile` | string | `conservative` | `conservative`, `balanced`, `speakerSafe` or `raw`. Matching is case-insensitive. |
|
|
104
|
+
|
|
105
|
+
The profile and voice capacity stay fixed for the session. Call `shutdown()`
|
|
106
|
+
before starting again with different values.
|
|
107
|
+
|
|
108
|
+
## `playTone(options)`
|
|
109
|
+
|
|
110
|
+
Plays one pitch. It uses 440 Hz when neither pitch field is present.
|
|
111
|
+
|
|
112
|
+
| Key | Type | Default | Description |
|
|
113
|
+
| --- | --- | ---: | --- |
|
|
114
|
+
| `frequency` | number | `440` | Starting frequency in Hz. Cannot be combined with `note`. |
|
|
115
|
+
| `note` | string | none | Note name. Cannot be combined with `frequency`. |
|
|
116
|
+
| `duration` | integer | `500` | Total event length in ms. |
|
|
117
|
+
| `attack` | integer | `50` | Fade-in time in ms. |
|
|
118
|
+
| `release` | integer | `50` | Fade-out time in ms. |
|
|
119
|
+
| `volume` | number | `1` | Event level multiplier. |
|
|
120
|
+
| `waveType` | integer | `SINE` | One of the five waveform values. |
|
|
121
|
+
| `pan` | number | `0` | Starting stereo position. |
|
|
122
|
+
| `panEnd` | number | `0` | Ending stereo position. |
|
|
123
|
+
| `frequencyEnd` | number | `0` | Ending frequency; 0 keeps the starting pitch. |
|
|
124
|
+
| `lfoFreq` | number | `0` | Vibrato rate in Hz. |
|
|
125
|
+
| `lfoDepth` | number | `0` | Vibrato depth in Hz. |
|
|
126
|
+
|
|
127
|
+
## `playChord(options)`
|
|
128
|
+
|
|
129
|
+
Starts 1 to 32 pitches as one atomic event. Exactly one pitch array is
|
|
130
|
+
required.
|
|
131
|
+
|
|
132
|
+
| Key | Type | Default | Description |
|
|
133
|
+
| --- | --- | ---: | --- |
|
|
134
|
+
| `frequencies` | number[] | required alternative | Frequencies in Hz. |
|
|
135
|
+
| `notes` | string[] | required alternative | Note names. |
|
|
136
|
+
| `duration` | integer | `500` | Total chord length in ms. |
|
|
137
|
+
| `attack` | integer | `50` | Fade-in time in ms. |
|
|
138
|
+
| `release` | integer | `50` | Fade-out time in ms. |
|
|
139
|
+
| `volume` | number | `1` | Chord event level. |
|
|
140
|
+
| `waveType` | integer | `SINE` | Waveform for every voice. |
|
|
141
|
+
| `panSpread` | number | `0` | Even stereo spread from 0 to 1. |
|
|
142
|
+
| `pans` | number[] | none | Per-note pan positions; overrides spread for supplied entries. |
|
|
143
|
+
|
|
144
|
+
`pans` cannot contain more entries than the pitch array. `playChord` does not
|
|
145
|
+
accept sweep or vibrato fields.
|
|
146
|
+
|
|
147
|
+
## `playPattern(options)`
|
|
148
|
+
|
|
149
|
+
Schedules a monophonic sequence. The required `steps` array must contain only
|
|
150
|
+
objects. A string or number step is rejected with `invalid_step_type`.
|
|
151
|
+
|
|
152
|
+
Pattern-level keys:
|
|
153
|
+
|
|
154
|
+
| Key | Type | Default | Description |
|
|
155
|
+
| --- | --- | ---: | --- |
|
|
156
|
+
| `steps` | object[] | required | Between 1 and 512 steps. |
|
|
157
|
+
| `bpm` | number | `120` | Tempo used by note values. |
|
|
158
|
+
| `swing` | number | `0` | Alternating timing offset. |
|
|
159
|
+
| `stepDuration` | number | `100` | Fallback step length in ms. |
|
|
160
|
+
| `noteValue` | string | none | Shared musical step length. |
|
|
161
|
+
| `attack` | integer | `50` | Inherited attack. |
|
|
162
|
+
| `release` | integer | `50` | Inherited release. |
|
|
163
|
+
| `volume` | number | `1` | Inherited event level. |
|
|
164
|
+
| `waveType` | integer | `SINE` | Inherited waveform. |
|
|
165
|
+
| `pan` | number | `0` | Inherited starting pan. |
|
|
166
|
+
| `panEnd` | number | `0` | Inherited ending pan. |
|
|
167
|
+
| `frequencyEnd` | number | `0` | Inherited sweep target. |
|
|
168
|
+
| `lfoFreq` | number | `0` | Inherited vibrato rate. |
|
|
169
|
+
| `lfoDepth` | number | `0` | Inherited vibrato depth. |
|
|
170
|
+
|
|
171
|
+
Step keys:
|
|
172
|
+
|
|
173
|
+
| Key | Type | Description |
|
|
174
|
+
| --- | --- | --- |
|
|
175
|
+
| `note` | string | Pitch name; exclusive with `frequency` and `rest`. |
|
|
176
|
+
| `frequency` | number | Pitch in Hz; exclusive with `note` and `rest`. |
|
|
177
|
+
| `rest` | Boolean | Must be exactly `true` for a rest. |
|
|
178
|
+
| `duration` | integer | Step length in ms. |
|
|
179
|
+
| `noteValue` | string | Step length derived from `bpm`. |
|
|
180
|
+
| `attack`, `release`, `volume`, `waveType` | number | Override the pattern value for this step. |
|
|
181
|
+
| `pan`, `panEnd`, `frequencyEnd`, `lfoFreq`, `lfoDepth` | number | Override motion or vibrato for this step. |
|
|
182
|
+
|
|
183
|
+
Step timing precedence is `duration`, step `noteValue`, pattern `noteValue`,
|
|
184
|
+
pattern `stepDuration`, then 100 ms. Only one pattern remains pending at a
|
|
185
|
+
time. Starting another one cancels steps that have not fired.
|
|
186
|
+
|
|
187
|
+
## `setVolume(options)`
|
|
188
|
+
|
|
189
|
+
Changes the master level with a short smoothing ramp.
|
|
190
|
+
|
|
191
|
+
| Key | Type | Default | Range |
|
|
192
|
+
| --- | --- | ---: | ---: |
|
|
193
|
+
| `volume` | number | `1` | 0 to 2 |
|
|
194
|
+
|
|
195
|
+
## `stopAll()`
|
|
196
|
+
|
|
197
|
+
Fades active voices over roughly 20 ms to avoid clicks. It does not cancel
|
|
198
|
+
future pattern steps.
|
|
199
|
+
|
|
200
|
+
## `shutdown()`
|
|
201
|
+
|
|
202
|
+
Cancels the pending pattern, stops the native stream and releases its audio
|
|
203
|
+
resources. Call it when the owner closes.
|
|
204
|
+
|
|
205
|
+
## Strict validation
|
|
206
|
+
|
|
207
|
+
Unknown keys and invalid values return `false` and produce one deduplicated
|
|
208
|
+
native log entry:
|
|
209
|
+
|
|
210
|
+
```text
|
|
211
|
+
event=validation_warning method=playTone key=duraton reason=unsupported_option action=rejected value=200
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Common reasons include:
|
|
215
|
+
|
|
216
|
+
| Reason | Meaning |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| `unsupported_option` | The option name is not part of that method or step. |
|
|
219
|
+
| `invalid_step_type` | A pattern step is not an object. |
|
|
220
|
+
| `non_numeric_or_non_finite` | A numeric field is the wrong type, `NaN` or infinite. |
|
|
221
|
+
| `not_integer` | An integer field contains a fractional number. |
|
|
222
|
+
| `out_of_range` | A number falls outside the documented range. |
|
|
223
|
+
| `not_string`, `not_array`, `not_boolean` | A field has the wrong container or primitive type. |
|
|
224
|
+
| `invalid_note` | A note name cannot be parsed. |
|
|
225
|
+
| `unsupported_note_value` | A note value is not one of the six supported values. |
|
|
226
|
+
| `missing_required` | A required chord pitch array is absent. |
|
|
227
|
+
| `missing_pitch_or_rest` | A pattern step has no pitch and is not a rest. |
|
|
228
|
+
| `conflicting_pitch_fields` | A call or step provides more than one pitch source. |
|
|
229
|
+
| `invalid_voice_count` | A chord has fewer than 1 or more than 32 pitches. |
|
|
230
|
+
| `invalid_step_count` | A pattern has fewer than 1 or more than 512 steps. |
|
|
231
|
+
| `too_many_values` | `pans` is longer than the chord. |
|
|
232
|
+
| `attack_greater_or_equal_duration` | An explicit attack leaves no time for the event. |
|
|
233
|
+
| `release_exceeds_remaining_duration` | An explicit release does not fit after attack. |
|
|
234
|
+
|
|
235
|
+
Explicit values are not clamped. Read `getDefaults().limits` when accepting
|
|
236
|
+
user input and decide in the app whether to reject or constrain it.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
> Source snapshot: official TiSynthEngine 1.0.0 documentation at commit `6685b81` (2026-08-29).
|
|
2
|
+
|
|
3
|
+
# Audio engine
|
|
4
|
+
|
|
5
|
+
This page explains the native signal path behind the JavaScript API. You do not
|
|
6
|
+
need it to play a sound, but it helps when choosing a mixing profile, sizing the
|
|
7
|
+
voice pool or investigating device behavior.
|
|
8
|
+
|
|
9
|
+
## Signal path
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
playTone / playChord / playPattern
|
|
13
|
+
|
|
|
14
|
+
strict validation
|
|
15
|
+
|
|
|
16
|
+
native command ring buffer
|
|
17
|
+
|
|
|
18
|
+
voice pool: oscillator * envelope * event gain * stereo pan
|
|
19
|
+
|
|
|
20
|
+
sum voices and steal tails
|
|
21
|
+
|
|
|
22
|
+
smoothed master volume
|
|
23
|
+
|
|
|
24
|
+
stereo-linked lookahead limiter
|
|
25
|
+
|
|
|
26
|
+
floating-point output
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The audio callback does not allocate memory or wait on a lock. JavaScript and
|
|
30
|
+
the pattern scheduler publish small commands; the real-time thread consumes
|
|
31
|
+
them and renders the samples.
|
|
32
|
+
|
|
33
|
+
## Oscillators
|
|
34
|
+
|
|
35
|
+
The engine has five sources:
|
|
36
|
+
|
|
37
|
+
| Waveform | Source behavior |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `SINE` | Direct sinusoid. |
|
|
40
|
+
| `SQUARE` | PolyBLEP band-limited discontinuity. |
|
|
41
|
+
| `SAWTOOTH` | PolyBLEP band-limited discontinuity. |
|
|
42
|
+
| `TRIANGLE` | Continuous triangle oscillator. |
|
|
43
|
+
| `NOISE` | White-noise generator. |
|
|
44
|
+
|
|
45
|
+
Pitch can move linearly from `frequency` to `frequencyEnd`. A sine LFO adds
|
|
46
|
+
`lfoDepth * sin(lfoPhase)` in hertz before the oscillator advances. This makes
|
|
47
|
+
vibrato depth absolute in hertz rather than cents.
|
|
48
|
+
|
|
49
|
+
## Envelope
|
|
50
|
+
|
|
51
|
+
Each voice has a linear attack, a full-level middle section and a linear
|
|
52
|
+
release. `duration` includes the whole envelope. Defaults are adjusted to fit a
|
|
53
|
+
short event, while an explicit envelope that cannot fit is rejected by the
|
|
54
|
+
contract.
|
|
55
|
+
|
|
56
|
+
Voice stealing does not cut the old waveform at an arbitrary sample. The old
|
|
57
|
+
voice contributes a short tail of roughly 6 ms while the slot begins the new
|
|
58
|
+
event. `stopAll()` uses a release of roughly 20 ms for the same reason.
|
|
59
|
+
|
|
60
|
+
## Event gain
|
|
61
|
+
|
|
62
|
+
In protected profiles, gain is fixed when an event starts:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
voiceGain = waveformGain(waveType) * profileFactor / sqrt(eventVoiceCount)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
One tone and one pattern step have an event voice count of 1. A chord uses the
|
|
69
|
+
number of pitches in that chord. Dividing by the square root of the count keeps
|
|
70
|
+
the event close to constant power without changing gain sample by sample.
|
|
71
|
+
|
|
72
|
+
Waveforms are calibrated toward a common source RMS:
|
|
73
|
+
|
|
74
|
+
| Waveform | Base gain | Approximate output RMS |
|
|
75
|
+
| --- | ---: | ---: |
|
|
76
|
+
| Sine | 0.440 | 0.311 |
|
|
77
|
+
| Square | 0.311 | 0.311 |
|
|
78
|
+
| Sawtooth, triangle and noise | 0.539 | 0.311 |
|
|
79
|
+
|
|
80
|
+
This calculation is why one three-note `playChord` and three `playTone` calls
|
|
81
|
+
are not equivalent. A protected sine triad assigns about 0.254 to each chord
|
|
82
|
+
voice (`0.440 / sqrt(3)`). Three separate tone events each receive 0.440, for a
|
|
83
|
+
much hotter worst-case sum.
|
|
84
|
+
|
|
85
|
+
## Mixing profiles
|
|
86
|
+
|
|
87
|
+
The profile is fixed when `startEngine()` opens the stream.
|
|
88
|
+
|
|
89
|
+
| Profile | Factor | Sine gain | Limiter ceiling | Intended use |
|
|
90
|
+
| --- | ---: | ---: | ---: | --- |
|
|
91
|
+
| `conservative` | 1 | 0.440 | 0.85 | Default headroom for polyphony and effects. |
|
|
92
|
+
| `balanced` | `sqrt(2)` | 0.622 | 0.85 | Mostly one or two simultaneous voices. |
|
|
93
|
+
| `speakerSafe` | `1 / sqrt(2)` | 0.311 | 0.70 | Small speakers and demanding low pitches. |
|
|
94
|
+
| `raw` | no source reduction | 1.000 | 0.85 | App-controlled gain staging. |
|
|
95
|
+
|
|
96
|
+
`raw` lets one full-volume oscillator reach full scale before the master. Any
|
|
97
|
+
overlap then relies on the limiter, so it is not a general-purpose loudness
|
|
98
|
+
setting.
|
|
99
|
+
|
|
100
|
+
## Limiter
|
|
101
|
+
|
|
102
|
+
A single stereo-linked peak limiter sits after master volume. Linking preserves
|
|
103
|
+
the stereo image when only one channel has the largest peak.
|
|
104
|
+
|
|
105
|
+
| Parameter | Value |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| Lookahead | 10 ms |
|
|
108
|
+
| Release | 100 ms, exponential |
|
|
109
|
+
| Ceiling | 0.85, or 0.70 with `speakerSafe` |
|
|
110
|
+
|
|
111
|
+
Lookahead gives the gain time to reach a safe value before the peak reaches the
|
|
112
|
+
output. Below the ceiling the limiter converges back to unity. It is meant to
|
|
113
|
+
catch exceptional sums, not to normalize every callback.
|
|
114
|
+
|
|
115
|
+
Repeated overlapping events can make any limiter audibly raise and lower the
|
|
116
|
+
bus. A loop that starts a 300 ms chord every 100 ms is therefore a useful load
|
|
117
|
+
test and a poor tonal reference. Judge the chord with one sustained event; use
|
|
118
|
+
the overlapping loop only when testing scheduling, stealing and recovery.
|
|
119
|
+
|
|
120
|
+
## Master volume and pan
|
|
121
|
+
|
|
122
|
+
`setVolume()` ramps the master rather than applying a discontinuous gain step.
|
|
123
|
+
Per-event `volume` is multiplied before the bus. Values up to 2 are valid, but
|
|
124
|
+
values above 1 use headroom and make limiting more likely.
|
|
125
|
+
|
|
126
|
+
Pan uses equal-power coefficients. A centered sound feeds both channels; the
|
|
127
|
+
sum does not simply double. `panEnd` interpolates the position across a tone or
|
|
128
|
+
pattern step. Chords use `panSpread` or the explicit `pans` array.
|
|
129
|
+
|
|
130
|
+
## Voice pool
|
|
131
|
+
|
|
132
|
+
`maxVoices` reserves between 1 and 32 active voice slots. The setting controls
|
|
133
|
+
capacity, not event gain. When every slot is occupied, the voice with the least
|
|
134
|
+
time remaining is faded and its slot is reused.
|
|
135
|
+
|
|
136
|
+
A chord is one command with up to 32 pitches. If its size exceeds the session's
|
|
137
|
+
voice capacity, slots with the least time remaining are reused. For normal apps,
|
|
138
|
+
choose the largest real overlap plus room for release tails.
|
|
139
|
+
|
|
140
|
+
## Command queue
|
|
141
|
+
|
|
142
|
+
Both ports use a 128-slot single-consumer ring buffer. Producers are serialized
|
|
143
|
+
with a lock that the audio callback never touches. When note commands cannot be
|
|
144
|
+
queued, the public call returns `false` and logs an `overflow` warning.
|
|
145
|
+
|
|
146
|
+
`playChord()` publishes its note data and advances the queue index once. The
|
|
147
|
+
consumer receives the complete event or nothing. `stopAll` and `shutdown` have
|
|
148
|
+
a control fallback so cleanup is not lost under queue pressure.
|
|
149
|
+
|
|
150
|
+
Pattern timing runs outside the audio callback. A platform timer places each
|
|
151
|
+
step on the command queue. The sequence is suitable for game cues and simple
|
|
152
|
+
musical patterns, but it is not a sample-accurate composition timeline.
|
|
153
|
+
|
|
154
|
+
## Android implementation
|
|
155
|
+
|
|
156
|
+
Android renders in C++ through Oboe. The stream requests floating-point output,
|
|
157
|
+
low-latency performance and exclusive sharing when the device supports it. If
|
|
158
|
+
exclusive access is unavailable, Oboe can use shared mode.
|
|
159
|
+
|
|
160
|
+
The renderer mixes in the data callback and targets a small device buffer. A
|
|
161
|
+
route or stream error triggers a reopen on the current output. The supported
|
|
162
|
+
ABIs are `arm64-v8a`, `armeabi-v7a`, `x86` and `x86_64`.
|
|
163
|
+
|
|
164
|
+
## iOS implementation
|
|
165
|
+
|
|
166
|
+
iOS renders with `AVAudioEngine` and `AVAudioSourceNode`, using the active
|
|
167
|
+
session's sample rate. The module configures the audio session as `Playback`,
|
|
168
|
+
activates it, and observes route and engine configuration notifications. If a
|
|
169
|
+
route change stops the graph, the port reactivates the session and resumes the
|
|
170
|
+
existing renderer.
|
|
171
|
+
|
|
172
|
+
`AVAudioSourceNode` requires iOS 13 or newer. The module archive contains device,
|
|
173
|
+
simulator and Mac Catalyst slices.
|
|
174
|
+
|
|
175
|
+
## Latency
|
|
176
|
+
|
|
177
|
+
End-to-end latency contains three main pieces:
|
|
178
|
+
|
|
179
|
+
1. The fixed 10 ms limiter lookahead.
|
|
180
|
+
2. The hardware and operating-system output buffer.
|
|
181
|
+
3. JavaScript or platform timer scheduling before the command reaches audio.
|
|
182
|
+
|
|
183
|
+
Immediate calls are suitable for interface and game feedback. A visual or
|
|
184
|
+
musical event that must align to a known future time should be scheduled ahead
|
|
185
|
+
by the app instead of relying on a JavaScript callback at that instant.
|
|
186
|
+
|
|
187
|
+
## Route behavior
|
|
188
|
+
|
|
189
|
+
Connecting headphones or changing another output route can interrupt the
|
|
190
|
+
hardware stream. The ports recover automatically, though the event crossing
|
|
191
|
+
the transition may be cut short and the operating system may keep the new
|
|
192
|
+
route muted briefly. Start later sounds normally after the route settles.
|
|
193
|
+
|
|
194
|
+
## Regression coverage
|
|
195
|
+
|
|
196
|
+
The repository checks the public contract and both source ports without a
|
|
197
|
+
device:
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
node tests/api_contract_regression.js
|
|
201
|
+
node tests/default_profile_regression.js
|
|
202
|
+
|
|
203
|
+
cd android
|
|
204
|
+
test_dir=$(mktemp -d /tmp/ti-synth-test.XXXXXX)
|
|
205
|
+
javac -d "$test_dir" \
|
|
206
|
+
src/ti/synthengine/SynthContract.java \
|
|
207
|
+
test/ti/synthengine/SynthContractRegression.java \
|
|
208
|
+
src/ti/synthengine/SynthDsp.java \
|
|
209
|
+
test/ti/synthengine/SynthDspRegression.java
|
|
210
|
+
java -cp "$test_dir" ti.synthengine.SynthContractRegression
|
|
211
|
+
java -cp "$test_dir" ti.synthengine.SynthDspRegression
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The DSP suite covers stable RMS from 1 through 32 voices, limiter transparency
|
|
215
|
+
and recovery, waveform discontinuities, envelope endpoints, master smoothing,
|
|
216
|
+
voice stealing and representative dense chords. Device tests remain necessary
|
|
217
|
+
for route changes, startup underruns and speaker acoustics.
|