From 0bb70e7966bd6fcf676eb2e686bafed23cdba7e0 Mon Sep 17 00:00:00 2001 From: Chris Frankland-Wright Date: Tue, 24 Jun 2025 21:39:53 +0100 Subject: [PATCH] improved probe --- ltc_probe.py | 64 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/ltc_probe.py b/ltc_probe.py index 4b63d37..93c3c88 100644 --- a/ltc_probe.py +++ b/ltc_probe.py @@ -2,8 +2,8 @@ """ ltc_probe.py -Probes audio input to detect LTC-like signal characteristics. -Reports zero crossings and estimated frequency. +Improved LTC-like signal probe — detects pulse duration patterns +consistent with bi-phase mark code used in SMPTE LTC. """ import numpy as np @@ -12,32 +12,60 @@ import sounddevice as sd DURATION = 1.0 # seconds SAMPLERATE = 48000 CHANNELS = 1 -EXPECTED_FREQ = 2000 # Approx LTC edge rate at 25fps +MIN_EDGES = 1000 # sanity threshold -def count_zero_crossings(signal): - signal = signal.flatten() - signs = np.sign(signal) - return np.count_nonzero(np.diff(signs)) +def detect_rising_edges(signal): + # signal: flattened 1D numpy array + above_zero = signal > 0 + edges = np.where(np.logical_and(~above_zero[:-1], above_zero[1:]))[0] + return edges -def verdict(freq): - if freq < 100: - return "❌ No signal detected (flatline or silence)" - elif 1800 <= freq <= 2200: - return f"✅ LTC-like signal detected (freq: {freq:.1f} Hz)" +def analyze_pulse_durations(edges, samplerate): + durations = np.diff(edges) / samplerate # in seconds + if len(durations) == 0: + return None + + short_pulse_threshold = np.median(durations) * 1.5 + short = durations[durations <= short_pulse_threshold] + long = durations[durations > short_pulse_threshold] + + return { + "count": len(durations), + "avg_width_ms": np.mean(durations) * 1000, + "short_pulses": len(short), + "long_pulses": len(long), + "short_pct": (len(short) / len(durations)) * 100, + "long_pct": (len(long) / len(durations)) * 100 + } + +def verdict(pulse_data): + if pulse_data is None or pulse_data["count"] < MIN_EDGES: + return "❌ No signal or not enough pulses" + elif 30 < pulse_data["short_pct"] < 70: + return f"✅ LTC-like bi-phase signal detected ({pulse_data['count']} pulses)" else: - return f"⚠️ Unstable or non-LTC signal (freq: {freq:.1f} Hz)" + return f"⚠️ Inconsistent signal — may be non-LTC or noisy" def main(): print("🔍 Capturing 1 second of audio for LTC probing...") audio = sd.rec(int(DURATION * SAMPLERATE), samplerate=SAMPLERATE, channels=CHANNELS, dtype='float32') sd.wait() - crossings = count_zero_crossings(audio) - estimated_freq = crossings / DURATION + signal = audio.flatten() + edges = detect_rising_edges(signal) + pulse_data = analyze_pulse_durations(edges, SAMPLERATE) - print(f"Zero crossings: {crossings}") - print(f"Estimated frequency: {estimated_freq:.1f} Hz") - print(verdict(estimated_freq)) + print(f"\n📊 Pulse Analysis:") + if pulse_data: + print(f" Total pulses: {pulse_data['count']}") + print(f" Avg pulse width: {pulse_data['avg_width_ms']:.2f} ms") + print(f" Short pulses: {pulse_data['short_pulses']} ({pulse_data['short_pct']:.1f}%)") + print(f" Long pulses: {pulse_data['long_pulses']} ({pulse_data['long_pct']:.1f}%)") + else: + print(" Not enough data to analyze.") + + print("\n🧭 Verdict:") + print(" ", verdict(pulse_data)) if __name__ == "__main__": main()