Quite often I find myself over at http://thesession.org checking out old traditional tunes. The site offers the tunes as sheet music and ABC Notation. While I certainly appreciate having ready access to the tunes, I find that, more often than not, I need to copy the ABC notation into a text file, convert the text file into a [lilypond[(http://lilypond.org) file, transpose the lilypond file to the G Major scale, and then finally add some code that will display the tune as banjo tabs. Oh man, did you catch all of that? There has to be an easier way to do this.....
Oh, there is...
For the most part, the process is just a series of text manipulations which can be accomplished in just about any programming language. Since I am currently on a Ruby kick, I figured I would just hammer out a quick script to automate the process of retrieving ABC notation from a given page and converting the results into something more usable.
Enter the Ruby
require 'optparse'
require 'open-uri'
require 'rexml/document'
require 'iconv'
def sanitize( string )
#replace non alpha-numerics with _
string = string.gsub(/[^A-z0-9_]/,'_')
return string.downcase
end
def error( string )
puts "**** ERROR ****"
puts string
exit
end
option = {}
OptionParser.new do |opts|
opts.banner = "Usage: sessionconvert [options] [thesession.org_URL]"
opts.on("-b", "--banjo", "add banjo tabulature") do |v|
option[:banjo] = v
end
opts.on("-l", "--lilypond", "compile with lilypond") do |v|
option[:lilypond] = v
end
end.parse!
url = ARGV[0]
# was a url supplied?
unless url
error "you must supply a thesession.org url"
end
begin
text = open(url).read
rescue
error "Failed to read #{url}"
end
begin
#watch out for sneaky
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
valid_string = ic.iconv(text)
doc = REXML::Document.new valid_string
rescue
error "Failed to parse #{url}"
end
root = doc.root
p = root.elements["body/div[@id='abc']/div[@class='box']/p"].to_s
abc = p.gsub(/<.*>/,'')
#get the title
m = /T: (?<title>.*)/.match( abc )
title = m['title']
#get the key
m = /K: (?<key>.*)/.match( abc )
key = m['key']
#sanitize the title, we will use this a bunch
sanitized_title = sanitize(title)
#what will the filenames be?
filename_abc = sanitized_title+".abc"
f = open(filename_abc,'w')
f.write(abc)
f.close
begin
`abc2ly #{filename_abc}`
rescue
error "abc2ly is not installed"
end
#what will the lilypond file be?
filename_ly = sanitized_title+".ly"
if option[:banjo]
#what is the key we are transposing from
t_key = key[0].downcase
banjer_bit = "\\transpose #{t_key} g,
\\context TabStaff = \"banjer\"{
\\set TabStaff.stringTunings = #banjo-open-g-tuning
\\set Staff.instrumentName = #\"Banjo\"
\\context TabVoice = \"banjer\" { \\tabFullNotation \\stemDown \\voicedefault }
}"
text = File::read(filename_ly)
#substitute the banjer stuff
text = text.gsub(/<<.*>>/m, "<<\n #{banjer_bit}\n\t>>")
#make a new lilypond file
filename_ly = sanitized_title+"-banjo.ly"
#write the new text to file
f = open(filename_ly,'w')
f.write(text)
f.close
end
#did the user request compiling
begin
`lilypond #{filename_ly}` if option[:lilypond]
rescue
error "lilypond is not installed"
end
If you have problems copying the code, please see http://hoof.jezra.net/snip/nQ
Usage
Since I don't always want to convert a tune to banjo tabs (some tunes are better on the concertina), a flag (-b) needs to be included with the arguments to specify that the tune should be converted to banjo tabs. Similarly, including the -l flag with compile the final lilypond file into a pdf of sheet music or tabs.
For example: The tune Rakes of Mallow is found at http://www.thesession.org/tunes/display/85 and to convert the tune to banjo tabs and compile to a PDF the following command would be used.
This is what the final PDF looks like. Damn that's nice!

...and this is me butchering the tune on my banjo. Damn, I need more practice.
Awesome! Now quit reading, and go butcher a tune on the instrument of your choice.