@maccesar/titools 5.2.4 → 5.3.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/README.md +27 -32
- package/agents/ti-pro.md +1 -1
- package/commands/ti-audit.md +1 -1
- package/docs/examples.md +15 -8
- package/package.json +1 -1
- package/skills/ti-synthengine/SKILL.md +62 -92
- package/skills/ti-synthengine/assets/example/README.md +51 -54
- package/skills/ti-synthengine/assets/example/app.js +96 -57
- package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +39 -40
- package/skills/ti-synthengine/assets/example/lib/soundPreset.js +34 -0
- package/skills/ti-synthengine/assets/index.d.ts +226 -0
- package/skills/ti-synthengine/references/advanced-examples.md +12 -12
- package/skills/ti-synthengine/references/api.md +125 -33
- package/skills/ti-synthengine/references/audio-engine.md +24 -59
- package/skills/ti-synthengine/references/examples.md +64 -7
- package/skills/ti-synthengine/references/guide.md +69 -63
- package/skills/ti-synthengine/references/instruments-and-effects.md +34 -27
- package/skills/ti-synthengine/references/native-variation.md +92 -51
- package/skills/ti-synthengine/references/recipes-interaction.md +26 -19
- package/skills/ti-synthengine/references/recipes.md +19 -15
- package/skills/ti-synthengine/references/recommendations.md +14 -14
- package/skills/ti-synthengine/references/sound-api.md +197 -0
- package/skills/ti-synthengine/references/sound-design.md +11 -11
- package/skills/ti-synthengine/references/source-notes.md +62 -147
- package/skills/ti-synthengine/references/troubleshooting.md +22 -24
- package/skills/ti-synthengine/references/upgrade-2.0.0.md +40 -0
- package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
- package/skills/ti-synthengine/references/android-sim80-correction.json +0 -162
- package/skills/ti-synthengine/references/audio-pipeline-validation.md +0 -19
- package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
- 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 +0 -1918
- package/skills/ti-synthengine/references/native-variation-proposal.md +0 -182
- package/skills/ti-synthengine/references/native-variation-validation.md +0 -63
- package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
- package/skills/ti-synthengine/references/performance-review.json +0 -1146
- package/skills/ti-synthengine/references/physical-validation.json +0 -1457
- package/skills/ti-synthengine/references/source-snapshot.json +0 -463
- package/skills/ti-synthengine/references/stats-performance.json +0 -2992
- package/skills/ti-synthengine/references/stats-performance.md +0 -82
- package/skills/ti-synthengine/references/studio-validation.json +0 -6202
- package/skills/ti-synthengine/references/unified-bypass-performance.json +0 -22425
- package/skills/ti-synthengine/references/unified-validation.json +0 -314
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
> Source snapshot: official TiSynthEngine documentation at commit `
|
|
1
|
+
> Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/api.md` SHA-256: `ca5b7f1a44ef79543399fd1a40fa83024c190c086b6412fb79dc54de91a2a329`.
|
|
2
2
|
|
|
3
3
|
# API reference
|
|
4
4
|
|
|
5
5
|
Module id: `ti.synthengine`
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Module version: `2.0.0`. Read the [Sound object reference](sound-api.md) for reusable definitions, playback controls and property units. This page documents the global controls and direct native parameters behind those interpretations.
|
|
8
8
|
|
|
9
|
-
Android and iOS expose the same
|
|
9
|
+
Android and iOS expose the same public methods and waveform values. Pattern controls emit `end`; direct native sequences emit `sequenceend`. Option objects are strict: an unknown key is rejected with `unsupported_option`. Reported metadata and rejection diagnostics have platform differences; this reference does not promise sample-identical rendering between Android and iOS.
|
|
10
10
|
|
|
11
11
|
<!-- TOC-START -->
|
|
12
12
|
## Contents
|
|
13
13
|
|
|
14
|
-
- [
|
|
14
|
+
- [Sound interface](#sound-interface)
|
|
15
|
+
- [Native attack variation](#native-attack-variation)
|
|
15
16
|
- [Return values](#return-values)
|
|
16
17
|
- [`getDefaults()`](#getdefaults)
|
|
17
18
|
- [Limits](#limits)
|
|
18
19
|
- [`start(options)`](#startoptions)
|
|
19
20
|
- [`noteOn(options)`](#noteonoptions)
|
|
20
21
|
- [`noteOff(options)`](#noteoffoptions)
|
|
21
|
-
- [`
|
|
22
|
+
- [`updateNote(options)`](#updatenoteoptions)
|
|
23
|
+
- [`updateVoice(options)` (deprecated)](#updatevoiceoptions-deprecated)
|
|
22
24
|
- [`playTone(options)`](#playtoneoptions)
|
|
25
|
+
- [`repeatTone(options)`](#repeattoneoptions)
|
|
26
|
+
- [Finite-tone repetition](#finite-tone-repetition)
|
|
23
27
|
- [Native sequence transport](#native-sequence-transport)
|
|
24
28
|
- [`setMasterProcessing(options)`](#setmasterprocessingoptions)
|
|
25
29
|
- [Additional voice modulation](#additional-voice-modulation)
|
|
@@ -32,21 +36,43 @@ Android and iOS expose the same nineteen methods, one event and waveform values.
|
|
|
32
36
|
- [Strict validation](#strict-validation)
|
|
33
37
|
- [Binaural front-plane positioning](#binaural-front-plane-positioning)
|
|
34
38
|
- [`getStats()`](#getstats)
|
|
35
|
-
- [`
|
|
39
|
+
- [`enableStats()` and `disableStats()`](#enablestats-and-disablestats)
|
|
40
|
+
- [`setStatsEnabled(enabled)` (deprecated)](#setstatsenabledenabled-deprecated)
|
|
36
41
|
- [`resetStats()`](#resetstats)
|
|
37
42
|
- [Routing and voice priority](#routing-and-voice-priority)
|
|
38
43
|
|
|
39
44
|
<!-- TOC-END -->
|
|
40
45
|
|
|
41
|
-
##
|
|
46
|
+
## Sound interface
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
| Method | Result |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `getContract()` | Installed module version, capabilities, units and structural property allowlists |
|
|
51
|
+
| `createSound({ name?, sound?, playback? })` | A reusable validated Sound; no audio starts |
|
|
52
|
+
| `Sound.play(changes?)` | Finite acceptance or an independent note/repetition/pattern control |
|
|
53
|
+
| `Sound.noteOn(changes?)` | One held-note control, or `false` on operational rejection |
|
|
54
|
+
| `Sound.set({ sound?, playback? })` | Atomic stored revision for future executions |
|
|
55
|
+
| `Sound.getPreset()` | Detached authored definition |
|
|
56
|
+
| Note `update(changes)`, `noteOff()` | Edit or release that held note |
|
|
57
|
+
| Repetition `update(changes)`, `stop()` | Edit future finite attacks or cancel repetition |
|
|
58
|
+
| Pattern `update(changes)`, `stop()`, `getState()` | Edit, stop or inspect that pattern |
|
|
59
|
+
| Pattern `addEventListener('end', fn)`, `removeEventListener('end', fn)` | Subscribe to that pattern's completion or stopping |
|
|
60
|
+
| `setProcessing(options)` | Shared effects with delay `time` and reverb `decay` in milliseconds |
|
|
61
|
+
| `setVolume(number)`, `setTempo(number)` | Global gain and beats per minute |
|
|
62
|
+
|
|
63
|
+
Sound definitions split synthesis into `sound` and execution defaults into `playback`. `play()` and `noteOn()` accept flat temporary overrides. Invalid configurations throw before publication; operational playback rejection returns `false`. Finite events end naturally without individual controls. Shutdown invalidates controls while definitions remain reusable.
|
|
64
|
+
|
|
65
|
+
The [Sound reference](sound-api.md) supplies all properties, defaults, units and interpretation restrictions. The direct native methods below remain supported and document the validators and rendering parameters behind each interpretation. Use the [upgrade guide](upgrade-2.0.0.md) for method mappings.
|
|
66
|
+
|
|
67
|
+
## Native attack variation
|
|
68
|
+
|
|
69
|
+
The optional `variation` dictionary is accepted by `playTone`, `repeatTone`, `playChord`, `noteOn`, sequence `voice` and step `overrides`. Both platforms advertise `capabilities.variation: true` and `variationSchemaVersion: 1`; consumers must require both before sending the dictionary. See [native variation](native-variation.md) for fields, source restrictions, seeds and examples. Native `amount` is linear; Studio divides its displayed percentage by 100. `updateNote` rejects the dictionary. An override replaces the whole inherited dictionary, and an amount-zero dictionary still validates its ranges.
|
|
44
70
|
|
|
45
71
|
## Return values
|
|
46
72
|
|
|
47
|
-
`start`, `playTone`, `playChord`, `noteOn`, `noteOff`, `updateVoice`, `setVolume`, `setTempo`, `updateSequence`, `stopSequence` and `setMasterProcessing` return a Boolean. `playSequence` returns a positive sequence identifier, or 0 when rejected. `true` means the request was accepted. `false` means validation failed, the engine was unavailable, or the native command queue was full.
|
|
73
|
+
`start`, `playTone`, `playChord`, `noteOn`, `noteOff`, `updateNote`, `updateVoice`, `updateTone`, `stopTone`, `setVolume`, `setTempo`, `updateSequence`, `stopSequence` and `setMasterProcessing` return a Boolean. `repeatTone` returns a positive tone identifier, or 0 when rejected. Its required `intervalMs` is an integer from 1 to 60,000 ms. `playSequence` returns a positive sequence identifier, or 0 when rejected. `true` means the request was accepted. `false` means validation failed, the engine was unavailable, or the native command queue was full.
|
|
48
74
|
|
|
49
|
-
`setStatsEnabled` also
|
|
75
|
+
`enableStats`, `disableStats` and the deprecated `setStatsEnabled` also return Boolean acceptance. `stopAll`, `resetStats` and `shutdown` do not return a value. `getDefaults` returns the contract object described below. `getStats` returns a new statistics snapshot. `getSequenceState` returns the applied sequence state or `null`.
|
|
50
76
|
|
|
51
77
|
## `getDefaults()`
|
|
52
78
|
|
|
@@ -56,12 +82,12 @@ Returns the installed contract without starting the audio stream.
|
|
|
56
82
|
var contract = synth.getDefaults()
|
|
57
83
|
```
|
|
58
84
|
|
|
59
|
-
The shared result in
|
|
85
|
+
The shared result in TiSynth 2.0.0 has this shape; Android adds the platform metadata described below. `version` identifies the installed module, with no independent API version:
|
|
60
86
|
|
|
61
87
|
```javascript
|
|
62
88
|
{
|
|
63
|
-
|
|
64
|
-
capabilities: { variation: true },
|
|
89
|
+
version: '2.0.0',
|
|
90
|
+
capabilities: { variation: true, toneRepetition: true },
|
|
65
91
|
variationSchemaVersion: 1,
|
|
66
92
|
defaults: {
|
|
67
93
|
maxVoices: 8,
|
|
@@ -123,7 +149,7 @@ The shared result in the local 1.1.0 candidates has this shape; Android adds the
|
|
|
123
149
|
|
|
124
150
|
`fmDepthEnd`, `detunes`, `spatialX` and `spatialY` have no reported default: their presence changes how the call is interpreted. Supplying a spatial coordinate enables binaural; the other coordinate then defaults to 0 internally. The maximum frequency is `sampleRate * 0.45`. Before the stream opens, the reported fallback is 19,845 Hz. Read the object again after `start()` if the precise device limit matters.
|
|
125
151
|
|
|
126
|
-
|
|
152
|
+
Both platforms report `capabilities.variation: true` and `variationSchemaVersion: 1`. Android also reports `nativeSequence`, `masterProcessing` and `modulations` capability flags and `limits.stepDurationMs`. iOS omits those three flags and `limits.stepDurationMs` while implementing the corresponding methods and millisecond sequences. Detect a required core method with `typeof synth.playSequence === 'function'` (and the other methods your feature needs); require the capability/schema pair specifically for variation. Both platforms report `limits.maxSequences`, `limits.priority` and `limits.effectSend`. `limits.stepDuration` is in beats on both platforms; millisecond sequences accept `4000 / sampleRate` through 60,000 even when `limits.stepDurationMs` is absent.
|
|
127
153
|
|
|
128
154
|
## Limits
|
|
129
155
|
|
|
@@ -131,6 +157,8 @@ All ranges include their endpoints. `getDefaults().limits` uses `{ min, max }` o
|
|
|
131
157
|
|
|
132
158
|
| Field | Minimum | Maximum | Unit or rule |
|
|
133
159
|
| --------------------------- | ------------------: | ------------------: | ------------------------------------------------------------------------- |
|
|
160
|
+
| `maxToneRepetitions` | 1 | 32 | fixed independent repetition capacity |
|
|
161
|
+
| `intervalMs` | 1 | 60,000 | integer milliseconds between finite-tone attacks |
|
|
134
162
|
| `maxSequences` | 1 | 4 | preallocated sequence capacity |
|
|
135
163
|
| `priority` | 0 | 3 | integer admission priority |
|
|
136
164
|
| `effectSend` | 0 | 1 | explicit processed share |
|
|
@@ -186,7 +214,7 @@ On iOS, `playback` ignores the silent switch and interrupts other apps' audio. `
|
|
|
186
214
|
|
|
187
215
|
## `noteOn(options)`
|
|
188
216
|
|
|
189
|
-
Starts a sustained native voice.
|
|
217
|
+
Starts a sustained native voice. Check `typeof synth.noteOn === 'function'`, `noteOff` and `updateNote` before enabling their controls. Read the installed module version from `getDefaults().version`. The note has no duration limit and needs no JavaScript timer to remain held.
|
|
190
218
|
|
|
191
219
|
| Field | Rule |
|
|
192
220
|
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -228,7 +256,7 @@ Repeating a public sustained note can inherit the oscillator phase and pre-envel
|
|
|
228
256
|
|
|
229
257
|
Begins release from the current level, including during attack or decay. Repeated release requests never restart or lengthen a release. An ended or stolen ID is a no-op. `true` reports validation and queue publication, not proof of an audible voice. On `false` from a full queue, an instrument should retry twice at 16 ms intervals, then shut down and report failure if release still cannot be published.
|
|
230
258
|
|
|
231
|
-
## `
|
|
259
|
+
## `updateNote(options)`
|
|
232
260
|
|
|
233
261
|
| Field | Rule |
|
|
234
262
|
| ------------------------ | -------------------------------------------------------------------------------- |
|
|
@@ -245,26 +273,32 @@ Begins release from the current level, including during attack or decay. Repeate
|
|
|
245
273
|
| `spatialX`, `spatialY` | Target position for a spatial held voice; omitted coordinate stays unchanged. |
|
|
246
274
|
| `effectSend` | Explicit processed share, 0-1, overriding category selection. |
|
|
247
275
|
|
|
248
|
-
|
|
276
|
+
Supply at least one change. An incompatible change rejects the entire request when the ID is known; unknown/ended IDs accept publication as a no-op. The callback also checks source compatibility before applying a command. Changed continuous parameters ramp linearly from their current values over 10 ms. A new target redirects the ramp without restarting oscillator phase or the envelope. Unchanged parameters keep their current ramps. These edits also work during release.
|
|
249
277
|
|
|
250
278
|
Pitch updates are absolute: an initial `detune` is already included in the note's starting frequency. To edit tuning later, compute the desired effective frequency in the app and send `frequency`. There is no `detune` update field. Filter type, ADSR, quantization and wavetable require a new attack. Noise clock, low-pass cutoff, send and compatible stereo/spatial position support live 10 ms updates.
|
|
251
279
|
|
|
252
280
|
```javascript
|
|
253
281
|
if (typeof synth.noteOn !== 'function' || typeof synth.noteOff !== 'function' ||
|
|
254
|
-
typeof synth.
|
|
282
|
+
typeof synth.updateNote !== 'function') throw new Error('Sustained voices unavailable')
|
|
255
283
|
if (!synth.noteOn({ voiceId: 1, note: 'C4', attack: 8, decay: 180, sustain: 0.6,
|
|
256
284
|
release: 300, filterType: 'lowpass', filterHz: 1200, volume: 0.25 })) {
|
|
257
285
|
throw new Error('Note rejected')
|
|
258
286
|
}
|
|
259
|
-
synth.
|
|
287
|
+
synth.updateNote({ voiceId: 1, filterHz: 3600 })
|
|
260
288
|
synth.noteOff({ voiceId: 1 })
|
|
261
289
|
```
|
|
262
290
|
|
|
263
|
-
Use `stopAll()` for panic: it stops every sequence, fades sounding voices and invalidates effect histories. Owner close should call `shutdown()` after cancelling app-owned UI/retry timers.
|
|
291
|
+
Use `stopAll()` for panic: it stops every sequence, cancels finite-tone repetitions, fades sounding voices and invalidates effect histories. Owner close should call `shutdown()` after cancelling app-owned UI/retry timers.
|
|
264
292
|
|
|
265
293
|
|
|
294
|
+
## `updateVoice(options)` (deprecated)
|
|
295
|
+
|
|
296
|
+
Compatibility alias of [`updateNote(options)`](#updatenoteoptions), with identical validation, partial updates, 10 ms transitions, `voiceId` ownership and ended/stolen-ID behavior. Use `updateNote` in new code. The alias remains available without repeated runtime warnings or a scheduled removal date.
|
|
297
|
+
|
|
266
298
|
## `playTone(options)`
|
|
267
299
|
|
|
300
|
+
`playTone` always plays one finite pitch and returns Boolean acceptance. Its closed options reject `intervalMs` with `false`. Use `repeatTone` for native repetition.
|
|
301
|
+
|
|
268
302
|
Plays one pitch. It uses 440 Hz when neither pitch field is present. Note names accept C through B or DO, RE, MI, FA, SOL, LA, SI and TI, followed by optional `#` or lowercase `b` and one octave digit from 0 to 9. Letters are case-insensitive, the omitted octave is 4, and A4 is 440 Hz. A valid named pitch is limited to the active frequency ceiling when rendered; an out-of-range numeric `frequency` is rejected.
|
|
269
303
|
|
|
270
304
|
| Key | Type | Default | Description |
|
|
@@ -307,7 +341,7 @@ Plays one pitch. It uses 440 Hz when neither pitch field is present. Note names
|
|
|
307
341
|
|
|
308
342
|
Omitted attack/release values are shortened to fit an event smaller than the default envelope. Explicit values must satisfy the documented envelope rules. `getDefaults()` reports the baseline defaults, not these per-event adjustments.
|
|
309
343
|
|
|
310
|
-
In the
|
|
344
|
+
In the native implementation, the iOS timed tone/chord command builder
|
|
311
345
|
initially rounds accepted zero attack/release values to one sample, before
|
|
312
346
|
trimming them to the event duration budget. Android retains zero samples.
|
|
313
347
|
Validator acceptance does not imply identical zero-edge rendering. Held notes
|
|
@@ -315,6 +349,60 @@ have the separately documented 3 ms minimum.
|
|
|
315
349
|
|
|
316
350
|
The controls from `decay` to `detune` are described in [Expressive controls](#expressive-controls), including the rules that tie them together.
|
|
317
351
|
|
|
352
|
+
## `repeatTone(options)`
|
|
353
|
+
|
|
354
|
+
Returns a positive `toneId`, or 0 on rejection. Requires integer `intervalMs` from 1 to 60,000 ms; missing or invalid intervals return 0. All remaining options use the same finite-tone contract as `playTone`, with the same admission, envelope, sweeps, gain and routes. Repetition uses neither sustained nor sequence voices.
|
|
355
|
+
|
|
356
|
+
Always supply exact integer milliseconds. The current Apple validator tolerates extremely near-integer values: for example, `1.0000000001` is accepted and converted to `1`, while Android rejects it. This is a native validation discrepancy, not an additional supported interval format; do not rely on that tolerance.
|
|
357
|
+
|
|
358
|
+
| Key | Type | Rule |
|
|
359
|
+
| ----------------- | -------- | ------------------------------------------------- |
|
|
360
|
+
| `intervalMs` | integer | Required; 1-60,000 ms |
|
|
361
|
+
| Other tone fields | as above | Same fields and validation as `playTone(options)` |
|
|
362
|
+
|
|
363
|
+
## Finite-tone repetition
|
|
364
|
+
|
|
365
|
+
Both platforms advertise `capabilities.toneRepetition: true`, `limits.maxToneRepetitions: { min: 1, max: 32 }` and `limits.intervalMs: { min: 1, max: 60000 }`. These 32 repetition slots are independent of the four sequence slots; attacks still share the configured voice pool. Reserve for overlaps, active voices and release tails if the application promises no stealing. Native priority policy remains in force. A positive `toneId` accepts the repeating recipe; it does not guarantee admission of every future attack.
|
|
366
|
+
|
|
367
|
+
The first attack occurs at the first render opportunity. Later attacks use the audio sample clock, retain fractional samples across intervals and blocks, and do not follow `setTempo`. If voice capacity rejects an attack, it is dropped without catch-up and without consuming a variation selection. Every admitted attack finishes its original duration even after `stopTone`.
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
var toneId = synth.repeatTone({ frequency: 1380, frequencyEnd: 340,
|
|
371
|
+
duration: 105, attack: 2, release: 40, waveType: synth.getDefaults().waveTypes.SQUARE,
|
|
372
|
+
volume: 0.1, intervalMs: 150 })
|
|
373
|
+
if (!toneId) throw new Error('Repeating tone rejected')
|
|
374
|
+
// Edits enter at the next scheduled attack, without adding an immediate attack.
|
|
375
|
+
if (!synth.updateTone({ toneId: toneId, intervalMs: 120, volume: 0.08 })) {
|
|
376
|
+
throw new Error('Tone edit rejected')
|
|
377
|
+
}
|
|
378
|
+
// On release/cancel/preset change/owner close:
|
|
379
|
+
if (!synth.stopTone({ toneId: toneId })) throw new Error('Tone stop rejected')
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Independent JavaScript calls remain supported and always keep their Boolean return through `playTone`. A game can fire in response to events or deliberately schedule a fixed count of standalone calls, for example:
|
|
383
|
+
|
|
384
|
+
```javascript
|
|
385
|
+
var recipe = { frequency: 880, duration: 100, attack: 2, release: 30, volume: 0.1 }
|
|
386
|
+
function fireStandalone() {
|
|
387
|
+
if (!synth.playTone(recipe)) Ti.API.warn('Independent tone rejected')
|
|
388
|
+
}
|
|
389
|
+
fireStandalone()
|
|
390
|
+
setTimeout(fireStandalone, 200)
|
|
391
|
+
setTimeout(fireStandalone, 400)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Those three calls use JavaScript timing and independent attack selection. For an ongoing native repetition, call `repeatTone` with `intervalMs` once and use its tone ID. Owner teardown must cancel any application timers it creates.
|
|
395
|
+
|
|
396
|
+
### `updateTone({ toneId, ...changes })`
|
|
397
|
+
|
|
398
|
+
Returns Boolean acceptance. Requires a known positive repetition identifier and at least one change. Accepts partial finite-tone options, including `intervalMs`, and merges them with the latest accepted recipe before validating the entire result. Supplying `note` replaces an earlier `frequency`; supplying `frequency` replaces an earlier `note`. Supplying both is invalid. Invalid edits leave the last accepted recipe intact.
|
|
399
|
+
|
|
400
|
+
An accepted edit affects the next scheduled attack without moving its deadline. The new interval sets the gap after that attack. Active attacks keep their original configuration. A supplied `variation` dictionary replaces the whole previous dictionary; `{ amount: 0 }` disables future variation. Each repetition keeps its own seed and admitted-attack counter. Edits preserve the stream unless the seed changes, which restarts it. Unknown or retired IDs reject edits.
|
|
401
|
+
|
|
402
|
+
### `stopTone({ toneId })`
|
|
403
|
+
|
|
404
|
+
Returns Boolean acceptance. Accepts only a known positive repetition identifier. Cancels future attacks at the next render block, including before the first attack, while admitted finite voices finish completely. Stop publication is independent of the ordinary command queue and remains available when that queue is full. Unknown or retired IDs return `false`. There is no `toneend` event. Identifiers are never reused during the module lifetime, including shutdown/start. `stopAll`, shutdown and destruction invalidate all repetitions and their pending edits.
|
|
405
|
+
|
|
318
406
|
## Native sequence transport
|
|
319
407
|
|
|
320
408
|
`start({ maxSequences: 1 })` preallocates capacity for 1-4 independent sequences; the default is 1. All sequences, timed sounds and held notes share one renderer and the same pool of at most 32 voices. No sequence or category reserves voices.
|
|
@@ -374,7 +462,7 @@ synth.stopSequence({ sequenceId: musicId })
|
|
|
374
462
|
|
|
375
463
|
Selected audio follows fixed input gain → chorus → ping-pong delay → reverb → master volume → optional saturation. Dry audio receives the same global input/master gains, then recombines before one final sample-peak protector with no retained samples. Independent notes never trigger voice-count normalization. The limiter remains the final protection.
|
|
376
464
|
|
|
377
|
-
|
|
465
|
+
This method requires a running engine and returns Boolean acceptance. It merges partial updates into the last accepted processing configuration, validates the complete result and publishes it atomically. Continuous controls ramp over 10 ms. Unknown fields, numeric booleans and non-finite values reject the whole edit. Check that the method exists before calling it.
|
|
378
466
|
|
|
379
467
|
| Section | Fields and initial values |
|
|
380
468
|
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -385,15 +473,15 @@ Requires a running engine and returns Boolean acceptance. Partial updates merge
|
|
|
385
473
|
| `reverb` | `enabled`: false; `decay`: 0.2-8 seconds, initially 1.5; `damping`: 0-1, initially 0.5; `mix`: 0-1, initially 0.2. Stereo damped comb/allpass Schroeder network. |
|
|
386
474
|
| `saturation` | `enabled`: false; `drive`: 1-4, initially 1; `mix`: 0-1, initially 1. Creative soft waveshaping that changes timbre. |
|
|
387
475
|
|
|
388
|
-
Delay sync accepts `off`, `quarter`, `eighth`, `sixteenth`, `quarterTriplet`, `eighthTriplet` or `sixteenthTriplet`. Sync follows the native transport BPM, initially 120. Disabling an effect gradually closes its input and drains its tail, then restores exact bypass. Tails are bounded to 30 seconds with a final fade. `stopAll()` performs panic: it fades voices, stops the
|
|
476
|
+
Delay sync accepts `off`, `quarter`, `eighth`, `sixteenth`, `quarterTriplet`, `eighthTriplet` or `sixteenthTriplet`. Sync follows the native transport BPM, initially 120. Disabling an effect gradually closes its input and drains its tail, then restores exact bypass. Tails are bounded to 30 seconds with a final fade. `stopAll()` performs panic: it fades voices, stops the transport and invalidates effect histories in bounded work. `shutdown()` stops the stream and frees buffers outside the callback.
|
|
389
477
|
|
|
390
478
|
Processing uses one shared chain for the selected categories. `applyTo` is an array drawn from `music`, `effects`, `ui` and `ambience`; default all four. Each event or held voice uses `bus: 'effects'` by default. Optional `effectSend: 0…1` overrides category selection for that voice. Input gain and master volume remain global. Selected audio receives chorus, delay, reverb and optional saturation, then recombines with dry audio before one final limiter. Send changes ramp over 10 ms.
|
|
391
479
|
|
|
392
|
-
Defaults keep all effects and saturation disabled, input gain at unity, and
|
|
480
|
+
Defaults keep all effects and saturation disabled, input gain at unity, and route audio through the unprocessed path. Enabling saturation is an explicit sound-design choice. Output protection is still required with dense voices and effect feedback.
|
|
393
481
|
|
|
394
482
|
## Additional voice modulation
|
|
395
483
|
|
|
396
|
-
`noteOn` and `
|
|
484
|
+
`noteOn` and `updateNote` accept `modulations`, an array of up to two LFO objects. An empty array disables them. Omission on update preserves the previous configuration. Each object accepts only these fields:
|
|
397
485
|
|
|
398
486
|
| Field | Rule |
|
|
399
487
|
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -403,7 +491,7 @@ Defaults keep all effects and saturation disabled, input gain at unity, and pres
|
|
|
403
491
|
| `depth` | 0-1, default 0; scales up to four cutoff octaves, 0.45 PWM offset or ten FM-index units. Two contributions sum with safe clamping. |
|
|
404
492
|
| `sync` | Same musical values as delay; default off. Synced rates follow native tempo (up to 30 Hz at 300 BPM in sixteenth-note triplets). |
|
|
405
493
|
|
|
406
|
-
Edits preserve oscillator/LFO phases. Continuous modulation contributions ramp over 10 ms; cutoff stays within 20 Hz and the frequency maximum, PWM within 0.05-0.95, FM index within 0-10.
|
|
494
|
+
Edits preserve oscillator/LFO phases. Continuous modulation contributions ramp over 10 ms; cutoff stays within 20 Hz and the frequency maximum, PWM within 0.05-0.95, FM index within 0-10. Sequence voices can inherit initial modulations, and steps can override them. These two slots coexist with the independent `lfoFreq`/`lfoDepth` pitch vibrato. Timed tones and chords reject them.
|
|
407
495
|
|
|
408
496
|
## `playChord(options)`
|
|
409
497
|
|
|
@@ -603,7 +691,7 @@ Listen with stereo headphones. Height perception depends on the listener, headph
|
|
|
603
691
|
|
|
604
692
|
Use `typeof synth.getStats === 'function'` to check that the expected module is installed before enabling a diagnostics panel.
|
|
605
693
|
|
|
606
|
-
|
|
694
|
+
This method returns a new snapshot without resetting counters and is available before start and after shutdown. Occupancy comes from the last completed render block; queued commands are read at query time. The query adds no audio-thread allocation, locks or waiting.
|
|
607
695
|
|
|
608
696
|
| Field | Meaning |
|
|
609
697
|
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -631,7 +719,7 @@ Returns a new snapshot without resetting counters. Available before start and af
|
|
|
631
719
|
| `bufferFrames` | Android Oboe output buffer size; otherwise `null`. This is distinct from `callbackFrames`. |
|
|
632
720
|
| `estimatedOutputLatencyMs` | Android timestamp-based stream-to-DAC estimate, when valid; otherwise `null` (always `null` on this iOS backend). |
|
|
633
721
|
|
|
634
|
-
In the
|
|
722
|
+
In the native implementation, iOS early rejections in tempo/sequence operations and a
|
|
635
723
|
stopped `setMasterProcessing` call can bypass rejection counters. Incompatible
|
|
636
724
|
held spatial-mode or modulation updates also have uncounted iOS returns.
|
|
637
725
|
Android counts more of these cases, but an unknown/retired `stopSequence` return
|
|
@@ -642,9 +730,13 @@ rejected request. These counters are diagnostics; the API return is authoritativ
|
|
|
642
730
|
|
|
643
731
|
Render load is a fraction: multiply by 100 for a percentage. For example, `0.012` is 1.2% of the block budget, while `1.0` is 100%. An open stream still renders silent blocks, including output/limiter work and enabled signal measurements, so no active voices does not imply zero load. Callback elapsed time varies with scheduling and system activity; displayed decimal places do not establish measurement accuracy. Compare the same configuration on the same device. Percentages from a simulator and a phone do not provide a direct performance comparison.
|
|
644
732
|
|
|
645
|
-
## `
|
|
733
|
+
## `enableStats()` and `disableStats()`
|
|
734
|
+
|
|
735
|
+
Both return Boolean acceptance. `enableStats()` enables optional timing and signal measurements; `disableStats()` disables them. Default `false`. Enabling starts a new diagnostic window, without restarting audio. Calling `enableStats()` again also starts a fresh window. Disabling hides timing and signal fields immediately. Basic counters remain available in either mode. The setting persists across shutdown/start, while a new engine starts with fresh counters.
|
|
736
|
+
|
|
737
|
+
## `setStatsEnabled(enabled)` (deprecated)
|
|
646
738
|
|
|
647
|
-
|
|
739
|
+
Compatibility alias for the same statistics logic. Requires a Boolean and returns Boolean acceptance. Use `enableStats()` or `disableStats()` in new code. It remains available without repeated runtime deprecation warnings or a scheduled removal date.
|
|
648
740
|
|
|
649
741
|
## `resetStats()`
|
|
650
742
|
|
|
@@ -652,11 +744,11 @@ Returns nothing. Resets accumulated counters and maxima without stopping sounds,
|
|
|
652
744
|
|
|
653
745
|
```javascript
|
|
654
746
|
// Optional diagnostics; poll at 5 Hz and stop the timer with the owner.
|
|
655
|
-
if (synth.
|
|
747
|
+
if (synth.enableStats()) {
|
|
656
748
|
var statsTimer = setInterval(function () {
|
|
657
749
|
Ti.API.info(JSON.stringify(synth.getStats()))
|
|
658
750
|
}, 200)
|
|
659
|
-
// Later: clearInterval(statsTimer); synth.
|
|
751
|
+
// Later: clearInterval(statsTimer); synth.disableStats()
|
|
660
752
|
}
|
|
661
753
|
```
|
|
662
754
|
|
|
@@ -664,4 +756,4 @@ if (synth.setStatsEnabled(true)) {
|
|
|
664
756
|
|
|
665
757
|
`playTone`, `playChord`, `noteOn` and sequence voices accept `bus`, `effectSend` and `priority`. Categories are `music`, `effects` (default), `ui` and `ambience`. An omitted send follows `setMasterProcessing({ applyTo })`; an explicit 0-1 send replaces that selection. Priority is integer 0-3, default 0. When the pool fills, the lowest eligible priority is replaced first; a lower-priority event cannot steal a higher-priority voice. Existing release/age tie-breakers remain. Chords are admitted or dropped completely; no voices are reserved. `getStats().priorityDropCount` counts these drops; `rejectedPublicationCount` counts unavailable sequence capacity/banks separately from output diagnostics.
|
|
666
758
|
|
|
667
|
-
`
|
|
759
|
+
`updateNote` also accepts `noiseRate`, `lowPassHz`, `pan`, `spatialX`, `spatialY` and `effectSend`, with 10 ms transitions. Noise rate 0 means white noise; low-pass 0 means bypass. Noise phase, oscillator phase and spatial history survive compatible edits. Pan updates require a stereo voice; coordinates require a voice started with spatial coordinates. An omitted coordinate retains its previous value. The source/filter type still requires a new attack. Updates are generation-scoped so queued old commands cannot change a later attack sharing the public voice ID.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
> Source snapshot: official TiSynthEngine documentation at commit `
|
|
1
|
+
> Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Module 2.0.0. Upstream `documentation/audio-engine.md` SHA-256: `d5cabb81a1f36d4f32d13d8a03bd492a73c3e34c1c0888bb8af9dfa15a4e4d41`.
|
|
2
2
|
|
|
3
3
|
# Audio engine
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This reference explains how the native signal path, mixing profiles and voice capacity affect the output. Use it to choose those settings or investigate device behavior. It is optional for basic playback.
|
|
6
6
|
|
|
7
7
|
<!-- TOC-START -->
|
|
8
8
|
## Contents
|
|
@@ -29,6 +29,7 @@ The native signal path explains how mixing profiles and voice capacity affect th
|
|
|
29
29
|
- [Sustained voices](#sustained-voices)
|
|
30
30
|
- [Native transport and processing](#native-transport-and-processing)
|
|
31
31
|
- [Unified transport acceptance boundaries](#unified-transport-acceptance-boundaries)
|
|
32
|
+
- [Native finite-tone repetition](#native-finite-tone-repetition)
|
|
32
33
|
|
|
33
34
|
<!-- TOC-END -->
|
|
34
35
|
|
|
@@ -68,7 +69,7 @@ The engine has six sources:
|
|
|
68
69
|
| `NOISE` | White or variable-rate sample-and-hold pseudo-random generator. |
|
|
69
70
|
| `WAVETABLE` | Caller-supplied 2 to 64-sample stepped cycle. |
|
|
70
71
|
|
|
71
|
-
Pitch can move linearly from `frequency` to `frequencyEnd`. A sine LFO adds `lfoDepth * sin(lfoPhase)` in hertz before the oscillator advances.
|
|
72
|
+
Pitch can move linearly from `frequency` to `frequencyEnd`. A sine LFO adds `lfoDepth * sin(lfoPhase)` in hertz before the oscillator advances. Vibrato depth is therefore an absolute value in hertz, rather than a value in cents.
|
|
72
73
|
|
|
73
74
|
Noise rate advances an independent phase accumulator. A zero `noiseRate` uses one new pseudo-random value per output sample; a positive rate holds that value until the accumulator crosses the next update. `noiseRateEnd` changes the increment linearly over the event.
|
|
74
75
|
|
|
@@ -78,7 +79,7 @@ The source is optionally quantized to the selected `bitDepth` (2 to 16 bits) and
|
|
|
78
79
|
|
|
79
80
|
A `SINE` voice with a positive `fmDepth`, or with `fmDepthEnd`, is phase modulated: the carrier computes `sin(2 * pi * phase + depth * sin(2 * pi * modPhase))`. The modulator phase advances by the carrier's instantaneous phase increment times `fmRatio`, so pitch sweeps and vibrato keep the ratio. The index moves linearly from `fmDepth` to `fmDepthEnd` over the event. With a depth of exactly 0 and no end, the voice takes the plain sine path.
|
|
80
81
|
|
|
81
|
-
The modulated sine has constant amplitude, so event gain is unchanged.
|
|
82
|
+
The modulated sine has constant amplitude, so event gain is unchanged. The sidebands are not band-limited; a high carrier with a large index aliases.
|
|
82
83
|
|
|
83
84
|
### State-variable filter
|
|
84
85
|
|
|
@@ -172,15 +173,15 @@ Both ports use a 128-slot single-consumer ring buffer. Producers are serialized
|
|
|
172
173
|
|
|
173
174
|
`playChord()` publishes its note data and advances the queue index once. The consumer receives the complete event or nothing. `stopAll` and `shutdown` have a control fallback so cleanup is not lost under queue pressure.
|
|
174
175
|
|
|
175
|
-
`playSequence` runs inside the sample-clock callback; JavaScript publishes entire configurations
|
|
176
|
+
`playSequence` runs inside the sample-clock callback; JavaScript publishes entire configurations instead of individual notes. Scheduled releases execute independently of public command-queue capacity. `sequenceend` records are delivered outside the callback and retain their sequence identifier when slots are reused.
|
|
176
177
|
|
|
177
178
|
## Shared DSP core
|
|
178
179
|
|
|
179
|
-
The envelope,
|
|
180
|
+
The envelope, state-variable filter, sweep helpers, FM sine and cents conversion share one implementation in `shared/synth_core.h`. It is plain C, header-only and allocation-free. The Android renderer includes it from `android/jni/SynthOboeEngine.cpp` and the iOS renderer from `ios/Classes/TiSynthengineModule.m`, so both platforms run the same code. Backend voice integration and mixing remain separate per platform. Master ramps and final stereo protection share `shared/synth_output.h`. Held envelopes/ramps, transport, master effects, extra modulation and compatible retrigger support also have shared C cores.
|
|
180
181
|
|
|
181
182
|
## Native per-attack variation
|
|
182
183
|
|
|
183
|
-
|
|
184
|
+
TiSynth supports optional `sound.variation` on new attacks. Check both `capabilities.variation === true` and `variationSchemaVersion === 1`, then select bounded values at admitted attacks. The chosen configuration stays stable during that voice; existing sweeps, envelopes and explicit live edits keep their normal roles. Parsing prepares fixed-size state outside audio; the callback uses bounded shared arithmetic without allocation or locks. See [native variation](native-variation.md) for supported locations, source constraints, inheritance and seeds, and [technical verification](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/native-variation-validation.md) for measured compatibility and limits.
|
|
184
185
|
|
|
185
186
|
## Android implementation
|
|
186
187
|
|
|
@@ -200,7 +201,7 @@ The module targets iOS 15 or newer. The module archive contains device, simulato
|
|
|
200
201
|
|
|
201
202
|
## Latency
|
|
202
203
|
|
|
203
|
-
The final output stage retains zero samples
|
|
204
|
+
The final output stage retains zero samples. End-to-end latency still includes the hardware and operating-system output buffers, route transport (including Bluetooth), and JavaScript publication plus callback-boundary latency for a newly submitted event. Accepted sequence steps then follow the sample clock. Attack envelopes and spatial/effect processing retain their existing timing.
|
|
204
205
|
|
|
205
206
|
Android reports a valid timestamp-based stream-to-DAC estimate without adding the former lookahead. Unavailable estimates remain `null`; this iOS backend continues to report `null`.
|
|
206
207
|
|
|
@@ -295,53 +296,11 @@ Android ARM64 reuses coefficients across eight consecutive samples, with four-sa
|
|
|
295
296
|
|
|
296
297
|
## Own-platform performance verification
|
|
297
298
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
The [performance review](performance-review.json) separates measured host improvements, exact compatibility checks and pending device acceptance. Its [raw evidence](performance-review-evidence.zip) retains rejected prototypes and the investigation of idle/stereo regressions. The Apple FIR, stable-control cache and exact idle-effects bypass were evaluated as one atomic candidate and then rejected after physical 32-stereo timing regressed. All three production changes were removed together; the full-score publication and control-memory fixes remain.
|
|
301
|
-
|
|
302
|
-
Matched 48 kHz/1024-frame iPad full-load runs completed 600 audio seconds without callback deadline misses, with p95 load upper bounds 0.427 for the reference and 0.383 for the rejected initial candidate. The original Apple callback was restored because 32-stereo bypass p95 regressed reproducibly, including continuous-voice windows. Android Sim20/Sim80 frame presentation remains near 60 FPS, while occasional Sim80 output underruns persist in both versions. Compact 48 kHz iPad memory growth is about 18.2 MiB in both versions; no material RAM saving or physical 192 kHz acceptance is claimed. These results and qualified listening observations are recorded separately in the review.
|
|
303
|
-
|
|
304
|
-
`tools/build_ios_render_host.py` compiles the actual Objective-C render-block body and voice/transport helpers on macOS. It replaces hardware/session/Titanium startup and fixes only the noise-seed/disabled-diagnostic clock. Output and semantic state are compared against a separately archived reference from the same platform. This is an Apple renderer test; it does not execute AVAudioEngine or establish iPad scheduling, route recovery, footprint or listening acceptance.
|
|
305
|
-
|
|
306
|
-
The commands below reproduce the historical pre-pipeline performance review. Use its archived candidate source when requiring exact PCM/state equality with `7ab3110`; the renderer since 1.0.2 intentionally removes the old final-output delay and changes overload behavior, so it cannot satisfy that earlier exact-output comparison. For the later correction, use the [audio pipeline validation](audio-pipeline-validation.md) protocol: align only the former output delay, compare no-overload PCM within the declared tolerance, and retain overload differences separately. The module's `tools/compare_output_pcm.py` checks the corresponding matched Android/Apple capture sets; host comparisons do not establish physical output latency.
|
|
307
|
-
|
|
308
|
-
```sh
|
|
309
|
-
mkdir -p /tmp/synth-reference
|
|
310
|
-
git archive 7ab3110 | tar -x -C /tmp/synth-reference
|
|
311
|
-
python3 tools/build_ios_render_host.py --source /tmp/synth-reference --output /tmp/apple-reference
|
|
312
|
-
python3 tools/build_ios_render_host.py --output /tmp/apple-candidate
|
|
313
|
-
python3 tools/compare_ios_render.py /tmp/apple-reference /tmp/apple-candidate
|
|
314
|
-
python3 tools/benchmark_performance_review.py --baseline /tmp/apple-reference --candidate /tmp/apple-candidate --output /tmp/apple-performance.json
|
|
315
|
-
/tmp/apple-candidate --metadata
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
The build tool defaults to the module's Apple Release `-Os` optimization. The comparator streams PCM for every fixture block and full original voice/tail, FIR/filter, limiter, effect-buffer and transport state at fixture checkpoints. Cases include five engine rates, zero/short/irregular blocks, all sources, held updates, replacements, send and position ramps, four sequences, effects, panic, drain and later re-enabling. A deliberately changed phase increment was rejected as a negative control.
|
|
319
|
-
|
|
320
|
-
Finish compilation and fidelity checks before timing. The benchmark uses fixed matched warmups and at least ten alternating pairs; CPU and elapsed totals, p95, maxima and process high-water memory remain separate. Investigate repeatable total regressions above 2% or p95 regressions above 5%. Short-process or single-kernel gains do not establish whole-app performance. The Android producer harness exercises the actual JNI publication with supplied JVM array accessors, so its numbers exclude Java validation and VM allocation time.
|
|
321
|
-
|
|
322
|
-
Full-score producers reserve a free bank without copying the previous score. Generic partial-bank callers still inherit the latest accepted bank, and publication retains its ownership, validation and mailbox checks. The iOS producer commits an already prepared score once; Android prepares its complete packet at native publication. Empty completion polls and empty metadata pruning avoid transient objects. Accepted edits of unknown iOS IDs do not create retained spatial metadata. Sequence capacity, diagnostics, event delivery cadence and public defaults are unchanged.
|
|
323
|
-
|
|
324
|
-
The shared processor check is `cc -std=c99 -O2 -Wall -Wextra -Wpedantic tests/spatial_regression.c -lm -o /tmp/spatial_regression` followed by `/tmp/spatial_regression`. `tests/spatial_engine_host.cpp` uses the same compile flags as `engine_render_host.cpp` and verifies native left/right rendering, elevation changes, stealing and stopping. Java spatial validation is checked by `SynthSpatialContractRegression`; the common Java/iOS corpus also covers spatial acceptance and conflicts.
|
|
325
|
-
|
|
326
|
-
### Reproduce a host CPU comparison
|
|
327
|
-
|
|
328
|
-
Compile `tests/spatial_benchmark_host.cpp` with the same Oboe/JDK includes and definitions as `engine_render_host.cpp` above, replacing the source filename and choosing an output such as `/tmp/spatial_benchmark`. Supply a whitespace-separated timeline, one event per line, with these 25 columns in order:
|
|
329
|
-
|
|
330
|
-
```text
|
|
331
|
-
timeMs frequency frequencyEnd durationMs attackMs releaseMs decayMs sustain waveType volume noiseRate noiseRateEnd lowPassHz pulseWidth lfoFreq lfoDepth filterTypeIndex filterHz filterHzEnd filterQ fmRatio fmDepth fmDepthEnd pan panEnd
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
The filter indices are 0=none, 1=lowpass, 2=highpass and 3=bandpass. This internal harness bypasses public contract validation, so only feed valid event recipes. In binaural mode every event uses its `pan` column as spatialX and a synthetic moving elevation `0.8 * sin(timeMs / 1000)`; the other pan column is irrelevant to that mode. This tests CPU, not the localization of the original timeline.
|
|
335
|
-
|
|
336
|
-
```sh
|
|
337
|
-
python3 tools/benchmark_spatial.py /tmp/spatial_benchmark /path/to/timeline.txt
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
The script compares stereo and binaural using a warmup and five measured trials, reporting the median by audio processing time. There is no hardware stream or JavaScript bridge. Results include peak level, non-finite/clipped frames, voice replacements and processing time; they do not measure device CPU, latency, underruns, heat or battery.
|
|
299
|
+
Source-specific measurements and reproduction commands are preserved in the [historical engine validation record](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/audio-engine-validation.md). Current Sound-interface acoustic and runtime checks are recorded in [Sound validation](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/sound-api-validation.md).
|
|
341
300
|
|
|
342
301
|
## Engine statistics
|
|
343
302
|
|
|
344
|
-
The [statistics API](api.md#getstats) has the same shape on both platforms. `shared/synth_stats.h` owns telemetry math and publication. Voice transitions update render-owned occupancy and maxima, including primary FIR draining and external replacement/stop tails.
|
|
303
|
+
The [statistics API](api.md#getstats) has the same shape on both platforms. `shared/synth_stats.h` owns telemetry math and publication. Voice transitions update render-owned occupancy and maxima, including primary FIR draining and external replacement/stop tails. The renderer can then skip whole empty groups without changing the signal. Producers are serialized separately from the renderer; querying queue depth does not include future scheduled pattern steps.
|
|
345
304
|
|
|
346
305
|
The callback publishes atomic words behind a sequence counter. Readers retry a snapshot if publication overlaps their read; the callback never waits for them. Eight-byte alignment and a compile-time lock-free assertion also cover Android's 32-bit ABIs. Reset/enable requests are consumed at callback boundaries. A new control generation hides stale optional measurements immediately. Shutdown waits for the backend callback to stop before clearing occupancy. Queries allocate result objects on the calling thread. Pattern publication and shutdown share a control monitor; that monitor is never taken by the audio callback.
|
|
347
306
|
|
|
@@ -357,11 +316,11 @@ python3 tests/run_stats_java.py
|
|
|
357
316
|
|
|
358
317
|
`tests/stats_engine_host.cpp` uses the Oboe/JDK host flags shown above. It tests native occupancy, reset while playing, saturated queues, binaural drain, shutdown and exact output equality with diagnostics enabled/disabled. CI obtains Oboe 1.11.0 headers and runs this native integration harness alongside the portable shared tests and stopped-engine Java API tests. iOS builds use the same telemetry core, but host Android renders do not execute the iOS audio callback.
|
|
359
318
|
|
|
360
|
-
`tests/stats_benchmark_host.cpp` extends the spatial replay harness with silence, one/32 voices, replacement pressure, chords, scheduled note publications and short FIR drains in stereo/binaural. Build it twice with identical `-O3` flags: once against the previous engine using `-DSTATS_BASELINE`, and once against the current engine. Run `python3 tools/benchmark_stats.py BASELINE CURRENT` for one warmup and ten alternating trials/configuration, including current snapshots at 5 Hz. The harness verifies identical output hashes across all modes. The [measured comparison and limitations](stats-performance.md) explain what the host percentages establish and which results still require device tests.
|
|
319
|
+
`tests/stats_benchmark_host.cpp` extends the spatial replay harness with silence, one/32 voices, replacement pressure, chords, scheduled note publications and short FIR drains in stereo/binaural. Build it twice with identical `-O3` flags: once against the previous engine using `-DSTATS_BASELINE`, and once against the current engine. Run `python3 tools/benchmark_stats.py BASELINE CURRENT` for one warmup and ten alternating trials/configuration, including current snapshots at 5 Hz. The harness verifies identical output hashes across all modes. The [measured comparison and limitations](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/stats-performance.md) explain what the host percentages establish and which results still require device tests.
|
|
361
320
|
|
|
362
321
|
## Sustained voices
|
|
363
322
|
|
|
364
|
-
`noteOn`, `noteOff` and `
|
|
323
|
+
`noteOn`, `noteOff` and `updateNote` use the existing bounded native command queue. Voice storage, smoothing ramps and identity snapshots are preallocated. The callback owns envelopes, oscillator/filter state and replacement decisions; it does not allocate or acquire producer locks. `shared/synth_held.h` supplies the same held ADSR, current-level release and independent 10 ms linear parameter ramps to both renderers. Held attack/decay counters stop advancing after those stages, avoiding sample-counter overflow on long notes.
|
|
365
324
|
|
|
366
325
|
IDs are app-supplied positive 32-bit integers. Atomic 32-bit identity snapshots and queued attacks let producers validate update source compatibility without reading mutable DSP state. The callback checks compatibility again. A full queue rejects note release with `false`; the caller owns its bounded retry and shutdown policy. See the [sustained contract](api.md#noteonoptions) for immutable configuration and supported updates.
|
|
367
326
|
|
|
@@ -372,18 +331,24 @@ Run `tests/held_core_regression.c` with C11 for shared envelope/ramp/identity ch
|
|
|
372
331
|
|
|
373
332
|
`shared/synth_transport.h` owns up to four independent sequences and one global tempo/beat/millisecond clock; `shared/synth_sequence.h` advances each traversal with compensated sample increments. Beat lengths follow tempo changes; millisecond lengths remain constant. A serialized producer prepares sorted traversal and all pitches before publishing one of three immutable banks. The callback accepts tempo at the block boundary and structure at a step boundary; swing changes at a pair boundary. Transport voices share the sustained pool, retain owner identifiers and absolute note deadlines beyond internal slot reuse and release independently of the public command queue. See the [public transport contract](api.md#native-sequence-transport).
|
|
374
333
|
|
|
375
|
-
`shared/synth_effects.h` supplies chorus, ping-pong delay, damped Schroeder reverb and optional saturation; `shared/synth_modulation.h` supplies two phase-preserving LFOs. Buffers allocate at engine initialization and free after the audio backend stops. Epoch tags on 16-sample pages invalidate histories during panic without walking delay memory; recovery clears at most one short page on a first write. Effects start disabled and default rendering follows exact bypass. Enabled effects drain on disable, bounded to 30 seconds.
|
|
334
|
+
`shared/synth_effects.h` supplies chorus, ping-pong delay, damped Schroeder reverb and optional saturation; `shared/synth_modulation.h` supplies two phase-preserving LFOs. Buffers allocate at engine initialization and free after the audio backend stops. Epoch tags on 16-sample pages invalidate histories during panic without walking delay memory; recovery clears at most one short page on a first write. Effects start disabled and default rendering follows exact bypass. Enabled effects drain on disable, bounded to 30 seconds. The implementation uses no additional external DSP dependency.
|
|
376
335
|
|
|
377
336
|
Pre-limiter peak and maximum limiter reduction accumulate natively until reset, so polling cannot miss a brief intervention. The callback tracks linear extrema and converts to decibels once per block. Existing output peak/RMS and reduction windows remain approximately 200 ms. Diagnostics are optional; host measurements do not establish device audio deadlines or listening acceptance.
|
|
378
337
|
|
|
379
|
-
Portable checks: compile and run `tests/sequence_core_regression.c`, `tests/effects_core_regression.c` and `tests/modulation_core_regression.c` with C11, optimization and libm. The transport test checks ten minutes at five sample rates against calculated event times, plus edits, swing, rests, chords and publication pressure. Effects tests check bypass, tails, panic, automation, memory and stability at 16, 44.1, 48, 96 and 192 kHz. On macOS, `tests/studio_contract_ios.m` validates the
|
|
338
|
+
Portable checks: compile and run `tests/sequence_core_regression.c`, `tests/effects_core_regression.c` and `tests/modulation_core_regression.c` with C11, optimization and libm. The transport test checks ten minutes at five sample rates against calculated event times, plus edits, swing, rests, chords and publication pressure. Effects tests check bypass, tails, panic, automation, memory and stability at 16, 44.1, 48, 96 and 192 kHz. On macOS, `tests/studio_contract_ios.m` validates the iOS configuration contract without Titanium.
|
|
380
339
|
|
|
381
|
-
`tools/calibrate_studio.py --studio-root PATH` measures the production Android callback; add `--platform ios` for the production Apple callback. The report covers each preset/scenario at 1, 2, 4, 8, 16 and 32 voices, including independent low/mid/high notes, grouped chords, coherent unisons and overlapping releases. Recommendations target −6 dBFS before protection (with 0.01 dB margin) using the maximum measured peak of the declared usual 1-8-voice configuration and the public voice-volume limit. Larger workloads are separate stress diagnostics. The tool reports fixed scalar recommendations and never edits Studio. Effects and
|
|
340
|
+
`tools/calibrate_studio.py --studio-root PATH` measures the production Android callback; add `--platform ios` for the production Apple callback. The report covers each preset/scenario at 1, 2, 4, 8, 16 and 32 voices, including independent low/mid/high notes, grouped chords, coherent unisons and overlapping releases. Recommendations target −6 dBFS before protection (with 0.01 dB margin) using the maximum measured peak of the declared usual 1-8-voice configuration and the public voice-volume limit. Larger workloads are separate stress diagnostics. The tool reports fixed scalar recommendations and never edits Studio. Effects and additional LFOs are disabled, so recommendations do not guarantee headroom for other configurations. See [audio pipeline validation](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/audio-pipeline-validation.md).
|
|
382
341
|
|
|
383
342
|
Device feedback superseded that headroom target for Studio's initial Cristal preset: its fixed gain is restored to 1 because the calibrated setting made a fresh Android installation barely audible. This adds 10.83 dB without saturation or voice-count normalization. Three coherent factory notes remain below the limiter ceiling at maximum master; eight independent coherent notes can require limiting. The calibration recommendations remain recorded separately from this active preset exception.
|
|
384
343
|
|
|
385
|
-
The [Studio technical validation report](studio-validation.json) records the calibration and corrected full-chain host benchmark, including failed deadline acceptance and pending device checks. Its host performance result must not be presented as hardware realtime acceptance.
|
|
344
|
+
The [Studio technical validation report](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/docs/validation/studio-validation.json) records the calibration and corrected full-chain host benchmark, including failed deadline acceptance and pending device checks. Its host performance result must not be presented as hardware realtime acceptance.
|
|
386
345
|
|
|
387
346
|
## Unified transport acceptance boundaries
|
|
388
347
|
|
|
389
|
-
|
|
348
|
+
Source-specific measurements and reproduction commands are preserved in the [historical engine validation record](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/audio-engine-validation.md). Current Sound-interface acoustic and runtime checks are recorded in [Sound validation](https://github.com/macCesar/TiSynthEngine/blob/9d74aa45f84d3f2822d4aab76874efe37d81257f/documentation/sound-api-validation.md).
|
|
349
|
+
|
|
350
|
+
## Native finite-tone repetition
|
|
351
|
+
|
|
352
|
+
The shared C scheduler owns 32 repetition slots independently of sequence capacity. It retains fractional sample deadlines across normal and spatial render blocks. Each `repeatTone` attack emits the same finite-tone command used by a standalone `playTone`. The first attack starts at the first render opportunity, independent of tempo. Preallocated publication banks carry complete validated configurations; the callback allocates no memory, takes no producer locks and runs no JavaScript. Stop publication has a separate atomic path, so a full ordinary command queue cannot prevent cancellation at the next block.
|
|
353
|
+
|
|
354
|
+
Each repetition owns its variation stream and admitted-attack counter. Admission failure skips that attack without advancing variation or queuing catch-up work. Updates preserve the pending deadline and affect its attack; a new interval controls the subsequent gap. `stopTone` cancels only future attacks, leaving admitted finite tones to complete. Global stop, shutdown and destruction invalidate slots and pending edits. Historical measurements elsewhere on this page do not measure this scheduler. See [the public contract](api.md#finite-tone-repetition).
|