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. not of unknown is still unknown, and a cue behind an if plays only when its condition is known to be true. Say what should happen otherwise with unavailable: on an if (skip, which is the default, then or else) or on a repeat that has an until (continue, the default, or stop).
  • Softening can be asked for. soften: gentle or soften: strong on a play, or on a with for 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…