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