Fix float32 precision leak in LTI stability guarantee and stale RoPE test contract - #86
Open
kelseyjmccorkle-stack wants to merge 7 commits into
Open
Conversation
Learn a DJ's mixing personality and apply it to any music library, with a bridge to the OpenMythos RDT for the learned (deep-learning) path. Modules: - harmonic.py Camelot-wheel key parsing + compatibility scoring - profile.py DJStyleProfile (tunable personality) + built-in archetypes - analysis.py Track + library analysis (optional librosa/mutagen; KS key est.) - planner.py MixPlanner -> MixPlan (harmonic + tempo + energy-curve ordering) - render.py render_mix -> beatmatched, crossfaded audio + cue sheet - setlist.py ingest setlists (incl. timestamped tracklists) + learn a profile - enrich.py pluggable BPM/key/energy lookup (CSV/MusicBrainz/GetSongBPM) + cache - mixlang.py serialize mixes to a token string for RDT training - __main__.py CLI: plan / analyze / render / enrich / learn The parent open_mythos/__init__.py is made lazy (PEP 562) so the torch-free DJ engine imports without pulling in the model stack. pyproject gains an [audio] extra. 34 tests (28 torch/audio-free + 6 audio) cover all layers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYYoch4CutCodkPevPXHLq
For DJs who don't publish tracklists, identify tracks from the mix audio. - identify.py: pluggable AudioIdentifier (AudD API), scan_mix slices a mix into windows, recognizes each, and assemble_setlist dedups consecutive hits into an ordered Setlist with timestamp-derived durations -> flows into enrich/learn. - CLI: `identify <audio> --provider audd:TOKEN --out set.json`. - Network call and audio slicing are isolated so assembly is unit-tested offline; 6 new identify tests + a scan_mix test over synthesized audio. The module never downloads audio — the user supplies the file (legally). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYYoch4CutCodkPevPXHLq
Getting an AudD API token is a signup hurdle; ShazamIdentifier needs no key. It uses the unofficial `shazamio` client (Shazam's engine is robust to the tempo/EQ shifts in DJ mixes). CLI: `identify <audio> --provider shazam`. - ShazamIdentifier + parse_shazam_result; async recognize isolated in _recognize so parsing is unit-tested offline (no shazamio dep in tests). - build_identifier accepts 'shazam'; pyproject gains a [shazam] extra. - shazamio is unofficial (ToS gray area) and may break if Shazam changes their protocol — documented in the class. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYYoch4CutCodkPevPXHLq
Add setlist_from_folder: treat an ordered folder of audio files (a split mixtape, one file per track in play order) as a DJ's setlist. Filenames give the clean "Artist - Title" (mixtape tags often store the DJ as artist), while audio analysis fills bpm/key/energy per track. Also fix energy on loud commercial masters: absolute RMS saturates at 1.0, so _analyze_signal now also exposes raw loudness and normalize_energy() min-max scales it across a set, yielding a meaningful relative energy curve. `learn` accepts folders as inputs. Verified on a real 22-track R&B mixtape: the learned profile correctly shows low harmonic strictness (0.19, song-cut mixing) and wide BPM drift (13) characteristic of an open-format R&B DJ. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYYoch4CutCodkPevPXHLq
Recognition (Shazam/AudD) yields only artist+title, leaving bpm/key/energy as placeholders on a scanned mix. Add analyze_setlist_audio: slice the mix at each track's stored offset and analyze it for bpm/key/energy, then normalize energy across the set. - assemble_setlist now records each track's mix offset in meta['offset']. - Factor array-level analysis into analysis.analyze_samples (reused by whole- file analysis and per-segment mix analysis). Verified on the 57-track reggae scan: profile went from placeholder defaults to real values (bpm_range 99-137, drift 2 — tight riddim-locked tempo, a genuine contrast to the hip-hop mixtapes' 6-13 drift). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYYoch4CutCodkPevPXHLq
render_mix previously downmixed every source to mono before any DSP, so every rendered file lost stereo width regardless of source quality. Internal representation is now (channels, n) throughout: loading, resampling, time-stretching, crossfading, and the lowpass-sweep transition all operate on 2D arrays. _to_stereo upmixes mono sources to dual-mono so every segment entering the mix shares a channel count; output is interleaved stereo PCM16 (soundfile preferred, stdlib wave as fallback). Mono sources still work fine end to end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYYoch4CutCodkPevPXHLq
…test contract LTIInjection.get_A() is documented and tested to guarantee ρ(A) < 1 by construction, but after a large gradient step the discretized exponent underflows and exp(-tiny) rounds to exactly 1.0 in float32, silently breaking the invariant. Clamp the result explicitly below 1.0. Also update TestGQAttention/TestMLAttention/TestTransformerBlock/ TestRecurrentBlock to pre-slice freqs_cis to the input's sequence length (and cache offset, for the KV-cache-accumulation tests) before calling submodules directly. 18cca89 moved RoPE position slicing responsibility to the caller (fixing decode tokens stuck at position 0), but these direct-submodule tests were never updated to the new contract and were passing the full unsliced buffer, causing shape mismatches in apply_rope. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
LTIInjection.get_A()is documented/tested to guaranteeρ(A) < 1by construction, but after a large gradient step the discretized exponent underflows andexp(-tiny)rounds to exactly1.0in float32, silently breaking the invariant that the whole stability section of the README is built around. Added an explicit clamp below1.0so the guarantee holds regardless of float precision.18cca89correctly moved RoPE position-slicing responsibility fromapply_ropeup toOpenMythos.forward(fixing decode tokens stuck at position 0), but the direct-submodule unit tests (TestGQAttention,TestMLAttention,TestTransformerBlock,TestRecurrentBlock) were never updated to the new contract — they still passed the full unslicedfreqs_cisbuffer straight intoGQAttention/MLAttention/etc., causing shape-mismatch failures wheneverT < max_seq_len. Updated them to pre-slice to the input's sequence length (and cache offset, for the two KV-cache-accumulation tests).Test plan
pytest tests/— 118 passed, 1 skipped (was 14 failed / 104 passed before this change)LTIInjection.get_A()stays strictly below 1.0 after an aggressive SGD step (lr=1e3) that previously produced exactly1.0🤖 Generated with Claude Code