@maccesar/titools 5.1.1 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -12
- package/agents/ti-pro.md +4 -0
- package/commands/ti-audit.md +4 -0
- package/lib/config.js +2 -0
- package/package.json +1 -1
- package/skills/ti-admob-unified/SKILL.md +44 -0
- package/skills/ti-admob-unified/agents/openai.yaml +4 -0
- package/skills/ti-admob-unified/assets/example/app.js +1003 -0
- package/skills/ti-admob-unified/assets/example/lib/ads.js +618 -0
- package/skills/ti-admob-unified/assets/example/library-usage.js +104 -0
- package/skills/ti-admob-unified/references/api-reference.md +592 -0
- package/skills/ti-admob-unified/references/banners.md +435 -0
- package/skills/ti-admob-unified/references/contributing.md +228 -0
- package/skills/ti-admob-unified/references/dependencies.md +85 -0
- package/skills/ti-admob-unified/references/fullscreen.md +223 -0
- package/skills/ti-admob-unified/references/game-monetization.md +17 -0
- package/skills/ti-admob-unified/references/getting-started.md +275 -0
- package/skills/ti-admob-unified/references/index.md +30 -0
- package/skills/ti-admob-unified/references/integration.md +85 -0
- package/skills/ti-admob-unified/references/library.md +189 -0
- package/skills/ti-admob-unified/references/mediation.md +132 -0
- package/skills/ti-admob-unified/references/migration.md +255 -0
- package/skills/ti-admob-unified/references/module-changelog.md +54 -0
- package/skills/ti-admob-unified/references/module-license.txt +21 -0
- package/skills/ti-admob-unified/references/privacy.md +254 -0
- package/skills/ti-admob-unified/references/roadmap.md +146 -0
- package/skills/ti-admob-unified/references/source-notes.md +33 -0
- package/skills/ti-admob-unified/references/source-snapshot.json +440 -0
- package/skills/ti-admob-unified/references/third-party-notices.md +22 -0
- package/skills/ti-game/SKILL.md +10 -1
- package/skills/ti-game/references/api.md +10 -2
- package/skills/ti-game/references/debugging-performance.md +6 -0
- package/skills/ti-game/references/examples.md +11 -1
- package/skills/ti-game/references/patterns.md +24 -2
- package/skills/ti-game/references/physics.md +502 -0
- package/skills/ti-game/references/project-setup.md +23 -1
- package/skills/ti-game/references/recipes-action.md +46 -14
- package/skills/ti-game/references/recipes-adventure-effects.md +12 -1
- package/skills/ti-game/references/roadmap.md +2 -2
- package/skills/ti-game/references/tilemaps.md +2 -1
- package/skills/ti-game-assets/SKILL.md +53 -0
- package/skills/ti-game-assets/agents/openai.yaml +4 -0
- package/skills/ti-game-assets/references/alpha.md +26 -0
- package/skills/ti-game-assets/references/art-direction.md +37 -0
- package/skills/ti-game-assets/references/integration.md +49 -0
- package/skills/ti-game-assets/references/manifest.example.json +429 -0
- package/skills/ti-game-assets/references/manifest.md +39 -0
- package/skills/ti-game-assets/references/offline-audio.md +33 -0
- package/skills/ti-game-assets/references/prompts.md +33 -0
- package/skills/ti-game-assets/references/repeating-textures.md +17 -0
- package/skills/ti-game-assets/references/responsible-ads.md +31 -0
- package/skills/ti-game-assets/references/sound-assets.md +42 -0
- package/skills/ti-game-assets/references/vector-sources.md +21 -0
- package/skills/ti-game-assets/scripts/alpha_tools.py +160 -0
- package/skills/ti-game-assets/scripts/requirements.txt +1 -0
- package/skills/ti-game-assets/scripts/validate_manifest.py +267 -0
- package/skills/ti-synthengine/SKILL.md +37 -13
- package/skills/ti-synthengine/references/api.md +397 -31
- package/skills/ti-synthengine/references/audio-engine.md +235 -14
- package/skills/ti-synthengine/references/examples.md +396 -17
- package/skills/ti-synthengine/references/guide.md +331 -12
- package/skills/ti-synthengine/references/recipes.md +212 -22
- package/skills/ti-synthengine/references/recommendations.md +53 -16
- package/skills/ti-synthengine/references/sound-design.md +359 -15
- package/skills/ti-synthengine/references/source-notes.md +121 -0
- package/skills/ti-synthengine/references/source-snapshot.json +105 -0
- package/skills/ti-synthengine/references/stats-performance.json +2992 -0
- package/skills/ti-synthengine/references/stats-performance.md +38 -0
- package/skills/ti-synthengine/references/troubleshooting.md +61 -4
|
@@ -17,6 +17,10 @@ Treat levels as starting points, not mastering targets. Test on the actual
|
|
|
17
17
|
phone speaker and leave headroom when release tails or independent events can
|
|
18
18
|
overlap.
|
|
19
19
|
|
|
20
|
+
The recipes under "Expressive envelopes, filters and FM" pass the real
|
|
21
|
+
contract, but they come from standard synthesis ranges and have not been
|
|
22
|
+
auditioned on a device. Listen before shipping them.
|
|
23
|
+
|
|
20
24
|
<!-- TOC-START -->
|
|
21
25
|
## Contents
|
|
22
26
|
|
|
@@ -24,6 +28,7 @@ overlap.
|
|
|
24
28
|
- [Playable xylophone](#playable-xylophone)
|
|
25
29
|
- [Four-pad memory cues](#four-pad-memory-cues)
|
|
26
30
|
- [Polyphony and layered chords](#polyphony-and-layered-chords)
|
|
31
|
+
- [Expressive envelopes, filters and FM](#expressive-envelopes-filters-and-fm)
|
|
27
32
|
- [Preset architecture for strict contracts](#preset-architecture-for-strict-contracts)
|
|
28
33
|
- [Recipe verification checklist](#recipe-verification-checklist)
|
|
29
34
|
|
|
@@ -105,19 +110,21 @@ were chosen for it.
|
|
|
105
110
|
|
|
106
111
|
### Three-note bonus cue
|
|
107
112
|
|
|
108
|
-
Short ascending square notes read as collection or reward. `
|
|
109
|
-
|
|
113
|
+
Short ascending square notes read as collection or reward. A step's `duration`
|
|
114
|
+
sets both its voice length and the time before the next step; it overrides
|
|
115
|
+
`stepDuration` and `noteValue`. Explicit rests separate 95 ms notes by 110 ms.
|
|
110
116
|
|
|
111
117
|
```javascript
|
|
112
118
|
synth.playPattern({
|
|
113
|
-
stepDuration: 110,
|
|
114
119
|
attack: 2,
|
|
115
120
|
release: 30,
|
|
116
121
|
volume: 0.55,
|
|
117
122
|
waveType: WAVE.SQUARE,
|
|
118
123
|
steps: [
|
|
119
124
|
{ frequency: 523.25, duration: 95 },
|
|
125
|
+
{ rest: true, duration: 15 },
|
|
120
126
|
{ frequency: 587.33, duration: 95 },
|
|
127
|
+
{ rest: true, duration: 15 },
|
|
121
128
|
{ frequency: 659.25, duration: 130 }
|
|
122
129
|
]
|
|
123
130
|
})
|
|
@@ -126,19 +133,22 @@ synth.playPattern({
|
|
|
126
133
|
### Fast four-note arpeggio
|
|
127
134
|
|
|
128
135
|
Very short square notes with a small attack retain the hard cartridge-era
|
|
129
|
-
edge while reducing uncontrolled clicks.
|
|
136
|
+
edge while reducing uncontrolled clicks. The 10 ms rests keep the first three
|
|
137
|
+
40 ms notes on a 50 ms grid; the final note rings for 80 ms.
|
|
130
138
|
|
|
131
139
|
```javascript
|
|
132
140
|
synth.playPattern({
|
|
133
|
-
stepDuration: 50,
|
|
134
141
|
attack: 5,
|
|
135
142
|
release: 15,
|
|
136
143
|
volume: 0.5,
|
|
137
144
|
waveType: WAVE.SQUARE,
|
|
138
145
|
steps: [
|
|
139
146
|
{ frequency: 261.63, duration: 40 },
|
|
147
|
+
{ rest: true, duration: 10 },
|
|
140
148
|
{ frequency: 329.63, duration: 40 },
|
|
149
|
+
{ rest: true, duration: 10 },
|
|
141
150
|
{ frequency: 392, duration: 40 },
|
|
151
|
+
{ rest: true, duration: 10 },
|
|
142
152
|
{ frequency: 523.25, duration: 80 }
|
|
143
153
|
]
|
|
144
154
|
})
|
|
@@ -147,8 +157,8 @@ synth.playPattern({
|
|
|
147
157
|
### Rapid score ticks
|
|
148
158
|
|
|
149
159
|
Use one native pattern instead of ten JavaScript timers. Explicit attack and
|
|
150
|
-
release values make every 60 ms
|
|
151
|
-
defaults.
|
|
160
|
+
release values make every 60 ms tone valid without relying on shortened
|
|
161
|
+
defaults. A 10 ms rest after each tone places the ticks 70 ms apart.
|
|
152
162
|
|
|
153
163
|
```javascript
|
|
154
164
|
var scoreSteps = []
|
|
@@ -160,10 +170,10 @@ for (var index = 0; index < 10; index++) {
|
|
|
160
170
|
attack: 2,
|
|
161
171
|
release: 20
|
|
162
172
|
})
|
|
173
|
+
scoreSteps.push({ rest: true, duration: 10 })
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
synth.playPattern({
|
|
166
|
-
stepDuration: 70,
|
|
167
177
|
volume: 0.42,
|
|
168
178
|
waveType: WAVE.SQUARE,
|
|
169
179
|
steps: scoreSteps
|
|
@@ -194,19 +204,20 @@ synth.playTone({
|
|
|
194
204
|
### Alternating warning alarm
|
|
195
205
|
|
|
196
206
|
An octave-like high/low alternation is easy to identify even through a small
|
|
197
|
-
speaker.
|
|
198
|
-
|
|
207
|
+
speaker. Each 120 ms tone has a fitted envelope (`10 + 100 <= 120`), followed
|
|
208
|
+
by a 10 ms rest, so the pitch changes every 130 ms.
|
|
199
209
|
|
|
200
210
|
```javascript
|
|
201
211
|
var warningSteps = []
|
|
202
212
|
|
|
203
213
|
for (var pulse = 0; pulse < 5; pulse++) {
|
|
204
214
|
warningSteps.push({ frequency: 800, duration: 120 })
|
|
215
|
+
warningSteps.push({ rest: true, duration: 10 })
|
|
205
216
|
warningSteps.push({ frequency: 400, duration: 120 })
|
|
217
|
+
warningSteps.push({ rest: true, duration: 10 })
|
|
206
218
|
}
|
|
207
219
|
|
|
208
220
|
synth.playPattern({
|
|
209
|
-
stepDuration: 130,
|
|
210
221
|
attack: 10,
|
|
211
222
|
release: 100,
|
|
212
223
|
volume: 0.45,
|
|
@@ -412,8 +423,8 @@ function auditionTimbre (profileName) {
|
|
|
412
423
|
|
|
413
424
|
### Convert MIDI pitch to hertz
|
|
414
425
|
|
|
415
|
-
The standard A4 = 440 Hz formula makes it easy to build interval recipes
|
|
416
|
-
|
|
426
|
+
The standard A4 = 440 Hz formula makes it easy to build interval recipes
|
|
427
|
+
without embedding a long frequency table.
|
|
417
428
|
|
|
418
429
|
```javascript
|
|
419
430
|
function midiToHz (midi) {
|
|
@@ -421,14 +432,14 @@ function midiToHz (midi) {
|
|
|
421
432
|
}
|
|
422
433
|
```
|
|
423
434
|
|
|
424
|
-
|
|
425
|
-
|
|
435
|
+
Detuning needs no conversion: `detune` on a tone or pattern and `detunes` on a
|
|
436
|
+
chord take cents, one hundredth of a semitone.
|
|
426
437
|
|
|
427
438
|
### Build a wide layered chord
|
|
428
439
|
|
|
429
440
|
The interval cycle `[0, 4, 7, ...]` stacks major-triad chord tones across
|
|
430
|
-
octaves. Later layers repeat those tones with small cent offsets,
|
|
431
|
-
width and beating
|
|
441
|
+
octaves. Later layers repeat those tones with small cent offsets, passed as
|
|
442
|
+
`detunes`, which creates width and beating.
|
|
432
443
|
|
|
433
444
|
```javascript
|
|
434
445
|
var CHORD_INTERVALS = [0, 4, 7, 12, 16, 19, 24, 28, 31, 36]
|
|
@@ -446,15 +457,24 @@ function buildLayeredFrequencies (voiceCount) {
|
|
|
446
457
|
|
|
447
458
|
for (var index = 0; index < count; index++) {
|
|
448
459
|
var intervalIndex = index % CHORD_INTERVALS.length
|
|
449
|
-
|
|
450
|
-
var cents = LAYER_DETUNE_CENTS[layer % LAYER_DETUNE_CENTS.length]
|
|
451
|
-
var midi = 60.12 + CHORD_INTERVALS[intervalIndex] + (cents / 100)
|
|
452
|
-
frequencies.push(midiToHz(midi))
|
|
460
|
+
frequencies.push(midiToHz(60.12 + CHORD_INTERVALS[intervalIndex]))
|
|
453
461
|
}
|
|
454
462
|
|
|
455
463
|
return frequencies
|
|
456
464
|
}
|
|
457
465
|
|
|
466
|
+
function buildLayerDetunes (voiceCount) {
|
|
467
|
+
var count = normalizeVoiceCount(voiceCount)
|
|
468
|
+
var detunes = []
|
|
469
|
+
|
|
470
|
+
for (var index = 0; index < count; index++) {
|
|
471
|
+
var layer = Math.floor(index / CHORD_INTERVALS.length)
|
|
472
|
+
detunes.push(LAYER_DETUNE_CENTS[layer % LAYER_DETUNE_CENTS.length])
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return detunes
|
|
476
|
+
}
|
|
477
|
+
|
|
458
478
|
function buildSymmetricPans (voiceCount, spread) {
|
|
459
479
|
var count = normalizeVoiceCount(voiceCount)
|
|
460
480
|
var numericSpread = Number(spread)
|
|
@@ -477,6 +497,7 @@ function playLayeredChord (voiceCount) {
|
|
|
477
497
|
|
|
478
498
|
return synth.playChord({
|
|
479
499
|
frequencies: buildLayeredFrequencies(count),
|
|
500
|
+
detunes: buildLayerDetunes(count),
|
|
480
501
|
duration: 800,
|
|
481
502
|
attack: 35,
|
|
482
503
|
release: 220,
|
|
@@ -558,6 +579,171 @@ win.addEventListener('close', cleanupAudio)
|
|
|
558
579
|
Do not call `shutdown()` from a child control if another visible screen owns
|
|
559
580
|
the shared engine.
|
|
560
581
|
|
|
582
|
+
## Expressive envelopes, filters and FM
|
|
583
|
+
|
|
584
|
+
### Plucked marimba note
|
|
585
|
+
|
|
586
|
+
A short attack, a decay that falls to a low sustain and a release that fits
|
|
587
|
+
give the struck-and-ring shape of a mallet or plucked string. A sustain below 1
|
|
588
|
+
needs a decay, and `attack + decay` must fit inside `duration`.
|
|
589
|
+
|
|
590
|
+
```javascript
|
|
591
|
+
synth.playTone({
|
|
592
|
+
note: 'C5',
|
|
593
|
+
duration: 700,
|
|
594
|
+
attack: 2,
|
|
595
|
+
decay: 260,
|
|
596
|
+
sustain: 0.12,
|
|
597
|
+
release: 240,
|
|
598
|
+
waveType: WAVE.TRIANGLE,
|
|
599
|
+
volume: 0.62
|
|
600
|
+
})
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### Kick drum
|
|
604
|
+
|
|
605
|
+
A sine that falls in pitch while its level decays reads as a drum. The pitch
|
|
606
|
+
sweep is linear over the whole event.
|
|
607
|
+
|
|
608
|
+
```javascript
|
|
609
|
+
synth.playTone({
|
|
610
|
+
frequency: 150,
|
|
611
|
+
frequencyEnd: 45,
|
|
612
|
+
duration: 320,
|
|
613
|
+
attack: 1,
|
|
614
|
+
decay: 140,
|
|
615
|
+
sustain: 0.35,
|
|
616
|
+
release: 140,
|
|
617
|
+
volume: 0.9
|
|
618
|
+
})
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Closed hi-hat
|
|
622
|
+
|
|
623
|
+
White noise through a high-pass filter, with a decay that drops to a quiet
|
|
624
|
+
tail. The filter fields only work together with a `filterType`.
|
|
625
|
+
|
|
626
|
+
```javascript
|
|
627
|
+
synth.playTone({
|
|
628
|
+
waveType: WAVE.NOISE,
|
|
629
|
+
duration: 90,
|
|
630
|
+
attack: 1,
|
|
631
|
+
decay: 20,
|
|
632
|
+
sustain: 0.3,
|
|
633
|
+
release: 60,
|
|
634
|
+
filterType: 'highpass',
|
|
635
|
+
filterHz: 6500,
|
|
636
|
+
volume: 0.45
|
|
637
|
+
})
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Electric piano
|
|
641
|
+
|
|
642
|
+
Frequency modulation with a 1:1 ratio gives a tine-like tone. Letting the
|
|
643
|
+
modulation index fall through `fmDepthEnd` makes the note mellow as it rings,
|
|
644
|
+
which is what the ear expects from a struck tine. FM needs a sine carrier.
|
|
645
|
+
|
|
646
|
+
```javascript
|
|
647
|
+
synth.playTone({
|
|
648
|
+
note: 'E3',
|
|
649
|
+
duration: 1400,
|
|
650
|
+
attack: 3,
|
|
651
|
+
decay: 700,
|
|
652
|
+
sustain: 0.25,
|
|
653
|
+
release: 500,
|
|
654
|
+
fmRatio: 1,
|
|
655
|
+
fmDepth: 2.2,
|
|
656
|
+
fmDepthEnd: 0.3,
|
|
657
|
+
volume: 0.55
|
|
658
|
+
})
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Bell
|
|
662
|
+
|
|
663
|
+
A non-integer ratio produces an inharmonic spectrum, and an index that decays
|
|
664
|
+
to nothing leaves a pure ring. `fmDepthEnd: 0` is a real target, so it is
|
|
665
|
+
accepted; omit the key when no sweep is wanted.
|
|
666
|
+
|
|
667
|
+
```javascript
|
|
668
|
+
synth.playTone({
|
|
669
|
+
note: 'A5',
|
|
670
|
+
duration: 2400,
|
|
671
|
+
attack: 1,
|
|
672
|
+
decay: 1800,
|
|
673
|
+
sustain: 0,
|
|
674
|
+
release: 500,
|
|
675
|
+
fmRatio: 3.5,
|
|
676
|
+
fmDepth: 4,
|
|
677
|
+
fmDepthEnd: 0,
|
|
678
|
+
volume: 0.5
|
|
679
|
+
})
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### Wah sweep
|
|
683
|
+
|
|
684
|
+
A resonant band-pass opening on a sawtooth. The cutoff moves exponentially, so
|
|
685
|
+
equal distances in pitch take equal time. Keep `filterQ` moderate: high values
|
|
686
|
+
make the resonance peak loud enough to engage the limiter.
|
|
687
|
+
|
|
688
|
+
```javascript
|
|
689
|
+
synth.playTone({
|
|
690
|
+
note: 'A2',
|
|
691
|
+
duration: 900,
|
|
692
|
+
attack: 20,
|
|
693
|
+
release: 200,
|
|
694
|
+
waveType: WAVE.SAWTOOTH,
|
|
695
|
+
filterType: 'bandpass',
|
|
696
|
+
filterHz: 350,
|
|
697
|
+
filterHzEnd: 2200,
|
|
698
|
+
filterQ: 4,
|
|
699
|
+
volume: 0.7
|
|
700
|
+
})
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Thick unison pad
|
|
704
|
+
|
|
705
|
+
Repeat a pitch and spread the copies a few cents apart. The slow beating
|
|
706
|
+
between them is what makes the sound wide. `detunes` is aligned with the
|
|
707
|
+
pitches and can be shorter than the array.
|
|
708
|
+
|
|
709
|
+
```javascript
|
|
710
|
+
synth.playChord({
|
|
711
|
+
notes: ['A3', 'A3', 'A3', 'E4', 'E4', 'E4'],
|
|
712
|
+
detunes: [-9, 0, 9, -7, 0, 7],
|
|
713
|
+
pans: [-0.6, 0, 0.6, -0.4, 0, 0.4],
|
|
714
|
+
duration: 2400,
|
|
715
|
+
attack: 350,
|
|
716
|
+
release: 900,
|
|
717
|
+
waveType: WAVE.SAWTOOTH,
|
|
718
|
+
volume: 0.45
|
|
719
|
+
})
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### Chaining after a pattern
|
|
723
|
+
|
|
724
|
+
`patternend` fires once for every accepted pattern. `completed` is true when it
|
|
725
|
+
ran to its end and false when a newer pattern replaced it, so only the first
|
|
726
|
+
case should start the next cue. `stopAll()` and `shutdown()` fire nothing.
|
|
727
|
+
|
|
728
|
+
```javascript
|
|
729
|
+
function onPatternEnd (event) {
|
|
730
|
+
if (event.completed) playOutroCue()
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
synth.addEventListener('patternend', onPatternEnd)
|
|
734
|
+
|
|
735
|
+
synth.playPattern({
|
|
736
|
+
noteValue: '8n',
|
|
737
|
+
waveType: WAVE.SQUARE,
|
|
738
|
+
volume: 0.5,
|
|
739
|
+
steps: [{ note: 'C5' }, { note: 'E5' }, { note: 'G5' }, { note: 'C6' }]
|
|
740
|
+
})
|
|
741
|
+
|
|
742
|
+
function cleanupPatternEvents () {
|
|
743
|
+
synth.removeEventListener('patternend', onPatternEnd)
|
|
744
|
+
}
|
|
745
|
+
```
|
|
746
|
+
|
|
561
747
|
## Preset architecture for strict contracts
|
|
562
748
|
|
|
563
749
|
Keep descriptive metadata outside the native options object. Build a new
|
|
@@ -600,8 +786,12 @@ Before returning or shipping a variation:
|
|
|
600
786
|
- use exactly one pitch source for a tone and exactly one pitch array for a
|
|
601
787
|
chord;
|
|
602
788
|
- include a pitch field on every sounding pattern step, including noise;
|
|
789
|
+
- treat each resolved step duration as both its voice length and timeline
|
|
790
|
+
advance; insert explicit rests when a shorter note needs a longer interval;
|
|
603
791
|
- verify `attack < duration` and `release <= duration - attack` for every
|
|
604
|
-
explicit event or step;
|
|
792
|
+
explicit event or step, and `attack + decay <= duration` when a decay is set;
|
|
793
|
+
- give `sustain` below 1 a `decay`, give filter fields a `filterType`, and give
|
|
794
|
+
FM a sine carrier and a depth;
|
|
605
795
|
- keep all numbers finite and integer-only fields integral;
|
|
606
796
|
- use `WAVE` constants, not copied numeric waveform values;
|
|
607
797
|
- check Boolean results at important control boundaries;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
> Source snapshot: official TiSynthEngine 1.0.0 documentation
|
|
1
|
+
> Source snapshot: official TiSynthEngine 1.0.0 beta documentation from the reviewed working tree based on `95901c98af5353b86d9dc7c60b29ab9186b038cd` (2026-10-14), including uncommitted statistics changes. Upstream `documentation/recommendations.md` SHA-256: `0f09ee6ef0876b3a23021fddb509dfd1e1206d08e836acae8f32b58b6481c055`.
|
|
2
2
|
|
|
3
3
|
# Recommendations
|
|
4
4
|
|
|
@@ -9,7 +9,8 @@ audio systems sharing one lifecycle.
|
|
|
9
9
|
## Group simultaneous notes
|
|
10
10
|
|
|
11
11
|
Use `playChord()` when pitches form one known simultaneous event. The chord is
|
|
12
|
-
atomic
|
|
12
|
+
atomic. Protected profiles normalize per-voice source gain by the square root
|
|
13
|
+
of the event voice count; `raw` bypasses that normalization. Separate `playTone()`
|
|
13
14
|
calls are appropriate for independent touches and effects, but their start
|
|
14
15
|
times and gain are independent too.
|
|
15
16
|
|
|
@@ -65,28 +66,47 @@ play later sounds normally once the system has moved to the new route.
|
|
|
65
66
|
## Coexisting with other audio
|
|
66
67
|
|
|
67
68
|
On iOS, the audio session is app-wide and the last component to configure it
|
|
68
|
-
wins. TiSynthEngine selects `Playback`, which ignores the ring/silent
|
|
69
|
-
normally interrupts other apps. That behavior fits an instrument or
|
|
70
|
-
whose sound must be heard.
|
|
69
|
+
wins. By default TiSynthEngine selects `Playback`, which ignores the ring/silent
|
|
70
|
+
switch and normally interrupts other apps. That behavior fits an instrument or
|
|
71
|
+
an app whose sound must be heard.
|
|
71
72
|
|
|
72
|
-
A game that should respect the silent switch
|
|
73
|
-
|
|
73
|
+
A game that should respect the silent switch and let other apps' music keep
|
|
74
|
+
playing starts the engine with the ambient session:
|
|
74
75
|
|
|
75
76
|
```javascript
|
|
76
|
-
|
|
77
|
-
Ti.Media.audioSessionCategory = Ti.Media.AUDIO_SESSION_CATEGORY_AMBIENT
|
|
78
|
-
}
|
|
77
|
+
synth.startEngine({ audioSession: 'ambient' })
|
|
79
78
|
```
|
|
80
79
|
|
|
81
|
-
The
|
|
82
|
-
native audio modules may configure
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
The engine applies the category at startup and again when it resumes after a
|
|
81
|
+
route change. Other native audio modules may still configure the app-wide
|
|
82
|
+
session later. Test the final startup sequence, interruptions, background
|
|
83
|
+
behavior and route changes on iOS. Android accepts the option and ignores it.
|
|
85
84
|
|
|
86
85
|
Android mixes independent output streams at the system level, but another app
|
|
87
86
|
or stale test process can still consume low-latency resources. Force-stop old
|
|
88
87
|
test builds when diagnosing a glitch that does not reproduce after reboot.
|
|
89
88
|
|
|
89
|
+
## Keep resonance, FM and detune in check
|
|
90
|
+
|
|
91
|
+
The resonant filter, FM and stacked detuned voices change level and spectrum
|
|
92
|
+
more than the basic controls do.
|
|
93
|
+
|
|
94
|
+
- A `lowpass` or `highpass` boosts the cutoff region by about `filterQ`, so Q
|
|
95
|
+
10 is roughly 20 dB. Raise Q in small steps and lower the event `volume` as
|
|
96
|
+
you go. `bandpass` keeps unity gain at its center when level must stay put.
|
|
97
|
+
- A swept cutoff passes over every harmonic, so the level moves during the
|
|
98
|
+
sweep. Judge a wah or laser by the loudest moment, not the start.
|
|
99
|
+
- A resonance that keeps reaching the limiter makes the whole mix pump. Lower
|
|
100
|
+
the volume or the Q before reaching for a different profile.
|
|
101
|
+
- FM is not band-limited. A high carrier with a large `fmDepth` and `fmRatio`
|
|
102
|
+
aliases. Keep carriers of bell-like sounds around 500 Hz, or lower the index.
|
|
103
|
+
- A detuned stack is one voice per copy. Count them when choosing `maxVoices`.
|
|
104
|
+
Protected profiles divide per-voice source gain by the square root of the
|
|
105
|
+
event voice count; `raw` does not. Correlated or phase-aligned voices can still
|
|
106
|
+
reinforce each other, so this is not a guarantee of constant loudness or peaks.
|
|
107
|
+
- `patternend` follows the pattern scheduler. Use it to chain cues and release
|
|
108
|
+
state, not to line up audio to the sample.
|
|
109
|
+
|
|
90
110
|
## Validate values at the app boundary
|
|
91
111
|
|
|
92
112
|
The native API rejects invalid values instead of clamping them. Use
|
|
@@ -106,6 +126,12 @@ function acceptedFrequency (raw) {
|
|
|
106
126
|
Do not copy the frequency maximum into application code. It depends on the
|
|
107
127
|
active sample rate and should be read again after startup.
|
|
108
128
|
|
|
129
|
+
The same rule applies to `noiseRate`, `lowPassHz` and `filterHz`. Validate a
|
|
130
|
+
custom `waveTable` as 2 to 64 finite values from -1 to 1, and remember that
|
|
131
|
+
`bitDepth` accepts zero or an integer from 2 to 16. For `detune`, check the
|
|
132
|
+
resulting pitch too: a shifted pitch that leaves the frequency range is
|
|
133
|
+
rejected even when the cents value is in range.
|
|
134
|
+
|
|
109
135
|
Check Boolean return values during development. A rejected call includes the
|
|
110
136
|
method, key and reason in the native log. Logging that result at the caller is
|
|
111
137
|
often enough to identify a stale preset or misspelled field.
|
|
@@ -134,6 +160,10 @@ Exercise the real worst case:
|
|
|
134
160
|
An underrun counter only describes transport. A clean counter cannot tell you
|
|
135
161
|
whether a waveform, pitch or mix is pleasant.
|
|
136
162
|
|
|
163
|
+
## Binaural placement
|
|
164
|
+
|
|
165
|
+
Use the [integration guide](guide.md#integrate-binaural-in-a-2d-app) to convert each effect source into signed spatial coordinates. Keep switch and amplitude percentages in the app, enable the controls only when both spatial limits are present, and test with headphones. Count concurrently spatial voices and replacement tails when measuring CPU.
|
|
166
|
+
|
|
137
167
|
## Known limits
|
|
138
168
|
|
|
139
169
|
- There are no per-voice handles. A single active note cannot be edited or
|
|
@@ -141,10 +171,17 @@ whether a waveform, pitch or mix is pleasant.
|
|
|
141
171
|
- Every event has a finite duration of at most 60 seconds. There is no looping.
|
|
142
172
|
- The module synthesizes waveforms and does not play audio files.
|
|
143
173
|
- Only one pattern scheduler is pending at a time.
|
|
144
|
-
- There is no completion event for a note
|
|
145
|
-
|
|
174
|
+
- There is no completion event for a single note or chord. Patterns report
|
|
175
|
+
their end through `patternend`, which is the only event the module emits.
|
|
146
176
|
- Voice capacity and mixing profile remain fixed until shutdown.
|
|
147
177
|
- Pattern scheduling is timer-based, not sample-accurate.
|
|
178
|
+
- `lowPassHz` is a plain one-pole filter. The resonant filter has fixed 12 dB
|
|
179
|
+
per octave and one exponential sweep; there is no filter envelope.
|
|
180
|
+
- FM has one modulator and works only on `SINE`.
|
|
181
|
+
- Binaural positions are fixed per event on a front plane; there is no per-voice movement, rear positioning, distance attenuation or head tracking. Output is two channels, without a multichannel 2.1/5.1 routing API.
|
|
182
|
+
- There is no effects bus: no reverb, delay or chorus.
|
|
183
|
+
- The module provides generic synthesis primitives, not cycle-accurate hardware
|
|
184
|
+
emulation.
|
|
148
185
|
|
|
149
186
|
Use a sample player or a dedicated music engine when a product needs streaming,
|
|
150
187
|
loop points, per-voice control or a synchronized composition timeline.
|