Grid2Drum / data /diffusion_cache_utils.py
soilkon's picture
Listener app: fetch weights from model repo at startup
9f7d425 verified
Raw History Blame Contribute Delete
17.5 kB
from __future__ import annotations
import hashlib
from typing import Any, Mapping, Sequence
import numpy as np
import torch
FAMILY_STATE_FAMILY_NAMES: tuple[str, ...] = (
"kick",
"snare",
"tom_high",
"tom_mid",
"tom_floor",
"hihat",
"crash",
"ride",
)
FAMILY_STATE_NUMERIC_COMPONENT_NAMES: tuple[str, ...] = (
"state_vel",
"onset_vel",
"onset_count",
)
FAMILY_STATE_FEATURE_ROW_NAMES: tuple[str, ...] = tuple(
f"{family_name}_{component_name}"
for family_name in FAMILY_STATE_FAMILY_NAMES
for component_name in FAMILY_STATE_NUMERIC_COMPONENT_NAMES
)
FAMILY_STATE_ID_VOCAB_SIZES: tuple[int, ...] = (
1,
3,
2,
2,
2,
5,
2,
3,
)
def hash_bytes(payload: bytes) -> str:
digest = hashlib.sha256()
digest.update(payload)
return digest.hexdigest()
def hash_tensor(tensor: torch.Tensor) -> str:
arr = tensor.detach().to(device="cpu").contiguous().numpy()
return hash_bytes(arr.tobytes())
def fit_pca(
x_nd: np.ndarray,
*,
pca_k: int,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, int]:
x = np.asarray(x_nd, dtype=np.float32)
if int(x.ndim) != 2:
raise ValueError(f"x_nd must be [N,D], got shape {tuple(x.shape)}")
if int(x.shape[0]) <= 0:
raise ValueError("x_nd must have at least one row")
mean = x.mean(axis=0, keepdims=True)
xc = x - mean
_u, s, vt = np.linalg.svd(xc, full_matrices=False)
var = (s ** 2) / max(1, (xc.shape[0] - 1))
ratio = var / max(var.sum(), 1.0e-8)
k = int(min(max(1, int(pca_k)), int(vt.shape[0])))
components = vt[:k].astype(np.float32, copy=False)
explained = ratio[:k].astype(np.float32, copy=False)
return mean.astype(np.float32, copy=False), components, explained, k
def fit_projected_pca(
x_nd: np.ndarray,
*,
projection_basis_dq: np.ndarray,
pca_k: int,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, int]:
x = np.asarray(x_nd, dtype=np.float32)
basis = np.asarray(projection_basis_dq, dtype=np.float32)
if int(x.ndim) != 2:
raise ValueError(f"x_nd must be [N,D], got shape {tuple(x.shape)}")
if int(basis.ndim) != 2:
raise ValueError(f"projection_basis_dq must be [D,Q], got shape {tuple(basis.shape)}")
if int(x.shape[0]) <= 0:
raise ValueError("x_nd must have at least one row")
if int(x.shape[1]) != int(basis.shape[0]):
raise ValueError(
f"x_nd / projection basis dimension mismatch: {tuple(x.shape)} vs {tuple(basis.shape)}"
)
mean_full = x.mean(axis=0, keepdims=True)
coeff = (x - mean_full) @ basis
coeff_mean = coeff.mean(axis=0, keepdims=True)
coeff_centered = coeff - coeff_mean
_u, s, vt = np.linalg.svd(coeff_centered, full_matrices=False)
var = (s ** 2) / max(1, (coeff_centered.shape[0] - 1))
ratio = var / max(var.sum(), 1.0e-8)
k = int(min(max(1, int(pca_k)), int(vt.shape[0])))
coeff_components = vt[:k].astype(np.float32, copy=False)
components = (coeff_components @ basis.transpose()).astype(np.float32, copy=False)
mean = (mean_full + (coeff_mean @ basis.transpose())).astype(np.float32, copy=False)
explained = ratio[:k].astype(np.float32, copy=False)
return mean.reshape(-1), components, explained, k
def fix_component_signs(components: np.ndarray) -> np.ndarray:
fixed = np.asarray(components, dtype=np.float32).copy()
for idx in range(int(fixed.shape[0])):
comp = fixed[int(idx)]
max_idx = int(np.argmax(np.abs(comp)))
if float(comp[int(max_idx)]) < 0.0:
fixed[int(idx)] = -comp
return fixed
def token_times_from_duration(code_frames: int, duration_sec: float) -> np.ndarray:
frames = int(max(0, int(code_frames)))
dur = float(max(0.0, float(duration_sec)))
if frames <= 0 or dur <= 0.0:
return np.zeros((0,), dtype=np.float32)
idx = np.arange(frames, dtype=np.float32)
return ((idx + 0.5) * (dur / float(frames))).astype(np.float32, copy=False)
def grid_times_from_fps(grid_frames: int, fps: float) -> np.ndarray:
frames = int(max(0, int(grid_frames)))
fps_eff = float(max(1.0e-6, float(fps)))
if frames <= 0:
return np.zeros((0,), dtype=np.float32)
return ((np.arange(frames, dtype=np.float32) + np.float32(0.5)) / fps_eff).astype(np.float32, copy=False)
def sample_beat_boundaries_from_source(
*,
shard: Mapping[str, Any],
row: Mapping[str, Any],
duration_sec: float,
) -> np.ndarray:
duration_eff = float(max(1.0e-6, float(duration_sec)))
beat_times_raw = shard.get("beat_times_sec")
if beat_times_raw is None:
return np.asarray([0.0, float(duration_eff)], dtype=np.float32)
if torch.is_tensor(beat_times_raw):
beat_times = beat_times_raw.detach().to(device="cpu", dtype=torch.float32).numpy().reshape(-1)
else:
beat_times = np.asarray(beat_times_raw, dtype=np.float32).reshape(-1)
beat_times = beat_times[np.isfinite(beat_times)]
boundaries = np.concatenate(([0.0], beat_times.astype(np.float32, copy=False)), dtype=np.float32)
start_sec = float(row.get("start_sec", 0.0) or 0.0)
end_sec = float(row.get("end_sec", start_sec + duration_eff) or (start_sec + duration_eff))
beat_index = int(row.get("beat_index", -1) or -1)
if row.get("num_beats") is not None:
num_beats = int(max(1, int(row.get("num_beats", 1) or 1)))
else:
beat_index_end = int(row.get("beat_index_end", beat_index) or beat_index)
num_beats = int(max(1, int(beat_index_end) - int(beat_index) + 1)) if int(beat_index) >= 0 else 1
if int(beat_index) >= 0 and int(beat_index) + int(num_beats) < int(boundaries.shape[0]):
sample_boundaries = np.asarray(
boundaries[int(beat_index) : int(beat_index) + int(num_beats) + 1],
dtype=np.float32,
)
else:
eps = np.float32(1.0e-4)
mask = (boundaries >= np.float32(float(start_sec) - float(eps))) & (
boundaries <= np.float32(float(end_sec) + float(eps))
)
sample_boundaries = np.asarray(boundaries[mask], dtype=np.float32)
sample_boundaries = np.concatenate(
(
np.asarray([float(start_sec)], dtype=np.float32),
sample_boundaries,
np.asarray([float(end_sec)], dtype=np.float32),
),
dtype=np.float32,
)
sample_boundaries = np.clip(
np.asarray(sample_boundaries, dtype=np.float32) - np.float32(float(start_sec)),
np.float32(0.0),
np.float32(float(duration_eff)),
)
sample_boundaries = np.sort(sample_boundaries, axis=None).astype(np.float32, copy=False)
if int(sample_boundaries.size) <= 0:
return np.asarray([0.0, float(duration_eff)], dtype=np.float32)
deduped: list[float] = [float(sample_boundaries[0])]
for value in list(sample_boundaries[1:]):
if float(value) > float(deduped[-1]) + 1.0e-5:
deduped.append(float(value))
if float(deduped[0]) > 1.0e-5:
deduped.insert(0, 0.0)
else:
deduped[0] = 0.0
if float(deduped[-1]) < float(duration_eff) - 1.0e-5:
deduped.append(float(duration_eff))
else:
deduped[-1] = float(duration_eff)
if int(len(deduped)) < 2:
deduped = [0.0, float(duration_eff)]
return np.asarray(deduped, dtype=np.float32)
def normalize_segment_beat_boundaries_rel(
boundaries_sec_rel: Sequence[float],
*,
expected_num_beats: int,
duration_sec: float,
merge_tolerance_sec: float = 1.0e-4,
) -> np.ndarray:
boundaries = np.asarray(boundaries_sec_rel, dtype=np.float64).reshape(-1)
expected_count = int(max(1, int(expected_num_beats)) + 1)
duration = float(max(1.0e-6, float(duration_sec)))
tol = float(max(1.0e-8, float(merge_tolerance_sec)))
if int(boundaries.size) <= 0:
return np.asarray([0.0, duration], dtype=np.float32)
boundaries = np.clip(boundaries, 0.0, duration)
merged: list[float] = [0.0]
for value in list(boundaries):
v = float(value)
if float(v) <= float(merged[-1]) + float(tol):
merged[-1] = max(float(merged[-1]), float(v))
else:
merged.append(float(v))
if float(merged[-1]) < float(duration) - float(tol):
merged.append(float(duration))
else:
merged[-1] = float(duration)
if int(len(merged)) == int(expected_count):
return np.asarray(merged, dtype=np.float32)
raise ValueError(
f"expected {expected_count} beat boundaries after normalization, got {tuple(np.asarray(merged).shape)} "
f"from raw boundaries {np.asarray(boundaries_sec_rel, dtype=np.float32).tolist()}"
)
def stack_family_state_grid(
*,
state_vel_ft: np.ndarray,
onset_vel_ft: np.ndarray,
onset_count_ft: np.ndarray,
) -> np.ndarray:
state_vel = np.asarray(state_vel_ft, dtype=np.float32)
onset_vel = np.asarray(onset_vel_ft, dtype=np.float32)
onset_count = np.asarray(onset_count_ft, dtype=np.float32)
if int(state_vel.ndim) != 2:
raise ValueError(f"state_vel_ft must be [F,T], got {tuple(state_vel.shape)}")
if tuple(onset_vel.shape) != tuple(state_vel.shape):
raise ValueError(f"onset_vel_ft must match state_vel_ft, got {tuple(onset_vel.shape)} / {tuple(state_vel.shape)}")
if tuple(onset_count.shape) != tuple(state_vel.shape):
raise ValueError(
f"onset_count_ft must match state_vel_ft, got {tuple(onset_count.shape)} / {tuple(state_vel.shape)}"
)
num_families, num_frames = [int(x) for x in list(state_vel.shape)]
if int(num_families) != int(len(FAMILY_STATE_FAMILY_NAMES)):
raise ValueError(
f"expected {len(FAMILY_STATE_FAMILY_NAMES)} families, got {tuple(state_vel.shape)}"
)
grid_rows = []
for family_idx in range(int(num_families)):
grid_rows.append(state_vel[int(family_idx)])
grid_rows.append(onset_vel[int(family_idx)])
grid_rows.append(onset_count[int(family_idx)])
out = np.stack(grid_rows, axis=0).astype(np.float32, copy=False)
if tuple(out.shape) != (int(len(FAMILY_STATE_FEATURE_ROW_NAMES)), int(num_frames)):
raise RuntimeError(f"unexpected stacked family-state grid shape {tuple(out.shape)}")
return out
def build_segment_beat_boundaries_rel(
*,
song_beat_times_sec: Sequence[float],
beat_index: int,
num_beats: int,
start_sec: float,
end_sec: float,
) -> np.ndarray:
beat_index = int(beat_index)
num_beats = int(num_beats)
start_sec = float(start_sec)
end_sec = float(end_sec)
if int(num_beats) <= 0:
raise ValueError(f"num_beats must be positive, got {num_beats}")
if not float(end_sec) > float(start_sec):
raise ValueError(f"end_sec must be greater than start_sec, got {start_sec} -> {end_sec}")
song_beats = np.asarray(song_beat_times_sec, dtype=np.float64).reshape(-1)
all_boundaries = np.concatenate((np.asarray([0.0], dtype=np.float64), song_beats), axis=0)
lo = int(beat_index)
hi = int(beat_index + num_beats)
if int(lo) < 0 or int(hi) >= int(all_boundaries.shape[0]):
raise ValueError(
f"beat boundary slice [{lo}:{hi}] is out of range for {int(all_boundaries.shape[0])} boundaries"
)
boundaries_abs = np.asarray(all_boundaries[int(lo) : int(hi) + 1], dtype=np.float64).copy()
if int(boundaries_abs.shape[0]) != int(num_beats + 1):
raise ValueError(
f"expected {int(num_beats + 1)} boundaries, got shape {tuple(boundaries_abs.shape)}"
)
boundaries_abs[0] = float(start_sec)
boundaries_abs[-1] = float(end_sec)
boundaries_rel = np.asarray(boundaries_abs - float(start_sec), dtype=np.float32)
duration_sec = np.float32(float(end_sec) - float(start_sec))
boundaries_rel[0] = np.float32(0.0)
boundaries_rel[-1] = duration_sec
boundaries_rel = np.clip(boundaries_rel, 0.0, float(duration_sec))
diffs = np.diff(boundaries_rel.astype(np.float64))
if np.any(diffs <= 0.0):
raise ValueError(
f"segment beat boundaries must be strictly increasing, got {boundaries_rel.tolist()}"
)
return boundaries_rel.astype(np.float32, copy=False)
def expand_beat_boundaries_to_16th_steps(beat_boundaries_sec_rel: Sequence[float]) -> np.ndarray:
beat_boundaries = np.asarray(beat_boundaries_sec_rel, dtype=np.float64).reshape(-1)
if int(beat_boundaries.shape[0]) != 5:
raise ValueError(f"expected 5 beat boundaries for a 4-beat segment, got {tuple(beat_boundaries.shape)}")
diffs = np.diff(beat_boundaries)
if np.any(diffs <= 0.0):
raise ValueError(f"beat boundaries must be strictly increasing, got {beat_boundaries.tolist()}")
step_boundaries: list[float] = []
for beat_idx in range(4):
beat_start = float(beat_boundaries[int(beat_idx)])
beat_end = float(beat_boundaries[int(beat_idx) + 1])
local = np.linspace(beat_start, beat_end, num=5, endpoint=True, dtype=np.float64)
step_boundaries.extend(local[:-1].tolist())
step_boundaries.append(float(beat_boundaries[-1]))
out = np.asarray(step_boundaries, dtype=np.float32)
if int(out.shape[0]) != 17:
raise RuntimeError(f"expected 17 step boundaries, got {tuple(out.shape)}")
return out
def aggregate_grid16_from_conditioning(
*,
state_vel_ft: np.ndarray,
onset_vel_ft: np.ndarray,
onset_ids_ft: np.ndarray,
family_onsets_ft: np.ndarray,
onset_count_ft: np.ndarray,
step_boundaries_sec_rel: Sequence[float],
duration_sec: float,
) -> dict[str, np.ndarray]:
state_vel = np.asarray(state_vel_ft, dtype=np.float32)
onset_vel = np.asarray(onset_vel_ft, dtype=np.float32)
onset_ids = np.asarray(onset_ids_ft, dtype=np.int64)
family_onsets = np.asarray(family_onsets_ft, dtype=np.bool_)
onset_count = np.asarray(onset_count_ft, dtype=np.uint8)
step_boundaries = np.asarray(step_boundaries_sec_rel, dtype=np.float64).reshape(-1)
duration_sec = float(duration_sec)
if int(state_vel.ndim) != 2:
raise ValueError(f"state_vel_ft must be [F,T], got {tuple(state_vel.shape)}")
if tuple(onset_vel.shape) != tuple(state_vel.shape):
raise ValueError(f"onset_vel_ft must match state_vel_ft, got {tuple(onset_vel.shape)} / {tuple(state_vel.shape)}")
if tuple(onset_ids.shape) != tuple(state_vel.shape):
raise ValueError(f"onset_ids_ft must match state_vel_ft, got {tuple(onset_ids.shape)} / {tuple(state_vel.shape)}")
if tuple(family_onsets.shape) != tuple(state_vel.shape):
raise ValueError(
f"family_onsets_ft must match state_vel_ft, got {tuple(family_onsets.shape)} / {tuple(state_vel.shape)}"
)
if tuple(onset_count.shape) != tuple(state_vel.shape):
raise ValueError(
f"onset_count_ft must match state_vel_ft, got {tuple(onset_count.shape)} / {tuple(state_vel.shape)}"
)
if int(step_boundaries.shape[0]) != 17:
raise ValueError(f"step_boundaries_sec_rel must have 17 values, got {tuple(step_boundaries.shape)}")
if not float(duration_sec) > 0.0:
raise ValueError(f"duration_sec must be positive, got {duration_sec}")
num_families, grid_frames = [int(x) for x in list(state_vel.shape)]
frame_edges = np.linspace(0.0, float(duration_sec), num=int(grid_frames) + 1, endpoint=True, dtype=np.float64)
frame_centers = 0.5 * (frame_edges[:-1] + frame_edges[1:])
grid16_state_vel = np.zeros((int(num_families), 16), dtype=np.float32)
grid16_onset_vel = np.zeros((int(num_families), 16), dtype=np.float32)
grid16_onset_count = np.zeros((int(num_families), 16), dtype=np.uint8)
grid16_onset_ids = np.full((int(num_families), 16), -1, dtype=np.int16)
for step_idx in range(16):
step_start = float(step_boundaries[int(step_idx)])
step_end = float(step_boundaries[int(step_idx) + 1])
overlap = (frame_edges[1:] > float(step_start)) & (frame_edges[:-1] < float(step_end))
frame_idx = np.flatnonzero(overlap)
if int(frame_idx.size) <= 0:
midpoint = 0.5 * (float(step_start) + float(step_end))
frame_idx = np.asarray([int(np.argmin(np.abs(frame_centers - float(midpoint))))], dtype=np.int64)
grid16_state_vel[:, int(step_idx)] = np.max(state_vel[:, frame_idx], axis=1)
count_sum = onset_count[:, frame_idx].astype(np.int64).sum(axis=1)
grid16_onset_count[:, int(step_idx)] = np.clip(count_sum, 0, 255).astype(np.uint8, copy=False)
for family_idx in range(int(num_families)):
hit_frames = frame_idx[np.flatnonzero(family_onsets[int(family_idx), frame_idx])]
if int(hit_frames.size) <= 0:
continue
hit_vels = onset_vel[int(family_idx), hit_frames]
sort_key = np.lexsort((hit_frames.astype(np.int64), hit_vels.astype(np.float64)))
best_frame = int(hit_frames[int(sort_key[-1])])
grid16_onset_vel[int(family_idx), int(step_idx)] = float(onset_vel[int(family_idx), int(best_frame)])
grid16_onset_ids[int(family_idx), int(step_idx)] = np.int16(onset_ids[int(family_idx), int(best_frame)])
return {
"grid16_state_vel": grid16_state_vel,
"grid16_onset_vel": grid16_onset_vel,
"grid16_onset_count": grid16_onset_count,
"grid16_onset_ids": grid16_onset_ids,
"step_boundaries_sec_rel": np.asarray(step_boundaries, dtype=np.float32),
}