mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
Already the oscilloscope calculated its length in beats, but everywhere the variable was called "lengthInRows." Renamed the variable to lengthInBeats and also changed the tooltip to be correct
184 lines
5.9 KiB
Go
184 lines
5.9 KiB
Go
package gioui
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"math"
|
|
|
|
"gioui.org/io/event"
|
|
"gioui.org/io/pointer"
|
|
"gioui.org/layout"
|
|
"gioui.org/op/clip"
|
|
"gioui.org/op/paint"
|
|
"gioui.org/widget/material"
|
|
"github.com/vsariola/sointu/tracker"
|
|
)
|
|
|
|
type (
|
|
Oscilloscope struct {
|
|
onceBtn *BoolClickable
|
|
wrapBtn *BoolClickable
|
|
lengthInBeatsNumber *NumberInput
|
|
triggerChannelNumber *NumberInput
|
|
xScale int
|
|
xOffset float32
|
|
dragging bool
|
|
dragId pointer.ID
|
|
dragStartPx float32
|
|
}
|
|
|
|
OscilloscopeStyle struct {
|
|
Oscilloscope *Oscilloscope
|
|
Wave tracker.RingBuffer[[2]float32]
|
|
Colors [2]color.NRGBA
|
|
ClippedColor color.NRGBA
|
|
Theme *material.Theme
|
|
}
|
|
)
|
|
|
|
func NewOscilloscope(model *tracker.Model) *Oscilloscope {
|
|
return &Oscilloscope{
|
|
onceBtn: NewBoolClickable(model.SignalAnalyzer().Once().Bool()),
|
|
wrapBtn: NewBoolClickable(model.SignalAnalyzer().Wrap().Bool()),
|
|
lengthInBeatsNumber: NewNumberInput(model.SignalAnalyzer().LengthInBeats().Int()),
|
|
triggerChannelNumber: NewNumberInput(model.SignalAnalyzer().TriggerChannel().Int()),
|
|
}
|
|
}
|
|
|
|
func LineOscilloscope(s *Oscilloscope, wave tracker.RingBuffer[[2]float32], th *material.Theme) *OscilloscopeStyle {
|
|
return &OscilloscopeStyle{Oscilloscope: s, Wave: wave, Colors: [2]color.NRGBA{primaryColor, secondaryColor}, Theme: th, ClippedColor: errorColor}
|
|
}
|
|
|
|
func (s *OscilloscopeStyle) Layout(gtx C) D {
|
|
wrapBtnStyle := ToggleButton(gtx, s.Theme, s.Oscilloscope.wrapBtn, "Wrap")
|
|
onceBtnStyle := ToggleButton(gtx, s.Theme, s.Oscilloscope.onceBtn, "Once")
|
|
triggerChannelStyle := NumericUpDown(s.Theme, s.Oscilloscope.triggerChannelNumber, "Trigger channel")
|
|
lengthNumberStyle := NumericUpDown(s.Theme, s.Oscilloscope.lengthInBeatsNumber, "Buffer length in beats")
|
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
|
layout.Flexed(1, func(gtx C) D { return s.layoutWave(gtx) }),
|
|
layout.Rigid(func(gtx C) D {
|
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
|
layout.Rigid(Label("TRG:", white, s.Theme.Shaper)),
|
|
layout.Rigid(triggerChannelStyle.Layout),
|
|
layout.Rigid(onceBtnStyle.Layout),
|
|
)
|
|
}),
|
|
layout.Rigid(func(gtx C) D {
|
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
|
layout.Rigid(Label("BUF:", white, s.Theme.Shaper)),
|
|
layout.Rigid(lengthNumberStyle.Layout),
|
|
layout.Rigid(wrapBtnStyle.Layout),
|
|
)
|
|
}),
|
|
)
|
|
}
|
|
|
|
func (s *OscilloscopeStyle) layoutWave(gtx C) D {
|
|
s.update(gtx)
|
|
if gtx.Constraints.Max.X == 0 || gtx.Constraints.Max.Y == 0 {
|
|
return D{}
|
|
}
|
|
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
|
|
event.Op(gtx.Ops, s.Oscilloscope)
|
|
paint.ColorOp{Color: disabledTextColor}.Add(gtx.Ops)
|
|
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)
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
stack.Pop()
|
|
for chn := 0; chn < 2; chn++ {
|
|
paint.ColorOp{Color: s.Colors[chn]}.Add(gtx.Ops)
|
|
clippedColorSet := false
|
|
yprev := int((s.Wave.Buffer[0][chn] + 1) / 2 * float32(gtx.Constraints.Max.Y))
|
|
for px := 0; px < gtx.Constraints.Max.X; px++ {
|
|
x := int(s.pxToSample(gtx, float32(px)))
|
|
if x < 0 || x >= len(s.Wave.Buffer) {
|
|
continue
|
|
}
|
|
y := int((s.Wave.Buffer[x][chn] + 1) / 2 * float32(gtx.Constraints.Max.Y))
|
|
if y < 0 {
|
|
y = 0
|
|
} else if y >= gtx.Constraints.Max.Y {
|
|
y = gtx.Constraints.Max.Y - 1
|
|
}
|
|
y1, y2 := yprev, y
|
|
if y < yprev {
|
|
y1, y2 = y, yprev-1
|
|
} else if y > yprev {
|
|
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)}
|
|
}
|
|
|
|
func (o *OscilloscopeStyle) update(gtx C) {
|
|
for {
|
|
ev, ok := gtx.Event(pointer.Filter{
|
|
Target: o.Oscilloscope,
|
|
Kinds: pointer.Scroll | pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
|
|
ScrollY: pointer.ScrollRange{Min: -1e6, Max: 1e6},
|
|
})
|
|
if !ok {
|
|
break
|
|
}
|
|
if e, ok := ev.(pointer.Event); ok {
|
|
switch e.Kind {
|
|
case pointer.Scroll:
|
|
s1 := o.pxToSample(gtx, e.Position.X)
|
|
o.Oscilloscope.xScale += min(max(-1, int(e.Scroll.Y)), 1)
|
|
s2 := o.pxToSample(gtx, e.Position.X)
|
|
o.Oscilloscope.xOffset -= s1 - s2
|
|
case pointer.Press:
|
|
if e.Buttons&pointer.ButtonSecondary != 0 {
|
|
o.Oscilloscope.xOffset = 0
|
|
o.Oscilloscope.xScale = 0
|
|
}
|
|
if e.Buttons&pointer.ButtonPrimary != 0 {
|
|
o.Oscilloscope.dragging = true
|
|
o.Oscilloscope.dragId = e.PointerID
|
|
o.Oscilloscope.dragStartPx = e.Position.X
|
|
}
|
|
case pointer.Drag:
|
|
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)
|
|
o.Oscilloscope.xOffset += delta
|
|
o.Oscilloscope.dragStartPx = e.Position.X
|
|
}
|
|
case pointer.Release | pointer.Cancel:
|
|
o.Oscilloscope.dragging = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (o *OscilloscopeStyle) scaleFactor() float32 {
|
|
return float32(math.Pow(1.1, float64(o.Oscilloscope.xScale)))
|
|
}
|
|
|
|
func (s *OscilloscopeStyle) pxToSample(gtx C, px float32) float32 {
|
|
return px*s.scaleFactor()*float32(len(s.Wave.Buffer))/float32(gtx.Constraints.Max.X) - s.Oscilloscope.xOffset
|
|
}
|
|
|
|
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()
|
|
}
|