A Beginner’s Guide to Using Android’s Inbuilt Equalizer + Sliders
What is an Equalizer?
- Bass → The low, rumbling sounds (like drums or deep beats).
- Mid → The middle range, usually where voices and guitars sit.
- Treble → The higher, sharper sounds (like cymbals, strings, or “s” sounds in vocals).
Android’s Inbuilt Equalizer
- Five frequency bands (Bass → Low Mid → Mid → High Mid → Treble).
- Presets like “Rock,” “Jazz,” “Pop,” or “Classical” for quick adjustments.
- A Bass Boost slider for extra low-end power.
- A Virtualizer to add a sense of 3D space.
How to Access It
- Play a song in your favorite music player (Spotify, YouTube Music, local MP3 player).
- Open the Now Playing screen and look for the sound settings icon or the equalizer option in the app’s settings.
- If the app supports it, it will open Android’s default equalizer panel.
- Adjust the sliders or choose a preset to hear instant changes.
Tips for Beginners
Start with Presets
Small Moves, Big Difference
Balance Over Boosting
Tailor to Your Environment
- In a noisy bus? Boost mids and treble for clearer vocals.
- At home with good speakers? Add bass for warmth.
Limitations of the Inbuilt Equalizer + Sliders
- It usually works only with media playback, not phone calls.
- Some apps may bypass the equalizer completely.
- Sound quality improvements are noticeable, but not as detailed as professional EQ apps or hardware.
Why Use It?
🎛️ Equalizer UI with Sliders + Presets
This example shows:
- Five frequency sliders (Bass → Treble).
- Preset buttons styled like modern chips.
- A clean card layout with rounded corners, shadows, and spacing.
@Composable
fun EqualizerScreen() {
val frequencyLabels = listOf("60Hz", "230Hz", "910Hz", "3.6kHz", "14kHz")
val sliderValues = remember { mutableStateListOf(0f, 0f, 0f, 0f, 0f) }
val presets = listOf("Normal", "Rock", "Pop", "Jazz", "Classical")
var selectedPreset by remember { mutableStateOf("Normal") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// Preset Section
Text("Presets", style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(8.dp))
FlowRow(
mainAxisSpacing = 8.dp,
crossAxisSpacing = 8.dp
) {
presets.forEach { preset ->
FilterChip(
selected = preset == selectedPreset,
onClick = { selectedPreset = preset },
label = { Text(preset) }
)
}
}
Spacer(Modifier.height(24.dp))
// Sliders Section
Text("Custom Equalizer", style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(12.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
frequencyLabels.forEachIndexed { index, label ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.weight(1f)
) {
// Vertical Slider
Slider(
value = sliderValues[index],
onValueChange = { sliderValues[index] = it },
valueRange = -10f..10f,
modifier = Modifier
.height(200.dp)
.rotate(-90f) // make vertical
)
Spacer(Modifier.height(8.dp))
Text(label, style = MaterialTheme.typography.labelSmall)
}
}
}
Spacer(Modifier.height(16.dp))
// Reset Button
Button(
onClick = {
sliderValues.indices.forEach { sliderValues[it] = 0f }
selectedPreset = "Normal"
},
modifier = Modifier.align(Alignment.CenterHorizontally),
shape = RoundedCornerShape(50)
) {
Text("Reset")
}
}
}
✨ Features of this UI
- Presets as Chips → quick, modern, and tappable.
- Vertical Sliders → more natural for equalizer controls.
- Rounded card style + shadows (can be wrapped in Card { . }).
- Reset button to bring everything back to normal.
🔹 EqualizerPlayerScreen
@Composable
fun EqualizerPlayerScreen() {
👉 Declares a Composable function. This will show the entire Equalizer screen UI.
val context = LocalContext.current
👉 Gets the current Android Context (needed to access assets, MediaPlayer, etc.).
var mediaPlayer by remember { mutableStateOf(null) }
var equalizer by remember { mutableStateOf(null) }
👉 Two states:
- mediaPlayer → Plays audio
- equalizer → Controls sound bands/presets.
var isPlaying by remember { mutableStateOf(false) }
var presetNames by remember { mutableStateOf(listOf()) }
var selectedPreset by remember { mutableIntStateOf(-1) }
var bandStates by remember { mutableStateOf(listOf>()) }
👉 UI states:- isPlaying → true if music is playing.
- presetNames → list of Equalizer preset names (e.g., "Rock", "Jazz").
- selectedPreset → index of current preset.
- bandStates → list of (centerFrequency, normalizedLevel) for each EQ band.
🎵 Setup when screen opens
LaunchedEffect(Unit) {
👉 Runs once when Composable first appears.
try {
val mp = MediaPlayer()👉 Create a new MediaPlayer.
val afd = context.assets.openFd("sample.mp3")
mp.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
afd.close()
👉 Load sample.mp3 from assets and set it as the MediaPlayer source.
mp.isLooping = true
mp.prepare()
mediaPlayer = mp
👉 Loop song forever, prepare it, and store in mediaPlayer.
val eq = Equalizer(0, mp.audioSessionId)
eq.enabled = true
equalizer = eq
👉 Create Equalizer linked to MediaPlayer’s session.
Enable it and save in state. val names = mutableListOf()
for (i in 0 until eq.numberOfPresets) names += eq.getPresetName(i.toShort())
presetNames = names
if (names.isNotEmpty()) selectedPreset = 0
👉 Get all preset names from Equalizer (Rock, Pop, etc.)
Store in presetNames. val range = eq.bandLevelRange
val min = range[0].toInt()
val max = range[1].toInt()
👉 Each EQ band has min & max level in millibels (mB). val bands = (0 until eq.numberOfBands).map { b ->
val center = eq.getCenterFreq(b.toShort()).toInt()
val level = eq.getBandLevel(b.toShort()).toInt()
val normalized = (level - min).toFloat() / (max - min).toFloat()
center to normalized
}
bandStates = bands
👉 For each EQ band: - Get center frequency (e.g., 60 Hz, 1kHz).
- Get current level.
- Normalize between 0–1 for slider UI.
- Store (frequency, normalizedLevel) in bandStates.
} catch (e: Exception) {
Log.e("EQ", "Init failed: ${e.message}")
}
}
👉 Catch any errors during setup (like file not found).🔹 Release resources
DisposableEffect(Unit) {
onDispose {
equalizer?.release()
mediaPlayer?.release()
}
}
👉 When Composable leaves screen → release MediaPlayer & Equalizer to free memory.
UI Layout
Column(
Modifier
.fillMaxSize()
.padding(20.dp)
.verticalScroll(rememberScrollState())
) {
👉 Whole screen is a scrollable Column with padding.Header
Text(
"🎵 Equalizer Player",
fontSize = 26.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
👉 Title at top.Player Controls
Card(
Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
shape = RoundedCornerShape(24.dp),
elevation = CardDefaults.cardElevation(8.dp),
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceVariant)
) {
👉 Music control section inside a card with rounded corners. IconButton(
onClick = {
mediaPlayer?.let {
if (isPlaying) {
it.pause(); isPlaying = false
} else {
it.start(); isPlaying = true
}
}
},
👉 Play/Pause button toggles MediaPlayer. Icon(painter = painterResource(if (isPlaying)R.drawable.pause else R.drawable.play),
contentDescription = "Play/Pause", tint = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.size(50.dp))
👉 Shows Pause icon if playing, else Play icon.Presets
LazyColumn {
items(presetNames.chunked(3)) { rowItems ->
👉 List of presets, shown in rows of 3 chips. AssistChip(
onClick = {
equalizer?.usePreset(index.toShort())
selectedPreset = index
👉 When chip clicked → Apply preset to Equalizer. bandStates = (0 until eq.numberOfBands).map { b -> ... }
👉 Refresh band sliders to show preset’s band levels.Bands
Row(
Modifier
.fillMaxWidth()
.height(220.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.Bottom
) {
👉 Show bands in a Row (side-by-side vertical sliders).
EqualizerBar(
value = normalized,
onValueChange = { newValue ->
bandStates = bandStates.toMutableList().also { list ->
list[index] = center to newValue
}
equalizer?.let { eq ->
val range = eq.bandLevelRange
val min = range[0].toInt()
val max = range[1].toInt()
val level = (min + (max - min) * newValue).toInt().toShort()
eq.setBandLevel(index.toShort(), level)
}
},
👉 Custom slider EqualizerBar: Text(
"${center / 1000} Hz",
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
👉 Show frequency label (e.g., "60 Hz").🔹 EqualizerBar (Custom Vertical Slider)
@Composable
fun EqualizerBar(
value: Float, // 0f → min, 1f → max
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.primary
) {
👉 A custom bar representing one EQ band.
var dragPosition by remember { mutableStateOf(value) }
👉 Current slider value, stored in state.
Box(
modifier
.pointerInput(Unit) {
detectVerticalDragGestures { change, dragAmount ->
change.consume()
dragPosition = (dragPosition - dragAmount / size.height)
.coerceIn(0f, 1f) // clamp 0–1
onValueChange(dragPosition)
}
}
) {
👉 Detect vertical drag. - When dragged → adjust dragPosition.
- Clamp between 0f and 1f.
- Send updated value back to parent.
Canvas(modifier = Modifier.fillMaxSize()) {
// background track
drawRoundRect(
color = Color.Gray.copy(alpha = 0.3f),
cornerRadius = CornerRadius(12f, 12f)
)
👉 Draw background rectangle (slider track).
val filledHeight = size.height * dragPosition
drawRoundRect(
color = color,
topLeft = Offset(0f, size.height - filledHeight),
size = Size(size.width, filledHeight),
cornerRadius = CornerRadius(12f, 12f)
)
}
}
}
👉 Draw filled rectangle (active level).
It grows upwards as dragPosition increases.
@Composable
fun EqualizerPlayerScreen() {
val context = LocalContext.current
var mediaPlayer by remember { mutableStateOf(null) }
var equalizer by remember { mutableStateOf(null) }
var isPlaying by remember { mutableStateOf(false) }
var presetNames by remember { mutableStateOf(listOf()) }
var selectedPreset by remember { mutableIntStateOf(-1) }
var bandStates by remember { mutableStateOf(listOf>()) } // (centerFreq, normalized)
// setup
LaunchedEffect(Unit) {
try {
val mp = MediaPlayer()
val afd = context.assets.openFd("sample.mp3")
mp.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
afd.close()
mp.isLooping = true
mp.prepare()
mediaPlayer = mp
val eq = Equalizer(0, mp.audioSessionId)
eq.enabled = true
equalizer = eq
// presets
val names = mutableListOf()
for (i in 0 until eq.numberOfPresets) names += eq.getPresetName(i.toShort())
presetNames = names
if (names.isNotEmpty()) selectedPreset = 0
// bands
val range = eq.bandLevelRange
val min = range[0].toInt()
val max = range[1].toInt()
val bands = (0 until eq.numberOfBands).map { b ->
val center = eq.getCenterFreq(b.toShort()).toInt()
val level = eq.getBandLevel(b.toShort()).toInt()
val normalized = (level - min).toFloat() / (max - min).toFloat()
center to normalized
}
bandStates = bands
} catch (e: Exception) {
Log.e("EQ", "Init failed: ${e.message}")
}
}
DisposableEffect(Unit) {
onDispose {
equalizer?.release()
mediaPlayer?.release()
}
}
Column(
Modifier
.fillMaxSize()
.padding(20.dp)
.systemBarsPadding()
.verticalScroll(rememberScrollState())
) {
// Header
Text(
"🎵 Equalizer Player",
fontSize = 26.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Spacer(Modifier.height(12.dp))
// Player Controls
Card(
Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
shape = RoundedCornerShape(24.dp),
elevation = CardDefaults.cardElevation(8.dp),
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceVariant)
) {
Row(
Modifier.fillMaxWidth().padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(32.dp)
) {
IconButton(
onClick = {
mediaPlayer?.let {
if (isPlaying) {
it.pause(); isPlaying = false
} else {
it.start(); isPlaying = true
}
}
},
modifier = Modifier
.size(60.dp)
.background(
MaterialTheme.colorScheme.primaryContainer,
CircleShape
)
) {
Icon(
painter = painterResource(if (isPlaying) R.drawable.pause else R.drawable.play),
contentDescription = "Play/Pause",
tint = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.size(50.dp),
)
}
Text(
if (isPlaying) "Now Playing..." else "Paused",
fontSize = 16.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
// Presets
Text("🎚 Presets", fontSize = 18.sp, fontWeight = FontWeight.Medium)
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 90.dp),
modifier = Modifier
.fillMaxWidth()
.height(280.dp)
) {
items(presetNames.size) { i ->
TextButton(
onClick = {
equalizer?.usePreset(i.toShort())
selectedPreset = i
equalizer?.let { eq ->
val range = eq.bandLevelRange
val min = range[0].toInt()
val max = range[1].toInt()
bandStates = (0 until eq.numberOfBands).map { b ->
val center = eq.getCenterFreq(b.toShort()).toInt()
val level = eq.getBandLevel(b.toShort()).toInt()
val normalized =
(level - min).toFloat() / (max - min).toFloat()
center to normalized
}
}
},
modifier = Modifier
.padding(8.dp)
.clip(RoundedCornerShape(12.dp))
.background(
if (i == selectedPreset)
MaterialTheme.colorScheme.primaryContainer
else
MaterialTheme.colorScheme.surfaceVariant
)
) {
Text(
presetNames[i],
fontSize = 14.sp,
color = if (i == selectedPreset)
MaterialTheme.colorScheme.onPrimaryContainer
else
MaterialTheme.colorScheme.onSurface
)
}
}
}
// Bands
Text("🎼 Bands", fontSize = 18.sp, fontWeight = FontWeight.Medium)
Spacer(Modifier.height(8.dp))
Row(
Modifier
.fillMaxWidth()
.height(200.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.Bottom
) {
bandStates.forEachIndexed { index, (center, normalized) ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Bottom
) {
Text(
"${center / 1000} Hz",
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
EqualizerBar(
value = normalized,
onValueChange = { newValue ->
bandStates = bandStates.toMutableList().also { list ->
list[index] = center to newValue
}
equalizer?.let { eq ->
val range = eq.bandLevelRange
val min = range[0].toInt()
val max = range[1].toInt()
val level = (min + (max - min) * newValue).toInt().toShort()
eq.setBandLevel(index.toShort(), level)
}
},
modifier = Modifier
.width(20.dp)
.fillMaxHeight()
)
}
}
}
}
}
@Composable
fun EqualizerBar(
value: Float, // 0f → min, 1f → max
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.primary
) {
var targetValue by remember { mutableFloatStateOf(value) }
// Whenever parent updates (preset change), update target
LaunchedEffect(value) {
targetValue = value
}
// Always animate towards targetValue (drag OR preset)
val animatedValue by animateFloatAsState(
targetValue = targetValue,
animationSpec = tween(durationMillis = 200, easing = LinearEasing) // fast & smooth for drag
)
Box(
modifier
.pointerInput(Unit) {
detectVerticalDragGestures { change, dragAmount ->
change.consume()
val newValue = (targetValue - dragAmount / size.height)
.coerceIn(0f, 1f)
targetValue = newValue
onValueChange(newValue)
}
}
) {
Canvas(modifier = Modifier.fillMaxSize()) {
// background track
drawRoundRect(
color = Color.Gray.copy(alpha = 0.3f),
cornerRadius = CornerRadius(12f, 12f)
)
// active level
val filledHeight = size.height * animatedValue
drawRoundRect(
color = color,
topLeft = Offset(0f, size.height - filledHeight),
size = Size(size.width, filledHeight),
cornerRadius = CornerRadius(12f, 12f)
)
}
}
}


