lushapp 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,569 @@
1
+ # LUSH - UI Helpers and Theme Color Initializer
2
+ import curses
3
+ from .constants import SHIMMER_PALETTES
4
+
5
+ def get_source_tag(url: str) -> str:
6
+ u = url.lower()
7
+ if 'youtube.com' in u or 'youtu.be' in u:
8
+ return 'YT'
9
+ elif 'somafm.com' in u:
10
+ return 'SOMA'
11
+ elif 'nightride.fm' in u:
12
+ return 'NR'
13
+ elif 'radioparadise.com' in u:
14
+ return 'RP'
15
+ elif 'ice' in u or 'streamafrica' in u or 'zeno.fm' in u or 'radioart' in u:
16
+ return 'ICE'
17
+ elif 'aac' in u:
18
+ return 'AAC'
19
+ return 'STREAM'
20
+
21
+ def trim(text: str, width: int) -> str:
22
+ if len(text) <= width: return text
23
+ if width <= 3: return text[:width]
24
+ return text[:width-3] + "..."
25
+
26
+ def apply_theme(theme_name: str, themes: dict):
27
+ if not curses.has_colors():
28
+ return
29
+ t = themes.get(theme_name, themes.get("Plur1bus", list(themes.values())[0]))
30
+ is_256 = curses.COLORS >= 256
31
+
32
+ fallback_map = {
33
+ "Plur1bus": {"header": (curses.COLOR_BLACK, curses.COLOR_CYAN), "accent": curses.COLOR_CYAN, "footer": (curses.COLOR_BLACK, curses.COLOR_CYAN), "track": curses.COLOR_CYAN, "playing": curses.COLOR_GREEN, "highlight": (curses.COLOR_BLACK, curses.COLOR_CYAN), "genre": curses.COLOR_CYAN, "border": curses.COLOR_CYAN, "sub": curses.COLOR_WHITE},
34
+ "Tokyo Night": {"header": (curses.COLOR_BLACK, curses.COLOR_BLUE), "accent": curses.COLOR_MAGENTA, "footer": (curses.COLOR_BLACK, curses.COLOR_BLUE), "track": curses.COLOR_CYAN, "playing": curses.COLOR_GREEN, "highlight": (curses.COLOR_BLACK, curses.COLOR_MAGENTA), "genre": curses.COLOR_BLUE, "border": curses.COLOR_BLUE, "sub": curses.COLOR_WHITE},
35
+ "Synthwave": {"header": (curses.COLOR_BLACK, curses.COLOR_MAGENTA), "accent": curses.COLOR_MAGENTA, "footer": (curses.COLOR_BLACK, curses.COLOR_MAGENTA), "track": curses.COLOR_YELLOW, "playing": curses.COLOR_CYAN, "highlight": (curses.COLOR_BLACK, curses.COLOR_MAGENTA), "genre": curses.COLOR_YELLOW, "border": curses.COLOR_MAGENTA, "sub": curses.COLOR_WHITE},
36
+ "Matrix": {"header": (curses.COLOR_BLACK, curses.COLOR_GREEN), "accent": curses.COLOR_GREEN, "footer": (curses.COLOR_BLACK, curses.COLOR_GREEN), "track": curses.COLOR_GREEN, "playing": curses.COLOR_GREEN, "highlight": (curses.COLOR_BLACK, curses.COLOR_GREEN), "genre": curses.COLOR_GREEN, "border": curses.COLOR_GREEN, "sub": curses.COLOR_WHITE},
37
+ }
38
+
39
+ if is_256:
40
+ h_fg, h_bg = t.get("header", [0, 45])
41
+ accent = t.get("accent", 45)
42
+ f_fg, f_bg = t.get("footer", [0, 45])
43
+ track = t.get("track", 51)
44
+ playing = t.get("playing", 48)
45
+ hl_fg, hl_bg = t.get("highlight", [0, 45])
46
+ genre = t.get("genre", 38)
47
+ border = t.get("border", 45)
48
+ sub = t.get("sub", 245)
49
+ else:
50
+ fb = fallback_map.get(theme_name, fallback_map["Plur1bus"])
51
+ h_fg, h_bg = fb["header"]
52
+ accent = fb["accent"]
53
+ f_fg, f_bg = fb["footer"]
54
+ track = fb["track"]
55
+ playing = fb["playing"]
56
+ hl_fg, hl_bg = fb["highlight"]
57
+ genre = fb["genre"]
58
+ border = fb["border"]
59
+ sub = fb["sub"]
60
+
61
+ curses.init_pair(1, h_fg, h_bg) # Header / Solid Box
62
+ curses.init_pair(2, accent, -1) # Neon Accent text (Cyan)
63
+ curses.init_pair(3, f_fg, f_bg) # Hotkey Bar (Reverse)
64
+ curses.init_pair(4, track, -1) # Track text / Light Cyan
65
+ curses.init_pair(5, playing, -1) # Playing status (Bright Green)
66
+ curses.init_pair(6, hl_fg, hl_bg) # Active selection highlight
67
+ curses.init_pair(7, genre, -1) # Genre / Muted Info
68
+ curses.init_pair(8, border, -1) # Cyan Borders & Frames
69
+ curses.init_pair(9, sub, -1) # Subtitle & count details
70
+
71
+ # Shimmer gradient color pairs 10 - 14
72
+ if is_256:
73
+ shimmer_cols = SHIMMER_PALETTES.get(theme_name, SHIMMER_PALETTES["Plur1bus"])
74
+ curses.init_pair(10, shimmer_cols[0], -1) # Peak White Hot Sparkle
75
+ curses.init_pair(11, shimmer_cols[1], -1) # Sheen Highlight
76
+ curses.init_pair(12, shimmer_cols[2], -1) # Neon Glow
77
+ curses.init_pair(13, shimmer_cols[3], -1) # Mid Glow
78
+ curses.init_pair(14, shimmer_cols[4], -1) # Base Tone
79
+ curses.init_pair(15, 196, -1) # Glowing Red (REC indicator)
80
+ else:
81
+ curses.init_pair(10, curses.COLOR_WHITE, -1)
82
+ curses.init_pair(11, curses.COLOR_WHITE, -1)
83
+ curses.init_pair(12, curses.COLOR_CYAN, -1)
84
+ curses.init_pair(13, curses.COLOR_CYAN, -1)
85
+ curses.init_pair(14, curses.COLOR_CYAN, -1)
86
+ curses.init_pair(15, curses.COLOR_RED, -1)
87
+
88
+ import math
89
+
90
+ def get_shimmer_color_pair(effect: str, x_norm: float, c_idx: int, row_i: int, t_now: float, cava_bars=None, is_playing=True) -> int:
91
+ if effect == "off":
92
+ return 14 # Base Theme Tone (Static)
93
+
94
+ if not is_playing or not cava_bars or all(b == 0 for b in cava_bars):
95
+ # Subtle ambient breathing glow when paused or silent
96
+ idle_phase = math.sin(t_now * 1.5 + x_norm * 3.14)
97
+ if idle_phase > 0.8: return 12
98
+ elif idle_phase > 0.4: return 13
99
+ return 14
100
+
101
+ # 1. CAVA Beat Reactive Flare
102
+ if effect == "cava_sync":
103
+ energy = sum(cava_bars) / max(1, len(cava_bars) * 8.0)
104
+ bass = max(cava_bars[:4]) if len(cava_bars) >= 4 else 0
105
+ speed = 0.8 + energy * 2.4
106
+ wave_pos = ((t_now * speed) % 2.2) - 0.4
107
+ dist = abs(x_norm - wave_pos)
108
+
109
+ bin_idx = min(len(cava_bars)-1, int(x_norm * len(cava_bars)))
110
+ bin_val = cava_bars[bin_idx]
111
+
112
+ wave_glow = max(0.0, 0.55 - dist * 2.2)
113
+ bass_kick = 0.40 if (bass >= 7 and dist < 0.28) else 0.0
114
+ total_intensity = (bin_val / 8.0) * 0.5 + wave_glow * 0.4 + bass_kick
115
+
116
+ if total_intensity > 0.80 or (bass >= 7 and dist < 0.15):
117
+ return 10
118
+ elif total_intensity > 0.60:
119
+ return 11
120
+ elif total_intensity > 0.40:
121
+ return 12
122
+ elif total_intensity > 0.20:
123
+ return 13
124
+ return 14
125
+
126
+ # NEW: CAVA Hyper-Fluid Plasma (Sub-MS Chroma Flow)
127
+ elif effect == "cava_hyper_fluid_plasma":
128
+ bass_e = sum(cava_bars[:5]) / 40.0 if len(cava_bars) >= 5 else 0
129
+ mid_e = sum(cava_bars[5:15]) / 80.0 if len(cava_bars) >= 15 else 0
130
+ high_e = sum(cava_bars[15:]) / max(1, len(cava_bars[15:]) * 8.0) if len(cava_bars) >= 16 else 0
131
+ v1 = math.sin(x_norm * 8.0 + t_now * (2.5 + bass_e * 4.0) + row_i * 2.0)
132
+ v2 = math.cos(x_norm * 12.0 - t_now * (3.0 + mid_e * 5.0) + c_idx * 0.3)
133
+ plasma = (v1 + v2) * 0.5
134
+ intensity = (plasma + 1.0) * 0.4 + (bass_e * 0.35) + (high_e * 0.25)
135
+ if intensity > 0.82: return 10
136
+ elif intensity > 0.62: return 11
137
+ elif intensity > 0.42: return 12
138
+ elif intensity > 0.20: return 13
139
+ return 14
140
+
141
+ # NEW: CAVA Ballistic Shockwave (Transient Impact Rings)
142
+ elif effect == "cava_ballistic_shockwave":
143
+ kick = max(cava_bars[:3]) / 8.0 if cava_bars else 0
144
+ speed = 2.0 + kick * 4.0
145
+ center_dist = abs(x_norm - 0.5) * 2.0
146
+ wave_phase = (t_now * speed) % 1.3
147
+ ring = max(0.0, 1.0 - abs(center_dist - wave_phase) * 5.0) * (0.4 + kick * 0.6)
148
+ if ring > 0.78: return 10
149
+ elif ring > 0.55: return 11
150
+ elif ring > 0.32: return 12
151
+ elif ring > 0.15: return 13
152
+ return 14
153
+
154
+ # NEW: CAVA Quantum Flux (Sub-Atomic Wavefunction)
155
+ elif effect == "cava_quantum_flux":
156
+ vocal = sum(cava_bars[4:12]) / 64.0 if len(cava_bars) >= 12 else 0
157
+ k1 = 14.0 + vocal * 10.0
158
+ psi = math.sin(x_norm * k1 - t_now * 6.0) * math.cos(row_i * 3.14 + t_now * (4.0 + vocal * 8.0))
159
+ prob = (psi * psi) * (0.5 + vocal * 0.5)
160
+ if prob > 0.75: return 10
161
+ elif prob > 0.50: return 11
162
+ elif prob > 0.28: return 12
163
+ elif prob > 0.12: return 13
164
+ return 14
165
+
166
+ # NEW: CAVA Ballistic VU Sweep (Studio Dual-Needle)
167
+ elif effect == "cava_analog_vu_sweep":
168
+ rms = sum(cava_bars) / max(1, len(cava_bars) * 8.0)
169
+ needle_pos = min(1.0, rms * 1.35)
170
+ dist = abs(x_norm - needle_pos)
171
+ needle_glow = max(0.0, 1.0 - dist * 4.5)
172
+ if needle_glow > 0.80: return 10
173
+ elif needle_glow > 0.55: return 11
174
+ elif needle_glow > 0.30: return 12
175
+ elif needle_glow > 0.12: return 13
176
+ return 14
177
+
178
+ # NEW: CAVA Solar Corona Flare (Sub-Bass Eruption)
179
+ elif effect == "cava_solar_flare":
180
+ sub = max(cava_bars[:3]) if cava_bars else 0
181
+ coronal = math.sin(x_norm * 6.28 + t_now * 3.0) * (sub / 8.0)
182
+ row_bias = 0.3 if row_i == 0 else 0.0
183
+ flare = (sub / 8.0) * 0.7 + coronal * 0.3 + row_bias
184
+ if flare > 0.76: return 10
185
+ elif flare > 0.52: return 11
186
+ elif flare > 0.32: return 12
187
+ elif flare > 0.15: return 13
188
+ return 14
189
+
190
+ # NEW: CAVA Neural Synapse (Cerebral Pulse Network)
191
+ elif effect == "cava_neon_synapse":
192
+ highs = max(cava_bars[12:]) if len(cava_bars) >= 13 else 0
193
+ fire = math.sin(c_idx * 9.1 + t_now * (18.0 + highs * 6.0))
194
+ pulse = (fire > 0.6) * (highs / 8.0)
195
+ if pulse > 0.70: return 10
196
+ elif pulse > 0.45: return 11
197
+ elif pulse > 0.25: return 12
198
+ elif pulse > 0.10: return 13
199
+ return 14
200
+
201
+ # NEW: CAVA Crystalline Prism (Acoustic Refraction)
202
+ elif effect == "cava_crystal_shatter":
203
+ lows = sum(cava_bars[:6]) / 48.0 if len(cava_bars) >= 6 else 0
204
+ highs = sum(cava_bars[14:]) / 48.0 if len(cava_bars) >= 15 else 0
205
+ facet = math.sin((x_norm + lows) * 16.0 + t_now * (3.0 + highs * 5.0))
206
+ refract = (facet + 1.0) * 0.5 * (0.4 + highs * 0.6)
207
+ if refract > 0.80: return 10
208
+ elif refract > 0.58: return 11
209
+ elif refract > 0.38: return 12
210
+ elif refract > 0.18: return 13
211
+ return 14
212
+
213
+ # NEW: CAVA Cyber Glitch (Matrix Digital Rain)
214
+ elif effect == "cava_cyber_matrix_glitch":
215
+ bass = max(cava_bars[:4]) if len(cava_bars) >= 4 else 0
216
+ treble = max(cava_bars[12:]) if len(cava_bars) >= 13 else 0
217
+ glitch_seed = math.sin(c_idx * 13.37 + math.floor(t_now * (12.0 + treble * 4.0)))
218
+ drop_pos = (t_now * (4.0 + (bass / 8.0) * 8.0) + (c_idx * 0.15)) % 1.2
219
+ dist_drop = abs((row_i / 6.0) - drop_pos)
220
+ is_glitch = (glitch_seed > 0.65 and treble >= 4) or (bass >= 7 and dist_drop < 0.25)
221
+ if is_glitch: return 10
222
+ elif dist_drop < 0.18: return 11
223
+ elif dist_drop < 0.35: return 12
224
+ elif (bass / 8.0) > 0.4: return 13
225
+ return 14
226
+
227
+ # NEW: CAVA Supernova Inferno (Thermal Combustion Flame)
228
+ elif effect == "cava_supernova_inferno":
229
+ bin_idx = min(len(cava_bars)-1, int(x_norm * len(cava_bars)))
230
+ col_pow = cava_bars[bin_idx] / 8.0
231
+ height_norm = 1.0 - (row_i / 5.0) if row_i <= 5 else 0.0
232
+ heat_flicker = math.sin(c_idx * 5.0 + t_now * 15.0) * 0.18
233
+ flame = col_pow + heat_flicker
234
+ if flame >= height_norm + 0.35: return 10
235
+ elif flame >= height_norm + 0.15: return 11
236
+ elif flame >= height_norm - 0.10: return 12
237
+ elif flame >= height_norm - 0.30: return 13
238
+ return 14
239
+
240
+ # NEW: CAVA Laser Crosshairs (Tactical Dual Scanner)
241
+ elif effect == "cava_laser_crosshair_scan":
242
+ mids = sum(cava_bars[4:14]) / 80.0 if len(cava_bars) >= 14 else 0
243
+ speed = 1.5 + mids * 3.5
244
+ laser_l = (t_now * speed) % 1.4 - 0.2
245
+ laser_r = 1.0 - laser_l
246
+ dist_l = abs(x_norm - laser_l)
247
+ dist_r = abs(x_norm - laser_r)
248
+ collision = (abs(laser_l - 0.5) < 0.1) and (mids > 0.5)
249
+ if collision or min(dist_l, dist_r) < 0.04: return 10
250
+ elif min(dist_l, dist_r) < 0.10: return 11
251
+ elif min(dist_l, dist_r) < 0.20: return 12
252
+ elif min(dist_l, dist_r) < 0.32: return 13
253
+ return 14
254
+
255
+ # NEW: CAVA Liquid Mercury (Viscous Chrome Flow)
256
+ elif effect == "cava_liquid_mercury_flow":
257
+ energy = sum(cava_bars) / max(1, len(cava_bars) * 8.0)
258
+ v1 = math.sin(x_norm * 9.0 + t_now * (2.0 + energy * 4.0))
259
+ v2 = math.cos(row_i * 2.0 - t_now * (3.0 + energy * 3.0))
260
+ wave = (v1 + v2) * 0.5
261
+ specular = max(0.0, math.cos(wave * 3.14 - x_norm * 2.0))
262
+ glow = specular * 0.6 + energy * 0.4
263
+ if glow > 0.78: return 10
264
+ elif glow > 0.55: return 11
265
+ elif glow > 0.35: return 12
266
+ elif glow > 0.15: return 13
267
+ return 14
268
+
269
+ # NEW: CAVA Hyperspace Warp (Interstellar Tunnel)
270
+ elif effect == "cava_hyperspace_warp_tunnel":
271
+ bass = max(cava_bars[:3]) / 8.0 if cava_bars else 0
272
+ rad_dist = math.sqrt((x_norm - 0.5) ** 2 + ((row_i - 2.5) / 5.0) ** 2) * 2.0
273
+ warp_speed = 3.0 + bass * 8.0
274
+ ring_phase = (rad_dist * 4.0 - t_now * warp_speed) % 1.0
275
+ tunnel_glow = max(0.0, 1.0 - abs(ring_phase - 0.5) * 3.0) * (0.3 + bass * 0.7)
276
+ if tunnel_glow > 0.72: return 10
277
+ elif tunnel_glow > 0.48: return 11
278
+ elif tunnel_glow > 0.28: return 12
279
+ elif tunnel_glow > 0.10: return 13
280
+ return 14
281
+
282
+ # NEW: CAVA Tesla Arc Discharge (High-Voltage Lightning)
283
+ elif effect == "cava_tesla_lightning_arc":
284
+ highs = max(cava_bars[10:]) if len(cava_bars) >= 11 else 0
285
+ sub = max(cava_bars[:3]) if cava_bars else 0
286
+ spark_rand = math.sin(c_idx * 17.3 + math.floor(t_now * 24.0))
287
+ is_bolt = (spark_rand > 0.60 and highs >= 5) or (sub >= 7 and spark_rand > 0.40)
288
+ if is_bolt: return 10
289
+ elif spark_rand > 0.35 and highs >= 3: return 11
290
+ elif spark_rand > 0.10: return 12
291
+ elif (highs / 8.0) > 0.3: return 13
292
+ return 14
293
+
294
+ # 2. CAVA Live Frequency Equalizer
295
+ elif effect == "cava_equalizer":
296
+ bin_idx = min(len(cava_bars)-1, int(x_norm * len(cava_bars)))
297
+ bin_val = cava_bars[bin_idx]
298
+ if bin_val >= 7: return 10
299
+ elif bin_val >= 5: return 11
300
+ elif bin_val >= 3: return 12
301
+ elif bin_val >= 1: return 13
302
+ return 14
303
+
304
+ # 3. CAVA Sub-Bass Radial Shockwave Bloom
305
+ elif effect == "cava_bass_bloom":
306
+ bass = max(cava_bars[:4]) if len(cava_bars) >= 4 else 0
307
+ bass_ratio = bass / 8.0
308
+ dist_center = abs(x_norm - 0.5) * 2.0
309
+ shock_pos = (t_now * 2.5) % 1.2
310
+ wave_dist = abs(dist_center - shock_pos)
311
+ flare = max(0.0, 0.6 - wave_dist * 2.5) * bass_ratio
312
+ if flare > 0.45 or (bass >= 7 and dist_center < 0.3):
313
+ return 10
314
+ elif flare > 0.30:
315
+ return 11
316
+ elif flare > 0.15:
317
+ return 12
318
+ elif bass >= 4:
319
+ return 13
320
+ return 14
321
+
322
+ # 4. CAVA Audio Laser Sweep (Transient Burst)
323
+ elif effect == "cava_laser_sweep":
324
+ mids = max(cava_bars[4:14]) if len(cava_bars) >= 14 else 0
325
+ treble = max(cava_bars[14:]) if len(cava_bars) >= 15 else 0
326
+ speed = 1.0 + (mids / 8.0) * 2.5 + (treble / 8.0) * 1.5
327
+ laser_pos = (t_now * speed) % 1.4 - 0.2
328
+ dist = abs(x_norm - laser_pos)
329
+ if dist < 0.05:
330
+ return 10
331
+ elif dist < 0.12:
332
+ return 11
333
+ elif dist < 0.22:
334
+ return 12
335
+ elif dist < 0.35:
336
+ return 13
337
+ return 14
338
+
339
+ # 5. CAVA Treble Sparkle Storm (Vocal & Cymbal Glitter)
340
+ elif effect == "cava_treble_sparkle":
341
+ treble = max(cava_bars[12:]) if len(cava_bars) >= 13 else 0
342
+ if treble < 2: return 14
343
+ spark_phase = math.sin(c_idx * 2.3 + row_i * 4.1 + t_now * (6.0 + treble))
344
+ if spark_phase > 0.75 and treble >= 6:
345
+ return 10
346
+ elif spark_phase > 0.45 and treble >= 4:
347
+ return 11
348
+ elif spark_phase > 0.15 and treble >= 2:
349
+ return 12
350
+ return 14
351
+
352
+ # 6. CAVA Stereo Channel Wave (Left/Right Channel Ping-Pong)
353
+ elif effect == "cava_stereo_pulse":
354
+ half = len(cava_bars) // 2
355
+ l_amp = max(cava_bars[:half]) / 8.0 if half > 0 else 0
356
+ r_amp = max(cava_bars[half:]) / 8.0 if half > 0 else 0
357
+ if x_norm < 0.5:
358
+ col_ratio = l_amp * (1.0 - x_norm * 0.8)
359
+ else:
360
+ col_ratio = r_amp * (0.6 + (x_norm - 0.5) * 0.8)
361
+ if col_ratio > 0.82: return 10
362
+ elif col_ratio > 0.62: return 11
363
+ elif col_ratio > 0.42: return 12
364
+ elif col_ratio > 0.22: return 13
365
+ return 14
366
+
367
+ # 7. CAVA Kick Strobe (808 Bass Flash)
368
+ elif effect == "cava_kick_strobe":
369
+ bass = max(cava_bars[:3]) if len(cava_bars) >= 3 else 0
370
+ if bass >= 7:
371
+ return 10
372
+ elif bass >= 5:
373
+ return 11
374
+ elif bass >= 3:
375
+ return 12
376
+ return 14
377
+
378
+ # 8. CAVA Vocal Aura (Mid-Range Siphon)
379
+ elif effect == "cava_vocal_aura":
380
+ mids = max(cava_bars[4:12]) if len(cava_bars) >= 12 else 0
381
+ dist_c = abs(x_norm - 0.5) * 2.0
382
+ aura = (mids / 8.0) * max(0.0, 1.0 - dist_c * 0.8)
383
+ if aura > 0.8: return 10
384
+ elif aura > 0.55: return 11
385
+ elif aura > 0.35: return 12
386
+ elif aura > 0.15: return 13
387
+ return 14
388
+
389
+ # 9. CAVA Hi-Hat Glitter (Percussion Shimmer)
390
+ elif effect == "cava_hihat_glitter":
391
+ highs = max(cava_bars[14:]) if len(cava_bars) >= 15 else 0
392
+ if highs < 2: return 14
393
+ tick = math.sin(c_idx * 3.7 + row_i * 2.3 + t_now * (12.0 + highs * 2))
394
+ if tick > 0.70 and highs >= 5: return 10
395
+ elif tick > 0.40 and highs >= 3: return 11
396
+ elif tick > 0.10: return 12
397
+ return 14
398
+
399
+ # 10. CAVA Spectral Drift (Chroma Mood Ring)
400
+ elif effect == "cava_frequency_drift":
401
+ bass = sum(cava_bars[:6])
402
+ mids = sum(cava_bars[6:14])
403
+ highs = sum(cava_bars[14:])
404
+ tot = max(1, bass + mids + highs)
405
+ cog = (mids * 1.0 + highs * 2.0) / tot
406
+ drift = (cog + x_norm * 0.5 + t_now * 0.4) % 1.0
407
+ if drift < 0.2: return 10
408
+ elif drift < 0.4: return 11
409
+ elif drift < 0.6: return 12
410
+ elif drift < 0.8: return 13
411
+ return 14
412
+
413
+ # 11. CAVA RMS Heatwave (Loudness Flare)
414
+ elif effect == "cava_rms_heatwave":
415
+ rms = sum(b * b for b in cava_bars) / max(1, len(cava_bars) * 64.0)
416
+ row_factor = 1.0 if row_i == 0 else 0.75
417
+ flare = rms * row_factor + max(0.0, math.sin(x_norm * 3.14) * 0.25)
418
+ if flare > 0.70: return 10
419
+ elif flare > 0.50: return 11
420
+ elif flare > 0.30: return 12
421
+ elif flare > 0.15: return 13
422
+ return 14
423
+
424
+ # 12. CAVA Stereo Cascade (Dual Interference)
425
+ elif effect == "cava_stereo_cascade":
426
+ half = len(cava_bars) // 2
427
+ l_amp = max(cava_bars[:half]) / 8.0 if half > 0 else 0
428
+ r_amp = max(cava_bars[half:]) / 8.0 if half > 0 else 0
429
+ l_wave = max(0.0, l_amp - x_norm)
430
+ r_wave = max(0.0, r_amp - (1.0 - x_norm))
431
+ interference = l_wave + r_wave
432
+ if interference > 1.2: return 10
433
+ elif interference > 0.8: return 11
434
+ elif interference > 0.5: return 12
435
+ elif interference > 0.2: return 13
436
+ return 14
437
+
438
+
439
+ # 13. CAVA Transient Spark (Dynamic Accent)
440
+ elif effect == "cava_transient_spark":
441
+ mids = max(cava_bars[6:12]) if len(cava_bars) >= 12 else 0
442
+ if mids < 3: return 14
443
+ spark = math.sin(c_idx * 5.1 + row_i * 3.7 + t_now * (8.0 + mids * 3))
444
+ if spark > 0.82 and mids >= 6: return 10
445
+ elif spark > 0.50 and mids >= 4: return 11
446
+ elif spark > 0.20: return 12
447
+ return 14
448
+
449
+ # 14. CAVA Harmonic Resonance (Chord Glow)
450
+ elif effect == "cava_harmonic_breath":
451
+ tot_energy = sum(cava_bars) / max(1, len(cava_bars) * 8.0)
452
+ breath = (math.sin(t_now * 2.0) + 1.0) * 0.5 * tot_energy
453
+ if breath > 0.65: return 10
454
+ elif breath > 0.45: return 11
455
+ elif breath > 0.25: return 12
456
+ elif breath > 0.10: return 13
457
+ return 14
458
+
459
+ # 15. CAVA Sub-Bass Tremor (Low-End Rumble)
460
+ elif effect == "cava_sub_earthquake":
461
+ sub = max(cava_bars[:2]) if len(cava_bars) >= 2 else 0
462
+ if sub >= 7:
463
+ tremor = int((t_now * 30.0 + c_idx) % 2)
464
+ return 10 if tremor == 0 else 11
465
+ elif sub >= 5:
466
+ return 11
467
+ elif sub >= 3:
468
+ return 12
469
+ return 14
470
+
471
+ # 16. CAVA Aurora Wave (Spectral Ribbon)
472
+ elif effect == "cava_aurora_wave":
473
+ speed = 1.0 + (sum(cava_bars) / max(1, len(cava_bars) * 8.0)) * 2.0
474
+ ribbon_pos = (t_now * speed) % 1.5 - 0.25
475
+ dist = abs(x_norm - ribbon_pos)
476
+ wave = max(0.0, 0.45 - dist * 2.0)
477
+ if wave > 0.35: return 10
478
+ elif wave > 0.25: return 11
479
+ elif wave > 0.15: return 12
480
+ elif wave > 0.05: return 13
481
+ return 14
482
+
483
+ # 17. CAVA Stereo Panning (Binaural Flow)
484
+ elif effect == "cava_stereo_pan":
485
+ half = len(cava_bars) // 2
486
+ l_pow = sum(cava_bars[:half])
487
+ r_pow = sum(cava_bars[half:])
488
+ tot = max(1, l_pow + r_pow)
489
+ pan_center = r_pow / tot
490
+ dist_pan = abs(x_norm - pan_center)
491
+ glow = max(0.0, 0.5 - dist_pan * 1.5)
492
+ if glow > 0.38: return 10
493
+ elif glow > 0.28: return 11
494
+ elif glow > 0.18: return 12
495
+ elif glow > 0.08: return 13
496
+ return 14
497
+
498
+ # 18. CAVA Vocal Resonance (Liquid Ripple)
499
+ elif effect == "cava_vocal_ripple":
500
+ mids = max(cava_bars[4:12]) if len(cava_bars) >= 12 else 0
501
+ ripple = math.sin(abs(x_norm - 0.5) * 14.0 - t_now * (3.0 + (mids / 8.0) * 5.0))
502
+ intensity = (mids / 8.0) * 0.6 + max(0.0, ripple * 0.4)
503
+ if intensity > 0.78: return 10
504
+ elif intensity > 0.55: return 11
505
+ elif intensity > 0.35: return 12
506
+ elif intensity > 0.15: return 13
507
+ return 14
508
+
509
+ # 19. CAVA Laser Crossfire (Dual Snare Lasers)
510
+ elif effect == "cava_laser_crossfire":
511
+ treble = max(cava_bars[12:]) if len(cava_bars) >= 13 else 0
512
+ speed = 1.5 + (treble / 8.0) * 4.0
513
+ laser_l = (t_now * speed) % 1.6 - 0.3
514
+ laser_r = 1.0 - ((t_now * speed) % 1.6 - 0.3)
515
+ dist_l = abs(x_norm - laser_l)
516
+ dist_r = abs(x_norm - laser_r)
517
+ min_dist = min(dist_l, dist_r)
518
+ if min_dist < 0.06: return 10
519
+ elif min_dist < 0.14: return 11
520
+ elif min_dist < 0.24: return 12
521
+ elif min_dist < 0.36: return 13
522
+ return 14
523
+
524
+ # 20. CAVA Spectrum Cascade (Multi-Band Rain)
525
+ elif effect == "cava_spectrum_cascade":
526
+ bin_idx = min(len(cava_bars)-1, int(x_norm * len(cava_bars)))
527
+ v = cava_bars[bin_idx]
528
+ drop = math.sin(row_i * 3.5 + c_idx * 1.8 + t_now * (4.0 + v))
529
+ flare = (v / 8.0) * 0.65 + max(0.0, drop * 0.35)
530
+ if flare > 0.80: return 10
531
+ elif flare > 0.60: return 11
532
+ elif flare > 0.40: return 12
533
+ elif flare > 0.20: return 13
534
+ return 14
535
+
536
+ # 21. CAVA Cardiac Sub-Pulse (Kick Rhythm)
537
+ elif effect == "cava_heartbeat_sub":
538
+ bass = max(cava_bars[:3]) if len(cava_bars) >= 3 else 0
539
+ pulse = math.sin(t_now * 8.0)
540
+ cardiac = (bass / 8.0) * (1.0 if pulse > 0.3 else 0.4)
541
+ if cardiac > 0.75: return 10
542
+ elif cardiac > 0.50: return 11
543
+ elif cardiac > 0.30: return 12
544
+ elif cardiac > 0.15: return 13
545
+ return 14
546
+
547
+ # 22. CAVA Diamond Glitter (Hi-Hat Storm)
548
+ elif effect == "cava_diamond_glitter":
549
+ highs = max(cava_bars[14:]) if len(cava_bars) >= 15 else 0
550
+ if highs < 2: return 14
551
+ shimmer = math.sin(c_idx * 7.7 + row_i * 5.3 + t_now * (16.0 + highs * 3))
552
+ if shimmer > 0.85 and highs >= 5: return 10
553
+ elif shimmer > 0.50 and highs >= 3: return 11
554
+ elif shimmer > 0.15: return 12
555
+ return 14
556
+
557
+ # 23. CAVA Harmonic Vortex (Acoustic Whirlwind)
558
+ elif effect == "cava_harmonic_vortex":
559
+ energy = sum(cava_bars) / max(1, len(cava_bars) * 8.0)
560
+ angle = math.atan2(row_i - 0.5, x_norm - 0.5)
561
+ vortex = math.sin(angle * 2.0 + t_now * (3.0 + energy * 6.0))
562
+ glow = energy * 0.6 + max(0.0, vortex * 0.4)
563
+ if glow > 0.75: return 10
564
+ elif glow > 0.55: return 11
565
+ elif glow > 0.35: return 12
566
+ elif glow > 0.15: return 13
567
+ return 14
568
+
569
+ return 14