abcnotes 0.1.0__py3-none-any.whl
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.
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abcnotes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Parse, validate and expand tunes in ABC music notation
|
|
5
|
+
Author: Luke Campagnola
|
|
6
|
+
Author-email: John McBride <jmmcbride@protonmail.com>
|
|
7
|
+
Maintainer-email: John McBride <jmmcbride@protonmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/jomimc/abcnotes
|
|
10
|
+
Project-URL: Issues, https://github.com/jomimc/abcnotes/issues
|
|
11
|
+
Keywords: abc,music,notation,folk,parser
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
17
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE.txt
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest; extra == "test"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
abcnotes
|
|
26
|
+
========
|
|
27
|
+
|
|
28
|
+
A parser for tunes written in [ABC music notation](https://abcnotation.com/),
|
|
29
|
+
for analysing the music rather than typesetting it. It reads a tune into
|
|
30
|
+
tokens, gives each note a pitch and a duration, writes out repeats and
|
|
31
|
+
variant endings in playback order, and reports problems in a tune's ABC.
|
|
32
|
+
|
|
33
|
+
It is a single module, `abcnotes.py`, with no dependencies. Python 3.9 or
|
|
34
|
+
later. MIT licence.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Installation
|
|
38
|
+
------------
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install abcnotes
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
or, from a clone of this repository:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Quick start
|
|
52
|
+
-----------
|
|
53
|
+
|
|
54
|
+
### Parse a tune
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from abcnotes import Tune
|
|
58
|
+
|
|
59
|
+
abc = """X: 1
|
|
60
|
+
T: The Road To Lisdoonvarna
|
|
61
|
+
R: slide
|
|
62
|
+
M: 12/8
|
|
63
|
+
L: 1/8
|
|
64
|
+
K: Edor
|
|
65
|
+
E2B B2A B2c d2A|F2A ABA D2E FED|E2B B2A B2c d3|cdc B2A B2E E3:|
|
|
66
|
+
|:e2f gfe d2B Bcd|c2A ABc d2B B3|e2f gfe d2B Bcd|cdc B2A B2E E3:||
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
tune = Tune(abc=abc)
|
|
70
|
+
print(tune.header['tune title'])
|
|
71
|
+
print(tune.header['key'], tune.header['meter'], tune.header['unit note length'])
|
|
72
|
+
print(len(tune.notes), len(tune.bars))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
The Road To Lisdoonvarna
|
|
77
|
+
Edor 12/8 1/8
|
|
78
|
+
69 10
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`tune.header` is keyed by the full field names from the ABC standard
|
|
82
|
+
(`'key'`, `'meter'`, `'unit note length'`, `'reference number'`, ...), with
|
|
83
|
+
the values as written. `tune.fields` keeps every field in order, including
|
|
84
|
+
repeated ones.
|
|
85
|
+
|
|
86
|
+
### Notes, pitches and durations
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
for note in tune.notes[:6]:
|
|
90
|
+
print(note.pitch.name, note.pitch.abs_value, note.duration, note.abs_duration)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
E 4 2.0 0.25
|
|
95
|
+
B 11 1.0 0.125
|
|
96
|
+
B 11 2.0 0.25
|
|
97
|
+
A 9 1.0 0.125
|
|
98
|
+
B 11 2.0 0.25
|
|
99
|
+
C# 13 1.0 0.125
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`note.pitch.abs_value` is the pitch in semitones relative to middle C, with
|
|
103
|
+
the key signature applied (the `c` in E dorian is a C sharp). `note.duration`
|
|
104
|
+
is in units of the `L:` field and `note.abs_duration` is a fraction of a
|
|
105
|
+
whole note. Broken rhythms (`A>B`), chords (`[DF]3`) and tuplets (`(3abc`)
|
|
106
|
+
are resolved in these values.
|
|
107
|
+
|
|
108
|
+
### Expand repeats
|
|
109
|
+
|
|
110
|
+
`tune.expand_repeats()` returns the bars in playback order. Each bar is a
|
|
111
|
+
list of tokens, so the notes of the tune as played are:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from abcnotes import Note, Tune
|
|
115
|
+
|
|
116
|
+
abc = """X: 2
|
|
117
|
+
T: Endings
|
|
118
|
+
M: 4/4
|
|
119
|
+
L: 1/4
|
|
120
|
+
K: G
|
|
121
|
+
|: G A B c |1 d2 d2 :|2 g2 g2 |]
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
tune = Tune(abc=abc)
|
|
125
|
+
for bar in tune.expand_repeats():
|
|
126
|
+
names = [token.pitch.name for token in bar if isinstance(token, Note)]
|
|
127
|
+
if names:
|
|
128
|
+
print(' '.join(names))
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
G A B C
|
|
133
|
+
D D
|
|
134
|
+
G A B C
|
|
135
|
+
G G
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
If the repeat structure is ambiguous (an ending number that is missing or
|
|
139
|
+
repeated, a `|:` that is never closed, endings with no repeat sign, ...) a
|
|
140
|
+
`RepeatError` is raised. Pass `strict=False` to get the most likely reading
|
|
141
|
+
instead; `check_repeats(tune.bars)` lists the problems without raising.
|
|
142
|
+
|
|
143
|
+
### Validate a file
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import abcnotes
|
|
147
|
+
|
|
148
|
+
text = """X: 1
|
|
149
|
+
T: First
|
|
150
|
+
M: 4/4
|
|
151
|
+
L: 1/8
|
|
152
|
+
K: D
|
|
153
|
+
D2 FA d2 fd | A2 F2 e2 | d2-f2 a2 f2 | d4 z4 |]
|
|
154
|
+
|
|
155
|
+
X: 2
|
|
156
|
+
T: Second
|
|
157
|
+
K: G
|
|
158
|
+
GBd g |]
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
for abc in abcnotes.split_tunes(text):
|
|
162
|
+
for issue in abcnotes.validate(abc):
|
|
163
|
+
print(issue)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
Issue(line=6, char=26, message='tie from d2 to a different note f2 (a slur was probably meant)')
|
|
168
|
+
Issue(line=6, char=15, message='bar 2 has length 3/4, expected 1')
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`split_tunes` cuts the text of an ABC file into one string per tune, with
|
|
172
|
+
any file-header fields prepended to each. `validate` returns a list of
|
|
173
|
+
`Issue(line, char, message)` namedtuples with 1-based positions in the
|
|
174
|
+
tune's text (`None` when the issue has no single position). It checks:
|
|
175
|
+
|
|
176
|
+
* required fields (`X:`, `T:`, `K:`) and header lines that are not fields;
|
|
177
|
+
* characters the tokenizer cannot read;
|
|
178
|
+
* unbalanced chord brackets, grace-note braces and slurs;
|
|
179
|
+
* ties between different notes;
|
|
180
|
+
* repeat structure (the same problems that make `expand_repeats` raise);
|
|
181
|
+
* `P:` parts in the header that are not in the body, and vice versa;
|
|
182
|
+
* bar lengths against the meter, tolerating pickups.
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
API overview
|
|
186
|
+
------------
|
|
187
|
+
|
|
188
|
+
Everything is in the `abcnotes` module.
|
|
189
|
+
|
|
190
|
+
| Name | What it is |
|
|
191
|
+
| --- | --- |
|
|
192
|
+
| `Tune(abc=text)` | Parse one tune from its ABC text. |
|
|
193
|
+
| `Tune(json=record)` | Parse a record from the [TheSession](https://thesession.org) data dump or API: a dict with keys `tune_id`, `setting_id`, `name`, `type`, `meter`, `mode` and `abc`. |
|
|
194
|
+
| `Tune(abc, strict=False)` | Unreadable characters become `Unknown` tokens instead of raising. |
|
|
195
|
+
| `tune.header` | Dict of header fields keyed by full name (`'key'`, `'meter'`, `'unit note length'`, ...). |
|
|
196
|
+
| `tune.fields` | All header fields in order, as `(name, value)` pairs. |
|
|
197
|
+
| `tune.tokens` | The body as a flat list of tokens. |
|
|
198
|
+
| `tune.notes` | The `Note` tokens only. |
|
|
199
|
+
| `tune.bars` | The tokens grouped by bar. Each bar starts with the `BarLine` that opens it, except for a pickup before the first bar line. |
|
|
200
|
+
| `tune.parts` | `(part name, bars)` pairs, split where a `P:` field appears in the body. |
|
|
201
|
+
| `tune.expand_repeats(strict=True)` | Bars in playback order, following the `P:` header field if there is one. |
|
|
202
|
+
| `tune.validate()` | Problems with the tune, as a list of `Issue`. |
|
|
203
|
+
| `Note` | `.pitch` (a `Pitch`, with `.name` and `.abs_value` in semitones from middle C), `.duration` (in units of `L:`), `.abs_duration` (fraction of a whole note), `.length` (as a `(numerator, denominator)` pair), `.tie`, `.grace`, `.accidental`, `.bar_accidental`, `.key`, `.time_sig`. |
|
|
204
|
+
| `Rest` | `.duration`, `.abs_duration` and `.length` as for `Note`; `.symbol` is one of `z Z x X`. |
|
|
205
|
+
| `BarLine` | `.repeat_start`, `.repeat_end`, `.endings` (list of pass numbers, or `None`) and `.double`. |
|
|
206
|
+
| `Key` | `Key('Edor')`: `.root`, `.mode`, `.key_signature`, `.accidentals` (including explicit ones such as `K:D ^g`), `.relative_ionian`. |
|
|
207
|
+
| `TimeSignature` | Meter, unit note length and tempo in force at a note (`note.time_sig`). |
|
|
208
|
+
| `expand_repeats(bars, strict=True)` | The module-level function behind `tune.expand_repeats`. |
|
|
209
|
+
| `check_repeats(bars)` | Repeat-structure problems as `(bar index, message)` pairs. |
|
|
210
|
+
| `RepeatError` | Raised by `expand_repeats` when the structure is ambiguous. |
|
|
211
|
+
| `validate(abc)` | Parse with `strict=False` and return `tune.validate()`. |
|
|
212
|
+
| `Issue` | `namedtuple('Issue', 'line char message')`. |
|
|
213
|
+
| `split_tunes(text)` | Cut the text of an ABC file into one string per tune. |
|
|
214
|
+
| `parse_parts(spec)` | Expand a `P:` field: `'(AB)2.C'` becomes `['A', 'B', 'A', 'B', 'C']`. |
|
|
215
|
+
|
|
216
|
+
Other token classes are `Space`, `Newline`, `Continuation`, `Slur`, `Tie`,
|
|
217
|
+
`Tuplet`, `ChordBracket`, `ChordSymbol`, `Annotation`, `Decoration`,
|
|
218
|
+
`GracenoteBrace`, `VoiceOverlay`, `BodyField`, `InlineField`, `Ignored` and
|
|
219
|
+
`Unknown`.
|
|
220
|
+
|
|
221
|
+
Some points of behaviour:
|
|
222
|
+
|
|
223
|
+
* An accidental lasts until the end of the bar, for the same note letter in
|
|
224
|
+
every octave, as the standard says; `I:propagate-accidentals` is honoured.
|
|
225
|
+
* Explicit accidentals in the `K:` field (`K:D ^g`, `K:C exp ^f`) are
|
|
226
|
+
applied.
|
|
227
|
+
* `K:`, `M:`, `L:` and `Q:` fields in the body, and inline fields such as
|
|
228
|
+
`[K:G]`, take effect from where they appear.
|
|
229
|
+
* Repeat expansion follows sections 4.8 to 4.10 of the ABC 2.1 standard:
|
|
230
|
+
`|1`, `[1,3` and `[1-3` endings, `::`, `|::` (three passes) and `P:`
|
|
231
|
+
parts. A `:|` with no matching `|:` restarts from the latest of the start
|
|
232
|
+
of the tune, a double bar and the previous `:|`.
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
Limitations
|
|
236
|
+
-----------
|
|
237
|
+
|
|
238
|
+
* Multi-voice tunes (`V:`) are parsed as a single line of music. Bar lengths
|
|
239
|
+
are not checked for them and `expand_repeats` refuses them unless
|
|
240
|
+
`strict=False`.
|
|
241
|
+
* `D.C.`, `D.S.`, coda and fine jumps are not followed.
|
|
242
|
+
* Lyrics (`w:`) are not aligned to notes.
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
Status
|
|
246
|
+
------
|
|
247
|
+
|
|
248
|
+
Alpha. The parser has been checked against the whole TheSession corpus and
|
|
249
|
+
the Norbeck, Cobbs, Nottingham and Chambers collections, and repeat
|
|
250
|
+
expansion has been cross-checked against music21. The API may still change.
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
Development
|
|
254
|
+
-----------
|
|
255
|
+
|
|
256
|
+
Run the tests from the root of the repository:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
python -m pytest
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`pyproject.toml` puts the repository root on the path for pytest, so no
|
|
263
|
+
install is needed.
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
History and credits
|
|
267
|
+
-------------------
|
|
268
|
+
|
|
269
|
+
abcnotes started as [pyabc](https://github.com/campagnola/pyabc), written by
|
|
270
|
+
Luke Campagnola in 2014 and released under the MIT licence as a pre-alpha.
|
|
271
|
+
John McBride forked it to read the TheSession data set and fixed and
|
|
272
|
+
extended it. In September 2026, in
|
|
273
|
+
[issue #31](https://github.com/campagnola/pyabc/issues/31) of the original
|
|
274
|
+
repository, Luke Campagnola agreed that the fork becomes the maintained
|
|
275
|
+
version under a new name, published to PyPI, and that the original
|
|
276
|
+
repository will point here.
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
Licence
|
|
280
|
+
-------
|
|
281
|
+
|
|
282
|
+
MIT. See `LICENSE.txt`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
abcnotes.py,sha256=WW8araiK7-IMf6niX_dPREnNghi7wpQDSk8Zu9N91kw,60816
|
|
2
|
+
abcnotes-0.1.0.dist-info/licenses/LICENSE.txt,sha256=hPhvKiUCRagmalAQG6Rg43U1BTl9juX7gsFx_Pi6Keg,1072
|
|
3
|
+
abcnotes-0.1.0.dist-info/METADATA,sha256=HMCs_jy3zoOcR4J7kTeJfw0iX6_tuEyAQN2K2DWJsck,9371
|
|
4
|
+
abcnotes-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
5
|
+
abcnotes-0.1.0.dist-info/top_level.txt,sha256=uaGmRhjA_I0LKR0CCleKThNZXVMOGyxE975EDBFovPI,9
|
|
6
|
+
abcnotes-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright 2018 Luke Campagnola
|
|
2
|
+
|
|
3
|
+
The MIT License
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
9
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
abcnotes
|