https://github.com/alexyorke/clatterdrive
WebDAV-backed novelty HDD simulator that adds mechanical latency and procedural hard-drive sounds to normal file activity.
https://github.com/alexyorke/clatterdrive
audio-synthesis hard-drive novelty python simulator sound-design webdav
Last synced: 25 days ago
JSON representation
WebDAV-backed novelty HDD simulator that adds mechanical latency and procedural hard-drive sounds to normal file activity.
- Host: GitHub
- URL: https://github.com/alexyorke/clatterdrive
- Owner: alexyorke
- Created: 2026-04-18T06:49:40.000Z (2 months ago)
- Default Branch: master
- Last Pushed: 2026-05-01T06:02:38.000Z (about 2 months ago)
- Last Synced: 2026-05-01T08:10:02.186Z (about 2 months ago)
- Topics: audio-synthesis, hard-drive, novelty, python, simulator, sound-design, webdav
- Language: Python
- Homepage: https://alexyorke.github.io/clatterdrive/
- Size: 13.2 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ClatterDrive
> [!WARNING]
> `ClatterDrive` is a novelty project for entertainment purposes. Contributions are welcome, but this is not a serious or trustworthy HDD simulator, and it should not be treated as accurate for storage, acoustics, or performance work.
`ClatterDrive` makes a normal directory feel and sound more like a mechanical hard drive.
- It exposes a directory over WebDAV.
- It injects HDD-like latency into reads, writes, metadata churn, standby/wake, and write-back behavior.
- It synthesizes drive noise procedurally from the same runtime events.
- It does **not** use prerecorded HDD sound effects.
## Audio Demo
- [Listen in the browser](https://alexyorke.github.io/clatterdrive/)
Checked-in sample renders:
| Scenario | Why it sounds different | File |
| --- | --- | --- |
| Spin-up, idle, park | Desk-coupled startup/body with a final park transient | [spinup-idle-park.wav](samples/spinup-idle-park.wav) |
| Idle, standby, wake | Quieter state change, then wake-up and renewed activity | [idle-standby-wake.wav](samples/idle-standby-wake.wav) |
| Metadata storm | Brighter, busier short/medium seeks on a barer mounting profile | [metadata-storm.wav](samples/metadata-storm.wav) |
## Quick Start
This repo uses `uv` and the committed [uv.lock](uv.lock).
```powershell
uv sync --group dev
uv run python main.py
```
Or:
```powershell
uv run python -m clatterdrive
uv run clatterdrive
```
The explicit backend commands are:
```powershell
uv run clatterdrive serve
uv run clatterdrive profiles --json
uv run clatterdrive doctor --json
```
Default URL:
- `http://127.0.0.1:8080`
## Windows App
The Windows-first desktop path is a native WPF launcher plus a packaged Python backend. It is not Tkinter, Electron, or a browser shell.
Local developer build:
```powershell
scripts\build-windows.ps1
```
Release zip:
```powershell
scripts\package-windows.ps1
```
MSI installer:
```powershell
scripts\build-installer.ps1
```
The packaged launcher lets a non-developer choose the backing folder, port, drive/acoustic profile, audio mode/device, start/stop the backend, open the WebDAV URL, copy the `net use` command, copy the unmount command, and inspect logs.
Installer E2E intentionally refuses to run on an unmarked local host because it installs and uninstalls the app. Run it in GitHub Actions, or inside a disposable Windows VM with `CLATTERDRIVE_INSTALLER_E2E_VM=1`:
```powershell
scripts\test-installer.ps1 -IncludeUiE2E -IncludeMappedDrive
```
## macOS App
The macOS desktop path is a native SwiftUI launcher plus a packaged Python backend. Build and package it on macOS, not from Windows or Linux:
```bash
bash scripts/build-macos.sh
bash scripts/package-macos.sh
```
The release artifacts are architecture-specific DMGs:
- `ClatterDrive-macos-arm64.dmg`
- `ClatterDrive-macos-x64.dmg`
Run macOS checks on a Mac or on GitHub-hosted macOS runners:
```bash
bash scripts/test-macos.sh
bash scripts/test-macos-e2e.sh --include-mount
bash scripts/test-macos-e2e.sh --packaged --from-dmg --include-mount
```
The macOS launcher copies `mount_webdav` and unmount commands instead of auto-mounting. Signing and notarization are conditional in release builds when Apple Developer ID secrets are configured; otherwise DMGs are unsigned internal builds.
See [docs/macos-vm.md](docs/macos-vm.md) for the VM decision. Short version: use GitHub-hosted macOS first; a local macOS VM is only appropriate on Apple hardware.
On an Apple Silicon Mac, the repeatable local VM path is:
```bash
bash scripts/test-macos-tart.sh
```
## Using It
The simulator serves `backing_storage/` by default. Use the WebDAV URL, not the backing directory directly, or you will bypass the latency/audio path.
Windows options:
- open `http://127.0.0.1:8080/` in Explorer as a network location
- upload/download with `curl.exe`
- map it as a WebDAV drive, for example `net use X: \\127.0.0.1@8080\DavWWWRoot /persistent:no`
Windows `curl.exe` example:
```powershell
$demoName = "demo-$([guid]::NewGuid().ToString('N').Substring(0, 8))"
$uploadPath = Join-Path $env:TEMP "clatterdrive-upload.bin"
$downloadPath = Join-Path $env:TEMP "clatterdrive-download.bin"
"hello from clatterdrive" | Set-Content -Path $uploadPath -Encoding ascii
curl.exe -X MKCOL "http://127.0.0.1:8080/$demoName/"
curl.exe -T $uploadPath "http://127.0.0.1:8080/$demoName/file.bin"
curl.exe "http://127.0.0.1:8080/$demoName/file.bin" --output $downloadPath
```
Notes:
- `MKCOL` only works for a directory that does not already exist. If you reuse a name like `demo/`, `405 Method Not Allowed` is expected.
- The `C:\path\to\...` strings were placeholders; replace them with real paths, or use the temp-file example above as-is.
- `curl.exe --output` will fail if the parent directory does not exist.
WSL option:
- use `davfs2` and disable client-side locks for this server
Verified in WSL 2 (Ubuntu 22.04) on this machine:
```bash
sudo apt-get update
sudo apt-get install -y davfs2
mkdir -p ~/.davfs2 ~/mnt/clatterdrive
cat > ~/.davfs2/clatterdrive.conf <<'EOF'
use_locks 0
delay_upload 0
EOF
sudo mount -t davfs http://127.0.0.1:8080/ ~/mnt/clatterdrive -o uid=$(id -u),gid=$(id -g),file_mode=0644,dir_mode=0755,conf=$HOME/.davfs2/clatterdrive.conf
echo "hello from wsl davfs" > /tmp/clatterdrive-src.txt
cp /tmp/clatterdrive-src.txt ~/mnt/clatterdrive/wsl-copy-test.txt
cat ~/mnt/clatterdrive/wsl-copy-test.txt
cmp -s /tmp/clatterdrive-src.txt ~/mnt/clatterdrive/wsl-copy-test.txt && echo matches=true
```
WSL notes:
- `davfs2` will prompt for a username and password; press Enter for both because the local server is anonymous.
- `use_locks 0` is currently required for `davfs2` writes against this server. Without it, `davfs2` tries to lock a not-yet-created path and uploads fail with `Input/output error`.
macOS options:
- Finder: `Go -> Connect to Server...` and enter `http://127.0.0.1:8080/`
- Terminal with the built-in WebDAV client:
```bash
mkdir -p ~/mnt/clatterdrive
mount_webdav http://127.0.0.1:8080/ ~/mnt/clatterdrive
cp /path/to/local-file ~/mnt/clatterdrive/
cat ~/mnt/clatterdrive/local-file
```
macOS note:
- the macOS E2E script verifies the standard built-in `mount_webdav` path on GitHub-hosted macOS runners; this Windows host cannot execute it locally.
If live audio is enabled, directory creation and listing, uploads, downloads, overwrites, deletes, fragmented reads, and wake-from-standby activity all feed the audio model.
## Useful Environment Variables
- `FAKE_HDD_HOST`: bind to a different interface
- `FAKE_HDD_PORT`: use a different port
- `FAKE_HDD_BACKING_DIR`: use a different backing directory
- `FAKE_HDD_AUDIO=off`: disable live audio
- `FAKE_HDD_AUDIO_DEVICE`: pick an explicit output device index/name for PortAudio
- `FAKE_HDD_AUDIO_TEE_PATH`: record rendered output to a WAV, even when live audio is off
- `FAKE_HDD_EVENT_TRACE_PATH`: export a structured storage-event JSON trace on shutdown
- `FAKE_HDD_TRACE_EVENTS=on`: print compact event debug lines to stderr
- `FAKE_HDD_COLD_START=off`: start already ready
- `FAKE_HDD_ASYNC_POWER_ON=off`: disable background startup sequencing
- `FAKE_HDD_DRIVE_PROFILE`: choose a drive preset
- `FAKE_HDD_ACOUSTIC_PROFILE`: choose an installation/acoustic preset
Current drive presets:
- `desktop_7200_internal`
- `archive_5900_internal`
- `enterprise_7200_bare`
- `wd_ultrastar_hc550`
- `seagate_ironwolf_pro_16tb`
- `external_usb_enclosure`
Current acoustic presets:
- `bare_drive_lab`
- `mounted_in_case`
- `external_enclosure`
- `drive_on_desk`
Example:
```powershell
$env:FAKE_HDD_DRIVE_PROFILE = "archive_5900_internal"
$env:FAKE_HDD_ACOUSTIC_PROFILE = "drive_on_desk"
uv run python main.py
```
## Docker
Docker is mainly for the headless WebDAV/latency path. Native host execution is better for live audio.
```powershell
docker compose up --build
```
Or:
```powershell
docker build -t clatterdrive .
docker run --rm -p 8080:8080 -e FAKE_HDD_AUDIO=off -v "${PWD}/backing_storage:/data" clatterdrive
```
Audible container output needs an explicit host-audio bridge; Docker does not expose your speakers automatically. The supported path is a host PulseAudio/PipeWire server that the container can reach via `PULSE_SERVER`.
Example:
```powershell
$env:FAKE_HDD_AUDIO = "live"
$env:PULSE_SERVER = "host.docker.internal"
docker compose up --build
```
If your host audio server requires a specific PortAudio device, also set:
```powershell
$env:FAKE_HDD_AUDIO_DEVICE = "0"
```
If you do not already have a PulseAudio/PipeWire network endpoint on the host, use the native host run instead of Docker for audible playback.
Headless Docker smoke test for WebDAV plus rendered audio artifacts:
```powershell
uv run python -m tools.docker_webdav_audio_smoke
```
This writes temporary artifacts under `.runtime/docker-e2e/`, uploads and downloads through WebDAV, then verifies both the event trace and tee WAV are nonempty.
It also exercises large transfer, many-small-file, fragmented, and large-directory-listing workloads.
## Repo Layout
- [clatterdrive](clatterdrive): packaged application code
- [tools](tools): repo-local generators, profilers, trace exporters, audits, and fitting utilities
- [samples](samples): checked-in demo WAVs
- [docs](docs): GitHub Pages demo assets plus the internal MH tuning lab
- [tests](tests): test suite
Key files:
- [main.py](main.py): thin startup entrypoint
- [clatterdrive/app.py](clatterdrive/app.py): server startup and wiring
- [clatterdrive/webdav/provider.py](clatterdrive/webdav/provider.py): WebDAV interception layer
- [clatterdrive/hdd/latency.py](clatterdrive/hdd/latency.py): HDD timing and power-state model
- [clatterdrive/hardware_priors.py](clatterdrive/hardware_priors.py): source-backed hardware priors and bounded calibration helpers
- [clatterdrive/audio/core.py](clatterdrive/audio/core.py): audio plant and render logic
- [clatterdrive/audio/physics.py](clatterdrive/audio/physics.py): labeled physical-state, plausible-model, and artistic-calibration audio primitives
- [clatterdrive/audio/engine.py](clatterdrive/audio/engine.py): runtime audio shell
## Development
One-command local PR gate:
```powershell
scripts\ci.ps1
```
Backend-only quick gate:
```powershell
scripts\ci.ps1 -PythonOnly -SkipE2E
```
Optional desktop and mapped-drive E2E additions:
```powershell
scripts\ci.ps1 -IncludeUiE2E -IncludeMappedDrive
```
Individual checks:
```powershell
scripts\bootstrap.ps1
scripts\lint.ps1
scripts\test.ps1
scripts\test-e2e.ps1
scripts\test-ui.ps1
```
macOS checks:
```bash
bash scripts/bootstrap-macos.sh
bash scripts/test-macos.sh
bash scripts/test-macos-e2e.sh --include-mount
```
Release-package E2E:
```powershell
scripts\test-release.ps1 -IncludeUiE2E -IncludeMappedDrive
```
Double-click/Command Prompt wrappers are available beside each PowerShell script, for example:
```cmd
scripts\ci.cmd
```
CI runs the same scripts on GitHub Actions. The normal PR workflows cover Linux/Windows Python lint/type/unit tests, backend E2E, WPF launcher build/tests, PyInstaller packaging, packaged smoke, macOS SwiftUI launcher tests, macOS DMG packaging, `mount_webdav` E2E, and Docker smoke.
The nightly/release desktop workflow is for checks that need real Windows desktop and WebClient behavior: extracted-package launcher FlaUI, bundled-backend start/stop, `net use`, mapped-drive write/read/delete, and clean shutdown. That runner can be a small Windows 11 VM or a physical Windows machine. Windows Docker containers are fine for backend/headless smoke, but they are not an accurate replacement for WPF UI automation or WebClient mapped-drive behavior because they do not provide the same interactive desktop and shell integration.
Regenerate the public sample clips:
```powershell
uv run python -m tools.generate_readme_demo_samples
```
Audio traces and audits:
```powershell
uv run python -m tools.trace_audio_scenarios
uv run python -m tools.audit_audio_stack
```
Internal calibration tooling:
```powershell
uv run python -m tools.fit_mh_reference
uv run python -m tools.calibrate_ironwolf_physics
```
Notes:
- `tools.fit_mh_reference` is internal calibration tooling for the MH thrash lab, not part of the normal runtime path.
- It requires a local reference bundle under `.runtime/local_refs/` that is intentionally not tracked in git.
- `tools.calibrate_ironwolf_physics` fits the Seagate IronWolf Pro 16TB source-backed hardware prior and writes its report under `.runtime/`.
## Limits
- This is not a real block device or kernel filesystem.
- Out-of-band edits to the backing tree are only partially reconciled.
- WebDAV locks are in-memory and exist to satisfy clients like the Windows WebClient; they are not persisted across restart.
- The project is physically structured and benchmark-gated, but it is not yet a reference-grade HDD/acoustics model without measured drive constants and transfer functions.
- The audio model is explicitly labeled by tier: physical state covers runtime variables such as spindle phase/RPM and actuator position/velocity; physical model covers normalized mechanics, source routing, modal radiation, and gain staging; artistic calibration is kept as an audit tier for any future nonphysical exception.