Skip to content

Commit ebdbfcb

Browse files
m_igashim_igashi
authored andcommitted
feat: add ReplayGain analysis (-r track gain, -a album gain)
- Add symphonia as optional dependency for MP3 decoding - Implement equal-loudness filter (Yule-Walker + Butterworth) - Implement RMS loudness calculation with 95th percentile - Add -r flag for track gain (normalizes each file to 89 dB) - Add -a flag for album gain (normalizes album to 89 dB) - Update README with full mp3gain compatibility Closes #1
1 parent 4d7cd36 commit ebdbfcb

File tree

5 files changed

+929
-34
lines changed

5 files changed

+929
-34
lines changed

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mp3rgain"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
edition = "2021"
55
authors = ["Masanari Higashi <M-Igashi@users.noreply.github.com>"]
66
description = "Lossless MP3 volume adjustment - a modern mp3gain replacement written in Rust"
@@ -11,9 +11,14 @@ readme = "README.md"
1111
keywords = ["mp3", "audio", "gain", "volume", "lossless"]
1212
categories = ["multimedia::audio", "command-line-utilities"]
1313

14+
[features]
15+
default = []
16+
replaygain = ["symphonia"]
17+
1418
[dependencies]
1519
anyhow = "1.0"
1620
colored = "2.0"
21+
symphonia = { version = "0.5", optional = true, default-features = false, features = ["mp3"] }
1722

1823
[lib]
1924
name = "mp3rgain"

README.md

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ mp3rgain adjusts MP3 volume without re-encoding by modifying the `global_gain` f
1212

1313
- **Lossless**: No re-encoding, preserves original audio quality
1414
- **Fast**: Direct binary manipulation, no audio decoding required
15-
- **Reversible**: All changes can be undone
15+
- **Reversible**: All changes can be undone (stored in APEv2 tags)
16+
- **ReplayGain**: Track and album gain analysis (optional feature)
1617
- **Zero dependencies**: Single static binary (no ffmpeg, no mp3gain)
1718
- **Cross-platform**: macOS, Linux, Windows (x86_64 and ARM64)
18-
- **mp3gain compatible**: Same command-line interface as the original mp3gain
19+
- **mp3gain compatible**: Full command-line compatibility with original mp3gain
1920
- **Pure Rust**: Memory-safe implementation
2021

2122
## Installation
@@ -29,7 +30,11 @@ brew install M-Igashi/tap/mp3rgain
2930
### Cargo (all platforms)
3031

3132
```bash
33+
# Basic installation
3234
cargo install mp3rgain
35+
36+
# With ReplayGain analysis support (-r and -a options)
37+
cargo install mp3rgain --features replaygain
3338
```
3439

3540
### Download binary
@@ -73,13 +78,22 @@ mp3rgain -g -3 *.mp3
7378
mp3rgain -g 2 -p song.mp3
7479
```
7580

76-
### Undo previous adjustment
81+
### ReplayGain (requires `--features replaygain`)
82+
83+
```bash
84+
# Apply track gain (normalize each file to 89 dB)
85+
mp3rgain -r song.mp3
86+
mp3rgain -r *.mp3
7787

78-
To undo a previous gain change, apply the inverse:
88+
# Apply album gain (normalize album to 89 dB)
89+
mp3rgain -a *.mp3
90+
```
91+
92+
### Undo previous adjustment
7993

8094
```bash
81-
# Undo a +2 step adjustment
82-
mp3rgain -g -2 song.mp3
95+
# Undo gain changes (uses APEv2 tag info)
96+
mp3rgain -u song.mp3
8397
```
8498

8599
## Command-Line Options
@@ -88,6 +102,9 @@ mp3rgain -g -2 song.mp3
88102
|--------|-------------|
89103
| `-g <i>` | Apply gain of i steps (each step = 1.5 dB) |
90104
| `-d <n>` | Apply gain of n dB (rounded to nearest step) |
105+
| `-r` | Apply Track gain (ReplayGain analysis) |
106+
| `-a` | Apply Album gain (ReplayGain analysis) |
107+
| `-u` | Undo gain changes (restore from APEv2 tag) |
91108
| `-s c` | Check/show file info (analysis only) |
92109
| `-p` | Preserve original file timestamp |
93110
| `-c` | Ignore clipping warnings |
@@ -97,18 +114,19 @@ mp3rgain -g -2 song.mp3
97114

98115
### mp3gain Compatibility
99116

100-
mp3rgain uses the same command-line syntax as the original mp3gain:
117+
mp3rgain is fully compatible with the original mp3gain command-line interface:
101118

102119
```bash
103120
# These commands work the same way in both mp3gain and mp3rgain
104121
mp3gain -g 2 song.mp3 # original mp3gain
105122
mp3rgain -g 2 song.mp3 # mp3rgain (drop-in replacement)
106-
```
107123

108-
**Not yet implemented:**
109-
- `-r` (Track gain) - requires ReplayGain analysis
110-
- `-a` (Album gain) - requires ReplayGain analysis
111-
- `-u` (Undo from tags) - requires APEv2 tag support
124+
mp3gain -r *.mp3 # original mp3gain
125+
mp3rgain -r *.mp3 # mp3rgain (requires --features replaygain)
126+
127+
mp3gain -a *.mp3 # original mp3gain
128+
mp3rgain -a *.mp3 # mp3rgain (requires --features replaygain)
129+
```
112130

113131
## Technical Details
114132

@@ -127,21 +145,32 @@ Each gain step equals **1.5 dB** (fixed by MP3 specification). The `global_gain`
127145

128146
MP3 files contain a `global_gain` field in each frame's side information that controls playback volume. mp3rgain directly modifies these values without touching the audio data, making the adjustment completely lossless and reversible.
129147

148+
### ReplayGain Analysis
149+
150+
When built with the `replaygain` feature, mp3rgain uses the [symphonia](https://github.com/pdrat/symphonia) crate for MP3 decoding and implements the ReplayGain 1.0 algorithm:
151+
152+
1. Decode MP3 to PCM audio
153+
2. Apply equal-loudness filter (Yule-Walker + Butterworth)
154+
3. Calculate RMS loudness in 50ms windows
155+
4. Use 95th percentile for loudness measurement
156+
5. Calculate gain to reach 89 dB reference level
157+
130158
### Compatibility
131159

132160
- MPEG1 Layer III (MP3)
133161
- MPEG2 Layer III
134162
- MPEG2.5 Layer III
135163
- Mono, Stereo, Joint Stereo, Dual Channel
136164
- ID3v2 tags (preserved)
165+
- APEv2 tags (for undo support)
137166
- VBR and CBR files
138167

139168
## Why mp3rgain?
140169

141170
The original [mp3gain](http://mp3gain.sourceforge.net/) has been unmaintained since ~2015 and has compatibility issues with modern systems (including Windows 11). mp3rgain is a modern replacement that:
142171

143172
- Works on Windows 11, macOS, and Linux
144-
- Has no external dependencies
173+
- Has no external dependencies (base installation)
145174
- Is written in memory-safe Rust
146175
- Uses the same command-line interface
147176
- Includes a library API for integration
@@ -161,13 +190,17 @@ let info = analyze(Path::new("song.mp3"))?;
161190
println!("Headroom: {} steps", info.headroom_steps);
162191
```
163192

193+
## Acknowledgments
194+
195+
- [symphonia](https://github.com/pdrat/symphonia) - Pure Rust audio decoding library (used for ReplayGain analysis)
196+
- [Original mp3gain](http://mp3gain.sourceforge.net/) - The original C implementation that inspired this project
197+
164198
## Contributing
165199

166200
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
167201

168202
We especially welcome:
169203
- Windows testing and compatibility reports
170-
- ReplayGain analysis implementation
171204
- Bug reports and feature requests
172205

173206
## License

src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
//! - **Compatible**: Works with all MP3 files (MPEG1/2/2.5 Layer III)
1313
//! - **Reversible**: Changes can be undone by applying negative gain
1414
//!
15+
//! ## Optional Features
16+
//!
17+
//! - **replaygain**: Enable ReplayGain analysis (requires symphonia)
18+
//! - Track gain calculation (`-r` flag)
19+
//! - Album gain calculation (`-a` flag)
20+
//!
1521
//! ## Example
1622
//!
1723
//! ```no_run
@@ -31,6 +37,8 @@
3137
//! Each gain step equals 1.5 dB (fixed by MP3 specification).
3238
//! The global_gain field is 8 bits, allowing values 0-255.
3339
40+
pub mod replaygain;
41+
3442
use anyhow::{Context, Result};
3543
use std::fs;
3644
use std::path::Path;
@@ -299,7 +307,7 @@ fn read_gain_at(data: &[u8], loc: &GainLocation) -> u8 {
299307
data[idx]
300308
} else if idx + 1 < data.len() {
301309
let shift = loc.bit_offset;
302-
let high = (data[idx] << shift) as u8;
310+
let high = data[idx] << shift;
303311
let low = data[idx + 1] >> (8 - shift);
304312
high | low
305313
} else {

0 commit comments

Comments
 (0)