Skip to content

feat: Add audio manipulation and analysis #1

@SuaveIV

Description

@SuaveIV

Depends on

Streaming support and the Lofty metadata refactor both need to land first. The commands here operate on the PCM stream format from the streaming issue, and the ReplayGain write step uses Lofty for tag I/O.

Manipulation commands

sound trim

Cut audio from the start or end of a stream.

sound decode audio.flac | sound trim --start 5sec --end 10sec | sound play

Rodio already has skip_duration and take_duration on Source. This is mostly just exposing those as a pipeline step.

sound fade

Apply a fade in, fade out, or both.

sound decode audio.flac | sound fade --in 2sec --out 3sec | sound play

Rodio has fade_in. Fade out needs a small custom Source wrapper since Rodio doesn't provide one — multiply sample amplitude by a linearly decreasing envelope over the tail duration. Straightforward enough.

sound amplify

Adjust volume by a fixed factor. This is distinct from the -a flag on sound play because it operates on the stream itself, making it composable with other steps.

sound decode audio.flac | sound amplify 0.5 | sound trim --start 5sec | sound play

sound concat

Combine multiple streams sequentially.

[intro.flac main.flac outro.flac] | each { sound decode $in } | sound concat | sound play

Analysis commands

sound scan

Measure loudness and peak levels of a stream. Returns a record, doesn't play anything.

sound decode audio.flac | sound scan

Example output:

╭──────────────────┬──────────╮
 integrated_lufs   -14.2    
 loudness_range    6.1      
 true_peak_dbtp    -1.3     
 track_gain_db     1.8      
╰──────────────────┴──────────╯

Uses the ebur128 crate for EBU R128 loudness measurement, which is the basis for ReplayGain 2.0. The scan consumes the stream so decoding happens before scanning.

sound replaygain

Scans and writes ReplayGain tags back to a file using Lofty. Supports track and album gain modes.

Track gain (per file):

ls *.flac | each { |f|
    sound decode $f.name | sound scan | sound replaygain write $f.name
}

Album gain requires a shared loudness reference across all tracks, which is where Nushell's pipeline really helps:

ls *.flac | each { |f|
    { file: $f.name, scan: (sound decode $f.name | sound scan) }
} | sound replaygain album

Album mode collects all per-track measurements, computes integrated loudness across all of them, then writes both per-track and album gain tags to each file.

Tags written:

  • REPLAYGAIN_TRACK_GAIN
  • REPLAYGAIN_TRACK_PEAK
  • REPLAYGAIN_ALBUM_GAIN
  • REPLAYGAIN_ALBUM_PEAK

These follow the standard names so other players pick them up without any extra configuration.

sound silence

Detect silence spans in a stream and return them as a table. Useful for splitting recordings or trimming padding automatically.

sound decode audio.flac | sound silence --threshold -40db --min-duration 500ms

Example output:

╭───┬────────────┬────────────┬──────────╮
 # │ start      │ end        │ duration │
├───┼────────────┼────────────┼──────────┤
 0  0ns         1sec        1sec     
 1  3min 22sec  3min 25sec  3sec     
╰───┴────────────┴────────────┴──────────╯

Applying ReplayGain during playback

Once tags are written, sound play should be able to apply them at playback time:

sound play audio.flac --replaygain track
sound play audio.flac --replaygain album

Reads the appropriate gain tag via Lofty and feeds the computed factor into Rodio's amplify wrapper. The math is just:

gain_factor = 10 ^ (replaygain_db / 20)

New dependencies

ebur128 for loudness measurement. Actively maintained, no significant transitive complexity. Everything else is already present via Rodio and Lofty.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions