master
J. David Lee 2019-12-05 21:37:16 +01:00
parent 78c9070763
commit d7a8d4ec74
3 changed files with 93 additions and 49 deletions

19
lib/extractfreq/freq.go Normal file
View File

@ -0,0 +1,19 @@
package extractfreq
type ExtractFreqArgs struct {
Data []float64
ApproxFreq float64
Log bool
LogPrefix string
LogDir string
}
/*
func ExtractFreq(args ExtractFreqArgs) float64 {
fft := fourier.NewFFT(len(args.Data))
cCoeffs := fft.Coefficients(nil, args.Data)
amps := make([]float64, len(cCoeffs))
freqs := make([]float64, len(cCoeffs))
ampMax := float64(0)
}
*/

View File

@ -1,17 +1,18 @@
package extractfreq
import (
"log"
"math"
"git.crumpington.com/public/jlaudio/lib/util"
"gonum.org/v1/gonum/floats"
"gonum.org/v1/gonum/stat"
)
type HarmonicWindow struct {
TargetFreq float64
Harmonic float64 // 0.25, 0.5, 1, 2, 3, ...
Amps []float64
Freqs []float64
Idx0 int
IdxPeak int
IdxPeakInterp float64
FreqPeakInterp float64
@ -20,37 +21,47 @@ type HarmonicWindow struct {
AmpStd float64
}
// Construct a window around the harmonic peak with a width of 100 cents.
func NewHarmonicWindow(
amps []float64,
freqs []float64,
targetFreq float64,
idxPeak int,
fFundamental float64,
harmonic float64,
rawAmps []float64,
rawFreqs []float64,
) *HarmonicWindow {
deltaF := freqs[1] - freqs[0] // Size of frequency bins.
fCenter := freqs[idxPeak] // Center frequency.
deltaF := rawFreqs[1] - rawFreqs[0]
// Create a window approximately 200 cents in width.
deltaIdx := int(math.Round(deltaF / (100 * util.CentDeltaFreq(fCenter))))
idxMin := idxPeak - deltaIdx
idxMax := idxPeak + deltaIdx
// TODO: At higher harmonic numbers, the harmonics are going to be spaced
// more cloesly together. That means that the window size must shrink.
winSizeCents := float64(150)
if idxMin < 0 {
idxMin = 0
// To start, we create a window around the approximate harmonic location.
idxPeak := int((fFundamental * harmonic) / deltaF)
winSize := int(
math.Round(
winSizeCents * util.CentDeltaFreq(fFundamental*harmonic) /
deltaF))
amps := rawAmps[idxPeak-winSize : idxPeak+winSize]
freqs := rawFreqs[idxPeak-winSize : idxPeak+winSize]
// Next, we refine the window be centering it on the actual peak.
idxPeak += (floats.MaxIdx(amps) - winSize)
amps = rawAmps[idxPeak-winSize : idxPeak+winSize]
freqs = rawFreqs[idxPeak-winSize : idxPeak+winSize]
idxPeak = winSize
// Next we compute statistical properties to determine if this peak is worth
// using.
mean, std := stat.MeanStdDev(amps, nil)
ampPeak := amps[idxPeak]
log.Printf("%0.2f: %0.4f", harmonic, (ampPeak-mean)/std)
return &HarmonicWindow{
Harmonic: harmonic,
Amps: amps,
Freqs: freqs,
IdxPeak: idxPeak,
AmpPeak: ampPeak,
AmpMean: mean,
AmpStd: std,
}
if idxMax > len(freqs) {
idxMax = len(freqs)
}
hw := &HarmonicWindow{
TargetFreq: targetFreq,
Amps: amps[idxMin:idxMax],
Freqs: freqs[idxMin:idxMax],
IdxPeak: idxPeak,
AmpPeak: amps[idxPeak],
}
hw.AmpMean, hw.AmpStd = stat.MeanStdDev(hw.Amps, nil)
return hw
}

View File

@ -1,6 +1,7 @@
package extractfreq
import (
"fmt"
"log"
"math/cmplx"
@ -62,28 +63,41 @@ func PlotFFT(path string, nSamples int, targetFreq float64) {
*/
// Find frequency window around midi note.
p, err := plot.New()
if err != nil {
panic(err)
}
harmonics := []float64{0.25, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
for _, harmonic := range harmonics {
log.Printf("Making points...")
pts := make(plotter.XYs, len(amps))
for i := range amps {
pts[i].X = freqs[i]
pts[i].Y = amps[i]
}
hw := NewHarmonicWindow(
targetFreq,
harmonic,
amps,
freqs)
log.Printf("Plotting...")
if err = plotutil.AddLines(p, "fft", pts); err != nil {
panic(err)
}
p, err := plot.New()
if err != nil {
panic(err)
}
p.Y.Scale = plot.LogScale{}
//p.X.Scale = plot.LogScale{}
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("Saving...")
if err := p.Save(8*vg.Inch, 8*vg.Inch, "plot.png"); err != nil {
panic(err)
log.Printf("Plotting...")
if err = plotutil.AddLines(p, "fft", pts); err != nil {
panic(err)
}
//p.Y.Scale = plot.LogScale{}
//p.X.Scale = plot.LogScale{}
log.Printf("Saving...")
filename := fmt.Sprintf("plot-%03.2f.png", harmonic)
if err := p.Save(8*vg.Inch, 8*vg.Inch, filename); err != nil {
panic(err)
}
}
}