## Terminal oscilloscope — braille dot rendering (4× resolution). import os, strutils, parseopt import posix/termios as ptermios from posix import read import osc/canvas/[braille, term, effects] import osc/[scope, audio] # ── Configuration ──────────────────────────────────────────────────── type Config = object decay: float beam: float bloom: float hotGlow: float warmGlow: float coolGlow: float palette: string proc defaultConfig(): Config = Config( decay: 0.85, beam: 0.4, bloom: 0.08, hotGlow: 0.7, warmGlow: 0.4, coolGlow: 0.15, palette: "green" ) proc usage() = echo """ Terminal oscilloscope Options: -p, --palette:NAME Palette name, default: green -d, --decay:FLOAT Phosphor decay, default: 0.85 --beam:FLOAT Beam intensity, default: 0.4 --bloom:FLOAT Bloom intensity, default: 0.08 --hot:FLOAT Hot glow, default: 0.7 --warm:FLOAT Warm glow, default: 0.4 --cool:FLOAT Cool glow, default: 0.15 -h, --help Show this help Example: ./oscilloscope --palette:amber --decay:0.92 --beam:0.6 --bloom:0.12 """ proc parseConfig(): Config = result = defaultConfig() for kind, key, val in getopt(): case kind of cmdLongOption, cmdShortOption: case key of "h", "help": usage() quit 0 of "palette", "p": result.palette = val of "decay", "d": result.decay = parseFloat(val) of "beam": result.beam = parseFloat(val) of "bloom": result.bloom = parseFloat(val) of "hot": result.hotGlow = parseFloat(val) of "warm": result.warmGlow = parseFloat(val) of "cool": result.coolGlow = parseFloat(val) else: quit "Unknown option: " & key else: discard # ── Audio thread ───────────────────────────────────────────────────── type AudioFrame = object samples: array[4096, array[2, float]] count: int var audioChan: Channel[AudioFrame] audioRunning: bool proc audioThread(aud: ptr AudioCapture) {.thread.} = var scope = initScope(1, 1) while audioRunning: aud[].readSamples(scope) if scope.sampleCount > 0: var frame: AudioFrame frame.count = min(scope.sampleCount, 4096) for i in 0..