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.
- euclidean_rhythm-0.2.0/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
- euclidean_rhythm-0.2.0/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
- euclidean_rhythm-0.2.0/.github/workflows/ci.yml +22 -0
- euclidean_rhythm-0.2.0/.gitignore +18 -0
- euclidean_rhythm-0.2.0/CHANGELOG.md +34 -0
- euclidean_rhythm-0.2.0/CLAUDE.md +47 -0
- euclidean_rhythm-0.2.0/CODE_OF_CONDUCT.md +37 -0
- euclidean_rhythm-0.2.0/CONTRIBUTING.md +31 -0
- euclidean_rhythm-0.2.0/LICENSE +21 -0
- euclidean_rhythm-0.2.0/PKG-INFO +205 -0
- euclidean_rhythm-0.2.0/README.md +153 -0
- euclidean_rhythm-0.2.0/SECURITY.md +18 -0
- euclidean_rhythm-0.2.0/assets/logo.png +0 -0
- euclidean_rhythm-0.2.0/docs/architecture.md +77 -0
- euclidean_rhythm-0.2.0/docs/charter.md +30 -0
- euclidean_rhythm-0.2.0/docs/logo-prompt.md +7 -0
- euclidean_rhythm-0.2.0/examples/basic_usage.py +43 -0
- euclidean_rhythm-0.2.0/pyproject.toml +63 -0
- euclidean_rhythm-0.2.0/src/euclidean_rhythm/__init__.py +27 -0
- euclidean_rhythm-0.2.0/src/euclidean_rhythm/analysis.py +177 -0
- euclidean_rhythm-0.2.0/src/euclidean_rhythm/cli.py +44 -0
- euclidean_rhythm-0.2.0/src/euclidean_rhythm/generation.py +95 -0
- euclidean_rhythm-0.2.0/src/euclidean_rhythm/metrics.py +224 -0
- euclidean_rhythm-0.2.0/src/euclidean_rhythm/operations.py +72 -0
- euclidean_rhythm-0.2.0/src/euclidean_rhythm/py.typed +0 -0
- euclidean_rhythm-0.2.0/tests/__init__.py +0 -0
- euclidean_rhythm-0.2.0/tests/test_analysis.py +109 -0
- euclidean_rhythm-0.2.0/tests/test_cli.py +41 -0
- euclidean_rhythm-0.2.0/tests/test_generation.py +99 -0
- euclidean_rhythm-0.2.0/tests/test_metrics.py +312 -0
- 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,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
|