97 lines
2.3 KiB
Go
97 lines
2.3 KiB
Go
package ui
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
// renderScope draws a Lissajous-style XY oscilloscope using Braille dots.
|
|
// Since the audio tap is mono, a phase-delayed copy of the signal is used as
|
|
// the Y axis. The delay slowly oscillates over time, producing continuously
|
|
// evolving Lissajous figures — circles for pure tones, complex knots for music.
|
|
func (v *Visualizer) renderScope() string {
|
|
height := v.Rows
|
|
dotRows := height * 4
|
|
dotCols := PanelWidth * 2
|
|
|
|
samples := v.waveBuf
|
|
n := len(samples)
|
|
|
|
grid := make([]bool, dotRows*dotCols)
|
|
|
|
if n > 1 {
|
|
// Phase delay slowly oscillates for evolving Lissajous patterns.
|
|
baseDelay := n / 4
|
|
wobble := int(math.Sin(float64(v.frame)*0.02) * float64(n/8))
|
|
delay := max(1, min(n-1, baseDelay+wobble))
|
|
|
|
// Plot ~512 XY pairs for a dense, smooth figure.
|
|
plotPoints := min(n-delay, 512)
|
|
step := max(1, (n-delay)/plotPoints)
|
|
|
|
var prevDotX, prevDotY int
|
|
first := true
|
|
|
|
for i := 0; i+delay < n; i += step {
|
|
x := samples[i]
|
|
y := samples[i+delay]
|
|
|
|
// Map [-1, 1] to dot coordinates.
|
|
dotX := int((x + 1.0) * 0.5 * float64(dotCols-1))
|
|
dotY := int((1.0 - y) * 0.5 * float64(dotRows-1))
|
|
dotX = max(0, min(dotCols-1, dotX))
|
|
dotY = max(0, min(dotRows-1, dotY))
|
|
|
|
grid[dotY*dotCols+dotX] = true
|
|
|
|
// Interpolate between consecutive points for smoother curves.
|
|
if !first {
|
|
dx := dotX - prevDotX
|
|
dy := dotY - prevDotY
|
|
adx := dx
|
|
if adx < 0 {
|
|
adx = -adx
|
|
}
|
|
ady := dy
|
|
if ady < 0 {
|
|
ady = -ady
|
|
}
|
|
steps := max(adx, ady)
|
|
if steps > 0 && steps < 30 {
|
|
for s := 1; s < steps; s++ {
|
|
mx := prevDotX + dx*s/steps
|
|
my := prevDotY + dy*s/steps
|
|
if mx >= 0 && mx < dotCols && my >= 0 && my < dotRows {
|
|
grid[my*dotCols+mx] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
prevDotX = dotX
|
|
prevDotY = dotY
|
|
first = false
|
|
}
|
|
}
|
|
|
|
// Convert dot grid to Braille characters.
|
|
lines := make([]string, height)
|
|
for row := range height {
|
|
var content strings.Builder
|
|
for ch := range PanelWidth {
|
|
var braille rune = '\u2800'
|
|
for dr := range 4 {
|
|
for dc := range 2 {
|
|
if grid[(row*4+dr)*dotCols+ch*2+dc] {
|
|
braille |= brailleBit[dr][dc]
|
|
}
|
|
}
|
|
}
|
|
content.WriteRune(braille)
|
|
}
|
|
lines[row] = specWrap(float64(height-1-row)/float64(height), content.String())
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
}
|