Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .claude/hooks/session-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash
# Canonical Tap House SessionStart hook for Claude Code on the web.
#
# Fresh web-session containers clone the repo bare: no submodules, no
# pre-commit hook installed — so `git commit` runs unformatted and the
# clang-format CI gate fails on code a local clone would have fixed at commit
# time. This hook closes that gap at session start:
#
# 1. init submodules recursively (kernels, SDKs, vendored test harnesses)
# 2. install pre-commit and register the repo's canonical hook
# (.pre-commit-config.yaml — the Tap-wide pinned clang-format)
# 3. warm the pinned clang-format binary so the first commit doesn't
# pay the download (the container snapshot caches it)
#
# Canonical copy: taphouse (distributed by scripts/sync.sh alongside
# .clang-format / .clang-tidy / .pre-commit-config.yaml; drift-guarded where
# present). Web-only; local clones are untouched.
set -euo pipefail

if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
exit 0
fi

cd "$CLAUDE_PROJECT_DIR"

echo "session-start: initializing submodules ..."
git submodule update --init --recursive

echo "session-start: installing pre-commit ..."
if ! python3 -m pre_commit --version >/dev/null 2>&1; then
python3 -m pip install --quiet pre-commit
fi
python3 -m pre_commit install
python3 -m pre_commit install-hooks

echo "session-start: done."
14 changes: 14 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh"
}
]
}
]
}
}
4 changes: 2 additions & 2 deletions .github/workflows/style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ on: [push, pull_request]
jobs:
drift:
# Fails if this repo's .clang-format/.clang-tidy/STYLE.md diverge from TapHouse.
uses: tap/taphouse/.github/workflows/drift-check.yml@v4
uses: tap/taphouse/.github/workflows/drift-check.yml@v5
with:
ref: v4
ref: v5

clang-format:
runs-on: ubuntu-latest
Expand Down
81 changes: 81 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

**TapTools** (this repo, `tap/taptools`) — the portable DSP kernel library behind the
[TapTools-Max](https://github.com/tap/TapTools-Max) package of Max/MSP externals: one
self-contained header per object under `include/taptools/`, namespace `tap::tools`, plain C++20
with the standard library only — no Max, no Min, no frameworks. The AmbiTap / AmbiTap-Max split:
DSP lives and is tested *here*; the Max repo is thin wrappers plus a submodule pin of this repo.
Shared low-level primitives (real FFT, YIN, PSOLA, phase vocoder) live one level further down in
the [DspTap](https://github.com/tap/dsptap) submodule (`submodules/dsptap`, target `tap::dsp`) —
general-purpose signal primitives go there; musical/object-level kernels stay here.

## Layout

- `include/taptools/` — one header per kernel (e.g. `tune.h`, `svf.h`, `grm_pitchaccum.h`);
`stft.h` is the shared overlap-add scaffold for the spectral kernels.
- `submodules/dsptap/` — `tap::dsp` (fft/yin/psola/pvoc + vendored Ooura/CMSIS), pinned.
- `tests/` — Catch2 (FetchContent), pure C++, no Max/min-api/mock kernel; runs via `ctest`.
- `tools/capi/` + `notebooks/` — the verification layer: a C ABI over the same kernel headers the
externals compile, a ctypes bridge (`notebooks/taptools_py.py`), and *executed* verification
notebooks. `tools/` also holds render tools (e.g. `tr808_render`).
- `book/` — *Tools on Tap* (mdbook), the field guide; `bench/` — benchmarks.

## Kernel conventions (load-bearing)

- **Shape:** a class in `tap::tools::<name>` with `prepare(sr)` (all allocation happens here),
per-sample `process(...)`, and setters that are allocation-free and safe while audio runs.
Worst-case geometry is bought at `prepare()` so runtime parameter changes never reallocate.
- **No zippers:** parameters ride per-sample linear ramps or one-pole slews (see `grm_comb.h` /
`grm_pitchaccum.h` for the param_index + ramp house pattern).
- **Provenance is documented.** Faithful ports keep magic constants and cite where they came from;
recreations cite papers/schematics. New DSP is implemented from published literature only —
an IP policy, not a citation habit.
- **Honest limits in the header.** Known failure modes and material contracts (e.g. PSOLA wants
harmonic input) are stated where the next reader will look.

## Style

`STYLE.md` + `.clang-format` + `.clang-tidy` are the canonical Tap House copies (synced from
taphouse; CI drift-checks them — never hand-edit). Run `pre-commit install` once per clone; on
Claude Code web the checked-in SessionStart hook does it automatically. clang-tidy compiles with a
clang front end — treat it as a second compiler, not a linter.

## Build & test

```sh
git submodule update --init --recursive
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure
```

Tests are Catch2 `SCENARIO`s named for the promise they pin. House patterns worth preserving:
**oracle-based measurement** (drive the kernel with synthesized material and measure the *output*
with an independently certified detector — `tap::dsp::yin` — rather than asserting internals; this
is how the tune kernel's 5-cent period-lock bias was caught), and **material contracts** (test
PSOLA-family code with sawtooth, not sine, per its documented contract).

## Verification layer

The notebooks drive the shipping C++ through `tools/capi` via ctypes — never Python
re-implementations. They are committed executed; re-execute when behavior changes. When adding a
kernel that notebooks (or other bindings) should reach, extend `tools/capi/taptools_capi.{h,cpp}`
and `notebooks/taptools_py.py` alongside it.

## The book

`book/` is *Tools on Tap* (mdbook, `src/SUMMARY.md`, `create-missing = false` so SUMMARY entries
must exist). One user-facing chapter per object family plus "The machine, file by file"
appendices. The standing promise: **every performance claim is measured, not remembered** — cite
the executed notebook cell or pinned test that carries each number. Chapter plans live as
`book/PLAN-*.md` drafting records and stay after the chapters ship.

## Release flow

DSP changes land here (or in dsptap, then bump `submodules/dsptap` here); TapTools-Max then bumps
its `submodules/taptools` pin. After a PR merges via rebase/squash, repoint any open consumer pins
at the identical tree on `main` so they stay reachable after branch cleanup.
77 changes: 77 additions & 0 deletions scripts/tidy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# Local mirror of the CI clang-tidy gate (.github/workflows/style.yml): the
# TapHouse .clang-tidy naming + mandatory-braces checks over this project's
# own translation units. Run it before pushing.
#
# WHY THIS EXISTS (and is not a pre-commit hook): the pre-commit hook only
# runs clang-FORMAT, which is cheap and context-free. clang-TIDY needs a
# compile database (a configured CMake build) and compiles every TU to reason
# about types — too slow and stateful for a per-commit hook, so CI keeps it as
# its own gate. This script is the fast local equivalent of that gate.
#
# Usage:
# scripts/tidy.sh # sweep every project TU (full CI mirror)
# scripts/tidy.sh tests/test_foo.cpp … # only the given TU(s) — fast, for a change
#
# Env:
# CLANG_TIDY=clang-tidy-18 # binary to use (default: clang-tidy-18, then clang-tidy)
# TIDY_BUILD=build-tidy # compile-database build dir (kept separate from ./build)
# TIDY_RECONFIGURE=1 # force a cmake re-configure of the compile database
set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$repo_root"

# Prefer clang-tidy-18 — the version the CI gate installs and the .clang-tidy
# checks are validated against; a different major version may disagree.
tidy="${CLANG_TIDY:-}"
if [ -z "$tidy" ]; then
if command -v clang-tidy-18 >/dev/null 2>&1; then
tidy=clang-tidy-18
elif command -v clang-tidy >/dev/null 2>&1; then
tidy=clang-tidy
echo "warning: clang-tidy-18 not found; using '$tidy'. CI pins v18 — results may differ." >&2
else
echo "error: no clang-tidy found. Install clang-tidy-18 (apt) or set CLANG_TIDY=..." >&2
exit 127
fi
fi

build="${TIDY_BUILD:-build-tidy}"

# A compile database is what clang-tidy needs; reuse it across runs unless it is
# missing or a reconfigure is forced. This does not touch your ./build dir.
if [ "${TIDY_RECONFIGURE:-0}" = "1" ] || [ ! -f "$build/compile_commands.json" ]; then
echo "== configuring compile database in $build/ (one-time; reuses cached deps) =="
cmake -B "$build" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON >/dev/null
fi

# File list: the given TUs, or — matching CI exactly — every project TU in the
# database with third_party/ and fetched deps (_deps) excluded.
if [ "$#" -gt 0 ]; then
files=("$@")
else
mapfile -t files < <(python3 -c "
import json
for e in json.load(open('$build/compile_commands.json')):
f = e['file']
if 'third_party' not in f and '_deps' not in f:
print(f)")
fi

echo "== clang-tidy ($tidy) over ${#files[@]} file(s) =="
fail=0
for f in "${files[@]}"; do
out="$("$tidy" -p "$build" "$f" 2>/dev/null || true)"
if printf '%s\n' "$out" | grep -qE "warning:|error:"; then
printf '%s\n' "$out"
fail=1
fi
done

if [ "$fail" -eq 0 ]; then
echo "clang-tidy clean."
else
echo "clang-tidy found violations (above); fix before pushing." >&2
exit 1
fi
Loading