Commit Graph
354 Commits
Author SHA1 Message Date
Dylan M. Taylor 9f2be37e36 Enforce Rust coding standards via Clippy (#1129)
* chore(lints): apply strict workspace Clippy rules

* refactor(h264): split decoding work by operation

* refactor(h264): clarify prediction and filter inputs

* docs: require workspace lint inheritance

* refactor(audio): clarify host FFI imports and inputs

* docs: clarify crate lint placement

* refactor(audio): replace wildcard imports in I/O modules

* refactor(mix): extract strip and routing stages

* refactor(ui): split edit window event handlers

* refactor(ui): clarify app and dialog actions

* refactor(ui): simplify mixer and panel workflows

* fix(ui): apply track resize only once
2026-10-28 11:48:06 -04:00
aiongg e82c1689b3 refactor(cli): parse soundcraft-cli arguments with clap, following RFC-0012 (#1136)
* test(cli): pin today's argument handling

Run the real binary and record how the hand-written parser behaves for every
subcommand: help and version spellings, no arguments, unknown subcommands and
flags, value flags in both spellings, missing and repeated values, positional
order, exit codes and which stream carries each message.

No behaviour change. Later parser changes update these assertions on purpose.

* refactor(cli): split main.rs into modules

Move only: args, error, output, convert, info, session, catalog, app and
mcp each get their functions and unit tests; main.rs keeps the usage doc,
help_text and the dispatch. Moved items become pub(crate) where other
modules call them. No behaviour change.

* refactor(cli): typed errors and run(args, out, err)

soundcraft-cli is now a library plus a thin binary. `soundcraft_cli::run(args,
out, err) -> i32` is the whole program; main.rs only passes the real streams
and shuts the plugin hosts down afterwards (process teardown, not part of run,
which tests call repeatedly in one process). Subcommands write to the given
writers, return `Result<(), CliError>`, and `fail` is gone. CliError wraps
engine, audio-io, io (with the path), serde_json and the control channel
errors; UsageError, ConvertError and RootFlagError cover the subcommands' own
failures. No Result<_, String> is left except where a dependency reports a
string (control-channel call, AutomationRoots::new), wrapped in a variant.

The tests that need no files, working directory, environment or stdin now call
`run` in-process (tests/it/run.rs); the rest still run the binary.

Behaviour changes:
- Error messages start with `error: ` instead of `soundcraft-cli: `.
- A script line that stops the run now prints `error: FILE:N: ID: ...` (it had
  no prefix). With --keep-going the line stays bare.
- A convert encode or write failure now names the output path.
- An empty or help-less call still prints the one-line usage to stderr without
  a prefix and exits 1; `files confined:` keeps its `soundcraft-cli:` prefix
  (it is a note, not an error).

* refactor(cli): parse arguments with clap

Replace the hand parser (arg_value, check_options, ENGINE_OPTIONS, the
lib.rs dispatch, the per-subcommand positional handling and the root
flags' consume) with clap derive types in args.rs: a Parser, a
Subcommand enum and one Args struct per subcommand, with the run/script
engine flags flattened. main passes args_os(); run takes OsString-like
arguments and renders clap's help, version and usage errors (help and
version on stdout, exit 0; usage errors on stderr, exit 2). Add
command() returning the clap definition. The usage block in the module
doc and help_text are gone: the doc comments are the help.

Behaviour changes:
- Usage errors exit 2 with clap's message and the --help hint on
  stderr, instead of exit 1 with hand-written text: unknown subcommand,
  unknown flag, missing value, missing or extra positional, a value on
  a boolean flag (--demo=1, --normalize=1).
- No arguments prints the help to stderr, exit 2 (was a one-line usage,
  exit 1).
- --help, -h and help print clap's generated help, and every subcommand
  has its own (SUB --help, SUB -h, help SUB). Before, info and script
  read --help as a file name, describe as an id, commands as a filter,
  run rejected --help and ignored -h, convert and app printed their
  usage error, mcp, parity and plugins ignored it, and app --port P -h
  called the method -h.
- --help or -h before -- shows the subcommand's help even after a
  mistake on the same line (run --bounce --help).
- --version, -V and version print "soundcraft-cli X.Y.Z" (was
  "SoundCraft CLI X.Y.Z").
- Unknown flags on info, commands, describe, app, mcp, parity and
  plugins are usage errors; they were ignored or read as a positional.
  mcp --automation-bogus exits 2 (was exit 1).
- Extra positionals are usage errors on every subcommand (were ignored).
- A single-dash word (-x) is a flag, so an unknown one is a usage
  error; a value starting with - is written --flag=-value.
- Flags may come before or between the positionals: convert IN --rate
  44100 OUT, convert --rate 44100 IN OUT and script --keep-going FILE
  parse (the flag word was taken as a file name).
- A repeated single-value flag keeps its last value (was the first):
  --rate, --bit-depth, --save, --port and the others.
- parity --write without a value is a usage error (printed the report).
- mcp --connect without a value, or followed by a flag, is a usage
  error (was exit 1, or took the flag as the port).
- mcp --demo=1 is a usage error (was ignored, without the demo).
- A root flag without a folder, with an empty one, or followed by a
  flag is a usage error with clap's message (was exit 1).
- A non-Unicode argument is a usage error where text is expected and
  accepted as a root folder (std::env::args panicked).
- The help lists --sample as --demo's alias.

Unchanged: --dither with --no-dither, bad --rate and --bit-depth values,
the roots with --connect and the SOUNDCRAFT_CONTROL_PORT fallback are
still checked after parsing (exit 1).

* refactor(cli): reject repeated flags, clap --version (photocraft#7071 decisions)

Behaviour changes:
- A flag given twice is a usage error (exit 2), in either spelling, including
  an alias pair such as --demo and --sample. Before, the last one won. Only
  run's --cmd and mcp's --automation-read-root / --automation-write-root
  repeat; they accumulate in order.
- --version and -V print the long version, "soundcraft-cli 0.13.1 (linux
  x86_64)": the version and the platform the binary was built for.
- The hidden `version` subcommand is gone; `soundcraft-cli version` is an
  unrecognized-subcommand usage error.
- --start and --end accept a negative number (`--start -1`, as the engine
  counts positions before 0); any other value starting with `-` needs `=`
  and otherwise is an unexpected-argument usage error. No flag has
  allow_hyphen_values.
- `help <subcommand>` is unchanged (tested).

* refactor(cli): typed values and flag relations

Handlers receive typed values: convert --bit-depth is a ValueEnum, --rate a
NonZeroU32, file arguments PathBuf, --start/--end a TimeArg, --cmd a parsed
(id, JSON) spec, app's JSON a serde_json::Value, app --port and mcp --connect
a validated port or host:port. Relations (--dither/--no-dither, --in/--demo,
--start/--end needing --bounce) are declared on the flags. describe ID,
commands FILTER and app METHOD refuse an empty string. parse_rate,
parse_bit_depth, parse_dither, parse_cmd_spec, time_arg and the Rate,
BitDepth, DitherConflict and CmdJson errors are gone.

Behaviour changes:
- --dither with --no-dither is a usage error (exit 2) instead of exit 1.
- --rate 0 / non-numeric and an unknown --bit-depth are usage errors (exit 2)
  with clap's wording, naming the flag. --bit-depth help lists the values.
- --in with --demo (or --sample) is a usage error; --in used to win silently.
- --start/--end without --bounce are a usage error; they used to be ignored.
- Malformed JSON in --cmd ID=JSON and in app's JSON argument is a usage error
  (exit 2) at parse time. Before, run opened the engine and executed every
  earlier --cmd (printing their results) before the bad one failed with exit 1.
- An empty --start/--end, an empty command id (--cmd =1), and an empty
  describe ID, commands FILTER or app METHOD are usage errors. An empty
  commands FILTER used to list everything.
- app --port and mcp --connect accept only a port (0-65535) or host:port;
  junk used to be accepted and failed at connect time (exit 1).
- Non-Unicode file names are accepted wherever the callee takes a Path (info
  audio files, convert, script, parity --write). Session and bounce/save paths
  go to the engine as text, so a non-Unicode one fails with exit 1, "the path
  is not valid Unicode", after any earlier --cmd ran.

* refactor(cli): shared launch flags and SOUNDCRAFT_CONTROL_PORT as clap env

soundcraft_automation::args holds the launch flags as clap Args structs:
ControlClientArgs (`app --port`, env SOUNDCRAFT_CONTROL_PORT, default
7979), ControlServerArgs (the desktop `--control`, same variable, wired
in a later step; its ControlPort value is a port, also from a loopback
host:port, or another host's host:port, which the app decides on, so one
export serves both), AutomationArgs (the two root flags, repeatable, flags
only), the ControlTarget value parser, and the blank_env_is_unset,
name_env_vars and conflict helpers. The module and its clap/thiserror
dependencies are left out of the wasm build. `app` and `mcp` flatten
them; the hand fallback to the variable in `app` is gone.

Behaviour changes:
- An empty or blank SOUNDCRAFT_CONTROL_PORT counts as unset, so `app`
  uses 7979; before, it tried to connect to `127.0.0.1:` (exit 1).
- A bad SOUNDCRAFT_CONTROL_PORT is a usage error naming the variable
  (exit 2); before, the connection failed (exit 1).
- Spaces around the `app --port` or `mcp --connect` value, or the
  variable's, are ignored; before, the connection failed (exit 1).
- An IPv6 host in `app --port`, `mcp --connect` or the variable goes in
  brackets (`[::1]:7979`); `::1:7979` is a usage error (exit 2), as the
  host and port can't be told apart; before, the connection failed
  (exit 1).
- `app --help` shows `[env: SOUNDCRAFT_CONTROL_PORT=]` and
  `[default: 7979]` instead of the hand-written default note.
- `mcp` with a root flag and --connect is clap's conflict error (exit 2,
  "the argument '...' cannot be used with '...'"), reported before any
  root folder is opened; before, a custom message with exit 1, after the
  roots were opened.

* refactor(app): move launch flag parsing into launch_args

The parsing moves out of main unchanged, into a LaunchArgs struct and a
parse function that takes SOUNDCRAFT_CONTROL_PORT as a parameter. Tests pin
the flags, the file list, ignored unknown flags and the fallback of a bad
--control to the variable. No behaviour change.

* refactor(app): parse launch flags with clap

soundcraft --help now comes from the LaunchArgs doc comments and ends with the
environment variables the app reads, and the shared ControlServerArgs supplies
--control and SOUNDCRAFT_CONTROL_PORT. Process tests cover the variable (bad
value, blank value, loopback host:port, another host).

Behaviour changes:
- An unknown flag (--bogus, -x) is a usage error on stderr, exit 2. It was
  ignored, and a single-dash word was opened as a file; after `--` it is a file.
- A bad --control (out of range, not a number, missing value) is a usage error,
  exit 2. It was ignored and the variable used instead.
- A bad SOUNDCRAFT_CONTROL_PORT is a usage error naming the variable, exit 2.
  It was ignored. An empty or blank variable still counts as unset.
- A loopback host:port (127.0.0.1, localhost or [::1]) in --control or
  SOUNDCRAFT_CONTROL_PORT gives its port, so the value `soundcraft-cli app`
  takes from the variable works for both. The control server listens on
  127.0.0.1 only, so another host in --control is a usage error. In the
  variable, which may point `soundcraft-cli app` at another machine, it is
  ignored with a log warning and the app opens without a control server.
  Both were ignored before.
- A repeated flag is a usage error (--demo twice, --control twice).
- --control=PORT and a port with surrounding spaces are accepted; a value that
  starts with `-` is not taken as the value, except a negative number, which is
  a bad port (`--control -1`).
- --help ends with an Environment section listing SOUNDCRAFT_CONTROL_PORT,
  SOUNDCRAFT_CONFIG_DIR, SOUNDCRAFT_NO_PREFS, SOUNDCRAFT_NO_SYSTEM_FONTS,
  SOUNDCRAFT_WAYLAND (Linux) and RUST_LOG.
- --help and -h print the help, exit 0. They were ignored.
- --version and -V print `soundcraft 0.13.1 (linux x86_64)`, as soundcraft-cli
  does. It printed `SoundCraft 0.13.1`.
- -V and --version are read in order with the other arguments: an earlier
  error comes first.
- macOS -psn_… process serial numbers are dropped, as before they were
  opened as files.
- A file whose name is not valid Unicode is skipped with a log warning. It
  used to panic in std::env::args.

* feat(cli): shell completions and man pages

An example, `cargo run -p soundcraft-cli --example man-and-completions -- <out-dir>`,
writes bash, zsh, fish, elvish and PowerShell completions and soundcraft-cli.1 plus a
page per subcommand, using clap_complete and clap_mangen (dev-dependencies of
soundcraft-cli only, not in the binary).

The Linux package (deb, rpm, tar.gz, AppImage) installs the pages in
/usr/share/man/man1 and the bash, zsh and fish completions in their standard
directories; the deb also links the zsh completion into Debian's
vendor-completions. A cross build runs the example through $EMULATOR and
leaves the files out without one. The FreeBSD tarball carries them in the
ports layout; a cross build builds the example for the host as well.

No change to the CLI's behaviour. A test pins the files the example writes.

* ci(cli): check the command-line code with ast-grep rules

Adds lint/ast-grep (rules for clap errors built from strings, String as an
error type, and std::env::args) and a workflow that runs the rule tests and a
scan over the CLI code through the shared craft-common-actions workflow. No
behaviour change; the scan has no hits.

* docs(cli): document soundcraft-cli and RFC-0012

Add docs/cli.md: the subcommands, exit codes, environment variables, the
desktop app's launch flags, man pages and completions, and the RFC-0012 rules
not followed yet (convert can overwrite its input; no --compact/--json).
Link it from the README and the MCP and control-protocol guides. No code change.

* feat(cli): refuse an output that replaces an input

An output that is one of the subcommand's inputs is a usage error (exit 2,
`error: …` on stderr, clap's usage line), reported while parsing, before
the engine loads or any --cmd runs. Paths are compared by file identity
(device and inode, through same-file), or, when neither exists yet, by
where they would be created, so `a.wav`, `./a.wav`, an absolute path and a
symbolic or hard link all count as the same file.

Inputs and outputs per subcommand:
- convert: input IN, output OUT. --in-place lets OUT replace IN.
- run: input --in, outputs --bounce and --save. --in-place lets --save
  replace the --in session; it requires --in. A --bounce naming the --in
  session is always refused.
- script: inputs FILE and --in, outputs --bounce and --save. --in-place
  lets --save replace the --in session; a --bounce naming it is always
  refused, and neither may ever replace FILE.
- run and script: --bounce and --save naming one file is refused with or
  without --in-place (the save would replace the bounce).
- info, commands, describe, app, mcp, plugins write no file; parity
  --write has no input. Paths inside --cmd parameters are not checked.

With --in-place, an output that is a symbolic link to the input is written
to the file the link names, so convert and --save both replace the input
and leave the link in place (a save renames a new file over its path,
which would otherwise replace the link and leave the input unchanged).

Behaviour changes:
- `convert a.wav a.wav`, `run --in s.scraft --save s.scraft`,
  `run --in s.scraft --bounce s.scraft` and the other cases above exit 2
  instead of replacing the input.
- New flag --in-place on convert, run and script.
- An existing file that is not an input is still replaced without asking.

* feat(cli): --compact for JSON output

RFC-0012 rule 1.3: JSON is pretty-printed by default and --compact prints
one line. Every pretty-printed JSON output now goes through
output::print_json.

Changed outputs:
- info <audio file>, describe, app: new --compact flag.
- run and script: new --compact flag, which requires --inspect and applies
  to the --inspect JSON only.

Unchanged on purpose:
- The `ID: JSON` result lines of run and script and the --bounce and --save
  results stay one line each. The prefix makes them text records, and the
  RFC lets a command document its own layout; scripts that parse them keep
  working.
- commands, plugins, parity and the .scraft summary of info are text and get
  no --json flag (an open question in the RFC); mcp speaks JSON-RPC lines.

Pretty output is byte-identical to before.

* fix(app): one Switch parser for boolean environment switches

SOUNDCRAFT_NO_PREFS, SOUNDCRAFT_NO_SYSTEM_FONTS and SOUNDCRAFT_WAYLAND each
counted any value, including 0, as on. They now share
soundcraft_ui_egui::switch::Switch: 1/on/true/yes is On, 0/off/false/no is
Off (case- and space-insensitive), unset or blank is Default, and any other
value is Default and logged with the variable name. The ENVIRONMENT help
text, docs/cli.md and the README say the same.

Behaviour changes (old -> new):
- SOUNDCRAFT_NO_PREFS=0 disabled preferences (any value did); now only On
  does.
- SOUNDCRAFT_NO_SYSTEM_FONTS=0 disabled system fonts (any value did); now
  only On does.
- SOUNDCRAFT_WAYLAND=0 kept native Wayland (any value did); now only On does.
- An empty value of any of the three counted as set; it now means the
  default (off).
- An unknown value of any of the three is logged.

* refactor(app): read the environment once into Env

The desktop app reads SOUNDCRAFT_CONFIG_DIR, SOUNDCRAFT_NO_PREFS,
SOUNDCRAFT_NO_SYSTEM_FONTS and SOUNDCRAFT_WAYLAND once at startup into
env::Env (typed fields, Switch for the on/off ones), after the logger is
installed so malformed switches are logged. The --help Environment
section is generated from the same VARS table, with a test that it lists
every name.

ui-egui no longer reads the app's variables: fonts::install and
definitions take system_fonts, set through SoundApp::set_system_fonts
(default on, so the web app is unchanged). The XWayland and prefs
decisions are methods on Env, tested on literals. logging::install takes
the RUST_LOG value, which stays outside Env because the logger needs it
before anything can log.

No behaviour change beyond help column spacing.

* refactor(cli): one long_version for both binaries

* refactor(audio-io): one FileFormat table for extensions

FileFormat gains the extension table every caller matches on:
from_extension / from_path (ASCII case-insensitive) over extensions,
from_write_extension over write_extensions, default_extension, name,
mime_type, the READ and WRITE lists, and readable_extensions /
writable_extensions for dialog filters, help and messages. Reading
takes every extension the decoder knows; writing takes only the names
whose readers expect what the encoder writes (no rf64 or aifc, whose
files would get plain RIFF and AIFF headers).
detect_format, bounce (mix and stems), convert, the bounce dialog, the
desktop and web file pickers, the workspace browser, the Sampler, web
downloads and the MCP bounce schema all use it; ENCODE_EXTENSIONS and
encode_format_for are gone. convert's IN/OUT and info's FILE help name
the extensions from the read and write lists.

Behaviour changes, where the lists disagreed:
- The bounce dialog's Browse result, the bounce save filter and the MCP
  bounce enum take wav, wave, bwf, aif, aiff and flac: wave and bwf are
  new to all three, and aif is new to the MCP enum. convert and
  file.bounce_mix already took these. The "supported:" list in the
  cannot-write errors is "wav, wave, bwf, aif, aiff, flac".
- file.bounce_stems reads its format name case-insensitively: `AIFF`
  or `FLAC` meant WAV before. Unknown names still mean WAV.
- The bounce dialog sends the format as its file extension (aif for
  AIFF); the engine accepts both spellings.
- File pickers, the workspace browser and the Sampler's drop and Load
  Sample filter list every extension the decoder reads: the workspace
  browser gains wave, bwf, rf64, aifc, mp1, mp2, oga, aac, mp4, adts,
  mkv and webm; the Sampler gains rf64, mp1, mp2, m4b, adts, mkv and
  webm; the desktop and web open filters gain rf64, mp1, mp2 and adts.
- Web downloads get a media type for every audio format, not only WAV,
  AIFF and FLAC.

* refactor(app): resolve the settings folder once in Env

* fix(app): keep paths as paths until the engine needs text

A path is text only where an engine command takes `"path": string`; a
name that is not valid Unicode is reported there instead of being opened
under a lossy rendering (another file, or a confusing "can't read").

- ui-egui: dialogs::unicode() and NotUnicode ("<path:?> is not valid
  Unicode"); dialogs::open_path(app, &Path), moved from the desktop app,
  is the one open-by-extension path for dropped, command-line and
  Workspace files.
- Services: the file dialogs return PathBuf (pick_save and pick_folder
  start from a &Path); callers convert where the text field or command
  takes the path.
- Sampler drops and Load Sample pass the path to mix.sampler_load
  through unicode(); the audio test checks the Path.
- Workspace panel keeps entry paths; names are shown lossily only as
  labels. workspace_dir stays a String (serialized preferences).
- soundcraft-cli: utf8() is unicode(), CliError::NonUnicodePath is
  NotUnicode; it is called only for info's session, --in, --bounce and
  --save. convert's output format test no longer treats a non-Unicode
  extension as "no extension".
- docs/cli.md: sessions and bounces the engine opens or writes must be
  valid Unicode.

Behaviour changes:
- A dropped, command-line, picked or Workspace file whose name is not
  valid Unicode is not opened: the status line (and log) says
  "<path:?> is not valid Unicode", with the bytes escaped. Dropped files
  used to be opened under a U+FFFD rendering.
- A file dialog result that is not valid Unicode leaves the path field
  unchanged and shows the same warning.
- Double-clicking a Workspace file opens it like a dropped file:
  .MID/.MIDI/.SCRAFT in any case open as MIDI/session (they used to
  import as audio), and a failed open is logged.
- convert OUT with an extension that is not valid Unicode is refused as
  an unsupported format instead of being written as WAV.

* fix(app): read Env after the launch flags; tidy Switch and Env

* fix(audio-io): decode every extension the format table lists

* fix(cli): write convert output atomically; tighten --connect and loopback

- convert writes every output to a new file in the output's folder, flushes it and renames it
  over the target, removing the new file on failure. A failed write (disk full, unwritable
  folder) no longer truncates the input under --in-place (RFC-0012 rule 1.6). A link output
  still writes the linked file.
- --save in place already goes through the engine's replace_file (tmp + rename), and
  file.bounce_mix renders to <name>.part and renames it when complete, so both are atomic.
- The desktop's --control / SOUNDCRAFT_CONTROL_PORT no longer treat [::1] as loopback: the
  control server binds 127.0.0.1 only. [::1]:P is a usage error from the flag and is ignored
  with a warning from the variable. The CLI's --connect / app --port still accept [::1]:P.
  Error tip and docs/cli.md updated.
- mcp --connect conflicts with --demo (exit 2): the demo session belongs to the in-process
  server. Help and docs/cli.md say so.

* test(audio-io): list the decode fixtures in ATTRIBUTION.md

cargo xtask assets needs a row for every committed audio file; one
directory row covers crates/audio-io/tests/fixtures/.

* fix(cli): refuse non-finite --start and --end values

* refactor(cli): parse the control target once; open sessions by path; keep error sources

- A1: ControlTarget { host, port } is parsed once by args::control_target; is_loopback() replaces
  control_port/ControlPort, and Remote::new takes &ControlTarget.
- A2: engine::io::open_session takes impl AsRef<Path>; the CLI no longer requires a Unicode path
  to open a session, only for the --bounce and --save paths that go into JSON.
- A3: ValueError::Json keeps the serde_json::Error; Remote::request returns RemoteError and
  AutomationRoots::new returns RootError, both carried by CliError.

* test(engine): pass session paths to open_session without a borrow

* test(cli): info opens a session at a non-Unicode path

* refactor(app): log the shared long version; info: session extension in any case; Switch beside Env
2026-10-27 21:09:43 -04:00
aiongg aabe5ae320 test(engine): cut the file_access scan only at inline test modules 2026-10-26 22:05:23 +00:00
aiongg 640180997c refactor(engine): split io.rs into io/ 2026-10-26 20:35:05 +00:00
aiongg 7c3547ffd4 refactor(engine): move edit.rs tests to edit/tests.rs 2026-10-26 18:15:31 +00:00
aiongg 23394f0b6a refactor(engine): move mix.rs tests to cmd/mix/tests.rs 2026-10-26 18:15:31 +00:00
aiongg 01c70688bf refactor(engine): move file.rs tests to cmd/file/tests.rs 2026-10-26 18:15:30 +00:00
aiongg c1df0b83cd refactor(engine): move cmd/mod.rs to cmd.rs 2026-10-26 18:15:14 +00:00
Dylan M. Taylor 088e270a5e refactor: split auto_pan process and audiosuite frame limit into helpers (#1104)
Reduces cognitive complexity flagged by SonarCloud (rust:S3776). Behaviour is unchanged:
per-sample order is envelope update, envelope trigger, LFO phase, modulator, width/manual/position
smoothing, output write.
2026-10-25 21:14:17 -04:00
Dylan M. Taylor d683aee3eb refactor: reduce cognitive complexity in audio-io, midi, dsp, engine, playback, plugin hosts (S3776) (#1097)
* refactor: reduce cognitive complexity in audio-io, midi, dsp, engine, playback, plugin hosts (S3776)

Extract helpers; behavior-preserving.

* fix: clippy lints in extracted helpers
2026-10-25 19:32:34 -04:00
aiongg cf96cb888c Follow-ups to #1058: Auto Pan keeps the stereo image, paste and Strip Silence fixes (#1060)
* Auto Pan: balance the left and right channels at constant power instead of panning their mono sum

* Strip Silence rectangles: on the web, rescan when a slider drag ends instead of on every frame of it

* Paste: keep the clipboard's remapped Auto Rename keys only when the paste succeeds

* Subharmonic: test that Q 5 filters after the saturator keep the output finite

* AudioSuite window: drop the constant-speed varispeed controls no menu item opens

* docs: gaps: Clip Looping has no loop crossfade and caps Number of Loops at 512
2026-10-25 12:06:36 -04:00
aiongg 353ea145aa Integration: AudioSuite speed ramp: Fit To plays the first two thirds for Slow Down and the last for Speed Up 2026-10-25 04:36:02 +00:00
aiongg 35905fb90b Integration: Auto Rename: a loaded scheme leaves room for its number 2026-10-25 03:53:11 +00:00
aiongg d7b1562cc1 Integration: Strip Silence naming: rustfmt 2026-10-25 02:54:13 +00:00
aiongg 9048e04d60 Integration: AudioSuite speed ramp: the clip suffix is speeramp
AudioSuite clip suffixes take four letters from each word of the process
id, so speed_ramp gives -speeramp.
2026-10-25 02:46:30 +00:00
aiongg 9821168bc4 Merge PR #1054: Clip List: Auto Rename sets the names of the clips separated from a clip
# Conflicts:
#	ROADMAP.md
2026-10-25 02:33:05 +00:00
aiongg e46339606a Merge PR #1055: Strip Silence: rectangles show what Strip keeps while the window is open
# Conflicts:
#	ROADMAP.md
2026-10-25 02:32:58 +00:00
aiongg 835de9f038 Integration: Strip Silence naming: refuse a name and suffix too long for a clip name
A name and suffix longer than MAX_NAME_CHARS - 29 leave no room for the
number, and a session truncates such names on open.
2026-10-25 02:32:54 +00:00
aiongg e910e72940 Merge PR #1044: Strip Silence: Rename sets the names of the clips it leaves
# Conflicts:
#	ROADMAP.md
2026-10-25 02:32:31 +00:00
aiongg 54467f544b Merge PR #1040: Strip Silence: Extract keeps the silence, and Separate cuts the clips without removing any
# Conflicts:
#	ROADMAP.md
2026-10-25 02:32:22 +00:00
aiongg 4311742624 Merge PR #1030: Clip > Loop... opens Clip Looping: Number of Loops, Loop Length, or until the next clip or the session end
# Conflicts:
#	ROADMAP.md
2026-10-25 02:32:11 +00:00
aiongg a8ca533b9b Merge PR #1053: Fades: Create refuses a shape that is not a string over a selection 2026-10-25 02:31:53 +00:00
aiongg 3a139d4bed Integration: AudioSuite speed ramp: name SoundCraft's ids speed_ramp
The process id, types, function, test module and clip suffix use
speed_ramp; the menu label stays as the catalog has it.
2026-10-25 02:31:48 +00:00
aiongg 092122b49c Merge PR #1016: AudioSuite: Vari-Fi slows a clip down to a stop or speeds it up from one
# Conflicts:
#	ROADMAP.md
#	docs/plugin-parity.md
2026-10-25 02:31:05 +00:00
aiongg c0dc2c3ec6 Merge PR #1017: Autosave: allow the Unix-only mut, so clippy passes on Windows 2026-10-25 02:30:09 +00:00
dotCooCoo 06f30628bb Clip List: a paste reuses a held Auto Rename scheme only when its text matches too
A paste reused the scheme of the session it went into whenever the
copied scheme's id matched, so an unrelated scheme that had the same id
could rename the pasted clip and count its numbers on. A scheme is now
reused only when its name, zeros and suffix match as well; any other is
added under a new key.
2026-10-24 18:37:54 -07:00
dotCooCoo 746a825ed5 Strip Silence: rectangles show what Strip keeps while the window is open
While the Strip Silence window is open, the Edit window draws a
rectangle around each range that Strip would keep on the selected
tracks: the audio above the threshold, padded by Clip Start Pad and
Clip End Pad. The rectangles follow the sliders and stay while Rename
Selected Clips is open. They are removed when the window closes. The
audio and the clips do not change.

The scan runs again when a value or the session changes. Outside the
web build it runs on a worker thread, one scan at a time, and the
rectangles last found stay until it ends. A scan for older values, or
one still running after the window closes, stops before its next
window of audio.

The new query command edit.strip_silence_preview returns the same
ranges for each track. It reads the threshold, minimum strip duration
and pads with the defaults and limits of edit.strip_silence, and both
use the same scan.
2026-10-24 18:17:53 -07:00
dotCooCoo fe4cd8095d Clip List: Auto Rename sets the names of the clips separated from a clip
Clip List › Auto Rename… opens Rename Selected Clips for the selected
clips. Name, Starting Number, Number of Places and Suffix set the names
that editing gives the clips it separates from them: the name, as many
zeros as Number of Places, a number counting up from Starting Number,
and the suffix. The piece after each cut is the new clip and takes the
next name. The piece before it keeps its name. The clips separated from
one clip, and from every clip that got the scheme together, count up
one number sequence. The guide does not say how Strip Silence's own
Rename fields and Auto Rename combine. Here, when Strip Silence's own
Rename fields are set, they name the audio pieces it cuts inside its
range, and those pieces take no Auto Rename name and do not count the
Auto Rename number up. In Strip and Extract, only the pieces that Strip
Silence keeps take Auto Rename names, in time order, so their numbers
have no gaps.

clip.auto_rename takes clips, name, number, zeros and suffix, with the
same limits and errors as edit.strip_silence. Without a name the clips
get no scheme. Cut and Copy keep a copy of the copied clips' schemes,
and a paste uses the session's scheme with the same id or adds the
copy under a new key, so a pasted copy keeps its names after an undo
or in another session. Saved sessions hold the schemes, and opening a
session repairs them and drops the ones no clip uses. Import Session
Data brings them in under new keys. Clips placed from a clip group file
get no scheme.
2026-10-24 17:12:22 -07:00
dotCooCoo a41f254aa0 Fades: Create refuses a shape that is not a string over a selection
edit.fades_create over tracks and a range read a null, boolean or number
shape as absent and made Equal Power fades. It now returns an error that
lists the accepted shapes, as the clip form already did, and leaves the
session and the undo history unchanged.
2026-10-24 15:19:28 -07:00
dotCooCoo 698507cf30 Strip Silence: Rename sets the names of the clips it leaves
The Strip Silence window has a Rename… button. It opens Rename Selected
Clips, where Name, Number, Zeros and Suffix set the names that Strip,
Extract and Separate give the clips they leave in the selection: the
name, that many zeros, a number counting up from Number, and the
suffix. SFX, 23, 1 and .Reel1 give SFX023.Reel1, SFX024.Reel1 and so
on. The app keeps the scheme for later uses of Strip Silence until
the app quits, or until Clear and then OK empty it. OK, Cancel and
Escape go back to Strip Silence with its slider values.

edit.strip_silence takes name, number, zeros and suffix. Numbering runs
on from one track to the next, and the result reports how many clips
were named. Without a name the clips keep their names. A name or suffix
that is not text, a number out of range, or a number, zeros or suffix
without a name is an error and leaves the tracks unchanged.
2026-10-24 14:18:26 -07:00
dotCooCoo 1e9a0e9c88 Strip Silence: a mode that is not a string is refused
edit.strip_silence read a null, boolean or number mode as no mode and ran Strip. It now returns an error and leaves the track unchanged, as it does for an unknown mode name.
2026-10-24 13:46:21 -07:00
dotCooCoo 0ca492e0ab Strip Silence: Extract keeps the silence, and Separate cuts the clips without removing any
The Strip Silence window had only a Strip button. It now also has
Extract and Separate. Extract removes the parts of the selection that
hold audio and keeps the silent parts. Separate separates the clips at
the same points Strip does and does not remove any clip. Enter on a
focused Extract or Separate runs that button, and Enter presses Strip
only when neither was clicked.

edit.strip_silence takes a mode param: strip (the default), extract or
separate. An unknown mode is an error. The Minimum Strip Duration
slider now goes up to 4,000 ms.
2026-10-24 12:57:38 -07:00
dotCooCoo d5be1e6de3 Clip > Loop... opens Clip Looping: Number of Loops, Loop Length, or until the next clip or the session end
Clip > Loop... and Cmd+Alt+L looped the selected clip four times at once.
They now open the Clip Looping dialog. Number of Loops counts the clip and
its loops together, 4 by default as before. Loop Length is typed in the
main counter's time format, and the last loop is cut short when the
length is not a whole number of loops. Loop Until End of Session or Next
Clip loops up to the next clip on the track, or else to the end of the
last clip in the session.

clip.loop takes the new fill option, and its length option now cuts the
last loop short instead of rounding down to whole loops. A fill or a
length that needs more than 1,000 loops is refused with an error instead
of stopping at 1,000 loops. The cut-short loop is trimmed with
Clip::trim_end_to, which shortens its fades to fit and removes a sync
point that falls past the cut.

Unloop takes a shorter last clip as part of the loop when it shares the
loop, no clip of the loop starts where it ends, and either a whole copy
comes before it or it holds the same material as the origin just before
it. It removes a short last loop too, and leaves both halves of a split
clip in place.
2026-10-24 09:14:06 -07:00
dotCooCoo 02b153797d Merge main into feat/vari-fi-speed-ramps 2026-10-24 07:44:44 -07:00
dotCooCoo c5ddbbafd1 Autosave: allow the Unix-only mut, so clippy passes on Windows
Autosave::create builds each session folder with a DirBuilder that only
the #[cfg(unix)] mode call mutates. On Windows, clippy -D warnings
reports the binding as unused_mut, and cargo xtask ci stops at its
clippy step. The binding now carries
#[cfg_attr(not(unix), allow(unused_mut))], as the recovery test's paths
in the same file does. Nothing changes at run time.
2026-10-24 02:55:17 -07:00
dotCooCoo f0f9addd16 AudioSuite: Vari-Fi slows a clip down to a stop or speeds it up from one
AudioSuite > Pitch Shift > Vari-Fi resampled the clip at a constant
speed. It now ramps the speed between normal and a complete stop, so
the pitch falls or rises with it. Slow Down and Speed Up set the
direction. Fit To keeps the clip's length and plays two thirds of it;
Extend plays all of it and makes the clip 150 % long. Fades On fades
the audio out with the slowdown or in with the speedup.

The ramp is offline::vari_fi, run by audiosuite.process as vari_fi
with change, selection and fades params. The constant-speed varispeed
process stays available to commands.
2026-10-24 02:24:30 -07:00
aiongg f6a27c2ccf Integration: automation: hold clip gain envelope ends to the clip edges 2026-10-24 09:13:19 +00:00
aiongg b47f544c7a Merge track r1-t02
# Conflicts:
#	contributors/people.toml
#	docs/plugin-parity.md
2026-10-24 06:54:04 +00:00
aiongg de206f46b6 Merge PR #798: Fix saved audio trust test on Windows by joining path parts separately (#789) 2026-10-24 06:53:57 +00:00
aiongg d33563ebe2 Merge PR #521: Keep MIDI note selection valid across clip, session and note-order changes (#443)
# Conflicts:
#	crates/engine/src/cmd/midi.rs
2026-10-24 06:53:56 +00:00
aiongg 4240873586 Merge PR #551: Bounce: a Dither option in the dialog and for stems (#522) 2026-10-24 06:40:52 +00:00
aiongg 3fe53ee834 Integration: #595 pass BUNDLE_LIMITS in main's sources_sharing_one_file test 2026-10-24 06:19:20 +00:00
aiongg 9e7583d99b Merge PR #595: Save a session bundle only if it opens again (#433)
crates/engine/src/io.rs: kept both bundle tests.
2026-10-24 06:19:17 +00:00
aiongg 045938728f Integration: #674 advance the source offset by delta/stretch on start trims and compare heal offsets in source time 2026-10-24 06:19:09 +00:00
aiongg 2e17315d5d Merge PR #674: Fix source offset when trimming the start of a stretched audio clip
crates/engine/src/edit.rs: kept main's gap-aware heal comparison and doc comment, the PR's source_len helper, and both sets of tests.
crates/model/src/clip.rs: kept main's clamped trim_start_to body (the stretch-aware offset follows in the Integration commit) and both tests.
2026-10-24 06:19:06 +00:00
aiongg 678cb0f57d Integration: #517 join_gain_env places the right envelope at the seam when heal bridges a gap 2026-10-24 06:18:49 +00:00
aiongg a1eafbd1c5 Merge PR #517: Heal keeps each clip's gain envelope and skips clips whose mute or stretch differ (#450)
crates/engine/src/edit.rs: heal combines main's gap-aware join (contiguous, gap, saturating adds) with the PR's same_playback check and join_gain_env call; both heal tests kept.
2026-10-24 06:18:46 +00:00
aiongg c8f44fbbef Merge PR #518: Keep unrelated overlapping clips untouched when deleting fades (#449)
crates/engine/src/edit.rs: kept both tests; delete_fades body merged without conflict.
2026-10-24 06:17:53 +00:00
aiongg 0cb3c692ac Merge PR #716: Keep the existing volume lane in automation.clip_gain_to_volume 2026-10-24 06:17:18 +00:00
aiongg bb990d9bfa Merge PR #663: Fix Split into Mono repeating the left channel
ROADMAP.md, docs/gaps.md: kept the newer header line, union of log, table and G-04 entries
crates/engine/src/cmd/track.rs: kept main's explicit imports, added ClipContent
2026-10-24 06:16:56 +00:00