euclidean-rhythm 0.2.0__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 (31) hide show
  1. euclidean_rhythm-0.2.0/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
  2. euclidean_rhythm-0.2.0/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
  3. euclidean_rhythm-0.2.0/.github/workflows/ci.yml +22 -0
  4. euclidean_rhythm-0.2.0/.gitignore +18 -0
  5. euclidean_rhythm-0.2.0/CHANGELOG.md +34 -0
  6. euclidean_rhythm-0.2.0/CLAUDE.md +47 -0
  7. euclidean_rhythm-0.2.0/CODE_OF_CONDUCT.md +37 -0
  8. euclidean_rhythm-0.2.0/CONTRIBUTING.md +31 -0
  9. euclidean_rhythm-0.2.0/LICENSE +21 -0
  10. euclidean_rhythm-0.2.0/PKG-INFO +205 -0
  11. euclidean_rhythm-0.2.0/README.md +153 -0
  12. euclidean_rhythm-0.2.0/SECURITY.md +18 -0
  13. euclidean_rhythm-0.2.0/assets/logo.png +0 -0
  14. euclidean_rhythm-0.2.0/docs/architecture.md +77 -0
  15. euclidean_rhythm-0.2.0/docs/charter.md +30 -0
  16. euclidean_rhythm-0.2.0/docs/logo-prompt.md +7 -0
  17. euclidean_rhythm-0.2.0/examples/basic_usage.py +43 -0
  18. euclidean_rhythm-0.2.0/pyproject.toml +63 -0
  19. euclidean_rhythm-0.2.0/src/euclidean_rhythm/__init__.py +27 -0
  20. euclidean_rhythm-0.2.0/src/euclidean_rhythm/analysis.py +177 -0
  21. euclidean_rhythm-0.2.0/src/euclidean_rhythm/cli.py +44 -0
  22. euclidean_rhythm-0.2.0/src/euclidean_rhythm/generation.py +95 -0
  23. euclidean_rhythm-0.2.0/src/euclidean_rhythm/metrics.py +224 -0
  24. euclidean_rhythm-0.2.0/src/euclidean_rhythm/operations.py +72 -0
  25. euclidean_rhythm-0.2.0/src/euclidean_rhythm/py.typed +0 -0
  26. euclidean_rhythm-0.2.0/tests/__init__.py +0 -0
  27. euclidean_rhythm-0.2.0/tests/test_analysis.py +109 -0
  28. euclidean_rhythm-0.2.0/tests/test_cli.py +41 -0
  29. euclidean_rhythm-0.2.0/tests/test_generation.py +99 -0
  30. euclidean_rhythm-0.2.0/tests/test_metrics.py +312 -0
  31. euclidean_rhythm-0.2.0/tests/test_operations.py +82 -0
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report incorrect output or an unexpected error
4
+ labels: bug
5
+ ---
6
+
7
+ **Function called**
8
+ <!-- e.g. euclidean(pulses=3, steps=8) -->
9
+
10
+ **Input**
11
+ <!-- The rhythm vector or parameters -->
12
+
13
+ **Expected output**
14
+ <!-- What you expected -->
15
+
16
+ **Actual output**
17
+ <!-- What you got -->
18
+
19
+ **Python version**
20
+ <!-- e.g. 3.11.4 -->
21
+
22
+ **euclidean-rhythm version**
23
+ <!-- e.g. 0.1.0 -->
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest a new rhythm measure or generation algorithm
4
+ labels: enhancement
5
+ ---
6
+
7
+ **What would you like?**
8
+ <!-- Describe the feature -->
9
+
10
+ **Reference**
11
+ <!-- If it's a published measure or algorithm, please cite the paper -->
12
+
13
+ **Example**
14
+ <!-- Show a concrete input/output example -->
@@ -0,0 +1,22 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python }}
19
+ - run: pip install -e ".[dev]"
20
+ - run: ruff check .
21
+ - run: mypy src
22
+ - run: pytest -q
@@ -0,0 +1,18 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ dist/
6
+ build/
7
+ *.egg
8
+ .venv/
9
+ venv/
10
+ env/
11
+ .env
12
+ *.so
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .pytest_cache/
16
+ .hypothesis/
17
+ uv.lock
18
+ *.dist-info/
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Planned
10
+ - PyPI release (pending new-project quota reset)
11
+
12
+ ## [0.2.0] - 2026-06-17
13
+
14
+ ### Added
15
+ - `offbeatness(rhythm)`: Toussaint's offbeatness -- count of onsets landing on off-beat
16
+ positions. Off-beat positions are those p with gcd(p, n) == 1 (equivalently, positions
17
+ not covered by any regular subdivision). PyPI publish queued behind new-project quota.
18
+ - `inter_onset_intervals(rhythm)`: gaps in pulses between consecutive onsets, wrapping
19
+ around the cycle. Always sums to n. Raises ValueError for zero onsets.
20
+ - `ioi_histogram(rhythm)`: histogram (interval length -> count) of inter-onset intervals.
21
+ - `onset_positions(rhythm)`: indices of onsets in a 0/1 rhythm vector.
22
+ - `pattern_from_onsets(*, positions, steps)`: inverse converter -- build a 0/1 pattern
23
+ from onset indices. Completes a round-trip with onset_positions.
24
+
25
+ ## [0.1.0] - 2026-06-17
26
+
27
+ ### Added
28
+ - `euclidean(*, pulses, steps)`: Bjorklund's algorithm for Euclidean rhythm generation.
29
+ - `rotate(rhythm, *, steps)`: Left rotation by steps (mod len).
30
+ - `necklace(rhythm)`: Lexicographically minimal rotation (canonical necklace form).
31
+ - `evenness(rhythm)`: Toussaint's geometric evenness on the unit circle, normalized to [0, 1].
32
+ - `syncopation(rhythm)`: Keith's (1991) syncopation measure based on metric weight differences.
33
+ - `rhythmic_oddity(rhythm)`: Pressing's (1983) rhythmic oddity property.
34
+ - `euclidean-rhythm` CLI: print a generated rhythm as `x`/`.` characters.
@@ -0,0 +1,47 @@
1
+ # euclidean-rhythm
2
+
3
+ Pure-Python Euclidean rhythm generation and analysis. Zero runtime dependencies.
4
+
5
+ ## Commands
6
+
7
+ - Create env and install: `uv venv && uv pip install -e ".[dev]"`
8
+ - Test: `uv run pytest -q`
9
+ - Lint: `uv run ruff check .` (format with `uv run ruff format .`)
10
+ - Types: `uv run mypy src`
11
+ - Build: `uv build` (then `uv run --with twine twine check dist/*` before publishing)
12
+
13
+ ## Architecture
14
+
15
+ `src/euclidean_rhythm/`:
16
+ - `generation.py`: Bjorklund's algorithm for euclidean()
17
+ - `operations.py`: rotate() and necklace()
18
+ - `analysis.py`: evenness(), syncopation(), rhythmic_oddity()
19
+ - `cli.py`: euclidean-rhythm CLI
20
+ - `__init__.py`: public surface
21
+
22
+ See `docs/architecture.md` for precise definitions and references.
23
+
24
+ ## Conventions
25
+
26
+ - Rhythms are `list[int]` of 0/1 onset values.
27
+ - All parameters are keyword-only with no default values.
28
+ - Pure functions, strict typing, zero runtime dependencies (only `math`).
29
+ - Validate inputs and raise clear ValueError messages.
30
+
31
+ ## Testing rules
32
+
33
+ - Golden values for named rhythms (son clave, bossa nova).
34
+ - Hypothesis property tests for pulse count, length, rotation invariance.
35
+ - Exact values for evenness (1.0 for maximally even) and syncopation.
36
+ - Bug fixes start with a failing test.
37
+
38
+ ## Release
39
+
40
+ - Semantic versioning; update CHANGELOG.md and __version__.
41
+ - Gates: `uv run pytest && uv run ruff check . && uv run mypy src && uv build && uv run --with twine twine check dist/*`.
42
+ - Do NOT publish to PyPI (pending quota reset). Tag vX.Y.Z and GitHub release.
43
+
44
+ ## Style
45
+
46
+ - No em dash characters in docs, comments, or commit messages.
47
+ - Comments explain non-obvious reasoning only.
@@ -0,0 +1,37 @@
1
+ # Code of Conduct
2
+
3
+ ## Our pledge
4
+
5
+ We as members, contributors, and maintainers pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our standards
13
+
14
+ Examples of behavior that contributes to a positive environment:
15
+
16
+ - Showing empathy and kindness toward other people.
17
+ - Being respectful of differing opinions, viewpoints, and experiences.
18
+ - Giving and gracefully accepting constructive feedback.
19
+ - Focusing on what is best for the community.
20
+
21
+ Examples of unacceptable behavior:
22
+
23
+ - Harassment, insulting or derogatory comments, and personal or political attacks.
24
+ - Public or private harassment.
25
+ - Publishing others' private information without explicit permission.
26
+ - Other conduct which could reasonably be considered inappropriate.
27
+
28
+ ## Enforcement
29
+
30
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
31
+ reported to the maintainer at amaar2cool@gmail.com. All complaints will be
32
+ reviewed and investigated promptly and fairly.
33
+
34
+ ## Attribution
35
+
36
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
37
+ version 2.1.
@@ -0,0 +1,31 @@
1
+ # Contributing to euclidean-rhythm
2
+
3
+ Thanks for your interest. This project values correctness, precise definitions, and zero
4
+ runtime dependencies.
5
+
6
+ ## Development
7
+
8
+ ```sh
9
+ uv venv
10
+ uv pip install -e ".[dev]"
11
+ uv run pytest -q
12
+ uv run ruff check .
13
+ uv run mypy src
14
+ ```
15
+
16
+ A standard virtual environment with `pip install -e ".[dev]"` works the same way.
17
+
18
+ ## Guidelines
19
+
20
+ - No runtime dependencies. Standard library `math` is enough.
21
+ - All functions are pure, with keyword-only parameters and no default values.
22
+ - Every function needs exact-value tests and, where applicable, property tests (Hypothesis).
23
+ - A bug fix starts with a failing test.
24
+ - Run `uv run ruff format .` before committing.
25
+ - Commit messages follow `type(scope): description`.
26
+ - No em dash characters in code, comments, or commit messages.
27
+
28
+ ## Reporting issues
29
+
30
+ Open an issue with the rhythm vector, the function called, and what you expected versus
31
+ what you observed.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amaar Chughtai
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.
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: euclidean-rhythm
3
+ Version: 0.2.0
4
+ Summary: Euclidean rhythms (Bjorklund) with Toussaint evenness, Keith syncopation, and Pressing rhythmic oddity, in pure Python.
5
+ Project-URL: Homepage, https://github.com/amaar-mc/euclidean-rhythm
6
+ Project-URL: Repository, https://github.com/amaar-mc/euclidean-rhythm
7
+ Project-URL: Issues, https://github.com/amaar-mc/euclidean-rhythm/issues
8
+ Project-URL: Changelog, https://github.com/amaar-mc/euclidean-rhythm/blob/main/CHANGELOG.md
9
+ Author: Amaar Chughtai
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Amaar Chughtai
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: bjorklund,euclidean-rhythm,generative-music,music,music-theory,python,rhythm,toussaint
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Intended Audience :: Science/Research
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Topic :: Multimedia :: Sound/Audio
43
+ Classifier: Topic :: Scientific/Engineering
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: >=3.10
46
+ Provides-Extra: dev
47
+ Requires-Dist: hypothesis>=6; extra == 'dev'
48
+ Requires-Dist: mypy>=1.11; extra == 'dev'
49
+ Requires-Dist: pytest>=8; extra == 'dev'
50
+ Requires-Dist: ruff>=0.6; extra == 'dev'
51
+ Description-Content-Type: text/markdown
52
+
53
+ # euclidean-rhythm
54
+
55
+ <p align="center">
56
+ <img src="assets/logo.png" alt="euclidean-rhythm logo" width="160">
57
+ </p>
58
+
59
+ Generate Euclidean rhythms and analyze them with standard geometric measures, in pure Python with zero dependencies.
60
+
61
+ ## What are Euclidean rhythms?
62
+
63
+ Euclidean rhythms distribute `k` onsets as evenly as possible over `n` time steps using
64
+ Bjorklund's algorithm - the same Euclidean GCD logic that underlies many traditional
65
+ musical patterns worldwide.
66
+
67
+ **The son clave** (3 onsets, 8 steps): `x . . x . . x .`
68
+ **The bossa nova clave** (5 onsets, 8 steps): `x . x x . x x .`
69
+
70
+ ## Install
71
+
72
+ ```sh
73
+ pip install euclidean-rhythm
74
+ ```
75
+
76
+ > PyPI release pending. Install from source:
77
+ > ```sh
78
+ > git clone https://github.com/amaar-mc/euclidean-rhythm
79
+ > cd euclidean-rhythm
80
+ > pip install -e .
81
+ > ```
82
+
83
+ ## Quick start
84
+
85
+ ```python
86
+ from euclidean_rhythm import (
87
+ euclidean,
88
+ evenness,
89
+ inter_onset_intervals,
90
+ ioi_histogram,
91
+ necklace,
92
+ offbeatness,
93
+ onset_positions,
94
+ pattern_from_onsets,
95
+ rhythmic_oddity,
96
+ rotate,
97
+ syncopation,
98
+ )
99
+
100
+ # Generate rhythms
101
+ son = euclidean(pulses=3, steps=8)
102
+ # [1, 0, 0, 1, 0, 0, 1, 0]
103
+
104
+ bossa = euclidean(pulses=5, steps=8)
105
+ # [1, 0, 1, 1, 0, 1, 1, 0]
106
+
107
+ # Rotate
108
+ rotate(son, steps=2)
109
+ # [0, 1, 0, 0, 1, 0, 1, 0]
110
+
111
+ # Canonical necklace form (rotation-invariant)
112
+ necklace(son) == necklace(rotate(son, steps=3))
113
+ # True
114
+
115
+ # Evenness (1.0 = maximally even)
116
+ evenness(euclidean(pulses=4, steps=8))
117
+ # 1.0
118
+
119
+ # Keith syncopation (0 = no syncopation)
120
+ syncopation(euclidean(pulses=4, steps=8))
121
+ # 0
122
+
123
+ # Pressing rhythmic oddity
124
+ rhythmic_oddity(son)
125
+ # True
126
+
127
+ # Off-beat onset count (Toussaint offbeatness)
128
+ offbeatness(son)
129
+ # 1 -- onset at position 3 is off-beat in n=8; 0 and 6 are on-beat
130
+
131
+ # Inter-onset intervals (gaps in pulses, wrapping)
132
+ inter_onset_intervals(son)
133
+ # [3, 3, 2] -- sums to 8
134
+
135
+ # Histogram of inter-onset intervals
136
+ ioi_histogram(son)
137
+ # {3: 2, 2: 1}
138
+
139
+ # Convert between 0/1 pattern and onset-position list
140
+ onset_positions(son)
141
+ # [0, 3, 6]
142
+ pattern_from_onsets(positions=[0, 3, 6], steps=8)
143
+ # [1, 0, 0, 1, 0, 0, 1, 0]
144
+ ```
145
+
146
+ ## CLI
147
+
148
+ ```sh
149
+ euclidean-rhythm 3 8
150
+ # x..x..x.
151
+
152
+ euclidean-rhythm 5 8
153
+ # x.xx.xx.
154
+ ```
155
+
156
+ ## API
157
+
158
+ All parameters are keyword-only.
159
+
160
+ | Function | Description |
161
+ |---|---|
162
+ | `euclidean(*, pulses, steps)` | Generate Euclidean rhythm (Bjorklund's algorithm) |
163
+ | `rotate(rhythm, *, steps)` | Rotate left by steps (mod len) |
164
+ | `necklace(rhythm)` | Lexicographically minimal rotation (canonical form) |
165
+ | `evenness(rhythm)` | Toussaint geometric evenness in (0, 1] |
166
+ | `syncopation(rhythm)` | Keith (1991) syncopation count |
167
+ | `rhythmic_oddity(rhythm)` | Pressing (1983) rhythmic oddity property |
168
+ | `offbeatness(rhythm)` | Count of onsets on off-beat positions (gcd-coprime to n) |
169
+ | `inter_onset_intervals(rhythm)` | Gaps in pulses between consecutive onsets, wrapping |
170
+ | `ioi_histogram(rhythm)` | Histogram of inter-onset interval lengths |
171
+ | `onset_positions(rhythm)` | Indices of onsets in a 0/1 pattern |
172
+ | `pattern_from_onsets(*, positions, steps)` | Build 0/1 pattern from onset indices |
173
+
174
+ ## Measures defined
175
+
176
+ **Evenness** (Toussaint 2005): Place onsets on a unit circle; sum all pairwise chord
177
+ lengths; normalize by the maximum (equally spaced onsets). Score 1.0 means maximally even.
178
+
179
+ **Syncopation** (Keith 1991): Metric weight of position `i` is `n` for the downbeat
180
+ (`i=0`) and the largest power of 2 dividing `i` for `i>0`. A syncopation occurs when
181
+ an onset at a weak beat is followed by a rest at a stronger beat; the score accumulates
182
+ the weight difference.
183
+
184
+ **Rhythmic oddity** (Pressing 1983): True if no two onsets are diametrically opposite
185
+ on the rhythm circle (no pair partitions the cycle into two equal halves).
186
+
187
+ **Offbeatness** (Toussaint): For a cycle of n pulses, position p is off-beat iff
188
+ gcd(p, n) == 1 -- equivalently, p is not covered by any regular subdivision of the
189
+ cycle (union of {k*n/d} for proper divisors d of n). Offbeatness is the count of onsets
190
+ at such positions. Both characterizations produce identical off-beat sets, verified for
191
+ n in 2..64.
192
+
193
+ **Inter-onset intervals**: The sequence of gaps (in pulses) between consecutive onsets
194
+ around the cycle, wrapping from the last onset back to the first. Always sums to n.
195
+
196
+ ## References
197
+
198
+ - Bjorklund, E. (2003). The theory of rep-rate pattern generation in the SNS timing system.
199
+ - Toussaint, G. (2005). The Euclidean algorithm generates traditional musical rhythms. BRIDGES.
200
+ - Keith, M. (1991). From Polychords to Polya: Adventures in Musical Combinatorics.
201
+ - Pressing, J. (1983). Cognitive isomorphisms between pitch and rhythm in world musics. Studies in Music.
202
+
203
+ ## License
204
+
205
+ MIT. Copyright (c) 2026 Amaar Chughtai.
@@ -0,0 +1,153 @@
1
+ # euclidean-rhythm
2
+
3
+ <p align="center">
4
+ <img src="assets/logo.png" alt="euclidean-rhythm logo" width="160">
5
+ </p>
6
+
7
+ Generate Euclidean rhythms and analyze them with standard geometric measures, in pure Python with zero dependencies.
8
+
9
+ ## What are Euclidean rhythms?
10
+
11
+ Euclidean rhythms distribute `k` onsets as evenly as possible over `n` time steps using
12
+ Bjorklund's algorithm - the same Euclidean GCD logic that underlies many traditional
13
+ musical patterns worldwide.
14
+
15
+ **The son clave** (3 onsets, 8 steps): `x . . x . . x .`
16
+ **The bossa nova clave** (5 onsets, 8 steps): `x . x x . x x .`
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ pip install euclidean-rhythm
22
+ ```
23
+
24
+ > PyPI release pending. Install from source:
25
+ > ```sh
26
+ > git clone https://github.com/amaar-mc/euclidean-rhythm
27
+ > cd euclidean-rhythm
28
+ > pip install -e .
29
+ > ```
30
+
31
+ ## Quick start
32
+
33
+ ```python
34
+ from euclidean_rhythm import (
35
+ euclidean,
36
+ evenness,
37
+ inter_onset_intervals,
38
+ ioi_histogram,
39
+ necklace,
40
+ offbeatness,
41
+ onset_positions,
42
+ pattern_from_onsets,
43
+ rhythmic_oddity,
44
+ rotate,
45
+ syncopation,
46
+ )
47
+
48
+ # Generate rhythms
49
+ son = euclidean(pulses=3, steps=8)
50
+ # [1, 0, 0, 1, 0, 0, 1, 0]
51
+
52
+ bossa = euclidean(pulses=5, steps=8)
53
+ # [1, 0, 1, 1, 0, 1, 1, 0]
54
+
55
+ # Rotate
56
+ rotate(son, steps=2)
57
+ # [0, 1, 0, 0, 1, 0, 1, 0]
58
+
59
+ # Canonical necklace form (rotation-invariant)
60
+ necklace(son) == necklace(rotate(son, steps=3))
61
+ # True
62
+
63
+ # Evenness (1.0 = maximally even)
64
+ evenness(euclidean(pulses=4, steps=8))
65
+ # 1.0
66
+
67
+ # Keith syncopation (0 = no syncopation)
68
+ syncopation(euclidean(pulses=4, steps=8))
69
+ # 0
70
+
71
+ # Pressing rhythmic oddity
72
+ rhythmic_oddity(son)
73
+ # True
74
+
75
+ # Off-beat onset count (Toussaint offbeatness)
76
+ offbeatness(son)
77
+ # 1 -- onset at position 3 is off-beat in n=8; 0 and 6 are on-beat
78
+
79
+ # Inter-onset intervals (gaps in pulses, wrapping)
80
+ inter_onset_intervals(son)
81
+ # [3, 3, 2] -- sums to 8
82
+
83
+ # Histogram of inter-onset intervals
84
+ ioi_histogram(son)
85
+ # {3: 2, 2: 1}
86
+
87
+ # Convert between 0/1 pattern and onset-position list
88
+ onset_positions(son)
89
+ # [0, 3, 6]
90
+ pattern_from_onsets(positions=[0, 3, 6], steps=8)
91
+ # [1, 0, 0, 1, 0, 0, 1, 0]
92
+ ```
93
+
94
+ ## CLI
95
+
96
+ ```sh
97
+ euclidean-rhythm 3 8
98
+ # x..x..x.
99
+
100
+ euclidean-rhythm 5 8
101
+ # x.xx.xx.
102
+ ```
103
+
104
+ ## API
105
+
106
+ All parameters are keyword-only.
107
+
108
+ | Function | Description |
109
+ |---|---|
110
+ | `euclidean(*, pulses, steps)` | Generate Euclidean rhythm (Bjorklund's algorithm) |
111
+ | `rotate(rhythm, *, steps)` | Rotate left by steps (mod len) |
112
+ | `necklace(rhythm)` | Lexicographically minimal rotation (canonical form) |
113
+ | `evenness(rhythm)` | Toussaint geometric evenness in (0, 1] |
114
+ | `syncopation(rhythm)` | Keith (1991) syncopation count |
115
+ | `rhythmic_oddity(rhythm)` | Pressing (1983) rhythmic oddity property |
116
+ | `offbeatness(rhythm)` | Count of onsets on off-beat positions (gcd-coprime to n) |
117
+ | `inter_onset_intervals(rhythm)` | Gaps in pulses between consecutive onsets, wrapping |
118
+ | `ioi_histogram(rhythm)` | Histogram of inter-onset interval lengths |
119
+ | `onset_positions(rhythm)` | Indices of onsets in a 0/1 pattern |
120
+ | `pattern_from_onsets(*, positions, steps)` | Build 0/1 pattern from onset indices |
121
+
122
+ ## Measures defined
123
+
124
+ **Evenness** (Toussaint 2005): Place onsets on a unit circle; sum all pairwise chord
125
+ lengths; normalize by the maximum (equally spaced onsets). Score 1.0 means maximally even.
126
+
127
+ **Syncopation** (Keith 1991): Metric weight of position `i` is `n` for the downbeat
128
+ (`i=0`) and the largest power of 2 dividing `i` for `i>0`. A syncopation occurs when
129
+ an onset at a weak beat is followed by a rest at a stronger beat; the score accumulates
130
+ the weight difference.
131
+
132
+ **Rhythmic oddity** (Pressing 1983): True if no two onsets are diametrically opposite
133
+ on the rhythm circle (no pair partitions the cycle into two equal halves).
134
+
135
+ **Offbeatness** (Toussaint): For a cycle of n pulses, position p is off-beat iff
136
+ gcd(p, n) == 1 -- equivalently, p is not covered by any regular subdivision of the
137
+ cycle (union of {k*n/d} for proper divisors d of n). Offbeatness is the count of onsets
138
+ at such positions. Both characterizations produce identical off-beat sets, verified for
139
+ n in 2..64.
140
+
141
+ **Inter-onset intervals**: The sequence of gaps (in pulses) between consecutive onsets
142
+ around the cycle, wrapping from the last onset back to the first. Always sums to n.
143
+
144
+ ## References
145
+
146
+ - Bjorklund, E. (2003). The theory of rep-rate pattern generation in the SNS timing system.
147
+ - Toussaint, G. (2005). The Euclidean algorithm generates traditional musical rhythms. BRIDGES.
148
+ - Keith, M. (1991). From Polychords to Polya: Adventures in Musical Combinatorics.
149
+ - Pressing, J. (1983). Cognitive isomorphisms between pitch and rhythm in world musics. Studies in Music.
150
+
151
+ ## License
152
+
153
+ MIT. Copyright (c) 2026 Amaar Chughtai.
@@ -0,0 +1,18 @@
1
+ # Security Policy
2
+
3
+ ## Scope
4
+
5
+ `euclidean-rhythm` is a pure computation library with no runtime dependencies, no network
6
+ access, and no file system access. The attack surface is limited to incorrect results from
7
+ malformed input, which the library guards against with explicit validation.
8
+
9
+ ## Reporting a vulnerability
10
+
11
+ If you find a security issue, please email amaar2cool@gmail.com with details and steps to
12
+ reproduce. Please do not open a public issue for security reports. You can expect an
13
+ initial response within a few days.
14
+
15
+ ## Supported versions
16
+
17
+ The latest published minor version receives fixes. Pre-1.0 releases may introduce breaking
18
+ changes in minor versions, as allowed by semantic versioning.
Binary file