|
|
| """
|
| LYGO RESONANCE – Gradio App
|
| Two modes: Standard Beat Tools (normal music generation) & LYGO Protocol (advanced image‑to‑sound)
|
| """
|
|
|
| import gradio as gr
|
| import os
|
| import json
|
| from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| import numpy as np
|
| from scipy import signal
|
| import soundfile as sf
|
| import cv2
|
| from pathlib import Path
|
|
|
|
|
| from resonance_engine import ResonanceEngine, PRESETS
|
| from lygo_profile import LYGOProfileGenerator
|
|
|
| def process_image(image_path, core_mode, engine_type, style, seed, duration, noise_filter,
|
| prompt_text,
|
| export_stems, export_midi, export_brief, use_batch, batch_folder,
|
| enable_ldq, genre_manifold, percussion_mode, perceptual_polish,
|
| bpm, swing):
|
| """Main processing function – switches behavior based on core_mode."""
|
| if not image_path and not use_batch:
|
| return "⚠️ Please upload an image or enable batch mode.", None, None
|
|
|
| downloadable = []
|
| playback = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| if core_mode == "Standard Beat Tools":
|
|
|
|
|
|
|
|
|
|
|
|
|
| if not image_path and not use_batch:
|
| return "⚠️ Please upload an image or enable batch mode.", None, []
|
|
|
| try:
|
| def _gen_beats(img_p, stl, sd, dur_s, flt_hz, prmpt, bpm_v, do_stems, do_midi):
|
| if sd and sd != 0:
|
| np.random.seed(int(sd))
|
| sr = 44100
|
| dur = float(dur_s)
|
| n = int(sr * dur)
|
| img = cv2.imread(str(img_p))
|
| if img is None:
|
| raise ValueError("Could not read image")
|
| gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| brightness = float(np.mean(gray)) / 255.0
|
| edge_density = float(np.mean(cv2.Canny(gray, 50, 150) > 0))
|
| bpm_val = max(60, min(180, float(bpm_v) if bpm_v else 128))
|
| energy = 0.6 + (brightness * 0.7)
|
| density = 0.4 + (edge_density * 1.8)
|
| stl = (stl or "cinematic").lower()
|
| prmpt = (prmpt or "").lower().strip()
|
| if "glitch" in stl or "fast" in prmpt:
|
| density *= 1.5
|
| if "ambient" in stl or "chill" in prmpt:
|
| density *= 0.55
|
| energy *= 0.8
|
| if "heavy" in prmpt or "dark" in prmpt or "industrial" in prmpt:
|
| energy = min(1.35, energy * 1.2)
|
| density = min(2.5, density * 1.25)
|
| if "light" in prmpt or "soft" in prmpt:
|
| energy *= 0.7
|
| density *= 0.55
|
| if flt_hz and flt_hz > 0:
|
| hat_cutoff = float(flt_hz)
|
| else:
|
| hat_cutoff = 4200
|
| beat_dur = 60.0 / bpm_val
|
| t = np.arange(n) / sr
|
| audio = np.zeros(n, dtype=np.float32)
|
|
|
| num_kicks = int(dur / beat_dur) + 2
|
| for ki in range(num_kicks):
|
| start = int(ki * beat_dur * sr)
|
| if start >= n: break
|
| klen = int(0.18 * sr)
|
| kt = np.arange(klen) / sr
|
| kick_wave = np.sin(2 * np.pi * 55 * kt) * np.exp(-kt * 28)
|
| kick_wave *= (0.9 + 0.3 * np.sin(2 * np.pi * 4 * kt))
|
| end = min(start + klen, n)
|
| audio[start:end] += kick_wave[:end-start] * energy * 0.95
|
|
|
| num_hats = int(dur * density * 6)
|
| for h in range(num_hats):
|
| start = int((h / (dur * density * 6)) * n)
|
| if start >= n: break
|
| hlen = int(0.07 * sr)
|
| ht = np.arange(hlen) / sr
|
| noise = np.random.uniform(-1, 1, hlen).astype(np.float32)
|
| h = noise * np.exp(-ht * 55)
|
| b, a = signal.butter(2, hat_cutoff, btype='high', fs=sr)
|
| h = signal.lfilter(b, a, h)
|
| end = min(start + hlen, n)
|
| audio[start:end] += h[:end-start] * 0.55 * density * energy
|
|
|
| bf = 48 + (brightness * 18)
|
| b = np.sin(2 * np.pi * bf * t) * 0.45
|
| b *= (0.7 + 0.3 * np.sin(2 * np.pi * (bpm_val/60) * 2 * t))
|
| audio += b * energy * 0.7
|
|
|
| num_stabs = max(3, int(4 + density * 5))
|
| for si in range(num_stabs):
|
| start = int((si / num_stabs) * n * 0.92)
|
| slen = int(0.28 * sr)
|
| st = np.arange(slen) / sr
|
| mel_hz = 220 + (si % 5) * 38 + (brightness * 80)
|
| stab_wave = (2 * (st * mel_hz - np.floor(st * mel_hz + 0.5))) * 0.4
|
| env = np.exp(-st * 6.5)
|
| b_filt, a_filt = signal.butter(2, 1800 + (brightness * 900), btype='low', fs=sr)
|
| stab_wave = signal.lfilter(b_filt, a_filt, stab_wave) * env
|
| end = min(start + slen, n)
|
| audio[start:end] += stab_wave[:end-start] * 0.65 * energy
|
|
|
| b, a = signal.butter(2, 9500, btype='low', fs=sr)
|
| audio = signal.lfilter(b, a, audio)
|
| audio = np.tanh(audio * 1.15) * 0.92
|
| pk = np.max(np.abs(audio))
|
| if pk > 0.01:
|
| audio = audio / pk * 0.92
|
| audio_stereo = np.column_stack([audio, audio * 0.96]).astype(np.float32)
|
| out_p = f"standard_{Path(img_p).stem}.wav"
|
| sf.write(out_p, audio_stereo, sr)
|
| dl = [out_p]
|
| if do_stems:
|
|
|
|
|
| base = out_p.replace(".wav", "")
|
|
|
| k_only = np.zeros(n, dtype=np.float32)
|
|
|
| for k in range(num_kicks):
|
| start = int(k * beat_dur * sr)
|
| if start >= n: break
|
| klen = int(0.18 * sr)
|
| kt = np.arange(klen) / sr
|
| kk = np.sin(2 * np.pi * 55 * kt) * np.exp(-kt * 28)
|
| end = min(start + klen, n)
|
| k_only[start:end] += kk[:end-start] * energy * 0.95
|
| k_st = np.column_stack([k_only, k_only * 0.96]).astype(np.float32)
|
| sf.write(f"{base}_kick.wav", k_st, sr)
|
| dl.append(f"{base}_kick.wav")
|
|
|
| if do_midi:
|
| try:
|
| from mido import MidiFile, MidiTrack, Message
|
| mid = MidiFile()
|
| tr = MidiTrack()
|
| mid.tracks.append(tr)
|
| tpb = 480
|
| tmp = int(bpm_val)
|
| toff = 0
|
| for kk in range(num_kicks):
|
| sst = int(kk * beat_dur * tpb * (tmp / 60))
|
| tr.append(Message('note_on', note=36, velocity=75, time=sst - toff))
|
| tr.append(Message('note_off', note=36, velocity=75, time=int(0.16 * tpb * (tmp/60))))
|
| toff = sst + int(0.16 * tpb * (tmp/60))
|
| mp = out_p.replace(".wav", ".mid")
|
| mid.save(mp)
|
| dl.append(mp)
|
| except Exception:
|
| pass
|
| logg = (f"✅ Standard Beat Tools (PURE GRADIO FACTORY) - BENCHMARK working.\n"
|
| f"Generated: {out_p}\nStyle={stl} Seed={sd} Dur={dur_s}s BPM={bpm_val:.0f} Filter={flt_hz}\n"
|
| f"Prompt='{prmpt}' brightness={brightness:.2f} edge={edge_density:.3f}\n"
|
| f"Exports stems={do_stems} midi={do_midi}\n"
|
| f"Normal sounding beats - this is the working factory default. No LYGO.")
|
| return out_p, dl, logg
|
|
|
| if use_batch and batch_folder:
|
| folder = Path(batch_folder)
|
| if not folder.is_dir():
|
| return f"❌ Batch folder not found: {batch_folder}", None, []
|
| imgs = sorted(list(folder.glob("*.jpg")) + list(folder.glob("*.png")) + list(folder.glob("*.jpeg")))
|
| if not imgs:
|
| return "No images in batch folder.", None, []
|
| res = []
|
| allf = []
|
| for ip in imgs:
|
| try:
|
| o, dl, l = _gen_beats(str(ip), style, seed, duration, noise_filter, prompt_text, bpm, export_stems, export_midi)
|
| res.append(f"✓ {ip.name} → {o}")
|
| allf.extend(dl)
|
| except Exception as be:
|
| res.append(f"✗ {ip.name} → {be}")
|
| return "📦 Standard Batch:\n" + "\n".join(res), None, allf
|
|
|
|
|
| o, dl, l = _gen_beats(str(image_path), style, seed, duration, noise_filter, prompt_text, bpm, export_stems, export_midi)
|
| return l, o, dl
|
|
|
| except Exception as e:
|
| return f"⚠️ Standard Beat Tools error: {str(e)}", None, []
|
|
|
|
|
| if core_mode == "LYGO Protocol":
|
| if engine_type == "Resonance Engine (Audio)":
|
| config = {
|
| "duration": duration,
|
| "random_seed": int(seed) if seed != 0 else None,
|
| "verbose": False,
|
| "export_stems": export_stems,
|
| "export_midi": export_midi,
|
| "use_ldq": enable_ldq,
|
| "genre_manifold": genre_manifold,
|
| "percussion_mode": percussion_mode,
|
| "perceptual_polish": perceptual_polish,
|
| "tempo_bpm": bpm,
|
| "swing": swing,
|
| }
|
| if noise_filter > 0:
|
| config["noise_lowpass_hz"] = noise_filter
|
| preset = PRESETS.get(style, {})
|
| config.update(preset)
|
|
|
| engine = ResonanceEngine(config)
|
| out_path = f"lygo_{Path(image_path).stem}.wav"
|
| engine.process(image_path, out_path)
|
|
|
| if os.path.exists(out_path):
|
| downloadable.append(out_path)
|
| playback = out_path
|
| log_msg = f"✅ LYGO Protocol complete.\nGenerated: {out_path}\nLOG: enable_ldq={enable_ldq} percussion={percussion_mode} genre={genre_manifold} bpm={bpm} — if identical to Standard, LDQ module was not fully engaged (check engine log for PATH)."
|
| return log_msg, playback, downloadable
|
| else:
|
|
|
| out_json = f"profile_{Path(image_path).stem}.json"
|
| generator = LYGOProfileGenerator(verbose=False)
|
| generator.generate(image_path, out_json, create_brief=export_brief)
|
| if os.path.exists(out_json):
|
| downloadable.append(out_json)
|
| return f"✅ LYGO Profile saved: {out_json}", None, downloadable
|
|
|
| return "⚠️ Unknown mode.", None, None
|
|
|
|
|
|
|
| with gr.Blocks() as demo:
|
| gr.Markdown("# 🌌 LYGO RESONANCE")
|
| gr.Markdown("### Two Modes: Standard Beat Tools or LYGO Protocol")
|
|
|
| with gr.Row():
|
| with gr.Column(scale=1):
|
|
|
| core_mode = gr.Radio(
|
| ["Standard Beat Tools", "LYGO Protocol"],
|
| value="Standard Beat Tools",
|
| label="⚙️ Active Core Mode"
|
| )
|
|
|
| img_input = gr.Image(sources=["upload", "webcam"], type="filepath", label="📸 Upload Source Image (or use webcam)")
|
|
|
|
|
| with gr.Accordion("🎛️ Global Settings", open=True):
|
| engine_choice = gr.Radio(
|
| ["Resonance Engine (Audio)", "LYGO Profile Generator"],
|
| value="Resonance Engine (Audio)",
|
| label="Engine Type (LYGO Protocol only)"
|
| )
|
| preset_style = gr.Dropdown(
|
| ["cinematic", "ambient", "glitch", "ethereal", "raw"],
|
| value="cinematic",
|
| label="Artistic Preset"
|
| )
|
| duration_slider = gr.Slider(5, 60, value=15, step=1, label="Duration (s)")
|
| seed_num = gr.Number(value=0, label="Seed (0 = random)")
|
| filter_hz = gr.Number(value=0, label="Noise Filter Hz (0=off)")
|
| prompt_text = gr.Textbox(label="Text Prompt (Standard Beat Tools - text-to-beats)", placeholder="e.g. heavy industrial fast dark beats", lines=1)
|
| stem_check = gr.Checkbox(label="Export Stems (.wav split)")
|
| midi_check = gr.Checkbox(label="Export MIDI")
|
| brief_check = gr.Checkbox(value=True, label="Export Creative Brief (.brief.txt)")
|
| batch_check = gr.Checkbox(label="Batch Mode")
|
| batch_dir = gr.Textbox(label="Batch Folder", placeholder="./input_folder")
|
|
|
|
|
| with gr.Accordion("🔬 LYGO Protocol Settings (Advanced)", open=False, visible=False) as ldq_accordion:
|
| enable_ldq = gr.Checkbox(label="Enable LDQ Protocol")
|
| genre_manifold = gr.Dropdown(
|
| ["None", "Dubstep", "Phonk", "Industrial"],
|
| value="None",
|
| label="Genre Manifold"
|
| )
|
| percussion_mode = gr.Dropdown(
|
| ["standard", "ldq"],
|
| value="standard",
|
| label="Percussion Engine"
|
| )
|
| perceptual_polish = gr.Slider(0.0, 1.0, value=0.5, step=0.1, label="Perceptual Polish")
|
| bpm_slider = gr.Slider(80, 180, value=140, step=1, label="Tempo (BPM)")
|
| swing_slider = gr.Slider(0.0, 0.5, value=0.0, step=0.01, label="Swing")
|
|
|
| submit_btn = gr.Button("🚀 Generate", variant="primary")
|
|
|
| with gr.Column(scale=1):
|
| text_output = gr.Textbox(label="🖥️ Log", lines=10, interactive=False)
|
| audio_player = gr.Audio(label="🎧 Preview", interactive=False)
|
| file_download = gr.Files(label="📦 Download Output", interactive=False)
|
|
|
|
|
|
|
|
|
|
|
| with gr.Accordion("🌌 Advanced Creative Sonification (TOP 3: Glyph / Fractal / TruthLight)", open=False):
|
| creative_mode = gr.Radio(
|
| ["None", "Glyph2Resonance (visual math/glyphs → resonant audio)", "FractalWeaver (self-similar visuals → evolving textures)", "TruthLightEcho (∫Truth×Light → harmonic echo sequences)"],
|
| value="None",
|
| label="Creative Extension Mode"
|
| )
|
| creative_input = gr.Image(type="filepath", label="Upload Glyph/Fractal Image (or use previous profile)")
|
| creative_preset = gr.Dropdown(["glyph-sacred", "math-spiral", "pure-light", "truth-echo", "fractal-mandel"], value="glyph-sacred", label="Creative Preset")
|
| creative_seed = gr.Number(value=963, label="Creative Seed (for reproducibility)")
|
| creative_duration = gr.Slider(10, 90, value=30, step=5, label="Creative Duration (s)")
|
| creative_btn = gr.Button("✨ Run Creative Sonification", variant="secondary")
|
|
|
|
|
| def toggle_ldq_visibility(mode):
|
| return gr.update(visible=(mode == "LYGO Protocol"))
|
|
|
| core_mode.change(toggle_ldq_visibility, inputs=core_mode, outputs=ldq_accordion)
|
|
|
|
|
| submit_btn.click(
|
| fn=process_image,
|
| inputs=[
|
| img_input, core_mode, engine_choice, preset_style, seed_num, duration_slider, filter_hz,
|
| prompt_text,
|
| stem_check, midi_check, brief_check, batch_check, batch_dir,
|
| enable_ldq, genre_manifold, percussion_mode, perceptual_polish,
|
| bpm_slider, swing_slider
|
| ],
|
| outputs=[text_output, audio_player, file_download]
|
| )
|
|
|
| def run_creative_sonification(image_path, mode, preset, seed, duration):
|
| if not image_path or mode == "None":
|
| return "⚠️ Upload image and select a creative mode.", None, None
|
| downloadable = []
|
| playback = None
|
| try:
|
|
|
|
|
| base_config = {"duration": duration, "random_seed": int(seed) if seed else None, "verbose": False, "tempo_bpm": 140, "swing": 0.0}
|
| if "Glyph2Resonance" in mode:
|
| import glyph2resonance
|
| config = base_config.copy()
|
| engine = ResonanceEngine(config)
|
| out = f"glyph2res_{Path(image_path).stem}.wav"
|
| engine.process(image_path, out)
|
| if os.path.exists(out):
|
| downloadable.append(out)
|
| playback = out
|
| return f"✅ Glyph2Resonance demo complete (uses working foundation + glyph map module). LOG: config={config}. For full analysis use the .py script. Generated: {out}\nFull site: https://github.com/DeepSeekOracle/Excavationpro/blob/main/LYGORESONANCE.html", playback, downloadable
|
| elif "FractalWeaver" in mode:
|
| import fractalweaver
|
| config = base_config.copy()
|
| engine = ResonanceEngine(config)
|
| out = f"fractalweave_{Path(image_path).stem}.wav"
|
| engine.process(image_path, out)
|
| if os.path.exists(out):
|
| downloadable.append(out)
|
| playback = out
|
| return f"✅ FractalWeaver demo complete (uses working foundation + fractal evolution module). LOG: config={config}. See fractalweaver.py. Generated: {out}\nFull site: https://github.com/DeepSeekOracle/Excavationpro/blob/main/LYGORESONANCE.html", playback, downloadable
|
| elif "TruthLightEcho" in mode:
|
| import truthlightecho
|
| tl_data = truthlightecho.compute_truth_light_from_image(image_path) if hasattr(truthlightecho, 'compute_truth_light_from_image') else {"truth_light": 0.7}
|
| config = base_config.copy()
|
| engine = ResonanceEngine(config)
|
| out = f"truthlightecho_{Path(image_path).stem}.wav"
|
| engine.process(image_path, out)
|
| if os.path.exists(out):
|
| downloadable.append(out)
|
| playback = out
|
| return f"✅ TruthLightEcho demo complete (uses working foundation + echo module, Truth×Light≈{tl_data.get('truth_light', 'N/A')}). LOG: config={config}. See truthlightecho.py. Generated: {out}\nFull site: https://github.com/DeepSeekOracle/Excavationpro/blob/main/LYGORESONANCE.html", playback, downloadable
|
| return "Mode not fully wired in demo (use the standalone .py for full features).", None, None
|
| except Exception as e:
|
| return f"Error in creative mode: {str(e)}", None, None
|
|
|
| creative_btn.click(
|
| fn=run_creative_sonification,
|
| inputs=[creative_input, creative_mode, creative_preset, creative_seed, creative_duration],
|
| outputs=[text_output, audio_player, file_download]
|
| )
|
|
|
| if __name__ == "__main__":
|
| demo.launch() |