Protip: You can approximate the f(x) = log(x) with g(x) = sqrt(x)-1
Then that pesky logarithm becomes much less annoying, as the square root will hit the 0 axis.
Also, Unity.Mathematics has a nice remap function. And if you want to give your users the opportunity to boost their channels beyond 0 decibels, you can just use the remap’s top value (the 0 next to the -80) from 0 to something like 7 (double perceived loudness) or even all the way to 20. I prefer 3 which is the average headroom I try to leave in my mix, allowing users to fill that if they want.
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Serialization;
using UnityEngine.UI;
[RequireComponent(typeof(Slider))]
public class MixerGroupSlider : MonoBehaviour
{
public AudioMixerGroup group;
[Tooltip("The minimum decibel value of the slider")]
public float minDB = -80;
[Tooltip("The maximum decibel value of the slider")]
public float maxDB = 3;
[Tooltip("Additionally scale the slider along a nonlinear curve to give more precision in the middle to top range.")]
[Range(0.1f, 1f)]
public float linearity = 0.5f;
[SerializeField]
[HideInInspector]
private Slider _slider;
private void Awake()
{
if (!_slider) _slider = GetComponent<Slider>();
}
private void OnEnable()
{
LoadValue();
_slider.onValueChanged.AddListener(OnValueChanged);
}
private void OnDisable()
{
_slider.onValueChanged.RemoveListener(OnValueChanged);
}
private void Start()
{
OnValueChanged(_slider.value);
}
private void LoadValue()
{
group.audioMixer.GetFloat(group.name, out var decibels);
var normalized = math.saturate(math.remap(minDB, maxDB, 0, 1, decibels));
var nonlinear = math.pow(normalized, 1f / linearity);
var exponential = math.pow(10f, nonlinear);
var remapped = math.remap(1, 10, 0, 1, exponential);
_slider.value = remapped;
}
private void OnValueChanged(float sliderValue)
{
var remapped = math.remap(0, 1, 1, 10, sliderValue);
var logarithmic = math.saturate(math.log10(remapped));
var nonlinear = math.pow(logarithmic, linearity);
var decibels = math.remap(0f, 1f, minDB, maxDB, nonlinear);
group.audioMixer.SetFloat(group.name, decibels);
}
private void OnValidate()
{
if (!_slider) _slider = GetComponent<Slider>();
_slider.minValue = 0;
_slider.maxValue = 1;
}
}
With these defaults, a MixerGroup at -7 dB(A), not an uncommon setting for games with rich mixes, will have its slider at around 75% of its range.
To the user, this feels like fine-grained, intuitive control. If you don’t want that, you can set the linearity exponent to 1 in the inspector.
If you set +7 as the maxDB, 0 db sits near the middle of the slider (allowing the user to “up to double” the loudness by sliding it to the max. However, this can lead to some logistics issues with headroom, so plan ahead when you design your game’s mix.
I find +3 max the most user-friendly, and 0 max is the most safe (you 100% know your mix will be, at most, the loudest you ever mixed it to.
Note: Technically this math is fudged, as we’re using the properties of another interval of the logarithm curve to augment our essential polynomial (nonlinear) curve. However, I find this both robust and pleasant for the user.
The green and red curve here are with linearity 0.5 and 1.0, respectively.
The red line with 1.0 is exactly between the “ideal logarithm that crosses below 0 at 10%” and the “offset logarithm that starts at 0%, but ends at 90%”. That would be the “best” log slider you can build that actually uses the whole length of the GUI slider value space.
However, The 0.5 curve has this hump near the mouse cursor that protrudes and drastically improves the auditory response of the slider in that area, and makes it less harshly sensitive near the top (100%) end.