Drift is the shuffle engine and audio analyzer inside Auric Player and Auric TUI. It reads your files once, extracts five musical features per track, and then picks what to play next with DJ-style harmonic mixing, tempo continuity, and spectral smoothing. Written in Rust. Runs locally. Never phones home.
Every track in your library gets a one-time analysis pass. Symphonia decodes whatever codec the file ships in; 4096-sample FFT with a Hann window and 75% overlap feeds the feature extractors. Long tracks are capped at 90 seconds. Batch indexing runs in parallel over every CPU you have.
Tempo estimation from 60 to 200 BPM. Onset detection via FFT spectral flux feeds a Pearson autocorrelation over candidate lags; octave errors are detected and corrected.
All 24 keys — twelve major, twelve minor. A band-limited chromagram aggregates pitch-class energy across the track, then correlates against Krumhansl-Kessler profiles.
RMS measured across all samples, converted to decibels, then normalized to 0–1. A gentle loud/soft measure that survives mastering differences.
Spectral centroid per frame, averaged and normalized. Bright tracks (cymbals, vocals, mids) sit high; warm tracks (bass, pad, drum machine) sit low. Drives similarity, not loudness.
Crest factor computed across ~100 segments: take RMS per segment, compare the 10th and 90th percentiles in dB. Separates compressed masters from ones that still breathe.
The DriftAnalyzer type reads an audio file and returns the five
features at once. The Mac player calls this over C FFI during library
indexing; the TUI uses the Rust API directly.
use auric_drift::DriftAnalyzer;
use std::path::Path;
let analyzer = DriftAnalyzer::new();
let features = analyzer.analyze_file(Path::new("songs/ok_computer.flac"))?;
println!(
"{} BPM · key {} · energy {:.2} · brightness {:.2} · DR {:.2}",
features.bpm,
features.key_name(), // e.g. "F#m"
features.energy,
features.brightness,
features.dynamic_range,
);
// 124.5 BPM · key F#m · energy 0.72 · brightness 0.58 · DR 0.31
Picking the next track is a weighted random draw over a scoring function.
Each candidate gets scored across seven signals; the product of those scores
determines how likely the track is to come up next. Everything below is tunable
at runtime through DriftConfig.
Camelot-wheel scoring between the current track’s key and every candidate. Perfect match scores 1.0, relative major/minor 0.95, one-step-adjacent 0.9, and decays with distance.
Absolute delta between the two tempos, and across half/double octaves, so a 128 BPM track cleanly leads to a 64 BPM one. Penalizes deltas over 12 BPM.
Caps energy jumps at 0.35 on the normalized scale. Keeps the session from lurching from ambient to peak-time.
Limits spectral-centroid shifts between adjacent tracks, mirroring what a DJ would do by ear.
Same artist can’t come back within 8 tracks by default; album within 4; genre within 3. Linear repulsion ramps, not hard filters.
Recently played tracks decay exponentially over a 48-hour half-life. Skipped tracks take a penalty. Never-played tracks get a discovery boost.
Optional hand-tuned adjacency matrix that smooths style shifts (shoegaze ↔ indie, rock ↔ metal) and dampens abrupt ones (classical ↔ hardcore).
The defaults target a listenable home-library shuffle. If you want something
closer to a DJ mix, raise the harmonic weight and tighten BPM delta. The
engine also supports Random, Artist, Album,
and Genre modes for when you explicitly want simpler behavior.
use auric_drift::{DriftEngine, DriftConfig, ShuffleMode};
let engine = DriftEngine::new();
let mut config = DriftConfig::default();
config.harmonic_weight = 0.8; // lean harder into Camelot mixing
config.max_bpm_delta = 8.0; // tighter tempo continuity
config.artist_separation = 12; // don't repeat artists within 12 tracks
config.discovery_boost = 0.25; // favor unplayed tracks
let sequence = engine.shuffle(&library, ShuffleMode::Smart, &config);
// sequence[0] is the seed; every subsequent pick scores the remaining
// candidates against the current track and draws from the top window. DJs have been using a simplified harmonic notation called the Camelot wheel since the seventies. Drift uses it verbatim for compatibility scoring.
Drift ships as a Rust crate with both rlib and
staticlib outputs. The Mac app links it via C FFI during
library indexing and shuffle; the TUI depends on it directly as a workspace
crate. Neither variant ships a network dependency.
Drift runs during folder scans and on-demand re-analysis. BPM and key are written back into the audio file’s own tags so the values travel with the music across apps. The fullscreen visualizer pulses at the current track’s BPM in the key’s hue.
See the player →
Same crate, same scoring. The terminal edition surfaces Drift’s
outputs in the library columns and uses Smart shuffle as
the default for queues.