diff --git a/README.md b/README.md index 76e6a8e..fd2c73e 100644 --- a/README.md +++ b/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 - **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 +- **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 - **Minimum Phase Transform (MPT)** option for reduced latency IRs @@ -57,6 +58,16 @@ Trim or zero-pad the output IR to a specific length (in milliseconds): This will ensure the output IR is exactly 100 ms long (trimming or zero-padding as needed). +### Fade-Out to Avoid Clicks + +By default, a 5 ms linear fade-out is applied to the end of the IR to avoid clicks. You can change the fade duration: + +```sh +./valhallir-deconvolver --sweep sweep.wav --recorded recorded.wav --output ir.wav --fade-ms 10 +``` + +This applies a 10 ms fade-out at the end of the IR. + ### Different Output Formats Generate IRs in different sample rates and bit depths: @@ -116,6 +127,7 @@ Generate IRs in different sample rates and bit depths: | `--normalize` | Normalize output to peak value (0.0-1.0) | 0.95 | 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 | +| `--fade-ms` | Fade-out duration in milliseconds at end of IR (default 5) | 5 | No | ## File Requirements @@ -143,6 +155,10 @@ Generate IRs in different sample rates and bit depths: - If `--length-ms` is set, the output IR (and MPT IR) will be trimmed or zero-padded to the specified length in milliseconds - If not set, the full IR is used +### Fade-Out +- 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` + ### Deconvolution Process 1. **FFT-based deconvolution** of recorded signal by sweep signal 2. **Regularization** to prevent division by zero diff --git a/go.mod b/go.mod index f6698e6..91e8da0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module valhallir-deconvolver -go 1.24.1 +go 1.24.5 require ( dagger.io/dagger v0.18.12 diff --git a/main.go b/main.go index da22755..b38a332 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,11 @@ func main() { Name: "length-ms", Usage: "Optional: Output IR length in milliseconds (will trim or zero-pad as needed)", }, + &cli.Float64Flag{ + Name: "fade-ms", + Usage: "Fade-out duration in milliseconds to apply at the end of the IR (default 5)", + Value: 5.0, + }, }, Action: func(c *cli.Context) error { // Read sweep WAV file @@ -135,6 +140,14 @@ func main() { ir = convolve.TrimOrPad(ir, targetSamples) } + // Apply fade-out + fadeMs := c.Float64("fade-ms") + fadeSamples := int(float64(targetSampleRate) * fadeMs / 1000.0) + if fadeSamples > 0 { + log.Printf("Applying linear fade-out: %d samples (%.2f ms)...", fadeSamples, fadeMs) + ir = convolve.FadeOutLinear(ir, fadeSamples) + } + // Write regular IR log.Printf("Writing IR to: %s (%dHz, %d-bit WAV)", c.String("output"), sampleRate, bitDepth) if err := wav.WriteWAVFileWithOptions(c.String("output"), ir, sampleRate, bitDepth); err != nil { @@ -165,6 +178,12 @@ func main() { mptIR = convolve.TrimOrPad(mptIR, targetSamples) } + // Apply fade-out to MPT IR + if fadeSamples > 0 { + log.Printf("Applying linear fade-out to MPT IR: %d samples (%.2f ms)...", fadeSamples, fadeMs) + mptIR = convolve.FadeOutLinear(mptIR, fadeSamples) + } + // Generate MPT output filename outputPath := c.String("output") if len(outputPath) > 4 && outputPath[len(outputPath)-4:] == ".wav" { diff --git a/pkg/convolve/convolve.go b/pkg/convolve/convolve.go index 6b26fea..50625db 100644 --- a/pkg/convolve/convolve.go +++ b/pkg/convolve/convolve.go @@ -322,3 +322,22 @@ func Resample(data []float64, fromSampleRate, toSampleRate int) []float64 { return result } + +// FadeOutLinear applies a linear fade-out to the last fadeSamples of the data. +// fadeSamples is the number of samples over which to fade to zero. +func FadeOutLinear(data []float64, fadeSamples int) []float64 { + if fadeSamples <= 0 || len(data) == 0 { + return data + } + if fadeSamples > len(data) { + fadeSamples = len(data) + } + out := make([]float64, len(data)) + copy(out, data) + start := len(data) - fadeSamples + for i := start; i < len(data); i++ { + fade := float64(len(data)-i) / float64(fadeSamples) + out[i] *= fade + } + return out +} diff --git a/testdata/ir.wav b/testdata/ir.wav new file mode 100644 index 0000000..32bfda3 Binary files /dev/null and b/testdata/ir.wav differ