1use sva_core::{
4 DEFAULT_LEDGER_DEPTH, DEFAULT_MAX_PEAKS, DEFAULT_OVERSAMPLE, DEFAULT_SILENT_BITS,
5 DEFAULT_SILENT_MAX_SECS,
6};
7use sva_engine::{DEFAULT_FRAME_SECS, DEFAULT_SAMPLE_RATE, PSYCHOACOUSTIC_V1};
8
9pub fn help_text() -> String {
12 let budget = PSYCHOACOUSTIC_V1.flop_budget;
13 format!(
14 r#"USAGE:
15 sva-cli (render | analyze | lint | trace | builtins | outline | new) [arguments]
16
17DESCRIPTION:
18 A composition is a directory of node files, each one closed-form expression in
19 `t` or `f`. sva-cli reads that composition and prints what it is and what it
20 sounds like, as JSON on stdout.
21
22 `--in <dir>` picks the composition for render, lint and trace, wherever in the
23 arguments it is written; without it they read the current directory. `new`
24 writes beside the current directory and `analyze` reads a file, so neither
25 takes `--in`.
26
27RENDER:
28 sva-cli render [<node|expression>] [query options]
29
30 Renders a node, `master` by default, and prints one reading per `--as`. The
31 argument may be an expression instead, in the grammar a node file's body uses.
32 Every reading states its `source` (exact or measured), the conformance profile
33 it ran under, and its rate.
34
35 `--as lines` and `--as atoms` read the closed form and allocate no buffer.
36 `--as samples=<path>.wav` writes 32-bit float audio, or 16-bit PCM under
37 `--pcm16`. Any other destination path takes the same JSON, uncapped. A path
38 that already holds a file refuses unless `--confirm` is written.
39
40 `--from`/`--to` bound the window a collapse runs over. `--sample-rate <hz>` is
41 the observation rate and is legal with every `--as`: no expression can read it.
42
43 `--to silent[:bits]` ends the render at its last sample at or over 2^-bits of
44 full scale, once a bound on every node proves no later sample reaches it.
45 Where silence is not proven by `--max <secs>` it refuses as
46 `engine.not_silent_by` with the bound there; a node that holds a level forever
47 refuses as `engine.never_silent`, and one no bound is derived for yet (a
48 physical solver other than chaigne_askenfelt, one string or a unison, a filter
49 whose coefficients move) as `engine.no_tail_bound`.
50 `--no-cache` skips the disk store. `cache.stats` lists every lookup the render
51 made of it, each a `hit` (with its `tier`) or computed, and stored or not.
52
53 `--as ledger` prints one row per node under the target. A row's `share` is the
54 part of its reader's own energy that row accounts for, so one reader's refs sum
55 to 1; a ref no addend isolates, such as one factor of a product, prints `null`.
56 Each ref carrying a share is collapsed once on its own, so a ledger costs one
57 collapse per attributed ref beyond the render, and `--depth` bounds how many.
58 `--brief` keeps only the rows that clipped, `--skim` drops the wider fields.
59
60 `--as arguments` renders nothing: for every instance under the target it
61 prints each builtin call's named arguments as the numbers the call was lowered
62 with, a solver's whole parameter set with `written: false` on each default it
63 filled in, and the operand each `min`/`max` `chosen` where a call folds one to a
64 number: in named arguments and a solver's, modal bank's or `noise`'s
65 positionals. One inside a filter's or cast's positional or `rand`'s key or
66 seed is not listed. `at` spans are bytes of the instance's
67 own body, as `sva-cli outline` counts them.
68
69ANALYZE:
70 sva-cli analyze <file.wav> [--as <representation>[=<destination>]]...
71
72 Runs the same readings over an external `.wav` at its own rate, never
73 resampled. Only the readings a buffer answers alone apply; the rest need the
74 graph behind it.
75
76LINT:
77 sva-cli lint [<node|expression>] [--in <dir>] [--format <json|text>]
78
79 Checks binding, ref and tempo resolution without rendering a sample. With no
80 target it checks the whole directory against `master`. With a target it checks
81 only the nodes that target reaches, and `entry-point` does not run, since the
82 target's own reach references every node in it.
83
84 Every check prints one `data.diagnostics` item. `advice` and `warning` exit 0,
85 `error` exits non-zero, so branch on the verdict and never on whether the array
86 is empty. No flag downgrades an error.
87
88 error missing-comment no `;` comment line
89 multiline-comment more than one
90 malformed-comment not four ` | ` fields, `Models:` `Neglects:`
91 `IO: <in> -> <out>` `Tags: <tag>[, <tag>...]`
92 long-comment-block a `;` block over 1000 characters, the line-1
93 doc comment's own run exempted
94 long-expression-body a body over 10000 characters, a backstop rather
95 than a complexity budget
96 warning grid-rows-per-bar a TSV grid's row count does not divide evenly
97 into its filename's bar span
98 key-is-not-a-pitch `variables/key` holds neither a note name nor
99 a number of hertz
100 entry-point-refused a node a whole-directory render reaches does
101 not type
102 advice entry-point nothing references this node
103 no-default-root the directory has no `master`
104 tag-shape a tag over 3 lowercase words or 24 characters
105 window-inside-ramp a window sits wholly inside a crop's shoulder
106 literal-sample-rate a written rate where `sp` belongs
107 not-a-file a socket, FIFO or device in the directory
108 not-a-node a filename no `@ref` can spell
109
110TRACE:
111 sva-cli trace <node|expression> [--in <dir>]
112
113 Prints one node's position without rendering audio: what it reads (`down`, one
114 hop), everything that reads it (`up`, transitively to an entry point), each
115 beside the expression doing the reading, the node that made it discrete, and
116 the feedback loop it sits in, if any.
117
118BUILTINS:
119 sva-cli builtins
120
121 Prints the whole callable and syntactic vocabulary: every builtin with its
122 arity and named arguments, each argument's `meaning`, `unit`, the model
123 `part` it sets and whether it `moves` with `t` (a filter's cutoff, q and gain;
124 every other named argument is one number, refused when it names none), each
125 positional's meaning where the model states one, unit suffixes, the note-name
126 grammar, reserved identifiers, special call shapes, and what has no operator
127 at all.
128
129OUTLINE:
130 sva-cli outline <expression>
131
132 Prints the parse tree the engine builds from one expression, each node with
133 the byte `span` it was written in: calls by `name` with positional and named
134 `args`, operators by `op`, refs by `path` with their `binds`, literals by
135 `value` and `unit`, names by `name`. A node the parser supplies itself, the
136 `0` of a prefix minus or the `t` of a bare `@ref`, has `written: false`.
137 Reads no composition.
138
139NEW:
140 sva-cli new <name> [--idempotency-key <key>]
141
142 Writes a starter composition at ./<name>, and refuses if that directory
143 exists. `--idempotency-key <key>` records the key beside the composition, so a
144 retry under the same key succeeds identically while the tree still holds what
145 was written. Any other key, or an edited tree, refuses.
146
147EXAMPLES:
148 sva-cli new song1 && cd song1
149 cd ./song1 && sva-cli render --as samples=/tmp/song1.wav
150 sva-cli render master --in ./song1 --as ledger --as loudness
151 cd ./song1 && sva-cli render chord/home --as lines
152 sva-cli lint --in ./song1
153 cd ./song1 && sva-cli lint voice/note
154 cd ./song1 && sva-cli trace grid/phrase-2b
155 sva-cli builtins
156
157OUTPUT:
158 {{"status": "success", "data": {{"node": "master", "down": {{"items": [...]}},
159 "diagnostics": {{"items": []}}}},
160 "meta": {{"request_id": "req_...", "timestamp": 1700000000}}}}
161
162 An error adds "error": {{"code", "message", "details": {{"count", "codes"}}}}.
163 Success or error, every response carries every finding in full at
164 "data": {{"diagnostics": {{"items": [{{"code", "severity", "message",
165 "location", "help"}}], "pagination": {{"count", "has_more", "next_cursor"}}}}}},
166 empty where it found none.
167
168 Every collection carries that same {{items, pagination}} pair. `count` is the
169 whole reading's, `has_more` says `items` holds less than that, and
170 `next_cursor` is a `--from` value to pass back verbatim for the rest. A framed
171 measurement restarts its state at that instant, so a second page is a second
172 reading rather than a continuation. `--as <name>=<path>` writes the whole
173 reading to a file instead, uncapped. This page is an envelope of its own, at
174 "data": {{"help"}}.
175
176DEFAULTS:
177 --depth <n> how deep below its target a `ledger` walks.
178 Default {DEFAULT_LEDGER_DEPTH}.
179 --peaks <n> peaks a `spectrum` keeps, notes a `pitch`, formants a
180 `formants`. Default {DEFAULT_MAX_PEAKS}.
181 --oversample <n> the multiple `alias` re-renders at to hear what folded.
182 Default {DEFAULT_OVERSAMPLE}.
183 --frame <secs> the step a framed reading advances by, in seconds.
184 Default {DEFAULT_FRAME_SECS}, except `spectrum`, which
185 sizes its own transform to the window unless this flag is
186 given.
187 --sample-rate <hz> the observation rate a render lays its seconds on.
188 Default {DEFAULT_SAMPLE_RATE}.
189 --flop-budget <n> the operation count paid before a render refuses.
190 Default {budget}, the `psychoacoustic-v1` profile's own.
191 --format <json|text> how `lint` prints its findings: the envelope, or one
192 terminal line each, colored where stdout is a terminal.
193 The same objects either way. Default json.
194 --in <dir> the composition `render`, `lint` and `trace` read.
195 Default: the directory the process runs in.
196 --node <path> the instance a reading is taken of. Defaults to the
197 target itself; `--as bindings` requires it.
198 --against <file.wav> the second signal `--as masking` reads against. No
199 default: that one analysis requires it.
200 --from <time> default 0s; `--to <time>` defaults to the node's extent.
201 --to silent[:bits] bits default {DEFAULT_SILENT_BITS}, the profile's precision;
202 `--max <secs>` defaults to {DEFAULT_SILENT_MAX_SECS}.
203 --confirm replaces a destination that already holds a file. Without
204 it a path already taken refuses as `conflict` and nothing
205 is written.
206 --no-cache, --brief, --skim and --pcm16 are all off unless written.
207
208EXIT CODES:
209 0 success (error.code absent)
210 1 internal_error (a destination could not be written)
211 3 validation_error (bad arguments, or a composition that failed to parse)
212 4 conflict (a name `new` would overwrite, or a destination already holding a
213 file, without `--confirm`)
214 24 not_found (a `.wav` file, or a node this composition does not define)
215
216VERB ALIASES:
217 validate = lint, list = builtins, create = new, show = trace, from the `cli`
218 standard's own verb list. `render` and `analyze` take a reading, which that
219 list has no word for, so they keep their own names.
220
221SEE ALSO:
222 sva-cli --version Show version information"#
223 )
224}