73 lines
1.6 KiB
Go
73 lines
1.6 KiB
Go
package ui
|
||
|
||
import "strings"
|
||
|
||
// renderWave draws a Braille-character oscilloscope waveform from raw audio samples.
|
||
// Each Braille character covers a 2×4 dot grid, giving smooth sub-cell resolution.
|
||
func (v *Visualizer) renderWave() string {
|
||
height := v.Rows
|
||
charCols := PanelWidth
|
||
dotRows := height * 4
|
||
dotCols := charCols * 2
|
||
|
||
samples := v.waveBuf
|
||
n := len(samples)
|
||
|
||
// Downsample audio to one y-position per horizontal dot column.
|
||
if cap(v.waveYBuf) >= dotCols {
|
||
v.waveYBuf = v.waveYBuf[:dotCols]
|
||
} else {
|
||
v.waveYBuf = make([]int, dotCols)
|
||
}
|
||
ypos := v.waveYBuf
|
||
for x := range dotCols {
|
||
var sample float64
|
||
if n > 0 {
|
||
idx := x * n / dotCols
|
||
if idx >= n {
|
||
idx = n - 1
|
||
}
|
||
sample = samples[idx]
|
||
}
|
||
// Map sample [-1, 1] to dot row [0, dotRows-1]; center is dotRows/2.
|
||
y := int((1.0 - sample) * float64(dotRows-1) / 2.0)
|
||
ypos[x] = max(0, min(dotRows-1, y))
|
||
}
|
||
|
||
lines := make([]string, height)
|
||
for row := range height {
|
||
var content strings.Builder
|
||
dotRowStart := row * 4
|
||
|
||
for ch := range charCols {
|
||
var braille rune = '\u2800'
|
||
dotColStart := ch * 2
|
||
|
||
for dc := range 2 {
|
||
x := dotColStart + dc
|
||
y := ypos[x]
|
||
|
||
// Connect to previous point so the waveform is continuous.
|
||
prevY := y
|
||
if x > 0 {
|
||
prevY = ypos[x-1]
|
||
}
|
||
yMin := min(y, prevY)
|
||
yMax := max(y, prevY)
|
||
|
||
for dr := range 4 {
|
||
dotY := dotRowStart + dr
|
||
if dotY >= yMin && dotY <= yMax {
|
||
braille |= brailleBit[dr][dc]
|
||
}
|
||
}
|
||
}
|
||
|
||
content.WriteRune(braille)
|
||
}
|
||
lines[row] = specWrap(float64(height-1-row)/float64(height), content.String())
|
||
}
|
||
|
||
return strings.Join(lines, "\n")
|
||
}
|