diff --git a/main.go b/main.go index 42edb07..6646998 100644 --- a/main.go +++ b/main.go @@ -205,7 +205,7 @@ func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string, now := t0 if r < d.rmsThresh { - slog.Debug("Window at %s: RMS %.2f below threshold %.2f, resetting", now.Format(time.RFC3339), r, d.rmsThresh) + slog.Debug("Window RMS below threshold, resetting", "at", now.Format(time.RFC3339), "RMS", r, "threshold", d.rmsThresh) d.reset() return "", 0, 0, 0, 0 } @@ -222,7 +222,7 @@ func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string, } ratio := bestPow / (total + 1e-12) if ratio < d.ratioThresh { - slog.Debug("Window at %s: ratio %.3f below threshold %.3f, resetting", now.Format(time.RFC3339), ratio, d.ratioThresh) + slog.Debug("Window ratio below threshold, resetting", "at", now.Format(time.RFC3339), "ratio", ratio, "threshold", d.ratioThresh) d.reset() return "", 0, 0, 0, 0 } @@ -244,7 +244,7 @@ func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string, d.gapRemainMs = d.gapMaxMs } } else { - slog.Debug("Window at %s: freq %.1f Hz differs from Tone A %.1f Hz, resetting", now.Format(time.RFC3339), freq, d.aFreq) + slog.Debug("Window freq differs from Tone A, resetting A", "at", now.Format(time.RFC3339), "freq (Hz)", freq, "Tone A (Hz)", d.aFreq) d.reset() } } else if d.waitingB { @@ -258,7 +258,7 @@ func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string, d.bFreq = freq d.bStart = now } else if math.Abs(freq-d.bFreq) > 10.0 { - slog.Debug("Window at %s: freq %.1f Hz differs from Tone B %.1f Hz, resetting B", now.Format(time.RFC3339), freq, d.bFreq) + slog.Debug("Window freq differs from Tone B, resetting B", "at", now.Format(time.RFC3339), "freq (Hz)", freq, "Tone B (Hz)", d.bFreq) d.bFreq = freq d.bAccumMs = 0 d.bStart = now @@ -426,8 +426,7 @@ func main() { if det.recording { if !det.bEnd.IsZero() && !chunkTime.Before(det.bEnd.Add(100*time.Millisecond)) { r := rmsPCM(chunk) - slog.Info("Chunk at %s: RMS=%.2f, silenceThresh=%.2f, silenceAccum=%d ms, recordBuf=%d samples", - chunkTime.Format(time.RFC3339), r, *silenceThresh, det.silenceAccumMs, len(det.recordBuf)) + slog.Info("chunk", "at", chunkTime.Format(time.RFC3339), "RMS", r, "silenceThresh", *silenceThresh, "silenceAccum (ms)", det.silenceAccumMs, "record buffer (samples)", len(det.recordBuf)) if r < *silenceThresh { det.silenceAccumMs += int(time.Duration(len(chunk)) * time.Second / time.Duration(fs) / time.Millisecond) if det.silenceAccumMs >= *silenceDurMs && len(det.recordBuf) > 0 { diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..4c5df5b --- /dev/null +++ b/main_test.go @@ -0,0 +1,100 @@ +package main + +import ( + "math" + "testing" + "time" +) + +// Test decodeMuLaw basic functionality +func TestDecodeMuLaw(t *testing.T) { + data := []byte{0x00, 0xFF, 0x7F, 0x80} + pcm, err := decodeMuLaw(data) + if err != nil { + t.Fatalf("decodeMuLaw returned error: %v", err) + } + if len(pcm) != len(data) { + t.Errorf("Expected %d samples, got %d", len(data), len(pcm)) + } +} + +// Test Goertzel power calculation +func TestGoertzelPower(t *testing.T) { + fs := 8000.0 + N := 100 + freq := 1000.0 + g := newGoertzel(freq, fs, N) + signal := make([]float64, N) + for i := range signal { + signal[i] = math.Sin(2 * math.Pi * freq * float64(i) / fs) + } + power := g.Power(signal) + if power <= 0 { + t.Errorf("Expected positive power, got %f", power) + } +} + +// Test windowHann modifies slice +func TestWindowHann(t *testing.T) { + x := []float64{1, 1, 1, 1} + windowHann(x) + for i, v := range x { + if v == 1 { + t.Errorf("windowHann did not modify value at index %d", i) + } + } +} + +// Test rmsPCM returns correct RMS +func TestRmsPCM(t *testing.T) { + buf := []int16{1000, -1000, 1000, -1000} + rms := rmsPCM(buf) + if rms < 900 || rms > 1100 { + t.Errorf("Unexpected RMS value: %f", rms) + } +} + +// Test twoToneDetector reset +func TestTwoToneDetectorReset(t *testing.T) { + d := newTwoToneDetector(8000, 100, 50, 0.65, 300, 1000, 3000, 5000) + d.inA = true + d.aFreq = 1000 + d.reset() + if d.inA || d.aFreq != 0 { + t.Errorf("reset did not clear state") + } +} + +// Test min function +func TestMin(t *testing.T) { + if min(1, 2) != 1 { + t.Errorf("min(1,2) should be 1") + } + if min(2, 1) != 1 { + t.Errorf("min(2,1) should be 1") + } + if min(2, 2) != 2 { + t.Errorf("min(2,2) should be 2") + } +} + +// Test saveToWAV (does not check file output, just runs function) +func TestSaveToWAV(t *testing.T) { + data := make([]int16, 8000) + for i := range data { + data[i] = int16(i % 32768) + } + saveToWAV(data, 8000) + // No assertion, just ensure no panic +} + +// Test stepWindow returns expected values for silence +func TestStepWindowSilence(t *testing.T) { + d := newTwoToneDetector(8000, 100, 50, 0.65, 300, 1000, 3000, 5000) + buf := make([]int16, 100) + t0 := time.Now() + event, aFreq, aDur, bFreq, bDur := d.stepWindow(buf, t0) + if event != "" || aFreq != 0 || aDur != 0 || bFreq != 0 || bDur != 0 { + t.Errorf("stepWindow should return zero values for silence") + } +}