@maccesar/titools 5.2.4 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -32
- package/agents/ti-pro.md +1 -1
- package/commands/ti-audit.md +1 -1
- package/docs/examples.md +15 -8
- package/package.json +1 -1
- package/skills/ti-synthengine/SKILL.md +62 -92
- package/skills/ti-synthengine/assets/example/README.md +51 -54
- package/skills/ti-synthengine/assets/example/app.js +96 -57
- package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +39 -40
- package/skills/ti-synthengine/assets/example/lib/soundPreset.js +34 -0
- package/skills/ti-synthengine/assets/index.d.ts +226 -0
- package/skills/ti-synthengine/references/advanced-examples.md +12 -12
- package/skills/ti-synthengine/references/api.md +125 -33
- package/skills/ti-synthengine/references/audio-engine.md +24 -59
- package/skills/ti-synthengine/references/examples.md +64 -7
- package/skills/ti-synthengine/references/guide.md +69 -63
- package/skills/ti-synthengine/references/instruments-and-effects.md +34 -27
- package/skills/ti-synthengine/references/native-variation.md +92 -51
- package/skills/ti-synthengine/references/recipes-interaction.md +26 -19
- package/skills/ti-synthengine/references/recipes.md +19 -15
- package/skills/ti-synthengine/references/recommendations.md +14 -14
- package/skills/ti-synthengine/references/sound-api.md +197 -0
- package/skills/ti-synthengine/references/sound-design.md +11 -11
- package/skills/ti-synthengine/references/source-notes.md +62 -147
- package/skills/ti-synthengine/references/troubleshooting.md +22 -24
- package/skills/ti-synthengine/references/upgrade-2.0.0.md +40 -0
- package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
- package/skills/ti-synthengine/references/android-sim80-correction.json +0 -162
- package/skills/ti-synthengine/references/audio-pipeline-validation.md +0 -19
- package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
- package/skills/ti-synthengine/references/frontier-physical-validation.json +0 -1918
- package/skills/ti-synthengine/references/native-variation-proposal.md +0 -182
- package/skills/ti-synthengine/references/native-variation-validation.md +0 -63
- package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
- package/skills/ti-synthengine/references/performance-review.json +0 -1146
- package/skills/ti-synthengine/references/physical-validation.json +0 -1457
- package/skills/ti-synthengine/references/source-snapshot.json +0 -463
- package/skills/ti-synthengine/references/stats-performance.json +0 -2992
- package/skills/ti-synthengine/references/stats-performance.md +0 -82
- package/skills/ti-synthengine/references/studio-validation.json +0 -6202
- package/skills/ti-synthengine/references/unified-bypass-performance.json +0 -22425
- package/skills/ti-synthengine/references/unified-validation.json +0 -314
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
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 =
|
|
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
|
|
22
|
-
if (!
|
|
23
|
-
current =
|
|
24
|
-
|
|
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 (
|
|
38
|
-
//
|
|
39
|
-
|
|
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', {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/** TiSynthEngine 2.0.0 candidate. One module version for the entire public interface. */
|
|
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
|
+
version: string;
|
|
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
|
+
/** 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. */
|
|
176
|
+
function getDefaults(): Defaults;
|
|
177
|
+
function start(options?: StartOptions): boolean;
|
|
178
|
+
/** One finite attack. intervalMs is rejected; use repeatTone for repetition. */
|
|
179
|
+
/** @deprecated Use createSound(...).play(). The existing signature and behavior remain supported. */
|
|
180
|
+
function playTone(options: ToneOptions & { intervalMs?: never }): boolean;
|
|
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. */
|
|
183
|
+
function repeatTone(options: RepeatingToneOptions): number;
|
|
184
|
+
/** @deprecated Use the repetition control update(). The existing signature and behavior remain supported. */
|
|
185
|
+
function updateTone(options: ToneUpdate): boolean;
|
|
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. */
|
|
188
|
+
function stopTone(options: { toneId: number }): boolean;
|
|
189
|
+
/** @deprecated Use a Sound with playback.type chord. The existing signature and behavior remain supported. */
|
|
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. */
|
|
192
|
+
function noteOn(options: NoteOptions): boolean;
|
|
193
|
+
/** @deprecated Use Note.update(). The existing signature and behavior remain supported. */
|
|
194
|
+
function updateNote(options: NoteUpdate): boolean;
|
|
195
|
+
/** @deprecated Use updateNote; identical behavior and voiceId contract. */
|
|
196
|
+
function updateVoice(options: NoteUpdate): boolean;
|
|
197
|
+
/** @deprecated Use Note.noteOff(). The existing signature and behavior remain supported. */
|
|
198
|
+
function noteOff(options: { voiceId: number }): boolean;
|
|
199
|
+
/** @deprecated Use a Sound with playback.type pattern. The existing signature and behavior remain supported. */
|
|
200
|
+
function playSequence(options: SequenceOptions): number;
|
|
201
|
+
/** @deprecated Use the pattern control update(). The existing signature and behavior remain supported. */
|
|
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. */
|
|
204
|
+
function stopSequence(options: { sequenceId: number }): boolean;
|
|
205
|
+
/** @deprecated Use the pattern control getState(). The existing signature and behavior remain supported. */
|
|
206
|
+
function getSequenceState(options: { sequenceId: number }): SequenceState | null;
|
|
207
|
+
function setTempo(bpm: number): boolean;
|
|
208
|
+
/** @deprecated Use setTempo(bpm); the dictionary signature remains supported. */
|
|
209
|
+
function setTempo(options: { bpm: number }): boolean;
|
|
210
|
+
function setVolume(volume: number): boolean;
|
|
211
|
+
/** @deprecated Use setVolume(volume); the dictionary signature remains supported. */
|
|
212
|
+
function setVolume(options: { volume: number }): boolean;
|
|
213
|
+
/** @deprecated Use setProcessing(); its reverb decay unit is milliseconds. The existing signature and behavior remain supported. */
|
|
214
|
+
function setMasterProcessing(options: Record<string, unknown>): boolean;
|
|
215
|
+
function enableStats(): boolean;
|
|
216
|
+
function disableStats(): boolean;
|
|
217
|
+
/** @deprecated Use enableStats or disableStats; identical diagnostic behavior. */
|
|
218
|
+
function setStatsEnabled(enabled: boolean): boolean;
|
|
219
|
+
function getStats(): Record<string, number | boolean | string | null>;
|
|
220
|
+
function resetStats(): void;
|
|
221
|
+
function stopAll(): void;
|
|
222
|
+
function shutdown(): void;
|
|
223
|
+
function addEventListener(name: 'sequenceend', listener: (event: { sequenceId: number; reason: 'completed' | 'stopped' }) => void): void;
|
|
224
|
+
function removeEventListener(name: 'sequenceend', listener: (event: { sequenceId: number; reason: 'completed' | 'stopped' }) => void): void;
|
|
225
|
+
}
|
|
226
|
+
export = synth;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
> Source snapshot: official TiSynthEngine documentation at commit `
|
|
1
|
+
> Source snapshot: official TiSynthEngine documentation at commit `9d74aa45f84d3f2822d4aab76874efe37d81257f` (2026-11-03). Published module 2.0.0. Upstream `documentation/examples.md` SHA-256: `ee8aed566a63062ae63ae78f0b96fa8d73c70ab2f50aed81081c26acc24718af`.
|
|
2
2
|
|
|
3
3
|
## Sustained game motor
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
if (!['noteOn', 'noteOff', '
|
|
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.
|
|
28
|
+
return synth.updateNote({ voiceId: motorId,
|
|
29
29
|
filterHz: boosted ? 320 : 220, volume: boosted ? 0.18 : 0.15 })
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -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
|
-
|
|
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') {
|
|
@@ -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.
|
|
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.
|
|
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 and chords do not accept
|
|
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 (!['
|
|
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.
|
|
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,7 +167,7 @@ var mixTimer = setInterval(function () {
|
|
|
167
167
|
|
|
168
168
|
function closeMixMeter () {
|
|
169
169
|
clearInterval(mixTimer)
|
|
170
|
-
if (!synth.
|
|
170
|
+
if (!synth.disableStats()) Ti.API.warn('Diagnostics disable rejected')
|
|
171
171
|
}
|
|
172
172
|
```
|
|
173
173
|
|
|
@@ -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
|
|
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')
|