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:
- Player-level toggle – If
CONF_VOLUME_NORMALIZATIONis disabled for the player, the mode returnsDISABLED. - Live source bypass – Audio-source streams (
MediaType.AUDIO_SOURCE) skip normalization because the upstream handles loudness. - Target loudness check – If
CONF_VOLUME_NORMALIZATION_TARGETis unset, normalization is disabled. - Preference selection – The core settings
CONF_VOLUME_NORMALIZATION_RADIOorCONF_VOLUME_NORMALIZATION_TRACKSdetermine the preferredVolumeNormalizationMode. - Fallback handling – When loudness measurement is unavailable (
streamdetails.loudness is None), the system falls back toDYNAMICorFIXED_GAINif the preference isFALLBACK_DYNAMICorFALLBACK_FIXED_GAIN.
The VolumeNormalizationMode enum defines six possible behaviors:
DISABLED– No gain appliedMEASUREMENT_ONLY– Uses measured loudness without fallbackDYNAMIC– Continuous loudness adjustmentFIXED_GAIN– Static gain valueFALLBACK_DYNAMIC– Falls back to dynamic processing when measurement is missingFALLBACK_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 playerCONF_VOLUME_NORMALIZATION_TARGET– Target loudness in LUFS (default -17.0)CONF_VOLUME_NORMALIZATION_TRACKS– Mode preference for track playbackCONF_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 switchinput_gain– Pre-processing gain in dBoutput_gain– Post-processing gain in dBfilters– 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:
mass.config.save_dsp_configpersists the configuration inmusic_assistant/controllers/config.py(lines 935-950)on_player_dsp_changeinmusic_assistant/controllers/players/controller.py(lines 2484-2490) detects the change- 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_NORMALIZATIONand uses theget_normalization_modefunction inmusic_assistant/helpers/audio.pyto determine whether to applyloudnormfilters, 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
DSPConfigmodel inmusic_assistant/models/dsp.pyto store filter chains, which are converted to ffmpeg arguments byfilter_to_ffmpeg_paramsinmusic_assistant/helpers/dsp.py. - Changes to DSP settings trigger immediate pipeline rebuilds via
on_player_dsp_changeinmusic_assistant/controllers/players/controller.py. - Both features are accessible programmatically through
mass.configmethods 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →