# Making TapCap
## I can haz voice clip?
For decades (decades I tell you!) I've wanted a super simple voice note app for my phone. I had pretty straightforward but rigid requirements:
- As soon as it launches, it starts recording
- One tap to stop recording and put the audio clip somewhere I can find it
- No fluff like built-in file management, transcription, cloud recording, ads, etc.
Obsidian and Samsung Voice Notes, as well as many other apps, do have voice recording. However, they always miss my first requirement: I have to launch the app and tap around before I get to the point of being able to record. And by the time I get there, the thought is gone.
## From nuh-uh to yay!
I've done a lot of hobby programming in the past (particularly Visual Basic back in the day and Xojo more recently) but every time I started with Android coding it just quickly became a soup of Intents and Manifests and Gradle and MVVM and the fun just drained out of it, so I never did end up writing something for myself.
Enter Codex.
I've been using Claude Code a lot at work lately (as a UXR!) and found that it's stupidly easy to create things. At home, I already had a ChatGPT subscription, so I decided to try Codex. And within a few hours of playing (if even that long), I had the app I've always wanted: TapCap.
![[tapcap-make.jpg|250]]
↑ That's TapCap while it's running.
I launch the app. It starts recording. The microphone pulses as it records so I know it's working. I tap anywhere on the screen and it saves and exits, or I tap Cancel.
## Make yer own!
If you want to make your own Voice Note app, just paste this into your Codex or Claude Code from an empty working directory:
```text
Create a complete native Android app named Voice Note.
Goal:
Build a minimal Android 10+ app for quick audio capture. When launched, it immediately records from the microphone. Tapping the main screen ends the recording, saves it, shows a toast, and exits.
Project requirements:
- Create a standard Gradle Android application project.
- Use Java or Kotlin; prefer a single Activity and no unnecessary dependencies.
- Minimum SDK: 29.
- Target/compile a current Android SDK available in the environment.
- App label: Voice Note.
- Package name can be com.example.voicenote unless there is an existing convention.
- Use a programmatic native Android UI. Do not use Compose.
Permissions:
- Request android.permission.RECORD_AUDIO at runtime.
- If permission is already granted, start recording immediately on launch.
- If permission is denied, show a short message on the screen and do not record.
Recording:
- Use MediaRecorder.
- Audio source: MediaRecorder.AudioSource.MIC.
- Output format: MediaRecorder.OutputFormat.MPEG_4.
- Audio encoder: MediaRecorder.AudioEncoder.AAC.
- Audio bit rate: 128000.
- Audio sample rate: 44100.
- Record first to a temporary .mp4 file in the app cache directory.
- Poll MediaRecorder.getMaxAmplitude() every 50ms while recording.
- Handle RuntimeException around MediaRecorder.stop(), because stop can fail for invalid or very short recordings.
- Stop polling amplitude before releasing the recorder.
- Always release MediaRecorder when finished.
Saving:
- On tap-to-end, stop recording and save the temp file to DCIM/Camera.
- Use MediaStore scoped storage, not direct filesystem writes to /sdcard.
- Use MediaStore.Video.Media and MIME type "video/mp4", because the destination is DCIM/Camera even though the MP4 is audio-only.
- Use MediaStore.MediaColumns.RELATIVE_PATH = Environment.DIRECTORY_DCIM + "/Camera".
- Use IS_PENDING = 1 while writing, then update IS_PENDING = 0 after copying bytes.
- Filename format must be yyyyMMdd_HHmmss_audio_only.mp4, for example 20260426_111018_audio_only.mp4.
- After a successful save, delete the temp file, show a toast saying it saved to DCIM/Camera, then finish/exit the activity.
Cancel:
- Add a Cancel button centered horizontally near the bottom.
- It should look like a native clean text/button control, not a crude hard rectangle.
- Pressing Cancel stops recording if needed, releases the recorder, deletes the temp file, shows a short cancellation toast, and exits without saving.
- Pressing Cancel must not trigger the main screen tap-to-save handler.
Background behavior:
- If the app is backgrounded while recording, schedule an automatic save after 30 seconds.
- If the app returns to foreground before 30 seconds, cancel that pending save.
- If Android destroys the Activity while still recording, attempt to save rather than silently discard the recording.
UI:
- Main screen background: black.
- Show a vertically centered cluster containing:
- an in-app microphone icon image,
- title text "Voice Note",
- caption text "Recording. Tap to end."
- The main root view should handle tap-to-end.
- Keep the phone status bar visible while the app is running. Use black status/navigation bars with light icons.
Animation:
- The in-app microphone icon itself should scale with live audio amplitude.
- Quiet/minimum volume scale: 1.0x.
- Maximum volume scale: 2.5x.
- Use this smoothing behavior or equivalent:
normalized = min(1, amplitude / 32767)
nextLevel = min(1, normalized * 3.4)
iconLevel += (nextLevel - iconLevel) * 0.32
scale = 1 + (2.5 - 1) * iconLevel
- Apply scale to the ImageView with setScaleX/setScaleY.
Icons/assets:
- If the working directory contains "app icon.png", use it as the launcher icon source.
- Generate launcher PNGs:
- mipmap-mdpi/ic_launcher.png: 48x48
- mipmap-hdpi/ic_launcher.png: 72x72
- mipmap-xhdpi/ic_launcher.png: 96x96
- mipmap-xxhdpi/ic_launcher.png: 144x144
- mipmap-xxxhdpi/ic_launcher.png: 192x192
- If the working directory contains "app icon-empty.png", use it as the in-app icon and copy/scale it to res/drawable-nodpi/app_icon_empty.png at 512x512.
- If those image files are not present, create simple placeholder assets so the app still builds, and clearly state where the real PNGs should be placed.
Implementation notes:
- Prefer a single MainActivity.
- Prefer programmatic views: FrameLayout root, LinearLayout main cluster, ImageView icon, TextView title, TextView caption, Button cancel.
- Root should be clickable and call stop-and-save.
- Cancel button should consume its own click and call cancel-without-saving.
- Avoid adding XML layouts unless there is a strong reason.
- Add a README with build/install instructions.
Build verification:
- Build the debug APK.
- If Gradle is available, run ./gradlew assembleDebug.
- On macOS with Android Studio installed, use:
JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" ./gradlew assembleDebug
- Report the APK path, typically app/build/outputs/apk/debug/app-debug.apk.
Common pitfalls to avoid:
- Do not write directly to /sdcard/DCIM/Camera on Android 10+.
- Do not forget to clear IS_PENDING after writing through MediaStore.
- Do not leave amplitude polling callbacks running after recorder release.
- Do not save on Cancel.
- Do not discard a backgrounded recording unless Cancel was pressed or recording never started correctly.
- If the installed launcher icon/name looks stale on a phone, instruct the user to uninstall and reinstall because launchers cache app metadata.
```