Merge pull request 'Add linear fade-out to IRs with --fade-ms option (default 5ms); update README and CLI' (#1) from feature/fade-out into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
16
README.md
16
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
|
||||||
|
- **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
|
||||||
- **Minimum Phase Transform (MPT)** option for reduced latency IRs
|
- **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).
|
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
|
### Different Output Formats
|
||||||
|
|
||||||
Generate IRs in different sample rates and bit depths:
|
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 |
|
| `--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 |
|
| `--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 |
|
||||||
|
|
||||||
## File Requirements
|
## 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 `--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
|
- 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
|
### 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
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module valhallir-deconvolver
|
module valhallir-deconvolver
|
||||||
|
|
||||||
go 1.24.1
|
go 1.24.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dagger.io/dagger v0.18.12
|
dagger.io/dagger v0.18.12
|
||||||
|
|||||||
19
main.go
19
main.go
@@ -60,6 +60,11 @@ func main() {
|
|||||||
Name: "length-ms",
|
Name: "length-ms",
|
||||||
Usage: "Optional: Output IR length in milliseconds (will trim or zero-pad as needed)",
|
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 {
|
Action: func(c *cli.Context) error {
|
||||||
// Read sweep WAV file
|
// Read sweep WAV file
|
||||||
@@ -135,6 +140,14 @@ func main() {
|
|||||||
ir = convolve.TrimOrPad(ir, targetSamples)
|
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
|
// Write regular IR
|
||||||
log.Printf("Writing IR to: %s (%dHz, %d-bit WAV)", c.String("output"), sampleRate, bitDepth)
|
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 {
|
if err := wav.WriteWAVFileWithOptions(c.String("output"), ir, sampleRate, bitDepth); err != nil {
|
||||||
@@ -165,6 +178,12 @@ func main() {
|
|||||||
mptIR = convolve.TrimOrPad(mptIR, targetSamples)
|
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
|
// Generate MPT output filename
|
||||||
outputPath := c.String("output")
|
outputPath := c.String("output")
|
||||||
if len(outputPath) > 4 && outputPath[len(outputPath)-4:] == ".wav" {
|
if len(outputPath) > 4 && outputPath[len(outputPath)-4:] == ".wav" {
|
||||||
|
|||||||
@@ -322,3 +322,22 @@ func Resample(data []float64, fromSampleRate, toSampleRate int) []float64 {
|
|||||||
|
|
||||||
return result
|
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
|
||||||
|
}
|
||||||
|
|||||||
BIN
testdata/ir.wav
vendored
Normal file
BIN
testdata/ir.wav
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user