Skip to content

Commit bda9861

Browse files
authored
Update news and add agentic guidance (#86)
1 parent f82c6f0 commit bda9861

3 files changed

Lines changed: 208 additions & 0 deletions

File tree

‎AGENTS.md‎

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to AI coding agents when working with code in this repository.
4+
5+
## What this is
6+
7+
python-rtmixer (`rtmixer` on PyPI) provides reliable low-latency audio playback and recording
8+
from Python. It builds on [sounddevice](https://python-sounddevice.readthedocs.io/) and
9+
PortAudio, but the audio callback is written in C (compiled with CFFI) so that it never enters
10+
the Python interpreter and is not affected by the GIL or garbage collection. It is a small
11+
project maintained by volunteers: one Python module, one C file, and one C header.
12+
13+
## Working with humans (read first)
14+
15+
Every change here is reviewed by a volunteer maintainer and then maintained by humans for years.
16+
The human you are working with is the author of anything that leaves their machine, and they are
17+
the one who talks to the maintainers.
18+
19+
- **The human is the sole author.** Do not add `Co-Authored-By:` trailers for Claude / Copilot /
20+
Cursor / any other tool, and do not make a bot the commit author. The human opening the PR
21+
takes full responsibility for it and must be able to explain and defend every line on request.
22+
Disclose which tools were used, and for what, in the PR description instead.
23+
- **Do not speak for the human in public.** Do not post agent-written text to issues, PRs, review
24+
comments, or discussions, and do not open, comment on, or close them on the human's behalf.
25+
Draft for the human if they ask; they decide whether to post and rewrite it in their own words.
26+
- **Do not commit or push unless asked**, and never force-push, rewrite published history, or
27+
create tags or releases.
28+
- **Agree on the design before writing code.** For anything beyond a local fix, describe the
29+
approach to the human in a few sentences and confirm it is the smallest one that works. If the
30+
change touches the C callback or the Python/C memory-ownership contract (see Architecture), say
31+
so explicitly; those are the changes a maintainer will look at hardest.
32+
- **Report what actually happened.** If a build failed, an example was not run, or something
33+
could not be tested without audio hardware, say so plainly rather than implying it works.
34+
35+
## Keep code and tests brief
36+
37+
A big diff is a cost, not an accomplishment. The whole library is about 850 lines of Python and C; a change that
38+
adds a large fraction of that needs a very good reason.
39+
40+
- **Stop and re-plan at roughly 50 new lines of implementation.** Growth past that, or reaching
41+
for a new module, a new dependency, or a new action type, is a signal that the design is too
42+
elaborate. Stop, tell the human, and offer the smaller version.
43+
- **YAGNI.** No retry logic, configuration knobs, platform-specific fallbacks, extension points,
44+
or "robustness" that nothing in the change needs. New public API (a method, a keyword argument,
45+
a new `actiontype`) needs a demonstrated user need, because it is documented and supported
46+
forever. Hard-code the single behavior that is wanted and add the option when someone asks.
47+
- **DRY.** Reuse what exists: `_Base._enqueue`, `_check_channels`, the `sounddevice` stream
48+
machinery, `pa_ringbuffer`. When the four `play_*`/`record_*` methods or the C action handlers
49+
need the same new logic, factor it into one private helper rather than editing four copies.
50+
- **Implement at the highest layer that exists.** Behavior shared by `Mixer`, `Recorder`, and
51+
`MixerAndRecorder` belongs in `_Base`.
52+
- **Keep tests and verification just as brief.** There is currently no unit test suite (CI only
53+
builds wheels and checks that `import rtmixer` works). Do not add a test framework, test
54+
directory, mock audio backend, or CI job without asking the human first. When a test is
55+
warranted, prefer a few compact assertions over a large harness, and prefer extending an
56+
existing example in `examples/` over writing a new script. Throwaway verification scripts go in
57+
a scratch directory, not the repo.
58+
- **Do not add or bump dependencies** (runtime or build) without asking first.
59+
60+
## Common commands
61+
62+
Development install (the `portaudio` git submodule is needed to compile the extension):
63+
```bash
64+
git submodule update --init
65+
python -m pip install -e .
66+
```
67+
68+
After changing `src/rtmixer.c`, `src/rtmixer.h`, or `rtmixer_build.py`, rebuild the extension
69+
(rerun the editable install; the compiled `_rtmixer*.so` lands in `src/`). A quick smoke test:
70+
```bash
71+
python -c "import rtmixer; print(rtmixer.__version__)"
72+
python examples/fetch_stats.py # needs a working audio device
73+
```
74+
75+
Docs (Sphinx, built on CircleCI and Read the Docs):
76+
```bash
77+
python -m pip install -r doc/requirements.txt
78+
python -m sphinx doc _build
79+
python -m sphinx doc _build -b linkcheck
80+
```
81+
82+
Wheels are built with cibuildwheel (`.github/workflows/build_wheels.yml`, configured in
83+
`pyproject.toml`) as abi3 wheels for CPython >= 3.10 plus PyPy.
84+
85+
Before handing work back, at minimum rebuild the extension if C code changed, check that
86+
`import rtmixer` works, run whichever example exercises the changed code if an audio device is
87+
available (and say so if not), and build the docs if docstrings or `doc/` changed.
88+
89+
## Boundaries
90+
91+
Ask the human first before:
92+
- adding or bumping a dependency, or changing the minimum Python version;
93+
- editing CI / release configuration (`.github/`, `.circleci/`, `.readthedocs.yml`, `tools/`,
94+
`[tool.cibuildwheel]` in `pyproject.toml`) or the `portaudio` submodule pointer;
95+
- adding, renaming, or removing public API, including new action types or keyword arguments;
96+
- changing the version number or editing existing `NEWS.rst` entries for released versions.
97+
98+
Never:
99+
- add AI co-authorship trailers or post agent-written text to issues or PRs (see above);
100+
- call into Python, allocate or free memory, take locks, or do I/O in the C audio callback;
101+
- silence compiler warnings or weaken a `CALLBACK_ASSERT` instead of fixing the cause;
102+
- commit build products (`_rtmixer*.so`, `*.egg-info`, `build/`, `_build/`) or audio files;
103+
- adapt code whose license is not compatible with this project's MIT license or cannot be
104+
determined.
105+
106+
## Architecture
107+
108+
### Files
109+
- `src/rtmixer.py`: the public Python API. `_Base` (a subclass of `sounddevice._StreamBase`)
110+
owns the queues and state; `Mixer` (output), `Recorder` (input), and `MixerAndRecorder`
111+
(duplex) add `play_buffer`/`play_ringbuffer` and `record_buffer`/`record_ringbuffer`.
112+
`RingBuffer` is re-exported from `pa_ringbuffer`. Upper-case constants from the C library
113+
(`PLAY_BUFFER`, `CANCEL`, ...) are copied into the module namespace at import.
114+
- `src/rtmixer.h`: declarations shared by C and CFFI (`enum actiontype`, `struct action`,
115+
`struct stats`, `struct state`). It is parsed by `ffibuilder.cdef()`, so it must stay within
116+
what CFFI's parser accepts (no `#include`, no macros beyond what is already there).
117+
- `src/rtmixer.c`: the PortAudio callback and its helpers.
118+
- `rtmixer_build.py`: the CFFI build script that produces the `_rtmixer` extension, compiling
119+
`rtmixer.c` together with `pa_ringbuffer.c` from the `portaudio` submodule. PortAudio itself
120+
is not linked; the callback is handed to sounddevice's already-loaded PortAudio.
121+
- `doc/`: Sphinx docs. `doc/api.rst` uses `literalinclude` on ranges of `src/rtmixer.h`, so
122+
editing `struct action` or `struct stats` changes the rendered docs. `doc/fake_rtmixer.py` and
123+
`doc/fake_sounddevice.py` stand in for the compiled modules during the docs build.
124+
- `examples/`: runnable scripts; these double as the manual test suite.
125+
126+
### Actions and queues
127+
Each command (`play_buffer`, `cancel`, `fetch_and_reset_stats`, ...) allocates a
128+
`struct action` with `ffi.new()` in Python and writes the pointer into `action_q`, a lock-free
129+
PortAudio ring buffer. Once per audio block the callback reads new actions into a singly linked
130+
list (`CANCEL` actions go to the front, everything else to the back), processes each one against
131+
the block's timing info (`requested_time`, `allow_belated` encoded via `actual_time`), and moves
132+
finished actions to `done_actions`, which are flushed into `result_q` as space allows. Python
133+
drains `result_q` in `_drain_result_q()` (called from `_enqueue()` and the `actions` property)
134+
and drops its references.
135+
136+
### Memory ownership
137+
The C callback only ever holds raw pointers; Python owns all memory. `_Base._actions` maps each
138+
in-flight action to the object it depends on (`keep_alive`: the audio buffer, the ring buffer,
139+
or the action being cancelled), and an entry is removed only after the callback has handed the
140+
action back through `result_q`. `_Base.__del__` aborts and closes the stream before that memory
141+
can be freed. Any change that adds a pointer to `struct action` or `struct state` must keep this
142+
contract, and is the most likely place for a use-after-free or crash.
143+
144+
### Real-time constraints
145+
The callback runs on PortAudio's audio thread. It must be bounded-time and must not allocate,
146+
lock, block, or call into Python. `CALLBACK_ASSERT` prints and returns `paAbort` on failure
147+
(debug builds only; `NDEBUG` disables it).
148+
149+
## Changelog (`NEWS.rst`)
150+
151+
Add a `NEWS.rst` entry for changes that users would notice: new or changed API or behavior, bug
152+
fixes in the callback or Python API, new or dropped Python versions or wheel platforms, and new
153+
or changed dependencies. Skip it for refactoring, docs-only edits, examples, and CI or tooling
154+
changes that do not change what gets shipped. Put the entry at the top of the file under a
155+
`Version x.y.z -- unreleased` heading (create one with the next patch version if it is missing).
156+
The maintainer fills in the date and the `PyPI__` / `diff__` links at release time. Use the same
157+
style as the existing entries: one short bullet per change, starting with a verb ("Add", "Fix",
158+
"Drop"), describing the effect on users rather than the implementation.
159+
160+
## Code conventions
161+
162+
- Python: PEP 8, lines at most 79 characters (see `.editorconfig`); single quotes are used
163+
throughout. Docstrings are reStructuredText rendered by Sphinx with `default_role = 'any'`, so
164+
single backticks cross-reference (`` `wait()` ``, `` `sounddevice.Stream` ``) and
165+
`nitpicky = True` means unresolved references break the docs build.
166+
- C: C99, two-space indentation, braces on their own lines, leading commas on continued
167+
parameter lists, `//` comments, as in `src/rtmixer.c`.
168+
- Private names start with an underscore; imported third-party modules are aliased privately
169+
(`import sounddevice as _sd`) so they do not show up in the public namespace.
170+
- Match the surrounding style rather than reformatting existing code; there is no
171+
auto-formatter, and unrelated whitespace changes make review harder.
172+
- Code adapted from an outside source must be under an MIT-compatible license (MIT, BSD, ISC,
173+
Apache-2.0, public domain, ...); attribute it in a comment directly above the adapted code,
174+
naming the source and its license, as `setup.py` and `.circleci/config.yml` already do.

‎CLAUDE.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# CLAUDE.md
2+
3+
See @AGENTS.md for guidance on working with this repository.

‎NEWS.rst‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,34 @@
1+
Version 0.1.7 -- 2024-07-02 -- PyPI__ -- diff__
2+
* Restore the ``x86_64`` Linux wheels that were missing from 0.1.6
3+
4+
__ https://pypi.org/project/rtmixer/0.1.7/
5+
__ https://github.com/spatialaudio/python-rtmixer/compare/0.1.6...0.1.7
6+
7+
Version 0.1.6 -- 2024-07-02 -- PyPI__ -- diff__
8+
* Build ``abi3`` wheels, so one CPython wheel per platform supports
9+
Python 3.8 and newer
10+
11+
__ https://pypi.org/project/rtmixer/0.1.6/
12+
__ https://github.com/spatialaudio/python-rtmixer/compare/0.1.5...0.1.6
13+
14+
Version 0.1.5 -- 2024-06-24 -- PyPI__ -- diff__
15+
* Build wheels on GitHub Actions instead of Azure Pipelines
16+
* Add wheels for Python 3.11 and 3.12, Linux ``aarch64`` and macOS ``arm64``
17+
* Drop wheels for Python 3.6 and 3.7 and for 32-bit Windows
18+
19+
__ https://pypi.org/project/rtmixer/0.1.5/
20+
__ https://github.com/spatialaudio/python-rtmixer/compare/0.1.4...0.1.5
21+
22+
Version 0.1.4 -- 2022-01-05 -- PyPI__ -- diff__
23+
* Add Python 3.10 support
24+
* Remove the ``allow_belated`` field of actions, it is now encoded in
25+
``actual_time``
26+
* Update PortAudio submodule to version 19.7.0
27+
* Document common parameters and the fields of actions and statistics
28+
29+
__ https://pypi.org/project/rtmixer/0.1.4/
30+
__ https://github.com/spatialaudio/python-rtmixer/compare/0.1.3...0.1.4
31+
132
Version 0.1.3 -- 2021-05-05 -- PyPI__ -- diff__
233
* Add Python 3.9 support by adding entries to ``azure-pipelines.yml``
334
for ``cibuildwheel``

0 commit comments

Comments
 (0)