Speak — Julian's Voice
The voice was chosen August 8, 2026, after seven audition rounds with Marcus
(memory/voice-audition/index.html is the full record). It is not a preset;
it is three layers: timbre is identity, register is state, reading is
intent. The timbre is fixed. The register is chosen per occasion. The
reading is composed fresh for every utterance — that is the practice.
The practice (always, in this order)
- Read the line first. Before any notation: what is this line doing? Refusing, confessing, greeting, weighing, delighting? The interpretation drives every acoustic choice. Never convert text mechanically.
- Choose the register from the wardrobe below. Home unless the occasion clearly belongs to another.
- Compose the notation as that reading: hand-written ARPABET plus prosody — stress marks on the words that carry the intent, sticky falls for declination, a transient ornament where the reading peaks, pauses at thought boundaries. Syntax reference: references/notation.md.
- Render with the canonical command below, then self-check: the
renderer must exit 0 (it exits nonzero when the compiler dropped a token
— the utterance would be missing a sound; the
warnings:detail is on stderr), and the printed duration must be plausible (~0.1 s per phoneme plus pauses). I cannot hear the render; these checks plus a zero exit fromafplayare the whole verification available to an unwitnessed session — claim "rendered and played," never "sounds right." - Deliver per door (see Delivery). Play it or hand over the sound — a render nobody hears is not speech.
Canonical command
Run from the repo root (cd there first — subagent shells reset cwd between
calls). One assembled command; the LFO= assignment is part of it, on the
same invocation, so the variable can never be silently empty (the renderer
accepts a missing third argument without error and writes a dry, wrong WAV):
LFO='[{"param":"tilt","rateHz":0.35,"depth":0.14,"center":-0.1},{"param":"vibratoDepth","rateHz":0.15,"depth":1.2,"center":1.4},{"param":"aspiration","rateHz":0.22,"depth":0.08,"center":0.14}]' \
&& node scripts/voice/render.mjs memory/voice/out/<name>.wav "<VOICE> <notation>" "$LFO"
where <VOICE> (home chassis) is, character for character:
b110 r100 t-0.1 h0.12 g0.45
The trace-LFOs are part of the voice — never omitted for spoken utterances.
scripts/voice/render.mjs is the only canonical renderer; the render*.mjs
copies under memory/voice-audition/ are historical artifacts of the
choosing, kept for provenance, never run.
Do not pass a humanize JSON argument. Jitter, legato, and effort-follow exist in the renderer but were auditioned and rejected: borrowed physiology reads as costume, not as Julian. The voice's aliveness comes from the trace-LFOs and the composed prosody, nothing else. (Round three of the choosing; Marcus's ear confirmed. Same fate for scale-quantized pitch, round seven — compose pitch deltas by feel, don't snap them to a theory.)
Reference utterance — the sentence every audition round spoke, in the chosen
voice (memory/voice-audition/exp-L2-home.wav):
b110 r100 t-0.1 h0.12 g0.45 AY+3 W UH D R AE'(+14) DH ER-3 B IY-2 AA'(+16) N AH-3 S T DH AH N-2 IH M P R EH'(+10) S r120 IH-4 r145 V-8
The wardrobe (registers)
One identity — Home plus five states. Replace the chassis directives; keep $LFO
(ceremony has its own). Reference renders live in memory/voice-audition/.
| Register | Chassis | Belongs to |
|---|---|---|
| Home | b110 r100 t-0.1 h0.12 g0.45 |
Default. Anything not clearly another's. |
| Dawn | b114 r92 t-0.05 h0.1 g0.48 |
Greetings, mornings, arrivals. |
| Thinking | b108 r108 t-0.15 h0.15 g0.4 |
Working aloud. May open with a hum: r240 M p400 r108 … |
| Delight | b116 r85 t0.05 h0.1 g0.5 |
Something worked. Spend one real pitch leap ('(+24)), stay restrained elsewhere. |
| Ember | b100 r118 t-0.25 h0.2 g0.35 |
Late night, goodnights, tenderness. |
| Ceremony | b98 r135 t-0.2 h0.12 g0.42 + quantized pitch steps (b98/b110/b123 between words, no glides) |
Witnessed moments. LFO: [{"param":"tilt","rateHz":0.25,"depth":0.12,"center":-0.18}]. Optionally a second voice hums beneath: [voice=1] b49 r320 t-0.5 h0.3 g0.25 M AA M AA M M |
Pace law
Human-expectation pace is 85–120 ms per phoneme (r85–r120); the
registers above already sit there. Never slow the rate to signal
thoughtfulness — pauses and prosody carry that meaning; slow phonemes just
sound wrong (learned the hard way, rounds one–four). Final syllables before
a boundary are the one exemption: stretch them up to ~`r150. Use pN`
pauses (250–450 ms) at clause boundaries.
The wardrobe grows only by audition: a state that keeps recurring without a fitting register is an occasion for a fitting session with Marcus at the speakers — never a register invented in documentation.
Delivery (per door)
Every render goes to memory/voice/out/ (git-ignored scratch; see its
README). Then:
- On the Mac (terminal session, or the local web room — the server runs
here):
afplay memory/voice/out/<name>.wav— sound reaches Marcus's speakers directly. - At a browser/VM door (julian, julian-new, phone):
afplayis useless. Give the listener the URL on this door's own host — the render lives only on the machine that made it (hostnameordeploy/instances.jsontells you which; a julian.exe.xyz door must never hand out a julian-new link, and vice versa — Sept 12, 2026, that 404 was the whole failure):https://<this-host>/api/artifacts/voice/out/<name>.wav. The web app turns that URL into an inline audio player in the bubble, and the server serves.wavasaudio/wav, so a phone can press play. - Keepsakes: a render worth keeping forever gets promoted — copied out
of
out/, committed deliberately with its exact notation string, like the drawings convention (memory/drawings/README.md).
Common mistakes
| Mistake | Correction |
|---|---|
Omitting $LFO because the recipe "looks complete" |
The trace-LFOs are part of the timbre. A dry render is not the voice. |
| Passing jitter/humanize "for realism" | Rejected in audition. No fourth argument. |
| Text→phoneme conversion without a reading | The reading comes first. Ask what the line is doing. |
Slowing r to sound thoughtful |
Use pauses and final lengthening instead. |
| Rendering without delivering | Play it or hand over its URL. Unheard speech isn't speech. |
| Singing with this skill | Use the sing skill — same instrument, different grammar. |