diff --git a/Session/MichaelTurnersWaltz.abc b/Session/MichaelTurnersWaltz.abc index 7abcd72..2aa2d11 100644 --- a/Session/MichaelTurnersWaltz.abc +++ b/Session/MichaelTurnersWaltz.abc @@ -1,11 +1,11 @@ X: 1 T: Michael Turner's Waltz +Z: Cry Havoc Booke of Dottes. Creative Commons by-nc-sa 2.0 UK licenced. M: 3/4 L: 1/8 R: waltz Q: 140 K: Gmaj -Z: Cry Havoc Booke of Dottes. Creative Commons by-nc-sa 2.0 UK licenced. DGA | "G" B2 B2 "D" c2 | "G" d4 gf | "C" e2>f2 ge | "G" d2>"D" D2 GA | "G" B2 B2 "D" c2 | "G" d2>e2 cA | "G" G3 B "D" AF | "G" G3 :| AB | "D" c2>d2 cB | "D" A4 Bc | "G" d2>e2 dc | "Em" B4 gf | diff --git a/Session/Railway.abc b/Session/Railway.abc index a5e8891..5b84466 100644 --- a/Session/Railway.abc +++ b/Session/Railway.abc @@ -1,10 +1,10 @@ X: 1 T: Railway, The +N: Change: FieryClockFace.abc M: 6/8 L: 1/8 Q: 160 K: Gmaj -N: Change: FieryClockFace.abc D | "G" G2 G GBd | g2 d d2 d | "Am" e2 d c2 B | "D7" ABA FED | "G" G2 G GBd | g2 d d2 d | "C" edc "G" BAG | "D7" A3 "G" G2 :| d | "G" b2 b b2 a | g2 g g2 d | "C" e2 d efg | "D" a2 g fed | diff --git a/Session/RoguesMarch.abc b/Session/RoguesMarch.abc index e80fe7a..c73317d 100644 --- a/Session/RoguesMarch.abc +++ b/Session/RoguesMarch.abc @@ -1,10 +1,10 @@ X: 1 T: Rogues' March +N: Change: CaptainLanoesQuickMarch.abc M: 6/8 L: 1/4 Q: 160 K: Gmaj -N: Change: CaptainLanoesQuickMarch.abc "G" B B/ B/c/d/ | "C" e e:+' *also* being a continuation. Note that a +# continuation is a distinct line in the field value; the value has a line +# break between it and the previous line. # import optparse @@ -87,7 +99,18 @@ accentedletters = { "ss" : ("ß", "\\ss"), } -def convertField(t, options): +abckeys = { + "m": "Minor", + "min": "Minor", + "mix": "Mixolydian", + "dor": "Dorian", + "phr": "Phrygian", + "lyd": "Lydian", + "loc": "Locrian", +} + +# Convert ABC accented chars to HTML entities or LaTex. +def convertAccents(t, latex=False): res = "" while True: p = t.partition('\\') @@ -97,64 +120,121 @@ def convertField(t, options): abc = p[2][0:2] t = p[2][2:] if abc in accentedletters: - if options.html: - res += accentedletters[abc][0] - else: + if latex: res += accentedletters[abc][1] + else: + res += accentedletters[abc][0] else: res += "\\" + abc return res -def process(inf, options): - n = options.index - found = False +# Convert Title fields from sort to display, so Bat, The->The Bat. +def convertTitleToDisplay(t): + p = t.rpartition(',') + if p[1] == "": + return t + else: + return p[2].strip() + " " + p[0].strip() + +# Convert Key field from ABC to display, so G#dor->G# Dorian. +def convertKeyToDisplay(t): + letter = t[0].upper() + accidental = "" + mode = "" + try: + accidental = t[1] + if accidental == '#' or accidental == 'b': + mode = t[2:] + else: + accidental = "" + mode = t[1:] + except IndexError: + pass + mode = mode.strip().lower() + return letter + accidental + ' ' + abckeys.get(mode, "Major") + +# Return the raw text for a given field. Optionally the nth field is taken, +# or the field data must start with a designated string to be recognised. +def getFieldText(inf, field, n = 1, starts = None): + res = "" for line in inf: line = line.strip() if len(line) > 2 and line[1] == ':': - if found: - if line[0] != '+': - break - line = line[2:].strip() - elif line[0] == options.field: - if n > 1: - n = n - 1 + if line[0] == "+" or (line[0] == field and line[2] == "+"): + if not res: continue + if line[0] == "+": + line = line[2:] else: - line = line[2:].strip() - if len(options.starts) > 0: - if line.find(options.starts) == 0: - line = line[len(options.starts):].strip() - else: - continue + line = line[3:] + res = res + '\n' + line.strip() else: - continue - found = True - print(convertField(line, options)) + if res: + break + if line[0] == field: + line = line[2:].strip() + if starts: + if line.find(starts) != 0: + continue + line = line[len(starts):].strip() + if n > 1: + n = n - 1 + continue + res = line + return res -parser = optparse.OptionParser(usage="usage: %prog [options] [filename]\n\n" - " Extract field data from ABC file.") -parser.add_option("-f", "--field", dest="field", default="T", - help="extract the field FIELD", metavar="FIELD") -parser.add_option("-l", "--latex", dest="latex", - action="store_true", default=False, - help="convert special characters for LaTeX") -parser.add_option("-n", "--index", dest="index", - action="store", type="int", default=1, - help="report INDEXth value [default: %default]", - metavar="INDEX") -parser.add_option("-s", "--starts", dest="starts", - action="store", type="string", default="", - help="report only if line starts CONTENT and remove CONTENT", - metavar="CONTENT") -(options, args) = parser.parse_args() +# Return display text for a given field. +def getFieldDisplayText(inf, field, n = 1, starts = None, latex = False): + res = getFieldText(inf, field, n, starts) + if res: + if field.upper() == "T": + res = convertTitleToDisplay(res) + elif field.upper() == "K": + res = convertKeyToDisplay(res) + res = convertAccents(res, latex) + return res -if len(args) > 0: - for arg in args: - try: - inf = open(arg, "r") - process(inf, options) - finally: - inf.close() -else: - process(sys.stdin, options) -sys.exit(0) +if __name__ == "__main__": + def process(inf, options): + if options.display: + line = getFieldDisplayText(inf, options.field, options.index, options.starts, options.latex) + else: + line = getFieldText(inf, options.field, options.index, options.starts) + if line: + print(line) + return True + else: + return False + + # execute only if run as a script + parser = optparse.OptionParser(usage="usage: %prog [options] [filename]\n\n" + " Extract field data from ABC file.") + parser.add_option("-f", "--field", dest="field", default="T", + help="extract the field FIELD", metavar="FIELD") + parser.add_option("-l", "--latex", dest="latex", + action="store_true", default=False, + help="convert special characters for LaTeX") + parser.add_option("-d", "--display", dest="display", + action="store_true", default=False, + help="convert to display text") + parser.add_option("-n", "--index", dest="index", + action="store", type="int", default=1, + help="report INDEXth value [default: %default]", + metavar="INDEX") + parser.add_option("-s", "--starts", dest="starts", + action="store", type="string", default=None, + help="report only if line starts CONTENT and remove CONTENT", + metavar="CONTENT") + (options, args) = parser.parse_args() + + res = False + if len(args) > 0: + for arg in args: + try: + inf = open(arg, "r") + res = res or process(inf, options) + finally: + inf.close() + else: + res = process(sys.stdin, options) + sys.exit(int(not res)) diff --git a/abcfirstline.py b/abcfirstline.py index ee06f21..f83a2c8 100755 --- a/abcfirstline.py +++ b/abcfirstline.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Write out a modified version of a .abc file with just the data # to print the first line of the music only. @@ -8,23 +8,23 @@ import sys def process(inf): continued = False - print "X:1" + print("X:1") for line in inf: line = line.strip() # If it is empty or starts "%", ignore it. if len(line) == 0 or line[0] == "%": continue - # Is it a header line? I.e. does it start LETTER COLON? + # Is it a header line? I.e. does it start LETTER (or +) COLON? # If so, output only ones we need. start = line[:2] - if len(start) > 1 and start[1] == ":" and start[0].isalpha(): + if len(start) > 1 and start[1] == ":" and (start[0].isalpha() or start[0] == '+'): if start[0] in ["M", "K", "L"]: - print line + print(line) # Output line. If it is a continuation, output at most one # continuation. else: - print line + print(line) if continued or line[-1] != "\\": break else: diff --git a/abcrange.py b/abcrange.py index 2c42e4f..834a579 100755 --- a/abcrange.py +++ b/abcrange.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Find the range of a tune. Do minimal parsing of an ABC input file # and print the lowest and highest notes therein. Accidentals are @@ -29,7 +29,7 @@ def process(filename, inf): # Is it a header line? I.e. does it start LETTER COLON? # If so, ignore. start = line[:2] - if len(start) > 1 and start[1] == ":" and start[0].isalpha(): + if len(start) > 1 and start[1] == ":" and (start[0].isalpha() or start[0] == '+'): continue # Tune line. @@ -82,7 +82,7 @@ def process(filename, inf): lowest = note note = 0 - print "{0}: {1} {2}".format(filename, highest, lowest) + print("{0}: {1} {2}".format(filename, highest, lowest)) if len(sys.argv) > 1: for arg in sys.argv[1:]: diff --git a/abctemplate.py b/abctemplate.py new file mode 100755 index 0000000..52e0be2 --- /dev/null +++ b/abctemplate.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# +# Fill in a template with data from fields in an ABC file. +# Fields have any ABC accented characters converted to HTML (default) or Latex. +# +# Rearrange some field contents into display format: +# * In Title fields, change 'sort' form such as 'Exploding Potato, The' +# to display format 'The Exploding Potato'. +# * In Key fields, translate the ABC key representation to full text, +# e.g. G#dor becomes G# Dorian. +# +# Recognise continuation header fields and print those too. The ABC standard +# defines continuation fields as starting ':+'. Regrettably none of the tools +# I am using the Booke recognise that syntax, so I am adopting a Booke +# convention of '
:+' *also* being a continuation. Note that a +# continuation is a distinct line in the field value; the value has a line +# break between it and the previous line. +# +# Templates are read from file, and are in Python standard library format. +# The following values are substituted: +# * name. The file base name. Base filename without extension. +# * title. The tune title. +# * subtitle. The tune subtitle (second Title field), if any. +# * composer. The tune composer. +# * key. The tune key. +# * changefile. The name of the 'change' file, if any. +# * changename. The change file base name. +# * changetitle. The change file tune title. +# * changevisibility. "yes" if there's a change value, otherwise "no". +# * credit. The 'credit' value. +# * creditvisibility. "yes" if there's a credit value, otherwise "no". +# + +import argparse +import pathlib +import string + +from abcfield import getFieldDisplayText + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Substitute values from ABC file into template.') + parser.add_argument('-l', '--latex', dest='latex', + action='store_true', + help='output LaTeX formatted values (default is HTML)') + parser.add_argument('-t', '--template', dest='template', + type=argparse.FileType('r'), + required=True, + help='template file') + parser.add_argument('-v', '--value', dest='values', action="append", + default=[], help='define var=value items for templater') + parser.add_argument('input', type=argparse.FileType('r'), + help='input ABC file') + args = parser.parse_args() + + with args.input as f: + lines = f.readlines() + + input_path = pathlib.Path(args.input.name) + + vars = dict() + vars["changename"] = "" + vars["changetitle"] = "" + vars["changevisibility"] = "no" + vars["creditvisibility"] = "no" + + vars["name"] = input_path.stem + vars["title"] = getFieldDisplayText(lines, "T", latex=args.latex) + vars["subtitle"] = getFieldDisplayText(lines, "T", n=2, latex=args.latex) + vars["composer"] = getFieldDisplayText(lines, "C", latex=args.latex) + vars["key"] = getFieldDisplayText(lines, "K", latex=args.latex) + vars["changefile"] = getFieldDisplayText(lines, "N", starts="Change:", latex=args.latex) + vars["credit"] = getFieldDisplayText(lines, "N", starts="Credit:", latex=args.latex) + + if vars["changefile"]: + vars["changevisibility"] = "yes" + vars["changename"] = pathlib.Path(vars["changefile"]).stem + cf = pathlib.Path(input_path.parent, vars["changefile"]) + with cf.open() as f: + vars["changetitle"] = getFieldDisplayText(f, "T", latex=args.latex) + + if vars["credit"]: + vars["creditvisibility"] = "yes" + + for val in args.values: + keyval = val.partition("=") + vars[keyval[0]] = keyval[2] + + print(string.Template(args.template.read()).substitute(vars)) diff --git a/dottes.html.learnertune b/dottes.html.learnertune index a1baff2..2b7045f 100644 --- a/dottes.html.learnertune +++ b/dottes.html.learnertune @@ -2,7 +2,7 @@ - Cry Havoc tunes - learning @TITLE@ + Cry Havoc tunes - learning ${title} @@ -33,7 +33,7 @@
- + Dottes @@ -43,15 +43,15 @@
-

@TITLE@

-

@SUBTITLE@

+

${title}

+

${subtitle}

- @COMPOSER@ + ${composer}
-

@TITLE@ is in the key of @KEY@. +

${title} is in the key of ${key}.