This commit is contained in:
5684185+vsariola@users.noreply.github.com
2026-01-17 14:54:40 +02:00
parent 06a1fb6b52
commit 655d736149
5 changed files with 137 additions and 120 deletions

View File

@ -27,7 +27,7 @@ type (
func NewOscilloscope(model *tracker.Model) *OscilloscopeState { func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
return &OscilloscopeState{ return &OscilloscopeState{
plot: NewPlot(plotRange{0, 1}, plotRange{-1, 1}), plot: NewPlot(plotRange{0, 1}, plotRange{-1, 1}, 0),
onceBtn: new(Clickable), onceBtn: new(Clickable),
wrapBtn: new(Clickable), wrapBtn: new(Clickable),
lengthInBeatsNumber: NewNumericUpDownState(), lengthInBeatsNumber: NewNumericUpDownState(),

View File

@ -17,6 +17,7 @@ import (
type ( type (
Plot struct { Plot struct {
origXlim, origYlim plotRange origXlim, origYlim plotRange
fixedYLevel float32
xScale, yScale float32 xScale, yScale float32
xOffset float32 xOffset float32
@ -41,10 +42,11 @@ type (
plotLogScale float32 plotLogScale float32
) )
func NewPlot(xlim, ylim plotRange) *Plot { func NewPlot(xlim, ylim plotRange, fixedYLevel float32) *Plot {
return &Plot{ return &Plot{
origXlim: xlim, origXlim: xlim,
origYlim: ylim, origYlim: ylim,
fixedYLevel: fixedYLevel,
} }
} }
@ -82,9 +84,11 @@ func (p *Plot) Layout(gtx C, data PlotDataFunc, xticks, yticks PlotTickFunc, cur
}) })
// draw cursor // draw cursor
paint.ColorOp{Color: style.CursorColor}.Add(gtx.Ops) if cursornx == cursornx { // check for NaN
csx := plotPx(s.X).toScreen(xlim.toRelative(cursornx)) paint.ColorOp{Color: style.CursorColor}.Add(gtx.Ops)
fillRect(gtx, clip.Rect{Min: image.Pt(csx, 0), Max: image.Pt(csx+1, s.Y)}) 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 // draw curves
for chn := range numchns { 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 (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) 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) { func fillRect(gtx C, rect clip.Rect) {
stack := rect.Push(gtx.Ops) 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)) num := o.ylim().fromRelative(plotPx(s.Y).fromScreenF32(e.Position.Y))
den := o.ylim().fromRelative(plotPx(s.Y).fromScreenF32(o.dragStartPoint.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 { if l := math.Abs(float64(num / den)); l > 1e-3 && l < 1e3 {
o.yScale -= float32(math.Log(l)) o.yScale -= float32(math.Log(l))
o.yScale = min(max(o.yScale, -1e3), 1e3) o.yScale = min(max(o.yScale, -1e3), 1e3)

View File

@ -1,6 +1,7 @@
package gioui package gioui
import ( import (
"fmt"
"math" "math"
"strconv" "strconv"
@ -12,19 +13,22 @@ import (
type ( type (
SpectrumState struct { SpectrumState struct {
resolutionNumber *NumericUpDownState resolutionNumber *NumericUpDownState
smoothingBtn *Clickable speed *NumericUpDownState
chnModeBtn *Clickable chnModeBtn *Clickable
plot *Plot plot *Plot
} }
) )
const SpectrumDisplayDb = 60 const (
SpectrumDbMin = -60
SpectrumDbMax = 12
)
func NewSpectrumState() *SpectrumState { func NewSpectrumState() *SpectrumState {
return &SpectrumState{ return &SpectrumState{
plot: NewPlot(plotRange{-4, 0}, plotRange{SpectrumDisplayDb, 0}), plot: NewPlot(plotRange{-4, 0}, plotRange{SpectrumDbMax, SpectrumDbMin}, SpectrumDbMin),
resolutionNumber: NewNumericUpDownState(), resolutionNumber: NewNumericUpDownState(),
smoothingBtn: new(Clickable), speed: NewNumericUpDownState(),
chnModeBtn: new(Clickable), chnModeBtn: new(Clickable),
} }
} }
@ -32,32 +36,20 @@ func NewSpectrumState() *SpectrumState {
func (s *SpectrumState) Layout(gtx C) D { func (s *SpectrumState) Layout(gtx C) D {
s.Update(gtx) s.Update(gtx)
t := TrackerFromContext(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 rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
var chnModeTxt string = "???" var chnModeTxt string = "???"
switch tracker.SpecChnMode(t.Model.SpecAnChannelsInt().Value()) { switch tracker.SpecChnMode(t.Model.SpecAnChannelsInt().Value()) {
case tracker.SpecChnModeCombine: case tracker.SpecChnModeSum:
chnModeTxt = "Sum" chnModeTxt = "Sum"
case tracker.SpecChnModeSeparate: case tracker.SpecChnModeSeparate:
chnModeTxt = "Separate" 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") resolution := NumUpDown(t.Model.SpecAnResolution(), t.Theme, s.resolutionNumber, "Resolution")
chnModeBtn := Btn(t.Theme, &t.Theme.Button.Filled, s.chnModeBtn, chnModeTxt, "Channel mode") 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 numchns := 0
speclen := len(t.Model.Spectrum()[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 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 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 { if chn >= numchns {
return plotRange{}, false return plotRange{}, false
} }
xr.a = float32(math.Pow(10, float64(xr.a))) xr.a = float32(math.Pow(10, float64(xr.a)))
xr.b = float32(math.Pow(10, float64(xr.b))) 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) - 1) // -1 cause we don't have the DC bin there
w2, f2 := math.Modf(float64(xr.b) * float64(speclen)) 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) x1 := max(int(w1), 0)
x2 := min(int(w2), speclen-1) x2 := min(int(w2), speclen-1)
if x1 > x2 { if x1 > x2 {
@ -110,10 +102,8 @@ func (s *SpectrumState) Layout(gtx C) D {
y2 = min(y2, sample) y2 = min(y2, sample)
} }
} }
y1 = SpectrumDisplayDb + y1 y1 = softplus((y1-SpectrumDbMin)/5)*5 + SpectrumDbMin // we "squash" the low volumes so the -Inf dB becomes -SpectrumDbMin
y2 = SpectrumDisplayDb + y2 y2 = softplus((y2-SpectrumDbMin)/5)*5 + SpectrumDbMin
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}, true return plotRange{y1, y2}, true
} }
@ -122,22 +112,23 @@ func (s *SpectrumState) Layout(gtx C) D {
freq float64 freq float64
label string label string
} }
for _, p := range []pair{ const offset = 0.343408593803857 // log10(22050/10000)
{freq: 10, label: "10 Hz"}, const startdiv = 3 * (1 << 8)
{freq: 20, label: "20 Hz"}, step := nextPowerOfTwo(int(float64(r.b-r.a)*startdiv/float64(count)) + 1)
{freq: 50, label: "50 Hz"}, start := int(math.Floor(float64(r.a+offset) * startdiv / float64(step)))
{freq: 100, label: "100 Hz"}, end := int(math.Ceil(float64(r.b+offset) * startdiv / float64(step)))
{freq: 200, label: "200 Hz"}, for i := start; i <= end; i++ {
{freq: 500, label: "500 Hz"}, lognormfreq := float32(i*step)/startdiv - offset
{freq: 1e3, label: "1 kHz"}, freq := math.Pow(10, float64(lognormfreq)) * 22050
{freq: 2e3, label: "2 kHz"}, df := freq * math.Log(10) * float64(step) / startdiv // this is roughly the difference in Hz between the ticks currently
{freq: 5e3, label: "5 kHz"}, rounding := int(math.Floor(math.Log10(df)))
{freq: 1e4, label: "10 kHz"}, r := math.Pow(10, float64(rounding))
{freq: 2e4, label: "20 kHz"}, freq = math.Round(freq/r) * r
} { tickpos := float32(math.Log10(freq / 22050))
x := float32(math.Log10(p.freq / 22050)) if rounding >= 3 {
if x >= r.a && x <= r.b { yield(tickpos, fmt.Sprintf("%.0f kHz", freq/1000))
yield(x, p.label) } 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 step := 3
var start, end int var start, end int
for { for {
start = int(math.Ceil(float64(r.b-SpectrumDisplayDb) / float64(step))) start = int(math.Ceil(float64(r.b) / float64(step)))
end = int(math.Floor(float64(r.a-SpectrumDisplayDb) / float64(step))) end = int(math.Floor(float64(r.a) / float64(step)))
step *= 2
if end-start+1 <= count*4 { // we use 4x density for the y-lines in the spectrum if end-start+1 <= count*4 { // we use 4x density for the y-lines in the spectrum
break break
} }
step *= 2
} }
for i := start; i <= end; i++ { 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 n := numchns
if biquadok { if biquadok {
n = 3 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 { 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,
layout.Rigid(leftSpacer), 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.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
layout.Rigid(chnModeBtn.Layout), layout.Rigid(chnModeBtn.Layout),
layout.Rigid(smoothBtn.Layout),
layout.Rigid(resolution.Layout),
layout.Rigid(rightSpacer), layout.Rigid(rightSpacer),
) )
}), }),
@ -184,13 +192,26 @@ func smoothInterpolate(a, b float32, t float32) float32 {
return (1-t)*a + t*b 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) { func (s *SpectrumState) Update(gtx C) {
t := TrackerFromContext(gtx) t := TrackerFromContext(gtx)
for s.chnModeBtn.Clicked(gtx) { for s.chnModeBtn.Clicked(gtx) {
t.Model.SpecAnChannelsInt().SetValue((t.SpecAnChannelsInt().Value() + 1) % int(tracker.NumSpecChnModes)) t.Model.SpecAnChannelsInt().SetValue((t.SpecAnChannelsInt().Value() + 1) % int(tracker.NumSpecChnModes))
} }
for s.smoothingBtn.Clicked(gtx) { s.resolutionNumber.Update(gtx, t.Model.SpecAnResolution())
r := t.Model.SpecAnSmoothing().Range() s.speed.Update(gtx, t.Model.SpecAnSpeed())
t.Model.SpecAnSmoothing().SetValue((t.SpecAnSmoothing().Value()+1)%(r.Max-r.Min+1) + r.Min)
}
} }

View File

@ -34,7 +34,7 @@ type (
Octave Model Octave Model
DetectorWeighting Model DetectorWeighting Model
SyntherIndex Model SyntherIndex Model
SpecAnSmoothing Model SpecAnSpeed Model
SpecAnResolution Model SpecAnResolution Model
SpecAnChannelsInt Model SpecAnChannelsInt Model
) )
@ -86,7 +86,7 @@ func (m *Model) Step() Int { return MakeInt((*Step)(m)) }
func (m *Model) Octave() Int { return MakeInt((*Octave)(m)) } func (m *Model) Octave() Int { return MakeInt((*Octave)(m)) }
func (m *Model) DetectorWeighting() Int { return MakeInt((*DetectorWeighting)(m)) } func (m *Model) DetectorWeighting() Int { return MakeInt((*DetectorWeighting)(m)) }
func (m *Model) SyntherIndex() Int { return MakeInt((*SyntherIndex)(m)) } func (m *Model) SyntherIndex() Int { return MakeInt((*SyntherIndex)(m)) }
func (m *Model) SpecAnSmoothing() Int { return MakeInt((*SpecAnSmoothing)(m)) } func (m *Model) SpecAnSpeed() Int { return MakeInt((*SpecAnSpeed)(m)) }
func (m *Model) SpecAnResolution() Int { return MakeInt((*SpecAnResolution)(m)) } func (m *Model) SpecAnResolution() Int { return MakeInt((*SpecAnResolution)(m)) }
func (m *Model) SpecAnChannelsInt() Int { return MakeInt((*SpecAnChannelsInt)(m)) } func (m *Model) SpecAnChannelsInt() Int { return MakeInt((*SpecAnChannelsInt)(m)) }
@ -158,13 +158,13 @@ func (v *DetectorWeighting) Range() IntRange { return IntRange{0, int(NumLoudnes
// SpecAn stuff // SpecAn stuff
func (v *SpecAnSmoothing) Value() int { return int(v.specAnSettings.Smooth) } func (v *SpecAnSpeed) Value() int { return int(v.specAnSettings.Smooth) }
func (v *SpecAnSmoothing) SetValue(value int) bool { func (v *SpecAnSpeed) SetValue(value int) bool {
v.specAnSettings.Smooth = SpecSmoothing(value) v.specAnSettings.Smooth = value
TrySend(v.broker.ToSpecAn, MsgToSpecAn{HasSettings: true, SpecSettings: v.specAnSettings}) TrySend(v.broker.ToSpecAn, MsgToSpecAn{HasSettings: true, SpecSettings: v.specAnSettings})
return true return true
} }
func (v *SpecAnSmoothing) Range() IntRange { return IntRange{0, int(NumSpecSmoothing) - 1} } func (v *SpecAnSpeed) Range() IntRange { return IntRange{SpecSpeedMin, SpecSpeedMax} }
func (v *SpecAnResolution) Value() int { return v.specAnSettings.Resolution } func (v *SpecAnResolution) Value() int { return v.specAnSettings.Resolution }
func (v *SpecAnResolution) SetValue(value int) bool { func (v *SpecAnResolution) SetValue(value int) bool {

View File

@ -18,13 +18,12 @@ type (
SpecAnSettings struct { SpecAnSettings struct {
ChnMode SpecChnMode ChnMode SpecChnMode
Smooth SpecSmoothing Smooth int
Resolution int Resolution int
} }
SpecChnMode int SpecChnMode int
SpecSmoothing int Spectrum [2][]float32
Spectrum [2][]float32
specTemp struct { specTemp struct {
power [2][]float32 power [2][]float32
@ -42,38 +41,24 @@ type (
) )
const ( const (
SpecResolutionMin = 7 SpecResolutionMin = -3
SpecResolutionMax = 16 SpecResolutionMax = 3
) )
const ( const (
SpecChnModeOff SpecChnMode = iota // no spectrum analysis is done to save CPU resources SpecSpeedMin = -3
SpecChnModeCombine // calculate a single combined spectrum for both channels SpecSpeedMax = 3
)
const (
SpecChnModeSum SpecChnMode = iota // calculate a single combined spectrum for both channels
SpecChnModeSeparate // calculate separate spectrums for left and right channels SpecChnModeSeparate // calculate separate spectrums for left and right channels
NumSpecChnModes NumSpecChnModes
) )
const (
SpecSmoothingMedium SpecSmoothing = iota
SpecSmoothingFast
SpecSmoothingSlow
NumSpecSmoothing
)
var spectrumSmoothingMap map[SpecSmoothing]float32 = map[SpecSmoothing]float32{
SpecSmoothingSlow: 0.1,
SpecSmoothingMedium: 0.2,
SpecSmoothingFast: 0.4,
}
func NewSpecAnalyzer(broker *Broker) *SpecAnalyzer { func NewSpecAnalyzer(broker *Broker) *SpecAnalyzer {
ret := &SpecAnalyzer{broker: broker} ret := &SpecAnalyzer{broker: broker}
ret.init(SpecAnSettings{ ret.init(SpecAnSettings{})
ChnMode: SpecChnModeCombine,
Smooth: SpecSmoothingMedium,
Resolution: 10,
})
return ret return ret
} }
@ -87,23 +72,28 @@ func (m *Model) BiquadCoeffs() (coeffs BiquadCoeffs, ok bool) {
case "filter": case "filter":
p := m.d.Song.Patch[i].Units[u].Parameters p := m.d.Song.Patch[i].Units[u].Parameters
f := float32(p["frequency"]) / 128 f := float32(p["frequency"]) / 128
res := float32(p["resonance"]) / 128 f *= f
f2 := f * f r := float32(p["resonance"]) / 128
g := f2 / (2 - f2) // in state-space, the filter has the form:
a1 := 2 * (g*g - 1) // s(n+1) = A*s(n)+B*u, where A = [1 f;-f 1-f*r-f*f] and B = [0;f]
a2 := (1 - g*(g-res)) // y(n) = C*s(n)+D*u, where
// C = [low band]
//
// The transfer function is then H(z) = C*(zI-A)^-1*B + D
// z*I-A = [z-1 -f; f z+f*r+f*f-1]
// Invert it:
// (z*I-A)^-1 = 1/det * [z+f*r+f*f-1 f; -f z-1], where det = (z-1)*(z+f*r+f*f-1)+f^2 = z^2 + z * (f*r+f*f2) + 1-f*r
// (z*I-A)^-1*B = 1/det * [-f*f; f*z-f]
var a0 float32 = 1
var a1 float32 = r*f + f*f - 2
var a2 float32 = 1 - r*f
var b0, b1, b2 float32 var b0, b1, b2 float32
if p["low"] == 1 { if p["lowpass"] == 1 {
b0 += g * g b2 = -f * f
b1 += 2 * g * g
b2 += g * g
} }
b0 += float32(p["high"]) b2 -= f * float32(p["bandpass"])
b1 += -2 * float32(p["high"]) b1 += f * float32(p["bandpass"])
b2 += float32(p["high"]) return BiquadCoeffs{a0: a0, a1: a1, a2: a2, b0: b0, b1: b1, b2: b2}, true
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": case "belleq":
f := float32(m.d.Song.Patch[i].Units[u].Parameters["frequency"]) / 128 f := float32(m.d.Song.Patch[i].Units[u].Parameters["frequency"]) / 128
band := float32(m.d.Song.Patch[i].Units[u].Parameters["bandwidth"]) / 128 band := float32(m.d.Song.Patch[i].Units[u].Parameters["bandwidth"]) / 128
@ -149,14 +139,12 @@ func (s *SpecAnalyzer) handleMsg(msg MsgToSpecAn) {
} }
switch m := msg.Data.(type) { switch m := msg.Data.(type) {
case *sointu.AudioBuffer: case *sointu.AudioBuffer:
if s.settings.ChnMode != SpecChnModeOff { buf := *m
buf := *m l := len(s.temp.window)
l := len(s.temp.window) // 50% overlap with the windows
// 50% overlap with the windows s.chunker.Process(buf, l, l>>1, func(chunk sointu.AudioBuffer) {
s.chunker.Process(buf, l, l>>1, func(chunk sointu.AudioBuffer) { TrySend(s.broker.ToModel, MsgToModel{Data: s.update(chunk)})
TrySend(s.broker.ToModel, MsgToModel{Data: s.update(chunk)}) })
})
}
s.broker.PutAudioBuffer(m) s.broker.PutAudioBuffer(m)
default: default:
// unknown message type; ignore // unknown message type; ignore
@ -164,7 +152,7 @@ func (s *SpecAnalyzer) handleMsg(msg MsgToSpecAn) {
} }
func (a *SpecAnalyzer) init(s SpecAnSettings) { func (a *SpecAnalyzer) init(s SpecAnSettings) {
s.Resolution = min(max(s.Resolution, SpecResolutionMin), SpecResolutionMax) s.Resolution = min(max(s.Resolution, SpecResolutionMin), SpecResolutionMax) + 10
a.settings = s a.settings = s
n := 1 << s.Resolution n := 1 << s.Resolution
a.temp = specTemp{ a.temp = specTemp{
@ -205,7 +193,7 @@ func (s *SpecAnalyzer) update(buf sointu.AudioBuffer) *Spectrum {
s.process(buf, 1) s.process(buf, 1)
ret[0] = append(ret[0], s.temp.power[0]...) ret[0] = append(ret[0], s.temp.power[0]...)
ret[1] = append(ret[1], s.temp.power[1]...) ret[1] = append(ret[1], s.temp.power[1]...)
case SpecChnModeCombine: case SpecChnModeSum:
s.process(buf, 0) s.process(buf, 0)
s.process(buf, 1) s.process(buf, 1)
ret[0] = append(ret[0], s.temp.power[0]...) ret[0] = append(ret[0], s.temp.power[0]...)
@ -260,7 +248,7 @@ func (sd *SpecAnalyzer) process(buf sointu.AudioBuffer, channel int) {
vek32.MulNumber_Inplace(t2[:m-1], 2) vek32.MulNumber_Inplace(t2[:m-1], 2)
// calculate difference to current spectrum and add back, multiplied by smoothing factor // calculate difference to current spectrum and add back, multiplied by smoothing factor
vek32.Sub_Inplace(t2, sd.temp.power[channel]) vek32.Sub_Inplace(t2, sd.temp.power[channel])
alpha := spectrumSmoothingMap[sd.settings.Smooth] alpha := float32(math.Pow(2, float64(sd.settings.Smooth-SpecSpeedMax)))
vek32.MulNumber_Inplace(t2, alpha) vek32.MulNumber_Inplace(t2, alpha)
vek32.Add_Inplace(sd.temp.power[channel], t2) vek32.Add_Inplace(sd.temp.power[channel], t2)
} }