← Build log

How I Replaced a GPL Time Bomb with a Native Swift Submodule

“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:

  1. ✅ Ditch SoX and all GPL-related modules
  2. ✅ Build a native Swift command-line recorder using AVFoundation
  3. ✅ Output clean, formatted .wav files (16kHz mono PCM) or stream to stdout
  4. ✅ Treat that Swift tool as a Git submodule of my main app
  5. ✅ 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 submodule to manage native code separately
  • child_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

  1. Audit your production dependencies for license risk
  2. If you’re using node-record-lpcm16 or SoX, start replacing it now
  3. Consider AVFoundation for native audio capture on macOS
  4. Build native tools as submodules for reuse and separation
  5. 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.