Files
valhallir-deconvolver/main.go

191 lines
5.8 KiB
Go

package main
import (
"fmt"
"log"
"os"
"valhallir-deconvolver/pkg/convolve"
"valhallir-deconvolver/pkg/wav"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "valhallir-deconvolver",
Usage: "Deconvolve sweep and recorded WAV files to create impulse responses",
Version: "v1.0.0",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "sweep",
Usage: "Path to the sweep WAV file (96kHz 24bit)",
Required: true,
},
&cli.StringFlag{
Name: "recorded",
Usage: "Path to the recorded WAV file (96kHz 24bit)",
Required: true,
},
&cli.StringFlag{
Name: "output",
Usage: "Path to the output IR WAV file (96kHz 24bit)",
Required: true,
},
&cli.BoolFlag{
Name: "mpt",
Usage: "Generate minimum phase transform IR in addition to regular IR",
},
&cli.IntFlag{
Name: "sample-rate",
Usage: "Output sample rate (44, 48, 88, 96 kHz)",
Value: 96000,
},
&cli.IntFlag{
Name: "bit-depth",
Usage: "Output bit depth (16, 24, 32 bit)",
Value: 24,
},
&cli.Float64Flag{
Name: "normalize",
Usage: "Normalize output to this peak value (0.0-1.0, default 0.95)",
Value: 0.95,
},
&cli.Float64Flag{
Name: "trim-threshold",
Usage: "Silence threshold for trimming (0.0-1.0, default 0.001)",
Value: 0.001,
},
&cli.Float64Flag{
Name: "length-ms",
Usage: "Optional: Output IR length in milliseconds (will trim or zero-pad as needed)",
},
},
Action: func(c *cli.Context) error {
// Read sweep WAV file
sweepData, err := wav.ReadWAVFile(c.String("sweep"))
if err != nil {
return err
}
// Read recorded WAV file
recordedData, err := wav.ReadWAVFile(c.String("recorded"))
if err != nil {
return err
}
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.Println("Performing deconvolution...")
ir := convolve.Deconvolve(sweepData.PCMData, recordedData.PCMData)
log.Printf("Deconvolution result: %d samples", len(ir))
log.Println("Trimming silence...")
ir = convolve.TrimSilence(ir, 1e-5)
log.Printf("After trimming: %d samples", len(ir))
log.Println("Normalizing...")
ir = convolve.Normalize(ir, c.Float64("normalize"))
log.Printf("Final IR: %d samples", len(ir))
// Validate output format options
sampleRate := c.Int("sample-rate")
bitDepth := c.Int("bit-depth")
// Validate sample rate
validSampleRates := []int{44100, 48000, 88200, 96000}
validSampleRate := false
for _, sr := range validSampleRates {
if sampleRate == sr {
validSampleRate = true
break
}
}
if !validSampleRate {
return fmt.Errorf("invalid sample rate: %d. Valid options: %v", sampleRate, validSampleRates)
}
// Validate bit depth
validBitDepths := []int{16, 24, 32}
validBitDepth := false
for _, bd := range validBitDepths {
if bitDepth == bd {
validBitDepth = true
break
}
}
if !validBitDepth {
return fmt.Errorf("invalid bit depth: %d. Valid options: %v", bitDepth, validBitDepths)
}
// Resample IR to target sample rate if different from input (96kHz)
targetSampleRate := sampleRate
if targetSampleRate != 96000 {
log.Printf("Resampling IR from 96kHz to %dHz...", targetSampleRate)
ir = convolve.Resample(ir, 96000, targetSampleRate)
log.Printf("Resampled IR: %d samples", len(ir))
}
// Trim or pad IR to requested length if --length-ms is set
lengthMs := c.Float64("length-ms")
if lengthMs > 0 {
targetSamples := int(float64(targetSampleRate) * lengthMs / 1000.0)
log.Printf("Trimming or padding IR to %d samples (%.2f ms)...", targetSamples, lengthMs)
ir = convolve.TrimOrPad(ir, targetSamples)
}
// 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 {
return err
}
// Generate MPT IR if requested
if c.Bool("mpt") {
log.Println("Generating minimum phase transform...")
// Use the original 96kHz IR for MPT generation
originalIR := convolve.Deconvolve(sweepData.PCMData, recordedData.PCMData)
originalIR = convolve.TrimSilence(originalIR, 1e-5)
mptIR := convolve.MinimumPhaseTransform(originalIR)
mptIR = convolve.Normalize(mptIR, c.Float64("normalize"))
log.Printf("MPT IR: %d samples", len(mptIR))
// Resample MPT IR to target sample rate if different from input (96kHz)
if targetSampleRate != 96000 {
log.Printf("Resampling MPT IR from 96kHz to %dHz...", targetSampleRate)
mptIR = convolve.Resample(mptIR, 96000, targetSampleRate)
log.Printf("Resampled MPT IR: %d samples", len(mptIR))
}
// Trim or pad MPT IR to requested length if --length-ms is set
if lengthMs > 0 {
targetSamples := int(float64(targetSampleRate) * lengthMs / 1000.0)
log.Printf("Trimming or padding MPT IR to %d samples (%.2f ms)...", targetSamples, lengthMs)
mptIR = convolve.TrimOrPad(mptIR, targetSamples)
}
// Generate MPT output filename
outputPath := c.String("output")
if len(outputPath) > 4 && outputPath[len(outputPath)-4:] == ".wav" {
outputPath = outputPath[:len(outputPath)-4]
}
mptOutputPath := outputPath + "_mpt.wav"
log.Printf("Writing MPT IR to: %s (%dHz, %d-bit WAV)", mptOutputPath, sampleRate, bitDepth)
if err := wav.WriteWAVFileWithOptions(mptOutputPath, mptIR, sampleRate, bitDepth); err != nil {
return err
}
log.Println("Minimum phase transform IR generated successfully!")
}
log.Println("Impulse response generated successfully!")
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}