Files
wehub-resource-sync ead81af521
Deploy to GitHub Pages / deploy (push) Failing after 0s
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

105 lines
2.3 KiB
Go

package ui
import (
"math"
"strings"
)
// renderHeartbeat draws a scrolling ECG/pulse-monitor trace using Braille dots.
// Bass energy triggers sharp QRS-complex spikes; silence produces a flat line.
// The trace scrolls left each frame for the classic hospital-monitor look.
func (v *Visualizer) renderHeartbeat() string {
height := v.Rows
dotRows := height * 4
dotCols := PanelWidth * 2
samples := v.waveBuf
n := len(samples)
// Build a y-position for each dot column from raw audio.
ypos := make([]int, dotCols)
centerY := float64(dotRows) / 2.0
amplitude := float64(dotRows) * 0.45
for x := range dotCols {
var sample float64
if n > 0 {
idx := x * n / dotCols
if idx >= n {
idx = n - 1
}
sample = samples[idx]
}
// Shape the waveform like an ECG trace: sharpen peaks, flatten noise.
shaped := sample * math.Abs(sample) // square the magnitude, keep sign
y := int(centerY - shaped*amplitude)
ypos[x] = max(0, min(dotRows-1, y))
}
grid := make([]bool, dotRows*dotCols)
// Draw the ECG trace with continuous line connections.
for x := range dotCols {
y := ypos[x]
grid[y*dotCols+x] = true
if x > 0 {
lo, hi := min(y, ypos[x-1]), max(y, ypos[x-1])
for fy := lo; fy <= hi; fy++ {
grid[fy*dotCols+x] = true
}
}
}
// Draw a faint baseline at center.
baseY := dotRows / 2
for x := range dotCols {
if !grid[baseY*dotCols+x] {
// Dashed baseline: on for 6, off for 4.
if (x/6)%2 == 0 {
grid[baseY*dotCols+x] = true
}
}
}
// Render braille characters.
lines := make([]string, height)
for row := range height {
var sb, run strings.Builder
tag := -1
for ch := range PanelWidth {
var braille rune = '\u2800'
hasTrace := false
for dr := range 4 {
for dc := range 2 {
dy := row*4 + dr
dx := ch*2 + dc
if grid[dy*dotCols+dx] {
braille |= brailleBit[dr][dc]
// Check if this is part of the trace (not baseline).
if dy != baseY {
hasTrace = true
}
}
}
}
newTag := 0 // green baseline
if hasTrace {
newTag = 2 // red trace
}
if newTag != tag {
flushStyleRun(&sb, &run, tag)
tag = newTag
}
run.WriteRune(braille)
}
flushStyleRun(&sb, &run, tag)
lines[row] = sb.String()
}
return strings.Join(lines, "\n")
}