Real-time audio & RF spectrum analyzer for Android, built with Jetpack Compose and Kotlin Canvas.
Live waveform · 3D waterfall · AI signal classification · RTL-SDR remote
- 7 Visualization Modes
- Waveform, Spectrum, 3D Waterfall, SDR Waterfall, IQ Constellation, Radar Spectrum, Phase Space
- Interactive Spectrum
- Pinch-to-zoom and pan frequency axis with reset button and live range readout
- Frequency / dB grid overlays for precise measurement
- AI Signal Analysis
- One-tap classification of peak spectral features via Google Gemini
- RTL-SDR Remote
- TCP connection to rtl_tcp for real-time RF monitoring
- Engine Tuning
- Adjustable gain, sensitivity, color themes, and audio source
- On-Device Tone Generator
- Self-test with sine, square, sawtooth, or noise — no microphone permission required
- Dark Scientific UI
- Cyan, Ocean, Fire, and Cyberpunk themes
AudioRecord → SpectrumProcessor → FFT → StateFlow → Compose Canvas
↓
Gemini AI Service
| Layer | Responsibility |
|---|---|
AudioAnalyzerViewModel |
Audio capture, state coordination, zoom/pan logic |
dsp/SpectrumProcessor |
Windowing, FFT, dB conversion, peak-hold, waterfall |
dsp/FFT |
In-place Cooley-Tukey transform |
ui/visualizations |
7 Canvas-based composables |
ai/AIService |
OkHttp-based Gemini API client |
screens/* |
Monitor, Capture, Engine, Remote tabs |
-
Clone the repo
git clone https://github.com/Jacobcdsmith/wave-analyzer.git cd wave-analyzer -
Create environment files
echo "sdk.dir=C:\\\\Android\\\\Sdk" > local.properties # or your SDK path echo "GEMINI_API_KEY=YOUR_KEY_HERE" > .env
-
Generate the Gradle wrapper (not included in repo)
gradle wrapper --gradle-version=9.3.1
-
Build & test
./gradlew assembleDebug ./gradlew test
The debug signing config is intentionally empty per project setup. No
google-services.jsonis required because the build usesgoogleServices.missing.passthrough=true.
- Unit tests for the DSP core (
FFTTest,SpectrumProcessorTest) - Robolectric + Roborazzi screenshot tests for UI baselines
- GitHub Actions CI runs
./gradlew testand./gradlew assembleDebugon every push/PR
./gradlew test # all unit tests
./gradlew recordRoborazziDebug # capture UI baselines
./gradlew verifyRoborazziDebug # verify against baselinesPush a version tag to trigger the release workflow:
git tag -a v1.0.0 -m "First release"
git push origin v1.0.0The GitHub Action will build a signed release APK and attach it to a new GitHub Release.
Go to Settings > Secrets and variables > Actions and add:
| Secret | How to generate |
|---|---|
SIGNING_KEY_BASE64 |
base64 -i my-upload-key.jks |
STORE_PASSWORD |
Your keystore password |
KEY_PASSWORD |
Your key password |
The release signing config expects the key alias to be upload.
- Zoom & pan frequency axis
- Frequency / dB grid overlays
- Extract testable
SpectrumProcessor - CI pipeline
- On-device tone generator
- Export captures as PNG / CSV
- Measurement cursors
- Max-hold & average trace overlays
- On-device tone generator for self-test
- Offline TFLite signal classifier
This is a personal project, but PRs and ideas are welcome. Open an issue before major changes.
MIT — see LICENSE for details.
Made with 💙 and a lot of FFTs.

