@maccesar/titools 5.2.3 → 5.2.5

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.
Files changed (26) hide show
  1. package/README.md +96 -87
  2. package/docs/examples.md +10 -3
  3. package/package.json +2 -2
  4. package/skills/ti-synthengine/SKILL.md +50 -18
  5. package/skills/ti-synthengine/assets/example/README.md +19 -6
  6. package/skills/ti-synthengine/assets/example/app.js +69 -12
  7. package/skills/ti-synthengine/assets/index.d.ts +106 -0
  8. package/skills/ti-synthengine/references/advanced-examples.md +34 -9
  9. package/skills/ti-synthengine/references/api.md +327 -246
  10. package/skills/ti-synthengine/references/audio-engine.md +28 -14
  11. package/skills/ti-synthengine/references/audio-pipeline-validation.md +1 -1
  12. package/skills/ti-synthengine/references/examples.md +5 -5
  13. package/skills/ti-synthengine/references/guide.md +40 -21
  14. package/skills/ti-synthengine/references/instruments-and-effects.md +164 -0
  15. package/skills/ti-synthengine/references/native-tone-repetition-validation.md +59 -0
  16. package/skills/ti-synthengine/references/native-variation-proposal.md +182 -0
  17. package/skills/ti-synthengine/references/native-variation-validation.md +63 -0
  18. package/skills/ti-synthengine/references/native-variation.md +285 -0
  19. package/skills/ti-synthengine/references/recipes-interaction.md +3 -1
  20. package/skills/ti-synthengine/references/recipes.md +3 -2
  21. package/skills/ti-synthengine/references/recommendations.md +5 -5
  22. package/skills/ti-synthengine/references/sound-design.md +18 -16
  23. package/skills/ti-synthengine/references/source-notes.md +54 -37
  24. package/skills/ti-synthengine/references/source-snapshot.json +188 -144
  25. package/skills/ti-synthengine/references/stats-performance.md +1 -1
  26. package/skills/ti-synthengine/references/troubleshooting.md +43 -35
@@ -7,14 +7,15 @@ var sequenceOwner = require('lib/sequenceOwner')(Synth, {
7
7
  function publishSequence(options) { return sequenceOwner.submit(options) }
8
8
 
9
9
  var defaults = Synth.getDefaults()
10
- if (!defaults || defaults.apiVersion !== '1.0.0') {
11
- throw new Error('TiSynthEngine 1.0.0 is required')
10
+ if (!defaults || defaults.apiVersion !== '1.1.0') {
11
+ throw new Error('TiSynthEngine 1.1.0 is required')
12
12
  }
13
13
 
14
14
  if (!defaults.limits || !defaults.limits.spatialX || !defaults.limits.spatialY
15
15
  || typeof Synth.getStats !== 'function' || typeof Synth.resetStats !== 'function'
16
- || typeof Synth.setStatsEnabled !== 'function') {
17
- throw new Error('Install the current TiSynthEngine 1.0.0 packages, then rebuild and reinstall this app')
16
+ || typeof Synth.enableStats !== 'function' || typeof Synth.disableStats !== 'function'
17
+ || typeof Synth.repeatTone !== 'function' || typeof Synth.updateTone !== 'function' || typeof Synth.stopTone !== 'function') {
18
+ throw new Error('Install the current TiSynthEngine 1.1.0 packages, then rebuild and reinstall this app')
18
19
  }
19
20
 
20
21
  var WAVE = defaults.waveTypes
@@ -23,6 +24,7 @@ var engineRunning = false
23
24
  var startTimer = null
24
25
  var statsTimer = null
25
26
  var diagnosticsEnabled = false
27
+ var repeatingToneId = 0
26
28
  var selectedSession = 'playback'
27
29
  var masterVolume = 1
28
30
  var binauralEnabled = true
@@ -200,7 +202,7 @@ function makeHero() {
200
202
  height: Ti.UI.SIZE,
201
203
  top: dp(16)
202
204
  })
203
- badgeRow.add(makeBadge('API 1.0.0', COLORS.indigo))
205
+ badgeRow.add(makeBadge('API 1.1.0', COLORS.indigo))
204
206
  badgeRow.add(makeBadge('ANDROID + iOS', COLORS.surfaceMuted))
205
207
  hero.add(badgeRow)
206
208
  hero.add(statusPill)
@@ -399,6 +401,40 @@ waveNames.forEach(function (name, index) {
399
401
  waveSection.add(Ti.UI.createView({ height: dp(12) }))
400
402
  scrollView.add(waveSection)
401
403
 
404
+ // A repeated finite recipe uses its native owner ID; there is no attack timer.
405
+ function stopRepeatingTone() {
406
+ if (!repeatingToneId) return true
407
+ if (!Synth.stopTone({ toneId: repeatingToneId })) return false
408
+ repeatingToneId = 0
409
+ return true
410
+ }
411
+ var repeatSection = makeSection('Finite-tone repetition',
412
+ 'The same complete 105 ms shot repeats every 200 ms. Edit its future attacks; stop lets current shots finish.',
413
+ COLORS.teal)
414
+ repeatSection.add(makeAction({
415
+ title: 'START REPEATING', color: COLORS.teal, pressedColor: COLORS.tealPressed,
416
+ onPress: function () {
417
+ if (repeatingToneId) return true
418
+ var id = Synth.repeatTone({ frequency: 1380, frequencyEnd: 340,
419
+ duration: 105, attack: 2, release: 40, waveType: WAVE.SQUARE,
420
+ volume: 0.1, intervalMs: 200 })
421
+ if (!Number.isInteger(id) || id <= 0) return false
422
+ repeatingToneId = id
423
+ return true
424
+ }
425
+ }))
426
+ repeatSection.add(makeAction({
427
+ title: 'EDIT REPEATING', color: COLORS.teal, pressedColor: COLORS.tealPressed,
428
+ onPress: function () {
429
+ return !!repeatingToneId && Synth.updateTone({ toneId: repeatingToneId, intervalMs: 150, volume: 0.08 })
430
+ }
431
+ }))
432
+ repeatSection.add(makeAction({
433
+ title: 'STOP REPEATING', color: COLORS.surfaceMuted, pressedColor: COLORS.surfaceRaised,
434
+ onPress: stopRepeatingTone
435
+ }))
436
+ scrollView.add(repeatSection)
437
+
402
438
  // ---------------------------------------------------------------------------
403
439
  // 2. Note names
404
440
  // ---------------------------------------------------------------------------
@@ -835,6 +871,26 @@ expressiveSection.add(makeAction({
835
871
  }
836
872
  }))
837
873
 
874
+ // Every press is a fresh native attack. Older packages receive the authored recipe.
875
+ expressiveSection.add(makeAction({
876
+ title: 'ATTACK VARIATION\nMetal strike · native ranges',
877
+ color: COLORS.teal,
878
+ pressedColor: COLORS.tealPressed,
879
+ status: 'Metal strike · a new native selection on each attack',
880
+ onPress: function () {
881
+ var options = { frequency: 620, duration: 180, attack: 1, decay: 65,
882
+ sustain: 0.12, release: 110, waveType: WAVE.SINE,
883
+ fmRatio: 2.7, fmDepth: 3.2, fmDepthEnd: 0.4, volume: 0.3 }
884
+ if (defaults.capabilities && defaults.capabilities.variation === true
885
+ && defaults.variationSchemaVersion === 1) {
886
+ options.variation = { amount: 1, ranges: {
887
+ volume: [0.291, 0.3], fmDepth: [2.944, 3.456], pitchCents: [-12, 12]
888
+ } }
889
+ }
890
+ return Synth.playTone(options)
891
+ }
892
+ }))
893
+
838
894
  var patternEndLabel = makeText('sequenceend: no pattern yet', {
839
895
  top: dp(8),
840
896
  left: dp(16),
@@ -979,7 +1035,7 @@ var sessionLabel = makeText('Session: playback', { left: dp(16), right: dp(16),
979
1035
  color: COLORS.blueDark, pressedColor: COLORS.blue,
980
1036
  accessibilityHint: 'Restarts the engine with this session policy',
981
1037
  onPress: function () {
982
- sequenceOwner.reset(); Synth.shutdown()
1038
+ repeatingToneId = 0; sequenceOwner.reset(); Synth.shutdown()
983
1039
  engineRunning = false
984
1040
  selectedSession = session
985
1041
  patternEndLabel.text = 'sequenceend: cancelled by shutdown (no event)'
@@ -1026,7 +1082,7 @@ statsSection.add(makeAction({
1026
1082
  title: 'TOGGLE DIAGNOSTICS', color: COLORS.orange, pressedColor: COLORS.orangePressed,
1027
1083
  accessibilityHint: 'Enables or disables timing and signal measurements',
1028
1084
  onPress: function () {
1029
- if (!Synth.setStatsEnabled(!diagnosticsEnabled)) return false
1085
+ if (!(diagnosticsEnabled ? Synth.disableStats() : Synth.enableStats())) return false
1030
1086
  diagnosticsEnabled = !diagnosticsEnabled
1031
1087
  stopStatsPolling()
1032
1088
  if (diagnosticsEnabled) statsTimer = setInterval(refreshStats, 200)
@@ -1083,10 +1139,10 @@ boostRow.add(makeGridAction({
1083
1139
  title: 'STOP ALL\nRelease active voices',
1084
1140
  color: COLORS.redDark,
1085
1141
  pressedColor: COLORS.red,
1086
- status: 'Active voices fading; pending pattern steps remain',
1087
- accessibilityHint: 'Fades current voices without cancelling future pattern steps',
1142
+ status: 'Future attacks cancelled; active voices fading',
1143
+ accessibilityHint: 'Cancels future attacks and fades current voices',
1088
1144
  onPress: function () {
1089
- sequenceOwner.stop(); Synth.stopAll()
1145
+ repeatingToneId = 0; sequenceOwner.stop(); Synth.stopAll()
1090
1146
  }
1091
1147
  }))
1092
1148
  controlSection.add(boostRow)
@@ -1150,8 +1206,9 @@ function hideAndroidActionBar() {
1150
1206
  }
1151
1207
 
1152
1208
  function cleanup() {
1209
+ if (!stopRepeatingTone()) Ti.API.warn('Repeating tone stop rejected; shutting down owner')
1153
1210
  stopStatsPolling()
1154
- Synth.setStatsEnabled(false)
1211
+ Synth.disableStats()
1155
1212
  diagnosticsEnabled = false
1156
1213
  Synth.removeEventListener('sequenceend', onSequenceEnd)
1157
1214
  win.removeEventListener('postlayout', onFirstPostLayout)
@@ -1164,7 +1221,7 @@ function cleanup() {
1164
1221
  startTimer = null
1165
1222
  }
1166
1223
  if (engineRunning) {
1167
- sequenceOwner.reset(); Synth.shutdown()
1224
+ repeatingToneId = 0; sequenceOwner.reset(); Synth.shutdown()
1168
1225
  engineRunning = false
1169
1226
  }
1170
1227
  }
@@ -0,0 +1,106 @@
1
+ /** TiSynthEngine local 1.1.0 candidate, public API 1.1.0. */
2
+ declare namespace synth {
3
+ type Bus = 'music' | 'effects' | 'ui' | 'ambience';
4
+ type FilterType = 'none' | 'lowpass' | 'highpass' | 'bandpass';
5
+ type Range = [number, number];
6
+ interface Variation {
7
+ amount?: number;
8
+ seed?: number;
9
+ ranges?: Partial<Record<'volume' | 'filterHz' | 'filterQ' | 'fmDepth' |
10
+ 'noiseRate' | 'lowPassHz' | 'pulseWidth' | 'pitchCents', Range>>;
11
+ }
12
+ interface VoiceOptions {
13
+ attack?: number; decay?: number; sustain?: number; release?: number;
14
+ volume?: number; waveType?: number; pan?: number;
15
+ lfoFreq?: number; lfoDepth?: number; noiseRate?: number; lowPassHz?: number;
16
+ pulseWidth?: number; waveTable?: number[]; bitDepth?: number;
17
+ filterType?: FilterType; filterHz?: number; filterQ?: number;
18
+ fmRatio?: number; fmDepth?: number; spatialX?: number; spatialY?: number;
19
+ bus?: Bus; effectSend?: number; priority?: number; variation?: Variation;
20
+ }
21
+ type Pitch = { note?: string; frequency?: never } | { frequency?: number; note?: never };
22
+ interface FiniteOptions extends VoiceOptions {
23
+ duration?: number; panEnd?: number; frequencyEnd?: number;
24
+ noiseRateEnd?: number; pulseWidthEnd?: number; filterHzEnd?: number;
25
+ fmDepthEnd?: number; detune?: number;
26
+ }
27
+ type ToneOptions = FiniteOptions & Pitch;
28
+ type RepeatingToneOptions = ToneOptions & { intervalMs: number };
29
+ /** Partial changes merge with the latest accepted recipe; never retriggers immediately. */
30
+ type ToneUpdate = Partial<FiniteOptions> & Pitch & { toneId: number; intervalMs?: number };
31
+ interface Modulation {
32
+ target: 'filterHz' | 'pulseWidth' | 'fmDepth';
33
+ wave?: 'sine' | 'triangle'; rate?: number; sync?: string; depth?: number;
34
+ }
35
+ type NoteOptions = VoiceOptions & Pitch & { voiceId: number; detune?: number; modulations?: Modulation[] };
36
+ type NoteUpdate = Pick<VoiceOptions, 'volume' | 'pan' | 'lfoFreq' | 'lfoDepth' |
37
+ 'noiseRate' | 'lowPassHz' | 'pulseWidth' | 'filterHz' | 'filterQ' |
38
+ 'fmRatio' | 'fmDepth' | 'spatialX' | 'spatialY' | 'effectSend'> & Pitch &
39
+ { voiceId: number; modulations?: Modulation[] };
40
+ type ChordOptions = VoiceOptions & { duration?: number; detune?: number;
41
+ detunes?: number[]; pans?: number[]; panSpread?: number } &
42
+ ({ notes: string[]; frequencies?: never } | { frequencies: number[]; notes?: never });
43
+ interface StartOptions {
44
+ maxVoices?: number; maxSequences?: number;
45
+ mixingProfile?: 'conservative' | 'raw' | 'balanced' | 'speakerSafe';
46
+ audioSession?: 'playback' | 'ambient';
47
+ }
48
+ interface Defaults {
49
+ apiVersion: '1.1.0';
50
+ capabilities: { variation: true; toneRepetition: true; [key: string]: boolean };
51
+ variationSchemaVersion: 1;
52
+ limits: Record<string, { min: number; max: number }>;
53
+ defaults: Record<string, unknown>;
54
+ waveTypes: Record<'SINE' | 'SQUARE' | 'SAWTOOTH' | 'TRIANGLE' | 'NOISE' | 'WAVETABLE', number>;
55
+ mixingProfiles: string[]; audioSessions: string[]; buses: Bus[]; filterTypes: FilterType[];
56
+ }
57
+ interface SequenceVoice extends Omit<FiniteOptions, 'duration' | 'detune'> { modulations?: Modulation[] }
58
+ type SequenceStep = { duration?: number; length?: number; gate?: number;
59
+ overrides?: SequenceVoice; bus?: Bus; effectSend?: number; priority?: number } &
60
+ ({ notes: number[]; frequencies?: never; rest?: never } |
61
+ { frequencies: number[]; notes?: never; rest?: never } |
62
+ { rest: true; notes?: never; frequencies?: never });
63
+ interface SequenceOptions {
64
+ timeUnit?: 'beats' | 'ms'; stepDuration?: number; gate?: number; loop?: boolean;
65
+ swing?: number; mode?: 'normal' | 'up' | 'down' | 'updown'; tuning?: number;
66
+ detune?: number; voice?: SequenceVoice; steps: SequenceStep[];
67
+ bus?: Bus; effectSend?: number; priority?: number;
68
+ }
69
+ interface SequenceState {
70
+ sequenceId: number; playing: boolean; step: number; beat: number;
71
+ position: number; bpm: number; revision: number;
72
+ }
73
+ function getDefaults(): Defaults;
74
+ function start(options?: StartOptions): boolean;
75
+ /** One finite attack. intervalMs is rejected; use repeatTone for repetition. */
76
+ function playTone(options: ToneOptions & { intervalMs?: never }): boolean;
77
+ /** Integer 1-60000 ms: positive module-owned ID, or 0 on rejection. */
78
+ function repeatTone(options: RepeatingToneOptions): number;
79
+ function updateTone(options: ToneUpdate): boolean;
80
+ /** Cancels future attacks at the next block; admitted attacks finish. */
81
+ function stopTone(options: { toneId: number }): boolean;
82
+ function playChord(options: ChordOptions): boolean;
83
+ function noteOn(options: NoteOptions): boolean;
84
+ function updateNote(options: NoteUpdate): boolean;
85
+ /** @deprecated Use updateNote; identical behavior and voiceId contract. */
86
+ function updateVoice(options: NoteUpdate): boolean;
87
+ function noteOff(options: { voiceId: number }): boolean;
88
+ function playSequence(options: SequenceOptions): number;
89
+ function updateSequence(options: Partial<Omit<SequenceOptions, 'timeUnit'>> & { sequenceId: number }): boolean;
90
+ function stopSequence(options: { sequenceId: number }): boolean;
91
+ function getSequenceState(options: { sequenceId: number }): SequenceState | null;
92
+ function setTempo(options: { bpm: number }): boolean;
93
+ function setVolume(options: { volume: number }): boolean;
94
+ function setMasterProcessing(options: Record<string, unknown>): boolean;
95
+ function enableStats(): boolean;
96
+ function disableStats(): boolean;
97
+ /** @deprecated Use enableStats or disableStats; identical diagnostic behavior. */
98
+ function setStatsEnabled(enabled: boolean): boolean;
99
+ function getStats(): Record<string, number | boolean | string | null>;
100
+ function resetStats(): void;
101
+ function stopAll(): void;
102
+ function shutdown(): void;
103
+ function addEventListener(name: 'sequenceend', listener: (event: { sequenceId: number; reason: 'completed' | 'stopped' }) => void): void;
104
+ function removeEventListener(name: 'sequenceend', listener: (event: { sequenceId: number; reason: 'completed' | 'stopped' }) => void): void;
105
+ }
106
+ export = synth;
@@ -1,11 +1,11 @@
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 `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/examples.md` SHA-256: `1b3be798d8f606381696d9e00cda330c11475402397e4760dcbc3041a39ef404`.
2
2
 
3
3
  ## Sustained game motor
4
4
 
5
5
  Assume one running engine owned by this service. The positive voice ID belongs to the app; do not reuse it during the session or share its counter with an independent audio owner. There is no `duration` or `*End` target on `noteOn`.
6
6
 
7
7
  ```javascript
8
- if (!['noteOn', 'noteOff', 'updateVoice'].every(function (name) {
8
+ if (!['noteOn', 'noteOff', 'updateNote'].every(function (name) {
9
9
  return typeof synth[name] === 'function'
10
10
  })) throw new Error('Held-voice APIs are required')
11
11
 
@@ -25,7 +25,7 @@ function startMotor () {
25
25
 
26
26
  function changeMotor (boosted) {
27
27
  if (motorId === null) return false
28
- return synth.updateVoice({ voiceId: motorId,
28
+ return synth.updateNote({ voiceId: motorId,
29
29
  filterHz: boosted ? 320 : 220, volume: boosted ? 0.18 : 0.15 })
30
30
  }
31
31
 
@@ -128,7 +128,7 @@ Call `setCreativeDrive(true)` only for a deliberate timbre change and check acce
128
128
  This square voice modulates cutoff and PWM independently. The original `lfoFreq`/`lfoDepth` pitch vibrato remains a separate control. All LFOs run natively; edits preserve their phases and ramp contributions over 10 ms.
129
129
 
130
130
  ```javascript
131
- if (typeof synth.noteOn !== 'function' || typeof synth.updateVoice !== 'function') {
131
+ if (typeof synth.noteOn !== 'function' || typeof synth.updateNote !== 'function') {
132
132
  throw new Error('Held-voice APIs are required')
133
133
  }
134
134
  var modulatedId = 100001 // Reserve an unused ID from the owner's counter in an app.
@@ -141,23 +141,23 @@ if (!synth.noteOn({ voiceId: modulatedId, note: 'A3', waveType: WAVE.SQUARE,
141
141
  ] })) throw new Error('Modulated voice rejected')
142
142
 
143
143
  function disableVoiceLfos () {
144
- return synth.updateVoice({ voiceId: modulatedId, modulations: [] })
144
+ return synth.updateNote({ voiceId: modulatedId, modulations: [] })
145
145
  }
146
146
  // Call disableVoiceLfos on the UI action and handle false.
147
147
  // Later release this ID with the bounded release policy above, or shut down its owner.
148
148
  ```
149
149
 
150
- Use `target: 'fmDepth'` only with `WAVE.SINE`; a filter target requires an active filter and PWM requires `WAVE.SQUARE`. Free rates are 0 to 20 Hz; synced subdivisions follow native transport BPM. Depth is normalized, with cutoff/PWM/FM contributions clamped to safe parameter ranges. `modulations: []` disables both slots; omitting the field on update preserves them. Copy the same initial `modulations` array into a sequence's `voice` or a step's `overrides` to modulate transport notes. Timed tones/chords/patterns do not accept the new array.
150
+ Use `target: 'fmDepth'` only with `WAVE.SINE`; a filter target requires an active filter and PWM requires `WAVE.SQUARE`. Free rates are 0 to 20 Hz; synced subdivisions follow native transport BPM. Depth is normalized, with cutoff/PWM/FM contributions clamped to safe parameter ranges. `modulations: []` disables both slots; omitting the field on update preserves them. Copy the same initial `modulations` array into a sequence's `voice` or a step's `overrides` to modulate transport notes. Timed tones and chords do not accept this array; native sequences accept it in `voice` or step `overrides`.
151
151
 
152
152
  ## Measure peaks and limiter reduction
153
153
 
154
154
  Enable diagnostics only while measuring and keep unavailable readings distinct from zero. A brief overload can disappear from the window meter while remaining in the accumulated maximum.
155
155
 
156
156
  ```javascript
157
- if (!['setStatsEnabled', 'getStats', 'resetStats'].every(function (name) {
157
+ if (!['enableStats', 'disableStats', 'getStats', 'resetStats'].every(function (name) {
158
158
  return typeof synth[name] === 'function'
159
159
  })) throw new Error('Statistics APIs are required')
160
- if (!synth.setStatsEnabled(true)) throw new Error('Diagnostics rejected')
160
+ if (!synth.enableStats()) throw new Error('Diagnostics rejected')
161
161
  synth.resetStats()
162
162
  var mixTimer = setInterval(function () {
163
163
  var stats = synth.getStats()
@@ -167,8 +167,33 @@ var mixTimer = setInterval(function () {
167
167
 
168
168
  function closeMixMeter () {
169
169
  clearInterval(mixTimer)
170
- if (!synth.setStatsEnabled(false)) Ti.API.warn('Diagnostics disable rejected')
170
+ if (!synth.disableStats()) Ti.API.warn('Diagnostics disable rejected')
171
171
  }
172
172
  ```
173
173
 
174
174
  Call `closeMixMeter` when its owner hides or closes. Display unavailable or unmeasured fields as unavailable. Reset deliberately before a comparison. These values measure digital peak protection, not speaker loudness, subjective quality or a guarantee that every audio deadline was met.
175
+
176
+ ## Bounded variation for repeated noise impacts
177
+
178
+ The 1.1.0 packages choose values once per admitted attack. This example keeps the noise source and envelope fixed, varies the initial noise/filter settings, and lets the authored sweep endpoints finish unchanged. Call it for actual gameplay events after normal engine startup. For periodic repetition, pass the finite recipe to `repeatTone` with `intervalMs` and retain its positive tone ID; use `stopTone` on cancellation.
179
+
180
+ ```javascript
181
+ var synth = require('ti.synthengine')
182
+ var contract = synth.getDefaults()
183
+ var impact = {
184
+ waveType: contract.waveTypes.NOISE,
185
+ frequency: 200, duration: 240, attack: 2, release: 170,
186
+ volume: 0.20, noiseRate: 5000, noiseRateEnd: 900,
187
+ filterType: 'lowpass', filterHz: 2800, filterHzEnd: 500, filterQ: 0.8
188
+ }
189
+ if (contract.capabilities && contract.capabilities.variation === true
190
+ && contract.variationSchemaVersion === 1) {
191
+ impact.variation = {
192
+ amount: 0.6,
193
+ ranges: { noiseRate: [4200, 5800], filterHz: [2400, 3200] }
194
+ }
195
+ }
196
+ synth.playTone(impact)
197
+ ```
198
+
199
+ Omit `seed` for successive native selections. An explicit seed repeats the selected parameters of standalone calls. The [variation reference](native-variation.md) includes chord gain, sustained ownership, native loop inheritance and revision examples. Its amount is linear; Studio divides the displayed percentage by 100 to obtain the same value.