Refactor main.go for improved readability and add detailed comments for functions
This commit is contained in:
62
main.go
62
main.go
@ -33,6 +33,16 @@ type goertzel struct {
|
|||||||
coeff float64
|
coeff float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newGoertzel initializes and returns a new instance of the Goertzel algorithm for detecting a specific target frequency.
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// targetHz - the target frequency in Hertz to detect.
|
||||||
|
// fs - the sampling rate in Hertz.
|
||||||
|
// N - the number of samples to process.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
//
|
||||||
|
// A pointer to a goertzel struct configured for the specified frequency and sample rate.
|
||||||
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)
|
||||||
@ -41,6 +51,9 @@ 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.
|
||||||
|
// It processes the input slice x of length g.N and returns the squared magnitude of the frequency component
|
||||||
|
// specified by g.k. The function is typically used for efficient detection of specific frequencies in a signal.
|
||||||
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++ {
|
||||||
@ -54,6 +67,10 @@ 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.
|
||||||
|
// The Hann window is commonly used in signal processing to reduce spectral leakage
|
||||||
|
// by tapering the beginning and end of the signal to zero.
|
||||||
|
// The function modifies the input slice directly.
|
||||||
func windowHann(x []float64) {
|
func windowHann(x []float64) {
|
||||||
n := float64(len(x))
|
n := float64(len(x))
|
||||||
for i := range x {
|
for i := range x {
|
||||||
@ -61,6 +78,9 @@ func windowHann(x []float64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pcmToFloat converts a slice of 16-bit PCM audio samples to a slice of float64 values.
|
||||||
|
// It processes up to N samples from the input buffer and returns the converted values.
|
||||||
|
// If the input buffer has fewer than N samples, only the available samples are converted.
|
||||||
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++ {
|
||||||
@ -69,6 +89,9 @@ 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.
|
||||||
|
// It returns the RMS as a float64, which is a measure of the signal's amplitude.
|
||||||
|
// If the input slice is empty, it returns 0.
|
||||||
func rmsPCM(buf []int16) float64 {
|
func rmsPCM(buf []int16) float64 {
|
||||||
var s float64
|
var s float64
|
||||||
for _, v := range buf {
|
for _, v := range buf {
|
||||||
@ -104,6 +127,23 @@ type twoToneDetector struct {
|
|||||||
gapRemainMs int
|
gapRemainMs int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newTwoToneDetector creates and initializes a twoToneDetector instance with the specified parameters.
|
||||||
|
// It sets up a bank of Goertzel filters for detecting tones in the frequency range 300–3000 Hz (in 10 Hz steps).
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// fs - Sample rate in Hz.
|
||||||
|
// winN - Window size (number of samples per analysis window).
|
||||||
|
// hopN - Hop size (number of samples to advance per analysis).
|
||||||
|
// ratioThresh- Threshold for the ratio used in tone detection.
|
||||||
|
// rmsThresh - RMS threshold for signal energy.
|
||||||
|
// minAms - Minimum duration of a detected tone in milliseconds.
|
||||||
|
// minBms - Minimum duration of a break between tones in milliseconds.
|
||||||
|
// gapMaxMs - Maximum allowed gap between tones in milliseconds.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
//
|
||||||
|
// Pointer to an initialized twoToneDetector.
|
||||||
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) *twoToneDetector {
|
||||||
// Frequency range: 300–3000 Hz, 10 Hz steps
|
// Frequency range: 300–3000 Hz, 10 Hz steps
|
||||||
freqs := make([]float64, 0)
|
freqs := make([]float64, 0)
|
||||||
@ -128,6 +168,25 @@ func newTwoToneDetector(fs, winN, hopN int, ratioThresh, rmsThresh float64, minA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stepWindow processes a window of PCM audio samples to detect a two-tone event.
|
||||||
|
// It applies a Hann window, computes the RMS, and searches for the strongest frequency.
|
||||||
|
// The function tracks the presence and duration of two distinct tones (A and B) separated by a gap.
|
||||||
|
// If both tones are detected with sufficient duration and within specified thresholds, it returns
|
||||||
|
// an event string ("TWO_TONE_DETECTED") along with the frequencies and durations of tones A and B.
|
||||||
|
// If detection criteria are not met, it resets the detector state and returns zero values.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// pcms []int16 - Slice of PCM audio samples for the current window.
|
||||||
|
// t0 time.Time - Timestamp corresponding to the start of the window.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
//
|
||||||
|
// event string - Event name if two-tone detected, otherwise empty string.
|
||||||
|
// aFreq float64 - Frequency of tone A (Hz).
|
||||||
|
// aDur float64 - Duration of tone A (milliseconds).
|
||||||
|
// bFreq float64 - Frequency of tone B (Hz).
|
||||||
|
// bDur float64 - Duration of tone B (milliseconds).
|
||||||
func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string, aFreq, aDur, bFreq, bDur float64) {
|
func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string, aFreq, aDur, bFreq, bDur float64) {
|
||||||
xi := pcmToFloat(pcms, d.winN)
|
xi := pcmToFloat(pcms, d.winN)
|
||||||
windowHann(xi)
|
windowHann(xi)
|
||||||
@ -206,6 +265,8 @@ func (d *twoToneDetector) stepWindow(pcms []int16, t0 time.Time) (event string,
|
|||||||
return "", 0, 0, 0, 0
|
return "", 0, 0, 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset reinitializes all internal state fields of the twoToneDetector to their default values.
|
||||||
|
// This includes clearing detection flags, frequencies, accumulated durations, start times, and gap timers.
|
||||||
func (d *twoToneDetector) reset() {
|
func (d *twoToneDetector) reset() {
|
||||||
d.inA = false
|
d.inA = false
|
||||||
d.aFreq = 0
|
d.aFreq = 0
|
||||||
@ -284,6 +345,7 @@ 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