stems: multitrack PipeWire recorder
Records every source feeding the default sink into sample-aligned float WAV stems plus the sink mix, so sessions can be mixed and mastered later. Release flow mirrors galdr: tag -> build tarballs on both arches -> Gitea release -> publish to the uhhm Arch registry (no AUR package). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
commit
9484d0d578
@@ -0,0 +1,64 @@
|
||||
# stems
|
||||
|
||||
Multitrack recorder for PipeWire. Run it, play your session, press Ctrl-C:
|
||||
every source that fed the default sink has its own WAV stem, plus the sink's
|
||||
mix as `00-master.wav`, all the same length and sample-aligned. Drop the
|
||||
folder into any DAW and mix or master later.
|
||||
|
||||
```
|
||||
pw-play / galdr / browser / mic loopback ──▶ default sink ──▶ speakers
|
||||
│ │ │ │
|
||||
└── stems taps each source port ────────┴── 00-master (sink monitor)
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
stems [--dir <path>] [--sink <node.name>] [--no-master] [--list]
|
||||
```
|
||||
|
||||
- `--dir` session root, default `~/Recordings/stems`. Each run writes
|
||||
`<dir>/<YYYY-MM-DD_HH-MM-SS>/NN-<source>.wav`.
|
||||
- `--sink` record the sources of this sink instead of the default one.
|
||||
- `--no-master` skip `00-master.wav`.
|
||||
- `--list` print the sources that would be recorded right now and exit.
|
||||
|
||||
Files are 32-bit float WAV at the graph rate, one per source node, with the
|
||||
source's channel count (mono sources stay mono). Sources that start mid-session
|
||||
get a track the moment they link to the sink; sources that stop keep their
|
||||
file. Ctrl-C (or SIGTERM) finalizes everything and prints a table:
|
||||
|
||||
```
|
||||
# track ch length starts at dropped
|
||||
00 00-master.wav 2 42.67s 0.04s 0
|
||||
01 01-galdr.wav 2 42.67s 0.00s 0
|
||||
02 02-fc.zoom.playback.wav 2 42.67s 0.00s 0
|
||||
03 03-pw-play.wav 2 42.67s 12.30s 0
|
||||
```
|
||||
|
||||
## How alignment works
|
||||
|
||||
PipeWire stamps every graph cycle with one timestamp that all streams in that
|
||||
cycle see (`pw_time.now`). Each stem's first buffer is placed at that
|
||||
timestamp's distance from the session origin, cycles a stream missed are
|
||||
filled with silence, and at the end every file is padded to the common length.
|
||||
So frame N in one file is frame N in every other file; "starts at" in the
|
||||
table is just how much leading silence a late-joining track got.
|
||||
|
||||
Each track is a capture stream linked by hand (`link-factory`) to the source's
|
||||
output ports, so the session manager never re-routes it, and the realtime
|
||||
callback only copies frames into a lock-free ring that a writer thread drains.
|
||||
|
||||
## Limitations
|
||||
|
||||
- A source that appears mid-session is tapped a few graph cycles after it
|
||||
starts (the link has to be created once the graph reports it), so roughly
|
||||
the first 30 to 50 ms of a new source are only in the master, not in its stem.
|
||||
- The default sink is read once at start; switching sinks during a session
|
||||
is not followed. Use `--sink` to pin one.
|
||||
- Stems capture what the source sends to the sink, pre-fader: the sink's
|
||||
per-stream volume is not applied (the master has it).
|
||||
|
||||
## Build
|
||||
|
||||
Rust, PipeWire 1.x. `cargo build --release`; the binary is `stems`.
|
||||
Reference in New Issue
Block a user