Created
January 17, 2018 07:37
-
-
Save broady/a42e8055517549fa7c39846b30aea4db to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"image" | |
"image/color" | |
"image/draw" | |
"log" | |
"math" | |
"sync" | |
"github.com/gordonklaus/portaudio" | |
"github.com/mjibson/go-dsp/spectral" | |
"golang.org/x/exp/shiny/driver" | |
"golang.org/x/exp/shiny/screen" | |
"golang.org/x/mobile/event/lifecycle" | |
"golang.org/x/mobile/event/paint" | |
) | |
func check(err error) { | |
if err != nil { | |
panic(err) | |
} | |
} | |
func lerp(min, max float64, val float64) float64 { | |
if val < min { | |
return 0 | |
} | |
if val > max { | |
return 1 | |
} | |
return (val - min) / (max - min) | |
} | |
func freq2Bin(freq float64, sampleRate float64, fftSize float64) int { | |
return int(freq / (sampleRate / fftSize)) | |
} | |
func bin2Freq(bin int, sampleRate float64, fftSize float64) float64 { | |
return float64(bin) * (sampleRate / fftSize) | |
} | |
var ( | |
valsMu sync.Mutex | |
vals []float64 | |
) | |
func main() { | |
go analyze() | |
driver.Main(func(s screen.Screen) { | |
w, err := s.NewWindow(&screen.NewWindowOptions{ | |
Width: 256 * 3, | |
Height: 256, | |
Title: "Viz", | |
}) | |
check(err) | |
for { | |
switch e := w.NextEvent().(type) { | |
case lifecycle.Event: | |
if e.To == lifecycle.StageDead { | |
return | |
} | |
case paint.Event: | |
valsMu.Lock() | |
for i := range vals { | |
v := uint8((1 - vals[i]) * 255) | |
w.Fill(image.Rectangle{Min: image.Point{256 * i, 0}, Max: image.Point{256 + 256*i, 256}}, color.RGBA{v, v, v, 1}, draw.Src) | |
} | |
valsMu.Unlock() | |
w.Publish() | |
w.Send(e) | |
case error: | |
log.Print(e) | |
} | |
} | |
}) | |
} | |
func analyze() { | |
if err := portaudio.Initialize(); err != nil { | |
log.Fatal(err) | |
} | |
defaultOutput, _ := portaudio.DefaultInputDevice() | |
log.Print("in from ", defaultOutput.Name) | |
in := make([]int32, 2048) | |
inF := make([]float64, 2048) | |
f2B := func(freq float64) int { | |
return freq2Bin(freq, 44100, float64(len(in))) | |
} | |
buckets := []struct { | |
Min, Max int | |
}{ | |
{ | |
Min: f2B(25), Max: f2B(150), | |
}, | |
{ | |
Min: f2B(150), Max: f2B(450), | |
}, | |
{ | |
Min: f2B(450), Max: f2B(700), | |
}, | |
} | |
thresholds := make([]float64, len(buckets)) | |
tmpVals := make([]float64, len(buckets)) // temp buffer | |
prevPxx := make([]float64, len(in)) | |
valsMu.Lock() | |
vals = make([]float64, len(buckets)) | |
valsMu.Unlock() | |
stream, err := portaudio.OpenDefaultStream(1, 0, 41000, len(in), in) | |
check(err) | |
check(stream.Start()) | |
for { | |
check(stream.Read()) | |
for i := range in { | |
inF[i] = float64(in[i]) | |
} | |
pxx, _ := spectral.Pwelch(inF, float64(41000), &spectral.PwelchOptions{ | |
NFFT: len(in), | |
}) | |
const smoothing = .2 | |
// smooth with previous | |
for i := range pxx { | |
pxx[i] = prevPxx[i]*smoothing + pxx[i]*(1-smoothing) | |
} | |
prevPxx = pxx | |
valsMu.Lock() | |
for i := range vals { | |
min := buckets[i].Min | |
max := buckets[i].Max + 1 | |
tmpVals[i] = 0 | |
for n := min; n < max; n++ { | |
tmpVals[i] += lerp(30, 200, math.Log10(pxx[n])*10) // db min/max | |
} | |
tmpVals[i] /= float64(max - min) | |
if tmpVals[i] > thresholds[i] { | |
thresholds[i] = tmpVals[i] | |
// on beat | |
vals[i] = tmpVals[i] | |
} else { | |
vals[i] = 0 | |
} | |
} | |
valsMu.Unlock() | |
for i := range thresholds { | |
thresholds[i] *= .995 // decay | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment