Skip to content

Instantly share code, notes, and snippets.

@thygrrr
Last active April 16, 2024 10:47
Show Gist options
  • Save thygrrr/ed17627cf3d6776aa1f6754a1f79daa0 to your computer and use it in GitHub Desktop.
Save thygrrr/ed17627cf3d6776aa1f6754a1f79daa0 to your computer and use it in GitHub Desktop.
MixerGroupSlider.cs - Unity Implementation of a better-than-logarithmic AudioMixerGroup fader GUI Slider that is more pleasant and intuitive for the users to use, and utilizes the entire value range of its slider. Furthermore, fader values are serialized in PlayerPrefs.
//SPDX-License-Identifier: Unlicense
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.UI;
namespace Tiger.Audio
{
[RequireComponent(typeof(Slider))]
[DisallowMultipleComponent]
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 string PrefsKey => $"MixerGroup/{group.name}";
private void OnEnable()
{
slider.onValueChanged.AddListener(OnValueChanged);
}
private void OnDisable()
{
slider.onValueChanged.RemoveListener(OnValueChanged);
}
private void Start()
{
var value = PlayerPrefs.HasKey(PrefsKey) ? PlayerPrefs.GetFloat(PrefsKey) : ReadInitialValueFromMixer();
slider.value = ValueForDecibels(value);
}
private float ReadInitialValueFromMixer()
{
group.audioMixer.GetFloat(group.name, out var decibels);
return ValueForDecibels(decibels);
}
private float ValueForDecibels(float 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);
var value = math.saturate(remapped);
return value;
}
private float DecibelsForValue(float value)
{
var remapped = math.remap(0, 1, 1, 10, value);
var logarithmic = math.saturate(math.log10(remapped));
var nonlinear = math.pow(logarithmic, linearity);
var decibels = math.remap(0f, 1f, minDB, maxDB, nonlinear);
return decibels;
}
private void OnValueChanged(float sliderValue)
{
var decibels = DecibelsForValue(sliderValue);
group.audioMixer.SetFloat(group.name, decibels);
PlayerPrefs.SetFloat(PrefsKey, decibels);
}
private void OnValidate()
{
if (!slider) slider = GetComponent<Slider>();
slider.minValue = 0;
slider.maxValue = 1;
}
}
}
@thygrrr
Copy link
Author

thygrrr commented Dec 2, 2023

With these defaults (+3 max), 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.

image

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 dashed "ideal logarithm that crosses below 0 at 10%" and the dotted "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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment