Skip to content

Add pantam drum Android app with Oboe low-latency audio#3

Open
sheix wants to merge 12 commits into
masterfrom
claude/pantam-drum-android-app-NjOcd
Open

Add pantam drum Android app with Oboe low-latency audio#3
sheix wants to merge 12 commits into
masterfrom
claude/pantam-drum-android-app-NjOcd

Conversation

@sheix
Copy link
Copy Markdown
Owner

@sheix sheix commented May 11, 2026

Full Android project under pantam-drum/:

  • C++ audio engine using Oboe (LowLatency + Exclusive mode) with
    lock-free atomic pending-trigger design so the audio callback is
    never blocked by the UI thread
  • SynthVoice: 3-partial handpan synthesis (fundamental, 2x octave,
    3.5x inharmonic) with ADSR envelope; attack 5ms, decay 300ms
  • Touch pressure (MotionEvent.getPressure) drives note velocity;
    hold duration extends release time from 1s up to 4s
  • PantamView: 9 circular tap zones (1 Ding center + 8-note ring),
    dark steel visual style with radial gradients and active highlights
  • 9 notes in E major / Fb scale: E3 164.81 Hz through B4 493.88 Hz
  • Multi-touch support; stream auto-restarts on Bluetooth disconnect

https://claude.ai/code/session_01H8bbxnAJEE71XduJ7QAPgu

claude and others added 12 commits May 11, 2026 14:34
Full Android project under pantam-drum/:
- C++ audio engine using Oboe (LowLatency + Exclusive mode) with
  lock-free atomic pending-trigger design so the audio callback is
  never blocked by the UI thread
- SynthVoice: 3-partial handpan synthesis (fundamental, 2x octave,
  3.5x inharmonic) with ADSR envelope; attack 5ms, decay 300ms
- Touch pressure (MotionEvent.getPressure) drives note velocity;
  hold duration extends release time from 1s up to 4s
- PantamView: 9 circular tap zones (1 Ding center + 8-note ring),
  dark steel visual style with radial gradients and active highlights
- 9 notes in E major / Fb scale: E3 164.81 Hz through B4 493.88 Hz
- Multi-touch support; stream auto-restarts on Bluetooth disconnect

https://claude.ai/code/session_01H8bbxnAJEE71XduJ7QAPgu
Gradle 8.6 is incompatible with JDK 25 (Android Studio's bundled JDK).
Pinning to the system Java 21 install makes the build work regardless
of which IDE or shell environment invokes it.

https://claude.ai/code/session_01H8bbxnAJEE71XduJ7QAPgu
…ild file

Android Studio's tooling can't determine the AGP version when it's
declared via a version catalog alias. Using a direct id/version string
in the root build.gradle.kts makes the version parseable by all tools.

https://claude.ai/code/session_01H8bbxnAJEE71XduJ7QAPgu
…e, GC

Three root causes of crackling when tapping multiple notes:

1. std::exp() was called once per sample inside the Decay stage render
   loop — at 48kHz with 4 voices decaying simultaneously that was
   ~192k expensive transcendental calls/sec, reliably overrunning the
   audio callback budget. Pre-compute mDecayCoeff in trigger() instead.

2. Resetting oscillator phases to 0 on every trigger caused a waveform
   discontinuity click when re-tapping an already-playing note. Now
   phases only reset when the voice was idle.

3. Per-voice amplitude (0.70+0.25+0.12 = 1.07 max) meant 2 simultaneous
   notes already exceeded 1.0 and tanh produced heavy saturation.
   Scaled weights down so 4 voices at full pressure sum to ~1.07.

Also cache RadialGradient objects in onSizeChanged() instead of
allocating 9 new ones per draw frame, eliminating GC pressure that
caused occasional audio thread jitter.

https://claude.ai/code/session_01H8bbxnAJEE71XduJ7QAPgu
AGP 8.3.2 was designed for Gradle 8.4–8.6. After the Gradle wrapper was
updated to 8.12, the version mismatch caused Android Studio to fail
project modelling and report "Unable to determine AGP version".

AGP 8.7.x officially supports Gradle 8.9+ so works correctly with 8.12.
Kotlin bumped to 2.0.21 (stable with AGP 8.7). compileSdk/targetSdk
raised to 35 as required by AGP 8.7.

https://claude.ai/code/session_01H8bbxnAJEE71XduJ7QAPgu
The Linux path was commented out since it doesn't exist on Windows.
Using the JBR bundled with Android Studio instead, which is Java 21
and compatible with Gradle 8.12 + AGP 8.7.3.

https://claude.ai/code/session_01H8bbxnAJEE71XduJ7QAPgu
…ffer

Three more click sources eliminated:

1. Phase reset condition was checked AFTER mStage was already changed to
   Attack, so the if (mStage == Stage::Idle) was always false — phases
   were never reset for truly fresh voices. Fixed by checking stage before
   modifying it, and zeroing mSmoothedAmp on fresh voice init.

2. Output amplitude (mEnvelope * mVelocity) was applied directly to audio
   samples with no smoothing. Any discontinuity at ADSR stage boundaries,
   re-triggers with different pressure, or velocity steps became audible
   clicks. Added a one-pole LP filter (mSmoothedAmp, coeff 0.05) that
   spreads any amplitude change over ~20 samples — fully inaudible.

3. Buffer size raised from 2 to 4 bursts to survive brief CPU spikes when
   multiple notes trigger simultaneously with a UI redraw. tanh() replaced
   with a hard clamp (cheaper, sufficient given controlled amplitude).

Also switched invalidate() to postInvalidateOnAnimation() so simultaneous
touch events don't schedule multiple redraws per vsync frame.

https://claude.ai/code/session_01H8bbxnAJEE71XduJ7QAPgu
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.

2 participants