Melodeon.net Forums

Please login or register.

Login with username, password and session length
Advanced search  

News:

Welcome to the new melodeon.net forum

Pages: 1 [2]   Go Down

Author Topic: abcmelodeon - annotate abc tunes with button numbers  (Read 5295 times)

0 Members and 1 Guest are viewing this topic.

Roger Hare

  • Hero Member
  • *****
  • Offline Offline
  • Posts: 829
  • Urmston, Lancashire, U.K.
Re: abcmelodeon - annotate abc tunes with button numbers
« Reply #20 on: October 09, 2019, 05:40:47 AM »

...Is the source for your concertina program online anywhere? Would be interesting to compare the two....

Thanks for the reply - I hope you had a good holiday. My modded version of your script is included later.

I had to put this project down for 2 years due to a months-long hiatus in my domestic life. It has only
just risen to the top of the pile of delayed projects. I have the most recent version of the source from
Github.

You are discreetly modest - you have added the capability to recognise a wider range of 'pitch sets'
than in the original script - this allows the successful tabbing of all keys with 1-sharp, 2-sharps, etc.
This is brilliant. So far, I have been unable to do the same in my own (Icon) program (see later).

What I have done recently/am continuing to do:
(i) Managed to get Python working on my Windoze 10 machine. (ii) Used your script to tab a few tunes
for melodeon just to see how it works - what a very clever program! (iii) Hacked the script so it now
writes tabs(*) for a G/D Anglo. I have not done any re-programming, merely some hacking of your script.

Briefly (I hope I get this right!), a D/G melodeon has a low (D) row and a high (G) row. The G row
is a fourth higher than the D row. A G/D concertina has a low (G) row and a high (D) row.  The D row
is a fifth higher than the G row.

In your script, note/button allocations are defined, (as I suspected) in 'associative arrays'. It seemed
to me that by carefully re-writing those definitions, it should be possible to modify your script so that
it produced tabs for a G/D concertina. For the benefit of anyone who has looked at your script, I have
renamed the existing arrays: 'dRow' as 'gRow', and 'gRow' as 'dRow'  and then redefined the button/note
mappings.

I have also added a third array to hold the mappings for the accidentals row of a 30-button G/D
concertina. With stunning originality, I have called this 'aRow'.

I have also added a fourth array to hold a global mapping of one button/note mapping for
each note on the concertina In a moment of divinely-inspired inspiration, I called this array 'allRows'.

'allRows' provides a single line of tabs, without any duplication of notes/buttons (in some cases there
are three ways of getting the same note on a 30-button G/D Anglo). This is something I personally
rather like.

I have made only one minor change to the actual code in your Python script - namely to change the
default mapping to 'allRows'.

Yup! It works, though I haven't tested it to destruction yet (that's 'evasive-speak' for 'there are still
some glitches in my version') .

What I won't be doing:
Learning Python. I'll be sticking to hacking - as I said, I've merely hacked your data structures, I've
added no new code. I'm too old to learn a new programming language...

What I've also done:
Written my own progam (using the Icon programming language). I now have a working protototype.
I went a different route than you - for example, by design, my program deals (largely by passing them
unchanged) with:

(1) all non-music lines (ABC headers, comments (%), abcm2ps & abcmidi directives (%%), abcpp directives (#));
(2) all 'bounded strings', as I call them, namely: "..." (annotations, accompaniment chords),
!...! (decorations), [...] (in-line key/meter changes & multi-headed notes), {...} (grace notes);
'&...|' and &...: (voice overlay).
(3) a couple of other 'odds-and-sods' which escape me at 0620...

A very significant difference is that you write your tabs into the w: field of the ABC script, I add my
tabs as annotations placed immediately before the note in question. Both sets of tabs appear below
the staff, however.

What my program doesn't do is the biggie - namely deal properly with different keys. Currently, it only
tabs tunes in G and D (and associated keys in 1- and 2- sharps). Your program does work for a range
of keys, and I hope to be able to use it as a template for getting my program to do the same.

Here is your code as modified by me, but let's be quite clear it is still your program - all I have
done is a little hacking. The modified code also includes some comments to serve as an aide-memoire
when I come back to it:

Code: [Select]
#!/usr/bin/env python
#Add (concertina) button numbers or note names to ABC music files
#
#This Python script will add tabs in the w: field to ABC music files, consisting of the button number for
#each note with a ^ suffix for pull notes. This is basically the Australian Bush Traditions button numbering system.
#
#To use:
#
#Unix:
#./abcconcertina.py infile outfile
#
#Windoze:
#python abcconcertina.py infile outfile
#
#where infile and outfile are the input and output abc music files.
#
#The program takes an optional --mappings argument. This should be supplied with one or more "mappings":
#dRow, gRow, aRow (which will print ABT style tabs for a 30-button Wheatstone/Jefferies G/D concertina),
#or noteNames (which will print the names of each note).
#
#By default the acccidental, G and D row notes will be printed, which helps when trying to work out how to cross rows.
#
#For example, to annotate mytune.abc with G row and note names to the file mytune_out.abc:
#
#Unix:
#./abcconcertina.py --mappings gRow,noteNames mytune.abc mytune_out.abc
#
#Windoze:
#python abcconcertina.py --mappings gRow,noteNames mytune.abc mytune_out.abc
#
#(note no space between gRow and noteNames)

print "abcconcertina.py - v1.0 - September 2019."
print "abcconcertina.py adds concertina tabs for a 3-row G/D Anglo concertina to an existing ABC tune file."
print "The program is based on the Python script abcmelodeon.py (authored by melodeon.net user 'David')"
print "  which adds melodeon tabs for a 2-row D/G melodeon to an existing ABC tune file (August 2019)."
print "The conversion from D/G melodeon to G/D concertina was done by concertina.net user 'lachenal74693'."
print " "

import re
import argparse

# Regexs
# The key line
rxkey = re.compile(r'^K: ?(.+)$')
# a note
rxnote = re.compile(r'([\^_]?[a-gA-G][,\']*)|(".+?")' )
rxtunestart = re.compile(r'^[XT]:')
rxblankline = re.compile(r'^$')
rxfieldline = re.compile(r'^\w:')

notemappings = {}

#Mapping to provide single line of tabs for all rows - added by RJH.
notemappings["allRows"]= {"G,,":"L5",
        "D,":"L5^",
        "D,":"L5^/L4",
        "F,":"L4^",
        "^F,":"L4^",
        "G,":"L3",
        "A,":"L3^",
        "B,":"L2",
        "C":"L2^",
        "^C":"L2^",
        "D":"L1",
        "E":"L1^",
        "G":"R1",
        "F":"R1^",
        "^F":"R1^",
        "B":"R2",
        "A":"R2^",
        "=c":"R3^",
        "c":"R3^",
        "^c":"dR1^",
        "d":"dR1",     
        "f":"dR2s",
        "^f":"dR2",
        "e":"dR2^",
        "a":"dR3",
        "g":"dR3^",
        "d'":"dR4",
        "b":"dR4^",
        "f'":"dR5",
        "^f'":"dR5",
        "c'":"dR5^",
        "^c'":"dR5^"
        }

#Mapping for accidental row of G/D concertina - added by RJH.
notemappings["aRow"]= {"B,":"aL5",
        "=C,":"aL5^",
        "E,":"aL4",
        "=F,":"aL4^",
        "_A,":"aL3",
        "_B,":"aL3^",
        "E":"aL2",
        "D":"aL2^",
        "_E":"aL1",
        "=F":"aL1^",
        "_A":"aR1",
        "_B":"aR1^",
        "e":"aR2",
        "d":"aR2^",
        "_e":"aR3",
        "=f":"aR3^",
        "_a":"aR4",
        "_b":"aR4^",
        "e'":"aR5",
        "=c'":"aR5^"
        }

#Mapping for D-row re-labelled and modified for G-row concertina by RJH.
notemappings["gRow"]= {"G,,":"L5",
        "D,":"L5^",
        "D,":"L5^/L4",
        "F,":"L4^",
        "^F,":"L4^",
        "G,":"L3",
        "A,":"L3^",
        "B,":"L2",
        "C":"L2^",
        "^C":"L2^",
        "D":"L1",
        "E":"L1^",
        "G":"R1",
        "F":"R1^",
        "^F":"R1^",
        "B":"R2",
        "A":"R2^",
        "d":"R3",
        "c":"R3^",
        "^c":"dR1^",
        "g":"R4",
        "e":"R4^",
        "b":"R5",
        "f":"R5^",
        "^f":"R5^"
        }

#Mapping for G-row re-labelled and modified for D-row concertina by RJH.
notemappings["dRow"]= {"F,":"dL5",
        "A,":"dL5^",
        "A,":"dL4",
        "C":"dL4^",
        "^C":"dL4^",
        "D":"dL3",
        "E":"dL3^",
        "F":"dL2",
        "^F":"dL2s",
        "G":"dL2^",
        "A":"dL1",
        "B":"dL1^",
        "d":"dR1",
        "=c":"R3^",
        "c":"R3^",
        "^c":"dR1^",     
        "f":"dR2s",
        "^f":"dR2",
        "e":"dR2^",
        "a":"dR3",
        "g":"dR3^",
        "d'":"dR4",
        "b":"dR4^",
        "f'":"dR5",
        "^f'":"dR5",
        "c'":"dR5^",
        "^c'":"dR5^"
        }

# noteNames will annotate with the note letter
# we build this semi-programatically
notenames = ["A","B","C","D","E","F","G"]
notesflat = ["_" + n for n in notenames]
notessharp = [ "^" + n for n in notenames]

allnotenames = notenames + notesflat + notessharp
allnotesymbs = notenames + \
        [n + 'b' for n in notenames] + \
        [n + '#' for n in notenames]
   

notemappings["noteNames"] = dict(zip(allnotenames, allnotesymbs))
notemappings["noteNames"].update(dict(zip([n.lower() for n in allnotenames], allnotesymbs)))
notemappings["noteNames"].update(dict(zip([n + "," for n in allnotenames], allnotesymbs)))
notemappings["noteNames"].update(dict(zip([n + "'" for n in allnotenames], allnotesymbs)))

def getkey (infile):
    """ Read an abc file and extract the key """
    key = None
    for thisline in infile:
        m = rxkey.search(thisline)
        if m:
            if key is not None:
                raise ValueError("Multiple key lines found")
            key = m.group(1).strip()

    return key


def annotateabc (infile):
    """ Annotate an abc file with button numbers """
    print "Depreciated - use annotabc2"
    quit()
    try:
        key = getkey(infile)
    except ValueError as e:
        print e
        print infile
    notes = extractnotes(infile)

    newnotes = [[applykeysig(n, key=key) for n in nn] for nn in notes]

    notestrings = []
    for m in mappings:
        notestrings.append(getNoteString(newnotes, notemappings[m]))

    outabc = ''
    foundkey = False
    for line in infile:
        outabc += line
        if foundkey:
            for n in notestrings:
                print n
                if len(n) > 0:
                    outabc += ("w: " + n.pop(0) + "\n")
        if rxkey.search(line):
            foundkey = True

    return outabc

def stripdecoration(line):
    """ Strip everything in +s or !s """
    return re.sub('[!\+].+[!\+]', '', line)

def annotateabc2(inabc):
    """ Annotate an abc file with button numbers - take 2
    This assumes we're passing in a whole abc file """
    # TODO Handle inline key changes?

    currentkey = None
    outabc  = ''
    for line in inabc:
        outabc += line

        m = rxkey.search(line)
        if m:
            currentkey = m.group(1).strip()

        if re.match(r'^%', line):
            break
       
        if not rxfieldline.match(line): # On a noteline
            notes = []
            if currentkey is None:
                raise ValueError("Notes found before key has been set")
            # Extract the notes
            line = stripdecoration(line)
            linenotes = [i[0] for i in rxnote.findall(line)]
            # Remove blank notes - these are where a chord was
            # extracted instead
            justnotes = filter(None, linenotes)
            notes.append(justnotes)

            newnotes = [[applykeysig(n, key=currentkey) for n in nn] for nn in notes]
            notestrings = []
            for m in mappings:
                notestrings.append(getNoteString(newnotes, notemappings[m])) # list of button #s
           
            for n in notestrings:
                if len(n) > 0:
                    thesenotes = n.pop(0)
                    if len(thesenotes.strip()) >= 1:
                        outabc += ("w: " + thesenotes + "\n")


    return outabc



def readfile (infile):
    """ Read in a file """
    with open(infile) as file:
        content = file.readlines()

    return content

def extractnotes (infile):
    """ Extract the notes from the file.
    We read everything from the Key line until we get to an empty line.
    We don't care about note length"""
    notes = []
    foundbody = False
    for thisline in infile:
        if rxkey.search(thisline):
            foundbody = True
            continue
        if foundbody:
            linenotes = [i[0] for i in rxnote.findall(thisline)]
            # Remove blank notes - these are where a chord was
            # extracted instead
            justnotes = filter(None, linenotes)
            notes.append( justnotes )

    return notes

def extractabc(tunebook):
    """ Extract the tunes from a tunebook """
    # TODO handle comments, formatting etc.
    tunes = []
    thistune = []
    intune = False
    for thisline in tunebook:
        if rxtunestart.search(thisline) and not intune:
            thistune = []
            intune = True

        if intune:
            thistune.append(thisline)
           

        if thisline in ['\n', '\r\n']:
            if len(thistune) > 3:
                tunes.append(thistune)
            intune = False

    # If there's no blank line at the end,add the tune
    if intune:
        if len(thistune) > 3:
            tunes.append(thistune)
       

    return tunes

def applykeysig(note, key):
    """ Sharpen the appropriate notes
    Only implemented for G/D for now
    """
    # TODO Handle naturals
    # TODO handle other keys - presumably there's a clever way
    # of doing this if you know music theory
#    print key
#keys in the natural pitch set
    if key in ["C", "Cmaj", "Am", "AMin", "Amin", "GDor", "Gdor", "GMix", "Gmix", "DDor", "Ddor"]:
        if note.upper() == "F":
            return ("=" + note)
        return note
#keys in the pitch set 1-sharp
    if key in ["G", "Gmaj", "Em", "EMin", "Emin", "ADor", "Ador", "DMix", "Dmix", "EDor", "Edor"]:
        if note.upper() == "F":
            return ("^" + note)
        return note
#keys in the pitch set 2-sharps
    if key in ["D", "Dmaj", "Bm", "BMin", "Bmin", "EDor", "Edor", "AMix", "Amix"]:
        if note.upper() == "F":
            return ("^" + note)
        if note.upper() == "C":
            return ("^" + note)
        return note
#keys in the pitch set 3-sharps
    if key in ["A", "Amaj", "F#m", "F#Min", "F#Min", "BDor", "Bdor", "EMix", "Emix","C#Phr", "DLyd", "G#Loc"]:
        if note.upper() == "F":
            return ("^" + note)
        if note.upper() == "C":
            return ("^" + note)
        if note.upper() == "G":
            return ("^" + note)
        return note
#keys in the pitch set 1-flat
    if key in ["F", "Fmaj"]:
        if note.upper() == "B":
            return ("_" + note)
        return note
    else:
        # Just return a rest if we can't work out the key
        return "z"



def getNoteString(notes, notemap):
    mappednotes = [[notemap.get(x,"*") for x in y] for y in notes]

    notestring = [' '.join(x)   for x in mappednotes]

    return notestring

parser = argparse.ArgumentParser(description = "Add button numbers to abc file")
parser.add_argument("infile")
parser.add_argument("outfile")
#parser.add_argument("--mappings", default ="aRow,gRow,dRow", \
parser.add_argument("--mappings", default ="allRows", \
       help = "Comma separated list of note names/numbers to show. One or more of: " + \
        ','.join(list(notemappings.keys())))

args = parser.parse_args()
mappings = args.mappings.split(",")

for m in mappings:
    if m not in notemappings:
        print "Cannot find " + m + "in note mappings"
        print "mapping should be one or more of:"
        print ",".join(list(notemappings.keys()))
        quit()


abcfiles = readfile(args.infile)
abcbook = extractabc(abcfiles)
annotatedabc = []
for tune in abcbook:
    annotated = annotateabc2(tune)
    annotatedabc.append(annotated)

with open(args.outfile, "w") as file:
    for abc in annotatedabc:
        file.write(abc)
       
       

I'm aware of one other program (in Java) which does the same sort of thing...

This has the potential to become highly technical, and to bore the pants off other users. If you wish
to take it off-line, my electronic mail address is at the bottom of this post.

Roger.

(*) I have used the tabbing system outlined on the Australian Bush Traditions web-site:
https://www.bushtraditions.org/tutors/concertina.htm
« Last Edit: October 09, 2019, 07:28:28 AM by Roger Hare »
Logged
For more about Manchester Morris, The Beech Band Folk Club or anything else,  please use the private messaging facility.
My (large) ABC Tune Book is here.

Roger Hare

  • Hero Member
  • *****
  • Offline Offline
  • Posts: 829
  • Urmston, Lancashire, U.K.
Re: abcmelodeon - annotate abc tunes with button numbers
« Reply #21 on: October 10, 2019, 11:05:02 AM »

As an ear player, I'd be interested to see what this looks like in print
I presume you mean the printed score? If yes, I'll post an example of a score modified
with concertina tabs (along with the ABC), just to give you an idea of what it all looks
like...
Logged
For more about Manchester Morris, The Beech Band Folk Club or anything else,  please use the private messaging facility.
My (large) ABC Tune Book is here.

David

  • Member
  • *
  • Offline Offline
  • Posts: 9
Re: abcmelodeon - annotate abc tunes with button numbers
« Reply #22 on: October 11, 2019, 02:46:16 PM »

Quote
You are discreetly modest - you have added the capability to recognise a wider range of 'pitch sets'
than in the original script - this allows the successful tabbing of all keys with 1-sharp, 2-sharps, etc.
This is brilliant. So far, I have been unable to do the same in my own (Icon) program (see later).

Thanks for your kind words.  It *should* be possible to get it to work with any number of sharps / flats, but lacking the music theory to work this out (or an instrument to play them on  ;) ) I didn't get as far as working out how to implement this.

I wrote a rather longer reply, which I'll email you to, as I realised I'd probably strayed into highly technical and boring the pants off other users territory

David
Logged

Roger Hare

  • Hero Member
  • *****
  • Offline Offline
  • Posts: 829
  • Urmston, Lancashire, U.K.
Re: abcmelodeon - annotate abc tunes with button numbers
« Reply #23 on: October 11, 2019, 03:13:01 PM »

Thanks for your kind words.  It *should* be possible to get it to work with any number of sharps / flats, but
lacking the music theory to work this out (or an instrument to play them on  ;) ) I didn't get as far as working
out how to implement this.

I wrote a rather longer reply, which I'll email you to, as I realised I'd probably strayed into highly technical and
boring the pants off other users territory

Thanks! I did send an even techier PM, and also tried to send the code for my program
separately, but the PM for the code exceeded the maximum message length. I will wait
till I get your email before doing anything else. The email address is at the bottom of this
post. Roger.
Logged
For more about Manchester Morris, The Beech Band Folk Club or anything else,  please use the private messaging facility.
My (large) ABC Tune Book is here.
Pages: 1 [2]   Go Up
 


Melodeon.net - (c) Theo Gibb; Clive Williams 2010. The access and use of this website and forum featuring these terms and conditions constitutes your acceptance of these terms and conditions.
SimplePortal 2.3.5 © 2008-2012, SimplePortal