Markdown To

Specifications v0.1 draft Sheet 03 of 03

audio@0.1

A narration manuscript: a piece of writing meant to be heard, where headings are chapters, prose is the script, and the small amount of direction a narrator needs lives in frontmatter and two sparse inline fields. It is the first spec with a produce adapter — and the file stays a document afterwards.

Status
normative · draft, will change
Inherits
shared conventions envelope, fields, MDTO001–099
Artifacts
schema.json
fixtures/
Owns codes
MDTO300–MDTO399 13 allocated

§ 01

The file, and the manuscript

On the left, a conformance fixture — specs/audio/fixtures/valid/full-direction.md, byte for byte. On the right, the manuscript summary: the direction the file declares, its chapters, and how much of each one is actually narrated. It is deliberately not a player, a waveform or a transcript — this spec describes what would be said; saying it belongs to a synthesis adapter.

Fig. 1 One file, two views — audio@0.1 static render
naming.audio.md
---
markdownto: audio@0.1
title: How the naming argument ended
voice: warm-narrator
pace: relaxed
pronunciations:
  agentsFS: agents F S
  mdto: em dee tee oh
  Kaufmann: KOWF-mun
---

# How the naming argument ended

We spent four days on a name. Here is the short version, and then the version I am
less proud of.

## The short version

agentsFS was never meant to be said out loud. It was a directory name, and directory
names are allowed to be ugly, because the only thing that ever reads them is a shell.

Then we put it on a slide.

[pause:: 2s]

Kaufmann pointed out that we had all been saying it differently for a month, in
meetings, to each other, without anyone stopping to check. That was the end of the
argument, and it was also the end of the slide.

## The version I am less proud of

> [narration:: skip] Reminder: clear the Kaufmann anecdote with him before this goes out.

The command is mdto, which nobody can say either, and which I defended for two entire
days on the grounds that it was shorter. [pause:: 1.5s]

Names are not decisions. They are the residue of decisions, and you can read the whole
argument back out of them later, which is the part that should have worried me.

## Postscript [narration:: skip]

Kaufmann has since changed his mind twice, both times by email, both times at
approximately eleven at night.
rendered · mdto render static · no script

How the naming argument ended

11 narratedof 13 blocks across 4 chaptersread-only

Directionvoice warm-narratorpace relaxedpronunciations 3

2/2
  • How the naming argument ended

    block heading

  • We spent four days on a name. Here is the short version, and then the version I am less proud of.

    block paragraph

The short version

5/5
  • agentsFS was never meant to be said out loud. It was a directory name, and directory names are allowed to be ugly, beca…

    block paragraph

  • Then we put it on a slide.

    block paragraph

  • block paragraphpause 2000ms

  • Kaufmann pointed out that we had all been saying it differently for a month, in meetings, to each other, without anyone…

    block paragraph

The version I am less proud of

3/4
  • Reminder: clear the Kaufmann anecdote with him before this goes out.

    block blockquotenot narrated

  • The command is mdto, which nobody can say either, and which I defended for two entire days on the grounds that it was s…

    block paragraphpause 1500ms

  • Names are not decisions. They are the residue of decisions, and you can read the whole argument back out of them later,…

    block paragraph

Postscript

1/2
  • Kaufmann has since changed his mind twice, both times by email, both times at approximately eleven at night.

    block paragraph

The struck-through rows are the blocks that will not be spoken. A skipped block is marked, never hidden — it is still visible, still diffable, still part of the document; it simply is not read aloud. The markup was produced by @markdownto/web at build time; see site/tools/build-specs.mjs.

Why the manuscript is the script

Every other route to narrated audio starts by converting a document into a production format — a JSON script, an SSML tree, a timeline — after which there are two artifacts and the readable one stops being true. Here there is one file. It reads as a document because it is a document, and the direction it carries is the direction a human narrator would need written in the margin, and no more.

If a construct would only ever be read by the TTS pipeline and never by a person holding the page, it does not belong in this spec.

§ 02

Grammar

Distilled for this page. audio@0.1 shares the envelope, inline-field and preservation rules with every other spec, and nothing else — it has no items, so the shared item vocabulary (priority, due, owner) is not recognized here. Those keys are unknown fields in a manuscript: preserved, never narrated, never an error.

2.1

Direction lives in the envelope

the envelope three added keys
---
markdownto: audio@0.1
title: Field notes, week 32   # optional, reserved — never narrated
voice: warm-narrator          # optional, spec-defined
pace: relaxed                 # optional, spec-defined
pronunciations:               # optional, spec-defined
  agentsFS: agents F S
  mdto: em dee tee oh
  Kaufmann: KOWF-mun
---
voice
A single token matching [A-Za-z][A-Za-z0-9._-]* (MDTO300), resolved against the provider’s catalogue at production time, not at validation time.
pace
slow | relaxed | natural | brisk | fast. Case-insensitive; default natural (MDTO301).
pronunciations
Written form to spoken form. Matching is case-insensitive on whole-word boundaries; multi-word keys are allowed; when several keys could match at one position, the longest wins and ties break by source order. That is the whole of the determinism rule, and it is why the IR carries them as an ordered array rather than a map.
Why validation cannot check the voice name

The catalogue lives with the provider and changes without the file changing. A validator that reached the network would break the offline guarantee, and would make the same file valid on Tuesday and invalid on Wednesday. So the file is checked for shape, and the name is resolved where the failure is immediate, obvious and free. MDTO306 exists so an implementation that does hold a catalogue can say something useful without making it fatal.

The same rule draws the line for everything else: frontmatter carries direction that belongs to the text; flags carry facts about a particular run. So there is no format:, no model:, no output: and no provider: — those are produce flags, recorded in the build receipt where a metered service can be audited against them.

2.2

The chapter level is discovered, not fixed

A chapter is opened by a heading at the document’s chapter level. The chapter level is ## if the document contains at least one ## heading, and # otherwise. The level is decided once, for the whole document, by a single pass over its headings. It never varies within a file.

both shapes people actually write
# Three letters to a younger engineer
# On being slow
# On being wrong
        no `##` anywhere, so `#` is the chapter level

# Field notes, week 32
## The idea
## What I built
        the file contains `##`, so `##` is the chapter
        level and that `#` is the leading title heading
below the level
### under ## chapters is ordinary narrated content. It is read where it stands and opens nothing — which is what makes a long chapter with sections in it a legal manuscript rather than a structural error.
above the level
Permitted only as the first block after the frontmatter, where it is the document’s title heading. Anywhere else it is MDTO310: it is narrated in place and opens nothing, so the prose beneath it cannot silently migrate.
the implicit chapter
Narratable content before the first chapter heading forms a leading chapter titled null — a preface, or a whole document with no headings at all. It exists only when there is such content.
numbering
1..N in document order, counting the implicit chapter. These are the numbers preview --chapter N addresses and the numbers that prefix output filenames. Titles need not be unique; chapters are addressed by number, never by name.
The one shape it cannot express

A file whose chapters are # and which wants a separate # title heading — there, the title becomes chapter 1. Fixing the level at ## instead would have made the letters-in-one-file shape invalid and, worse, would have silently reinterpreted a #-chaptered file as one chapter with a very long title: the failure mode where the tool is confidently wrong and says nothing. If you want a title heading, use ## for chapters — that is the repair, and it is why MDTO310 says so out loud.

2.3

What is narrated

Within a narrated block the rule is what a person would read aloud off the page: link text but not the URL, a wikilink’s name but not its brackets, a tag’s word but not its #, the emphasized words but not the markers. An inline code span is read as its literal characters; only block code is excluded. An image contributes nothing, because alt text describes a picture and turning it into speech would be the spec guessing.

narrated
Paragraphs · headings at any level, including the one that opens the chapter · lists of every kind, each item its own utterance · blockquotes.
never narrated
Fenced and indented code · tables · HTML blocks · thematic breaks, which insert nothing, not even silence. Excluded by kind, permanently: speech is linear and these are not, and there is nowhere on a fence line to write a direction field, so a default that could be overridden would be one nobody could reach.
never narrated, ever
Inline fields, in any Markdown To spec. That is exactly what makes them usable as direction here. List markers, ordinal numbers and task checkboxes are not read either — - [x] Water within reach is read as “Water within reach”.
nothing at all
A document with no narratable content is MDTO330: it names itself a manuscript and gives produce nothing to say.
2.4

Two fields of direction, and no more

A direction field applies to the top-level block that contains it, wherever in that block it is written — position is never meaningful, here or in any other spec. The one finer grain is the list item, because a list is a sequence of separate utterances rather than one run of prose.

a beat, and a producer note
Then we put it on a slide.

[pause:: 2s]

Kaufmann pointed out that we had all been
saying it differently for a month.

> [narration:: skip] Clear this anecdote with him first.
[narration:: skip]
The block it appears in is not narrated. skip is the only value in 0.1 (MDTO320). On a chapter heading it silences the title only — the chapter still exists, is still numbered, and its blocks are still narrated.
[pause:: …]
Holds silence after the block. A number and a unit, ms or s; greater than 0 and at most 60s. The unit is required — a bare 2 is MDTO321, because seconds and milliseconds are three orders of magnitude apart and guessing is not worth being wrong once.
at most one of each
Per block. A repeat is the shared MDTO023: two values with no defined precedence is ambiguity.
Why [narration:: skip] and not a comment

An HTML comment would hide the text from every viewer, which is precisely what the governing principle rejects. A skipped block is still visible, still diffable, still part of the document; the renderer marks it so a reader can see the difference. A skipped block is not deleted content and must never be treated as deletable.

Read audio@0.1 in full — the normative text Shared conventions

§ 03

Verb reference

audio@0.1 owns estimate, produce, preview, voices. It shares nothing with todo or kanban, which is the clearest demonstration that verb vocabularies are spec-owned: those specs mutate a list, this one spends money on a manuscript. Every verb here is read-only with respect to the file.

verbsignaturewhat it does

estimate

mdto audio estimate [--file f] [--voice v] [--pace p] [--json]

Validates, builds the narration plan, and reports — per chapter and in total — narratable characters, estimated duration and estimated cost. It must not call the provider and must be deterministic. Cache awareness is the point: it reports both the full cost and the incremental cost of running produce right now against the current cache. If no price basis is known it says the cost is unknown; it never invents a number.

producemetered

mdto audio produce [--out DIR] [--chapters 2-5 | --chapter N] [--voice v] [--pace p] [--model m] [--dry-run] [--yes]

Chaptered generation, cached and resumable. It prints the estimate and requires confirmation before any provider call, and it refuses on any error-severity diagnostic with no override — todo and kanban offer --force because the worst case is a bad edit to a text file; here the worst case is a bill. Writes NN-<slug>.mp3 per chapter, a joined file, and a receipt whose cost figures are measured, never estimated.

preview

mdto audio preview --chapter N [--voice v] [--pace p] [--play]

Generates exactly one chapter. --chapter is required: preview exists to answer “does this voice work for this passage” without buying the book. Because the cache is keyed on content and settings and not on which verb asked, a preview is a cache entry — a later produce with matching settings reuses it and does not pay twice. That safety is structural, not a check someone has to remember to write.

voices

mdto audio voices [--provider p] [--json]

The catalogue: name, language, a short description, and whether it is usable. The only audio verb that needs no file, and the only one that may reach the network. It is also what MDTO306 is checked against, and the answer to “what do I put in voice:”.

3.1

The cache key is the chapter’s content, and nothing else

A SHA-256 over the spec identifier, the effective voice, pace, provider and model; then, for each narrated unit of that chapter in order, its speech text, and after any unit carrying a pause, that pause in milliseconds. Excluded deliberately: the chapter’s index, its title slug, the output path, the document title, the rest of the frontmatter, source line numbers, and every other chapter. The consequences are the contract.

edit chapter four
Regenerates chapter four. Nothing else is re-bought, ever.
insert a new chapter two
Renumbers the output files and regenerates none of the old ones.
reflow a paragraph
Changes no key, so nothing regenerates.
change voice or pace
Changes every key, so everything regenerates — which is correct, and estimate will have said so first.
Why the chapter is the unit of everything

Chapters are the unit of narration, of output, of caching, of preview and of resumption, and that is not five coincidences. Generation is the expensive step, so the boundary the author already drew for the reader’s benefit is reused as the boundary that keeps a typo in chapter four from re-buying chapters one through three. A tool that regenerates a whole book because one paragraph changed is not a slow implementation of this spec; it is a non-conforming one.

What no verb ever does

Modifies the manuscript·pins or normalizes an identifier·rewrites a field or a frontmatter value·inserts silence the file did not ask for·narrates a code block or a table·calls a provider before the estimate has been shown and acknowledged·regenerates a chapter whose key is unchanged·produces audio from a file with an error ·invents a cost.

§ 04

Diagnostics carry the repair

Direction is the part of a manuscript a reader cannot proof-read, so its diagnostics work hardest. Each one names the offending value, says why the spec refuses it, and — where the mistake has a common cause — says what the author probably meant.

rehearsal.audio.md 5 errors, 2 warnings
---
markdownto: audio@0.1
title: Rehearsal notes
---

The room went quiet. [pause:: 2]

Then the door opened. [pause:: two seconds]

She still did not look up. [pause:: 0s]

[pause:: 90s]

[narration:: whisper] Say this next part gently.

[narration:: skip] Cut this line for now. [pause:: 3s]

[narration:: skip]

And that is the whole scene, which takes four minutes and feels like ten.

mdto validate · verbatim 7 diagnostics

  • MDTO321 error line 6

    The room went quiet. [pause:: 2]

    Malformed pause duration 2: a duration is a number followed by ms or s, for example 2s or 750ms. The unit is required.

  • MDTO321 error line 8

    Then the door opened. [pause:: two seconds]

    Malformed pause duration two seconds: a duration is a number followed by ms or s, for example 2s or 750ms. The unit is required.

  • MDTO322 error line 10

    She still did not look up. [pause:: 0s]

    pause duration 0s is out of range: a pause must be greater than 0 and at most 60s.

  • MDTO322 error line 12

    [pause:: 90s]

    pause duration 90s is out of range: a pause must be greater than 0 and at most 60s.

  • MDTO320 error line 14

    [narration:: whisper] Say this next part gently.

    Unknown value for narration: audio@0.1 defines only skip, found whisper. Per-block voice direction is deliberately not in 0.1.

  • MDTO323 warning line 16

    [narration:: skip] Cut this line for now. [pause:: 3s]

    pause on a block marked [narration:: skip] has no effect: a skipped block contributes neither speech nor silence. Move the pause to a neighbouring block.

  • MDTO324 warning line 18

    [narration:: skip]

    Block consists only of direction fields and produces neither speech nor silence. [narration:: skip] applies to the block it is written in, not to the block after it.

Recovery never silences an author

An invalid narration value recovers to narrated: the spec will not stop reading somebody’s text because of a typo. An invalid or out-of-range pause produces no silence and the block is otherwise unaffected — and it is not additionally reported as having no effect, because the diagnostic it already has is the one that matters. Note that [pause:: 90s] above reports only MDTO322 for exactly that reason.

MDTO320 is the sharpest example of a diagnostic pointing the way: [narration:: whisper] is not an unrecognized value, it is a named extension point that 0.1 chose not to take. Per-block voice direction is deferred explicitly. Do not start honouring values MDTO320 rejects.

4.1

The codes this spec owns

MDTO300 error voice is not a well-formed voice name: a single token, no spaces.
MDTO301 error pace is not one of the five defined values.
MDTO302 error A pronunciations key is not a string. This bites more often than it looks: YAML reads 2026: as an integer.
MDTO303 error A pronunciations value is not a non-empty string. To leave a word alone, delete the entry.
MDTO304 error Two pronunciations keys differ only in case; matching is case-insensitive, so neither could win.
MDTO305 warning A pronunciations key never occurs in any narrated text. Harmless, and usually a typo.
MDTO306 warning voice is well-formed but not in this implementation’s catalogue. Deliberately unfixtured — what it reports depends on a local registry, not on the file.
MDTO310 error Heading is above the document’s chapter level and is not the leading title heading.
MDTO311 error Chapter heading has no title. The chapter still opens; its output filename falls back to chapter-NN.
MDTO312 warning Chapter has no narratable content besides its own heading. Suppressed when MDTO330 fires.
MDTO320 error Unknown value for narration; 0.1 defines only skip.
MDTO321 error Malformed pause duration.
MDTO322 error pause duration is out of range: greater than 0 and at most 60s.
MDTO323 warning pause on a skipped block has no effect; skip wins.
MDTO324 warning Block consists only of direction fields and produces neither speech nor silence — almost always an author who expected the field to apply to the block below it.
MDTO330 error Document has no narratable content.

§ 05

For agents

This is the one spec where a wrong guess costs real money, so its agent-facing document spends most of its length on why. An agent that understands the cache contract will not re-buy a book; one that does not, will.

mdto — working name in progress
$ mdto spec audio
audio@0.1 — agent-facing specification
  1. Purpose   what the format is for
  2. Grammar   heading = chapter, prose = script
  3. Rationale why it is shaped that way
  4. Examples  valid + invalid + diagnostics
  5. Verbs     estimate · produce · preview · voices

$ mdto audio estimate week-32.audio.md
6 chapters · 18,412 narratable characters
est. 14m 20s at 14 chars/sec, pace relaxed
full      6 chapters to generate
incremental  1 chapter — 5 served from cache
note produce is metered — hosted tier coming

$ mdto audio produce --chapter 4 --dry-run
04-what-i-got-wrong.mp3   2,904 chars   est. 3m 27s
cache miss — chapter text changed
note --dry-run: no provider call made
  • Read-only, structurally

    No audio verb edits the .md file, pins an identifier, normalizes a field or stamps a duration. Artifacts are written beside the file, never into it — which is why this spec needs no --pin, no patch engine and no conflict protocol on write. The only thing that can change the file is the author.

  • Nothing is derived in the IR

    No character counts, no durations, no cache keys, no costs. The IR says what the file says; a receipt says what one run did. Keeping them apart means a figure can never be stale relative to the document it describes, and the metered tier’s numbers come from one auditable place.

  • Fetchable, not scrapeable

    The agent-facing document lives at a stable URL beside an llms.txt so an agent never has to scrape a human documentation site to author a conforming manuscript.

    fetch markdownto.ai/specs/audio.md

    universal validaterenderspec