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