Tune sources¶
Here we demonstrate included utilities for loading tune data.
from pyabc2 import Tune
from pyabc2.sources import load_example, norbeck, the_session, eskin, bill_black, hardy
A few examples are included in the package, accessible with pyabc2.sources.load_example() (returns Tune) and pyabc2.sources.load_example_abc() (returns ABC string).
load_example("For the Love of Music")
Tune(title='For The Love Of Music', key=Gmaj, type='slip jig')
The tune source modules, demonstrated below, download tune data from the internet.
Norbeck¶
norbeck.load() gives us a list of Tunes for one of Norbeck’s tune type groups (e.g. ‘jigs’, ‘reels’, ‘slip jigs’).
tunes = norbeck.load("jigs")
print(len(tunes), "jigs loaded")
tunes[0]
downloading...
done
556 jigs loaded
Tune(title="Bride's Favourite, The", key=Gmaj, type='jig')
tunes[-1]
Tune(title='Stone Step, The', key=Gmaj, type='jig')
The Session¶
the_session.load() gives us a list of Tunes loaded from a (frequently updated) archive of all of the tunes in The Session. This is a large dataset, so here we cap the processing.
tunes = the_session.load(n=500)
tunes[0]
downloading...
done
/tmp/ipykernel_738/93315575.py:1: UserWarning: 19 out of 500 The Session tune(s) failed to load. Enable logging debug messages to see more info.
tunes = the_session.load(n=500)
Tune(title="'Ma' McNulty's Favourite", key=Edor, type='hornpipe')
tunes[-1]
Tune(title='A Tribute To Jim', key=Dmaj, type='jig')
tune = the_session.load_url("https://thesession.org/tunes/21799#setting43712")
tune
Tune(title='The Cherrytree', key=Gmaj, type='jig')
tune.print_measures()
01: G2 d c B G
02: G F G A F D
03: G2 d A G G
04: d e e f g g
05: G2 d c B G
06: G F G A F D
07: B c c g2 B
08: B c g a f d
09: G2 d c B G
10: G F G A F D
11: G2 d A G G
12: d e e f g g
13: G2 d c B G
14: G F G A F D
15: B c c e2 B
16: A G E G A F
17: E B e d B e
18: d e g d B e
19: f B g f B f
20: f g g g a a
21: g a f f g e
22: f g d e d B
23: B d e f g A
24: A B d A G F
25: E B e d B e
26: d e g d B e
27: f B g f B f
28: f g g g a a
29: b2 a a g e
30: f g e e d e
31: d f a e d e
32: d B A A G F
Data archive¶
The Session data archive (https://github.com/adactio/TheSession-data) has many datasets (pyabc2.sources.the_session.load_meta()),
which we can use in other ways besides parsing ABCs to Tunes.
For example, we can look for the most common ABC notes in the corpus.
%%time
df = the_session.load_meta("tunes", convert_dtypes=True)
df
CPU times: user 848 ms, sys: 163 ms, total: 1.01 s
Wall time: 1.13 s
| tune_id | setting_id | name | type | meter | mode | abc | date | username | composer | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 26102 | 55813 | 'Ma' McNulty's Favourite | hornpipe | 4/4 | Edorian | B2 E>F G>A B2|A2 D>E F>G A2| B2 E>F G>AB>c| d>... | 2025-11-13 19:01:49 | The Archivist | <NA> |
| 1 | 15326 | 28560 | 'S Ann An Ìle | strathspey | 4/4 | Gmajor | |:G>A B>G c>A B>G|E<E A>G F<D D2|G>A B>G c>A B... | 2016-03-31 15:34:45 | danninagh | <NA> |
| 2 | 15326 | 28582 | 'S Ann An Ìle | strathspey | 4/4 | Gmajor | uD2|:{F}v[G,2G2]uB>ud c>A B>G|{D}E2 uA>uG F<D ... | 2016-04-03 09:15:08 | DonaldK | <NA> |
| 3 | 14625 | 26955 | 'S Daor An Tabac | reel | 4/4 | Bminor | |:eAAB eABB|eAAB gedB|eAAB eABB|G2AB gedB:|\r\... | 2015-07-31 02:47:47 | Charles Mackenzie | <NA> |
| 4 | 5478 | 55481 | 'S Iomadh Rud A Chunnaic Mi | reel | 4/4 | Gmajor | ABBA GEDE|G2AG EGDG|DBBA GEDE|GEDE G3A|\r\nDBB... | 2025-10-06 06:21:01 | JACKB | <NA> |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 55469 | 11995 | 11995 | Zoidberg's | jig | 6/8 | Dmajor | |:f2f f2a|fed B2A|A2A ABd|e2e ~e2e|\r\nf2f f2a... | 2012-06-19 12:12:46 | DrugCrazed | Patrick Rose |
| 55470 | 22155 | 44612 | Zolloko San Martinak | polka | 2/4 | Fmajor | K:F\r\na|:"A" ad' e'f'|"Dm" (3e'e'e' d'a|ac' b... | 2022-08-21 12:24:32 | Fernando Durbán Galnares | Kepa Junkera |
| 55471 | 11584 | 11584 | Zonaradikos | jig | 6/8 | Dmajor | A|:d3 e3|f3f2e|fga gfe|d2c B2A|\r\nd3 e3|f3f2e... | 2011-11-14 16:06:26 | gian marco | <NA> |
| 55472 | 9013 | 9013 | Zucchini Reel, The | reel | 4/4 | Dmajor | Ac|d2d=c ADFA|G2BG =cGBG|Add=c ADFD|GBAG FDAc|... | 2008-10-18 06:05:43 | Shelley | <NA> |
| 55473 | 13875 | 24924 | Zuppa Inglese | jig | 6/8 | Gmajor | |:GAG GAB|cBc cde|d2B GAB|A2G E2D|\r\nGAG GAB|... | 2014-10-07 22:55:26 | Edward Nunn | <NA> |
55474 rows × 10 columns
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 55474 entries, 0 to 55473
Data columns (total 10 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 tune_id 55474 non-null Int64
1 setting_id 55474 non-null Int64
2 name 55474 non-null string
3 type 55474 non-null category
4 meter 55474 non-null category
5 mode 55474 non-null category
6 abc 55474 non-null string
7 date 55474 non-null datetime64[ns]
8 username 55474 non-null string
9 composer 19069 non-null string
dtypes: Int64(2), category(3), datetime64[ns](1), string(4)
memory usage: 3.2 MB
from pyabc2.note import RE_NOTE as rx
rx
re.compile(r"(?P<acc>\^|\^\^|=|_|__)?(?P<note>[a-gA-G])(?P<oct>[,']*)(?P<num>[0-9]+)?(?P<slash>/+)?(?P<den>[0-9]+)?",
re.UNICODE)
This regular expression does also match letters in tune titles, say.
["".join(tup) for tup in rx.findall("the quick brown fox jumps over the lazy dog")]
['e', 'c', 'b', 'f', 'e', 'e', 'a', 'd', 'g']
But The Session stores the tune body separately (in the abc field) and encourages a bare-bones melody-focused approach, so we can expect to mostly be matching actual notes.
from pprint import pprint
cool = df.query("tune_id == 1 and setting_id == 1")
display(cool.T)
abc = cool.abc.iloc[0]
print(abc, "\n")
pprint([m.group() for m in rx.finditer(abc)], compact=True)
| 10504 | |
|---|---|
| tune_id | 1 |
| setting_id | 1 |
| name | Cooley's |
| type | reel |
| meter | 4/4 |
| mode | Edorian |
| abc | |:D2|EBBA B2 EB|B2 AB dBAG|FDAD BDAD|FDAD dAFD... |
| date | 2001-05-14 18:45:18 |
| username | Jeremy |
| composer | <NA> |
|:D2|EBBA B2 EB|B2 AB dBAG|FDAD BDAD|FDAD dAFD|
EBBA B2 EB|B2 AB defg|afec dBAF|DEFD E2:|
|:gf|eB B2 efge|eB B2 gedB|A2 FA DAFA|A2 FA defg|
eB B2 eBgB|eB B2 defg|afec dBAF|DEFD E2:|
['D2', 'E', 'B', 'B', 'A', 'B2', 'E', 'B', 'B2', 'A', 'B', 'd', 'B', 'A', 'G',
'F', 'D', 'A', 'D', 'B', 'D', 'A', 'D', 'F', 'D', 'A', 'D', 'd', 'A', 'F', 'D',
'E', 'B', 'B', 'A', 'B2', 'E', 'B', 'B2', 'A', 'B', 'd', 'e', 'f', 'g', 'a',
'f', 'e', 'c', 'd', 'B', 'A', 'F', 'D', 'E', 'F', 'D', 'E2', 'g', 'f', 'e',
'B', 'B2', 'e', 'f', 'g', 'e', 'e', 'B', 'B2', 'g', 'e', 'd', 'B', 'A2', 'F',
'A', 'D', 'A', 'F', 'A', 'A2', 'F', 'A', 'd', 'e', 'f', 'g', 'e', 'B', 'B2',
'e', 'B', 'g', 'B', 'e', 'B', 'B2', 'd', 'e', 'f', 'g', 'a', 'f', 'e', 'c',
'd', 'B', 'A', 'F', 'D', 'E', 'F', 'D', 'E2']
%%time
note_counts = (
df.abc
.str.findall(rx)
.explode()
.str.join("")
.value_counts()
)
note_counts
CPU times: user 4.64 s, sys: 446 ms, total: 5.09 s
Wall time: 5.07 s
abc
A 750802
d 685132
B 679911
e 571636
c 463044
...
_f4 1
f'/2 1
^b4 1
_G4 1
_A4 1
Name: count, Length: 1040, dtype: int64
note_counts[:20]
abc
A 750802
d 685132
B 679911
e 571636
c 463044
G 460719
f 402395
F 325427
g 311915
E 276070
D 237697
a 209657
A2 99258
d2 96281
B2 82005
G2 77033
b 61527
e2 61373
c2 48448
C 46178
Name: count, dtype: int64
👆 We can see that A (unit duration) is the leader, being a prominent pitch in many of the common keys.
5 in Dmaj
2 in Gmaj
1 in Ador, Amin, Amix, Amaj
Note
A implies A₄, the A above middle C, the A string on a violin, the lower register on the flute, etc.
Note
In general we don’t know the duration of A without context (L: header field, or based on M: if L: is not set).
However, in this case, we know that The Session presets the unit duration to 1/8,
so A is an eighth note.
from textwrap import wrap
print("\n".join(wrap(" ".join(note_counts[note_counts == 1].index))))
b,,3 =G8 B4/ A2/4 e2// e'1 D'' f9/ =c1 _d6 B7/4 _c3 =e6
B11 e,,2 f,,3 A6// b5/ A,12 ^c'4 ^c'3 E6/ e2/4 D2/3 =c2/3
=A4 ^G,, ^A,, e,,3 G6/ A4/3 =A/4 g,/ d16 =c16 E24 ^f2/3
^c2/ A11 C,,2 _d/ F//// F/// E33 c,,, D,,2 D,,3 d'/4 ^A3/4
A,9 B,' f6/2 e6/2 f,3 d6/ _d'3 ^b/ D11 C6/ ^D/2 ^B3/4 A'/
a'4 e4/ f4/ =e// ^g/2 ^A// ^D6 ^D10 ^D8 e'3/2 ^f'2 D,'3
^A10 ^a8 _A,6 b,/2 D/8 E/8 B/8 _a3/2 G,12 _e8 ^F' a'/ ^G10
^C,2 ^C, =E,2 _g/ d1/3 ^c,2 A7/2 ^a// e5/2 e7/2 ^B,2 c'3/4
d5/2 e'' e8/3 a8/3 B32 c'1/2 C,12 =B5 A7/9 e23 ^E,2 =F,4
f,8 =D6 __d d14 _c'2 c7 a/3 B16 G16 E23 A'4 E,,4 D22 _e'/
f5/2 =c'3/2 =G6 E5/4 ^G' A/1 =B/4 B,9 ^A5 c13 G'4 F'3 =a3
G/1 B,// _B,4 _a4 e,2 ^g/8 _c' =c'4 E75 B'/2 F,6 E,8 ^e/2
c9 =b, b'2 c3// D'/ C'/ =C, ^F,6 =B,6 B,1 D,1 =F,3 =F,,3
_B,3/2 _f4 f'/2 ^b4 _G4 _A4
👆 A variety of ABC note specs appear only once. Many of these have unusual durations or accidentals.
What if we ignore everything except the natural note name?
nat_cased_counts = (
note_counts
.reset_index(drop=False)
.rename(columns={"abc": "note"})
.assign(nat=lambda df: df.note.str.extract(r"([a-gA-G])"))
.groupby("nat")
.aggregate({"count": "sum"})["count"]
.sort_values(ascending=False)
)
nat_cased_counts
nat
A 946059
B 864777
d 844838
e 680163
G 604338
c 589108
f 484251
g 391948
F 391164
E 350571
D 308109
a 256851
b 73074
C 56493
Name: count, dtype: int64
👆 A is still our leader, but otherwise things have shifted a bit.
Note C, which generally implies a pitch outside of the range of most whistles and flutes,
has the lowest count.
Although b is inside that range, many tunes don’t have one.
from pyabc2 import Note
(
nat_cased_counts
.to_frame()
.assign(value=lambda df: df.index.map(lambda x: Note.from_abc(x).value))
.sort_values("value")["count"]
.plot.bar(
xlabel="ABC letters\n(accidentals, octave indicators, and context in key ignored)",
rot=0,
ylabel="Count",
title="ABC prevalance in The Session",
)
);
Eskin¶
Michael Eskin has tunebooks available at https://michaeleskin.com/tunebooks.html, viewable with his ABC Transcription Tools.
We can load selected tunebooks from there, e.g. the King Street Sessions:
df = eskin.load_meta("kss")
df
downloading...
done
| id | abc | group | |
|---|---|---|---|
| 0 | sir-john-fenwick-s-the-flower-amang-them-all | X: 929\nT:Sir John Fenwick's the flower amang ... | Air |
| 1 | farewell-to-whiskey | X: 940\nT:Farewell to whiskey\nR:Air\nO:Scotla... | Air |
| 2 | give-me-your-hand | X: 942\nT:Give me your hand\nR:Air\nQ:180\nC:R... | Air |
| 3 | the-hills-of-lorne | X: 943\nT:The hills of Lorne\nR:Air\nO:Scotlan... | Air |
| 4 | mo-ghile-mear | X: 944\nT:Mo Ghile Mear\nR:Air\nP:Play AABA\nB... | Air |
| ... | ... | ... | ... |
| 1001 | mrs-power | X: 982\nT:Mrs. Power\nT:O'Carolan's concerto\n... | Other |
| 1002 | boot-dance | X: 990\nT:Boot dance\nR:Other\nP:Played with a... | Other |
| 1003 | the-girl-i-left-behind-me | X: 992\nT:The girl I left behind me\nT:The ram... | Other |
| 1004 | music-for-a-found-harmonium | X: 999\nT:Music for a found harmonium\nR:Other... | Other |
| 1005 | the-porcupine-bit | X: 1002\nT:The porcupine bit\nT:The 8 1/3p Bit... | Other |
1006 rows × 3 columns
df.group.value_counts()
group
Reel 427
Jig 257
Hornpipe 76
Polka 46
Slip Jig 38
Waltz 32
Strathspey 28
Slide 19
Other 18
March 17
Air 13
Single Jig 8
Set Dance 6
Highland Schottische 5
Barndance 3
Mazurka 2
Long Dance 1
Fling 1
Bourree 1
Jig or March 1
Schottische 1
Polka or March 1
Polka or Reel 1
Pipe Reel 1
Quadrille 1
Slow Air 1
Song 1
Name: count, dtype: int64
Tune(df.query("group == 'Jig'").iloc[0].abc)
Tune(title='The academy jig', key=Gmaj, type='Jig')
from IPython.display import display, Markdown
url = "https://michaeleskin.com/abctools/abctools.html?lzw=BoLgUAKiBiD2BOACCALApogMrAbhg8gGaICyArgM4CWAxmAEogUA2VADogFZUDmYAwiExUAXon4BDePFjNmYEiACcAegAcYTCACM6sAGkQAcTBGAogBFEFs0cQBBIwCFEAHwdG7zgCaI0333dzKxs7Rxo3RCc0DCd7F3MzRBBXMB5-PxVCFR4EpxUaFUDEdN80HgAjRAkAJmJ3Uszs3Id8wuL-F28nMKdAtIy0LJy8gqLIxvKq2olIipimnIxankjOxG7e+zdUoA"
display(Markdown(f"<{url}>"))
eskin.load_url(url)
Tune(title='For The Love Of Music', key=Gmaj, type='slip jig')
Bill Black¶
Bill Black has an extensive ABC library, available at http://www.capeirish.com/ittl/.
We can load all of the tune blocks (strings) with pyabc2.sources.bill_black.load_meta().
abcs = bill_black.load_meta()
len(abcs)
downloading...
done
10609
Tune(abcs[0])
Tune(title='A FEW GOOD MEN* (march)', key=Dmaj, type='march')
Hardy¶
Paul Hardy has a tunebook collection available at https://pghardy.net/tunebooks/. We can load selected tunebooks as a list of tune blocks (strings) with pyabc2.sources.hardy.load_meta().
abcs = hardy.load_meta("basic")
len(abcs)
downloading...
done
58
Tune(abcs[0])
Tune(title='Ash Grove, The', key=Gmaj, type='Waltz')