Remove demo signal generator
Audio capture only — shows NO SIGNAL when libav can't connect. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,8 +66,6 @@ proc main() =
|
|||||||
of gsGrid: gsCross
|
of gsGrid: gsCross
|
||||||
of gsCross: gsOff
|
of gsCross: gsOff
|
||||||
of gsOff: gsGrid
|
of gsOff: gsGrid
|
||||||
of Key.D:
|
|
||||||
audio.cyclePreset()
|
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
## Audio capture via libavdevice/libavformat (dlopen at runtime),
|
## Audio capture via libavdevice/libavformat (dlopen at runtime).
|
||||||
## with fallback to demo signal.
|
|
||||||
|
|
||||||
import osproc, strutils, math
|
import osproc, strutils
|
||||||
import scope
|
import scope
|
||||||
|
|
||||||
# ── libav C helper bindings ──────────────────────────────────────────
|
# ── libav C helper bindings ──────────────────────────────────────────
|
||||||
@@ -10,8 +9,8 @@ import scope
|
|||||||
{.passL: "-ldl".}
|
{.passL: "-ldl".}
|
||||||
|
|
||||||
type
|
type
|
||||||
AVFormatContext = object # opaque, only used as pointer
|
AVFormatContext = object
|
||||||
AVPacket = object # opaque, only used as pointer
|
AVPacket = object
|
||||||
|
|
||||||
proc av_helper_init(): cint {.importc, cdecl.}
|
proc av_helper_init(): cint {.importc, cdecl.}
|
||||||
proc av_helper_open_pulse(ctx: ptr ptr AVFormatContext,
|
proc av_helper_open_pulse(ctx: ptr ptr AVFormatContext,
|
||||||
@@ -46,102 +45,62 @@ proc findMonitorSource(): string =
|
|||||||
except: discard
|
except: discard
|
||||||
""
|
""
|
||||||
|
|
||||||
# ── Audio capture types ──────────────────────────────────────────────
|
# ── Audio capture ────────────────────────────────────────────────────
|
||||||
|
|
||||||
type
|
type
|
||||||
AudioMode* = enum
|
|
||||||
amLive ## Direct libav capture
|
|
||||||
amDemo ## Built-in synthesized waveforms
|
|
||||||
|
|
||||||
AudioCapture* = object
|
AudioCapture* = object
|
||||||
mode*: AudioMode
|
|
||||||
fmtCtx: ptr AVFormatContext
|
fmtCtx: ptr AVFormatContext
|
||||||
packet: ptr AVPacket
|
packet: ptr AVPacket
|
||||||
streamIdx: cint
|
streamIdx: cint
|
||||||
phase: float
|
live*: bool
|
||||||
demoFreqL*, demoFreqR*: float
|
|
||||||
demoPreset*: int
|
|
||||||
|
|
||||||
# ── Start / stop ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
proc startAudio*(): AudioCapture =
|
proc startAudio*(): AudioCapture =
|
||||||
let monitor = findMonitorSource()
|
let monitor = findMonitorSource()
|
||||||
if monitor.len > 0:
|
if monitor.len == 0: return
|
||||||
block libav:
|
|
||||||
if av_helper_init() < 0: break libav
|
if av_helper_init() < 0: return
|
||||||
|
|
||||||
var ctx: ptr AVFormatContext = nil
|
var ctx: ptr AVFormatContext = nil
|
||||||
if av_helper_open_pulse(addr ctx, monitor.cstring) < 0: break libav
|
if av_helper_open_pulse(addr ctx, monitor.cstring) < 0: return
|
||||||
if av_helper_find_stream_info(ctx) < 0:
|
if av_helper_find_stream_info(ctx) < 0:
|
||||||
av_helper_close(addr ctx)
|
av_helper_close(addr ctx)
|
||||||
break libav
|
return
|
||||||
|
|
||||||
let idx = av_helper_find_audio_stream(ctx)
|
let idx = av_helper_find_audio_stream(ctx)
|
||||||
let pkt = av_helper_packet_alloc()
|
let pkt = av_helper_packet_alloc()
|
||||||
if pkt != nil:
|
if pkt == nil:
|
||||||
return AudioCapture(
|
|
||||||
mode: amLive, fmtCtx: ctx, packet: pkt,
|
|
||||||
streamIdx: idx.cint,
|
|
||||||
demoFreqL: 440.0, demoFreqR: 330.0)
|
|
||||||
av_helper_close(addr ctx)
|
av_helper_close(addr ctx)
|
||||||
|
return
|
||||||
|
|
||||||
AudioCapture(mode: amDemo, demoFreqL: 440.0, demoFreqR: 330.0)
|
AudioCapture(fmtCtx: ctx, packet: pkt, streamIdx: idx.cint, live: true)
|
||||||
|
|
||||||
proc stop*(cap: var AudioCapture) =
|
proc stop*(cap: var AudioCapture) =
|
||||||
if cap.mode == amLive:
|
if cap.live:
|
||||||
if cap.packet != nil: av_helper_packet_free(addr cap.packet)
|
if cap.packet != nil: av_helper_packet_free(addr cap.packet)
|
||||||
if cap.fmtCtx != nil: av_helper_close(addr cap.fmtCtx)
|
if cap.fmtCtx != nil: av_helper_close(addr cap.fmtCtx)
|
||||||
|
|
||||||
proc sourceLabel*(cap: AudioCapture): string =
|
proc sourceLabel*(cap: AudioCapture): string =
|
||||||
if cap.mode == amLive: "LIVE" else: "DEMO"
|
if cap.live: "LIVE" else: "NO SIGNAL"
|
||||||
|
|
||||||
# ── Preset cycling ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
proc cyclePreset*(cap: var AudioCapture) =
|
|
||||||
if cap.mode != amDemo: return
|
|
||||||
cap.demoPreset = (cap.demoPreset + 1) mod 4
|
|
||||||
case cap.demoPreset
|
|
||||||
of 0: cap.demoFreqL = 440.0; cap.demoFreqR = 330.0 # 4:3
|
|
||||||
of 1: cap.demoFreqL = 440.0; cap.demoFreqR = 440.0 # 1:1
|
|
||||||
of 2: cap.demoFreqL = 440.0; cap.demoFreqR = 220.0 # 2:1
|
|
||||||
of 3: cap.demoFreqL = 440.0; cap.demoFreqR = 293.3 # 3:2
|
|
||||||
else: discard
|
|
||||||
|
|
||||||
# ── Sample reading ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
proc readSamples*(cap: var AudioCapture, scope: var Scope) =
|
proc readSamples*(cap: var AudioCapture, scope: var Scope) =
|
||||||
case cap.mode
|
if not cap.live: return
|
||||||
of amLive:
|
|
||||||
const frameSize = 4 # 2ch × 16-bit
|
const frameSize = 4 # 2ch × 16-bit
|
||||||
var totalSamples = 0
|
var total = 0
|
||||||
while totalSamples < scope.samplesL.len:
|
while total < scope.samplesL.len:
|
||||||
let ret = av_helper_read_frame(cap.fmtCtx, cap.packet)
|
let ret = av_helper_read_frame(cap.fmtCtx, cap.packet)
|
||||||
if ret < 0: break
|
if ret < 0: break
|
||||||
if av_helper_packet_stream(cap.packet) == cap.streamIdx:
|
if av_helper_packet_stream(cap.packet) == cap.streamIdx:
|
||||||
let data = av_helper_packet_data(cap.packet)
|
let data = av_helper_packet_data(cap.packet)
|
||||||
let size = av_helper_packet_size(cap.packet)
|
let size = av_helper_packet_size(cap.packet)
|
||||||
let frames = size div frameSize
|
for i in 0..<(size div frameSize):
|
||||||
for i in 0..<frames:
|
if total >= scope.samplesL.len: break
|
||||||
if totalSamples >= scope.samplesL.len: break
|
|
||||||
let off = i * frameSize
|
let off = i * frameSize
|
||||||
let left = cast[int16]((data[off + 1].uint16 shl 8) or data[off].uint16)
|
let left = cast[int16]((data[off + 1].uint16 shl 8) or data[off].uint16)
|
||||||
let right = cast[int16]((data[off + 3].uint16 shl 8) or data[off + 2].uint16)
|
let right = cast[int16]((data[off + 3].uint16 shl 8) or data[off + 2].uint16)
|
||||||
scope.samplesL[totalSamples] = left.float / 32768.0
|
scope.samplesL[total] = left.float / 32768.0
|
||||||
scope.samplesR[totalSamples] = right.float / 32768.0
|
scope.samplesR[total] = right.float / 32768.0
|
||||||
totalSamples += 1
|
total += 1
|
||||||
av_helper_packet_unref(cap.packet)
|
av_helper_packet_unref(cap.packet)
|
||||||
if totalSamples > 0: break
|
if total > 0: break
|
||||||
scope.sampleCount = totalSamples
|
scope.sampleCount = total
|
||||||
|
|
||||||
of amDemo:
|
|
||||||
scope.sampleCount = scope.samplesL.len
|
|
||||||
let cycles = 3.0 / scope.timeDiv
|
|
||||||
let drift = sin(cap.phase * 0.3) * 0.1
|
|
||||||
let rL = cap.demoFreqL / 440.0
|
|
||||||
let rR = cap.demoFreqR / 440.0
|
|
||||||
for i in 0..<scope.sampleCount:
|
|
||||||
let t = cap.phase + (i.float / scope.sampleCount.float) * cycles * 2.0 * PI
|
|
||||||
scope.samplesL[i] = sin(t * rL) * 0.7 +
|
|
||||||
sin(t * rL * 2.0) * 0.15 +
|
|
||||||
sin(t * rL * 3.0 + drift) * 0.08
|
|
||||||
scope.samplesR[i] = sin(t * rR + 0.5) * 0.7 +
|
|
||||||
sin(t * rR * 2.0 + 0.3) * 0.2
|
|
||||||
cap.phase += 0.05
|
|
||||||
|
|||||||
Reference in New Issue
Block a user