Files
terminal-oscilloscope/src/osc.nim
rolandnsharp 1f320b7ff1 Handle terminal resize dynamically
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:59:01 +10:00

91 lines
2.0 KiB
Nim
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Terminal oscilloscope with CRT phosphor physics.
##
## - CRT boot/shutdown animations (ported from AetherTune)
## - Y-T (time-domain) and X-Y (Lissajous) display modes
## - Phosphor persistence with bloom and decay
## - Half-block rendering for 2× vertical resolution
## - Live audio via libavdevice (dlopen, zero dependencies)
import illwill, os
import osc/[effects, phosphor, scope, audio]
proc exitProc() {.noconv.} =
illwillDeinit()
showCursor()
quit(0)
proc main() =
illwillInit(fullscreen = true)
setControlCHook(exitProc)
hideCursor()
var w = terminalWidth()
var h = terminalHeight()
var tb = newTerminalBuffer(w, h)
crtTurnOn(tb, w, h)
var scope = initScope(w, h)
var audio = startAudio()
var running = true
while running:
let nw = terminalWidth()
let nh = terminalHeight()
if nw != w or nh != h:
w = nw
h = nh
scope.resize(w, h)
if not scope.frozen:
audio.readSamples(scope)
scope.phosphor.decay()
if not scope.frozen:
scope.renderTrace()
tb = newTerminalBuffer(w, h)
scope.phosphor.render(tb)
drawGraticule(tb, w, h, scope.grid)
drawHUD(tb, w, h, scope, audio.sourceLabel)
tb.display()
let key = getKey()
case key
of Key.Q, Key.Escape:
running = false
of Key.M:
scope.mode = if scope.mode == ModeYT: ModeXY else: ModeYT
of Key.Plus, Key.Equals:
scope.gain = min(scope.gain * 1.3, 20.0)
of Key.Minus:
scope.gain = max(scope.gain / 1.3, 0.5)
of Key.RightBracket:
scope.timeDiv = min(scope.timeDiv * 1.5, 16.0)
of Key.LeftBracket:
scope.timeDiv = max(scope.timeDiv / 1.5, 0.25)
of Key.Space:
scope.frozen = not scope.frozen
of Key.G:
scope.grid = case scope.grid
of gsGrid: gsCross
of gsCross: gsOff
of gsOff: gsGrid
else:
discard
sleep(16)
audio.stop()
crtTurnOff(tb, w, h)
tb = newTerminalBuffer(w, h)
tb.display()
sleep(100)
illwillDeinit()
showCursor()
when isMainModule:
main()