How to Configure Volume Normalization and DSP Settings in Music Assistant

Music Assistant applies per-player volume normalization and DSP effects through configuration entries stored in mass.config, using the VolumeNormalizationMode enum to control loudness and the DSPConfig model to manage filter chains that are translated into ffmpeg arguments.

Music Assistant is an open-source music server that provides advanced audio processing capabilities including automatic loudness normalization and customizable digital signal processing (DSP). These features are controlled through specific configuration keys and data models defined in the server codebase at music-assistant/server. This guide explains how to configure volume normalization and DSP settings using the internal Python API and configuration controllers.

Understanding Volume Normalization Architecture

Volume normalization in Music Assistant operates on a per-player basis through a decision tree implemented in the audio pipeline.

The Normalization Mode Decision Tree

The system determines how to apply gain using the get_normalization_mode function in music_assistant/helpers/audio.py (lines 92-108). This function evaluates the following criteria in order:

  1. Player-level toggle – If CONF_VOLUME_NORMALIZATION is disabled for the player, the mode returns DISABLED.
  2. Live source bypass – Audio-source streams (MediaType.AUDIO_SOURCE) skip normalization because the upstream handles loudness.
  3. Target loudness check – If CONF_VOLUME_NORMALIZATION_TARGET is unset, normalization is disabled.
  4. Preference selection – The core settings CONF_VOLUME_NORMALIZATION_RADIO or CONF_VOLUME_NORMALIZATION_TRACKS determine the preferred VolumeNormalizationMode.
  5. Fallback handling – When loudness measurement is unavailable (streamdetails.loudness is None), the system falls back to DYNAMIC or FIXED_GAIN if the preference is FALLBACK_DYNAMIC or FALLBACK_FIXED_GAIN.

The VolumeNormalizationMode enum defines six possible behaviors:

  • DISABLED – No gain applied
  • MEASUREMENT_ONLY – Uses measured loudness without fallback
  • DYNAMIC – Continuous loudness adjustment
  • FIXED_GAIN – Static gain value
  • FALLBACK_DYNAMIC – Falls back to dynamic processing when measurement is missing
  • FALLBACK_FIXED_GAIN – Falls back to fixed gain when measurement is missing

Core Configuration Keys

Normalization settings are stored in mass.config using constants defined in music_assistant/constants.py:

  • CONF_VOLUME_NORMALIZATION – Boolean toggle per player
  • CONF_VOLUME_NORMALIZATION_TARGET – Target loudness in LUFS (default -17.0)
  • CONF_VOLUME_NORMALIZATION_TRACKS – Mode preference for track playback
  • CONF_VOLUME_NORMALIZATION_RADIO – Mode preference for radio streams

The measured loudness data flows through StreamDetails objects (music_assistant_models/streamdetails.py), which carry volume_normalization_mode, target_loudness, and loudness values for each playback session.

Understanding DSP Architecture

Digital Signal Processing in Music Assistant uses a filter-chain model that converts human-readable configuration into ffmpeg filtergraph arguments.

The DSPConfig Model

The DSPConfig class in music_assistant/models/dsp.py stores the complete processing chain as a JSON-serializable model containing:

  • enabled – Boolean master switch
  • input_gain – Pre-processing gain in dB
  • output_gain – Post-processing gain in dB
  • filters – Ordered list of filter objects (ParametricEQFilter, ToneControlFilter)

Each filter is translated to ffmpeg arguments by filter_to_ffmpeg_params in music_assistant/helpers/dsp.py (lines 17-53).

Runtime Integration

When you modify DSP settings, the following chain executes:

  1. mass.config.save_dsp_config persists the configuration in music_assistant/controllers/config.py (lines 935-950)
  2. on_player_dsp_change in music_assistant/controllers/players/controller.py (lines 2484-2490) detects the change
  3. The player restarts its ffmpeg pipeline with the new filtergraph

DSP configurations are accessed via HTTP API endpoints: config/dsp/get, config/dsp/save, and config/dsp_presets/*.

Configuration Examples

Enabling Volume Normalization

Enable normalization for a specific player and set the target loudness:

from music_assistant.constants import (
    CONF_VOLUME_NORMALIZATION,
    CONF_VOLUME_NORMALIZATION_TARGET,
    CONF_VOLUME_NORMALIZATION_TRACKS
)
from music_assistant_models.enums import VolumeNormalizationMode

# Enable per-player normalization

await mass.config.set_player_config_value(
    player_id="sonos_kitchen",
    key=CONF_VOLUME_NORMALIZATION,
    value=True
)

# Set global target loudness (-17 LUFS is default)

await mass.config.set_core_config_value(
    CONF_VOLUME_NORMALIZATION_TARGET,
    -16.0
)

# Configure fallback behavior for tracks

await mass.config.set_core_config_value(
    CONF_VOLUME_NORMALIZATION_TRACKS,
    VolumeNormalizationMode.FALLBACK_DYNAMIC.value
)

Configuring a Parametric EQ

Create and apply a custom EQ filter to a player:

from music_assistant.models.dsp import DSPConfig, ParametricEQFilter, EQBand

# Retrieve existing config

dsp_cfg = await mass.config.get_player_dsp_config("chromecast_livingroom")

# Create 3-band parametric EQ

eq_filter = ParametricEQFilter(
    enabled=True,
    preamp=0,
    bands=[
        EQBand(frequency=100, gain=4.0, q=1.0),   # Boost bass

        EQBand(frequency=1000, gain=-2.0, q=1.0), # Cut mids

        EQBand(frequency=8000, gain=3.0, q=1.0),  # Boost treble

    ]
)

# Update configuration

dsp_cfg.enabled = True
dsp_cfg.input_gain = 0.0
dsp_cfg.output_gain = -2.0
dsp_cfg.filters.append(eq_filter)

# Persist changes - triggers pipeline restart

await mass.config.save_dsp_config("chromecast_livingroom", dsp_cfg)

Setting Radio-Specific Normalization

Apply different normalization strategies for radio versus track playback:


# Strict measurement-only for radio streams

await mass.config.set_core_config_value(
    CONF_VOLUME_NORMALIZATION_RADIO,
    VolumeNormalizationMode.MEASUREMENT_ONLY.value
)

# Dynamic fallback for tracks (adjusts in real-time)

await mass.config.set_core_config_value(
    CONF_VOLUME_NORMALIZATION_TRACKS,
    VolumeNormalizationMode.FALLBACK_DYNAMIC.value
)

Summary

  • Volume normalization is controlled per-player via CONF_VOLUME_NORMALIZATION and uses the get_normalization_mode function in music_assistant/helpers/audio.py to determine whether to apply loudnorm filters, static gain, or no processing.
  • The normalization decision tree considers player toggles, media type, target LUFS settings (CONF_VOLUME_NORMALIZATION_TARGET), and fallback preferences for tracks versus radio.
  • DSP configuration uses the DSPConfig model in music_assistant/models/dsp.py to store filter chains, which are converted to ffmpeg arguments by filter_to_ffmpeg_params in music_assistant/helpers/dsp.py.
  • Changes to DSP settings trigger immediate pipeline rebuilds via on_player_dsp_change in music_assistant/controllers/players/controller.py.
  • Both features are accessible programmatically through mass.config methods and via HTTP API endpoints for external integrations.

Frequently Asked Questions

What is the default target loudness for normalization?

The default target loudness is -17 LUFS, defined in the core configuration under CONF_VOLUME_NORMALIZATION_TARGET. You can adjust this value through mass.config.set_core_config_value(), but values closer to 0 dB increase the risk of clipping.

Can I use DSP on grouped players?

Multi-device DSP is not supported for grouped players. When players are grouped, the system disables individual DSP processing to maintain synchronization across the group. The is_grouping_preventing_dsp helper in the audio module returns True for grouped configurations, causing the pipeline to skip DSP processing.

What happens when I change DSP settings during playback?

When you call save_dsp_config(), the on_player_dsp_change handler in music_assistant/controllers/players/controller.py immediately triggers a pipeline restart. For single players, this rebuilds the ffmpeg filtergraph. For groups, it rebuilds the per-member DSP chain if applicable, ensuring new EQ or gain settings take effect without requiring a manual restart.

How does fallback dynamic mode work?

Fallback dynamic mode (FALLBACK_DYNAMIC) uses the DYNAMIC normalization algorithm when loudness metadata is unavailable for a track. Normally, if a stream lacks loudness measurement (streamdetails.loudness is None), normalization would be disabled. With fallback modes enabled, the system degrades gracefully to real-time loudness analysis or fixed gain rather than disabling normalization entirely.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →