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",
    )
);
../_images/f101e0becaf8a692d120fde154ed08a9fe37d012530b26aa1b348a1163e7a9f5.png

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)

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')