diff --git a/README.md b/README.md index 7fb7c4f..bc59d6b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A CLI tool for processing WAV files to generate impulse responses (IR) from swee - **Fast FFT-based deconvolution** for accurate IR extraction - **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 low-cut and high-cut filtering:** Apply 2nd-order Butterworth filters to the recorded sweep before IR extraction (--lowcut, --highcut) +- **Optional low-cut and high-cut filtering:** Apply Butterworth filters to the recorded sweep before IR extraction (--lowcut, --highcut, --cut-slope) - **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 - **Multiple output formats** with configurable sample rates and bit depths @@ -79,6 +79,14 @@ You can apply a low-cut (high-pass) and/or high-cut (low-pass) filter to the rec This applies a 40 Hz low-cut (high-pass) and 18 kHz high-cut (low-pass) filter to the recorded sweep. +You can control the filter steepness (slope) with `--cut-slope` (in dB/octave, default 12). For example: + +```sh +./valhallir-deconvolver --sweep sweep.wav --recorded recorded.wav --output ir.wav --lowcut 40 --highcut 18000 --cut-slope 24 +``` + +This applies a 40 Hz low-cut and 18 kHz high-cut, both with a 24 dB/octave slope (steeper than the default 12). + ### Different Output Formats Generate IRs in different sample rates and bit depths: @@ -141,6 +149,7 @@ Generate IRs in different sample rates and bit depths: | `--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 | +| `--cut-slope` | Filter slope in dB/octave (12, 24, 36, ...; default 12) | 12 | No | ## File Requirements @@ -173,8 +182,9 @@ Generate IRs in different sample rates and bit depths: - 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 +- You can apply a 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 +- Use `--cut-slope` to control the filter steepness (12 dB/octave = gentle, 24+ = steeper) ### Deconvolution Process 1. **FFT-based deconvolution** of recorded signal by sweep signal diff --git a/main.go b/main.go index d09e773..fc11935 100644 --- a/main.go +++ b/main.go @@ -73,6 +73,11 @@ func main() { Name: "lowcut", Usage: "Low-cut filter (high-pass) cutoff frequency in Hz (applied to recorded sweep, optional)", }, + &cli.IntFlag{ + Name: "cut-slope", + Usage: "Cut filter slope in dB/octave (12, 24, 36, 48, ...; default 12)", + Value: 12, + }, }, Action: func(c *cli.Context) error { // Read sweep WAV file @@ -95,13 +100,17 @@ func main() { recSampleRate := recordedData.SampleRate highcutHz := c.Float64("highcut") lowcutHz := c.Float64("lowcut") + cutSlope := c.Int("cut-slope") + if cutSlope < 12 || cutSlope%12 != 0 { + return fmt.Errorf("cut-slope must be a positive multiple of 12 (got %d)", cutSlope) + } if lowcutHz > 0 { - log.Printf("Applying low-cut (high-pass) filter to recorded sweep: %.2f Hz", lowcutHz) - recordedFiltered = convolve.ApplyHighpassButterworth(recordedFiltered, recSampleRate, lowcutHz) + log.Printf("Applying low-cut (high-pass) filter to recorded sweep: %.2f Hz, slope: %d dB/oct", lowcutHz, cutSlope) + recordedFiltered = convolve.CascadeLowcut(recordedFiltered, recSampleRate, lowcutHz, cutSlope) } if highcutHz > 0 { - log.Printf("Applying high-cut (low-pass) filter to recorded sweep: %.2f Hz", highcutHz) - recordedFiltered = convolve.ApplyLowpassButterworth(recordedFiltered, recSampleRate, highcutHz) + log.Printf("Applying high-cut (low-pass) filter to recorded sweep: %.2f Hz, slope: %d dB/oct", highcutHz, cutSlope) + recordedFiltered = convolve.CascadeHighcut(recordedFiltered, recSampleRate, highcutHz, cutSlope) } log.Println("Performing deconvolution...") diff --git a/pkg/convolve/convolve.go b/pkg/convolve/convolve.go index aa17c71..4257580 100644 --- a/pkg/convolve/convolve.go +++ b/pkg/convolve/convolve.go @@ -425,3 +425,31 @@ func ApplyHighpassButterworth(data []float64, sampleRate int, cutoffHz float64) } return out } + +// CascadeLowcut applies the low-cut (high-pass) filter multiple times for steeper slopes. +// slopeDb: 12, 24, 36, ... (dB/octave) +func CascadeLowcut(data []float64, sampleRate int, cutoffHz float64, slopeDb int) []float64 { + if slopeDb < 12 { + slopeDb = 12 + } + n := slopeDb / 12 + out := data + for i := 0; i < n; i++ { + out = ApplyHighpassButterworth(out, sampleRate, cutoffHz) + } + return out +} + +// CascadeHighcut applies the high-cut (low-pass) filter multiple times for steeper slopes. +// slopeDb: 12, 24, 36, ... (dB/octave) +func CascadeHighcut(data []float64, sampleRate int, cutoffHz float64, slopeDb int) []float64 { + if slopeDb < 12 { + slopeDb = 12 + } + n := slopeDb / 12 + out := data + for i := 0; i < n; i++ { + out = ApplyLowpassButterworth(out, sampleRate, cutoffHz) + } + return out +} diff --git a/testdata/ir.wav b/testdata/ir.wav index af46e9e..c1f3c2c 100644 Binary files a/testdata/ir.wav and b/testdata/ir.wav differ