Skip to content

feat(dsp): add experimental switch for lower and finer buffer size control#3

Closed
JadenJSJ wants to merge 3 commits intomasterfrom
feat/experimental-lower-buffer-size
Closed

feat(dsp): add experimental switch for lower and finer buffer size control#3
JadenJSJ wants to merge 3 commits intomasterfrom
feat/experimental-lower-buffer-size

Conversation

@JadenJSJ
Copy link
Member

@JadenJSJ JadenJSJ commented Feb 23, 2026

Summary by CodeRabbit

  • New Features
    • Added experimental "Allow low buffer sizes" option enabling audio buffer sizes as low as 2 samples (previously minimum 128 samples)
    • Reduced buffer size adjustment increments for finer tuning control
    • Includes warning that low settings may cause audio dropouts, crackling, or instability

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/experimental-lower-buffer-size

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/main/java/me/timschneeberger/rootlessjamesdsp/service/RootlessAudioProcessorService.kt (1)

425-431: Consider using maxOf for cleaner buffer size clamping.

The buffer size clamping logic is correct, but could be simplified. Additionally, the magic numbers 2 and 128 are duplicated in SettingsAudioFormatFragment.kt as constants. Consider extracting these to a shared location to prevent drift.

♻️ Suggested simplification
 val allowLowSamples = preferences.get<Boolean>(R.string.key_audioformat_allow_low_samples)
 val minBufferSize = if (allowLowSamples) 2 else 128
 val requestedBufferSize = preferences.get<Float>(R.string.key_audioformat_buffersize).toInt()
-var bufferSize = if (requestedBufferSize < minBufferSize) minBufferSize else requestedBufferSize
+var bufferSize = maxOf(requestedBufferSize, minBufferSize)
 if ((bufferSize % 2) != 0) {
     bufferSize += 1
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/timschneeberger/rootlessjamesdsp/service/RootlessAudioProcessorService.kt`
around lines 425 - 431, Replace the manual clamp and evenness logic with a
clearer maxOf-based clamp and centralize the magic numbers: use allowLowSamples,
requestedBufferSize and bufferSize in RootlessAudioProcessorService.kt to set
bufferSize = maxOf(requestedBufferSize, MIN_BUFFER_SIZE) (then ensure evenness),
where MIN_BUFFER_SIZE and DEFAULT_MIN_BUFFER_SIZE_LOW (or similarly named
constants) are pulled from a shared constants location (the same shared
constants used instead of the duplicated 2 and 128 in
SettingsAudioFormatFragment.kt) so both files reference the single source of
truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/settings/SettingsAudioFormatFragment.kt`:
- Around line 119-122: When allowLowSamples is toggled the UI updates via
applyBufferSizeRange(allowLowSamples) but the running audio service is not
restarted; update the allowLowSamples?.setOnPreferenceChangeListener block to
also send the same hard-reboot intent used for bufferSize and encoding changes
(ACTION_SERVICE_HARD_REBOOT_CORE) so the service re-reads the preference (the
service currently reads it in startRecording()); locate allowLowSamples and
applyBufferSizeRange in SettingsAudioFormatFragment and add the
intent-broadcast/Service restart call identical to the existing handlers for
bufferSize/encoding.

---

Nitpick comments:
In
`@app/src/main/java/me/timschneeberger/rootlessjamesdsp/service/RootlessAudioProcessorService.kt`:
- Around line 425-431: Replace the manual clamp and evenness logic with a
clearer maxOf-based clamp and centralize the magic numbers: use allowLowSamples,
requestedBufferSize and bufferSize in RootlessAudioProcessorService.kt to set
bufferSize = maxOf(requestedBufferSize, MIN_BUFFER_SIZE) (then ensure evenness),
where MIN_BUFFER_SIZE and DEFAULT_MIN_BUFFER_SIZE_LOW (or similarly named
constants) are pulled from a shared constants location (the same shared
constants used instead of the duplicated 2 and 128 in
SettingsAudioFormatFragment.kt) so both files reference the single source of
truth.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/settings/SettingsAudioFormatFragment.kt (1)

138-148: Potential unnecessary service restart on settings screen open.

applyBufferSizeRange is called during onCreatePreferences (line 111). If the stored buffer size happens to be below 128 while allowLowSamples is false (e.g., from a previous session with the flag enabled), this will trigger ACTION_SERVICE_HARD_REBOOT_CORE every time the settings screen is opened, even if the service has already applied the corrected value.

Consider adding a flag or check to avoid broadcasting the reboot intent during initial preference loading, or only send the broadcast when the value actually changes from user interaction.

♻️ Proposed fix
-    private fun applyBufferSizeRange(allowLow: Boolean) {
+    private fun applyBufferSizeRange(allowLow: Boolean, sendBroadcast: Boolean = true) {
         if (allowLow) {
             return
         }

         val currentValue = preferences.get<Float>(R.string.key_audioformat_buffersize)
         if(currentValue < Constants.AUDIO_BUFFER_MIN_DEFAULT) {
             bufferSize?.setValue(Constants.AUDIO_BUFFER_MIN_DEFAULT.toFloat())
-            context?.sendLocalBroadcast(Intent(Constants.ACTION_SERVICE_HARD_REBOOT_CORE))
+            if (sendBroadcast) {
+                context?.sendLocalBroadcast(Intent(Constants.ACTION_SERVICE_HARD_REBOOT_CORE))
+            }
         }
     }

Then update the initial call:

-        applyBufferSizeRange(preferences.get<Boolean>(R.string.key_audioformat_allow_low_samples))
+        applyBufferSizeRange(preferences.get<Boolean>(R.string.key_audioformat_allow_low_samples), sendBroadcast = false)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/settings/SettingsAudioFormatFragment.kt`
around lines 138 - 148, applyBufferSizeRange currently always sends
ACTION_SERVICE_HARD_REBOOT_CORE when it corrects the stored buffer size during
onCreatePreferences, causing a reboot every time settings open; modify
applyBufferSizeRange (and its callers) to accept a boolean like
suppressBroadcast or to take an explicit "isInitialLoad" flag and only send the
broadcast when the buffer size is actually changed by user action (i.e., compare
previousValue from preferences.get<Float>(...) to the corrected value and call
context?.sendLocalBroadcast(...) only if previousValue != correctedValue and
suppressBroadcast is false), and ensure the initial call from
onCreatePreferences passes suppressBroadcast=true so opening the settings won't
trigger a reboot.
ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3791e59 and e3a188c.

📒 Files selected for processing (8)
  • app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/settings/SettingsAudioFormatFragment.kt
  • app/src/main/java/me/timschneeberger/rootlessjamesdsp/service/RootlessAudioProcessorService.kt
  • app/src/main/java/me/timschneeberger/rootlessjamesdsp/utils/Constants.kt
  • app/src/main/res/values/defaults.xml
  • app/src/main/res/values/keys.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/res/xml/app_audio_format_preferences.xml
  • buildError.txt
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/res/xml/app_audio_format_preferences.xml`:
- Around line 16-26: The seekbar still allows sliding to values below the
effective minimum because applyBufferSizeRange() only clamps stored values;
update applyBufferSizeRange(allowLow: Boolean) to call bufferSize?.setMin(...)
(using Constants.AUDIO_BUFFER_MIN_LOW or Constants.AUDIO_BUFFER_MIN_DEFAULT) so
the UI minimum updates, then read the current value via preferences.get(...) and
if it's below the new min call bufferSize?.setValue(...) and send the reboot
broadcast Intent(Constants.ACTION_SERVICE_HARD_REBOOT_CORE); this uses
MaterialSeekbarPreference.setMin(), bufferSize, preferences.get,
bufferSize.setValue, and the ACTION_SERVICE_HARD_REBOOT_CORE constant.

---

Nitpick comments:
In
`@app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/settings/SettingsAudioFormatFragment.kt`:
- Around line 138-148: applyBufferSizeRange currently always sends
ACTION_SERVICE_HARD_REBOOT_CORE when it corrects the stored buffer size during
onCreatePreferences, causing a reboot every time settings open; modify
applyBufferSizeRange (and its callers) to accept a boolean like
suppressBroadcast or to take an explicit "isInitialLoad" flag and only send the
broadcast when the buffer size is actually changed by user action (i.e., compare
previousValue from preferences.get<Float>(...) to the corrected value and call
context?.sendLocalBroadcast(...) only if previousValue != correctedValue and
suppressBroadcast is false), and ensure the initial call from
onCreatePreferences passes suppressBroadcast=true so opening the settings won't
trigger a reboot.

Comment on lines +16 to 26
app:seekBarIncrement="2"
app:precision="0"
app:unit="@string/audio_format_buffer_size_unit"
app:labelMinWidth="120dp"
app:minValue="128"
app:minValue="2"
app:maxValue="16384"
app:defaultValue="@integer/default_audioformat_buffersize"
app:allowDividerAbove="false"
app:updatesContinuously="false"
app:showSeekBarValue="true"
app:iconSpaceReserved="false"/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the XML file to confirm the hardcoded values
find . -name "app_audio_format_preferences.xml" -type f

Repository: JSJ-Experiments/RootlessJamesDSP

Length of output: 131


🏁 Script executed:

# Find the SettingsAudioFormatFragment to verify the clamping logic
find . -name "SettingsAudioFormatFragment.kt" -type f

Repository: JSJ-Experiments/RootlessJamesDSP

Length of output: 180


🏁 Script executed:

# Search for MaterialSeekbarPreference implementation
find . -name "*MaterialSeekbar*" -o -name "*Seekbar*" | grep -E "\.(kt|java)$"

Repository: JSJ-Experiments/RootlessJamesDSP

Length of output: 171


🏁 Script executed:

# Read the XML file to confirm the hardcoded values
cat -n ./app/src/main/res/xml/app_audio_format_preferences.xml

Repository: JSJ-Experiments/RootlessJamesDSP

Length of output: 4868


🏁 Script executed:

# Read the SettingsAudioFormatFragment to check for change listeners and clamping
wc -l ./app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/settings/SettingsAudioFormatFragment.kt

Repository: JSJ-Experiments/RootlessJamesDSP

Length of output: 184


🏁 Script executed:

# Read the MaterialSeekbarPreference to see if minValue can be set dynamically
cat -n ./app/src/main/java/me/timschneeberger/rootlessjamesdsp/preference/MaterialSeekbarPreference.kt

Repository: JSJ-Experiments/RootlessJamesDSP

Length of output: 21634


🏁 Script executed:

# Read the SettingsAudioFormatFragment to find the change listener and applyBufferSizeRange method
cat -n ./app/src/main/java/me/timschneeberger/rootlessjamesdsp/fragment/settings/SettingsAudioFormatFragment.kt

Repository: JSJ-Experiments/RootlessJamesDSP

Length of output: 8635


UI allows selecting invalid buffer sizes when "Allow low buffer sizes" is disabled.

The seekbar is configured with minValue="2" in XML, but when the toggle is off, the fragment's change listener (lines 115-118 in SettingsAudioFormatFragment.kt) clamps values below 128. Users can slide to invalid values like 64 only to have them silently adjusted to 128.

The applyBufferSizeRange() method currently only clamps the stored value but doesn't update the seekbar's visible minimum. Since MaterialSeekbarPreference exposes a public setMin() method that triggers UI updates via notifyChanged(), you should call it to reflect the effective range:

♻️ Update applyBufferSizeRange() to dynamically set seekbar minimum
private fun applyBufferSizeRange(allowLow: Boolean) {
    val minValue = if (allowLow) {
        Constants.AUDIO_BUFFER_MIN_LOW.toFloat()
    } else {
        Constants.AUDIO_BUFFER_MIN_DEFAULT.toFloat()
    }
    
    bufferSize?.setMin(minValue)
    
    val currentValue = preferences.get<Float>(R.string.key_audioformat_buffersize)
    if(currentValue < minValue) {
        bufferSize?.setValue(minValue)
        context?.sendLocalBroadcast(Intent(Constants.ACTION_SERVICE_HARD_REBOOT_CORE))
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/res/xml/app_audio_format_preferences.xml` around lines 16 - 26,
The seekbar still allows sliding to values below the effective minimum because
applyBufferSizeRange() only clamps stored values; update
applyBufferSizeRange(allowLow: Boolean) to call bufferSize?.setMin(...) (using
Constants.AUDIO_BUFFER_MIN_LOW or Constants.AUDIO_BUFFER_MIN_DEFAULT) so the UI
minimum updates, then read the current value via preferences.get(...) and if
it's below the new min call bufferSize?.setValue(...) and send the reboot
broadcast Intent(Constants.ACTION_SERVICE_HARD_REBOOT_CORE); this uses
MaterialSeekbarPreference.setMin(), bufferSize, preferences.get,
bufferSize.setValue, and the ACTION_SERVICE_HARD_REBOOT_CORE constant.

@JadenJSJ JadenJSJ closed this Feb 23, 2026
@JadenJSJ JadenJSJ deleted the feat/experimental-lower-buffer-size branch February 23, 2026 07:33
@JadenJSJ
Copy link
Member Author

JadenJSJ commented Feb 23, 2026

I guess other parts of the chain cause more latency then the processing part.
There is only so much we can reduce in the process stage
cuz it needs a record and track buffer
capture -> process -> playback

i still got like 300ms worth of delay with the buffersize set to 2

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant