@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,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.