@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,91 +1,88 @@
|
|
|
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 `example/README.md` SHA-256: `db0f5921699859a1dd6918f215a4d30b008dd933cae458f21033216d7f807dd0`.
|
|
2
2
|
|
|
3
3
|
# Interactive example
|
|
4
4
|
|
|
5
|
-
The Classic Titanium app in [app.js](app.js) demonstrates the
|
|
5
|
+
The Classic Titanium app in [app.js](app.js) demonstrates the Sound object API on Android and iOS. It requires TiSynth 2.0.0, reported by `getContract().version`. `getDefaults().version` reports the same installed module version.
|
|
6
|
+
|
|
7
|
+
The default configuration is 16 voices, `conservative` mixing and the `playback` session. Master volume starts at 100% and optional diagnostics start off. Creating a Sound does not reserve a voice.
|
|
6
8
|
|
|
7
9
|
## Run it
|
|
8
10
|
|
|
9
|
-
1. Create a Classic Titanium application with Android and/or iOS support.
|
|
10
|
-
2.
|
|
11
|
-
3.
|
|
11
|
+
1. Create a Classic Titanium application with Android and/or iOS support. For Alloy projects, keep the example in a separate Classic test app so the Alloy-generated files remain intact.
|
|
12
|
+
2. Build and install the current module source for the target platforms. Select the version declared by that build in `tiapp.xml`; rebuilding and reinstalling the test app is necessary after changing module packages.
|
|
13
|
+
3. Copy the entire `example/` directory into the app's `Resources/`, including `app.js` and every file under `lib/`. No images or audio files are needed.
|
|
14
|
+
4. Run with Titanium SDK 13.2.0.GA or newer, Android compile API 35 or newer, and iOS 15 or newer. See the [installation guide](../../references/guide.md#install-the-module).
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
<modules>
|
|
15
|
-
<module platform="android" version="1.0.2">ti.synthengine</module>
|
|
16
|
-
<module platform="iphone" version="1.0.2">ti.synthengine</module>
|
|
17
|
-
</modules>
|
|
18
|
-
```
|
|
16
|
+
The example checks the Sound contract, spatial limits and enable/disable statistics methods before constructing the screen. Audio starts after layout settles. If startup fails, session buttons remain available to retry. Closing the window retires its pattern listener, clears startup/statistics timers, disables diagnostics and shuts down the engine.
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
This example plays foreground finite sounds and patterns. Sustained notes, master processing and voice LFOs have separate examples in the [advanced documentation](../../references/advanced-examples.md#sustained-game-motor).
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
5. Build and run the test app with the Titanium CLI or your existing Titanium launch configuration. Use Titanium SDK 13.2.0.GA or newer, Android compile API 35 or newer, and iOS 15 or newer. See the [module installation guide](../../references/guide.md#install-the-module).
|
|
20
|
+
## Synthesis and sequencing
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
`createSound({ sound, playback })` separates synthesis properties from interpretation and timing. `lib/soundPreset.js` converts the example's editable recipes to this schema without playing audio. Finite sounds call `Sound.play()` and preserve their Boolean acceptance result. Chords declare `type: 'chord'`, including a chord containing just one note.
|
|
23
|
+
|
|
24
|
+
## Native finite-tone repetition
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
var synth = require('ti.synthengine')
|
|
28
|
+
var shot = synth.createSound({
|
|
29
|
+
sound: { waveType: synth.getContract().waveTypes.SQUARE, attack: 2, release: 40, volume: 0.1 },
|
|
30
|
+
playback: { type: 'repeat', frequencies: [1380], frequencyEnd: 340, duration: 105, interval: 200 }
|
|
31
|
+
})
|
|
32
|
+
if (synth.start({ maxVoices: 16, mixingProfile: 'conservative' })) {
|
|
33
|
+
synth.setVolume(1)
|
|
34
|
+
var repetition = shot.play()
|
|
35
|
+
if (repetition) {
|
|
36
|
+
repetition.update({ interval: 150, volume: 0.08 })
|
|
37
|
+
// Later: repetition.stop()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
Finite `duration` includes release; envelope times and repetition `interval` are milliseconds. Per-call overrides do not change the Sound definition. Repetition controls keep private execution identity. An update preserves the next deadline and changes subsequent attacks; stopping cancels future attacks while admitted shots finish. There is no JavaScript attack timer. Close, session changes and STOP ALL also release or invalidate repetition ownership.
|
|
28
43
|
|
|
29
|
-
|
|
44
|
+
The screen retains its waveform, English/solfege pitch, stereo motion, vibrato, variable-rate noise, pulse-width, wavetable, quantization, chord, arpeggio, tempo/swing, percussion, decay/sustain, filter, FM and detune demonstrations. Master BOOST intentionally raises the bus above unity; 100% restores unity. Repeated taps can overlap voices and cause limiting or replacement.
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
- Try stereo pan, pitch sweep, vibrato, variable-rate noise, pulse width, custom wavetable and quantization.
|
|
33
|
-
- Compare atomic chords with explicit pans, matched arpeggios, tempo/swing and noise percussion.
|
|
34
|
-
- Hear decay/sustain, resonant filtering, FM bell/electric piano and detuned voices.
|
|
46
|
+
## Native attack variation
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
ATTACK VARIATION retains its metal-strike recipe. When both `capabilities.variation === true` and `variationSchemaVersion === 1` are announced, the renderer chooses pitch, FM depth and level once per admitted attack. Otherwise the recipe omits variation. See the [variation guide](../../references/native-variation.md).
|
|
37
49
|
|
|
38
|
-
##
|
|
50
|
+
## Sequence completion and cancellation
|
|
39
51
|
|
|
40
|
-
|
|
52
|
+
`lib/sequenceScore.js` compiles note-name scores to millisecond onsets and explicit release onsets. `soundPreset.js` maps step spacing to `playback.interval` or `steps[].interval`, keeps `length`, and places synthesis in `sound` or `steps[].sound`. The native sample clock schedules every attack.
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
`lib/sequenceOwner.js` retains the pattern control returned by `Sound.play()`. Tap PATTERN END once to see `end: completed`. Tap repeatedly to request a stop, coalesce pending scores and publish only the newest complete score after the matching control emits `end`. Its callback receives `{ reason: 'completed' | 'stopped' }`, without native IDs. Late events from retired controls cannot change application ownership.
|
|
43
55
|
|
|
44
|
-
|
|
56
|
+
A completed pattern can leave audible releases or shared effect tails. STOP ALL cancels future attacks and fades active voices. Changing sessions or closing the example shuts down its engine and cancels pending application publication.
|
|
45
57
|
|
|
46
|
-
##
|
|
58
|
+
## Headphone positioning
|
|
47
59
|
|
|
48
|
-
|
|
60
|
+
Use stereo headphones to compare the nine noise positions individually. Horizontal coordinates run from -1 to 1; vertical coordinates run from 1 above to -1 below. Center still uses binaural processing. Elevation perception varies by listener and headphones.
|
|
49
61
|
|
|
50
|
-
|
|
62
|
+
STEREO MODE omits both spatial coordinates and uses horizontal pan alone. BINAURAL MODE restores coordinates for new events; an active voice retains its original position. These modes affect only this section. SPATIAL CHORD places a whole chord above/right, and SPATIAL PATTERN places three broadband hits using per-step coordinates. Binaural events omit stereo pan fields.
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
Output remains two channels on a front plane, without rear positioning, distance attenuation or head tracking. See the [integration guide](../../references/guide.md#integrate-binaural-in-a-2d-app).
|
|
53
65
|
|
|
54
|
-
|
|
55
|
-
2. Tap it twice quickly. The example requests stop for its accepted sequence, coalesces repeated taps to the latest complete score, and publishes it only after the matching retirement event frees capacity. Native `sequenceend` events report `{ sequenceId, reason: 'completed' | 'stopped' }`.
|
|
56
|
-
3. Selecting a session shuts down the engine and restarts it with the selected policy. Events already queued for JavaScript can still arrive.
|
|
57
|
-
4. STOP ALL releases active voices and cancels future attacks. Closing the example shuts down its engine.
|
|
66
|
+
## Audio session
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
PLAYBACK SESSION and AMBIENT SESSION shut down the existing stream, cancel pending patterns, start the selected policy and restore master volume. They do not resume old sounds; a second `start()` alone cannot change a running session's policy.
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
On physical iOS hardware, compare another app's music, silent mode and an output-route change. Ambient respects silent mode and mixes with other apps; playback ignores silent mode and normally interrupts them. Android accepts both strings without changing its output. Simulator results do not establish physical interruption or route behavior.
|
|
62
71
|
|
|
63
72
|
## Statistics
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
- TOGGLE DIAGNOSTICS enables callback timing and signal meters, refreshing at 5 Hz. Toggle it off to stop the timer and optional measurements; basic counters remain available.
|
|
67
|
-
- RESET STATS clears accumulated statistics without stopping voices, cancelling queued work or changing the diagnostic switch. The next callback applies render-owned resets.
|
|
68
|
-
|
|
69
|
-
The panel shows active/peak/spatial/tail voices, pending commands, replacements, request/publication rejections, priority drops, callback load, stereo output peak and backend underruns. It displays `n/a` for unmeasured or unavailable values, keeping them distinct from zero. Signal windows need about 200 ms of output after enabling. Native snapshots also include RMS, limiter reduction, sample rate, buffer and latency fields; consult the [complete field reference](../../references/api.md#getstats) when integrating a full diagnostic view.
|
|
70
|
-
|
|
71
|
-
Render load divides callback elapsed time by the audio budget for that block; it does not measure total app CPU. The panel multiplies the native fraction by 100, so a reading of 1.20% means that fraction of the block budget, not 120%.
|
|
74
|
+
READ STATS takes a snapshot without resetting counters or enabling diagnostics. TOGGLE DIAGNOSTICS enables callback timing and signal meters, refreshing at 5 Hz; disabling it stops the timer and optional measurements while retaining basic counters. RESET STATS clears accumulated statistics without stopping voices or cancelling scheduled work.
|
|
72
75
|
|
|
73
|
-
The
|
|
76
|
+
The panel shows active/peak/spatial/tail voices, pending commands, replacements, request/publication rejections, priority drops, callback load, stereo peak and backend underruns. Unavailable measurements remain `n/a`, distinct from zero. Signal windows need about 200 ms after enabling diagnostics. See the [field reference](../../references/api.md#getstats).
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
Render load is callback elapsed time divided by the budget for that block. It is not total app CPU. The stream continues rendering silence with zero voices. Compare measurements on the same device, route, configuration and load; a clean counter does not establish audio quality. Turn diagnostics off before leaving this foreground example in the background.
|
|
76
79
|
|
|
77
80
|
## Checks and limits
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
From the module repository root:
|
|
80
83
|
|
|
81
84
|
```sh
|
|
82
|
-
node tests/
|
|
83
|
-
node --test tests/example_app_regression.js
|
|
85
|
+
node --test tests/example_app_regression.js tests/sequence_owner_regression.js
|
|
84
86
|
```
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
## Native attack variation
|
|
90
|
-
|
|
91
|
-
The **ATTACK VARIATION** button submits the same metal-strike recipe on each press. With `capabilities.variation === true` and `variationSchemaVersion === 1`, the renderer chooses pitch, FM depth and level once per admitted attack. Older packages receive the original recipe without the dictionary. This button uses independent timed events; the [variation guide](../../references/native-variation.md) separately demonstrates sustained ownership and native sequence retriggers. Physical listening acceptance remains separate from host correctness checks.
|
|
88
|
+
These checks execute the real interactive callbacks and the current public adapter over native payload spies. They cover preserved synthesis parameters, rejection feedback, spatial bypass, repeated finite shots, session/gain restoration, diagnostics, native retirement coalescing, active updates, late events and close-before-start. Native validator replay and platform listening acceptance remain separate. Host checks do not establish layout, perceived elevation, silent-mode behavior or sound quality.
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
+
var SoundPreset = require('lib/soundPreset')
|
|
1
2
|
var sequenceScore = require('lib/sequenceScore')
|
|
2
3
|
var Synth = require('ti.synthengine')
|
|
3
4
|
|
|
4
5
|
var sequenceOwner = require('lib/sequenceOwner')(Synth, {
|
|
5
|
-
rejected: function() { setStatus('Request rejected · sequence publication', COLORS.red) }
|
|
6
|
+
rejected: function() { setStatus('Request rejected · sequence publication', COLORS.red) },
|
|
7
|
+
ended: onSequenceEnd
|
|
6
8
|
})
|
|
7
9
|
function publishSequence(options) { return sequenceOwner.submit(options) }
|
|
8
10
|
|
|
9
|
-
var defaults = Synth.
|
|
10
|
-
if (!defaults || defaults.
|
|
11
|
-
throw new Error('TiSynthEngine
|
|
11
|
+
var defaults = Synth.getContract()
|
|
12
|
+
if (!defaults || defaults.version !== '2.0.0') {
|
|
13
|
+
throw new Error('TiSynthEngine 2.0.0 is required')
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
if (!defaults.limits || !defaults.limits.spatialX || !defaults.limits.spatialY
|
|
15
17
|
|| typeof Synth.getStats !== 'function' || typeof Synth.resetStats !== 'function'
|
|
16
|
-
|| typeof Synth.
|
|
17
|
-
|
|
18
|
+
|| typeof Synth.enableStats !== 'function' || typeof Synth.disableStats !== 'function'
|
|
19
|
+
|| typeof Synth.createSound !== 'function') {
|
|
20
|
+
throw new Error('Install the current TiSynthEngine Sound API packages, then rebuild and reinstall this app')
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
var WAVE = defaults.waveTypes
|
|
@@ -23,6 +26,11 @@ var engineRunning = false
|
|
|
23
26
|
var startTimer = null
|
|
24
27
|
var statsTimer = null
|
|
25
28
|
var diagnosticsEnabled = false
|
|
29
|
+
var repeatingTone = null
|
|
30
|
+
var repeatingSound = Synth.createSound({
|
|
31
|
+
sound: { attack: 2, release: 40, waveType: WAVE.SQUARE, volume: 0.1 },
|
|
32
|
+
playback: { type: 'repeat', frequencies: [1380], frequencyEnd: 340, duration: 105, interval: 200 }
|
|
33
|
+
})
|
|
26
34
|
var selectedSession = 'playback'
|
|
27
35
|
var masterVolume = 1
|
|
28
36
|
var binauralEnabled = true
|
|
@@ -200,7 +208,7 @@ function makeHero() {
|
|
|
200
208
|
height: Ti.UI.SIZE,
|
|
201
209
|
top: dp(16)
|
|
202
210
|
})
|
|
203
|
-
badgeRow.add(makeBadge('API
|
|
211
|
+
badgeRow.add(makeBadge('API 2.0.0', COLORS.indigo))
|
|
204
212
|
badgeRow.add(makeBadge('ANDROID + iOS', COLORS.surfaceMuted))
|
|
205
213
|
hero.add(badgeRow)
|
|
206
214
|
hero.add(statusPill)
|
|
@@ -325,14 +333,14 @@ function makeCompactAction(options) {
|
|
|
325
333
|
}
|
|
326
334
|
|
|
327
335
|
function playMatchedSine(note) {
|
|
328
|
-
return Synth.
|
|
336
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
329
337
|
note: note,
|
|
330
338
|
duration: 600,
|
|
331
339
|
attack: 10,
|
|
332
340
|
release: 80,
|
|
333
341
|
volume: 0.7,
|
|
334
342
|
waveType: WAVE.SINE
|
|
335
|
-
})
|
|
343
|
+
})).play()
|
|
336
344
|
}
|
|
337
345
|
|
|
338
346
|
function playMatchedArpeggio(pitchKey, pitches) {
|
|
@@ -392,13 +400,45 @@ waveNames.forEach(function (name, index) {
|
|
|
392
400
|
waveType: waveTypes[index]
|
|
393
401
|
}
|
|
394
402
|
if (waveTypes[index] === WAVE.WAVETABLE) options.waveTable = STEPPED_WAVE
|
|
395
|
-
return Synth.
|
|
403
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', options)).play()
|
|
396
404
|
}
|
|
397
405
|
}))
|
|
398
406
|
})
|
|
399
407
|
waveSection.add(Ti.UI.createView({ height: dp(12) }))
|
|
400
408
|
scrollView.add(waveSection)
|
|
401
409
|
|
|
410
|
+
// A repeated finite recipe retains its own control; there is no attack timer.
|
|
411
|
+
function stopRepeatingTone() {
|
|
412
|
+
if (!repeatingTone) return true
|
|
413
|
+
if (!repeatingTone.stop()) return false
|
|
414
|
+
repeatingTone = null
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
417
|
+
var repeatSection = makeSection('Finite-tone repetition',
|
|
418
|
+
'The same complete 105 ms shot repeats every 200 ms. Edit its future attacks; stop lets current shots finish.',
|
|
419
|
+
COLORS.teal)
|
|
420
|
+
repeatSection.add(makeAction({
|
|
421
|
+
title: 'START REPEATING', color: COLORS.teal, pressedColor: COLORS.tealPressed,
|
|
422
|
+
onPress: function () {
|
|
423
|
+
if (repeatingTone) return true
|
|
424
|
+
var control = repeatingSound.play()
|
|
425
|
+
if (!control) return false
|
|
426
|
+
repeatingTone = control
|
|
427
|
+
return true
|
|
428
|
+
}
|
|
429
|
+
}))
|
|
430
|
+
repeatSection.add(makeAction({
|
|
431
|
+
title: 'EDIT REPEATING', color: COLORS.teal, pressedColor: COLORS.tealPressed,
|
|
432
|
+
onPress: function () {
|
|
433
|
+
return !!repeatingTone && repeatingTone.update({ interval: 150, volume: 0.08 })
|
|
434
|
+
}
|
|
435
|
+
}))
|
|
436
|
+
repeatSection.add(makeAction({
|
|
437
|
+
title: 'STOP REPEATING', color: COLORS.surfaceMuted, pressedColor: COLORS.surfaceRaised,
|
|
438
|
+
onPress: stopRepeatingTone
|
|
439
|
+
}))
|
|
440
|
+
scrollView.add(repeatSection)
|
|
441
|
+
|
|
402
442
|
// ---------------------------------------------------------------------------
|
|
403
443
|
// 2. Note names
|
|
404
444
|
// ---------------------------------------------------------------------------
|
|
@@ -452,7 +492,7 @@ motionSection.add(makeAction({
|
|
|
452
492
|
pressedColor: COLORS.tealPressed,
|
|
453
493
|
status: 'Pan sweep · left to right',
|
|
454
494
|
onPress: function () {
|
|
455
|
-
return Synth.
|
|
495
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
456
496
|
frequency: 440,
|
|
457
497
|
duration: 2000,
|
|
458
498
|
attack: 20,
|
|
@@ -460,7 +500,7 @@ motionSection.add(makeAction({
|
|
|
460
500
|
volume: 0.8,
|
|
461
501
|
pan: -1.0,
|
|
462
502
|
panEnd: 1.0
|
|
463
|
-
})
|
|
503
|
+
})).play()
|
|
464
504
|
}
|
|
465
505
|
}))
|
|
466
506
|
motionSection.add(makeAction({
|
|
@@ -469,7 +509,7 @@ motionSection.add(makeAction({
|
|
|
469
509
|
pressedColor: COLORS.pinkPressed,
|
|
470
510
|
status: 'Pitch sweep · 200 to 900 Hz',
|
|
471
511
|
onPress: function () {
|
|
472
|
-
return Synth.
|
|
512
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
473
513
|
frequency: 200,
|
|
474
514
|
frequencyEnd: 900,
|
|
475
515
|
duration: 400,
|
|
@@ -477,7 +517,7 @@ motionSection.add(makeAction({
|
|
|
477
517
|
release: 40,
|
|
478
518
|
volume: 0.7,
|
|
479
519
|
waveType: WAVE.SAWTOOTH
|
|
480
|
-
})
|
|
520
|
+
})).play()
|
|
481
521
|
}
|
|
482
522
|
}))
|
|
483
523
|
motionSection.add(makeAction({
|
|
@@ -486,7 +526,7 @@ motionSection.add(makeAction({
|
|
|
486
526
|
pressedColor: COLORS.orangePressed,
|
|
487
527
|
status: 'LFO vibrato · 5 Hz',
|
|
488
528
|
onPress: function () {
|
|
489
|
-
return Synth.
|
|
529
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
490
530
|
frequency: 440,
|
|
491
531
|
duration: 2000,
|
|
492
532
|
attack: 20,
|
|
@@ -494,7 +534,7 @@ motionSection.add(makeAction({
|
|
|
494
534
|
volume: 0.7,
|
|
495
535
|
lfoFreq: 5,
|
|
496
536
|
lfoDepth: 30
|
|
497
|
-
})
|
|
537
|
+
})).play()
|
|
498
538
|
}
|
|
499
539
|
}))
|
|
500
540
|
motionSection.add(Ti.UI.createView({ height: dp(16) }))
|
|
@@ -515,7 +555,7 @@ retroSection.add(makeAction({
|
|
|
515
555
|
pressedColor: COLORS.brownPressed,
|
|
516
556
|
status: 'Filtered sample-and-hold noise sweep',
|
|
517
557
|
onPress: function () {
|
|
518
|
-
return Synth.
|
|
558
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
519
559
|
frequency: 120,
|
|
520
560
|
duration: 760,
|
|
521
561
|
attack: 1,
|
|
@@ -526,7 +566,7 @@ retroSection.add(makeAction({
|
|
|
526
566
|
noiseRateEnd: 650,
|
|
527
567
|
lowPassHz: 2500,
|
|
528
568
|
bitDepth: 8
|
|
529
|
-
})
|
|
569
|
+
})).play()
|
|
530
570
|
}
|
|
531
571
|
}))
|
|
532
572
|
retroSection.add(makeAction({
|
|
@@ -535,7 +575,7 @@ retroSection.add(makeAction({
|
|
|
535
575
|
pressedColor: COLORS.orangePressed,
|
|
536
576
|
status: 'Square pulse-width sweep',
|
|
537
577
|
onPress: function () {
|
|
538
|
-
return Synth.
|
|
578
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
539
579
|
note: 'A3',
|
|
540
580
|
duration: 700,
|
|
541
581
|
attack: 3,
|
|
@@ -544,7 +584,7 @@ retroSection.add(makeAction({
|
|
|
544
584
|
waveType: WAVE.SQUARE,
|
|
545
585
|
pulseWidth: 0.1,
|
|
546
586
|
pulseWidthEnd: 0.86
|
|
547
|
-
})
|
|
587
|
+
})).play()
|
|
548
588
|
}
|
|
549
589
|
}))
|
|
550
590
|
retroSection.add(makeAction({
|
|
@@ -553,7 +593,7 @@ retroSection.add(makeAction({
|
|
|
553
593
|
pressedColor: COLORS.tealPressed,
|
|
554
594
|
status: 'Custom stepped wavetable',
|
|
555
595
|
onPress: function () {
|
|
556
|
-
return Synth.
|
|
596
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
557
597
|
note: 'D4',
|
|
558
598
|
duration: 680,
|
|
559
599
|
attack: 4,
|
|
@@ -562,7 +602,7 @@ retroSection.add(makeAction({
|
|
|
562
602
|
waveType: WAVE.WAVETABLE,
|
|
563
603
|
waveTable: STEPPED_WAVE,
|
|
564
604
|
bitDepth: 8
|
|
565
|
-
})
|
|
605
|
+
})).play()
|
|
566
606
|
}
|
|
567
607
|
}))
|
|
568
608
|
retroSection.add(makeAction({
|
|
@@ -622,14 +662,14 @@ harmonySection.add(makeAction({
|
|
|
622
662
|
pressedColor: COLORS.blue,
|
|
623
663
|
status: 'C major triad · sine',
|
|
624
664
|
onPress: function () {
|
|
625
|
-
return Synth.
|
|
665
|
+
return Synth.createSound(SoundPreset.fromRecipe('chord', {
|
|
626
666
|
frequencies: [261.63, 329.63, 392.0],
|
|
627
667
|
duration: 1200,
|
|
628
668
|
attack: 20,
|
|
629
669
|
release: 300,
|
|
630
670
|
volume: 0.7,
|
|
631
671
|
panSpread: 0.8
|
|
632
|
-
})
|
|
672
|
+
})).play()
|
|
633
673
|
}
|
|
634
674
|
}))
|
|
635
675
|
harmonySection.add(makeAction({
|
|
@@ -638,7 +678,7 @@ harmonySection.add(makeAction({
|
|
|
638
678
|
pressedColor: COLORS.indigoPressed,
|
|
639
679
|
status: 'C major chord · square and wide',
|
|
640
680
|
onPress: function () {
|
|
641
|
-
return Synth.
|
|
681
|
+
return Synth.createSound(SoundPreset.fromRecipe('chord', {
|
|
642
682
|
notes: ['C4', 'E4', 'G4', 'C5'],
|
|
643
683
|
duration: 1000,
|
|
644
684
|
attack: 10,
|
|
@@ -646,7 +686,7 @@ harmonySection.add(makeAction({
|
|
|
646
686
|
volume: 0.6,
|
|
647
687
|
waveType: WAVE.SQUARE,
|
|
648
688
|
pans: [-0.9, -0.3, 0.3, 0.9]
|
|
649
|
-
})
|
|
689
|
+
})).play()
|
|
650
690
|
}
|
|
651
691
|
}))
|
|
652
692
|
harmonySection.add(Ti.UI.createView({ height: dp(16) }))
|
|
@@ -740,7 +780,7 @@ expressiveSection.add(makeAction({
|
|
|
740
780
|
pressedColor: COLORS.greenPressed,
|
|
741
781
|
status: 'Pluck · decay and sustain',
|
|
742
782
|
onPress: function () {
|
|
743
|
-
return Synth.
|
|
783
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
744
784
|
note: 'E4',
|
|
745
785
|
duration: 900,
|
|
746
786
|
attack: 2,
|
|
@@ -749,7 +789,7 @@ expressiveSection.add(makeAction({
|
|
|
749
789
|
release: 380,
|
|
750
790
|
volume: 0.6,
|
|
751
791
|
waveType: WAVE.TRIANGLE
|
|
752
|
-
})
|
|
792
|
+
})).play()
|
|
753
793
|
}
|
|
754
794
|
}))
|
|
755
795
|
expressiveSection.add(makeAction({
|
|
@@ -758,7 +798,7 @@ expressiveSection.add(makeAction({
|
|
|
758
798
|
pressedColor: COLORS.orangePressed,
|
|
759
799
|
status: 'Wah · band-pass cutoff sweep',
|
|
760
800
|
onPress: function () {
|
|
761
|
-
return Synth.
|
|
801
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
762
802
|
note: 'A2',
|
|
763
803
|
duration: 1000,
|
|
764
804
|
attack: 10,
|
|
@@ -769,7 +809,7 @@ expressiveSection.add(makeAction({
|
|
|
769
809
|
filterHz: 350,
|
|
770
810
|
filterHzEnd: 2400,
|
|
771
811
|
filterQ: 5
|
|
772
|
-
})
|
|
812
|
+
})).play()
|
|
773
813
|
}
|
|
774
814
|
}))
|
|
775
815
|
expressiveSection.add(makeAction({
|
|
@@ -778,7 +818,7 @@ expressiveSection.add(makeAction({
|
|
|
778
818
|
pressedColor: COLORS.indigoPressed,
|
|
779
819
|
status: 'Bell · FM index decays',
|
|
780
820
|
onPress: function () {
|
|
781
|
-
return Synth.
|
|
821
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
782
822
|
note: 'C5',
|
|
783
823
|
duration: 2600,
|
|
784
824
|
attack: 1,
|
|
@@ -790,7 +830,7 @@ expressiveSection.add(makeAction({
|
|
|
790
830
|
fmRatio: 3.5,
|
|
791
831
|
fmDepth: 4,
|
|
792
832
|
fmDepthEnd: 0
|
|
793
|
-
})
|
|
833
|
+
})).play()
|
|
794
834
|
}
|
|
795
835
|
}))
|
|
796
836
|
expressiveSection.add(makeAction({
|
|
@@ -799,7 +839,7 @@ expressiveSection.add(makeAction({
|
|
|
799
839
|
pressedColor: COLORS.blue,
|
|
800
840
|
status: 'Electric piano · FM index mellows',
|
|
801
841
|
onPress: function () {
|
|
802
|
-
return Synth.
|
|
842
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', {
|
|
803
843
|
note: 'E3',
|
|
804
844
|
duration: 1800,
|
|
805
845
|
attack: 3,
|
|
@@ -811,7 +851,7 @@ expressiveSection.add(makeAction({
|
|
|
811
851
|
fmRatio: 1,
|
|
812
852
|
fmDepth: 2.2,
|
|
813
853
|
fmDepthEnd: 0.25
|
|
814
|
-
})
|
|
854
|
+
})).play()
|
|
815
855
|
}
|
|
816
856
|
}))
|
|
817
857
|
expressiveSection.add(makeAction({
|
|
@@ -820,7 +860,7 @@ expressiveSection.add(makeAction({
|
|
|
820
860
|
pressedColor: COLORS.pinkPressed,
|
|
821
861
|
status: 'Thick chord · detuned voices',
|
|
822
862
|
onPress: function () {
|
|
823
|
-
return Synth.
|
|
863
|
+
return Synth.createSound(SoundPreset.fromRecipe('chord', {
|
|
824
864
|
notes: ['A3', 'E4', 'A3', 'E4', 'A3', 'E4'],
|
|
825
865
|
detunes: [-9, -9, 0, 0, 9, 9],
|
|
826
866
|
duration: 3200,
|
|
@@ -831,7 +871,7 @@ expressiveSection.add(makeAction({
|
|
|
831
871
|
filterType: 'lowpass',
|
|
832
872
|
filterHz: 1500,
|
|
833
873
|
panSpread: 0.8
|
|
834
|
-
})
|
|
874
|
+
})).play()
|
|
835
875
|
}
|
|
836
876
|
}))
|
|
837
877
|
|
|
@@ -851,11 +891,11 @@ expressiveSection.add(makeAction({
|
|
|
851
891
|
volume: [0.291, 0.3], fmDepth: [2.944, 3.456], pitchCents: [-12, 12]
|
|
852
892
|
} }
|
|
853
893
|
}
|
|
854
|
-
return Synth.
|
|
894
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', options)).play()
|
|
855
895
|
}
|
|
856
896
|
}))
|
|
857
897
|
|
|
858
|
-
var patternEndLabel = makeText('
|
|
898
|
+
var patternEndLabel = makeText('end: no pattern yet', {
|
|
859
899
|
top: dp(8),
|
|
860
900
|
left: dp(16),
|
|
861
901
|
right: dp(16),
|
|
@@ -865,21 +905,20 @@ var patternEndLabel = makeText('sequenceend: no pattern yet', {
|
|
|
865
905
|
|
|
866
906
|
function onSequenceEnd(event) {
|
|
867
907
|
patternEndLabel.text = event.reason === 'completed'
|
|
868
|
-
? '
|
|
869
|
-
: '
|
|
908
|
+
? 'end: completed'
|
|
909
|
+
: 'end: stopped'
|
|
870
910
|
setStatus(patternEndLabel.text, event.reason === 'completed' ? COLORS.green : COLORS.yellow)
|
|
871
911
|
}
|
|
872
912
|
|
|
873
|
-
Synth.addEventListener('sequenceend', onSequenceEnd)
|
|
874
913
|
|
|
875
914
|
expressiveSection.add(makeAction({
|
|
876
915
|
title: 'PATTERN END\nTap once to finish; tap again quickly to replace',
|
|
877
916
|
color: COLORS.yellow,
|
|
878
917
|
pressedColor: COLORS.yellowPressed,
|
|
879
918
|
textColor: COLORS.canvas,
|
|
880
|
-
status: 'Pattern running · waiting for
|
|
919
|
+
status: 'Pattern running · waiting for end',
|
|
881
920
|
onPress: function () {
|
|
882
|
-
patternEndLabel.text = '
|
|
921
|
+
patternEndLabel.text = 'end: pattern running'
|
|
883
922
|
return publishSequence(sequenceScore({
|
|
884
923
|
bpm: 132,
|
|
885
924
|
noteValue: '8n',
|
|
@@ -948,10 +987,10 @@ spatialPoints.forEach(function (point, index) {
|
|
|
948
987
|
title: point[0] + '\nNoise burst',
|
|
949
988
|
fontSize: 11, color: COLORS.teal, pressedColor: COLORS.tealPressed,
|
|
950
989
|
onPress: function () {
|
|
951
|
-
return Synth.
|
|
990
|
+
return Synth.createSound(SoundPreset.fromRecipe('tone', Object.assign({
|
|
952
991
|
frequency: 200, waveType: WAVE.NOISE, duration: 550,
|
|
953
992
|
attack: 5, release: 200, volume: 0.3
|
|
954
|
-
}, placement(point[1], point[2])))
|
|
993
|
+
}, placement(point[1], point[2])))).play()
|
|
955
994
|
}
|
|
956
995
|
}))
|
|
957
996
|
})
|
|
@@ -960,10 +999,10 @@ spatialSection.add(makeAction({
|
|
|
960
999
|
color: COLORS.blueDark, pressedColor: COLORS.blue,
|
|
961
1000
|
onPress: function () {
|
|
962
1001
|
var position = binauralEnabled ? { spatialX: 0.7, spatialY: 1 } : { panSpread: 0, pans: [0.7, 0.7, 0.7] }
|
|
963
|
-
return Synth.
|
|
1002
|
+
return Synth.createSound(SoundPreset.fromRecipe('chord', Object.assign({
|
|
964
1003
|
notes: ['C4', 'E4', 'G4'], duration: 1200, attack: 15, release: 300,
|
|
965
1004
|
volume: 0.4, waveType: WAVE.SAWTOOTH
|
|
966
|
-
}, position))
|
|
1005
|
+
}, position))).play()
|
|
967
1006
|
}
|
|
968
1007
|
}))
|
|
969
1008
|
spatialSection.add(makeAction({
|
|
@@ -999,10 +1038,10 @@ var sessionLabel = makeText('Session: playback', { left: dp(16), right: dp(16),
|
|
|
999
1038
|
color: COLORS.blueDark, pressedColor: COLORS.blue,
|
|
1000
1039
|
accessibilityHint: 'Restarts the engine with this session policy',
|
|
1001
1040
|
onPress: function () {
|
|
1002
|
-
sequenceOwner.reset(); Synth.shutdown()
|
|
1041
|
+
repeatingTone = null; sequenceOwner.reset(); Synth.shutdown()
|
|
1003
1042
|
engineRunning = false
|
|
1004
1043
|
selectedSession = session
|
|
1005
|
-
patternEndLabel.text = '
|
|
1044
|
+
patternEndLabel.text = 'end: cancelled by shutdown (no event)'
|
|
1006
1045
|
return startAppCycles()
|
|
1007
1046
|
}
|
|
1008
1047
|
}))
|
|
@@ -1046,7 +1085,7 @@ statsSection.add(makeAction({
|
|
|
1046
1085
|
title: 'TOGGLE DIAGNOSTICS', color: COLORS.orange, pressedColor: COLORS.orangePressed,
|
|
1047
1086
|
accessibilityHint: 'Enables or disables timing and signal measurements',
|
|
1048
1087
|
onPress: function () {
|
|
1049
|
-
if (!Synth.
|
|
1088
|
+
if (!(diagnosticsEnabled ? Synth.disableStats() : Synth.enableStats())) return false
|
|
1050
1089
|
diagnosticsEnabled = !diagnosticsEnabled
|
|
1051
1090
|
stopStatsPolling()
|
|
1052
1091
|
if (diagnosticsEnabled) statsTimer = setInterval(refreshStats, 200)
|
|
@@ -1103,10 +1142,10 @@ boostRow.add(makeGridAction({
|
|
|
1103
1142
|
title: 'STOP ALL\nRelease active voices',
|
|
1104
1143
|
color: COLORS.redDark,
|
|
1105
1144
|
pressedColor: COLORS.red,
|
|
1106
|
-
status: '
|
|
1107
|
-
accessibilityHint: '
|
|
1145
|
+
status: 'Future attacks cancelled; active voices fading',
|
|
1146
|
+
accessibilityHint: 'Cancels future attacks and fades current voices',
|
|
1108
1147
|
onPress: function () {
|
|
1109
|
-
sequenceOwner.stop(); Synth.stopAll()
|
|
1148
|
+
repeatingTone = null; sequenceOwner.stop(); Synth.stopAll()
|
|
1110
1149
|
}
|
|
1111
1150
|
}))
|
|
1112
1151
|
controlSection.add(boostRow)
|
|
@@ -1128,7 +1167,7 @@ win.add(scrollView)
|
|
|
1128
1167
|
// ---------------------------------------------------------------------------
|
|
1129
1168
|
|
|
1130
1169
|
function setMasterVolume(volume) {
|
|
1131
|
-
if (!Synth.setVolume(
|
|
1170
|
+
if (!Synth.setVolume(volume)) return false
|
|
1132
1171
|
masterVolume = volume
|
|
1133
1172
|
return true
|
|
1134
1173
|
}
|
|
@@ -1170,10 +1209,10 @@ function hideAndroidActionBar() {
|
|
|
1170
1209
|
}
|
|
1171
1210
|
|
|
1172
1211
|
function cleanup() {
|
|
1212
|
+
if (!stopRepeatingTone()) Ti.API.warn('Repeating tone stop rejected; shutting down owner')
|
|
1173
1213
|
stopStatsPolling()
|
|
1174
|
-
Synth.
|
|
1214
|
+
Synth.disableStats()
|
|
1175
1215
|
diagnosticsEnabled = false
|
|
1176
|
-
Synth.removeEventListener('sequenceend', onSequenceEnd)
|
|
1177
1216
|
win.removeEventListener('postlayout', onFirstPostLayout)
|
|
1178
1217
|
win.removeEventListener('open', hideAndroidActionBar)
|
|
1179
1218
|
win.removeEventListener('focus', hideAndroidActionBar)
|
|
@@ -1184,7 +1223,7 @@ function cleanup() {
|
|
|
1184
1223
|
startTimer = null
|
|
1185
1224
|
}
|
|
1186
1225
|
if (engineRunning) {
|
|
1187
|
-
sequenceOwner.reset(); Synth.shutdown()
|
|
1226
|
+
repeatingTone = null; sequenceOwner.reset(); Synth.shutdown()
|
|
1188
1227
|
engineRunning = false
|
|
1189
1228
|
}
|
|
1190
1229
|
}
|