#!/usr/bin/env python3 """Generate assets/office_ambience.wav — low, unintelligible office murmur. Mixed continuously into the outbound call audio (SoundfileMixer) so the caller hears a live-office room tone instead of dead digital silence while the agent thinks. Built from Kokoro voices speaking mundane phrases, overlapped and heavily low-pass filtered so no words are intelligible (nothing that could be mistaken for real conversation/PHI), plus a touch of room noise. Output: 60s mono 16 kHz PCM16 loop, quiet by design (the mixer volume scales it further). Run inside the pipecat venv: python scripts/make_ambience.py """ import os import numpy as np import soundfile as sf from kokoro_onnx import Kokoro HERE = os.path.dirname(os.path.abspath(__file__)) PROJ = os.path.dirname(HERE) MODEL_DIR = os.environ.get("KOKORO_MODEL_DIR", "/home/tocmo0nlord/pipecat-run/models") OUT = os.path.join(PROJ, "assets", "office_ambience.wav") SR = 16000 # match PIPELINE_SAMPLE_RATE (transport output rate) DUR = 60.0 # loop length in seconds RNG = np.random.default_rng(42) PHRASES = [ "Okay, so I'll move that over to Thursday and send the reminder out this afternoon.", "Could you pull the file for the three o'clock? I think it's already up front.", "Yes, we got the shipment in this morning, it's in the back on the second shelf.", "Let me transfer you over, one moment please, thank you so much for holding.", "The afternoon looks pretty full but the morning still has a couple of openings.", "I'll leave a note for the doctor and we'll follow up first thing tomorrow.", "They said the delivery should arrive before noon so we should be all set.", "Can you double check the calendar for next Tuesday? I think there's a conflict.", ] VOICES = ["af_bella", "am_michael", "bf_emma", "am_adam", "af_nicole", "bm_george"] def lowpass(x, cutoff_hz, sr): """Simple FFT brick-wall low-pass — fine for ambience shaping.""" X = np.fft.rfft(x) freqs = np.fft.rfftfreq(len(x), 1 / sr) X[freqs > cutoff_hz] = 0 return np.fft.irfft(X, n=len(x)) def main(): k = Kokoro(os.path.join(MODEL_DIR, "kokoro-v1.0.onnx"), os.path.join(MODEL_DIR, "voices-v1.0.bin")) bed = np.zeros(int(SR * DUR), dtype=np.float64) # ~18 murmured utterances scattered through the minute, different voices/speeds. for i in range(18): text = PHRASES[i % len(PHRASES)] voice = VOICES[i % len(VOICES)] samples, sr0 = k.create(text, voice=voice, speed=float(RNG.uniform(0.9, 1.1))) # Resample to SR by linear interpolation (quality is irrelevant post-filter). t_src = np.arange(len(samples)) / sr0 t_dst = np.arange(int(len(samples) * SR / sr0)) / SR s = np.interp(t_dst, t_src, samples.astype(np.float64)) s = lowpass(s, 900, SR) # muffle: through-the-wall murmur s *= RNG.uniform(0.25, 0.5) # each talker is quiet and uneven start = int(RNG.uniform(0, DUR - len(s) / SR) * SR) bed[start:start + len(s)] += s # Gentle room tone under the voices (shaped noise), so pauses aren't pure silence. noise = lowpass(RNG.standard_normal(len(bed)), 400, SR) * 0.02 bed += noise # Loop seamlessly: crossfade the last second into the first. xf = SR ramp = np.linspace(0, 1, xf) bed[:xf] = bed[:xf] * ramp + bed[-xf:] * (1 - ramp) bed = bed[:-xf] # Normalize conservatively — this is a QUIET bed; mixer volume scales it down further. bed = bed / (np.abs(bed).max() + 1e-9) * 0.35 os.makedirs(os.path.dirname(OUT), exist_ok=True) sf.write(OUT, bed.astype(np.float32), SR, subtype="PCM_16") print(f"wrote {OUT}: {len(bed)/SR:.1f}s @ {SR}Hz, peak {np.abs(bed).max():.2f}") if __name__ == "__main__": main()