LYGO-Resonance-Engine / factory_engine.py
DeepSeekOracle's picture
LYGO lattice align: Ethical Guardian tab + protocol_stack bundle
fea0574 verified
Raw
History Blame Contribute Delete
15.7 kB
#!/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()