A macOS menubar app that listens for physical taps and knocks on your MacBook and plays funny sounds.
MacTapper uses the built-in microphone to detect sudden physical impacts on the MacBook body (knocks, taps, desk bangs). When a sharp acoustic spike is detected it plays a random sound from one of three categories: Vine Boom, Farts, or Memes.
Mic input
└── AVAudioEngine tap (441 frames @ 44100 Hz = ~100 windows/sec)
└── RMS computed per window
└── delta = |rms - prevRMS|
└── delta > threshold AND cooldown (350ms) elapsed?
└── onTapDetected() → play sound + flash icon
The key insight is that a physical knock produces a brief, sharp acoustic transient — a sudden spike in RMS that drops immediately back to the noise floor. Comparing consecutive RMS windows (delta) catches this spike reliably without reacting to sustained sounds like speech or music.
CMMotionManager is API_UNAVAILABLE(macos) — Apple's motion framework is iOS/watchOS only. The Apple Silicon MacBook's hardware accelerometer (AppleSPUHIDInterface) exists but requires private Apple entitlements to receive events. Microphone analysis is the practical alternative.
NSApplication— app lifecycle,LSUIElement = truehides it from the DockNSStatusBar/NSStatusItem— menubar icon and menuNSMenu/NSMenuItem— full dropdown menu with actions, state, represented objectsNSAlert— mic permission denied warning dialogNSWorkspace— opens System Settings to the Microphone privacy paneNSSound— fallback system sound playback if a sound file fails to loadTimer— icon flash reset (150 ms) and watchdog restart (every 2 s)
AVAudioEngine— low-latency audio graph, used here purely for mic inputAVAudioEngine.inputNode— the microphone input nodeinstallTap(onBus:bufferSize:format:)— installs a real-time callback that fires every 441 frames with raw PCM float dataAVAudioPCMBuffer/floatChannelData— raw float samples for RMS calculationAVAudioPlayer— plays sound files from the resource bundle; multiple instances kept alive inactivePlayersarray to prevent deallocation mid-playbackAVCaptureDevice.requestAccess(for: .audio)— requests microphone permission at runtime
AudioObjectGetPropertyData— enumerates all audio devices viakAudioHardwarePropertyDeviceskAudioObjectPropertyName— reads each device's display name to find the built-in microphone (name contains "Microphone")AudioUnitSetPropertywithkAudioOutputUnitProperty_CurrentDevice— pinsAVAudioEngine's input node directly to the built-in mic'sAudioDeviceID, preventing WhatsApp / Zoom / other apps from stealing the input and silencing the engine
Package.swiftwith.process("Sounds")resource rule — SPM copies the entireSounds/directory intoMacTapper_MacTapper.bundleat build time, flat (no subdirectory)Bundle.module— the generated accessor for the SPM resource bundle; sound files are looked up withBundle.module.url(forResource:withExtension:)(no subdirectory, because SPM flattens resources)
MacTapper/
├── Package.swift # SPM manifest — macOS 12+, AVFoundation + AppKit linked
├── Info.plist # LSUIElement=true, NSMicrophoneUsageDescription
├── run.sh # Build → bundle → kill old instance → open
└── Sources/MacTapper/
├── AppDelegate.swift # Entire app (menubar, mic engine, sound playback)
└── Sounds/ # All sound assets (copied flat into resource bundle)
├── vine_boom.mp3
├── vine_boom2.aiff
├── fart1–3.aiff
├── wet_fart.aiff
├── oof.aiff
├── bruh.aiff
├── wow.aiff
├── no_no_no.aiff
├── among_us.aiff
└── fahhhhh.aiff
| Category | Files |
|---|---|
| Vine Boom | vine_boom, vine_boom2 |
| Farts | fart1, fart2, fart3, wet_fart |
| Memes | oof, bruh, among_us, no_no_no, wow, fahhhhh |
Each tap picks a random enabled category, then a random file from that category.
- Tap counter — increments on every detected tap
- Mic status — shows
listening,reconnecting,denied, orerror - Mute toggle — silences playback without stopping detection; icon switches to muted state
- Per-category toggles — enable/disable Vine Boom, Farts, Memes independently
- Sensitivity — Low / Medium / High, maps to RMS delta thresholds:
- High:
0.005(catches light finger taps) - Medium:
0.010(solid knock) - Low:
0.018(hard knock only)
- High:
- Icon flash — icon switches to 💥 on tap, reverts after 150 ms
Problem: When another app (WhatsApp, Zoom, FaceTime) holds the microphone exclusively, AVAudioEngine stops receiving audio and goes silent.
Solutions implemented:
- Built-in mic pinning — CoreAudio
kAudioOutputUnitProperty_CurrentDeviceforces the engine to use the physical built-in microphone device ID directly, bypassing the system's default input selection - Watchdog timer — fires every 2 seconds; if
audioEngine.isRunningis false, tears down and restarts the engine - Config change handler —
AVAudioEngineConfigurationChangenotification triggers a rebuild of the engine when audio devices are added or removed (e.g. plugging in headphones)
Requires macOS 12+, Xcode Command Line Tools, and Swift 5.7+.
bash run.shThis script:
- Runs
swift build - Creates
MacTapper.app/Contents/MacOS|Resourcesstructure - Copies the binary,
Info.plist, and the SPM resource bundle (MacTapper_MacTapper.bundle) into the app bundle - Kills any existing MacTapper instance
- Runs
open MacTapper.app
The app must be launched as a .app bundle via open. Running the raw binary directly bypasses LSUIElement and NSStatusBar silently produces nothing.
Grant microphone access when prompted (or via System Settings → Privacy & Security → Microphone).