@maccesar/titools 5.2.5 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +27 -37
  2. package/agents/ti-pro.md +1 -1
  3. package/commands/ti-audit.md +1 -1
  4. package/docs/examples.md +15 -13
  5. package/package.json +1 -1
  6. package/skills/ti-synthengine/SKILL.md +62 -111
  7. package/skills/ti-synthengine/assets/example/README.md +50 -59
  8. package/skills/ti-synthengine/assets/example/app.js +67 -65
  9. package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +39 -40
  10. package/skills/ti-synthengine/assets/example/lib/soundPreset.js +34 -0
  11. package/skills/ti-synthengine/assets/index.d.ts +122 -2
  12. package/skills/ti-synthengine/references/advanced-examples.md +4 -4
  13. package/skills/ti-synthengine/references/api.md +40 -18
  14. package/skills/ti-synthengine/references/audio-engine.md +15 -57
  15. package/skills/ti-synthengine/references/examples.md +63 -6
  16. package/skills/ti-synthengine/references/guide.md +61 -57
  17. package/skills/ti-synthengine/references/instruments-and-effects.md +15 -15
  18. package/skills/ti-synthengine/references/native-variation.md +53 -14
  19. package/skills/ti-synthengine/references/recipes-interaction.md +25 -20
  20. package/skills/ti-synthengine/references/recipes.md +19 -16
  21. package/skills/ti-synthengine/references/recommendations.md +12 -12
  22. package/skills/ti-synthengine/references/sound-api.md +197 -0
  23. package/skills/ti-synthengine/references/sound-design.md +7 -7
  24. package/skills/ti-synthengine/references/source-notes.md +62 -172
  25. package/skills/ti-synthengine/references/troubleshooting.md +17 -19
  26. package/skills/ti-synthengine/references/upgrade-2.0.0.md +40 -0
  27. package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
  28. package/skills/ti-synthengine/references/android-sim80-correction.json +0 -162
  29. package/skills/ti-synthengine/references/audio-pipeline-validation.md +0 -19
  30. package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
  31. package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
  32. package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
  33. package/skills/ti-synthengine/references/frontier-physical-validation.json +0 -1918
  34. package/skills/ti-synthengine/references/native-tone-repetition-validation.md +0 -59
  35. package/skills/ti-synthengine/references/native-variation-proposal.md +0 -182
  36. package/skills/ti-synthengine/references/native-variation-validation.md +0 -63
  37. package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
  38. package/skills/ti-synthengine/references/performance-review.json +0 -1146
  39. package/skills/ti-synthengine/references/physical-validation.json +0 -1457
  40. package/skills/ti-synthengine/references/source-snapshot.json +0 -542
  41. package/skills/ti-synthengine/references/stats-performance.json +0 -2992
  42. package/skills/ti-synthengine/references/stats-performance.md +0 -82
  43. package/skills/ti-synthengine/references/studio-validation.json +0 -6202
  44. package/skills/ti-synthengine/references/unified-bypass-performance.json +0 -22425
  45. package/skills/ti-synthengine/references/unified-validation.json +0 -314
@@ -1,15 +1,8 @@
1
- // One application owner, with latest-request coalescing across native retirement.
2
- // A stop is accepted before its slot becomes reusable. Only sequenceend permits
3
- // the next complete publication; JavaScript never schedules individual notes.
1
+ // Wait for native retirement before publishing the latest requested pattern.
2
+ var SoundPreset = require('./soundPreset')
4
3
  module.exports = function createSequenceOwner(synth, options) {
5
4
  options = options || {}
6
- var current = 0, pending = null, stopping = false, listening = false
7
- function listen() {
8
- if (!listening) {
9
- synth.addEventListener('sequenceend', ended)
10
- listening = true
11
- }
12
- }
5
+ var current = null, pending = null, stopping = false, recipe = null, listener = null
13
6
  function rejected(method, payload) {
14
7
  if (options.rejected) options.rejected(method, payload)
15
8
  return false
@@ -18,32 +11,40 @@ module.exports = function createSequenceOwner(synth, options) {
18
11
  if (!pending) return true
19
12
  var payload = pending
20
13
  pending = null
21
- var id = synth.playSequence(payload)
22
- if (!Number.isInteger(id) || id <= 0) return rejected('playSequence', payload)
23
- current = id
24
- if (options.published) options.published(id, payload)
14
+ var control = synth.createSound(SoundPreset.fromRecipe('pattern', payload)).play()
15
+ if (!control) return rejected('playSequence', payload)
16
+ current = control
17
+ recipe = payload
18
+ listener = function(event) {
19
+ if (current !== control) return
20
+ control.removeEventListener('end', listener)
21
+ current = null
22
+ listener = null
23
+ stopping = false
24
+ if (pending) publish()
25
+ if (options.ended) options.ended(event)
26
+ }
27
+ control.addEventListener('end', listener)
28
+ if (options.published) options.published(control, payload)
25
29
  return true
26
30
  }
27
- function ended(event) {
28
- if (event.sequenceId !== current) return
29
- current = 0
30
- stopping = false
31
- if (pending) publish()
32
- if (options.ended) options.ended(event)
33
- }
34
31
  function requestStop() {
35
32
  if (!current || stopping) return true
36
33
  stopping = true
37
- if (synth.stopSequence({ sequenceId: current })) return true
38
- // A naturally retired ID can already have its completion queued for JS.
39
- // Its slot still needs event draining before a replacement publication.
40
- if (synth.getSequenceState({ sequenceId: current }) === null) return true
34
+ if (current.stop()) return true
35
+ // An ended pattern can have its completion queued for JavaScript.
36
+ if (current.getState() === null) return true
41
37
  stopping = false
42
- return rejected('stopSequence', { sequenceId: current })
38
+ return rejected('stopSequence', {})
39
+ }
40
+ function merged(previous, changes) {
41
+ var voice = changes.voice ? Object.assign({}, previous.voice || {}, changes.voice) : previous.voice
42
+ var next = Object.assign({}, previous, changes)
43
+ if (voice) next.voice = voice
44
+ return next
43
45
  }
44
46
  return {
45
47
  submit: function(payload) {
46
- listen()
47
48
  pending = payload
48
49
  if (!current) return publish()
49
50
  if (!requestStop()) {
@@ -54,28 +55,26 @@ module.exports = function createSequenceOwner(synth, options) {
54
55
  },
55
56
  update: function(changes) {
56
57
  if (pending) {
57
- if (changes.steps !== undefined) {
58
- pending = changes
59
- return true
60
- }
61
- var voice = changes.voice ? Object.assign({}, pending.voice || {}, changes.voice) : pending.voice
62
- pending = Object.assign({}, pending, changes)
63
- if (voice) pending.voice = voice
58
+ pending = changes.steps !== undefined ? changes : merged(pending, changes)
64
59
  return true
65
60
  }
66
61
  if (!current || stopping) return false
67
- return synth.updateSequence(Object.assign({ sequenceId: current }, changes))
62
+ var next = merged(recipe, changes)
63
+ var preset = SoundPreset.fromRecipe('pattern', next)
64
+ if (!current.update(Object.assign({}, preset.sound, preset.playback))) return false
65
+ recipe = next
66
+ return true
68
67
  },
69
68
  stop: function() { pending = null; return requestStop() },
70
69
  reset: function() {
71
70
  pending = null
72
- current = 0
71
+ if (current && listener) current.removeEventListener('end', listener)
72
+ current = null
73
+ listener = null
74
+ recipe = null
73
75
  stopping = false
74
- if (listening) {
75
- synth.removeEventListener('sequenceend', ended)
76
- listening = false
77
- }
78
76
  },
77
+ getState: function() { return current ? current.getState() : null },
79
78
  id: function() { return current },
80
79
  pending: function() { return pending !== null },
81
80
  stopping: function() { return stopping }
@@ -0,0 +1,34 @@
1
+ // Convert an editable recipe to a Sound definition; this module never plays audio.
2
+ exports.fromRecipe = function(type, recipe) {
3
+ var source = JSON.parse(JSON.stringify(recipe || {}))
4
+ var sound = type === 'pattern' ? source.voice || {} : {}
5
+ var playback = { type: type }
6
+ if (type === 'pattern') {
7
+ ;['timeUnit', 'gate', 'loop', 'swing', 'mode', 'tuning'].forEach(function(key) {
8
+ if (source[key] !== undefined) playback[key] = source[key]
9
+ })
10
+ if (sound.frequencyEnd !== undefined) {
11
+ playback.frequencyEnd = sound.frequencyEnd
12
+ delete sound.frequencyEnd
13
+ }
14
+ if (source.detune !== undefined) sound.detune = source.detune
15
+ if (source.stepDuration !== undefined) playback.interval = source.stepDuration
16
+ playback.steps = (source.steps || []).map(function(step) {
17
+ var result = {}
18
+ ;['notes', 'frequencies', 'rest', 'length', 'gate'].forEach(function(key) {
19
+ if (step[key] !== undefined) result[key] = step[key]
20
+ })
21
+ if (step.duration !== undefined) result.interval = step.duration
22
+ if (step.overrides !== undefined) result.sound = step.overrides
23
+ return result
24
+ })
25
+ } else {
26
+ Object.keys(source).forEach(function(key) {
27
+ if (key === 'note') playback.notes = [source[key]]
28
+ else if (key === 'frequency') playback.frequencies = [source[key]]
29
+ else if (['notes', 'frequencies', 'frequencyEnd', 'duration'].indexOf(key) >= 0) playback[key] = source[key]
30
+ else sound[key] = source[key]
31
+ })
32
+ }
33
+ return { sound: sound, playback: playback }
34
+ }
@@ -1,4 +1,4 @@
1
- /** TiSynthEngine local 1.1.0 candidate, public API 1.1.0. */
1
+ /** TiSynthEngine 2.0.0 candidate. One module version for the entire public interface. */
2
2
  declare namespace synth {
3
3
  type Bus = 'music' | 'effects' | 'ui' | 'ambience';
4
4
  type FilterType = 'none' | 'lowpass' | 'highpass' | 'bandpass';
@@ -46,7 +46,7 @@ declare namespace synth {
46
46
  audioSession?: 'playback' | 'ambient';
47
47
  }
48
48
  interface Defaults {
49
- apiVersion: '1.1.0';
49
+ version: string;
50
50
  capabilities: { variation: true; toneRepetition: true; [key: string]: boolean };
51
51
  variationSchemaVersion: 1;
52
52
  limits: Record<string, { min: number; max: number }>;
@@ -70,27 +70,147 @@ declare namespace synth {
70
70
  sequenceId: number; playing: boolean; step: number; beat: number;
71
71
  position: number; bpm: number; revision: number;
72
72
  }
73
+ /** Numbers are MIDI notes 12..119; strings are native note names. */
74
+ type Notes = string[] | number[];
75
+ type SoundType = 'tone' | 'chord' | 'repeat' | 'pattern' | 'held';
76
+ type ModernPitch = { notes?: Notes; frequencies?: never } | { frequencies?: number[]; notes?: never };
77
+ /** Shared allowlist; each interpretation also validates source-specific compatibility. */
78
+ interface SoundProperties extends VoiceOptions {
79
+ detune?: number; panEnd?: number; noiseRateEnd?: number; pulseWidthEnd?: number;
80
+ filterHzEnd?: number; fmDepthEnd?: number; detunes?: number[]; pans?: number[];
81
+ panSpread?: number; modulations?: Modulation[];
82
+ }
83
+ type TonePlayback = ModernPitch & { type?: 'tone'; duration?: number; frequencyEnd?: number };
84
+ type ChordPlayback = ({ notes: Notes; frequencies?: never } | { frequencies: number[]; notes?: never }) & { type: 'chord'; duration?: number };
85
+ type RepeatPlayback = ModernPitch & { type: 'repeat'; duration?: number; frequencyEnd?: number; interval: number };
86
+ type HeldPlayback = ModernPitch & { type: 'held' };
87
+ type PatternStep = { interval?: number; length?: number; gate?: number;
88
+ sound?: SoundProperties & { frequencyEnd?: number } } &
89
+ ({ notes: Notes; frequencies?: never; rest?: never } |
90
+ { frequencies: number[]; notes?: never; rest?: never } |
91
+ { rest: true; notes?: never; frequencies?: never });
92
+ interface PatternPlayback {
93
+ type?: 'pattern'; timeUnit?: 'beats' | 'ms'; interval?: number; gate?: number;
94
+ loop?: boolean; swing?: number; mode?: 'normal' | 'up' | 'down' | 'updown';
95
+ tuning?: number; frequencyEnd?: number; steps: PatternStep[];
96
+ }
97
+ type SoundPlayback = TonePlayback | ChordPlayback | RepeatPlayback | PatternPlayback | HeldPlayback;
98
+ interface SoundPreset { name?: string; sound?: SoundProperties; playback?: SoundPlayback }
99
+ /** Objects merge recursively; arrays replace. Null removes a leaf if the resulting preset is valid. */
100
+ type PresetPatch<T> = T extends unknown[] ? T | null : T extends object ?
101
+ { [K in keyof T]?: PresetPatch<T[K]> } : T | null;
102
+ interface SoundChanges {
103
+ name?: string;
104
+ sound?: PresetPatch<SoundProperties>;
105
+ playback?: PresetPatch<SoundPlayback>;
106
+ }
107
+ /** Execution overrides are flat, unlike the stored sound/playback definition. */
108
+ type RepeatChanges = PresetPatch<SoundProperties> & ModernPitch & {
109
+ type?: 'repeat'; duration?: number | null; frequencyEnd?: number | null; interval?: number;
110
+ };
111
+ type PatternChanges = PresetPatch<SoundProperties> & PresetPatch<Partial<PatternPlayback>>;
112
+ type SoundOverrides = SoundProperties & (TonePlayback | Partial<ChordPlayback> | HeldPlayback |
113
+ (ModernPitch & { type?: 'repeat'; duration?: number; frequencyEnd?: number; interval?: number }) |
114
+ Partial<PatternPlayback>);
115
+ type NoteChanges = Pick<VoiceOptions, 'volume' | 'pan' | 'lfoFreq' | 'lfoDepth' |
116
+ 'noiseRate' | 'lowPassHz' | 'pulseWidth' | 'filterHz' | 'filterQ' | 'fmRatio' |
117
+ 'fmDepth' | 'spatialX' | 'spatialY' | 'effectSend'> & ModernPitch & { modulations?: Modulation[] };
118
+ interface Note {
119
+ /** Ramps compatible live fields without retriggering. Attack-only fields are rejected. */
120
+ update(changes: NoteChanges): boolean;
121
+ /** Begins this note's release; false after release, shutdown or invalidation. */
122
+ noteOff(): boolean;
123
+ }
124
+ interface RepeatingPlaybackControl {
125
+ /** Applies at the next scheduled attack without moving its existing deadline. */
126
+ update(changes: RepeatChanges): boolean;
127
+ /** Cancels future attacks; already sounding finite attacks finish. */
128
+ stop(): boolean;
129
+ }
130
+ type PatternState = Omit<SequenceState, 'sequenceId'>;
131
+ interface PatternEndEvent { reason: 'completed' | 'stopped' }
132
+ interface PatternPlaybackControl {
133
+ update(changes: PatternChanges): boolean;
134
+ stop(): boolean;
135
+ getState(): PatternState | null;
136
+ /** Transport retirement, not a promise of audible silence. No native ID is exposed. */
137
+ addEventListener(name: 'end', listener: (event: PatternEndEvent) => void): void;
138
+ removeEventListener(name: 'end', listener: (event: PatternEndEvent) => void): void;
139
+ }
140
+ type PlaybackResult = boolean | Note | RepeatingPlaybackControl | PatternPlaybackControl;
141
+ interface Sound {
142
+ getPreset(): SoundPreset;
143
+ /** Validates before replacing the stored definition; affects future executions only. */
144
+ set(changes: SoundChanges): void;
145
+ play(changes: SoundOverrides & { type: 'held' }): Note | false;
146
+ play(changes: SoundOverrides & { type: 'pattern' }): PatternPlaybackControl | false;
147
+ play(changes: SoundOverrides & { type: 'repeat' }): RepeatingPlaybackControl | false;
148
+ play(changes: SoundOverrides & { type: 'tone' | 'chord' }): boolean;
149
+ /** The stored type can change through set(), so an omitted type has a mixed return. */
150
+ play(changes?: SoundOverrides): PlaybackResult;
151
+ noteOn(changes?: SoundProperties & ModernPitch): Note | false;
152
+ }
153
+ interface Contract extends Defaults {
154
+ capabilities: Defaults['capabilities'] & { soundObjects: true };
155
+ interpretations: SoundType[];
156
+ units: { envelope: 'ms'; duration: 'msIncludingRelease'; repetitionInterval: 'ms'; pattern: ('beats' | 'ms')[] };
157
+ soundProperties: (keyof SoundProperties)[];
158
+ playbackProperties: string[];
159
+ }
160
+ type Sync = 'off' | 'quarter' | 'eighth' | 'sixteenth' | 'quarterTriplet' | 'eighthTriplet' | 'sixteenthTriplet';
161
+ interface ProcessingOptions {
162
+ inputGain?: number; applyTo?: Bus[];
163
+ chorus?: { enabled?: boolean; rate?: number; depth?: number; mix?: number };
164
+ /** time is milliseconds; timeMs is a deprecated exclusive input spelling. */
165
+ delay?: { enabled?: boolean; sync?: Sync; feedback?: number; mix?: number } &
166
+ ({ time?: number; timeMs?: never } | { time?: never; timeMs?: number });
167
+ /** decay is milliseconds (200..8000), unlike setMasterProcessing's seconds. */
168
+ reverb?: { enabled?: boolean; decay?: number; damping?: number; mix?: number };
169
+ saturation?: { enabled?: boolean; drive?: number; mix?: number };
170
+ }
171
+ function createSound(preset: SoundPreset): Sound;
172
+ function getContract(): Contract;
173
+ /** Strict configuration errors throw; operational rejection returns false. */
174
+ function setProcessing(options: ProcessingOptions): boolean;
175
+ /** Installed module version, native defaults, limits and capabilities. */
73
176
  function getDefaults(): Defaults;
74
177
  function start(options?: StartOptions): boolean;
75
178
  /** One finite attack. intervalMs is rejected; use repeatTone for repetition. */
179
+ /** @deprecated Use createSound(...).play(). The existing signature and behavior remain supported. */
76
180
  function playTone(options: ToneOptions & { intervalMs?: never }): boolean;
77
181
  /** Integer 1-60000 ms: positive module-owned ID, or 0 on rejection. */
182
+ /** @deprecated Use a Sound with playback.type repeat and its playback control. The existing signature and behavior remain supported. */
78
183
  function repeatTone(options: RepeatingToneOptions): number;
184
+ /** @deprecated Use the repetition control update(). The existing signature and behavior remain supported. */
79
185
  function updateTone(options: ToneUpdate): boolean;
80
186
  /** Cancels future attacks at the next block; admitted attacks finish. */
187
+ /** @deprecated Use the repetition control stop(). The existing signature and behavior remain supported. */
81
188
  function stopTone(options: { toneId: number }): boolean;
189
+ /** @deprecated Use a Sound with playback.type chord. The existing signature and behavior remain supported. */
82
190
  function playChord(options: ChordOptions): boolean;
191
+ /** @deprecated Use Sound.noteOn() or a Sound with playback.type held. The existing signature and behavior remain supported. */
83
192
  function noteOn(options: NoteOptions): boolean;
193
+ /** @deprecated Use Note.update(). The existing signature and behavior remain supported. */
84
194
  function updateNote(options: NoteUpdate): boolean;
85
195
  /** @deprecated Use updateNote; identical behavior and voiceId contract. */
86
196
  function updateVoice(options: NoteUpdate): boolean;
197
+ /** @deprecated Use Note.noteOff(). The existing signature and behavior remain supported. */
87
198
  function noteOff(options: { voiceId: number }): boolean;
199
+ /** @deprecated Use a Sound with playback.type pattern. The existing signature and behavior remain supported. */
88
200
  function playSequence(options: SequenceOptions): number;
201
+ /** @deprecated Use the pattern control update(). The existing signature and behavior remain supported. */
89
202
  function updateSequence(options: Partial<Omit<SequenceOptions, 'timeUnit'>> & { sequenceId: number }): boolean;
203
+ /** @deprecated Use the pattern control stop(). The existing signature and behavior remain supported. */
90
204
  function stopSequence(options: { sequenceId: number }): boolean;
205
+ /** @deprecated Use the pattern control getState(). The existing signature and behavior remain supported. */
91
206
  function getSequenceState(options: { sequenceId: number }): SequenceState | null;
207
+ function setTempo(bpm: number): boolean;
208
+ /** @deprecated Use setTempo(bpm); the dictionary signature remains supported. */
92
209
  function setTempo(options: { bpm: number }): boolean;
210
+ function setVolume(volume: number): boolean;
211
+ /** @deprecated Use setVolume(volume); the dictionary signature remains supported. */
93
212
  function setVolume(options: { volume: number }): boolean;
213
+ /** @deprecated Use setProcessing(); its reverb decay unit is milliseconds. The existing signature and behavior remain supported. */
94
214
  function setMasterProcessing(options: Record<string, unknown>): boolean;
95
215
  function enableStats(): boolean;
96
216
  function disableStats(): boolean;
@@ -1,8 +1,8 @@
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`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/examples.md` SHA-256: `ee8aed566a63062ae63ae78f0b96fa8d73c70ab2f50aed81081c26acc24718af`.
2
2
 
3
3
  ## Sustained game motor
4
4
 
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`.
5
+ This example assumes the service owns one running engine. 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
8
  if (!['noteOn', 'noteOff', 'updateNote'].every(function (name) {
@@ -92,7 +92,7 @@ The UI timer presents state; native audio schedules every attack/release. For fi
92
92
 
93
93
  ## Chorus, delay, reverb and creative saturation
94
94
 
95
- This configures the selected share of a running engine; `applyTo` defaults to all categories. Check method presence and Boolean acceptance; partial section updates merge with the last accepted settings. Effects start disabled, input gain starts at 1, and no LFOs are added to voices implicitly.
95
+ The example below configures the selected share of a running engine; `applyTo` defaults to all categories. Check method presence and Boolean acceptance; partial section updates merge with the last accepted settings. Effects start disabled, input gain starts at 1, and no LFOs are added to voices implicitly.
96
96
 
97
97
  ```javascript
98
98
  if (typeof synth.setMasterProcessing !== 'function') {
@@ -175,7 +175,7 @@ Call `closeMixMeter` when its owner hides or closes. Display unavailable or unme
175
175
 
176
176
  ## Bounded variation for repeated noise impacts
177
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.
178
+ The native engine chooses 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
179
 
180
180
  ```javascript
181
181
  var synth = require('ti.synthengine')
@@ -1,17 +1,18 @@
1
- > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `documentation/api.md` SHA-256: `b58d60cf38a96157e523722d1f9cb9508f785cc2de9c2c1ff306e328c77df9dc`.
1
+ > Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/api.md` SHA-256: `ca5b7f1a44ef79543399fd1a40fa83024c190c086b6412fb79dc54de91a2a329`.
2
2
 
3
3
  # API reference
4
4
 
5
5
  Module id: `ti.synthengine`
6
6
 
7
- Public API version: `1.1.0`
7
+ Module version: `2.0.0`. Read the [Sound object reference](sound-api.md) for reusable definitions, playback controls and property units. This page documents the global controls and direct native parameters behind those interpretations.
8
8
 
9
- Android and iOS expose the same public methods, one event and waveform values. Option objects are strict: an unknown key is rejected with `unsupported_option`. Reported metadata and rejection diagnostics have platform differences; this reference does not promise sample-identical rendering between Android and iOS.
9
+ Android and iOS expose the same public methods and waveform values. Pattern controls emit `end`; direct native sequences emit `sequenceend`. Option objects are strict: an unknown key is rejected with `unsupported_option`. Reported metadata and rejection diagnostics have platform differences; this reference does not promise sample-identical rendering between Android and iOS.
10
10
 
11
11
  <!-- TOC-START -->
12
12
  ## Contents
13
13
 
14
- - [Native attack variation (package 1.1.0)](#native-attack-variation-package-110)
14
+ - [Sound interface](#sound-interface)
15
+ - [Native attack variation](#native-attack-variation)
15
16
  - [Return values](#return-values)
16
17
  - [`getDefaults()`](#getdefaults)
17
18
  - [Limits](#limits)
@@ -42,9 +43,30 @@ Android and iOS expose the same public methods, one event and waveform values. O
42
43
 
43
44
  <!-- TOC-END -->
44
45
 
45
- ## Native attack variation (package 1.1.0)
46
+ ## Sound interface
46
47
 
47
- The optional `variation` dictionary is accepted by `playTone`, `repeatTone`, `playChord`, `noteOn`, sequence `voice` and step `overrides`. Version 1.1.0 reports public API `1.1.0`. Both platforms advertise `capabilities.variation: true` and `variationSchemaVersion: 1`; consumers must require both before sending the dictionary. The published 1.0.2 packages do not support it. See [native variation](native-variation.md) for fields, source restrictions, seeds and examples. Native `amount` is linear; Studio divides its displayed percentage by 100. `updateNote` rejects the dictionary. An override replaces the whole inherited dictionary, and an amount-zero dictionary still validates its ranges.
48
+ | Method | Result |
49
+ | --- | --- |
50
+ | `getContract()` | Installed module version, capabilities, units and structural property allowlists |
51
+ | `createSound({ name?, sound?, playback? })` | A reusable validated Sound; no audio starts |
52
+ | `Sound.play(changes?)` | Finite acceptance or an independent note/repetition/pattern control |
53
+ | `Sound.noteOn(changes?)` | One held-note control, or `false` on operational rejection |
54
+ | `Sound.set({ sound?, playback? })` | Atomic stored revision for future executions |
55
+ | `Sound.getPreset()` | Detached authored definition |
56
+ | Note `update(changes)`, `noteOff()` | Edit or release that held note |
57
+ | Repetition `update(changes)`, `stop()` | Edit future finite attacks or cancel repetition |
58
+ | Pattern `update(changes)`, `stop()`, `getState()` | Edit, stop or inspect that pattern |
59
+ | Pattern `addEventListener('end', fn)`, `removeEventListener('end', fn)` | Subscribe to that pattern's completion or stopping |
60
+ | `setProcessing(options)` | Shared effects with delay `time` and reverb `decay` in milliseconds |
61
+ | `setVolume(number)`, `setTempo(number)` | Global gain and beats per minute |
62
+
63
+ Sound definitions split synthesis into `sound` and execution defaults into `playback`. `play()` and `noteOn()` accept flat temporary overrides. Invalid configurations throw before publication; operational playback rejection returns `false`. Finite events end naturally without individual controls. Shutdown invalidates controls while definitions remain reusable.
64
+
65
+ The [Sound reference](sound-api.md) supplies all properties, defaults, units and interpretation restrictions. The direct native methods below remain supported and document the validators and rendering parameters behind each interpretation. Use the [upgrade guide](upgrade-2.0.0.md) for method mappings.
66
+
67
+ ## Native attack variation
68
+
69
+ The optional `variation` dictionary is accepted by `playTone`, `repeatTone`, `playChord`, `noteOn`, sequence `voice` and step `overrides`. Both platforms advertise `capabilities.variation: true` and `variationSchemaVersion: 1`; consumers must require both before sending the dictionary. See [native variation](native-variation.md) for fields, source restrictions, seeds and examples. Native `amount` is linear; Studio divides its displayed percentage by 100. `updateNote` rejects the dictionary. An override replaces the whole inherited dictionary, and an amount-zero dictionary still validates its ranges.
48
70
 
49
71
  ## Return values
50
72
 
@@ -60,11 +82,11 @@ Returns the installed contract without starting the audio stream.
60
82
  var contract = synth.getDefaults()
61
83
  ```
62
84
 
63
- The shared result in version 1.1.0 has this shape; Android adds the platform metadata described below. Published 1.0.2 packages retain the core contract but omit the variation capability/schema fields:
85
+ The shared result in TiSynth 2.0.0 has this shape; Android adds the platform metadata described below. `version` identifies the installed module, with no independent API version:
64
86
 
65
87
  ```javascript
66
88
  {
67
- apiVersion: '1.1.0',
89
+ version: '2.0.0',
68
90
  capabilities: { variation: true, toneRepetition: true },
69
91
  variationSchemaVersion: 1,
70
92
  defaults: {
@@ -127,7 +149,7 @@ The shared result in version 1.1.0 has this shape; Android adds the platform met
127
149
 
128
150
  `fmDepthEnd`, `detunes`, `spatialX` and `spatialY` have no reported default: their presence changes how the call is interpreted. Supplying a spatial coordinate enables binaural; the other coordinate then defaults to 0 internally. The maximum frequency is `sampleRate * 0.45`. Before the stream opens, the reported fallback is 19,845 Hz. Read the object again after `start()` if the precise device limit matters.
129
151
 
130
- In version 1.1.0, both platforms report `capabilities.variation: true` and `variationSchemaVersion: 1`. Android also reports `nativeSequence`, `masterProcessing` and `modulations` capability flags and `limits.stepDurationMs`. iOS omits those three older flags and `limits.stepDurationMs` while implementing the corresponding methods and millisecond sequences. Published 1.0.2 packages do not advertise variation; their iOS metadata has no `capabilities` map. Detect a required core method with `typeof synth.playSequence === 'function'` (and the other methods your feature needs); require the capability/schema pair specifically for variation. Both platforms report `limits.maxSequences`, `limits.priority` and `limits.effectSend`. `limits.stepDuration` is in beats on both platforms; millisecond sequences accept `4000 / sampleRate` through 60,000 even when `limits.stepDurationMs` is absent.
152
+ Both platforms report `capabilities.variation: true` and `variationSchemaVersion: 1`. Android also reports `nativeSequence`, `masterProcessing` and `modulations` capability flags and `limits.stepDurationMs`. iOS omits those three flags and `limits.stepDurationMs` while implementing the corresponding methods and millisecond sequences. Detect a required core method with `typeof synth.playSequence === 'function'` (and the other methods your feature needs); require the capability/schema pair specifically for variation. Both platforms report `limits.maxSequences`, `limits.priority` and `limits.effectSend`. `limits.stepDuration` is in beats on both platforms; millisecond sequences accept `4000 / sampleRate` through 60,000 even when `limits.stepDurationMs` is absent.
131
153
 
132
154
  ## Limits
133
155
 
@@ -192,7 +214,7 @@ On iOS, `playback` ignores the silent switch and interrupts other apps' audio. `
192
214
 
193
215
  ## `noteOn(options)`
194
216
 
195
- Starts a sustained native voice. Detect `typeof synth.noteOn === 'function'`, `noteOff` and `updateNote` before enabling their controls; the current `apiVersion` is `1.1.0`. There is no duration limit and no JavaScript timer holds the note.
217
+ Starts a sustained native voice. Check `typeof synth.noteOn === 'function'`, `noteOff` and `updateNote` before enabling their controls. Read the installed module version from `getDefaults().version`. The note has no duration limit and needs no JavaScript timer to remain held.
196
218
 
197
219
  | Field | Rule |
198
220
  | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
@@ -251,7 +273,7 @@ Begins release from the current level, including during attack or decay. Repeate
251
273
  | `spatialX`, `spatialY` | Target position for a spatial held voice; omitted coordinate stays unchanged. |
252
274
  | `effectSend` | Explicit processed share, 0-1, overriding category selection. |
253
275
 
254
- At least one change is required. An incompatible change rejects the entire request when the ID is known; unknown/ended IDs accept publication as a no-op. The callback also checks source compatibility before applying a command. Changed continuous parameters ramp linearly from their current values over 10 ms. New targets retarget that ramp without restarting oscillator phase or the envelope. Unchanged parameters retain their current ramps. This also works during release.
276
+ Supply at least one change. An incompatible change rejects the entire request when the ID is known; unknown/ended IDs accept publication as a no-op. The callback also checks source compatibility before applying a command. Changed continuous parameters ramp linearly from their current values over 10 ms. A new target redirects the ramp without restarting oscillator phase or the envelope. Unchanged parameters keep their current ramps. These edits also work during release.
255
277
 
256
278
  Pitch updates are absolute: an initial `detune` is already included in the note's starting frequency. To edit tuning later, compute the desired effective frequency in the app and send `frequency`. There is no `detune` update field. Filter type, ADSR, quantization and wavetable require a new attack. Noise clock, low-pass cutoff, send and compatible stereo/spatial position support live 10 ms updates.
257
279
 
@@ -319,7 +341,7 @@ Plays one pitch. It uses 440 Hz when neither pitch field is present. Note names
319
341
 
320
342
  Omitted attack/release values are shortened to fit an event smaller than the default envelope. Explicit values must satisfy the documented envelope rules. `getDefaults()` reports the baseline defaults, not these per-event adjustments.
321
343
 
322
- In the current 1.0.2/1.1.0 implementations, the iOS timed tone/chord command builder
344
+ In the native implementation, the iOS timed tone/chord command builder
323
345
  initially rounds accepted zero attack/release values to one sample, before
324
346
  trimming them to the event duration budget. Android retains zero samples.
325
347
  Validator acceptance does not imply identical zero-edge rendering. Held notes
@@ -375,7 +397,7 @@ Those three calls use JavaScript timing and independent attack selection. For an
375
397
 
376
398
  Returns Boolean acceptance. Requires a known positive repetition identifier and at least one change. Accepts partial finite-tone options, including `intervalMs`, and merges them with the latest accepted recipe before validating the entire result. Supplying `note` replaces an earlier `frequency`; supplying `frequency` replaces an earlier `note`. Supplying both is invalid. Invalid edits leave the last accepted recipe intact.
377
399
 
378
- An accepted edit affects the next already scheduled attack, preserving its deadline. The new interval determines separation after that attack. Active attacks retain their original configuration. A supplied `variation` dictionary replaces the whole previous dictionary; `{ amount: 0 }` disables future variation. Each repetition retains its own seed and admitted-attack counter. Edits preserve the stream; changing its seed restarts that succession. Unknown or retired IDs reject edits.
400
+ An accepted edit affects the next scheduled attack without moving its deadline. The new interval sets the gap after that attack. Active attacks keep their original configuration. A supplied `variation` dictionary replaces the whole previous dictionary; `{ amount: 0 }` disables future variation. Each repetition keeps its own seed and admitted-attack counter. Edits preserve the stream unless the seed changes, which restarts it. Unknown or retired IDs reject edits.
379
401
 
380
402
  ### `stopTone({ toneId })`
381
403
 
@@ -440,7 +462,7 @@ synth.stopSequence({ sequenceId: musicId })
440
462
 
441
463
  Selected audio follows fixed input gain → chorus → ping-pong delay → reverb → master volume → optional saturation. Dry audio receives the same global input/master gains, then recombines before one final sample-peak protector with no retained samples. Independent notes never trigger voice-count normalization. The limiter remains the final protection.
442
464
 
443
- Requires a running engine and returns Boolean acceptance. Partial updates merge into the last accepted processing configuration, validate the complete result and publish atomically. Continuous controls ramp over 10 ms. Unknown fields, numeric booleans and non-finite values reject the whole edit. Detect method presence before use.
465
+ This method requires a running engine and returns Boolean acceptance. It merges partial updates into the last accepted processing configuration, validates the complete result and publishes it atomically. Continuous controls ramp over 10 ms. Unknown fields, numeric booleans and non-finite values reject the whole edit. Check that the method exists before calling it.
444
466
 
445
467
  | Section | Fields and initial values |
446
468
  | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -455,7 +477,7 @@ Delay sync accepts `off`, `quarter`, `eighth`, `sixteenth`, `quarterTriplet`, `e
455
477
 
456
478
  Processing uses one shared chain for the selected categories. `applyTo` is an array drawn from `music`, `effects`, `ui` and `ambience`; default all four. Each event or held voice uses `bus: 'effects'` by default. Optional `effectSend: 0…1` overrides category selection for that voice. Input gain and master volume remain global. Selected audio receives chorus, delay, reverb and optional saturation, then recombines with dry audio before one final limiter. Send changes ramp over 10 ms.
457
479
 
458
- Defaults keep all effects and saturation disabled, input gain at unity, and preserve the earlier render exactly. Enabling saturation is an explicit sound-design choice. Output protection is still required with dense voices and effect feedback.
480
+ Defaults keep all effects and saturation disabled, input gain at unity, and route audio through the unprocessed path. Enabling saturation is an explicit sound-design choice. Output protection is still required with dense voices and effect feedback.
459
481
 
460
482
  ## Additional voice modulation
461
483
 
@@ -469,7 +491,7 @@ Defaults keep all effects and saturation disabled, input gain at unity, and pres
469
491
  | `depth` | 0-1, default 0; scales up to four cutoff octaves, 0.45 PWM offset or ten FM-index units. Two contributions sum with safe clamping. |
470
492
  | `sync` | Same musical values as delay; default off. Synced rates follow native tempo (up to 30 Hz at 300 BPM in sixteenth-note triplets). |
471
493
 
472
- Edits preserve oscillator/LFO phases. Continuous modulation contributions ramp over 10 ms; cutoff stays within 20 Hz and the frequency maximum, PWM within 0.05-0.95, FM index within 0-10. Initial modulations may also be inherited by sequence voices and overridden per step. These two slots coexist with the independent `lfoFreq`/`lfoDepth` pitch vibrato; they are not accepted by timed tones, chords .
494
+ Edits preserve oscillator/LFO phases. Continuous modulation contributions ramp over 10 ms; cutoff stays within 20 Hz and the frequency maximum, PWM within 0.05-0.95, FM index within 0-10. Sequence voices can inherit initial modulations, and steps can override them. These two slots coexist with the independent `lfoFreq`/`lfoDepth` pitch vibrato. Timed tones and chords reject them.
473
495
 
474
496
  ## `playChord(options)`
475
497
 
@@ -669,7 +691,7 @@ Listen with stereo headphones. Height perception depends on the listener, headph
669
691
 
670
692
  Use `typeof synth.getStats === 'function'` to check that the expected module is installed before enabling a diagnostics panel.
671
693
 
672
- Returns a new snapshot without resetting counters. Available before start and after shutdown. Occupancy is the last completed render block; queued commands are read at query time. No audio-thread allocation, locks or waiting are added.
694
+ This method returns a new snapshot without resetting counters and is available before start and after shutdown. Occupancy comes from the last completed render block; queued commands are read at query time. The query adds no audio-thread allocation, locks or waiting.
673
695
 
674
696
  | Field | Meaning |
675
697
  | -------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
@@ -697,7 +719,7 @@ Returns a new snapshot without resetting counters. Available before start and af
697
719
  | `bufferFrames` | Android Oboe output buffer size; otherwise `null`. This is distinct from `callbackFrames`. |
698
720
  | `estimatedOutputLatencyMs` | Android timestamp-based stream-to-DAC estimate, when valid; otherwise `null` (always `null` on this iOS backend). |
699
721
 
700
- In the current 1.0.2/1.1.0 implementations, iOS early rejections in tempo/sequence operations and a
722
+ In the native implementation, iOS early rejections in tempo/sequence operations and a
701
723
  stopped `setMasterProcessing` call can bypass rejection counters. Incompatible
702
724
  held spatial-mode or modulation updates also have uncounted iOS returns.
703
725
  Android counts more of these cases, but an unknown/retired `stopSequence` return