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::DEFAULT_STORE_BYTES;
4use sva_engine::{
5    DEFAULT_BUFFER_SECS, DEFAULT_CELL_SECS, DEFAULT_FLOOR_DB, DEFAULT_FRAME_SECS,
6    DEFAULT_LEDGER_DEPTH, DEFAULT_MAX_PAIRS, DEFAULT_MAX_PEAKS, DEFAULT_MODULATION_WINDOW_SECS,
7    DEFAULT_NOVELTY_WINDOW_SECS, DEFAULT_OVERSAMPLE, DEFAULT_PERIODICITY_WINDOW_SECS,
8    DEFAULT_SAMPLE_RATE, DEFAULT_SMOOTH_SECS, PSYCHOACOUSTIC_V1, READINGS, Reads,
9    self_similarity::MAX_CELLS,
10};
11
12/// Read off the constants the parser itself defaults to, so a printed default cannot drift
13/// from the one a render actually uses.
14pub fn help_text() -> String {
15    let bits = PSYCHOACOUSTIC_V1.precision_bits;
16    let store_gb = DEFAULT_STORE_BYTES >> 30;
17    let representations = representations();
18    format!(
19        r#"USAGE:
20  sva-cli (render | analyze | lint | trace | builtins | outline | new) [arguments]
21
22DESCRIPTION:
23  A composition is a directory of node files, each one closed-form expression in
24  `t` or `f` on one line, or a score placing notes in time. sva-cli reads that
25  composition and prints what it is and what it sounds like, as JSON on stdout.
26  `render`, `lint` and `trace` read the current directory; `new` writes beside it
27  and `analyze` reads a file.
28
29SCORES:
30  A score is a `.jsonl` file: JSON Lines, one object per line, sorted by `at`;
31  `@score/drop` reads `score/drop.jsonl`, and a ref never writes `.jsonl`. A
32  file with no extension opening with `{{` refuses with the rename. Line 1 is
33  its annotation, the doc comment's four fields, which `lint` holds it to:
34    {{"annotation": "Models: ... | Neglects: ... | IO: (t) -> amplitude | Tags: ..."}}
35  Every other line is one kind, told by its keys, and may add `"comment"`:
36    event    "at", "node", "deg", "len" and the node's parameters: plays it
37    mark     "at", "mark": a point `mark(name)` reads in this file's times
38    comment  "at", "comment": a note to the reader at a time
39    move     "at", "lane", "to", and "node", "len", "shape" where written
40  A score lowers to equations, the sum of its events in written order, each
41  its node shifted to its `at`; marks, comments and moves sound nothing.
42    at     a time string, "0.25b" or "0.5s", or arithmetic over times, "2b +
43           1b/3" for a triplet; a bare number has no unit and refuses. A file
44           counts every time in bars or every one in seconds. "mark(verse) -
45           2b" is a position, "mark(drop) - mark(build)" a duration.
46    node   an `@ref` from the score's own directory: a sound, an equation or
47           another score. On a line placing a score `len` crops it there and
48           nothing else binds; a `node` list mixing scores and sounds refuses
49           `len`.
50    deg    a whole scale degree counted from 1, the tonic: sugar for "f0":
51           "deg(n)". In a seven-row scale 8 is the tonic an octave up, 0 the
52           seventh below and -6 the tonic an octave down. A chromatic note
53           binds "f0": "deg(7)*1st"; a line writing both refuses.
54    len    a time as `at` takes, bound as the node's `len`.
55    other  binds the node's parameter of that name: a number, or a constant in
56           a string, "0.03s", "-6db", "900hz", "A1", "0.8*0.9", "deg(5)/2".
57  Any field but `comment` may be a list. A line listing N items is N events,
58  moves or marks, item i of every list going to the i-th and a single value to
59  all; lists of different lengths refuse. A chord is a `deg` list on one `at`:
60    {{"at": "0b", "node": "@../instruments/keys", "deg": [1, 3, 5],
61      "vel": [0.8, 0.5, 0.6]}}
62  `deg(n)` is the tonic in `variables/key` times degree n's ratio in the scale
63  `variables/scale` names, read where the line sits, in a score or in any
64  equation. A scale is one the language names, as `minor` or `dorian` (`sva-cli
65  builtins` lists each with its cents), or a file of one row per degree in cents
66  above the tonic, ascending, the last the period, named by a ref: a file
67  `scales/custom` holding 200 400 500 700 900 1100 1200 is the major scale. A
68  value reading `t` or a `@ref`, a field the node does not read, a parameter it
69  reads that nothing binds, and a `deg` with no scale refuse before any audio,
70  each naming the score's file, line and column.
71
72  A lane is one value moving through the song: a node's parameter, a move
73  naming "node" and "lane": "cutoff", or a variable under `variables/`, a move
74  naming "lane" alone: "energy", "key", "scale", "bpm" (never "meter"). A move
75  with no `len` sets the lane at `at`; with "len" and "shape", `lin` or `exp`,
76  it ramps there from the value it holds:
77    {{"at": "24b", "node": "@../instruments/saw", "lane": "cutoff",
78      "to": "9khz", "len": "8b", "shape": "exp"}}
79    {{"at": "72b", "lane": "key", "to": "key*2st", "comment": "a tone up"}}
80    {{"at": "92b", "lane": "bpm", "to": "bpm*8/9", "len": "4b", "shape": "lin"}}
81  A `to` naming the lane means its home value, the node's default or the
82  variable's file, so it follows `--set`. A lane reaches every instance of its
83  node, from a score or an equation, inside a wrapper too, unless the line or
84  an invoking equation passes that parameter, which wins. Where it holds one
85  value over an instance's span it binds that number; where it moves there the
86  instance reads it at its song time, and where the engine refuses a moving
87  value, such as `sat`'s `drive`, the move refuses as `bad-lane`: pass a
88  constant, move the lane between notes, or close the sound's tail with a crop.
89  A score's times ride the tempo curve the `bpm` lane draws; `b` in an equation
90  stays a bar of `variables/bpm`. A song places its sections at marks:
91    {{"at": "0b", "mark": "intro"}}
92    {{"at": "mark(intro)", "node": "@intro"}}
93    {{"at": "mark(intro) + 8b", "mark": "verse"}}
94    {{"at": "mark(verse)", "node": "@verse"}}
95  `concat` and `repeat` place parts on the curve too: `repeat(@score/riff-1b, 4)`.
96  A control score is one no sounding score places, read by an equation as a
97  signal: `@score/song(t) * (1 - @score/pump(t))` ducks the song under it.
98  `sva-cli outline @score/song` prints a song's marks, tempo, lanes, events and
99  size. Lint and render warn of a line passing a parameter whose lane ramps
100  under it (`line-overrides-lane`), and of a lane no instance reads
101  (`lane-reaches-nothing`).
102
103RESERVED VARIABLES:
104  Four nodes under `variables/` are reserved: a ref reads one like any node,
105  and a bar or a degree reads one by name:
106    variables/bpm     a number, the beats per minute: 120
107    variables/meter   beats per bar over the note: 4/4
108    variables/key     the tonic `deg` counts from, a note or hertz: C3
109    variables/scale   the scale `deg` counts in, a built-in's name: major, or a
110                      ref to a scale file: @../scales/custom
111  A `b` literal or a file's `-2b` span reads bpm and meter; a `deg(n)` reads key
112  and scale. Before anything renders, each read is replaced by its value, by one
113  pass for all four: a bar becomes `n*(beats*60/bpm)` seconds and `deg(n)`
114  becomes `@variables/key` times degree n's ratio, the tonic still a ref. A
115  read with no value to replace it refuses as `bad-tempo` or `bad-scale`, as
116  does a variable holding no such value, such as bpm without meter, or a
117  scale name no built-in has, which refuses listing every built-in. A stream's
118  edit to a variable, or to the scale file it names, replaces every read of it
119  again. A node elsewhere named `bpm` is an ordinary node. Any other file under
120  `variables/`, as `variables/energy` holding 0.5, is a variable of the
121  composition's own: a ref reads it, `--set` sets it, and a score's moves move
122  it, as they move the reserved ones but `meter` (SCORES).
123
124RENDER:
125  sva-cli render '<expression>' --representation <list> [--until '<condition>']
126                 [--bits <n>] [--rate <hz>] [--threads <n>] [--cache <path|none>]
127                 [--set <name>=<text>]... [--confirm]
128
129  Renders one expression, in the grammar a node file's body uses, and prints one
130  reading per representation under `data.representations`. `@path` reads a node
131  from the current directory and `@/abs/path` one anywhere; there is no default
132  target. Every reading states its `source` (exact or measured), the
133  conformance profile it ran under, and its rate.
134
135  The target's own ref may read an interval: `@piano4([0, 2b], f0=C4,
136  vel=0.5, release=1s)`, where `release` is the instrument's own parameter. Its ends are the language's literals (`s`, `ms`, `b`,
137  `sp`, or a bare `0`); `[a, inf)` and `[a,)` leave the end open; the other
138  arguments are the node's own named ones. The start trims the output alone:
139  history before it is still computed, so a loop or a filter carries the state
140  it had there. With no interval the render starts at 0, earlier only where a
141  crop reaches before it, and its end is open. Inside an expression a window is
142  a `crop`. A target that samples its one ref read, `sample(@piano4([0, 2b],
143  f0=C4))`, reads that ref's interval and binds and the same node values.
144
145  Every node is computed over its support met with what reads it, and nowhere
146  else: outside its support a node is exactly zero. A closed interval renders
147  exactly its length, nothing cut. An open one ends where the root's support
148  does: a crop's end, the exact underflow of an `exp`, a clamp holding a factor
149  at zero; or earlier, where a proven upper bound on the root's magnitude stays
150  under the profile's silence threshold (-120 dBFS) from a sample on. A root
151  with no such bound (a held sine, a loop) is never cut. The label's `pruned`
152  states the threshold as `db` and the root's cut, `from` the sample the render
153  is treated as silent from. A stream's term leaves `@notes` where its support
154  ends. An open interval over a root whose support never ends and is never cut
155  (a held sine, a physical solver) refuses as `render.no_end`. A short-time
156  transform reads its input whole, and refuses one with no end as
157  `engine.unbounded_extent`. A form in `f` with no dual in `t` has no samples
158  and refuses as its dual does, `cast.left_algebra`.
159
160  A render is a stream pulled to its end: a closed form, a short-time
161  transform and what either reads are computed over their whole extent first,
162  and every other node block by block. `--until '<condition>'` stops the render
163  at the first sample the condition holds at, or at the interval's end,
164  whichever is first. It is checked as each block is pulled, so no node driven
165  block by block runs past the block it holds in. A condition compares (`<`,
166  `<=`, `>`, `>=`) `t`, `envelope(t)` (the RMS over every channel of the
167  `envelope` representation's frame holding `t`, framed by its own `frame`
168  where one is asked) and literals, joined by `and`/`or`.
169
170  `--representation <list>` takes a comma list of readings and may repeat. Each
171  is a call in the language's own syntax, its options its named arguments:
172  `spectrum(peaks=8, frame=50ms)`, `ledger(depth=3, brief=1)`. An entry
173  followed by `=<path>` writes a file instead, listed under `written`:
174  `samples=<path>.wav` writes audio, encoded as `--bits` says; any other path
175  takes the reading's JSON, uncapped. A path that already holds a file refuses
176  unless `--confirm` is written. `samples=alsa:<device>` plays the samples on
177  that ALSA device (`alsa:default`, `alsa:hw:1`, `alsa:plughw:1,0`) as each
178  block is pulled, in real time, through `aplay` from alsa-utils; Linux only,
179  anywhere else it refuses as `destination.unsupported_platform`. It sends
180  32-bit floats, or integer PCM where `--bits` is 16 or under. A device that
181  cannot take the format refuses with aplay's reason, and one aplay says plays
182  another rate than the render's stops there as `destination.rate_inexact`.
183  `samples(buffer=<t>)` is how much the device holds ahead of what it plays.
184  Once it has played out, `data.played` states the device, its seconds, rate,
185  channels, format, the buffer asked and the one granted, and every underrun
186  aplay reported, with what it cannot see.
187  `lines`, `atoms`, `derivative`, `bindings`, `arguments`, and a closed form's
188  `spectrum`, `envelope` and `pitch`, read the expression and ignore the range.
189
190  A closed form's `spectrum` and `pitch` are its exact lines, so a term under a
191  crop or an envelope, which has a width and is no line, refuses them; so do
192  `spectrum(frame=)` and `envelope(frame=)`, as a closed form has no frames. To
193  measure any of them frame by frame, a reader samples the target's ref,
194  `sample(@x([0, 1s]))`, and an author writes `sample(...)` inside the node.
195  A closed form with no exact envelope, such as a crop of a decay, refuses
196  `envelope` with the reason rather than measuring it unasked; `sample(...)`
197  asks for the measured one. A measured `spectrum` is one spectrum: every
198  `frame`-long window across the range, averaged. `pitch` is framed, one entry
199  per `frame`; for the spectrum at one instant, read a range one frame long.
200  A measured `envelope` is framed too, and reads every channel: a frame's
201  `rms` is the root mean square of all its channels' samples together, its
202  `peak` the largest magnitude in any channel.
203
204  `--bits <n>` is the precision every sample is written to, from 2 to 52: the
205  point where a series is truncated, and the encoding of a `.wav`, integer PCM
206  at n bits up to 16 and 32-bit float above. `--rate <hz>` (default
207  {DEFAULT_SAMPLE_RATE}) is the one rate a render samples at: the target is read
208  at its instants, and every node at the instants its reader asks for. A
209  closed form is exact at any instant, and so is a continuous loop, one
210  constant delay at a gain under 1 over a closed form such as
211  `x + 0.5*self(t - 17ms)`, which is its series. A filter, a discrete loop, a
212  solver and `rand` step at the rate asked for, where `1sp` is one step, so an
213  `sp` count or `self[idx(t) - 1]` means one sample at whatever rate is asked.
214  `rand` draws once per step, keyed by that step's index; a key between steps
215  reads the step nearest it, ties to even.
216
217  `--set <name>=<text>` renders as if the variable `variables/<name>` held
218  `<text>`, and writes no file: `--set key=G3` transposes every degree to G3,
219  `--set scale=dorian` changes mode (a scale file is a ref written from
220  `variables/`, as the file holds it: `--set scale=@../scales/custom`), `--set
221  bpm=140` moves every bar, `--set meter=3/4` regroups them and `--set
222  energy=0.8` sets a variable of the composition's own. It sets a lane's home
223  value: a move whose `to` names it follows. It may repeat, once per variable: a
224  variable set twice refuses as `render.set_twice`, a name with no file under
225  `variables/` that is no reserved one as `render.set_unreserved`, listing the
226  variables there, and the file's path, `variables/key=G3`, as
227  `render.set_path`, naming the short form.
228
229  `--threads <n>` (default: every core the machine reports) is the most
230  threads a render computes on; `1` computes every value in turn on one.
231  Values none of the others reads, and the blocks of a value that holds no
232  state, compute apart, to the same bits on any number of threads.
233
234  A discrete loop reads its own past only by index; `self(t - d)` in one
235  refuses as `type.discrete_self_at_time`, naming what made it discrete. A
236  read `@x(k*t - d)` steps `x` at `k` times the step, every input it reads,
237  `sp` and `idx` with it, and rounds `d` to the nearest sample of that step,
238  ties to even: shifts are rounded to the nearest sample so placements share
239  one cached value; timing is exact to half a sample, and the label's
240  `moved_s` states the most any read moved. A node is computed once
241  per step however many reads ask for it. A node that holds state read at a
242  time that moves refuses as `type.stateful_warp`, naming what
243  holds its state; `@x[idx(...)]` reads its nearest step instead, as `p[idx(...)]`
244  does a signal passed in as parameter `p`, and any `idx`, as
245  `idx(t - 5ms - 2ms*sin(2*pi*t))`, is read sample by sample. An edit to a
246  stream plays from the next sample on.
247
248  A render keeps in a store on disk its target and, under it, each node of
249  samples that holds state (a filter, a discrete loop or a solver in it) or
250  that two or more nodes read. Nothing else is kept. A closed form, `sample`
251  of one and `rand` have a value at any instant and are computed where read.
252  A node without state that one node alone reads, however many times, is
253  computed again by that reader from what the node itself reads: no other
254  reader could ask for it, and without state it restarts at any sample, where
255  a filter or a loop must run from its start. So a stage worth keeping is a
256  node of its own that ends in a filter or a loop, or one two nodes read. A
257  kept value is named by what it computes: never its file's name, directory,
258  comments, spacing, or the order its named arguments are written in, so a
259  renamed or moved copy, a constant lifted into a default, an expression split
260  into a file of its own or written back inline, and two addends swapped all
261  read one entry. The next render of anything that reads the same value reads
262  it back, bit for bit, and computes nothing under it. The store is on by
263  default, at `$XDG_CACHE_HOME/sva`, else `~/.cache/sva`; `--cache <path>`
264  moves it (a `/dev/shm` path keeps it in memory) and `--cache none` turns it
265  off. It holds at most {store_gb} GB, the least recently used values going
266  first, and a store another build of sva-cli wrote is emptied when opened. A
267  render stages what it computes beside the store as it goes, and the store
268  itself is written once, when the render ends, fails, or is stopped by SIGINT
269  or SIGTERM.
270
271  `ledger` prints one row per node under the target. A row's `share` is the
272  part of its reader's own energy that row accounts for, so one reader's refs
273  sum to 1; a ref no addend isolates, such as one factor of a product, prints
274  `null`. Each ref carrying a share is collapsed once on its own, so a ledger
275  costs one collapse per attributed ref beyond the render, and `depth` bounds
276  how many, counted in refs from one file to another. Every row is computed
277  whole over the range, however late a reader reads it. `brief=1` keeps only
278  the rows that clipped, `skim=1` drops the wider fields.
279
280  `arguments` renders nothing: for every instance under the target it prints
281  each builtin call's named arguments as the numbers the call was lowered
282  with, a solver's whole parameter set with `written: false` on each default
283  it filled in, and the operand each `min`/`max` `chosen` where a call folds
284  one to a number: in named arguments and a solver's, modal bank's or
285  `noise`'s positionals. One inside a filter's or cast's positional or
286  `rand`'s key or seed is not listed. `at` spans are bytes of the instance's
287  own body, as `sva-cli outline` counts them.
288
289REPRESENTATIONS:
290  Each one a `--representation` call, its arguments in parentheses, every
291  default under DEFAULTS. `exact` is read off the written math, `measured` off
292  samples.
293
294{representations}
295
296ANALYZE:
297  sva-cli analyze <file.wav> --representation <list> [--confirm]
298
299  Runs the same readings over a whole external `.wav` at its own rate, never
300  resampled. Only the readings a buffer answers alone apply, each listed under
301  REPRESENTATIONS as `measured` or `exact off a closed form, measured off
302  samples`; the rest need the graph behind it.
303
304LINT:
305  sva-cli lint ['<expression>'] [--format <json|text>]
306
307  Checks the current directory without rendering a sample. With no target it
308  checks every file under its own rules, and types every entry point, which
309  types every file it reaches. With a target, in render's grammar, it checks
310  the files that target reaches, and prints the interval a render of it reads.
311  Either refuses a type error as `render` does.
312
313  A score's annotation is its doc comment, under the same codes, and its lines
314  no body, so the length checks pass it by; a file of two or more lines that is
315  neither a score nor a scale refuses as `multiline-body` before any check
316  runs. Each song lint types prints its warnings beside the findings.
317
318  Every check prints one `data.diagnostics` item. `warning` exits 0, `error`
319  exits non-zero, so branch on the verdict and never on whether the array is
320  empty. No flag downgrades an error.
321
322  error    missing-comment       no `;` comment line
323           multiline-comment     more than one
324           malformed-comment     not four ` | ` fields, `Models:` `Neglects:`
325                                 `IO: <in> -> <out>` `Tags: <tag>[, <tag>...]`
326           long-comment-block    a `;` block over 1000 characters, the line-1
327                                 doc comment's own run exempted
328           long-expression-body  a body over 10000 characters, a backstop rather
329                                 than a complexity budget
330           literal-sample-rate   a written rate where `sp` belongs
331           arity                 a builtin called without an argument it reads,
332                                 or with one it does not, as `rand(seed=k)`
333  warning  key-is-not-a-pitch    `variables/key` holds neither a note name nor
334                                 a number of hertz
335           parameter-has-no-default
336                                 a node reads a parameter no `name = value` line
337                                 binds, so a bare `@node` cannot play
338           support-never-ends    a node's support, cut where its bound falls
339                                 under the silence threshold, has no end for a
340                                 bare render to stop at
341
342TRACE:
343  sva-cli trace <node|expression>
344
345  Prints one node's position without rendering audio: what it reads (`down`, one
346  hop), everything that reads it (`up`, transitively to an entry point), each
347  beside the expression doing the reading, the node that made it discrete, and
348  the feedback loop it sits in, if any. A score is shown as its line count, its
349  first and last `at` and the nodes it plays, never the sum it lowers to; every
350  entry point's song is lowered first, each at song time 0. An interval the
351  target reads is ignored.
352
353BUILTINS:
354  sva-cli builtins
355
356  Prints the whole callable and syntactic vocabulary: every builtin with its
357  arity and named arguments, each argument's `meaning`, `unit`, the model
358  `part` it sets and whether it `moves` with `t` (a filter's cutoff, q and gain;
359  every other named argument is one number, refused when it names none), each
360  positional's meaning where the model states one, unit suffixes, the note-name
361  grammar, the scales `variables/scale` may name with their cents, reserved
362  identifiers, special call shapes, and what has no operator at all.
363
364OUTLINE:
365  sva-cli outline <expression>
366
367  Prints the parse tree the engine builds from one expression, each node with
368  the byte `span` it was written in: calls by `name` with positional and named
369  `args`, operators by `op`, refs by `path` with their `binds`, refs and `self`
370  by how they `read`, `time` or `index`, literals by `value` and `unit`, names
371  by `name`. A node the parser supplies itself, the `0` of a prefix minus or the
372  `t` of a bare `@ref`, has `written: false`. Reads no composition. Score text,
373  JSON opening with `{{`, prints `score` instead of `outline`: one item per item
374  a line lists, with its `kind`, `line`, `span`, `at` with marks resolved, and
375  its `node`, `deg`, `args`, `mark` or `lane`, `to`, `len` and `shape`.
376
377  sva-cli outline @score/song
378
379  Run in a composition, outlines that score as the song a render of it is:
380  `marks` in bars and seconds, the `tempo` curve's pieces, each lane with its
381  home and every move resolved with its placement chain, how many instances it
382  reaches and how many see it move, each placed event's position, node, pitch
383  in hertz and where each parameter comes from (`line`, `lane` or `default`),
384  and the `size`: placed events and the engine's instances beside its limit.
385
386NEW:
387  sva-cli new <name> [--idempotency-key <key>]
388
389  Writes a starter composition at ./<name>, and refuses if that directory
390  exists. `--idempotency-key <key>` records the key beside the composition, so a
391  retry under the same key succeeds identically while the tree still holds what
392  was written. Any other key, or an edited tree, refuses.
393
394EXAMPLES:
395  sva-cli new song1 && cd song1
396  sva-cli render '@master' --representation samples=/tmp/song1.wav
397  sva-cli render '@master([0, 8b])' --representation 'ledger(depth=2),loudness'
398  sva-cli render '@voice/note([0, inf), f0=C4, len=2s)' \
399    --until 'envelope(t) < -60db and t > 1s' --representation samples=/tmp/note.wav
400  sva-cli render 'sample(@chord/home)' --representation 'spectrum(peaks=8)' --rate 48000
401  sva-cli render 'sample(@voice/note([0, 2s], f0=C4))' --representation pitch
402  sva-cli render '@master' --set key=G3 --set bpm=140 --set scale=dorian \
403    --representation samples=/tmp/song1-g.wav
404  sva-cli render '@master' \
405    --representation 'samples(buffer=100ms)=alsa:default,loudness'
406  sva-cli lint
407  sva-cli lint '@master'
408  sva-cli trace score/phrase
409  sva-cli outline @score/phrase
410  sva-cli builtins
411
412OUTPUT:
413  {{"status": "success", "data": {{"target": "@master([0, 8b])", "sample_rate":
414  {DEFAULT_SAMPLE_RATE}, "bits": {bits}, "profile": "psychoacoustic-v1",
415  "interval": {{"start_secs":
416  0, "end_secs": 16}}, "label": {{...}}, "written": {{"items": [...]}},
417  "representations": {{"ledger": {{...}}}}, "diagnostics": {{"items": []}}}},
418  "meta": {{"request_id": "req_...", "timestamp": 1700000000}}}}
419
420  `interval` is null where no reading read samples. An error adds "error":
421  {{"code", "message", "details": {{"count", "codes"}}}}. Success or error,
422  every response carries every finding in full at "data": {{"diagnostics":
423  {{"items": [{{"code", "severity", "message", "location", "help"}}],
424  "pagination": {{"count", "has_more", "next_cursor"}}}}}}, empty where it found
425  none.
426
427  Every collection carries that same {{items, pagination}} pair. `count` is the
428  whole reading's, `has_more` says `items` holds less than that, and
429  `next_cursor` is the interval start, as `0.5s`, the rest is read from. A
430  reading written to a file is written whole, uncapped. This page is an
431  envelope of its own, at "data": {{"help"}}.
432
433DEFAULTS:
434  --rate <hz>          the one rate a render samples at, and `1sp`.
435                       Default {DEFAULT_SAMPLE_RATE}.
436  --bits <n>           the precision every sample is written to. Default {bits},
437                       the `psychoacoustic-v1` profile's own.
438  ledger(depth=<n>)    how deep below its target a `ledger` walks.
439                       Default {DEFAULT_LEDGER_DEPTH}.
440  (peaks=<n>)          peaks a `spectrum` keeps, notes a `pitch`, formants a
441                       `formants`, stripes and novelty peaks a `self_similarity`
442                       lists per view. Default {DEFAULT_MAX_PEAKS}.
443  alias(oversample=<n>) the multiple `alias` re-renders at to hear what folded.
444                       Default {DEFAULT_OVERSAMPLE}.
445  (frame=<t>)          the step a framed reading advances by, in seconds.
446                       Default {DEFAULT_FRAME_SECS}, except `spectrum`, which sizes
447                       its own transform to the range unless it is set.
448  (floor=<db>)         the amplitude, as `-40db`, under which a harmonic reading
449                       or `self_similarity` drops a partial, and under which
450                       `self_similarity` counts a band level as zero.
451                       Default {DEFAULT_FLOOR_DB}db.
452  voice_leading(norm=<l1|l2>) how a voice leading's moves add up. Default l1.
453  dissonance(pairs=<n>) the most partial pairs one frame sums before it refuses.
454                       Default {DEFAULT_MAX_PAIRS}.
455  periodicity(window=<t>) the span of onset strength one window autocorrelates,
456                       in seconds, at least twice the longest period it reads.
457                       Default {DEFAULT_PERIODICITY_WINDOW_SECS}.
458  periodicity(curve=<0|1>) `1` adds every window's height at every lag.
459                       Default 0.
460  modulation(window=<t>) the span of band envelope one window transforms, in
461                       seconds, at least twice the slowest period it reads.
462                       Default {DEFAULT_MODULATION_WINDOW_SECS}.
463  modulation(bpm=<n>)  a tempo, as `140`, adding each rate's period in beats.
464                       Default none.
465  self_similarity(cell=<t>) the side of one matrix cell, in seconds, a whole
466                       number of frames; the target may span at most
467                       {MAX_CELLS} cells. Default {DEFAULT_CELL_SECS}.
468  self_similarity(smooth=<t>) the length each diagonal is averaged over
469                       before stripes are picked, in seconds.
470                       Default {DEFAULT_SMOOTH_SECS}.
471  self_similarity(window=<t>) the whole width of the novelty kernel, in
472                       seconds. Default {DEFAULT_NOVELTY_WINDOW_SECS}.
473  self_similarity(curve=<0|1>) `1` adds every cell's chroma and band levels and
474                       each view's matrix. Default 0.
475  bindings(node=@<path>) the instance whose bindings are read; required.
476  samples(buffer=<t>)  how much an `alsa:` device holds ahead of what it plays,
477                       in seconds. Default {DEFAULT_BUFFER_SECS}; only a device
478                       has one.
479  ledger(brief, skim)  `0` unless set `1`.
480  --format <json|text> how `lint` prints its findings: the envelope, or one
481                       terminal line each, colored where stdout is a terminal.
482                       The same objects either way. Default json.
483  --cache <path|none>  where the store lives. Default `$XDG_CACHE_HOME/sva`, else
484                       `~/.cache/sva`, holding at most {store_gb} GB; `none` keeps
485                       no store. A store that fails fails no render: it answers
486                       as without one, warned why in `diagnostics`.
487  --set <name>=<text>  a variable read as `<text>` for this render.
488                       Default none: each is read off its file.
489  --confirm            replaces a destination that already holds a file. Without
490                       it a path already taken refuses as `conflict` and nothing
491                       is written.
492
493ENVIRONMENT:
494  SVA_LOG=debug        `render` logs, once it ends, what it asked of each value
495                       to stderr; stdout and the samples are unchanged. Any
496                       other value, or none, logs nothing. One line per second
497                       of output, then one per node and a total:
498                         sva-cache pass hit=0 miss=190 ... reused=0 cum-hit=0.0%
499                         sva-cache t=1.022s hit=1 miss=2 ... cum-hit=0.5%
500                         sva-cache node hit=1 miss=0 ... reused=3 <node>
501                         sva-cache total hit=1 miss=198 ... hit-rate=0.5% ...
502                       `pass` is what was looked up before the first block.
503                       Each value is looked up once: a `hit` was answered by
504                       the store, `prefix` found a stored run up to a switch,
505                       `miss` computed it, `new` held it in memory, where the
506                       store takes what it keeps from. Each read of a value
507                       past its first is `reused`.
508
509EXIT CODES:
510  0  success (error.code absent)
511  1  internal_error (a destination could not be written)
512  3  validation_error (bad arguments, a composition that failed to parse, or a
513     render the engine refused)
514  4  conflict (a name `new` would overwrite, or a destination already holding a
515     file, without `--confirm`)
516  24 not_found (a `.wav` file, or a node this composition does not define)
517
518VERB ALIASES:
519  validate = lint, list = builtins, create = new, show = trace, from the `cli`
520  standard's own verb list. `render` and `analyze` take a reading, which that
521  list has no word for, so they keep their own names.
522
523SEE ALSO:
524  sva-cli --version    Show version information"#
525    )
526}
527
528/// One entry per reading, off the one table: its call, what it answers off, and its line.
529fn representations() -> String {
530    READINGS
531        .iter()
532        .map(|row| {
533            let keys: Vec<&str> = row.takes.iter().map(|arg| arg.key()).collect();
534            let call = match keys[..] {
535                [] => row.name.to_string(),
536                _ => format!("{}({})", row.name, keys.join(", ")),
537            };
538            let source = match row.reads {
539                Reads::ClosedForm | Reads::Structure => "exact",
540                Reads::Either => "exact off a closed form, measured off samples",
541                Reads::Samples => "measured",
542                Reads::Graph => "measured, and the graph behind the samples",
543            };
544            format!("  {call:<27}{source}\n      {}", row.help)
545        })
546        .collect::<Vec<_>>()
547        .join("\n")
548}