Skip to content

Audio Engine

Nokkvi plays through PipeWire on Linux as a native audio stream — low latency and integrated with your system mixer. It decodes all common formats (MP3, FLAC, Opus, AAC, and more — full list in Under the Hood). This page covers the user-facing audio features: gapless playback and crossfade, the 10-band equalizer, volume normalization, and bit-perfect output.

Nokkvi has two ways to transition between tracks:

  • Gapless — songs flow into each other with no silence in between, exactly as the artist mastered them. Perfect for live albums, concept albums, or any continuous mix.
  • Crossfade — the outgoing track fades out while the next fades in, blending the two for a smoother handoff. Good for shuffled libraries or background listening.

Crossfade is enabled by default at 7 seconds. There’s no separate “enable gapless” toggle — turn off crossfade (with the F hotkey or crossfade_enabled) and gapless takes over automatically. Either way, nokkvi pre-decodes the next track in the queue so the handoff is seamless.

These settings live in Settings → Playback — the crossfade options under a Transitions heading, and the softer edges below them under Fading.

  • Toggle: crossfade_enabled, or hit F.
  • Duration: crossfade_duration_secs — 1 to 12 seconds. Applies as tracks change on their own, not when you skip.
  • Curve (crossfade_curve) — the shape of the volume sweep across the blend:
    • equal_power (default) — holds perceived loudness steady through the middle of the blend. The right pick for different songs, which is the normal track-boundary case.
    • constant_gain — dips about 3 dB in the middle, a softer center some prefer for same-album material.
    • linear — a plain straight-line fade with harder ends.
  • Minimum track length (crossfade_min_track_secs) — tracks shorter than this play gapless instead of crossfading (0–60 s, default 10). 0 blends everything including short interludes; 30 keeps segues sharp and only blends full-length songs.
  • Keep gapless albums seamless (crossfade_album_gapless) — off by default. When on, consecutive tracks from the same album play gapless so authored segues stay tight; crossfade still applies between different albums, on shuffle, and on compilations.

A set of softer edges for everything around the blend — track starts, pause and stop, radio switches, and manual skips. Everything here is off by default except Smooth Track Starts, so out of the box nokkvi changes only one thing: track starts lose their click.

  • Smooth Track Starts (smooth_track_starts, on) — ramps up the first ~20 ms of each track to kill the click when a skip or seek lands mid-waveform. Turn it off for an instant, honest onset.
  • Fade on Pause / Resume (fade_on_pause) and Fade on Stop (fade_on_stop) — soft gain ramps instead of instant cuts, sized by fade_pause_ms / fade_stop_ms (20–500 ms, default 100). Track changes aren’t touched — blending those is the crossfade’s job.
  • Fade Radio Switches (fade_radio_transitions) — a short fade (about a quarter-second each way) when starting a radio station or returning to the queue. The fade-in waits for the stream’s first real audio instead of popping after the prebuffer.
  • Fade on Skip (fade_on_skip) — what a manual Next/Previous does to the sound, over fade_skip_secs (1–4 s, default 2):
    • off (default) — instant cut, the classic behavior.
    • boundary_fade — eases the outgoing track out, then starts the next one fresh.
    • crossfade — overlaps and blends into the skipped-to track like an automatic track change, falling back to a boundary fade (or a plain cut) when a blend is blocked — a bit-perfect format mismatch, a track under the minimum length, or nothing audibly playing.
  • Skip Silence Between Tracks (skip_silence) — trims near-silent lead-ins from tracks prepared in advance and starts the blend early over a silent outro.
  • Gap / Overlap Trim (crossfade_offset_secs, −2 to +2 s) — negative starts the blend early, folding the outgoing tail in; positive holds that many seconds of silence between tracks on gapless joins. A live crossfade overrides the gap.
  • Snap Crossfade to Musical Bars (crossfade_bar_snap) — rounds the blend length to whole bars of the outgoing track’s BPM tag so beats line up through the fade. Needs a BPM tag; ignored when a track has none.

Boost or cut ten frequency ranges to your taste — handy for compensating for headphones, room acoustics, or just dialing in more bass. Per-band gains are stored in eq_gains, and you can save your favorite EQ curves as presets that persist in config.toml.

  • Toggle: eq_enabled, or hit Q.
  • Bands: 31Hz, 62Hz, 125Hz, 250Hz, 500Hz, 1kHz, 2kHz, 4kHz, 8kHz, 16kHz.
  • Presets: custom_eq_presets — save and reload custom EQ curves.

Different tracks can be mastered at wildly different loudness levels. Volume normalization evens them out so a quiet acoustic track doesn’t get drowned out after a wall-of-sound rock track — or vice versa.

Nokkvi has four modes via volume_normalization_mode:

  • off — no leveling. Tracks play at their recorded loudness.
  • replay_gain_track — uses each track’s pre-computed loudness tag. Every song lands at the same target level.
  • replay_gain_album — uses each album’s loudness tag. Different albums get levelled against each other, but the loudness dynamics the artist intended within an album are preserved.
  • agc — real-time automatic gain control. Adjusts on the fly as you listen, no metadata required.

ReplayGain is a tagging standard that stores each track’s loudness in the file itself. Nokkvi reads the tag and applies a fixed gain so every track plays at the same target level.

Tag your library with rsgain or loudgain (both use modern EBU R128 loudness measurement) and Navidrome will read them during scan and serve them to nokkvi via the Subsonic API. Older replaygain_* tag pairs work too — Navidrome converts everything to a single dB value before the API call.

Track vs. album mode:

  • Choose replay_gain_track for shuffled listening — every song lands at the same level.
  • Choose replay_gain_album for whole-album listening — albums get levelled against each other, but the loudness contrast within each album stays intact.

Fine-tuning with four knobs:

  • replay_gain_preamp_db — extra dB on top of the tag value. Default 0 matches ReplayGain’s reference level (relatively quiet by modern standards). +6 is typical for listeners who find reference output too quiet.
  • replay_gain_fallback_db — applied to tracks with no ReplayGain tag. Default 0 (no change).
  • replay_gain_fallback_to_agc — when true, untagged tracks engage real-time AGC instead of the fallback dB. Useful for libraries with mixed tagging coverage.
  • replay_gain_prevent_clipping — keeps the gain from pushing loud peaks into distortion. On by default.

Smart cross-fallback is always on: in track mode, a track with only album_gain falls back to that, and vice versa. A track with neither tag uses the fallback dB or AGC fallthrough.

Pick agc for libraries without ReplayGain tags. Instead of reading a pre-computed value, nokkvi measures loudness as the track plays and pushes it toward a target level.

The normalization_level setting controls how aggressively:

  • quiet — softer overall, maximum headroom for peaks.
  • normal (default) — keeps each track close to its natural perceived level.
  • loud — boosts quiet tracks more aggressively.

If you’re using both crossfade and AGC mode, you may hear a brief loudness mismatch right at track boundaries when two adjacent tracks differ a lot. Lowering normalization_level from normal to quiet softens it. The two ReplayGain modes don’t have this issue — both sides of a crossfade are already at target level, so the transition is clean. If your library is tagged, ReplayGain is the cleanest pairing with crossfade.

Bit-perfect output sends each track to your DAC exactly as it was decoded — no equalizer, no software volume, no limiter, no ReplayGain. The output device follows each track’s native sample rate, so a 96 kHz hi-res file plays back at 96 kHz instead of being resampled to nokkvi’s usual 48 kHz. It’s off by default; reach for Strict or Relaxed (below) if you have a wired DAC and want the signal untouched.

It’s a Linux/PipeWire feature: volume moves onto the PipeWire node, so it only engages on nokkvi’s native PipeWire output — the cpal fallback stays on the normal path. The full-precision decode that makes it lossless applies to all playback anyway; bit-perfect just stops anything from touching the samples afterward.

There are three modes. The player-bar mode button, the kebab (⋮) menu, and the B hotkey each cycle through them in order; you can also set bit_perfect directly:

  • Off — the standard path. EQ, software volume, and crossfade all apply.
  • Strict — untouched samples to the DAC, hard-cut between every track. Nothing ever touches the audio.
  • Relaxed — the same untouched bodies, but it crossfades between adjacent tracks that share a sample rate and channel count, using your crossfade_duration_secs. Only that few-second blend isn’t bit-perfect; a cross-rate change still hard-cuts. A good middle ground if Strict’s hard cuts feel too abrupt.

Strict and Relaxed are mutually exclusive with the standalone Crossfade mode — switching to either turns Crossfade off (Relaxed brings its own same-rate crossfade instead).

PipeWire only re-clocks a device on a fresh open. Nokkvi tears its sink fully down and reopens it on every rate change, so a track plays at its native rate in either direction — a step up to 96 kHz and a step down to 44.1 kHz both re-clock the device. The exception is when something holds the card open across that gap: a hard-cut crossfade that spans the rate change, or another app streaming to the same DAC. Then the device stays at its current rate and PipeWire high-quality resamples to it. For native switching to work at all, the rates you want have to be in PipeWire’s default.clock.allowed-rates (a system-level setting, outside nokkvi); otherwise the device stays put and PipeWire resamples.

Bit-perfect mode reads your sound card’s real clock — from /proc/asound, not the rate nokkvi requested — and shows an honest status in the now-playing strip: BIT-PERFECT when the device is clocked at the track’s rate, RESAMPLED when something forced a different one, or UNVERIFIED when the clock can’t be read (Bluetooth, which re-encodes, or an idle device). During a Relaxed crossfade the badge drops while the blend plays — that overlap isn’t bit-perfect — and returns once the next track settles. See Track Info Display for the full legend.

For the technically curious:

Decoding goes through Symphonia. Supported codecs: MP3, FLAC, Ogg Vorbis, WAV, AAC, MP4/ISO-BMFF, ALAC, AIFF — plus Opus via the symphonia-adapter-libopus shim.

Mixing and output use rodio. On Linux, audio is delivered to PipeWire as a native stream — minimal latency, integrated with the system mixer, and routable to any output via tools like pavucontrol.

Internal format is stereo 48 kHz; other sample rates get resampled — unless bit-perfect mode is on, which follows each track’s native rate instead. A dynamic_content peak limiter (LimitSettings::dynamic_content()) sits at the end of every chain to catch clipping from EQ boosts, ReplayGain pre-amp, or AGC overshoot before mixing; bit-perfect streams skip it along with the rest of the DSP.

ReplayGain implementation: values come from the Subsonic API as a single resolved dB value per track (Navidrome reads the file tags during scan and converts them; nokkvi never parses tags itself) and are applied as a static amplify() factor to each stream. Because the gain is computed offline and constant per track, both sides of a crossfade are already at target — no convergence artifacts.

AGC implementation: uses rodio’s automatic_gain_control with a linear amplitude target (target_level = 0.6 / 1.0 / 1.4 for quiet / normal / loud), not LUFS.