hyperscore 0.1.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.
- hyperscore-0.1.0/PKG-INFO +128 -0
- hyperscore-0.1.0/README.md +112 -0
- hyperscore-0.1.0/pyproject.toml +59 -0
- hyperscore-0.1.0/src/hyperscore/__init__.py +35 -0
- hyperscore-0.1.0/src/hyperscore/core/__init__.py +42 -0
- hyperscore-0.1.0/src/hyperscore/core/score.py +369 -0
- hyperscore-0.1.0/src/hyperscore/core/time.py +100 -0
- hyperscore-0.1.0/src/hyperscore/core/time_pipeline.py +118 -0
- hyperscore-0.1.0/src/hyperscore/core/time_transforms.py +109 -0
- hyperscore-0.1.0/src/hyperscore/io/__init__.py +29 -0
- hyperscore-0.1.0/src/hyperscore/io/midi.py +355 -0
- hyperscore-0.1.0/src/hyperscore/py.typed +0 -0
- hyperscore-0.1.0/src/hyperscore/rhythm/__init__.py +32 -0
- hyperscore-0.1.0/src/hyperscore/rhythm/rhythm_tree.py +384 -0
- hyperscore-0.1.0/src/hyperscore/theory/__init__.py +41 -0
- hyperscore-0.1.0/src/hyperscore/theory/pcset.py +145 -0
- hyperscore-0.1.0/src/hyperscore/theory/scales.py +296 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: hyperscore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Structural music composition framework
|
|
5
|
+
Author: inaba1115
|
|
6
|
+
Author-email: inaba1115 <inaba1115@gmail.com>
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering
|
|
11
|
+
Requires-Dist: lark>=1.3.1
|
|
12
|
+
Requires-Dist: mido>=1.3.3
|
|
13
|
+
Requires-Dist: python-rtmidi>=1.5.8
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# hyperscore
|
|
18
|
+
|
|
19
|
+
**hyperscore** is a structural music composition framework that models music as explicit, composable structure rather than notation or performance.
|
|
20
|
+
|
|
21
|
+
Time is represented uniformly using immutable time spans, pitch is handled as pitch-class structure independent of register, and rhythm is expressed as relative proportions that are resolved into concrete time only when needed. External formats such as MIDI are treated as lossy boundaries rather than as the source of truth.
|
|
22
|
+
|
|
23
|
+
hyperscore is designed for experimentation, analysis, and integration with algorithmic systems, rather than for score engraving or DAW-style workflows.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Minimal example
|
|
28
|
+
|
|
29
|
+
This minimal example demonstrates the core hyperscore workflow: selecting pitch material using theory objects, describing rhythm structurally, generating time-based events, and exporting the result to MIDI.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from hyperscore import CHORDS, Score, parse_rhythm
|
|
33
|
+
from hyperscore.core import NoteEvent, bpm_to_ms
|
|
34
|
+
from hyperscore.io import MidiExporter
|
|
35
|
+
from hyperscore.rhythm import rhythm_ast_to_timespans
|
|
36
|
+
|
|
37
|
+
# ----------------
|
|
38
|
+
# theory
|
|
39
|
+
# ----------------
|
|
40
|
+
chord = CHORDS["major7"]
|
|
41
|
+
|
|
42
|
+
pitches = [n for n in range(60, 72) if n % 12 in chord.intervals]
|
|
43
|
+
pitch_iter = iter(pitches)
|
|
44
|
+
|
|
45
|
+
# ----------------
|
|
46
|
+
# rhythm
|
|
47
|
+
# ----------------
|
|
48
|
+
ast = parse_rhythm("1*4")
|
|
49
|
+
total = int(bpm_to_ms(120, 1))
|
|
50
|
+
spans = rhythm_ast_to_timespans(ast, total=total)
|
|
51
|
+
|
|
52
|
+
# ----------------
|
|
53
|
+
# score
|
|
54
|
+
# ----------------
|
|
55
|
+
score = Score()
|
|
56
|
+
|
|
57
|
+
score.add_timespans(
|
|
58
|
+
spans,
|
|
59
|
+
factory=lambda span: NoteEvent(
|
|
60
|
+
pitch=next(pitch_iter),
|
|
61
|
+
velocity=100,
|
|
62
|
+
span=span,
|
|
63
|
+
channel=0,
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# ----------------
|
|
68
|
+
# output
|
|
69
|
+
# ----------------
|
|
70
|
+
MidiExporter().export(score, "example.mid")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This example intentionally avoids musical interpretation and focuses on structural composition. Pitch, rhythm, and time are treated as independent layers that are combined explicitly.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## What this example demonstrates
|
|
78
|
+
|
|
79
|
+
- Rhythm is expressed as **structure**, not notation
|
|
80
|
+
- Time is handled explicitly via immutable `TimeSpan` objects
|
|
81
|
+
- Pitch and rhythm are **independent layers**
|
|
82
|
+
- MIDI is treated as a **lossy output format**
|
|
83
|
+
|
|
84
|
+
For more advanced usage, see:
|
|
85
|
+
|
|
86
|
+
- `examples/`
|
|
87
|
+
- `tests/test_smoke.py`
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Optional: ZippedNotes shortcut
|
|
92
|
+
|
|
93
|
+
For simple sequential note generation without explicit TimeSpan construction, hyperscore provides the `ZippedNotes` convenience API.
|
|
94
|
+
|
|
95
|
+
This approach is suitable for basic sketches or quick tests, but offers less control than TimeSpan-based workflows.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from hyperscore import Score
|
|
99
|
+
from hyperscore.core import NoteEvent
|
|
100
|
+
|
|
101
|
+
score = Score()
|
|
102
|
+
|
|
103
|
+
score.add(
|
|
104
|
+
pitch=[60, 64, 67, 71], # C major 7 chord tones
|
|
105
|
+
velocity=[100],
|
|
106
|
+
duration=[125],
|
|
107
|
+
channel=[0],
|
|
108
|
+
event_factory=lambda **kw: NoteEvent(
|
|
109
|
+
pitch=kw["pitch"],
|
|
110
|
+
velocity=kw["velocity"],
|
|
111
|
+
span=kw["span"],
|
|
112
|
+
channel=kw["channel"],
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
For complex timing, transformations, or algorithmic rhythm generation, prefer TimeSpan-based workflows using `parse_rhythm`, `rhythm_ast_to_timespans`, and `TimeSpanPipeline`.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Project status
|
|
122
|
+
|
|
123
|
+
- Python >= 3.10
|
|
124
|
+
- Typed (`py.typed`)
|
|
125
|
+
- Experimental / research-oriented
|
|
126
|
+
- API may evolve between minor versions
|
|
127
|
+
|
|
128
|
+
hyperscore favors clarity of structure and explicitness of time over completeness or stylistic prescription.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# hyperscore
|
|
2
|
+
|
|
3
|
+
**hyperscore** is a structural music composition framework that models music as explicit, composable structure rather than notation or performance.
|
|
4
|
+
|
|
5
|
+
Time is represented uniformly using immutable time spans, pitch is handled as pitch-class structure independent of register, and rhythm is expressed as relative proportions that are resolved into concrete time only when needed. External formats such as MIDI are treated as lossy boundaries rather than as the source of truth.
|
|
6
|
+
|
|
7
|
+
hyperscore is designed for experimentation, analysis, and integration with algorithmic systems, rather than for score engraving or DAW-style workflows.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Minimal example
|
|
12
|
+
|
|
13
|
+
This minimal example demonstrates the core hyperscore workflow: selecting pitch material using theory objects, describing rhythm structurally, generating time-based events, and exporting the result to MIDI.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from hyperscore import CHORDS, Score, parse_rhythm
|
|
17
|
+
from hyperscore.core import NoteEvent, bpm_to_ms
|
|
18
|
+
from hyperscore.io import MidiExporter
|
|
19
|
+
from hyperscore.rhythm import rhythm_ast_to_timespans
|
|
20
|
+
|
|
21
|
+
# ----------------
|
|
22
|
+
# theory
|
|
23
|
+
# ----------------
|
|
24
|
+
chord = CHORDS["major7"]
|
|
25
|
+
|
|
26
|
+
pitches = [n for n in range(60, 72) if n % 12 in chord.intervals]
|
|
27
|
+
pitch_iter = iter(pitches)
|
|
28
|
+
|
|
29
|
+
# ----------------
|
|
30
|
+
# rhythm
|
|
31
|
+
# ----------------
|
|
32
|
+
ast = parse_rhythm("1*4")
|
|
33
|
+
total = int(bpm_to_ms(120, 1))
|
|
34
|
+
spans = rhythm_ast_to_timespans(ast, total=total)
|
|
35
|
+
|
|
36
|
+
# ----------------
|
|
37
|
+
# score
|
|
38
|
+
# ----------------
|
|
39
|
+
score = Score()
|
|
40
|
+
|
|
41
|
+
score.add_timespans(
|
|
42
|
+
spans,
|
|
43
|
+
factory=lambda span: NoteEvent(
|
|
44
|
+
pitch=next(pitch_iter),
|
|
45
|
+
velocity=100,
|
|
46
|
+
span=span,
|
|
47
|
+
channel=0,
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# ----------------
|
|
52
|
+
# output
|
|
53
|
+
# ----------------
|
|
54
|
+
MidiExporter().export(score, "example.mid")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This example intentionally avoids musical interpretation and focuses on structural composition. Pitch, rhythm, and time are treated as independent layers that are combined explicitly.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## What this example demonstrates
|
|
62
|
+
|
|
63
|
+
- Rhythm is expressed as **structure**, not notation
|
|
64
|
+
- Time is handled explicitly via immutable `TimeSpan` objects
|
|
65
|
+
- Pitch and rhythm are **independent layers**
|
|
66
|
+
- MIDI is treated as a **lossy output format**
|
|
67
|
+
|
|
68
|
+
For more advanced usage, see:
|
|
69
|
+
|
|
70
|
+
- `examples/`
|
|
71
|
+
- `tests/test_smoke.py`
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Optional: ZippedNotes shortcut
|
|
76
|
+
|
|
77
|
+
For simple sequential note generation without explicit TimeSpan construction, hyperscore provides the `ZippedNotes` convenience API.
|
|
78
|
+
|
|
79
|
+
This approach is suitable for basic sketches or quick tests, but offers less control than TimeSpan-based workflows.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from hyperscore import Score
|
|
83
|
+
from hyperscore.core import NoteEvent
|
|
84
|
+
|
|
85
|
+
score = Score()
|
|
86
|
+
|
|
87
|
+
score.add(
|
|
88
|
+
pitch=[60, 64, 67, 71], # C major 7 chord tones
|
|
89
|
+
velocity=[100],
|
|
90
|
+
duration=[125],
|
|
91
|
+
channel=[0],
|
|
92
|
+
event_factory=lambda **kw: NoteEvent(
|
|
93
|
+
pitch=kw["pitch"],
|
|
94
|
+
velocity=kw["velocity"],
|
|
95
|
+
span=kw["span"],
|
|
96
|
+
channel=kw["channel"],
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For complex timing, transformations, or algorithmic rhythm generation, prefer TimeSpan-based workflows using `parse_rhythm`, `rhythm_ast_to_timespans`, and `TimeSpanPipeline`.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Project status
|
|
106
|
+
|
|
107
|
+
- Python >= 3.10
|
|
108
|
+
- Typed (`py.typed`)
|
|
109
|
+
- Experimental / research-oriented
|
|
110
|
+
- API may evolve between minor versions
|
|
111
|
+
|
|
112
|
+
hyperscore favors clarity of structure and explicitness of time over completeness or stylistic prescription.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hyperscore"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Structural music composition framework"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "inaba1115", email = "inaba1115@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"lark>=1.3.1",
|
|
12
|
+
"mido>=1.3.3",
|
|
13
|
+
"python-rtmidi>=1.5.8",
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Topic :: Multimedia :: Sound/Audio",
|
|
19
|
+
"Topic :: Scientific/Engineering",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["uv_build>=0.9.21,<0.10.0"]
|
|
24
|
+
build-backend = "uv_build"
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = [
|
|
28
|
+
"pytest>=9.0.2",
|
|
29
|
+
"pytest-cov>=7.0.0",
|
|
30
|
+
"ruff>=0.14.10",
|
|
31
|
+
"ty>=0.0.8",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 120
|
|
36
|
+
|
|
37
|
+
[tool.ruff.lint]
|
|
38
|
+
extend-select = ["I", "RUF", "UP", "TID", "C", "SIM", "PTH"]
|
|
39
|
+
extend-ignore = ["C901", "F401", "F403", "E722", "RUF007"]
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
minversion = "9.0"
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
addopts = [
|
|
45
|
+
"-ra",
|
|
46
|
+
"--strict-markers",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.coverage.run]
|
|
50
|
+
branch = true
|
|
51
|
+
source = ["hyperscore"]
|
|
52
|
+
omit = [
|
|
53
|
+
"*/__init__.py",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[tool.coverage.report]
|
|
57
|
+
show_missing = true
|
|
58
|
+
skip_covered = true
|
|
59
|
+
precision = 2
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
hyperscore: a structural music composition framework.
|
|
3
|
+
|
|
4
|
+
hyperscore provides a minimal, composable foundation for
|
|
5
|
+
algorithmic and structural music composition.
|
|
6
|
+
|
|
7
|
+
Key ideas
|
|
8
|
+
---------
|
|
9
|
+
- Music is modeled as explicit structure, not notation.
|
|
10
|
+
- Time is represented uniformly using immutable TimeSpan objects.
|
|
11
|
+
- Pitch is handled as pitch-class structure, independent of register.
|
|
12
|
+
- External formats (e.g. MIDI) are treated as lossy boundaries.
|
|
13
|
+
|
|
14
|
+
The library is organized into clear layers:
|
|
15
|
+
- core: time and event primitives
|
|
16
|
+
- rhythm: structural rhythm descriptions
|
|
17
|
+
- theory: pitch-class and scale structures
|
|
18
|
+
- io: adapters to external systems
|
|
19
|
+
|
|
20
|
+
hyperscore is designed for experimentation, analysis,
|
|
21
|
+
and integration with other algorithmic systems,
|
|
22
|
+
rather than for direct score engraving or DAW replacement.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from hyperscore.core import Score, ZippedNotes
|
|
26
|
+
from hyperscore.rhythm import parse_rhythm
|
|
27
|
+
from hyperscore.theory import CHORDS, SCALES
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"CHORDS",
|
|
31
|
+
"SCALES",
|
|
32
|
+
"Score",
|
|
33
|
+
"ZippedNotes",
|
|
34
|
+
"parse_rhythm",
|
|
35
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core time and event primitives for hyperscore.
|
|
3
|
+
|
|
4
|
+
This module defines the foundational abstractions used throughout
|
|
5
|
+
hyperscore:
|
|
6
|
+
|
|
7
|
+
- TimeSpan: immutable representation of linear time intervals
|
|
8
|
+
- NoteEvent: minimal time-bounded musical event
|
|
9
|
+
- Score: container and coordinator for events on a time axis
|
|
10
|
+
- TimeSpanPipeline: composable transformations over TimeSpans
|
|
11
|
+
- ZippedNotes: convenience API for simple sequential note generation
|
|
12
|
+
|
|
13
|
+
Design principles
|
|
14
|
+
-----------------
|
|
15
|
+
- Time is represented explicitly and uniformly via TimeSpan.
|
|
16
|
+
- Core abstractions are unit-agnostic (milliseconds, ticks, etc.).
|
|
17
|
+
- Musical interpretation (harmony, rhythm, MIDI) is handled in
|
|
18
|
+
higher-level modules.
|
|
19
|
+
- All core objects favor immutability and composability.
|
|
20
|
+
|
|
21
|
+
This module is intentionally minimal and free of musical semantics.
|
|
22
|
+
It serves as the stable foundation upon which rhythm, theory,
|
|
23
|
+
and I/O layers are built.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from .score import NoteEvent, Score, ZippedNotes
|
|
27
|
+
from .time import TimeSpan, bpm_to_ms
|
|
28
|
+
from .time_pipeline import TimeSpanPipeline
|
|
29
|
+
from .time_transforms import gate, probability, shift, stretch
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"NoteEvent",
|
|
33
|
+
"Score",
|
|
34
|
+
"TimeSpan",
|
|
35
|
+
"TimeSpanPipeline",
|
|
36
|
+
"ZippedNotes",
|
|
37
|
+
"bpm_to_ms",
|
|
38
|
+
"gate",
|
|
39
|
+
"probability",
|
|
40
|
+
"shift",
|
|
41
|
+
"stretch",
|
|
42
|
+
]
|