feat(presets): 6-colour contrast derivation fns
This commit is contained in:
@@ -39,6 +39,54 @@ def _mel(pairs):
|
||||
return [None if p is None else (int(p[0]), round(float(p[1]), 2)) for p in pairs]
|
||||
|
||||
|
||||
_DRUM_VELS = (0.30, 0.50, 0.80, 1.0)
|
||||
|
||||
|
||||
def _dhit(v):
|
||||
return None if v == 0.0 else (0, round(v, 2))
|
||||
|
||||
|
||||
def derive_drum_colors(base):
|
||||
"""Colours 2-6 from a 16-step drum base (None | (0, vel))."""
|
||||
n = len(base)
|
||||
busier = [base[i] if base[i] is not None else (_dhit(0.30) if i % 4 == 2 else None)
|
||||
for i in range(n)]
|
||||
broken = base[-1:] + base[:-1] # rotate right 1: same count, displaced
|
||||
double = [base[i] if base[i] is not None else (_dhit(0.80) if i % 2 == 0 else None)
|
||||
for i in range(n)]
|
||||
sparse = [base[i] if (base[i] is not None and ((base[i][1] >= 1.0) or i % 4 == 0)) else None
|
||||
for i in range(n)]
|
||||
fill = list(base)
|
||||
for k, v in enumerate((0.80, 0.80, 1.0, 1.0)):
|
||||
fill[n - 4 + k] = _dhit(v)
|
||||
return [busier, broken, double, sparse, fill]
|
||||
|
||||
|
||||
def derive_mel_colors(base):
|
||||
"""Colours 2-6 from a 16-step melodic base (None | (degree, vel))."""
|
||||
n = len(base)
|
||||
actives = [base[i][0] for i in range(n) if base[i] is not None]
|
||||
last_deg = actives[-1] if actives else 0
|
||||
|
||||
def prev_deg(i):
|
||||
for j in range(i, -1, -1):
|
||||
if base[j] is not None:
|
||||
return base[j][0]
|
||||
return last_deg
|
||||
|
||||
busier = [base[i] if base[i] is not None else ((prev_deg(i), 0.5) if i % 4 == 2 else None)
|
||||
for i in range(n)]
|
||||
broken = [((base[i][0] + 7, base[i][1]) if (base[i] is not None and i % 4 == 0) else base[i])
|
||||
for i in range(n)]
|
||||
double = [base[i] if base[i] is not None else ((prev_deg(i), 0.6) if i % 2 == 0 else None)
|
||||
for i in range(n)]
|
||||
sparse = [base[i] if (base[i] is not None and i % 4 == 0) else None for i in range(n)]
|
||||
fill = list(base)
|
||||
for k in range(4):
|
||||
fill[n - 4 + k] = (last_deg + k, round(0.6 + 0.1 * k, 2))
|
||||
return [busier, broken, double, sparse, fill]
|
||||
|
||||
|
||||
N = None
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Standalone test for the 6-colour contrast derivation.
|
||||
Run: cd sound_algo/data_only/matrix_presets && uv run python test_contrast.py
|
||||
Prints TEST PASS / TEST FAIL; exits non-zero on failure."""
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from patterns import derive_drum_colors, derive_mel_colors, _rhythm, _mel
|
||||
|
||||
ok = True
|
||||
def check(c, m):
|
||||
global ok
|
||||
if not c: ok = False; print("FAIL:", m)
|
||||
|
||||
def dens(p): return sum(1 for s in p if s is not None)
|
||||
|
||||
# --- drums ---
|
||||
base = _rhythm("X...x...X...x...") # 4 hits
|
||||
c2, c3, c4, c5, c6 = derive_drum_colors(base)
|
||||
check(all(len(c) == 16 for c in (c2,c3,c4,c5,c6)), "drum colours 16 steps")
|
||||
check(dens(c2) >= dens(base), "busier >= base")
|
||||
check(dens(c4) >= dens(base), "double >= base")
|
||||
check(dens(c5) <= dens(base), "sparse <= base")
|
||||
check(dens(c3) == dens(base), "broken keeps hit count")
|
||||
check([i for i in range(16) if (c3[i] is not None) != (base[i] is not None)] != [], "broken displaces")
|
||||
check(all(c6[i] is not None for i in range(12, 16)), "fill last 4 active")
|
||||
# velocities legal
|
||||
legal = {0.30, 0.50, 0.80, 1.0}
|
||||
for c in (c2, c3, c4, c5, c6):
|
||||
check(all(s is None or s[1] in legal for s in c), "drum vel legal")
|
||||
# determinism
|
||||
check(derive_drum_colors(base) == derive_drum_colors(base), "drum deterministic")
|
||||
|
||||
# --- mel ---
|
||||
mbase = _mel([(0,0.8),None,(3,0.6),None,(7,0.7),None,None,None,
|
||||
(5,0.6),None,(3,0.6),None,(0,0.8),None,None,None])
|
||||
m2, m3, m4, m5, m6 = derive_mel_colors(mbase)
|
||||
check(all(len(c) == 16 for c in (m2,m3,m4,m5,m6)), "mel colours 16 steps")
|
||||
check(dens(m4) >= dens(mbase), "mel double >= base")
|
||||
check(dens(m5) <= dens(mbase), "mel sparse <= base")
|
||||
check(all((s is None) or (isinstance(s[0], int) and isinstance(s[1], float)) for s in m4), "mel step shape")
|
||||
check(all(m6[i] is not None for i in range(12, 16)), "mel fill last 4 active")
|
||||
check(derive_mel_colors(mbase) == derive_mel_colors(mbase), "mel deterministic")
|
||||
|
||||
print("TEST PASS" if ok else "TEST FAIL")
|
||||
sys.exit(0 if ok else 1)
|
||||
Reference in New Issue
Block a user