130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
package ui
|
|
|
|
import (
|
|
"math"
|
|
"math/rand/v2"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// benchSampleRate matches the default mixer sample rate used elsewhere in the package.
|
|
const benchSampleRate = 44100
|
|
|
|
// benchSamplesSine fills a buffer with a 440 Hz sine wave — a clean, single-tone
|
|
// workload that exercises the FFT without triggering any silence short-circuit.
|
|
func benchSamplesSine(n int) []float64 {
|
|
buf := make([]float64, n)
|
|
for i := range buf {
|
|
buf[i] = math.Sin(2 * math.Pi * 440 * float64(i) / float64(benchSampleRate))
|
|
}
|
|
return buf
|
|
}
|
|
|
|
// benchSamplesNoise fills a buffer with deterministic white noise — the worst
|
|
// case for per-band averaging (energy spread across every bin).
|
|
func benchSamplesNoise(n int) []float64 {
|
|
r := rand.New(rand.NewPCG(1, 2))
|
|
buf := make([]float64, n)
|
|
for i := range buf {
|
|
buf[i] = r.Float64()*2 - 1
|
|
}
|
|
return buf
|
|
}
|
|
|
|
// benchSamplesSilence returns a zeroed buffer — the common case between tracks
|
|
// or during pause, where ideally we'd skip most of the FFT pipeline.
|
|
func benchSamplesSilence(n int) []float64 {
|
|
return make([]float64, n)
|
|
}
|
|
|
|
func BenchmarkAnalyze(b *testing.B) {
|
|
spec := spectrumAnalysisSpec(DefaultSpectrumBands)
|
|
cases := []struct {
|
|
name string
|
|
samples []float64
|
|
}{
|
|
{"Sine440", benchSamplesSine(defaultFFTSize)},
|
|
{"Noise", benchSamplesNoise(defaultFFTSize)},
|
|
{"Silence", benchSamplesSilence(defaultFFTSize)},
|
|
}
|
|
for _, tc := range cases {
|
|
b.Run(tc.name, func(b *testing.B) {
|
|
v := NewVisualizer(benchSampleRate)
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
v.Analyze(tc.samples, spec)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// benchDriverModes covers the representative rendering shapes: pure spectrum
|
|
// bars, peak-cap animation, frame-driven animation, waveform oscilloscope.
|
|
var benchDriverModes = []VisMode{
|
|
VisBars,
|
|
VisBarsDot,
|
|
VisBarsOutline,
|
|
VisClassicPeak,
|
|
VisMatrix,
|
|
VisRain,
|
|
VisFlame,
|
|
VisLogo,
|
|
VisPulse,
|
|
VisWave,
|
|
VisScope,
|
|
}
|
|
|
|
func BenchmarkRender(b *testing.B) {
|
|
samples := benchSamplesSine(defaultFFTSize)
|
|
for _, mode := range benchDriverModes {
|
|
name := visModes[mode].name
|
|
b.Run(name, func(b *testing.B) {
|
|
v := NewVisualizer(benchSampleRate)
|
|
v.Mode = mode
|
|
v.Rows = 5
|
|
// Prime driver state and bands so Render reflects realistic output.
|
|
ctx := VisTickContext{
|
|
Now: time.Now(),
|
|
Playing: true,
|
|
Analyze: func(spec VisAnalysisSpec) []float64 {
|
|
return v.Analyze(samples, spec)
|
|
},
|
|
}
|
|
for range 4 {
|
|
v.Tick(ctx)
|
|
}
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
_ = v.Render()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkTickPipeline(b *testing.B) {
|
|
samples := benchSamplesSine(defaultFFTSize)
|
|
for _, mode := range benchDriverModes {
|
|
name := visModes[mode].name
|
|
b.Run(name, func(b *testing.B) {
|
|
v := NewVisualizer(benchSampleRate)
|
|
v.Mode = mode
|
|
v.Rows = 5
|
|
ctx := VisTickContext{
|
|
Now: time.Now(),
|
|
Playing: true,
|
|
Analyze: func(spec VisAnalysisSpec) []float64 {
|
|
return v.Analyze(samples, spec)
|
|
},
|
|
}
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
v.Tick(ctx)
|
|
_ = v.Render()
|
|
}
|
|
})
|
|
}
|
|
}
|