@maccesar/titools 5.2.3 → 5.2.4

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.
@@ -1,4 +1,4 @@
1
- > Source snapshot: official TiSynthEngine 1.0.2 documentation from reviewed working-tree source based on `4a45f21230ffc5a8838fbd5fc108dd3ab470362a` (2026-10-29). Upstream `documentation/audio-engine.md` SHA-256: `17fb01f7e32a91cc44a3b5c0f30efcd7b5c3d9ec6c49369f7c789e1f22a8b442`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `b07c97c762be5905f132cecf33e9d60ea0c21316` (2026-11-02). Published 1.0.2; local 1.1.0 candidates; public API 1.0.0. Upstream `documentation/audio-engine.md` SHA-256: `a3231af42efb2e3bf0ec457083f4c1128728f09c4b2f52d1cbf1df1e76b04886`.
2
2
 
3
3
  # Audio engine
4
4
 
@@ -17,6 +17,7 @@ The native signal path explains how mixing profiles and voice capacity affect th
17
17
  - [Voice pool](#voice-pool)
18
18
  - [Command queue](#command-queue)
19
19
  - [Shared DSP core](#shared-dsp-core)
20
+ - [Native per-attack variation](#native-per-attack-variation)
20
21
  - [Android implementation](#android-implementation)
21
22
  - [iOS implementation](#ios-implementation)
22
23
  - [Latency](#latency)
@@ -141,11 +142,11 @@ The profile is fixed when `start()` opens the stream.
141
142
 
142
143
  A single stereo-linked peak limiter sits after master volume and optional creative saturation. Linking preserves the stereo image when only one channel has the largest peak.
143
144
 
144
- | Parameter | Value |
145
- | --------- | -------------------------------- |
146
- | Retained samples | 0 (no lookahead) |
147
- | Release | 100 ms, exponential |
148
- | Ceiling | 0.85, or 0.70 with `speakerSafe` |
145
+ | Parameter | Value |
146
+ | ---------------- | -------------------------------- |
147
+ | Retained samples | 0 (no lookahead) |
148
+ | Release | 100 ms, exponential |
149
+ | Ceiling | 0.85, or 0.70 with `speakerSafe` |
149
150
 
150
151
  The shared `synth_output.h` core finds the larger absolute channel value for each stereo frame and immediately applies any required reduction to both channels. Recovery toward unity uses a 100 ms exponential constant, bounded by the current frame's required gain; two consecutive quiet stereo frames (each channel ≤1e-12 in magnitude) reset reduction; an isolated zero crossing does not. There is no audio delay buffer. The ceilings bound sample values, not inter-sample peaks or loudspeaker excursion.
151
152
 
@@ -177,6 +178,10 @@ Both ports use a 128-slot single-consumer ring buffer. Producers are serialized
177
178
 
178
179
  The envelope, the state-variable filter, the sweep helpers, the FM sine and the cents conversion live once, 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.
179
180
 
181
+ ## Native per-attack variation
182
+
183
+ Local 1.1.0 candidates add optional `variation`; published 1.0.2 packages reject it. 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](native-variation-validation.md) for measured compatibility and limits.
184
+
180
185
  ## Android implementation
181
186
 
182
187
  Android renders in C++ through Oboe. The stream requests floating-point output, low-latency performance and exclusive sharing when the device supports it. If exclusive access is unavailable, Oboe can use shared mode.
@@ -290,13 +295,15 @@ Android ARM64 reuses coefficients across eight consecutive samples, with four-sa
290
295
 
291
296
  ## Own-platform performance verification
292
297
 
298
+ The measurements in this section predate native variation and describe the archived revisions in the cited reports. They do not measure the local 1.1.0 candidate. Its separate [variation verification](native-variation-validation.md) records compatibility, storage and callback checks.
299
+
293
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.
294
301
 
295
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.
296
303
 
297
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.
298
305
 
299
- The commands below reproduce the historical pre-pipeline performance review. Use its archived candidate source when requiring exact PCM/state equality with `7ab3110`; the current 1.0.2 renderer 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.
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.
300
307
 
301
308
  ```sh
302
309
  mkdir -p /tmp/synth-reference
@@ -379,4 +386,4 @@ The [Studio technical validation report](studio-validation.json) records the cal
379
386
 
380
387
  ## Unified transport acceptance boundaries
381
388
 
382
- The 1.0.0 contract uses one renderer, at most 32 voices and capacity for 1-4 sequences. `bus` categories and per-voice sends share one effects chain. These API changes do not establish complete multiplatform ten-minute maximum-load acceptance; the measured iPad result and Android limits are recorded separately in [physical validation](physical-validation.json) and [the performance summary](stats-performance.md). Host timing, bypass comparisons, memory accounting, actual Android/iOS builds and the remaining device/listening checks are separate evidence. Historical Studio benchmarks below predate these changes and remain historical observations. At 192 kHz, the revised effects buffers use approximately 13.1 MiB; the maximum sequence pool uses approximately 8.23 MiB before renderer voices/queues. Total native accounting must include those remaining structures and recovery peaks; the target is at most 32 MiB additional memory.
389
+ Public API 1.0.0 uses one renderer, at most 32 voices and capacity for 1-4 sequences. `bus` categories and per-voice sends share one effects chain. These API changes do not establish complete multiplatform ten-minute maximum-load acceptance; the measured iPad result and Android limits are recorded separately in [physical validation](physical-validation.json) and [the performance summary](stats-performance.md). Host timing, bypass comparisons, memory accounting, actual Android/iOS builds and the remaining device/listening checks are separate evidence. The cited Studio and unified-transport benchmarks are historical observations. In the pre-variation layout, effects buffers at 192 kHz used approximately 13.1 MiB and four maximum-size triple-bank transports used 8,627,712 bytes (8.23 MiB), excluding renderer voices and queues. The local 1.1.0 candidate grows those four transports to 9,537,152 bytes (9.10 MiB) on the measured 64-bit macOS layout, an increase of 909,440 bytes; see [variation storage measurements](native-variation-validation.md#storage-and-callback-work). These structure sizes are not a new whole-process or physical-device measurement. Total accounting must also include renderer voices/tails, command queues, effects, bridge/canonical options and recovery peaks. The 32 MiB additional-memory target remains a target, not a certified bound for the candidate.
@@ -1,4 +1,4 @@
1
- > Source snapshot: official TiSynthEngine 1.0.2 documentation from reviewed working-tree source based on `4a45f21230ffc5a8838fbd5fc108dd3ab470362a` (2026-10-29). Upstream `documentation/audio-pipeline-validation.md` SHA-256: `7359d85de63e9fe94f85477e03a22fc1531c8acaabe30333e7ff2515cc5ec3e9`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `b07c97c762be5905f132cecf33e9d60ea0c21316` (2026-11-02). Published 1.0.2; local 1.1.0 candidates; public API 1.0.0. Upstream `documentation/audio-pipeline-validation.md` SHA-256: `7359d85de63e9fe94f85477e03a22fc1531c8acaabe30333e7ff2515cc5ec3e9`.
2
2
 
3
3
  # Audio pipeline correction
4
4
 
@@ -1,4 +1,4 @@
1
- > Source snapshot: official TiSynthEngine 1.0.2 documentation from reviewed working-tree based on commit `4a45f21230ffc5a8838fbd5fc108dd3ab470362a` (2026-10-29). Upstream `documentation/examples.md` SHA-256: `48104b9d413050d439ee605c92a2456a50b42c4c80e2d7c2c05706eecc7db215`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `b07c97c762be5905f132cecf33e9d60ea0c21316` (2026-11-02). Published 1.0.2; local 1.1.0 candidates; public API 1.0.0. Upstream `documentation/examples.md` SHA-256: `637806516d2804c43427d187a37786cfcf2f0d28758762f01a570ba15194bae3`.
2
2
 
3
3
  # Examples
4
4
 
@@ -1,4 +1,4 @@
1
- > Source snapshot: official TiSynthEngine 1.0.2 documentation from the reviewed working-tree based on `4a45f21230ffc5a8838fbd5fc108dd3ab470362a` (2026-10-29). Upstream `documentation/guide.md` SHA-256: `6241f84ba84e20c0516bf01bc0850f9625d2b41f0bef39289c056e687b21ee37`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `b07c97c762be5905f132cecf33e9d60ea0c21316` (2026-11-02). Published 1.0.2; local 1.1.0 candidates; public API 1.0.0. Upstream `documentation/guide.md` SHA-256: `4da2d85f329a49a8b81a9c671c75051b41788371b2c00990fbd499176623894d`.
2
2
 
3
3
  # Guide
4
4
 
@@ -28,12 +28,13 @@ Install TiSynthEngine, start it after layout settles, and release its native aud
28
28
  - [Read engine statistics](#read-engine-statistics)
29
29
  - [Next steps](#next-steps)
30
30
  - [Diagnose mix headroom](#diagnose-mix-headroom)
31
+ - [Local native-variation candidate](#local-native-variation-candidate)
31
32
 
32
33
  <!-- TOC-END -->
33
34
 
34
35
  ## Install the module
35
36
 
36
- Download both official packages from [Android 1.0.2](https://github.com/macCesar/TiSynthEngine/releases/tag/android-1.0.2) and [iOS 1.0.2](https://github.com/macCesar/TiSynthEngine/releases/tag/ios-1.0.2). For source builds, the matching archives are produced under `android/dist` and `ios/dist`:
37
+ Download both official packages from [Android 1.0.2](https://github.com/macCesar/TiSynthEngine/releases/tag/android-1.0.2) and [iOS 1.0.2](https://github.com/macCesar/TiSynthEngine/releases/tag/ios-1.0.2). For the published 1.0.2 revision, matching archives are produced under `android/dist` and `ios/dist`:
37
38
 
38
39
  - `ti.synthengine-android-1.0.2.zip`
39
40
  - `ti.synthengine-iphone-1.0.2.zip`
@@ -49,6 +50,8 @@ Put the archives in the Titanium project root or install them through the Titani
49
50
 
50
51
  TiSynthEngine requires Titanium SDK 13.2.0.GA or newer. Android projects must compile against API 35 or newer. The iOS module requires iOS 15 or newer.
51
52
 
53
+ These declarations select the published 1.0.2 packages. Current source builds produce unpublished local 1.1.0 candidates; use the separate [candidate setup](#local-native-variation-candidate) below when testing native attack variation.
54
+
52
55
  ## Load and verify the contract
53
56
 
54
57
  Require the module once in the code that owns audio. Check `apiVersion` before starting. Also check the [spatial limits](#detect-the-installed-feature) and [statistics methods](#read-engine-statistics) if the app needs those features. Check [required capabilities](#detect-required-capabilities) for held voices, musical transport and master processing.
@@ -143,7 +146,7 @@ Store the positive identifier returned by `playSequence`, and include it in each
143
146
  synth.setTempo({ bpm: 112 })
144
147
  var sequenceId = synth.playSequence({
145
148
  stepDuration: 0.5, swing: 0.12, bus: 'music',
146
- voice: { waveType: WAVE.SQUARE, volume: 0.55 },
149
+ voice: { waveType: contract.waveTypes.SQUARE, volume: 0.55 },
147
150
  steps: [{ notes: [60] }, { notes: [64], overrides: { pan: -0.4, panEnd: 0.4 } },
148
151
  { rest: true }, { notes: [67], duration: 1, length: 0.8 }]
149
152
  })
@@ -365,7 +368,7 @@ synth.playChord({
365
368
  synth.setTempo({ bpm: 112 })
366
369
  var sequenceId = synth.playSequence({
367
370
  stepDuration: 0.5, swing: 0.12, bus: 'music',
368
- voice: { waveType: WAVE.SQUARE, volume: 0.55 },
371
+ voice: { waveType: contract.waveTypes.SQUARE, volume: 0.55 },
369
372
  steps: [{ notes: [60] }, { notes: [64], overrides: { pan: -0.4, panEnd: 0.4 } },
370
373
  { rest: true }, { notes: [67], duration: 1, length: 0.8 }]
371
374
  })
@@ -601,3 +604,17 @@ See the [complete field contract](api.md#getstats), [measurement limits](stats-p
601
604
  The current diagnostics add `preLimiterPeakDb` (maximum signal before final limiting, in dBFS) and `maxLimiterReductionDb` (greatest limiter attenuation in dB) accumulated since reset. Unlike the approximately 200 ms window reduction field, brief peaks remain visible between UI polls. Enable measurements, reset before the scenario and keep `null`/absent fields distinct from measured zero. The [measurement example](advanced-examples.md#measure-peaks-and-limiter-reduction) shows opt-in polling and cleanup.
602
605
 
603
606
  Fixed event/input gain does not normalize continuously by active voice count. Coherent overlapping notes, effects feedback or resonance can still exceed the ceiling; the limiter then acts on the whole stereo sum. Saturation is a separate creative processor, disabled initially. Physical loudness and small-speaker bass response are not measured by these digital meters.
607
+
608
+
609
+ ## Local native-variation candidate
610
+
611
+ Current source builds unpublished local candidates at `android/dist/ti.synthengine-android-1.1.0.zip` and `ios/dist/ti.synthengine-iphone-1.1.0.zip`. Keep them separate from the published 1.0.2 packages. Install both matching candidates in the consumer app and select them explicitly:
612
+
613
+ ```xml
614
+ <modules>
615
+ <module platform="android" version="1.1.0">ti.synthengine</module>
616
+ <module platform="iphone" version="1.1.0">ti.synthengine</module>
617
+ </modules>
618
+ ```
619
+
620
+ Rebuild and reinstall the application after replacing the native modules. Both package lines retain public API `1.0.0`; it does not identify variation support. Send a `variation` dictionary only when `getDefaults().capabilities.variation === true` and `getDefaults().variationSchemaVersion === 1`. If either announcement is absent, omit the property completely, including at amount zero. See [native attack variation](native-variation.md) for accepted locations, bounded ranges and seed behavior. The 1.1.0 candidates have not been published as a release.
@@ -0,0 +1,157 @@
1
+ > Source snapshot: official TiSynthEngine documentation at commit `b07c97c762be5905f132cecf33e9d60ea0c21316` (2026-11-02). Published 1.0.2; local 1.1.0 candidates; public API 1.0.0. Upstream `documentation/instruments-and-effects.md` SHA-256: `bcd7c2191bfae2b07688c9dbe19d813fe4eb8fcc45ab38d49b29dff7bdc2981c`.
2
+
3
+ # Instruments, sound effects and complete sequences
4
+
5
+ A playable instrument and a missile sound can use the same oscillator. What makes them behave differently is the complete recipe: pitch, envelope, pitch/filter movement, layers, timing, and how the app starts and ends it. This guide explains which public API to use and how to preserve that recipe.
6
+
7
+ These are application roles built on the same native synthesizer and output chain. They are not different audio engines, quality modes, or guarantees against distortion. An instrument can be noisy or inharmonic; an effect can be a single pitched tone, a chord, or a musical phrase.
8
+
9
+ ## Choose the playback contract
10
+
11
+ | Intention | Native methods | Who ends the sound? | What to preserve |
12
+ | ---------------------------------------------------------------- | ------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- |
13
+ | Play a key until the finger lifts | `noteOn`, `updateVoice`, `noteOff` | The app releases its unique voice ID | Timbre, tuning, envelope, contact ownership |
14
+ | Fire one finite sound | `playTone` | Native duration and release | Original frequency, endpoints, duration, envelope, gain |
15
+ | Fire a simultaneous chord with shared shaping | `playChord` | Native duration and release | All pitches, per-note pan/detune, shared envelope, native chord compensation |
16
+ | Play a melody, alarm, layered effect, or repeated firing pattern | `playSequence`, optionally `updateSequence` | Native step clock; `stopSequence` cancels future attacks | Complete ordered steps, rests, lengths, overrides and independent layers |
17
+ | Add chorus, delay, reverb or creative saturation | `setMasterProcessing` | The shared processor owns its tails | Routing, sends, input gain, effect settings and output headroom |
18
+
19
+ The last row describes **audio processing effects**. Those processors are different from an authored **sound effect**, such as a laser or an explosion. A laser may use no reverb or delay at all.
20
+
21
+ All examples below assume successful startup. Use constants from the installed module and check native acceptance:
22
+
23
+ ```javascript
24
+ var synth = require('ti.synthengine')
25
+ var WAVE = synth.getDefaults().waveTypes
26
+ if (!synth.start({ maxVoices: 32, maxSequences: 4,
27
+ mixingProfile: 'conservative', audioSession: 'playback' })) {
28
+ throw new Error('Could not start TiSynthEngine')
29
+ }
30
+ synth.setVolume({ volume: 0.6 })
31
+ ```
32
+
33
+ See [API limits](api.md) and [startup and lifecycle](guide.md) for application integration. Do not call `start` on each key just to reset a voice pool. Shut the engine down when its application owner finishes.
34
+
35
+ ## 1. Playable instruments and sustained voices
36
+
37
+ An instrument recipe defines the response of a new note: waveform, filter, FM, envelope, level and positioning. The keyboard supplies its pitch and release gesture. Each simultaneous contact needs its own positive voice ID.
38
+
39
+ ```javascript
40
+ var nextVoiceId = 1
41
+ function pressKey(frequency) {
42
+ var voiceId = nextVoiceId++
43
+ var accepted = synth.noteOn({ voiceId: voiceId, frequency: frequency,
44
+ waveType: WAVE.TRIANGLE, volume: 0.16,
45
+ attack: 8, decay: 220, sustain: 0.35, release: 180,
46
+ bus: 'music' })
47
+ return accepted ? voiceId : 0
48
+ }
49
+ function releaseKey(voiceId) {
50
+ return synth.noteOff({ voiceId: voiceId })
51
+ }
52
+ ```
53
+
54
+ Store the returned ID with that contact. Sliding to another key must release the old owner and admit the new attack; do not release another finger's voice. An app-level Hold control retains selected IDs until explicitly released. If `noteOff` is rejected, keep ownership and follow a bounded retry/error policy; silently forgetting the ID can leave a stuck voice. The [held voice guide](guide.md#hold-and-edit-a-voice) covers lifecycle and compatible live edits.
55
+
56
+ `noteOn` does not accept timed-event endpoint sweeps such as `frequencyEnd`. A held voice can use its supported vibrato/LFOs and compatible `updateVoice` edits. Removing a timed sweep, replacing the original frequency with the key's pitch, and holding indefinitely changes the sound's meaning: that is a new playable timbre, not an exact audition of the original effect.
57
+
58
+ A short envelope also does not automatically make a held voice finite. With `sustain: 0`, it can become silent while still requiring release of its ID. Choose a timed event for a self-contained strike when contact ownership is unnecessary.
59
+
60
+ ### Instrument demonstrations are musical performances
61
+
62
+ A demo provides notes and articulation for the current timbre. It should not silently substitute another sound or replace the user's editable pattern. A useful finite phrase has an opening, development and a resolved ending:
63
+
64
+ - Winds need an appropriate register, separated attacks and breathing spaces.
65
+ - Strings benefit from longer notes and nearly connected lengths.
66
+ - Basses benefit from a low register, rests and a deliberate groove.
67
+ - Guitars and keyboards can use arpeggios, chord changes and varied note lengths.
68
+ - Percussion needs separate strikes and enough room for its decay and release.
69
+ - Slow pads need broad entries long enough for their attack to develop.
70
+ - Unpitched textures demonstrate density, filtering and movement; changing a noise clock is not the same as transposing a harmonic note.
71
+
72
+ Schedule such phrases with the native transport. Make looping an explicit application choice. See [sequences](api.md#playsequenceoptions) and [sound-design envelopes](sound-design.md#envelope-and-total-duration).
73
+
74
+ ## 2. Independent finite sound effects
75
+
76
+ A sound effect often communicates a specific action: launch, impact, warning, confirmation. Its original pitch is part of that action. A rising missile and a descending shot can share the same square-wave timbre while conveying different movement.
77
+
78
+ ```javascript
79
+ function fireMissile() {
80
+ return synth.playTone({ frequency: 196, frequencyEnd: 523,
81
+ duration: 350, waveType: WAVE.SQUARE,
82
+ attack: 2, decay: 0, sustain: 1, release: 50,
83
+ volume: 0.15, bus: 'effects' })
84
+ }
85
+ ```
86
+
87
+ This event completes natively. Repeated independent calls can overlap naturally. Their frequencies and endpoints remain those of the recipe; they are not implicitly transposed to the key being touched.
88
+
89
+ A keyboard adapter that keeps only the square wave and envelope, starts at C7, and omits `frequencyEnd` will sound very different. Neither result establishes an engine fault: the adapter has selected a different pitch, duration model and motion. An effect audition should execute the whole recipe. Label a separately offered playable adaptation clearly.
90
+
91
+ Use `playChord` when its contract exactly represents the intended chord. It applies its own chord compensation. Independent layers authored with distinct levels, envelopes or waves are not automatically equivalent to one chord call. Preserve those layers rather than normalizing them again or flattening them into one source.
92
+
93
+ ## 3. Complete sound sequences and layered effects
94
+
95
+ Some effects need multiple ordered attacks. Others combine simultaneous body, noise and high-frequency detail, each with a different envelope. A sustained key containing only the first layer cannot reproduce the complete event.
96
+
97
+ This native sequence defines a missile with 300 ms of pitch motion followed by 50 ms of release. It is a sequence interpretation, not a sample-equivalent conversion of the timed example above:
98
+
99
+ ```javascript
100
+ function missileSequence() {
101
+ return synth.playSequence({ timeUnit: 'ms', loop: false,
102
+ voice: { waveType: WAVE.SQUARE, attack: 2,
103
+ decay: 0, sustain: 1, release: 50, volume: 0.15,
104
+ bus: 'effects' },
105
+ steps: [{ frequencies: [196], duration: 350, length: 300,
106
+ overrides: { frequencyEnd: 523 } }] })
107
+ }
108
+ ```
109
+
110
+ `duration` on a sequence step is the interval until the next attack. `length` is how long that note runs before release starts. Both use the sequence's time unit; attack, decay and release remain milliseconds. A note can extend beyond its step and overlap later attacks. In a timed `playTone` call, `duration` includes release. These values cannot be copied interchangeably without accounting for release.
111
+
112
+ **Timed and sequence routes are not automatically interchangeable.** In the current native implementation, a `playTone` endpoint sweep progresses across the total timed duration, including release. A sequence endpoint sweep progresses across its explicit `length`, then holds the endpoint during release. Thus the timed 350 ms example and the sequence with `length: 300` do not have the same pitch trajectory, even though both finish after 350 ms and use the same endpoints. Sequence voices also use the held envelope's minimum 3 ms attack/release edges; very short or zero timed edges can differ. This matters most for short weapon/noise effects and is not corrected by merely copying the option names.
113
+
114
+ For consistent manual and repeated auditions, use the same native interpretation for both. Preserve the original attack length when changing only the repetition interval; each new attack may overlap the prior voice. Do not claim exact timed-to-sequence fidelity from field equality or validator acceptance. Preserving the exact timed renderer inside a native repetition would require native support for that rendering contract; it cannot be obtained by a JavaScript adapter that simply renames `duration` to `length`.
115
+
116
+ For a sequence-owned burst, `stopSequence` also releases emitted voices. To stop repetitions while preserving those attacks, publish a finite traversal with `loop: false`, retain its current effect steps and suppress later variant attacks as appropriate. Natural completion leaves emitted voices to their own lengths/releases. Retain the first attack in an update that can arrive before the initial callback. Handle publication rejection explicitly, with bounded control retries if necessary; those retries must not trigger notes or queue new sound requests.
117
+
118
+ Use complete step dictionaries and their required overrides. Do not identify variants by guessing from silence, remove an intentional gap, round source frequencies to MIDI integers, discard a sweep, or merge independent layers solely because they begin together. A source importer should carry explicit interpretation, variant boundaries and demo association alongside the recipe.
119
+
120
+ ### Repeated firing uses the native clock
121
+
122
+ ```javascript
123
+ function repeatShortShots(intervalMs) {
124
+ return synth.playSequence({ timeUnit: 'ms', loop: true,
125
+ voice: { waveType: WAVE.SQUARE, attack: 2,
126
+ decay: 0, sustain: 1, release: 40, volume: 0.1,
127
+ bus: 'effects' },
128
+ steps: [{ frequencies: [1380], duration: intervalMs, length: 65,
129
+ overrides: { frequencyEnd: 340 } }] })
130
+ }
131
+ ```
132
+
133
+ Validate `intervalMs` against native limits and the application's voice budget. This example has a 105 ms event (65 ms plus 40 ms release). At a shorter interval, tails overlap; at 150 ms they have room to finish. A compound multi-attack effect also needs enough interval for its complete pattern if the app promises complete repetitions.
134
+
135
+ The app can begin an independent first attack immediately and publish delayed native repetitions, or publish a complete native repeating score with its first attack at time zero. Avoid emitting the same initial attack through both `touchstart` and `click`. JavaScript polling may display transport position, but `setInterval`/`setTimeout` should not schedule individual musical attacks.
136
+
137
+ Keep every positive sequence ID. Releasing a momentary pad, disabling a fixed burst, switching presets, backgrounding the app, and pressing Stop should cancel the sequences owned by that panel. `stopSequence` releases that sequence's voices and cancels future attacks; independent timed calls already emitted are separate and finish naturally. Stop is different from global `stopAll`/shutdown panic.
138
+
139
+ A successful stop request does not mean the native slot is already reusable. Wait for the matching `sequenceend` retirement when replacing a sequence; do not poll and enqueue a backlog of missed firing requests. Keep authored variant order explicit and advance it only for accepted work. Application recipe functions are ordinary JavaScript; there is no built-in public named-recipe registry. Local 1.1.0 candidates support optional [native per-attack variation](native-variation.md), which chooses once at each admitted attack, including native loop attacks. It does not continuously resample an already sounding voice, and `updateVoice` rejects the variation dictionary. Published 1.0.2 packages reject it entirely; require both the variation capability and schema announcement before adding that property.
140
+
141
+ ## 4. Polyphony, gain and output protection
142
+
143
+ Both musical notes and effects share the voice pool and output chain. Either can distort when too many contributions sum at high gain, when filters resonate strongly, when tails overlap, or when creative saturation is enabled. A successful effect in one game does not certify arbitrary held chords, denser firing, different processing, or a device's physical speaker volume.
144
+
145
+ - `maxVoices` is a capacity, not a loudness control. Reserve for simultaneous attacks and release tails, including future overlap in a repeating score.
146
+ - `maxSequences` limits independent native tracks. Do not silently drop a layer to fit the limit.
147
+ - Check native acceptance. A positive sequence ID acknowledges publication; it does not prove every future note will find enough voices.
148
+ - Native admission can replace eligible voices under pressure. If the product promises no stealing, the application must reserve capacity across all of its sound owners and reject excess work visibly.
149
+ - Do not apply chord normalization to an already authored set of independent layers unless that gain change is intended.
150
+ - Keep headroom at voice, processor input and master stages. Preserve source gains when importing a known recipe; changing the overlap pattern still needs new validation.
151
+ - Creative `saturation` intentionally changes timbre. Final output limiting protects peaks but is not proof that the earlier mix is clean.
152
+
153
+ Use `getStats` to inspect active/tail voices, pending commands, rejected publications, priority drops and voice steals. Enable diagnostics for `preLimiterPeakDb`, `maxLimiterReductionDb`, output peaks and callback load. See [gain staging](sound-design.md#chords-polyphony-and-gain), [processing](api.md#setmasterprocessingoptions), [statistics](stats-performance.md), and [troubleshooting](troubleshooting.md). Host measurements and successful validation do not replace listening on the target speaker/headphones at the intended overlap and physical volume.
154
+
155
+ ## Verify the route you actually ship
156
+
157
+ First compare an effect against its original complete recipe, including timing and levels. Separately test its playable adaptation, if offered. For instruments, test single keys, simultaneous keys, slides, Hold, release tails and finite demos. For effects, test rapid taps, native bursts, explicit variants, layered sounds, cancellation and exhausted capacity. Distinguish publication acceptance, rendered output, physical responsiveness and listening results in the report.
@@ -0,0 +1,182 @@
1
+ > Source snapshot: official TiSynthEngine documentation at commit `b07c97c762be5905f132cecf33e9d60ea0c21316` (2026-11-02). Published 1.0.2; local 1.1.0 candidates; public API 1.0.0. Upstream `documentation/native-variation-proposal.md` SHA-256: `17b74434b0e03e5353b71c693d4ec722dede92fee8aed142003e67f60e158e27`.
2
+
3
+ <a id="native-per-attack-variation--proposal-and-implementation-plan"></a>
4
+
5
+ # Native per-attack variation: proposal and implementation plan
6
+
7
+ Historical proposal, written on November 1, 2026 before implementation. The design and unchecked plan below record that starting point, not outstanding implementation work. Current source and local 1.1.0 candidates implement the feature; use the [implemented contract](native-variation.md) and [technical verification](native-variation-validation.md) for current behavior and acceptance. Writing the original proposal did not change native sources, binaries or packages.
8
+
9
+ Implementation update (November 1, 2026): current source and local 1.1.0 candidates implement the contract on both platforms. See [the implemented contract](native-variation.md) and [verification](native-variation-validation.md). The proposal and historical feasibility findings below describe the starting point; they are not release announcements.
10
+
11
+ ## Purpose and ownership
12
+
13
+ Generate subtly different versions of the **same** sound: an anvil struck at different places, not a different object on each hit. The Engine owns random selection once per new native voice. Apps supply an authored recipe, permitted ranges and amount. Studio exposes the controls and saves those choices; Frontier and Guardian use the same public property.
14
+
15
+ The existing oscillator, anti-aliasing, ADSR, fixed event gain, chord compensation, routing, output protection and Oboe/Apple audio backends remain intact. This is an optional addition at voice creation, not a redesign of mixing or dynamics. A recipe without variation must preserve its existing output exactly. Do not revive the rejected Studio touch adapters, gesture delays, normalization or output-protector experiments.
16
+
17
+ Studio's independent catalog expansion imports Playground, Frontier and Guardian sound examples. That work can ship independently. It does not implement random selection or require this future feature. The accepted pure-sine preset formerly named Cristal is now Seno puro (`clean`); preserve its approved sound.
18
+
19
+ ## Proposed public contract
20
+
21
+ Use one optional dictionary named `variation` on `playTone`, `playChord`, `noteOn`, sequence `voice`, and step `overrides`. Unknown fields remain rejected by the closed contracts.
22
+
23
+ ```javascript
24
+ synth.playTone({
25
+ frequency: 620,
26
+ waveType: WAVE.SINE,
27
+ duration: 180,
28
+ attack: 1, decay: 65, sustain: 0.12, release: 110,
29
+ fmRatio: 2.7, fmDepth: 3.2, fmDepthEnd: 0.4,
30
+ volume: 0.30,
31
+ variation: {
32
+ amount: 0.5,
33
+ // seed: 20261101, // Optional: deterministic comparison/testing.
34
+ ranges: {
35
+ volume: [0.291, 0.30],
36
+ fmDepth: [2.944, 3.456],
37
+ pitchCents: [-12, 12]
38
+ }
39
+ }
40
+ });
41
+ ```
42
+
43
+ - Omitted `variation`: current behavior, no extra RNG calls or changed noise state.
44
+ - `amount`: finite number 0 to 1, default 1 when variation is supplied. Zero bypasses sampling and leaves authored values unchanged. The dictionary is still validated.
45
+ - `ranges`: dictionary of permitted field ranges. Empty ranges have no audible effect. Each value is exactly two finite numbers `[minimum, maximum]` with minimum ≤ maximum. Equal endpoints deliberately fix a value when amount is 1.
46
+ - `seed`: optional integer 0 to 4294967295. Zero is a valid deterministic seed, not the sentinel for automatic seeding. Omission uses the Engine's independent variation stream.
47
+ - Non-pitch ranges are **absolute native parameter values**, not multipliers. Interpolate from the authored value toward the sampled value: `authored + amount * (sampled - authored)`.
48
+ - `pitchCents` is a relative offset, initially zero. Multiply the already tuned/detuned frequency and its active sweep endpoint by `2 ** (amount * sampledCents / 1200)`.
49
+ - Each range samples a uniform distribution. No quantized three-variant selection, hidden rounding to preset choices or automatic rejection of a natural repeated value.
50
+
51
+ ### Version-one fields
52
+
53
+ | Range field | Bounds and compatibility | Identity rule |
54
+ | ------------ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
55
+ | `volume` | Current native event volume 0 to 2 | Sample before existing fixed chord compensation; no other voice is rescaled. |
56
+ | `filterHz` | 20 to active frequency ceiling; an enabled resonant filter is required | Changes starting cutoff; the authored `filterHzEnd` remains the same endpoint. |
57
+ | `filterQ` | 0.5 to 10; enabled resonant filter required | Does not change filter type. |
58
+ | `fmDepth` | 0 to 10; SINE only | Does not change `fmRatio`; an authored FM endpoint, including zero, remains unchanged. |
59
+ | `noiseRate` | Positive colored-noise range accepted by current API; NOISE only | Does not change waveform; authored noise sweep endpoint stays fixed. |
60
+ | `lowPassHz` | Enabled cutoff, 20 to active ceiling | Does not randomly switch the low-pass on/off. A disabled low-pass cannot receive this range. |
61
+ | `pulseWidth` | 0.05 to 0.95; SQUARE only | Does not change waveform; authored PWM endpoint stays fixed. |
62
+ | `pitchCents` | −1200…1200, intentional opt-in | Preserve both the starting pitch and pitch-sweep trajectory under the same multiplier. |
63
+
64
+ Native validators must use the actual existing source/range rules rather than assuming all numeric intervals are continuous. In particular, do not interpolate through an invalid bypass/cutoff gap. Pitch corners must remain within the active frequency ceiling, including an existing detune and every frequency in a chord/step. Reject a dictionary whose reachable extremes are invalid; never silently clamp the variation to conceal an invalid recipe.
65
+
66
+ Version one deliberately keeps waveform, wavetable, quantization, FM ratio, filter type, ADSR, event duration, rhythmic interval, routing and priority authored. This preserves the principal sound shape and simplifies timed-envelope correctness. Optional duration/ADSR variation is a separate future design: it must not randomly violate envelope budgets or move a sequence's rhythmic grid. The earlier discussion of varying 140 to 170 ms was an illustration, not a shipped promise.
67
+
68
+ ### Attacks, held notes, chords and sequences
69
+
70
+ 1. **Timed tone / held note:** sample once as the new voice starts. Reuse the sampled values throughout sustain and release. Playing another note, lifting another finger or repainting UI must not resample this voice.
71
+ 2. **Chord:** validate the complete call before admission. Each admitted voice gets its own deterministic draws, then the existing compensation applies. Keep atomic chord admission and existing pan/detune behavior.
72
+ 3. **Sequence:** sample when each scheduled voice actually starts in the native transport, not when JavaScript submits the score. Every new iteration gets new draws. Rests consume none. This is the main advantage over baking variations in JS step overrides.
73
+ 4. **Inheritance:** a step without `variation` inherits the prepared root voice. A step with `variation` replaces that whole dictionary. `{amount:0}` disables it for that step; partial nested merge semantics are intentionally avoided.
74
+ 5. **Updates:** `updateVoice` does not accept this attack-only dictionary. Explicit live edits retain their normal smoothing and set the requested fields without resampling other fields or restarting the envelope. `updateSequence` may change ranges/amount for future attacks; active voices keep their sampled state.
75
+ 6. **Seed stream:** a seeded tone/note call begins at attack index zero, so repeating exactly that call reproduces it. A seeded chord uses note ordinal as a further index. A seeded sequence owns an attack counter that continues across loops and normal revisions. Stop/new play restarts the seeded stream; changing the seed restarts it when the new revision applies. Re-publishing the same seed must not accidentally reset it.
76
+
77
+ ## Deterministic and realtime design
78
+
79
+ Implement one small C-compatible shared helper, provisionally `shared/synth_variation.h`, used by both platform renderers. Use an integer-defined generator/hash with golden vectors, stable field slots and explicitly defined conversion to a uniform scalar. A counter-based `(seed, attack index, chord voice ordinal, field slot)` scheme avoids dependence on JavaScript property order and on adding another optional field. Review statistical quality against this sound-design use; this is not a cryptographic RNG.
80
+
81
+ Maintain a separate random stream/counter from oscillator noise. Enabling variation in another voice must not change an existing noise waveform. Omitted/zero amount consumes no variation draws and does not disturb legacy noise state. Seed automatic streams at control/start time, never by calling an OS entropy service in the audio callback.
82
+
83
+ Parse/validate dictionaries and prepare numeric fixed-size configuration on the producer/control side. At an attack, native code uses bounded constant-time arithmetic and preallocated storage. No allocation, locks, sleeps, collection creation, JSON, JS calls, OS RNG or logging in the callback. No per-sample randomization of these parameters. Existing LFO and sweep processing continues normally after the attack.
84
+
85
+ Inspect actual packet and bank sizes before choosing storage. `SynthSequenceVoice` is copied into up to 512 steps, three prepared banks and four transports; adding a dictionary-sized object or copying large tables for variation would be wasteful. Use a bit mask plus fixed numeric fields, and measure the resulting worst-case memory instead of guessing.
86
+
87
+ ## Verified implementation seams to inspect
88
+
89
+ - `android/src/ti/synthengine/SynthContract.java`: closed timed/held/chord validation, including `validateNoteOn`.
90
+ - `android/src/ti/synthengine/SynthStudioContract.java`: sequence voice/override validation and prepared packet encoding.
91
+ - `android/src/ti/synthengine/SynthThread.java` and `TiSynthEngineModule.java`: command preparation, bridge/default metadata.
92
+ - `android/jni/SynthOboeEngine.cpp`: `Voice`, command-to-voice setup, sequence attack creation and JNI packet decoding.
93
+ - `ios/Classes/TiSynthengineContract.{h,m}`: equivalent validation and prepared sequence/command structures.
94
+ - `ios/Classes/TiSynthengineModule.m`: `startVoiceFromCommand`, sequence-created voices and defaults/capabilities.
95
+ - `shared/synth_sequence.h`: `SynthSequenceVoice`, `SynthSequenceStep`, prepared banks and attack events.
96
+ - `shared/synth_held.h`, `synth_modulation.h`, `synth_core.h`: preserve held envelope, modulation and oscillator contracts.
97
+ - Existing `tests/engine_render_host.cpp`, platform contract regression tests and actual Apple callback host tooling: extend their relevant cases rather than inventing a second renderer.
98
+
99
+ The graph was used to identify these seams. It reports partial indexing of `synth_sequence.h`; exact source is authoritative. Confirm concrete entry-point signatures against the current checkout before editing.
100
+
101
+ ## Range authoring policy
102
+
103
+ A preset should define a small subset of the eight fields, with most fields left alone. Musical instruments preserve tuning by default. Mechanical/game impacts may opt into small pitch changes. Amount 100% means the full **authored subtle range**, not an uncontrolled maximum across the API.
104
+
105
+ Starting references from the archived Frontier experiment:
106
+
107
+ - Impacts/destruction: pitch multiplier 0.95 to 1.05, about −88.8…84.5 cents. Shots: 0.97 to 1.03, about −52.7…51.2 cents. These are optional FX bounds, not instrument defaults.
108
+ - Colored-noise rate/cutoff: 88 to 112% of the anchor; FM depth 92 to 108%; Q 95 to 105%.
109
+ - Event level: 97 to 100% of anchor, deliberately never louder than that anchor.
110
+ - ADSR/timing/voice count remain unchanged.
111
+
112
+ Convert these references into absolute native ranges in each recipe. Do not bake those percentages as mandatory Engine defaults: the Engine must not guess what an anvil, glass or trumpet means. Technical range validity and recognizability are distinct; audition the material at low, midpoint and full amount.
113
+
114
+ <a id="feasibility-and-compatibility-review--november-1-2026"></a>
115
+
116
+ ## Feasibility and compatibility review: November 1, 2026
117
+
118
+ The proposed opt-in feature is feasible. It requires changes to both validators, bridge packets, command preparation and the voice-start paths, including sequence attacks. No implementation or performance acceptance is claimed by this review.
119
+
120
+ ### Compatibility requirements and consumer impact
121
+
122
+ | Consumer scenario | Required behavior |
123
+ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
124
+ | An installed app keeps its existing module package | Nothing changes; the module is bundled with that app. |
125
+ | An app rebuilds with the new package but never supplies `variation` | Existing recipe parameters, admission, envelopes, gains, processing and noise algorithm/state progression remain unchanged. No variation draws occur. |
126
+ | An app supplies valid `{amount:0}` | Validate the dictionary, then take the same rendering path as the unvaried recipe. |
127
+ | An app explicitly supplies active ranges | Only new attacks sample the permitted fields. Existing sustained voices are not resampled. |
128
+ | A newer app runs with an older package | Omit the entire `variation` key unless feature support is advertised. Old closed validators reject unknown keys, including an amount-zero dictionary. Do not retry an unsupported recipe indefinitely. |
129
+
130
+ Package version and reported API compatibility are separate concerns. Playground's `Resources/lib/synthSafe.js` requires `apiVersion === '1.0.0'`; Guardian's audio system also compares its expected API version exactly. Changing that string would make those apps reject the module or fall back even if every existing sound call remained valid. Studio's inspected source does not impose that exact-version check; Frontier's inspected app/Resources source contains no `apiVersion` check.
131
+
132
+ **Recommendation, not a published contract:** retain the existing compatibility identifier for unchanged core methods and advertise this optional extension explicitly on both platforms, for example `capabilities.variation: true` plus `variationSchemaVersion: 1`. Publish a new module package version when implementation is ready; do not replace the 1.0.2 release. If the API identifier instead changes, prepare the affected consumer checks before claiming drop-in compatibility. Current iOS metadata omits Android's capability map, so adding the variation flag must be implemented on iOS too.
133
+
134
+ Apps that do not opt in need no recipe edits. Opting in requires the new package and a support check. Studio additionally needs saved range/amount fields, schema migration, validation, serialization to every supported playback path and explicit controls. Frontier/Guardian need material-specific ranges attached to their recipes; visual variant selection remains a separate concern. No shared recipe registry or reusable sound-object API is required.
135
+
136
+ ### Native details that the implementation must preserve
137
+
138
+ 1. **Validate the effective step voice.** Root voice and step overrides already merge before validation. Validate inherited ranges against each effective waveform/filter and every effective pitch. A step that changes from sine to noise cannot silently discard inherited FM variation: replace the variation dictionary or explicitly disable it for that step. A variation-only `updateSequence` edit must not reset its seed counter or touch active voices.
139
+ 2. **Apply samples before all derived state.** Android held starts initialize `SynthLive` and may inherit compatible release-tail phase/filter state. Sequence sweeps also read a retained `sequenceShape`. Sample before building those derived values, and retain the sampled starting shape and pitch-scaled endpoint. Sampling only the initial volume/filter/frequency would allow the next live/sweep update to overwrite it. Apply the equivalent rule on iOS.
140
+ 3. **Define admission and draw order.** Use a fixed-width attack counter and stable field slots. Advance an event counter only after that attack is admitted; rejected events and rests consume no draws. All notes in an admitted chord share that event index and use their note ordinal for independent draws. Automatic streams must distinguish successive one-shot calls. Explicitly seeded one-shot calls restart at index zero, so repeating the same seeded call intentionally repeats its sampled parameters. Specify counter wrap in the shared helper.
141
+ 4. **Do not confuse parameter determinism with noise PCM.** An explicit variation seed reproduces selected parameter values. Existing oscillator noise is separate: Android currently seeds a voice from its address/sample index; iOS uses time/slot state. A variation seed does not promise identical noise samples, complete PCM or cross-platform output. Compare absent/zero variation against the old renderer using the same controlled noise initialization in the test harness; preserve production noise behavior. Do not change the noise generator to make this test pass.
142
+ 5. **Preserve waveform meaning.** Pitch variation is useful for tuned sources; ordinary oscillator pitch does not transpose a NOISE source. Author noise effects with clock/filter variation. Keep waveform, ADSR, duration, timing, routing, priority and FM ratio outside V1, as already specified. In particular, authoring FM ranges from an initially zero depth must still satisfy the effective FM-ratio/source rules.
143
+ 6. **Measure storage and callback cost.** A 64-bit macOS host `sizeof` probe of the unchanged header measured `SynthSequenceVoice=824`, `SynthSequenceStep=1400`, one config `718,920` and one triple-bank transport `2,156,928` bytes. Four such transports total `8,627,712` bytes, excluding renderer voices, command queues and other state. A naive candidate containing amount, sixteen double endpoints, mask and seed flags is 152 bytes; adding it to every step of every bank of four transports would add 933,888 bytes before other copies. These are baseline/layout measurements, not an implemented storage design or a device benchmark. Prefer a compact representation and consider sharing repeated effective configurations within an immutable prepared bank if measurement justifies it.
144
+
145
+ ### Acceptance before consumer rollout
146
+
147
+ The compatibility claim is a release gate, not a conclusion established by this proposal. Tests must cover omitted and amount-zero variation through timed tones, chords, held notes and native sequences on both platforms, including existing profiles, release overlap and deterministic noise fixtures. Assert unchanged authored configs, stable sustained voices, no extra noise-state consumption, atomic chord admission, unchanged gain compensation, correct sweep starts/endpoints and newly sampled loop attacks. Measure dense publication memory and callback load; retain physical listening as a separate acceptance step.
148
+
149
+ The review ran only source inspection and the isolated host layout probe. No renderer, bridge, package, existing app sound or physical device was changed.
150
+
151
+ ## Implementation plan and gates
152
+
153
+ - [ ] **Contract first:** add paired Android/iOS rejection/acceptance tests for omitted, zero, minimum/midpoint/maximum amount, unknown keys, malformed/inverted/nonfinite ranges, incompatible sources, bypass gaps, invalid seeds and pitch corners. Watch new-feature tests fail before adding support.
154
+ - [ ] **Shared sampling helper:** implement fixed-size config and deterministic math; test golden vectors, field ordering, seed zero, repeatability, independence from noise RNG, bounded outputs and no mutation of authored configurations.
155
+ - [ ] **Both renderers in one work cycle:** add strict bridge/prepared fields, initialize them on every path and sample at actual voice creation, including native sequence retriggers. Review timed/chord/held/sequence paths separately. No platform-only completion.
156
+ - [ ] **Native behavior tests:** compare absent/zero-variation PCM to the prior renderer for every waveform/profile/path with controlled identical noise initialization in the harness; use representative fixed seeds to compare platform selections and rendered outcomes. Confirm held notes stay stable when unrelated voices start/release; confirm eight/32-note chords and looping sequence attacks advance the stream correctly.
157
+ - [ ] **Lifecycle and edits:** stop/panic/restart, failed admission, voice stealing, capacity saturation, stale sequence revisions, seed changes, 512 steps/four transports, varying override source types, updates while a note sustains. Verify no partially admitted chord and no stale uninitialized variation mask.
158
+ - [ ] **Performance/memory:** measure callback work for 1/8/16/32 voices and four active sequences, max bank memory and publication allocation behavior. Absent/disabled paths must remain effectively unchanged. Never reinterpret a host benchmark as physical latency/listening acceptance.
159
+ - [ ] **Metadata/docs/example:** advertise support on both platforms in `getDefaults()` capabilities/limits, document fields/defaults/seed/loop behavior and add a clear example. Review exact API-version checks in consumer apps before selecting the feature's development/API version. Do not silently publish 1.0.2 binaries with an undocumented new contract.
160
+ - [ ] **Consumer integration:** once the native packages exist, Studio forwards the property from its switch/amount/preset bounds; Frontier and Guardian supply their ranges. Remove app-side samplers, preserve visual variant selection, stable IDs and saved edits. The catalog remains usable when the optional native property is absent.
161
+ - [ ] **Physical acceptance:** same-anvil identity, non-repeating nearby strikes, tuned instrument chords, stable held notes, complete finite effects, fresh sequence-loop variations and representative headphones/speakers on Android/iOS. Record listening separately from validator/PCM evidence.
162
+
163
+ Do not broaden to the entire module suite automatically. Start with the new shared helper and affected contract/render tests. Broaden when a shared struct/packet change makes existing serialization/lifecycle regressions relevant, and explain that concrete reason. Run release-required checks only if an actual release is requested.
164
+
165
+ ## References and workspace state at proposal time
166
+
167
+ - Current base: TiSynthEngine 1.0.2 packages, public API 1.0.0. Engine checkout was `main` ahead of `origin/main` by one preexisting documentation commit before this proposal; do not discard it.
168
+ - Engine mixing/output/oscillator fixes are valued by César. He initially froze the Engine during the unrelated Cristal investigation, then authorized this specific optional variation feature and requested the proposal in the Engine repository.
169
+ - Native implementation is **not started** by this document. No release, tag, force push, consumer installation or physical-device operation is authorized merely by the plan.
170
+ - Complete Studio JS prototype (archived, removed from production): `/Users/cesar/Developer/validation-evidence/TiSynthEngine/native-variation-proposal-2026-11-01/studio-prototype/README.md`.
171
+ - Complete Frontier JS prototype/range tests (archived, removed from production): `/Users/cesar/Developer/validation-evidence/TiSynthEngine/native-variation-proposal-2026-11-01/frontier-prototype/README.md`.
172
+ - Frontier prototype proved 271 zero-amount recipe results identical to HEAD and exercised 162 generator/action cases ×32 seeds. Studio prototype checked per-attack ownership and sampled-sequence limitations. These are reference experiments, **not native implementation evidence**. They sampled some fields differently from the proposed native V1 contract; do not copy them wholesale.
173
+ - Studio ongoing catalog: `/Users/cesar/Developer/Apps/TiSynth-Studio/documentation/sound-library.json`; generator `tools/import-sound-examples.js`. Source files are snapshotted at development time; the shipped app must not require sibling source projects.
174
+
175
+ ### Historical implementation request
176
+
177
+ > Read AGENTS.md, docs/project/status.md and documentation/native-variation-proposal.md. Implement the optional native per-attack variation contract on Android and iOS, preserving absent/disabled behavior and existing DSP/mixing. Start with the scoped tests and verify the sequence-loop/held-note/seed guarantees. Treat the document as a proposal whose contradictions should be resolved explicitly; do not publish a release or install on devices without the corresponding instruction. Integrate consumer controls after the native contract and packages are verified.
178
+
179
+
180
+ ## Deferred API-model question (separate scope)
181
+
182
+ César raised whether a recipe should be a registered reusable sound object with a method such as `clickSeco.play()`, instead of a reusable options dictionary submitted on every call. He explicitly asked to defer this observation and continue the current catalog/proposal work. Record it for a later API-design discussion; it is not authorization to add a sound registry, cache or another playback wrapper to this feature. Do not expand native variation into a preset-object lifecycle project.
@@ -0,0 +1,63 @@
1
+ > Source snapshot: official TiSynthEngine documentation at commit `b07c97c762be5905f132cecf33e9d60ea0c21316` (2026-11-02). Published 1.0.2; local 1.1.0 candidates; public API 1.0.0. Upstream `documentation/native-variation-validation.md` SHA-256: `df18826824984cb440d7412b547663ba6b8a7152bc347508114d7af982c90e68`.
2
+
3
+ # Native variation: technical verification
4
+
5
+ Initial host and build verification on November 1, 2026, against Engine base `5a328ad`. Native sources build local 1.1.0 Android/iOS candidates; public API remains 1.0.0. The measurements below describe that technical stage. Later consumer installation and listening observations are recorded separately in the [follow-up](#consumer-installation-and-listening-follow-up); they do not turn the host measurements into physical-device benchmarks. No 1.1.0 release is announced by this report.
6
+
7
+ ## Contract and rendering
8
+
9
+ - Shared C helper: a known SplitMix64 vector, independent attack/ordinal/field coordinates, bounded sampling, repeatability, no mutation of authored configuration, amount-zero bypass and schema-slot independence.
10
+ - The paired corpus plus nonfinite probes exercises 156 cases on each production validator: tones, chords, held starts, sequence voices/overrides, source restrictions, malformed/inverted ranges, booleans, seeds including zero/uint32 maximum, amount endpoints, tuning/detune/chord/sweep pitch corners and zero-depth FM starts. `updateVoice` rejects variation.
11
+ - Actual Android renderer and extracted actual Apple callback hosts cover deterministic parameters, held stability, independent chord ordinals, loop attacks, rests, rejected admission, revision continuity and changed seeds. A focused shared regression reproduced the structural-edit counter reset before its fix, then verified inherited-rest insertion, explicit root-seed change, appended explicit step seed zero and reordered declarations.
12
+ - Absent/amount-zero PCM matches within the new renderer across six waveforms, four mixing profiles, held/chord/sequence paths and release. Noise initialization is controlled by existing host fixtures; production noise generation is unchanged.
13
+ - Android's pre-feature and candidate `engine_render` fixture output, including its PCM hashes, matches exactly.
14
+ - Apple pre-feature versus candidate PCM is byte-identical across the original 120 lifecycle/effects/transport scenarios: 29,692,526 bytes. The comparator's `--pcm-only` mode excludes internal structure snapshots because prepared sequence layouts intentionally changed.
15
+ - An additional Apple compatibility driver compares the pre-feature renderer against both candidate absent and amount-zero modes across six waveforms, four profiles and four paths (timed, held, chord, native sequence), including overlap/release: 2,359,296 bytes match exactly in each comparison.
16
+ - Existing native renderer, sequence-publication, retrigger, bypass, startup, contract and canonical-state memory checks accompany the focused variation cases. Existing tests with handcrafted packets now use the new internal header/step widths; the public score format is unchanged.
17
+
18
+ Commands: `python3 tests/run_variation_contracts.py`, the two `variation_*_regression.c` executables, `python3 tests/run_native_hosts.py variation_engine`, and `python3 tools/build_ios_render_host.py --output /tmp/variation-ios --driver tests/variation_render_ios.m`. The compatibility driver is `tests/variation_compatibility_ios.m`, built once against the Git base extracted to an external temporary directory and once against the candidate. No checkout/HEAD changes are needed.
19
+
20
+ ## Storage and callback work
21
+
22
+ 64-bit macOS layouts:
23
+
24
+ | Structure | Base bytes | Candidate bytes |
25
+ | ---------------------- | ---------: | --------------: |
26
+ | Sequence voice | 824 | 968 |
27
+ | Sequence step | 1,400 | 1,544 |
28
+ | Prepared configuration | 718,920 | 794,704 |
29
+ | Triple-bank transport | 2,156,928 | 2,384,288 |
30
+ | Four transports | 8,627,712 | 9,537,152 |
31
+
32
+ The four-bank-owner total grows by 909,440 bytes, including prepared explicit-seed identities. This excludes renderer voices/tails, command queues, effect rings, bridge arrays and canonical Java/Objective-C options. A variation configuration itself is 144 bytes. No dictionary or variable-size object reaches the callback. Explicit step seed declarations are sorted during preparation; application compares prepared root identity and seed declarations without sorting or allocation.
33
+
34
+ The callback measurements below run four native transports at 48 kHz, 256 frames/block, 1,000 warmup and 1,000 measured blocks. Voice counts are distributed over the four transports; unused transports contain rests. Full-amount three-field variation is selected per admitted attack. Measurements are thread CPU time divided by rendered block budget, on the macOS hosts, without opening audio hardware.
35
+
36
+ | Target voices | Android variation off | Android variation on | Apple variation off | Apple variation on |
37
+ | ------------- | --------------------: | -------------------: | ------------------: | -----------------: |
38
+ | 1 | 0.49% | 0.41% | 0.48% | 0.50% |
39
+ | 8 | 1.43% | 1.41% | 1.85% | 1.78% |
40
+ | 16 | 3.01% | 2.59% | 3.07% | 3.08% |
41
+ | 32 | 6.48% | 4.80% | 5.53% | 6.04% |
42
+
43
+ Run-to-run/cache/scheduling variation is visible; these single samples do not estimate a causal speedup or physical overhead bound. Other app builds were running during parts of this session. Wall time is retained in the external logs, separately from thread CPU time. At 32 voices, enabled mean CPU work is approximately 0.256 ms/Android callback and 0.322 ms/Apple callback on this host, with a 5.333 ms block budget.
44
+
45
+ A 512-step publication check (100 edits, 32 pitches/step) measured Android JNI/native producer CPU p95 0.335 ms and Apple Foundation producer CPU p95 10.609 ms. These execute outside the audio callback and exclude Android Java preparation. The canonical Java memory regression verifies detached input, immutable reuse, rejected-edit isolation and release of accepted bridge packets. None of these results represents phone memory pressure, thermal stability, underruns or latency.
46
+
47
+ ## Packages and consumer acceptance
48
+
49
+ Both module builds passed with `ti build -p android/ios --build-only`. Native-build archive SHA-256 before the final documentation-only refresh (the external packaging proof records the refreshed archives and proves native binary members unchanged):
50
+
51
+ - Android: `4fc5cb31ecb16a0d3d8acc9599f87f71e322778f92073fa0042e49b24fcfbacb`.
52
+ - iOS: `71b28d2473a7b56d7e966addad636037b985c1eb5bffa9f8a2e0737efe3ce2b7`.
53
+
54
+ Consumer source tests, installed candidate-file parity and platform build evidence are recorded in each app's validation/session notes. The official 1.0.2 directories/archives are retained. Capability-gated fallback and authored range gains are tested independently from listening. Studio's personal presets, A/B, disabled/zero paths, layer ownership and pattern restoration have their own tests; module validation does not replace those checks.
55
+
56
+ Technical hosts establish parameter/transport/rendering behavior within their stated scope. Physical real-time acceptance and comparative listening at low/mid/full amount remain separate and require free authorized devices.
57
+
58
+
59
+ ## Consumer installation and listening follow-up
60
+
61
+ The current Studio, Frontier and Guardian builds using module 1.1.0 were installed over the existing apps on the OPPO CPH2639 and physical iPad (9th generation), preserving app data. Signed physical iOS builds succeeded with the projects' selected SDKs; these device builds do not require the missing simulator runtime.
62
+
63
+ César reported that Studio's variations and control edits sound good, and specifically accepted Frontier's Órbita Neón / Roca Frágil variation. The device and output route were not specified in that feedback. This records those observations without extending them to all effects, both platforms or sustained maximum-load performance.