This commit is contained in:
5684185+vsariola@users.noreply.github.com
2026-01-02 02:03:05 +02:00
parent fcb9a06249
commit 06a1fb6b52
5 changed files with 145 additions and 38 deletions

View File

@ -44,6 +44,7 @@ func Scope(th *Theme, m *tracker.ScopeModel, st *OscilloscopeState) Oscilloscope
}
func (s *Oscilloscope) Layout(gtx C) D {
t := TrackerFromContext(gtx)
leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
@ -58,11 +59,11 @@ func (s *Oscilloscope) Layout(gtx C) D {
w := s.Model.Waveform()
cx := float32(w.Cursor) / float32(len(w.Buffer))
data := func(chn int, xr plotRange) (yr plotRange) {
data := func(chn int, xr plotRange) (yr plotRange, ok bool) {
x1 := max(int(xr.a*float32(len(w.Buffer))), 0)
x2 := min(int(xr.b*float32(len(w.Buffer))), len(w.Buffer)-1)
if x1 > x2 {
return plotRange{1, 0}
return plotRange{}, false
}
y1 := float32(math.Inf(-1))
y2 := float32(math.Inf(+1))
@ -71,18 +72,31 @@ func (s *Oscilloscope) Layout(gtx C) D {
y1 = max(y1, sample)
y2 = min(y2, sample)
}
return plotRange{-y1, -y2}
return plotRange{-y1, -y2}, true
}
xticks := func(r plotRange, yield func(pos float32, label string)) {
l := s.Model.LengthInBeats().Value()
rpb := max(t.Model.RowsPerBeat().Value(), 1)
xticks := func(r plotRange, count int, yield func(pos float32, label string)) {
l := s.Model.LengthInBeats().Value() * rpb
a := max(int(math.Ceil(float64(r.a*float32(l)))), 0)
b := min(int(math.Floor(float64(r.b*float32(l)))), l)
for i := a; i <= b; i++ {
yield(float32(i)/float32(l), strconv.Itoa(i))
step := 1
n := rpb
for (b-a+1)/step > count {
step *= n
n = 2
}
a = (a / step) * step
for i := a; i <= b; i += step {
if i%rpb == 0 {
beat := i / rpb
yield(float32(i)/float32(l), strconv.Itoa(beat))
} else {
yield(float32(i)/float32(l), "")
}
}
}
yticks := func(r plotRange, yield func(pos float32, label string)) {
yticks := func(r plotRange, count int, yield func(pos float32, label string)) {
yield(-1, "")
yield(1, "")
}

View File

@ -11,6 +11,7 @@ import (
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
)
type (
@ -25,14 +26,15 @@ type (
}
PlotStyle struct {
CurveColors [2]color.NRGBA `yaml:",flow"`
CurveColors [3]color.NRGBA `yaml:",flow"`
LimitColor color.NRGBA `yaml:",flow"`
CursorColor color.NRGBA `yaml:",flow"`
Ticks LabelStyle
DpPerTick unit.Dp
}
PlotDataFunc func(chn int, xr plotRange) (yr plotRange)
PlotTickFunc func(r plotRange, yield func(pos float32, label string))
PlotDataFunc func(chn int, xr plotRange) (yr plotRange, ok bool)
PlotTickFunc func(r plotRange, num int, yield func(pos float32, label string))
plotRange struct{ a, b float32 }
plotRel float32
plotPx int
@ -61,8 +63,8 @@ func (p *Plot) Layout(gtx C, data PlotDataFunc, xticks, yticks PlotTickFunc, cur
ylim := p.ylim()
// draw tick marks
xticks(xlim, func(x float32, txt string) {
numxticks := s.X / gtx.Dp(style.DpPerTick)
xticks(xlim, numxticks, func(x float32, txt string) {
paint.ColorOp{Color: style.LimitColor}.Add(gtx.Ops)
sx := plotPx(s.X).toScreen(xlim.toRelative(x))
fillRect(gtx, clip.Rect{Min: image.Pt(sx, 0), Max: image.Pt(sx+1, s.Y)})
@ -70,7 +72,8 @@ func (p *Plot) Layout(gtx C, data PlotDataFunc, xticks, yticks PlotTickFunc, cur
Label(t.Theme, &t.Theme.Plot.Ticks, txt).Layout(gtx)
})
yticks(ylim, func(y float32, txt string) {
numyticks := s.Y / gtx.Dp(style.DpPerTick)
yticks(ylim, numyticks, func(y float32, txt string) {
paint.ColorOp{Color: style.LimitColor}.Add(gtx.Ops)
sy := plotPx(s.Y).toScreen(ylim.toRelative(y))
fillRect(gtx, clip.Rect{Min: image.Pt(0, sy), Max: image.Pt(s.X, sy+1)})
@ -91,13 +94,13 @@ func (p *Plot) Layout(gtx C, data PlotDataFunc, xticks, yticks PlotTickFunc, cur
// left and right is the sample range covered by the pixel
left := right
right = xlim.fromRelative(plotPx(s.X).fromScreen(sx + 1))
yr := data(chn, plotRange{left, right})
if yr.b < yr.a {
yr, ok := data(chn, plotRange{left, right})
if !ok {
continue
}
y1 := plotPx(s.Y).toScreen(ylim.toRelative(yr.a))
y2 := plotPx(s.Y).toScreen(ylim.toRelative(yr.b))
fillRect(gtx, clip.Rect{Min: image.Pt(sx, y1), Max: image.Pt(sx+1, y2+1)})
fillRect(gtx, clip.Rect{Min: image.Pt(sx, min(y1, y2)), Max: image.Pt(sx+1, max(y1, y2)+1)})
}
}
return D{Size: s}

View File

@ -2,6 +2,7 @@ package gioui
import (
"math"
"strconv"
"gioui.org/layout"
"gioui.org/unit"
@ -17,9 +18,11 @@ type (
}
)
const SpectrumDisplayDb = 60
func NewSpectrumState() *SpectrumState {
return &SpectrumState{
plot: NewPlot(plotRange{0, 1}, plotRange{-1, 0}),
plot: NewPlot(plotRange{-4, 0}, plotRange{SpectrumDisplayDb, 0}),
resolutionNumber: NewNumericUpDownState(),
smoothingBtn: new(Clickable),
chnModeBtn: new(Clickable),
@ -67,15 +70,27 @@ func (s *SpectrumState) Layout(gtx C) D {
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))
biquad, biquadok := t.Model.BiquadCoeffs()
data := func(chn int, xr plotRange) (yr plotRange, ok bool) {
if chn == 2 {
if xr.a >= 0 {
return plotRange{}, false
}
ya := math.Log10(float64(biquad.Gain(float32(math.Pi*math.Pow(10, float64(xr.a)))))) * 20
yb := math.Log10(float64(biquad.Gain(float32(math.Pi*math.Pow(10, float64(xr.b)))))) * 20
return plotRange{float32(ya) + SpectrumDisplayDb, float32(yb) + SpectrumDisplayDb}, true
}
if chn >= numchns {
return plotRange{}, false
}
xr.a = float32(math.Pow(10, float64(xr.a)))
xr.b = float32(math.Pow(10, float64(xr.b)))
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}
return plotRange{}, false
}
y1 := float32(math.Inf(-1))
y2 := float32(math.Inf(+1))
@ -95,18 +110,18 @@ func (s *SpectrumState) Layout(gtx C) D {
y2 = min(y2, sample)
}
}
y1 = (y1 / 80) + 1
y2 = (y2 / 80) + 1
y1 = softplus(y1*10) / 10
y2 = softplus(y2*10) / 10
y1 = SpectrumDisplayDb + y1
y2 = SpectrumDisplayDb + y2
y1 = softplus(y1/5) * 5 // we "squash" the low volumes so the -Inf dB becomes -SpectrumDisplayDb
y2 = softplus(y2/5) * 5
return plotRange{-y1, -y2}
return plotRange{y1, y2}, true
}
type pair struct {
freq float64
label string
}
xticks := func(r plotRange, yield func(pos float32, label string)) {
xticks := func(r plotRange, count int, yield func(pos float32, label string)) {
type pair struct {
freq float64
label string
}
for _, p := range []pair{
{freq: 10, label: "10 Hz"},
{freq: 20, label: "20 Hz"},
@ -120,17 +135,32 @@ func (s *SpectrumState) Layout(gtx C) D {
{freq: 1e4, label: "10 kHz"},
{freq: 2e4, label: "20 kHz"},
} {
x := float32(math.Log2((p.freq/22050))/8 + 1)
x := float32(math.Log10(p.freq / 22050))
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")
yticks := func(r plotRange, count int, yield func(pos float32, label string)) {
step := 3
var start, end int
for {
start = int(math.Ceil(float64(r.b-SpectrumDisplayDb) / float64(step)))
end = int(math.Floor(float64(r.a-SpectrumDisplayDb) / float64(step)))
step *= 2
if end-start+1 <= count*4 { // we use 4x density for the y-lines in the spectrum
break
}
}
for i := start; i <= end; i++ {
yield(float32(i*step)+SpectrumDisplayDb, strconv.Itoa(i*step))
}
}
return s.plot.Layout(gtx, data, xticks, yticks, 0, numchns)
n := numchns
if biquadok {
n = 3
}
return s.plot.Layout(gtx, data, xticks, yticks, 0, n)
}),
layout.Rigid(func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,

View File

@ -84,10 +84,11 @@ iconbutton:
size: 24
inset: { top: 6, bottom: 6, left: 6, right: 6 }
plot:
curvecolors: [*primarycolor, *secondarycolor]
curvecolors: [*primarycolor, *secondarycolor,*disabled]
limitcolor: { r: 255, g: 255, b: 255, a: 8 }
cursorcolor: { r: 252, g: 186, b: 3, a: 255 }
ticks: { textsize: 12, color: *disabled, maxlines: 1}
dppertick: 50
numericupdown:
bgcolor: { r: 255, g: 255, b: 255, a: 3 }
textcolor: *fg