From 93b4bb62ebf367b99fd0f869534dfe376828997c Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Mon, 3 Aug 2026 19:00:10 -0500 Subject: [PATCH] docs(reference): the generated index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine appendices and no way to look anything up. The index is generated from the same introspection the appendices are, crossed with the book's own Markdown: every name the program can utter — configuration sections and keys, CLI flags, scene types, overlays, display modes, generators, effects, live-tune targets — against the sections that discuss it. Locators come from scanning the committed chapters for each name in a code span or a heading and taking the section it sits in, so the index cannot name a term the program does not have or point at a section the book does not have. Ordered so the section *written about* a term leads, then prose before the tables: "where is dither explained" answers with the section that explains dithering, not with its row in Appendix A. Section titles are entries too — "Companding", "The Audio Slot" — which is what lets a reader look a thing up before they know its key. A key is listed bare, and again qualified with its section only where two sections share the name, which is the rule the Notation section states. Each locator carries its chapter, because three sections here are called some case of "MIDI" and a bare title left the reader with `midi`, MIDI, `midi` and no way to tell them apart. No `number` in its front matter, so it renders after Appendix J as a plain heading instead of claiming a letter. It reuses the fields-table machinery whole; the one template change is that the identity column no longer justifies, since a section title set into 1.5in and stretched to the margins is three words with two rivers through it. --- CHANGELOG.md | 10 + CLAUDE.md | 5 +- Makefile | 16 +- docs/reference/01-introduction.md | 7 + docs/reference/30-index.md | 657 ++++++++++++++++++++++++++++ docs/reference/README.md | 15 +- docs/shared/template.typ | 8 +- scripts/gen_reference_appendices.py | 402 ++++++++++++++++- tests/test_reference_appendices.py | 119 ++++- 9 files changed, 1216 insertions(+), 23 deletions(-) create mode 100644 docs/reference/30-index.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 533cbea3..32508b4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,16 @@ the version and stamps it with the date. instead of a page with a big numeral on it. The anchor is GitHub's own, because the Markdown is the book: the same link resolves on github.com and in the PDF. One that resolves nowhere fails the build and names the nearest ones it knows. +- **The Programmer's Reference Guide has an index**, and it is generated like + its appendices. Every name the program can utter goes in — configuration + sections and keys, command-line flags, scene types, overlays, display modes, + generators, effects and live-tune targets — against the sections of the book + that discuss it, the section written about the name first and the table it + appears in last. Section titles are entries too, so "Companding" and "The + Audio Slot" can be looked up by a reader who does not yet know which key they + mean. A key is listed bare, and again qualified with its section where two + sections share the name, which is the rule the book's own Notation section + states. Every locator is a link that works on github.com and in the PDF alike. - **The books' symbols no longer depend on the machine that built them.** Jost has no ✓ and no →, and Typst was filling them from whatever was installed — so the compatibility matrix was set in a heavy upright check locally and a thin diff --git a/CLAUDE.md b/CLAUDE.md index 44e0d199..ee76ac06 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,8 +84,9 @@ directory of numbered Markdown plus a `book.toml` that [scripts/build_book.py](scripts/build_book.py) renders through the one shared [template](docs/shared/template.typ): [docs/guide/](docs/guide/README.md) (the User's Guide, read in order), [docs/reference/](docs/reference/README.md) (the -Programmer's Reference Guide — seven chapters over ten appendices, of which A–I -are generated by [scripts/gen_reference_appendices.py](scripts/gen_reference_appendices.py) +Programmer's Reference Guide — seven chapters over ten appendices and an index, +of which appendices A–I and the whole index are generated by +[scripts/gen_reference_appendices.py](scripts/gen_reference_appendices.py) and regenerated with `make reference-appendices`), and [docs/card/](docs/card/README.md) (the two-page Performance Card, whose live-target table comes from that same pass). `make books` renders all three; each book's diff --git a/Makefile b/Makefile index 5f803eb6..ffb862ac 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ help: @echo " card render docs/card/*.md to the Performance Card PDF (needs typst)" @echo " books render every book" @echo " guide-figures redraw the guide's placeholder figures" - @echo " reference-appendices regenerate the reference guide's appendices A-I" + @echo " reference-appendices regenerate the reference guide's appendices A-I + index" @echo " check lint + typecheck + test" @echo " clean remove build artifacts" @@ -149,14 +149,16 @@ card: $(SYNC) books: guide reference card -# Rewrite the Programmer's Reference Guide's generated appendices (A-I) and the -# performance card's live-target table from the config metadata. Unlike the -# books themselves this needs the project env, since it imports c64cast — which -# is exactly why it is a separate script from build_book.py, and why its output -# is committed: the release renders the PDFs with `uv run --no-project`. +# Rewrite the Programmer's Reference Guide's generated appendices (A-I), its +# index and the performance card's live-target table from the config metadata. +# Unlike the books themselves this needs the project env, since it imports +# c64cast — which is exactly why it is a separate script from build_book.py, +# and why its output is committed: the release renders the PDFs with +# `uv run --no-project`. # tests/test_reference_appendices.py fails if the committed files drift from # this output, so run it after changing any config field, overlay, generator, -# effect, CLI flag, example config or install extra. +# effect, CLI flag, example config or install extra — and after renaming a +# section, which moves an anchor the index links at. reference-appendices: $(SYNC) $(PY) scripts/gen_reference_appendices.py diff --git a/docs/reference/01-introduction.md b/docs/reference/01-introduction.md index 084a5413..8dc2801c 100644 --- a/docs/reference/01-introduction.md +++ b/docs/reference/01-introduction.md @@ -35,6 +35,13 @@ installs. Appendix J is a glossary, which is hand-written because a machine has no opinion about which words a reader will not know. +The index at the back is generated too, and from both halves at once: every +name the program can utter, against the sections of this book that discuss it. +A configuration key is listed bare, and again qualified with its section where +two sections share the name. A section title in ordinary words — "Companding", +"The Audio Slot" — is an entry in its own right, so the concept can be looked +up by somebody who does not yet know what it is called. + ## What Is Not Three things live outside this book on purpose. diff --git a/docs/reference/30-index.md b/docs/reference/30-index.md new file mode 100644 index 00000000..ad3920eb --- /dev/null +++ b/docs/reference/30-index.md @@ -0,0 +1,657 @@ +--- +generated: true +--- + +# Index + +*Generated from the code by `scripts/gen_reference_appendices.py`. +Edits here are overwritten; run `make reference-appendices`.* + +Every name c64cast answers to — 503 of them — and the sections that discuss each one, the section written about it first. Names are listed as this book writes them: a configuration key appears bare, and again qualified with its section where two sections share the name. An entry in ordinary words is a section of the book by that title. The chapter or appendix each locator is in follows it, in parentheses. + +## # + + +| Term | Discussed in | +|---|---| +| **The 4-Bit DAC** | [The 4-Bit DAC (4)](05-sound-and-music.md#the-4-bit-dac) | + +## A + + +| Term | Discussed in | +|---|---| +| **`agc`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`agc_max_gain_db`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`agc_noise_floor_db`** | [Shaping the Signal (4)](05-sound-and-music.md#shaping-the-signal), [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`agc_target_db`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`agc_time_ms`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **ASID** | [ASID (4)](05-sound-and-music.md#asid) | +| **`asid`** | [`asid` (2)](03-vocabulary.md#asid), [`asid` (B)](21-appendix-b-scene-types.md#asid), [Frame Rate (2)](03-vocabulary.md#frame-rate) | +| **ASID and MIDI** | [ASID and MIDI (4)](05-sound-and-music.md#asid-and-midi) | +| **`asid_buffered_player`** | [`asid` (2)](03-vocabulary.md#asid), [ASID (4)](05-sound-and-music.md#asid), [`asid` (B)](21-appendix-b-scene-types.md#asid) | +| **`asid_max_sids`** | [`asid` (2)](03-vocabulary.md#asid), [ASID (4)](05-sound-and-music.md#asid), [`asid` (B)](21-appendix-b-scene-types.md#asid) | +| **`asid_multi_sid`** | [`asid` (2)](03-vocabulary.md#asid), [ASID (4)](05-sound-and-music.md#asid), [`asid` (B)](21-appendix-b-scene-types.md#asid) | +| **`asid_port`** | [`asid` (B)](21-appendix-b-scene-types.md#asid) | +| **`aspect_mode`** | [`slideshow` (2)](03-vocabulary.md#slideshow), [From Frame to Screen (3)](04-display-pipeline.md#from-frame-to-screen), [`slideshow` (B)](21-appendix-b-scene-types.md#slideshow) | +| **`--audio`** | [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **Audio** | [Audio (2)](03-vocabulary.md#audio), [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **`[audio]`** | [`audio` (A)](20-appendix-a-configuration.md#audio), [The Extra Layer (1)](02-config-rules.md#the-extra-layer), [What a Scene Records About Itself (6)](07-inputs-and-outputs.md#what-a-scene-records-about-itself) | +| **`audio`** | [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings), [Audio (2)](03-vocabulary.md#audio), [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain) | +| **The Audio Slot** | [The Audio Slot (6)](07-inputs-and-outputs.md#the-audio-slot) | +| **`--audio-device`** | [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings), [Choosing a Microphone (6)](07-inputs-and-outputs.md#choosing-a-microphone), [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **`audio.backend`** | [The Shape of a Value (1)](02-config-rules.md#the-shape-of-a-value), [Two Ways Out (4)](05-sound-and-music.md#two-ways-out), [Notation](01-introduction.md#notation) | +| **`audio.device`** | [Choosing a Microphone (6)](07-inputs-and-outputs.md#choosing-a-microphone), [`audio` (A)](20-appendix-a-configuration.md#audio), [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **`audio.dither`** | [Companding — `dac_curve` (4)](05-sound-and-music.md#companding--dac_curve), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`audio.enabled`** | [Audio (2)](03-vocabulary.md#audio), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`[audio_features]`** | [`audio_features` (A)](20-appendix-a-configuration.md#audio_features) | +| **`audio_source`** | [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line), [`generative` (2)](03-vocabulary.md#generative), [Playing SID Tunes (4)](05-sound-and-music.md#playing-sid-tunes) | +| **`auto_cycles`** | [`waveform` (2)](03-vocabulary.md#waveform), [`waveform` (B)](21-appendix-b-scene-types.md#waveform), [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`auto_fit`** | [From Frame to Screen (3)](04-display-pipeline.md#from-frame-to-screen), [`color` (A)](20-appendix-a-configuration.md#color) | +| **`auto_fit_strength`** | [Saving What a Run Changed (6)](07-inputs-and-outputs.md#saving-what-a-run-changed), [The Vocabulary (6)](07-inputs-and-outputs.md#the-vocabulary), [`color` (A)](20-appendix-a-configuration.md#color) | +| **`auto_reu`** | [`ultimate64` (A)](20-appendix-a-configuration.md#ultimate64) | + +## B + + +| Term | Discussed in | +|---|---| +| **`backend`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [The Shape of a Value (1)](02-config-rules.md#the-shape-of-a-value), [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings) | +| **`background`** | [Between One Scene and the Next (2)](03-vocabulary.md#between-one-scene-and-the-next), [`interstitial` (A)](20-appendix-a-configuration.md#interstitial), [`blank` (B)](21-appendix-b-scene-types.md#blank) | +| **`bands`** | [Listening Back (4)](05-sound-and-music.md#listening-back), [`audio_features` (A)](20-appendix-a-configuration.md#audio_features) | +| **The BASIC Program Underneath** | [The BASIC Program Underneath (5)](06-under-the-hood.md#the-basic-program-underneath) | +| **`baud`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [`teensyrom` (A)](20-appendix-a-configuration.md#teensyrom) | +| **The Beat Grid** | [The Beat Grid (6)](07-inputs-and-outputs.md#the-beat-grid) | +| **`beats_per_bar`** | [`performance` (A)](20-appendix-a-configuration.md#performance) | +| **Between One Scene and the Next** | [Between One Scene and the Next (2)](03-vocabulary.md#between-one-scene-and-the-next) | +| **`bg_color`** | [`callsign` (C)](22-appendix-c-overlays.md#callsign), [`clock` (C)](22-appendix-c-overlays.md#clock), [`countdown` (C)](22-appendix-c-overlays.md#countdown) | +| **`big_text`** | [`big_text` Wants the Scene to Itself (2)](03-vocabulary.md#big_text-wants-the-scene-to-itself), [`big_text` (C)](22-appendix-c-overlays.md#big_text), [What Ends a Scene (2)](03-vocabulary.md#what-ends-a-scene) | +| **`big_text` Wants the Scene to Itself** | [`big_text` Wants the Scene to Itself (2)](03-vocabulary.md#big_text-wants-the-scene-to-itself) | +| **The Bitmap-and-DAC Time Stretch** | [The Bitmap-and-DAC Time Stretch (4)](05-sound-and-music.md#the-bitmap-and-dac-time-stretch) | +| **`blank`** | [`blank` (2)](03-vocabulary.md#blank), [`blank` (B)](21-appendix-b-scene-types.md#blank), [`video` (2)](03-vocabulary.md#video) | +| **`blur`** | [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain), [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`video` (B)](21-appendix-b-scene-types.md#video) | +| **`border`** | [`blank` (B)](21-appendix-b-scene-types.md#blank) | +| **`bpm`** | [Listening Back (4)](05-sound-and-music.md#listening-back), [The Beat Grid (6)](07-inputs-and-outputs.md#the-beat-grid), [`performance` (A)](20-appendix-a-configuration.md#performance) | +| **`broadcast`** | [WLED (6)](07-inputs-and-outputs.md#wled), [`wled` (A)](20-appendix-a-configuration.md#wled) | +| **Broadcast — Fixtures React to the Music** | [Broadcast — Fixtures React to the Music (6)](07-inputs-and-outputs.md#broadcast--fixtures-react-to-the-music) | +| **`broadcast_channel`** | [One Surface for the Whole Ensemble (6)](07-inputs-and-outputs.md#one-surface-for-the-whole-ensemble), [`midi_control` (A)](20-appendix-a-configuration.md#midi_control) | +| **`broadcast_tempo_fallback`** | [Broadcast — Fixtures React to the Music (6)](07-inputs-and-outputs.md#broadcast--fixtures-react-to-the-music), [`wled` (A)](20-appendix-a-configuration.md#wled) | +| **`bypass_audio_lock`** | [`launcher` (2)](03-vocabulary.md#launcher), [The Audio Slot (6)](07-inputs-and-outputs.md#the-audio-slot), [`launcher` (B)](21-appendix-b-scene-types.md#launcher) | + +## C + + +| Term | Discussed in | +|---|---| +| **`--calibrate-dac`** | [`audio` (A)](20-appendix-a-configuration.md#audio), [Audio (G)](26-appendix-g-cli-flags.md#audio), [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`callsign`** | [`callsign` (C)](22-appendix-c-overlays.md#callsign), [The Overlays (2)](03-vocabulary.md#the-overlays), [The Matrix (D)](23-appendix-d-compatibility.md#the-matrix) | +| **Cameras and Microphones** | [Cameras and Microphones (6)](07-inputs-and-outputs.md#cameras-and-microphones) | +| **`cc_map`** | [The Vocabulary (6)](07-inputs-and-outputs.md#the-vocabulary), [Learning a Controller (6)](07-inputs-and-outputs.md#learning-a-controller), [`midi_control` (A)](20-appendix-a-configuration.md#midi_control) | +| **`cell_strategy`** | [Which Colours — `cell_strategy` (3)](04-display-pipeline.md#which-colours--cell_strategy), [What to Give Up First (5)](06-under-the-hood.md#what-to-give-up-first), [Saving What a Run Changed (6)](07-inputs-and-outputs.md#saving-what-a-run-changed) | +| **`channel_boost`** | [From Frame to Screen (3)](04-display-pipeline.md#from-frame-to-screen), [How Near — `color_match` (3)](04-display-pipeline.md#how-near--color_match), [`color` (A)](20-appendix-a-configuration.md#color) | +| **The Character ROM** | [The Character ROM (5)](06-under-the-hood.md#the-character-rom) | +| **`charset_path`** | [`preview` (A)](20-appendix-a-configuration.md#preview), [`big_text` (C)](22-appendix-c-overlays.md#big_text) | +| **Choosing a Camera** | [Choosing a Camera (6)](07-inputs-and-outputs.md#choosing-a-camera) | +| **Choosing a Display Mode for an Overlay** | [Choosing a Display Mode for an Overlay (2)](03-vocabulary.md#choosing-a-display-mode-for-an-overlay) | +| **Choosing a Microphone** | [Choosing a Microphone (6)](07-inputs-and-outputs.md#choosing-a-microphone) | +| **The Clip Grid** | [The Clip Grid (6)](07-inputs-and-outputs.md#the-clip-grid) | +| **`clips`** | [Performing (6)](07-inputs-and-outputs.md#performing), [`performance` (A)](20-appendix-a-configuration.md#performance), [The Demos (H)](27-appendix-h-examples.md#the-demos) | +| **Clock** | [Clock (6)](07-inputs-and-outputs.md#clock) | +| **`clock`** | [`clock` (C)](22-appendix-c-overlays.md#clock), [The Overlays (2)](03-vocabulary.md#the-overlays), [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain) | +| **`clock_port`** | [Clock (6)](07-inputs-and-outputs.md#clock), [`performance` (A)](20-appendix-a-configuration.md#performance) | +| **`col`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`logo` (C)](22-appendix-c-overlays.md#logo) | +| **`[color]`** | [`color` (A)](20-appendix-a-configuration.md#color), [Machine Settings (1)](02-config-rules.md#machine-settings), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`color.dither`** | [The Shape of a Value (1)](02-config-rules.md#the-shape-of-a-value), [`color` (A)](20-appendix-a-configuration.md#color) | +| **`color_match`** | [How Near — `color_match` (3)](04-display-pipeline.md#how-near--color_match), [Saving What a Run Changed (6)](07-inputs-and-outputs.md#saving-what-a-run-changed), [The Vocabulary (6)](07-inputs-and-outputs.md#the-vocabulary) | +| **`color_mode`** | [`waveform` (2)](03-vocabulary.md#waveform), [`waveform` (B)](21-appendix-b-scene-types.md#waveform), [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`colored_bursts`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`comp_attack_ms`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`comp_knee_db`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`comp_makeup_auto`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`comp_makeup_db`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`comp_ratio`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`comp_release_ms`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`comp_threshold_db`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **Companding** | [Companding — `dac_curve` (4)](05-sound-and-music.md#companding--dac_curve) | +| **`--compat`** | [Choosing a Display Mode for an Overlay (2)](03-vocabulary.md#choosing-a-display-mode-for-an-overlay), [What Is In Here](01-introduction.md#what-is-in-here), [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`compress`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`--config`** | [Files and Where They Are Found (1)](02-config-rules.md#files-and-where-they-are-found), [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line), [The Ensemble Cascade (1)](02-config-rules.md#the-ensemble-cascade) | +| **`[control]`** | [`control` (A)](20-appendix-a-configuration.md#control), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **The Control Plane** | [The Control Plane (6)](07-inputs-and-outputs.md#the-control-plane) | +| **`control.enabled`** | [The Control Plane (6)](07-inputs-and-outputs.md#the-control-plane), [`control` (A)](20-appendix-a-configuration.md#control) | +| **`control.host`** | [`control` (A)](20-appendix-a-configuration.md#control) | +| **`control.port`** | [`control` (A)](20-appendix-a-configuration.md#control) | +| **`controller_profile`** | [Learning a Controller (6)](07-inputs-and-outputs.md#learning-a-controller), [`midi_control` (A)](20-appendix-a-configuration.md#midi_control) | +| **`corner`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`callsign` (C)](22-appendix-c-overlays.md#callsign), [`clock` (C)](22-appendix-c-overlays.md#clock) | +| **`countdown`** | [`countdown` (C)](22-appendix-c-overlays.md#countdown), [The Overlays (2)](03-vocabulary.md#the-overlays), [The Matrix (D)](23-appendix-d-compatibility.md#the-matrix) | + +## D + + +| Term | Discussed in | +|---|---| +| **`--dac-calibration-profile`** | [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **`dac_bitmap_tempo_hires`** | [The Bitmap-and-DAC Time Stretch (4)](05-sound-and-music.md#the-bitmap-and-dac-time-stretch), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`dac_bitmap_tempo_mhires`** | [The Bitmap-and-DAC Time Stretch (4)](05-sound-and-music.md#the-bitmap-and-dac-time-stretch), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`dac_calibration_profile`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`dac_curve`** | [Companding — `dac_curve` (4)](05-sound-and-music.md#companding--dac_curve), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`date_format`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`clock` (C)](22-appendix-c-overlays.md#clock) | +| **`[debug]`** | [`debug` (A)](20-appendix-a-configuration.md#debug), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`--describe`** | [Seeing Which Layer Answered (1)](02-config-rules.md#seeing-which-layer-answered), [The Scene Types (2)](03-vocabulary.md#the-scene-types), [What Is In Here](01-introduction.md#what-is-in-here) | +| **`--device`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings), [Flags in Ensemble Mode (1)](02-config-rules.md#flags-in-ensemble-mode) | +| **`device`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [Machine Settings (1)](02-config-rules.md#machine-settings), [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings) | +| **`digi_boost`** | [Companding — `dac_curve` (4)](05-sound-and-music.md#companding--dac_curve), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`--display`** | [Quick Playback (With Media Args) (G)](26-appendix-g-cli-flags.md#quick-playback-with-media-args) | +| **`display`** | [The Precedence Ladder (1)](02-config-rules.md#the-precedence-ladder), [The Shape of a Scene (2)](03-vocabulary.md#the-shape-of-a-scene), [`asid` (2)](03-vocabulary.md#asid) | +| **`dither`** | [Which Pixel Takes Which — `dither` (3)](04-display-pipeline.md#which-pixel-takes-which--dither), [The Shape of a Value (1)](02-config-rules.md#the-shape-of-a-value), [Companding — `dac_curve` (4)](05-sound-and-music.md#companding--dac_curve) | +| **`dither_strength`** | [Which Pixel Takes Which — `dither` (3)](04-display-pipeline.md#which-pixel-takes-which--dither), [Saving What a Run Changed (6)](07-inputs-and-outputs.md#saving-what-a-run-changed), [The Vocabulary (6)](07-inputs-and-outputs.md#the-vocabulary) | +| **`dma_password`** | [The Precedence Ladder (1)](02-config-rules.md#the-precedence-ladder), [`ultimate64` (A)](20-appendix-a-configuration.md#ultimate64) | +| **`dma_port`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [`ultimate64` (A)](20-appendix-a-configuration.md#ultimate64) | +| **`dna`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`--doctor`** | [`--doctor` (1)](02-config-rules.md#--doctor), [Files and Where They Are Found (1)](02-config-rules.md#files-and-where-they-are-found), [The Shape of a Value (1)](02-config-rules.md#the-shape-of-a-value) | +| **`done_text`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`countdown` (C)](22-appendix-c-overlays.md#countdown) | +| **`dotswarm`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`double_buffer`** | [`video` (A)](20-appendix-a-configuration.md#video) | +| **`drift`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **Driving a Video** | [Driving a Video (6)](07-inputs-and-outputs.md#driving-a-video) | +| **`[dsp]`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`dsp.enabled`** | [Shaping the Signal (4)](05-sound-and-music.md#shaping-the-signal), [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`--dump-char-rom`** | [`preview` (A)](20-appendix-a-configuration.md#preview), [`big_text` (C)](22-appendix-c-overlays.md#big_text), [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`dump_char_rom`** | [`hardware` (A)](20-appendix-a-configuration.md#hardware) | +| **`--duration`** | [Quick Playback (With Media Args) (G)](26-appendix-g-cli-flags.md#quick-playback-with-media-args) | +| **Duration and Subtunes** | [Duration and Subtunes (4)](05-sound-and-music.md#duration-and-subtunes) | +| **`duration_s`** | [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [Validation (1)](02-config-rules.md#validation), [The Shape of a Scene (2)](03-vocabulary.md#the-shape-of-a-scene) | + +## E + + +| Term | Discussed in | +|---|---| +| **Editor Autocomplete** | [Editor Autocomplete (1)](02-config-rules.md#editor-autocomplete) | +| **`effect`** | [The Vocabulary (6)](07-inputs-and-outputs.md#the-vocabulary), [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`video` (B)](21-appendix-b-scene-types.md#video) | +| **The Effect Chain** | [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain) | +| **`effect.axis`** | [Effects (E)](24-appendix-e-generators-effects.md#effects), [Effect (F)](25-appendix-f-live-targets.md#effect) | +| **`effect.decay`** | [What Ships Mapped (6)](07-inputs-and-outputs.md#what-ships-mapped), [Effects (E)](24-appendix-e-generators-effects.md#effects), [Effect (F)](25-appendix-f-live-targets.md#effect) | +| **`effect.duty`** | [Effects (E)](24-appendix-e-generators-effects.md#effects), [Effect (F)](25-appendix-f-live-targets.md#effect) | +| **`effect.intensity`** | [Effects (E)](24-appendix-e-generators-effects.md#effects), [Effect (F)](25-appendix-f-live-targets.md#effect) | +| **`effect.levels`** | [Effects (E)](24-appendix-e-generators-effects.md#effects), [Effect (F)](25-appendix-f-live-targets.md#effect) | +| **`effect.mix`** | [Effects (E)](24-appendix-e-generators-effects.md#effects), [Effect (F)](25-appendix-f-live-targets.md#effect) | +| **`effect.rate`** | [Effects (E)](24-appendix-e-generators-effects.md#effects), [Effect (F)](25-appendix-f-live-targets.md#effect) | +| **`effects`** | [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`video` (B)](21-appendix-b-scene-types.md#video), [`slideshow` (B)](21-appendix-b-scene-types.md#slideshow) | +| **`enabled`** | [Audio (2)](03-vocabulary.md#audio), [Shaping the Signal (4)](05-sound-and-music.md#shaping-the-signal), [The Machine's Own Keyboard (6)](07-inputs-and-outputs.md#the-machines-own-keyboard) | +| **The Ensemble Cascade** | [The Ensemble Cascade (1)](02-config-rules.md#the-ensemble-cascade) | +| **`epicycle`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`expander`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`expander_attack_ms`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`expander_floor_db`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`expander_hysteresis_db`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`expander_ratio`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`expander_release_ms`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`expander_threshold_db`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **Expansion Memory** | [Expansion Memory (5)](06-under-the-hood.md#expansion-memory) | +| **The Extra Layer** | [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | + +## F + + +| Term | Discussed in | +|---|---| +| **`fade_duration_s`** | [Between One Scene and the Next (2)](03-vocabulary.md#between-one-scene-and-the-next), [Fades (3)](04-display-pipeline.md#fades), [`playlist` (A)](20-appendix-a-configuration.md#playlist) | +| **Fades** | [Fades (3)](04-display-pipeline.md#fades) | +| **`feedback_port`** | [`performance` (A)](20-appendix-a-configuration.md#performance) | +| **`fft_size`** | [`audio_features` (A)](20-appendix-a-configuration.md#audio_features) | +| **`fg_color`** | [`callsign` (C)](22-appendix-c-overlays.md#callsign), [`clock` (C)](22-appendix-c-overlays.md#clock), [`countdown` (C)](22-appendix-c-overlays.md#countdown) | +| **`file`** | [Paths Inside a File (1)](02-config-rules.md#paths-inside-a-file), [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line), [The Shape of a Value (1)](02-config-rules.md#the-shape-of-a-value) | +| **Files** | [Files (2)](03-vocabulary.md#files) | +| **Files and Where They Are Found** | [Files and Where They Are Found (1)](02-config-rules.md#files-and-where-they-are-found) | +| **`fire`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`fireworks`** | [Generators (3)](04-display-pipeline.md#generators), [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators) | +| **Flags in Ensemble Mode** | [Flags in Ensemble Mode (1)](02-config-rules.md#flags-in-ensemble-mode) | +| **`follower_only`** | [The Shape of a Scene (2)](03-vocabulary.md#the-shape-of-a-scene), [Span and Mirror (6)](07-inputs-and-outputs.md#span-and-mirror), [Keys Every Scene Takes (B)](21-appendix-b-scene-types.md#keys-every-scene-takes) | +| **`force_palette`** | [Forced and Rolling Palettes (3)](04-display-pipeline.md#forced-and-rolling-palettes), [`color` (A)](20-appendix-a-configuration.md#color) | +| **`force_palette_colors`** | [Forced and Rolling Palettes (3)](04-display-pipeline.md#forced-and-rolling-palettes), [`color` (A)](20-appendix-a-configuration.md#color) | +| **Forced and Rolling Palettes** | [Forced and Rolling Palettes (3)](04-display-pipeline.md#forced-and-rolling-palettes) | +| **`format`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`clock` (C)](22-appendix-c-overlays.md#clock), [`countdown` (C)](22-appendix-c-overlays.md#countdown) | +| **`fourcc`** | [Recording (6)](07-inputs-and-outputs.md#recording), [`recording` (A)](20-appendix-a-configuration.md#recording) | +| **`fps`** | [Recording (6)](07-inputs-and-outputs.md#recording), [`preview` (A)](20-appendix-a-configuration.md#preview), [`recording` (A)](20-appendix-a-configuration.md#recording) | +| **Frame Rate** | [Frame Rate (2)](03-vocabulary.md#frame-rate) | +| **`--frame-numbers`** | [From Frame to Screen (3)](04-display-pipeline.md#from-frame-to-screen), [`midi_control` (A)](20-appendix-a-configuration.md#midi_control), [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`frame_numbers`** | [`debug` (A)](20-appendix-a-configuration.md#debug) | +| **From Frame to Screen** | [From Frame to Screen (3)](04-display-pipeline.md#from-frame-to-screen) | + +## G + + +| Term | Discussed in | +|---|---| +| **`gain`** | [The Vocabulary (6)](07-inputs-and-outputs.md#the-vocabulary), [`spectrum_bitmap` (C)](22-appendix-c-overlays.md#spectrum_bitmap), [`spectrum_petscii` (C)](22-appendix-c-overlays.md#spectrum_petscii) | +| **`game_of_life`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`generative`** | [`generative` (2)](03-vocabulary.md#generative), [`generative` (B)](21-appendix-b-scene-types.md#generative), [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line) | +| **A Generator or an Effect** | [A Generator or an Effect (7)](08-extending.md#a-generator-or-an-effect) | +| **Generators** | [Generators (3)](04-display-pipeline.md#generators), [Generators (E)](24-appendix-e-generators-effects.md#generators) | +| **`gesture_cooldown_s`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **`gesture_dwell_s`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **Gestures** | [Gestures (6)](07-inputs-and-outputs.md#gestures) | +| **Getting Bytes In** | [Getting Bytes In (5)](06-under-the-hood.md#getting-bytes-in) | + +## H + + +| Term | Discussed in | +|---|---| +| **`halo`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`[hardware]`** | [`hardware` (A)](20-appendix-a-configuration.md#hardware), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`hardware.backend`** | [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings), [`hardware` (A)](20-appendix-a-configuration.md#hardware) | +| **`--heartbeat`** | [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`heartbeat`** | [`debug` (A)](20-appendix-a-configuration.md#debug), [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`height_frac`** | [`spectrum_bitmap` (C)](22-appendix-c-overlays.md#spectrum_bitmap) | +| **`height_rows`** | [`spectrum_petscii` (C)](22-appendix-c-overlays.md#spectrum_petscii) | +| **`--help`** | [Options (G)](26-appendix-g-cli-flags.md#options) | +| **`hiphotic`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`hires`** | [`generative` (2)](03-vocabulary.md#generative), [`slideshow` (2)](03-vocabulary.md#slideshow), [`video` (2)](03-vocabulary.md#video) | +| **`hires_edges`** | [Between One Scene and the Next (2)](03-vocabulary.md#between-one-scene-and-the-next), [`generative` (2)](03-vocabulary.md#generative), [`slideshow` (2)](03-vocabulary.md#slideshow) | +| **`hold_threshold_s`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **`hopalong`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`host`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [`teensyrom` (A)](20-appendix-a-configuration.md#teensyrom), [`control` (A)](20-appendix-a-configuration.md#control) | +| **`host_dma_servo`** | [The Pitch Knobs That Default Off (4)](05-sound-and-music.md#the-pitch-knobs-that-default-off), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **How Near** | [How Near — `color_match` (3)](04-display-pipeline.md#how-near--color_match) | +| **How the Oscilloscope Knows** | [How the Oscilloscope Knows (4)](05-sound-and-music.md#how-the-oscilloscope-knows) | +| **`hue_corrections`** | [From Frame to Screen (3)](04-display-pipeline.md#from-frame-to-screen), [`color` (A)](20-appendix-a-configuration.md#color) | +| **`hue_corrections_replace_defaults`** | [`color` (A)](20-appendix-a-configuration.md#color) | + +## I + + +| Term | Discussed in | +|---|---| +| **`image_duration_s`** | [`slideshow` (2)](03-vocabulary.md#slideshow), [`slideshow` (B)](21-appendix-b-scene-types.md#slideshow) | +| **`--init`** | [Editor Autocomplete (1)](02-config-rules.md#editor-autocomplete), [Introspection (G)](26-appendix-g-cli-flags.md#introspection), [The Extras (I)](28-appendix-i-extras.md#the-extras) | +| **`input_source`** | [`launcher` (2)](03-vocabulary.md#launcher), [`launcher` (B)](21-appendix-b-scene-types.md#launcher) | +| **`--install-char-rom`** | [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`inter_message_pause_s`** | [`big_text` (C)](22-appendix-c-overlays.md#big_text) | +| **`interleave_videos`** | [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [Single-Scene Mode (1)](02-config-rules.md#single-scene-mode), [Between One Scene and the Next (2)](03-vocabulary.md#between-one-scene-and-the-next) | +| **`[interstitial]`** | [`interstitial` (A)](20-appendix-a-configuration.md#interstitial), [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`invert`** | [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`video` (B)](21-appendix-b-scene-types.md#video), [`slideshow` (B)](21-appendix-b-scene-types.md#slideshow) | +| **`items`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`network` (C)](22-appendix-c-overlays.md#network) | + +## J + + +| Term | Discussed in | +|---|---| +| **`jump_transition`** | [`midi_control` (A)](20-appendix-a-configuration.md#midi_control) | + +## K + + +| Term | Discussed in | +|---|---| +| **Keeping Sound and Picture Together** | [Keeping Sound and Picture Together (4)](05-sound-and-music.md#keeping-sound-and-picture-together) | + +## L + + +| Term | Discussed in | +|---|---| +| **`lat`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`weather` (C)](22-appendix-c-overlays.md#weather) | +| **`launcher`** | [`launcher` (2)](03-vocabulary.md#launcher), [`launcher` (B)](21-appendix-b-scene-types.md#launcher), [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line) | +| **Learning a Controller** | [Learning a Controller (6)](07-inputs-and-outputs.md#learning-a-controller) | +| **`limiter`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`limiter_ceiling`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`limiter_release_ms`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp) | +| **`lissajous`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`--list-devices`** | [`webcam` (2)](03-vocabulary.md#webcam), [`video` (A)](20-appendix-a-configuration.md#video), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`--list-examples`** | [Options (G)](26-appendix-g-cli-flags.md#options), [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`--list-modes`** | [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`--list-overlays`** | [Keys Every Scene Takes (B)](21-appendix-b-scene-types.md#keys-every-scene-takes), [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`--list-scenes`** | [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`listen`** | [`mic` and `listen` (4)](05-sound-and-music.md#mic-and-listen), [`generative` (2)](03-vocabulary.md#generative), [The Beat Grid (6)](07-inputs-and-outputs.md#the-beat-grid) | +| **Listen — The App Controls c64cast** | [Listen — The App Controls c64cast (6)](07-inputs-and-outputs.md#listen--the-app-controls-c64cast) | +| **`listen_sample_rate`** | [`mic` and `listen` (4)](05-sound-and-music.md#mic-and-listen), [`audio_features` (A)](20-appendix-a-configuration.md#audio_features) | +| **Listening Back** | [Listening Back (4)](05-sound-and-music.md#listening-back) | +| **`location`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`weather` (C)](22-appendix-c-overlays.md#weather) | +| **`--log-file`** | [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`log_file`** | [Paths Inside a File (1)](02-config-rules.md#paths-inside-a-file), [`debug` (A)](20-appendix-a-configuration.md#debug) | +| **`logo`** | [`logo` (C)](22-appendix-c-overlays.md#logo), [The Overlays (2)](03-vocabulary.md#the-overlays), [The Matrix (D)](23-appendix-d-compatibility.md#the-matrix) | +| **`lon`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`weather` (C)](22-appendix-c-overlays.md#weather) | +| **Looks** | [Looks (6)](07-inputs-and-outputs.md#looks) | +| **`--loop`** | [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line), [Positional Arguments (G)](26-appendix-g-cli-flags.md#positional-arguments), [Playlist (G)](26-appendix-g-cli-flags.md#playlist) | +| **`loop`** | [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line), [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [What Ends a Scene (2)](03-vocabulary.md#what-ends-a-scene) | +| **`loop_audio`** | [Driving a Video (6)](07-inputs-and-outputs.md#driving-a-video), [`midi_control` (A)](20-appendix-a-configuration.md#midi_control) | + +## M + + +| Term | Discussed in | +|---|---| +| **Machine Settings** | [Machine Settings (1)](02-config-rules.md#machine-settings) | +| **The Machine's Own Keyboard** | [The Machine's Own Keyboard (6)](07-inputs-and-outputs.md#the-machines-own-keyboard) | +| **`mandelbrot`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`marquee`** | [`marquee` (C)](22-appendix-c-overlays.md#marquee), [The Overlays (2)](03-vocabulary.md#the-overlays), [The Matrix (D)](23-appendix-d-compatibility.md#the-matrix) | +| **Matching the Chip to the Tune** | [Matching the Chip to the Tune (4)](05-sound-and-music.md#matching-the-chip-to-the-tune) | +| **`max_duration_s`** | [`launcher` (2)](03-vocabulary.md#launcher), [`launcher` (B)](21-appendix-b-scene-types.md#launcher) | +| **`max_items`** | [`rss` (C)](22-appendix-c-overlays.md#rss) | +| **`mcm`** | [`generative` (2)](03-vocabulary.md#generative), [`slideshow` (2)](03-vocabulary.md#slideshow), [`video` (2)](03-vocabulary.md#video) | +| **Media on the Command Line** | [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line) | +| **`[menu]`** | [`menu` (A)](20-appendix-a-configuration.md#menu), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`menu.enabled`** | [The Machine's Own Keyboard (6)](07-inputs-and-outputs.md#the-machines-own-keyboard), [The On-C64 Menu (6)](07-inputs-and-outputs.md#the-on-c64-menu), [`menu` (A)](20-appendix-a-configuration.md#menu) | +| **`messages`** | [`big_text` (C)](22-appendix-c-overlays.md#big_text), [`scrolling_text` (C)](22-appendix-c-overlays.md#scrolling_text) | +| **`metaballs`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`mhires`** | [`generative` (2)](03-vocabulary.md#generative), [`slideshow` (2)](03-vocabulary.md#slideshow), [`video` (2)](03-vocabulary.md#video) | +| **`--mic-sensitivity`** | [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **`mic_sensitivity`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`mic` and `listen`** | [`mic` and `listen` (4)](05-sound-and-music.md#mic-and-listen) | +| **MIDI** | [MIDI (4)](05-sound-and-music.md#midi) | +| **`midi`** | [`midi` (2)](03-vocabulary.md#midi), [`midi` (B)](21-appendix-b-scene-types.md#midi), [Frame Rate (2)](03-vocabulary.md#frame-rate) | +| **MIDI In and Out** | [MIDI In and Out (6)](07-inputs-and-outputs.md#midi-in-and-out) | +| **MIDI Out — Lighting the Pads** | [MIDI Out — Lighting the Pads (6)](07-inputs-and-outputs.md#midi-out--lighting-the-pads) | +| **`--midi-setup`** | [`midi_control` (A)](20-appendix-a-configuration.md#midi_control), [`performance` (A)](20-appendix-a-configuration.md#performance), [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`midi_adsr`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`[midi_control]`** | [`midi_control` (A)](20-appendix-a-configuration.md#midi_control), [The Extra Layer (1)](02-config-rules.md#the-extra-layer), [MIDI In and Out (6)](07-inputs-and-outputs.md#midi-in-and-out) | +| **`midi_control.enabled`** | [`midi_control` (A)](20-appendix-a-configuration.md#midi_control) | +| **`midi_control.port`** | [`midi_control` (A)](20-appendix-a-configuration.md#midi_control) | +| **`midi_feedback`** | [MIDI Out — Lighting the Pads (6)](07-inputs-and-outputs.md#midi-out--lighting-the-pads), [`performance` (A)](20-appendix-a-configuration.md#performance) | +| **`midi_filter_cutoff`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_filter_mode`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_filter_resonance`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_master_volume`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_port`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_program_change`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_pulse_width`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_voice_channels`** | [`midi` (2)](03-vocabulary.md#midi), [MIDI (4)](05-sound-and-music.md#midi), [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_voice_mode`** | [`midi` (2)](03-vocabulary.md#midi), [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_voice_waveforms`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`midi_waveform`** | [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`min_detection_confidence`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **`min_duration_s`** | [`launcher` (2)](03-vocabulary.md#launcher), [`launcher` (B)](21-appendix-b-scene-types.md#launcher) | +| **`min_tracking_confidence`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **`mirror`** | [`vision` (A)](20-appendix-a-configuration.md#vision), [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`video` (B)](21-appendix-b-scene-types.md#video) | +| **`mod_source`** | [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain), [What Reads the Features (4)](05-sound-and-music.md#what-reads-the-features), [`webcam` (B)](21-appendix-b-scene-types.md#webcam) | +| **`mode.auto_fit_strength`** | [`color` (A)](20-appendix-a-configuration.md#color), [Color pipeline (F)](25-appendix-f-live-targets.md#color-pipeline) | +| **`mode.cell_strategy`** | [`color` (A)](20-appendix-a-configuration.md#color), [Color pipeline (F)](25-appendix-f-live-targets.md#color-pipeline) | +| **`mode.color_match`** | [`color` (A)](20-appendix-a-configuration.md#color), [Color pipeline (F)](25-appendix-f-live-targets.md#color-pipeline) | +| **`mode.dither_method`** | [`color` (A)](20-appendix-a-configuration.md#color), [Color pipeline (F)](25-appendix-f-live-targets.md#color-pipeline) | +| **`mode.dither_strength`** | [`color` (A)](20-appendix-a-configuration.md#color), [Color pipeline (F)](25-appendix-f-live-targets.md#color-pipeline) | +| **`mode.motion_smoothing`** | [`color` (A)](20-appendix-a-configuration.md#color), [Color pipeline (F)](25-appendix-f-live-targets.md#color-pipeline) | +| **`mode.palette_mode`** | [Notation](01-introduction.md#notation), [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`video` (B)](21-appendix-b-scene-types.md#video) | +| **`model_path`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **`moire2`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **Motion Smoothing and Fades** | [Motion Smoothing and Fades (3)](04-display-pipeline.md#motion-smoothing-and-fades) | +| **`motion_smoothing`** | [`motion_smoothing` (3)](04-display-pipeline.md#motion_smoothing), [Saving What a Run Changed (6)](07-inputs-and-outputs.md#saving-what-a-run-changed), [The Vocabulary (6)](07-inputs-and-outputs.md#the-vocabulary) | + +## N + + +| Term | Discussed in | +|---|---| +| **`name`** | [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [Validation (1)](02-config-rules.md#validation), [The Shape of a Scene (2)](03-vocabulary.md#the-shape-of-a-scene) | +| **Naming the Hardware** | [Naming the Hardware (1)](02-config-rules.md#naming-the-hardware) | +| **`network`** | [`network` (C)](22-appendix-c-overlays.md#network), [The Overlays (2)](03-vocabulary.md#the-overlays), [The Matrix (D)](23-appendix-d-compatibility.md#the-matrix) | +| **`nmi_rate_adaptive`** | [The Pitch Knobs That Default Off (4)](05-sound-and-music.md#the-pitch-knobs-that-default-off), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`--no-audio`** | [`audio` (A)](20-appendix-a-configuration.md#audio), [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **`--no-loop`** | [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [`playlist` (A)](20-appendix-a-configuration.md#playlist), [Playlist (G)](26-appendix-g-cli-flags.md#playlist) | +| **`--no-profile`** | [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`--noise-gate`** | [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **`noise_gate`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **NTSC or PAL** | [NTSC or PAL (1)](02-config-rules.md#ntsc-or-pal) | +| **`num_hands`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | + +## O + + +| Term | Discussed in | +|---|---| +| **OBS** | [OBS (6)](07-inputs-and-outputs.md#obs) | +| **`obs_status`** | [`obs_status` (C)](22-appendix-c-overlays.md#obs_status), [The Overlays (2)](03-vocabulary.md#the-overlays), [OBS (6)](07-inputs-and-outputs.md#obs) | +| **The On-C64 Menu** | [The On-C64 Menu (6)](07-inputs-and-outputs.md#the-on-c64-menu) | +| **One Surface for the Whole Ensemble** | [One Surface for the Whole Ensemble (6)](07-inputs-and-outputs.md#one-surface-for-the-whole-ensemble) | +| **`onset_sensitivity`** | [Listening Back (4)](05-sound-and-music.md#listening-back), [`audio_features` (A)](20-appendix-a-configuration.md#audio_features) | +| **`orchestrate`** | [The Extra Layer (1)](02-config-rules.md#the-extra-layer), [Validation (1)](02-config-rules.md#validation), [The Shape of a Scene (2)](03-vocabulary.md#the-shape-of-a-scene) | +| **`osd`** | [`midi_control` (A)](20-appendix-a-configuration.md#midi_control) | +| **An Overlay** | [An Overlay (7)](08-extending.md#an-overlay) | +| **Overlays** | [Overlays (2)](03-vocabulary.md#overlays) | +| **The Overlays** | [The Overlays (2)](03-vocabulary.md#the-overlays) | +| **`overlays`** | [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [The Shape of a Scene (2)](03-vocabulary.md#the-shape-of-a-scene), [Keys Every Scene Takes (B)](21-appendix-b-scene-types.md#keys-every-scene-takes) | +| **`--overwrite`** | [Saving What a Run Changed (6)](07-inputs-and-outputs.md#saving-what-a-run-changed), [Debug (G)](26-appendix-g-cli-flags.md#debug) | + +## P + + +| Term | Discussed in | +|---|---| +| **`palette_mode`** | [The Six Display Modes (3)](04-display-pipeline.md#the-six-display-modes), [Which Colours — `cell_strategy` (3)](04-display-pipeline.md#which-colours--cell_strategy), [`motion_smoothing` (3)](04-display-pipeline.md#motion_smoothing) | +| **`password`** | [`obs_status` (C)](22-appendix-c-overlays.md#obs_status) | +| **`path`** | [Paths Inside a File (1)](02-config-rules.md#paths-inside-a-file), [Recording (6)](07-inputs-and-outputs.md#recording), [`recording` (A)](20-appendix-a-configuration.md#recording) | +| **Paths Inside a File** | [Paths Inside a File (1)](02-config-rules.md#paths-inside-a-file) | +| **`[performance]`** | [`performance` (A)](20-appendix-a-configuration.md#performance), [The Extra Layer (1)](02-config-rules.md#the-extra-layer), [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain) | +| **`performance`** | [Gestures (6)](07-inputs-and-outputs.md#gestures), [`vision` (A)](20-appendix-a-configuration.md#vision), [The Demos (H)](27-appendix-h-examples.md#the-demos) | +| **The Performance Console** | [The Performance Console (6)](07-inputs-and-outputs.md#the-performance-console) | +| **Performing** | [Performing (6)](07-inputs-and-outputs.md#performing) | +| **`persistence`** | [`waveform` (2)](03-vocabulary.md#waveform), [`waveform` (B)](21-appendix-b-scene-types.md#waveform), [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`petscii`** | [`generative` (2)](03-vocabulary.md#generative), [`slideshow` (2)](03-vocabulary.md#slideshow), [`video` (2)](03-vocabulary.md#video) | +| **The Picture** | [The Picture (5)](06-under-the-hood.md#the-picture) | +| **`pinch_threshold`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **The Pitch Knobs That Default Off** | [The Pitch Knobs That Default Off (4)](05-sound-and-music.md#the-pitch-knobs-that-default-off) | +| **`pitch_mult_blank`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`pitch_mult_hires`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`pitch_mult_mcm`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`pitch_mult_mhires`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`pitch_mult_petscii`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **The Pixel Sink** | [The Pixel Sink (6)](07-inputs-and-outputs.md#the-pixel-sink) | +| **`placement`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`spectrum_bitmap` (C)](22-appendix-c-overlays.md#spectrum_bitmap), [`spectrum_petscii` (C)](22-appendix-c-overlays.md#spectrum_petscii) | +| **Placing and Balancing Voices** | [Placing and Balancing Voices (4)](05-sound-and-music.md#placing-and-balancing-voices) | +| **`plasma`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **The Player** | [The Player (4)](05-sound-and-music.md#the-player) | +| **Playing SID Tunes** | [Playing SID Tunes (4)](05-sound-and-music.md#playing-sid-tunes) | +| **`[playlist]`** | [`playlist` (A)](20-appendix-a-configuration.md#playlist), [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`poll_hz`** | [`audio_features` (A)](20-appendix-a-configuration.md#audio_features) | +| **`poll_interval_s`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **`port`** | [`control` (A)](20-appendix-a-configuration.md#control), [`midi_control` (A)](20-appendix-a-configuration.md#midi_control), [`obs_status` (C)](22-appendix-c-overlays.md#obs_status) | +| **`posterize`** | [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain), [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`video` (B)](21-appendix-b-scene-types.md#video) | +| **`pre_emphasis`** | [`dsp` (A)](20-appendix-a-configuration.md#dsp), [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`blank` (B)](21-appendix-b-scene-types.md#blank) | +| **The Precedence Ladder** | [The Precedence Ladder (1)](02-config-rules.md#the-precedence-ladder) | +| **`[preview]`** | [`preview` (A)](20-appendix-a-configuration.md#preview), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **The Preview Window** | [The Preview Window (6)](07-inputs-and-outputs.md#the-preview-window) | +| **`preview.enabled`** | [The Preview Window (6)](07-inputs-and-outputs.md#the-preview-window), [`preview` (A)](20-appendix-a-configuration.md#preview) | +| **`preview.fps`** | [`preview` (A)](20-appendix-a-configuration.md#preview) | +| **`preview.scale`** | [`preview` (A)](20-appendix-a-configuration.md#preview) | +| **`--print-example`** | [Files and Where They Are Found (1)](02-config-rules.md#files-and-where-they-are-found), [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`--print-schema`** | [Editor Autocomplete (1)](02-config-rules.md#editor-autocomplete), [What Is In Here](01-introduction.md#what-is-in-here), [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`--profile`** | [The Write Budget (5)](06-under-the-hood.md#the-write-budget), [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`profile`** | [The Write Budget (5)](06-under-the-hood.md#the-write-budget), [`debug` (A)](20-appendix-a-configuration.md#debug), [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **`--profile-interval`** | [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`profile_interval`** | [`debug` (A)](20-appendix-a-configuration.md#debug) | +| **`prompt_to_save`** | [The On-C64 Menu (6)](07-inputs-and-outputs.md#the-on-c64-menu), [Saving What a Run Changed (6)](07-inputs-and-outputs.md#saving-what-a-run-changed), [`menu` (A)](20-appendix-a-configuration.md#menu) | +| **`provider`** | [`weather` (C)](22-appendix-c-overlays.md#weather) | +| **`pulse`** | [`midi` (2)](03-vocabulary.md#midi), [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain), [What Reads the Features (4)](05-sound-and-music.md#what-reads-the-features) | + +## Q + + +| Term | Discussed in | +|---|---| +| **Quantising a Cell** | [Quantising a Cell (3)](04-display-pipeline.md#quantising-a-cell) | + +## R + + +| Term | Discussed in | +|---|---| +| **`rate_hz`** | [`wled` (A)](20-appendix-a-configuration.md#wled) | +| **`reactive`** | [`generative` (2)](03-vocabulary.md#generative), [Generators (3)](04-display-pipeline.md#generators), [Listening Back (4)](05-sound-and-music.md#listening-back) | +| **Recording** | [Recording (6)](07-inputs-and-outputs.md#recording) | +| **`[recording]`** | [`recording` (A)](20-appendix-a-configuration.md#recording), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **Recording and Streaming** | [Recording and Streaming (6)](07-inputs-and-outputs.md#recording-and-streaming) | +| **`recording.enabled`** | [Recording (6)](07-inputs-and-outputs.md#recording), [`recording` (A)](20-appendix-a-configuration.md#recording) | +| **`recording.fps`** | [Recording (6)](07-inputs-and-outputs.md#recording), [`recording` (A)](20-appendix-a-configuration.md#recording) | +| **`recording.scale`** | [Recording (6)](07-inputs-and-outputs.md#recording), [`recording` (A)](20-appendix-a-configuration.md#recording) | +| **`refresh_minutes`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`rss` (C)](22-appendix-c-overlays.md#rss), [`weather` (C)](22-appendix-c-overlays.md#weather) | +| **`refresh_s`** | [`clock` (C)](22-appendix-c-overlays.md#clock), [`countdown` (C)](22-appendix-c-overlays.md#countdown), [`network` (C)](22-appendix-c-overlays.md#network) | +| **Regions and the Dirty Model** | [Regions and the Dirty Model (5)](06-under-the-hood.md#regions-and-the-dirty-model) | +| **`reset_before_launch`** | [`launcher` (B)](21-appendix-b-scene-types.md#launcher) | +| **`reu_pump_governor`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`rgb_shift`** | [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain), [What Reads the Features (4)](05-sound-and-music.md#what-reads-the-features), [`webcam` (B)](21-appendix-b-scene-types.md#webcam) | +| **`rorschach`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`rotozoomer`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`row`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`big_text` (C)](22-appendix-c-overlays.md#big_text), [`logo` (C)](22-appendix-c-overlays.md#logo) | +| **`rss`** | [`rss` (C)](22-appendix-c-overlays.md#rss), [The Overlays (2)](03-vocabulary.md#the-overlays), [The Matrix (D)](23-appendix-d-compatibility.md#the-matrix) | + +## S + + +| Term | Discussed in | +|---|---| +| **`--sample-rate`** | [The Precedence Ladder (1)](02-config-rules.md#the-precedence-ladder), [Audio (G)](26-appendix-g-cli-flags.md#audio) | +| **`sample_rate`** | [The 4-Bit DAC (4)](05-sound-and-music.md#the-4-bit-dac), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **The Sampler's Clock** | [The Sampler's Clock (4)](05-sound-and-music.md#the-samplers-clock) | +| **`sampler_bits`** | [The Ultimate Audio Sampler (4)](05-sound-and-music.md#the-ultimate-audio-sampler), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`sampler_clock_hz`** | [The Ultimate Audio Sampler (4)](05-sound-and-music.md#the-ultimate-audio-sampler), [The Sampler's Clock (4)](05-sound-and-music.md#the-samplers-clock), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`sampler_sample_rate`** | [The Ultimate Audio Sampler (4)](05-sound-and-music.md#the-ultimate-audio-sampler), [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`--save-settings`** | [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings), [NTSC or PAL (1)](02-config-rules.md#ntsc-or-pal), [Choosing a Microphone (6)](07-inputs-and-outputs.md#choosing-a-microphone) | +| **Saving What a Run Changed** | [Saving What a Run Changed (6)](07-inputs-and-outputs.md#saving-what-a-run-changed) | +| **`scale`** | [Generators (3)](04-display-pipeline.md#generators), [What Ships Mapped (6)](07-inputs-and-outputs.md#what-ships-mapped), [Recording (6)](07-inputs-and-outputs.md#recording) | +| **The Scene Types** | [The Scene Types (2)](03-vocabulary.md#the-scene-types) | +| **`scene.gain`** | [Scope (F)](25-appendix-f-live-targets.md#scope) | +| **Scenes and Playlists** | [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists) | +| **`scroll_columns`** | [`waveform` (2)](03-vocabulary.md#waveform), [`waveform` (B)](21-appendix-b-scene-types.md#waveform), [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`scrolling_text`** | [`scrolling_text` (C)](22-appendix-c-overlays.md#scrolling_text), [The Overlays (2)](03-vocabulary.md#the-overlays), [The Matrix (D)](23-appendix-d-compatibility.md#the-matrix) | +| **Seeing Which Layer Answered** | [Seeing Which Layer Answered (1)](02-config-rules.md#seeing-which-layer-answered) | +| **`separator`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`rss` (C)](22-appendix-c-overlays.md#rss) | +| **`serial_port`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [`teensyrom` (A)](20-appendix-a-configuration.md#teensyrom) | +| **Several Commodores at Once** | [Several Commodores at Once (6)](07-inputs-and-outputs.md#several-commodores-at-once) | +| **The Shape of a Scene** | [The Shape of a Scene (2)](03-vocabulary.md#the-shape-of-a-scene) | +| **The Shape of a Value** | [The Shape of a Value (1)](02-config-rules.md#the-shape-of-a-value) | +| **Shaping the Signal** | [Shaping the Signal (4)](05-sound-and-music.md#shaping-the-signal) | +| **`show_date`** | [`clock` (C)](22-appendix-c-overlays.md#clock) | +| **`show_dropped`** | [`obs_status` (C)](22-appendix-c-overlays.md#obs_status) | +| **The SID Player and Friends** | [The SID Player and Friends (5)](06-under-the-hood.md#the-sid-player-and-friends) | +| **`--sid-model`** | [Machine Settings (1)](02-config-rules.md#machine-settings), [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings), [Connection (G)](26-appendix-g-cli-flags.md#connection) | +| **`sid_filter_cutoff`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`sid_model`** | [Machine Settings (1)](02-config-rules.md#machine-settings), [Matching the Chip to the Tune (4)](05-sound-and-music.md#matching-the-chip-to-the-tune), [`ultimate64` (A)](20-appendix-a-configuration.md#ultimate64) | +| **`sid_panning`** | [`sid_panning` (4)](05-sound-and-music.md#sid_panning), [`ultimate64` (A)](20-appendix-a-configuration.md#ultimate64) | +| **`sid_volume`** | [`sid_volume` (4)](05-sound-and-music.md#sid_volume), [`ultimate64` (A)](20-appendix-a-configuration.md#ultimate64) | +| **Single-Scene Mode** | [Single-Scene Mode (1)](02-config-rules.md#single-scene-mode) | +| **`sink_height`** | [`wled` (2)](03-vocabulary.md#wled), [`wled` (B)](21-appendix-b-scene-types.md#wled) | +| **`sink_width`** | [`wled` (2)](03-vocabulary.md#wled), [`wled` (B)](21-appendix-b-scene-types.md#wled) | +| **The Six Display Modes** | [The Six Display Modes (3)](04-display-pipeline.md#the-six-display-modes) | +| **`--skip-probe`** | [Seeing Which Layer Answered (1)](02-config-rules.md#seeing-which-layer-answered), [`--doctor` (1)](02-config-rules.md#--doctor), [Why Cuts Do Not Tear (5)](06-under-the-hood.md#why-cuts-do-not-tear) | +| **`skip_probe`** | [`debug` (A)](20-appendix-a-configuration.md#debug) | +| **`slideshow`** | [`slideshow` (2)](03-vocabulary.md#slideshow), [`slideshow` (B)](21-appendix-b-scene-types.md#slideshow), [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line) | +| **`soap`** | [Generators (3)](04-display-pipeline.md#generators), [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators) | +| **`song`** | [`waveform` (B)](21-appendix-b-scene-types.md#waveform), [`generative` (B)](21-appendix-b-scene-types.md#generative) | +| **`songlengths_file`** | [Duration and Subtunes (4)](05-sound-and-music.md#duration-and-subtunes), [`playlist` (A)](20-appendix-a-configuration.md#playlist) | +| **Sound** | [Sound (5)](06-under-the-hood.md#sound) | +| **`source`** | [`generative` (2)](03-vocabulary.md#generative), [The Vocabulary (6)](07-inputs-and-outputs.md#the-vocabulary), [`generative` (B)](21-appendix-b-scene-types.md#generative) | +| **`source.a`** | [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.cycle_speed`** | [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.drift_speed`** | [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.grow_speed`** | [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.intensity`** | [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.pulse_speed`** | [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.ring_freq`** | [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.scale`** | [What Ships Mapped (6)](07-inputs-and-outputs.md#what-ships-mapped), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.scroll_speed`** | [What Ships Mapped (6)](07-inputs-and-outputs.md#what-ships-mapped), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.speed`** | [What Ships Mapped (6)](07-inputs-and-outputs.md#what-ships-mapped), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source.zoom_speed`** | [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **`source_alignment_marker`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **Span and Mirror** | [Span and Mirror (6)](07-inputs-and-outputs.md#span-and-mirror) | +| **`spectrum_bitmap`** | [`spectrum_bitmap` (C)](22-appendix-c-overlays.md#spectrum_bitmap), [The Overlays (2)](03-vocabulary.md#the-overlays), [Choosing a Display Mode for an Overlay (2)](03-vocabulary.md#choosing-a-display-mode-for-an-overlay) | +| **`spectrum_petscii`** | [`spectrum_petscii` (C)](22-appendix-c-overlays.md#spectrum_petscii), [The Overlays (2)](03-vocabulary.md#the-overlays), [Choosing a Display Mode for an Overlay (2)](03-vocabulary.md#choosing-a-display-mode-for-an-overlay) | +| **`speed_cells_per_s`** | [`big_text` (C)](22-appendix-c-overlays.md#big_text), [`marquee` (C)](22-appendix-c-overlays.md#marquee), [`rss` (C)](22-appendix-c-overlays.md#rss) | +| **`start_s`** | [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line), [`video` (2)](03-vocabulary.md#video), [`video` (B)](21-appendix-b-scene-types.md#video) | +| **`storage`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [`teensyrom` (A)](20-appendix-a-configuration.md#teensyrom) | +| **`strobe`** | [The Effect Chain (3)](04-display-pipeline.md#the-effect-chain), [What Reads the Features (4)](05-sound-and-music.md#what-reads-the-features), [`webcam` (B)](21-appendix-b-scene-types.md#webcam) | +| **`style`** | [`webcam` (2)](03-vocabulary.md#webcam), [The Six Display Modes (3)](04-display-pipeline.md#the-six-display-modes), [`webcam` (B)](21-appendix-b-scene-types.md#webcam) | +| **`--suggest-palette`** | [`color` (A)](20-appendix-a-configuration.md#color), [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`swipe_velocity`** | [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **`--system`** | [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings), [Connection (G)](26-appendix-g-cli-flags.md#connection), [Introspection (G)](26-appendix-g-cli-flags.md#introspection) | +| **`system`** | [NTSC or PAL (1)](02-config-rules.md#ntsc-or-pal), [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings), [The Control Plane (6)](07-inputs-and-outputs.md#the-control-plane) | + +## T + + +| Term | Discussed in | +|---|---| +| **`target`** | [The Overlays (2)](03-vocabulary.md#the-overlays), [`countdown` (C)](22-appendix-c-overlays.md#countdown) | +| **`target_fps`** | [NTSC or PAL (1)](02-config-rules.md#ntsc-or-pal), [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [The Shape of a Scene (2)](03-vocabulary.md#the-shape-of-a-scene) | +| **`tcp_port`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [`teensyrom` (A)](20-appendix-a-configuration.md#teensyrom) | +| **`[teensyrom]`** | [`teensyrom` (A)](20-appendix-a-configuration.md#teensyrom), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **The TeensyROM Link** | [The TeensyROM Link (5)](06-under-the-hood.md#the-teensyrom-link) | +| **`teensyrom.host`** | [`teensyrom` (A)](20-appendix-a-configuration.md#teensyrom) | +| **`tempo_source`** | [The Beat Grid (6)](07-inputs-and-outputs.md#the-beat-grid), [`performance` (A)](20-appendix-a-configuration.md#performance) | +| **`text`** | [`callsign` (C)](22-appendix-c-overlays.md#callsign), [`marquee` (C)](22-appendix-c-overlays.md#marquee) | +| **`text_color`** | [Between One Scene and the Next (2)](03-vocabulary.md#between-one-scene-and-the-next), [`interstitial` (A)](20-appendix-a-configuration.md#interstitial) | +| **`text_double_height`** | [Overlays (2)](03-vocabulary.md#overlays), [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`video` (B)](21-appendix-b-scene-types.md#video) | +| **The Three Network Services** | [The Three Network Services (1)](02-config-rules.md#the-three-network-services) | +| **`time_base`** | [`waveform` (2)](03-vocabulary.md#waveform), [`waveform` (B)](21-appendix-b-scene-types.md#waveform), [`midi` (B)](21-appendix-b-scene-types.md#midi) | +| **`trails`** | [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [`video` (B)](21-appendix-b-scene-types.md#video), [`slideshow` (B)](21-appendix-b-scene-types.md#slideshow) | +| **`transport`** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [Driving a Video (6)](07-inputs-and-outputs.md#driving-a-video), [`teensyrom` (A)](20-appendix-a-configuration.md#teensyrom) | +| **`tunnel`** | [`generative` (B)](21-appendix-b-scene-types.md#generative), [Generators (E)](24-appendix-e-generators-effects.md#generators), [Generator (F)](25-appendix-f-live-targets.md#generator) | +| **Two Ways Out** | [Two Ways Out (4)](05-sound-and-music.md#two-ways-out) | +| **`type`** | [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [The Shape of a Scene (2)](03-vocabulary.md#the-shape-of-a-scene), [Keys Every Scene Takes (B)](21-appendix-b-scene-types.md#keys-every-scene-takes) | + +## U + + +| Term | Discussed in | +|---|---| +| **The Ultimate Audio Sampler** | [The Ultimate Audio Sampler (4)](05-sound-and-music.md#the-ultimate-audio-sampler) | +| **`[ultimate64]`** | [`ultimate64` (A)](20-appendix-a-configuration.md#ultimate64), [NTSC or PAL (1)](02-config-rules.md#ntsc-or-pal), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`units`** | [`weather` (C)](22-appendix-c-overlays.md#weather) | +| **`--url`** | [Naming the Hardware (1)](02-config-rules.md#naming-the-hardware), [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings), [Flags in Ensemble Mode (1)](02-config-rules.md#flags-in-ensemble-mode) | +| **`url`** | [Naming the Hardware (1)](02-config-rules.md#naming-the-hardware), [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into), [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings) | +| **`use_reu_pump`** | [`audio` (A)](20-appendix-a-configuration.md#audio) | +| **`use_reu_staged`** | [The Preview Window (6)](07-inputs-and-outputs.md#the-preview-window), [`video` (A)](20-appendix-a-configuration.md#video) | + +## V + + +| Term | Discussed in | +|---|---| +| **Validation** | [Validation (1)](02-config-rules.md#validation) | +| **`--verbose`** | [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`verbose`** | [`debug` (A)](20-appendix-a-configuration.md#debug), [Debug (G)](26-appendix-g-cli-flags.md#debug) | +| **`--version`** | [Options (G)](26-appendix-g-cli-flags.md#options) | +| **`[video]`** | [`video` (A)](20-appendix-a-configuration.md#video), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`video`** | [`video` (2)](03-vocabulary.md#video), [`video` (B)](21-appendix-b-scene-types.md#video), [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line) | +| **`video.device`** | [Machine Settings (1)](02-config-rules.md#machine-settings), [Choosing a Camera (6)](07-inputs-and-outputs.md#choosing-a-camera), [`video` (A)](20-appendix-a-configuration.md#video) | +| **`--videos`** | [Playlist (G)](26-appendix-g-cli-flags.md#playlist) | +| **`videos_dir`** | [Scenes and Playlists (1)](02-config-rules.md#scenes-and-playlists), [Between One Scene and the Next (2)](03-vocabulary.md#between-one-scene-and-the-next), [`playlist` (A)](20-appendix-a-configuration.md#playlist) | +| **`--vision`** | [Vision Input (G)](26-appendix-g-cli-flags.md#vision-input) | +| **`[vision]`** | [`vision` (A)](20-appendix-a-configuration.md#vision), [The Extra Layer (1)](02-config-rules.md#the-extra-layer) | +| **`--vision-model`** | [Vision Input (G)](26-appendix-g-cli-flags.md#vision-input) | +| **`vision.enabled`** | [Gestures (6)](07-inputs-and-outputs.md#gestures), [`vision` (A)](20-appendix-a-configuration.md#vision) | +| **The Vocabulary** | [The Vocabulary (6)](07-inputs-and-outputs.md#the-vocabulary) | +| **`voice_colors`** | [`waveform` (2)](03-vocabulary.md#waveform), [`waveform` (B)](21-appendix-b-scene-types.md#waveform), [`midi` (B)](21-appendix-b-scene-types.md#midi) | + +## W + + +| Term | Discussed in | +|---|---| +| **`waveform`** | [`waveform` (2)](03-vocabulary.md#waveform), [`waveform` (B)](21-appendix-b-scene-types.md#waveform), [Media on the Command Line (1)](02-config-rules.md#media-on-the-command-line) | +| **`waveform_colors`** | [`waveform` (B)](21-appendix-b-scene-types.md#waveform), [`midi` (B)](21-appendix-b-scene-types.md#midi), [`asid` (B)](21-appendix-b-scene-types.md#asid) | +| **`weather`** | [`weather` (C)](22-appendix-c-overlays.md#weather), [The Overlays (2)](03-vocabulary.md#the-overlays), [The Matrix (D)](23-appendix-d-compatibility.md#the-matrix) | +| **`webcam`** | [`webcam` (2)](03-vocabulary.md#webcam), [`webcam` (B)](21-appendix-b-scene-types.md#webcam), [Frame Rate (2)](03-vocabulary.md#frame-rate) | +| **What a Scene Records About Itself** | [What a Scene Records About Itself (6)](07-inputs-and-outputs.md#what-a-scene-records-about-itself) | +| **What a Target Decomposes Into** | [What a Target Decomposes Into (1)](02-config-rules.md#what-a-target-decomposes-into) | +| **What Each Backend Can Do** | [What Each Backend Can Do (5)](06-under-the-hood.md#what-each-backend-can-do) | +| **What Each Costs in Frame Rate** | [What Each Costs in Frame Rate (4)](05-sound-and-music.md#what-each-costs-in-frame-rate) | +| **What Ends a Scene** | [What Ends a Scene (2)](03-vocabulary.md#what-ends-a-scene) | +| **What Is Refused** | [What Is Refused (4)](05-sound-and-music.md#what-is-refused) | +| **What Lands in Memory** | [What Lands in Memory (5)](06-under-the-hood.md#what-lands-in-memory) | +| **What Reads the Features** | [What Reads the Features (4)](05-sound-and-music.md#what-reads-the-features) | +| **What Ships Mapped** | [What Ships Mapped (6)](07-inputs-and-outputs.md#what-ships-mapped) | +| **What to Give Up First** | [What to Give Up First (5)](06-under-the-hood.md#what-to-give-up-first) | +| **Where the Working Code Is** | [Where the Working Code Is (7)](08-extending.md#where-the-working-code-is) | +| **Which Colours** | [Which Colours — `cell_strategy` (3)](04-display-pipeline.md#which-colours--cell_strategy) | +| **Which Pixel Takes Which** | [Which Pixel Takes Which — `dither` (3)](04-display-pipeline.md#which-pixel-takes-which--dither) | +| **Why a Spare Core Stays Mapped** | [Why a Spare Core Stays Mapped (4)](05-sound-and-music.md#why-a-spare-core-stays-mapped) | +| **Why Cuts Do Not Tear** | [Why Cuts Do Not Tear (5)](06-under-the-hood.md#why-cuts-do-not-tear) | +| **WLED** | [WLED (6)](07-inputs-and-outputs.md#wled) | +| **`[wled]`** | [`wled` (A)](20-appendix-a-configuration.md#wled), [The Extra Layer (1)](02-config-rules.md#the-extra-layer), [`wled` (2)](03-vocabulary.md#wled) | +| **`wled`** | [`wled` (2)](03-vocabulary.md#wled), [`wled` (B)](21-appendix-b-scene-types.md#wled), [WLED (6)](07-inputs-and-outputs.md#wled) | +| **The Write Budget** | [The Write Budget (5)](06-under-the-hood.md#the-write-budget) | +| **Writing It With `--save-settings`** | [Writing It With `--save-settings` (1)](02-config-rules.md#writing-it-with---save-settings) | +| **Writing Your Own Overlay or Generator** | [Writing Your Own Overlay or Generator (7)](08-extending.md#writing-your-own-overlay-or-generator) | +| **Writing Your Own Scene** | [Writing Your Own Scene (7)](08-extending.md#writing-your-own-scene) | diff --git a/docs/reference/README.md b/docs/reference/README.md index da130141..8a7c6263 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -26,10 +26,11 @@ make reference # -> docs/reference/c64cast-reference-guide.pdf make books # every book at once ``` -## The generated appendices +## The generated appendices and index -Appendices A to I are **not written by hand**. They are read out of the same -definitions that answer `--describe`, `--compat` and `--print-schema`, by +Appendices A to I and the index are **not written by hand**. They are read out +of the same definitions that answer `--describe`, `--compat` and +`--print-schema`, by [`scripts/gen_reference_appendices.py`](../../scripts/gen_reference_appendices.py): | Appendix | Comes from | @@ -43,6 +44,14 @@ definitions that answer `--describe`, `--compat` and `--print-schema`, by | G — Command-line flags | the argparse parser in `cli.py` | | H — Example configurations | the packaged `examples/`, read through `paths` | | I — Optional extras | `doctor._EXTRAS` joined with `pyproject.toml` | +| The index | all of the above, crossed with the book's own Markdown | + +The index is the only one that reads the book rather than the code. Every name +the program can utter goes in; the locators come from scanning the committed +chapters for that name in a code span or a heading and taking the section it +sits in, which is why a section renamed without a regeneration shows up as a +broken link rather than as a wrong page number. It carries no `number`, so it +renders after Appendix J as a plain heading rather than as Appendix K. The same pass writes the [Performance Card](../card/README.md)'s live-target table, which is the card's most drift-prone page. diff --git a/docs/shared/template.typ b/docs/shared/template.typ index 401ca434..f9789ab9 100644 --- a/docs/shared/template.typ +++ b/docs/shared/template.typ @@ -182,8 +182,14 @@ // The handful of longer ones wrap, which is the right thing to spend on them. // --------------------------------------------------------------------------- +// The identity column is not justified. It holds names, which do not stretch, +// until the index puts a section title there — and "The BASIC Program +// Underneath" justified into 1.5in is three words with two rivers through it. #let fields-column = 1.5in -#let fields-table(..args) = table(columns: (fields-column, 1fr), ..args) +#let fields-table(..args) = { + show table.cell.where(x: 0): set par(justify: false) + table(columns: (fields-column, 1fr), ..args) +} // --------------------------------------------------------------------------- // Chapter openers diff --git a/scripts/gen_reference_appendices.py b/scripts/gen_reference_appendices.py index b41e6ec7..71266edb 100644 --- a/scripts/gen_reference_appendices.py +++ b/scripts/gen_reference_appendices.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Generate the Programmer's Reference Guide's appendices from the code. +"""Generate the Programmer's Reference Guide's appendices and index from the code. make reference-appendices # rewrite them make reference-appendices && git diff --exit-code # the drift guard @@ -10,6 +10,10 @@ answers ``--describe``, ``--compat`` and ``--print-schema``: :mod:`c64cast.introspect`. An appendix cannot disagree with the program. +The index is the same model read the other way round, crossed with the book's +own prose: every name the program can utter, against the sections that discuss +it. See :func:`build_index`. + The output is committed Markdown, not Typst, for two reasons. The books are rendered to GitHub Pages from these same sources, so nothing may live only in the PDF; and ``scripts/build_book.py`` is deliberately stdlib-only (the release @@ -33,11 +37,14 @@ from __future__ import annotations import argparse +import importlib.util import re import sys import tomllib from collections.abc import Callable, Iterable, Sequence +from dataclasses import dataclass from pathlib import Path +from types import ModuleType from c64cast import cli as climod from c64cast import doctor, effects, generators, introspect @@ -47,6 +54,34 @@ REFERENCE_DIR = REPO_ROOT / "docs" / "reference" CARD_DIR = REPO_ROOT / "docs" / "card" + +def _build_book() -> ModuleType: + """``scripts/build_book.py``, loaded by path. + + The index writes a link per locator, and a link resolves only if the anchor + it names is spelled exactly the way the converter spells it. Borrowing + ``heading_slug`` rather than reimplementing GitHub's rule a second time is + what stops the two from drifting into a book full of dead links. + + scripts/ is not a package, so neither ``import`` nor ``sys.path`` can be + relied on -- this module is loaded by path itself, from the tests. An + already-loaded copy is reused rather than a second one built. + """ + module = sys.modules.get("build_book") + if module is None: + path = Path(__file__).resolve().with_name("build_book.py") + spec = importlib.util.spec_from_file_location("build_book", path) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + # Registered before exec: @dataclass resolves annotations through + # sys.modules[cls.__module__], which blows up if the module isn't there. + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +bb = _build_book() + # A default longer than this is summarised rather than printed. Only one field # hits it -- [midi_control].cc_map, whose shipped default is two dozen mappings # and 2,500 characters. A table cell is the wrong place to read that; the @@ -232,15 +267,23 @@ def identity(*lines: str) -> str: return "
".join([f"**{kept[0]}**", *kept[1:]]) -def fields_table(label: str, rows: Iterable[Sequence[str]]) -> list[str]: +def fields_table( + label: str, + rows: Iterable[Sequence[str]], + *, + description: str = "Description", +) -> list[str]: """A two-column table: :func:`identity` on the left, prose on the right. The directive is an HTML comment, invisible on github.com, that tells `build_book.py` to hand this table the one column width every table of this shape uses -- so a scene key, an overlay parameter and a CLI flag all line up down the book instead of each being sized to its own longest entry. + + The right-hand heading is a parameter only because the index's right column + holds locators rather than a description; every appendix takes the default. """ - body = table([label, "Description"], rows) + body = table([label, description], rows) return ["", *body] if body else [] @@ -687,6 +730,358 @@ def appendix_extras() -> list[str]: return out +# --------------------------------------------------------------------------- +# The index +# --------------------------------------------------------------------------- +# +# Two halves, and both are mechanical. The terms come from the same +# introspection the appendices are built from, so the index cannot list a +# setting the program does not have. The locators come from the book's own +# Markdown, so it cannot point at a section that is not there. + +INDEX_PATH = REFERENCE_DIR / "30-index.md" + +# Words that arrive in a code span looking like terms and are not: every one of +# them is what you write on the *right* of an `=`. Left in, `auto` alone would +# collect a locator in five chapters and mean nothing in any of them. +_INDEX_STOP_WORDS = frozenset({"auto", "true", "false", "none", "on", "off", "random"}) + +# Under this length a token is an abbreviation the scan cannot tell from an +# accident -- `id`, `hz`, a short flag's single letter. `fps` sits just above. +_MIN_TERM_LEN = 3 + +# Locators per term. The fourth is never the one you wanted, and the column it +# has to fit in is about three inches. +_MAX_LOCATORS = 3 + + +@dataclass(frozen=True) +class Term: + """One index entry. + + `key` is what a code span has to say for the term to be found; `display` is + how the entry prints; `sort` is what it files under, which is neither -- + `--config` files under C and ``[audio]`` under A. + """ + + key: str + display: str + sort: str + + +@dataclass(frozen=True) +class Locator: + """One section a term was found in, and where it ranks among the rest.""" + + filename: str + slug: str + title: str + chapter: str + order: tuple[int, int, int, int] + + def markdown(self) -> str: + """The locator as a link, with the chapter it is in. + + The number is inside the link text rather than beside it, so the whole + locator is one thing to press rather than a link with a loose tail. + + Carrying it at all is not decoration: three sections in this book are + called some case of "MIDI", and `midi_voice_channels` is discussed in + all three. Without the number the reader gets `midi`, MIDI, `midi` and + no way to tell which is which. + """ + where = f" ({self.chapter})" if self.chapter else "" + return f"[{locator_text(self.title)}{where}]({self.filename}#{self.slug})" + + +_CODE_SPAN_SCAN_RE = re.compile(r"(?P`+)(?P[^`]+)(?P=fence)") +_IDENT_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*") +_LONG_FLAG_RE = re.compile(r"--[a-z][a-z0-9-]*") +_QUALIFIED_KEY_RE = re.compile(r"\[(\w+)\]\.(\w+)") +_SECTION_BRACKET_RE = re.compile(r"\[\w+\]") +_LEADING_ARTICLE_RE = re.compile(r"^(?:the|a|an)\s+") +_TRAILING_NAME_RE = re.compile(r"\s+—\s+`[^`]+`$") +_PURE_CODE_RE = re.compile(r"^`[^`]+`$") + + +def mentions(text: str) -> set[str]: + """Every string in one line of Markdown that could be naming something. + + Only what is inside a code span counts. The book writes every name it means + in `this face`, and matching bare prose would file the sentence "the video + scene plays a file" under `video`, `scene` and `file` at once. + + A span is picked apart rather than matched whole because one span carries + several names: ``[audio].backend`` is both the qualified key and the bare + field, and `mode.dither_strength` is a live target and a field. + + A bracketed section is taken whole and then masked out, so ``[audio].backend`` + stops crediting the *scene* key `audio` — which is a different setting, and + was collecting the whole of the `[audio]` section's mentions. + """ + found: set[str] = set() + for span in _CODE_SPAN_SCAN_RE.finditer(text): + body = span.group("body").strip() + found.add(body) + found.update(_LONG_FLAG_RE.findall(body)) + for m in _QUALIFIED_KEY_RE.finditer(body): + found.add(f"{m.group(1)}.{m.group(2)}") + for m in _IDENT_RE.finditer(_SECTION_BRACKET_RE.sub(" ", body)): + token = m.group(0) + found.add(token) + found.add(token.rpartition(".")[2]) + return found + + +def concept(title: str) -> str: + """A section title as an index entry, or `""` if it should not be one. + + A title that is nothing but a name -- ``### `sid_panning` `` -- is already + an entry under that name, and a second spelled identically would be a + duplicate row pointing at the same place. A title that *ends* in a + qualifying name -- "Companding — `dac_curve`" -- files under the concept, + which is the word a reader who does not yet know the key looks for. + """ + text = _TRAILING_NAME_RE.sub("", title).strip() + return "" if not text or _PURE_CODE_RE.match(text) else text + + +def sort_key(text: str) -> str: + """What an entry files under: the word a reader would look it up by. + + Leading punctuation goes because nobody looks for `--config` under a + hyphen, and a leading article goes because "The Audio Slot" is an entry + about the audio slot. + """ + return _LEADING_ARTICLE_RE.sub("", text.strip("`[]-").lower()) + + +def code_terms() -> dict[str, Term]: + """Every name the program can utter, keyed by what a code span would say. + + A configuration field is entered bare, and additionally qualified when two + sections both have a key by that name -- which is the rule the book's own + Notation section states, and the only case where the qualified spelling + tells the reader anything. `dither` is `[color]`'s dithering and `[audio]`'s + noise shaping, so both are listed; `agc` belongs to `[dsp]` alone, and a + `dsp.agc` row would point at the same place the `agc` row does. + + A name that two registries share -- a scene type and a generator called the + same thing -- is one entry, since one entry is what it is. + """ + terms: dict[str, Term] = {} + + def add(key: str, display: str | None = None) -> None: + if len(key) < _MIN_TERM_LEN or key.lower() in _INDEX_STOP_WORDS: + return + terms.setdefault(key, Term(key, display or code(key), sort_key(key))) + + sections = introspect.config_sections() + shared: dict[str, int] = {} + for sd in sections: + for fd in sd.fields: + shared[fd.name] = shared.get(fd.name, 0) + 1 + for sd in sections: + add(f"[{sd.name}]") + for fd in sd.fields: + if shared[fd.name] > 1: + add(f"{sd.name}.{fd.name}") + add(fd.name) + for st in introspect.scene_types(): + add(st.name) + for fd in st.fields: + add(fd.name) + for od in introspect.overlay_docs(): + add(od.name) + for p in od.params: + add(p.name) + for md in introspect.display_modes(): + add(md.name) + for name in (*generators.REGISTRY, *effects.REGISTRY): + add(name) + for lt in introspect.live_targets(): + add(lt.target) + for action in climod.build_parser()._actions: + for flag in action.option_strings: + if flag.startswith("--"): + add(flag) + return terms + + +def concept_terms(paths: Sequence[Path]) -> dict[str, Term]: + """The book's own section titles, as entries. + + Only from the numbered prose chapters. An appendix's headings are names, + which :func:`code_terms` already has, and the introduction's are about the + book rather than about c64cast -- "What Is In Here" is not something anyone + looks up. + """ + terms: dict[str, Term] = {} + for path in paths: + fields, body, _ = bb.parse_front_matter(path.read_text(encoding="utf-8"), path) + if not (fields.get("number") or "").isdigit(): + continue + for title in _headings(body): + name = concept(title) + if name: + terms.setdefault(name, Term(name, name, sort_key(name))) + return terms + + +def _headings(body: str) -> list[str]: + """The `##`/`###` titles of one file, in the order `file_section_slugs` + returns their slugs -- the two are zipped, so they must not diverge.""" + titles: list[str] = [] + fenced = False + for line in body.split("\n"): + if line.strip().startswith("```"): + fenced = not fenced + continue + m = bb._HEADING_RE.match(line) + if not fenced and m and len(m.group("hashes")) in (2, 3): + titles.append(m.group("text").strip()) + return titles + + +def scan( + paths: Sequence[Path], + codes: dict[str, Term], + concepts: dict[str, Term], +) -> dict[str, list[Locator]]: + """Where each term is discussed, best first. + + Best is a section *titled* with the term, because that is the one written + about it; then the prose chapters before the appendices, which is what + keeps the locator for `dither` from being its own row in Appendix A's + `[color]` table rather than the section explaining what dithering is for. + The two rules do not fight: an appendix names a term in a heading only + where the whole section is that term's entry, and every other appendix hit + is a table cell, which now sorts last. + """ + hits: dict[str, dict[tuple[str, str], Locator]] = {} + for file_order, path in enumerate(paths): + fields, body, _ = bb.parse_front_matter(path.read_text(encoding="utf-8"), path) + chapter = fields.get("number") or "" + prose_rank = 0 if chapter.isdigit() else 1 + slugs = iter(bb.file_section_slugs(body)) + section: tuple[str, str] | None = None + fenced = False + for lineno, line in enumerate(body.split("\n")): + if line.strip().startswith("```"): + # An example configuration names half the program. Indexing + # what a listing happens to contain would bury the discussion. + fenced = not fenced + continue + if fenced: + continue + heading = bb._HEADING_RE.match(line) + if heading is not None and len(heading.group("hashes")) in (2, 3): + in_title = True + section = (next(slugs), heading.group("text").strip()) + found = mentions(section[1]) + name = concept(section[1]) + if name: + found.add(name) + elif section is not None: + in_title = False + found = mentions(line) + else: + continue + slug, title = section + # Appendix A writes a field bare inside the section it belongs to, + # so `dither` under `## [color]` is where `color.dither` is + # defined. Offering the qualified spelling here is what gives the + # qualified entries their table locator; a slug that is not a + # section name forms nothing the term table will match. + found |= {f"{slug}.{name}" for name in found} + for name in found: + term = codes.get(name) or concepts.get(name) + if term is None: + continue + order = (0 if in_title else 1, prose_rank, file_order, lineno) + where = hits.setdefault(term.key, {}) + where.setdefault((path.name, slug), Locator(path.name, slug, title, chapter, order)) + return { + key: sorted(found.values(), key=lambda loc: loc.order)[:_MAX_LOCATORS] + for key, found in hits.items() + } + + +def locator_text(title: str) -> str: + """A section title, safe as the text of a Markdown link. + + Brackets are dropped rather than escaped. Appendix A calls its sections + ``[audio]``, and both renderers stop a link's text at the first `]` -- so a + bracketed title reaches the page as literal text with its URL showing. The + brackets say nothing here that the link around them does not. + """ + return title.replace("[", "").replace("]", "") + + +def build_index() -> list[str]: + """Every name the program can utter, against the sections that discuss it. + + Not an appendix: it carries no `number`, which is what makes it render as a + plain heading after Appendix J rather than as Appendix K. + + Locators are section titles rather than page numbers. A page number is + reachable -- the contents page queries for them -- but it would need a + Typst-side helper per locator, and a title is the more useful of the two on + github.com, where the Markdown is the book and there are no pages. + """ + paths = [p for p in bb.discover_chapters(REFERENCE_DIR) if p != INDEX_PATH] + codes = code_terms() + concepts = concept_terms(paths) + found = scan(paths, codes, concepts) + + entries = sorted( + (term for term in (*codes.values(), *concepts.values()) if term.key in found), + key=lambda t: (t.sort, t.key), + ) + out = [ + "---", + "generated: true", + "---", + "", + "# Index", + "", + "*Generated from the code by `scripts/gen_reference_appendices.py`.", + "Edits here are overwritten; run `make reference-appendices`.*", + "", + prose( + f"Every name c64cast answers to — {len(entries)} of them — and the sections " + "that discuss each one, the section written about it first. Names are " + "listed as this book writes them: a configuration key appears bare, and " + "again qualified with its section where two sections share the name. An " + "entry in ordinary words is a section of the book by that title. The " + "chapter or appendix each locator is in follows it, in parentheses." + ), + "", + ] + for letter, group in _by_letter(entries): + out += [f"## {letter}", ""] + rows = [ + [identity(term.display), ", ".join(loc.markdown() for loc in found[term.key])] + for term in group + ] + out += fields_table("Term", rows, description="Discussed in") + return out + + +def _by_letter(entries: Sequence[Term]) -> list[tuple[str, list[Term]]]: + """The entries grouped under their initial, in order. + + Anything that does not file under a letter opens the index under `#`, where + a printed one has always put it. Nothing lands there today; a setting named + for a number would go somewhere rather than vanish. + """ + groups: dict[str, list[Term]] = {} + for term in entries: + initial = term.sort[:1].upper() + groups.setdefault(initial if initial.isalpha() else "#", []).append(term) + return sorted(groups.items(), key=lambda kv: (kv[0].isalpha(), kv[0])) + + # --------------------------------------------------------------------------- # The performance card's live-target table # --------------------------------------------------------------------------- @@ -786,6 +1181,7 @@ def card_live_targets() -> list[str]: REFERENCE_DIR / "26-appendix-g-cli-flags.md": appendix_cli, REFERENCE_DIR / "27-appendix-h-examples.md": appendix_examples, REFERENCE_DIR / "28-appendix-i-extras.md": appendix_extras, + INDEX_PATH: build_index, CARD_DIR / "02-live-targets.md": card_live_targets, } diff --git a/tests/test_reference_appendices.py b/tests/test_reference_appendices.py index 1208ff62..ffd756ed 100644 --- a/tests/test_reference_appendices.py +++ b/tests/test_reference_appendices.py @@ -71,12 +71,12 @@ def test_every_generated_file_says_so(self): self.assertEqual(fields.get("generated"), "true") def test_the_appendices_cover_the_reference_book(self): - # A-I are generated; the introduction, the seven chapters and the - # glossary are not. If a hand-written chapter ever acquires the marker, - # the next `make reference-appendices` would not touch it and the drift - # guard above would silently pass on a file nobody generates. + # A-I and the index are generated; the introduction, the seven chapters + # and the glossary are not. If a hand-written chapter ever acquires the + # marker, the next `make reference-appendices` would not touch it and + # the drift guard above would silently pass on a file nobody generates. generated = {p for p in gen.APPENDICES if p.parent == gen.REFERENCE_DIR} - self.assertEqual(len(generated), 9) + self.assertEqual(len(generated), 10) for path in bb.discover_chapters(gen.REFERENCE_DIR): fields, _, _ = bb.parse_front_matter(path.read_text(encoding="utf-8"), path) with self.subTest(chapter=path.name): @@ -95,13 +95,118 @@ class ConverterSafetyTest(unittest.TestCase): def test_every_generated_file_converts(self): for path in gen.APPENDICES: with self.subTest(file=path.name): - chapters = bb.chapter_numbers(bb.discover_chapters(path.parent)) - self.assertTrue(bb.load_chapter(path, chapters).title) + paths = bb.discover_chapters(path.parent) + anchors = bb.section_anchors(paths) + # The anchors are not optional here: the index is nothing but + # links at sections, and the converter refuses one it cannot + # resolve rather than emitting a dead destination. + chapter = bb.load_chapter(path, bb.chapter_numbers(paths), anchors) + self.assertTrue(chapter.title) def test_the_reference_book_builds(self): self.assertIn("#show: guide.with(", bb.build(gen.REFERENCE_DIR)) +class IndexTest(unittest.TestCase): + """The generated index. + + Freshness is the drift guard's job. What is particular to the index is that + it is made almost entirely of links into the rest of the book, so a section + renamed without a regeneration turns every locator into it stale -- and a + stale locator is the one failure a reader meets rather than the build. + """ + + @classmethod + def setUpClass(cls) -> None: + cls.text = gen.INDEX_PATH.read_text(encoding="utf-8") + cls.rows = dict(re.findall(r"^\| \*\*(.+?)\*\* \| (.+?) \|$", cls.text, re.M)) + + def test_every_locator_names_a_section_that_exists(self): + anchors = bb.section_anchors(bb.discover_chapters(gen.REFERENCE_DIR)) + links = re.findall(r"\]\((\d+-[\w.-]+\.md)#([\w-]+)\)", self.text) + self.assertGreater(len(links), 500, "the index lost most of its locators") + for filename, slug in links: + with self.subTest(link=f"{filename}#{slug}"): + self.assertIn(bb.section_label(Path(filename).stem, slug), anchors) + + def test_it_is_not_an_appendix(self): + # No `number`, which is what makes it render after Appendix J as a + # plain heading instead of claiming a letter of its own. + fields, _, _ = bb.parse_front_matter(self.text, gen.INDEX_PATH) + self.assertNotIn("number", fields) + + def test_it_does_not_index_itself(self): + self.assertNotIn(gen.INDEX_PATH.name, self.text) + + def test_the_section_written_about_a_term_leads_its_entry(self): + # Appendix A has a row for every configuration field, so ordering by + # position alone would answer "where is dither explained" with the + # table rather than with the section that explains it. + self.assertTrue( + self.rows["`dither`"].startswith("[Which Pixel Takes Which"), + self.rows["`dither`"], + ) + + def test_a_section_title_is_an_entry_in_its_own_words(self): + # The concept entries are what let a reader look up the thing before + # they know what it is called. + for title in ("Companding", "The Audio Slot", "Fades"): + with self.subTest(title=title): + self.assertIn(title, self.rows) + + +class IndexTermTest(unittest.TestCase): + """The pure parts of the index: what counts as a name, and where it files.""" + + def test_a_span_names_every_key_it_contains(self): + self.assertEqual( + gen.mentions("set `[color].dither` to `ordered`"), + {"[color].dither", "color.dither", "dither", "ordered"}, + ) + + def test_a_bracketed_section_does_not_credit_the_bare_word(self): + # `audio` is a scene key. Left in, it would collect every mention of + # the `[audio]` section as though the two were the same setting. + self.assertNotIn("audio", gen.mentions("`[audio].backend`")) + + def test_a_flag_is_found_inside_a_command(self): + self.assertIn("--save-settings", gen.mentions("run `c64cast --save-settings`")) + + def test_a_title_that_is_only_a_name_is_not_a_concept(self): + for title in ("`sid_panning`", "`[hardware]`"): + with self.subTest(title=title): + self.assertEqual(gen.concept(title), "") + + def test_a_title_qualified_by_a_name_files_under_the_concept(self): + self.assertEqual(gen.concept("Companding — `dac_curve`"), "Companding") + # Only a *trailing* name is a qualifier; an em dash joining two phrases + # is part of the title. + self.assertEqual( + gen.concept("Broadcast — Fixtures React to the Music"), + "Broadcast — Fixtures React to the Music", + ) + + def test_an_entry_files_under_the_word_it_is_looked_up_by(self): + self.assertEqual(gen.sort_key("--config"), "config") + self.assertEqual(gen.sort_key("[audio]"), "audio") + self.assertEqual(gen.sort_key("The Audio Slot"), "audio slot") + + def test_a_value_or_an_abbreviation_is_never_a_term(self): + keys = gen.code_terms() + self.assertFalse({k.lower() for k in keys} & gen._INDEX_STOP_WORDS) + self.assertFalse([k for k in keys if len(k) < gen._MIN_TERM_LEN]) + + def test_a_field_is_qualified_only_where_two_sections_share_the_name(self): + keys = gen.code_terms() + # `dither` is [color]'s dithering and [audio]'s noise shaping. + self.assertIn("color.dither", keys) + self.assertIn("audio.dither", keys) + # `agc` belongs to [dsp] alone, so a `dsp.agc` row would be a second + # entry pointing where the `agc` one already points. + self.assertIn("agc", keys) + self.assertNotIn("dsp.agc", keys) + + class LiveMarkTest(unittest.TestCase): """The two *live* marks Appendices A and B carry.