#!/usr/bin/env python3 """ FACTORY DEFAULT RESONANCE ENGINE (Isolated Pure Gradio Factory) This is the ORIGINAL working 4-layer sonification engine, completely separate from LYGO/LDQ. - NO LDQ imports, NO use_ldq, NO sidechain, NO genre manifold, NO perceptual, NO fingerprint. - Pure image-driven stereo soundscapes using only the classic layers: 1. Texture Floor (filtered noise from edge density) 2. Drones (saw from structural lines) 3. Melody (sine from contours) 4. Glitch (short tones from FAST keypoints) Goal: Reliable "industry quality beats / musical" output as the default factory experience. This file is the drop-in clean factory. The LYGO tab continues to use the other engine. Based on the clean original implementations that produced good musical results (historical working versions). """ import cv2 import numpy as np import soundfile as sf import math from pathlib import Path from typing import Optional, Dict, Any __version__ = "factory-1.0.0-isolated" # Artistic Presets tuned for musical / "industry beats" factory output # (balanced noise, strong drones + contour melodies for rhythmic feel, tasteful glitch) PRESETS = { "raw": { "noise_vol": 0.06, "drone_vol": 0.07, "note_vol": 0.12, "glitch_vol": 0.02, "noise_lowpass_hz": 800, }, "ambient": { "noise_vol": 0.04, "drone_vol": 0.09, "note_vol": 0.10, "glitch_vol": 0.008, "drone_attack": 5.0, "drone_decay": 6.0, "note_decay": 0.55, "noise_lowpass_hz": 450, }, "glitch": { "noise_vol": 0.07, "drone_vol": 0.05, "note_vol": 0.09, "glitch_vol": 0.06, "max_glitches": 25, "noise_lowpass_hz": 2200, }, "ethereal": { "noise_vol": 0.03, "drone_vol": 0.08, "note_vol": 0.11, "glitch_vol": 0.01, "note_decay": 0.6, "noise_lowpass_hz": 380, }, "cinematic": { "noise_vol": 0.05, "drone_vol": 0.08, "note_vol": 0.13, "glitch_vol": 0.025, "drone_attack": 4.0, "drone_decay": 5.5, "note_attack": 0.05, "note_decay": 0.38, "max_glitches": 18, "noise_lowpass_hz": 720, "root_freq_range": (28, 72), }, # Strong musical factory default - "industry quality" feel "musical": { "noise_vol": 0.045, "drone_vol": 0.085, "note_vol": 0.135, "glitch_vol": 0.022, "noise_lowpass_hz": 680, "drone_attack": 3.8, "drone_decay": 5.2, "note_decay": 0.36, "max_notes": 9, "max_glitches": 16, }, } class FactoryResonanceEngine: """Completely isolated factory default engine. No LYGO/LDQ dependencies.""" def __init__(self, config: Optional[Dict[str, Any]] = None): self.config = { "sr": 44100, "duration": 15.0, "global_fade": 0.65, "soft_clip": True, "soft_clip_amount": 1.35, "max_drones": 5, "max_notes": 8, "max_glitches": 14, "noise_vol": 0.05, "drone_vol": 0.07, "note_vol": 0.12, "glitch_vol": 0.018, "root_freq_range": (32, 68), "theta_lock_range": (4.8, 10.5), "drone_attack": 3.8, "drone_decay": 5.0, "note_attack": 0.045, "note_decay": 0.34, "glitch_attack": 0.004, "glitch_decay": 0.014, "noise_lowpass_hz": 650, "random_seed": None, "verbose": True, "export_stems": False, "export_midi": False, } if config: self.config.update(config) def _log(self, msg: str): if self.config.get("verbose", True): print(msg) def analyze_image(self, image_path: str) -> Dict[str, Any]: """Standard computer vision analysis — unchanged from original working versions.""" img = cv2.imread(str(image_path)) if img is None: raise FileNotFoundError(f"Could not load image: {image_path}") if len(img.shape) == 2: img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) h, w = gray.shape avg_blue, avg_green, avg_red, _ = cv2.mean(img) edges = cv2.Canny(gray, 50, 150) edge_density = np.sum(edges > 0) / (h * w) contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 50, minLineLength=28, maxLineGap=12) fast = cv2.FastFeatureDetector_create(threshold=38) keypoints = fast.detect(gray, None) return { "width": w, "height": h, "avg_red": avg_red, "avg_green": avg_green, "avg_blue": avg_blue, "edge_density": edge_density, "contours": contours, "lines": lines if lines is not None else [], "keypoints": keypoints, } def _generate_tone(self, freq: float, duration: float, wave_type: str = "sine") -> np.ndarray: sr = self.config["sr"] n = int(sr * duration) if n <= 0: return np.zeros(1, dtype=np.float32) t = np.linspace(0, duration, n, dtype=np.float32) if wave_type == "sine": return np.sin(freq * t * 2 * np.pi).astype(np.float32) elif wave_type == "sawtooth": return (2 * (t * freq - np.floor(0.5 + t * freq)) * 0.58).astype(np.float32) elif wave_type == "noise": return np.random.uniform(-0.32, 0.32, n).astype(np.float32) return np.zeros(n, dtype=np.float32) def _apply_envelope(self, audio: np.ndarray, attack: float, decay: float) -> np.ndarray: sr = self.config["sr"] n = len(audio) if n <= 0: return audio a = max(1, int(attack * sr)) d = max(1, int(decay * sr)) env = np.ones(n, dtype=np.float32) if n > a + d: env[:a] = np.linspace(0, 1, a, dtype=np.float32) env[-d:] = np.linspace(1, 0, d, dtype=np.float32) return (audio * env).astype(np.float32) def _stereo_pan(self, mono: np.ndarray, pan: float) -> np.ndarray: if mono.ndim == 1: mono = mono[:, np.newaxis] pan = max(-1.0, min(1.0, pan)) left = math.cos((pan + 1) * math.pi / 4) right = math.sin((pan + 1) * math.pi / 4) return np.column_stack((mono * left, mono * right)).astype(np.float32) def _fft_lowpass(self, audio: np.ndarray, cutoff_hz: float) -> np.ndarray: if cutoff_hz <= 0 or len(audio) < 32: return audio sr = self.config["sr"] n = len(audio) fft = np.fft.rfft(audio) freqs = np.fft.rfftfreq(n, 1.0 / sr) fft[freqs > cutoff_hz] = 0 return np.fft.irfft(fft, n=n).real.astype(np.float32) def _soft_limit(self, audio: np.ndarray) -> np.ndarray: if self.config["soft_clip"]: amt = self.config["soft_clip_amount"] return (np.tanh(audio * amt) / np.tanh(amt)).astype(np.float32) return audio def synthesize(self, features: Dict[str, Any], output_path: str): cfg = self.config if cfg["random_seed"] is not None: np.random.seed(cfg["random_seed"]) sr = cfg["sr"] duration = cfg["duration"] n_total = int(sr * duration) if n_total <= 0: sf.write(output_path, np.zeros((1, 2)), sr) return audio = np.zeros((n_total, 2), dtype=np.float32) root = np.interp(features["avg_red"], [0, 255], cfg["root_freq_range"]) theta = np.interp(features["avg_green"], [0, 255], cfg["theta_lock_range"]) w, h = features["width"], features["height"] self._log("šŸŽµ FACTORY ENGINE — Pure 4-layer (no LYGO/LDQ)") # Collections for stems audio_noise = np.zeros((n_total, 2), dtype=np.float32) audio_drone = np.zeros((n_total, 2), dtype=np.float32) audio_melody = np.zeros((n_total, 2), dtype=np.float32) audio_glitch = np.zeros((n_total, 2), dtype=np.float32) melody_events = [] # Layer 1: Texture Floor (Noise) — filtered for musicality if features["edge_density"] > 0.006: noise = self._generate_tone(0, duration, "noise") if cfg.get("noise_lowpass_hz", 650) > 0: noise = self._fft_lowpass(noise, cfg["noise_lowpass_hz"]) noise = self._apply_envelope(noise, cfg["drone_attack"], cfg["drone_decay"]) vol = min(features["edge_density"] * 1.35, cfg["noise_vol"]) stereo_noise = self._stereo_pan(noise, 0.0) * vol audio += stereo_noise audio_noise += stereo_noise self._log(f" Layer1 noise vol={vol:.3f} lowpass={cfg.get('noise_lowpass_hz')}") # Layer 2: Drones (Lines) — rhythmic backbone for i, line in enumerate(features["lines"][:cfg["max_drones"]]): x1, _, x2, _ = line[0] length = math.hypot(x2 - x1, 0) detune = (i * 0.65) if cfg["random_seed"] is not None else 0 freq = root + (max(1, int(length / 46)) * theta * 0.58) + detune tone = self._generate_tone(freq, duration, "sawtooth") tone = self._apply_envelope(tone, cfg["drone_attack"], cfg["drone_decay"]) pan = (x1 / w) * 2 - 1 stereo_drone = self._stereo_pan(tone, pan) * cfg["drone_vol"] audio += stereo_drone audio_drone += stereo_drone # Layer 3: Contours → Melody (main musical/rhythmic content) valid = [c for c in features["contours"] if 85 < cv2.contourArea(c) < (w * h * 0.62)] valid.sort(key=lambda c: cv2.boundingRect(c)[0]) for i, cnt in enumerate(valid[:cfg["max_notes"]]): area = cv2.contourArea(cnt) verts = len(cv2.approxPolyDP(cnt, 0.04 * cv2.arcLength(cnt, True), True)) freq = (root * 3.65) + (verts * theta * 1.55) dur = min(2.8, 0.18 + (area / 12800)) tone = self._generate_tone(freq, dur, "sine") tone = self._apply_envelope(tone, cfg["note_attack"], cfg["note_decay"]) M = cv2.moments(cnt) cx = int(M["m10"] / M["m00"]) if M["m00"] != 0 else cv2.boundingRect(cnt)[0] start = (cx / w) * (duration - dur) idx = int(start * sr) end = min(idx + len(tone), n_total) pan = (cx / w) * 2 - 1 stereo_note = self._stereo_pan(tone[:end-idx], pan) * cfg["note_vol"] audio[idx:end] += stereo_note audio_melody[idx:end] += stereo_note melody_events.append((freq, dur, start)) # Layer 4: Glitch / Micro events (adds percussive "beat" character) for i, kp in enumerate(features["keypoints"][:cfg["max_glitches"]]): x, y = kp.pt freq = root * 13.2 + (y % 82) * 1.35 tone = self._generate_tone(freq, 0.048, "sine") tone = self._apply_envelope(tone, cfg["glitch_attack"], cfg["glitch_decay"]) start = (y / h) * (duration - 0.048) idx = int(start * sr) end = min(idx + len(tone), n_total) pan = (x / w) * 2 - 1 stereo_glitch = self._stereo_pan(tone[:end-idx], pan) * cfg["glitch_vol"] audio[idx:end] += stereo_glitch audio_glitch[idx:end] += stereo_glitch # Final factory polish (safe, no LDQ) audio = self._soft_limit(audio) fade = int(cfg["global_fade"] * sr) if fade > 0 and n_total > fade * 2: audio[:fade] *= np.linspace(0, 1, fade)[:, np.newaxis] audio[-fade:] *= np.linspace(1, 0, fade)[:, np.newaxis] peak = np.max(np.abs(audio)) if peak > 0: audio = audio / peak * 0.965 sf.write(output_path, audio, sr) self._log(f"āœ“ FACTORY Saved: {output_path} | Peak: {peak:.3f}") # Stems (factory feature) if cfg.get("export_stems"): base = output_path.replace(".wav", "") for stem, name in [(audio_noise, "noise"), (audio_drone, "drone"), (audio_melody, "melody"), (audio_glitch, "glitch")]: mv = np.max(np.abs(stem)) if mv > 0: stem = stem / mv * 0.965 sf.write(f"{base}_{name}.wav", stem, sr) self._log(f"āœ“ Stem: {base}_{name}.wav") # MIDI (factory feature) if cfg.get("export_midi") and melody_events: try: from mido import MidiFile, MidiTrack, Message mid = MidiFile() track = MidiTrack() mid.tracks.append(track) ticks_per_beat = 480 tempo = 128 tick_offset = 0 for freq, dur, start in melody_events: midi_note = max(0, min(127, int(12 * math.log2(max(20, freq) / 440) + 69))) duration_ticks = int(dur * ticks_per_beat * (tempo / 60)) start_ticks = int(start * ticks_per_beat * (tempo / 60)) track.append(Message('note_on', note=midi_note, velocity=62, time=start_ticks - tick_offset)) track.append(Message('note_off', note=midi_note, velocity=62, time=duration_ticks)) tick_offset = start_ticks + duration_ticks mid_path = output_path.replace(".wav", ".mid") mid.save(mid_path) self._log(f"āœ“ MIDI: {mid_path}") except Exception as e: self._log(f"MIDI export skipped: {e}") def process(self, image_path: str, output_path: str): self._log(f"\n╔════════════════════════════════════════════╗") self._log(f"ā•‘ FACTORY DEFAULT RESONANCE ENGINE v{__version__} ā•‘") self._log(f"ā•‘ Pure image → musical stereo soundscape ā•‘") self._log(f"ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n") self._log(f"Analyzing: {image_path}") features = self.analyze_image(image_path) self.synthesize(features, output_path) def main(): import argparse parser = argparse.ArgumentParser() parser.add_argument("image") parser.add_argument("-o", "--output", default=None) parser.add_argument("--duration", type=float, default=15.0) parser.add_argument("--style", choices=list(PRESETS.keys()), default="cinematic") parser.add_argument("--seed", type=int, default=None) parser.add_argument("--noise-filter", type=float, default=None) parser.add_argument("--stems", action="store_true") parser.add_argument("--midi", action="store_true") parser.add_argument("--quiet", action="store_true") args = parser.parse_args() config = { "duration": args.duration, "random_seed": args.seed, "verbose": not args.quiet, "export_stems": args.stems, "export_midi": args.midi, } if args.noise_filter is not None: config["noise_lowpass_hz"] = args.noise_filter preset = PRESETS.get(args.style, {}) config.update(preset) out_path = args.output or f"factory_{Path(args.image).stem}.wav" engine = FactoryResonanceEngine(config) engine.process(args.image, out_path) if __name__ == "__main__": main()