Skip to content

🧪feat: ReplayGain + Pre-amp support#504

Merged
cyanChill merged 23 commits into
devfrom
feat/replay-gain
Jun 4, 2026
Merged

🧪feat: ReplayGain + Pre-amp support#504
cyanChill merged 23 commits into
devfrom
feat/replay-gain

Conversation

@cyanChill

@cyanChill cyanChill commented Jun 4, 2026

Copy link
Copy Markdown
Member

Why

This PR adds experimental support for ReplayGain & a ReplayGain Pre-amp. The strategy on how it gets applied is as followed:

  1. Before we load a track into AudioBrowser, we use the new getR128Gain() function from our @missingcore/react-native-metadata-retriever package to get the embedded value on the fly.
  2. To that, we add the ReplayGain Pre-amp (value used is based on whether there's an embedded ReplayGain tag or not).
  3. The track should be played with the current dB adjustment (given the ReplayGain feature is enabled).

Current expectations when ReplayGain is enabled is for the audio to be noticeably quieter (as it normalize the audio to 89 dB).

  • Our implementation supports positive gains.
  • The regular volume slider doesn't need to be removed.
  • Since we get the embedded value on the fly, a Deep Rescan is not required.
  • A side-effect of our currently implementation is that the embedded ReplayGain value may not be applied immediately.

We have a new ReplayGain doc that goes over this.

These settings currently can only be found in the Audio Effects settings screen, which is currently located in the Experimental Features settings screen.

Checklist

  • Documentation is up to date to reflect these changes.
  • Ensure dependency licenses are up-to-date by running pnpm sync:licenses.
  • This diff will work correctly for pnpm android:prod.

Summary by CodeRabbit

  • New Features

    • Added experimental ReplayGain audio enhancement feature with toggle control
    • Added Audio Effects settings screen with pre-amp adjustment sliders (±15dB range)
    • New "Audio Effects" entry in Experimental Settings menu
    • ReplayGain automatically applied during playback with optional pre-amp customization
  • Documentation

    • Added ReplayGain feature documentation with usage details and specifications

cyanChill added 16 commits June 3, 2026 00:50
- The idea currently is to fetch this as well when getting the database entry for the current/upcoming track.
- No way to turn it off right now.
- Don't really know if its working or not.
- We expose a new `setReplayGainStatus` in our `react-native-audio-browser` fork.
- Current location in "Playback Options" sheet is temporary - we'll probably have a new "Audio Effects" tab where we'll move this into.
- We'll eventually move some other settings from the Preference store into here (in a different PR).
- First thing we export is an `applyReplayGainToTrack` which handles getting the replaygain tag value & combining it with track data to create a "Track" object that can be passed to AudioBrowser.
- Will only be applied when Replay Gain is enabled.
- Current strategy will result in changes only apply on the next track forwards.
- This is due to getting the effect of having 2 opac elements of the same color overlapping each other.
- `needsOffscreenAlphaCompositing` is the solution for this case.
- This was due to our "optimization" of not fetching the embedded replay gain tags when we play a track when the feature is disabled.
- React Native docs suggest we turn them off when we're not using them.
@cyanChill cyanChill added enhancement New feature or request Experimental Features that may not get official support or work as expected. labels Jun 4, 2026
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a complete ReplayGain audio processing feature with user-adjustable pre-amp controls. It extends playback state persistence, implements R128 gain fetching with tag-aware pre-amp variants, adds UI settings with navigation integration, and applies gain normalization throughout all track loading paths.

Changes

ReplayGain Audio Processing

Layer / File(s) Summary
Playback store schema and constants for replay gain
mobile/src/stores/Playback/constants.ts, mobile/src/stores/Playback/store.ts, mobile/src/modules/audio/replayGain/core/constants.ts
PlaybackStore extended with isReplayGainEnabled, preAmpWTags, and preAmpWOTags fields, store initialized with defaults, and DB_OFFSET constant defined with ±15 dB bounds.
ReplayGain state actions
mobile/src/modules/audio/replayGain/core/actions.ts
toggleStatus flips replay gain enablement and syncs to AudioBrowser; updatePreAmpWithTags and updatePreAmpWithoutTags clamp input dB values and persist to store.
ReplayGain track audio processing
mobile/src/modules/audio/replayGain/core/apply.ts
applyReplayGainToTrack fetches R128 gain, merges with pre-amp adjustments based on tag availability, and returns react-native-audio-browser-compatible track objects with computed gain.
ReplayGain settings UI component
mobile/src/modules/audio/replayGain/components/ReplayGainSettings.tsx
ReplayGainSettings renders toggle switch and two PreAmpSlider variants (with/without tags) with dB readouts, responsive disable state when ReplayGain is off.
Audio effects screen routing and navigation
mobile/src/modules/audio/_screens.tsx, mobile/src/resources/icons/GraphicEQ.tsx, mobile/src/navigation/routes.tsx, mobile/src/navigation/screens/settings/ExperimentalSettingsView.tsx
AudioEffectScreenGroup wraps ReplayGainSettings in screen definition, GraphicEQ SVG icon added, screen registered in root navigation, and Audio Effects menu entry added to ExperimentalSettingsView.
Track loading pipeline integration
mobile/src/initServices.ts, mobile/src/stores/Playback/actions/playbackControls.ts, mobile/src/stores/Playback/actions/queue.ts, mobile/src/stores/Playback/actions/resynchronize.ts
loadCurrentTrack, queue removeIds/synchronize, updateNowPlaying, and queue preload paths updated to use applyReplayGainToTrack instead of deprecated formatTrackforPlayer.
App startup initialization with replay gain
mobile/src/modules/scanning/hooks/useSetup.ts
useSetup reads isReplayGainEnabled from playback store and applies it to AudioBrowser.setReplayGainStatus during app initialization.
Documentation and localization
mobile/src/modules/i18n/translations/en.json, docs/experimental-features.md, docs/replaygain.md
English translations added for audio effects and replay gain UI labels, experimental features documentation updated with ReplayGain link, and comprehensive ReplayGain documentation created describing computation, pre-amp adjustment, and tag support.
Dependency updates for native audio modules
mobile/package.json, mobile/pnpm-workspace.yaml
react-native-metadata-retriever and react-native-audio-browser pinned to new GitHub commit references; metadata-retriever added to workspace build allowlist.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly identifies the main feature addition: ReplayGain support with pre-amp functionality. The emoji indicates experimental status, aligning with the feature's designation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mobile/src/modules/audio/replayGain/core/apply.ts`:
- Around line 16-21: applyReplayGainToTrack currently awaits
getR128Gain(track.uri) without error handling which can throw and break
playback; wrap the await call in a try/catch inside applyReplayGainToTrack
(keeping the existing apply parameter logic) and on any error set replayGain to
null (or otherwise skip using tag gain) so finalDB falls back to preAmpWOTags;
reference the function applyReplayGainToTrack, the call to
getR128Gain(track.uri), and the state variables preAmpWTags/preAmpWOTags from
playbackStore when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab0688fd-7dff-4bb3-a394-44f6577b1174

📥 Commits

Reviewing files that changed from the base of the PR and between 945e378 and a51af92.

⛔ Files ignored due to path filters (1)
  • mobile/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • mobile/package.json
  • mobile/pnpm-workspace.yaml
  • mobile/src/data/track/utils.ts
  • mobile/src/initServices.ts
  • mobile/src/modules/audio/_screens.tsx
  • mobile/src/modules/audio/replayGain/components/ReplayGainSettings.tsx
  • mobile/src/modules/audio/replayGain/core/actions.ts
  • mobile/src/modules/audio/replayGain/core/apply.ts
  • mobile/src/modules/i18n/translations/en.json
  • mobile/src/modules/scanning/hooks/useSetup.ts
  • mobile/src/navigation/routes.tsx
  • mobile/src/navigation/screens/settings/ExperimentalSettingsView.tsx
  • mobile/src/resources/icons/GraphicEQ.tsx
  • mobile/src/stores/Playback/actions/playbackControls.ts
  • mobile/src/stores/Playback/actions/queue.ts
  • mobile/src/stores/Playback/actions/resynchronize.ts
  • mobile/src/stores/Playback/constants.ts
  • mobile/src/stores/Playback/store.ts
💤 Files with no reviewable changes (1)
  • mobile/src/data/track/utils.ts

Comment thread mobile/src/modules/audio/replayGain/core/apply.ts
@cyanChill cyanChill changed the title 🧪feat: ReplayGain support + pre-amp 🧪feat: ReplayGain + pre-amp support Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/replaygain.md`:
- Line 5: Update the sentence in docs/replaygain.md that begins "ReplayGain is a
feature..." to clarify that ReplayGain normalizes perceived loudness to a
reference level rather than peak amplitude; specifically replace the phrase "its
peak loudness hits `89 dB`" with wording like "its perceived loudness to a
reference level (commonly cited as 89 dB SPL)"; keep the rest of the sentence
about enabled audio becoming quieter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e6205209-74d5-4b47-a4f2-47739049d2ab

📥 Commits

Reviewing files that changed from the base of the PR and between a51af92 and b7eb42e.

📒 Files selected for processing (8)
  • docs/experimental-features.md
  • docs/replaygain.md
  • mobile/src/modules/audio/replayGain/components/ReplayGainSettings.tsx
  • mobile/src/modules/audio/replayGain/core/actions.ts
  • mobile/src/modules/audio/replayGain/core/apply.ts
  • mobile/src/modules/audio/replayGain/core/constants.ts
  • mobile/src/stores/Playback/actions/playbackControls.ts
  • mobile/src/stores/Playback/constants.ts
💤 Files with no reviewable changes (1)
  • mobile/src/stores/Playback/actions/playbackControls.ts
✅ Files skipped from review due to trivial changes (2)
  • mobile/src/modules/audio/replayGain/core/constants.ts
  • docs/experimental-features.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • mobile/src/stores/Playback/constants.ts
  • mobile/src/modules/audio/replayGain/core/actions.ts
  • mobile/src/modules/audio/replayGain/core/apply.ts

Comment thread docs/replaygain.md Outdated
@cyanChill cyanChill marked this pull request as ready for review June 4, 2026 23:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mobile/src/modules/audio/replayGain/core/actions.ts (1)

8-12: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Consider error handling for native synchronization.

If AudioBrowser.setReplayGainStatus throws, the store state will be updated but the native player won't be synchronized, leading to inconsistent behavior. Consider wrapping the native call in a try-catch and reverting the store state on failure, or reordering the operations to call the native API before updating the store.

🛡️ Proposed fix with error handling
 export function toggleStatus() {
-  const nextState = !playbackStore.getState().isReplayGainEnabled;
-  playbackStore.setState({ isReplayGainEnabled: nextState });
-  AudioBrowser.setReplayGainStatus(nextState);
+  const nextState = !playbackStore.getState().isReplayGainEnabled;
+  try {
+    AudioBrowser.setReplayGainStatus(nextState);
+    playbackStore.setState({ isReplayGainEnabled: nextState });
+  } catch (error) {
+    console.error('[ReplayGain] Failed to sync status to native:', error);
+    throw error;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/modules/audio/replayGain/core/actions.ts` around lines 8 - 12, The
toggleStatus function updates playbackStore before calling the native API
(AudioBrowser.setReplayGainStatus), which can cause state drift if the native
call fails; change toggleStatus to synchronize with native first or add
try/catch around the native call and revert the store on failure. Specifically,
either call AudioBrowser.setReplayGainStatus(nextState) before
playbackStore.setState(...) (so the store only updates on success), or wrap the
native call in try/catch and if it throws call playbackStore.setState({
isReplayGainEnabled: !nextState }) to roll back; reference the toggleStatus
function, playbackStore.getState()/setState, and
AudioBrowser.setReplayGainStatus when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@mobile/src/modules/audio/replayGain/core/actions.ts`:
- Around line 8-12: The toggleStatus function updates playbackStore before
calling the native API (AudioBrowser.setReplayGainStatus), which can cause state
drift if the native call fails; change toggleStatus to synchronize with native
first or add try/catch around the native call and revert the store on failure.
Specifically, either call AudioBrowser.setReplayGainStatus(nextState) before
playbackStore.setState(...) (so the store only updates on success), or wrap the
native call in try/catch and if it throws call playbackStore.setState({
isReplayGainEnabled: !nextState }) to roll back; reference the toggleStatus
function, playbackStore.getState()/setState, and
AudioBrowser.setReplayGainStatus when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5dc5ae17-9a3e-4377-9bde-f7389b030f82

📥 Commits

Reviewing files that changed from the base of the PR and between a51af92 and 27af357.

📒 Files selected for processing (8)
  • docs/experimental-features.md
  • docs/replaygain.md
  • mobile/src/modules/audio/replayGain/components/ReplayGainSettings.tsx
  • mobile/src/modules/audio/replayGain/core/actions.ts
  • mobile/src/modules/audio/replayGain/core/apply.ts
  • mobile/src/modules/audio/replayGain/core/constants.ts
  • mobile/src/stores/Playback/actions/playbackControls.ts
  • mobile/src/stores/Playback/constants.ts
💤 Files with no reviewable changes (1)
  • mobile/src/stores/Playback/actions/playbackControls.ts
✅ Files skipped from review due to trivial changes (1)
  • mobile/src/modules/audio/replayGain/core/constants.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • mobile/src/stores/Playback/constants.ts
  • mobile/src/modules/audio/replayGain/components/ReplayGainSettings.tsx
  • mobile/src/modules/audio/replayGain/core/apply.ts

@cyanChill cyanChill merged commit e6bd010 into dev Jun 4, 2026
1 check passed
@cyanChill cyanChill deleted the feat/replay-gain branch June 4, 2026 23:27
@cyanChill cyanChill changed the title 🧪feat: ReplayGain + pre-amp support 🧪feat: ReplayGain + Pre-amp support Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request Experimental Features that may not get official support or work as expected.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant