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