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:
uses: tap/taphouse/.github/workflows/drift-check.yml@v3
uses: tap/taphouse/.github/workflows/drift-check.yml@v5
with:
ref: v3
ref: v5

clang-tidy:
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ compile_commands.json
CMakeUserPresets.json
.vscode/
.idea/
.claude/
.claude/*
!.claude/settings.json
!.claude/hooks/
build_capi/
87 changes: 87 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# CLAUDE.md

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

## What this is

**DspTap** — shared DSP primitives for the Tap family of audio libraries. Header-only, plain
portable C++20 (standard library only, no frameworks), consumed as a git submodule by the
individual libraries (TapTools pins it as `submodules/dsptap`; the AmbiTap/MuTap lineage is where
the FFT came from). Four primitives today: the real FFT (`fft.h`), the YIN pitch detector
(`yin.h`), and two pitch shifters (`psola.h`, `pvoc.h`). See `README.md` for each primitive's
contract summary.

## The design discipline (load-bearing — every primitive follows it)

- **Fixed numeric contracts.** Each header documents its packing, conventions, normalization, and
latency as *numbers*, and the tests pin them. Changing a documented contract point is a breaking
change for every consumer.
- **Double is the golden model; float32 is the embedded profile.** `basic_*<Sample>` templates
with `using x = basic_x<double>` / `x32 = basic_x<float>` aliases. The double path never changes
for speed; accelerated float backends (vDSP, CMSIS-Helium for the FFT) must re-present the
*exact* golden contract, so the double test battery stays a valid oracle. Cross-precision
agreement is pinned by tests.
- **Real-time safe by construction.** Geometry fixed at construction, every buffer allocated
there; processing is `noexcept` and allocation-free. Numerically fragile recursions (e.g. the
order-48 Levinson–Durbin inside `pvoc`) run in double even in the float profile — documented
where it happens.
- **Hot loops stay plain.** The expensive inner loops (YIN's difference function above all) are
written as contiguous, branch-light scalar code on purpose: they are the designated seams for
future MVE/HVX backends behind the same contract.
- **Implement from published literature only.** This is an IP policy, not a citation habit: cite
the paper (YIN: de Cheveigné & Kawahara 2002; peak locking: Laroche–Dolson; LPC: standard
source-filter literature) and never reverse-engineer a shipping product's behavior.
- **Document honest limits.** When an algorithm has a known failure mode (PSOLA thinning pure
tones, YIN's first-dip rule on missing-fundamental synthetics), the header says so and a test
*pins* the behavior rather than papering over it.

## Style

`STYLE.md` is the shared Tap house style; `.clang-format` and `.clang-tidy` enforce it and CI runs
both (plus a drift check that the config files match the canonical taphouse copies — never edit
them locally). Run `pre-commit install` once per clone so commits are formatted automatically; on
Claude Code web the checked-in SessionStart hook (`.claude/hooks/session-start.sh`) does this for
you at session start. clang-tidy compiles with a *clang* front end — code that GCC accepts can
still fail there, so treat the tidy job as a second compiler.

## Build & test

```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure
```

Tests are GoogleTest (FetchContent), typed over `float`/`double` (`TYPED_TEST_SUITE`). Patterns to
preserve: contract tests named for the promise they pin (`RoundTripReproducesInput`,
`PureToneOctaveUpThinsOut`); the certified-geometry parity tests for accelerated FFT backends; and
`tap::dsp::yin` (certified by its own battery) used as the pitch oracle for the shifters. The
CMSIS-Helium backend is compile-verified under `cmake/arm-cortex-m55-mps3.cmake`; its runtime
parity is the consuming library's job.

## Verification layer (C ABI + notebooks)

`tools/capi/` exposes the primitives through a minimal C ABI (`cmake -B build_capi -S tools/capi`);
`notebooks/dsptap_py.py` is the ctypes bridge (builds `build_capi/` on first import), and
`notebooks/pitchshift.ipynb` is committed *executed* — it measures the shipping C++, not a Python
re-implementation (the one deliberate exception is a labeled naive-phase-vocoder strawman). If you
change a primitive's behavior, re-execute the notebook; if you add a primitive that notebooks
should measure, extend the capi + bridge alongside it.

## Adding a primitive (checklist)

1. `include/tap/dsp/<name>.h` — `basic_<name><Sample>` + aliases, full contract docstring
(geometry, conventions, latency, limits), MIT SPDX banner.
2. `tests/test_<name>.cpp` — typed battery pinning every contract point, plus float/double
cross-precision agreement; add to `tests/CMakeLists.txt`.
3. `README.md` section (and bump the primitive count in the intro line).
4. capi + `dsptap_py` exposure if the notebooks need it.
5. Vendored code goes under `third_party/` with license retained and a `NOTICE.md` entry — and is
excluded from formatting.

## Consumers & release flow

Changes land here first, then consumers bump their submodule pin (TapTools pins this repo;
TapTools-Max picks it up transitively through TapTools). 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.
Loading