“There is a person to blame. That person is me.”
The Setup
I built a macOS app called VoiceCatcher—a local-first audio transcription tool that uses Whisper.cpp and runs natively on Apple Silicon. It’s fast. It’s private. It’s delightful.
And then I realized I couldn’t sell it.
See, deep in the guts of my app was node-record-lpcm16, a seemingly harmless Node module that uses SoX behind the scenes. What no one told me (and what I didn’t check soon enough) was that SoX is GPL-licensed, and so are some of the audio codecs it depends on.
That meant one thing: if I shipped this app commercially, I’d be legally obligated to open source the entire codebase.
Cute, huh?
TL;DR
What You’ll Gain:
- A step-by-step escape hatch from GPL dependencies
- A production-safe audio recording solution using AVFoundation
- A Git submodule setup that future-proofs shared native tools
- A realistic plan for integrating Swift binaries into Electron apps
- Bottom Line Value: You don’t need to use GPL-licensed audio tools to build great desktop software.
The Problem: Licensing as an Invisible Time Bomb
If you’ve ever shipped a commercial app, you know the checklist: code quality, features, security, design.
But unless you’ve been burned, you don’t think about licenses—especially not when you’re deep in solo-builder flow.
So I did what any reasonable dev would do: I grabbed node-record-lpcm16, which worked great, and moved on.
What I didn’t realize was:
- It used SoX, which is GPL.
- SoX uses other GPL-licensed libraries (like
libFLAC,libmad,libmp3lame). - Whisper needs audio in a very specific format (16kHz, mono, 16-bit PCM).
- SoX was quietly doing that processing for me.
And now I couldn’t use any of it. Because shipping GPL in a closed-source app? That’s not a gray area. That’s a lawsuit waiting to happen.
The Approach: Native Audio Capture Without the GPL
Here’s the move I made:
- ✅ Ditch SoX and all GPL-related modules
- ✅ Build a native Swift command-line recorder using AVFoundation
- ✅ Output clean, formatted
.wavfiles (16kHz mono PCM) or stream tostdout - ✅ Treat that Swift tool as a Git submodule of my main app
- ✅ Bundle it at build-time into the Electron app and invoke it with
child_process.spawn
This gave me:
- Total control
- MIT licensing
- Better performance
- Cross-project reusability
The Implementation
Project Structure:
voicecatcher/ ← Electron app (Node/React)
├── bin/
│ └── audio-capture/ ← Git submodule (Swift)
Tools Used:
- AVFoundation (Swift) for mic access + WAV writing
- Swift Package Manager for CLI setup
git submoduleto manage native code separatelychild_process.spawn()to invoke it from Node
Prompt I used in Cursor:
Create a Swift Package Manager CLI app that records from the default macOS mic using AVFoundation, outputs 16-bit PCM mono WAV audio at 16kHz, supports --stdout and --file, and is designed to be used as a Git submodule of an Electron app.
Key Commands:
cd bin/audio-capture
swift build -c release
.build/release/audio-capture --file test.wav --duration 5
The Human Reality
I’ll admit it: this one freaked me out.
It wasn’t that I couldn’t fix it. It was that I almost didn’t notice. I could have shipped this, made it to Product Hunt, and then had someone reverse-engineer the app bundle and ask why I was quietly shipping SoX binaries under GPL.
This is the kind of thing that makes you want to go back to being a PM who writes requirements docs in Confluence.
But then I remembered: this is why I’m doing this. Because I don’t want to be the PM asking engineers to fix the licensing issue. I want to be the engineer who saw it coming.
The Business Impact
- 🚫 Removed GPL blockers = legally sellable product
- 🔁 Reusable Swift recorder = asset for future apps
- ✅ Buildable by Cursor = maintainable as a solo dev
- 🧩 Submodule pattern = scalable architecture
This setup not only unblocked my launch—it gave me a native audio tool I can use across multiple products. And it saved me hours of debugging when I moved away from black-box CLI tools.
What This Means for You
If you’re building commercial desktop software—even as a solo dev—you need to:
- Know your transitive licenses
- Replace GPL tools before they go to production
- Treat native tooling as a first-class part of your architecture
This Swift-based submodule approach is a pattern I’ll reuse again and again, and I think you will too.
Try It Yourself
If you want to replicate this setup:
cd your-app/
git submodule add https://github.com/YOUR_USERNAME/audio-capture-tool bin/audio-capture
cd bin/audio-capture
swift build -c release
Use spawn() in Node to invoke it, pipe the output into Whisper.cpp, and boom—you’ve got legal, local transcription.
Next Steps
- Audit your production dependencies for license risk
- If you’re using
node-record-lpcm16or SoX, start replacing it now - Consider AVFoundation for native audio capture on macOS
- Build native tools as submodules for reuse and separation
- Share this post with someone quietly shipping GPL in their product
If this helped you dodge a licensing landmine or sparked an idea for your own app, drop me a message or join my newsletter. Let’s keep building smarter.