# lidcreak — the lid-creaking sound FreeSlapMac forgot to implement

FreeSlapMac (the free version of SlapMac) ships a **"Lid Creaking Sound"** switch that does
nothing. It isn't a permissions problem and it isn't your Mac: the free app's binary contains the
toggle, the description and the `_creakingEnabled` preference — and **no lid-sensor code at all**,
no `creaking.mp3` reference, and no wake/clamshell hooks. The switch is dead UI carried over from
the paid app, which does have a `LidAngleSensor` / `CreakAudioEngine` implementation.

The `creaking.mp3` asset is sitting right there in the bundle, unused.

This is a ~380-line user-level helper that reads the MacBook's hidden lid-angle sensor and plays
that unused asset while the lid moves. The app's own switch becomes the on/off control.

```
lidcreak/
├── lidcreak.py    the daemon (IOHID feature-report read + afplay)
├── install.sh     copies it, writes the LaunchAgent, self-tests it
├── uninstall.sh   removes every trace
├── README.md
└── LICENSE
```

## Requirements

| | |
|---|---|
| Mac | Apple Silicon MacBook (the lid-angle sensor is exposed on macOS 13+; verified on Mac14,2 / M2 Air, macOS 26.6) |
| App | FreeSlapMac installed in `/Applications` (or `~/Applications`) — provides `creaking.mp3` |
| Python | any `python3` (the one from Command Line Tools is enough) |
| Root | **none** — feature-report HID reads are unprivileged |

## Install

```bash
unzip freeslapmac-lidcreak-1.0.1.zip
cd freeslapmac-lidcreak
bash install.sh
```

It prints a step-by-step report and ends with a self-test that reads your real lid angle and plays
one creak. Nothing to approve, no login items to click, no System Settings trip.

If the self-test says `SYSTEM OUTPUT: volume=0 muted=True`, nothing on your Mac can be heard — not
this helper, not music, not anything. Unmute first:

```bash
osascript -e 'set volume output muted false' -e 'set volume output volume 60'
python3 "$HOME/Library/Application Support/FreeSlapLidCreak/lidcreak.py" --prove
```

`--prove` plays the creak three times at rising gain so you can hear exactly what you are
choosing between.

## Verify

```bash
tail -f ~/Library/Logs/lidcreak.log      # tilt the screen: move … CREAK / skip:slow
python3 "$HOME/Library/Application Support/FreeSlapLidCreak/lidcreak.py" --self-test
launchctl list | grep lidcreak           # agent is loaded and alive
```

A real session looks like this — 20 Hz sampling, one creak per movement burst:

```
20:31:03  move 80->85 deg speed=32.9 deg/s CREAK
20:31:03  creak vol=1.82
20:31:03  move 84->80 deg speed=39.6 deg/s skip:retrigger   (one creak per movement)
20:31:13  move 95->94 deg speed=4.6  deg/s skip:slow        (drift, ignored)
```

## Uninstall

```bash
bash uninstall.sh        # from the extracted folder
```

Removes the agent, the helper, and the logs. FreeSlapMac is not touched.

## How it works

The MacBook's lid angle is a HID sensor on **usage page `0x20`, usage `0x8A`**, read as
**feature report 1** (3 bytes: report ID, then the angle as uint16 little-endian — `01 68 00` = 104°).
`lidcreak.py` polls it at 20 Hz through `IOHIDManagerCreate` + `IOHIDDeviceGetReport`, computes
`Δangle / Δt`, and:

- fires when speed ≥ **8 °/s** (so slow drift and typing vibration stay silent),
- holds a **1.4 s** retrigger gap so one lid movement gives one creak,
- sets gain from speed: `clamp(1.0 + speed/40, 1.0, 2.0)` — `creaking.mp3` is mastered about
  30 dB below the tap sounds (mean −30.5 dBFS, peak −6.2 dBFS), so a "reasonable" 0.35 volume was
  inaudible even at full system volume. 2.0 is the loudest setting that does not clip,
- plays with `afplay -t 1.8` (truncated to the creak, not the whole 5.1 s file),
- reopens the device if the lid closes (the sensor disappears on sleep) instead of dying,
- obeys `_creakingEnabled` and nothing else: it creaks with FreeSlapMac open or quit, because the
  app was never the thing playing this sound (1.0.0 required the app to be running, which meant a
  closed app produced silence and no explanation),
- logs *why* a movement did not creak (`skip:slow`, `skip:toggle off`, `skip:retrigger`), and warns
  once when the system output is muted or at zero.

Tuning knobs (no reinstall needed, edit the `ProgramArguments`/constants or pass flags):

```bash
lidcreak.py --watch                 # live angle + speed, to pick your own threshold
lidcreak.py --trigger 5             # more sensitive
lidcreak.py --volume 1.5            # fixed gain (1.0-2.0) instead of speed-scaled
lidcreak.py --prove                 # three creaks, quiet to loud
lidcreak.py --trigger-test          # offline check of the trigger logic, no audio
```

## Honest limitations

- **Closing the lid will not creak.** macOS suspends the machine as the lid reaches ~10°, so the
  sensor is gone before the hinge gets there. The creak fires while the lid *moves* — the same
  behaviour the paid app has. This is physics, not a bug in the helper.
- **It is not a patch of the app.** The toggle in FreeSlapMac is still dead UI; this helper just
  reads it. Uninstalling the helper (or turning the toggle off) restores silence.
- **Audio goes through `afplay`**, i.e. routes wherever system output goes. There is no mixing
  into the app's own audio session — the app never had one for this.
- The sensor exists on Apple Silicon MacBooks; on Intel Macs and desktops `install.sh` says so and
  leaves nothing running.
- FreeSlapMac's slap detection itself is untouched. One quirk worth knowing: if several copies of
  the app's privileged wrapper are alive, a single slap can retrigger more than once. Quitting and
  reopening the app collapses them to one.

## License

MIT — see `LICENSE`. The `creaking.mp3` asset is **not** redistributed here: it is read from your
own FreeSlapMac installation. FreeSlapMac and SlapMac are the work of their respective authors;
this helper is an independent, unofficial companion.

Written by Isaac Chan, October 2026.

## Changelog

**1.0.1** — the version that actually makes a noise. Three faults, all found by reading the log
instead of trusting the code:

1. The daemon only played while FreeSlapMac was open. The app was never the thing playing the
   creak, so that gate only ever produced silence with the app quit — and the log said
   `trigger=no`, which reads like "your lid didn't move enough".
2. Gain. `0.35` on a file mastered at −30.5 dBFS mean is inaudible even at full system volume.
   Now `1.0–2.0`, measured against the file's −6.2 dBFS peak so it cannot clip.
3. No audio diagnostics. A muted Mac and a missing creak looked identical. `--self-test` now
   prints the output volume and mute state, the daemon warns once if the output is muted, and
   each skipped movement is tagged with its reason.

**1.0.0** — first release: HID sensor read, speed trigger, retrigger gap, LaunchAgent, self-test.
