package main import ( "fmt" "log" "os" "valhallir-convoluter/pkg/convolve" "valhallir-convoluter/pkg/wav" "github.com/urfave/cli/v2" ) func main() { app := &cli.App{ Name: "valhallir-convoluter", Usage: "Convolve sweep and recorded WAV files to create impulse responses", 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) } }