Rename filter flags: --highpass to --lowcut, --lowpass to --highcut; clarify docs and usage
This commit is contained in:
17
README.md
17
README.md
@@ -7,6 +7,7 @@ A CLI tool for processing WAV files to generate impulse responses (IR) from swee
|
|||||||
- **Fast FFT-based deconvolution** for accurate IR extraction
|
- **Fast FFT-based deconvolution** for accurate IR extraction
|
||||||
- **Automatic input conversion:** Accepts any WAV sample rate, bit depth, or channel count
|
- **Automatic input conversion:** Accepts any WAV sample rate, bit depth, or channel count
|
||||||
- **Optional output IR length:** Specify output IR length in milliseconds with --length-ms
|
- **Optional output IR length:** Specify output IR length in milliseconds with --length-ms
|
||||||
|
- **Optional low-cut and high-cut filtering:** Apply 2nd-order Butterworth filters to the recorded sweep before IR extraction (--lowcut, --highcut)
|
||||||
- **Automatic fade-out:** Linear fade-out at the end of the IR to avoid clicks (default 5 ms, configurable with --fade-ms)
|
- **Automatic fade-out:** Linear fade-out at the end of the IR to avoid clicks (default 5 ms, configurable with --fade-ms)
|
||||||
- **96kHz 24-bit WAV file support** for high-quality audio processing
|
- **96kHz 24-bit WAV file support** for high-quality audio processing
|
||||||
- **Multiple output formats** with configurable sample rates and bit depths
|
- **Multiple output formats** with configurable sample rates and bit depths
|
||||||
@@ -68,6 +69,16 @@ By default, a 5 ms linear fade-out is applied to the end of the IR to avoid clic
|
|||||||
|
|
||||||
This applies a 10 ms fade-out at the end of the IR.
|
This applies a 10 ms fade-out at the end of the IR.
|
||||||
|
|
||||||
|
### Filtering the Recorded Sweep
|
||||||
|
|
||||||
|
You can apply a low-cut (high-pass) and/or high-cut (low-pass) filter to the recorded sweep before IR extraction. This is useful for removing rumble, DC, or high-frequency noise:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./valhallir-deconvolver --sweep sweep.wav --recorded recorded.wav --output ir.wav --lowcut 40 --highcut 18000
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies a 40 Hz low-cut (high-pass) and 18 kHz high-cut (low-pass) filter to the recorded sweep.
|
||||||
|
|
||||||
### Different Output Formats
|
### Different Output Formats
|
||||||
|
|
||||||
Generate IRs in different sample rates and bit depths:
|
Generate IRs in different sample rates and bit depths:
|
||||||
@@ -128,6 +139,8 @@ Generate IRs in different sample rates and bit depths:
|
|||||||
| `--trim-threshold` | Silence threshold for trimming (0.0-1.0) | 0.001 | No |
|
| `--trim-threshold` | Silence threshold for trimming (0.0-1.0) | 0.001 | No |
|
||||||
| `--length-ms` | Output IR length in milliseconds (trim or zero-pad) | - | No |
|
| `--length-ms` | Output IR length in milliseconds (trim or zero-pad) | - | No |
|
||||||
| `--fade-ms` | Fade-out duration in milliseconds at end of IR (default 5) | 5 | No |
|
| `--fade-ms` | Fade-out duration in milliseconds at end of IR (default 5) | 5 | No |
|
||||||
|
| `--lowcut` | Low-cut filter (high-pass) cutoff frequency in Hz (recorded sweep) | - | No |
|
||||||
|
| `--highcut` | High-cut filter (low-pass) cutoff frequency in Hz (recorded sweep) | - | No |
|
||||||
|
|
||||||
## File Requirements
|
## File Requirements
|
||||||
|
|
||||||
@@ -159,6 +172,10 @@ Generate IRs in different sample rates and bit depths:
|
|||||||
- By default, a 5 ms linear fade-out is applied to the end of the IR (and MPT IR) to avoid clicks
|
- By default, a 5 ms linear fade-out is applied to the end of the IR (and MPT IR) to avoid clicks
|
||||||
- You can change the fade duration with `--fade-ms`
|
- You can change the fade duration with `--fade-ms`
|
||||||
|
|
||||||
|
### Filtering
|
||||||
|
- You can apply a 2nd-order Butterworth low-cut (high-pass) and/or high-cut (low-pass) filter to the recorded sweep before IR extraction
|
||||||
|
- Use `--lowcut` and/or `--highcut` to specify cutoff frequencies in Hz
|
||||||
|
|
||||||
### Deconvolution Process
|
### Deconvolution Process
|
||||||
1. **FFT-based deconvolution** of recorded signal by sweep signal
|
1. **FFT-based deconvolution** of recorded signal by sweep signal
|
||||||
2. **Regularization** to prevent division by zero
|
2. **Regularization** to prevent division by zero
|
||||||
|
|||||||
24
main.go
24
main.go
@@ -65,6 +65,14 @@ func main() {
|
|||||||
Usage: "Fade-out duration in milliseconds to apply at the end of the IR (default 5)",
|
Usage: "Fade-out duration in milliseconds to apply at the end of the IR (default 5)",
|
||||||
Value: 5.0,
|
Value: 5.0,
|
||||||
},
|
},
|
||||||
|
&cli.Float64Flag{
|
||||||
|
Name: "highcut",
|
||||||
|
Usage: "High-cut filter (low-pass) cutoff frequency in Hz (applied to recorded sweep, optional)",
|
||||||
|
},
|
||||||
|
&cli.Float64Flag{
|
||||||
|
Name: "lowcut",
|
||||||
|
Usage: "Low-cut filter (high-pass) cutoff frequency in Hz (applied to recorded sweep, optional)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
// Read sweep WAV file
|
// Read sweep WAV file
|
||||||
@@ -82,8 +90,22 @@ func main() {
|
|||||||
log.Printf("Sweep: %d samples, %d channels", len(sweepData.PCMData), sweepData.Channels)
|
log.Printf("Sweep: %d samples, %d channels", len(sweepData.PCMData), sweepData.Channels)
|
||||||
log.Printf("Recorded: %d samples, %d channels", len(recordedData.PCMData), recordedData.Channels)
|
log.Printf("Recorded: %d samples, %d channels", len(recordedData.PCMData), recordedData.Channels)
|
||||||
|
|
||||||
|
// Optionally filter the recorded sweep
|
||||||
|
recordedFiltered := recordedData.PCMData
|
||||||
|
recSampleRate := recordedData.SampleRate
|
||||||
|
highcutHz := c.Float64("highcut")
|
||||||
|
lowcutHz := c.Float64("lowcut")
|
||||||
|
if lowcutHz > 0 {
|
||||||
|
log.Printf("Applying low-cut (high-pass) filter to recorded sweep: %.2f Hz", lowcutHz)
|
||||||
|
recordedFiltered = convolve.ApplyHighpassButterworth(recordedFiltered, recSampleRate, lowcutHz)
|
||||||
|
}
|
||||||
|
if highcutHz > 0 {
|
||||||
|
log.Printf("Applying high-cut (low-pass) filter to recorded sweep: %.2f Hz", highcutHz)
|
||||||
|
recordedFiltered = convolve.ApplyLowpassButterworth(recordedFiltered, recSampleRate, highcutHz)
|
||||||
|
}
|
||||||
|
|
||||||
log.Println("Performing deconvolution...")
|
log.Println("Performing deconvolution...")
|
||||||
ir := convolve.Deconvolve(sweepData.PCMData, recordedData.PCMData)
|
ir := convolve.Deconvolve(sweepData.PCMData, recordedFiltered)
|
||||||
log.Printf("Deconvolution result: %d samples", len(ir))
|
log.Printf("Deconvolution result: %d samples", len(ir))
|
||||||
|
|
||||||
log.Println("Trimming silence...")
|
log.Println("Trimming silence...")
|
||||||
|
|||||||
@@ -341,3 +341,87 @@ func FadeOutLinear(data []float64, fadeSamples int) []float64 {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyLowpassButterworth applies a 2nd-order Butterworth low-pass filter to the data.
|
||||||
|
// cutoffHz: cutoff frequency in Hz, sampleRate: sample rate in Hz.
|
||||||
|
func ApplyLowpassButterworth(data []float64, sampleRate int, cutoffHz float64) []float64 {
|
||||||
|
if cutoffHz <= 0 || cutoffHz >= float64(sampleRate)/2 {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
// Biquad coefficients
|
||||||
|
w0 := 2 * math.Pi * cutoffHz / float64(sampleRate)
|
||||||
|
cosw0 := math.Cos(w0)
|
||||||
|
sinw0 := math.Sin(w0)
|
||||||
|
Q := 1.0 / math.Sqrt(2) // Butterworth Q
|
||||||
|
alpha := sinw0 / (2 * Q)
|
||||||
|
|
||||||
|
b0 := (1 - cosw0) / 2
|
||||||
|
b1 := 1 - cosw0
|
||||||
|
b2 := (1 - cosw0) / 2
|
||||||
|
a0 := 1 + alpha
|
||||||
|
a1 := -2 * cosw0
|
||||||
|
a2 := 1 - alpha
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
b0 /= a0
|
||||||
|
b1 /= a0
|
||||||
|
b2 /= a0
|
||||||
|
a1 /= a0
|
||||||
|
a2 /= a0
|
||||||
|
|
||||||
|
// Apply filter (Direct Form I)
|
||||||
|
out := make([]float64, len(data))
|
||||||
|
var x1, x2, y1, y2 float64
|
||||||
|
for i := 0; i < len(data); i++ {
|
||||||
|
x0 := data[i]
|
||||||
|
y0 := b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2
|
||||||
|
out[i] = y0
|
||||||
|
x2 = x1
|
||||||
|
x1 = x0
|
||||||
|
y2 = y1
|
||||||
|
y1 = y0
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyHighpassButterworth applies a 2nd-order Butterworth high-pass filter to the data.
|
||||||
|
// cutoffHz: cutoff frequency in Hz, sampleRate: sample rate in Hz.
|
||||||
|
func ApplyHighpassButterworth(data []float64, sampleRate int, cutoffHz float64) []float64 {
|
||||||
|
if cutoffHz <= 0 || cutoffHz >= float64(sampleRate)/2 {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
// Biquad coefficients
|
||||||
|
w0 := 2 * math.Pi * cutoffHz / float64(sampleRate)
|
||||||
|
cosw0 := math.Cos(w0)
|
||||||
|
sinw0 := math.Sin(w0)
|
||||||
|
Q := 1.0 / math.Sqrt(2) // Butterworth Q
|
||||||
|
alpha := sinw0 / (2 * Q)
|
||||||
|
|
||||||
|
b0 := (1 + cosw0) / 2
|
||||||
|
b1 := -(1 + cosw0)
|
||||||
|
b2 := (1 + cosw0) / 2
|
||||||
|
a0 := 1 + alpha
|
||||||
|
a1 := -2 * cosw0
|
||||||
|
a2 := 1 - alpha
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
b0 /= a0
|
||||||
|
b1 /= a0
|
||||||
|
b2 /= a0
|
||||||
|
a1 /= a0
|
||||||
|
a2 /= a0
|
||||||
|
|
||||||
|
// Apply filter (Direct Form I)
|
||||||
|
out := make([]float64, len(data))
|
||||||
|
var x1, x2, y1, y2 float64
|
||||||
|
for i := 0; i < len(data); i++ {
|
||||||
|
x0 := data[i]
|
||||||
|
y0 := b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2
|
||||||
|
out[i] = y0
|
||||||
|
x2 = x1
|
||||||
|
x1 = x0
|
||||||
|
y2 = y1
|
||||||
|
y1 = y0
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
BIN
testdata/ir.wav
vendored
BIN
testdata/ir.wav
vendored
Binary file not shown.
BIN
testdata/ir_mpt.wav
vendored
Normal file
BIN
testdata/ir_mpt.wav
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user