LYGO-Resonance-Engine / ldq_percussion.py
DeepSeekOracle's picture
Upload ldq_percussion.py with huggingface_hub
d20b4a9 verified
Raw
History Blame Contribute Delete
4.11 kB
#!/usr/bin/env python3
"""
LDQ Percussion Engine v2.1
Professional drum synthesis – FIXED: broadcasting, truthiness, and shape mismatches.
"""
import numpy as np
import math
import cv2
from typing import Dict, Any
def generate_kick(features: Dict[str, Any], sr: int, duration: float = 0.5) -> np.ndarray:
"""Generate a punchy kick drum. Output shape: (sr*duration,)."""
n = int(sr * duration)
if n <= 0:
return np.zeros(1)
contours = features.get("contours")
if contours is None or len(contours) == 0:
# default kick
t = np.linspace(0, duration, n)
pitch = np.linspace(60, 30, n)
kick = np.sin(2 * np.pi * pitch * t)
env = np.exp(-t * 25)
return (kick * env).astype(np.float32)
largest = max(contours, key=cv2.contourArea)
area = cv2.contourArea(largest)
perimeter = cv2.arcLength(largest, True)
f0 = 80 - (area / 10000) * 30 # 50–80 Hz
f1 = 30 + (perimeter / 100) * 5 # 30–35 Hz
t = np.linspace(0, duration, n)
pitch = np.linspace(f0, f1, n)
kick = np.sin(2 * np.pi * pitch * t).astype(np.float32)
env = np.exp(-t * 20).astype(np.float32)
kick = kick * env
# click transient
click = np.zeros(n, dtype=np.float32)
click_len = min(int(sr * 0.003), n)
click[:click_len] = np.random.normal(0, 0.3, click_len)
click[:click_len] *= np.linspace(1, 0, click_len)
kick += click * 0.2
kick = np.tanh(kick * 1.0) / np.tanh(1.0)
return kick
def generate_snare(features: Dict[str, Any], sr: int, duration: float = 0.15) -> np.ndarray:
"""Generate snare. Output shape: (sr*duration,)."""
n = int(sr * duration)
if n <= 0:
return np.zeros(1)
t = np.linspace(0, duration, n)
# body
body_freq = 180.0
body = np.sin(2 * np.pi * body_freq * t).astype(np.float32)
body_env = np.exp(-t * 15).astype(np.float32)
body = body * body_env
# crack
noise = np.random.normal(0, 1, n).astype(np.float32)
# simple high‑pass via convolution (very short kernel)
kernel = np.array([1, -1], dtype=np.float32)
noise = np.convolve(noise, kernel, mode='same')
crack_env = np.exp(-t * 30).astype(np.float32)
crack = noise * crack_env
snare = body * 0.5 + crack * 0.5
snare = snare * 0.8
# optional angle modulation
lines = features.get("lines")
if lines is not None and len(lines) > 0:
angles = []
for line in lines[:10]:
x1, y1, x2, y2 = line[0]
angles.append(math.degrees(math.atan2(y2 - y1, x2 - x1)) % 180)
if angles:
avg_angle = np.mean(angles)
detune = (avg_angle / 180) * 20
body2 = np.sin(2 * np.pi * (body_freq + detune) * t).astype(np.float32)
body2 = body2 * body_env
snare = body2 * 0.5 + crack * 0.5
return (snare * 0.8).astype(np.float32)
def generate_hihats(features: Dict[str, Any], sr: int, bpm: float, duration: float = 2.0) -> np.ndarray:
"""Generate hi‑hats. Output shape: (sr*duration,)."""
n = int(sr * duration)
if n <= 0:
return np.zeros(1)
edge_density = features.get("edge_density", 0.02)
brightness = features.get("average_brightness", 0.5)
if edge_density < 0.03:
subdivision = 8
elif edge_density < 0.06:
subdivision = 16
else:
subdivision = 32
beats = duration * (bpm / 60)
n_ticks = int(beats * subdivision)
hihat = np.zeros(n, dtype=np.float32)
amp = 0.05 + brightness * 0.1
for i in range(n_ticks):
pos = int(i * sr * 60 / (bpm * subdivision))
if pos >= n:
break
if i % 4 == 0:
level = amp
elif i % 8 == 1:
level = amp * 0.7
else:
level = amp * 0.4
pulse_len = int(sr * 0.015)
end = min(pos + pulse_len, n)
noise = np.random.normal(0, level, pulse_len).astype(np.float32)
env = np.linspace(1, 0, pulse_len, dtype=np.float32)
hihat[pos:end] += noise * env
hihat = np.tanh(hihat * 0.7) / np.tanh(0.7)
return hihat