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 = softplus(xr.a*10) / 10 xr.b = softplus(xr.b*10) / 10 xr.a = float32(math.Log(float64(xr.a))) + 1 xr.b = float32(math.Log(float64(xr.b))) + 1 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} } xticks := func(r plotRange, yield func(pos float32, label string)) { yield(0, "") yield(1, "") } yticks := func(r plotRange, yield func(pos float32, label string)) { yield(-1, "") yield(0, "") } 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) } }