WIP
parent
78c9070763
commit
d7a8d4ec74
|
@ -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)
|
||||
}
|
||||
*/
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Reference in New Issue