mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
playback: call the context-aware ReplayGain mode Auto
Rename the ReplayGainMode value, string resource and user-facing label from Dynamic to Auto. No behaviour change.
This commit is contained in:
@@ -57,7 +57,7 @@ const val MAX_REPLAY_GAIN_PREAMP_DB = 15f
|
||||
|
||||
enum class SyncMode { Off, WifiOnly, Always }
|
||||
|
||||
enum class ReplayGainMode { Off, Track, Album, Dynamic }
|
||||
enum class ReplayGainMode { Off, Track, Album, Auto }
|
||||
|
||||
data class ArtworkSeedValue(
|
||||
val cacheKey: String,
|
||||
|
||||
@@ -37,7 +37,7 @@ internal fun replayGainLinearGain(
|
||||
// headroom without a peak, so a peakless track is only ever attenuated.
|
||||
val trackPeak = gain.trackPeak?.takeIf { it.isFinite() && it > 0f }
|
||||
val albumPeak = gain.albumPeak?.takeIf { it.isFinite() && it > 0f }
|
||||
val album = settings.mode == ReplayGainMode.Album || (settings.mode == ReplayGainMode.Dynamic && albumContext)
|
||||
val album = settings.mode == ReplayGainMode.Album || (settings.mode == ReplayGainMode.Auto && albumContext)
|
||||
val (requestedDb, peak) =
|
||||
if (album) {
|
||||
(gain.albumGain ?: gain.trackGain) to (albumPeak ?: trackPeak)
|
||||
|
||||
@@ -32,7 +32,7 @@ internal fun replayGainModeLabel(
|
||||
ReplayGainMode.Off -> resources.getString(R.string.settings_replay_gain_mode_off)
|
||||
ReplayGainMode.Track -> resources.getString(R.string.settings_replay_gain_mode_track)
|
||||
ReplayGainMode.Album -> resources.getString(R.string.settings_replay_gain_mode_album)
|
||||
ReplayGainMode.Dynamic -> resources.getString(R.string.settings_replay_gain_mode_dynamic)
|
||||
ReplayGainMode.Auto -> resources.getString(R.string.settings_replay_gain_mode_auto)
|
||||
}
|
||||
|
||||
internal fun replayGainPreampLabel(
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<string name="settings_music_options_scrobble_description_on">Send now playing and play counts</string>
|
||||
<string name="settings_music_options_scrobble_title">Scrobble to server</string>
|
||||
<string name="settings_replay_gain_mode_album">Album gain</string>
|
||||
<string name="settings_replay_gain_mode_dynamic">Dynamic</string>
|
||||
<string name="settings_replay_gain_mode_auto">Auto</string>
|
||||
<string name="settings_replay_gain_mode_off">Off</string>
|
||||
<string name="settings_replay_gain_mode_track">Track gain</string>
|
||||
<string name="settings_replay_gain_preamp_decrease">Lower preamp</string>
|
||||
|
||||
@@ -225,8 +225,8 @@ class UserPreferencesTest {
|
||||
assertEquals(ReplayGainMode.Album, prefs.replayGainMode().first())
|
||||
assertEquals(3.5f, prefs.replayGainPreampDb().first(), 0f)
|
||||
|
||||
prefs.setReplayGainMode(ReplayGainMode.Dynamic)
|
||||
assertEquals(ReplayGainMode.Dynamic, prefs.replayGainMode().first())
|
||||
prefs.setReplayGainMode(ReplayGainMode.Auto)
|
||||
assertEquals(ReplayGainMode.Auto, prefs.replayGainMode().first())
|
||||
|
||||
prefs.setReplayGainPreampDb(99f)
|
||||
assertEquals(MAX_REPLAY_GAIN_PREAMP_DB, prefs.replayGainPreampDb().first(), 0f)
|
||||
|
||||
@@ -39,8 +39,8 @@ class ReplayGainControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dynamicModeFollowsTheItemsGainContext() {
|
||||
controller.setSettings(ReplayGainSettings(ReplayGainMode.Dynamic, 0f))
|
||||
fun autoModeFollowsTheItemsGainContext() {
|
||||
controller.setSettings(ReplayGainSettings(ReplayGainMode.Auto, 0f))
|
||||
|
||||
controller.applyItem(item("a", trackGain = -6.0206f, albumGain = -1f))
|
||||
assertEquals(0.5f, applied.last(), TOLERANCE)
|
||||
|
||||
@@ -54,12 +54,12 @@ class ReplayGainTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dynamicModeAppliesTheAlbumGainInAnAlbumContext() {
|
||||
fun autoModeAppliesTheAlbumGainInAnAlbumContext() {
|
||||
assertEquals(
|
||||
0.5f,
|
||||
replayGainLinearGain(
|
||||
ReplayGain(trackGain = -1f, albumGain = -6.0206f, trackPeak = 1f, albumPeak = 1f),
|
||||
ReplayGainSettings(ReplayGainMode.Dynamic, 0f),
|
||||
ReplayGainSettings(ReplayGainMode.Auto, 0f),
|
||||
albumContext = true,
|
||||
),
|
||||
TOLERANCE,
|
||||
@@ -67,12 +67,12 @@ class ReplayGainTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dynamicModeAppliesTheTrackGainInAMixedContext() {
|
||||
fun autoModeAppliesTheTrackGainInAMixedContext() {
|
||||
assertEquals(
|
||||
0.5f,
|
||||
replayGainLinearGain(
|
||||
ReplayGain(trackGain = -6.0206f, albumGain = -1f, trackPeak = 1f, albumPeak = 1f),
|
||||
ReplayGainSettings(ReplayGainMode.Dynamic, 0f),
|
||||
ReplayGainSettings(ReplayGainMode.Auto, 0f),
|
||||
albumContext = false,
|
||||
),
|
||||
TOLERANCE,
|
||||
@@ -80,8 +80,8 @@ class ReplayGainTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dynamicModeFallsBackInEitherContext() {
|
||||
val settings = ReplayGainSettings(ReplayGainMode.Dynamic, 0f)
|
||||
fun autoModeFallsBackInEitherContext() {
|
||||
val settings = ReplayGainSettings(ReplayGainMode.Auto, 0f)
|
||||
|
||||
assertEquals(0.5f, replayGainLinearGain(ReplayGain(trackGain = -6.0206f), settings, albumContext = true), TOLERANCE)
|
||||
assertEquals(0.5f, replayGainLinearGain(ReplayGain(albumGain = -6.0206f), settings, albumContext = false), TOLERANCE)
|
||||
|
||||
@@ -19,7 +19,7 @@ class ReplayGainControlsTest {
|
||||
assertEquals("Off", replayGainModeLabel(resources, ReplayGainMode.Off))
|
||||
assertEquals("Track gain", replayGainModeLabel(resources, ReplayGainMode.Track))
|
||||
assertEquals("Album gain", replayGainModeLabel(resources, ReplayGainMode.Album))
|
||||
assertEquals("Dynamic", replayGainModeLabel(resources, ReplayGainMode.Dynamic))
|
||||
assertEquals("Auto", replayGainModeLabel(resources, ReplayGainMode.Auto))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -98,7 +98,7 @@ Server writes — now-playing, scrobbles and stars — go through a `ServerActio
|
||||
|
||||
The media notification carries a star button for the playing track. `PlaybackService` adds a `com.subtracks.STAR` session command and a `CommandButton` to the `MediaSession`, and drives its filled/unfilled icon from the playing song's Room row. That path — `MediaSession.Callback`, `SessionCommand`, `CommandButton`, `SessionResult`/`SessionError` and `MediaSession.setMediaButtonPreferences` — is annotated `@UnstableApi` in Media3 1.11.1 and is therefore opted into explicitly: when bumping Media3, re-check those signatures and the notification's button resolution. The stable `setCustomLayout` is not a substitute here, because the notification manager only refreshes on `onMediaButtonPreferencesChanged`, so the icon would not update mid-playback.
|
||||
|
||||
ReplayGain is applied at the audio sink. The gain the server reports per song (`trackGain`/`albumGain` and their peaks; `baseGain` and `fallbackGain` are ignored) is synced into the song row, carried onto the media item, and applied by a `ReplayGainProcessor` in `DefaultAudioSink`'s processor chain, installed through a `RenderersFactory`. The gain is set from `AudioProcessor.flush(StreamMetadata)`, which Media3 1.11 populates with the playback `Timeline` and the current period id (`androidx/media#418`) and calls on every sink configuration — including a media-item transition at an unchanged format — so a new item's gain is in place before any of its buffers are processed. That timing is the point: `Player.Listener.onMediaItemTransition` arrives on the app thread after the playback thread has already rendered, which is too late. When bumping Media3, re-check that `flush` still carries the timeline and period, and that the chain is reached at all: float output and audio offload both bypass it, which is why float output stays off. The mode (track/album/dynamic/off) and a preamp are DataStore preferences read by `PlaybackService`, and the anti-clip cap uses the peak when the server sends one: without a peak there is no headroom to check, so a peakless track is only ever attenuated. Dynamic mode selects album gain for items whose queue entry references an album and track gain for playlists, singles and other mixed contexts, including shuffled album queues; the choice is carried on each media item because the audio thread cannot query the queue.
|
||||
ReplayGain is applied at the audio sink. The gain the server reports per song (`trackGain`/`albumGain` and their peaks; `baseGain` and `fallbackGain` are ignored) is synced into the song row, carried onto the media item, and applied by a `ReplayGainProcessor` in `DefaultAudioSink`'s processor chain, installed through a `RenderersFactory`. The gain is set from `AudioProcessor.flush(StreamMetadata)`, which Media3 1.11 populates with the playback `Timeline` and the current period id (`androidx/media#418`) and calls on every sink configuration — including a media-item transition at an unchanged format — so a new item's gain is in place before any of its buffers are processed. That timing is the point: `Player.Listener.onMediaItemTransition` arrives on the app thread after the playback thread has already rendered, which is too late. When bumping Media3, re-check that `flush` still carries the timeline and period, and that the chain is reached at all: float output and audio offload both bypass it, which is why float output stays off. The mode (track/album/auto/off) and a preamp are DataStore preferences read by `PlaybackService`, and the anti-clip cap uses the peak when the server sends one: without a peak there is no headroom to check, so a peakless track is only ever attenuated. Auto mode selects album gain for items whose queue entry references an album and track gain for playlists, singles and other mixed contexts, including shuffled album queues; the choice is carried on each media item because the audio thread cannot query the queue.
|
||||
|
||||
## Data model
|
||||
|
||||
|
||||
Reference in New Issue
Block a user