|
@@ -6,10 +6,10 @@ import struct
|
|
|
import numpy as np
|
|
import numpy as np
|
|
|
import fcntl
|
|
import fcntl
|
|
|
|
|
|
|
|
-# Lobotomize the Linux kernel pipe so it can't hoard future audio
|
|
|
|
|
|
|
+# Loosen the lobotomy slightly to 8192 bytes.
|
|
|
try:
|
|
try:
|
|
|
F_SETPIPE_SZ = 1031
|
|
F_SETPIPE_SZ = 1031
|
|
|
- fcntl.fcntl(sys.stdin.fileno(), F_SETPIPE_SZ, 4096)
|
|
|
|
|
|
|
+ fcntl.fcntl(sys.stdin.fileno(), F_SETPIPE_SZ, 8192)
|
|
|
except Exception:
|
|
except Exception:
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
@@ -17,22 +17,22 @@ except Exception:
|
|
|
UDP_IP = os.environ.get("UDP_IP", "239.0.0.1")
|
|
UDP_IP = os.environ.get("UDP_IP", "239.0.0.1")
|
|
|
UDP_PORT = int(os.environ.get("UDP_PORT", "11988"))
|
|
UDP_PORT = int(os.environ.get("UDP_PORT", "11988"))
|
|
|
|
|
|
|
|
-# Audio stream constants
|
|
|
|
|
-SAMPLE_RATE = 44100
|
|
|
|
|
-CHUNK_SIZE = 1024
|
|
|
|
|
|
|
+# Audio stream constants optimized for 24kHz downsampling
|
|
|
|
|
+SAMPLE_RATE = 24000
|
|
|
|
|
+CHUNK_SIZE = 512
|
|
|
CHUNK_DURATION = CHUNK_SIZE / SAMPLE_RATE
|
|
CHUNK_DURATION = CHUNK_SIZE / SAMPLE_RATE
|
|
|
FRAME_BYTES = CHUNK_SIZE * 4
|
|
FRAME_BYTES = CHUNK_SIZE * 4
|
|
|
-MAX_SILENT_FRAMES = 43 # ~1 second of silence at 43.06 FPS
|
|
|
|
|
|
|
+MAX_SILENT_FRAMES = 43
|
|
|
|
|
|
|
|
-# Hardcoded DSP tuning
|
|
|
|
|
-FREQ_MIN = 44.0 # Aligned with physical 43.06Hz FFT bin resolution
|
|
|
|
|
-FREQ_MAX = 12000.0 # Capture crisp cymbals and high transients
|
|
|
|
|
|
|
+# Hardcoded DSP tuning for violent clipping
|
|
|
|
|
+FREQ_MIN = 44.0
|
|
|
|
|
+FREQ_MAX = 12000.0
|
|
|
SILENCE_THRESHOLD = 0.5
|
|
SILENCE_THRESHOLD = 0.5
|
|
|
-GAIN_MIN = 800.0 # Floor: prevents squashing heavily mastered EDM
|
|
|
|
|
-GAIN_MAX = 8500.0 # Ceiling: prevents boosting background tape hiss
|
|
|
|
|
-TILT_EXPONENT = 0.42 # Treble tilt compensation
|
|
|
|
|
-DECAY_RATE = 0.95 # Faster recovery (~1s) for actual snap and bounce
|
|
|
|
|
-CONTRAST_EXPONENT = 1.2 # >1.0 crushes noise floor & exaggerates beats
|
|
|
|
|
|
|
+GAIN_MIN = 800.0
|
|
|
|
|
+GAIN_MAX = 1500.0
|
|
|
|
|
+TILT_EXPONENT = 0.45
|
|
|
|
|
+DECAY_RATE = 0.40
|
|
|
|
|
+CONTRAST_EXPONENT = 4.5
|
|
|
|
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
|
|
@@ -49,7 +49,6 @@ band_centers = np.sqrt(FREQ_EDGES[:-1] * FREQ_EDGES[1:])
|
|
|
TILT_WEIGHTS = ((band_centers / FREQ_MIN) ** TILT_EXPONENT).astype(np.float32)
|
|
TILT_WEIGHTS = ((band_centers / FREQ_MIN) ** TILT_EXPONENT).astype(np.float32)
|
|
|
|
|
|
|
|
# Build a C-optimized Matrix for dot-product binning
|
|
# Build a C-optimized Matrix for dot-product binning
|
|
|
-# Shape: (16 bands, 513 FFT bins).
|
|
|
|
|
BIN_MATRIX = np.zeros((16, len(fft_freqs)), dtype=np.float32)
|
|
BIN_MATRIX = np.zeros((16, len(fft_freqs)), dtype=np.float32)
|
|
|
|
|
|
|
|
for i in range(16):
|
|
for i in range(16):
|
|
@@ -62,7 +61,6 @@ for i in range(16):
|
|
|
closest = non_zero_bins[np.argmin(np.abs(fft_freqs[non_zero_bins] - low))]
|
|
closest = non_zero_bins[np.argmin(np.abs(fft_freqs[non_zero_bins] - low))]
|
|
|
idx = [closest]
|
|
idx = [closest]
|
|
|
|
|
|
|
|
- # The mean is just 1.0 / count. Multiply by the tilt weight immediately.
|
|
|
|
|
BIN_MATRIX[i, idx] = (1.0 / len(idx)) * TILT_WEIGHTS[i]
|
|
BIN_MATRIX[i, idx] = (1.0 / len(idx)) * TILT_WEIGHTS[i]
|
|
|
|
|
|
|
|
# State variables
|
|
# State variables
|
|
@@ -71,21 +69,28 @@ silence_frames = 0
|
|
|
running_peak = 0.05
|
|
running_peak = 0.05
|
|
|
STRUCT_FMT_V2 = "<6s2xffB3x16sd"
|
|
STRUCT_FMT_V2 = "<6s2xffB3x16sd"
|
|
|
|
|
|
|
|
|
|
+def read_exact_chunk(fd, size):
|
|
|
|
|
+ buf = bytearray(size)
|
|
|
|
|
+ view = memoryview(buf)
|
|
|
|
|
+ pos = 0
|
|
|
|
|
+ while pos < size:
|
|
|
|
|
+ chunk = fd.readinto(view[pos:])
|
|
|
|
|
+ if not chunk:
|
|
|
|
|
+ return None
|
|
|
|
|
+ pos += chunk
|
|
|
|
|
+ return bytes(buf)
|
|
|
|
|
+
|
|
|
start_time = None
|
|
start_time = None
|
|
|
frames_processed = 0
|
|
frames_processed = 0
|
|
|
|
|
|
|
|
while True:
|
|
while True:
|
|
|
t0 = time.perf_counter()
|
|
t0 = time.perf_counter()
|
|
|
- raw_data = sys.stdin.buffer.read(FRAME_BYTES)
|
|
|
|
|
|
|
+ raw_data = read_exact_chunk(sys.stdin.buffer, FRAME_BYTES)
|
|
|
read_duration = time.perf_counter() - t0
|
|
read_duration = time.perf_counter() - t0
|
|
|
|
|
|
|
|
- # Short-read survival patch
|
|
|
|
|
if not raw_data:
|
|
if not raw_data:
|
|
|
break
|
|
break
|
|
|
- if len(raw_data) < FRAME_BYTES:
|
|
|
|
|
- continue
|
|
|
|
|
|
|
|
|
|
- # Micro-reset baseline to permanently kill accumulated audio pipeline drift
|
|
|
|
|
if read_duration > 0.015:
|
|
if read_duration > 0.015:
|
|
|
start_time = time.perf_counter()
|
|
start_time = time.perf_counter()
|
|
|
frames_processed = 0
|
|
frames_processed = 0
|
|
@@ -94,7 +99,6 @@ while True:
|
|
|
if start_time is None:
|
|
if start_time is None:
|
|
|
start_time = time.perf_counter()
|
|
start_time = time.perf_counter()
|
|
|
|
|
|
|
|
- # Audio ingestion and magnitude extraction
|
|
|
|
|
audio = np.frombuffer(raw_data, dtype=np.int16).astype(np.float32)
|
|
audio = np.frombuffer(raw_data, dtype=np.int16).astype(np.float32)
|
|
|
left = audio[0::2]
|
|
left = audio[0::2]
|
|
|
right = audio[1::2]
|
|
right = audio[1::2]
|
|
@@ -113,20 +117,16 @@ while True:
|
|
|
windowed = mono * HANNING_WINDOW
|
|
windowed = mono * HANNING_WINDOW
|
|
|
fft_vals = np.abs(np.fft.rfft(windowed)) * (2.0 / CHUNK_SIZE)
|
|
fft_vals = np.abs(np.fft.rfft(windowed)) * (2.0 / CHUNK_SIZE)
|
|
|
|
|
|
|
|
- # C-optimized single matrix multiplication replaces the 16-step for loop and tilt math
|
|
|
|
|
tilted_energies = BIN_MATRIX @ fft_vals
|
|
tilted_energies = BIN_MATRIX @ fft_vals
|
|
|
|
|
|
|
|
- # Track rolling peak
|
|
|
|
|
current_max = float(np.max(tilted_energies))
|
|
current_max = float(np.max(tilted_energies))
|
|
|
if current_max > running_peak:
|
|
if current_max > running_peak:
|
|
|
running_peak = current_max
|
|
running_peak = current_max
|
|
|
else:
|
|
else:
|
|
|
running_peak = max(0.005, running_peak * DECAY_RATE)
|
|
running_peak = max(0.005, running_peak * DECAY_RATE)
|
|
|
|
|
|
|
|
- # Dynamic gain bounded by floor and ceiling
|
|
|
|
|
dynamic_gain = np.clip(1.0 / running_peak, GAIN_MIN / 255.0, GAIN_MAX / 255.0)
|
|
dynamic_gain = np.clip(1.0 / running_peak, GAIN_MIN / 255.0, GAIN_MAX / 255.0)
|
|
|
|
|
|
|
|
- # Normalize 0.0 to 1.0, apply contrast exponent, scale to 0-255
|
|
|
|
|
normalized = np.clip(tilted_energies * dynamic_gain, 0.0, 1.0)
|
|
normalized = np.clip(tilted_energies * dynamic_gain, 0.0, 1.0)
|
|
|
contrasted = (normalized ** CONTRAST_EXPONENT) * 255.0
|
|
contrasted = (normalized ** CONTRAST_EXPONENT) * 255.0
|
|
|
fft_result = bytes(np.clip(contrasted, 0, 255).astype(np.uint8))
|
|
fft_result = bytes(np.clip(contrasted, 0, 255).astype(np.uint8))
|
|
@@ -146,13 +146,17 @@ while True:
|
|
|
except Exception:
|
|
except Exception:
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- # Metronome pacing
|
|
|
|
|
|
|
+ # Hybrid PLL Metronome to provide flawless backpressure
|
|
|
frames_processed += 1
|
|
frames_processed += 1
|
|
|
target_time = start_time + (frames_processed * CHUNK_DURATION)
|
|
target_time = start_time + (frames_processed * CHUNK_DURATION)
|
|
|
sleep_time = target_time - time.perf_counter()
|
|
sleep_time = target_time - time.perf_counter()
|
|
|
|
|
|
|
|
- if sleep_time > 0:
|
|
|
|
|
- time.sleep(sleep_time)
|
|
|
|
|
- elif sleep_time < -1.0:
|
|
|
|
|
|
|
+ if sleep_time > 0.002:
|
|
|
|
|
+ time.sleep(sleep_time - 0.001)
|
|
|
|
|
+
|
|
|
|
|
+ while time.perf_counter() < target_time:
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ if sleep_time < -1.0:
|
|
|
start_time = time.perf_counter()
|
|
start_time = time.perf_counter()
|
|
|
frames_processed = 0
|
|
frames_processed = 0
|