package gioui import ( "math" "gioui.org/layout" "gioui.org/unit" "github.com/vsariola/sointu/tracker" ) type ( SpectrumState struct { resolutionNumber *NumericUpDownState smoothingBtn *Clickable chnModeBtn *Clickable plot *Plot } ) func NewSpectrumState() *SpectrumState { return &SpectrumState{ plot: NewPlot(plotRange{0, 1}, plotRange{-1, 0}), resolutionNumber: NewNumericUpDownState(), smoothingBtn: new(Clickable), chnModeBtn: new(Clickable), } } func (s *SpectrumState) Layout(gtx C) D { s.Update(gtx) t := TrackerFromContext(gtx) leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout var chnModeTxt string = "???" switch tracker.SpecChnMode(t.Model.SpecAnChannelsInt().Value()) { case tracker.SpecChnModeCombine: chnModeTxt = "Sum" case tracker.SpecChnModeSeparate: chnModeTxt = "Separate" case tracker.SpecChnModeOff: chnModeTxt = "Off" } var smoothTxt string = "???" switch tracker.SpecSmoothing(t.Model.SpecAnSmoothing().Value()) { case tracker.SpecSmoothingSlow: smoothTxt = "Slow" case tracker.SpecSmoothingMedium: smoothTxt = "Medium" case tracker.SpecSmoothingFast: smoothTxt = "Fast" } resolution := NumUpDown(t.Model.SpecAnResolution(), t.Theme, s.resolutionNumber, "Resolution") chnModeBtn := Btn(t.Theme, &t.Theme.Button.Filled, s.chnModeBtn, chnModeTxt, "Channel mode") smoothBtn := Btn(t.Theme, &t.Theme.Button.Filled, s.smoothingBtn, smoothTxt, "Smoothing") numchns := 0 speclen := len(t.Model.Spectrum()[0]) if speclen > 0 { numchns = 1 if len(t.Model.Spectrum()[1]) == speclen { numchns = 2 } } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Flexed(1, func(gtx C) D { data := func(chn int, xr plotRange) (yr plotRange) { xr.a = float32(math.Exp2(float64(xr.a-1) * 8)) xr.b = float32(math.Exp2(float64(xr.b-1) * 8)) w1, f1 := math.Modf(float64(xr.a) * float64(speclen)) w2, f2 := math.Modf(float64(xr.b) * float64(speclen)) x1 := max(int(w1), 0) x2 := min(int(w2), speclen-1) if x1 > x2 { return plotRange{1, 0} } y1 := float32(math.Inf(-1)) y2 := float32(math.Inf(+1)) switch { case x2 <= x1+1 && x2 < speclen-1: // perform smoothstep interpolation when we are overlapping only a few bins l := t.Model.Spectrum()[chn][x1] r := t.Model.Spectrum()[chn][x1+1] y1 = smoothInterpolate(l, r, float32(f1)) l = t.Model.Spectrum()[chn][x2] r = t.Model.Spectrum()[chn][x2+1] y2 = smoothInterpolate(l, r, float32(f2)) y1, y2 = max(y1, y2), min(y1, y2) default: for i := x1; i <= x2; i++ { sample := t.Model.Spectrum()[chn][i] y1 = max(y1, sample) y2 = min(y2, sample) } } y1 = (y1 / 80) + 1 y2 = (y2 / 80) + 1 y1 = softplus(y1*10) / 10 y2 = softplus(y2*10) / 10 return plotRange{-y1, -y2} } type pair struct { freq float64 label string } xticks := func(r plotRange, yield func(pos float32, label string)) { for _, p := range []pair{ {freq: 10, label: "10 Hz"}, {freq: 20, label: "20 Hz"}, {freq: 50, label: "50 Hz"}, {freq: 100, label: "100 Hz"}, {freq: 200, label: "200 Hz"}, {freq: 500, label: "500 Hz"}, {freq: 1e3, label: "1 kHz"}, {freq: 2e3, label: "2 kHz"}, {freq: 5e3, label: "5 kHz"}, {freq: 1e4, label: "10 kHz"}, {freq: 2e4, label: "20 kHz"}, } { x := float32(math.Log2((p.freq/22050))/8 + 1) if x >= r.a && x <= r.b { yield(x, p.label) } } } yticks := func(r plotRange, yield func(pos float32, label string)) { yield(-1, "0 dB") yield(0, "-Inf dB") } return s.plot.Layout(gtx, data, xticks, yticks, 0, numchns) }), layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(leftSpacer), layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }), layout.Rigid(chnModeBtn.Layout), layout.Rigid(smoothBtn.Layout), layout.Rigid(resolution.Layout), layout.Rigid(rightSpacer), ) }), ) } func softplus(f float32) float32 { return float32(math.Log(1 + math.Exp(float64(f)))) } func smoothInterpolate(a, b float32, t float32) float32 { t = t * t * (3 - 2*t) return (1-t)*a + t*b } func (s *SpectrumState) Update(gtx C) { t := TrackerFromContext(gtx) for s.chnModeBtn.Clicked(gtx) { t.Model.SpecAnChannelsInt().SetValue((t.SpecAnChannelsInt().Value() + 1) % int(tracker.NumSpecChnModes)) } for s.smoothingBtn.Clicked(gtx) { r := t.Model.SpecAnSmoothing().Range() t.Model.SpecAnSmoothing().SetValue((t.SpecAnSmoothing().Value()+1)%(r.Max-r.Min+1) + r.Min) } }