Update ldq_percussion.py
Browse files- ldq_percussion.py +25 -32
ldq_percussion.py
CHANGED
|
@@ -19,25 +19,26 @@ def generate_kick(features: Dict[str, Any], sr: int, duration: float = 0.5) -> n
|
|
| 19 |
area = cv2.contourArea(largest)
|
| 20 |
perimeter = cv2.arcLength(largest, True)
|
| 21 |
|
| 22 |
-
#
|
| 23 |
-
f0 = 30 + (area / 10000) * 30
|
| 24 |
-
|
| 25 |
-
mod_idx = 0.5 + (perimeter / 100) * 2.0
|
| 26 |
-
# Decay time from area
|
| 27 |
decay = 0.3 + (area / 50000) * 0.4
|
| 28 |
|
| 29 |
n = int(sr * duration)
|
| 30 |
t = np.linspace(0, duration, n)
|
| 31 |
|
| 32 |
-
# FM synthesis
|
| 33 |
carrier = np.sin(2 * np.pi * f0 * t)
|
| 34 |
modulator = np.sin(2 * np.pi * f0 * 2 * t)
|
| 35 |
kick = np.sin(2 * np.pi * f0 * t + mod_idx * modulator)
|
| 36 |
|
| 37 |
-
# Exponential decay
|
| 38 |
-
envelope = np.exp(-t * decay *
|
| 39 |
kick *= envelope
|
| 40 |
|
|
|
|
|
|
|
|
|
|
| 41 |
return kick
|
| 42 |
|
| 43 |
def generate_snare(features: Dict[str, Any], sr: int, duration: float = 0.15) -> np.ndarray:
|
|
@@ -46,19 +47,8 @@ def generate_snare(features: Dict[str, Any], sr: int, duration: float = 0.15) ->
|
|
| 46 |
if not lines:
|
| 47 |
return np.zeros(int(sr * duration))
|
| 48 |
|
| 49 |
-
#
|
| 50 |
-
intersections = []
|
| 51 |
-
for i in range(len(lines)):
|
| 52 |
-
for j in range(i+1, len(lines)):
|
| 53 |
-
x1, y1, x2, y2 = lines[i][0]
|
| 54 |
-
x3, y3, x4, y4 = lines[j][0]
|
| 55 |
-
# Simple intersection check (line segments)
|
| 56 |
-
# For brevity, we approximate with angle detection
|
| 57 |
-
pass
|
| 58 |
-
|
| 59 |
-
# Approximate intersection count from line density
|
| 60 |
n_lines = len(lines)
|
| 61 |
-
intersection_count = max(1, int(n_lines * 0.5))
|
| 62 |
|
| 63 |
# Center frequency from average line angle
|
| 64 |
angles = []
|
|
@@ -67,14 +57,15 @@ def generate_snare(features: Dict[str, Any], sr: int, duration: float = 0.15) ->
|
|
| 67 |
angle = math.degrees(math.atan2(y2-y1, x2-x1)) % 180
|
| 68 |
angles.append(angle)
|
| 69 |
avg_angle = np.mean(angles) if angles else 45
|
| 70 |
-
center_freq =
|
| 71 |
|
| 72 |
-
#
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
| 78 |
|
| 79 |
return snare
|
| 80 |
|
|
@@ -95,17 +86,19 @@ def generate_hihats(features: Dict[str, Any], sr: int, bpm: float, duration: flo
|
|
| 95 |
beats = duration * (bpm / 60)
|
| 96 |
n_ticks = int(beats * subdivision)
|
| 97 |
|
| 98 |
-
# Amplitude from brightness
|
| 99 |
-
amp = 0.
|
| 100 |
|
| 101 |
# Generate pulse train
|
| 102 |
hihat = np.zeros(int(sr * duration))
|
| 103 |
for i in range(n_ticks):
|
| 104 |
pos = int(i * sr * 60 / (bpm * subdivision))
|
| 105 |
if pos < len(hihat):
|
| 106 |
-
# Short
|
| 107 |
-
pulse_len = int(sr * 0.
|
| 108 |
end = min(pos + pulse_len, len(hihat))
|
| 109 |
-
|
|
|
|
|
|
|
| 110 |
|
| 111 |
return hihat
|
|
|
|
| 19 |
area = cv2.contourArea(largest)
|
| 20 |
perimeter = cv2.arcLength(largest, True)
|
| 21 |
|
| 22 |
+
# Tuned for less buzz: lower modulation index
|
| 23 |
+
f0 = 30 + (area / 10000) * 30 # 30-60 Hz
|
| 24 |
+
mod_idx = 0.3 + (perimeter / 100) * 1.0 # Lower modulation
|
|
|
|
|
|
|
| 25 |
decay = 0.3 + (area / 50000) * 0.4
|
| 26 |
|
| 27 |
n = int(sr * duration)
|
| 28 |
t = np.linspace(0, duration, n)
|
| 29 |
|
| 30 |
+
# FM synthesis with softer carrier
|
| 31 |
carrier = np.sin(2 * np.pi * f0 * t)
|
| 32 |
modulator = np.sin(2 * np.pi * f0 * 2 * t)
|
| 33 |
kick = np.sin(2 * np.pi * f0 * t + mod_idx * modulator)
|
| 34 |
|
| 35 |
+
# Exponential decay with longer tail
|
| 36 |
+
envelope = np.exp(-t * decay * 8)
|
| 37 |
kick *= envelope
|
| 38 |
|
| 39 |
+
# Soft clipping to reduce harshness
|
| 40 |
+
kick = np.tanh(kick * 0.8) / np.tanh(0.8)
|
| 41 |
+
|
| 42 |
return kick
|
| 43 |
|
| 44 |
def generate_snare(features: Dict[str, Any], sr: int, duration: float = 0.15) -> np.ndarray:
|
|
|
|
| 47 |
if not lines:
|
| 48 |
return np.zeros(int(sr * duration))
|
| 49 |
|
| 50 |
+
# Use low-frequency sawtooth with fast decay for a softer snare
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
n_lines = len(lines)
|
|
|
|
| 52 |
|
| 53 |
# Center frequency from average line angle
|
| 54 |
angles = []
|
|
|
|
| 57 |
angle = math.degrees(math.atan2(y2-y1, x2-x1)) % 180
|
| 58 |
angles.append(angle)
|
| 59 |
avg_angle = np.mean(angles) if angles else 45
|
| 60 |
+
center_freq = 150 + (avg_angle / 180) * 150 # 150-300 Hz (lower for less buzz)
|
| 61 |
|
| 62 |
+
# Sawtooth with fast decay
|
| 63 |
+
n = int(sr * duration)
|
| 64 |
+
t = np.linspace(0, duration, n)
|
| 65 |
+
snare = np.sin(2 * np.pi * center_freq * t) * 0.5
|
| 66 |
+
snare += np.random.normal(0, 0.1, n) # Small noise component
|
| 67 |
+
envelope = np.exp(-t * 15)
|
| 68 |
+
snare *= envelope
|
| 69 |
|
| 70 |
return snare
|
| 71 |
|
|
|
|
| 86 |
beats = duration * (bpm / 60)
|
| 87 |
n_ticks = int(beats * subdivision)
|
| 88 |
|
| 89 |
+
# Amplitude from brightness (reduced for less buzz)
|
| 90 |
+
amp = 0.05 + brightness * 0.1
|
| 91 |
|
| 92 |
# Generate pulse train
|
| 93 |
hihat = np.zeros(int(sr * duration))
|
| 94 |
for i in range(n_ticks):
|
| 95 |
pos = int(i * sr * 60 / (bpm * subdivision))
|
| 96 |
if pos < len(hihat):
|
| 97 |
+
# Short triangle wave pulse (smoother than square)
|
| 98 |
+
pulse_len = int(sr * 0.015)
|
| 99 |
end = min(pos + pulse_len, len(hihat))
|
| 100 |
+
t_pulse = np.linspace(0, 0.015, pulse_len)
|
| 101 |
+
pulse = np.sin(2 * np.pi * 8000 * t_pulse) * amp * 0.3
|
| 102 |
+
hihat[pos:end] += pulse[:end-pos]
|
| 103 |
|
| 104 |
return hihat
|