mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-03 09:08:18 -04:00
feat(tracker/gioui): oscilloscope allows y-scaling and shows limits
Closes #61
This commit is contained in:
parent
554a840982
commit
0f42a993dc
@ -5,6 +5,7 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"gioui.org/f32"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
@ -23,9 +24,10 @@ type (
|
|||||||
triggerChannelNumber *NumberInput
|
triggerChannelNumber *NumberInput
|
||||||
xScale int
|
xScale int
|
||||||
xOffset float32
|
xOffset float32
|
||||||
|
yScale float64
|
||||||
dragging bool
|
dragging bool
|
||||||
dragId pointer.ID
|
dragId pointer.ID
|
||||||
dragStartPx float32
|
dragStartPoint f32.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
OscilloscopeStyle struct {
|
OscilloscopeStyle struct {
|
||||||
@ -91,56 +93,51 @@ func (s *OscilloscopeStyle) layoutWave(gtx C) D {
|
|||||||
}
|
}
|
||||||
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
|
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
|
||||||
event.Op(gtx.Ops, s.Oscilloscope)
|
event.Op(gtx.Ops, s.Oscilloscope)
|
||||||
paint.ColorOp{Color: disabledTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: oscilloscopeCursorColor}.Add(gtx.Ops)
|
||||||
cursorX := int(s.sampleToPx(gtx, float32(s.Wave.Cursor)))
|
cursorX := int(s.sampleToPx(gtx, float32(s.Wave.Cursor)))
|
||||||
stack := clip.Rect{Min: image.Pt(cursorX, 0), Max: image.Pt(cursorX+1, gtx.Constraints.Max.Y)}.Push(gtx.Ops)
|
fillRect(gtx, clip.Rect{Min: image.Pt(cursorX, 0), Max: image.Pt(cursorX+1, gtx.Constraints.Max.Y)})
|
||||||
paint.PaintOp{}.Add(gtx.Ops)
|
paint.ColorOp{Color: oscilloscopeLimitColor}.Add(gtx.Ops)
|
||||||
stack.Pop()
|
minusOneY := int(s.ampToY(gtx, -1))
|
||||||
for chn := 0; chn < 2; chn++ {
|
fillRect(gtx, clip.Rect{Min: image.Pt(0, minusOneY), Max: image.Pt(gtx.Constraints.Max.X, minusOneY+1)})
|
||||||
|
plusOneY := int(s.ampToY(gtx, 1))
|
||||||
|
fillRect(gtx, clip.Rect{Min: image.Pt(0, plusOneY), Max: image.Pt(gtx.Constraints.Max.X, plusOneY+1)})
|
||||||
|
leftX := int(s.sampleToPx(gtx, 0))
|
||||||
|
fillRect(gtx, clip.Rect{Min: image.Pt(leftX, 0), Max: image.Pt(leftX+1, gtx.Constraints.Max.Y)})
|
||||||
|
rightX := int(s.sampleToPx(gtx, float32(len(s.Wave.Buffer)-1)))
|
||||||
|
fillRect(gtx, clip.Rect{Min: image.Pt(rightX, 0), Max: image.Pt(rightX+1, gtx.Constraints.Max.Y)})
|
||||||
|
for chn := range 2 {
|
||||||
paint.ColorOp{Color: s.Colors[chn]}.Add(gtx.Ops)
|
paint.ColorOp{Color: s.Colors[chn]}.Add(gtx.Ops)
|
||||||
clippedColorSet := false
|
for px := range gtx.Constraints.Max.X {
|
||||||
yprev := int((s.Wave.Buffer[0][chn] + 1) / 2 * float32(gtx.Constraints.Max.Y))
|
// left and right is the sample range covered by the pixel
|
||||||
for px := 0; px < gtx.Constraints.Max.X; px++ {
|
left := int(s.pxToSample(gtx, float32(px)-0.5))
|
||||||
x := int(s.pxToSample(gtx, float32(px)))
|
right := int(s.pxToSample(gtx, float32(px)+0.5))
|
||||||
if x < 0 || x >= len(s.Wave.Buffer) {
|
if right < 0 || left >= len(s.Wave.Buffer) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
y := int((s.Wave.Buffer[x][chn] + 1) / 2 * float32(gtx.Constraints.Max.Y))
|
right = min(right, len(s.Wave.Buffer)-1)
|
||||||
if y < 0 {
|
left = max(left, 0)
|
||||||
y = 0
|
// smin and smax are the smallest and largest sample values in the pixel range
|
||||||
} else if y >= gtx.Constraints.Max.Y {
|
smax := float32(math.Inf(-1))
|
||||||
y = gtx.Constraints.Max.Y - 1
|
smin := float32(math.Inf(1))
|
||||||
|
for x := left; x <= right; x++ {
|
||||||
|
smax = max(smax, s.Wave.Buffer[x][chn])
|
||||||
|
smin = min(smin, s.Wave.Buffer[x][chn])
|
||||||
}
|
}
|
||||||
y1, y2 := yprev, y
|
// y1 and y2 are the pixel range covered by the sample value
|
||||||
if y < yprev {
|
y1 := min(max(int(s.ampToY(gtx, smax)+0.5), 0), gtx.Constraints.Max.Y-1)
|
||||||
y1, y2 = y, yprev-1
|
y2 := min(max(int(s.ampToY(gtx, smin)+0.5), 0), gtx.Constraints.Max.Y-1)
|
||||||
} else if y > yprev {
|
fillRect(gtx, clip.Rect{Min: image.Pt(px, y1), Max: image.Pt(px+1, y2+1)})
|
||||||
y1++
|
|
||||||
}
|
|
||||||
clipped := false
|
|
||||||
if y1 == y2 && y1 == 0 {
|
|
||||||
clipped = true
|
|
||||||
}
|
|
||||||
if y1 == y2 && y1 == gtx.Constraints.Max.Y-1 {
|
|
||||||
clipped = true
|
|
||||||
}
|
|
||||||
if clippedColorSet != clipped {
|
|
||||||
if clipped {
|
|
||||||
paint.ColorOp{Color: s.ClippedColor}.Add(gtx.Ops)
|
|
||||||
} else {
|
|
||||||
paint.ColorOp{Color: s.Colors[chn]}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
clippedColorSet = clipped
|
|
||||||
}
|
|
||||||
stack := clip.Rect{Min: image.Pt(px, y1), Max: image.Pt(px+1, y2+1)}.Push(gtx.Ops)
|
|
||||||
paint.PaintOp{}.Add(gtx.Ops)
|
|
||||||
stack.Pop()
|
|
||||||
yprev = y
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return D{Size: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}
|
return D{Size: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fillRect(gtx C, rect clip.Rect) {
|
||||||
|
stack := rect.Push(gtx.Ops)
|
||||||
|
paint.PaintOp{}.Add(gtx.Ops)
|
||||||
|
stack.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
func (o *OscilloscopeStyle) update(gtx C) {
|
func (o *OscilloscopeStyle) update(gtx C) {
|
||||||
for {
|
for {
|
||||||
ev, ok := gtx.Event(pointer.Filter{
|
ev, ok := gtx.Event(pointer.Filter{
|
||||||
@ -162,17 +159,25 @@ func (o *OscilloscopeStyle) update(gtx C) {
|
|||||||
if e.Buttons&pointer.ButtonSecondary != 0 {
|
if e.Buttons&pointer.ButtonSecondary != 0 {
|
||||||
o.Oscilloscope.xOffset = 0
|
o.Oscilloscope.xOffset = 0
|
||||||
o.Oscilloscope.xScale = 0
|
o.Oscilloscope.xScale = 0
|
||||||
|
o.Oscilloscope.yScale = 0
|
||||||
}
|
}
|
||||||
if e.Buttons&pointer.ButtonPrimary != 0 {
|
if e.Buttons&pointer.ButtonPrimary != 0 {
|
||||||
o.Oscilloscope.dragging = true
|
o.Oscilloscope.dragging = true
|
||||||
o.Oscilloscope.dragId = e.PointerID
|
o.Oscilloscope.dragId = e.PointerID
|
||||||
o.Oscilloscope.dragStartPx = e.Position.X
|
o.Oscilloscope.dragStartPoint = e.Position
|
||||||
}
|
}
|
||||||
case pointer.Drag:
|
case pointer.Drag:
|
||||||
if e.Buttons&pointer.ButtonPrimary != 0 && o.Oscilloscope.dragging && e.PointerID == o.Oscilloscope.dragId {
|
if e.Buttons&pointer.ButtonPrimary != 0 && o.Oscilloscope.dragging && e.PointerID == o.Oscilloscope.dragId {
|
||||||
delta := o.pxToSample(gtx, e.Position.X) - o.pxToSample(gtx, o.Oscilloscope.dragStartPx)
|
deltaX := o.pxToSample(gtx, e.Position.X) - o.pxToSample(gtx, o.Oscilloscope.dragStartPoint.X)
|
||||||
o.Oscilloscope.xOffset += delta
|
o.Oscilloscope.xOffset += deltaX
|
||||||
o.Oscilloscope.dragStartPx = e.Position.X
|
num := o.yToAmp(gtx, e.Position.Y)
|
||||||
|
den := o.yToAmp(gtx, o.Oscilloscope.dragStartPoint.Y)
|
||||||
|
if l := math.Abs(float64(num / den)); l > 1e-3 && l < 1e3 {
|
||||||
|
o.Oscilloscope.yScale += math.Log(l)
|
||||||
|
o.Oscilloscope.yScale = min(max(o.Oscilloscope.yScale, -1e3), 1e3)
|
||||||
|
}
|
||||||
|
o.Oscilloscope.dragStartPoint = e.Position
|
||||||
|
|
||||||
}
|
}
|
||||||
case pointer.Release | pointer.Cancel:
|
case pointer.Release | pointer.Cancel:
|
||||||
o.Oscilloscope.dragging = false
|
o.Oscilloscope.dragging = false
|
||||||
@ -192,3 +197,13 @@ func (s *OscilloscopeStyle) pxToSample(gtx C, px float32) float32 {
|
|||||||
func (s *OscilloscopeStyle) sampleToPx(gtx C, sample float32) float32 {
|
func (s *OscilloscopeStyle) sampleToPx(gtx C, sample float32) float32 {
|
||||||
return (sample + s.Oscilloscope.xOffset) * float32(gtx.Constraints.Max.X) / float32(len(s.Wave.Buffer)) / s.scaleFactor()
|
return (sample + s.Oscilloscope.xOffset) * float32(gtx.Constraints.Max.X) / float32(len(s.Wave.Buffer)) / s.scaleFactor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OscilloscopeStyle) ampToY(gtx C, amp float32) float32 {
|
||||||
|
scale := float32(math.Exp(s.Oscilloscope.yScale))
|
||||||
|
return (1 - amp*scale) / 2 * float32(gtx.Constraints.Max.Y-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OscilloscopeStyle) yToAmp(gtx C, y float32) float32 {
|
||||||
|
scale := float32(math.Exp(s.Oscilloscope.yScale))
|
||||||
|
return (1 - y/float32(gtx.Constraints.Max.Y-1)*2) / scale
|
||||||
|
}
|
||||||
|
@ -77,3 +77,6 @@ var dialogBgColor = color.NRGBA{R: 0, G: 0, B: 0, A: 224}
|
|||||||
|
|
||||||
var paramIsSendTargetColor = color.NRGBA{R: 120, G: 120, B: 210, A: 255}
|
var paramIsSendTargetColor = color.NRGBA{R: 120, G: 120, B: 210, A: 255}
|
||||||
var paramValueInvalidColor = color.NRGBA{R: 120, G: 120, B: 120, A: 190}
|
var paramValueInvalidColor = color.NRGBA{R: 120, G: 120, B: 120, A: 190}
|
||||||
|
|
||||||
|
var oscilloscopeLimitColor = color.NRGBA{R: 255, G: 255, B: 255, A: 8}
|
||||||
|
var oscilloscopeCursorColor = color.NRGBA{R: 252, G: 186, B: 3, A: 255}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user