@maccesar/titools 5.1.1 → 5.2.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 +44 -12
- package/agents/ti-pro.md +4 -0
- package/commands/ti-audit.md +4 -0
- package/lib/config.js +2 -0
- package/package.json +1 -1
- package/skills/ti-admob-unified/SKILL.md +44 -0
- package/skills/ti-admob-unified/agents/openai.yaml +4 -0
- package/skills/ti-admob-unified/assets/example/app.js +1003 -0
- package/skills/ti-admob-unified/assets/example/lib/ads.js +618 -0
- package/skills/ti-admob-unified/assets/example/library-usage.js +104 -0
- package/skills/ti-admob-unified/references/api-reference.md +592 -0
- package/skills/ti-admob-unified/references/banners.md +435 -0
- package/skills/ti-admob-unified/references/contributing.md +228 -0
- package/skills/ti-admob-unified/references/dependencies.md +85 -0
- package/skills/ti-admob-unified/references/fullscreen.md +223 -0
- package/skills/ti-admob-unified/references/game-monetization.md +17 -0
- package/skills/ti-admob-unified/references/getting-started.md +275 -0
- package/skills/ti-admob-unified/references/index.md +30 -0
- package/skills/ti-admob-unified/references/integration.md +85 -0
- package/skills/ti-admob-unified/references/library.md +189 -0
- package/skills/ti-admob-unified/references/mediation.md +132 -0
- package/skills/ti-admob-unified/references/migration.md +255 -0
- package/skills/ti-admob-unified/references/module-changelog.md +54 -0
- package/skills/ti-admob-unified/references/module-license.txt +21 -0
- package/skills/ti-admob-unified/references/privacy.md +254 -0
- package/skills/ti-admob-unified/references/roadmap.md +146 -0
- package/skills/ti-admob-unified/references/source-notes.md +33 -0
- package/skills/ti-admob-unified/references/source-snapshot.json +440 -0
- package/skills/ti-admob-unified/references/third-party-notices.md +22 -0
- package/skills/ti-game/SKILL.md +10 -1
- package/skills/ti-game/references/api.md +10 -2
- package/skills/ti-game/references/debugging-performance.md +6 -0
- package/skills/ti-game/references/examples.md +11 -1
- package/skills/ti-game/references/patterns.md +24 -2
- package/skills/ti-game/references/physics.md +502 -0
- package/skills/ti-game/references/project-setup.md +23 -1
- package/skills/ti-game/references/recipes-action.md +46 -14
- package/skills/ti-game/references/recipes-adventure-effects.md +12 -1
- package/skills/ti-game/references/roadmap.md +2 -2
- package/skills/ti-game/references/tilemaps.md +2 -1
- package/skills/ti-game-assets/SKILL.md +53 -0
- package/skills/ti-game-assets/agents/openai.yaml +4 -0
- package/skills/ti-game-assets/references/alpha.md +26 -0
- package/skills/ti-game-assets/references/art-direction.md +37 -0
- package/skills/ti-game-assets/references/integration.md +49 -0
- package/skills/ti-game-assets/references/manifest.example.json +429 -0
- package/skills/ti-game-assets/references/manifest.md +39 -0
- package/skills/ti-game-assets/references/offline-audio.md +33 -0
- package/skills/ti-game-assets/references/prompts.md +33 -0
- package/skills/ti-game-assets/references/repeating-textures.md +17 -0
- package/skills/ti-game-assets/references/responsible-ads.md +31 -0
- package/skills/ti-game-assets/references/sound-assets.md +42 -0
- package/skills/ti-game-assets/references/vector-sources.md +21 -0
- package/skills/ti-game-assets/scripts/alpha_tools.py +160 -0
- package/skills/ti-game-assets/scripts/requirements.txt +1 -0
- package/skills/ti-game-assets/scripts/validate_manifest.py +267 -0
- package/skills/ti-synthengine/SKILL.md +37 -13
- package/skills/ti-synthengine/references/api.md +397 -31
- package/skills/ti-synthengine/references/audio-engine.md +235 -14
- package/skills/ti-synthengine/references/examples.md +396 -17
- package/skills/ti-synthengine/references/guide.md +331 -12
- package/skills/ti-synthengine/references/recipes.md +212 -22
- package/skills/ti-synthengine/references/recommendations.md +53 -16
- package/skills/ti-synthengine/references/sound-design.md +359 -15
- package/skills/ti-synthengine/references/source-notes.md +121 -0
- package/skills/ti-synthengine/references/source-snapshot.json +105 -0
- package/skills/ti-synthengine/references/stats-performance.json +2992 -0
- package/skills/ti-synthengine/references/stats-performance.md +38 -0
- package/skills/ti-synthengine/references/troubleshooting.md +61 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
> Source snapshot: official TiSynthEngine 1.0.0 documentation
|
|
1
|
+
> Source snapshot: official TiSynthEngine 1.0.0 beta documentation from the reviewed working tree based on `95901c98af5353b86d9dc7c60b29ab9186b038cd` (2026-10-14), including uncommitted statistics changes. Upstream `documentation/audio-engine.md` SHA-256: `7b5707ee22e01a351af3633be14458726f8956069fb9355d1fb31cc4f7b9438a`.
|
|
2
2
|
|
|
3
3
|
# Audio engine
|
|
4
4
|
|
|
@@ -6,6 +6,29 @@ This page explains the native signal path behind the JavaScript API. You do not
|
|
|
6
6
|
need it to play a sound, but it helps when choosing a mixing profile, sizing the
|
|
7
7
|
voice pool or investigating device behavior.
|
|
8
8
|
|
|
9
|
+
<!-- TOC-START -->
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
- [Signal path](#signal-path)
|
|
13
|
+
- [Oscillators](#oscillators)
|
|
14
|
+
- [Envelope](#envelope)
|
|
15
|
+
- [Event gain](#event-gain)
|
|
16
|
+
- [Mixing profiles](#mixing-profiles)
|
|
17
|
+
- [Limiter](#limiter)
|
|
18
|
+
- [Master volume and pan](#master-volume-and-pan)
|
|
19
|
+
- [Voice pool](#voice-pool)
|
|
20
|
+
- [Command queue](#command-queue)
|
|
21
|
+
- [Shared DSP core](#shared-dsp-core)
|
|
22
|
+
- [Android implementation](#android-implementation)
|
|
23
|
+
- [iOS implementation](#ios-implementation)
|
|
24
|
+
- [Latency](#latency)
|
|
25
|
+
- [Route behavior](#route-behavior)
|
|
26
|
+
- [Regression coverage](#regression-coverage)
|
|
27
|
+
- [Binaural front-plane renderer](#binaural-front-plane-renderer)
|
|
28
|
+
- [Engine statistics](#engine-statistics)
|
|
29
|
+
|
|
30
|
+
<!-- TOC-END -->
|
|
31
|
+
|
|
9
32
|
## Signal path
|
|
10
33
|
|
|
11
34
|
```text
|
|
@@ -15,7 +38,9 @@ playTone / playChord / playPattern
|
|
|
15
38
|
|
|
|
16
39
|
native command ring buffer
|
|
17
40
|
|
|
|
18
|
-
voice pool:
|
|
41
|
+
voice pool: source (or FM) -> quantizer -> one-pole low-pass
|
|
42
|
+
-> state-variable filter -> envelope -> gain
|
|
43
|
+
-> stereo pan OR binaural FIR
|
|
19
44
|
|
|
|
20
45
|
sum voices and steal tails
|
|
21
46
|
|
|
|
@@ -32,7 +57,7 @@ them and renders the samples.
|
|
|
32
57
|
|
|
33
58
|
## Oscillators
|
|
34
59
|
|
|
35
|
-
The engine has
|
|
60
|
+
The engine has six sources:
|
|
36
61
|
|
|
37
62
|
| Waveform | Source behavior |
|
|
38
63
|
| --- | --- |
|
|
@@ -40,18 +65,68 @@ The engine has five sources:
|
|
|
40
65
|
| `SQUARE` | PolyBLEP band-limited discontinuity. |
|
|
41
66
|
| `SAWTOOTH` | PolyBLEP band-limited discontinuity. |
|
|
42
67
|
| `TRIANGLE` | Continuous triangle oscillator. |
|
|
43
|
-
| `NOISE` | White-
|
|
68
|
+
| `NOISE` | White or variable-rate sample-and-hold pseudo-random generator. |
|
|
69
|
+
| `WAVETABLE` | Caller-supplied 2 to 64-sample stepped cycle. |
|
|
44
70
|
|
|
45
71
|
Pitch can move linearly from `frequency` to `frequencyEnd`. A sine LFO adds
|
|
46
72
|
`lfoDepth * sin(lfoPhase)` in hertz before the oscillator advances. This makes
|
|
47
73
|
vibrato depth absolute in hertz rather than cents.
|
|
48
74
|
|
|
75
|
+
Noise rate advances an independent phase accumulator. A zero `noiseRate` uses
|
|
76
|
+
one new pseudo-random value per output sample; a positive rate holds that value
|
|
77
|
+
until the accumulator crosses the next update. `noiseRateEnd` changes the
|
|
78
|
+
increment linearly over the event.
|
|
79
|
+
|
|
80
|
+
The source is optionally quantized to the selected `bitDepth` (2 to 16 bits)
|
|
81
|
+
and then passes through a one-pole low-pass filter when `lowPassHz` is nonzero.
|
|
82
|
+
Square waves use `pulseWidth` for the second PolyBLEP discontinuity and can
|
|
83
|
+
interpolate that duty cycle toward `pulseWidthEnd`.
|
|
84
|
+
|
|
85
|
+
### Frequency modulation
|
|
86
|
+
|
|
87
|
+
A `SINE` voice with a positive `fmDepth`, or with `fmDepthEnd`, is phase
|
|
88
|
+
modulated: the carrier computes `sin(2 * pi * phase + depth * sin(2 * pi *
|
|
89
|
+
modPhase))`. The modulator phase advances by the carrier's instantaneous phase
|
|
90
|
+
increment times `fmRatio`, so pitch sweeps and vibrato keep the ratio. The
|
|
91
|
+
index moves linearly from `fmDepth` to `fmDepthEnd` over the event. With a depth
|
|
92
|
+
of exactly 0 and no end, the voice takes the plain sine path.
|
|
93
|
+
|
|
94
|
+
The modulated sine has constant amplitude, so event gain is unchanged. Nothing
|
|
95
|
+
band-limits the sidebands, so a high carrier with a large index aliases.
|
|
96
|
+
|
|
97
|
+
### State-variable filter
|
|
98
|
+
|
|
99
|
+
`filterType` selects a topology-preserving state-variable filter that runs
|
|
100
|
+
after the one-pole `lowPassHz` stage. It is stable while its coefficients move,
|
|
101
|
+
which is why a cutoff sweep cannot blow up even at high Q.
|
|
102
|
+
|
|
103
|
+
| Output | Response |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `lowpass` | 12 dB per octave, peak of about Q at the cutoff. |
|
|
106
|
+
| `highpass` | 12 dB per octave, peak of about Q at the cutoff. |
|
|
107
|
+
| `bandpass` | Unity gain at the center for every Q. |
|
|
108
|
+
|
|
109
|
+
The cutoff is held between 20 Hz and `sampleRate * 0.45`. With `filterHzEnd`
|
|
110
|
+
the cutoff follows `from * (to / from) ^ t`, where `t` runs from 0 to 1 across
|
|
111
|
+
the event, and coefficients are recomputed every eighth sample. Without a
|
|
112
|
+
sweep they are computed once, when the note starts.
|
|
113
|
+
|
|
49
114
|
## Envelope
|
|
50
115
|
|
|
51
|
-
Each voice has a
|
|
52
|
-
release. `duration` includes the whole
|
|
53
|
-
|
|
54
|
-
contract.
|
|
116
|
+
Each voice has a smoothstep attack, an optional smoothstep decay to `sustain`,
|
|
117
|
+
a hold at that level and a smoothstep release. `duration` includes the whole
|
|
118
|
+
envelope. Defaults are adjusted to fit a short event, while an explicit
|
|
119
|
+
envelope that cannot fit is rejected by the contract.
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
base = attack ramp, then decay ramp from 1 to sustain, then sustain
|
|
123
|
+
level = base * release ramp
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The release ramp multiplies the base level, so a release that begins during the
|
|
127
|
+
decay is continuous. With `decay: 0` and `sustain: 1` the base is 1 after the
|
|
128
|
+
attack and the result is the plain attack, hold and release shape. Voice-steal
|
|
129
|
+
tails read the final level, so they stay continuous with the decay too.
|
|
55
130
|
|
|
56
131
|
Voice stealing does not cut the old waveform at an arbitrary sample. The old
|
|
57
132
|
voice contributes a short tail of roughly 6 ms while the slot begins the new
|
|
@@ -75,7 +150,7 @@ Waveforms are calibrated toward a common source RMS:
|
|
|
75
150
|
| --- | ---: | ---: |
|
|
76
151
|
| Sine | 0.440 | 0.311 |
|
|
77
152
|
| Square | 0.311 | 0.311 |
|
|
78
|
-
| Sawtooth, triangle and
|
|
153
|
+
| Sawtooth, triangle, noise and wavetable | 0.539 | signal-dependent |
|
|
79
154
|
|
|
80
155
|
This calculation is why one three-note `playChord` and three `playTone` calls
|
|
81
156
|
are not equivalent. A protected sine triad assigns about 0.254 to each chord
|
|
@@ -149,7 +224,18 @@ a control fallback so cleanup is not lost under queue pressure.
|
|
|
149
224
|
|
|
150
225
|
Pattern timing runs outside the audio callback. A platform timer places each
|
|
151
226
|
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.
|
|
227
|
+
musical patterns, but it is not a sample-accurate composition timeline. The
|
|
228
|
+
same timer fires `patternend`, about 1 ms after the sum of the step lengths,
|
|
229
|
+
and the event is handed to the JavaScript runtime asynchronously.
|
|
230
|
+
|
|
231
|
+
## Shared DSP core
|
|
232
|
+
|
|
233
|
+
The envelope, the state-variable filter, the sweep helpers, the FM sine and
|
|
234
|
+
the cents conversion live once, in `shared/synth_core.h`. It is plain C,
|
|
235
|
+
header-only and allocation-free. The Android renderer includes it from
|
|
236
|
+
`android/jni/SynthOboeEngine.cpp` and the iOS renderer from
|
|
237
|
+
`ios/Classes/TiSynthengineModule.m`, so both platforms run the same code. Voice
|
|
238
|
+
management, mixing and the limiter are still separate per platform.
|
|
153
239
|
|
|
154
240
|
## Android implementation
|
|
155
241
|
|
|
@@ -165,11 +251,12 @@ ABIs are `arm64-v8a`, `armeabi-v7a`, `x86` and `x86_64`.
|
|
|
165
251
|
|
|
166
252
|
iOS renders with `AVAudioEngine` and `AVAudioSourceNode`, using the active
|
|
167
253
|
session's sample rate. The module configures the audio session as `Playback`,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
254
|
+
or `Ambient` when `startEngine()` receives `audioSession: 'ambient'`, activates
|
|
255
|
+
it, and observes route and engine configuration notifications. If a route
|
|
256
|
+
change stops the graph, the port reapplies the category, reactivates the
|
|
257
|
+
session and resumes the existing renderer.
|
|
171
258
|
|
|
172
|
-
|
|
259
|
+
The module targets iOS 15 or newer. The module archive contains device,
|
|
173
260
|
simulator and Mac Catalyst slices.
|
|
174
261
|
|
|
175
262
|
## Latency
|
|
@@ -199,15 +286,18 @@ device:
|
|
|
199
286
|
```sh
|
|
200
287
|
node tests/api_contract_regression.js
|
|
201
288
|
node tests/default_profile_regression.js
|
|
289
|
+
node tests/documentation_regression.js
|
|
202
290
|
|
|
203
291
|
cd android
|
|
204
292
|
test_dir=$(mktemp -d /tmp/ti-synth-test.XXXXXX)
|
|
205
293
|
javac -d "$test_dir" \
|
|
206
294
|
src/ti/synthengine/SynthContract.java \
|
|
207
295
|
test/ti/synthengine/SynthContractRegression.java \
|
|
296
|
+
test/ti/synthengine/SynthSpatialContractRegression.java \
|
|
208
297
|
src/ti/synthengine/SynthDsp.java \
|
|
209
298
|
test/ti/synthengine/SynthDspRegression.java
|
|
210
299
|
java -cp "$test_dir" ti.synthengine.SynthContractRegression
|
|
300
|
+
java -cp "$test_dir" ti.synthengine.SynthSpatialContractRegression
|
|
211
301
|
java -cp "$test_dir" ti.synthengine.SynthDspRegression
|
|
212
302
|
```
|
|
213
303
|
|
|
@@ -215,3 +305,134 @@ The DSP suite covers stable RMS from 1 through 32 voices, limiter transparency
|
|
|
215
305
|
and recovery, waveform discontinuities, envelope endpoints, master smoothing,
|
|
216
306
|
voice stealing and representative dense chords. Device tests remain necessary
|
|
217
307
|
for route changes, startup underruns and speaker acoustics.
|
|
308
|
+
|
|
309
|
+
Three more checks cover the shared core, the Android engine and the iOS contract.
|
|
310
|
+
|
|
311
|
+
The shared DSP core builds with any C99 compiler:
|
|
312
|
+
|
|
313
|
+
```sh
|
|
314
|
+
test_dir=$(mktemp -d /tmp/ti-synth-core.XXXXXX)
|
|
315
|
+
cc -std=c99 -O2 -Wall -Wextra -o "$test_dir/dsp_core" \
|
|
316
|
+
tests/dsp_core_regression.c -lm
|
|
317
|
+
"$test_dir/dsp_core"
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
It checks that the envelope equals the original attack, hold and release shape
|
|
321
|
+
for every attack and release combination when decay is off, the decay and
|
|
322
|
+
sustain endpoints and continuity, the response of each filter type (slope,
|
|
323
|
+
cutoff and the resonance peak), stability of every filter type under a cutoff
|
|
324
|
+
sweep on noise, FM sidebands against the Bessel function values and the cents
|
|
325
|
+
conversion.
|
|
326
|
+
|
|
327
|
+
The Android engine renders on the host. `tests/engine_render_host.cpp` compiles
|
|
328
|
+
`SynthOboeEngine.cpp` without opening an audio stream and drives the same
|
|
329
|
+
command and callback path Oboe uses. It needs the Oboe headers, which any
|
|
330
|
+
Gradle cache that built the module has, and the JDK's `jni.h`, so it is a local
|
|
331
|
+
check and does not run in CI:
|
|
332
|
+
|
|
333
|
+
```sh
|
|
334
|
+
OBOE=$(find ~/.gradle -path '*oboe-1.10.0/prefab/modules/oboe/include' | head -1)
|
|
335
|
+
JH=$(/usr/libexec/java_home)
|
|
336
|
+
test_dir=$(mktemp -d /tmp/ti-synth-engine.XXXXXX)
|
|
337
|
+
clang++ -std=c++17 -O1 -Wall -I"$OBOE" -I"$JH/include" -I"$JH/include/darwin" \
|
|
338
|
+
-DHAS_EXPRESSIVE -DENGINE_CPP='"../android/jni/SynthOboeEngine.cpp"' \
|
|
339
|
+
-Wl,-undefined,dynamic_lookup tests/engine_render_host.cpp \
|
|
340
|
+
-o "$test_dir/engine_render"
|
|
341
|
+
"$test_dir/engine_render"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
It renders decay and sustain levels, filter attenuation, an opening cutoff
|
|
345
|
+
sweep, FM sidebands and an FM index that decays to nothing, plus voice stealing
|
|
346
|
+
and `stopAll` with every control active. Built without `-DHAS_EXPRESSIVE` it
|
|
347
|
+
renders only the original controls and prints one hash per scenario, so the
|
|
348
|
+
same file can be built against an older copy of the engine and the hashes
|
|
349
|
+
compared.
|
|
350
|
+
|
|
351
|
+
The iOS contract replays the cases that the Java contract test writes to
|
|
352
|
+
`tests/contract_corpus.jsonl`. The Java test fails when that file no longer
|
|
353
|
+
matches the Java contract; regenerate it from `android/` with
|
|
354
|
+
`-Dsynth.corpus.out=../tests/contract_corpus.jsonl`. On macOS, run the iOS
|
|
355
|
+
side from the repository root:
|
|
356
|
+
|
|
357
|
+
```sh
|
|
358
|
+
test_dir=$(mktemp -d /tmp/ti-synth-ios.XXXXXX)
|
|
359
|
+
clang -fobjc-arc -Wall -Wextra -framework Foundation \
|
|
360
|
+
tests/contract_corpus_ios.m ios/Classes/TiSynthengineContract.m \
|
|
361
|
+
-o "$test_dir/contract_corpus_ios"
|
|
362
|
+
"$test_dir/contract_corpus_ios"
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The iOS renderer shares the DSP math through the same header, but nothing
|
|
366
|
+
renders its output on the host, so check it on a device.
|
|
367
|
+
|
|
368
|
+
## Binaural front-plane renderer
|
|
369
|
+
|
|
370
|
+
`shared/synth_spatial.h` runs the same allocation-free FIR processor in both native ports, after each voice envelope and before summation/master/limiter. Nine stereo impulse responses from Gardner and Martin's MIT KEMAR measurements form a compact front-plane grid. The complete 128-sample source responses at 44.1 kHz are interpolated across direction and linearly resampled once at voice onset to the output rate. A single front-response energy calibration preserves directional differences. At 48 kHz the convolution uses 140 padded taps per ear. Buffers are reserved with the voice/tail pools; there are no runtime downloads, callback allocations, or additional callback locks.
|
|
371
|
+
|
|
372
|
+
Spatial voice-steal and stop tails copy the filter history as well as oscillator state. After the source envelope or fade ends, both ports drain the FIR with zero input for `taps - 1` samples before freeing the voice; this preserves even 1 ms events. Ordinary voices bypass the FIR. CPU cost grows with spatial voices, including short replacement tails. A fixed position avoids changing the filter during an event. The intended output-rate range is 8 to 192 kHz. Interpolated generic HRTFs provide directional cues, not guaranteed individual localization.
|
|
373
|
+
|
|
374
|
+
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.
|
|
375
|
+
|
|
376
|
+
### Reproduce a host CPU comparison
|
|
377
|
+
|
|
378
|
+
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:
|
|
379
|
+
|
|
380
|
+
```text
|
|
381
|
+
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
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
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.
|
|
385
|
+
|
|
386
|
+
```sh
|
|
387
|
+
python3 tools/benchmark_spatial.py /tmp/spatial_benchmark /path/to/timeline.txt
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
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.
|
|
391
|
+
|
|
392
|
+
## Engine statistics
|
|
393
|
+
|
|
394
|
+
The [statistics API](api.md#getstats) has the same shape on both platforms.
|
|
395
|
+
`shared/synth_stats.h` owns telemetry math and publication. Voice transitions
|
|
396
|
+
update render-owned occupancy and maxima, including primary FIR draining and
|
|
397
|
+
external replacement/stop tails. Whole empty groups can then be skipped without
|
|
398
|
+
changing the signal. Producers are serialized separately from the renderer;
|
|
399
|
+
querying queue depth does not include future scheduled pattern steps.
|
|
400
|
+
|
|
401
|
+
The callback publishes atomic words behind a sequence counter. Readers retry a
|
|
402
|
+
snapshot if publication overlaps their read; the callback never waits for them.
|
|
403
|
+
Eight-byte alignment and a compile-time lock-free assertion also cover Android's
|
|
404
|
+
32-bit ABIs. Reset/enable requests are consumed at callback boundaries. A new
|
|
405
|
+
control generation hides stale optional measurements immediately. Shutdown waits
|
|
406
|
+
for the backend callback to stop before clearing occupancy. Queries allocate
|
|
407
|
+
result objects on the calling thread. Pattern publication and shutdown share a
|
|
408
|
+
control monitor; that monitor is never taken by the audio callback.
|
|
409
|
+
|
|
410
|
+
Diagnostic timing uses a monotonic host clock only while enabled. Stereo peak,
|
|
411
|
+
RMS and maximum limiter reduction accumulate over approximately 200 ms of final
|
|
412
|
+
float output; logarithms are calculated at window boundaries. These readings do
|
|
413
|
+
not diagnose physical distortion, latency, heat, battery use or overall app CPU.
|
|
414
|
+
|
|
415
|
+
Run portable telemetry and Java API tests with:
|
|
416
|
+
|
|
417
|
+
```sh
|
|
418
|
+
c++ -std=c++17 -O2 -Wall -Wextra -pthread tests/stats_regression.cpp -o /tmp/stats
|
|
419
|
+
/tmp/stats
|
|
420
|
+
python3 tests/run_stats_java.py
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
`tests/stats_engine_host.cpp` uses the Oboe/JDK host flags shown above. It tests
|
|
424
|
+
native occupancy, reset while playing, saturated queues, binaural drain,
|
|
425
|
+
shutdown and exact output equality with diagnostics enabled/disabled. This
|
|
426
|
+
native integration harness requires local Oboe headers; portable shared tests
|
|
427
|
+
and the stopped-engine Java API tests also run in CI. iOS builds use the same
|
|
428
|
+
telemetry core, but host Android renders do not execute the iOS audio callback.
|
|
429
|
+
|
|
430
|
+
`tests/stats_benchmark_host.cpp` extends the spatial replay harness with silence,
|
|
431
|
+
one/32 voices, replacement pressure, chords, scheduled note publications and
|
|
432
|
+
short FIR drains in stereo/binaural. Build it twice with identical `-O3` flags:
|
|
433
|
+
once against the previous engine using `-DSTATS_BASELINE`, and once against the
|
|
434
|
+
current engine. Run `python3 tools/benchmark_stats.py BASELINE CURRENT` for one
|
|
435
|
+
warmup and ten alternating trials/configuration, including current snapshots at
|
|
436
|
+
5 Hz. The harness verifies identical output hashes across all modes. See the
|
|
437
|
+
[measured comparison and limitations](stats-performance.md) before interpreting
|
|
438
|
+
host percentages as a device guarantee.
|