playback: add dynamic replaygain mode [#7]
Add a "Dynamic" replaygain mode inspired by the FooBar2000 plugin. This will automatically determine whether the playback is in an album or not and use the album gain or track gain accordingly.
This commit is contained in:
parent
5359c819bd
commit
ddf2757cb6
7 changed files with 85 additions and 28 deletions
|
@ -63,9 +63,9 @@ I primarily built Auxio for myself, but you can use it too, I guess.
|
|||
|
||||
## Building
|
||||
|
||||
Auxio relies on a local version of ExoPlayer that enables some extra features. So, the build process is as follows:
|
||||
Auxio relies on a custom version of ExoPlayer that enables some extra features. So, the build process is as follows:
|
||||
|
||||
1. Change into the project directory
|
||||
1. Enter into the project directory
|
||||
2. Run `python3 prebuild.py`, which installs ExoPlayer and it's extensions.
|
||||
3. Build the project normally in Android Studio.
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import androidx.media.AudioManagerCompat
|
|||
import com.google.android.exoplayer2.metadata.Metadata
|
||||
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
|
||||
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||
|
@ -88,30 +89,54 @@ class AudioReactor(
|
|||
* This is based off Vanilla Music's implementation.
|
||||
*/
|
||||
fun applyReplayGain(metadata: Metadata?) {
|
||||
if (settingsManager.replayGainMode == ReplayGainMode.OFF || metadata == null) {
|
||||
logD("ReplayGain is disabled or cannot be determined for this track, resetting volume.")
|
||||
if (metadata == null) {
|
||||
logD("No metadata.")
|
||||
volume = 1f
|
||||
return
|
||||
}
|
||||
|
||||
// ReplayGain is configurable, so determine what to do based off of the mode.
|
||||
val useAlbumGain: (Gain) -> Boolean = when (settingsManager.replayGainMode) {
|
||||
ReplayGainMode.OFF -> {
|
||||
logD("ReplayGain is off.")
|
||||
volume = 1f
|
||||
return
|
||||
}
|
||||
|
||||
// User wants track gain to be preferred
|
||||
ReplayGainMode.TRACK ->
|
||||
{ gain ->
|
||||
gain.track == 0f
|
||||
}
|
||||
|
||||
ReplayGainMode.ALBUM ->
|
||||
{ gain ->
|
||||
gain.album != 0f
|
||||
}
|
||||
|
||||
ReplayGainMode.DYNAMIC ->
|
||||
{ _ ->
|
||||
playbackManager.parent is Album &&
|
||||
playbackManager.song?.album == playbackManager.parent
|
||||
}
|
||||
}
|
||||
val gain = parseReplayGain(metadata)
|
||||
|
||||
// Currently we consider both the album and the track gain.
|
||||
var adjust = 0f
|
||||
|
||||
if (gain != null) {
|
||||
// Allow the user to configure a preferred mode for ReplayGain.
|
||||
adjust = if (settingsManager.replayGainMode == ReplayGainMode.TRACK) {
|
||||
if (gain.track != 0f) gain.track else gain.album
|
||||
val adjust = if (gain != null) {
|
||||
if (useAlbumGain(gain)) {
|
||||
logD("Using album gain.")
|
||||
gain.album
|
||||
} else {
|
||||
if (gain.album != 0f) gain.album else gain.track
|
||||
logD("Using track gain.")
|
||||
gain.track
|
||||
}
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
|
||||
// Final adjustment along the volume curve.
|
||||
// Ensure this is clamped to 0 or 1 so that it can be used as a volume.
|
||||
volume = MathUtils.clamp((10f.pow((adjust / 20f))), 0f, 1f)
|
||||
logD("Applied ReplayGain adjustment: $volume")
|
||||
}
|
||||
|
||||
private fun parseReplayGain(metadata: Metadata): Gain? {
|
||||
|
@ -126,14 +151,25 @@ class AudioReactor(
|
|||
for (i in 0 until metadata.length()) {
|
||||
val entry = metadata.get(i)
|
||||
|
||||
// Sometimes the ReplayGain keys will be lowercase, so make them uppercase.
|
||||
if (entry is TextInformationFrame && entry.description?.uppercase() in REPLAY_GAIN_TAGS) {
|
||||
tags.add(GainTag(entry.description!!.uppercase(), parseReplayGainFloat(entry.value)))
|
||||
continue
|
||||
val key: String?
|
||||
val value: String
|
||||
|
||||
when (entry) {
|
||||
is TextInformationFrame -> {
|
||||
key = entry.description?.uppercase()
|
||||
value = entry.value
|
||||
}
|
||||
|
||||
is VorbisComment -> {
|
||||
key = entry.key
|
||||
value = entry.value
|
||||
}
|
||||
|
||||
else -> continue
|
||||
}
|
||||
|
||||
if (entry is VorbisComment && entry.key.uppercase() in REPLAY_GAIN_TAGS) {
|
||||
tags.add(GainTag(entry.key.uppercase(), parseReplayGainFloat(entry.value)))
|
||||
if (key in REPLAY_GAIN_TAGS) {
|
||||
tags.add(GainTag(key!!, parseReplayGainFloat(value)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,15 @@ package org.oxycblt.auxio.playback.system
|
|||
enum class ReplayGainMode {
|
||||
OFF,
|
||||
TRACK,
|
||||
ALBUM;
|
||||
ALBUM,
|
||||
DYNAMIC;
|
||||
|
||||
fun toInt(): Int {
|
||||
return when (this) {
|
||||
OFF -> INT_OFF
|
||||
TRACK -> INT_TRACK
|
||||
ALBUM -> INT_ALBUM
|
||||
DYNAMIC -> INT_DYNAMIC
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,12 +19,14 @@ enum class ReplayGainMode {
|
|||
private const val INT_OFF = 0xA110
|
||||
private const val INT_TRACK = 0xA111
|
||||
private const val INT_ALBUM = 0xA112
|
||||
private const val INT_DYNAMIC = 0xA113
|
||||
|
||||
fun fromInt(value: Int): ReplayGainMode? {
|
||||
return when (value) {
|
||||
INT_OFF -> OFF
|
||||
INT_TRACK -> TRACK
|
||||
INT_ALBUM -> ALBUM
|
||||
INT_DYNAMIC -> DYNAMIC
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,12 +36,14 @@
|
|||
<item>@string/set_replay_gain_off</item>
|
||||
<item>@string/set_replay_gain_track</item>
|
||||
<item>@string/set_replay_gain_album</item>
|
||||
<item>@string/set_replay_gain_dynamic</item>
|
||||
</array>
|
||||
|
||||
<string-array name="values_replay_gain">
|
||||
<item>@integer/replay_gain_off</item>
|
||||
<item>@integer/replay_gain_track</item>
|
||||
<item>@integer/replay_gain_album</item>
|
||||
<item>@integer/replay_gain_dynamic</item>
|
||||
</string-array>
|
||||
|
||||
<integer name="theme_auto">-1</integer>
|
||||
|
@ -56,4 +58,5 @@
|
|||
<integer name="replay_gain_off">0xA110</integer>
|
||||
<integer name="replay_gain_track">0xA111</integer>
|
||||
<integer name="replay_gain_album">0xA112</integer>
|
||||
<integer name="replay_gain_dynamic">0xA113</integer>
|
||||
</resources>
|
|
@ -85,10 +85,11 @@
|
|||
<string name="set_focus_desc">Pause when other audio plays (ex. Calls)</string>
|
||||
<string name="set_plug_mgt">Headset focus</string>
|
||||
<string name="set_plug_mgt_desc">Play/Pause when the headset connection changes</string>
|
||||
<string name="set_replay_gain">ReplayGain (MP3/FLAC Only)</string>
|
||||
<string name="set_replay_gain">ReplayGain</string>
|
||||
<string name="set_replay_gain_off">Off</string>
|
||||
<string name="set_replay_gain_track">Prefer track</string>
|
||||
<string name="set_replay_gain_album">Prefer album</string>
|
||||
<string name="set_replay_gain_dynamic">Dynamic</string>
|
||||
|
||||
<string name="set_behavior">Behavior</string>
|
||||
<string name="set_song_mode">When a song is selected</string>
|
||||
|
|
13
info/FAQ.md
13
info/FAQ.md
|
@ -30,6 +30,19 @@ As per the [Supported ExoPlayer Formats](https://exoplayer.dev/supported-formats
|
|||
MP4, MP3, MKA, OGG, WAV, MPEG, AAC on all versions of Android. Auxio also supports FLAC on all versions
|
||||
of Android through the use of the ExoPlayer FLAC extension.
|
||||
|
||||
#### ReplayGain isn't working on my music!
|
||||
|
||||
This is for a couple reason:
|
||||
- Auxio doesn't extract ReplayGain tags for your format. This is the case with MP4 files since there's no
|
||||
defined ReplayGain standard for those.
|
||||
- Auxio doesn't recognize your ReplayGain tags. This is usually because of a non-standard tag like ID3v2's `RVAD` or
|
||||
an unrecognized name.
|
||||
|
||||
#### What is dynamic ReplayGain?
|
||||
|
||||
Dynamic ReplayGain is a quirk based off the FooBar2000 plugin that dynamically switches from track gain to album
|
||||
gain depending on if the current playback is from an album or not.
|
||||
|
||||
#### Why are accents lighter/less saturated in dark mode?
|
||||
|
||||
As per the [Material Design Guidelines](https://material.io/design/color/dark-theme.html), accents should be less
|
||||
|
|
14
prebuild.py
14
prebuild.py
|
@ -33,7 +33,7 @@ def sh(cmd):
|
|||
exoplayer_path = os.path.join(os.path.abspath(os.curdir), "deps", "exoplayer")
|
||||
|
||||
if os.path.exists(exoplayer_path):
|
||||
reinstall = input(INFO + "info:" + NC + " ExoPlayer is already installed. Would you like to reinstall it? [y/n] ")
|
||||
reinstall = input(INFO + "info:" + NC + " exoplayer is already installed. would you like to reinstall it? [y/n] ")
|
||||
|
||||
if not re.match("[yY][eE][sS]|[yY]", reinstall):
|
||||
sys.exit(0)
|
||||
|
@ -55,31 +55,31 @@ if ndk_path is None or not os.path.isfile(os.path.join(ndk_path, "ndk_build")):
|
|||
candidates.append(entry.path)
|
||||
|
||||
if len(candidates) > 0:
|
||||
print(WARN + "warn:" + NC + " NDK_PATH was not set or invalid. Multiple candidates were found however:")
|
||||
print(WARN + "warn:" + NC + " NDK_PATH was not set or invalid. multiple candidates were found however:")
|
||||
|
||||
for i, candidate in enumerate(candidates):
|
||||
print("[" + str(i) + "] " + candidate)
|
||||
|
||||
try:
|
||||
ndk_path = candidates[int(input("Enter the NDK to use [Default 0]: "))]
|
||||
ndk_path = candidates[int(input("Enter the ndk to use [Default 0]: "))]
|
||||
except:
|
||||
ndk_path = candidates[0]
|
||||
else:
|
||||
print(FATAL + "fatal:" + NC + " NDK_PATH is either invalid, or the Android NDK was not installed at a recognized location.")
|
||||
print(FATAL + "fatal:" + NC + " NDK_PATH is either invalid, or the android ndk was not installed at a recognized location.")
|
||||
system.exit(1)
|
||||
|
||||
# Now try to install ExoPlayer.
|
||||
sh("rm -rf deps")
|
||||
|
||||
print(INFO + "info:" + NC + " Cloning ExoPlayer...")
|
||||
print(INFO + "info:" + NC + " cloning ExoPlayer...")
|
||||
sh("git clone https://github.com/oxygencobalt/ExoPlayer.git " + exoplayer_path)
|
||||
os.chdir(exoplayer_path)
|
||||
sh("git checkout release-v2")
|
||||
|
||||
flac_ext_jni_path = os.path.join("extensions", "flac", "src", "main", "jni")
|
||||
print(INFO + "info:" + NC + " Installing FLAC extension...")
|
||||
print(INFO + "info:" + NC + " installing FLAC extension...")
|
||||
os.chdir(flac_ext_jni_path)
|
||||
sh('curl "https://ftp.osuosl.org/pub/xiph/releases/flac/flac-' + FLAC_VERSION + '.tar.xz" | tar xJ && mv "flac-' + FLAC_VERSION + '" flac')
|
||||
sh(ndk_path + "/ndk-build APP_ABI=all -j4")
|
||||
|
||||
print(OK + "success:" + NC + " Completed pre-build.")
|
||||
print(OK + "success:" + NC + " completed pre-build.")
|
||||
|
|
Loading…
Reference in a new issue