Reference
The script format
A script is a YAML document describing what to play through the night. There is no separate language to learn beyond the handful of statements below, and no editor to fight: write one in any text editor, keep it in a file, and add it to the app.
You do not have to write one yourself: let an assistant write your script, or gather your own sounds and scripts on your computer.
Shape
A script has a name, a version, an optional master volume, and a
body — a list of statements executed in order. Each
statement is a single-key mapping whose key names what it does.
name: MILD with REM targeting
version: 1
volume: 0.4 # master gain for this script, 0..1
body:
- log: "run started"
- wait: 90m # let the first sleep cycle pass
- repeat: 6 # finite loop
body:
- play: chime
- wait: 10s
- play: bell
- wait: 20m
- with: { gain: 0.6 } # scoped: applies to everything inside
body:
- repeat: infinite
until: { elapsed: { gte: 8h } }
body:
- if: { all: [{ rem: true }, { hr: { lt: 60 } }] }
then:
- play: { signal: chime, gain: 0.8, wait: true }
- wait: 3s
- play: chime
else:
- wait: 5m
Statements
| Statement | Form | What it does |
|---|---|---|
play |
play: <name> or
play: { signal, gain?, rate?, wait? }
|
Plays a signal. gain multiplies the gain of the
enclosing scope. wait: true blocks until
playback finishes; by default the script carries on
immediately.
|
wait |
wait: 10s · 2m ·
1h30m
|
Pauses for a duration. |
repeat |
repeat: <n> or infinite,
with body: and optional until:
|
A loop. until is checked before each pass. An
infinite loop with no until runs
until you stop the run.
|
if |
if: <condition>, then:,
optional else:
|
A branch. |
with |
with: { gain?, rate? }, body:
|
Applies an effect to everything inside it. Nested scopes
multiply.
gain and rate are the only effects
available.
|
set |
set: { volume: 0.3 } |
Changes the master volume for the rest of the run. |
log |
log: "text" |
Writes your own marker into the run log. |
stop |
stop: |
Ends the run cleanly. |
Durations
Durations are a number followed by a unit — ms,
s, m, h — and can be
joined, as in 1h30m. Anywhere a duration is accepted
you may instead write $short, $medium or
$long, which expand to the period presets configured
in the app's Settings. Referring to a preset that has no value
configured is an error the app reports by name before the run
starts.
Conditions
Conditions compare a context field using eq,
ne, lt, lte,
gt or gte, and combine with
all, any and not.
| Field | Type | Where the value comes from |
|---|---|---|
elapsed |
duration | Time since the run started |
clock |
"HH:MM" |
The device clock |
iteration |
number |
Index of the enclosing repeat, counting from
zero
|
hr, hrv |
number | A context provider |
rem |
true / false | A context provider |
sleepStage |
awake · light ·
deep · rem
|
A context provider |
charging |
true / false | Whether the phone is plugged in |
movement, soundLevel |
number | The phone's own sensing, when it is turned on in Settings |
- In a version 1 script, a field with no reading is false. If nothing is providing a value, the condition simply does not hold, the app notes it in the log, and the script carries on rather than stalling.
Version 2
A script that begins with version: 2 can ask for
three more things. A version 1 script keeps meaning exactly what
it meant.
name: Soft, and honest about what it knows
version: 2
sounds:
Cue: soft-pulse # a role; you choose its sound in the app
body:
- wait: $long
- repeat: infinite
until: { clock: { gte: "06:00" } } # ends at dawn, not at bedtime
body:
- if: { rem: true }
unavailable: skip # no reading: play nothing
then:
- play: { signal: $Cue, soften: strong, wait: true }
- wait: $medium
-
A reading that is missing is unknown, not false.
notof unknown is still unknown, and a cue behind anifplays only when its condition is known to be true. Say what should happen otherwise withunavailable:on anif(skip, which is the default,thenorelse) or on arepeatthat has anuntil(continue, the default, orstop). -
Softening can be asked for.
soften: gentleorsoften: strongon aplay, or on awithfor everything inside it, asks for at least that much softening for those sounds. A script can ask for more than the app's Soft sounds setting, never for less. -
Clock times are ordered within the night. The
night is taken to begin at noon, so 23:00 comes before 06:00 and
clock: { gte: "06:00" }becomes true at six in the morning. In a version 1 script the same condition is already true at 23:00.
You do not have to write any of this by hand. In the app's Library, the sliders button on a script, or a long press on it, opens an editor: Adjust changes how long a script waits, how many times it repeats and how loud its cues are, and Steps adds, removes, moves and changes its steps. What you make is saved as a script of your own beside the original.
Published scripts and signals
These are hosted here and can be added to the app directly. Copy a URL, then in LucidDream open Library → Import new script (or Import new signal) and paste it. Anything you add from a URL can be saved offline so it is available with no network at 3 a.m.
To add everything at once, use
Library → Import extension → Import from URL and
enter
https://luciddream.countinglight.com/content/std/; the
manifest.json in that folder is found for you. The last
five addresses you used are listed, narrowing as you type. You can
load several extensions: a name you already have is skipped, and the
rest is added. To use a copy of the folder from your own device,
choose Choose a folder on this device, which on
Android includes a folder in Google Drive. If your storage offers no
folder, Choose files lets you select the manifest
together with the files it lists.
Using LucidDream 1.0? It reads only the scripts of
version 1, so these collections are refused there. Its own
collections stay available: enter
https://luciddream.countinglight.com/content-v1/std/
instead (or content-v1/gal/, buka/,
vs/).
Loading the published library…