"""Wool texture and a soft bleat for the sheep plushie (for a friend). Run with local/hear-venv python. wool.png: tileable cream fleece of little curls (each curl a shaded ring), 256 px. baa.wav: a small, soft "baa": glottal pulses ~330 Hz with vibrato and a tremolo flutter, shaped by /a/ formants, a quick "b" onset (lowpassed, closed lips) opening into the vowel, ~0.75 s. 44.1 kHz mono. Claude Opus 5.5 (Tessel), 2026-10-10.""" import wave from pathlib import Path import numpy as np from PIL import Image, ImageFilter HERE = Path(__file__).parent rng = np.random.default_rng(5) # ---- wool ---- S = 256 img = np.zeros((S, S), np.float32) yy, xx = np.mgrid[0:S, 0:S].astype(np.float32) for _ in range(900): cx, cy, r = rng.random() * S, rng.random() * S, 5 + rng.random() * 6 for ox in (-S, 0, S): # wrap so it tiles for oy in (-S, 0, S): d = np.hypot(xx - cx - ox, yy - cy - oy) ring = np.exp(-((d - r * 0.6) / (r * 0.35)) ** 2) img = np.maximum(img, ring * (0.7 + 0.3 * rng.random())) img = np.asarray(Image.fromarray((img * 255).astype(np.uint8)).filter(ImageFilter.GaussianBlur(0.8)), np.float32) / 255 base = np.array([253, 251, 248], np.float32) shade = np.array([222, 214, 210], np.float32) col = shade + (base - shade) * img[..., None] Image.fromarray(np.clip(col, 0, 255).astype(np.uint8)).save(HERE / "wool.png") print(HERE / "wool.png") # ---- baa ---- SR = 44100 D = 0.75 t = np.arange(int(SR * D)) / SR f0 = 330 * (1 + 0.03 * np.sin(2 * np.pi * 5.5 * t)) * (1 - 0.08 * t / D) # vibrato, falling a little ph = np.cumsum(f0) / SR src = (ph % 1.0) ** 3 - 0.25 # bright glottal-ish pulse src -= src.mean() trem = 1 - 0.35 * (0.5 + 0.5 * np.sin(2 * np.pi * 9 * t)) # sheep flutter def formant(x, f, bw): """Two-pole resonator.""" r = np.exp(-np.pi * bw / SR) a1, a2 = -2 * r * np.cos(2 * np.pi * f / SR), r * r y = np.zeros_like(x) for i in range(len(x)): y[i] = x[i] - a1 * y[i - 1] - a2 * y[i - 2] return y open_ = np.clip((t - 0.03) / 0.08, 0, 1) # lips opening: "b" vowel = formant(src, 850, 110) + 0.6 * formant(src, 1250, 140) + 0.25 * formant(src, 2600, 200) closed = formant(src, 250, 80) sig = (closed * (1 - open_) * 0.6 + vowel * open_) * trem env = np.clip(t / 0.02, 0, 1) * np.clip((D - t) / 0.25, 0, 1) ** 1.5 sig *= env sig /= np.abs(sig).max() with wave.open(str(HERE / "baa.wav"), "wb") as w: w.setnchannels(1); w.setsampwidth(2); w.setframerate(SR) w.writeframes((sig * 0.7 * 32767).astype("