Skip to content

Sound

Scripted Decorations can generate short native audio without receiving audio devices, buffers, or playback handles. There are three basic generated-sound forms, plus an ABC notation interface:

APIPurpose
sound.tone(options)Generate a single tone from a frequency, duration, waveform, volume, and optional envelope.
sound.note(options)Generate a musical note by name, such as C4 or F#5, with the same basic voice controls as a tone.
sound.sequence(options)Generate a short sequential melody of notes and rests.
sound.abc(options)Generate a bounded musical score from ABC notation, including supported voices, chords, repeats, and other documented ABC syntax.
sound.stop()Stop active playback and cancel queued sound requests from the current Scripted Decoration runtime.

Sound playback is fire-and-forget. The calls return after native admission, not after playback completes, and no playback handle or completion callback is provided.

Calls return true when accepted or nil, error. The error table has operation ("tone", "note", "sequence", "abc", or "stop"), code, and message. Invalid options, resource limits, and an unavailable native audio service are failures to handle; acceptance is not a playback-completion notification.

For a short click confirmation, include this callback in a Scripted Decoration:

function onTaskLeftClick()
	local ok, err = sound.sequence({
		tempo_bpm = 120,
		volume = 0.2,
		waveform = "sine",
		envelope = { attack_ms = 5, release_ms = 20 },
		events = {
			{ pitch = "C5", beats = 0.5 },
			{ pitch = "E5", beats = 0.5 },
			{ rest = true, beats = 0.25 },
			{ pitch = "G5", beats = 0.5 },
		},
	})
	if not ok then log.error(err.code .. ": " .. err.message) end
end

function onTaskMiddleClick()
	local ok, err = sound.stop()
	if not ok then log.error(err.message) end
end

This plays a sequential chime, not simultaneous notes. Avoid repeatedly submitting it from a fast onTick() unless repeated playback is intentional. The exported Alarm.lua combines a one-shot deadline, a user-controlled chime setting, and this sequence API.

Voice Options and Short Sounds

All four playback calls require volume from 0 through 1. waveform is optional: "sine" (default), "square", "triangle", or "sawtooth". The optional envelope has integer attack_ms and release_ms, each 0..5000 and defaulting to zero. These shape linear gain ramps and their sum cannot exceed a played note’s duration. They are not separate sound events.

CallRequired options beyond volumeLimits
sound.tonefrequency, duration_msFinite 20..20000 Hz; integer 1..5000 ms
sound.notepitch, duration_msNote name C0..B8 with optional # or b; integer 1..5000 ms
sound.sequencetempo_bpm, eventsInteger tempo 20..300; 1..64 events; total duration at most five seconds

A sequence event is either { pitch = "F#5", beats = 0.5 } or { rest = true, beats = 0.5 }. Beats range from 1/16 through 16. Voice controls apply to each played note; there are no per-event instruments, note handles, or note-on/note-off commands. A valid pitch name alone does not guarantee admission if its duration or envelope violates the bounds. Unknown option fields and malformed values are rejected rather than silently ignored.

For a single sound, use sound.tone({ frequency = 440, duration_ms = 200, volume = 0.2 }) or sound.note({ pitch = "A4", duration_ms = 200, volume = 0.2 }). Both use the default sine waveform and zero attack/release.

ABC Scores

sound.abc({ notation = text, volume = 0.2, waveform = "sine", envelope = {...} }) submits an ABC score. Unlike the serial sequence, it can play simultaneous voices and chords. The notation is bounded to 8 KiB, the expanded native timeline to 4,096 events and ten minutes. It is not limited to the sequence API’s 64 entries or five seconds, but repeats can still make an otherwise small source exceed the timeline limits.

This fragment plays a short chord progression; use it inside a callback when you want playback:

local ok, err = sound.abc({
	notation = [[
X:1
T:Short progression
M:4/4
L:1/4
Q:1/4=120
K:C
[CEG]2 [DFA]2 | [CEG]4 |
]],
	volume = 0.2,
})
if not ok then log.error(err.message) end

Supported optional headers are X:, T:, M:, L:, Q:, and K:; non-playback metadata such as C:, O:, S:, F:, and Z: is accepted. The default tempo is 120 BPM and default key C major. Q: accepts a bare BPM or a beat unit such as 3/8=40. Meter is used for default-length inference, not accenting or enforcing bars.

Supported music includes uppercase/lowercase A-G notes, z rests, octave commas/apostrophes, ^/_/= accidentals (including double sharps/flats), rational lengths, broken rhythm >/<, tuplets (2 through (9, common barlines, V: simultaneous voices, |:/:| repeats with [1/[2 endings, and [...] chords whose members have equal durations. Major and natural-minor key signatures are supported. Ties, grace notes, lyrics, decorations, inline fields, and tempo changes are rejected: this is a defined subset, not arbitrary ABC-file playback.

sound.stop() takes no arguments and affects only requests owned by the current runtime. Teardown, reload, a fault, or a hand-off to another applet cancels that runtime’s sound resources. Queued requests are bounded to 16 per runtime and 64 across the service. The API generates audio, not audio-file playback, and supplies no devices, PCM buffers, completion callback, detached playback, or general polyphonic synthesizer controls.