@maccesar/titools 5.2.5 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +27 -37
  2. package/agents/ti-pro.md +1 -1
  3. package/commands/ti-audit.md +1 -1
  4. package/docs/examples.md +15 -13
  5. package/package.json +1 -1
  6. package/skills/ti-synthengine/SKILL.md +62 -111
  7. package/skills/ti-synthengine/assets/example/README.md +50 -59
  8. package/skills/ti-synthengine/assets/example/app.js +67 -65
  9. package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +39 -40
  10. package/skills/ti-synthengine/assets/example/lib/soundPreset.js +34 -0
  11. package/skills/ti-synthengine/assets/index.d.ts +122 -2
  12. package/skills/ti-synthengine/references/advanced-examples.md +4 -4
  13. package/skills/ti-synthengine/references/api.md +40 -18
  14. package/skills/ti-synthengine/references/audio-engine.md +15 -57
  15. package/skills/ti-synthengine/references/examples.md +63 -6
  16. package/skills/ti-synthengine/references/guide.md +61 -57
  17. package/skills/ti-synthengine/references/instruments-and-effects.md +15 -15
  18. package/skills/ti-synthengine/references/native-variation.md +53 -14
  19. package/skills/ti-synthengine/references/recipes-interaction.md +25 -20
  20. package/skills/ti-synthengine/references/recipes.md +19 -16
  21. package/skills/ti-synthengine/references/recommendations.md +12 -12
  22. package/skills/ti-synthengine/references/sound-api.md +197 -0
  23. package/skills/ti-synthengine/references/sound-design.md +7 -7
  24. package/skills/ti-synthengine/references/source-notes.md +62 -172
  25. package/skills/ti-synthengine/references/troubleshooting.md +17 -19
  26. package/skills/ti-synthengine/references/upgrade-2.0.0.md +40 -0
  27. package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
  28. package/skills/ti-synthengine/references/android-sim80-correction.json +0 -162
  29. package/skills/ti-synthengine/references/audio-pipeline-validation.md +0 -19
  30. package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
  31. package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
  32. package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
  33. package/skills/ti-synthengine/references/frontier-physical-validation.json +0 -1918
  34. package/skills/ti-synthengine/references/native-tone-repetition-validation.md +0 -59
  35. package/skills/ti-synthengine/references/native-variation-proposal.md +0 -182
  36. package/skills/ti-synthengine/references/native-variation-validation.md +0 -63
  37. package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
  38. package/skills/ti-synthengine/references/performance-review.json +0 -1146
  39. package/skills/ti-synthengine/references/physical-validation.json +0 -1457
  40. package/skills/ti-synthengine/references/source-snapshot.json +0 -542
  41. package/skills/ti-synthengine/references/stats-performance.json +0 -2992
  42. package/skills/ti-synthengine/references/stats-performance.md +0 -82
  43. package/skills/ti-synthengine/references/studio-validation.json +0 -6202
  44. package/skills/ti-synthengine/references/unified-bypass-performance.json +0 -22425
  45. package/skills/ti-synthengine/references/unified-validation.json +0 -314
@@ -1,97 +1,88 @@
1
- > Source snapshot: official TiSynthEngine documentation at commit `ec04f4e634075c41b55f66279eabcd78b9652d58` (2026-11-02). Published module 1.1.0; public API 1.1.0. Upstream `example/README.md` SHA-256: `e191a701a3a913c85b15503fd256e0aec085eb731a587b1bf4faa43865c81982`.
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 timed sounds, native sequences and statistics with the `sequenceend` event on Android and iOS. It requires module version 1.1.0 and public API 1.1.0 for native finite-tone repetition, attack variation and the recommended note/statistics methods. Held voices, master processing and voice LFOs have copyable examples in the [advanced documentation](../../references/advanced-examples.md#sustained-game-motor); this reference app does not expose those newer controls. The default engine configuration is 16 voices, `conservative` mixing and the `playback` session; master volume starts at 100% and optional diagnostics start off.
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. If the app uses Alloy, create a separate Classic test project and leave the Alloy-generated files in place.
10
- 2. Download the official [Android 1.1.0](https://github.com/macCesar/TiSynthEngine/releases/tag/android-1.1.0) and [iOS 1.1.0](https://github.com/macCesar/TiSynthEngine/releases/tag/ios-1.1.0) packages, or build the current module source. Source builds produce `android/dist/ti.synthengine-android-1.1.0.zip` and `ios/dist/ti.synthengine-iphone-1.1.0.zip`; select version `1.1.0` in both module entries when using them.
11
- 3. Put the archive for each target platform in the test app root. Add these entries inside its `tiapp.xml`:
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
- ```xml
14
- <modules>
15
- <module platform="android" version="1.1.0">ti.synthengine</module>
16
- <module platform="iphone" version="1.1.0">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
- Both packages report public API `1.1.0`; detect variation through both capability/schema announcements. Rebuild and reinstall after switching packages.
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
- 4. Copy the entire `example/` folder into the test app's `Resources/`, including [app.js](app.js), [sequenceScore.js](lib/sequenceScore.js) and [sequenceOwner.js](lib/sequenceOwner.js). It needs no images or audio files.
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
- The example checks the installed API version, spatial limits and the recommended enable/disable statistics methods before constructing the screen. If it requests current packages, replace the native module, then rebuild and reinstall the app; copying module files alone does not update an app already on a device.
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.
26
23
 
27
- Audio starts after the first layout settles. Sound controls report native rejections. If startup fails, the session buttons remain available to retry. Closing the window removes its sequence listener, clears startup/statistics timers, disables diagnostics and shuts down the engine. The example plays finite sounds in the foreground and does not demonstrate background audio. Turn diagnostics off before leaving it in the background.
24
+ ## Native finite-tone repetition
28
25
 
29
- ## Synthesis and sequencing
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
+ ```
30
41
 
31
- - Compare all six waveforms and equivalent English/solfege note names.
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.
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.
35
43
 
36
- Keep system volume moderate while comparing sounds. Master BOOST intentionally raises the bus above unity; use the 100% control to restore it. Repeated taps overlap events and may cause limiting or voice replacement.
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.
37
45
 
38
- ## Headphone positioning
46
+ ## Native attack variation
39
47
 
40
- Use stereo headphones. In the binaural section, compare each of the nine noise positions one at a time: left/right runs from -1 to 1, above/below from 1 to -1. The center still uses binaural processing. Elevation perception varies with the listener and headphones, so the controls cannot guarantee localization for each person.
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).
41
49
 
42
- Select STEREO MODE and repeat points with the same horizontal position. Their height no longer affects placement because both spatial keys are omitted. BINAURAL MODE restores per-event coordinates. These two modes affect this section only; the ordinary stereo demos remain unchanged. An active voice keeps its original position.
50
+ ## Sequence completion and cancellation
43
51
 
44
- SPATIAL CHORD places the whole chord above/right. SPATIAL PATTERN places three broadband hits above/left, below/center and above/right using per-step coordinates. Neither sends stereo pan fields while binaural is active. The output remains two channels on a front plane, without rear positioning, distance attenuation or head tracking. See the [app integration guide](../../references/guide.md#integrate-binaural-in-a-2d-app) for source mapping and amplitude sliders.
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.
45
53
 
46
- ## Audio session
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.
47
55
 
48
- On iOS, compare PLAYBACK SESSION and AMBIENT SESSION. Changing the selection shuts down the engine, silently cancels any pending pattern, starts the new policy and restores the selected master volume. It does not resume the previous sound. The policy is fixed while the stream runs; a second `start()` alone cannot change it.
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.
49
57
 
50
- On an iOS device, play music from another app and try a short sound in ambient mode; compare with playback. Also compare the device's silent mode where available. Ambient should respect silent mode and mix with other apps; playback ignores silent mode and normally interrupts their audio. Android validates both strings but does not change its output. Simulator behavior does not establish the physical silent-mode, interruption or route behavior.
58
+ ## Headphone positioning
51
59
 
52
- ## Sequence completion and cancellation
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.
53
61
 
54
- 1. Tap PATTERN END and wait for `sequenceend: completed`.
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.
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.
58
63
 
59
- All successive attacks run on the native sample clock. The example compiles its note-name scores to millisecond sequences with explicit `duration` and `length`, preserving the original interval and release onset. Each sequence has one of `frequencies`, MIDI `notes`, or `rest: true` per step, with sound shaping in `voice`/`overrides`. Copy the entire `example/` folder, including `lib/sequenceScore.js` and `lib/sequenceOwner.js`, into a Classic app's Resources folder.
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).
60
65
 
61
- Completion marks the final scheduled step; releases and shared effect tails can remain audible. See the [event contract](../../references/api.md#sequenceend-event).
66
+ ## Audio session
62
67
 
63
- ## Statistics
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.
64
69
 
65
- - READ STATS takes a fresh snapshot without resetting counters or enabling diagnostics.
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.
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.
68
71
 
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.
72
+ ## Statistics
70
73
 
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 shared final output stage retains zero samples. Master volume changes use a linear 10 ms ramp; overload can audibly change the mix. Even with zero voices, the stream renders silent blocks and runs output protection and any enabled diagnostics. Expect a small, changing baseline. The two decimal places are display formatting and do not guarantee that precision.
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
- Compare readings on the same device with the same configuration and load; simulator and phone readings are not equivalent. Android backend metrics are best-effort. This iOS backend has no underrun/buffer/latency measurement, and a clean counter cannot establish sound quality.
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
- Run these focused checks from the module repository root:
82
+ From the module repository root:
80
83
 
81
84
  ```sh
82
- node tests/documentation_regression.js
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
- The example regression executes its real action callbacks with simulated Titanium UI/audio boundaries. It covers rejection feedback, placement/bypass, session restart and gain preservation, diagnostic cleanup and close-before-start. It does not render native audio or establish layout, perceived elevation, silent-mode behavior or sound quality. Use the device checklist above, including headphones and an output-route change, before claiming those results.
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.
92
-
93
- ## Native finite-tone repetition
94
-
95
- START REPEATING calls `repeatTone` once with a complete 105 ms square-wave shot and required `intervalMs: 200`. It retains the positive tone ID. EDIT REPEATING calls `updateTone` for that ID, keeping the next deadline and changing subsequent separation to 150 ms. STOP REPEATING calls `stopTone`, cancelling future attacks while admitted shots finish. Close, session changes and global Stop also release or invalidate ownership. There is no JavaScript attack timer for this demo.
96
-
97
- `playTone` remains a standalone Boolean operation and rejects `intervalMs`; `repeatTone` always returns a numeric ID or 0 and requires the interval. Independent JavaScript calls remain supported, including finite bursts scheduled by an application's own event clock. See [the full contract and standalone burst example](../../references/api.md#finite-tone-repetition).
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,21 +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.getDefaults()
10
- if (!defaults || defaults.apiVersion !== '1.1.0') {
11
- throw new Error('TiSynthEngine 1.1.0 is required')
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
18
  || typeof Synth.enableStats !== 'function' || typeof Synth.disableStats !== 'function'
17
- || typeof Synth.repeatTone !== 'function' || typeof Synth.updateTone !== 'function' || typeof Synth.stopTone !== 'function') {
18
- throw new Error('Install the current TiSynthEngine 1.1.0 packages, then rebuild and reinstall this app')
19
+ || typeof Synth.createSound !== 'function') {
20
+ throw new Error('Install the current TiSynthEngine Sound API packages, then rebuild and reinstall this app')
19
21
  }
20
22
 
21
23
  var WAVE = defaults.waveTypes
@@ -24,7 +26,11 @@ var engineRunning = false
24
26
  var startTimer = null
25
27
  var statsTimer = null
26
28
  var diagnosticsEnabled = false
27
- var repeatingToneId = 0
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
+ })
28
34
  var selectedSession = 'playback'
29
35
  var masterVolume = 1
30
36
  var binauralEnabled = true
@@ -202,7 +208,7 @@ function makeHero() {
202
208
  height: Ti.UI.SIZE,
203
209
  top: dp(16)
204
210
  })
205
- badgeRow.add(makeBadge('API 1.1.0', COLORS.indigo))
211
+ badgeRow.add(makeBadge('API 2.0.0', COLORS.indigo))
206
212
  badgeRow.add(makeBadge('ANDROID + iOS', COLORS.surfaceMuted))
207
213
  hero.add(badgeRow)
208
214
  hero.add(statusPill)
@@ -327,14 +333,14 @@ function makeCompactAction(options) {
327
333
  }
328
334
 
329
335
  function playMatchedSine(note) {
330
- return Synth.playTone({
336
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
331
337
  note: note,
332
338
  duration: 600,
333
339
  attack: 10,
334
340
  release: 80,
335
341
  volume: 0.7,
336
342
  waveType: WAVE.SINE
337
- })
343
+ })).play()
338
344
  }
339
345
 
340
346
  function playMatchedArpeggio(pitchKey, pitches) {
@@ -394,18 +400,18 @@ waveNames.forEach(function (name, index) {
394
400
  waveType: waveTypes[index]
395
401
  }
396
402
  if (waveTypes[index] === WAVE.WAVETABLE) options.waveTable = STEPPED_WAVE
397
- return Synth.playTone(options)
403
+ return Synth.createSound(SoundPreset.fromRecipe('tone', options)).play()
398
404
  }
399
405
  }))
400
406
  })
401
407
  waveSection.add(Ti.UI.createView({ height: dp(12) }))
402
408
  scrollView.add(waveSection)
403
409
 
404
- // A repeated finite recipe uses its native owner ID; there is no attack timer.
410
+ // A repeated finite recipe retains its own control; there is no attack timer.
405
411
  function stopRepeatingTone() {
406
- if (!repeatingToneId) return true
407
- if (!Synth.stopTone({ toneId: repeatingToneId })) return false
408
- repeatingToneId = 0
412
+ if (!repeatingTone) return true
413
+ if (!repeatingTone.stop()) return false
414
+ repeatingTone = null
409
415
  return true
410
416
  }
411
417
  var repeatSection = makeSection('Finite-tone repetition',
@@ -414,19 +420,17 @@ var repeatSection = makeSection('Finite-tone repetition',
414
420
  repeatSection.add(makeAction({
415
421
  title: 'START REPEATING', color: COLORS.teal, pressedColor: COLORS.tealPressed,
416
422
  onPress: function () {
417
- if (repeatingToneId) return true
418
- var id = Synth.repeatTone({ frequency: 1380, frequencyEnd: 340,
419
- duration: 105, attack: 2, release: 40, waveType: WAVE.SQUARE,
420
- volume: 0.1, intervalMs: 200 })
421
- if (!Number.isInteger(id) || id <= 0) return false
422
- repeatingToneId = id
423
+ if (repeatingTone) return true
424
+ var control = repeatingSound.play()
425
+ if (!control) return false
426
+ repeatingTone = control
423
427
  return true
424
428
  }
425
429
  }))
426
430
  repeatSection.add(makeAction({
427
431
  title: 'EDIT REPEATING', color: COLORS.teal, pressedColor: COLORS.tealPressed,
428
432
  onPress: function () {
429
- return !!repeatingToneId && Synth.updateTone({ toneId: repeatingToneId, intervalMs: 150, volume: 0.08 })
433
+ return !!repeatingTone && repeatingTone.update({ interval: 150, volume: 0.08 })
430
434
  }
431
435
  }))
432
436
  repeatSection.add(makeAction({
@@ -488,7 +492,7 @@ motionSection.add(makeAction({
488
492
  pressedColor: COLORS.tealPressed,
489
493
  status: 'Pan sweep · left to right',
490
494
  onPress: function () {
491
- return Synth.playTone({
495
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
492
496
  frequency: 440,
493
497
  duration: 2000,
494
498
  attack: 20,
@@ -496,7 +500,7 @@ motionSection.add(makeAction({
496
500
  volume: 0.8,
497
501
  pan: -1.0,
498
502
  panEnd: 1.0
499
- })
503
+ })).play()
500
504
  }
501
505
  }))
502
506
  motionSection.add(makeAction({
@@ -505,7 +509,7 @@ motionSection.add(makeAction({
505
509
  pressedColor: COLORS.pinkPressed,
506
510
  status: 'Pitch sweep · 200 to 900 Hz',
507
511
  onPress: function () {
508
- return Synth.playTone({
512
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
509
513
  frequency: 200,
510
514
  frequencyEnd: 900,
511
515
  duration: 400,
@@ -513,7 +517,7 @@ motionSection.add(makeAction({
513
517
  release: 40,
514
518
  volume: 0.7,
515
519
  waveType: WAVE.SAWTOOTH
516
- })
520
+ })).play()
517
521
  }
518
522
  }))
519
523
  motionSection.add(makeAction({
@@ -522,7 +526,7 @@ motionSection.add(makeAction({
522
526
  pressedColor: COLORS.orangePressed,
523
527
  status: 'LFO vibrato · 5 Hz',
524
528
  onPress: function () {
525
- return Synth.playTone({
529
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
526
530
  frequency: 440,
527
531
  duration: 2000,
528
532
  attack: 20,
@@ -530,7 +534,7 @@ motionSection.add(makeAction({
530
534
  volume: 0.7,
531
535
  lfoFreq: 5,
532
536
  lfoDepth: 30
533
- })
537
+ })).play()
534
538
  }
535
539
  }))
536
540
  motionSection.add(Ti.UI.createView({ height: dp(16) }))
@@ -551,7 +555,7 @@ retroSection.add(makeAction({
551
555
  pressedColor: COLORS.brownPressed,
552
556
  status: 'Filtered sample-and-hold noise sweep',
553
557
  onPress: function () {
554
- return Synth.playTone({
558
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
555
559
  frequency: 120,
556
560
  duration: 760,
557
561
  attack: 1,
@@ -562,7 +566,7 @@ retroSection.add(makeAction({
562
566
  noiseRateEnd: 650,
563
567
  lowPassHz: 2500,
564
568
  bitDepth: 8
565
- })
569
+ })).play()
566
570
  }
567
571
  }))
568
572
  retroSection.add(makeAction({
@@ -571,7 +575,7 @@ retroSection.add(makeAction({
571
575
  pressedColor: COLORS.orangePressed,
572
576
  status: 'Square pulse-width sweep',
573
577
  onPress: function () {
574
- return Synth.playTone({
578
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
575
579
  note: 'A3',
576
580
  duration: 700,
577
581
  attack: 3,
@@ -580,7 +584,7 @@ retroSection.add(makeAction({
580
584
  waveType: WAVE.SQUARE,
581
585
  pulseWidth: 0.1,
582
586
  pulseWidthEnd: 0.86
583
- })
587
+ })).play()
584
588
  }
585
589
  }))
586
590
  retroSection.add(makeAction({
@@ -589,7 +593,7 @@ retroSection.add(makeAction({
589
593
  pressedColor: COLORS.tealPressed,
590
594
  status: 'Custom stepped wavetable',
591
595
  onPress: function () {
592
- return Synth.playTone({
596
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
593
597
  note: 'D4',
594
598
  duration: 680,
595
599
  attack: 4,
@@ -598,7 +602,7 @@ retroSection.add(makeAction({
598
602
  waveType: WAVE.WAVETABLE,
599
603
  waveTable: STEPPED_WAVE,
600
604
  bitDepth: 8
601
- })
605
+ })).play()
602
606
  }
603
607
  }))
604
608
  retroSection.add(makeAction({
@@ -658,14 +662,14 @@ harmonySection.add(makeAction({
658
662
  pressedColor: COLORS.blue,
659
663
  status: 'C major triad · sine',
660
664
  onPress: function () {
661
- return Synth.playChord({
665
+ return Synth.createSound(SoundPreset.fromRecipe('chord', {
662
666
  frequencies: [261.63, 329.63, 392.0],
663
667
  duration: 1200,
664
668
  attack: 20,
665
669
  release: 300,
666
670
  volume: 0.7,
667
671
  panSpread: 0.8
668
- })
672
+ })).play()
669
673
  }
670
674
  }))
671
675
  harmonySection.add(makeAction({
@@ -674,7 +678,7 @@ harmonySection.add(makeAction({
674
678
  pressedColor: COLORS.indigoPressed,
675
679
  status: 'C major chord · square and wide',
676
680
  onPress: function () {
677
- return Synth.playChord({
681
+ return Synth.createSound(SoundPreset.fromRecipe('chord', {
678
682
  notes: ['C4', 'E4', 'G4', 'C5'],
679
683
  duration: 1000,
680
684
  attack: 10,
@@ -682,7 +686,7 @@ harmonySection.add(makeAction({
682
686
  volume: 0.6,
683
687
  waveType: WAVE.SQUARE,
684
688
  pans: [-0.9, -0.3, 0.3, 0.9]
685
- })
689
+ })).play()
686
690
  }
687
691
  }))
688
692
  harmonySection.add(Ti.UI.createView({ height: dp(16) }))
@@ -776,7 +780,7 @@ expressiveSection.add(makeAction({
776
780
  pressedColor: COLORS.greenPressed,
777
781
  status: 'Pluck · decay and sustain',
778
782
  onPress: function () {
779
- return Synth.playTone({
783
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
780
784
  note: 'E4',
781
785
  duration: 900,
782
786
  attack: 2,
@@ -785,7 +789,7 @@ expressiveSection.add(makeAction({
785
789
  release: 380,
786
790
  volume: 0.6,
787
791
  waveType: WAVE.TRIANGLE
788
- })
792
+ })).play()
789
793
  }
790
794
  }))
791
795
  expressiveSection.add(makeAction({
@@ -794,7 +798,7 @@ expressiveSection.add(makeAction({
794
798
  pressedColor: COLORS.orangePressed,
795
799
  status: 'Wah · band-pass cutoff sweep',
796
800
  onPress: function () {
797
- return Synth.playTone({
801
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
798
802
  note: 'A2',
799
803
  duration: 1000,
800
804
  attack: 10,
@@ -805,7 +809,7 @@ expressiveSection.add(makeAction({
805
809
  filterHz: 350,
806
810
  filterHzEnd: 2400,
807
811
  filterQ: 5
808
- })
812
+ })).play()
809
813
  }
810
814
  }))
811
815
  expressiveSection.add(makeAction({
@@ -814,7 +818,7 @@ expressiveSection.add(makeAction({
814
818
  pressedColor: COLORS.indigoPressed,
815
819
  status: 'Bell · FM index decays',
816
820
  onPress: function () {
817
- return Synth.playTone({
821
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
818
822
  note: 'C5',
819
823
  duration: 2600,
820
824
  attack: 1,
@@ -826,7 +830,7 @@ expressiveSection.add(makeAction({
826
830
  fmRatio: 3.5,
827
831
  fmDepth: 4,
828
832
  fmDepthEnd: 0
829
- })
833
+ })).play()
830
834
  }
831
835
  }))
832
836
  expressiveSection.add(makeAction({
@@ -835,7 +839,7 @@ expressiveSection.add(makeAction({
835
839
  pressedColor: COLORS.blue,
836
840
  status: 'Electric piano · FM index mellows',
837
841
  onPress: function () {
838
- return Synth.playTone({
842
+ return Synth.createSound(SoundPreset.fromRecipe('tone', {
839
843
  note: 'E3',
840
844
  duration: 1800,
841
845
  attack: 3,
@@ -847,7 +851,7 @@ expressiveSection.add(makeAction({
847
851
  fmRatio: 1,
848
852
  fmDepth: 2.2,
849
853
  fmDepthEnd: 0.25
850
- })
854
+ })).play()
851
855
  }
852
856
  }))
853
857
  expressiveSection.add(makeAction({
@@ -856,7 +860,7 @@ expressiveSection.add(makeAction({
856
860
  pressedColor: COLORS.pinkPressed,
857
861
  status: 'Thick chord · detuned voices',
858
862
  onPress: function () {
859
- return Synth.playChord({
863
+ return Synth.createSound(SoundPreset.fromRecipe('chord', {
860
864
  notes: ['A3', 'E4', 'A3', 'E4', 'A3', 'E4'],
861
865
  detunes: [-9, -9, 0, 0, 9, 9],
862
866
  duration: 3200,
@@ -867,7 +871,7 @@ expressiveSection.add(makeAction({
867
871
  filterType: 'lowpass',
868
872
  filterHz: 1500,
869
873
  panSpread: 0.8
870
- })
874
+ })).play()
871
875
  }
872
876
  }))
873
877
 
@@ -887,11 +891,11 @@ expressiveSection.add(makeAction({
887
891
  volume: [0.291, 0.3], fmDepth: [2.944, 3.456], pitchCents: [-12, 12]
888
892
  } }
889
893
  }
890
- return Synth.playTone(options)
894
+ return Synth.createSound(SoundPreset.fromRecipe('tone', options)).play()
891
895
  }
892
896
  }))
893
897
 
894
- var patternEndLabel = makeText('sequenceend: no pattern yet', {
898
+ var patternEndLabel = makeText('end: no pattern yet', {
895
899
  top: dp(8),
896
900
  left: dp(16),
897
901
  right: dp(16),
@@ -901,21 +905,20 @@ var patternEndLabel = makeText('sequenceend: no pattern yet', {
901
905
 
902
906
  function onSequenceEnd(event) {
903
907
  patternEndLabel.text = event.reason === 'completed'
904
- ? 'sequenceend: completed'
905
- : 'sequenceend: stopped'
908
+ ? 'end: completed'
909
+ : 'end: stopped'
906
910
  setStatus(patternEndLabel.text, event.reason === 'completed' ? COLORS.green : COLORS.yellow)
907
911
  }
908
912
 
909
- Synth.addEventListener('sequenceend', onSequenceEnd)
910
913
 
911
914
  expressiveSection.add(makeAction({
912
915
  title: 'PATTERN END\nTap once to finish; tap again quickly to replace',
913
916
  color: COLORS.yellow,
914
917
  pressedColor: COLORS.yellowPressed,
915
918
  textColor: COLORS.canvas,
916
- status: 'Pattern running · waiting for sequenceend',
919
+ status: 'Pattern running · waiting for end',
917
920
  onPress: function () {
918
- patternEndLabel.text = 'sequenceend: pattern running'
921
+ patternEndLabel.text = 'end: pattern running'
919
922
  return publishSequence(sequenceScore({
920
923
  bpm: 132,
921
924
  noteValue: '8n',
@@ -984,10 +987,10 @@ spatialPoints.forEach(function (point, index) {
984
987
  title: point[0] + '\nNoise burst',
985
988
  fontSize: 11, color: COLORS.teal, pressedColor: COLORS.tealPressed,
986
989
  onPress: function () {
987
- return Synth.playTone(Object.assign({
990
+ return Synth.createSound(SoundPreset.fromRecipe('tone', Object.assign({
988
991
  frequency: 200, waveType: WAVE.NOISE, duration: 550,
989
992
  attack: 5, release: 200, volume: 0.3
990
- }, placement(point[1], point[2])))
993
+ }, placement(point[1], point[2])))).play()
991
994
  }
992
995
  }))
993
996
  })
@@ -996,10 +999,10 @@ spatialSection.add(makeAction({
996
999
  color: COLORS.blueDark, pressedColor: COLORS.blue,
997
1000
  onPress: function () {
998
1001
  var position = binauralEnabled ? { spatialX: 0.7, spatialY: 1 } : { panSpread: 0, pans: [0.7, 0.7, 0.7] }
999
- return Synth.playChord(Object.assign({
1002
+ return Synth.createSound(SoundPreset.fromRecipe('chord', Object.assign({
1000
1003
  notes: ['C4', 'E4', 'G4'], duration: 1200, attack: 15, release: 300,
1001
1004
  volume: 0.4, waveType: WAVE.SAWTOOTH
1002
- }, position))
1005
+ }, position))).play()
1003
1006
  }
1004
1007
  }))
1005
1008
  spatialSection.add(makeAction({
@@ -1035,10 +1038,10 @@ var sessionLabel = makeText('Session: playback', { left: dp(16), right: dp(16),
1035
1038
  color: COLORS.blueDark, pressedColor: COLORS.blue,
1036
1039
  accessibilityHint: 'Restarts the engine with this session policy',
1037
1040
  onPress: function () {
1038
- repeatingToneId = 0; sequenceOwner.reset(); Synth.shutdown()
1041
+ repeatingTone = null; sequenceOwner.reset(); Synth.shutdown()
1039
1042
  engineRunning = false
1040
1043
  selectedSession = session
1041
- patternEndLabel.text = 'sequenceend: cancelled by shutdown (no event)'
1044
+ patternEndLabel.text = 'end: cancelled by shutdown (no event)'
1042
1045
  return startAppCycles()
1043
1046
  }
1044
1047
  }))
@@ -1142,7 +1145,7 @@ boostRow.add(makeGridAction({
1142
1145
  status: 'Future attacks cancelled; active voices fading',
1143
1146
  accessibilityHint: 'Cancels future attacks and fades current voices',
1144
1147
  onPress: function () {
1145
- repeatingToneId = 0; sequenceOwner.stop(); Synth.stopAll()
1148
+ repeatingTone = null; sequenceOwner.stop(); Synth.stopAll()
1146
1149
  }
1147
1150
  }))
1148
1151
  controlSection.add(boostRow)
@@ -1164,7 +1167,7 @@ win.add(scrollView)
1164
1167
  // ---------------------------------------------------------------------------
1165
1168
 
1166
1169
  function setMasterVolume(volume) {
1167
- if (!Synth.setVolume({ volume: volume })) return false
1170
+ if (!Synth.setVolume(volume)) return false
1168
1171
  masterVolume = volume
1169
1172
  return true
1170
1173
  }
@@ -1210,7 +1213,6 @@ function cleanup() {
1210
1213
  stopStatsPolling()
1211
1214
  Synth.disableStats()
1212
1215
  diagnosticsEnabled = false
1213
- Synth.removeEventListener('sequenceend', onSequenceEnd)
1214
1216
  win.removeEventListener('postlayout', onFirstPostLayout)
1215
1217
  win.removeEventListener('open', hideAndroidActionBar)
1216
1218
  win.removeEventListener('focus', hideAndroidActionBar)
@@ -1221,7 +1223,7 @@ function cleanup() {
1221
1223
  startTimer = null
1222
1224
  }
1223
1225
  if (engineRunning) {
1224
- repeatingToneId = 0; sequenceOwner.reset(); Synth.shutdown()
1226
+ repeatingTone = null; sequenceOwner.reset(); Synth.shutdown()
1225
1227
  engineRunning = false
1226
1228
  }
1227
1229
  }