Auric

The shuffle engine
that mixes your library.

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.

Language Rust
Interface C FFI + Cargo
Analyzed features 5 per track
Keys 24 (major + minor)
Used in Player · TUI

What Drift analyzes.

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.

01

BPM

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.

spectral flux → autocorrelation
02

Musical key

All 24 keys — twelve major, twelve minor. A band-limited chromagram aggregates pitch-class energy across the track, then correlates against Krumhansl-Kessler profiles.

chromagram + Krumhansl-Kessler
03

Energy

RMS measured across all samples, converted to decibels, then normalized to 0–1. A gentle loud/soft measure that survives mastering differences.

RMS dB
04

Brightness

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.

spectral centroid
05

Dynamic range

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.

crest factor, percentile ratio

Running an analysis.

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.

analyze.rs
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

The shuffle algorithm.

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.

01 · 0.60

Harmonic compatibility

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.

02 · ± 12 BPM

BPM continuity

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.

03 · ± 0.35

Energy smoothing

Caps energy jumps at 0.35 on the normalized scale. Keeps the session from lurching from ambient to peak-time.

04 · ± 0.40

Brightness smoothing

Limits spectral-centroid shifts between adjacent tracks, mirroring what a DJ would do by ear.

05 · 8 / 4 / 3

Separation

Same artist can’t come back within 8 tracks by default; album within 4; genre within 3. Linear repulsion ramps, not hard filters.

06 · 48 h

Freshness decay

Recently played tracks decay exponentially over a 48-hour half-life. Skipped tracks take a penalty. Never-played tracks get a discovery boost.

07 · optional

Genre transition

Optional hand-tuned adjacency matrix that smooths style shifts (shoegaze ↔ indie, rock ↔ metal) and dampens abrupt ones (classical ↔ hardcore).

Tuning the mix.

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.

shuffle.rs
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.

The Camelot wheel.

DJs have been using a simplified harmonic notation called the Camelot wheel since the seventies. Drift uses it verbatim for compatibility scoring.

1.00
Perfect match — the same key on both sides of the transition. Always plays well, but can feel static on long stretches.
0.95
Relative major/minor — C major into A minor, or 8B into 8A on the wheel. Smooth, emotionally inflected.
0.90
One step adjacent — move one number clockwise or counterclockwise on the wheel. A perfect fifth apart in classical terms.
0.70 → 0.15
Longer distances — decays with steps around the wheel. Drift still considers them, but the weighted draw makes them rare.

Built once, used twice.

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.

Inside Auric Player

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 →

Inside Auric TUI

Same crate, same scoring. The terminal edition surfaces Drift’s outputs in the library columns and uses Smart shuffle as the default for queues.

See the terminal edition →