LYGO-Resonance-Engine / fractalweaver.py
DeepSeekOracle's picture
Upload fractalweaver.py with huggingface_hub
c98cf9a verified
Raw
History Blame Contribute Delete
16.8 kB
#!/usr/bin/env python3
"""
LYGO FractalWeaver v0.1
Weaves self-similar visuals (fractals, recursive patterns) into evolving audio textures.
Integrates with LYGO RESONANCE engine (falls back to built-in evolving synth).
Analyzes fractal self-similarity (dimension, iteration, recursive structure) and maps to audio that evolves organically:
- Textures layer and "zoom" recursively over time.
- Parameters modulate with fractal depth (e.g., drones shift with scale, glitches branch with iteration).
- Self-similar motifs repeat at different time/frequency "octaves".
Outputs:
- Stereo WAV with evolving texture (default 30-60s).
- .fractal.weave.json profile with LYGO mappings (fractal_dimension, self_similarity_evolution, recursive_harmony, etc.).
- Optional stems, MIDI with fractal-like self-similar patterns.
Usage examples:
python fractalweaver.py my_mandelbrot.png --preset fractal-mandel --seed 963 --duration 45
python fractalweaver.py --generate-mandelbrot --output test_fractal.png --width 512 --max_iter 60
python fractalweaver.py --batch ./fractals/ --preset sierpinski-weave
Ties to LYGO ecosystem:
- Companion to lygo-resonance for fractal visuals.
- Compatible with lygo-ollama-army (fractal-weaver or resonance-analyst roles, champions like ARKOS/COSMARA).
- Grows profiles to 3-Brain as recursive/self-similar nodes.
- P0/Oath/Guardian aware: local-first, seed-locked reproducibility, review before external use.
Full instructions in SKILL.md. Links to Resonance site and donation included.
"""
import cv2
import numpy as np
import soundfile as sf
import math
import argparse
import json
from pathlib import Path
from datetime import datetime
from typing import Dict, Any, Optional
import sys
try:
from resonance_engine import ResonanceEngine, PRESETS
HAS_FULL_ENGINE = True
except ImportError:
HAS_FULL_ENGINE = False
PRESETS = {
"fractal-mandel": {"noise_vol": 0.06, "drone_vol": 0.08, "note_vol": 0.12, "glitch_vol": 0.035},
"sierpinski-weave": {"noise_vol": 0.04, "drone_vol": 0.10, "note_vol": 0.09, "glitch_vol": 0.02},
"julia-harmonic": {"noise_vol": 0.05, "drone_vol": 0.12, "note_vol": 0.14, "glitch_vol": 0.015},
}
__version__ = "0.1.0"
def generate_fractal_image(fractal_type: str = "mandelbrot", width: int = 512, height: int = 512, max_iter: int = 50,
cx: float = -0.7, cy: float = 0.3, zoom: float = 1.0) -> np.ndarray:
"""Generate a simple fractal image using numpy for demo/testing."""
img = np.zeros((height, width, 3), dtype=np.uint8)
if fractal_type == "mandelbrot":
for x in range(width):
for y in range(height):
zx, zy = (x - width / 2) / (0.5 * zoom * width) , (y - height / 2) / (0.5 * zoom * height)
c = complex(zx, zy)
z = complex(0, 0)
for i in range(max_iter):
if abs(z) > 2:
break
z = z * z + c
color = int(255 * i / max_iter)
img[y, x] = [color, int(color * 0.7), int(color * 0.4)]
elif fractal_type == "julia":
for x in range(width):
for y in range(height):
zx = (x - width / 2) / (0.5 * zoom * width)
zy = (y - height / 2) / (0.5 * zoom * height)
z = complex(zx, zy)
c = complex(cx, cy)
for i in range(max_iter):
if abs(z) > 2:
break
z = z * z + c
color = int(255 * i / max_iter)
img[y, x] = [int(color * 0.6), color, int(color * 0.8)]
else: # simple recursive tree-like pattern
img = np.zeros((height, width, 3), dtype=np.uint8)
def draw_tree(x, y, angle, depth, length):
if depth == 0:
return
x2 = int(x + length * math.cos(math.radians(angle)))
y2 = int(y - length * math.sin(math.radians(angle)))
cv2.line(img, (int(x), int(y)), (x2, y2), (200, 180, 255), 1)
draw_tree(x2, y2, angle - 30, depth - 1, length * 0.7)
draw_tree(x2, y2, angle + 30, depth - 1, length * 0.7)
draw_tree(width // 2, height - 20, -90, 8, 80)
return img
def analyze_fractal(image_path: str) -> Dict[str, Any]:
"""Analyze self-similar/fractal properties (builds on LYGO RESONANCE CV + Glyph2Resonance)."""
img = cv2.imread(str(image_path))
if img is None:
raise FileNotFoundError(f"Could not load fractal 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
total = h * w
# Base features
avg_blue, avg_green, avg_red, _ = cv2.mean(img)
edges = cv2.Canny(gray, 50, 150)
edge_density = np.sum(edges > 0) / total
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
fast = cv2.FastFeatureDetector_create(threshold=38)
keypoints = fast.detect(gray, None) or []
# Fractal / Self-similarity specific
# Approximate fractal dimension (box counting simplified)
scales = [2, 4, 8, 16]
counts = []
for s in scales:
boxes = 0
for y in range(0, h, s):
for x in range(0, w, s):
if np.any(edges[y:y+s, x:x+s] > 0):
boxes += 1
counts.append(boxes if boxes > 0 else 1)
# log-log slope for dimension
logs = np.log(scales)
logc = np.log(counts)
dim = -np.polyfit(logs, logc, 1)[0] if len(logs) > 1 else 1.5
fractal_dimension = float(max(1.0, min(dim, 2.0)))
# Self-similarity score (compare to downscaled versions)
sim_scores = []
for factor in [0.5, 0.25]:
small = cv2.resize(gray, (0,0), fx=factor, fy=factor)
if small.shape[0] > 10 and small.shape[1] > 10:
diff = np.abs(gray.astype(float)[:small.shape[0], :small.shape[1]] -
cv2.resize(small, (gray.shape[1], gray.shape[0])).astype(float))
score = 1.0 - (np.mean(diff) / 255.0)
sim_scores.append(max(0.0, min(1.0, score)))
self_similarity = float(np.mean(sim_scores)) if sim_scores else 0.5
# Iteration / recursion proxy (multi-scale complexity)
iter_proxy = float(np.std([cv2.resize(edges, (0,0), fx=1/s, fy=1/s).mean() for s in [1,2,4,8]]))
# Branching (radial/contour variation)
radial_variation = 0.0
if contours:
areas = [cv2.contourArea(c) for c in contours]
radial_variation = float(np.std(areas) / (np.mean(areas) + 1))
features = {
"width": w, "height": h,
"avg_red": round(avg_red, 1), "avg_green": round(avg_green, 1), "avg_blue": round(avg_blue, 1),
"edge_density": round(edge_density, 5),
"fractal_dimension": round(fractal_dimension, 3),
"self_similarity": round(self_similarity, 4),
"iteration_proxy": round(iter_proxy, 4),
"branching": round(radial_variation, 4),
"num_contours": len(contours),
"num_keypoints": len(keypoints),
"source": Path(image_path).name,
}
return features
def map_to_evolving_params(features: Dict[str, Any], preset: str = "fractal-mandel") -> Dict[str, Any]:
"""Map fractal features to base params + evolution curves for weaving."""
base_root = 30 + features["avg_red"] * 0.12
base_theta = 5.0 + features["avg_green"] * 0.02
dim = features["fractal_dimension"]
sim = features["self_similarity"]
it = features["iteration_proxy"]
br = features["branching"]
edge = features["edge_density"]
if preset == "fractal-mandel":
base_drone = 0.07 + dim * 0.04
base_note = 0.11 + sim * 0.05
base_glitch = 0.025 + it * 0.03
evolution_rate = 0.8 + br * 1.2 # how fast params change
weave_layers = int(4 + dim * 3)
elif preset == "sierpinski-weave":
base_drone = 0.09 + sim * 0.06
base_note = 0.08 + edge * 0.05
base_glitch = 0.015 + br * 0.02
evolution_rate = 1.2 + it * 0.8
weave_layers = int(3 + sim * 4)
else: # julia-harmonic
base_drone = 0.10 + dim * 0.03
base_note = 0.13 + sim * 0.04
base_glitch = 0.012 + it * 0.02
evolution_rate = 0.6 + br * 0.9
weave_layers = int(5 + dim * 2)
base_config = {
"sr": 44100,
"duration": 40.0,
"root_freq_range": (base_root - 2, base_root + 2),
"theta_lock_range": (base_theta - 0.5, base_theta + 0.5),
"noise_vol": min(0.05 + edge * 0.04, 0.14),
"drone_vol": min(base_drone, 0.16),
"note_vol": min(base_note, 0.16),
"glitch_vol": min(base_glitch, 0.10),
"max_drones": max(3, min(weave_layers, 8)),
"max_notes": max(4, min(int(weave_layers * 1.5), 12)),
"max_glitches": int(10 + it * 15),
"random_seed": None,
"verbose": True,
}
lygo_meta = {
"fractal_dimension": round(dim, 3),
"self_similarity": round(sim, 4),
"iteration_evolution": round(it, 4),
"recursive_harmony": round((sim + (dim - 1.0)) / 2, 4),
"weave_complexity": round(br + edge, 4),
"evolution_rate": round(evolution_rate, 2),
"suggested_duration": base_config["duration"],
"preset_used": preset,
}
return base_config, lygo_meta
def synthesize_evolving_texture(features: Dict, config: Dict, output_wav: str):
"""Built-in evolving synth (segments with interpolated params for self-similar evolution)."""
sr = config["sr"]
dur = config.get("duration", 40.0)
n = int(sr * dur)
audio = np.zeros(n, dtype=np.float32)
root = float(np.mean(config["root_freq_range"]))
theta = float(np.mean(config.get("theta_lock_range", (5.0, 6.0))))
evo = features.get("iteration_evolution", 0.5) + 0.5 # normalized evolution speed
num_segments = max(4, int(evo * 8))
seg_len = n // num_segments
for seg in range(num_segments):
t = seg / max(1, num_segments - 1)
# Evolve params self-similarly (zoom in = more detail, higher freqs, more glitches)
cur_root = root + t * (seg % 3) * 4
cur_drone_v = config["drone_vol"] * (0.7 + t * 0.6)
cur_glitch_v = config["glitch_vol"] * (0.5 + t * 1.2)
cur_note_v = config["note_vol"] * (0.8 + (1 - t) * 0.4)
start = seg * seg_len
end = min(start + seg_len, n)
seg_n = end - start
if seg_n <= 0:
continue
t_arr = np.linspace(0, seg_n / sr, seg_n, False, dtype=np.float32)
# Layer 1: Base texture (evolving noise floor)
noise = (np.random.uniform(-0.8, 0.8, seg_n) * 0.5).astype(np.float32)
audio[start:end] += noise * config["noise_vol"] * (0.6 + t * 0.8)
# Layer 2: Drones that "branch" (self-similar freqs)
for i in range(config["max_drones"]):
f = cur_root + (i * theta * (1 + t * 0.3))
tone = np.sin(2 * np.pi * f * t_arr).astype(np.float32)
audio[start:end] += tone * cur_drone_v * (0.8 + (i % 2) * 0.2)
# Layer 3: Notes/motifs that repeat at different "scales" (time offsets)
for i in range(config["max_notes"]):
f = cur_root * 1.8 + (i * theta * 0.8)
note_dur = 0.6 + (seg % 3) * 0.2
note_t = np.linspace(0, note_dur, min(int(sr * note_dur), seg_n), False, dtype=np.float32)
tone = np.sin(2 * np.pi * f * note_t).astype(np.float32)
note_start = start + (i * 1200) % (seg_n - len(tone) if seg_n > len(tone) else 0)
note_end = min(note_start + len(tone), end)
if note_end > note_start:
audio[note_start:note_end] += tone[:note_end - note_start] * cur_note_v
# Layer 4: Glitch "recursion" (bursts that echo the structure)
for i in range(int(config["max_glitches"] * (0.6 + t * 0.8))):
f = cur_root * 3.5 + (i % 7) * 11
g_t = np.linspace(0, 0.04, int(sr * 0.04), False, dtype=np.float32)
tone = np.sin(2 * np.pi * f * g_t).astype(np.float32)
g_start = start + (i * 700 + int(t * 2000)) % (seg_n - len(g_t) if seg_n > len(g_t) else 0)
g_end = min(g_start + len(tone), end)
if g_end > g_start:
audio[g_start:g_end] += tone[:g_end - g_start] * cur_glitch_v
# Final polish
audio = np.tanh(audio * 1.5) / 1.5
peak = np.max(np.abs(audio))
if peak > 0:
audio = (audio / peak * 0.95).astype(np.float32)
sf.write(output_wav, audio, sr)
return output_wav
def main():
parser = argparse.ArgumentParser(
description="LYGO FractalWeaver — Turn self-similar visuals and fractals into evolving resonant audio textures"
)
parser.add_argument("image", nargs="?", help="Input fractal/self-similar image")
parser.add_argument("--preset", choices=["fractal-mandel", "sierpinski-weave", "julia-harmonic"], default="fractal-mandel")
parser.add_argument("--seed", type=int, default=None)
parser.add_argument("--duration", type=float, default=40.0)
parser.add_argument("-o", "--output", default=None)
parser.add_argument("--profile", default=None)
parser.add_argument("--batch", action="store_true")
parser.add_argument("--generate-mandelbrot", action="store_true")
parser.add_argument("--generate-julia", action="store_true")
parser.add_argument("--width", type=int, default=512)
parser.add_argument("--height", type=int, default=512)
parser.add_argument("--max_iter", type=int, default=50)
args = parser.parse_args()
if args.generate_mandelbrot or args.generate_julia:
ftype = "mandelbrot" if args.generate_mandelbrot else "julia"
img = generate_fractal_image(ftype, args.width, args.height, args.max_iter)
out = f"generated_{ftype}.png"
cv2.imwrite(out, img)
print(f"Generated test fractal: {out}")
return
if not args.image:
parser.print_help()
return
img_path = Path(args.image)
if args.batch:
folder = img_path
images = sorted(list(folder.glob("*.png")) + list(folder.glob("*.jpg")) + list(folder.glob("*.jpeg")))
if not images:
print("No images found.")
return
for im in images:
print(f"\nWeaving fractal: {im.name}")
_process_one(im, args.preset, args.seed, args.duration, args.output, args.profile)
return
_process_one(img_path, args.preset, args.seed, args.duration, args.output, args.profile)
def _process_one(image_path: Path, preset: str, seed: Optional[int], duration: float, out_wav: Optional[str], out_json: Optional[str]):
features = analyze_fractal(str(image_path))
config, lygo_meta = map_to_evolving_params(features, preset)
if seed is not None:
config["random_seed"] = seed
config["duration"] = duration
wav_path = out_wav or f"fractalweave_{image_path.stem}.wav"
json_path = out_json or f"fractalweave_{image_path.stem}.fractal.weave.json"
if HAS_FULL_ENGINE:
eng_config = config.copy()
eng_config.update(PRESETS.get(preset, {}))
engine = ResonanceEngine(eng_config)
# For evolving effect, we still use our segment-based approach below for now
# (full engine can be extended later; this keeps the "weave" logic)
synthesize_evolving_texture(features, config, wav_path)
full_profile = {
"LYGO_FRACTALWEAVER": {
"version": __version__,
"source_fractal": str(image_path),
"preset": preset,
"features": features,
"audio_config": config,
"lygo_mappings": lygo_meta,
"generated_at": datetime.now().isoformat(),
"reproducible_with_seed": seed,
}
}
with open(json_path, "w", encoding="utf-8") as f:
json.dump(full_profile, f, indent=2)
print(f"✓ Evolving texture: {wav_path}")
print(f"✓ Profile: {json_path}")
print(f" dimension={lygo_meta['fractal_dimension']}, self_sim={lygo_meta['self_similarity']}, recursive_harmony={lygo_meta['recursive_harmony']}")
# Grow to 3-Brain (recursive/self-similar nodes)
try:
sys.path.insert(0, str(Path.cwd()))
from lyra_brain import LyraThreeBrainMemory
brain = LyraThreeBrainMemory(base_dir=Path.cwd(), use_advanced=True)
summary = f"FractalWeaver: {image_path.name}{preset} evolving weave | dim={lygo_meta['fractal_dimension']} sim={lygo_meta['self_similarity']} harmony={lygo_meta['recursive_harmony']}"
nid = brain.grow(summary, source="fractalweaver")
print(f" Grown to 3-Brain node: {nid}")
except Exception:
pass
if __name__ == "__main__":
main()