Refactor main.go to remove slog logger integration and replace with standard log package
This commit is contained in:
91
main.go
91
main.go
@ -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
|
||||||
|
Reference in New Issue
Block a user