Skip to main content

sva_cli/
help.rs

1// Concern: the `--help` page, each flag's default printed from its own constant | Non-concern: parsing those flags (args/), the JSON a subcommand answers (output.rs) | IO: () -> the page
2
3use sva_core::{
4    DEFAULT_LEDGER_DEPTH, DEFAULT_MAX_PEAKS, DEFAULT_OVERSAMPLE, DEFAULT_SILENT_BITS,
5    DEFAULT_SILENT_MAX_SECS,
6};
7use sva_engine::{DEFAULT_FRAME_SECS, DEFAULT_SAMPLE_RATE, PSYCHOACOUSTIC_V1};
8
9/// Read off the constants the parser itself defaults to, so a printed default cannot drift
10/// from the one a render actually uses.
11pub fn help_text() -> String {
12    let budget = PSYCHOACOUSTIC_V1.flop_budget;
13    format!(
14        r#"USAGE:
15  sva-cli (render | analyze | lint | trace | builtins | outline | new) [arguments]
16
17DESCRIPTION:
18  A composition is a directory of node files, each one closed-form expression in
19  `t` or `f`. sva-cli reads that composition and prints what it is and what it
20  sounds like, as JSON on stdout.
21
22  `--in <dir>` picks the composition for render, lint and trace, wherever in the
23  arguments it is written; without it they read the current directory. `new`
24  writes beside the current directory and `analyze` reads a file, so neither
25  takes `--in`.
26
27RENDER:
28  sva-cli render [<node|expression>] [query options]
29
30  Renders a node, `master` by default, and prints one reading per `--as`. The
31  argument may be an expression instead, in the grammar a node file's body uses.
32  Every reading states its `source` (exact or measured), the conformance profile
33  it ran under, and its rate.
34
35  `--as lines` and `--as atoms` read the closed form and allocate no buffer.
36  `--as samples=<path>.wav` writes 32-bit float audio, or 16-bit PCM under
37  `--pcm16`. Any other destination path takes the same JSON, uncapped. A path
38  that already holds a file refuses unless `--confirm` is written.
39
40  `--from`/`--to` bound the window a collapse runs over. `--sample-rate <hz>` is
41  the observation rate and is legal with every `--as`: no expression can read it.
42
43  `--to silent[:bits]` ends the render at its last sample at or over 2^-bits of
44  full scale, once a bound on every node proves no later sample reaches it.
45  Where silence is not proven by `--max <secs>` it refuses as
46  `engine.not_silent_by` with the bound there; a node that holds a level forever
47  refuses as `engine.never_silent`, and one no bound is derived for yet (a
48  physical solver other than chaigne_askenfelt, one string or a unison, a filter
49  whose coefficients move) as `engine.no_tail_bound`.
50
51  `--as ledger` prints one row per node under the target. A row's `share` is the
52  part of its reader's own energy that row accounts for, so one reader's refs sum
53  to 1; a ref no addend isolates, such as one factor of a product, prints `null`.
54  Each ref carrying a share is collapsed once on its own, so a ledger costs one
55  collapse per attributed ref beyond the render, and `--depth` bounds how many.
56  `--brief` keeps only the rows that clipped, `--skim` drops the wider fields.
57
58  `--as arguments` renders nothing: for every instance under the target it
59  prints each builtin call's named arguments as the numbers the call was lowered
60  with, a solver's whole parameter set with `written: false` on each default it
61  filled in, and the operand each `min`/`max` `chosen` where a call folds one to a
62  number: in named arguments and a solver's, modal bank's or `noise`'s
63  positionals. One inside a filter's or cast's positional or `rand`'s key or
64  seed is not listed. `at` spans are bytes of the instance's
65  own body, as `sva-cli outline` counts them.
66
67ANALYZE:
68  sva-cli analyze <file.wav> [--as <representation>[=<destination>]]...
69
70  Runs the same readings over an external `.wav` at its own rate, never
71  resampled. Only the readings a buffer answers alone apply; the rest need the
72  graph behind it.
73
74LINT:
75  sva-cli lint [<node|expression>] [--in <dir>] [--format <json|text>]
76
77  Checks binding, ref and tempo resolution without rendering a sample. With no
78  target it checks the whole directory against `master`. With a target it checks
79  only the nodes that target reaches, and `entry-point` does not run, since the
80  target's own reach references every node in it.
81
82  Every check prints one `data.diagnostics` item. `advice` and `warning` exit 0,
83  `error` exits non-zero, so branch on the verdict and never on whether the array
84  is empty. No flag downgrades an error.
85
86  error    missing-comment       no `;` comment line
87           multiline-comment     more than one
88           malformed-comment     not four ` | ` fields, `Models:` `Neglects:`
89                                 `IO: <in> -> <out>` `Tags: <tag>[, <tag>...]`
90           long-comment-block    a `;` block over 1000 characters, the line-1
91                                 doc comment's own run exempted
92           long-expression-body  a body over 10000 characters, a backstop rather
93                                 than a complexity budget
94  warning  grid-rows-per-bar     a TSV grid's row count does not divide evenly
95                                 into its filename's bar span
96           key-is-not-a-pitch    `variables/key` holds neither a note name nor
97                                 a number of hertz
98           entry-point-refused   a node a whole-directory render reaches does
99                                 not type
100  advice   entry-point           nothing references this node
101           no-default-root       the directory has no `master`
102           tag-shape             a tag over 3 lowercase words or 24 characters
103           window-inside-ramp    a window sits wholly inside a crop's shoulder
104           literal-sample-rate   a written rate where `sp` belongs
105           not-a-file            a socket, FIFO or device in the directory
106           not-a-node            a filename no `@ref` can spell
107
108TRACE:
109  sva-cli trace <node|expression> [--in <dir>]
110
111  Prints one node's position without rendering audio: what it reads (`down`, one
112  hop), everything that reads it (`up`, transitively to an entry point), each
113  beside the expression doing the reading, the node that made it discrete, and
114  the feedback loop it sits in, if any.
115
116BUILTINS:
117  sva-cli builtins
118
119  Prints the whole callable and syntactic vocabulary: every builtin with its
120  arity and named arguments, each argument's `meaning`, `unit`, the model
121  `part` it sets and whether it `moves` with `t` (a filter's cutoff, q and gain;
122  every other named argument is one number, refused when it names none), each
123  positional's meaning where the model states one, unit suffixes, the note-name
124  grammar, reserved identifiers, special call shapes, and what has no operator
125  at all.
126
127OUTLINE:
128  sva-cli outline <expression>
129
130  Prints the parse tree the engine builds from one expression, each node with
131  the byte `span` it was written in: calls by `name` with positional and named
132  `args`, operators by `op`, refs by `path` with their `binds`, literals by
133  `value` and `unit`, names by `name`. A node the parser supplies itself, the
134  `0` of a prefix minus or the `t` of a bare `@ref`, has `written: false`.
135  Reads no composition.
136
137NEW:
138  sva-cli new <name> [--idempotency-key <key>]
139
140  Writes a starter composition at ./<name>, and refuses if that directory
141  exists. `--idempotency-key <key>` records the key beside the composition, so a
142  retry under the same key succeeds identically while the tree still holds what
143  was written. Any other key, or an edited tree, refuses.
144
145EXAMPLES:
146  sva-cli new song1 && cd song1
147  cd ./song1 && sva-cli render --as samples=/tmp/song1.wav
148  sva-cli render master --in ./song1 --as ledger --as loudness
149  cd ./song1 && sva-cli render chord/home --as lines
150  sva-cli lint --in ./song1
151  cd ./song1 && sva-cli lint voice/note
152  cd ./song1 && sva-cli trace grid/phrase-2b
153  sva-cli builtins
154
155OUTPUT:
156  {{"status": "success", "data": {{"node": "master", "down": {{"items": [...]}},
157  "diagnostics": {{"items": []}}}},
158   "meta": {{"request_id": "req_...", "timestamp": 1700000000}}}}
159
160  An error adds "error": {{"code", "message", "details": {{"count", "codes"}}}}.
161  Success or error, every response carries every finding in full at
162  "data": {{"diagnostics": {{"items": [{{"code", "severity", "message",
163  "location", "help"}}], "pagination": {{"count", "has_more", "next_cursor"}}}}}},
164  empty where it found none.
165
166  Every collection carries that same {{items, pagination}} pair. `count` is the
167  whole reading's, `has_more` says `items` holds less than that, and
168  `next_cursor` is a `--from` value to pass back verbatim for the rest. A framed
169  measurement restarts its state at that instant, so a second page is a second
170  reading rather than a continuation. `--as <name>=<path>` writes the whole
171  reading to a file instead, uncapped. This page is an envelope of its own, at
172  "data": {{"help"}}.
173
174DEFAULTS:
175  --depth <n>          how deep below its target a `ledger` walks.
176                       Default {DEFAULT_LEDGER_DEPTH}.
177  --peaks <n>          peaks a `spectrum` keeps, notes a `pitch`, formants a
178                       `formants`. Default {DEFAULT_MAX_PEAKS}.
179  --oversample <n>     the multiple `alias` re-renders at to hear what folded.
180                       Default {DEFAULT_OVERSAMPLE}.
181  --frame <secs>       the step a framed reading advances by, in seconds.
182                       Default {DEFAULT_FRAME_SECS}, except `spectrum`, which
183                       sizes its own transform to the window unless this flag is
184                       given.
185  --sample-rate <hz>   the observation rate a render lays its seconds on.
186                       Default {DEFAULT_SAMPLE_RATE}.
187  --flop-budget <n>    the operation count paid before a render refuses.
188                       Default {budget}, the `psychoacoustic-v1` profile's own.
189  --format <json|text> how `lint` prints its findings: the envelope, or one
190                       terminal line each, colored where stdout is a terminal.
191                       The same objects either way. Default json.
192  --in <dir>           the composition `render`, `lint` and `trace` read.
193                       Default: the directory the process runs in.
194  --node <path>        the instance a reading is taken of. Defaults to the
195                       target itself; `--as bindings` requires it.
196  --against <file.wav> the second signal `--as masking` reads against. No
197                       default: that one analysis requires it.
198  --from <time>        default 0s; `--to <time>` defaults to the node's extent.
199  --to silent[:bits]   bits default {DEFAULT_SILENT_BITS}, the profile's precision;
200                       `--max <secs>` defaults to {DEFAULT_SILENT_MAX_SECS}.
201  --confirm            replaces a destination that already holds a file. Without
202                       it a path already taken refuses as `conflict` and nothing
203                       is written.
204  --brief, --skim and --pcm16 are all off unless written.
205
206EXIT CODES:
207  0  success (error.code absent)
208  1  internal_error (a destination could not be written)
209  3  validation_error (bad arguments, or a composition that failed to parse)
210  4  conflict (a name `new` would overwrite, or a destination already holding a
211     file, without `--confirm`)
212  24 not_found (a `.wav` file, or a node this composition does not define)
213
214VERB ALIASES:
215  validate = lint, list = builtins, create = new, show = trace, from the `cli`
216  standard's own verb list. `render` and `analyze` take a reading, which that
217  list has no word for, so they keep their own names.
218
219SEE ALSO:
220  sva-cli --version    Show version information"#
221    )
222}