tachyaudio 0.2.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. tachyaudio-0.2.0b1/.github/workflows/tests.yml +59 -0
  2. tachyaudio-0.2.0b1/.github/workflows/wheels.yml +54 -0
  3. tachyaudio-0.2.0b1/CHANGELOG.md +88 -0
  4. tachyaudio-0.2.0b1/CONTRIBUTING.md +47 -0
  5. tachyaudio-0.2.0b1/LICENSE +22 -0
  6. tachyaudio-0.2.0b1/MANIFEST.in +14 -0
  7. tachyaudio-0.2.0b1/PKG-INFO +144 -0
  8. tachyaudio-0.2.0b1/README.md +118 -0
  9. tachyaudio-0.2.0b1/docs/architecture.md +100 -0
  10. tachyaudio-0.2.0b1/docs/python-support.md +36 -0
  11. tachyaudio-0.2.0b1/docs/release.md +51 -0
  12. tachyaudio-0.2.0b1/examples/capture_level.py +55 -0
  13. tachyaudio-0.2.0b1/examples/play_tone.py +65 -0
  14. tachyaudio-0.2.0b1/examples/play_wav.py +377 -0
  15. tachyaudio-0.2.0b1/pyproject.toml +62 -0
  16. tachyaudio-0.2.0b1/setup.cfg +4 -0
  17. tachyaudio-0.2.0b1/setup.py +29 -0
  18. tachyaudio-0.2.0b1/src/tachyaudio/__init__.py +36 -0
  19. tachyaudio-0.2.0b1/src/tachyaudio/_backend.py +131 -0
  20. tachyaudio-0.2.0b1/src/tachyaudio/_device.py +37 -0
  21. tachyaudio-0.2.0b1/src/tachyaudio/_errors.py +14 -0
  22. tachyaudio-0.2.0b1/src/tachyaudio/_native.c +3432 -0
  23. tachyaudio-0.2.0b1/src/tachyaudio/_native_backend.py +165 -0
  24. tachyaudio-0.2.0b1/src/tachyaudio/_stream.py +548 -0
  25. tachyaudio-0.2.0b1/src/tachyaudio/_version.py +1 -0
  26. tachyaudio-0.2.0b1/src/tachyaudio/py.typed +1 -0
  27. tachyaudio-0.2.0b1/src/tachyaudio/vendor/README.md +5 -0
  28. tachyaudio-0.2.0b1/src/tachyaudio/vendor/miniaudio.LICENSE +47 -0
  29. tachyaudio-0.2.0b1/src/tachyaudio/vendor/miniaudio.h +95864 -0
  30. tachyaudio-0.2.0b1/src/tachyaudio.egg-info/PKG-INFO +144 -0
  31. tachyaudio-0.2.0b1/src/tachyaudio.egg-info/SOURCES.txt +33 -0
  32. tachyaudio-0.2.0b1/src/tachyaudio.egg-info/dependency_links.txt +1 -0
  33. tachyaudio-0.2.0b1/src/tachyaudio.egg-info/requires.txt +5 -0
  34. tachyaudio-0.2.0b1/src/tachyaudio.egg-info/top_level.txt +1 -0
  35. tachyaudio-0.2.0b1/tests/test_public_api.py +633 -0
@@ -0,0 +1,59 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ env:
10
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
11
+
12
+ jobs:
13
+ linux:
14
+ name: Linux Python ${{ matrix.python-version }}
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - uses: actions/setup-python@v5
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+
28
+ - name: Install package
29
+ run: python -m pip install .
30
+
31
+ - name: Run tests
32
+ run: python -m unittest discover -s tests
33
+
34
+ - name: Import smoke test
35
+ run: python -c "import tachyaudio as ta; assert ta.get_backend().name == 'miniaudio'; print(len(ta.list_devices()))"
36
+
37
+ macos:
38
+ name: macOS Python ${{ matrix.python-version }}
39
+ runs-on: macos-14
40
+ strategy:
41
+ fail-fast: false
42
+ matrix:
43
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
44
+
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+
48
+ - uses: actions/setup-python@v5
49
+ with:
50
+ python-version: ${{ matrix.python-version }}
51
+
52
+ - name: Install package
53
+ run: python -m pip install .
54
+
55
+ - name: Run tests
56
+ run: python -m unittest discover -s tests
57
+
58
+ - name: Import smoke test
59
+ run: python -c "import tachyaudio as ta; print(ta.get_backend().name); print(len(ta.list_devices()))"
@@ -0,0 +1,54 @@
1
+ name: wheels
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch:
7
+
8
+ env:
9
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
10
+
11
+ jobs:
12
+ linux:
13
+ name: Build Linux wheels
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+
23
+ - name: Install cibuildwheel
24
+ run: python -m pip install cibuildwheel
25
+
26
+ - name: Build wheels
27
+ run: python -m cibuildwheel --output-dir wheelhouse
28
+
29
+ - uses: actions/upload-artifact@v4
30
+ with:
31
+ name: linux-wheels
32
+ path: wheelhouse/*.whl
33
+
34
+ macos:
35
+ name: Build macOS wheels
36
+ runs-on: macos-14
37
+
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+
41
+ - uses: actions/setup-python@v5
42
+ with:
43
+ python-version: "3.12"
44
+
45
+ - name: Install cibuildwheel
46
+ run: python -m pip install cibuildwheel
47
+
48
+ - name: Build wheels
49
+ run: python -m cibuildwheel --output-dir wheelhouse
50
+
51
+ - uses: actions/upload-artifact@v4
52
+ with:
53
+ name: macos-wheels
54
+ path: wheelhouse/*.whl
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ All notable changes to `tachyaudio` will be documented in this file.
4
+
5
+ The project follows semantic versioning once releases begin. During `0.x`,
6
+ public APIs may still change while the backend design stabilizes.
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0b1] - 2026-06-12
11
+
12
+ ### Added
13
+
14
+ - Added the public `DuplexStream` API contract for backend-level full-duplex
15
+ capture and playback.
16
+ - Added native macOS `DuplexStream` support backed by Core Audio input and
17
+ output queues owned by one native stream object.
18
+ - Added native Linux `DuplexStream` support backed by one miniaudio duplex
19
+ device callback.
20
+
21
+ ### Changed
22
+
23
+ - Promoted package metadata from pre-alpha to beta.
24
+ - Kept wheel builds intentionally per-CPython-version rather than stable ABI.
25
+
26
+ ## [0.2.0a3] - 2026-06-12
27
+
28
+ ### Changed
29
+
30
+ - Refactored native ring-buffer and stream-stat helpers without changing public
31
+ behavior.
32
+ - Added `hardware_latency` to stream statistics. macOS reports Core Audio
33
+ device latency plus safety offset; Linux reports `None` until a reliable
34
+ miniaudio latency value is available.
35
+ - Added blocking convenience helpers `OutputStream.write_all()` and
36
+ `InputStream.read_exactly()`.
37
+
38
+ ## [0.2.0a2] - 2026-06-10
39
+
40
+ ### Changed
41
+
42
+ - Linux device enumeration now returns no devices instead of raising when
43
+ miniaudio cannot initialize or enumerate an audio backend in headless
44
+ containers.
45
+
46
+ ## [0.2.0a1] - 2026-06-10
47
+
48
+ ### Added
49
+
50
+ - Linux native backend using vendored `miniaudio`.
51
+ - Linux device enumeration through miniaudio.
52
+ - Linux native output playback with a ring-buffer-backed `OutputStream`.
53
+ - Linux native input capture with nonblocking `InputStream.read()`.
54
+ - Linux CI coverage for Python 3.10 through 3.14.
55
+ - Vendored miniaudio license attribution.
56
+
57
+ ### Changed
58
+
59
+ - Linux backend initialization prefers PulseAudio, then falls back to ALSA.
60
+
61
+ ### Known Limitations
62
+
63
+ - Linux support is new and has only been validated on a small set of Ubuntu
64
+ audio configurations.
65
+ - Windows backend support is not implemented yet.
66
+ - The API is pre-alpha and may change before a stable release.
67
+
68
+ ## [0.1.0] - 2026-06-10
69
+
70
+ ### Added
71
+
72
+ - Initial Python package scaffold.
73
+ - macOS Core Audio device enumeration.
74
+ - macOS native output playback with a ring-buffer-backed `OutputStream`.
75
+ - Finite playback helper via `tachyaudio.play()`.
76
+ - Output stream lifecycle methods: `start()`, `stop()`, `drain()`, `flush()`,
77
+ and `close()`.
78
+ - macOS native input capture with nonblocking `InputStream.read()`.
79
+ - Stream statistics for processed frames, underruns, overruns, queued frames,
80
+ queued latency, and buffer size.
81
+ - Playback and capture examples.
82
+ - macOS CI and wheel workflow scaffolding for Python 3.10 through 3.14.
83
+
84
+ ### Known Limitations
85
+
86
+ - Native backend support is currently macOS-only.
87
+ - Windows and Linux backends are not implemented yet.
88
+ - The API is pre-alpha and may change before the first PyPI release.
@@ -0,0 +1,47 @@
1
+ # Contributing
2
+
3
+ `tachyaudio` is pre-alpha. The current implementation is intentionally narrow:
4
+ macOS Core Audio device enumeration, output playback, and input capture.
5
+
6
+ ## Development setup
7
+
8
+ Use Python 3.10 through 3.14.
9
+
10
+ ```bash
11
+ python -m pip install -e .
12
+ python -m unittest discover -s tests
13
+ ```
14
+
15
+ On macOS, restricted sandboxes may hide Core Audio devices. Validate audio
16
+ examples from an unsandboxed terminal before treating empty device lists as bugs.
17
+
18
+ ## Manual smoke tests
19
+
20
+ Playback:
21
+
22
+ ```bash
23
+ PYTHONPATH=src python examples/play_tone.py
24
+ ```
25
+
26
+ Capture:
27
+
28
+ ```bash
29
+ PYTHONPATH=src python examples/capture_level.py
30
+ ```
31
+
32
+ ## Contribution rules
33
+
34
+ - Keep the public Python API compatible with Python 3.10.
35
+ - Keep native audio callbacks free of Python calls.
36
+ - Add or update tests for behavior changes.
37
+ - Keep platform-specific backend behavior behind the backend protocol.
38
+ - Avoid new runtime dependencies unless they materially improve correctness or
39
+ packaging.
40
+
41
+ ## Pull request checklist
42
+
43
+ - Unit tests pass locally.
44
+ - Relevant examples still run when the change affects audio behavior.
45
+ - README/docs are updated for public API changes.
46
+ - Native code handles allocation and shutdown failures explicitly.
47
+
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tachyaudio contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,14 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include CONTRIBUTING.md
5
+ include pyproject.toml
6
+ include setup.py
7
+ recursive-include src/tachyaudio *.c *.h *.LICENSE *.md *.py py.typed
8
+ recursive-include docs *.md
9
+ recursive-include examples *.py
10
+ recursive-include tests *.py
11
+ recursive-include .github/workflows *.yml
12
+ global-exclude __pycache__/*
13
+ global-exclude *.py[cod]
14
+ global-exclude .DS_Store
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: tachyaudio
3
+ Version: 0.2.0b1
4
+ Summary: Low-level audio primitives for tachypy and Python experiments.
5
+ Author: tachyaudio contributors
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Multimedia :: Sound/Audio
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: dev
22
+ Requires-Dist: build>=1.2; extra == "dev"
23
+ Requires-Dist: mypy>=1.10; extra == "dev"
24
+ Requires-Dist: ruff>=0.5; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # tachyaudio
28
+
29
+ `tachyaudio` is a low-level audio package intended to replace tachypy’s direct
30
+ dependency on `sounddevice`/PortAudio over time.
31
+
32
+ Status: beta. The native backend currently supports macOS through Core Audio and
33
+ Linux through vendored `miniaudio`. Windows support is planned but intentionally
34
+ deferred until Windows test hardware is available.
35
+
36
+ ## Goals
37
+
38
+ - install from `pip` without requiring users to install PortAudio separately
39
+ - keep real-time audio work out of Python callbacks where possible
40
+ - expose explicit latency, underrun, and overrun diagnostics
41
+ - support playback, capture, duplex streams, and device enumeration
42
+ - keep tachypy’s public API stable while the backend evolves
43
+
44
+ ## Non-goals
45
+
46
+ - full DAW-style audio graph support in the initial release
47
+ - replacing OS audio stacks such as Core Audio, WASAPI, ALSA, or PulseAudio
48
+ - exposing backend-specific details as the primary user API
49
+
50
+ ## Initial API shape
51
+
52
+ ```python
53
+ import tachyaudio as ta
54
+
55
+ devices = ta.list_devices()
56
+
57
+ stream = ta.OutputStream(sample_rate=48_000, channels=2)
58
+ stream.write(samples)
59
+ with stream:
60
+ ...
61
+
62
+ stats = ta.play(samples, sample_rate=48_000, channels=2)
63
+ ```
64
+
65
+ The native backend currently supports device enumeration, float32 output
66
+ playback, and nonblocking float32 input capture on macOS and Linux.
67
+
68
+ `OutputStream` is currently a continuous stream. Write audio before or during
69
+ playback; if the stream runs out of queued frames it outputs silence and counts
70
+ an underrun.
71
+
72
+ Use `tachyaudio.play()` for finite stimuli that should start, drain, and close
73
+ as a single operation.
74
+
75
+ See `examples/play_wav.py` for dependency-free playback of WAV files exported
76
+ from an editor such as Audacity. The repository example uses a 48 kHz float WAV
77
+ to avoid runtime resampling. Pass `--eq` to show a lightweight five-band
78
+ terminal meter during playback.
79
+
80
+ Use blocking helpers when callers need complete buffer transfer:
81
+
82
+ - `OutputStream.write_all(frames, timeout=None)`: wait until all frames are
83
+ accepted by the output ring
84
+ - `InputStream.read_exactly(frame_count, timeout=None)`: wait until exactly the
85
+ requested number of frames has been captured
86
+
87
+ Full-duplex capture/playback is exposed as `DuplexStream`. Native macOS and
88
+ Linux support is available.
89
+
90
+ Lifecycle semantics:
91
+
92
+ - `stop()`: stop playback without discarding queued frames
93
+ - `drain()`: wait for queued frames to play
94
+ - `flush()`: discard queued frames without closing the stream
95
+ - `close()`: stop playback and release native resources
96
+
97
+ `StreamStats` reports:
98
+
99
+ - `frames_processed`: frames consumed by the backend
100
+ - `underruns` / `overruns`: buffer starvation or rejected writes
101
+ - `hardware_latency`: backend-reported device latency in seconds when available
102
+ - `queued_frames`: frames currently waiting in the native ring
103
+ - `queued_latency`: queued ring duration in seconds
104
+ - `estimated_latency`: `queued_latency + hardware_latency` when hardware latency
105
+ is available, otherwise queued latency
106
+ - `buffer_size`: native callback buffer size in frames
107
+
108
+ ## Development
109
+
110
+ Supported Python versions:
111
+
112
+ - Python 3.10
113
+ - Python 3.11
114
+ - Python 3.12
115
+ - Python 3.13
116
+ - Python 3.14
117
+
118
+ Run the standard-library test suite:
119
+
120
+ ```bash
121
+ python3 -m unittest discover -s tests
122
+ ```
123
+
124
+ Play a short test tone:
125
+
126
+ ```bash
127
+ PYTHONPATH=src python3 examples/play_tone.py
128
+ ```
129
+
130
+ Capture a short input buffer and print its RMS level:
131
+
132
+ ```bash
133
+ PYTHONPATH=src python3 examples/capture_level.py
134
+ ```
135
+
136
+ `InputStream.read(frame_count)` is nonblocking and returns currently available
137
+ frames up to `frame_count`.
138
+
139
+ On macOS, a restricted sandbox may hide Core Audio devices. On Linux, sandboxed
140
+ processes may be unable to reach the user PipeWire/PulseAudio server. If
141
+ `tachyaudio.list_devices()` returns an empty tuple or only generic ALSA devices
142
+ in a sandboxed environment, verify from an unsandboxed terminal before debugging
143
+ the backend. Headless Linux containers may return no devices while still being
144
+ able to build and import the native extension.
@@ -0,0 +1,118 @@
1
+ # tachyaudio
2
+
3
+ `tachyaudio` is a low-level audio package intended to replace tachypy’s direct
4
+ dependency on `sounddevice`/PortAudio over time.
5
+
6
+ Status: beta. The native backend currently supports macOS through Core Audio and
7
+ Linux through vendored `miniaudio`. Windows support is planned but intentionally
8
+ deferred until Windows test hardware is available.
9
+
10
+ ## Goals
11
+
12
+ - install from `pip` without requiring users to install PortAudio separately
13
+ - keep real-time audio work out of Python callbacks where possible
14
+ - expose explicit latency, underrun, and overrun diagnostics
15
+ - support playback, capture, duplex streams, and device enumeration
16
+ - keep tachypy’s public API stable while the backend evolves
17
+
18
+ ## Non-goals
19
+
20
+ - full DAW-style audio graph support in the initial release
21
+ - replacing OS audio stacks such as Core Audio, WASAPI, ALSA, or PulseAudio
22
+ - exposing backend-specific details as the primary user API
23
+
24
+ ## Initial API shape
25
+
26
+ ```python
27
+ import tachyaudio as ta
28
+
29
+ devices = ta.list_devices()
30
+
31
+ stream = ta.OutputStream(sample_rate=48_000, channels=2)
32
+ stream.write(samples)
33
+ with stream:
34
+ ...
35
+
36
+ stats = ta.play(samples, sample_rate=48_000, channels=2)
37
+ ```
38
+
39
+ The native backend currently supports device enumeration, float32 output
40
+ playback, and nonblocking float32 input capture on macOS and Linux.
41
+
42
+ `OutputStream` is currently a continuous stream. Write audio before or during
43
+ playback; if the stream runs out of queued frames it outputs silence and counts
44
+ an underrun.
45
+
46
+ Use `tachyaudio.play()` for finite stimuli that should start, drain, and close
47
+ as a single operation.
48
+
49
+ See `examples/play_wav.py` for dependency-free playback of WAV files exported
50
+ from an editor such as Audacity. The repository example uses a 48 kHz float WAV
51
+ to avoid runtime resampling. Pass `--eq` to show a lightweight five-band
52
+ terminal meter during playback.
53
+
54
+ Use blocking helpers when callers need complete buffer transfer:
55
+
56
+ - `OutputStream.write_all(frames, timeout=None)`: wait until all frames are
57
+ accepted by the output ring
58
+ - `InputStream.read_exactly(frame_count, timeout=None)`: wait until exactly the
59
+ requested number of frames has been captured
60
+
61
+ Full-duplex capture/playback is exposed as `DuplexStream`. Native macOS and
62
+ Linux support is available.
63
+
64
+ Lifecycle semantics:
65
+
66
+ - `stop()`: stop playback without discarding queued frames
67
+ - `drain()`: wait for queued frames to play
68
+ - `flush()`: discard queued frames without closing the stream
69
+ - `close()`: stop playback and release native resources
70
+
71
+ `StreamStats` reports:
72
+
73
+ - `frames_processed`: frames consumed by the backend
74
+ - `underruns` / `overruns`: buffer starvation or rejected writes
75
+ - `hardware_latency`: backend-reported device latency in seconds when available
76
+ - `queued_frames`: frames currently waiting in the native ring
77
+ - `queued_latency`: queued ring duration in seconds
78
+ - `estimated_latency`: `queued_latency + hardware_latency` when hardware latency
79
+ is available, otherwise queued latency
80
+ - `buffer_size`: native callback buffer size in frames
81
+
82
+ ## Development
83
+
84
+ Supported Python versions:
85
+
86
+ - Python 3.10
87
+ - Python 3.11
88
+ - Python 3.12
89
+ - Python 3.13
90
+ - Python 3.14
91
+
92
+ Run the standard-library test suite:
93
+
94
+ ```bash
95
+ python3 -m unittest discover -s tests
96
+ ```
97
+
98
+ Play a short test tone:
99
+
100
+ ```bash
101
+ PYTHONPATH=src python3 examples/play_tone.py
102
+ ```
103
+
104
+ Capture a short input buffer and print its RMS level:
105
+
106
+ ```bash
107
+ PYTHONPATH=src python3 examples/capture_level.py
108
+ ```
109
+
110
+ `InputStream.read(frame_count)` is nonblocking and returns currently available
111
+ frames up to `frame_count`.
112
+
113
+ On macOS, a restricted sandbox may hide Core Audio devices. On Linux, sandboxed
114
+ processes may be unable to reach the user PipeWire/PulseAudio server. If
115
+ `tachyaudio.list_devices()` returns an empty tuple or only generic ALSA devices
116
+ in a sandboxed environment, verify from an unsandboxed terminal before debugging
117
+ the backend. Headless Linux containers may return no devices while still being
118
+ able to build and import the native extension.
@@ -0,0 +1,100 @@
1
+ # tachyaudio architecture
2
+
3
+ ## Package boundary
4
+
5
+ `tachyaudio` owns low-level audio primitives. `tachypy` should not depend on
6
+ backend-specific concepts directly. Instead, tachypy should wrap `tachyaudio`
7
+ from `tachypy.audio` and provide experiment-oriented scheduling, stimulus
8
+ construction, and compatibility behavior.
9
+
10
+ ## Backend boundary
11
+
12
+ The public Python API delegates to an internal backend protocol:
13
+
14
+ - `list_devices()`
15
+ - `open_output_stream(config)`
16
+ - `open_input_stream(config)`
17
+
18
+ The first production backend should be native. The current first slices expose
19
+ macOS Core Audio device enumeration and output playback directly so the build
20
+ and Python boundary can be validated. Playback uses preallocated Core Audio
21
+ queue buffers fed by a native ring buffer. A future cross-platform
22
+ implementation can move to vendored `miniaudio` once the desired callback,
23
+ buffering, and drain semantics are clearer.
24
+
25
+ Finite playback uses an explicit drain mode: partial final buffers are padded
26
+ with silence without counting as underruns, and callbacks stop refilling once
27
+ queued audio has drained.
28
+
29
+ Stream lifecycle methods have distinct responsibilities: `stop()` halts playback,
30
+ `drain()` waits for queued audio to finish, `flush()` discards queued ring-buffer
31
+ audio, and `close()` releases native resources.
32
+
33
+ Input capture mirrors output buffering in reverse: Core Audio callbacks write
34
+ captured float32 frames into a native ring buffer, and Python reads available
35
+ frames without invoking Python from the audio callback.
36
+
37
+ The core stream methods remain nonblocking: `write()` accepts as many frames as
38
+ the output ring can hold, and `read()` returns currently available captured
39
+ frames. Blocking helpers (`write_all()` and `read_exactly()`) are Python-level
40
+ conveniences layered on top of those primitives.
41
+
42
+ Full-duplex support is modeled as a backend-level `DuplexStream`, not as a
43
+ Python wrapper around one `OutputStream` and one `InputStream`. Backends should
44
+ use a single native duplex callback where available so capture and playback share
45
+ one scheduling clock. The current macOS implementation owns both native Core
46
+ Audio queues inside one native stream object. The Linux miniaudio implementation
47
+ uses one native duplex device callback.
48
+
49
+ Stream statistics distinguish queue state from hardware behavior. `queued_frames`
50
+ and `queued_latency` describe the native ring buffer. `hardware_latency`
51
+ describes backend-reported device latency when available. `buffer_size`
52
+ describes the callback buffer size in frames. `estimated_latency` reports
53
+ `queued_latency + hardware_latency` when hardware latency is available, otherwise
54
+ queued latency.
55
+
56
+ ## Real-time constraints
57
+
58
+ Audio callbacks must not call Python. Native backend code should own:
59
+
60
+ - device callback execution
61
+ - ring-buffer reads and writes
62
+ - underrun and overrun counters
63
+ - format conversion when needed
64
+ - stream start/stop synchronization
65
+
66
+ Python should submit buffers, inspect diagnostics, and control stream lifecycle.
67
+
68
+ ## Packaging strategy
69
+
70
+ Support CPython 3.10 through 3.14. Keep the Python layer syntax-compatible with
71
+ 3.10, and build wheels for each supported CPython minor version because native
72
+ extension modules are ABI-specific unless we intentionally move to the stable
73
+ ABI.
74
+
75
+ Ship wheels for supported platforms:
76
+
77
+ - macOS arm64/x86_64 using Core Audio
78
+ - Windows x86_64/arm64 using WASAPI
79
+ - Linux x86_64/aarch64 using the best available runtime backend
80
+
81
+ Source builds should remain possible, but routine users should not need to
82
+ install PortAudio or a compiler.
83
+
84
+ ## Native backend milestone
85
+
86
+ The minimum useful native backend is:
87
+
88
+ 1. device enumeration
89
+ 2. output stream
90
+ 3. input stream
91
+ 4. frame counters
92
+ 5. underrun/overrun counters
93
+ 6. explicit latency configuration
94
+ 7. wheel builds in CI
95
+
96
+ ## macOS sandbox note
97
+
98
+ Core Audio device enumeration can return zero devices when run from a restricted
99
+ sandbox, even when the host Mac has audio hardware. Validate native enumeration
100
+ from an unsandboxed process before treating an empty list as a backend defect.
@@ -0,0 +1,36 @@
1
+ # Python support policy
2
+
3
+ `tachyaudio` targets CPython 3.10 through 3.14.
4
+
5
+ ## Compatibility rules
6
+
7
+ - Keep public Python code syntax-compatible with Python 3.10.
8
+ - Avoid runtime dependencies unless they materially improve packaging or audio
9
+ correctness.
10
+ - Build and test native wheels for every supported CPython minor version.
11
+ - Treat Python 3.10 as the lower-bound type-checking target.
12
+ - Add version-specific code only behind narrow compatibility helpers.
13
+
14
+ ## Wheel targets and ABI decision
15
+
16
+ Initial wheel scope:
17
+
18
+ - `cp310`
19
+ - `cp311`
20
+ - `cp312`
21
+ - `cp313`
22
+ - `cp314`
23
+
24
+ The native extension currently uses the regular CPython C API. That means wheel
25
+ builds are per-CPython-version. Moving to Python’s stable ABI can be evaluated
26
+ later, but only after the native API settles.
27
+
28
+ For the public beta, `tachyaudio` intentionally ships per-version CPython
29
+ wheels (`cp310` through `cp314`) rather than `abi3` wheels. This keeps the
30
+ native backend implementation unconstrained while macOS and Linux playback,
31
+ capture, duplex streams, and diagnostics are still evolving. The additional
32
+ wheel matrix is acceptable because cibuildwheel already builds the supported
33
+ Python minors explicitly.
34
+
35
+ CI should run unit tests on every supported Python minor version before a wheel
36
+ is published.