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, spectrum,
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 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, and
300 every row under a silent reader, which has no energy to share out, print
301 `null`. A ref carrying a share reads as the addend it gave its reader, its
302 `rms`, `peak` and `clipped` too: in `2*@a`, `a`'s row is `a` doubled, and in
303 `@a - @a` it is silence. Each ref carrying a share is collapsed once on its
304 own, so a ledger costs one collapse per attributed ref beyond the render,
305 and `depth` bounds how many, counted in refs from one file to another. Every
306 row is computed whole over the range, however late a reader reads it.
307 `brief=1` keeps only the rows that clipped, `skim=1` drops the wider fields.
308
309 `arguments` renders nothing: for every instance under the target it prints
310 each builtin call's named arguments as the numbers the call was lowered
311 with, a solver's whole parameter set with `written: false` on each default
312 it filled in, and the operand each `min`/`max` `chosen` where a call folds
313 one to a number: in named arguments and a solver's, modal bank's or
314 `noise`'s positionals. One inside a filter's or cast's positional or
315 `rand`'s key or seed is not listed. `at` spans are bytes of the instance's
316 own body, as `sva-cli outline` counts them.
317
318REPRESENTATIONS:
319 Each one a `--representation` call, its arguments in parentheses, every
320 default under DEFAULTS. `exact` is read off the written math, `measured` off
321 samples.
322
323{representations}
324
325ANALYZE:
326 sva-cli analyze <file.wav> --representation <list> [--confirm]
327
328 Runs the same readings over a whole external `.wav` at its own rate, never
329 resampled. Only the readings a buffer answers alone apply, each listed under
330 REPRESENTATIONS as `measured` or `exact off a closed form, measured off
331 samples`; the rest need the graph behind it.
332
333LINT:
334 sva-cli lint ['<expression>'] [--format <json|text>]
335
336 Checks the current directory without rendering a sample. With no target it
337 checks every file under its own rules, and types every entry point, which
338 types every file it reaches. With a target, in render's grammar, it checks
339 the files that target reaches, and prints the interval a render of it reads.
340 Either refuses a type error as `render` does.
341
342 A score's annotation is its doc comment, under the same codes, and its lines
343 no body, so the length checks pass it by; a file of two or more lines that is
344 neither a score nor a scale refuses as `multiline-body` before any check
345 runs. Each song lint types prints its warnings beside the findings.
346
347 Every check prints one `data.diagnostics` item. `warning` exits 0, `error`
348 exits non-zero, so branch on the verdict and never on whether the array is
349 empty. No flag downgrades an error.
350
351 error missing-comment no `;` comment line
352 multiline-comment more than one
353 malformed-comment not four ` | ` fields, `Models:` `Neglects:`
354 `IO: <in> -> <out>` `Tags: <tag>[, <tag>...]`
355 long-comment-block a `;` block over 1000 characters, the line-1
356 doc comment's own run exempted
357 long-expression-body a body over 10000 characters, a backstop rather
358 than a complexity budget
359 literal-sample-rate a written rate where `sp` belongs
360 arity a builtin called without an argument it reads,
361 or with one it does not, as `rand(seed=k)`
362 warning key-is-not-a-pitch `variables/key` holds neither a note name nor
363 a number of hertz
364 parameter-has-no-default
365 a node reads a parameter no `name = value` line
366 binds, so a bare `@node` cannot play
367 support-never-ends a node's support, cut where its bound falls
368 under the silence threshold, has no end for a
369 bare render to stop at
370
371TRACE:
372 sva-cli trace <node|expression>
373
374 Prints one node's position without rendering audio: what it reads (`down`, one
375 hop), everything that reads it (`up`, transitively to an entry point), each
376 beside the expression doing the reading, the node that made it discrete, and
377 the feedback loop it sits in, if any. A score is shown as its line count, its
378 first and last `at` and the nodes it plays, never the sum it lowers to; every
379 entry point's song is lowered first, each at song time 0. An interval the
380 target reads is ignored.
381
382BUILTINS:
383 sva-cli builtins
384
385 Prints the whole callable and syntactic vocabulary: every builtin with its
386 arity and named arguments, each argument's `meaning`, `unit`, the model
387 `part` it sets and whether it `moves` with `t` (a filter's cutoff, q and gain;
388 every other named argument is one number, refused when it names none), each
389 positional's meaning where the model states one, unit suffixes, the note-name
390 grammar, the scales `variables/scale` may name with their cents, reserved
391 identifiers, special call shapes, and what has no operator at all.
392
393OUTLINE:
394 sva-cli outline <expression>
395
396 Prints the parse tree the engine builds from one expression, each node with
397 the byte `span` it was written in: calls by `name` with positional and named
398 `args`, operators by `op`, refs by `path` with their `binds`, refs and `self`
399 by how they `read`, `time` or `index`, literals by `value` and `unit`, names
400 by `name`. A node the parser supplies itself, the `0` of a prefix minus or the
401 `t` of a bare `@ref`, has `written: false`. Reads no composition. Score text,
402 JSON opening with `{{`, prints `score` instead of `outline`: one item per item
403 a line lists, with its `kind`, `line`, `span`, `at` with marks resolved, and
404 its `node`, `deg`, `args`, `mark` or `lane`, `to`, `len` and `shape`.
405
406 sva-cli outline @score/song
407
408 Run in a composition, outlines that score as the song a render of it is:
409 `marks` in bars and seconds, the `tempo` curve's pieces, each lane with its
410 home and every move resolved with its placement chain, how many instances it
411 reaches and how many see it move, and each placed event once, however many
412 reads reach it: its song time `at` (where it reads lanes, key and scale), when
413 it first `sounds` (later by the least, over the reads reaching it, of the
414 delays of the `t - d` reads of a score above it), its `len` in song time where
415 its line writes one, node, pitch in hertz and where each parameter comes from
416 (`line`, `lane` or `default`), and the `size`: placed events and the engine's
417 instances beside its limit.
418
419NEW:
420 sva-cli new <name> [--idempotency-key <key>]
421
422 Writes a starter composition at ./<name>, and refuses if that directory
423 exists. `--idempotency-key <key>` records the key beside the composition, so a
424 retry under the same key succeeds identically while the tree still holds what
425 was written. Any other key, or an edited tree, refuses.
426
427EXAMPLES:
428 sva-cli new song1 && cd song1
429 sva-cli render '@master' --representation samples=/tmp/song1.wav
430 sva-cli render '@master([0, 8b])' --representation 'ledger(depth=2),loudness'
431 sva-cli render '@voice/note([0, inf), f0=C4, len=2s)' \
432 --until 'envelope(t) < -60db and t > 1s' --representation samples=/tmp/note.wav
433 sva-cli render 'sample(@chord/home)' --representation 'spectrum(peaks=8)' --rate 48000
434 sva-cli render 'sample(@voice/note([0, 2s], f0=C4))' --representation pitch
435 sva-cli render '@master' --set key=G3 --set bpm=140 --set scale=dorian \
436 --representation samples=/tmp/song1-g.wav
437 sva-cli render '@master' \
438 --representation 'samples(buffer=100ms)=alsa:default,loudness'
439 sva-cli lint
440 sva-cli lint '@master'
441 sva-cli trace score/phrase
442 sva-cli outline @score/phrase
443 sva-cli builtins
444
445OUTPUT:
446 {{"status": "success", "data": {{"target": "@master([0, 8b])", "sample_rate":
447 {DEFAULT_SAMPLE_RATE}, "bits": {bits}, "profile": "psychoacoustic-v1",
448 "interval": {{"start_secs":
449 0, "end_secs": 16}}, "label": {{...}}, "written": {{"items": [...]}},
450 "representations": {{"ledger": {{...}}}}, "diagnostics": {{"items": []}}}},
451 "meta": {{"request_id": "req_...", "timestamp": 1700000000}}}}
452
453 `interval` is null where no reading read samples. An error adds "error":
454 {{"code", "message", "details": {{"count", "codes"}}}}. Success or error,
455 every response carries every finding in full at "data": {{"diagnostics":
456 {{"items": [{{"code", "severity", "message", "location", "help"}}],
457 "pagination": {{"count", "has_more", "next_cursor"}}}}}}, empty where it found
458 none.
459
460 Every collection carries that same {{items, pagination}} pair. `count` is the
461 whole reading's, `has_more` says `items` holds less than that, and
462 `next_cursor` is the interval start, as `0.5s`, the rest is read from. A
463 reading written to a file is written whole, uncapped. This page is an
464 envelope of its own, at "data": {{"help"}}.
465
466DEFAULTS:
467 --rate <hz> the one rate a render samples at, and `1sp`.
468 Default {DEFAULT_SAMPLE_RATE}.
469 --bits <n> the precision every sample is written to. Default {bits},
470 the `psychoacoustic-v1` profile's own.
471 ledger(depth=<n>) how deep below its target a `ledger` walks.
472 Default {DEFAULT_LEDGER_DEPTH}.
473 (peaks=<n>) peaks a `spectrum` keeps, notes a `pitch`, formants a
474 `formants`, stripes and novelty peaks a `self_similarity`
475 lists per view. Default {DEFAULT_MAX_PEAKS}.
476 alias(oversample=<n>) the multiple `alias` re-renders at to hear what folded.
477 Default {DEFAULT_OVERSAMPLE}.
478 (frame=<t>) the step a framed reading advances by, in seconds.
479 Default {DEFAULT_FRAME_SECS}, except `spectrum`, which sizes
480 its own transform to the range unless it is set.
481 (floor=<db>) the amplitude, as `-40db`, under which a harmonic reading
482 or `self_similarity` drops a partial, and under which
483 `self_similarity` counts a band level as zero.
484 Default {DEFAULT_FLOOR_DB}db.
485 voice_leading(norm=<l1|l2>) how a voice leading's moves add up. Default l1.
486 dissonance(pairs=<n>) the most partial pairs one frame sums before it refuses.
487 Default {DEFAULT_MAX_PAIRS}.
488 periodicity(window=<t>) the span of onset strength one window autocorrelates,
489 in seconds, at least twice the shortest period it reads;
490 it reads periods up to half its span.
491 Default {DEFAULT_PERIODICITY_WINDOW_SECS}.
492 periodicity(curve=<0|1>) `1` adds every window's height at every lag.
493 Default 0.
494 modulation(window=<t>) the span of band envelope one window transforms, in
495 seconds, at least twice the fastest rate's period; it
496 reads rates down to two periods per span.
497 Default {DEFAULT_MODULATION_WINDOW_SECS}.
498 modulation(bpm=<n>) a tempo, as `140`, adding each rate's period in beats.
499 Default none.
500 self_similarity(cell=<t>) the side of one matrix cell, in seconds, a whole
501 number of frames holding a sample of the band
502 envelopes; the target may span at most
503 {MAX_CELLS} cells. Default {DEFAULT_CELL_SECS}.
504 self_similarity(smooth=<t>) the length each diagonal is averaged over
505 before stripes are picked, in seconds.
506 Default {DEFAULT_SMOOTH_SECS}.
507 self_similarity(window=<t>) the whole width of the novelty kernel, in
508 seconds, at most {MAX_CELLS} cells a side.
509 Default {DEFAULT_NOVELTY_WINDOW_SECS}.
510 self_similarity(curve=<0|1>) `1` adds every cell's chroma and band levels and
511 each view's matrix. Default 0.
512 bindings(node=@<path>) the instance whose bindings are read; required. An
513 instance is named exactly as the render spells it; any
514 other spelling refuses, listing its file's instances.
515 samples(buffer=<t>) how much an `alsa:` device holds ahead of what it plays,
516 in seconds. Default {DEFAULT_BUFFER_SECS}; only a device
517 has one.
518 ledger(brief, skim) `0` unless set `1`.
519 --format <json|text> how `lint` prints its findings: the envelope, or one
520 terminal line each, colored where stdout is a terminal.
521 The same objects either way. Default json.
522 --cache <path|none> where the store lives. Default `$XDG_CACHE_HOME/sva`, else
523 `~/.cache/sva`, holding at most {store_gb} GB; `none` keeps
524 no store. A store that fails fails no render: it answers
525 as without one, warned why in `diagnostics`.
526 --set <name>=<text> a variable read as `<text>` for this render.
527 Default none: each is read off its file.
528 --confirm replaces a destination that already holds a file. Without
529 it a path already taken refuses as `conflict` and nothing
530 is written.
531
532ENVIRONMENT:
533 SVA_LOG=debug `render` logs, once it ends, what it asked of each value
534 to stderr; stdout and the samples are unchanged. Any
535 other value, or none, logs nothing. One line per second
536 of output, then one per node and a total:
537 sva-cache pass hit=0 miss=190 ... reused=0 cum-hit=0.0%
538 sva-cache t=1.022s hit=1 miss=2 ... cum-hit=0.5%
539 sva-cache node hit=1 miss=0 ... reused=3 <node>
540 sva-cache total hit=1 miss=198 ... hit-rate=0.5% ...
541 `pass` is what was looked up before the first block.
542 Each value is looked up once: a `hit` was answered by
543 the store, `prefix` found a stored run up to a switch,
544 `miss` computed it, `new` held it in memory, where the
545 store takes what it keeps from. Each read of a value
546 past its first is `reused`.
547
548EXIT CODES:
549 0 success (error.code absent)
550 1 internal_error (a destination could not be written)
551 3 validation_error (bad arguments, a composition that failed to parse, or a
552 render the engine refused)
553 4 conflict (a name `new` would overwrite, or a destination already holding a
554 file, without `--confirm`)
555 24 not_found (a `.wav` file, or a node this composition does not define)
556
557VERB ALIASES:
558 validate = lint, list = builtins, create = new, show = trace, from the `cli`
559 standard's own verb list. `render` and `analyze` take a reading, which that
560 list has no word for, so they keep their own names.
561
562SEE ALSO:
563 sva-cli --version Show version information"#
564 )
565}
566
567fn representations() -> String {
569 READINGS
570 .iter()
571 .map(|row| {
572 let keys: Vec<&str> = row.takes.iter().map(|arg| arg.key()).collect();
573 let call = match keys[..] {
574 [] => row.name.to_string(),
575 _ => format!("{}({})", row.name, keys.join(", ")),
576 };
577 let source = match row.reads {
578 Reads::ClosedForm | Reads::Structure => "exact",
579 Reads::Either => "exact off a closed form, measured off samples",
580 Reads::Samples => "measured",
581 Reads::Graph => "measured, and the graph behind the samples",
582 };
583 format!(" {call:<26} {source}\n {}", row.help)
584 })
585 .collect::<Vec<_>>()
586 .join("\n")
587}