mirror of
https://github.com/vsariola/sointu.git
synced 2026-02-14 12:13:20 -05:00
drafting
This commit is contained in:
parent
06a1fb6b52
commit
655d736149
@ -27,7 +27,7 @@ type (
|
||||
|
||||
func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
|
||||
return &OscilloscopeState{
|
||||
plot: NewPlot(plotRange{0, 1}, plotRange{-1, 1}),
|
||||
plot: NewPlot(plotRange{0, 1}, plotRange{-1, 1}, 0),
|
||||
onceBtn: new(Clickable),
|
||||
wrapBtn: new(Clickable),
|
||||
lengthInBeatsNumber: NewNumericUpDownState(),
|
||||
|
||||
@ -17,6 +17,7 @@ import (
|
||||
type (
|
||||
Plot struct {
|
||||
origXlim, origYlim plotRange
|
||||
fixedYLevel float32
|
||||
|
||||
xScale, yScale float32
|
||||
xOffset float32
|
||||
@ -41,10 +42,11 @@ type (
|
||||
plotLogScale float32
|
||||
)
|
||||
|
||||
func NewPlot(xlim, ylim plotRange) *Plot {
|
||||
func NewPlot(xlim, ylim plotRange, fixedYLevel float32) *Plot {
|
||||
return &Plot{
|
||||
origXlim: xlim,
|
||||
origYlim: ylim,
|
||||
origXlim: xlim,
|
||||
origYlim: ylim,
|
||||
fixedYLevel: fixedYLevel,
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,9 +84,11 @@ func (p *Plot) Layout(gtx C, data PlotDataFunc, xticks, yticks PlotTickFunc, cur
|
||||
})
|
||||
|
||||
// draw cursor
|
||||
paint.ColorOp{Color: style.CursorColor}.Add(gtx.Ops)
|
||||
csx := plotPx(s.X).toScreen(xlim.toRelative(cursornx))
|
||||
fillRect(gtx, clip.Rect{Min: image.Pt(csx, 0), Max: image.Pt(csx+1, s.Y)})
|
||||
if cursornx == cursornx { // check for NaN
|
||||
paint.ColorOp{Color: style.CursorColor}.Add(gtx.Ops)
|
||||
csx := plotPx(s.X).toScreen(xlim.toRelative(cursornx))
|
||||
fillRect(gtx, clip.Rect{Min: image.Pt(csx, 0), Max: image.Pt(csx+1, s.Y)})
|
||||
}
|
||||
|
||||
// draw curves
|
||||
for chn := range numchns {
|
||||
@ -119,7 +123,9 @@ func (s plotPx) fromScreen(px int) plotRel { return plotRel(float32(px) /
|
||||
func (s plotPx) fromScreenF32(px float32) plotRel { return plotRel(px / float32(s-1)) }
|
||||
|
||||
func (o *Plot) xlim() plotRange { return o.origXlim.scale(o.xScale).offset(o.xOffset) }
|
||||
func (o *Plot) ylim() plotRange { return o.origYlim.scale(o.yScale) }
|
||||
func (o *Plot) ylim() plotRange {
|
||||
return o.origYlim.offset(-o.fixedYLevel).scale(o.yScale).offset(o.fixedYLevel)
|
||||
}
|
||||
|
||||
func fillRect(gtx C, rect clip.Rect) {
|
||||
stack := rect.Push(gtx.Ops)
|
||||
@ -164,6 +170,8 @@ func (o *Plot) update(gtx C) {
|
||||
|
||||
num := o.ylim().fromRelative(plotPx(s.Y).fromScreenF32(e.Position.Y))
|
||||
den := o.ylim().fromRelative(plotPx(s.Y).fromScreenF32(o.dragStartPoint.Y))
|
||||
num -= o.fixedYLevel
|
||||
den -= o.fixedYLevel
|
||||
if l := math.Abs(float64(num / den)); l > 1e-3 && l < 1e3 {
|
||||
o.yScale -= float32(math.Log(l))
|
||||
o.yScale = min(max(o.yScale, -1e3), 1e3)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package gioui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
@ -12,19 +13,22 @@ import (
|
||||
type (
|
||||
SpectrumState struct {
|
||||
resolutionNumber *NumericUpDownState
|
||||
smoothingBtn *Clickable
|
||||
speed *NumericUpDownState
|
||||
chnModeBtn *Clickable
|
||||
plot *Plot
|
||||
}
|
||||
)
|
||||
|
||||
const SpectrumDisplayDb = 60
|
||||
const (
|
||||
SpectrumDbMin = -60
|
||||
SpectrumDbMax = 12
|
||||
)
|
||||
|
||||
func NewSpectrumState() *SpectrumState {
|
||||
return &SpectrumState{
|
||||
plot: NewPlot(plotRange{-4, 0}, plotRange{SpectrumDisplayDb, 0}),
|
||||
plot: NewPlot(plotRange{-4, 0}, plotRange{SpectrumDbMax, SpectrumDbMin}, SpectrumDbMin),
|
||||
resolutionNumber: NewNumericUpDownState(),
|
||||
smoothingBtn: new(Clickable),
|
||||
speed: NewNumericUpDownState(),
|
||||
chnModeBtn: new(Clickable),
|
||||
}
|
||||
}
|
||||
@ -32,32 +36,20 @@ func NewSpectrumState() *SpectrumState {
|
||||
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
|
||||
leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(36)}.Layout
|
||||
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
|
||||
|
||||
var chnModeTxt string = "???"
|
||||
switch tracker.SpecChnMode(t.Model.SpecAnChannelsInt().Value()) {
|
||||
case tracker.SpecChnModeCombine:
|
||||
case tracker.SpecChnModeSum:
|
||||
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")
|
||||
speed := NumUpDown(t.Model.SpecAnSpeed(), t.Theme, s.speed, "Speed")
|
||||
|
||||
numchns := 0
|
||||
speclen := len(t.Model.Spectrum()[0])
|
||||
@ -78,15 +70,15 @@ func (s *SpectrumState) Layout(gtx C) D {
|
||||
}
|
||||
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
|
||||
return plotRange{float32(ya), float32(yb)}, 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))
|
||||
w1, f1 := math.Modf(float64(xr.a)*float64(speclen) - 1) // -1 cause we don't have the DC bin there
|
||||
w2, f2 := math.Modf(float64(xr.b)*float64(speclen) - 1) // -1 cause we don't have the DC bin there
|
||||
x1 := max(int(w1), 0)
|
||||
x2 := min(int(w2), speclen-1)
|
||||
if x1 > x2 {
|
||||
@ -110,10 +102,8 @@ func (s *SpectrumState) Layout(gtx C) D {
|
||||
y2 = min(y2, sample)
|
||||
}
|
||||
}
|
||||
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
|
||||
y1 = softplus((y1-SpectrumDbMin)/5)*5 + SpectrumDbMin // we "squash" the low volumes so the -Inf dB becomes -SpectrumDbMin
|
||||
y2 = softplus((y2-SpectrumDbMin)/5)*5 + SpectrumDbMin
|
||||
|
||||
return plotRange{y1, y2}, true
|
||||
}
|
||||
@ -122,22 +112,23 @@ func (s *SpectrumState) Layout(gtx C) D {
|
||||
freq float64
|
||||
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.Log10(p.freq / 22050))
|
||||
if x >= r.a && x <= r.b {
|
||||
yield(x, p.label)
|
||||
const offset = 0.343408593803857 // log10(22050/10000)
|
||||
const startdiv = 3 * (1 << 8)
|
||||
step := nextPowerOfTwo(int(float64(r.b-r.a)*startdiv/float64(count)) + 1)
|
||||
start := int(math.Floor(float64(r.a+offset) * startdiv / float64(step)))
|
||||
end := int(math.Ceil(float64(r.b+offset) * startdiv / float64(step)))
|
||||
for i := start; i <= end; i++ {
|
||||
lognormfreq := float32(i*step)/startdiv - offset
|
||||
freq := math.Pow(10, float64(lognormfreq)) * 22050
|
||||
df := freq * math.Log(10) * float64(step) / startdiv // this is roughly the difference in Hz between the ticks currently
|
||||
rounding := int(math.Floor(math.Log10(df)))
|
||||
r := math.Pow(10, float64(rounding))
|
||||
freq = math.Round(freq/r) * r
|
||||
tickpos := float32(math.Log10(freq / 22050))
|
||||
if rounding >= 3 {
|
||||
yield(tickpos, fmt.Sprintf("%.0f kHz", freq/1000))
|
||||
} else {
|
||||
yield(tickpos, fmt.Sprintf("%s Hz", strconv.FormatFloat(freq, 'f', -rounding, 64)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -145,30 +136,47 @@ func (s *SpectrumState) Layout(gtx C) D {
|
||||
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
|
||||
start = int(math.Ceil(float64(r.b) / float64(step)))
|
||||
end = int(math.Floor(float64(r.a) / float64(step)))
|
||||
if end-start+1 <= count*4 { // we use 4x density for the y-lines in the spectrum
|
||||
break
|
||||
}
|
||||
step *= 2
|
||||
}
|
||||
for i := start; i <= end; i++ {
|
||||
yield(float32(i*step)+SpectrumDisplayDb, strconv.Itoa(i*step))
|
||||
yield(float32(i*step), strconv.Itoa(i*step))
|
||||
}
|
||||
}
|
||||
n := numchns
|
||||
if biquadok {
|
||||
n = 3
|
||||
}
|
||||
return s.plot.Layout(gtx, data, xticks, yticks, 0, n)
|
||||
return s.plot.Layout(gtx, data, xticks, yticks, float32(math.NaN()), n)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Rigid(leftSpacer),
|
||||
layout.Rigid(Label(t.Theme, &t.Theme.SongPanel.RowHeader, "Resolution").Layout),
|
||||
layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
|
||||
layout.Rigid(resolution.Layout),
|
||||
layout.Rigid(rightSpacer),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Rigid(leftSpacer),
|
||||
layout.Rigid(Label(t.Theme, &t.Theme.SongPanel.RowHeader, "Speed").Layout),
|
||||
layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
|
||||
layout.Rigid(speed.Layout),
|
||||
layout.Rigid(rightSpacer),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Rigid(leftSpacer),
|
||||
layout.Rigid(Label(t.Theme, &t.Theme.SongPanel.RowHeader, "Channels").Layout),
|
||||
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),
|
||||
)
|
||||
}),
|
||||
@ -184,13 +192,26 @@ func smoothInterpolate(a, b float32, t float32) float32 {
|
||||
return (1-t)*a + t*b
|
||||
}
|
||||
|
||||
func nextPowerOfTwo(v int) int {
|
||||
if v <= 0 {
|
||||
return 1
|
||||
}
|
||||
v--
|
||||
v |= v >> 1
|
||||
v |= v >> 2
|
||||
v |= v >> 4
|
||||
v |= v >> 8
|
||||
v |= v >> 16
|
||||
v |= v >> 32
|
||||
v++
|
||||
return v
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
s.resolutionNumber.Update(gtx, t.Model.SpecAnResolution())
|
||||
s.speed.Update(gtx, t.Model.SpecAnSpeed())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user