mirror of
https://github.com/vsariola/sointu.git
synced 2026-02-20 15:13:25 -05:00
drafting
This commit is contained in:
parent
fcb9a06249
commit
06a1fb6b52
@ -44,6 +44,7 @@ func Scope(th *Theme, m *tracker.ScopeModel, st *OscilloscopeState) Oscilloscope
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Oscilloscope) Layout(gtx C) D {
|
func (s *Oscilloscope) Layout(gtx C) D {
|
||||||
|
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(24)}.Layout
|
||||||
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
|
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
|
||||||
|
|
||||||
@ -58,11 +59,11 @@ func (s *Oscilloscope) Layout(gtx C) D {
|
|||||||
w := s.Model.Waveform()
|
w := s.Model.Waveform()
|
||||||
cx := float32(w.Cursor) / float32(len(w.Buffer))
|
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)
|
x1 := max(int(xr.a*float32(len(w.Buffer))), 0)
|
||||||
x2 := min(int(xr.b*float32(len(w.Buffer))), len(w.Buffer)-1)
|
x2 := min(int(xr.b*float32(len(w.Buffer))), len(w.Buffer)-1)
|
||||||
if x1 > x2 {
|
if x1 > x2 {
|
||||||
return plotRange{1, 0}
|
return plotRange{}, false
|
||||||
}
|
}
|
||||||
y1 := float32(math.Inf(-1))
|
y1 := float32(math.Inf(-1))
|
||||||
y2 := float32(math.Inf(+1))
|
y2 := float32(math.Inf(+1))
|
||||||
@ -71,18 +72,31 @@ func (s *Oscilloscope) Layout(gtx C) D {
|
|||||||
y1 = max(y1, sample)
|
y1 = max(y1, sample)
|
||||||
y2 = min(y2, sample)
|
y2 = min(y2, sample)
|
||||||
}
|
}
|
||||||
return plotRange{-y1, -y2}
|
return plotRange{-y1, -y2}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
xticks := func(r plotRange, yield func(pos float32, label string)) {
|
rpb := max(t.Model.RowsPerBeat().Value(), 1)
|
||||||
l := s.Model.LengthInBeats().Value()
|
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)
|
a := max(int(math.Ceil(float64(r.a*float32(l)))), 0)
|
||||||
b := min(int(math.Floor(float64(r.b*float32(l)))), l)
|
b := min(int(math.Floor(float64(r.b*float32(l)))), l)
|
||||||
for i := a; i <= b; i++ {
|
step := 1
|
||||||
yield(float32(i)/float32(l), strconv.Itoa(i))
|
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, "")
|
||||||
yield(1, "")
|
yield(1, "")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
"gioui.org/op/paint"
|
"gioui.org/op/paint"
|
||||||
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -25,14 +26,15 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlotStyle struct {
|
PlotStyle struct {
|
||||||
CurveColors [2]color.NRGBA `yaml:",flow"`
|
CurveColors [3]color.NRGBA `yaml:",flow"`
|
||||||
LimitColor color.NRGBA `yaml:",flow"`
|
LimitColor color.NRGBA `yaml:",flow"`
|
||||||
CursorColor color.NRGBA `yaml:",flow"`
|
CursorColor color.NRGBA `yaml:",flow"`
|
||||||
Ticks LabelStyle
|
Ticks LabelStyle
|
||||||
|
DpPerTick unit.Dp
|
||||||
}
|
}
|
||||||
|
|
||||||
PlotDataFunc func(chn int, xr plotRange) (yr plotRange)
|
PlotDataFunc func(chn int, xr plotRange) (yr plotRange, ok bool)
|
||||||
PlotTickFunc func(r plotRange, yield func(pos float32, label string))
|
PlotTickFunc func(r plotRange, num int, yield func(pos float32, label string))
|
||||||
plotRange struct{ a, b float32 }
|
plotRange struct{ a, b float32 }
|
||||||
plotRel float32
|
plotRel float32
|
||||||
plotPx int
|
plotPx int
|
||||||
@ -61,8 +63,8 @@ func (p *Plot) Layout(gtx C, data PlotDataFunc, xticks, yticks PlotTickFunc, cur
|
|||||||
ylim := p.ylim()
|
ylim := p.ylim()
|
||||||
|
|
||||||
// draw tick marks
|
// draw tick marks
|
||||||
|
numxticks := s.X / gtx.Dp(style.DpPerTick)
|
||||||
xticks(xlim, func(x float32, txt string) {
|
xticks(xlim, numxticks, func(x float32, txt string) {
|
||||||
paint.ColorOp{Color: style.LimitColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: style.LimitColor}.Add(gtx.Ops)
|
||||||
sx := plotPx(s.X).toScreen(xlim.toRelative(x))
|
sx := plotPx(s.X).toScreen(xlim.toRelative(x))
|
||||||
fillRect(gtx, clip.Rect{Min: image.Pt(sx, 0), Max: image.Pt(sx+1, s.Y)})
|
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)
|
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)
|
paint.ColorOp{Color: style.LimitColor}.Add(gtx.Ops)
|
||||||
sy := plotPx(s.Y).toScreen(ylim.toRelative(y))
|
sy := plotPx(s.Y).toScreen(ylim.toRelative(y))
|
||||||
fillRect(gtx, clip.Rect{Min: image.Pt(0, sy), Max: image.Pt(s.X, sy+1)})
|
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 and right is the sample range covered by the pixel
|
||||||
left := right
|
left := right
|
||||||
right = xlim.fromRelative(plotPx(s.X).fromScreen(sx + 1))
|
right = xlim.fromRelative(plotPx(s.X).fromScreen(sx + 1))
|
||||||
yr := data(chn, plotRange{left, right})
|
yr, ok := data(chn, plotRange{left, right})
|
||||||
if yr.b < yr.a {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
y1 := plotPx(s.Y).toScreen(ylim.toRelative(yr.a))
|
y1 := plotPx(s.Y).toScreen(ylim.toRelative(yr.a))
|
||||||
y2 := plotPx(s.Y).toScreen(ylim.toRelative(yr.b))
|
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}
|
return D{Size: s}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package gioui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
@ -17,9 +18,11 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const SpectrumDisplayDb = 60
|
||||||
|
|
||||||
func NewSpectrumState() *SpectrumState {
|
func NewSpectrumState() *SpectrumState {
|
||||||
return &SpectrumState{
|
return &SpectrumState{
|
||||||
plot: NewPlot(plotRange{0, 1}, plotRange{-1, 0}),
|
plot: NewPlot(plotRange{-4, 0}, plotRange{SpectrumDisplayDb, 0}),
|
||||||
resolutionNumber: NewNumericUpDownState(),
|
resolutionNumber: NewNumericUpDownState(),
|
||||||
smoothingBtn: new(Clickable),
|
smoothingBtn: new(Clickable),
|
||||||
chnModeBtn: new(Clickable),
|
chnModeBtn: new(Clickable),
|
||||||
@ -67,15 +70,27 @@ func (s *SpectrumState) Layout(gtx C) D {
|
|||||||
|
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Flexed(1, func(gtx C) D {
|
layout.Flexed(1, func(gtx C) D {
|
||||||
data := func(chn int, xr plotRange) (yr plotRange) {
|
biquad, biquadok := t.Model.BiquadCoeffs()
|
||||||
xr.a = float32(math.Exp2(float64(xr.a-1) * 8))
|
data := func(chn int, xr plotRange) (yr plotRange, ok bool) {
|
||||||
xr.b = float32(math.Exp2(float64(xr.b-1) * 8))
|
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))
|
w1, f1 := math.Modf(float64(xr.a) * float64(speclen))
|
||||||
w2, f2 := math.Modf(float64(xr.b) * float64(speclen))
|
w2, f2 := math.Modf(float64(xr.b) * float64(speclen))
|
||||||
x1 := max(int(w1), 0)
|
x1 := max(int(w1), 0)
|
||||||
x2 := min(int(w2), speclen-1)
|
x2 := min(int(w2), speclen-1)
|
||||||
if x1 > x2 {
|
if x1 > x2 {
|
||||||
return plotRange{1, 0}
|
return plotRange{}, false
|
||||||
}
|
}
|
||||||
y1 := float32(math.Inf(-1))
|
y1 := float32(math.Inf(-1))
|
||||||
y2 := float32(math.Inf(+1))
|
y2 := float32(math.Inf(+1))
|
||||||
@ -95,18 +110,18 @@ func (s *SpectrumState) Layout(gtx C) D {
|
|||||||
y2 = min(y2, sample)
|
y2 = min(y2, sample)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
y1 = (y1 / 80) + 1
|
y1 = SpectrumDisplayDb + y1
|
||||||
y2 = (y2 / 80) + 1
|
y2 = SpectrumDisplayDb + y2
|
||||||
y1 = softplus(y1*10) / 10
|
y1 = softplus(y1/5) * 5 // we "squash" the low volumes so the -Inf dB becomes -SpectrumDisplayDb
|
||||||
y2 = softplus(y2*10) / 10
|
y2 = softplus(y2/5) * 5
|
||||||
|
|
||||||
return plotRange{-y1, -y2}
|
return plotRange{y1, y2}, true
|
||||||
}
|
}
|
||||||
type pair struct {
|
xticks := func(r plotRange, count int, yield func(pos float32, label string)) {
|
||||||
freq float64
|
type pair struct {
|
||||||
label string
|
freq float64
|
||||||
}
|
label string
|
||||||
xticks := func(r plotRange, yield func(pos float32, label string)) {
|
}
|
||||||
for _, p := range []pair{
|
for _, p := range []pair{
|
||||||
{freq: 10, label: "10 Hz"},
|
{freq: 10, label: "10 Hz"},
|
||||||
{freq: 20, label: "20 Hz"},
|
{freq: 20, label: "20 Hz"},
|
||||||
@ -120,17 +135,32 @@ func (s *SpectrumState) Layout(gtx C) D {
|
|||||||
{freq: 1e4, label: "10 kHz"},
|
{freq: 1e4, label: "10 kHz"},
|
||||||
{freq: 2e4, label: "20 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 {
|
if x >= r.a && x <= r.b {
|
||||||
yield(x, p.label)
|
yield(x, p.label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yticks := func(r plotRange, yield func(pos float32, label string)) {
|
yticks := func(r plotRange, count int, yield func(pos float32, label string)) {
|
||||||
yield(-1, "0 dB")
|
step := 3
|
||||||
yield(0, "-Inf dB")
|
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 {
|
layout.Rigid(func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
|||||||
@ -84,10 +84,11 @@ iconbutton:
|
|||||||
size: 24
|
size: 24
|
||||||
inset: { top: 6, bottom: 6, left: 6, right: 6 }
|
inset: { top: 6, bottom: 6, left: 6, right: 6 }
|
||||||
plot:
|
plot:
|
||||||
curvecolors: [*primarycolor, *secondarycolor]
|
curvecolors: [*primarycolor, *secondarycolor,*disabled]
|
||||||
limitcolor: { r: 255, g: 255, b: 255, a: 8 }
|
limitcolor: { r: 255, g: 255, b: 255, a: 8 }
|
||||||
cursorcolor: { r: 252, g: 186, b: 3, a: 255 }
|
cursorcolor: { r: 252, g: 186, b: 3, a: 255 }
|
||||||
ticks: { textsize: 12, color: *disabled, maxlines: 1}
|
ticks: { textsize: 12, color: *disabled, maxlines: 1}
|
||||||
|
dppertick: 50
|
||||||
numericupdown:
|
numericupdown:
|
||||||
bgcolor: { r: 255, g: 255, b: 255, a: 3 }
|
bgcolor: { r: 255, g: 255, b: 255, a: 3 }
|
||||||
textcolor: *fg
|
textcolor: *fg
|
||||||
|
|||||||
@ -34,6 +34,11 @@ type (
|
|||||||
tmpC []complex128 // temporary buffer for FFT
|
tmpC []complex128 // temporary buffer for FFT
|
||||||
tmp1, tmp2 []float32 // temporary buffers for processing
|
tmp1, tmp2 []float32 // temporary buffers for processing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BiquadCoeffs struct {
|
||||||
|
b0, b1, b2 float32
|
||||||
|
a0, a1, a2 float32
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -72,6 +77,60 @@ func NewSpecAnalyzer(broker *Broker) *SpecAnalyzer {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) BiquadCoeffs() (coeffs BiquadCoeffs, ok bool) {
|
||||||
|
i := m.d.InstrIndex
|
||||||
|
u := m.d.UnitIndex
|
||||||
|
if i < 0 || i >= len(m.d.Song.Patch) || u < 0 || u >= len(m.d.Song.Patch[i].Units) {
|
||||||
|
return BiquadCoeffs{}, false
|
||||||
|
}
|
||||||
|
switch m.d.Song.Patch[i].Units[u].Type {
|
||||||
|
case "filter":
|
||||||
|
p := m.d.Song.Patch[i].Units[u].Parameters
|
||||||
|
f := float32(p["frequency"]) / 128
|
||||||
|
res := float32(p["resonance"]) / 128
|
||||||
|
f2 := f * f
|
||||||
|
g := f2 / (2 - f2)
|
||||||
|
a1 := 2 * (g*g - 1)
|
||||||
|
a2 := (1 - g*(g-res))
|
||||||
|
var b0, b1, b2 float32
|
||||||
|
if p["low"] == 1 {
|
||||||
|
b0 += g * g
|
||||||
|
b1 += 2 * g * g
|
||||||
|
b2 += g * g
|
||||||
|
}
|
||||||
|
b0 += float32(p["high"])
|
||||||
|
b1 += -2 * float32(p["high"])
|
||||||
|
b2 += float32(p["high"])
|
||||||
|
b0 += g * float32(p["band"])
|
||||||
|
b2 += -g * float32(p["band"])
|
||||||
|
return BiquadCoeffs{a0: 1, a1: a1, a2: a2, b0: b0, b1: b1, b2: b2}, true
|
||||||
|
case "belleq":
|
||||||
|
f := float32(m.d.Song.Patch[i].Units[u].Parameters["frequency"]) / 128
|
||||||
|
band := float32(m.d.Song.Patch[i].Units[u].Parameters["bandwidth"]) / 128
|
||||||
|
gain := float32(m.d.Song.Patch[i].Units[u].Parameters["gain"]) / 128
|
||||||
|
omega0 := 2 * f * f
|
||||||
|
alpha := float32(math.Sin(float64(omega0))) * 2 * band
|
||||||
|
A := float32(math.Pow(2, float64(gain-.5)*6.643856189774724))
|
||||||
|
u, v := alpha*A, alpha/A
|
||||||
|
return BiquadCoeffs{
|
||||||
|
b0: 1 + u,
|
||||||
|
b1: -2 * float32(math.Cos(float64(omega0))),
|
||||||
|
b2: 1 - u,
|
||||||
|
a0: 1 + v,
|
||||||
|
a1: -2 * float32(math.Cos(float64(omega0))),
|
||||||
|
a2: 1 - v,
|
||||||
|
}, true
|
||||||
|
default:
|
||||||
|
return BiquadCoeffs{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BiquadCoeffs) Gain(omega float32) float32 {
|
||||||
|
e := cmplx.Rect(1, -float64(omega))
|
||||||
|
return float32(cmplx.Abs((complex(float64(c.b0), 0) + complex(float64(c.b1), 0)*e + complex(float64(c.b2), 0)*(e*e)) /
|
||||||
|
(complex(float64(c.a0), 0) + complex(float64(c.a1), 0)*e + complex(float64(c.a2), 0)*e*e)))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SpecAnalyzer) Run() {
|
func (s *SpecAnalyzer) Run() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|||||||
Reference in New Issue
Block a user