Refactor main.go to remove slog logger integration and replace with standard log package

This commit is contained in:
2025-08-15 19:08:38 -04:00
parent bd64dd24dc
commit 89b1999048

91
main.go
View File

@ -3,7 +3,7 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"log/slog" "log"
"math" "math"
"os" "os"
"time" "time"
@ -22,7 +22,6 @@ var (
hopMs = flag.Int("hop", 50, "Hop size (ms)") hopMs = flag.Int("hop", 50, "Hop size (ms)")
ratioThresh = flag.Float64("ratio", 0.65, "Power ratio threshold for tone detection") ratioThresh = flag.Float64("ratio", 0.65, "Power ratio threshold for tone detection")
rmsThresh = flag.Float64("rms", 300.0, "Minimum RMS for valid signal") rmsThresh = flag.Float64("rms", 300.0, "Minimum RMS for valid signal")
verbose = flag.Bool("verbose", false, "Enable debug logging")
) )
// Goertzel struct for frequency detection // Goertzel struct for frequency detection
@ -33,7 +32,6 @@ type goertzel struct {
coeff float64 coeff float64
} }
// newGoertzel initializes and returns a new instance of the Goertzel algorithm for detecting a specific target frequency.
func newGoertzel(targetHz float64, fs float64, N int) *goertzel { func newGoertzel(targetHz float64, fs float64, N int) *goertzel {
g := &goertzel{N: N, fs: fs} g := &goertzel{N: N, fs: fs}
g.k = int(0.5 + (float64(g.N)*targetHz)/fs) g.k = int(0.5 + (float64(g.N)*targetHz)/fs)
@ -42,7 +40,6 @@ func newGoertzel(targetHz float64, fs float64, N int) *goertzel {
return g return g
} }
// Power computes the power of the target frequency in the input signal x using the Goertzel algorithm.
func (g *goertzel) Power(x []float64) float64 { func (g *goertzel) Power(x []float64) float64 {
var s0, s1, s2 float64 var s0, s1, s2 float64
for i := 0; i < g.N; i++ { for i := 0; i < g.N; i++ {
@ -56,7 +53,6 @@ func (g *goertzel) Power(x []float64) float64 {
return real*real + imag*imag return real*real + imag*imag
} }
// windowHann applies a Hann window to the input slice x in-place.
func windowHann(x []float64) { func windowHann(x []float64) {
n := float64(len(x)) n := float64(len(x))
for i := range x { for i := range x {
@ -64,7 +60,6 @@ func windowHann(x []float64) {
} }
} }
// pcmToFloat converts a slice of 16-bit PCM audio samples to a slice of float64 values.
func pcmToFloat(buf []int16, N int) []float64 { func pcmToFloat(buf []int16, N int) []float64 {
out := make([]float64, N) out := make([]float64, N)
for i := 0; i < N && i < len(buf); i++ { for i := 0; i < N && i < len(buf); i++ {
@ -73,7 +68,6 @@ func pcmToFloat(buf []int16, N int) []float64 {
return out return out
} }
// rmsPCM calculates the root mean square (RMS) value of a slice of 16-bit PCM audio samples.
func rmsPCM(buf []int16) float64 { func rmsPCM(buf []int16) float64 {
var s float64 var s float64
for _, v := range buf { for _, v := range buf {
@ -102,16 +96,16 @@ type twoToneDetector struct {
aFreq float64 aFreq float64
aAccumMs int aAccumMs int
aStart time.Time aStart time.Time
aEnd time.Time
waitingB bool waitingB bool
bFreq float64 bFreq float64
bAccumMs int bAccumMs int
bStart time.Time bStart time.Time
bEnd time.Time
gapRemainMs int gapRemainMs int
logger *slog.Logger
} }
// newTwoToneDetector creates and initializes a twoToneDetector instance with the specified parameters. func newTwoToneDetector(fs, winN, hopN int, ratioThresh, rmsThresh float64, minAms, minBms, gapMaxMs int) *twoToneDetector {
func newTwoToneDetector(fs, winN, hopN int, ratioThresh, rmsThresh float64, minAms, minBms, gapMaxMs int, logger *slog.Logger) *twoToneDetector {
freqs := make([]float64, 0) freqs := make([]float64, 0)
for f := 300.0; f <= 3000.0; f += 10.0 { for f := 300.0; f <= 3000.0; f += 10.0 {
freqs = append(freqs, f) freqs = append(freqs, f)
@ -131,11 +125,9 @@ func newTwoToneDetector(fs, winN, hopN int, ratioThresh, rmsThresh float64, minA
gapMaxMs: gapMaxMs, gapMaxMs: gapMaxMs,
freqs: freqs, freqs: freqs,
gzBank: gzBank, gzBank: gzBank,
logger: logger,
} }
} }
// stepWindow processes a window of PCM audio samples to detect a two-tone event.
func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string, aFreq, aDur, bFreq, bDur float64, timestamp time.Time) { func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string, aFreq, aDur, bFreq, bDur float64, timestamp time.Time) {
xi := pcmToFloat(pcms, d.winN) xi := pcmToFloat(pcms, d.winN)
windowHann(xi) windowHann(xi)
@ -150,10 +142,6 @@ func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string,
now := t0 now := t0
if r < d.rmsThresh { if r < d.rmsThresh {
d.logger.Debug("RMS below threshold, resetting",
"time", now.Format(time.RFC3339),
"rms", fmt.Sprintf("%.2f", r),
"threshold", d.rmsThresh)
d.reset() d.reset()
return "", 0, 0, 0, 0, time.Time{} return "", 0, 0, 0, 0, time.Time{}
} }
@ -170,10 +158,6 @@ func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string,
} }
ratio := bestPow / (total + 1e-12) ratio := bestPow / (total + 1e-12)
if ratio < d.ratioThresh { if ratio < d.ratioThresh {
d.logger.Debug("Ratio below threshold, resetting",
"time", now.Format(time.RFC3339),
"ratio", fmt.Sprintf("%.3f", ratio),
"threshold", d.ratioThresh)
d.reset() d.reset()
return "", 0, 0, 0, 0, time.Time{} return "", 0, 0, 0, 0, time.Time{}
} }
@ -183,30 +167,23 @@ func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string,
// Looking for Tone A // Looking for Tone A
d.inA = true d.inA = true
d.aFreq = freq d.aFreq = freq
d.aAccumMs = int(hopDur.Milliseconds())
d.aStart = now d.aStart = now
} else if d.inA && !d.waitingB { } else if d.inA && !d.waitingB {
// Confirming Tone A // Confirming Tone A
if math.Abs(freq-d.aFreq) <= 10.0 { if math.Abs(freq-d.aFreq) <= 10.0 {
d.aAccumMs += int(hopDur.Milliseconds()) d.aAccumMs += int(hopDur.Milliseconds())
d.aEnd = now.Add(hopDur)
if d.aAccumMs >= d.minAms { if d.aAccumMs >= d.minAms {
d.inA = false d.inA = false
d.waitingB = true d.waitingB = true
d.gapRemainMs = d.gapMaxMs d.gapRemainMs = d.gapMaxMs
} }
} else { } else {
d.logger.Debug("Frequency differs from Tone A, resetting",
"time", now.Format(time.RFC3339),
"freq", fmt.Sprintf("%.1f", freq),
"tone_a_freq", fmt.Sprintf("%.1f", d.aFreq))
d.reset() d.reset()
} }
} else if d.waitingB { } else if d.waitingB {
d.gapRemainMs -= int(hopDur.Milliseconds()) d.gapRemainMs -= int(hopDur.Milliseconds())
if d.gapRemainMs <= 0 { if d.gapRemainMs <= 0 {
d.logger.Debug("Gap exceeded max duration, resetting",
"time", now.Format(time.RFC3339),
"gap_max_ms", d.gapMaxMs)
d.reset() d.reset()
} else if math.Abs(freq-d.aFreq) > 10.0 { } else if math.Abs(freq-d.aFreq) > 10.0 {
// Check for Tone B // Check for Tone B
@ -214,91 +191,66 @@ func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string,
d.bFreq = freq d.bFreq = freq
d.bStart = now d.bStart = now
} else if math.Abs(freq-d.bFreq) > 10.0 { } else if math.Abs(freq-d.bFreq) > 10.0 {
d.logger.Debug("Frequency differs from Tone B, resetting B",
"time", now.Format(time.RFC3339),
"freq", fmt.Sprintf("%.1f", freq),
"tone_b_freq", fmt.Sprintf("%.1f", d.bFreq))
d.bFreq = freq d.bFreq = freq
d.bAccumMs = 0 d.bAccumMs = 0
d.bStart = now d.bStart = now
} }
d.bAccumMs += int(hopDur.Milliseconds()) d.bAccumMs += int(hopDur.Milliseconds())
d.bEnd = now.Add(hopDur)
if d.bAccumMs >= d.minBms { if d.bAccumMs >= d.minBms {
event = "TWO_TONE_DETECTED" event = "TWO_TONE_DETECTED"
d.logger.Info("Two-tone detected", aDurMs := float64(d.aEnd.Sub(d.aStart).Milliseconds())
"time", now.Format(time.RFC3339), bDurMs := float64(d.bEnd.Sub(d.bStart).Milliseconds())
"tone_a_freq", fmt.Sprintf("%.1f", d.aFreq), return event, d.aFreq, aDurMs, d.bFreq, bDurMs, now
"tone_a_duration_ms", d.aAccumMs,
"tone_b_freq", fmt.Sprintf("%.1f", d.bFreq),
"tone_b_duration_ms", d.bAccumMs)
return event, d.aFreq, float64(d.aAccumMs), d.bFreq, float64(d.bAccumMs), now
} }
} }
} }
return "", 0, 0, 0, 0, time.Time{} return "", 0, 0, 0, 0, time.Time{}
} }
// reset reinitializes all internal state fields of the twoToneDetector.
func (d *twoToneDetector) reset() { func (d *twoToneDetector) reset() {
d.inA = false d.inA = false
d.aFreq = 0 d.aFreq = 0
d.aAccumMs = 0 d.aAccumMs = 0
d.aStart = time.Time{} d.aStart = time.Time{}
d.aEnd = time.Time{}
d.waitingB = false d.waitingB = false
d.bFreq = 0 d.bFreq = 0
d.bAccumMs = 0 d.bAccumMs = 0
d.bStart = time.Time{} d.bStart = time.Time{}
d.bEnd = time.Time{}
d.gapRemainMs = 0 d.gapRemainMs = 0
} }
func main() { func main() {
flag.Parse() flag.Parse()
// Initialize slog logger
logLevel := &slog.LevelVar{}
logLevel.Set(slog.LevelInfo)
if *verbose {
logLevel.Set(slog.LevelDebug)
}
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
Level: logLevel,
}))
if *wavFile == "" { if *wavFile == "" {
logger.Error("WAV file path is required", "flag", "-wav") log.Fatal("WAV file path is required (use -wav flag)")
os.Exit(1)
} }
file, err := os.Open(*wavFile) file, err := os.Open(*wavFile)
if err != nil { if err != nil {
logger.Error("Failed to open WAV file", "error", err) log.Fatalf("Failed to open WAV file: %v", err)
os.Exit(1)
} }
defer file.Close() defer file.Close()
decoder := wav.NewDecoder(file) decoder := wav.NewDecoder(file)
if !decoder.IsValidFile() { if !decoder.IsValidFile() {
logger.Error("Invalid WAV file") log.Fatal("Invalid WAV file")
os.Exit(1)
} }
if decoder.Format().SampleRate != 8000 || decoder.Format().NumChannels != 1 { if decoder.Format().SampleRate != 8000 || decoder.Format().NumChannels != 1 {
logger.Error("WAV file must be mono 8kHz", log.Fatalf("WAV file must be mono 8kHz, got %d Hz, %d channels",
"sample_rate", decoder.Format().SampleRate, decoder.Format().SampleRate, decoder.Format().NumChannels)
"channels", decoder.Format().NumChannels)
os.Exit(1)
} }
const fs = 8000 const fs = 8000
winN := int(float64(fs) * float64(*winMs) / 1000.0) winN := int(float64(fs) * float64(*winMs) / 1000.0)
hopN := int(float64(fs) * float64(*hopMs) / 1000.0) hopN := int(float64(fs) * float64(*hopMs) / 1000.0)
if winN <= 0 || hopN <= 0 || hopN > winN { if winN <= 0 || hopN <= 0 || hopN > winN {
logger.Error("Invalid window/hop parameters", log.Fatalf("Invalid window/hop: winN=%d, hopN=%d", winN, hopN)
"winN", winN,
"hopN", hopN)
os.Exit(1)
} }
det := newTwoToneDetector(fs, winN, hopN, *ratioThresh, *rmsThresh, *minAms, *minBms, *gapMaxMs, logger) det := newTwoToneDetector(fs, winN, hopN, *ratioThresh, *rmsThresh, *minAms, *minBms, *gapMaxMs)
buf := &audio.IntBuffer{ buf := &audio.IntBuffer{
Format: &audio.Format{SampleRate: fs, NumChannels: 1}, Format: &audio.Format{SampleRate: fs, NumChannels: 1},
@ -308,13 +260,11 @@ func main() {
sampleCount := 0 sampleCount := 0
startTime := time.Now() startTime := time.Now()
logger.Info("Processing WAV file") log.Println("Processing WAV file...")
for { for {
n, err := decoder.PCMBuffer(buf) n, err := decoder.PCMBuffer(buf)
if err != nil || n == 0 || len(buf.Data) == 0 { if err != nil || n == 0 || len(buf.Data) == 0 {
logger.Info("Finished processing", log.Printf("Finished processing %d samples (%.2f seconds)", sampleCount, float64(sampleCount)/float64(fs))
"samples", sampleCount,
"duration_sec", fmt.Sprintf("%.2f", float64(sampleCount)/float64(fs)))
break break
} }
@ -338,7 +288,6 @@ func main() {
} }
} }
// min returns the smaller of two integer values a and b.
func min(a, b int) int { func min(a, b int) int {
if a < b { if a < b {
return a return a