Pocket Cowbell
Swing your phone. It rings. Four years of making that feel real.








01 The story
A cowbell you play by swinging your phone. It started in 2022 as a weekend web app and grew into twin native apps in Kotlin and Swift, with a physics-based clapper, bell sounds modeled from real recordings, 54 songs to play and global leaderboards. Live on Google Play and the App Store.
Pocket Cowbell started in September 2022 as a progressive web app on Firebase Hosting: a picture of a maroon cowbell that swayed and played a sample whenever the browser's devicemotion event crossed a threshold. It worked on Android, but iOS Safari wouldn't play the sound, so within a week I was sketching SwiftUI and React Native versions. The native iPhone app went to the App Store as version 1.0 on November 23, 2022.
The iPhone app kept growing through 2023 and 2024: sound with the ringer switch off, a Pro upgrade, then a 4.0 redesign with Game Center leaderboards, custom cowbells, recording and stats. In October 2024 I wrote a native Android version in Kotlin and Jetpack Compose, with Play Games leaderboards, Time Trials and bells to collect. I tried Flutter twice, but the store apps stayed native. In 2026 they got a limited-time World Cup edition with 48 country bells, CI, a privacy manifest, and an adaptive redesign for iPad and foldables.
The November 2026 update rebuilt how the bell rings. A clapper model decides when a swing really strikes: the phone is treated as a bell held by its handle, gravity is tracked with the gyroscope, and a chop or flick rings as it stops, as hard as it lands, while jiggles, flat waves and setting the phone down don't. Each strike is synthesized from bell models fit to the app's real cowbell recordings and mixed through one resonator, so fast shakes build and beat like real metal. Android plays it through AAudio, with a direct MMAP path where the phone allows; iOS uses AVAudioEngine with a 5 ms buffer.
On top of that sit Song mode, where each ring plays the next note of one of 54 songs, a Notes mode you can bend with a finger, and sound packs with jams where a band plays along. The same update drops app-open ads and random interstitials, leaving one banner. On November 9, 2026 I merged both apps and their histories into one repo, with a shared folder for audio, swing test data and the bell-model tools, a parity map from every Android file to its Swift twin, and a script that keeps the shared files identical.
Android 3.3 and iOS 6.2.1 are in the stores. The new ringing engine is in TestFlight on iOS (6.3.0, build 46) and in an open pull request on Android.
▶ Film
02 What it does
- 01
Swing, chop or flick your phone to ring the bell, or tap it; the harder the swing, the louder the ring
- 02
Song mode: every ring plays the next note of one of 54 songs, with the song's name on screen
- 03
Notes mode: touch a row to ring a note, slide to bend it, use several fingers for a chord
- 04
A wall of bells to collect (8-Bit, Cello, Cloud, Smoke and more), plus a photo bell made from your own picture
- 05
Sound packs (Pets, Instruments, Space, Party, Jams), free at ring milestones or available early
- 06
Combo counter for rings in a row, daily and lifetime stats, and a shareable ring count
- 07
Global leaderboards on Game Center and Google Play Games, with your exact rank
- 08
First-run tutorial on the real bell, a daily reminder, adjustable vibration, and no surprise ads
03 How it's built
Everything that makes sound runs on the phone: motion goes through the clapper model to a strike mixer that synthesizes each ring from bell models generated offline by Python tools. The cloud is only analytics, remote config, crash reports, leaderboards, purchases and ads.
All 10 connections, as text
- Accelerometer + gyroscope → Clapper + strike mixer (on device): motion samples
- Clapper + strike mixer (on device) → Android app (Kotlin, Compose): AAudio strikes
- Clapper + strike mixer (on device) → iPhone app (SwiftUI): AVAudioEngine strikes
- shared/: audio, swing data, bell tools → Clapper + strike mixer (on device): generated bell models
- Android app (Kotlin, Compose) → Game Center / Play Games: ring counts
- iPhone app (SwiftUI) → Game Center / Play Games: ring counts
- Android app (Kotlin, Compose) → Firebase: analytics, config, crashes, FCM: events, config
- iPhone app (SwiftUI) → Firebase: analytics, config, crashes, FCM: events, config
- iPhone app (SwiftUI) → StoreKit / Play Billing, AdMob: Pro, packs, banner
- Android app (Kotlin, Compose) → StoreKit / Play Billing, AdMob: Pro, packs, banner
04 Engineering notes
Clapper model (Clapper.kt / Clapper.swift): a stroke rings only when the push reverses and the clapper crosses the bell; gravity is tracked through 18 rad/s chops; tuned on swings recorded on a Pixel and a few hundred simulated motions
Modal bell synthesis: Python tools measure each recorded cowbell's modes and decay times, fit strike parameters by log-mel distance, judge realism by energy distance, then emit the bell tables as generated Kotlin and Swift
Low-latency audio: C++ AAudio on Android (MMAP where available, about a quarter of AudioTrack's latency on a Pixel) and AVAudioEngine with a 5 ms buffer on iOS. The audio thread never allocates and only try-locks
Line-for-line Android-to-iOS port with the same constants, settings keys and analytics events, tracked in a parity document, plus UI tests that drive every screen, a 300-tap stress test and shared CSV swing fixtures
Monorepo built from two separate app histories (about 220 commits) with a shared-assets manifest and a sync/check script, so Gradle and Xcode build without an extra step
Store plumbing on both platforms: StoreKit 2 and Play Billing for Pro and packs (refunds revoke, reinstalls restore), AdMob with Google's consent platform, Firebase Analytics, Remote Config and Crashlytics
05 By the numbers
- 10K+
- Google Play downloads
- November 2022
- On the App Store since (v1.0, 2022-11-23)
- 54
- Songs in Song mode
- 48
- World Cup country bells
06 Stack
App
- Kotlin
- Jetpack Compose
- Material 3
- Swift
- SwiftUI
- Core Motion
- AVAudioEngine
- Accelerate (vDSP)
- C++ / AAudio (NDK)
- Core Haptics
- PhotosUI
Backend
- Firebase Analytics
- Firebase Remote Config
- Firebase Crashlytics
- Firebase Cloud Messaging
- Game Center
- Google Play Games Services
Data
- DataStore / SharedPreferences
- UserDefaults
- generated bell-model tables
Infrastructure
- Google Play
- App Store / TestFlight
- AdMob + User Messaging Platform
- StoreKit 2
- Play Billing 8
- GitHub Actions (Android CI, iOS CI, simulator check)
Tooling
- Python (numpy, scipy) bell-model and sound-lab tools
- ffmpeg
- Gradle
- Xcode
- XCTest / XCUITest
- Blender + Swift for marketing renders
Lines of code (approx.)
- Kotlin14,643
- Swift12,942
- Python2,042
- C++161
≈ 29,788 lines
Repositories
- pocket-cowbell-appprivate
- PocketCowbellprivate
- pocket-cowbell-iosprivate
- pocket-cowbellprivate
- Pocket-Cowbellv0private
- Pocket-Cowbell-3private
- pc2 ↗public
- pocket_cowbell_flutterprivate
- pocket-cowbell-bothprivate
- ai-cowbellprivate
- pocket_cowbell_dsp_labprivate
- pocket_cowbell_dsp_lab_v2private
The 451 commits are spread across 10 repos. pocket-cowbell-app (224 commits, 2024-10-08 to 2026-11-09) holds the imported histories of the Android and iOS apps. The separate PocketCowbell (101) and pocket-cowbell-ios (140) repos have a few extra commits on branches that weren't imported, so they aren't counted again. Older repos: the 2022 PWA pocket-cowbell (21), Pocket-Cowbellv0 (4), Pocket-Cowbell-3 SwiftUI (58, 2023-03 to 2025-03), pc2 Flutter prototype (2, Feb 2024), pocket-cowbell-both (11), ai-cowbell (12), pocket_cowbell_dsp_lab (1), pocket_cowbell_dsp_lab_v2 (14) and pocket_cowbell_flutter (104, Apr 2026). I uploaded the 2022-2023 Swift code through GitHub's web UI, so local iOS history before 2025 is thin. A public one-commit repo, bellfiles (2022-11-20), holds a 2022 web build with the same clang/cling/clong/clung recordings.