WIP: peak finding

master
J. David Lee 2019-12-08 20:07:15 +01:00
parent 90fbc089f4
commit 72af0bdd82
2 changed files with 64 additions and 82 deletions

View File

@ -2,8 +2,11 @@ package extractfreq
import "math" import "math"
// A-weight filter is supposed to correspond to how a human would perceive
// loudness.
func AWeight(f float64) float64 { func AWeight(f float64) float64 {
return (148693636.0 * f * f * f * f) / ((424.36 + f*f) * math.Sqrt( return (148693636.0 * f * f * f * f) /
(f*f+11599.29)*(f*f+544496.41)) * ((424.36 + f*f) *
(f*f + 148693636.0)) math.Sqrt((f*f+11599.29)*(f*f+544496.41)) *
(f*f + 148693636.0))
} }

View File

@ -16,9 +16,12 @@ import (
// Steps: // Steps:
// //
// * Create amplitude spectrum (abs of FFT). // * Create amplitude spectrum (abs of real FFT).
// * Multiply by A-weight curve // * Multiply by A-weight curve
// * Take log10 of result // * Find peaks
// * Sort peaks by amplitude
// * Use strongest peak to estimate fFundamental?
// *
func PlotFFT(path string, nSamples int, fFundamental float64) { func PlotFFT(path string, nSamples int, fFundamental float64) {
L, R, err := flac.Load(path) L, R, err := flac.Load(path)
if err != nil { if err != nil {
@ -34,98 +37,96 @@ func PlotFFT(path string, nSamples int, fFundamental float64) {
data[i] = float64(L[i]) + float64(R[i]) data[i] = float64(L[i]) + float64(R[i])
} }
// Make initial fft.
fft := fourier.NewFFT(nSamples) fft := fourier.NewFFT(nSamples)
cCoeffs := fft.Coefficients(nil, data) cCoeffs := fft.Coefficients(nil, data)
amps := make([]float64, len(cCoeffs)) amps := make([]float64, len(cCoeffs))
freqs := make([]float64, len(cCoeffs)) freqs := make([]float64, len(cCoeffs))
for i := range freqs { for i := range freqs {
freqs[i] = fft.Freq(i) * 48000 freqs[i] = fft.Freq(i) * 48000
if freqs[i] < 20 { amps[i] = cmplx.Abs(cCoeffs[i])
amps[i] = 0
} else {
amps[i] = math.Log10(cmplx.Abs(cCoeffs[i]) * AWeight(freqs[i]))
}
} }
// Normalize coefficients. // Trim to frequencies of interest.
//for i := range amps { deltaF := freqs[1] - freqs[0]
//amps[i] /= ampMax idx0 := int(20 / deltaF)
//} idx1 := int(16000 / deltaF)
freqs = freqs[idx0:idx1]
amps = amps[idx0:idx1]
// Find highest N peaks and sort by size. // Multiply by A-weight function.
for i := range amps {
amps[i] *= AWeight(freqs[i])
}
/* // Find peaks.
// Use a moving window to search for local peaks. dFreq := freqs[1] - freqs[0]
hws := []*HarmonicWindow{} halfWinSize := int(math.Round((fFundamental / dFreq) / 3))
peakInds, peakAmps := getPeaks(halfWinSize, amps)
// Bypass low-frequency samples. peakInds, peakAmps = sortPeaks(peakInds, peakAmps)
i := 0
for i < len(freqs) && freqs[i] < 24 {
i++
}
iCenter := i
for iC{
}
*/
// Find frequency window around midi note.
p, err := plot.New() p, err := plot.New()
if err != nil { if err != nil {
panic(err) panic(err)
} }
fMin := float64(20)
fMax := float64(16000)
log.Printf("Making points...") log.Printf("Making points...")
pts := make(plotter.XYs, 0, len(amps)) pts := make(plotter.XYs, 0, len(amps))
for i := range amps { for i := range amps {
if freqs[i] < 20 || freqs[i] > 16000 { if freqs[i] > fMin && freqs[i] < fMax {
continue pts = append(pts, plotter.XY{
X: freqs[i],
Y: amps[i],
})
}
}
pts2 := make(plotter.XYs, 0, len(amps))
for i := range peakInds {
if freqs[peakInds[i]] > fMin && freqs[peakInds[i]] < fMax {
pts2 = append(pts2, plotter.XY{
X: freqs[peakInds[i]],
Y: peakAmps[i],
})
} }
pts = append(pts, plotter.XY{
X: freqs[i],
Y: amps[i],
})
} }
log.Printf("Plotting...") log.Printf("Plotting...")
if err = plotutil.AddLinePoints(p, "fft", pts); err != nil { if err = plotutil.AddScatters(p, "fft", pts, "max", pts2); err != nil {
panic(err) panic(err)
} }
p.Add(plotter.NewGrid())
//p.Y.Scale = plot.LogScale{}
p.X.Scale = plot.LogScale{}
log.Printf("Saving...") log.Printf("Saving...")
if err := p.Save(16*vg.Inch, 8*vg.Inch, "fft.png"); err != nil { if err := p.Save(16*vg.Inch, 8*vg.Inch, "fft.png"); err != nil {
panic(err) panic(err)
} }
harmonics := []float64{0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} for i, idx := range peakInds {
hws := []*HarmonicWindow{} //amp := peakAmps[i]
for _, harmonic := range harmonics {
hw := NewHarmonicWindow( // Make window.
fFundamental, ampsWin := amps[idx-halfWinSize : idx+halfWinSize]
harmonic, freqWin := freqs[idx-halfWinSize : idx+halfWinSize]
amps,
freqs)
log.Printf("Making points...")
pts := make(plotter.XYs, 0, 2*halfWinSize)
for i := range ampsWin {
pts = append(pts, plotter.XY{
X: freqWin[i],
Y: ampsWin[i],
})
}
log.Printf("Plotting...")
p, err := plot.New() p, err := plot.New()
if err != nil { if err != nil {
panic(err) panic(err)
} }
log.Printf("Making points...")
pts := make(plotter.XYs, 0, len(hw.Amps))
for i := range hw.Amps {
pts = append(pts, plotter.XY{
X: hw.Freqs[i],
Y: hw.Amps[i],
})
}
log.Printf("Plotting...")
if err = plotutil.AddLines(p, "fft", pts); err != nil { if err = plotutil.AddLines(p, "fft", pts); err != nil {
panic(err) panic(err)
} }
@ -134,31 +135,9 @@ func PlotFFT(path string, nSamples int, fFundamental float64) {
//p.X.Scale = plot.LogScale{} //p.X.Scale = plot.LogScale{}
log.Printf("Saving...") log.Printf("Saving...")
filename := fmt.Sprintf("plot-%03.2f.png", harmonic) filename := fmt.Sprintf("plot-%03d.png", i)
if err := p.Save(8*vg.Inch, 8*vg.Inch, filename); err != nil { if err := p.Save(8*vg.Inch, 8*vg.Inch, filename); err != nil {
panic(err) panic(err)
} }
hws = append(hws, hw)
}
pts = make(plotter.XYs, len(hws))
for i, hw := range hws[:11] {
pts[i].X = hw.Harmonic
pts[i].Y = hw.FreqPeakInterp
}
p, err = plot.New()
if err != nil {
panic(err)
}
if err = plotutil.AddScatters(p, "", pts); err != nil {
panic(err)
}
log.Printf("Saving...")
if err := p.Save(8*vg.Inch, 8*vg.Inch, "harmonics.png"); err != nil {
panic(err)
} }
} }