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