# Lid Creak 2.2.0

A small macOS app that makes your MacBook's lid creak as you open and close it. It reads the
lid-angle sensor 20 times a second and plays a recorded creak when the lid actually moves —
loud and quick when you flop it shut, a single quiet creak when you drag it slowly.

It exists because FreeSlapMac 1.1.0 ships a **"Lid Creaking Sound"** switch — the label, the
description, the `_creakingEnabled` preference in its binary, and the unused
`creaking.mp3` in its own bundle — and no code that ever reads the lid sensor. The switch is
dead. This app is the missing half.

```
Lid Creak.app  →  reads the lid-angle sensor  →  plays FreeSlapMac's own creak sample
```

No admin password, no kernel extension, no private API, no network. The sensor is an
unprivileged HID feature report; the sound is read from your own FreeSlapMac installation.

---

## Requirements

| | |
|---|---|
| macOS | 13 or newer |
| CPU | Apple silicon (the shipped binary is arm64) |
| Sound | FreeSlapMac in `/Applications` — the creak sample belongs to its author, so it is **not** redistributed here. The app looks for `FreeSlapMac.app`, `FreeSlapMac Silent.app`, `SlapMac.app` or `slapmac.app`. |

Without FreeSlapMac the app still runs and still reads the sensor, but there is no sound to
play; the menu says **Test creak (no sound file)** so you are not left guessing.

## Install

1. Unzip → you get `Lid Creak.app`.
2. Drag it to `/Applications`.
3. First launch: **right-click → Open** (it is ad-hoc signed, not notarised, so a plain
   double-click from the internet is refused by Gatekeeper). Or, in Terminal:

   ```sh
   xattr -dr com.apple.quarantine "/Applications/Lid Creak.app"
   open "/Applications/Lid Creak.app"
   ```

4. The window opens with the live lid angle, a speed readout and an on/off switch.

## What it does

| control | effect |
|---|---|
| **Creak sound** switch | the on/off switch for the whole feature |
| **Test creak** | plays one at full level — the fastest way to check audio |
| live readout | `Lid: 102° · moving 34 °/s · 12 creaks`, 20×/s |
| **Sensitivity** | **Low 35 · Normal 22 · High 14 °/s** |
| **Start at login** | writes/removes `~/Library/LaunchAgents/com.isaac.lidcreak.plist` |
| **Reveal sound file** | shows the prepared clip in Finder |
| **Open log** | `~/Library/Logs/LidCreak.log` |
| menu bar item | same switch, reachable when the window is closed |

Settings live in `defaults` under `com.isaac.lidcreak` (`enabled`, `threshold`), so they
survive a restart and can be scripted:

```sh
defaults write com.isaac.lidcreak enabled -bool false     # off
defaults write com.isaac.lidcreak threshold -float 35     # Low
```

## How it decides to creak

The sensor is HID usage page `0x20` / usage `0x8A`, feature report 1, a `uint16` little-endian
angle in degrees — sampled at 20 Hz. A creak has to pass three gates:

| gate | rule | what it kills |
|---|---|---|
| travel | ≥ **3°** across a 0.35 s window | 1–2° jitter, knocks, taps |
| speed | measured across a **0.15 s** span, not one sample | a 1° blip reads **6.7 °/s**, not 20 |
| sustain | 2 consecutive qualifying ticks (~100 ms) | single-sample spikes |

Then a 1.4 s retrigger cooldown, so a long drag creaks once instead of machine-gunning.

Playback volume rides the measured speed (0.55 → 1.0). The clip is trimmed to its first 1.8 s
and peak-normalised to −1 dBFS once, into `~/Library/Application Support/LidCreak/creak.caf`.

## Why 2.1.0's sensitivity setting did nothing

2.1.0 measured speed between two consecutive 50 ms samples. The sensor reports **whole
degrees**, so one 1° step in one sample is `1 / 0.05 = 20 °/s` — which beat *every* threshold,
including Low at 15 °/s. The least sensitive setting was already more sensitive than a single
quantisation step. Reported by the user as: *"the sensitivity didnt matter i did low and i angle
changed 1 degree still creaked"*. 2.2.0 changes what is measured (span-averaged speed plus a
travel gate) and rescales the thresholds.

## Tests

The app is built around a pure `Detector` that can be fed a scripted angle timeline, so the
decision logic is testable without moving the machine.

```sh
"Lid Creak.app/Contents/MacOS/LidCreak" --simulate       # 8 scripted lid timelines
"Lid Creak.app/Contents/MacOS/LidCreak" --trigger-test   # 10 boundary assertions
"Lid Creak.app/Contents/MacOS/LidCreak" --selftest       # sensor + audio + system volume
"Lid Creak.app/Contents/MacOS/LidCreak" --once 4         # print a few real angle samples
"Lid Creak.app/Contents/MacOS/LidCreak" --record 10      # log a real sensor trace
```

`--simulate` output on the machine this was built on:

```
1° blip then held still                     fires=0  peak=6.7 °/s  PASS
1° every 300 ms (slow creep)                fires=0  peak=3.3 °/s  PASS
12 °/s opening, High (needs 14)             fires=0  peak=12.0 °/s PASS
3° tap in 150 ms, High (a tap, not a slide) fires=0  peak=20.0 °/s PASS
16 °/s sustained 2 s, High (gentle slide)   fires=2  peak=16.0 °/s PASS
22 °/s opening, Normal                      fires=1  peak=22.0 °/s PASS
80 °/s slam, Low                            fires=1  peak=80.0 °/s PASS
20 °/s opening, Low (needs 35)              fires=0  peak=20.0 °/s PASS
```

`--selftest` exits 2 and prints a warning when the system output is muted or at volume 0 —
the failure that made an earlier build look like it did nothing at all.

## Build from source

```sh
bash src/build.sh          # needs the Xcode command line tools (swiftc)
```

`src/build.sh` generates the icon, compiles `LidCreak.swift` (one file, ~1,160 lines, no Xcode
project), assembles the bundle, ad-hoc signs it, and runs the detector tests. On this machine
the shell runs under Rosetta, so the script prefixes `swiftc` with `arch -arm64`.

## Honest limits

- **The sound is FreeSlapMac's.** Without it installed, there is nothing to play.
- **arm64 only.** No universal binary was built; an Intel Mac needs a rebuild.
- **Not notarised.** Ad-hoc signature; first launch needs right-click → Open, or
  `xattr -dr com.apple.quarantine`.
- **The 1.4 s cooldown is a taste decision**, not a measurement: a slow drag creaks once.
- **Screen-recording permission is not used and not needed** — the app only reads a sensor.
- Verified on one machine (MacBook, macOS 26.6), by driving the sensor and the detector, not
  by listening to every creak.

## Files

```
Lid Creak.app/          prebuilt, ad-hoc signed, arm64
src/LidCreak.swift      the whole app (sensor + detector + audio + UI)
src/mkicon.swift        draws the icon with CoreGraphics
src/Info.plist          bundle id com.isaac.lidcreak, LSUIElement = false
src/build.sh            build + sign + tests
README.md               this file
INSTALL.txt             the two-step version
LICENSE                 MIT
SHA256SUMS.txt          checksums of the files above
```

## Licence

MIT for the code — see `LICENSE`. The creak sample is FreeSlapMac's and is not included.
