# SongMatch

Automated EQ matching between your audio and a reference track.

SongMatch analyzes a reference track to find its loudest segment, splits both tracks into stems (vocals, drums, bass, other), applies per-stem spectral EQ matching, and recombines. It can also perform whole-mix mastering via [matchering](https://github.com/sergree/matchering).

## Features

- **Per-stem EQ matching** (`--stems-match`): Separates both tracks into 4 stems, matches the spectral profile of each stem independently, then recombines
- **Whole-mix matching** (`--mix-match`): Full-mix frequency response, RMS, stereo width, and peak amplitude matching via matchering
- **Combined mode**: Run both — stems-match first for tonal correction, then mix-match for overall polish
- **Automatic loudest segment detection**: Finds the chorus/loudest section of the reference using LUFS analysis with beat-aligned cropping
- **GPU acceleration**: Uses CUDA/MPS when available for faster stem separation

## Installation

Requires Python 3.10+ and [ffmpeg](https://ffmpeg.org/).

```bash
pip install songmatch
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv pip install songmatch
```

> **Note:** SongMatch pulls in large dependencies including PyTorch (~75MB) and audio-separator. First run will also download the stem separation model (~340MB).

## Usage

```bash
# Per-stem EQ match
songmatch my_song.wav reference.wav --stems-match

# Whole-mix match
songmatch my_song.wav reference.wav --mix-match

# Both: per-stem EQ first, then whole-mix polish
songmatch my_song.wav reference.wav --stems-match --mix-match
```

At least one of `--stems-match` or `--mix-match` must be specified.

### Options

| Option | Default | Description |
|---|---|---|
| `--eq-smoothing` | 0.33 | EQ smoothing in octave fractions (0 = none) |
| `--eq-max-gain` | 12.0 | Max EQ boost/cut in dB |
| `--loudness-threshold` | 2.0 | dB below peak LUFS for segment detection |
| `--min-segment` | 5.0 | Minimum reference crop duration (seconds) |
| `--max-segment` | 30.0 | Maximum reference crop duration (seconds) |
| `--dynamic-range-floor` | 4.0 | Below this LRA in dB, skip cropping |
| `--model` | htdemucs_ft.yaml | Stem separation model |
| `--output-sr` | 44100 | Output sample rate (Hz) |
| `--output-bits` | 16 | Output bit depth (16 or 24) |
| `--keep-ref-stems` | false | Keep reference stem temp files |
| `-v` / `--verbose` | false | Debug logging |

### Output files

For input `my_song.wav`:

```
my_song_stemsmatched.wav          # --stems-match result
my_song_mixmatched.wav            # --mix-match result
my_song_stems/                    # EQ-matched stems (from --stems-match)
  vocals.wav
  drums.wav
  bass.wav
  other.wav
```

## How it works

### --stems-match pipeline

1. Analyze reference loudness (LUFS envelope) and find the loudest segment
2. Snap crop boundaries to nearest beats
3. Separate both reference segment and user audio into 4 stems (vocals, drums, bass, other)
4. For each stem pair: compute PSDs via Welch's method, derive a smoothed transfer function, design a FIR filter, and convolve
5. Recombine EQ-matched stems with peak normalization

### --mix-match pipeline

1. Pass user audio (or stems-match output) and full reference to matchering
2. Matchering handles frequency response, RMS, stereo width, peak amplitude, and limiting

### Combined mode

Stems-match runs first, then mix-match uses its output as input. Both output files are kept.

## License

MIT
