"""Numpy-only audio helpers for the FastWave AX650 SDK. - WAV read/write (PCM16 / PCM24 / float32) without soundfile - polyphase resampling (scipy.signal.resample_poly compatible) - band one-hot, hann window, overlap-add Only numpy is required on the board. """ import math import numpy as np SR_OUT = 48000 N_FFT = 1024 HOP = 256 FREQ = N_FFT // 2 + 1 CHUNK = 32768 # --------------------------------------------------------------------------- WAV def read_wav(path): """Return (float32 mono in [-1, 1], sample_rate).""" import wave with wave.open(path, "rb") as w: sr = w.getframerate() n_ch = w.getnchannels() sw = w.getsampwidth() n = w.getnframes() raw = w.readframes(n) if sw == 2: x = np.frombuffer(raw, dtype="= 1 << 23, v - (1 << 24), v) x = v.astype(np.float32) / (1 << 23) elif sw == 4: x = np.frombuffer(raw, dtype=" 1: x = x.reshape(-1, n_ch).mean(axis=1) return np.ascontiguousarray(x, dtype=np.float32), sr def write_wav(path, x, sr): import wave x = np.asarray(x, dtype=np.float32) x = np.clip(x, -1.0, 1.0) pcm = (x * 32767.0).astype("= 0) & (j < hl) & (m < len(x)) acc[valid] += h[j[valid]] * x[m[valid]] return acc.astype(np.float32) def resample_to(x, sr_in, sr_out): if sr_in == sr_out: return np.asarray(x, dtype=np.float32).copy() return resample_poly(x, int(sr_out), int(sr_in)) def low_quality(x, sr_in, out_sr=SR_OUT): """Upsample the band-limited input to out_sr (same role as the training pipeline).""" n_out = int(round(len(x) * out_sr / sr_in)) y = resample_to(x, sr_in, out_sr) if len(y) > n_out: y = y[:n_out] elif len(y) < n_out: y = np.pad(y, (0, n_out - len(y))) return y # ------------------------------------------------------------------------ misc def band_onehot(sr_in, sr_out=SR_OUT, freq=FREQ): """(2, freq) float32 one-hot of usable frequency bins.""" hi = float(sr_in) / float(sr_out) band = np.zeros(freq, dtype=np.int64) band[: int(hi * freq)] = 1 # matches F.one_hot(band).transpose(1, -1): ch0 = high band, ch1 = low band out = np.zeros((2, freq), dtype=np.float32) out[0, band == 0] = 1.0 out[1, band == 1] = 1.0 return out def hann(n): return (0.5 - 0.5 * np.cos(2.0 * np.pi * np.arange(n) / n)).astype(np.float32) def chunk_bounds(n, chunk, hop): """Start positions of overlapping chunks covering n samples.""" if n <= chunk: return [0] starts = list(range(0, n - chunk + 1, hop)) if starts[-1] + chunk < n: starts.append(n - chunk) return starts def overlap_add(chunks, starts, n, chunk): """Hann-window overlap-add.""" w = hann(chunk) acc = np.zeros(n + chunk, dtype=np.float64) wsum = np.zeros(n + chunk, dtype=np.float64) for c, s in zip(chunks, starts): acc[s: s + chunk] += c * w wsum[s: s + chunk] += w wsum = np.maximum(wsum, 1e-8) return (acc[:n] / wsum[:n]).astype(np.float32)