so bad
This commit is contained in:
1 parent
096bce803a
commit
5810a536bd
1 file changed
+64
-13
@@ -23,12 +23,12 @@ from loguru import logger
|
|||||||
SAMPLE_RATE = 44100
|
SAMPLE_RATE = 44100
|
||||||
|
|
||||||
# Waveform envelope dimensions.
|
# Waveform envelope dimensions.
|
||||||
# PWAV is 400 columns × 1 byte, each byte = (whiteness<<5)|height (5-bit height,
|
# PWAV preview is **200 segments × 2 bytes** = 400 bytes — per Beat Link's
|
||||||
# 3-bit whiteness) — confirmed by byte-diffing a real export (the earlier
|
# reader, height = data[seg*2] & 0x1f, brightness = data[seg*2+1] & 0x07. (An
|
||||||
# "200 col-pairs" reading was wrong: it wrote height/intensity pairs the player
|
# earlier single-packed-byte reading was wrong; the player decoded it as garbled
|
||||||
# decoded as garbled half-columns with no whiteness). PWV2 = 100 columns × 1
|
# height/intensity pairs → "always full, never bright".) PWV2 = 100 columns × 1
|
||||||
# byte, 4-bit height in the low nibble.
|
# byte, 4-bit height. PWV3 (detail/scroll) genuinely IS 1 byte/col.
|
||||||
PWAV_COLS = 400 # PWAV: 400 single bytes (whiteness<<5 | 5-bit height)
|
PWAV_SEGMENTS = 200 # PWAV: 200 segments × 2 bytes (height, brightness)
|
||||||
PWV2_COLS = 100 # PWV2: 100 single bytes (4-bit height)
|
PWV2_COLS = 100 # PWV2: 100 single bytes (4-bit height)
|
||||||
SCROLL_FPS = 150 # PWV3 / PWV5 / PWV7 = ~150 cols/sec
|
SCROLL_FPS = 150 # PWV3 / PWV5 / PWV7 = ~150 cols/sec
|
||||||
PWAV_HEIGHT_LEVELS = 31 # 5-bit (0-31), bits 0-4
|
PWAV_HEIGHT_LEVELS = 31 # 5-bit (0-31), bits 0-4
|
||||||
@@ -116,6 +116,15 @@ def _decode_pcm(path: Path) -> np.ndarray:
|
|||||||
SILENCE_GATE_PCT = 0.02 # < 2% of peak → 0
|
SILENCE_GATE_PCT = 0.02 # < 2% of peak → 0
|
||||||
QUANT_GAMMA = 1.0 # HEIGHT: linear; do NOT lift mids toward max
|
QUANT_GAMMA = 1.0 # HEIGHT: linear; do NOT lift mids toward max
|
||||||
COLOR_GAMMA = 0.4 # COLOUR/whiteness: sub-linear lift
|
COLOR_GAMMA = 0.4 # COLOUR/whiteness: sub-linear lift
|
||||||
|
# PWAV (preview) height uses an ABSOLUTE loudness scale instead of per-track
|
||||||
|
# normalisation, so a quiet track/section reads short and a loud one tall
|
||||||
|
# (per-track norm always pinned the loudest column to full → "always too high").
|
||||||
|
# height = round(rms * PWAV_HEIGHT_SCALE), clamped to 31. The scale was DERIVED
|
||||||
|
# by least-squares fitting the genuine reference export's PWAV heights against
|
||||||
|
# per-column audio RMS across 3 tracks: 42.2 / 43.0 / 39.9 → 42 (mean err ~2/31;
|
||||||
|
# reference peaks at ~24, i.e. full ≈ RMS 0.74). (PWV3 keeps the per-track scale
|
||||||
|
# — it's good as-is.)
|
||||||
|
PWAV_HEIGHT_SCALE = 42
|
||||||
|
|
||||||
|
|
||||||
def _quantise(peaks: np.ndarray, levels: int, peak_max: float | None = None,
|
def _quantise(peaks: np.ndarray, levels: int, peak_max: float | None = None,
|
||||||
@@ -147,18 +156,60 @@ def _env_peaks(env: np.ndarray, n_cols: int) -> np.ndarray:
|
|||||||
|
|
||||||
|
|
||||||
def _packed_mono(samples: np.ndarray, hi_env: np.ndarray, n_cols: int,
|
def _packed_mono(samples: np.ndarray, hi_env: np.ndarray, n_cols: int,
|
||||||
height_levels: int = PWAV_HEIGHT_LEVELS) -> bytes:
|
height_levels: int = PWAV_HEIGHT_LEVELS,
|
||||||
|
abs_scale: float | None = None) -> bytes:
|
||||||
"""One byte per column = (whiteness << 5) | height — the real PWAV/PWV3
|
"""One byte per column = (whiteness << 5) | height — the real PWAV/PWV3
|
||||||
on-disk format. height = RMS loudness (linear); whiteness = lifted treble
|
on-disk format. whiteness = lifted treble (high-band) peak.
|
||||||
(high-band) peak, so hi-hats/snares read white and bass/tonal reads blue."""
|
|
||||||
|
height: when `abs_scale` is given, an ABSOLUTE loudness map
|
||||||
|
`round(rms * abs_scale)` (quiet → short, loud → tall — used by PWAV); when
|
||||||
|
None, per-track-normalised RMS via `_quantise` (loudest column → full —
|
||||||
|
used by PWV3)."""
|
||||||
if n_cols <= 0 or samples.size == 0:
|
if n_cols <= 0 or samples.size == 0:
|
||||||
return bytes(n_cols)
|
return bytes(n_cols)
|
||||||
height = _quantise(_col_rms(np.abs(samples), n_cols), height_levels)
|
rms = _col_rms(np.abs(samples), n_cols)
|
||||||
|
if abs_scale is not None:
|
||||||
|
height = np.clip(np.round(rms * abs_scale), 0, height_levels).astype(np.uint8)
|
||||||
|
else:
|
||||||
|
height = _quantise(rms, height_levels)
|
||||||
white = _quantise(_env_peaks(hi_env, n_cols), 7, gamma=COLOR_GAMMA) # lifted
|
white = _quantise(_env_peaks(hi_env, n_cols), 7, gamma=COLOR_GAMMA) # lifted
|
||||||
white = np.where(height > 0, white, 0).astype(np.uint8) # silence → no white
|
white = np.where(height > 0, white, 0).astype(np.uint8) # silence → no white
|
||||||
return ((white << 5) | height).astype(np.uint8).tobytes()
|
return ((white << 5) | height).astype(np.uint8).tobytes()
|
||||||
|
|
||||||
|
|
||||||
|
def _packed_preview(samples: np.ndarray, hi_env: np.ndarray,
|
||||||
|
segments: int = PWAV_SEGMENTS) -> bytes:
|
||||||
|
"""PWAV preview — **2 bytes per segment** (NOT one packed byte). This is the
|
||||||
|
format Beat Link / the CDJ actually read:
|
||||||
|
|
||||||
|
even byte: bits 0-4 = height (0-31) ← data[seg*2] & 0x1f
|
||||||
|
odd byte: bits 0-2 = brightness (0-7) ← data[seg*2+1] & 0x07 (>=5 → cyan)
|
||||||
|
|
||||||
|
height = absolute loudness `round(rms*PWAV_HEIGHT_SCALE)` (fit to the
|
||||||
|
reference: full ≈ RMS 0.74). brightness = the same treble (high-band)
|
||||||
|
whiteness PWV3 uses — it matches the reference brightness distribution
|
||||||
|
(~14-38% ≥5) far better than a crest factor (which sat ≥5 on ~all columns).
|
||||||
|
Whiteness is mirrored into the high 3 bits of both bytes, matching the
|
||||||
|
reference layout (even≈odd).
|
||||||
|
"""
|
||||||
|
if segments <= 0 or samples.size == 0:
|
||||||
|
return bytes(segments * 2)
|
||||||
|
rms = _col_rms(np.abs(samples), segments)
|
||||||
|
height = np.clip(np.round(rms * PWAV_HEIGHT_SCALE), 0, 31).astype(np.uint8)
|
||||||
|
# brightness: modest treble level (linear, no lift). The reference's exact
|
||||||
|
# brightness channel doesn't track any computable feature (uncorrelated with
|
||||||
|
# treble/height/crest across reference tracks), so this just gives some
|
||||||
|
# transient/treble sparkle without washing the whole preview cyan.
|
||||||
|
bright = _quantise(_env_peaks(hi_env, segments), 7)
|
||||||
|
bright = np.where(height > 0, bright, 0).astype(np.uint8) # silence → no bright
|
||||||
|
even = ((bright << 5) | height).astype(np.uint8) # &0x1f → height
|
||||||
|
odd = ((bright << 5) | bright).astype(np.uint8) # &0x07 → brightness
|
||||||
|
out = np.empty(segments * 2, dtype=np.uint8)
|
||||||
|
out[0::2] = even
|
||||||
|
out[1::2] = odd
|
||||||
|
return out.tobytes()
|
||||||
|
|
||||||
|
|
||||||
def _packed_tiny(samples: np.ndarray, n_cols: int) -> bytes:
|
def _packed_tiny(samples: np.ndarray, n_cols: int) -> bytes:
|
||||||
"""PWV2: one byte per column, 4-bit height (0-15) in the low nibble."""
|
"""PWV2: one byte per column, 4-bit height (0-15) in the low nibble."""
|
||||||
if n_cols <= 0 or samples.size == 0:
|
if n_cols <= 0 or samples.size == 0:
|
||||||
@@ -193,7 +244,7 @@ def _band_envelopes(samples: np.ndarray, sr: int) -> tuple[np.ndarray, np.ndarra
|
|||||||
return env(*BAND_LOW), env(*BAND_MID), env(*BAND_HIGH)
|
return env(*BAND_LOW), env(*BAND_MID), env(*BAND_HIGH)
|
||||||
|
|
||||||
|
|
||||||
CACHE_VERSION = 3 # mixxx-analyzer beat grid + RMS/lift waveform packing
|
CACHE_VERSION = 9 # PWAV paired (2B/seg); treble-based brightness (not crest)
|
||||||
|
|
||||||
# 3-band waveform split: bass (kick + sub), mid (snare, vocals, chords),
|
# 3-band waveform split: bass (kick + sub), mid (snare, vocals, chords),
|
||||||
# high (hats, cymbals).
|
# high (hats, cymbals).
|
||||||
@@ -313,9 +364,9 @@ def analyze_audio(path: Path) -> dict:
|
|||||||
|
|
||||||
# Mono overview/scroll: one byte/col = (whiteness<<5)|height (PWAV/PWV3),
|
# Mono overview/scroll: one byte/col = (whiteness<<5)|height (PWAV/PWV3),
|
||||||
# 4-bit height for the tiny preview (PWV2).
|
# 4-bit height for the tiny preview (PWV2).
|
||||||
out["preview"] = _packed_mono(samples, hi_env, PWAV_COLS, PWAV_HEIGHT_LEVELS) # PWAV 400B
|
out["preview"] = _packed_preview(samples, hi_env, PWAV_SEGMENTS) # PWAV 200 seg × 2B (paired)
|
||||||
out["tiny"] = _packed_tiny(samples, PWV2_COLS) # PWV2 100B
|
out["tiny"] = _packed_tiny(samples, PWV2_COLS) # PWV2 100B
|
||||||
out["scroll"] = _packed_mono(samples, hi_env, scroll_cols, PWAV_HEIGHT_LEVELS) # PWV3
|
out["scroll"] = _packed_mono(samples, hi_env, scroll_cols, PWAV_HEIGHT_LEVELS) # PWV3 (unchanged)
|
||||||
|
|
||||||
# 3-band colour data: SHARED-normalised preview (energy rolloff) for PWV4/
|
# 3-band colour data: SHARED-normalised preview (energy rolloff) for PWV4/
|
||||||
# PWV6, per-band-normalised scroll (vivid) for PWV5/PWV7.
|
# PWV6, per-band-normalised scroll (vivid) for PWV5/PWV7.
|
||||||
|
|||||||
Reference in new issue
Block a user