diff --git a/README.md b/README.md index 7dfbacb..cd0478e 100644 --- a/README.md +++ b/README.md @@ -57,22 +57,30 @@ sudo ln -s $(pwd)/osc /usr/local/bin/osc ## Configuration -Edit the constants at the top of `src/osc.nim` or `src/osc_braille.nim`: +Runtime options can be passed via CLI flags: -```nim -const - # Phosphor physics - Decay = 0.85 # persistence per frame (0.0–1.0) - Beam = 0.4 # intensity at beam impact - Bloom = 0.08 # horizontal glow spread +```bash +./osc --palette:amber --decay:0.92 --beam:0.6 --bloom:0.12 +./osc_braille --palette:green --hot:0.8 --warm:0.45 --cool:0.12 +``` - # Phosphor glow thresholds - HotGlow = 0.7 # white-hot beam core - WarmGlow = 0.4 # bright phosphor - CoolGlow = 0.15 # dim persistence trail +Available options: - # Palette: green, amber, cyan, blue, white, red - Palette = "green" +| Option | Description | Default | +|--------|-------------|---------| +| `-p`, `--palette:NAME` | Palette name | `green` | +| `-d`, `--decay:FLOAT` | Phosphor persistence per frame | `0.85` | +| `--beam:FLOAT` | Beam impact intensity | `0.4` | +| `--bloom:FLOAT` | Horizontal glow spread | `0.08` | +| `--hot:FLOAT` | White-hot beam core threshold | `0.7` | +| `--warm:FLOAT` | Bright phosphor threshold | `0.4` | +| `--cool:FLOAT` | Dim persistence trail threshold | `0.15` | + +Show help: + +```bash +./osc --help +./osc_braille --help ``` ### Palettes diff --git a/src/osc.nim b/src/osc.nim index e021e21..4ec0a1d 100644 --- a/src/osc.nim +++ b/src/osc.nim @@ -1,28 +1,83 @@ ## Terminal oscilloscope with CRT phosphor physics. ## Zero dependencies beyond Nim stdlib + libav (dlopen at runtime). -import os, strutils +import os, strutils, parseopt import posix/termios as ptermios from posix import read import osc/canvas/[term, effects] import osc/[scope, audio] # ── Configuration ──────────────────────────────────────────────────── -# Edit these to tune the look. -const - # Phosphor physics - Decay = 0.85 # persistence per frame (0.0–1.0) - Beam = 0.4 # intensity at beam impact - Bloom = 0.08 # horizontal glow spread +type Config = object + decay: float + beam: float + bloom: float + hotGlow: float + warmGlow: float + coolGlow: float + palette: string - # Phosphor glow thresholds - HotGlow = 0.7 # white-hot beam core - WarmGlow = 0.4 # bright phosphor - CoolGlow = 0.15 # dim persistence trail +proc defaultConfig(): Config = + Config( + decay: 0.85, + beam: 0.4, + bloom: 0.08, + hotGlow: 0.7, + warmGlow: 0.4, + coolGlow: 0.15, + palette: "green" + ) - # Palette: green, amber, cyan, blue, white, red - 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 + +Palettes: + green, amber, cyan, blue, white, red + +Example: + ./osc --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 via Channel ───────────────────────────────────────── @@ -66,94 +121,171 @@ proc readKey(): char = # ── Phosphor ───────────────────────────────────────────────────────── -proc plotDot(c: var Canvas, fx, fy: float) = - let x = int(fx); let y = int(fy) - c.addPixel(x, y, Beam) - c.addPixel(x - 1, y, Bloom) - c.addPixel(x + 1, y, Bloom) +proc plotDot(c: var Canvas, fx, fy: float, cfg: Config) = + let x = int(fx) + let y = int(fy) -proc plotLine(c: var Canvas, x0, y0, x1, y1: float) = + c.addPixel(x, y, cfg.beam) + c.addPixel(x - 1, y, cfg.bloom) + c.addPixel(x + 1, y, cfg.bloom) + +proc plotLine( + c: var Canvas, + x0, y0, x1, y1: float, + cfg: Config +) = let steps = max(int(max(abs(x1 - x0), abs(y1 - y0))), 1) + for i in 0..steps: let t = i.float / steps.float - c.plotDot(x0 + (x1 - x0) * t, y0 + (y1 - y0) * t) -proc renderTrace(c: var Canvas, scope: Scope) = - if scope.sampleCount < 2: return - let w = c.pixW; let h = c.pixH - let cy = h.float / 2.0; let gain = scope.gain + c.plotDot( + x0 + (x1 - x0) * t, + y0 + (y1 - y0) * t, + cfg + ) + +proc renderTrace(c: var Canvas, scope: Scope, cfg: Config) = + if scope.sampleCount < 2: + return + + let w = c.pixW + let h = c.pixH + let cy = h.float / 2.0 + let gain = scope.gain case scope.mode of ModeYT: let visible = max(int(scope.sampleCount.float / scope.timeDiv), 2) - var px, py: float; var first = true + + var px, py: float + var first = true + for col in 0..